mirror of
https://github.com/openai/codex.git
synced 2026-04-07 06:14:48 +00:00
Compare commits
3 Commits
mstar/remo
...
codex/clip
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
9f3986e6b4 | ||
|
|
24ce07949e | ||
|
|
e6d2da4716 |
1
codex-rs/Cargo.lock
generated
1
codex-rs/Cargo.lock
generated
@@ -1978,6 +1978,7 @@ dependencies = [
|
||||
"codex-app-server-protocol",
|
||||
"codex-apply-patch",
|
||||
"codex-arg0",
|
||||
"codex-backend-client",
|
||||
"codex-cloud-requirements",
|
||||
"codex-core",
|
||||
"codex-feedback",
|
||||
|
||||
@@ -147,6 +147,115 @@
|
||||
],
|
||||
"type": "object"
|
||||
},
|
||||
"CodexAvatarAdminAwardGrantParams": {
|
||||
"properties": {
|
||||
"accountUserId": {
|
||||
"type": "string"
|
||||
},
|
||||
"avatarId": {
|
||||
"type": "string"
|
||||
},
|
||||
"awardId": {
|
||||
"type": "string"
|
||||
},
|
||||
"awardedAt": {
|
||||
"format": "int64",
|
||||
"type": [
|
||||
"integer",
|
||||
"null"
|
||||
]
|
||||
},
|
||||
"awardedBy": {
|
||||
"type": [
|
||||
"string",
|
||||
"null"
|
||||
]
|
||||
},
|
||||
"metadataJson": {
|
||||
"type": [
|
||||
"string",
|
||||
"null"
|
||||
]
|
||||
},
|
||||
"sourceRef": {
|
||||
"type": [
|
||||
"string",
|
||||
"null"
|
||||
]
|
||||
},
|
||||
"sourceSummary": {
|
||||
"type": [
|
||||
"string",
|
||||
"null"
|
||||
]
|
||||
},
|
||||
"sourceType": {
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"accountUserId",
|
||||
"avatarId",
|
||||
"awardId",
|
||||
"sourceType"
|
||||
],
|
||||
"type": "object"
|
||||
},
|
||||
"CodexAvatarAdminCapabilitiesReadParams": {
|
||||
"type": "object"
|
||||
},
|
||||
"CodexAvatarAdminProofDropGrantParams": {
|
||||
"properties": {
|
||||
"accountUserId": {
|
||||
"type": "string"
|
||||
},
|
||||
"awardId": {
|
||||
"type": "string"
|
||||
},
|
||||
"awardedAt": {
|
||||
"format": "int64",
|
||||
"type": [
|
||||
"integer",
|
||||
"null"
|
||||
]
|
||||
},
|
||||
"sourceRef": {
|
||||
"type": [
|
||||
"string",
|
||||
"null"
|
||||
]
|
||||
},
|
||||
"sourceSummary": {
|
||||
"type": [
|
||||
"string",
|
||||
"null"
|
||||
]
|
||||
},
|
||||
"sourceType": {
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"accountUserId",
|
||||
"awardId",
|
||||
"sourceType"
|
||||
],
|
||||
"type": "object"
|
||||
},
|
||||
"CodexAvatarEquipParams": {
|
||||
"properties": {
|
||||
"avatarId": {
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"avatarId"
|
||||
],
|
||||
"type": "object"
|
||||
},
|
||||
"CodexAvatarInventoryReadParams": {
|
||||
"type": "object"
|
||||
},
|
||||
"CollaborationMode": {
|
||||
"description": "Collaboration mode for a Codex session.",
|
||||
"properties": {
|
||||
@@ -4538,6 +4647,126 @@
|
||||
"title": "Account/rateLimits/readRequest",
|
||||
"type": "object"
|
||||
},
|
||||
{
|
||||
"properties": {
|
||||
"id": {
|
||||
"$ref": "#/definitions/RequestId"
|
||||
},
|
||||
"method": {
|
||||
"enum": [
|
||||
"avatar/inventory/read"
|
||||
],
|
||||
"title": "Avatar/inventory/readRequestMethod",
|
||||
"type": "string"
|
||||
},
|
||||
"params": {
|
||||
"$ref": "#/definitions/CodexAvatarInventoryReadParams"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"id",
|
||||
"method",
|
||||
"params"
|
||||
],
|
||||
"title": "Avatar/inventory/readRequest",
|
||||
"type": "object"
|
||||
},
|
||||
{
|
||||
"properties": {
|
||||
"id": {
|
||||
"$ref": "#/definitions/RequestId"
|
||||
},
|
||||
"method": {
|
||||
"enum": [
|
||||
"avatar/equip"
|
||||
],
|
||||
"title": "Avatar/equipRequestMethod",
|
||||
"type": "string"
|
||||
},
|
||||
"params": {
|
||||
"$ref": "#/definitions/CodexAvatarEquipParams"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"id",
|
||||
"method",
|
||||
"params"
|
||||
],
|
||||
"title": "Avatar/equipRequest",
|
||||
"type": "object"
|
||||
},
|
||||
{
|
||||
"properties": {
|
||||
"id": {
|
||||
"$ref": "#/definitions/RequestId"
|
||||
},
|
||||
"method": {
|
||||
"enum": [
|
||||
"avatar/admin/award"
|
||||
],
|
||||
"title": "Avatar/admin/awardRequestMethod",
|
||||
"type": "string"
|
||||
},
|
||||
"params": {
|
||||
"$ref": "#/definitions/CodexAvatarAdminAwardGrantParams"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"id",
|
||||
"method",
|
||||
"params"
|
||||
],
|
||||
"title": "Avatar/admin/awardRequest",
|
||||
"type": "object"
|
||||
},
|
||||
{
|
||||
"properties": {
|
||||
"id": {
|
||||
"$ref": "#/definitions/RequestId"
|
||||
},
|
||||
"method": {
|
||||
"enum": [
|
||||
"avatar/admin/proof-drop"
|
||||
],
|
||||
"title": "Avatar/admin/proofDropRequestMethod",
|
||||
"type": "string"
|
||||
},
|
||||
"params": {
|
||||
"$ref": "#/definitions/CodexAvatarAdminProofDropGrantParams"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"id",
|
||||
"method",
|
||||
"params"
|
||||
],
|
||||
"title": "Avatar/admin/proofDropRequest",
|
||||
"type": "object"
|
||||
},
|
||||
{
|
||||
"properties": {
|
||||
"id": {
|
||||
"$ref": "#/definitions/RequestId"
|
||||
},
|
||||
"method": {
|
||||
"enum": [
|
||||
"avatar/admin/capabilities/read"
|
||||
],
|
||||
"title": "Avatar/admin/capabilities/readRequestMethod",
|
||||
"type": "string"
|
||||
},
|
||||
"params": {
|
||||
"$ref": "#/definitions/CodexAvatarAdminCapabilitiesReadParams"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"id",
|
||||
"method",
|
||||
"params"
|
||||
],
|
||||
"title": "Avatar/admin/capabilities/readRequest",
|
||||
"type": "object"
|
||||
},
|
||||
{
|
||||
"properties": {
|
||||
"id": {
|
||||
|
||||
@@ -0,0 +1,54 @@
|
||||
{
|
||||
"$schema": "http://json-schema.org/draft-07/schema#",
|
||||
"properties": {
|
||||
"accountUserId": {
|
||||
"type": "string"
|
||||
},
|
||||
"avatarId": {
|
||||
"type": "string"
|
||||
},
|
||||
"awardId": {
|
||||
"type": "string"
|
||||
},
|
||||
"awardedAt": {
|
||||
"format": "int64",
|
||||
"type": "integer"
|
||||
},
|
||||
"awardedBy": {
|
||||
"type": [
|
||||
"string",
|
||||
"null"
|
||||
]
|
||||
},
|
||||
"metadataJson": {
|
||||
"type": [
|
||||
"string",
|
||||
"null"
|
||||
]
|
||||
},
|
||||
"sourceRef": {
|
||||
"type": [
|
||||
"string",
|
||||
"null"
|
||||
]
|
||||
},
|
||||
"sourceSummary": {
|
||||
"type": [
|
||||
"string",
|
||||
"null"
|
||||
]
|
||||
},
|
||||
"sourceType": {
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"accountUserId",
|
||||
"avatarId",
|
||||
"awardId",
|
||||
"awardedAt",
|
||||
"sourceType"
|
||||
],
|
||||
"title": "CodexAvatarAward",
|
||||
"type": "object"
|
||||
}
|
||||
@@ -1319,6 +1319,126 @@
|
||||
"title": "Account/rateLimits/readRequest",
|
||||
"type": "object"
|
||||
},
|
||||
{
|
||||
"properties": {
|
||||
"id": {
|
||||
"$ref": "#/definitions/v2/RequestId"
|
||||
},
|
||||
"method": {
|
||||
"enum": [
|
||||
"avatar/inventory/read"
|
||||
],
|
||||
"title": "Avatar/inventory/readRequestMethod",
|
||||
"type": "string"
|
||||
},
|
||||
"params": {
|
||||
"$ref": "#/definitions/v2/CodexAvatarInventoryReadParams"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"id",
|
||||
"method",
|
||||
"params"
|
||||
],
|
||||
"title": "Avatar/inventory/readRequest",
|
||||
"type": "object"
|
||||
},
|
||||
{
|
||||
"properties": {
|
||||
"id": {
|
||||
"$ref": "#/definitions/v2/RequestId"
|
||||
},
|
||||
"method": {
|
||||
"enum": [
|
||||
"avatar/equip"
|
||||
],
|
||||
"title": "Avatar/equipRequestMethod",
|
||||
"type": "string"
|
||||
},
|
||||
"params": {
|
||||
"$ref": "#/definitions/v2/CodexAvatarEquipParams"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"id",
|
||||
"method",
|
||||
"params"
|
||||
],
|
||||
"title": "Avatar/equipRequest",
|
||||
"type": "object"
|
||||
},
|
||||
{
|
||||
"properties": {
|
||||
"id": {
|
||||
"$ref": "#/definitions/v2/RequestId"
|
||||
},
|
||||
"method": {
|
||||
"enum": [
|
||||
"avatar/admin/award"
|
||||
],
|
||||
"title": "Avatar/admin/awardRequestMethod",
|
||||
"type": "string"
|
||||
},
|
||||
"params": {
|
||||
"$ref": "#/definitions/v2/CodexAvatarAdminAwardGrantParams"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"id",
|
||||
"method",
|
||||
"params"
|
||||
],
|
||||
"title": "Avatar/admin/awardRequest",
|
||||
"type": "object"
|
||||
},
|
||||
{
|
||||
"properties": {
|
||||
"id": {
|
||||
"$ref": "#/definitions/v2/RequestId"
|
||||
},
|
||||
"method": {
|
||||
"enum": [
|
||||
"avatar/admin/proof-drop"
|
||||
],
|
||||
"title": "Avatar/admin/proofDropRequestMethod",
|
||||
"type": "string"
|
||||
},
|
||||
"params": {
|
||||
"$ref": "#/definitions/v2/CodexAvatarAdminProofDropGrantParams"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"id",
|
||||
"method",
|
||||
"params"
|
||||
],
|
||||
"title": "Avatar/admin/proofDropRequest",
|
||||
"type": "object"
|
||||
},
|
||||
{
|
||||
"properties": {
|
||||
"id": {
|
||||
"$ref": "#/definitions/v2/RequestId"
|
||||
},
|
||||
"method": {
|
||||
"enum": [
|
||||
"avatar/admin/capabilities/read"
|
||||
],
|
||||
"title": "Avatar/admin/capabilities/readRequestMethod",
|
||||
"type": "string"
|
||||
},
|
||||
"params": {
|
||||
"$ref": "#/definitions/v2/CodexAvatarAdminCapabilitiesReadParams"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"id",
|
||||
"method",
|
||||
"params"
|
||||
],
|
||||
"title": "Avatar/admin/capabilities/readRequest",
|
||||
"type": "object"
|
||||
},
|
||||
{
|
||||
"properties": {
|
||||
"id": {
|
||||
@@ -1637,6 +1757,60 @@
|
||||
],
|
||||
"title": "ClientRequest"
|
||||
},
|
||||
"CodexAvatarAward": {
|
||||
"$schema": "http://json-schema.org/draft-07/schema#",
|
||||
"properties": {
|
||||
"accountUserId": {
|
||||
"type": "string"
|
||||
},
|
||||
"avatarId": {
|
||||
"type": "string"
|
||||
},
|
||||
"awardId": {
|
||||
"type": "string"
|
||||
},
|
||||
"awardedAt": {
|
||||
"format": "int64",
|
||||
"type": "integer"
|
||||
},
|
||||
"awardedBy": {
|
||||
"type": [
|
||||
"string",
|
||||
"null"
|
||||
]
|
||||
},
|
||||
"metadataJson": {
|
||||
"type": [
|
||||
"string",
|
||||
"null"
|
||||
]
|
||||
},
|
||||
"sourceRef": {
|
||||
"type": [
|
||||
"string",
|
||||
"null"
|
||||
]
|
||||
},
|
||||
"sourceSummary": {
|
||||
"type": [
|
||||
"string",
|
||||
"null"
|
||||
]
|
||||
},
|
||||
"sourceType": {
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"accountUserId",
|
||||
"avatarId",
|
||||
"awardId",
|
||||
"awardedAt",
|
||||
"sourceType"
|
||||
],
|
||||
"title": "CodexAvatarAward",
|
||||
"type": "object"
|
||||
},
|
||||
"CommandExecutionApprovalDecision": {
|
||||
"oneOf": [
|
||||
{
|
||||
@@ -5553,6 +5727,426 @@
|
||||
],
|
||||
"type": "string"
|
||||
},
|
||||
"CodexAvatarAdminAwardGrantParams": {
|
||||
"$schema": "http://json-schema.org/draft-07/schema#",
|
||||
"properties": {
|
||||
"accountUserId": {
|
||||
"type": "string"
|
||||
},
|
||||
"avatarId": {
|
||||
"type": "string"
|
||||
},
|
||||
"awardId": {
|
||||
"type": "string"
|
||||
},
|
||||
"awardedAt": {
|
||||
"format": "int64",
|
||||
"type": [
|
||||
"integer",
|
||||
"null"
|
||||
]
|
||||
},
|
||||
"awardedBy": {
|
||||
"type": [
|
||||
"string",
|
||||
"null"
|
||||
]
|
||||
},
|
||||
"metadataJson": {
|
||||
"type": [
|
||||
"string",
|
||||
"null"
|
||||
]
|
||||
},
|
||||
"sourceRef": {
|
||||
"type": [
|
||||
"string",
|
||||
"null"
|
||||
]
|
||||
},
|
||||
"sourceSummary": {
|
||||
"type": [
|
||||
"string",
|
||||
"null"
|
||||
]
|
||||
},
|
||||
"sourceType": {
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"accountUserId",
|
||||
"avatarId",
|
||||
"awardId",
|
||||
"sourceType"
|
||||
],
|
||||
"title": "CodexAvatarAdminAwardGrantParams",
|
||||
"type": "object"
|
||||
},
|
||||
"CodexAvatarAdminCapabilitiesReadParams": {
|
||||
"$schema": "http://json-schema.org/draft-07/schema#",
|
||||
"title": "CodexAvatarAdminCapabilitiesReadParams",
|
||||
"type": "object"
|
||||
},
|
||||
"CodexAvatarAdminCapabilitiesReadResponse": {
|
||||
"$schema": "http://json-schema.org/draft-07/schema#",
|
||||
"properties": {
|
||||
"canGrantAwards": {
|
||||
"type": "boolean"
|
||||
},
|
||||
"canGrantProofDropBoxes": {
|
||||
"type": "boolean"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"canGrantAwards",
|
||||
"canGrantProofDropBoxes"
|
||||
],
|
||||
"title": "CodexAvatarAdminCapabilitiesReadResponse",
|
||||
"type": "object"
|
||||
},
|
||||
"CodexAvatarAdminProofDropGrantParams": {
|
||||
"$schema": "http://json-schema.org/draft-07/schema#",
|
||||
"properties": {
|
||||
"accountUserId": {
|
||||
"type": "string"
|
||||
},
|
||||
"awardId": {
|
||||
"type": "string"
|
||||
},
|
||||
"awardedAt": {
|
||||
"format": "int64",
|
||||
"type": [
|
||||
"integer",
|
||||
"null"
|
||||
]
|
||||
},
|
||||
"sourceRef": {
|
||||
"type": [
|
||||
"string",
|
||||
"null"
|
||||
]
|
||||
},
|
||||
"sourceSummary": {
|
||||
"type": [
|
||||
"string",
|
||||
"null"
|
||||
]
|
||||
},
|
||||
"sourceType": {
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"accountUserId",
|
||||
"awardId",
|
||||
"sourceType"
|
||||
],
|
||||
"title": "CodexAvatarAdminProofDropGrantParams",
|
||||
"type": "object"
|
||||
},
|
||||
"CodexAvatarBoxOddsBucket": {
|
||||
"properties": {
|
||||
"bucketId": {
|
||||
"type": "string"
|
||||
},
|
||||
"label": {
|
||||
"type": "string"
|
||||
},
|
||||
"probabilityPercent": {
|
||||
"format": "int64",
|
||||
"type": "integer"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"bucketId",
|
||||
"label",
|
||||
"probabilityPercent"
|
||||
],
|
||||
"type": "object"
|
||||
},
|
||||
"CodexAvatarBoxRules": {
|
||||
"properties": {
|
||||
"guaranteedNewThreshold": {
|
||||
"format": "int64",
|
||||
"type": "integer"
|
||||
},
|
||||
"legendaryPityThreshold": {
|
||||
"format": "int64",
|
||||
"type": "integer"
|
||||
},
|
||||
"odds": {
|
||||
"items": {
|
||||
"$ref": "#/definitions/v2/CodexAvatarBoxOddsBucket"
|
||||
},
|
||||
"type": "array"
|
||||
},
|
||||
"oddsTableVersion": {
|
||||
"type": "string"
|
||||
},
|
||||
"rareOrBetterPityThreshold": {
|
||||
"format": "int64",
|
||||
"type": "integer"
|
||||
},
|
||||
"rulesetVersion": {
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"guaranteedNewThreshold",
|
||||
"legendaryPityThreshold",
|
||||
"odds",
|
||||
"oddsTableVersion",
|
||||
"rareOrBetterPityThreshold",
|
||||
"rulesetVersion"
|
||||
],
|
||||
"type": "object"
|
||||
},
|
||||
"CodexAvatarDefinition": {
|
||||
"properties": {
|
||||
"accentClassName": {
|
||||
"type": "string"
|
||||
},
|
||||
"assetRef": {
|
||||
"type": "string"
|
||||
},
|
||||
"avatarId": {
|
||||
"type": "string"
|
||||
},
|
||||
"collectionDescription": {
|
||||
"type": "string"
|
||||
},
|
||||
"collectionName": {
|
||||
"type": "string"
|
||||
},
|
||||
"description": {
|
||||
"type": "string"
|
||||
},
|
||||
"displayName": {
|
||||
"type": "string"
|
||||
},
|
||||
"isProgressVisible": {
|
||||
"type": "boolean"
|
||||
},
|
||||
"lore": {
|
||||
"type": "string"
|
||||
},
|
||||
"rarity": {
|
||||
"$ref": "#/definitions/v2/CodexAvatarRarity"
|
||||
},
|
||||
"silhouetteGlowClassName": {
|
||||
"type": "string"
|
||||
},
|
||||
"sortOrder": {
|
||||
"format": "int64",
|
||||
"type": "integer"
|
||||
},
|
||||
"status": {
|
||||
"$ref": "#/definitions/v2/CodexAvatarStatus"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"accentClassName",
|
||||
"assetRef",
|
||||
"avatarId",
|
||||
"collectionDescription",
|
||||
"collectionName",
|
||||
"description",
|
||||
"displayName",
|
||||
"isProgressVisible",
|
||||
"lore",
|
||||
"rarity",
|
||||
"silhouetteGlowClassName",
|
||||
"sortOrder",
|
||||
"status"
|
||||
],
|
||||
"type": "object"
|
||||
},
|
||||
"CodexAvatarEquipParams": {
|
||||
"$schema": "http://json-schema.org/draft-07/schema#",
|
||||
"properties": {
|
||||
"avatarId": {
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"avatarId"
|
||||
],
|
||||
"title": "CodexAvatarEquipParams",
|
||||
"type": "object"
|
||||
},
|
||||
"CodexAvatarInventoryReadParams": {
|
||||
"$schema": "http://json-schema.org/draft-07/schema#",
|
||||
"title": "CodexAvatarInventoryReadParams",
|
||||
"type": "object"
|
||||
},
|
||||
"CodexAvatarInventoryReadResponse": {
|
||||
"$schema": "http://json-schema.org/draft-07/schema#",
|
||||
"properties": {
|
||||
"accountUserId": {
|
||||
"type": "string"
|
||||
},
|
||||
"avatarDefinitions": {
|
||||
"items": {
|
||||
"$ref": "#/definitions/v2/CodexAvatarDefinition"
|
||||
},
|
||||
"type": "array"
|
||||
},
|
||||
"boxRules": {
|
||||
"$ref": "#/definitions/v2/CodexAvatarBoxRules"
|
||||
},
|
||||
"equippedAvatarId": {
|
||||
"type": "string"
|
||||
},
|
||||
"ownedAvatars": {
|
||||
"items": {
|
||||
"$ref": "#/definitions/v2/CodexAvatarOwnership"
|
||||
},
|
||||
"type": "array"
|
||||
},
|
||||
"pendingRevealAwards": {
|
||||
"items": {
|
||||
"$ref": "#/definitions/v2/CodexAvatarRevealAward"
|
||||
},
|
||||
"type": "array"
|
||||
},
|
||||
"pityState": {
|
||||
"$ref": "#/definitions/v2/CodexAvatarPityState"
|
||||
},
|
||||
"updatedAt": {
|
||||
"format": "int64",
|
||||
"type": "integer"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"accountUserId",
|
||||
"avatarDefinitions",
|
||||
"boxRules",
|
||||
"equippedAvatarId",
|
||||
"ownedAvatars",
|
||||
"pendingRevealAwards",
|
||||
"pityState",
|
||||
"updatedAt"
|
||||
],
|
||||
"title": "CodexAvatarInventoryReadResponse",
|
||||
"type": "object"
|
||||
},
|
||||
"CodexAvatarOwnership": {
|
||||
"properties": {
|
||||
"accountUserId": {
|
||||
"type": "string"
|
||||
},
|
||||
"avatarId": {
|
||||
"type": "string"
|
||||
},
|
||||
"sourceSummary": {
|
||||
"type": [
|
||||
"string",
|
||||
"null"
|
||||
]
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"accountUserId",
|
||||
"avatarId"
|
||||
],
|
||||
"type": "object"
|
||||
},
|
||||
"CodexAvatarPityState": {
|
||||
"properties": {
|
||||
"guaranteedNewAvailable": {
|
||||
"type": "boolean"
|
||||
},
|
||||
"nonNewOutcomeStreak": {
|
||||
"format": "int64",
|
||||
"type": "integer"
|
||||
},
|
||||
"rollsSinceLegendary": {
|
||||
"format": "int64",
|
||||
"type": "integer"
|
||||
},
|
||||
"rollsSinceRareOrBetter": {
|
||||
"format": "int64",
|
||||
"type": "integer"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"guaranteedNewAvailable",
|
||||
"nonNewOutcomeStreak",
|
||||
"rollsSinceLegendary",
|
||||
"rollsSinceRareOrBetter"
|
||||
],
|
||||
"type": "object"
|
||||
},
|
||||
"CodexAvatarRarity": {
|
||||
"enum": [
|
||||
"common",
|
||||
"rare",
|
||||
"epic",
|
||||
"legendary"
|
||||
],
|
||||
"type": "string"
|
||||
},
|
||||
"CodexAvatarRevealAward": {
|
||||
"properties": {
|
||||
"awardId": {
|
||||
"type": "string"
|
||||
},
|
||||
"awardedAt": {
|
||||
"format": "int64",
|
||||
"type": "integer"
|
||||
},
|
||||
"metadataJson": {
|
||||
"type": [
|
||||
"string",
|
||||
"null"
|
||||
]
|
||||
},
|
||||
"outcomeAvatarId": {
|
||||
"type": [
|
||||
"string",
|
||||
"null"
|
||||
]
|
||||
},
|
||||
"outcomeKind": {
|
||||
"type": "string"
|
||||
},
|
||||
"pityStateAfter": {
|
||||
"$ref": "#/definitions/v2/CodexAvatarPityState"
|
||||
},
|
||||
"sourceRef": {
|
||||
"type": [
|
||||
"string",
|
||||
"null"
|
||||
]
|
||||
},
|
||||
"sourceSummary": {
|
||||
"type": [
|
||||
"string",
|
||||
"null"
|
||||
]
|
||||
},
|
||||
"sourceType": {
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"awardId",
|
||||
"awardedAt",
|
||||
"outcomeKind",
|
||||
"pityStateAfter",
|
||||
"sourceType"
|
||||
],
|
||||
"type": "object"
|
||||
},
|
||||
"CodexAvatarStatus": {
|
||||
"enum": [
|
||||
"active",
|
||||
"hidden",
|
||||
"retired"
|
||||
],
|
||||
"type": "string"
|
||||
},
|
||||
"CodexErrorInfo": {
|
||||
"description": "This translation layer make sure that we expose codex error code in camel case.\n\nWhen an upstream HTTP status is available (for example, from the Responses API or a provider), it is forwarded in `httpStatusCode` on the relevant `codexErrorInfo` variant.",
|
||||
"oneOf": [
|
||||
|
||||
@@ -1894,6 +1894,126 @@
|
||||
"title": "Account/rateLimits/readRequest",
|
||||
"type": "object"
|
||||
},
|
||||
{
|
||||
"properties": {
|
||||
"id": {
|
||||
"$ref": "#/definitions/RequestId"
|
||||
},
|
||||
"method": {
|
||||
"enum": [
|
||||
"avatar/inventory/read"
|
||||
],
|
||||
"title": "Avatar/inventory/readRequestMethod",
|
||||
"type": "string"
|
||||
},
|
||||
"params": {
|
||||
"$ref": "#/definitions/CodexAvatarInventoryReadParams"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"id",
|
||||
"method",
|
||||
"params"
|
||||
],
|
||||
"title": "Avatar/inventory/readRequest",
|
||||
"type": "object"
|
||||
},
|
||||
{
|
||||
"properties": {
|
||||
"id": {
|
||||
"$ref": "#/definitions/RequestId"
|
||||
},
|
||||
"method": {
|
||||
"enum": [
|
||||
"avatar/equip"
|
||||
],
|
||||
"title": "Avatar/equipRequestMethod",
|
||||
"type": "string"
|
||||
},
|
||||
"params": {
|
||||
"$ref": "#/definitions/CodexAvatarEquipParams"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"id",
|
||||
"method",
|
||||
"params"
|
||||
],
|
||||
"title": "Avatar/equipRequest",
|
||||
"type": "object"
|
||||
},
|
||||
{
|
||||
"properties": {
|
||||
"id": {
|
||||
"$ref": "#/definitions/RequestId"
|
||||
},
|
||||
"method": {
|
||||
"enum": [
|
||||
"avatar/admin/award"
|
||||
],
|
||||
"title": "Avatar/admin/awardRequestMethod",
|
||||
"type": "string"
|
||||
},
|
||||
"params": {
|
||||
"$ref": "#/definitions/CodexAvatarAdminAwardGrantParams"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"id",
|
||||
"method",
|
||||
"params"
|
||||
],
|
||||
"title": "Avatar/admin/awardRequest",
|
||||
"type": "object"
|
||||
},
|
||||
{
|
||||
"properties": {
|
||||
"id": {
|
||||
"$ref": "#/definitions/RequestId"
|
||||
},
|
||||
"method": {
|
||||
"enum": [
|
||||
"avatar/admin/proof-drop"
|
||||
],
|
||||
"title": "Avatar/admin/proofDropRequestMethod",
|
||||
"type": "string"
|
||||
},
|
||||
"params": {
|
||||
"$ref": "#/definitions/CodexAvatarAdminProofDropGrantParams"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"id",
|
||||
"method",
|
||||
"params"
|
||||
],
|
||||
"title": "Avatar/admin/proofDropRequest",
|
||||
"type": "object"
|
||||
},
|
||||
{
|
||||
"properties": {
|
||||
"id": {
|
||||
"$ref": "#/definitions/RequestId"
|
||||
},
|
||||
"method": {
|
||||
"enum": [
|
||||
"avatar/admin/capabilities/read"
|
||||
],
|
||||
"title": "Avatar/admin/capabilities/readRequestMethod",
|
||||
"type": "string"
|
||||
},
|
||||
"params": {
|
||||
"$ref": "#/definitions/CodexAvatarAdminCapabilitiesReadParams"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"id",
|
||||
"method",
|
||||
"params"
|
||||
],
|
||||
"title": "Avatar/admin/capabilities/readRequest",
|
||||
"type": "object"
|
||||
},
|
||||
{
|
||||
"properties": {
|
||||
"id": {
|
||||
@@ -2212,6 +2332,426 @@
|
||||
],
|
||||
"title": "ClientRequest"
|
||||
},
|
||||
"CodexAvatarAdminAwardGrantParams": {
|
||||
"$schema": "http://json-schema.org/draft-07/schema#",
|
||||
"properties": {
|
||||
"accountUserId": {
|
||||
"type": "string"
|
||||
},
|
||||
"avatarId": {
|
||||
"type": "string"
|
||||
},
|
||||
"awardId": {
|
||||
"type": "string"
|
||||
},
|
||||
"awardedAt": {
|
||||
"format": "int64",
|
||||
"type": [
|
||||
"integer",
|
||||
"null"
|
||||
]
|
||||
},
|
||||
"awardedBy": {
|
||||
"type": [
|
||||
"string",
|
||||
"null"
|
||||
]
|
||||
},
|
||||
"metadataJson": {
|
||||
"type": [
|
||||
"string",
|
||||
"null"
|
||||
]
|
||||
},
|
||||
"sourceRef": {
|
||||
"type": [
|
||||
"string",
|
||||
"null"
|
||||
]
|
||||
},
|
||||
"sourceSummary": {
|
||||
"type": [
|
||||
"string",
|
||||
"null"
|
||||
]
|
||||
},
|
||||
"sourceType": {
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"accountUserId",
|
||||
"avatarId",
|
||||
"awardId",
|
||||
"sourceType"
|
||||
],
|
||||
"title": "CodexAvatarAdminAwardGrantParams",
|
||||
"type": "object"
|
||||
},
|
||||
"CodexAvatarAdminCapabilitiesReadParams": {
|
||||
"$schema": "http://json-schema.org/draft-07/schema#",
|
||||
"title": "CodexAvatarAdminCapabilitiesReadParams",
|
||||
"type": "object"
|
||||
},
|
||||
"CodexAvatarAdminCapabilitiesReadResponse": {
|
||||
"$schema": "http://json-schema.org/draft-07/schema#",
|
||||
"properties": {
|
||||
"canGrantAwards": {
|
||||
"type": "boolean"
|
||||
},
|
||||
"canGrantProofDropBoxes": {
|
||||
"type": "boolean"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"canGrantAwards",
|
||||
"canGrantProofDropBoxes"
|
||||
],
|
||||
"title": "CodexAvatarAdminCapabilitiesReadResponse",
|
||||
"type": "object"
|
||||
},
|
||||
"CodexAvatarAdminProofDropGrantParams": {
|
||||
"$schema": "http://json-schema.org/draft-07/schema#",
|
||||
"properties": {
|
||||
"accountUserId": {
|
||||
"type": "string"
|
||||
},
|
||||
"awardId": {
|
||||
"type": "string"
|
||||
},
|
||||
"awardedAt": {
|
||||
"format": "int64",
|
||||
"type": [
|
||||
"integer",
|
||||
"null"
|
||||
]
|
||||
},
|
||||
"sourceRef": {
|
||||
"type": [
|
||||
"string",
|
||||
"null"
|
||||
]
|
||||
},
|
||||
"sourceSummary": {
|
||||
"type": [
|
||||
"string",
|
||||
"null"
|
||||
]
|
||||
},
|
||||
"sourceType": {
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"accountUserId",
|
||||
"awardId",
|
||||
"sourceType"
|
||||
],
|
||||
"title": "CodexAvatarAdminProofDropGrantParams",
|
||||
"type": "object"
|
||||
},
|
||||
"CodexAvatarBoxOddsBucket": {
|
||||
"properties": {
|
||||
"bucketId": {
|
||||
"type": "string"
|
||||
},
|
||||
"label": {
|
||||
"type": "string"
|
||||
},
|
||||
"probabilityPercent": {
|
||||
"format": "int64",
|
||||
"type": "integer"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"bucketId",
|
||||
"label",
|
||||
"probabilityPercent"
|
||||
],
|
||||
"type": "object"
|
||||
},
|
||||
"CodexAvatarBoxRules": {
|
||||
"properties": {
|
||||
"guaranteedNewThreshold": {
|
||||
"format": "int64",
|
||||
"type": "integer"
|
||||
},
|
||||
"legendaryPityThreshold": {
|
||||
"format": "int64",
|
||||
"type": "integer"
|
||||
},
|
||||
"odds": {
|
||||
"items": {
|
||||
"$ref": "#/definitions/CodexAvatarBoxOddsBucket"
|
||||
},
|
||||
"type": "array"
|
||||
},
|
||||
"oddsTableVersion": {
|
||||
"type": "string"
|
||||
},
|
||||
"rareOrBetterPityThreshold": {
|
||||
"format": "int64",
|
||||
"type": "integer"
|
||||
},
|
||||
"rulesetVersion": {
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"guaranteedNewThreshold",
|
||||
"legendaryPityThreshold",
|
||||
"odds",
|
||||
"oddsTableVersion",
|
||||
"rareOrBetterPityThreshold",
|
||||
"rulesetVersion"
|
||||
],
|
||||
"type": "object"
|
||||
},
|
||||
"CodexAvatarDefinition": {
|
||||
"properties": {
|
||||
"accentClassName": {
|
||||
"type": "string"
|
||||
},
|
||||
"assetRef": {
|
||||
"type": "string"
|
||||
},
|
||||
"avatarId": {
|
||||
"type": "string"
|
||||
},
|
||||
"collectionDescription": {
|
||||
"type": "string"
|
||||
},
|
||||
"collectionName": {
|
||||
"type": "string"
|
||||
},
|
||||
"description": {
|
||||
"type": "string"
|
||||
},
|
||||
"displayName": {
|
||||
"type": "string"
|
||||
},
|
||||
"isProgressVisible": {
|
||||
"type": "boolean"
|
||||
},
|
||||
"lore": {
|
||||
"type": "string"
|
||||
},
|
||||
"rarity": {
|
||||
"$ref": "#/definitions/CodexAvatarRarity"
|
||||
},
|
||||
"silhouetteGlowClassName": {
|
||||
"type": "string"
|
||||
},
|
||||
"sortOrder": {
|
||||
"format": "int64",
|
||||
"type": "integer"
|
||||
},
|
||||
"status": {
|
||||
"$ref": "#/definitions/CodexAvatarStatus"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"accentClassName",
|
||||
"assetRef",
|
||||
"avatarId",
|
||||
"collectionDescription",
|
||||
"collectionName",
|
||||
"description",
|
||||
"displayName",
|
||||
"isProgressVisible",
|
||||
"lore",
|
||||
"rarity",
|
||||
"silhouetteGlowClassName",
|
||||
"sortOrder",
|
||||
"status"
|
||||
],
|
||||
"type": "object"
|
||||
},
|
||||
"CodexAvatarEquipParams": {
|
||||
"$schema": "http://json-schema.org/draft-07/schema#",
|
||||
"properties": {
|
||||
"avatarId": {
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"avatarId"
|
||||
],
|
||||
"title": "CodexAvatarEquipParams",
|
||||
"type": "object"
|
||||
},
|
||||
"CodexAvatarInventoryReadParams": {
|
||||
"$schema": "http://json-schema.org/draft-07/schema#",
|
||||
"title": "CodexAvatarInventoryReadParams",
|
||||
"type": "object"
|
||||
},
|
||||
"CodexAvatarInventoryReadResponse": {
|
||||
"$schema": "http://json-schema.org/draft-07/schema#",
|
||||
"properties": {
|
||||
"accountUserId": {
|
||||
"type": "string"
|
||||
},
|
||||
"avatarDefinitions": {
|
||||
"items": {
|
||||
"$ref": "#/definitions/CodexAvatarDefinition"
|
||||
},
|
||||
"type": "array"
|
||||
},
|
||||
"boxRules": {
|
||||
"$ref": "#/definitions/CodexAvatarBoxRules"
|
||||
},
|
||||
"equippedAvatarId": {
|
||||
"type": "string"
|
||||
},
|
||||
"ownedAvatars": {
|
||||
"items": {
|
||||
"$ref": "#/definitions/CodexAvatarOwnership"
|
||||
},
|
||||
"type": "array"
|
||||
},
|
||||
"pendingRevealAwards": {
|
||||
"items": {
|
||||
"$ref": "#/definitions/CodexAvatarRevealAward"
|
||||
},
|
||||
"type": "array"
|
||||
},
|
||||
"pityState": {
|
||||
"$ref": "#/definitions/CodexAvatarPityState"
|
||||
},
|
||||
"updatedAt": {
|
||||
"format": "int64",
|
||||
"type": "integer"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"accountUserId",
|
||||
"avatarDefinitions",
|
||||
"boxRules",
|
||||
"equippedAvatarId",
|
||||
"ownedAvatars",
|
||||
"pendingRevealAwards",
|
||||
"pityState",
|
||||
"updatedAt"
|
||||
],
|
||||
"title": "CodexAvatarInventoryReadResponse",
|
||||
"type": "object"
|
||||
},
|
||||
"CodexAvatarOwnership": {
|
||||
"properties": {
|
||||
"accountUserId": {
|
||||
"type": "string"
|
||||
},
|
||||
"avatarId": {
|
||||
"type": "string"
|
||||
},
|
||||
"sourceSummary": {
|
||||
"type": [
|
||||
"string",
|
||||
"null"
|
||||
]
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"accountUserId",
|
||||
"avatarId"
|
||||
],
|
||||
"type": "object"
|
||||
},
|
||||
"CodexAvatarPityState": {
|
||||
"properties": {
|
||||
"guaranteedNewAvailable": {
|
||||
"type": "boolean"
|
||||
},
|
||||
"nonNewOutcomeStreak": {
|
||||
"format": "int64",
|
||||
"type": "integer"
|
||||
},
|
||||
"rollsSinceLegendary": {
|
||||
"format": "int64",
|
||||
"type": "integer"
|
||||
},
|
||||
"rollsSinceRareOrBetter": {
|
||||
"format": "int64",
|
||||
"type": "integer"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"guaranteedNewAvailable",
|
||||
"nonNewOutcomeStreak",
|
||||
"rollsSinceLegendary",
|
||||
"rollsSinceRareOrBetter"
|
||||
],
|
||||
"type": "object"
|
||||
},
|
||||
"CodexAvatarRarity": {
|
||||
"enum": [
|
||||
"common",
|
||||
"rare",
|
||||
"epic",
|
||||
"legendary"
|
||||
],
|
||||
"type": "string"
|
||||
},
|
||||
"CodexAvatarRevealAward": {
|
||||
"properties": {
|
||||
"awardId": {
|
||||
"type": "string"
|
||||
},
|
||||
"awardedAt": {
|
||||
"format": "int64",
|
||||
"type": "integer"
|
||||
},
|
||||
"metadataJson": {
|
||||
"type": [
|
||||
"string",
|
||||
"null"
|
||||
]
|
||||
},
|
||||
"outcomeAvatarId": {
|
||||
"type": [
|
||||
"string",
|
||||
"null"
|
||||
]
|
||||
},
|
||||
"outcomeKind": {
|
||||
"type": "string"
|
||||
},
|
||||
"pityStateAfter": {
|
||||
"$ref": "#/definitions/CodexAvatarPityState"
|
||||
},
|
||||
"sourceRef": {
|
||||
"type": [
|
||||
"string",
|
||||
"null"
|
||||
]
|
||||
},
|
||||
"sourceSummary": {
|
||||
"type": [
|
||||
"string",
|
||||
"null"
|
||||
]
|
||||
},
|
||||
"sourceType": {
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"awardId",
|
||||
"awardedAt",
|
||||
"outcomeKind",
|
||||
"pityStateAfter",
|
||||
"sourceType"
|
||||
],
|
||||
"type": "object"
|
||||
},
|
||||
"CodexAvatarStatus": {
|
||||
"enum": [
|
||||
"active",
|
||||
"hidden",
|
||||
"retired"
|
||||
],
|
||||
"type": "string"
|
||||
},
|
||||
"CodexErrorInfo": {
|
||||
"description": "This translation layer make sure that we expose codex error code in camel case.\n\nWhen an upstream HTTP status is available (for example, from the Responses API or a provider), it is forwarded in `httpStatusCode` on the relevant `codexErrorInfo` variant.",
|
||||
"oneOf": [
|
||||
|
||||
@@ -0,0 +1,56 @@
|
||||
{
|
||||
"$schema": "http://json-schema.org/draft-07/schema#",
|
||||
"properties": {
|
||||
"accountUserId": {
|
||||
"type": "string"
|
||||
},
|
||||
"avatarId": {
|
||||
"type": "string"
|
||||
},
|
||||
"awardId": {
|
||||
"type": "string"
|
||||
},
|
||||
"awardedAt": {
|
||||
"format": "int64",
|
||||
"type": [
|
||||
"integer",
|
||||
"null"
|
||||
]
|
||||
},
|
||||
"awardedBy": {
|
||||
"type": [
|
||||
"string",
|
||||
"null"
|
||||
]
|
||||
},
|
||||
"metadataJson": {
|
||||
"type": [
|
||||
"string",
|
||||
"null"
|
||||
]
|
||||
},
|
||||
"sourceRef": {
|
||||
"type": [
|
||||
"string",
|
||||
"null"
|
||||
]
|
||||
},
|
||||
"sourceSummary": {
|
||||
"type": [
|
||||
"string",
|
||||
"null"
|
||||
]
|
||||
},
|
||||
"sourceType": {
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"accountUserId",
|
||||
"avatarId",
|
||||
"awardId",
|
||||
"sourceType"
|
||||
],
|
||||
"title": "CodexAvatarAdminAwardGrantParams",
|
||||
"type": "object"
|
||||
}
|
||||
@@ -0,0 +1,5 @@
|
||||
{
|
||||
"$schema": "http://json-schema.org/draft-07/schema#",
|
||||
"title": "CodexAvatarAdminCapabilitiesReadParams",
|
||||
"type": "object"
|
||||
}
|
||||
@@ -0,0 +1,17 @@
|
||||
{
|
||||
"$schema": "http://json-schema.org/draft-07/schema#",
|
||||
"properties": {
|
||||
"canGrantAwards": {
|
||||
"type": "boolean"
|
||||
},
|
||||
"canGrantProofDropBoxes": {
|
||||
"type": "boolean"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"canGrantAwards",
|
||||
"canGrantProofDropBoxes"
|
||||
],
|
||||
"title": "CodexAvatarAdminCapabilitiesReadResponse",
|
||||
"type": "object"
|
||||
}
|
||||
@@ -0,0 +1,13 @@
|
||||
{
|
||||
"$schema": "http://json-schema.org/draft-07/schema#",
|
||||
"properties": {
|
||||
"avatarId": {
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"avatarId"
|
||||
],
|
||||
"title": "CodexAvatarEquipParams",
|
||||
"type": "object"
|
||||
}
|
||||
@@ -0,0 +1,5 @@
|
||||
{
|
||||
"$schema": "http://json-schema.org/draft-07/schema#",
|
||||
"title": "CodexAvatarInventoryReadParams",
|
||||
"type": "object"
|
||||
}
|
||||
@@ -0,0 +1,286 @@
|
||||
{
|
||||
"$schema": "http://json-schema.org/draft-07/schema#",
|
||||
"definitions": {
|
||||
"CodexAvatarBoxOddsBucket": {
|
||||
"properties": {
|
||||
"bucketId": {
|
||||
"type": "string"
|
||||
},
|
||||
"label": {
|
||||
"type": "string"
|
||||
},
|
||||
"probabilityPercent": {
|
||||
"format": "int64",
|
||||
"type": "integer"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"bucketId",
|
||||
"label",
|
||||
"probabilityPercent"
|
||||
],
|
||||
"type": "object"
|
||||
},
|
||||
"CodexAvatarBoxRules": {
|
||||
"properties": {
|
||||
"guaranteedNewThreshold": {
|
||||
"format": "int64",
|
||||
"type": "integer"
|
||||
},
|
||||
"legendaryPityThreshold": {
|
||||
"format": "int64",
|
||||
"type": "integer"
|
||||
},
|
||||
"odds": {
|
||||
"items": {
|
||||
"$ref": "#/definitions/CodexAvatarBoxOddsBucket"
|
||||
},
|
||||
"type": "array"
|
||||
},
|
||||
"oddsTableVersion": {
|
||||
"type": "string"
|
||||
},
|
||||
"rareOrBetterPityThreshold": {
|
||||
"format": "int64",
|
||||
"type": "integer"
|
||||
},
|
||||
"rulesetVersion": {
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"guaranteedNewThreshold",
|
||||
"legendaryPityThreshold",
|
||||
"odds",
|
||||
"oddsTableVersion",
|
||||
"rareOrBetterPityThreshold",
|
||||
"rulesetVersion"
|
||||
],
|
||||
"type": "object"
|
||||
},
|
||||
"CodexAvatarDefinition": {
|
||||
"properties": {
|
||||
"accentClassName": {
|
||||
"type": "string"
|
||||
},
|
||||
"assetRef": {
|
||||
"type": "string"
|
||||
},
|
||||
"avatarId": {
|
||||
"type": "string"
|
||||
},
|
||||
"collectionDescription": {
|
||||
"type": "string"
|
||||
},
|
||||
"collectionName": {
|
||||
"type": "string"
|
||||
},
|
||||
"description": {
|
||||
"type": "string"
|
||||
},
|
||||
"displayName": {
|
||||
"type": "string"
|
||||
},
|
||||
"isProgressVisible": {
|
||||
"type": "boolean"
|
||||
},
|
||||
"lore": {
|
||||
"type": "string"
|
||||
},
|
||||
"rarity": {
|
||||
"$ref": "#/definitions/CodexAvatarRarity"
|
||||
},
|
||||
"silhouetteGlowClassName": {
|
||||
"type": "string"
|
||||
},
|
||||
"sortOrder": {
|
||||
"format": "int64",
|
||||
"type": "integer"
|
||||
},
|
||||
"status": {
|
||||
"$ref": "#/definitions/CodexAvatarStatus"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"accentClassName",
|
||||
"assetRef",
|
||||
"avatarId",
|
||||
"collectionDescription",
|
||||
"collectionName",
|
||||
"description",
|
||||
"displayName",
|
||||
"isProgressVisible",
|
||||
"lore",
|
||||
"rarity",
|
||||
"silhouetteGlowClassName",
|
||||
"sortOrder",
|
||||
"status"
|
||||
],
|
||||
"type": "object"
|
||||
},
|
||||
"CodexAvatarOwnership": {
|
||||
"properties": {
|
||||
"accountUserId": {
|
||||
"type": "string"
|
||||
},
|
||||
"avatarId": {
|
||||
"type": "string"
|
||||
},
|
||||
"sourceSummary": {
|
||||
"type": [
|
||||
"string",
|
||||
"null"
|
||||
]
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"accountUserId",
|
||||
"avatarId"
|
||||
],
|
||||
"type": "object"
|
||||
},
|
||||
"CodexAvatarPityState": {
|
||||
"properties": {
|
||||
"guaranteedNewAvailable": {
|
||||
"type": "boolean"
|
||||
},
|
||||
"nonNewOutcomeStreak": {
|
||||
"format": "int64",
|
||||
"type": "integer"
|
||||
},
|
||||
"rollsSinceLegendary": {
|
||||
"format": "int64",
|
||||
"type": "integer"
|
||||
},
|
||||
"rollsSinceRareOrBetter": {
|
||||
"format": "int64",
|
||||
"type": "integer"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"guaranteedNewAvailable",
|
||||
"nonNewOutcomeStreak",
|
||||
"rollsSinceLegendary",
|
||||
"rollsSinceRareOrBetter"
|
||||
],
|
||||
"type": "object"
|
||||
},
|
||||
"CodexAvatarRarity": {
|
||||
"enum": [
|
||||
"common",
|
||||
"rare",
|
||||
"epic",
|
||||
"legendary"
|
||||
],
|
||||
"type": "string"
|
||||
},
|
||||
"CodexAvatarRevealAward": {
|
||||
"properties": {
|
||||
"awardId": {
|
||||
"type": "string"
|
||||
},
|
||||
"awardedAt": {
|
||||
"format": "int64",
|
||||
"type": "integer"
|
||||
},
|
||||
"metadataJson": {
|
||||
"type": [
|
||||
"string",
|
||||
"null"
|
||||
]
|
||||
},
|
||||
"outcomeAvatarId": {
|
||||
"type": [
|
||||
"string",
|
||||
"null"
|
||||
]
|
||||
},
|
||||
"outcomeKind": {
|
||||
"type": "string"
|
||||
},
|
||||
"pityStateAfter": {
|
||||
"$ref": "#/definitions/CodexAvatarPityState"
|
||||
},
|
||||
"sourceRef": {
|
||||
"type": [
|
||||
"string",
|
||||
"null"
|
||||
]
|
||||
},
|
||||
"sourceSummary": {
|
||||
"type": [
|
||||
"string",
|
||||
"null"
|
||||
]
|
||||
},
|
||||
"sourceType": {
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"awardId",
|
||||
"awardedAt",
|
||||
"outcomeKind",
|
||||
"pityStateAfter",
|
||||
"sourceType"
|
||||
],
|
||||
"type": "object"
|
||||
},
|
||||
"CodexAvatarStatus": {
|
||||
"enum": [
|
||||
"active",
|
||||
"hidden",
|
||||
"retired"
|
||||
],
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"properties": {
|
||||
"accountUserId": {
|
||||
"type": "string"
|
||||
},
|
||||
"avatarDefinitions": {
|
||||
"items": {
|
||||
"$ref": "#/definitions/CodexAvatarDefinition"
|
||||
},
|
||||
"type": "array"
|
||||
},
|
||||
"boxRules": {
|
||||
"$ref": "#/definitions/CodexAvatarBoxRules"
|
||||
},
|
||||
"equippedAvatarId": {
|
||||
"type": "string"
|
||||
},
|
||||
"ownedAvatars": {
|
||||
"items": {
|
||||
"$ref": "#/definitions/CodexAvatarOwnership"
|
||||
},
|
||||
"type": "array"
|
||||
},
|
||||
"pendingRevealAwards": {
|
||||
"items": {
|
||||
"$ref": "#/definitions/CodexAvatarRevealAward"
|
||||
},
|
||||
"type": "array"
|
||||
},
|
||||
"pityState": {
|
||||
"$ref": "#/definitions/CodexAvatarPityState"
|
||||
},
|
||||
"updatedAt": {
|
||||
"format": "int64",
|
||||
"type": "integer"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"accountUserId",
|
||||
"avatarDefinitions",
|
||||
"boxRules",
|
||||
"equippedAvatarId",
|
||||
"ownedAvatars",
|
||||
"pendingRevealAwards",
|
||||
"pityState",
|
||||
"updatedAt"
|
||||
],
|
||||
"title": "CodexAvatarInventoryReadResponse",
|
||||
"type": "object"
|
||||
}
|
||||
File diff suppressed because one or more lines are too long
@@ -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 CodexAvatarAdminAwardGrantParams = { accountUserId: string, awardId: string, avatarId: string, sourceType: string, sourceRef?: string | null, awardedAt?: bigint | null, awardedBy?: string | null, metadataJson?: string | null, sourceSummary?: string | null, };
|
||||
@@ -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 CodexAvatarAdminCapabilitiesReadParams = Record<string, never>;
|
||||
@@ -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 CodexAvatarAdminCapabilitiesReadResponse = { canGrantAwards: boolean, canGrantProofDropBoxes: boolean, };
|
||||
@@ -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 CodexAvatarAward = { awardId: string, accountUserId: string, avatarId: string, sourceType: string, sourceRef: string | null, awardedAt: bigint, awardedBy: string | null, metadataJson: string | null, sourceSummary: string | null, };
|
||||
@@ -0,0 +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 { CodexAvatarRarity } from "./CodexAvatarRarity";
|
||||
import type { CodexAvatarStatus } from "./CodexAvatarStatus";
|
||||
|
||||
export type CodexAvatarDefinition = { avatarId: string, displayName: string, description: string, rarity: CodexAvatarRarity, assetRef: string, status: CodexAvatarStatus, sortOrder: bigint, collectionName: string, collectionDescription: string, lore: string, accentClassName: string, silhouetteGlowClassName: string, isProgressVisible: boolean, };
|
||||
@@ -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 CodexAvatarEquipParams = { avatarId: string, };
|
||||
@@ -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 CodexAvatarInventoryReadParams = Record<string, never>;
|
||||
@@ -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.
|
||||
import type { CodexAvatarBoxRules } from "./CodexAvatarBoxRules";
|
||||
import type { CodexAvatarDefinition } from "./CodexAvatarDefinition";
|
||||
import type { CodexAvatarOwnership } from "./CodexAvatarOwnership";
|
||||
import type { CodexAvatarPityState } from "./CodexAvatarPityState";
|
||||
import type { CodexAvatarRevealAward } from "./CodexAvatarRevealAward";
|
||||
|
||||
export type CodexAvatarInventoryReadResponse = { accountUserId: string, avatarDefinitions: Array<CodexAvatarDefinition>, ownedAvatars: Array<CodexAvatarOwnership>, equippedAvatarId: string, boxRules: CodexAvatarBoxRules, pityState: CodexAvatarPityState, pendingRevealAwards: Array<CodexAvatarRevealAward>, updatedAt: bigint, };
|
||||
@@ -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 CodexAvatarOwnership = { accountUserId: string, avatarId: string, sourceSummary: string | null, };
|
||||
@@ -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 CodexAvatarRarity = "common" | "rare" | "epic" | "legendary";
|
||||
@@ -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 CodexAvatarStatus = "active" | "hidden" | "retired";
|
||||
@@ -31,6 +31,22 @@ export type { CancelLoginAccountStatus } from "./CancelLoginAccountStatus";
|
||||
export type { ChatgptAuthTokensRefreshParams } from "./ChatgptAuthTokensRefreshParams";
|
||||
export type { ChatgptAuthTokensRefreshReason } from "./ChatgptAuthTokensRefreshReason";
|
||||
export type { ChatgptAuthTokensRefreshResponse } from "./ChatgptAuthTokensRefreshResponse";
|
||||
export type { CodexAvatarAdminAwardGrantParams } from "./CodexAvatarAdminAwardGrantParams";
|
||||
export type { CodexAvatarAdminCapabilitiesReadParams } from "./CodexAvatarAdminCapabilitiesReadParams";
|
||||
export type { CodexAvatarAdminCapabilitiesReadResponse } from "./CodexAvatarAdminCapabilitiesReadResponse";
|
||||
export type { CodexAvatarAdminProofDropGrantParams } from "./CodexAvatarAdminProofDropGrantParams";
|
||||
export type { CodexAvatarAward } from "./CodexAvatarAward";
|
||||
export type { CodexAvatarBoxOddsBucket } from "./CodexAvatarBoxOddsBucket";
|
||||
export type { CodexAvatarBoxRules } from "./CodexAvatarBoxRules";
|
||||
export type { CodexAvatarDefinition } from "./CodexAvatarDefinition";
|
||||
export type { CodexAvatarEquipParams } from "./CodexAvatarEquipParams";
|
||||
export type { CodexAvatarInventoryReadParams } from "./CodexAvatarInventoryReadParams";
|
||||
export type { CodexAvatarInventoryReadResponse } from "./CodexAvatarInventoryReadResponse";
|
||||
export type { CodexAvatarOwnership } from "./CodexAvatarOwnership";
|
||||
export type { CodexAvatarPityState } from "./CodexAvatarPityState";
|
||||
export type { CodexAvatarRarity } from "./CodexAvatarRarity";
|
||||
export type { CodexAvatarRevealAward } from "./CodexAvatarRevealAward";
|
||||
export type { CodexAvatarStatus } from "./CodexAvatarStatus";
|
||||
export type { CodexErrorInfo } from "./CodexErrorInfo";
|
||||
export type { CollabAgentState } from "./CollabAgentState";
|
||||
export type { CollabAgentStatus } from "./CollabAgentStatus";
|
||||
|
||||
@@ -118,6 +118,7 @@ pub fn generate_ts_with_options(
|
||||
ServerRequest::export_all_to(out_dir)?;
|
||||
export_server_responses(out_dir)?;
|
||||
ServerNotification::export_all_to(out_dir)?;
|
||||
crate::protocol::v2::CodexAvatarAward::export_all_to(out_dir)?;
|
||||
|
||||
if !options.experimental_api {
|
||||
filter_experimental_ts(out_dir)?;
|
||||
@@ -206,6 +207,12 @@ pub fn generate_json_with_experimental(out_dir: &Path, experimental_api: bool) -
|
||||
|d| write_json_schema_with_return::<crate::ServerRequest>(d, "ServerRequest"),
|
||||
|d| write_json_schema_with_return::<crate::ClientNotification>(d, "ClientNotification"),
|
||||
|d| write_json_schema_with_return::<crate::ServerNotification>(d, "ServerNotification"),
|
||||
|d| {
|
||||
write_json_schema_with_return::<crate::protocol::v2::CodexAvatarAward>(
|
||||
d,
|
||||
"CodexAvatarAward",
|
||||
)
|
||||
},
|
||||
];
|
||||
|
||||
let mut schemas: Vec<GeneratedSchema> = Vec::new();
|
||||
|
||||
@@ -485,6 +485,31 @@ client_request_definitions! {
|
||||
response: v2::GetAccountRateLimitsResponse,
|
||||
},
|
||||
|
||||
AvatarInventoryRead => "avatar/inventory/read" {
|
||||
params: v2::CodexAvatarInventoryReadParams,
|
||||
response: v2::CodexAvatarInventoryReadResponse,
|
||||
},
|
||||
|
||||
AvatarEquip => "avatar/equip" {
|
||||
params: v2::CodexAvatarEquipParams,
|
||||
response: v2::CodexAvatarInventoryReadResponse,
|
||||
},
|
||||
|
||||
AvatarAdminAward => "avatar/admin/award" {
|
||||
params: v2::CodexAvatarAdminAwardGrantParams,
|
||||
response: v2::CodexAvatarInventoryReadResponse,
|
||||
},
|
||||
|
||||
AvatarAdminProofDrop => "avatar/admin/proof-drop" {
|
||||
params: v2::CodexAvatarAdminProofDropGrantParams,
|
||||
response: v2::CodexAvatarInventoryReadResponse,
|
||||
},
|
||||
|
||||
AvatarAdminCapabilitiesRead => "avatar/admin/capabilities/read" {
|
||||
params: v2::CodexAvatarAdminCapabilitiesReadParams,
|
||||
response: v2::CodexAvatarAdminCapabilitiesReadResponse,
|
||||
},
|
||||
|
||||
FeedbackUpload => "feedback/upload" {
|
||||
params: v2::FeedbackUploadParams,
|
||||
response: v2::FeedbackUploadResponse,
|
||||
@@ -1517,6 +1542,129 @@ mod tests {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn serialize_avatar_inventory_read() -> Result<()> {
|
||||
let request = ClientRequest::AvatarInventoryRead {
|
||||
request_id: RequestId::Integer(7),
|
||||
params: v2::CodexAvatarInventoryReadParams::default(),
|
||||
};
|
||||
assert_eq!(
|
||||
json!({
|
||||
"method": "avatar/inventory/read",
|
||||
"id": 7,
|
||||
"params": {}
|
||||
}),
|
||||
serde_json::to_value(&request)?,
|
||||
);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn serialize_avatar_equip() -> Result<()> {
|
||||
let request = ClientRequest::AvatarEquip {
|
||||
request_id: RequestId::Integer(8),
|
||||
params: v2::CodexAvatarEquipParams {
|
||||
avatar_id: "clippy".to_string(),
|
||||
},
|
||||
};
|
||||
assert_eq!(
|
||||
json!({
|
||||
"method": "avatar/equip",
|
||||
"id": 8,
|
||||
"params": {
|
||||
"avatarId": "clippy"
|
||||
}
|
||||
}),
|
||||
serde_json::to_value(&request)?,
|
||||
);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn serialize_avatar_admin_award() -> Result<()> {
|
||||
let request = ClientRequest::AvatarAdminAward {
|
||||
request_id: RequestId::Integer(9),
|
||||
params: v2::CodexAvatarAdminAwardGrantParams {
|
||||
account_user_id: "target-user-123".to_string(),
|
||||
award_id: "manual-grant-1".to_string(),
|
||||
avatar_id: "prism".to_string(),
|
||||
source_type: "manual-admin-grant".to_string(),
|
||||
source_ref: Some("support-ticket-1".to_string()),
|
||||
awarded_at: Some(123),
|
||||
awarded_by: Some("admin-user".to_string()),
|
||||
metadata_json: Some("{\"reason\":\"support\"}".to_string()),
|
||||
source_summary: Some("Manual support grant".to_string()),
|
||||
},
|
||||
};
|
||||
assert_eq!(
|
||||
json!({
|
||||
"method": "avatar/admin/award",
|
||||
"id": 9,
|
||||
"params": {
|
||||
"accountUserId": "target-user-123",
|
||||
"awardId": "manual-grant-1",
|
||||
"avatarId": "prism",
|
||||
"sourceType": "manual-admin-grant",
|
||||
"sourceRef": "support-ticket-1",
|
||||
"awardedAt": 123,
|
||||
"awardedBy": "admin-user",
|
||||
"metadataJson": "{\"reason\":\"support\"}",
|
||||
"sourceSummary": "Manual support grant"
|
||||
}
|
||||
}),
|
||||
serde_json::to_value(&request)?,
|
||||
);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn serialize_avatar_admin_proof_drop() -> Result<()> {
|
||||
let request = ClientRequest::AvatarAdminProofDrop {
|
||||
request_id: RequestId::Integer(10),
|
||||
params: v2::CodexAvatarAdminProofDropGrantParams {
|
||||
account_user_id: "target-user-123".to_string(),
|
||||
award_id: "proof-drop-1".to_string(),
|
||||
source_type: "proof-drop-box".to_string(),
|
||||
source_ref: Some("support-ticket-1".to_string()),
|
||||
awarded_at: Some(123),
|
||||
source_summary: Some("Manual proof-drop box".to_string()),
|
||||
},
|
||||
};
|
||||
assert_eq!(
|
||||
json!({
|
||||
"method": "avatar/admin/proof-drop",
|
||||
"id": 10,
|
||||
"params": {
|
||||
"accountUserId": "target-user-123",
|
||||
"awardId": "proof-drop-1",
|
||||
"sourceType": "proof-drop-box",
|
||||
"sourceRef": "support-ticket-1",
|
||||
"awardedAt": 123,
|
||||
"sourceSummary": "Manual proof-drop box"
|
||||
}
|
||||
}),
|
||||
serde_json::to_value(&request)?,
|
||||
);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn serialize_avatar_admin_capabilities_read() -> Result<()> {
|
||||
let request = ClientRequest::AvatarAdminCapabilitiesRead {
|
||||
request_id: RequestId::Integer(11),
|
||||
params: v2::CodexAvatarAdminCapabilitiesReadParams::default(),
|
||||
};
|
||||
assert_eq!(
|
||||
json!({
|
||||
"method": "avatar/admin/capabilities/read",
|
||||
"id": 11,
|
||||
"params": {}
|
||||
}),
|
||||
serde_json::to_value(&request)?,
|
||||
);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn account_serializes_fields_in_camel_case() -> Result<()> {
|
||||
let api_key = v2::Account::ApiKey {};
|
||||
|
||||
@@ -1737,6 +1737,188 @@ pub struct GetAccountResponse {
|
||||
pub requires_openai_auth: bool,
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq, JsonSchema, TS)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
#[ts(export_to = "v2/")]
|
||||
pub enum CodexAvatarStatus {
|
||||
Active,
|
||||
Hidden,
|
||||
Retired,
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq, JsonSchema, TS)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
#[ts(export_to = "v2/")]
|
||||
pub enum CodexAvatarRarity {
|
||||
Common,
|
||||
Rare,
|
||||
Epic,
|
||||
Legendary,
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq, JsonSchema, TS)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
#[ts(export_to = "v2/")]
|
||||
pub struct CodexAvatarDefinition {
|
||||
pub avatar_id: String,
|
||||
pub display_name: String,
|
||||
pub description: String,
|
||||
pub rarity: CodexAvatarRarity,
|
||||
pub asset_ref: String,
|
||||
pub status: CodexAvatarStatus,
|
||||
pub sort_order: i64,
|
||||
pub collection_name: String,
|
||||
pub collection_description: String,
|
||||
pub lore: String,
|
||||
pub accent_class_name: String,
|
||||
pub silhouette_glow_class_name: String,
|
||||
pub is_progress_visible: bool,
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq, JsonSchema, TS)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
#[ts(export_to = "v2/")]
|
||||
pub struct CodexAvatarOwnership {
|
||||
pub account_user_id: String,
|
||||
pub avatar_id: String,
|
||||
pub source_summary: Option<String>,
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq, JsonSchema, TS)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
#[ts(export_to = "v2/")]
|
||||
pub struct CodexAvatarAward {
|
||||
pub award_id: String,
|
||||
pub account_user_id: String,
|
||||
pub avatar_id: String,
|
||||
pub source_type: String,
|
||||
pub source_ref: Option<String>,
|
||||
pub awarded_at: i64,
|
||||
pub awarded_by: Option<String>,
|
||||
pub metadata_json: Option<String>,
|
||||
pub source_summary: Option<String>,
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq, Default, JsonSchema, TS)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
#[ts(export_to = "v2/")]
|
||||
pub struct CodexAvatarInventoryReadParams {}
|
||||
|
||||
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq, JsonSchema, TS)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
#[ts(export_to = "v2/")]
|
||||
pub struct CodexAvatarEquipParams {
|
||||
pub avatar_id: String,
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq, JsonSchema, TS)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
#[ts(export_to = "v2/")]
|
||||
pub struct CodexAvatarAdminAwardGrantParams {
|
||||
pub account_user_id: String,
|
||||
pub award_id: String,
|
||||
pub avatar_id: String,
|
||||
pub source_type: String,
|
||||
#[ts(optional = nullable)]
|
||||
pub source_ref: Option<String>,
|
||||
#[ts(optional = nullable)]
|
||||
pub awarded_at: Option<i64>,
|
||||
#[ts(optional = nullable)]
|
||||
pub awarded_by: Option<String>,
|
||||
#[ts(optional = nullable)]
|
||||
pub metadata_json: Option<String>,
|
||||
#[ts(optional = nullable)]
|
||||
pub source_summary: Option<String>,
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq, JsonSchema, TS)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
#[ts(export_to = "v2/")]
|
||||
pub struct CodexAvatarAdminProofDropGrantParams {
|
||||
pub account_user_id: String,
|
||||
pub award_id: String,
|
||||
pub source_type: String,
|
||||
#[ts(optional = nullable)]
|
||||
pub source_ref: Option<String>,
|
||||
#[ts(optional = nullable)]
|
||||
pub awarded_at: Option<i64>,
|
||||
#[ts(optional = nullable)]
|
||||
pub source_summary: Option<String>,
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq, Default, JsonSchema, TS)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
#[ts(export_to = "v2/")]
|
||||
pub struct CodexAvatarAdminCapabilitiesReadParams {}
|
||||
|
||||
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq, JsonSchema, TS)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
#[ts(export_to = "v2/")]
|
||||
pub struct CodexAvatarAdminCapabilitiesReadResponse {
|
||||
pub can_grant_awards: bool,
|
||||
pub can_grant_proof_drop_boxes: bool,
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq, JsonSchema, TS)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
#[ts(export_to = "v2/")]
|
||||
pub struct CodexAvatarBoxOddsBucket {
|
||||
pub bucket_id: String,
|
||||
pub label: String,
|
||||
pub probability_percent: i64,
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq, JsonSchema, TS)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
#[ts(export_to = "v2/")]
|
||||
pub struct CodexAvatarPityState {
|
||||
pub rolls_since_rare_or_better: i64,
|
||||
pub rolls_since_legendary: i64,
|
||||
pub non_new_outcome_streak: i64,
|
||||
pub guaranteed_new_available: bool,
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq, JsonSchema, TS)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
#[ts(export_to = "v2/")]
|
||||
pub struct CodexAvatarBoxRules {
|
||||
pub ruleset_version: String,
|
||||
pub odds_table_version: String,
|
||||
pub rare_or_better_pity_threshold: i64,
|
||||
pub legendary_pity_threshold: i64,
|
||||
pub guaranteed_new_threshold: i64,
|
||||
pub odds: Vec<CodexAvatarBoxOddsBucket>,
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq, JsonSchema, TS)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
#[ts(export_to = "v2/")]
|
||||
pub struct CodexAvatarRevealAward {
|
||||
pub award_id: String,
|
||||
pub awarded_at: i64,
|
||||
pub source_type: String,
|
||||
pub source_ref: Option<String>,
|
||||
pub source_summary: Option<String>,
|
||||
pub outcome_kind: String,
|
||||
pub outcome_avatar_id: Option<String>,
|
||||
pub metadata_json: Option<String>,
|
||||
pub pity_state_after: CodexAvatarPityState,
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq, JsonSchema, TS)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
#[ts(export_to = "v2/")]
|
||||
pub struct CodexAvatarInventoryReadResponse {
|
||||
pub account_user_id: String,
|
||||
pub avatar_definitions: Vec<CodexAvatarDefinition>,
|
||||
pub owned_avatars: Vec<CodexAvatarOwnership>,
|
||||
pub equipped_avatar_id: String,
|
||||
pub box_rules: CodexAvatarBoxRules,
|
||||
pub pity_state: CodexAvatarPityState,
|
||||
pub pending_reveal_awards: Vec<CodexAvatarRevealAward>,
|
||||
pub updated_at: i64,
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Default, JsonSchema, TS)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
#[ts(export_to = "v2/")]
|
||||
|
||||
@@ -7,6 +7,7 @@ use crate::export::filter_experimental_ts_tree;
|
||||
use crate::export::generate_index_ts_tree;
|
||||
use crate::protocol::common::visit_client_response_types;
|
||||
use crate::protocol::common::visit_server_response_types;
|
||||
use crate::protocol::v2::CodexAvatarAward;
|
||||
use anyhow::Context;
|
||||
use anyhow::Result;
|
||||
use serde_json::Map;
|
||||
@@ -65,6 +66,7 @@ pub fn generate_typescript_schema_fixture_subtree_for_tests() -> Result<BTreeMap
|
||||
visit_server_response_types(visitor);
|
||||
})?;
|
||||
collect_typescript_fixture_file::<ServerNotification>(&mut files, &mut seen)?;
|
||||
collect_typescript_fixture_file::<CodexAvatarAward>(&mut files, &mut seen)?;
|
||||
|
||||
filter_experimental_ts_tree(&mut files)?;
|
||||
generate_index_ts_tree(&mut files);
|
||||
|
||||
284
codex-rs/app-server/src/avatar_rpc.rs
Normal file
284
codex-rs/app-server/src/avatar_rpc.rs
Normal file
@@ -0,0 +1,284 @@
|
||||
use crate::error_code::INTERNAL_ERROR_CODE;
|
||||
use crate::error_code::INVALID_REQUEST_ERROR_CODE;
|
||||
use codex_app_server_protocol::CodexAvatarAdminAwardGrantParams;
|
||||
use codex_app_server_protocol::CodexAvatarAdminCapabilitiesReadResponse;
|
||||
use codex_app_server_protocol::CodexAvatarAdminProofDropGrantParams;
|
||||
use codex_app_server_protocol::CodexAvatarBoxOddsBucket;
|
||||
use codex_app_server_protocol::CodexAvatarBoxRules;
|
||||
use codex_app_server_protocol::CodexAvatarDefinition;
|
||||
use codex_app_server_protocol::CodexAvatarEquipParams;
|
||||
use codex_app_server_protocol::CodexAvatarInventoryReadResponse;
|
||||
use codex_app_server_protocol::CodexAvatarOwnership;
|
||||
use codex_app_server_protocol::CodexAvatarPityState;
|
||||
use codex_app_server_protocol::CodexAvatarRarity;
|
||||
use codex_app_server_protocol::CodexAvatarRevealAward;
|
||||
use codex_app_server_protocol::CodexAvatarStatus;
|
||||
use codex_app_server_protocol::JSONRPCErrorError;
|
||||
use codex_backend_client::Client as BackendClient;
|
||||
use codex_backend_client::CodexAvatarAdminAwardGrantRequest as BackendAvatarAdminAwardGrantRequest;
|
||||
use codex_backend_client::CodexAvatarAdminCapabilitiesResponse as BackendAvatarAdminCapabilitiesResponse;
|
||||
use codex_backend_client::CodexAvatarAdminProofDropGrantRequest as BackendAvatarAdminProofDropGrantRequest;
|
||||
use codex_backend_client::CodexAvatarBoxOddsBucket as BackendAvatarBoxOddsBucket;
|
||||
use codex_backend_client::CodexAvatarBoxRules as BackendAvatarBoxRules;
|
||||
use codex_backend_client::CodexAvatarDefinition as BackendAvatarDefinition;
|
||||
use codex_backend_client::CodexAvatarInventoryResponse as BackendAvatarInventoryResponse;
|
||||
use codex_backend_client::CodexAvatarOwnership as BackendAvatarOwnership;
|
||||
use codex_backend_client::CodexAvatarPityState as BackendAvatarPityState;
|
||||
use codex_backend_client::CodexAvatarRarity as BackendAvatarRarity;
|
||||
use codex_backend_client::CodexAvatarRevealAward as BackendAvatarRevealAward;
|
||||
use codex_backend_client::CodexAvatarStatus as BackendAvatarStatus;
|
||||
use codex_backend_client::RequestError;
|
||||
use codex_core::AuthManager;
|
||||
use serde_json::Value;
|
||||
|
||||
pub(crate) async fn read_avatar_inventory(
|
||||
auth_manager: &AuthManager,
|
||||
chatgpt_base_url: &str,
|
||||
) -> Result<CodexAvatarInventoryReadResponse, JSONRPCErrorError> {
|
||||
let client = avatar_backend_client(auth_manager, chatgpt_base_url).await?;
|
||||
let response = client
|
||||
.get_avatar_inventory()
|
||||
.await
|
||||
.map_err(|err| backend_avatar_error("read avatar inventory", err))?;
|
||||
Ok(map_avatar_inventory_response(response))
|
||||
}
|
||||
|
||||
pub(crate) async fn equip_avatar(
|
||||
auth_manager: &AuthManager,
|
||||
chatgpt_base_url: &str,
|
||||
params: CodexAvatarEquipParams,
|
||||
) -> Result<CodexAvatarInventoryReadResponse, JSONRPCErrorError> {
|
||||
let client = avatar_backend_client(auth_manager, chatgpt_base_url).await?;
|
||||
let response = client
|
||||
.equip_avatar(params.avatar_id)
|
||||
.await
|
||||
.map_err(|err| backend_avatar_error("equip avatar", err))?;
|
||||
Ok(map_avatar_inventory_response(response))
|
||||
}
|
||||
|
||||
pub(crate) async fn grant_admin_avatar_award(
|
||||
auth_manager: &AuthManager,
|
||||
chatgpt_base_url: &str,
|
||||
params: CodexAvatarAdminAwardGrantParams,
|
||||
) -> Result<CodexAvatarInventoryReadResponse, JSONRPCErrorError> {
|
||||
let client = avatar_backend_client(auth_manager, chatgpt_base_url).await?;
|
||||
let response = client
|
||||
.grant_admin_avatar_award(BackendAvatarAdminAwardGrantRequest {
|
||||
account_user_id: params.account_user_id,
|
||||
award_id: params.award_id,
|
||||
avatar_id: params.avatar_id,
|
||||
source_type: params.source_type,
|
||||
source_ref: params.source_ref,
|
||||
awarded_at: params.awarded_at,
|
||||
awarded_by: params.awarded_by,
|
||||
metadata_json: params.metadata_json,
|
||||
source_summary: params.source_summary,
|
||||
})
|
||||
.await
|
||||
.map_err(|err| backend_avatar_error("grant avatar award", err))?;
|
||||
Ok(map_avatar_inventory_response(response))
|
||||
}
|
||||
|
||||
pub(crate) async fn grant_admin_avatar_proof_drop(
|
||||
auth_manager: &AuthManager,
|
||||
chatgpt_base_url: &str,
|
||||
params: CodexAvatarAdminProofDropGrantParams,
|
||||
) -> Result<CodexAvatarInventoryReadResponse, JSONRPCErrorError> {
|
||||
let client = avatar_backend_client(auth_manager, chatgpt_base_url).await?;
|
||||
let response = client
|
||||
.grant_admin_avatar_proof_drop(BackendAvatarAdminProofDropGrantRequest {
|
||||
account_user_id: params.account_user_id,
|
||||
award_id: params.award_id,
|
||||
source_type: params.source_type,
|
||||
source_ref: params.source_ref,
|
||||
awarded_at: params.awarded_at,
|
||||
source_summary: params.source_summary,
|
||||
})
|
||||
.await
|
||||
.map_err(|err| backend_avatar_error("grant proof-drop box", err))?;
|
||||
Ok(map_avatar_inventory_response(response))
|
||||
}
|
||||
|
||||
pub(crate) async fn read_avatar_admin_capabilities(
|
||||
auth_manager: &AuthManager,
|
||||
chatgpt_base_url: &str,
|
||||
) -> Result<CodexAvatarAdminCapabilitiesReadResponse, JSONRPCErrorError> {
|
||||
let client = avatar_backend_client(auth_manager, chatgpt_base_url).await?;
|
||||
let response = client
|
||||
.get_avatar_admin_capabilities()
|
||||
.await
|
||||
.map_err(|err| backend_avatar_error("read avatar admin capabilities", err))?;
|
||||
Ok(map_avatar_admin_capabilities_response(response))
|
||||
}
|
||||
|
||||
async fn avatar_backend_client(
|
||||
auth_manager: &AuthManager,
|
||||
chatgpt_base_url: &str,
|
||||
) -> Result<BackendClient, JSONRPCErrorError> {
|
||||
let Some(auth) = auth_manager.auth().await else {
|
||||
return Err(JSONRPCErrorError {
|
||||
code: INVALID_REQUEST_ERROR_CODE,
|
||||
message: "codex account authentication required to manage avatars".to_string(),
|
||||
data: None,
|
||||
});
|
||||
};
|
||||
|
||||
if !auth.is_chatgpt_auth() {
|
||||
return Err(JSONRPCErrorError {
|
||||
code: INVALID_REQUEST_ERROR_CODE,
|
||||
message: "chatgpt authentication required to manage avatars".to_string(),
|
||||
data: None,
|
||||
});
|
||||
}
|
||||
|
||||
BackendClient::from_auth(chatgpt_base_url.to_string(), &auth).map_err(|err| JSONRPCErrorError {
|
||||
code: INTERNAL_ERROR_CODE,
|
||||
message: format!("failed to construct backend client: {err}"),
|
||||
data: None,
|
||||
})
|
||||
}
|
||||
|
||||
fn backend_avatar_error(action: &str, err: RequestError) -> JSONRPCErrorError {
|
||||
match &err {
|
||||
RequestError::UnexpectedStatus { status, body, .. }
|
||||
if status.as_u16() == 400 || status.as_u16() == 403 =>
|
||||
{
|
||||
JSONRPCErrorError {
|
||||
code: INVALID_REQUEST_ERROR_CODE,
|
||||
message: avatar_error_detail(body)
|
||||
.unwrap_or_else(|| format!("failed to {action}: {err}")),
|
||||
data: None,
|
||||
}
|
||||
}
|
||||
_ => JSONRPCErrorError {
|
||||
code: INTERNAL_ERROR_CODE,
|
||||
message: format!("failed to {action}: {err}"),
|
||||
data: None,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
fn avatar_error_detail(body: &str) -> Option<String> {
|
||||
let value: Value = serde_json::from_str(body).ok()?;
|
||||
value
|
||||
.get("detail")
|
||||
.and_then(Value::as_str)
|
||||
.map(str::to_string)
|
||||
}
|
||||
|
||||
fn map_avatar_inventory_response(
|
||||
response: BackendAvatarInventoryResponse,
|
||||
) -> CodexAvatarInventoryReadResponse {
|
||||
CodexAvatarInventoryReadResponse {
|
||||
account_user_id: response.account_user_id,
|
||||
avatar_definitions: response
|
||||
.avatar_definitions
|
||||
.into_iter()
|
||||
.map(map_avatar_definition)
|
||||
.collect(),
|
||||
owned_avatars: response
|
||||
.owned_avatars
|
||||
.into_iter()
|
||||
.map(map_avatar_ownership)
|
||||
.collect(),
|
||||
equipped_avatar_id: response.equipped_avatar_id,
|
||||
box_rules: map_avatar_box_rules(response.box_rules),
|
||||
pity_state: map_avatar_pity_state(response.pity_state),
|
||||
pending_reveal_awards: response
|
||||
.pending_reveal_awards
|
||||
.into_iter()
|
||||
.map(map_avatar_reveal_award)
|
||||
.collect(),
|
||||
updated_at: response.updated_at,
|
||||
}
|
||||
}
|
||||
|
||||
fn map_avatar_admin_capabilities_response(
|
||||
response: BackendAvatarAdminCapabilitiesResponse,
|
||||
) -> CodexAvatarAdminCapabilitiesReadResponse {
|
||||
CodexAvatarAdminCapabilitiesReadResponse {
|
||||
can_grant_awards: response.can_grant_awards,
|
||||
can_grant_proof_drop_boxes: response.can_grant_proof_drop_boxes,
|
||||
}
|
||||
}
|
||||
|
||||
fn map_avatar_box_rules(rules: BackendAvatarBoxRules) -> CodexAvatarBoxRules {
|
||||
CodexAvatarBoxRules {
|
||||
ruleset_version: rules.ruleset_version,
|
||||
odds_table_version: rules.odds_table_version,
|
||||
rare_or_better_pity_threshold: rules.rare_or_better_pity_threshold,
|
||||
legendary_pity_threshold: rules.legendary_pity_threshold,
|
||||
guaranteed_new_threshold: rules.guaranteed_new_threshold,
|
||||
odds: rules
|
||||
.odds
|
||||
.into_iter()
|
||||
.map(map_avatar_box_odds_bucket)
|
||||
.collect(),
|
||||
}
|
||||
}
|
||||
|
||||
fn map_avatar_box_odds_bucket(bucket: BackendAvatarBoxOddsBucket) -> CodexAvatarBoxOddsBucket {
|
||||
CodexAvatarBoxOddsBucket {
|
||||
bucket_id: bucket.bucket_id,
|
||||
label: bucket.label,
|
||||
probability_percent: bucket.probability_percent,
|
||||
}
|
||||
}
|
||||
|
||||
fn map_avatar_pity_state(pity_state: BackendAvatarPityState) -> CodexAvatarPityState {
|
||||
CodexAvatarPityState {
|
||||
rolls_since_rare_or_better: pity_state.rolls_since_rare_or_better,
|
||||
rolls_since_legendary: pity_state.rolls_since_legendary,
|
||||
non_new_outcome_streak: pity_state.non_new_outcome_streak,
|
||||
guaranteed_new_available: pity_state.guaranteed_new_available,
|
||||
}
|
||||
}
|
||||
|
||||
fn map_avatar_reveal_award(award: BackendAvatarRevealAward) -> CodexAvatarRevealAward {
|
||||
CodexAvatarRevealAward {
|
||||
award_id: award.award_id,
|
||||
awarded_at: award.awarded_at,
|
||||
source_type: award.source_type,
|
||||
source_ref: award.source_ref,
|
||||
source_summary: award.source_summary,
|
||||
outcome_kind: award.outcome_kind,
|
||||
outcome_avatar_id: award.outcome_avatar_id,
|
||||
metadata_json: award.metadata_json,
|
||||
pity_state_after: map_avatar_pity_state(award.pity_state_after),
|
||||
}
|
||||
}
|
||||
|
||||
fn map_avatar_definition(definition: BackendAvatarDefinition) -> CodexAvatarDefinition {
|
||||
CodexAvatarDefinition {
|
||||
avatar_id: definition.avatar_id,
|
||||
display_name: definition.display_name,
|
||||
description: definition.description,
|
||||
rarity: match definition.rarity {
|
||||
BackendAvatarRarity::Common => CodexAvatarRarity::Common,
|
||||
BackendAvatarRarity::Rare => CodexAvatarRarity::Rare,
|
||||
BackendAvatarRarity::Epic => CodexAvatarRarity::Epic,
|
||||
BackendAvatarRarity::Legendary => CodexAvatarRarity::Legendary,
|
||||
},
|
||||
asset_ref: definition.asset_ref,
|
||||
status: match definition.status {
|
||||
BackendAvatarStatus::Active => CodexAvatarStatus::Active,
|
||||
BackendAvatarStatus::Hidden => CodexAvatarStatus::Hidden,
|
||||
BackendAvatarStatus::Retired => CodexAvatarStatus::Retired,
|
||||
},
|
||||
sort_order: definition.sort_order,
|
||||
collection_name: definition.collection_name,
|
||||
collection_description: definition.collection_description,
|
||||
lore: definition.lore,
|
||||
accent_class_name: definition.accent_class_name,
|
||||
silhouette_glow_class_name: definition.silhouette_glow_class_name,
|
||||
is_progress_visible: definition.is_progress_visible,
|
||||
}
|
||||
}
|
||||
|
||||
fn map_avatar_ownership(ownership: BackendAvatarOwnership) -> CodexAvatarOwnership {
|
||||
CodexAvatarOwnership {
|
||||
account_user_id: ownership.account_user_id,
|
||||
avatar_id: ownership.avatar_id,
|
||||
source_summary: ownership.source_summary,
|
||||
}
|
||||
}
|
||||
@@ -1,3 +1,4 @@
|
||||
use crate::avatar_rpc;
|
||||
use crate::bespoke_event_handling::apply_bespoke_event_handling;
|
||||
use crate::command_exec::CommandExecManager;
|
||||
use crate::command_exec::StartCommandExecParams;
|
||||
@@ -33,6 +34,9 @@ use codex_app_server_protocol::CancelLoginAccountResponse;
|
||||
use codex_app_server_protocol::CancelLoginAccountStatus;
|
||||
use codex_app_server_protocol::ClientRequest;
|
||||
use codex_app_server_protocol::ClientResponse;
|
||||
use codex_app_server_protocol::CodexAvatarAdminAwardGrantParams;
|
||||
use codex_app_server_protocol::CodexAvatarAdminProofDropGrantParams;
|
||||
use codex_app_server_protocol::CodexAvatarEquipParams;
|
||||
use codex_app_server_protocol::CodexErrorInfo as AppServerCodexErrorInfo;
|
||||
use codex_app_server_protocol::CollaborationModeListParams;
|
||||
use codex_app_server_protocol::CollaborationModeListResponse;
|
||||
@@ -904,6 +908,32 @@ impl CodexMessageProcessor {
|
||||
self.get_account(to_connection_request_id(request_id), params)
|
||||
.await;
|
||||
}
|
||||
ClientRequest::AvatarInventoryRead {
|
||||
request_id,
|
||||
params: _,
|
||||
} => {
|
||||
self.avatar_inventory_read(to_connection_request_id(request_id))
|
||||
.await;
|
||||
}
|
||||
ClientRequest::AvatarEquip { request_id, params } => {
|
||||
self.avatar_equip(to_connection_request_id(request_id), params)
|
||||
.await;
|
||||
}
|
||||
ClientRequest::AvatarAdminAward { request_id, params } => {
|
||||
self.avatar_admin_award(to_connection_request_id(request_id), params)
|
||||
.await;
|
||||
}
|
||||
ClientRequest::AvatarAdminProofDrop { request_id, params } => {
|
||||
self.avatar_admin_proof_drop(to_connection_request_id(request_id), params)
|
||||
.await;
|
||||
}
|
||||
ClientRequest::AvatarAdminCapabilitiesRead {
|
||||
request_id,
|
||||
params: _,
|
||||
} => {
|
||||
self.avatar_admin_capabilities_read(to_connection_request_id(request_id))
|
||||
.await;
|
||||
}
|
||||
ClientRequest::GitDiffToRemote { request_id, params } => {
|
||||
self.git_diff_to_origin(to_connection_request_id(request_id), params.cwd)
|
||||
.await;
|
||||
@@ -1686,6 +1716,90 @@ impl CodexMessageProcessor {
|
||||
}
|
||||
}
|
||||
|
||||
async fn avatar_inventory_read(&self, request_id: ConnectionRequestId) {
|
||||
match avatar_rpc::read_avatar_inventory(&self.auth_manager, &self.config.chatgpt_base_url)
|
||||
.await
|
||||
{
|
||||
Ok(response) => {
|
||||
self.outgoing.send_response(request_id, response).await;
|
||||
}
|
||||
Err(error) => {
|
||||
self.outgoing.send_error(request_id, error).await;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
async fn avatar_equip(&self, request_id: ConnectionRequestId, params: CodexAvatarEquipParams) {
|
||||
match avatar_rpc::equip_avatar(&self.auth_manager, &self.config.chatgpt_base_url, params)
|
||||
.await
|
||||
{
|
||||
Ok(response) => {
|
||||
self.outgoing.send_response(request_id, response).await;
|
||||
}
|
||||
Err(error) => {
|
||||
self.outgoing.send_error(request_id, error).await;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
async fn avatar_admin_award(
|
||||
&self,
|
||||
request_id: ConnectionRequestId,
|
||||
params: CodexAvatarAdminAwardGrantParams,
|
||||
) {
|
||||
match avatar_rpc::grant_admin_avatar_award(
|
||||
&self.auth_manager,
|
||||
&self.config.chatgpt_base_url,
|
||||
params,
|
||||
)
|
||||
.await
|
||||
{
|
||||
Ok(response) => {
|
||||
self.outgoing.send_response(request_id, response).await;
|
||||
}
|
||||
Err(error) => {
|
||||
self.outgoing.send_error(request_id, error).await;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
async fn avatar_admin_proof_drop(
|
||||
&self,
|
||||
request_id: ConnectionRequestId,
|
||||
params: CodexAvatarAdminProofDropGrantParams,
|
||||
) {
|
||||
match avatar_rpc::grant_admin_avatar_proof_drop(
|
||||
&self.auth_manager,
|
||||
&self.config.chatgpt_base_url,
|
||||
params,
|
||||
)
|
||||
.await
|
||||
{
|
||||
Ok(response) => {
|
||||
self.outgoing.send_response(request_id, response).await;
|
||||
}
|
||||
Err(error) => {
|
||||
self.outgoing.send_error(request_id, error).await;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
async fn avatar_admin_capabilities_read(&self, request_id: ConnectionRequestId) {
|
||||
match avatar_rpc::read_avatar_admin_capabilities(
|
||||
&self.auth_manager,
|
||||
&self.config.chatgpt_base_url,
|
||||
)
|
||||
.await
|
||||
{
|
||||
Ok(response) => {
|
||||
self.outgoing.send_response(request_id, response).await;
|
||||
}
|
||||
Err(error) => {
|
||||
self.outgoing.send_error(request_id, error).await;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
async fn fetch_account_rate_limits(
|
||||
&self,
|
||||
) -> Result<
|
||||
|
||||
@@ -61,6 +61,7 @@ use tracing_subscriber::registry::Registry;
|
||||
use tracing_subscriber::util::SubscriberInitExt;
|
||||
|
||||
mod app_server_tracing;
|
||||
mod avatar_rpc;
|
||||
mod bespoke_event_handling;
|
||||
mod codex_message_processor;
|
||||
mod command_exec;
|
||||
|
||||
@@ -15,6 +15,9 @@ use codex_app_server_protocol::AppsListParams;
|
||||
use codex_app_server_protocol::CancelLoginAccountParams;
|
||||
use codex_app_server_protocol::ClientInfo;
|
||||
use codex_app_server_protocol::ClientNotification;
|
||||
use codex_app_server_protocol::CodexAvatarAdminAwardGrantParams;
|
||||
use codex_app_server_protocol::CodexAvatarAdminProofDropGrantParams;
|
||||
use codex_app_server_protocol::CodexAvatarEquipParams;
|
||||
use codex_app_server_protocol::CollaborationModeListParams;
|
||||
use codex_app_server_protocol::CommandExecParams;
|
||||
use codex_app_server_protocol::CommandExecResizeParams;
|
||||
@@ -296,6 +299,46 @@ impl McpProcess {
|
||||
self.send_request("account/read", params).await
|
||||
}
|
||||
|
||||
/// Send an `avatar/inventory/read` JSON-RPC request.
|
||||
pub async fn send_avatar_inventory_read_request(&mut self) -> anyhow::Result<i64> {
|
||||
let params = Some(serde_json::json!({}));
|
||||
self.send_request("avatar/inventory/read", params).await
|
||||
}
|
||||
|
||||
/// Send an `avatar/equip` JSON-RPC request.
|
||||
pub async fn send_avatar_equip_request(
|
||||
&mut self,
|
||||
params: CodexAvatarEquipParams,
|
||||
) -> anyhow::Result<i64> {
|
||||
let params = Some(serde_json::to_value(params)?);
|
||||
self.send_request("avatar/equip", params).await
|
||||
}
|
||||
|
||||
/// Send an `avatar/admin/award` JSON-RPC request.
|
||||
pub async fn send_avatar_admin_award_request(
|
||||
&mut self,
|
||||
params: CodexAvatarAdminAwardGrantParams,
|
||||
) -> anyhow::Result<i64> {
|
||||
let params = Some(serde_json::to_value(params)?);
|
||||
self.send_request("avatar/admin/award", params).await
|
||||
}
|
||||
|
||||
/// Send an `avatar/admin/proof-drop` JSON-RPC request.
|
||||
pub async fn send_avatar_admin_proof_drop_request(
|
||||
&mut self,
|
||||
params: CodexAvatarAdminProofDropGrantParams,
|
||||
) -> anyhow::Result<i64> {
|
||||
let params = Some(serde_json::to_value(params)?);
|
||||
self.send_request("avatar/admin/proof-drop", params).await
|
||||
}
|
||||
|
||||
/// Send an `avatar/admin/capabilities/read` JSON-RPC request.
|
||||
pub async fn send_avatar_admin_capabilities_read_request(&mut self) -> anyhow::Result<i64> {
|
||||
let params = Some(serde_json::json!({}));
|
||||
self.send_request("avatar/admin/capabilities/read", params)
|
||||
.await
|
||||
}
|
||||
|
||||
/// Send an `account/login/start` JSON-RPC request with ChatGPT auth tokens.
|
||||
pub async fn send_chatgpt_auth_tokens_login_request(
|
||||
&mut self,
|
||||
|
||||
686
codex-rs/app-server/tests/suite/v2/avatar.rs
Normal file
686
codex-rs/app-server/tests/suite/v2/avatar.rs
Normal file
@@ -0,0 +1,686 @@
|
||||
use anyhow::Result;
|
||||
use app_test_support::ChatGptAuthFixture;
|
||||
use app_test_support::McpProcess;
|
||||
use app_test_support::to_response;
|
||||
use app_test_support::write_chatgpt_auth;
|
||||
use codex_app_server_protocol::CodexAvatarAdminAwardGrantParams;
|
||||
use codex_app_server_protocol::CodexAvatarAdminCapabilitiesReadResponse;
|
||||
use codex_app_server_protocol::CodexAvatarAdminProofDropGrantParams;
|
||||
use codex_app_server_protocol::CodexAvatarBoxOddsBucket;
|
||||
use codex_app_server_protocol::CodexAvatarBoxRules;
|
||||
use codex_app_server_protocol::CodexAvatarDefinition;
|
||||
use codex_app_server_protocol::CodexAvatarEquipParams;
|
||||
use codex_app_server_protocol::CodexAvatarInventoryReadResponse;
|
||||
use codex_app_server_protocol::CodexAvatarOwnership;
|
||||
use codex_app_server_protocol::CodexAvatarPityState;
|
||||
use codex_app_server_protocol::CodexAvatarRarity;
|
||||
use codex_app_server_protocol::CodexAvatarRevealAward;
|
||||
use codex_app_server_protocol::CodexAvatarStatus;
|
||||
use codex_app_server_protocol::JSONRPCError;
|
||||
use codex_app_server_protocol::JSONRPCResponse;
|
||||
use codex_app_server_protocol::LoginAccountResponse;
|
||||
use codex_app_server_protocol::RequestId;
|
||||
use codex_core::auth::AuthCredentialsStoreMode;
|
||||
use pretty_assertions::assert_eq;
|
||||
use serde_json::Value;
|
||||
use serde_json::json;
|
||||
use std::path::Path;
|
||||
use tempfile::TempDir;
|
||||
use tokio::time::timeout;
|
||||
use wiremock::Mock;
|
||||
use wiremock::MockServer;
|
||||
use wiremock::ResponseTemplate;
|
||||
use wiremock::matchers::body_json;
|
||||
use wiremock::matchers::header;
|
||||
use wiremock::matchers::method;
|
||||
use wiremock::matchers::path;
|
||||
|
||||
const DEFAULT_READ_TIMEOUT: std::time::Duration = std::time::Duration::from_secs(10);
|
||||
const INVALID_REQUEST_ERROR_CODE: i64 = -32600;
|
||||
|
||||
#[tokio::test]
|
||||
async fn avatar_inventory_read_requires_auth() -> Result<()> {
|
||||
let codex_home = TempDir::new()?;
|
||||
|
||||
let mut mcp = McpProcess::new_with_env(codex_home.path(), &[("OPENAI_API_KEY", None)]).await?;
|
||||
timeout(DEFAULT_READ_TIMEOUT, mcp.initialize()).await??;
|
||||
|
||||
let request_id = mcp.send_avatar_inventory_read_request().await?;
|
||||
let error: JSONRPCError = timeout(
|
||||
DEFAULT_READ_TIMEOUT,
|
||||
mcp.read_stream_until_error_message(RequestId::Integer(request_id)),
|
||||
)
|
||||
.await??;
|
||||
|
||||
assert_eq!(error.id, RequestId::Integer(request_id));
|
||||
assert_eq!(error.error.code, INVALID_REQUEST_ERROR_CODE);
|
||||
assert_eq!(
|
||||
error.error.message,
|
||||
"codex account authentication required to manage avatars"
|
||||
);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn avatar_inventory_read_requires_chatgpt_auth() -> Result<()> {
|
||||
let codex_home = TempDir::new()?;
|
||||
|
||||
let mut mcp = McpProcess::new(codex_home.path()).await?;
|
||||
timeout(DEFAULT_READ_TIMEOUT, mcp.initialize()).await??;
|
||||
|
||||
let login_request_id = mcp
|
||||
.send_login_account_api_key_request("sk-test-key")
|
||||
.await?;
|
||||
let login_response: JSONRPCResponse = timeout(
|
||||
DEFAULT_READ_TIMEOUT,
|
||||
mcp.read_stream_until_response_message(RequestId::Integer(login_request_id)),
|
||||
)
|
||||
.await??;
|
||||
let login: LoginAccountResponse = to_response(login_response)?;
|
||||
assert_eq!(login, LoginAccountResponse::ApiKey {});
|
||||
|
||||
let request_id = mcp.send_avatar_inventory_read_request().await?;
|
||||
let error: JSONRPCError = timeout(
|
||||
DEFAULT_READ_TIMEOUT,
|
||||
mcp.read_stream_until_error_message(RequestId::Integer(request_id)),
|
||||
)
|
||||
.await??;
|
||||
|
||||
assert_eq!(error.id, RequestId::Integer(request_id));
|
||||
assert_eq!(error.error.code, INVALID_REQUEST_ERROR_CODE);
|
||||
assert_eq!(
|
||||
error.error.message,
|
||||
"chatgpt authentication required to manage avatars"
|
||||
);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn avatar_inventory_read_returns_snapshot() -> Result<()> {
|
||||
let codex_home = TempDir::new()?;
|
||||
write_chatgpt_auth(
|
||||
codex_home.path(),
|
||||
ChatGptAuthFixture::new("chatgpt-token")
|
||||
.account_id("account-123")
|
||||
.plan_type("pro"),
|
||||
AuthCredentialsStoreMode::File,
|
||||
)?;
|
||||
|
||||
let server = MockServer::start().await;
|
||||
write_chatgpt_base_url(codex_home.path(), &server.uri())?;
|
||||
Mock::given(method("GET"))
|
||||
.and(path("/api/codex/avatars/inventory"))
|
||||
.and(header("authorization", "Bearer chatgpt-token"))
|
||||
.and(header("chatgpt-account-id", "account-123"))
|
||||
.respond_with(ResponseTemplate::new(200).set_body_json(snapshot_json("prism")))
|
||||
.mount(&server)
|
||||
.await;
|
||||
|
||||
let mut mcp = McpProcess::new_with_env(codex_home.path(), &[("OPENAI_API_KEY", None)]).await?;
|
||||
timeout(DEFAULT_READ_TIMEOUT, mcp.initialize()).await??;
|
||||
|
||||
let request_id = mcp.send_avatar_inventory_read_request().await?;
|
||||
let response: JSONRPCResponse = timeout(
|
||||
DEFAULT_READ_TIMEOUT,
|
||||
mcp.read_stream_until_response_message(RequestId::Integer(request_id)),
|
||||
)
|
||||
.await??;
|
||||
let received: CodexAvatarInventoryReadResponse = to_response(response)?;
|
||||
|
||||
assert_eq!(received, expected_snapshot("prism"));
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn avatar_equip_forwards_backend_validation_error() -> Result<()> {
|
||||
let codex_home = TempDir::new()?;
|
||||
write_chatgpt_auth(
|
||||
codex_home.path(),
|
||||
ChatGptAuthFixture::new("chatgpt-token")
|
||||
.account_id("account-123")
|
||||
.plan_type("pro"),
|
||||
AuthCredentialsStoreMode::File,
|
||||
)?;
|
||||
|
||||
let server = MockServer::start().await;
|
||||
write_chatgpt_base_url(codex_home.path(), &server.uri())?;
|
||||
Mock::given(method("POST"))
|
||||
.and(path("/api/codex/avatars/equip"))
|
||||
.and(header("authorization", "Bearer chatgpt-token"))
|
||||
.and(header("chatgpt-account-id", "account-123"))
|
||||
.respond_with(ResponseTemplate::new(400).set_body_json(json!({
|
||||
"detail": "cannot equip unowned avatar prism",
|
||||
})))
|
||||
.mount(&server)
|
||||
.await;
|
||||
|
||||
let mut mcp = McpProcess::new_with_env(codex_home.path(), &[("OPENAI_API_KEY", None)]).await?;
|
||||
timeout(DEFAULT_READ_TIMEOUT, mcp.initialize()).await??;
|
||||
|
||||
let request_id = mcp
|
||||
.send_avatar_equip_request(CodexAvatarEquipParams {
|
||||
avatar_id: "prism".to_string(),
|
||||
})
|
||||
.await?;
|
||||
let error: JSONRPCError = timeout(
|
||||
DEFAULT_READ_TIMEOUT,
|
||||
mcp.read_stream_until_error_message(RequestId::Integer(request_id)),
|
||||
)
|
||||
.await??;
|
||||
|
||||
assert_eq!(error.id, RequestId::Integer(request_id));
|
||||
assert_eq!(error.error.code, INVALID_REQUEST_ERROR_CODE);
|
||||
assert_eq!(error.error.message, "cannot equip unowned avatar prism");
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn avatar_equip_returns_snapshot_with_clippy_fallback() -> Result<()> {
|
||||
let codex_home = TempDir::new()?;
|
||||
write_chatgpt_auth(
|
||||
codex_home.path(),
|
||||
ChatGptAuthFixture::new("chatgpt-token")
|
||||
.account_id("account-123")
|
||||
.plan_type("pro"),
|
||||
AuthCredentialsStoreMode::File,
|
||||
)?;
|
||||
|
||||
let server = MockServer::start().await;
|
||||
write_chatgpt_base_url(codex_home.path(), &server.uri())?;
|
||||
Mock::given(method("POST"))
|
||||
.and(path("/api/codex/avatars/equip"))
|
||||
.and(header("authorization", "Bearer chatgpt-token"))
|
||||
.and(header("chatgpt-account-id", "account-123"))
|
||||
.respond_with(ResponseTemplate::new(200).set_body_json(snapshot_json("clippy")))
|
||||
.mount(&server)
|
||||
.await;
|
||||
|
||||
let mut mcp = McpProcess::new_with_env(codex_home.path(), &[("OPENAI_API_KEY", None)]).await?;
|
||||
timeout(DEFAULT_READ_TIMEOUT, mcp.initialize()).await??;
|
||||
|
||||
let request_id = mcp
|
||||
.send_avatar_equip_request(CodexAvatarEquipParams {
|
||||
avatar_id: "sunset".to_string(),
|
||||
})
|
||||
.await?;
|
||||
let response: JSONRPCResponse = timeout(
|
||||
DEFAULT_READ_TIMEOUT,
|
||||
mcp.read_stream_until_response_message(RequestId::Integer(request_id)),
|
||||
)
|
||||
.await??;
|
||||
let received: CodexAvatarInventoryReadResponse = to_response(response)?;
|
||||
|
||||
assert_eq!(received, expected_snapshot("clippy"));
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn avatar_admin_award_forwards_request_and_returns_target_user_snapshot() -> Result<()> {
|
||||
let codex_home = TempDir::new()?;
|
||||
write_chatgpt_auth(
|
||||
codex_home.path(),
|
||||
ChatGptAuthFixture::new("chatgpt-token")
|
||||
.account_id("account-123")
|
||||
.plan_type("pro"),
|
||||
AuthCredentialsStoreMode::File,
|
||||
)?;
|
||||
|
||||
let server = MockServer::start().await;
|
||||
write_chatgpt_base_url(codex_home.path(), &server.uri())?;
|
||||
Mock::given(method("POST"))
|
||||
.and(path("/api/codex/avatars/admin/awards"))
|
||||
.and(header("authorization", "Bearer chatgpt-token"))
|
||||
.and(header("chatgpt-account-id", "account-123"))
|
||||
.and(body_json(json!({
|
||||
"accountUserId": "target-user-456",
|
||||
"awardId": "manual-grant-1",
|
||||
"avatarId": "prism",
|
||||
"sourceType": "manual-admin-grant",
|
||||
"sourceRef": "support-ticket-1",
|
||||
"awardedAt": 123,
|
||||
"awardedBy": "admin-user",
|
||||
"metadataJson": "{\"reason\":\"support\"}",
|
||||
"sourceSummary": "Manual support grant"
|
||||
})))
|
||||
.respond_with(
|
||||
ResponseTemplate::new(200)
|
||||
.set_body_json(snapshot_json_for_user("prism", "target-user-456")),
|
||||
)
|
||||
.mount(&server)
|
||||
.await;
|
||||
|
||||
let mut mcp = McpProcess::new_with_env(codex_home.path(), &[("OPENAI_API_KEY", None)]).await?;
|
||||
timeout(DEFAULT_READ_TIMEOUT, mcp.initialize()).await??;
|
||||
|
||||
let request_id = mcp
|
||||
.send_avatar_admin_award_request(CodexAvatarAdminAwardGrantParams {
|
||||
account_user_id: "target-user-456".to_string(),
|
||||
award_id: "manual-grant-1".to_string(),
|
||||
avatar_id: "prism".to_string(),
|
||||
source_type: "manual-admin-grant".to_string(),
|
||||
source_ref: Some("support-ticket-1".to_string()),
|
||||
awarded_at: Some(123),
|
||||
awarded_by: Some("admin-user".to_string()),
|
||||
metadata_json: Some("{\"reason\":\"support\"}".to_string()),
|
||||
source_summary: Some("Manual support grant".to_string()),
|
||||
})
|
||||
.await?;
|
||||
let response: JSONRPCResponse = timeout(
|
||||
DEFAULT_READ_TIMEOUT,
|
||||
mcp.read_stream_until_response_message(RequestId::Integer(request_id)),
|
||||
)
|
||||
.await??;
|
||||
let received: CodexAvatarInventoryReadResponse = to_response(response)?;
|
||||
|
||||
assert_eq!(
|
||||
received,
|
||||
expected_snapshot_for_user("prism", "target-user-456")
|
||||
);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn avatar_admin_proof_drop_forwards_request_and_returns_reveal_snapshot() -> Result<()> {
|
||||
let codex_home = TempDir::new()?;
|
||||
write_chatgpt_auth(
|
||||
codex_home.path(),
|
||||
ChatGptAuthFixture::new("chatgpt-token")
|
||||
.account_id("account-123")
|
||||
.plan_type("pro"),
|
||||
AuthCredentialsStoreMode::File,
|
||||
)?;
|
||||
|
||||
let server = MockServer::start().await;
|
||||
write_chatgpt_base_url(codex_home.path(), &server.uri())?;
|
||||
Mock::given(method("POST"))
|
||||
.and(path("/api/codex/avatars/admin/proof-drop"))
|
||||
.and(header("authorization", "Bearer chatgpt-token"))
|
||||
.and(header("chatgpt-account-id", "account-123"))
|
||||
.and(body_json(json!({
|
||||
"accountUserId": "target-user-456",
|
||||
"awardId": "proof-drop-1",
|
||||
"sourceType": "proof-drop-box",
|
||||
"sourceRef": "support-ticket-1",
|
||||
"awardedAt": 123,
|
||||
"sourceSummary": "Manual proof-drop box"
|
||||
})))
|
||||
.respond_with(
|
||||
ResponseTemplate::new(200)
|
||||
.set_body_json(snapshot_json_with_reveal("prism", "target-user-456")),
|
||||
)
|
||||
.mount(&server)
|
||||
.await;
|
||||
|
||||
let mut mcp = McpProcess::new_with_env(codex_home.path(), &[("OPENAI_API_KEY", None)]).await?;
|
||||
timeout(DEFAULT_READ_TIMEOUT, mcp.initialize()).await??;
|
||||
|
||||
let request_id = mcp
|
||||
.send_avatar_admin_proof_drop_request(CodexAvatarAdminProofDropGrantParams {
|
||||
account_user_id: "target-user-456".to_string(),
|
||||
award_id: "proof-drop-1".to_string(),
|
||||
source_type: "proof-drop-box".to_string(),
|
||||
source_ref: Some("support-ticket-1".to_string()),
|
||||
awarded_at: Some(123),
|
||||
source_summary: Some("Manual proof-drop box".to_string()),
|
||||
})
|
||||
.await?;
|
||||
let response: JSONRPCResponse = timeout(
|
||||
DEFAULT_READ_TIMEOUT,
|
||||
mcp.read_stream_until_response_message(RequestId::Integer(request_id)),
|
||||
)
|
||||
.await??;
|
||||
let received: CodexAvatarInventoryReadResponse = to_response(response)?;
|
||||
|
||||
assert_eq!(
|
||||
received,
|
||||
expected_snapshot_with_reveal("prism", "target-user-456")
|
||||
);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn avatar_admin_award_forwards_backend_permission_error() -> Result<()> {
|
||||
let codex_home = TempDir::new()?;
|
||||
write_chatgpt_auth(
|
||||
codex_home.path(),
|
||||
ChatGptAuthFixture::new("chatgpt-token")
|
||||
.account_id("account-123")
|
||||
.plan_type("pro"),
|
||||
AuthCredentialsStoreMode::File,
|
||||
)?;
|
||||
|
||||
let server = MockServer::start().await;
|
||||
write_chatgpt_base_url(codex_home.path(), &server.uri())?;
|
||||
Mock::given(method("POST"))
|
||||
.and(path("/api/codex/avatars/admin/awards"))
|
||||
.respond_with(ResponseTemplate::new(403).set_body_json(json!({
|
||||
"detail": "Not a Codex admin",
|
||||
})))
|
||||
.mount(&server)
|
||||
.await;
|
||||
|
||||
let mut mcp = McpProcess::new_with_env(codex_home.path(), &[("OPENAI_API_KEY", None)]).await?;
|
||||
timeout(DEFAULT_READ_TIMEOUT, mcp.initialize()).await??;
|
||||
|
||||
let request_id = mcp
|
||||
.send_avatar_admin_award_request(CodexAvatarAdminAwardGrantParams {
|
||||
account_user_id: "target-user-456".to_string(),
|
||||
award_id: "manual-grant-1".to_string(),
|
||||
avatar_id: "prism".to_string(),
|
||||
source_type: "manual-admin-grant".to_string(),
|
||||
source_ref: None,
|
||||
awarded_at: None,
|
||||
awarded_by: None,
|
||||
metadata_json: None,
|
||||
source_summary: None,
|
||||
})
|
||||
.await?;
|
||||
let error: JSONRPCError = timeout(
|
||||
DEFAULT_READ_TIMEOUT,
|
||||
mcp.read_stream_until_error_message(RequestId::Integer(request_id)),
|
||||
)
|
||||
.await??;
|
||||
|
||||
assert_eq!(error.id, RequestId::Integer(request_id));
|
||||
assert_eq!(error.error.code, INVALID_REQUEST_ERROR_CODE);
|
||||
assert_eq!(error.error.message, "Not a Codex admin");
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn avatar_admin_capabilities_read_returns_backend_capability_flags() -> Result<()> {
|
||||
let codex_home = TempDir::new()?;
|
||||
write_chatgpt_auth(
|
||||
codex_home.path(),
|
||||
ChatGptAuthFixture::new("chatgpt-token")
|
||||
.account_id("account-123")
|
||||
.plan_type("pro"),
|
||||
AuthCredentialsStoreMode::File,
|
||||
)?;
|
||||
|
||||
let server = MockServer::start().await;
|
||||
write_chatgpt_base_url(codex_home.path(), &server.uri())?;
|
||||
Mock::given(method("GET"))
|
||||
.and(path("/api/codex/avatars/admin/capabilities"))
|
||||
.and(header("authorization", "Bearer chatgpt-token"))
|
||||
.and(header("chatgpt-account-id", "account-123"))
|
||||
.respond_with(ResponseTemplate::new(200).set_body_json(json!({
|
||||
"canGrantAwards": true,
|
||||
"canGrantProofDropBoxes": true,
|
||||
})))
|
||||
.mount(&server)
|
||||
.await;
|
||||
|
||||
let mut mcp = McpProcess::new_with_env(codex_home.path(), &[("OPENAI_API_KEY", None)]).await?;
|
||||
timeout(DEFAULT_READ_TIMEOUT, mcp.initialize()).await??;
|
||||
|
||||
let request_id = mcp.send_avatar_admin_capabilities_read_request().await?;
|
||||
let response: JSONRPCResponse = timeout(
|
||||
DEFAULT_READ_TIMEOUT,
|
||||
mcp.read_stream_until_response_message(RequestId::Integer(request_id)),
|
||||
)
|
||||
.await??;
|
||||
let received: CodexAvatarAdminCapabilitiesReadResponse = to_response(response)?;
|
||||
|
||||
assert_eq!(
|
||||
received,
|
||||
CodexAvatarAdminCapabilitiesReadResponse {
|
||||
can_grant_awards: true,
|
||||
can_grant_proof_drop_boxes: true,
|
||||
}
|
||||
);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn write_chatgpt_base_url(codex_home: &Path, base_url: &str) -> std::io::Result<()> {
|
||||
std::fs::write(
|
||||
codex_home.join("config.toml"),
|
||||
format!("chatgpt_base_url = \"{base_url}\"\n"),
|
||||
)
|
||||
}
|
||||
|
||||
fn snapshot_json(equipped_avatar_id: &str) -> Value {
|
||||
snapshot_json_for_user(equipped_avatar_id, "account-123")
|
||||
}
|
||||
|
||||
fn snapshot_json_for_user(equipped_avatar_id: &str, account_user_id: &str) -> Value {
|
||||
json!({
|
||||
"accountUserId": account_user_id,
|
||||
"avatarDefinitions": [
|
||||
{
|
||||
"avatarId": "clippy",
|
||||
"displayName": "Clippy",
|
||||
"description": "The default Codex paperclip avatar",
|
||||
"rarity": "common",
|
||||
"assetRef": "builtin:clippy",
|
||||
"status": "active",
|
||||
"sortOrder": 0,
|
||||
"collectionName": "Desktop Set",
|
||||
"collectionDescription": "Classic office companions.",
|
||||
"lore": "Helpful and persistent.",
|
||||
"accentClassName": "bg-slate-100 text-slate-950",
|
||||
"silhouetteGlowClassName": "bg-slate-300/45",
|
||||
"isProgressVisible": true,
|
||||
},
|
||||
{
|
||||
"avatarId": "prism",
|
||||
"displayName": "Prism",
|
||||
"description": "A shiny earnable avatar",
|
||||
"rarity": "rare",
|
||||
"assetRef": "builtin:prism",
|
||||
"status": "hidden",
|
||||
"sortOrder": 10,
|
||||
"collectionName": "Prism Set",
|
||||
"collectionDescription": "Light-bent mascots.",
|
||||
"lore": "Glows softly.",
|
||||
"accentClassName": "bg-violet-100 text-violet-950",
|
||||
"silhouetteGlowClassName": "bg-violet-300/45",
|
||||
"isProgressVisible": true,
|
||||
}
|
||||
],
|
||||
"ownedAvatars": [
|
||||
{
|
||||
"accountUserId": account_user_id,
|
||||
"avatarId": "clippy",
|
||||
"sourceSummary": "Default avatar",
|
||||
},
|
||||
{
|
||||
"accountUserId": account_user_id,
|
||||
"avatarId": "prism",
|
||||
"sourceSummary": "Quest reward",
|
||||
}
|
||||
],
|
||||
"equippedAvatarId": equipped_avatar_id,
|
||||
"boxRules": box_rules_json(),
|
||||
"pityState": pity_state_json(),
|
||||
"pendingRevealAwards": [],
|
||||
"updatedAt": 500,
|
||||
})
|
||||
}
|
||||
|
||||
fn snapshot_json_with_reveal(equipped_avatar_id: &str, account_user_id: &str) -> Value {
|
||||
let mut snapshot = snapshot_json_for_user(equipped_avatar_id, account_user_id);
|
||||
snapshot["pendingRevealAwards"] = json!([reveal_award_json()]);
|
||||
snapshot
|
||||
}
|
||||
|
||||
fn box_rules_json() -> Value {
|
||||
json!({
|
||||
"rulesetVersion": "signal-seasons-v1",
|
||||
"oddsTableVersion": "signal-seasons-v1",
|
||||
"rareOrBetterPityThreshold": 10,
|
||||
"legendaryPityThreshold": 40,
|
||||
"guaranteedNewThreshold": 6,
|
||||
"odds": [
|
||||
{
|
||||
"bucketId": "common",
|
||||
"label": "Common",
|
||||
"probabilityPercent": 63,
|
||||
},
|
||||
{
|
||||
"bucketId": "rare",
|
||||
"label": "Rare",
|
||||
"probabilityPercent": 26,
|
||||
},
|
||||
{
|
||||
"bucketId": "legendary",
|
||||
"label": "Legendary",
|
||||
"probabilityPercent": 1,
|
||||
},
|
||||
{
|
||||
"bucketId": "no_drop",
|
||||
"label": "No drop",
|
||||
"probabilityPercent": 10,
|
||||
},
|
||||
],
|
||||
})
|
||||
}
|
||||
|
||||
fn pity_state_json() -> Value {
|
||||
json!({
|
||||
"rollsSinceRareOrBetter": 1,
|
||||
"rollsSinceLegendary": 8,
|
||||
"nonNewOutcomeStreak": 0,
|
||||
"guaranteedNewAvailable": true,
|
||||
})
|
||||
}
|
||||
|
||||
fn reveal_award_json() -> Value {
|
||||
json!({
|
||||
"awardId": "proof-drop-1",
|
||||
"awardedAt": 123,
|
||||
"sourceType": "proof-drop-box",
|
||||
"sourceRef": "support-ticket-1",
|
||||
"sourceSummary": "Manual proof-drop box",
|
||||
"outcomeKind": "unlock",
|
||||
"outcomeAvatarId": "prism",
|
||||
"metadataJson": "{\"revealable\":true}",
|
||||
"pityStateAfter": pity_state_json(),
|
||||
})
|
||||
}
|
||||
|
||||
fn expected_snapshot(equipped_avatar_id: &str) -> CodexAvatarInventoryReadResponse {
|
||||
expected_snapshot_for_user(equipped_avatar_id, "account-123")
|
||||
}
|
||||
|
||||
fn expected_snapshot_with_reveal(
|
||||
equipped_avatar_id: &str,
|
||||
account_user_id: &str,
|
||||
) -> CodexAvatarInventoryReadResponse {
|
||||
let mut snapshot = expected_snapshot_for_user(equipped_avatar_id, account_user_id);
|
||||
snapshot.pending_reveal_awards = vec![expected_reveal_award()];
|
||||
snapshot
|
||||
}
|
||||
|
||||
fn expected_snapshot_for_user(
|
||||
equipped_avatar_id: &str,
|
||||
account_user_id: &str,
|
||||
) -> CodexAvatarInventoryReadResponse {
|
||||
CodexAvatarInventoryReadResponse {
|
||||
account_user_id: account_user_id.to_string(),
|
||||
avatar_definitions: vec![
|
||||
CodexAvatarDefinition {
|
||||
avatar_id: "clippy".to_string(),
|
||||
display_name: "Clippy".to_string(),
|
||||
description: "The default Codex paperclip avatar".to_string(),
|
||||
rarity: CodexAvatarRarity::Common,
|
||||
asset_ref: "builtin:clippy".to_string(),
|
||||
status: CodexAvatarStatus::Active,
|
||||
sort_order: 0,
|
||||
collection_name: "Desktop Set".to_string(),
|
||||
collection_description: "Classic office companions.".to_string(),
|
||||
lore: "Helpful and persistent.".to_string(),
|
||||
accent_class_name: "bg-slate-100 text-slate-950".to_string(),
|
||||
silhouette_glow_class_name: "bg-slate-300/45".to_string(),
|
||||
is_progress_visible: true,
|
||||
},
|
||||
CodexAvatarDefinition {
|
||||
avatar_id: "prism".to_string(),
|
||||
display_name: "Prism".to_string(),
|
||||
description: "A shiny earnable avatar".to_string(),
|
||||
rarity: CodexAvatarRarity::Rare,
|
||||
asset_ref: "builtin:prism".to_string(),
|
||||
status: CodexAvatarStatus::Hidden,
|
||||
sort_order: 10,
|
||||
collection_name: "Prism Set".to_string(),
|
||||
collection_description: "Light-bent mascots.".to_string(),
|
||||
lore: "Glows softly.".to_string(),
|
||||
accent_class_name: "bg-violet-100 text-violet-950".to_string(),
|
||||
silhouette_glow_class_name: "bg-violet-300/45".to_string(),
|
||||
is_progress_visible: true,
|
||||
},
|
||||
],
|
||||
owned_avatars: vec![
|
||||
CodexAvatarOwnership {
|
||||
account_user_id: account_user_id.to_string(),
|
||||
avatar_id: "clippy".to_string(),
|
||||
source_summary: Some("Default avatar".to_string()),
|
||||
},
|
||||
CodexAvatarOwnership {
|
||||
account_user_id: account_user_id.to_string(),
|
||||
avatar_id: "prism".to_string(),
|
||||
source_summary: Some("Quest reward".to_string()),
|
||||
},
|
||||
],
|
||||
equipped_avatar_id: equipped_avatar_id.to_string(),
|
||||
box_rules: expected_box_rules(),
|
||||
pity_state: expected_pity_state(),
|
||||
pending_reveal_awards: Vec::new(),
|
||||
updated_at: 500,
|
||||
}
|
||||
}
|
||||
|
||||
fn expected_box_rules() -> CodexAvatarBoxRules {
|
||||
CodexAvatarBoxRules {
|
||||
ruleset_version: "signal-seasons-v1".to_string(),
|
||||
odds_table_version: "signal-seasons-v1".to_string(),
|
||||
rare_or_better_pity_threshold: 10,
|
||||
legendary_pity_threshold: 40,
|
||||
guaranteed_new_threshold: 6,
|
||||
odds: vec![
|
||||
CodexAvatarBoxOddsBucket {
|
||||
bucket_id: "common".to_string(),
|
||||
label: "Common".to_string(),
|
||||
probability_percent: 63,
|
||||
},
|
||||
CodexAvatarBoxOddsBucket {
|
||||
bucket_id: "rare".to_string(),
|
||||
label: "Rare".to_string(),
|
||||
probability_percent: 26,
|
||||
},
|
||||
CodexAvatarBoxOddsBucket {
|
||||
bucket_id: "legendary".to_string(),
|
||||
label: "Legendary".to_string(),
|
||||
probability_percent: 1,
|
||||
},
|
||||
CodexAvatarBoxOddsBucket {
|
||||
bucket_id: "no_drop".to_string(),
|
||||
label: "No drop".to_string(),
|
||||
probability_percent: 10,
|
||||
},
|
||||
],
|
||||
}
|
||||
}
|
||||
|
||||
fn expected_pity_state() -> CodexAvatarPityState {
|
||||
CodexAvatarPityState {
|
||||
rolls_since_rare_or_better: 1,
|
||||
rolls_since_legendary: 8,
|
||||
non_new_outcome_streak: 0,
|
||||
guaranteed_new_available: true,
|
||||
}
|
||||
}
|
||||
|
||||
fn expected_reveal_award() -> CodexAvatarRevealAward {
|
||||
CodexAvatarRevealAward {
|
||||
award_id: "proof-drop-1".to_string(),
|
||||
awarded_at: 123,
|
||||
source_type: "proof-drop-box".to_string(),
|
||||
source_ref: Some("support-ticket-1".to_string()),
|
||||
source_summary: Some("Manual proof-drop box".to_string()),
|
||||
outcome_kind: "unlock".to_string(),
|
||||
outcome_avatar_id: Some("prism".to_string()),
|
||||
metadata_json: Some("{\"revealable\":true}".to_string()),
|
||||
pity_state_after: expected_pity_state(),
|
||||
}
|
||||
}
|
||||
@@ -1,6 +1,7 @@
|
||||
mod account;
|
||||
mod analytics;
|
||||
mod app_list;
|
||||
mod avatar;
|
||||
mod collaboration_mode_list;
|
||||
#[cfg(unix)]
|
||||
mod command_exec;
|
||||
|
||||
@@ -1,8 +1,14 @@
|
||||
use crate::types::CodeTaskDetailsResponse;
|
||||
use crate::types::CodexAvatarAdminAwardGrantRequest;
|
||||
use crate::types::CodexAvatarAdminCapabilitiesResponse;
|
||||
use crate::types::CodexAvatarAdminProofDropGrantRequest;
|
||||
use crate::types::CodexAvatarEquipRequest;
|
||||
use crate::types::CodexAvatarInventoryResponse;
|
||||
use crate::types::ConfigFileResponse;
|
||||
use crate::types::PaginatedListTaskListItem;
|
||||
use crate::types::RateLimitStatusPayload;
|
||||
use crate::types::TurnAttemptsSiblingTurnsResponse;
|
||||
use crate::types::WorkspaceOutOfCreditsNotificationResponse;
|
||||
use anyhow::Result;
|
||||
use codex_client::build_reqwest_client_with_custom_ca;
|
||||
use codex_core::auth::CodexAuth;
|
||||
@@ -265,6 +271,97 @@ impl Client {
|
||||
Ok(Self::rate_limit_snapshots_from_payload(payload))
|
||||
}
|
||||
|
||||
pub async fn get_avatar_inventory(
|
||||
&self,
|
||||
) -> std::result::Result<CodexAvatarInventoryResponse, RequestError> {
|
||||
let url = match self.path_style {
|
||||
PathStyle::CodexApi => format!("{}/api/codex/avatars/inventory", self.base_url),
|
||||
PathStyle::ChatGptApi => format!("{}/wham/avatars/inventory", self.base_url),
|
||||
};
|
||||
let req = self.http.get(&url).headers(self.headers());
|
||||
let (body, ct) = self.exec_request_detailed(req, "GET", &url).await?;
|
||||
self.decode_json::<CodexAvatarInventoryResponse>(&url, &ct, &body)
|
||||
.map_err(RequestError::from)
|
||||
}
|
||||
|
||||
pub async fn equip_avatar(
|
||||
&self,
|
||||
avatar_id: String,
|
||||
) -> std::result::Result<CodexAvatarInventoryResponse, RequestError> {
|
||||
let url = match self.path_style {
|
||||
PathStyle::CodexApi => format!("{}/api/codex/avatars/equip", self.base_url),
|
||||
PathStyle::ChatGptApi => format!("{}/wham/avatars/equip", self.base_url),
|
||||
};
|
||||
let req = self
|
||||
.http
|
||||
.post(&url)
|
||||
.headers(self.headers())
|
||||
.header(CONTENT_TYPE, HeaderValue::from_static("application/json"))
|
||||
.json(&CodexAvatarEquipRequest { avatar_id });
|
||||
let (body, ct) = self.exec_request_detailed(req, "POST", &url).await?;
|
||||
self.decode_json::<CodexAvatarInventoryResponse>(&url, &ct, &body)
|
||||
.map_err(RequestError::from)
|
||||
}
|
||||
|
||||
pub async fn grant_admin_avatar_award(
|
||||
&self,
|
||||
request_body: CodexAvatarAdminAwardGrantRequest,
|
||||
) -> std::result::Result<CodexAvatarInventoryResponse, RequestError> {
|
||||
let url = match self.path_style {
|
||||
PathStyle::CodexApi => format!("{}/api/codex/avatars/admin/awards", self.base_url),
|
||||
PathStyle::ChatGptApi => format!("{}/wham/avatars/admin/awards", self.base_url),
|
||||
};
|
||||
let req = self
|
||||
.http
|
||||
.post(&url)
|
||||
.headers(self.headers())
|
||||
.header(CONTENT_TYPE, HeaderValue::from_static("application/json"))
|
||||
.json(&request_body);
|
||||
let (body, ct) = self.exec_request_detailed(req, "POST", &url).await?;
|
||||
self.decode_json::<CodexAvatarInventoryResponse>(&url, &ct, &body)
|
||||
.map_err(RequestError::from)
|
||||
}
|
||||
|
||||
pub async fn grant_admin_avatar_proof_drop(
|
||||
&self,
|
||||
request_body: CodexAvatarAdminProofDropGrantRequest,
|
||||
) -> std::result::Result<CodexAvatarInventoryResponse, RequestError> {
|
||||
let url = match self.path_style {
|
||||
PathStyle::CodexApi => {
|
||||
format!("{}/api/codex/avatars/admin/proof-drop", self.base_url)
|
||||
}
|
||||
PathStyle::ChatGptApi => {
|
||||
format!("{}/wham/avatars/admin/proof-drop", self.base_url)
|
||||
}
|
||||
};
|
||||
let req = self
|
||||
.http
|
||||
.post(&url)
|
||||
.headers(self.headers())
|
||||
.header(CONTENT_TYPE, HeaderValue::from_static("application/json"))
|
||||
.json(&request_body);
|
||||
let (body, ct) = self.exec_request_detailed(req, "POST", &url).await?;
|
||||
self.decode_json::<CodexAvatarInventoryResponse>(&url, &ct, &body)
|
||||
.map_err(RequestError::from)
|
||||
}
|
||||
|
||||
pub async fn get_avatar_admin_capabilities(
|
||||
&self,
|
||||
) -> std::result::Result<CodexAvatarAdminCapabilitiesResponse, RequestError> {
|
||||
let url = match self.path_style {
|
||||
PathStyle::CodexApi => {
|
||||
format!("{}/api/codex/avatars/admin/capabilities", self.base_url)
|
||||
}
|
||||
PathStyle::ChatGptApi => {
|
||||
format!("{}/wham/avatars/admin/capabilities", self.base_url)
|
||||
}
|
||||
};
|
||||
let req = self.http.get(&url).headers(self.headers());
|
||||
let (body, ct) = self.exec_request_detailed(req, "GET", &url).await?;
|
||||
self.decode_json::<CodexAvatarAdminCapabilitiesResponse>(&url, &ct, &body)
|
||||
.map_err(RequestError::from)
|
||||
}
|
||||
|
||||
pub async fn list_tasks(
|
||||
&self,
|
||||
limit: Option<i32>,
|
||||
@@ -357,6 +454,16 @@ impl Client {
|
||||
.map_err(RequestError::from)
|
||||
}
|
||||
|
||||
pub async fn post_workspace_out_of_credits_notification(
|
||||
&self,
|
||||
) -> std::result::Result<WorkspaceOutOfCreditsNotificationResponse, RequestError> {
|
||||
let url = self.workspace_out_of_credits_notification_url()?;
|
||||
let req = self.http.post(&url).headers(self.headers());
|
||||
let (body, ct) = self.exec_request_detailed(req, "POST", &url).await?;
|
||||
self.decode_json::<WorkspaceOutOfCreditsNotificationResponse>(&url, &ct, &body)
|
||||
.map_err(RequestError::from)
|
||||
}
|
||||
|
||||
/// Create a new task (user turn) by POSTing to the appropriate backend path
|
||||
/// based on `path_style`. Returns the created task id.
|
||||
pub async fn create_task(&self, request_body: serde_json::Value) -> Result<String> {
|
||||
@@ -498,6 +605,20 @@ impl Client {
|
||||
let seconds_i64 = i64::from(seconds);
|
||||
Some((seconds_i64 + 59) / 60)
|
||||
}
|
||||
|
||||
fn workspace_out_of_credits_notification_url(
|
||||
&self,
|
||||
) -> std::result::Result<String, RequestError> {
|
||||
match self.path_style {
|
||||
PathStyle::ChatGptApi => Ok(format!(
|
||||
"{}/credits/workspace_out_of_credits_notifications",
|
||||
self.base_url
|
||||
)),
|
||||
PathStyle::CodexApi => Err(RequestError::Other(anyhow::anyhow!(
|
||||
"workspace out-of-credits notifications are only supported for ChatGPT backend-api"
|
||||
))),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
@@ -649,4 +770,51 @@ mod tests {
|
||||
.unwrap_or_else(|| snapshots[0].clone());
|
||||
assert_eq!(preferred.limit_id.as_deref(), Some("codex"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn workspace_notification_url_uses_chatgpt_credits_path() {
|
||||
let client = Client::new("https://chatgpt.com").expect("client");
|
||||
|
||||
let url = client
|
||||
.workspace_out_of_credits_notification_url()
|
||||
.expect("chatgpt backend-api url");
|
||||
|
||||
assert_eq!(
|
||||
url,
|
||||
"https://chatgpt.com/backend-api/credits/workspace_out_of_credits_notifications"
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn workspace_notification_url_rejects_codex_api_paths() {
|
||||
let client = Client::new("https://api.openai.com").expect("client");
|
||||
|
||||
let err = client
|
||||
.workspace_out_of_credits_notification_url()
|
||||
.expect_err("codex api path should be rejected");
|
||||
|
||||
assert_eq!(
|
||||
err.to_string(),
|
||||
"workspace out-of-credits notifications are only supported for ChatGPT backend-api"
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn workspace_notification_response_decodes() {
|
||||
let client = Client::new("https://chatgpt.com").expect("client");
|
||||
let response: WorkspaceOutOfCreditsNotificationResponse = client
|
||||
.decode_json(
|
||||
"https://chatgpt.com/backend-api/credits/workspace_out_of_credits_notifications",
|
||||
"application/json",
|
||||
r#"{"status":"cooldown_active"}"#,
|
||||
)
|
||||
.expect("response should decode");
|
||||
|
||||
assert_eq!(
|
||||
response,
|
||||
WorkspaceOutOfCreditsNotificationResponse {
|
||||
status: crate::types::WorkspaceOutOfCreditsNotificationStatus::CooldownActive,
|
||||
}
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -5,7 +5,22 @@ pub use client::Client;
|
||||
pub use client::RequestError;
|
||||
pub use types::CodeTaskDetailsResponse;
|
||||
pub use types::CodeTaskDetailsResponseExt;
|
||||
pub use types::CodexAvatarAdminAwardGrantRequest;
|
||||
pub use types::CodexAvatarAdminCapabilitiesResponse;
|
||||
pub use types::CodexAvatarAdminProofDropGrantRequest;
|
||||
pub use types::CodexAvatarBoxOddsBucket;
|
||||
pub use types::CodexAvatarBoxRules;
|
||||
pub use types::CodexAvatarDefinition;
|
||||
pub use types::CodexAvatarEquipRequest;
|
||||
pub use types::CodexAvatarInventoryResponse;
|
||||
pub use types::CodexAvatarOwnership;
|
||||
pub use types::CodexAvatarPityState;
|
||||
pub use types::CodexAvatarRarity;
|
||||
pub use types::CodexAvatarRevealAward;
|
||||
pub use types::CodexAvatarStatus;
|
||||
pub use types::ConfigFileResponse;
|
||||
pub use types::PaginatedListTaskListItem;
|
||||
pub use types::TaskListItem;
|
||||
pub use types::TurnAttemptsSiblingTurnsResponse;
|
||||
pub use types::WorkspaceOutOfCreditsNotificationResponse;
|
||||
pub use types::WorkspaceOutOfCreditsNotificationStatus;
|
||||
|
||||
@@ -9,10 +9,159 @@ pub use codex_backend_openapi_models::models::RateLimitWindowSnapshot;
|
||||
pub use codex_backend_openapi_models::models::TaskListItem;
|
||||
|
||||
use serde::Deserialize;
|
||||
use serde::Serialize;
|
||||
use serde::de::Deserializer;
|
||||
use serde_json::Value;
|
||||
use std::collections::HashMap;
|
||||
|
||||
#[derive(Clone, Debug, Deserialize, PartialEq, Eq)]
|
||||
#[serde(rename_all = "snake_case")]
|
||||
pub enum WorkspaceOutOfCreditsNotificationStatus {
|
||||
Sent,
|
||||
CooldownActive,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Deserialize, PartialEq, Eq)]
|
||||
pub struct WorkspaceOutOfCreditsNotificationResponse {
|
||||
pub status: WorkspaceOutOfCreditsNotificationStatus,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Deserialize, PartialEq, Eq)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub enum CodexAvatarStatus {
|
||||
Active,
|
||||
Hidden,
|
||||
Retired,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Deserialize, PartialEq, Eq)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub enum CodexAvatarRarity {
|
||||
Common,
|
||||
Rare,
|
||||
Epic,
|
||||
Legendary,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Deserialize, PartialEq, Eq)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct CodexAvatarDefinition {
|
||||
pub avatar_id: String,
|
||||
pub display_name: String,
|
||||
pub description: String,
|
||||
pub rarity: CodexAvatarRarity,
|
||||
pub asset_ref: String,
|
||||
pub status: CodexAvatarStatus,
|
||||
pub sort_order: i64,
|
||||
pub collection_name: String,
|
||||
pub collection_description: String,
|
||||
pub lore: String,
|
||||
pub accent_class_name: String,
|
||||
pub silhouette_glow_class_name: String,
|
||||
pub is_progress_visible: bool,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Deserialize, PartialEq, Eq)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct CodexAvatarOwnership {
|
||||
pub account_user_id: String,
|
||||
pub avatar_id: String,
|
||||
pub source_summary: Option<String>,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Deserialize, Serialize, PartialEq, Eq)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct CodexAvatarEquipRequest {
|
||||
pub avatar_id: String,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Deserialize, Serialize, PartialEq, Eq)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct CodexAvatarAdminAwardGrantRequest {
|
||||
pub account_user_id: String,
|
||||
pub award_id: String,
|
||||
pub avatar_id: String,
|
||||
pub source_type: String,
|
||||
pub source_ref: Option<String>,
|
||||
pub awarded_at: Option<i64>,
|
||||
pub awarded_by: Option<String>,
|
||||
pub metadata_json: Option<String>,
|
||||
pub source_summary: Option<String>,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Deserialize, Serialize, PartialEq, Eq)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct CodexAvatarAdminProofDropGrantRequest {
|
||||
pub account_user_id: String,
|
||||
pub award_id: String,
|
||||
pub source_type: String,
|
||||
pub source_ref: Option<String>,
|
||||
pub awarded_at: Option<i64>,
|
||||
pub source_summary: Option<String>,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Deserialize, PartialEq, Eq)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct CodexAvatarBoxOddsBucket {
|
||||
pub bucket_id: String,
|
||||
pub label: String,
|
||||
pub probability_percent: i64,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Deserialize, PartialEq, Eq)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct CodexAvatarPityState {
|
||||
pub rolls_since_rare_or_better: i64,
|
||||
pub rolls_since_legendary: i64,
|
||||
pub non_new_outcome_streak: i64,
|
||||
pub guaranteed_new_available: bool,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Deserialize, PartialEq, Eq)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct CodexAvatarBoxRules {
|
||||
pub ruleset_version: String,
|
||||
pub odds_table_version: String,
|
||||
pub rare_or_better_pity_threshold: i64,
|
||||
pub legendary_pity_threshold: i64,
|
||||
pub guaranteed_new_threshold: i64,
|
||||
pub odds: Vec<CodexAvatarBoxOddsBucket>,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Deserialize, PartialEq, Eq)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct CodexAvatarRevealAward {
|
||||
pub award_id: String,
|
||||
pub awarded_at: i64,
|
||||
pub source_type: String,
|
||||
pub source_ref: Option<String>,
|
||||
pub source_summary: Option<String>,
|
||||
pub outcome_kind: String,
|
||||
pub outcome_avatar_id: Option<String>,
|
||||
pub metadata_json: Option<String>,
|
||||
pub pity_state_after: CodexAvatarPityState,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Deserialize, PartialEq, Eq)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct CodexAvatarInventoryResponse {
|
||||
pub account_user_id: String,
|
||||
pub avatar_definitions: Vec<CodexAvatarDefinition>,
|
||||
pub owned_avatars: Vec<CodexAvatarOwnership>,
|
||||
pub equipped_avatar_id: String,
|
||||
pub box_rules: CodexAvatarBoxRules,
|
||||
pub pity_state: CodexAvatarPityState,
|
||||
pub pending_reveal_awards: Vec<CodexAvatarRevealAward>,
|
||||
pub updated_at: i64,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Deserialize, PartialEq, Eq)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct CodexAvatarAdminCapabilitiesResponse {
|
||||
pub can_grant_awards: bool,
|
||||
pub can_grant_proof_drop_boxes: bool,
|
||||
}
|
||||
|
||||
/// Hand-rolled models for the Cloud Tasks task-details response.
|
||||
/// The generated OpenAPI models are pretty bad. This is a half-step
|
||||
/// towards hand-rolling them.
|
||||
|
||||
@@ -26,6 +26,7 @@ clap = { workspace = true, features = ["derive"] }
|
||||
codex-arg0 = { workspace = true }
|
||||
codex-app-server-client = { workspace = true }
|
||||
codex-app-server-protocol = { workspace = true }
|
||||
codex-backend-client = { workspace = true }
|
||||
codex-cloud-requirements = { workspace = true }
|
||||
codex-core = { workspace = true }
|
||||
codex-feedback = { workspace = true }
|
||||
|
||||
@@ -9,6 +9,7 @@ mod event_processor;
|
||||
mod event_processor_with_human_output;
|
||||
pub mod event_processor_with_jsonl_output;
|
||||
pub mod exec_events;
|
||||
mod workspace_out_of_credits;
|
||||
|
||||
pub use cli::Cli;
|
||||
pub use cli::Command;
|
||||
@@ -730,6 +731,7 @@ async fn run_exec_session(args: ExecRunArgs) -> anyhow::Result<()> {
|
||||
// Track whether a fatal error was reported by the server so we can
|
||||
// exit with a non-zero status for automation-friendly signaling.
|
||||
let mut error_seen = false;
|
||||
let mut workspace_out_of_credits_candidate = false;
|
||||
let mut interrupt_channel_open = true;
|
||||
let primary_thread_id_for_requests = primary_thread_id.to_string();
|
||||
loop {
|
||||
@@ -768,6 +770,14 @@ async fn run_exec_session(args: ExecRunArgs) -> anyhow::Result<()> {
|
||||
handle_server_request(&client, request, &mut error_seen).await;
|
||||
}
|
||||
InProcessServerEvent::ServerNotification(mut notification) => {
|
||||
if workspace_out_of_credits::notification_is_usage_limit_failure(
|
||||
¬ification,
|
||||
&primary_thread_id_for_requests,
|
||||
&task_id,
|
||||
) {
|
||||
workspace_out_of_credits_candidate = true;
|
||||
}
|
||||
|
||||
if let ServerNotification::Error(payload) = ¬ification {
|
||||
if payload.thread_id == primary_thread_id_for_requests
|
||||
&& payload.turn_id == task_id
|
||||
@@ -824,6 +834,15 @@ async fn run_exec_session(args: ExecRunArgs) -> anyhow::Result<()> {
|
||||
warn!("in-process app-server shutdown failed: {err}");
|
||||
}
|
||||
event_processor.print_final_output();
|
||||
if let Err(err) = workspace_out_of_credits::maybe_prompt_for_workspace_out_of_credits(
|
||||
&config,
|
||||
workspace_out_of_credits_candidate && error_seen,
|
||||
!json_mode,
|
||||
)
|
||||
.await
|
||||
{
|
||||
warn!("workspace out-of-credits prompt failed: {err}");
|
||||
}
|
||||
if error_seen {
|
||||
std::process::exit(1);
|
||||
}
|
||||
|
||||
376
codex-rs/exec/src/workspace_out_of_credits.rs
Normal file
376
codex-rs/exec/src/workspace_out_of_credits.rs
Normal file
@@ -0,0 +1,376 @@
|
||||
use codex_app_server_protocol::CodexErrorInfo;
|
||||
use codex_app_server_protocol::ServerNotification;
|
||||
use codex_app_server_protocol::TurnError;
|
||||
use codex_backend_client::Client as BackendClient;
|
||||
use codex_backend_client::WorkspaceOutOfCreditsNotificationStatus;
|
||||
use codex_core::AuthManager;
|
||||
use codex_core::CodexAuth;
|
||||
use codex_core::config::Config;
|
||||
use codex_protocol::account::PlanType;
|
||||
use codex_protocol::protocol::RateLimitSnapshot;
|
||||
use std::io;
|
||||
use std::io::IsTerminal;
|
||||
|
||||
const REQUEST_PROMPT: &str =
|
||||
"Workspace out of credits. Request more from your workspace owner? [y/N]";
|
||||
const REQUEST_SENT_MESSAGE: &str = "Request sent!";
|
||||
|
||||
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
|
||||
struct PromptDecisionInput {
|
||||
candidate_usage_limit_failure: bool,
|
||||
human_output: bool,
|
||||
stdin_is_tty: bool,
|
||||
stderr_is_tty: bool,
|
||||
auth_supports_request: bool,
|
||||
}
|
||||
|
||||
pub(crate) fn notification_is_usage_limit_failure(
|
||||
notification: &ServerNotification,
|
||||
thread_id: &str,
|
||||
turn_id: &str,
|
||||
) -> bool {
|
||||
match notification {
|
||||
ServerNotification::Error(payload) => {
|
||||
payload.thread_id == thread_id
|
||||
&& payload.turn_id == turn_id
|
||||
&& !payload.will_retry
|
||||
&& turn_error_is_usage_limit(&payload.error)
|
||||
}
|
||||
ServerNotification::TurnCompleted(payload) => {
|
||||
payload.thread_id == thread_id
|
||||
&& payload.turn.id == turn_id
|
||||
&& payload.turn.status == codex_app_server_protocol::TurnStatus::Failed
|
||||
&& payload
|
||||
.turn
|
||||
.error
|
||||
.as_ref()
|
||||
.is_some_and(turn_error_is_usage_limit)
|
||||
}
|
||||
_ => false,
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) async fn maybe_prompt_for_workspace_out_of_credits(
|
||||
config: &Config,
|
||||
candidate_usage_limit_failure: bool,
|
||||
human_output: bool,
|
||||
) -> anyhow::Result<()> {
|
||||
let stdin_is_tty = io::stdin().is_terminal();
|
||||
let stderr_is_tty = io::stderr().is_terminal();
|
||||
let initial_decision = PromptDecisionInput {
|
||||
candidate_usage_limit_failure,
|
||||
human_output,
|
||||
stdin_is_tty,
|
||||
stderr_is_tty,
|
||||
auth_supports_request: false,
|
||||
};
|
||||
if !terminal_prompt_prerequisites_met(initial_decision) {
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
let auth_manager = AuthManager::shared(
|
||||
config.codex_home.clone(),
|
||||
/*enable_codex_api_key_env*/ true,
|
||||
config.cli_auth_credentials_store_mode,
|
||||
);
|
||||
let auth = auth_manager.auth().await;
|
||||
let auth_supports_request = auth_supports_workspace_request(auth.as_ref());
|
||||
let decision = PromptDecisionInput {
|
||||
auth_supports_request,
|
||||
..initial_decision
|
||||
};
|
||||
if !decision.auth_supports_request {
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
let Some(auth) = auth else {
|
||||
return Ok(());
|
||||
};
|
||||
let client = BackendClient::from_auth(config.chatgpt_base_url.clone(), &auth)?;
|
||||
let rate_limits = client.get_rate_limits().await?;
|
||||
if !should_offer_workspace_out_of_credits_prompt(decision, Some(&rate_limits)) {
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
if !prompt_for_workspace_request()? {
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
let response = client.post_workspace_out_of_credits_notification().await?;
|
||||
match response.status {
|
||||
WorkspaceOutOfCreditsNotificationStatus::Sent
|
||||
| WorkspaceOutOfCreditsNotificationStatus::CooldownActive => {
|
||||
eprintln!("{REQUEST_SENT_MESSAGE}");
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn turn_error_is_usage_limit(error: &TurnError) -> bool {
|
||||
matches!(
|
||||
error.codex_error_info,
|
||||
Some(CodexErrorInfo::UsageLimitExceeded)
|
||||
)
|
||||
}
|
||||
|
||||
fn terminal_prompt_prerequisites_met(input: PromptDecisionInput) -> bool {
|
||||
input.candidate_usage_limit_failure
|
||||
&& input.human_output
|
||||
&& input.stdin_is_tty
|
||||
&& input.stderr_is_tty
|
||||
}
|
||||
|
||||
fn auth_supports_workspace_request(auth: Option<&CodexAuth>) -> bool {
|
||||
auth.is_some_and(|auth| auth.is_chatgpt_auth() && auth.get_account_id().is_some())
|
||||
}
|
||||
|
||||
fn should_offer_workspace_out_of_credits_prompt(
|
||||
input: PromptDecisionInput,
|
||||
snapshot: Option<&RateLimitSnapshot>,
|
||||
) -> bool {
|
||||
terminal_prompt_prerequisites_met(input)
|
||||
&& input.auth_supports_request
|
||||
&& snapshot.is_some_and(rate_limits_confirm_workspace_out_of_credits)
|
||||
}
|
||||
|
||||
fn rate_limits_confirm_workspace_out_of_credits(snapshot: &RateLimitSnapshot) -> bool {
|
||||
if snapshot.plan_type != Some(PlanType::SelfServeBusinessUsageBased) {
|
||||
return false;
|
||||
}
|
||||
|
||||
let Some(credits) = snapshot.credits.as_ref() else {
|
||||
return false;
|
||||
};
|
||||
if credits.unlimited {
|
||||
return false;
|
||||
}
|
||||
|
||||
credits
|
||||
.balance
|
||||
.as_deref()
|
||||
.and_then(parse_credit_balance)
|
||||
.is_some_and(|balance| balance <= 0.0)
|
||||
}
|
||||
|
||||
fn parse_credit_balance(balance: &str) -> Option<f64> {
|
||||
balance.trim().parse::<f64>().ok()
|
||||
}
|
||||
|
||||
fn prompt_for_workspace_request() -> io::Result<bool> {
|
||||
eprintln!("{REQUEST_PROMPT}");
|
||||
|
||||
let mut input = String::new();
|
||||
io::stdin().read_line(&mut input)?;
|
||||
Ok(parse_affirmative_response(&input))
|
||||
}
|
||||
|
||||
fn parse_affirmative_response(input: &str) -> bool {
|
||||
let answer = input.trim();
|
||||
answer.eq_ignore_ascii_case("y") || answer.eq_ignore_ascii_case("yes")
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use codex_app_server_protocol::ErrorNotification;
|
||||
use codex_app_server_protocol::Turn;
|
||||
use codex_app_server_protocol::TurnCompletedNotification;
|
||||
use codex_app_server_protocol::TurnStatus;
|
||||
use codex_protocol::protocol::CreditsSnapshot;
|
||||
use pretty_assertions::assert_eq;
|
||||
|
||||
fn zero_credit_snapshot() -> RateLimitSnapshot {
|
||||
RateLimitSnapshot {
|
||||
limit_id: Some("codex".to_string()),
|
||||
limit_name: None,
|
||||
primary: None,
|
||||
secondary: None,
|
||||
credits: Some(CreditsSnapshot {
|
||||
has_credits: false,
|
||||
unlimited: false,
|
||||
balance: Some("0".to_string()),
|
||||
}),
|
||||
plan_type: Some(PlanType::SelfServeBusinessUsageBased),
|
||||
}
|
||||
}
|
||||
|
||||
fn usage_limit_turn_error() -> TurnError {
|
||||
TurnError {
|
||||
message: "You've hit your usage limit.".to_string(),
|
||||
codex_error_info: Some(CodexErrorInfo::UsageLimitExceeded),
|
||||
additional_details: None,
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn error_notification_marks_usage_limit_failure() {
|
||||
let notification = ServerNotification::Error(ErrorNotification {
|
||||
error: usage_limit_turn_error(),
|
||||
will_retry: false,
|
||||
thread_id: "thread-1".to_string(),
|
||||
turn_id: "turn-1".to_string(),
|
||||
});
|
||||
|
||||
assert!(notification_is_usage_limit_failure(
|
||||
¬ification,
|
||||
"thread-1",
|
||||
"turn-1"
|
||||
));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn unrelated_notification_does_not_mark_usage_limit_failure() {
|
||||
let notification = ServerNotification::Error(ErrorNotification {
|
||||
error: TurnError {
|
||||
message: "network failed".to_string(),
|
||||
codex_error_info: Some(CodexErrorInfo::ResponseStreamDisconnected {
|
||||
http_status_code: Some(503),
|
||||
}),
|
||||
additional_details: None,
|
||||
},
|
||||
will_retry: false,
|
||||
thread_id: "thread-1".to_string(),
|
||||
turn_id: "turn-1".to_string(),
|
||||
});
|
||||
|
||||
assert!(!notification_is_usage_limit_failure(
|
||||
¬ification,
|
||||
"thread-1",
|
||||
"turn-1"
|
||||
));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn failed_turn_marks_usage_limit_failure() {
|
||||
let notification = ServerNotification::TurnCompleted(TurnCompletedNotification {
|
||||
thread_id: "thread-1".to_string(),
|
||||
turn: Turn {
|
||||
id: "turn-1".to_string(),
|
||||
items: Vec::new(),
|
||||
status: TurnStatus::Failed,
|
||||
error: Some(usage_limit_turn_error()),
|
||||
},
|
||||
});
|
||||
|
||||
assert!(notification_is_usage_limit_failure(
|
||||
¬ification,
|
||||
"thread-1",
|
||||
"turn-1"
|
||||
));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn prompt_decision_requires_ttys_and_human_output() {
|
||||
let snapshot = zero_credit_snapshot();
|
||||
let input = PromptDecisionInput {
|
||||
candidate_usage_limit_failure: true,
|
||||
human_output: false,
|
||||
stdin_is_tty: true,
|
||||
stderr_is_tty: true,
|
||||
auth_supports_request: true,
|
||||
};
|
||||
|
||||
assert!(!should_offer_workspace_out_of_credits_prompt(
|
||||
input,
|
||||
Some(&snapshot)
|
||||
));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn prompt_decision_requires_supported_auth() {
|
||||
let snapshot = zero_credit_snapshot();
|
||||
let input = PromptDecisionInput {
|
||||
candidate_usage_limit_failure: true,
|
||||
human_output: true,
|
||||
stdin_is_tty: true,
|
||||
stderr_is_tty: true,
|
||||
auth_supports_request: false,
|
||||
};
|
||||
|
||||
assert!(!should_offer_workspace_out_of_credits_prompt(
|
||||
input,
|
||||
Some(&snapshot)
|
||||
));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn workspace_out_of_credits_confirmation_requires_zero_balance() {
|
||||
assert!(rate_limits_confirm_workspace_out_of_credits(
|
||||
&zero_credit_snapshot()
|
||||
));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn workspace_out_of_credits_confirmation_rejects_missing_balance() {
|
||||
let mut snapshot = zero_credit_snapshot();
|
||||
snapshot.credits.as_mut().expect("credits").balance = None;
|
||||
|
||||
assert!(!rate_limits_confirm_workspace_out_of_credits(&snapshot));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn workspace_out_of_credits_confirmation_rejects_positive_balance() {
|
||||
let mut snapshot = zero_credit_snapshot();
|
||||
snapshot.credits.as_mut().expect("credits").balance = Some("12.5".to_string());
|
||||
|
||||
assert!(!rate_limits_confirm_workspace_out_of_credits(&snapshot));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn workspace_out_of_credits_confirmation_rejects_wrong_plan() {
|
||||
let mut snapshot = zero_credit_snapshot();
|
||||
snapshot.plan_type = Some(PlanType::Team);
|
||||
|
||||
assert!(!rate_limits_confirm_workspace_out_of_credits(&snapshot));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn workspace_out_of_credits_confirmation_rejects_unlimited_credits() {
|
||||
let mut snapshot = zero_credit_snapshot();
|
||||
snapshot.credits = Some(CreditsSnapshot {
|
||||
has_credits: true,
|
||||
unlimited: true,
|
||||
balance: Some("0".to_string()),
|
||||
});
|
||||
|
||||
assert!(!rate_limits_confirm_workspace_out_of_credits(&snapshot));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn chatgpt_auth_with_account_id_is_supported() {
|
||||
let auth = CodexAuth::create_dummy_chatgpt_auth_for_testing();
|
||||
|
||||
assert!(auth_supports_workspace_request(Some(&auth)));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn api_key_auth_is_not_supported() {
|
||||
let auth = CodexAuth::from_api_key("test-key");
|
||||
|
||||
assert!(!auth_supports_workspace_request(Some(&auth)));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn affirmative_response_accepts_y_and_yes() {
|
||||
assert!(parse_affirmative_response("y"));
|
||||
assert!(parse_affirmative_response("YES"));
|
||||
assert!(!parse_affirmative_response(""));
|
||||
assert!(!parse_affirmative_response("n"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn prompt_decision_accepts_usage_limit_zero_credit_case() {
|
||||
let input = PromptDecisionInput {
|
||||
candidate_usage_limit_failure: true,
|
||||
human_output: true,
|
||||
stdin_is_tty: true,
|
||||
stderr_is_tty: true,
|
||||
auth_supports_request: true,
|
||||
};
|
||||
|
||||
assert_eq!(
|
||||
should_offer_workspace_out_of_credits_prompt(input, Some(&zero_credit_snapshot())),
|
||||
true
|
||||
);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user