Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions rust/src/generated/api_types.rs
Original file line number Diff line number Diff line change
Expand Up @@ -703,6 +703,9 @@ pub struct AccountGetQuotaRequest {
#[derive(Debug, Clone, Default, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct AccountQuotaSnapshot {
/// Number of AI credits used so far this period, when reported by dotcom
#[serde(skip_serializing_if = "Option::is_none")]
pub credits_used: Option<f64>,
/// Number of requests included in the entitlement, or -1 for unlimited entitlements
pub entitlement_requests: i64,
/// Whether the user has an unlimited usage entitlement
Expand Down
44 changes: 42 additions & 2 deletions rust/tests/api_types_test.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,9 @@
#![allow(clippy::unwrap_used)]

use github_copilot_sdk::rpc::{
Extension, ExtensionList, ExtensionSource, ExtensionStatus, ExtensionsDisableRequest,
ExtensionsEnableRequest, FleetStartRequest, FleetStartResult, TasksStartAgentRequest,
AccountQuotaSnapshot, Extension, ExtensionList, ExtensionSource, ExtensionStatus,
ExtensionsDisableRequest, ExtensionsEnableRequest, FleetStartRequest, FleetStartResult,
TasksStartAgentRequest,
};

#[test]
Expand Down Expand Up @@ -84,6 +85,45 @@ fn tasks_start_agent_request_fields_are_accessible() {
assert_eq!(request.description.as_deref(), Some("SDK task agent"));
}

#[test]
fn account_quota_snapshot_supports_omitted_credits_used() {
let snapshot: AccountQuotaSnapshot =
serde_json::from_value(account_quota_snapshot_json()).unwrap();

assert_eq!(snapshot.credits_used, None);
assert!(
serde_json::to_value(snapshot)
.unwrap()
.get("creditsUsed")
.is_none()
);
}

#[test]
fn account_quota_snapshot_credits_used_round_trips_as_camel_case() {
let mut json = account_quota_snapshot_json();
json["creditsUsed"] = serde_json::json!(12.5);

let snapshot: AccountQuotaSnapshot = serde_json::from_value(json).unwrap();
assert_eq!(snapshot.credits_used, Some(12.5));

let serialized = serde_json::to_value(snapshot).unwrap();
assert_eq!(serialized["creditsUsed"], serde_json::json!(12.5));
assert!(serialized.get("credits_used").is_none());
}

fn account_quota_snapshot_json() -> serde_json::Value {
serde_json::json!({
"entitlementRequests": 100,
"isUnlimitedEntitlement": false,
"overage": 0,
"overageAllowedWithExhaustedQuota": false,
"remainingPercentage": 75,
"usageAllowedWithExhaustedQuota": true,
"usedRequests": 25
})
}

fn running_extension(id: &str, name: &str) -> Extension {
Extension {
id: id.to_string(),
Expand Down
19 changes: 19 additions & 0 deletions scripts/codegen/rust.ts
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,24 @@ const EXTERNAL_SCHEMA_RUST_TYPE_MODULE: Record<string, Record<string, string>> =
},
};

// account.getQuota can include this dotcom metadata before the bundled CLI schema advertises it.
function addAccountQuotaCreditsUsed(schema: ApiSchema): void {
for (const definitions of [schema.definitions, schema.$defs]) {
const snapshot = definitions?.AccountQuotaSnapshot;
if (!snapshot || typeof snapshot !== "object") continue;

const properties = snapshot.properties;
if (!properties) continue;

properties.creditsUsed ??= {
type: "number",
minimum: 0,
description:
"Number of AI credits used so far this period, when reported by dotcom",
};
}
}

function rustDeprecatedAttributes(indent = ""): string[] {
return [`${indent}#[doc(hidden)]`, `${indent}#[deprecated]`];
}
Expand Down Expand Up @@ -2165,6 +2183,7 @@ async function generate(): Promise<void> {
const apiRaw = normalizeSchemaBrandCasing(
JSON.parse(await fs.readFile(apiSchemaPath, "utf-8")) as ApiSchema,
);
addAccountQuotaCreditsUsed(apiRaw);

const sessionEventsSchema = propagateInternalVisibility(
postProcessSchema(
Expand Down
Loading