@@ -33113,3 +33212,28 @@ pub enum WorkspacesWorkspaceDetailsHostType {
#[serde(other)]
Unknown,
}
+
+/// Current normalized autopilot objective lifecycle status.
+///
+///
+///
+/// **Experimental.** This type is part of an experimental wire-protocol surface
+/// and may change or be removed in future SDK or CLI releases.
+///
+///
+#[derive(Debug, Clone, Default, PartialEq, Eq, Serialize, Deserialize)]
+pub enum AutopilotObjectiveStatus {
+ /// The objective is actively running.
+ #[serde(rename = "active")]
+ Active,
+ /// The objective is paused and may be resumed.
+ #[serde(rename = "paused")]
+ Paused,
+ /// The objective completed.
+ #[serde(rename = "completed")]
+ Completed,
+ /// Unknown variant for forward compatibility.
+ #[default]
+ #[serde(other)]
+ Unknown,
+}
diff --git a/rust/src/generated/rpc.rs b/rust/src/generated/rpc.rs
index c955637e31..ec5c7ef8f7 100644
--- a/rust/src/generated/rpc.rs
+++ b/rust/src/generated/rpc.rs
@@ -3003,6 +3003,13 @@ impl<'a> SessionRpc<'a> {
}
}
+ /// `session.autopilotObjective.*` sub-namespace.
+ pub fn autopilot_objective(&self) -> SessionRpcAutopilotObjective<'a> {
+ SessionRpcAutopilotObjective {
+ session: self.session,
+ }
+ }
+
/// `session.canvas.*` sub-namespace.
pub fn canvas(&self) -> SessionRpcCanvas<'a> {
SessionRpcCanvas {
@@ -3718,6 +3725,42 @@ impl<'a> SessionRpcAgent<'a> {
}
}
+/// `session.autopilotObjective.*` RPCs.
+#[derive(Clone, Copy)]
+pub struct SessionRpcAutopilotObjective<'a> {
+ pub(crate) session: &'a Session,
+}
+
+impl<'a> SessionRpcAutopilotObjective<'a> {
+ /// Reads the current canonical autopilot objective state for this session.
+ ///
+ /// Wire method: `session.autopilotObjective.getState`.
+ ///
+ /// # Returns
+ ///
+ /// Canonical runtime state for the session's current autopilot objective.
+ ///
+ ///
+ ///
+ /// **Experimental.** This API is part of an experimental wire-protocol surface
+ /// and may change or be removed in future SDK or CLI releases. Pin both the
+ /// SDK and CLI versions if your code depends on it.
+ ///
+ ///
+ pub async fn get_state(&self) -> Result
{
+ let wire_params = serde_json::json!({ "sessionId": self.session.id() });
+ let _value = self
+ .session
+ .client()
+ .call(
+ rpc_methods::SESSION_AUTOPILOTOBJECTIVE_GETSTATE,
+ Some(wire_params),
+ )
+ .await?;
+ Ok(serde_json::from_value(_value)?)
+ }
+}
+
/// `session.canvas.*` RPCs.
#[derive(Clone, Copy)]
pub struct SessionRpcCanvas<'a> {
diff --git a/rust/tests/api_types_test.rs b/rust/tests/api_types_test.rs
index 9b86b1367a..1620de00b2 100644
--- a/rust/tests/api_types_test.rs
+++ b/rust/tests/api_types_test.rs
@@ -4,8 +4,9 @@
#![allow(clippy::unwrap_used)]
use github_copilot_sdk::rpc::{
- Extension, ExtensionList, ExtensionSource, ExtensionStatus, ExtensionsDisableRequest,
- ExtensionsEnableRequest, FleetStartRequest, FleetStartResult, TasksStartAgentRequest,
+ AutopilotObjectiveGetStateResult, AutopilotObjectiveStatus, Extension, ExtensionList,
+ ExtensionSource, ExtensionStatus, ExtensionsDisableRequest, ExtensionsEnableRequest,
+ FleetStartRequest, FleetStartResult, TasksStartAgentRequest,
};
use github_copilot_sdk::session_events::{PermissionRequest, PermissionRequestedData};
@@ -104,6 +105,63 @@ fn permission_event_exposes_managed_approval_required() {
assert_eq!(request.managed_approval_required, Some(true));
}
+#[test]
+fn autopilot_objective_state_preserves_canonical_payloads() {
+ let no_objective: AutopilotObjectiveGetStateResult =
+ serde_json::from_str(r#"{"state":null}"#).unwrap();
+ assert!(no_objective.state.is_none());
+
+ let active: AutopilotObjectiveGetStateResult = serde_json::from_str(
+ r#"{"state":{"id":1,"objective":"Ship the release","status":"active","turnCount":2,"creditCountNanoAiu":"0"}}"#,
+ )
+ .unwrap();
+ let active = active.state.unwrap();
+ assert_eq!(active.id, 1);
+ assert_eq!(active.objective, "Ship the release");
+ assert_eq!(active.status, AutopilotObjectiveStatus::Active);
+ assert_eq!(active.turn_count, 2);
+ assert_eq!(active.credit_count_nano_aiu, "0");
+ let active_json = serde_json::to_value(active).unwrap();
+ assert!(active_json.get("pauseReason").is_none());
+ assert!(active_json.get("completionSummary").is_none());
+ assert!(active_json.get("creditLimit").is_none());
+
+ let paused: AutopilotObjectiveGetStateResult = serde_json::from_str(
+ r#"{"state":{"id":2,"objective":"Wait for approval","status":"paused","turnCount":3,"pauseReason":"Approval required","creditCountNanoAiu":"9007199254740993","creditLimit":{"creditsUsed":9007199.254740993,"creditsUsedNanoAiu":"9007199254740993"}}}"#,
+ )
+ .unwrap();
+ let paused = paused.state.unwrap();
+ assert_eq!(paused.id, 2);
+ assert_eq!(paused.objective, "Wait for approval");
+ assert_eq!(paused.status, AutopilotObjectiveStatus::Paused);
+ assert_eq!(paused.turn_count, 3);
+ assert_eq!(paused.pause_reason.as_deref(), Some("Approval required"));
+ assert_eq!(paused.credit_count_nano_aiu, "9007199254740993");
+ let paused_credit_limit = paused.credit_limit.unwrap();
+ assert_eq!(paused_credit_limit.credits, None);
+ assert_eq!(paused_credit_limit.credits_used, 9007199.254740993);
+ assert_eq!(
+ paused_credit_limit.credits_used_nano_aiu,
+ "9007199254740993"
+ );
+
+ let completed: AutopilotObjectiveGetStateResult = serde_json::from_str(
+ r#"{"state":{"id":3,"objective":"Publish the SDK","status":"completed","turnCount":4,"completionSummary":"Published","creditCountNanoAiu":"9007199254740994","creditLimit":{"credits":2.5,"creditsUsed":1.25,"creditsUsedNanoAiu":"1250000000"}}}"#,
+ )
+ .unwrap();
+ let completed = completed.state.unwrap();
+ assert_eq!(completed.id, 3);
+ assert_eq!(completed.objective, "Publish the SDK");
+ assert_eq!(completed.status, AutopilotObjectiveStatus::Completed);
+ assert_eq!(completed.turn_count, 4);
+ assert_eq!(completed.completion_summary.as_deref(), Some("Published"));
+ assert_eq!(completed.credit_count_nano_aiu, "9007199254740994");
+ let completed_credit_limit = completed.credit_limit.unwrap();
+ assert_eq!(completed_credit_limit.credits, Some(2.5));
+ assert_eq!(completed_credit_limit.credits_used, 1.25);
+ assert_eq!(completed_credit_limit.credits_used_nano_aiu, "1250000000");
+}
+
fn running_extension(id: &str, name: &str) -> Extension {
Extension {
id: id.to_string(),
diff --git a/rust/tests/e2e/rpc_session_state.rs b/rust/tests/e2e/rpc_session_state.rs
index 15db6e3a9a..5572335259 100644
--- a/rust/tests/e2e/rpc_session_state.rs
+++ b/rust/tests/e2e/rpc_session_state.rs
@@ -226,6 +226,36 @@ async fn should_set_and_get_each_session_mode_value() {
.await;
}
+#[tokio::test]
+async fn should_get_empty_autopilot_objective_state() {
+ super::support::with_shared_e2e_context(
+ &E2E,
+ "rpc_session_state",
+ "should_get_empty_autopilot_objective_state",
+ |ctx| {
+ Box::pin(async move {
+ let client = ctx.start_client().await;
+ let session = client
+ .create_session(ctx.approve_all_session_config())
+ .await
+ .expect("create session");
+
+ let result = session
+ .rpc()
+ .autopilot_objective()
+ .get_state()
+ .await
+ .expect("get autopilot objective state");
+ assert!(result.state.is_none());
+
+ session.disconnect().await.expect("disconnect session");
+ client.stop().await.expect("stop client");
+ })
+ },
+ )
+ .await;
+}
+
#[tokio::test]
async fn should_read_update_and_delete_plan() {
super::support::with_shared_e2e_context(
@@ -1212,4 +1242,4 @@ fn assistant_message_content_if_present(
}
}
static E2E: super::support::SharedE2eGroup =
- super::support::SharedE2eGroup::standard("rpc_session_state", 22);
+ super::support::SharedE2eGroup::standard("rpc_session_state", 23);
diff --git a/rust/tests/session_test.rs b/rust/tests/session_test.rs
index 69a65a558a..4536705b50 100644
--- a/rust/tests/session_test.rs
+++ b/rust/tests/session_test.rs
@@ -4088,6 +4088,25 @@ async fn rpc_namespace_session_tasks_list_dispatches_correctly() {
assert!(result.tasks.is_empty());
}
+#[tokio::test]
+async fn rpc_namespace_session_autopilot_objective_get_state_dispatches_correctly() {
+ let (session, mut server) = create_session_pair().await;
+ let session = Arc::new(session);
+
+ let s = session.clone();
+ let handle = tokio::spawn(async move { s.rpc().autopilot_objective().get_state().await });
+
+ let request = server.read_request().await;
+ assert_eq!(request["method"], "session.autopilotObjective.getState");
+ assert_eq!(request["params"]["sessionId"], server.session_id);
+ server
+ .respond(&request, serde_json::json!({ "state": null }))
+ .await;
+
+ let result = timeout(TIMEOUT, handle).await.unwrap().unwrap().unwrap();
+ assert!(result.state.is_none());
+}
+
#[tokio::test]
async fn rpc_namespace_client_models_list_dispatches_correctly() {
let (session, mut server) = create_session_pair().await;
diff --git a/test/snapshots/rpc_session_state/should_get_empty_autopilot_objective_state.yaml b/test/snapshots/rpc_session_state/should_get_empty_autopilot_objective_state.yaml
new file mode 100644
index 0000000000..056351ddb4
--- /dev/null
+++ b/test/snapshots/rpc_session_state/should_get_empty_autopilot_objective_state.yaml
@@ -0,0 +1,3 @@
+models:
+ - claude-sonnet-4.5
+conversations: []