From bdfecd4e4d01120aa93ae537a8b1d8eeb32f1c7f Mon Sep 17 00:00:00 2001 From: Michael Dinh Date: Wed, 23 Sep 2026 10:43:17 -0700 Subject: [PATCH 01/25] test: verify observed runtime execution identity Document existing typed identity sources and add content-free compatibility and delivery coverage without changing SDK behavior. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- docs/features/execution-identity.md | 125 ++++++++++++ docs/features/streaming-events.md | 4 + rust/tests/execution_identity_test.rs | 189 ++++++++++++++++++ .../tests/fixtures/execution-identity-v1.json | 184 +++++++++++++++++ rust/tests/prepared_session_test.rs | 181 ++++++++++++++++- 5 files changed, 680 insertions(+), 3 deletions(-) create mode 100644 docs/features/execution-identity.md create mode 100644 rust/tests/execution_identity_test.rs create mode 100644 rust/tests/fixtures/execution-identity-v1.json diff --git a/docs/features/execution-identity.md b/docs/features/execution-identity.md new file mode 100644 index 0000000000..a49a150f68 --- /dev/null +++ b/docs/features/execution-identity.md @@ -0,0 +1,125 @@ +# Observing runtime execution identity + +The SDK exposes identities observed on the runtime wire. Applications own the +association between their requests or Turns and runtime work; the SDK does not +infer that association or publish telemetry facts. + +## Field sources and compatibility + +These fields are available in SDK 1.0.14 with its default CLI 1.0.85. A configured +CLI path or external server can select a different runtime. Record both versions +and the connection/session lifetime when diagnosing an association; the SDK +version alone does not prove that a runtime emitted an optional field. + +The Rust payloads are generated by +[`scripts/codegen/rust.ts`](../../scripts/codegen/rust.ts) from the runtime's +`api.schema.json` and `session-events.schema.json`. Standalone SDK generation +uses the pinned CLI release schemas; runtime-checkout generation uses explicitly +selected same-checkout schemas. Change those contracts or their generators, not +the generated Rust files, when an additional runtime-owned field is required. + +| Wire source | Rust access | Meaning | +| --- | --- | --- | +| `session.create` result `sessionId`; outer `session.event.params.sessionId` | `Session::id()`, `SessionEventNotification.session_id` | Runtime logical session and event routing context | +| `session.send` result `messageId` | `Session::send()`, `rpc::SendResult.message_id` | Accepted logical user message, not an event or loop iteration | +| `queue.pendingItems.items[].messageId?` | `rpc::QueuePendingItems.message_id` | Same logical message identity, when present | +| `queue.pendingItems.items[].id` | `rpc::QueuePendingItems.id` | Separate opaque queue-item key; batch rows share this key | +| `user.message.data.{messageId?,turnId?,interactionId?}` | `session_events::UserMessageData` | Message identity and the loop/interaction that consumed it, when observed | +| `assistant.turn_start.data.{turnId,interactionId?}` | `session_events::AssistantTurnStartData` | Agent-loop iteration and optional interaction | +| `assistant.message.data.{messageId,originatingMessageId?,turnId?,interactionId?}` | `session_events::AssistantMessageData` | Assistant-message identity, primary initiating message, and optional execution keys | +| `event.{id,parentId?,agentId?}` | `SessionEvent`, `TypedSessionEvent` | Event identity, chronological predecessor, and optional subagent identity | + +Use `event.typed_data::()` and the corresponding generated +payload types for typed access. A malformed payload returns `None`; it must not +be replaced with a different event's identity. Unknown fields remain available +in the untyped event data. Do not forward that data wholesale into telemetry: +it includes message and tool content. + +Optional fields remain absent on older runtimes. Empty strings are not usable +identities. The high-level Rust `send()` retains its legacy empty-string result +when an acknowledgement lacks `messageId`; consumers must treat it as missing. +The generated `SendResult` instead requires a string `messageId`. Neither +behavior licenses substituting an event ID or a locally generated ID. + +## Distinguish a message, a loop iteration, and an interaction + +An application Turn, including an AHP Turn, is a separate identity. AHP Turn IDs +are opaque and session-scoped; they are not required to be UUIDs and must never +be equated with runtime `turnId`. + +Runtime `turnId` is a loop counter represented as a string. Counters can restart +across invocations. Interaction IDs can be reused for system-sourced work and +no-user HMAC sessions. Even `(sessionId, interactionId, turnId)` is not a +universally unique execution key. + +The exact accepted `messageId` connects a send to `user.message.messageId`. +`assistant.message.originatingMessageId`, when present, identifies the primary +message that started the run, not the assistant message itself: + +* Model/tool iterations and stop-hook corrections retain the primary origin. +* Immediate steering does not replace the active run's origin. +* For a batch starting a run, the last message ID is primary; earlier messages + provide context. An empty batch has no initiating message. +* A subagent has its own initiating message. Its origin must not be substituted + with the root agent's current message. + +`user.message.delivery` distinguishes `idle`, `steering`, and `queued`; +`source` separately identifies origin. A queued acknowledgement means admission, +not consumption. Pending steering snapshots expose display text and counts, not +a supported per-message identity bridge. Never associate an arbitrary event with +the oldest pending send, queue position, or display text. + +## Consume observations without inventing associations + +1. Subscribe before create/resume using + [PreparedSession](./streaming-events.md#subscribing-before-a-session-starts). + Notifications can precede an RPC response. Drain concurrently or size the + finite buffer; handle `Lagged` as missing evidence, not a successful outcome. +1. Retain the accepted message ID with the application's own request/Turn + identity and session provenance. Allow either the acknowledgement or event + to arrive first; only join matching observed keys. +1. Retain event and agent provenance when handling duplicates or late events. + The SDK forwards duplicate notifications; it does not deduplicate or rewrite + them. Deduplication of replayed evidence must not merge different sessions, + executions, or conflicting payloads. +1. Resume/reconnect does not prove new execution, automatic replay, or identity + continuity across a replaced runtime. Subscribe before resuming; request + persisted history explicitly when needed. `eventLog.read` cursor expiry can + produce overlap, and ephemeral events are not durable history. Keep the + observation's connection/lifetime provenance separate from runtime IDs. +1. Keep incomplete associations incomplete. A returned send ID can be lost when + its waiting future is cancelled after dispatch. Missing interaction, worker, + or recovery keys are not supplied by the event's chronological `parentId`. + +The root stream also contains subagent events marked by `agentId`. This marker +alone does not establish a worker's runtime session or its relationship to an +application Turn. A late worker event does not belong to whichever root Turn is +currently open. Likewise, `user.message.parentAgentTaskId` is a separate +runtime-provided task relationship, not an AHP parent or a replacement session ID. +Complete worker/recovery and product-telemetry joins require a verified +runtime-owned bridge; this interface does not promise one from an under-scoped +tuple. + +## Content-free compatibility fixtures + +[execution-identity-v1.json](../../rust/tests/fixtures/execution-identity-v1.json) +contains synthetic wire fragments, not a captured session or a prescribed event +order. `version` versions the fixture format; `sdkVersion` and `cliVersion` +identify its compatibility baseline. `sendResults` and `pendingItems` are RPC +result bodies; `notifications` contains complete `session.event` notifications. +These fixture metadata fields are not new runtime protocol fields. + +The fragments deliberately include distinct message/event/queue identities, +repeated loop/interaction keys, another session, a worker, resume, and absent +legacy keys. Message/display content is empty and tool arguments are absent. +Consumers can replay permutations and duplicates to test their own association +logic without collecting prompts or tool output. SDK tests verify typed +round-tripping and delivery before/after acknowledgements, across interleaved +sessions, resume, and new connections. They do not establish deployed runtime +ordering, export coverage, or end-to-end telemetry joins. + +## Further reading + +* [Streaming events](./streaming-events.md): Subscription and buffering behavior +* [Steering and queueing](./steering-and-queueing.md): Message admission and delivery +* [Session persistence](./session-persistence.md): Reading and resuming session history diff --git a/docs/features/streaming-events.md b/docs/features/streaming-events.md index 0292f98e00..900690f62c 100644 --- a/docs/features/streaming-events.md +++ b/docs/features/streaming-events.md @@ -222,6 +222,10 @@ session.on(AssistantMessageDeltaEvent.class, event -> A session can emit events before its create or resume call returns. The agent may already be working—especially on resume with `continuePendingWork`—and ephemeral events such as `session.idle` are never written to the session log, so `getMessages` cannot recover them afterwards. A subscription installed after the session handle exists misses that startup window. +For exact message, loop, interaction, and worker field sources, see +[Observing runtime execution identity](./execution-identity.md). Event order alone +does not associate an event with an application's pending request or Turn. + > [!TIP] > **(Rust)** `Client::prepare_session` and `Client::prepare_resume_session` return a `PreparedSession` that owns the session's event channel before any protocol activity happens. Subscribe first, then call `start()`. diff --git a/rust/tests/execution_identity_test.rs b/rust/tests/execution_identity_test.rs new file mode 100644 index 0000000000..c2422d668f --- /dev/null +++ b/rust/tests/execution_identity_test.rs @@ -0,0 +1,189 @@ +//! Compatibility tests for observed identity, not an application association reducer. + +#![allow(clippy::unwrap_used)] + +use github_copilot_sdk::rpc::{QueuePendingItemsResult, SendResult}; +use github_copilot_sdk::session_events::{ + AssistantMessageData, AssistantTurnStartData, TypedSessionEvent, UserMessageData, +}; +use github_copilot_sdk::types::{SessionEvent, SessionEventNotification}; +use serde_json::{Value, json}; + +fn fixture() -> Value { + serde_json::from_str(include_str!("fixtures/execution-identity-v1.json")).unwrap() +} + +fn event(fixture: &Value, name: &str) -> SessionEvent { + serde_json::from_value(fixture["notifications"][name]["params"]["event"].clone()).unwrap() +} + +#[test] +fn identity_fixture_round_trips_through_public_event_types() { + let fixture = fixture(); + assert_eq!(fixture["version"], 1); + assert_eq!(fixture["synthetic"], true); + + for notification in fixture["notifications"].as_object().unwrap().values() { + let routed: SessionEventNotification = + serde_json::from_value(notification["params"].clone()).unwrap(); + let typed: TypedSessionEvent = + serde_json::from_value(notification["params"]["event"].clone()).unwrap(); + + assert_eq!(typed.id, routed.event.id); + assert_eq!(typed.agent_id, routed.event.agent_id); + assert_eq!( + serde_json::to_value(routed).unwrap(), + notification["params"] + ); + assert_eq!( + serde_json::to_value(typed).unwrap(), + notification["params"]["event"] + ); + } +} + +#[test] +fn message_identity_is_distinct_from_event_queue_and_assistant_identity() { + let fixture = fixture(); + let pending: QueuePendingItemsResult = + serde_json::from_value(fixture["pendingItems"].clone()).unwrap(); + assert_eq!( + serde_json::to_value(&pending).unwrap(), + fixture["pendingItems"] + ); + assert_eq!(pending.items[0].id, pending.items[1].id); + + for (name, item) in ["user", "system"].into_iter().zip(&pending.items) { + let accepted: SendResult = + serde_json::from_value(fixture["sendResults"][name].clone()).unwrap(); + let notification = event(&fixture, name); + let user = notification.typed_data::().unwrap(); + assert_eq!( + user.message_id.as_deref(), + Some(accepted.message_id.as_str()) + ); + assert_eq!(item.message_id, user.message_id); + assert_ne!(item.id, accepted.message_id); + assert_ne!(notification.id, accepted.message_id); + assert_ne!( + notification.parent_id.as_deref(), + user.message_id.as_deref() + ); + } + + let user = event(&fixture, "user") + .typed_data::() + .unwrap(); + let assistant = event(&fixture, "assistant") + .typed_data::() + .unwrap(); + assert_eq!(assistant.originating_message_id, user.message_id); + assert_ne!(Some(assistant.message_id), user.message_id); + assert_ne!(assistant.turn_id, user.turn_id); +} + +#[test] +fn loop_and_interaction_keys_can_repeat_without_replacing_message_identity() { + let fixture = fixture(); + let user = event(&fixture, "user") + .typed_data::() + .unwrap(); + let system = event(&fixture, "system") + .typed_data::() + .unwrap(); + assert_eq!(user.turn_id, system.turn_id); + assert_eq!(user.interaction_id, system.interaction_id); + assert_ne!(user.message_id, system.message_id); + + let worker = event(&fixture, "worker"); + let worker_data = worker.typed_data::().unwrap(); + assert!(worker.agent_id.is_some()); + assert_ne!(worker_data.originating_message_id, user.message_id); + assert_ne!(worker_data.interaction_id, user.interaction_id); +} + +#[test] +fn absent_identity_is_not_inferred_from_an_event_or_assistant_message_id() { + let fixture = fixture(); + let legacy = event(&fixture, "legacy") + .typed_data::() + .unwrap(); + assert!(legacy.message_id.is_none()); + assert!(legacy.turn_id.is_none()); + assert!(legacy.interaction_id.is_none()); + assert!(legacy.parent_agent_task_id.is_none()); + + let mut assistant = event(&fixture, "assistant"); + for key in ["originatingMessageId", "turnId", "interactionId"] { + assistant.data.as_object_mut().unwrap().remove(key); + } + let data = assistant.typed_data::().unwrap(); + assert!(data.originating_message_id.is_none()); + assert!(data.turn_id.is_none()); + assert!(data.interaction_id.is_none()); + assert!(!data.message_id.is_empty()); + + let mut turn = event(&fixture, "turnStart"); + turn.data.as_object_mut().unwrap().remove("interactionId"); + let data = turn.typed_data::().unwrap(); + assert_eq!(data.turn_id, "0"); + assert!(data.interaction_id.is_none()); + turn.data.as_object_mut().unwrap().remove("turnId"); + assert!(turn.typed_data::().is_none()); +} + +#[test] +fn empty_identity_remains_empty_and_malformed_identity_is_not_coerced() { + let fixture = fixture(); + let mut empty = event(&fixture, "user"); + for key in ["messageId", "turnId", "interactionId"] { + empty.data[key] = json!(""); + } + let data = empty.typed_data::().unwrap(); + assert_eq!(data.message_id.as_deref(), Some("")); + assert_eq!(data.turn_id.as_deref(), Some("")); + assert_eq!(data.interaction_id.as_deref(), Some("")); + + for key in ["messageId", "turnId", "interactionId"] { + let mut malformed = event(&fixture, "user"); + malformed.data[key] = json!(0); + assert!(malformed.typed_data::().is_none()); + assert_eq!(malformed.data[key], 0); + } + + for response in [ + json!({}), + json!({"messageId": null}), + json!({"messageId": 0}), + ] { + assert!(serde_json::from_value::(response).is_err()); + } +} + +#[test] +fn identity_fixture_contains_no_message_or_tool_content() { + fn check(value: &Value) { + match value { + Value::Object(fields) => { + for (key, value) in fields { + if matches!(key.as_str(), "content" | "displayText") { + assert_eq!(value, ""); + } + assert!(!matches!( + key.as_str(), + "prompt" + | "arguments" + | "attachments" + | "transformedContent" + | "reasoningText" + | "result" + )); + check(value); + } + } + Value::Array(values) => values.iter().for_each(check), + _ => {} + } + } + check(&fixture()); +} diff --git a/rust/tests/fixtures/execution-identity-v1.json b/rust/tests/fixtures/execution-identity-v1.json new file mode 100644 index 0000000000..a503896d80 --- /dev/null +++ b/rust/tests/fixtures/execution-identity-v1.json @@ -0,0 +1,184 @@ +{ + "version": 1, + "synthetic": true, + "sdkVersion": "1.0.14", + "cliVersion": "1.0.85", + "sendResults": { + "user": {"messageId": "20000000-0000-4000-8000-000000000001"}, + "system": {"messageId": "20000000-0000-4000-8000-000000000002"} + }, + "pendingItems": { + "items": [ + { + "agentMode": "interactive", + "displayText": "", + "id": "queue-batch-a", + "kind": "message", + "messageId": "20000000-0000-4000-8000-000000000001" + }, + { + "agentMode": "interactive", + "displayText": "", + "id": "queue-batch-a", + "kind": "message", + "messageId": "20000000-0000-4000-8000-000000000002" + } + ], + "steeringMessages": [] + }, + "notifications": { + "user": { + "jsonrpc": "2.0", + "method": "session.event", + "params": { + "sessionId": "runtime-session-a", + "event": { + "id": "10000000-0000-4000-8000-000000000001", + "timestamp": "2026-09-16T00:00:01Z", + "parentId": "10000000-0000-4000-8000-000000000000", + "type": "user.message", + "data": { + "content": "", + "delivery": "idle", + "source": "user", + "messageId": "20000000-0000-4000-8000-000000000001", + "turnId": "0", + "interactionId": "30000000-0000-4000-8000-000000000001" + } + } + } + }, + "turnStart": { + "jsonrpc": "2.0", + "method": "session.event", + "params": { + "sessionId": "runtime-session-a", + "event": { + "id": "10000000-0000-4000-8000-000000000002", + "timestamp": "2026-09-16T00:00:02Z", + "parentId": "10000000-0000-4000-8000-000000000001", + "type": "assistant.turn_start", + "data": { + "turnId": "0", + "interactionId": "30000000-0000-4000-8000-000000000001" + } + } + } + }, + "assistant": { + "jsonrpc": "2.0", + "method": "session.event", + "params": { + "sessionId": "runtime-session-a", + "event": { + "id": "10000000-0000-4000-8000-000000000003", + "timestamp": "2026-09-16T00:00:03Z", + "parentId": "10000000-0000-4000-8000-000000000002", + "type": "assistant.message", + "data": { + "content": "", + "messageId": "40000000-0000-4000-8000-000000000001", + "originatingMessageId": "20000000-0000-4000-8000-000000000001", + "turnId": "1", + "interactionId": "30000000-0000-4000-8000-000000000001" + } + } + } + }, + "system": { + "jsonrpc": "2.0", + "method": "session.event", + "params": { + "sessionId": "runtime-session-a", + "event": { + "id": "10000000-0000-4000-8000-000000000004", + "timestamp": "2026-09-16T00:00:04Z", + "parentId": "10000000-0000-4000-8000-000000000003", + "type": "user.message", + "data": { + "content": "", + "delivery": "queued", + "source": "system", + "messageId": "20000000-0000-4000-8000-000000000002", + "turnId": "0", + "interactionId": "30000000-0000-4000-8000-000000000001" + } + } + } + }, + "worker": { + "jsonrpc": "2.0", + "method": "session.event", + "params": { + "sessionId": "runtime-session-a", + "event": { + "id": "10000000-0000-4000-8000-000000000005", + "timestamp": "2026-09-16T00:00:05Z", + "parentId": "10000000-0000-4000-8000-000000000004", + "agentId": "50000000-0000-4000-8000-000000000001", + "type": "assistant.message", + "data": { + "content": "", + "messageId": "40000000-0000-4000-8000-000000000002", + "originatingMessageId": "20000000-0000-4000-8000-000000000003", + "turnId": "0", + "interactionId": "30000000-0000-4000-8000-000000000002" + } + } + } + }, + "otherSession": { + "jsonrpc": "2.0", + "method": "session.event", + "params": { + "sessionId": "runtime-session-b", + "event": { + "id": "10000000-0000-4000-8000-000000000006", + "timestamp": "2026-09-16T00:00:06Z", + "parentId": "10000000-0000-4000-8000-000000000000", + "type": "user.message", + "data": { + "content": "", + "delivery": "idle", + "source": "user", + "messageId": "20000000-0000-4000-8000-000000000004", + "turnId": "0", + "interactionId": "30000000-0000-4000-8000-000000000001" + } + } + } + }, + "legacy": { + "jsonrpc": "2.0", + "method": "session.event", + "params": { + "sessionId": "runtime-session-a", + "event": { + "id": "10000000-0000-4000-8000-000000000007", + "timestamp": "2026-09-16T00:00:07Z", + "parentId": "10000000-0000-4000-8000-000000000006", + "type": "user.message", + "data": {"content": ""} + } + } + }, + "resume": { + "jsonrpc": "2.0", + "method": "session.event", + "params": { + "sessionId": "runtime-session-a", + "event": { + "id": "10000000-0000-4000-8000-000000000008", + "timestamp": "2026-09-16T00:00:08Z", + "parentId": "10000000-0000-4000-8000-000000000007", + "type": "session.resume", + "data": { + "eventCount": 7, + "resumeTime": "2026-09-16T00:00:08Z", + "continuePendingWork": true + } + } + } + } + } +} diff --git a/rust/tests/prepared_session_test.rs b/rust/tests/prepared_session_test.rs index 56698bac92..0d6d671473 100644 --- a/rust/tests/prepared_session_test.rs +++ b/rust/tests/prepared_session_test.rs @@ -13,11 +13,11 @@ use std::time::Duration; use async_trait::async_trait; use github_copilot_sdk::handler::{McpAuthHandler, McpAuthRequest, McpAuthResult}; -use github_copilot_sdk::session::PreparedSession; +use github_copilot_sdk::session::{PreparedSession, Session}; use github_copilot_sdk::subscription::{EventSubscription, RecvErrorKind}; use github_copilot_sdk::types::{ - CloudSessionOptions, CloudSessionRepository, RequestId, ResumeSessionConfig, SessionConfig, - SessionId, + CloudSessionOptions, CloudSessionRepository, MessageOptions, RequestId, ResumeSessionConfig, + SessionConfig, SessionId, }; use github_copilot_sdk::{Client, ErrorKind, SessionErrorKind}; use serde_json::{Value, json}; @@ -133,6 +133,10 @@ impl FakeServer { write_framed(&mut self.write, &serde_json::to_vec(¬ification).unwrap()).await; } + async fn send_notification(&mut self, notification: &Value) { + write_framed(&mut self.write, &serde_json::to_vec(notification).unwrap()).await; + } + /// Emit the startup burst the host cares about: `BURST` ordered events /// followed by an ephemeral `session.idle` that `getMessages` could /// never recover. @@ -1347,3 +1351,174 @@ async fn deferred_create_cancelled_after_callback_registered_is_cleaned_up() { ); drop(session); } + +fn identity_fixture() -> Value { + serde_json::from_str(include_str!("fixtures/execution-identity-v1.json")).unwrap() +} + +async fn start_identity_session( + client: &Client, + server: &mut FakeServer, + session_id: &str, +) -> (Session, EventSubscription) { + let prepared = client + .prepare_session(SessionConfig::default().with_session_id(session_id)) + .unwrap(); + let events = prepared.subscribe(); + let start = tokio::spawn(prepared.start()); + let request = server.read_request().await; + assert_eq!(request["method"], "session.create"); + server + .respond(&request, json!({"sessionId": session_id})) + .await; + let session = timeout(TIMEOUT, start).await.unwrap().unwrap().unwrap(); + assert_eq!(session.id().as_str(), session_id); + (session, events) +} + +async fn expect_identity_event(events: &mut EventSubscription, notification: &Value) { + let event = timeout(TIMEOUT, events.recv()).await.unwrap().unwrap(); + assert_eq!( + serde_json::to_value(event).unwrap(), + notification["params"]["event"] + ); +} + +#[tokio::test] +async fn identity_delivery_does_not_depend_on_send_acknowledgement_order() { + let fixture = identity_fixture(); + let (client, mut server) = make_client(); + let session_id = fixture["notifications"]["user"]["params"]["sessionId"] + .as_str() + .unwrap(); + let (session, mut events) = start_identity_session(&client, &mut server, session_id).await; + let session = Arc::new(session); + + for (name, before_ack) in [("user", true), ("system", false)] { + let sending = tokio::spawn({ + let session = session.clone(); + async move { + let source = if name == "system" { + github_copilot_sdk::MessageSource::System + } else { + github_copilot_sdk::MessageSource::User + }; + session + .send(MessageOptions::new("").with_source(source)) + .await + } + }); + let request = server.read_request().await; + assert_eq!(request["method"], "session.send"); + let notification = &fixture["notifications"][name]; + if before_ack { + server.send_notification(notification).await; + expect_identity_event(&mut events, notification).await; + } + server + .respond(&request, fixture["sendResults"][name].clone()) + .await; + let message_id = timeout(TIMEOUT, sending).await.unwrap().unwrap().unwrap(); + assert_eq!( + message_id, + fixture["sendResults"][name]["messageId"].as_str().unwrap() + ); + if !before_ack { + server.send_notification(notification).await; + expect_identity_event(&mut events, notification).await; + } + } +} + +#[tokio::test] +async fn identity_delivery_preserves_duplicates_workers_and_session_isolation() { + let fixture = identity_fixture(); + let notifications = &fixture["notifications"]; + let (client, mut server) = make_client(); + let (_first, mut first_events) = start_identity_session( + &client, + &mut server, + notifications["user"]["params"]["sessionId"] + .as_str() + .unwrap(), + ) + .await; + let (_second, mut second_events) = start_identity_session( + &client, + &mut server, + notifications["otherSession"]["params"]["sessionId"] + .as_str() + .unwrap(), + ) + .await; + + for name in ["user", "otherSession", "system", "worker", "user", "legacy"] { + server.send_notification(¬ifications[name]).await; + } + let mut conflicting_duplicate = notifications["user"].clone(); + conflicting_duplicate["params"]["event"]["data"]["messageId"] = + fixture["sendResults"]["system"]["messageId"].clone(); + server.send_notification(&conflicting_duplicate).await; + for name in ["user", "system", "worker", "user", "legacy"] { + expect_identity_event(&mut first_events, ¬ifications[name]).await; + } + expect_identity_event(&mut first_events, &conflicting_duplicate).await; + expect_identity_event(&mut second_events, ¬ifications["otherSession"]).await; + assert!(timeout(QUIET, first_events.recv()).await.is_err()); + assert!(timeout(QUIET, second_events.recv()).await.is_err()); +} + +#[tokio::test] +async fn routed_replay_and_missing_identity_survive_resume_without_reassociation() { + let fixture = identity_fixture(); + let notifications = &fixture["notifications"]; + let session_id = notifications["user"]["params"]["sessionId"] + .as_str() + .unwrap(); + let (client, mut server) = make_client(); + let (session, mut events) = start_identity_session(&client, &mut server, session_id).await; + server.send_notification(¬ifications["user"]).await; + expect_identity_event(&mut events, ¬ifications["user"]).await; + drop(session); + expect_closed(&mut events).await; + + let prepared = client + .prepare_resume_session( + ResumeSessionConfig::new(SessionId::new(session_id)).with_continue_pending_work(true), + ) + .unwrap(); + let mut resumed_events = prepared.subscribe(); + let start = tokio::spawn(prepared.start()); + let request = server.read_request().await; + assert_eq!(request["method"], "session.resume"); + assert_eq!(request["params"]["continuePendingWork"], true); + for name in ["resume", "user", "legacy", "worker"] { + server.send_notification(¬ifications[name]).await; + } + server + .respond(&request, json!({"sessionId": session_id})) + .await; + server.answer_skills_reload().await; + let resumed = timeout(TIMEOUT, start).await.unwrap().unwrap().unwrap(); + assert_eq!(resumed.id().as_str(), session_id); + for name in ["resume", "user", "legacy", "worker"] { + expect_identity_event(&mut resumed_events, ¬ifications[name]).await; + } +} + +#[tokio::test] +async fn a_new_connection_does_not_reuse_observed_identity_for_the_same_session_id() { + let fixture = identity_fixture(); + let notifications = &fixture["notifications"]; + let session_id = notifications["user"]["params"]["sessionId"] + .as_str() + .unwrap(); + for name in ["user", "legacy"] { + let (client, mut server) = make_client(); + let (session, mut events) = start_identity_session(&client, &mut server, session_id).await; + server.send_notification(¬ifications[name]).await; + expect_identity_event(&mut events, ¬ifications[name]).await; + drop(session); + expect_closed(&mut events).await; + } +} From e818a28f981fc14c4178ac75aeb617e17b9e2548 Mon Sep 17 00:00:00 2001 From: Michael Dinh Date: Wed, 23 Sep 2026 10:47:34 -0700 Subject: [PATCH 02/25] docs: clarify runtime session identity provenance Use the accepted SDK session ID instead of deriving it from an application URI; hosts can remap paths and resolve collisions. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- docs/features/execution-identity.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/docs/features/execution-identity.md b/docs/features/execution-identity.md index a49a150f68..aef012e725 100644 --- a/docs/features/execution-identity.md +++ b/docs/features/execution-identity.md @@ -11,6 +11,10 @@ CLI path or external server can select a different runtime. Record both versions and the connection/session lifetime when diagnosing an association; the SDK version alone does not prove that a runtime emitted an optional field. +Use the accepted `Session::id()`, not a transformation of an application's +session URI. Hosts can remap URI paths or resolve collisions before creating SDK +sessions. Retain the application's full session identity separately. + The Rust payloads are generated by [`scripts/codegen/rust.ts`](../../scripts/codegen/rust.ts) from the runtime's `api.schema.json` and `session-events.schema.json`. Standalone SDK generation From 31877542ca21d7bcb93cc9e9a53d4980df64f1fd Mon Sep 17 00:00:00 2001 From: Michael Dinh Date: Wed, 23 Sep 2026 11:04:31 -0700 Subject: [PATCH 03/25] test: preserve identity isolation after cancelled sends Cover late acknowledgements after a send wait is cancelled, and document the missing caller-visible admission bridge without inventing one. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- docs/features/execution-identity.md | 10 ++++- rust/tests/prepared_session_test.rs | 60 +++++++++++++++++++++++++++++ 2 files changed, 68 insertions(+), 2 deletions(-) diff --git a/docs/features/execution-identity.md b/docs/features/execution-identity.md index aef012e725..20a8911cd3 100644 --- a/docs/features/execution-identity.md +++ b/docs/features/execution-identity.md @@ -95,6 +95,11 @@ the oldest pending send, queue position, or display text. its waiting future is cancelled after dispatch. Missing interaction, worker, or recovery keys are not supplied by the event's chronological `parentId`. +The supported send request has no caller-supplied message ID. If the response is +lost, an arriving `user.message` alone cannot identify the application's pending +send. That boundary needs a verified runtime-owned bridge; neither queue order +nor repurposing `source` as a correlation nonce supplies one. + The root stream also contains subagent events marked by `agentId`. This marker alone does not establish a worker's runtime session or its relationship to an application Turn. A late worker event does not belong to whichever root Turn is @@ -119,8 +124,9 @@ legacy keys. Message/display content is empty and tool arguments are absent. Consumers can replay permutations and duplicates to test their own association logic without collecting prompts or tool output. SDK tests verify typed round-tripping and delivery before/after acknowledgements, across interleaved -sessions, resume, and new connections. They do not establish deployed runtime -ordering, export coverage, or end-to-end telemetry joins. +sessions, cancelled waits with late responses, resume, and new connections. +They do not establish deployed runtime ordering, export coverage, or end-to-end +telemetry joins. ## Further reading diff --git a/rust/tests/prepared_session_test.rs b/rust/tests/prepared_session_test.rs index 0d6d671473..aa7b31b378 100644 --- a/rust/tests/prepared_session_test.rs +++ b/rust/tests/prepared_session_test.rs @@ -1430,6 +1430,66 @@ async fn identity_delivery_does_not_depend_on_send_acknowledgement_order() { } } +#[tokio::test] +async fn cancelled_send_response_does_not_supply_the_next_message_identity() { + let fixture = identity_fixture(); + let (client, mut server) = make_client(); + let session_id = fixture["notifications"]["user"]["params"]["sessionId"] + .as_str() + .unwrap(); + let (session, mut events) = start_identity_session(&client, &mut server, session_id).await; + let session = Arc::new(session); + let abandoned = tokio::spawn({ + let session = session.clone(); + async move { session.send("").await } + }); + let abandoned_request = server.read_request().await; + assert_eq!(abandoned_request["method"], "session.send"); + abandoned.abort(); + assert!( + timeout(TIMEOUT, abandoned) + .await + .unwrap() + .unwrap_err() + .is_cancelled() + ); + + let next = tokio::spawn({ + let session = session.clone(); + async move { + session + .send( + MessageOptions::new("").with_source(github_copilot_sdk::MessageSource::System), + ) + .await + } + }); + let next_request = server.read_request().await; + assert_eq!(next_request["method"], "session.send"); + assert_ne!(abandoned_request["id"], next_request["id"]); + server + .respond(&abandoned_request, fixture["sendResults"]["user"].clone()) + .await; + server + .send_notification(&fixture["notifications"]["user"]) + .await; + server + .respond(&next_request, fixture["sendResults"]["system"].clone()) + .await; + server + .send_notification(&fixture["notifications"]["system"]) + .await; + assert_eq!( + timeout(TIMEOUT, next).await.unwrap().unwrap().unwrap(), + fixture["sendResults"]["system"]["messageId"] + .as_str() + .unwrap() + ); + for name in ["user", "system"] { + expect_identity_event(&mut events, &fixture["notifications"][name]).await; + } +} + #[tokio::test] async fn identity_delivery_preserves_duplicates_workers_and_session_isolation() { let fixture = identity_fixture(); From cc85c001e80375b3c1f438391cff4239f654038c Mon Sep 17 00:00:00 2001 From: Michael Dinh Date: Wed, 23 Sep 2026 11:16:38 -0700 Subject: [PATCH 04/25] test: distinguish worker spawn and correction identities Verify existing initial-worker and one-to-many interaction fields while documenting follow-up, recovery, and product-reporting limitations. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- docs/features/execution-identity.md | 42 ++++++++++++++----- rust/tests/execution_identity_test.rs | 35 +++++++++++++++- .../tests/fixtures/execution-identity-v1.json | 40 ++++++++++++++++++ rust/tests/prepared_session_test.rs | 19 ++++++++- 4 files changed, 122 insertions(+), 14 deletions(-) diff --git a/docs/features/execution-identity.md b/docs/features/execution-identity.md index 20a8911cd3..cc1c44cfca 100644 --- a/docs/features/execution-identity.md +++ b/docs/features/execution-identity.md @@ -32,6 +32,9 @@ the generated Rust files, when an additional runtime-owned field is required. | `assistant.turn_start.data.{turnId,interactionId?}` | `session_events::AssistantTurnStartData` | Agent-loop iteration and optional interaction | | `assistant.message.data.{messageId,originatingMessageId?,turnId?,interactionId?}` | `session_events::AssistantMessageData` | Assistant-message identity, primary initiating message, and optional execution keys | | `event.{id,parentId?,agentId?}` | `SessionEvent`, `TypedSessionEvent` | Event identity, chronological predecessor, and optional subagent identity | +| `subagent.started.data.toolCallId` with envelope `agentId` | `session_events::SubagentStartedData` | Initial spawning-tool/worker relationship; API spawns without a tool use the worker ID as a lifecycle fallback | +| `session.tasks.startAgent` result `agentId` | `rpc::TasksStartAgentResult.agent_id` | Initial API-spawned worker identity | +| `session.tasks.sendMessage` result `sent` | `rpc::TasksSendMessageResult` | Delivery status, without a message admission ID at this baseline | Use `event.typed_data::()` and the corresponding generated payload types for typed access. A malformed payload returns `None`; it must not @@ -61,6 +64,7 @@ The exact accepted `messageId` connects a send to `user.message.messageId`. message that started the run, not the assistant message itself: * Model/tool iterations and stop-hook corrections retain the primary origin. + A correction can start a new interaction while retaining that origin. * Immediate steering does not replace the active run's origin. * For a batch starting a run, the last message ID is primary; earlier messages provide context. An empty batch has no initiating message. @@ -100,14 +104,31 @@ lost, an arriving `user.message` alone cannot identify the application's pending send. That boundary needs a verified runtime-owned bridge; neither queue order nor repurposing `source` as a correlation nonce supplies one. -The root stream also contains subagent events marked by `agentId`. This marker -alone does not establish a worker's runtime session or its relationship to an -application Turn. A late worker event does not belong to whichever root Turn is -currently open. Likewise, `user.message.parentAgentTaskId` is a separate -runtime-provided task relationship, not an AHP parent or a replacement session ID. -Complete worker/recovery and product-telemetry joins require a verified -runtime-owned bridge; this interface does not promise one from an under-scoped -tuple. +For an initial tool-spawned worker, match the scoped spawning `toolCallId` to +`subagent.started` and retain its envelope `agentId`. An API spawn instead +provides the returned worker ID; its lifecycle fallback is not evidence of a +real tool call. Subsequent worker events retain that worker scope on the root +stream, without requiring an exposed native child-session ID. + +This initial relationship does not establish the cause of a worker follow-up. +At this baseline, `session.tasks.sendMessage` returns no message ID and the internal +queued-message identity is not retained in the later worker `user.message`. +`source` identifies a sender, not its sending Turn. Never attribute all worker +follow-ups to the spawn Turn or the parent's current interaction at delivery. +Recovery with no primary message similarly has no `user.message` admission or +`originatingMessageId`. These cases need an approved causal bridge. + +Despite its name, `user.message.parentAgentTaskId` reflects the preparing +`TurnIdentity`'s task identifier, not a parent interaction ID. Do not equate it +with the distinct CAPI `X-Parent-Agent-Id` value. + +Product-telemetry identities also need their own verified mapping. The runtime +can normalize non-UUID reporting session IDs, report under a parent's identity, +fill a child event's missing interaction from root state, or remint an event +envelope. An observed SDK session, interaction, worker, or event ID is therefore +not a universal equality join to similarly named product fields. Keep the raw +observations and explicit provenance; do not add a second telemetry writer or +put these diagnostic identifiers in metric tags. ## Content-free compatibility fixtures @@ -119,8 +140,9 @@ result bodies; `notifications` contains complete `session.event` notifications. These fixture metadata fields are not new runtime protocol fields. The fragments deliberately include distinct message/event/queue identities, -repeated loop/interaction keys, another session, a worker, resume, and absent -legacy keys. Message/display content is empty and tool arguments are absent. +repeated loop/interaction keys, a correction retaining its origin, another +session, initial worker scope, resume, and absent legacy keys. Message/display +content and agent descriptions are empty; tool arguments are absent. Consumers can replay permutations and duplicates to test their own association logic without collecting prompts or tool output. SDK tests verify typed round-tripping and delivery before/after acknowledgements, across interleaved diff --git a/rust/tests/execution_identity_test.rs b/rust/tests/execution_identity_test.rs index c2422d668f..2563c68e41 100644 --- a/rust/tests/execution_identity_test.rs +++ b/rust/tests/execution_identity_test.rs @@ -4,7 +4,8 @@ use github_copilot_sdk::rpc::{QueuePendingItemsResult, SendResult}; use github_copilot_sdk::session_events::{ - AssistantMessageData, AssistantTurnStartData, TypedSessionEvent, UserMessageData, + AssistantMessageData, AssistantTurnStartData, SubagentStartedData, TypedSessionEvent, + UserMessageData, }; use github_copilot_sdk::types::{SessionEvent, SessionEventNotification}; use serde_json::{Value, json}; @@ -102,6 +103,29 @@ fn loop_and_interaction_keys_can_repeat_without_replacing_message_identity() { assert_ne!(worker_data.interaction_id, user.interaction_id); } +#[test] +fn spawn_and_correction_identity_preserve_distinct_relationships() { + let fixture = fixture(); + let started = event(&fixture, "workerStarted"); + let spawn = started.typed_data::().unwrap(); + let worker = event(&fixture, "worker"); + assert_eq!(started.agent_id, worker.agent_id); + assert_eq!(spawn.tool_call_id, "tool-spawn-a"); + assert_ne!( + started.agent_id.as_deref(), + Some(spawn.tool_call_id.as_str()) + ); + + let user = event(&fixture, "user") + .typed_data::() + .unwrap(); + let corrected = event(&fixture, "correctedAssistant") + .typed_data::() + .unwrap(); + assert_eq!(corrected.originating_message_id, user.message_id); + assert_ne!(corrected.interaction_id, user.interaction_id); +} + #[test] fn absent_identity_is_not_inferred_from_an_event_or_assistant_message_id() { let fixture = fixture(); @@ -166,7 +190,14 @@ fn identity_fixture_contains_no_message_or_tool_content() { match value { Value::Object(fields) => { for (key, value) in fields { - if matches!(key.as_str(), "content" | "displayText") { + if matches!( + key.as_str(), + "content" + | "displayText" + | "agentDescription" + | "agentDisplayName" + | "agentName" + ) { assert_eq!(value, ""); } assert!(!matches!( diff --git a/rust/tests/fixtures/execution-identity-v1.json b/rust/tests/fixtures/execution-identity-v1.json index a503896d80..2df45230fe 100644 --- a/rust/tests/fixtures/execution-identity-v1.json +++ b/rust/tests/fixtures/execution-identity-v1.json @@ -85,6 +85,26 @@ } } }, + "correctedAssistant": { + "jsonrpc": "2.0", + "method": "session.event", + "params": { + "sessionId": "runtime-session-a", + "event": { + "id": "10000000-0000-4000-8000-000000000009", + "timestamp": "2026-09-16T00:00:03.500Z", + "parentId": "10000000-0000-4000-8000-000000000003", + "type": "assistant.message", + "data": { + "content": "", + "messageId": "40000000-0000-4000-8000-000000000003", + "originatingMessageId": "20000000-0000-4000-8000-000000000001", + "turnId": "0", + "interactionId": "30000000-0000-4000-8000-000000000003" + } + } + } + }, "system": { "jsonrpc": "2.0", "method": "session.event", @@ -106,6 +126,26 @@ } } }, + "workerStarted": { + "jsonrpc": "2.0", + "method": "session.event", + "params": { + "sessionId": "runtime-session-a", + "event": { + "id": "10000000-0000-4000-8000-000000000010", + "timestamp": "2026-09-16T00:00:04.500Z", + "parentId": "10000000-0000-4000-8000-000000000004", + "agentId": "50000000-0000-4000-8000-000000000001", + "type": "subagent.started", + "data": { + "agentDescription": "", + "agentDisplayName": "", + "agentName": "", + "toolCallId": "tool-spawn-a" + } + } + } + }, "worker": { "jsonrpc": "2.0", "method": "session.event", diff --git a/rust/tests/prepared_session_test.rs b/rust/tests/prepared_session_test.rs index aa7b31b378..e2a88bcf0a 100644 --- a/rust/tests/prepared_session_test.rs +++ b/rust/tests/prepared_session_test.rs @@ -1512,14 +1512,29 @@ async fn identity_delivery_preserves_duplicates_workers_and_session_isolation() ) .await; - for name in ["user", "otherSession", "system", "worker", "user", "legacy"] { + for name in [ + "user", + "otherSession", + "system", + "worker", + "workerStarted", + "user", + "legacy", + ] { server.send_notification(¬ifications[name]).await; } let mut conflicting_duplicate = notifications["user"].clone(); conflicting_duplicate["params"]["event"]["data"]["messageId"] = fixture["sendResults"]["system"]["messageId"].clone(); server.send_notification(&conflicting_duplicate).await; - for name in ["user", "system", "worker", "user", "legacy"] { + for name in [ + "user", + "system", + "worker", + "workerStarted", + "user", + "legacy", + ] { expect_identity_event(&mut first_events, ¬ifications[name]).await; } expect_identity_event(&mut first_events, &conflicting_duplicate).await; From 4eefdc204e874fb3b41b47394b846be8483f8917 Mon Sep 17 00:00:00 2001 From: Michael Dinh Date: Wed, 23 Sep 2026 11:33:43 -0700 Subject: [PATCH 05/25] test: retain observed loop-occurrence event identities Document the runtime-verified root-projection bridge for user and turn events, without generalizing it to other telemetry or inventing causal identities. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- docs/features/execution-identity.md | 31 ++++++++++++++----- rust/tests/execution_identity_test.rs | 13 ++++++++ .../tests/fixtures/execution-identity-v1.json | 17 ++++++++++ 3 files changed, 53 insertions(+), 8 deletions(-) diff --git a/docs/features/execution-identity.md b/docs/features/execution-identity.md index cc1c44cfca..ac31a0b3a7 100644 --- a/docs/features/execution-identity.md +++ b/docs/features/execution-identity.md @@ -59,6 +59,10 @@ across invocations. Interaction IDs can be reused for system-sourced work and no-user HMAC sessions. Even `(sessionId, interactionId, turnId)` is not a universally unique execution key. +Record the actual `assistant.turn_start` **event ID** as the loop-occurrence +identity, with its observed runtime session and agent scope. Neither the loop +counter nor the initiating message ID identifies an individual occurrence. + The exact accepted `messageId` connects a send to `user.message.messageId`. `assistant.message.originatingMessageId`, when present, identifies the primary message that started the run, not the assistant message itself: @@ -122,13 +126,23 @@ Despite its name, `user.message.parentAgentTaskId` reflects the preparing `TurnIdentity`'s task identifier, not a parent interaction ID. Do not equate it with the distinct CAPI `X-Parent-Agent-Id` value. -Product-telemetry identities also need their own verified mapping. The runtime -can normalize non-UUID reporting session IDs, report under a parent's identity, +There is a narrower, existing product-telemetry bridge in CLI 1.0.85: +the root listener copies the final root-subscription event ID into +`properties.event_id` for `user_message`, `assistant_turn_start`, and +`assistant_turn_end`, corresponding to the same dotted SDK event types. +Child listeners skip these bridge families, so use the final root-bridged ID, +not an original child-log ID. Match the telemetry kind as well as the observed +ID, account for reporting-session normalization, and retain session/agent +provenance. Supplied event IDs are preserved during replay; a duplicate does not +establish a new occurrence. An end event has its own ID, not its start event's ID. + +This is not a universal equality join for all product records. The runtime can +normalize non-UUID reporting session IDs, report under a parent's identity, fill a child event's missing interaction from root state, or remint an event -envelope. An observed SDK session, interaction, worker, or event ID is therefore -not a universal equality join to similarly named product fields. Keep the raw +envelope before root projection. Knowing an occurrence ID also does not resolve +the missing cause of a worker follow-up or recovery submission. Keep raw observations and explicit provenance; do not add a second telemetry writer or -put these diagnostic identifiers in metric tags. +put diagnostic identifiers in metric tags. ## Content-free compatibility fixtures @@ -140,9 +154,10 @@ result bodies; `notifications` contains complete `session.event` notifications. These fixture metadata fields are not new runtime protocol fields. The fragments deliberately include distinct message/event/queue identities, -repeated loop/interaction keys, a correction retaining its origin, another -session, initial worker scope, resume, and absent legacy keys. Message/display -content and agent descriptions are empty; tool arguments are absent. +repeated loop/interaction keys with distinct start-event IDs, a correction +retaining its origin, another session, initial worker scope, resume, and absent +legacy keys. Message/display content and agent descriptions are empty; tool +arguments are absent. Consumers can replay permutations and duplicates to test their own association logic without collecting prompts or tool output. SDK tests verify typed round-tripping and delivery before/after acknowledgements, across interleaved diff --git a/rust/tests/execution_identity_test.rs b/rust/tests/execution_identity_test.rs index 2563c68e41..8be6f2c503 100644 --- a/rust/tests/execution_identity_test.rs +++ b/rust/tests/execution_identity_test.rs @@ -103,6 +103,19 @@ fn loop_and_interaction_keys_can_repeat_without_replacing_message_identity() { assert_ne!(worker_data.interaction_id, user.interaction_id); } +#[test] +fn distinct_loop_occurrences_preserve_event_ids_when_counter_keys_repeat() { + let fixture = fixture(); + let first = event(&fixture, "turnStart"); + let second = event(&fixture, "reusedTurnStart"); + assert_eq!(first.data, second.data); + assert_ne!(first.id, second.id); + assert_eq!( + fixture["notifications"]["turnStart"]["params"]["sessionId"], + fixture["notifications"]["reusedTurnStart"]["params"]["sessionId"] + ); +} + #[test] fn spawn_and_correction_identity_preserve_distinct_relationships() { let fixture = fixture(); diff --git a/rust/tests/fixtures/execution-identity-v1.json b/rust/tests/fixtures/execution-identity-v1.json index 2df45230fe..298fac094d 100644 --- a/rust/tests/fixtures/execution-identity-v1.json +++ b/rust/tests/fixtures/execution-identity-v1.json @@ -126,6 +126,23 @@ } } }, + "reusedTurnStart": { + "jsonrpc": "2.0", + "method": "session.event", + "params": { + "sessionId": "runtime-session-a", + "event": { + "id": "10000000-0000-4000-8000-000000000011", + "timestamp": "2026-09-16T00:00:04.250Z", + "parentId": "10000000-0000-4000-8000-000000000004", + "type": "assistant.turn_start", + "data": { + "turnId": "0", + "interactionId": "30000000-0000-4000-8000-000000000001" + } + } + } + }, "workerStarted": { "jsonrpc": "2.0", "method": "session.event", From 3d9dc7037fcb015f705e09b02c047079f62029a6 Mon Sep 17 00:00:00 2001 From: Michael Dinh Date: Wed, 23 Sep 2026 11:38:39 -0700 Subject: [PATCH 06/25] docs: separate product and OpenTelemetry identity bridges Record the verified product event-ID bridge without implying that existing runtime spans carry the same occurrence key. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- docs/features/execution-identity.md | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/docs/features/execution-identity.md b/docs/features/execution-identity.md index ac31a0b3a7..5d585c5e87 100644 --- a/docs/features/execution-identity.md +++ b/docs/features/execution-identity.md @@ -136,6 +136,12 @@ ID, account for reporting-session normalization, and retain session/agent provenance. Supplied event IDs are preserved during replay; a duplicate does not establish a new occurrence. An end event has its own ID, not its start event's ID. +That product bridge is not an OpenTelemetry occurrence join. At this baseline, +chat spans do not carry the root event ID; host-known trace parentage is a +separate relationship. Worker chat spans can cover multiple loop iterations, +so overwriting one span attribute cannot preserve all occurrence identities. +Runtime emission and collector/query support need their own verified contract. + This is not a universal equality join for all product records. The runtime can normalize non-UUID reporting session IDs, report under a parent's identity, fill a child event's missing interaction from root state, or remint an event From 183d0842857af24c5569318c88f9c6f6b6cd6ab7 Mon Sep 17 00:00:00 2001 From: Michael Dinh Date: Wed, 23 Sep 2026 11:43:15 -0700 Subject: [PATCH 07/25] docs: distinguish client-tool execution from late context Document that callback context cannot retroactively parent an earlier tool-start handoff; preserve nonblocking execution and require a verified early source. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- docs/features/execution-identity.md | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/docs/features/execution-identity.md b/docs/features/execution-identity.md index 5d585c5e87..1aed999b97 100644 --- a/docs/features/execution-identity.md +++ b/docs/features/execution-identity.md @@ -142,6 +142,13 @@ separate relationship. Worker chat spans can cover multiple loop iterations, so overwriting one span attribute cannot preserve all occurrence identities. Runtime emission and collector/query support need their own verified contract. +Client-tool timing is a separate boundary: `ToolInvocation.traceparent` and +`tracestate` come from `external_tool.requested`; the earlier +`tool.execution_start` has no typed trace-context fields at this baseline. +If a host dispatches client execution on that earlier event, a later callback +cache cannot retroactively parent it. Use a verified context source available +before handoff, and never wait for telemetry to allow the tool to execute. + This is not a universal equality join for all product records. The runtime can normalize non-UUID reporting session IDs, report under a parent's identity, fill a child event's missing interaction from root state, or remint an event From 6f558b37d207bc42ce71f658f10f383edbc97c67 Mon Sep 17 00:00:00 2001 From: Michael Dinh Date: Wed, 23 Sep 2026 15:40:09 -0700 Subject: [PATCH 08/25] docs: clarify prepared create notification buffering Distinguish retaining routed pre-response events from delivering them before create acknowledgement; resume starts its event loop earlier. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- docs/features/execution-identity.md | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/docs/features/execution-identity.md b/docs/features/execution-identity.md index 1aed999b97..bd2300fb76 100644 --- a/docs/features/execution-identity.md +++ b/docs/features/execution-identity.md @@ -87,6 +87,11 @@ the oldest pending send, queue position, or display text. [PreparedSession](./streaming-events.md#subscribing-before-a-session-starts). Notifications can precede an RPC response. Drain concurrently or size the finite buffer; handle `Lagged` as missing evidence, not a successful outcome. + On create with a known session ID, routing is registered before the RPC but + the event loop starts after the response; early notifications wait in the + router queue. Retention does not guarantee subscriber delivery before the + create acknowledgement. Resume starts the event loop before awaiting its + response. 1. Retain the accepted message ID with the application's own request/Turn identity and session provenance. Allow either the acknowledgement or event to arrive first; only join matching observed keys. From 0466a702e41ca3ca6010604faf3444863a86144d Mon Sep 17 00:00:00 2001 From: Michael Dinh Date: Wed, 23 Sep 2026 16:51:19 -0700 Subject: [PATCH 09/25] feat: expose observed early tool trace context across SDKs Regenerate all six projections from canonical runtime 7ab2ab8 schemas, preserving optionality and the runtime's independent default-off emission gate. Add compatibility, early-delivery, and replay coverage without an SDK writer or behavior change. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- docs/features/execution-identity.md | 69 ++++++++++++++++--- dotnet/src/Generated/SessionEvents.cs | 21 ++++++ .../Unit/SessionEventSerializationTests.cs | 36 ++++++++++ go/rpc/message_identity_test.go | 47 +++++++++++++ go/rpc/zrpc.go | 5 ++ go/rpc/zrpc_encoding.go | 2 + go/rpc/zsession_events.go | 9 +++ .../McpOauthRequiredStaticClientConfig.java | 4 +- .../generated/SessionFusionResolvedEvent.java | 2 + .../generated/ToolExecutionStartEvent.java | 4 ++ .../MessageIdentitySerializationTest.java | 30 ++++++++ nodejs/src/generated/rpc.ts | 4 ++ nodejs/src/generated/session-events.ts | 18 +++++ nodejs/test/message-identity-types.test.ts | 29 +++++++- python/copilot/generated/rpc.py | 33 ++++++++- python/copilot/generated/session_events.py | 21 ++++++ python/test_message_identity_generated.py | 21 +++++- rust/src/generated/api_types.rs | 3 + rust/src/generated/session_events.rs | 19 +++++ .../tests/fixtures/tool-trace-context-v1.json | 61 ++++++++++++++++ rust/tests/prepared_session_test.rs | 48 +++++++++++++ rust/tests/session_events_test.rs | 52 ++++++++++++++ 22 files changed, 524 insertions(+), 14 deletions(-) create mode 100644 rust/tests/fixtures/tool-trace-context-v1.json diff --git a/docs/features/execution-identity.md b/docs/features/execution-identity.md index bd2300fb76..704422d113 100644 --- a/docs/features/execution-identity.md +++ b/docs/features/execution-identity.md @@ -6,7 +6,9 @@ infer that association or publish telemetry facts. ## Field sources and compatibility -These fields are available in SDK 1.0.14 with its default CLI 1.0.85. A configured +The core identity fields below are available in SDK 1.0.14 with its default CLI +1.0.85. The newer early tool-context fields have a separate prerequisite +[below](#live-client-tool-trace-context). A configured CLI path or external server can select a different runtime. Record both versions and the connection/session lifetime when diagnosing an association; the SDK version alone does not prove that a runtime emitted an optional field. @@ -147,13 +149,6 @@ separate relationship. Worker chat spans can cover multiple loop iterations, so overwriting one span attribute cannot preserve all occurrence identities. Runtime emission and collector/query support need their own verified contract. -Client-tool timing is a separate boundary: `ToolInvocation.traceparent` and -`tracestate` come from `external_tool.requested`; the earlier -`tool.execution_start` has no typed trace-context fields at this baseline. -If a host dispatches client execution on that earlier event, a later callback -cache cannot retroactively parent it. Use a verified context source available -before handoff, and never wait for telemetry to allow the tool to execute. - This is not a universal equality join for all product records. The runtime can normalize non-UUID reporting session IDs, report under a parent's identity, fill a child event's missing interaction from root state, or remint an event @@ -162,6 +157,58 @@ the missing cause of a worker follow-up or recovery submission. Keep raw observations and explicit provenance; do not add a second telemetry writer or put diagnostic identifiers in metric tags. +## Live client-tool trace context + +The generated `ToolExecutionStartData` now exposes optional `traceparent` and +`tracestate` strings on `tool.execution_start.data`, including before a later +`external_tool.requested` callback. No SDK flag, writer, or new event family is +introduced. All six projections come from the canonical schemas in +[runtime commit 7ab2ab8](https://github.com/github/copilot-agent-runtime/commit/7ab2ab8ea278c812664eece9feab83ef376936eb), +delivered in [the runtime companion](https://github.com/github/copilot-agent-runtime/pull/22693). +This is an unreleased producer prerequisite, not a new published SDK/CLI floor. +The standalone CLI release pin is unchanged; it does not supply these new fields. +Release-generation inputs must advance through the normal release process before +this generated exposure can pass a pinned-release freshness check. + +The producer uses the actual active `execute_tool` span, checks the tool, +native-session and start-event identities, and decorates the live wire copy. +Its static `RUNTIME_TOOL_TRACE_CONTEXT` flag is off by default and independent +of model propagation. Missing, disabled, unavailable, or persisted-history +context remains absent. Enabling this flag does not enable an exporter. + +Rust access is +`event.typed_data::()?.traceparent` and `.tracestate`; +both are `Option`. Older payloads deserialize with `None` and serialize +without new keys. The SDK preserves supplied strings rather than interpreting +W3C validity or authorizing execution. Consumers must independently validate +context and ignore invalid telemetry without failing a valid tool operation. +No context is inherited from a previous tool or added when replay omits it. + +For AHP consumers, the agreed existing extension is +`action._meta["com.github.copilot.traceContext"]` on `chat/toolCallStart` and +`chat/toolCallReady`, with value +`{version: 1, traceparent: string, tracestate?: string}`. The wrapper is not part +of the SDK's runtime event data. Host/browser emission and consumption remain +default-off; absent, invalid, or unknown-version metadata preserves old tool +behavior. Retain unrelated metadata and ownership/confirmation checks. + +Ready can invoke a client tool synchronously. Copy the observed matching tool's +context before that handoff, never from activation identity, an arbitrary latest +tool, or a computed tool ID. `ToolInvocation` still carries the later callback +context, but a callback cache cannot retroactively parent earlier execution. +Never delay tools waiting for telemetry. Neither a start event nor trace context +is evidence of permission to execute. + +The unmodified schema inputs used for generation have SHA-256 checksums: + +* `session-events.schema.json`: `0268ec8b872b7272a011f4905cf3506b59426d930fd8d5249f4ff8b80040ffe6` +* `api.schema.json`: `141d44c48ef5665b97032a8a400271b4140c92b2ed228fc9c84736dbd3cf4fcf` + +Relative to the standalone pin, these canonical inputs also contain the existing +optional `FusionResolvedData.hint`, `McpOauthRequiredStaticClientConfig.scope`, +and `McpServerConfigHttp.oauthScopes` additions. They are generated together; +there is no hand-edited schema subset or shadow Rust field. + ## Content-free compatibility fixtures [execution-identity-v1.json](../../rust/tests/fixtures/execution-identity-v1.json) @@ -183,6 +230,12 @@ sessions, cancelled waits with late responses, resume, and new connections. They do not establish deployed runtime ordering, export coverage, or end-to-end telemetry joins. +[tool-trace-context-v1.json](../../rust/tests/fixtures/tool-trace-context-v1.json) +adds full synthetic early-tool notifications using the shared sampled and +unsampled v1 carrier values. Tests retain exact per-tool fields, duplicates, +missing context, and a same-ID persisted copy without live context, without +waiting for any tool callback. The fixture is not a recorded runtime execution. + ## Further reading * [Streaming events](./streaming-events.md): Subscription and buffering behavior diff --git a/dotnet/src/Generated/SessionEvents.cs b/dotnet/src/Generated/SessionEvents.cs index 7c53b17588..f7fa132e3b 100644 --- a/dotnet/src/Generated/SessionEvents.cs +++ b/dotnet/src/Generated/SessionEvents.cs @@ -3371,6 +3371,12 @@ public sealed partial class SessionFusionResolvedData [JsonPropertyName("fusionId")] public required string FusionId { get; set; } + /// Short human-readable summary of the selected workflow, suitable for immediate client display after routing. May be absent in older durable events; omit the explanation or derive one from pattern and phasePlan. Display text, not a stable machine-readable value. + [Experimental(Diagnostics.Experimental)] + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] + [JsonPropertyName("hint")] + public string? Hint { get; set; } + /// Version of the executable model universe used for selection. [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] [JsonPropertyName("modelUniverseVersion")] @@ -4871,6 +4877,16 @@ public sealed partial class ToolExecutionStartData [JsonPropertyName("toolTitle")] public string? ToolTitle { get; set; } + /// W3C traceparent of this tool's active runtime execute_tool span. Available on live events when tool-context propagation is enabled; absent when the span is unavailable or on persisted history. This diagnostic context does not authorize execution. + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] + [JsonPropertyName("traceparent")] + public string? Traceparent { get; set; } + + /// Optional W3C tracestate associated with traceparent. Omitted when no valid vendor state is available. + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] + [JsonPropertyName("tracestate")] + public string? Tracestate { get; set; } + /// Identifier for the agent loop turn this tool was invoked in, matching the corresponding assistant.turn_start event. [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] [JsonPropertyName("turnId")] @@ -11847,6 +11863,11 @@ public sealed partial class McpOauthRequiredStaticClientConfig [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] [JsonPropertyName("publicClient")] public bool? PublicClient { get; set; } + + /// Configured OAuth scope string used when the server challenge omits scope or provides an empty scope. + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] + [JsonPropertyName("scope")] + public string? Scope { get; set; } } /// OAuth WWW-Authenticate parameters parsed from an MCP auth challenge. diff --git a/dotnet/test/Unit/SessionEventSerializationTests.cs b/dotnet/test/Unit/SessionEventSerializationTests.cs index 4d5c041051..c25c40e3fa 100644 --- a/dotnet/test/Unit/SessionEventSerializationTests.cs +++ b/dotnet/test/Unit/SessionEventSerializationTests.cs @@ -149,6 +149,42 @@ public void UserMessageEvent_MessageId_UsesCamelCaseAndIsOptional(string? messag } } + [Theory] + [InlineData(null, null)] + [InlineData("00-11111111111111111111111111111111-2222222222222222-01", "vendor=value")] + [InlineData("00-11111111111111111111111111111111-2222222222222222-00", null)] + [InlineData("invalid", "invalid")] + [InlineData("", null)] + public void ToolStartEvent_PreservesOptionalTraceContext(string? traceparent, string? tracestate) + { + var data = new JsonObject + { + ["toolCallId"] = "tool-call-a", + ["toolName"] = "client-tool", + }; + if (traceparent is not null) + { + data["traceparent"] = traceparent; + } + if (tracestate is not null) + { + data["tracestate"] = tracestate; + } + var wire = new JsonObject + { + ["id"] = "11111111-1111-1111-1111-111111111111", + ["timestamp"] = "2026-09-18T22:00:00+00:00", + ["parentId"] = "22222222-2222-2222-2222-222222222222", + ["type"] = "tool.execution_start", + ["data"] = data, + }; + var decoded = Assert.IsType(SessionEvent.FromJson(wire.ToJsonString())); + + Assert.Equal(traceparent, decoded.Data.Traceparent); + Assert.Equal(tracestate, decoded.Data.Tracestate); + Assert.True(JsonNode.DeepEquals(wire, JsonNode.Parse(decoded.ToJson()))); + } + public static TheoryData AutoTierSwitchFailureReasons => new() { "policy_rejected", diff --git a/go/rpc/message_identity_test.go b/go/rpc/message_identity_test.go index 6124d7084a..4cca23e3ba 100644 --- a/go/rpc/message_identity_test.go +++ b/go/rpc/message_identity_test.go @@ -2,6 +2,7 @@ package rpc import ( "encoding/json" + "reflect" "testing" ) @@ -99,3 +100,49 @@ func TestUserMessageDataMessageIDJSONCompatibility(t *testing.T) { t.Fatal("messageId should be omitted when absent") } } + +func TestToolExecutionStartTraceContextJSONCompatibility(t *testing.T) { + for _, context := range []map[string]string{ + {}, + { + "traceparent": "00-11111111111111111111111111111111-2222222222222222-01", + "tracestate": "vendor=value", + }, + {"traceparent": "00-11111111111111111111111111111111-2222222222222222-00"}, + {"traceparent": "invalid", "tracestate": "invalid"}, + {"traceparent": ""}, + } { + wire := map[string]string{"toolCallId": "tool-call-a", "toolName": "client-tool"} + for key, value := range context { + wire[key] = value + } + encoded, err := json.Marshal(wire) + if err != nil { + t.Fatal(err) + } + var data ToolExecutionStartData + if err := json.Unmarshal(encoded, &data); err != nil { + t.Fatal(err) + } + for key, value := range map[string]*string{ + "traceparent": data.Traceparent, + "tracestate": data.Tracestate, + } { + expected, present := context[key] + if present && (value == nil || *value != expected) || !present && value != nil { + t.Fatalf("%s presence/value changed", key) + } + } + encoded, err = json.Marshal(data) + if err != nil { + t.Fatal(err) + } + var roundTrip map[string]string + if err := json.Unmarshal(encoded, &roundTrip); err != nil { + t.Fatal(err) + } + if !reflect.DeepEqual(wire, roundTrip) { + t.Fatal("tool-start data changed during round trip") + } + } +} diff --git a/go/rpc/zrpc.go b/go/rpc/zrpc.go index bb4f7b1e1e..86185407f4 100644 --- a/go/rpc/zrpc.go +++ b/go/rpc/zrpc.go @@ -7448,6 +7448,11 @@ type MCPServerConfigHTTP struct { OauthGrantType *MCPServerConfigHTTPOauthGrantType `json:"oauthGrantType,omitempty"` // Whether the configured OAuth client is public and does not require a client secret. OauthPublicClient *bool `json:"oauthPublicClient,omitempty"` + // Non-empty array of valid RFC 6749 scope-token strings to request for the statically + // configured OAuth client when the server challenge omits scope or provides an empty scope. + // Requires a non-empty oauthClientId. These scopes take precedence over protected-resource + // metadata. + OauthScopes []string `json:"oauthScopes,omitzero"` // Set to `true` to use defaults, or provide an object with additional auth or OIDC settings. Oidc MCPServerAuthConfig `json:"oidc,omitempty"` // Telemetry-obfuscation policy for this server's tools. diff --git a/go/rpc/zrpc_encoding.go b/go/rpc/zrpc_encoding.go index 28d192afb2..97d51702da 100644 --- a/go/rpc/zrpc_encoding.go +++ b/go/rpc/zrpc_encoding.go @@ -2854,6 +2854,7 @@ func (r *MCPServerConfigHTTP) UnmarshalJSON(data []byte) error { OauthClientID *string `json:"oauthClientId,omitempty"` OauthGrantType *MCPServerConfigHTTPOauthGrantType `json:"oauthGrantType,omitempty"` OauthPublicClient *bool `json:"oauthPublicClient,omitempty"` + OauthScopes []string `json:"oauthScopes,omitzero"` Oidc json.RawMessage `json:"oidc,omitempty"` SafeForTelemetry json.RawMessage `json:"safeForTelemetry,omitempty"` SlowConnectionThresholdMs *int64 `json:"slowConnectionThresholdMs,omitempty"` @@ -2899,6 +2900,7 @@ func (r *MCPServerConfigHTTP) UnmarshalJSON(data []byte) error { r.OauthClientID = raw.OauthClientID r.OauthGrantType = raw.OauthGrantType r.OauthPublicClient = raw.OauthPublicClient + r.OauthScopes = raw.OauthScopes if raw.Oidc != nil { value, err := unmarshalMCPServerAuthConfig(raw.Oidc) if err != nil { diff --git a/go/rpc/zsession_events.go b/go/rpc/zsession_events.go index 6e9b170661..8ffa81326e 100644 --- a/go/rpc/zsession_events.go +++ b/go/rpc/zsession_events.go @@ -1197,6 +1197,9 @@ type SessionFusionResolvedData struct { FollowUpModel string `json:"followUpModel"` // Stable identifier for the resolved HydraFusion turn. FusionID string `json:"fusionId"` + // Short human-readable summary of the selected workflow, suitable for immediate client display after routing. May be absent in older durable events; omit the explanation or derive one from pattern and phasePlan. Display text, not a stable machine-readable value. + // Experimental: Hint is part of an experimental API and may change or be removed. + Hint *string `json:"hint,omitempty"` // Version of the executable model universe used for selection. ModelUniverseVersion *string `json:"modelUniverseVersion,omitempty"` // Validated orchestration pattern selected for the turn. @@ -3110,6 +3113,10 @@ type ToolExecutionStartData struct { ToolName string `json:"toolName"` // Human-readable display title for the tool, when the selected tool descriptor has a non-empty title. ToolTitle *string `json:"toolTitle,omitempty"` + // W3C traceparent of this tool's active runtime execute_tool span. Available on live events when tool-context propagation is enabled; absent when the span is unavailable or on persisted history. This diagnostic context does not authorize execution. + Traceparent *string `json:"traceparent,omitempty"` + // Optional W3C tracestate associated with traceparent. Omitted when no valid vendor state is available. + Tracestate *string `json:"tracestate,omitempty"` // Identifier for the agent loop turn this tool was invoked in, matching the corresponding assistant.turn_start event TurnID *string `json:"turnId,omitempty"` } @@ -3851,6 +3858,8 @@ type MCPOauthRequiredStaticClientConfig struct { GrantType *MCPOauthRequiredStaticClientConfigGrantType `json:"grantType,omitempty"` // Whether this is a public OAuth client PublicClient *bool `json:"publicClient,omitempty"` + // Configured OAuth scope string used when the server challenge omits scope or provides an empty scope + Scope *string `json:"scope,omitempty"` } // OAuth WWW-Authenticate parameters parsed from an MCP auth challenge diff --git a/java/sdk/src/generated/java/com/github/copilot/generated/McpOauthRequiredStaticClientConfig.java b/java/sdk/src/generated/java/com/github/copilot/generated/McpOauthRequiredStaticClientConfig.java index 5f42ec90c4..26140e64dc 100644 --- a/java/sdk/src/generated/java/com/github/copilot/generated/McpOauthRequiredStaticClientConfig.java +++ b/java/sdk/src/generated/java/com/github/copilot/generated/McpOauthRequiredStaticClientConfig.java @@ -28,6 +28,8 @@ public record McpOauthRequiredStaticClientConfig( /** Whether this is a public OAuth client */ @JsonProperty("publicClient") Boolean publicClient, /** Optional non-default OAuth grant type. When set to 'client_credentials', the OAuth flow runs headlessly using the client_id + keychain-stored secret (no browser, no callback server). */ - @JsonProperty("grantType") String grantType + @JsonProperty("grantType") String grantType, + /** Configured OAuth scope string used when the server challenge omits scope or provides an empty scope */ + @JsonProperty("scope") String scope ) { } diff --git a/java/sdk/src/generated/java/com/github/copilot/generated/SessionFusionResolvedEvent.java b/java/sdk/src/generated/java/com/github/copilot/generated/SessionFusionResolvedEvent.java index d63bee33b3..d9f5ff91cd 100644 --- a/java/sdk/src/generated/java/com/github/copilot/generated/SessionFusionResolvedEvent.java +++ b/java/sdk/src/generated/java/com/github/copilot/generated/SessionFusionResolvedEvent.java @@ -63,6 +63,8 @@ public record SessionFusionResolvedEventData( @JsonProperty("scores") FusionScores scores, /** Validated orchestration pattern selected for the turn. */ @JsonProperty("pattern") FusionPattern pattern, + /** Short human-readable summary of the selected workflow, suitable for immediate client display after routing. May be absent in older durable events; omit the explanation or derive one from pattern and phasePlan. Display text, not a stable machine-readable value. */ + @JsonProperty("hint") String hint, /** Presentation-neutral phase plan for clients that render workflow progress. */ @JsonProperty("phasePlan") List phasePlan, /** Concrete model selected for the primary solver phase. */ diff --git a/java/sdk/src/generated/java/com/github/copilot/generated/ToolExecutionStartEvent.java b/java/sdk/src/generated/java/com/github/copilot/generated/ToolExecutionStartEvent.java index 8bd686beaf..12c6c2ffd8 100644 --- a/java/sdk/src/generated/java/com/github/copilot/generated/ToolExecutionStartEvent.java +++ b/java/sdk/src/generated/java/com/github/copilot/generated/ToolExecutionStartEvent.java @@ -38,6 +38,10 @@ public record ToolExecutionStartEventData( @JsonProperty("toolCallId") String toolCallId, /** Name of the tool being executed */ @JsonProperty("toolName") String toolName, + /** W3C traceparent of this tool's active runtime execute_tool span. Available on live events when tool-context propagation is enabled; absent when the span is unavailable or on persisted history. This diagnostic context does not authorize execution. */ + @JsonProperty("traceparent") String traceparent, + /** Optional W3C tracestate associated with traceparent. Omitted when no valid vendor state is available. */ + @JsonProperty("tracestate") String tracestate, /** Human-readable display title for the tool, when the selected tool descriptor has a non-empty title. */ @JsonProperty("toolTitle") String toolTitle, /** Arguments passed to the tool */ diff --git a/java/sdk/src/test/java/com/github/copilot/generated/MessageIdentitySerializationTest.java b/java/sdk/src/test/java/com/github/copilot/generated/MessageIdentitySerializationTest.java index 3ef4770562..1f88cdab06 100644 --- a/java/sdk/src/test/java/com/github/copilot/generated/MessageIdentitySerializationTest.java +++ b/java/sdk/src/test/java/com/github/copilot/generated/MessageIdentitySerializationTest.java @@ -12,6 +12,7 @@ import com.fasterxml.jackson.databind.JsonNode; import com.fasterxml.jackson.databind.ObjectMapper; +import com.fasterxml.jackson.databind.node.ObjectNode; import com.github.copilot.generated.UserMessageEvent.UserMessageEventData; import com.github.copilot.generated.rpc.QueuePendingItems; @@ -63,4 +64,33 @@ void testUserMessageIdUsesCamelCaseAndIsOptional() throws Exception { assertNull(olderMessage.messageId()); assertFalse(MAPPER.valueToTree(olderMessage).has("messageId")); } + + @Test + void testToolStartTraceContextIsOptionalAndPreserved() throws Exception { + for (var context : new String[] { + "{}", + """ + {"traceparent":"00-11111111111111111111111111111111-2222222222222222-01","tracestate":"vendor=value"} + """, + """ + {"traceparent":"00-11111111111111111111111111111111-2222222222222222-00"} + """, + """ + {"traceparent":"invalid","tracestate":"invalid"} + """, + """ + {"traceparent":""} + """ + }) { + var wire = MAPPER.createObjectNode() + .put("toolCallId", "tool-call-a") + .put("toolName", "client-tool"); + wire.setAll(MAPPER.readValue(context, ObjectNode.class)); + var data = MAPPER.treeToValue(wire, ToolExecutionStartEvent.ToolExecutionStartEventData.class); + + assertEquals(wire.path("traceparent").asText(null), data.traceparent()); + assertEquals(wire.path("tracestate").asText(null), data.tracestate()); + assertEquals(wire, MAPPER.valueToTree(data)); + } + } } diff --git a/nodejs/src/generated/rpc.ts b/nodejs/src/generated/rpc.ts index da14db8f03..bf7b24fd8b 100644 --- a/nodejs/src/generated/rpc.ts +++ b/nodejs/src/generated/rpc.ts @@ -12420,6 +12420,10 @@ export interface McpServerConfigHttp { * OAuth client ID for a pre-registered remote MCP OAuth client. */ oauthClientId?: string; + /** + * Non-empty array of valid RFC 6749 scope-token strings to request for the statically configured OAuth client when the server challenge omits scope or provides an empty scope. Requires a non-empty oauthClientId. These scopes take precedence over protected-resource metadata. + */ + oauthScopes?: string[]; /** * Whether the configured OAuth client is public and does not require a client secret. */ diff --git a/nodejs/src/generated/session-events.ts b/nodejs/src/generated/session-events.ts index 6bc6600d08..8e8ce70b3a 100644 --- a/nodejs/src/generated/session-events.ts +++ b/nodejs/src/generated/session-events.ts @@ -4014,6 +4014,12 @@ export interface FusionResolvedData { * Stable identifier for the resolved HydraFusion turn. */ fusionId: string; + /** + * Short human-readable summary of the selected workflow, suitable for immediate client display after routing. May be absent in older durable events; omit the explanation or derive one from pattern and phasePlan. Display text, not a stable machine-readable value. + * + * @experimental + */ + hint?: string; /** * Version of the executable model universe used for selection. */ @@ -6736,6 +6742,14 @@ export interface ToolExecutionStartData { * Human-readable display title for the tool, when the selected tool descriptor has a non-empty title. */ toolTitle?: string; + /** + * W3C traceparent of this tool's active runtime execute_tool span. Available on live events when tool-context propagation is enabled; absent when the span is unavailable or on persisted history. This diagnostic context does not authorize execution. + */ + traceparent?: string; + /** + * Optional W3C tracestate associated with traceparent. Omitted when no valid vendor state is available. + */ + tracestate?: string; /** * Identifier for the agent loop turn this tool was invoked in, matching the corresponding assistant.turn_start event */ @@ -10848,6 +10862,10 @@ export interface McpOauthRequiredStaticClientConfig { * Whether this is a public OAuth client */ publicClient?: boolean; + /** + * Configured OAuth scope string used when the server challenge omits scope or provides an empty scope + */ + scope?: string; } /** * OAuth WWW-Authenticate parameters parsed from an MCP auth challenge diff --git a/nodejs/test/message-identity-types.test.ts b/nodejs/test/message-identity-types.test.ts index 903fc7533c..a3c7c01a3e 100644 --- a/nodejs/test/message-identity-types.test.ts +++ b/nodejs/test/message-identity-types.test.ts @@ -1,6 +1,6 @@ import { describe, expect, it } from "vitest"; import type { QueuePendingItems } from "../src/generated/rpc.js"; -import type { UserMessageData } from "../src/generated/session-events.js"; +import type { ToolExecutionStartData, UserMessageData } from "../src/generated/session-events.js"; describe("generated message identity types", () => { it("exposes optional camelCase message IDs", () => { @@ -30,4 +30,31 @@ describe("generated message identity types", () => { expect(userMessageWithIdentity.messageId).toBe("message-1"); expect(userMessageFromOlderRuntime.messageId).toBeUndefined(); }); + + it("exposes optional early tool context without interpreting it", () => { + const contexts: Array> = [ + {}, + { + traceparent: "00-11111111111111111111111111111111-2222222222222222-01", + tracestate: "vendor=value", + }, + { traceparent: "00-11111111111111111111111111111111-2222222222222222-00" }, + { traceparent: "invalid", tracestate: "invalid" }, + { traceparent: "" }, + ]; + for (const context of contexts) { + const data: ToolExecutionStartData = { + toolCallId: "tool-call-a", + toolName: "client-tool", + ...context, + }; + expect(data.traceparent).toBe(context.traceparent); + expect(data.tracestate).toBe(context.tracestate); + expect(JSON.parse(JSON.stringify(data))).toEqual({ + toolCallId: "tool-call-a", + toolName: "client-tool", + ...context, + }); + } + }); }); diff --git a/python/copilot/generated/rpc.py b/python/copilot/generated/rpc.py index c7c9016899..8d852a7b4b 100644 --- a/python/copilot/generated/rpc.py +++ b/python/copilot/generated/rpc.py @@ -34081,6 +34081,12 @@ class MCPSerializableServerConfig: oauth_public_client: bool | None = None """Whether the configured OAuth client is public and does not require a client secret.""" + oauth_scopes: list[str] | None = None + """Non-empty array of valid RFC 6749 scope-token strings to request for the statically + configured OAuth client when the server challenge omits scope or provides an empty scope. + Requires a non-empty oauthClientId. These scopes take precedence over protected-resource + metadata. + """ url: str | None = None """URL of the remote MCP server endpoint.""" @@ -34118,8 +34124,9 @@ def from_dict(obj: Any) -> 'MCPSerializableServerConfig': oauth_client_id = from_union([from_str, from_none], obj.get("oauthClientId")) oauth_grant_type = from_union([MCPGrantType, from_none], obj.get("oauthGrantType")) oauth_public_client = from_union([from_bool, from_none], obj.get("oauthPublicClient")) + oauth_scopes = from_union([lambda x: from_list(from_str, x), from_none], obj.get("oauthScopes")) url = from_union([from_str, from_none], obj.get("url")) - return MCPSerializableServerConfig(args, auth, command, config_warnings, cwd, defer_tools, disable_secret_masking, disable_tool_cache, display_name, env, events, exclude_tools, filter_mapping, is_default_server, notifications, oidc, safe_for_telemetry, slow_connection_threshold_ms, source, source_path, source_plugin, source_plugin_spec, source_plugin_version, timeout, tools, type, headers, headers_refresh_ttl_ms, oauth_client_id, oauth_grant_type, oauth_public_client, url) + return MCPSerializableServerConfig(args, auth, command, config_warnings, cwd, defer_tools, disable_secret_masking, disable_tool_cache, display_name, env, events, exclude_tools, filter_mapping, is_default_server, notifications, oidc, safe_for_telemetry, slow_connection_threshold_ms, source, source_path, source_plugin, source_plugin_spec, source_plugin_version, timeout, tools, type, headers, headers_refresh_ttl_ms, oauth_client_id, oauth_grant_type, oauth_public_client, oauth_scopes, url) def to_dict(self) -> dict: result: dict = {} @@ -34185,6 +34192,8 @@ def to_dict(self) -> dict: result["oauthGrantType"] = from_union([lambda x: to_enum(MCPGrantType, x), from_none], self.oauth_grant_type) if self.oauth_public_client is not None: result["oauthPublicClient"] = from_union([from_bool, from_none], self.oauth_public_client) + if self.oauth_scopes is not None: + result["oauthScopes"] = from_union([lambda x: from_list(from_str, x), from_none], self.oauth_scopes) if self.url is not None: result["url"] = from_union([from_str, from_none], self.url) return result @@ -34249,6 +34258,12 @@ class MCPServerConfigHTTP: oauth_public_client: bool | None = None """Whether the configured OAuth client is public and does not require a client secret.""" + oauth_scopes: list[str] | None = None + """Non-empty array of valid RFC 6749 scope-token strings to request for the statically + configured OAuth client when the server challenge omits scope or provides an empty scope. + Requires a non-empty oauthClientId. These scopes take precedence over protected-resource + metadata. + """ oidc: bool | MCPServerAuthConfigRedirectPort | None = None """Set to `true` to use defaults, or provide an object with additional auth or OIDC settings.""" @@ -34304,6 +34319,7 @@ def from_dict(obj: Any) -> 'MCPServerConfigHTTP': oauth_client_id = from_union([from_str, from_none], obj.get("oauthClientId")) oauth_grant_type = from_union([MCPGrantType, from_none], obj.get("oauthGrantType")) oauth_public_client = from_union([from_bool, from_none], obj.get("oauthPublicClient")) + oauth_scopes = from_union([lambda x: from_list(from_str, x), from_none], obj.get("oauthScopes")) oidc = from_union([from_bool, MCPServerAuthConfigRedirectPort.from_dict, from_none], obj.get("oidc")) safe_for_telemetry = from_union([from_bool, MCPSafeForTelemetryFields.from_dict, from_none], obj.get("safeForTelemetry")) slow_connection_threshold_ms = from_union([from_int, from_none], obj.get("slowConnectionThresholdMs")) @@ -34315,7 +34331,7 @@ def from_dict(obj: Any) -> 'MCPServerConfigHTTP': timeout = from_union([from_int, from_none], obj.get("timeout")) tools = from_union([lambda x: from_list(from_str, x), from_none], obj.get("tools")) type = from_union([MCPServerConfigHTTPType, from_none], obj.get("type")) - return MCPServerConfigHTTP(url, auth, config_warnings, defer_tools, disable_secret_masking, disable_tool_cache, display_name, events, exclude_tools, filter_mapping, headers, headers_refresh_ttl_ms, is_default_server, notifications, oauth_client_id, oauth_grant_type, oauth_public_client, oidc, safe_for_telemetry, slow_connection_threshold_ms, source, source_path, source_plugin, source_plugin_spec, source_plugin_version, timeout, tools, type) + return MCPServerConfigHTTP(url, auth, config_warnings, defer_tools, disable_secret_masking, disable_tool_cache, display_name, events, exclude_tools, filter_mapping, headers, headers_refresh_ttl_ms, is_default_server, notifications, oauth_client_id, oauth_grant_type, oauth_public_client, oauth_scopes, oidc, safe_for_telemetry, slow_connection_threshold_ms, source, source_path, source_plugin, source_plugin_spec, source_plugin_version, timeout, tools, type) def to_dict(self) -> dict: result: dict = {} @@ -34352,6 +34368,8 @@ def to_dict(self) -> dict: result["oauthGrantType"] = from_union([lambda x: to_enum(MCPGrantType, x), from_none], self.oauth_grant_type) if self.oauth_public_client is not None: result["oauthPublicClient"] = from_union([from_bool, from_none], self.oauth_public_client) + if self.oauth_scopes is not None: + result["oauthScopes"] = from_union([lambda x: from_list(from_str, x), from_none], self.oauth_scopes) if self.oidc is not None: result["oidc"] = from_union([from_bool, lambda x: to_class(MCPServerAuthConfigRedirectPort, x), from_none], self.oidc) if self.safe_for_telemetry is not None: @@ -38774,6 +38792,12 @@ class MCPServerConfig: oauth_public_client: bool | None = None """Whether the configured OAuth client is public and does not require a client secret.""" + oauth_scopes: list[str] | None = None + """Non-empty array of valid RFC 6749 scope-token strings to request for the statically + configured OAuth client when the server challenge omits scope or provides an empty scope. + Requires a non-empty oauthClientId. These scopes take precedence over protected-resource + metadata. + """ url: str | None = None """URL of the remote MCP server endpoint.""" @@ -38814,9 +38838,10 @@ def from_dict(obj: Any) -> 'MCPServerConfig': oauth_client_id = from_union([from_str, from_none], obj.get("oauthClientId")) oauth_grant_type = from_union([MCPGrantType, from_none], obj.get("oauthGrantType")) oauth_public_client = from_union([from_bool, from_none], obj.get("oauthPublicClient")) + oauth_scopes = from_union([lambda x: from_list(from_str, x), from_none], obj.get("oauthScopes")) url = from_union([from_str, from_none], obj.get("url")) server_instance = obj.get("serverInstance") - return MCPServerConfig(args, auth, command, config_warnings, cwd, defer_tools, disable_secret_masking, disable_tool_cache, display_name, env, events, exclude_tools, filter_mapping, is_default_server, notifications, oidc, safe_for_telemetry, slow_connection_threshold_ms, source, source_path, source_plugin, source_plugin_spec, source_plugin_version, timeout, tools, type, headers, headers_refresh_ttl_ms, oauth_client_id, oauth_grant_type, oauth_public_client, url, server_instance) + return MCPServerConfig(args, auth, command, config_warnings, cwd, defer_tools, disable_secret_masking, disable_tool_cache, display_name, env, events, exclude_tools, filter_mapping, is_default_server, notifications, oidc, safe_for_telemetry, slow_connection_threshold_ms, source, source_path, source_plugin, source_plugin_spec, source_plugin_version, timeout, tools, type, headers, headers_refresh_ttl_ms, oauth_client_id, oauth_grant_type, oauth_public_client, oauth_scopes, url, server_instance) def to_dict(self) -> dict: result: dict = {} @@ -38882,6 +38907,8 @@ def to_dict(self) -> dict: result["oauthGrantType"] = from_union([lambda x: to_enum(MCPGrantType, x), from_none], self.oauth_grant_type) if self.oauth_public_client is not None: result["oauthPublicClient"] = from_union([from_bool, from_none], self.oauth_public_client) + if self.oauth_scopes is not None: + result["oauthScopes"] = from_union([lambda x: from_list(from_str, x), from_none], self.oauth_scopes) if self.url is not None: result["url"] = from_union([from_str, from_none], self.url) if self.server_instance is not None: diff --git a/python/copilot/generated/session_events.py b/python/copilot/generated/session_events.py index aa16f5ce64..4207a9d14a 100644 --- a/python/copilot/generated/session_events.py +++ b/python/copilot/generated/session_events.py @@ -2192,6 +2192,8 @@ class SessionFusionResolvedData: synthetic_model: str turn_id: str follow_up: FusionFollowUpRecommendation | None = None + # Experimental: this field is part of an experimental API and may change or be removed. + hint: str | None = None model_universe_version: str | None = None # Experimental: this field is part of an experimental API and may change or be removed. phase_plan: list[FusionPhasePlanStep] | None = None @@ -2218,6 +2220,7 @@ def from_dict(obj: Any) -> "SessionFusionResolvedData": synthetic_model = from_str(obj.get("syntheticModel")) turn_id = from_str(obj.get("turnId")) follow_up = from_union([from_none, FusionFollowUpRecommendation.from_dict], obj.get("followUp")) + hint = from_union([from_none, from_str], obj.get("hint")) model_universe_version = from_union([from_none, from_str], obj.get("modelUniverseVersion")) phase_plan = from_union([from_none, lambda x: from_list(FusionPhasePlanStep.from_dict, x)], obj.get("phasePlan")) plan_version = from_union([from_none, from_str], obj.get("planVersion")) @@ -2240,6 +2243,7 @@ def from_dict(obj: Any) -> "SessionFusionResolvedData": synthetic_model=synthetic_model, turn_id=turn_id, follow_up=follow_up, + hint=hint, model_universe_version=model_universe_version, phase_plan=phase_plan, plan_version=plan_version, @@ -2266,6 +2270,8 @@ def to_dict(self) -> dict: result["turnId"] = from_str(self.turn_id) if self.follow_up is not None: result["followUp"] = from_union([from_none, lambda x: to_class(FusionFollowUpRecommendation, x)], self.follow_up) + if self.hint is not None: + result["hint"] = from_union([from_none, from_str], self.hint) if self.model_universe_version is not None: result["modelUniverseVersion"] = from_union([from_none, from_str], self.model_universe_version) if self.phase_plan is not None: @@ -5626,6 +5632,7 @@ class McpOauthRequiredStaticClientConfig: client_secret: str | None = None grant_type: str | None = None public_client: bool | None = None + scope: str | None = None @staticmethod def from_dict(obj: Any) -> "McpOauthRequiredStaticClientConfig": @@ -5634,11 +5641,13 @@ def from_dict(obj: Any) -> "McpOauthRequiredStaticClientConfig": client_secret = from_union([from_none, from_str], obj.get("clientSecret")) grant_type = from_union([from_none, from_str], obj.get("grantType")) public_client = from_union([from_none, from_bool], obj.get("publicClient")) + scope = from_union([from_none, from_str], obj.get("scope")) return McpOauthRequiredStaticClientConfig( client_id=client_id, client_secret=client_secret, grant_type=grant_type, public_client=public_client, + scope=scope, ) def to_dict(self) -> dict: @@ -5650,6 +5659,8 @@ def to_dict(self) -> dict: result["grantType"] = from_union([from_none, from_str], self.grant_type) if self.public_client is not None: result["publicClient"] = from_union([from_none, from_bool], self.public_client) + if self.scope is not None: + result["scope"] = from_union([from_none, from_str], self.scope) return result @@ -12249,6 +12260,8 @@ class ToolExecutionStartData: shell_tool_info: ToolExecutionStartShellToolInfo | None = None tool_description: ToolExecutionStartToolDescription | None = None tool_title: str | None = None + traceparent: str | None = None + tracestate: str | None = None turn_id: str | None = None @staticmethod @@ -12270,6 +12283,8 @@ def from_dict(obj: Any) -> "ToolExecutionStartData": shell_tool_info = from_union([from_none, ToolExecutionStartShellToolInfo.from_dict], obj.get("shellToolInfo")) tool_description = from_union([from_none, ToolExecutionStartToolDescription.from_dict], obj.get("toolDescription")) tool_title = from_union([from_none, from_str], obj.get("toolTitle")) + traceparent = from_union([from_none, from_str], obj.get("traceparent")) + tracestate = from_union([from_none, from_str], obj.get("tracestate")) turn_id = from_union([from_none, from_str], obj.get("turnId")) return ToolExecutionStartData( tool_call_id=tool_call_id, @@ -12288,6 +12303,8 @@ def from_dict(obj: Any) -> "ToolExecutionStartData": shell_tool_info=shell_tool_info, tool_description=tool_description, tool_title=tool_title, + traceparent=traceparent, + tracestate=tracestate, turn_id=turn_id, ) @@ -12323,6 +12340,10 @@ def to_dict(self) -> dict: result["toolDescription"] = from_union([from_none, lambda x: to_class(ToolExecutionStartToolDescription, x)], self.tool_description) if self.tool_title is not None: result["toolTitle"] = from_union([from_none, from_str], self.tool_title) + if self.traceparent is not None: + result["traceparent"] = from_union([from_none, from_str], self.traceparent) + if self.tracestate is not None: + result["tracestate"] = from_union([from_none, from_str], self.tracestate) if self.turn_id is not None: result["turnId"] = from_union([from_none, from_str], self.turn_id) return result diff --git a/python/test_message_identity_generated.py b/python/test_message_identity_generated.py index 024223ac96..388699d885 100644 --- a/python/test_message_identity_generated.py +++ b/python/test_message_identity_generated.py @@ -1,5 +1,5 @@ from copilot.generated.rpc import QueuePendingItems -from copilot.generated.session_events import UserMessageData +from copilot.generated.session_events import ToolExecutionStartData, UserMessageData def test_queue_pending_message_id_uses_camel_case_and_is_optional(): @@ -39,3 +39,22 @@ def test_user_message_id_uses_camel_case_and_is_optional(): assert older_message.message_id is None assert "messageId" not in older_message.to_dict() + + +def test_tool_start_trace_context_is_optional_and_preserved(): + for context in [ + {}, + { + "traceparent": "00-11111111111111111111111111111111-2222222222222222-01", + "tracestate": "vendor=value", + }, + {"traceparent": "00-11111111111111111111111111111111-2222222222222222-00"}, + {"traceparent": "invalid", "tracestate": "invalid"}, + {"traceparent": ""}, + ]: + wire = {"toolCallId": "tool-call-a", "toolName": "client-tool", **context} + data = ToolExecutionStartData.from_dict(wire) + + assert data.traceparent == context.get("traceparent") + assert data.tracestate == context.get("tracestate") + assert data.to_dict() == wire diff --git a/rust/src/generated/api_types.rs b/rust/src/generated/api_types.rs index 17d500d7d0..55e8767fb5 100644 --- a/rust/src/generated/api_types.rs +++ b/rust/src/generated/api_types.rs @@ -10985,6 +10985,9 @@ pub struct McpServerConfigHttp { /// Whether the configured OAuth client is public and does not require a client secret. #[serde(skip_serializing_if = "Option::is_none")] pub oauth_public_client: Option, + /// Non-empty array of valid RFC 6749 scope-token strings to request for the statically configured OAuth client when the server challenge omits scope or provides an empty scope. Requires a non-empty oauthClientId. These scopes take precedence over protected-resource metadata. + #[serde(skip_serializing_if = "Option::is_none")] + pub oauth_scopes: Option>, /// Set to `true` to use defaults, or provide an object with additional auth or OIDC settings. #[serde(skip_serializing_if = "Option::is_none")] pub oidc: Option, diff --git a/rust/src/generated/session_events.rs b/rust/src/generated/session_events.rs index 492a453f2f..3810d49f7f 100644 --- a/rust/src/generated/session_events.rs +++ b/rust/src/generated/session_events.rs @@ -2463,6 +2463,16 @@ pub struct SessionFusionResolvedData { pub follow_up_model: String, /// Stable identifier for the resolved HydraFusion turn. pub fusion_id: String, + /// Short human-readable summary of the selected workflow, suitable for immediate client display after routing. May be absent in older durable events; omit the explanation or derive one from pattern and phasePlan. Display text, not a stable machine-readable value. + /// + ///
+ /// + /// **Experimental.** This type is part of an experimental wire-protocol surface + /// and may change or be removed in future SDK or CLI releases. + /// + ///
+ #[serde(skip_serializing_if = "Option::is_none")] + pub hint: Option, /// Version of the executable model universe used for selection. #[serde(skip_serializing_if = "Option::is_none")] pub model_universe_version: Option, @@ -3926,6 +3936,12 @@ pub struct ToolExecutionStartData { /// Human-readable display title for the tool, when the selected tool descriptor has a non-empty title. #[serde(skip_serializing_if = "Option::is_none")] pub tool_title: Option, + /// W3C traceparent of this tool's active runtime execute_tool span. Available on live events when tool-context propagation is enabled; absent when the span is unavailable or on persisted history. This diagnostic context does not authorize execution. + #[serde(skip_serializing_if = "Option::is_none")] + pub traceparent: Option, + /// Optional W3C tracestate associated with traceparent. Omitted when no valid vendor state is available. + #[serde(skip_serializing_if = "Option::is_none")] + pub tracestate: Option, /// Identifier for the agent loop turn this tool was invoked in, matching the corresponding assistant.turn_start event #[serde(skip_serializing_if = "Option::is_none")] pub turn_id: Option, @@ -6841,6 +6857,9 @@ pub struct McpOauthRequiredStaticClientConfig { /// Whether this is a public OAuth client #[serde(skip_serializing_if = "Option::is_none")] pub public_client: Option, + /// Configured OAuth scope string used when the server challenge omits scope or provides an empty scope + #[serde(skip_serializing_if = "Option::is_none")] + pub scope: Option, } /// OAuth WWW-Authenticate parameters parsed from an MCP auth challenge diff --git a/rust/tests/fixtures/tool-trace-context-v1.json b/rust/tests/fixtures/tool-trace-context-v1.json new file mode 100644 index 0000000000..6be7e28c89 --- /dev/null +++ b/rust/tests/fixtures/tool-trace-context-v1.json @@ -0,0 +1,61 @@ +{ + "version": 1, + "synthetic": true, + "runtimeCommit": "7ab2ab8ea278c812664eece9feab83ef376936eb", + "notifications": { + "sampled": { + "jsonrpc": "2.0", + "method": "session.event", + "params": { + "sessionId": "runtime-session-a", + "event": { + "id": "60000000-0000-4000-8000-000000000001", + "timestamp": "2026-09-23T00:00:01Z", + "parentId": "60000000-0000-4000-8000-000000000000", + "type": "tool.execution_start", + "data": { + "toolCallId": "tool-a", + "toolName": "client-tool", + "traceparent": "00-11111111111111111111111111111111-2222222222222222-01", + "tracestate": "vendor=value" + } + } + } + }, + "unsampled": { + "jsonrpc": "2.0", + "method": "session.event", + "params": { + "sessionId": "runtime-session-a", + "event": { + "id": "60000000-0000-4000-8000-000000000002", + "timestamp": "2026-09-23T00:00:02Z", + "parentId": "60000000-0000-4000-8000-000000000001", + "type": "tool.execution_start", + "data": { + "toolCallId": "tool-b", + "toolName": "client-tool", + "traceparent": "00-11111111111111111111111111111111-2222222222222222-00" + } + } + } + }, + "absent": { + "jsonrpc": "2.0", + "method": "session.event", + "params": { + "sessionId": "runtime-session-a", + "event": { + "id": "60000000-0000-4000-8000-000000000003", + "timestamp": "2026-09-23T00:00:03Z", + "parentId": "60000000-0000-4000-8000-000000000002", + "type": "tool.execution_start", + "data": { + "toolCallId": "tool-c", + "toolName": "client-tool" + } + } + } + } + } +} diff --git a/rust/tests/prepared_session_test.rs b/rust/tests/prepared_session_test.rs index e2a88bcf0a..8c9c1a78d2 100644 --- a/rust/tests/prepared_session_test.rs +++ b/rust/tests/prepared_session_test.rs @@ -1597,3 +1597,51 @@ async fn a_new_connection_does_not_reuse_observed_identity_for_the_same_session_ expect_closed(&mut events).await; } } + +#[tokio::test] +async fn early_tool_context_is_delivered_without_waiting_for_a_callback_or_reusing_context() { + use github_copilot_sdk::session_events::ToolExecutionStartData; + + let fixture: Value = + serde_json::from_str(include_str!("fixtures/tool-trace-context-v1.json")).unwrap(); + let notifications = &fixture["notifications"]; + let session_id = notifications["sampled"]["params"]["sessionId"] + .as_str() + .unwrap(); + let (client, mut server) = make_client(); + let (_session, mut events) = start_identity_session(&client, &mut server, session_id).await; + + for name in ["sampled", "unsampled", "absent", "sampled"] { + server.send_notification(¬ifications[name]).await; + } + let mut persisted = notifications["sampled"].clone(); + let data = persisted["params"]["event"]["data"] + .as_object_mut() + .unwrap(); + data.remove("traceparent"); + data.remove("tracestate"); + server.send_notification(&persisted).await; + + for wire in [ + ¬ifications["sampled"], + ¬ifications["unsampled"], + ¬ifications["absent"], + ¬ifications["sampled"], + &persisted, + ] { + let event = timeout(TIMEOUT, events.recv()).await.unwrap().unwrap(); + let data = event.typed_data::().unwrap(); + let expected = &wire["params"]["event"]["data"]; + assert_eq!(data.tool_call_id, expected["toolCallId"].as_str().unwrap()); + assert_eq!( + data.traceparent.as_deref(), + expected["traceparent"].as_str() + ); + assert_eq!(data.tracestate.as_deref(), expected["tracestate"].as_str()); + assert_eq!( + serde_json::to_value(event).unwrap(), + wire["params"]["event"] + ); + } + server.expect_quiet().await; +} diff --git a/rust/tests/session_events_test.rs b/rust/tests/session_events_test.rs index aabdd7f4f4..f324d636f0 100644 --- a/rust/tests/session_events_test.rs +++ b/rust/tests/session_events_test.rs @@ -284,3 +284,55 @@ fn user_message_id_is_optional_for_older_hosts() { .is_none() ); } + +#[test] +fn tool_execution_start_preserves_optional_trace_context_without_changing_tool_data() { + let traceparent = "00-33333333333333333333333333333333-4444444444444444-01"; + for (parent, state) in [ + (None, None), + (Some(traceparent), None), + (Some(traceparent), Some("vendor=tool")), + (Some("invalid"), Some("invalid")), + (Some(""), None), + ] { + let mut data = serde_json::json!({ + "toolCallId": "tool-call-a", + "toolName": "client-tool" + }); + if let Some(parent) = parent { + data["traceparent"] = serde_json::json!(parent); + } + if let Some(state) = state { + data["tracestate"] = serde_json::json!(state); + } + let wire = event_envelope("tool.execution_start", data); + let event: TypedSessionEvent = serde_json::from_value(wire.clone()).unwrap(); + let SessionEventData::ToolExecutionStart(data) = &event.payload else { + panic!("expected tool-start event"); + }; + assert_eq!(data.tool_call_id, "tool-call-a"); + assert_eq!(data.tool_name, "client-tool"); + assert_eq!(data.traceparent.as_deref(), parent); + assert_eq!(data.tracestate.as_deref(), state); + assert_eq!(serde_json::to_value(event).unwrap(), wire); + } +} + +#[test] +fn early_tool_context_fixture_round_trips_without_content_or_invented_fields() { + let fixture: serde_json::Value = + serde_json::from_str(include_str!("fixtures/tool-trace-context-v1.json")).unwrap(); + assert_eq!(fixture["synthetic"], true); + for notification in fixture["notifications"].as_object().unwrap().values() { + let wire = ¬ification["params"]["event"]; + let event: TypedSessionEvent = serde_json::from_value(wire.clone()).unwrap(); + assert!(matches!( + &event.payload, + SessionEventData::ToolExecutionStart(_) + )); + assert!(wire["data"].get("arguments").is_none()); + assert!(wire["data"].get("version").is_none()); + assert!(wire["data"].get("_meta").is_none()); + assert_eq!(serde_json::to_value(event).unwrap(), *wire); + } +} From c48694fa94f674c8e1b92cc52a494e836a81962f Mon Sep 17 00:00:00 2001 From: Michael Dinh Date: Wed, 23 Sep 2026 17:24:32 -0700 Subject: [PATCH 10/25] docs: require duplicate-stable runtime tool context Keep schema provenance at 7ab2ab8 while requiring runtime producer 27867ac0 for stable duplicate-start context and documenting its gated existing event-ID span attribute. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- docs/features/execution-identity.md | 16 +++++++++++----- 1 file changed, 11 insertions(+), 5 deletions(-) diff --git a/docs/features/execution-identity.md b/docs/features/execution-identity.md index 704422d113..1d1a523063 100644 --- a/docs/features/execution-identity.md +++ b/docs/features/execution-identity.md @@ -143,11 +143,13 @@ ID, account for reporting-session normalization, and retain session/agent provenance. Supplied event IDs are preserved during replay; a duplicate does not establish a new occurrence. An end event has its own ID, not its start event's ID. -That product bridge is not an OpenTelemetry occurrence join. At this baseline, -chat spans do not carry the root event ID; host-known trace parentage is a -separate relationship. Worker chat spans can cover multiple loop iterations, -so overwriting one span attribute cannot preserve all occurrence identities. -Runtime emission and collector/query support need their own verified contract. +CLI 1.0.85 does not put that occurrence ID on OpenTelemetry chat spans. The +updated runtime companion adds `copilot.runtime.event_id` from the observed +Turn-start event, behind the default-off `RUNTIME_OPERATION_TRACE_CONTEXT` +flag. Worker iterations use bounded child spans rather than overwriting a +multi-iteration invocation span. This adds no SDK identity or metric tag. +Host-known trace parentage and collector/query availability remain separate +relationships and verification requirements. This is not a universal equality join for all product records. The runtime can normalize non-UUID reporting session IDs, report under a parent's identity, @@ -165,6 +167,10 @@ The generated `ToolExecutionStartData` now exposes optional `traceparent` and introduced. All six projections come from the canonical schemas in [runtime commit 7ab2ab8](https://github.com/github/copilot-agent-runtime/commit/7ab2ab8ea278c812664eece9feab83ef376936eb), delivered in [the runtime companion](https://github.com/github/copilot-agent-runtime/pull/22693). +Use its [producer commit 27867ac0](https://github.com/github/copilot-agent-runtime/commit/27867ac02ac1606ed4d112cc613011e0ebc69ba5) +or a release containing it: the producer also preserves the published tool-span +context when the same canonical start is projected twice, keeping the later +callback parent consistent. The schema checksums are unchanged from `7ab2ab8`. This is an unreleased producer prerequisite, not a new published SDK/CLI floor. The standalone CLI release pin is unchanged; it does not supply these new fields. Release-generation inputs must advance through the normal release process before From de20080b0dd33fadf84f384d1c49693205eed850 Mon Sep 17 00:00:00 2001 From: Michael Dinh Date: Wed, 23 Sep 2026 17:36:31 -0700 Subject: [PATCH 11/25] docs: regenerate canonical runtime identity descriptions Use runtime c4e6d732 schemas to clarify prepared-run parentAgentTaskId and API-spawn toolCallId fallback across all generated SDK documentation. Exactly four schema descriptions change; wire shapes and runtime behavior do not. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- docs/features/execution-identity.md | 7 ++++--- dotnet/src/Generated/SessionEvents.cs | 8 ++++---- go/rpc/zsession_events.go | 8 ++++---- .../github/copilot/generated/SubagentCompletedEvent.java | 2 +- .../com/github/copilot/generated/SubagentFailedEvent.java | 2 +- .../github/copilot/generated/SubagentStartedEvent.java | 2 +- .../com/github/copilot/generated/UserMessageEvent.java | 2 +- nodejs/src/generated/session-events.ts | 8 ++++---- rust/src/generated/session_events.rs | 8 ++++---- 9 files changed, 24 insertions(+), 23 deletions(-) diff --git a/docs/features/execution-identity.md b/docs/features/execution-identity.md index 1d1a523063..8f9b55e589 100644 --- a/docs/features/execution-identity.md +++ b/docs/features/execution-identity.md @@ -165,12 +165,13 @@ The generated `ToolExecutionStartData` now exposes optional `traceparent` and `tracestate` strings on `tool.execution_start.data`, including before a later `external_tool.requested` callback. No SDK flag, writer, or new event family is introduced. All six projections come from the canonical schemas in -[runtime commit 7ab2ab8](https://github.com/github/copilot-agent-runtime/commit/7ab2ab8ea278c812664eece9feab83ef376936eb), +[runtime commit c4e6d732](https://github.com/github/copilot-agent-runtime/commit/c4e6d7322f6ccf316e2a452b2a08a3ee956cae1a), delivered in [the runtime companion](https://github.com/github/copilot-agent-runtime/pull/22693). Use its [producer commit 27867ac0](https://github.com/github/copilot-agent-runtime/commit/27867ac02ac1606ed4d112cc613011e0ebc69ba5) or a release containing it: the producer also preserves the published tool-span context when the same canonical start is projected twice, keeping the later -callback parent consistent. The schema checksums are unchanged from `7ab2ab8`. +callback parent consistent. The later `c4e6d732` schema corrects four existing +identity descriptions without changing the early-field shapes from `7ab2ab8`. This is an unreleased producer prerequisite, not a new published SDK/CLI floor. The standalone CLI release pin is unchanged; it does not supply these new fields. Release-generation inputs must advance through the normal release process before @@ -207,7 +208,7 @@ is evidence of permission to execute. The unmodified schema inputs used for generation have SHA-256 checksums: -* `session-events.schema.json`: `0268ec8b872b7272a011f4905cf3506b59426d930fd8d5249f4ff8b80040ffe6` +* `session-events.schema.json`: `4f70dfad0e6e15b44247f1df4f5df29269527c308d2e10ca49f56d3acd8b69ff` * `api.schema.json`: `141d44c48ef5665b97032a8a400271b4140c92b2ed228fc9c84736dbd3cf4fcf` Relative to the standalone pin, these canonical inputs also contain the existing diff --git a/dotnet/src/Generated/SessionEvents.cs b/dotnet/src/Generated/SessionEvents.cs index f7fa132e3b..d84549cc82 100644 --- a/dotnet/src/Generated/SessionEvents.cs +++ b/dotnet/src/Generated/SessionEvents.cs @@ -3601,7 +3601,7 @@ public sealed partial class UserMessageData [JsonPropertyName("nativeDocumentPathFallbackPaths")] public string[]? NativeDocumentPathFallbackPaths { get; set; } - /// Parent agent task ID for background telemetry correlated to this user turn. + /// Task ID minted when the runtime prepares this user-message run. This is not a parent interaction ID or worker instance ID and must not be equated with CAPI's X-Parent-Agent-Id. [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] [JsonPropertyName("parentAgentTaskId")] public string? ParentAgentTaskId { get; set; } @@ -5263,7 +5263,7 @@ public sealed partial class SubagentStartedData [JsonPropertyName("taskModelSource")] public SubagentTaskModelSource? TaskModelSource { get; set; } - /// Tool call ID of the parent tool invocation that spawned this sub-agent. + /// Spawning tool invocation ID, or the canonical sub-agent ID used as a fallback for an API launch without a tool invocation. The fallback is not evidence of a tool call. [JsonPropertyName("toolCallId")] public required string ToolCallId { get; set; } } @@ -5352,7 +5352,7 @@ public sealed partial class SubagentCompletedData [JsonPropertyName("modelSelectionSource")] public SubagentModelSelectionSource? ModelSelectionSource { get; set; } - /// Tool call ID of the parent tool invocation that spawned this sub-agent. + /// Spawning tool invocation ID, or the canonical sub-agent ID used as a fallback for an API launch without a tool invocation. The fallback is not evidence of a tool call. [JsonPropertyName("toolCallId")] public required string ToolCallId { get; set; } @@ -5428,7 +5428,7 @@ public sealed partial class SubagentFailedData [JsonPropertyName("modelSelectionSource")] public SubagentModelSelectionSource? ModelSelectionSource { get; set; } - /// Tool call ID of the parent tool invocation that spawned this sub-agent. + /// Spawning tool invocation ID, or the canonical sub-agent ID used as a fallback for an API launch without a tool invocation. The fallback is not evidence of a tool call. [JsonPropertyName("toolCallId")] public required string ToolCallId { get; set; } diff --git a/go/rpc/zsession_events.go b/go/rpc/zsession_events.go index 8ffa81326e..9898fa4f52 100644 --- a/go/rpc/zsession_events.go +++ b/go/rpc/zsession_events.go @@ -2146,7 +2146,7 @@ type UserMessageData struct { MessageID *string `json:"messageId,omitempty"` // Path-backed native document attachments that stayed on the tagged_files path flow because native upload could not read them or would exceed the request size limit NativeDocumentPathFallbackPaths []string `json:"nativeDocumentPathFallbackPaths,omitzero"` - // Parent agent task ID for background telemetry correlated to this user turn + // Task ID minted when the runtime prepares this user-message run. This is not a parent interaction ID or worker instance ID and must not be equated with CAPI's X-Parent-Agent-Id. ParentAgentTaskID *string `json:"parentAgentTaskId,omitempty"` // Responses reasoning settings anchored before this model-facing message, for cache-stable history replay ResponsesReasoning *ResponsesReasoning `json:"responsesReasoning,omitempty"` @@ -2876,7 +2876,7 @@ type SubagentCompletedData struct { ModelOverrideReason *string `json:"modelOverrideReason,omitempty"` // Authority or runtime mechanism responsible for sub-agent model selection ModelSelectionSource *SubagentModelSelectionSource `json:"modelSelectionSource,omitempty"` - // Tool call ID of the parent tool invocation that spawned this sub-agent + // Spawning tool invocation ID, or the canonical sub-agent ID used as a fallback for an API launch without a tool invocation. The fallback is not evidence of a tool call. ToolCallID string `json:"toolCallId"` // Total tokens (input + output) consumed by the sub-agent TotalTokens *int64 `json:"totalTokens,omitempty"` @@ -2913,7 +2913,7 @@ type SubagentFailedData struct { ModelOverrideReason *string `json:"modelOverrideReason,omitempty"` // Authority or runtime mechanism responsible for sub-agent model selection ModelSelectionSource *SubagentModelSelectionSource `json:"modelSelectionSource,omitempty"` - // Tool call ID of the parent tool invocation that spawned this sub-agent + // Spawning tool invocation ID, or the canonical sub-agent ID used as a fallback for an API launch without a tool invocation. The fallback is not evidence of a tool call. ToolCallID string `json:"toolCallId"` // Total tokens (input + output) consumed before the sub-agent failed TotalTokens *int64 `json:"totalTokens,omitempty"` @@ -2948,7 +2948,7 @@ type SubagentStartedData struct { Resumable *bool `json:"resumable,omitempty"` // Where the model input for this sub-agent came from. Present when the task planner resolved the launch (the task tool and factory agents); absent for sub-agents created through other runtime paths. TaskModelSource *SubagentTaskModelSource `json:"taskModelSource,omitempty"` - // Tool call ID of the parent tool invocation that spawned this sub-agent + // Spawning tool invocation ID, or the canonical sub-agent ID used as a fallback for an API launch without a tool invocation. The fallback is not evidence of a tool call. ToolCallID string `json:"toolCallId"` } diff --git a/java/sdk/src/generated/java/com/github/copilot/generated/SubagentCompletedEvent.java b/java/sdk/src/generated/java/com/github/copilot/generated/SubagentCompletedEvent.java index 064efdd544..294c840139 100644 --- a/java/sdk/src/generated/java/com/github/copilot/generated/SubagentCompletedEvent.java +++ b/java/sdk/src/generated/java/com/github/copilot/generated/SubagentCompletedEvent.java @@ -34,7 +34,7 @@ public final class SubagentCompletedEvent extends SessionEvent { @JsonIgnoreProperties(ignoreUnknown = true) @JsonInclude(JsonInclude.Include.NON_NULL) public record SubagentCompletedEventData( - /** Tool call ID of the parent tool invocation that spawned this sub-agent */ + /** Spawning tool invocation ID, or the canonical sub-agent ID used as a fallback for an API launch without a tool invocation. The fallback is not evidence of a tool call. */ @JsonProperty("toolCallId") String toolCallId, /** Internal name of the sub-agent */ @JsonProperty("agentName") String agentName, diff --git a/java/sdk/src/generated/java/com/github/copilot/generated/SubagentFailedEvent.java b/java/sdk/src/generated/java/com/github/copilot/generated/SubagentFailedEvent.java index 1b0d99cbee..f00467b594 100644 --- a/java/sdk/src/generated/java/com/github/copilot/generated/SubagentFailedEvent.java +++ b/java/sdk/src/generated/java/com/github/copilot/generated/SubagentFailedEvent.java @@ -34,7 +34,7 @@ public final class SubagentFailedEvent extends SessionEvent { @JsonIgnoreProperties(ignoreUnknown = true) @JsonInclude(JsonInclude.Include.NON_NULL) public record SubagentFailedEventData( - /** Tool call ID of the parent tool invocation that spawned this sub-agent */ + /** Spawning tool invocation ID, or the canonical sub-agent ID used as a fallback for an API launch without a tool invocation. The fallback is not evidence of a tool call. */ @JsonProperty("toolCallId") String toolCallId, /** Internal name of the sub-agent */ @JsonProperty("agentName") String agentName, diff --git a/java/sdk/src/generated/java/com/github/copilot/generated/SubagentStartedEvent.java b/java/sdk/src/generated/java/com/github/copilot/generated/SubagentStartedEvent.java index 7d8f5afe9e..7eeb1e08e2 100644 --- a/java/sdk/src/generated/java/com/github/copilot/generated/SubagentStartedEvent.java +++ b/java/sdk/src/generated/java/com/github/copilot/generated/SubagentStartedEvent.java @@ -34,7 +34,7 @@ public final class SubagentStartedEvent extends SessionEvent { @JsonIgnoreProperties(ignoreUnknown = true) @JsonInclude(JsonInclude.Include.NON_NULL) public record SubagentStartedEventData( - /** Tool call ID of the parent tool invocation that spawned this sub-agent */ + /** Spawning tool invocation ID, or the canonical sub-agent ID used as a fallback for an API launch without a tool invocation. The fallback is not evidence of a tool call. */ @JsonProperty("toolCallId") String toolCallId, /** Internal name of the sub-agent */ @JsonProperty("agentName") String agentName, diff --git a/java/sdk/src/generated/java/com/github/copilot/generated/UserMessageEvent.java b/java/sdk/src/generated/java/com/github/copilot/generated/UserMessageEvent.java index aca76d26d8..0b3e0d65fd 100644 --- a/java/sdk/src/generated/java/com/github/copilot/generated/UserMessageEvent.java +++ b/java/sdk/src/generated/java/com/github/copilot/generated/UserMessageEvent.java @@ -61,7 +61,7 @@ public record UserMessageEventData( @JsonProperty("interactionId") String interactionId, /** The agent-loop turn ID that consumed this message; absent when no agent-loop turn consumed it */ @JsonProperty("turnId") String turnId, - /** Parent agent task ID for background telemetry correlated to this user turn */ + /** Task ID minted when the runtime prepares this user-message run. This is not a parent interaction ID or worker instance ID and must not be equated with CAPI's X-Parent-Agent-Id. */ @JsonProperty("parentAgentTaskId") String parentAgentTaskId ) { } diff --git a/nodejs/src/generated/session-events.ts b/nodejs/src/generated/session-events.ts index 8e8ce70b3a..7ba892bea1 100644 --- a/nodejs/src/generated/session-events.ts +++ b/nodejs/src/generated/session-events.ts @@ -4324,7 +4324,7 @@ export interface UserMessageData { */ nativeDocumentPathFallbackPaths?: string[]; /** - * Parent agent task ID for background telemetry correlated to this user turn + * Task ID minted when the runtime prepares this user-message run. This is not a parent interaction ID or worker instance ID and must not be equated with CAPI's X-Parent-Agent-Id. */ parentAgentTaskId?: string; responsesReasoning?: ResponsesReasoning; @@ -7680,7 +7680,7 @@ export interface SubagentStartedData { resumable?: boolean; taskModelSource?: SubagentTaskModelSource; /** - * Tool call ID of the parent tool invocation that spawned this sub-agent + * Spawning tool invocation ID, or the canonical sub-agent ID used as a fallback for an API launch without a tool invocation. The fallback is not evidence of a tool call. */ toolCallId: string; } @@ -7815,7 +7815,7 @@ export interface SubagentCompletedData { modelOverrideReason?: string; modelSelectionSource?: SubagentModelSelectionSource; /** - * Tool call ID of the parent tool invocation that spawned this sub-agent + * Spawning tool invocation ID, or the canonical sub-agent ID used as a fallback for an API launch without a tool invocation. The fallback is not evidence of a tool call. */ toolCallId: string; /** @@ -7907,7 +7907,7 @@ export interface SubagentFailedData { modelOverrideReason?: string; modelSelectionSource?: SubagentModelSelectionSource; /** - * Tool call ID of the parent tool invocation that spawned this sub-agent + * Spawning tool invocation ID, or the canonical sub-agent ID used as a fallback for an API launch without a tool invocation. The fallback is not evidence of a tool call. */ toolCallId: string; /** diff --git a/rust/src/generated/session_events.rs b/rust/src/generated/session_events.rs index 3810d49f7f..8fc2122813 100644 --- a/rust/src/generated/session_events.rs +++ b/rust/src/generated/session_events.rs @@ -2619,7 +2619,7 @@ pub struct UserMessageData { /// Path-backed native document attachments that stayed on the tagged_files path flow because native upload could not read them or would exceed the request size limit #[serde(skip_serializing_if = "Option::is_none")] pub native_document_path_fallback_paths: Option>, - /// Parent agent task ID for background telemetry correlated to this user turn + /// Task ID minted when the runtime prepares this user-message run. This is not a parent interaction ID or worker instance ID and must not be equated with CAPI's X-Parent-Agent-Id. #[serde(skip_serializing_if = "Option::is_none")] pub parent_agent_task_id: Option, /// Responses reasoning settings anchored before this model-facing message, for cache-stable history replay @@ -4922,7 +4922,7 @@ pub struct SubagentStartedData { /// Where the model input for this sub-agent came from. Present when the task planner resolved the launch (the task tool and factory agents); absent for sub-agents created through other runtime paths. #[serde(skip_serializing_if = "Option::is_none")] pub task_model_source: Option, - /// Tool call ID of the parent tool invocation that spawned this sub-agent + /// Spawning tool invocation ID, or the canonical sub-agent ID used as a fallback for an API launch without a tool invocation. The fallback is not evidence of a tool call. pub tool_call_id: String, } @@ -4980,7 +4980,7 @@ pub struct SubagentCompletedData { /// Authority or runtime mechanism responsible for sub-agent model selection #[serde(skip_serializing_if = "Option::is_none")] pub model_selection_source: Option, - /// Tool call ID of the parent tool invocation that spawned this sub-agent + /// Spawning tool invocation ID, or the canonical sub-agent ID used as a fallback for an API launch without a tool invocation. The fallback is not evidence of a tool call. pub tool_call_id: String, /// Total tokens (input + output) consumed by the sub-agent #[serde(skip_serializing_if = "Option::is_none")] @@ -5027,7 +5027,7 @@ pub struct SubagentFailedData { /// Authority or runtime mechanism responsible for sub-agent model selection #[serde(skip_serializing_if = "Option::is_none")] pub model_selection_source: Option, - /// Tool call ID of the parent tool invocation that spawned this sub-agent + /// Spawning tool invocation ID, or the canonical sub-agent ID used as a fallback for an API launch without a tool invocation. The fallback is not evidence of a tool call. pub tool_call_id: String, /// Total tokens (input + output) consumed before the sub-agent failed #[serde(skip_serializing_if = "Option::is_none")] From 1bf63c586a2616048a887b952053c074e316daad Mon Sep 17 00:00:00 2001 From: Michael Dinh Date: Thu, 24 Sep 2026 12:28:50 -0700 Subject: [PATCH 12/25] feat: expose optional RPC admission correlation Generate all six SDK projections from the verified canonical runtime schemas and add thin caller-owned send options. Preserve default wire behavior, source and trace context, and existing .NET/Java call shapes without changing release pins. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Copilot-Session: e845e41f-91a5-4596-b3e7-a1414264fa8b --- docs/features/execution-identity.md | 96 ++++++++- dotnet/src/Generated/Rpc.cs | 38 +++- dotnet/src/Generated/SessionEvents.cs | 5 + dotnet/src/Session.cs | 2 + dotnet/src/Types.cs | 8 + .../test/Unit/ClientSessionLifetimeTests.cs | 101 +++++++++ .../Unit/SessionEventSerializationTests.cs | 55 +++++ go/message_source_test.go | 87 +++++++- go/rpc/message_identity_test.go | 58 +++++ go/rpc/zrpc.go | 19 ++ go/rpc/zrpc_encoding.go | 44 ++-- go/rpc/zsession_encoding.go | 2 + go/rpc/zsession_events.go | 2 + go/session.go | 21 +- go/types.go | 27 ++- java/scripts/codegen/java.test.ts | 38 ++++ java/scripts/codegen/java.ts | 22 ++ .../copilot/generated/UserMessageEvent.java | 6 + .../generated/rpc/QueuePendingItems.java | 6 + .../generated/rpc/SendMessageItem.java | 6 + .../generated/rpc/SessionSendParams.java | 6 + .../com/github/copilot/CopilotSession.java | 1 + .../github/copilot/rpc/MessageOptions.java | 25 +++ .../copilot/rpc/SendMessageRequest.java | 16 ++ .../com/github/copilot/MessageSourceTest.java | 61 +++++- .../MessageIdentitySerializationTest.java | 78 +++++-- nodejs/src/generated/rpc.ts | 12 ++ nodejs/src/generated/session-events.ts | 4 + nodejs/src/session.ts | 3 + nodejs/src/types.ts | 7 + nodejs/test/csharp-codegen.test.ts | 33 +++ nodejs/test/message-identity-types.test.ts | 54 ++++- nodejs/test/message-source.test.ts | 111 ++++++++++ python/copilot/generated/rpc.py | 34 ++- python/copilot/generated/session_events.py | 5 + python/copilot/session.py | 24 +++ python/test_message_identity_generated.py | 50 ++++- python/test_session.py | 82 +++++++ python/test_structured_output.py | 9 +- rust/README.md | 8 + rust/src/generated/api_types.rs | 9 + rust/src/generated/session_events.rs | 3 + rust/src/session.rs | 3 + rust/src/types.rs | 11 + rust/tests/api_types_test.rs | 47 +++- rust/tests/session_test.rs | 204 +++++++++++++++++- scripts/codegen/csharp.ts | 25 +++ 47 files changed, 1487 insertions(+), 81 deletions(-) diff --git a/docs/features/execution-identity.md b/docs/features/execution-identity.md index 8f9b55e589..2b04dac52b 100644 --- a/docs/features/execution-identity.md +++ b/docs/features/execution-identity.md @@ -110,10 +110,11 @@ the oldest pending send, queue position, or display text. its waiting future is cancelled after dispatch. Missing interaction, worker, or recovery keys are not supplied by the event's chronological `parentId`. -The supported send request has no caller-supplied message ID. If the response is -lost, an arriving `user.message` alone cannot identify the application's pending -send. That boundary needs a verified runtime-owned bridge; neither queue order -nor repurposing `source` as a correlation nonce supplies one. +The supported send request has no caller-supplied canonical message ID. Without +the optional admission support [below](#optional-rpc-admission-correlation), a lost +response leaves an arriving `user.message` unable to identify the application's +pending send. Neither queue order nor repurposing `source` as a correlation nonce +supplies that bridge. For an initial tool-spawned worker, match the scoped spawning `toolCallId` to `subagent.started` and retain its envelope `agentId`. An API spawn instead @@ -159,6 +160,90 @@ the missing cause of a worker follow-up or recovery submission. Keep raw observations and explicit provenance; do not add a second telemetry writer or put diagnostic identifiers in metric tags. +## Optional RPC admission correlation + +`clientCorrelationId` is optional, caller-owned diagnostic metadata for a single +RPC input. Any SDK client can use it to reconcile an admission with observed +runtime evidence. The SDK does not generate values, associate them with +application measurement units, or publish telemetry. Application-specific +measurement envelopes stay outside this contract. + +The new convenience options are: + +| SDK | Single-message option | +| --- | --- | +| Node.js | `MessageOptions.clientCorrelationId` | +| Python | `client_correlation_id` keyword on `send`, `send_and_wait`, and `send_and_wait_typed` | +| Go | `MessageOptions.ClientCorrelationID` (`*string`) | +| .NET | `MessageOptions.ClientCorrelationId` | +| Java | `MessageOptions.setClientCorrelationId` | +| Rust | `MessageOptions::with_client_correlation_id` | + +Generated `session.send` requests expose the same optional string. For +`session.sendMessages`, set it on individual `SendMessageItem` values, including +preceding context items. There is no batch-level correlation field. Unset +convenience options are omitted, not sent as JSON null. Cloning an options value +preserves explicitly supplied metadata; independent sends do not inherit it. + +SDKs forward supplied strings unchanged, without UUID parsing, lowercasing, or +semantic validation. A supporting native runtime echoes only valid lowercase, +hyphenated 36-character UUIDs when its independent +`RUNTIME_ADMISSION_TRACE_CONTEXT` flag is enabled. This flag is off by default. +Missing, invalid, disabled, or unsupported metadata is ignored without failing +the product send. Receiving an optional field is not proof that another runtime +or connection supports it. + +The echo appears on the actual admitted `user.message.data.clientCorrelationId` +and its genuinely owned pending message row. Canonical `messageId`, queue `id`, +event `id`, and agent-loop `interactionId` retain their existing meanings. +Snapshot-only or hydrated rows do not acquire correlation metadata. Immediate +steering still exposes text-only steering rows; never reconstruct identity from +that text, row order, or timing. Preserve ordinary request headers and W3C trace +context independently. + +This value is not an idempotency key, authorization, or permission to retry. +Reusing a UUID does not deduplicate submissions; several admissions with the +same value remain ambiguous. Retain observed canonical message IDs and existing +application command/Turn ownership. Missing evidence must remain missing. + +### Release prerequisites and the existing Rust raw API + +These typed additions come from canonical runtime source +`8001dfcc37b6f40bf63b2eef79fdea8ea3a7796f`. That source checkpoint is local and its +publication is blocked; it is not a released runtime or an available dependency +pin. This SDK's bundled CLI floor, dependency versions, and release-schema pin +are unchanged. Normal reviewed runtime publication, SDK generation/release, and +runtime-version plus flag verification are required before consuming the new +typed contract in a deployed fleet. The default CLI is not claimed to emit it. + +The released Rust SDK 1.0.14 calls its convenience type `MessageOptions`, not +`SendOptions`. It has neither this typed field nor a convenience extension map. +It does expose the public +`Session::client().call(method, Option)` API. A caller can use +that existing raw JSON-RPC seam without an unreleased SDK dependency, supplying +the accepted `Session::id()` and exact optional metadata. Raw +`SessionEvent.data` and raw RPC results preserve new and unknown fields; 1.0.14 +generated typed payloads do not expose the new field. + +Raw calls do not apply the convenience helper's wait guard, attachment +preparation, or trace-provider injection. Callers must preserve their existing +command ownership, serialization, cancellation handling, and request/trace +context. Do not issue a second send as a fallback after uncertain dispatch. +A controlled-peer SDK test proves transport forwarding, not deployed runtime +support or an end-to-end telemetry join. + +The complete canonical generation inputs have SHA-256 checksums: + +* `api.schema.json`: `4584674516106f39048a3347d4fb8a222ec9baf00527dbd59b99f006296ad2e8` +* `session-events.schema.json`: `b40db84f3232adbbfd7c6e8469c41d189f0ea6fe2dd4baecef79651182e2fc0c` + +All six projections are generated together. Existing early tool trace context +and source fields are preserved. The .NET generated send method retains +existing positional data arguments and its previous CLR signature, including the +cancellation token. The extended overload adds correlation before its final +cancellation parameter. Java records retain constructors that omit the new +optional component. + ## Live client-tool trace context The generated `ToolExecutionStartData` now exposes optional `traceparent` and @@ -206,7 +291,8 @@ context, but a callback cache cannot retroactively parent earlier execution. Never delay tools waiting for telemetry. Neither a start event nor trace context is evidence of permission to execute. -The unmodified schema inputs used for generation have SHA-256 checksums: +The earlier unmodified schema inputs for the tool-context-only checkpoint had +SHA-256 checksums (the current inputs are listed in the admission section above): * `session-events.schema.json`: `4f70dfad0e6e15b44247f1df4f5df29269527c308d2e10ca49f56d3acd8b69ff` * `api.schema.json`: `141d44c48ef5665b97032a8a400271b4140c92b2ed228fc9c84736dbd3cf4fcf` diff --git a/dotnet/src/Generated/Rpc.cs b/dotnet/src/Generated/Rpc.cs index 8fa4e70ee1..ef61d59465 100644 --- a/dotnet/src/Generated/Rpc.cs +++ b/dotnet/src/Generated/Rpc.cs @@ -6209,6 +6209,10 @@ internal sealed class SendRequest [JsonPropertyName("billable")] public bool? Billable { get; set; } + /// Optional caller-generated diagnostic UUID for this single message. Native sessions with RUNTIME_ADMISSION_TRACE_CONTEXT enabled echo the exact lowercase, hyphenated 36-character UUID on user.message and its existing pending message row. Missing, invalid, disabled, or unsupported metadata is ignored without rejecting the send. Does not change messageId, deduplicate submissions, authorize work, or make an uncertain retry safe. + [JsonPropertyName("clientCorrelationId")] + public string? ClientCorrelationId { get; set; } + /// If provided, this is shown in the timeline instead of `prompt`. [JsonPropertyName("displayPrompt")] public string? DisplayPrompt { get; set; } @@ -6282,6 +6286,10 @@ public sealed class SendMessageItem [JsonPropertyName("billable")] internal bool? Billable { get; set; } + /// Optional caller-generated diagnostic UUID for this item only, with the same validation and opt-in native echo as session.send.clientCorrelationId. The batch has no request-level correlation value; each item retains its own value, including preceding context messages. Reused values do not deduplicate messages and remain ambiguous. + [JsonPropertyName("clientCorrelationId")] + public string? ClientCorrelationId { get; set; } + /// If provided, this is shown in the timeline instead of `prompt`. [JsonPropertyName("displayPrompt")] public string? DisplayPrompt { get; set; } @@ -20769,6 +20777,10 @@ public sealed class QueuePendingItems [JsonPropertyName("agentMode")] public SendAgentMode AgentMode { get; set; } + /// Caller-owned diagnostic UUID from the exact accepted native session.send or sendMessages item, when RUNTIME_ADMISSION_TRACE_CONTEXT is enabled. Omitted for unsupported or identity-less rows, including snapshot-only mirrors. Not an idempotency key, authorization, or permission to retry; repeated values remain ambiguous. + [JsonPropertyName("clientCorrelationId")] + public string? ClientCorrelationId { get; set; } + /// Human-readable text to display for this queue entry in the UI. [JsonPropertyName("displayText")] public string DisplayText { get; set; } = string.Empty; @@ -38186,18 +38198,40 @@ public async Task SuspendAsync(CancellationToken cancellationToken = default) /// W3C Trace Context traceparent header for distributed tracing of this agent turn. /// W3C Trace Context tracestate header for distributed tracing. /// If true, await completion of the agentic loop for this message before returning. Defaults to false (fire-and-forget). When true, the result still contains the same `messageId`; the caller can rely on the agent having processed the message before the call resolves. Transport-dependent tail semantics: on a LOCAL (in-process) session the wait additionally blocks until the completed turn's event tail has been dispatched to this session's in-process subscribers, so a subsequent read of subscriber state already reflects the turn; on a REMOTE session the wait resolves once the loop completes and mirrored delivery follows over the wire. Callers that need the stronger local guarantee on remote sessions should await the event stream explicitly. + /// Optional caller-generated diagnostic UUID for this single message. Native sessions with RUNTIME_ADMISSION_TRACE_CONTEXT enabled echo the exact lowercase, hyphenated 36-character UUID on user.message and its existing pending message row. Missing, invalid, disabled, or unsupported metadata is ignored without rejecting the send. Does not change messageId, deduplicate submissions, authorize work, or make an uncertain retry safe. /// The to monitor for cancellation requests. The default is . /// Result of sending a user message. [Experimental(Diagnostics.Experimental)] - public async Task SendAsync(string prompt, string? displayPrompt = null, IList? attachments = null, SendMode? mode = null, bool? prepend = null, bool? billable = null, string? requiredTool = null, string? source = null, SendAgentMode? agentMode = null, IDictionary? requestHeaders = null, ResponseFormat? responseFormat = null, string? traceparent = null, string? tracestate = null, bool? wait = null, CancellationToken cancellationToken = default) + public async Task SendAsync(string prompt, string? displayPrompt = null, IList? attachments = null, SendMode? mode = null, bool? prepend = null, bool? billable = null, string? requiredTool = null, string? source = null, SendAgentMode? agentMode = null, IDictionary? requestHeaders = null, ResponseFormat? responseFormat = null, string? traceparent = null, string? tracestate = null, bool? wait = null, string? clientCorrelationId = null, CancellationToken cancellationToken = default) { ArgumentNullException.ThrowIfNull(prompt); _session.ThrowIfDisposed(); - var request = new SendRequest { SessionId = _session.SessionId, Prompt = prompt, DisplayPrompt = displayPrompt, Attachments = attachments, Mode = mode, Prepend = prepend, Billable = billable, RequiredTool = requiredTool, Source = source, AgentMode = agentMode, RequestHeaders = requestHeaders, ResponseFormat = responseFormat, Traceparent = traceparent, Tracestate = tracestate, Wait = wait }; + var request = new SendRequest { SessionId = _session.SessionId, Prompt = prompt, ClientCorrelationId = clientCorrelationId, DisplayPrompt = displayPrompt, Attachments = attachments, Mode = mode, Prepend = prepend, Billable = billable, RequiredTool = requiredTool, Source = source, AgentMode = agentMode, RequestHeaders = requestHeaders, ResponseFormat = responseFormat, Traceparent = traceparent, Tracestate = tracestate, Wait = wait }; return await CopilotClient.InvokeRpcAsync(_session.Rpc, "session.send", [request], cancellationToken); } + /// Sends a user message to the session and returns its message ID. + /// The user message text. + /// If provided, this is shown in the timeline instead of `prompt`. + /// Optional attachments (files, directories, selections, blobs, GitHub references) to include with the message. + /// How to deliver the message. `enqueue` (default) appends to the message queue. `immediate` interjects during an in-progress turn. + /// If true, adds the message to the front of the queue instead of the end. + /// If false, this message will not trigger a Premium Request Unit charge. User messages default to billable. + /// If set, the request will fail if the named tool is not available when this message is among the user messages at the start of the current exchange. + /// Optional provenance tag copied to the resulting user.message event. Must be `user`, `system`, `command-<command-id>` for command-originated messages, `schedule-<numeric-id>` for scheduled prompts, or `agent-<agent-id>` for prompts sent by another agent. + /// The UI mode the agent was in when this message was sent. Defaults to the session's current mode. + /// Custom HTTP headers to include in outbound model requests for this turn. Merged with session-level provider headers; per-turn headers augment and overwrite session-level headers with the same key. + /// Provider-native output format for this turn, including all tool-call iterations. Not inherited by later turns or subagents. Ordinary steering inherits the active format; specifying responseFormat with mode: immediate is an error, even while idle. Returned assistant content remains text; the runtime does not parse or validate it. Unsupported models or schemas produce provider errors. + /// W3C Trace Context traceparent header for distributed tracing of this agent turn. + /// W3C Trace Context tracestate header for distributed tracing. + /// If true, await completion of the agentic loop for this message before returning. Defaults to false (fire-and-forget). When true, the result still contains the same `messageId`; the caller can rely on the agent having processed the message before the call resolves. Transport-dependent tail semantics: on a LOCAL (in-process) session the wait additionally blocks until the completed turn's event tail has been dispatched to this session's in-process subscribers, so a subsequent read of subscriber state already reflects the turn; on a REMOTE session the wait resolves once the loop completes and mirrored delivery follows over the wire. Callers that need the stronger local guarantee on remote sessions should await the event stream explicitly. + /// The to monitor for cancellation requests. The default is . + /// Result of sending a user message. + [Experimental(Diagnostics.Experimental)] + public Task SendAsync(string prompt, string? displayPrompt, IList? attachments, SendMode? mode, bool? prepend, bool? billable, string? requiredTool, string? source, SendAgentMode? agentMode, IDictionary? requestHeaders, ResponseFormat? responseFormat, string? traceparent, string? tracestate, bool? wait, CancellationToken cancellationToken) + => SendAsync(prompt, displayPrompt, attachments, mode, prepend, billable, requiredTool, source, agentMode, requestHeaders, responseFormat, traceparent, tracestate, wait, clientCorrelationId: null, cancellationToken: cancellationToken); + /// Sends zero or more user messages to the session in a single turn and returns their message IDs. All provided messages are appended to the conversation in order, then exactly one agent turn runs over the resulting history. When the list is empty, one turn runs over the existing history with no new user message. Remote-backed (Mission Control) sessions do not support this method and will return an error. /// The user messages to append to the conversation, in order, before running one agent loop. When the batch starts a run, its final message is the primary initiating message; earlier messages provide context, not separate runs or replies. May be empty, in which case a single turn runs over the existing history with no new user message or originatingMessageId. /// How to deliver the messages. `enqueue` (default) appends to the message queue. `immediate` interjects during an in-progress turn. diff --git a/dotnet/src/Generated/SessionEvents.cs b/dotnet/src/Generated/SessionEvents.cs index d84549cc82..ab302aca91 100644 --- a/dotnet/src/Generated/SessionEvents.cs +++ b/dotnet/src/Generated/SessionEvents.cs @@ -3572,6 +3572,11 @@ public sealed partial class UserMessageData [JsonPropertyName("attachments")] public Attachment[]? Attachments { get; set; } + /// Exact caller-owned diagnostic UUID carried by this accepted native session.send or sendMessages item when RUNTIME_ADMISSION_TRACE_CONTEXT is enabled. Omitted when input, native ownership, or support is missing. Independent of the canonical messageId; not an idempotency key, authorization, or permission to retry. Multiple messages with the same value remain ambiguous. + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] + [JsonPropertyName("clientCorrelationId")] + public string? ClientCorrelationId { get; set; } + /// The user's message text as displayed in the timeline. [JsonPropertyName("content")] public required string Content { get; set; } diff --git a/dotnet/src/Session.cs b/dotnet/src/Session.cs index 3b2e1c349d..53b9d70417 100644 --- a/dotnet/src/Session.cs +++ b/dotnet/src/Session.cs @@ -324,6 +324,7 @@ public async Task SendAsync(MessageOptions options, CancellationToken ca { SessionId = SessionId, Prompt = options.Prompt, + ClientCorrelationId = options.ClientCorrelationId, DisplayPrompt = options.DisplayPrompt, Attachments = options.Attachments, Mode = options.Mode, @@ -2281,6 +2282,7 @@ internal record SendMessageRequest { public string SessionId { get; init; } = string.Empty; public string Prompt { get; init; } = string.Empty; + public string? ClientCorrelationId { get; init; } public string? DisplayPrompt { get; init; } public IList? Attachments { get; init; } public string? Mode { get; init; } diff --git a/dotnet/src/Types.cs b/dotnet/src/Types.cs index 5e559c4cd0..54c35d8684 100644 --- a/dotnet/src/Types.cs +++ b/dotnet/src/Types.cs @@ -4099,6 +4099,7 @@ private MessageOptions(MessageOptions? other) AgentMode = other.AgentMode; Source = other.Source; Prompt = other.Prompt; + ClientCorrelationId = other.ClientCorrelationId; DisplayPrompt = other.DisplayPrompt; ResponseSchema = other.ResponseSchema; RequestHeaders = other.RequestHeaders is not null @@ -4111,6 +4112,13 @@ private MessageOptions(MessageOptions? other) /// public string Prompt { get; set; } = string.Empty; /// + /// Optional caller-owned diagnostic UUID for this RPC admission, sent unchanged. + /// Supported runtimes echo accepted values only with RUNTIME_ADMISSION_TRACE_CONTEXT + /// enabled. Not an idempotency key; the SDK never generates this value. + /// + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] + public string? ClientCorrelationId { get; set; } + /// /// File or data attachments to include with the message. /// public IList? Attachments { get; set; } diff --git a/dotnet/test/Unit/ClientSessionLifetimeTests.cs b/dotnet/test/Unit/ClientSessionLifetimeTests.cs index 3ae2493a8d..a843c22641 100644 --- a/dotnet/test/Unit/ClientSessionLifetimeTests.cs +++ b/dotnet/test/Unit/ClientSessionLifetimeTests.cs @@ -1499,6 +1499,107 @@ public async Task SendAsync_MessageSource_Is_Omitted_By_Default() Assert.All(requests, request => AssertMessageSource(request.Params, null)); } + [Theory] + [InlineData(null)] + [InlineData("01234567-89ab-4cde-8f01-23456789abcd")] + [InlineData("01234567-89AB-4CDE-8F01-23456789ABCD")] + [InlineData("not-a-uuid")] + [InlineData("")] + public async Task RpcAdmissionCorrelation_Preserves_Send_Options(string? correlation) + { + await using var server = await FakeCopilotServer.StartAsync(); + await using var client = new CopilotClient(new CopilotClientOptions { Connection = RuntimeConnection.ForUri(server.Url) }); + await using var session = await client.CreateSessionAsync(new SessionConfig()); + var options = new MessageOptions + { + Prompt = "hello", + Source = MessageSource.System, + DisplayPrompt = "display", + RequestHeaders = new Dictionary { ["X-Test"] = "preserved" }, + ClientCorrelationId = correlation, + }; + Assert.Equal(correlation, options.Clone().ClientCorrelationId); + + using var activity = new Activity("rpc-admission-test").SetIdFormat(ActivityIdFormat.W3C); + activity.TraceStateString = "vendor=preserved"; + activity.Start(); + Assert.Equal("message-1", await session.SendAsync(options)); + var request = Assert.Single(server.Requests, request => request.Method == "session.send").Params; + Assert.Equal(session.SessionId, request.GetProperty("sessionId").GetString()); + Assert.Equal("system", request.GetProperty("source").GetString()); + Assert.Equal("display", request.GetProperty("displayPrompt").GetString()); + Assert.Equal("preserved", request.GetProperty("requestHeaders").GetProperty("X-Test").GetString()); + Assert.Equal(activity.Id, request.GetProperty("traceparent").GetString()); + Assert.Equal(activity.TraceStateString, request.GetProperty("tracestate").GetString()); + AssertAdmissionCorrelation(request, correlation); + + server.ClearRequests(); + var result = await session.Rpc.SendAsync("hello", clientCorrelationId: correlation); + Assert.Equal("message-1", result.MessageId); + AssertAdmissionCorrelation(Assert.Single(server.Requests, request => request.Method == "session.send").Params, correlation); + } + + [Fact] + public async Task RpcAdmissionCorrelation_Preserves_Positional_Send_Arguments() + { + await using var server = await FakeCopilotServer.StartAsync(); + await using var client = new CopilotClient(new CopilotClientOptions { Connection = RuntimeConnection.ForUri(server.Url) }); + await using var session = await client.CreateSessionAsync(new SessionConfig()); + await session.Rpc.SendAsync("hello", "display", null, null, null, null, null, null, null, null, null, null, null, null, CancellationToken.None); + var request = Assert.Single(server.Requests, request => request.Method == "session.send").Params; + Assert.Equal("display", request.GetProperty("displayPrompt").GetString()); + AssertAdmissionCorrelation(request, null); + + server.ClearRequests(); + await session.Rpc.SendAsync("hello", "display", null, null, null, null, null, null, null, null, null, null, null, null, default); + request = Assert.Single(server.Requests, request => request.Method == "session.send").Params; + Assert.Equal("display", request.GetProperty("displayPrompt").GetString()); + AssertAdmissionCorrelation(request, null); + } + + [Fact] + public async Task RpcAdmissionCorrelation_Is_Per_Input_Not_Per_Batch() + { + const string first = "01234567-89ab-4cde-8f01-23456789abcd"; + const string second = "abcdef01-2345-4678-9abc-def012345678"; + await using var server = await FakeCopilotServer.StartAsync(); + server.UniqueMessageIds = true; + await using var client = new CopilotClient(new CopilotClientOptions { Connection = RuntimeConnection.ForUri(server.Url) }); + await using var session = await client.CreateSessionAsync(new SessionConfig()); + var ids = await Task.WhenAll( + session.SendAsync(new MessageOptions { Prompt = "first", ClientCorrelationId = first }), + session.SendAsync(new MessageOptions { Prompt = "second", ClientCorrelationId = second })); + Assert.Equal(2, ids.Distinct().Count()); + foreach (var request in server.Requests.Where(request => request.Method == "session.send")) + { + AssertAdmissionCorrelation(request.Params, request.Params.GetProperty("prompt").GetString() == "first" ? first : second); + } + await session.Rpc.SendMessagesAsync([ + new SendMessageItem { Prompt = "context", ClientCorrelationId = second }, + new SendMessageItem { Prompt = "plain" }, + new SendMessageItem { Prompt = "reused", ClientCorrelationId = first }, + ]); + var batch = Assert.Single(server.Requests, request => request.Method == "session.sendMessages").Params; + AssertAdmissionCorrelation(batch, null); + var messages = batch.GetProperty("messages").EnumerateArray().ToArray(); + Assert.Equal(3, messages.Length); + AssertAdmissionCorrelation(messages[0], second); + AssertAdmissionCorrelation(messages[1], null); + AssertAdmissionCorrelation(messages[2], first); + } + + private static void AssertAdmissionCorrelation(JsonElement request, string? correlation) + { + if (correlation is null) + { + Assert.False(request.TryGetProperty("clientCorrelationId", out _)); + } + else + { + Assert.Equal(correlation, request.GetProperty("clientCorrelationId").GetString()); + } + } + [Theory] [MemberData(nameof(SerializationTests.MessageSources), MemberType = typeof(SerializationTests))] public async Task SendAsync_MessageSource_Preserves_Other_Options(MessageSource? source, string? wireSource) diff --git a/dotnet/test/Unit/SessionEventSerializationTests.cs b/dotnet/test/Unit/SessionEventSerializationTests.cs index c25c40e3fa..54a4bfae5b 100644 --- a/dotnet/test/Unit/SessionEventSerializationTests.cs +++ b/dotnet/test/Unit/SessionEventSerializationTests.cs @@ -4,12 +4,67 @@ using System.Text.Json; using System.Text.Json.Nodes; +using System.Text.Json.Serialization; +using GitHub.Copilot.Rpc; using Xunit; namespace GitHub.Copilot.Test.Unit; +[JsonSourceGenerationOptions(DefaultIgnoreCondition = JsonIgnoreCondition.WhenWritingNull)] +[JsonSerializable(typeof(QueuePendingItems))] +internal partial class RpcAdmissionJsonContext : JsonSerializerContext; + public class SessionEventSerializationTests { + [Theory] + [InlineData(null)] + [InlineData("01234567-89ab-4cde-8f01-23456789abcd")] + [InlineData("01234567-89AB-4CDE-8F01-23456789ABCD")] + [InlineData("not-a-uuid")] + [InlineData("")] + public void RpcAdmissionCorrelation_RoundTrips_Optional_Projections(string? correlation) + { + var metadata = new JsonObject { ["futureField"] = true }; + if (correlation is not null) + { + metadata["clientCorrelationId"] = correlation; + } + var row = metadata.DeepClone().AsObject(); + row["id"] = "queue-1"; + row["messageId"] = "canonical-1"; + row["kind"] = "message"; + row["displayText"] = "hello"; + row["agentMode"] = "interactive"; + var pending = JsonSerializer.Deserialize(row.ToJsonString(), RpcAdmissionJsonContext.Default.QueuePendingItems)!; + Assert.Equal(correlation, pending.ClientCorrelationId); + Assert.Equal("canonical-1", pending.MessageId); + Assert.Equal("queue-1", pending.Id); + Assert.Equal(correlation is not null, JsonNode.Parse(JsonSerializer.Serialize(pending, RpcAdmissionJsonContext.Default.QueuePendingItems))!.AsObject().ContainsKey("clientCorrelationId")); + + var data = metadata.DeepClone().AsObject(); + data["content"] = "hello"; + data["messageId"] = "canonical-1"; + data["interactionId"] = "agent-loop-1"; + var envelope = new JsonObject + { + ["id"] = "11111111-1111-1111-1111-111111111111", + ["timestamp"] = "2026-09-18T22:00:00+00:00", + ["parentId"] = null, + ["type"] = "user.message", + ["data"] = data, + }; + var decoded = Assert.IsType(SessionEvent.FromJson(envelope.ToJsonString())); + Assert.Equal(correlation, decoded.Data.ClientCorrelationId); + Assert.Equal("canonical-1", decoded.Data.MessageId); + Assert.Equal("agent-loop-1", decoded.Data.InteractionId); + Assert.Equal(correlation is not null, JsonNode.Parse(decoded.ToJson())!["data"]!.AsObject().ContainsKey("clientCorrelationId")); + + row["clientCorrelationId"] = null; + pending = JsonSerializer.Deserialize(row.ToJsonString(), RpcAdmissionJsonContext.Default.QueuePendingItems)!; + Assert.Null(pending.ClientCorrelationId); + Assert.False(JsonNode.Parse(JsonSerializer.Serialize(pending, RpcAdmissionJsonContext.Default.QueuePendingItems))!.AsObject().ContainsKey("clientCorrelationId")); + } + [Theory] [InlineData("session.idle", "{}")] [InlineData("user.message", """{"content":"hello"}""")] diff --git a/go/message_source_test.go b/go/message_source_test.go index e813617b9e..ac1c511d48 100644 --- a/go/message_source_test.go +++ b/go/message_source_test.go @@ -12,10 +12,85 @@ import ( "time" "github.com/github/copilot-sdk/go/internal/jsonrpc2" + "github.com/github/copilot-sdk/go/rpc" "go.opentelemetry.io/otel" "go.opentelemetry.io/otel/propagation" ) +func TestSession_SendAdmissionCorrelation(t *testing.T) { + ctx, cancel := context.WithTimeout(t.Context(), 2*time.Second) + defer cancel() + for _, value := range []any{nil, "01234567-89ab-4cde-8f01-23456789abcd", + "01234567-89AB-4CDE-8F01-23456789ABCD", "not-a-uuid", ""} { + var correlation *string + if value != nil { + id := value.(string) + correlation = &id + } + for _, generated := range []bool{false, true} { + want := map[string]any{"sessionId": "session-1", "prompt": "hello"} + if correlation != nil { + want["clientCorrelationId"] = *correlation + } + params := captureMessageSourceRequest(t, nil, nil, func(session *Session) { + var messageID string + var err error + if generated { + result, rpcErr := session.RPC.Send(ctx, &rpc.SendRequest{ + Prompt: "hello", ClientCorrelationID: correlation, + }) + err = rpcErr + if result != nil { + messageID = result.MessageID + } + } else { + messageID, err = session.Send(ctx, MessageOptions{ + Prompt: "hello", ClientCorrelationID: correlation, + }) + } + if err != nil || messageID != "message-1" { + t.Fatalf("send result = %q, %v", messageID, err) + } + }) + if !reflect.DeepEqual(params, want) { + t.Fatalf("admission metadata changed: got %#v, want %#v", params, want) + } + } + } +} + +func TestSession_SendMessagesAdmissionCorrelationStaysPerItem(t *testing.T) { + ctx, cancel := context.WithTimeout(t.Context(), 2*time.Second) + defer cancel() + id := "01234567-89ab-4cde-8f01-23456789abcd" + ids := []string{"context-id", "plain-id", "reused-id"} + params := captureSessionRPCRequest(t, "session.sendMessages", map[string]any{"messageIds": ids}, + nil, nil, false, func(session *Session) { + result, err := session.RPC.SendMessages(ctx, &rpc.SendMessagesRequest{Messages: []rpc.SendMessageItem{ + {Prompt: "context", ClientCorrelationID: &id}, + {Prompt: "plain"}, + {Prompt: "reused", ClientCorrelationID: &id}, + }}) + if err != nil { + t.Fatal(err) + } + if !reflect.DeepEqual(result.MessageIDs, ids) { + t.Fatalf("canonical message IDs changed: %#v", result.MessageIDs) + } + }) + want := map[string]any{ + "sessionId": "session-1", + "messages": []any{ + map[string]any{"prompt": "context", "clientCorrelationId": id}, + map[string]any{"prompt": "plain"}, + map[string]any{"prompt": "reused", "clientCorrelationId": id}, + }, + } + if !reflect.DeepEqual(params, want) { + t.Fatalf("unexpected batch wire request: %#v", params) + } +} + func messageSourceTestCases() []struct { name string value MessageSource @@ -203,6 +278,12 @@ func captureMessageSourceRequest(t *testing.T, rpcError *jsonrpc2.Error, events func captureSessionSendRequest(t *testing.T, rpcError *jsonrpc2.Error, events []SessionEvent, beforeResponse bool, invoke func(*Session)) map[string]any { t.Helper() + return captureSessionRPCRequest(t, "session.send", map[string]any{"messageId": "message-1"}, + rpcError, events, beforeResponse, invoke) +} + +func captureSessionRPCRequest(t *testing.T, method string, result map[string]any, rpcError *jsonrpc2.Error, events []SessionEvent, beforeResponse bool, invoke func(*Session)) map[string]any { + t.Helper() stdinR, stdinW := io.Pipe() stdoutR, stdoutW := io.Pipe() @@ -235,15 +316,15 @@ func captureSessionSendRequest(t *testing.T, rpcError *jsonrpc2.Error, events [] errCh <- err return } - if request.Method != "session.send" { - errCh <- fmt.Errorf("expected session.send, got %s", request.Method) + if request.Method != method { + errCh <- fmt.Errorf("expected %s, got %s", method, request.Method) return } response := map[string]any{"jsonrpc": "2.0", "id": request.ID} if rpcError != nil { response["error"] = rpcError } else { - response["result"] = map[string]any{"messageId": "message-1"} + response["result"] = result } data, err := json.Marshal(response) if err != nil { diff --git a/go/rpc/message_identity_test.go b/go/rpc/message_identity_test.go index 4cca23e3ba..616cf76de0 100644 --- a/go/rpc/message_identity_test.go +++ b/go/rpc/message_identity_test.go @@ -6,6 +6,64 @@ import ( "testing" ) +func checkAdmissionCorrelationJSON[T any](t *testing.T, base string) { + t.Helper() + for _, value := range []any{nil, "01234567-89ab-4cde-8f01-23456789abcd", + "01234567-89AB-4CDE-8F01-23456789ABCD", "not-a-uuid", ""} { + var expected map[string]any + if err := json.Unmarshal([]byte(base), &expected); err != nil { + t.Fatal(err) + } + if value != nil { + expected["clientCorrelationId"] = value + } + encoded, err := json.Marshal(expected) + if err != nil { + t.Fatal(err) + } + for _, input := range [][]byte{encoded, append(encoded[:len(encoded)-1:len(encoded)-1], []byte(`,"futureField":true}`)...)} { + var decoded T + if err := json.Unmarshal(input, &decoded); err != nil { + t.Fatal(err) + } + roundTrip, err := json.Marshal(decoded) + if err != nil { + t.Fatal(err) + } + var actual map[string]any + if err := json.Unmarshal(roundTrip, &actual); err != nil { + t.Fatal(err) + } + if !reflect.DeepEqual(actual, expected) { + t.Fatalf("round trip changed admission metadata: got %#v, want %#v", actual, expected) + } + } + } + var decoded T + input := base[:len(base)-1] + `,"clientCorrelationId":null}` + if err := json.Unmarshal([]byte(input), &decoded); err != nil { + t.Fatal(err) + } + encoded, err := json.Marshal(decoded) + if err != nil { + t.Fatal(err) + } + var actual map[string]any + if err := json.Unmarshal(encoded, &actual); err != nil { + t.Fatal(err) + } + if _, exists := actual["clientCorrelationId"]; exists { + t.Fatal("nil admission metadata must be omitted") + } +} + +func TestAdmissionCorrelationJSONCompatibility(t *testing.T) { + checkAdmissionCorrelationJSON[SendRequest](t, `{"prompt":"hello"}`) + checkAdmissionCorrelationJSON[SendMessageItem](t, `{"prompt":"hello"}`) + checkAdmissionCorrelationJSON[QueuePendingItems](t, `{"id":"queue-1","messageId":"canonical-1","kind":"message","displayText":"hello","agentMode":"interactive"}`) + checkAdmissionCorrelationJSON[UserMessageData](t, `{"content":"hello","messageId":"canonical-1","interactionId":"agent-loop-1"}`) +} + func TestQueuePendingItemsMessageIDJSONCompatibility(t *testing.T) { var item QueuePendingItems if err := json.Unmarshal([]byte(`{ diff --git a/go/rpc/zrpc.go b/go/rpc/zrpc.go index 86185407f4..4491dca05d 100644 --- a/go/rpc/zrpc.go +++ b/go/rpc/zrpc.go @@ -11369,6 +11369,11 @@ type QueuePendingItems struct { // the turn: a plan or autopilot session applies its own write gate, continuation loop and // permission posture to every drained item regardless of the mode stored here. AgentMode SendAgentMode `json:"agentMode"` + // Caller-owned diagnostic UUID from the exact accepted native session.send or sendMessages + // item, when RUNTIME_ADMISSION_TRACE_CONTEXT is enabled. Omitted for unsupported or + // identity-less rows, including snapshot-only mirrors. Not an idempotency key, + // authorization, or permission to retry; repeated values remain ambiguous. + ClientCorrelationID *string `json:"clientCorrelationId,omitempty"` // Human-readable text to display for this queue entry in the UI DisplayText string `json:"displayText"` // Stable opaque id for the canonical queued item. Batch rows share one id. @@ -12167,6 +12172,11 @@ type SendMessageItem struct { // Internal: Billable is part of the SDK's internal API surface and is not intended for // external use. Billable *bool `json:"billable,omitempty"` + // Optional caller-generated diagnostic UUID for this item only, with the same validation + // and opt-in native echo as session.send.clientCorrelationId. The batch has no + // request-level correlation value; each item retains its own value, including preceding + // context messages. Reused values do not deduplicate messages and remain ambiguous. + ClientCorrelationID *string `json:"clientCorrelationId,omitempty"` // If provided, this is shown in the timeline instead of `prompt` DisplayPrompt *string `json:"displayPrompt,omitempty"` // The user message text @@ -12252,6 +12262,12 @@ type SendRequest struct { // If false, this message will not trigger a Premium Request Unit charge. User messages // default to billable. Billable *bool `json:"billable,omitempty"` + // Optional caller-generated diagnostic UUID for this single message. Native sessions with + // RUNTIME_ADMISSION_TRACE_CONTEXT enabled echo the exact lowercase, hyphenated 36-character + // UUID on user.message and its existing pending message row. Missing, invalid, disabled, or + // unsupported metadata is ignored without rejecting the send. Does not change messageId, + // deduplicate submissions, authorize work, or make an uncertain retry safe. + ClientCorrelationID *string `json:"clientCorrelationId,omitempty"` // If provided, this is shown in the timeline instead of `prompt` DisplayPrompt *string `json:"displayPrompt,omitempty"` // How to deliver the message. `enqueue` (default) appends to the message queue. `immediate` @@ -32219,6 +32235,9 @@ func (a *SessionRPC) Send(ctx context.Context, params *SendRequest) (*SendResult if params.Billable != nil { req["billable"] = *params.Billable } + if params.ClientCorrelationID != nil { + req["clientCorrelationId"] = *params.ClientCorrelationID + } if params.DisplayPrompt != nil { req["displayPrompt"] = *params.DisplayPrompt } diff --git a/go/rpc/zrpc_encoding.go b/go/rpc/zrpc_encoding.go index 97d51702da..0fe065835d 100644 --- a/go/rpc/zrpc_encoding.go +++ b/go/rpc/zrpc_encoding.go @@ -6064,12 +6064,13 @@ func (r *SendAttachmentsToMessageParams) UnmarshalJSON(data []byte) error { func (r *SendMessageItem) UnmarshalJSON(data []byte) error { type rawSendMessageItem struct { - Attachments []json.RawMessage `json:"attachments,omitzero"` - Billable *bool `json:"billable,omitempty"` - DisplayPrompt *string `json:"displayPrompt,omitempty"` - Prompt string `json:"prompt"` - RequiredTool *string `json:"requiredTool,omitempty"` - Source *string `json:"source,omitempty"` + Attachments []json.RawMessage `json:"attachments,omitzero"` + Billable *bool `json:"billable,omitempty"` + ClientCorrelationID *string `json:"clientCorrelationId,omitempty"` + DisplayPrompt *string `json:"displayPrompt,omitempty"` + Prompt string `json:"prompt"` + RequiredTool *string `json:"requiredTool,omitempty"` + Source *string `json:"source,omitempty"` } var raw rawSendMessageItem if err := json.Unmarshal(data, &raw); err != nil { @@ -6086,6 +6087,7 @@ func (r *SendMessageItem) UnmarshalJSON(data []byte) error { } } r.Billable = raw.Billable + r.ClientCorrelationID = raw.ClientCorrelationID r.DisplayPrompt = raw.DisplayPrompt r.Prompt = raw.Prompt r.RequiredTool = raw.RequiredTool @@ -6095,20 +6097,21 @@ func (r *SendMessageItem) UnmarshalJSON(data []byte) error { func (r *SendRequest) UnmarshalJSON(data []byte) error { type rawSendRequest struct { - AgentMode *SendAgentMode `json:"agentMode,omitempty"` - Attachments []json.RawMessage `json:"attachments,omitzero"` - Billable *bool `json:"billable,omitempty"` - DisplayPrompt *string `json:"displayPrompt,omitempty"` - Mode *SendMode `json:"mode,omitempty"` - Prepend *bool `json:"prepend,omitempty"` - Prompt string `json:"prompt"` - RequestHeaders map[string]string `json:"requestHeaders,omitzero"` - RequiredTool *string `json:"requiredTool,omitempty"` - ResponseFormat *ResponseFormat `json:"responseFormat,omitempty"` - Source *string `json:"source,omitempty"` - Traceparent *string `json:"traceparent,omitempty"` - Tracestate *string `json:"tracestate,omitempty"` - Wait *bool `json:"wait,omitempty"` + AgentMode *SendAgentMode `json:"agentMode,omitempty"` + Attachments []json.RawMessage `json:"attachments,omitzero"` + Billable *bool `json:"billable,omitempty"` + ClientCorrelationID *string `json:"clientCorrelationId,omitempty"` + DisplayPrompt *string `json:"displayPrompt,omitempty"` + Mode *SendMode `json:"mode,omitempty"` + Prepend *bool `json:"prepend,omitempty"` + Prompt string `json:"prompt"` + RequestHeaders map[string]string `json:"requestHeaders,omitzero"` + RequiredTool *string `json:"requiredTool,omitempty"` + ResponseFormat *ResponseFormat `json:"responseFormat,omitempty"` + Source *string `json:"source,omitempty"` + Traceparent *string `json:"traceparent,omitempty"` + Tracestate *string `json:"tracestate,omitempty"` + Wait *bool `json:"wait,omitempty"` } var raw rawSendRequest if err := json.Unmarshal(data, &raw); err != nil { @@ -6126,6 +6129,7 @@ func (r *SendRequest) UnmarshalJSON(data []byte) error { } } r.Billable = raw.Billable + r.ClientCorrelationID = raw.ClientCorrelationID r.DisplayPrompt = raw.DisplayPrompt r.Mode = raw.Mode r.Prepend = raw.Prepend diff --git a/go/rpc/zsession_encoding.go b/go/rpc/zsession_encoding.go index a840934e39..ecd93f1dfc 100644 --- a/go/rpc/zsession_encoding.go +++ b/go/rpc/zsession_encoding.go @@ -976,6 +976,7 @@ func (r *UserMessageData) UnmarshalJSON(data []byte) error { type rawUserMessageData struct { AgentMode *UserMessageAgentMode `json:"agentMode,omitempty"` Attachments []json.RawMessage `json:"attachments,omitzero"` + ClientCorrelationID *string `json:"clientCorrelationId,omitempty"` Content string `json:"content"` Delivery *UserMessageDelivery `json:"delivery,omitempty"` InteractionID *string `json:"interactionId,omitempty"` @@ -1004,6 +1005,7 @@ func (r *UserMessageData) UnmarshalJSON(data []byte) error { r.Attachments = append(r.Attachments, value) } } + r.ClientCorrelationID = raw.ClientCorrelationID r.Content = raw.Content r.Delivery = raw.Delivery r.InteractionID = raw.InteractionID diff --git a/go/rpc/zsession_events.go b/go/rpc/zsession_events.go index 9898fa4f52..a9343a3157 100644 --- a/go/rpc/zsession_events.go +++ b/go/rpc/zsession_events.go @@ -2134,6 +2134,8 @@ type UserMessageData struct { AgentMode *UserMessageAgentMode `json:"agentMode,omitempty"` // Files, selections, or GitHub references attached to the message Attachments []Attachment `json:"attachments,omitzero"` + // Exact caller-owned diagnostic UUID carried by this accepted native session.send or sendMessages item when RUNTIME_ADMISSION_TRACE_CONTEXT is enabled. Omitted when input, native ownership, or support is missing. Independent of the canonical messageId; not an idempotency key, authorization, or permission to retry. Multiple messages with the same value remain ambiguous. + ClientCorrelationID *string `json:"clientCorrelationId,omitempty"` // The user's message text as displayed in the timeline Content string `json:"content"` // How this message was delivered to the agentic loop relative to loop state (idle-start vs. steering/queued while busy). The timing axis; combine with `source` (origin) for the full picture. Used for telemetry attribution. diff --git a/go/session.go b/go/session.go index 4b279f3f98..320a37819d 100644 --- a/go/session.go +++ b/go/session.go @@ -430,16 +430,17 @@ func newSession( func (s *Session) Send(ctx context.Context, options MessageOptions) (string, error) { traceparent, tracestate := getTraceContext(ctx) req := sessionSendRequest{ - SessionID: s.SessionID, - Prompt: options.Prompt, - Source: options.Source, - DisplayPrompt: options.DisplayPrompt, - Attachments: options.Attachments, - Mode: options.Mode, - AgentMode: options.AgentMode, - Traceparent: traceparent, - Tracestate: tracestate, - RequestHeaders: options.RequestHeaders, + SessionID: s.SessionID, + Prompt: options.Prompt, + ClientCorrelationID: options.ClientCorrelationID, + Source: options.Source, + DisplayPrompt: options.DisplayPrompt, + Attachments: options.Attachments, + Mode: options.Mode, + AgentMode: options.AgentMode, + Traceparent: traceparent, + Tracestate: tracestate, + RequestHeaders: options.RequestHeaders, } if options.ResponseSchema != nil { strict := true diff --git a/go/types.go b/go/types.go index e9d9c9a684..e29d32ae1e 100644 --- a/go/types.go +++ b/go/types.go @@ -2482,6 +2482,10 @@ type MessageOptions struct { ResponseSchema map[string]any // Prompt is the message to send Prompt string + // ClientCorrelationID is an optional caller-owned diagnostic UUID for this RPC + // admission, sent unchanged. Supported runtimes echo accepted values only with + // RUNTIME_ADMISSION_TRACE_CONTEXT enabled. It is not an idempotency key. + ClientCorrelationID *string // Source identifies the message origin independently of Mode and AgentMode. // The empty value omits source from the request, preserving runtime defaults. Source MessageSource @@ -2963,17 +2967,18 @@ type sessionAbortRequest struct { } type sessionSendRequest struct { - ResponseFormat *rpc.ResponseFormat `json:"responseFormat,omitempty"` - SessionID string `json:"sessionId"` - Prompt string `json:"prompt"` - Source MessageSource `json:"source,omitempty"` - DisplayPrompt string `json:"displayPrompt,omitempty"` - Attachments []Attachment `json:"attachments,omitempty"` - Mode string `json:"mode,omitempty"` - AgentMode AgentMode `json:"agentMode,omitempty"` - Traceparent string `json:"traceparent,omitempty"` - Tracestate string `json:"tracestate,omitempty"` - RequestHeaders map[string]string `json:"requestHeaders,omitempty"` + ResponseFormat *rpc.ResponseFormat `json:"responseFormat,omitempty"` + SessionID string `json:"sessionId"` + Prompt string `json:"prompt"` + ClientCorrelationID *string `json:"clientCorrelationId,omitempty"` + Source MessageSource `json:"source,omitempty"` + DisplayPrompt string `json:"displayPrompt,omitempty"` + Attachments []Attachment `json:"attachments,omitempty"` + Mode string `json:"mode,omitempty"` + AgentMode AgentMode `json:"agentMode,omitempty"` + Traceparent string `json:"traceparent,omitempty"` + Tracestate string `json:"tracestate,omitempty"` + RequestHeaders map[string]string `json:"requestHeaders,omitempty"` } // sessionSendResponse is the response from session.send diff --git a/java/scripts/codegen/java.test.ts b/java/scripts/codegen/java.test.ts index 7ab5bb0222..3f36cdc43d 100644 --- a/java/scripts/codegen/java.test.ts +++ b/java/scripts/codegen/java.test.ts @@ -40,6 +40,44 @@ function renderPayload(dataSchema: JSONSchema7): string { }, "com.github.copilot.generated"); } +test("admission correlation preserves existing Java record constructors", async () => { + const schema: JSONSchema7 = { + type: "object", + properties: { + prompt: { type: "string" }, + clientCorrelationId: { type: "string" }, + displayPrompt: { type: "string" }, + }, + required: ["prompt"], + }; + const files = await renderRpcTypes({ + session: { + send: { + rpcMethod: "session.send", + params: { ...schema, title: "SessionSendParams" }, + }, + }, + }, {}); + const code = [...files.values()].join("\n"); + assert.match(code, /public SessionSendParams\(String prompt, String displayPrompt\)/); + assert.match(code, /this\(prompt, null, displayPrompt\);/); + + const event = renderEventVariantClass({ + typeName: "user.message", + className: "UserMessageEvent", + dataSchema: { + type: "object", + properties: { + content: { type: "string" }, + clientCorrelationId: { type: "string" }, + messageId: { type: "string" }, + }, + }, + }, "com.github.copilot.generated"); + assert.match(event, /public UserMessageEventData\(String content, String messageId\)/); + assert.match(event, /this\(content, null, messageId\);/); +}); + for (const keyword of ["anyOf", "oneOf"] as const) { test(`root ${keyword} payload preserves raw JSON and existing data descriptors`, () => { const source = renderPayload({ diff --git a/java/scripts/codegen/java.ts b/java/scripts/codegen/java.ts index 472eedfd61..b4ee2e5193 100644 --- a/java/scripts/codegen/java.ts +++ b/java/scripts/codegen/java.ts @@ -1360,6 +1360,7 @@ export function renderEventVariantClass(variant: EventVariant, packageName: stri } lines.push(` ) {`); } + lines.push(...renderAdmissionCompatibilityConstructor(`${variant.className}Data`, dataFields, " ")); // Render nested types inside Data record for (const [, nested] of nestedTypes) { lines.push(...renderNestedType(nested, 2, nestedTypes, allImports)); @@ -1564,6 +1565,25 @@ function schemaAllowsNull(schema: JSONSchema7): boolean { ); } +/** Preserve pre-admission constructor descriptors while adding optional record components. */ +function renderAdmissionCompatibilityConstructor( + className: string, + fields: { javaName: string; javaType: string }[], + indent: string, +): string[] { + const types = new Set(["SessionSendParams", "SendMessageItem", "QueuePendingItems", "UserMessageEventData"]); + if (!types.has(className) || !fields.some((field) => field.javaName === "clientCorrelationId")) return []; + const previous = fields.filter((field) => field.javaName !== "clientCorrelationId"); + const parameters = previous.map((field) => `${field.javaType} ${field.javaName}`).join(", "); + const arguments_ = fields.map((field) => field.javaName === "clientCorrelationId" ? "null" : field.javaName).join(", "); + return [ + `${indent}/** Creates a value without optional admission correlation metadata. */`, + `${indent}public ${className}(${parameters}) {`, + `${indent} this(${arguments_});`, + `${indent}}`, + ]; +} + /** Generate a Java record for a JSON Schema object type. Returns the class content. */ function generateRpcClass( className: string, @@ -1615,6 +1635,8 @@ function generateRpcClass( lines.push(`) {`); } + lines.push(...renderAdmissionCompatibilityConstructor(className, fields, " ")); + // Add nested types as nested records/enums inside this record for (const [, nested] of localNestedTypes) { lines.push(...renderNestedType(nested, 1, new Map(), imports)); diff --git a/java/sdk/src/generated/java/com/github/copilot/generated/UserMessageEvent.java b/java/sdk/src/generated/java/com/github/copilot/generated/UserMessageEvent.java index 0b3e0d65fd..bd9abb324c 100644 --- a/java/sdk/src/generated/java/com/github/copilot/generated/UserMessageEvent.java +++ b/java/sdk/src/generated/java/com/github/copilot/generated/UserMessageEvent.java @@ -41,6 +41,8 @@ public record UserMessageEventData( @JsonProperty("responsesReasoning") ResponsesReasoning responsesReasoning, /** Stable identity of the logical user message, matching the ID returned by send and retained by pending queue snapshots */ @JsonProperty("messageId") String messageId, + /** Exact caller-owned diagnostic UUID carried by this accepted native session.send or sendMessages item when RUNTIME_ADMISSION_TRACE_CONTEXT is enabled. Omitted when input, native ownership, or support is missing. Independent of the canonical messageId; not an idempotency key, authorization, or permission to retry. Multiple messages with the same value remain ambiguous. */ + @JsonProperty("clientCorrelationId") String clientCorrelationId, /** Transformed version of the message sent to the model, with XML wrapping, timestamps, and other augmentations for prompt caching */ @JsonProperty("transformedContent") String transformedContent, /** Files, selections, or GitHub references attached to the message */ @@ -64,5 +66,9 @@ public record UserMessageEventData( /** Task ID minted when the runtime prepares this user-message run. This is not a parent interaction ID or worker instance ID and must not be equated with CAPI's X-Parent-Agent-Id. */ @JsonProperty("parentAgentTaskId") String parentAgentTaskId ) { + /** Creates a value without optional admission correlation metadata. */ + public UserMessageEventData(String content, ResponsesReasoning responsesReasoning, String messageId, String transformedContent, List attachments, List supportedNativeDocumentMimeTypes, List nativeDocumentPathFallbackPaths, String source, UserMessageDelivery delivery, UserMessageAgentMode agentMode, Boolean isAutopilotContinuation, String interactionId, String turnId, String parentAgentTaskId) { + this(content, responsesReasoning, messageId, null, transformedContent, attachments, supportedNativeDocumentMimeTypes, nativeDocumentPathFallbackPaths, source, delivery, agentMode, isAutopilotContinuation, interactionId, turnId, parentAgentTaskId); + } } } diff --git a/java/sdk/src/generated/java/com/github/copilot/generated/rpc/QueuePendingItems.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/QueuePendingItems.java index f815f565f5..80b6b6bd07 100644 --- a/java/sdk/src/generated/java/com/github/copilot/generated/rpc/QueuePendingItems.java +++ b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/QueuePendingItems.java @@ -25,6 +25,8 @@ public record QueuePendingItems( @JsonProperty("id") String id, /** Stable identity of the queued user message. Present for message rows and absent for slash commands and model changes. */ @JsonProperty("messageId") String messageId, + /** Caller-owned diagnostic UUID from the exact accepted native session.send or sendMessages item, when RUNTIME_ADMISSION_TRACE_CONTEXT is enabled. Omitted for unsupported or identity-less rows, including snapshot-only mirrors. Not an idempotency key, authorization, or permission to retry; repeated values remain ambiguous. */ + @JsonProperty("clientCorrelationId") String clientCorrelationId, /** Whether this item is a queued user message or a queued slash command / model change */ @JsonProperty("kind") QueuePendingItemsKind kind, /** Human-readable text to display for this queue entry in the UI */ @@ -32,4 +34,8 @@ public record QueuePendingItems( /** Agent mode stored on this queued entry, as stamped when it was enqueued. Items without an explicit mode report interactive. This is not necessarily the mode that will constrain the turn: a plan or autopilot session applies its own write gate, continuation loop and permission posture to every drained item regardless of the mode stored here. */ @JsonProperty("agentMode") SendAgentMode agentMode ) { + /** Creates a value without optional admission correlation metadata. */ + public QueuePendingItems(String id, String messageId, QueuePendingItemsKind kind, String displayText, SendAgentMode agentMode) { + this(id, messageId, null, kind, displayText, agentMode); + } } diff --git a/java/sdk/src/generated/java/com/github/copilot/generated/rpc/SendMessageItem.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/SendMessageItem.java index 4a6696c01d..a1a0e4b3ae 100644 --- a/java/sdk/src/generated/java/com/github/copilot/generated/rpc/SendMessageItem.java +++ b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/SendMessageItem.java @@ -24,6 +24,8 @@ public record SendMessageItem( /** The user message text */ @JsonProperty("prompt") String prompt, + /** Optional caller-generated diagnostic UUID for this item only, with the same validation and opt-in native echo as session.send.clientCorrelationId. The batch has no request-level correlation value; each item retains its own value, including preceding context messages. Reused values do not deduplicate messages and remain ambiguous. */ + @JsonProperty("clientCorrelationId") String clientCorrelationId, /** If provided, this is shown in the timeline instead of `prompt` */ @JsonProperty("displayPrompt") String displayPrompt, /** Optional attachments (files, directories, selections, blobs, GitHub references) to include with this message */ @@ -35,4 +37,8 @@ public record SendMessageItem( /** Optional provenance tag copied to the resulting user.message event. Must be `user`, `system`, `command-` for command-originated messages, `schedule-` for scheduled prompts, or `agent-` for prompts sent by another agent. */ @JsonProperty("source") String source ) { + /** Creates a value without optional admission correlation metadata. */ + public SendMessageItem(String prompt, String displayPrompt, List attachments, Boolean billable, String requiredTool, String source) { + this(prompt, null, displayPrompt, attachments, billable, requiredTool, source); + } } diff --git a/java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionSendParams.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionSendParams.java index 964c48b438..7ee000a6be 100644 --- a/java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionSendParams.java +++ b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionSendParams.java @@ -30,6 +30,8 @@ public record SessionSendParams( @JsonProperty("sessionId") String sessionId, /** The user message text */ @JsonProperty("prompt") String prompt, + /** Optional caller-generated diagnostic UUID for this single message. Native sessions with RUNTIME_ADMISSION_TRACE_CONTEXT enabled echo the exact lowercase, hyphenated 36-character UUID on user.message and its existing pending message row. Missing, invalid, disabled, or unsupported metadata is ignored without rejecting the send. Does not change messageId, deduplicate submissions, authorize work, or make an uncertain retry safe. */ + @JsonProperty("clientCorrelationId") String clientCorrelationId, /** If provided, this is shown in the timeline instead of `prompt` */ @JsonProperty("displayPrompt") String displayPrompt, /** Optional attachments (files, directories, selections, blobs, GitHub references) to include with the message */ @@ -57,6 +59,10 @@ public record SessionSendParams( /** If true, await completion of the agentic loop for this message before returning. Defaults to false (fire-and-forget). When true, the result still contains the same `messageId`; the caller can rely on the agent having processed the message before the call resolves. Transport-dependent tail semantics: on a LOCAL (in-process) session the wait additionally blocks until the completed turn's event tail has been dispatched to this session's in-process subscribers, so a subsequent read of subscriber state already reflects the turn; on a REMOTE session the wait resolves once the loop completes and mirrored delivery follows over the wire. Callers that need the stronger local guarantee on remote sessions should await the event stream explicitly. */ @JsonProperty("wait") Boolean wait_ ) { + /** Creates a value without optional admission correlation metadata. */ + public SessionSendParams(String sessionId, String prompt, String displayPrompt, List attachments, SendMode mode, Boolean prepend, Boolean billable, String requiredTool, String source, SendAgentMode agentMode, Map requestHeaders, SessionSendParamsResponseFormat responseFormat, String traceparent, String tracestate, Boolean wait_) { + this(sessionId, prompt, null, displayPrompt, attachments, mode, prepend, billable, requiredTool, source, agentMode, requestHeaders, responseFormat, traceparent, tracestate, wait_); + } @JsonIgnoreProperties(ignoreUnknown = true) @JsonInclude(JsonInclude.Include.NON_NULL) diff --git a/java/sdk/src/main/java/com/github/copilot/CopilotSession.java b/java/sdk/src/main/java/com/github/copilot/CopilotSession.java index 12662bfa95..8b94799d65 100644 --- a/java/sdk/src/main/java/com/github/copilot/CopilotSession.java +++ b/java/sdk/src/main/java/com/github/copilot/CopilotSession.java @@ -560,6 +560,7 @@ public CompletableFuture send(MessageOptions options) { var request = new SendMessageRequest(); request.setSessionId(sessionId); request.setPrompt(options.getPrompt()); + request.setClientCorrelationId(options.getClientCorrelationId()); request.setAttachments(options.getAttachments()); request.setMode(options.getMode()); request.setSource(options.getSource()); diff --git a/java/sdk/src/main/java/com/github/copilot/rpc/MessageOptions.java b/java/sdk/src/main/java/com/github/copilot/rpc/MessageOptions.java index b06264a0c4..b4cd1328eb 100644 --- a/java/sdk/src/main/java/com/github/copilot/rpc/MessageOptions.java +++ b/java/sdk/src/main/java/com/github/copilot/rpc/MessageOptions.java @@ -43,6 +43,7 @@ public class MessageOptions { private String prompt; + private String clientCorrelationId; private List attachments; private String mode; private MessageSource source; @@ -96,6 +97,29 @@ public MessageOptions setPrompt(String prompt) { return this; } + /** + * Gets the caller-owned diagnostic UUID for this RPC admission. + * + * @return the correlation value, or {@code null} when unset + */ + public String getClientCorrelationId() { + return clientCorrelationId; + } + + /** + * Sets optional caller-owned admission metadata, sent unchanged. Supported + * runtimes echo accepted UUIDs only with RUNTIME_ADMISSION_TRACE_CONTEXT + * enabled. This is not an idempotency key; the SDK never generates a value. + * + * @param clientCorrelationId + * the diagnostic UUID, or {@code null} to omit it + * @return this options instance for method chaining + */ + public MessageOptions setClientCorrelationId(String clientCorrelationId) { + this.clientCorrelationId = clientCorrelationId; + return this; + } + /** * Gets the attachments. * @@ -265,6 +289,7 @@ public MessageOptions setDisplayPrompt(String displayPrompt) { public MessageOptions clone() { MessageOptions copy = new MessageOptions(); copy.prompt = this.prompt; + copy.clientCorrelationId = this.clientCorrelationId; copy.attachments = this.attachments != null ? new ArrayList<>(this.attachments) : null; copy.mode = this.mode; copy.source = this.source; diff --git a/java/sdk/src/main/java/com/github/copilot/rpc/SendMessageRequest.java b/java/sdk/src/main/java/com/github/copilot/rpc/SendMessageRequest.java index fb27324831..613f5a9324 100644 --- a/java/sdk/src/main/java/com/github/copilot/rpc/SendMessageRequest.java +++ b/java/sdk/src/main/java/com/github/copilot/rpc/SendMessageRequest.java @@ -31,6 +31,9 @@ public final class SendMessageRequest { @JsonProperty("prompt") private String prompt; + @JsonProperty("clientCorrelationId") + private String clientCorrelationId; + @JsonProperty("attachments") private List attachments; @@ -82,6 +85,19 @@ public void setPrompt(String prompt) { this.prompt = prompt; } + /** Gets the caller-owned admission UUID. @return the value, or {@code null} */ + public String getClientCorrelationId() { + return clientCorrelationId; + } + + /** + * Sets admission metadata. @param clientCorrelationId the value, or + * {@code null} + */ + public void setClientCorrelationId(String clientCorrelationId) { + this.clientCorrelationId = clientCorrelationId; + } + /** Gets the attachments. @return the list of attachments */ public List getAttachments() { return attachments == null ? null : Collections.unmodifiableList(attachments); diff --git a/java/sdk/src/test/java/com/github/copilot/MessageSourceTest.java b/java/sdk/src/test/java/com/github/copilot/MessageSourceTest.java index 6ef9d4e2b0..ce4efc1a74 100644 --- a/java/sdk/src/test/java/com/github/copilot/MessageSourceTest.java +++ b/java/sdk/src/test/java/com/github/copilot/MessageSourceTest.java @@ -31,6 +31,7 @@ import com.fasterxml.jackson.databind.JsonNode; import com.fasterxml.jackson.databind.ObjectMapper; import com.github.copilot.generated.rpc.SessionSendParams; +import com.github.copilot.generated.rpc.SessionSendMessagesParams; import com.github.copilot.rpc.AgentMode; import com.github.copilot.rpc.Attachment; import com.github.copilot.rpc.CopilotClientOptions; @@ -180,6 +181,62 @@ void sendWithoutSourcePreservesLegacyPayload() throws Exception { } } + @ParameterizedTest + @NullSource + @ValueSource(strings = {"01234567-89ab-4cde-8f01-23456789abcd", "01234567-89AB-4CDE-8F01-23456789ABCD", + "not-a-uuid", ""}) + void admissionCorrelationIsOptionalAndDoesNotRewriteOptions(String correlation) throws Exception { + try (var server = new SendServer(Outcome.IDLE); + var client = server.createClient(); + var session = client.createSession(sessionConfig()).get(5, TimeUnit.SECONDS)) { + var options = fullOptions().setSource(MessageSource.SYSTEM).setClientCorrelationId(correlation); + assertEquals(correlation, options.clone().getClientCorrelationId()); + assertEquals("message-1", session.send(options).get(5, TimeUnit.SECONDS)); + var wire = server.takeSendParams(); + assertEquals("system", wire.get("source").asText()); + assertEquals("display", wire.get("displayPrompt").asText()); + assertEquals("trace-id", wire.get("requestHeaders").get("X-Trace").asText()); + assertEquals(MAPPER.valueToTree(options.getAttachments()), wire.get("attachments")); + assertEquals(correlation, wire.path("clientCorrelationId").asText(null)); + assertEquals(correlation != null, wire.has("clientCorrelationId")); + + assertNull(session.sendAndWait(options).get(5, TimeUnit.SECONDS)); + assertEquals(correlation, server.takeSendParams().path("clientCorrelationId").asText(null)); + + var params = MAPPER.createObjectNode().put("prompt", "hello"); + if (correlation != null) { + params.put("clientCorrelationId", correlation); + } + var generated = MAPPER.treeToValue(params, SessionSendParams.class); + assertEquals("message-1", session.getRpc().send(generated).get(5, TimeUnit.SECONDS).messageId()); + params.put("sessionId", "source-session"); + assertEquals(params, server.takeSendParams()); + options.setClientCorrelationId(null); + assertFalse(MAPPER.valueToTree(options).has("clientCorrelationId")); + } + } + + @Test + void admissionCorrelationRemainsPerBatchItem() throws Exception { + try (var server = new SendServer(Outcome.IDLE); + var client = server.createClient(); + var session = client.createSession(sessionConfig()).get(5, TimeUnit.SECONDS)) { + var json = MAPPER.readTree(""" + {"messages":[ + {"prompt":"context","clientCorrelationId":"01234567-89ab-4cde-8f01-23456789abcd"}, + {"prompt":"plain"}, + {"prompt":"reused","clientCorrelationId":"01234567-89ab-4cde-8f01-23456789abcd"} + ]} + """); + var params = MAPPER.treeToValue(json, SessionSendMessagesParams.class); + var result = session.getRpc().sendMessages(params).get(5, TimeUnit.SECONDS); + assertEquals(List.of("batch-0", "batch-1", "batch-2"), result.messageIds()); + var wire = server.takeSendParams(); + assertFalse(wire.has("clientCorrelationId")); + assertEquals(json.get("messages"), wire.get("messages")); + } + } + @ParameterizedTest @MethodSource("sourcesAndModes") void sendForwardsSourceWithoutChangingOtherOptions(MessageSource source, String mode) throws Exception { @@ -313,10 +370,12 @@ private void serve() { case "connect" -> Map.of("ok", true, "protocolVersion", 3, "version", "test"); case "session.create" -> Map.of("sessionId", params.path("sessionId").asText()); case "session.send" -> Map.of("messageId", "message-1"); + case "session.sendMessages" -> Map.of("messageIds", java.util.stream.IntStream + .range(0, params.path("messages").size()).mapToObj(index -> "batch-" + index).toList()); case "session.detach" -> Map.of("success", true); default -> Map.of(); }; - boolean send = "session.send".equals(method); + boolean send = "session.send".equals(method) || "session.sendMessages".equals(method); if (send) { sends.add(params); } diff --git a/java/sdk/src/test/java/com/github/copilot/generated/MessageIdentitySerializationTest.java b/java/sdk/src/test/java/com/github/copilot/generated/MessageIdentitySerializationTest.java index 1f88cdab06..7570c3cab9 100644 --- a/java/sdk/src/test/java/com/github/copilot/generated/MessageIdentitySerializationTest.java +++ b/java/sdk/src/test/java/com/github/copilot/generated/MessageIdentitySerializationTest.java @@ -13,13 +13,63 @@ import com.fasterxml.jackson.databind.JsonNode; import com.fasterxml.jackson.databind.ObjectMapper; import com.fasterxml.jackson.databind.node.ObjectNode; +import com.github.copilot.AllowCopilotExperimental; import com.github.copilot.generated.UserMessageEvent.UserMessageEventData; import com.github.copilot.generated.rpc.QueuePendingItems; +import com.github.copilot.generated.rpc.QueuePendingItemsKind; +import com.github.copilot.generated.rpc.SendAgentMode; +import com.github.copilot.generated.rpc.SendMessageItem; +import com.github.copilot.generated.rpc.SessionSendParams; +@AllowCopilotExperimental class MessageIdentitySerializationTest { private static final ObjectMapper MAPPER = new ObjectMapper(); + @Test + void testPreviousRecordConstructorsStillOmitCorrelation() { + var item = new SendMessageItem("hello", null, null, null, null, null); + var row = new QueuePendingItems("queue-1", "canonical-1", QueuePendingItemsKind.MESSAGE, "hello", + SendAgentMode.INTERACTIVE); + var send = new SessionSendParams(null, "hello", null, null, null, null, null, null, null, null, null, null, + null, null, null); + assertNull(item.clientCorrelationId()); + assertNull(row.clientCorrelationId()); + assertNull(send.clientCorrelationId()); + assertEquals("canonical-1", row.messageId()); + } + + @Test + void testAdmissionCorrelationOptionalRoundTrips() throws Exception { + for (String value : new String[]{null, "01234567-89ab-4cde-8f01-23456789abcd", + "01234567-89AB-4CDE-8F01-23456789ABCD", "not-a-uuid", ""}) { + for (var type : new Class[]{SessionSendParams.class, SendMessageItem.class, QueuePendingItems.class, + UserMessageEventData.class}) { + var wire = MAPPER.createObjectNode(); + if (type == QueuePendingItems.class) { + wire.put("id", "queue-1").put("messageId", "canonical-1").put("kind", "message") + .put("displayText", "hello").put("agentMode", "interactive"); + } else if (type == UserMessageEventData.class) { + wire.put("content", "hello").put("messageId", "canonical-1").put("interactionId", "agent-loop-1"); + } else { + wire.put("prompt", "hello"); + } + if (value != null) { + wire.put("clientCorrelationId", value); + } + var decoded = MAPPER.readValue(wire.toString(), type); + assertEquals(wire, MAPPER.valueToTree(decoded)); + wire.put("futureField", true); + decoded = MAPPER.treeToValue(wire, type); + wire.remove("futureField"); + assertEquals(wire, MAPPER.valueToTree(decoded)); + wire.putNull("clientCorrelationId"); + decoded = MAPPER.treeToValue(wire, type); + assertFalse(MAPPER.valueToTree(decoded).has("clientCorrelationId")); + } + } + } + @Test void testQueuePendingMessageIdUsesCamelCaseAndIsOptional() throws Exception { var item = MAPPER.readValue(""" @@ -67,24 +117,16 @@ void testUserMessageIdUsesCamelCaseAndIsOptional() throws Exception { @Test void testToolStartTraceContextIsOptionalAndPreserved() throws Exception { - for (var context : new String[] { - "{}", - """ - {"traceparent":"00-11111111111111111111111111111111-2222222222222222-01","tracestate":"vendor=value"} - """, - """ - {"traceparent":"00-11111111111111111111111111111111-2222222222222222-00"} - """, - """ - {"traceparent":"invalid","tracestate":"invalid"} - """, - """ - {"traceparent":""} - """ - }) { - var wire = MAPPER.createObjectNode() - .put("toolCallId", "tool-call-a") - .put("toolName", "client-tool"); + for (var context : new String[]{"{}", """ + {"traceparent":"00-11111111111111111111111111111111-2222222222222222-01","tracestate":"vendor=value"} + """, """ + {"traceparent":"00-11111111111111111111111111111111-2222222222222222-00"} + """, """ + {"traceparent":"invalid","tracestate":"invalid"} + """, """ + {"traceparent":""} + """}) { + var wire = MAPPER.createObjectNode().put("toolCallId", "tool-call-a").put("toolName", "client-tool"); wire.setAll(MAPPER.readValue(context, ObjectNode.class)); var data = MAPPER.treeToValue(wire, ToolExecutionStartEvent.ToolExecutionStartEventData.class); diff --git a/nodejs/src/generated/rpc.ts b/nodejs/src/generated/rpc.ts index bf7b24fd8b..1a3b604005 100644 --- a/nodejs/src/generated/rpc.ts +++ b/nodejs/src/generated/rpc.ts @@ -18321,6 +18321,10 @@ export interface QueuePendingItems { * Stable identity of the queued user message. Present for message rows and absent for slash commands and model changes. */ messageId?: string; + /** + * Caller-owned diagnostic UUID from the exact accepted native session.send or sendMessages item, when RUNTIME_ADMISSION_TRACE_CONTEXT is enabled. Omitted for unsupported or identity-less rows, including snapshot-only mirrors. Not an idempotency key, authorization, or permission to retry; repeated values remain ambiguous. + */ + clientCorrelationId?: string; kind: QueuePendingItemsKind; /** * Human-readable text to display for this queue entry in the UI @@ -19401,6 +19405,10 @@ export interface SendMessageItem { * The user message text */ prompt: string; + /** + * Optional caller-generated diagnostic UUID for this item only, with the same validation and opt-in native echo as session.send.clientCorrelationId. The batch has no request-level correlation value; each item retains its own value, including preceding context messages. Reused values do not deduplicate messages and remain ambiguous. + */ + clientCorrelationId?: string; /** * If provided, this is shown in the timeline instead of `prompt` */ @@ -19489,6 +19497,10 @@ export interface SendRequest { * The user message text */ prompt: string; + /** + * Optional caller-generated diagnostic UUID for this single message. Native sessions with RUNTIME_ADMISSION_TRACE_CONTEXT enabled echo the exact lowercase, hyphenated 36-character UUID on user.message and its existing pending message row. Missing, invalid, disabled, or unsupported metadata is ignored without rejecting the send. Does not change messageId, deduplicate submissions, authorize work, or make an uncertain retry safe. + */ + clientCorrelationId?: string; /** * If provided, this is shown in the timeline instead of `prompt` */ diff --git a/nodejs/src/generated/session-events.ts b/nodejs/src/generated/session-events.ts index 7ba892bea1..c1881656e2 100644 --- a/nodejs/src/generated/session-events.ts +++ b/nodejs/src/generated/session-events.ts @@ -4302,6 +4302,10 @@ export interface UserMessageData { * Files, selections, or GitHub references attached to the message */ attachments?: Attachment[]; + /** + * Exact caller-owned diagnostic UUID carried by this accepted native session.send or sendMessages item when RUNTIME_ADMISSION_TRACE_CONTEXT is enabled. Omitted when input, native ownership, or support is missing. Independent of the canonical messageId; not an idempotency key, authorization, or permission to retry. Multiple messages with the same value remain ambiguous. + */ + clientCorrelationId?: string; /** * The user's message text as displayed in the timeline */ diff --git a/nodejs/src/session.ts b/nodejs/src/session.ts index 97c073f64d..bc586cf775 100644 --- a/nodejs/src/session.ts +++ b/nodejs/src/session.ts @@ -1125,6 +1125,9 @@ export class CopilotSession { ...(await getTraceContext(this.traceContextProvider)), sessionId: this.sessionId, prompt: options.prompt, + ...(options.clientCorrelationId == null + ? {} + : { clientCorrelationId: options.clientCorrelationId }), source: options.source, displayPrompt: options.displayPrompt, attachments: options.attachments, diff --git a/nodejs/src/types.ts b/nodejs/src/types.ts index 6a19116a37..6d52b362fa 100644 --- a/nodejs/src/types.ts +++ b/nodejs/src/types.ts @@ -3362,6 +3362,13 @@ export interface MessageOptions { */ prompt: string; + /** + * Optional caller-owned diagnostic UUID for this RPC admission, sent unchanged. + * Supported runtimes echo accepted values only with RUNTIME_ADMISSION_TRACE_CONTEXT + * enabled. Not an idempotency key; the SDK never generates this value. + */ + clientCorrelationId?: string; + /** * Optional message provenance. Omitted by default to preserve the runtime's * default for user messages. Use "system" for application-generated context diff --git a/nodejs/test/csharp-codegen.test.ts b/nodejs/test/csharp-codegen.test.ts index a0c802ac49..461f3e8234 100644 --- a/nodejs/test/csharp-codegen.test.ts +++ b/nodejs/test/csharp-codegen.test.ts @@ -52,6 +52,39 @@ describe("C# root event payload unions", () => { }); describe("C# RPC codegen", () => { + it("preserves positional send arguments and the existing CLR signature", () => { + const code = generateRpcCode({ + session: { + send: { + rpcMethod: "session.send", + params: { + title: "SendRequest", + type: "object", + properties: { + sessionId: { type: "string" }, + prompt: { type: "string" }, + clientCorrelationId: { type: "string" }, + displayPrompt: { type: "string" }, + wait: { type: "boolean" }, + }, + required: ["sessionId", "prompt"], + }, + }, + }, + }); + + expect(code).toContain( + "SendAsync(string prompt, string? displayPrompt = null, bool? wait = null, string? clientCorrelationId = null, CancellationToken cancellationToken = default)" + ); + expect(code).toContain("ClientCorrelationId = clientCorrelationId"); + expect(code).toContain( + "SendAsync(string prompt, string? displayPrompt, bool? wait, CancellationToken cancellationToken)" + ); + expect(code).toContain( + "=> SendAsync(prompt, displayPrompt, wait, clientCorrelationId: null, cancellationToken: cancellationToken);" + ); + }); + it.each(["uninstall", "update"])( "separates the session wire envelope from the shared plugins %s request", (method) => { diff --git a/nodejs/test/message-identity-types.test.ts b/nodejs/test/message-identity-types.test.ts index a3c7c01a3e..51933ff7bd 100644 --- a/nodejs/test/message-identity-types.test.ts +++ b/nodejs/test/message-identity-types.test.ts @@ -1,8 +1,58 @@ -import { describe, expect, it } from "vitest"; -import type { QueuePendingItems } from "../src/generated/rpc.js"; +import { describe, expect, expectTypeOf, it } from "vitest"; +import type { QueuePendingItems, SendMessageItem, SendRequest } from "../src/generated/rpc.js"; import type { ToolExecutionStartData, UserMessageData } from "../src/generated/session-events.js"; describe("generated message identity types", () => { + it("keeps admission correlation optional, string-valued, and independent of messageId", () => { + expectTypeOf().toEqualTypeOf(); + expectTypeOf().toEqualTypeOf(); + expectTypeOf().toEqualTypeOf< + string | undefined + >(); + expectTypeOf().toEqualTypeOf(); + + for (const clientCorrelationId of [ + undefined, + "01234567-89ab-4cde-8f01-23456789abcd", + "01234567-89AB-4CDE-8F01-23456789ABCD", + "not-a-uuid", + "", + ]) { + const values: Array< + SendRequest | SendMessageItem | QueuePendingItems | UserMessageData + > = [ + { prompt: "hello", clientCorrelationId }, + { + id: "queue-1", + messageId: "canonical-1", + kind: "message", + displayText: "hello", + agentMode: "interactive", + clientCorrelationId, + }, + { + content: "hello", + messageId: "canonical-1", + interactionId: "agent-loop-1", + clientCorrelationId, + }, + ]; + for (const value of values) { + const decoded = JSON.parse(JSON.stringify(value)); + expect(decoded.clientCorrelationId).toBe(clientCorrelationId); + expect(Object.hasOwn(decoded, "clientCorrelationId")).toBe( + clientCorrelationId !== undefined + ); + expect(decoded.messageId).toBe("messageId" in value ? value.messageId : undefined); + const future = JSON.parse( + JSON.stringify({ ...value, futureField: { enabled: true } }) + ); + expect(future.clientCorrelationId).toBe(clientCorrelationId); + expect(future.futureField).toEqual({ enabled: true }); + } + } + }); + it("exposes optional camelCase message IDs", () => { const queueItemWithIdentity: QueuePendingItems = { id: "queue-1", diff --git a/nodejs/test/message-source.test.ts b/nodejs/test/message-source.test.ts index 394e42163c..71d911904c 100644 --- a/nodejs/test/message-source.test.ts +++ b/nodejs/test/message-source.test.ts @@ -46,6 +46,117 @@ function sessionPair(traceContextProvider?: ConstructorParameters { + it.each([ + undefined, + "01234567-89ab-4cde-8f01-23456789abcd", + "01234567-89AB-4CDE-8F01-23456789ABCD", + "not-a-uuid", + "", + ])( + "forwards only explicitly supplied strings without changing other options: %s", + async (clientCorrelationId) => { + const trace = { + traceparent: "00-11111111111111111111111111111111-2222222222222222-01", + tracestate: "vendor=preserved", + }; + const { session, server } = sessionPair(() => trace); + const options = { + prompt: "hello", + source: "system", + mode: "enqueue", + displayPrompt: "display", + requestHeaders: { "X-Test": "preserved" }, + clientCorrelationId, + } satisfies MessageOptions; + server.onRequest("session.send", (params: unknown) => { + expect(params).toEqual( + JSON.parse(JSON.stringify({ sessionId: "session-1", ...trace, ...options })) + ); + return { messageId: "canonical-1", futureField: true }; + }); + await expect(session.send(options)).resolves.toBe("canonical-1"); + await expect(session.rpc.send({ ...trace, ...options })).resolves.toMatchObject({ + messageId: "canonical-1", + }); + } + ); + + it("omits null metadata at the untyped convenience boundary", async () => { + const { session, server } = sessionPair(); + server.onRequest("session.send", (params: unknown) => { + expect(params).toEqual({ sessionId: "session-1", prompt: "hello" }); + return { messageId: "canonical-1" }; + }); + await expect( + session.send({ + prompt: "hello", + clientCorrelationId: null, + } as unknown as MessageOptions) + ).resolves.toBe("canonical-1"); + }); + + it("retains admission metadata through sendAndWait", async () => { + const clientCorrelationId = "01234567-89ab-4cde-8f01-23456789abcd"; + const { session, server } = sessionPair(); + server.onRequest("session.send", (params: unknown) => { + expect(params).toEqual({ + sessionId: "session-1", + prompt: "hello", + clientCorrelationId, + }); + session._dispatchEvent({ + type: "session.idle", + id: "idle-1", + parentId: null, + timestamp: "2026-09-24T18:00:00Z", + ephemeral: true, + data: {}, + }); + return { messageId: "canonical-1" }; + }); + await expect( + session.sendAndWait({ prompt: "hello", clientCorrelationId }, 1000) + ).resolves.toBeUndefined(); + }); + + it("keeps concurrent and repeated values per input, not per batch", async () => { + const first = "01234567-89ab-4cde-8f01-23456789abcd"; + const second = "abcdef01-2345-4678-9abc-def012345678"; + const { session, server } = sessionPair(); + const messages = [ + { prompt: "context", clientCorrelationId: second }, + { prompt: "plain" }, + { prompt: "reused", clientCorrelationId: first }, + ]; + let sends = 0; + server.onRequest( + "session.send", + (params: { prompt: string; clientCorrelationId: string }) => { + sends++; + expect(params.clientCorrelationId).toBe(params.prompt === "first" ? first : second); + return { messageId: `${params.prompt}-id` }; + } + ); + server.onRequest("session.sendMessages", (params: unknown) => { + expect(params).toEqual({ sessionId: "session-1", messages }); + return { messageIds: ["context-id", "plain-id", "reused-id"] }; + }); + await expect( + Promise.all([ + session.send({ prompt: "first", clientCorrelationId: first }), + session.send({ prompt: "second", clientCorrelationId: second }), + session.rpc.sendMessages({ messages }), + ]) + ).resolves.toEqual([ + "first-id", + "second-id", + { messageIds: ["context-id", "plain-id", "reused-id"] }, + ]); + expect(sends).toBe(2); + }); +}); + it("omits source when sending a plain human prompt", async () => { const { session, server } = sessionPair(); server.onRequest("session.send", (params: unknown) => { diff --git a/python/copilot/generated/rpc.py b/python/copilot/generated/rpc.py index 8d852a7b4b..dd58e4f3b0 100644 --- a/python/copilot/generated/rpc.py +++ b/python/copilot/generated/rpc.py @@ -11184,6 +11184,12 @@ class SendMessageItem: """If false, this message will not trigger a Premium Request Unit charge. User messages default to billable. """ + client_correlation_id: str | None = None + """Optional caller-generated diagnostic UUID for this item only, with the same validation + and opt-in native echo as session.send.clientCorrelationId. The batch has no + request-level correlation value; each item retains its own value, including preceding + context messages. Reused values do not deduplicate messages and remain ambiguous. + """ display_prompt: str | None = None """If provided, this is shown in the timeline instead of `prompt`""" @@ -11204,10 +11210,11 @@ def from_dict(obj: Any) -> 'SendMessageItem': prompt = from_str(obj.get("prompt")) attachments = from_union([lambda x: from_list(_load_Attachment, x), from_none], obj.get("attachments")) billable = from_union([from_bool, from_none], obj.get("billable")) + client_correlation_id = from_union([from_str, from_none], obj.get("clientCorrelationId")) display_prompt = from_union([from_str, from_none], obj.get("displayPrompt")) required_tool = from_union([from_str, from_none], obj.get("requiredTool")) source = from_union([from_str, from_none], obj.get("source")) - return SendMessageItem(prompt, attachments, billable, display_prompt, required_tool, source) + return SendMessageItem(prompt, attachments, billable, client_correlation_id, display_prompt, required_tool, source) def to_dict(self) -> dict: result: dict = {} @@ -11216,6 +11223,8 @@ def to_dict(self) -> dict: result["attachments"] = from_union([lambda x: from_list(lambda x: to_class(Attachment, x), x), from_none], self.attachments) if self.billable is not None: result["billable"] = from_union([from_bool, from_none], self.billable) + if self.client_correlation_id is not None: + result["clientCorrelationId"] = from_union([from_str, from_none], self.client_correlation_id) if self.display_prompt is not None: result["displayPrompt"] = from_union([from_str, from_none], self.display_prompt) if self.required_tool is not None: @@ -25740,6 +25749,12 @@ class QueuePendingItems: kind: QueuePendingItemsKind """Whether this item is a queued user message or a queued slash command / model change""" + client_correlation_id: str | None = None + """Caller-owned diagnostic UUID from the exact accepted native session.send or sendMessages + item, when RUNTIME_ADMISSION_TRACE_CONTEXT is enabled. Omitted for unsupported or + identity-less rows, including snapshot-only mirrors. Not an idempotency key, + authorization, or permission to retry; repeated values remain ambiguous. + """ message_id: str | None = None """Stable identity of the queued user message. Present for message rows and absent for slash commands and model changes. @@ -25752,8 +25767,9 @@ def from_dict(obj: Any) -> 'QueuePendingItems': display_text = from_str(obj.get("displayText")) id = from_str(obj.get("id")) kind = QueuePendingItemsKind(obj.get("kind")) + client_correlation_id = from_union([from_str, from_none], obj.get("clientCorrelationId")) message_id = from_union([from_str, from_none], obj.get("messageId")) - return QueuePendingItems(agent_mode, display_text, id, kind, message_id) + return QueuePendingItems(agent_mode, display_text, id, kind, client_correlation_id, message_id) def to_dict(self) -> dict: result: dict = {} @@ -25761,6 +25777,8 @@ def to_dict(self) -> dict: result["displayText"] = from_str(self.display_text) result["id"] = from_str(self.id) result["kind"] = to_enum(QueuePendingItemsKind, self.kind) + if self.client_correlation_id is not None: + result["clientCorrelationId"] = from_union([from_str, from_none], self.client_correlation_id) if self.message_id is not None: result["messageId"] = from_union([from_str, from_none], self.message_id) return result @@ -32658,6 +32676,13 @@ class SendRequest: """If false, this message will not trigger a Premium Request Unit charge. User messages default to billable. """ + client_correlation_id: str | None = None + """Optional caller-generated diagnostic UUID for this single message. Native sessions with + RUNTIME_ADMISSION_TRACE_CONTEXT enabled echo the exact lowercase, hyphenated 36-character + UUID on user.message and its existing pending message row. Missing, invalid, disabled, or + unsupported metadata is ignored without rejecting the send. Does not change messageId, + deduplicate submissions, authorize work, or make an uncertain retry safe. + """ display_prompt: str | None = None """If provided, this is shown in the timeline instead of `prompt`""" @@ -32715,6 +32740,7 @@ def from_dict(obj: Any) -> 'SendRequest': agent_mode = from_union([SendAgentMode, from_none], obj.get("agentMode")) attachments = from_union([lambda x: from_list(_load_Attachment, x), from_none], obj.get("attachments")) billable = from_union([from_bool, from_none], obj.get("billable")) + client_correlation_id = from_union([from_str, from_none], obj.get("clientCorrelationId")) display_prompt = from_union([from_str, from_none], obj.get("displayPrompt")) mode = from_union([SendMode, from_none], obj.get("mode")) prepend = from_union([from_bool, from_none], obj.get("prepend")) @@ -32725,7 +32751,7 @@ def from_dict(obj: Any) -> 'SendRequest': traceparent = from_union([from_str, from_none], obj.get("traceparent")) tracestate = from_union([from_str, from_none], obj.get("tracestate")) wait = from_union([from_bool, from_none], obj.get("wait")) - return SendRequest(prompt, agent_mode, attachments, billable, display_prompt, mode, prepend, request_headers, required_tool, response_format, source, traceparent, tracestate, wait) + return SendRequest(prompt, agent_mode, attachments, billable, client_correlation_id, display_prompt, mode, prepend, request_headers, required_tool, response_format, source, traceparent, tracestate, wait) def to_dict(self) -> dict: result: dict = {} @@ -32736,6 +32762,8 @@ def to_dict(self) -> dict: result["attachments"] = from_union([lambda x: from_list(lambda x: to_class(Attachment, x), x), from_none], self.attachments) if self.billable is not None: result["billable"] = from_union([from_bool, from_none], self.billable) + if self.client_correlation_id is not None: + result["clientCorrelationId"] = from_union([from_str, from_none], self.client_correlation_id) if self.display_prompt is not None: result["displayPrompt"] = from_union([from_str, from_none], self.display_prompt) if self.mode is not None: diff --git a/python/copilot/generated/session_events.py b/python/copilot/generated/session_events.py index 4207a9d14a..e010a07c85 100644 --- a/python/copilot/generated/session_events.py +++ b/python/copilot/generated/session_events.py @@ -12604,6 +12604,7 @@ class UserMessageData: content: str agent_mode: UserMessageAgentMode | None = None attachments: list[Attachment] | None = None + client_correlation_id: str | None = None delivery: UserMessageDelivery | None = None interaction_id: str | None = None is_autopilot_continuation: bool | None = None @@ -12622,6 +12623,7 @@ def from_dict(obj: Any) -> "UserMessageData": content = from_str(obj.get("content")) agent_mode = from_union([from_none, lambda x: parse_enum(UserMessageAgentMode, x)], obj.get("agentMode")) attachments = from_union([from_none, lambda x: from_list(_load_Attachment, x)], obj.get("attachments")) + client_correlation_id = from_union([from_none, from_str], obj.get("clientCorrelationId")) delivery = from_union([from_none, lambda x: parse_enum(UserMessageDelivery, x)], obj.get("delivery")) interaction_id = from_union([from_none, from_str], obj.get("interactionId")) is_autopilot_continuation = from_union([from_none, from_bool], obj.get("isAutopilotContinuation")) @@ -12637,6 +12639,7 @@ def from_dict(obj: Any) -> "UserMessageData": content=content, agent_mode=agent_mode, attachments=attachments, + client_correlation_id=client_correlation_id, delivery=delivery, interaction_id=interaction_id, is_autopilot_continuation=is_autopilot_continuation, @@ -12657,6 +12660,8 @@ def to_dict(self) -> dict: result["agentMode"] = from_union([from_none, lambda x: to_enum(UserMessageAgentMode, x)], self.agent_mode) if self.attachments is not None: result["attachments"] = from_union([from_none, lambda x: from_list(lambda x: x.to_dict(), x)], self.attachments) + if self.client_correlation_id is not None: + result["clientCorrelationId"] = from_union([from_none, from_str], self.client_correlation_id) if self.delivery is not None: result["delivery"] = from_union([from_none, lambda x: to_enum(UserMessageDelivery, x)], self.delivery) if self.interaction_id is not None: diff --git a/python/copilot/session.py b/python/copilot/session.py index 6534e4cbae..f398b98d3d 100644 --- a/python/copilot/session.py +++ b/python/copilot/session.py @@ -104,6 +104,15 @@ logger = logging.getLogger(__name__) TResponse = TypeVar("TResponse", bound=BaseModel) + +class _AdmissionCorrelationKwargs(TypedDict, total=False): + client_correlation_id: str + + +def _admission_correlation_kwargs(value: str | None) -> _AdmissionCorrelationKwargs: + return {"client_correlation_id": value} if value is not None else {} + + # Fixed name of the runtime's built-in tool-search tool. A client can replace # its behavior by registering a tool with this exact name and # ``overrides_built_in_tool=True``. @@ -1766,6 +1775,7 @@ async def send( request_headers: dict[str, str] | None = None, display_prompt: str | None = None, response_schema: dict[str, Any] | type[BaseModel] | None = None, + client_correlation_id: str | None = None, ) -> str: """ Send a message to this session. @@ -1790,6 +1800,9 @@ async def send( ``prompt``. response_schema: JSON Schema or a Pydantic model for this run. Independent sends do not inherit it. Immediate steering cannot specify a schema. + client_correlation_id: Optional caller-owned diagnostic UUID for this RPC + admission, sent unchanged. Supported runtimes echo accepted values only + with RUNTIME_ADMISSION_TRACE_CONTEXT enabled. Not an idempotency key. Returns: The message ID assigned by the server, which can be used to correlate events. @@ -1807,6 +1820,8 @@ async def send( "sessionId": self.session_id, "prompt": prompt, } + if client_correlation_id is not None: + params["clientCorrelationId"] = client_correlation_id if attachments is not None: params["attachments"] = attachments if source is not None: @@ -1858,6 +1873,7 @@ async def send_and_wait( display_prompt: str | None = None, response_schema: dict[str, Any] | type[BaseModel] | None = None, timeout: float = 60.0, + client_correlation_id: str | None = None, ) -> SessionEvent | None: """ Send a message to this session and wait until the session becomes idle. @@ -1885,6 +1901,8 @@ async def send_and_wait( does not abort in-flight agent work. response_schema: A per-run schema. Waits for the last correlated root assistant message without tool requests at non-autopilot idle. + client_correlation_id: Optional caller-owned admission UUID, forwarded + unchanged to :meth:`send`. The SDK never generates this value. Returns: The final assistant message event, or None if none was received. @@ -1912,6 +1930,7 @@ async def send_and_wait( request_headers=request_headers, display_prompt=display_prompt, response_schema=response_schema, + **_admission_correlation_kwargs(client_correlation_id), ), timeout, ) @@ -1958,6 +1977,7 @@ def handler(event: SessionEventTypeAlias) -> None: agent_mode=agent_mode, request_headers=request_headers, display_prompt=display_prompt, + **_admission_correlation_kwargs(client_correlation_id), ) await asyncio.wait_for(idle_event.wait(), timeout=timeout) if error_event: @@ -2005,6 +2025,7 @@ async def send_and_wait_typed( request_headers: dict[str, str] | None = None, display_prompt: str | None = None, timeout: float = 60.0, + client_correlation_id: str | None = None, ) -> TResponse: """Infer a schema using Pydantic, then validate and return the final result. @@ -2014,6 +2035,8 @@ async def send_and_wait_typed( nested models, regardless of model-level alias validation settings. Streaming events remain text. Timeout or cancellation only stops waiting, not the agent. Errors remain session-scoped. + ``client_correlation_id`` is optional caller-owned admission metadata, + forwarded unchanged to :meth:`send`. """ if not isinstance(response_type, type) or not issubclass(response_type, BaseModel): raise TypeError("response_type must be a Pydantic BaseModel subclass") @@ -2031,6 +2054,7 @@ async def send_and_wait_typed( display_prompt=display_prompt, response_schema=response_type, timeout=timeout, + **_admission_correlation_kwargs(client_correlation_id), ) assert response is not None and isinstance(response.data, AssistantMessageData) return response_type.model_validate_json( diff --git a/python/test_message_identity_generated.py b/python/test_message_identity_generated.py index 388699d885..62b50482ad 100644 --- a/python/test_message_identity_generated.py +++ b/python/test_message_identity_generated.py @@ -1,7 +1,55 @@ -from copilot.generated.rpc import QueuePendingItems +import json + +import pytest + +from copilot.generated.rpc import QueuePendingItems, SendMessageItem, SendRequest from copilot.generated.session_events import ToolExecutionStartData, UserMessageData +@pytest.mark.parametrize( + ("projection", "base"), + [ + (SendRequest, {"prompt": "hello"}), + (SendMessageItem, {"prompt": "hello"}), + ( + QueuePendingItems, + { + "id": "queue-1", + "messageId": "canonical-1", + "kind": "message", + "displayText": "hello", + "agentMode": "interactive", + }, + ), + ( + UserMessageData, + {"content": "hello", "messageId": "canonical-1", "interactionId": "agent-loop-1"}, + ), + ], +) +@pytest.mark.parametrize( + "value", + [ + None, + "01234567-89ab-4cde-8f01-23456789abcd", + "01234567-89AB-4CDE-8F01-23456789ABCD", + "not-a-uuid", + "", + ], +) +def test_admission_correlation_optional_round_trip(projection, base, value): + expected = dict(base) + if value is not None: + expected["clientCorrelationId"] = value + decoded = projection.from_dict(json.loads(json.dumps(expected))) + assert decoded.client_correlation_id == value + assert json.loads(json.dumps(decoded.to_dict())) == expected + future = projection.from_dict({**expected, "futureField": {"enabled": True}}) + assert future.to_dict() == expected + explicit_null = projection.from_dict({**base, "clientCorrelationId": None}) + assert explicit_null.to_dict() == base + + def test_queue_pending_message_id_uses_camel_case_and_is_optional(): item = QueuePendingItems.from_dict( { diff --git a/python/test_session.py b/python/test_session.py index 5750364bb5..05a051dd73 100644 --- a/python/test_session.py +++ b/python/test_session.py @@ -10,6 +10,7 @@ from _session_test_helpers import get_next_event_of_type, wait_for_event from copilot import AgentMessageSource, MessageSource +from copilot.rpc import SendMessageItem, SendMessagesRequest, SendRequest from copilot.session import Attachment, CopilotSession from copilot.session_events import ( AssistantMessageData, @@ -36,6 +37,87 @@ ] +@pytest.mark.parametrize( + "value", + [ + None, + "01234567-89ab-4cde-8f01-23456789abcd", + "01234567-89AB-4CDE-8F01-23456789ABCD", + "not-a-uuid", + "", + ], +) +@pytest.mark.asyncio +async def test_admission_correlation_preserves_send_and_wait_options(value, monkeypatch): + trace = { + "traceparent": "00-11111111111111111111111111111111-2222222222222222-01", + "tracestate": "vendor=preserved", + } + monkeypatch.setattr("copilot.session.get_trace_context", lambda: trace) + client = Mock() + session = CopilotSession("session-1", client) + expected = { + "sessionId": "session-1", + "prompt": "hello", + "source": "system", + "displayPrompt": "display", + "requestHeaders": {"X-Test": "preserved"}, + **trace, + } + if value is not None: + expected["clientCorrelationId"] = value + + async def respond(method, params, **_kwargs): + assert method == "session.send" + assert params == expected + session._dispatch_event(_event(SessionIdleData(), SessionEventType.SESSION_IDLE)) + return {"messageId": "canonical-1", "futureField": True} + + client.request = AsyncMock(side_effect=respond) + options = { + "source": "system", + "display_prompt": "display", + "request_headers": {"X-Test": "preserved"}, + "client_correlation_id": value, + } + assert await session.send("hello", **options) == "canonical-1" + assert await session.send_and_wait("hello", **options, timeout=1) is None + request = SendRequest.from_dict( + {key: val for key, val in expected.items() if key != "sessionId"} + ) + assert (await session.rpc.send(request)).message_id == "canonical-1" + + +@pytest.mark.asyncio +async def test_admission_correlation_is_per_item_not_per_batch(): + first = "01234567-89ab-4cde-8f01-23456789abcd" + second = "abcdef01-2345-4678-9abc-def012345678" + client = Mock() + client.request = AsyncMock(return_value={"messageIds": ["context-id", "plain-id", "reused-id"]}) + session = CopilotSession("session-1", client) + result = await session.rpc.send_messages( + SendMessagesRequest( + messages=[ + SendMessageItem(prompt="context", client_correlation_id=second), + SendMessageItem(prompt="plain"), + SendMessageItem(prompt="reused", client_correlation_id=first), + ] + ) + ) + assert result.message_ids == ["context-id", "plain-id", "reused-id"] + client.request.assert_awaited_once_with( + "session.sendMessages", + { + "sessionId": "session-1", + "messages": [ + {"prompt": "context", "clientCorrelationId": second}, + {"prompt": "plain"}, + {"prompt": "reused", "clientCorrelationId": first}, + ], + }, + ) + + @pytest.mark.parametrize("agent_id", [None, 42, False, b"reviewer"]) def test_agent_message_source_rejects_non_string_ids(agent_id): with pytest.raises(TypeError, match="agent_id must be a string"): diff --git a/python/test_structured_output.py b/python/test_structured_output.py index 2b626bc6bf..0ada60d1cc 100644 --- a/python/test_structured_output.py +++ b/python/test_structured_output.py @@ -67,11 +67,16 @@ def assert_clean(session): @pytest.mark.asyncio -async def test_typed_output_buffers_pre_ack_events_and_infers_schema(): +@pytest.mark.parametrize("correlation", [None, "01234567-89ab-4cde-8f01-23456789abcd"]) +async def test_typed_output_buffers_pre_ack_events_and_infers_schema(correlation): session, client = fake_session([user(), assistant(), idle()]) - result = await session.send_and_wait_typed("inventory", Inventory, timeout=1) + result = await session.send_and_wait_typed( + "inventory", Inventory, timeout=1, client_correlation_id=correlation + ) assert result == Inventory(count=42, color="red") params = client.request.call_args.args[1] + assert params.get("clientCorrelationId") == correlation + assert ("clientCorrelationId" in params) == (correlation is not None) assert params["responseFormat"] == { "type": "json_schema", "jsonSchema": {"name": "response", "strict": True, "schema": Inventory.model_json_schema()}, diff --git a/rust/README.md b/rust/README.md index 36d6ddff80..25e454351b 100644 --- a/rust/README.md +++ b/rust/README.md @@ -857,6 +857,14 @@ session.rpc().send(request).await?; Both paths use ordinary `session.send`. Source does not select a delivery mode or set billing flags; the runtime applies its existing source behavior. `send_and_wait` still completes on `session.idle` and may return `Ok(None)` when no assistant message was emitted. Genuine errors still propagate. +For optional, caller-owned RPC admission metadata, use +`MessageOptions::with_client_correlation_id` or the generated send request's +`client_correlation_id` field. Batch requests carry it per `SendMessageItem`, +never on the batch. The SDK does not generate or validate UUIDs and omits unset +values. See [RPC admission correlation](../docs/features/execution-identity.md#optional-rpc-admission-correlation) +for runtime/release prerequisites, default-off echo behavior, and the public +raw-call seam available to existing Rust SDK 1.0.14 callers. + ### Progress Reporting (`send_and_wait`) For fire-and-forget messaging where you need to block until the agent finishes: diff --git a/rust/src/generated/api_types.rs b/rust/src/generated/api_types.rs index 55e8767fb5..d2cc0d7fb8 100644 --- a/rust/src/generated/api_types.rs +++ b/rust/src/generated/api_types.rs @@ -16142,6 +16142,9 @@ pub struct QueueMoveItemResult { pub struct QueuePendingItems { /// Agent mode stored on this queued entry, as stamped when it was enqueued. Items without an explicit mode report interactive. This is not necessarily the mode that will constrain the turn: a plan or autopilot session applies its own write gate, continuation loop and permission posture to every drained item regardless of the mode stored here. pub agent_mode: SendAgentMode, + /// Caller-owned diagnostic UUID from the exact accepted native session.send or sendMessages item, when RUNTIME_ADMISSION_TRACE_CONTEXT is enabled. Omitted for unsupported or identity-less rows, including snapshot-only mirrors. Not an idempotency key, authorization, or permission to retry; repeated values remain ambiguous. + #[serde(skip_serializing_if = "Option::is_none")] + pub client_correlation_id: Option, /// Human-readable text to display for this queue entry in the UI pub display_text: String, /// Stable opaque id for the canonical queued item. Batch rows share one id. @@ -17307,6 +17310,9 @@ pub struct SendMessageItem { #[doc(hidden)] #[serde(skip_serializing_if = "Option::is_none")] pub(crate) billable: Option, + /// Optional caller-generated diagnostic UUID for this item only, with the same validation and opt-in native echo as session.send.clientCorrelationId. The batch has no request-level correlation value; each item retains its own value, including preceding context messages. Reused values do not deduplicate messages and remain ambiguous. + #[serde(skip_serializing_if = "Option::is_none")] + pub client_correlation_id: Option, /// If provided, this is shown in the timeline instead of `prompt` #[serde(skip_serializing_if = "Option::is_none")] pub display_prompt: Option, @@ -17413,6 +17419,9 @@ pub struct SendRequest { /// If false, this message will not trigger a Premium Request Unit charge. User messages default to billable. #[serde(skip_serializing_if = "Option::is_none")] pub billable: Option, + /// Optional caller-generated diagnostic UUID for this single message. Native sessions with RUNTIME_ADMISSION_TRACE_CONTEXT enabled echo the exact lowercase, hyphenated 36-character UUID on user.message and its existing pending message row. Missing, invalid, disabled, or unsupported metadata is ignored without rejecting the send. Does not change messageId, deduplicate submissions, authorize work, or make an uncertain retry safe. + #[serde(skip_serializing_if = "Option::is_none")] + pub client_correlation_id: Option, /// If provided, this is shown in the timeline instead of `prompt` #[serde(skip_serializing_if = "Option::is_none")] pub display_prompt: Option, diff --git a/rust/src/generated/session_events.rs b/rust/src/generated/session_events.rs index 8fc2122813..0346f92f19 100644 --- a/rust/src/generated/session_events.rs +++ b/rust/src/generated/session_events.rs @@ -2602,6 +2602,9 @@ pub struct UserMessageData { /// Files, selections, or GitHub references attached to the message #[serde(skip_serializing_if = "Option::is_none")] pub attachments: Option>, + /// Exact caller-owned diagnostic UUID carried by this accepted native session.send or sendMessages item when RUNTIME_ADMISSION_TRACE_CONTEXT is enabled. Omitted when input, native ownership, or support is missing. Independent of the canonical messageId; not an idempotency key, authorization, or permission to retry. Multiple messages with the same value remain ambiguous. + #[serde(skip_serializing_if = "Option::is_none")] + pub client_correlation_id: Option, /// The user's message text as displayed in the timeline pub content: String, /// How this message was delivered to the agentic loop relative to loop state (idle-start vs. steering/queued while busy). The timing axis; combine with `source` (origin) for the full picture. Used for telemetry attribution. diff --git a/rust/src/session.rs b/rust/src/session.rs index 4c39a084a2..b57e9d2915 100644 --- a/rust/src/session.rs +++ b/rust/src/session.rs @@ -606,6 +606,9 @@ impl Session { "sessionId": self.id, "prompt": opts.prompt, }); + if let Some(client_correlation_id) = opts.client_correlation_id { + params["clientCorrelationId"] = serde_json::to_value(client_correlation_id)?; + } if let Some(source) = opts.source { params["source"] = serde_json::to_value(source)?; } diff --git a/rust/src/types.rs b/rust/src/types.rs index b3dd113948..cac31dc923 100644 --- a/rust/src/types.rs +++ b/rust/src/types.rs @@ -5535,6 +5535,10 @@ pub struct MessageOptions { pub response_schema: Option, /// The user prompt to send. pub prompt: String, + /// Optional caller-owned diagnostic UUID for this RPC admission. + /// Sent unchanged; supported runtimes echo accepted values only when + /// `RUNTIME_ADMISSION_TRACE_CONTEXT` is enabled. Not an idempotency key. + pub client_correlation_id: Option, /// Optional message provenance. When `None`, the field is omitted, /// preserving the runtime's default for user messages. pub source: Option, @@ -5577,6 +5581,7 @@ impl MessageOptions { pub fn new(prompt: impl Into) -> Self { Self { prompt: prompt.into(), + client_correlation_id: None, response_schema: None, source: None, mode: None, @@ -5590,6 +5595,12 @@ impl MessageOptions { } } + /// Set an opaque caller-owned admission UUID without generating or validating it. + pub fn with_client_correlation_id(mut self, client_correlation_id: impl Into) -> Self { + self.client_correlation_id = Some(client_correlation_id.into()); + self + } + /// Set the message provenance without changing its delivery mode. pub fn with_source(mut self, source: MessageSource) -> Self { self.source = Some(source); diff --git a/rust/tests/api_types_test.rs b/rust/tests/api_types_test.rs index 546adaa510..7f15da9485 100644 --- a/rust/tests/api_types_test.rs +++ b/rust/tests/api_types_test.rs @@ -8,8 +8,8 @@ use github_copilot_sdk::rpc::{ ExtensionStatus, ExtensionsDisableRequest, ExtensionsEnableRequest, FleetStartRequest, FleetStartResult, ModelSetAllowedModelsRequest, ModelSetAllowedModelsResult, ModelSwitchAutoTierRequest, ModelSwitchAutoTierResult, ModelSwitchAutoTierStatus, - QueuePendingItems, QueuePendingItemsKind, SandboxConfig, SendAgentMode, TasksStartAgentRequest, - UnsupportedEnqueueCommandResult, + QueuePendingItems, QueuePendingItemsKind, SandboxConfig, SendAgentMode, SendMessageItem, + SendRequest, TasksStartAgentRequest, UnsupportedEnqueueCommandResult, }; use github_copilot_sdk::session_events::{ PermissionRequest, PermissionRequestedData, SessionEventData, TypedSessionEvent, @@ -194,6 +194,7 @@ fn queue_pending_message_id_uses_camel_case_wire_name() { id: "batch-1".to_string(), kind: QueuePendingItemsKind::Message, message_id: Some("message-2".to_string()), + client_correlation_id: None, }; let serialized = serde_json::to_value(&item).unwrap(); @@ -204,6 +205,48 @@ fn queue_pending_message_id_uses_camel_case_wire_name() { assert_eq!(deserialized.message_id.as_deref(), Some("message-2")); } +#[test] +fn admission_correlation_has_optional_string_round_trip_semantics() { + fn check(base: serde_json::Value) { + for value in [ + None, + Some("01234567-89ab-4cde-8f01-23456789abcd"), + Some("01234567-89AB-4CDE-8F01-23456789ABCD"), + Some("not-a-uuid"), + Some(""), + ] { + let mut wire = base.clone(); + if let Some(value) = value { + wire["clientCorrelationId"] = serde_json::json!(value); + } + let decoded: T = serde_json::from_value(wire.clone()).unwrap(); + assert_eq!(serde_json::to_value(decoded).unwrap(), wire); + wire["futureField"] = serde_json::json!({"enabled": true}); + let decoded: T = serde_json::from_value(wire.clone()).unwrap(); + wire.as_object_mut().unwrap().remove("futureField"); + assert_eq!(serde_json::to_value(decoded).unwrap(), wire); + } + let mut explicit_null = base.clone(); + explicit_null["clientCorrelationId"] = serde_json::Value::Null; + let decoded: T = serde_json::from_value(explicit_null).unwrap(); + assert_eq!(serde_json::to_value(decoded).unwrap(), base); + let mut invalid_type = base; + invalid_type["clientCorrelationId"] = serde_json::json!(42); + assert!(serde_json::from_value::(invalid_type).is_err()); + } + + check::(serde_json::json!({"prompt": "hello"})); + check::(serde_json::json!({"prompt": "hello"})); + check::(serde_json::json!({ + "id": "queue-1", "messageId": "server-message-1", "kind": "message", + "displayText": "hello", "agentMode": "interactive" + })); + check::(serde_json::json!({ + "content": "hello", "messageId": "server-message-1", + "interactionId": "agent-loop-1", "turnId": "0" + })); +} + #[test] fn queue_pending_message_id_is_optional_for_older_hosts() { let item: QueuePendingItems = serde_json::from_value(serde_json::json!({ diff --git a/rust/tests/session_test.rs b/rust/tests/session_test.rs index 920eed4561..98230c7d2f 100644 --- a/rust/tests/session_test.rs +++ b/rust/tests/session_test.rs @@ -19,7 +19,8 @@ use github_copilot_sdk::handler::{ }; use github_copilot_sdk::rpc::{ CanvasProviderInvokeActionRequest, CanvasProviderOpenRequest, CanvasProviderOpenResult, - ModelSetAllowedModelsRequest, OpenCanvasInstance, SendAgentMode, SendMode, SendRequest, + ModelSetAllowedModelsRequest, OpenCanvasInstance, SendAgentMode, SendMessagesRequest, SendMode, + SendRequest, }; use github_copilot_sdk::session_events::{ ManagedSettingsResolvedSource, McpOauthRequiredData, ReasoningSummary, SessionLimitsConfig, @@ -2080,6 +2081,207 @@ async fn send_injects_session_id() { timeout(TIMEOUT, handle).await.unwrap().unwrap().unwrap(); } +#[tokio::test] +async fn admission_correlation_is_opt_in_and_preserves_existing_send_options() { + let (session, mut server) = create_session_pair().await; + let session = Arc::new(session); + + for correlation in [ + None, + Some("01234567-89ab-4cde-8f01-23456789abcd"), + Some("01234567-89AB-4CDE-8F01-23456789ABCD"), + Some("not-a-uuid"), + Some(""), + ] { + for typed_rpc in [false, true] { + let mut options = MessageOptions::new("hello"); + assert!(options.client_correlation_id.is_none()); + if let Some(value) = correlation { + options = options.with_client_correlation_id(value); + } + assert_eq!( + options.clone().client_correlation_id.as_deref(), + correlation + ); + let mut expected = serde_json::json!({ + "sessionId": server.session_id, "prompt": "hello", "source": "system", + "mode": "enqueue", "displayPrompt": "display", "agentMode": "plan", + "requestHeaders": {"X-Test": "preserved"}, + "traceparent": "00-11111111111111111111111111111111-2222222222222222-01", + "tracestate": "vendor=preserved" + }); + if let Some(value) = correlation { + expected["clientCorrelationId"] = serde_json::json!(value); + } + let handle = tokio::spawn({ + let session = session.clone(); + let mut request = expected.clone(); + request.as_object_mut().unwrap().remove("sessionId"); + async move { + if typed_rpc { + session + .rpc() + .send(serde_json::from_value(request).unwrap()) + .await + .map(|result| result.message_id) + } else { + session + .send( + options + .with_source(MessageSource::System) + .with_mode(DeliveryMode::Enqueue) + .with_agent_mode(AgentMode::Plan) + .with_display_prompt("display") + .with_request_headers(HashMap::from([( + "X-Test".into(), + "preserved".into(), + )])) + .with_traceparent( + "00-11111111111111111111111111111111-2222222222222222-01", + ) + .with_tracestate("vendor=preserved"), + ) + .await + } + } + }); + let request = timeout(TIMEOUT, server.read_request()).await.unwrap(); + assert_eq!(request["method"], "session.send"); + assert_eq!(request["params"], expected); + server + .respond( + &request, + serde_json::json!({"messageId": "server-message-1", "future": true}), + ) + .await; + assert_eq!( + timeout(TIMEOUT, handle).await.unwrap().unwrap().unwrap(), + "server-message-1" + ); + } + } +} + +#[tokio::test] +async fn admission_correlation_stays_per_item_across_concurrent_sends_and_batch() { + let (session, mut server) = create_session_pair().await; + let session = Arc::new(session); + const FIRST: &str = "01234567-89ab-4cde-8f01-23456789abcd"; + const SECOND: &str = "abcdef01-2345-4678-9abc-def012345678"; + let handle = tokio::spawn({ + let session = session.clone(); + async move { + let batch: SendMessagesRequest = serde_json::from_value(serde_json::json!({ + "messages": [ + {"prompt": "context", "clientCorrelationId": SECOND}, + {"prompt": "plain"}, + {"prompt": "reused", "clientCorrelationId": FIRST} + ] + })) + .unwrap(); + let rpc = session.rpc(); + tokio::join!( + session.send(MessageOptions::new("first").with_client_correlation_id(FIRST)), + session.send(MessageOptions::new("second").with_client_correlation_id(SECOND)), + rpc.send_messages(batch) + ) + } + }); + + let mut requests = Vec::new(); + for _ in 0..3 { + requests.push(timeout(TIMEOUT, server.read_request()).await.unwrap()); + } + for request in requests.into_iter().rev() { + let params = &request["params"]; + assert_eq!(params["sessionId"], server.session_id); + if request["method"] == "session.sendMessages" { + assert!(params.get("clientCorrelationId").is_none()); + assert_eq!( + params["messages"], + serde_json::json!([ + {"prompt": "context", "clientCorrelationId": SECOND}, + {"prompt": "plain"}, + {"prompt": "reused", "clientCorrelationId": FIRST} + ]) + ); + server + .respond( + &request, + serde_json::json!({"messageIds": ["context-id", "plain-id", "reused-id"]}), + ) + .await; + } else { + assert_eq!(request["method"], "session.send"); + let (correlation, message_id) = if params["prompt"] == "first" { + (FIRST, "first-id") + } else { + assert_eq!(params["prompt"], "second"); + (SECOND, "second-id") + }; + assert_eq!(params["clientCorrelationId"], correlation); + server + .respond(&request, serde_json::json!({"messageId": message_id})) + .await; + } + } + let (first, second, batch) = timeout(TIMEOUT, handle).await.unwrap().unwrap(); + assert_eq!(first.unwrap(), "first-id"); + assert_eq!(second.unwrap(), "second-id"); + assert_eq!( + batch.unwrap().message_ids, + ["context-id", "plain-id", "reused-id"] + ); +} + +#[tokio::test] +async fn admission_correlation_event_and_pending_rows_preserve_canonical_message_ids() { + use github_copilot_sdk::session_events::UserMessageData; + + let (session, mut server) = create_session_pair().await; + let session = Arc::new(session); + let mut events = session.subscribe(); + const CORRELATION: &str = "01234567-89ab-4cde-8f01-23456789abcd"; + for correlation in [Some(CORRELATION), None] { + let mut data = serde_json::json!({ + "content": "hello", "messageId": "canonical-id", + "interactionId": "agent-loop-id", "turnId": "0", "futureField": {"x": true} + }); + if let Some(value) = correlation { + data["clientCorrelationId"] = serde_json::json!(value); + } + server.send_event("user.message", data.clone()).await; + let event = timeout(TIMEOUT, events.recv()).await.unwrap().unwrap(); + assert_eq!(event.data, data); + let typed = event.typed_data::().unwrap(); + assert_eq!(typed.client_correlation_id.as_deref(), correlation); + assert_eq!(typed.message_id.as_deref(), Some("canonical-id")); + assert_eq!(typed.interaction_id.as_deref(), Some("agent-loop-id")); + assert_ne!(event.id, "canonical-id"); + } + + let handle = tokio::spawn({ + let session = session.clone(); + async move { session.rpc().queue().pending_items().await } + }); + let request = timeout(TIMEOUT, server.read_request()).await.unwrap(); + assert_eq!(request["method"], "session.queue.pendingItems"); + server.respond(&request, serde_json::json!({"steeringMessages": [], "items": [ + {"id": "queue-id", "messageId": "canonical-id", "clientCorrelationId": CORRELATION, + "kind": "message", "displayText": "hello", "agentMode": "interactive", "futureField": true}, + {"id": "legacy-queue-id", "kind": "message", "displayText": "older", "agentMode": "interactive"} + ]})).await; + let pending = timeout(TIMEOUT, handle).await.unwrap().unwrap().unwrap(); + assert_eq!( + pending.items[0].client_correlation_id.as_deref(), + Some(CORRELATION) + ); + assert_eq!(pending.items[0].message_id.as_deref(), Some("canonical-id")); + assert_eq!(pending.items[0].id, "queue-id"); + assert!(pending.items[1].client_correlation_id.is_none()); + assert!(pending.items[1].message_id.is_none()); +} + #[test] fn message_options_source_is_opt_in() { let prompt = "hello".to_string(); diff --git a/scripts/codegen/csharp.ts b/scripts/codegen/csharp.ts index aa7399290a..b8edb93aab 100644 --- a/scripts/codegen/csharp.ts +++ b/scripts/codegen/csharp.ts @@ -2358,6 +2358,17 @@ function emitSessionMethod(key: string, method: RpcMethod, lines: string[], clas escapeDescription: false, }); + // Preserve existing data positions; retain the cancellation position with an overload below. + if (method.rpcMethod === "session.send") { + const index = parameterDescriptions.findIndex(({ name }) => name === "clientCorrelationId"); + if (index !== -1) { + const [parameter] = sigParams.splice(index, 1); + const [description] = parameterDescriptions.splice(index, 1); + sigParams.splice(sigParams.length - 1, 0, parameter); + parameterDescriptions.splice(parameterDescriptions.length - 1, 0, description); + } + } + const taskType = !isVoidSchema(resultSchema) ? `Task<${resultClassName}>` : "Task"; const localRequestName = localRequestVariableName(paramEntries, useRequestParameter); lines.push(""); @@ -2379,6 +2390,20 @@ function emitSessionMethod(key: string, method: RpcMethod, lines: string[], clas } else { lines.push(`${indent} await CopilotClient.InvokeRpcAsync(_session.Rpc, "${method.rpcMethod}", [${localRequestName}], cancellationToken);`, `${indent}}`); } + + if (method.rpcMethod === "session.send" && parameterDescriptions.some(({ name }) => name === "clientCorrelationId")) { + // Retain the old CLR signature; defaults on the extended overload preserve source calls. + const previousIndexes = parameterDescriptions.flatMap(({ name }, index) => name === "clientCorrelationId" ? [] : [index]); + const previousParameters = previousIndexes.map((index) => sigParams[index].replace(/ = (?:null|default)$/, "")); + const previousDescriptions = previousIndexes.map((index) => parameterDescriptions[index]); + const previousArguments = previousDescriptions.filter(({ name }) => name !== "cancellationToken").map(({ name }) => name); + lines.push(""); + pushRpcMethodXmlDocs(lines, method, indent, previousDescriptions, resultSchema); + if (method.stability === "experimental" && !groupExperimental) pushExperimentalAttribute(lines, indent); + if (method.deprecated && !groupDeprecated) pushObsoleteAttributes(lines, indent); + lines.push(`${indent}${methodVisibility} ${taskType} ${methodName}Async(${previousParameters.join(", ")})`); + lines.push(`${indent} => ${methodName}Async(${previousArguments.join(", ")}, clientCorrelationId: null, cancellationToken: cancellationToken);`); + } } function emitSessionApiClass(className: string, node: Record, classes: string[]): string[] { From 1ef6e91f3d271506b84eee0534648d70a46e410a Mon Sep 17 00:00:00 2001 From: Michael Dinh Date: Fri, 25 Sep 2026 22:23:00 -0400 Subject: [PATCH 13/25] feat: expose worker causality across SDKs Consume the sealed runtime worker-causality v1 contract across all six public SDK projections. Preserve existing admission and constructor compatibility, tolerate invalid optional diagnostics without changing product handling, and document the actual-Turn and frontend boundaries. Runtime-Seal: ba93cab47430a4df6a8b6a83244676d9cc6a7c64 Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> --- docs/features/execution-identity.md | 16 + dotnet/src/Generated/Rpc.cs | 16 + dotnet/src/Generated/SessionEvents.cs | 440 ++++++++++ dotnet/src/WorkerCausalityConverter.cs | 221 +++++ dotnet/test/GitHub.Copilot.SDK.Test.csproj | 1 + dotnet/test/Unit/WorkerCausalityTests.cs | 72 ++ .../diagnosticmetadata/worker_causality.go | 192 +++++ go/rpc/zrpc.go | 141 ++++ go/rpc/zrpc_encoding.go | 19 + go/rpc/zsession_encoding.go | 29 + go/rpc/zsession_events.go | 6 + go/worker_causality_test.go | 117 +++ go/zsession_events.go | 22 + java/scripts/codegen/java.test.ts | 70 ++ java/scripts/codegen/java.ts | 62 +- .../generated/AssistantTurnStartEvent.java | 7 + .../generated/SystemNotificationEvent.java | 7 + .../copilot/generated/UserMessageEvent.java | 12 +- .../copilot/generated/WorkerAdmission.java | 34 + .../generated/WorkerAdmissionKind.java | 35 + .../generated/WorkerBridgeObservation.java | 29 + .../copilot/generated/WorkerCausality.java | 36 + .../generated/WorkerEventReference.java | 36 + .../copilot/generated/WorkerEventType.java | 41 + .../github/copilot/generated/WorkerInput.java | 35 + .../generated/WorkerNotificationMode.java | 35 + .../WorkerNotificationReference.java | 32 + .../WorkerObservationProvenance.java | 35 + .../copilot/generated/WorkerSource.java | 38 + .../rpc/SessionTasksSendMessageResult.java | 7 + .../generated/rpc/WorkerAdmission.java | 34 + .../generated/rpc/WorkerAdmissionKind.java | 35 + .../rpc/WorkerBridgeObservation.java | 29 + .../generated/rpc/WorkerCausality.java | 36 + .../generated/rpc/WorkerEventReference.java | 36 + .../generated/rpc/WorkerEventType.java | 41 + .../copilot/generated/rpc/WorkerInput.java | 35 + .../generated/rpc/WorkerNotificationMode.java | 35 + .../rpc/WorkerNotificationReference.java | 32 + .../rpc/WorkerObservationProvenance.java | 35 + .../copilot/generated/rpc/WorkerSource.java | 38 + .../copilot/WorkerCausalityDeserializer.java | 185 ++++ .../github/copilot/WorkerCausalityTest.java | 66 ++ nodejs/src/generated/rpc.ts | 8 +- nodejs/src/generated/session-events.ts | 137 +++ nodejs/src/session.ts | 10 +- nodejs/src/workerCausality.ts | 142 ++++ nodejs/test/csharp-codegen.test.ts | 40 + nodejs/test/worker-causality.test.ts | 114 +++ python/copilot/_worker_causality.py | 166 ++++ python/copilot/generated/rpc.py | 13 +- python/copilot/generated/session_events.py | 278 ++++++ python/test_worker_causality.py | 51 ++ rust/src/generated/api_types.rs | 16 +- rust/src/generated/session_events.rs | 188 +++++ rust/src/lib.rs | 1 + rust/src/worker_causality.rs | 185 ++++ rust/tests/worker_causality_test.rs | 78 ++ scripts/codegen/csharp.ts | 6 + scripts/codegen/go.ts | 12 +- scripts/codegen/python.ts | 10 + scripts/codegen/rust.ts | 4 +- scripts/codegen/typescript.ts | 9 +- test/worker-causality.json | 795 ++++++++++++++++++ 64 files changed, 4715 insertions(+), 28 deletions(-) create mode 100644 dotnet/src/WorkerCausalityConverter.cs create mode 100644 dotnet/test/Unit/WorkerCausalityTests.cs create mode 100644 go/internal/diagnosticmetadata/worker_causality.go create mode 100644 go/worker_causality_test.go create mode 100644 java/sdk/src/generated/java/com/github/copilot/generated/WorkerAdmission.java create mode 100644 java/sdk/src/generated/java/com/github/copilot/generated/WorkerAdmissionKind.java create mode 100644 java/sdk/src/generated/java/com/github/copilot/generated/WorkerBridgeObservation.java create mode 100644 java/sdk/src/generated/java/com/github/copilot/generated/WorkerCausality.java create mode 100644 java/sdk/src/generated/java/com/github/copilot/generated/WorkerEventReference.java create mode 100644 java/sdk/src/generated/java/com/github/copilot/generated/WorkerEventType.java create mode 100644 java/sdk/src/generated/java/com/github/copilot/generated/WorkerInput.java create mode 100644 java/sdk/src/generated/java/com/github/copilot/generated/WorkerNotificationMode.java create mode 100644 java/sdk/src/generated/java/com/github/copilot/generated/WorkerNotificationReference.java create mode 100644 java/sdk/src/generated/java/com/github/copilot/generated/WorkerObservationProvenance.java create mode 100644 java/sdk/src/generated/java/com/github/copilot/generated/WorkerSource.java create mode 100644 java/sdk/src/generated/java/com/github/copilot/generated/rpc/WorkerAdmission.java create mode 100644 java/sdk/src/generated/java/com/github/copilot/generated/rpc/WorkerAdmissionKind.java create mode 100644 java/sdk/src/generated/java/com/github/copilot/generated/rpc/WorkerBridgeObservation.java create mode 100644 java/sdk/src/generated/java/com/github/copilot/generated/rpc/WorkerCausality.java create mode 100644 java/sdk/src/generated/java/com/github/copilot/generated/rpc/WorkerEventReference.java create mode 100644 java/sdk/src/generated/java/com/github/copilot/generated/rpc/WorkerEventType.java create mode 100644 java/sdk/src/generated/java/com/github/copilot/generated/rpc/WorkerInput.java create mode 100644 java/sdk/src/generated/java/com/github/copilot/generated/rpc/WorkerNotificationMode.java create mode 100644 java/sdk/src/generated/java/com/github/copilot/generated/rpc/WorkerNotificationReference.java create mode 100644 java/sdk/src/generated/java/com/github/copilot/generated/rpc/WorkerObservationProvenance.java create mode 100644 java/sdk/src/generated/java/com/github/copilot/generated/rpc/WorkerSource.java create mode 100644 java/sdk/src/main/java/com/github/copilot/WorkerCausalityDeserializer.java create mode 100644 java/sdk/src/test/java/com/github/copilot/WorkerCausalityTest.java create mode 100644 nodejs/src/workerCausality.ts create mode 100644 nodejs/test/worker-causality.test.ts create mode 100644 python/copilot/_worker_causality.py create mode 100644 python/test_worker_causality.py create mode 100644 rust/src/worker_causality.rs create mode 100644 rust/tests/worker_causality_test.rs create mode 100644 test/worker-causality.json diff --git a/docs/features/execution-identity.md b/docs/features/execution-identity.md index 2b04dac52b..524face2be 100644 --- a/docs/features/execution-identity.md +++ b/docs/features/execution-identity.md @@ -56,6 +56,14 @@ An application Turn, including an AHP Turn, is a separate identity. AHP Turn IDs are opaque and session-scoped; they are not required to be UUIDs and must never be equated with runtime `turnId`. +A Turn identity exists only after an actual dispatch receipt or accepted host +action supplies that Turn ID. Ownership claims, active-client selection, +pre-dispatch processing, callback resolution, late acknowledgements and host +acknowledgements alone are not Turn or transport evidence. A pre-dispatch +failure therefore has no Turn identity, and a late ownership acknowledgement +after reconnect cannot retroactively mark a successful submission or a Turn +failure. + Runtime `turnId` is a loop counter represented as a string. Counters can restart across invocations. Interaction IDs can be reused for system-sourced work and no-user HMAC sessions. Even `(sessionId, interactionId, turnId)` is not a @@ -206,6 +214,14 @@ Reusing a UUID does not deduplicate submissions; several admissions with the same value remain ambiguous. Retain observed canonical message IDs and existing application command/Turn ownership. Missing evidence must remain missing. +Admission correlation is not a frontend outcome. The SDK does not infer a +displayed submission error, preserved draft, recovery result, browser attempt, +surface attribution or physical dispatch from protocol acceptance. Pending +application recovery must be generation-bound and invalidated by a +session/client-generation change or Stop. The current frontend association +contract is github/github-ui#35143 at +`4c2a7dfd381a57206995041ba4e4b1dbd4600e83`; backend carriers remain generic. + ### Release prerequisites and the existing Rust raw API These typed additions come from canonical runtime source diff --git a/dotnet/src/Generated/Rpc.cs b/dotnet/src/Generated/Rpc.cs index ef61d59465..825c9d8e72 100644 --- a/dotnet/src/Generated/Rpc.cs +++ b/dotnet/src/Generated/Rpc.cs @@ -12179,6 +12179,11 @@ public sealed class TasksSendMessageResult /// Whether the message was successfully delivered or steered. [JsonPropertyName("sent")] public bool Sent { get; set; } + + /// Optional exact queue admission receipt on sent=true only. No implicit event or execution-success claim. + [JsonConverter(typeof(WorkerCausalityConverter))] + [JsonPropertyName("workerCausality")] + public WorkerCausality? WorkerCausality { get; set; } } /// Identifier of the target agent task, message content, and optional sender agent ID. @@ -43983,6 +43988,17 @@ public static void RegisterClientGlobalApiHandlers(JsonRpc rpc, ClientGlobalApiH [JsonSerializable(typeof(GitHub.Copilot.UserToolSessionApprovalRead), TypeInfoPropertyName = "SessionEventsUserToolSessionApprovalRead")] [JsonSerializable(typeof(GitHub.Copilot.UserToolSessionApprovalWrite), TypeInfoPropertyName = "SessionEventsUserToolSessionApprovalWrite")] [JsonSerializable(typeof(GitHub.Copilot.Verbosity), TypeInfoPropertyName = "SessionEventsVerbosity")] +[JsonSerializable(typeof(GitHub.Copilot.WorkerAdmission), TypeInfoPropertyName = "SessionEventsWorkerAdmission")] +[JsonSerializable(typeof(GitHub.Copilot.WorkerAdmissionKind), TypeInfoPropertyName = "SessionEventsWorkerAdmissionKind")] +[JsonSerializable(typeof(GitHub.Copilot.WorkerBridgeObservation), TypeInfoPropertyName = "SessionEventsWorkerBridgeObservation")] +[JsonSerializable(typeof(GitHub.Copilot.WorkerCausality), TypeInfoPropertyName = "SessionEventsWorkerCausality")] +[JsonSerializable(typeof(GitHub.Copilot.WorkerEventReference), TypeInfoPropertyName = "SessionEventsWorkerEventReference")] +[JsonSerializable(typeof(GitHub.Copilot.WorkerEventType), TypeInfoPropertyName = "SessionEventsWorkerEventType")] +[JsonSerializable(typeof(GitHub.Copilot.WorkerInput), TypeInfoPropertyName = "SessionEventsWorkerInput")] +[JsonSerializable(typeof(GitHub.Copilot.WorkerNotificationMode), TypeInfoPropertyName = "SessionEventsWorkerNotificationMode")] +[JsonSerializable(typeof(GitHub.Copilot.WorkerNotificationReference), TypeInfoPropertyName = "SessionEventsWorkerNotificationReference")] +[JsonSerializable(typeof(GitHub.Copilot.WorkerObservationProvenance), TypeInfoPropertyName = "SessionEventsWorkerObservationProvenance")] +[JsonSerializable(typeof(GitHub.Copilot.WorkerSource), TypeInfoPropertyName = "SessionEventsWorkerSource")] [JsonSerializable(typeof(GitHub.Copilot.WorkingDirectoryContext), TypeInfoPropertyName = "SessionEventsWorkingDirectoryContext")] [JsonSerializable(typeof(GitHub.Copilot.WorkingDirectoryContextHostType), TypeInfoPropertyName = "SessionEventsWorkingDirectoryContextHostType")] [JsonSerializable(typeof(GitHub.Copilot.WorkspaceFileChangedOperation), TypeInfoPropertyName = "SessionEventsWorkspaceFileChangedOperation")] diff --git a/dotnet/src/Generated/SessionEvents.cs b/dotnet/src/Generated/SessionEvents.cs index ab302aca91..d44c6b993e 100644 --- a/dotnet/src/Generated/SessionEvents.cs +++ b/dotnet/src/Generated/SessionEvents.cs @@ -3635,6 +3635,12 @@ public sealed partial class UserMessageData [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] [JsonPropertyName("turnId")] public string? TurnId { get; set; } + + /// Optional worker admission observations; self identity requires the matching enclosing message and agent. + [JsonConverter(typeof(WorkerCausalityConverter))] + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] + [JsonPropertyName("workerCausality")] + public WorkerCausality? WorkerCausality { get; set; } } /// Empty payload; the event signals that the pending message queue has changed. @@ -3658,6 +3664,12 @@ public sealed partial class AssistantTurnStartData /// Identifier for this turn within the agentic loop, typically a stringified turn number. [JsonPropertyName("turnId")] public required string TurnId { get; set; } + + /// Optional bounded worker observations. Missing or invalid metadata is unavailable, not known-empty. + [JsonConverter(typeof(WorkerCausalityConverter))] + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] + [JsonPropertyName("workerCausality")] + public WorkerCausality? WorkerCausality { get; set; } } /// Metadata for an additional model inference attempt within an existing assistant turn. @@ -5617,6 +5629,12 @@ public sealed partial class SystemNotificationData [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] [JsonPropertyName("responsesReasoning")] public ResponsesReasoning? ResponsesReasoning { get; set; } + + /// Optional owned worker notification observations; an omitted notification event refers only to this occurrence. + [JsonConverter(typeof(WorkerCausalityConverter))] + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] + [JsonPropertyName("workerCausality")] + public WorkerCausality? WorkerCausality { get; set; } } /// Permission request notification requiring client approval with request details. @@ -8046,6 +8064,168 @@ public partial class Attachment } +/// Exact observed event identity. No private registration generation or execution handle. +/// Nested data type for WorkerEventReference. +public sealed partial class WorkerEventReference +{ + /// Actual event agent scope, absent for a root occurrence. + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] + [JsonPropertyName("agentId")] + public string? AgentId { get; set; } + + /// Actual event occurrence UUID; copied without normalization. + [JsonPropertyName("eventId")] + public required Guid EventId { get; set; } + + /// Type of the observed occurrence. + [JsonPropertyName("eventType")] + public required WorkerEventType EventType { get; set; } + + /// Explicit provenance of this reference. + [JsonPropertyName("provenance")] + public required WorkerObservationProvenance Provenance { get; set; } + + /// Actual runtime session scope. + [JsonPropertyName("sessionId")] + public required string SessionId { get; set; } +} + +/// An observed worker admission, not a claim that execution succeeded. +/// Nested data type for WorkerAdmission. +public sealed partial class WorkerAdmission +{ + /// Actual AHP participant Turn UUID, not a native turn counter or provenance signal. + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] + [JsonPropertyName("ahpTurnId")] + public Guid? AhpTurnId { get; set; } + + /// May be omitted only for the matching current worker user.message. + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] + [JsonPropertyName("event")] + public WorkerEventReference? Event { get; set; } + + /// The producer's admission kind. + [JsonPropertyName("kind")] + public required WorkerAdmissionKind Kind { get; set; } + + /// Canonical logical message identity, independent of queueItemId. + [JsonPropertyName("messageId")] + public required string MessageId { get; set; } +} + +/// One indivisible source-to-reported bridge observation, not a root alias. +/// Nested data type for WorkerBridgeObservation. +public sealed partial class WorkerBridgeObservation +{ + /// Full occurrence actually emitted by that bridge. + [JsonPropertyName("reported")] + public required WorkerEventReference Reported { get; set; } + + /// Full event received by this bridge. + [JsonPropertyName("source")] + public required WorkerEventReference Source { get; set; } +} + +/// Exact accepted worker input, distinct from a message, event, caller correlation or Turn. +/// Nested data type for WorkerInput. +public sealed partial class WorkerInput +{ + /// Actual recipient task. + [JsonPropertyName("agentId")] + public required string AgentId { get; set; } + + /// UUID allocated for this queue item by the admitting producer. + [JsonPropertyName("queueItemId")] + public required Guid QueueItemId { get; set; } + + /// Original invoking occurrence, never replaced by a reported/root alias. + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] + [JsonPropertyName("sender")] + public WorkerEventReference? Sender { get; set; } + + /// Exact captured edges in producer order. Requires sender; at most 32 whole pairs. + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] + [JsonPropertyName("senderBridges")] + public WorkerBridgeObservation[]? SenderBridges { get; set; } +} + +/// Exact delivery and optional occurrence of a consumed worker notification. +/// Nested data type for WorkerNotificationReference. +public sealed partial class WorkerNotificationReference +{ + /// Actual notificationDeliveryId UUID. + [JsonPropertyName("deliveryId")] + public required Guid DeliveryId { get; set; } + + /// May be omitted only on the matching current system.notification. + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] + [JsonPropertyName("event")] + public WorkerEventReference? Event { get; set; } + + /// Actual consumption mode. + [JsonPropertyName("mode")] + public required WorkerNotificationMode Mode { get; set; } +} + +/// One captured source at this placement and observation boundary. +/// Nested data type for WorkerSource. +public sealed partial class WorkerSource +{ + /// Actual admissions in capture order; at most 32. + [JsonPropertyName("admissions")] + public required WorkerAdmission[] Admissions { get; set; } + + /// Exact already-open iteration when an immediate notification was consumed. + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] + [JsonPropertyName("admittedDuring")] + public WorkerEventReference? AdmittedDuring { get; set; } + + /// Applicable observations were all captured here, never work completion or success. + [JsonPropertyName("captureComplete")] + public required bool CaptureComplete { get; set; } + + /// Actual completion emission only. Absence is not an execution outcome. + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] + [JsonPropertyName("completion")] + public WorkerEventReference? Completion { get; set; } + + /// Indivisible accepted input identity. + [JsonPropertyName("input")] + public required WorkerInput Input { get; set; } + + /// Actual consumed notification, when observed. + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] + [JsonPropertyName("notification")] + public WorkerNotificationReference? Notification { get; set; } +} + +/// +/// Optional v1 worker diagnostics. The compact UTF-8 {"workerCausality":value} +/// must fit 4096 bytes. Ignore invalid/unknown/oversize metadata, not the product event. +/// +/// Nested data type for WorkerCausality. +public sealed partial class WorkerCausality +{ + /// + /// Complete placement-aware observed capture, not global causality or execution success. + /// Empty true requires explicit native invocation attestation. + /// + [JsonPropertyName("captureComplete")] + public required bool CaptureComplete { get; set; } + + /// Provenance of this enclosing observation; never inferred from other fields. + [JsonPropertyName("observationProvenance")] + public required WorkerObservationProvenance ObservationProvenance { get; set; } + + /// Sources in capture order, at most 32. Absent/unknown is not known-empty. + [JsonPropertyName("sources")] + public required WorkerSource[] Sources { get; set; } + + /// Supported version, exactly 1. + [JsonPropertyName("version")] + public required long Version { get; set; } +} + /// Internal durable terminal request staged by a HydraFusion phase until an idempotent final commit selects it. /// Nested data type for FusionStagedTerminal. [Experimental(Diagnostics.Experimental)] @@ -15129,6 +15309,259 @@ public override void Write(Utf8JsonWriter writer, UserMessageDelivery value, Jso } } +/// Producer of an observation, not the execution location of every referenced source. +[JsonConverter(typeof(Converter))] +[DebuggerDisplay("{Value,nq}")] +public readonly struct WorkerObservationProvenance : IEquatable +{ + private readonly string? _value; + + /// Initializes a new instance of the struct. + /// The value to associate with this . + [JsonConstructor] + public WorkerObservationProvenance(string value) + { + ArgumentException.ThrowIfNullOrWhiteSpace(value); + _value = value; + } + + /// Gets the value associated with this . + public string Value => _value ?? string.Empty; + + /// Gets the native value. + public static WorkerObservationProvenance Native { get; } = new("native"); + + /// Gets the ahp_coordinator value. + public static WorkerObservationProvenance AhpCoordinator { get; } = new("ahp_coordinator"); + + /// Returns a value indicating whether two instances are equivalent. + public static bool operator ==(WorkerObservationProvenance left, WorkerObservationProvenance right) => left.Equals(right); + + /// Returns a value indicating whether two instances are not equivalent. + public static bool operator !=(WorkerObservationProvenance left, WorkerObservationProvenance right) => !(left == right); + + /// + public override bool Equals(object? obj) => obj is WorkerObservationProvenance other && Equals(other); + + /// + public bool Equals(WorkerObservationProvenance other) => string.Equals(Value, other.Value, StringComparison.OrdinalIgnoreCase); + + /// + public override int GetHashCode() => StringComparer.OrdinalIgnoreCase.GetHashCode(Value); + + /// + public override string ToString() => Value; + + /// Provides a for serializing instances. + [EditorBrowsable(EditorBrowsableState.Never)] + public sealed class Converter : JsonConverter + { + /// + public override WorkerObservationProvenance Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) + { + return new(GeneratedStringEnumJson.ReadValue(ref reader, typeToConvert)); + } + + /// + public override void Write(Utf8JsonWriter writer, WorkerObservationProvenance value, JsonSerializerOptions options) + { + GeneratedStringEnumJson.WriteValue(writer, value.Value, typeof(WorkerObservationProvenance)); + } + } +} + +/// Supported observed occurrences. Chronological parentId is not a causal reference. +[JsonConverter(typeof(Converter))] +[DebuggerDisplay("{Value,nq}")] +public readonly struct WorkerEventType : IEquatable +{ + private readonly string? _value; + + /// Initializes a new instance of the struct. + /// The value to associate with this . + [JsonConstructor] + public WorkerEventType(string value) + { + ArgumentException.ThrowIfNullOrWhiteSpace(value); + _value = value; + } + + /// Gets the value associated with this . + public string Value => _value ?? string.Empty; + + /// Gets the tool.execution_start value. + public static WorkerEventType ToolExecutionStart { get; } = new("tool.execution_start"); + + /// Gets the user.message value. + public static WorkerEventType UserMessage { get; } = new("user.message"); + + /// Gets the subagent.completed value. + public static WorkerEventType SubagentCompleted { get; } = new("subagent.completed"); + + /// Gets the system.notification value. + public static WorkerEventType SystemNotification { get; } = new("system.notification"); + + /// Gets the assistant.turn_start value. + public static WorkerEventType AssistantTurnStart { get; } = new("assistant.turn_start"); + + /// Returns a value indicating whether two instances are equivalent. + public static bool operator ==(WorkerEventType left, WorkerEventType right) => left.Equals(right); + + /// Returns a value indicating whether two instances are not equivalent. + public static bool operator !=(WorkerEventType left, WorkerEventType right) => !(left == right); + + /// + public override bool Equals(object? obj) => obj is WorkerEventType other && Equals(other); + + /// + public bool Equals(WorkerEventType other) => string.Equals(Value, other.Value, StringComparison.OrdinalIgnoreCase); + + /// + public override int GetHashCode() => StringComparer.OrdinalIgnoreCase.GetHashCode(Value); + + /// + public override string ToString() => Value; + + /// Provides a for serializing instances. + [EditorBrowsable(EditorBrowsableState.Never)] + public sealed class Converter : JsonConverter + { + /// + public override WorkerEventType Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) + { + return new(GeneratedStringEnumJson.ReadValue(ref reader, typeToConvert)); + } + + /// + public override void Write(Utf8JsonWriter writer, WorkerEventType value, JsonSerializerOptions options) + { + GeneratedStringEnumJson.WriteValue(writer, value.Value, typeof(WorkerEventType)); + } + } +} + +/// Why this exact worker admission was made. +[JsonConverter(typeof(Converter))] +[DebuggerDisplay("{Value,nq}")] +public readonly struct WorkerAdmissionKind : IEquatable +{ + private readonly string? _value; + + /// Initializes a new instance of the struct. + /// The value to associate with this . + [JsonConstructor] + public WorkerAdmissionKind(string value) + { + ArgumentException.ThrowIfNullOrWhiteSpace(value); + _value = value; + } + + /// Gets the value associated with this . + public string Value => _value ?? string.Empty; + + /// Gets the queued_input value. + public static WorkerAdmissionKind QueuedInput { get; } = new("queued_input"); + + /// Gets the system_continuation value. + public static WorkerAdmissionKind SystemContinuation { get; } = new("system_continuation"); + + /// Returns a value indicating whether two instances are equivalent. + public static bool operator ==(WorkerAdmissionKind left, WorkerAdmissionKind right) => left.Equals(right); + + /// Returns a value indicating whether two instances are not equivalent. + public static bool operator !=(WorkerAdmissionKind left, WorkerAdmissionKind right) => !(left == right); + + /// + public override bool Equals(object? obj) => obj is WorkerAdmissionKind other && Equals(other); + + /// + public bool Equals(WorkerAdmissionKind other) => string.Equals(Value, other.Value, StringComparison.OrdinalIgnoreCase); + + /// + public override int GetHashCode() => StringComparer.OrdinalIgnoreCase.GetHashCode(Value); + + /// + public override string ToString() => Value; + + /// Provides a for serializing instances. + [EditorBrowsable(EditorBrowsableState.Never)] + public sealed class Converter : JsonConverter + { + /// + public override WorkerAdmissionKind Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) + { + return new(GeneratedStringEnumJson.ReadValue(ref reader, typeToConvert)); + } + + /// + public override void Write(Utf8JsonWriter writer, WorkerAdmissionKind value, JsonSerializerOptions options) + { + GeneratedStringEnumJson.WriteValue(writer, value.Value, typeof(WorkerAdmissionKind)); + } + } +} + +/// How the owned notification was consumed. +[JsonConverter(typeof(Converter))] +[DebuggerDisplay("{Value,nq}")] +public readonly struct WorkerNotificationMode : IEquatable +{ + private readonly string? _value; + + /// Initializes a new instance of the struct. + /// The value to associate with this . + [JsonConstructor] + public WorkerNotificationMode(string value) + { + ArgumentException.ThrowIfNullOrWhiteSpace(value); + _value = value; + } + + /// Gets the value associated with this . + public string Value => _value ?? string.Empty; + + /// Gets the queued value. + public static WorkerNotificationMode Queued { get; } = new("queued"); + + /// Gets the immediate value. + public static WorkerNotificationMode Immediate { get; } = new("immediate"); + + /// Returns a value indicating whether two instances are equivalent. + public static bool operator ==(WorkerNotificationMode left, WorkerNotificationMode right) => left.Equals(right); + + /// Returns a value indicating whether two instances are not equivalent. + public static bool operator !=(WorkerNotificationMode left, WorkerNotificationMode right) => !(left == right); + + /// + public override bool Equals(object? obj) => obj is WorkerNotificationMode other && Equals(other); + + /// + public bool Equals(WorkerNotificationMode other) => string.Equals(Value, other.Value, StringComparison.OrdinalIgnoreCase); + + /// + public override int GetHashCode() => StringComparer.OrdinalIgnoreCase.GetHashCode(Value); + + /// + public override string ToString() => Value; + + /// Provides a for serializing instances. + [EditorBrowsable(EditorBrowsableState.Never)] + public sealed class Converter : JsonConverter + { + /// + public override WorkerNotificationMode Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) + { + return new(GeneratedStringEnumJson.ReadValue(ref reader, typeToConvert)); + } + + /// + public override void Write(Utf8JsonWriter writer, WorkerNotificationMode value, JsonSerializerOptions options) + { + GeneratedStringEnumJson.WriteValue(writer, value.Value, typeof(WorkerNotificationMode)); + } + } +} + /// What the agent was doing when the user interrupted it. [JsonConverter(typeof(Converter))] [DebuggerDisplay("{Value,nq}")] @@ -20708,6 +21141,13 @@ public override void Write(Utf8JsonWriter writer, ExtensionsLoadedExtensionStatu [JsonSerializable(typeof(UserToolSessionApprovalMemory))] [JsonSerializable(typeof(UserToolSessionApprovalRead))] [JsonSerializable(typeof(UserToolSessionApprovalWrite))] +[JsonSerializable(typeof(WorkerAdmission))] +[JsonSerializable(typeof(WorkerBridgeObservation))] +[JsonSerializable(typeof(WorkerCausality))] +[JsonSerializable(typeof(WorkerEventReference))] +[JsonSerializable(typeof(WorkerInput))] +[JsonSerializable(typeof(WorkerNotificationReference))] +[JsonSerializable(typeof(WorkerSource))] [JsonSerializable(typeof(WorkingDirectoryContext))] [JsonSerializable(typeof(JsonElement))] internal sealed partial class SessionEventsJsonContext : JsonSerializerContext; \ No newline at end of file diff --git a/dotnet/src/WorkerCausalityConverter.cs b/dotnet/src/WorkerCausalityConverter.cs new file mode 100644 index 0000000000..40fc361ff6 --- /dev/null +++ b/dotnet/src/WorkerCausalityConverter.cs @@ -0,0 +1,221 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + *--------------------------------------------------------------------------------------------*/ + +using System.Diagnostics; +using System.Text.Json; +using System.Text.Json.Serialization; +using System.Text.Json.Serialization.Metadata; + +namespace GitHub.Copilot; + +internal sealed class WorkerCausalityConverter : JsonConverter where T : class +{ + public override T? Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) + { + using var document = JsonDocument.ParseValue(ref reader); + var value = document.RootElement; + var remaining = 4096; + if (Small(value, ref remaining, 0) && Supported(value)) + { + try + { + if (!WithinBudget(value)) + throw new JsonException("optional worker metadata budget"); + return JsonSerializer.Deserialize(value, (JsonTypeInfo)options.GetTypeInfo(typeof(T))); + } + catch (JsonException) + { + // Optional diagnostics do not change product event handling. + } + } + Trace.TraceWarning("Ignoring invalid, unsupported or oversized workerCausality metadata"); + return null; + } + + public override void Write(Utf8JsonWriter writer, T value, JsonSerializerOptions options) => + JsonSerializer.Serialize(writer, value, (JsonTypeInfo)options.GetTypeInfo(typeof(T))); + + private static bool Supported(JsonElement value) + { + if (value.ValueKind != JsonValueKind.Object || + !Fields(value, "version", "observationProvenance", "sources", "captureComplete") || + !value.TryGetProperty("version", out var version) || version.ValueKind != JsonValueKind.Number || !version.TryGetInt32(out var number) || number != 1 || + !value.TryGetProperty("observationProvenance", out var provenance) || provenance.ValueKind != JsonValueKind.String || + provenance.GetString() is not ("native" or "ahp_coordinator") || + !value.TryGetProperty("captureComplete", out var complete) || complete.ValueKind is not (JsonValueKind.True or JsonValueKind.False) || + !value.TryGetProperty("sources", out var sources) || sources.ValueKind != JsonValueKind.Array || sources.GetArrayLength() > 32) + { + return false; + } + foreach (var source in sources.EnumerateArray()) + { + if (source.ValueKind != JsonValueKind.Object || + !Fields(source, "input", "admissions", "captureComplete", "completion", "notification", "admittedDuring") || + !source.TryGetProperty("captureComplete", out var sourceComplete) || sourceComplete.ValueKind is not (JsonValueKind.True or JsonValueKind.False) || + (complete.ValueKind == JsonValueKind.True && sourceComplete.ValueKind != JsonValueKind.True) || + !source.TryGetProperty("admissions", out var admissions) || admissions.ValueKind != JsonValueKind.Array || admissions.GetArrayLength() > 32 || + !source.TryGetProperty("input", out var input) || input.ValueKind != JsonValueKind.Object || + !Fields(input, "queueItemId", "agentId", "sender", "senderBridges") || + !Uuid(input, "queueItemId") || !Text(input, "agentId") || + !OptionalReference(input, "sender", "tool.execution_start") || + !OptionalReference(source, "completion", "subagent.completed") || + !OptionalReference(source, "admittedDuring", "assistant.turn_start")) + { + return false; + } + if (input.TryGetProperty("senderBridges", out var edges)) + { + if (!input.TryGetProperty("sender", out _) || edges.ValueKind != JsonValueKind.Array || edges.GetArrayLength() > 32) + return false; + foreach (var edge in edges.EnumerateArray()) + { + if (edge.ValueKind != JsonValueKind.Object || + !Fields(edge, "source", "reported") || + !edge.TryGetProperty("source", out var origin) || !Reference(origin, "tool.execution_start") || + !edge.TryGetProperty("reported", out var reported) || !Reference(reported, "tool.execution_start")) + return false; + } + } + foreach (var admission in admissions.EnumerateArray()) + { + if (admission.ValueKind != JsonValueKind.Object || + !Fields(admission, "kind", "messageId", "event", "ahpTurnId") || + !Text(admission, "messageId") || + !Text(admission, "kind") || admission.GetProperty("kind").GetString() is not ("queued_input" or "system_continuation") || + (admission.TryGetProperty("ahpTurnId", out _) && !Uuid(admission, "ahpTurnId"))) + return false; + if (admission.TryGetProperty("event", out var occurrence) && + (!Reference(occurrence, "user.message") || !Text(occurrence, "agentId") || + occurrence.GetProperty("agentId").GetString() != input.GetProperty("agentId").GetString())) + return false; + } + if (source.TryGetProperty("notification", out var notification) && + (notification.ValueKind != JsonValueKind.Object || + !Fields(notification, "deliveryId", "event", "mode") || + !Uuid(notification, "deliveryId") || + !Text(notification, "mode") || notification.GetProperty("mode").GetString() is not ("queued" or "immediate") || + !OptionalReference(notification, "event", "system.notification"))) + return false; + } + return true; + } + + private static bool Fields(JsonElement value, params string[] allowed) + { + foreach (var property in value.EnumerateObject()) + if (!allowed.Contains(property.Name)) + return false; + return true; + } + + private static bool Text(JsonElement value, string property) => + value.TryGetProperty(property, out var text) && text.ValueKind == JsonValueKind.String && text.GetString()!.Length > 0; + + private static bool Uuid(JsonElement value, string property) => + Text(value, property) && Guid.TryParseExact(value.GetProperty(property).GetString(), "D", out _); + + private static bool OptionalReference(JsonElement value, string property, string eventType) => + !value.TryGetProperty(property, out var reference) || Reference(reference, eventType); + + private static bool Reference(JsonElement value, string eventType) => + value.ValueKind == JsonValueKind.Object && + Fields(value, "sessionId", "eventId", "agentId", "eventType", "provenance") && + Text(value, "sessionId") && + value.GetProperty("sessionId").GetString()!.All(c => c is >= 'A' and <= 'Z' or >= 'a' and <= 'z' or >= '0' and <= '9' or '_' or '-') && + Uuid(value, "eventId") && (!value.TryGetProperty("agentId", out _) || Text(value, "agentId")) && + Text(value, "eventType") && value.GetProperty("eventType").GetString() == eventType && + Text(value, "provenance") && value.GetProperty("provenance").GetString() is "native" or "ahp_coordinator"; + + private static bool Small(JsonElement value, ref int remaining, int depth) + { + if (--remaining < 0 || depth > 32) return false; + switch (value.ValueKind) + { + case JsonValueKind.String: + remaining -= value.GetString()!.Length; + break; + case JsonValueKind.Array: + foreach (var item in value.EnumerateArray()) + if (!Small(item, ref remaining, depth + 1)) return false; + break; + case JsonValueKind.Object: + foreach (var property in value.EnumerateObject()) + { + remaining -= property.Name.Length; + if (!Small(property.Value, ref remaining, depth + 1)) return false; + } + break; + } + return remaining >= 0; + } + + private static bool WithinBudget(JsonElement value) => + 3 + CompactStringBytes("workerCausality") + CompactBytes(value) <= 4096; + + private static int CompactBytes(JsonElement value) + { + switch (value.ValueKind) + { + case JsonValueKind.Object: + var objectBytes = 2; + var firstProperty = true; + foreach (var property in value.EnumerateObject()) + { + if (!firstProperty) + objectBytes++; + firstProperty = false; + objectBytes += CompactStringBytes(property.Name) + 1 + CompactBytes(property.Value); + } + return objectBytes; + case JsonValueKind.Array: + var arrayBytes = 2; + var firstItem = true; + foreach (var item in value.EnumerateArray()) + { + if (!firstItem) + arrayBytes++; + firstItem = false; + arrayBytes += CompactBytes(item); + } + return arrayBytes; + case JsonValueKind.String: + return CompactStringBytes(value.GetString()!); + case JsonValueKind.True: + return 4; + case JsonValueKind.False: + return 5; + case JsonValueKind.Null: + return 4; + case JsonValueKind.Number: + return value.GetRawText().Length; + default: + return int.MaxValue; + } + } + + private static int CompactStringBytes(string value) + { + var bytes = 2; + for (var index = 0; index < value.Length; index++) + { + var character = value[index]; + bytes += character switch + { + '"' or '\\' or '\b' or '\f' or '\n' or '\r' or '\t' => 2, + <= '\u001f' => 6, + <= '\u007f' => 1, + <= '\u07ff' => 2, + _ when char.IsHighSurrogate(character) && + index + 1 < value.Length && + char.IsLowSurrogate(value[index + 1]) => 4, + _ => 3, + }; + if (char.IsHighSurrogate(character) && + index + 1 < value.Length && + char.IsLowSurrogate(value[index + 1])) + index++; + } + return bytes; + } +} diff --git a/dotnet/test/GitHub.Copilot.SDK.Test.csproj b/dotnet/test/GitHub.Copilot.SDK.Test.csproj index 3f1c24f973..18843913bb 100644 --- a/dotnet/test/GitHub.Copilot.SDK.Test.csproj +++ b/dotnet/test/GitHub.Copilot.SDK.Test.csproj @@ -32,6 +32,7 @@ + diff --git a/dotnet/test/Unit/WorkerCausalityTests.cs b/dotnet/test/Unit/WorkerCausalityTests.cs new file mode 100644 index 0000000000..00b7e1dc99 --- /dev/null +++ b/dotnet/test/Unit/WorkerCausalityTests.cs @@ -0,0 +1,72 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + *--------------------------------------------------------------------------------------------*/ + +using System.Text.Json; +using System.Text.Json.Nodes; +using System.Text.Json.Serialization; +using System.Text.Json.Serialization.Metadata; +using GitHub.Copilot.Rpc; +using Xunit; + +namespace GitHub.Copilot.Test.Unit; + +public class WorkerCausalityTests +{ + private static readonly JsonNode Corpus = JsonNode.Parse(File.ReadAllText( + Path.Combine(AppContext.BaseDirectory, "worker-causality.json")))!; + private static readonly JsonSerializerOptions RpcOptions = new() + { + PropertyNamingPolicy = JsonNamingPolicy.CamelCase, + DefaultIgnoreCondition = JsonIgnoreCondition.WhenWritingNull, + TypeInfoResolver = new DefaultJsonTypeInfoResolver(), + }; + + public static IEnumerable Cases => Corpus["valid"]!.AsArray().Select( + value => new object[] { value!["name"]!.GetValue(), value!.ToJsonString() }); + + private static string Decode(JsonNode value, bool isEvent) + { + var wire = value.ToJsonString(); + return isEvent ? SessionEvent.FromJson(wire).ToJson() : + JsonSerializer.Serialize(JsonSerializer.Deserialize(wire, RpcOptions), RpcOptions); + } + + private static JsonObject Payload(JsonNode value, bool isEvent) => + (isEvent ? value["data"]! : value).AsObject(); + + [Theory] + [MemberData(nameof(Cases))] + public void PublicReadersPreserveSourceAndHistoricalProductBytes(string name, string fixture) + { + var test = JsonNode.Parse(fixture)!; + var isEvent = test["event"] is not null; + var wire = (test[isEvent ? "event" : "result"]!).DeepClone(); + var observed = JsonNode.Parse(Decode(wire, isEvent))!; + Assert.True(JsonNode.DeepEquals(Payload(observed, isEvent)["workerCausality"], + Payload(wire, isEvent)["workerCausality"]), name); + + Payload(wire, isEvent).Remove("workerCausality"); + var baseline = Decode(wire, isEvent); + foreach (var invalid in Corpus["invalid"]!.AsArray()) + { + Payload(wire, isEvent)["workerCausality"] = invalid!["value"]?.DeepClone(); + Assert.Equal(baseline, Decode(wire, isEvent)); + } + } + + [Fact] + public void ExactUtf8BudgetAndUnknownFields() + { + foreach (var test in Corpus["boundaries"]!.AsArray()) + { + var wire = Corpus["valid"]![0]!["event"]!.DeepClone(); + Payload(wire, true)["workerCausality"] = test!["value"]!.DeepClone(); + var observed = Payload(JsonNode.Parse(Decode(wire, true))!, true); + Assert.True( + observed.ContainsKey("workerCausality") == test["accepted"]!.GetValue(), + test["name"]!.GetValue()); + Assert.Equal(Payload(wire, true)["content"]!.GetValue(), observed["content"]!.GetValue()); + } + } +} diff --git a/go/internal/diagnosticmetadata/worker_causality.go b/go/internal/diagnosticmetadata/worker_causality.go new file mode 100644 index 0000000000..19f0eb3c85 --- /dev/null +++ b/go/internal/diagnosticmetadata/worker_causality.go @@ -0,0 +1,192 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. + +// Package diagnosticmetadata keeps optional diagnostics from rejecting product data. +package diagnosticmetadata + +import ( + "encoding/json" + "errors" + "log" + "regexp" +) + +var uuidPattern = regexp.MustCompile(`^[0-9a-fA-F]{8}-([0-9a-fA-F]{4}-){3}[0-9a-fA-F]{12}$`) +var sessionPattern = regexp.MustCompile(`^[A-Za-z0-9_-]+$`) + +func text(value any) bool { + s, ok := value.(string) + return ok && s != "" +} + +func uuid(value any) bool { + s, ok := value.(string) + return ok && uuidPattern.MatchString(s) +} + +func fields(value map[string]any, allowed ...string) bool { + if len(value) > len(allowed) { + return false + } + for key := range value { + found := false + for _, candidate := range allowed { + if key == candidate { + found = true + break + } + } + if !found { + return false + } + } + return true +} + +func reference(value any, eventType string) bool { + ref, ok := value.(map[string]any) + if !ok { + return false + } + session, ok := ref["sessionId"].(string) + agent, hasAgent := ref["agentId"] + return fields(ref, "sessionId", "eventId", "agentId", "eventType", "provenance") && + ok && sessionPattern.MatchString(session) && uuid(ref["eventId"]) && + (!hasAgent || text(agent)) && ref["eventType"] == eventType && + (ref["provenance"] == "native" || ref["provenance"] == "ahp_coordinator") +} + +func source(value any) bool { + s, ok := value.(map[string]any) + if !ok { + return false + } + input, inputOK := s["input"].(map[string]any) + admissions, admissionsOK := s["admissions"].([]any) + _, complete := s["captureComplete"].(bool) + if !fields(s, "input", "admissions", "captureComplete", "completion", "notification", "admittedDuring") || + !inputOK || !fields(input, "queueItemId", "agentId", "sender", "senderBridges") || + !admissionsOK || len(admissions) > 32 || !complete || + !uuid(input["queueItemId"]) || !text(input["agentId"]) { + return false + } + sender, hasSender := input["sender"] + if hasSender && !reference(sender, "tool.execution_start") { + return false + } + if raw, exists := input["senderBridges"]; exists { + edges, ok := raw.([]any) + if !ok || !hasSender || len(edges) > 32 { + return false + } + for _, raw := range edges { + edge, ok := raw.(map[string]any) + if !ok || !fields(edge, "source", "reported") || + !reference(edge["source"], "tool.execution_start") || !reference(edge["reported"], "tool.execution_start") { + return false + } + } + } + for _, raw := range admissions { + admission, ok := raw.(map[string]any) + if !ok || !fields(admission, "kind", "messageId", "event", "ahpTurnId") || + !text(admission["messageId"]) || + (admission["kind"] != "queued_input" && admission["kind"] != "system_continuation") { + return false + } + if turn, exists := admission["ahpTurnId"]; exists && !uuid(turn) { + return false + } + if event, exists := admission["event"]; exists { + if !reference(event, "user.message") || event.(map[string]any)["agentId"] != input["agentId"] { + return false + } + } + } + for field, eventType := range map[string]string{"completion": "subagent.completed", "admittedDuring": "assistant.turn_start"} { + if event, exists := s[field]; exists && !reference(event, eventType) { + return false + } + } + if raw, exists := s["notification"]; exists { + n, ok := raw.(map[string]any) + if !ok || !fields(n, "deliveryId", "event", "mode") || + !uuid(n["deliveryId"]) || (n["mode"] != "queued" && n["mode"] != "immediate") { + return false + } + if event, exists := n["event"]; exists && !reference(event, "system.notification") { + return false + } + } + return true +} + +type budget struct{ count int } + +func (b *budget) Write(p []byte) (int, error) { + b.count += len(p) + // Encoder adds one newline; the compact single-property field is limited to 4096. + if b.count > 4097 { + b.count = 4098 + return 0, errors.New("worker metadata budget") + } + return len(p), nil +} + +func small(value any, remaining *int, depth int) bool { + *remaining-- + if *remaining < 0 || depth > 32 { + return false + } + switch value := value.(type) { + case string: + *remaining -= len(value) + case []any: + for _, item := range value { + if !small(item, remaining, depth+1) { + return false + } + } + case map[string]any: + for key, item := range value { + if !small(key, remaining, depth+1) || !small(item, remaining, depth+1) { + return false + } + } + } + return *remaining >= 0 +} + +func supported(value map[string]any) bool { + sources, ok := value["sources"].([]any) + _, complete := value["captureComplete"].(bool) + if !fields(value, "version", "observationProvenance", "sources", "captureComplete") || + !ok || len(sources) > 32 || !complete || value["version"] != float64(1) || + (value["observationProvenance"] != "native" && value["observationProvenance"] != "ahp_coordinator") { + return false + } + for _, entry := range sources { + if !source(entry) || (value["captureComplete"] == true && entry.(map[string]any)["captureComplete"] != true) { + return false + } + } + return true +} + +// ReadWorkerCausality decodes availability only, not enclosing self identity or association. +func ReadWorkerCausality(raw json.RawMessage, target any) bool { + if len(raw) == 0 { + return false + } + var value map[string]any + remaining := 4096 + if json.Unmarshal(raw, &value) == nil && small(value, &remaining, 0) && supported(value) { + writer := &budget{} + encoder := json.NewEncoder(writer) + encoder.SetEscapeHTML(false) + if encoder.Encode(map[string]any{"workerCausality": value}) == nil && json.Unmarshal(raw, target) == nil { + return true + } + } + log.Print("Ignoring invalid, unsupported or oversized workerCausality metadata") + return false +} diff --git a/go/rpc/zrpc.go b/go/rpc/zrpc.go index 4491dca05d..6a2612614d 100644 --- a/go/rpc/zrpc.go +++ b/go/rpc/zrpc.go @@ -16409,6 +16409,9 @@ type TasksSendMessageResult struct { Error *string `json:"error,omitempty"` // Whether the message was successfully delivered or steered Sent bool `json:"sent"` + // Optional exact queue admission receipt on sent=true only. No implicit event or + // execution-success claim. + WorkerCausality *WorkerCausality `json:"workerCausality,omitempty"` } // Agent type, prompt, name, and optional description and model override for the new task. @@ -17552,6 +17555,102 @@ type VisibilitySetResult struct { Synced bool `json:"synced"` } +// An observed worker admission, not a claim that execution succeeded. +// Experimental: WorkerAdmission is part of an experimental API and may change or be removed. +type WorkerAdmission struct { + // Actual AHP participant Turn UUID, not a native turn counter or provenance signal. + AhpTurnID *string `json:"ahpTurnId,omitempty"` + // May be omitted only for the matching current worker user.message. + Event *WorkerEventReference `json:"event,omitempty"` + // The producer's admission kind. + Kind WorkerAdmissionKind `json:"kind"` + // Canonical logical message identity, independent of queueItemId. + MessageID string `json:"messageId"` +} + +// One indivisible source-to-reported bridge observation, not a root alias. +// Experimental: WorkerBridgeObservation is part of an experimental API and may change or be +// removed. +type WorkerBridgeObservation struct { + // Full occurrence actually emitted by that bridge. + Reported WorkerEventReference `json:"reported"` + // Full event received by this bridge. + Source WorkerEventReference `json:"source"` +} + +// Optional v1 worker diagnostics. The compact UTF-8 {"workerCausality":value} +// must fit 4096 bytes. Ignore invalid/unknown/oversize metadata, not the product event. +// Experimental: WorkerCausality is part of an experimental API and may change or be removed. +type WorkerCausality struct { + // Complete placement-aware observed capture, not global causality or execution success. + // Empty true requires explicit native invocation attestation. + CaptureComplete bool `json:"captureComplete"` + // Provenance of this enclosing observation; never inferred from other fields. + ObservationProvenance WorkerObservationProvenance `json:"observationProvenance"` + // Sources in capture order, at most 32. Absent/unknown is not known-empty. + Sources []WorkerSource `json:"sources"` + // Supported version, exactly 1. + Version int64 `json:"version"` +} + +// Exact observed event identity. No private registration generation or execution handle. +// Experimental: WorkerEventReference is part of an experimental API and may change or be +// removed. +type WorkerEventReference struct { + // Actual event agent scope, absent for a root occurrence. + AgentID *string `json:"agentId,omitempty"` + // Actual event occurrence UUID; copied without normalization. + EventID string `json:"eventId"` + // Type of the observed occurrence. + EventType WorkerEventType `json:"eventType"` + // Explicit provenance of this reference. + Provenance WorkerObservationProvenance `json:"provenance"` + // Actual runtime session scope. + SessionID string `json:"sessionId"` +} + +// Exact accepted worker input, distinct from a message, event, caller correlation or Turn. +// Experimental: WorkerInput is part of an experimental API and may change or be removed. +type WorkerInput struct { + // Actual recipient task. + AgentID string `json:"agentId"` + // UUID allocated for this queue item by the admitting producer. + QueueItemID string `json:"queueItemId"` + // Original invoking occurrence, never replaced by a reported/root alias. + Sender *WorkerEventReference `json:"sender,omitempty"` + // Exact captured edges in producer order. Requires sender; at most 32 whole pairs. + SenderBridges []WorkerBridgeObservation `json:"senderBridges,omitzero"` +} + +// Exact delivery and optional occurrence of a consumed worker notification. +// Experimental: WorkerNotificationReference is part of an experimental API and may change +// or be removed. +type WorkerNotificationReference struct { + // Actual notificationDeliveryId UUID. + DeliveryID string `json:"deliveryId"` + // May be omitted only on the matching current system.notification. + Event *WorkerEventReference `json:"event,omitempty"` + // Actual consumption mode. + Mode WorkerNotificationMode `json:"mode"` +} + +// One captured source at this placement and observation boundary. +// Experimental: WorkerSource is part of an experimental API and may change or be removed. +type WorkerSource struct { + // Actual admissions in capture order; at most 32. + Admissions []WorkerAdmission `json:"admissions"` + // Exact already-open iteration when an immediate notification was consumed. + AdmittedDuring *WorkerEventReference `json:"admittedDuring,omitempty"` + // Applicable observations were all captured here, never work completion or success. + CaptureComplete bool `json:"captureComplete"` + // Actual completion emission only. Absence is not an execution outcome. + Completion *WorkerEventReference `json:"completion,omitempty"` + // Indivisible accepted input identity. + Input WorkerInput `json:"input"` + // Actual consumed notification, when observed. + Notification *WorkerNotificationReference `json:"notification,omitempty"` +} + // Parameters for cooperatively aborting a workflow body. // Experimental: WorkflowAbortRequest is part of an experimental API and may change or be // removed. @@ -22839,6 +22938,48 @@ const ( VerbosityMedium Verbosity = "medium" ) +// Why this exact worker admission was made. +// Experimental: WorkerAdmissionKind is part of an experimental API and may change or be +// removed. +type WorkerAdmissionKind string + +const ( + WorkerAdmissionKindQueuedInput WorkerAdmissionKind = "queued_input" + WorkerAdmissionKindSystemContinuation WorkerAdmissionKind = "system_continuation" +) + +// Supported observed occurrences. Chronological parentId is not a causal reference. +// Experimental: WorkerEventType is part of an experimental API and may change or be removed. +type WorkerEventType string + +const ( + WorkerEventTypeAssistantTurnStart WorkerEventType = "assistant.turn_start" + WorkerEventTypeSubagentCompleted WorkerEventType = "subagent.completed" + WorkerEventTypeSystemNotification WorkerEventType = "system.notification" + WorkerEventTypeToolExecutionStart WorkerEventType = "tool.execution_start" + WorkerEventTypeUserMessage WorkerEventType = "user.message" +) + +// How the owned notification was consumed. +// Experimental: WorkerNotificationMode is part of an experimental API and may change or be +// removed. +type WorkerNotificationMode string + +const ( + WorkerNotificationModeImmediate WorkerNotificationMode = "immediate" + WorkerNotificationModeQueued WorkerNotificationMode = "queued" +) + +// Producer of an observation, not the execution location of every referenced source. +// Experimental: WorkerObservationProvenance is part of an experimental API and may change +// or be removed. +type WorkerObservationProvenance string + +const ( + WorkerObservationProvenanceAhpCoordinator WorkerObservationProvenance = "ahp_coordinator" + WorkerObservationProvenanceNative WorkerObservationProvenance = "native" +) + // Execution-critical workflow storage operation. // Experimental: WorkflowDurableOperation is part of an experimental API and may change or // be removed. diff --git a/go/rpc/zrpc_encoding.go b/go/rpc/zrpc_encoding.go index 0fe065835d..5e5b8fac70 100644 --- a/go/rpc/zrpc_encoding.go +++ b/go/rpc/zrpc_encoding.go @@ -6,6 +6,7 @@ package rpc import ( "encoding/json" "errors" + "github.com/github/copilot-sdk/go/internal/diagnosticmetadata" ) func unmarshalAuthInfo(data []byte) (AuthInfo, error) { @@ -7397,6 +7398,24 @@ func (r *TasksPromoteCurrentToBackgroundResult) UnmarshalJSON(data []byte) error return nil } +func (r *TasksSendMessageResult) UnmarshalJSON(data []byte) error { + type rawTasksSendMessageResult struct { + Error *string `json:"error,omitempty"` + Sent bool `json:"sent"` + WorkerCausality json.RawMessage `json:"workerCausality,omitempty"` + } + var raw rawTasksSendMessageResult + if err := json.Unmarshal(data, &raw); err != nil { + return err + } + r.Error = raw.Error + r.Sent = raw.Sent + if !diagnosticmetadata.ReadWorkerCausality(raw.WorkerCausality, &r.WorkerCausality) { + r.WorkerCausality = nil + } + return nil +} + func (r *TasksUpdateRequest) UnmarshalJSON(data []byte) error { type rawTasksUpdateRequest struct { ID string `json:"id"` diff --git a/go/rpc/zsession_encoding.go b/go/rpc/zsession_encoding.go index ecd93f1dfc..f1795c99c6 100644 --- a/go/rpc/zsession_encoding.go +++ b/go/rpc/zsession_encoding.go @@ -6,6 +6,7 @@ package rpc import ( "encoding/json" "errors" + "github.com/github/copilot-sdk/go/internal/diagnosticmetadata" "time" ) @@ -989,6 +990,7 @@ func (r *UserMessageData) UnmarshalJSON(data []byte) error { SupportedNativeDocumentMIMETypes []string `json:"supportedNativeDocumentMimeTypes,omitzero"` TransformedContent *string `json:"transformedContent,omitempty"` TurnID *string `json:"turnId,omitempty"` + WorkerCausality json.RawMessage `json:"workerCausality,omitempty"` } var raw rawUserMessageData if err := json.Unmarshal(data, &raw); err != nil { @@ -1018,6 +1020,29 @@ func (r *UserMessageData) UnmarshalJSON(data []byte) error { r.SupportedNativeDocumentMIMETypes = raw.SupportedNativeDocumentMIMETypes r.TransformedContent = raw.TransformedContent r.TurnID = raw.TurnID + if !diagnosticmetadata.ReadWorkerCausality(raw.WorkerCausality, &r.WorkerCausality) { + r.WorkerCausality = nil + } + return nil +} + +func (r *AssistantTurnStartData) UnmarshalJSON(data []byte) error { + type rawAssistantTurnStartData struct { + InteractionID *string `json:"interactionId,omitempty"` + Model *string `json:"model,omitempty"` + TurnID string `json:"turnId"` + WorkerCausality json.RawMessage `json:"workerCausality,omitempty"` + } + var raw rawAssistantTurnStartData + if err := json.Unmarshal(data, &raw); err != nil { + return err + } + r.InteractionID = raw.InteractionID + r.Model = raw.Model + r.TurnID = raw.TurnID + if !diagnosticmetadata.ReadWorkerCausality(raw.WorkerCausality, &r.WorkerCausality) { + r.WorkerCausality = nil + } return nil } @@ -1832,6 +1857,7 @@ func (r *SystemNotificationData) UnmarshalJSON(data []byte) error { Content string `json:"content"` Kind json.RawMessage `json:"kind"` ResponsesReasoning *ResponsesReasoning `json:"responsesReasoning,omitempty"` + WorkerCausality json.RawMessage `json:"workerCausality,omitempty"` } var raw rawSystemNotificationData if err := json.Unmarshal(data, &raw); err != nil { @@ -1846,6 +1872,9 @@ func (r *SystemNotificationData) UnmarshalJSON(data []byte) error { r.Kind = value } r.ResponsesReasoning = raw.ResponsesReasoning + if !diagnosticmetadata.ReadWorkerCausality(raw.WorkerCausality, &r.WorkerCausality) { + r.WorkerCausality = nil + } return nil } diff --git a/go/rpc/zsession_events.go b/go/rpc/zsession_events.go index a9343a3157..6ca48c3db0 100644 --- a/go/rpc/zsession_events.go +++ b/go/rpc/zsession_events.go @@ -2160,6 +2160,8 @@ type UserMessageData struct { TransformedContent *string `json:"transformedContent,omitempty"` // The agent-loop turn ID that consumed this message; absent when no agent-loop turn consumed it TurnID *string `json:"turnId,omitempty"` + // Optional worker admission observations; self identity requires the matching enclosing message and agent. + WorkerCausality *WorkerCausality `json:"workerCausality,omitempty"` } func (*UserMessageData) sessionEventData() {} @@ -2965,6 +2967,8 @@ type SystemNotificationData struct { Kind SystemNotification `json:"kind"` // Responses reasoning settings anchored before this model-facing message, for cache-stable history replay ResponsesReasoning *ResponsesReasoning `json:"responsesReasoning,omitempty"` + // Optional owned worker notification observations; an omitted notification event refers only to this occurrence. + WorkerCausality *WorkerCausality `json:"workerCausality,omitempty"` } func (*SystemNotificationData) sessionEventData() {} @@ -3179,6 +3183,8 @@ type AssistantTurnStartData struct { Model *string `json:"model,omitempty"` // Identifier for this turn within the agentic loop, typically a stringified turn number TurnID string `json:"turnId"` + // Optional bounded worker observations. Missing or invalid metadata is unavailable, not known-empty. + WorkerCausality *WorkerCausality `json:"workerCausality,omitempty"` } func (*AssistantTurnStartData) sessionEventData() {} diff --git a/go/worker_causality_test.go b/go/worker_causality_test.go new file mode 100644 index 0000000000..3e3b005b85 --- /dev/null +++ b/go/worker_causality_test.go @@ -0,0 +1,117 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. + +package copilot + +import ( + "encoding/json" + "os" + "reflect" + "testing" + + "github.com/github/copilot-sdk/go/rpc" +) + +type workerCausalityCorpus struct { + Valid []struct { + Name string + Event json.RawMessage + Result json.RawMessage + } + Invalid []struct { + Name string + Value any + } + Boundaries []struct { + Name string + Value any + Accepted bool + } +} + +func workerCorpus(t *testing.T) workerCausalityCorpus { + t.Helper() + data, err := os.ReadFile("../test/worker-causality.json") + if err != nil { + t.Fatal(err) + } + var corpus workerCausalityCorpus + if err := json.Unmarshal(data, &corpus); err != nil { + t.Fatal(err) + } + return corpus +} + +func workerProduct(t *testing.T, value map[string]any, event bool) []byte { + t.Helper() + wire, err := json.Marshal(value) + if err != nil { + t.Fatal(err) + } + var product any = &rpc.TasksSendMessageResult{} + if event { + product = &SessionEvent{} + } + if err := json.Unmarshal(wire, product); err != nil { + t.Fatal(err) + } + result, err := json.Marshal(product) + if err != nil { + t.Fatal(err) + } + return result +} + +func workerPayload(t *testing.T, wire []byte, event bool) (map[string]any, map[string]any) { + t.Helper() + var value map[string]any + if err := json.Unmarshal(wire, &value); err != nil { + t.Fatal(err) + } + if event { + return value, value["data"].(map[string]any) + } + return value, value +} + +func TestWorkerCausalityPublicReaders(t *testing.T) { + corpus := workerCorpus(t) + for _, test := range corpus.Valid { + t.Run(test.Name, func(t *testing.T) { + event := len(test.Event) > 0 + wire := test.Result + if event { + wire = test.Event + } + value, payload := workerPayload(t, wire, event) + _, result := workerPayload(t, workerProduct(t, value, event), event) + if !reflect.DeepEqual(result["workerCausality"], payload["workerCausality"]) { + t.Fatal("typed reader changed captured source identities") + } + delete(payload, "workerCausality") + baseline := string(workerProduct(t, value, event)) + for _, invalid := range corpus.Invalid { + payload["workerCausality"] = invalid.Value + if actual := string(workerProduct(t, value, event)); actual != baseline { + t.Fatalf("%s changed product bytes: %s", invalid.Name, actual) + } + } + }) + } +} + +func TestWorkerCausalityExactUTF8Budget(t *testing.T) { + corpus := workerCorpus(t) + for _, test := range corpus.Boundaries { + t.Run(test.Name, func(t *testing.T) { + value, payload := workerPayload(t, corpus.Valid[0].Event, true) + payload["workerCausality"] = test.Value + _, result := workerPayload(t, workerProduct(t, value, true), true) + if _, present := result["workerCausality"]; present != test.Accepted { + t.Fatalf("metadata presence = %v, want %v", present, test.Accepted) + } + if result["content"] != payload["content"] { + t.Fatal("product content changed") + } + }) + } +} diff --git a/go/zsession_events.go b/go/zsession_events.go index a1aab5837f..270678a6f0 100644 --- a/go/zsession_events.go +++ b/go/zsession_events.go @@ -467,6 +467,17 @@ type ( UserToolSessionApprovalRead = rpc.UserToolSessionApprovalRead UserToolSessionApprovalWrite = rpc.UserToolSessionApprovalWrite Verbosity = rpc.Verbosity + WorkerAdmission = rpc.WorkerAdmission + WorkerAdmissionKind = rpc.WorkerAdmissionKind + WorkerBridgeObservation = rpc.WorkerBridgeObservation + WorkerCausality = rpc.WorkerCausality + WorkerEventReference = rpc.WorkerEventReference + WorkerEventType = rpc.WorkerEventType + WorkerInput = rpc.WorkerInput + WorkerNotificationMode = rpc.WorkerNotificationMode + WorkerNotificationReference = rpc.WorkerNotificationReference + WorkerObservationProvenance = rpc.WorkerObservationProvenance + WorkerSource = rpc.WorkerSource WorkingDirectoryContext = rpc.WorkingDirectoryContext WorkingDirectoryContextHostType = rpc.WorkingDirectoryContextHostType WorkspaceFileChangedOperation = rpc.WorkspaceFileChangedOperation @@ -1072,6 +1083,17 @@ const ( VerbosityHigh = rpc.VerbosityHigh VerbosityLow = rpc.VerbosityLow VerbosityMedium = rpc.VerbosityMedium + WorkerAdmissionKindQueuedInput = rpc.WorkerAdmissionKindQueuedInput + WorkerAdmissionKindSystemContinuation = rpc.WorkerAdmissionKindSystemContinuation + WorkerEventTypeAssistantTurnStart = rpc.WorkerEventTypeAssistantTurnStart + WorkerEventTypeSubagentCompleted = rpc.WorkerEventTypeSubagentCompleted + WorkerEventTypeSystemNotification = rpc.WorkerEventTypeSystemNotification + WorkerEventTypeToolExecutionStart = rpc.WorkerEventTypeToolExecutionStart + WorkerEventTypeUserMessage = rpc.WorkerEventTypeUserMessage + WorkerNotificationModeImmediate = rpc.WorkerNotificationModeImmediate + WorkerNotificationModeQueued = rpc.WorkerNotificationModeQueued + WorkerObservationProvenanceAhpCoordinator = rpc.WorkerObservationProvenanceAhpCoordinator + WorkerObservationProvenanceNative = rpc.WorkerObservationProvenanceNative WorkingDirectoryContextHostTypeADO = rpc.WorkingDirectoryContextHostTypeADO WorkingDirectoryContextHostTypeGitHub = rpc.WorkingDirectoryContextHostTypeGitHub WorkspaceFileChangedOperationCreate = rpc.WorkspaceFileChangedOperationCreate diff --git a/java/scripts/codegen/java.test.ts b/java/scripts/codegen/java.test.ts index 3f36cdc43d..b8ebf4d971 100644 --- a/java/scripts/codegen/java.test.ts +++ b/java/scripts/codegen/java.test.ts @@ -7,6 +7,7 @@ import type { JSONSchema7 } from "json-schema"; import { collectNestedDiscriminatedUnionTypeNames, + generateRpcClass, isMainModule, renderEventVariantClass, renderRpcTypes, @@ -62,6 +63,23 @@ test("admission correlation preserves existing Java record constructors", async assert.match(code, /public SessionSendParams\(String prompt, String displayPrompt\)/); assert.match(code, /this\(prompt, null, displayPrompt\);/); + const pending = generateRpcClass("QueuePendingItems", { + type: "object", + properties: { + id: { type: "string" }, + messageId: { type: "string" }, + clientCorrelationId: { type: "string" }, + source: { type: "string" }, + }, + }, new Map(), "com.github.copilot.generated").code; + assert.match( + pending, + /public QueuePendingItems\(String id, String messageId, String source\)/ + ); + assert.match(pending, /this\(id, messageId, null, source\);/); + assert.match(pending, /public QueuePendingItems\(String id, String messageId\)/); + assert.match(pending, /this\(id, messageId, null, null\);/); + const event = renderEventVariantClass({ typeName: "user.message", className: "UserMessageEvent", @@ -78,6 +96,58 @@ test("admission correlation preserves existing Java record constructors", async assert.match(event, /this\(content, null, messageId\);/); }); +test("worker causality preserves existing Java event and RPC record constructors", () => { + const event = renderEventVariantClass({ + typeName: "user.message", + className: "UserMessageEvent", + dataSchema: { + type: "object", + properties: { + workerCausality: { type: "object" }, + content: { type: "string" }, + clientCorrelationId: { type: "string" }, + messageId: { type: "string" }, + }, + }, + }, "com.github.copilot.generated"); + assert.match( + event, + /public UserMessageEventData\(String content, String clientCorrelationId, String messageId\)/ + ); + assert.match(event, /this\(null, content, clientCorrelationId, messageId\);/); + assert.match(event, /public UserMessageEventData\(String content, String messageId\)/); + assert.match(event, /this\(null, content, null, messageId\);/); + + const notification = renderEventVariantClass({ + typeName: "system.notification", + className: "SystemNotificationEvent", + dataSchema: { + type: "object", + properties: { + workerCausality: { type: "object" }, + content: { type: "string" }, + kind: { type: "object" }, + }, + }, + }, "com.github.copilot.generated"); + assert.match( + notification, + /public SystemNotificationEventData\(String content, Map kind\)/ + ); + assert.match(notification, /this\(null, content, kind\);/); + + const result = generateRpcClass("SessionTasksSendMessageResult", { + type: "object", + properties: { + workerCausality: { type: "object" }, + sent: { type: "boolean" }, + error: { type: "string" }, + }, + }, new Map(), "com.github.copilot.generated").code; + assert.match(result, /public SessionTasksSendMessageResult\(Boolean sent, String error\)/); + assert.match(result, /this\(null, sent, error\);/); +}); + for (const keyword of ["anyOf", "oneOf"] as const) { test(`root ${keyword} payload preserves raw JSON and existing data descriptors`, () => { const source = renderPayload({ diff --git a/java/scripts/codegen/java.ts b/java/scripts/codegen/java.ts index b4ee2e5193..76c1d3f188 100644 --- a/java/scripts/codegen/java.ts +++ b/java/scripts/codegen/java.ts @@ -1356,11 +1356,14 @@ export function renderEventVariantClass(variant: EventVariant, packageName: stri if (field.description) { lines.push(` /** ${field.description} */`); } + if (field.jsonName === "workerCausality") { + lines.push(` @com.fasterxml.jackson.databind.annotation.JsonDeserialize(using = com.github.copilot.WorkerCausalityDeserializer.class)`); + } lines.push(` @JsonProperty("${field.jsonName}") ${field.javaType} ${field.javaName}${comma}`); } lines.push(` ) {`); } - lines.push(...renderAdmissionCompatibilityConstructor(`${variant.className}Data`, dataFields, " ")); + lines.push(...renderCompatibilityConstructors(`${variant.className}Data`, dataFields, " ")); // Render nested types inside Data record for (const [, nested] of nestedTypes) { lines.push(...renderNestedType(nested, 2, nestedTypes, allImports)); @@ -1565,27 +1568,51 @@ function schemaAllowsNull(schema: JSONSchema7): boolean { ); } -/** Preserve pre-admission constructor descriptors while adding optional record components. */ -function renderAdmissionCompatibilityConstructor( +/** Preserve public record descriptors while adding optional diagnostic components. */ +function renderCompatibilityConstructors( className: string, fields: { javaName: string; javaType: string }[], indent: string, ): string[] { - const types = new Set(["SessionSendParams", "SendMessageItem", "QueuePendingItems", "UserMessageEventData"]); - if (!types.has(className) || !fields.some((field) => field.javaName === "clientCorrelationId")) return []; - const previous = fields.filter((field) => field.javaName !== "clientCorrelationId"); - const parameters = previous.map((field) => `${field.javaType} ${field.javaName}`).join(", "); - const arguments_ = fields.map((field) => field.javaName === "clientCorrelationId" ? "null" : field.javaName).join(", "); - return [ - `${indent}/** Creates a value without optional admission correlation metadata. */`, - `${indent}public ${className}(${parameters}) {`, - `${indent} this(${arguments_});`, - `${indent}}`, - ]; + const names = new Set(fields.map((field) => field.javaName)); + let omissions: string[][] = []; + if (className === "UserMessageEventData") { + omissions = names.has("workerCausality") + ? [["workerCausality"], ["workerCausality", "clientCorrelationId"]] + : [["clientCorrelationId"]]; + } else if (className === "QueuePendingItems") { + omissions = [["clientCorrelationId"], ["clientCorrelationId", "source"]]; + } else if (["SessionSendParams", "SendMessageItem"].includes(className)) { + omissions = [["clientCorrelationId"]]; + } else if ([ + "SystemNotificationEventData", + "AssistantTurnStartEventData", + "SessionTasksSendMessageResult", + ].includes(className)) { + omissions = [["workerCausality"]]; + } + return omissions.flatMap((omitted, index) => { + if (!omitted.every((field) => names.has(field))) return []; + const previous = fields.filter((field) => !omitted.includes(field.javaName)); + const parameters = previous.map((field) => `${field.javaType} ${field.javaName}`).join(", "); + const arguments_ = fields + .map((field) => omitted.includes(field.javaName) ? "null" : field.javaName) + .join(", "); + const description = omitted.includes("workerCausality") + ? "Creates a value without optional worker diagnostics." + : "Creates a value without optional admission correlation metadata."; + return [ + ...(index === 0 ? [] : [""]), + `${indent}/** ${description} */`, + `${indent}public ${className}(${parameters}) {`, + `${indent} this(${arguments_});`, + `${indent}}`, + ]; + }); } /** Generate a Java record for a JSON Schema object type. Returns the class content. */ -function generateRpcClass( +export function generateRpcClass( className: string, schema: JSONSchema7, _nestedTypes: Map, @@ -1630,12 +1657,15 @@ function generateRpcClass( if (f.includeNull) { lines.push(` @JsonInclude(JsonInclude.Include.ALWAYS)`); } + if (f.propName === "workerCausality") { + lines.push(` @com.fasterxml.jackson.databind.annotation.JsonDeserialize(using = com.github.copilot.WorkerCausalityDeserializer.class)`); + } lines.push(` @JsonProperty("${f.propName}") ${f.javaType} ${f.javaName}${comma}`); } lines.push(`) {`); } - lines.push(...renderAdmissionCompatibilityConstructor(className, fields, " ")); + lines.push(...renderCompatibilityConstructors(className, fields, " ")); // Add nested types as nested records/enums inside this record for (const [, nested] of localNestedTypes) { diff --git a/java/sdk/src/generated/java/com/github/copilot/generated/AssistantTurnStartEvent.java b/java/sdk/src/generated/java/com/github/copilot/generated/AssistantTurnStartEvent.java index a9c6b2932d..c80e18fd9e 100644 --- a/java/sdk/src/generated/java/com/github/copilot/generated/AssistantTurnStartEvent.java +++ b/java/sdk/src/generated/java/com/github/copilot/generated/AssistantTurnStartEvent.java @@ -34,6 +34,9 @@ public final class AssistantTurnStartEvent extends SessionEvent { @JsonIgnoreProperties(ignoreUnknown = true) @JsonInclude(JsonInclude.Include.NON_NULL) public record AssistantTurnStartEventData( + /** Optional bounded worker observations. Missing or invalid metadata is unavailable, not known-empty. */ + @com.fasterxml.jackson.databind.annotation.JsonDeserialize(using = com.github.copilot.WorkerCausalityDeserializer.class) + @JsonProperty("workerCausality") WorkerCausality workerCausality, /** Identifier for this turn within the agentic loop, typically a stringified turn number */ @JsonProperty("turnId") String turnId, /** Model identifier used for this turn, when known */ @@ -41,5 +44,9 @@ public record AssistantTurnStartEventData( /** CAPI interaction ID for correlating this turn with upstream telemetry */ @JsonProperty("interactionId") String interactionId ) { + /** Creates a value without optional worker diagnostics. */ + public AssistantTurnStartEventData(String turnId, String model, String interactionId) { + this(null, turnId, model, interactionId); + } } } diff --git a/java/sdk/src/generated/java/com/github/copilot/generated/SystemNotificationEvent.java b/java/sdk/src/generated/java/com/github/copilot/generated/SystemNotificationEvent.java index c2e1e55706..9b8be8c060 100644 --- a/java/sdk/src/generated/java/com/github/copilot/generated/SystemNotificationEvent.java +++ b/java/sdk/src/generated/java/com/github/copilot/generated/SystemNotificationEvent.java @@ -34,6 +34,9 @@ public final class SystemNotificationEvent extends SessionEvent { @JsonIgnoreProperties(ignoreUnknown = true) @JsonInclude(JsonInclude.Include.NON_NULL) public record SystemNotificationEventData( + /** Optional owned worker notification observations; an omitted notification event refers only to this occurrence. */ + @com.fasterxml.jackson.databind.annotation.JsonDeserialize(using = com.github.copilot.WorkerCausalityDeserializer.class) + @JsonProperty("workerCausality") WorkerCausality workerCausality, /** The notification text, typically wrapped in XML tags */ @JsonProperty("content") String content, /** Structured metadata identifying what triggered this notification */ @@ -41,5 +44,9 @@ public record SystemNotificationEventData( /** Responses reasoning settings anchored before this model-facing message, for cache-stable history replay */ @JsonProperty("responsesReasoning") ResponsesReasoning responsesReasoning ) { + /** Creates a value without optional worker diagnostics. */ + public SystemNotificationEventData(String content, Object kind, ResponsesReasoning responsesReasoning) { + this(null, content, kind, responsesReasoning); + } } } diff --git a/java/sdk/src/generated/java/com/github/copilot/generated/UserMessageEvent.java b/java/sdk/src/generated/java/com/github/copilot/generated/UserMessageEvent.java index bd9abb324c..9705382855 100644 --- a/java/sdk/src/generated/java/com/github/copilot/generated/UserMessageEvent.java +++ b/java/sdk/src/generated/java/com/github/copilot/generated/UserMessageEvent.java @@ -35,6 +35,9 @@ public final class UserMessageEvent extends SessionEvent { @JsonIgnoreProperties(ignoreUnknown = true) @JsonInclude(JsonInclude.Include.NON_NULL) public record UserMessageEventData( + /** Optional worker admission observations; self identity requires the matching enclosing message and agent. */ + @com.fasterxml.jackson.databind.annotation.JsonDeserialize(using = com.github.copilot.WorkerCausalityDeserializer.class) + @JsonProperty("workerCausality") WorkerCausality workerCausality, /** The user's message text as displayed in the timeline */ @JsonProperty("content") String content, /** Responses reasoning settings anchored before this model-facing message, for cache-stable history replay */ @@ -66,9 +69,14 @@ public record UserMessageEventData( /** Task ID minted when the runtime prepares this user-message run. This is not a parent interaction ID or worker instance ID and must not be equated with CAPI's X-Parent-Agent-Id. */ @JsonProperty("parentAgentTaskId") String parentAgentTaskId ) { - /** Creates a value without optional admission correlation metadata. */ + /** Creates a value without optional worker diagnostics. */ + public UserMessageEventData(String content, ResponsesReasoning responsesReasoning, String messageId, String clientCorrelationId, String transformedContent, List attachments, List supportedNativeDocumentMimeTypes, List nativeDocumentPathFallbackPaths, String source, UserMessageDelivery delivery, UserMessageAgentMode agentMode, Boolean isAutopilotContinuation, String interactionId, String turnId, String parentAgentTaskId) { + this(null, content, responsesReasoning, messageId, clientCorrelationId, transformedContent, attachments, supportedNativeDocumentMimeTypes, nativeDocumentPathFallbackPaths, source, delivery, agentMode, isAutopilotContinuation, interactionId, turnId, parentAgentTaskId); + } + + /** Creates a value without optional worker diagnostics. */ public UserMessageEventData(String content, ResponsesReasoning responsesReasoning, String messageId, String transformedContent, List attachments, List supportedNativeDocumentMimeTypes, List nativeDocumentPathFallbackPaths, String source, UserMessageDelivery delivery, UserMessageAgentMode agentMode, Boolean isAutopilotContinuation, String interactionId, String turnId, String parentAgentTaskId) { - this(content, responsesReasoning, messageId, null, transformedContent, attachments, supportedNativeDocumentMimeTypes, nativeDocumentPathFallbackPaths, source, delivery, agentMode, isAutopilotContinuation, interactionId, turnId, parentAgentTaskId); + this(null, content, responsesReasoning, messageId, null, transformedContent, attachments, supportedNativeDocumentMimeTypes, nativeDocumentPathFallbackPaths, source, delivery, agentMode, isAutopilotContinuation, interactionId, turnId, parentAgentTaskId); } } } diff --git a/java/sdk/src/generated/java/com/github/copilot/generated/WorkerAdmission.java b/java/sdk/src/generated/java/com/github/copilot/generated/WorkerAdmission.java new file mode 100644 index 0000000000..dc8e906b59 --- /dev/null +++ b/java/sdk/src/generated/java/com/github/copilot/generated/WorkerAdmission.java @@ -0,0 +1,34 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + *--------------------------------------------------------------------------------------------*/ + +// AUTO-GENERATED FILE - DO NOT EDIT +// Generated from: session-events.schema.json + +package com.github.copilot.generated; + +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import com.fasterxml.jackson.annotation.JsonInclude; +import com.fasterxml.jackson.annotation.JsonProperty; +import java.util.UUID; +import javax.annotation.processing.Generated; + +/** + * An observed worker admission, not a claim that execution succeeded. + * + * @since 1.0.0 + */ +@javax.annotation.processing.Generated("copilot-sdk-codegen") +@JsonInclude(JsonInclude.Include.NON_NULL) +@JsonIgnoreProperties(ignoreUnknown = true) +public record WorkerAdmission( + /** The producer's admission kind. */ + @JsonProperty("kind") WorkerAdmissionKind kind, + /** Canonical logical message identity, independent of queueItemId. */ + @JsonProperty("messageId") String messageId, + /** May be omitted only for the matching current worker user.message. */ + @JsonProperty("event") WorkerEventReference event, + /** Actual AHP participant Turn UUID, not a native turn counter or provenance signal. */ + @JsonProperty("ahpTurnId") UUID ahpTurnId +) { +} diff --git a/java/sdk/src/generated/java/com/github/copilot/generated/WorkerAdmissionKind.java b/java/sdk/src/generated/java/com/github/copilot/generated/WorkerAdmissionKind.java new file mode 100644 index 0000000000..5cb66b3ebf --- /dev/null +++ b/java/sdk/src/generated/java/com/github/copilot/generated/WorkerAdmissionKind.java @@ -0,0 +1,35 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + *--------------------------------------------------------------------------------------------*/ + +// AUTO-GENERATED FILE - DO NOT EDIT +// Generated from: session-events.schema.json + +package com.github.copilot.generated; + +import javax.annotation.processing.Generated; + +/** + * Why this exact worker admission was made. + * + * @since 1.0.0 + */ +@javax.annotation.processing.Generated("copilot-sdk-codegen") +public enum WorkerAdmissionKind { + /** The {@code queued_input} variant. */ + QUEUED_INPUT("queued_input"), + /** The {@code system_continuation} variant. */ + SYSTEM_CONTINUATION("system_continuation"); + + private final String value; + WorkerAdmissionKind(String value) { this.value = value; } + @com.fasterxml.jackson.annotation.JsonValue + public String getValue() { return value; } + @com.fasterxml.jackson.annotation.JsonCreator + public static WorkerAdmissionKind fromValue(String value) { + for (WorkerAdmissionKind v : values()) { + if (v.value.equals(value)) return v; + } + throw new IllegalArgumentException("Unknown WorkerAdmissionKind value: " + value); + } +} diff --git a/java/sdk/src/generated/java/com/github/copilot/generated/WorkerBridgeObservation.java b/java/sdk/src/generated/java/com/github/copilot/generated/WorkerBridgeObservation.java new file mode 100644 index 0000000000..76fe42af6c --- /dev/null +++ b/java/sdk/src/generated/java/com/github/copilot/generated/WorkerBridgeObservation.java @@ -0,0 +1,29 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + *--------------------------------------------------------------------------------------------*/ + +// AUTO-GENERATED FILE - DO NOT EDIT +// Generated from: session-events.schema.json + +package com.github.copilot.generated; + +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import com.fasterxml.jackson.annotation.JsonInclude; +import com.fasterxml.jackson.annotation.JsonProperty; +import javax.annotation.processing.Generated; + +/** + * One indivisible source-to-reported bridge observation, not a root alias. + * + * @since 1.0.0 + */ +@javax.annotation.processing.Generated("copilot-sdk-codegen") +@JsonInclude(JsonInclude.Include.NON_NULL) +@JsonIgnoreProperties(ignoreUnknown = true) +public record WorkerBridgeObservation( + /** Full event received by this bridge. */ + @JsonProperty("source") WorkerEventReference source, + /** Full occurrence actually emitted by that bridge. */ + @JsonProperty("reported") WorkerEventReference reported +) { +} diff --git a/java/sdk/src/generated/java/com/github/copilot/generated/WorkerCausality.java b/java/sdk/src/generated/java/com/github/copilot/generated/WorkerCausality.java new file mode 100644 index 0000000000..6ae6cb9454 --- /dev/null +++ b/java/sdk/src/generated/java/com/github/copilot/generated/WorkerCausality.java @@ -0,0 +1,36 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + *--------------------------------------------------------------------------------------------*/ + +// AUTO-GENERATED FILE - DO NOT EDIT +// Generated from: session-events.schema.json + +package com.github.copilot.generated; + +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import com.fasterxml.jackson.annotation.JsonInclude; +import com.fasterxml.jackson.annotation.JsonProperty; +import java.util.List; +import javax.annotation.processing.Generated; + +/** + * Optional v1 worker diagnostics. The compact UTF-8 {"workerCausality":value} +must fit 4096 bytes. Ignore invalid/unknown/oversize metadata, not the product event. + * + * @since 1.0.0 + */ +@javax.annotation.processing.Generated("copilot-sdk-codegen") +@JsonInclude(JsonInclude.Include.NON_NULL) +@JsonIgnoreProperties(ignoreUnknown = true) +public record WorkerCausality( + /** Supported version, exactly 1. */ + @JsonProperty("version") Long version, + /** Provenance of this enclosing observation; never inferred from other fields. */ + @JsonProperty("observationProvenance") WorkerObservationProvenance observationProvenance, + /** Sources in capture order, at most 32. Absent/unknown is not known-empty. */ + @JsonProperty("sources") List sources, + /** Complete placement-aware observed capture, not global causality or execution success. +Empty true requires explicit native invocation attestation. */ + @JsonProperty("captureComplete") Boolean captureComplete +) { +} diff --git a/java/sdk/src/generated/java/com/github/copilot/generated/WorkerEventReference.java b/java/sdk/src/generated/java/com/github/copilot/generated/WorkerEventReference.java new file mode 100644 index 0000000000..4a5f5851ed --- /dev/null +++ b/java/sdk/src/generated/java/com/github/copilot/generated/WorkerEventReference.java @@ -0,0 +1,36 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + *--------------------------------------------------------------------------------------------*/ + +// AUTO-GENERATED FILE - DO NOT EDIT +// Generated from: session-events.schema.json + +package com.github.copilot.generated; + +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import com.fasterxml.jackson.annotation.JsonInclude; +import com.fasterxml.jackson.annotation.JsonProperty; +import java.util.UUID; +import javax.annotation.processing.Generated; + +/** + * Exact observed event identity. No private registration generation or execution handle. + * + * @since 1.0.0 + */ +@javax.annotation.processing.Generated("copilot-sdk-codegen") +@JsonInclude(JsonInclude.Include.NON_NULL) +@JsonIgnoreProperties(ignoreUnknown = true) +public record WorkerEventReference( + /** Actual runtime session scope. */ + @JsonProperty("sessionId") String sessionId, + /** Actual event occurrence UUID; copied without normalization. */ + @JsonProperty("eventId") UUID eventId, + /** Actual event agent scope, absent for a root occurrence. */ + @JsonProperty("agentId") String agentId, + /** Type of the observed occurrence. */ + @JsonProperty("eventType") WorkerEventType eventType, + /** Explicit provenance of this reference. */ + @JsonProperty("provenance") WorkerObservationProvenance provenance +) { +} diff --git a/java/sdk/src/generated/java/com/github/copilot/generated/WorkerEventType.java b/java/sdk/src/generated/java/com/github/copilot/generated/WorkerEventType.java new file mode 100644 index 0000000000..f89bcda964 --- /dev/null +++ b/java/sdk/src/generated/java/com/github/copilot/generated/WorkerEventType.java @@ -0,0 +1,41 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + *--------------------------------------------------------------------------------------------*/ + +// AUTO-GENERATED FILE - DO NOT EDIT +// Generated from: session-events.schema.json + +package com.github.copilot.generated; + +import javax.annotation.processing.Generated; + +/** + * Supported observed occurrences. Chronological parentId is not a causal reference. + * + * @since 1.0.0 + */ +@javax.annotation.processing.Generated("copilot-sdk-codegen") +public enum WorkerEventType { + /** The {@code tool.execution_start} variant. */ + TOOL_EXECUTION_START("tool.execution_start"), + /** The {@code user.message} variant. */ + USER_MESSAGE("user.message"), + /** The {@code subagent.completed} variant. */ + SUBAGENT_COMPLETED("subagent.completed"), + /** The {@code system.notification} variant. */ + SYSTEM_NOTIFICATION("system.notification"), + /** The {@code assistant.turn_start} variant. */ + ASSISTANT_TURN_START("assistant.turn_start"); + + private final String value; + WorkerEventType(String value) { this.value = value; } + @com.fasterxml.jackson.annotation.JsonValue + public String getValue() { return value; } + @com.fasterxml.jackson.annotation.JsonCreator + public static WorkerEventType fromValue(String value) { + for (WorkerEventType v : values()) { + if (v.value.equals(value)) return v; + } + throw new IllegalArgumentException("Unknown WorkerEventType value: " + value); + } +} diff --git a/java/sdk/src/generated/java/com/github/copilot/generated/WorkerInput.java b/java/sdk/src/generated/java/com/github/copilot/generated/WorkerInput.java new file mode 100644 index 0000000000..0e06852e94 --- /dev/null +++ b/java/sdk/src/generated/java/com/github/copilot/generated/WorkerInput.java @@ -0,0 +1,35 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + *--------------------------------------------------------------------------------------------*/ + +// AUTO-GENERATED FILE - DO NOT EDIT +// Generated from: session-events.schema.json + +package com.github.copilot.generated; + +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import com.fasterxml.jackson.annotation.JsonInclude; +import com.fasterxml.jackson.annotation.JsonProperty; +import java.util.List; +import java.util.UUID; +import javax.annotation.processing.Generated; + +/** + * Exact accepted worker input, distinct from a message, event, caller correlation or Turn. + * + * @since 1.0.0 + */ +@javax.annotation.processing.Generated("copilot-sdk-codegen") +@JsonInclude(JsonInclude.Include.NON_NULL) +@JsonIgnoreProperties(ignoreUnknown = true) +public record WorkerInput( + /** UUID allocated for this queue item by the admitting producer. */ + @JsonProperty("queueItemId") UUID queueItemId, + /** Actual recipient task. */ + @JsonProperty("agentId") String agentId, + /** Original invoking occurrence, never replaced by a reported/root alias. */ + @JsonProperty("sender") WorkerEventReference sender, + /** Exact captured edges in producer order. Requires sender; at most 32 whole pairs. */ + @JsonProperty("senderBridges") List senderBridges +) { +} diff --git a/java/sdk/src/generated/java/com/github/copilot/generated/WorkerNotificationMode.java b/java/sdk/src/generated/java/com/github/copilot/generated/WorkerNotificationMode.java new file mode 100644 index 0000000000..cbdc6d7687 --- /dev/null +++ b/java/sdk/src/generated/java/com/github/copilot/generated/WorkerNotificationMode.java @@ -0,0 +1,35 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + *--------------------------------------------------------------------------------------------*/ + +// AUTO-GENERATED FILE - DO NOT EDIT +// Generated from: session-events.schema.json + +package com.github.copilot.generated; + +import javax.annotation.processing.Generated; + +/** + * How the owned notification was consumed. + * + * @since 1.0.0 + */ +@javax.annotation.processing.Generated("copilot-sdk-codegen") +public enum WorkerNotificationMode { + /** The {@code queued} variant. */ + QUEUED("queued"), + /** The {@code immediate} variant. */ + IMMEDIATE("immediate"); + + private final String value; + WorkerNotificationMode(String value) { this.value = value; } + @com.fasterxml.jackson.annotation.JsonValue + public String getValue() { return value; } + @com.fasterxml.jackson.annotation.JsonCreator + public static WorkerNotificationMode fromValue(String value) { + for (WorkerNotificationMode v : values()) { + if (v.value.equals(value)) return v; + } + throw new IllegalArgumentException("Unknown WorkerNotificationMode value: " + value); + } +} diff --git a/java/sdk/src/generated/java/com/github/copilot/generated/WorkerNotificationReference.java b/java/sdk/src/generated/java/com/github/copilot/generated/WorkerNotificationReference.java new file mode 100644 index 0000000000..21661e059e --- /dev/null +++ b/java/sdk/src/generated/java/com/github/copilot/generated/WorkerNotificationReference.java @@ -0,0 +1,32 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + *--------------------------------------------------------------------------------------------*/ + +// AUTO-GENERATED FILE - DO NOT EDIT +// Generated from: session-events.schema.json + +package com.github.copilot.generated; + +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import com.fasterxml.jackson.annotation.JsonInclude; +import com.fasterxml.jackson.annotation.JsonProperty; +import java.util.UUID; +import javax.annotation.processing.Generated; + +/** + * Exact delivery and optional occurrence of a consumed worker notification. + * + * @since 1.0.0 + */ +@javax.annotation.processing.Generated("copilot-sdk-codegen") +@JsonInclude(JsonInclude.Include.NON_NULL) +@JsonIgnoreProperties(ignoreUnknown = true) +public record WorkerNotificationReference( + /** Actual notificationDeliveryId UUID. */ + @JsonProperty("deliveryId") UUID deliveryId, + /** May be omitted only on the matching current system.notification. */ + @JsonProperty("event") WorkerEventReference event, + /** Actual consumption mode. */ + @JsonProperty("mode") WorkerNotificationMode mode +) { +} diff --git a/java/sdk/src/generated/java/com/github/copilot/generated/WorkerObservationProvenance.java b/java/sdk/src/generated/java/com/github/copilot/generated/WorkerObservationProvenance.java new file mode 100644 index 0000000000..2e10b629b2 --- /dev/null +++ b/java/sdk/src/generated/java/com/github/copilot/generated/WorkerObservationProvenance.java @@ -0,0 +1,35 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + *--------------------------------------------------------------------------------------------*/ + +// AUTO-GENERATED FILE - DO NOT EDIT +// Generated from: session-events.schema.json + +package com.github.copilot.generated; + +import javax.annotation.processing.Generated; + +/** + * Producer of an observation, not the execution location of every referenced source. + * + * @since 1.0.0 + */ +@javax.annotation.processing.Generated("copilot-sdk-codegen") +public enum WorkerObservationProvenance { + /** The {@code native} variant. */ + NATIVE("native"), + /** The {@code ahp_coordinator} variant. */ + AHP_COORDINATOR("ahp_coordinator"); + + private final String value; + WorkerObservationProvenance(String value) { this.value = value; } + @com.fasterxml.jackson.annotation.JsonValue + public String getValue() { return value; } + @com.fasterxml.jackson.annotation.JsonCreator + public static WorkerObservationProvenance fromValue(String value) { + for (WorkerObservationProvenance v : values()) { + if (v.value.equals(value)) return v; + } + throw new IllegalArgumentException("Unknown WorkerObservationProvenance value: " + value); + } +} diff --git a/java/sdk/src/generated/java/com/github/copilot/generated/WorkerSource.java b/java/sdk/src/generated/java/com/github/copilot/generated/WorkerSource.java new file mode 100644 index 0000000000..33dab65b9c --- /dev/null +++ b/java/sdk/src/generated/java/com/github/copilot/generated/WorkerSource.java @@ -0,0 +1,38 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + *--------------------------------------------------------------------------------------------*/ + +// AUTO-GENERATED FILE - DO NOT EDIT +// Generated from: session-events.schema.json + +package com.github.copilot.generated; + +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import com.fasterxml.jackson.annotation.JsonInclude; +import com.fasterxml.jackson.annotation.JsonProperty; +import java.util.List; +import javax.annotation.processing.Generated; + +/** + * One captured source at this placement and observation boundary. + * + * @since 1.0.0 + */ +@javax.annotation.processing.Generated("copilot-sdk-codegen") +@JsonInclude(JsonInclude.Include.NON_NULL) +@JsonIgnoreProperties(ignoreUnknown = true) +public record WorkerSource( + /** Indivisible accepted input identity. */ + @JsonProperty("input") WorkerInput input, + /** Actual admissions in capture order; at most 32. */ + @JsonProperty("admissions") List admissions, + /** Applicable observations were all captured here, never work completion or success. */ + @JsonProperty("captureComplete") Boolean captureComplete, + /** Actual completion emission only. Absence is not an execution outcome. */ + @JsonProperty("completion") WorkerEventReference completion, + /** Actual consumed notification, when observed. */ + @JsonProperty("notification") WorkerNotificationReference notification, + /** Exact already-open iteration when an immediate notification was consumed. */ + @JsonProperty("admittedDuring") WorkerEventReference admittedDuring +) { +} diff --git a/java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionTasksSendMessageResult.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionTasksSendMessageResult.java index 4bdf603fb2..c0cf4a5fa6 100644 --- a/java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionTasksSendMessageResult.java +++ b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionTasksSendMessageResult.java @@ -24,9 +24,16 @@ @JsonInclude(JsonInclude.Include.NON_NULL) @JsonIgnoreProperties(ignoreUnknown = true) public record SessionTasksSendMessageResult( + /** Optional exact queue admission receipt on sent=true only. No implicit event or execution-success claim. */ + @com.fasterxml.jackson.databind.annotation.JsonDeserialize(using = com.github.copilot.WorkerCausalityDeserializer.class) + @JsonProperty("workerCausality") WorkerCausality workerCausality, /** Whether the message was successfully delivered or steered */ @JsonProperty("sent") Boolean sent, /** Error message if delivery failed */ @JsonProperty("error") String error ) { + /** Creates a value without optional worker diagnostics. */ + public SessionTasksSendMessageResult(Boolean sent, String error) { + this(null, sent, error); + } } diff --git a/java/sdk/src/generated/java/com/github/copilot/generated/rpc/WorkerAdmission.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/WorkerAdmission.java new file mode 100644 index 0000000000..afca7361b2 --- /dev/null +++ b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/WorkerAdmission.java @@ -0,0 +1,34 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + *--------------------------------------------------------------------------------------------*/ + +// AUTO-GENERATED FILE - DO NOT EDIT +// Generated from: api.schema.json + +package com.github.copilot.generated.rpc; + +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import com.fasterxml.jackson.annotation.JsonInclude; +import com.fasterxml.jackson.annotation.JsonProperty; +import java.util.UUID; +import javax.annotation.processing.Generated; + +/** + * An observed worker admission, not a claim that execution succeeded. + * + * @since 1.0.0 + */ +@javax.annotation.processing.Generated("copilot-sdk-codegen") +@JsonInclude(JsonInclude.Include.NON_NULL) +@JsonIgnoreProperties(ignoreUnknown = true) +public record WorkerAdmission( + /** The producer's admission kind. */ + @JsonProperty("kind") WorkerAdmissionKind kind, + /** Canonical logical message identity, independent of queueItemId. */ + @JsonProperty("messageId") String messageId, + /** May be omitted only for the matching current worker user.message. */ + @JsonProperty("event") WorkerEventReference event, + /** Actual AHP participant Turn UUID, not a native turn counter or provenance signal. */ + @JsonProperty("ahpTurnId") UUID ahpTurnId +) { +} diff --git a/java/sdk/src/generated/java/com/github/copilot/generated/rpc/WorkerAdmissionKind.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/WorkerAdmissionKind.java new file mode 100644 index 0000000000..7259f1e3a8 --- /dev/null +++ b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/WorkerAdmissionKind.java @@ -0,0 +1,35 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + *--------------------------------------------------------------------------------------------*/ + +// AUTO-GENERATED FILE - DO NOT EDIT +// Generated from: api.schema.json + +package com.github.copilot.generated.rpc; + +import javax.annotation.processing.Generated; + +/** + * Why this exact worker admission was made. + * + * @since 1.0.0 + */ +@javax.annotation.processing.Generated("copilot-sdk-codegen") +public enum WorkerAdmissionKind { + /** The {@code queued_input} variant. */ + QUEUED_INPUT("queued_input"), + /** The {@code system_continuation} variant. */ + SYSTEM_CONTINUATION("system_continuation"); + + private final String value; + WorkerAdmissionKind(String value) { this.value = value; } + @com.fasterxml.jackson.annotation.JsonValue + public String getValue() { return value; } + @com.fasterxml.jackson.annotation.JsonCreator + public static WorkerAdmissionKind fromValue(String value) { + for (WorkerAdmissionKind v : values()) { + if (v.value.equals(value)) return v; + } + throw new IllegalArgumentException("Unknown WorkerAdmissionKind value: " + value); + } +} diff --git a/java/sdk/src/generated/java/com/github/copilot/generated/rpc/WorkerBridgeObservation.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/WorkerBridgeObservation.java new file mode 100644 index 0000000000..713d449430 --- /dev/null +++ b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/WorkerBridgeObservation.java @@ -0,0 +1,29 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + *--------------------------------------------------------------------------------------------*/ + +// AUTO-GENERATED FILE - DO NOT EDIT +// Generated from: api.schema.json + +package com.github.copilot.generated.rpc; + +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import com.fasterxml.jackson.annotation.JsonInclude; +import com.fasterxml.jackson.annotation.JsonProperty; +import javax.annotation.processing.Generated; + +/** + * One indivisible source-to-reported bridge observation, not a root alias. + * + * @since 1.0.0 + */ +@javax.annotation.processing.Generated("copilot-sdk-codegen") +@JsonInclude(JsonInclude.Include.NON_NULL) +@JsonIgnoreProperties(ignoreUnknown = true) +public record WorkerBridgeObservation( + /** Full event received by this bridge. */ + @JsonProperty("source") WorkerEventReference source, + /** Full occurrence actually emitted by that bridge. */ + @JsonProperty("reported") WorkerEventReference reported +) { +} diff --git a/java/sdk/src/generated/java/com/github/copilot/generated/rpc/WorkerCausality.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/WorkerCausality.java new file mode 100644 index 0000000000..ade846df3d --- /dev/null +++ b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/WorkerCausality.java @@ -0,0 +1,36 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + *--------------------------------------------------------------------------------------------*/ + +// AUTO-GENERATED FILE - DO NOT EDIT +// Generated from: api.schema.json + +package com.github.copilot.generated.rpc; + +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import com.fasterxml.jackson.annotation.JsonInclude; +import com.fasterxml.jackson.annotation.JsonProperty; +import java.util.List; +import javax.annotation.processing.Generated; + +/** + * Optional v1 worker diagnostics. The compact UTF-8 {"workerCausality":value} +must fit 4096 bytes. Ignore invalid/unknown/oversize metadata, not the product event. + * + * @since 1.0.0 + */ +@javax.annotation.processing.Generated("copilot-sdk-codegen") +@JsonInclude(JsonInclude.Include.NON_NULL) +@JsonIgnoreProperties(ignoreUnknown = true) +public record WorkerCausality( + /** Supported version, exactly 1. */ + @JsonProperty("version") Long version, + /** Provenance of this enclosing observation; never inferred from other fields. */ + @JsonProperty("observationProvenance") WorkerObservationProvenance observationProvenance, + /** Sources in capture order, at most 32. Absent/unknown is not known-empty. */ + @JsonProperty("sources") List sources, + /** Complete placement-aware observed capture, not global causality or execution success. +Empty true requires explicit native invocation attestation. */ + @JsonProperty("captureComplete") Boolean captureComplete +) { +} diff --git a/java/sdk/src/generated/java/com/github/copilot/generated/rpc/WorkerEventReference.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/WorkerEventReference.java new file mode 100644 index 0000000000..4aff22471e --- /dev/null +++ b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/WorkerEventReference.java @@ -0,0 +1,36 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + *--------------------------------------------------------------------------------------------*/ + +// AUTO-GENERATED FILE - DO NOT EDIT +// Generated from: api.schema.json + +package com.github.copilot.generated.rpc; + +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import com.fasterxml.jackson.annotation.JsonInclude; +import com.fasterxml.jackson.annotation.JsonProperty; +import java.util.UUID; +import javax.annotation.processing.Generated; + +/** + * Exact observed event identity. No private registration generation or execution handle. + * + * @since 1.0.0 + */ +@javax.annotation.processing.Generated("copilot-sdk-codegen") +@JsonInclude(JsonInclude.Include.NON_NULL) +@JsonIgnoreProperties(ignoreUnknown = true) +public record WorkerEventReference( + /** Actual runtime session scope. */ + @JsonProperty("sessionId") String sessionId, + /** Actual event occurrence UUID; copied without normalization. */ + @JsonProperty("eventId") UUID eventId, + /** Actual event agent scope, absent for a root occurrence. */ + @JsonProperty("agentId") String agentId, + /** Type of the observed occurrence. */ + @JsonProperty("eventType") WorkerEventType eventType, + /** Explicit provenance of this reference. */ + @JsonProperty("provenance") WorkerObservationProvenance provenance +) { +} diff --git a/java/sdk/src/generated/java/com/github/copilot/generated/rpc/WorkerEventType.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/WorkerEventType.java new file mode 100644 index 0000000000..118938f2ad --- /dev/null +++ b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/WorkerEventType.java @@ -0,0 +1,41 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + *--------------------------------------------------------------------------------------------*/ + +// AUTO-GENERATED FILE - DO NOT EDIT +// Generated from: api.schema.json + +package com.github.copilot.generated.rpc; + +import javax.annotation.processing.Generated; + +/** + * Supported observed occurrences. Chronological parentId is not a causal reference. + * + * @since 1.0.0 + */ +@javax.annotation.processing.Generated("copilot-sdk-codegen") +public enum WorkerEventType { + /** The {@code tool.execution_start} variant. */ + TOOL_EXECUTION_START("tool.execution_start"), + /** The {@code user.message} variant. */ + USER_MESSAGE("user.message"), + /** The {@code subagent.completed} variant. */ + SUBAGENT_COMPLETED("subagent.completed"), + /** The {@code system.notification} variant. */ + SYSTEM_NOTIFICATION("system.notification"), + /** The {@code assistant.turn_start} variant. */ + ASSISTANT_TURN_START("assistant.turn_start"); + + private final String value; + WorkerEventType(String value) { this.value = value; } + @com.fasterxml.jackson.annotation.JsonValue + public String getValue() { return value; } + @com.fasterxml.jackson.annotation.JsonCreator + public static WorkerEventType fromValue(String value) { + for (WorkerEventType v : values()) { + if (v.value.equals(value)) return v; + } + throw new IllegalArgumentException("Unknown WorkerEventType value: " + value); + } +} diff --git a/java/sdk/src/generated/java/com/github/copilot/generated/rpc/WorkerInput.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/WorkerInput.java new file mode 100644 index 0000000000..b114e57c20 --- /dev/null +++ b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/WorkerInput.java @@ -0,0 +1,35 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + *--------------------------------------------------------------------------------------------*/ + +// AUTO-GENERATED FILE - DO NOT EDIT +// Generated from: api.schema.json + +package com.github.copilot.generated.rpc; + +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import com.fasterxml.jackson.annotation.JsonInclude; +import com.fasterxml.jackson.annotation.JsonProperty; +import java.util.List; +import java.util.UUID; +import javax.annotation.processing.Generated; + +/** + * Exact accepted worker input, distinct from a message, event, caller correlation or Turn. + * + * @since 1.0.0 + */ +@javax.annotation.processing.Generated("copilot-sdk-codegen") +@JsonInclude(JsonInclude.Include.NON_NULL) +@JsonIgnoreProperties(ignoreUnknown = true) +public record WorkerInput( + /** UUID allocated for this queue item by the admitting producer. */ + @JsonProperty("queueItemId") UUID queueItemId, + /** Actual recipient task. */ + @JsonProperty("agentId") String agentId, + /** Original invoking occurrence, never replaced by a reported/root alias. */ + @JsonProperty("sender") WorkerEventReference sender, + /** Exact captured edges in producer order. Requires sender; at most 32 whole pairs. */ + @JsonProperty("senderBridges") List senderBridges +) { +} diff --git a/java/sdk/src/generated/java/com/github/copilot/generated/rpc/WorkerNotificationMode.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/WorkerNotificationMode.java new file mode 100644 index 0000000000..859d5e864d --- /dev/null +++ b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/WorkerNotificationMode.java @@ -0,0 +1,35 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + *--------------------------------------------------------------------------------------------*/ + +// AUTO-GENERATED FILE - DO NOT EDIT +// Generated from: api.schema.json + +package com.github.copilot.generated.rpc; + +import javax.annotation.processing.Generated; + +/** + * How the owned notification was consumed. + * + * @since 1.0.0 + */ +@javax.annotation.processing.Generated("copilot-sdk-codegen") +public enum WorkerNotificationMode { + /** The {@code queued} variant. */ + QUEUED("queued"), + /** The {@code immediate} variant. */ + IMMEDIATE("immediate"); + + private final String value; + WorkerNotificationMode(String value) { this.value = value; } + @com.fasterxml.jackson.annotation.JsonValue + public String getValue() { return value; } + @com.fasterxml.jackson.annotation.JsonCreator + public static WorkerNotificationMode fromValue(String value) { + for (WorkerNotificationMode v : values()) { + if (v.value.equals(value)) return v; + } + throw new IllegalArgumentException("Unknown WorkerNotificationMode value: " + value); + } +} diff --git a/java/sdk/src/generated/java/com/github/copilot/generated/rpc/WorkerNotificationReference.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/WorkerNotificationReference.java new file mode 100644 index 0000000000..8f75f8c4c1 --- /dev/null +++ b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/WorkerNotificationReference.java @@ -0,0 +1,32 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + *--------------------------------------------------------------------------------------------*/ + +// AUTO-GENERATED FILE - DO NOT EDIT +// Generated from: api.schema.json + +package com.github.copilot.generated.rpc; + +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import com.fasterxml.jackson.annotation.JsonInclude; +import com.fasterxml.jackson.annotation.JsonProperty; +import java.util.UUID; +import javax.annotation.processing.Generated; + +/** + * Exact delivery and optional occurrence of a consumed worker notification. + * + * @since 1.0.0 + */ +@javax.annotation.processing.Generated("copilot-sdk-codegen") +@JsonInclude(JsonInclude.Include.NON_NULL) +@JsonIgnoreProperties(ignoreUnknown = true) +public record WorkerNotificationReference( + /** Actual notificationDeliveryId UUID. */ + @JsonProperty("deliveryId") UUID deliveryId, + /** May be omitted only on the matching current system.notification. */ + @JsonProperty("event") WorkerEventReference event, + /** Actual consumption mode. */ + @JsonProperty("mode") WorkerNotificationMode mode +) { +} diff --git a/java/sdk/src/generated/java/com/github/copilot/generated/rpc/WorkerObservationProvenance.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/WorkerObservationProvenance.java new file mode 100644 index 0000000000..07cc0b671b --- /dev/null +++ b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/WorkerObservationProvenance.java @@ -0,0 +1,35 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + *--------------------------------------------------------------------------------------------*/ + +// AUTO-GENERATED FILE - DO NOT EDIT +// Generated from: api.schema.json + +package com.github.copilot.generated.rpc; + +import javax.annotation.processing.Generated; + +/** + * Producer of an observation, not the execution location of every referenced source. + * + * @since 1.0.0 + */ +@javax.annotation.processing.Generated("copilot-sdk-codegen") +public enum WorkerObservationProvenance { + /** The {@code native} variant. */ + NATIVE("native"), + /** The {@code ahp_coordinator} variant. */ + AHP_COORDINATOR("ahp_coordinator"); + + private final String value; + WorkerObservationProvenance(String value) { this.value = value; } + @com.fasterxml.jackson.annotation.JsonValue + public String getValue() { return value; } + @com.fasterxml.jackson.annotation.JsonCreator + public static WorkerObservationProvenance fromValue(String value) { + for (WorkerObservationProvenance v : values()) { + if (v.value.equals(value)) return v; + } + throw new IllegalArgumentException("Unknown WorkerObservationProvenance value: " + value); + } +} diff --git a/java/sdk/src/generated/java/com/github/copilot/generated/rpc/WorkerSource.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/WorkerSource.java new file mode 100644 index 0000000000..af24785ff4 --- /dev/null +++ b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/WorkerSource.java @@ -0,0 +1,38 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + *--------------------------------------------------------------------------------------------*/ + +// AUTO-GENERATED FILE - DO NOT EDIT +// Generated from: api.schema.json + +package com.github.copilot.generated.rpc; + +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import com.fasterxml.jackson.annotation.JsonInclude; +import com.fasterxml.jackson.annotation.JsonProperty; +import java.util.List; +import javax.annotation.processing.Generated; + +/** + * One captured source at this placement and observation boundary. + * + * @since 1.0.0 + */ +@javax.annotation.processing.Generated("copilot-sdk-codegen") +@JsonInclude(JsonInclude.Include.NON_NULL) +@JsonIgnoreProperties(ignoreUnknown = true) +public record WorkerSource( + /** Indivisible accepted input identity. */ + @JsonProperty("input") WorkerInput input, + /** Actual admissions in capture order; at most 32. */ + @JsonProperty("admissions") List admissions, + /** Applicable observations were all captured here, never work completion or success. */ + @JsonProperty("captureComplete") Boolean captureComplete, + /** Actual completion emission only. Absence is not an execution outcome. */ + @JsonProperty("completion") WorkerEventReference completion, + /** Actual consumed notification, when observed. */ + @JsonProperty("notification") WorkerNotificationReference notification, + /** Exact already-open iteration when an immediate notification was consumed. */ + @JsonProperty("admittedDuring") WorkerEventReference admittedDuring +) { +} diff --git a/java/sdk/src/main/java/com/github/copilot/WorkerCausalityDeserializer.java b/java/sdk/src/main/java/com/github/copilot/WorkerCausalityDeserializer.java new file mode 100644 index 0000000000..ac3c7b0126 --- /dev/null +++ b/java/sdk/src/main/java/com/github/copilot/WorkerCausalityDeserializer.java @@ -0,0 +1,185 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + *--------------------------------------------------------------------------------------------*/ + +package com.github.copilot; + +import com.fasterxml.jackson.core.JsonParser; +import com.fasterxml.jackson.databind.BeanProperty; +import com.fasterxml.jackson.databind.DeserializationContext; +import com.fasterxml.jackson.databind.JavaType; +import com.fasterxml.jackson.databind.JsonDeserializer; +import com.fasterxml.jackson.databind.JsonNode; +import com.fasterxml.jackson.databind.deser.ContextualDeserializer; +import java.io.IOException; +import java.nio.charset.StandardCharsets; +import java.util.logging.Logger; +import java.util.regex.Pattern; + +/** + * Tolerant decoder for optional worker diagnostics, not a causal association + * validator. + */ +public final class WorkerCausalityDeserializer extends JsonDeserializer implements ContextualDeserializer { + private static final Logger LOG = Logger.getLogger(WorkerCausalityDeserializer.class.getName()); + private static final Pattern UUID = Pattern.compile("[0-9a-fA-F]{8}-(?:[0-9a-fA-F]{4}-){3}[0-9a-fA-F]{12}"); + private static final Pattern SESSION = Pattern.compile("[A-Za-z0-9_-]+"); + private final JavaType target; + + /** Creates the annotation's contextual decoder. */ + public WorkerCausalityDeserializer() { + this(null); + } + + private WorkerCausalityDeserializer(JavaType target) { + this.target = target; + } + + @Override + public JsonDeserializer createContextual(DeserializationContext context, BeanProperty property) { + return new WorkerCausalityDeserializer(property.getType()); + } + + @Override + public Object deserialize(JsonParser parser, DeserializationContext context) throws IOException { + JsonNode value = parser.readValueAsTree(); + if (small(value, new int[]{4096}, 0) && supported(value)) { + String field = "{\"workerCausality\":" + value + "}"; + if (field.getBytes(StandardCharsets.UTF_8).length <= 4096) { + try { + return context.readTreeAsValue(value, target); + } catch (IOException | IllegalArgumentException ignored) { + // Optional diagnostics must not reject the product event. + } + } + } + LOG.warning("Ignoring invalid, unsupported or oversized workerCausality metadata"); + return null; + } + + private static boolean supported(JsonNode value) { + if (!fields(value, "version", "observationProvenance", "sources", "captureComplete") + || !value.path("version").isIntegralNumber() || !value.path("version").canConvertToInt() + || value.path("version").asInt() != 1 + || !(value.path("observationProvenance").asText().equals("native") + || value.path("observationProvenance").asText().equals("ahp_coordinator")) + || !value.path("captureComplete").isBoolean() || !value.path("sources").isArray() + || value.path("sources").size() > 32) { + return false; + } + for (JsonNode source : value.path("sources")) { + JsonNode input = source.path("input"); + if (!fields(source, "input", "admissions", "captureComplete", "completion", "notification", + "admittedDuring") || !fields(input, "queueItemId", "agentId", "sender", "senderBridges") + || !source.path("captureComplete").isBoolean() || !source.path("admissions").isArray() + || source.path("admissions").size() > 32 || !input.isObject() + || (value.path("captureComplete").asBoolean() && !source.path("captureComplete").asBoolean()) + || !uuid(input.path("queueItemId")) || !text(input.path("agentId")) + || !optionalReference(input, "sender", "tool.execution_start") + || !optionalReference(source, "completion", "subagent.completed") + || !optionalReference(source, "admittedDuring", "assistant.turn_start")) { + return false; + } + if (input.has("senderBridges")) { + JsonNode edges = input.path("senderBridges"); + if (!input.has("sender") || !edges.isArray() || edges.size() > 32) { + return false; + } + for (JsonNode edge : edges) { + if (!fields(edge, "source", "reported") || !reference(edge.path("source"), "tool.execution_start") + || !reference(edge.path("reported"), "tool.execution_start")) { + return false; + } + } + } + for (JsonNode admission : source.path("admissions")) { + String kind = admission.path("kind").asText(); + if (!fields(admission, "kind", "messageId", "event", "ahpTurnId") + || !(kind.equals("queued_input") || kind.equals("system_continuation")) + || !text(admission.path("messageId")) + || (admission.has("ahpTurnId") && !uuid(admission.path("ahpTurnId"))) + || (admission.has("event") && (!reference(admission.path("event"), "user.message") + || !admission.path("event").path("agentId").equals(input.path("agentId"))))) { + return false; + } + } + if (source.has("notification")) { + JsonNode notification = source.path("notification"); + String mode = notification.path("mode").asText(); + if (!fields(notification, "deliveryId", "event", "mode") || !uuid(notification.path("deliveryId")) + || !(mode.equals("queued") || mode.equals("immediate")) + || !optionalReference(notification, "event", "system.notification")) { + return false; + } + } + } + return true; + } + + private static boolean fields(JsonNode value, String... allowed) { + if (!value.isObject()) { + return false; + } + var names = value.fieldNames(); + while (names.hasNext()) { + String name = names.next(); + boolean found = false; + for (String candidate : allowed) { + if (candidate.equals(name)) { + found = true; + break; + } + } + if (!found) { + return false; + } + } + return true; + } + + private static boolean text(JsonNode value) { + return value.isTextual() && !value.textValue().isEmpty(); + } + + private static boolean uuid(JsonNode value) { + return value.isTextual() && UUID.matcher(value.textValue()).matches(); + } + + private static boolean optionalReference(JsonNode value, String field, String eventType) { + return !value.has(field) || reference(value.path(field), eventType); + } + + private static boolean reference(JsonNode value, String eventType) { + return fields(value, "sessionId", "eventId", "agentId", "eventType", "provenance") + && text(value.path("sessionId")) && SESSION.matcher(value.path("sessionId").textValue()).matches() + && uuid(value.path("eventId")) && (!value.has("agentId") || text(value.path("agentId"))) + && value.path("eventType").asText().equals(eventType) + && (value.path("provenance").asText().equals("native") + || value.path("provenance").asText().equals("ahp_coordinator")); + } + + private static boolean small(JsonNode value, int[] remaining, int depth) { + if (--remaining[0] < 0 || depth > 32) { + return false; + } + if (value.isTextual()) { + remaining[0] -= value.textValue().length(); + } else if (value.isObject()) { + var fields = value.fields(); + while (fields.hasNext()) { + var field = fields.next(); + remaining[0] -= field.getKey().length(); + if (!small(field.getValue(), remaining, depth + 1)) { + return false; + } + } + } else if (value.isArray()) { + for (JsonNode item : value) { + if (!small(item, remaining, depth + 1)) { + return false; + } + } + } + return remaining[0] >= 0; + } +} diff --git a/java/sdk/src/test/java/com/github/copilot/WorkerCausalityTest.java b/java/sdk/src/test/java/com/github/copilot/WorkerCausalityTest.java new file mode 100644 index 0000000000..8aa50df749 --- /dev/null +++ b/java/sdk/src/test/java/com/github/copilot/WorkerCausalityTest.java @@ -0,0 +1,66 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + *--------------------------------------------------------------------------------------------*/ + +package com.github.copilot; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +import java.nio.file.Path; + +import org.junit.jupiter.api.Test; + +import com.fasterxml.jackson.databind.JsonNode; +import com.fasterxml.jackson.databind.ObjectMapper; +import com.fasterxml.jackson.databind.node.ObjectNode; +import com.github.copilot.generated.SessionEvent; +import com.github.copilot.generated.rpc.SessionTasksSendMessageResult; + +class WorkerCausalityTest { + private static final ObjectMapper MAPPER = JsonRpcClient.getObjectMapper(); + + private static JsonNode corpus() throws Exception { + return MAPPER.readTree(Path.of(System.getProperty("copilot.tests.dir"), "worker-causality.json").toFile()); + } + + private static String decode(JsonNode value, boolean event) throws Exception { + return event + ? MAPPER.writeValueAsString(MAPPER.treeToValue(value, SessionEvent.class)) + : MAPPER.writeValueAsString(MAPPER.treeToValue(value, SessionTasksSendMessageResult.class)); + } + + private static ObjectNode payload(JsonNode value, boolean event) { + return (ObjectNode) (event ? value.get("data") : value); + } + + @Test + void publicReadersPreserveSourceAndHistoricalProductBytes() throws Exception { + JsonNode corpus = corpus(); + for (JsonNode test : corpus.get("valid")) { + boolean event = test.has("event"); + JsonNode wire = test.get(event ? "event" : "result").deepCopy(); + JsonNode observed = MAPPER.readTree(decode(wire, event)); + assertEquals(payload(wire, event).get("workerCausality"), payload(observed, event).get("workerCausality"), + test.get("name").asText()); + + payload(wire, event).remove("workerCausality"); + String baseline = decode(wire, event); + for (JsonNode invalid : corpus.get("invalid")) { + payload(wire, event).set("workerCausality", invalid.get("value")); + assertEquals(baseline, decode(wire, event), test.get("name") + ": " + invalid.get("name")); + } + } + } + + @Test + void exactUtf8BudgetAndUnknownFields() throws Exception { + JsonNode corpus = corpus(); + for (JsonNode test : corpus.get("boundaries")) { + JsonNode wire = corpus.get("valid").get(0).get("event").deepCopy(); + payload(wire, true).set("workerCausality", test.get("value")); + ObjectNode observed = payload(MAPPER.readTree(decode(wire, true)), true); + assertEquals(test.get("accepted").asBoolean(), observed.has("workerCausality"), test.get("name").asText()); + assertEquals(payload(wire, true).get("content"), observed.get("content")); + } + } +} diff --git a/nodejs/src/generated/rpc.ts b/nodejs/src/generated/rpc.ts index 1a3b604005..875e5871cf 100644 --- a/nodejs/src/generated/rpc.ts +++ b/nodejs/src/generated/rpc.ts @@ -4,8 +4,9 @@ */ import type { MessageConnection } from "vscode-jsonrpc/node.js"; +import { withWorkerCausality, withWorkerCausalityEvents } from "../workerCausality.js"; -import type { AbortReason, AgentModelPolicy, Attachment, AutoTier, ContextTier, EmbeddedBlobResourceContents, EmbeddedTextResourceContents, IndexedSearchState, ManagedSettingsResolvedData, McpOauthHttpResponse, McpOauthWWWAuthenticateParams, McpServerMetadata, McpServerSource, McpServerStatus, ModelChangeSource, PermissionDecisionSource, PermissionMode, PermissionPromptRequest, PermissionRule, ReasoningSummary, RemediationAction, SessionEvent, SessionLimitsConfig, SessionMode, ShutdownType, SkillSource, TaskCompleteData, TaskCompletionOutcome, UserToolSessionApproval, Verbosity } from "./session-events.js"; +import type { AbortReason, AgentModelPolicy, Attachment, AutoTier, ContextTier, EmbeddedBlobResourceContents, EmbeddedTextResourceContents, IndexedSearchState, ManagedSettingsResolvedData, McpOauthHttpResponse, McpOauthWWWAuthenticateParams, McpServerMetadata, McpServerSource, McpServerStatus, ModelChangeSource, PermissionDecisionSource, PermissionMode, PermissionPromptRequest, PermissionRule, ReasoningSummary, RemediationAction, SessionEvent, SessionLimitsConfig, SessionMode, ShutdownType, SkillSource, TaskCompleteData, TaskCompletionOutcome, UserToolSessionApproval, Verbosity, WorkerCausality } from "./session-events.js"; /** A value that can be represented losslessly on the SDK JSON wire. */ export type JsonValue = null | boolean | number | string | JsonValue[] | { [key: string]: JsonValue }; @@ -23810,6 +23811,7 @@ export interface TasksSendMessageRequest { */ /** @experimental */ export interface TasksSendMessageResult { + workerCausality?: WorkerCausality; /** * Whether the message was successfully delivered or steered */ @@ -28683,7 +28685,7 @@ export function createSessionRpc(connection: MessageConnection, sessionId: strin * @returns Indicates whether the message was delivered, with an error message when delivery failed. */ sendMessage: async (params: TasksSendMessageRequest): Promise => - connection.sendRequest("session.tasks.sendMessage", { sessionId, ...params }), + connection.sendRequest("session.tasks.sendMessage", { sessionId, ...params }).then(withWorkerCausality), }, /** @experimental */ skills: { @@ -30033,7 +30035,7 @@ export function createSessionRpc(connection: MessageConnection, sessionId: strin * @returns Batch of session events returned by a read, with cursor and continuation metadata. */ read: async (params: EventLogReadRequest): Promise => - connection.sendRequest("session.eventLog.read", { sessionId, ...params }), + connection.sendRequest("session.eventLog.read", { sessionId, ...params }).then(withWorkerCausalityEvents), /** * Returns a snapshot of the current tail cursor without consuming events. * diff --git a/nodejs/src/generated/session-events.ts b/nodejs/src/generated/session-events.ts index c1881656e2..2e48f0cc9d 100644 --- a/nodejs/src/generated/session-events.ts +++ b/nodejs/src/generated/session-events.ts @@ -769,6 +769,27 @@ export type UserMessageDelivery = | "steering" /** Enqueued while the agent was busy; processed as its own run afterward. */ | "queued"; +/** + * Producer of an observation, not the execution location of every referenced source. + */ +export type WorkerObservationProvenance = "native" | "ahp_coordinator"; +/** + * Supported observed occurrences. Chronological parentId is not a causal reference. + */ +export type WorkerEventType = + | "tool.execution_start" + | "user.message" + | "subagent.completed" + | "system.notification" + | "assistant.turn_start"; +/** + * Why this exact worker admission was made. + */ +export type WorkerAdmissionKind = "queued_input" | "system_continuation"; +/** + * How the owned notification was consumed. + */ +export type WorkerNotificationMode = "queued" | "immediate"; /** * Content-safe activity observed while a HydraFusion phase is running. */ @@ -4348,6 +4369,7 @@ export interface UserMessageData { * The agent-loop turn ID that consumed this message; absent when no agent-loop turn consumed it */ turnId?: string; + workerCausality?: WorkerCausality; } /** * File attachment @@ -4787,6 +4809,119 @@ export interface AttachmentExtensionContext { */ type: "extension_context"; } +/** + * Optional v1 worker diagnostics. The compact UTF-8 {"workerCausality":value} + * must fit 4096 bytes. Ignore invalid/unknown/oversize metadata, not the product event. + */ +export interface WorkerCausality { + /** + * Complete placement-aware observed capture, not global causality or execution success. + * Empty true requires explicit native invocation attestation. + */ + captureComplete: boolean; + observationProvenance: WorkerObservationProvenance; + /** + * Sources in capture order, at most 32. Absent/unknown is not known-empty. + * + * @maxItems 32 + */ + sources: WorkerSource[]; + /** + * Supported version, exactly 1. + */ + version: 1; +} +/** + * One captured source at this placement and observation boundary. + */ +export interface WorkerSource { + /** + * Actual admissions in capture order; at most 32. + * + * @maxItems 32 + */ + admissions: WorkerAdmission[]; + admittedDuring?: WorkerEventReference; + /** + * Applicable observations were all captured here, never work completion or success. + */ + captureComplete: boolean; + completion?: WorkerEventReference; + input: WorkerInput; + notification?: WorkerNotificationReference; +} +/** + * An observed worker admission, not a claim that execution succeeded. + */ +export interface WorkerAdmission { + /** + * Actual AHP participant Turn UUID, not a native turn counter or provenance signal. + */ + ahpTurnId?: string; + event?: WorkerEventReference; + kind: WorkerAdmissionKind; + /** + * Canonical logical message identity, independent of queueItemId. + */ + messageId: string; +} +/** + * Exact observed event identity. No private registration generation or execution handle. + */ +export interface WorkerEventReference { + /** + * Actual event agent scope, absent for a root occurrence. + */ + agentId?: string; + /** + * Actual event occurrence UUID; copied without normalization. + */ + eventId: string; + eventType: WorkerEventType; + provenance: WorkerObservationProvenance; + /** + * Actual runtime session scope. + */ + sessionId: string; +} +/** + * Exact accepted worker input, distinct from a message, event, caller correlation or Turn. + */ +export interface WorkerInput { + /** + * Actual recipient task. + */ + agentId: string; + /** + * UUID allocated for this queue item by the admitting producer. + */ + queueItemId: string; + sender?: WorkerEventReference; + /** + * Exact captured edges in producer order. Requires sender; at most 32 whole pairs. + * + * @maxItems 32 + */ + senderBridges?: WorkerBridgeObservation[]; +} +/** + * One indivisible source-to-reported bridge observation, not a root alias. + */ +export interface WorkerBridgeObservation { + reported: WorkerEventReference; + source: WorkerEventReference; +} +/** + * Exact delivery and optional occurrence of a consumed worker notification. + */ +export interface WorkerNotificationReference { + /** + * Actual notificationDeliveryId UUID. + */ + deliveryId: string; + event?: WorkerEventReference; + mode: WorkerNotificationMode; +} /** * Session event "pending_messages.modified". Empty payload; the event signals that the pending message queue has changed */ @@ -4867,6 +5002,7 @@ export interface AssistantTurnStartData { * Identifier for this turn within the agentic loop, typically a stringified turn number */ turnId: string; + workerCausality?: WorkerCausality; } /** * Session event "assistant.intent". Agent intent description for current activity or plan @@ -8359,6 +8495,7 @@ export interface SystemNotificationData { content: string; kind: SystemNotification; responsesReasoning?: ResponsesReasoning; + workerCausality?: WorkerCausality; } /** * System notification metadata for a background agent that completed or failed, including agent ID, type, status, description, and prompt. diff --git a/nodejs/src/session.ts b/nodejs/src/session.ts index bc586cf775..c6fb956c84 100644 --- a/nodejs/src/session.ts +++ b/nodejs/src/session.ts @@ -25,6 +25,7 @@ import type { import { type Canvas, CanvasError } from "./canvas.js"; import type { OpenCanvasInstance } from "./generated/rpc.js"; import { getTraceContext } from "./telemetry.js"; +import { withWorkerCausality, withWorkerCausalityEvents } from "./workerCausality.js"; import { isResponseSchema, toJsonSchema } from "./schema.js"; import { isAttributedPermissionResult } from "./types.js"; import type { @@ -1517,6 +1518,13 @@ export class CopilotSession { * @internal This method is for internal use by the SDK. */ _dispatchEvent(event: SessionEvent): void { + if ( + event.type === "user.message" || + event.type === "system.notification" || + event.type === "assistant.turn_start" + ) { + event = { ...event, data: withWorkerCausality(event.data) } as SessionEvent; + } // Handle broadcast request events internally (fire-and-forget) this._handleBroadcastEvent(event); @@ -2849,7 +2857,7 @@ export class CopilotSession { sessionId: this.sessionId, }); - return (response as { events: SessionEvent[] }).events; + return withWorkerCausalityEvents(response as { events: SessionEvent[] }).events; } /** diff --git a/nodejs/src/workerCausality.ts b/nodejs/src/workerCausality.ts new file mode 100644 index 0000000000..d1c64791e3 --- /dev/null +++ b/nodejs/src/workerCausality.ts @@ -0,0 +1,142 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + *--------------------------------------------------------------------------------------------*/ + +function object(value: unknown): value is Record { + return typeof value === "object" && value !== null && !Array.isArray(value); +} + +function fields(value: Record, allowed: readonly string[]): boolean { + return Object.keys(value).every((key) => allowed.includes(key)); +} + +function small(value: unknown, budget: { remaining: number }, depth = 0): boolean { + budget.remaining--; + if (budget.remaining < 0 || depth > 32) return false; + if (typeof value === "string") budget.remaining -= value.length; + if (object(value)) { + for (const [key, item] of Object.entries(value)) { + if (!small(key, budget, depth + 1) || !small(item, budget, depth + 1)) return false; + } + } else if (Array.isArray(value)) { + for (const item of value) if (!small(item, budget, depth + 1)) return false; + } + return budget.remaining >= 0; +} + +function reference(value: unknown, type: string): boolean { + return ( + object(value) && + fields(value, ["sessionId", "eventId", "agentId", "eventType", "provenance"]) && + typeof value.sessionId === "string" && + /^[A-Za-z0-9_-]+$/.test(value.sessionId) && + typeof value.eventId === "string" && + /^[\da-f]{8}-[\da-f]{4}-[\da-f]{4}-[\da-f]{4}-[\da-f]{12}$/i.test(value.eventId) && + (!("agentId" in value) || + (typeof value.agentId === "string" && value.agentId.length > 0)) && + value.eventType === type && + (value.provenance === "native" || value.provenance === "ahp_coordinator") + ); +} + +function source(value: unknown): boolean { + if (!object(value) || !object(value.input)) return false; + const input = value.input; + return ( + fields(value, [ + "input", + "admissions", + "captureComplete", + "completion", + "notification", + "admittedDuring", + ]) && + fields(input, ["queueItemId", "agentId", "sender", "senderBridges"]) && + typeof value.captureComplete === "boolean" && + typeof input.queueItemId === "string" && + /^[\da-f]{8}-[\da-f]{4}-[\da-f]{4}-[\da-f]{4}-[\da-f]{12}$/i.test(input.queueItemId) && + typeof input.agentId === "string" && + input.agentId.length > 0 && + (!("sender" in input) || reference(input.sender, "tool.execution_start")) && + (!("senderBridges" in input) || + ("sender" in input && + Array.isArray(input.senderBridges) && + input.senderBridges.length <= 32 && + input.senderBridges.every( + (edge: unknown) => + object(edge) && + fields(edge, ["source", "reported"]) && + reference(edge.source, "tool.execution_start") && + reference(edge.reported, "tool.execution_start") + ))) && + Array.isArray(value.admissions) && + value.admissions.length <= 32 && + value.admissions.every( + (item: unknown) => + object(item) && + fields(item, ["kind", "messageId", "event", "ahpTurnId"]) && + (item.kind === "queued_input" || item.kind === "system_continuation") && + typeof item.messageId === "string" && + item.messageId.length > 0 && + (!("event" in item) || + (reference(item.event, "user.message") && + (item.event as Record).agentId === input.agentId)) && + (!("ahpTurnId" in item) || + (typeof item.ahpTurnId === "string" && + /^[\da-f]{8}-[\da-f]{4}-[\da-f]{4}-[\da-f]{4}-[\da-f]{12}$/i.test( + item.ahpTurnId + ))) + ) && + (!("completion" in value) || reference(value.completion, "subagent.completed")) && + (!("admittedDuring" in value) || reference(value.admittedDuring, "assistant.turn_start")) && + (!("notification" in value) || + (object(value.notification) && + fields(value.notification, ["deliveryId", "event", "mode"]) && + typeof value.notification.deliveryId === "string" && + /^[\da-f]{8}-[\da-f]{4}-[\da-f]{4}-[\da-f]{4}-[\da-f]{12}$/i.test( + value.notification.deliveryId + ) && + (value.notification.mode === "queued" || value.notification.mode === "immediate") && + (!("event" in value.notification) || + reference(value.notification.event, "system.notification")))) + ); +} + +/** Decode availability only; consumers must still validate placement and enclosing self identity. */ +export function withWorkerCausality(data: T): T { + if (!("workerCausality" in data)) return data; + const value = data.workerCausality; + const valid = + object(value) && + fields(value, ["version", "observationProvenance", "sources", "captureComplete"]) && + value.version === 1 && + (value.observationProvenance === "native" || + value.observationProvenance === "ahp_coordinator") && + typeof value.captureComplete === "boolean" && + Array.isArray(value.sources) && + value.sources.length <= 32 && + small(value, { remaining: 4096 }) && + value.sources.every( + (item: unknown) => + source(item) && + (!value.captureComplete || + (item as Record).captureComplete === true) + ) && + Buffer.byteLength(JSON.stringify({ workerCausality: value }), "utf8") <= 4096; + if (valid) return data; + console.warn("Ignoring invalid, unsupported or oversized workerCausality metadata"); + const copy = { ...data }; + delete (copy as { workerCausality?: unknown }).workerCausality; + return copy; +} + +/** Apply the same diagnostic tolerance to historical events as live delivery. */ +export function withWorkerCausalityEvents(result: T): T { + const events = result.events.map((event) => { + const data = withWorkerCausality(event.data); + return data === event.data ? event : { ...event, data }; + }); + return events.every((event, index) => event === result.events[index]) + ? result + : { ...result, events }; +} diff --git a/nodejs/test/csharp-codegen.test.ts b/nodejs/test/csharp-codegen.test.ts index 461f3e8234..0ca503a89e 100644 --- a/nodejs/test/csharp-codegen.test.ts +++ b/nodejs/test/csharp-codegen.test.ts @@ -49,6 +49,46 @@ describe("C# root event payload unions", () => { expect(code).toContain("public sealed partial class SampleEmptyData"); expect(code).not.toContain("public sealed partial class SampleUnionData { }"); }); + + it("keeps optional worker converter attributes attached after XML documentation", () => { + const code = generateSessionEventsCode({ + definitions: { + WorkerCausality: { + type: "object", + properties: { version: { type: "integer" } }, + required: ["version"], + }, + SessionEvent: { + anyOf: [ + { + type: "object", + properties: { + type: { const: "user.message" }, + data: { + type: "object", + properties: { + content: { type: "string" }, + workerCausality: { + $ref: "#/definitions/WorkerCausality", + description: "Optional worker diagnostics.", + }, + }, + required: ["content"], + }, + }, + required: ["type", "data"], + }, + ], + }, + }, + }); + expect(code).toContain( + "/// Optional worker diagnostics.\n" + + " [JsonConverter(typeof(WorkerCausalityConverter))]\n" + + " [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)]\n" + + ' [JsonPropertyName("workerCausality")]' + ); + }); }); describe("C# RPC codegen", () => { diff --git a/nodejs/test/worker-causality.test.ts b/nodejs/test/worker-causality.test.ts new file mode 100644 index 0000000000..02353fe587 --- /dev/null +++ b/nodejs/test/worker-causality.test.ts @@ -0,0 +1,114 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + *--------------------------------------------------------------------------------------------*/ + +import { readFileSync } from "node:fs"; +import { PassThrough } from "node:stream"; +import { + createMessageConnection, + StreamMessageReader, + StreamMessageWriter, +} from "vscode-jsonrpc/node.js"; +import { describe, expect, it, onTestFinished } from "vitest"; +import { CopilotSession } from "../src/session.js"; +import type { SessionEvent } from "../src/index.js"; + +type Wire = Record; +const corpus = JSON.parse( + readFileSync(new URL("../../test/worker-causality.json", import.meta.url), "utf8") +) as { + valid: { name: string; event?: Wire; result?: Wire }[]; + invalid: { name: string; value: unknown }[]; + boundaries: { name: string; value: unknown; accepted: boolean }[]; +}; + +function payload(value: Wire, event: boolean): Wire { + return event ? (value.data as Wire) : value; +} + +function readers() { + const requests = new PassThrough(); + const responses = new PassThrough(); + const client = createMessageConnection( + new StreamMessageReader(responses), + new StreamMessageWriter(requests) + ); + const server = createMessageConnection( + new StreamMessageReader(requests), + new StreamMessageWriter(responses) + ); + let reply: unknown; + server.onRequest(() => reply); + client.listen(); + server.listen(); + const session = new CopilotSession("codec-session", client); + const observed: SessionEvent[] = []; + const unsubscribe = session.on((event) => observed.push(event)); + onTestFinished(() => { + unsubscribe(); + client.dispose(); + server.dispose(); + requests.destroy(); + responses.destroy(); + }); + return { + async read(wire: Wire, event: boolean): Promise { + if (event) { + session._dispatchEvent(wire as unknown as SessionEvent); + return observed.at(-1) as unknown as Wire; + } + reply = wire; + return (await session.rpc.tasks.sendMessage({ + id: "worker", + message: "test", + })) as unknown as Wire; + }, + async history(wire: Wire): Promise { + reply = { events: [wire], cursor: "cursor", hasMore: false, cursorStatus: "ok" }; + return [await session.getEvents(), (await session.rpc.eventLog.read({})).events]; + }, + }; +} + +describe("worker causality public readers", () => { + it.each(corpus.valid)( + "$name preserves exact observations and historical product bytes", + async (test) => { + const reader = readers(); + const event = test.event !== undefined; + const wire = structuredClone((test.event ?? test.result) as Wire); + const original = JSON.stringify(wire); + expect(payload(await reader.read(wire, event), event).workerCausality).toEqual( + payload(wire, event).workerCausality + ); + expect(JSON.stringify(wire)).toBe(original); + + delete payload(wire, event).workerCausality; + const baseline = JSON.stringify(await reader.read(wire, event)); + for (const invalid of corpus.invalid) { + const candidate = structuredClone(wire); + payload(candidate, event).workerCausality = invalid.value; + expect(JSON.stringify(await reader.read(candidate, event)), invalid.name).toBe( + baseline + ); + if (event) { + for (const events of await reader.history(candidate)) { + expect(JSON.stringify(events[0]), invalid.name).toBe(baseline); + } + } + } + } + ); + + it.each(corpus.boundaries)( + "$name enforces compact UTF-8 bytes and tolerates unknown fields", + async (test) => { + const reader = readers(); + const wire = structuredClone(corpus.valid[0].event as Wire); + payload(wire, true).workerCausality = test.value; + const data = payload(await reader.read(wire, true), true); + expect("workerCausality" in data).toBe(test.accepted); + expect(data.content).toBe(payload(wire, true).content); + } + ); +}); diff --git a/python/copilot/_worker_causality.py b/python/copilot/_worker_causality.py new file mode 100644 index 0000000000..9afc8aefdc --- /dev/null +++ b/python/copilot/_worker_causality.py @@ -0,0 +1,166 @@ +# Copyright (c) Microsoft Corporation. All rights reserved. + +"""Optional diagnostic decoding; consumers still validate enclosing self identity.""" + +import json +import logging +import re +from collections.abc import Callable +from typing import Any, TypeVar + +T = TypeVar("T") +_LOG = logging.getLogger(__name__) +_UUID = re.compile(r"[0-9a-fA-F]{8}-(?:[0-9a-fA-F]{4}-){3}[0-9a-fA-F]{12}") + + +def _uuid(value: Any) -> bool: + return isinstance(value, str) and _UUID.fullmatch(value) is not None + + +def _text(value: Any) -> bool: + return isinstance(value, str) and bool(value) + + +def _fields(value: dict[str, Any], allowed: set[str]) -> bool: + return value.keys() <= allowed + + +def _reference(value: Any, event_type: str) -> bool: + return ( + isinstance(value, dict) + and _fields(value, {"sessionId", "eventId", "agentId", "eventType", "provenance"}) + and isinstance(value.get("sessionId"), str) + and re.fullmatch(r"[A-Za-z0-9_-]+", value["sessionId"]) is not None + and _uuid(value.get("eventId")) + and ("agentId" not in value or _text(value["agentId"])) + and value.get("eventType") == event_type + and value.get("provenance") in ("native", "ahp_coordinator") + ) + + +def _source(value: Any) -> bool: + if not isinstance(value, dict) or not isinstance(value.get("input"), dict): + return False + source_input = value["input"] + if ( + not _fields( + value, + { + "input", + "admissions", + "captureComplete", + "completion", + "notification", + "admittedDuring", + }, + ) + or not _fields(source_input, {"queueItemId", "agentId", "sender", "senderBridges"}) + or type(value.get("captureComplete")) is not bool + or not _uuid(source_input.get("queueItemId")) + or not _text(source_input.get("agentId")) + or not isinstance(value.get("admissions"), list) + or len(value["admissions"]) > 32 + ): + return False + if "sender" in source_input and not _reference(source_input["sender"], "tool.execution_start"): + return False + if "senderBridges" in source_input: + edges = source_input["senderBridges"] + if "sender" not in source_input or not isinstance(edges, list) or len(edges) > 32: + return False + if any( + not isinstance(edge, dict) + or not _fields(edge, {"source", "reported"}) + or not _reference(edge.get("source"), "tool.execution_start") + or not _reference(edge.get("reported"), "tool.execution_start") + for edge in edges + ): + return False + for admission in value["admissions"]: + if ( + not isinstance(admission, dict) + or not _fields(admission, {"kind", "messageId", "event", "ahpTurnId"}) + or admission.get("kind") not in ("queued_input", "system_continuation") + or not _text(admission.get("messageId")) + or ("ahpTurnId" in admission and not _uuid(admission["ahpTurnId"])) + or ( + "event" in admission + and ( + not _reference(admission["event"], "user.message") + or admission["event"].get("agentId") != source_input["agentId"] + ) + ) + ): + return False + for field, event_type in [ + ("completion", "subagent.completed"), + ("admittedDuring", "assistant.turn_start"), + ]: + if field in value and not _reference(value[field], event_type): + return False + if "notification" in value: + notification = value["notification"] + if ( + not isinstance(notification, dict) + or not _fields(notification, {"deliveryId", "event", "mode"}) + or not _uuid(notification.get("deliveryId")) + or notification.get("mode") not in ("queued", "immediate") + or ( + "event" in notification + and not _reference(notification["event"], "system.notification") + ) + ): + return False + return True + + +def _supported(value: Any) -> bool: + return ( + isinstance(value, dict) + and _fields(value, {"version", "observationProvenance", "sources", "captureComplete"}) + and type(value.get("version")) is int + and value["version"] == 1 + and value.get("observationProvenance") in ("native", "ahp_coordinator") + and type(value.get("captureComplete")) is bool + and isinstance(value.get("sources"), list) + and len(value["sources"]) <= 32 + and all( + _source(source) and (not value["captureComplete"] or source["captureComplete"]) + for source in value["sources"] + ) + ) + + +def _small(value: Any, remaining: list[int], depth: int = 0) -> bool: + remaining[0] -= 1 + if remaining[0] < 0 or depth > 32: + return False + if isinstance(value, str): + remaining[0] -= len(value) + elif isinstance(value, dict): + for key, item in value.items(): + if not _small(key, remaining, depth + 1) or not _small(item, remaining, depth + 1): + return False + elif isinstance(value, list): + for item in value: + if not _small(item, remaining, depth + 1): + return False + return remaining[0] >= 0 + + +def optional_worker_causality(value: Any, load: Callable[[], T]) -> T | None: + if value is None: + return None + try: + if not _small(value, [4096]) or not _supported(value): + raise ValueError("invalid optional worker metadata") + size = 0 + encoder = json.JSONEncoder(ensure_ascii=False, separators=(",", ":")) + for chunk in encoder.iterencode({"workerCausality": value}): + size += len(chunk.encode("utf-8")) + if size > 4096: + raise ValueError("optional worker metadata budget") + return load() + except (AssertionError, AttributeError, KeyError, TypeError, ValueError, UnicodeError): + _LOG.warning("Ignoring invalid, unsupported or oversized workerCausality metadata") + return None diff --git a/python/copilot/generated/rpc.py b/python/copilot/generated/rpc.py index dd58e4f3b0..6b42a10137 100644 --- a/python/copilot/generated/rpc.py +++ b/python/copilot/generated/rpc.py @@ -6,7 +6,7 @@ from typing import ClassVar, TYPE_CHECKING -from .session_events import AbortReason, AgentModelPolicy, Attachment, AutoTier, ContextTier, EmbeddedBlobResourceContents, EmbeddedTextResourceContents, IndexedSearchState, ManagedSettingsResolvedSource, McpOauthHttpResponse, McpOauthWWWAuthenticateParams, McpServerMetadata, McpServerSource, McpServerStatus, ModelChangeSource, PermissionDecisionSource, PermissionMode, PermissionPromptRequest, PermissionRule, ReasoningSummary, RemediationAction, SessionEvent, SessionLimitsConfig, SessionMode, ShutdownType, SkillSource, TaskBlocker, TaskCompletionOutcome, UserToolSessionApproval, Verbosity, _load_Attachment, _load_PermissionPromptRequest, _load_UserToolSessionApproval +from .session_events import AbortReason, AgentModelPolicy, Attachment, AutoTier, ContextTier, EmbeddedBlobResourceContents, EmbeddedTextResourceContents, IndexedSearchState, ManagedSettingsResolvedSource, McpOauthHttpResponse, McpOauthWWWAuthenticateParams, McpServerMetadata, McpServerSource, McpServerStatus, ModelChangeSource, PermissionDecisionSource, PermissionMode, PermissionPromptRequest, PermissionRule, ReasoningSummary, RemediationAction, SessionEvent, SessionLimitsConfig, SessionMode, ShutdownType, SkillSource, TaskBlocker, TaskCompletionOutcome, UserToolSessionApproval, Verbosity, WorkerCausality, _load_Attachment, _load_PermissionPromptRequest, _load_UserToolSessionApproval if TYPE_CHECKING: from .._jsonrpc import JsonRpcClient @@ -19,6 +19,7 @@ from uuid import UUID import dateutil.parser +from .._worker_causality import optional_worker_causality T = TypeVar("T") EnumT = TypeVar("EnumT", bound=Enum) @@ -14715,18 +14716,26 @@ class TasksSendMessageResult: error: str | None = None """Error message if delivery failed""" + worker_causality: WorkerCausality | None = None + """Optional exact queue admission receipt on sent=true only. No implicit event or + execution-success claim. + """ + @staticmethod def from_dict(obj: Any) -> 'TasksSendMessageResult': assert isinstance(obj, dict) sent = from_bool(obj.get("sent")) error = from_union([from_str, from_none], obj.get("error")) - return TasksSendMessageResult(sent, error) + worker_causality = optional_worker_causality(obj.get("workerCausality"), lambda: from_union([WorkerCausality.from_dict, from_none], obj.get("workerCausality"))) + return TasksSendMessageResult(sent, error, worker_causality) def to_dict(self) -> dict: result: dict = {} result["sent"] = from_bool(self.sent) if self.error is not None: result["error"] = from_union([from_str, from_none], self.error) + if self.worker_causality is not None: + result["workerCausality"] = from_union([lambda x: to_class(WorkerCausality, x), from_none], self.worker_causality) return result # Experimental: this type is part of an experimental API and may change or be removed. diff --git a/python/copilot/generated/session_events.py b/python/copilot/generated/session_events.py index e010a07c85..a84ae8fbee 100644 --- a/python/copilot/generated/session_events.py +++ b/python/copilot/generated/session_events.py @@ -13,6 +13,7 @@ from uuid import UUID import dateutil.parser +from .._worker_causality import optional_worker_causality T = TypeVar("T") EnumT = TypeVar("EnumT", bound=Enum) @@ -3201,6 +3202,7 @@ class AssistantTurnStartData: turn_id: str interaction_id: str | None = None model: str | None = None + worker_causality: WorkerCausality | None = None @staticmethod def from_dict(obj: Any) -> "AssistantTurnStartData": @@ -3208,10 +3210,12 @@ def from_dict(obj: Any) -> "AssistantTurnStartData": turn_id = from_str(obj.get("turnId")) interaction_id = from_union([from_none, from_str], obj.get("interactionId")) model = from_union([from_none, from_str], obj.get("model")) + worker_causality = optional_worker_causality(obj.get("workerCausality"), lambda: from_union([from_none, WorkerCausality.from_dict], obj.get("workerCausality"))) return AssistantTurnStartData( turn_id=turn_id, interaction_id=interaction_id, model=model, + worker_causality=worker_causality, ) def to_dict(self) -> dict: @@ -3221,6 +3225,8 @@ def to_dict(self) -> dict: result["interactionId"] = from_union([from_none, from_str], self.interaction_id) if self.model is not None: result["model"] = from_union([from_none, from_str], self.model) + if self.worker_causality is not None: + result["workerCausality"] = from_union([from_none, lambda x: to_class(WorkerCausality, x)], self.worker_causality) return result @@ -11247,6 +11253,7 @@ class SystemNotificationData: content: str kind: SystemNotification responses_reasoning: ResponsesReasoning | None = None + worker_causality: WorkerCausality | None = None @staticmethod def from_dict(obj: Any) -> "SystemNotificationData": @@ -11254,10 +11261,12 @@ def from_dict(obj: Any) -> "SystemNotificationData": content = from_str(obj.get("content")) kind = _load_SystemNotification(obj.get("kind")) responses_reasoning = from_union([from_none, ResponsesReasoning.from_dict], obj.get("responsesReasoning")) + worker_causality = optional_worker_causality(obj.get("workerCausality"), lambda: from_union([from_none, WorkerCausality.from_dict], obj.get("workerCausality"))) return SystemNotificationData( content=content, kind=kind, responses_reasoning=responses_reasoning, + worker_causality=worker_causality, ) def to_dict(self) -> dict: @@ -11266,6 +11275,8 @@ def to_dict(self) -> dict: result["kind"] = self.kind.to_dict() if self.responses_reasoning is not None: result["responsesReasoning"] = from_union([from_none, lambda x: to_class(ResponsesReasoning, x)], self.responses_reasoning) + if self.worker_causality is not None: + result["workerCausality"] = from_union([from_none, lambda x: to_class(WorkerCausality, x)], self.worker_causality) return result @@ -12616,6 +12627,7 @@ class UserMessageData: supported_native_document_mime_types: list[str] | None = None transformed_content: str | None = None turn_id: str | None = None + worker_causality: WorkerCausality | None = None @staticmethod def from_dict(obj: Any) -> "UserMessageData": @@ -12635,6 +12647,7 @@ def from_dict(obj: Any) -> "UserMessageData": supported_native_document_mime_types = from_union([from_none, lambda x: from_list(from_str, x)], obj.get("supportedNativeDocumentMimeTypes")) transformed_content = from_union([from_none, from_str], obj.get("transformedContent")) turn_id = from_union([from_none, from_str], obj.get("turnId")) + worker_causality = optional_worker_causality(obj.get("workerCausality"), lambda: from_union([from_none, WorkerCausality.from_dict], obj.get("workerCausality"))) return UserMessageData( content=content, agent_mode=agent_mode, @@ -12651,6 +12664,7 @@ def from_dict(obj: Any) -> "UserMessageData": supported_native_document_mime_types=supported_native_document_mime_types, transformed_content=transformed_content, turn_id=turn_id, + worker_causality=worker_causality, ) def to_dict(self) -> dict: @@ -12684,6 +12698,8 @@ def to_dict(self) -> dict: result["transformedContent"] = from_union([from_none, from_str], self.transformed_content) if self.turn_id is not None: result["turnId"] = from_union([from_none, from_str], self.turn_id) + if self.worker_causality is not None: + result["workerCausality"] = from_union([from_none, lambda x: to_class(WorkerCausality, x)], self.worker_causality) return result @@ -12895,6 +12911,230 @@ def to_dict(self) -> dict: return result +@dataclass +class WorkerAdmission: + "An observed worker admission, not a claim that execution succeeded." + kind: WorkerAdmissionKind + message_id: str + ahp_turn_id: UUID | None = None + event: WorkerEventReference | None = None + + @staticmethod + def from_dict(obj: Any) -> "WorkerAdmission": + assert isinstance(obj, dict) + kind = parse_enum(WorkerAdmissionKind, obj.get("kind")) + message_id = from_str(obj.get("messageId")) + ahp_turn_id = from_union([from_none, from_uuid], obj.get("ahpTurnId")) + event = from_union([from_none, WorkerEventReference.from_dict], obj.get("event")) + return WorkerAdmission( + kind=kind, + message_id=message_id, + ahp_turn_id=ahp_turn_id, + event=event, + ) + + def to_dict(self) -> dict: + result: dict = {} + result["kind"] = to_enum(WorkerAdmissionKind, self.kind) + result["messageId"] = from_str(self.message_id) + if self.ahp_turn_id is not None: + result["ahpTurnId"] = from_union([from_none, to_uuid], self.ahp_turn_id) + if self.event is not None: + result["event"] = from_union([from_none, lambda x: to_class(WorkerEventReference, x)], self.event) + return result + + +@dataclass +class WorkerBridgeObservation: + "One indivisible source-to-reported bridge observation, not a root alias." + reported: WorkerEventReference + source: WorkerEventReference + + @staticmethod + def from_dict(obj: Any) -> "WorkerBridgeObservation": + assert isinstance(obj, dict) + reported = WorkerEventReference.from_dict(obj.get("reported")) + source = WorkerEventReference.from_dict(obj.get("source")) + return WorkerBridgeObservation( + reported=reported, + source=source, + ) + + def to_dict(self) -> dict: + result: dict = {} + result["reported"] = to_class(WorkerEventReference, self.reported) + result["source"] = to_class(WorkerEventReference, self.source) + return result + + +@dataclass +class WorkerCausality: + "Optional v1 worker diagnostics. The compact UTF-8 {\"workerCausality\":value}\nmust fit 4096 bytes. Ignore invalid/unknown/oversize metadata, not the product event." + capture_complete: bool + observation_provenance: WorkerObservationProvenance + sources: list[WorkerSource] + version: int + + @staticmethod + def from_dict(obj: Any) -> "WorkerCausality": + assert isinstance(obj, dict) + capture_complete = from_bool(obj.get("captureComplete")) + observation_provenance = parse_enum(WorkerObservationProvenance, obj.get("observationProvenance")) + sources = from_list(WorkerSource.from_dict, obj.get("sources")) + version = from_int(obj.get("version")) + return WorkerCausality( + capture_complete=capture_complete, + observation_provenance=observation_provenance, + sources=sources, + version=version, + ) + + def to_dict(self) -> dict: + result: dict = {} + result["captureComplete"] = from_bool(self.capture_complete) + result["observationProvenance"] = to_enum(WorkerObservationProvenance, self.observation_provenance) + result["sources"] = from_list(lambda x: to_class(WorkerSource, x), self.sources) + result["version"] = to_int(self.version) + return result + + +@dataclass +class WorkerEventReference: + "Exact observed event identity. No private registration generation or execution handle." + event_id: UUID + event_type: WorkerEventType + provenance: WorkerObservationProvenance + session_id: str + agent_id: str | None = None + + @staticmethod + def from_dict(obj: Any) -> "WorkerEventReference": + assert isinstance(obj, dict) + event_id = from_uuid(obj.get("eventId")) + event_type = parse_enum(WorkerEventType, obj.get("eventType")) + provenance = parse_enum(WorkerObservationProvenance, obj.get("provenance")) + session_id = from_str(obj.get("sessionId")) + agent_id = from_union([from_none, from_str], obj.get("agentId")) + return WorkerEventReference( + event_id=event_id, + event_type=event_type, + provenance=provenance, + session_id=session_id, + agent_id=agent_id, + ) + + def to_dict(self) -> dict: + result: dict = {} + result["eventId"] = to_uuid(self.event_id) + result["eventType"] = to_enum(WorkerEventType, self.event_type) + result["provenance"] = to_enum(WorkerObservationProvenance, self.provenance) + result["sessionId"] = from_str(self.session_id) + if self.agent_id is not None: + result["agentId"] = from_union([from_none, from_str], self.agent_id) + return result + + +@dataclass +class WorkerInput: + "Exact accepted worker input, distinct from a message, event, caller correlation or Turn." + agent_id: str + queue_item_id: UUID + sender: WorkerEventReference | None = None + sender_bridges: list[WorkerBridgeObservation] | None = None + + @staticmethod + def from_dict(obj: Any) -> "WorkerInput": + assert isinstance(obj, dict) + agent_id = from_str(obj.get("agentId")) + queue_item_id = from_uuid(obj.get("queueItemId")) + sender = from_union([from_none, WorkerEventReference.from_dict], obj.get("sender")) + sender_bridges = from_union([from_none, lambda x: from_list(WorkerBridgeObservation.from_dict, x)], obj.get("senderBridges")) + return WorkerInput( + agent_id=agent_id, + queue_item_id=queue_item_id, + sender=sender, + sender_bridges=sender_bridges, + ) + + def to_dict(self) -> dict: + result: dict = {} + result["agentId"] = from_str(self.agent_id) + result["queueItemId"] = to_uuid(self.queue_item_id) + if self.sender is not None: + result["sender"] = from_union([from_none, lambda x: to_class(WorkerEventReference, x)], self.sender) + if self.sender_bridges is not None: + result["senderBridges"] = from_union([from_none, lambda x: from_list(lambda x: to_class(WorkerBridgeObservation, x), x)], self.sender_bridges) + return result + + +@dataclass +class WorkerNotificationReference: + "Exact delivery and optional occurrence of a consumed worker notification." + delivery_id: UUID + mode: WorkerNotificationMode + event: WorkerEventReference | None = None + + @staticmethod + def from_dict(obj: Any) -> "WorkerNotificationReference": + assert isinstance(obj, dict) + delivery_id = from_uuid(obj.get("deliveryId")) + mode = parse_enum(WorkerNotificationMode, obj.get("mode")) + event = from_union([from_none, WorkerEventReference.from_dict], obj.get("event")) + return WorkerNotificationReference( + delivery_id=delivery_id, + mode=mode, + event=event, + ) + + def to_dict(self) -> dict: + result: dict = {} + result["deliveryId"] = to_uuid(self.delivery_id) + result["mode"] = to_enum(WorkerNotificationMode, self.mode) + if self.event is not None: + result["event"] = from_union([from_none, lambda x: to_class(WorkerEventReference, x)], self.event) + return result + + +@dataclass +class WorkerSource: + "One captured source at this placement and observation boundary." + admissions: list[WorkerAdmission] + capture_complete: bool + input: WorkerInput + admitted_during: WorkerEventReference | None = None + completion: WorkerEventReference | None = None + notification: WorkerNotificationReference | None = None + + @staticmethod + def from_dict(obj: Any) -> "WorkerSource": + assert isinstance(obj, dict) + admissions = from_list(WorkerAdmission.from_dict, obj.get("admissions")) + capture_complete = from_bool(obj.get("captureComplete")) + input = WorkerInput.from_dict(obj.get("input")) + admitted_during = from_union([from_none, WorkerEventReference.from_dict], obj.get("admittedDuring")) + completion = from_union([from_none, WorkerEventReference.from_dict], obj.get("completion")) + notification = from_union([from_none, WorkerNotificationReference.from_dict], obj.get("notification")) + return WorkerSource( + admissions=admissions, + capture_complete=capture_complete, + input=input, + admitted_during=admitted_during, + completion=completion, + notification=notification, + ) + + def to_dict(self) -> dict: + result: dict = {} + result["admissions"] = from_list(lambda x: to_class(WorkerAdmission, x), self.admissions) + result["captureComplete"] = from_bool(self.capture_complete) + result["input"] = to_class(WorkerInput, self.input) + if self.admitted_during is not None: + result["admittedDuring"] = from_union([from_none, lambda x: to_class(WorkerEventReference, x)], self.admitted_during) + if self.completion is not None: + result["completion"] = from_union([from_none, lambda x: to_class(WorkerEventReference, x)], self.completion) + if self.notification is not None: + result["notification"] = from_union([from_none, lambda x: to_class(WorkerNotificationReference, x)], self.notification) + return result @dataclass class WorkingDirectoryContext: "Working directory and git context at session start" @@ -14544,6 +14784,33 @@ class Verbosity(Enum): HIGH = "high" +class WorkerAdmissionKind(Enum): + "Why this exact worker admission was made." + QUEUED_INPUT = "queued_input" + SYSTEM_CONTINUATION = "system_continuation" + + +class WorkerEventType(Enum): + "Supported observed occurrences. Chronological parentId is not a causal reference." + TOOL_EXECUTION_START = "tool.execution_start" + USER_MESSAGE = "user.message" + SUBAGENT_COMPLETED = "subagent.completed" + SYSTEM_NOTIFICATION = "system.notification" + ASSISTANT_TURN_START = "assistant.turn_start" + + +class WorkerNotificationMode(Enum): + "How the owned notification was consumed." + QUEUED = "queued" + IMMEDIATE = "immediate" + + +class WorkerObservationProvenance(Enum): + "Producer of an observation, not the execution location of every referenced source." + NATIVE = "native" + AHP_COORDINATOR = "ahp_coordinator" + + class WorkingDirectoryContextHostType(Enum): "Hosting platform type of the repository (github or ado)" # Repository is hosted on GitHub. @@ -15227,6 +15494,17 @@ def session_event_to_dict(x: SessionEvent) -> Any: "UserToolSessionApprovalRead", "UserToolSessionApprovalWrite", "Verbosity", + "WorkerAdmission", + "WorkerAdmissionKind", + "WorkerBridgeObservation", + "WorkerCausality", + "WorkerEventReference", + "WorkerEventType", + "WorkerInput", + "WorkerNotificationMode", + "WorkerNotificationReference", + "WorkerObservationProvenance", + "WorkerSource", "WorkingDirectoryContext", "WorkingDirectoryContextHostType", "WorkspaceFileChangedOperation", diff --git a/python/test_worker_causality.py b/python/test_worker_causality.py new file mode 100644 index 0000000000..bc2e5762eb --- /dev/null +++ b/python/test_worker_causality.py @@ -0,0 +1,51 @@ +# Copyright (c) Microsoft Corporation. All rights reserved. + +import copy +import json +from pathlib import Path + +import pytest + +from copilot.rpc import TasksSendMessageResult +from copilot.session_events import session_event_from_dict, session_event_to_dict + +CORPUS = json.loads((Path(__file__).parent.parent / "test/worker-causality.json").read_text()) + + +def decode(value, event): + if event: + return session_event_to_dict(session_event_from_dict(value)) + return TasksSendMessageResult.from_dict(value).to_dict() + + +def payload(value, event): + return value["data"] if event else value + + +@pytest.mark.parametrize("case", CORPUS["valid"], ids=lambda case: case["name"]) +def test_worker_causality_public_readers_preserve_source_and_product(case): + event = "event" in case + wire = case["event"] if event else case["result"] + original = copy.deepcopy(wire) + assert ( + payload(decode(wire, event), event)["workerCausality"] + == payload(wire, event)["workerCausality"] + ) + assert wire == original + + legacy = copy.deepcopy(wire) + del payload(legacy, event)["workerCausality"] + baseline = json.dumps(decode(legacy, event), sort_keys=True) + for invalid in CORPUS["invalid"]: + candidate = copy.deepcopy(legacy) + payload(candidate, event)["workerCausality"] = invalid["value"] + assert json.dumps(decode(candidate, event), sort_keys=True) == baseline, invalid["name"] + + +@pytest.mark.parametrize("case", CORPUS["boundaries"], ids=lambda case: case["name"]) +def test_worker_causality_exact_utf8_budget_and_unknown_fields(case): + wire = copy.deepcopy(CORPUS["valid"][0]["event"]) + wire["data"]["workerCausality"] = case["value"] + result = decode(wire, True) + assert ("workerCausality" in result["data"]) == case["accepted"] + assert result["data"]["content"] == wire["data"]["content"] diff --git a/rust/src/generated/api_types.rs b/rust/src/generated/api_types.rs index d2cc0d7fb8..d6fefc76cc 100644 --- a/rust/src/generated/api_types.rs +++ b/rust/src/generated/api_types.rs @@ -16,7 +16,7 @@ pub use super::session_events::{ OmittedBinaryOmittedReason, PermissionDecisionSource, PermissionMode, PermissionPromptRequest, PermissionRule, ReasoningSummary, RemediationAction, SessionLimitsConfig, SessionMode, ShutdownType, SkillSource, TaskBlocker, TaskCompletionOutcome, UserToolSessionApproval, - Verbosity, + Verbosity, WorkerCausality, }; use crate::types::{RequestId, SessionEvent, SessionId}; @@ -22255,6 +22255,13 @@ pub struct TasksSendMessageResult { pub error: Option, /// Whether the message was successfully delivered or steered pub sent: bool, + /// Optional exact queue admission receipt on sent=true only. No implicit event or execution-success claim. + #[serde(skip_serializing_if = "Option::is_none")] + #[serde( + default, + deserialize_with = "crate::worker_causality::deserialize_optional" + )] + pub worker_causality: Option, } /// Agent type, prompt, name, and optional description and model override for the new task. @@ -28609,6 +28616,13 @@ pub struct SessionTasksSendMessageResult { pub error: Option, /// Whether the message was successfully delivered or steered pub sent: bool, + /// Optional exact queue admission receipt on sent=true only. No implicit event or execution-success claim. + #[serde(skip_serializing_if = "Option::is_none")] + #[serde( + default, + deserialize_with = "crate::worker_causality::deserialize_optional" + )] + pub worker_causality: Option, } /// Identifies the target session. diff --git a/rust/src/generated/session_events.rs b/rust/src/generated/session_events.rs index 0346f92f19..9209ea6e1f 100644 --- a/rust/src/generated/session_events.rs +++ b/rust/src/generated/session_events.rs @@ -2592,6 +2592,115 @@ pub struct SessionPermissionRecoveryData { pub status: PermissionRecoveryStatus, } +/// Exact observed event identity. No private registration generation or execution handle. +#[derive(Debug, Clone, Default, Serialize, Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct WorkerEventReference { + /// Actual event agent scope, absent for a root occurrence. + #[serde(skip_serializing_if = "Option::is_none")] + pub agent_id: Option, + /// Actual event occurrence UUID; copied without normalization. + pub event_id: String, + /// Type of the observed occurrence. + pub event_type: WorkerEventType, + /// Explicit provenance of this reference. + pub provenance: WorkerObservationProvenance, + /// Actual runtime session scope. + pub session_id: SessionId, +} + +/// An observed worker admission, not a claim that execution succeeded. +#[derive(Debug, Clone, Default, Serialize, Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct WorkerAdmission { + /// Actual AHP participant Turn UUID, not a native turn counter or provenance signal. + #[serde(skip_serializing_if = "Option::is_none")] + pub ahp_turn_id: Option, + /// May be omitted only for the matching current worker user.message. + #[serde(skip_serializing_if = "Option::is_none")] + pub event: Option, + /// The producer's admission kind. + pub kind: WorkerAdmissionKind, + /// Canonical logical message identity, independent of queueItemId. + pub message_id: String, +} + +/// One indivisible source-to-reported bridge observation, not a root alias. +#[derive(Debug, Clone, Default, Serialize, Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct WorkerBridgeObservation { + /// Full occurrence actually emitted by that bridge. + pub reported: WorkerEventReference, + /// Full event received by this bridge. + pub source: WorkerEventReference, +} + +/// Exact accepted worker input, distinct from a message, event, caller correlation or Turn. +#[derive(Debug, Clone, Default, Serialize, Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct WorkerInput { + /// Actual recipient task. + pub agent_id: String, + /// UUID allocated for this queue item by the admitting producer. + pub queue_item_id: String, + /// Original invoking occurrence, never replaced by a reported/root alias. + #[serde(skip_serializing_if = "Option::is_none")] + pub sender: Option, + /// Exact captured edges in producer order. Requires sender; at most 32 whole pairs. + #[serde(skip_serializing_if = "Option::is_none")] + pub sender_bridges: Option>, +} + +/// Exact delivery and optional occurrence of a consumed worker notification. +#[derive(Debug, Clone, Default, Serialize, Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct WorkerNotificationReference { + /// Actual notificationDeliveryId UUID. + pub delivery_id: String, + /// May be omitted only on the matching current system.notification. + #[serde(skip_serializing_if = "Option::is_none")] + pub event: Option, + /// Actual consumption mode. + pub mode: WorkerNotificationMode, +} + +/// One captured source at this placement and observation boundary. +#[derive(Debug, Clone, Default, Serialize, Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct WorkerSource { + /// Actual admissions in capture order; at most 32. + pub admissions: Vec, + /// Exact already-open iteration when an immediate notification was consumed. + #[serde(skip_serializing_if = "Option::is_none")] + pub admitted_during: Option, + /// Applicable observations were all captured here, never work completion or success. + pub capture_complete: bool, + /// Actual completion emission only. Absence is not an execution outcome. + #[serde(skip_serializing_if = "Option::is_none")] + pub completion: Option, + /// Indivisible accepted input identity. + pub input: WorkerInput, + /// Actual consumed notification, when observed. + #[serde(skip_serializing_if = "Option::is_none")] + pub notification: Option, +} + +/// Optional v1 worker diagnostics. The compact UTF-8 {"workerCausality":value} +/// must fit 4096 bytes. Ignore invalid/unknown/oversize metadata, not the product event. +#[derive(Debug, Clone, Default, Serialize, Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct WorkerCausality { + /// Complete placement-aware observed capture, not global causality or execution success. + /// Empty true requires explicit native invocation attestation. + pub capture_complete: bool, + /// Provenance of this enclosing observation; never inferred from other fields. + pub observation_provenance: WorkerObservationProvenance, + /// Sources in capture order, at most 32. Absent/unknown is not known-empty. + pub sources: Vec, + /// Supported version, exactly 1. + pub version: serde_json::Value, +} + /// Session event "user.message". Payload of `user.message` with displayed and model-transformed content, attachments, source/delivery metadata, mode, and telemetry IDs. #[derive(Debug, Clone, Default, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] @@ -2640,6 +2749,13 @@ pub struct UserMessageData { /// The agent-loop turn ID that consumed this message; absent when no agent-loop turn consumed it #[serde(skip_serializing_if = "Option::is_none")] pub turn_id: Option, + /// Optional worker admission observations; self identity requires the matching enclosing message and agent. + #[serde(skip_serializing_if = "Option::is_none")] + #[serde( + default, + deserialize_with = "crate::worker_causality::deserialize_optional" + )] + pub worker_causality: Option, } /// Session event "pending_messages.modified". Empty payload; the event signals that the pending message queue has changed @@ -2659,6 +2775,13 @@ pub struct AssistantTurnStartData { pub model: Option, /// Identifier for this turn within the agentic loop, typically a stringified turn number pub turn_id: String, + /// Optional bounded worker observations. Missing or invalid metadata is unavailable, not known-empty. + #[serde(skip_serializing_if = "Option::is_none")] + #[serde( + default, + deserialize_with = "crate::worker_causality::deserialize_optional" + )] + pub worker_causality: Option, } /// Session event "assistant.turn_retry". Metadata for an additional model inference attempt within an existing assistant turn @@ -5200,6 +5323,13 @@ pub struct SystemNotificationData { /// Responses reasoning settings anchored before this model-facing message, for cache-stable history replay #[serde(skip_serializing_if = "Option::is_none")] pub responses_reasoning: Option, + /// Optional owned worker notification observations; an omitted notification event refers only to this occurrence. + #[serde(skip_serializing_if = "Option::is_none")] + #[serde( + default, + deserialize_with = "crate::worker_causality::deserialize_optional" + )] + pub worker_causality: Option, } /// A parsed command identifier in a shell permission request, including whether it is read-only. @@ -8777,6 +8907,64 @@ pub enum UserMessageDelivery { Unknown, } +/// Producer of an observation, not the execution location of every referenced source. +#[derive(Debug, Clone, Default, PartialEq, Eq, Serialize, Deserialize)] +pub enum WorkerObservationProvenance { + #[serde(rename = "native")] + Native, + #[serde(rename = "ahp_coordinator")] + AhpCoordinator, + /// Unknown variant for forward compatibility. + #[default] + #[serde(other)] + Unknown, +} + +/// Supported observed occurrences. Chronological parentId is not a causal reference. +#[derive(Debug, Clone, Default, PartialEq, Eq, Serialize, Deserialize)] +pub enum WorkerEventType { + #[serde(rename = "tool.execution_start")] + ToolExecutionStart, + #[serde(rename = "user.message")] + UserMessage, + #[serde(rename = "subagent.completed")] + SubagentCompleted, + #[serde(rename = "system.notification")] + SystemNotification, + #[serde(rename = "assistant.turn_start")] + AssistantTurnStart, + /// Unknown variant for forward compatibility. + #[default] + #[serde(other)] + Unknown, +} + +/// Why this exact worker admission was made. +#[derive(Debug, Clone, Default, PartialEq, Eq, Serialize, Deserialize)] +pub enum WorkerAdmissionKind { + #[serde(rename = "queued_input")] + QueuedInput, + #[serde(rename = "system_continuation")] + SystemContinuation, + /// Unknown variant for forward compatibility. + #[default] + #[serde(other)] + Unknown, +} + +/// How the owned notification was consumed. +#[derive(Debug, Clone, Default, PartialEq, Eq, Serialize, Deserialize)] +pub enum WorkerNotificationMode { + #[serde(rename = "queued")] + Queued, + #[serde(rename = "immediate")] + Immediate, + /// Unknown variant for forward compatibility. + #[default] + #[serde(other)] + Unknown, +} + /// What the agent was doing when the user interrupted it. #[derive(Debug, Clone, Default, PartialEq, Eq, Serialize, Deserialize)] pub enum AgentInterruptedActivity { diff --git a/rust/src/lib.rs b/rust/src/lib.rs index 21add4b7cf..69deed66b0 100644 --- a/rust/src/lib.rs +++ b/rust/src/lib.rs @@ -60,6 +60,7 @@ pub mod transforms; /// Protocol types shared between the SDK and the GitHub Copilot CLI. pub mod types; mod wire; +mod worker_causality; /// Session event payload types — auto-generated from the protocol schema. pub mod session_events; diff --git a/rust/src/worker_causality.rs b/rust/src/worker_causality.rs new file mode 100644 index 0000000000..779b84d37e --- /dev/null +++ b/rust/src/worker_causality.rs @@ -0,0 +1,185 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + *--------------------------------------------------------------------------------------------*/ + +use std::io::{self, Write}; + +use serde::de::DeserializeOwned; +use serde::{Deserialize, Deserializer}; +use serde_json::Value; + +struct Budget(usize); + +impl Write for Budget { + fn write(&mut self, bytes: &[u8]) -> io::Result { + self.0 = self.0.saturating_add(bytes.len()).min(4097); + if self.0 > 4096 { + Err(io::Error::other("optional worker metadata budget")) + } else { + Ok(bytes.len()) + } + } + + fn flush(&mut self) -> io::Result<()> { + Ok(()) + } +} + +fn text(value: &Value) -> bool { + value.as_str().is_some_and(|value| !value.is_empty()) +} + +fn uuid(value: &Value) -> bool { + value.as_str().is_some_and(|value| { + value.len() == 36 + && value.bytes().enumerate().all(|(index, byte)| { + if matches!(index, 8 | 13 | 18 | 23) { + byte == b'-' + } else { + byte.is_ascii_hexdigit() + } + }) + }) +} + +fn fields(value: &Value, allowed: &[&str]) -> bool { + value + .as_object() + .is_some_and(|object| object.keys().all(|key| allowed.contains(&key.as_str()))) +} + +fn reference(value: &Value, event_type: &str) -> bool { + fields( + value, + &["sessionId", "eventId", "agentId", "eventType", "provenance"], + ) && value["sessionId"].as_str().is_some_and(|session| { + !session.is_empty() + && session + .bytes() + .all(|c| c.is_ascii_alphanumeric() || matches!(c, b'_' | b'-')) + }) && uuid(&value["eventId"]) + && value.get("agentId").is_none_or(text) + && value["eventType"] == event_type + && matches!( + value["provenance"].as_str(), + Some("native" | "ahp_coordinator") + ) +} + +fn source(value: &Value) -> bool { + let input = &value["input"]; + let Some(admissions) = value["admissions"].as_array() else { + return false; + }; + if !fields( + value, + &[ + "input", + "admissions", + "captureComplete", + "completion", + "notification", + "admittedDuring", + ], + ) || !fields( + input, + &["queueItemId", "agentId", "sender", "senderBridges"], + ) || !value["captureComplete"].is_boolean() + || !uuid(&input["queueItemId"]) + || !text(&input["agentId"]) + || admissions.len() > 32 + || input + .get("sender") + .is_some_and(|sender| !reference(sender, "tool.execution_start")) + { + return false; + } + if let Some(edges) = input.get("senderBridges") { + let Some(edges) = edges.as_array() else { + return false; + }; + if input.get("sender").is_none() + || edges.len() > 32 + || edges.iter().any(|edge| { + !fields(edge, &["source", "reported"]) + || !reference(&edge["source"], "tool.execution_start") + || !reference(&edge["reported"], "tool.execution_start") + }) + { + return false; + } + } + if admissions.iter().any(|admission| { + !fields(admission, &["kind", "messageId", "event", "ahpTurnId"]) + || !matches!( + admission["kind"].as_str(), + Some("queued_input" | "system_continuation") + ) + || !text(&admission["messageId"]) + || admission.get("ahpTurnId").is_some_and(|turn| !uuid(turn)) + || admission.get("event").is_some_and(|event| { + !reference(event, "user.message") || event["agentId"] != input["agentId"] + }) + }) { + return false; + } + for (field, event_type) in [ + ("completion", "subagent.completed"), + ("admittedDuring", "assistant.turn_start"), + ] { + if value + .get(field) + .is_some_and(|event| !reference(event, event_type)) + { + return false; + } + } + value.get("notification").is_none_or(|notification| { + fields(notification, &["deliveryId", "event", "mode"]) + && uuid(¬ification["deliveryId"]) + && matches!(notification["mode"].as_str(), Some("queued" | "immediate")) + && notification + .get("event") + .is_none_or(|event| reference(event, "system.notification")) + }) +} + +// Availability only: consumers must additionally validate placement and enclosing self identity. +fn supported(value: &Value) -> bool { + fields( + value, + &[ + "version", + "observationProvenance", + "sources", + "captureComplete", + ], + ) && value["version"].as_u64() == Some(1) + && matches!( + value["observationProvenance"].as_str(), + Some("native" | "ahp_coordinator") + ) + && value["captureComplete"].is_boolean() + && value["sources"].as_array().is_some_and(|sources| { + sources.len() <= 32 + && sources.iter().all(|item| { + source(item) + && (value["captureComplete"] != true || item["captureComplete"] == true) + }) + }) +} + +pub(crate) fn deserialize_optional<'de, D: Deserializer<'de>, T: DeserializeOwned>( + deserializer: D, +) -> Result, D::Error> { + let value = Value::deserialize(deserializer)?; + let field = std::collections::BTreeMap::from([("workerCausality", &value)]); + if serde_json::to_writer(&mut Budget(0), &field).is_ok() + && supported(&value) + && let Ok(capture) = serde_json::from_value(value) + { + return Ok(Some(capture)); + } + tracing::warn!("Ignoring invalid, unsupported or oversized workerCausality metadata"); + Ok(None) +} diff --git a/rust/tests/worker_causality_test.rs b/rust/tests/worker_causality_test.rs new file mode 100644 index 0000000000..ec9429ea66 --- /dev/null +++ b/rust/tests/worker_causality_test.rs @@ -0,0 +1,78 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. + +#![allow(clippy::unwrap_used)] + +use github_copilot_sdk::rpc::TasksSendMessageResult; +use github_copilot_sdk::session_events::TypedSessionEvent; +use serde_json::Value; + +fn corpus() -> Value { + serde_json::from_str(include_str!("../../test/worker-causality.json")).unwrap() +} + +fn decode(value: &Value, event: bool) -> Value { + if event { + serde_json::to_value(serde_json::from_value::(value.clone()).unwrap()) + .unwrap() + } else { + serde_json::to_value( + serde_json::from_value::(value.clone()).unwrap(), + ) + .unwrap() + } +} + +fn payload(value: &Value, event: bool) -> &Value { + if event { &value["data"] } else { value } +} + +fn payload_mut(value: &mut Value, event: bool) -> &mut Value { + if event { &mut value["data"] } else { value } +} + +#[test] +fn worker_causality_public_readers_preserve_source_and_product() { + let corpus = corpus(); + for case in corpus["valid"].as_array().unwrap() { + let event = case.get("event").is_some(); + let mut wire = case[if event { "event" } else { "result" }].clone(); + assert_eq!( + payload(&decode(&wire, event), event)["workerCausality"], + payload(&wire, event)["workerCausality"], + "{}", + case["name"] + ); + payload_mut(&mut wire, event) + .as_object_mut() + .unwrap() + .remove("workerCausality"); + let baseline = serde_json::to_vec(&decode(&wire, event)).unwrap(); + for invalid in corpus["invalid"].as_array().unwrap() { + payload_mut(&mut wire, event)["workerCausality"] = invalid["value"].clone(); + assert_eq!( + serde_json::to_vec(&decode(&wire, event)).unwrap(), + baseline, + "{}: {}", + case["name"], + invalid["name"] + ); + } + } +} + +#[test] +fn worker_causality_exact_utf8_budget_and_unknown_fields() { + let corpus = corpus(); + for case in corpus["boundaries"].as_array().unwrap() { + let mut wire = corpus["valid"][0]["event"].clone(); + wire["data"]["workerCausality"] = case["value"].clone(); + let decoded = decode(&wire, true); + assert_eq!( + decoded["data"].get("workerCausality").is_some(), + case["accepted"].as_bool().unwrap(), + "{}", + case["name"] + ); + assert_eq!(decoded["data"]["content"], wire["data"]["content"]); + } +} diff --git a/scripts/codegen/csharp.ts b/scripts/codegen/csharp.ts index b8edb93aab..6ee15a89b7 100644 --- a/scripts/codegen/csharp.ts +++ b/scripts/codegen/csharp.ts @@ -1432,6 +1432,9 @@ function generateDataClass(variant: EventVariant, knownTypes: Map))]`); + } lines.push(...emitDataAnnotations(prop, " ", csharpType)); if (isSchemaDeprecated(prop)) pushObsoleteAttributes(lines, " "); if (isSchemaExperimental(prop)) pushExperimentalAttribute(lines, " "); @@ -1896,6 +1899,9 @@ function emitRpcClass( const csharpType = resolveRpcType(prop, isReq, inlineTypeParentName, csharpName, extraClasses); lines.push(...xmlDocPropertyComment(prop.description, propName, " ")); + if (propName === "workerCausality" && !isReq) { + lines.push(` [JsonConverter(typeof(WorkerCausalityConverter<${csharpType.replace(/\?$/, "")}>))]`); + } lines.push(...emitDataAnnotations(prop, " ", csharpType)); if (isSchemaDeprecated(prop)) pushObsoleteAttributes(lines, " "); if (isSchemaExperimental(prop)) pushExperimentalAttribute(lines, " "); diff --git a/scripts/codegen/go.ts b/scripts/codegen/go.ts index 7dfc4e444c..a4421f02eb 100644 --- a/scripts/codegen/go.ts +++ b/scripts/codegen/go.ts @@ -1138,7 +1138,7 @@ function pushGoStructUnmarshalJSON(lines: string[], typeName: string, fields: Go const unionFields = fields .map((field) => ({ field, unionField: goDiscriminatedUnionField(field.goType, ctx) })) .filter((entry): entry is { field: GoStructField; unionField: GoDiscriminatedUnionField } => entry.unionField !== undefined); - if (unionFields.length === 0) return; + if (unionFields.length === 0 && !fields.some((field) => field.propName === "workerCausality")) return; const blockLines: string[] = []; blockLines.push(`func (r *${typeName}) UnmarshalJSON(data []byte) error {`); @@ -1146,6 +1146,7 @@ function pushGoStructUnmarshalJSON(lines: string[], typeName: string, fields: Go for (const field of fields) { const unionField = goDiscriminatedUnionField(field.goType, ctx); let rawType = field.goType; + if (field.propName === "workerCausality") rawType = "json.RawMessage"; if (unionField?.kind === "single") rawType = "json.RawMessage"; if (unionField?.kind === "slice") rawType = "[]json.RawMessage"; if (unionField?.kind === "map") rawType = "map[string]json.RawMessage"; @@ -1159,6 +1160,12 @@ function pushGoStructUnmarshalJSON(lines: string[], typeName: string, fields: Go for (const field of fields) { const unionField = goDiscriminatedUnionField(field.goType, ctx); + if (field.propName === "workerCausality") { + blockLines.push(`\tif !diagnosticmetadata.ReadWorkerCausality(raw.${field.goName}, &r.${field.goName}) {`); + blockLines.push(`\t\tr.${field.goName} = nil`); + blockLines.push(`\t}`); + continue; + } if (!unionField) { blockLines.push(`\tr.${field.goName} = raw.${field.goName}`); continue; @@ -3086,6 +3093,9 @@ function goGeneratedEncodingFileCode(schemaFileName: string, packageName: string if (generatedEncodingCode.includes("time.Time")) { imports.push(`"time"`); } + if (generatedEncodingCode.includes("diagnosticmetadata.")) { + imports.push(`"github.com/github/copilot-sdk/go/internal/diagnosticmetadata"`); + } if (packageName !== "rpc" && generatedEncodingCode.includes("rpc.")) { imports.push(`"github.com/github/copilot-sdk/go/rpc"`); } diff --git a/scripts/codegen/python.ts b/scripts/codegen/python.ts index f10110c490..6dd544d7f2 100644 --- a/scripts/codegen/python.ts +++ b/scripts/codegen/python.ts @@ -2477,6 +2477,10 @@ function emitPyClass( lines.push(` assert isinstance(obj, dict)`); for (const field of fieldInfos) { const sourceExpr = `obj.get(${JSON.stringify(field.jsonName)})`; + if (field.jsonName === "workerCausality" && !field.isRequired) { + lines.push(` ${field.fieldName} = optional_worker_causality(${sourceExpr}, lambda: ${field.resolved.fromExpr(sourceExpr)})`); + continue; + } lines.push( ` ${field.fieldName} = ${field.resolved.fromExpr(sourceExpr)}` ); @@ -2728,6 +2732,7 @@ export function generatePythonSessionEventsCode(schema: JSONSchema7): string { out.push(`from uuid import UUID`); out.push(``); out.push(`import dateutil.parser`); + out.push(`from .._worker_causality import optional_worker_causality`); out.push(``); out.push(`T = TypeVar("T")`); out.push(`EnumT = TypeVar("EnumT", bound=Enum)`); @@ -3391,11 +3396,16 @@ from typing import Any, Protocol, TypeVar, cast from uuid import UUID import dateutil.parser +from .._worker_causality import optional_worker_causality T = TypeVar("T") EnumT = TypeVar("EnumT", bound=Enum) `); + typesCode = typesCode.replace( + /^(\s*)worker_causality = (.*obj\.get\("workerCausality"\).*)$/gm, + '$1worker_causality = optional_worker_causality(obj.get("workerCausality"), lambda: $2)' + ); lines.push(typesCode); if (publicTypeAliases.size > 0) { lines.push(""); diff --git a/scripts/codegen/rust.ts b/scripts/codegen/rust.ts index 875082e893..cd10896ca1 100644 --- a/scripts/codegen/rust.ts +++ b/scripts/codegen/rust.ts @@ -1059,7 +1059,9 @@ function emitRustStruct( lines.push(` #[serde(rename = "${propName}")]`); } - if (prop.$ref && typeof prop.const === "string") { + if (propName === "workerCausality" && !isReq) { + lines.push(` #[serde(default, deserialize_with = "crate::worker_causality::deserialize_optional")]`); + } else if (prop.$ref && typeof prop.const === "string") { lines.push( ` #[serde(${isReq ? "" : "default, "}deserialize_with = "${typeName}::deserialize_${snakeField}")]`, ); diff --git a/scripts/codegen/typescript.ts b/scripts/codegen/typescript.ts index a4680803e0..76e37ebf14 100644 --- a/scripts/codegen/typescript.ts +++ b/scripts/codegen/typescript.ts @@ -719,6 +719,7 @@ async function generateRpc(schemaPath?: string, sessionEventsSchema?: JSONSchema */ import type { MessageConnection } from "vscode-jsonrpc/node.js"; +import { withWorkerCausality, withWorkerCausalityEvents } from "../workerCausality.js"; `); const externalSchemaRefs = collectExternalSchemaRefNames(schema); @@ -1017,7 +1018,13 @@ function emitGroup( includeExperimental: (value as RpcMethod).stability === "experimental" && !parentExperimental, }); lines.push(`${indent}${key}: async (${sigParams.join(", ")}): Promise<${resultType}> =>`); - lines.push(`${indent} connection.sendRequest("${rpcMethod}", ${bodyArg}),`); + if (rpcMethod === "session.tasks.sendMessage") { + lines.push(`${indent} connection.sendRequest<${resultType}>("${rpcMethod}", ${bodyArg}).then(withWorkerCausality),`); + } else if (rpcMethod === "session.getMessages" || rpcMethod === "session.eventLog.read") { + lines.push(`${indent} connection.sendRequest<${resultType}>("${rpcMethod}", ${bodyArg}).then(withWorkerCausalityEvents),`); + } else { + lines.push(`${indent} connection.sendRequest("${rpcMethod}", ${bodyArg}),`); + } } else if (typeof value === "object" && value !== null) { const groupExperimental = isNodeFullyExperimental(value as Record); const groupDeprecated = isNodeFullyDeprecated(value as Record); diff --git a/test/worker-causality.json b/test/worker-causality.json new file mode 100644 index 0000000000..46ffc54f77 --- /dev/null +++ b/test/worker-causality.json @@ -0,0 +1,795 @@ +{ + "version": 1, + "producerEvidence": { + "kind": "native_worker_causality_test_output", + "logSha256": "625f992064a37b5b51c3f6be3efcb7f16423a1a24648a8f7daea7b4db67c4d15" + }, + "valid": [ + { + "name": "native_nested_user_message", + "event": { + "type": "user.message", + "data": { + "content": "test input", + "messageId": "bb61b7c0-bc90-4290-a1e4-79558dc35139", + "workerCausality": { + "version": 1, + "observationProvenance": "native", + "sources": [ + { + "input": { + "queueItemId": "2dbbe116-73b0-4011-bfa3-3b571b41404b", + "agentId": "recipient", + "sender": { + "sessionId": "0187f098-31d3-4cdc-ab71-e6ab387e01b4", + "eventId": "773e548e-64e5-4418-853b-aabf128ce858", + "eventType": "tool.execution_start", + "provenance": "native" + }, + "senderBridges": [ + { + "source": { + "sessionId": "0187f098-31d3-4cdc-ab71-e6ab387e01b4", + "eventId": "773e548e-64e5-4418-853b-aabf128ce858", + "eventType": "tool.execution_start", + "provenance": "native" + }, + "reported": { + "sessionId": "23ac4baf-eb8e-4fdc-b8e0-5633e4ed550b", + "eventId": "8f608be4-c132-4e19-8bdd-82ff5ba9b0b8", + "agentId": "leaf-agent", + "eventType": "tool.execution_start", + "provenance": "native" + } + }, + { + "source": { + "sessionId": "23ac4baf-eb8e-4fdc-b8e0-5633e4ed550b", + "eventId": "8f608be4-c132-4e19-8bdd-82ff5ba9b0b8", + "agentId": "leaf-agent", + "eventType": "tool.execution_start", + "provenance": "native" + }, + "reported": { + "sessionId": "ce52204a-54b0-4ebd-ba13-4854598c4399", + "eventId": "69362832-885b-4787-ab3e-1a2f6c17c73b", + "agentId": "leaf-agent", + "eventType": "tool.execution_start", + "provenance": "native" + } + }, + { + "source": { + "sessionId": "23ac4baf-eb8e-4fdc-b8e0-5633e4ed550b", + "eventId": "8f608be4-c132-4e19-8bdd-82ff5ba9b0b8", + "agentId": "leaf-agent", + "eventType": "tool.execution_start", + "provenance": "native" + }, + "reported": { + "sessionId": "f651ee35-bdb9-4c91-aa21-0057a9446269", + "eventId": "b13511dc-0c7a-4d9c-9618-c04c1c50c4bc", + "agentId": "leaf-agent", + "eventType": "tool.execution_start", + "provenance": "native" + } + } + ] + }, + "admissions": [ + { + "kind": "queued_input", + "messageId": "bb61b7c0-bc90-4290-a1e4-79558dc35139" + } + ], + "captureComplete": true + } + ], + "captureComplete": true + } + }, + "agentId": "recipient", + "id": "e31ee08e-c3ea-4720-b0d6-c7435b5a429c", + "timestamp": "2026-09-26T00:10:18.598Z", + "parentId": null + } + }, + { + "name": "ahp_coordinator_user_message", + "event": { + "type": "user.message", + "data": { + "content": "first prompt", + "turnId": "ffcc4a93-3bc1-4f78-ac2b-c47f6948b20c", + "messageId": "efbe1be8-3082-45c5-8c02-8dd922db78c0", + "workerCausality": { + "version": 1, + "observationProvenance": "ahp_coordinator", + "sources": [ + { + "input": { + "queueItemId": "9ce2ac75-5062-457e-abe2-11f0f950e5b9", + "agentId": "ahp-agent" + }, + "admissions": [ + { + "kind": "queued_input", + "messageId": "efbe1be8-3082-45c5-8c02-8dd922db78c0", + "ahpTurnId": "ffcc4a93-3bc1-4f78-ac2b-c47f6948b20c" + } + ], + "captureComplete": true + } + ], + "captureComplete": true + } + }, + "agentId": "ahp-agent", + "id": "9a2a348c-d403-4153-9554-3acc2ca61ed9", + "timestamp": "2026-09-26T00:10:18.610Z", + "parentId": null + } + }, + { + "name": "queued_notification", + "event": { + "type": "system.notification", + "data": { + "content": "\nAgent \"worker\" (general-purpose) has finished processing and is now idle.\n", + "kind": { + "type": "agent_idle", + "agentId": "worker", + "agentType": "general-purpose" + }, + "workerCausality": { + "version": 1, + "observationProvenance": "native", + "sources": [ + { + "input": { + "queueItemId": "bb31eee3-11b8-41bb-b392-b5d4f25061be", + "agentId": "worker" + }, + "admissions": [ + { + "kind": "queued_input", + "messageId": "c7b4146b-6a6a-40e7-873a-6e67438a7923", + "event": { + "sessionId": "b9230967-dd47-416e-bc03-c470afd2c87d", + "eventId": "33db0052-4391-4418-a46e-26e9bac8d5a9", + "agentId": "worker", + "eventType": "user.message", + "provenance": "native" + } + } + ], + "captureComplete": true, + "completion": { + "sessionId": "b9230967-dd47-416e-bc03-c470afd2c87d", + "eventId": "30f8471f-343a-44b8-ae95-86c8eed36afc", + "agentId": "worker", + "eventType": "subagent.completed", + "provenance": "native" + }, + "notification": { + "deliveryId": "9cef2f54-c16f-49a8-a2cd-58da52108e60", + "mode": "queued" + } + } + ], + "captureComplete": true + } + }, + "id": "3943fdab-2f6a-4b52-9b1f-e23a449fa8bd", + "timestamp": "2026-09-26T00:01:00.730Z", + "parentId": "30f8471f-343a-44b8-ae95-86c8eed36afc" + } + }, + { + "name": "immediate_notification", + "event": { + "type": "system.notification", + "data": { + "content": "\nAgent \"worker-b\" (general-purpose) has finished processing and is now idle.\n", + "kind": { + "type": "agent_idle", + "agentId": "worker-b", + "agentType": "general-purpose" + }, + "workerCausality": { + "version": 1, + "observationProvenance": "native", + "sources": [ + { + "input": { + "queueItemId": "68c1fe08-f176-4fe9-acca-3e1714bee0d0", + "agentId": "worker-b" + }, + "admissions": [ + { + "kind": "queued_input", + "messageId": "f304b273-03ec-4de9-b7fb-c1c20c4a8b44", + "event": { + "sessionId": "804ee2d2-9d44-40be-bc3a-4276ede7a258", + "eventId": "0be71b00-c858-49b6-b913-70ebd421f398", + "agentId": "worker-b", + "eventType": "user.message", + "provenance": "native" + } + } + ], + "captureComplete": true, + "completion": { + "sessionId": "804ee2d2-9d44-40be-bc3a-4276ede7a258", + "eventId": "bff8358e-e988-4488-967f-5d66ae754df5", + "agentId": "worker-b", + "eventType": "subagent.completed", + "provenance": "native" + }, + "notification": { + "deliveryId": "fdac777b-3e92-4b35-94ab-d3ec905b8d93", + "mode": "immediate" + }, + "admittedDuring": { + "sessionId": "804ee2d2-9d44-40be-bc3a-4276ede7a258", + "eventId": "3b105bff-0011-4658-9855-70d64f5d6eef", + "eventType": "assistant.turn_start", + "provenance": "native" + } + } + ], + "captureComplete": true + } + }, + "id": "5d7a225b-b974-4acb-a701-c626dfc4292c", + "timestamp": "2026-09-26T00:01:00.735Z", + "parentId": "3b105bff-0011-4658-9855-70d64f5d6eef" + } + }, + { + "name": "two_source_assistant_turn_start", + "event": { + "type": "assistant.turn_start", + "data": { + "turnId": "1", + "interactionId": "test-interaction", + "workerCausality": { + "version": 1, + "observationProvenance": "native", + "sources": [ + { + "input": { + "queueItemId": "68c1fe08-f176-4fe9-acca-3e1714bee0d0", + "agentId": "worker-b" + }, + "admissions": [ + { + "kind": "queued_input", + "messageId": "f304b273-03ec-4de9-b7fb-c1c20c4a8b44", + "event": { + "sessionId": "804ee2d2-9d44-40be-bc3a-4276ede7a258", + "eventId": "0be71b00-c858-49b6-b913-70ebd421f398", + "agentId": "worker-b", + "eventType": "user.message", + "provenance": "native" + } + } + ], + "captureComplete": true, + "completion": { + "sessionId": "804ee2d2-9d44-40be-bc3a-4276ede7a258", + "eventId": "bff8358e-e988-4488-967f-5d66ae754df5", + "agentId": "worker-b", + "eventType": "subagent.completed", + "provenance": "native" + }, + "notification": { + "deliveryId": "fdac777b-3e92-4b35-94ab-d3ec905b8d93", + "event": { + "sessionId": "804ee2d2-9d44-40be-bc3a-4276ede7a258", + "eventId": "5d7a225b-b974-4acb-a701-c626dfc4292c", + "eventType": "system.notification", + "provenance": "native" + }, + "mode": "immediate" + }, + "admittedDuring": { + "sessionId": "804ee2d2-9d44-40be-bc3a-4276ede7a258", + "eventId": "3b105bff-0011-4658-9855-70d64f5d6eef", + "eventType": "assistant.turn_start", + "provenance": "native" + } + }, + { + "input": { + "queueItemId": "845ea619-b3d7-4371-922f-fa6e2b459aec", + "agentId": "worker-a" + }, + "admissions": [ + { + "kind": "queued_input", + "messageId": "15fb0cf7-b9c0-4780-b220-eaec4394c2c4", + "event": { + "sessionId": "804ee2d2-9d44-40be-bc3a-4276ede7a258", + "eventId": "81725409-6a40-4865-b7a6-8a28bb2a86b2", + "agentId": "worker-a", + "eventType": "user.message", + "provenance": "native" + } + } + ], + "captureComplete": true, + "completion": { + "sessionId": "804ee2d2-9d44-40be-bc3a-4276ede7a258", + "eventId": "523256b3-a0fa-47dc-a20e-897532a63282", + "agentId": "worker-a", + "eventType": "subagent.completed", + "provenance": "native" + }, + "notification": { + "deliveryId": "30a214f7-26e5-42d1-ac0a-907045470ce5", + "event": { + "sessionId": "804ee2d2-9d44-40be-bc3a-4276ede7a258", + "eventId": "de0ae0b2-2b8a-4f8d-825c-9ffc8ce9717a", + "eventType": "system.notification", + "provenance": "native" + }, + "mode": "immediate" + }, + "admittedDuring": { + "sessionId": "804ee2d2-9d44-40be-bc3a-4276ede7a258", + "eventId": "3b105bff-0011-4658-9855-70d64f5d6eef", + "eventType": "assistant.turn_start", + "provenance": "native" + } + } + ], + "captureComplete": true + } + }, + "id": "fa1ff4f8-da1b-4cd6-88cb-9ab17f9e2dd7", + "timestamp": "2026-09-26T00:01:00.739Z", + "parentId": "0473e682-62c4-4967-88ea-4ee0bc7b272f" + } + }, + { + "name": "incomplete_no_completion_start", + "event": { + "type": "assistant.turn_start", + "data": { + "turnId": "0", + "interactionId": "e4ee7454-40c1-43a8-9f23-24ef9fb5dccf", + "workerCausality": { + "version": 1, + "observationProvenance": "native", + "sources": [ + { + "input": { + "queueItemId": "fe58ed76-866d-4c98-96f6-6991bb4c1d91", + "agentId": "worker" + }, + "admissions": [ + { + "kind": "queued_input", + "messageId": "a9b49f68-fd65-40ba-86dc-c5fae4fe62c3", + "event": { + "sessionId": "496e2310-08bb-4a12-9cf9-4d45250b9137", + "eventId": "b3a0df0d-1cbd-4abe-b017-b3a8f61a2679", + "agentId": "worker", + "eventType": "user.message", + "provenance": "native" + } + } + ], + "captureComplete": false, + "notification": { + "deliveryId": "b28ffb50-7a63-4bd0-86dc-de1b8f2467a1", + "event": { + "sessionId": "496e2310-08bb-4a12-9cf9-4d45250b9137", + "eventId": "78370a8b-9ffc-4a0f-ad16-057779fe6351", + "eventType": "system.notification", + "provenance": "native" + }, + "mode": "queued" + } + } + ], + "captureComplete": false + } + }, + "id": "041d5901-34b8-4b5e-a5c3-0567150caa75", + "timestamp": "2026-09-26T00:01:00.736Z", + "parentId": "4348bb60-8821-4af3-add3-22461951d959" + } + }, + { + "name": "tasks_send_message_result", + "result": { + "sent": true, + "workerCausality": { + "version": 1, + "observationProvenance": "native", + "sources": [ + { + "input": { + "queueItemId": "46fc3a43-7f19-4b0a-9e32-d5ef4dc25552", + "agentId": "worker" + }, + "admissions": [], + "captureComplete": true + } + ], + "captureComplete": true + } + } + } + ], + "invalid": [ + { + "name": "null", + "value": null + }, + { + "name": "non_object", + "value": true + }, + { + "name": "unsupported_version", + "value": { + "version": 2, + "observationProvenance": "native", + "sources": [ + { + "input": { + "queueItemId": "46fc3a43-7f19-4b0a-9e32-d5ef4dc25552", + "agentId": "worker" + }, + "admissions": [], + "captureComplete": true + } + ], + "captureComplete": true + } + }, + { + "name": "unknown_top_level_field", + "value": { + "version": 1, + "observationProvenance": "native", + "sources": [ + { + "input": { + "queueItemId": "46fc3a43-7f19-4b0a-9e32-d5ef4dc25552", + "agentId": "worker" + }, + "admissions": [], + "captureComplete": true + } + ], + "captureComplete": true, + "future": true + } + }, + { + "name": "unknown_source_field", + "value": { + "version": 1, + "observationProvenance": "native", + "sources": [ + { + "input": { + "queueItemId": "46fc3a43-7f19-4b0a-9e32-d5ef4dc25552", + "agentId": "worker" + }, + "admissions": [], + "captureComplete": true, + "future": true + } + ], + "captureComplete": true + } + }, + { + "name": "unknown_input_field", + "value": { + "version": 1, + "observationProvenance": "native", + "sources": [ + { + "input": { + "queueItemId": "46fc3a43-7f19-4b0a-9e32-d5ef4dc25552", + "agentId": "worker", + "future": true + }, + "admissions": [], + "captureComplete": true + } + ], + "captureComplete": true + } + }, + { + "name": "unknown_admission_field", + "value": { + "version": 1, + "observationProvenance": "native", + "sources": [ + { + "input": { + "queueItemId": "2dbbe116-73b0-4011-bfa3-3b571b41404b", + "agentId": "recipient", + "sender": { + "sessionId": "0187f098-31d3-4cdc-ab71-e6ab387e01b4", + "eventId": "773e548e-64e5-4418-853b-aabf128ce858", + "eventType": "tool.execution_start", + "provenance": "native" + }, + "senderBridges": [ + { + "source": { + "sessionId": "0187f098-31d3-4cdc-ab71-e6ab387e01b4", + "eventId": "773e548e-64e5-4418-853b-aabf128ce858", + "eventType": "tool.execution_start", + "provenance": "native" + }, + "reported": { + "sessionId": "23ac4baf-eb8e-4fdc-b8e0-5633e4ed550b", + "eventId": "8f608be4-c132-4e19-8bdd-82ff5ba9b0b8", + "agentId": "leaf-agent", + "eventType": "tool.execution_start", + "provenance": "native" + } + }, + { + "source": { + "sessionId": "23ac4baf-eb8e-4fdc-b8e0-5633e4ed550b", + "eventId": "8f608be4-c132-4e19-8bdd-82ff5ba9b0b8", + "agentId": "leaf-agent", + "eventType": "tool.execution_start", + "provenance": "native" + }, + "reported": { + "sessionId": "ce52204a-54b0-4ebd-ba13-4854598c4399", + "eventId": "69362832-885b-4787-ab3e-1a2f6c17c73b", + "agentId": "leaf-agent", + "eventType": "tool.execution_start", + "provenance": "native" + } + }, + { + "source": { + "sessionId": "23ac4baf-eb8e-4fdc-b8e0-5633e4ed550b", + "eventId": "8f608be4-c132-4e19-8bdd-82ff5ba9b0b8", + "agentId": "leaf-agent", + "eventType": "tool.execution_start", + "provenance": "native" + }, + "reported": { + "sessionId": "f651ee35-bdb9-4c91-aa21-0057a9446269", + "eventId": "b13511dc-0c7a-4d9c-9618-c04c1c50c4bc", + "agentId": "leaf-agent", + "eventType": "tool.execution_start", + "provenance": "native" + } + } + ] + }, + "admissions": [ + { + "kind": "queued_input", + "messageId": "bb61b7c0-bc90-4290-a1e4-79558dc35139", + "future": true + } + ], + "captureComplete": true + } + ], + "captureComplete": true + } + }, + { + "name": "unknown_reference_field", + "value": { + "version": 1, + "observationProvenance": "native", + "sources": [ + { + "input": { + "queueItemId": "2dbbe116-73b0-4011-bfa3-3b571b41404b", + "agentId": "recipient", + "sender": { + "sessionId": "0187f098-31d3-4cdc-ab71-e6ab387e01b4", + "eventId": "773e548e-64e5-4418-853b-aabf128ce858", + "eventType": "tool.execution_start", + "provenance": "native", + "future": true + }, + "senderBridges": [ + { + "source": { + "sessionId": "0187f098-31d3-4cdc-ab71-e6ab387e01b4", + "eventId": "773e548e-64e5-4418-853b-aabf128ce858", + "eventType": "tool.execution_start", + "provenance": "native" + }, + "reported": { + "sessionId": "23ac4baf-eb8e-4fdc-b8e0-5633e4ed550b", + "eventId": "8f608be4-c132-4e19-8bdd-82ff5ba9b0b8", + "agentId": "leaf-agent", + "eventType": "tool.execution_start", + "provenance": "native" + } + }, + { + "source": { + "sessionId": "23ac4baf-eb8e-4fdc-b8e0-5633e4ed550b", + "eventId": "8f608be4-c132-4e19-8bdd-82ff5ba9b0b8", + "agentId": "leaf-agent", + "eventType": "tool.execution_start", + "provenance": "native" + }, + "reported": { + "sessionId": "ce52204a-54b0-4ebd-ba13-4854598c4399", + "eventId": "69362832-885b-4787-ab3e-1a2f6c17c73b", + "agentId": "leaf-agent", + "eventType": "tool.execution_start", + "provenance": "native" + } + }, + { + "source": { + "sessionId": "23ac4baf-eb8e-4fdc-b8e0-5633e4ed550b", + "eventId": "8f608be4-c132-4e19-8bdd-82ff5ba9b0b8", + "agentId": "leaf-agent", + "eventType": "tool.execution_start", + "provenance": "native" + }, + "reported": { + "sessionId": "f651ee35-bdb9-4c91-aa21-0057a9446269", + "eventId": "b13511dc-0c7a-4d9c-9618-c04c1c50c4bc", + "agentId": "leaf-agent", + "eventType": "tool.execution_start", + "provenance": "native" + } + } + ] + }, + "admissions": [ + { + "kind": "queued_input", + "messageId": "bb61b7c0-bc90-4290-a1e4-79558dc35139" + } + ], + "captureComplete": true + } + ], + "captureComplete": true + } + }, + { + "name": "malformed_queue_uuid", + "value": { + "version": 1, + "observationProvenance": "native", + "sources": [ + { + "input": { + "queueItemId": "not-a-uuid", + "agentId": "worker" + }, + "admissions": [], + "captureComplete": true + } + ], + "captureComplete": true + } + }, + { + "name": "contradictory_complete_capture", + "value": { + "version": 1, + "observationProvenance": "native", + "sources": [ + { + "input": { + "queueItemId": "fe58ed76-866d-4c98-96f6-6991bb4c1d91", + "agentId": "worker" + }, + "admissions": [ + { + "kind": "queued_input", + "messageId": "a9b49f68-fd65-40ba-86dc-c5fae4fe62c3", + "event": { + "sessionId": "496e2310-08bb-4a12-9cf9-4d45250b9137", + "eventId": "b3a0df0d-1cbd-4abe-b017-b3a8f61a2679", + "agentId": "worker", + "eventType": "user.message", + "provenance": "native" + } + } + ], + "captureComplete": false, + "notification": { + "deliveryId": "b28ffb50-7a63-4bd0-86dc-de1b8f2467a1", + "event": { + "sessionId": "496e2310-08bb-4a12-9cf9-4d45250b9137", + "eventId": "78370a8b-9ffc-4a0f-ad16-057779fe6351", + "eventType": "system.notification", + "provenance": "native" + }, + "mode": "queued" + } + } + ], + "captureComplete": true + } + }, + { + "name": "missing_required_sources", + "value": { + "version": 1, + "observationProvenance": "native", + "captureComplete": false + } + } + ], + "boundaries": [ + { + "name": "exact_4096_ascii", + "value": { + "version": 1, + "observationProvenance": "native", + "sources": [ + { + "input": { + "queueItemId": "46fc3a43-7f19-4b0a-9e32-d5ef4dc25552", + "agentId": "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" + }, + "admissions": [], + "captureComplete": true + } + ], + "captureComplete": true + }, + "accepted": true + }, + { + "name": "4097_ascii", + "value": { + "version": 1, + "observationProvenance": "native", + "sources": [ + { + "input": { + "queueItemId": "46fc3a43-7f19-4b0a-9e32-d5ef4dc25552", + "agentId": "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaax" + }, + "admissions": [], + "captureComplete": true + } + ], + "captureComplete": true + }, + "accepted": false + }, + { + "name": "exact_4096_utf8_and_escaping", + "value": { + "version": 1, + "observationProvenance": "native", + "sources": [ + { + "input": { + "queueItemId": "46fc3a43-7f19-4b0a-9e32-d5ef4dc25552", + "agentId": "é🐈\"\\\naaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" + }, + "admissions": [], + "captureComplete": true + } + ], + "captureComplete": true + }, + "accepted": true + } + ] +} From f2a70351aad67e0b75a2f62566a9667f8be001c8 Mon Sep 17 00:00:00 2001 From: Michael Dinh Date: Sat, 26 Sep 2026 01:55:15 -0400 Subject: [PATCH 14/25] fix: enforce worker causality bounds Consume runtime worker-causality seal revision 3. Enforce the fully materialized 4096-byte graph domain and 256-byte UTF-8 identities, add ASCII and multibyte 257-byte rejection vectors, and document the authoritative integrated UI boundary. Runtime-Seal: dcf9cb24b3c1c25b81318df794a9addf9a5ebffe Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> --- docs/features/execution-identity.md | 8 +- dotnet/src/Generated/SessionEvents.cs | 23 +- dotnet/src/WorkerCausalityConverter.cs | 6 +- .../diagnosticmetadata/worker_causality.go | 4 +- go/rpc/zrpc.go | 11 +- .../copilot/generated/WorkerAdmission.java | 2 +- .../copilot/generated/WorkerCausality.java | 3 +- .../generated/WorkerEventReference.java | 4 +- .../github/copilot/generated/WorkerInput.java | 2 +- .../generated/rpc/WorkerAdmission.java | 2 +- .../generated/rpc/WorkerCausality.java | 3 +- .../generated/rpc/WorkerEventReference.java | 4 +- .../copilot/generated/rpc/WorkerInput.java | 2 +- .../copilot/WorkerCausalityDeserializer.java | 3 +- nodejs/src/generated/session-events.ts | 11 +- nodejs/src/workerCausality.ts | 15 +- python/copilot/_worker_causality.py | 4 +- python/copilot/generated/session_events.py | 2 +- rust/src/generated/session_events.rs | 11 +- rust/src/worker_causality.rs | 6 +- test/worker-causality.json | 676 +++++++++++++++++- 21 files changed, 746 insertions(+), 56 deletions(-) diff --git a/docs/features/execution-identity.md b/docs/features/execution-identity.md index 524face2be..092e133031 100644 --- a/docs/features/execution-identity.md +++ b/docs/features/execution-identity.md @@ -218,9 +218,11 @@ Admission correlation is not a frontend outcome. The SDK does not infer a displayed submission error, preserved draft, recovery result, browser attempt, surface attribution or physical dispatch from protocol acceptance. Pending application recovery must be generation-bound and invalidated by a -session/client-generation change or Stop. The current frontend association -contract is github/github-ui#35143 at -`4c2a7dfd381a57206995041ba4e4b1dbd4600e83`; backend carriers remain generic. +session/client-generation change or Stop. The original frontend +association-boundary ancestor is github/github-ui#35143 at +`4c2a7dfd381a57206995041ba4e4b1dbd4600e83`; the current authoritative integrated +UI head is `485a6c2c3aad0f77f41aad7057c117490f358c43`. Backend carriers remain generic, +and UI evidence does not enter the runtime contract. ### Release prerequisites and the existing Rust raw API diff --git a/dotnet/src/Generated/SessionEvents.cs b/dotnet/src/Generated/SessionEvents.cs index d44c6b993e..993c84bce8 100644 --- a/dotnet/src/Generated/SessionEvents.cs +++ b/dotnet/src/Generated/SessionEvents.cs @@ -8068,7 +8068,10 @@ public partial class Attachment /// Nested data type for WorkerEventReference. public sealed partial class WorkerEventReference { - /// Actual event agent scope, absent for a root occurrence. + /// Actual event agent scope, absent for a root occurrence; at most 256 UTF-8 bytes. + [UnconditionalSuppressMessage("Trimming", "IL2026", Justification = "Safe for generated string properties: JSON Schema minLength/maxLength map to string length validation, not reflection over trimmed Count members")] + [MinLength(1)] + [MaxLength(256)] [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] [JsonPropertyName("agentId")] public string? AgentId { get; set; } @@ -8085,7 +8088,10 @@ public sealed partial class WorkerEventReference [JsonPropertyName("provenance")] public required WorkerObservationProvenance Provenance { get; set; } - /// Actual runtime session scope. + /// Actual runtime session scope, at most 256 UTF-8 bytes. + [UnconditionalSuppressMessage("Trimming", "IL2026", Justification = "Safe for generated string properties: JSON Schema minLength/maxLength map to string length validation, not reflection over trimmed Count members")] + [MinLength(1)] + [MaxLength(256)] [JsonPropertyName("sessionId")] public required string SessionId { get; set; } } @@ -8108,7 +8114,10 @@ public sealed partial class WorkerAdmission [JsonPropertyName("kind")] public required WorkerAdmissionKind Kind { get; set; } - /// Canonical logical message identity, independent of queueItemId. + /// Canonical logical message identity, independent of queueItemId; at most 256 UTF-8 bytes. + [UnconditionalSuppressMessage("Trimming", "IL2026", Justification = "Safe for generated string properties: JSON Schema minLength/maxLength map to string length validation, not reflection over trimmed Count members")] + [MinLength(1)] + [MaxLength(256)] [JsonPropertyName("messageId")] public required string MessageId { get; set; } } @@ -8130,7 +8139,10 @@ public sealed partial class WorkerBridgeObservation /// Nested data type for WorkerInput. public sealed partial class WorkerInput { - /// Actual recipient task. + /// Actual recipient task, at most 256 UTF-8 bytes. + [UnconditionalSuppressMessage("Trimming", "IL2026", Justification = "Safe for generated string properties: JSON Schema minLength/maxLength map to string length validation, not reflection over trimmed Count members")] + [MinLength(1)] + [MaxLength(256)] [JsonPropertyName("agentId")] public required string AgentId { get; set; } @@ -8201,7 +8213,8 @@ public sealed partial class WorkerSource /// /// Optional v1 worker diagnostics. The compact UTF-8 {"workerCausality":value} -/// must fit 4096 bytes. Ignore invalid/unknown/oversize metadata, not the product event. +/// must fit 4096 bytes after materializing an allowed implicit self-reference. +/// Ignore invalid/unknown/oversize metadata, not the product event. /// /// Nested data type for WorkerCausality. public sealed partial class WorkerCausality diff --git a/dotnet/src/WorkerCausalityConverter.cs b/dotnet/src/WorkerCausalityConverter.cs index 40fc361ff6..db2f2156db 100644 --- a/dotnet/src/WorkerCausalityConverter.cs +++ b/dotnet/src/WorkerCausalityConverter.cs @@ -3,6 +3,7 @@ *--------------------------------------------------------------------------------------------*/ using System.Diagnostics; +using System.Text; using System.Text.Json; using System.Text.Json.Serialization; using System.Text.Json.Serialization.Metadata; @@ -110,7 +111,10 @@ private static bool Fields(JsonElement value, params string[] allowed) } private static bool Text(JsonElement value, string property) => - value.TryGetProperty(property, out var text) && text.ValueKind == JsonValueKind.String && text.GetString()!.Length > 0; + value.TryGetProperty(property, out var text) && + text.ValueKind == JsonValueKind.String && + text.GetString()!.Length > 0 && + Encoding.UTF8.GetByteCount(text.GetString()!) <= 256; private static bool Uuid(JsonElement value, string property) => Text(value, property) && Guid.TryParseExact(value.GetProperty(property).GetString(), "D", out _); diff --git a/go/internal/diagnosticmetadata/worker_causality.go b/go/internal/diagnosticmetadata/worker_causality.go index 19f0eb3c85..17dbca22ab 100644 --- a/go/internal/diagnosticmetadata/worker_causality.go +++ b/go/internal/diagnosticmetadata/worker_causality.go @@ -15,7 +15,7 @@ var sessionPattern = regexp.MustCompile(`^[A-Za-z0-9_-]+$`) func text(value any) bool { s, ok := value.(string) - return ok && s != "" + return ok && s != "" && len(s) <= 256 } func uuid(value any) bool { @@ -50,7 +50,7 @@ func reference(value any, eventType string) bool { session, ok := ref["sessionId"].(string) agent, hasAgent := ref["agentId"] return fields(ref, "sessionId", "eventId", "agentId", "eventType", "provenance") && - ok && sessionPattern.MatchString(session) && uuid(ref["eventId"]) && + ok && len(session) <= 256 && sessionPattern.MatchString(session) && uuid(ref["eventId"]) && (!hasAgent || text(agent)) && ref["eventType"] == eventType && (ref["provenance"] == "native" || ref["provenance"] == "ahp_coordinator") } diff --git a/go/rpc/zrpc.go b/go/rpc/zrpc.go index 6a2612614d..84e1328dcd 100644 --- a/go/rpc/zrpc.go +++ b/go/rpc/zrpc.go @@ -17564,7 +17564,7 @@ type WorkerAdmission struct { Event *WorkerEventReference `json:"event,omitempty"` // The producer's admission kind. Kind WorkerAdmissionKind `json:"kind"` - // Canonical logical message identity, independent of queueItemId. + // Canonical logical message identity, independent of queueItemId; at most 256 UTF-8 bytes. MessageID string `json:"messageId"` } @@ -17579,7 +17579,8 @@ type WorkerBridgeObservation struct { } // Optional v1 worker diagnostics. The compact UTF-8 {"workerCausality":value} -// must fit 4096 bytes. Ignore invalid/unknown/oversize metadata, not the product event. +// must fit 4096 bytes after materializing an allowed implicit self-reference. +// Ignore invalid/unknown/oversize metadata, not the product event. // Experimental: WorkerCausality is part of an experimental API and may change or be removed. type WorkerCausality struct { // Complete placement-aware observed capture, not global causality or execution success. @@ -17597,7 +17598,7 @@ type WorkerCausality struct { // Experimental: WorkerEventReference is part of an experimental API and may change or be // removed. type WorkerEventReference struct { - // Actual event agent scope, absent for a root occurrence. + // Actual event agent scope, absent for a root occurrence; at most 256 UTF-8 bytes. AgentID *string `json:"agentId,omitempty"` // Actual event occurrence UUID; copied without normalization. EventID string `json:"eventId"` @@ -17605,14 +17606,14 @@ type WorkerEventReference struct { EventType WorkerEventType `json:"eventType"` // Explicit provenance of this reference. Provenance WorkerObservationProvenance `json:"provenance"` - // Actual runtime session scope. + // Actual runtime session scope, at most 256 UTF-8 bytes. SessionID string `json:"sessionId"` } // Exact accepted worker input, distinct from a message, event, caller correlation or Turn. // Experimental: WorkerInput is part of an experimental API and may change or be removed. type WorkerInput struct { - // Actual recipient task. + // Actual recipient task, at most 256 UTF-8 bytes. AgentID string `json:"agentId"` // UUID allocated for this queue item by the admitting producer. QueueItemID string `json:"queueItemId"` diff --git a/java/sdk/src/generated/java/com/github/copilot/generated/WorkerAdmission.java b/java/sdk/src/generated/java/com/github/copilot/generated/WorkerAdmission.java index dc8e906b59..ed71918779 100644 --- a/java/sdk/src/generated/java/com/github/copilot/generated/WorkerAdmission.java +++ b/java/sdk/src/generated/java/com/github/copilot/generated/WorkerAdmission.java @@ -24,7 +24,7 @@ public record WorkerAdmission( /** The producer's admission kind. */ @JsonProperty("kind") WorkerAdmissionKind kind, - /** Canonical logical message identity, independent of queueItemId. */ + /** Canonical logical message identity, independent of queueItemId; at most 256 UTF-8 bytes. */ @JsonProperty("messageId") String messageId, /** May be omitted only for the matching current worker user.message. */ @JsonProperty("event") WorkerEventReference event, diff --git a/java/sdk/src/generated/java/com/github/copilot/generated/WorkerCausality.java b/java/sdk/src/generated/java/com/github/copilot/generated/WorkerCausality.java index 6ae6cb9454..6b3b35e760 100644 --- a/java/sdk/src/generated/java/com/github/copilot/generated/WorkerCausality.java +++ b/java/sdk/src/generated/java/com/github/copilot/generated/WorkerCausality.java @@ -15,7 +15,8 @@ /** * Optional v1 worker diagnostics. The compact UTF-8 {"workerCausality":value} -must fit 4096 bytes. Ignore invalid/unknown/oversize metadata, not the product event. +must fit 4096 bytes after materializing an allowed implicit self-reference. +Ignore invalid/unknown/oversize metadata, not the product event. * * @since 1.0.0 */ diff --git a/java/sdk/src/generated/java/com/github/copilot/generated/WorkerEventReference.java b/java/sdk/src/generated/java/com/github/copilot/generated/WorkerEventReference.java index 4a5f5851ed..a57913ede5 100644 --- a/java/sdk/src/generated/java/com/github/copilot/generated/WorkerEventReference.java +++ b/java/sdk/src/generated/java/com/github/copilot/generated/WorkerEventReference.java @@ -22,11 +22,11 @@ @JsonInclude(JsonInclude.Include.NON_NULL) @JsonIgnoreProperties(ignoreUnknown = true) public record WorkerEventReference( - /** Actual runtime session scope. */ + /** Actual runtime session scope, at most 256 UTF-8 bytes. */ @JsonProperty("sessionId") String sessionId, /** Actual event occurrence UUID; copied without normalization. */ @JsonProperty("eventId") UUID eventId, - /** Actual event agent scope, absent for a root occurrence. */ + /** Actual event agent scope, absent for a root occurrence; at most 256 UTF-8 bytes. */ @JsonProperty("agentId") String agentId, /** Type of the observed occurrence. */ @JsonProperty("eventType") WorkerEventType eventType, diff --git a/java/sdk/src/generated/java/com/github/copilot/generated/WorkerInput.java b/java/sdk/src/generated/java/com/github/copilot/generated/WorkerInput.java index 0e06852e94..d4bb60ee16 100644 --- a/java/sdk/src/generated/java/com/github/copilot/generated/WorkerInput.java +++ b/java/sdk/src/generated/java/com/github/copilot/generated/WorkerInput.java @@ -25,7 +25,7 @@ public record WorkerInput( /** UUID allocated for this queue item by the admitting producer. */ @JsonProperty("queueItemId") UUID queueItemId, - /** Actual recipient task. */ + /** Actual recipient task, at most 256 UTF-8 bytes. */ @JsonProperty("agentId") String agentId, /** Original invoking occurrence, never replaced by a reported/root alias. */ @JsonProperty("sender") WorkerEventReference sender, diff --git a/java/sdk/src/generated/java/com/github/copilot/generated/rpc/WorkerAdmission.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/WorkerAdmission.java index afca7361b2..8d6a2d29e1 100644 --- a/java/sdk/src/generated/java/com/github/copilot/generated/rpc/WorkerAdmission.java +++ b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/WorkerAdmission.java @@ -24,7 +24,7 @@ public record WorkerAdmission( /** The producer's admission kind. */ @JsonProperty("kind") WorkerAdmissionKind kind, - /** Canonical logical message identity, independent of queueItemId. */ + /** Canonical logical message identity, independent of queueItemId; at most 256 UTF-8 bytes. */ @JsonProperty("messageId") String messageId, /** May be omitted only for the matching current worker user.message. */ @JsonProperty("event") WorkerEventReference event, diff --git a/java/sdk/src/generated/java/com/github/copilot/generated/rpc/WorkerCausality.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/WorkerCausality.java index ade846df3d..6ee72fd991 100644 --- a/java/sdk/src/generated/java/com/github/copilot/generated/rpc/WorkerCausality.java +++ b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/WorkerCausality.java @@ -15,7 +15,8 @@ /** * Optional v1 worker diagnostics. The compact UTF-8 {"workerCausality":value} -must fit 4096 bytes. Ignore invalid/unknown/oversize metadata, not the product event. +must fit 4096 bytes after materializing an allowed implicit self-reference. +Ignore invalid/unknown/oversize metadata, not the product event. * * @since 1.0.0 */ diff --git a/java/sdk/src/generated/java/com/github/copilot/generated/rpc/WorkerEventReference.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/WorkerEventReference.java index 4aff22471e..677655a200 100644 --- a/java/sdk/src/generated/java/com/github/copilot/generated/rpc/WorkerEventReference.java +++ b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/WorkerEventReference.java @@ -22,11 +22,11 @@ @JsonInclude(JsonInclude.Include.NON_NULL) @JsonIgnoreProperties(ignoreUnknown = true) public record WorkerEventReference( - /** Actual runtime session scope. */ + /** Actual runtime session scope, at most 256 UTF-8 bytes. */ @JsonProperty("sessionId") String sessionId, /** Actual event occurrence UUID; copied without normalization. */ @JsonProperty("eventId") UUID eventId, - /** Actual event agent scope, absent for a root occurrence. */ + /** Actual event agent scope, absent for a root occurrence; at most 256 UTF-8 bytes. */ @JsonProperty("agentId") String agentId, /** Type of the observed occurrence. */ @JsonProperty("eventType") WorkerEventType eventType, diff --git a/java/sdk/src/generated/java/com/github/copilot/generated/rpc/WorkerInput.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/WorkerInput.java index b114e57c20..718851fa15 100644 --- a/java/sdk/src/generated/java/com/github/copilot/generated/rpc/WorkerInput.java +++ b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/WorkerInput.java @@ -25,7 +25,7 @@ public record WorkerInput( /** UUID allocated for this queue item by the admitting producer. */ @JsonProperty("queueItemId") UUID queueItemId, - /** Actual recipient task. */ + /** Actual recipient task, at most 256 UTF-8 bytes. */ @JsonProperty("agentId") String agentId, /** Original invoking occurrence, never replaced by a reported/root alias. */ @JsonProperty("sender") WorkerEventReference sender, diff --git a/java/sdk/src/main/java/com/github/copilot/WorkerCausalityDeserializer.java b/java/sdk/src/main/java/com/github/copilot/WorkerCausalityDeserializer.java index ac3c7b0126..0549302438 100644 --- a/java/sdk/src/main/java/com/github/copilot/WorkerCausalityDeserializer.java +++ b/java/sdk/src/main/java/com/github/copilot/WorkerCausalityDeserializer.java @@ -138,7 +138,8 @@ private static boolean fields(JsonNode value, String... allowed) { } private static boolean text(JsonNode value) { - return value.isTextual() && !value.textValue().isEmpty(); + return value.isTextual() && !value.textValue().isEmpty() + && value.textValue().getBytes(StandardCharsets.UTF_8).length <= 256; } private static boolean uuid(JsonNode value) { diff --git a/nodejs/src/generated/session-events.ts b/nodejs/src/generated/session-events.ts index 2e48f0cc9d..1cad647008 100644 --- a/nodejs/src/generated/session-events.ts +++ b/nodejs/src/generated/session-events.ts @@ -4811,7 +4811,8 @@ export interface AttachmentExtensionContext { } /** * Optional v1 worker diagnostics. The compact UTF-8 {"workerCausality":value} - * must fit 4096 bytes. Ignore invalid/unknown/oversize metadata, not the product event. + * must fit 4096 bytes after materializing an allowed implicit self-reference. + * Ignore invalid/unknown/oversize metadata, not the product event. */ export interface WorkerCausality { /** @@ -4861,7 +4862,7 @@ export interface WorkerAdmission { event?: WorkerEventReference; kind: WorkerAdmissionKind; /** - * Canonical logical message identity, independent of queueItemId. + * Canonical logical message identity, independent of queueItemId; at most 256 UTF-8 bytes. */ messageId: string; } @@ -4870,7 +4871,7 @@ export interface WorkerAdmission { */ export interface WorkerEventReference { /** - * Actual event agent scope, absent for a root occurrence. + * Actual event agent scope, absent for a root occurrence; at most 256 UTF-8 bytes. */ agentId?: string; /** @@ -4880,7 +4881,7 @@ export interface WorkerEventReference { eventType: WorkerEventType; provenance: WorkerObservationProvenance; /** - * Actual runtime session scope. + * Actual runtime session scope, at most 256 UTF-8 bytes. */ sessionId: string; } @@ -4889,7 +4890,7 @@ export interface WorkerEventReference { */ export interface WorkerInput { /** - * Actual recipient task. + * Actual recipient task, at most 256 UTF-8 bytes. */ agentId: string; /** diff --git a/nodejs/src/workerCausality.ts b/nodejs/src/workerCausality.ts index d1c64791e3..1f89e52334 100644 --- a/nodejs/src/workerCausality.ts +++ b/nodejs/src/workerCausality.ts @@ -10,6 +10,10 @@ function fields(value: Record, allowed: readonly string[]): boo return Object.keys(value).every((key) => allowed.includes(key)); } +function text(value: unknown): value is string { + return typeof value === "string" && value.length > 0 && Buffer.byteLength(value, "utf8") <= 256; +} + function small(value: unknown, budget: { remaining: number }, depth = 0): boolean { budget.remaining--; if (budget.remaining < 0 || depth > 32) return false; @@ -28,12 +32,11 @@ function reference(value: unknown, type: string): boolean { return ( object(value) && fields(value, ["sessionId", "eventId", "agentId", "eventType", "provenance"]) && - typeof value.sessionId === "string" && + text(value.sessionId) && /^[A-Za-z0-9_-]+$/.test(value.sessionId) && typeof value.eventId === "string" && /^[\da-f]{8}-[\da-f]{4}-[\da-f]{4}-[\da-f]{4}-[\da-f]{12}$/i.test(value.eventId) && - (!("agentId" in value) || - (typeof value.agentId === "string" && value.agentId.length > 0)) && + (!("agentId" in value) || text(value.agentId)) && value.eventType === type && (value.provenance === "native" || value.provenance === "ahp_coordinator") ); @@ -55,8 +58,7 @@ function source(value: unknown): boolean { typeof value.captureComplete === "boolean" && typeof input.queueItemId === "string" && /^[\da-f]{8}-[\da-f]{4}-[\da-f]{4}-[\da-f]{4}-[\da-f]{12}$/i.test(input.queueItemId) && - typeof input.agentId === "string" && - input.agentId.length > 0 && + text(input.agentId) && (!("sender" in input) || reference(input.sender, "tool.execution_start")) && (!("senderBridges" in input) || ("sender" in input && @@ -76,8 +78,7 @@ function source(value: unknown): boolean { object(item) && fields(item, ["kind", "messageId", "event", "ahpTurnId"]) && (item.kind === "queued_input" || item.kind === "system_continuation") && - typeof item.messageId === "string" && - item.messageId.length > 0 && + text(item.messageId) && (!("event" in item) || (reference(item.event, "user.message") && (item.event as Record).agentId === input.agentId)) && diff --git a/python/copilot/_worker_causality.py b/python/copilot/_worker_causality.py index 9afc8aefdc..774a4331e0 100644 --- a/python/copilot/_worker_causality.py +++ b/python/copilot/_worker_causality.py @@ -18,7 +18,7 @@ def _uuid(value: Any) -> bool: def _text(value: Any) -> bool: - return isinstance(value, str) and bool(value) + return isinstance(value, str) and bool(value) and len(value.encode("utf-8")) <= 256 def _fields(value: dict[str, Any], allowed: set[str]) -> bool: @@ -29,7 +29,7 @@ def _reference(value: Any, event_type: str) -> bool: return ( isinstance(value, dict) and _fields(value, {"sessionId", "eventId", "agentId", "eventType", "provenance"}) - and isinstance(value.get("sessionId"), str) + and _text(value.get("sessionId")) and re.fullmatch(r"[A-Za-z0-9_-]+", value["sessionId"]) is not None and _uuid(value.get("eventId")) and ("agentId" not in value or _text(value["agentId"])) diff --git a/python/copilot/generated/session_events.py b/python/copilot/generated/session_events.py index a84ae8fbee..cb783f4d6e 100644 --- a/python/copilot/generated/session_events.py +++ b/python/copilot/generated/session_events.py @@ -12969,7 +12969,7 @@ def to_dict(self) -> dict: @dataclass class WorkerCausality: - "Optional v1 worker diagnostics. The compact UTF-8 {\"workerCausality\":value}\nmust fit 4096 bytes. Ignore invalid/unknown/oversize metadata, not the product event." + "Optional v1 worker diagnostics. The compact UTF-8 {\"workerCausality\":value}\nmust fit 4096 bytes after materializing an allowed implicit self-reference.\nIgnore invalid/unknown/oversize metadata, not the product event." capture_complete: bool observation_provenance: WorkerObservationProvenance sources: list[WorkerSource] diff --git a/rust/src/generated/session_events.rs b/rust/src/generated/session_events.rs index 9209ea6e1f..b87a6a099e 100644 --- a/rust/src/generated/session_events.rs +++ b/rust/src/generated/session_events.rs @@ -2596,7 +2596,7 @@ pub struct SessionPermissionRecoveryData { #[derive(Debug, Clone, Default, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] pub struct WorkerEventReference { - /// Actual event agent scope, absent for a root occurrence. + /// Actual event agent scope, absent for a root occurrence; at most 256 UTF-8 bytes. #[serde(skip_serializing_if = "Option::is_none")] pub agent_id: Option, /// Actual event occurrence UUID; copied without normalization. @@ -2605,7 +2605,7 @@ pub struct WorkerEventReference { pub event_type: WorkerEventType, /// Explicit provenance of this reference. pub provenance: WorkerObservationProvenance, - /// Actual runtime session scope. + /// Actual runtime session scope, at most 256 UTF-8 bytes. pub session_id: SessionId, } @@ -2621,7 +2621,7 @@ pub struct WorkerAdmission { pub event: Option, /// The producer's admission kind. pub kind: WorkerAdmissionKind, - /// Canonical logical message identity, independent of queueItemId. + /// Canonical logical message identity, independent of queueItemId; at most 256 UTF-8 bytes. pub message_id: String, } @@ -2639,7 +2639,7 @@ pub struct WorkerBridgeObservation { #[derive(Debug, Clone, Default, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] pub struct WorkerInput { - /// Actual recipient task. + /// Actual recipient task, at most 256 UTF-8 bytes. pub agent_id: String, /// UUID allocated for this queue item by the admitting producer. pub queue_item_id: String, @@ -2686,7 +2686,8 @@ pub struct WorkerSource { } /// Optional v1 worker diagnostics. The compact UTF-8 {"workerCausality":value} -/// must fit 4096 bytes. Ignore invalid/unknown/oversize metadata, not the product event. +/// must fit 4096 bytes after materializing an allowed implicit self-reference. +/// Ignore invalid/unknown/oversize metadata, not the product event. #[derive(Debug, Clone, Default, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] pub struct WorkerCausality { diff --git a/rust/src/worker_causality.rs b/rust/src/worker_causality.rs index 779b84d37e..d8896cc638 100644 --- a/rust/src/worker_causality.rs +++ b/rust/src/worker_causality.rs @@ -26,7 +26,9 @@ impl Write for Budget { } fn text(value: &Value) -> bool { - value.as_str().is_some_and(|value| !value.is_empty()) + value + .as_str() + .is_some_and(|value| !value.is_empty() && value.len() <= 256) } fn uuid(value: &Value) -> bool { @@ -53,7 +55,7 @@ fn reference(value: &Value, event_type: &str) -> bool { value, &["sessionId", "eventId", "agentId", "eventType", "provenance"], ) && value["sessionId"].as_str().is_some_and(|session| { - !session.is_empty() + text(&value["sessionId"]) && session .bytes() .all(|c| c.is_ascii_alphanumeric() || matches!(c, b'_' | b'-')) diff --git a/test/worker-causality.json b/test/worker-causality.json index 46ffc54f77..4866a09b44 100644 --- a/test/worker-causality.json +++ b/test/worker-causality.json @@ -422,6 +422,108 @@ "captureComplete": true } } + }, + { + "name": "identity_256_utf8_bytes", + "result": { + "sent": true, + "workerCausality": { + "version": 1, + "observationProvenance": "native", + "sources": [ + { + "input": { + "queueItemId": "46fc3a43-7f19-4b0a-9e32-d5ef4dc25552", + "agentId": "éééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééé" + }, + "admissions": [], + "captureComplete": true + } + ], + "captureComplete": true + } + } + }, + { + "name": "implicit_self_wire_fits_but_expansion_exceeds_budget", + "event": { + "type": "user.message", + "data": { + "content": "test input", + "messageId": "mmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmm", + "workerCausality": { + "version": 1, + "observationProvenance": "native", + "sources": [ + { + "input": { + "queueItemId": "46fc3a43-7f19-4b0a-9e32-d5ef4dc25552", + "agentId": "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" + }, + "admissions": [ + { + "kind": "queued_input", + "messageId": "mmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmm" + }, + { + "kind": "queued_input", + "messageId": "00xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx", + "event": { + "sessionId": "source", + "eventId": "33333333-3333-4333-8333-333333333333", + "agentId": "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa", + "eventType": "user.message", + "provenance": "native" + } + }, + { + "kind": "queued_input", + "messageId": "01xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx", + "event": { + "sessionId": "source", + "eventId": "33333333-3333-4333-8333-333333333333", + "agentId": "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa", + "eventType": "user.message", + "provenance": "native" + } + }, + { + "kind": "queued_input", + "messageId": "02xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx", + "event": { + "sessionId": "source", + "eventId": "33333333-3333-4333-8333-333333333333", + "agentId": "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa", + "eventType": "user.message", + "provenance": "native" + } + }, + { + "kind": "queued_input", + "messageId": "03xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx", + "event": { + "sessionId": "source", + "eventId": "33333333-3333-4333-8333-333333333333", + "agentId": "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa", + "eventType": "user.message", + "provenance": "native" + } + } + ], + "captureComplete": true + } + ], + "captureComplete": true + } + }, + "agentId": "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa", + "id": "22222222-2222-4222-8222-222222222222", + "timestamp": "2026-09-26T00:10:18.598Z", + "parentId": null + }, + "placementValid": false, + "wireBytes": 3540, + "expandedBytes": 4188 } ], "invalid": [ @@ -731,6 +833,254 @@ "observationProvenance": "native", "captureComplete": false } + }, + { + "name": "session_id_257_bytes", + "value": { + "version": 1, + "observationProvenance": "native", + "sources": [ + { + "input": { + "queueItemId": "2dbbe116-73b0-4011-bfa3-3b571b41404b", + "agentId": "recipient", + "sender": { + "sessionId": "sssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssss", + "eventId": "773e548e-64e5-4418-853b-aabf128ce858", + "eventType": "tool.execution_start", + "provenance": "native" + }, + "senderBridges": [ + { + "source": { + "sessionId": "0187f098-31d3-4cdc-ab71-e6ab387e01b4", + "eventId": "773e548e-64e5-4418-853b-aabf128ce858", + "eventType": "tool.execution_start", + "provenance": "native" + }, + "reported": { + "sessionId": "23ac4baf-eb8e-4fdc-b8e0-5633e4ed550b", + "eventId": "8f608be4-c132-4e19-8bdd-82ff5ba9b0b8", + "agentId": "leaf-agent", + "eventType": "tool.execution_start", + "provenance": "native" + } + }, + { + "source": { + "sessionId": "23ac4baf-eb8e-4fdc-b8e0-5633e4ed550b", + "eventId": "8f608be4-c132-4e19-8bdd-82ff5ba9b0b8", + "agentId": "leaf-agent", + "eventType": "tool.execution_start", + "provenance": "native" + }, + "reported": { + "sessionId": "ce52204a-54b0-4ebd-ba13-4854598c4399", + "eventId": "69362832-885b-4787-ab3e-1a2f6c17c73b", + "agentId": "leaf-agent", + "eventType": "tool.execution_start", + "provenance": "native" + } + }, + { + "source": { + "sessionId": "23ac4baf-eb8e-4fdc-b8e0-5633e4ed550b", + "eventId": "8f608be4-c132-4e19-8bdd-82ff5ba9b0b8", + "agentId": "leaf-agent", + "eventType": "tool.execution_start", + "provenance": "native" + }, + "reported": { + "sessionId": "f651ee35-bdb9-4c91-aa21-0057a9446269", + "eventId": "b13511dc-0c7a-4d9c-9618-c04c1c50c4bc", + "agentId": "leaf-agent", + "eventType": "tool.execution_start", + "provenance": "native" + } + } + ] + }, + "admissions": [ + { + "kind": "queued_input", + "messageId": "bb61b7c0-bc90-4290-a1e4-79558dc35139" + } + ], + "captureComplete": true + } + ], + "captureComplete": true + } + }, + { + "name": "agent_id_257_bytes", + "value": { + "version": 1, + "observationProvenance": "native", + "sources": [ + { + "input": { + "queueItemId": "46fc3a43-7f19-4b0a-9e32-d5ef4dc25552", + "agentId": "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" + }, + "admissions": [], + "captureComplete": true + } + ], + "captureComplete": true + } + }, + { + "name": "message_id_257_bytes", + "value": { + "version": 1, + "observationProvenance": "native", + "sources": [ + { + "input": { + "queueItemId": "46fc3a43-7f19-4b0a-9e32-d5ef4dc25552", + "agentId": "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" + }, + "admissions": [ + { + "kind": "queued_input", + "messageId": "mmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmm" + }, + { + "kind": "queued_input", + "messageId": "00xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx", + "event": { + "sessionId": "source", + "eventId": "33333333-3333-4333-8333-333333333333", + "agentId": "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa", + "eventType": "user.message", + "provenance": "native" + } + }, + { + "kind": "queued_input", + "messageId": "01xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx", + "event": { + "sessionId": "source", + "eventId": "33333333-3333-4333-8333-333333333333", + "agentId": "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa", + "eventType": "user.message", + "provenance": "native" + } + }, + { + "kind": "queued_input", + "messageId": "02xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx", + "event": { + "sessionId": "source", + "eventId": "33333333-3333-4333-8333-333333333333", + "agentId": "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa", + "eventType": "user.message", + "provenance": "native" + } + }, + { + "kind": "queued_input", + "messageId": "03xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx", + "event": { + "sessionId": "source", + "eventId": "33333333-3333-4333-8333-333333333333", + "agentId": "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa", + "eventType": "user.message", + "provenance": "native" + } + } + ], + "captureComplete": true + } + ], + "captureComplete": true + } + }, + { + "name": "agent_id_257_multibyte", + "value": { + "version": 1, + "observationProvenance": "native", + "sources": [ + { + "input": { + "queueItemId": "46fc3a43-7f19-4b0a-9e32-d5ef4dc25552", + "agentId": "ééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééx" + }, + "admissions": [], + "captureComplete": true + } + ], + "captureComplete": true + } + }, + { + "name": "message_id_257_multibyte", + "value": { + "version": 1, + "observationProvenance": "native", + "sources": [ + { + "input": { + "queueItemId": "46fc3a43-7f19-4b0a-9e32-d5ef4dc25552", + "agentId": "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" + }, + "admissions": [ + { + "kind": "queued_input", + "messageId": "ééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééx" + }, + { + "kind": "queued_input", + "messageId": "00xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx", + "event": { + "sessionId": "source", + "eventId": "33333333-3333-4333-8333-333333333333", + "agentId": "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa", + "eventType": "user.message", + "provenance": "native" + } + }, + { + "kind": "queued_input", + "messageId": "01xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx", + "event": { + "sessionId": "source", + "eventId": "33333333-3333-4333-8333-333333333333", + "agentId": "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa", + "eventType": "user.message", + "provenance": "native" + } + }, + { + "kind": "queued_input", + "messageId": "02xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx", + "event": { + "sessionId": "source", + "eventId": "33333333-3333-4333-8333-333333333333", + "agentId": "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa", + "eventType": "user.message", + "provenance": "native" + } + }, + { + "kind": "queued_input", + "messageId": "03xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx", + "event": { + "sessionId": "source", + "eventId": "33333333-3333-4333-8333-333333333333", + "agentId": "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa", + "eventType": "user.message", + "provenance": "native" + } + } + ], + "captureComplete": true + } + ], + "captureComplete": true + } } ], "boundaries": [ @@ -743,9 +1093,109 @@ { "input": { "queueItemId": "46fc3a43-7f19-4b0a-9e32-d5ef4dc25552", - "agentId": "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" + "agentId": "worker" }, - "admissions": [], + "admissions": [ + { + "kind": "queued_input", + "messageId": "mmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmm", + "event": { + "sessionId": "s", + "eventId": "33333333-3333-4333-8333-333333333333", + "agentId": "worker", + "eventType": "user.message", + "provenance": "native" + } + }, + { + "kind": "queued_input", + "messageId": "mmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmm", + "event": { + "sessionId": "s", + "eventId": "33333333-3333-4333-8333-333333333333", + "agentId": "worker", + "eventType": "user.message", + "provenance": "native" + } + }, + { + "kind": "queued_input", + "messageId": "mmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmm", + "event": { + "sessionId": "s", + "eventId": "33333333-3333-4333-8333-333333333333", + "agentId": "worker", + "eventType": "user.message", + "provenance": "native" + } + }, + { + "kind": "queued_input", + "messageId": "mmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmm", + "event": { + "sessionId": "s", + "eventId": "33333333-3333-4333-8333-333333333333", + "agentId": "worker", + "eventType": "user.message", + "provenance": "native" + } + }, + { + "kind": "queued_input", + "messageId": "mmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmm", + "event": { + "sessionId": "s", + "eventId": "33333333-3333-4333-8333-333333333333", + "agentId": "worker", + "eventType": "user.message", + "provenance": "native" + } + }, + { + "kind": "queued_input", + "messageId": "mmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmm", + "event": { + "sessionId": "s", + "eventId": "33333333-3333-4333-8333-333333333333", + "agentId": "worker", + "eventType": "user.message", + "provenance": "native" + } + }, + { + "kind": "queued_input", + "messageId": "mmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmm", + "event": { + "sessionId": "s", + "eventId": "33333333-3333-4333-8333-333333333333", + "agentId": "worker", + "eventType": "user.message", + "provenance": "native" + } + }, + { + "kind": "queued_input", + "messageId": "mmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmm", + "event": { + "sessionId": "s", + "eventId": "33333333-3333-4333-8333-333333333333", + "agentId": "worker", + "eventType": "user.message", + "provenance": "native" + } + }, + { + "kind": "queued_input", + "messageId": "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx", + "event": { + "sessionId": "s", + "eventId": "33333333-3333-4333-8333-333333333333", + "agentId": "worker", + "eventType": "user.message", + "provenance": "native" + } + } + ], "captureComplete": true } ], @@ -762,9 +1212,109 @@ { "input": { "queueItemId": "46fc3a43-7f19-4b0a-9e32-d5ef4dc25552", - "agentId": "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaax" + "agentId": "worker" }, - "admissions": [], + "admissions": [ + { + "kind": "queued_input", + "messageId": "mmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmm", + "event": { + "sessionId": "s", + "eventId": "33333333-3333-4333-8333-333333333333", + "agentId": "worker", + "eventType": "user.message", + "provenance": "native" + } + }, + { + "kind": "queued_input", + "messageId": "mmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmm", + "event": { + "sessionId": "s", + "eventId": "33333333-3333-4333-8333-333333333333", + "agentId": "worker", + "eventType": "user.message", + "provenance": "native" + } + }, + { + "kind": "queued_input", + "messageId": "mmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmm", + "event": { + "sessionId": "s", + "eventId": "33333333-3333-4333-8333-333333333333", + "agentId": "worker", + "eventType": "user.message", + "provenance": "native" + } + }, + { + "kind": "queued_input", + "messageId": "mmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmm", + "event": { + "sessionId": "s", + "eventId": "33333333-3333-4333-8333-333333333333", + "agentId": "worker", + "eventType": "user.message", + "provenance": "native" + } + }, + { + "kind": "queued_input", + "messageId": "mmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmm", + "event": { + "sessionId": "s", + "eventId": "33333333-3333-4333-8333-333333333333", + "agentId": "worker", + "eventType": "user.message", + "provenance": "native" + } + }, + { + "kind": "queued_input", + "messageId": "mmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmm", + "event": { + "sessionId": "s", + "eventId": "33333333-3333-4333-8333-333333333333", + "agentId": "worker", + "eventType": "user.message", + "provenance": "native" + } + }, + { + "kind": "queued_input", + "messageId": "mmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmm", + "event": { + "sessionId": "s", + "eventId": "33333333-3333-4333-8333-333333333333", + "agentId": "worker", + "eventType": "user.message", + "provenance": "native" + } + }, + { + "kind": "queued_input", + "messageId": "mmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmm", + "event": { + "sessionId": "s", + "eventId": "33333333-3333-4333-8333-333333333333", + "agentId": "worker", + "eventType": "user.message", + "provenance": "native" + } + }, + { + "kind": "queued_input", + "messageId": "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx", + "event": { + "sessionId": "s", + "eventId": "33333333-3333-4333-8333-333333333333", + "agentId": "worker", + "eventType": "user.message", + "provenance": "native" + } + } + ], "captureComplete": true } ], @@ -781,9 +1331,109 @@ { "input": { "queueItemId": "46fc3a43-7f19-4b0a-9e32-d5ef4dc25552", - "agentId": "é🐈\"\\\naaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" + "agentId": "worker" }, - "admissions": [], + "admissions": [ + { + "kind": "queued_input", + "messageId": "mmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmm", + "event": { + "sessionId": "s", + "eventId": "33333333-3333-4333-8333-333333333333", + "agentId": "worker", + "eventType": "user.message", + "provenance": "native" + } + }, + { + "kind": "queued_input", + "messageId": "mmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmm", + "event": { + "sessionId": "s", + "eventId": "33333333-3333-4333-8333-333333333333", + "agentId": "worker", + "eventType": "user.message", + "provenance": "native" + } + }, + { + "kind": "queued_input", + "messageId": "mmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmm", + "event": { + "sessionId": "s", + "eventId": "33333333-3333-4333-8333-333333333333", + "agentId": "worker", + "eventType": "user.message", + "provenance": "native" + } + }, + { + "kind": "queued_input", + "messageId": "mmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmm", + "event": { + "sessionId": "s", + "eventId": "33333333-3333-4333-8333-333333333333", + "agentId": "worker", + "eventType": "user.message", + "provenance": "native" + } + }, + { + "kind": "queued_input", + "messageId": "mmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmm", + "event": { + "sessionId": "s", + "eventId": "33333333-3333-4333-8333-333333333333", + "agentId": "worker", + "eventType": "user.message", + "provenance": "native" + } + }, + { + "kind": "queued_input", + "messageId": "mmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmm", + "event": { + "sessionId": "s", + "eventId": "33333333-3333-4333-8333-333333333333", + "agentId": "worker", + "eventType": "user.message", + "provenance": "native" + } + }, + { + "kind": "queued_input", + "messageId": "mmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmm", + "event": { + "sessionId": "s", + "eventId": "33333333-3333-4333-8333-333333333333", + "agentId": "worker", + "eventType": "user.message", + "provenance": "native" + } + }, + { + "kind": "queued_input", + "messageId": "mmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmm", + "event": { + "sessionId": "s", + "eventId": "33333333-3333-4333-8333-333333333333", + "agentId": "worker", + "eventType": "user.message", + "provenance": "native" + } + }, + { + "kind": "queued_input", + "messageId": "é🐈\"\\\nxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx", + "event": { + "sessionId": "s", + "eventId": "33333333-3333-4333-8333-333333333333", + "agentId": "worker", + "eventType": "user.message", + "provenance": "native" + } + } + ], "captureComplete": true } ], @@ -791,5 +1441,17 @@ }, "accepted": true } - ] + ], + "identityBoundaries": { + "utf8MaxBytes": 256, + "acceptedBytes": 256, + "rejectedBytes": 257, + "multibyteRejectedBytes": 257 + }, + "expandedGraphBudget": { + "maxBytes": 4096, + "wireBytes": 3540, + "expandedBytes": 4188, + "domain": "fully_materialized_workerCausality_field" + } } From cb273a5b03dc4a33517ecfc9ae7308f435568ec2 Mon Sep 17 00:00:00 2001 From: Michael Dinh Date: Sat, 26 Sep 2026 11:53:31 -0400 Subject: [PATCH 15/25] fix: consume final worker causality contract Apply runtime worker-causality seal revision 4. Accept printable non-control UTF-8 identities, enforce closed optional diagnostic shapes, expose canonical workflow completion in all six SDKs, and preserve legacy factory run event compatibility. Runtime-Seal: d636c4a1eaf18af1c08ebd7922c245ab614c0873 Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> --- BUILD.bazel | 2 + docs/features/execution-identity.md | 25 +- dotnet/src/Generated/Rpc.cs | 30 +- dotnet/src/Generated/SessionEvents.cs | 449 ++++---- dotnet/src/WorkerCausalityConverter.cs | 4 +- dotnet/test/Unit/WorkerCausalityTests.cs | 12 + go/factory_session_event_compat.go | 27 + .../diagnosticmetadata/worker_causality.go | 15 +- go/rpc/factory_session_event_compat.go | 64 ++ go/rpc/permission_request_managed_approval.go | 2 +- go/rpc/zrpc.go | 16 + go/rpc/zrpc_encoding.go | 6 + go/rpc/zsession_encoding.go | 274 ++--- go/rpc/zsession_events.go | 699 ++++++------ go/worker_causality_test.go | 24 + go/zsession_events.go | 75 +- java/scripts/codegen/java.ts | 148 ++- .../copilot/generated/SessionEvent.java | 20 +- .../copilot/generated/SystemNotification.java | 41 + .../SystemNotificationAgentCompleted.java | 72 ++ ...ystemNotificationAgentCompletedStatus.java | 35 + .../SystemNotificationAgentIdle.java | 58 + .../generated/SystemNotificationEvent.java | 6 +- ...stemNotificationInstructionDiscovered.java | 58 + .../SystemNotificationNewInboxMessage.java | 58 + .../SystemNotificationShellCompleted.java | 51 + ...temNotificationShellDetachedCompleted.java | 44 + .../SystemNotificationUnclassified.java | 37 + .../SystemNotificationWorkflowCompleted.java | 107 ++ ...emNotificationWorkflowCompletedStatus.java | 41 + .../generated/WorkflowRunSettledEvent.java | 51 + .../generated/WorkflowRunSettledStatus.java | 41 + .../generated/WorkflowRunStartedEvent.java | 45 + .../generated/WorkflowRunUpdatedEvent.java | 43 + .../copilot/WorkerCausalityDeserializer.java | 8 +- .../github/copilot/WorkerCausalityTest.java | 16 + nodejs/src/generated/session-events.ts | 268 +++-- nodejs/src/session.ts | 34 +- nodejs/src/workerCausality.ts | 8 +- nodejs/test/worker-causality.test.ts | 15 + python/copilot/_worker_causality.py | 9 +- python/copilot/generated/session_events.py | 1013 +++++++++-------- python/test_worker_causality.py | 10 + rust/src/generated/session_events.rs | 485 ++++++-- rust/src/worker_causality.rs | 14 +- rust/tests/worker_causality_test.rs | 26 +- scripts/codegen/go.ts | 9 + scripts/codegen/rust.ts | 1 + test/worker-causality.json | 378 +++++- 49 files changed, 3397 insertions(+), 1577 deletions(-) create mode 100644 go/factory_session_event_compat.go create mode 100644 go/rpc/factory_session_event_compat.go create mode 100644 java/sdk/src/generated/java/com/github/copilot/generated/SystemNotification.java create mode 100644 java/sdk/src/generated/java/com/github/copilot/generated/SystemNotificationAgentCompleted.java create mode 100644 java/sdk/src/generated/java/com/github/copilot/generated/SystemNotificationAgentCompletedStatus.java create mode 100644 java/sdk/src/generated/java/com/github/copilot/generated/SystemNotificationAgentIdle.java create mode 100644 java/sdk/src/generated/java/com/github/copilot/generated/SystemNotificationInstructionDiscovered.java create mode 100644 java/sdk/src/generated/java/com/github/copilot/generated/SystemNotificationNewInboxMessage.java create mode 100644 java/sdk/src/generated/java/com/github/copilot/generated/SystemNotificationShellCompleted.java create mode 100644 java/sdk/src/generated/java/com/github/copilot/generated/SystemNotificationShellDetachedCompleted.java create mode 100644 java/sdk/src/generated/java/com/github/copilot/generated/SystemNotificationUnclassified.java create mode 100644 java/sdk/src/generated/java/com/github/copilot/generated/SystemNotificationWorkflowCompleted.java create mode 100644 java/sdk/src/generated/java/com/github/copilot/generated/SystemNotificationWorkflowCompletedStatus.java create mode 100644 java/sdk/src/generated/java/com/github/copilot/generated/WorkflowRunSettledEvent.java create mode 100644 java/sdk/src/generated/java/com/github/copilot/generated/WorkflowRunSettledStatus.java create mode 100644 java/sdk/src/generated/java/com/github/copilot/generated/WorkflowRunStartedEvent.java create mode 100644 java/sdk/src/generated/java/com/github/copilot/generated/WorkflowRunUpdatedEvent.java diff --git a/BUILD.bazel b/BUILD.bazel index f3a51c9a39..57d2ecd2c1 100644 --- a/BUILD.bazel +++ b/BUILD.bazel @@ -1,3 +1,5 @@ +exports_files(["test/worker-causality.json"]) + SHARED_CODEGEN_INPUTS = [ "nodejs/scripts/releaseArtifacts.ts", "nodejs/src/cliVersion.ts", diff --git a/docs/features/execution-identity.md b/docs/features/execution-identity.md index 092e133031..9e4d0e8352 100644 --- a/docs/features/execution-identity.md +++ b/docs/features/execution-identity.md @@ -131,12 +131,25 @@ real tool call. Subsequent worker events retain that worker scope on the root stream, without requiring an exposed native child-session ID. This initial relationship does not establish the cause of a worker follow-up. -At this baseline, `session.tasks.sendMessage` returns no message ID and the internal -queued-message identity is not retained in the later worker `user.message`. -`source` identifies a sender, not its sending Turn. Never attribute all worker -follow-ups to the spawn Turn or the parent's current interaction at delivery. -Recovery with no primary message similarly has no `user.message` admission or -`originatingMessageId`. These cases need an approved causal bridge. +When the independent default-off `RUNTIME_WORKER_CAUSAL_CONTEXT` gate is +enabled, optional version-1 `workerCausality` diagnostics can preserve the +accepted queue input and its actual admission on worker `user.message`, owned +`system.notification`, `assistant.turn_start`, and successful +`session.tasks.sendMessage` results. Typed SDK decoding validates only that the +optional graph is supported, closed-shape and within its limits. Consumers must +still validate the graph against the enclosing placement and must not infer a +Turn, successful submission, draft retention, recovery outcome, browser +surface, physical dispatch or UI policy. `source` identifies a sender, not its +sending Turn. Recovery with no primary message still has no `user.message` +admission or `originatingMessageId`. + +Workflow completion notifications use the canonical public +`system.notification.data.kind.type = "workflow_completed"` discriminator and +typed `runId`, `workflowName`, status, usage and attempt fields. The AHP relay +normalizes legacy host `factory_completed` input before public emission, while +runtime history classification continues to recognize that legacy input. New +schemas and SDK types expose `workflow_completed`; they do not silently rename +the discriminator independently of the producer. Despite its name, `user.message.parentAgentTaskId` reflects the preparing `TurnIdentity`'s task identifier, not a parent interaction ID. Do not equate it diff --git a/dotnet/src/Generated/Rpc.cs b/dotnet/src/Generated/Rpc.cs index 825c9d8e72..e08cfc4149 100644 --- a/dotnet/src/Generated/Rpc.cs +++ b/dotnet/src/Generated/Rpc.cs @@ -43675,15 +43675,15 @@ public static void RegisterClientGlobalApiHandlers(JsonRpc rpc, ClientGlobalApiH [JsonSerializable(typeof(GitHub.Copilot.ExternalToolCompletedEvent), TypeInfoPropertyName = "SessionEventsExternalToolCompletedEvent")] [JsonSerializable(typeof(GitHub.Copilot.ExternalToolRequestedData), TypeInfoPropertyName = "SessionEventsExternalToolRequestedData")] [JsonSerializable(typeof(GitHub.Copilot.ExternalToolRequestedEvent), TypeInfoPropertyName = "SessionEventsExternalToolRequestedEvent")] -[JsonSerializable(typeof(GitHub.Copilot.FactoryPermissionOperation), TypeInfoPropertyName = "SessionEventsFactoryPermissionOperation")] -[JsonSerializable(typeof(GitHub.Copilot.FactoryPermissionPhase), TypeInfoPropertyName = "SessionEventsFactoryPermissionPhase")] -[JsonSerializable(typeof(GitHub.Copilot.FactoryRunSettledData), TypeInfoPropertyName = "SessionEventsFactoryRunSettledData")] -[JsonSerializable(typeof(GitHub.Copilot.FactoryRunSettledEvent), TypeInfoPropertyName = "SessionEventsFactoryRunSettledEvent")] -[JsonSerializable(typeof(GitHub.Copilot.FactoryRunSettledStatus), TypeInfoPropertyName = "SessionEventsFactoryRunSettledStatus")] -[JsonSerializable(typeof(GitHub.Copilot.FactoryRunStartedData), TypeInfoPropertyName = "SessionEventsFactoryRunStartedData")] -[JsonSerializable(typeof(GitHub.Copilot.FactoryRunStartedEvent), TypeInfoPropertyName = "SessionEventsFactoryRunStartedEvent")] -[JsonSerializable(typeof(GitHub.Copilot.FactoryRunUpdatedData), TypeInfoPropertyName = "SessionEventsFactoryRunUpdatedData")] -[JsonSerializable(typeof(GitHub.Copilot.FactoryRunUpdatedEvent), TypeInfoPropertyName = "SessionEventsFactoryRunUpdatedEvent")] +[JsonSerializable(typeof(GitHub.Copilot.WorkflowPermissionOperation), TypeInfoPropertyName = "SessionEventsWorkflowPermissionOperation")] +[JsonSerializable(typeof(GitHub.Copilot.WorkflowPermissionPhase), TypeInfoPropertyName = "SessionEventsWorkflowPermissionPhase")] +[JsonSerializable(typeof(GitHub.Copilot.WorkflowRunSettledData), TypeInfoPropertyName = "SessionEventsWorkflowRunSettledData")] +[JsonSerializable(typeof(GitHub.Copilot.WorkflowRunSettledEvent), TypeInfoPropertyName = "SessionEventsWorkflowRunSettledEvent")] +[JsonSerializable(typeof(GitHub.Copilot.WorkflowRunSettledStatus), TypeInfoPropertyName = "SessionEventsWorkflowRunSettledStatus")] +[JsonSerializable(typeof(GitHub.Copilot.WorkflowRunStartedData), TypeInfoPropertyName = "SessionEventsWorkflowRunStartedData")] +[JsonSerializable(typeof(GitHub.Copilot.WorkflowRunStartedEvent), TypeInfoPropertyName = "SessionEventsWorkflowRunStartedEvent")] +[JsonSerializable(typeof(GitHub.Copilot.WorkflowRunUpdatedData), TypeInfoPropertyName = "SessionEventsWorkflowRunUpdatedData")] +[JsonSerializable(typeof(GitHub.Copilot.WorkflowRunUpdatedEvent), TypeInfoPropertyName = "SessionEventsWorkflowRunUpdatedEvent")] [JsonSerializable(typeof(GitHub.Copilot.FusionAttribution), TypeInfoPropertyName = "SessionEventsFusionAttribution")] [JsonSerializable(typeof(GitHub.Copilot.FusionConversationScope), TypeInfoPropertyName = "SessionEventsFusionConversationScope")] [JsonSerializable(typeof(GitHub.Copilot.FusionFollowUpAction), TypeInfoPropertyName = "SessionEventsFusionFollowUpAction")] @@ -43789,7 +43789,7 @@ public static void RegisterClientGlobalApiHandlers(JsonRpc rpc, ClientGlobalApiH [JsonSerializable(typeof(GitHub.Copilot.PermissionPromptRequestExtensionEnvAccess), TypeInfoPropertyName = "SessionEventsPermissionPromptRequestExtensionEnvAccess")] [JsonSerializable(typeof(GitHub.Copilot.PermissionPromptRequestExtensionManagement), TypeInfoPropertyName = "SessionEventsPermissionPromptRequestExtensionManagement")] [JsonSerializable(typeof(GitHub.Copilot.PermissionPromptRequestExtensionPermissionAccess), TypeInfoPropertyName = "SessionEventsPermissionPromptRequestExtensionPermissionAccess")] -[JsonSerializable(typeof(GitHub.Copilot.PermissionPromptRequestFactory), TypeInfoPropertyName = "SessionEventsPermissionPromptRequestFactory")] +[JsonSerializable(typeof(GitHub.Copilot.PermissionPromptRequestWorkflow), TypeInfoPropertyName = "SessionEventsPermissionPromptRequestWorkflow")] [JsonSerializable(typeof(GitHub.Copilot.PermissionPromptRequestHook), TypeInfoPropertyName = "SessionEventsPermissionPromptRequestHook")] [JsonSerializable(typeof(GitHub.Copilot.PermissionPromptRequestMcp), TypeInfoPropertyName = "SessionEventsPermissionPromptRequestMcp")] [JsonSerializable(typeof(GitHub.Copilot.PermissionPromptRequestMemory), TypeInfoPropertyName = "SessionEventsPermissionPromptRequestMemory")] @@ -43812,7 +43812,7 @@ public static void RegisterClientGlobalApiHandlers(JsonRpc rpc, ClientGlobalApiH [JsonSerializable(typeof(GitHub.Copilot.PermissionRequestExtensionEnvAccess), TypeInfoPropertyName = "SessionEventsPermissionRequestExtensionEnvAccess")] [JsonSerializable(typeof(GitHub.Copilot.PermissionRequestExtensionManagement), TypeInfoPropertyName = "SessionEventsPermissionRequestExtensionManagement")] [JsonSerializable(typeof(GitHub.Copilot.PermissionRequestExtensionPermissionAccess), TypeInfoPropertyName = "SessionEventsPermissionRequestExtensionPermissionAccess")] -[JsonSerializable(typeof(GitHub.Copilot.PermissionRequestFactory), TypeInfoPropertyName = "SessionEventsPermissionRequestFactory")] +[JsonSerializable(typeof(GitHub.Copilot.PermissionRequestWorkflow), TypeInfoPropertyName = "SessionEventsPermissionRequestWorkflow")] [JsonSerializable(typeof(GitHub.Copilot.PermissionRequestHook), TypeInfoPropertyName = "SessionEventsPermissionRequestHook")] [JsonSerializable(typeof(GitHub.Copilot.PermissionRequestMcp), TypeInfoPropertyName = "SessionEventsPermissionRequestMcp")] [JsonSerializable(typeof(GitHub.Copilot.PermissionRequestMemory), TypeInfoPropertyName = "SessionEventsPermissionRequestMemory")] @@ -43913,9 +43913,9 @@ public static void RegisterClientGlobalApiHandlers(JsonRpc rpc, ClientGlobalApiH [JsonSerializable(typeof(GitHub.Copilot.SystemNotificationAgentIdle), TypeInfoPropertyName = "SessionEventsSystemNotificationAgentIdle")] [JsonSerializable(typeof(GitHub.Copilot.SystemNotificationData), TypeInfoPropertyName = "SessionEventsSystemNotificationData")] [JsonSerializable(typeof(GitHub.Copilot.SystemNotificationEvent), TypeInfoPropertyName = "SessionEventsSystemNotificationEvent")] -[JsonSerializable(typeof(GitHub.Copilot.SystemNotificationFactoryCompleted), TypeInfoPropertyName = "SessionEventsSystemNotificationFactoryCompleted")] -[JsonSerializable(typeof(GitHub.Copilot.SystemNotificationFactoryCompletedStatus), TypeInfoPropertyName = "SessionEventsSystemNotificationFactoryCompletedStatus")] -[JsonSerializable(typeof(GitHub.Copilot.SystemNotificationFactoryPauseInfo), TypeInfoPropertyName = "SessionEventsSystemNotificationFactoryPauseInfo")] +[JsonSerializable(typeof(GitHub.Copilot.SystemNotificationWorkflowCompleted), TypeInfoPropertyName = "SessionEventsSystemNotificationWorkflowCompleted")] +[JsonSerializable(typeof(GitHub.Copilot.SystemNotificationWorkflowCompletedStatus), TypeInfoPropertyName = "SessionEventsSystemNotificationWorkflowCompletedStatus")] +[JsonSerializable(typeof(GitHub.Copilot.SystemNotificationWorkflowPauseInfo), TypeInfoPropertyName = "SessionEventsSystemNotificationWorkflowPauseInfo")] [JsonSerializable(typeof(GitHub.Copilot.SystemNotificationInstructionDiscovered), TypeInfoPropertyName = "SessionEventsSystemNotificationInstructionDiscovered")] [JsonSerializable(typeof(GitHub.Copilot.SystemNotificationNewInboxMessage), TypeInfoPropertyName = "SessionEventsSystemNotificationNewInboxMessage")] [JsonSerializable(typeof(GitHub.Copilot.SystemNotificationShellCompleted), TypeInfoPropertyName = "SessionEventsSystemNotificationShellCompleted")] @@ -43982,7 +43982,7 @@ public static void RegisterClientGlobalApiHandlers(JsonRpc rpc, ClientGlobalApiH [JsonSerializable(typeof(GitHub.Copilot.UserToolSessionApprovalExtensionEnvAccess), TypeInfoPropertyName = "SessionEventsUserToolSessionApprovalExtensionEnvAccess")] [JsonSerializable(typeof(GitHub.Copilot.UserToolSessionApprovalExtensionManagement), TypeInfoPropertyName = "SessionEventsUserToolSessionApprovalExtensionManagement")] [JsonSerializable(typeof(GitHub.Copilot.UserToolSessionApprovalExtensionPermissionAccess), TypeInfoPropertyName = "SessionEventsUserToolSessionApprovalExtensionPermissionAccess")] -[JsonSerializable(typeof(GitHub.Copilot.UserToolSessionApprovalFactory), TypeInfoPropertyName = "SessionEventsUserToolSessionApprovalFactory")] +[JsonSerializable(typeof(GitHub.Copilot.UserToolSessionApprovalWorkflow), TypeInfoPropertyName = "SessionEventsUserToolSessionApprovalWorkflow")] [JsonSerializable(typeof(GitHub.Copilot.UserToolSessionApprovalMcp), TypeInfoPropertyName = "SessionEventsUserToolSessionApprovalMcp")] [JsonSerializable(typeof(GitHub.Copilot.UserToolSessionApprovalMemory), TypeInfoPropertyName = "SessionEventsUserToolSessionApprovalMemory")] [JsonSerializable(typeof(GitHub.Copilot.UserToolSessionApprovalRead), TypeInfoPropertyName = "SessionEventsUserToolSessionApprovalRead")] diff --git a/dotnet/src/Generated/SessionEvents.cs b/dotnet/src/Generated/SessionEvents.cs index 993c84bce8..9cde888043 100644 --- a/dotnet/src/Generated/SessionEvents.cs +++ b/dotnet/src/Generated/SessionEvents.cs @@ -57,9 +57,6 @@ namespace GitHub.Copilot; [JsonDerivedType(typeof(ExitPlanModeRequestedEvent), "exit_plan_mode.requested")] [JsonDerivedType(typeof(ExternalToolCompletedEvent), "external_tool.completed")] [JsonDerivedType(typeof(ExternalToolRequestedEvent), "external_tool.requested")] -[JsonDerivedType(typeof(FactoryRunSettledEvent), "factory.run_settled")] -[JsonDerivedType(typeof(FactoryRunStartedEvent), "factory.run_started")] -[JsonDerivedType(typeof(FactoryRunUpdatedEvent), "factory.run_updated")] [JsonDerivedType(typeof(HookEndEvent), "hook.end")] [JsonDerivedType(typeof(HookProgressEvent), "hook.progress")] [JsonDerivedType(typeof(HookStartEvent), "hook.start")] @@ -173,6 +170,9 @@ namespace GitHub.Copilot; [JsonDerivedType(typeof(UserInputCompletedEvent), "user_input.completed")] [JsonDerivedType(typeof(UserInputRequestedEvent), "user_input.requested")] [JsonDerivedType(typeof(UserMessageEvent), "user.message")] +[JsonDerivedType(typeof(WorkflowRunSettledEvent), "workflow.run_settled")] +[JsonDerivedType(typeof(WorkflowRunStartedEvent), "workflow.run_started")] +[JsonDerivedType(typeof(WorkflowRunUpdatedEvent), "workflow.run_updated")] public partial class SessionEvent { /// Sub-agent instance identifier. Absent for events from the root/main agent and session-level events. @@ -1426,7 +1426,7 @@ public sealed partial class PermissionCompletedEvent : SessionEvent public required PermissionCompletedData Data { get; set; } } -/// Records that a live authorization record from an earlier human decision in this session contained a permission proposal, so it ran without another prompt. This mints no authority: it accounts for one more effect against the prior grant, which is what lets a replayed session agree with the live one about how much of that grant is left. +/// Historical decode-only receipt from the retired Assisted Permissions authorization extractor. Current runtimes ignore it for permission decisions. /// Represents the permission.carriedForward event. [Experimental(Diagnostics.Experimental)] public sealed partial class PermissionCarriedForwardEvent : SessionEvent @@ -1440,7 +1440,7 @@ public sealed partial class PermissionCarriedForwardEvent : SessionEvent public required PermissionCarriedForwardData Data { get; set; } } -/// Freezes one blinded, verbatim-verified authorization claim the runtime minted from a human user message, so a resumed session re-establishes the same grant deterministically instead of re-running the extraction model. This mints no authority on its own: it records what a blinded proposer pointed at and the trusted discriminator the runtime established, and deterministic establishment runs on replay. Persisted so recorded authority survives compaction and process resume. +/// Historical decode-only claim from the retired Assisted Permissions authorization extractor. Current runtimes preserve the payload but do not establish authority from it. /// Represents the permission.messageAuthorization event. [Experimental(Diagnostics.Experimental)] public sealed partial class PermissionMessageAuthorizationEvent : SessionEvent @@ -1454,7 +1454,7 @@ public sealed partial class PermissionMessageAuthorizationEvent : SessionEvent public required PermissionMessageAuthorizationData Data { get; set; } } -/// Records that one human turn has been read by the blinded authorization proposer, whether or not it minted anything, so a resumed session does not re-run the extraction model on a turn the live session already read. Also records whether that pass activates ongoing extraction; contextual-assent-only passes do not, so unrelated future messages remain outside extraction. +/// Historical decode-only extractor progress marker. Current runtimes do not run or resume extraction from it. /// Represents the permission.messageAuthorizationRead event. [Experimental(Diagnostics.Experimental)] public sealed partial class PermissionMessageAuthorizationReadEvent : SessionEvent @@ -1468,7 +1468,7 @@ public sealed partial class PermissionMessageAuthorizationReadEvent : SessionEve public required PermissionMessageAuthorizationReadData Data { get; set; } } -/// Records that message-backed authorization could not safely represent one human turn before compaction. The runtime may compact the original message after this marker is durable, but message-derived carry-forward and assisted auto-approval remain disabled for the rest of the session so subsequent commands continue through the ordinary permission prompt. +/// Historical decode-only degradation marker from the retired extractor. Current runtimes ignore it for permission decisions. /// Represents the permission.messageAuthorizationDegraded event. [Experimental(Diagnostics.Experimental)] public sealed partial class PermissionMessageAuthorizationDegradedEvent : SessionEvent @@ -1482,7 +1482,7 @@ public sealed partial class PermissionMessageAuthorizationDegradedEvent : Sessio public required PermissionMessageAuthorizationDegradedData Data { get; set; } } -/// Records that deterministic text recognition found likely assent in the human turn immediately following a root Autopilot permission request that was blocked because no interactive response was available. This event grants no authority; its model-facing projection only suggests retrying the unchanged operation. +/// Historical decode-only contextual-assent marker. Current runtimes do not project it into the conversation or permission flow. /// Represents the permission.assentDetected event. [Experimental(Diagnostics.Experimental)] public sealed partial class PermissionAssentDetectedEvent : SessionEvent @@ -1496,7 +1496,7 @@ public sealed partial class PermissionAssentDetectedEvent : SessionEvent public required PermissionAssentDetectedData Data { get; set; } } -/// Freezes a blinded contextual authorization proposal whose verbatim human span was deterministically bound to the immediately preceding blocked permission request. The event carries no action fields; replay re-derives the exact action from the earlier permission request and mints a one-shot message grant only when the binding and span still verify. +/// Historical decode-only contextual authorization claim. Current runtimes preserve the payload but do not establish authority from it. /// Represents the permission.contextualAuthorization event. [Experimental(Diagnostics.Experimental)] public sealed partial class PermissionContextualAuthorizationEvent : SessionEvent @@ -1904,46 +1904,46 @@ public sealed partial class SessionBackgroundTasksChangedEvent : SessionEvent public required SessionBackgroundTasksChangedData Data { get; set; } } -/// Ephemeral invalidation signal for a changed factory run. -/// Represents the factory.run_updated event. +/// Ephemeral invalidation signal for a changed workflow run. +/// Represents the workflow.run_updated event. [Experimental(Diagnostics.Experimental)] -public sealed partial class FactoryRunUpdatedEvent : SessionEvent +public sealed partial class WorkflowRunUpdatedEvent : SessionEvent { /// [JsonIgnore] - public override string Type => "factory.run_updated"; + public override string Type => "workflow.run_updated"; - /// The factory.run_updated event payload. + /// The workflow.run_updated event payload. [JsonPropertyName("data")] - public required FactoryRunUpdatedData Data { get; set; } + public required WorkflowRunUpdatedData Data { get; set; } } -/// Ephemeral signal that a factory run attempt began executing. -/// Represents the factory.run_started event. +/// Ephemeral signal that a workflow run attempt began executing. +/// Represents the workflow.run_started event. [Experimental(Diagnostics.Experimental)] -public sealed partial class FactoryRunStartedEvent : SessionEvent +public sealed partial class WorkflowRunStartedEvent : SessionEvent { /// [JsonIgnore] - public override string Type => "factory.run_started"; + public override string Type => "workflow.run_started"; - /// The factory.run_started event payload. + /// The workflow.run_started event payload. [JsonPropertyName("data")] - public required FactoryRunStartedData Data { get; set; } + public required WorkflowRunStartedData Data { get; set; } } -/// Ephemeral signal that a factory run reached a terminal status. -/// Represents the factory.run_settled event. +/// Ephemeral signal that a workflow run reached a terminal status. +/// Represents the workflow.run_settled event. [Experimental(Diagnostics.Experimental)] -public sealed partial class FactoryRunSettledEvent : SessionEvent +public sealed partial class WorkflowRunSettledEvent : SessionEvent { /// [JsonIgnore] - public override string Type => "factory.run_settled"; + public override string Type => "workflow.run_settled"; - /// The factory.run_settled event payload. + /// The workflow.run_settled event payload. [JsonPropertyName("data")] - public required FactoryRunSettledData Data { get; set; } + public required WorkflowRunSettledData Data { get; set; } } /// Payload of `session.skills_loaded` listing resolved skill metadata. @@ -2844,11 +2844,16 @@ public sealed partial class SessionTruncationData /// Session rewind details including target event and count of removed events. public sealed partial class SessionSnapshotRewindData { + /// The removed events, starting with `upToEventId`. Later events not listed were kept, such as a background agent's events that interleaved with a withdrawn turn. + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] + [JsonPropertyName("eventIds")] + public string[]? EventIds { get; set; } + /// Number of events that were removed by the rewind. [JsonPropertyName("eventsRemoved")] public required long EventsRemoved { get; set; } - /// Event ID that was rewound to; this event and all after it were removed. + /// First removed event. Without `eventIds`, it and every event after it were removed. [JsonPropertyName("upToEventId")] public required string UpToEventId { get; set; } } @@ -3100,12 +3105,18 @@ public sealed partial class SessionCompactionStartData /// Conversation compaction results including success status, metrics, and optional error details. public sealed partial class SessionCompactionCompleteData { - /// Authoritative active-factory reminder appended to the compacted context. + /// Legacy active-workflow reminder retained for replay compatibility. [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] [JsonInclude] [JsonPropertyName("activeFactorySummary")] internal string? ActiveFactorySummary { get; set; } + /// Authoritative active-workflow reminder appended to the compacted context. + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] + [JsonInclude] + [JsonPropertyName("activeWorkflowSummary")] + internal string? ActiveWorkflowSummary { get; set; } + /// Canonical model identifier used for model-specific behavior when replaying compaction. [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] [JsonPropertyName("behaviorModelId")] @@ -3611,7 +3622,7 @@ public sealed partial class UserMessageData [JsonPropertyName("parentAgentTaskId")] public string? ParentAgentTaskId { get; set; } - /// Responses reasoning settings anchored before this model-facing message, for cache-stable history replay. + /// Provider reasoning settings anchored before this model-facing message for cache-stable replay; the historical responsesReasoning name is retained for compatibility. [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] [JsonPropertyName("responsesReasoning")] public ResponsesReasoning? ResponsesReasoning { get; set; } @@ -5250,7 +5261,7 @@ public sealed partial class SubagentStartedData [JsonPropertyName("executionMode")] public string? ExecutionMode { get; set; } - /// Root id of the factory run that spawned this sub-agent, when it was spawned by one. + /// Legacy root id of the workflow run that spawned this sub-agent. New consumers should use workflowRunId. [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] [JsonPropertyName("factoryRunId")] public string? FactoryRunId { get; set; } @@ -5275,7 +5286,7 @@ public sealed partial class SubagentStartedData [JsonPropertyName("resumable")] public bool? Resumable { get; set; } - /// Where the model input for this sub-agent came from. Present when the task planner resolved the launch (the task tool and factory agents); absent for sub-agents created through other runtime paths. + /// Where the model input for this sub-agent came from. Present when the task planner resolved the launch (the task tool and workflow agents); absent for sub-agents created through other runtime paths. [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] [JsonPropertyName("taskModelSource")] public SubagentTaskModelSource? TaskModelSource { get; set; } @@ -5283,6 +5294,11 @@ public sealed partial class SubagentStartedData /// Spawning tool invocation ID, or the canonical sub-agent ID used as a fallback for an API launch without a tool invocation. The fallback is not evidence of a tool call. [JsonPropertyName("toolCallId")] public required string ToolCallId { get; set; } + + /// Root id of the workflow run that spawned this sub-agent, when it was spawned by one. + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] + [JsonPropertyName("workflowRunId")] + public string? WorkflowRunId { get; set; } } /// Resolved runtime configuration for a configured sub-agent. @@ -5625,7 +5641,7 @@ public sealed partial class SystemNotificationData [JsonPropertyName("kind")] public required SystemNotification Kind { get; set; } - /// Responses reasoning settings anchored before this model-facing message, for cache-stable history replay. + /// Provider reasoning settings anchored before this model-facing message for cache-stable replay; the historical responsesReasoning name is retained for compatibility. [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] [JsonPropertyName("responsesReasoning")] public ResponsesReasoning? ResponsesReasoning { get; set; } @@ -5712,7 +5728,7 @@ public sealed partial class PermissionCompletedData public string? ToolCallId { get; set; } } -/// Records that a live authorization record from an earlier human decision in this session contained a permission proposal, so it ran without another prompt. This mints no authority: it accounts for one more effect against the prior grant, which is what lets a replayed session agree with the live one about how much of that grant is left. +/// Historical decode-only receipt from the retired Assisted Permissions authorization extractor. Current runtimes ignore it for permission decisions. [Experimental(Diagnostics.Experimental)] public sealed partial class PermissionCarriedForwardData { @@ -5737,7 +5753,7 @@ public sealed partial class PermissionCarriedForwardData public required string ToolCallId { get; set; } } -/// Freezes one blinded, verbatim-verified authorization claim the runtime minted from a human user message, so a resumed session re-establishes the same grant deterministically instead of re-running the extraction model. This mints no authority on its own: it records what a blinded proposer pointed at and the trusted discriminator the runtime established, and deterministic establishment runs on replay. Persisted so recorded authority survives compaction and process resume. +/// Historical decode-only claim from the retired Assisted Permissions authorization extractor. Current runtimes preserve the payload but do not establish authority from it. [Experimental(Diagnostics.Experimental)] public sealed partial class PermissionMessageAuthorizationData { @@ -5790,7 +5806,7 @@ public sealed partial class PermissionMessageAuthorizationData public JsonElement? World { get; set; } } -/// Records that one human turn has been read by the blinded authorization proposer, whether or not it minted anything, so a resumed session does not re-run the extraction model on a turn the live session already read. Also records whether that pass activates ongoing extraction; contextual-assent-only passes do not, so unrelated future messages remain outside extraction. +/// Historical decode-only extractor progress marker. Current runtimes do not run or resume extraction from it. [Experimental(Diagnostics.Experimental)] public sealed partial class PermissionMessageAuthorizationReadData { @@ -5806,7 +5822,7 @@ public sealed partial class PermissionMessageAuthorizationReadData public required long TurnIndex { get; set; } } -/// Records that message-backed authorization could not safely represent one human turn before compaction. The runtime may compact the original message after this marker is durable, but message-derived carry-forward and assisted auto-approval remain disabled for the rest of the session so subsequent commands continue through the ordinary permission prompt. +/// Historical decode-only degradation marker from the retired extractor. Current runtimes ignore it for permission decisions. [Experimental(Diagnostics.Experimental)] public sealed partial class PermissionMessageAuthorizationDegradedData { @@ -5816,7 +5832,7 @@ public sealed partial class PermissionMessageAuthorizationDegradedData public required long TurnIndex { get; set; } } -/// Records that deterministic text recognition found likely assent in the human turn immediately following a root Autopilot permission request that was blocked because no interactive response was available. This event grants no authority; its model-facing projection only suggests retrying the unchanged operation. +/// Historical decode-only contextual-assent marker. Current runtimes do not project it into the conversation or permission flow. [Experimental(Diagnostics.Experimental)] public sealed partial class PermissionAssentDetectedData { @@ -5831,7 +5847,7 @@ public sealed partial class PermissionAssentDetectedData public required long TurnIndex { get; set; } } -/// Freezes a blinded contextual authorization proposal whose verbatim human span was deterministically bound to the immediately preceding blocked permission request. The event carries no action fields; replay re-derives the exact action from the earlier permission request and mints a one-shot message grant only when the binding and span still verify. +/// Historical decode-only contextual authorization claim. Current runtimes preserve the payload but do not establish authority from it. [Experimental(Diagnostics.Experimental)] public sealed partial class PermissionContextualAuthorizationData { @@ -6529,39 +6545,39 @@ public sealed partial class SessionBackgroundTasksChangedData { } -/// Ephemeral invalidation signal for a changed factory run. +/// Ephemeral invalidation signal for a changed workflow run. [Experimental(Diagnostics.Experimental)] -public sealed partial class FactoryRunUpdatedData +public sealed partial class WorkflowRunUpdatedData { /// Monotonic revision now available for the run. [JsonPropertyName("revision")] public required long Revision { get; set; } - /// Factory run identifier. + /// Workflow run identifier. [JsonPropertyName("runId")] public required string RunId { get; set; } } -/// Ephemeral signal that a factory run attempt began executing. +/// Ephemeral signal that a workflow run attempt began executing. [Experimental(Diagnostics.Experimental)] -public sealed partial class FactoryRunStartedData +public sealed partial class WorkflowRunStartedData { /// Attempt number this start committed; a resumed run increments it. [JsonPropertyName("attempt")] public required long Attempt { get; set; } - /// Name of the factory this run executes. Low cardinality by construction. - [JsonPropertyName("factoryName")] - public required string FactoryName { get; set; } - - /// Identifier of the factory run that started. + /// Identifier of the workflow run that started. [JsonPropertyName("runId")] public required string RunId { get; set; } + + /// Name of the workflow this run executes. Low cardinality by construction. + [JsonPropertyName("workflowName")] + public required string WorkflowName { get; set; } } -/// Ephemeral signal that a factory run reached a terminal status. +/// Ephemeral signal that a workflow run reached a terminal status. [Experimental(Diagnostics.Experimental)] -public sealed partial class FactoryRunSettledData +public sealed partial class WorkflowRunSettledData { /// AI credits this run consumed, in nano-AIU. [JsonPropertyName("consumedNanoAiu")] @@ -6575,18 +6591,18 @@ public sealed partial class FactoryRunSettledData [JsonPropertyName("elapsedMs")] public required long ElapsedMs { get; set; } - /// Typed failure class recorded on the run, when it failed with one (e.g. `factory_limit_reached`). + /// Typed failure class recorded on the run, when it failed with one (e.g. `workflow_limit_reached`). [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] [JsonPropertyName("failureType")] public string? FailureType { get; set; } - /// Identifier of the factory run that settled. + /// Identifier of the workflow run that settled. [JsonPropertyName("runId")] public required string RunId { get; set; } /// Terminal status the run committed. [JsonPropertyName("status")] - public required FactoryRunSettledStatus Status { get; set; } + public required WorkflowRunSettledStatus Status { get; set; } } /// Payload of `session.skills_loaded` listing resolved skill metadata. @@ -7315,7 +7331,7 @@ public sealed partial class CompactionCompleteCompactionTokensUsed public long? OutputTokens { get; set; } } -/// Original request-level and effective conversation reasoning effort for a Responses history boundary. +/// Original request-level and effective conversation reasoning effort for a provider history boundary; the historical type name is retained for compatibility. /// Nested data type for ResponsesReasoning. public sealed partial class ResponsesReasoning { @@ -8472,6 +8488,11 @@ public sealed partial class FusionAttribution [JsonPropertyName("fusionId")] public required string FusionId { get; set; } + /// Whether this model request consumed a user steering message rather than only internal Fusion work. + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] + [JsonPropertyName("hasUserSteering")] + public bool? HasUserSteering { get; set; } + /// HydraFusion orchestration pattern selected for the turn. [JsonPropertyName("pattern")] public required string Pattern { get; set; } @@ -10261,16 +10282,16 @@ public sealed partial class SystemNotificationInstructionDiscovered : SystemNoti public required string TriggerTool { get; set; } } -/// The user variant of . -public sealed partial class SystemNotificationFactoryPauseInfoUser : SystemNotificationFactoryPauseInfo +/// The user variant of . +public sealed partial class SystemNotificationWorkflowPauseInfoUser : SystemNotificationWorkflowPauseInfo { /// [JsonIgnore] public override string Type => "user"; } -/// The checkpoint variant of . -public sealed partial class SystemNotificationFactoryPauseInfoCheckpoint : SystemNotificationFactoryPauseInfo +/// The checkpoint variant of . +public sealed partial class SystemNotificationWorkflowPauseInfoCheckpoint : SystemNotificationWorkflowPauseInfo { /// [JsonIgnore] @@ -10281,14 +10302,14 @@ public sealed partial class SystemNotificationFactoryPauseInfoCheckpoint : Syste public required string Key { get; set; } } -/// Durable metadata describing who initiated a factory pause. +/// Durable metadata describing who initiated a workflow pause. /// Polymorphic base type discriminated by type. [JsonPolymorphic( TypeDiscriminatorPropertyName = "type", UnknownDerivedTypeHandling = JsonUnknownDerivedTypeHandling.FallBackToBaseType)] -[JsonDerivedType(typeof(SystemNotificationFactoryPauseInfoUser), "user")] -[JsonDerivedType(typeof(SystemNotificationFactoryPauseInfoCheckpoint), "checkpoint")] -public partial class SystemNotificationFactoryPauseInfo +[JsonDerivedType(typeof(SystemNotificationWorkflowPauseInfoUser), "user")] +[JsonDerivedType(typeof(SystemNotificationWorkflowPauseInfoCheckpoint), "checkpoint")] +public partial class SystemNotificationWorkflowPauseInfo { /// The type discriminator. [JsonPropertyName("type")] @@ -10296,13 +10317,13 @@ public partial class SystemNotificationFactoryPauseInfo } -/// System notification metadata for a factory execution attempt that reached a terminal state. -/// The factory_completed variant of . -public sealed partial class SystemNotificationFactoryCompleted : SystemNotification +/// System notification metadata for a workflow execution attempt that reached a terminal state. +/// The workflow_completed variant of . +public sealed partial class SystemNotificationWorkflowCompleted : SystemNotification { /// [JsonIgnore] - public override string Type => "factory_completed"; + public override string Type => "workflow_completed"; /// Execution attempt that reached this terminal state. [JsonPropertyName("attempt")] @@ -10320,10 +10341,6 @@ public sealed partial class SystemNotificationFactoryCompleted : SystemNotificat [JsonPropertyName("elapsedMs")] public required long ElapsedMs { get; set; } - /// Persisted factory name. - [JsonPropertyName("factoryName")] - public required string FactoryName { get; set; } - /// Machine-readable terminal failure details, when present. [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] [JsonPropertyName("failure")] @@ -10332,7 +10349,7 @@ public sealed partial class SystemNotificationFactoryCompleted : SystemNotificat /// Pause initiator metadata when this attempt settled as paused. [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] [JsonPropertyName("pauseInfo")] - public SystemNotificationFactoryPauseInfo? PauseInfo { get; set; } + public SystemNotificationWorkflowPauseInfo? PauseInfo { get; set; } /// Bounded prompt-safe preview of the completed result. [UnconditionalSuppressMessage("Trimming", "IL2026", Justification = "Safe for generated string properties: JSON Schema minLength/maxLength map to string length validation, not reflection over trimmed Count members")] @@ -10341,18 +10358,22 @@ public sealed partial class SystemNotificationFactoryCompleted : SystemNotificat [JsonPropertyName("resultPreview")] public string? ResultPreview { get; set; } - /// Actionable run_factory resume guidance for a resource-limit failure. + /// Actionable run_dynamic_workflow resume guidance for a resource-limit failure. [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] [JsonPropertyName("retryGuidance")] public string? RetryGuidance { get; set; } - /// Factory run identifier. + /// Workflow run identifier. [JsonPropertyName("runId")] public required string RunId { get; set; } /// Terminal status reached by this execution attempt. [JsonPropertyName("status")] - public required SystemNotificationFactoryCompletedStatus Status { get; set; } + public required SystemNotificationWorkflowCompletedStatus Status { get; set; } + + /// Persisted workflow name. + [JsonPropertyName("workflowName")] + public required string WorkflowName { get; set; } } /// System notification metadata from an external host that does not match a runtime-owned notification kind. @@ -10380,7 +10401,7 @@ public sealed partial class SystemNotificationUnclassified : SystemNotification [JsonDerivedType(typeof(SystemNotificationShellCompleted), "shell_completed")] [JsonDerivedType(typeof(SystemNotificationShellDetachedCompleted), "shell_detached_completed")] [JsonDerivedType(typeof(SystemNotificationInstructionDiscovered), "instruction_discovered")] -[JsonDerivedType(typeof(SystemNotificationFactoryCompleted), "factory_completed")] +[JsonDerivedType(typeof(SystemNotificationWorkflowCompleted), "workflow_completed")] [JsonDerivedType(typeof(SystemNotificationUnclassified), "unclassified")] public partial class SystemNotification { @@ -10901,9 +10922,9 @@ public sealed partial class PermissionRequestExtensionManagement : PermissionReq public string? ToolCallId { get; set; } } -/// A declared phase shown in a factory permission prompt. -/// Nested data type for FactoryPermissionPhase. -public sealed partial class FactoryPermissionPhase +/// A declared phase shown in a workflow permission prompt. +/// Nested data type for WorkflowPermissionPhase. +public sealed partial class WorkflowPermissionPhase { /// Optional phase detail. [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] @@ -10915,46 +10936,55 @@ public sealed partial class FactoryPermissionPhase public required string Title { get; set; } } -/// Factory run or authoring permission request. -/// The factory variant of . -public sealed partial class PermissionRequestFactory : PermissionRequest +/// Workflow run or authoring permission request. +/// The workflow variant of . +public sealed partial class PermissionRequestWorkflow : PermissionRequest { /// [JsonIgnore] - public override string Kind => "factory"; + public override string Kind => "workflow"; - /// Canonical key used for scoped factory approvals. + /// Canonical key used for scoped workflow approvals. [JsonPropertyName("approvalKey")] public required string ApprovalKey { get; set; } - /// Whether this factory is eligible for persistent approval. + /// Whether this workflow is eligible for persistent approval. [JsonPropertyName("canPersistApproval")] public required bool CanPersistApproval { get; set; } - /// Factory-declared AI-credit limit before any run/resume caller override is applied. + /// Workflow-declared AI-credit limit before any run/resume caller override is applied. [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] [JsonPropertyName("declaredMaxAiCredits")] public double? DeclaredMaxAiCredits { get; set; } - /// Factory-declared concurrent-subagent limit before any run/resume caller override is applied. + /// Workflow-declared concurrent-subagent limit before any run/resume caller override is applied. [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] [JsonPropertyName("declaredMaxConcurrentSubagents")] public long? DeclaredMaxConcurrentSubagents { get; set; } - /// Factory-declared total-subagent limit before any run/resume caller override is applied. + /// Workflow-declared total-subagent limit before any run/resume caller override is applied. [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] [JsonPropertyName("declaredMaxTotalSubagents")] public long? DeclaredMaxTotalSubagents { get; set; } - /// Factory-declared active-time limit in seconds before any run/resume caller override is applied. + /// Workflow-declared active-time limit in seconds before any run/resume caller override is applied. [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] [JsonPropertyName("declaredTimeoutSeconds")] public double? DeclaredTimeoutSeconds { get; set; } - /// Factory description. + /// Workflow description. [JsonPropertyName("description")] public required string Description { get; set; } + /// + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] + [JsonPropertyName("managedApprovalRequired")] + public override bool? ManagedApprovalRequired + { + get => base.ManagedApprovalRequired; + set => base.ManagedApprovalRequired = value; + } + /// Effective AI-credit limit; omitted means unlimited. [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] [JsonPropertyName("maxAiCredits")] @@ -10970,17 +11000,17 @@ public sealed partial class PermissionRequestFactory : PermissionRequest [JsonPropertyName("maxTotalSubagents")] public long? MaxTotalSubagents { get; set; } - /// Factory name. + /// Workflow name. [JsonPropertyName("name")] public required string Name { get; set; } - /// Factory operation, either run or author. + /// Workflow operation, either run or author. [JsonPropertyName("operation")] - public required FactoryPermissionOperation Operation { get; set; } + public required WorkflowPermissionOperation Operation { get; set; } - /// Declared factory phases. + /// Declared workflow phases. [JsonPropertyName("phases")] - public required FactoryPermissionPhase[] Phases { get; set; } + public required WorkflowPermissionPhase[] Phases { get; set; } /// Effective active-time limit in seconds; omitted means unlimited. [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] @@ -11051,7 +11081,7 @@ public sealed partial class PermissionRequestExtensionEnvAccess : PermissionRequ [JsonDerivedType(typeof(PermissionRequestCustomTool), "custom-tool")] [JsonDerivedType(typeof(PermissionRequestHook), "hook")] [JsonDerivedType(typeof(PermissionRequestExtensionManagement), "extension-management")] -[JsonDerivedType(typeof(PermissionRequestFactory), "factory")] +[JsonDerivedType(typeof(PermissionRequestWorkflow), "workflow")] [JsonDerivedType(typeof(PermissionRequestExtensionPermissionAccess), "extension-permission-access")] [JsonDerivedType(typeof(PermissionRequestExtensionEnvAccess), "extension-env-access")] public partial class PermissionRequest @@ -11488,15 +11518,15 @@ public sealed partial class PermissionPromptRequestExtensionManagement : Permiss public string? ToolCallId { get; set; } } -/// Factory run or authoring permission prompt. -/// The factory variant of . -public sealed partial class PermissionPromptRequestFactory : PermissionPromptRequest +/// Workflow run or authoring permission prompt. +/// The workflow variant of . +public sealed partial class PermissionPromptRequestWorkflow : PermissionPromptRequest { /// [JsonIgnore] - public override string Kind => "factory"; + public override string Kind => "workflow"; - /// Canonical key used for scoped factory approvals. + /// Canonical key used for scoped workflow approvals. [JsonPropertyName("approvalKey")] public required string ApprovalKey { get; set; } @@ -11506,31 +11536,31 @@ public sealed partial class PermissionPromptRequestFactory : PermissionPromptReq [JsonPropertyName("assistedApproval")] public PermissionAssistedApproval? AssistedApproval { get; set; } - /// Whether this factory is eligible for persistent approval. + /// Whether this workflow is eligible for persistent approval. [JsonPropertyName("canPersistApproval")] public required bool CanPersistApproval { get; set; } - /// Factory-declared AI-credit limit before any run/resume caller override is applied. + /// Workflow-declared AI-credit limit before any run/resume caller override is applied. [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] [JsonPropertyName("declaredMaxAiCredits")] public double? DeclaredMaxAiCredits { get; set; } - /// Factory-declared concurrent-subagent limit before any run/resume caller override is applied. + /// Workflow-declared concurrent-subagent limit before any run/resume caller override is applied. [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] [JsonPropertyName("declaredMaxConcurrentSubagents")] public long? DeclaredMaxConcurrentSubagents { get; set; } - /// Factory-declared total-subagent limit before any run/resume caller override is applied. + /// Workflow-declared total-subagent limit before any run/resume caller override is applied. [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] [JsonPropertyName("declaredMaxTotalSubagents")] public long? DeclaredMaxTotalSubagents { get; set; } - /// Factory-declared active-time limit in seconds before any run/resume caller override is applied. + /// Workflow-declared active-time limit in seconds before any run/resume caller override is applied. [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] [JsonPropertyName("declaredTimeoutSeconds")] public double? DeclaredTimeoutSeconds { get; set; } - /// Factory description. + /// Workflow description. [JsonPropertyName("description")] public required string Description { get; set; } @@ -11554,17 +11584,17 @@ public sealed partial class PermissionPromptRequestFactory : PermissionPromptReq [JsonPropertyName("maxTotalSubagents")] public long? MaxTotalSubagents { get; set; } - /// Factory name. + /// Workflow name. [JsonPropertyName("name")] public required string Name { get; set; } - /// Factory operation, either run or author. + /// Workflow operation, either run or author. [JsonPropertyName("operation")] - public required FactoryPermissionOperation Operation { get; set; } + public required WorkflowPermissionOperation Operation { get; set; } - /// Declared factory phases. + /// Declared workflow phases. [JsonPropertyName("phases")] - public required FactoryPermissionPhase[] Phases { get; set; } + public required WorkflowPermissionPhase[] Phases { get; set; } /// Effective active-time limit in seconds; omitted means unlimited. [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] @@ -11648,7 +11678,7 @@ public sealed partial class PermissionPromptRequestExtensionEnvAccess : Permissi [JsonDerivedType(typeof(PermissionPromptRequestPath), "path")] [JsonDerivedType(typeof(PermissionPromptRequestHook), "hook")] [JsonDerivedType(typeof(PermissionPromptRequestExtensionManagement), "extension-management")] -[JsonDerivedType(typeof(PermissionPromptRequestFactory), "factory")] +[JsonDerivedType(typeof(PermissionPromptRequestWorkflow), "workflow")] [JsonDerivedType(typeof(PermissionPromptRequestExtensionPermissionAccess), "extension-permission-access")] [JsonDerivedType(typeof(PermissionPromptRequestExtensionEnvAccess), "extension-env-access")] public partial class PermissionPromptRequest @@ -11757,15 +11787,15 @@ public sealed partial class UserToolSessionApprovalExtensionManagement : UserToo public string? Operation { get; set; } } -/// Session-scoped factory approval, optionally narrowed by approval key. -/// The factory variant of . -public sealed partial class UserToolSessionApprovalFactory : UserToolSessionApproval +/// Session-scoped workflow approval, optionally narrowed by approval key. +/// The workflow variant of . +public sealed partial class UserToolSessionApprovalWorkflow : UserToolSessionApproval { /// [JsonIgnore] - public override string Kind => "factory"; + public override string Kind => "workflow"; - /// Optional factory operation name or canonical approval key. + /// Optional workflow operation name or canonical approval key. [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] [JsonPropertyName("approvalKey")] public string? ApprovalKey { get; set; } @@ -11813,7 +11843,7 @@ public sealed partial class UserToolSessionApprovalExtensionEnvAccess : UserTool [JsonDerivedType(typeof(UserToolSessionApprovalMemory), "memory")] [JsonDerivedType(typeof(UserToolSessionApprovalCustomTool), "custom-tool")] [JsonDerivedType(typeof(UserToolSessionApprovalExtensionManagement), "extension-management")] -[JsonDerivedType(typeof(UserToolSessionApprovalFactory), "factory")] +[JsonDerivedType(typeof(UserToolSessionApprovalWorkflow), "workflow")] [JsonDerivedType(typeof(UserToolSessionApprovalExtensionPermissionAccess), "extension-permission-access")] [JsonDerivedType(typeof(UserToolSessionApprovalExtensionEnvAccess), "extension-env-access")] public partial class UserToolSessionApproval @@ -13458,6 +13488,9 @@ public ModelChangeSource(string value) /// An SDK or RPC caller selected the model. public static ModelChangeSource Sdk { get; } = new("sdk"); + /// The user accepted a CAPI-issued Auto tier recommendation. + public static ModelChangeSource AutoTierRecommendation { get; } = new("auto_tier_recommendation"); + /// Returns a value indicating whether two instances are equivalent. public static bool operator ==(ModelChangeSource left, ModelChangeSource right) => left.Equals(right); @@ -18324,51 +18357,51 @@ public override void Write(Utf8JsonWriter writer, SystemNotificationAgentComplet } } -/// Terminal status reached by a factory execution attempt. +/// Terminal status reached by a workflow execution attempt. [JsonConverter(typeof(Converter))] [DebuggerDisplay("{Value,nq}")] -public readonly struct SystemNotificationFactoryCompletedStatus : IEquatable +public readonly struct SystemNotificationWorkflowCompletedStatus : IEquatable { private readonly string? _value; - /// Initializes a new instance of the struct. - /// The value to associate with this . + /// Initializes a new instance of the struct. + /// The value to associate with this . [JsonConstructor] - public SystemNotificationFactoryCompletedStatus(string value) + public SystemNotificationWorkflowCompletedStatus(string value) { ArgumentException.ThrowIfNullOrWhiteSpace(value); _value = value; } - /// Gets the value associated with this . + /// Gets the value associated with this . public string Value => _value ?? string.Empty; - /// The factory completed successfully. - public static SystemNotificationFactoryCompletedStatus Completed { get; } = new("completed"); + /// The workflow completed successfully. + public static SystemNotificationWorkflowCompletedStatus Completed { get; } = new("completed"); - /// The factory was halted. - public static SystemNotificationFactoryCompletedStatus Halted { get; } = new("halted"); + /// The workflow was halted. + public static SystemNotificationWorkflowCompletedStatus Halted { get; } = new("halted"); - /// The factory attempt paused intentionally. - public static SystemNotificationFactoryCompletedStatus Paused { get; } = new("paused"); + /// The workflow attempt paused intentionally. + public static SystemNotificationWorkflowCompletedStatus Paused { get; } = new("paused"); - /// The factory was cancelled. - public static SystemNotificationFactoryCompletedStatus Cancelled { get; } = new("cancelled"); + /// The workflow was cancelled. + public static SystemNotificationWorkflowCompletedStatus Cancelled { get; } = new("cancelled"); - /// The factory failed. - public static SystemNotificationFactoryCompletedStatus Error { get; } = new("error"); + /// The workflow failed. + public static SystemNotificationWorkflowCompletedStatus Error { get; } = new("error"); - /// Returns a value indicating whether two instances are equivalent. - public static bool operator ==(SystemNotificationFactoryCompletedStatus left, SystemNotificationFactoryCompletedStatus right) => left.Equals(right); + /// Returns a value indicating whether two instances are equivalent. + public static bool operator ==(SystemNotificationWorkflowCompletedStatus left, SystemNotificationWorkflowCompletedStatus right) => left.Equals(right); - /// Returns a value indicating whether two instances are not equivalent. - public static bool operator !=(SystemNotificationFactoryCompletedStatus left, SystemNotificationFactoryCompletedStatus right) => !(left == right); + /// Returns a value indicating whether two instances are not equivalent. + public static bool operator !=(SystemNotificationWorkflowCompletedStatus left, SystemNotificationWorkflowCompletedStatus right) => !(left == right); /// - public override bool Equals(object? obj) => obj is SystemNotificationFactoryCompletedStatus other && Equals(other); + public override bool Equals(object? obj) => obj is SystemNotificationWorkflowCompletedStatus other && Equals(other); /// - public bool Equals(SystemNotificationFactoryCompletedStatus other) => string.Equals(Value, other.Value, StringComparison.OrdinalIgnoreCase); + public bool Equals(SystemNotificationWorkflowCompletedStatus other) => string.Equals(Value, other.Value, StringComparison.OrdinalIgnoreCase); /// public override int GetHashCode() => StringComparer.OrdinalIgnoreCase.GetHashCode(Value); @@ -18376,20 +18409,20 @@ public SystemNotificationFactoryCompletedStatus(string value) /// public override string ToString() => Value; - /// Provides a for serializing instances. + /// Provides a for serializing instances. [EditorBrowsable(EditorBrowsableState.Never)] - public sealed class Converter : JsonConverter + public sealed class Converter : JsonConverter { /// - public override SystemNotificationFactoryCompletedStatus Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) + public override SystemNotificationWorkflowCompletedStatus Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) { return new(GeneratedStringEnumJson.ReadValue(ref reader, typeToConvert)); } /// - public override void Write(Utf8JsonWriter writer, SystemNotificationFactoryCompletedStatus value, JsonSerializerOptions options) + public override void Write(Utf8JsonWriter writer, SystemNotificationWorkflowCompletedStatus value, JsonSerializerOptions options) { - GeneratedStringEnumJson.WriteValue(writer, value.Value, typeof(SystemNotificationFactoryCompletedStatus)); + GeneratedStringEnumJson.WriteValue(writer, value.Value, typeof(SystemNotificationWorkflowCompletedStatus)); } } } @@ -19051,42 +19084,42 @@ public override void Write(Utf8JsonWriter writer, PermissionRequestMemoryScope v } } -/// Operation gated by a factory permission request. +/// Operation gated by a workflow permission request. [JsonConverter(typeof(Converter))] [DebuggerDisplay("{Value,nq}")] -public readonly struct FactoryPermissionOperation : IEquatable +public readonly struct WorkflowPermissionOperation : IEquatable { private readonly string? _value; - /// Initializes a new instance of the struct. - /// The value to associate with this . + /// Initializes a new instance of the struct. + /// The value to associate with this . [JsonConstructor] - public FactoryPermissionOperation(string value) + public WorkflowPermissionOperation(string value) { ArgumentException.ThrowIfNullOrWhiteSpace(value); _value = value; } - /// Gets the value associated with this . + /// Gets the value associated with this . public string Value => _value ?? string.Empty; - /// Running a registered factory, which spends subagents, active time, and AI credits under the approved limits. - public static FactoryPermissionOperation Run { get; } = new("run"); + /// Running a registered workflow, which spends subagents, active time, and AI credits under the approved limits. + public static WorkflowPermissionOperation Run { get; } = new("run"); - /// Authoring a factory, which writes JavaScript into a session-scoped extension and loads it. - public static FactoryPermissionOperation Author { get; } = new("author"); + /// Authoring a workflow, which writes JavaScript into a session-scoped extension and loads it. + public static WorkflowPermissionOperation Author { get; } = new("author"); - /// Returns a value indicating whether two instances are equivalent. - public static bool operator ==(FactoryPermissionOperation left, FactoryPermissionOperation right) => left.Equals(right); + /// Returns a value indicating whether two instances are equivalent. + public static bool operator ==(WorkflowPermissionOperation left, WorkflowPermissionOperation right) => left.Equals(right); - /// Returns a value indicating whether two instances are not equivalent. - public static bool operator !=(FactoryPermissionOperation left, FactoryPermissionOperation right) => !(left == right); + /// Returns a value indicating whether two instances are not equivalent. + public static bool operator !=(WorkflowPermissionOperation left, WorkflowPermissionOperation right) => !(left == right); /// - public override bool Equals(object? obj) => obj is FactoryPermissionOperation other && Equals(other); + public override bool Equals(object? obj) => obj is WorkflowPermissionOperation other && Equals(other); /// - public bool Equals(FactoryPermissionOperation other) => string.Equals(Value, other.Value, StringComparison.OrdinalIgnoreCase); + public bool Equals(WorkflowPermissionOperation other) => string.Equals(Value, other.Value, StringComparison.OrdinalIgnoreCase); /// public override int GetHashCode() => StringComparer.OrdinalIgnoreCase.GetHashCode(Value); @@ -19094,20 +19127,20 @@ public FactoryPermissionOperation(string value) /// public override string ToString() => Value; - /// Provides a for serializing instances. + /// Provides a for serializing instances. [EditorBrowsable(EditorBrowsableState.Never)] - public sealed class Converter : JsonConverter + public sealed class Converter : JsonConverter { /// - public override FactoryPermissionOperation Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) + public override WorkflowPermissionOperation Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) { return new(GeneratedStringEnumJson.ReadValue(ref reader, typeToConvert)); } /// - public override void Write(Utf8JsonWriter writer, FactoryPermissionOperation value, JsonSerializerOptions options) + public override void Write(Utf8JsonWriter writer, WorkflowPermissionOperation value, JsonSerializerOptions options) { - GeneratedStringEnumJson.WriteValue(writer, value.Value, typeof(FactoryPermissionOperation)); + GeneratedStringEnumJson.WriteValue(writer, value.Value, typeof(WorkflowPermissionOperation)); } } } @@ -19207,7 +19240,7 @@ public PermissionDecisionSource(string value) /// The host denied the request because no interactive user response was available. public static PermissionDecisionSource UnattendedFallback { get; } = new("unattended_fallback"); - /// A live authorization record from an earlier human decision in this session contained the proposal, so it ran without another prompt. This is not a new human decision and never mints authority of its own. + /// Historical compatibility value for sessions created while authorization carry-forward was executable. Current runtimes do not produce this source. public static PermissionDecisionSource AuthorizationCarryForward { get; } = new("authorization_carry_forward"); /// Returns a value indicating whether two instances are equivalent. @@ -19246,7 +19279,7 @@ public override void Write(Utf8JsonWriter writer, PermissionDecisionSource value } } -/// Which direction a message-backed authorization claim moves authority in. +/// Direction stored in a historical extractor claim. Current runtimes do not apply it. [Experimental(Diagnostics.Experimental)] [JsonConverter(typeof(Converter))] [DebuggerDisplay("{Value,nq}")] @@ -19266,10 +19299,10 @@ public PermissionMessageAuthorizationPolarity(string value) /// Gets the value associated with this . public string Value => _value ?? string.Empty; - /// The human's words authorized an effect. + /// Historical claim recorded as a grant. public static PermissionMessageAuthorizationPolarity Grant { get; } = new("grant"); - /// The human's words refused an effect. + /// Historical claim recorded as a denial. public static PermissionMessageAuthorizationPolarity Denial { get; } = new("denial"); /// Returns a value indicating whether two instances are equivalent. @@ -20229,51 +20262,51 @@ public override void Write(Utf8JsonWriter writer, ExitPlanModeAction value, Json } } -/// Terminal status a factory run committed. A settled run is never `pending` or `running`, so those two members of the run-status domain are deliberately absent. +/// Terminal status a workflow run committed. A settled run is never `pending` or `running`, so those two members of the run-status domain are deliberately absent. [JsonConverter(typeof(Converter))] [DebuggerDisplay("{Value,nq}")] -public readonly struct FactoryRunSettledStatus : IEquatable +public readonly struct WorkflowRunSettledStatus : IEquatable { private readonly string? _value; - /// Initializes a new instance of the struct. - /// The value to associate with this . + /// Initializes a new instance of the struct. + /// The value to associate with this . [JsonConstructor] - public FactoryRunSettledStatus(string value) + public WorkflowRunSettledStatus(string value) { ArgumentException.ThrowIfNullOrWhiteSpace(value); _value = value; } - /// Gets the value associated with this . + /// Gets the value associated with this . public string Value => _value ?? string.Empty; - /// The factory body resolved and its result was committed. - public static FactoryRunSettledStatus Completed { get; } = new("completed"); + /// The workflow body resolved and its result was committed. + public static WorkflowRunSettledStatus Completed { get; } = new("completed"); /// The run was stopped by a limit, an approval refusal or another policy decision. - public static FactoryRunSettledStatus Halted { get; } = new("halted"); + public static WorkflowRunSettledStatus Halted { get; } = new("halted"); /// The attempt paused intentionally while preserving resumable run state. - public static FactoryRunSettledStatus Paused { get; } = new("paused"); + public static WorkflowRunSettledStatus Paused { get; } = new("paused"); /// The run was cancelled by its caller or by session disposal. - public static FactoryRunSettledStatus Cancelled { get; } = new("cancelled"); + public static WorkflowRunSettledStatus Cancelled { get; } = new("cancelled"); /// The run failed, with `failureType` carrying the class when it has one. - public static FactoryRunSettledStatus Error { get; } = new("error"); + public static WorkflowRunSettledStatus Error { get; } = new("error"); - /// Returns a value indicating whether two instances are equivalent. - public static bool operator ==(FactoryRunSettledStatus left, FactoryRunSettledStatus right) => left.Equals(right); + /// Returns a value indicating whether two instances are equivalent. + public static bool operator ==(WorkflowRunSettledStatus left, WorkflowRunSettledStatus right) => left.Equals(right); - /// Returns a value indicating whether two instances are not equivalent. - public static bool operator !=(FactoryRunSettledStatus left, FactoryRunSettledStatus right) => !(left == right); + /// Returns a value indicating whether two instances are not equivalent. + public static bool operator !=(WorkflowRunSettledStatus left, WorkflowRunSettledStatus right) => !(left == right); /// - public override bool Equals(object? obj) => obj is FactoryRunSettledStatus other && Equals(other); + public override bool Equals(object? obj) => obj is WorkflowRunSettledStatus other && Equals(other); /// - public bool Equals(FactoryRunSettledStatus other) => string.Equals(Value, other.Value, StringComparison.OrdinalIgnoreCase); + public bool Equals(WorkflowRunSettledStatus other) => string.Equals(Value, other.Value, StringComparison.OrdinalIgnoreCase); /// public override int GetHashCode() => StringComparer.OrdinalIgnoreCase.GetHashCode(Value); @@ -20281,20 +20314,20 @@ public FactoryRunSettledStatus(string value) /// public override string ToString() => Value; - /// Provides a for serializing instances. + /// Provides a for serializing instances. [EditorBrowsable(EditorBrowsableState.Never)] - public sealed class Converter : JsonConverter + public sealed class Converter : JsonConverter { /// - public override FactoryRunSettledStatus Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) + public override WorkflowRunSettledStatus Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) { return new(GeneratedStringEnumJson.ReadValue(ref reader, typeToConvert)); } /// - public override void Write(Utf8JsonWriter writer, FactoryRunSettledStatus value, JsonSerializerOptions options) + public override void Write(Utf8JsonWriter writer, WorkflowRunSettledStatus value, JsonSerializerOptions options) { - GeneratedStringEnumJson.WriteValue(writer, value.Value, typeof(FactoryRunSettledStatus)); + GeneratedStringEnumJson.WriteValue(writer, value.Value, typeof(WorkflowRunSettledStatus)); } } } @@ -20773,13 +20806,6 @@ public override void Write(Utf8JsonWriter writer, ExtensionsLoadedExtensionStatu [JsonSerializable(typeof(ExternalToolCompletedEvent))] [JsonSerializable(typeof(ExternalToolRequestedData))] [JsonSerializable(typeof(ExternalToolRequestedEvent))] -[JsonSerializable(typeof(FactoryPermissionPhase))] -[JsonSerializable(typeof(FactoryRunSettledData))] -[JsonSerializable(typeof(FactoryRunSettledEvent))] -[JsonSerializable(typeof(FactoryRunStartedData))] -[JsonSerializable(typeof(FactoryRunStartedEvent))] -[JsonSerializable(typeof(FactoryRunUpdatedData))] -[JsonSerializable(typeof(FactoryRunUpdatedEvent))] [JsonSerializable(typeof(FusionAttribution))] [JsonSerializable(typeof(FusionFollowUpRecommendation))] [JsonSerializable(typeof(FusionPhasePlanStep))] @@ -20853,13 +20879,13 @@ public override void Write(Utf8JsonWriter writer, ExtensionsLoadedExtensionStatu [JsonSerializable(typeof(PermissionPromptRequestExtensionEnvAccess))] [JsonSerializable(typeof(PermissionPromptRequestExtensionManagement))] [JsonSerializable(typeof(PermissionPromptRequestExtensionPermissionAccess))] -[JsonSerializable(typeof(PermissionPromptRequestFactory))] [JsonSerializable(typeof(PermissionPromptRequestHook))] [JsonSerializable(typeof(PermissionPromptRequestMcp))] [JsonSerializable(typeof(PermissionPromptRequestMemory))] [JsonSerializable(typeof(PermissionPromptRequestPath))] [JsonSerializable(typeof(PermissionPromptRequestRead))] [JsonSerializable(typeof(PermissionPromptRequestUrl))] +[JsonSerializable(typeof(PermissionPromptRequestWorkflow))] [JsonSerializable(typeof(PermissionPromptRequestWrite))] [JsonSerializable(typeof(PermissionRecoveryAttempt))] [JsonSerializable(typeof(PermissionRecoveryData))] @@ -20868,7 +20894,6 @@ public override void Write(Utf8JsonWriter writer, ExtensionsLoadedExtensionStatu [JsonSerializable(typeof(PermissionRequestExtensionEnvAccess))] [JsonSerializable(typeof(PermissionRequestExtensionManagement))] [JsonSerializable(typeof(PermissionRequestExtensionPermissionAccess))] -[JsonSerializable(typeof(PermissionRequestFactory))] [JsonSerializable(typeof(PermissionRequestHook))] [JsonSerializable(typeof(PermissionRequestMcp))] [JsonSerializable(typeof(PermissionRequestMemory))] @@ -20878,6 +20903,7 @@ public override void Write(Utf8JsonWriter writer, ExtensionsLoadedExtensionStatu [JsonSerializable(typeof(PermissionRequestShellCommandSegment))] [JsonSerializable(typeof(PermissionRequestShellPossibleUrl))] [JsonSerializable(typeof(PermissionRequestUrl))] +[JsonSerializable(typeof(PermissionRequestWorkflow))] [JsonSerializable(typeof(PermissionRequestWrite))] [JsonSerializable(typeof(PermissionRequestedData))] [JsonSerializable(typeof(PermissionRequestedEvent))] @@ -21083,15 +21109,15 @@ public override void Write(Utf8JsonWriter writer, ExtensionsLoadedExtensionStatu [JsonSerializable(typeof(SystemNotificationAgentIdle))] [JsonSerializable(typeof(SystemNotificationData))] [JsonSerializable(typeof(SystemNotificationEvent))] -[JsonSerializable(typeof(SystemNotificationFactoryCompleted))] -[JsonSerializable(typeof(SystemNotificationFactoryPauseInfo))] -[JsonSerializable(typeof(SystemNotificationFactoryPauseInfoCheckpoint))] -[JsonSerializable(typeof(SystemNotificationFactoryPauseInfoUser))] [JsonSerializable(typeof(SystemNotificationInstructionDiscovered))] [JsonSerializable(typeof(SystemNotificationNewInboxMessage))] [JsonSerializable(typeof(SystemNotificationShellCompleted))] [JsonSerializable(typeof(SystemNotificationShellDetachedCompleted))] [JsonSerializable(typeof(SystemNotificationUnclassified))] +[JsonSerializable(typeof(SystemNotificationWorkflowCompleted))] +[JsonSerializable(typeof(SystemNotificationWorkflowPauseInfo))] +[JsonSerializable(typeof(SystemNotificationWorkflowPauseInfoCheckpoint))] +[JsonSerializable(typeof(SystemNotificationWorkflowPauseInfoUser))] [JsonSerializable(typeof(TaskBlocker))] [JsonSerializable(typeof(ToolExecutionCompleteContent))] [JsonSerializable(typeof(ToolExecutionCompleteContentAudio))] @@ -21149,10 +21175,10 @@ public override void Write(Utf8JsonWriter writer, ExtensionsLoadedExtensionStatu [JsonSerializable(typeof(UserToolSessionApprovalExtensionEnvAccess))] [JsonSerializable(typeof(UserToolSessionApprovalExtensionManagement))] [JsonSerializable(typeof(UserToolSessionApprovalExtensionPermissionAccess))] -[JsonSerializable(typeof(UserToolSessionApprovalFactory))] [JsonSerializable(typeof(UserToolSessionApprovalMcp))] [JsonSerializable(typeof(UserToolSessionApprovalMemory))] [JsonSerializable(typeof(UserToolSessionApprovalRead))] +[JsonSerializable(typeof(UserToolSessionApprovalWorkflow))] [JsonSerializable(typeof(UserToolSessionApprovalWrite))] [JsonSerializable(typeof(WorkerAdmission))] [JsonSerializable(typeof(WorkerBridgeObservation))] @@ -21161,6 +21187,13 @@ public override void Write(Utf8JsonWriter writer, ExtensionsLoadedExtensionStatu [JsonSerializable(typeof(WorkerInput))] [JsonSerializable(typeof(WorkerNotificationReference))] [JsonSerializable(typeof(WorkerSource))] +[JsonSerializable(typeof(WorkflowPermissionPhase))] +[JsonSerializable(typeof(WorkflowRunSettledData))] +[JsonSerializable(typeof(WorkflowRunSettledEvent))] +[JsonSerializable(typeof(WorkflowRunStartedData))] +[JsonSerializable(typeof(WorkflowRunStartedEvent))] +[JsonSerializable(typeof(WorkflowRunUpdatedData))] +[JsonSerializable(typeof(WorkflowRunUpdatedEvent))] [JsonSerializable(typeof(WorkingDirectoryContext))] [JsonSerializable(typeof(JsonElement))] internal sealed partial class SessionEventsJsonContext : JsonSerializerContext; \ No newline at end of file diff --git a/dotnet/src/WorkerCausalityConverter.cs b/dotnet/src/WorkerCausalityConverter.cs index db2f2156db..618e5e3857 100644 --- a/dotnet/src/WorkerCausalityConverter.cs +++ b/dotnet/src/WorkerCausalityConverter.cs @@ -114,7 +114,8 @@ private static bool Text(JsonElement value, string property) => value.TryGetProperty(property, out var text) && text.ValueKind == JsonValueKind.String && text.GetString()!.Length > 0 && - Encoding.UTF8.GetByteCount(text.GetString()!) <= 256; + Encoding.UTF8.GetByteCount(text.GetString()!) <= 256 && + text.GetString()!.All(character => !char.IsControl(character)); private static bool Uuid(JsonElement value, string property) => Text(value, property) && Guid.TryParseExact(value.GetProperty(property).GetString(), "D", out _); @@ -126,7 +127,6 @@ private static bool Reference(JsonElement value, string eventType) => value.ValueKind == JsonValueKind.Object && Fields(value, "sessionId", "eventId", "agentId", "eventType", "provenance") && Text(value, "sessionId") && - value.GetProperty("sessionId").GetString()!.All(c => c is >= 'A' and <= 'Z' or >= 'a' and <= 'z' or >= '0' and <= '9' or '_' or '-') && Uuid(value, "eventId") && (!value.TryGetProperty("agentId", out _) || Text(value, "agentId")) && Text(value, "eventType") && value.GetProperty("eventType").GetString() == eventType && Text(value, "provenance") && value.GetProperty("provenance").GetString() is "native" or "ahp_coordinator"; diff --git a/dotnet/test/Unit/WorkerCausalityTests.cs b/dotnet/test/Unit/WorkerCausalityTests.cs index 00b7e1dc99..ff3d1d2d19 100644 --- a/dotnet/test/Unit/WorkerCausalityTests.cs +++ b/dotnet/test/Unit/WorkerCausalityTests.cs @@ -69,4 +69,16 @@ public void ExactUtf8BudgetAndUnknownFields() Assert.Equal(Payload(wire, true)["content"]!.GetValue(), observed["content"]!.GetValue()); } } + + [Fact] + public void CanonicalWorkflowCompletionDecodesTypedFields() + { + var parsed = SessionEvent.FromJson(Corpus["workflowCompleted"]!["event"]!.ToJsonString()); + var notification = Assert.IsType(parsed); + var completion = Assert.IsType(notification.Data.Kind); + Assert.Equal("fix-ci", completion.WorkflowName); + Assert.Equal("run-1", completion.RunId); + Assert.Equal(SystemNotificationWorkflowCompletedStatus.Completed, completion.Status); + Assert.Equal(1u, completion.ConsumedSubagents); + } } diff --git a/go/factory_session_event_compat.go b/go/factory_session_event_compat.go new file mode 100644 index 0000000000..9a4a2f5fb6 --- /dev/null +++ b/go/factory_session_event_compat.go @@ -0,0 +1,27 @@ +package copilot + +import "github.com/github/copilot-sdk/go/rpc" + +// Deprecated: use WorkflowRunUpdatedData. +type FactoryRunUpdatedData = rpc.FactoryRunUpdatedData + +// Deprecated: use WorkflowRunStartedData. +type FactoryRunStartedData = rpc.FactoryRunStartedData + +// Deprecated: use WorkflowRunSettledData. +type FactoryRunSettledData = rpc.FactoryRunSettledData + +// Deprecated: use WorkflowRunSettledStatus. +type FactoryRunSettledStatus = rpc.FactoryRunSettledStatus + +const ( + FactoryRunSettledStatusCancelled = rpc.FactoryRunSettledStatusCancelled + FactoryRunSettledStatusCompleted = rpc.FactoryRunSettledStatusCompleted + FactoryRunSettledStatusError = rpc.FactoryRunSettledStatusError + FactoryRunSettledStatusHalted = rpc.FactoryRunSettledStatusHalted + FactoryRunSettledStatusPaused = rpc.FactoryRunSettledStatusPaused + + SessionEventTypeFactoryRunSettled = rpc.SessionEventTypeFactoryRunSettled + SessionEventTypeFactoryRunStarted = rpc.SessionEventTypeFactoryRunStarted + SessionEventTypeFactoryRunUpdated = rpc.SessionEventTypeFactoryRunUpdated +) diff --git a/go/internal/diagnosticmetadata/worker_causality.go b/go/internal/diagnosticmetadata/worker_causality.go index 17dbca22ab..0b07035b39 100644 --- a/go/internal/diagnosticmetadata/worker_causality.go +++ b/go/internal/diagnosticmetadata/worker_causality.go @@ -8,14 +8,22 @@ import ( "errors" "log" "regexp" + "unicode" ) var uuidPattern = regexp.MustCompile(`^[0-9a-fA-F]{8}-([0-9a-fA-F]{4}-){3}[0-9a-fA-F]{12}$`) -var sessionPattern = regexp.MustCompile(`^[A-Za-z0-9_-]+$`) func text(value any) bool { s, ok := value.(string) - return ok && s != "" && len(s) <= 256 + if !ok || s == "" || len(s) > 256 { + return false + } + for _, character := range s { + if unicode.IsControl(character) { + return false + } + } + return true } func uuid(value any) bool { @@ -47,10 +55,9 @@ func reference(value any, eventType string) bool { if !ok { return false } - session, ok := ref["sessionId"].(string) agent, hasAgent := ref["agentId"] return fields(ref, "sessionId", "eventId", "agentId", "eventType", "provenance") && - ok && len(session) <= 256 && sessionPattern.MatchString(session) && uuid(ref["eventId"]) && + text(ref["sessionId"]) && uuid(ref["eventId"]) && (!hasAgent || text(agent)) && ref["eventType"] == eventType && (ref["provenance"] == "native" || ref["provenance"] == "ahp_coordinator") } diff --git a/go/rpc/factory_session_event_compat.go b/go/rpc/factory_session_event_compat.go new file mode 100644 index 0000000000..59a3f318a7 --- /dev/null +++ b/go/rpc/factory_session_event_compat.go @@ -0,0 +1,64 @@ +package rpc + +// FactoryRunUpdatedData is the legacy factory-run invalidation event payload. +// +// Use WorkflowRunUpdatedData for current runtime events. +type FactoryRunUpdatedData struct { + Revision int64 `json:"revision"` + RunID string `json:"runId"` +} + +func (*FactoryRunUpdatedData) sessionEventData() {} +func (*FactoryRunUpdatedData) Type() SessionEventType { + return SessionEventTypeFactoryRunUpdated +} + +// FactoryRunStartedData is the legacy factory-run start event payload. +// +// Use WorkflowRunStartedData for current runtime events. +type FactoryRunStartedData struct { + Attempt int64 `json:"attempt"` + FactoryName string `json:"factoryName"` + RunID string `json:"runId"` +} + +func (*FactoryRunStartedData) sessionEventData() {} +func (*FactoryRunStartedData) Type() SessionEventType { + return SessionEventTypeFactoryRunStarted +} + +// FactoryRunSettledData is the legacy factory-run terminal event payload. +// +// Use WorkflowRunSettledData for current runtime events. +type FactoryRunSettledData struct { + ConsumedNanoAiu int64 `json:"consumedNanoAiu"` + ConsumedSubagents int64 `json:"consumedSubagents"` + ElapsedMs int64 `json:"elapsedMs"` + FailureType *string `json:"failureType,omitempty"` + RunID string `json:"runId"` + Status FactoryRunSettledStatus `json:"status"` +} + +func (*FactoryRunSettledData) sessionEventData() {} +func (*FactoryRunSettledData) Type() SessionEventType { + return SessionEventTypeFactoryRunSettled +} + +// FactoryRunSettledStatus is the legacy factory-run terminal status. +// +// Use WorkflowRunSettledStatus for current runtime events. +type FactoryRunSettledStatus string + +const ( + FactoryRunSettledStatusCancelled FactoryRunSettledStatus = "cancelled" + FactoryRunSettledStatusCompleted FactoryRunSettledStatus = "completed" + FactoryRunSettledStatusError FactoryRunSettledStatus = "error" + FactoryRunSettledStatusHalted FactoryRunSettledStatus = "halted" + FactoryRunSettledStatusPaused FactoryRunSettledStatus = "paused" +) + +const ( + SessionEventTypeFactoryRunSettled SessionEventType = "factory.run_settled" + SessionEventTypeFactoryRunStarted SessionEventType = "factory.run_started" + SessionEventTypeFactoryRunUpdated SessionEventType = "factory.run_updated" +) diff --git a/go/rpc/permission_request_managed_approval.go b/go/rpc/permission_request_managed_approval.go index d1bfa37356..6724e2f430 100644 --- a/go/rpc/permission_request_managed_approval.go +++ b/go/rpc/permission_request_managed_approval.go @@ -34,7 +34,7 @@ func (r PermissionRequestExtensionPermissionAccess) RequiresManagedApproval() bo // RequiresManagedApproval reports whether managed policy requires an explicit // human decision for this request. -func (r PermissionRequestFactory) RequiresManagedApproval() bool { +func (r PermissionRequestWorkflow) RequiresManagedApproval() bool { return managedApprovalRequired(r.ManagedApprovalRequired) } diff --git a/go/rpc/zrpc.go b/go/rpc/zrpc.go index 84e1328dcd..6625c3cb43 100644 --- a/go/rpc/zrpc.go +++ b/go/rpc/zrpc.go @@ -17504,6 +17504,19 @@ func (UserToolSessionApprovalRead) Kind() UserToolSessionApprovalKind { return UserToolSessionApprovalKindRead } +// Session-scoped workflow approval, optionally narrowed by approval key. +// Experimental: UserToolSessionApprovalWorkflow is part of an experimental API and may +// change or be removed. +type UserToolSessionApprovalWorkflow struct { + // Optional workflow operation name or canonical approval key + ApprovalKey *string `json:"approvalKey,omitempty"` +} + +func (UserToolSessionApprovalWorkflow) userToolSessionApproval() {} +func (UserToolSessionApprovalWorkflow) Kind() UserToolSessionApprovalKind { + return UserToolSessionApprovalKindWorkflow +} + // Session-scoped tool-approval rule for filesystem write operations. // Experimental: UserToolSessionApprovalWrite is part of an experimental API and may change // or be removed. @@ -21121,6 +21134,8 @@ const ( // The runtime selected the model automatically, such as rate-limit recovery or refusal // fallback. ModelChangeSourceAutomatic ModelChangeSource = "automatic" + // The user accepted a CAPI-issued Auto tier recommendation. + ModelChangeSourceAutoTierRecommendation ModelChangeSource = "auto_tier_recommendation" // The user selected the promoted model from the changeboarding card or its keyboard // shortcut. ModelChangeSourceChangeboardingShortcut ModelChangeSource = "changeboarding_shortcut" @@ -22923,6 +22938,7 @@ const ( UserToolSessionApprovalKindMCP UserToolSessionApprovalKind = "mcp" UserToolSessionApprovalKindMemory UserToolSessionApprovalKind = "memory" UserToolSessionApprovalKindRead UserToolSessionApprovalKind = "read" + UserToolSessionApprovalKindWorkflow UserToolSessionApprovalKind = "workflow" UserToolSessionApprovalKindWrite UserToolSessionApprovalKind = "write" ) diff --git a/go/rpc/zrpc_encoding.go b/go/rpc/zrpc_encoding.go index 5e5b8fac70..36804499bd 100644 --- a/go/rpc/zrpc_encoding.go +++ b/go/rpc/zrpc_encoding.go @@ -4378,6 +4378,12 @@ func unmarshalUserToolSessionApproval(data []byte) (UserToolSessionApproval, err return nil, err } return &d, nil + case UserToolSessionApprovalKindWorkflow: + var d UserToolSessionApprovalWorkflow + if err := json.Unmarshal(data, &d); err != nil { + return nil, err + } + return &d, nil case UserToolSessionApprovalKindWrite: var d UserToolSessionApprovalWrite if err := json.Unmarshal(data, &d); err != nil { diff --git a/go/rpc/zsession_encoding.go b/go/rpc/zsession_encoding.go index f1795c99c6..eea6610613 100644 --- a/go/rpc/zsession_encoding.go +++ b/go/rpc/zsession_encoding.go @@ -930,6 +930,24 @@ func (e *SessionEvent) UnmarshalJSON(data []byte) error { return err } e.Data = &d + case SessionEventTypeWorkflowRunSettled: + var d WorkflowRunSettledData + if err := json.Unmarshal(raw.Data, &d); err != nil { + return err + } + e.Data = &d + case SessionEventTypeWorkflowRunStarted: + var d WorkflowRunStartedData + if err := json.Unmarshal(raw.Data, &d); err != nil { + return err + } + e.Data = &d + case SessionEventTypeWorkflowRunUpdated: + var d WorkflowRunUpdatedData + if err := json.Unmarshal(raw.Data, &d); err != nil { + return err + } + e.Data = &d default: e.Data = &RawSessionEventData{EventType: raw.Type, Raw: raw.Data} } @@ -1611,12 +1629,6 @@ func unmarshalSystemNotification(data []byte) (SystemNotification, error) { return nil, err } return &d, nil - case SystemNotificationTypeFactoryCompleted: - var d SystemNotificationFactoryCompleted - if err := json.Unmarshal(data, &d); err != nil { - return nil, err - } - return &d, nil case SystemNotificationTypeInstructionDiscovered: var d SystemNotificationInstructionDiscovered if err := json.Unmarshal(data, &d); err != nil { @@ -1647,6 +1659,12 @@ func unmarshalSystemNotification(data []byte) (SystemNotification, error) { return nil, err } return &d, nil + case SystemNotificationTypeWorkflowCompleted: + var d SystemNotificationWorkflowCompleted + if err := json.Unmarshal(data, &d); err != nil { + return nil, err + } + return &d, nil default: return &RawSystemNotification{Discriminator: raw.Type, Raw: data}, nil } @@ -1685,12 +1703,67 @@ func (r SystemNotificationAgentIdle) MarshalJSON() ([]byte, error) { }) } -func unmarshalSystemNotificationFactoryPauseInfo(data []byte) (SystemNotificationFactoryPauseInfo, error) { +func (r SystemNotificationInstructionDiscovered) MarshalJSON() ([]byte, error) { + type alias SystemNotificationInstructionDiscovered + return json.Marshal(struct { + Type SystemNotificationType `json:"type"` + alias + }{ + Type: r.Type(), + alias: alias(r), + }) +} + +func (r SystemNotificationNewInboxMessage) MarshalJSON() ([]byte, error) { + type alias SystemNotificationNewInboxMessage + return json.Marshal(struct { + Type SystemNotificationType `json:"type"` + alias + }{ + Type: r.Type(), + alias: alias(r), + }) +} + +func (r SystemNotificationShellCompleted) MarshalJSON() ([]byte, error) { + type alias SystemNotificationShellCompleted + return json.Marshal(struct { + Type SystemNotificationType `json:"type"` + alias + }{ + Type: r.Type(), + alias: alias(r), + }) +} + +func (r SystemNotificationShellDetachedCompleted) MarshalJSON() ([]byte, error) { + type alias SystemNotificationShellDetachedCompleted + return json.Marshal(struct { + Type SystemNotificationType `json:"type"` + alias + }{ + Type: r.Type(), + alias: alias(r), + }) +} + +func (r SystemNotificationUnclassified) MarshalJSON() ([]byte, error) { + type alias SystemNotificationUnclassified + return json.Marshal(struct { + Type SystemNotificationType `json:"type"` + alias + }{ + Type: r.Type(), + alias: alias(r), + }) +} + +func unmarshalSystemNotificationWorkflowPauseInfo(data []byte) (SystemNotificationWorkflowPauseInfo, error) { if string(data) == "null" { return nil, nil } type rawUnion struct { - Type SystemNotificationFactoryPauseInfoType `json:"type"` + Type SystemNotificationWorkflowPauseInfoType `json:"type"` } var raw rawUnion if err := json.Unmarshal(data, &raw); err != nil { @@ -1698,38 +1771,38 @@ func unmarshalSystemNotificationFactoryPauseInfo(data []byte) (SystemNotificatio } switch raw.Type { - case SystemNotificationFactoryPauseInfoTypeCheckpoint: - var d SystemNotificationFactoryPauseInfoCheckpoint + case SystemNotificationWorkflowPauseInfoTypeCheckpoint: + var d SystemNotificationWorkflowPauseInfoCheckpoint if err := json.Unmarshal(data, &d); err != nil { return nil, err } return &d, nil - case SystemNotificationFactoryPauseInfoTypeUser: - var d SystemNotificationFactoryPauseInfoUser + case SystemNotificationWorkflowPauseInfoTypeUser: + var d SystemNotificationWorkflowPauseInfoUser if err := json.Unmarshal(data, &d); err != nil { return nil, err } return &d, nil default: - return &RawSystemNotificationFactoryPauseInfo{Discriminator: raw.Type, Raw: data}, nil + return &RawSystemNotificationWorkflowPauseInfo{Discriminator: raw.Type, Raw: data}, nil } } -func (r RawSystemNotificationFactoryPauseInfo) MarshalJSON() ([]byte, error) { +func (r RawSystemNotificationWorkflowPauseInfo) MarshalJSON() ([]byte, error) { if r.Raw != nil { return r.Raw, nil } return json.Marshal(struct { - Type SystemNotificationFactoryPauseInfoType `json:"type"` + Type SystemNotificationWorkflowPauseInfoType `json:"type"` }{ Type: r.Discriminator, }) } -func (r SystemNotificationFactoryPauseInfoCheckpoint) MarshalJSON() ([]byte, error) { - type alias SystemNotificationFactoryPauseInfoCheckpoint +func (r SystemNotificationWorkflowPauseInfoCheckpoint) MarshalJSON() ([]byte, error) { + type alias SystemNotificationWorkflowPauseInfoCheckpoint return json.Marshal(struct { - Type SystemNotificationFactoryPauseInfoType `json:"type"` + Type SystemNotificationWorkflowPauseInfoType `json:"type"` alias }{ Type: r.Type(), @@ -1737,10 +1810,10 @@ func (r SystemNotificationFactoryPauseInfoCheckpoint) MarshalJSON() ([]byte, err }) } -func (r SystemNotificationFactoryPauseInfoUser) MarshalJSON() ([]byte, error) { - type alias SystemNotificationFactoryPauseInfoUser +func (r SystemNotificationWorkflowPauseInfoUser) MarshalJSON() ([]byte, error) { + type alias SystemNotificationWorkflowPauseInfoUser return json.Marshal(struct { - Type SystemNotificationFactoryPauseInfoType `json:"type"` + Type SystemNotificationWorkflowPauseInfoType `json:"type"` alias }{ Type: r.Type(), @@ -1748,21 +1821,21 @@ func (r SystemNotificationFactoryPauseInfoUser) MarshalJSON() ([]byte, error) { }) } -func (r *SystemNotificationFactoryCompleted) UnmarshalJSON(data []byte) error { - type rawSystemNotificationFactoryCompleted struct { - Attempt int64 `json:"attempt"` - ConsumedNanoAiu int64 `json:"consumedNanoAiu"` - ConsumedSubagents int64 `json:"consumedSubagents"` - ElapsedMs int64 `json:"elapsedMs"` - FactoryName string `json:"factoryName"` - Failure any `json:"failure,omitempty"` - PauseInfo json.RawMessage `json:"pauseInfo,omitempty"` - ResultPreview *string `json:"resultPreview,omitempty"` - RetryGuidance *string `json:"retryGuidance,omitempty"` - RunID string `json:"runId"` - Status SystemNotificationFactoryCompletedStatus `json:"status"` +func (r *SystemNotificationWorkflowCompleted) UnmarshalJSON(data []byte) error { + type rawSystemNotificationWorkflowCompleted struct { + Attempt int64 `json:"attempt"` + ConsumedNanoAiu int64 `json:"consumedNanoAiu"` + ConsumedSubagents int64 `json:"consumedSubagents"` + ElapsedMs int64 `json:"elapsedMs"` + Failure any `json:"failure,omitempty"` + PauseInfo json.RawMessage `json:"pauseInfo,omitempty"` + ResultPreview *string `json:"resultPreview,omitempty"` + RetryGuidance *string `json:"retryGuidance,omitempty"` + RunID string `json:"runId"` + Status SystemNotificationWorkflowCompletedStatus `json:"status"` + WorkflowName string `json:"workflowName"` } - var raw rawSystemNotificationFactoryCompleted + var raw rawSystemNotificationWorkflowCompleted if err := json.Unmarshal(data, &raw); err != nil { return err } @@ -1770,10 +1843,9 @@ func (r *SystemNotificationFactoryCompleted) UnmarshalJSON(data []byte) error { r.ConsumedNanoAiu = raw.ConsumedNanoAiu r.ConsumedSubagents = raw.ConsumedSubagents r.ElapsedMs = raw.ElapsedMs - r.FactoryName = raw.FactoryName r.Failure = raw.Failure if raw.PauseInfo != nil { - value, err := unmarshalSystemNotificationFactoryPauseInfo(raw.PauseInfo) + value, err := unmarshalSystemNotificationWorkflowPauseInfo(raw.PauseInfo) if err != nil { return err } @@ -1783,66 +1855,12 @@ func (r *SystemNotificationFactoryCompleted) UnmarshalJSON(data []byte) error { r.RetryGuidance = raw.RetryGuidance r.RunID = raw.RunID r.Status = raw.Status + r.WorkflowName = raw.WorkflowName return nil } -func (r SystemNotificationFactoryCompleted) MarshalJSON() ([]byte, error) { - type alias SystemNotificationFactoryCompleted - return json.Marshal(struct { - Type SystemNotificationType `json:"type"` - alias - }{ - Type: r.Type(), - alias: alias(r), - }) -} - -func (r SystemNotificationInstructionDiscovered) MarshalJSON() ([]byte, error) { - type alias SystemNotificationInstructionDiscovered - return json.Marshal(struct { - Type SystemNotificationType `json:"type"` - alias - }{ - Type: r.Type(), - alias: alias(r), - }) -} - -func (r SystemNotificationNewInboxMessage) MarshalJSON() ([]byte, error) { - type alias SystemNotificationNewInboxMessage - return json.Marshal(struct { - Type SystemNotificationType `json:"type"` - alias - }{ - Type: r.Type(), - alias: alias(r), - }) -} - -func (r SystemNotificationShellCompleted) MarshalJSON() ([]byte, error) { - type alias SystemNotificationShellCompleted - return json.Marshal(struct { - Type SystemNotificationType `json:"type"` - alias - }{ - Type: r.Type(), - alias: alias(r), - }) -} - -func (r SystemNotificationShellDetachedCompleted) MarshalJSON() ([]byte, error) { - type alias SystemNotificationShellDetachedCompleted - return json.Marshal(struct { - Type SystemNotificationType `json:"type"` - alias - }{ - Type: r.Type(), - alias: alias(r), - }) -} - -func (r SystemNotificationUnclassified) MarshalJSON() ([]byte, error) { - type alias SystemNotificationUnclassified +func (r SystemNotificationWorkflowCompleted) MarshalJSON() ([]byte, error) { + type alias SystemNotificationWorkflowCompleted return json.Marshal(struct { Type SystemNotificationType `json:"type"` alias @@ -1915,12 +1933,6 @@ func unmarshalPermissionRequest(data []byte) (PermissionRequest, error) { return nil, err } return &d, nil - case PermissionRequestKindFactory: - var d PermissionRequestFactory - if err := json.Unmarshal(data, &d); err != nil { - return nil, err - } - return &d, nil case PermissionRequestKindHook: var d PermissionRequestHook if err := json.Unmarshal(data, &d); err != nil { @@ -1957,6 +1969,12 @@ func unmarshalPermissionRequest(data []byte) (PermissionRequest, error) { return nil, err } return &d, nil + case PermissionRequestKindWorkflow: + var d PermissionRequestWorkflow + if err := json.Unmarshal(data, &d); err != nil { + return nil, err + } + return &d, nil case PermissionRequestKindWrite: var d PermissionRequestWrite if err := json.Unmarshal(data, &d); err != nil { @@ -2023,17 +2041,6 @@ func (r PermissionRequestExtensionPermissionAccess) MarshalJSON() ([]byte, error }) } -func (r PermissionRequestFactory) MarshalJSON() ([]byte, error) { - type alias PermissionRequestFactory - return json.Marshal(struct { - Kind PermissionRequestKind `json:"kind"` - alias - }{ - Kind: r.Kind(), - alias: alias(r), - }) -} - func (r PermissionRequestHook) MarshalJSON() ([]byte, error) { type alias PermissionRequestHook return json.Marshal(struct { @@ -2100,6 +2107,17 @@ func (r PermissionRequestURL) MarshalJSON() ([]byte, error) { }) } +func (r PermissionRequestWorkflow) MarshalJSON() ([]byte, error) { + type alias PermissionRequestWorkflow + return json.Marshal(struct { + Kind PermissionRequestKind `json:"kind"` + alias + }{ + Kind: r.Kind(), + alias: alias(r), + }) +} + func (r PermissionRequestWrite) MarshalJSON() ([]byte, error) { type alias PermissionRequestWrite return json.Marshal(struct { @@ -2154,12 +2172,6 @@ func unmarshalPermissionPromptRequest(data []byte) (PermissionPromptRequest, err return nil, err } return &d, nil - case PermissionPromptRequestKindFactory: - var d PermissionPromptRequestFactory - if err := json.Unmarshal(data, &d); err != nil { - return nil, err - } - return &d, nil case PermissionPromptRequestKindHook: var d PermissionPromptRequestHook if err := json.Unmarshal(data, &d); err != nil { @@ -2196,6 +2208,12 @@ func unmarshalPermissionPromptRequest(data []byte) (PermissionPromptRequest, err return nil, err } return &d, nil + case PermissionPromptRequestKindWorkflow: + var d PermissionPromptRequestWorkflow + if err := json.Unmarshal(data, &d); err != nil { + return nil, err + } + return &d, nil case PermissionPromptRequestKindWrite: var d PermissionPromptRequestWrite if err := json.Unmarshal(data, &d); err != nil { @@ -2273,17 +2291,6 @@ func (r PermissionPromptRequestExtensionPermissionAccess) MarshalJSON() ([]byte, }) } -func (r PermissionPromptRequestFactory) MarshalJSON() ([]byte, error) { - type alias PermissionPromptRequestFactory - return json.Marshal(struct { - Kind PermissionPromptRequestKind `json:"kind"` - alias - }{ - Kind: r.Kind(), - alias: alias(r), - }) -} - func (r PermissionPromptRequestHook) MarshalJSON() ([]byte, error) { type alias PermissionPromptRequestHook return json.Marshal(struct { @@ -2350,6 +2357,17 @@ func (r PermissionPromptRequestURL) MarshalJSON() ([]byte, error) { }) } +func (r PermissionPromptRequestWorkflow) MarshalJSON() ([]byte, error) { + type alias PermissionPromptRequestWorkflow + return json.Marshal(struct { + Kind PermissionPromptRequestKind `json:"kind"` + alias + }{ + Kind: r.Kind(), + alias: alias(r), + }) +} + func (r PermissionPromptRequestWrite) MarshalJSON() ([]byte, error) { type alias PermissionPromptRequestWrite return json.Marshal(struct { diff --git a/go/rpc/zsession_events.go b/go/rpc/zsession_events.go index 6ca48c3db0..f15f8ef566 100644 --- a/go/rpc/zsession_events.go +++ b/go/rpc/zsession_events.go @@ -94,30 +94,21 @@ const ( SessionEventTypeExitPlanModeRequested SessionEventType = "exit_plan_mode.requested" SessionEventTypeExternalToolCompleted SessionEventType = "external_tool.completed" SessionEventTypeExternalToolRequested SessionEventType = "external_tool.requested" - // Experimental: SessionEventTypeFactoryRunSettled identifies an experimental event that may - // change or be removed. - SessionEventTypeFactoryRunSettled SessionEventType = "factory.run_settled" - // Experimental: SessionEventTypeFactoryRunStarted identifies an experimental event that may - // change or be removed. - SessionEventTypeFactoryRunStarted SessionEventType = "factory.run_started" - // Experimental: SessionEventTypeFactoryRunUpdated identifies an experimental event that may - // change or be removed. - SessionEventTypeFactoryRunUpdated SessionEventType = "factory.run_updated" - SessionEventTypeHookEnd SessionEventType = "hook.end" - SessionEventTypeHookProgress SessionEventType = "hook.progress" - SessionEventTypeHookStart SessionEventType = "hook.start" - SessionEventTypeMCPAppToolCallComplete SessionEventType = "mcp_app.tool_call_complete" - SessionEventTypeMCPHeadersRefreshCompleted SessionEventType = "mcp.headers_refresh_completed" - SessionEventTypeMCPHeadersRefreshRequired SessionEventType = "mcp.headers_refresh_required" - SessionEventTypeMCPOauthCompleted SessionEventType = "mcp.oauth_completed" - SessionEventTypeMCPOauthRequired SessionEventType = "mcp.oauth_required" - SessionEventTypeMCPPromptsListChanged SessionEventType = "mcp.prompts.list_changed" - SessionEventTypeMCPResourcesListChanged SessionEventType = "mcp.resources.list_changed" - SessionEventTypeMCPToolsListChanged SessionEventType = "mcp.tools.list_changed" - SessionEventTypeModelCallFailure SessionEventType = "model.call_failure" - SessionEventTypeModelCallFinished SessionEventType = "model.call_finished" - SessionEventTypeModelCallStart SessionEventType = "model.call_start" - SessionEventTypePendingMessagesModified SessionEventType = "pending_messages.modified" + SessionEventTypeHookEnd SessionEventType = "hook.end" + SessionEventTypeHookProgress SessionEventType = "hook.progress" + SessionEventTypeHookStart SessionEventType = "hook.start" + SessionEventTypeMCPAppToolCallComplete SessionEventType = "mcp_app.tool_call_complete" + SessionEventTypeMCPHeadersRefreshCompleted SessionEventType = "mcp.headers_refresh_completed" + SessionEventTypeMCPHeadersRefreshRequired SessionEventType = "mcp.headers_refresh_required" + SessionEventTypeMCPOauthCompleted SessionEventType = "mcp.oauth_completed" + SessionEventTypeMCPOauthRequired SessionEventType = "mcp.oauth_required" + SessionEventTypeMCPPromptsListChanged SessionEventType = "mcp.prompts.list_changed" + SessionEventTypeMCPResourcesListChanged SessionEventType = "mcp.resources.list_changed" + SessionEventTypeMCPToolsListChanged SessionEventType = "mcp.tools.list_changed" + SessionEventTypeModelCallFailure SessionEventType = "model.call_failure" + SessionEventTypeModelCallFinished SessionEventType = "model.call_finished" + SessionEventTypeModelCallStart SessionEventType = "model.call_start" + SessionEventTypePendingMessagesModified SessionEventType = "pending_messages.modified" // Experimental: SessionEventTypePermissionAssentDetected identifies an experimental event // that may change or be removed. SessionEventTypePermissionAssentDetected SessionEventType = "permission.assentDetected" @@ -270,6 +261,15 @@ const ( SessionEventTypeUserInputCompleted SessionEventType = "user_input.completed" SessionEventTypeUserInputRequested SessionEventType = "user_input.requested" SessionEventTypeUserMessage SessionEventType = "user.message" + // Experimental: SessionEventTypeWorkflowRunSettled identifies an experimental event that + // may change or be removed. + SessionEventTypeWorkflowRunSettled SessionEventType = "workflow.run_settled" + // Experimental: SessionEventTypeWorkflowRunStarted identifies an experimental event that + // may change or be removed. + SessionEventTypeWorkflowRunStarted SessionEventType = "workflow.run_started" + // Experimental: SessionEventTypeWorkflowRunUpdated identifies an experimental event that + // may change or be removed. + SessionEventTypeWorkflowRunUpdated SessionEventType = "workflow.run_updated" ) // A detected loss of a previously cached prompt prefix @@ -639,9 +639,12 @@ func (*SessionContextClearedData) Type() SessionEventType { // Conversation compaction results including success status, metrics, and optional error details type SessionCompactionCompleteData struct { - // Authoritative active-factory reminder appended to the compacted context + // Legacy active-workflow reminder retained for replay compatibility // Internal: ActiveFactorySummary is part of the SDK's internal API surface and is not intended for external use. ActiveFactorySummary *string `json:"activeFactorySummary,omitempty"` + // Authoritative active-workflow reminder appended to the compacted context + // Internal: ActiveWorkflowSummary is part of the SDK's internal API surface and is not intended for external use. + ActiveWorkflowSummary *string `json:"activeWorkflowSummary,omitempty"` // Canonical model identifier used for model-specific behavior when replaying compaction BehaviorModelID *string `json:"behaviorModelId,omitempty"` // Checkpoint snapshot number created for recovery @@ -912,17 +915,17 @@ func (*SessionManagedSettingsResolvedData) Type() SessionEventType { return SessionEventTypeSessionManagedSettingsResolved } -// Ephemeral invalidation signal for a changed factory run. -// Experimental: FactoryRunUpdatedData is part of an experimental API and may change or be removed. -type FactoryRunUpdatedData struct { +// Ephemeral invalidation signal for a changed workflow run. +// Experimental: WorkflowRunUpdatedData is part of an experimental API and may change or be removed. +type WorkflowRunUpdatedData struct { // Monotonic revision now available for the run. Revision int64 `json:"revision"` - // Factory run identifier. + // Workflow run identifier. RunID string `json:"runId"` } -func (*FactoryRunUpdatedData) sessionEventData() {} -func (*FactoryRunUpdatedData) Type() SessionEventType { return SessionEventTypeFactoryRunUpdated } +func (*WorkflowRunUpdatedData) sessionEventData() {} +func (*WorkflowRunUpdatedData) Type() SessionEventType { return SessionEventTypeWorkflowRunUpdated } // Ephemeral progress update from a running hook process type HookProgressData struct { @@ -935,39 +938,39 @@ type HookProgressData struct { func (*HookProgressData) sessionEventData() {} func (*HookProgressData) Type() SessionEventType { return SessionEventTypeHookProgress } -// Ephemeral signal that a factory run attempt began executing. -// Experimental: FactoryRunStartedData is part of an experimental API and may change or be removed. -type FactoryRunStartedData struct { +// Ephemeral signal that a workflow run attempt began executing. +// Experimental: WorkflowRunStartedData is part of an experimental API and may change or be removed. +type WorkflowRunStartedData struct { // Attempt number this start committed; a resumed run increments it. Attempt int64 `json:"attempt"` - // Name of the factory this run executes. Low cardinality by construction. - FactoryName string `json:"factoryName"` - // Identifier of the factory run that started. + // Identifier of the workflow run that started. RunID string `json:"runId"` + // Name of the workflow this run executes. Low cardinality by construction. + WorkflowName string `json:"workflowName"` } -func (*FactoryRunStartedData) sessionEventData() {} -func (*FactoryRunStartedData) Type() SessionEventType { return SessionEventTypeFactoryRunStarted } +func (*WorkflowRunStartedData) sessionEventData() {} +func (*WorkflowRunStartedData) Type() SessionEventType { return SessionEventTypeWorkflowRunStarted } -// Ephemeral signal that a factory run reached a terminal status. -// Experimental: FactoryRunSettledData is part of an experimental API and may change or be removed. -type FactoryRunSettledData struct { +// Ephemeral signal that a workflow run reached a terminal status. +// Experimental: WorkflowRunSettledData is part of an experimental API and may change or be removed. +type WorkflowRunSettledData struct { // AI credits this run consumed, in nano-AIU. ConsumedNanoAiu int64 `json:"consumedNanoAiu"` // Subagents this run consumed against its limits. ConsumedSubagents int64 `json:"consumedSubagents"` // Active milliseconds accumulated across every attempt of this run. ElapsedMs int64 `json:"elapsedMs"` - // Typed failure class recorded on the run, when it failed with one (e.g. `factory_limit_reached`). + // Typed failure class recorded on the run, when it failed with one (e.g. `workflow_limit_reached`). FailureType *string `json:"failureType,omitempty"` - // Identifier of the factory run that settled. + // Identifier of the workflow run that settled. RunID string `json:"runId"` // Terminal status the run committed. - Status FactoryRunSettledStatus `json:"status"` + Status WorkflowRunSettledStatus `json:"status"` } -func (*FactoryRunSettledData) sessionEventData() {} -func (*FactoryRunSettledData) Type() SessionEventType { return SessionEventTypeFactoryRunSettled } +func (*WorkflowRunSettledData) sessionEventData() {} +func (*WorkflowRunSettledData) Type() SessionEventType { return SessionEventTypeWorkflowRunSettled } // Error details for timeline display including message and optional diagnostic information type SessionErrorData struct { @@ -1398,35 +1401,7 @@ type ModelCallFinishedData struct { func (*ModelCallFinishedData) sessionEventData() {} func (*ModelCallFinishedData) Type() SessionEventType { return SessionEventTypeModelCallFinished } -// Freezes a blinded contextual authorization proposal whose verbatim human span was deterministically bound to the immediately preceding blocked permission request. The event carries no action fields; replay re-derives the exact action from the earlier permission request and mints a one-shot message grant only when the binding and span still verify. -// Experimental: PermissionContextualAuthorizationData is part of an experimental API and may change or be removed. -type PermissionContextualAuthorizationData struct { - // Whether the contextual human span granted or denied authority. - // Experimental: Polarity is part of an experimental API and may change or be removed. - Polarity PermissionMessageAuthorizationPolarity `json:"polarity"` - // Deterministic identity of the contextual message grant. - // Experimental: RecordID is part of an experimental API and may change or be removed. - RecordID string `json:"recordId"` - // Original blocked permission request selected by deterministic event ordering, never by the extraction model. - // Experimental: RequestID is part of an experimental API and may change or be removed. - RequestID string `json:"requestId"` - // End byte offset of the contextual decision span within the turn. - // Experimental: SpanEnd is part of an experimental API and may change or be removed. - SpanEnd int64 `json:"spanEnd"` - // Start byte offset of the contextual decision span within the turn. - // Experimental: SpanStart is part of an experimental API and may change or be removed. - SpanStart int64 `json:"spanStart"` - // Human turn containing the contextual decision. - // Experimental: TurnIndex is part of an experimental API and may change or be removed. - TurnIndex int64 `json:"turnIndex"` -} - -func (*PermissionContextualAuthorizationData) sessionEventData() {} -func (*PermissionContextualAuthorizationData) Type() SessionEventType { - return SessionEventTypePermissionContextualAuthorization -} - -// Freezes one blinded, verbatim-verified authorization claim the runtime minted from a human user message, so a resumed session re-establishes the same grant deterministically instead of re-running the extraction model. This mints no authority on its own: it records what a blinded proposer pointed at and the trusted discriminator the runtime established, and deterministic establishment runs on replay. Persisted so recorded authority survives compaction and process resume. +// Historical decode-only claim from the retired Assisted Permissions authorization extractor. Current runtimes preserve the payload but do not establish authority from it. // Experimental: PermissionMessageAuthorizationData is part of an experimental API and may change or be removed. type PermissionMessageAuthorizationData struct { // The kind of effect authorized, as an action-class identifier. @@ -1463,6 +1438,101 @@ func (*PermissionMessageAuthorizationData) Type() SessionEventType { return SessionEventTypePermissionMessageAuthorization } +// Historical decode-only contextual authorization claim. Current runtimes preserve the payload but do not establish authority from it. +// Experimental: PermissionContextualAuthorizationData is part of an experimental API and may change or be removed. +type PermissionContextualAuthorizationData struct { + // Whether the contextual human span granted or denied authority. + // Experimental: Polarity is part of an experimental API and may change or be removed. + Polarity PermissionMessageAuthorizationPolarity `json:"polarity"` + // Deterministic identity of the contextual message grant. + // Experimental: RecordID is part of an experimental API and may change or be removed. + RecordID string `json:"recordId"` + // Original blocked permission request selected by deterministic event ordering, never by the extraction model. + // Experimental: RequestID is part of an experimental API and may change or be removed. + RequestID string `json:"requestId"` + // End byte offset of the contextual decision span within the turn. + // Experimental: SpanEnd is part of an experimental API and may change or be removed. + SpanEnd int64 `json:"spanEnd"` + // Start byte offset of the contextual decision span within the turn. + // Experimental: SpanStart is part of an experimental API and may change or be removed. + SpanStart int64 `json:"spanStart"` + // Human turn containing the contextual decision. + // Experimental: TurnIndex is part of an experimental API and may change or be removed. + TurnIndex int64 `json:"turnIndex"` +} + +func (*PermissionContextualAuthorizationData) sessionEventData() {} +func (*PermissionContextualAuthorizationData) Type() SessionEventType { + return SessionEventTypePermissionContextualAuthorization +} + +// Historical decode-only contextual-assent marker. Current runtimes do not project it into the conversation or permission flow. +// Experimental: PermissionAssentDetectedData is part of an experimental API and may change or be removed. +type PermissionAssentDetectedData struct { + // Permission request the likely assent may refer to. The runtime derives this from the preceding durable blocker; the human message and extraction model do not choose it. + // Experimental: RequestID is part of an experimental API and may change or be removed. + RequestID string `json:"requestId"` + // Human turn whose text triggered the deterministic assent recognizer. + // Experimental: TurnIndex is part of an experimental API and may change or be removed. + TurnIndex int64 `json:"turnIndex"` +} + +func (*PermissionAssentDetectedData) sessionEventData() {} +func (*PermissionAssentDetectedData) Type() SessionEventType { + return SessionEventTypePermissionAssentDetected +} + +// Historical decode-only degradation marker from the retired extractor. Current runtimes ignore it for permission decisions. +// Experimental: PermissionMessageAuthorizationDegradedData is part of an experimental API and may change or be removed. +type PermissionMessageAuthorizationDegradedData struct { + // The human turn that could not be represented safely. + // Experimental: TurnIndex is part of an experimental API and may change or be removed. + TurnIndex int64 `json:"turnIndex"` +} + +func (*PermissionMessageAuthorizationDegradedData) sessionEventData() {} +func (*PermissionMessageAuthorizationDegradedData) Type() SessionEventType { + return SessionEventTypePermissionMessageAuthorizationDegraded +} + +// Historical decode-only extractor progress marker. Current runtimes do not run or resume extraction from it. +// Experimental: PermissionMessageAuthorizationReadData is part of an experimental API and may change or be removed. +type PermissionMessageAuthorizationReadData struct { + // Whether this read activates ongoing message-backed extraction. False for a contextual-assent-only pass while auto-approval is off, so unrelated future messages remain outside extraction. + // Experimental: ActivatesExtraction is part of an experimental API and may change or be removed. + ActivatesExtraction *bool `json:"activatesExtraction,omitempty"` + // The human turn that was read by the proposer. + // Experimental: TurnIndex is part of an experimental API and may change or be removed. + TurnIndex int64 `json:"turnIndex"` +} + +func (*PermissionMessageAuthorizationReadData) sessionEventData() {} +func (*PermissionMessageAuthorizationReadData) Type() SessionEventType { + return SessionEventTypePermissionMessageAuthorizationRead +} + +// Historical decode-only receipt from the retired Assisted Permissions authorization extractor. Current runtimes ignore it for permission decisions. +// Experimental: PermissionCarriedForwardData is part of an experimental API and may change or be removed. +type PermissionCarriedForwardData struct { + // Always `authorization_carry_forward`. Stated explicitly so a consumer reading this event cannot mistake it for a human, host-policy, or assisted-approval decision. + // Experimental: DecisionSource is part of an experimental API and may change or be removed. + DecisionSource PermissionDecisionSource `json:"decisionSource"` + // Identity of the prior authorization record that contained the proposal. + // Experimental: RecordID is part of an experimental API and may change or be removed. + RecordID string `json:"recordId"` + // Authorization edge minted for this admission. Not a prompt id: no prompt was raised, so no client should expect a request with this id. + // Experimental: RequestID is part of an experimental API and may change or be removed. + RequestID string `json:"requestId"` + // Tool call this admission authorizes. Its execution receipts the prior grant, which is how a single-effect approval is spent rather than carried forward again. + // Experimental: ToolCallID is part of an experimental API and may change or be removed. + ToolCallID string `json:"toolCallId"` +} + +func (*PermissionCarriedForwardData) sessionEventData() {} +func (*PermissionCarriedForwardData) Type() SessionEventType { + return SessionEventTypePermissionCarriedForward +} + // Hook invocation completion details including output, success status, and error information type HookEndData struct { // Error details when the hook failed @@ -2150,7 +2220,7 @@ type UserMessageData struct { NativeDocumentPathFallbackPaths []string `json:"nativeDocumentPathFallbackPaths,omitzero"` // Task ID minted when the runtime prepares this user-message run. This is not a parent interaction ID or worker instance ID and must not be equated with CAPI's X-Parent-Agent-Id. ParentAgentTaskID *string `json:"parentAgentTaskId,omitempty"` - // Responses reasoning settings anchored before this model-facing message, for cache-stable history replay + // Provider reasoning settings anchored before this model-facing message for cache-stable replay; the historical responsesReasoning name is retained for compatibility ResponsesReasoning *ResponsesReasoning `json:"responsesReasoning,omitempty"` // Origin of this message, used for timeline filtering and attribution (e.g., `skill-pdf` for hidden skill injection or `agent-` for an inter-agent prompt) Source *string `json:"source,omitempty"` @@ -2309,28 +2379,6 @@ type CommandQueuedData struct { func (*CommandQueuedData) sessionEventData() {} func (*CommandQueuedData) Type() SessionEventType { return SessionEventTypeCommandQueued } -// Records that a live authorization record from an earlier human decision in this session contained a permission proposal, so it ran without another prompt. This mints no authority: it accounts for one more effect against the prior grant, which is what lets a replayed session agree with the live one about how much of that grant is left. -// Experimental: PermissionCarriedForwardData is part of an experimental API and may change or be removed. -type PermissionCarriedForwardData struct { - // Always `authorization_carry_forward`. Stated explicitly so a consumer reading this event cannot mistake it for a human, host-policy, or assisted-approval decision. - // Experimental: DecisionSource is part of an experimental API and may change or be removed. - DecisionSource PermissionDecisionSource `json:"decisionSource"` - // Identity of the prior authorization record that contained the proposal. - // Experimental: RecordID is part of an experimental API and may change or be removed. - RecordID string `json:"recordId"` - // Authorization edge minted for this admission. Not a prompt id: no prompt was raised, so no client should expect a request with this id. - // Experimental: RequestID is part of an experimental API and may change or be removed. - RequestID string `json:"requestId"` - // Tool call this admission authorizes. Its execution receipts the prior grant, which is how a single-effect approval is spent rather than carried forward again. - // Experimental: ToolCallID is part of an experimental API and may change or be removed. - ToolCallID string `json:"toolCallId"` -} - -func (*PermissionCarriedForwardData) sessionEventData() {} -func (*PermissionCarriedForwardData) Type() SessionEventType { - return SessionEventTypePermissionCarriedForward -} - // Records that a mode transition notice reached the model so cache-stable mode tools can remain offered across resume. type SessionModeNoticeDeliveredData struct { // Model-visible transition notice persisted for a mid-turn delivery @@ -2344,51 +2392,6 @@ func (*SessionModeNoticeDeliveredData) Type() SessionEventType { return SessionEventTypeSessionModeNoticeDelivered } -// Records that deterministic text recognition found likely assent in the human turn immediately following a root Autopilot permission request that was blocked because no interactive response was available. This event grants no authority; its model-facing projection only suggests retrying the unchanged operation. -// Experimental: PermissionAssentDetectedData is part of an experimental API and may change or be removed. -type PermissionAssentDetectedData struct { - // Permission request the likely assent may refer to. The runtime derives this from the preceding durable blocker; the human message and extraction model do not choose it. - // Experimental: RequestID is part of an experimental API and may change or be removed. - RequestID string `json:"requestId"` - // Human turn whose text triggered the deterministic assent recognizer. - // Experimental: TurnIndex is part of an experimental API and may change or be removed. - TurnIndex int64 `json:"turnIndex"` -} - -func (*PermissionAssentDetectedData) sessionEventData() {} -func (*PermissionAssentDetectedData) Type() SessionEventType { - return SessionEventTypePermissionAssentDetected -} - -// Records that message-backed authorization could not safely represent one human turn before compaction. The runtime may compact the original message after this marker is durable, but message-derived carry-forward and assisted auto-approval remain disabled for the rest of the session so subsequent commands continue through the ordinary permission prompt. -// Experimental: PermissionMessageAuthorizationDegradedData is part of an experimental API and may change or be removed. -type PermissionMessageAuthorizationDegradedData struct { - // The human turn that could not be represented safely. - // Experimental: TurnIndex is part of an experimental API and may change or be removed. - TurnIndex int64 `json:"turnIndex"` -} - -func (*PermissionMessageAuthorizationDegradedData) sessionEventData() {} -func (*PermissionMessageAuthorizationDegradedData) Type() SessionEventType { - return SessionEventTypePermissionMessageAuthorizationDegraded -} - -// Records that one human turn has been read by the blinded authorization proposer, whether or not it minted anything, so a resumed session does not re-run the extraction model on a turn the live session already read. Also records whether that pass activates ongoing extraction; contextual-assent-only passes do not, so unrelated future messages remain outside extraction. -// Experimental: PermissionMessageAuthorizationReadData is part of an experimental API and may change or be removed. -type PermissionMessageAuthorizationReadData struct { - // Whether this read activates ongoing message-backed extraction. False for a contextual-assent-only pass while auto-approval is off, so unrelated future messages remain outside extraction. - // Experimental: ActivatesExtraction is part of an experimental API and may change or be removed. - ActivatesExtraction *bool `json:"activatesExtraction,omitempty"` - // The human turn that was read by the proposer. - // Experimental: TurnIndex is part of an experimental API and may change or be removed. - TurnIndex int64 `json:"turnIndex"` -} - -func (*PermissionMessageAuthorizationReadData) sessionEventData() {} -func (*PermissionMessageAuthorizationReadData) Type() SessionEventType { - return SessionEventTypePermissionMessageAuthorizationRead -} - // Registered command dispatch request routed to the owning client type CommandExecuteData struct { // Raw argument string after the command name @@ -2672,9 +2675,11 @@ func (*SessionResumeData) Type() SessionEventType { return SessionEventTypeSessi // Session rewind details including target event and count of removed events type SessionSnapshotRewindData struct { + // The removed events, starting with `upToEventId`. Later events not listed were kept, such as a background agent's events that interleaved with a withdrawn turn + EventIDs []string `json:"eventIds,omitzero"` // Number of events that were removed by the rewind EventsRemoved int64 `json:"eventsRemoved"` - // Event ID that was rewound to; this event and all after it were removed + // First removed event. Without `eventIds`, it and every event after it were removed UpToEventID string `json:"upToEventId"` } @@ -2940,7 +2945,7 @@ type SubagentStartedData struct { AgentType *string `json:"agentType,omitempty"` // Whether the sub-agent runs synchronously or in the background. ExecutionMode *string `json:"executionMode,omitempty"` - // Root id of the factory run that spawned this sub-agent, when it was spawned by one. + // Legacy root id of the workflow run that spawned this sub-agent. New consumers should use workflowRunId. FactoryRunID *string `json:"factoryRunId,omitempty"` // Model the sub-agent will run with, when known at start. Model *string `json:"model,omitempty"` @@ -2950,10 +2955,12 @@ type SubagentStartedData struct { ParentID *string `json:"parentId,omitempty"` // Whether this sub-agent can be resumed. Currently always false. Resumable *bool `json:"resumable,omitempty"` - // Where the model input for this sub-agent came from. Present when the task planner resolved the launch (the task tool and factory agents); absent for sub-agents created through other runtime paths. + // Where the model input for this sub-agent came from. Present when the task planner resolved the launch (the task tool and workflow agents); absent for sub-agents created through other runtime paths. TaskModelSource *SubagentTaskModelSource `json:"taskModelSource,omitempty"` // Spawning tool invocation ID, or the canonical sub-agent ID used as a fallback for an API launch without a tool invocation. The fallback is not evidence of a tool call. ToolCallID string `json:"toolCallId"` + // Root id of the workflow run that spawned this sub-agent, when it was spawned by one. + WorkflowRunID *string `json:"workflowRunId,omitempty"` } func (*SubagentStartedData) sessionEventData() {} @@ -2965,7 +2972,7 @@ type SystemNotificationData struct { Content string `json:"content"` // Structured metadata identifying what triggered this notification Kind SystemNotification `json:"kind"` - // Responses reasoning settings anchored before this model-facing message, for cache-stable history replay + // Provider reasoning settings anchored before this model-facing message for cache-stable replay; the historical responsesReasoning name is retained for compatibility ResponsesReasoning *ResponsesReasoning `json:"responsesReasoning,omitempty"` // Optional owned worker notification observations; an omitted notification event refers only to this occurrence. WorkerCausality *WorkerCausality `json:"workerCausality,omitempty"` @@ -3688,14 +3695,6 @@ type ExtensionsLoadedExtension struct { Status ExtensionsLoadedExtensionStatus `json:"status"` } -// A declared phase shown in a factory permission prompt. -type FactoryPermissionPhase struct { - // Optional phase detail - Detail *string `json:"detail,omitempty"` - // Phase title - Title string `json:"title"` -} - // Experimental attribution linking an ordinary event to the HydraFusion turn, phase, and concrete source that produced it. // Experimental: FusionAttribution is part of an experimental API and may change or be removed. type FusionAttribution struct { @@ -3705,6 +3704,8 @@ type FusionAttribution struct { ConversationScope *string `json:"conversationScope,omitempty"` // Stable identifier for the HydraFusion turn that produced the event. FusionID string `json:"fusionId"` + // Whether this model request consumed a user steering message rather than only internal Fusion work. + HasUserSteering *bool `json:"hasUserSteering,omitempty"` // HydraFusion orchestration pattern selected for the turn. Pattern string `json:"pattern"` // Identifier of the concrete phase that produced the event. @@ -4069,50 +4070,6 @@ func (PermissionPromptRequestExtensionPermissionAccess) Kind() PermissionPromptR return PermissionPromptRequestKindExtensionPermissionAccess } -// Factory run or authoring permission prompt -type PermissionPromptRequestFactory struct { - // Canonical key used for scoped factory approvals - ApprovalKey string `json:"approvalKey"` - // Assisted-approval judge information for this request; present only in assisted mode. - // Experimental: AssistedApproval is part of an experimental API and may change or be removed. - AssistedApproval *PermissionAssistedApproval `json:"assistedApproval,omitempty"` - // Whether this factory is eligible for persistent approval - CanPersistApproval bool `json:"canPersistApproval"` - // Factory-declared AI-credit limit before any run/resume caller override is applied. - DeclaredMaxAiCredits *float64 `json:"declaredMaxAiCredits,omitempty"` - // Factory-declared concurrent-subagent limit before any run/resume caller override is applied. - DeclaredMaxConcurrentSubagents *int64 `json:"declaredMaxConcurrentSubagents,omitempty"` - // Factory-declared total-subagent limit before any run/resume caller override is applied. - DeclaredMaxTotalSubagents *int64 `json:"declaredMaxTotalSubagents,omitempty"` - // Factory-declared active-time limit in seconds before any run/resume caller override is applied. - DeclaredTimeoutSeconds *float64 `json:"declaredTimeoutSeconds,omitempty"` - // Factory description - Description string `json:"description"` - // Whether managed policy requires a human response and forbids host auto-approval - ManagedApprovalRequired *bool `json:"managedApprovalRequired,omitempty"` - // Effective AI-credit limit; omitted means unlimited - MaxAiCredits *float64 `json:"maxAiCredits,omitempty"` - // Effective concurrent-subagent limit; omitted means unlimited - MaxConcurrentSubagents *int64 `json:"maxConcurrentSubagents,omitempty"` - // Effective total-subagent limit; omitted means unlimited - MaxTotalSubagents *int64 `json:"maxTotalSubagents,omitempty"` - // Factory name - Name string `json:"name"` - // Factory operation, either run or author - Operation FactoryPermissionOperation `json:"operation"` - // Declared factory phases - Phases []FactoryPermissionPhase `json:"phases"` - // Effective active-time limit in seconds; omitted means unlimited - TimeoutSeconds *float64 `json:"timeoutSeconds,omitempty"` - // Tool call ID that triggered this permission request - ToolCallID *string `json:"toolCallId,omitempty"` -} - -func (PermissionPromptRequestFactory) permissionPromptRequest() {} -func (PermissionPromptRequestFactory) Kind() PermissionPromptRequestKind { - return PermissionPromptRequestKindFactory -} - // Hook confirmation permission prompt type PermissionPromptRequestHook struct { // Assisted-approval judge information for this request; present only in assisted mode. @@ -4253,6 +4210,50 @@ func (PermissionPromptRequestURL) Kind() PermissionPromptRequestKind { return PermissionPromptRequestKindURL } +// Workflow run or authoring permission prompt +type PermissionPromptRequestWorkflow struct { + // Canonical key used for scoped workflow approvals + ApprovalKey string `json:"approvalKey"` + // Assisted-approval judge information for this request; present only in assisted mode. + // Experimental: AssistedApproval is part of an experimental API and may change or be removed. + AssistedApproval *PermissionAssistedApproval `json:"assistedApproval,omitempty"` + // Whether this workflow is eligible for persistent approval + CanPersistApproval bool `json:"canPersistApproval"` + // Workflow-declared AI-credit limit before any run/resume caller override is applied. + DeclaredMaxAiCredits *float64 `json:"declaredMaxAiCredits,omitempty"` + // Workflow-declared concurrent-subagent limit before any run/resume caller override is applied. + DeclaredMaxConcurrentSubagents *int64 `json:"declaredMaxConcurrentSubagents,omitempty"` + // Workflow-declared total-subagent limit before any run/resume caller override is applied. + DeclaredMaxTotalSubagents *int64 `json:"declaredMaxTotalSubagents,omitempty"` + // Workflow-declared active-time limit in seconds before any run/resume caller override is applied. + DeclaredTimeoutSeconds *float64 `json:"declaredTimeoutSeconds,omitempty"` + // Workflow description + Description string `json:"description"` + // Whether managed policy requires a human response and forbids host auto-approval + ManagedApprovalRequired *bool `json:"managedApprovalRequired,omitempty"` + // Effective AI-credit limit; omitted means unlimited + MaxAiCredits *float64 `json:"maxAiCredits,omitempty"` + // Effective concurrent-subagent limit; omitted means unlimited + MaxConcurrentSubagents *int64 `json:"maxConcurrentSubagents,omitempty"` + // Effective total-subagent limit; omitted means unlimited + MaxTotalSubagents *int64 `json:"maxTotalSubagents,omitempty"` + // Workflow name + Name string `json:"name"` + // Workflow operation, either run or author + Operation WorkflowPermissionOperation `json:"operation"` + // Declared workflow phases + Phases []WorkflowPermissionPhase `json:"phases"` + // Effective active-time limit in seconds; omitted means unlimited + TimeoutSeconds *float64 `json:"timeoutSeconds,omitempty"` + // Tool call ID that triggered this permission request + ToolCallID *string `json:"toolCallId,omitempty"` +} + +func (PermissionPromptRequestWorkflow) permissionPromptRequest() {} +func (PermissionPromptRequestWorkflow) Kind() PermissionPromptRequestKind { + return PermissionPromptRequestKindWorkflow +} + // File write permission prompt type PermissionPromptRequestWrite struct { // Assisted-approval judge information for this request; present only in assisted mode. @@ -4371,47 +4372,6 @@ func (PermissionRequestExtensionPermissionAccess) Kind() PermissionRequestKind { return PermissionRequestKindExtensionPermissionAccess } -// Factory run or authoring permission request -type PermissionRequestFactory struct { - // Canonical key used for scoped factory approvals - ApprovalKey string `json:"approvalKey"` - // Whether this factory is eligible for persistent approval - CanPersistApproval bool `json:"canPersistApproval"` - // Factory-declared AI-credit limit before any run/resume caller override is applied. - DeclaredMaxAiCredits *float64 `json:"declaredMaxAiCredits,omitempty"` - // Factory-declared concurrent-subagent limit before any run/resume caller override is applied. - DeclaredMaxConcurrentSubagents *int64 `json:"declaredMaxConcurrentSubagents,omitempty"` - // Factory-declared total-subagent limit before any run/resume caller override is applied. - DeclaredMaxTotalSubagents *int64 `json:"declaredMaxTotalSubagents,omitempty"` - // Factory-declared active-time limit in seconds before any run/resume caller override is applied. - DeclaredTimeoutSeconds *float64 `json:"declaredTimeoutSeconds,omitempty"` - // Factory description - Description string `json:"description"` - // When true, managed policy requires an explicit user decision and automatic approval must be bypassed. - ManagedApprovalRequired *bool `json:"managedApprovalRequired,omitempty"` - // Effective AI-credit limit; omitted means unlimited - MaxAiCredits *float64 `json:"maxAiCredits,omitempty"` - // Effective concurrent-subagent limit; omitted means unlimited - MaxConcurrentSubagents *int64 `json:"maxConcurrentSubagents,omitempty"` - // Effective total-subagent limit; omitted means unlimited - MaxTotalSubagents *int64 `json:"maxTotalSubagents,omitempty"` - // Factory name - Name string `json:"name"` - // Factory operation, either run or author - Operation FactoryPermissionOperation `json:"operation"` - // Declared factory phases - Phases []FactoryPermissionPhase `json:"phases"` - // Effective active-time limit in seconds; omitted means unlimited - TimeoutSeconds *float64 `json:"timeoutSeconds,omitempty"` - // Tool call ID that triggered this permission request - ToolCallID *string `json:"toolCallId,omitempty"` -} - -func (PermissionRequestFactory) permissionRequest() {} -func (PermissionRequestFactory) Kind() PermissionRequestKind { - return PermissionRequestKindFactory -} - // Hook confirmation permission request type PermissionRequestHook struct { // Optional message from the hook explaining why confirmation is needed @@ -4579,6 +4539,47 @@ func (PermissionRequestURL) Kind() PermissionRequestKind { return PermissionRequestKindURL } +// Workflow run or authoring permission request +type PermissionRequestWorkflow struct { + // Canonical key used for scoped workflow approvals + ApprovalKey string `json:"approvalKey"` + // Whether this workflow is eligible for persistent approval + CanPersistApproval bool `json:"canPersistApproval"` + // Workflow-declared AI-credit limit before any run/resume caller override is applied. + DeclaredMaxAiCredits *float64 `json:"declaredMaxAiCredits,omitempty"` + // Workflow-declared concurrent-subagent limit before any run/resume caller override is applied. + DeclaredMaxConcurrentSubagents *int64 `json:"declaredMaxConcurrentSubagents,omitempty"` + // Workflow-declared total-subagent limit before any run/resume caller override is applied. + DeclaredMaxTotalSubagents *int64 `json:"declaredMaxTotalSubagents,omitempty"` + // Workflow-declared active-time limit in seconds before any run/resume caller override is applied. + DeclaredTimeoutSeconds *float64 `json:"declaredTimeoutSeconds,omitempty"` + // Workflow description + Description string `json:"description"` + // Whether managed policy requires a human response and forbids host auto-approval + ManagedApprovalRequired *bool `json:"managedApprovalRequired,omitempty"` + // Effective AI-credit limit; omitted means unlimited + MaxAiCredits *float64 `json:"maxAiCredits,omitempty"` + // Effective concurrent-subagent limit; omitted means unlimited + MaxConcurrentSubagents *int64 `json:"maxConcurrentSubagents,omitempty"` + // Effective total-subagent limit; omitted means unlimited + MaxTotalSubagents *int64 `json:"maxTotalSubagents,omitempty"` + // Workflow name + Name string `json:"name"` + // Workflow operation, either run or author + Operation WorkflowPermissionOperation `json:"operation"` + // Declared workflow phases + Phases []WorkflowPermissionPhase `json:"phases"` + // Effective active-time limit in seconds; omitted means unlimited + TimeoutSeconds *float64 `json:"timeoutSeconds,omitempty"` + // Tool call ID that triggered this permission request + ToolCallID *string `json:"toolCallId,omitempty"` +} + +func (PermissionRequestWorkflow) permissionRequest() {} +func (PermissionRequestWorkflow) Kind() PermissionRequestKind { + return PermissionRequestKindWorkflow +} + // File write permission request type PermissionRequestWrite struct { // Whether the UI can offer session-wide approval for file write operations @@ -4840,7 +4841,7 @@ func (r PersistedBinaryImage) Type() PersistedBinaryResultType { return PersistedBinaryResultType(r.Discriminator) } -// Original request-level and effective conversation reasoning effort for a Responses history boundary +// Original request-level and effective conversation reasoning effort for a provider history boundary; the historical type name is retained for compatibility type ResponsesReasoning struct { // Effective effort selected before this message, independent of the response-level reasoning field Effort string `json:"effort"` @@ -5025,37 +5026,6 @@ func (SystemNotificationAgentIdle) Type() SystemNotificationType { return SystemNotificationTypeAgentIdle } -// System notification metadata for a factory execution attempt that reached a terminal state. -type SystemNotificationFactoryCompleted struct { - // Execution attempt that reached this terminal state. - Attempt int64 `json:"attempt"` - // Consumed AI usage in nano-AIU. - ConsumedNanoAiu int64 `json:"consumedNanoAiu"` - // Subagents consumed by the run across all attempts. - ConsumedSubagents int64 `json:"consumedSubagents"` - // Accumulated active execution time in milliseconds. - ElapsedMs int64 `json:"elapsedMs"` - // Persisted factory name. - FactoryName string `json:"factoryName"` - // Machine-readable terminal failure details, when present. - Failure any `json:"failure,omitempty"` - // Pause initiator metadata when this attempt settled as paused. - PauseInfo SystemNotificationFactoryPauseInfo `json:"pauseInfo,omitempty"` - // Bounded prompt-safe preview of the completed result. - ResultPreview *string `json:"resultPreview,omitempty"` - // Actionable run_factory resume guidance for a resource-limit failure. - RetryGuidance *string `json:"retryGuidance,omitempty"` - // Factory run identifier. - RunID string `json:"runId"` - // Terminal status reached by this execution attempt. - Status SystemNotificationFactoryCompletedStatus `json:"status"` -} - -func (SystemNotificationFactoryCompleted) systemNotification() {} -func (SystemNotificationFactoryCompleted) Type() SystemNotificationType { - return SystemNotificationTypeFactoryCompleted -} - // System notification metadata for an instruction file discovered during tool access, including source, trigger file, and tool. type SystemNotificationInstructionDiscovered struct { // Human-readable label for the timeline (e.g., 'AGENTS.md from packages/billing/') @@ -5129,38 +5099,69 @@ func (SystemNotificationUnclassified) Type() SystemNotificationType { return SystemNotificationTypeUnclassified } -// Durable metadata describing who initiated a factory pause. -type SystemNotificationFactoryPauseInfo interface { - systemNotificationFactoryPauseInfo() - Type() SystemNotificationFactoryPauseInfoType +// System notification metadata for a workflow execution attempt that reached a terminal state. +type SystemNotificationWorkflowCompleted struct { + // Execution attempt that reached this terminal state. + Attempt int64 `json:"attempt"` + // Consumed AI usage in nano-AIU. + ConsumedNanoAiu int64 `json:"consumedNanoAiu"` + // Subagents consumed by the run across all attempts. + ConsumedSubagents int64 `json:"consumedSubagents"` + // Accumulated active execution time in milliseconds. + ElapsedMs int64 `json:"elapsedMs"` + // Machine-readable terminal failure details, when present. + Failure any `json:"failure,omitempty"` + // Pause initiator metadata when this attempt settled as paused. + PauseInfo SystemNotificationWorkflowPauseInfo `json:"pauseInfo,omitempty"` + // Bounded prompt-safe preview of the completed result. + ResultPreview *string `json:"resultPreview,omitempty"` + // Actionable run_dynamic_workflow resume guidance for a resource-limit failure. + RetryGuidance *string `json:"retryGuidance,omitempty"` + // Workflow run identifier. + RunID string `json:"runId"` + // Terminal status reached by this execution attempt. + Status SystemNotificationWorkflowCompletedStatus `json:"status"` + // Persisted workflow name. + WorkflowName string `json:"workflowName"` +} + +func (SystemNotificationWorkflowCompleted) systemNotification() {} +func (SystemNotificationWorkflowCompleted) Type() SystemNotificationType { + return SystemNotificationTypeWorkflowCompleted } -type RawSystemNotificationFactoryPauseInfo struct { - Discriminator SystemNotificationFactoryPauseInfoType +// Durable metadata describing who initiated a workflow pause. +type SystemNotificationWorkflowPauseInfo interface { + systemNotificationWorkflowPauseInfo() + Type() SystemNotificationWorkflowPauseInfoType +} + +type RawSystemNotificationWorkflowPauseInfo struct { + Discriminator SystemNotificationWorkflowPauseInfoType Raw json.RawMessage } -func (RawSystemNotificationFactoryPauseInfo) systemNotificationFactoryPauseInfo() {} -func (r RawSystemNotificationFactoryPauseInfo) Type() SystemNotificationFactoryPauseInfoType { +func (RawSystemNotificationWorkflowPauseInfo) systemNotificationWorkflowPauseInfo() {} +func (r RawSystemNotificationWorkflowPauseInfo) Type() SystemNotificationWorkflowPauseInfoType { return r.Discriminator } -type SystemNotificationFactoryPauseInfoCheckpoint struct { +type SystemNotificationWorkflowPauseInfoCheckpoint struct { // Stable author-defined checkpoint key that initiated the pause. Key string `json:"key"` } -func (SystemNotificationFactoryPauseInfoCheckpoint) systemNotificationFactoryPauseInfo() {} -func (SystemNotificationFactoryPauseInfoCheckpoint) Type() SystemNotificationFactoryPauseInfoType { - return SystemNotificationFactoryPauseInfoTypeCheckpoint +func (SystemNotificationWorkflowPauseInfoCheckpoint) systemNotificationWorkflowPauseInfo() {} +func (SystemNotificationWorkflowPauseInfoCheckpoint) Type() SystemNotificationWorkflowPauseInfoType { + return SystemNotificationWorkflowPauseInfoTypeCheckpoint } -type SystemNotificationFactoryPauseInfoUser struct { +type SystemNotificationWorkflowPauseInfoUser struct { } -func (SystemNotificationFactoryPauseInfoUser) systemNotificationFactoryPauseInfo() {} -func (SystemNotificationFactoryPauseInfoUser) Type() SystemNotificationFactoryPauseInfoType { - return SystemNotificationFactoryPauseInfoTypeUser +func (SystemNotificationWorkflowPauseInfoUser) systemNotificationWorkflowPauseInfo() {} +func (SystemNotificationWorkflowPauseInfoUser) Type() SystemNotificationWorkflowPauseInfoType { + return SystemNotificationWorkflowPauseInfoTypeUser } // A content block within a tool result, which may be text, terminal output, image, audio, or a resource @@ -5487,6 +5488,14 @@ type UsageCheckpointModelCacheState struct { ModelID string `json:"modelId"` } +// A declared phase shown in a workflow permission prompt. +type WorkflowPermissionPhase struct { + // Optional phase detail + Detail *string `json:"detail,omitempty"` + // Phase title + Title string `json:"title"` +} + // Working directory and git context at session start type WorkingDirectoryContext struct { // Base commit of current git branch at session start time @@ -5828,32 +5837,6 @@ const ( ExtensionsLoadedExtensionStatusStarting ExtensionsLoadedExtensionStatus = "starting" ) -// Operation gated by a factory permission request. -type FactoryPermissionOperation string - -const ( - // Authoring a factory, which writes JavaScript into a session-scoped extension and loads it. - FactoryPermissionOperationAuthor FactoryPermissionOperation = "author" - // Running a registered factory, which spends subagents, active time, and AI credits under the approved limits. - FactoryPermissionOperationRun FactoryPermissionOperation = "run" -) - -// Terminal status a factory run committed. A settled run is never `pending` or `running`, so those two members of the run-status domain are deliberately absent. -type FactoryRunSettledStatus string - -const ( - // The run was cancelled by its caller or by session disposal. - FactoryRunSettledStatusCancelled FactoryRunSettledStatus = "cancelled" - // The factory body resolved and its result was committed. - FactoryRunSettledStatusCompleted FactoryRunSettledStatus = "completed" - // The run failed, with `failureType` carrying the class when it has one. - FactoryRunSettledStatusError FactoryRunSettledStatus = "error" - // The run was stopped by a limit, an approval refusal or another policy decision. - FactoryRunSettledStatusHalted FactoryRunSettledStatus = "halted" - // The attempt paused intentionally while preserving resumable run state. - FactoryRunSettledStatusPaused FactoryRunSettledStatus = "paused" -) - // Conversation scope in which a HydraFusion phase executes. // Experimental: FusionConversationScope is part of an experimental API and may change or be removed. type FusionConversationScope string @@ -6233,14 +6216,14 @@ const ( PermissionApprovalEvaluationReasonCodeUnreviewableScriptInvocation PermissionApprovalEvaluationReasonCode = "unreviewable-script-invocation" ) -// Which direction a message-backed authorization claim moves authority in. +// Direction stored in a historical extractor claim. Current runtimes do not apply it. // Experimental: PermissionMessageAuthorizationPolarity is part of an experimental API and may change or be removed. type PermissionMessageAuthorizationPolarity string const ( - // The human's words refused an effect. + // Historical claim recorded as a denial. PermissionMessageAuthorizationPolarityDenial PermissionMessageAuthorizationPolarity = "denial" - // The human's words authorized an effect. + // Historical claim recorded as a grant. PermissionMessageAuthorizationPolarityGrant PermissionMessageAuthorizationPolarity = "grant" ) @@ -6253,13 +6236,13 @@ const ( PermissionPromptRequestKindExtensionEnvAccess PermissionPromptRequestKind = "extension-env-access" PermissionPromptRequestKindExtensionManagement PermissionPromptRequestKind = "extension-management" PermissionPromptRequestKindExtensionPermissionAccess PermissionPromptRequestKind = "extension-permission-access" - PermissionPromptRequestKindFactory PermissionPromptRequestKind = "factory" PermissionPromptRequestKindHook PermissionPromptRequestKind = "hook" PermissionPromptRequestKindMCP PermissionPromptRequestKind = "mcp" PermissionPromptRequestKindMemory PermissionPromptRequestKind = "memory" PermissionPromptRequestKindPath PermissionPromptRequestKind = "path" PermissionPromptRequestKindRead PermissionPromptRequestKind = "read" PermissionPromptRequestKindURL PermissionPromptRequestKind = "url" + PermissionPromptRequestKindWorkflow PermissionPromptRequestKind = "workflow" PermissionPromptRequestKindWrite PermissionPromptRequestKind = "write" ) @@ -6292,13 +6275,13 @@ const ( PermissionRequestKindExtensionEnvAccess PermissionRequestKind = "extension-env-access" PermissionRequestKindExtensionManagement PermissionRequestKind = "extension-management" PermissionRequestKindExtensionPermissionAccess PermissionRequestKind = "extension-permission-access" - PermissionRequestKindFactory PermissionRequestKind = "factory" PermissionRequestKindHook PermissionRequestKind = "hook" PermissionRequestKindMCP PermissionRequestKind = "mcp" PermissionRequestKindMemory PermissionRequestKind = "memory" PermissionRequestKindRead PermissionRequestKind = "read" PermissionRequestKindShell PermissionRequestKind = "shell" PermissionRequestKindURL PermissionRequestKind = "url" + PermissionRequestKindWorkflow PermissionRequestKind = "workflow" PermissionRequestKindWrite PermissionRequestKind = "write" ) @@ -6480,42 +6463,42 @@ const ( SystemNotificationAgentCompletedStatusFailed SystemNotificationAgentCompletedStatus = "failed" ) -// Terminal status reached by a factory execution attempt. -type SystemNotificationFactoryCompletedStatus string - -const ( - // The factory was cancelled. - SystemNotificationFactoryCompletedStatusCancelled SystemNotificationFactoryCompletedStatus = "cancelled" - // The factory completed successfully. - SystemNotificationFactoryCompletedStatusCompleted SystemNotificationFactoryCompletedStatus = "completed" - // The factory failed. - SystemNotificationFactoryCompletedStatusError SystemNotificationFactoryCompletedStatus = "error" - // The factory was halted. - SystemNotificationFactoryCompletedStatusHalted SystemNotificationFactoryCompletedStatus = "halted" - // The factory attempt paused intentionally. - SystemNotificationFactoryCompletedStatusPaused SystemNotificationFactoryCompletedStatus = "paused" -) - -// Type discriminator for SystemNotificationFactoryPauseInfo. -type SystemNotificationFactoryPauseInfoType string - -const ( - SystemNotificationFactoryPauseInfoTypeCheckpoint SystemNotificationFactoryPauseInfoType = "checkpoint" - SystemNotificationFactoryPauseInfoTypeUser SystemNotificationFactoryPauseInfoType = "user" -) - // Type discriminator for SystemNotification. type SystemNotificationType string const ( SystemNotificationTypeAgentCompleted SystemNotificationType = "agent_completed" SystemNotificationTypeAgentIdle SystemNotificationType = "agent_idle" - SystemNotificationTypeFactoryCompleted SystemNotificationType = "factory_completed" SystemNotificationTypeInstructionDiscovered SystemNotificationType = "instruction_discovered" SystemNotificationTypeNewInboxMessage SystemNotificationType = "new_inbox_message" SystemNotificationTypeShellCompleted SystemNotificationType = "shell_completed" SystemNotificationTypeShellDetachedCompleted SystemNotificationType = "shell_detached_completed" SystemNotificationTypeUnclassified SystemNotificationType = "unclassified" + SystemNotificationTypeWorkflowCompleted SystemNotificationType = "workflow_completed" +) + +// Terminal status reached by a workflow execution attempt. +type SystemNotificationWorkflowCompletedStatus string + +const ( + // The workflow was cancelled. + SystemNotificationWorkflowCompletedStatusCancelled SystemNotificationWorkflowCompletedStatus = "cancelled" + // The workflow completed successfully. + SystemNotificationWorkflowCompletedStatusCompleted SystemNotificationWorkflowCompletedStatus = "completed" + // The workflow failed. + SystemNotificationWorkflowCompletedStatusError SystemNotificationWorkflowCompletedStatus = "error" + // The workflow was halted. + SystemNotificationWorkflowCompletedStatusHalted SystemNotificationWorkflowCompletedStatus = "halted" + // The workflow attempt paused intentionally. + SystemNotificationWorkflowCompletedStatusPaused SystemNotificationWorkflowCompletedStatus = "paused" +) + +// Type discriminator for SystemNotificationWorkflowPauseInfo. +type SystemNotificationWorkflowPauseInfoType string + +const ( + SystemNotificationWorkflowPauseInfoTypeCheckpoint SystemNotificationWorkflowPauseInfoType = "checkpoint" + SystemNotificationWorkflowPauseInfoTypeUser SystemNotificationWorkflowPauseInfoType = "user" ) // Theme variant this icon is intended for @@ -6604,6 +6587,32 @@ const ( UserMessageDeliverySteering UserMessageDelivery = "steering" ) +// Operation gated by a workflow permission request. +type WorkflowPermissionOperation string + +const ( + // Authoring a workflow, which writes JavaScript into a session-scoped extension and loads it. + WorkflowPermissionOperationAuthor WorkflowPermissionOperation = "author" + // Running a registered workflow, which spends subagents, active time, and AI credits under the approved limits. + WorkflowPermissionOperationRun WorkflowPermissionOperation = "run" +) + +// Terminal status a workflow run committed. A settled run is never `pending` or `running`, so those two members of the run-status domain are deliberately absent. +type WorkflowRunSettledStatus string + +const ( + // The run was cancelled by its caller or by session disposal. + WorkflowRunSettledStatusCancelled WorkflowRunSettledStatus = "cancelled" + // The workflow body resolved and its result was committed. + WorkflowRunSettledStatusCompleted WorkflowRunSettledStatus = "completed" + // The run failed, with `failureType` carrying the class when it has one. + WorkflowRunSettledStatusError WorkflowRunSettledStatus = "error" + // The run was stopped by a limit, an approval refusal or another policy decision. + WorkflowRunSettledStatusHalted WorkflowRunSettledStatus = "halted" + // The attempt paused intentionally while preserving resumable run state. + WorkflowRunSettledStatusPaused WorkflowRunSettledStatus = "paused" +) + // Hosting platform type of the repository (github or ado) type WorkingDirectoryContextHostType string diff --git a/go/worker_causality_test.go b/go/worker_causality_test.go index 3e3b005b85..96eea10015 100644 --- a/go/worker_causality_test.go +++ b/go/worker_causality_test.go @@ -26,6 +26,9 @@ type workerCausalityCorpus struct { Value any Accepted bool } + WorkflowCompleted struct { + Event json.RawMessage + } } func workerCorpus(t *testing.T) workerCausalityCorpus { @@ -115,3 +118,24 @@ func TestWorkerCausalityExactUTF8Budget(t *testing.T) { }) } } + +func TestCanonicalWorkflowCompletionDecodesTypedFields(t *testing.T) { + corpus := workerCorpus(t) + var event rpc.SessionEvent + if err := json.Unmarshal(corpus.WorkflowCompleted.Event, &event); err != nil { + t.Fatal(err) + } + data, ok := event.Data.(*rpc.SystemNotificationData) + if !ok { + t.Fatalf("data type = %T", event.Data) + } + kind, ok := data.Kind.(*rpc.SystemNotificationWorkflowCompleted) + if !ok { + t.Fatalf("kind type = %T", data.Kind) + } + if kind.WorkflowName != "fix-ci" || kind.RunID != "run-1" || + kind.Status != rpc.SystemNotificationWorkflowCompletedStatusCompleted || + kind.ConsumedSubagents != 1 { + t.Fatalf("unexpected workflow completion: %+v", kind) + } +} diff --git a/go/zsession_events.go b/go/zsession_events.go index 270678a6f0..408ea52e96 100644 --- a/go/zsession_events.go +++ b/go/zsession_events.go @@ -122,12 +122,6 @@ type ( ExtensionsLoadedExtensionStatus = rpc.ExtensionsLoadedExtensionStatus ExternalToolCompletedData = rpc.ExternalToolCompletedData ExternalToolRequestedData = rpc.ExternalToolRequestedData - FactoryPermissionOperation = rpc.FactoryPermissionOperation - FactoryPermissionPhase = rpc.FactoryPermissionPhase - FactoryRunSettledData = rpc.FactoryRunSettledData - FactoryRunSettledStatus = rpc.FactoryRunSettledStatus - FactoryRunStartedData = rpc.FactoryRunStartedData - FactoryRunUpdatedData = rpc.FactoryRunUpdatedData FusionAttribution = rpc.FusionAttribution FusionConversationScope = rpc.FusionConversationScope FusionFollowUpAction = rpc.FusionFollowUpAction @@ -221,7 +215,6 @@ type ( PermissionPromptRequestExtensionEnvAccess = rpc.PermissionPromptRequestExtensionEnvAccess PermissionPromptRequestExtensionManagement = rpc.PermissionPromptRequestExtensionManagement PermissionPromptRequestExtensionPermissionAccess = rpc.PermissionPromptRequestExtensionPermissionAccess - PermissionPromptRequestFactory = rpc.PermissionPromptRequestFactory PermissionPromptRequestHook = rpc.PermissionPromptRequestHook PermissionPromptRequestKind = rpc.PermissionPromptRequestKind PermissionPromptRequestMCP = rpc.PermissionPromptRequestMCP @@ -230,6 +223,7 @@ type ( PermissionPromptRequestPathAccessKind = rpc.PermissionPromptRequestPathAccessKind PermissionPromptRequestRead = rpc.PermissionPromptRequestRead PermissionPromptRequestURL = rpc.PermissionPromptRequestURL + PermissionPromptRequestWorkflow = rpc.PermissionPromptRequestWorkflow PermissionPromptRequestWrite = rpc.PermissionPromptRequestWrite PermissionRecommendation = rpc.PermissionRecommendation PermissionRecoveryAttempt = rpc.PermissionRecoveryAttempt @@ -247,7 +241,6 @@ type ( PermissionRequestExtensionEnvAccess = rpc.PermissionRequestExtensionEnvAccess PermissionRequestExtensionManagement = rpc.PermissionRequestExtensionManagement PermissionRequestExtensionPermissionAccess = rpc.PermissionRequestExtensionPermissionAccess - PermissionRequestFactory = rpc.PermissionRequestFactory PermissionRequestHook = rpc.PermissionRequestHook PermissionRequestKind = rpc.PermissionRequestKind PermissionRequestMCP = rpc.PermissionRequestMCP @@ -261,6 +254,7 @@ type ( PermissionRequestShellCommandSegment = rpc.PermissionRequestShellCommandSegment PermissionRequestShellPossibleURL = rpc.PermissionRequestShellPossibleURL PermissionRequestURL = rpc.PermissionRequestURL + PermissionRequestWorkflow = rpc.PermissionRequestWorkflow PermissionRequestWrite = rpc.PermissionRequestWrite PermissionResult = rpc.PermissionResult PermissionResultKind = rpc.PermissionResultKind @@ -279,7 +273,7 @@ type ( RawPersistedBinaryResult = rpc.RawPersistedBinaryResult RawSessionEventData = rpc.RawSessionEventData RawSystemNotification = rpc.RawSystemNotification - RawSystemNotificationFactoryPauseInfo = rpc.RawSystemNotificationFactoryPauseInfo + RawSystemNotificationWorkflowPauseInfo = rpc.RawSystemNotificationWorkflowPauseInfo RawToolExecutionCompleteContent = rpc.RawToolExecutionCompleteContent ReasoningSummary = rpc.ReasoningSummary RecommendedAutoTier = rpc.RecommendedAutoTier @@ -392,18 +386,18 @@ type ( SystemNotificationAgentCompletedStatus = rpc.SystemNotificationAgentCompletedStatus SystemNotificationAgentIdle = rpc.SystemNotificationAgentIdle SystemNotificationData = rpc.SystemNotificationData - SystemNotificationFactoryCompleted = rpc.SystemNotificationFactoryCompleted - SystemNotificationFactoryCompletedStatus = rpc.SystemNotificationFactoryCompletedStatus - SystemNotificationFactoryPauseInfo = rpc.SystemNotificationFactoryPauseInfo - SystemNotificationFactoryPauseInfoCheckpoint = rpc.SystemNotificationFactoryPauseInfoCheckpoint - SystemNotificationFactoryPauseInfoType = rpc.SystemNotificationFactoryPauseInfoType - SystemNotificationFactoryPauseInfoUser = rpc.SystemNotificationFactoryPauseInfoUser SystemNotificationInstructionDiscovered = rpc.SystemNotificationInstructionDiscovered SystemNotificationNewInboxMessage = rpc.SystemNotificationNewInboxMessage SystemNotificationShellCompleted = rpc.SystemNotificationShellCompleted SystemNotificationShellDetachedCompleted = rpc.SystemNotificationShellDetachedCompleted SystemNotificationType = rpc.SystemNotificationType SystemNotificationUnclassified = rpc.SystemNotificationUnclassified + SystemNotificationWorkflowCompleted = rpc.SystemNotificationWorkflowCompleted + SystemNotificationWorkflowCompletedStatus = rpc.SystemNotificationWorkflowCompletedStatus + SystemNotificationWorkflowPauseInfo = rpc.SystemNotificationWorkflowPauseInfo + SystemNotificationWorkflowPauseInfoCheckpoint = rpc.SystemNotificationWorkflowPauseInfoCheckpoint + SystemNotificationWorkflowPauseInfoType = rpc.SystemNotificationWorkflowPauseInfoType + SystemNotificationWorkflowPauseInfoUser = rpc.SystemNotificationWorkflowPauseInfoUser TaskBlocker = rpc.TaskBlocker TaskBlockerKind = rpc.TaskBlockerKind TaskCompleteData = rpc.TaskCompleteData @@ -460,11 +454,11 @@ type ( UserToolSessionApprovalExtensionEnvAccess = rpc.UserToolSessionApprovalExtensionEnvAccess UserToolSessionApprovalExtensionManagement = rpc.UserToolSessionApprovalExtensionManagement UserToolSessionApprovalExtensionPermissionAccess = rpc.UserToolSessionApprovalExtensionPermissionAccess - UserToolSessionApprovalFactory = rpc.UserToolSessionApprovalFactory UserToolSessionApprovalKind = rpc.UserToolSessionApprovalKind UserToolSessionApprovalMCP = rpc.UserToolSessionApprovalMCP UserToolSessionApprovalMemory = rpc.UserToolSessionApprovalMemory UserToolSessionApprovalRead = rpc.UserToolSessionApprovalRead + UserToolSessionApprovalWorkflow = rpc.UserToolSessionApprovalWorkflow UserToolSessionApprovalWrite = rpc.UserToolSessionApprovalWrite Verbosity = rpc.Verbosity WorkerAdmission = rpc.WorkerAdmission @@ -478,6 +472,12 @@ type ( WorkerNotificationReference = rpc.WorkerNotificationReference WorkerObservationProvenance = rpc.WorkerObservationProvenance WorkerSource = rpc.WorkerSource + WorkflowPermissionOperation = rpc.WorkflowPermissionOperation + WorkflowPermissionPhase = rpc.WorkflowPermissionPhase + WorkflowRunSettledData = rpc.WorkflowRunSettledData + WorkflowRunSettledStatus = rpc.WorkflowRunSettledStatus + WorkflowRunStartedData = rpc.WorkflowRunStartedData + WorkflowRunUpdatedData = rpc.WorkflowRunUpdatedData WorkingDirectoryContext = rpc.WorkingDirectoryContext WorkingDirectoryContextHostType = rpc.WorkingDirectoryContextHostType WorkspaceFileChangedOperation = rpc.WorkspaceFileChangedOperation @@ -594,13 +594,6 @@ const ( ExtensionsLoadedExtensionStatusFailed = rpc.ExtensionsLoadedExtensionStatusFailed ExtensionsLoadedExtensionStatusRunning = rpc.ExtensionsLoadedExtensionStatusRunning ExtensionsLoadedExtensionStatusStarting = rpc.ExtensionsLoadedExtensionStatusStarting - FactoryPermissionOperationAuthor = rpc.FactoryPermissionOperationAuthor - FactoryPermissionOperationRun = rpc.FactoryPermissionOperationRun - FactoryRunSettledStatusCancelled = rpc.FactoryRunSettledStatusCancelled - FactoryRunSettledStatusCompleted = rpc.FactoryRunSettledStatusCompleted - FactoryRunSettledStatusError = rpc.FactoryRunSettledStatusError - FactoryRunSettledStatusHalted = rpc.FactoryRunSettledStatusHalted - FactoryRunSettledStatusPaused = rpc.FactoryRunSettledStatusPaused FusionConversationScopeReview = rpc.FusionConversationScopeReview FusionConversationScopeRoot = rpc.FusionConversationScopeRoot FusionFollowUpActionReroute = rpc.FusionFollowUpActionReroute @@ -691,6 +684,7 @@ const ( ModelCallFinishedOutcomeSuccess = rpc.ModelCallFinishedOutcomeSuccess ModelChangeSourceAgent = rpc.ModelChangeSourceAgent ModelChangeSourceAutomatic = rpc.ModelChangeSourceAutomatic + ModelChangeSourceAutoTierRecommendation = rpc.ModelChangeSourceAutoTierRecommendation ModelChangeSourceChangeboardingShortcut = rpc.ModelChangeSourceChangeboardingShortcut ModelChangeSourceConfigCommand = rpc.ModelChangeSourceConfigCommand ModelChangeSourceManagedSettings = rpc.ModelChangeSourceManagedSettings @@ -754,13 +748,13 @@ const ( PermissionPromptRequestKindExtensionEnvAccess = rpc.PermissionPromptRequestKindExtensionEnvAccess PermissionPromptRequestKindExtensionManagement = rpc.PermissionPromptRequestKindExtensionManagement PermissionPromptRequestKindExtensionPermissionAccess = rpc.PermissionPromptRequestKindExtensionPermissionAccess - PermissionPromptRequestKindFactory = rpc.PermissionPromptRequestKindFactory PermissionPromptRequestKindHook = rpc.PermissionPromptRequestKindHook PermissionPromptRequestKindMCP = rpc.PermissionPromptRequestKindMCP PermissionPromptRequestKindMemory = rpc.PermissionPromptRequestKindMemory PermissionPromptRequestKindPath = rpc.PermissionPromptRequestKindPath PermissionPromptRequestKindRead = rpc.PermissionPromptRequestKindRead PermissionPromptRequestKindURL = rpc.PermissionPromptRequestKindURL + PermissionPromptRequestKindWorkflow = rpc.PermissionPromptRequestKindWorkflow PermissionPromptRequestKindWrite = rpc.PermissionPromptRequestKindWrite PermissionPromptRequestPathAccessKindRead = rpc.PermissionPromptRequestPathAccessKindRead PermissionPromptRequestPathAccessKindShell = rpc.PermissionPromptRequestPathAccessKindShell @@ -799,13 +793,13 @@ const ( PermissionRequestKindExtensionEnvAccess = rpc.PermissionRequestKindExtensionEnvAccess PermissionRequestKindExtensionManagement = rpc.PermissionRequestKindExtensionManagement PermissionRequestKindExtensionPermissionAccess = rpc.PermissionRequestKindExtensionPermissionAccess - PermissionRequestKindFactory = rpc.PermissionRequestKindFactory PermissionRequestKindHook = rpc.PermissionRequestKindHook PermissionRequestKindMCP = rpc.PermissionRequestKindMCP PermissionRequestKindMemory = rpc.PermissionRequestKindMemory PermissionRequestKindRead = rpc.PermissionRequestKindRead PermissionRequestKindShell = rpc.PermissionRequestKindShell PermissionRequestKindURL = rpc.PermissionRequestKindURL + PermissionRequestKindWorkflow = rpc.PermissionRequestKindWorkflow PermissionRequestKindWrite = rpc.PermissionRequestKindWrite PermissionRequestMemoryActionStore = rpc.PermissionRequestMemoryActionStore PermissionRequestMemoryActionVote = rpc.PermissionRequestMemoryActionVote @@ -875,9 +869,6 @@ const ( SessionEventTypeExitPlanModeRequested = rpc.SessionEventTypeExitPlanModeRequested SessionEventTypeExternalToolCompleted = rpc.SessionEventTypeExternalToolCompleted SessionEventTypeExternalToolRequested = rpc.SessionEventTypeExternalToolRequested - SessionEventTypeFactoryRunSettled = rpc.SessionEventTypeFactoryRunSettled - SessionEventTypeFactoryRunStarted = rpc.SessionEventTypeFactoryRunStarted - SessionEventTypeFactoryRunUpdated = rpc.SessionEventTypeFactoryRunUpdated SessionEventTypeHookEnd = rpc.SessionEventTypeHookEnd SessionEventTypeHookProgress = rpc.SessionEventTypeHookProgress SessionEventTypeHookStart = rpc.SessionEventTypeHookStart @@ -991,6 +982,9 @@ const ( SessionEventTypeUserInputCompleted = rpc.SessionEventTypeUserInputCompleted SessionEventTypeUserInputRequested = rpc.SessionEventTypeUserInputRequested SessionEventTypeUserMessage = rpc.SessionEventTypeUserMessage + SessionEventTypeWorkflowRunSettled = rpc.SessionEventTypeWorkflowRunSettled + SessionEventTypeWorkflowRunStarted = rpc.SessionEventTypeWorkflowRunStarted + SessionEventTypeWorkflowRunUpdated = rpc.SessionEventTypeWorkflowRunUpdated SessionLimitsExhaustedResponseActionAdd = rpc.SessionLimitsExhaustedResponseActionAdd SessionLimitsExhaustedResponseActionCancel = rpc.SessionLimitsExhaustedResponseActionCancel SessionLimitsExhaustedResponseActionSet = rpc.SessionLimitsExhaustedResponseActionSet @@ -1026,21 +1020,21 @@ const ( SystemMessageRoleSystem = rpc.SystemMessageRoleSystem SystemNotificationAgentCompletedStatusCompleted = rpc.SystemNotificationAgentCompletedStatusCompleted SystemNotificationAgentCompletedStatusFailed = rpc.SystemNotificationAgentCompletedStatusFailed - SystemNotificationFactoryCompletedStatusCancelled = rpc.SystemNotificationFactoryCompletedStatusCancelled - SystemNotificationFactoryCompletedStatusCompleted = rpc.SystemNotificationFactoryCompletedStatusCompleted - SystemNotificationFactoryCompletedStatusError = rpc.SystemNotificationFactoryCompletedStatusError - SystemNotificationFactoryCompletedStatusHalted = rpc.SystemNotificationFactoryCompletedStatusHalted - SystemNotificationFactoryCompletedStatusPaused = rpc.SystemNotificationFactoryCompletedStatusPaused - SystemNotificationFactoryPauseInfoTypeCheckpoint = rpc.SystemNotificationFactoryPauseInfoTypeCheckpoint - SystemNotificationFactoryPauseInfoTypeUser = rpc.SystemNotificationFactoryPauseInfoTypeUser SystemNotificationTypeAgentCompleted = rpc.SystemNotificationTypeAgentCompleted SystemNotificationTypeAgentIdle = rpc.SystemNotificationTypeAgentIdle - SystemNotificationTypeFactoryCompleted = rpc.SystemNotificationTypeFactoryCompleted SystemNotificationTypeInstructionDiscovered = rpc.SystemNotificationTypeInstructionDiscovered SystemNotificationTypeNewInboxMessage = rpc.SystemNotificationTypeNewInboxMessage SystemNotificationTypeShellCompleted = rpc.SystemNotificationTypeShellCompleted SystemNotificationTypeShellDetachedCompleted = rpc.SystemNotificationTypeShellDetachedCompleted SystemNotificationTypeUnclassified = rpc.SystemNotificationTypeUnclassified + SystemNotificationTypeWorkflowCompleted = rpc.SystemNotificationTypeWorkflowCompleted + SystemNotificationWorkflowCompletedStatusCancelled = rpc.SystemNotificationWorkflowCompletedStatusCancelled + SystemNotificationWorkflowCompletedStatusCompleted = rpc.SystemNotificationWorkflowCompletedStatusCompleted + SystemNotificationWorkflowCompletedStatusError = rpc.SystemNotificationWorkflowCompletedStatusError + SystemNotificationWorkflowCompletedStatusHalted = rpc.SystemNotificationWorkflowCompletedStatusHalted + SystemNotificationWorkflowCompletedStatusPaused = rpc.SystemNotificationWorkflowCompletedStatusPaused + SystemNotificationWorkflowPauseInfoTypeCheckpoint = rpc.SystemNotificationWorkflowPauseInfoTypeCheckpoint + SystemNotificationWorkflowPauseInfoTypeUser = rpc.SystemNotificationWorkflowPauseInfoTypeUser TaskBlockerKindPermissionRecovery = rpc.TaskBlockerKindPermissionRecovery TaskCompletionOutcomeBlocked = rpc.TaskCompletionOutcomeBlocked TaskCompletionOutcomeCompleted = rpc.TaskCompletionOutcomeCompleted @@ -1075,10 +1069,10 @@ const ( UserToolSessionApprovalKindExtensionEnvAccess = rpc.UserToolSessionApprovalKindExtensionEnvAccess UserToolSessionApprovalKindExtensionManagement = rpc.UserToolSessionApprovalKindExtensionManagement UserToolSessionApprovalKindExtensionPermissionAccess = rpc.UserToolSessionApprovalKindExtensionPermissionAccess - UserToolSessionApprovalKindFactory = rpc.UserToolSessionApprovalKindFactory UserToolSessionApprovalKindMCP = rpc.UserToolSessionApprovalKindMCP UserToolSessionApprovalKindMemory = rpc.UserToolSessionApprovalKindMemory UserToolSessionApprovalKindRead = rpc.UserToolSessionApprovalKindRead + UserToolSessionApprovalKindWorkflow = rpc.UserToolSessionApprovalKindWorkflow UserToolSessionApprovalKindWrite = rpc.UserToolSessionApprovalKindWrite VerbosityHigh = rpc.VerbosityHigh VerbosityLow = rpc.VerbosityLow @@ -1094,6 +1088,13 @@ const ( WorkerNotificationModeQueued = rpc.WorkerNotificationModeQueued WorkerObservationProvenanceAhpCoordinator = rpc.WorkerObservationProvenanceAhpCoordinator WorkerObservationProvenanceNative = rpc.WorkerObservationProvenanceNative + WorkflowPermissionOperationAuthor = rpc.WorkflowPermissionOperationAuthor + WorkflowPermissionOperationRun = rpc.WorkflowPermissionOperationRun + WorkflowRunSettledStatusCancelled = rpc.WorkflowRunSettledStatusCancelled + WorkflowRunSettledStatusCompleted = rpc.WorkflowRunSettledStatusCompleted + WorkflowRunSettledStatusError = rpc.WorkflowRunSettledStatusError + WorkflowRunSettledStatusHalted = rpc.WorkflowRunSettledStatusHalted + WorkflowRunSettledStatusPaused = rpc.WorkflowRunSettledStatusPaused WorkingDirectoryContextHostTypeADO = rpc.WorkingDirectoryContextHostTypeADO WorkingDirectoryContextHostTypeGitHub = rpc.WorkingDirectoryContextHostTypeGitHub WorkspaceFileChangedOperationCreate = rpc.WorkspaceFileChangedOperationCreate diff --git a/java/scripts/codegen/java.ts b/java/scripts/codegen/java.ts index 76c1d3f188..e0563ea85f 100644 --- a/java/scripts/codegen/java.ts +++ b/java/scripts/codegen/java.ts @@ -232,6 +232,7 @@ interface JavaTypeResult { let currentDefinitions: Record = {}; const pendingStandaloneTypes = new Map(); const promotedNestedUnionTypes = new Set(); +const SESSION_EVENT_PROMOTED_UNION_TYPES = new Set(["SystemNotification"]); const generatedSessionEventTypeNames = new Set(); // Cross-schema definitions: keyed by schema filename (e.g. "session-events.schema.json"), @@ -360,43 +361,47 @@ function resolveAnyOfVariants( .filter((v) => v.type !== "null"); } +function localDefinitionName(schema: JSONSchema7): string | null { + return schema.$ref?.match(/^#\/definitions\/([^/]+)$/)?.[1] ?? null; +} + +function closedDiscriminatedUnionVariants( + schema: JSONSchema7, + definitions: Record +): JSONSchema7[] | null { + const name = localDefinitionName(schema); + const resolved = name ? definitions[name] ?? null : schema; + if (!resolved?.anyOf || !Array.isArray(resolved.anyOf)) return null; + const variants = resolveAnyOfVariants(resolved.anyOf as JSONSchema7[], definitions); + return variants.length > 1 + && findDiscriminator(variants) + && variants.every((variant) => variant.additionalProperties === false) + ? variants + : null; +} + export function collectNestedDiscriminatedUnionTypeNames( root: unknown, definitions: Record ): Set { const promotedTypes = new Set(); - const definitionName = (schema: JSONSchema7): string | null => { - return schema.$ref?.match(/^#\/definitions\/([^/]+)$/)?.[1] ?? null; - }; - const resolveLocal = (schema: JSONSchema7): JSONSchema7 | null => { - const name = definitionName(schema); - return name ? definitions[name] ?? null : schema; - }; - const closedDiscriminatedUnionVariants = (schema: JSONSchema7): JSONSchema7[] | null => { - const resolved = resolveLocal(schema); - if (!resolved?.anyOf || !Array.isArray(resolved.anyOf)) return null; - const variants = resolveAnyOfVariants(resolved.anyOf as JSONSchema7[], definitions); - return variants.length > 1 - && findDiscriminator(variants) - && variants.every((variant) => variant.additionalProperties === false) - ? variants - : null; - }; - const rootSchema = typeof root === "object" && root !== null ? root as JSONSchema7 : null; - const rootVariants = rootSchema ? closedDiscriminatedUnionVariants(rootSchema) : null; + const rootVariants = rootSchema ? closedDiscriminatedUnionVariants(rootSchema, definitions) : null; if (!rootVariants) return promotedTypes; const nestedUnionItems: JSONSchema7[] = []; for (const variant of rootVariants) { for (const property of Object.values(variant.properties ?? {})) { if (!property || typeof property !== "object") continue; - const propertySchema = resolveLocal(property as JSONSchema7); + const propertyName = localDefinitionName(property as JSONSchema7); + const propertySchema = propertyName + ? definitions[propertyName] ?? null + : property as JSONSchema7; if ( propertySchema?.type === "array" && propertySchema.items && !Array.isArray(propertySchema.items) - && closedDiscriminatedUnionVariants(propertySchema.items as JSONSchema7) + && closedDiscriminatedUnionVariants(propertySchema.items as JSONSchema7, definitions) ) { nestedUnionItems.push(propertySchema.items as JSONSchema7); } @@ -405,13 +410,13 @@ export function collectNestedDiscriminatedUnionTypeNames( const visitedDefinitions = new Set(); const visit = (schema: JSONSchema7): void => { - const name = definitionName(schema); + const name = localDefinitionName(schema); if (name) { if (visitedDefinitions.has(name)) return; visitedDefinitions.add(name); const resolved = definitions[name]; if (!resolved) return; - if (closedDiscriminatedUnionVariants(schema)) { + if (closedDiscriminatedUnionVariants(resolved, definitions)) { promotedTypes.add(name); } visit(resolved); @@ -440,6 +445,42 @@ export function collectNestedDiscriminatedUnionTypeNames( return promotedTypes; } +function collectReferencedDiscriminatedUnionTypeNames( + schema: JSONSchema7 | null, + definitions: Record +): Set { + const promotedTypes = new Set(); + const visitedDefinitions = new Set(); + const visit = (candidate: JSONSchema7): void => { + const name = localDefinitionName(candidate); + if (name) { + if (visitedDefinitions.has(name)) return; + visitedDefinitions.add(name); + const resolved = definitions[name]; + if (!resolved) return; + if (closedDiscriminatedUnionVariants(resolved, definitions)) promotedTypes.add(name); + visit(resolved); + return; + } + for (const property of Object.values(candidate.properties ?? {})) { + if (property && typeof property === "object") visit(property as JSONSchema7); + } + if (candidate.items && !Array.isArray(candidate.items)) visit(candidate.items as JSONSchema7); + if (candidate.additionalProperties && typeof candidate.additionalProperties === "object") { + visit(candidate.additionalProperties as JSONSchema7); + } + for (const branch of [ + ...(candidate.anyOf ?? []), + ...(candidate.oneOf ?? []), + ...(candidate.allOf ?? []), + ]) { + if (branch && typeof branch === "object") visit(branch as JSONSchema7); + } + }; + if (schema) visit(schema); + return promotedTypes; +} + /** * Generate a polymorphic base class and variant subclasses for a discriminated union result type. */ @@ -521,7 +562,8 @@ async function generatePolymorphicResultClass( className: string, schema: JSONSchema7, packageName: string, - packageDir: string + packageDir: string, + headerComment = GENERATED_FROM_API ): Promise { const anyOf = schema.anyOf as JSONSchema7[]; const variants = resolveAnyOfVariants(anyOf); @@ -549,7 +591,7 @@ async function generatePolymorphicResultClass( // Resolve fields now so nested/standalone unions participate in the same // ownership plan. Nothing is written until every membership is known. for (const variant of variantInfos) { - await generatePolymorphicVariantClass(variant.variantClassName, variant.schema, variant.discriminatorValue, discriminator.property, className, packageName, packageDir); + await generatePolymorphicVariantClass(variant.variantClassName, variant.schema, variant.discriminatorValue, discriminator.property, className, packageName, packageDir, headerComment); } return; } @@ -566,7 +608,7 @@ async function generatePolymorphicResultClass( baseLines.push(COPYRIGHT); baseLines.push(""); baseLines.push(AUTO_GENERATED_HEADER); - baseLines.push(GENERATED_FROM_API); + baseLines.push(headerComment); baseLines.push(""); baseLines.push(`package ${packageName};`); baseLines.push(""); @@ -610,7 +652,7 @@ async function generatePolymorphicResultClass( // Generate each variant subclass for (const variant of variantInfos) { - await generatePolymorphicVariantClass(variant.variantClassName, variant.schema, variant.discriminatorValue, discriminator.property, className, packageName, packageDir); + await generatePolymorphicVariantClass(variant.variantClassName, variant.schema, variant.discriminatorValue, discriminator.property, className, packageName, packageDir, headerComment); } } @@ -624,7 +666,8 @@ async function generatePolymorphicVariantClass( discriminatorProperty: string, baseClassName: string, packageName: string, - packageDir: string + packageDir: string, + headerComment = GENERATED_FROM_API ): Promise { const allImports = new Set([ "com.fasterxml.jackson.annotation.JsonIgnoreProperties", @@ -663,7 +706,7 @@ async function generatePolymorphicVariantClass( lines.push(COPYRIGHT); lines.push(""); lines.push(AUTO_GENERATED_HEADER); - lines.push(GENERATED_FROM_API); + lines.push(headerComment); lines.push(""); lines.push(`package ${packageName};`); lines.push(""); @@ -930,6 +973,31 @@ interface EventVariant { deprecated?: boolean; } +const LEGACY_FACTORY_EVENT_VARIANTS: EventVariant[] = [ + { + typeName: "factory.run_updated", + className: "FactoryRunUpdatedEvent", + dataSchema: null, + }, + { + typeName: "factory.run_started", + className: "FactoryRunStartedEvent", + dataSchema: null, + }, + { + typeName: "factory.run_settled", + className: "FactoryRunSettledEvent", + dataSchema: null, + }, +]; + +const LEGACY_FACTORY_EVENT_FILES = [ + "FactoryRunUpdatedEvent.java", + "FactoryRunStartedEvent.java", + "FactoryRunSettledEvent.java", + "FactoryRunSettledStatus.java", +]; + function extractEventVariants(schema: JSONSchema7): EventVariant[] { const definitions = schema.definitions as Record; const sessionEvent = definitions?.SessionEvent; @@ -974,18 +1042,36 @@ async function generateSessionEvents(schemaPath: string): Promise { // Set module-level definitions for $ref resolution currentDefinitions = (schema.definitions ?? {}) as Record; pendingStandaloneTypes.clear(); + promotedNestedUnionTypes.clear(); const variants = extractEventVariants(schema); + const legacyVariants = variants.some((variant) => variant.typeName === "workflow.run_updated") + ? LEGACY_FACTORY_EVENT_VARIANTS.filter( + (legacy) => !variants.some((variant) => variant.typeName === legacy.typeName) + ) + : []; + for (const variant of variants) { + for (const typeName of collectReferencedDiscriminatedUnionTypeNames(variant.dataSchema, currentDefinitions)) { + if (SESSION_EVENT_PROMOTED_UNION_TYPES.has(typeName)) { + promotedNestedUnionTypes.add(typeName); + } + } + } const packageName = "com.github.copilot.generated"; const packageDir = `sdk/src/generated/java/com/github/copilot/generated`; // Generate base SessionEvent class - await generateSessionEventBaseClass(variants, packageName, packageDir); + await generateSessionEventBaseClass([...variants, ...legacyVariants], packageName, packageDir); // Generate one class file per event variant for (const variant of variants) { await generateEventVariantClass(variant, packageName, packageDir); } + for (const fileName of legacyVariants.length > 0 ? LEGACY_FACTORY_EVENT_FILES : []) { + const relativePath = `${packageDir}/${fileName}`; + const content = await fs.readFile(path.join(REPO_ROOT, relativePath), "utf-8"); + await writeGeneratedFile(relativePath, content); + } // Generate standalone types discovered via $ref resolution await generatePendingStandaloneTypes(packageName, packageDir, GENERATED_FROM_SESSION_EVENTS); @@ -1416,7 +1502,7 @@ async function generatePendingStandaloneTypes( } else if (schema.anyOf && Array.isArray(schema.anyOf)) { const variants = resolveAnyOfVariants(schema.anyOf as JSONSchema7[]); if (variants.length > 1 && findDiscriminator(variants)) { - await generatePolymorphicResultClass(name, schema, packageName, packageDir); + await generatePolymorphicResultClass(name, schema, packageName, packageDir, headerComment); } else { console.warn(`[codegen] Cannot generate standalone type for ${name}: anyOf without discriminator`); } diff --git a/java/sdk/src/generated/java/com/github/copilot/generated/SessionEvent.java b/java/sdk/src/generated/java/com/github/copilot/generated/SessionEvent.java index b8419f2ab0..252fb42df2 100644 --- a/java/sdk/src/generated/java/com/github/copilot/generated/SessionEvent.java +++ b/java/sdk/src/generated/java/com/github/copilot/generated/SessionEvent.java @@ -153,9 +153,9 @@ @JsonSubTypes.Type(value = ExitPlanModeCompletedEvent.class, name = "exit_plan_mode.completed"), @JsonSubTypes.Type(value = SessionToolsUpdatedEvent.class, name = "session.tools_updated"), @JsonSubTypes.Type(value = SessionBackgroundTasksChangedEvent.class, name = "session.background_tasks_changed"), - @JsonSubTypes.Type(value = FactoryRunUpdatedEvent.class, name = "factory.run_updated"), - @JsonSubTypes.Type(value = FactoryRunStartedEvent.class, name = "factory.run_started"), - @JsonSubTypes.Type(value = FactoryRunSettledEvent.class, name = "factory.run_settled"), + @JsonSubTypes.Type(value = WorkflowRunUpdatedEvent.class, name = "workflow.run_updated"), + @JsonSubTypes.Type(value = WorkflowRunStartedEvent.class, name = "workflow.run_started"), + @JsonSubTypes.Type(value = WorkflowRunSettledEvent.class, name = "workflow.run_settled"), @JsonSubTypes.Type(value = SessionSkillsLoadedEvent.class, name = "session.skills_loaded"), @JsonSubTypes.Type(value = SessionCustomAgentsUpdatedEvent.class, name = "session.custom_agents_updated"), @JsonSubTypes.Type(value = SessionMcpServersLoadedEvent.class, name = "session.mcp_servers_loaded"), @@ -173,7 +173,10 @@ @JsonSubTypes.Type(value = SessionCanvasRecordedEvent.class, name = "session.canvas.recorded"), @JsonSubTypes.Type(value = SessionCanvasRemovedEvent.class, name = "session.canvas.removed"), @JsonSubTypes.Type(value = SessionExtensionsAttachmentsPushedEvent.class, name = "session.extensions.attachments_pushed"), - @JsonSubTypes.Type(value = McpAppToolCallCompleteEvent.class, name = "mcp_app.tool_call_complete") + @JsonSubTypes.Type(value = McpAppToolCallCompleteEvent.class, name = "mcp_app.tool_call_complete"), + @JsonSubTypes.Type(value = FactoryRunUpdatedEvent.class, name = "factory.run_updated"), + @JsonSubTypes.Type(value = FactoryRunStartedEvent.class, name = "factory.run_started"), + @JsonSubTypes.Type(value = FactoryRunSettledEvent.class, name = "factory.run_settled") }) @javax.annotation.processing.Generated("copilot-sdk-codegen") public abstract sealed class SessionEvent permits @@ -305,9 +308,9 @@ public abstract sealed class SessionEvent permits ExitPlanModeCompletedEvent, SessionToolsUpdatedEvent, SessionBackgroundTasksChangedEvent, - FactoryRunUpdatedEvent, - FactoryRunStartedEvent, - FactoryRunSettledEvent, + WorkflowRunUpdatedEvent, + WorkflowRunStartedEvent, + WorkflowRunSettledEvent, SessionSkillsLoadedEvent, SessionCustomAgentsUpdatedEvent, SessionMcpServersLoadedEvent, @@ -326,6 +329,9 @@ public abstract sealed class SessionEvent permits SessionCanvasRemovedEvent, SessionExtensionsAttachmentsPushedEvent, McpAppToolCallCompleteEvent, + FactoryRunUpdatedEvent, + FactoryRunStartedEvent, + FactoryRunSettledEvent, UnknownSessionEvent { /** Unique event identifier (UUID v4), generated when the event is emitted. */ diff --git a/java/sdk/src/generated/java/com/github/copilot/generated/SystemNotification.java b/java/sdk/src/generated/java/com/github/copilot/generated/SystemNotification.java new file mode 100644 index 0000000000..938ebe8377 --- /dev/null +++ b/java/sdk/src/generated/java/com/github/copilot/generated/SystemNotification.java @@ -0,0 +1,41 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + *--------------------------------------------------------------------------------------------*/ + +// AUTO-GENERATED FILE - DO NOT EDIT +// Generated from: session-events.schema.json + +package com.github.copilot.generated; + +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import com.fasterxml.jackson.annotation.JsonSubTypes; +import com.fasterxml.jackson.annotation.JsonTypeInfo; +import javax.annotation.processing.Generated; + +/** + * Structured metadata identifying what triggered this notification + * + * @since 1.0.0 + */ +@JsonTypeInfo(use = JsonTypeInfo.Id.NAME, include = JsonTypeInfo.As.EXISTING_PROPERTY, property = "type", visible = true) +@JsonSubTypes({ + @JsonSubTypes.Type(value = SystemNotificationAgentCompleted.class, name = "agent_completed"), + @JsonSubTypes.Type(value = SystemNotificationAgentIdle.class, name = "agent_idle"), + @JsonSubTypes.Type(value = SystemNotificationNewInboxMessage.class, name = "new_inbox_message"), + @JsonSubTypes.Type(value = SystemNotificationShellCompleted.class, name = "shell_completed"), + @JsonSubTypes.Type(value = SystemNotificationShellDetachedCompleted.class, name = "shell_detached_completed"), + @JsonSubTypes.Type(value = SystemNotificationInstructionDiscovered.class, name = "instruction_discovered"), + @JsonSubTypes.Type(value = SystemNotificationWorkflowCompleted.class, name = "workflow_completed"), + @JsonSubTypes.Type(value = SystemNotificationUnclassified.class, name = "unclassified") +}) +@JsonIgnoreProperties(ignoreUnknown = true) +@javax.annotation.processing.Generated("copilot-sdk-codegen") +public abstract class SystemNotification { + + /** + * Returns the discriminator value for this variant. + * + * @return the type discriminator + */ + public abstract String getType(); +} diff --git a/java/sdk/src/generated/java/com/github/copilot/generated/SystemNotificationAgentCompleted.java b/java/sdk/src/generated/java/com/github/copilot/generated/SystemNotificationAgentCompleted.java new file mode 100644 index 0000000000..e5ead3c07f --- /dev/null +++ b/java/sdk/src/generated/java/com/github/copilot/generated/SystemNotificationAgentCompleted.java @@ -0,0 +1,72 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + *--------------------------------------------------------------------------------------------*/ + +// AUTO-GENERATED FILE - DO NOT EDIT +// Generated from: session-events.schema.json + +package com.github.copilot.generated; + +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import com.fasterxml.jackson.annotation.JsonInclude; +import com.fasterxml.jackson.annotation.JsonProperty; +import javax.annotation.processing.Generated; + +/** + * System notification metadata for a background agent that completed or failed, including agent ID, type, status, description, and prompt. + * + * @since 1.0.0 + */ +@JsonIgnoreProperties(ignoreUnknown = true) +@JsonInclude(JsonInclude.Include.NON_NULL) +@javax.annotation.processing.Generated("copilot-sdk-codegen") +public final class SystemNotificationAgentCompleted extends SystemNotification { + + @JsonProperty("type") + private final String type = "agent_completed"; + + @Override + public String getType() { return type; } + + /** Unique task identifier */ + @JsonProperty("agentId") + private String agentId; + + /** Friendly, non-unique name intended for display */ + @JsonProperty("displayName") + private String displayName; + + /** Type of the agent (e.g., explore, task, general-purpose) */ + @JsonProperty("agentType") + private String agentType; + + /** Whether the agent completed successfully or failed */ + @JsonProperty("status") + private SystemNotificationAgentCompletedStatus status; + + /** Human-readable description of the agent task */ + @JsonProperty("description") + private String description; + + /** The full prompt given to the background agent */ + @JsonProperty("prompt") + private String prompt; + + public String getAgentId() { return agentId; } + public void setAgentId(String agentId) { this.agentId = agentId; } + + public String getDisplayName() { return displayName; } + public void setDisplayName(String displayName) { this.displayName = displayName; } + + public String getAgentType() { return agentType; } + public void setAgentType(String agentType) { this.agentType = agentType; } + + public SystemNotificationAgentCompletedStatus getStatus() { return status; } + public void setStatus(SystemNotificationAgentCompletedStatus status) { this.status = status; } + + public String getDescription() { return description; } + public void setDescription(String description) { this.description = description; } + + public String getPrompt() { return prompt; } + public void setPrompt(String prompt) { this.prompt = prompt; } +} diff --git a/java/sdk/src/generated/java/com/github/copilot/generated/SystemNotificationAgentCompletedStatus.java b/java/sdk/src/generated/java/com/github/copilot/generated/SystemNotificationAgentCompletedStatus.java new file mode 100644 index 0000000000..a78d83e3e4 --- /dev/null +++ b/java/sdk/src/generated/java/com/github/copilot/generated/SystemNotificationAgentCompletedStatus.java @@ -0,0 +1,35 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + *--------------------------------------------------------------------------------------------*/ + +// AUTO-GENERATED FILE - DO NOT EDIT +// Generated from: session-events.schema.json + +package com.github.copilot.generated; + +import javax.annotation.processing.Generated; + +/** + * Whether the agent completed successfully or failed + * + * @since 1.0.0 + */ +@javax.annotation.processing.Generated("copilot-sdk-codegen") +public enum SystemNotificationAgentCompletedStatus { + /** The {@code completed} variant. */ + COMPLETED("completed"), + /** The {@code failed} variant. */ + FAILED("failed"); + + private final String value; + SystemNotificationAgentCompletedStatus(String value) { this.value = value; } + @com.fasterxml.jackson.annotation.JsonValue + public String getValue() { return value; } + @com.fasterxml.jackson.annotation.JsonCreator + public static SystemNotificationAgentCompletedStatus fromValue(String value) { + for (SystemNotificationAgentCompletedStatus v : values()) { + if (v.value.equals(value)) return v; + } + throw new IllegalArgumentException("Unknown SystemNotificationAgentCompletedStatus value: " + value); + } +} diff --git a/java/sdk/src/generated/java/com/github/copilot/generated/SystemNotificationAgentIdle.java b/java/sdk/src/generated/java/com/github/copilot/generated/SystemNotificationAgentIdle.java new file mode 100644 index 0000000000..4b2dd68f47 --- /dev/null +++ b/java/sdk/src/generated/java/com/github/copilot/generated/SystemNotificationAgentIdle.java @@ -0,0 +1,58 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + *--------------------------------------------------------------------------------------------*/ + +// AUTO-GENERATED FILE - DO NOT EDIT +// Generated from: session-events.schema.json + +package com.github.copilot.generated; + +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import com.fasterxml.jackson.annotation.JsonInclude; +import com.fasterxml.jackson.annotation.JsonProperty; +import javax.annotation.processing.Generated; + +/** + * System notification metadata for a background agent that became idle, including agent ID, type, and description. + * + * @since 1.0.0 + */ +@JsonIgnoreProperties(ignoreUnknown = true) +@JsonInclude(JsonInclude.Include.NON_NULL) +@javax.annotation.processing.Generated("copilot-sdk-codegen") +public final class SystemNotificationAgentIdle extends SystemNotification { + + @JsonProperty("type") + private final String type = "agent_idle"; + + @Override + public String getType() { return type; } + + /** Unique task identifier */ + @JsonProperty("agentId") + private String agentId; + + /** Friendly, non-unique name intended for display */ + @JsonProperty("displayName") + private String displayName; + + /** Type of the agent (e.g., explore, task, general-purpose) */ + @JsonProperty("agentType") + private String agentType; + + /** Human-readable description of the agent task */ + @JsonProperty("description") + private String description; + + public String getAgentId() { return agentId; } + public void setAgentId(String agentId) { this.agentId = agentId; } + + public String getDisplayName() { return displayName; } + public void setDisplayName(String displayName) { this.displayName = displayName; } + + public String getAgentType() { return agentType; } + public void setAgentType(String agentType) { this.agentType = agentType; } + + public String getDescription() { return description; } + public void setDescription(String description) { this.description = description; } +} diff --git a/java/sdk/src/generated/java/com/github/copilot/generated/SystemNotificationEvent.java b/java/sdk/src/generated/java/com/github/copilot/generated/SystemNotificationEvent.java index 9b8be8c060..2e671c3cd1 100644 --- a/java/sdk/src/generated/java/com/github/copilot/generated/SystemNotificationEvent.java +++ b/java/sdk/src/generated/java/com/github/copilot/generated/SystemNotificationEvent.java @@ -40,12 +40,12 @@ public record SystemNotificationEventData( /** The notification text, typically wrapped in XML tags */ @JsonProperty("content") String content, /** Structured metadata identifying what triggered this notification */ - @JsonProperty("kind") Object kind, - /** Responses reasoning settings anchored before this model-facing message, for cache-stable history replay */ + @JsonProperty("kind") SystemNotification kind, + /** Provider reasoning settings anchored before this model-facing message for cache-stable replay; the historical responsesReasoning name is retained for compatibility */ @JsonProperty("responsesReasoning") ResponsesReasoning responsesReasoning ) { /** Creates a value without optional worker diagnostics. */ - public SystemNotificationEventData(String content, Object kind, ResponsesReasoning responsesReasoning) { + public SystemNotificationEventData(String content, SystemNotification kind, ResponsesReasoning responsesReasoning) { this(null, content, kind, responsesReasoning); } } diff --git a/java/sdk/src/generated/java/com/github/copilot/generated/SystemNotificationInstructionDiscovered.java b/java/sdk/src/generated/java/com/github/copilot/generated/SystemNotificationInstructionDiscovered.java new file mode 100644 index 0000000000..f416764042 --- /dev/null +++ b/java/sdk/src/generated/java/com/github/copilot/generated/SystemNotificationInstructionDiscovered.java @@ -0,0 +1,58 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + *--------------------------------------------------------------------------------------------*/ + +// AUTO-GENERATED FILE - DO NOT EDIT +// Generated from: session-events.schema.json + +package com.github.copilot.generated; + +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import com.fasterxml.jackson.annotation.JsonInclude; +import com.fasterxml.jackson.annotation.JsonProperty; +import javax.annotation.processing.Generated; + +/** + * System notification metadata for an instruction file discovered during tool access, including source, trigger file, and tool. + * + * @since 1.0.0 + */ +@JsonIgnoreProperties(ignoreUnknown = true) +@JsonInclude(JsonInclude.Include.NON_NULL) +@javax.annotation.processing.Generated("copilot-sdk-codegen") +public final class SystemNotificationInstructionDiscovered extends SystemNotification { + + @JsonProperty("type") + private final String type = "instruction_discovered"; + + @Override + public String getType() { return type; } + + /** Relative path to the discovered instruction file */ + @JsonProperty("sourcePath") + private String sourcePath; + + /** Path of the file access that triggered discovery */ + @JsonProperty("triggerFile") + private String triggerFile; + + /** Tool command that triggered discovery (currently always 'view') */ + @JsonProperty("triggerTool") + private String triggerTool; + + /** Human-readable label for the timeline (e.g., 'AGENTS.md from packages/billing/') */ + @JsonProperty("description") + private String description; + + public String getSourcePath() { return sourcePath; } + public void setSourcePath(String sourcePath) { this.sourcePath = sourcePath; } + + public String getTriggerFile() { return triggerFile; } + public void setTriggerFile(String triggerFile) { this.triggerFile = triggerFile; } + + public String getTriggerTool() { return triggerTool; } + public void setTriggerTool(String triggerTool) { this.triggerTool = triggerTool; } + + public String getDescription() { return description; } + public void setDescription(String description) { this.description = description; } +} diff --git a/java/sdk/src/generated/java/com/github/copilot/generated/SystemNotificationNewInboxMessage.java b/java/sdk/src/generated/java/com/github/copilot/generated/SystemNotificationNewInboxMessage.java new file mode 100644 index 0000000000..619a66b813 --- /dev/null +++ b/java/sdk/src/generated/java/com/github/copilot/generated/SystemNotificationNewInboxMessage.java @@ -0,0 +1,58 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + *--------------------------------------------------------------------------------------------*/ + +// AUTO-GENERATED FILE - DO NOT EDIT +// Generated from: session-events.schema.json + +package com.github.copilot.generated; + +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import com.fasterxml.jackson.annotation.JsonInclude; +import com.fasterxml.jackson.annotation.JsonProperty; +import javax.annotation.processing.Generated; + +/** + * System notification metadata for a new inbox message, including entry ID, sender details, and summary. + * + * @since 1.0.0 + */ +@JsonIgnoreProperties(ignoreUnknown = true) +@JsonInclude(JsonInclude.Include.NON_NULL) +@javax.annotation.processing.Generated("copilot-sdk-codegen") +public final class SystemNotificationNewInboxMessage extends SystemNotification { + + @JsonProperty("type") + private final String type = "new_inbox_message"; + + @Override + public String getType() { return type; } + + /** Unique identifier of the inbox entry */ + @JsonProperty("entryId") + private String entryId; + + /** Human-readable name of the sender */ + @JsonProperty("senderName") + private String senderName; + + /** Category of the sender (e.g., sidekick-agent, plugin, hook) */ + @JsonProperty("senderType") + private String senderType; + + /** Short summary shown before the agent decides whether to read the inbox */ + @JsonProperty("summary") + private String summary; + + public String getEntryId() { return entryId; } + public void setEntryId(String entryId) { this.entryId = entryId; } + + public String getSenderName() { return senderName; } + public void setSenderName(String senderName) { this.senderName = senderName; } + + public String getSenderType() { return senderType; } + public void setSenderType(String senderType) { this.senderType = senderType; } + + public String getSummary() { return summary; } + public void setSummary(String summary) { this.summary = summary; } +} diff --git a/java/sdk/src/generated/java/com/github/copilot/generated/SystemNotificationShellCompleted.java b/java/sdk/src/generated/java/com/github/copilot/generated/SystemNotificationShellCompleted.java new file mode 100644 index 0000000000..1d432d66dc --- /dev/null +++ b/java/sdk/src/generated/java/com/github/copilot/generated/SystemNotificationShellCompleted.java @@ -0,0 +1,51 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + *--------------------------------------------------------------------------------------------*/ + +// AUTO-GENERATED FILE - DO NOT EDIT +// Generated from: session-events.schema.json + +package com.github.copilot.generated; + +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import com.fasterxml.jackson.annotation.JsonInclude; +import com.fasterxml.jackson.annotation.JsonProperty; +import javax.annotation.processing.Generated; + +/** + * System notification metadata for a shell session that completed, including shell ID, optional exit code, and description. + * + * @since 1.0.0 + */ +@JsonIgnoreProperties(ignoreUnknown = true) +@JsonInclude(JsonInclude.Include.NON_NULL) +@javax.annotation.processing.Generated("copilot-sdk-codegen") +public final class SystemNotificationShellCompleted extends SystemNotification { + + @JsonProperty("type") + private final String type = "shell_completed"; + + @Override + public String getType() { return type; } + + /** Unique identifier of the shell session */ + @JsonProperty("shellId") + private String shellId; + + /** Exit code of the shell command, if available */ + @JsonProperty("exitCode") + private Long exitCode; + + /** Human-readable description of the command */ + @JsonProperty("description") + private String description; + + public String getShellId() { return shellId; } + public void setShellId(String shellId) { this.shellId = shellId; } + + public Long getExitCode() { return exitCode; } + public void setExitCode(Long exitCode) { this.exitCode = exitCode; } + + public String getDescription() { return description; } + public void setDescription(String description) { this.description = description; } +} diff --git a/java/sdk/src/generated/java/com/github/copilot/generated/SystemNotificationShellDetachedCompleted.java b/java/sdk/src/generated/java/com/github/copilot/generated/SystemNotificationShellDetachedCompleted.java new file mode 100644 index 0000000000..c5ab7959a1 --- /dev/null +++ b/java/sdk/src/generated/java/com/github/copilot/generated/SystemNotificationShellDetachedCompleted.java @@ -0,0 +1,44 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + *--------------------------------------------------------------------------------------------*/ + +// AUTO-GENERATED FILE - DO NOT EDIT +// Generated from: session-events.schema.json + +package com.github.copilot.generated; + +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import com.fasterxml.jackson.annotation.JsonInclude; +import com.fasterxml.jackson.annotation.JsonProperty; +import javax.annotation.processing.Generated; + +/** + * System notification metadata for a detached shell session that completed, including shell ID and description. + * + * @since 1.0.0 + */ +@JsonIgnoreProperties(ignoreUnknown = true) +@JsonInclude(JsonInclude.Include.NON_NULL) +@javax.annotation.processing.Generated("copilot-sdk-codegen") +public final class SystemNotificationShellDetachedCompleted extends SystemNotification { + + @JsonProperty("type") + private final String type = "shell_detached_completed"; + + @Override + public String getType() { return type; } + + /** Unique identifier of the detached shell session */ + @JsonProperty("shellId") + private String shellId; + + /** Human-readable description of the command */ + @JsonProperty("description") + private String description; + + public String getShellId() { return shellId; } + public void setShellId(String shellId) { this.shellId = shellId; } + + public String getDescription() { return description; } + public void setDescription(String description) { this.description = description; } +} diff --git a/java/sdk/src/generated/java/com/github/copilot/generated/SystemNotificationUnclassified.java b/java/sdk/src/generated/java/com/github/copilot/generated/SystemNotificationUnclassified.java new file mode 100644 index 0000000000..c37f0b8435 --- /dev/null +++ b/java/sdk/src/generated/java/com/github/copilot/generated/SystemNotificationUnclassified.java @@ -0,0 +1,37 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + *--------------------------------------------------------------------------------------------*/ + +// AUTO-GENERATED FILE - DO NOT EDIT +// Generated from: session-events.schema.json + +package com.github.copilot.generated; + +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import com.fasterxml.jackson.annotation.JsonInclude; +import com.fasterxml.jackson.annotation.JsonProperty; +import javax.annotation.processing.Generated; + +/** + * System notification metadata from an external host that does not match a runtime-owned notification kind. + * + * @since 1.0.0 + */ +@JsonIgnoreProperties(ignoreUnknown = true) +@JsonInclude(JsonInclude.Include.NON_NULL) +@javax.annotation.processing.Generated("copilot-sdk-codegen") +public final class SystemNotificationUnclassified extends SystemNotification { + + @JsonProperty("type") + private final String type = "unclassified"; + + @Override + public String getType() { return type; } + + /** Opaque metadata supplied by the external host, when present. */ + @JsonProperty("metadata") + private Object metadata; + + public Object getMetadata() { return metadata; } + public void setMetadata(Object metadata) { this.metadata = metadata; } +} diff --git a/java/sdk/src/generated/java/com/github/copilot/generated/SystemNotificationWorkflowCompleted.java b/java/sdk/src/generated/java/com/github/copilot/generated/SystemNotificationWorkflowCompleted.java new file mode 100644 index 0000000000..c9c3121318 --- /dev/null +++ b/java/sdk/src/generated/java/com/github/copilot/generated/SystemNotificationWorkflowCompleted.java @@ -0,0 +1,107 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + *--------------------------------------------------------------------------------------------*/ + +// AUTO-GENERATED FILE - DO NOT EDIT +// Generated from: session-events.schema.json + +package com.github.copilot.generated; + +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import com.fasterxml.jackson.annotation.JsonInclude; +import com.fasterxml.jackson.annotation.JsonProperty; +import javax.annotation.processing.Generated; + +/** + * System notification metadata for a workflow execution attempt that reached a terminal state. + * + * @since 1.0.0 + */ +@JsonIgnoreProperties(ignoreUnknown = true) +@JsonInclude(JsonInclude.Include.NON_NULL) +@javax.annotation.processing.Generated("copilot-sdk-codegen") +public final class SystemNotificationWorkflowCompleted extends SystemNotification { + + @JsonProperty("type") + private final String type = "workflow_completed"; + + @Override + public String getType() { return type; } + + /** Workflow run identifier. */ + @JsonProperty("runId") + private String runId; + + /** Persisted workflow name. */ + @JsonProperty("workflowName") + private String workflowName; + + /** Terminal status reached by this execution attempt. */ + @JsonProperty("status") + private SystemNotificationWorkflowCompletedStatus status; + + /** Subagents consumed by the run across all attempts. */ + @JsonProperty("consumedSubagents") + private Long consumedSubagents; + + /** Accumulated active execution time in milliseconds. */ + @JsonProperty("elapsedMs") + private Long elapsedMs; + + /** Consumed AI usage in nano-AIU. */ + @JsonProperty("consumedNanoAiu") + private Long consumedNanoAiu; + + /** Execution attempt that reached this terminal state. */ + @JsonProperty("attempt") + private Long attempt; + + /** Bounded prompt-safe preview of the completed result. */ + @JsonProperty("resultPreview") + private String resultPreview; + + /** Machine-readable terminal failure details, when present. */ + @JsonProperty("failure") + private Object failure; + + /** Actionable run_dynamic_workflow resume guidance for a resource-limit failure. */ + @JsonProperty("retryGuidance") + private String retryGuidance; + + /** Pause initiator metadata when this attempt settled as paused. */ + @JsonProperty("pauseInfo") + private Object pauseInfo; + + public String getRunId() { return runId; } + public void setRunId(String runId) { this.runId = runId; } + + public String getWorkflowName() { return workflowName; } + public void setWorkflowName(String workflowName) { this.workflowName = workflowName; } + + public SystemNotificationWorkflowCompletedStatus getStatus() { return status; } + public void setStatus(SystemNotificationWorkflowCompletedStatus status) { this.status = status; } + + public Long getConsumedSubagents() { return consumedSubagents; } + public void setConsumedSubagents(Long consumedSubagents) { this.consumedSubagents = consumedSubagents; } + + public Long getElapsedMs() { return elapsedMs; } + public void setElapsedMs(Long elapsedMs) { this.elapsedMs = elapsedMs; } + + public Long getConsumedNanoAiu() { return consumedNanoAiu; } + public void setConsumedNanoAiu(Long consumedNanoAiu) { this.consumedNanoAiu = consumedNanoAiu; } + + public Long getAttempt() { return attempt; } + public void setAttempt(Long attempt) { this.attempt = attempt; } + + public String getResultPreview() { return resultPreview; } + public void setResultPreview(String resultPreview) { this.resultPreview = resultPreview; } + + public Object getFailure() { return failure; } + public void setFailure(Object failure) { this.failure = failure; } + + public String getRetryGuidance() { return retryGuidance; } + public void setRetryGuidance(String retryGuidance) { this.retryGuidance = retryGuidance; } + + public Object getPauseInfo() { return pauseInfo; } + public void setPauseInfo(Object pauseInfo) { this.pauseInfo = pauseInfo; } +} diff --git a/java/sdk/src/generated/java/com/github/copilot/generated/SystemNotificationWorkflowCompletedStatus.java b/java/sdk/src/generated/java/com/github/copilot/generated/SystemNotificationWorkflowCompletedStatus.java new file mode 100644 index 0000000000..d53b09f135 --- /dev/null +++ b/java/sdk/src/generated/java/com/github/copilot/generated/SystemNotificationWorkflowCompletedStatus.java @@ -0,0 +1,41 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + *--------------------------------------------------------------------------------------------*/ + +// AUTO-GENERATED FILE - DO NOT EDIT +// Generated from: session-events.schema.json + +package com.github.copilot.generated; + +import javax.annotation.processing.Generated; + +/** + * Terminal status reached by a workflow execution attempt. + * + * @since 1.0.0 + */ +@javax.annotation.processing.Generated("copilot-sdk-codegen") +public enum SystemNotificationWorkflowCompletedStatus { + /** The {@code completed} variant. */ + COMPLETED("completed"), + /** The {@code halted} variant. */ + HALTED("halted"), + /** The {@code paused} variant. */ + PAUSED("paused"), + /** The {@code cancelled} variant. */ + CANCELLED("cancelled"), + /** The {@code error} variant. */ + ERROR("error"); + + private final String value; + SystemNotificationWorkflowCompletedStatus(String value) { this.value = value; } + @com.fasterxml.jackson.annotation.JsonValue + public String getValue() { return value; } + @com.fasterxml.jackson.annotation.JsonCreator + public static SystemNotificationWorkflowCompletedStatus fromValue(String value) { + for (SystemNotificationWorkflowCompletedStatus v : values()) { + if (v.value.equals(value)) return v; + } + throw new IllegalArgumentException("Unknown SystemNotificationWorkflowCompletedStatus value: " + value); + } +} diff --git a/java/sdk/src/generated/java/com/github/copilot/generated/WorkflowRunSettledEvent.java b/java/sdk/src/generated/java/com/github/copilot/generated/WorkflowRunSettledEvent.java new file mode 100644 index 0000000000..7cfef0df02 --- /dev/null +++ b/java/sdk/src/generated/java/com/github/copilot/generated/WorkflowRunSettledEvent.java @@ -0,0 +1,51 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + *--------------------------------------------------------------------------------------------*/ + +// AUTO-GENERATED FILE - DO NOT EDIT +// Generated from: session-events.schema.json + +package com.github.copilot.generated; + +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import com.fasterxml.jackson.annotation.JsonInclude; +import com.fasterxml.jackson.annotation.JsonProperty; +import javax.annotation.processing.Generated; + +/** + * Session event "workflow.run_settled". Ephemeral signal that a workflow run reached a terminal status. + * @since 1.0.0 + */ +@JsonIgnoreProperties(ignoreUnknown = true) +@JsonInclude(JsonInclude.Include.NON_NULL) +@javax.annotation.processing.Generated("copilot-sdk-codegen") +public final class WorkflowRunSettledEvent extends SessionEvent { + + @Override + public String getType() { return "workflow.run_settled"; } + + @JsonProperty("data") + private WorkflowRunSettledEventData data; + + public WorkflowRunSettledEventData getData() { return data; } + public void setData(WorkflowRunSettledEventData data) { this.data = data; } + + /** Data payload for {@link WorkflowRunSettledEvent}. */ + @JsonIgnoreProperties(ignoreUnknown = true) + @JsonInclude(JsonInclude.Include.NON_NULL) + public record WorkflowRunSettledEventData( + /** Identifier of the workflow run that settled. */ + @JsonProperty("runId") String runId, + /** Terminal status the run committed. */ + @JsonProperty("status") WorkflowRunSettledStatus status, + /** Subagents this run consumed against its limits. */ + @JsonProperty("consumedSubagents") Long consumedSubagents, + /** AI credits this run consumed, in nano-AIU. */ + @JsonProperty("consumedNanoAiu") Long consumedNanoAiu, + /** Active milliseconds accumulated across every attempt of this run. */ + @JsonProperty("elapsedMs") Long elapsedMs, + /** Typed failure class recorded on the run, when it failed with one (e.g. `workflow_limit_reached`). */ + @JsonProperty("failureType") String failureType + ) { + } +} diff --git a/java/sdk/src/generated/java/com/github/copilot/generated/WorkflowRunSettledStatus.java b/java/sdk/src/generated/java/com/github/copilot/generated/WorkflowRunSettledStatus.java new file mode 100644 index 0000000000..05dee5d53a --- /dev/null +++ b/java/sdk/src/generated/java/com/github/copilot/generated/WorkflowRunSettledStatus.java @@ -0,0 +1,41 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + *--------------------------------------------------------------------------------------------*/ + +// AUTO-GENERATED FILE - DO NOT EDIT +// Generated from: session-events.schema.json + +package com.github.copilot.generated; + +import javax.annotation.processing.Generated; + +/** + * Terminal status a workflow run committed. A settled run is never `pending` or `running`, so those two members of the run-status domain are deliberately absent. + * + * @since 1.0.0 + */ +@javax.annotation.processing.Generated("copilot-sdk-codegen") +public enum WorkflowRunSettledStatus { + /** The {@code completed} variant. */ + COMPLETED("completed"), + /** The {@code halted} variant. */ + HALTED("halted"), + /** The {@code paused} variant. */ + PAUSED("paused"), + /** The {@code cancelled} variant. */ + CANCELLED("cancelled"), + /** The {@code error} variant. */ + ERROR("error"); + + private final String value; + WorkflowRunSettledStatus(String value) { this.value = value; } + @com.fasterxml.jackson.annotation.JsonValue + public String getValue() { return value; } + @com.fasterxml.jackson.annotation.JsonCreator + public static WorkflowRunSettledStatus fromValue(String value) { + for (WorkflowRunSettledStatus v : values()) { + if (v.value.equals(value)) return v; + } + throw new IllegalArgumentException("Unknown WorkflowRunSettledStatus value: " + value); + } +} diff --git a/java/sdk/src/generated/java/com/github/copilot/generated/WorkflowRunStartedEvent.java b/java/sdk/src/generated/java/com/github/copilot/generated/WorkflowRunStartedEvent.java new file mode 100644 index 0000000000..656b51f596 --- /dev/null +++ b/java/sdk/src/generated/java/com/github/copilot/generated/WorkflowRunStartedEvent.java @@ -0,0 +1,45 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + *--------------------------------------------------------------------------------------------*/ + +// AUTO-GENERATED FILE - DO NOT EDIT +// Generated from: session-events.schema.json + +package com.github.copilot.generated; + +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import com.fasterxml.jackson.annotation.JsonInclude; +import com.fasterxml.jackson.annotation.JsonProperty; +import javax.annotation.processing.Generated; + +/** + * Session event "workflow.run_started". Ephemeral signal that a workflow run attempt began executing. + * @since 1.0.0 + */ +@JsonIgnoreProperties(ignoreUnknown = true) +@JsonInclude(JsonInclude.Include.NON_NULL) +@javax.annotation.processing.Generated("copilot-sdk-codegen") +public final class WorkflowRunStartedEvent extends SessionEvent { + + @Override + public String getType() { return "workflow.run_started"; } + + @JsonProperty("data") + private WorkflowRunStartedEventData data; + + public WorkflowRunStartedEventData getData() { return data; } + public void setData(WorkflowRunStartedEventData data) { this.data = data; } + + /** Data payload for {@link WorkflowRunStartedEvent}. */ + @JsonIgnoreProperties(ignoreUnknown = true) + @JsonInclude(JsonInclude.Include.NON_NULL) + public record WorkflowRunStartedEventData( + /** Identifier of the workflow run that started. */ + @JsonProperty("runId") String runId, + /** Name of the workflow this run executes. Low cardinality by construction. */ + @JsonProperty("workflowName") String workflowName, + /** Attempt number this start committed; a resumed run increments it. */ + @JsonProperty("attempt") Long attempt + ) { + } +} diff --git a/java/sdk/src/generated/java/com/github/copilot/generated/WorkflowRunUpdatedEvent.java b/java/sdk/src/generated/java/com/github/copilot/generated/WorkflowRunUpdatedEvent.java new file mode 100644 index 0000000000..6a51db49f6 --- /dev/null +++ b/java/sdk/src/generated/java/com/github/copilot/generated/WorkflowRunUpdatedEvent.java @@ -0,0 +1,43 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + *--------------------------------------------------------------------------------------------*/ + +// AUTO-GENERATED FILE - DO NOT EDIT +// Generated from: session-events.schema.json + +package com.github.copilot.generated; + +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import com.fasterxml.jackson.annotation.JsonInclude; +import com.fasterxml.jackson.annotation.JsonProperty; +import javax.annotation.processing.Generated; + +/** + * Session event "workflow.run_updated". Ephemeral invalidation signal for a changed workflow run. + * @since 1.0.0 + */ +@JsonIgnoreProperties(ignoreUnknown = true) +@JsonInclude(JsonInclude.Include.NON_NULL) +@javax.annotation.processing.Generated("copilot-sdk-codegen") +public final class WorkflowRunUpdatedEvent extends SessionEvent { + + @Override + public String getType() { return "workflow.run_updated"; } + + @JsonProperty("data") + private WorkflowRunUpdatedEventData data; + + public WorkflowRunUpdatedEventData getData() { return data; } + public void setData(WorkflowRunUpdatedEventData data) { this.data = data; } + + /** Data payload for {@link WorkflowRunUpdatedEvent}. */ + @JsonIgnoreProperties(ignoreUnknown = true) + @JsonInclude(JsonInclude.Include.NON_NULL) + public record WorkflowRunUpdatedEventData( + /** Workflow run identifier. */ + @JsonProperty("runId") String runId, + /** Monotonic revision now available for the run. */ + @JsonProperty("revision") Long revision + ) { + } +} diff --git a/java/sdk/src/main/java/com/github/copilot/WorkerCausalityDeserializer.java b/java/sdk/src/main/java/com/github/copilot/WorkerCausalityDeserializer.java index 0549302438..f2992a7bd8 100644 --- a/java/sdk/src/main/java/com/github/copilot/WorkerCausalityDeserializer.java +++ b/java/sdk/src/main/java/com/github/copilot/WorkerCausalityDeserializer.java @@ -23,7 +23,6 @@ public final class WorkerCausalityDeserializer extends JsonDeserializer implements ContextualDeserializer { private static final Logger LOG = Logger.getLogger(WorkerCausalityDeserializer.class.getName()); private static final Pattern UUID = Pattern.compile("[0-9a-fA-F]{8}-(?:[0-9a-fA-F]{4}-){3}[0-9a-fA-F]{12}"); - private static final Pattern SESSION = Pattern.compile("[A-Za-z0-9_-]+"); private final JavaType target; /** Creates the annotation's contextual decoder. */ @@ -139,7 +138,8 @@ private static boolean fields(JsonNode value, String... allowed) { private static boolean text(JsonNode value) { return value.isTextual() && !value.textValue().isEmpty() - && value.textValue().getBytes(StandardCharsets.UTF_8).length <= 256; + && value.textValue().getBytes(StandardCharsets.UTF_8).length <= 256 + && value.textValue().codePoints().noneMatch(Character::isISOControl); } private static boolean uuid(JsonNode value) { @@ -152,8 +152,8 @@ private static boolean optionalReference(JsonNode value, String field, String ev private static boolean reference(JsonNode value, String eventType) { return fields(value, "sessionId", "eventId", "agentId", "eventType", "provenance") - && text(value.path("sessionId")) && SESSION.matcher(value.path("sessionId").textValue()).matches() - && uuid(value.path("eventId")) && (!value.has("agentId") || text(value.path("agentId"))) + && text(value.path("sessionId")) && uuid(value.path("eventId")) + && (!value.has("agentId") || text(value.path("agentId"))) && value.path("eventType").asText().equals(eventType) && (value.path("provenance").asText().equals("native") || value.path("provenance").asText().equals("ahp_coordinator")); diff --git a/java/sdk/src/test/java/com/github/copilot/WorkerCausalityTest.java b/java/sdk/src/test/java/com/github/copilot/WorkerCausalityTest.java index 8aa50df749..9d35c26260 100644 --- a/java/sdk/src/test/java/com/github/copilot/WorkerCausalityTest.java +++ b/java/sdk/src/test/java/com/github/copilot/WorkerCausalityTest.java @@ -5,6 +5,7 @@ package com.github.copilot; import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertInstanceOf; import java.nio.file.Path; @@ -14,6 +15,9 @@ import com.fasterxml.jackson.databind.ObjectMapper; import com.fasterxml.jackson.databind.node.ObjectNode; import com.github.copilot.generated.SessionEvent; +import com.github.copilot.generated.SystemNotificationEvent; +import com.github.copilot.generated.SystemNotificationWorkflowCompleted; +import com.github.copilot.generated.SystemNotificationWorkflowCompletedStatus; import com.github.copilot.generated.rpc.SessionTasksSendMessageResult; class WorkerCausalityTest { @@ -63,4 +67,16 @@ void exactUtf8BudgetAndUnknownFields() throws Exception { assertEquals(payload(wire, true).get("content"), observed.get("content")); } } + + @Test + void canonicalWorkflowCompletionDecodesTypedFields() throws Exception { + SessionEvent parsed = MAPPER.treeToValue(corpus().path("workflowCompleted").path("event"), SessionEvent.class); + SystemNotificationEvent notification = assertInstanceOf(SystemNotificationEvent.class, parsed); + SystemNotificationWorkflowCompleted completion = assertInstanceOf(SystemNotificationWorkflowCompleted.class, + notification.getData().kind()); + assertEquals("fix-ci", completion.getWorkflowName()); + assertEquals("run-1", completion.getRunId()); + assertEquals(SystemNotificationWorkflowCompletedStatus.COMPLETED, completion.getStatus()); + assertEquals(1L, completion.getConsumedSubagents()); + } } diff --git a/nodejs/src/generated/session-events.ts b/nodejs/src/generated/session-events.ts index 1cad647008..95f35ce17a 100644 --- a/nodejs/src/generated/session-events.ts +++ b/nodejs/src/generated/session-events.ts @@ -130,9 +130,9 @@ export type SessionEvent = | ExitPlanModeCompletedEvent | ToolsUpdatedEvent | BackgroundTasksChangedEvent - | FactoryRunUpdatedEvent - | FactoryRunStartedEvent - | FactoryRunSettledEvent + | WorkflowRunUpdatedEvent + | WorkflowRunStartedEvent + | WorkflowRunSettledEvent | SkillsLoadedEvent | CustomAgentsUpdatedEvent | McpServersLoadedEvent @@ -443,7 +443,9 @@ export type ModelChangeSource = /** The user selected the promoted model from the changeboarding card or its keyboard shortcut. */ | "changeboarding_shortcut" /** An SDK or RPC caller selected the model. */ - | "sdk"; + | "sdk" + /** The user accepted a CAPI-issued Auto tier recommendation. */ + | "auto_tier_recommendation"; /** * Why the session no longer has an explicitly selected model. */ @@ -1090,7 +1092,7 @@ export type SystemNotification = | SystemNotificationShellCompleted | SystemNotificationShellDetachedCompleted | SystemNotificationInstructionDiscovered - | SystemNotificationFactoryCompleted + | SystemNotificationWorkflowCompleted | SystemNotificationUnclassified; /** * Whether the agent completed successfully or failed @@ -1101,12 +1103,12 @@ export type SystemNotificationAgentCompletedStatus = /** The agent failed. */ | "failed"; /** - * Durable metadata describing who initiated a factory pause. + * Durable metadata describing who initiated a workflow pause. */ -export type SystemNotificationFactoryPauseInfo = +export type SystemNotificationWorkflowPauseInfo = | { /** - * Factory pause initiator discriminator. + * Workflow pause initiator discriminator. */ type: "user"; } @@ -1116,23 +1118,23 @@ export type SystemNotificationFactoryPauseInfo = */ key: string; /** - * Factory pause initiator discriminator. + * Workflow pause initiator discriminator. */ type: "checkpoint"; }; /** - * Terminal status reached by a factory execution attempt. + * Terminal status reached by a workflow execution attempt. */ -export type SystemNotificationFactoryCompletedStatus = - /** The factory completed successfully. */ +export type SystemNotificationWorkflowCompletedStatus = + /** The workflow completed successfully. */ | "completed" - /** The factory was halted. */ + /** The workflow was halted. */ | "halted" - /** The factory attempt paused intentionally. */ + /** The workflow attempt paused intentionally. */ | "paused" - /** The factory was cancelled. */ + /** The workflow was cancelled. */ | "cancelled" - /** The factory failed. */ + /** The workflow failed. */ | "error"; /** * Details of the permission being requested @@ -1147,7 +1149,7 @@ export type PermissionRequest = | PermissionRequestCustomTool | PermissionRequestHook | PermissionRequestExtensionManagement - | PermissionRequestFactory + | PermissionRequestWorkflow | PermissionRequestExtensionPermissionAccess | PermissionRequestExtensionEnvAccess; /** @@ -1210,12 +1212,12 @@ export type PermissionRequestMemoryScope = /** Store the memory for the current user. */ | "user"; /** - * Operation gated by a factory permission request. + * Operation gated by a workflow permission request. */ -export type FactoryPermissionOperation = - /** Running a registered factory, which spends subagents, active time, and AI credits under the approved limits. */ +export type WorkflowPermissionOperation = + /** Running a registered workflow, which spends subagents, active time, and AI credits under the approved limits. */ | "run" - /** Authoring a factory, which writes JavaScript into a session-scoped extension and loads it. */ + /** Authoring a workflow, which writes JavaScript into a session-scoped extension and loads it. */ | "author"; /** * Derived user-facing permission prompt details for UI consumers @@ -1231,7 +1233,7 @@ export type PermissionPromptRequest = | PermissionPromptRequestPath | PermissionPromptRequestHook | PermissionPromptRequestExtensionManagement - | PermissionPromptRequestFactory + | PermissionPromptRequestWorkflow | PermissionPromptRequestExtensionPermissionAccess | PermissionPromptRequestExtensionEnvAccess; /** @@ -1256,7 +1258,7 @@ export type PermissionDecisionSource = | "host_policy" /** The host denied the request because no interactive user response was available. */ | "unattended_fallback" - /** A live authorization record from an earlier human decision in this session contained the proposal, so it ran without another prompt. This is not a new human decision and never mints authority of its own. */ + /** Historical compatibility value for sessions created while authorization carry-forward was executable. Current runtimes do not produce this source. */ | "authorization_carry_forward"; /** * The result of the permission request @@ -1282,17 +1284,17 @@ export type UserToolSessionApproval = | UserToolSessionApprovalMemory | UserToolSessionApprovalCustomTool | UserToolSessionApprovalExtensionManagement - | UserToolSessionApprovalFactory + | UserToolSessionApprovalWorkflow | UserToolSessionApprovalExtensionPermissionAccess | UserToolSessionApprovalExtensionEnvAccess; /** - * Which direction a message-backed authorization claim moves authority in. + * Direction stored in a historical extractor claim. Current runtimes do not apply it. */ /** @experimental */ export type PermissionMessageAuthorizationPolarity = - /** The human's words authorized an effect. */ + /** Historical claim recorded as a grant. */ | "grant" - /** The human's words refused an effect. */ + /** Historical claim recorded as a denial. */ | "denial"; /** * Elicitation mode; "form" for structured input, "url" for browser-based. Defaults to "form" when absent. @@ -1460,10 +1462,10 @@ export type ExitPlanModeAction = /** Exit plan mode and continue with parallel autonomous workers. */ | "autopilot_fleet"; /** - * Terminal status a factory run committed. A settled run is never `pending` or `running`, so those two members of the run-status domain are deliberately absent. + * Terminal status a workflow run committed. A settled run is never `pending` or `running`, so those two members of the run-status domain are deliberately absent. */ -export type FactoryRunSettledStatus = - /** The factory body resolved and its result was committed. */ +export type WorkflowRunSettledStatus = + /** The workflow body resolved and its result was committed. */ | "completed" /** The run was stopped by a limit, an approval refusal or another policy decision. */ | "halted" @@ -2938,12 +2940,16 @@ export interface SnapshotRewindEvent { * Session rewind details including target event and count of removed events */ export interface SnapshotRewindData { + /** + * The removed events, starting with `upToEventId`. Later events not listed were kept, such as a background agent's events that interleaved with a withdrawn turn + */ + eventIds?: string[]; /** * Number of events that were removed by the rewind */ eventsRemoved: number; /** - * Event ID that was rewound to; this event and all after it were removed + * First removed event. Without `eventIds`, it and every event after it were removed */ upToEventId: string; } @@ -3481,11 +3487,17 @@ export interface CompactionCompleteEvent { */ export interface CompactionCompleteData { /** - * Authoritative active-factory reminder appended to the compacted context + * Legacy active-workflow reminder retained for replay compatibility * * @internal */ activeFactorySummary?: string; + /** + * Authoritative active-workflow reminder appended to the compacted context + * + * @internal + */ + activeWorkflowSummary?: string; /** * Canonical model identifier used for model-specific behavior when replaying compaction */ @@ -3649,7 +3661,7 @@ export interface CompactionCompleteCompactionTokensUsedCopilotUsageTokenDetail { tokenType: string; } /** - * Original request-level and effective conversation reasoning effort for a Responses history boundary + * Original request-level and effective conversation reasoning effort for a provider history boundary; the historical type name is retained for compatibility */ export interface ResponsesReasoning { /** @@ -5866,6 +5878,10 @@ export interface FusionAttribution { * Stable identifier for the HydraFusion turn that produced the event. */ fusionId: string; + /** + * Whether this model request consumed a user steering message rather than only internal Fusion work. + */ + hasUserSteering?: boolean; /** * HydraFusion orchestration pattern selected for the turn. */ @@ -7803,7 +7819,7 @@ export interface SubagentStartedData { */ executionMode?: string; /** - * Root id of the factory run that spawned this sub-agent, when it was spawned by one. + * Legacy root id of the workflow run that spawned this sub-agent. New consumers should use workflowRunId. */ factoryRunId?: string; /** @@ -7824,6 +7840,10 @@ export interface SubagentStartedData { * Spawning tool invocation ID, or the canonical sub-agent ID used as a fallback for an API launch without a tool invocation. The fallback is not evidence of a tool call. */ toolCallId: string; + /** + * Root id of the workflow run that spawned this sub-agent, when it was spawned by one. + */ + workflowRunId?: string; } /** * Session event "subagent.configured". Resolved runtime configuration for a configured sub-agent @@ -8642,9 +8662,9 @@ export interface SystemNotificationInstructionDiscovered { type: "instruction_discovered"; } /** - * System notification metadata for a factory execution attempt that reached a terminal state. + * System notification metadata for a workflow execution attempt that reached a terminal state. */ -export interface SystemNotificationFactoryCompleted { +export interface SystemNotificationWorkflowCompleted { /** * Execution attempt that reached this terminal state. */ @@ -8661,32 +8681,32 @@ export interface SystemNotificationFactoryCompleted { * Accumulated active execution time in milliseconds. */ elapsedMs: number; - /** - * Persisted factory name. - */ - factoryName: string; /** * Machine-readable terminal failure details, when present. */ failure?: JsonValue; - pauseInfo?: SystemNotificationFactoryPauseInfo; + pauseInfo?: SystemNotificationWorkflowPauseInfo; /** * Bounded prompt-safe preview of the completed result. */ resultPreview?: string; /** - * Actionable run_factory resume guidance for a resource-limit failure. + * Actionable run_dynamic_workflow resume guidance for a resource-limit failure. */ retryGuidance?: string; /** - * Factory run identifier. + * Workflow run identifier. */ runId: string; - status: SystemNotificationFactoryCompletedStatus; + status: SystemNotificationWorkflowCompletedStatus; + /** + * Type discriminator. Always "workflow_completed". + */ + type: "workflow_completed"; /** - * Type discriminator. Always "factory_completed". + * Persisted workflow name. */ - type: "factory_completed"; + workflowName: string; } /** * System notification metadata from an external host that does not match a runtime-owned notification kind. @@ -9263,41 +9283,45 @@ export interface PermissionRequestExtensionManagement { toolCallId?: string; } /** - * Factory run or authoring permission request + * Workflow run or authoring permission request */ -export interface PermissionRequestFactory { +export interface PermissionRequestWorkflow { /** - * Canonical key used for scoped factory approvals + * Canonical key used for scoped workflow approvals */ approvalKey: string; /** - * Whether this factory is eligible for persistent approval + * Whether this workflow is eligible for persistent approval */ canPersistApproval: boolean; /** - * Factory-declared AI-credit limit before any run/resume caller override is applied. + * Workflow-declared AI-credit limit before any run/resume caller override is applied. */ declaredMaxAiCredits?: number; /** - * Factory-declared concurrent-subagent limit before any run/resume caller override is applied. + * Workflow-declared concurrent-subagent limit before any run/resume caller override is applied. */ declaredMaxConcurrentSubagents?: number; /** - * Factory-declared total-subagent limit before any run/resume caller override is applied. + * Workflow-declared total-subagent limit before any run/resume caller override is applied. */ declaredMaxTotalSubagents?: number; /** - * Factory-declared active-time limit in seconds before any run/resume caller override is applied. + * Workflow-declared active-time limit in seconds before any run/resume caller override is applied. */ declaredTimeoutSeconds?: number; /** - * Factory description + * Workflow description */ description: string; /** * Permission kind discriminator */ - kind: "factory"; + kind: "workflow"; + /** + * Whether managed policy requires a human response and forbids host auto-approval + */ + managedApprovalRequired?: boolean; /** * Effective AI-credit limit; omitted means unlimited */ @@ -9311,14 +9335,14 @@ export interface PermissionRequestFactory { */ maxTotalSubagents?: number; /** - * Factory name + * Workflow name */ name: string; - operation: FactoryPermissionOperation; + operation: WorkflowPermissionOperation; /** - * Declared factory phases + * Declared workflow phases */ - phases: FactoryPermissionPhase[]; + phases: WorkflowPermissionPhase[]; /** * Effective active-time limit in seconds; omitted means unlimited */ @@ -9329,9 +9353,9 @@ export interface PermissionRequestFactory { toolCallId?: string; } /** - * A declared phase shown in a factory permission prompt. + * A declared phase shown in a workflow permission prompt. */ -export interface FactoryPermissionPhase { +export interface WorkflowPermissionPhase { /** * Optional phase detail */ @@ -9765,11 +9789,11 @@ export interface PermissionPromptRequestExtensionManagement { toolCallId?: string; } /** - * Factory run or authoring permission prompt + * Workflow run or authoring permission prompt */ -export interface PermissionPromptRequestFactory { +export interface PermissionPromptRequestWorkflow { /** - * Canonical key used for scoped factory approvals + * Canonical key used for scoped workflow approvals */ approvalKey: string; /** @@ -9779,33 +9803,33 @@ export interface PermissionPromptRequestFactory { */ assistedApproval?: PermissionAssistedApproval; /** - * Whether this factory is eligible for persistent approval + * Whether this workflow is eligible for persistent approval */ canPersistApproval: boolean; /** - * Factory-declared AI-credit limit before any run/resume caller override is applied. + * Workflow-declared AI-credit limit before any run/resume caller override is applied. */ declaredMaxAiCredits?: number; /** - * Factory-declared concurrent-subagent limit before any run/resume caller override is applied. + * Workflow-declared concurrent-subagent limit before any run/resume caller override is applied. */ declaredMaxConcurrentSubagents?: number; /** - * Factory-declared total-subagent limit before any run/resume caller override is applied. + * Workflow-declared total-subagent limit before any run/resume caller override is applied. */ declaredMaxTotalSubagents?: number; /** - * Factory-declared active-time limit in seconds before any run/resume caller override is applied. + * Workflow-declared active-time limit in seconds before any run/resume caller override is applied. */ declaredTimeoutSeconds?: number; /** - * Factory description + * Workflow description */ description: string; /** * Prompt kind discriminator */ - kind: "factory"; + kind: "workflow"; /** * Whether managed policy requires a human response and forbids host auto-approval */ @@ -9823,14 +9847,14 @@ export interface PermissionPromptRequestFactory { */ maxTotalSubagents?: number; /** - * Factory name + * Workflow name */ name: string; - operation: FactoryPermissionOperation; + operation: WorkflowPermissionOperation; /** - * Declared factory phases + * Declared workflow phases */ - phases: FactoryPermissionPhase[]; + phases: WorkflowPermissionPhase[]; /** * Effective active-time limit in seconds; omitted means unlimited */ @@ -10062,17 +10086,17 @@ export interface UserToolSessionApprovalExtensionManagement { operation?: string; } /** - * Session-scoped factory approval, optionally narrowed by approval key. + * Session-scoped workflow approval, optionally narrowed by approval key. */ -export interface UserToolSessionApprovalFactory { +export interface UserToolSessionApprovalWorkflow { /** - * Optional factory operation name or canonical approval key + * Optional workflow operation name or canonical approval key */ approvalKey?: string; /** - * Factory approval kind + * Workflow approval kind */ - kind: "factory"; + kind: "workflow"; } /** * Session-scoped tool-approval rule for an extension's permission-gated capability access, keyed by extension name. @@ -10224,7 +10248,7 @@ export interface PermissionDeniedByPermissionRequestHook { message?: string; } /** - * Session event "permission.carriedForward". Records that a live authorization record from an earlier human decision in this session contained a permission proposal, so it ran without another prompt. This mints no authority: it accounts for one more effect against the prior grant, which is what lets a replayed session agree with the live one about how much of that grant is left. + * Session event "permission.carriedForward". Historical decode-only receipt from the retired Assisted Permissions authorization extractor. Current runtimes ignore it for permission decisions. */ /** @experimental */ export interface PermissionCarriedForwardEvent { @@ -10255,7 +10279,7 @@ export interface PermissionCarriedForwardEvent { type: "permission.carriedForward"; } /** - * Records that a live authorization record from an earlier human decision in this session contained a permission proposal, so it ran without another prompt. This mints no authority: it accounts for one more effect against the prior grant, which is what lets a replayed session agree with the live one about how much of that grant is left. + * Historical decode-only receipt from the retired Assisted Permissions authorization extractor. Current runtimes ignore it for permission decisions. */ /** @experimental */ export interface PermissionCarriedForwardData { @@ -10285,7 +10309,7 @@ export interface PermissionCarriedForwardData { toolCallId: string; } /** - * Session event "permission.messageAuthorization". Freezes one blinded, verbatim-verified authorization claim the runtime minted from a human user message, so a resumed session re-establishes the same grant deterministically instead of re-running the extraction model. This mints no authority on its own: it records what a blinded proposer pointed at and the trusted discriminator the runtime established, and deterministic establishment runs on replay. Persisted so recorded authority survives compaction and process resume. + * Session event "permission.messageAuthorization". Historical decode-only claim from the retired Assisted Permissions authorization extractor. Current runtimes preserve the payload but do not establish authority from it. */ /** @experimental */ export interface PermissionMessageAuthorizationEvent { @@ -10316,7 +10340,7 @@ export interface PermissionMessageAuthorizationEvent { type: "permission.messageAuthorization"; } /** - * Freezes one blinded, verbatim-verified authorization claim the runtime minted from a human user message, so a resumed session re-establishes the same grant deterministically instead of re-running the extraction model. This mints no authority on its own: it records what a blinded proposer pointed at and the trusted discriminator the runtime established, and deterministic establishment runs on replay. Persisted so recorded authority survives compaction and process resume. + * Historical decode-only claim from the retired Assisted Permissions authorization extractor. Current runtimes preserve the payload but do not establish authority from it. */ /** @experimental */ export interface PermissionMessageAuthorizationData { @@ -10376,7 +10400,7 @@ export interface PermissionMessageAuthorizationData { world?: JsonValue; } /** - * Session event "permission.messageAuthorizationRead". Records that one human turn has been read by the blinded authorization proposer, whether or not it minted anything, so a resumed session does not re-run the extraction model on a turn the live session already read. Also records whether that pass activates ongoing extraction; contextual-assent-only passes do not, so unrelated future messages remain outside extraction. + * Session event "permission.messageAuthorizationRead". Historical decode-only extractor progress marker. Current runtimes do not run or resume extraction from it. */ /** @experimental */ export interface PermissionMessageAuthorizationReadEvent { @@ -10407,7 +10431,7 @@ export interface PermissionMessageAuthorizationReadEvent { type: "permission.messageAuthorizationRead"; } /** - * Records that one human turn has been read by the blinded authorization proposer, whether or not it minted anything, so a resumed session does not re-run the extraction model on a turn the live session already read. Also records whether that pass activates ongoing extraction; contextual-assent-only passes do not, so unrelated future messages remain outside extraction. + * Historical decode-only extractor progress marker. Current runtimes do not run or resume extraction from it. */ /** @experimental */ export interface PermissionMessageAuthorizationReadData { @@ -10425,7 +10449,7 @@ export interface PermissionMessageAuthorizationReadData { turnIndex: number; } /** - * Session event "permission.messageAuthorizationDegraded". Records that message-backed authorization could not safely represent one human turn before compaction. The runtime may compact the original message after this marker is durable, but message-derived carry-forward and assisted auto-approval remain disabled for the rest of the session so subsequent commands continue through the ordinary permission prompt. + * Session event "permission.messageAuthorizationDegraded". Historical decode-only degradation marker from the retired extractor. Current runtimes ignore it for permission decisions. */ /** @experimental */ export interface PermissionMessageAuthorizationDegradedEvent { @@ -10456,7 +10480,7 @@ export interface PermissionMessageAuthorizationDegradedEvent { type: "permission.messageAuthorizationDegraded"; } /** - * Records that message-backed authorization could not safely represent one human turn before compaction. The runtime may compact the original message after this marker is durable, but message-derived carry-forward and assisted auto-approval remain disabled for the rest of the session so subsequent commands continue through the ordinary permission prompt. + * Historical decode-only degradation marker from the retired extractor. Current runtimes ignore it for permission decisions. */ /** @experimental */ export interface PermissionMessageAuthorizationDegradedData { @@ -10468,7 +10492,7 @@ export interface PermissionMessageAuthorizationDegradedData { turnIndex: number; } /** - * Session event "permission.assentDetected". Records that deterministic text recognition found likely assent in the human turn immediately following a root Autopilot permission request that was blocked because no interactive response was available. This event grants no authority; its model-facing projection only suggests retrying the unchanged operation. + * Session event "permission.assentDetected". Historical decode-only contextual-assent marker. Current runtimes do not project it into the conversation or permission flow. */ /** @experimental */ export interface PermissionAssentDetectedEvent { @@ -10499,7 +10523,7 @@ export interface PermissionAssentDetectedEvent { type: "permission.assentDetected"; } /** - * Records that deterministic text recognition found likely assent in the human turn immediately following a root Autopilot permission request that was blocked because no interactive response was available. This event grants no authority; its model-facing projection only suggests retrying the unchanged operation. + * Historical decode-only contextual-assent marker. Current runtimes do not project it into the conversation or permission flow. */ /** @experimental */ export interface PermissionAssentDetectedData { @@ -10517,7 +10541,7 @@ export interface PermissionAssentDetectedData { turnIndex: number; } /** - * Session event "permission.contextualAuthorization". Freezes a blinded contextual authorization proposal whose verbatim human span was deterministically bound to the immediately preceding blocked permission request. The event carries no action fields; replay re-derives the exact action from the earlier permission request and mints a one-shot message grant only when the binding and span still verify. + * Session event "permission.contextualAuthorization". Historical decode-only contextual authorization claim. Current runtimes preserve the payload but do not establish authority from it. */ /** @experimental */ export interface PermissionContextualAuthorizationEvent { @@ -10548,7 +10572,7 @@ export interface PermissionContextualAuthorizationEvent { type: "permission.contextualAuthorization"; } /** - * Freezes a blinded contextual authorization proposal whose verbatim human span was deterministically bound to the immediately preceding blocked permission request. The event carries no action fields; replay re-derives the exact action from the earlier permission request and mints a one-shot message grant only when the binding and span still verify. + * Historical decode-only contextual authorization claim. Current runtimes preserve the payload but do not establish authority from it. */ /** @experimental */ export interface PermissionContextualAuthorizationData { @@ -12206,15 +12230,15 @@ export interface BackgroundTasksChangedEvent { */ export interface BackgroundTasksChangedData {} /** - * Session event "factory.run_updated". Ephemeral invalidation signal for a changed factory run. + * Session event "workflow.run_updated". Ephemeral invalidation signal for a changed workflow run. */ /** @experimental */ -export interface FactoryRunUpdatedEvent { +export interface WorkflowRunUpdatedEvent { /** * Sub-agent instance identifier. Absent for events from the root/main agent and session-level events. */ agentId?: string; - data: FactoryRunUpdatedData; + data: WorkflowRunUpdatedData; /** * Always true for events that are transient and not persisted to the session event log on disk. */ @@ -12232,34 +12256,34 @@ export interface FactoryRunUpdatedEvent { */ timestamp: string; /** - * Type discriminator. Always "factory.run_updated". + * Type discriminator. Always "workflow.run_updated". */ - type: "factory.run_updated"; + type: "workflow.run_updated"; } /** - * Ephemeral invalidation signal for a changed factory run. + * Ephemeral invalidation signal for a changed workflow run. */ /** @experimental */ -export interface FactoryRunUpdatedData { +export interface WorkflowRunUpdatedData { /** * Monotonic revision now available for the run. */ revision: number; /** - * Factory run identifier. + * Workflow run identifier. */ runId: string; } /** - * Session event "factory.run_started". Ephemeral signal that a factory run attempt began executing. + * Session event "workflow.run_started". Ephemeral signal that a workflow run attempt began executing. */ /** @experimental */ -export interface FactoryRunStartedEvent { +export interface WorkflowRunStartedEvent { /** * Sub-agent instance identifier. Absent for events from the root/main agent and session-level events. */ agentId?: string; - data: FactoryRunStartedData; + data: WorkflowRunStartedData; /** * Always true for events that are transient and not persisted to the session event log on disk. */ @@ -12277,38 +12301,38 @@ export interface FactoryRunStartedEvent { */ timestamp: string; /** - * Type discriminator. Always "factory.run_started". + * Type discriminator. Always "workflow.run_started". */ - type: "factory.run_started"; + type: "workflow.run_started"; } /** - * Ephemeral signal that a factory run attempt began executing. + * Ephemeral signal that a workflow run attempt began executing. */ /** @experimental */ -export interface FactoryRunStartedData { +export interface WorkflowRunStartedData { /** * Attempt number this start committed; a resumed run increments it. */ attempt: number; /** - * Name of the factory this run executes. Low cardinality by construction. + * Identifier of the workflow run that started. */ - factoryName: string; + runId: string; /** - * Identifier of the factory run that started. + * Name of the workflow this run executes. Low cardinality by construction. */ - runId: string; + workflowName: string; } /** - * Session event "factory.run_settled". Ephemeral signal that a factory run reached a terminal status. + * Session event "workflow.run_settled". Ephemeral signal that a workflow run reached a terminal status. */ /** @experimental */ -export interface FactoryRunSettledEvent { +export interface WorkflowRunSettledEvent { /** * Sub-agent instance identifier. Absent for events from the root/main agent and session-level events. */ agentId?: string; - data: FactoryRunSettledData; + data: WorkflowRunSettledData; /** * Always true for events that are transient and not persisted to the session event log on disk. */ @@ -12326,15 +12350,15 @@ export interface FactoryRunSettledEvent { */ timestamp: string; /** - * Type discriminator. Always "factory.run_settled". + * Type discriminator. Always "workflow.run_settled". */ - type: "factory.run_settled"; + type: "workflow.run_settled"; } /** - * Ephemeral signal that a factory run reached a terminal status. + * Ephemeral signal that a workflow run reached a terminal status. */ /** @experimental */ -export interface FactoryRunSettledData { +export interface WorkflowRunSettledData { /** * AI credits this run consumed, in nano-AIU. */ @@ -12348,14 +12372,14 @@ export interface FactoryRunSettledData { */ elapsedMs: number; /** - * Typed failure class recorded on the run, when it failed with one (e.g. `factory_limit_reached`). + * Typed failure class recorded on the run, when it failed with one (e.g. `workflow_limit_reached`). */ failureType?: string; /** - * Identifier of the factory run that settled. + * Identifier of the workflow run that settled. */ runId: string; - status: FactoryRunSettledStatus; + status: WorkflowRunSettledStatus; } /** * Session event "session.skills_loaded". Payload of `session.skills_loaded` listing resolved skill metadata. diff --git a/nodejs/src/session.ts b/nodejs/src/session.ts index c6fb956c84..992f6314e7 100644 --- a/nodejs/src/session.ts +++ b/nodejs/src/session.ts @@ -911,11 +911,22 @@ export class CopilotSession { signal.addEventListener("abort", onAbort, { once: true }); } - unsubscribe = this.on("factory.run_updated", (event) => { - if (event.data.runId === runId) { + const subscribe = this.on.bind(this) as unknown as ( + eventType: string, + handler: (event: SessionEvent) => void + ) => () => void; + const handleRunUpdated = (event: SessionEvent): void => { + const data = event.data as { runId?: unknown }; + if (data.runId === runId) { void read(); } - }); + }; + const unsubscribeFactory = subscribe("factory.run_updated", handleRunUpdated); + const unsubscribeWorkflow = subscribe("workflow.run_updated", handleRunUpdated); + unsubscribe = (): void => { + unsubscribeFactory(); + unsubscribeWorkflow(); + }; pollHandle = setInterval(() => void read(), 5_000); // The re-read is a safety net, not work the process owes anyone: an @@ -1002,11 +1013,22 @@ export class CopilotSession { signal.addEventListener("abort", onAbort, { once: true }); } - unsubscribe = this.on("factory.run_updated", (event) => { - if (event.data.runId === runId) { + const subscribe = this.on.bind(this) as unknown as ( + eventType: string, + handler: (event: SessionEvent) => void + ) => () => void; + const handleRunUpdated = (event: SessionEvent): void => { + const data = event.data as { runId?: unknown }; + if (data.runId === runId) { void read(); } - }); + }; + const unsubscribeFactory = subscribe("factory.run_updated", handleRunUpdated); + const unsubscribeWorkflow = subscribe("workflow.run_updated", handleRunUpdated); + unsubscribe = (): void => { + unsubscribeFactory(); + unsubscribeWorkflow(); + }; pollHandle = setInterval(() => void read(), 5_000); // The re-read is a safety net, not work the process owes anyone: an diff --git a/nodejs/src/workerCausality.ts b/nodejs/src/workerCausality.ts index 1f89e52334..af44369ecc 100644 --- a/nodejs/src/workerCausality.ts +++ b/nodejs/src/workerCausality.ts @@ -11,7 +11,12 @@ function fields(value: Record, allowed: readonly string[]): boo } function text(value: unknown): value is string { - return typeof value === "string" && value.length > 0 && Buffer.byteLength(value, "utf8") <= 256; + return ( + typeof value === "string" && + value.length > 0 && + Buffer.byteLength(value, "utf8") <= 256 && + !/\p{Cc}/u.test(value) + ); } function small(value: unknown, budget: { remaining: number }, depth = 0): boolean { @@ -33,7 +38,6 @@ function reference(value: unknown, type: string): boolean { object(value) && fields(value, ["sessionId", "eventId", "agentId", "eventType", "provenance"]) && text(value.sessionId) && - /^[A-Za-z0-9_-]+$/.test(value.sessionId) && typeof value.eventId === "string" && /^[\da-f]{8}-[\da-f]{4}-[\da-f]{4}-[\da-f]{4}-[\da-f]{12}$/i.test(value.eventId) && (!("agentId" in value) || text(value.agentId)) && diff --git a/nodejs/test/worker-causality.test.ts b/nodejs/test/worker-causality.test.ts index 02353fe587..079b0b16f5 100644 --- a/nodejs/test/worker-causality.test.ts +++ b/nodejs/test/worker-causality.test.ts @@ -20,6 +20,7 @@ const corpus = JSON.parse( valid: { name: string; event?: Wire; result?: Wire }[]; invalid: { name: string; value: unknown }[]; boundaries: { name: string; value: unknown; accepted: boolean }[]; + workflowCompleted: { event: Wire }; }; function payload(value: Wire, event: boolean): Wire { @@ -111,4 +112,18 @@ describe("worker causality public readers", () => { expect(data.content).toBe(payload(wire, true).content); } ); + + it("decodes the canonical workflow completion notification with typed fields", async () => { + const event = corpus.workflowCompleted.event as unknown as SessionEvent; + expect(event.type).toBe("system.notification"); + if (event.type !== "system.notification") throw new Error("expected system.notification"); + expect(event.data.kind.type).toBe("workflow_completed"); + if (event.data.kind.type !== "workflow_completed") { + throw new Error("expected workflow_completed"); + } + expect(event.data.kind.workflowName).toBe("fix-ci"); + expect(event.data.kind.runId).toBe("run-1"); + expect(event.data.kind.status).toBe("completed"); + expect(event.data.kind.consumedSubagents).toBe(1); + }); }); diff --git a/python/copilot/_worker_causality.py b/python/copilot/_worker_causality.py index 774a4331e0..edd9696cae 100644 --- a/python/copilot/_worker_causality.py +++ b/python/copilot/_worker_causality.py @@ -5,6 +5,7 @@ import json import logging import re +import unicodedata from collections.abc import Callable from typing import Any, TypeVar @@ -18,7 +19,12 @@ def _uuid(value: Any) -> bool: def _text(value: Any) -> bool: - return isinstance(value, str) and bool(value) and len(value.encode("utf-8")) <= 256 + return ( + isinstance(value, str) + and bool(value) + and len(value.encode("utf-8")) <= 256 + and all(unicodedata.category(char) != "Cc" for char in value) + ) def _fields(value: dict[str, Any], allowed: set[str]) -> bool: @@ -30,7 +36,6 @@ def _reference(value: Any, event_type: str) -> bool: isinstance(value, dict) and _fields(value, {"sessionId", "eventId", "agentId", "eventType", "provenance"}) and _text(value.get("sessionId")) - and re.fullmatch(r"[A-Za-z0-9_-]+", value["sessionId"]) is not None and _uuid(value.get("eventId")) and ("agentId" not in value or _text(value["agentId"])) and value.get("eventType") == event_type diff --git a/python/copilot/generated/session_events.py b/python/copilot/generated/session_events.py index cb783f4d6e..c45375a60e 100644 --- a/python/copilot/generated/session_events.py +++ b/python/copilot/generated/session_events.py @@ -278,11 +278,11 @@ class SessionEventType(Enum): SESSION_TOOLS_UPDATED = "session.tools_updated" SESSION_BACKGROUND_TASKS_CHANGED = "session.background_tasks_changed" # Experimental: this event is part of an experimental API and may change or be removed. - FACTORY_RUN_UPDATED = "factory.run_updated" + WORKFLOW_RUN_UPDATED = "workflow.run_updated" # Experimental: this event is part of an experimental API and may change or be removed. - FACTORY_RUN_STARTED = "factory.run_started" + WORKFLOW_RUN_STARTED = "workflow.run_started" # Experimental: this event is part of an experimental API and may change or be removed. - FACTORY_RUN_SETTLED = "factory.run_settled" + WORKFLOW_RUN_SETTLED = "workflow.run_settled" SESSION_SKILLS_LOADED = "session.skills_loaded" SESSION_CUSTOM_AGENTS_UPDATED = "session.custom_agents_updated" SESSION_MCP_SERVERS_LOADED = "session.mcp_servers_loaded" @@ -1103,99 +1103,6 @@ def to_dict(self) -> dict: return result -# Experimental: this type is part of an experimental API and may change or be removed. -@dataclass -class FactoryRunSettledData: - "Ephemeral signal that a factory run reached a terminal status." - consumed_nano_aiu: int - consumed_subagents: int - elapsed_ms: int - run_id: str - status: FactoryRunSettledStatus - failure_type: str | None = None - - @staticmethod - def from_dict(obj: Any) -> "FactoryRunSettledData": - assert isinstance(obj, dict) - consumed_nano_aiu = from_int(obj.get("consumedNanoAiu")) - consumed_subagents = from_int(obj.get("consumedSubagents")) - elapsed_ms = from_int(obj.get("elapsedMs")) - run_id = from_str(obj.get("runId")) - status = parse_enum(FactoryRunSettledStatus, obj.get("status")) - failure_type = from_union([from_none, from_str], obj.get("failureType")) - return FactoryRunSettledData( - consumed_nano_aiu=consumed_nano_aiu, - consumed_subagents=consumed_subagents, - elapsed_ms=elapsed_ms, - run_id=run_id, - status=status, - failure_type=failure_type, - ) - - def to_dict(self) -> dict: - result: dict = {} - result["consumedNanoAiu"] = to_int(self.consumed_nano_aiu) - result["consumedSubagents"] = to_int(self.consumed_subagents) - result["elapsedMs"] = to_int(self.elapsed_ms) - result["runId"] = from_str(self.run_id) - result["status"] = to_enum(FactoryRunSettledStatus, self.status) - if self.failure_type is not None: - result["failureType"] = from_union([from_none, from_str], self.failure_type) - return result - - -# Experimental: this type is part of an experimental API and may change or be removed. -@dataclass -class FactoryRunStartedData: - "Ephemeral signal that a factory run attempt began executing." - attempt: int - factory_name: str - run_id: str - - @staticmethod - def from_dict(obj: Any) -> "FactoryRunStartedData": - assert isinstance(obj, dict) - attempt = from_int(obj.get("attempt")) - factory_name = from_str(obj.get("factoryName")) - run_id = from_str(obj.get("runId")) - return FactoryRunStartedData( - attempt=attempt, - factory_name=factory_name, - run_id=run_id, - ) - - def to_dict(self) -> dict: - result: dict = {} - result["attempt"] = to_int(self.attempt) - result["factoryName"] = from_str(self.factory_name) - result["runId"] = from_str(self.run_id) - return result - - -# Experimental: this type is part of an experimental API and may change or be removed. -@dataclass -class FactoryRunUpdatedData: - "Ephemeral invalidation signal for a changed factory run." - revision: int - run_id: str - - @staticmethod - def from_dict(obj: Any) -> "FactoryRunUpdatedData": - assert isinstance(obj, dict) - revision = from_int(obj.get("revision")) - run_id = from_str(obj.get("runId")) - return FactoryRunUpdatedData( - revision=revision, - run_id=run_id, - ) - - def to_dict(self) -> dict: - result: dict = {} - result["revision"] = to_int(self.revision) - result["runId"] = from_str(self.run_id) - return result - - # Experimental: this type is part of an experimental API and may change or be removed. @dataclass class FusionAttribution: @@ -1206,6 +1113,7 @@ class FusionAttribution: synthetic_model: str commit_id: str | None = None conversation_scope: str | None = None + has_user_steering: bool | None = None phase_id: str | None = None phase_kind: str | None = None role: str | None = None @@ -1221,6 +1129,7 @@ def from_dict(obj: Any) -> "FusionAttribution": synthetic_model = from_str(obj.get("syntheticModel")) commit_id = from_union([from_none, from_str], obj.get("commitId")) conversation_scope = from_union([from_none, from_str], obj.get("conversationScope")) + has_user_steering = from_union([from_none, from_bool], obj.get("hasUserSteering")) phase_id = from_union([from_none, from_str], obj.get("phaseId")) phase_kind = from_union([from_none, from_str], obj.get("phaseKind")) role = from_union([from_none, from_str], obj.get("role")) @@ -1233,6 +1142,7 @@ def from_dict(obj: Any) -> "FusionAttribution": synthetic_model=synthetic_model, commit_id=commit_id, conversation_scope=conversation_scope, + has_user_steering=has_user_steering, phase_id=phase_id, phase_kind=phase_kind, role=role, @@ -1250,6 +1160,8 @@ def to_dict(self) -> dict: result["commitId"] = from_union([from_none, from_str], self.commit_id) if self.conversation_scope is not None: result["conversationScope"] = from_union([from_none, from_str], self.conversation_scope) + if self.has_user_steering is not None: + result["hasUserSteering"] = from_union([from_none, from_bool], self.has_user_steering) if self.phase_id is not None: result["phaseId"] = from_union([from_none, from_str], self.phase_id) if self.phase_kind is not None: @@ -1473,7 +1385,7 @@ def to_dict(self) -> dict: # Experimental: this type is part of an experimental API and may change or be removed. @dataclass class PermissionAssentDetectedData: - "Records that deterministic text recognition found likely assent in the human turn immediately following a root Autopilot permission request that was blocked because no interactive response was available. This event grants no authority; its model-facing projection only suggests retrying the unchanged operation." + "Historical decode-only contextual-assent marker. Current runtimes do not project it into the conversation or permission flow." # Experimental: this field is part of an experimental API and may change or be removed. request_id: str # Experimental: this field is part of an experimental API and may change or be removed. @@ -1539,7 +1451,7 @@ def to_dict(self) -> dict: # Experimental: this type is part of an experimental API and may change or be removed. @dataclass class PermissionCarriedForwardData: - "Records that a live authorization record from an earlier human decision in this session contained a permission proposal, so it ran without another prompt. This mints no authority: it accounts for one more effect against the prior grant, which is what lets a replayed session agree with the live one about how much of that grant is left." + "Historical decode-only receipt from the retired Assisted Permissions authorization extractor. Current runtimes ignore it for permission decisions." # Experimental: this field is part of an experimental API and may change or be removed. decision_source: PermissionDecisionSource # Experimental: this field is part of an experimental API and may change or be removed. @@ -1575,7 +1487,7 @@ def to_dict(self) -> dict: # Experimental: this type is part of an experimental API and may change or be removed. @dataclass class PermissionContextualAuthorizationData: - "Freezes a blinded contextual authorization proposal whose verbatim human span was deterministically bound to the immediately preceding blocked permission request. The event carries no action fields; replay re-derives the exact action from the earlier permission request and mints a one-shot message grant only when the binding and span still verify." + "Historical decode-only contextual authorization claim. Current runtimes preserve the payload but do not establish authority from it." # Experimental: this field is part of an experimental API and may change or be removed. polarity: PermissionMessageAuthorizationPolarity # Experimental: this field is part of an experimental API and may change or be removed. @@ -1621,7 +1533,7 @@ def to_dict(self) -> dict: # Experimental: this type is part of an experimental API and may change or be removed. @dataclass class PermissionMessageAuthorizationData: - "Freezes one blinded, verbatim-verified authorization claim the runtime minted from a human user message, so a resumed session re-establishes the same grant deterministically instead of re-running the extraction model. This mints no authority on its own: it records what a blinded proposer pointed at and the trusted discriminator the runtime established, and deterministic establishment runs on replay. Persisted so recorded authority survives compaction and process resume." + "Historical decode-only claim from the retired Assisted Permissions authorization extractor. Current runtimes preserve the payload but do not establish authority from it." # Experimental: this field is part of an experimental API and may change or be removed. action_class: str # Experimental: this field is part of an experimental API and may change or be removed. @@ -1685,7 +1597,7 @@ def to_dict(self) -> dict: # Experimental: this type is part of an experimental API and may change or be removed. @dataclass class PermissionMessageAuthorizationDegradedData: - "Records that message-backed authorization could not safely represent one human turn before compaction. The runtime may compact the original message after this marker is durable, but message-derived carry-forward and assisted auto-approval remain disabled for the rest of the session so subsequent commands continue through the ordinary permission prompt." + "Historical decode-only degradation marker from the retired extractor. Current runtimes ignore it for permission decisions." # Experimental: this field is part of an experimental API and may change or be removed. turn_index: int @@ -1706,7 +1618,7 @@ def to_dict(self) -> dict: # Experimental: this type is part of an experimental API and may change or be removed. @dataclass class PermissionMessageAuthorizationReadData: - "Records that one human turn has been read by the blinded authorization proposer, whether or not it minted anything, so a resumed session does not re-run the extraction model on a turn the live session already read. Also records whether that pass activates ongoing extraction; contextual-assent-only passes do not, so unrelated future messages remain outside extraction." + "Historical decode-only extractor progress marker. Current runtimes do not run or resume extraction from it." # Experimental: this field is part of an experimental API and may change or be removed. turn_index: int # Experimental: this field is part of an experimental API and may change or be removed. @@ -2603,6 +2515,99 @@ def to_dict(self) -> dict: return result +# Experimental: this type is part of an experimental API and may change or be removed. +@dataclass +class WorkflowRunSettledData: + "Ephemeral signal that a workflow run reached a terminal status." + consumed_nano_aiu: int + consumed_subagents: int + elapsed_ms: int + run_id: str + status: WorkflowRunSettledStatus + failure_type: str | None = None + + @staticmethod + def from_dict(obj: Any) -> "WorkflowRunSettledData": + assert isinstance(obj, dict) + consumed_nano_aiu = from_int(obj.get("consumedNanoAiu")) + consumed_subagents = from_int(obj.get("consumedSubagents")) + elapsed_ms = from_int(obj.get("elapsedMs")) + run_id = from_str(obj.get("runId")) + status = parse_enum(WorkflowRunSettledStatus, obj.get("status")) + failure_type = from_union([from_none, from_str], obj.get("failureType")) + return WorkflowRunSettledData( + consumed_nano_aiu=consumed_nano_aiu, + consumed_subagents=consumed_subagents, + elapsed_ms=elapsed_ms, + run_id=run_id, + status=status, + failure_type=failure_type, + ) + + def to_dict(self) -> dict: + result: dict = {} + result["consumedNanoAiu"] = to_int(self.consumed_nano_aiu) + result["consumedSubagents"] = to_int(self.consumed_subagents) + result["elapsedMs"] = to_int(self.elapsed_ms) + result["runId"] = from_str(self.run_id) + result["status"] = to_enum(WorkflowRunSettledStatus, self.status) + if self.failure_type is not None: + result["failureType"] = from_union([from_none, from_str], self.failure_type) + return result + + +# Experimental: this type is part of an experimental API and may change or be removed. +@dataclass +class WorkflowRunStartedData: + "Ephemeral signal that a workflow run attempt began executing." + attempt: int + run_id: str + workflow_name: str + + @staticmethod + def from_dict(obj: Any) -> "WorkflowRunStartedData": + assert isinstance(obj, dict) + attempt = from_int(obj.get("attempt")) + run_id = from_str(obj.get("runId")) + workflow_name = from_str(obj.get("workflowName")) + return WorkflowRunStartedData( + attempt=attempt, + run_id=run_id, + workflow_name=workflow_name, + ) + + def to_dict(self) -> dict: + result: dict = {} + result["attempt"] = to_int(self.attempt) + result["runId"] = from_str(self.run_id) + result["workflowName"] = from_str(self.workflow_name) + return result + + +# Experimental: this type is part of an experimental API and may change or be removed. +@dataclass +class WorkflowRunUpdatedData: + "Ephemeral invalidation signal for a changed workflow run." + revision: int + run_id: str + + @staticmethod + def from_dict(obj: Any) -> "WorkflowRunUpdatedData": + assert isinstance(obj, dict) + revision = from_int(obj.get("revision")) + run_id = from_str(obj.get("runId")) + return WorkflowRunUpdatedData( + revision=revision, + run_id=run_id, + ) + + def to_dict(self) -> dict: + result: dict = {} + result["revision"] = to_int(self.revision) + result["runId"] = from_str(self.run_id) + return result + + @dataclass class AbortData: "Turn abort information including the reason for termination" @@ -5094,30 +5099,6 @@ def to_dict(self) -> dict: return result -@dataclass -class FactoryPermissionPhase: - "A declared phase shown in a factory permission prompt." - title: str - detail: str | None = None - - @staticmethod - def from_dict(obj: Any) -> "FactoryPermissionPhase": - assert isinstance(obj, dict) - title = from_str(obj.get("title")) - detail = from_union([from_none, from_str], obj.get("detail")) - return FactoryPermissionPhase( - title=title, - detail=detail, - ) - - def to_dict(self) -> dict: - result: dict = {} - result["title"] = from_str(self.title) - if self.detail is not None: - result["detail"] = from_union([from_none, from_str], self.detail) - return result - - @dataclass class GitHubMcpToolConfig: "Per-session configuration for the built-in GitHub MCP server" @@ -6620,103 +6601,6 @@ def to_dict(self) -> dict: return result -@dataclass -class PermissionPromptRequestFactory: - "Factory run or authoring permission prompt" - approval_key: str - can_persist_approval: bool - description: str - kind: ClassVar[str] = "factory" - name: str - operation: FactoryPermissionOperation - phases: list[FactoryPermissionPhase] - # Experimental: this field is part of an experimental API and may change or be removed. - assisted_approval: PermissionAssistedApproval | None = None - declared_max_ai_credits: float | None = None - declared_max_concurrent_subagents: int | None = None - declared_max_total_subagents: int | None = None - declared_timeout_seconds: float | None = None - managed_approval_required: bool | None = None - max_ai_credits: float | None = None - max_concurrent_subagents: int | None = None - max_total_subagents: int | None = None - timeout_seconds: float | None = None - tool_call_id: str | None = None - - @staticmethod - def from_dict(obj: Any) -> "PermissionPromptRequestFactory": - assert isinstance(obj, dict) - approval_key = from_str(obj.get("approvalKey")) - can_persist_approval = from_bool(obj.get("canPersistApproval")) - description = from_str(obj.get("description")) - name = from_str(obj.get("name")) - operation = parse_enum(FactoryPermissionOperation, obj.get("operation")) - phases = from_list(FactoryPermissionPhase.from_dict, obj.get("phases")) - assisted_approval = from_union([from_none, PermissionAssistedApproval.from_dict], obj.get("assistedApproval")) - declared_max_ai_credits = from_union([from_none, from_float], obj.get("declaredMaxAiCredits")) - declared_max_concurrent_subagents = from_union([from_none, from_int], obj.get("declaredMaxConcurrentSubagents")) - declared_max_total_subagents = from_union([from_none, from_int], obj.get("declaredMaxTotalSubagents")) - declared_timeout_seconds = from_union([from_none, from_float], obj.get("declaredTimeoutSeconds")) - managed_approval_required = from_union([from_none, from_bool], obj.get("managedApprovalRequired")) - max_ai_credits = from_union([from_none, from_float], obj.get("maxAiCredits")) - max_concurrent_subagents = from_union([from_none, from_int], obj.get("maxConcurrentSubagents")) - max_total_subagents = from_union([from_none, from_int], obj.get("maxTotalSubagents")) - timeout_seconds = from_union([from_none, from_float], obj.get("timeoutSeconds")) - tool_call_id = from_union([from_none, from_str], obj.get("toolCallId")) - return PermissionPromptRequestFactory( - approval_key=approval_key, - can_persist_approval=can_persist_approval, - description=description, - name=name, - operation=operation, - phases=phases, - assisted_approval=assisted_approval, - declared_max_ai_credits=declared_max_ai_credits, - declared_max_concurrent_subagents=declared_max_concurrent_subagents, - declared_max_total_subagents=declared_max_total_subagents, - declared_timeout_seconds=declared_timeout_seconds, - managed_approval_required=managed_approval_required, - max_ai_credits=max_ai_credits, - max_concurrent_subagents=max_concurrent_subagents, - max_total_subagents=max_total_subagents, - timeout_seconds=timeout_seconds, - tool_call_id=tool_call_id, - ) - - def to_dict(self) -> dict: - result: dict = {} - result["approvalKey"] = from_str(self.approval_key) - result["canPersistApproval"] = from_bool(self.can_persist_approval) - result["description"] = from_str(self.description) - result["kind"] = self.kind - result["name"] = from_str(self.name) - result["operation"] = to_enum(FactoryPermissionOperation, self.operation) - result["phases"] = from_list(lambda x: to_class(FactoryPermissionPhase, x), self.phases) - if self.assisted_approval is not None: - result["assistedApproval"] = from_union([from_none, lambda x: to_class(PermissionAssistedApproval, x)], self.assisted_approval) - if self.declared_max_ai_credits is not None: - result["declaredMaxAiCredits"] = from_union([from_none, to_float], self.declared_max_ai_credits) - if self.declared_max_concurrent_subagents is not None: - result["declaredMaxConcurrentSubagents"] = from_union([from_none, to_int], self.declared_max_concurrent_subagents) - if self.declared_max_total_subagents is not None: - result["declaredMaxTotalSubagents"] = from_union([from_none, to_int], self.declared_max_total_subagents) - if self.declared_timeout_seconds is not None: - result["declaredTimeoutSeconds"] = from_union([from_none, to_float], self.declared_timeout_seconds) - if self.managed_approval_required is not None: - result["managedApprovalRequired"] = from_union([from_none, from_bool], self.managed_approval_required) - if self.max_ai_credits is not None: - result["maxAiCredits"] = from_union([from_none, to_float], self.max_ai_credits) - if self.max_concurrent_subagents is not None: - result["maxConcurrentSubagents"] = from_union([from_none, to_int], self.max_concurrent_subagents) - if self.max_total_subagents is not None: - result["maxTotalSubagents"] = from_union([from_none, to_int], self.max_total_subagents) - if self.timeout_seconds is not None: - result["timeoutSeconds"] = from_union([from_none, to_float], self.timeout_seconds) - if self.tool_call_id is not None: - result["toolCallId"] = from_union([from_none, from_str], self.tool_call_id) - return result - - @dataclass class PermissionPromptRequestHook: "Hook confirmation permission prompt" @@ -7011,6 +6895,103 @@ def to_dict(self) -> dict: return result +@dataclass +class PermissionPromptRequestWorkflow: + "Workflow run or authoring permission prompt" + approval_key: str + can_persist_approval: bool + description: str + kind: ClassVar[str] = "workflow" + name: str + operation: WorkflowPermissionOperation + phases: list[WorkflowPermissionPhase] + # Experimental: this field is part of an experimental API and may change or be removed. + assisted_approval: PermissionAssistedApproval | None = None + declared_max_ai_credits: float | None = None + declared_max_concurrent_subagents: int | None = None + declared_max_total_subagents: int | None = None + declared_timeout_seconds: float | None = None + managed_approval_required: bool | None = None + max_ai_credits: float | None = None + max_concurrent_subagents: int | None = None + max_total_subagents: int | None = None + timeout_seconds: float | None = None + tool_call_id: str | None = None + + @staticmethod + def from_dict(obj: Any) -> "PermissionPromptRequestWorkflow": + assert isinstance(obj, dict) + approval_key = from_str(obj.get("approvalKey")) + can_persist_approval = from_bool(obj.get("canPersistApproval")) + description = from_str(obj.get("description")) + name = from_str(obj.get("name")) + operation = parse_enum(WorkflowPermissionOperation, obj.get("operation")) + phases = from_list(WorkflowPermissionPhase.from_dict, obj.get("phases")) + assisted_approval = from_union([from_none, PermissionAssistedApproval.from_dict], obj.get("assistedApproval")) + declared_max_ai_credits = from_union([from_none, from_float], obj.get("declaredMaxAiCredits")) + declared_max_concurrent_subagents = from_union([from_none, from_int], obj.get("declaredMaxConcurrentSubagents")) + declared_max_total_subagents = from_union([from_none, from_int], obj.get("declaredMaxTotalSubagents")) + declared_timeout_seconds = from_union([from_none, from_float], obj.get("declaredTimeoutSeconds")) + managed_approval_required = from_union([from_none, from_bool], obj.get("managedApprovalRequired")) + max_ai_credits = from_union([from_none, from_float], obj.get("maxAiCredits")) + max_concurrent_subagents = from_union([from_none, from_int], obj.get("maxConcurrentSubagents")) + max_total_subagents = from_union([from_none, from_int], obj.get("maxTotalSubagents")) + timeout_seconds = from_union([from_none, from_float], obj.get("timeoutSeconds")) + tool_call_id = from_union([from_none, from_str], obj.get("toolCallId")) + return PermissionPromptRequestWorkflow( + approval_key=approval_key, + can_persist_approval=can_persist_approval, + description=description, + name=name, + operation=operation, + phases=phases, + assisted_approval=assisted_approval, + declared_max_ai_credits=declared_max_ai_credits, + declared_max_concurrent_subagents=declared_max_concurrent_subagents, + declared_max_total_subagents=declared_max_total_subagents, + declared_timeout_seconds=declared_timeout_seconds, + managed_approval_required=managed_approval_required, + max_ai_credits=max_ai_credits, + max_concurrent_subagents=max_concurrent_subagents, + max_total_subagents=max_total_subagents, + timeout_seconds=timeout_seconds, + tool_call_id=tool_call_id, + ) + + def to_dict(self) -> dict: + result: dict = {} + result["approvalKey"] = from_str(self.approval_key) + result["canPersistApproval"] = from_bool(self.can_persist_approval) + result["description"] = from_str(self.description) + result["kind"] = self.kind + result["name"] = from_str(self.name) + result["operation"] = to_enum(WorkflowPermissionOperation, self.operation) + result["phases"] = from_list(lambda x: to_class(WorkflowPermissionPhase, x), self.phases) + if self.assisted_approval is not None: + result["assistedApproval"] = from_union([from_none, lambda x: to_class(PermissionAssistedApproval, x)], self.assisted_approval) + if self.declared_max_ai_credits is not None: + result["declaredMaxAiCredits"] = from_union([from_none, to_float], self.declared_max_ai_credits) + if self.declared_max_concurrent_subagents is not None: + result["declaredMaxConcurrentSubagents"] = from_union([from_none, to_int], self.declared_max_concurrent_subagents) + if self.declared_max_total_subagents is not None: + result["declaredMaxTotalSubagents"] = from_union([from_none, to_int], self.declared_max_total_subagents) + if self.declared_timeout_seconds is not None: + result["declaredTimeoutSeconds"] = from_union([from_none, to_float], self.declared_timeout_seconds) + if self.managed_approval_required is not None: + result["managedApprovalRequired"] = from_union([from_none, from_bool], self.managed_approval_required) + if self.max_ai_credits is not None: + result["maxAiCredits"] = from_union([from_none, to_float], self.max_ai_credits) + if self.max_concurrent_subagents is not None: + result["maxConcurrentSubagents"] = from_union([from_none, to_int], self.max_concurrent_subagents) + if self.max_total_subagents is not None: + result["maxTotalSubagents"] = from_union([from_none, to_int], self.max_total_subagents) + if self.timeout_seconds is not None: + result["timeoutSeconds"] = from_union([from_none, to_float], self.timeout_seconds) + if self.tool_call_id is not None: + result["toolCallId"] = from_union([from_none, from_str], self.tool_call_id) + return result + + @dataclass class PermissionPromptRequestWrite: "File write permission prompt" @@ -7308,97 +7289,6 @@ def to_dict(self) -> dict: return result -@dataclass -class PermissionRequestFactory: - "Factory run or authoring permission request" - approval_key: str - can_persist_approval: bool - description: str - kind: ClassVar[str] = "factory" - name: str - operation: FactoryPermissionOperation - phases: list[FactoryPermissionPhase] - declared_max_ai_credits: float | None = None - declared_max_concurrent_subagents: int | None = None - declared_max_total_subagents: int | None = None - declared_timeout_seconds: float | None = None - max_ai_credits: float | None = None - max_concurrent_subagents: int | None = None - max_total_subagents: int | None = None - timeout_seconds: float | None = None - tool_call_id: str | None = None - managed_approval_required: bool | None = None - - @staticmethod - def from_dict(obj: Any) -> "PermissionRequestFactory": - assert isinstance(obj, dict) - approval_key = from_str(obj.get("approvalKey")) - can_persist_approval = from_bool(obj.get("canPersistApproval")) - description = from_str(obj.get("description")) - name = from_str(obj.get("name")) - operation = parse_enum(FactoryPermissionOperation, obj.get("operation")) - phases = from_list(FactoryPermissionPhase.from_dict, obj.get("phases")) - declared_max_ai_credits = from_union([from_none, from_float], obj.get("declaredMaxAiCredits")) - declared_max_concurrent_subagents = from_union([from_none, from_int], obj.get("declaredMaxConcurrentSubagents")) - declared_max_total_subagents = from_union([from_none, from_int], obj.get("declaredMaxTotalSubagents")) - declared_timeout_seconds = from_union([from_none, from_float], obj.get("declaredTimeoutSeconds")) - max_ai_credits = from_union([from_none, from_float], obj.get("maxAiCredits")) - max_concurrent_subagents = from_union([from_none, from_int], obj.get("maxConcurrentSubagents")) - max_total_subagents = from_union([from_none, from_int], obj.get("maxTotalSubagents")) - timeout_seconds = from_union([from_none, from_float], obj.get("timeoutSeconds")) - tool_call_id = from_union([from_none, from_str], obj.get("toolCallId")) - managed_approval_required = from_union([from_none, from_bool], obj.get("managedApprovalRequired")) - return PermissionRequestFactory( - approval_key=approval_key, - can_persist_approval=can_persist_approval, - description=description, - name=name, - operation=operation, - phases=phases, - declared_max_ai_credits=declared_max_ai_credits, - declared_max_concurrent_subagents=declared_max_concurrent_subagents, - declared_max_total_subagents=declared_max_total_subagents, - declared_timeout_seconds=declared_timeout_seconds, - max_ai_credits=max_ai_credits, - max_concurrent_subagents=max_concurrent_subagents, - max_total_subagents=max_total_subagents, - timeout_seconds=timeout_seconds, - tool_call_id=tool_call_id, - managed_approval_required=managed_approval_required, - ) - - def to_dict(self) -> dict: - result: dict = {} - result["approvalKey"] = from_str(self.approval_key) - result["canPersistApproval"] = from_bool(self.can_persist_approval) - result["description"] = from_str(self.description) - result["kind"] = self.kind - result["name"] = from_str(self.name) - result["operation"] = to_enum(FactoryPermissionOperation, self.operation) - result["phases"] = from_list(lambda x: to_class(FactoryPermissionPhase, x), self.phases) - if self.declared_max_ai_credits is not None: - result["declaredMaxAiCredits"] = from_union([from_none, to_float], self.declared_max_ai_credits) - if self.declared_max_concurrent_subagents is not None: - result["declaredMaxConcurrentSubagents"] = from_union([from_none, to_int], self.declared_max_concurrent_subagents) - if self.declared_max_total_subagents is not None: - result["declaredMaxTotalSubagents"] = from_union([from_none, to_int], self.declared_max_total_subagents) - if self.declared_timeout_seconds is not None: - result["declaredTimeoutSeconds"] = from_union([from_none, to_float], self.declared_timeout_seconds) - if self.max_ai_credits is not None: - result["maxAiCredits"] = from_union([from_none, to_float], self.max_ai_credits) - if self.max_concurrent_subagents is not None: - result["maxConcurrentSubagents"] = from_union([from_none, to_int], self.max_concurrent_subagents) - if self.max_total_subagents is not None: - result["maxTotalSubagents"] = from_union([from_none, to_int], self.max_total_subagents) - if self.timeout_seconds is not None: - result["timeoutSeconds"] = from_union([from_none, to_float], self.timeout_seconds) - if self.tool_call_id is not None: - result["toolCallId"] = from_union([from_none, from_str], self.tool_call_id) - if self.managed_approval_required is not None: - result["managedApprovalRequired"] = from_union([from_none, from_bool], self.managed_approval_required) - return result - - @dataclass class PermissionRequestHook: "Hook confirmation permission request" @@ -7808,17 +7698,108 @@ def from_dict(obj: Any) -> "PermissionRequestUrl": def to_dict(self) -> dict: result: dict = {} - result["intention"] = from_str(self.intention) + result["intention"] = from_str(self.intention) + result["kind"] = self.kind + result["url"] = from_str(self.url) + if self.managed_approval_required is not None: + result["managedApprovalRequired"] = from_union([from_none, from_bool], self.managed_approval_required) + if self.redirected_from is not None: + result["redirectedFrom"] = from_union([from_none, from_str], self.redirected_from) + if self.request_sandbox_bypass is not None: + result["requestSandboxBypass"] = from_union([from_none, from_bool], self.request_sandbox_bypass) + if self.request_sandbox_bypass_reason is not None: + result["requestSandboxBypassReason"] = from_union([from_none, from_str], self.request_sandbox_bypass_reason) + if self.tool_call_id is not None: + result["toolCallId"] = from_union([from_none, from_str], self.tool_call_id) + return result + + +@dataclass +class PermissionRequestWorkflow: + "Workflow run or authoring permission request" + approval_key: str + can_persist_approval: bool + description: str + kind: ClassVar[str] = "workflow" + name: str + operation: WorkflowPermissionOperation + phases: list[WorkflowPermissionPhase] + declared_max_ai_credits: float | None = None + declared_max_concurrent_subagents: int | None = None + declared_max_total_subagents: int | None = None + declared_timeout_seconds: float | None = None + managed_approval_required: bool | None = None + max_ai_credits: float | None = None + max_concurrent_subagents: int | None = None + max_total_subagents: int | None = None + timeout_seconds: float | None = None + tool_call_id: str | None = None + + @staticmethod + def from_dict(obj: Any) -> "PermissionRequestWorkflow": + assert isinstance(obj, dict) + approval_key = from_str(obj.get("approvalKey")) + can_persist_approval = from_bool(obj.get("canPersistApproval")) + description = from_str(obj.get("description")) + name = from_str(obj.get("name")) + operation = parse_enum(WorkflowPermissionOperation, obj.get("operation")) + phases = from_list(WorkflowPermissionPhase.from_dict, obj.get("phases")) + declared_max_ai_credits = from_union([from_none, from_float], obj.get("declaredMaxAiCredits")) + declared_max_concurrent_subagents = from_union([from_none, from_int], obj.get("declaredMaxConcurrentSubagents")) + declared_max_total_subagents = from_union([from_none, from_int], obj.get("declaredMaxTotalSubagents")) + declared_timeout_seconds = from_union([from_none, from_float], obj.get("declaredTimeoutSeconds")) + managed_approval_required = from_union([from_none, from_bool], obj.get("managedApprovalRequired")) + max_ai_credits = from_union([from_none, from_float], obj.get("maxAiCredits")) + max_concurrent_subagents = from_union([from_none, from_int], obj.get("maxConcurrentSubagents")) + max_total_subagents = from_union([from_none, from_int], obj.get("maxTotalSubagents")) + timeout_seconds = from_union([from_none, from_float], obj.get("timeoutSeconds")) + tool_call_id = from_union([from_none, from_str], obj.get("toolCallId")) + return PermissionRequestWorkflow( + approval_key=approval_key, + can_persist_approval=can_persist_approval, + description=description, + name=name, + operation=operation, + phases=phases, + declared_max_ai_credits=declared_max_ai_credits, + declared_max_concurrent_subagents=declared_max_concurrent_subagents, + declared_max_total_subagents=declared_max_total_subagents, + declared_timeout_seconds=declared_timeout_seconds, + managed_approval_required=managed_approval_required, + max_ai_credits=max_ai_credits, + max_concurrent_subagents=max_concurrent_subagents, + max_total_subagents=max_total_subagents, + timeout_seconds=timeout_seconds, + tool_call_id=tool_call_id, + ) + + def to_dict(self) -> dict: + result: dict = {} + result["approvalKey"] = from_str(self.approval_key) + result["canPersistApproval"] = from_bool(self.can_persist_approval) + result["description"] = from_str(self.description) result["kind"] = self.kind - result["url"] = from_str(self.url) + result["name"] = from_str(self.name) + result["operation"] = to_enum(WorkflowPermissionOperation, self.operation) + result["phases"] = from_list(lambda x: to_class(WorkflowPermissionPhase, x), self.phases) + if self.declared_max_ai_credits is not None: + result["declaredMaxAiCredits"] = from_union([from_none, to_float], self.declared_max_ai_credits) + if self.declared_max_concurrent_subagents is not None: + result["declaredMaxConcurrentSubagents"] = from_union([from_none, to_int], self.declared_max_concurrent_subagents) + if self.declared_max_total_subagents is not None: + result["declaredMaxTotalSubagents"] = from_union([from_none, to_int], self.declared_max_total_subagents) + if self.declared_timeout_seconds is not None: + result["declaredTimeoutSeconds"] = from_union([from_none, to_float], self.declared_timeout_seconds) if self.managed_approval_required is not None: result["managedApprovalRequired"] = from_union([from_none, from_bool], self.managed_approval_required) - if self.redirected_from is not None: - result["redirectedFrom"] = from_union([from_none, from_str], self.redirected_from) - if self.request_sandbox_bypass is not None: - result["requestSandboxBypass"] = from_union([from_none, from_bool], self.request_sandbox_bypass) - if self.request_sandbox_bypass_reason is not None: - result["requestSandboxBypassReason"] = from_union([from_none, from_str], self.request_sandbox_bypass_reason) + if self.max_ai_credits is not None: + result["maxAiCredits"] = from_union([from_none, to_float], self.max_ai_credits) + if self.max_concurrent_subagents is not None: + result["maxConcurrentSubagents"] = from_union([from_none, to_int], self.max_concurrent_subagents) + if self.max_total_subagents is not None: + result["maxTotalSubagents"] = from_union([from_none, to_int], self.max_total_subagents) + if self.timeout_seconds is not None: + result["timeoutSeconds"] = from_union([from_none, to_float], self.timeout_seconds) if self.tool_call_id is not None: result["toolCallId"] = from_union([from_none, from_str], self.tool_call_id) return result @@ -8144,7 +8125,7 @@ def to_dict(self) -> dict: @dataclass class ResponsesReasoning: - "Original request-level and effective conversation reasoning effort for a Responses history boundary" + "Original request-level and effective conversation reasoning effort for a provider history boundary; the historical type name is retained for compatibility" effort: str initial_effort: str model: str @@ -8517,6 +8498,8 @@ class SessionCompactionCompleteData: success: bool # Internal: this field is an internal SDK API and is not part of the public surface. _active_factory_summary: str | None = None + # Internal: this field is an internal SDK API and is not part of the public surface. + _active_workflow_summary: str | None = None behavior_model_id: str | None = None checkpoint_number: int | None = None checkpoint_path: str | None = None @@ -8544,6 +8527,7 @@ def from_dict(obj: Any) -> "SessionCompactionCompleteData": assert isinstance(obj, dict) success = from_bool(obj.get("success")) _active_factory_summary = from_union([from_none, from_str], obj.get("activeFactorySummary")) + _active_workflow_summary = from_union([from_none, from_str], obj.get("activeWorkflowSummary")) behavior_model_id = from_union([from_none, from_str], obj.get("behaviorModelId")) checkpoint_number = from_union([from_none, from_int], obj.get("checkpointNumber")) checkpoint_path = from_union([from_none, from_str], obj.get("checkpointPath")) @@ -8568,6 +8552,7 @@ def from_dict(obj: Any) -> "SessionCompactionCompleteData": return SessionCompactionCompleteData( success=success, _active_factory_summary=_active_factory_summary, + _active_workflow_summary=_active_workflow_summary, behavior_model_id=behavior_model_id, checkpoint_number=checkpoint_number, checkpoint_path=checkpoint_path, @@ -8596,6 +8581,8 @@ def to_dict(self) -> dict: result["success"] = from_bool(self.success) if self._active_factory_summary is not None: result["activeFactorySummary"] = from_union([from_none, from_str], self._active_factory_summary) + if self._active_workflow_summary is not None: + result["activeWorkflowSummary"] = from_union([from_none, from_str], self._active_workflow_summary) if self.behavior_model_id is not None: result["behaviorModelId"] = from_union([from_none, from_str], self.behavior_model_id) if self.checkpoint_number is not None: @@ -9897,21 +9884,26 @@ class SessionSnapshotRewindData: "Session rewind details including target event and count of removed events" events_removed: int up_to_event_id: str + event_ids: list[str] | None = None @staticmethod def from_dict(obj: Any) -> "SessionSnapshotRewindData": assert isinstance(obj, dict) events_removed = from_int(obj.get("eventsRemoved")) up_to_event_id = from_str(obj.get("upToEventId")) + event_ids = from_union([from_none, lambda x: from_list(from_str, x)], obj.get("eventIds")) return SessionSnapshotRewindData( events_removed=events_removed, up_to_event_id=up_to_event_id, + event_ids=event_ids, ) def to_dict(self) -> dict: result: dict = {} result["eventsRemoved"] = to_int(self.events_removed) result["upToEventId"] = from_str(self.up_to_event_id) + if self.event_ids is not None: + result["eventIds"] = from_union([from_none, lambda x: from_list(from_str, x)], self.event_ids) return result @@ -11015,6 +11007,7 @@ class SubagentStartedData: parent_id: str | None = None resumable: bool | None = None task_model_source: SubagentTaskModelSource | None = None + workflow_run_id: str | None = None @staticmethod def from_dict(obj: Any) -> "SubagentStartedData": @@ -11031,6 +11024,7 @@ def from_dict(obj: Any) -> "SubagentStartedData": parent_id = from_union([from_none, from_str], obj.get("parentId")) resumable = from_union([from_none, from_bool], obj.get("resumable")) task_model_source = from_union([from_none, lambda x: parse_enum(SubagentTaskModelSource, x)], obj.get("taskModelSource")) + workflow_run_id = from_union([from_none, from_str], obj.get("workflowRunId")) return SubagentStartedData( agent_description=agent_description, agent_display_name=agent_display_name, @@ -11044,6 +11038,7 @@ def from_dict(obj: Any) -> "SubagentStartedData": parent_id=parent_id, resumable=resumable, task_model_source=task_model_source, + workflow_run_id=workflow_run_id, ) def to_dict(self) -> dict: @@ -11068,6 +11063,8 @@ def to_dict(self) -> dict: result["resumable"] = from_union([from_none, from_bool], self.resumable) if self.task_model_source is not None: result["taskModelSource"] = from_union([from_none, lambda x: to_enum(SubagentTaskModelSource, x)], self.task_model_source) + if self.workflow_run_id is not None: + result["workflowRunId"] = from_union([from_none, from_str], self.workflow_run_id) return result @@ -11280,95 +11277,6 @@ def to_dict(self) -> dict: return result -@dataclass -class SystemNotificationFactoryCompleted: - "System notification metadata for a factory execution attempt that reached a terminal state." - attempt: int - consumed_nano_aiu: int - consumed_subagents: int - elapsed_ms: int - factory_name: str - run_id: str - status: SystemNotificationFactoryCompletedStatus - type: ClassVar[str] = "factory_completed" - failure: Any = None - pause_info: SystemNotificationFactoryPauseInfo | None = None - result_preview: str | None = None - retry_guidance: str | None = None - - @staticmethod - def from_dict(obj: Any) -> "SystemNotificationFactoryCompleted": - assert isinstance(obj, dict) - attempt = from_int(obj.get("attempt")) - consumed_nano_aiu = from_int(obj.get("consumedNanoAiu")) - consumed_subagents = from_int(obj.get("consumedSubagents")) - elapsed_ms = from_int(obj.get("elapsedMs")) - factory_name = from_str(obj.get("factoryName")) - run_id = from_str(obj.get("runId")) - status = parse_enum(SystemNotificationFactoryCompletedStatus, obj.get("status")) - failure = obj.get("failure") - pause_info = from_union([from_none, SystemNotificationFactoryPauseInfo.from_dict], obj.get("pauseInfo")) - result_preview = from_union([from_none, from_str], obj.get("resultPreview")) - retry_guidance = from_union([from_none, from_str], obj.get("retryGuidance")) - return SystemNotificationFactoryCompleted( - attempt=attempt, - consumed_nano_aiu=consumed_nano_aiu, - consumed_subagents=consumed_subagents, - elapsed_ms=elapsed_ms, - factory_name=factory_name, - run_id=run_id, - status=status, - failure=failure, - pause_info=pause_info, - result_preview=result_preview, - retry_guidance=retry_guidance, - ) - - def to_dict(self) -> dict: - result: dict = {} - result["attempt"] = to_int(self.attempt) - result["consumedNanoAiu"] = to_int(self.consumed_nano_aiu) - result["consumedSubagents"] = to_int(self.consumed_subagents) - result["elapsedMs"] = to_int(self.elapsed_ms) - result["factoryName"] = from_str(self.factory_name) - result["runId"] = from_str(self.run_id) - result["status"] = to_enum(SystemNotificationFactoryCompletedStatus, self.status) - result["type"] = self.type - if self.failure is not None: - result["failure"] = self.failure - if self.pause_info is not None: - result["pauseInfo"] = from_union([from_none, lambda x: to_class(SystemNotificationFactoryPauseInfo, x)], self.pause_info) - if self.result_preview is not None: - result["resultPreview"] = from_union([from_none, from_str], self.result_preview) - if self.retry_guidance is not None: - result["retryGuidance"] = from_union([from_none, from_str], self.retry_guidance) - return result - - -@dataclass -class SystemNotificationFactoryPauseInfo: - "Durable metadata describing who initiated a factory pause." - type: SystemNotificationFactoryPauseInfoType - key: str | None = None - - @staticmethod - def from_dict(obj: Any) -> "SystemNotificationFactoryPauseInfo": - assert isinstance(obj, dict) - type = parse_enum(SystemNotificationFactoryPauseInfoType, obj.get("type")) - key = from_union([from_none, from_str], obj.get("key")) - return SystemNotificationFactoryPauseInfo( - type=type, - key=key, - ) - - def to_dict(self) -> dict: - result: dict = {} - result["type"] = to_enum(SystemNotificationFactoryPauseInfoType, self.type) - if self.key is not None: - result["key"] = from_union([from_none, from_str], self.key) - return result - - @dataclass class SystemNotificationInstructionDiscovered: "System notification metadata for an instruction file discovered during tool access, including source, trigger file, and tool." @@ -11515,6 +11423,95 @@ def to_dict(self) -> dict: return result +@dataclass +class SystemNotificationWorkflowCompleted: + "System notification metadata for a workflow execution attempt that reached a terminal state." + attempt: int + consumed_nano_aiu: int + consumed_subagents: int + elapsed_ms: int + run_id: str + status: SystemNotificationWorkflowCompletedStatus + type: ClassVar[str] = "workflow_completed" + workflow_name: str + failure: Any = None + pause_info: SystemNotificationWorkflowPauseInfo | None = None + result_preview: str | None = None + retry_guidance: str | None = None + + @staticmethod + def from_dict(obj: Any) -> "SystemNotificationWorkflowCompleted": + assert isinstance(obj, dict) + attempt = from_int(obj.get("attempt")) + consumed_nano_aiu = from_int(obj.get("consumedNanoAiu")) + consumed_subagents = from_int(obj.get("consumedSubagents")) + elapsed_ms = from_int(obj.get("elapsedMs")) + run_id = from_str(obj.get("runId")) + status = parse_enum(SystemNotificationWorkflowCompletedStatus, obj.get("status")) + workflow_name = from_str(obj.get("workflowName")) + failure = obj.get("failure") + pause_info = from_union([from_none, SystemNotificationWorkflowPauseInfo.from_dict], obj.get("pauseInfo")) + result_preview = from_union([from_none, from_str], obj.get("resultPreview")) + retry_guidance = from_union([from_none, from_str], obj.get("retryGuidance")) + return SystemNotificationWorkflowCompleted( + attempt=attempt, + consumed_nano_aiu=consumed_nano_aiu, + consumed_subagents=consumed_subagents, + elapsed_ms=elapsed_ms, + run_id=run_id, + status=status, + workflow_name=workflow_name, + failure=failure, + pause_info=pause_info, + result_preview=result_preview, + retry_guidance=retry_guidance, + ) + + def to_dict(self) -> dict: + result: dict = {} + result["attempt"] = to_int(self.attempt) + result["consumedNanoAiu"] = to_int(self.consumed_nano_aiu) + result["consumedSubagents"] = to_int(self.consumed_subagents) + result["elapsedMs"] = to_int(self.elapsed_ms) + result["runId"] = from_str(self.run_id) + result["status"] = to_enum(SystemNotificationWorkflowCompletedStatus, self.status) + result["type"] = self.type + result["workflowName"] = from_str(self.workflow_name) + if self.failure is not None: + result["failure"] = self.failure + if self.pause_info is not None: + result["pauseInfo"] = from_union([from_none, lambda x: to_class(SystemNotificationWorkflowPauseInfo, x)], self.pause_info) + if self.result_preview is not None: + result["resultPreview"] = from_union([from_none, from_str], self.result_preview) + if self.retry_guidance is not None: + result["retryGuidance"] = from_union([from_none, from_str], self.retry_guidance) + return result + + +@dataclass +class SystemNotificationWorkflowPauseInfo: + "Durable metadata describing who initiated a workflow pause." + type: SystemNotificationWorkflowPauseInfoType + key: str | None = None + + @staticmethod + def from_dict(obj: Any) -> "SystemNotificationWorkflowPauseInfo": + assert isinstance(obj, dict) + type = parse_enum(SystemNotificationWorkflowPauseInfoType, obj.get("type")) + key = from_union([from_none, from_str], obj.get("key")) + return SystemNotificationWorkflowPauseInfo( + type=type, + key=key, + ) + + def to_dict(self) -> dict: + result: dict = {} + result["type"] = to_enum(SystemNotificationWorkflowPauseInfoType, self.type) + if self.key is not None: + result["key"] = from_union([from_none, from_str], self.key) + return result + + @dataclass class ToolExecutionCompleteContentAudio: "Audio content block with base64-encoded data" @@ -12813,28 +12810,6 @@ def to_dict(self) -> dict: return result -@dataclass -class UserToolSessionApprovalFactory: - "Session-scoped factory approval, optionally narrowed by approval key." - kind: ClassVar[str] = "factory" - approval_key: str | None = None - - @staticmethod - def from_dict(obj: Any) -> "UserToolSessionApprovalFactory": - assert isinstance(obj, dict) - approval_key = from_union([from_none, from_str], obj.get("approvalKey")) - return UserToolSessionApprovalFactory( - approval_key=approval_key, - ) - - def to_dict(self) -> dict: - result: dict = {} - result["kind"] = self.kind - if self.approval_key is not None: - result["approvalKey"] = from_union([from_none, from_str], self.approval_key) - return result - - @dataclass class UserToolSessionApprovalMcp: "Session-scoped tool-approval rule for an MCP server tool, or all tools on the server when `toolName` is null." @@ -12894,6 +12869,28 @@ def to_dict(self) -> dict: return result +@dataclass +class UserToolSessionApprovalWorkflow: + "Session-scoped workflow approval, optionally narrowed by approval key." + kind: ClassVar[str] = "workflow" + approval_key: str | None = None + + @staticmethod + def from_dict(obj: Any) -> "UserToolSessionApprovalWorkflow": + assert isinstance(obj, dict) + approval_key = from_union([from_none, from_str], obj.get("approvalKey")) + return UserToolSessionApprovalWorkflow( + approval_key=approval_key, + ) + + def to_dict(self) -> dict: + result: dict = {} + result["kind"] = self.kind + if self.approval_key is not None: + result["approvalKey"] = from_union([from_none, from_str], self.approval_key) + return result + + @dataclass class UserToolSessionApprovalWrite: "Session-scoped tool-approval rule for filesystem write operations." @@ -13135,6 +13132,32 @@ def to_dict(self) -> dict: if self.notification is not None: result["notification"] = from_union([from_none, lambda x: to_class(WorkerNotificationReference, x)], self.notification) return result + + +@dataclass +class WorkflowPermissionPhase: + "A declared phase shown in a workflow permission prompt." + title: str + detail: str | None = None + + @staticmethod + def from_dict(obj: Any) -> "WorkflowPermissionPhase": + assert isinstance(obj, dict) + title = from_str(obj.get("title")) + detail = from_union([from_none, from_str], obj.get("detail")) + return WorkflowPermissionPhase( + title=title, + detail=detail, + ) + + def to_dict(self) -> dict: + result: dict = {} + result["title"] = from_str(self.title) + if self.detail is not None: + result["detail"] = from_union([from_none, from_str], self.detail) + return result + + @dataclass class WorkingDirectoryContext: "Working directory and git context at session start" @@ -13240,7 +13263,7 @@ def _load_PermissionPromptRequest(obj: Any) -> "PermissionPromptRequest": case "path": return PermissionPromptRequestPath.from_dict(obj) case "hook": return PermissionPromptRequestHook.from_dict(obj) case "extension-management": return PermissionPromptRequestExtensionManagement.from_dict(obj) - case "factory": return PermissionPromptRequestFactory.from_dict(obj) + case "workflow": return PermissionPromptRequestWorkflow.from_dict(obj) case "extension-permission-access": return PermissionPromptRequestExtensionPermissionAccess.from_dict(obj) case "extension-env-access": return PermissionPromptRequestExtensionEnvAccess.from_dict(obj) case _: raise ValueError(f"Unknown PermissionPromptRequest kind: {kind!r}") @@ -13259,7 +13282,7 @@ def _load_PermissionRequest(obj: Any) -> "PermissionRequest": case "custom-tool": return PermissionRequestCustomTool.from_dict(obj) case "hook": return PermissionRequestHook.from_dict(obj) case "extension-management": return PermissionRequestExtensionManagement.from_dict(obj) - case "factory": return PermissionRequestFactory.from_dict(obj) + case "workflow": return PermissionRequestWorkflow.from_dict(obj) case "extension-permission-access": return PermissionRequestExtensionPermissionAccess.from_dict(obj) case "extension-env-access": return PermissionRequestExtensionEnvAccess.from_dict(obj) case _: raise ValueError(f"Unknown PermissionRequest kind: {kind!r}") @@ -13291,7 +13314,7 @@ def _load_SystemNotification(obj: Any) -> "SystemNotification": case "shell_completed": return SystemNotificationShellCompleted.from_dict(obj) case "shell_detached_completed": return SystemNotificationShellDetachedCompleted.from_dict(obj) case "instruction_discovered": return SystemNotificationInstructionDiscovered.from_dict(obj) - case "factory_completed": return SystemNotificationFactoryCompleted.from_dict(obj) + case "workflow_completed": return SystemNotificationWorkflowCompleted.from_dict(obj) case "unclassified": return SystemNotificationUnclassified.from_dict(obj) case _: raise ValueError(f"Unknown SystemNotification type: {kind!r}") @@ -13321,7 +13344,7 @@ def _load_UserToolSessionApproval(obj: Any) -> "UserToolSessionApproval": case "memory": return UserToolSessionApprovalMemory.from_dict(obj) case "custom-tool": return UserToolSessionApprovalCustomTool.from_dict(obj) case "extension-management": return UserToolSessionApprovalExtensionManagement.from_dict(obj) - case "factory": return UserToolSessionApprovalFactory.from_dict(obj) + case "workflow": return UserToolSessionApprovalWorkflow.from_dict(obj) case "extension-permission-access": return UserToolSessionApprovalExtensionPermissionAccess.from_dict(obj) case "extension-env-access": return UserToolSessionApprovalExtensionEnvAccess.from_dict(obj) case _: raise ValueError(f"Unknown UserToolSessionApproval kind: {kind!r}") @@ -13340,11 +13363,11 @@ def _load_UserToolSessionApproval(obj: Any) -> "UserToolSessionApproval": # Derived user-facing permission prompt details for UI consumers -PermissionPromptRequest = PermissionPromptRequestCommands | PermissionPromptRequestWrite | PermissionPromptRequestRead | PermissionPromptRequestMcp | PermissionPromptRequestUrl | PermissionPromptRequestMemory | PermissionPromptRequestCustomTool | PermissionPromptRequestPath | PermissionPromptRequestHook | PermissionPromptRequestExtensionManagement | PermissionPromptRequestFactory | PermissionPromptRequestExtensionPermissionAccess | PermissionPromptRequestExtensionEnvAccess +PermissionPromptRequest = PermissionPromptRequestCommands | PermissionPromptRequestWrite | PermissionPromptRequestRead | PermissionPromptRequestMcp | PermissionPromptRequestUrl | PermissionPromptRequestMemory | PermissionPromptRequestCustomTool | PermissionPromptRequestPath | PermissionPromptRequestHook | PermissionPromptRequestExtensionManagement | PermissionPromptRequestWorkflow | PermissionPromptRequestExtensionPermissionAccess | PermissionPromptRequestExtensionEnvAccess # Details of the permission being requested -PermissionRequest = PermissionRequestShell | PermissionRequestWrite | PermissionRequestRead | PermissionRequestMcp | PermissionRequestUrl | PermissionRequestMemory | PermissionRequestCustomTool | PermissionRequestHook | PermissionRequestExtensionManagement | PermissionRequestFactory | PermissionRequestExtensionPermissionAccess | PermissionRequestExtensionEnvAccess +PermissionRequest = PermissionRequestShell | PermissionRequestWrite | PermissionRequestRead | PermissionRequestMcp | PermissionRequestUrl | PermissionRequestMemory | PermissionRequestCustomTool | PermissionRequestHook | PermissionRequestExtensionManagement | PermissionRequestWorkflow | PermissionRequestExtensionPermissionAccess | PermissionRequestExtensionEnvAccess # Location within a cited source (character, page, or content-block range) that supports a span. @@ -13352,11 +13375,11 @@ def _load_UserToolSessionApproval(obj: Any) -> "UserToolSessionApproval": # Structured metadata identifying what triggered this notification -SystemNotification = SystemNotificationAgentCompleted | SystemNotificationAgentIdle | SystemNotificationNewInboxMessage | SystemNotificationShellCompleted | SystemNotificationShellDetachedCompleted | SystemNotificationInstructionDiscovered | SystemNotificationFactoryCompleted | SystemNotificationUnclassified +SystemNotification = SystemNotificationAgentCompleted | SystemNotificationAgentIdle | SystemNotificationNewInboxMessage | SystemNotificationShellCompleted | SystemNotificationShellDetachedCompleted | SystemNotificationInstructionDiscovered | SystemNotificationWorkflowCompleted | SystemNotificationUnclassified # The approval to add as a session-scoped rule -UserToolSessionApproval = UserToolSessionApprovalCommands | UserToolSessionApprovalRead | UserToolSessionApprovalWrite | UserToolSessionApprovalMcp | UserToolSessionApprovalMemory | UserToolSessionApprovalCustomTool | UserToolSessionApprovalExtensionManagement | UserToolSessionApprovalFactory | UserToolSessionApprovalExtensionPermissionAccess | UserToolSessionApprovalExtensionEnvAccess +UserToolSessionApproval = UserToolSessionApprovalCommands | UserToolSessionApprovalRead | UserToolSessionApprovalWrite | UserToolSessionApprovalMcp | UserToolSessionApprovalMemory | UserToolSessionApprovalCustomTool | UserToolSessionApprovalExtensionManagement | UserToolSessionApprovalWorkflow | UserToolSessionApprovalExtensionPermissionAccess | UserToolSessionApprovalExtensionEnvAccess # The embedded resource contents, either text or base64-encoded binary @@ -13498,10 +13521,10 @@ class FusionTurnKind(Enum): # Experimental: this enum is part of an experimental API and may change or be removed. class PermissionMessageAuthorizationPolarity(Enum): - "Which direction a message-backed authorization claim moves authority in." - # The human's words authorized an effect. + "Direction stored in a historical extractor claim. Current runtimes do not apply it." + # Historical claim recorded as a grant. GRANT = "grant" - # The human's words refused an effect. + # Historical claim recorded as a denial. DENIAL = "denial" @@ -13803,28 +13826,6 @@ class ExtensionsLoadedExtensionStatus(Enum): STARTING = "starting" -class FactoryPermissionOperation(Enum): - "Operation gated by a factory permission request." - # Running a registered factory, which spends subagents, active time, and AI credits under the approved limits. - RUN = "run" - # Authoring a factory, which writes JavaScript into a session-scoped extension and loads it. - AUTHOR = "author" - - -class FactoryRunSettledStatus(Enum): - "Terminal status a factory run committed. A settled run is never `pending` or `running`, so those two members of the run-status domain are deliberately absent." - # The factory body resolved and its result was committed. - COMPLETED = "completed" - # The run was stopped by a limit, an approval refusal or another policy decision. - HALTED = "halted" - # The attempt paused intentionally while preserving resumable run state. - PAUSED = "paused" - # The run was cancelled by its caller or by session disposal. - CANCELLED = "cancelled" - # The run failed, with `failureType` carrying the class when it has one. - ERROR = "error" - - class HandoffSourceType(Enum): "Origin type of the session being handed off" # The handoff originated from a remote session. @@ -14099,6 +14100,8 @@ class ModelChangeSource(Enum): CHANGEBOARDING_SHORTCUT = "changeboarding_shortcut" # An SDK or RPC caller selected the model. SDK = "sdk" + # The user accepted a CAPI-issued Auto tier recommendation. + AUTO_TIER_RECOMMENDATION = "auto_tier_recommendation" class ModelDeselectedReason(Enum): @@ -14219,7 +14222,7 @@ class PermissionDecisionSource(Enum): HOST_POLICY = "host_policy" # The host denied the request because no interactive user response was available. UNATTENDED_FALLBACK = "unattended_fallback" - # A live authorization record from an earlier human decision in this session contained the proposal, so it ran without another prompt. This is not a new human decision and never mints authority of its own. + # Historical compatibility value for sessions created while authorization carry-forward was executable. Current runtimes do not produce this source. AUTHORIZATION_CARRY_FORWARD = "authorization_carry_forward" @@ -14692,22 +14695,22 @@ class SystemNotificationAgentCompletedStatus(Enum): FAILED = "failed" -class SystemNotificationFactoryCompletedStatus(Enum): - "Terminal status reached by a factory execution attempt." - # The factory completed successfully. +class SystemNotificationWorkflowCompletedStatus(Enum): + "Terminal status reached by a workflow execution attempt." + # The workflow completed successfully. COMPLETED = "completed" - # The factory was halted. + # The workflow was halted. HALTED = "halted" - # The factory attempt paused intentionally. + # The workflow attempt paused intentionally. PAUSED = "paused" - # The factory was cancelled. + # The workflow was cancelled. CANCELLED = "cancelled" - # The factory failed. + # The workflow failed. ERROR = "error" -class SystemNotificationFactoryPauseInfoType(Enum): - "Durable metadata describing who initiated a factory pause. discriminator" +class SystemNotificationWorkflowPauseInfoType(Enum): + "Durable metadata describing who initiated a workflow pause. discriminator" USER = "user" CHECKPOINT = "checkpoint" @@ -14811,6 +14814,28 @@ class WorkerObservationProvenance(Enum): AHP_COORDINATOR = "ahp_coordinator" +class WorkflowPermissionOperation(Enum): + "Operation gated by a workflow permission request." + # Running a registered workflow, which spends subagents, active time, and AI credits under the approved limits. + RUN = "run" + # Authoring a workflow, which writes JavaScript into a session-scoped extension and loads it. + AUTHOR = "author" + + +class WorkflowRunSettledStatus(Enum): + "Terminal status a workflow run committed. A settled run is never `pending` or `running`, so those two members of the run-status domain are deliberately absent." + # The workflow body resolved and its result was committed. + COMPLETED = "completed" + # The run was stopped by a limit, an approval refusal or another policy decision. + HALTED = "halted" + # The attempt paused intentionally while preserving resumable run state. + PAUSED = "paused" + # The run was cancelled by its caller or by session disposal. + CANCELLED = "cancelled" + # The run failed, with `failureType` carrying the class when it has one. + ERROR = "error" + + class WorkingDirectoryContextHostType(Enum): "Hosting platform type of the repository (github or ado)" # Repository is hosted on GitHub. @@ -14827,7 +14852,7 @@ class WorkspaceFileChangedOperation(Enum): UPDATE = "update" -SessionEventData = SessionStartData | SessionResumeData | SessionRemoteSteerableChangedData | SessionErrorData | SessionIdleData | SessionTitleChangedData | SessionScheduleCreatedData | SessionScheduleCancelledData | SessionScheduleRearmedData | SessionAutopilotObjectiveChangedData | SessionInfoData | SessionIndexedSearchData | SessionWarningData | SessionModelChangeData | SessionModelDeselectedData | SessionAutoTierRecommendationData | SessionAutoTierSwitchFailedData | SessionModeChangedData | SessionModeNoticeDeliveredData | SessionSessionLimitsChangedData | SessionPermissionsChangedData | SessionPlanChangedData | SessionTodosChangedData | SessionWorkspaceFileChangedData | SessionHandoffData | SessionTruncationData | SessionSnapshotRewindData | SessionShutdownData | SessionUsageCheckpointData | SessionContextChangedData | SessionUsageInfoData | SessionContextClearedData | SessionCompactionStartData | SessionCompactionCompleteData | SessionTaskCompleteData | SessionCompletionReceiptData | SessionFusionRouteStartedData | SessionFusionRouteFailedData | SessionFusionResolvedData | SessionFusionCompletedData | SessionPermissionRecoveryData | UserMessageData | PendingMessagesModifiedData | AssistantTurnStartData | AssistantTurnRetryData | AgentInterruptedData | AssistantIntentData | AssistantFusionPhaseStartedData | AssistantFusionPhaseActivityData | AssistantFusionPhaseCompletedData | AssistantFusionPhaseFailedData | AssistantServerToolProgressData | AssistantReasoningData | AssistantReasoningDeltaData | AssistantToolCallDeltaData | AssistantStreamingDeltaData | AssistantMessageData | AssistantMessageStartData | AssistantMessageDeltaData | AssistantTurnEndData | AssistantIdleData | AssistantUsageData | PromptCacheBreakData | ModelCallFailureData | ModelCallFinishedData | ModelCallStartData | AbortData | ToolUserRequestedData | ToolExecutionStartData | ToolExecutionPartialResultData | ToolExecutionProgressData | ToolExecutionCompleteData | ToolSearchActivatedData | SkillInvokedData | SkillInvokedRefData | SkillContextDeliveredData | SkillContextDeliveredRefData | SandboxDecisionData | SubagentStartedData | SubagentConfiguredData | SubagentCompletedData | SubagentFailedData | SubagentSelectedData | SubagentDeselectedData | HookStartData | HookEndData | HookProgressData | SessionBinaryAssetData | SystemMessageData | SystemNotificationData | PermissionRequestedData | PermissionCompletedData | PermissionCarriedForwardData | PermissionMessageAuthorizationData | PermissionMessageAuthorizationReadData | PermissionMessageAuthorizationDegradedData | PermissionAssentDetectedData | PermissionContextualAuthorizationData | UserInputRequestedData | UserInputCompletedData | ElicitationRequestedData | ElicitationCompletedData | SamplingRequestedData | SamplingCompletedData | McpOauthRequiredData | McpOauthCompletedData | McpHeadersRefreshRequiredData | McpHeadersRefreshCompletedData | SessionCustomNotificationData | UiEphemeralQueryData | ExternalToolRequestedData | ExternalToolCompletedData | CommandQueuedData | CommandExecuteData | CommandCompletedData | AutoModeSwitchRequestedData | AutoModeSwitchCompletedData | SessionLimitsExhaustedRequestedData | SessionLimitsExhaustedCompletedData | SessionAutoModeResolvedData | SessionManagedSettingsResolvedData | SessionManagedSettingsEnforcedData | CommandsChangedData | CapabilitiesChangedData | ExitPlanModeRequestedData | ExitPlanModeCompletedData | SessionToolsUpdatedData | SessionBackgroundTasksChangedData | FactoryRunUpdatedData | FactoryRunStartedData | FactoryRunSettledData | SessionSkillsLoadedData | SessionCustomAgentsUpdatedData | SessionMcpServersLoadedData | SessionMcpServerStatusChangedData | SessionMcpServerRemovedData | SessionMcpServerNeedsReconnectData | McpToolsListChangedData | McpResourcesListChangedData | McpPromptsListChangedData | SessionExtensionsLoadedData | SessionCanvasOpenedData | SessionCanvasRegistryChangedData | SessionCanvasClosedData | SessionCanvasUnavailableData | SessionCanvasRecordedData | SessionCanvasRemovedData | SessionExtensionsAttachmentsPushedData | McpAppToolCallCompleteData | RawSessionEventData | Data +SessionEventData = SessionStartData | SessionResumeData | SessionRemoteSteerableChangedData | SessionErrorData | SessionIdleData | SessionTitleChangedData | SessionScheduleCreatedData | SessionScheduleCancelledData | SessionScheduleRearmedData | SessionAutopilotObjectiveChangedData | SessionInfoData | SessionIndexedSearchData | SessionWarningData | SessionModelChangeData | SessionModelDeselectedData | SessionAutoTierRecommendationData | SessionAutoTierSwitchFailedData | SessionModeChangedData | SessionModeNoticeDeliveredData | SessionSessionLimitsChangedData | SessionPermissionsChangedData | SessionPlanChangedData | SessionTodosChangedData | SessionWorkspaceFileChangedData | SessionHandoffData | SessionTruncationData | SessionSnapshotRewindData | SessionShutdownData | SessionUsageCheckpointData | SessionContextChangedData | SessionUsageInfoData | SessionContextClearedData | SessionCompactionStartData | SessionCompactionCompleteData | SessionTaskCompleteData | SessionCompletionReceiptData | SessionFusionRouteStartedData | SessionFusionRouteFailedData | SessionFusionResolvedData | SessionFusionCompletedData | SessionPermissionRecoveryData | UserMessageData | PendingMessagesModifiedData | AssistantTurnStartData | AssistantTurnRetryData | AgentInterruptedData | AssistantIntentData | AssistantFusionPhaseStartedData | AssistantFusionPhaseActivityData | AssistantFusionPhaseCompletedData | AssistantFusionPhaseFailedData | AssistantServerToolProgressData | AssistantReasoningData | AssistantReasoningDeltaData | AssistantToolCallDeltaData | AssistantStreamingDeltaData | AssistantMessageData | AssistantMessageStartData | AssistantMessageDeltaData | AssistantTurnEndData | AssistantIdleData | AssistantUsageData | PromptCacheBreakData | ModelCallFailureData | ModelCallFinishedData | ModelCallStartData | AbortData | ToolUserRequestedData | ToolExecutionStartData | ToolExecutionPartialResultData | ToolExecutionProgressData | ToolExecutionCompleteData | ToolSearchActivatedData | SkillInvokedData | SkillInvokedRefData | SkillContextDeliveredData | SkillContextDeliveredRefData | SandboxDecisionData | SubagentStartedData | SubagentConfiguredData | SubagentCompletedData | SubagentFailedData | SubagentSelectedData | SubagentDeselectedData | HookStartData | HookEndData | HookProgressData | SessionBinaryAssetData | SystemMessageData | SystemNotificationData | PermissionRequestedData | PermissionCompletedData | PermissionCarriedForwardData | PermissionMessageAuthorizationData | PermissionMessageAuthorizationReadData | PermissionMessageAuthorizationDegradedData | PermissionAssentDetectedData | PermissionContextualAuthorizationData | UserInputRequestedData | UserInputCompletedData | ElicitationRequestedData | ElicitationCompletedData | SamplingRequestedData | SamplingCompletedData | McpOauthRequiredData | McpOauthCompletedData | McpHeadersRefreshRequiredData | McpHeadersRefreshCompletedData | SessionCustomNotificationData | UiEphemeralQueryData | ExternalToolRequestedData | ExternalToolCompletedData | CommandQueuedData | CommandExecuteData | CommandCompletedData | AutoModeSwitchRequestedData | AutoModeSwitchCompletedData | SessionLimitsExhaustedRequestedData | SessionLimitsExhaustedCompletedData | SessionAutoModeResolvedData | SessionManagedSettingsResolvedData | SessionManagedSettingsEnforcedData | CommandsChangedData | CapabilitiesChangedData | ExitPlanModeRequestedData | ExitPlanModeCompletedData | SessionToolsUpdatedData | SessionBackgroundTasksChangedData | WorkflowRunUpdatedData | WorkflowRunStartedData | WorkflowRunSettledData | SessionSkillsLoadedData | SessionCustomAgentsUpdatedData | SessionMcpServersLoadedData | SessionMcpServerStatusChangedData | SessionMcpServerRemovedData | SessionMcpServerNeedsReconnectData | McpToolsListChangedData | McpResourcesListChangedData | McpPromptsListChangedData | SessionExtensionsLoadedData | SessionCanvasOpenedData | SessionCanvasRegistryChangedData | SessionCanvasClosedData | SessionCanvasUnavailableData | SessionCanvasRecordedData | SessionCanvasRemovedData | SessionExtensionsAttachmentsPushedData | McpAppToolCallCompleteData | RawSessionEventData | Data @dataclass @@ -14981,9 +15006,9 @@ def from_dict(obj: Any) -> "SessionEvent": case SessionEventType.EXIT_PLAN_MODE_COMPLETED: data = ExitPlanModeCompletedData.from_dict(data_obj) case SessionEventType.SESSION_TOOLS_UPDATED: data = SessionToolsUpdatedData.from_dict(data_obj) case SessionEventType.SESSION_BACKGROUND_TASKS_CHANGED: data = SessionBackgroundTasksChangedData.from_dict(data_obj) - case SessionEventType.FACTORY_RUN_UPDATED: data = FactoryRunUpdatedData.from_dict(data_obj) - case SessionEventType.FACTORY_RUN_STARTED: data = FactoryRunStartedData.from_dict(data_obj) - case SessionEventType.FACTORY_RUN_SETTLED: data = FactoryRunSettledData.from_dict(data_obj) + case SessionEventType.WORKFLOW_RUN_UPDATED: data = WorkflowRunUpdatedData.from_dict(data_obj) + case SessionEventType.WORKFLOW_RUN_STARTED: data = WorkflowRunStartedData.from_dict(data_obj) + case SessionEventType.WORKFLOW_RUN_SETTLED: data = WorkflowRunSettledData.from_dict(data_obj) case SessionEventType.SESSION_SKILLS_LOADED: data = SessionSkillsLoadedData.from_dict(data_obj) case SessionEventType.SESSION_CUSTOM_AGENTS_UPDATED: data = SessionCustomAgentsUpdatedData.from_dict(data_obj) case SessionEventType.SESSION_MCP_SERVERS_LOADED: data = SessionMcpServersLoadedData.from_dict(data_obj) @@ -15150,12 +15175,6 @@ def session_event_to_dict(x: SessionEvent) -> Any: "ExtensionsLoadedExtensionStatus", "ExternalToolCompletedData", "ExternalToolRequestedData", - "FactoryPermissionOperation", - "FactoryPermissionPhase", - "FactoryRunSettledData", - "FactoryRunSettledStatus", - "FactoryRunStartedData", - "FactoryRunUpdatedData", "FusionAttribution", "FusionConversationScope", "FusionFollowUpAction", @@ -15253,7 +15272,6 @@ def session_event_to_dict(x: SessionEvent) -> Any: "PermissionPromptRequestExtensionEnvAccess", "PermissionPromptRequestExtensionManagement", "PermissionPromptRequestExtensionPermissionAccess", - "PermissionPromptRequestFactory", "PermissionPromptRequestHook", "PermissionPromptRequestMcp", "PermissionPromptRequestMemory", @@ -15261,6 +15279,7 @@ def session_event_to_dict(x: SessionEvent) -> Any: "PermissionPromptRequestPathAccessKind", "PermissionPromptRequestRead", "PermissionPromptRequestUrl", + "PermissionPromptRequestWorkflow", "PermissionPromptRequestWrite", "PermissionRecommendation", "PermissionRecoveryAttempt", @@ -15276,7 +15295,6 @@ def session_event_to_dict(x: SessionEvent) -> Any: "PermissionRequestExtensionEnvAccess", "PermissionRequestExtensionManagement", "PermissionRequestExtensionPermissionAccess", - "PermissionRequestFactory", "PermissionRequestHook", "PermissionRequestMcp", "PermissionRequestMemory", @@ -15289,6 +15307,7 @@ def session_event_to_dict(x: SessionEvent) -> Any: "PermissionRequestShellCommandSegment", "PermissionRequestShellPossibleUrl", "PermissionRequestUrl", + "PermissionRequestWorkflow", "PermissionRequestWrite", "PermissionRequestedData", "PermissionResult", @@ -15425,15 +15444,15 @@ def session_event_to_dict(x: SessionEvent) -> Any: "SystemNotificationAgentCompletedStatus", "SystemNotificationAgentIdle", "SystemNotificationData", - "SystemNotificationFactoryCompleted", - "SystemNotificationFactoryCompletedStatus", - "SystemNotificationFactoryPauseInfo", - "SystemNotificationFactoryPauseInfoType", "SystemNotificationInstructionDiscovered", "SystemNotificationNewInboxMessage", "SystemNotificationShellCompleted", "SystemNotificationShellDetachedCompleted", "SystemNotificationUnclassified", + "SystemNotificationWorkflowCompleted", + "SystemNotificationWorkflowCompletedStatus", + "SystemNotificationWorkflowPauseInfo", + "SystemNotificationWorkflowPauseInfoType", "TaskBlocker", "TaskBlockerKind", "TaskCompletionOutcome", @@ -15488,10 +15507,10 @@ def session_event_to_dict(x: SessionEvent) -> Any: "UserToolSessionApprovalExtensionEnvAccess", "UserToolSessionApprovalExtensionManagement", "UserToolSessionApprovalExtensionPermissionAccess", - "UserToolSessionApprovalFactory", "UserToolSessionApprovalMcp", "UserToolSessionApprovalMemory", "UserToolSessionApprovalRead", + "UserToolSessionApprovalWorkflow", "UserToolSessionApprovalWrite", "Verbosity", "WorkerAdmission", @@ -15505,6 +15524,12 @@ def session_event_to_dict(x: SessionEvent) -> Any: "WorkerNotificationReference", "WorkerObservationProvenance", "WorkerSource", + "WorkflowPermissionOperation", + "WorkflowPermissionPhase", + "WorkflowRunSettledData", + "WorkflowRunSettledStatus", + "WorkflowRunStartedData", + "WorkflowRunUpdatedData", "WorkingDirectoryContext", "WorkingDirectoryContextHostType", "WorkspaceFileChangedOperation", diff --git a/python/test_worker_causality.py b/python/test_worker_causality.py index bc2e5762eb..696655abba 100644 --- a/python/test_worker_causality.py +++ b/python/test_worker_causality.py @@ -49,3 +49,13 @@ def test_worker_causality_exact_utf8_budget_and_unknown_fields(case): result = decode(wire, True) assert ("workerCausality" in result["data"]) == case["accepted"] assert result["data"]["content"] == wire["data"]["content"] + + +def test_canonical_workflow_completion_decodes_typed_fields(): + event = session_event_from_dict(CORPUS["workflowCompleted"]["event"]) + assert event.type.value == "system.notification" + assert event.data.kind.type == "workflow_completed" + assert event.data.kind.workflow_name == "fix-ci" + assert event.data.kind.run_id == "run-1" + assert event.data.kind.status.value == "completed" + assert event.data.kind.consumed_subagents == 1 diff --git a/rust/src/generated/session_events.rs b/rust/src/generated/session_events.rs index b87a6a099e..5f51d8f128 100644 --- a/rust/src/generated/session_events.rs +++ b/rust/src/generated/session_events.rs @@ -449,8 +449,8 @@ pub enum SessionEventType { /// and may change or be removed in future SDK or CLI releases. /// /// - #[serde(rename = "factory.run_updated")] - FactoryRunUpdated, + #[serde(rename = "workflow.run_updated")] + WorkflowRunUpdated, /// ///
/// @@ -458,8 +458,8 @@ pub enum SessionEventType { /// and may change or be removed in future SDK or CLI releases. /// ///
- #[serde(rename = "factory.run_started")] - FactoryRunStarted, + #[serde(rename = "workflow.run_started")] + WorkflowRunStarted, /// ///
/// @@ -467,8 +467,8 @@ pub enum SessionEventType { /// and may change or be removed in future SDK or CLI releases. /// ///
- #[serde(rename = "factory.run_settled")] - FactoryRunSettled, + #[serde(rename = "workflow.run_settled")] + WorkflowRunSettled, #[serde(rename = "session.skills_loaded")] SessionSkillsLoaded, #[serde(rename = "session.custom_agents_updated")] @@ -969,8 +969,8 @@ pub enum SessionEventData { /// and may change or be removed in future SDK or CLI releases. /// /// - #[serde(rename = "factory.run_updated")] - FactoryRunUpdated(FactoryRunUpdatedData), + #[serde(rename = "workflow.run_updated")] + WorkflowRunUpdated(WorkflowRunUpdatedData), /// ///
/// @@ -978,8 +978,8 @@ pub enum SessionEventData { /// and may change or be removed in future SDK or CLI releases. /// ///
- #[serde(rename = "factory.run_started")] - FactoryRunStarted(FactoryRunStartedData), + #[serde(rename = "workflow.run_started")] + WorkflowRunStarted(WorkflowRunStartedData), /// ///
/// @@ -987,8 +987,8 @@ pub enum SessionEventData { /// and may change or be removed in future SDK or CLI releases. /// ///
- #[serde(rename = "factory.run_settled")] - FactoryRunSettled(FactoryRunSettledData), + #[serde(rename = "workflow.run_settled")] + WorkflowRunSettled(WorkflowRunSettledData), #[serde(rename = "session.skills_loaded")] SessionSkillsLoaded(SessionSkillsLoadedData), #[serde(rename = "session.custom_agents_updated")] @@ -1738,9 +1738,12 @@ pub struct SessionTruncationData { #[derive(Debug, Clone, Default, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] pub struct SessionSnapshotRewindData { + /// The removed events, starting with `upToEventId`. Later events not listed were kept, such as a background agent's events that interleaved with a withdrawn turn + #[serde(skip_serializing_if = "Option::is_none")] + pub event_ids: Option>, /// Number of events that were removed by the rewind pub events_removed: i64, - /// Event ID that was rewound to; this event and all after it were removed + /// First removed event. Without `eventIds`, it and every event after it were removed pub up_to_event_id: String, } @@ -2102,7 +2105,7 @@ pub struct CompactionCompleteCompactionTokensUsed { pub output_tokens: Option, } -/// Original request-level and effective conversation reasoning effort for a Responses history boundary +/// Original request-level and effective conversation reasoning effort for a provider history boundary; the historical type name is retained for compatibility #[derive(Debug, Clone, Default, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] pub struct ResponsesReasoning { @@ -2118,10 +2121,14 @@ pub struct ResponsesReasoning { #[derive(Debug, Clone, Default, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] pub struct SessionCompactionCompleteData { - /// Authoritative active-factory reminder appended to the compacted context + /// Legacy active-workflow reminder retained for replay compatibility #[doc(hidden)] #[serde(skip_serializing_if = "Option::is_none")] pub(crate) active_factory_summary: Option, + /// Authoritative active-workflow reminder appended to the compacted context + #[doc(hidden)] + #[serde(skip_serializing_if = "Option::is_none")] + pub(crate) active_workflow_summary: Option, /// Canonical model identifier used for model-specific behavior when replaying compaction #[serde(skip_serializing_if = "Option::is_none")] pub behavior_model_id: Option, @@ -2735,7 +2742,7 @@ pub struct UserMessageData { /// Task ID minted when the runtime prepares this user-message run. This is not a parent interaction ID or worker instance ID and must not be equated with CAPI's X-Parent-Agent-Id. #[serde(skip_serializing_if = "Option::is_none")] pub parent_agent_task_id: Option, - /// Responses reasoning settings anchored before this model-facing message, for cache-stable history replay + /// Provider reasoning settings anchored before this model-facing message for cache-stable replay; the historical responsesReasoning name is retained for compatibility #[serde(skip_serializing_if = "Option::is_none")] pub responses_reasoning: Option, /// Origin of this message, used for timeline filtering and attribution (e.g., `skill-pdf` for hidden skill injection or `agent-` for an inter-agent prompt) @@ -3203,6 +3210,9 @@ pub struct FusionAttribution { pub conversation_scope: Option, /// Stable identifier for the HydraFusion turn that produced the event. pub fusion_id: String, + /// Whether this model request consumed a user steering message rather than only internal Fusion work. + #[serde(skip_serializing_if = "Option::is_none")] + pub has_user_steering: Option, /// HydraFusion orchestration pattern selected for the turn. pub pattern: String, /// Identifier of the concrete phase that produced the event. @@ -5031,7 +5041,7 @@ pub struct SubagentStartedData { /// Whether the sub-agent runs synchronously or in the background. #[serde(skip_serializing_if = "Option::is_none")] pub execution_mode: Option, - /// Root id of the factory run that spawned this sub-agent, when it was spawned by one. + /// Legacy root id of the workflow run that spawned this sub-agent. New consumers should use workflowRunId. #[serde(skip_serializing_if = "Option::is_none")] pub factory_run_id: Option, /// Model the sub-agent will run with, when known at start. @@ -5046,11 +5056,14 @@ pub struct SubagentStartedData { /// Whether this sub-agent can be resumed. Currently always false. #[serde(skip_serializing_if = "Option::is_none")] pub resumable: Option, - /// Where the model input for this sub-agent came from. Present when the task planner resolved the launch (the task tool and factory agents); absent for sub-agents created through other runtime paths. + /// Where the model input for this sub-agent came from. Present when the task planner resolved the launch (the task tool and workflow agents); absent for sub-agents created through other runtime paths. #[serde(skip_serializing_if = "Option::is_none")] pub task_model_source: Option, /// Spawning tool invocation ID, or the canonical sub-agent ID used as a fallback for an API launch without a tool invocation. The fallback is not evidence of a tool call. pub tool_call_id: String, + /// Root id of the workflow run that spawned this sub-agent, when it was spawned by one. + #[serde(skip_serializing_if = "Option::is_none")] + pub workflow_run_id: Option, } /// Session event "subagent.configured". Resolved runtime configuration for a configured sub-agent @@ -5313,15 +5326,163 @@ pub struct SystemMessageData { pub role: SystemMessageRole, } -/// Session event "system.notification". System-generated notification for runtime events like background task completion +/// System notification metadata for a background agent that completed or failed, including agent ID, type, status, description, and prompt. +#[derive(Debug, Clone, Default, Serialize, Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct SystemNotificationAgentCompleted { + /// Unique task identifier + pub agent_id: String, + /// Type of the agent (e.g., explore, task, general-purpose) + pub agent_type: String, + /// Human-readable description of the agent task + #[serde(skip_serializing_if = "Option::is_none")] + pub description: Option, + /// Friendly, non-unique name intended for display + #[serde(skip_serializing_if = "Option::is_none")] + pub display_name: Option, + /// The full prompt given to the background agent + #[serde(skip_serializing_if = "Option::is_none")] + pub prompt: Option, + /// Whether the agent completed successfully or failed + pub status: SystemNotificationAgentCompletedStatus, + /// Type discriminator. Always "agent_completed". + pub r#type: SystemNotificationAgentCompletedType, +} + +/// System notification metadata for a background agent that became idle, including agent ID, type, and description. #[derive(Debug, Clone, Default, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] +pub struct SystemNotificationAgentIdle { + /// Unique task identifier + pub agent_id: String, + /// Type of the agent (e.g., explore, task, general-purpose) + pub agent_type: String, + /// Human-readable description of the agent task + #[serde(skip_serializing_if = "Option::is_none")] + pub description: Option, + /// Friendly, non-unique name intended for display + #[serde(skip_serializing_if = "Option::is_none")] + pub display_name: Option, + /// Type discriminator. Always "agent_idle". + pub r#type: SystemNotificationAgentIdleType, +} + +/// System notification metadata for a new inbox message, including entry ID, sender details, and summary. +#[derive(Debug, Clone, Default, Serialize, Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct SystemNotificationNewInboxMessage { + /// Unique identifier of the inbox entry + pub entry_id: String, + /// Human-readable name of the sender + pub sender_name: String, + /// Category of the sender (e.g., sidekick-agent, plugin, hook) + pub sender_type: String, + /// Short summary shown before the agent decides whether to read the inbox + pub summary: String, + /// Type discriminator. Always "new_inbox_message". + pub r#type: SystemNotificationNewInboxMessageType, +} + +/// System notification metadata for a shell session that completed, including shell ID, optional exit code, and description. +#[derive(Debug, Clone, Default, Serialize, Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct SystemNotificationShellCompleted { + /// Human-readable description of the command + #[serde(skip_serializing_if = "Option::is_none")] + pub description: Option, + /// Exit code of the shell command, if available + #[serde(skip_serializing_if = "Option::is_none")] + pub exit_code: Option, + /// Unique identifier of the shell session + pub shell_id: String, + /// Type discriminator. Always "shell_completed". + pub r#type: SystemNotificationShellCompletedType, +} + +/// System notification metadata for a detached shell session that completed, including shell ID and description. +#[derive(Debug, Clone, Default, Serialize, Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct SystemNotificationShellDetachedCompleted { + /// Human-readable description of the command + #[serde(skip_serializing_if = "Option::is_none")] + pub description: Option, + /// Unique identifier of the detached shell session + pub shell_id: String, + /// Type discriminator. Always "shell_detached_completed". + pub r#type: SystemNotificationShellDetachedCompletedType, +} + +/// System notification metadata for an instruction file discovered during tool access, including source, trigger file, and tool. +#[derive(Debug, Clone, Default, Serialize, Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct SystemNotificationInstructionDiscovered { + /// Human-readable label for the timeline (e.g., 'AGENTS.md from packages/billing/') + #[serde(skip_serializing_if = "Option::is_none")] + pub description: Option, + /// Relative path to the discovered instruction file + pub source_path: String, + /// Path of the file access that triggered discovery + pub trigger_file: String, + /// Tool command that triggered discovery (currently always 'view') + pub trigger_tool: String, + /// Type discriminator. Always "instruction_discovered". + pub r#type: SystemNotificationInstructionDiscoveredType, +} + +/// System notification metadata for a workflow execution attempt that reached a terminal state. +#[derive(Debug, Clone, Default, Serialize, Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct SystemNotificationWorkflowCompleted { + /// Execution attempt that reached this terminal state. + pub attempt: i64, + /// Consumed AI usage in nano-AIU. + pub consumed_nano_aiu: i64, + /// Subagents consumed by the run across all attempts. + pub consumed_subagents: i64, + /// Accumulated active execution time in milliseconds. + pub elapsed_ms: i64, + /// Machine-readable terminal failure details, when present. + #[serde(skip_serializing_if = "Option::is_none")] + pub failure: Option, + /// Pause initiator metadata when this attempt settled as paused. + #[serde(skip_serializing_if = "Option::is_none")] + pub pause_info: Option, + /// Bounded prompt-safe preview of the completed result. + #[serde(skip_serializing_if = "Option::is_none")] + pub result_preview: Option, + /// Actionable run_dynamic_workflow resume guidance for a resource-limit failure. + #[serde(skip_serializing_if = "Option::is_none")] + pub retry_guidance: Option, + /// Workflow run identifier. + pub run_id: String, + /// Terminal status reached by this execution attempt. + pub status: SystemNotificationWorkflowCompletedStatus, + /// Type discriminator. Always "workflow_completed". + pub r#type: SystemNotificationWorkflowCompletedType, + /// Persisted workflow name. + pub workflow_name: String, +} + +/// System notification metadata from an external host that does not match a runtime-owned notification kind. +#[derive(Debug, Clone, Default, Serialize, Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct SystemNotificationUnclassified { + /// Opaque metadata supplied by the external host, when present. + #[serde(skip_serializing_if = "Option::is_none")] + pub metadata: Option, + /// Type discriminator. Always "unclassified". + pub r#type: SystemNotificationUnclassifiedType, +} + +/// Session event "system.notification". System-generated notification for runtime events like background task completion +#[derive(Debug, Clone, Serialize, Deserialize)] +#[serde(rename_all = "camelCase")] pub struct SystemNotificationData { /// The notification text, typically wrapped in `` XML tags pub content: String, /// Structured metadata identifying what triggered this notification - pub kind: serde_json::Value, - /// Responses reasoning settings anchored before this model-facing message, for cache-stable history replay + pub kind: SystemNotification, + /// Provider reasoning settings anchored before this model-facing message for cache-stable replay; the historical responsesReasoning name is retained for compatibility #[serde(skip_serializing_if = "Option::is_none")] pub responses_reasoning: Option, /// Optional owned worker notification observations; an omitted notification event refers only to this occurrence. @@ -5715,10 +5876,10 @@ pub struct PermissionRequestExtensionManagement { pub tool_call_id: Option, } -/// A declared phase shown in a factory permission prompt. +/// A declared phase shown in a workflow permission prompt. #[derive(Debug, Clone, Default, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] -pub struct FactoryPermissionPhase { +pub struct WorkflowPermissionPhase { /// Optional phase detail #[serde(skip_serializing_if = "Option::is_none")] pub detail: Option, @@ -5726,31 +5887,31 @@ pub struct FactoryPermissionPhase { pub title: String, } -/// Factory run or authoring permission request +/// Workflow run or authoring permission request #[derive(Debug, Clone, Default, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] -pub struct PermissionRequestFactory { - /// Canonical key used for scoped factory approvals +pub struct PermissionRequestWorkflow { + /// Canonical key used for scoped workflow approvals pub approval_key: String, - /// Whether this factory is eligible for persistent approval + /// Whether this workflow is eligible for persistent approval pub can_persist_approval: bool, - /// Factory-declared AI-credit limit before any run/resume caller override is applied. + /// Workflow-declared AI-credit limit before any run/resume caller override is applied. #[serde(skip_serializing_if = "Option::is_none")] pub declared_max_ai_credits: Option, - /// Factory-declared concurrent-subagent limit before any run/resume caller override is applied. + /// Workflow-declared concurrent-subagent limit before any run/resume caller override is applied. #[serde(skip_serializing_if = "Option::is_none")] pub declared_max_concurrent_subagents: Option, - /// Factory-declared total-subagent limit before any run/resume caller override is applied. + /// Workflow-declared total-subagent limit before any run/resume caller override is applied. #[serde(skip_serializing_if = "Option::is_none")] pub declared_max_total_subagents: Option, - /// Factory-declared active-time limit in seconds before any run/resume caller override is applied. + /// Workflow-declared active-time limit in seconds before any run/resume caller override is applied. #[serde(skip_serializing_if = "Option::is_none")] pub declared_timeout_seconds: Option, - /// Factory description + /// Workflow description pub description: String, /// Permission kind discriminator - pub kind: PermissionRequestFactoryKind, - /// When true, managed policy requires an explicit user decision and automatic approval must be bypassed. + pub kind: PermissionRequestWorkflowKind, + /// Whether managed policy requires a human response and forbids host auto-approval #[serde(skip_serializing_if = "Option::is_none")] pub managed_approval_required: Option, /// Effective AI-credit limit; omitted means unlimited @@ -5762,12 +5923,12 @@ pub struct PermissionRequestFactory { /// Effective total-subagent limit; omitted means unlimited #[serde(skip_serializing_if = "Option::is_none")] pub max_total_subagents: Option, - /// Factory name + /// Workflow name pub name: String, - /// Factory operation, either run or author - pub operation: FactoryPermissionOperation, - /// Declared factory phases - pub phases: Vec, + /// Workflow operation, either run or author + pub operation: WorkflowPermissionOperation, + /// Declared workflow phases + pub phases: Vec, /// Effective active-time limit in seconds; omitted means unlimited #[serde(skip_serializing_if = "Option::is_none")] pub timeout_seconds: Option, @@ -6165,11 +6326,11 @@ pub struct PermissionPromptRequestExtensionManagement { pub tool_call_id: Option, } -/// Factory run or authoring permission prompt +/// Workflow run or authoring permission prompt #[derive(Debug, Clone, Default, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] -pub struct PermissionPromptRequestFactory { - /// Canonical key used for scoped factory approvals +pub struct PermissionPromptRequestWorkflow { + /// Canonical key used for scoped workflow approvals pub approval_key: String, /// Assisted-approval judge information for this request; present only in assisted mode. /// @@ -6181,24 +6342,24 @@ pub struct PermissionPromptRequestFactory { /// #[serde(skip_serializing_if = "Option::is_none")] pub assisted_approval: Option, - /// Whether this factory is eligible for persistent approval + /// Whether this workflow is eligible for persistent approval pub can_persist_approval: bool, - /// Factory-declared AI-credit limit before any run/resume caller override is applied. + /// Workflow-declared AI-credit limit before any run/resume caller override is applied. #[serde(skip_serializing_if = "Option::is_none")] pub declared_max_ai_credits: Option, - /// Factory-declared concurrent-subagent limit before any run/resume caller override is applied. + /// Workflow-declared concurrent-subagent limit before any run/resume caller override is applied. #[serde(skip_serializing_if = "Option::is_none")] pub declared_max_concurrent_subagents: Option, - /// Factory-declared total-subagent limit before any run/resume caller override is applied. + /// Workflow-declared total-subagent limit before any run/resume caller override is applied. #[serde(skip_serializing_if = "Option::is_none")] pub declared_max_total_subagents: Option, - /// Factory-declared active-time limit in seconds before any run/resume caller override is applied. + /// Workflow-declared active-time limit in seconds before any run/resume caller override is applied. #[serde(skip_serializing_if = "Option::is_none")] pub declared_timeout_seconds: Option, - /// Factory description + /// Workflow description pub description: String, /// Prompt kind discriminator - pub kind: PermissionPromptRequestFactoryKind, + pub kind: PermissionPromptRequestWorkflowKind, /// Whether managed policy requires a human response and forbids host auto-approval #[serde(skip_serializing_if = "Option::is_none")] pub managed_approval_required: Option, @@ -6211,12 +6372,12 @@ pub struct PermissionPromptRequestFactory { /// Effective total-subagent limit; omitted means unlimited #[serde(skip_serializing_if = "Option::is_none")] pub max_total_subagents: Option, - /// Factory name + /// Workflow name pub name: String, - /// Factory operation, either run or author - pub operation: FactoryPermissionOperation, - /// Declared factory phases - pub phases: Vec, + /// Workflow operation, either run or author + pub operation: WorkflowPermissionOperation, + /// Declared workflow phases + pub phases: Vec, /// Effective active-time limit in seconds; omitted means unlimited #[serde(skip_serializing_if = "Option::is_none")] pub timeout_seconds: Option, @@ -6381,15 +6542,15 @@ pub struct UserToolSessionApprovalExtensionManagement { pub operation: Option, } -/// Session-scoped factory approval, optionally narrowed by approval key. +/// Session-scoped workflow approval, optionally narrowed by approval key. #[derive(Debug, Clone, Default, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] -pub struct UserToolSessionApprovalFactory { - /// Optional factory operation name or canonical approval key +pub struct UserToolSessionApprovalWorkflow { + /// Optional workflow operation name or canonical approval key #[serde(skip_serializing_if = "Option::is_none")] pub approval_key: Option, - /// Factory approval kind - pub kind: UserToolSessionApprovalFactoryKind, + /// Workflow approval kind + pub kind: UserToolSessionApprovalWorkflowKind, } /// Session-scoped tool-approval rule for an extension's permission-gated capability access, keyed by extension name. @@ -6550,7 +6711,7 @@ pub struct PermissionCompletedData { pub tool_call_id: Option, } -/// Session event "permission.carriedForward". Records that a live authorization record from an earlier human decision in this session contained a permission proposal, so it ran without another prompt. This mints no authority: it accounts for one more effect against the prior grant, which is what lets a replayed session agree with the live one about how much of that grant is left. +/// Session event "permission.carriedForward". Historical decode-only receipt from the retired Assisted Permissions authorization extractor. Current runtimes ignore it for permission decisions. /// ///
/// @@ -6599,7 +6760,7 @@ pub struct PermissionCarriedForwardData { pub tool_call_id: String, } -/// Session event "permission.messageAuthorization". Freezes one blinded, verbatim-verified authorization claim the runtime minted from a human user message, so a resumed session re-establishes the same grant deterministically instead of re-running the extraction model. This mints no authority on its own: it records what a blinded proposer pointed at and the trusted discriminator the runtime established, and deterministic establishment runs on replay. Persisted so recorded authority survives compaction and process resume. +/// Session event "permission.messageAuthorization". Historical decode-only claim from the retired Assisted Permissions authorization extractor. Current runtimes preserve the payload but do not establish authority from it. /// ///
/// @@ -6696,7 +6857,7 @@ pub struct PermissionMessageAuthorizationData { pub world: Option, } -/// Session event "permission.messageAuthorizationRead". Records that one human turn has been read by the blinded authorization proposer, whether or not it minted anything, so a resumed session does not re-run the extraction model on a turn the live session already read. Also records whether that pass activates ongoing extraction; contextual-assent-only passes do not, so unrelated future messages remain outside extraction. +/// Session event "permission.messageAuthorizationRead". Historical decode-only extractor progress marker. Current runtimes do not run or resume extraction from it. /// ///
/// @@ -6728,7 +6889,7 @@ pub struct PermissionMessageAuthorizationReadData { pub turn_index: i64, } -/// Session event "permission.messageAuthorizationDegraded". Records that message-backed authorization could not safely represent one human turn before compaction. The runtime may compact the original message after this marker is durable, but message-derived carry-forward and assisted auto-approval remain disabled for the rest of the session so subsequent commands continue through the ordinary permission prompt. +/// Session event "permission.messageAuthorizationDegraded". Historical decode-only degradation marker from the retired extractor. Current runtimes ignore it for permission decisions. /// ///
/// @@ -6750,7 +6911,7 @@ pub struct PermissionMessageAuthorizationDegradedData { pub turn_index: i64, } -/// Session event "permission.assentDetected". Records that deterministic text recognition found likely assent in the human turn immediately following a root Autopilot permission request that was blocked because no interactive response was available. This event grants no authority; its model-facing projection only suggests retrying the unchanged operation. +/// Session event "permission.assentDetected". Historical decode-only contextual-assent marker. Current runtimes do not project it into the conversation or permission flow. /// ///
/// @@ -6781,7 +6942,7 @@ pub struct PermissionAssentDetectedData { pub turn_index: i64, } -/// Session event "permission.contextualAuthorization". Freezes a blinded contextual authorization proposal whose verbatim human span was deterministically bound to the immediately preceding blocked permission request. The event carries no action fields; replay re-derives the exact action from the earlier permission request and mints a one-shot message grant only when the binding and span still verify. +/// Session event "permission.contextualAuthorization". Historical decode-only contextual authorization claim. Current runtimes preserve the payload but do not establish authority from it. /// ///
/// @@ -7460,7 +7621,7 @@ pub struct SessionToolsUpdatedData { #[serde(rename_all = "camelCase")] pub struct SessionBackgroundTasksChangedData {} -/// Session event "factory.run_updated". Ephemeral invalidation signal for a changed factory run. +/// Session event "workflow.run_updated". Ephemeral invalidation signal for a changed workflow run. /// ///
/// @@ -7470,14 +7631,14 @@ pub struct SessionBackgroundTasksChangedData {} ///
#[derive(Debug, Clone, Default, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] -pub struct FactoryRunUpdatedData { +pub struct WorkflowRunUpdatedData { /// Monotonic revision now available for the run. pub revision: i64, - /// Factory run identifier. + /// Workflow run identifier. pub run_id: String, } -/// Session event "factory.run_started". Ephemeral signal that a factory run attempt began executing. +/// Session event "workflow.run_started". Ephemeral signal that a workflow run attempt began executing. /// ///
/// @@ -7487,16 +7648,16 @@ pub struct FactoryRunUpdatedData { ///
#[derive(Debug, Clone, Default, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] -pub struct FactoryRunStartedData { +pub struct WorkflowRunStartedData { /// Attempt number this start committed; a resumed run increments it. pub attempt: i64, - /// Name of the factory this run executes. Low cardinality by construction. - pub factory_name: String, - /// Identifier of the factory run that started. + /// Identifier of the workflow run that started. pub run_id: String, + /// Name of the workflow this run executes. Low cardinality by construction. + pub workflow_name: String, } -/// Session event "factory.run_settled". Ephemeral signal that a factory run reached a terminal status. +/// Session event "workflow.run_settled". Ephemeral signal that a workflow run reached a terminal status. /// ///
/// @@ -7506,20 +7667,20 @@ pub struct FactoryRunStartedData { ///
#[derive(Debug, Clone, Default, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] -pub struct FactoryRunSettledData { +pub struct WorkflowRunSettledData { /// AI credits this run consumed, in nano-AIU. pub consumed_nano_aiu: i64, /// Subagents this run consumed against its limits. pub consumed_subagents: i64, /// Active milliseconds accumulated across every attempt of this run. pub elapsed_ms: i64, - /// Typed failure class recorded on the run, when it failed with one (e.g. `factory_limit_reached`). + /// Typed failure class recorded on the run, when it failed with one (e.g. `workflow_limit_reached`). #[serde(skip_serializing_if = "Option::is_none")] pub failure_type: Option, - /// Identifier of the factory run that settled. + /// Identifier of the workflow run that settled. pub run_id: String, /// Terminal status the run committed. - pub status: FactoryRunSettledStatus, + pub status: WorkflowRunSettledStatus, } /// A single resolved skill in `session.skills_loaded`, including source, invocability, enabled state, path, and argument hint. @@ -8359,6 +8520,9 @@ pub enum ModelChangeSource { /// An SDK or RPC caller selected the model. #[serde(rename = "sdk")] Sdk, + /// The user accepted a CAPI-issued Auto tier recommendation. + #[serde(rename = "auto_tier_recommendation")] + AutoTierRecommendation, /// Unknown variant for forward compatibility. #[default] #[serde(other)] @@ -9968,6 +10132,123 @@ pub enum SystemMessageRole { Unknown, } +/// Whether the agent completed successfully or failed +#[derive(Debug, Clone, Default, PartialEq, Eq, Serialize, Deserialize)] +pub enum SystemNotificationAgentCompletedStatus { + /// The agent completed successfully. + #[serde(rename = "completed")] + Completed, + /// The agent failed. + #[serde(rename = "failed")] + Failed, + /// Unknown variant for forward compatibility. + #[default] + #[serde(other)] + Unknown, +} + +/// Type discriminator. Always "agent_completed". +#[derive(Debug, Clone, Default, PartialEq, Eq, Serialize, Deserialize)] +pub enum SystemNotificationAgentCompletedType { + #[serde(rename = "agent_completed")] + #[default] + AgentCompleted, +} + +/// Type discriminator. Always "agent_idle". +#[derive(Debug, Clone, Default, PartialEq, Eq, Serialize, Deserialize)] +pub enum SystemNotificationAgentIdleType { + #[serde(rename = "agent_idle")] + #[default] + AgentIdle, +} + +/// Type discriminator. Always "new_inbox_message". +#[derive(Debug, Clone, Default, PartialEq, Eq, Serialize, Deserialize)] +pub enum SystemNotificationNewInboxMessageType { + #[serde(rename = "new_inbox_message")] + #[default] + NewInboxMessage, +} + +/// Type discriminator. Always "shell_completed". +#[derive(Debug, Clone, Default, PartialEq, Eq, Serialize, Deserialize)] +pub enum SystemNotificationShellCompletedType { + #[serde(rename = "shell_completed")] + #[default] + ShellCompleted, +} + +/// Type discriminator. Always "shell_detached_completed". +#[derive(Debug, Clone, Default, PartialEq, Eq, Serialize, Deserialize)] +pub enum SystemNotificationShellDetachedCompletedType { + #[serde(rename = "shell_detached_completed")] + #[default] + ShellDetachedCompleted, +} + +/// Type discriminator. Always "instruction_discovered". +#[derive(Debug, Clone, Default, PartialEq, Eq, Serialize, Deserialize)] +pub enum SystemNotificationInstructionDiscoveredType { + #[serde(rename = "instruction_discovered")] + #[default] + InstructionDiscovered, +} + +/// Terminal status reached by a workflow execution attempt. +#[derive(Debug, Clone, Default, PartialEq, Eq, Serialize, Deserialize)] +pub enum SystemNotificationWorkflowCompletedStatus { + /// The workflow completed successfully. + #[serde(rename = "completed")] + Completed, + /// The workflow was halted. + #[serde(rename = "halted")] + Halted, + /// The workflow attempt paused intentionally. + #[serde(rename = "paused")] + Paused, + /// The workflow was cancelled. + #[serde(rename = "cancelled")] + Cancelled, + /// The workflow failed. + #[serde(rename = "error")] + Error, + /// Unknown variant for forward compatibility. + #[default] + #[serde(other)] + Unknown, +} + +/// Type discriminator. Always "workflow_completed". +#[derive(Debug, Clone, Default, PartialEq, Eq, Serialize, Deserialize)] +pub enum SystemNotificationWorkflowCompletedType { + #[serde(rename = "workflow_completed")] + #[default] + WorkflowCompleted, +} + +/// Type discriminator. Always "unclassified". +#[derive(Debug, Clone, Default, PartialEq, Eq, Serialize, Deserialize)] +pub enum SystemNotificationUnclassifiedType { + #[serde(rename = "unclassified")] + #[default] + Unclassified, +} + +/// Structured metadata identifying what triggered this notification +#[derive(Debug, Clone, Serialize, Deserialize)] +#[serde(untagged)] +pub enum SystemNotification { + AgentCompleted(SystemNotificationAgentCompleted), + AgentIdle(SystemNotificationAgentIdle), + NewInboxMessage(SystemNotificationNewInboxMessage), + ShellCompleted(SystemNotificationShellCompleted), + ShellDetachedCompleted(SystemNotificationShellDetachedCompleted), + InstructionDiscovered(SystemNotificationInstructionDiscovered), + WorkflowCompleted(SystemNotificationWorkflowCompleted), + Unclassified(SystemNotificationUnclassified), +} + /// Permission kind discriminator #[derive(Debug, Clone, Default, PartialEq, Eq, Serialize, Deserialize)] pub enum PermissionRequestShellKind { @@ -10303,19 +10584,19 @@ pub enum PermissionRequestExtensionManagementKind { /// Permission kind discriminator #[derive(Debug, Clone, Default, PartialEq, Eq, Serialize, Deserialize)] -pub enum PermissionRequestFactoryKind { - #[serde(rename = "factory")] +pub enum PermissionRequestWorkflowKind { + #[serde(rename = "workflow")] #[default] - Factory, + Workflow, } -/// Operation gated by a factory permission request. +/// Operation gated by a workflow permission request. #[derive(Debug, Clone, Default, PartialEq, Eq, Serialize, Deserialize)] -pub enum FactoryPermissionOperation { - /// Running a registered factory, which spends subagents, active time, and AI credits under the approved limits. +pub enum WorkflowPermissionOperation { + /// Running a registered workflow, which spends subagents, active time, and AI credits under the approved limits. #[serde(rename = "run")] Run, - /// Authoring a factory, which writes JavaScript into a session-scoped extension and loads it. + /// Authoring a workflow, which writes JavaScript into a session-scoped extension and loads it. #[serde(rename = "author")] Author, /// Unknown variant for forward compatibility. @@ -10353,7 +10634,7 @@ pub enum PermissionRequest { CustomTool(PermissionRequestCustomTool), Hook(PermissionRequestHook), ExtensionManagement(PermissionRequestExtensionManagement), - Factory(PermissionRequestFactory), + Workflow(PermissionRequestWorkflow), ExtensionPermissionAccess(PermissionRequestExtensionPermissionAccess), ExtensionEnvAccess(PermissionRequestExtensionEnvAccess), } @@ -10458,10 +10739,10 @@ pub enum PermissionPromptRequestExtensionManagementKind { /// Prompt kind discriminator #[derive(Debug, Clone, Default, PartialEq, Eq, Serialize, Deserialize)] -pub enum PermissionPromptRequestFactoryKind { - #[serde(rename = "factory")] +pub enum PermissionPromptRequestWorkflowKind { + #[serde(rename = "workflow")] #[default] - Factory, + Workflow, } /// Prompt kind discriminator @@ -10494,7 +10775,7 @@ pub enum PermissionPromptRequest { Path(PermissionPromptRequestPath), Hook(PermissionPromptRequestHook), ExtensionManagement(PermissionPromptRequestExtensionManagement), - Factory(PermissionPromptRequestFactory), + Workflow(PermissionPromptRequestWorkflow), ExtensionPermissionAccess(PermissionPromptRequestExtensionPermissionAccess), ExtensionEnvAccess(PermissionPromptRequestExtensionEnvAccess), } @@ -10514,7 +10795,7 @@ pub enum PermissionDecisionSource { /// The host denied the request because no interactive user response was available. #[serde(rename = "unattended_fallback")] UnattendedFallback, - /// A live authorization record from an earlier human decision in this session contained the proposal, so it ran without another prompt. This is not a new human decision and never mints authority of its own. + /// Historical compatibility value for sessions created while authorization carry-forward was executable. Current runtimes do not produce this source. #[serde(rename = "authorization_carry_forward")] AuthorizationCarryForward, /// Unknown variant for forward compatibility. @@ -10587,12 +10868,12 @@ pub enum UserToolSessionApprovalExtensionManagementKind { ExtensionManagement, } -/// Factory approval kind +/// Workflow approval kind #[derive(Debug, Clone, Default, PartialEq, Eq, Serialize, Deserialize)] -pub enum UserToolSessionApprovalFactoryKind { - #[serde(rename = "factory")] +pub enum UserToolSessionApprovalWorkflowKind { + #[serde(rename = "workflow")] #[default] - Factory, + Workflow, } /// Extension permission access approval kind @@ -10622,7 +10903,7 @@ pub enum UserToolSessionApproval { Memory(UserToolSessionApprovalMemory), CustomTool(UserToolSessionApprovalCustomTool), ExtensionManagement(UserToolSessionApprovalExtensionManagement), - Factory(UserToolSessionApprovalFactory), + Workflow(UserToolSessionApprovalWorkflow), ExtensionPermissionAccess(UserToolSessionApprovalExtensionPermissionAccess), ExtensionEnvAccess(UserToolSessionApprovalExtensionEnvAccess), } @@ -10708,7 +10989,7 @@ pub enum PermissionResult { DeniedByPermissionRequestHook(PermissionDeniedByPermissionRequestHook), } -/// Which direction a message-backed authorization claim moves authority in. +/// Direction stored in a historical extractor claim. Current runtimes do not apply it. /// ///
/// @@ -10718,10 +10999,10 @@ pub enum PermissionResult { ///
#[derive(Debug, Clone, Default, PartialEq, Eq, Serialize, Deserialize)] pub enum PermissionMessageAuthorizationPolarity { - /// The human's words authorized an effect. + /// Historical claim recorded as a grant. #[serde(rename = "grant")] Grant, - /// The human's words refused an effect. + /// Historical claim recorded as a denial. #[serde(rename = "denial")] Denial, /// Unknown variant for forward compatibility. @@ -11029,10 +11310,10 @@ pub enum ExitPlanModeAction { Unknown, } -/// Terminal status a factory run committed. A settled run is never `pending` or `running`, so those two members of the run-status domain are deliberately absent. +/// Terminal status a workflow run committed. A settled run is never `pending` or `running`, so those two members of the run-status domain are deliberately absent. #[derive(Debug, Clone, Default, PartialEq, Eq, Serialize, Deserialize)] -pub enum FactoryRunSettledStatus { - /// The factory body resolved and its result was committed. +pub enum WorkflowRunSettledStatus { + /// The workflow body resolved and its result was committed. #[serde(rename = "completed")] Completed, /// The run was stopped by a limit, an approval refusal or another policy decision. diff --git a/rust/src/worker_causality.rs b/rust/src/worker_causality.rs index d8896cc638..da26f0e037 100644 --- a/rust/src/worker_causality.rs +++ b/rust/src/worker_causality.rs @@ -26,9 +26,9 @@ impl Write for Budget { } fn text(value: &Value) -> bool { - value - .as_str() - .is_some_and(|value| !value.is_empty() && value.len() <= 256) + value.as_str().is_some_and(|value| { + !value.is_empty() && value.len() <= 256 && !value.chars().any(char::is_control) + }) } fn uuid(value: &Value) -> bool { @@ -54,12 +54,8 @@ fn reference(value: &Value, event_type: &str) -> bool { fields( value, &["sessionId", "eventId", "agentId", "eventType", "provenance"], - ) && value["sessionId"].as_str().is_some_and(|session| { - text(&value["sessionId"]) - && session - .bytes() - .all(|c| c.is_ascii_alphanumeric() || matches!(c, b'_' | b'-')) - }) && uuid(&value["eventId"]) + ) && text(&value["sessionId"]) + && uuid(&value["eventId"]) && value.get("agentId").is_none_or(text) && value["eventType"] == event_type && matches!( diff --git a/rust/tests/worker_causality_test.rs b/rust/tests/worker_causality_test.rs index ec9429ea66..4291ad8bcf 100644 --- a/rust/tests/worker_causality_test.rs +++ b/rust/tests/worker_causality_test.rs @@ -3,7 +3,9 @@ #![allow(clippy::unwrap_used)] use github_copilot_sdk::rpc::TasksSendMessageResult; -use github_copilot_sdk::session_events::TypedSessionEvent; +use github_copilot_sdk::session_events::{ + SystemNotification, SystemNotificationWorkflowCompletedStatus, TypedSessionEvent, +}; use serde_json::Value; fn corpus() -> Value { @@ -76,3 +78,25 @@ fn worker_causality_exact_utf8_budget_and_unknown_fields() { assert_eq!(decoded["data"]["content"], wire["data"]["content"]); } } + +#[test] +fn canonical_workflow_completion_decodes_typed_fields() { + let corpus = corpus(); + let event: TypedSessionEvent = + serde_json::from_value(corpus["workflowCompleted"]["event"].clone()).unwrap(); + let github_copilot_sdk::session_events::SessionEventData::SystemNotification(notification) = + event.payload + else { + panic!("expected system.notification"); + }; + let SystemNotification::WorkflowCompleted(completion) = notification.kind else { + panic!("expected workflow_completed"); + }; + assert_eq!(completion.workflow_name, "fix-ci"); + assert_eq!(completion.run_id, "run-1"); + assert!(matches!( + completion.status, + SystemNotificationWorkflowCompletedStatus::Completed + )); + assert_eq!(completion.consumed_subagents, 1); +} diff --git a/scripts/codegen/go.ts b/scripts/codegen/go.ts index a4421f02eb..93b0aa68b9 100644 --- a/scripts/codegen/go.ts +++ b/scripts/codegen/go.ts @@ -3357,6 +3357,15 @@ export function generateGoSessionEventsCode( .join(""), dataClassName: variant.dataClassName, })) + .concat( + variants.some((variant) => variant.typeName === "workflow.run_updated") + ? [ + { constName: "SessionEventTypeFactoryRunSettled", dataClassName: "FactoryRunSettledData" }, + { constName: "SessionEventTypeFactoryRunStarted", dataClassName: "FactoryRunStartedData" }, + { constName: "SessionEventTypeFactoryRunUpdated", dataClassName: "FactoryRunUpdatedData" }, + ] + : [] + ) .sort((left, right) => left.constName.localeCompare(right.constName)); // Type method diff --git a/scripts/codegen/rust.ts b/scripts/codegen/rust.ts index cd10896ca1..c610ee3b0d 100644 --- a/scripts/codegen/rust.ts +++ b/scripts/codegen/rust.ts @@ -1322,6 +1322,7 @@ export function generateSessionEventsCode(schema: JSONSchema7): string { { allowUntaggedUnions: true, allowedUnionTypeNames: [ + "SystemNotification", "ToolExecutionCompleteContent", "ToolExecutionCompleteContentResourceDetails", ], diff --git a/test/worker-causality.json b/test/worker-causality.json index 4866a09b44..d593cc4977 100644 --- a/test/worker-causality.json +++ b/test/worker-causality.json @@ -1,8 +1,21 @@ { "version": 1, "producerEvidence": { - "kind": "native_worker_causality_test_output", - "logSha256": "625f992064a37b5b51c3f6be3efcb7f16423a1a24648a8f7daea7b4db67c4d15" + "kind": "runtime_actual_output_conformance", + "sourceTests": [ + "src/runtime/src/subagents/orchestrator/worker_causality_tests.rs", + "src/runtime/src/session/session_notification_delivery/worker_causality_tests.rs", + "src/runtime/src/shared_api/tasks/tests.rs", + "test/cli/ahp/client/relaySession.test.ts" + ], + "vectors": [ + "native_nested_user_message", + "queued_notification", + "two_source_assistant_turn_start", + "tasks_send_message_result", + "workflow_completed" + ], + "canonicalization": "volatile UUID envelope and identity values are replaced by a fixed token before structural comparison; fixed product content, placement, ordering and typed fields remain exact" }, "valid": [ { @@ -424,7 +437,35 @@ } }, { - "name": "identity_256_utf8_bytes", + "name": "session_id_256_multibyte", + "result": { + "sent": true, + "workerCausality": { + "version": 1, + "observationProvenance": "native", + "sources": [ + { + "input": { + "queueItemId": "46fc3a43-7f19-4b0a-9e32-d5ef4dc25552", + "agentId": "worker" + }, + "admissions": [], + "captureComplete": true, + "completion": { + "sessionId": "éééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééé", + "eventId": "44444444-4444-4444-8444-444444444444", + "agentId": "worker", + "eventType": "subagent.completed", + "provenance": "native" + } + } + ], + "captureComplete": true + } + } + }, + { + "name": "agent_id_256_multibyte", "result": { "sent": true, "workerCausality": { @@ -444,6 +485,32 @@ } } }, + { + "name": "message_id_256_multibyte", + "result": { + "sent": true, + "workerCausality": { + "version": 1, + "observationProvenance": "native", + "sources": [ + { + "input": { + "queueItemId": "46fc3a43-7f19-4b0a-9e32-d5ef4dc25552", + "agentId": "worker" + }, + "admissions": [ + { + "kind": "queued_input", + "messageId": "éééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééé" + } + ], + "captureComplete": true + } + ], + "captureComplete": true + } + } + }, { "name": "implicit_self_wire_fits_but_expansion_exceeds_budget", "event": { @@ -768,6 +835,127 @@ "captureComplete": true } }, + { + "name": "unknown_bridge_field", + "value": { + "version": 1, + "observationProvenance": "native", + "sources": [ + { + "input": { + "queueItemId": "2dbbe116-73b0-4011-bfa3-3b571b41404b", + "agentId": "recipient", + "sender": { + "sessionId": "0187f098-31d3-4cdc-ab71-e6ab387e01b4", + "eventId": "773e548e-64e5-4418-853b-aabf128ce858", + "eventType": "tool.execution_start", + "provenance": "native" + }, + "senderBridges": [ + { + "source": { + "sessionId": "0187f098-31d3-4cdc-ab71-e6ab387e01b4", + "eventId": "773e548e-64e5-4418-853b-aabf128ce858", + "eventType": "tool.execution_start", + "provenance": "native" + }, + "reported": { + "sessionId": "23ac4baf-eb8e-4fdc-b8e0-5633e4ed550b", + "eventId": "8f608be4-c132-4e19-8bdd-82ff5ba9b0b8", + "agentId": "leaf-agent", + "eventType": "tool.execution_start", + "provenance": "native" + }, + "future": true + }, + { + "source": { + "sessionId": "23ac4baf-eb8e-4fdc-b8e0-5633e4ed550b", + "eventId": "8f608be4-c132-4e19-8bdd-82ff5ba9b0b8", + "agentId": "leaf-agent", + "eventType": "tool.execution_start", + "provenance": "native" + }, + "reported": { + "sessionId": "ce52204a-54b0-4ebd-ba13-4854598c4399", + "eventId": "69362832-885b-4787-ab3e-1a2f6c17c73b", + "agentId": "leaf-agent", + "eventType": "tool.execution_start", + "provenance": "native" + } + }, + { + "source": { + "sessionId": "23ac4baf-eb8e-4fdc-b8e0-5633e4ed550b", + "eventId": "8f608be4-c132-4e19-8bdd-82ff5ba9b0b8", + "agentId": "leaf-agent", + "eventType": "tool.execution_start", + "provenance": "native" + }, + "reported": { + "sessionId": "f651ee35-bdb9-4c91-aa21-0057a9446269", + "eventId": "b13511dc-0c7a-4d9c-9618-c04c1c50c4bc", + "agentId": "leaf-agent", + "eventType": "tool.execution_start", + "provenance": "native" + } + } + ] + }, + "admissions": [ + { + "kind": "queued_input", + "messageId": "bb61b7c0-bc90-4290-a1e4-79558dc35139" + } + ], + "captureComplete": true + } + ], + "captureComplete": true + } + }, + { + "name": "unknown_notification_field", + "value": { + "version": 1, + "observationProvenance": "native", + "sources": [ + { + "input": { + "queueItemId": "bb31eee3-11b8-41bb-b392-b5d4f25061be", + "agentId": "worker" + }, + "admissions": [ + { + "kind": "queued_input", + "messageId": "c7b4146b-6a6a-40e7-873a-6e67438a7923", + "event": { + "sessionId": "b9230967-dd47-416e-bc03-c470afd2c87d", + "eventId": "33db0052-4391-4418-a46e-26e9bac8d5a9", + "agentId": "worker", + "eventType": "user.message", + "provenance": "native" + } + } + ], + "captureComplete": true, + "completion": { + "sessionId": "b9230967-dd47-416e-bc03-c470afd2c87d", + "eventId": "30f8471f-343a-44b8-ae95-86c8eed36afc", + "agentId": "worker", + "eventType": "subagent.completed", + "provenance": "native" + }, + "notification": { + "deliveryId": "9cef2f54-c16f-49a8-a2cd-58da52108e60", + "mode": "queued", + "future": true + } + } + ], + "captureComplete": true + } + }, { "name": "malformed_queue_uuid", "value": { @@ -997,6 +1185,31 @@ "captureComplete": true } }, + { + "name": "session_id_257_multibyte", + "value": { + "version": 1, + "observationProvenance": "native", + "sources": [ + { + "input": { + "queueItemId": "46fc3a43-7f19-4b0a-9e32-d5ef4dc25552", + "agentId": "worker" + }, + "admissions": [], + "captureComplete": true, + "completion": { + "sessionId": "ééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééx", + "eventId": "44444444-4444-4444-8444-444444444444", + "agentId": "worker", + "eventType": "subagent.completed", + "provenance": "native" + } + } + ], + "captureComplete": true + } + }, { "name": "agent_id_257_multibyte", "value": { @@ -1024,56 +1237,78 @@ { "input": { "queueItemId": "46fc3a43-7f19-4b0a-9e32-d5ef4dc25552", - "agentId": "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" + "agentId": "worker" }, "admissions": [ { "kind": "queued_input", "messageId": "ééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééx" - }, - { - "kind": "queued_input", - "messageId": "00xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx", - "event": { - "sessionId": "source", - "eventId": "33333333-3333-4333-8333-333333333333", - "agentId": "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa", - "eventType": "user.message", - "provenance": "native" - } - }, - { - "kind": "queued_input", - "messageId": "01xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx", - "event": { - "sessionId": "source", - "eventId": "33333333-3333-4333-8333-333333333333", - "agentId": "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa", - "eventType": "user.message", - "provenance": "native" - } - }, - { - "kind": "queued_input", - "messageId": "02xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx", - "event": { - "sessionId": "source", - "eventId": "33333333-3333-4333-8333-333333333333", - "agentId": "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa", - "eventType": "user.message", - "provenance": "native" - } - }, + } + ], + "captureComplete": true + } + ], + "captureComplete": true + } + }, + { + "name": "session_id_control_character", + "value": { + "version": 1, + "observationProvenance": "native", + "sources": [ + { + "input": { + "queueItemId": "46fc3a43-7f19-4b0a-9e32-d5ef4dc25552", + "agentId": "worker" + }, + "admissions": [], + "captureComplete": true, + "completion": { + "sessionId": "session\u0000id", + "eventId": "44444444-4444-4444-8444-444444444444", + "agentId": "worker", + "eventType": "subagent.completed", + "provenance": "native" + } + } + ], + "captureComplete": true + } + }, + { + "name": "agent_id_control_character", + "value": { + "version": 1, + "observationProvenance": "native", + "sources": [ + { + "input": { + "queueItemId": "46fc3a43-7f19-4b0a-9e32-d5ef4dc25552", + "agentId": "worker\nagent" + }, + "admissions": [], + "captureComplete": true + } + ], + "captureComplete": true + } + }, + { + "name": "message_id_control_character", + "value": { + "version": 1, + "observationProvenance": "native", + "sources": [ + { + "input": { + "queueItemId": "46fc3a43-7f19-4b0a-9e32-d5ef4dc25552", + "agentId": "worker" + }, + "admissions": [ { "kind": "queued_input", - "messageId": "03xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx", - "event": { - "sessionId": "source", - "eventId": "33333333-3333-4333-8333-333333333333", - "agentId": "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa", - "eventType": "user.message", - "provenance": "native" - } + "messageId": "message…id" } ], "captureComplete": true @@ -1424,7 +1659,7 @@ }, { "kind": "queued_input", - "messageId": "é🐈\"\\\nxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx", + "messageId": "é🐈\"\\Ωxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx", "event": { "sessionId": "s", "eventId": "33333333-3333-4333-8333-333333333333", @@ -1446,12 +1681,59 @@ "utf8MaxBytes": 256, "acceptedBytes": 256, "rejectedBytes": 257, - "multibyteRejectedBytes": 257 + "multibyteRejectedBytes": 257, + "multibyteAcceptedBytes": 256, + "multibyteAcceptedFields": [ + "sessionId", + "agentId", + "messageId" + ], + "multibyteRejectedFields": [ + "sessionId", + "agentId", + "messageId" + ], + "rejectUnicodeControlCharacters": true }, "expandedGraphBudget": { "maxBytes": 4096, "wireBytes": 3540, "expandedBytes": 4188, "domain": "fully_materialized_workerCausality_field" + }, + "workflowCompleted": { + "hostInput": { + "kind": "systemNotification", + "content": "Factory run completed", + "metadata": { + "type": "factory_completed", + "runId": "run-1", + "factoryName": "fix-ci", + "status": "completed", + "attempt": 2, + "elapsedMs": 500, + "consumedNanoAiu": 10, + "consumedSubagents": 1 + } + }, + "event": { + "type": "system.notification", + "data": { + "content": "Factory run completed", + "kind": { + "type": "workflow_completed", + "runId": "run-1", + "workflowName": "fix-ci", + "status": "completed", + "attempt": 2, + "elapsedMs": 500, + "consumedNanoAiu": 10, + "consumedSubagents": 1 + } + }, + "id": "55555555-5555-4555-8555-555555555555", + "timestamp": "2026-09-26T00:02:00.000Z", + "parentId": null + } } } From 7c4f3bbfc6a70425451adffa90e8051c5a57ec08 Mon Sep 17 00:00:00 2001 From: Michael Dinh Date: Sat, 26 Sep 2026 19:17:42 -0400 Subject: [PATCH 16/25] fix: finalize worker causality history readers Consume authoritative runtime seal revision 12. Reject invalid Unicode scalar identities, canonicalize legacy persisted workflow completion across public readers, harden duplicate-aware raw JSON repair, and preserve unrelated event payloads. Runtime-Seal: d2e0c4735cf9f49905f19b5521939b81d5931a74 Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> --- docs/features/execution-identity.md | 5 + dotnet/src/Generated/SessionEvents.cs | 22 +- dotnet/src/WorkerCausalityConverter.cs | 47 +- dotnet/test/Unit/WorkerCausalityTests.cs | 31 +- .../diagnosticmetadata/worker_causality.go | 74 ++- go/rpc/zrpc.go | 21 +- go/worker_causality_test.go | 60 ++- .../copilot/WorkerCausalityDeserializer.java | 19 +- .../github/copilot/WorkerCausalityTest.java | 28 +- nodejs/src/generated/rpc.ts | 2 +- nodejs/src/generated/session-events.ts | 23 +- nodejs/src/workerCausality.ts | 34 +- nodejs/test/worker-causality.test.ts | 175 +++++++- python/copilot/_worker_causality.py | 2 +- python/copilot/generated/session_events.py | 11 + python/test_worker_causality.py | 23 +- rust/Cargo.toml | 4 +- .../crates/BUILD.serde_json-1.0.151.bazel | 2 + rust/src/generated/api_types.rs | 4 +- rust/src/generated/session_events.rs | 11 + rust/src/jsonrpc.rs | 421 +++++++++++++++++- rust/src/worker_causality.rs | 20 +- rust/tests/worker_causality_test.rs | 63 ++- scripts/codegen/rust.ts | 9 +- scripts/codegen/typescript.ts | 6 +- test/worker-causality.json | 66 ++- 26 files changed, 1048 insertions(+), 135 deletions(-) diff --git a/docs/features/execution-identity.md b/docs/features/execution-identity.md index 9e4d0e8352..3ea026cea9 100644 --- a/docs/features/execution-identity.md +++ b/docs/features/execution-identity.md @@ -143,6 +143,11 @@ surface, physical dispatch or UI policy. `source` identifies a sender, not its sending Turn. Recovery with no primary message still has no `user.message` admission or `originatingMessageId`. +Worker identity strings contain valid Unicode scalar values, are non-empty, +contain no Unicode control characters, and encode to at most 256 UTF-8 bytes. +Printable non-ASCII values are supported; invalid surrogate code units are +rejected rather than preserved through replacement encoding. + Workflow completion notifications use the canonical public `system.notification.data.kind.type = "workflow_completed"` discriminator and typed `runId`, `workflowName`, status, usage and attempt fields. The AHP relay diff --git a/dotnet/src/Generated/SessionEvents.cs b/dotnet/src/Generated/SessionEvents.cs index bc76b93886..8cd3c3ac50 100644 --- a/dotnet/src/Generated/SessionEvents.cs +++ b/dotnet/src/Generated/SessionEvents.cs @@ -15374,10 +15374,10 @@ public WorkerObservationProvenance(string value) /// Gets the value associated with this . public string Value => _value ?? string.Empty; - /// Gets the native value. + /// Observed by the native runtime. public static WorkerObservationProvenance Native { get; } = new("native"); - /// Gets the ahp_coordinator value. + /// Observed by the AHP coordinator. public static WorkerObservationProvenance AhpCoordinator { get; } = new("ahp_coordinator"); /// Returns a value indicating whether two instances are equivalent. @@ -15435,19 +15435,19 @@ public WorkerEventType(string value) /// Gets the value associated with this . public string Value => _value ?? string.Empty; - /// Gets the tool.execution_start value. + /// Observed tool execution start occurrence. public static WorkerEventType ToolExecutionStart { get; } = new("tool.execution_start"); - /// Gets the user.message value. + /// Observed admitted user message occurrence. public static WorkerEventType UserMessage { get; } = new("user.message"); - /// Gets the subagent.completed value. + /// Observed worker completion occurrence. public static WorkerEventType SubagentCompleted { get; } = new("subagent.completed"); - /// Gets the system.notification value. + /// Observed worker notification occurrence. public static WorkerEventType SystemNotification { get; } = new("system.notification"); - /// Gets the assistant.turn_start value. + /// Observed assistant turn start occurrence. public static WorkerEventType AssistantTurnStart { get; } = new("assistant.turn_start"); /// Returns a value indicating whether two instances are equivalent. @@ -15505,10 +15505,10 @@ public WorkerAdmissionKind(string value) /// Gets the value associated with this . public string Value => _value ?? string.Empty; - /// Gets the queued_input value. + /// A queued worker input was admitted. public static WorkerAdmissionKind QueuedInput { get; } = new("queued_input"); - /// Gets the system_continuation value. + /// A system continuation was admitted. public static WorkerAdmissionKind SystemContinuation { get; } = new("system_continuation"); /// Returns a value indicating whether two instances are equivalent. @@ -15566,10 +15566,10 @@ public WorkerNotificationMode(string value) /// Gets the value associated with this . public string Value => _value ?? string.Empty; - /// Gets the queued value. + /// The notification was consumed through the queued input path. public static WorkerNotificationMode Queued { get; } = new("queued"); - /// Gets the immediate value. + /// The notification was consumed during an already-open iteration. public static WorkerNotificationMode Immediate { get; } = new("immediate"); /// Returns a value indicating whether two instances are equivalent. diff --git a/dotnet/src/WorkerCausalityConverter.cs b/dotnet/src/WorkerCausalityConverter.cs index 618e5e3857..5bbd376394 100644 --- a/dotnet/src/WorkerCausalityConverter.cs +++ b/dotnet/src/WorkerCausalityConverter.cs @@ -14,21 +14,21 @@ internal sealed class WorkerCausalityConverter : JsonConverter where T : c { public override T? Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) { - using var document = JsonDocument.ParseValue(ref reader); - var value = document.RootElement; - var remaining = 4096; - if (Small(value, ref remaining, 0) && Supported(value)) + try { - try + using var document = JsonDocument.ParseValue(ref reader); + var value = document.RootElement; + var remaining = 4096; + if (Small(value, ref remaining, 0) && Supported(value)) { if (!WithinBudget(value)) throw new JsonException("optional worker metadata budget"); return JsonSerializer.Deserialize(value, (JsonTypeInfo)options.GetTypeInfo(typeof(T))); } - catch (JsonException) - { - // Optional diagnostics do not change product event handling. - } + } + catch (Exception error) when (error is JsonException or InvalidOperationException) + { + // Optional diagnostics do not change product event handling. } Trace.TraceWarning("Ignoring invalid, unsupported or oversized workerCausality metadata"); return null; @@ -110,12 +110,29 @@ private static bool Fields(JsonElement value, params string[] allowed) return true; } - private static bool Text(JsonElement value, string property) => - value.TryGetProperty(property, out var text) && - text.ValueKind == JsonValueKind.String && - text.GetString()!.Length > 0 && - Encoding.UTF8.GetByteCount(text.GetString()!) <= 256 && - text.GetString()!.All(character => !char.IsControl(character)); + private static bool Text(JsonElement value, string property) + { + if (!value.TryGetProperty(property, out var text) || text.ValueKind != JsonValueKind.String) + return false; + var content = text.GetString()!; + if (content.Length == 0) + return false; + for (var index = 0; index < content.Length; index++) + { + var character = content[index]; + if (char.IsHighSurrogate(character)) + { + if (index + 1 >= content.Length || !char.IsLowSurrogate(content[index + 1])) + return false; + index++; + } + else if (char.IsLowSurrogate(character) || char.IsControl(character)) + { + return false; + } + } + return Encoding.UTF8.GetByteCount(content) <= 256; + } private static bool Uuid(JsonElement value, string property) => Text(value, property) && Guid.TryParseExact(value.GetProperty(property).GetString(), "D", out _); diff --git a/dotnet/test/Unit/WorkerCausalityTests.cs b/dotnet/test/Unit/WorkerCausalityTests.cs index ff3d1d2d19..2afb627a69 100644 --- a/dotnet/test/Unit/WorkerCausalityTests.cs +++ b/dotnet/test/Unit/WorkerCausalityTests.cs @@ -73,12 +73,29 @@ public void ExactUtf8BudgetAndUnknownFields() [Fact] public void CanonicalWorkflowCompletionDecodesTypedFields() { - var parsed = SessionEvent.FromJson(Corpus["workflowCompleted"]!["event"]!.ToJsonString()); - var notification = Assert.IsType(parsed); - var completion = Assert.IsType(notification.Data.Kind); - Assert.Equal("fix-ci", completion.WorkflowName); - Assert.Equal("run-1", completion.RunId); - Assert.Equal(SystemNotificationWorkflowCompletedStatus.Completed, completion.Status); - Assert.Equal(1u, completion.ConsumedSubagents); + foreach (var fixtureName in new[] { "workflowCompleted", "legacyWorkflowCompleted" }) + { + var parsed = SessionEvent.FromJson(Corpus[fixtureName]!["event"]!.ToJsonString()); + var notification = Assert.IsType(parsed); + var completion = Assert.IsType(notification.Data.Kind); + Assert.Equal("fix-ci", completion.WorkflowName); + Assert.Equal("run-1", completion.RunId); + Assert.Equal(SystemNotificationWorkflowCompletedStatus.Completed, completion.Status); + Assert.Equal(1u, completion.ConsumedSubagents); + } + } + + [Fact] + public void InvalidUnicodeScalarIdentitiesAreUnavailable() + { + foreach (var test in Corpus["invalidRaw"]!.AsArray()) + { + var raw = test!["json"]!.GetValue(); + var wire = $"{{\"sent\":true,\"workerCausality\":{raw}}}"; + var result = JsonSerializer.Deserialize(wire, RpcOptions); + Assert.NotNull(result); + Assert.Null(result.WorkerCausality); + Assert.True(result.Sent); + } } } diff --git a/go/internal/diagnosticmetadata/worker_causality.go b/go/internal/diagnosticmetadata/worker_causality.go index 0b07035b39..1f6395cc32 100644 --- a/go/internal/diagnosticmetadata/worker_causality.go +++ b/go/internal/diagnosticmetadata/worker_causality.go @@ -9,6 +9,7 @@ import ( "log" "regexp" "unicode" + "unicode/utf8" ) var uuidPattern = regexp.MustCompile(`^[0-9a-fA-F]{8}-([0-9a-fA-F]{4}-){3}[0-9a-fA-F]{12}$`) @@ -31,6 +32,77 @@ func uuid(value any) bool { return ok && uuidPattern.MatchString(s) } +func hexQuad(value []byte) (uint16, bool) { + if len(value) < 4 { + return 0, false + } + var result uint16 + for _, character := range value[:4] { + result <<= 4 + switch { + case character >= '0' && character <= '9': + result += uint16(character - '0') + case character >= 'a' && character <= 'f': + result += uint16(character-'a') + 10 + case character >= 'A' && character <= 'F': + result += uint16(character-'A') + 10 + default: + return 0, false + } + } + return result, true +} + +func validUnicodeScalarEscapes(raw []byte) bool { + if !utf8.Valid(raw) { + return false + } + inString := false + for index := 0; index < len(raw); index++ { + character := raw[index] + if !inString { + if character == '"' { + inString = true + } + continue + } + if character == '"' { + inString = false + continue + } + if character != '\\' { + continue + } + if index+1 >= len(raw) { + return false + } + if raw[index+1] != 'u' { + index++ + continue + } + codeUnit, ok := hexQuad(raw[index+2:]) + if !ok { + return false + } + if codeUnit >= 0xd800 && codeUnit <= 0xdbff { + if index+11 >= len(raw) || raw[index+6] != '\\' || raw[index+7] != 'u' { + return false + } + low, ok := hexQuad(raw[index+8:]) + if !ok || low < 0xdc00 || low > 0xdfff { + return false + } + index += 11 + } else { + if codeUnit >= 0xdc00 && codeUnit <= 0xdfff { + return false + } + index += 5 + } + } + return !inString +} + func fields(value map[string]any, allowed ...string) bool { if len(value) > len(allowed) { return false @@ -181,7 +253,7 @@ func supported(value map[string]any) bool { // ReadWorkerCausality decodes availability only, not enclosing self identity or association. func ReadWorkerCausality(raw json.RawMessage, target any) bool { - if len(raw) == 0 { + if len(raw) == 0 || !validUnicodeScalarEscapes(raw) { return false } var value map[string]any diff --git a/go/rpc/zrpc.go b/go/rpc/zrpc.go index fbad2ad1b5..a829037c41 100644 --- a/go/rpc/zrpc.go +++ b/go/rpc/zrpc.go @@ -23245,7 +23245,9 @@ const ( type WorkerAdmissionKind string const ( - WorkerAdmissionKindQueuedInput WorkerAdmissionKind = "queued_input" + // A queued worker input was admitted. + WorkerAdmissionKindQueuedInput WorkerAdmissionKind = "queued_input" + // A system continuation was admitted. WorkerAdmissionKindSystemContinuation WorkerAdmissionKind = "system_continuation" ) @@ -23254,11 +23256,16 @@ const ( type WorkerEventType string const ( + // Observed assistant turn start occurrence. WorkerEventTypeAssistantTurnStart WorkerEventType = "assistant.turn_start" - WorkerEventTypeSubagentCompleted WorkerEventType = "subagent.completed" + // Observed worker completion occurrence. + WorkerEventTypeSubagentCompleted WorkerEventType = "subagent.completed" + // Observed worker notification occurrence. WorkerEventTypeSystemNotification WorkerEventType = "system.notification" + // Observed tool execution start occurrence. WorkerEventTypeToolExecutionStart WorkerEventType = "tool.execution_start" - WorkerEventTypeUserMessage WorkerEventType = "user.message" + // Observed admitted user message occurrence. + WorkerEventTypeUserMessage WorkerEventType = "user.message" ) // How the owned notification was consumed. @@ -23267,8 +23274,10 @@ const ( type WorkerNotificationMode string const ( + // The notification was consumed during an already-open iteration. WorkerNotificationModeImmediate WorkerNotificationMode = "immediate" - WorkerNotificationModeQueued WorkerNotificationMode = "queued" + // The notification was consumed through the queued input path. + WorkerNotificationModeQueued WorkerNotificationMode = "queued" ) // Producer of an observation, not the execution location of every referenced source. @@ -23277,8 +23286,10 @@ const ( type WorkerObservationProvenance string const ( + // Observed by the AHP coordinator. WorkerObservationProvenanceAhpCoordinator WorkerObservationProvenance = "ahp_coordinator" - WorkerObservationProvenanceNative WorkerObservationProvenance = "native" + // Observed by the native runtime. + WorkerObservationProvenanceNative WorkerObservationProvenance = "native" ) // Execution-critical workflow storage operation. diff --git a/go/worker_causality_test.go b/go/worker_causality_test.go index 96eea10015..136852c19e 100644 --- a/go/worker_causality_test.go +++ b/go/worker_causality_test.go @@ -4,6 +4,7 @@ package copilot import ( "encoding/json" + "fmt" "os" "reflect" "testing" @@ -21,6 +22,10 @@ type workerCausalityCorpus struct { Name string Value any } + InvalidRaw []struct { + Name string + JSON string + } Boundaries []struct { Name string Value any @@ -29,6 +34,9 @@ type workerCausalityCorpus struct { WorkflowCompleted struct { Event json.RawMessage } + LegacyWorkflowCompleted struct { + Event json.RawMessage + } } func workerCorpus(t *testing.T) workerCausalityCorpus { @@ -121,21 +129,43 @@ func TestWorkerCausalityExactUTF8Budget(t *testing.T) { func TestCanonicalWorkflowCompletionDecodesTypedFields(t *testing.T) { corpus := workerCorpus(t) - var event rpc.SessionEvent - if err := json.Unmarshal(corpus.WorkflowCompleted.Event, &event); err != nil { - t.Fatal(err) + for _, raw := range []json.RawMessage{ + corpus.WorkflowCompleted.Event, + corpus.LegacyWorkflowCompleted.Event, + } { + var event rpc.SessionEvent + if err := json.Unmarshal(raw, &event); err != nil { + t.Fatal(err) + } + data, ok := event.Data.(*rpc.SystemNotificationData) + if !ok { + t.Fatalf("data type = %T", event.Data) + } + kind, ok := data.Kind.(*rpc.SystemNotificationWorkflowCompleted) + if !ok { + t.Fatalf("kind type = %T", data.Kind) + } + if kind.WorkflowName != "fix-ci" || kind.RunID != "run-1" || + kind.Status != rpc.SystemNotificationWorkflowCompletedStatusCompleted || + kind.ConsumedSubagents != 1 { + t.Fatalf("unexpected workflow completion: %+v", kind) + } } - data, ok := event.Data.(*rpc.SystemNotificationData) - if !ok { - t.Fatalf("data type = %T", event.Data) - } - kind, ok := data.Kind.(*rpc.SystemNotificationWorkflowCompleted) - if !ok { - t.Fatalf("kind type = %T", data.Kind) - } - if kind.WorkflowName != "fix-ci" || kind.RunID != "run-1" || - kind.Status != rpc.SystemNotificationWorkflowCompletedStatusCompleted || - kind.ConsumedSubagents != 1 { - t.Fatalf("unexpected workflow completion: %+v", kind) +} + +func TestInvalidUnicodeScalarIdentitiesAreUnavailable(t *testing.T) { + for _, test := range workerCorpus(t).InvalidRaw { + var result rpc.TasksSendMessageResult + wire := []byte(fmt.Sprintf(`{"sent":true,"workerCausality":%s}`, test.JSON)) + if err := json.Unmarshal(wire, &result); err != nil { + t.Fatalf("%s rejected product result: %v", test.Name, err) + } + encoded, err := json.Marshal(result) + if err != nil { + t.Fatal(err) + } + if string(encoded) != `{"sent":true}` { + t.Fatalf("%s preserved invalid metadata: %s", test.Name, encoded) + } } } diff --git a/java/sdk/src/main/java/com/github/copilot/WorkerCausalityDeserializer.java b/java/sdk/src/main/java/com/github/copilot/WorkerCausalityDeserializer.java index f2992a7bd8..b9529988cc 100644 --- a/java/sdk/src/main/java/com/github/copilot/WorkerCausalityDeserializer.java +++ b/java/sdk/src/main/java/com/github/copilot/WorkerCausalityDeserializer.java @@ -137,9 +137,22 @@ private static boolean fields(JsonNode value, String... allowed) { } private static boolean text(JsonNode value) { - return value.isTextual() && !value.textValue().isEmpty() - && value.textValue().getBytes(StandardCharsets.UTF_8).length <= 256 - && value.textValue().codePoints().noneMatch(Character::isISOControl); + if (!value.isTextual() || value.textValue().isEmpty()) { + return false; + } + String text = value.textValue(); + for (int index = 0; index < text.length(); index++) { + char character = text.charAt(index); + if (Character.isHighSurrogate(character)) { + if (index + 1 >= text.length() || !Character.isLowSurrogate(text.charAt(index + 1))) { + return false; + } + index++; + } else if (Character.isLowSurrogate(character) || Character.isISOControl(character)) { + return false; + } + } + return text.getBytes(StandardCharsets.UTF_8).length <= 256; } private static boolean uuid(JsonNode value) { diff --git a/java/sdk/src/test/java/com/github/copilot/WorkerCausalityTest.java b/java/sdk/src/test/java/com/github/copilot/WorkerCausalityTest.java index 9d35c26260..90d5163b7e 100644 --- a/java/sdk/src/test/java/com/github/copilot/WorkerCausalityTest.java +++ b/java/sdk/src/test/java/com/github/copilot/WorkerCausalityTest.java @@ -70,13 +70,25 @@ void exactUtf8BudgetAndUnknownFields() throws Exception { @Test void canonicalWorkflowCompletionDecodesTypedFields() throws Exception { - SessionEvent parsed = MAPPER.treeToValue(corpus().path("workflowCompleted").path("event"), SessionEvent.class); - SystemNotificationEvent notification = assertInstanceOf(SystemNotificationEvent.class, parsed); - SystemNotificationWorkflowCompleted completion = assertInstanceOf(SystemNotificationWorkflowCompleted.class, - notification.getData().kind()); - assertEquals("fix-ci", completion.getWorkflowName()); - assertEquals("run-1", completion.getRunId()); - assertEquals(SystemNotificationWorkflowCompletedStatus.COMPLETED, completion.getStatus()); - assertEquals(1L, completion.getConsumedSubagents()); + for (String fixtureName : new String[]{"workflowCompleted", "legacyWorkflowCompleted"}) { + SessionEvent parsed = MAPPER.treeToValue(corpus().path(fixtureName).path("event"), SessionEvent.class); + SystemNotificationEvent notification = assertInstanceOf(SystemNotificationEvent.class, parsed); + SystemNotificationWorkflowCompleted completion = assertInstanceOf(SystemNotificationWorkflowCompleted.class, + notification.getData().kind()); + assertEquals("fix-ci", completion.getWorkflowName()); + assertEquals("run-1", completion.getRunId()); + assertEquals(SystemNotificationWorkflowCompletedStatus.COMPLETED, completion.getStatus()); + assertEquals(1L, completion.getConsumedSubagents()); + } + } + + @Test + void invalidUnicodeScalarIdentitiesAreUnavailable() throws Exception { + for (JsonNode test : corpus().path("invalidRaw")) { + String wire = "{\"sent\":true,\"workerCausality\":" + test.path("json").asText() + "}"; + SessionTasksSendMessageResult result = MAPPER.readValue(wire, SessionTasksSendMessageResult.class); + assertEquals(true, result.sent(), test.path("name").asText()); + assertEquals(null, result.workerCausality(), test.path("name").asText()); + } } } diff --git a/nodejs/src/generated/rpc.ts b/nodejs/src/generated/rpc.ts index 93c1b5ac00..724b0dcdfd 100644 --- a/nodejs/src/generated/rpc.ts +++ b/nodejs/src/generated/rpc.ts @@ -27835,7 +27835,7 @@ export function createServerRpc(connection: MessageConnection) { * @returns Batch of session events returned by a read, with cursor and continuation metadata. */ readPersistedEvents: async (params: SessionsReadPersistedEventsRequest): Promise => - connection.sendRequest("sessions.readPersistedEvents", params), + connection.sendRequest("sessions.readPersistedEvents", params).then(withWorkerCausalityEvents), /** * Finds the local session bound to a GitHub task ID, if any. * diff --git a/nodejs/src/generated/session-events.ts b/nodejs/src/generated/session-events.ts index 95f35ce17a..6bddaaab08 100644 --- a/nodejs/src/generated/session-events.ts +++ b/nodejs/src/generated/session-events.ts @@ -774,24 +774,41 @@ export type UserMessageDelivery = /** * Producer of an observation, not the execution location of every referenced source. */ -export type WorkerObservationProvenance = "native" | "ahp_coordinator"; +export type WorkerObservationProvenance = + /** Observed by the native runtime. */ + | "native" + /** Observed by the AHP coordinator. */ + | "ahp_coordinator"; /** * Supported observed occurrences. Chronological parentId is not a causal reference. */ export type WorkerEventType = + /** Observed tool execution start occurrence. */ | "tool.execution_start" + /** Observed admitted user message occurrence. */ | "user.message" + /** Observed worker completion occurrence. */ | "subagent.completed" + /** Observed worker notification occurrence. */ | "system.notification" + /** Observed assistant turn start occurrence. */ | "assistant.turn_start"; /** * Why this exact worker admission was made. */ -export type WorkerAdmissionKind = "queued_input" | "system_continuation"; +export type WorkerAdmissionKind = + /** A queued worker input was admitted. */ + | "queued_input" + /** A system continuation was admitted. */ + | "system_continuation"; /** * How the owned notification was consumed. */ -export type WorkerNotificationMode = "queued" | "immediate"; +export type WorkerNotificationMode = + /** The notification was consumed through the queued input path. */ + | "queued" + /** The notification was consumed during an already-open iteration. */ + | "immediate"; /** * Content-safe activity observed while a HydraFusion phase is running. */ diff --git a/nodejs/src/workerCausality.ts b/nodejs/src/workerCausality.ts index af44369ecc..159025cc06 100644 --- a/nodejs/src/workerCausality.ts +++ b/nodejs/src/workerCausality.ts @@ -10,10 +10,25 @@ function fields(value: Record, allowed: readonly string[]): boo return Object.keys(value).every((key) => allowed.includes(key)); } +function scalarString(value: string): boolean { + for (let index = 0; index < value.length; index++) { + const unit = value.charCodeAt(index); + if (unit >= 0xd800 && unit <= 0xdbff) { + const next = value.charCodeAt(index + 1); + if (!(next >= 0xdc00 && next <= 0xdfff)) return false; + index++; + } else if (unit >= 0xdc00 && unit <= 0xdfff) { + return false; + } + } + return true; +} + function text(value: unknown): value is string { return ( typeof value === "string" && value.length > 0 && + scalarString(value) && Buffer.byteLength(value, "utf8") <= 256 && !/\p{Cc}/u.test(value) ); @@ -136,12 +151,23 @@ export function withWorkerCausality(data: T): T { } /** Apply the same diagnostic tolerance to historical events as live delivery. */ -export function withWorkerCausalityEvents(result: T): T { - const events = result.events.map((event) => { +export function withWorkerCausalityEvents(result: T): T { + if (!("events" in result) || !Array.isArray(result.events)) return result; + const originalEvents = result.events; + const events = originalEvents.map((event) => { + if ( + !object(event) || + !["user.message", "system.notification", "assistant.turn_start"].includes( + String(event.type) + ) || + !object(event.data) + ) { + return event; + } const data = withWorkerCausality(event.data); return data === event.data ? event : { ...event, data }; }); - return events.every((event, index) => event === result.events[index]) + return events.every((event, index) => event === originalEvents[index]) ? result - : { ...result, events }; + : ({ ...result, events } as T); } diff --git a/nodejs/test/worker-causality.test.ts b/nodejs/test/worker-causality.test.ts index 079b0b16f5..57a18b8819 100644 --- a/nodejs/test/worker-causality.test.ts +++ b/nodejs/test/worker-causality.test.ts @@ -2,7 +2,10 @@ * Copyright (c) Microsoft Corporation. All rights reserved. *--------------------------------------------------------------------------------------------*/ +import { randomUUID } from "node:crypto"; import { readFileSync } from "node:fs"; +import { writeFile } from "node:fs/promises"; +import { join } from "node:path"; import { PassThrough } from "node:stream"; import { createMessageConnection, @@ -10,23 +13,43 @@ import { StreamMessageWriter, } from "vscode-jsonrpc/node.js"; import { describe, expect, it, onTestFinished } from "vitest"; -import { CopilotSession } from "../src/session.js"; +import { approveAll, CopilotSession } from "../src/index.js"; import type { SessionEvent } from "../src/index.js"; +import { createServerRpc } from "../src/generated/rpc.js"; +import { withWorkerCausalityEvents } from "../src/workerCausality.js"; +import { createSdkTestContext } from "./e2e/harness/sdkTestContext.js"; type Wire = Record; +type HistoryRead = { name: string; events: SessionEvent[] }; const corpus = JSON.parse( readFileSync(new URL("../../test/worker-causality.json", import.meta.url), "utf8") ) as { valid: { name: string; event?: Wire; result?: Wire }[]; invalid: { name: string; value: unknown }[]; + invalidRaw: { name: string; json: string }[]; boundaries: { name: string; value: unknown; accepted: boolean }[]; workflowCompleted: { event: Wire }; + legacyWorkflowCompleted: { persistedEvent: Wire }; }; function payload(value: Wire, event: boolean): Wire { return event ? (value.data as Wire) : value; } +function expectWorkflowCompleted(events: SessionEvent[], eventId: string, reader: string): void { + const event = events.find((candidate) => candidate.id === eventId); + expect(event?.type, reader).toBe("system.notification"); + if (event?.type !== "system.notification") { + throw new Error(`${reader} did not return the workflow completion event`); + } + expect(event.data.kind.type, reader).toBe("workflow_completed"); + if (event.data.kind.type !== "workflow_completed") { + throw new Error(`${reader} did not canonicalize the workflow completion event`); + } + expect(event.data.kind.workflowName, reader).toBe("fix-ci"); + expect(event.data.kind, reader).not.toHaveProperty("factoryName"); +} + function readers() { const requests = new PassThrough(); const responses = new PassThrough(); @@ -43,6 +66,7 @@ function readers() { client.listen(); server.listen(); const session = new CopilotSession("codec-session", client); + const serverRpc = createServerRpc(client); const observed: SessionEvent[] = []; const unsubscribe = session.on((event) => observed.push(event)); onTestFinished(() => { @@ -64,14 +88,48 @@ function readers() { message: "test", })) as unknown as Wire; }, - async history(wire: Wire): Promise { + async history(wire: Wire): Promise { reply = { events: [wire], cursor: "cursor", hasMore: false, cursorStatus: "ok" }; - return [await session.getEvents(), (await session.rpc.eventLog.read({})).events]; + return [ + { name: "session.getMessages", events: await session.getEvents() }, + { + name: "session.eventLog.read", + events: (await session.rpc.eventLog.read({})).events, + }, + { + name: "sessions.readPersistedEvents", + events: ( + await serverRpc.sessions.readPersistedEvents({ + sessionId: "codec-session", + }) + ).events, + }, + ]; }, }; } describe("worker causality public readers", () => { + it("preserves unrelated event payloads that reuse the diagnostic field name", () => { + const result = { + events: [ + { + type: "session.start", + data: { workerCausality: { extensionPayload: true } }, + }, + ], + }; + expect(withWorkerCausalityEvents(result)).toBe(result); + expect(result.events[0].data.workerCausality).toEqual({ extensionPayload: true }); + }); + + it.each([ + ["absent events", { method: "session.eventLog.read", state: { phase: "covered" } }], + ["non-array events", { events: { state: "covered" } }], + ])("leaves RPC results with $0 unchanged", (_name, result) => { + expect(withWorkerCausalityEvents(result)).toBe(result); + }); + it.each(corpus.valid)( "$name preserves exact observations and historical product bytes", async (test) => { @@ -83,9 +141,25 @@ describe("worker causality public readers", () => { payload(wire, event).workerCausality ); expect(JSON.stringify(wire)).toBe(original); + if (event) { + for (const history of await reader.history(wire)) { + expect( + payload(history.events[0] as unknown as Wire, true).workerCausality, + history.name + ).toEqual(payload(wire, true).workerCausality); + expect(JSON.stringify(history.events[0]), history.name).toBe(original); + } + } delete payload(wire, event).workerCausality; const baseline = JSON.stringify(await reader.read(wire, event)); + if (event) { + for (const history of await reader.history(wire)) { + expect(JSON.stringify(history.events[0]), `${history.name} absent`).toBe( + baseline + ); + } + } for (const invalid of corpus.invalid) { const candidate = structuredClone(wire); payload(candidate, event).workerCausality = invalid.value; @@ -93,8 +167,11 @@ describe("worker causality public readers", () => { baseline ); if (event) { - for (const events of await reader.history(candidate)) { - expect(JSON.stringify(events[0]), invalid.name).toBe(baseline); + for (const history of await reader.history(candidate)) { + expect( + JSON.stringify(history.events[0]), + `${history.name} ${invalid.name}` + ).toBe(baseline); } } } @@ -114,16 +191,84 @@ describe("worker causality public readers", () => { ); it("decodes the canonical workflow completion notification with typed fields", async () => { - const event = corpus.workflowCompleted.event as unknown as SessionEvent; - expect(event.type).toBe("system.notification"); - if (event.type !== "system.notification") throw new Error("expected system.notification"); - expect(event.data.kind.type).toBe("workflow_completed"); - if (event.data.kind.type !== "workflow_completed") { - throw new Error("expected workflow_completed"); + const reader = readers(); + const eventId = corpus.workflowCompleted.event.id; + if (typeof eventId !== "string") { + throw new Error("workflow completion fixture must have an event ID"); } - expect(event.data.kind.workflowName).toBe("fix-ci"); - expect(event.data.kind.runId).toBe("run-1"); - expect(event.data.kind.status).toBe("completed"); - expect(event.data.kind.consumedSubagents).toBe(1); + for (const history of await reader.history(corpus.workflowCompleted.event)) { + expectWorkflowCompleted(history.events, eventId, history.name); + } + }); + + it.each(corpus.invalidRaw)("$name rejects invalid Unicode scalar identities", async (test) => { + const reader = readers(); + const result = { + sent: true, + workerCausality: JSON.parse(test.json), + }; + expect(await reader.read(result, false)).toEqual({ sent: true }); }); }); + +describe.skipIf(process.env.COPILOT_RUNTIME_SOURCE !== "checkout")( + "worker causality persisted history", + async () => { + const { copilotClient: client, env } = await createSdkTestContext(); + + it("canonicalizes legacy workflow completion through every history reader", async () => { + const session = await client.createSession({ onPermissionRequest: approveAll }); + const sessionId = session.sessionId; + await client.rpc.sessions.save({ sessionId }); + await session.disconnect(); + + const startId = randomUUID(); + const timestamp = new Date().toISOString(); + const persistedEvent = { + ...structuredClone(corpus.legacyWorkflowCompleted.persistedEvent), + parentId: startId, + }; + const eventId = persistedEvent.id; + if (typeof eventId !== "string") { + throw new Error("legacy workflow fixture must have an event ID"); + } + const copilotHome = env.COPILOT_HOME; + if (copilotHome === undefined) { + throw new Error("SDK test context must define COPILOT_HOME"); + } + await writeFile( + join(copilotHome, "session-state", sessionId, "events.jsonl"), + `${JSON.stringify({ + type: "session.start", + data: { + sessionId, + version: 1, + producer: "node-sdk-worker-history-test", + copilotVersion: "test", + startTime: timestamp, + }, + id: startId, + timestamp, + parentId: null, + })}\n${JSON.stringify(persistedEvent)}\n` + ); + + const persisted = await client.rpc.sessions.readPersistedEvents({ + sessionId, + max: 1000, + }); + expectWorkflowCompleted(persisted.events, eventId, "sessions.readPersistedEvents"); + + const resumed = await client.resumeSession(sessionId, { + onPermissionRequest: approveAll, + }); + try { + expectWorkflowCompleted(await resumed.getEvents(), eventId, "session.getMessages"); + const active = await resumed.rpc.eventLog.read({ max: 1000, waitMs: 0 }); + expectWorkflowCompleted(active.events, eventId, "session.eventLog.read"); + } finally { + await resumed.disconnect(); + } + }); + } +); diff --git a/python/copilot/_worker_causality.py b/python/copilot/_worker_causality.py index edd9696cae..af9834af2a 100644 --- a/python/copilot/_worker_causality.py +++ b/python/copilot/_worker_causality.py @@ -23,7 +23,7 @@ def _text(value: Any) -> bool: isinstance(value, str) and bool(value) and len(value.encode("utf-8")) <= 256 - and all(unicodedata.category(char) != "Cc" for char in value) + and all(unicodedata.category(char) not in ("Cc", "Cs") for char in value) ) diff --git a/python/copilot/generated/session_events.py b/python/copilot/generated/session_events.py index c45375a60e..89ceacea5c 100644 --- a/python/copilot/generated/session_events.py +++ b/python/copilot/generated/session_events.py @@ -14789,28 +14789,39 @@ class Verbosity(Enum): class WorkerAdmissionKind(Enum): "Why this exact worker admission was made." + # A queued worker input was admitted. QUEUED_INPUT = "queued_input" + # A system continuation was admitted. SYSTEM_CONTINUATION = "system_continuation" class WorkerEventType(Enum): "Supported observed occurrences. Chronological parentId is not a causal reference." + # Observed tool execution start occurrence. TOOL_EXECUTION_START = "tool.execution_start" + # Observed admitted user message occurrence. USER_MESSAGE = "user.message" + # Observed worker completion occurrence. SUBAGENT_COMPLETED = "subagent.completed" + # Observed worker notification occurrence. SYSTEM_NOTIFICATION = "system.notification" + # Observed assistant turn start occurrence. ASSISTANT_TURN_START = "assistant.turn_start" class WorkerNotificationMode(Enum): "How the owned notification was consumed." + # The notification was consumed through the queued input path. QUEUED = "queued" + # The notification was consumed during an already-open iteration. IMMEDIATE = "immediate" class WorkerObservationProvenance(Enum): "Producer of an observation, not the execution location of every referenced source." + # Observed by the native runtime. NATIVE = "native" + # Observed by the AHP coordinator. AHP_COORDINATOR = "ahp_coordinator" diff --git a/python/test_worker_causality.py b/python/test_worker_causality.py index 696655abba..2897a4c812 100644 --- a/python/test_worker_causality.py +++ b/python/test_worker_causality.py @@ -52,10 +52,19 @@ def test_worker_causality_exact_utf8_budget_and_unknown_fields(case): def test_canonical_workflow_completion_decodes_typed_fields(): - event = session_event_from_dict(CORPUS["workflowCompleted"]["event"]) - assert event.type.value == "system.notification" - assert event.data.kind.type == "workflow_completed" - assert event.data.kind.workflow_name == "fix-ci" - assert event.data.kind.run_id == "run-1" - assert event.data.kind.status.value == "completed" - assert event.data.kind.consumed_subagents == 1 + for fixture in (CORPUS["workflowCompleted"], CORPUS["legacyWorkflowCompleted"]): + event = session_event_from_dict(fixture["event"]) + assert event.type.value == "system.notification" + assert event.data.kind.type == "workflow_completed" + assert event.data.kind.workflow_name == "fix-ci" + assert event.data.kind.run_id == "run-1" + assert event.data.kind.status.value == "completed" + assert event.data.kind.consumed_subagents == 1 + + +@pytest.mark.parametrize("case", CORPUS["invalidRaw"], ids=lambda case: case["name"]) +def test_invalid_unicode_scalar_identities_are_unavailable(case): + result = TasksSendMessageResult.from_dict( + {"sent": True, "workerCausality": json.loads(case["json"])} + ) + assert result.to_dict() == {"sent": True} diff --git a/rust/Cargo.toml b/rust/Cargo.toml index 22cc081fd8..e04844e881 100644 --- a/rust/Cargo.toml +++ b/rust/Cargo.toml @@ -48,7 +48,7 @@ async-trait = "0.1" indexmap = { version = "2", features = ["serde"] } schemars = { version = "1", optional = true } serde = { version = "1", features = ["derive"] } -serde_json = "1" +serde_json = { version = "1", features = ["raw_value"] } tokio = { version = "1", features = ["io-util", "sync", "rt", "process", "net", "time", "macros"] } tokio-stream = { version = "0.1", features = ["sync"] } tokio-util = { version = "0.7", default-features = false } @@ -128,7 +128,7 @@ required-features = ["test-support"] dirs = "5" flate2 = "1" native-tls = "0.2" -serde_json = "1" +serde_json = { version = "1", features = ["raw_value"] } # 0.11 adds a second digest/block-buffer stack beside the SDK's transitive 0.10. sha2 = "0.10" tar = "0.4" diff --git a/rust/bazel/crates/BUILD.serde_json-1.0.151.bazel b/rust/bazel/crates/BUILD.serde_json-1.0.151.bazel index e02e3ef934..84754570fe 100644 --- a/rust/bazel/crates/BUILD.serde_json-1.0.151.bazel +++ b/rust/bazel/crates/BUILD.serde_json-1.0.151.bazel @@ -41,6 +41,7 @@ rust_library( crate_features = [ "alloc", "default", + "raw_value", "std", ], crate_root = "src/lib.rs", @@ -102,6 +103,7 @@ cargo_build_script( crate_features = [ "alloc", "default", + "raw_value", "std", ], crate_name = "build_script_build", diff --git a/rust/src/generated/api_types.rs b/rust/src/generated/api_types.rs index e924f7fb32..266130e68e 100644 --- a/rust/src/generated/api_types.rs +++ b/rust/src/generated/api_types.rs @@ -22522,7 +22522,7 @@ pub struct TasksSendMessageResult { #[serde(skip_serializing_if = "Option::is_none")] #[serde( default, - deserialize_with = "crate::worker_causality::deserialize_optional" + deserialize_with = "crate::worker_causality::deserialize_optional_raw" )] pub worker_causality: Option, } @@ -28950,7 +28950,7 @@ pub struct SessionTasksSendMessageResult { #[serde(skip_serializing_if = "Option::is_none")] #[serde( default, - deserialize_with = "crate::worker_causality::deserialize_optional" + deserialize_with = "crate::worker_causality::deserialize_optional_raw" )] pub worker_causality: Option, } diff --git a/rust/src/generated/session_events.rs b/rust/src/generated/session_events.rs index 5f51d8f128..5ae0e680ec 100644 --- a/rust/src/generated/session_events.rs +++ b/rust/src/generated/session_events.rs @@ -9075,8 +9075,10 @@ pub enum UserMessageDelivery { /// Producer of an observation, not the execution location of every referenced source. #[derive(Debug, Clone, Default, PartialEq, Eq, Serialize, Deserialize)] pub enum WorkerObservationProvenance { + /// Observed by the native runtime. #[serde(rename = "native")] Native, + /// Observed by the AHP coordinator. #[serde(rename = "ahp_coordinator")] AhpCoordinator, /// Unknown variant for forward compatibility. @@ -9088,14 +9090,19 @@ pub enum WorkerObservationProvenance { /// Supported observed occurrences. Chronological parentId is not a causal reference. #[derive(Debug, Clone, Default, PartialEq, Eq, Serialize, Deserialize)] pub enum WorkerEventType { + /// Observed tool execution start occurrence. #[serde(rename = "tool.execution_start")] ToolExecutionStart, + /// Observed admitted user message occurrence. #[serde(rename = "user.message")] UserMessage, + /// Observed worker completion occurrence. #[serde(rename = "subagent.completed")] SubagentCompleted, + /// Observed worker notification occurrence. #[serde(rename = "system.notification")] SystemNotification, + /// Observed assistant turn start occurrence. #[serde(rename = "assistant.turn_start")] AssistantTurnStart, /// Unknown variant for forward compatibility. @@ -9107,8 +9114,10 @@ pub enum WorkerEventType { /// Why this exact worker admission was made. #[derive(Debug, Clone, Default, PartialEq, Eq, Serialize, Deserialize)] pub enum WorkerAdmissionKind { + /// A queued worker input was admitted. #[serde(rename = "queued_input")] QueuedInput, + /// A system continuation was admitted. #[serde(rename = "system_continuation")] SystemContinuation, /// Unknown variant for forward compatibility. @@ -9120,8 +9129,10 @@ pub enum WorkerAdmissionKind { /// How the owned notification was consumed. #[derive(Debug, Clone, Default, PartialEq, Eq, Serialize, Deserialize)] pub enum WorkerNotificationMode { + /// The notification was consumed through the queued input path. #[serde(rename = "queued")] Queued, + /// The notification was consumed during an already-open iteration. #[serde(rename = "immediate")] Immediate, /// Unknown variant for forward compatibility. diff --git a/rust/src/jsonrpc.rs b/rust/src/jsonrpc.rs index 1a590c555b..2f5be531d9 100644 --- a/rust/src/jsonrpc.rs +++ b/rust/src/jsonrpc.rs @@ -29,6 +29,7 @@ pub(crate) type InlineResponseCallback = /// Internal pairing of the response delivery channel with an optional /// inline callback that the read loop runs synchronously before delivery. struct PendingRequest { + method: String, sender: oneshot::Sender, inline_callback: Option, } @@ -250,6 +251,283 @@ fn repair_lone_surrogates(body: &[u8]) -> Option> { repaired } +#[derive(Default)] +struct RepairedWorkerPaths { + result: bool, + result_events: Vec, + notification_event: bool, +} + +struct ReadMessage { + message: JsonRpcMessage, + repaired_worker_paths: RepairedWorkerPaths, +} + +fn skip_json_string(raw: &[u8], index: &mut usize) -> bool { + if raw.get(*index) != Some(&b'"') { + return false; + } + *index += 1; + while let Some(byte) = raw.get(*index) { + match byte { + b'"' => { + *index += 1; + return true; + } + b'\\' => { + *index += if raw.get(*index + 1) == Some(&b'u') { + 6 + } else { + 2 + }; + } + _ => *index += 1, + } + } + false +} + +fn skip_json_whitespace(raw: &[u8], index: &mut usize) { + while raw + .get(*index) + .is_some_and(|byte| matches!(byte, b' ' | b'\n' | b'\r' | b'\t')) + { + *index += 1; + } +} + +fn skip_json_value(raw: &[u8], index: &mut usize) -> bool { + skip_json_whitespace(raw, index); + match raw.get(*index) { + Some(b'"') => skip_json_string(raw, index), + Some(b'{' | b'[') => { + let mut stack = vec![if raw[*index] == b'{' { b'}' } else { b']' }]; + *index += 1; + while let Some(byte) = raw.get(*index) { + match byte { + b'"' => { + if !skip_json_string(raw, index) { + return false; + } + } + b'{' => { + stack.push(b'}'); + *index += 1; + } + b'[' => { + stack.push(b']'); + *index += 1; + } + b'}' | b']' => { + if stack.pop() != Some(*byte) { + return false; + } + *index += 1; + if stack.is_empty() { + return true; + } + } + _ => *index += 1, + } + } + false + } + Some(_) => { + while raw.get(*index).is_some_and(|byte| { + !matches!(byte, b',' | b'}' | b']' | b' ' | b'\n' | b'\r' | b'\t') + }) { + *index += 1; + } + true + } + None => false, + } +} + +fn raw_ascii_string(raw: &[u8]) -> Option { + if raw.first() != Some(&b'"') || raw.last() != Some(&b'"') { + return None; + } + let mut output = String::new(); + let mut index = 1; + while index + 1 < raw.len() { + let byte = raw[index]; + if byte != b'\\' { + if !byte.is_ascii() { + return None; + } + output.push(char::from(byte)); + index += 1; + continue; + } + index += 1; + match raw.get(index)? { + b'"' | b'\\' | b'/' => output.push(char::from(raw[index])), + b'u' => { + let digits = std::str::from_utf8(raw.get(index + 1..index + 5)?).ok()?; + let value = u16::from_str_radix(digits, 16).ok()?; + if value > 0x7f { + return None; + } + output.push(char::from(value as u8)); + index += 4; + } + _ => return None, + } + index += 1; + } + Some(output) +} + +fn raw_object_property<'a>(raw: &'a [u8], property: &str) -> Option<&'a [u8]> { + let mut index = 0; + skip_json_whitespace(raw, &mut index); + if raw.get(index) != Some(&b'{') { + return None; + } + index += 1; + let mut found = None; + loop { + skip_json_whitespace(raw, &mut index); + if raw.get(index) == Some(&b'}') { + return found; + } + let key_start = index; + if !skip_json_string(raw, &mut index) { + return None; + } + let key = raw_ascii_string(&raw[key_start..index]); + skip_json_whitespace(raw, &mut index); + if raw.get(index) != Some(&b':') { + return None; + } + index += 1; + skip_json_whitespace(raw, &mut index); + let value_start = index; + if !skip_json_value(raw, &mut index) { + return None; + } + if key.as_deref() == Some(property) { + found = Some(&raw[value_start..index]); + } + skip_json_whitespace(raw, &mut index); + if raw.get(index) == Some(&b',') { + index += 1; + } else if raw.get(index) == Some(&b'}') { + return found; + } else { + return None; + } + } +} + +fn raw_array_values(raw: &[u8]) -> Option> { + let mut index = 0; + skip_json_whitespace(raw, &mut index); + if raw.get(index) != Some(&b'[') { + return None; + } + index += 1; + let mut values = Vec::new(); + loop { + skip_json_whitespace(raw, &mut index); + if raw.get(index) == Some(&b']') { + return Some(values); + } + let start = index; + if !skip_json_value(raw, &mut index) { + return None; + } + values.push(&raw[start..index]); + skip_json_whitespace(raw, &mut index); + match raw.get(index) { + Some(b',') => index += 1, + Some(b']') => return Some(values), + _ => return None, + } + } +} + +fn raw_event_worker_needs_removal(raw: &[u8]) -> bool { + let Some(event_type) = raw_object_property(raw, "type").and_then(raw_ascii_string) else { + return false; + }; + if !matches!( + event_type.as_str(), + "user.message" | "assistant.turn_start" | "system.notification" + ) { + return false; + } + let Some(data) = raw_object_property(raw, "data") else { + return false; + }; + raw_object_property(data, "workerCausality") + .is_some_and(|worker| repair_lone_surrogates(worker).is_some()) +} + +fn repaired_worker_paths(body: &[u8]) -> RepairedWorkerPaths { + let mut paths = RepairedWorkerPaths::default(); + if let Some(result) = raw_object_property(body, "result") { + paths.result = raw_object_property(result, "workerCausality") + .is_some_and(|worker| repair_lone_surrogates(worker).is_some()); + if let Some(events) = raw_object_property(result, "events").and_then(raw_array_values) { + paths.result_events = events + .iter() + .enumerate() + .filter_map(|(index, event)| raw_event_worker_needs_removal(event).then_some(index)) + .collect(); + } + } + if let Some(params) = raw_object_property(body, "params") { + paths.notification_event = + raw_object_property(params, "event").is_some_and(raw_event_worker_needs_removal); + } + paths +} + +fn remove_repaired_response_worker_causality( + response: &mut JsonRpcResponse, + paths: &RepairedWorkerPaths, + method: &str, +) { + let Some(result) = response.result.as_mut() else { + return; + }; + if method == "session.tasks.sendMessage" && paths.result { + result + .as_object_mut() + .map(|result| result.remove("workerCausality")); + } + if matches!( + method, + "session.eventLog.read" | "session.getMessages" | "sessions.readPersistedEvents" + ) && let Some(events) = result.get_mut("events").and_then(Value::as_array_mut) + { + for index in &paths.result_events { + events + .get_mut(*index) + .and_then(|event| event.get_mut("data")) + .and_then(Value::as_object_mut) + .map(|data| data.remove("workerCausality")); + } + } +} + +fn remove_repaired_notification_worker_causality( + notification: &mut JsonRpcNotification, + paths: &RepairedWorkerPaths, +) { + if paths.notification_event { + notification + .params + .as_mut() + .and_then(|params| params.get_mut("event")) + .and_then(|event| event.get_mut("data")) + .and_then(Value::as_object_mut) + .map(|data| data.remove("workerCausality")); + } +} + /// One framed JSON-RPC message handed to the writer actor. /// /// `frame` is the fully serialized bytes (header + body); the caller pays @@ -399,15 +677,24 @@ impl JsonRpcClient { loop { match Self::read_message(&mut reader).await { - Ok(Some(message)) => match message { + Ok(Some(ReadMessage { + message, + repaired_worker_paths, + })) => match message { JsonRpcMessage::Response(mut response) => { let id = response.id; let pending = pending_requests.write().remove(&id); if let Some(PendingRequest { + method, sender, inline_callback, }) = pending { + remove_repaired_response_worker_causality( + &mut response, + &repaired_worker_paths, + &method, + ); // Run the inline callback synchronously on the // read loop so any state it mutates (e.g. // registering a server-assigned session id with @@ -454,7 +741,13 @@ impl JsonRpcClient { warn!(request_id = %id, "received response for unknown request id"); } } - JsonRpcMessage::Notification(notification) => { + JsonRpcMessage::Notification(mut notification) => { + if notification.method == "session.event" { + remove_repaired_notification_worker_causality( + &mut notification, + &repaired_worker_paths, + ); + } let _ = notification_tx.send(notification); } JsonRpcMessage::Request(request) => { @@ -487,7 +780,7 @@ impl JsonRpcClient { async fn read_message( reader: &mut BufReader, - ) -> Result, Error> { + ) -> Result, Error> { let mut line = String::new(); let mut content_length = None; @@ -519,10 +812,14 @@ impl JsonRpcClient { reader.read_exact(&mut body).await?; match serde_json::from_slice::(&body) { - Ok(message) => Ok(Some(message)), + Ok(message) => Ok(Some(ReadMessage { + message, + repaired_worker_paths: RepairedWorkerPaths::default(), + })), Err(error) => { // Dropping an undecodable frame could leave its pending // request waiting forever because this layer has no timeout. + let paths = repaired_worker_paths(&body); match repair_lone_surrogates(&body) .and_then(|repaired| serde_json::from_slice::(&repaired).ok()) { @@ -532,7 +829,10 @@ impl JsonRpcClient { length, "recovered JSON-RPC frame containing unpaired UTF-16 surrogates" ); - Ok(Some(message)) + Ok(Some(ReadMessage { + message, + repaired_worker_paths: paths, + })) } None => Err(error.into()), } @@ -590,6 +890,7 @@ impl JsonRpcClient { self.pending_requests.write().insert( id, PendingRequest { + method: method.to_owned(), sender: tx, inline_callback, }, @@ -824,6 +1125,116 @@ mod tests { } } + #[tokio::test] + async fn framed_response_drops_worker_metadata_changed_by_surrogate_repair() { + async fn read_response(body: &[u8], method: &str) -> JsonRpcResponse { + let frame = format!("Content-Length: {}\r\n\r\n", body.len()); + let (mut writer, reader) = tokio::io::duplex(frame.len() + body.len()); + writer.write_all(frame.as_bytes()).await.unwrap(); + writer.write_all(body).await.unwrap(); + writer.shutdown().await.unwrap(); + let ReadMessage { + message, + repaired_worker_paths, + } = JsonRpcClient::read_message(&mut BufReader::new(reader)) + .await + .unwrap() + .unwrap(); + let JsonRpcMessage::Response(mut response) = message else { + panic!("expected response"); + }; + remove_repaired_response_worker_causality( + &mut response, + &repaired_worker_paths, + method, + ); + response + } + + async fn read_notification(body: &[u8]) -> JsonRpcNotification { + let frame = format!("Content-Length: {}\r\n\r\n", body.len()); + let (mut writer, reader) = tokio::io::duplex(frame.len() + body.len()); + writer.write_all(frame.as_bytes()).await.unwrap(); + writer.write_all(body).await.unwrap(); + writer.shutdown().await.unwrap(); + let ReadMessage { + message, + repaired_worker_paths, + } = JsonRpcClient::read_message(&mut BufReader::new(reader)) + .await + .unwrap() + .unwrap(); + let JsonRpcMessage::Notification(mut notification) = message else { + panic!("expected notification"); + }; + if notification.method == "session.event" { + remove_repaired_notification_worker_causality( + &mut notification, + &repaired_worker_paths, + ); + } + notification + } + + let body = br#"{"jsonrpc":"2.0","id":1,"result":{"sent":true,"other":"\ud800","workerCausality":{"version":1,"observationProvenance":"native","sources":[{"input":{"queueItemId":"11111111-1111-4111-8111-111111111111","agentId":"\ud800"},"admissions":[],"captureComplete":true}],"captureComplete":true}}}"#; + let response = read_response(body, "session.tasks.sendMessage").await; + let result = response.result.unwrap(); + assert_eq!(result["sent"], true); + assert_eq!(result["other"], "\u{fffd}"); + assert!(result.get("workerCausality").is_none()); + + let body = br#"{"jsonrpc":"2.0","id":2,"result":{"sent":true,"other":"\ud800","workerCausality":{"version":1,"observationProvenance":"native","sources":[{"input":{"queueItemId":"11111111-1111-4111-8111-111111111111","agentId":"\ufffd"},"admissions":[],"captureComplete":true}],"captureComplete":true}}}"#; + let response = read_response(body, "session.tasks.sendMessage").await; + assert_eq!( + response.result.unwrap()["workerCausality"]["sources"][0]["input"]["agentId"], + "\u{fffd}" + ); + + let body = br#"{"jsonrpc":"2.0","id":3,"result":{"\ud800":"extension","sent":true,"workerCausality":{"version":1,"observationProvenance":"native","sources":[{"input":{"queueItemId":"11111111-1111-4111-8111-111111111111","agentId":"\ud800"},"admissions":[],"captureComplete":true}],"captureComplete":true}}}"#; + let response = read_response(body, "session.tasks.sendMessage").await; + assert!(response.result.unwrap().get("workerCausality").is_none()); + + let body = br#"{"jsonrpc":"2.0","id":4,"result":{"events":[{"type":"session.start","data":{"workerCausality":{"identity":"\ud800"}}}]}}"#; + let response = read_response(body, "session.eventLog.read").await; + assert_eq!( + response.result.unwrap()["events"][0]["data"]["workerCausality"]["identity"], + "\u{fffd}" + ); + + let body = br#"{"jsonrpc":"2.0","id":8,"result":{"events":[{"type":"user.message","data":{"workerCausality":{"identity":"\ud800"}}}]}}"#; + let response = read_response(body, "session.getMessages").await; + assert!( + response.result.unwrap()["events"][0]["data"] + .get("workerCausality") + .is_none() + ); + + let body = br#"{"jsonrpc":"2.0","id":5,"result":{"sent":true,"workerCausality":{"identity":"\ud800"}}}"#; + let response = read_response(body, "unrelated.method").await; + assert_eq!( + response.result.unwrap()["workerCausality"]["identity"], + "\u{fffd}" + ); + + let body = br#"{"jsonrpc":"2.0","id":6,"result":{"sent":true,"workerCausality":{"identity":"\ud800"},"workerCausality":{"identity":"\ufffd"}}}"#; + let response = read_response(body, "session.tasks.sendMessage").await; + assert_eq!( + response.result.unwrap()["workerCausality"]["identity"], + "\u{fffd}" + ); + + let body = br#"{"jsonrpc":"2.0","id":7,"result":{"sent":true,"workerCausality":{"identity":"\ufffd"},"workerCausality":{"identity":"\ud800"}}}"#; + let response = read_response(body, "session.tasks.sendMessage").await; + assert!(response.result.unwrap().get("workerCausality").is_none()); + + let body = br#"{"jsonrpc":"2.0","method":"unrelated.notification","params":{"event":{"type":"user.message","data":{"workerCausality":{"identity":"\ud800"}}}}}"#; + let notification = read_notification(body).await; + assert_eq!( + notification.params.unwrap()["event"]["data"]["workerCausality"]["identity"], + "\u{fffd}" + ); + } + #[test] fn request_new_sets_version() { let req = JsonRpcRequest::new(42, "test.method", None); diff --git a/rust/src/worker_causality.rs b/rust/src/worker_causality.rs index da26f0e037..71cc4413be 100644 --- a/rust/src/worker_causality.rs +++ b/rust/src/worker_causality.rs @@ -7,6 +7,7 @@ use std::io::{self, Write}; use serde::de::DeserializeOwned; use serde::{Deserialize, Deserializer}; use serde_json::Value; +use serde_json::value::RawValue; struct Budget(usize); @@ -171,13 +172,28 @@ pub(crate) fn deserialize_optional<'de, D: Deserializer<'de>, T: DeserializeOwne deserializer: D, ) -> Result, D::Error> { let value = Value::deserialize(deserializer)?; + Ok(decode_optional(value)) +} + +pub(crate) fn deserialize_optional_raw<'de, D: Deserializer<'de>, T: DeserializeOwned>( + deserializer: D, +) -> Result, D::Error> { + let raw = Box::::deserialize(deserializer)?; + let Ok(value) = serde_json::from_str::(raw.get()) else { + tracing::warn!("Ignoring invalid, unsupported or oversized workerCausality metadata"); + return Ok(None); + }; + Ok(decode_optional(value)) +} + +fn decode_optional(value: Value) -> Option { let field = std::collections::BTreeMap::from([("workerCausality", &value)]); if serde_json::to_writer(&mut Budget(0), &field).is_ok() && supported(&value) && let Ok(capture) = serde_json::from_value(value) { - return Ok(Some(capture)); + return Some(capture); } tracing::warn!("Ignoring invalid, unsupported or oversized workerCausality metadata"); - Ok(None) + None } diff --git a/rust/tests/worker_causality_test.rs b/rust/tests/worker_causality_test.rs index 4291ad8bcf..240552757c 100644 --- a/rust/tests/worker_causality_test.rs +++ b/rust/tests/worker_causality_test.rs @@ -13,14 +13,15 @@ fn corpus() -> Value { } fn decode(value: &Value, event: bool) -> Value { + let wire = serde_json::to_string(value).unwrap(); if event { - serde_json::to_value(serde_json::from_value::(value.clone()).unwrap()) - .unwrap() + let parsed = + serde_json::from_str::(&wire).expect("deserialize typed event"); + serde_json::to_value(parsed).expect("serialize typed event") } else { - serde_json::to_value( - serde_json::from_value::(value.clone()).unwrap(), - ) - .unwrap() + let parsed = + serde_json::from_str::(&wire).expect("deserialize task result"); + serde_json::to_value(parsed).expect("serialize task result") } } @@ -82,21 +83,37 @@ fn worker_causality_exact_utf8_budget_and_unknown_fields() { #[test] fn canonical_workflow_completion_decodes_typed_fields() { let corpus = corpus(); - let event: TypedSessionEvent = - serde_json::from_value(corpus["workflowCompleted"]["event"].clone()).unwrap(); - let github_copilot_sdk::session_events::SessionEventData::SystemNotification(notification) = - event.payload - else { - panic!("expected system.notification"); - }; - let SystemNotification::WorkflowCompleted(completion) = notification.kind else { - panic!("expected workflow_completed"); - }; - assert_eq!(completion.workflow_name, "fix-ci"); - assert_eq!(completion.run_id, "run-1"); - assert!(matches!( - completion.status, - SystemNotificationWorkflowCompletedStatus::Completed - )); - assert_eq!(completion.consumed_subagents, 1); + for fixture_name in ["workflowCompleted", "legacyWorkflowCompleted"] { + let event: TypedSessionEvent = + serde_json::from_value(corpus[fixture_name]["event"].clone()).unwrap(); + let github_copilot_sdk::session_events::SessionEventData::SystemNotification(notification) = + event.payload + else { + panic!("expected system.notification"); + }; + let SystemNotification::WorkflowCompleted(completion) = notification.kind else { + panic!("expected workflow_completed"); + }; + assert_eq!(completion.workflow_name, "fix-ci"); + assert_eq!(completion.run_id, "run-1"); + assert!(matches!( + completion.status, + SystemNotificationWorkflowCompletedStatus::Completed + )); + assert_eq!(completion.consumed_subagents, 1); + } +} + +#[test] +fn invalid_unicode_scalar_identities_are_unavailable() { + let corpus = corpus(); + for case in corpus["invalidRaw"].as_array().unwrap() { + let wire = format!( + r#"{{"sent":true,"workerCausality":{}}}"#, + case["json"].as_str().unwrap() + ); + let result: TasksSendMessageResult = serde_json::from_str(&wire).unwrap(); + assert!(result.sent); + assert!(result.worker_causality.is_none(), "{}", case["name"]); + } } diff --git a/scripts/codegen/rust.ts b/scripts/codegen/rust.ts index c610ee3b0d..007f5d7dc3 100644 --- a/scripts/codegen/rust.ts +++ b/scripts/codegen/rust.ts @@ -191,6 +191,10 @@ const RUST_KEYWORDS = new Set([ "while", "yield", ]); +const RAW_WORKER_CAUSALITY_TYPES = new Set([ + "TasksSendMessageResult", + "SessionTasksSendMessageResult", +]); function safeRustFieldName(name: string): string { const snake = toRustFieldName(name); @@ -1060,7 +1064,10 @@ function emitRustStruct( } if (propName === "workerCausality" && !isReq) { - lines.push(` #[serde(default, deserialize_with = "crate::worker_causality::deserialize_optional")]`); + const workerDeserializer = RAW_WORKER_CAUSALITY_TYPES.has(typeName) + ? "deserialize_optional_raw" + : "deserialize_optional"; + lines.push(` #[serde(default, deserialize_with = "crate::worker_causality::${workerDeserializer}")]`); } else if (prop.$ref && typeof prop.const === "string") { lines.push( ` #[serde(${isReq ? "" : "default, "}deserialize_with = "${typeName}::deserialize_${snakeField}")]`, diff --git a/scripts/codegen/typescript.ts b/scripts/codegen/typescript.ts index 76e37ebf14..2063e3a23a 100644 --- a/scripts/codegen/typescript.ts +++ b/scripts/codegen/typescript.ts @@ -1020,7 +1020,11 @@ function emitGroup( lines.push(`${indent}${key}: async (${sigParams.join(", ")}): Promise<${resultType}> =>`); if (rpcMethod === "session.tasks.sendMessage") { lines.push(`${indent} connection.sendRequest<${resultType}>("${rpcMethod}", ${bodyArg}).then(withWorkerCausality),`); - } else if (rpcMethod === "session.getMessages" || rpcMethod === "session.eventLog.read") { + } else if ( + rpcMethod === "session.getMessages" || + rpcMethod === "session.eventLog.read" || + rpcMethod === "sessions.readPersistedEvents" + ) { lines.push(`${indent} connection.sendRequest<${resultType}>("${rpcMethod}", ${bodyArg}).then(withWorkerCausalityEvents),`); } else { lines.push(`${indent} connection.sendRequest("${rpcMethod}", ${bodyArg}),`); diff --git a/test/worker-causality.json b/test/worker-causality.json index d593cc4977..cc6e3ad223 100644 --- a/test/worker-causality.json +++ b/test/worker-causality.json @@ -6,14 +6,16 @@ "src/runtime/src/subagents/orchestrator/worker_causality_tests.rs", "src/runtime/src/session/session_notification_delivery/worker_causality_tests.rs", "src/runtime/src/shared_api/tasks/tests.rs", - "test/cli/ahp/client/relaySession.test.ts" + "test/cli/ahp/client/relaySession.test.ts", + "src/runtime/src/support/events/tests.rs" ], "vectors": [ "native_nested_user_message", "queued_notification", "two_source_assistant_turn_start", "tasks_send_message_result", - "workflow_completed" + "workflow_completed", + "legacy_factory_completed_history" ], "canonicalization": "volatile UUID envelope and identity values are replaced by a fixed token before structural comparison; fixed product content, placement, ordering and typed fields remain exact" }, @@ -1693,7 +1695,8 @@ "agentId", "messageId" ], - "rejectUnicodeControlCharacters": true + "rejectUnicodeControlCharacters": true, + "requireValidUnicodeScalarValues": true }, "expandedGraphBudget": { "maxBytes": 4096, @@ -1735,5 +1738,62 @@ "timestamp": "2026-09-26T00:02:00.000Z", "parentId": null } + }, + "invalidRaw": [ + { + "name": "session_id_unpaired_high_surrogate", + "field": "sessionId", + "json": "{\"version\":1,\"observationProvenance\":\"native\",\"sources\":[{\"input\":{\"queueItemId\":\"46fc3a43-7f19-4b0a-9e32-d5ef4dc25552\",\"agentId\":\"worker\"},\"admissions\":[],\"captureComplete\":true,\"completion\":{\"sessionId\":\"\\ud800\",\"eventId\":\"44444444-4444-4444-8444-444444444444\",\"agentId\":\"worker\",\"eventType\":\"subagent.completed\",\"provenance\":\"native\"}}],\"captureComplete\":true}" + }, + { + "name": "agent_id_unpaired_low_surrogate", + "field": "agentId", + "json": "{\"version\":1,\"observationProvenance\":\"native\",\"sources\":[{\"input\":{\"queueItemId\":\"46fc3a43-7f19-4b0a-9e32-d5ef4dc25552\",\"agentId\":\"\\udc00\"},\"admissions\":[],\"captureComplete\":true}],\"captureComplete\":true}" + }, + { + "name": "message_id_unpaired_high_surrogate", + "field": "messageId", + "json": "{\"version\":1,\"observationProvenance\":\"native\",\"sources\":[{\"input\":{\"queueItemId\":\"46fc3a43-7f19-4b0a-9e32-d5ef4dc25552\",\"agentId\":\"worker\"},\"admissions\":[{\"kind\":\"queued_input\",\"messageId\":\"\\ud800\"}],\"captureComplete\":true}],\"captureComplete\":true}" + } + ], + "legacyWorkflowCompleted": { + "persistedEvent": { + "type": "system.notification", + "data": { + "content": "Factory run completed", + "kind": { + "type": "factory_completed", + "runId": "run-1", + "status": "completed", + "attempt": 2, + "elapsedMs": 500, + "consumedNanoAiu": 10, + "consumedSubagents": 1, + "factoryName": "fix-ci" + } + }, + "id": "55555555-5555-4555-8555-555555555555", + "timestamp": "2026-09-26T00:02:00.000Z", + "parentId": null + }, + "event": { + "type": "system.notification", + "data": { + "content": "Factory run completed", + "kind": { + "type": "workflow_completed", + "runId": "run-1", + "workflowName": "fix-ci", + "status": "completed", + "attempt": 2, + "elapsedMs": 500, + "consumedNanoAiu": 10, + "consumedSubagents": 1 + } + }, + "id": "55555555-5555-4555-8555-555555555555", + "timestamp": "2026-09-26T00:02:00.000Z", + "parentId": null + } } } From 248d41559571f24f4ba518caf4c3ab1ce47c7cf8 Mon Sep 17 00:00:00 2001 From: Michael Dinh Date: Sat, 26 Sep 2026 19:34:36 -0400 Subject: [PATCH 17/25] fix: preserve Python factory RPC compatibility Reconcile the final workflow RPC schema with the existing public FactoryApi and current main fields so standalone consumers and cross-platform generated-surface tests retain source compatibility. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> --- python/copilot/generated/rpc.py | 4768 +++++++++++++++++++------------ 1 file changed, 3008 insertions(+), 1760 deletions(-) diff --git a/python/copilot/generated/rpc.py b/python/copilot/generated/rpc.py index e19faf9d40..0b732d64b0 100644 --- a/python/copilot/generated/rpc.py +++ b/python/copilot/generated/rpc.py @@ -455,24 +455,6 @@ def to_dict(self) -> dict: result["resetDate"] = from_union([from_str, from_none], self.reset_date) return result -# Experimental: this type is part of an experimental API and may change or be removed. -class AccountKind(Enum): - """The provider kind stamped on a signed-in account. - - The provider kind of this account. - - The neutral provider kind. - - A provider a consumer may interactively sign in with. - - The provider kind to sign in with. - """ - ENTRA = "entra" - ENTRA_EMU = "entraEmu" - GITHUB_DOT_COM = "githubDotCom" - LOKI = "loki" - PROXIMA = "proxima" - # Experimental: this type is part of an experimental API and may change or be removed. @dataclass class AccountLoginRequest: @@ -573,20 +555,6 @@ def to_dict(self) -> dict: result["hasMoreUsers"] = from_bool(self.has_more_users) return result -class AuthEnumerateQueryKind(Enum): - ACCOUNTS = "accounts" - PROVIDERS = "providers" - -class AuthReadQueryKind(Enum): - ACTIVE_ACCOUNT = "activeAccount" - LAST_ERRORS = "lastErrors" - STATUS = "status" - -class AuthWriteKind(Enum): - LOGOUT = "logout" - SET_CREDENTIALS = "setCredentials" - SWITCH_ACTIVE = "switchActive" - # Experimental: this type is part of an experimental API and may change or be removed. class AdaptiveThinkingSupport(Enum): """Resolved Anthropic adaptive-thinking capability for a model. @@ -793,82 +761,6 @@ def to_dict(self) -> dict: class APIKeyAuthInfoType(Enum): API_KEY = "api-key" -# Experimental: this type is part of an experimental API and may change or be removed. -@dataclass -class AuthLoginAdvanceRequest: - """Advance an in-flight login flow, optionally fulfilling an input-required step.""" - - flow_id: str - """Opaque flow id from begin.""" - - input: str | None = None - """Neutral input fulfilling a preceding input-required step (e.g. a GHEC host); ignored - otherwise. - """ - - @staticmethod - def from_dict(obj: Any) -> 'AuthLoginAdvanceRequest': - assert isinstance(obj, dict) - flow_id = from_str(obj.get("flowId")) - input = from_union([from_str, from_none], obj.get("input")) - return AuthLoginAdvanceRequest(flow_id, input) - - def to_dict(self) -> dict: - result: dict = {} - result["flowId"] = from_str(self.flow_id) - if self.input is not None: - result["input"] = from_union([from_str, from_none], self.input) - return result - -# Experimental: this type is part of an experimental API and may change or be removed. -class LoginProviderKind(Enum): - """The neutral provider kind. - - A provider a consumer may interactively sign in with. - - The provider kind to sign in with. - """ - ENTRA = "entra" - GITHUB_DOT_COM = "githubDotCom" - PROXIMA = "proxima" - -class AuthLoginStepKind(Enum): - AWAITING = "awaiting" - COMPLETED = "completed" - ERROR = "error" - INPUT_REQUIRED = "input-required" - NEEDS_INTERACTION = "needs-interaction" - OPEN_URL = "open-url" - -# Experimental: this type is part of an experimental API and may change or be removed. -class AuthLoginResultStatus(Enum): - """Terminal disposition of the login. - - Terminal disposition of a login persistence attempt. - """ - COMPLETED = "completed" - DECLINED = "declined" - NEEDS_PLAINTEXT_CONSENT = "needs-plaintext-consent" - -# Experimental: this type is part of an experimental API and may change or be removed. -@dataclass -class AuthLoginCancelRequest: - """Cancel an in-flight login flow.""" - - flow_id: str - """Opaque flow id from begin.""" - - @staticmethod - def from_dict(obj: Any) -> 'AuthLoginCancelRequest': - assert isinstance(obj, dict) - flow_id = from_str(obj.get("flowId")) - return AuthLoginCancelRequest(flow_id) - - def to_dict(self) -> dict: - result: dict = {} - result["flowId"] = from_str(self.flow_id) - return result - # Experimental: this type is part of an experimental API and may change or be removed. @dataclass class AuthValidationError: @@ -896,75 +788,6 @@ def to_dict(self) -> dict: result["githubMessage"] = from_union([from_str, from_none], self.github_message) return result -# Experimental: this type is part of an experimental API and may change or be removed. -@dataclass -class AuthStatusDto: - """The neutral authentication status summary. - - Neutral authentication status summary. - """ - account_count: int - """Number of signed-in accounts in the roster.""" - - is_authenticated: bool - """Whether the session has resolved authentication.""" - - active_host: str | None = None - """Active account host, if authenticated.""" - - active_login: str | None = None - """Active account login, if authenticated.""" - - copilot_plan: str | None = None - """Copilot plan tier of the active account, if known.""" - - @staticmethod - def from_dict(obj: Any) -> 'AuthStatusDto': - assert isinstance(obj, dict) - account_count = from_int(obj.get("accountCount")) - is_authenticated = from_bool(obj.get("isAuthenticated")) - active_host = from_union([from_str, from_none], obj.get("activeHost")) - active_login = from_union([from_str, from_none], obj.get("activeLogin")) - copilot_plan = from_union([from_str, from_none], obj.get("copilotPlan")) - return AuthStatusDto(account_count, is_authenticated, active_host, active_login, copilot_plan) - - def to_dict(self) -> dict: - result: dict = {} - result["accountCount"] = from_int(self.account_count) - result["isAuthenticated"] = from_bool(self.is_authenticated) - if self.active_host is not None: - result["activeHost"] = from_union([from_str, from_none], self.active_host) - if self.active_login is not None: - result["activeLogin"] = from_union([from_str, from_none], self.active_login) - if self.copilot_plan is not None: - result["copilotPlan"] = from_union([from_str, from_none], self.copilot_plan) - return result - -# Experimental: this type is part of an experimental API and may change or be removed. -@dataclass -class AuthWriteResult: - """Result of a non-interactive accounts mutation.""" - - ok: bool - """Whether the mutation was applied.""" - - more_users: bool | None = None - """For a logout, whether other signed-in accounts remain.""" - - @staticmethod - def from_dict(obj: Any) -> 'AuthWriteResult': - assert isinstance(obj, dict) - ok = from_bool(obj.get("ok")) - more_users = from_union([from_bool, from_none], obj.get("moreUsers")) - return AuthWriteResult(ok, more_users) - - def to_dict(self) -> dict: - result: dict = {} - result["ok"] = from_bool(self.ok) - if self.more_users is not None: - result["moreUsers"] = from_union([from_bool, from_none], self.more_users) - return result - # Experimental: this type is part of an experimental API and may change or be removed. @dataclass class AutopilotObjectiveCreditLimit: @@ -3571,384 +3394,1038 @@ class KindEnum(Enum): # Experimental: this type is part of an experimental API and may change or be removed. @dataclass -class FleetStartRequest: - """Parameters for starting fleet orchestration: an optional user prompt combined with the - fleet instructions, plus the send options forwarded to the resulting turn. - """ - attachments: list[Attachment] | None = None - """Optional attachments (files, directories, selections, blobs, GitHub references) to - include with the fleet request - """ - # Internal: this field is an internal SDK API and is not part of the public surface. - billable: bool | None = None - """If false, this request will not trigger a Premium Request Unit charge. User requests - default to billable. - """ - prompt: str | None = None - """Optional user prompt to combine with fleet instructions""" +class FactoryAbortRequest: + """Parameters for cooperatively aborting a factory body.""" - wait: bool | None = None - """If true, await completion of the agentic loop for this fleet request before returning. - Defaults to false. - """ + execution_token: str + """Opaque token identifying the execution attempt to abort.""" + + run_id: str + """Factory run identifier.""" + + session_id: str + """Target session identifier""" @staticmethod - def from_dict(obj: Any) -> 'FleetStartRequest': + def from_dict(obj: Any) -> 'FactoryAbortRequest': assert isinstance(obj, dict) - attachments = from_union([lambda x: from_list(_load_Attachment, x), from_none], obj.get("attachments")) - billable = from_union([from_bool, from_none], obj.get("billable")) - prompt = from_union([from_str, from_none], obj.get("prompt")) - wait = from_union([from_bool, from_none], obj.get("wait")) - return FleetStartRequest(attachments, billable, prompt, wait) + execution_token = from_str(obj.get("executionToken")) + run_id = from_str(obj.get("runId")) + session_id = from_str(obj.get("sessionId")) + return FactoryAbortRequest(execution_token, run_id, session_id) def to_dict(self) -> dict: result: dict = {} - if self.attachments is not None: - result["attachments"] = from_union([lambda x: from_list(lambda x: to_class(Attachment, x), x), from_none], self.attachments) - if self.billable is not None: - result["billable"] = from_union([from_bool, from_none], self.billable) - if self.prompt is not None: - result["prompt"] = from_union([from_str, from_none], self.prompt) - if self.wait is not None: - result["wait"] = from_union([from_bool, from_none], self.wait) + result["executionToken"] = from_str(self.execution_token) + result["runId"] = from_str(self.run_id) + result["sessionId"] = from_str(self.session_id) return result # Experimental: this type is part of an experimental API and may change or be removed. @dataclass -class FleetStartResult: - """Indicates whether fleet mode was successfully activated.""" - - started: bool - """Whether fleet mode was successfully activated""" - +class FactoryACKResult: + """Acknowledgement that a factory request was accepted.""" @staticmethod - def from_dict(obj: Any) -> 'FleetStartResult': + def from_dict(obj: Any) -> 'FactoryACKResult': assert isinstance(obj, dict) - started = from_bool(obj.get("started")) - return FleetStartResult(started) + return FactoryACKResult() def to_dict(self) -> dict: result: dict = {} - result["started"] = from_bool(self.started) return result # Experimental: this type is part of an experimental API and may change or be removed. @dataclass -class FolderTrustAddParams: - """Folder path to add to trusted folders.""" +class FactoryAgentOptions: + """Options for one factory-scoped subagent call. - path: str - """Folder path to mark as trusted""" + Subagent execution options. + """ + agent: str | None = None + """Optional built-in or custom agent name whose definition configures the subagent.""" + + context_tier: ContextTier | None = None + """Optional context tier override for the subagent.""" + + label: str | None = None + """Optional label distinguishing otherwise identical memoized agent calls.""" + + model: str | None = None + """Optional model identifier for the subagent.""" + + reasoning_effort: str | None = None + """Optional reasoning effort override for the subagent.""" + + schema: Any = None + """Optional JSON Schema for structured agent output.""" @staticmethod - def from_dict(obj: Any) -> 'FolderTrustAddParams': + def from_dict(obj: Any) -> 'FactoryAgentOptions': assert isinstance(obj, dict) - path = from_str(obj.get("path")) - return FolderTrustAddParams(path) + agent = from_union([from_str, from_none], obj.get("agent")) + context_tier = from_union([ContextTier, from_none], obj.get("contextTier")) + label = from_union([from_str, from_none], obj.get("label")) + model = from_union([from_str, from_none], obj.get("model")) + reasoning_effort = from_union([from_str, from_none], obj.get("reasoningEffort")) + schema = obj.get("schema") + return FactoryAgentOptions(agent, context_tier, label, model, reasoning_effort, schema) def to_dict(self) -> dict: result: dict = {} - result["path"] = from_str(self.path) + if self.agent is not None: + result["agent"] = from_union([from_str, from_none], self.agent) + if self.context_tier is not None: + result["contextTier"] = from_union([lambda x: to_enum(ContextTier, x), from_none], self.context_tier) + if self.label is not None: + result["label"] = from_union([from_str, from_none], self.label) + if self.model is not None: + result["model"] = from_union([from_str, from_none], self.model) + if self.reasoning_effort is not None: + result["reasoningEffort"] = from_union([from_str, from_none], self.reasoning_effort) + if self.schema is not None: + result["schema"] = self.schema return result # Experimental: this type is part of an experimental API and may change or be removed. @dataclass -class FolderTrustCheckParams: - """Folder path to check for trust.""" +class FactoryAgentResult: + """Result of one factory-scoped subagent call.""" - path: str - """Folder path to check""" + result: Any = None + """Agent result, omitted when the agent produced no result.""" @staticmethod - def from_dict(obj: Any) -> 'FolderTrustCheckParams': + def from_dict(obj: Any) -> 'FactoryAgentResult': assert isinstance(obj, dict) - path = from_str(obj.get("path")) - return FolderTrustCheckParams(path) + result = obj.get("result") + return FactoryAgentResult(result) def to_dict(self) -> dict: result: dict = {} - result["path"] = from_str(self.path) + if self.result is not None: + result["result"] = self.result return result # Experimental: this type is part of an experimental API and may change or be removed. @dataclass -class FolderTrustCheckResult: - """Folder trust check result.""" +class FactoryCancelRequest: + """Parameters for cancelling a factory run.""" - trusted: bool - """Whether the folder is trusted""" + run_id: str + """Factory run identifier.""" @staticmethod - def from_dict(obj: Any) -> 'FolderTrustCheckResult': + def from_dict(obj: Any) -> 'FactoryCancelRequest': assert isinstance(obj, dict) - trusted = from_bool(obj.get("trusted")) - return FolderTrustCheckResult(trusted) + run_id = from_str(obj.get("runId")) + return FactoryCancelRequest(run_id) def to_dict(self) -> dict: result: dict = {} - result["trusted"] = from_bool(self.trusted) + result["runId"] = from_str(self.run_id) return result -class GhCLIAuthInfoType(Enum): - GH_CLI = "gh-cli" - # Experimental: this type is part of an experimental API and may change or be removed. @dataclass -class GitHubTelemetryClientInfo: - """Client environment metadata describing the process that produced a telemetry event. - - Client environment metadata. - """ - cli_version: str - """Copilot CLI version string.""" - - node_version: str - """Node.js runtime version string.""" +class FactoryCurrentPhase: + """Current factory phase identity.""" - os_arch: str - """Operating system architecture (e.g. arm64, x64).""" - - os_platform: str - """Operating system platform (e.g. darwin, linux, win32).""" + id: str + """Current phase identifier.""" - os_version: str - """Operating system version string.""" + ordinal: int | None = None + """Zero-based declared phase ordinal, or null for an undeclared phase.""" - client_name: str | None = None - """Name of the client application.""" + @staticmethod + def from_dict(obj: Any) -> 'FactoryCurrentPhase': + assert isinstance(obj, dict) + id = from_str(obj.get("id")) + ordinal = from_union([from_none, from_int], obj.get("ordinal")) + return FactoryCurrentPhase(id, ordinal) - client_type: str | None = None - """Type of client.""" + def to_dict(self) -> dict: + result: dict = {} + result["id"] = from_str(self.id) + result["ordinal"] = from_union([from_none, from_int], self.ordinal) + return result - copilot_plan: str | None = None - """Copilot subscription plan, when known.""" +# Experimental: this type is part of an experimental API and may change or be removed. +@dataclass +class FactoryDeclaredLimits: + """Declared or approved factory resource ceilings. - cpu_count: int | None = None - """Number of logical CPU cores on the host.""" + Resource ceilings declared by the factory. + """ + max_ai_credits: float | None = None + """Maximum AI credits consumed by subagents and descendants.""" - cpu_model: str | None = None - """Distinct CPU model names for the host, comma-separated.""" + max_concurrent_subagents: int | None = None + """Maximum concurrently active subagents.""" - dev_device_id: str | None = None - """Stable machine identifier for the device.""" + max_total_subagents: int | None = None + """Maximum total subagents spawned by the run.""" - is_staff: bool | None = None - """Whether the user is a GitHub/Microsoft staff member.""" + timeout_seconds: float | None = None + """Maximum accumulated active execution time in seconds.""" @staticmethod - def from_dict(obj: Any) -> 'GitHubTelemetryClientInfo': + def from_dict(obj: Any) -> 'FactoryDeclaredLimits': assert isinstance(obj, dict) - cli_version = from_str(obj.get("cli_version")) - node_version = from_str(obj.get("node_version")) - os_arch = from_str(obj.get("os_arch")) - os_platform = from_str(obj.get("os_platform")) - os_version = from_str(obj.get("os_version")) - client_name = from_union([from_str, from_none], obj.get("client_name")) - client_type = from_union([from_str, from_none], obj.get("client_type")) - copilot_plan = from_union([from_str, from_none], obj.get("copilot_plan")) - cpu_count = from_union([from_int, from_none], obj.get("cpu_count")) - cpu_model = from_union([from_str, from_none], obj.get("cpu_model")) - dev_device_id = from_union([from_str, from_none], obj.get("dev_device_id")) - is_staff = from_union([from_bool, from_none], obj.get("is_staff")) - return GitHubTelemetryClientInfo(cli_version, node_version, os_arch, os_platform, os_version, client_name, client_type, copilot_plan, cpu_count, cpu_model, dev_device_id, is_staff) + max_ai_credits = from_union([from_float, from_none], obj.get("maxAiCredits")) + max_concurrent_subagents = from_union([from_int, from_none], obj.get("maxConcurrentSubagents")) + max_total_subagents = from_union([from_int, from_none], obj.get("maxTotalSubagents")) + timeout_seconds = from_union([from_float, from_none], obj.get("timeoutSeconds")) + return FactoryDeclaredLimits(max_ai_credits, max_concurrent_subagents, max_total_subagents, timeout_seconds) def to_dict(self) -> dict: result: dict = {} - result["cli_version"] = from_str(self.cli_version) - result["node_version"] = from_str(self.node_version) - result["os_arch"] = from_str(self.os_arch) - result["os_platform"] = from_str(self.os_platform) - result["os_version"] = from_str(self.os_version) - if self.client_name is not None: - result["client_name"] = from_union([from_str, from_none], self.client_name) - if self.client_type is not None: - result["client_type"] = from_union([from_str, from_none], self.client_type) - if self.copilot_plan is not None: - result["copilot_plan"] = from_union([from_str, from_none], self.copilot_plan) - if self.cpu_count is not None: - result["cpu_count"] = from_union([from_int, from_none], self.cpu_count) - if self.cpu_model is not None: - result["cpu_model"] = from_union([from_str, from_none], self.cpu_model) - if self.dev_device_id is not None: - result["dev_device_id"] = from_union([from_str, from_none], self.dev_device_id) - if self.is_staff is not None: - result["is_staff"] = from_union([from_bool, from_none], self.is_staff) + if self.max_ai_credits is not None: + result["maxAiCredits"] = from_union([to_float, from_none], self.max_ai_credits) + if self.max_concurrent_subagents is not None: + result["maxConcurrentSubagents"] = from_union([from_int, from_none], self.max_concurrent_subagents) + if self.max_total_subagents is not None: + result["maxTotalSubagents"] = from_union([from_int, from_none], self.max_total_subagents) + if self.timeout_seconds is not None: + result["timeoutSeconds"] = from_union([to_float, from_none], self.timeout_seconds) return result -class GitHubTokenAcquireReason(Enum): - """Why the runtime is requesting a GitHub credential.""" +# Experimental: this type is part of an experimental API and may change or be removed. +class DurableOperation(Enum): + """Execution-critical factory storage operation. - INITIAL = "initial" - REFRESH = "refresh" + Execution-critical durable operation that failed. -class GitHubTokenAcquireResultKind(Enum): - CANCELLED = "cancelled" - TOKEN = "token" + Execution-critical workflow storage operation. + """ + ADD_ELAPSED = "addElapsed" + CHARGE_CREDIT = "chargeCredit" + CREATE_RUN = "createRun" + FINISH_RUN = "finishRun" + JOURNAL_GET = "journalGet" + JOURNAL_PUT = "journalPut" + MARK_RUN_STARTED = "markRunStarted" + RECONCILE_CREDIT_TOTAL = "reconcileCreditTotal" + REFRESH_LEASE = "refreshLease" + RELEASE_AGENT = "releaseAgent" + RESERVE_AGENT = "reserveAgent" # Experimental: this type is part of an experimental API and may change or be removed. @dataclass -class HandlePendingToolCallResult: - """Indicates whether the external tool call result was handled successfully.""" +class FactoryExecuteRequest: + """Parameters sent to the owning extension to execute a factory closure.""" - success: bool - """Whether the tool call result was handled successfully""" + args: Any + """Factory input value.""" + + execution_token: str + """Opaque token identifying this factory execution attempt.""" + + name: str + """Registered factory name.""" + + run_id: str + """Factory run identifier.""" + + session_id: str + """Target session identifier""" @staticmethod - def from_dict(obj: Any) -> 'HandlePendingToolCallResult': + def from_dict(obj: Any) -> 'FactoryExecuteRequest': assert isinstance(obj, dict) - success = from_bool(obj.get("success")) - return HandlePendingToolCallResult(success) + args = obj.get("args") + execution_token = from_str(obj.get("executionToken")) + name = from_str(obj.get("name")) + run_id = from_str(obj.get("runId")) + session_id = from_str(obj.get("sessionId")) + return FactoryExecuteRequest(args, execution_token, name, run_id, session_id) def to_dict(self) -> dict: result: dict = {} - result["success"] = from_bool(self.success) + result["args"] = self.args + result["executionToken"] = from_str(self.execution_token) + result["name"] = from_str(self.name) + result["runId"] = from_str(self.run_id) + result["sessionId"] = from_str(self.session_id) return result # Experimental: this type is part of an experimental API and may change or be removed. @dataclass -class HistoryAbortManualCompactionResult: - """Indicates whether an in-progress manual compaction was aborted.""" +class FactoryExecuteResult: + """Result returned by an extension factory closure.""" - aborted: bool - """Whether an in-progress manual compaction was aborted. False when no manual compaction was - running, when its abort controller was already aborted, or when the session is remote. - """ + result: Any = None + """Factory result value.""" @staticmethod - def from_dict(obj: Any) -> 'HistoryAbortManualCompactionResult': + def from_dict(obj: Any) -> 'FactoryExecuteResult': assert isinstance(obj, dict) - aborted = from_bool(obj.get("aborted")) - return HistoryAbortManualCompactionResult(aborted) + result = obj.get("result") + return FactoryExecuteResult(result) def to_dict(self) -> dict: result: dict = {} - result["aborted"] = from_bool(self.aborted) + if self.result is not None: + result["result"] = self.result return result # Experimental: this type is part of an experimental API and may change or be removed. @dataclass -class HistoryCancelBackgroundCompactionResult: - """Indicates whether an in-progress background compaction was cancelled.""" +class FactoryGetRunProgressRequest: + """Parameters for paging factory progress.""" - cancelled: bool - """Whether an in-progress background compaction was cancelled. False when no compaction was - running, when the session is remote, or when the underlying processor was unavailable. - """ + run_id: str + """Factory run identifier.""" + + after_seq: int | None = None + """Exclusive forward cursor.""" + + before_seq: int | None = None + """Exclusive backward cursor.""" + + limit: int | None = None + """Maximum records to return. Defaults to 200 and is capped at 500.""" + + phase_id: str | None = None + """Optional phase identifier used to scope records and cursors.""" @staticmethod - def from_dict(obj: Any) -> 'HistoryCancelBackgroundCompactionResult': + def from_dict(obj: Any) -> 'FactoryGetRunProgressRequest': assert isinstance(obj, dict) - cancelled = from_bool(obj.get("cancelled")) - return HistoryCancelBackgroundCompactionResult(cancelled) + run_id = from_str(obj.get("runId")) + after_seq = from_union([from_int, from_none], obj.get("afterSeq")) + before_seq = from_union([from_int, from_none], obj.get("beforeSeq")) + limit = from_union([from_int, from_none], obj.get("limit")) + phase_id = from_union([from_str, from_none], obj.get("phaseId")) + return FactoryGetRunProgressRequest(run_id, after_seq, before_seq, limit, phase_id) def to_dict(self) -> dict: result: dict = {} - result["cancelled"] = from_bool(self.cancelled) + result["runId"] = from_str(self.run_id) + if self.after_seq is not None: + result["afterSeq"] = from_union([from_int, from_none], self.after_seq) + if self.before_seq is not None: + result["beforeSeq"] = from_union([from_int, from_none], self.before_seq) + if self.limit is not None: + result["limit"] = from_union([from_int, from_none], self.limit) + if self.phase_id is not None: + result["phaseId"] = from_union([from_str, from_none], self.phase_id) return result # Experimental: this type is part of an experimental API and may change or be removed. @dataclass -class HistoryClearContextRequest: - """Parameters for clearing the conversation and seeding the window that replaces it.""" +class FactoryGetRunRequest: + """Parameters for retrieving a factory run.""" - prompt: str - """First user message of the fresh context window. Required: a cleared window holding only - system and developer messages is not a conversation a model can answer, so every clear - seeds the window it creates. Delivered by the enclosing turn driver once the agentic loop - exits, which is why the call must be made from inside a tool handler. - """ + run_id: str + """Factory run identifier.""" @staticmethod - def from_dict(obj: Any) -> 'HistoryClearContextRequest': + def from_dict(obj: Any) -> 'FactoryGetRunRequest': assert isinstance(obj, dict) - prompt = from_str(obj.get("prompt")) - return HistoryClearContextRequest(prompt) + run_id = from_str(obj.get("runId")) + return FactoryGetRunRequest(run_id) def to_dict(self) -> dict: result: dict = {} - result["prompt"] = from_str(self.prompt) + result["runId"] = from_str(self.run_id) return result # Experimental: this type is part of an experimental API and may change or be removed. @dataclass -class HistoryClearContextResult: - """What a successful clear removed. A clear that could not be applied rejects instead of - reporting a count. - """ - messages_cleared: int - """Number of non-system, non-developer messages that were removed from the conversation. - Zero only when the window already held no conversation. - """ +class FactoryJournalGetRequest: + """Parameters for reading a factory journal entry.""" + + execution_token: str + """Opaque token identifying the current factory execution attempt.""" + + key: str + """Namespaced journal key.""" + + run_id: str + """Factory run identifier.""" @staticmethod - def from_dict(obj: Any) -> 'HistoryClearContextResult': + def from_dict(obj: Any) -> 'FactoryJournalGetRequest': assert isinstance(obj, dict) - messages_cleared = from_int(obj.get("messagesCleared")) - return HistoryClearContextResult(messages_cleared) + execution_token = from_str(obj.get("executionToken")) + key = from_str(obj.get("key")) + run_id = from_str(obj.get("runId")) + return FactoryJournalGetRequest(execution_token, key, run_id) def to_dict(self) -> dict: result: dict = {} - result["messagesCleared"] = from_int(self.messages_cleared) + result["executionToken"] = from_str(self.execution_token) + result["key"] = from_str(self.key) + result["runId"] = from_str(self.run_id) return result # Experimental: this type is part of an experimental API and may change or be removed. @dataclass -class HistoryCompactContextWindow: - """Post-compaction context window usage breakdown""" - - current_tokens: int - """Current total tokens in the context window (system + conversation + tool definitions)""" - - messages_length: int - """Current number of messages in the conversation""" - - token_limit: int - """Maximum token count for the model's context window""" - - conversation_tokens: int | None = None - """Token count from non-system messages (user, assistant, tool)""" +class FactoryJournalGetResult: + """Result of reading a factory journal entry.""" - system_tokens: int | None = None - """Token count from system message(s)""" + hit: bool + """Whether the journal contained the requested key.""" - tool_definitions_tokens: int | None = None - """Token count from tool definitions""" + result_json: Any = None + """Cached JSON result. The hit field distinguishes a cached JSON null from a miss.""" @staticmethod - def from_dict(obj: Any) -> 'HistoryCompactContextWindow': + def from_dict(obj: Any) -> 'FactoryJournalGetResult': assert isinstance(obj, dict) - current_tokens = from_int(obj.get("currentTokens")) - messages_length = from_int(obj.get("messagesLength")) - token_limit = from_int(obj.get("tokenLimit")) - conversation_tokens = from_union([from_int, from_none], obj.get("conversationTokens")) - system_tokens = from_union([from_int, from_none], obj.get("systemTokens")) - tool_definitions_tokens = from_union([from_int, from_none], obj.get("toolDefinitionsTokens")) - return HistoryCompactContextWindow(current_tokens, messages_length, token_limit, conversation_tokens, system_tokens, tool_definitions_tokens) + hit = from_bool(obj.get("hit")) + result_json = obj.get("resultJson") + return FactoryJournalGetResult(hit, result_json) def to_dict(self) -> dict: result: dict = {} - result["currentTokens"] = from_int(self.current_tokens) - result["messagesLength"] = from_int(self.messages_length) - result["tokenLimit"] = from_int(self.token_limit) - if self.conversation_tokens is not None: - result["conversationTokens"] = from_union([from_int, from_none], self.conversation_tokens) - if self.system_tokens is not None: - result["systemTokens"] = from_union([from_int, from_none], self.system_tokens) - if self.tool_definitions_tokens is not None: - result["toolDefinitionsTokens"] = from_union([from_int, from_none], self.tool_definitions_tokens) + result["hit"] = from_bool(self.hit) + if self.result_json is not None: + result["resultJson"] = self.result_json return result # Experimental: this type is part of an experimental API and may change or be removed. -class HistoryFileRestoreSkipReason(Enum): - """Reason a captured file was not restored. +@dataclass +class FactoryJournalPutRequest: + """Parameters for storing a factory journal entry.""" - Reason the file was not restored. - """ - SKIPPED_CAPTURE = "skipped-capture" - USER_MODIFIED = "user-modified" + execution_token: str + """Opaque token identifying the current factory execution attempt.""" -# Experimental: this type is part of an experimental API and may change or be removed. -@dataclass + key: str + """Namespaced journal key.""" + + result_json: Any + """JSON result to memoize.""" + + run_id: str + """Factory run identifier.""" + + @staticmethod + def from_dict(obj: Any) -> 'FactoryJournalPutRequest': + assert isinstance(obj, dict) + execution_token = from_str(obj.get("executionToken")) + key = from_str(obj.get("key")) + result_json = obj.get("resultJson") + run_id = from_str(obj.get("runId")) + return FactoryJournalPutRequest(execution_token, key, result_json, run_id) + + def to_dict(self) -> dict: + result: dict = {} + result["executionToken"] = from_str(self.execution_token) + result["key"] = from_str(self.key) + result["resultJson"] = self.result_json + result["runId"] = from_str(self.run_id) + return result + +# Experimental: this type is part of an experimental API and may change or be removed. +@dataclass +class FactoryListRunsRequest: + """Parameters for paging factory runs.""" + + after_seq: int | None = None + """Exclusive forward cursor.""" + + before_seq: int | None = None + """Exclusive backward cursor.""" + + limit: int | None = None + """Maximum terminal runs to return. Defaults to 200 and is capped at 500.""" + + @staticmethod + def from_dict(obj: Any) -> 'FactoryListRunsRequest': + assert isinstance(obj, dict) + after_seq = from_union([from_int, from_none], obj.get("afterSeq")) + before_seq = from_union([from_int, from_none], obj.get("beforeSeq")) + limit = from_union([from_int, from_none], obj.get("limit")) + return FactoryListRunsRequest(after_seq, before_seq, limit) + + def to_dict(self) -> dict: + result: dict = {} + if self.after_seq is not None: + result["afterSeq"] = from_union([from_int, from_none], self.after_seq) + if self.before_seq is not None: + result["beforeSeq"] = from_union([from_int, from_none], self.before_seq) + if self.limit is not None: + result["limit"] = from_union([from_int, from_none], self.limit) + return result + +# Experimental: this type is part of an experimental API and may change or be removed. +@dataclass +class FactoryRunConsumed: + """Durable resource consumption. + + Durable factory resource consumption. + """ + active_ms: int + """Accumulated active execution time in milliseconds.""" + + nano_aiu: int + """AI usage consumed by the run in nano-AIU.""" + + subagents: int + """Total subagents spawned by the run.""" + + @staticmethod + def from_dict(obj: Any) -> 'FactoryRunConsumed': + assert isinstance(obj, dict) + active_ms = from_int(obj.get("activeMs")) + nano_aiu = from_int(obj.get("nanoAiu")) + subagents = from_int(obj.get("subagents")) + return FactoryRunConsumed(active_ms, nano_aiu, subagents) + + def to_dict(self) -> dict: + result: dict = {} + result["activeMs"] = from_int(self.active_ms) + result["nanoAiu"] = from_int(self.nano_aiu) + result["subagents"] = from_int(self.subagents) + return result + +# Experimental: this type is part of an experimental API and may change or be removed. +class RunStatus(Enum): + """Current factory run status. + + Current or terminal state of a factory run. + + Current or terminal factory run status. + + Current workflow run status. + + Current or terminal state of a workflow run. + + Current or terminal workflow run status. + """ + CANCELLED = "cancelled" + COMPLETED = "completed" + ERROR = "error" + HALTED = "halted" + PAUSED = "paused" + PENDING = "pending" + RUNNING = "running" + +# Experimental: this type is part of an experimental API and may change or be removed. +class RunFailureKind(Enum): + """Resource ceiling that stopped the run. + + Cumulative resource ceiling that stopped a factory run. + + Cumulative resource ceiling that stopped a workflow run. + """ + MAX_AI_CREDITS = "maxAiCredits" + MAX_TOTAL_SUBAGENTS = "maxTotalSubagents" + TIMEOUT_SECONDS = "timeoutSeconds" + +class FactoryRunFailureType(Enum): + FACTORY_ACCOUNTING_INCOMPLETE = "factory_accounting_incomplete" + FACTORY_DURABLE_FAILURE = "factory_durable_failure" + FACTORY_LIMIT_REACHED = "factory_limit_reached" + FACTORY_PROVIDER_DISCONNECTED = "factory_provider_disconnected" + FACTORY_RESUME_DECLINED = "factory_resume_declined" + +class PauseInfoType(Enum): + CHECKPOINT = "checkpoint" + USER = "user" + +# Experimental: this type is part of an experimental API and may change or be removed. +class LogLineKind(Enum): + """Progress line kind. + + Kind of factory progress line. + + Progress record kind. + + Kind of workflow progress line. + """ + LOG = "log" + PHASE = "phase" + +# Experimental: this type is part of an experimental API and may change or be removed. +@dataclass +class FactoryPauseCheckpointRequest: + """Parameters for an owned durable pause checkpoint.""" + + execution_token: str + """Opaque token identifying the execution attempt that reached the checkpoint.""" + + key: str + """Stable author-defined checkpoint key.""" + + run_id: str + """Factory run identifier.""" + + @staticmethod + def from_dict(obj: Any) -> 'FactoryPauseCheckpointRequest': + assert isinstance(obj, dict) + execution_token = from_str(obj.get("executionToken")) + key = from_str(obj.get("key")) + run_id = from_str(obj.get("runId")) + return FactoryPauseCheckpointRequest(execution_token, key, run_id) + + def to_dict(self) -> dict: + result: dict = {} + result["executionToken"] = from_str(self.execution_token) + result["key"] = from_str(self.key) + result["runId"] = from_str(self.run_id) + return result + +# Experimental: this type is part of an experimental API and may change or be removed. +@dataclass +class FactoryPauseRequest: + """Parameters for pausing a running factory.""" + + run_id: str + """Factory run identifier.""" + + @staticmethod + def from_dict(obj: Any) -> 'FactoryPauseRequest': + assert isinstance(obj, dict) + run_id = from_str(obj.get("runId")) + return FactoryPauseRequest(run_id) + + def to_dict(self) -> dict: + result: dict = {} + result["runId"] = from_str(self.run_id) + return result + +# Experimental: this type is part of an experimental API and may change or be removed. +class PhaseStatus(Enum): + """Derived lifecycle state of the phase. + + Derived lifecycle state of a factory phase. + + Derived lifecycle state of a workflow phase. + """ + ACTIVE = "active" + COMPLETED = "completed" + PENDING = "pending" + SKIPPED = "skipped" + +# Experimental: this type is part of an experimental API and may change or be removed. +@dataclass +class FactoryRunLimits: + """Optional per-invocation resource ceiling overrides. + + Wire-only per-invocation factory resource ceiling overrides. + + Per-invocation resource ceiling overrides. + """ + max_ai_credits: float | None = None + """Maximum AI credits consumed by factory subagents and their descendants. The post-paid + ceiling is soft: parallel turns can settle beyond it before the run stops. + """ + max_concurrent_subagents: int | None = None + """Maximum number of factory subagents that may run concurrently.""" + + max_total_subagents: int | None = None + """Maximum total number of factory subagents that may be admitted.""" + + timeout_seconds: float | None = None + """Maximum accumulated active-execution time in seconds. Active execution includes the + entire extension body, subprocess waits, queued-agent waits, and sleeps; time between + resumed attempts is not counted. + """ + + @staticmethod + def from_dict(obj: Any) -> 'FactoryRunLimits': + assert isinstance(obj, dict) + max_ai_credits = from_union([from_float, from_none], obj.get("maxAiCredits")) + max_concurrent_subagents = from_union([from_none, from_int], obj.get("maxConcurrentSubagents")) + max_total_subagents = from_union([from_none, from_int], obj.get("maxTotalSubagents")) + timeout_seconds = from_union([from_none, from_float], obj.get("timeoutSeconds")) + return FactoryRunLimits(max_ai_credits, max_concurrent_subagents, max_total_subagents, timeout_seconds) + + def to_dict(self) -> dict: + result: dict = {} + if self.max_ai_credits is not None: + result["maxAiCredits"] = from_union([to_float, from_none], self.max_ai_credits) + if self.max_concurrent_subagents is not None: + result["maxConcurrentSubagents"] = from_union([from_none, from_int], self.max_concurrent_subagents) + if self.max_total_subagents is not None: + result["maxTotalSubagents"] = from_union([from_none, from_int], self.max_total_subagents) + if self.timeout_seconds is not None: + result["timeoutSeconds"] = from_union([from_none, to_float], self.timeout_seconds) + return result + +# Experimental: this type is part of an experimental API and may change or be removed. +@dataclass +class FleetStartRequest: + """Parameters for starting fleet orchestration: an optional user prompt combined with the + fleet instructions, plus the send options forwarded to the resulting turn. + """ + attachments: list[Attachment] | None = None + """Optional attachments (files, directories, selections, blobs, GitHub references) to + include with the fleet request + """ + # Internal: this field is an internal SDK API and is not part of the public surface. + billable: bool | None = None + """If false, this request will not trigger a Premium Request Unit charge. User requests + default to billable. + """ + prompt: str | None = None + """Optional user prompt to combine with fleet instructions""" + + wait: bool | None = None + """If true, await completion of the agentic loop for this fleet request before returning. + Defaults to false. + """ + + @staticmethod + def from_dict(obj: Any) -> 'FleetStartRequest': + assert isinstance(obj, dict) + attachments = from_union([lambda x: from_list(_load_Attachment, x), from_none], obj.get("attachments")) + billable = from_union([from_bool, from_none], obj.get("billable")) + prompt = from_union([from_str, from_none], obj.get("prompt")) + wait = from_union([from_bool, from_none], obj.get("wait")) + return FleetStartRequest(attachments, billable, prompt, wait) + + def to_dict(self) -> dict: + result: dict = {} + if self.attachments is not None: + result["attachments"] = from_union([lambda x: from_list(lambda x: to_class(Attachment, x), x), from_none], self.attachments) + if self.billable is not None: + result["billable"] = from_union([from_bool, from_none], self.billable) + if self.prompt is not None: + result["prompt"] = from_union([from_str, from_none], self.prompt) + if self.wait is not None: + result["wait"] = from_union([from_bool, from_none], self.wait) + return result + +# Experimental: this type is part of an experimental API and may change or be removed. +@dataclass +class FleetStartResult: + """Indicates whether fleet mode was successfully activated.""" + + started: bool + """Whether fleet mode was successfully activated""" + + @staticmethod + def from_dict(obj: Any) -> 'FleetStartResult': + assert isinstance(obj, dict) + started = from_bool(obj.get("started")) + return FleetStartResult(started) + + def to_dict(self) -> dict: + result: dict = {} + result["started"] = from_bool(self.started) + return result + +# Experimental: this type is part of an experimental API and may change or be removed. +@dataclass +class FolderTrustAddParams: + """Folder path to add to trusted folders.""" + + path: str + """Folder path to mark as trusted""" + + @staticmethod + def from_dict(obj: Any) -> 'FolderTrustAddParams': + assert isinstance(obj, dict) + path = from_str(obj.get("path")) + return FolderTrustAddParams(path) + + def to_dict(self) -> dict: + result: dict = {} + result["path"] = from_str(self.path) + return result + +# Experimental: this type is part of an experimental API and may change or be removed. +@dataclass +class FolderTrustCheckParams: + """Folder path to check for trust.""" + + path: str + """Folder path to check""" + + @staticmethod + def from_dict(obj: Any) -> 'FolderTrustCheckParams': + assert isinstance(obj, dict) + path = from_str(obj.get("path")) + return FolderTrustCheckParams(path) + + def to_dict(self) -> dict: + result: dict = {} + result["path"] = from_str(self.path) + return result + +# Experimental: this type is part of an experimental API and may change or be removed. +@dataclass +class FolderTrustCheckResult: + """Folder trust check result.""" + + trusted: bool + """Whether the folder is trusted""" + + @staticmethod + def from_dict(obj: Any) -> 'FolderTrustCheckResult': + assert isinstance(obj, dict) + trusted = from_bool(obj.get("trusted")) + return FolderTrustCheckResult(trusted) + + def to_dict(self) -> dict: + result: dict = {} + result["trusted"] = from_bool(self.trusted) + return result + +class GhCLIAuthInfoType(Enum): + GH_CLI = "gh-cli" + +# Experimental: this type is part of an experimental API and may change or be removed. +@dataclass +class GitHubTelemetryClientInfo: + """Client environment metadata describing the process that produced a telemetry event. + + Client environment metadata. + """ + cli_version: str + """Copilot CLI version string.""" + + node_version: str + """Node.js runtime version string.""" + + os_arch: str + """Operating system architecture (e.g. arm64, x64).""" + + os_platform: str + """Operating system platform (e.g. darwin, linux, win32).""" + + os_version: str + """Operating system version string.""" + + client_name: str | None = None + """Name of the client application.""" + + client_type: str | None = None + """Type of client.""" + + copilot_plan: str | None = None + """Copilot subscription plan, when known.""" + + cpu_count: int | None = None + """Number of logical CPU cores on the host.""" + + cpu_model: str | None = None + """Distinct CPU model names for the host, comma-separated.""" + + dev_device_id: str | None = None + """Stable machine identifier for the device.""" + + is_staff: bool | None = None + """Whether the user is a GitHub/Microsoft staff member.""" + + @staticmethod + def from_dict(obj: Any) -> 'GitHubTelemetryClientInfo': + assert isinstance(obj, dict) + cli_version = from_str(obj.get("cli_version")) + node_version = from_str(obj.get("node_version")) + os_arch = from_str(obj.get("os_arch")) + os_platform = from_str(obj.get("os_platform")) + os_version = from_str(obj.get("os_version")) + client_name = from_union([from_str, from_none], obj.get("client_name")) + client_type = from_union([from_str, from_none], obj.get("client_type")) + copilot_plan = from_union([from_str, from_none], obj.get("copilot_plan")) + cpu_count = from_union([from_int, from_none], obj.get("cpu_count")) + cpu_model = from_union([from_str, from_none], obj.get("cpu_model")) + dev_device_id = from_union([from_str, from_none], obj.get("dev_device_id")) + is_staff = from_union([from_bool, from_none], obj.get("is_staff")) + return GitHubTelemetryClientInfo(cli_version, node_version, os_arch, os_platform, os_version, client_name, client_type, copilot_plan, cpu_count, cpu_model, dev_device_id, is_staff) + + def to_dict(self) -> dict: + result: dict = {} + result["cli_version"] = from_str(self.cli_version) + result["node_version"] = from_str(self.node_version) + result["os_arch"] = from_str(self.os_arch) + result["os_platform"] = from_str(self.os_platform) + result["os_version"] = from_str(self.os_version) + if self.client_name is not None: + result["client_name"] = from_union([from_str, from_none], self.client_name) + if self.client_type is not None: + result["client_type"] = from_union([from_str, from_none], self.client_type) + if self.copilot_plan is not None: + result["copilot_plan"] = from_union([from_str, from_none], self.copilot_plan) + if self.cpu_count is not None: + result["cpu_count"] = from_union([from_int, from_none], self.cpu_count) + if self.cpu_model is not None: + result["cpu_model"] = from_union([from_str, from_none], self.cpu_model) + if self.dev_device_id is not None: + result["dev_device_id"] = from_union([from_str, from_none], self.dev_device_id) + if self.is_staff is not None: + result["is_staff"] = from_union([from_bool, from_none], self.is_staff) + return result + +class GitHubTokenAcquireReason(Enum): + """Why the runtime is requesting a GitHub credential.""" + + INITIAL = "initial" + REFRESH = "refresh" + +class GitHubTokenAcquireResultKind(Enum): + CANCELLED = "cancelled" + TOKEN = "token" + +# Experimental: this type is part of an experimental API and may change or be removed. +@dataclass +class HandlePendingToolCallResult: + """Indicates whether the external tool call result was handled successfully.""" + + success: bool + """Whether the tool call result was handled successfully""" + + @staticmethod + def from_dict(obj: Any) -> 'HandlePendingToolCallResult': + assert isinstance(obj, dict) + success = from_bool(obj.get("success")) + return HandlePendingToolCallResult(success) + + def to_dict(self) -> dict: + result: dict = {} + result["success"] = from_bool(self.success) + return result + +# Experimental: this type is part of an experimental API and may change or be removed. +@dataclass +class HistoryAbortManualCompactionResult: + """Indicates whether an in-progress manual compaction was aborted.""" + + aborted: bool + """Whether an in-progress manual compaction was aborted. False when no manual compaction was + running, when its abort controller was already aborted, or when the session is remote. + """ + + @staticmethod + def from_dict(obj: Any) -> 'HistoryAbortManualCompactionResult': + assert isinstance(obj, dict) + aborted = from_bool(obj.get("aborted")) + return HistoryAbortManualCompactionResult(aborted) + + def to_dict(self) -> dict: + result: dict = {} + result["aborted"] = from_bool(self.aborted) + return result + +# Experimental: this type is part of an experimental API and may change or be removed. +@dataclass +class HistoryCancelBackgroundCompactionResult: + """Indicates whether an in-progress background compaction was cancelled.""" + + cancelled: bool + """Whether an in-progress background compaction was cancelled. False when no compaction was + running, when the session is remote, or when the underlying processor was unavailable. + """ + + @staticmethod + def from_dict(obj: Any) -> 'HistoryCancelBackgroundCompactionResult': + assert isinstance(obj, dict) + cancelled = from_bool(obj.get("cancelled")) + return HistoryCancelBackgroundCompactionResult(cancelled) + + def to_dict(self) -> dict: + result: dict = {} + result["cancelled"] = from_bool(self.cancelled) + return result + +# Experimental: this type is part of an experimental API and may change or be removed. +@dataclass +class HistoryClearContextRequest: + """Parameters for clearing the conversation and seeding the window that replaces it.""" + + prompt: str + """First user message of the fresh context window. Required: a cleared window holding only + system and developer messages is not a conversation a model can answer, so every clear + seeds the window it creates. Delivered by the enclosing turn driver once the agentic loop + exits, which is why the call must be made from inside a tool handler. + """ + + @staticmethod + def from_dict(obj: Any) -> 'HistoryClearContextRequest': + assert isinstance(obj, dict) + prompt = from_str(obj.get("prompt")) + return HistoryClearContextRequest(prompt) + + def to_dict(self) -> dict: + result: dict = {} + result["prompt"] = from_str(self.prompt) + return result + +# Experimental: this type is part of an experimental API and may change or be removed. +@dataclass +class HistoryClearContextResult: + """What a successful clear removed. A clear that could not be applied rejects instead of + reporting a count. + """ + messages_cleared: int + """Number of non-system, non-developer messages that were removed from the conversation. + Zero only when the window already held no conversation. + """ + + @staticmethod + def from_dict(obj: Any) -> 'HistoryClearContextResult': + assert isinstance(obj, dict) + messages_cleared = from_int(obj.get("messagesCleared")) + return HistoryClearContextResult(messages_cleared) + + def to_dict(self) -> dict: + result: dict = {} + result["messagesCleared"] = from_int(self.messages_cleared) + return result + +# Experimental: this type is part of an experimental API and may change or be removed. +@dataclass +class HistoryCompactContextWindow: + """Post-compaction context window usage breakdown""" + + current_tokens: int + """Current total tokens in the context window (system + conversation + tool definitions)""" + + messages_length: int + """Current number of messages in the conversation""" + + token_limit: int + """Maximum token count for the model's context window""" + + conversation_tokens: int | None = None + """Token count from non-system messages (user, assistant, tool)""" + + system_tokens: int | None = None + """Token count from system message(s)""" + + tool_definitions_tokens: int | None = None + """Token count from tool definitions""" + + @staticmethod + def from_dict(obj: Any) -> 'HistoryCompactContextWindow': + assert isinstance(obj, dict) + current_tokens = from_int(obj.get("currentTokens")) + messages_length = from_int(obj.get("messagesLength")) + token_limit = from_int(obj.get("tokenLimit")) + conversation_tokens = from_union([from_int, from_none], obj.get("conversationTokens")) + system_tokens = from_union([from_int, from_none], obj.get("systemTokens")) + tool_definitions_tokens = from_union([from_int, from_none], obj.get("toolDefinitionsTokens")) + return HistoryCompactContextWindow(current_tokens, messages_length, token_limit, conversation_tokens, system_tokens, tool_definitions_tokens) + + def to_dict(self) -> dict: + result: dict = {} + result["currentTokens"] = from_int(self.current_tokens) + result["messagesLength"] = from_int(self.messages_length) + result["tokenLimit"] = from_int(self.token_limit) + if self.conversation_tokens is not None: + result["conversationTokens"] = from_union([from_int, from_none], self.conversation_tokens) + if self.system_tokens is not None: + result["systemTokens"] = from_union([from_int, from_none], self.system_tokens) + if self.tool_definitions_tokens is not None: + result["toolDefinitionsTokens"] = from_union([from_int, from_none], self.tool_definitions_tokens) + return result + +# Experimental: this type is part of an experimental API and may change or be removed. +class HistoryFileRestoreSkipReason(Enum): + """Reason a captured file was not restored. + + Reason the file was not restored. + """ + SKIPPED_CAPTURE = "skipped-capture" + USER_MODIFIED = "user-modified" + +# Experimental: this type is part of an experimental API and may change or be removed. +@dataclass class HistoryRewindPoint: """A root user turn that the session can rewind to.""" @@ -7663,11 +8140,11 @@ class ApprovalKind(Enum): EXTENSION_ENV_ACCESS = "extension-env-access" EXTENSION_MANAGEMENT = "extension-management" EXTENSION_PERMISSION_ACCESS = "extension-permission-access" + FACTORY = "factory" MCP = "mcp" MCP_SAMPLING = "mcp-sampling" MEMORY = "memory" READ = "read" - WORKFLOW = "workflow" WRITE = "write" class PermissionDecisionKind(Enum): @@ -7705,15 +8182,15 @@ class PermissionDecisionApproveForLocationApprovalExtensionManagementKind(Enum): class PermissionDecisionApproveForLocationApprovalExtensionPermissionAccessKind(Enum): EXTENSION_PERMISSION_ACCESS = "extension-permission-access" +class PermissionDecisionApproveForLocationApprovalFactoryKind(Enum): + FACTORY = "factory" + class PermissionDecisionApproveForLocationApprovalMCPSamplingKind(Enum): MCP_SAMPLING = "mcp-sampling" class PermissionDecisionApproveForLocationApprovalReadKind(Enum): READ = "read" -class PermissionDecisionApproveForLocationApprovalWorkflowKind(Enum): - WORKFLOW = "workflow" - class PermissionDecisionApproveForLocationApprovalWriteKind(Enum): WRITE = "write" @@ -15370,24 +15847,6 @@ def to_dict(self) -> dict: result["timeoutSeconds"] = from_union([to_float, from_none], self.timeout_seconds) return result -# Experimental: this type is part of an experimental API and may change or be removed. -class WorkflowDurableOperation(Enum): - """Execution-critical workflow storage operation. - - Execution-critical durable operation that failed. - """ - ADD_ELAPSED = "addElapsed" - CHARGE_CREDIT = "chargeCredit" - CREATE_RUN = "createRun" - FINISH_RUN = "finishRun" - JOURNAL_GET = "journalGet" - JOURNAL_PUT = "journalPut" - MARK_RUN_STARTED = "markRunStarted" - RECONCILE_CREDIT_TOTAL = "reconcileCreditTotal" - REFRESH_LEASE = "refreshLease" - RELEASE_AGENT = "releaseAgent" - RESERVE_AGENT = "reserveAgent" - # Experimental: this type is part of an experimental API and may change or be removed. @dataclass class WorkflowExecuteRequest: @@ -15660,32 +16119,6 @@ def to_dict(self) -> dict: result["subagents"] = from_int(self.subagents) return result -# Experimental: this type is part of an experimental API and may change or be removed. -class WorkflowRunStatus(Enum): - """Current workflow run status. - - Current or terminal state of a workflow run. - - Current or terminal workflow run status. - """ - CANCELLED = "cancelled" - COMPLETED = "completed" - ERROR = "error" - HALTED = "halted" - PAUSED = "paused" - PENDING = "pending" - RUNNING = "running" - -# Experimental: this type is part of an experimental API and may change or be removed. -class WorkflowRunFailureKind(Enum): - """Resource ceiling that stopped the run. - - Cumulative resource ceiling that stopped a workflow run. - """ - MAX_AI_CREDITS = "maxAiCredits" - MAX_TOTAL_SUBAGENTS = "maxTotalSubagents" - TIMEOUT_SECONDS = "timeoutSeconds" - class WorkflowRunFailureType(Enum): WORKFLOW_ACCOUNTING_INCOMPLETE = "workflow_accounting_incomplete" WORKFLOW_DURABLE_FAILURE = "workflow_durable_failure" @@ -15693,21 +16126,6 @@ class WorkflowRunFailureType(Enum): WORKFLOW_PROVIDER_DISCONNECTED = "workflow_provider_disconnected" WORKFLOW_RESUME_DECLINED = "workflow_resume_declined" -class PauseInfoType(Enum): - CHECKPOINT = "checkpoint" - USER = "user" - -# Experimental: this type is part of an experimental API and may change or be removed. -class WorkflowLogLineKind(Enum): - """Progress line kind. - - Kind of workflow progress line. - - Progress record kind. - """ - LOG = "log" - PHASE = "phase" - # Experimental: this type is part of an experimental API and may change or be removed. @dataclass class WorkflowPauseCheckpointRequest: @@ -15756,17 +16174,6 @@ def to_dict(self) -> dict: result["runId"] = from_str(self.run_id) return result -# Experimental: this type is part of an experimental API and may change or be removed. -class WorkflowPhaseStatus(Enum): - """Derived lifecycle state of the phase. - - Derived lifecycle state of a workflow phase. - """ - ACTIVE = "active" - COMPLETED = "completed" - PENDING = "pending" - SKIPPED = "skipped" - # Experimental: this type is part of an experimental API and may change or be removed. @dataclass class WorkflowRunLimits: @@ -16388,222 +16795,6 @@ def to_dict(self) -> dict: result["quotaSnapshots"] = from_dict(lambda x: to_class(AccountQuotaSnapshot, x), self.quota_snapshots) return result -# Experimental: this type is part of an experimental API and may change or be removed. -@dataclass -class AccountStatus: - """One signed-in account in the roster forest. - - The active account, or absent when not logged in. - """ - active: bool - """Whether this is the active account.""" - - host: str - """Authentication host URL.""" - - kind: AccountKind - """The provider kind of this account.""" - - login: str - """Authenticated login/username.""" - - selection_id: str - """Opaque selection id used to switch to, or log out, this account.""" - - derived_from: str | None = None - """Opaque id of the account this one was derived from (e.g. an EMU account's base Entra - identity); absent for a root account. Matches the base identity account's selectionId, - forming the derivation edge. - """ - - @staticmethod - def from_dict(obj: Any) -> 'AccountStatus': - assert isinstance(obj, dict) - active = from_bool(obj.get("active")) - host = from_str(obj.get("host")) - kind = AccountKind(obj.get("kind")) - login = from_str(obj.get("login")) - selection_id = from_str(obj.get("selectionId")) - derived_from = from_union([from_str, from_none], obj.get("derivedFrom")) - return AccountStatus(active, host, kind, login, selection_id, derived_from) - - def to_dict(self) -> dict: - result: dict = {} - result["active"] = from_bool(self.active) - result["host"] = from_str(self.host) - result["kind"] = to_enum(AccountKind, self.kind) - result["login"] = from_str(self.login) - result["selectionId"] = from_str(self.selection_id) - if self.derived_from is not None: - result["derivedFrom"] = from_union([from_str, from_none], self.derived_from) - return result - -@dataclass -class ItemElement: - """One signed-in account in the roster forest. - - The active account, or absent when not logged in. - - A provider offered for interactive login. - """ - kind: AccountKind - """The provider kind of this account. - - The neutral provider kind. - """ - active: bool | None = None - """Whether this is the active account.""" - - derived_from: str | None = None - """Opaque id of the account this one was derived from (e.g. an EMU account's base Entra - identity); absent for a root account. Matches the base identity account's selectionId, - forming the derivation edge. - """ - host: str | None = None - """Authentication host URL.""" - - login: str | None = None - """Authenticated login/username.""" - - selection_id: str | None = None - """Opaque selection id used to switch to, or log out, this account.""" - - available: bool | None = None - """Whether this provider is currently available to sign in with.""" - - label: str | None = None - """Human-readable menu label, owned by the runtime so every consumer renders identical text.""" - - @staticmethod - def from_dict(obj: Any) -> 'ItemElement': - assert isinstance(obj, dict) - kind = AccountKind(obj.get("kind")) - active = from_union([from_bool, from_none], obj.get("active")) - derived_from = from_union([from_str, from_none], obj.get("derivedFrom")) - host = from_union([from_str, from_none], obj.get("host")) - login = from_union([from_str, from_none], obj.get("login")) - selection_id = from_union([from_str, from_none], obj.get("selectionId")) - available = from_union([from_bool, from_none], obj.get("available")) - label = from_union([from_str, from_none], obj.get("label")) - return ItemElement(kind, active, derived_from, host, login, selection_id, available, label) - - def to_dict(self) -> dict: - result: dict = {} - result["kind"] = to_enum(AccountKind, self.kind) - if self.active is not None: - result["active"] = from_union([from_bool, from_none], self.active) - if self.derived_from is not None: - result["derivedFrom"] = from_union([from_str, from_none], self.derived_from) - if self.host is not None: - result["host"] = from_union([from_str, from_none], self.host) - if self.login is not None: - result["login"] = from_union([from_str, from_none], self.login) - if self.selection_id is not None: - result["selectionId"] = from_union([from_str, from_none], self.selection_id) - if self.available is not None: - result["available"] = from_union([from_bool, from_none], self.available) - if self.label is not None: - result["label"] = from_union([from_str, from_none], self.label) - return result - -# Experimental: this type is part of an experimental API and may change or be removed. -@dataclass -class AuthEnumerateQuery: - """Which typed accounts collection to enumerate. - - Selects which accounts collection to enumerate. A no-arg selector is the empty-payload - variant. - """ - kind: AuthEnumerateQueryKind - """Account-collection query variant discriminator.""" - - broker_available: bool | None = None - """Whether an interactive Entra broker is available on the host; gates Entra availability in - the returned list. - """ - - @staticmethod - def from_dict(obj: Any) -> 'AuthEnumerateQuery': - assert isinstance(obj, dict) - kind = AuthEnumerateQueryKind(obj.get("kind")) - broker_available = from_union([from_bool, from_none], obj.get("brokerAvailable")) - return AuthEnumerateQuery(kind, broker_available) - - def to_dict(self) -> dict: - result: dict = {} - result["kind"] = to_enum(AuthEnumerateQueryKind, self.kind) - if self.broker_available is not None: - result["brokerAvailable"] = from_union([from_bool, from_none], self.broker_available) - return result - -# Experimental: this type is part of an experimental API and may change or be removed. -@dataclass -class AuthReadQuery: - """Which typed accounts datum to read. - - Selects which typed accounts datum to read. - """ - kind: AuthReadQueryKind - """Account read-datum query variant discriminator.""" - - @staticmethod - def from_dict(obj: Any) -> 'AuthReadQuery': - assert isinstance(obj, dict) - kind = AuthReadQueryKind(obj.get("kind")) - return AuthReadQuery(kind) - - def to_dict(self) -> dict: - result: dict = {} - result["kind"] = to_enum(AuthReadQueryKind, self.kind) - return result - -# Experimental: this type is part of an experimental API and may change or be removed. -@dataclass -class AuthWrite: - """The non-interactive mutation command to apply. - - One non-interactive accounts mutation command (the selector is fused with its typed args). - """ - kind: AuthWriteKind - """Account mutation command variant discriminator.""" - - selection_id: str | None = None - """Opaque selection id of the account to make active. - - Opaque selection id of the account to log out; absent logs out the active account. - """ - host: str | None = None - """Authentication host URL.""" - - login: str | None = None - """Login/username for the credential.""" - - token: str | None = None - """GitHub authentication token to install.""" - - @staticmethod - def from_dict(obj: Any) -> 'AuthWrite': - assert isinstance(obj, dict) - kind = AuthWriteKind(obj.get("kind")) - selection_id = from_union([from_str, from_none], obj.get("selectionId")) - host = from_union([from_str, from_none], obj.get("host")) - login = from_union([from_str, from_none], obj.get("login")) - token = from_union([from_str, from_none], obj.get("token")) - return AuthWrite(kind, selection_id, host, login, token) - - def to_dict(self) -> dict: - result: dict = {} - result["kind"] = to_enum(AuthWriteKind, self.kind) - if self.selection_id is not None: - result["selectionId"] = from_union([from_str, from_none], self.selection_id) - if self.host is not None: - result["host"] = from_union([from_str, from_none], self.host) - if self.login is not None: - result["login"] = from_union([from_str, from_none], self.login) - if self.token is not None: - result["token"] = from_union([from_str, from_none], self.token) - return result - # Experimental: this type is part of an experimental API and may change or be removed. @dataclass class ModelCapabilitiesSupports: @@ -16831,54 +17022,6 @@ def to_dict(self) -> dict: result["field"] = from_union([lambda x: to_enum(AgentRegistrySpawnValidationErrorField, x), from_none], self.field) return result -# Experimental: this type is part of an experimental API and may change or be removed. -@dataclass -class AuthLoginBeginRequest: - """Begin an interactive login flow for a provider kind. Dispatch is kind-only.""" - - kind: LoginProviderKind - """The provider kind to sign in with.""" - - @staticmethod - def from_dict(obj: Any) -> 'AuthLoginBeginRequest': - assert isinstance(obj, dict) - kind = LoginProviderKind(obj.get("kind")) - return AuthLoginBeginRequest(kind) - - def to_dict(self) -> dict: - result: dict = {} - result["kind"] = to_enum(LoginProviderKind, self.kind) - return result - -# Experimental: this type is part of an experimental API and may change or be removed. -@dataclass -class ProviderDescriptor: - """A provider offered for interactive login.""" - - available: bool - """Whether this provider is currently available to sign in with.""" - - kind: LoginProviderKind - """The neutral provider kind.""" - - label: str - """Human-readable menu label, owned by the runtime so every consumer renders identical text.""" - - @staticmethod - def from_dict(obj: Any) -> 'ProviderDescriptor': - assert isinstance(obj, dict) - available = from_bool(obj.get("available")) - kind = LoginProviderKind(obj.get("kind")) - label = from_str(obj.get("label")) - return ProviderDescriptor(available, kind, label) - - def to_dict(self) -> dict: - result: dict = {} - result["available"] = from_bool(self.available) - result["kind"] = to_enum(LoginProviderKind, self.kind) - result["label"] = from_str(self.label) - return result - # Experimental: this type is part of an experimental API and may change or be removed. @dataclass class AutopilotObjectiveState: @@ -18656,54 +18799,21 @@ class MetadataContextHeaviestMessagesResult: """Heaviest messages, most-expensive first.""" total_tokens: int - """Total token count of the current context window, so callers can compute each message's - share without a second call. - """ - - @staticmethod - def from_dict(obj: Any) -> 'MetadataContextHeaviestMessagesResult': - assert isinstance(obj, dict) - messages = from_list(ContextHeaviestMessage.from_dict, obj.get("messages")) - total_tokens = from_int(obj.get("totalTokens")) - return MetadataContextHeaviestMessagesResult(messages, total_tokens) - - def to_dict(self) -> dict: - result: dict = {} - result["messages"] = from_list(lambda x: to_class(ContextHeaviestMessage, x), self.messages) - result["totalTokens"] = from_int(self.total_tokens) - return result - -# Experimental: this type is part of an experimental API and may change or be removed. -@dataclass -class AuthLoginResultDto: - """The terminal login result. - - Terminal result of an interactive login flow. - """ - status: AuthLoginResultStatus - """Terminal disposition of the login.""" - - host: str | None = None - """Host that was signed in, when completed.""" - - login: str | None = None - """Login that was signed in, when completed.""" + """Total token count of the current context window, so callers can compute each message's + share without a second call. + """ @staticmethod - def from_dict(obj: Any) -> 'AuthLoginResultDto': + def from_dict(obj: Any) -> 'MetadataContextHeaviestMessagesResult': assert isinstance(obj, dict) - status = AuthLoginResultStatus(obj.get("status")) - host = from_union([from_str, from_none], obj.get("host")) - login = from_union([from_str, from_none], obj.get("login")) - return AuthLoginResultDto(status, host, login) + messages = from_list(ContextHeaviestMessage.from_dict, obj.get("messages")) + total_tokens = from_int(obj.get("totalTokens")) + return MetadataContextHeaviestMessagesResult(messages, total_tokens) def to_dict(self) -> dict: result: dict = {} - result["status"] = to_enum(AuthLoginResultStatus, self.status) - if self.host is not None: - result["host"] = from_union([from_str, from_none], self.host) - if self.login is not None: - result["login"] = from_union([from_str, from_none], self.login) + result["messages"] = from_list(lambda x: to_class(ContextHeaviestMessage, x), self.messages) + result["totalTokens"] = from_int(self.total_tokens) return result # Experimental: this type is part of an experimental API and may change or be removed. @@ -19607,275 +19717,911 @@ def to_dict(self) -> dict: # Experimental: this type is part of an experimental API and may change or be removed. @dataclass -class ExternalToolTextResultForLlmContentResourceLinkIcon: - """Icon image for a resource""" - - src: str - """URL or path to the icon image""" +class ExternalToolTextResultForLlmContentResourceLinkIcon: + """Icon image for a resource""" + + src: str + """URL or path to the icon image""" + + mime_type: str | None = None + """MIME type of the icon image""" + + sizes: list[str] | None = None + """Available icon sizes (e.g., ['16x16', '32x32'])""" + + theme: Theme | None = None + """Theme variant this icon is intended for""" + + @staticmethod + def from_dict(obj: Any) -> 'ExternalToolTextResultForLlmContentResourceLinkIcon': + assert isinstance(obj, dict) + src = from_str(obj.get("src")) + mime_type = from_union([from_str, from_none], obj.get("mimeType")) + sizes = from_union([lambda x: from_list(from_str, x), from_none], obj.get("sizes")) + theme = from_union([Theme, from_none], obj.get("theme")) + return ExternalToolTextResultForLlmContentResourceLinkIcon(src, mime_type, sizes, theme) + + def to_dict(self) -> dict: + result: dict = {} + result["src"] = from_str(self.src) + if self.mime_type is not None: + result["mimeType"] = from_union([from_str, from_none], self.mime_type) + if self.sizes is not None: + result["sizes"] = from_union([lambda x: from_list(from_str, x), from_none], self.sizes) + if self.theme is not None: + result["theme"] = from_union([lambda x: to_enum(Theme, x), from_none], self.theme) + return result + +ExternalToolTextResultForLlmContentResourceDetails = EmbeddedTextResourceContents | EmbeddedBlobResourceContents + +# Experimental: this type is part of an experimental API and may change or be removed. +@dataclass +class MCPResourceIcon: + """A resource icon descriptor plus preserved non-standard icon fields.""" + + src: str + """Icon URI""" + + additional_properties: dict[str, Any] | None = None + """Server-provided non-standard icon fields preserved from the MCP response""" + + mime_type: str | None = None + """Icon MIME type, when known""" + + sizes: str | None = None + """Icon sizes hint""" + + theme: str | None = None + """Theme hint for this icon""" + + @staticmethod + def from_dict(obj: Any) -> 'MCPResourceIcon': + assert isinstance(obj, dict) + src = from_str(obj.get("src")) + additional_properties = from_union([lambda x: from_dict(lambda x: x, x), from_none], obj.get("additionalProperties")) + mime_type = from_union([from_str, from_none], obj.get("mimeType")) + sizes = from_union([from_str, from_none], obj.get("sizes")) + theme = from_union([from_str, from_none], obj.get("theme")) + return MCPResourceIcon(src, additional_properties, mime_type, sizes, theme) + + def to_dict(self) -> dict: + result: dict = {} + result["src"] = from_str(self.src) + if self.additional_properties is not None: + result["additionalProperties"] = from_union([lambda x: from_dict(lambda x: x, x), from_none], self.additional_properties) + if self.mime_type is not None: + result["mimeType"] = from_union([from_str, from_none], self.mime_type) + if self.sizes is not None: + result["sizes"] = from_union([from_str, from_none], self.sizes) + if self.theme is not None: + result["theme"] = from_union([from_str, from_none], self.theme) + return result + +# Experimental: this type is part of an experimental API and may change or be removed. +@dataclass +class ExternalToolTextResultForLlmContentAudio: + """Audio content block with base64-encoded data""" + + data: str + """Base64-encoded audio data""" + + mime_type: str + """MIME type of the audio (e.g., audio/wav, audio/mpeg)""" + + type: ClassVar[str] = "audio" + """Content block type discriminator""" + + @staticmethod + def from_dict(obj: Any) -> 'ExternalToolTextResultForLlmContentAudio': + assert isinstance(obj, dict) + data = from_str(obj.get("data")) + mime_type = from_str(obj.get("mimeType")) + return ExternalToolTextResultForLlmContentAudio(data, mime_type) + + def to_dict(self) -> dict: + result: dict = {} + result["data"] = from_str(self.data) + result["mimeType"] = from_str(self.mime_type) + result["type"] = self.type + return result + +# Experimental: this type is part of an experimental API and may change or be removed. +@dataclass +class ExternalToolTextResultForLlmContentImage: + """Image content block with base64-encoded data""" + + data: str + """Base64-encoded image data""" + + mime_type: str + """MIME type of the image (e.g., image/png, image/jpeg)""" + + type: ClassVar[str] = "image" + """Content block type discriminator""" + + @staticmethod + def from_dict(obj: Any) -> 'ExternalToolTextResultForLlmContentImage': + assert isinstance(obj, dict) + data = from_str(obj.get("data")) + mime_type = from_str(obj.get("mimeType")) + return ExternalToolTextResultForLlmContentImage(data, mime_type) + + def to_dict(self) -> dict: + result: dict = {} + result["data"] = from_str(self.data) + result["mimeType"] = from_str(self.mime_type) + result["type"] = self.type + return result + +# Experimental: this type is part of an experimental API and may change or be removed. +@dataclass +class ExternalToolTextResultForLlmContentResource: + """Embedded resource content block with inline text or binary data""" + + resource: ExternalToolTextResultForLlmContentResourceDetails + """The embedded resource contents, either text or base64-encoded binary""" + + type: ClassVar[str] = "resource" + """Content block type discriminator""" + + @staticmethod + def from_dict(obj: Any) -> 'ExternalToolTextResultForLlmContentResource': + assert isinstance(obj, dict) + resource = (lambda x: from_union([EmbeddedTextResourceContents.from_dict, EmbeddedBlobResourceContents.from_dict], x))(obj.get("resource")) + return ExternalToolTextResultForLlmContentResource(resource) + + def to_dict(self) -> dict: + result: dict = {} + result["resource"] = from_union([lambda x: to_class(EmbeddedTextResourceContents, x), lambda x: to_class(EmbeddedBlobResourceContents, x)], self.resource) + result["type"] = self.type + return result + +# Experimental: this type is part of an experimental API and may change or be removed. +@dataclass +class ExternalToolTextResultForLlmContentShellExit: + """Shell command exit metadata with optional output preview""" + + exit_code: int + """Exit code from the completed shell command""" + + shell_id: str + """Shell id, as assigned by Copilot runtime""" + + type: ClassVar[str] = "shell_exit" + """Content block type discriminator""" + + cwd: str | None = None + """Working directory where the shell command was executed""" + + output_file_path: str | None = None + """Path reported in the shell session's filesystem namespace when shell output exceeded the + configured large-output threshold. + """ + output_preview: str | None = None + """Output associated with this shell command, if available. May be partial, truncated, or a + preview; not guaranteed to be full output. + """ + output_truncated: bool | None = None + """Whether outputPreview is known to be incomplete or truncated""" + + @staticmethod + def from_dict(obj: Any) -> 'ExternalToolTextResultForLlmContentShellExit': + assert isinstance(obj, dict) + exit_code = from_int(obj.get("exitCode")) + shell_id = from_str(obj.get("shellId")) + cwd = from_union([from_str, from_none], obj.get("cwd")) + output_file_path = from_union([from_str, from_none], obj.get("outputFilePath")) + output_preview = from_union([from_str, from_none], obj.get("outputPreview")) + output_truncated = from_union([from_bool, from_none], obj.get("outputTruncated")) + return ExternalToolTextResultForLlmContentShellExit(exit_code, shell_id, cwd, output_file_path, output_preview, output_truncated) + + def to_dict(self) -> dict: + result: dict = {} + result["exitCode"] = from_int(self.exit_code) + result["shellId"] = from_str(self.shell_id) + result["type"] = self.type + if self.cwd is not None: + result["cwd"] = from_union([from_str, from_none], self.cwd) + if self.output_file_path is not None: + result["outputFilePath"] = from_union([from_str, from_none], self.output_file_path) + if self.output_preview is not None: + result["outputPreview"] = from_union([from_str, from_none], self.output_preview) + if self.output_truncated is not None: + result["outputTruncated"] = from_union([from_bool, from_none], self.output_truncated) + return result + +# Experimental: this type is part of an experimental API and may change or be removed. +@dataclass +class ExternalToolTextResultForLlmContentTerminal: + """Terminal/shell output content block with optional exit code and working directory""" + + text: str + """Terminal/shell output text""" + + type: ClassVar[str] = "terminal" + """Content block type discriminator""" + + cwd: str | None = None + """Working directory where the command was executed""" + + exit_code: int | None = None + """Process exit code, if the command has completed""" + + @staticmethod + def from_dict(obj: Any) -> 'ExternalToolTextResultForLlmContentTerminal': + assert isinstance(obj, dict) + text = from_str(obj.get("text")) + cwd = from_union([from_str, from_none], obj.get("cwd")) + exit_code = from_union([from_int, from_none], obj.get("exitCode")) + return ExternalToolTextResultForLlmContentTerminal(text, cwd, exit_code) + + def to_dict(self) -> dict: + result: dict = {} + result["text"] = from_str(self.text) + result["type"] = self.type + if self.cwd is not None: + result["cwd"] = from_union([from_str, from_none], self.cwd) + if self.exit_code is not None: + result["exitCode"] = from_union([from_int, from_none], self.exit_code) + return result + +# Experimental: this type is part of an experimental API and may change or be removed. +@dataclass +class ExternalToolTextResultForLlmContentText: + """Plain text content block""" + + text: str + """The text content""" + + type: ClassVar[str] = "text" + """Content block type discriminator""" + + @staticmethod + def from_dict(obj: Any) -> 'ExternalToolTextResultForLlmContentText': + assert isinstance(obj, dict) + text = from_str(obj.get("text")) + return ExternalToolTextResultForLlmContentText(text) + + def to_dict(self) -> dict: + result: dict = {} + result["text"] = from_str(self.text) + result["type"] = self.type + return result + +# Experimental: this type is part of an experimental API and may change or be removed. +@dataclass +class FactoryAgentRequest: + """Parameters for one factory-scoped subagent call.""" + + execution_token: str + """Opaque token identifying the current factory execution attempt.""" + + factory_run_id: str + """Factory run identifier that owns the subagent.""" + + opts: FactoryAgentOptions + """Subagent execution options.""" + + prompt: str + """Prompt to send to the subagent.""" + + @staticmethod + def from_dict(obj: Any) -> 'FactoryAgentRequest': + assert isinstance(obj, dict) + execution_token = from_str(obj.get("executionToken")) + factory_run_id = from_str(obj.get("factoryRunId")) + opts = FactoryAgentOptions.from_dict(obj.get("opts")) + prompt = from_str(obj.get("prompt")) + return FactoryAgentRequest(execution_token, factory_run_id, opts, prompt) + + def to_dict(self) -> dict: + result: dict = {} + result["executionToken"] = from_str(self.execution_token) + result["factoryRunId"] = from_str(self.factory_run_id) + result["opts"] = to_class(FactoryAgentOptions, self.opts) + result["prompt"] = from_str(self.prompt) + return result + +# Experimental: this type is part of an experimental API and may change or be removed. +@dataclass +class FactoryRunFailure: + """Machine-readable terminal failure. + + Machine-readable factory run failure. + + Machine-readable failure details for a halted or errored run. + + The run stopped because its usage accounting could not be completed. + + The extension that owns the factory disconnected while the run was executing, so the host + halted it. The run's journaled subagent results are preserved so a resume can reuse them. + """ + run_id: str + """Factory run identifier. + + Factory run identifier whose changed limits were declined. + """ + type: FactoryRunFailureType + """Factory failure variant discriminator.""" + + kind: RunFailureKind | None = None + """Resource ceiling that stopped the run.""" + + suggested_value: float | None = None + """Suggested larger ceiling when the runtime can derive one safely.""" + + value: float | None = None + """Approved effective ceiling that was reached.""" + + reason: str | None = None + """Human-readable reason the resume did not proceed.""" + + code: str | None = None + """Stable failure code.""" + + operation: DurableOperation | None = None + """Execution-critical durable operation that failed.""" + + drained_nano_aiu: int | None = None + """Confirmed usage in nano-AIU, representing the floor of what the run spent.""" + + @staticmethod + def from_dict(obj: Any) -> 'FactoryRunFailure': + assert isinstance(obj, dict) + run_id = from_str(obj.get("runId")) + type = FactoryRunFailureType(obj.get("type")) + kind = from_union([RunFailureKind, from_none], obj.get("kind")) + suggested_value = from_union([from_float, from_none], obj.get("suggestedValue")) + value = from_union([from_float, from_none], obj.get("value")) + reason = from_union([from_str, from_none], obj.get("reason")) + code = from_union([from_str, from_none], obj.get("code")) + operation = from_union([DurableOperation, from_none], obj.get("operation")) + drained_nano_aiu = from_union([from_int, from_none], obj.get("drainedNanoAiu")) + return FactoryRunFailure(run_id, type, kind, suggested_value, value, reason, code, operation, drained_nano_aiu) + + def to_dict(self) -> dict: + result: dict = {} + result["runId"] = from_str(self.run_id) + result["type"] = to_enum(FactoryRunFailureType, self.type) + if self.kind is not None: + result["kind"] = from_union([lambda x: to_enum(RunFailureKind, x), from_none], self.kind) + if self.suggested_value is not None: + result["suggestedValue"] = from_union([to_float, from_none], self.suggested_value) + if self.value is not None: + result["value"] = from_union([to_float, from_none], self.value) + if self.reason is not None: + result["reason"] = from_union([from_str, from_none], self.reason) + if self.code is not None: + result["code"] = from_union([from_str, from_none], self.code) + if self.operation is not None: + result["operation"] = from_union([lambda x: to_enum(DurableOperation, x), from_none], self.operation) + if self.drained_nano_aiu is not None: + result["drainedNanoAiu"] = from_union([from_int, from_none], self.drained_nano_aiu) + return result + +@dataclass +class PauseInfoClass: + type: PauseInfoType + """Factory pause initiator discriminator.""" + + key: str | None = None + """Stable author-defined checkpoint key that initiated the pause.""" + + @staticmethod + def from_dict(obj: Any) -> 'PauseInfoClass': + assert isinstance(obj, dict) + type = PauseInfoType(obj.get("type")) + key = from_union([from_str, from_none], obj.get("key")) + return PauseInfoClass(type, key) + + def to_dict(self) -> dict: + result: dict = {} + result["type"] = to_enum(PauseInfoType, self.type) + if self.key is not None: + result["key"] = from_union([from_str, from_none], self.key) + return result + +# Experimental: this type is part of an experimental API and may change or be removed. +@dataclass +class FactoryPauseInfo: + """Durable metadata describing who initiated a factory pause. + + Structured pause initiator metadata for a paused attempt. + """ + type: PauseInfoType + """Factory pause initiator discriminator.""" + + key: str | None = None + """Stable author-defined checkpoint key that initiated the pause.""" + + @staticmethod + def from_dict(obj: Any) -> 'FactoryPauseInfo': + assert isinstance(obj, dict) + type = PauseInfoType(obj.get("type")) + key = from_union([from_str, from_none], obj.get("key")) + return FactoryPauseInfo(type, key) + + def to_dict(self) -> dict: + result: dict = {} + result["type"] = to_enum(PauseInfoType, self.type) + if self.key is not None: + result["key"] = from_union([from_str, from_none], self.key) + return result + +@dataclass +class WorkflowRunTerminalPauseInfo: + type: PauseInfoType + """Workflow pause initiator discriminator.""" + + key: str | None = None + """Stable author-defined checkpoint key that initiated the pause.""" + + @staticmethod + def from_dict(obj: Any) -> 'WorkflowRunTerminalPauseInfo': + assert isinstance(obj, dict) + type = PauseInfoType(obj.get("type")) + key = from_union([from_str, from_none], obj.get("key")) + return WorkflowRunTerminalPauseInfo(type, key) + + def to_dict(self) -> dict: + result: dict = {} + result["type"] = to_enum(PauseInfoType, self.type) + if self.key is not None: + result["key"] = from_union([from_str, from_none], self.key) + return result + +# Experimental: this type is part of an experimental API and may change or be removed. +@dataclass +class WorkflowPauseInfo: + """Durable metadata describing who initiated a workflow pause. + + Structured pause initiator metadata for a paused attempt. + """ + type: PauseInfoType + """Workflow pause initiator discriminator.""" + + key: str | None = None + """Stable author-defined checkpoint key that initiated the pause.""" + + @staticmethod + def from_dict(obj: Any) -> 'WorkflowPauseInfo': + assert isinstance(obj, dict) + type = PauseInfoType(obj.get("type")) + key = from_union([from_str, from_none], obj.get("key")) + return WorkflowPauseInfo(type, key) + + def to_dict(self) -> dict: + result: dict = {} + result["type"] = to_enum(PauseInfoType, self.type) + if self.key is not None: + result["key"] = from_union([from_str, from_none], self.key) + return result + +# Experimental: this type is part of an experimental API and may change or be removed. +@dataclass +class FactoryLogLine: + """One ordered factory progress line.""" + + kind: LogLineKind + """Progress line kind.""" + + seq: int + """Monotonic sequence number within the factory run.""" + + text: str + """Progress text.""" + + @staticmethod + def from_dict(obj: Any) -> 'FactoryLogLine': + assert isinstance(obj, dict) + kind = LogLineKind(obj.get("kind")) + seq = from_int(obj.get("seq")) + text = from_str(obj.get("text")) + return FactoryLogLine(kind, seq, text) + + def to_dict(self) -> dict: + result: dict = {} + result["kind"] = to_enum(LogLineKind, self.kind) + result["seq"] = from_int(self.seq) + result["text"] = from_str(self.text) + return result + +# Experimental: this type is part of an experimental API and may change or be removed. +@dataclass +class FactoryProgressLine: + """One durable factory progress record.""" + + attempt: int + """Resume attempt that emitted this record.""" + + kind: LogLineKind + """Progress record kind.""" + + recorded_at: int + """Epoch milliseconds when the record was persisted.""" + + seq: int + """Global monotonic sequence number within the run.""" + + text: str + """Prompt-safe progress text.""" + + phase_id: str | None = None + """Phase active when the record was emitted, or null before any phase.""" + + @staticmethod + def from_dict(obj: Any) -> 'FactoryProgressLine': + assert isinstance(obj, dict) + attempt = from_int(obj.get("attempt")) + kind = LogLineKind(obj.get("kind")) + recorded_at = from_int(obj.get("recordedAt")) + seq = from_int(obj.get("seq")) + text = from_str(obj.get("text")) + phase_id = from_union([from_none, from_str], obj.get("phaseId")) + return FactoryProgressLine(attempt, kind, recorded_at, seq, text, phase_id) + + def to_dict(self) -> dict: + result: dict = {} + result["attempt"] = from_int(self.attempt) + result["kind"] = to_enum(LogLineKind, self.kind) + result["recordedAt"] = from_int(self.recorded_at) + result["seq"] = from_int(self.seq) + result["text"] = from_str(self.text) + result["phaseId"] = from_union([from_none, from_str], self.phase_id) + return result + +# Experimental: this type is part of an experimental API and may change or be removed. +@dataclass +class WorkflowLogLine: + """One ordered workflow progress line.""" - mime_type: str | None = None - """MIME type of the icon image""" + kind: LogLineKind + """Progress line kind.""" - sizes: list[str] | None = None - """Available icon sizes (e.g., ['16x16', '32x32'])""" + seq: int + """Monotonic sequence number within the workflow run.""" - theme: Theme | None = None - """Theme variant this icon is intended for""" + text: str + """Progress text.""" @staticmethod - def from_dict(obj: Any) -> 'ExternalToolTextResultForLlmContentResourceLinkIcon': + def from_dict(obj: Any) -> 'WorkflowLogLine': assert isinstance(obj, dict) - src = from_str(obj.get("src")) - mime_type = from_union([from_str, from_none], obj.get("mimeType")) - sizes = from_union([lambda x: from_list(from_str, x), from_none], obj.get("sizes")) - theme = from_union([Theme, from_none], obj.get("theme")) - return ExternalToolTextResultForLlmContentResourceLinkIcon(src, mime_type, sizes, theme) + kind = LogLineKind(obj.get("kind")) + seq = from_int(obj.get("seq")) + text = from_str(obj.get("text")) + return WorkflowLogLine(kind, seq, text) def to_dict(self) -> dict: result: dict = {} - result["src"] = from_str(self.src) - if self.mime_type is not None: - result["mimeType"] = from_union([from_str, from_none], self.mime_type) - if self.sizes is not None: - result["sizes"] = from_union([lambda x: from_list(from_str, x), from_none], self.sizes) - if self.theme is not None: - result["theme"] = from_union([lambda x: to_enum(Theme, x), from_none], self.theme) + result["kind"] = to_enum(LogLineKind, self.kind) + result["seq"] = from_int(self.seq) + result["text"] = from_str(self.text) return result -ExternalToolTextResultForLlmContentResourceDetails = EmbeddedTextResourceContents | EmbeddedBlobResourceContents - # Experimental: this type is part of an experimental API and may change or be removed. @dataclass -class MCPResourceIcon: - """A resource icon descriptor plus preserved non-standard icon fields.""" +class WorkflowProgressLine: + """One durable workflow progress record.""" - src: str - """Icon URI""" + attempt: int + """Resume attempt that emitted this record.""" - additional_properties: dict[str, Any] | None = None - """Server-provided non-standard icon fields preserved from the MCP response""" + kind: LogLineKind + """Progress record kind.""" - mime_type: str | None = None - """Icon MIME type, when known""" + recorded_at: int + """Epoch milliseconds when the record was persisted.""" - sizes: str | None = None - """Icon sizes hint""" + seq: int + """Global monotonic sequence number within the run.""" - theme: str | None = None - """Theme hint for this icon""" + text: str + """Prompt-safe progress text.""" + + phase_id: str | None = None + """Phase active when the record was emitted, or null before any phase.""" @staticmethod - def from_dict(obj: Any) -> 'MCPResourceIcon': + def from_dict(obj: Any) -> 'WorkflowProgressLine': assert isinstance(obj, dict) - src = from_str(obj.get("src")) - additional_properties = from_union([lambda x: from_dict(lambda x: x, x), from_none], obj.get("additionalProperties")) - mime_type = from_union([from_str, from_none], obj.get("mimeType")) - sizes = from_union([from_str, from_none], obj.get("sizes")) - theme = from_union([from_str, from_none], obj.get("theme")) - return MCPResourceIcon(src, additional_properties, mime_type, sizes, theme) + attempt = from_int(obj.get("attempt")) + kind = LogLineKind(obj.get("kind")) + recorded_at = from_int(obj.get("recordedAt")) + seq = from_int(obj.get("seq")) + text = from_str(obj.get("text")) + phase_id = from_union([from_none, from_str], obj.get("phaseId")) + return WorkflowProgressLine(attempt, kind, recorded_at, seq, text, phase_id) def to_dict(self) -> dict: result: dict = {} - result["src"] = from_str(self.src) - if self.additional_properties is not None: - result["additionalProperties"] = from_union([lambda x: from_dict(lambda x: x, x), from_none], self.additional_properties) - if self.mime_type is not None: - result["mimeType"] = from_union([from_str, from_none], self.mime_type) - if self.sizes is not None: - result["sizes"] = from_union([from_str, from_none], self.sizes) - if self.theme is not None: - result["theme"] = from_union([from_str, from_none], self.theme) + result["attempt"] = from_int(self.attempt) + result["kind"] = to_enum(LogLineKind, self.kind) + result["recordedAt"] = from_int(self.recorded_at) + result["seq"] = from_int(self.seq) + result["text"] = from_str(self.text) + result["phaseId"] = from_union([from_none, from_str], self.phase_id) return result # Experimental: this type is part of an experimental API and may change or be removed. @dataclass -class ExternalToolTextResultForLlmContentAudio: - """Audio content block with base64-encoded data""" +class FactoryPhaseObservation: + """Durable lifecycle and timing for one factory phase.""" - data: str - """Base64-encoded audio data""" + accumulated_active_ms: int + """Completed active time accumulated by this phase in milliseconds.""" - mime_type: str - """MIME type of the audio (e.g., audio/wav, audio/mpeg)""" + current_active_ms: int + """Current live active time for this phase in milliseconds.""" - type: ClassVar[str] = "audio" - """Content block type discriminator""" + entry_count: int + """Number of times execution entered this phase.""" - @staticmethod - def from_dict(obj: Any) -> 'ExternalToolTextResultForLlmContentAudio': - assert isinstance(obj, dict) - data = from_str(obj.get("data")) - mime_type = from_str(obj.get("mimeType")) - return ExternalToolTextResultForLlmContentAudio(data, mime_type) + id: str + """Phase identifier.""" - def to_dict(self) -> dict: - result: dict = {} - result["data"] = from_str(self.data) - result["mimeType"] = from_str(self.mime_type) - result["type"] = self.type - return result + last_entered_run_attempt: int + """Most recent run attempt that entered this phase, or `0` if the phase has never been + entered. + """ + live_agent_count: int + """Direct agents in this phase that are currently live.""" -# Experimental: this type is part of an experimental API and may change or be removed. -@dataclass -class ExternalToolTextResultForLlmContentImage: - """Image content block with base64-encoded data""" + status: PhaseStatus + """Derived lifecycle state of the phase.""" - data: str - """Base64-encoded image data""" + title: str + """Human-readable phase title.""" - mime_type: str - """MIME type of the image (e.g., image/png, image/jpeg)""" + total_agent_count: int + """Total direct agents associated with this phase.""" - type: ClassVar[str] = "image" - """Content block type discriminator""" + completed_at: int | None = None + """Epoch milliseconds when this phase completed; for a skipped phase, the synthetic skip + timestamp (equal to `startedAt`). + """ + detail: str | None = None + """Optional human-readable phase detail.""" + + ordinal: int | None = None + """Zero-based declared phase ordinal, or null for an undeclared phase.""" + + started_at: int | None = None + """Epoch milliseconds when this phase first started; for a skipped phase, the synthetic skip + timestamp (equal to `completedAt`). + """ @staticmethod - def from_dict(obj: Any) -> 'ExternalToolTextResultForLlmContentImage': + def from_dict(obj: Any) -> 'FactoryPhaseObservation': assert isinstance(obj, dict) - data = from_str(obj.get("data")) - mime_type = from_str(obj.get("mimeType")) - return ExternalToolTextResultForLlmContentImage(data, mime_type) + accumulated_active_ms = from_int(obj.get("accumulatedActiveMs")) + current_active_ms = from_int(obj.get("currentActiveMs")) + entry_count = from_int(obj.get("entryCount")) + id = from_str(obj.get("id")) + last_entered_run_attempt = from_int(obj.get("lastEnteredRunAttempt")) + live_agent_count = from_int(obj.get("liveAgentCount")) + status = PhaseStatus(obj.get("status")) + title = from_str(obj.get("title")) + total_agent_count = from_int(obj.get("totalAgentCount")) + completed_at = from_union([from_int, from_none], obj.get("completedAt")) + detail = from_union([from_str, from_none], obj.get("detail")) + ordinal = from_union([from_none, from_int], obj.get("ordinal")) + started_at = from_union([from_int, from_none], obj.get("startedAt")) + return FactoryPhaseObservation(accumulated_active_ms, current_active_ms, entry_count, id, last_entered_run_attempt, live_agent_count, status, title, total_agent_count, completed_at, detail, ordinal, started_at) def to_dict(self) -> dict: result: dict = {} - result["data"] = from_str(self.data) - result["mimeType"] = from_str(self.mime_type) - result["type"] = self.type + result["accumulatedActiveMs"] = from_int(self.accumulated_active_ms) + result["currentActiveMs"] = from_int(self.current_active_ms) + result["entryCount"] = from_int(self.entry_count) + result["id"] = from_str(self.id) + result["lastEnteredRunAttempt"] = from_int(self.last_entered_run_attempt) + result["liveAgentCount"] = from_int(self.live_agent_count) + result["status"] = to_enum(PhaseStatus, self.status) + result["title"] = from_str(self.title) + result["totalAgentCount"] = from_int(self.total_agent_count) + if self.completed_at is not None: + result["completedAt"] = from_union([from_int, from_none], self.completed_at) + if self.detail is not None: + result["detail"] = from_union([from_str, from_none], self.detail) + result["ordinal"] = from_union([from_none, from_int], self.ordinal) + if self.started_at is not None: + result["startedAt"] = from_union([from_int, from_none], self.started_at) return result # Experimental: this type is part of an experimental API and may change or be removed. @dataclass -class ExternalToolTextResultForLlmContentResource: - """Embedded resource content block with inline text or binary data""" +class WorkflowPhaseObservation: + """Durable lifecycle and timing for one workflow phase.""" - resource: ExternalToolTextResultForLlmContentResourceDetails - """The embedded resource contents, either text or base64-encoded binary""" + accumulated_active_ms: int + """Completed active time accumulated by this phase in milliseconds.""" - type: ClassVar[str] = "resource" - """Content block type discriminator""" + current_active_ms: int + """Current live active time for this phase in milliseconds.""" + + entry_count: int + """Number of times execution entered this phase.""" + + id: str + """Phase identifier.""" + + last_entered_run_attempt: int + """Most recent run attempt that entered this phase, or `0` if the phase has never been + entered. + """ + live_agent_count: int + """Direct agents in this phase that are currently live.""" + + status: PhaseStatus + """Derived lifecycle state of the phase.""" + + title: str + """Human-readable phase title.""" + + total_agent_count: int + """Total direct agents associated with this phase.""" + + completed_at: int | None = None + """Epoch milliseconds when this phase completed; for a skipped phase, the synthetic skip + timestamp (equal to `startedAt`). + """ + detail: str | None = None + """Optional human-readable phase detail.""" + + ordinal: int | None = None + """Zero-based declared phase ordinal, or null for an undeclared phase.""" + + started_at: int | None = None + """Epoch milliseconds when this phase first started; for a skipped phase, the synthetic skip + timestamp (equal to `completedAt`). + """ @staticmethod - def from_dict(obj: Any) -> 'ExternalToolTextResultForLlmContentResource': + def from_dict(obj: Any) -> 'WorkflowPhaseObservation': assert isinstance(obj, dict) - resource = (lambda x: from_union([EmbeddedTextResourceContents.from_dict, EmbeddedBlobResourceContents.from_dict], x))(obj.get("resource")) - return ExternalToolTextResultForLlmContentResource(resource) + accumulated_active_ms = from_int(obj.get("accumulatedActiveMs")) + current_active_ms = from_int(obj.get("currentActiveMs")) + entry_count = from_int(obj.get("entryCount")) + id = from_str(obj.get("id")) + last_entered_run_attempt = from_int(obj.get("lastEnteredRunAttempt")) + live_agent_count = from_int(obj.get("liveAgentCount")) + status = PhaseStatus(obj.get("status")) + title = from_str(obj.get("title")) + total_agent_count = from_int(obj.get("totalAgentCount")) + completed_at = from_union([from_int, from_none], obj.get("completedAt")) + detail = from_union([from_str, from_none], obj.get("detail")) + ordinal = from_union([from_none, from_int], obj.get("ordinal")) + started_at = from_union([from_int, from_none], obj.get("startedAt")) + return WorkflowPhaseObservation(accumulated_active_ms, current_active_ms, entry_count, id, last_entered_run_attempt, live_agent_count, status, title, total_agent_count, completed_at, detail, ordinal, started_at) def to_dict(self) -> dict: result: dict = {} - result["resource"] = from_union([lambda x: to_class(EmbeddedTextResourceContents, x), lambda x: to_class(EmbeddedBlobResourceContents, x)], self.resource) - result["type"] = self.type + result["accumulatedActiveMs"] = from_int(self.accumulated_active_ms) + result["currentActiveMs"] = from_int(self.current_active_ms) + result["entryCount"] = from_int(self.entry_count) + result["id"] = from_str(self.id) + result["lastEnteredRunAttempt"] = from_int(self.last_entered_run_attempt) + result["liveAgentCount"] = from_int(self.live_agent_count) + result["status"] = to_enum(PhaseStatus, self.status) + result["title"] = from_str(self.title) + result["totalAgentCount"] = from_int(self.total_agent_count) + if self.completed_at is not None: + result["completedAt"] = from_union([from_int, from_none], self.completed_at) + if self.detail is not None: + result["detail"] = from_union([from_str, from_none], self.detail) + result["ordinal"] = from_union([from_none, from_int], self.ordinal) + if self.started_at is not None: + result["startedAt"] = from_union([from_int, from_none], self.started_at) return result # Experimental: this type is part of an experimental API and may change or be removed. @dataclass -class ExternalToolTextResultForLlmContentShellExit: - """Shell command exit metadata with optional output preview""" - - exit_code: int - """Exit code from the completed shell command""" +class FactoryResumeRequest: + """Parameters for resuming a factory run from its persisted identity.""" - shell_id: str - """Shell id, as assigned by Copilot runtime""" + run_id: str + """Factory run identifier.""" - type: ClassVar[str] = "shell_exit" - """Content block type discriminator""" + limits: FactoryRunLimits | None = None + """Optional per-invocation resource ceiling overrides.""" - cwd: str | None = None - """Working directory where the shell command was executed""" + log_phase_names: bool | None = None + """Whether to emit factory phase names to the session transcript.""" - output_file_path: str | None = None - """Path reported in the shell session's filesystem namespace when shell output exceeded the - configured large-output threshold. - """ - output_preview: str | None = None - """Output associated with this shell command, if available. May be partial, truncated, or a - preview; not guaranteed to be full output. - """ - output_truncated: bool | None = None - """Whether outputPreview is known to be incomplete or truncated""" + notify_on_complete: bool | None = None + """Whether to notify the originating session when the factory completes.""" @staticmethod - def from_dict(obj: Any) -> 'ExternalToolTextResultForLlmContentShellExit': + def from_dict(obj: Any) -> 'FactoryResumeRequest': assert isinstance(obj, dict) - exit_code = from_int(obj.get("exitCode")) - shell_id = from_str(obj.get("shellId")) - cwd = from_union([from_str, from_none], obj.get("cwd")) - output_file_path = from_union([from_str, from_none], obj.get("outputFilePath")) - output_preview = from_union([from_str, from_none], obj.get("outputPreview")) - output_truncated = from_union([from_bool, from_none], obj.get("outputTruncated")) - return ExternalToolTextResultForLlmContentShellExit(exit_code, shell_id, cwd, output_file_path, output_preview, output_truncated) + run_id = from_str(obj.get("runId")) + limits = from_union([FactoryRunLimits.from_dict, from_none], obj.get("limits")) + log_phase_names = from_union([from_bool, from_none], obj.get("logPhaseNames")) + notify_on_complete = from_union([from_bool, from_none], obj.get("notifyOnComplete")) + return FactoryResumeRequest(run_id, limits, log_phase_names, notify_on_complete) def to_dict(self) -> dict: result: dict = {} - result["exitCode"] = from_int(self.exit_code) - result["shellId"] = from_str(self.shell_id) - result["type"] = self.type - if self.cwd is not None: - result["cwd"] = from_union([from_str, from_none], self.cwd) - if self.output_file_path is not None: - result["outputFilePath"] = from_union([from_str, from_none], self.output_file_path) - if self.output_preview is not None: - result["outputPreview"] = from_union([from_str, from_none], self.output_preview) - if self.output_truncated is not None: - result["outputTruncated"] = from_union([from_bool, from_none], self.output_truncated) + result["runId"] = from_str(self.run_id) + if self.limits is not None: + result["limits"] = from_union([lambda x: to_class(FactoryRunLimits, x), from_none], self.limits) + if self.log_phase_names is not None: + result["logPhaseNames"] = from_union([from_bool, from_none], self.log_phase_names) + if self.notify_on_complete is not None: + result["notifyOnComplete"] = from_union([from_bool, from_none], self.notify_on_complete) return result # Experimental: this type is part of an experimental API and may change or be removed. @dataclass -class ExternalToolTextResultForLlmContentTerminal: - """Terminal/shell output content block with optional exit code and working directory""" +class RunOptions: + """Factory invocation options. - text: str - """Terminal/shell output text""" + Options controlling factory invocation. + """ + limits: FactoryRunLimits | None = None + """Per-invocation resource ceiling overrides.""" - type: ClassVar[str] = "terminal" - """Content block type discriminator""" + log_phase_names: bool | None = None + """Whether to emit factory phase names to the session transcript.""" - cwd: str | None = None - """Working directory where the command was executed""" + notify_on_complete: bool | None = None + """Whether to notify the originating session when the factory completes.""" - exit_code: int | None = None - """Process exit code, if the command has completed""" + resume_from_run_id: str | None = None + """Run identifier whose journal and progress should seed this resumed run.""" @staticmethod - def from_dict(obj: Any) -> 'ExternalToolTextResultForLlmContentTerminal': + def from_dict(obj: Any) -> 'RunOptions': assert isinstance(obj, dict) - text = from_str(obj.get("text")) - cwd = from_union([from_str, from_none], obj.get("cwd")) - exit_code = from_union([from_int, from_none], obj.get("exitCode")) - return ExternalToolTextResultForLlmContentTerminal(text, cwd, exit_code) + limits = from_union([FactoryRunLimits.from_dict, from_none], obj.get("limits")) + log_phase_names = from_union([from_bool, from_none], obj.get("logPhaseNames")) + notify_on_complete = from_union([from_bool, from_none], obj.get("notifyOnComplete")) + resume_from_run_id = from_union([from_str, from_none], obj.get("resumeFromRunId")) + return RunOptions(limits, log_phase_names, notify_on_complete, resume_from_run_id) def to_dict(self) -> dict: result: dict = {} - result["text"] = from_str(self.text) - result["type"] = self.type - if self.cwd is not None: - result["cwd"] = from_union([from_str, from_none], self.cwd) - if self.exit_code is not None: - result["exitCode"] = from_union([from_int, from_none], self.exit_code) + if self.limits is not None: + result["limits"] = from_union([lambda x: to_class(FactoryRunLimits, x), from_none], self.limits) + if self.log_phase_names is not None: + result["logPhaseNames"] = from_union([from_bool, from_none], self.log_phase_names) + if self.notify_on_complete is not None: + result["notifyOnComplete"] = from_union([from_bool, from_none], self.notify_on_complete) + if self.resume_from_run_id is not None: + result["resumeFromRunId"] = from_union([from_str, from_none], self.resume_from_run_id) return result # Experimental: this type is part of an experimental API and may change or be removed. +# Internal: this type is an internal SDK API and is not part of the public surface. @dataclass -class ExternalToolTextResultForLlmContentText: - """Plain text content block""" +class _FactoryToolResumeRequest: + """Internal parameters for resuming a factory run from a tool.""" - text: str - """The text content""" + run_id: str + """Factory run identifier.""" - type: ClassVar[str] = "text" - """Content block type discriminator""" + limits: FactoryRunLimits | None = None + """Optional per-invocation resource ceiling overrides.""" + + tool_call_id: str | None = None + """Opaque identifier of the originating tool call.""" @staticmethod - def from_dict(obj: Any) -> 'ExternalToolTextResultForLlmContentText': + def from_dict(obj: Any) -> '_FactoryToolResumeRequest': assert isinstance(obj, dict) - text = from_str(obj.get("text")) - return ExternalToolTextResultForLlmContentText(text) + run_id = from_str(obj.get("runId")) + limits = from_union([FactoryRunLimits.from_dict, from_none], obj.get("limits")) + tool_call_id = from_union([from_str, from_none], obj.get("toolCallId")) + return _FactoryToolResumeRequest(run_id, limits, tool_call_id) def to_dict(self) -> dict: result: dict = {} - result["text"] = from_str(self.text) - result["type"] = self.type + result["runId"] = from_str(self.run_id) + if self.limits is not None: + result["limits"] = from_union([lambda x: to_class(FactoryRunLimits, x), from_none], self.limits) + if self.tool_call_id is not None: + result["toolCallId"] = from_union([from_str, from_none], self.tool_call_id) return result # Experimental: this type is part of an experimental API and may change or be removed. @@ -22834,6 +23580,84 @@ def to_dict(self) -> dict: result["operation"] = from_union([from_str, from_none], self.operation) return result +# Experimental: this type is part of an experimental API and may change or be removed. +@dataclass +class PermissionDecisionApproveForLocationApprovalFactory: + """Location-scoped factory approval, optionally narrowed by approval key.""" + + kind: ClassVar[str] = "factory" + """Approval covering factory operations.""" + + approval_key: str | None = None + """Optional factory operation name or canonical approval key; when omitted, the approval + covers all factory operations. + """ + + @staticmethod + def from_dict(obj: Any) -> 'PermissionDecisionApproveForLocationApprovalFactory': + assert isinstance(obj, dict) + approval_key = from_union([from_str, from_none], obj.get("approvalKey")) + return PermissionDecisionApproveForLocationApprovalFactory(approval_key) + + def to_dict(self) -> dict: + result: dict = {} + result["kind"] = self.kind + if self.approval_key is not None: + result["approvalKey"] = from_union([from_str, from_none], self.approval_key) + return result + +# Experimental: this type is part of an experimental API and may change or be removed. +@dataclass +class PermissionDecisionApproveForSessionApprovalFactory: + """Session-scoped factory approval, optionally narrowed by approval key.""" + + kind: ClassVar[str] = "factory" + """Approval covering factory operations.""" + + approval_key: str | None = None + """Optional factory operation name or canonical approval key; when omitted, the approval + covers all factory operations. + """ + + @staticmethod + def from_dict(obj: Any) -> 'PermissionDecisionApproveForSessionApprovalFactory': + assert isinstance(obj, dict) + approval_key = from_union([from_str, from_none], obj.get("approvalKey")) + return PermissionDecisionApproveForSessionApprovalFactory(approval_key) + + def to_dict(self) -> dict: + result: dict = {} + result["kind"] = self.kind + if self.approval_key is not None: + result["approvalKey"] = from_union([from_str, from_none], self.approval_key) + return result + +# Experimental: this type is part of an experimental API and may change or be removed. +@dataclass +class PermissionsLocationsAddToolApprovalDetailsFactory: + """Location-persisted factory approval, optionally narrowed by approval key.""" + + kind: ClassVar[str] = "factory" + """Approval covering factory operations.""" + + approval_key: str | None = None + """Optional factory operation name or canonical approval key; when omitted, the approval + covers all factory operations. + """ + + @staticmethod + def from_dict(obj: Any) -> 'PermissionsLocationsAddToolApprovalDetailsFactory': + assert isinstance(obj, dict) + approval_key = from_union([from_str, from_none], obj.get("approvalKey")) + return PermissionsLocationsAddToolApprovalDetailsFactory(approval_key) + + def to_dict(self) -> dict: + result: dict = {} + result["kind"] = self.kind + if self.approval_key is not None: + result["approvalKey"] = from_union([from_str, from_none], self.approval_key) + return result + # Experimental: this type is part of an experimental API and may change or be removed. @dataclass class PermissionDecisionApproveForLocationApprovalMCPSampling: @@ -22957,84 +23781,6 @@ def to_dict(self) -> dict: result["kind"] = self.kind return result -# Experimental: this type is part of an experimental API and may change or be removed. -@dataclass -class PermissionDecisionApproveForLocationApprovalWorkflow: - """Location-scoped workflow approval, optionally narrowed by approval key.""" - - kind: ClassVar[str] = "workflow" - """Approval covering workflow operations.""" - - approval_key: str | None = None - """Optional workflow operation name or canonical approval key; when omitted, the approval - covers all workflow operations. - """ - - @staticmethod - def from_dict(obj: Any) -> 'PermissionDecisionApproveForLocationApprovalWorkflow': - assert isinstance(obj, dict) - approval_key = from_union([from_str, from_none], obj.get("approvalKey")) - return PermissionDecisionApproveForLocationApprovalWorkflow(approval_key) - - def to_dict(self) -> dict: - result: dict = {} - result["kind"] = self.kind - if self.approval_key is not None: - result["approvalKey"] = from_union([from_str, from_none], self.approval_key) - return result - -# Experimental: this type is part of an experimental API and may change or be removed. -@dataclass -class PermissionDecisionApproveForSessionApprovalWorkflow: - """Session-scoped workflow approval, optionally narrowed by approval key.""" - - kind: ClassVar[str] = "workflow" - """Approval covering workflow operations.""" - - approval_key: str | None = None - """Optional workflow operation name or canonical approval key; when omitted, the approval - covers all workflow operations. - """ - - @staticmethod - def from_dict(obj: Any) -> 'PermissionDecisionApproveForSessionApprovalWorkflow': - assert isinstance(obj, dict) - approval_key = from_union([from_str, from_none], obj.get("approvalKey")) - return PermissionDecisionApproveForSessionApprovalWorkflow(approval_key) - - def to_dict(self) -> dict: - result: dict = {} - result["kind"] = self.kind - if self.approval_key is not None: - result["approvalKey"] = from_union([from_str, from_none], self.approval_key) - return result - -# Experimental: this type is part of an experimental API and may change or be removed. -@dataclass -class PermissionsLocationsAddToolApprovalDetailsWorkflow: - """Location-persisted workflow approval, optionally narrowed by approval key.""" - - kind: ClassVar[str] = "workflow" - """Approval covering workflow operations.""" - - approval_key: str | None = None - """Optional workflow operation name or canonical approval key; when omitted, the approval - covers all workflow operations. - """ - - @staticmethod - def from_dict(obj: Any) -> 'PermissionsLocationsAddToolApprovalDetailsWorkflow': - assert isinstance(obj, dict) - approval_key = from_union([from_str, from_none], obj.get("approvalKey")) - return PermissionsLocationsAddToolApprovalDetailsWorkflow(approval_key) - - def to_dict(self) -> dict: - result: dict = {} - result["kind"] = self.kind - if self.approval_key is not None: - result["approvalKey"] = from_union([from_str, from_none], self.approval_key) - return result - # Experimental: this type is part of an experimental API and may change or be removed. @dataclass class PermissionDecisionApproveForLocationApprovalWrite: @@ -24319,6 +25065,17 @@ def to_dict(self) -> dict: result["input"] = self.input return result +# Experimental: this type is part of an experimental API and may change or be removed. +class PauseCheckpointAction(Enum): + """Action the runtime selected for a durable factory pause checkpoint. + + Whether this execution attempt must pause or may continue. + + Action the runtime selected for a durable workflow pause checkpoint. + """ + CONTINUE = "continue" + PAUSE = "pause" + # Experimental: this type is part of an experimental API and may change or be removed. @dataclass class ModeSetRequest: @@ -24446,15 +25203,6 @@ def to_dict(self) -> dict: result["action"] = from_str(self.action) return result -# Experimental: this type is part of an experimental API and may change or be removed. -class WorkflowPauseCheckpointAction(Enum): - """Whether this execution attempt must pause or may continue. - - Action the runtime selected for a durable workflow pause checkpoint. - """ - CONTINUE = "continue" - PAUSE = "pause" - # Experimental: this type is part of an experimental API and may change or be removed. class UISessionLimitsExhaustedResponseAction(Enum): """Action selected by the user. @@ -27909,7 +28657,7 @@ class WorkflowRunFailure: type: WorkflowRunFailureType """Workflow failure variant discriminator.""" - kind: WorkflowRunFailureKind | None = None + kind: RunFailureKind | None = None """Resource ceiling that stopped the run.""" suggested_value: float | None = None @@ -27924,7 +28672,7 @@ class WorkflowRunFailure: code: str | None = None """Stable failure code.""" - operation: WorkflowDurableOperation | None = None + operation: DurableOperation | None = None """Execution-critical durable operation that failed.""" drained_nano_aiu: int | None = None @@ -27935,12 +28683,12 @@ def from_dict(obj: Any) -> 'WorkflowRunFailure': assert isinstance(obj, dict) run_id = from_str(obj.get("runId")) type = WorkflowRunFailureType(obj.get("type")) - kind = from_union([WorkflowRunFailureKind, from_none], obj.get("kind")) + kind = from_union([RunFailureKind, from_none], obj.get("kind")) suggested_value = from_union([from_float, from_none], obj.get("suggestedValue")) value = from_union([from_float, from_none], obj.get("value")) reason = from_union([from_str, from_none], obj.get("reason")) code = from_union([from_str, from_none], obj.get("code")) - operation = from_union([WorkflowDurableOperation, from_none], obj.get("operation")) + operation = from_union([DurableOperation, from_none], obj.get("operation")) drained_nano_aiu = from_union([from_int, from_none], obj.get("drainedNanoAiu")) return WorkflowRunFailure(run_id, type, kind, suggested_value, value, reason, code, operation, drained_nano_aiu) @@ -27949,7 +28697,7 @@ def to_dict(self) -> dict: result["runId"] = from_str(self.run_id) result["type"] = to_enum(WorkflowRunFailureType, self.type) if self.kind is not None: - result["kind"] = from_union([lambda x: to_enum(WorkflowRunFailureKind, x), from_none], self.kind) + result["kind"] = from_union([lambda x: to_enum(RunFailureKind, x), from_none], self.kind) if self.suggested_value is not None: result["suggestedValue"] = from_union([to_float, from_none], self.suggested_value) if self.value is not None: @@ -27959,219 +28707,11 @@ def to_dict(self) -> dict: if self.code is not None: result["code"] = from_union([from_str, from_none], self.code) if self.operation is not None: - result["operation"] = from_union([lambda x: to_enum(WorkflowDurableOperation, x), from_none], self.operation) + result["operation"] = from_union([lambda x: to_enum(DurableOperation, x), from_none], self.operation) if self.drained_nano_aiu is not None: result["drainedNanoAiu"] = from_union([from_int, from_none], self.drained_nano_aiu) return result -@dataclass -class PauseInfoClass: - type: PauseInfoType - """Workflow pause initiator discriminator.""" - - key: str | None = None - """Stable author-defined checkpoint key that initiated the pause.""" - - @staticmethod - def from_dict(obj: Any) -> 'PauseInfoClass': - assert isinstance(obj, dict) - type = PauseInfoType(obj.get("type")) - key = from_union([from_str, from_none], obj.get("key")) - return PauseInfoClass(type, key) - - def to_dict(self) -> dict: - result: dict = {} - result["type"] = to_enum(PauseInfoType, self.type) - if self.key is not None: - result["key"] = from_union([from_str, from_none], self.key) - return result - -# Experimental: this type is part of an experimental API and may change or be removed. -@dataclass -class WorkflowPauseInfo: - """Durable metadata describing who initiated a workflow pause. - - Structured pause initiator metadata for a paused attempt. - """ - type: PauseInfoType - """Workflow pause initiator discriminator.""" - - key: str | None = None - """Stable author-defined checkpoint key that initiated the pause.""" - - @staticmethod - def from_dict(obj: Any) -> 'WorkflowPauseInfo': - assert isinstance(obj, dict) - type = PauseInfoType(obj.get("type")) - key = from_union([from_str, from_none], obj.get("key")) - return WorkflowPauseInfo(type, key) - - def to_dict(self) -> dict: - result: dict = {} - result["type"] = to_enum(PauseInfoType, self.type) - if self.key is not None: - result["key"] = from_union([from_str, from_none], self.key) - return result - -# Experimental: this type is part of an experimental API and may change or be removed. -@dataclass -class WorkflowLogLine: - """One ordered workflow progress line.""" - - kind: WorkflowLogLineKind - """Progress line kind.""" - - seq: int - """Monotonic sequence number within the workflow run.""" - - text: str - """Progress text.""" - - @staticmethod - def from_dict(obj: Any) -> 'WorkflowLogLine': - assert isinstance(obj, dict) - kind = WorkflowLogLineKind(obj.get("kind")) - seq = from_int(obj.get("seq")) - text = from_str(obj.get("text")) - return WorkflowLogLine(kind, seq, text) - - def to_dict(self) -> dict: - result: dict = {} - result["kind"] = to_enum(WorkflowLogLineKind, self.kind) - result["seq"] = from_int(self.seq) - result["text"] = from_str(self.text) - return result - -# Experimental: this type is part of an experimental API and may change or be removed. -@dataclass -class WorkflowProgressLine: - """One durable workflow progress record.""" - - attempt: int - """Resume attempt that emitted this record.""" - - kind: WorkflowLogLineKind - """Progress record kind.""" - - recorded_at: int - """Epoch milliseconds when the record was persisted.""" - - seq: int - """Global monotonic sequence number within the run.""" - - text: str - """Prompt-safe progress text.""" - - phase_id: str | None = None - """Phase active when the record was emitted, or null before any phase.""" - - @staticmethod - def from_dict(obj: Any) -> 'WorkflowProgressLine': - assert isinstance(obj, dict) - attempt = from_int(obj.get("attempt")) - kind = WorkflowLogLineKind(obj.get("kind")) - recorded_at = from_int(obj.get("recordedAt")) - seq = from_int(obj.get("seq")) - text = from_str(obj.get("text")) - phase_id = from_union([from_none, from_str], obj.get("phaseId")) - return WorkflowProgressLine(attempt, kind, recorded_at, seq, text, phase_id) - - def to_dict(self) -> dict: - result: dict = {} - result["attempt"] = from_int(self.attempt) - result["kind"] = to_enum(WorkflowLogLineKind, self.kind) - result["recordedAt"] = from_int(self.recorded_at) - result["seq"] = from_int(self.seq) - result["text"] = from_str(self.text) - result["phaseId"] = from_union([from_none, from_str], self.phase_id) - return result - -# Experimental: this type is part of an experimental API and may change or be removed. -@dataclass -class WorkflowPhaseObservation: - """Durable lifecycle and timing for one workflow phase.""" - - accumulated_active_ms: int - """Completed active time accumulated by this phase in milliseconds.""" - - current_active_ms: int - """Current live active time for this phase in milliseconds.""" - - entry_count: int - """Number of times execution entered this phase.""" - - id: str - """Phase identifier.""" - - last_entered_run_attempt: int - """Most recent run attempt that entered this phase, or `0` if the phase has never been - entered. - """ - live_agent_count: int - """Direct agents in this phase that are currently live.""" - - status: WorkflowPhaseStatus - """Derived lifecycle state of the phase.""" - - title: str - """Human-readable phase title.""" - - total_agent_count: int - """Total direct agents associated with this phase.""" - - completed_at: int | None = None - """Epoch milliseconds when this phase completed; for a skipped phase, the synthetic skip - timestamp (equal to `startedAt`). - """ - detail: str | None = None - """Optional human-readable phase detail.""" - - ordinal: int | None = None - """Zero-based declared phase ordinal, or null for an undeclared phase.""" - - started_at: int | None = None - """Epoch milliseconds when this phase first started; for a skipped phase, the synthetic skip - timestamp (equal to `completedAt`). - """ - - @staticmethod - def from_dict(obj: Any) -> 'WorkflowPhaseObservation': - assert isinstance(obj, dict) - accumulated_active_ms = from_int(obj.get("accumulatedActiveMs")) - current_active_ms = from_int(obj.get("currentActiveMs")) - entry_count = from_int(obj.get("entryCount")) - id = from_str(obj.get("id")) - last_entered_run_attempt = from_int(obj.get("lastEnteredRunAttempt")) - live_agent_count = from_int(obj.get("liveAgentCount")) - status = WorkflowPhaseStatus(obj.get("status")) - title = from_str(obj.get("title")) - total_agent_count = from_int(obj.get("totalAgentCount")) - completed_at = from_union([from_int, from_none], obj.get("completedAt")) - detail = from_union([from_str, from_none], obj.get("detail")) - ordinal = from_union([from_none, from_int], obj.get("ordinal")) - started_at = from_union([from_int, from_none], obj.get("startedAt")) - return WorkflowPhaseObservation(accumulated_active_ms, current_active_ms, entry_count, id, last_entered_run_attempt, live_agent_count, status, title, total_agent_count, completed_at, detail, ordinal, started_at) - - def to_dict(self) -> dict: - result: dict = {} - result["accumulatedActiveMs"] = from_int(self.accumulated_active_ms) - result["currentActiveMs"] = from_int(self.current_active_ms) - result["entryCount"] = from_int(self.entry_count) - result["id"] = from_str(self.id) - result["lastEnteredRunAttempt"] = from_int(self.last_entered_run_attempt) - result["liveAgentCount"] = from_int(self.live_agent_count) - result["status"] = to_enum(WorkflowPhaseStatus, self.status) - result["title"] = from_str(self.title) - result["totalAgentCount"] = from_int(self.total_agent_count) - if self.completed_at is not None: - result["completedAt"] = from_union([from_int, from_none], self.completed_at) - if self.detail is not None: - result["detail"] = from_union([from_str, from_none], self.detail) - result["ordinal"] = from_union([from_none, from_int], self.ordinal) - if self.started_at is not None: - result["startedAt"] = from_union([from_int, from_none], self.started_at) - return result - # Experimental: this type is part of an experimental API and may change or be removed. @dataclass class WorkflowResumeRequest: @@ -28370,126 +28910,6 @@ def to_dict(self) -> dict: result["saved"] = from_union([lambda x: to_class(Saved, x), from_none], self.saved) return result -# Experimental: this type is part of an experimental API and may change or be removed. -@dataclass -class AuthReadValue: - """The read result, keyed by the same selector as the query.""" - - kind: AuthReadQueryKind - """Account read-datum variant discriminator.""" - - account: AccountStatus | None = None - """The active account, or absent when not logged in.""" - - status: AuthStatusDto | None = None - """The neutral authentication status summary.""" - - errors: list[AuthValidationError] | None = None - """Validation errors from the most recent authentication attempt.""" - - @staticmethod - def from_dict(obj: Any) -> 'AuthReadValue': - assert isinstance(obj, dict) - kind = AuthReadQueryKind(obj.get("kind")) - account = from_union([AccountStatus.from_dict, from_none], obj.get("account")) - status = from_union([AuthStatusDto.from_dict, from_none], obj.get("status")) - errors = from_union([lambda x: from_list(AuthValidationError.from_dict, x), from_none], obj.get("errors")) - return AuthReadValue(kind, account, status, errors) - - def to_dict(self) -> dict: - result: dict = {} - result["kind"] = to_enum(AuthReadQueryKind, self.kind) - if self.account is not None: - result["account"] = from_union([lambda x: to_class(AccountStatus, x), from_none], self.account) - if self.status is not None: - result["status"] = from_union([lambda x: to_class(AuthStatusDto, x), from_none], self.status) - if self.errors is not None: - result["errors"] = from_union([lambda x: from_list(lambda x: to_class(AuthValidationError, x), x), from_none], self.errors) - return result - -# Experimental: this type is part of an experimental API and may change or be removed. -@dataclass -class AuthEnumerateValue: - """The enumerated collection, keyed by the same selector as the query.""" - - items: list[ItemElement] - """The signed-in account forest; empty when not logged in. - - The providers offered for interactive login. - """ - kind: AuthEnumerateQueryKind - """Enumerated account-collection variant discriminator.""" - - @staticmethod - def from_dict(obj: Any) -> 'AuthEnumerateValue': - assert isinstance(obj, dict) - items = from_list(ItemElement.from_dict, obj.get("items")) - kind = AuthEnumerateQueryKind(obj.get("kind")) - return AuthEnumerateValue(items, kind) - - def to_dict(self) -> dict: - result: dict = {} - result["items"] = from_list(lambda x: to_class(ItemElement, x), self.items) - result["kind"] = to_enum(AuthEnumerateQueryKind, self.kind) - return result - -# Experimental: this type is part of an experimental API and may change or be removed. -@dataclass -class AccountsEnumerateRequest: - """Enumerate request carrying the typed collection query.""" - - query: AuthEnumerateQuery - """Which typed accounts collection to enumerate.""" - - @staticmethod - def from_dict(obj: Any) -> 'AccountsEnumerateRequest': - assert isinstance(obj, dict) - query = AuthEnumerateQuery.from_dict(obj.get("query")) - return AccountsEnumerateRequest(query) - - def to_dict(self) -> dict: - result: dict = {} - result["query"] = to_class(AuthEnumerateQuery, self.query) - return result - -# Experimental: this type is part of an experimental API and may change or be removed. -@dataclass -class AccountsGetRequest: - """Read request carrying the typed datum query.""" - - query: AuthReadQuery - """Which typed accounts datum to read.""" - - @staticmethod - def from_dict(obj: Any) -> 'AccountsGetRequest': - assert isinstance(obj, dict) - query = AuthReadQuery.from_dict(obj.get("query")) - return AccountsGetRequest(query) - - def to_dict(self) -> dict: - result: dict = {} - result["query"] = to_class(AuthReadQuery, self.query) - return result - -# Experimental: this type is part of an experimental API and may change or be removed. -@dataclass -class AccountsSetRequest: - """Mutation request carrying the typed write command.""" - - command: AuthWrite - """The non-interactive mutation command to apply.""" - - @staticmethod - def from_dict(obj: Any) -> 'AccountsSetRequest': - assert isinstance(obj, dict) - command = AuthWrite.from_dict(obj.get("command")) - return AccountsSetRequest(command) - - def to_dict(self) -> dict: - result: dict = {} - result["command"] = to_class(AuthWrite, self.command) - return result - # Experimental: this type is part of an experimental API and may change or be removed. @dataclass class AgentDiscoveryPathList: @@ -28558,6 +28978,35 @@ def to_dict(self) -> dict: result["state"] = from_union([lambda x: to_class(AutopilotObjectiveState, x), from_none], self.state) return result +# Experimental: this type is part of an experimental API and may change or be removed. +# Internal: this type is an internal SDK API and is not part of the public surface. +@dataclass +class _FactoryToolRunOptions: + """Options for an internal tool-originated factory invocation. + + Tool-originated factory invocation options. + """ + limits: FactoryRunLimits | None = None + """Per-invocation resource ceiling overrides.""" + + resume_from_run_id: str | None = None + """Run identifier whose journal and progress should seed this resumed run.""" + + @staticmethod + def from_dict(obj: Any) -> '_FactoryToolRunOptions': + assert isinstance(obj, dict) + limits = from_union([FactoryRunLimits.from_dict, from_none], obj.get("limits")) + resume_from_run_id = from_union([from_str, from_none], obj.get("resumeFromRunId")) + return _FactoryToolRunOptions(limits, resume_from_run_id) + + def to_dict(self) -> dict: + result: dict = {} + if self.limits is not None: + result["limits"] = from_union([lambda x: to_class(FactoryRunLimits, x), from_none], self.limits) + if self.resume_from_run_id is not None: + result["resumeFromRunId"] = from_union([from_str, from_none], self.resume_from_run_id) + return result + # Experimental: this type is part of an experimental API and may change or be removed. @dataclass class MCPAppsCallToolRequest: @@ -29370,56 +29819,6 @@ def to_dict(self) -> dict: result["revision"] = from_int(self.revision) return result -# Experimental: this type is part of an experimental API and may change or be removed. -@dataclass -class AuthLoginStep: - """The first step of the flow. - - One step in an interactive login flow. The consumer acts on the step and calls advance to - proceed. Browser-open is encoded as two distinct steps by design: `open-url` is - CONSUMER-driven (the provider surfaces the authorize URL and the consumer opens it — - github.com/GHEC web), while `needs-interaction` is PROVIDER-driven (the provider opens - the browser or broker UI itself and does not surface a URL — Entra). - """ - kind: AuthLoginStepKind - """Login flow step variant discriminator.""" - - url: str | None = None - """Authorize URL the consumer should open in a browser (consumer-driven browser-open).""" - - prompt: str | None = None - """Prompt for the value the provider needs; the consumer supplies it as advance input (e.g. - a GitHub Enterprise Cloud host, *.ghe.com). - """ - result: AuthLoginResultDto | None = None - """The terminal login result.""" - - message: str | None = None - """Human-readable failure message.""" - - @staticmethod - def from_dict(obj: Any) -> 'AuthLoginStep': - assert isinstance(obj, dict) - kind = AuthLoginStepKind(obj.get("kind")) - url = from_union([from_str, from_none], obj.get("url")) - prompt = from_union([from_str, from_none], obj.get("prompt")) - result = from_union([AuthLoginResultDto.from_dict, from_none], obj.get("result")) - message = from_union([from_str, from_none], obj.get("message")) - return AuthLoginStep(kind, url, prompt, result, message) - - def to_dict(self) -> dict: - result: dict = {} - result["kind"] = to_enum(AuthLoginStepKind, self.kind) - if self.url is not None: - result["url"] = from_union([from_str, from_none], self.url) - if self.prompt is not None: - result["prompt"] = from_union([from_str, from_none], self.prompt) - if self.result is not None: - result["result"] = from_union([lambda x: to_class(AuthLoginResultDto, x), from_none], self.result) - if self.message is not None: - result["message"] = from_union([from_str, from_none], self.message) - return result - # Experimental: this type is part of an experimental API and may change or be removed. @dataclass class CanvasHostContext: @@ -30220,6 +30619,298 @@ def to_dict(self) -> dict: result["title"] = from_union([from_str, from_none], self.title) return result +# Experimental: this type is part of an experimental API and may change or be removed. +@dataclass +class FactoryRunTerminal: + """Prompt-safe terminal factory outcome.""" + + error: str | None = None + """Human-readable terminal error.""" + + failure: FactoryRunFailure | None = None + """Machine-readable terminal failure.""" + + pause_info: PauseInfoClass | None = None + """Pause initiator metadata, or null when the run did not pause.""" + + reason: str | None = None + """Human-readable terminal reason.""" + + result_preview: str | None = None + """Prompt-safe preview of the completed result.""" + + @staticmethod + def from_dict(obj: Any) -> 'FactoryRunTerminal': + assert isinstance(obj, dict) + error = from_union([from_str, from_none], obj.get("error")) + failure = from_union([FactoryRunFailure.from_dict, from_none], obj.get("failure")) + pause_info = from_union([PauseInfoClass.from_dict, from_none], obj.get("pauseInfo")) + reason = from_union([from_str, from_none], obj.get("reason")) + result_preview = from_union([from_str, from_none], obj.get("resultPreview")) + return FactoryRunTerminal(error, failure, pause_info, reason, result_preview) + + def to_dict(self) -> dict: + result: dict = {} + if self.error is not None: + result["error"] = from_union([from_str, from_none], self.error) + if self.failure is not None: + result["failure"] = from_union([lambda x: to_class(FactoryRunFailure, x), from_none], self.failure) + result["pauseInfo"] = from_union([lambda x: to_class(PauseInfoClass, x), from_none], self.pause_info) + if self.reason is not None: + result["reason"] = from_union([from_str, from_none], self.reason) + if self.result_preview is not None: + result["resultPreview"] = from_union([from_str, from_none], self.result_preview) + return result + +# Experimental: this type is part of an experimental API and may change or be removed. +@dataclass +class FactoryRunResult: + """Terminal resumed run envelope. + + Complete current or terminal factory run envelope. + """ + run_id: str + """Factory run identifier.""" + + status: RunStatus + """Current or terminal factory run status.""" + + attempt: int | None = None + """One-based execution attempt represented by this envelope. Absent before the first attempt + starts or when returned by an older runtime. + """ + error: str | None = None + """Error message for an errored run.""" + + failure: FactoryRunFailure | None = None + """Machine-readable failure details for a halted or errored run.""" + + pause_info: FactoryPauseInfo | None = None + """Structured pause initiator metadata for a paused attempt.""" + + reason: str | None = None + """Reason for a halted or cancelled run.""" + + result: Any = None + """Completed factory result.""" + + snapshot: Any = None + """Partial journal and progress snapshot for a halted, cancelled, or errored run.""" + + @staticmethod + def from_dict(obj: Any) -> 'FactoryRunResult': + assert isinstance(obj, dict) + run_id = from_str(obj.get("runId")) + status = RunStatus(obj.get("status")) + attempt = from_union([from_int, from_none], obj.get("attempt")) + error = from_union([from_str, from_none], obj.get("error")) + failure = from_union([FactoryRunFailure.from_dict, from_none], obj.get("failure")) + pause_info = from_union([FactoryPauseInfo.from_dict, from_none], obj.get("pauseInfo")) + reason = from_union([from_str, from_none], obj.get("reason")) + result = obj.get("result") + snapshot = obj.get("snapshot") + return FactoryRunResult(run_id, status, attempt, error, failure, pause_info, reason, result, snapshot) + + def to_dict(self) -> dict: + result: dict = {} + result["runId"] = from_str(self.run_id) + result["status"] = to_enum(RunStatus, self.status) + if self.attempt is not None: + result["attempt"] = from_union([from_int, from_none], self.attempt) + if self.error is not None: + result["error"] = from_union([from_str, from_none], self.error) + if self.failure is not None: + result["failure"] = from_union([lambda x: to_class(FactoryRunFailure, x), from_none], self.failure) + if self.pause_info is not None: + result["pauseInfo"] = from_union([lambda x: to_class(FactoryPauseInfo, x), from_none], self.pause_info) + if self.reason is not None: + result["reason"] = from_union([from_str, from_none], self.reason) + if self.result is not None: + result["result"] = self.result + if self.snapshot is not None: + result["snapshot"] = self.snapshot + return result + +# Experimental: this type is part of an experimental API and may change or be removed. +@dataclass +class FactoryLogRequest: + """Parameters for recording factory progress.""" + + execution_token: str + """Opaque token identifying the current factory execution attempt.""" + + lines: list[FactoryLogLine] + """Ordered progress lines to append.""" + + run_id: str + """Factory run identifier.""" + + @staticmethod + def from_dict(obj: Any) -> 'FactoryLogRequest': + assert isinstance(obj, dict) + execution_token = from_str(obj.get("executionToken")) + lines = from_list(FactoryLogLine.from_dict, obj.get("lines")) + run_id = from_str(obj.get("runId")) + return FactoryLogRequest(execution_token, lines, run_id) + + def to_dict(self) -> dict: + result: dict = {} + result["executionToken"] = from_str(self.execution_token) + result["lines"] = from_list(lambda x: to_class(FactoryLogLine, x), self.lines) + result["runId"] = from_str(self.run_id) + return result + +# Experimental: this type is part of an experimental API and may change or be removed. +@dataclass +class FactoryProgressPage: + """A bidirectional page of factory progress. + + Bidirectional page of durable factory progress. + """ + has_more_newer: bool + """Whether progress records newer than this page exist.""" + + has_more_older: bool + """Whether progress records older than this page exist.""" + + records: list[FactoryProgressLine] + """Progress records in sequence order.""" + + revision: int + """Run revision reflected by this page.""" + + newest_seq: int | None = None + """Newest sequence number in this page, or null when empty.""" + + oldest_seq: int | None = None + """Oldest sequence number in this page, or null when empty.""" + + @staticmethod + def from_dict(obj: Any) -> 'FactoryProgressPage': + assert isinstance(obj, dict) + has_more_newer = from_bool(obj.get("hasMoreNewer")) + has_more_older = from_bool(obj.get("hasMoreOlder")) + records = from_list(FactoryProgressLine.from_dict, obj.get("records")) + revision = from_int(obj.get("revision")) + newest_seq = from_union([from_int, from_none], obj.get("newestSeq")) + oldest_seq = from_union([from_int, from_none], obj.get("oldestSeq")) + return FactoryProgressPage(has_more_newer, has_more_older, records, revision, newest_seq, oldest_seq) + + def to_dict(self) -> dict: + result: dict = {} + result["hasMoreNewer"] = from_bool(self.has_more_newer) + result["hasMoreOlder"] = from_bool(self.has_more_older) + result["records"] = from_list(lambda x: to_class(FactoryProgressLine, x), self.records) + result["revision"] = from_int(self.revision) + result["newestSeq"] = from_union([from_int, from_none], self.newest_seq) + result["oldestSeq"] = from_union([from_int, from_none], self.oldest_seq) + return result + +# Experimental: this type is part of an experimental API and may change or be removed. +@dataclass +class WorkflowLogRequest: + """Parameters for recording workflow progress.""" + + execution_token: str + """Opaque token identifying the current workflow execution attempt.""" + + lines: list[WorkflowLogLine] + """Ordered progress lines to append.""" + + run_id: str + """Workflow run identifier.""" + + @staticmethod + def from_dict(obj: Any) -> 'WorkflowLogRequest': + assert isinstance(obj, dict) + execution_token = from_str(obj.get("executionToken")) + lines = from_list(WorkflowLogLine.from_dict, obj.get("lines")) + run_id = from_str(obj.get("runId")) + return WorkflowLogRequest(execution_token, lines, run_id) + + def to_dict(self) -> dict: + result: dict = {} + result["executionToken"] = from_str(self.execution_token) + result["lines"] = from_list(lambda x: to_class(WorkflowLogLine, x), self.lines) + result["runId"] = from_str(self.run_id) + return result + +# Experimental: this type is part of an experimental API and may change or be removed. +@dataclass +class WorkflowProgressPage: + """A bidirectional page of workflow progress. + + Bidirectional page of durable workflow progress. + """ + has_more_newer: bool + """Whether progress records newer than this page exist.""" + + has_more_older: bool + """Whether progress records older than this page exist.""" + + records: list[WorkflowProgressLine] + """Progress records in sequence order.""" + + revision: int + """Run revision reflected by this page.""" + + newest_seq: int | None = None + """Newest sequence number in this page, or null when empty.""" + + oldest_seq: int | None = None + """Oldest sequence number in this page, or null when empty.""" + + @staticmethod + def from_dict(obj: Any) -> 'WorkflowProgressPage': + assert isinstance(obj, dict) + has_more_newer = from_bool(obj.get("hasMoreNewer")) + has_more_older = from_bool(obj.get("hasMoreOlder")) + records = from_list(WorkflowProgressLine.from_dict, obj.get("records")) + revision = from_int(obj.get("revision")) + newest_seq = from_union([from_int, from_none], obj.get("newestSeq")) + oldest_seq = from_union([from_int, from_none], obj.get("oldestSeq")) + return WorkflowProgressPage(has_more_newer, has_more_older, records, revision, newest_seq, oldest_seq) + + def to_dict(self) -> dict: + result: dict = {} + result["hasMoreNewer"] = from_bool(self.has_more_newer) + result["hasMoreOlder"] = from_bool(self.has_more_older) + result["records"] = from_list(lambda x: to_class(WorkflowProgressLine, x), self.records) + result["revision"] = from_int(self.revision) + result["newestSeq"] = from_union([from_int, from_none], self.newest_seq) + result["oldestSeq"] = from_union([from_int, from_none], self.oldest_seq) + return result + +# Experimental: this type is part of an experimental API and may change or be removed. +@dataclass +class FactoryRunRequest: + """Parameters for invoking a registered factory.""" + + args: Any + """Factory input value.""" + + name: str + """Registered factory name.""" + + options: RunOptions | None = None + """Factory invocation options.""" + + @staticmethod + def from_dict(obj: Any) -> 'FactoryRunRequest': + assert isinstance(obj, dict) + args = obj.get("args") + name = from_str(obj.get("name")) + options = from_union([RunOptions.from_dict, from_none], obj.get("options")) + return FactoryRunRequest(args, name, options) + + def to_dict(self) -> dict: + result: dict = {} + result["args"] = self.args + result["name"] = from_str(self.name) + if self.options is not None: + result["options"] = from_union([lambda x: to_class(RunOptions, x), from_none], self.options) + return result + # Experimental: this type is part of an experimental API and may change or be removed. @dataclass class MCPOauthHandlePendingRequest: @@ -31820,38 +32511,72 @@ def to_dict(self) -> dict: result["inputSchema"] = self.input_schema return result +# Experimental: this type is part of an experimental API and may change or be removed. +@dataclass +class FactoryPauseCheckpointResult: + action: PauseCheckpointAction + """Whether this execution attempt must pause or may continue.""" + + @staticmethod + def from_dict(obj: Any) -> 'FactoryPauseCheckpointResult': + assert isinstance(obj, dict) + action = PauseCheckpointAction(obj.get("action")) + return FactoryPauseCheckpointResult(action) + + def to_dict(self) -> dict: + result: dict = {} + result["action"] = to_enum(PauseCheckpointAction, self.action) + return result + +# Experimental: this type is part of an experimental API and may change or be removed. +@dataclass +class SessionFactoryPauseAtCheckpointResult: + action: PauseCheckpointAction + """Whether this execution attempt must pause or may continue.""" + + @staticmethod + def from_dict(obj: Any) -> 'SessionFactoryPauseAtCheckpointResult': + assert isinstance(obj, dict) + action = PauseCheckpointAction(obj.get("action")) + return SessionFactoryPauseAtCheckpointResult(action) + + def to_dict(self) -> dict: + result: dict = {} + result["action"] = to_enum(PauseCheckpointAction, self.action) + return result + # Experimental: this type is part of an experimental API and may change or be removed. @dataclass class SessionWorkflowPauseAtCheckpointResult: - action: WorkflowPauseCheckpointAction + action: PauseCheckpointAction """Whether this execution attempt must pause or may continue.""" @staticmethod def from_dict(obj: Any) -> 'SessionWorkflowPauseAtCheckpointResult': assert isinstance(obj, dict) - action = WorkflowPauseCheckpointAction(obj.get("action")) + action = PauseCheckpointAction(obj.get("action")) return SessionWorkflowPauseAtCheckpointResult(action) def to_dict(self) -> dict: result: dict = {} - result["action"] = to_enum(WorkflowPauseCheckpointAction, self.action) + result["action"] = to_enum(PauseCheckpointAction, self.action) return result # Experimental: this type is part of an experimental API and may change or be removed. @dataclass class WorkflowPauseCheckpointResult: - action: WorkflowPauseCheckpointAction + action: PauseCheckpointAction """Whether this execution attempt must pause or may continue.""" @staticmethod def from_dict(obj: Any) -> 'WorkflowPauseCheckpointResult': assert isinstance(obj, dict) - action = WorkflowPauseCheckpointAction(obj.get("action")) + action = PauseCheckpointAction(obj.get("action")) return WorkflowPauseCheckpointResult(action) def to_dict(self) -> dict: result: dict = {} - result["action"] = to_enum(WorkflowPauseCheckpointAction, self.action) + result["action"] = to_enum(PauseCheckpointAction, self.action) return result # Experimental: this type is part of an experimental API and may change or be removed. @@ -33411,7 +34136,7 @@ class WorkflowRunTerminal: failure: WorkflowRunFailure | None = None """Machine-readable terminal failure.""" - pause_info: PauseInfoClass | None = None + pause_info: WorkflowRunTerminalPauseInfo | None = None """Pause initiator metadata, or null when the run did not pause.""" reason: str | None = None @@ -33425,7 +34150,7 @@ def from_dict(obj: Any) -> 'WorkflowRunTerminal': assert isinstance(obj, dict) error = from_union([from_str, from_none], obj.get("error")) failure = from_union([WorkflowRunFailure.from_dict, from_none], obj.get("failure")) - pause_info = from_union([PauseInfoClass.from_dict, from_none], obj.get("pauseInfo")) + pause_info = from_union([WorkflowRunTerminalPauseInfo.from_dict, from_none], obj.get("pauseInfo")) reason = from_union([from_str, from_none], obj.get("reason")) result_preview = from_union([from_str, from_none], obj.get("resultPreview")) return WorkflowRunTerminal(error, failure, pause_info, reason, result_preview) @@ -33436,7 +34161,7 @@ def to_dict(self) -> dict: result["error"] = from_union([from_str, from_none], self.error) if self.failure is not None: result["failure"] = from_union([lambda x: to_class(WorkflowRunFailure, x), from_none], self.failure) - result["pauseInfo"] = from_union([lambda x: to_class(PauseInfoClass, x), from_none], self.pause_info) + result["pauseInfo"] = from_union([lambda x: to_class(WorkflowRunTerminalPauseInfo, x), from_none], self.pause_info) if self.reason is not None: result["reason"] = from_union([from_str, from_none], self.reason) if self.result_preview is not None: @@ -33453,7 +34178,7 @@ class WorkflowRunResult: run_id: str """Workflow run identifier.""" - status: WorkflowRunStatus + status: RunStatus """Current or terminal workflow run status.""" attempt: int | None = None @@ -33482,7 +34207,7 @@ class WorkflowRunResult: def from_dict(obj: Any) -> 'WorkflowRunResult': assert isinstance(obj, dict) run_id = from_str(obj.get("runId")) - status = WorkflowRunStatus(obj.get("status")) + status = RunStatus(obj.get("status")) attempt = from_union([from_int, from_none], obj.get("attempt")) error = from_union([from_str, from_none], obj.get("error")) failure = from_union([WorkflowRunFailure.from_dict, from_none], obj.get("failure")) @@ -33495,7 +34220,7 @@ def from_dict(obj: Any) -> 'WorkflowRunResult': def to_dict(self) -> dict: result: dict = {} result["runId"] = from_str(self.run_id) - result["status"] = to_enum(WorkflowRunStatus, self.status) + result["status"] = to_enum(RunStatus, self.status) if self.attempt is not None: result["attempt"] = from_union([from_int, from_none], self.attempt) if self.error is not None: @@ -33512,81 +34237,6 @@ def to_dict(self) -> dict: result["snapshot"] = self.snapshot return result -# Experimental: this type is part of an experimental API and may change or be removed. -@dataclass -class WorkflowLogRequest: - """Parameters for recording workflow progress.""" - - execution_token: str - """Opaque token identifying the current workflow execution attempt.""" - - lines: list[WorkflowLogLine] - """Ordered progress lines to append.""" - - run_id: str - """Workflow run identifier.""" - - @staticmethod - def from_dict(obj: Any) -> 'WorkflowLogRequest': - assert isinstance(obj, dict) - execution_token = from_str(obj.get("executionToken")) - lines = from_list(WorkflowLogLine.from_dict, obj.get("lines")) - run_id = from_str(obj.get("runId")) - return WorkflowLogRequest(execution_token, lines, run_id) - - def to_dict(self) -> dict: - result: dict = {} - result["executionToken"] = from_str(self.execution_token) - result["lines"] = from_list(lambda x: to_class(WorkflowLogLine, x), self.lines) - result["runId"] = from_str(self.run_id) - return result - -# Experimental: this type is part of an experimental API and may change or be removed. -@dataclass -class WorkflowProgressPage: - """A bidirectional page of workflow progress. - - Bidirectional page of durable workflow progress. - """ - has_more_newer: bool - """Whether progress records newer than this page exist.""" - - has_more_older: bool - """Whether progress records older than this page exist.""" - - records: list[WorkflowProgressLine] - """Progress records in sequence order.""" - - revision: int - """Run revision reflected by this page.""" - - newest_seq: int | None = None - """Newest sequence number in this page, or null when empty.""" - - oldest_seq: int | None = None - """Oldest sequence number in this page, or null when empty.""" - - @staticmethod - def from_dict(obj: Any) -> 'WorkflowProgressPage': - assert isinstance(obj, dict) - has_more_newer = from_bool(obj.get("hasMoreNewer")) - has_more_older = from_bool(obj.get("hasMoreOlder")) - records = from_list(WorkflowProgressLine.from_dict, obj.get("records")) - revision = from_int(obj.get("revision")) - newest_seq = from_union([from_int, from_none], obj.get("newestSeq")) - oldest_seq = from_union([from_int, from_none], obj.get("oldestSeq")) - return WorkflowProgressPage(has_more_newer, has_more_older, records, revision, newest_seq, oldest_seq) - - def to_dict(self) -> dict: - result: dict = {} - result["hasMoreNewer"] = from_bool(self.has_more_newer) - result["hasMoreOlder"] = from_bool(self.has_more_older) - result["records"] = from_list(lambda x: to_class(WorkflowProgressLine, x), self.records) - result["revision"] = from_int(self.revision) - result["newestSeq"] = from_union([from_int, from_none], self.newest_seq) - result["oldestSeq"] = from_union([from_int, from_none], self.oldest_seq) - return result - # Experimental: this type is part of an experimental API and may change or be removed. @dataclass class WorkflowRunRequest: @@ -33671,6 +34321,43 @@ def to_dict(self) -> dict: result["unavailableReason"] = from_union([lambda x: to_enum(HistoryRewindUnavailableReason, x), from_none], self.unavailable_reason) return result +# Experimental: this type is part of an experimental API and may change or be removed. +# Internal: this type is an internal SDK API and is not part of the public surface. +@dataclass +class _FactoryToolRunRequest: + """Internal parameters for invoking a registered factory from a tool.""" + + args: Any + """Factory input value.""" + + name: str + """Registered factory name.""" + + options: _FactoryToolRunOptions | None = None + """Tool-originated factory invocation options.""" + + tool_call_id: str | None = None + """Opaque identifier of the originating tool call.""" + + @staticmethod + def from_dict(obj: Any) -> '_FactoryToolRunRequest': + assert isinstance(obj, dict) + args = obj.get("args") + name = from_str(obj.get("name")) + options = from_union([_FactoryToolRunOptions.from_dict, from_none], obj.get("options")) + tool_call_id = from_union([from_str, from_none], obj.get("toolCallId")) + return _FactoryToolRunRequest(args, name, options, tool_call_id) + + def to_dict(self) -> dict: + result: dict = {} + result["args"] = self.args + result["name"] = from_str(self.name) + if self.options is not None: + result["options"] = from_union([lambda x: to_class(_FactoryToolRunOptions, x), from_none], self.options) + if self.tool_call_id is not None: + result["toolCallId"] = from_union([from_str, from_none], self.tool_call_id) + return result + # Experimental: this type is part of an experimental API and may change or be removed. @dataclass class MCPSerializableServerConfig: @@ -34568,30 +35255,6 @@ def to_dict(self) -> dict: result["catalog"] = from_union([lambda x: to_class(ConnectorCatalogResult, x), from_none], self.catalog) return result -# Experimental: this type is part of an experimental API and may change or be removed. -@dataclass -class AuthLoginBegun: - """A started login flow: its opaque id and first step.""" - - flow_id: str - """Opaque flow id used to advance or cancel this login.""" - - step: AuthLoginStep - """The first step of the flow.""" - - @staticmethod - def from_dict(obj: Any) -> 'AuthLoginBegun': - assert isinstance(obj, dict) - flow_id = from_str(obj.get("flowId")) - step = AuthLoginStep.from_dict(obj.get("step")) - return AuthLoginBegun(flow_id, step) - - def to_dict(self) -> dict: - result: dict = {} - result["flowId"] = from_str(self.flow_id) - result["step"] = to_class(AuthLoginStep, self.step) - return result - # Experimental: this type is part of an experimental API and may change or be removed. @dataclass class CanvasProviderCloseRequest: @@ -34842,6 +35505,144 @@ def to_dict(self) -> dict: result["toolArgs"] = self.tool_args return result +# Experimental: this type is part of an experimental API and may change or be removed. +@dataclass +class FactoryRunSummary: + """Durable factory run summary with read-time live overlays.""" + + can_resume: bool + """Whether the durable run state currently passes runtime resume eligibility checks.""" + + consumed: FactoryRunConsumed + """Durable resource consumption.""" + + created_at: int + """Epoch milliseconds when the run was created.""" + + declared_limits: FactoryDeclaredLimits + """Resource ceilings declared by the factory.""" + + declared_phase_count: int + """Number of phases declared by the factory.""" + + description: str + """Human-readable factory description.""" + + factory_name: str + """Registered factory name.""" + + live_agent_count: int + """Number of direct factory agents currently live.""" + + observed_at: int + """Epoch milliseconds when this live-overlay snapshot was observed.""" + + revision: int + """Monotonic durable run revision.""" + + run_id: str + """Factory run identifier.""" + + status: RunStatus + """Current factory run status.""" + + total_spawned_agent_count: int + """Total direct factory agents spawned across all attempts.""" + + updated_at: int + """Epoch milliseconds when the durable run was last updated.""" + + active_segment_started_at: int | None = None + """Epoch milliseconds when the current active segment started, or null while inactive.""" + + approved: FactoryDeclaredLimits | None = None + """Approved effective resource ceilings, or null until approved.""" + + completed_at: int | None = None + """Epoch milliseconds when the run completed, or null while nonterminal.""" + + current_phase: FactoryCurrentPhase | None = None + """Current phase identity, or null before any phase is entered.""" + + started_at: int | None = None + """Epoch milliseconds when execution first started, or null before start.""" + + terminal: FactoryRunTerminal | None = None + """Terminal run outcome, or null while nonterminal.""" + + @staticmethod + def from_dict(obj: Any) -> 'FactoryRunSummary': + assert isinstance(obj, dict) + can_resume = from_bool(obj.get("canResume")) + consumed = FactoryRunConsumed.from_dict(obj.get("consumed")) + created_at = from_int(obj.get("createdAt")) + declared_limits = FactoryDeclaredLimits.from_dict(obj.get("declaredLimits")) + declared_phase_count = from_int(obj.get("declaredPhaseCount")) + description = from_str(obj.get("description")) + factory_name = from_str(obj.get("factoryName")) + live_agent_count = from_int(obj.get("liveAgentCount")) + observed_at = from_int(obj.get("observedAt")) + revision = from_int(obj.get("revision")) + run_id = from_str(obj.get("runId")) + status = RunStatus(obj.get("status")) + total_spawned_agent_count = from_int(obj.get("totalSpawnedAgentCount")) + updated_at = from_int(obj.get("updatedAt")) + active_segment_started_at = from_union([from_int, from_none], obj.get("activeSegmentStartedAt")) + approved = from_union([FactoryDeclaredLimits.from_dict, from_none], obj.get("approved")) + completed_at = from_union([from_int, from_none], obj.get("completedAt")) + current_phase = from_union([FactoryCurrentPhase.from_dict, from_none], obj.get("currentPhase")) + started_at = from_union([from_int, from_none], obj.get("startedAt")) + terminal = from_union([FactoryRunTerminal.from_dict, from_none], obj.get("terminal")) + return FactoryRunSummary(can_resume, consumed, created_at, declared_limits, declared_phase_count, description, factory_name, live_agent_count, observed_at, revision, run_id, status, total_spawned_agent_count, updated_at, active_segment_started_at, approved, completed_at, current_phase, started_at, terminal) + + def to_dict(self) -> dict: + result: dict = {} + result["canResume"] = from_bool(self.can_resume) + result["consumed"] = to_class(FactoryRunConsumed, self.consumed) + result["createdAt"] = from_int(self.created_at) + result["declaredLimits"] = to_class(FactoryDeclaredLimits, self.declared_limits) + result["declaredPhaseCount"] = from_int(self.declared_phase_count) + result["description"] = from_str(self.description) + result["factoryName"] = from_str(self.factory_name) + result["liveAgentCount"] = from_int(self.live_agent_count) + result["observedAt"] = from_int(self.observed_at) + result["revision"] = from_int(self.revision) + result["runId"] = from_str(self.run_id) + result["status"] = to_enum(RunStatus, self.status) + result["totalSpawnedAgentCount"] = from_int(self.total_spawned_agent_count) + result["updatedAt"] = from_int(self.updated_at) + result["activeSegmentStartedAt"] = from_union([from_int, from_none], self.active_segment_started_at) + result["approved"] = from_union([lambda x: to_class(FactoryDeclaredLimits, x), from_none], self.approved) + result["completedAt"] = from_union([from_int, from_none], self.completed_at) + result["currentPhase"] = from_union([lambda x: to_class(FactoryCurrentPhase, x), from_none], self.current_phase) + result["startedAt"] = from_union([from_int, from_none], self.started_at) + result["terminal"] = from_union([lambda x: to_class(FactoryRunTerminal, x), from_none], self.terminal) + return result + +# Experimental: this type is part of an experimental API and may change or be removed. +@dataclass +class FactoryResumeResult: + """Resolved persisted factory identity and resumed run envelope.""" + + factory_name: str + """Persisted factory name resolved for the resumed run.""" + + run: FactoryRunResult + """Terminal resumed run envelope.""" + + @staticmethod + def from_dict(obj: Any) -> 'FactoryResumeResult': + assert isinstance(obj, dict) + factory_name = from_str(obj.get("factoryName")) + run = FactoryRunResult.from_dict(obj.get("run")) + return FactoryResumeResult(factory_name, run) + + def to_dict(self) -> dict: + result: dict = {} + result["factoryName"] = from_str(self.factory_name) + result["run"] = to_class(FactoryRunResult, self.run) + return result + # Experimental: this type is part of an experimental API and may change or be removed. @dataclass class SessionsSetAdditionalPluginsRequest: @@ -36028,7 +36829,7 @@ class WorkflowRunSummary: run_id: str """Workflow run identifier.""" - status: WorkflowRunStatus + status: RunStatus """Current workflow run status.""" total_spawned_agent_count: int @@ -36071,7 +36872,7 @@ def from_dict(obj: Any) -> 'WorkflowRunSummary': observed_at = from_int(obj.get("observedAt")) revision = from_int(obj.get("revision")) run_id = from_str(obj.get("runId")) - status = WorkflowRunStatus(obj.get("status")) + status = RunStatus(obj.get("status")) total_spawned_agent_count = from_int(obj.get("totalSpawnedAgentCount")) updated_at = from_int(obj.get("updatedAt")) workflow_name = from_str(obj.get("workflowName")) @@ -36095,7 +36896,7 @@ def to_dict(self) -> dict: result["observedAt"] = from_int(self.observed_at) result["revision"] = from_int(self.revision) result["runId"] = from_str(self.run_id) - result["status"] = to_enum(WorkflowRunStatus, self.status) + result["status"] = to_enum(RunStatus, self.status) result["totalSpawnedAgentCount"] = from_int(self.total_spawned_agent_count) result["updatedAt"] = from_int(self.updated_at) result["workflowName"] = from_str(self.workflow_name) @@ -36373,6 +37174,49 @@ def to_dict(self) -> dict: result["status"] = to_class(ConnectorStatus, self.status) return result +# Experimental: this type is part of an experimental API and may change or be removed. +@dataclass +class FactoryListRunsResult: + """A page of factory runs in durable creation order.""" + + runs: list[FactoryRunSummary] + """Factory run summaries in durable creation order.""" + + has_more_newer: bool | None = None + """Whether terminal runs newer than this page exist.""" + + newest_seq: int | None = None + """Newest terminal-run cursor in this page, or null when the terminal window is empty.""" + + oldest_seq: int | None = None + """Oldest terminal-run cursor in this page, or null when the terminal window is empty.""" + + omitted_older: int | None = None + """Number of terminal runs older than this page.""" + + @staticmethod + def from_dict(obj: Any) -> 'FactoryListRunsResult': + assert isinstance(obj, dict) + runs = from_list(FactoryRunSummary.from_dict, obj.get("runs")) + has_more_newer = from_union([from_bool, from_none], obj.get("hasMoreNewer")) + newest_seq = from_union([from_int, from_none], obj.get("newestSeq")) + oldest_seq = from_union([from_int, from_none], obj.get("oldestSeq")) + omitted_older = from_union([from_int, from_none], obj.get("omittedOlder")) + return FactoryListRunsResult(runs, has_more_newer, newest_seq, oldest_seq, omitted_older) + + def to_dict(self) -> dict: + result: dict = {} + result["runs"] = from_list(lambda x: to_class(FactoryRunSummary, x), self.runs) + if self.has_more_newer is not None: + result["hasMoreNewer"] = from_union([from_bool, from_none], self.has_more_newer) + if self.newest_seq is not None: + result["newestSeq"] = from_union([from_int, from_none], self.newest_seq) + if self.oldest_seq is not None: + result["oldestSeq"] = from_union([from_int, from_none], self.oldest_seq) + if self.omitted_older is not None: + result["omittedOlder"] = from_union([from_int, from_none], self.omitted_older) + return result + # Experimental: this type is part of an experimental API and may change or be removed. @dataclass class CatalogSearchSucceeded: @@ -37761,6 +38605,225 @@ def to_dict(self) -> dict: result["hasMore"] = from_bool(self.has_more) return result +# Experimental: this type is part of an experimental API and may change or be removed. +@dataclass +class FactoryAgentSummary: + """Prompt-safe durable identity and live status for a direct factory agent.""" + + active_ms: int + """Accumulated active agent time in milliseconds.""" + + agent_id: str + """Stable direct-agent identifier.""" + + agent_type: str + """Registered agent type.""" + + label: str + """Friendly, non-unique name intended for display""" + + run_id: str + """Owning factory run identifier.""" + + status: str + """Current durable or live agent status.""" + + tool_call_id: str + """Tool-call identifier that launched the agent.""" + + activity: str | None = None + """Prompt-safe live activity text.""" + + completed_at: int | None = None + """Epoch milliseconds when the agent completed.""" + + display_name: str | None = None + """Friendly, non-unique name intended for display""" + + phase_id: str | None = None + """Phase identifier active when the agent was launched, or null.""" + + requested_model: str | None = None + """Model requested when the agent was launched.""" + + resolved_model: str | None = None + """Concrete model resolved for the agent.""" + + started_at: int | None = None + """Epoch milliseconds when the agent started.""" + + @staticmethod + def from_dict(obj: Any) -> 'FactoryAgentSummary': + assert isinstance(obj, dict) + active_ms = from_int(obj.get("activeMs")) + agent_id = from_str(obj.get("agentId")) + agent_type = from_str(obj.get("agentType")) + label = from_str(obj.get("label")) + run_id = from_str(obj.get("runId")) + status = from_str(obj.get("status")) + tool_call_id = from_str(obj.get("toolCallId")) + activity = from_union([from_str, from_none], obj.get("activity")) + completed_at = from_union([from_int, from_none], obj.get("completedAt")) + display_name = from_union([from_str, from_none], obj.get("displayName")) + phase_id = from_union([from_none, from_str], obj.get("phaseId")) + requested_model = from_union([from_str, from_none], obj.get("requestedModel")) + resolved_model = from_union([from_str, from_none], obj.get("resolvedModel")) + started_at = from_union([from_int, from_none], obj.get("startedAt")) + return FactoryAgentSummary(active_ms, agent_id, agent_type, label, run_id, status, tool_call_id, activity, completed_at, display_name, phase_id, requested_model, resolved_model, started_at) + + def to_dict(self) -> dict: + result: dict = {} + result["activeMs"] = from_int(self.active_ms) + result["agentId"] = from_str(self.agent_id) + result["agentType"] = from_str(self.agent_type) + result["label"] = from_str(self.label) + result["runId"] = from_str(self.run_id) + result["status"] = from_str(self.status) + result["toolCallId"] = from_str(self.tool_call_id) + if self.activity is not None: + result["activity"] = from_union([from_str, from_none], self.activity) + if self.completed_at is not None: + result["completedAt"] = from_union([from_int, from_none], self.completed_at) + if self.display_name is not None: + result["displayName"] = from_union([from_str, from_none], self.display_name) + result["phaseId"] = from_union([from_none, from_str], self.phase_id) + if self.requested_model is not None: + result["requestedModel"] = from_union([from_str, from_none], self.requested_model) + if self.resolved_model is not None: + result["resolvedModel"] = from_union([from_str, from_none], self.resolved_model) + if self.started_at is not None: + result["startedAt"] = from_union([from_int, from_none], self.started_at) + return result + +# Experimental: this type is part of an experimental API and may change or be removed. +@dataclass +class FactoryRunDetail: + """Full factory run observability detail.""" + + agents: list[FactoryAgentSummary] + """Durable identities and live statuses for direct factory agents.""" + + can_resume: bool + """Whether the durable run state currently passes runtime resume eligibility checks.""" + + consumed: FactoryRunConsumed + """Durable resource consumption.""" + + created_at: int + """Epoch milliseconds when the run was created.""" + + declared_limits: FactoryDeclaredLimits + """Resource ceilings declared by the factory.""" + + declared_phase_count: int + """Number of phases declared by the factory.""" + + description: str + """Human-readable factory description.""" + + factory_name: str + """Registered factory name.""" + + live_agent_count: int + """Number of direct factory agents currently live.""" + + observed_at: int + """Epoch milliseconds when this live-overlay snapshot was observed.""" + + phases: list[FactoryPhaseObservation] + """Lifecycle and timing observations for each factory phase.""" + + progress: FactoryProgressPage + """Bidirectional page of durable factory progress.""" + + revision: int + """Monotonic durable run revision.""" + + run_id: str + """Factory run identifier.""" + + status: RunStatus + """Current factory run status.""" + + total_spawned_agent_count: int + """Total direct factory agents spawned across all attempts.""" + + updated_at: int + """Epoch milliseconds when the durable run was last updated.""" + + active_segment_started_at: int | None = None + """Epoch milliseconds when the current active segment started, or null while inactive.""" + + approved: FactoryDeclaredLimits | None = None + """Approved effective resource ceilings, or null until approved.""" + + completed_at: int | None = None + """Epoch milliseconds when the run completed, or null while nonterminal.""" + + current_phase: FactoryCurrentPhase | None = None + """Current phase identity, or null before any phase is entered.""" + + started_at: int | None = None + """Epoch milliseconds when execution first started, or null before start.""" + + terminal: FactoryRunTerminal | None = None + """Terminal run outcome, or null while nonterminal.""" + + @staticmethod + def from_dict(obj: Any) -> 'FactoryRunDetail': + assert isinstance(obj, dict) + agents = from_list(FactoryAgentSummary.from_dict, obj.get("agents")) + can_resume = from_bool(obj.get("canResume")) + consumed = FactoryRunConsumed.from_dict(obj.get("consumed")) + created_at = from_int(obj.get("createdAt")) + declared_limits = FactoryDeclaredLimits.from_dict(obj.get("declaredLimits")) + declared_phase_count = from_int(obj.get("declaredPhaseCount")) + description = from_str(obj.get("description")) + factory_name = from_str(obj.get("factoryName")) + live_agent_count = from_int(obj.get("liveAgentCount")) + observed_at = from_int(obj.get("observedAt")) + phases = from_list(FactoryPhaseObservation.from_dict, obj.get("phases")) + progress = FactoryProgressPage.from_dict(obj.get("progress")) + revision = from_int(obj.get("revision")) + run_id = from_str(obj.get("runId")) + status = RunStatus(obj.get("status")) + total_spawned_agent_count = from_int(obj.get("totalSpawnedAgentCount")) + updated_at = from_int(obj.get("updatedAt")) + active_segment_started_at = from_union([from_int, from_none], obj.get("activeSegmentStartedAt")) + approved = from_union([FactoryDeclaredLimits.from_dict, from_none], obj.get("approved")) + completed_at = from_union([from_int, from_none], obj.get("completedAt")) + current_phase = from_union([FactoryCurrentPhase.from_dict, from_none], obj.get("currentPhase")) + started_at = from_union([from_int, from_none], obj.get("startedAt")) + terminal = from_union([FactoryRunTerminal.from_dict, from_none], obj.get("terminal")) + return FactoryRunDetail(agents, can_resume, consumed, created_at, declared_limits, declared_phase_count, description, factory_name, live_agent_count, observed_at, phases, progress, revision, run_id, status, total_spawned_agent_count, updated_at, active_segment_started_at, approved, completed_at, current_phase, started_at, terminal) + + def to_dict(self) -> dict: + result: dict = {} + result["agents"] = from_list(lambda x: to_class(FactoryAgentSummary, x), self.agents) + result["canResume"] = from_bool(self.can_resume) + result["consumed"] = to_class(FactoryRunConsumed, self.consumed) + result["createdAt"] = from_int(self.created_at) + result["declaredLimits"] = to_class(FactoryDeclaredLimits, self.declared_limits) + result["declaredPhaseCount"] = from_int(self.declared_phase_count) + result["description"] = from_str(self.description) + result["factoryName"] = from_str(self.factory_name) + result["liveAgentCount"] = from_int(self.live_agent_count) + result["observedAt"] = from_int(self.observed_at) + result["phases"] = from_list(lambda x: to_class(FactoryPhaseObservation, x), self.phases) + result["progress"] = to_class(FactoryProgressPage, self.progress) + result["revision"] = from_int(self.revision) + result["runId"] = from_str(self.run_id) + result["status"] = to_enum(RunStatus, self.status) + result["totalSpawnedAgentCount"] = from_int(self.total_spawned_agent_count) + result["updatedAt"] = from_int(self.updated_at) + result["activeSegmentStartedAt"] = from_union([from_int, from_none], self.active_segment_started_at) + result["approved"] = from_union([lambda x: to_class(FactoryDeclaredLimits, x), from_none], self.approved) + result["completedAt"] = from_union([from_int, from_none], self.completed_at) + result["currentPhase"] = from_union([lambda x: to_class(FactoryCurrentPhase, x), from_none], self.current_phase) + result["startedAt"] = from_union([from_int, from_none], self.started_at) + result["terminal"] = from_union([lambda x: to_class(FactoryRunTerminal, x), from_none], self.terminal) + return result + # Experimental: this type is part of an experimental API and may change or be removed. @dataclass class GhCLIAuthInfo: @@ -38992,16 +40055,6 @@ class ModelApplyStartupOverlayRequest: device_managed_model: str | None = None """Model required by device-managed policy, when configured.""" - managed_context_tier: str | None = None - """Context tier paired with the effective organization-managed model. Applies only when that - concrete managed model is selected; it is ignored for Auto and for CLI, resume, or user - overrides. - """ - managed_reasoning_effort: str | None = None - """Reasoning effort paired with the effective organization-managed model. Applies only when - that concrete managed model is selected; it is ignored for Auto and for CLI, resume, or - user overrides. - """ policy_helper_model: str | None = None """Startup default model from the enterprise policy helper, when configured. Weakest of the managed sources: it applies only when neither device nor server policy names a model, and @@ -39030,15 +40083,13 @@ def from_dict(obj: Any) -> 'ModelApplyStartupOverlayRequest': cli_model = from_union([from_str, from_none], obj.get("cliModel")) deferred_resume = from_union([from_bool, from_none], obj.get("deferredResume")) device_managed_model = from_union([from_str, from_none], obj.get("deviceManagedModel")) - managed_context_tier = from_union([from_str, from_none], obj.get("managedContextTier")) - managed_reasoning_effort = from_union([from_str, from_none], obj.get("managedReasoningEffort")) policy_helper_model = from_union([from_str, from_none], obj.get("policyHelperModel")) repo_auto_tier = from_union([from_str, from_none], obj.get("repoAutoTier")) repo_context_tier = from_union([from_str, from_none], obj.get("repoContextTier")) repo_model = from_union([from_str, from_none], obj.get("repoModel")) repo_reasoning_effort = from_union([from_str, from_none], obj.get("repoReasoningEffort")) server_managed_model = from_union([from_str, from_none], obj.get("serverManagedModel")) - return ModelApplyStartupOverlayRequest(auto_tier, cli_model, deferred_resume, device_managed_model, managed_context_tier, managed_reasoning_effort, policy_helper_model, repo_auto_tier, repo_context_tier, repo_model, repo_reasoning_effort, server_managed_model) + return ModelApplyStartupOverlayRequest(auto_tier, cli_model, deferred_resume, device_managed_model, policy_helper_model, repo_auto_tier, repo_context_tier, repo_model, repo_reasoning_effort, server_managed_model) def to_dict(self) -> dict: result: dict = {} @@ -39050,10 +40101,6 @@ def to_dict(self) -> dict: result["deferredResume"] = from_union([from_bool, from_none], self.deferred_resume) if self.device_managed_model is not None: result["deviceManagedModel"] = from_union([from_str, from_none], self.device_managed_model) - if self.managed_context_tier is not None: - result["managedContextTier"] = from_union([from_str, from_none], self.managed_context_tier) - if self.managed_reasoning_effort is not None: - result["managedReasoningEffort"] = from_union([from_str, from_none], self.managed_reasoning_effort) if self.policy_helper_model is not None: result["policyHelperModel"] = from_union([from_str, from_none], self.policy_helper_model) if self.repo_auto_tier is not None: @@ -40615,9 +41662,6 @@ class SlashCommandSetModelResult: model: str """Model selected by the command.""" - auto_tier: AutoTier | None = None - """Auto routing profile selected by the command, when the model is Auto.""" - reasoning_effort: str | None = None """Reasoning effort selected for the model.""" @@ -40640,21 +41684,18 @@ class SlashCommandSetModelResult: def from_dict(obj: Any) -> 'SlashCommandSetModelResult': assert isinstance(obj, dict) model = from_str(obj.get("model")) - auto_tier = from_union([AutoTier, from_none], obj.get("autoTier")) reasoning_effort = from_union([from_str, from_none], obj.get("reasoningEffort")) repo_scope = from_union([from_str, from_none], obj.get("repoScope")) revert_on_cancel = from_union([lambda x: from_dict(lambda x: x, x), from_none], obj.get("revertOnCancel")) runtime_settings_changed = from_union([from_bool, from_none], obj.get("runtimeSettingsChanged")) scope = from_union([from_str, from_none], obj.get("scope")) warning = from_union([from_str, from_none], obj.get("warning")) - return SlashCommandSetModelResult(model, auto_tier, reasoning_effort, repo_scope, revert_on_cancel, runtime_settings_changed, scope, warning) + return SlashCommandSetModelResult(model, reasoning_effort, repo_scope, revert_on_cancel, runtime_settings_changed, scope, warning) def to_dict(self) -> dict: result: dict = {} result["kind"] = self.kind result["model"] = from_str(self.model) - if self.auto_tier is not None: - result["autoTier"] = from_union([lambda x: to_enum(AutoTier, x), from_none], self.auto_tier) if self.reasoning_effort is not None: result["reasoningEffort"] = from_union([from_str, from_none], self.reasoning_effort) if self.repo_scope is not None: @@ -41109,7 +42150,7 @@ class WorkflowRunDetail: run_id: str """Workflow run identifier.""" - status: WorkflowRunStatus + status: RunStatus """Current workflow run status.""" total_spawned_agent_count: int @@ -41155,7 +42196,7 @@ def from_dict(obj: Any) -> 'WorkflowRunDetail': progress = WorkflowProgressPage.from_dict(obj.get("progress")) revision = from_int(obj.get("revision")) run_id = from_str(obj.get("runId")) - status = WorkflowRunStatus(obj.get("status")) + status = RunStatus(obj.get("status")) total_spawned_agent_count = from_int(obj.get("totalSpawnedAgentCount")) updated_at = from_int(obj.get("updatedAt")) workflow_name = from_str(obj.get("workflowName")) @@ -41182,7 +42223,7 @@ def to_dict(self) -> dict: result["progress"] = to_class(WorkflowProgressPage, self.progress) result["revision"] = from_int(self.revision) result["runId"] = from_str(self.run_id) - result["status"] = to_enum(WorkflowRunStatus, self.status) + result["status"] = to_enum(RunStatus, self.status) result["totalSpawnedAgentCount"] = from_int(self.total_spawned_agent_count) result["updatedAt"] = from_int(self.updated_at) result["workflowName"] = from_str(self.workflow_name) @@ -41204,16 +42245,11 @@ class RPC: account_get_current_auth_result: AccountGetCurrentAuthResult account_get_quota_request: AccountGetQuotaRequest account_get_quota_result: AccountGetQuotaResult - account_kind: AccountKind account_login_request: AccountLoginRequest account_login_result: AccountLoginResult account_logout_request: AccountLogoutRequest account_logout_result: AccountLogoutResult account_quota_snapshot: AccountQuotaSnapshot - accounts_enumerate_request: AccountsEnumerateRequest - accounts_get_request: AccountsGetRequest - accounts_set_request: AccountsSetRequest - account_status: AccountStatus adaptive_thinking_support: AdaptiveThinkingSupport agent_discovery_path: AgentDiscoveryPath agent_discovery_path_list: AgentDiscoveryPathList @@ -41246,25 +42282,11 @@ class RPC: agent_set_prompt_request: AgentSetPromptRequest agents_get_discovery_paths_request: AgentsGetDiscoveryPathsRequest api_key_auth_info: APIKeyAuthInfo - auth_enumerate_query: AuthEnumerateQuery - auth_enumerate_value: AuthEnumerateValue auth_identity: AuthIdentity auth_info: AuthInfo auth_info_type: AuthInfoType - auth_login_advance_request: AuthLoginAdvanceRequest - auth_login_begin_request: AuthLoginBeginRequest - auth_login_begun: AuthLoginBegun - auth_login_cancel_request: AuthLoginCancelRequest - auth_login_result_dto: AuthLoginResultDto - auth_login_result_status: AuthLoginResultStatus - auth_login_step: AuthLoginStep - auth_read_query: AuthReadQuery - auth_read_value: AuthReadValue - auth_status_dto: AuthStatusDto auth_validation_error: AuthValidationError auth_validation_errors: list[AuthValidationError] - auth_write: AuthWrite - auth_write_result: AuthWriteResult autopilot_objective_credit_limit: AutopilotObjectiveCreditLimit autopilot_objective_get_state_result: AutopilotObjectiveGetStateResult autopilot_objective_state: AutopilotObjectiveState @@ -41516,6 +42538,52 @@ class RPC: external_tool_text_result_for_llm_content_shell_exit: ExternalToolTextResultForLlmContentShellExit external_tool_text_result_for_llm_content_terminal: ExternalToolTextResultForLlmContentTerminal external_tool_text_result_for_llm_content_text: ExternalToolTextResultForLlmContentText + factory_abort_request: FactoryAbortRequest + factory_ack_result: FactoryACKResult + factory_agent_options: FactoryAgentOptions + factory_agent_request: FactoryAgentRequest + factory_agent_result: FactoryAgentResult + factory_agent_summary: FactoryAgentSummary + factory_cancel_request: FactoryCancelRequest + factory_current_phase: FactoryCurrentPhase + factory_declared_limits: FactoryDeclaredLimits + factory_durable_operation: DurableOperation + factory_execute_request: FactoryExecuteRequest + factory_execute_result: FactoryExecuteResult + factory_get_run_progress_request: FactoryGetRunProgressRequest + factory_get_run_request: FactoryGetRunRequest + factory_journal_get_request: FactoryJournalGetRequest + factory_journal_get_result: FactoryJournalGetResult + factory_journal_put_request: FactoryJournalPutRequest + factory_list_runs_request: FactoryListRunsRequest + factory_list_runs_result: FactoryListRunsResult + factory_log_line: FactoryLogLine + factory_log_line_kind: LogLineKind + factory_log_request: FactoryLogRequest + factory_pause_checkpoint_action: PauseCheckpointAction + factory_pause_checkpoint_request: FactoryPauseCheckpointRequest + factory_pause_checkpoint_result: FactoryPauseCheckpointResult + factory_pause_info: FactoryPauseInfo + factory_pause_request: FactoryPauseRequest + factory_phase_observation: FactoryPhaseObservation + factory_phase_status: PhaseStatus + factory_progress_line: FactoryProgressLine + factory_progress_page: FactoryProgressPage + factory_resume_request: FactoryResumeRequest + factory_resume_result: FactoryResumeResult + factory_run_consumed: FactoryRunConsumed + factory_run_detail: FactoryRunDetail + factory_run_failure: FactoryRunFailure + factory_run_failure_kind: RunFailureKind + factory_run_limits: FactoryRunLimits + factory_run_request: FactoryRunRequest + factory_run_result: FactoryRunResult + factory_run_status: RunStatus + factory_run_summary: FactoryRunSummary + factory_run_terminal: FactoryRunTerminal + factory_tool_resume_request: _FactoryToolResumeRequest + factory_tool_run_options: _FactoryToolRunOptions + factory_tool_run_request: _FactoryToolRunRequest filter_mapping: dict[str, ContentFilterMode] | ContentFilterMode fleet_start_request: FleetStartRequest fleet_start_result: FleetStartResult @@ -41593,7 +42661,6 @@ class RPC: llm_inference_http_response_start_result: LlmInferenceHTTPResponseStartResult llm_inference_set_provider_result: LlmInferenceSetProviderResult local_session_metadata_value: LocalSessionMetadataValue - login_provider_kind: LoginProviderKind log_request: LogRequest log_result: LogResult lsp_initialize_request: LspInitializeRequest @@ -41849,11 +42916,11 @@ class RPC: permission_decision_approve_for_location_approval_extension_env_access: PermissionDecisionApproveForLocationApprovalExtensionEnvAccess permission_decision_approve_for_location_approval_extension_management: PermissionDecisionApproveForLocationApprovalExtensionManagement permission_decision_approve_for_location_approval_extension_permission_access: PermissionDecisionApproveForLocationApprovalExtensionPermissionAccess + permission_decision_approve_for_location_approval_factory: PermissionDecisionApproveForLocationApprovalFactory permission_decision_approve_for_location_approval_mcp: PermissionDecisionApproveForLocationApprovalMCP permission_decision_approve_for_location_approval_mcp_sampling: PermissionDecisionApproveForLocationApprovalMCPSampling permission_decision_approve_for_location_approval_memory: PermissionDecisionApproveForLocationApprovalMemory permission_decision_approve_for_location_approval_read: PermissionDecisionApproveForLocationApprovalRead - permission_decision_approve_for_location_approval_workflow: PermissionDecisionApproveForLocationApprovalWorkflow permission_decision_approve_for_location_approval_write: PermissionDecisionApproveForLocationApprovalWrite permission_decision_approve_for_session: PermissionDecisionApproveForSession permission_decision_approve_for_session_approval: PermissionDecisionApproveForSessionApproval @@ -41862,11 +42929,11 @@ class RPC: permission_decision_approve_for_session_approval_extension_env_access: PermissionDecisionApproveForSessionApprovalExtensionEnvAccess permission_decision_approve_for_session_approval_extension_management: PermissionDecisionApproveForSessionApprovalExtensionManagement permission_decision_approve_for_session_approval_extension_permission_access: PermissionDecisionApproveForSessionApprovalExtensionPermissionAccess + permission_decision_approve_for_session_approval_factory: PermissionDecisionApproveForSessionApprovalFactory permission_decision_approve_for_session_approval_mcp: PermissionDecisionApproveForSessionApprovalMCP permission_decision_approve_for_session_approval_mcp_sampling: PermissionDecisionApproveForSessionApprovalMCPSampling permission_decision_approve_for_session_approval_memory: PermissionDecisionApproveForSessionApprovalMemory permission_decision_approve_for_session_approval_read: PermissionDecisionApproveForSessionApprovalRead - permission_decision_approve_for_session_approval_workflow: PermissionDecisionApproveForSessionApprovalWorkflow permission_decision_approve_for_session_approval_write: PermissionDecisionApproveForSessionApprovalWrite permission_decision_approve_once: PermissionDecisionApproveOnce permission_decision_approve_permanently: PermissionDecisionApprovePermanently @@ -41916,11 +42983,11 @@ class RPC: permissions_locations_add_tool_approval_details_extension_env_access: PermissionsLocationsAddToolApprovalDetailsExtensionEnvAccess permissions_locations_add_tool_approval_details_extension_management: PermissionsLocationsAddToolApprovalDetailsExtensionManagement permissions_locations_add_tool_approval_details_extension_permission_access: PermissionsLocationsAddToolApprovalDetailsExtensionPermissionAccess + permissions_locations_add_tool_approval_details_factory: PermissionsLocationsAddToolApprovalDetailsFactory permissions_locations_add_tool_approval_details_mcp: PermissionsLocationsAddToolApprovalDetailsMCP permissions_locations_add_tool_approval_details_mcp_sampling: PermissionsLocationsAddToolApprovalDetailsMCPSampling permissions_locations_add_tool_approval_details_memory: PermissionsLocationsAddToolApprovalDetailsMemory permissions_locations_add_tool_approval_details_read: PermissionsLocationsAddToolApprovalDetailsRead - permissions_locations_add_tool_approval_details_workflow: PermissionsLocationsAddToolApprovalDetailsWorkflow permissions_locations_add_tool_approval_details_write: PermissionsLocationsAddToolApprovalDetailsWrite permissions_locations_add_tool_approval_result: PermissionsLocationsAddToolApprovalResult permissions_modify_rules_params: PermissionsModifyRulesParams @@ -41990,7 +43057,6 @@ class RPC: provider_config_transport: ProviderTransport provider_config_type: ProviderType provider_config_wire_api: ProviderWireAPI - provider_descriptor: ProviderDescriptor provider_endpoint: ProviderEndpoint provider_endpoint_transport: ProviderTransport provider_endpoint_type: ProviderType @@ -42083,6 +43149,7 @@ class RPC: remote_session_mode: RemoteSessionMode remote_session_repository: RemoteSessionRepository response_format: ResponseFormat + run_options: RunOptions sandbox_config: SandboxConfig sandbox_config_auth: SandboxConfigAuth sandbox_config_source: _SandboxConfigSource @@ -42140,6 +43207,7 @@ class RPC: session_context: SessionContext session_context_host_type: HostType session_enrich_metadata_result: SessionEnrichMetadataResult + session_factory_pause_at_checkpoint_result: SessionFactoryPauseAtCheckpointResult session_fs_append_file_request: SessionFSAppendFileRequest session_fs_error: SessionFSError session_fs_error_code: SessionFSErrorCode @@ -42483,7 +43551,7 @@ class RPC: workflow_cancel_request: WorkflowCancelRequest workflow_current_phase: WorkflowCurrentPhase workflow_declared_limits: WorkflowDeclaredLimits - workflow_durable_operation: WorkflowDurableOperation + workflow_durable_operation: DurableOperation workflow_execute_request: WorkflowExecuteRequest workflow_execute_result: WorkflowExecuteResult workflow_get_run_progress_request: WorkflowGetRunProgressRequest @@ -42494,15 +43562,15 @@ class RPC: workflow_list_runs_request: WorkflowListRunsRequest workflow_list_runs_result: WorkflowListRunsResult workflow_log_line: WorkflowLogLine - workflow_log_line_kind: WorkflowLogLineKind + workflow_log_line_kind: LogLineKind workflow_log_request: WorkflowLogRequest - workflow_pause_checkpoint_action: WorkflowPauseCheckpointAction + workflow_pause_checkpoint_action: PauseCheckpointAction workflow_pause_checkpoint_request: WorkflowPauseCheckpointRequest workflow_pause_checkpoint_result: WorkflowPauseCheckpointResult workflow_pause_info: WorkflowPauseInfo workflow_pause_request: WorkflowPauseRequest workflow_phase_observation: WorkflowPhaseObservation - workflow_phase_status: WorkflowPhaseStatus + workflow_phase_status: PhaseStatus workflow_progress_line: WorkflowProgressLine workflow_progress_page: WorkflowProgressPage workflow_resume_request: WorkflowResumeRequest @@ -42510,12 +43578,12 @@ class RPC: workflow_run_consumed: WorkflowRunConsumed workflow_run_detail: WorkflowRunDetail workflow_run_failure: WorkflowRunFailure - workflow_run_failure_kind: WorkflowRunFailureKind + workflow_run_failure_kind: RunFailureKind workflow_run_limits: WorkflowRunLimits workflow_run_options: WorkflowRunOptions workflow_run_request: WorkflowRunRequest workflow_run_result: WorkflowRunResult - workflow_run_status: WorkflowRunStatus + workflow_run_status: RunStatus workflow_run_summary: WorkflowRunSummary workflow_run_terminal: WorkflowRunTerminal workflow_tool_resume_request: _WorkflowToolResumeRequest @@ -42572,16 +43640,11 @@ def from_dict(obj: Any) -> 'RPC': account_get_current_auth_result = AccountGetCurrentAuthResult.from_dict(obj.get("AccountGetCurrentAuthResult")) account_get_quota_request = AccountGetQuotaRequest.from_dict(obj.get("AccountGetQuotaRequest")) account_get_quota_result = AccountGetQuotaResult.from_dict(obj.get("AccountGetQuotaResult")) - account_kind = AccountKind(obj.get("AccountKind")) account_login_request = AccountLoginRequest.from_dict(obj.get("AccountLoginRequest")) account_login_result = AccountLoginResult.from_dict(obj.get("AccountLoginResult")) account_logout_request = AccountLogoutRequest.from_dict(obj.get("AccountLogoutRequest")) account_logout_result = AccountLogoutResult.from_dict(obj.get("AccountLogoutResult")) account_quota_snapshot = AccountQuotaSnapshot.from_dict(obj.get("AccountQuotaSnapshot")) - accounts_enumerate_request = AccountsEnumerateRequest.from_dict(obj.get("AccountsEnumerateRequest")) - accounts_get_request = AccountsGetRequest.from_dict(obj.get("AccountsGetRequest")) - accounts_set_request = AccountsSetRequest.from_dict(obj.get("AccountsSetRequest")) - account_status = AccountStatus.from_dict(obj.get("AccountStatus")) adaptive_thinking_support = AdaptiveThinkingSupport(obj.get("AdaptiveThinkingSupport")) agent_discovery_path = AgentDiscoveryPath.from_dict(obj.get("AgentDiscoveryPath")) agent_discovery_path_list = AgentDiscoveryPathList.from_dict(obj.get("AgentDiscoveryPathList")) @@ -42614,25 +43677,11 @@ def from_dict(obj: Any) -> 'RPC': agent_set_prompt_request = AgentSetPromptRequest.from_dict(obj.get("AgentSetPromptRequest")) agents_get_discovery_paths_request = AgentsGetDiscoveryPathsRequest.from_dict(obj.get("AgentsGetDiscoveryPathsRequest")) api_key_auth_info = APIKeyAuthInfo.from_dict(obj.get("ApiKeyAuthInfo")) - auth_enumerate_query = AuthEnumerateQuery.from_dict(obj.get("AuthEnumerateQuery")) - auth_enumerate_value = AuthEnumerateValue.from_dict(obj.get("AuthEnumerateValue")) auth_identity = AuthIdentity.from_dict(obj.get("AuthIdentity")) auth_info = _load_AuthInfo(obj.get("AuthInfo")) auth_info_type = AuthInfoType(obj.get("AuthInfoType")) - auth_login_advance_request = AuthLoginAdvanceRequest.from_dict(obj.get("AuthLoginAdvanceRequest")) - auth_login_begin_request = AuthLoginBeginRequest.from_dict(obj.get("AuthLoginBeginRequest")) - auth_login_begun = AuthLoginBegun.from_dict(obj.get("AuthLoginBegun")) - auth_login_cancel_request = AuthLoginCancelRequest.from_dict(obj.get("AuthLoginCancelRequest")) - auth_login_result_dto = AuthLoginResultDto.from_dict(obj.get("AuthLoginResultDto")) - auth_login_result_status = AuthLoginResultStatus(obj.get("AuthLoginResultStatus")) - auth_login_step = AuthLoginStep.from_dict(obj.get("AuthLoginStep")) - auth_read_query = AuthReadQuery.from_dict(obj.get("AuthReadQuery")) - auth_read_value = AuthReadValue.from_dict(obj.get("AuthReadValue")) - auth_status_dto = AuthStatusDto.from_dict(obj.get("AuthStatusDto")) auth_validation_error = AuthValidationError.from_dict(obj.get("AuthValidationError")) auth_validation_errors = from_list(AuthValidationError.from_dict, obj.get("AuthValidationErrors")) - auth_write = AuthWrite.from_dict(obj.get("AuthWrite")) - auth_write_result = AuthWriteResult.from_dict(obj.get("AuthWriteResult")) autopilot_objective_credit_limit = AutopilotObjectiveCreditLimit.from_dict(obj.get("AutopilotObjectiveCreditLimit")) autopilot_objective_get_state_result = AutopilotObjectiveGetStateResult.from_dict(obj.get("AutopilotObjectiveGetStateResult")) autopilot_objective_state = AutopilotObjectiveState.from_dict(obj.get("AutopilotObjectiveState")) @@ -42884,6 +43933,52 @@ def from_dict(obj: Any) -> 'RPC': external_tool_text_result_for_llm_content_shell_exit = ExternalToolTextResultForLlmContentShellExit.from_dict(obj.get("ExternalToolTextResultForLlmContentShellExit")) external_tool_text_result_for_llm_content_terminal = ExternalToolTextResultForLlmContentTerminal.from_dict(obj.get("ExternalToolTextResultForLlmContentTerminal")) external_tool_text_result_for_llm_content_text = ExternalToolTextResultForLlmContentText.from_dict(obj.get("ExternalToolTextResultForLlmContentText")) + factory_abort_request = FactoryAbortRequest.from_dict(obj.get("FactoryAbortRequest")) + factory_ack_result = FactoryACKResult.from_dict(obj.get("FactoryAckResult")) + factory_agent_options = FactoryAgentOptions.from_dict(obj.get("FactoryAgentOptions")) + factory_agent_request = FactoryAgentRequest.from_dict(obj.get("FactoryAgentRequest")) + factory_agent_result = FactoryAgentResult.from_dict(obj.get("FactoryAgentResult")) + factory_agent_summary = FactoryAgentSummary.from_dict(obj.get("FactoryAgentSummary")) + factory_cancel_request = FactoryCancelRequest.from_dict(obj.get("FactoryCancelRequest")) + factory_current_phase = FactoryCurrentPhase.from_dict(obj.get("FactoryCurrentPhase")) + factory_declared_limits = FactoryDeclaredLimits.from_dict(obj.get("FactoryDeclaredLimits")) + factory_durable_operation = DurableOperation(obj.get("FactoryDurableOperation")) + factory_execute_request = FactoryExecuteRequest.from_dict(obj.get("FactoryExecuteRequest")) + factory_execute_result = FactoryExecuteResult.from_dict(obj.get("FactoryExecuteResult")) + factory_get_run_progress_request = FactoryGetRunProgressRequest.from_dict(obj.get("FactoryGetRunProgressRequest")) + factory_get_run_request = FactoryGetRunRequest.from_dict(obj.get("FactoryGetRunRequest")) + factory_journal_get_request = FactoryJournalGetRequest.from_dict(obj.get("FactoryJournalGetRequest")) + factory_journal_get_result = FactoryJournalGetResult.from_dict(obj.get("FactoryJournalGetResult")) + factory_journal_put_request = FactoryJournalPutRequest.from_dict(obj.get("FactoryJournalPutRequest")) + factory_list_runs_request = FactoryListRunsRequest.from_dict(obj.get("FactoryListRunsRequest")) + factory_list_runs_result = FactoryListRunsResult.from_dict(obj.get("FactoryListRunsResult")) + factory_log_line = FactoryLogLine.from_dict(obj.get("FactoryLogLine")) + factory_log_line_kind = LogLineKind(obj.get("FactoryLogLineKind")) + factory_log_request = FactoryLogRequest.from_dict(obj.get("FactoryLogRequest")) + factory_pause_checkpoint_action = PauseCheckpointAction(obj.get("FactoryPauseCheckpointAction")) + factory_pause_checkpoint_request = FactoryPauseCheckpointRequest.from_dict(obj.get("FactoryPauseCheckpointRequest")) + factory_pause_checkpoint_result = FactoryPauseCheckpointResult.from_dict(obj.get("FactoryPauseCheckpointResult")) + factory_pause_info = FactoryPauseInfo.from_dict(obj.get("FactoryPauseInfo")) + factory_pause_request = FactoryPauseRequest.from_dict(obj.get("FactoryPauseRequest")) + factory_phase_observation = FactoryPhaseObservation.from_dict(obj.get("FactoryPhaseObservation")) + factory_phase_status = PhaseStatus(obj.get("FactoryPhaseStatus")) + factory_progress_line = FactoryProgressLine.from_dict(obj.get("FactoryProgressLine")) + factory_progress_page = FactoryProgressPage.from_dict(obj.get("FactoryProgressPage")) + factory_resume_request = FactoryResumeRequest.from_dict(obj.get("FactoryResumeRequest")) + factory_resume_result = FactoryResumeResult.from_dict(obj.get("FactoryResumeResult")) + factory_run_consumed = FactoryRunConsumed.from_dict(obj.get("FactoryRunConsumed")) + factory_run_detail = FactoryRunDetail.from_dict(obj.get("FactoryRunDetail")) + factory_run_failure = FactoryRunFailure.from_dict(obj.get("FactoryRunFailure")) + factory_run_failure_kind = RunFailureKind(obj.get("FactoryRunFailureKind")) + factory_run_limits = FactoryRunLimits.from_dict(obj.get("FactoryRunLimits")) + factory_run_request = FactoryRunRequest.from_dict(obj.get("FactoryRunRequest")) + factory_run_result = FactoryRunResult.from_dict(obj.get("FactoryRunResult")) + factory_run_status = RunStatus(obj.get("FactoryRunStatus")) + factory_run_summary = FactoryRunSummary.from_dict(obj.get("FactoryRunSummary")) + factory_run_terminal = FactoryRunTerminal.from_dict(obj.get("FactoryRunTerminal")) + factory_tool_resume_request = _FactoryToolResumeRequest.from_dict(obj.get("FactoryToolResumeRequest")) + factory_tool_run_options = _FactoryToolRunOptions.from_dict(obj.get("FactoryToolRunOptions")) + factory_tool_run_request = _FactoryToolRunRequest.from_dict(obj.get("FactoryToolRunRequest")) filter_mapping = from_union([lambda x: from_dict(ContentFilterMode, x), ContentFilterMode], obj.get("FilterMapping")) fleet_start_request = FleetStartRequest.from_dict(obj.get("FleetStartRequest")) fleet_start_result = FleetStartResult.from_dict(obj.get("FleetStartResult")) @@ -42961,7 +44056,6 @@ def from_dict(obj: Any) -> 'RPC': llm_inference_http_response_start_result = LlmInferenceHTTPResponseStartResult.from_dict(obj.get("LlmInferenceHttpResponseStartResult")) llm_inference_set_provider_result = LlmInferenceSetProviderResult.from_dict(obj.get("LlmInferenceSetProviderResult")) local_session_metadata_value = LocalSessionMetadataValue.from_dict(obj.get("LocalSessionMetadataValue")) - login_provider_kind = LoginProviderKind(obj.get("LoginProviderKind")) log_request = LogRequest.from_dict(obj.get("LogRequest")) log_result = LogResult.from_dict(obj.get("LogResult")) lsp_initialize_request = LspInitializeRequest.from_dict(obj.get("LspInitializeRequest")) @@ -43217,11 +44311,11 @@ def from_dict(obj: Any) -> 'RPC': permission_decision_approve_for_location_approval_extension_env_access = PermissionDecisionApproveForLocationApprovalExtensionEnvAccess.from_dict(obj.get("PermissionDecisionApproveForLocationApprovalExtensionEnvAccess")) permission_decision_approve_for_location_approval_extension_management = PermissionDecisionApproveForLocationApprovalExtensionManagement.from_dict(obj.get("PermissionDecisionApproveForLocationApprovalExtensionManagement")) permission_decision_approve_for_location_approval_extension_permission_access = PermissionDecisionApproveForLocationApprovalExtensionPermissionAccess.from_dict(obj.get("PermissionDecisionApproveForLocationApprovalExtensionPermissionAccess")) + permission_decision_approve_for_location_approval_factory = PermissionDecisionApproveForLocationApprovalFactory.from_dict(obj.get("PermissionDecisionApproveForLocationApprovalFactory")) permission_decision_approve_for_location_approval_mcp = PermissionDecisionApproveForLocationApprovalMCP.from_dict(obj.get("PermissionDecisionApproveForLocationApprovalMcp")) permission_decision_approve_for_location_approval_mcp_sampling = PermissionDecisionApproveForLocationApprovalMCPSampling.from_dict(obj.get("PermissionDecisionApproveForLocationApprovalMcpSampling")) permission_decision_approve_for_location_approval_memory = PermissionDecisionApproveForLocationApprovalMemory.from_dict(obj.get("PermissionDecisionApproveForLocationApprovalMemory")) permission_decision_approve_for_location_approval_read = PermissionDecisionApproveForLocationApprovalRead.from_dict(obj.get("PermissionDecisionApproveForLocationApprovalRead")) - permission_decision_approve_for_location_approval_workflow = PermissionDecisionApproveForLocationApprovalWorkflow.from_dict(obj.get("PermissionDecisionApproveForLocationApprovalWorkflow")) permission_decision_approve_for_location_approval_write = PermissionDecisionApproveForLocationApprovalWrite.from_dict(obj.get("PermissionDecisionApproveForLocationApprovalWrite")) permission_decision_approve_for_session = PermissionDecisionApproveForSession.from_dict(obj.get("PermissionDecisionApproveForSession")) permission_decision_approve_for_session_approval = _load_PermissionDecisionApproveForSessionApproval(obj.get("PermissionDecisionApproveForSessionApproval")) @@ -43230,11 +44324,11 @@ def from_dict(obj: Any) -> 'RPC': permission_decision_approve_for_session_approval_extension_env_access = PermissionDecisionApproveForSessionApprovalExtensionEnvAccess.from_dict(obj.get("PermissionDecisionApproveForSessionApprovalExtensionEnvAccess")) permission_decision_approve_for_session_approval_extension_management = PermissionDecisionApproveForSessionApprovalExtensionManagement.from_dict(obj.get("PermissionDecisionApproveForSessionApprovalExtensionManagement")) permission_decision_approve_for_session_approval_extension_permission_access = PermissionDecisionApproveForSessionApprovalExtensionPermissionAccess.from_dict(obj.get("PermissionDecisionApproveForSessionApprovalExtensionPermissionAccess")) + permission_decision_approve_for_session_approval_factory = PermissionDecisionApproveForSessionApprovalFactory.from_dict(obj.get("PermissionDecisionApproveForSessionApprovalFactory")) permission_decision_approve_for_session_approval_mcp = PermissionDecisionApproveForSessionApprovalMCP.from_dict(obj.get("PermissionDecisionApproveForSessionApprovalMcp")) permission_decision_approve_for_session_approval_mcp_sampling = PermissionDecisionApproveForSessionApprovalMCPSampling.from_dict(obj.get("PermissionDecisionApproveForSessionApprovalMcpSampling")) permission_decision_approve_for_session_approval_memory = PermissionDecisionApproveForSessionApprovalMemory.from_dict(obj.get("PermissionDecisionApproveForSessionApprovalMemory")) permission_decision_approve_for_session_approval_read = PermissionDecisionApproveForSessionApprovalRead.from_dict(obj.get("PermissionDecisionApproveForSessionApprovalRead")) - permission_decision_approve_for_session_approval_workflow = PermissionDecisionApproveForSessionApprovalWorkflow.from_dict(obj.get("PermissionDecisionApproveForSessionApprovalWorkflow")) permission_decision_approve_for_session_approval_write = PermissionDecisionApproveForSessionApprovalWrite.from_dict(obj.get("PermissionDecisionApproveForSessionApprovalWrite")) permission_decision_approve_once = PermissionDecisionApproveOnce.from_dict(obj.get("PermissionDecisionApproveOnce")) permission_decision_approve_permanently = PermissionDecisionApprovePermanently.from_dict(obj.get("PermissionDecisionApprovePermanently")) @@ -43284,11 +44378,11 @@ def from_dict(obj: Any) -> 'RPC': permissions_locations_add_tool_approval_details_extension_env_access = PermissionsLocationsAddToolApprovalDetailsExtensionEnvAccess.from_dict(obj.get("PermissionsLocationsAddToolApprovalDetailsExtensionEnvAccess")) permissions_locations_add_tool_approval_details_extension_management = PermissionsLocationsAddToolApprovalDetailsExtensionManagement.from_dict(obj.get("PermissionsLocationsAddToolApprovalDetailsExtensionManagement")) permissions_locations_add_tool_approval_details_extension_permission_access = PermissionsLocationsAddToolApprovalDetailsExtensionPermissionAccess.from_dict(obj.get("PermissionsLocationsAddToolApprovalDetailsExtensionPermissionAccess")) + permissions_locations_add_tool_approval_details_factory = PermissionsLocationsAddToolApprovalDetailsFactory.from_dict(obj.get("PermissionsLocationsAddToolApprovalDetailsFactory")) permissions_locations_add_tool_approval_details_mcp = PermissionsLocationsAddToolApprovalDetailsMCP.from_dict(obj.get("PermissionsLocationsAddToolApprovalDetailsMcp")) permissions_locations_add_tool_approval_details_mcp_sampling = PermissionsLocationsAddToolApprovalDetailsMCPSampling.from_dict(obj.get("PermissionsLocationsAddToolApprovalDetailsMcpSampling")) permissions_locations_add_tool_approval_details_memory = PermissionsLocationsAddToolApprovalDetailsMemory.from_dict(obj.get("PermissionsLocationsAddToolApprovalDetailsMemory")) permissions_locations_add_tool_approval_details_read = PermissionsLocationsAddToolApprovalDetailsRead.from_dict(obj.get("PermissionsLocationsAddToolApprovalDetailsRead")) - permissions_locations_add_tool_approval_details_workflow = PermissionsLocationsAddToolApprovalDetailsWorkflow.from_dict(obj.get("PermissionsLocationsAddToolApprovalDetailsWorkflow")) permissions_locations_add_tool_approval_details_write = PermissionsLocationsAddToolApprovalDetailsWrite.from_dict(obj.get("PermissionsLocationsAddToolApprovalDetailsWrite")) permissions_locations_add_tool_approval_result = PermissionsLocationsAddToolApprovalResult.from_dict(obj.get("PermissionsLocationsAddToolApprovalResult")) permissions_modify_rules_params = PermissionsModifyRulesParams.from_dict(obj.get("PermissionsModifyRulesParams")) @@ -43358,7 +44452,6 @@ def from_dict(obj: Any) -> 'RPC': provider_config_transport = ProviderTransport(obj.get("ProviderConfigTransport")) provider_config_type = ProviderType(obj.get("ProviderConfigType")) provider_config_wire_api = ProviderWireAPI(obj.get("ProviderConfigWireApi")) - provider_descriptor = ProviderDescriptor.from_dict(obj.get("ProviderDescriptor")) provider_endpoint = ProviderEndpoint.from_dict(obj.get("ProviderEndpoint")) provider_endpoint_transport = ProviderTransport(obj.get("ProviderEndpointTransport")) provider_endpoint_type = ProviderType(obj.get("ProviderEndpointType")) @@ -43451,6 +44544,7 @@ def from_dict(obj: Any) -> 'RPC': remote_session_mode = RemoteSessionMode(obj.get("RemoteSessionMode")) remote_session_repository = RemoteSessionRepository.from_dict(obj.get("RemoteSessionRepository")) response_format = ResponseFormat.from_dict(obj.get("ResponseFormat")) + run_options = RunOptions.from_dict(obj.get("RunOptions")) sandbox_config = SandboxConfig.from_dict(obj.get("SandboxConfig")) sandbox_config_auth = SandboxConfigAuth.from_dict(obj.get("SandboxConfigAuth")) sandbox_config_source = _SandboxConfigSource(obj.get("SandboxConfigSource")) @@ -43508,6 +44602,7 @@ def from_dict(obj: Any) -> 'RPC': session_context = SessionContext.from_dict(obj.get("SessionContext")) session_context_host_type = HostType(obj.get("SessionContextHostType")) session_enrich_metadata_result = SessionEnrichMetadataResult.from_dict(obj.get("SessionEnrichMetadataResult")) + session_factory_pause_at_checkpoint_result = SessionFactoryPauseAtCheckpointResult.from_dict(obj.get("SessionFactoryPauseAtCheckpointResult")) session_fs_append_file_request = SessionFSAppendFileRequest.from_dict(obj.get("SessionFsAppendFileRequest")) session_fs_error = SessionFSError.from_dict(obj.get("SessionFsError")) session_fs_error_code = SessionFSErrorCode(obj.get("SessionFsErrorCode")) @@ -43851,7 +44946,7 @@ def from_dict(obj: Any) -> 'RPC': workflow_cancel_request = WorkflowCancelRequest.from_dict(obj.get("WorkflowCancelRequest")) workflow_current_phase = WorkflowCurrentPhase.from_dict(obj.get("WorkflowCurrentPhase")) workflow_declared_limits = WorkflowDeclaredLimits.from_dict(obj.get("WorkflowDeclaredLimits")) - workflow_durable_operation = WorkflowDurableOperation(obj.get("WorkflowDurableOperation")) + workflow_durable_operation = DurableOperation(obj.get("WorkflowDurableOperation")) workflow_execute_request = WorkflowExecuteRequest.from_dict(obj.get("WorkflowExecuteRequest")) workflow_execute_result = WorkflowExecuteResult.from_dict(obj.get("WorkflowExecuteResult")) workflow_get_run_progress_request = WorkflowGetRunProgressRequest.from_dict(obj.get("WorkflowGetRunProgressRequest")) @@ -43862,15 +44957,15 @@ def from_dict(obj: Any) -> 'RPC': workflow_list_runs_request = WorkflowListRunsRequest.from_dict(obj.get("WorkflowListRunsRequest")) workflow_list_runs_result = WorkflowListRunsResult.from_dict(obj.get("WorkflowListRunsResult")) workflow_log_line = WorkflowLogLine.from_dict(obj.get("WorkflowLogLine")) - workflow_log_line_kind = WorkflowLogLineKind(obj.get("WorkflowLogLineKind")) + workflow_log_line_kind = LogLineKind(obj.get("WorkflowLogLineKind")) workflow_log_request = WorkflowLogRequest.from_dict(obj.get("WorkflowLogRequest")) - workflow_pause_checkpoint_action = WorkflowPauseCheckpointAction(obj.get("WorkflowPauseCheckpointAction")) + workflow_pause_checkpoint_action = PauseCheckpointAction(obj.get("WorkflowPauseCheckpointAction")) workflow_pause_checkpoint_request = WorkflowPauseCheckpointRequest.from_dict(obj.get("WorkflowPauseCheckpointRequest")) workflow_pause_checkpoint_result = WorkflowPauseCheckpointResult.from_dict(obj.get("WorkflowPauseCheckpointResult")) workflow_pause_info = WorkflowPauseInfo.from_dict(obj.get("WorkflowPauseInfo")) workflow_pause_request = WorkflowPauseRequest.from_dict(obj.get("WorkflowPauseRequest")) workflow_phase_observation = WorkflowPhaseObservation.from_dict(obj.get("WorkflowPhaseObservation")) - workflow_phase_status = WorkflowPhaseStatus(obj.get("WorkflowPhaseStatus")) + workflow_phase_status = PhaseStatus(obj.get("WorkflowPhaseStatus")) workflow_progress_line = WorkflowProgressLine.from_dict(obj.get("WorkflowProgressLine")) workflow_progress_page = WorkflowProgressPage.from_dict(obj.get("WorkflowProgressPage")) workflow_resume_request = WorkflowResumeRequest.from_dict(obj.get("WorkflowResumeRequest")) @@ -43878,12 +44973,12 @@ def from_dict(obj: Any) -> 'RPC': workflow_run_consumed = WorkflowRunConsumed.from_dict(obj.get("WorkflowRunConsumed")) workflow_run_detail = WorkflowRunDetail.from_dict(obj.get("WorkflowRunDetail")) workflow_run_failure = WorkflowRunFailure.from_dict(obj.get("WorkflowRunFailure")) - workflow_run_failure_kind = WorkflowRunFailureKind(obj.get("WorkflowRunFailureKind")) + workflow_run_failure_kind = RunFailureKind(obj.get("WorkflowRunFailureKind")) workflow_run_limits = WorkflowRunLimits.from_dict(obj.get("WorkflowRunLimits")) workflow_run_options = WorkflowRunOptions.from_dict(obj.get("WorkflowRunOptions")) workflow_run_request = WorkflowRunRequest.from_dict(obj.get("WorkflowRunRequest")) workflow_run_result = WorkflowRunResult.from_dict(obj.get("WorkflowRunResult")) - workflow_run_status = WorkflowRunStatus(obj.get("WorkflowRunStatus")) + workflow_run_status = RunStatus(obj.get("WorkflowRunStatus")) workflow_run_summary = WorkflowRunSummary.from_dict(obj.get("WorkflowRunSummary")) workflow_run_terminal = WorkflowRunTerminal.from_dict(obj.get("WorkflowRunTerminal")) workflow_tool_resume_request = _WorkflowToolResumeRequest.from_dict(obj.get("WorkflowToolResumeRequest")) @@ -43928,7 +45023,7 @@ def from_dict(obj: Any) -> 'RPC': subagent_settings = from_union([SubagentSettings.from_dict, from_none], obj.get("SubagentSettings")) task_progress = from_union([TaskProgress.from_dict, from_none], obj.get("TaskProgress")) workspace_summary = from_union([WorkspaceSummary.from_dict, from_none], obj.get("WorkspaceSummary")) - return RPC(abort_request, abort_result, accepted_enqueue_command_result, account_all_users, account_get_all_users_result, account_get_current_auth_result, account_get_quota_request, account_get_quota_result, account_kind, account_login_request, account_login_result, account_logout_request, account_logout_result, account_quota_snapshot, accounts_enumerate_request, accounts_get_request, accounts_set_request, account_status, adaptive_thinking_support, agent_discovery_path, agent_discovery_path_list, agent_discovery_path_scope, agent_get_current_result, agent_info, agent_info_source, agent_list, agent_list_request, agent_registry_live_target_entry, agent_registry_live_target_entry_attention_kind, agent_registry_live_target_entry_kind, agent_registry_live_target_entry_last_terminal_event, agent_registry_live_target_entry_status, agent_registry_log_capture, agent_registry_log_capture_open_error_reason, agent_registry_spawn_error, agent_registry_spawn_permission_mode, agent_registry_spawn_registry_timeout, agent_registry_spawn_request, agent_registry_spawn_result, agent_registry_spawn_spawned, agent_registry_spawn_validation_error, agent_registry_spawn_validation_error_field, agent_registry_spawn_validation_error_reason, agent_reload_result, agents_discover_request, agent_select_request, agent_select_result, agent_set_prompt_request, agents_get_discovery_paths_request, api_key_auth_info, auth_enumerate_query, auth_enumerate_value, auth_identity, auth_info, auth_info_type, auth_login_advance_request, auth_login_begin_request, auth_login_begun, auth_login_cancel_request, auth_login_result_dto, auth_login_result_status, auth_login_step, auth_read_query, auth_read_value, auth_status_dto, auth_validation_error, auth_validation_errors, auth_write, auth_write_result, autopilot_objective_credit_limit, autopilot_objective_get_state_result, autopilot_objective_state, autopilot_objective_status, built_in_model_catalog, built_in_model_catalog_entry, builtin_tool_descriptor, builtin_tool_format, builtin_tool_format_type, builtin_tool_input_schema, builtin_tool_input_schema_type, builtin_tool_safe_for_telemetry, builtin_tool_safe_telemetry_fields, cancel_user_requested_shell_command_result, canvas_action, canvas_action_invoke_request, canvas_action_invoke_result, canvas_close_request, canvas_host_context, canvas_host_context_capabilities, canvas_json_schema, canvas_list, canvas_list_open_result, canvas_open_request, canvas_provider_close_request, canvas_provider_invoke_action_request, canvas_provider_open_request, canvas_provider_open_result, canvas_provider_register_request, canvas_provider_unregister_request, canvas_session_context, capi_session_options, card_digest, card_digest_algorithm, card_digest_value, catalog_agent_plugin_candidate, catalog_agent_plugin_candidate_kind, catalog_agent_plugin_candidate_provenance, catalog_agent_plugin_compatibility_tag, catalog_agent_plugin_media_type, catalog_ai_skill_candidate, catalog_ai_skill_candidate_kind, catalog_ai_skill_candidate_provenance, catalog_ai_skill_installability, catalog_ai_skill_media_type, catalog_authentication_required_error, catalog_authentication_required_reason, catalog_candidate, catalog_candidate_kind, catalog_candidate_source, catalog_candidate_source_embedded, catalog_candidate_source_url, catalog_capability, catalog_capability_id, catalog_client_contract, catalog_contract_violation_error, catalog_contract_violation_reason, catalog_handle_rejected_error, catalog_handle_rejection_reason, catalog_handle_type, catalog_invalid_request_error, catalog_invalid_request_field, catalog_malformed_card_error, catalog_malformed_card_reason, catalog_mcp_server_candidate, catalog_mcp_server_candidate_kind, catalog_mcp_server_candidate_provenance, catalog_mcp_server_installability, catalog_media_type, catalog_negotiated_contract, catalog_negotiation_refused_error, catalog_negotiation_refused_reason, catalog_network_failure_error, catalog_network_failure_reason, catalog_not_installable_error, catalog_not_installable_reason, catalog_plugin_repository_source, catalog_policy_rejected_error, catalog_resource_identity, catalog_resource_version, catalog_search_page, catalog_search_pagination, catalog_search_request, catalog_search_result, catalog_search_succeeded, catalog_search_total_count_relation, catalog_selection_cancelled, catalog_selection_decision, catalog_selection_declined, catalog_selection_foreign, catalog_selection_invalid, catalog_selection_replayed, catalog_selection_request, catalog_selection_result, catalog_selection_selected, catalog_selection_stale, catalog_selection_timed_out, catalog_selection_wrong_kind, catalog_trust_eligibility, catalog_trust_provenance, catalog_trust_snapshot, catalog_trust_snapshot_absent, catalog_trust_snapshot_absent_status, catalog_trust_snapshot_current, catalog_trust_snapshot_current_status, catalog_trust_snapshot_downgraded, catalog_trust_snapshot_downgraded_status, catalog_trust_snapshot_malformed, catalog_trust_snapshot_malformed_status, catalog_trust_snapshot_revoked, catalog_trust_snapshot_revoked_status, catalog_trust_snapshot_schema_version, catalog_trust_snapshot_stale, catalog_trust_snapshot_stale_status, catalog_trust_snapshot_unsupported, catalog_trust_snapshot_unsupported_status, catalog_trust_source, catalog_trust_tier, catalog_unavailable_error, catalog_unavailable_reason, catalog_unavailable_transport_error, catalog_unavailable_transport_reason, catalog_unsafe_retrieval_error, catalog_unsafe_retrieval_reason, catalog_unsupported_kind_error, client_metadata, client_task_cancel_reason, client_task_cancel_request, client_task_cancel_result, command_list, commands_finalize_invocation_effect_request, commands_finalize_invocation_effect_result, commands_handle_pending_command_request, commands_handle_pending_command_result, commands_invocation_effect_outcome, commands_invocation_origin, commands_invoke_request, commands_list_request, commands_respond_to_queued_command_request, commands_respond_to_queued_command_result, completions_get_trigger_characters_result, completions_request_request, completions_request_result, configure_session_extensions_params, connect_client_info, connected_remote_session_metadata, connected_remote_session_metadata_kind, connected_remote_session_metadata_repository, connector_account_request, connector_authorization_requirement, connector_authorization_scope, connector_availability, connector_capabilities, connector_catalog_entry, connector_catalog_result, connector_catalog_status, connector_connect_request, connector_connect_result, connector_continue_request, connector_disconnect_result, connector_mcp_status, connector_reconcile_request, connector_runtime_status, connector_status, connect_remote_session_params, connect_request, connect_result, content_exclusion_check_paths_request, content_exclusion_check_paths_result, content_exclusion_path_check, content_filter_mode, context_heaviest_message, copilot_api_token_auth_info, copilot_user_response, copilot_user_response_endpoints, copilot_user_response_quota_snapshots, copilot_user_response_quota_snapshots_chat, copilot_user_response_quota_snapshots_completions, copilot_user_response_quota_snapshots_premium_interactions, current_model, current_tool_metadata, debug_collect_logs_collected_entry, debug_collect_logs_destination, debug_collect_logs_entry, debug_collect_logs_entry_kind, debug_collect_logs_include, debug_collect_logs_redaction, debug_collect_logs_request, debug_collect_logs_result, debug_collect_logs_result_kind, debug_collect_logs_skipped_entry, debug_collect_logs_source, diagnostic_cursor_status, diagnostic_entry, diagnostic_log_level, diagnostics_configuration, diagnostics_configure_request, diagnostic_severity, diagnostic_source, diagnostic_sources_configuration, diagnostics_read_request, diagnostics_read_result, discovered_canvas, discovered_extension, discovered_extension_mode, discovered_extension_plugin, discovered_extensions, discovered_extensions_disable_request, discovered_extensions_enable_request, discovered_extension_source, discovered_hook, discovered_mcp_server, discovered_mcp_server_type, enqueue_command_params, enqueue_command_result, env_auth_info, event_log_read_request, event_log_release_interest_result, event_log_tail_result, event_log_types, events_agent_scope, events_cursor_status, events_read_direction, events_read_result, execute_command_params, execute_command_result, extension, extension_context_push_input, extension_launch_profile, extension_launch_provider_resolve_request, extension_launch_provider_resolve_result, extension_list, extensions_disable_request, extensions_enable_request, extension_source, extension_status, external_tool_result, external_tool_text_result_for_llm, external_tool_text_result_for_llm_binary_results_for_llm, external_tool_text_result_for_llm_binary_results_for_llm_type, external_tool_text_result_for_llm_content, external_tool_text_result_for_llm_content_audio, external_tool_text_result_for_llm_content_image, external_tool_text_result_for_llm_content_resource, external_tool_text_result_for_llm_content_resource_details, external_tool_text_result_for_llm_content_resource_link, external_tool_text_result_for_llm_content_resource_link_icon, external_tool_text_result_for_llm_content_resource_link_icon_theme, external_tool_text_result_for_llm_content_shell_exit, external_tool_text_result_for_llm_content_terminal, external_tool_text_result_for_llm_content_text, filter_mapping, fleet_start_request, fleet_start_result, folder_trust_add_params, folder_trust_check_params, folder_trust_check_result, gh_cli_auth_info, git_hub_telemetry_client_info, git_hub_telemetry_event, git_hub_telemetry_notification, git_hub_token_acquire_reason, git_hub_token_acquire_request, git_hub_token_acquire_result, handle_pending_tool_call_request, handle_pending_tool_call_result, history_abort_manual_compaction_result, history_cancel_background_compaction_result, history_clear_context_request, history_clear_context_result, history_compact_context_window, history_compact_request, history_compact_result, history_file_restore_skip_reason, history_list_rewind_points_result, history_preview_rewind_request, history_preview_rewind_result, history_rewind_change_type, history_rewind_file_preview, history_rewind_mode, history_rewind_outcome, history_rewind_point, history_rewind_request, history_rewind_result, history_rewind_unavailable_reason, history_skipped_file_restore, history_summarize_for_handoff_result, history_truncate_request, history_truncate_result, hmac_auth_info, hook_invoke_request, hook_invoke_response, hook_origin, hooks_discover_request, hooks_discover_result, hook_type, installed_plugin, installed_plugin_info, installed_plugin_source, installed_plugin_source_git_hub, installed_plugin_source_local, installed_plugin_source_url, instruction_discovery_path, instruction_discovery_path_kind, instruction_discovery_path_list, instruction_discovery_path_location, instructions_discover_request, instructions_get_discovery_paths_request, instructions_get_sources_result, instruction_source, instruction_source_location, instruction_source_type, interrupt_main_turn_request, interrupt_main_turn_result, json_schema_response_format, llm_inference_headers, llm_inference_http_request_chunk_request, llm_inference_http_request_chunk_result, llm_inference_http_request_start_request, llm_inference_http_request_start_result, llm_inference_http_request_start_transport, llm_inference_http_response_chunk_error, llm_inference_http_response_chunk_request, llm_inference_http_response_chunk_result, llm_inference_http_response_start_request, llm_inference_http_response_start_result, llm_inference_set_provider_result, local_session_metadata_value, login_provider_kind, log_request, log_result, lsp_initialize_request, managed_mcp_server_config, managed_settings_read_result, managed_settings_resolved_data, marketplace_add_result, marketplace_browse_result, marketplace_info, marketplace_list_result, marketplace_plugin_info, marketplace_refresh_entry, marketplace_refresh_result, marketplace_remove_result, mcp_allowed_server, mcp_apps_call_tool_request, mcp_apps_diagnose_capability, mcp_apps_diagnose_request, mcp_apps_diagnose_result, mcp_apps_diagnose_server, mcp_apps_host_context, mcp_apps_host_context_details, mcp_apps_host_context_details_available_display_mode, mcp_apps_host_context_details_display_mode, mcp_apps_host_context_details_platform, mcp_apps_host_context_details_theme, mcp_apps_list_tools_request, mcp_apps_list_tools_result, mcp_apps_read_resource_request, mcp_apps_read_resource_result, mcp_apps_resource_content, mcp_apps_set_host_context_details, mcp_apps_set_host_context_details_available_display_mode, mcp_apps_set_host_context_details_display_mode, mcp_apps_set_host_context_details_platform, mcp_apps_set_host_context_details_theme, mcp_apps_set_host_context_request, mcp_cancel_sampling_execution_params, mcp_cancel_sampling_execution_result, mcp_config_add_request, mcp_config_disable_request, mcp_config_enable_request, mcp_config_list, mcp_config_remove_request, mcp_config_update_request, mcp_configure_git_hub_request, mcp_configure_git_hub_result, mcp_diagnostic_details, mcp_diagnostic_direction, mcp_diagnostic_kind, mcp_diagnostic_source_configuration, mcp_disable_request, mcp_discover_request, mcp_discover_result, mcp_elicitation_form_mode, mcp_enable_request, mcp_execute_sampling_params, mcp_execute_sampling_request, mcp_execute_sampling_result, mcp_failed_server, mcp_filtered_server, mcp_headers_handle_pending_headers_refresh_request, mcp_headers_handle_pending_headers_refresh_request_request, mcp_headers_handle_pending_headers_refresh_request_result, mcp_host_state, mcp_install_plan, mcp_is_server_running_request, mcp_is_server_running_result, mcp_list_tools_request, mcp_list_tools_result, mcp_oauth_authentication_state_changed_request, mcp_oauth_handle_pending_request, mcp_oauth_handle_pending_result, mcp_oauth_login_grant_type, mcp_oauth_login_request, mcp_oauth_login_result, mcp_oauth_pending_request_response, mcp_oauth_probe_needs_auth_reason, mcp_oauth_probe_request, mcp_oauth_probe_result, mcp_oauth_respond_request, mcp_oauth_respond_result, mcp_plan_configuration_change, mcp_plan_configuration_operation, mcp_plan_enum_value_type, mcp_plan_install_planned, mcp_plan_install_request, mcp_plan_install_result, mcp_plan_install_source, mcp_plan_install_source_candidate, mcp_plan_install_source_candidate_kind, mcp_plan_install_source_card, mcp_plan_install_source_card_kind, mcp_plan_package_install_method, mcp_plan_package_transport, mcp_plan_policy_decision, mcp_plan_policy_result, mcp_plan_policy_source, mcp_plan_provenance, mcp_plan_remote_install_method, mcp_plan_remote_transport, mcp_plan_required_value, mcp_plan_required_value_enum, mcp_plan_required_value_enum_kind, mcp_plan_required_value_scalar, mcp_plan_required_value_scalar_kind, mcp_plan_resource_identity, mcp_plan_scalar_value_type, mcp_plan_scope, mcp_plan_secret_placeholder, mcp_plan_secret_reference, mcp_plan_target, mcp_plan_transport_choice, mcp_plan_transport_choice_package, mcp_plan_transport_choice_remote, mcp_plan_value_category, mcp_register_external_client_request, mcp_reload_config, mcp_reload_with_config_request, mcp_remove_git_hub_result, mcp_resource, mcp_resource_annotations, mcp_resource_content, mcp_resource_icon, mcp_resources_list_request, mcp_resources_list_result, mcp_resources_list_templates_request, mcp_resources_list_templates_result, mcp_resources_read_request, mcp_resources_read_result, mcp_resource_template, mcp_restart_server_request, mcp_safe_for_telemetry, mcp_safe_for_telemetry_fields, mcp_sampling_execution_action, mcp_sampling_execution_result, mcp_serializable_server_config, mcp_server, mcp_server_auth_config, mcp_server_auth_config_redirect_port, mcp_server_card_embedded, mcp_server_card_embedded_kind, mcp_server_card_media_type, mcp_server_card_reference, mcp_server_card_url, mcp_server_card_url_kind, mcp_server_config, mcp_server_config_defer_tools, mcp_server_config_http, mcp_server_config_http_oauth_grant_type, mcp_server_config_http_type, mcp_server_config_memory, mcp_server_config_memory_type, mcp_server_config_stdio, mcp_server_config_stdio_type, mcp_server_failure_info, mcp_server_list, mcp_server_needs_auth_info, mcp_set_env_value_mode_details, mcp_set_env_value_mode_params, mcp_set_env_value_mode_result, mcp_source_file, mcp_source_plugin, mcp_source_ref, mcp_start_server_request, mcp_start_servers_result, mcp_stop_server_request, mcp_task_metadata, mcp_tools, mcp_tool_ui, mcp_tool_ui_visibility, mcp_unregister_external_client_request, memory_configuration, metadata_context_attribution_result, metadata_context_heaviest_messages_request, metadata_context_heaviest_messages_result, metadata_context_info_request, metadata_context_info_result, metadata_is_processing_result, metadata_recompute_context_tokens_request, metadata_recompute_context_tokens_result, metadata_record_context_change_request, metadata_record_context_change_result, metadata_set_working_directory_request, metadata_set_working_directory_result, metadata_snapshot_current_mode, metadata_snapshot_remote_metadata, metadata_snapshot_remote_metadata_repository, metadata_snapshot_remote_metadata_task_type, metadata_update_client_metadata_request, model, model_apply_startup_overlay_request, model_billing, model_billing_promo, model_billing_token_prices, model_billing_token_prices_long_context, model_capabilities, model_capabilities_limits, model_capabilities_limits_vision, model_capabilities_override, model_capabilities_override_limits, model_capabilities_override_limits_vision, model_capabilities_override_supports, model_capabilities_supports, model_list, model_list_request, model_message, model_picker_category, model_picker_persistence_request, model_picker_price_category, model_picker_settings_context, model_policy, model_policy_state, model_set_allowed_models_request, model_set_allowed_models_result, model_set_reasoning_effort_request, model_set_reasoning_effort_result, models_list_request, model_switch_auto_tier_request, model_switch_auto_tier_result, model_switch_auto_tier_status, model_switch_confirmation, model_switch_to_request, model_switch_to_result, model_warning_text, mode_set_request, mode_set_result, move_mcp_loading_to_background_result, named_provider_config, name_get_result, name_set_auto_request, name_set_auto_result, name_set_request, open_canvas_instance, options_update_additional_content_exclusion_policy, options_update_additional_content_exclusion_policy_rule, options_update_additional_content_exclusion_policy_rule_source, options_update_additional_content_exclusion_policy_scope, options_update_context_tier, options_update_env_value_mode, options_update_reasoning_summary, options_update_tool_filter_precedence, pending_permission_request, pending_permission_request_list, permission_decision, permission_decision_approved, permission_decision_approved_for_location, permission_decision_approved_for_session, permission_decision_approve_for_location, permission_decision_approve_for_location_approval, permission_decision_approve_for_location_approval_commands, permission_decision_approve_for_location_approval_custom_tool, permission_decision_approve_for_location_approval_extension_env_access, permission_decision_approve_for_location_approval_extension_management, permission_decision_approve_for_location_approval_extension_permission_access, permission_decision_approve_for_location_approval_mcp, permission_decision_approve_for_location_approval_mcp_sampling, permission_decision_approve_for_location_approval_memory, permission_decision_approve_for_location_approval_read, permission_decision_approve_for_location_approval_workflow, permission_decision_approve_for_location_approval_write, permission_decision_approve_for_session, permission_decision_approve_for_session_approval, permission_decision_approve_for_session_approval_commands, permission_decision_approve_for_session_approval_custom_tool, permission_decision_approve_for_session_approval_extension_env_access, permission_decision_approve_for_session_approval_extension_management, permission_decision_approve_for_session_approval_extension_permission_access, permission_decision_approve_for_session_approval_mcp, permission_decision_approve_for_session_approval_mcp_sampling, permission_decision_approve_for_session_approval_memory, permission_decision_approve_for_session_approval_read, permission_decision_approve_for_session_approval_workflow, permission_decision_approve_for_session_approval_write, permission_decision_approve_once, permission_decision_approve_permanently, permission_decision_cancelled, permission_decision_context, permission_decision_denied_by_content_exclusion_policy, permission_decision_denied_by_permission_request_hook, permission_decision_denied_by_rules, permission_decision_denied_interactively_by_user, permission_decision_denied_no_approval_rule_and_could_not_request_from_user, permission_decision_outcome, permission_decision_reject, permission_decision_request, permission_decision_surface, permission_decision_user_not_available, permission_location_add_tool_approval_params, permission_location_apply_params, permission_location_apply_result, permission_location_resolve_params, permission_location_resolve_result, permission_location_type, permission_mode_source, permission_paths_add_params, permission_paths_allowed_check_params, permission_paths_allowed_check_result, permission_paths_config, permission_paths_list, permission_paths_update_primary_params, permission_paths_workspace_check_params, permission_paths_workspace_check_result, permission_prompt_shown_notification, permission_request_result, permission_response_capability, permission_rules_set, permissions_configure_additional_content_exclusion_policy, permissions_configure_additional_content_exclusion_policy_rule, permissions_configure_additional_content_exclusion_policy_rule_source, permissions_configure_additional_content_exclusion_policy_scope, permissions_configure_params, permissions_configure_result, permissions_folder_trust_add_trusted_result, permissions_get_mode_request, permissions_get_mode_result, permissions_locations_add_tool_approval_details, permissions_locations_add_tool_approval_details_commands, permissions_locations_add_tool_approval_details_custom_tool, permissions_locations_add_tool_approval_details_extension_env_access, permissions_locations_add_tool_approval_details_extension_management, permissions_locations_add_tool_approval_details_extension_permission_access, permissions_locations_add_tool_approval_details_mcp, permissions_locations_add_tool_approval_details_mcp_sampling, permissions_locations_add_tool_approval_details_memory, permissions_locations_add_tool_approval_details_read, permissions_locations_add_tool_approval_details_workflow, permissions_locations_add_tool_approval_details_write, permissions_locations_add_tool_approval_result, permissions_modify_rules_params, permissions_modify_rules_result, permissions_modify_rules_scope, permissions_notify_prompt_shown_result, permissions_paths_add_result, permissions_paths_list_request, permissions_paths_update_primary_result, permissions_pending_requests_request, permissions_reset_session_approvals_request, permissions_reset_session_approvals_result, permissions_set_approve_all_request, permissions_set_approve_all_result, permissions_set_approve_all_source, permissions_set_mode_request, permissions_set_mode_result, permissions_set_required_request, permissions_set_required_result, permissions_urls_set_unrestricted_mode_result, permission_urls_config, permission_urls_set_unrestricted_mode_params, ping_request, ping_result, plan_read_result, plan_read_sql_todos_result, plan_read_sql_todos_with_dependencies_result, plan_sql_todo_dependency, plan_sql_todos_row, plan_update_request, plugin, plugin_install_result, plugin_install_staging_mode, plugin_list, plugin_list_result, plugins_builtin_set_request, plugins_disable_request, plugins_enable_request, plugins_install_request, plugins_marketplaces_add_request, plugins_marketplaces_browse_request, plugins_marketplaces_refresh_request, plugins_marketplaces_remove_request, plugins_reload_request, plugins_uninstall_request, plugins_update_request, plugin_update_all_entry, plugin_update_all_result, plugin_update_result, protocol_append_mode, protocol_customize_mode, protocol_external_tool_defer, protocol_external_tool_definition, protocol_marker_section_override, protocol_replace_mode, protocol_section_override, protocol_static_section_action, protocol_static_section_override, protocol_system_message_append_config, protocol_system_message_config, protocol_system_message_customize_config, protocol_system_message_replace_config, provider_add_request, provider_add_result, provider_config, provider_config_azure, provider_config_transport, provider_config_type, provider_config_wire_api, provider_descriptor, provider_endpoint, provider_endpoint_transport, provider_endpoint_type, provider_endpoint_wire_api, provider_get_endpoint_request, provider_model_config, provider_session_token, provider_sync_request, provider_sync_result, provider_token_acquire_request, provider_token_acquire_result, push_attachment, push_attachment_blob, push_attachment_directory, push_attachment_file, push_attachment_file_line_range, push_attachment_git_hub_actions_job, push_attachment_git_hub_commit, push_attachment_git_hub_file, push_attachment_git_hub_file_diff, push_attachment_git_hub_file_diff_side, push_attachment_git_hub_reference, push_attachment_git_hub_reference_type, push_attachment_git_hub_release, push_attachment_git_hub_repository, push_attachment_git_hub_snippet, push_attachment_git_hub_tree_comparison, push_attachment_git_hub_tree_comparison_side, push_attachment_git_hub_url, push_attachment_selection, push_attachment_selection_details, push_attachment_selection_details_end, push_attachment_selection_details_start, push_git_hub_repo_ref, queue_append_steering_request, queue_begin_deferred_idle_drain_request, queue_begin_deferred_idle_drain_result, queue_consume_system_notifications_request, queued_command_handled, queued_command_not_handled, queued_command_result, queue_defer_session_idle_request, queue_duplicate_at_request, queue_duplicate_at_result, queue_enqueue_resume_pending_result, queue_finish_deferred_idle_drain_request, queue_finish_deferred_idle_drain_result, queue_has_pending_result, queue_insert_at_request, queue_insert_at_result, queue_insert_message, queue_move_item_request, queue_move_item_result, queue_pending_items, queue_pending_items_kind, queue_pending_items_result, queue_remove_at_request, queue_remove_at_result, queue_remove_most_recent_result, queue_send_now_request, queue_send_now_result, queue_set_drain_paused_request, queue_snapshot_result, queue_update_text_request, queue_update_text_result, queue_withdraw_message_request, queue_withdraw_message_result, register_event_interest_params, register_event_interest_result, release_event_interest_params, remote_control_config, remote_control_config_existing_mc_session, remote_control_status, remote_control_status_active, remote_control_status_connecting, remote_control_status_error, remote_control_status_off, remote_control_status_result, remote_control_stop_result, remote_control_transfer_result, remote_enable_request, remote_enable_result, remote_notify_steerable_changed_request, remote_notify_steerable_changed_result, remote_session_connection_result, remote_session_host_status, remote_session_metadata_repository, remote_session_metadata_task_type, remote_session_metadata_value, remote_session_mode, remote_session_repository, response_format, sandbox_config, sandbox_config_auth, sandbox_config_source, sandbox_config_user_policy, sandbox_config_user_policy_experimental, sandbox_config_user_policy_experimental_seatbelt, sandbox_config_user_policy_filesystem, sandbox_config_user_policy_network, sandbox_config_user_policy_network_proxy, sandbox_config_user_policy_seatbelt, sandbox_disable_for_session_request, sandbox_disable_for_session_result, sandbox_enforcement_status, sandbox_host_capability, sandbox_host_capability_name, sandbox_host_support, sandbox_session_change, schedule_add_at_request, schedule_add_cron_request, schedule_add_request, schedule_add_result, schedule_add_self_paced_request, schedule_entry, schedule_has_self_paced_result, schedule_list, schedule_rearm_self_paced_request, schedule_stop_request, schedule_stop_result, secrets_add_filter_values_request, secrets_add_filter_values_result, send_agent_mode, send_attachments_to_message_params, send_message_item, send_messages_request, send_messages_result, send_mode, send_request, send_result, send_system_notification_request, server_agent_list, server_instruction_source_list, server_skill, server_skill_list, session_activity, session_agent_list_request, session_auth_login_request, session_auth_logout_user_request, session_auth_status, session_auth_switch_request, session_bulk_delete_result, session_cancel_all_background_agents_result, session_capability, session_commands_list_request, session_completion_item, session_context, session_context_host_type, session_enrich_metadata_result, session_fs_append_file_request, session_fs_error, session_fs_error_code, session_fs_exists_request, session_fs_exists_result, session_fs_mkdir_request, session_fs_readdir_request, session_fs_readdir_result, session_fs_readdir_with_types_entry, session_fs_readdir_with_types_entry_type, session_fs_readdir_with_types_request, session_fs_readdir_with_types_result, session_fs_read_file_request, session_fs_read_file_result, session_fs_rename_request, session_fs_rm_request, session_fs_set_provider_capabilities, session_fs_set_provider_conventions, session_fs_set_provider_request, session_fs_set_provider_result, session_fs_sqlite_exists_request, session_fs_sqlite_exists_result, session_fs_sqlite_query_request, session_fs_sqlite_query_result, session_fs_sqlite_query_type, session_fs_sqlite_transaction_error, session_fs_sqlite_transaction_error_class, session_fs_sqlite_transaction_request, session_fs_sqlite_transaction_result, session_fs_sqlite_transaction_statement, session_fs_stat_request, session_fs_stat_result, session_fs_write_file_request, session_git_hub_auth_get_all_auth_available_result, session_git_hub_auth_logout_result, session_git_hub_auth_logout_user_result, session_history_compact_request, session_installed_plugin, session_installed_plugin_source, session_installed_plugin_source_git_hub, session_installed_plugin_source_local, session_installed_plugin_source_url, session_limit_prediction_baseline_data, session_limit_prediction_client_type, session_limit_prediction_details, session_limit_prediction_predict_request, session_limit_prediction_request, session_limit_prediction_result, session_limit_prediction_source, session_limit_prediction_tier, session_limit_prediction_tier_option, session_limit_prediction_unavailable_reason, session_list, session_list_entry, session_list_filter, session_load_deferred_repo_hooks_result, session_log_level, session_managed_permissions, session_managed_settings, session_mcp_apps_call_tool_result, session_metadata_snapshot, session_mode, session_model_list, session_model_list_request, session_model_price_category, session_open_options, session_open_options_additional_content_exclusion_policy, session_open_options_additional_content_exclusion_policy_rule, session_open_options_additional_content_exclusion_policy_rule_source, session_open_options_additional_content_exclusion_policy_scope, session_open_options_env_value_mode, session_open_options_reasoning_summary, session_open_params, session_open_result, session_plugins_disable_request, session_plugins_enable_request, session_plugins_install_request, session_plugins_marketplaces_refresh_request, session_plugins_reload_request, session_provider_get_endpoint_request, session_prune_result, sessions_bulk_delete_request, sessions_check_in_use_request, sessions_check_in_use_result, sessions_client_metadata_entry, sessions_close_request, sessions_close_result, sessions_delete_request, sessions_enrich_metadata_request, session_set_credentials_params, session_set_credentials_result, session_settings_built_in_tool_availability_snapshot, session_settings_evaluate_predicate_request, session_settings_evaluate_predicate_result, session_settings_job_snapshot, session_settings_model_snapshot, session_settings_online_evaluation_snapshot, session_settings_predicate_name, session_settings_repo_snapshot, session_settings_snapshot, session_settings_validation_snapshot, sessions_find_by_prefix_request, sessions_find_by_prefix_result, sessions_find_by_task_id_request, sessions_find_by_task_id_result, sessions_fork_request, sessions_fork_result, sessions_get_board_entry_count_request, sessions_get_board_entry_count_result, sessions_get_client_metadata_request, sessions_get_client_metadata_result, sessions_get_event_file_path_request, sessions_get_event_file_path_result, sessions_get_last_for_context_request, sessions_get_last_for_context_result, sessions_get_metadata_request, sessions_get_metadata_result, sessions_get_persisted_remote_steerable_request, sessions_get_persisted_remote_steerable_result, session_sizes, sessions_list_non_empty_session_ids_request, sessions_list_non_empty_session_ids_result, sessions_list_request, sessions_load_deferred_repo_hooks_request, sessions_open_attach, sessions_open_cloud, sessions_open_create, sessions_open_handoff, sessions_open_handoff_task_type, sessions_open_progress, sessions_open_progress_status, sessions_open_progress_step, sessions_open_remote, sessions_open_resume, sessions_open_resume_last, sessions_open_status, session_source, sessions_prune_old_request, sessions_read_persisted_events_request, sessions_release_lock_request, sessions_release_lock_result, sessions_reload_plugin_hooks_request, sessions_reload_plugin_hooks_result, sessions_save_request, sessions_save_result, sessions_set_additional_plugins_request, sessions_set_additional_plugins_result, sessions_set_remote_control_steering_request, sessions_start_remote_control_request, sessions_stop_remote_control_request, sessions_transfer_remote_control_request, session_telemetry_engagement, session_update_options_params, session_update_options_result, session_visibility_status, session_workflow_pause_at_checkpoint_result, session_working_directory_context, session_working_directory_context_host_type, settable_auth_info, settable_token_auth_info, shell_cancel_user_requested_request, shell_credentials, shell_exec_request, shell_exec_result, shell_execute_user_requested_request, shell_init_profile, shell_init_script, shell_init_script_shell, shell_kill_request, shell_kill_result, shell_kill_signal, shell_options, shutdown_request, skill, skill_discovery_path, skill_discovery_path_list, skill_discovery_scope, skill_list, skill_provider_descriptor, skill_provider_list_request, skill_provider_list_result, skill_provider_read_request, skill_provider_read_result, skills_config_set_disabled_skills_request, skills_config_set_skill_disabled_request, skills_disable_request, skills_discover_request, skills_enable_request, skills_get_discovery_paths_request, skills_get_invoked_result, skills_invoked_skill, skills_load_diagnostics, slash_command_add_timeline_entry_result, slash_command_agent_prompt_result, slash_command_completed_result, slash_command_info, slash_command_input, slash_command_input_choice, slash_command_input_completion, slash_command_invocation_result, slash_command_kind, slash_command_model_picker_dialog, slash_command_select_subcommand_option, slash_command_select_subcommand_result, slash_command_set_model_result, slash_command_set_plan_model_result, slash_command_show_dialog_result, slash_command_text_result, slash_command_timeline_entry, subagent_settings_entry, subagent_settings_entry_context_tier, system_message_block, task_agent_info, task_agent_progress, task_client_active_status, task_client_execution_mode, task_client_info, task_client_owner, task_client_owner_kind, task_client_owner_presence, task_client_progress, task_client_status, task_client_type, task_client_update, task_complete_data, task_completion_decision, task_execution_mode, task_info, task_kind, task_list, task_progress_line, tasks_cancel_request, tasks_cancel_result, tasks_get_current_promotable_result, tasks_get_progress_request, tasks_get_progress_result, task_shell_info, task_shell_info_attachment_mode, task_shell_progress, tasks_promote_current_to_background_result, tasks_promote_to_background_request, tasks_promote_to_background_result, tasks_refresh_result, tasks_register_request, tasks_register_result, tasks_remove_request, tasks_remove_result, tasks_send_message_request, tasks_send_message_result, tasks_start_agent_request, tasks_start_agent_result, task_status, tasks_update_request, tasks_update_result, tasks_wait_for_pending_result, telemetry_set_feature_overrides_request, token_auth_info, token_provider_auth_info, tool, tool_list, tool_result, tool_result_expanded, tool_result_new_message, tool_result_type, tools_execute_request, tools_get_builtin_descriptors_request, tools_get_builtin_descriptors_result, tools_get_current_metadata_result, tools_initialize_and_validate_result, tools_list_request, tools_set_request, tools_set_result, tools_shell_descriptor_config, tools_task_complete_event_data_request, tools_update_subagent_settings_result, ui_auto_mode_switch_response, ui_elicitation_array_any_of_field, ui_elicitation_array_any_of_field_items, ui_elicitation_array_any_of_field_items_any_of, ui_elicitation_array_enum_field, ui_elicitation_array_enum_field_items, ui_elicitation_field_value, ui_elicitation_request, ui_elicitation_response, ui_elicitation_response_action, ui_elicitation_response_content, ui_elicitation_result, ui_elicitation_schema, ui_elicitation_schema_property, ui_elicitation_schema_property_boolean, ui_elicitation_schema_property_number, ui_elicitation_schema_property_number_type, ui_elicitation_schema_property_string, ui_elicitation_schema_property_string_format, ui_elicitation_string_enum_field, ui_elicitation_string_one_of_field, ui_elicitation_string_one_of_field_one_of, ui_ephemeral_query_request, ui_ephemeral_query_result, ui_exit_plan_mode_action, ui_exit_plan_mode_response, ui_handle_pending_auto_mode_switch_request, ui_handle_pending_elicitation_request, ui_handle_pending_exit_plan_mode_request, ui_handle_pending_result, ui_handle_pending_sampling_request, ui_handle_pending_sampling_response, ui_handle_pending_session_limits_exhausted_request, ui_handle_pending_user_input_request, ui_register_direct_auto_mode_switch_handler_result, ui_session_limits_exhausted_response, ui_session_limits_exhausted_response_action, ui_unregister_direct_auto_mode_switch_handler_request, ui_unregister_direct_auto_mode_switch_handler_result, ui_user_input_response, unsupported_enqueue_command_result, update_subagent_settings_request, usage_get_metrics_result, usage_metrics_agent_metric, usage_metrics_code_changes, usage_metrics_model_metric, usage_metrics_model_metric_requests, usage_metrics_model_metric_token_detail, usage_metrics_model_metric_usage, usage_metrics_token_detail, user_auth_info, user_requested_shell_command_result, user_setting_metadata, user_settings_get_result, user_settings_set_request, user_settings_set_result, visibility_get_result, visibility_set_request, visibility_set_result, workflow_abort_request, workflow_ack_result, workflow_agent_options, workflow_agent_request, workflow_agent_result, workflow_agent_summary, workflow_cancel_request, workflow_current_phase, workflow_declared_limits, workflow_durable_operation, workflow_execute_request, workflow_execute_result, workflow_get_run_progress_request, workflow_get_run_request, workflow_journal_get_request, workflow_journal_get_result, workflow_journal_put_request, workflow_list_runs_request, workflow_list_runs_result, workflow_log_line, workflow_log_line_kind, workflow_log_request, workflow_pause_checkpoint_action, workflow_pause_checkpoint_request, workflow_pause_checkpoint_result, workflow_pause_info, workflow_pause_request, workflow_phase_observation, workflow_phase_status, workflow_progress_line, workflow_progress_page, workflow_resume_request, workflow_resume_result, workflow_run_consumed, workflow_run_detail, workflow_run_failure, workflow_run_failure_kind, workflow_run_limits, workflow_run_options, workflow_run_request, workflow_run_result, workflow_run_status, workflow_run_summary, workflow_run_terminal, workflow_tool_resume_request, workflow_tool_run_options, workflow_tool_run_request, workspace_diff_file_change, workspace_diff_file_change_type, workspace_diff_mode, workspace_diff_result, workspaces_add_summary_request, workspaces_add_summary_result, workspaces_autopilot_objective_exists_result, workspaces_checkpoints, workspaces_create_directory_request, workspaces_create_file_request, workspaces_delete_autopilot_objective_result, workspaces_diff_request, workspaces_ensure_request, workspaces_get_workspace_result, workspaces_list_checkpoints_result, workspaces_list_files_result, workspaces_read_autopilot_objective_result, workspaces_read_checkpoint_request, workspaces_read_checkpoint_result, workspaces_read_file_request, workspaces_read_file_result, workspaces_remove_path_request, workspaces_rename_path_request, workspaces_save_large_paste_request, workspaces_save_large_paste_result, workspaces_stat_file_request, workspaces_stat_file_result, workspaces_truncate_summaries_request, workspace_summary_host_type, workspaces_update_metadata_request, workspaces_workspace_details_host_type, workspaces_write_autopilot_objective_request, workspaces_write_autopilot_objective_result, session_auth_info_result, session_context_attribution, session_context_info, subagent_settings, task_progress, workspace_summary) + return RPC(abort_request, abort_result, accepted_enqueue_command_result, account_all_users, account_get_all_users_result, account_get_current_auth_result, account_get_quota_request, account_get_quota_result, account_login_request, account_login_result, account_logout_request, account_logout_result, account_quota_snapshot, adaptive_thinking_support, agent_discovery_path, agent_discovery_path_list, agent_discovery_path_scope, agent_get_current_result, agent_info, agent_info_source, agent_list, agent_list_request, agent_registry_live_target_entry, agent_registry_live_target_entry_attention_kind, agent_registry_live_target_entry_kind, agent_registry_live_target_entry_last_terminal_event, agent_registry_live_target_entry_status, agent_registry_log_capture, agent_registry_log_capture_open_error_reason, agent_registry_spawn_error, agent_registry_spawn_permission_mode, agent_registry_spawn_registry_timeout, agent_registry_spawn_request, agent_registry_spawn_result, agent_registry_spawn_spawned, agent_registry_spawn_validation_error, agent_registry_spawn_validation_error_field, agent_registry_spawn_validation_error_reason, agent_reload_result, agents_discover_request, agent_select_request, agent_select_result, agent_set_prompt_request, agents_get_discovery_paths_request, api_key_auth_info, auth_identity, auth_info, auth_info_type, auth_validation_error, auth_validation_errors, autopilot_objective_credit_limit, autopilot_objective_get_state_result, autopilot_objective_state, autopilot_objective_status, built_in_model_catalog, built_in_model_catalog_entry, builtin_tool_descriptor, builtin_tool_format, builtin_tool_format_type, builtin_tool_input_schema, builtin_tool_input_schema_type, builtin_tool_safe_for_telemetry, builtin_tool_safe_telemetry_fields, cancel_user_requested_shell_command_result, canvas_action, canvas_action_invoke_request, canvas_action_invoke_result, canvas_close_request, canvas_host_context, canvas_host_context_capabilities, canvas_json_schema, canvas_list, canvas_list_open_result, canvas_open_request, canvas_provider_close_request, canvas_provider_invoke_action_request, canvas_provider_open_request, canvas_provider_open_result, canvas_provider_register_request, canvas_provider_unregister_request, canvas_session_context, capi_session_options, card_digest, card_digest_algorithm, card_digest_value, catalog_agent_plugin_candidate, catalog_agent_plugin_candidate_kind, catalog_agent_plugin_candidate_provenance, catalog_agent_plugin_compatibility_tag, catalog_agent_plugin_media_type, catalog_ai_skill_candidate, catalog_ai_skill_candidate_kind, catalog_ai_skill_candidate_provenance, catalog_ai_skill_installability, catalog_ai_skill_media_type, catalog_authentication_required_error, catalog_authentication_required_reason, catalog_candidate, catalog_candidate_kind, catalog_candidate_source, catalog_candidate_source_embedded, catalog_candidate_source_url, catalog_capability, catalog_capability_id, catalog_client_contract, catalog_contract_violation_error, catalog_contract_violation_reason, catalog_handle_rejected_error, catalog_handle_rejection_reason, catalog_handle_type, catalog_invalid_request_error, catalog_invalid_request_field, catalog_malformed_card_error, catalog_malformed_card_reason, catalog_mcp_server_candidate, catalog_mcp_server_candidate_kind, catalog_mcp_server_candidate_provenance, catalog_mcp_server_installability, catalog_media_type, catalog_negotiated_contract, catalog_negotiation_refused_error, catalog_negotiation_refused_reason, catalog_network_failure_error, catalog_network_failure_reason, catalog_not_installable_error, catalog_not_installable_reason, catalog_plugin_repository_source, catalog_policy_rejected_error, catalog_resource_identity, catalog_resource_version, catalog_search_page, catalog_search_pagination, catalog_search_request, catalog_search_result, catalog_search_succeeded, catalog_search_total_count_relation, catalog_selection_cancelled, catalog_selection_decision, catalog_selection_declined, catalog_selection_foreign, catalog_selection_invalid, catalog_selection_replayed, catalog_selection_request, catalog_selection_result, catalog_selection_selected, catalog_selection_stale, catalog_selection_timed_out, catalog_selection_wrong_kind, catalog_trust_eligibility, catalog_trust_provenance, catalog_trust_snapshot, catalog_trust_snapshot_absent, catalog_trust_snapshot_absent_status, catalog_trust_snapshot_current, catalog_trust_snapshot_current_status, catalog_trust_snapshot_downgraded, catalog_trust_snapshot_downgraded_status, catalog_trust_snapshot_malformed, catalog_trust_snapshot_malformed_status, catalog_trust_snapshot_revoked, catalog_trust_snapshot_revoked_status, catalog_trust_snapshot_schema_version, catalog_trust_snapshot_stale, catalog_trust_snapshot_stale_status, catalog_trust_snapshot_unsupported, catalog_trust_snapshot_unsupported_status, catalog_trust_source, catalog_trust_tier, catalog_unavailable_error, catalog_unavailable_reason, catalog_unavailable_transport_error, catalog_unavailable_transport_reason, catalog_unsafe_retrieval_error, catalog_unsafe_retrieval_reason, catalog_unsupported_kind_error, client_metadata, client_task_cancel_reason, client_task_cancel_request, client_task_cancel_result, command_list, commands_finalize_invocation_effect_request, commands_finalize_invocation_effect_result, commands_handle_pending_command_request, commands_handle_pending_command_result, commands_invocation_effect_outcome, commands_invocation_origin, commands_invoke_request, commands_list_request, commands_respond_to_queued_command_request, commands_respond_to_queued_command_result, completions_get_trigger_characters_result, completions_request_request, completions_request_result, configure_session_extensions_params, connect_client_info, connected_remote_session_metadata, connected_remote_session_metadata_kind, connected_remote_session_metadata_repository, connector_account_request, connector_authorization_requirement, connector_authorization_scope, connector_availability, connector_capabilities, connector_catalog_entry, connector_catalog_result, connector_catalog_status, connector_connect_request, connector_connect_result, connector_continue_request, connector_disconnect_result, connector_mcp_status, connector_reconcile_request, connector_runtime_status, connector_status, connect_remote_session_params, connect_request, connect_result, content_exclusion_check_paths_request, content_exclusion_check_paths_result, content_exclusion_path_check, content_filter_mode, context_heaviest_message, copilot_api_token_auth_info, copilot_user_response, copilot_user_response_endpoints, copilot_user_response_quota_snapshots, copilot_user_response_quota_snapshots_chat, copilot_user_response_quota_snapshots_completions, copilot_user_response_quota_snapshots_premium_interactions, current_model, current_tool_metadata, debug_collect_logs_collected_entry, debug_collect_logs_destination, debug_collect_logs_entry, debug_collect_logs_entry_kind, debug_collect_logs_include, debug_collect_logs_redaction, debug_collect_logs_request, debug_collect_logs_result, debug_collect_logs_result_kind, debug_collect_logs_skipped_entry, debug_collect_logs_source, diagnostic_cursor_status, diagnostic_entry, diagnostic_log_level, diagnostics_configuration, diagnostics_configure_request, diagnostic_severity, diagnostic_source, diagnostic_sources_configuration, diagnostics_read_request, diagnostics_read_result, discovered_canvas, discovered_extension, discovered_extension_mode, discovered_extension_plugin, discovered_extensions, discovered_extensions_disable_request, discovered_extensions_enable_request, discovered_extension_source, discovered_hook, discovered_mcp_server, discovered_mcp_server_type, enqueue_command_params, enqueue_command_result, env_auth_info, event_log_read_request, event_log_release_interest_result, event_log_tail_result, event_log_types, events_agent_scope, events_cursor_status, events_read_direction, events_read_result, execute_command_params, execute_command_result, extension, extension_context_push_input, extension_launch_profile, extension_launch_provider_resolve_request, extension_launch_provider_resolve_result, extension_list, extensions_disable_request, extensions_enable_request, extension_source, extension_status, external_tool_result, external_tool_text_result_for_llm, external_tool_text_result_for_llm_binary_results_for_llm, external_tool_text_result_for_llm_binary_results_for_llm_type, external_tool_text_result_for_llm_content, external_tool_text_result_for_llm_content_audio, external_tool_text_result_for_llm_content_image, external_tool_text_result_for_llm_content_resource, external_tool_text_result_for_llm_content_resource_details, external_tool_text_result_for_llm_content_resource_link, external_tool_text_result_for_llm_content_resource_link_icon, external_tool_text_result_for_llm_content_resource_link_icon_theme, external_tool_text_result_for_llm_content_shell_exit, external_tool_text_result_for_llm_content_terminal, external_tool_text_result_for_llm_content_text, factory_abort_request, factory_ack_result, factory_agent_options, factory_agent_request, factory_agent_result, factory_agent_summary, factory_cancel_request, factory_current_phase, factory_declared_limits, factory_durable_operation, factory_execute_request, factory_execute_result, factory_get_run_progress_request, factory_get_run_request, factory_journal_get_request, factory_journal_get_result, factory_journal_put_request, factory_list_runs_request, factory_list_runs_result, factory_log_line, factory_log_line_kind, factory_log_request, factory_pause_checkpoint_action, factory_pause_checkpoint_request, factory_pause_checkpoint_result, factory_pause_info, factory_pause_request, factory_phase_observation, factory_phase_status, factory_progress_line, factory_progress_page, factory_resume_request, factory_resume_result, factory_run_consumed, factory_run_detail, factory_run_failure, factory_run_failure_kind, factory_run_limits, factory_run_request, factory_run_result, factory_run_status, factory_run_summary, factory_run_terminal, factory_tool_resume_request, factory_tool_run_options, factory_tool_run_request, filter_mapping, fleet_start_request, fleet_start_result, folder_trust_add_params, folder_trust_check_params, folder_trust_check_result, gh_cli_auth_info, git_hub_telemetry_client_info, git_hub_telemetry_event, git_hub_telemetry_notification, git_hub_token_acquire_reason, git_hub_token_acquire_request, git_hub_token_acquire_result, handle_pending_tool_call_request, handle_pending_tool_call_result, history_abort_manual_compaction_result, history_cancel_background_compaction_result, history_clear_context_request, history_clear_context_result, history_compact_context_window, history_compact_request, history_compact_result, history_file_restore_skip_reason, history_list_rewind_points_result, history_preview_rewind_request, history_preview_rewind_result, history_rewind_change_type, history_rewind_file_preview, history_rewind_mode, history_rewind_outcome, history_rewind_point, history_rewind_request, history_rewind_result, history_rewind_unavailable_reason, history_skipped_file_restore, history_summarize_for_handoff_result, history_truncate_request, history_truncate_result, hmac_auth_info, hook_invoke_request, hook_invoke_response, hook_origin, hooks_discover_request, hooks_discover_result, hook_type, installed_plugin, installed_plugin_info, installed_plugin_source, installed_plugin_source_git_hub, installed_plugin_source_local, installed_plugin_source_url, instruction_discovery_path, instruction_discovery_path_kind, instruction_discovery_path_list, instruction_discovery_path_location, instructions_discover_request, instructions_get_discovery_paths_request, instructions_get_sources_result, instruction_source, instruction_source_location, instruction_source_type, interrupt_main_turn_request, interrupt_main_turn_result, json_schema_response_format, llm_inference_headers, llm_inference_http_request_chunk_request, llm_inference_http_request_chunk_result, llm_inference_http_request_start_request, llm_inference_http_request_start_result, llm_inference_http_request_start_transport, llm_inference_http_response_chunk_error, llm_inference_http_response_chunk_request, llm_inference_http_response_chunk_result, llm_inference_http_response_start_request, llm_inference_http_response_start_result, llm_inference_set_provider_result, local_session_metadata_value, log_request, log_result, lsp_initialize_request, managed_mcp_server_config, managed_settings_read_result, managed_settings_resolved_data, marketplace_add_result, marketplace_browse_result, marketplace_info, marketplace_list_result, marketplace_plugin_info, marketplace_refresh_entry, marketplace_refresh_result, marketplace_remove_result, mcp_allowed_server, mcp_apps_call_tool_request, mcp_apps_diagnose_capability, mcp_apps_diagnose_request, mcp_apps_diagnose_result, mcp_apps_diagnose_server, mcp_apps_host_context, mcp_apps_host_context_details, mcp_apps_host_context_details_available_display_mode, mcp_apps_host_context_details_display_mode, mcp_apps_host_context_details_platform, mcp_apps_host_context_details_theme, mcp_apps_list_tools_request, mcp_apps_list_tools_result, mcp_apps_read_resource_request, mcp_apps_read_resource_result, mcp_apps_resource_content, mcp_apps_set_host_context_details, mcp_apps_set_host_context_details_available_display_mode, mcp_apps_set_host_context_details_display_mode, mcp_apps_set_host_context_details_platform, mcp_apps_set_host_context_details_theme, mcp_apps_set_host_context_request, mcp_cancel_sampling_execution_params, mcp_cancel_sampling_execution_result, mcp_config_add_request, mcp_config_disable_request, mcp_config_enable_request, mcp_config_list, mcp_config_remove_request, mcp_config_update_request, mcp_configure_git_hub_request, mcp_configure_git_hub_result, mcp_diagnostic_details, mcp_diagnostic_direction, mcp_diagnostic_kind, mcp_diagnostic_source_configuration, mcp_disable_request, mcp_discover_request, mcp_discover_result, mcp_elicitation_form_mode, mcp_enable_request, mcp_execute_sampling_params, mcp_execute_sampling_request, mcp_execute_sampling_result, mcp_failed_server, mcp_filtered_server, mcp_headers_handle_pending_headers_refresh_request, mcp_headers_handle_pending_headers_refresh_request_request, mcp_headers_handle_pending_headers_refresh_request_result, mcp_host_state, mcp_install_plan, mcp_is_server_running_request, mcp_is_server_running_result, mcp_list_tools_request, mcp_list_tools_result, mcp_oauth_authentication_state_changed_request, mcp_oauth_handle_pending_request, mcp_oauth_handle_pending_result, mcp_oauth_login_grant_type, mcp_oauth_login_request, mcp_oauth_login_result, mcp_oauth_pending_request_response, mcp_oauth_probe_needs_auth_reason, mcp_oauth_probe_request, mcp_oauth_probe_result, mcp_oauth_respond_request, mcp_oauth_respond_result, mcp_plan_configuration_change, mcp_plan_configuration_operation, mcp_plan_enum_value_type, mcp_plan_install_planned, mcp_plan_install_request, mcp_plan_install_result, mcp_plan_install_source, mcp_plan_install_source_candidate, mcp_plan_install_source_candidate_kind, mcp_plan_install_source_card, mcp_plan_install_source_card_kind, mcp_plan_package_install_method, mcp_plan_package_transport, mcp_plan_policy_decision, mcp_plan_policy_result, mcp_plan_policy_source, mcp_plan_provenance, mcp_plan_remote_install_method, mcp_plan_remote_transport, mcp_plan_required_value, mcp_plan_required_value_enum, mcp_plan_required_value_enum_kind, mcp_plan_required_value_scalar, mcp_plan_required_value_scalar_kind, mcp_plan_resource_identity, mcp_plan_scalar_value_type, mcp_plan_scope, mcp_plan_secret_placeholder, mcp_plan_secret_reference, mcp_plan_target, mcp_plan_transport_choice, mcp_plan_transport_choice_package, mcp_plan_transport_choice_remote, mcp_plan_value_category, mcp_register_external_client_request, mcp_reload_config, mcp_reload_with_config_request, mcp_remove_git_hub_result, mcp_resource, mcp_resource_annotations, mcp_resource_content, mcp_resource_icon, mcp_resources_list_request, mcp_resources_list_result, mcp_resources_list_templates_request, mcp_resources_list_templates_result, mcp_resources_read_request, mcp_resources_read_result, mcp_resource_template, mcp_restart_server_request, mcp_safe_for_telemetry, mcp_safe_for_telemetry_fields, mcp_sampling_execution_action, mcp_sampling_execution_result, mcp_serializable_server_config, mcp_server, mcp_server_auth_config, mcp_server_auth_config_redirect_port, mcp_server_card_embedded, mcp_server_card_embedded_kind, mcp_server_card_media_type, mcp_server_card_reference, mcp_server_card_url, mcp_server_card_url_kind, mcp_server_config, mcp_server_config_defer_tools, mcp_server_config_http, mcp_server_config_http_oauth_grant_type, mcp_server_config_http_type, mcp_server_config_memory, mcp_server_config_memory_type, mcp_server_config_stdio, mcp_server_config_stdio_type, mcp_server_failure_info, mcp_server_list, mcp_server_needs_auth_info, mcp_set_env_value_mode_details, mcp_set_env_value_mode_params, mcp_set_env_value_mode_result, mcp_source_file, mcp_source_plugin, mcp_source_ref, mcp_start_server_request, mcp_start_servers_result, mcp_stop_server_request, mcp_task_metadata, mcp_tools, mcp_tool_ui, mcp_tool_ui_visibility, mcp_unregister_external_client_request, memory_configuration, metadata_context_attribution_result, metadata_context_heaviest_messages_request, metadata_context_heaviest_messages_result, metadata_context_info_request, metadata_context_info_result, metadata_is_processing_result, metadata_recompute_context_tokens_request, metadata_recompute_context_tokens_result, metadata_record_context_change_request, metadata_record_context_change_result, metadata_set_working_directory_request, metadata_set_working_directory_result, metadata_snapshot_current_mode, metadata_snapshot_remote_metadata, metadata_snapshot_remote_metadata_repository, metadata_snapshot_remote_metadata_task_type, metadata_update_client_metadata_request, model, model_apply_startup_overlay_request, model_billing, model_billing_promo, model_billing_token_prices, model_billing_token_prices_long_context, model_capabilities, model_capabilities_limits, model_capabilities_limits_vision, model_capabilities_override, model_capabilities_override_limits, model_capabilities_override_limits_vision, model_capabilities_override_supports, model_capabilities_supports, model_list, model_list_request, model_message, model_picker_category, model_picker_persistence_request, model_picker_price_category, model_picker_settings_context, model_policy, model_policy_state, model_set_allowed_models_request, model_set_allowed_models_result, model_set_reasoning_effort_request, model_set_reasoning_effort_result, models_list_request, model_switch_auto_tier_request, model_switch_auto_tier_result, model_switch_auto_tier_status, model_switch_confirmation, model_switch_to_request, model_switch_to_result, model_warning_text, mode_set_request, mode_set_result, move_mcp_loading_to_background_result, named_provider_config, name_get_result, name_set_auto_request, name_set_auto_result, name_set_request, open_canvas_instance, options_update_additional_content_exclusion_policy, options_update_additional_content_exclusion_policy_rule, options_update_additional_content_exclusion_policy_rule_source, options_update_additional_content_exclusion_policy_scope, options_update_context_tier, options_update_env_value_mode, options_update_reasoning_summary, options_update_tool_filter_precedence, pending_permission_request, pending_permission_request_list, permission_decision, permission_decision_approved, permission_decision_approved_for_location, permission_decision_approved_for_session, permission_decision_approve_for_location, permission_decision_approve_for_location_approval, permission_decision_approve_for_location_approval_commands, permission_decision_approve_for_location_approval_custom_tool, permission_decision_approve_for_location_approval_extension_env_access, permission_decision_approve_for_location_approval_extension_management, permission_decision_approve_for_location_approval_extension_permission_access, permission_decision_approve_for_location_approval_factory, permission_decision_approve_for_location_approval_mcp, permission_decision_approve_for_location_approval_mcp_sampling, permission_decision_approve_for_location_approval_memory, permission_decision_approve_for_location_approval_read, permission_decision_approve_for_location_approval_write, permission_decision_approve_for_session, permission_decision_approve_for_session_approval, permission_decision_approve_for_session_approval_commands, permission_decision_approve_for_session_approval_custom_tool, permission_decision_approve_for_session_approval_extension_env_access, permission_decision_approve_for_session_approval_extension_management, permission_decision_approve_for_session_approval_extension_permission_access, permission_decision_approve_for_session_approval_factory, permission_decision_approve_for_session_approval_mcp, permission_decision_approve_for_session_approval_mcp_sampling, permission_decision_approve_for_session_approval_memory, permission_decision_approve_for_session_approval_read, permission_decision_approve_for_session_approval_write, permission_decision_approve_once, permission_decision_approve_permanently, permission_decision_cancelled, permission_decision_context, permission_decision_denied_by_content_exclusion_policy, permission_decision_denied_by_permission_request_hook, permission_decision_denied_by_rules, permission_decision_denied_interactively_by_user, permission_decision_denied_no_approval_rule_and_could_not_request_from_user, permission_decision_outcome, permission_decision_reject, permission_decision_request, permission_decision_surface, permission_decision_user_not_available, permission_location_add_tool_approval_params, permission_location_apply_params, permission_location_apply_result, permission_location_resolve_params, permission_location_resolve_result, permission_location_type, permission_mode_source, permission_paths_add_params, permission_paths_allowed_check_params, permission_paths_allowed_check_result, permission_paths_config, permission_paths_list, permission_paths_update_primary_params, permission_paths_workspace_check_params, permission_paths_workspace_check_result, permission_prompt_shown_notification, permission_request_result, permission_response_capability, permission_rules_set, permissions_configure_additional_content_exclusion_policy, permissions_configure_additional_content_exclusion_policy_rule, permissions_configure_additional_content_exclusion_policy_rule_source, permissions_configure_additional_content_exclusion_policy_scope, permissions_configure_params, permissions_configure_result, permissions_folder_trust_add_trusted_result, permissions_get_mode_request, permissions_get_mode_result, permissions_locations_add_tool_approval_details, permissions_locations_add_tool_approval_details_commands, permissions_locations_add_tool_approval_details_custom_tool, permissions_locations_add_tool_approval_details_extension_env_access, permissions_locations_add_tool_approval_details_extension_management, permissions_locations_add_tool_approval_details_extension_permission_access, permissions_locations_add_tool_approval_details_factory, permissions_locations_add_tool_approval_details_mcp, permissions_locations_add_tool_approval_details_mcp_sampling, permissions_locations_add_tool_approval_details_memory, permissions_locations_add_tool_approval_details_read, permissions_locations_add_tool_approval_details_write, permissions_locations_add_tool_approval_result, permissions_modify_rules_params, permissions_modify_rules_result, permissions_modify_rules_scope, permissions_notify_prompt_shown_result, permissions_paths_add_result, permissions_paths_list_request, permissions_paths_update_primary_result, permissions_pending_requests_request, permissions_reset_session_approvals_request, permissions_reset_session_approvals_result, permissions_set_approve_all_request, permissions_set_approve_all_result, permissions_set_approve_all_source, permissions_set_mode_request, permissions_set_mode_result, permissions_set_required_request, permissions_set_required_result, permissions_urls_set_unrestricted_mode_result, permission_urls_config, permission_urls_set_unrestricted_mode_params, ping_request, ping_result, plan_read_result, plan_read_sql_todos_result, plan_read_sql_todos_with_dependencies_result, plan_sql_todo_dependency, plan_sql_todos_row, plan_update_request, plugin, plugin_install_result, plugin_install_staging_mode, plugin_list, plugin_list_result, plugins_builtin_set_request, plugins_disable_request, plugins_enable_request, plugins_install_request, plugins_marketplaces_add_request, plugins_marketplaces_browse_request, plugins_marketplaces_refresh_request, plugins_marketplaces_remove_request, plugins_reload_request, plugins_uninstall_request, plugins_update_request, plugin_update_all_entry, plugin_update_all_result, plugin_update_result, protocol_append_mode, protocol_customize_mode, protocol_external_tool_defer, protocol_external_tool_definition, protocol_marker_section_override, protocol_replace_mode, protocol_section_override, protocol_static_section_action, protocol_static_section_override, protocol_system_message_append_config, protocol_system_message_config, protocol_system_message_customize_config, protocol_system_message_replace_config, provider_add_request, provider_add_result, provider_config, provider_config_azure, provider_config_transport, provider_config_type, provider_config_wire_api, provider_endpoint, provider_endpoint_transport, provider_endpoint_type, provider_endpoint_wire_api, provider_get_endpoint_request, provider_model_config, provider_session_token, provider_sync_request, provider_sync_result, provider_token_acquire_request, provider_token_acquire_result, push_attachment, push_attachment_blob, push_attachment_directory, push_attachment_file, push_attachment_file_line_range, push_attachment_git_hub_actions_job, push_attachment_git_hub_commit, push_attachment_git_hub_file, push_attachment_git_hub_file_diff, push_attachment_git_hub_file_diff_side, push_attachment_git_hub_reference, push_attachment_git_hub_reference_type, push_attachment_git_hub_release, push_attachment_git_hub_repository, push_attachment_git_hub_snippet, push_attachment_git_hub_tree_comparison, push_attachment_git_hub_tree_comparison_side, push_attachment_git_hub_url, push_attachment_selection, push_attachment_selection_details, push_attachment_selection_details_end, push_attachment_selection_details_start, push_git_hub_repo_ref, queue_append_steering_request, queue_begin_deferred_idle_drain_request, queue_begin_deferred_idle_drain_result, queue_consume_system_notifications_request, queued_command_handled, queued_command_not_handled, queued_command_result, queue_defer_session_idle_request, queue_duplicate_at_request, queue_duplicate_at_result, queue_enqueue_resume_pending_result, queue_finish_deferred_idle_drain_request, queue_finish_deferred_idle_drain_result, queue_has_pending_result, queue_insert_at_request, queue_insert_at_result, queue_insert_message, queue_move_item_request, queue_move_item_result, queue_pending_items, queue_pending_items_kind, queue_pending_items_result, queue_remove_at_request, queue_remove_at_result, queue_remove_most_recent_result, queue_send_now_request, queue_send_now_result, queue_set_drain_paused_request, queue_snapshot_result, queue_update_text_request, queue_update_text_result, queue_withdraw_message_request, queue_withdraw_message_result, register_event_interest_params, register_event_interest_result, release_event_interest_params, remote_control_config, remote_control_config_existing_mc_session, remote_control_status, remote_control_status_active, remote_control_status_connecting, remote_control_status_error, remote_control_status_off, remote_control_status_result, remote_control_stop_result, remote_control_transfer_result, remote_enable_request, remote_enable_result, remote_notify_steerable_changed_request, remote_notify_steerable_changed_result, remote_session_connection_result, remote_session_host_status, remote_session_metadata_repository, remote_session_metadata_task_type, remote_session_metadata_value, remote_session_mode, remote_session_repository, response_format, run_options, sandbox_config, sandbox_config_auth, sandbox_config_source, sandbox_config_user_policy, sandbox_config_user_policy_experimental, sandbox_config_user_policy_experimental_seatbelt, sandbox_config_user_policy_filesystem, sandbox_config_user_policy_network, sandbox_config_user_policy_network_proxy, sandbox_config_user_policy_seatbelt, sandbox_disable_for_session_request, sandbox_disable_for_session_result, sandbox_enforcement_status, sandbox_host_capability, sandbox_host_capability_name, sandbox_host_support, sandbox_session_change, schedule_add_at_request, schedule_add_cron_request, schedule_add_request, schedule_add_result, schedule_add_self_paced_request, schedule_entry, schedule_has_self_paced_result, schedule_list, schedule_rearm_self_paced_request, schedule_stop_request, schedule_stop_result, secrets_add_filter_values_request, secrets_add_filter_values_result, send_agent_mode, send_attachments_to_message_params, send_message_item, send_messages_request, send_messages_result, send_mode, send_request, send_result, send_system_notification_request, server_agent_list, server_instruction_source_list, server_skill, server_skill_list, session_activity, session_agent_list_request, session_auth_login_request, session_auth_logout_user_request, session_auth_status, session_auth_switch_request, session_bulk_delete_result, session_cancel_all_background_agents_result, session_capability, session_commands_list_request, session_completion_item, session_context, session_context_host_type, session_enrich_metadata_result, session_factory_pause_at_checkpoint_result, session_fs_append_file_request, session_fs_error, session_fs_error_code, session_fs_exists_request, session_fs_exists_result, session_fs_mkdir_request, session_fs_readdir_request, session_fs_readdir_result, session_fs_readdir_with_types_entry, session_fs_readdir_with_types_entry_type, session_fs_readdir_with_types_request, session_fs_readdir_with_types_result, session_fs_read_file_request, session_fs_read_file_result, session_fs_rename_request, session_fs_rm_request, session_fs_set_provider_capabilities, session_fs_set_provider_conventions, session_fs_set_provider_request, session_fs_set_provider_result, session_fs_sqlite_exists_request, session_fs_sqlite_exists_result, session_fs_sqlite_query_request, session_fs_sqlite_query_result, session_fs_sqlite_query_type, session_fs_sqlite_transaction_error, session_fs_sqlite_transaction_error_class, session_fs_sqlite_transaction_request, session_fs_sqlite_transaction_result, session_fs_sqlite_transaction_statement, session_fs_stat_request, session_fs_stat_result, session_fs_write_file_request, session_git_hub_auth_get_all_auth_available_result, session_git_hub_auth_logout_result, session_git_hub_auth_logout_user_result, session_history_compact_request, session_installed_plugin, session_installed_plugin_source, session_installed_plugin_source_git_hub, session_installed_plugin_source_local, session_installed_plugin_source_url, session_limit_prediction_baseline_data, session_limit_prediction_client_type, session_limit_prediction_details, session_limit_prediction_predict_request, session_limit_prediction_request, session_limit_prediction_result, session_limit_prediction_source, session_limit_prediction_tier, session_limit_prediction_tier_option, session_limit_prediction_unavailable_reason, session_list, session_list_entry, session_list_filter, session_load_deferred_repo_hooks_result, session_log_level, session_managed_permissions, session_managed_settings, session_mcp_apps_call_tool_result, session_metadata_snapshot, session_mode, session_model_list, session_model_list_request, session_model_price_category, session_open_options, session_open_options_additional_content_exclusion_policy, session_open_options_additional_content_exclusion_policy_rule, session_open_options_additional_content_exclusion_policy_rule_source, session_open_options_additional_content_exclusion_policy_scope, session_open_options_env_value_mode, session_open_options_reasoning_summary, session_open_params, session_open_result, session_plugins_disable_request, session_plugins_enable_request, session_plugins_install_request, session_plugins_marketplaces_refresh_request, session_plugins_reload_request, session_provider_get_endpoint_request, session_prune_result, sessions_bulk_delete_request, sessions_check_in_use_request, sessions_check_in_use_result, sessions_client_metadata_entry, sessions_close_request, sessions_close_result, sessions_delete_request, sessions_enrich_metadata_request, session_set_credentials_params, session_set_credentials_result, session_settings_built_in_tool_availability_snapshot, session_settings_evaluate_predicate_request, session_settings_evaluate_predicate_result, session_settings_job_snapshot, session_settings_model_snapshot, session_settings_online_evaluation_snapshot, session_settings_predicate_name, session_settings_repo_snapshot, session_settings_snapshot, session_settings_validation_snapshot, sessions_find_by_prefix_request, sessions_find_by_prefix_result, sessions_find_by_task_id_request, sessions_find_by_task_id_result, sessions_fork_request, sessions_fork_result, sessions_get_board_entry_count_request, sessions_get_board_entry_count_result, sessions_get_client_metadata_request, sessions_get_client_metadata_result, sessions_get_event_file_path_request, sessions_get_event_file_path_result, sessions_get_last_for_context_request, sessions_get_last_for_context_result, sessions_get_metadata_request, sessions_get_metadata_result, sessions_get_persisted_remote_steerable_request, sessions_get_persisted_remote_steerable_result, session_sizes, sessions_list_non_empty_session_ids_request, sessions_list_non_empty_session_ids_result, sessions_list_request, sessions_load_deferred_repo_hooks_request, sessions_open_attach, sessions_open_cloud, sessions_open_create, sessions_open_handoff, sessions_open_handoff_task_type, sessions_open_progress, sessions_open_progress_status, sessions_open_progress_step, sessions_open_remote, sessions_open_resume, sessions_open_resume_last, sessions_open_status, session_source, sessions_prune_old_request, sessions_read_persisted_events_request, sessions_release_lock_request, sessions_release_lock_result, sessions_reload_plugin_hooks_request, sessions_reload_plugin_hooks_result, sessions_save_request, sessions_save_result, sessions_set_additional_plugins_request, sessions_set_additional_plugins_result, sessions_set_remote_control_steering_request, sessions_start_remote_control_request, sessions_stop_remote_control_request, sessions_transfer_remote_control_request, session_telemetry_engagement, session_update_options_params, session_update_options_result, session_visibility_status, session_workflow_pause_at_checkpoint_result, session_working_directory_context, session_working_directory_context_host_type, settable_auth_info, settable_token_auth_info, shell_cancel_user_requested_request, shell_credentials, shell_exec_request, shell_exec_result, shell_execute_user_requested_request, shell_init_profile, shell_init_script, shell_init_script_shell, shell_kill_request, shell_kill_result, shell_kill_signal, shell_options, shutdown_request, skill, skill_discovery_path, skill_discovery_path_list, skill_discovery_scope, skill_list, skill_provider_descriptor, skill_provider_list_request, skill_provider_list_result, skill_provider_read_request, skill_provider_read_result, skills_config_set_disabled_skills_request, skills_config_set_skill_disabled_request, skills_disable_request, skills_discover_request, skills_enable_request, skills_get_discovery_paths_request, skills_get_invoked_result, skills_invoked_skill, skills_load_diagnostics, slash_command_add_timeline_entry_result, slash_command_agent_prompt_result, slash_command_completed_result, slash_command_info, slash_command_input, slash_command_input_choice, slash_command_input_completion, slash_command_invocation_result, slash_command_kind, slash_command_model_picker_dialog, slash_command_select_subcommand_option, slash_command_select_subcommand_result, slash_command_set_model_result, slash_command_set_plan_model_result, slash_command_show_dialog_result, slash_command_text_result, slash_command_timeline_entry, subagent_settings_entry, subagent_settings_entry_context_tier, system_message_block, task_agent_info, task_agent_progress, task_client_active_status, task_client_execution_mode, task_client_info, task_client_owner, task_client_owner_kind, task_client_owner_presence, task_client_progress, task_client_status, task_client_type, task_client_update, task_complete_data, task_completion_decision, task_execution_mode, task_info, task_kind, task_list, task_progress_line, tasks_cancel_request, tasks_cancel_result, tasks_get_current_promotable_result, tasks_get_progress_request, tasks_get_progress_result, task_shell_info, task_shell_info_attachment_mode, task_shell_progress, tasks_promote_current_to_background_result, tasks_promote_to_background_request, tasks_promote_to_background_result, tasks_refresh_result, tasks_register_request, tasks_register_result, tasks_remove_request, tasks_remove_result, tasks_send_message_request, tasks_send_message_result, tasks_start_agent_request, tasks_start_agent_result, task_status, tasks_update_request, tasks_update_result, tasks_wait_for_pending_result, telemetry_set_feature_overrides_request, token_auth_info, token_provider_auth_info, tool, tool_list, tool_result, tool_result_expanded, tool_result_new_message, tool_result_type, tools_execute_request, tools_get_builtin_descriptors_request, tools_get_builtin_descriptors_result, tools_get_current_metadata_result, tools_initialize_and_validate_result, tools_list_request, tools_set_request, tools_set_result, tools_shell_descriptor_config, tools_task_complete_event_data_request, tools_update_subagent_settings_result, ui_auto_mode_switch_response, ui_elicitation_array_any_of_field, ui_elicitation_array_any_of_field_items, ui_elicitation_array_any_of_field_items_any_of, ui_elicitation_array_enum_field, ui_elicitation_array_enum_field_items, ui_elicitation_field_value, ui_elicitation_request, ui_elicitation_response, ui_elicitation_response_action, ui_elicitation_response_content, ui_elicitation_result, ui_elicitation_schema, ui_elicitation_schema_property, ui_elicitation_schema_property_boolean, ui_elicitation_schema_property_number, ui_elicitation_schema_property_number_type, ui_elicitation_schema_property_string, ui_elicitation_schema_property_string_format, ui_elicitation_string_enum_field, ui_elicitation_string_one_of_field, ui_elicitation_string_one_of_field_one_of, ui_ephemeral_query_request, ui_ephemeral_query_result, ui_exit_plan_mode_action, ui_exit_plan_mode_response, ui_handle_pending_auto_mode_switch_request, ui_handle_pending_elicitation_request, ui_handle_pending_exit_plan_mode_request, ui_handle_pending_result, ui_handle_pending_sampling_request, ui_handle_pending_sampling_response, ui_handle_pending_session_limits_exhausted_request, ui_handle_pending_user_input_request, ui_register_direct_auto_mode_switch_handler_result, ui_session_limits_exhausted_response, ui_session_limits_exhausted_response_action, ui_unregister_direct_auto_mode_switch_handler_request, ui_unregister_direct_auto_mode_switch_handler_result, ui_user_input_response, unsupported_enqueue_command_result, update_subagent_settings_request, usage_get_metrics_result, usage_metrics_agent_metric, usage_metrics_code_changes, usage_metrics_model_metric, usage_metrics_model_metric_requests, usage_metrics_model_metric_token_detail, usage_metrics_model_metric_usage, usage_metrics_token_detail, user_auth_info, user_requested_shell_command_result, user_setting_metadata, user_settings_get_result, user_settings_set_request, user_settings_set_result, visibility_get_result, visibility_set_request, visibility_set_result, workflow_abort_request, workflow_ack_result, workflow_agent_options, workflow_agent_request, workflow_agent_result, workflow_agent_summary, workflow_cancel_request, workflow_current_phase, workflow_declared_limits, workflow_durable_operation, workflow_execute_request, workflow_execute_result, workflow_get_run_progress_request, workflow_get_run_request, workflow_journal_get_request, workflow_journal_get_result, workflow_journal_put_request, workflow_list_runs_request, workflow_list_runs_result, workflow_log_line, workflow_log_line_kind, workflow_log_request, workflow_pause_checkpoint_action, workflow_pause_checkpoint_request, workflow_pause_checkpoint_result, workflow_pause_info, workflow_pause_request, workflow_phase_observation, workflow_phase_status, workflow_progress_line, workflow_progress_page, workflow_resume_request, workflow_resume_result, workflow_run_consumed, workflow_run_detail, workflow_run_failure, workflow_run_failure_kind, workflow_run_limits, workflow_run_options, workflow_run_request, workflow_run_result, workflow_run_status, workflow_run_summary, workflow_run_terminal, workflow_tool_resume_request, workflow_tool_run_options, workflow_tool_run_request, workspace_diff_file_change, workspace_diff_file_change_type, workspace_diff_mode, workspace_diff_result, workspaces_add_summary_request, workspaces_add_summary_result, workspaces_autopilot_objective_exists_result, workspaces_checkpoints, workspaces_create_directory_request, workspaces_create_file_request, workspaces_delete_autopilot_objective_result, workspaces_diff_request, workspaces_ensure_request, workspaces_get_workspace_result, workspaces_list_checkpoints_result, workspaces_list_files_result, workspaces_read_autopilot_objective_result, workspaces_read_checkpoint_request, workspaces_read_checkpoint_result, workspaces_read_file_request, workspaces_read_file_result, workspaces_remove_path_request, workspaces_rename_path_request, workspaces_save_large_paste_request, workspaces_save_large_paste_result, workspaces_stat_file_request, workspaces_stat_file_result, workspaces_truncate_summaries_request, workspace_summary_host_type, workspaces_update_metadata_request, workspaces_workspace_details_host_type, workspaces_write_autopilot_objective_request, workspaces_write_autopilot_objective_result, session_auth_info_result, session_context_attribution, session_context_info, subagent_settings, task_progress, workspace_summary) def to_dict(self) -> dict: result: dict = {} @@ -43940,16 +45035,11 @@ def to_dict(self) -> dict: result["AccountGetCurrentAuthResult"] = to_class(AccountGetCurrentAuthResult, self.account_get_current_auth_result) result["AccountGetQuotaRequest"] = to_class(AccountGetQuotaRequest, self.account_get_quota_request) result["AccountGetQuotaResult"] = to_class(AccountGetQuotaResult, self.account_get_quota_result) - result["AccountKind"] = to_enum(AccountKind, self.account_kind) result["AccountLoginRequest"] = to_class(AccountLoginRequest, self.account_login_request) result["AccountLoginResult"] = to_class(AccountLoginResult, self.account_login_result) result["AccountLogoutRequest"] = to_class(AccountLogoutRequest, self.account_logout_request) result["AccountLogoutResult"] = to_class(AccountLogoutResult, self.account_logout_result) result["AccountQuotaSnapshot"] = to_class(AccountQuotaSnapshot, self.account_quota_snapshot) - result["AccountsEnumerateRequest"] = to_class(AccountsEnumerateRequest, self.accounts_enumerate_request) - result["AccountsGetRequest"] = to_class(AccountsGetRequest, self.accounts_get_request) - result["AccountsSetRequest"] = to_class(AccountsSetRequest, self.accounts_set_request) - result["AccountStatus"] = to_class(AccountStatus, self.account_status) result["AdaptiveThinkingSupport"] = to_enum(AdaptiveThinkingSupport, self.adaptive_thinking_support) result["AgentDiscoveryPath"] = to_class(AgentDiscoveryPath, self.agent_discovery_path) result["AgentDiscoveryPathList"] = to_class(AgentDiscoveryPathList, self.agent_discovery_path_list) @@ -43982,25 +45072,11 @@ def to_dict(self) -> dict: result["AgentSetPromptRequest"] = to_class(AgentSetPromptRequest, self.agent_set_prompt_request) result["AgentsGetDiscoveryPathsRequest"] = to_class(AgentsGetDiscoveryPathsRequest, self.agents_get_discovery_paths_request) result["ApiKeyAuthInfo"] = to_class(APIKeyAuthInfo, self.api_key_auth_info) - result["AuthEnumerateQuery"] = to_class(AuthEnumerateQuery, self.auth_enumerate_query) - result["AuthEnumerateValue"] = to_class(AuthEnumerateValue, self.auth_enumerate_value) result["AuthIdentity"] = to_class(AuthIdentity, self.auth_identity) result["AuthInfo"] = (self.auth_info).to_dict() result["AuthInfoType"] = to_enum(AuthInfoType, self.auth_info_type) - result["AuthLoginAdvanceRequest"] = to_class(AuthLoginAdvanceRequest, self.auth_login_advance_request) - result["AuthLoginBeginRequest"] = to_class(AuthLoginBeginRequest, self.auth_login_begin_request) - result["AuthLoginBegun"] = to_class(AuthLoginBegun, self.auth_login_begun) - result["AuthLoginCancelRequest"] = to_class(AuthLoginCancelRequest, self.auth_login_cancel_request) - result["AuthLoginResultDto"] = to_class(AuthLoginResultDto, self.auth_login_result_dto) - result["AuthLoginResultStatus"] = to_enum(AuthLoginResultStatus, self.auth_login_result_status) - result["AuthLoginStep"] = to_class(AuthLoginStep, self.auth_login_step) - result["AuthReadQuery"] = to_class(AuthReadQuery, self.auth_read_query) - result["AuthReadValue"] = to_class(AuthReadValue, self.auth_read_value) - result["AuthStatusDto"] = to_class(AuthStatusDto, self.auth_status_dto) result["AuthValidationError"] = to_class(AuthValidationError, self.auth_validation_error) result["AuthValidationErrors"] = from_list(lambda x: to_class(AuthValidationError, x), self.auth_validation_errors) - result["AuthWrite"] = to_class(AuthWrite, self.auth_write) - result["AuthWriteResult"] = to_class(AuthWriteResult, self.auth_write_result) result["AutopilotObjectiveCreditLimit"] = to_class(AutopilotObjectiveCreditLimit, self.autopilot_objective_credit_limit) result["AutopilotObjectiveGetStateResult"] = to_class(AutopilotObjectiveGetStateResult, self.autopilot_objective_get_state_result) result["AutopilotObjectiveState"] = to_class(AutopilotObjectiveState, self.autopilot_objective_state) @@ -44252,6 +45328,52 @@ def to_dict(self) -> dict: result["ExternalToolTextResultForLlmContentShellExit"] = to_class(ExternalToolTextResultForLlmContentShellExit, self.external_tool_text_result_for_llm_content_shell_exit) result["ExternalToolTextResultForLlmContentTerminal"] = to_class(ExternalToolTextResultForLlmContentTerminal, self.external_tool_text_result_for_llm_content_terminal) result["ExternalToolTextResultForLlmContentText"] = to_class(ExternalToolTextResultForLlmContentText, self.external_tool_text_result_for_llm_content_text) + result["FactoryAbortRequest"] = to_class(FactoryAbortRequest, self.factory_abort_request) + result["FactoryAckResult"] = to_class(FactoryACKResult, self.factory_ack_result) + result["FactoryAgentOptions"] = to_class(FactoryAgentOptions, self.factory_agent_options) + result["FactoryAgentRequest"] = to_class(FactoryAgentRequest, self.factory_agent_request) + result["FactoryAgentResult"] = to_class(FactoryAgentResult, self.factory_agent_result) + result["FactoryAgentSummary"] = to_class(FactoryAgentSummary, self.factory_agent_summary) + result["FactoryCancelRequest"] = to_class(FactoryCancelRequest, self.factory_cancel_request) + result["FactoryCurrentPhase"] = to_class(FactoryCurrentPhase, self.factory_current_phase) + result["FactoryDeclaredLimits"] = to_class(FactoryDeclaredLimits, self.factory_declared_limits) + result["FactoryDurableOperation"] = to_enum(DurableOperation, self.factory_durable_operation) + result["FactoryExecuteRequest"] = to_class(FactoryExecuteRequest, self.factory_execute_request) + result["FactoryExecuteResult"] = to_class(FactoryExecuteResult, self.factory_execute_result) + result["FactoryGetRunProgressRequest"] = to_class(FactoryGetRunProgressRequest, self.factory_get_run_progress_request) + result["FactoryGetRunRequest"] = to_class(FactoryGetRunRequest, self.factory_get_run_request) + result["FactoryJournalGetRequest"] = to_class(FactoryJournalGetRequest, self.factory_journal_get_request) + result["FactoryJournalGetResult"] = to_class(FactoryJournalGetResult, self.factory_journal_get_result) + result["FactoryJournalPutRequest"] = to_class(FactoryJournalPutRequest, self.factory_journal_put_request) + result["FactoryListRunsRequest"] = to_class(FactoryListRunsRequest, self.factory_list_runs_request) + result["FactoryListRunsResult"] = to_class(FactoryListRunsResult, self.factory_list_runs_result) + result["FactoryLogLine"] = to_class(FactoryLogLine, self.factory_log_line) + result["FactoryLogLineKind"] = to_enum(LogLineKind, self.factory_log_line_kind) + result["FactoryLogRequest"] = to_class(FactoryLogRequest, self.factory_log_request) + result["FactoryPauseCheckpointAction"] = to_enum(PauseCheckpointAction, self.factory_pause_checkpoint_action) + result["FactoryPauseCheckpointRequest"] = to_class(FactoryPauseCheckpointRequest, self.factory_pause_checkpoint_request) + result["FactoryPauseCheckpointResult"] = to_class(FactoryPauseCheckpointResult, self.factory_pause_checkpoint_result) + result["FactoryPauseInfo"] = to_class(FactoryPauseInfo, self.factory_pause_info) + result["FactoryPauseRequest"] = to_class(FactoryPauseRequest, self.factory_pause_request) + result["FactoryPhaseObservation"] = to_class(FactoryPhaseObservation, self.factory_phase_observation) + result["FactoryPhaseStatus"] = to_enum(PhaseStatus, self.factory_phase_status) + result["FactoryProgressLine"] = to_class(FactoryProgressLine, self.factory_progress_line) + result["FactoryProgressPage"] = to_class(FactoryProgressPage, self.factory_progress_page) + result["FactoryResumeRequest"] = to_class(FactoryResumeRequest, self.factory_resume_request) + result["FactoryResumeResult"] = to_class(FactoryResumeResult, self.factory_resume_result) + result["FactoryRunConsumed"] = to_class(FactoryRunConsumed, self.factory_run_consumed) + result["FactoryRunDetail"] = to_class(FactoryRunDetail, self.factory_run_detail) + result["FactoryRunFailure"] = to_class(FactoryRunFailure, self.factory_run_failure) + result["FactoryRunFailureKind"] = to_enum(RunFailureKind, self.factory_run_failure_kind) + result["FactoryRunLimits"] = to_class(FactoryRunLimits, self.factory_run_limits) + result["FactoryRunRequest"] = to_class(FactoryRunRequest, self.factory_run_request) + result["FactoryRunResult"] = to_class(FactoryRunResult, self.factory_run_result) + result["FactoryRunStatus"] = to_enum(RunStatus, self.factory_run_status) + result["FactoryRunSummary"] = to_class(FactoryRunSummary, self.factory_run_summary) + result["FactoryRunTerminal"] = to_class(FactoryRunTerminal, self.factory_run_terminal) + result["FactoryToolResumeRequest"] = to_class(_FactoryToolResumeRequest, self.factory_tool_resume_request) + result["FactoryToolRunOptions"] = to_class(_FactoryToolRunOptions, self.factory_tool_run_options) + result["FactoryToolRunRequest"] = to_class(_FactoryToolRunRequest, self.factory_tool_run_request) result["FilterMapping"] = from_union([lambda x: from_dict(lambda x: to_enum(ContentFilterMode, x), x), lambda x: to_enum(ContentFilterMode, x)], self.filter_mapping) result["FleetStartRequest"] = to_class(FleetStartRequest, self.fleet_start_request) result["FleetStartResult"] = to_class(FleetStartResult, self.fleet_start_result) @@ -44329,7 +45451,6 @@ def to_dict(self) -> dict: result["LlmInferenceHttpResponseStartResult"] = to_class(LlmInferenceHTTPResponseStartResult, self.llm_inference_http_response_start_result) result["LlmInferenceSetProviderResult"] = to_class(LlmInferenceSetProviderResult, self.llm_inference_set_provider_result) result["LocalSessionMetadataValue"] = to_class(LocalSessionMetadataValue, self.local_session_metadata_value) - result["LoginProviderKind"] = to_enum(LoginProviderKind, self.login_provider_kind) result["LogRequest"] = to_class(LogRequest, self.log_request) result["LogResult"] = to_class(LogResult, self.log_result) result["LspInitializeRequest"] = to_class(LspInitializeRequest, self.lsp_initialize_request) @@ -44585,11 +45706,11 @@ def to_dict(self) -> dict: result["PermissionDecisionApproveForLocationApprovalExtensionEnvAccess"] = to_class(PermissionDecisionApproveForLocationApprovalExtensionEnvAccess, self.permission_decision_approve_for_location_approval_extension_env_access) result["PermissionDecisionApproveForLocationApprovalExtensionManagement"] = to_class(PermissionDecisionApproveForLocationApprovalExtensionManagement, self.permission_decision_approve_for_location_approval_extension_management) result["PermissionDecisionApproveForLocationApprovalExtensionPermissionAccess"] = to_class(PermissionDecisionApproveForLocationApprovalExtensionPermissionAccess, self.permission_decision_approve_for_location_approval_extension_permission_access) + result["PermissionDecisionApproveForLocationApprovalFactory"] = to_class(PermissionDecisionApproveForLocationApprovalFactory, self.permission_decision_approve_for_location_approval_factory) result["PermissionDecisionApproveForLocationApprovalMcp"] = to_class(PermissionDecisionApproveForLocationApprovalMCP, self.permission_decision_approve_for_location_approval_mcp) result["PermissionDecisionApproveForLocationApprovalMcpSampling"] = to_class(PermissionDecisionApproveForLocationApprovalMCPSampling, self.permission_decision_approve_for_location_approval_mcp_sampling) result["PermissionDecisionApproveForLocationApprovalMemory"] = to_class(PermissionDecisionApproveForLocationApprovalMemory, self.permission_decision_approve_for_location_approval_memory) result["PermissionDecisionApproveForLocationApprovalRead"] = to_class(PermissionDecisionApproveForLocationApprovalRead, self.permission_decision_approve_for_location_approval_read) - result["PermissionDecisionApproveForLocationApprovalWorkflow"] = to_class(PermissionDecisionApproveForLocationApprovalWorkflow, self.permission_decision_approve_for_location_approval_workflow) result["PermissionDecisionApproveForLocationApprovalWrite"] = to_class(PermissionDecisionApproveForLocationApprovalWrite, self.permission_decision_approve_for_location_approval_write) result["PermissionDecisionApproveForSession"] = to_class(PermissionDecisionApproveForSession, self.permission_decision_approve_for_session) result["PermissionDecisionApproveForSessionApproval"] = (self.permission_decision_approve_for_session_approval).to_dict() @@ -44598,11 +45719,11 @@ def to_dict(self) -> dict: result["PermissionDecisionApproveForSessionApprovalExtensionEnvAccess"] = to_class(PermissionDecisionApproveForSessionApprovalExtensionEnvAccess, self.permission_decision_approve_for_session_approval_extension_env_access) result["PermissionDecisionApproveForSessionApprovalExtensionManagement"] = to_class(PermissionDecisionApproveForSessionApprovalExtensionManagement, self.permission_decision_approve_for_session_approval_extension_management) result["PermissionDecisionApproveForSessionApprovalExtensionPermissionAccess"] = to_class(PermissionDecisionApproveForSessionApprovalExtensionPermissionAccess, self.permission_decision_approve_for_session_approval_extension_permission_access) + result["PermissionDecisionApproveForSessionApprovalFactory"] = to_class(PermissionDecisionApproveForSessionApprovalFactory, self.permission_decision_approve_for_session_approval_factory) result["PermissionDecisionApproveForSessionApprovalMcp"] = to_class(PermissionDecisionApproveForSessionApprovalMCP, self.permission_decision_approve_for_session_approval_mcp) result["PermissionDecisionApproveForSessionApprovalMcpSampling"] = to_class(PermissionDecisionApproveForSessionApprovalMCPSampling, self.permission_decision_approve_for_session_approval_mcp_sampling) result["PermissionDecisionApproveForSessionApprovalMemory"] = to_class(PermissionDecisionApproveForSessionApprovalMemory, self.permission_decision_approve_for_session_approval_memory) result["PermissionDecisionApproveForSessionApprovalRead"] = to_class(PermissionDecisionApproveForSessionApprovalRead, self.permission_decision_approve_for_session_approval_read) - result["PermissionDecisionApproveForSessionApprovalWorkflow"] = to_class(PermissionDecisionApproveForSessionApprovalWorkflow, self.permission_decision_approve_for_session_approval_workflow) result["PermissionDecisionApproveForSessionApprovalWrite"] = to_class(PermissionDecisionApproveForSessionApprovalWrite, self.permission_decision_approve_for_session_approval_write) result["PermissionDecisionApproveOnce"] = to_class(PermissionDecisionApproveOnce, self.permission_decision_approve_once) result["PermissionDecisionApprovePermanently"] = to_class(PermissionDecisionApprovePermanently, self.permission_decision_approve_permanently) @@ -44652,11 +45773,11 @@ def to_dict(self) -> dict: result["PermissionsLocationsAddToolApprovalDetailsExtensionEnvAccess"] = to_class(PermissionsLocationsAddToolApprovalDetailsExtensionEnvAccess, self.permissions_locations_add_tool_approval_details_extension_env_access) result["PermissionsLocationsAddToolApprovalDetailsExtensionManagement"] = to_class(PermissionsLocationsAddToolApprovalDetailsExtensionManagement, self.permissions_locations_add_tool_approval_details_extension_management) result["PermissionsLocationsAddToolApprovalDetailsExtensionPermissionAccess"] = to_class(PermissionsLocationsAddToolApprovalDetailsExtensionPermissionAccess, self.permissions_locations_add_tool_approval_details_extension_permission_access) + result["PermissionsLocationsAddToolApprovalDetailsFactory"] = to_class(PermissionsLocationsAddToolApprovalDetailsFactory, self.permissions_locations_add_tool_approval_details_factory) result["PermissionsLocationsAddToolApprovalDetailsMcp"] = to_class(PermissionsLocationsAddToolApprovalDetailsMCP, self.permissions_locations_add_tool_approval_details_mcp) result["PermissionsLocationsAddToolApprovalDetailsMcpSampling"] = to_class(PermissionsLocationsAddToolApprovalDetailsMCPSampling, self.permissions_locations_add_tool_approval_details_mcp_sampling) result["PermissionsLocationsAddToolApprovalDetailsMemory"] = to_class(PermissionsLocationsAddToolApprovalDetailsMemory, self.permissions_locations_add_tool_approval_details_memory) result["PermissionsLocationsAddToolApprovalDetailsRead"] = to_class(PermissionsLocationsAddToolApprovalDetailsRead, self.permissions_locations_add_tool_approval_details_read) - result["PermissionsLocationsAddToolApprovalDetailsWorkflow"] = to_class(PermissionsLocationsAddToolApprovalDetailsWorkflow, self.permissions_locations_add_tool_approval_details_workflow) result["PermissionsLocationsAddToolApprovalDetailsWrite"] = to_class(PermissionsLocationsAddToolApprovalDetailsWrite, self.permissions_locations_add_tool_approval_details_write) result["PermissionsLocationsAddToolApprovalResult"] = to_class(PermissionsLocationsAddToolApprovalResult, self.permissions_locations_add_tool_approval_result) result["PermissionsModifyRulesParams"] = to_class(PermissionsModifyRulesParams, self.permissions_modify_rules_params) @@ -44726,7 +45847,6 @@ def to_dict(self) -> dict: result["ProviderConfigTransport"] = to_enum(ProviderTransport, self.provider_config_transport) result["ProviderConfigType"] = to_enum(ProviderType, self.provider_config_type) result["ProviderConfigWireApi"] = to_enum(ProviderWireAPI, self.provider_config_wire_api) - result["ProviderDescriptor"] = to_class(ProviderDescriptor, self.provider_descriptor) result["ProviderEndpoint"] = to_class(ProviderEndpoint, self.provider_endpoint) result["ProviderEndpointTransport"] = to_enum(ProviderTransport, self.provider_endpoint_transport) result["ProviderEndpointType"] = to_enum(ProviderType, self.provider_endpoint_type) @@ -44819,6 +45939,7 @@ def to_dict(self) -> dict: result["RemoteSessionMode"] = to_enum(RemoteSessionMode, self.remote_session_mode) result["RemoteSessionRepository"] = to_class(RemoteSessionRepository, self.remote_session_repository) result["ResponseFormat"] = to_class(ResponseFormat, self.response_format) + result["RunOptions"] = to_class(RunOptions, self.run_options) result["SandboxConfig"] = to_class(SandboxConfig, self.sandbox_config) result["SandboxConfigAuth"] = to_class(SandboxConfigAuth, self.sandbox_config_auth) result["SandboxConfigSource"] = to_enum(_SandboxConfigSource, self.sandbox_config_source) @@ -44876,6 +45997,7 @@ def to_dict(self) -> dict: result["SessionContext"] = to_class(SessionContext, self.session_context) result["SessionContextHostType"] = to_enum(HostType, self.session_context_host_type) result["SessionEnrichMetadataResult"] = to_class(SessionEnrichMetadataResult, self.session_enrich_metadata_result) + result["SessionFactoryPauseAtCheckpointResult"] = to_class(SessionFactoryPauseAtCheckpointResult, self.session_factory_pause_at_checkpoint_result) result["SessionFsAppendFileRequest"] = to_class(SessionFSAppendFileRequest, self.session_fs_append_file_request) result["SessionFsError"] = to_class(SessionFSError, self.session_fs_error) result["SessionFsErrorCode"] = to_enum(SessionFSErrorCode, self.session_fs_error_code) @@ -45219,7 +46341,7 @@ def to_dict(self) -> dict: result["WorkflowCancelRequest"] = to_class(WorkflowCancelRequest, self.workflow_cancel_request) result["WorkflowCurrentPhase"] = to_class(WorkflowCurrentPhase, self.workflow_current_phase) result["WorkflowDeclaredLimits"] = to_class(WorkflowDeclaredLimits, self.workflow_declared_limits) - result["WorkflowDurableOperation"] = to_enum(WorkflowDurableOperation, self.workflow_durable_operation) + result["WorkflowDurableOperation"] = to_enum(DurableOperation, self.workflow_durable_operation) result["WorkflowExecuteRequest"] = to_class(WorkflowExecuteRequest, self.workflow_execute_request) result["WorkflowExecuteResult"] = to_class(WorkflowExecuteResult, self.workflow_execute_result) result["WorkflowGetRunProgressRequest"] = to_class(WorkflowGetRunProgressRequest, self.workflow_get_run_progress_request) @@ -45230,15 +46352,15 @@ def to_dict(self) -> dict: result["WorkflowListRunsRequest"] = to_class(WorkflowListRunsRequest, self.workflow_list_runs_request) result["WorkflowListRunsResult"] = to_class(WorkflowListRunsResult, self.workflow_list_runs_result) result["WorkflowLogLine"] = to_class(WorkflowLogLine, self.workflow_log_line) - result["WorkflowLogLineKind"] = to_enum(WorkflowLogLineKind, self.workflow_log_line_kind) + result["WorkflowLogLineKind"] = to_enum(LogLineKind, self.workflow_log_line_kind) result["WorkflowLogRequest"] = to_class(WorkflowLogRequest, self.workflow_log_request) - result["WorkflowPauseCheckpointAction"] = to_enum(WorkflowPauseCheckpointAction, self.workflow_pause_checkpoint_action) + result["WorkflowPauseCheckpointAction"] = to_enum(PauseCheckpointAction, self.workflow_pause_checkpoint_action) result["WorkflowPauseCheckpointRequest"] = to_class(WorkflowPauseCheckpointRequest, self.workflow_pause_checkpoint_request) result["WorkflowPauseCheckpointResult"] = to_class(WorkflowPauseCheckpointResult, self.workflow_pause_checkpoint_result) result["WorkflowPauseInfo"] = to_class(WorkflowPauseInfo, self.workflow_pause_info) result["WorkflowPauseRequest"] = to_class(WorkflowPauseRequest, self.workflow_pause_request) result["WorkflowPhaseObservation"] = to_class(WorkflowPhaseObservation, self.workflow_phase_observation) - result["WorkflowPhaseStatus"] = to_enum(WorkflowPhaseStatus, self.workflow_phase_status) + result["WorkflowPhaseStatus"] = to_enum(PhaseStatus, self.workflow_phase_status) result["WorkflowProgressLine"] = to_class(WorkflowProgressLine, self.workflow_progress_line) result["WorkflowProgressPage"] = to_class(WorkflowProgressPage, self.workflow_progress_page) result["WorkflowResumeRequest"] = to_class(WorkflowResumeRequest, self.workflow_resume_request) @@ -45246,12 +46368,12 @@ def to_dict(self) -> dict: result["WorkflowRunConsumed"] = to_class(WorkflowRunConsumed, self.workflow_run_consumed) result["WorkflowRunDetail"] = to_class(WorkflowRunDetail, self.workflow_run_detail) result["WorkflowRunFailure"] = to_class(WorkflowRunFailure, self.workflow_run_failure) - result["WorkflowRunFailureKind"] = to_enum(WorkflowRunFailureKind, self.workflow_run_failure_kind) + result["WorkflowRunFailureKind"] = to_enum(RunFailureKind, self.workflow_run_failure_kind) result["WorkflowRunLimits"] = to_class(WorkflowRunLimits, self.workflow_run_limits) result["WorkflowRunOptions"] = to_class(WorkflowRunOptions, self.workflow_run_options) result["WorkflowRunRequest"] = to_class(WorkflowRunRequest, self.workflow_run_request) result["WorkflowRunResult"] = to_class(WorkflowRunResult, self.workflow_run_result) - result["WorkflowRunStatus"] = to_enum(WorkflowRunStatus, self.workflow_run_status) + result["WorkflowRunStatus"] = to_enum(RunStatus, self.workflow_run_status) result["WorkflowRunSummary"] = to_class(WorkflowRunSummary, self.workflow_run_summary) result["WorkflowRunTerminal"] = to_class(WorkflowRunTerminal, self.workflow_run_terminal) result["WorkflowToolResumeRequest"] = to_class(_WorkflowToolResumeRequest, self.workflow_tool_resume_request) @@ -45520,7 +46642,7 @@ def _load_PermissionDecision(obj: Any) -> "PermissionDecision": case _: raise ValueError(f"Unknown PermissionDecision kind: {kind!r}") # Approval to persist for this location -PermissionDecisionApproveForLocationApproval = PermissionDecisionApproveForLocationApprovalCommands | PermissionDecisionApproveForLocationApprovalRead | PermissionDecisionApproveForLocationApprovalWrite | PermissionDecisionApproveForLocationApprovalMCP | PermissionDecisionApproveForLocationApprovalMCPSampling | PermissionDecisionApproveForLocationApprovalMemory | PermissionDecisionApproveForLocationApprovalCustomTool | PermissionDecisionApproveForLocationApprovalExtensionManagement | PermissionDecisionApproveForLocationApprovalWorkflow | PermissionDecisionApproveForLocationApprovalExtensionPermissionAccess | PermissionDecisionApproveForLocationApprovalExtensionEnvAccess +PermissionDecisionApproveForLocationApproval = PermissionDecisionApproveForLocationApprovalCommands | PermissionDecisionApproveForLocationApprovalRead | PermissionDecisionApproveForLocationApprovalWrite | PermissionDecisionApproveForLocationApprovalMCP | PermissionDecisionApproveForLocationApprovalMCPSampling | PermissionDecisionApproveForLocationApprovalMemory | PermissionDecisionApproveForLocationApprovalCustomTool | PermissionDecisionApproveForLocationApprovalExtensionManagement | PermissionDecisionApproveForLocationApprovalFactory | PermissionDecisionApproveForLocationApprovalExtensionPermissionAccess | PermissionDecisionApproveForLocationApprovalExtensionEnvAccess def _load_PermissionDecisionApproveForLocationApproval(obj: Any) -> "PermissionDecisionApproveForLocationApproval": assert isinstance(obj, dict) @@ -45534,13 +46656,13 @@ def _load_PermissionDecisionApproveForLocationApproval(obj: Any) -> "PermissionD case "memory": return PermissionDecisionApproveForLocationApprovalMemory.from_dict(obj) case "custom-tool": return PermissionDecisionApproveForLocationApprovalCustomTool.from_dict(obj) case "extension-management": return PermissionDecisionApproveForLocationApprovalExtensionManagement.from_dict(obj) - case "workflow": return PermissionDecisionApproveForLocationApprovalWorkflow.from_dict(obj) + case "factory": return PermissionDecisionApproveForLocationApprovalFactory.from_dict(obj) case "extension-permission-access": return PermissionDecisionApproveForLocationApprovalExtensionPermissionAccess.from_dict(obj) case "extension-env-access": return PermissionDecisionApproveForLocationApprovalExtensionEnvAccess.from_dict(obj) case _: raise ValueError(f"Unknown PermissionDecisionApproveForLocationApproval kind: {kind!r}") # Session-scoped approval to remember (tool prompts only; omitted for path/url prompts) -PermissionDecisionApproveForSessionApproval = PermissionDecisionApproveForSessionApprovalCommands | PermissionDecisionApproveForSessionApprovalRead | PermissionDecisionApproveForSessionApprovalWrite | PermissionDecisionApproveForSessionApprovalMCP | PermissionDecisionApproveForSessionApprovalMCPSampling | PermissionDecisionApproveForSessionApprovalMemory | PermissionDecisionApproveForSessionApprovalCustomTool | PermissionDecisionApproveForSessionApprovalExtensionManagement | PermissionDecisionApproveForSessionApprovalWorkflow | PermissionDecisionApproveForSessionApprovalExtensionPermissionAccess | PermissionDecisionApproveForSessionApprovalExtensionEnvAccess +PermissionDecisionApproveForSessionApproval = PermissionDecisionApproveForSessionApprovalCommands | PermissionDecisionApproveForSessionApprovalRead | PermissionDecisionApproveForSessionApprovalWrite | PermissionDecisionApproveForSessionApprovalMCP | PermissionDecisionApproveForSessionApprovalMCPSampling | PermissionDecisionApproveForSessionApprovalMemory | PermissionDecisionApproveForSessionApprovalCustomTool | PermissionDecisionApproveForSessionApprovalExtensionManagement | PermissionDecisionApproveForSessionApprovalFactory | PermissionDecisionApproveForSessionApprovalExtensionPermissionAccess | PermissionDecisionApproveForSessionApprovalExtensionEnvAccess def _load_PermissionDecisionApproveForSessionApproval(obj: Any) -> "PermissionDecisionApproveForSessionApproval": assert isinstance(obj, dict) @@ -45554,13 +46676,13 @@ def _load_PermissionDecisionApproveForSessionApproval(obj: Any) -> "PermissionDe case "memory": return PermissionDecisionApproveForSessionApprovalMemory.from_dict(obj) case "custom-tool": return PermissionDecisionApproveForSessionApprovalCustomTool.from_dict(obj) case "extension-management": return PermissionDecisionApproveForSessionApprovalExtensionManagement.from_dict(obj) - case "workflow": return PermissionDecisionApproveForSessionApprovalWorkflow.from_dict(obj) + case "factory": return PermissionDecisionApproveForSessionApprovalFactory.from_dict(obj) case "extension-permission-access": return PermissionDecisionApproveForSessionApprovalExtensionPermissionAccess.from_dict(obj) case "extension-env-access": return PermissionDecisionApproveForSessionApprovalExtensionEnvAccess.from_dict(obj) case _: raise ValueError(f"Unknown PermissionDecisionApproveForSessionApproval kind: {kind!r}") # Tool approval to persist and apply -PermissionsLocationsAddToolApprovalDetails = PermissionsLocationsAddToolApprovalDetailsCommands | PermissionsLocationsAddToolApprovalDetailsRead | PermissionsLocationsAddToolApprovalDetailsWrite | PermissionsLocationsAddToolApprovalDetailsMCP | PermissionsLocationsAddToolApprovalDetailsMCPSampling | PermissionsLocationsAddToolApprovalDetailsMemory | PermissionsLocationsAddToolApprovalDetailsCustomTool | PermissionsLocationsAddToolApprovalDetailsExtensionManagement | PermissionsLocationsAddToolApprovalDetailsWorkflow | PermissionsLocationsAddToolApprovalDetailsExtensionPermissionAccess | PermissionsLocationsAddToolApprovalDetailsExtensionEnvAccess +PermissionsLocationsAddToolApprovalDetails = PermissionsLocationsAddToolApprovalDetailsCommands | PermissionsLocationsAddToolApprovalDetailsRead | PermissionsLocationsAddToolApprovalDetailsWrite | PermissionsLocationsAddToolApprovalDetailsMCP | PermissionsLocationsAddToolApprovalDetailsMCPSampling | PermissionsLocationsAddToolApprovalDetailsMemory | PermissionsLocationsAddToolApprovalDetailsCustomTool | PermissionsLocationsAddToolApprovalDetailsExtensionManagement | PermissionsLocationsAddToolApprovalDetailsFactory | PermissionsLocationsAddToolApprovalDetailsExtensionPermissionAccess | PermissionsLocationsAddToolApprovalDetailsExtensionEnvAccess def _load_PermissionsLocationsAddToolApprovalDetails(obj: Any) -> "PermissionsLocationsAddToolApprovalDetails": assert isinstance(obj, dict) @@ -45574,7 +46696,7 @@ def _load_PermissionsLocationsAddToolApprovalDetails(obj: Any) -> "PermissionsLo case "memory": return PermissionsLocationsAddToolApprovalDetailsMemory.from_dict(obj) case "custom-tool": return PermissionsLocationsAddToolApprovalDetailsCustomTool.from_dict(obj) case "extension-management": return PermissionsLocationsAddToolApprovalDetailsExtensionManagement.from_dict(obj) - case "workflow": return PermissionsLocationsAddToolApprovalDetailsWorkflow.from_dict(obj) + case "factory": return PermissionsLocationsAddToolApprovalDetailsFactory.from_dict(obj) case "extension-permission-access": return PermissionsLocationsAddToolApprovalDetailsExtensionPermissionAccess.from_dict(obj) case "extension-env-access": return PermissionsLocationsAddToolApprovalDetailsExtensionEnvAccess.from_dict(obj) case _: raise ValueError(f"Unknown PermissionsLocationsAddToolApprovalDetails kind: {kind!r}") @@ -45717,6 +46839,12 @@ def _load_TaskInfo(obj: Any) -> "TaskInfo": EventsCursorStatus = CursorStatus ExternalToolResult = ExternalToolTextResultForLlm ExternalToolTextResultForLlmContentResourceLinkIconTheme = Theme +FactoryDurableOperation = DurableOperation +FactoryLogLineKind = LogLineKind +FactoryPauseCheckpointAction = PauseCheckpointAction +FactoryPhaseStatus = PhaseStatus +FactoryRunFailureKind = RunFailureKind +FactoryRunStatus = RunStatus FilterMapping = dict HistoryCompactRequest = Any InstructionDiscoveryPathKind = DebugCollectLogsEntryKind @@ -45780,6 +46908,12 @@ def _load_TaskInfo(obj: Any) -> "TaskInfo": TaskInfoExecutionMode = TaskExecutionMode TaskInfoStatus = TaskStatus ToolResult = ToolResultExpanded +WorkflowDurableOperation = DurableOperation +WorkflowLogLineKind = LogLineKind +WorkflowPauseCheckpointAction = PauseCheckpointAction +WorkflowPhaseStatus = PhaseStatus +WorkflowRunFailureKind = RunFailureKind +WorkflowRunStatus = RunStatus WorkspaceSummaryHostType = HostType WorkspacesWorkspaceDetailsHostType = HostType @@ -46491,57 +47625,6 @@ async def set_credentials(self, params: SessionSetCredentialsParams, *, timeout: return SessionSetCredentialsResult.from_dict(await self._client.request("session.gitHubAuth.setCredentials", params_dict, **_timeout_kwargs(timeout))) -# Experimental: this API group is experimental and may change or be removed. -class AccountsLoginApi: - def __init__(self, client: "JsonRpcClient", session_id: str): - self._client = client - self._session_id = session_id - - async def begin(self, params: AuthLoginBeginRequest, *, timeout: float | None = None) -> AuthLoginBegun: - "Begin an interactive login flow for a provider kind (dispatch is kind-only) and return its opaque flow id and first step.\n\nArgs:\n params: Begin an interactive login flow for a provider kind. Dispatch is kind-only.\n\nReturns:\n A started login flow: its opaque id and first step." - params_dict: dict[str, Any] = {k: v for k, v in params.to_dict().items() if v is not None} - params_dict["sessionId"] = self._session_id - return AuthLoginBegun.from_dict(await self._client.request("session.accounts.login.begin", params_dict, **_timeout_kwargs(timeout))) - - async def advance(self, params: AuthLoginAdvanceRequest, *, timeout: float | None = None) -> AuthLoginStep: - "Advance an in-flight login flow, optionally fulfilling an input-required step, and return the next step.\n\nArgs:\n params: Advance an in-flight login flow, optionally fulfilling an input-required step.\n\nReturns:\n One step in an interactive login flow. The consumer acts on the step and calls advance to proceed. Browser-open is encoded as two distinct steps by design: `open-url` is CONSUMER-driven (the provider surfaces the authorize URL and the consumer opens it — github.com/GHEC web), while `needs-interaction` is PROVIDER-driven (the provider opens the browser or broker UI itself and does not surface a URL — Entra)." - params_dict: dict[str, Any] = {k: v for k, v in params.to_dict().items() if v is not None} - params_dict["sessionId"] = self._session_id - return AuthLoginStep.from_dict(await self._client.request("session.accounts.login.advance", params_dict, **_timeout_kwargs(timeout))) - - async def cancel(self, params: AuthLoginCancelRequest, *, timeout: float | None = None) -> None: - "Cancel an in-flight login flow and release its resources.\n\nArgs:\n params: Cancel an in-flight login flow." - params_dict: dict[str, Any] = {k: v for k, v in params.to_dict().items() if v is not None} - params_dict["sessionId"] = self._session_id - await self._client.request("session.accounts.login.cancel", params_dict, **_timeout_kwargs(timeout)) - - -# Experimental: this API group is experimental and may change or be removed. -class AccountsApi: - def __init__(self, client: "JsonRpcClient", session_id: str): - self._client = client - self._session_id = session_id - self.login = AccountsLoginApi(client, session_id) - - async def enumerate(self, params: AccountsEnumerateRequest, *, timeout: float | None = None) -> AuthEnumerateValue: - "Enumerate a typed accounts collection: the signed-in accounts, or the providers offered for interactive login.\n\nArgs:\n params: Enumerate request carrying the typed collection query.\n\nReturns:\n The enumerated collection, keyed by the same selector as the query." - params_dict: dict[str, Any] = {k: v for k, v in params.to_dict().items() if v is not None} - params_dict["sessionId"] = self._session_id - return AuthEnumerateValue.from_dict(await self._client.request("session.accounts.enumerate", params_dict, **_timeout_kwargs(timeout))) - - async def get(self, params: AccountsGetRequest, *, timeout: float | None = None) -> AuthReadValue: - "Read one typed accounts datum: the active account, a neutral status summary, or the last authentication errors.\n\nArgs:\n params: Read request carrying the typed datum query.\n\nReturns:\n The read result, keyed by the same selector as the query." - params_dict: dict[str, Any] = {k: v for k, v in params.to_dict().items() if v is not None} - params_dict["sessionId"] = self._session_id - return AuthReadValue.from_dict(await self._client.request("session.accounts.get", params_dict, **_timeout_kwargs(timeout))) - - async def set(self, params: AccountsSetRequest, *, timeout: float | None = None) -> AuthWriteResult: - "Apply one non-interactive accounts mutation: switch the active account, log an account out, or set credentials from a token.\n\nArgs:\n params: Mutation request carrying the typed write command.\n\nReturns:\n Result of a non-interactive accounts mutation." - params_dict: dict[str, Any] = {k: v for k, v in params.to_dict().items() if v is not None} - params_dict["sessionId"] = self._session_id - return AuthWriteResult.from_dict(await self._client.request("session.accounts.set", params_dict, **_timeout_kwargs(timeout))) - - # Experimental: this API group is experimental and may change or be removed. class DebugApi: def __init__(self, client: "JsonRpcClient", session_id: str): @@ -46596,6 +47679,93 @@ async def close(self, params: CanvasCloseRequest, *, timeout: float | None = Non await self._client.request("session.canvas.close", params_dict, **_timeout_kwargs(timeout)) +# Experimental: this API group is experimental and may change or be removed. +class FactoryJournalApi: + def __init__(self, client: "JsonRpcClient", session_id: str): + self._client = client + self._session_id = session_id + + async def get(self, params: FactoryJournalGetRequest, *, timeout: float | None = None) -> FactoryJournalGetResult: + "Reads a memoized factory journal entry.\n\nArgs:\n params: Parameters for reading a factory journal entry.\n\nReturns:\n Result of reading a factory journal entry." + params_dict: dict[str, Any] = {k: v for k, v in params.to_dict().items() if v is not None} + params_dict["sessionId"] = self._session_id + return FactoryJournalGetResult.from_dict(await self._client.request("session.factory.journal.get", params_dict, **_timeout_kwargs(timeout))) + + async def put(self, params: FactoryJournalPutRequest, *, timeout: float | None = None) -> FactoryACKResult: + "Stores a memoized factory journal entry.\n\nArgs:\n params: Parameters for storing a factory journal entry.\n\nReturns:\n Acknowledgement that a factory request was accepted." + params_dict: dict[str, Any] = {k: v for k, v in params.to_dict().items() if v is not None} + params_dict["sessionId"] = self._session_id + return FactoryACKResult.from_dict(await self._client.request("session.factory.journal.put", params_dict, **_timeout_kwargs(timeout))) + + +# Experimental: this API group is experimental and may change or be removed. +class FactoryApi: + def __init__(self, client: "JsonRpcClient", session_id: str): + self._client = client + self._session_id = session_id + self.journal = FactoryJournalApi(client, session_id) + + async def run(self, params: FactoryRunRequest, *, timeout: float | None = None) -> FactoryRunResult: + "Runs a registered factory by name at the top level.\n\nArgs:\n params: Parameters for invoking a registered factory.\n\nReturns:\n Complete current or terminal factory run envelope." + params_dict: dict[str, Any] = {k: v for k, v in params.to_dict().items() if v is not None} + params_dict["sessionId"] = self._session_id + return FactoryRunResult.from_dict(await self._client.request("session.factory.run", params_dict, **_timeout_kwargs(timeout))) + + async def resume(self, params: FactoryResumeRequest, *, timeout: float | None = None) -> FactoryResumeResult: + "Resumes a factory run using its persisted name, arguments, journal, and accounting.\n\nArgs:\n params: Parameters for resuming a factory run from its persisted identity.\n\nReturns:\n Resolved persisted factory identity and resumed run envelope." + params_dict: dict[str, Any] = {k: v for k, v in params.to_dict().items() if v is not None} + params_dict["sessionId"] = self._session_id + return FactoryResumeResult.from_dict(await self._client.request("session.factory.resume", params_dict, **_timeout_kwargs(timeout))) + + async def get_run(self, params: FactoryGetRunRequest, *, timeout: float | None = None) -> FactoryRunResult: + "Gets the current or settled envelope for a factory run.\n\nArgs:\n params: Parameters for retrieving a factory run.\n\nReturns:\n Complete current or terminal factory run envelope." + params_dict: dict[str, Any] = {k: v for k, v in params.to_dict().items() if v is not None} + params_dict["sessionId"] = self._session_id + return FactoryRunResult.from_dict(await self._client.request("session.factory.getRun", params_dict, **_timeout_kwargs(timeout))) + + async def list_runs(self, params: FactoryListRunsRequest, *, timeout: float | None = None) -> FactoryListRunsResult: + "Lists durable factory runs for this session in creation order.\n\nArgs:\n params: Parameters for paging factory runs.\n\nReturns:\n A page of factory runs in durable creation order." + params_dict: dict[str, Any] = {k: v for k, v in params.to_dict().items() if v is not None} + params_dict["sessionId"] = self._session_id + return FactoryListRunsResult.from_dict(await self._client.request("session.factory.listRuns", params_dict, **_timeout_kwargs(timeout))) + + async def get_run_detail(self, params: FactoryGetRunRequest, *, timeout: float | None = None) -> FactoryRunDetail: + "Gets durable and live observability detail for one factory run.\n\nArgs:\n params: Parameters for retrieving a factory run.\n\nReturns:\n Full factory run observability detail." + params_dict: dict[str, Any] = {k: v for k, v in params.to_dict().items() if v is not None} + params_dict["sessionId"] = self._session_id + return FactoryRunDetail.from_dict(await self._client.request("session.factory.getRunDetail", params_dict, **_timeout_kwargs(timeout))) + + async def get_run_progress(self, params: FactoryGetRunProgressRequest, *, timeout: float | None = None) -> FactoryProgressPage: + "Pages durable progress for one factory run.\n\nArgs:\n params: Parameters for paging factory progress.\n\nReturns:\n A bidirectional page of factory progress." + params_dict: dict[str, Any] = {k: v for k, v in params.to_dict().items() if v is not None} + params_dict["sessionId"] = self._session_id + return FactoryProgressPage.from_dict(await self._client.request("session.factory.getRunProgress", params_dict, **_timeout_kwargs(timeout))) + + async def cancel(self, params: FactoryCancelRequest, *, timeout: float | None = None) -> FactoryRunResult: + "Requests cancellation of a factory run and returns its run envelope.\n\nArgs:\n params: Parameters for cancelling a factory run.\n\nReturns:\n Complete current or terminal factory run envelope." + params_dict: dict[str, Any] = {k: v for k, v in params.to_dict().items() if v is not None} + params_dict["sessionId"] = self._session_id + return FactoryRunResult.from_dict(await self._client.request("session.factory.cancel", params_dict, **_timeout_kwargs(timeout))) + + async def pause(self, params: FactoryPauseRequest, *, timeout: float | None = None) -> FactoryRunResult: + "Pauses a running factory and returns its settled run envelope.\n\nArgs:\n params: Parameters for pausing a running factory.\n\nReturns:\n Complete current or terminal factory run envelope." + params_dict: dict[str, Any] = {k: v for k, v in params.to_dict().items() if v is not None} + params_dict["sessionId"] = self._session_id + return FactoryRunResult.from_dict(await self._client.request("session.factory.pause", params_dict, **_timeout_kwargs(timeout))) + + async def log(self, params: FactoryLogRequest, *, timeout: float | None = None) -> FactoryACKResult: + "Records a batch of ordered factory progress lines.\n\nArgs:\n params: Parameters for recording factory progress.\n\nReturns:\n Acknowledgement that a factory request was accepted." + params_dict: dict[str, Any] = {k: v for k, v in params.to_dict().items() if v is not None} + params_dict["sessionId"] = self._session_id + return FactoryACKResult.from_dict(await self._client.request("session.factory.log", params_dict, **_timeout_kwargs(timeout))) + + async def agent(self, params: FactoryAgentRequest, *, timeout: float | None = None) -> FactoryAgentResult: + "Runs one factory-scoped subagent and returns its result.\n\nArgs:\n params: Parameters for one factory-scoped subagent call.\n\nReturns:\n Result of one factory-scoped subagent call." + params_dict: dict[str, Any] = {k: v for k, v in params.to_dict().items() if v is not None} + params_dict["sessionId"] = self._session_id + return FactoryAgentResult.from_dict(await self._client.request("session.factory.agent", params_dict, **_timeout_kwargs(timeout))) + + # Experimental: this API group is experimental and may change or be removed. class WorkflowJournalApi: def __init__(self, client: "JsonRpcClient", session_id: str): @@ -48264,9 +49434,9 @@ def __init__(self, client: "JsonRpcClient", session_id: str): self._session_id = session_id self.sandbox = SandboxApi(client, session_id) self.git_hub_auth = GitHubAuthApi(client, session_id) - self.accounts = AccountsApi(client, session_id) self.debug = DebugApi(client, session_id) self.canvas = CanvasApi(client, session_id) + self.factory = FactoryApi(client, session_id) self.workflow = WorkflowApi(client, session_id) self.model = ModelApi(client, session_id) self.mode = ModeApi(client, session_id) @@ -48426,6 +49596,31 @@ def __init__(self, client: "JsonRpcClient", session_id: str): self.provider = _InternalCanvasProviderApi(client, session_id) +# Experimental: this API group is experimental and may change or be removed. +class _InternalFactoryApi: + def __init__(self, client: "JsonRpcClient", session_id: str): + self._client = client + self._session_id = session_id + + async def _run_from_tool(self, params: _FactoryToolRunRequest, *, timeout: float | None = None) -> FactoryRunResult: + "Internal tool-originated factory invocation.\n\nArgs:\n params: Internal parameters for invoking a registered factory from a tool.\n\nReturns:\n Complete current or terminal factory run envelope.\n\n:meta private:\n\nInternal SDK API; not part of the public surface." + params_dict: dict[str, Any] = {k: v for k, v in params.to_dict().items() if v is not None} + params_dict["sessionId"] = self._session_id + return FactoryRunResult.from_dict(await self._client.request("session.factory.runFromTool", params_dict, **_timeout_kwargs(timeout))) + + async def _resume_from_tool(self, params: _FactoryToolResumeRequest, *, timeout: float | None = None) -> FactoryResumeResult: + "Internal tool-originated factory resume.\n\nArgs:\n params: Internal parameters for resuming a factory run from a tool.\n\nReturns:\n Resolved persisted factory identity and resumed run envelope.\n\n:meta private:\n\nInternal SDK API; not part of the public surface." + params_dict: dict[str, Any] = {k: v for k, v in params.to_dict().items() if v is not None} + params_dict["sessionId"] = self._session_id + return FactoryResumeResult.from_dict(await self._client.request("session.factory.resumeFromTool", params_dict, **_timeout_kwargs(timeout))) + + async def _pause_at_checkpoint(self, params: FactoryPauseCheckpointRequest, *, timeout: float | None = None) -> SessionFactoryPauseAtCheckpointResult: + "Atomically pauses an owned factory attempt at a durable checkpoint.\n\nArgs:\n params: Parameters for an owned durable pause checkpoint.\n\n:meta private:\n\nInternal SDK API; not part of the public surface." + params_dict: dict[str, Any] = {k: v for k, v in params.to_dict().items() if v is not None} + params_dict["sessionId"] = self._session_id + return SessionFactoryPauseAtCheckpointResult.from_dict(await self._client.request("session.factory.pauseAtCheckpoint", params_dict, **_timeout_kwargs(timeout))) + + # Experimental: this API group is experimental and may change or be removed. class _InternalWorkflowApi: def __init__(self, client: "JsonRpcClient", session_id: str): @@ -48641,6 +49836,7 @@ def __init__(self, client: "JsonRpcClient", session_id: str): self._session_id = session_id self.git_hub_auth = _InternalGitHubAuthApi(client, session_id) self.canvas = _InternalCanvasApi(client, session_id) + self.factory = _InternalFactoryApi(client, session_id) self.workflow = _InternalWorkflowApi(client, session_id) self.model = _InternalModelApi(client, session_id) self.mcp = _InternalMcpApi(client, session_id) @@ -48663,6 +49859,15 @@ async def get_token(self, params: ProviderTokenAcquireRequest) -> ProviderTokenA "Asks the SDK client to get a bearer token for a BYOK provider whose config set `hasBearerTokenProvider: true`. Session-scoped: the runtime calls it back on the connection that most recently supplied that provider's config for the session (the creating connection, or a resuming connection if the session was resumed — distinct providers may be owned by different connections), passing the provider name, and uses the returned token as the Authorization header for the outbound model request. The runtime does no caching — it calls this once per outbound request; the SDK consumer owns token acquisition, caching, and refresh.\n\nArgs:\n params: Asks the SDK client to acquire a bearer token for a BYOK provider whose config set `hasBearerTokenProvider: true`. Issued by the runtime before each outbound model request; the runtime does no caching, so this is sent once per request.\n\nReturns:\n A bearer token supplied by the SDK client for a BYOK provider. The runtime sets it as `Authorization: Bearer ` on the outbound request and does no caching; the SDK consumer owns token caching and refresh." pass +# Experimental: this API group is experimental and may change or be removed. +class FactoryHandler(Protocol): + async def execute(self, params: FactoryExecuteRequest) -> FactoryExecuteResult: + "Asks the owning extension connection to execute a registered factory closure.\n\nArgs:\n params: Parameters sent to the owning extension to execute a factory closure.\n\nReturns:\n Result returned by an extension factory closure." + pass + async def abort(self, params: FactoryAbortRequest) -> FactoryACKResult: + "Asks the owning extension connection to abort a running factory cooperatively.\n\nArgs:\n params: Parameters for cooperatively aborting a factory body.\n\nReturns:\n Acknowledgement that a factory request was accepted." + pass + # Experimental: this API group is experimental and may change or be removed. class WorkflowHandler(Protocol): async def execute(self, params: WorkflowExecuteRequest) -> WorkflowExecuteResult: @@ -48735,6 +49940,7 @@ async def invoke(self, params: CanvasProviderInvokeActionRequest) -> Any: @dataclass class ClientSessionApiHandlers: provider_token: ProviderTokenHandler | None = None + factory: FactoryHandler | None = None workflow: WorkflowHandler | None = None tasks: TasksHandler | None = None session_fs: SessionFsHandler | None = None @@ -48752,6 +49958,20 @@ async def handle_provider_token_get_token(params: dict) -> dict | None: result = await handler.get_token(request) return result.to_dict() client.set_request_handler("providerToken.getToken", handle_provider_token_get_token) + async def handle_factory_execute(params: dict) -> dict | None: + request = FactoryExecuteRequest.from_dict(params) + handler = get_handlers(request.session_id).factory + if handler is None: raise RuntimeError(f"No factory handler registered for session: {request.session_id}") + result = await handler.execute(request) + return result.to_dict() + client.set_request_handler("factory.execute", handle_factory_execute) + async def handle_factory_abort(params: dict) -> dict | None: + request = FactoryAbortRequest.from_dict(params) + handler = get_handlers(request.session_id).factory + if handler is None: raise RuntimeError(f"No factory handler registered for session: {request.session_id}") + result = await handler.abort(request) + return result.to_dict() + client.set_request_handler("factory.abort", handle_factory_abort) async def handle_workflow_execute(params: dict) -> dict | None: request = WorkflowExecuteRequest.from_dict(params) handler = get_handlers(request.session_id).workflow @@ -48991,18 +50211,11 @@ async def handle_git_hub_token_get_token(params: dict) -> dict | None: "AccountGetCurrentAuthResult", "AccountGetQuotaRequest", "AccountGetQuotaResult", - "AccountKind", "AccountLoginRequest", "AccountLoginResult", "AccountLogoutRequest", "AccountLogoutResult", "AccountQuotaSnapshot", - "AccountStatus", - "AccountsApi", - "AccountsEnumerateRequest", - "AccountsGetRequest", - "AccountsLoginApi", - "AccountsSetRequest", "Action", "AdaptiveThinkingSupport", "AdditionalContentExclusionPolicyScope", @@ -49043,29 +50256,11 @@ async def handle_git_hub_token_get_token(params: dict) -> dict | None: "AgentsDiscoverRequest", "AgentsGetDiscoveryPathsRequest", "ApprovalKind", - "AuthEnumerateQuery", - "AuthEnumerateQueryKind", - "AuthEnumerateValue", "AuthIdentity", "AuthInfo", "AuthInfoType", - "AuthLoginAdvanceRequest", - "AuthLoginBeginRequest", - "AuthLoginBegun", - "AuthLoginCancelRequest", - "AuthLoginResultDto", - "AuthLoginResultStatus", - "AuthLoginStep", - "AuthLoginStepKind", - "AuthReadQuery", - "AuthReadQueryKind", - "AuthReadValue", - "AuthStatusDto", "AuthValidationError", "AuthValidationErrors", - "AuthWrite", - "AuthWriteKind", - "AuthWriteResult", "AutopilotObjectiveApi", "AutopilotObjectiveCreditLimit", "AutopilotObjectiveGetStateResult", @@ -49324,6 +50519,7 @@ async def handle_git_hub_token_get_token(params: dict) -> dict | None: "DiscoveredHook", "DiscoveredMCPServer", "DiscoveredMCPServerType", + "DurableOperation", "EnqueueCommandParams", "EnqueueCommandResult", "EnterpriseList", @@ -49376,6 +50572,53 @@ async def handle_git_hub_token_get_token(params: dict) -> dict | None: "ExternalToolTextResultForLlmContentTerminalType", "ExternalToolTextResultForLlmContentText", "ExternalToolTextResultForLlmContentType", + "FactoryACKResult", + "FactoryAbortRequest", + "FactoryAgentOptions", + "FactoryAgentRequest", + "FactoryAgentResult", + "FactoryAgentSummary", + "FactoryApi", + "FactoryCancelRequest", + "FactoryCurrentPhase", + "FactoryDeclaredLimits", + "FactoryDurableOperation", + "FactoryExecuteRequest", + "FactoryExecuteResult", + "FactoryGetRunProgressRequest", + "FactoryGetRunRequest", + "FactoryHandler", + "FactoryJournalApi", + "FactoryJournalGetRequest", + "FactoryJournalGetResult", + "FactoryJournalPutRequest", + "FactoryListRunsRequest", + "FactoryListRunsResult", + "FactoryLogLine", + "FactoryLogLineKind", + "FactoryLogRequest", + "FactoryPauseCheckpointAction", + "FactoryPauseCheckpointRequest", + "FactoryPauseCheckpointResult", + "FactoryPauseInfo", + "FactoryPauseRequest", + "FactoryPhaseObservation", + "FactoryPhaseStatus", + "FactoryProgressLine", + "FactoryProgressPage", + "FactoryResumeRequest", + "FactoryResumeResult", + "FactoryRunConsumed", + "FactoryRunDetail", + "FactoryRunFailure", + "FactoryRunFailureKind", + "FactoryRunFailureType", + "FactoryRunLimits", + "FactoryRunRequest", + "FactoryRunResult", + "FactoryRunStatus", + "FactoryRunSummary", + "FactoryRunTerminal", "FilterMapping", "FleetApi", "FleetStartRequest", @@ -49453,7 +50696,6 @@ async def handle_git_hub_token_get_token(params: dict) -> dict | None: "InstructionsGetSourcesResult", "InterruptMainTurnRequest", "InterruptMainTurnResult", - "ItemElement", "JSONSchemaResponseFormat", "KindEnum", "LimitPredictionApi", @@ -49471,9 +50713,9 @@ async def handle_git_hub_token_get_token(params: dict) -> dict | None: "LlmInferenceHeaders", "LlmInferenceSetProviderResult", "LocalSessionMetadataValue", + "LogLineKind", "LogRequest", "LogResult", - "LoginProviderKind", "LspApi", "LspInitializeRequest", "MCPAllowedServer", @@ -49746,6 +50988,7 @@ async def handle_git_hub_token_get_token(params: dict) -> dict | None: "OptionsUpdateEnvValueMode", "OptionsUpdateReasoningSummary", "OptionsUpdateToolFilterPrecedence", + "PauseCheckpointAction", "PauseInfoClass", "PauseInfoType", "PendingPermissionRequest", @@ -49763,14 +51006,14 @@ async def handle_git_hub_token_get_token(params: dict) -> dict | None: "PermissionDecisionApproveForLocationApprovalExtensionManagementKind", "PermissionDecisionApproveForLocationApprovalExtensionPermissionAccess", "PermissionDecisionApproveForLocationApprovalExtensionPermissionAccessKind", + "PermissionDecisionApproveForLocationApprovalFactory", + "PermissionDecisionApproveForLocationApprovalFactoryKind", "PermissionDecisionApproveForLocationApprovalMCP", "PermissionDecisionApproveForLocationApprovalMCPSampling", "PermissionDecisionApproveForLocationApprovalMCPSamplingKind", "PermissionDecisionApproveForLocationApprovalMemory", "PermissionDecisionApproveForLocationApprovalRead", "PermissionDecisionApproveForLocationApprovalReadKind", - "PermissionDecisionApproveForLocationApprovalWorkflow", - "PermissionDecisionApproveForLocationApprovalWorkflowKind", "PermissionDecisionApproveForLocationApprovalWrite", "PermissionDecisionApproveForLocationApprovalWriteKind", "PermissionDecisionApproveForLocationKind", @@ -49781,11 +51024,11 @@ async def handle_git_hub_token_get_token(params: dict) -> dict | None: "PermissionDecisionApproveForSessionApprovalExtensionEnvAccess", "PermissionDecisionApproveForSessionApprovalExtensionManagement", "PermissionDecisionApproveForSessionApprovalExtensionPermissionAccess", + "PermissionDecisionApproveForSessionApprovalFactory", "PermissionDecisionApproveForSessionApprovalMCP", "PermissionDecisionApproveForSessionApprovalMCPSampling", "PermissionDecisionApproveForSessionApprovalMemory", "PermissionDecisionApproveForSessionApprovalRead", - "PermissionDecisionApproveForSessionApprovalWorkflow", "PermissionDecisionApproveForSessionApprovalWrite", "PermissionDecisionApproveForSessionKind", "PermissionDecisionApproveOnce", @@ -49856,11 +51099,11 @@ async def handle_git_hub_token_get_token(params: dict) -> dict | None: "PermissionsLocationsAddToolApprovalDetailsExtensionEnvAccess", "PermissionsLocationsAddToolApprovalDetailsExtensionManagement", "PermissionsLocationsAddToolApprovalDetailsExtensionPermissionAccess", + "PermissionsLocationsAddToolApprovalDetailsFactory", "PermissionsLocationsAddToolApprovalDetailsMCP", "PermissionsLocationsAddToolApprovalDetailsMCPSampling", "PermissionsLocationsAddToolApprovalDetailsMemory", "PermissionsLocationsAddToolApprovalDetailsRead", - "PermissionsLocationsAddToolApprovalDetailsWorkflow", "PermissionsLocationsAddToolApprovalDetailsWrite", "PermissionsLocationsAddToolApprovalResult", "PermissionsLocationsApi", @@ -49884,6 +51127,7 @@ async def handle_git_hub_token_get_token(params: dict) -> dict | None: "PermissionsSetRequiredResult", "PermissionsUrlsApi", "PermissionsUrlsSetUnrestrictedModeResult", + "PhaseStatus", "PingRequest", "PingResult", "PlanApi", @@ -49937,7 +51181,6 @@ async def handle_git_hub_token_get_token(params: dict) -> dict | None: "ProviderConfigTransport", "ProviderConfigType", "ProviderConfigWireApi", - "ProviderDescriptor", "ProviderEndpoint", "ProviderEndpointTransport", "ProviderEndpointType", @@ -50058,6 +51301,9 @@ async def handle_git_hub_token_get_token(params: dict) -> dict | None: "RemoteSessionRepository", "ResponseFormat", "ResponseFormatType", + "RunFailureKind", + "RunOptions", + "RunStatus", "SandboxApi", "SandboxConfig", "SandboxConfigAuth", @@ -50179,6 +51425,7 @@ async def handle_git_hub_token_get_token(params: dict) -> dict | None: "SessionFSStatRequest", "SessionFSStatResult", "SessionFSWriteFileRequest", + "SessionFactoryPauseAtCheckpointResult", "SessionFsHandler", "SessionFsReaddirWithTypesEntryType", "SessionGitHubAuthGetAllAuthAvailableResult", @@ -50570,6 +51817,7 @@ async def handle_git_hub_token_get_token(params: dict) -> dict | None: "WorkflowRunStatus", "WorkflowRunSummary", "WorkflowRunTerminal", + "WorkflowRunTerminalPauseInfo", "Workspace", "WorkspaceDiffFileChange", "WorkspaceDiffFileChangeType", From 5c5c7d666b21187e19f433046ab5c483b9136b70 Mon Sep 17 00:00:00 2001 From: Michael Dinh Date: Sat, 26 Sep 2026 19:59:28 -0400 Subject: [PATCH 18/25] fix: address worker causality CodeQL findings Use the supported Jackson object-property iterator and avoid Path.Combine root-reset semantics in the shared fixture test. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> --- dotnet/test/Unit/WorkerCausalityTests.cs | 2 +- .../java/com/github/copilot/WorkerCausalityDeserializer.java | 4 +--- 2 files changed, 2 insertions(+), 4 deletions(-) diff --git a/dotnet/test/Unit/WorkerCausalityTests.cs b/dotnet/test/Unit/WorkerCausalityTests.cs index 2afb627a69..ec7e09fbb5 100644 --- a/dotnet/test/Unit/WorkerCausalityTests.cs +++ b/dotnet/test/Unit/WorkerCausalityTests.cs @@ -14,7 +14,7 @@ namespace GitHub.Copilot.Test.Unit; public class WorkerCausalityTests { private static readonly JsonNode Corpus = JsonNode.Parse(File.ReadAllText( - Path.Combine(AppContext.BaseDirectory, "worker-causality.json")))!; + Path.Join(AppContext.BaseDirectory, "worker-causality.json")))!; private static readonly JsonSerializerOptions RpcOptions = new() { PropertyNamingPolicy = JsonNamingPolicy.CamelCase, diff --git a/java/sdk/src/main/java/com/github/copilot/WorkerCausalityDeserializer.java b/java/sdk/src/main/java/com/github/copilot/WorkerCausalityDeserializer.java index b9529988cc..6ddc0711cb 100644 --- a/java/sdk/src/main/java/com/github/copilot/WorkerCausalityDeserializer.java +++ b/java/sdk/src/main/java/com/github/copilot/WorkerCausalityDeserializer.java @@ -179,9 +179,7 @@ private static boolean small(JsonNode value, int[] remaining, int depth) { if (value.isTextual()) { remaining[0] -= value.textValue().length(); } else if (value.isObject()) { - var fields = value.fields(); - while (fields.hasNext()) { - var field = fields.next(); + for (var field : value.properties()) { remaining[0] -= field.getKey().length(); if (!small(field.getValue(), remaining, depth + 1)) { return false; From 36541c86b7c2a53c9b4725f7ec115356e6d35a95 Mon Sep 17 00:00:00 2001 From: Michael Dinh Date: Sun, 27 Sep 2026 03:31:37 -0400 Subject: [PATCH 19/25] Address worker fixture CodeQL finding Project invalid raw fixture values before the assertion loop. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> --- dotnet/test/Unit/WorkerCausalityTests.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/dotnet/test/Unit/WorkerCausalityTests.cs b/dotnet/test/Unit/WorkerCausalityTests.cs index ec7e09fbb5..96d70a604a 100644 --- a/dotnet/test/Unit/WorkerCausalityTests.cs +++ b/dotnet/test/Unit/WorkerCausalityTests.cs @@ -88,9 +88,9 @@ public void CanonicalWorkflowCompletionDecodesTypedFields() [Fact] public void InvalidUnicodeScalarIdentitiesAreUnavailable() { - foreach (var test in Corpus["invalidRaw"]!.AsArray()) + foreach (var raw in Corpus["invalidRaw"]!.AsArray().Select( + test => test!["json"]!.GetValue())) { - var raw = test!["json"]!.GetValue(); var wire = $"{{\"sent\":true,\"workerCausality\":{raw}}}"; var result = JsonSerializer.Deserialize(wire, RpcOptions); Assert.NotNull(result); From 8affcc530daabbdb8b39186817eee290fc4db6cd Mon Sep 17 00:00:00 2001 From: Michael Dinh Date: Sun, 27 Sep 2026 03:44:07 -0400 Subject: [PATCH 20/25] Remove orphaned legacy factory wait methods Keep current workflow APIs and legacy run-update invalidation without referencing removed high-level factory types. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> --- nodejs/src/session.ts | 109 ------------------------------------------ 1 file changed, 109 deletions(-) diff --git a/nodejs/src/session.ts b/nodejs/src/session.ts index fb7f50e71f..475f423ced 100644 --- a/nodejs/src/session.ts +++ b/nodejs/src/session.ts @@ -527,115 +527,6 @@ export class CopilotSession { cancel: async (runId) => this.rpc.workflow.cancel({ runId }), }; - /** - * Resolve a start/resume envelope into the terminal envelope callers expect. - * - * The CLI may answer `session.factory.run` and `session.factory.resume` - * before the run settles, so a non-terminal envelope is followed by a wait - * on the run's terminal state. - */ - private settleFactoryRun(envelope: WireFactoryRunResult): Promise { - if (isFactoryRunTerminal(envelope.status)) { - return Promise.resolve(envelope); - } - return this.waitForFactoryRun(envelope.runId); - } - - /** - * Resolve when a factory run reaches a terminal status. - * - * The subscription is installed *before* the first read so a transition - * landing between the two cannot be missed, and re-reads are serialized so - * overlapping invalidation events cannot interleave — the run's revision - * advances once per operation, so a burst of events is common and must - * collapse into a single in-flight read. A bounded periodic re-read keeps a - * dropped invalidation from leaving the wait pending forever. - */ - private waitForFactoryRun(runId: string, signal?: AbortSignal): Promise { - const abortError = (): unknown => - signal?.reason ?? new DOMException("Factory run wait was aborted", "AbortError"); - if (signal?.aborted === true) { - return Promise.reject(abortError()); - } - - return new Promise((resolve, reject) => { - let settled = false; - let reading = false; - let rereadRequested = false; - let pollHandle: ReturnType | undefined; - let unsubscribe: (() => void) | undefined; - let onAbort: (() => void) | undefined; - - const finish = (complete: () => void): void => { - if (settled) { - return; - } - settled = true; - if (pollHandle !== undefined) { - clearInterval(pollHandle); - } - unsubscribe?.(); - if (onAbort !== undefined) { - signal?.removeEventListener("abort", onAbort); - } - complete(); - }; - - const read = async (): Promise => { - if (settled) { - return; - } - if (reading) { - rereadRequested = true; - return; - } - reading = true; - try { - do { - rereadRequested = false; - const envelope = await this.rpc.factory.getRun({ runId }); - if (isFactoryRunTerminal(envelope.status)) { - finish(() => resolve(envelope)); - return; - } - } while (rereadRequested && !settled); - } catch (error) { - finish(() => reject(error)); - } finally { - reading = false; - } - }; - - if (signal !== undefined) { - onAbort = (): void => finish(() => reject(abortError())); - signal.addEventListener("abort", onAbort, { once: true }); - } - - const subscribe = this.on.bind(this) as unknown as ( - eventType: string, - handler: (event: SessionEvent) => void - ) => () => void; - const handleRunUpdated = (event: SessionEvent): void => { - const data = event.data as { runId?: unknown }; - if (data.runId === runId) { - void read(); - } - }; - const unsubscribeFactory = subscribe("factory.run_updated", handleRunUpdated); - const unsubscribeWorkflow = subscribe("workflow.run_updated", handleRunUpdated); - unsubscribe = (): void => { - unsubscribeFactory(); - unsubscribeWorkflow(); - }; - - pollHandle = setInterval(() => void read(), 5_000); - // The re-read is a safety net, not work the process owes anyone: an - // outstanding wait must never keep Node alive on its own. - pollHandle.unref?.(); - void read(); - }); - } - private settleWorkflowRun(envelope: WireWorkflowRunResult): Promise { if (isWorkflowRunTerminal(envelope.status)) { return Promise.resolve(envelope); From f7b90398267921b71783f42ae89e3a5693371335 Mon Sep 17 00:00:00 2001 From: Michael Dinh Date: Sun, 27 Sep 2026 10:12:42 -0400 Subject: [PATCH 21/25] Complete CodeQL projection cleanup Project each invalid fixture directly to its wire payload before entering the assertion loop. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> --- dotnet/test/Unit/WorkerCausalityTests.cs | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/dotnet/test/Unit/WorkerCausalityTests.cs b/dotnet/test/Unit/WorkerCausalityTests.cs index 96d70a604a..9bb5ea7aa7 100644 --- a/dotnet/test/Unit/WorkerCausalityTests.cs +++ b/dotnet/test/Unit/WorkerCausalityTests.cs @@ -88,10 +88,9 @@ public void CanonicalWorkflowCompletionDecodesTypedFields() [Fact] public void InvalidUnicodeScalarIdentitiesAreUnavailable() { - foreach (var raw in Corpus["invalidRaw"]!.AsArray().Select( - test => test!["json"]!.GetValue())) + foreach (var wire in Corpus["invalidRaw"]!.AsArray().Select( + test => $"{{\"sent\":true,\"workerCausality\":{test!["json"]!.GetValue()}}}")) { - var wire = $"{{\"sent\":true,\"workerCausality\":{raw}}}"; var result = JsonSerializer.Deserialize(wire, RpcOptions); Assert.NotNull(result); Assert.Null(result.WorkerCausality); From 2052b42a121de8e5f3366de74c276c704f64baf8 Mon Sep 17 00:00:00 2001 From: Michael Dinh Date: Mon, 28 Sep 2026 08:51:04 -0400 Subject: [PATCH 22/25] Preserve worker identities across SDK readers Keep worker UUID identities as source strings, preserve Go turn metadata, and decode legacy factory events through typed compatibility cases. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> --- dotnet/src/Generated/SessionEvents.cs | 8 +-- go/rpc/factory_session_event_compat_test.go | 64 +++++++++++++++++++ go/rpc/zsession_encoding.go | 28 ++++++-- go/worker_causality_test.go | 33 ++++++++++ java/scripts/codegen/java.test.ts | 24 +++++++ java/scripts/codegen/java.ts | 14 ++++ .../copilot/generated/WorkerAdmission.java | 3 +- .../generated/WorkerEventReference.java | 3 +- .../github/copilot/generated/WorkerInput.java | 3 +- .../WorkerNotificationReference.java | 3 +- .../generated/rpc/WorkerAdmission.java | 3 +- .../generated/rpc/WorkerEventReference.java | 3 +- .../copilot/generated/rpc/WorkerInput.java | 3 +- .../rpc/WorkerNotificationReference.java | 3 +- nodejs/test/csharp-codegen.test.ts | 44 +++++++++++++ nodejs/test/python-codegen.test.ts | 46 +++++++++++++ python/copilot/generated/session_events.py | 24 +++---- scripts/codegen/csharp.ts | 17 +++++ scripts/codegen/python.ts | 15 +++++ test/worker-causality.json | 38 +++++++++++ 20 files changed, 343 insertions(+), 36 deletions(-) create mode 100644 go/rpc/factory_session_event_compat_test.go diff --git a/dotnet/src/Generated/SessionEvents.cs b/dotnet/src/Generated/SessionEvents.cs index f994cf12da..c9d7f80ede 100644 --- a/dotnet/src/Generated/SessionEvents.cs +++ b/dotnet/src/Generated/SessionEvents.cs @@ -8114,7 +8114,7 @@ public sealed partial class WorkerEventReference /// Actual event occurrence UUID; copied without normalization. [JsonPropertyName("eventId")] - public required Guid EventId { get; set; } + public required string EventId { get; set; } /// Type of the observed occurrence. [JsonPropertyName("eventType")] @@ -8139,7 +8139,7 @@ public sealed partial class WorkerAdmission /// Actual AHP participant Turn UUID, not a native turn counter or provenance signal. [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] [JsonPropertyName("ahpTurnId")] - public Guid? AhpTurnId { get; set; } + public string? AhpTurnId { get; set; } /// May be omitted only for the matching current worker user.message. [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] @@ -8184,7 +8184,7 @@ public sealed partial class WorkerInput /// UUID allocated for this queue item by the admitting producer. [JsonPropertyName("queueItemId")] - public required Guid QueueItemId { get; set; } + public required string QueueItemId { get; set; } /// Original invoking occurrence, never replaced by a reported/root alias. [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] @@ -8203,7 +8203,7 @@ public sealed partial class WorkerNotificationReference { /// Actual notificationDeliveryId UUID. [JsonPropertyName("deliveryId")] - public required Guid DeliveryId { get; set; } + public required string DeliveryId { get; set; } /// May be omitted only on the matching current system.notification. [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] diff --git a/go/rpc/factory_session_event_compat_test.go b/go/rpc/factory_session_event_compat_test.go new file mode 100644 index 0000000000..a952177744 --- /dev/null +++ b/go/rpc/factory_session_event_compat_test.go @@ -0,0 +1,64 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. + +package rpc + +import ( + "encoding/json" + "testing" +) + +func TestLegacyFactoryEventsDecodeToCompatibilityTypes(t *testing.T) { + tests := []struct { + name string + wire string + want SessionEventData + }{ + { + name: "updated", + wire: `{"type":"factory.run_updated","data":{"revision":2,"runId":"run-1"}}`, + want: &FactoryRunUpdatedData{Revision: 2, RunID: "run-1"}, + }, + { + name: "started", + wire: `{"type":"factory.run_started","data":{"attempt":3,"factoryName":"fix-ci","runId":"run-1"}}`, + want: &FactoryRunStartedData{Attempt: 3, FactoryName: "fix-ci", RunID: "run-1"}, + }, + { + name: "settled", + wire: `{"type":"factory.run_settled","data":{"consumedNanoAiu":4,"consumedSubagents":1,"elapsedMs":5,"runId":"run-1","status":"completed"}}`, + want: &FactoryRunSettledData{ + ConsumedNanoAiu: 4, + ConsumedSubagents: 1, + ElapsedMs: 5, + RunID: "run-1", + Status: FactoryRunSettledStatusCompleted, + }, + }, + } + + for _, test := range tests { + t.Run(test.name, func(t *testing.T) { + var event SessionEvent + if err := json.Unmarshal([]byte(test.wire), &event); err != nil { + t.Fatal(err) + } + switch want := test.want.(type) { + case *FactoryRunUpdatedData: + got, ok := event.Data.(*FactoryRunUpdatedData) + if !ok || *got != *want { + t.Fatalf("data = %#v", event.Data) + } + case *FactoryRunStartedData: + got, ok := event.Data.(*FactoryRunStartedData) + if !ok || *got != *want { + t.Fatalf("data = %#v", event.Data) + } + case *FactoryRunSettledData: + got, ok := event.Data.(*FactoryRunSettledData) + if !ok || *got != *want { + t.Fatalf("data = %#v", event.Data) + } + } + }) + } +} diff --git a/go/rpc/zsession_encoding.go b/go/rpc/zsession_encoding.go index e165661dd9..0e07cd3d1f 100644 --- a/go/rpc/zsession_encoding.go +++ b/go/rpc/zsession_encoding.go @@ -234,6 +234,24 @@ func (e *SessionEvent) UnmarshalJSON(data []byte) error { return err } e.Data = &d + case SessionEventTypeFactoryRunSettled: + var d FactoryRunSettledData + if err := json.Unmarshal(raw.Data, &d); err != nil { + return err + } + e.Data = &d + case SessionEventTypeFactoryRunStarted: + var d FactoryRunStartedData + if err := json.Unmarshal(raw.Data, &d); err != nil { + return err + } + e.Data = &d + case SessionEventTypeFactoryRunUpdated: + var d FactoryRunUpdatedData + if err := json.Unmarshal(raw.Data, &d); err != nil { + return err + } + e.Data = &d case SessionEventTypeHookEnd: var d HookEndData if err := json.Unmarshal(raw.Data, &d); err != nil { @@ -1028,10 +1046,11 @@ func (r *UserMessageData) UnmarshalJSON(data []byte) error { func (r *AssistantTurnStartData) UnmarshalJSON(data []byte) error { type rawAssistantTurnStartData struct { - InteractionID *string `json:"interactionId,omitempty"` - Model *string `json:"model,omitempty"` - TurnID string `json:"turnId"` - WorkerCausality json.RawMessage `json:"workerCausality,omitempty"` + InteractionID *string `json:"interactionId,omitempty"` + Model *string `json:"model,omitempty"` + ParentToolCallID *string `json:"parentToolCallId,omitempty"` + TurnID string `json:"turnId"` + WorkerCausality json.RawMessage `json:"workerCausality,omitempty"` } var raw rawAssistantTurnStartData if err := json.Unmarshal(data, &raw); err != nil { @@ -1039,6 +1058,7 @@ func (r *AssistantTurnStartData) UnmarshalJSON(data []byte) error { } r.InteractionID = raw.InteractionID r.Model = raw.Model + r.ParentToolCallID = raw.ParentToolCallID r.TurnID = raw.TurnID if !diagnosticmetadata.ReadWorkerCausality(raw.WorkerCausality, &r.WorkerCausality) { r.WorkerCausality = nil diff --git a/go/worker_causality_test.go b/go/worker_causality_test.go index 136852c19e..e370a894d5 100644 --- a/go/worker_causality_test.go +++ b/go/worker_causality_test.go @@ -110,6 +110,39 @@ func TestWorkerCausalityPublicReaders(t *testing.T) { } } +func TestAssistantTurnStartPreservesParentToolCallIDWithWorkerCausality(t *testing.T) { + for _, test := range workerCorpus(t).Valid { + if test.Name != "two_source_assistant_turn_start" { + continue + } + var event rpc.SessionEvent + if err := json.Unmarshal(test.Event, &event); err != nil { + t.Fatal(err) + } + data, ok := event.Data.(*rpc.AssistantTurnStartData) + if !ok { + t.Fatalf("data type = %T", event.Data) + } + if data.ParentToolCallID == nil || *data.ParentToolCallID != "parent-tool-call" { + t.Fatalf("parent tool call ID = %v", data.ParentToolCallID) + } + encoded, err := json.Marshal(event) + if err != nil { + t.Fatal(err) + } + var roundTrip map[string]any + if err := json.Unmarshal(encoded, &roundTrip); err != nil { + t.Fatal(err) + } + payload := roundTrip["data"].(map[string]any) + if payload["parentToolCallId"] != "parent-tool-call" { + t.Fatalf("round-trip parent tool call ID = %v", payload["parentToolCallId"]) + } + return + } + t.Fatal("two_source_assistant_turn_start fixture not found") +} + func TestWorkerCausalityExactUTF8Budget(t *testing.T) { corpus := workerCorpus(t) for _, test := range corpus.Boundaries { diff --git a/java/scripts/codegen/java.test.ts b/java/scripts/codegen/java.test.ts index 7709d9f92a..2f2a421a7a 100644 --- a/java/scripts/codegen/java.test.ts +++ b/java/scripts/codegen/java.test.ts @@ -169,6 +169,30 @@ test("worker causality preserves existing Java event and RPC record constructors assert.match(result, /this\(null, sent, error\);/); }); +test("worker UUID identities remain strings without changing ordinary UUID fields", () => { + const nestedTypes = new Map(); + assert.equal( + schemaTypeToJava( + { type: "string", format: "uuid" }, + true, + "WorkerEventReference", + "eventId", + nestedTypes, + ).javaType, + "String", + ); + assert.equal( + schemaTypeToJava( + { type: "string", format: "uuid" }, + true, + "OtherReference", + "eventId", + nestedTypes, + ).javaType, + "UUID", + ); +}); + for (const keyword of ["anyOf", "oneOf"] as const) { test(`root ${keyword} payload preserves raw JSON and existing data descriptors`, () => { const source = renderPayload({ diff --git a/java/scripts/codegen/java.ts b/java/scripts/codegen/java.ts index 48f3b81347..5b35481fe9 100644 --- a/java/scripts/codegen/java.ts +++ b/java/scripts/codegen/java.ts @@ -815,6 +815,17 @@ interface JavaTypeResolution { promotedUnionTypes: Set; } +const WORKER_STRING_UUID_PROPERTIES = new Set([ + "WorkerAdmission.ahpTurnId", + "WorkerEventReference.eventId", + "WorkerInput.queueItemId", + "WorkerNotificationReference.deliveryId", +]); + +function isWorkerStringUuidProperty(context: string, propName: string): boolean { + return WORKER_STRING_UUID_PROPERTIES.has(`${context}.${propName}`); +} + export function schemaTypeToJava( schema: JSONSchema7, required: boolean, @@ -892,6 +903,9 @@ export function schemaTypeToJava( if (schema.type === "string") { if (schema.format === "uuid") { + if (isWorkerStringUuidProperty(context, propName)) { + return { javaType: "String", imports }; + } imports.add("java.util.UUID"); return { javaType: "UUID", imports }; } diff --git a/java/sdk/src/generated/java/com/github/copilot/generated/WorkerAdmission.java b/java/sdk/src/generated/java/com/github/copilot/generated/WorkerAdmission.java index ed71918779..d60e2ab664 100644 --- a/java/sdk/src/generated/java/com/github/copilot/generated/WorkerAdmission.java +++ b/java/sdk/src/generated/java/com/github/copilot/generated/WorkerAdmission.java @@ -10,7 +10,6 @@ import com.fasterxml.jackson.annotation.JsonIgnoreProperties; import com.fasterxml.jackson.annotation.JsonInclude; import com.fasterxml.jackson.annotation.JsonProperty; -import java.util.UUID; import javax.annotation.processing.Generated; /** @@ -29,6 +28,6 @@ public record WorkerAdmission( /** May be omitted only for the matching current worker user.message. */ @JsonProperty("event") WorkerEventReference event, /** Actual AHP participant Turn UUID, not a native turn counter or provenance signal. */ - @JsonProperty("ahpTurnId") UUID ahpTurnId + @JsonProperty("ahpTurnId") String ahpTurnId ) { } diff --git a/java/sdk/src/generated/java/com/github/copilot/generated/WorkerEventReference.java b/java/sdk/src/generated/java/com/github/copilot/generated/WorkerEventReference.java index a57913ede5..ad3a74ef12 100644 --- a/java/sdk/src/generated/java/com/github/copilot/generated/WorkerEventReference.java +++ b/java/sdk/src/generated/java/com/github/copilot/generated/WorkerEventReference.java @@ -10,7 +10,6 @@ import com.fasterxml.jackson.annotation.JsonIgnoreProperties; import com.fasterxml.jackson.annotation.JsonInclude; import com.fasterxml.jackson.annotation.JsonProperty; -import java.util.UUID; import javax.annotation.processing.Generated; /** @@ -25,7 +24,7 @@ public record WorkerEventReference( /** Actual runtime session scope, at most 256 UTF-8 bytes. */ @JsonProperty("sessionId") String sessionId, /** Actual event occurrence UUID; copied without normalization. */ - @JsonProperty("eventId") UUID eventId, + @JsonProperty("eventId") String eventId, /** Actual event agent scope, absent for a root occurrence; at most 256 UTF-8 bytes. */ @JsonProperty("agentId") String agentId, /** Type of the observed occurrence. */ diff --git a/java/sdk/src/generated/java/com/github/copilot/generated/WorkerInput.java b/java/sdk/src/generated/java/com/github/copilot/generated/WorkerInput.java index d4bb60ee16..2047557430 100644 --- a/java/sdk/src/generated/java/com/github/copilot/generated/WorkerInput.java +++ b/java/sdk/src/generated/java/com/github/copilot/generated/WorkerInput.java @@ -11,7 +11,6 @@ import com.fasterxml.jackson.annotation.JsonInclude; import com.fasterxml.jackson.annotation.JsonProperty; import java.util.List; -import java.util.UUID; import javax.annotation.processing.Generated; /** @@ -24,7 +23,7 @@ @JsonIgnoreProperties(ignoreUnknown = true) public record WorkerInput( /** UUID allocated for this queue item by the admitting producer. */ - @JsonProperty("queueItemId") UUID queueItemId, + @JsonProperty("queueItemId") String queueItemId, /** Actual recipient task, at most 256 UTF-8 bytes. */ @JsonProperty("agentId") String agentId, /** Original invoking occurrence, never replaced by a reported/root alias. */ diff --git a/java/sdk/src/generated/java/com/github/copilot/generated/WorkerNotificationReference.java b/java/sdk/src/generated/java/com/github/copilot/generated/WorkerNotificationReference.java index 21661e059e..6653814f8f 100644 --- a/java/sdk/src/generated/java/com/github/copilot/generated/WorkerNotificationReference.java +++ b/java/sdk/src/generated/java/com/github/copilot/generated/WorkerNotificationReference.java @@ -10,7 +10,6 @@ import com.fasterxml.jackson.annotation.JsonIgnoreProperties; import com.fasterxml.jackson.annotation.JsonInclude; import com.fasterxml.jackson.annotation.JsonProperty; -import java.util.UUID; import javax.annotation.processing.Generated; /** @@ -23,7 +22,7 @@ @JsonIgnoreProperties(ignoreUnknown = true) public record WorkerNotificationReference( /** Actual notificationDeliveryId UUID. */ - @JsonProperty("deliveryId") UUID deliveryId, + @JsonProperty("deliveryId") String deliveryId, /** May be omitted only on the matching current system.notification. */ @JsonProperty("event") WorkerEventReference event, /** Actual consumption mode. */ diff --git a/java/sdk/src/generated/java/com/github/copilot/generated/rpc/WorkerAdmission.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/WorkerAdmission.java index 8d6a2d29e1..92edb593dd 100644 --- a/java/sdk/src/generated/java/com/github/copilot/generated/rpc/WorkerAdmission.java +++ b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/WorkerAdmission.java @@ -10,7 +10,6 @@ import com.fasterxml.jackson.annotation.JsonIgnoreProperties; import com.fasterxml.jackson.annotation.JsonInclude; import com.fasterxml.jackson.annotation.JsonProperty; -import java.util.UUID; import javax.annotation.processing.Generated; /** @@ -29,6 +28,6 @@ public record WorkerAdmission( /** May be omitted only for the matching current worker user.message. */ @JsonProperty("event") WorkerEventReference event, /** Actual AHP participant Turn UUID, not a native turn counter or provenance signal. */ - @JsonProperty("ahpTurnId") UUID ahpTurnId + @JsonProperty("ahpTurnId") String ahpTurnId ) { } diff --git a/java/sdk/src/generated/java/com/github/copilot/generated/rpc/WorkerEventReference.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/WorkerEventReference.java index 677655a200..a86be736ed 100644 --- a/java/sdk/src/generated/java/com/github/copilot/generated/rpc/WorkerEventReference.java +++ b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/WorkerEventReference.java @@ -10,7 +10,6 @@ import com.fasterxml.jackson.annotation.JsonIgnoreProperties; import com.fasterxml.jackson.annotation.JsonInclude; import com.fasterxml.jackson.annotation.JsonProperty; -import java.util.UUID; import javax.annotation.processing.Generated; /** @@ -25,7 +24,7 @@ public record WorkerEventReference( /** Actual runtime session scope, at most 256 UTF-8 bytes. */ @JsonProperty("sessionId") String sessionId, /** Actual event occurrence UUID; copied without normalization. */ - @JsonProperty("eventId") UUID eventId, + @JsonProperty("eventId") String eventId, /** Actual event agent scope, absent for a root occurrence; at most 256 UTF-8 bytes. */ @JsonProperty("agentId") String agentId, /** Type of the observed occurrence. */ diff --git a/java/sdk/src/generated/java/com/github/copilot/generated/rpc/WorkerInput.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/WorkerInput.java index 718851fa15..1c4cc2b120 100644 --- a/java/sdk/src/generated/java/com/github/copilot/generated/rpc/WorkerInput.java +++ b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/WorkerInput.java @@ -11,7 +11,6 @@ import com.fasterxml.jackson.annotation.JsonInclude; import com.fasterxml.jackson.annotation.JsonProperty; import java.util.List; -import java.util.UUID; import javax.annotation.processing.Generated; /** @@ -24,7 +23,7 @@ @JsonIgnoreProperties(ignoreUnknown = true) public record WorkerInput( /** UUID allocated for this queue item by the admitting producer. */ - @JsonProperty("queueItemId") UUID queueItemId, + @JsonProperty("queueItemId") String queueItemId, /** Actual recipient task, at most 256 UTF-8 bytes. */ @JsonProperty("agentId") String agentId, /** Original invoking occurrence, never replaced by a reported/root alias. */ diff --git a/java/sdk/src/generated/java/com/github/copilot/generated/rpc/WorkerNotificationReference.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/WorkerNotificationReference.java index 8f75f8c4c1..32b4865288 100644 --- a/java/sdk/src/generated/java/com/github/copilot/generated/rpc/WorkerNotificationReference.java +++ b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/WorkerNotificationReference.java @@ -10,7 +10,6 @@ import com.fasterxml.jackson.annotation.JsonIgnoreProperties; import com.fasterxml.jackson.annotation.JsonInclude; import com.fasterxml.jackson.annotation.JsonProperty; -import java.util.UUID; import javax.annotation.processing.Generated; /** @@ -23,7 +22,7 @@ @JsonIgnoreProperties(ignoreUnknown = true) public record WorkerNotificationReference( /** Actual notificationDeliveryId UUID. */ - @JsonProperty("deliveryId") UUID deliveryId, + @JsonProperty("deliveryId") String deliveryId, /** May be omitted only on the matching current system.notification. */ @JsonProperty("event") WorkerEventReference event, /** Actual consumption mode. */ diff --git a/nodejs/test/csharp-codegen.test.ts b/nodejs/test/csharp-codegen.test.ts index 29158febab..9a6ca7510d 100644 --- a/nodejs/test/csharp-codegen.test.ts +++ b/nodejs/test/csharp-codegen.test.ts @@ -215,6 +215,50 @@ describe("C# root event payload unions", () => { ' [JsonPropertyName("workerCausality")]' ); }); + + it("projects worker UUID identities as strings", () => { + const code = generateSessionEventsCode({ + definitions: { + WorkerEventReference: { + type: "object", + properties: { + eventId: { type: "string", format: "uuid" }, + }, + required: ["eventId"], + }, + WorkerCausality: { + type: "object", + properties: { + event: { $ref: "#/definitions/WorkerEventReference" }, + }, + required: ["event"], + }, + SessionEvent: { + anyOf: [ + { + type: "object", + properties: { + type: { const: "user.message" }, + data: { + type: "object", + properties: { + content: { type: "string" }, + workerCausality: { + $ref: "#/definitions/WorkerCausality", + }, + }, + required: ["content"], + }, + }, + required: ["type", "data"], + }, + ], + }, + }, + }); + expect(code).toContain("public required string EventId { get; set; }"); + expect(code).not.toContain("public required Guid EventId { get; set; }"); + }); }); describe("C# RPC codegen", () => { diff --git a/nodejs/test/python-codegen.test.ts b/nodejs/test/python-codegen.test.ts index c381a406c2..9f43a4c21a 100644 --- a/nodejs/test/python-codegen.test.ts +++ b/nodejs/test/python-codegen.test.ts @@ -49,6 +49,52 @@ describe("Python root event payload unions", () => { expect(code).toContain("data = SampleUnionData.from_dict(data_obj)"); expect(code).toContain("return SampleEmptyData()"); }); + + it("projects worker UUID identities as strings", () => { + const code = generatePythonSessionEventsCode({ + definitions: { + WorkerEventReference: { + type: "object", + properties: { + eventId: { type: "string", format: "uuid" }, + }, + required: ["eventId"], + }, + WorkerCausality: { + type: "object", + properties: { + event: { $ref: "#/definitions/WorkerEventReference" }, + }, + required: ["event"], + }, + SessionEvent: { + anyOf: [ + { + type: "object", + properties: { + type: { const: "user.message" }, + data: { + type: "object", + properties: { + content: { type: "string" }, + workerCausality: { + $ref: "#/definitions/WorkerCausality", + }, + }, + required: ["content"], + }, + }, + required: ["type", "data"], + }, + ], + }, + }, + }); + expect(code).toContain("class WorkerEventReference:"); + expect(code).toContain(" event_id: str"); + expect(code).toContain('event_id = from_str(obj.get("eventId"))'); + expect(code).not.toContain('event_id = from_uuid(obj.get("eventId"))'); + }); }); function pythonRequestSnippet(additions: 1 | 2): string { diff --git a/python/copilot/generated/session_events.py b/python/copilot/generated/session_events.py index 674c1036d3..9ca262540b 100644 --- a/python/copilot/generated/session_events.py +++ b/python/copilot/generated/session_events.py @@ -12933,7 +12933,7 @@ class WorkerAdmission: "An observed worker admission, not a claim that execution succeeded." kind: WorkerAdmissionKind message_id: str - ahp_turn_id: UUID | None = None + ahp_turn_id: str | None = None event: WorkerEventReference | None = None @staticmethod @@ -12941,7 +12941,7 @@ def from_dict(obj: Any) -> "WorkerAdmission": assert isinstance(obj, dict) kind = parse_enum(WorkerAdmissionKind, obj.get("kind")) message_id = from_str(obj.get("messageId")) - ahp_turn_id = from_union([from_none, from_uuid], obj.get("ahpTurnId")) + ahp_turn_id = from_union([from_none, from_str], obj.get("ahpTurnId")) event = from_union([from_none, WorkerEventReference.from_dict], obj.get("event")) return WorkerAdmission( kind=kind, @@ -12955,7 +12955,7 @@ def to_dict(self) -> dict: result["kind"] = to_enum(WorkerAdmissionKind, self.kind) result["messageId"] = from_str(self.message_id) if self.ahp_turn_id is not None: - result["ahpTurnId"] = from_union([from_none, to_uuid], self.ahp_turn_id) + result["ahpTurnId"] = from_union([from_none, from_str], self.ahp_turn_id) if self.event is not None: result["event"] = from_union([from_none, lambda x: to_class(WorkerEventReference, x)], self.event) return result @@ -13018,7 +13018,7 @@ def to_dict(self) -> dict: @dataclass class WorkerEventReference: "Exact observed event identity. No private registration generation or execution handle." - event_id: UUID + event_id: str event_type: WorkerEventType provenance: WorkerObservationProvenance session_id: str @@ -13027,7 +13027,7 @@ class WorkerEventReference: @staticmethod def from_dict(obj: Any) -> "WorkerEventReference": assert isinstance(obj, dict) - event_id = from_uuid(obj.get("eventId")) + event_id = from_str(obj.get("eventId")) event_type = parse_enum(WorkerEventType, obj.get("eventType")) provenance = parse_enum(WorkerObservationProvenance, obj.get("provenance")) session_id = from_str(obj.get("sessionId")) @@ -13042,7 +13042,7 @@ def from_dict(obj: Any) -> "WorkerEventReference": def to_dict(self) -> dict: result: dict = {} - result["eventId"] = to_uuid(self.event_id) + result["eventId"] = from_str(self.event_id) result["eventType"] = to_enum(WorkerEventType, self.event_type) result["provenance"] = to_enum(WorkerObservationProvenance, self.provenance) result["sessionId"] = from_str(self.session_id) @@ -13055,7 +13055,7 @@ def to_dict(self) -> dict: class WorkerInput: "Exact accepted worker input, distinct from a message, event, caller correlation or Turn." agent_id: str - queue_item_id: UUID + queue_item_id: str sender: WorkerEventReference | None = None sender_bridges: list[WorkerBridgeObservation] | None = None @@ -13063,7 +13063,7 @@ class WorkerInput: def from_dict(obj: Any) -> "WorkerInput": assert isinstance(obj, dict) agent_id = from_str(obj.get("agentId")) - queue_item_id = from_uuid(obj.get("queueItemId")) + queue_item_id = from_str(obj.get("queueItemId")) sender = from_union([from_none, WorkerEventReference.from_dict], obj.get("sender")) sender_bridges = from_union([from_none, lambda x: from_list(WorkerBridgeObservation.from_dict, x)], obj.get("senderBridges")) return WorkerInput( @@ -13076,7 +13076,7 @@ def from_dict(obj: Any) -> "WorkerInput": def to_dict(self) -> dict: result: dict = {} result["agentId"] = from_str(self.agent_id) - result["queueItemId"] = to_uuid(self.queue_item_id) + result["queueItemId"] = from_str(self.queue_item_id) if self.sender is not None: result["sender"] = from_union([from_none, lambda x: to_class(WorkerEventReference, x)], self.sender) if self.sender_bridges is not None: @@ -13087,14 +13087,14 @@ def to_dict(self) -> dict: @dataclass class WorkerNotificationReference: "Exact delivery and optional occurrence of a consumed worker notification." - delivery_id: UUID + delivery_id: str mode: WorkerNotificationMode event: WorkerEventReference | None = None @staticmethod def from_dict(obj: Any) -> "WorkerNotificationReference": assert isinstance(obj, dict) - delivery_id = from_uuid(obj.get("deliveryId")) + delivery_id = from_str(obj.get("deliveryId")) mode = parse_enum(WorkerNotificationMode, obj.get("mode")) event = from_union([from_none, WorkerEventReference.from_dict], obj.get("event")) return WorkerNotificationReference( @@ -13105,7 +13105,7 @@ def from_dict(obj: Any) -> "WorkerNotificationReference": def to_dict(self) -> dict: result: dict = {} - result["deliveryId"] = to_uuid(self.delivery_id) + result["deliveryId"] = from_str(self.delivery_id) result["mode"] = to_enum(WorkerNotificationMode, self.mode) if self.event is not None: result["event"] = from_union([from_none, lambda x: to_class(WorkerEventReference, x)], self.event) diff --git a/scripts/codegen/csharp.ts b/scripts/codegen/csharp.ts index 21022a02f5..ac489f6f7f 100644 --- a/scripts/codegen/csharp.ts +++ b/scripts/codegen/csharp.ts @@ -396,6 +396,17 @@ function requiresArgumentNullCheck(typeName: string, isRequired: boolean): boole return isRequired && !typeName.endsWith("?") && !isNonNullableCSharpValueType(typeName); } +const WORKER_STRING_UUID_PROPERTIES = new Set([ + "WorkerAdmission.AhpTurnId", + "WorkerEventReference.EventId", + "WorkerInput.QueueItemId", + "WorkerNotificationReference.DeliveryId", +]); + +function isWorkerStringUuidProperty(parentClassName: string, propName: string, schema: JSONSchema7): boolean { + return schema.format === "uuid" && WORKER_STRING_UUID_PROPERTIES.has(`${parentClassName}.${propName}`); +} + async function formatCSharpFile(filePath: string): Promise { try { const projectFile = path.join(REPO_ROOT, "dotnet/src/GitHub.Copilot.SDK.csproj"); @@ -1290,6 +1301,9 @@ function resolveSessionPropertyType( nestedClasses: Map, enumOutput: string[] ): string { + if (isWorkerStringUuidProperty(parentClassName, propName, propSchema)) { + return isRequired ? "string" : "string?"; + } if (isOpaqueJson(propSchema)) { return isRequired ? "JsonElement" : "JsonElement?"; } @@ -1791,6 +1805,9 @@ function getRpcUnionMatchExpression(schema: JSONSchema7, seenRefs: ReadonlySet Date: Mon, 28 Sep 2026 11:12:37 -0400 Subject: [PATCH 23/25] Fix CLI 1.0.89-6 test regressions Resolve schemas across standalone and runtime layouts, update RPC surface coverage, and make Rust workspace context assertions cross-platform and single-purpose. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> --- .../test/e2e/rpc_surface_coverage.e2e.test.ts | 2 +- nodejs/test/rust-codegen.test.ts | 11 ++-- rust/tests/e2e/rpc_workspace_checkpoints.rs | 50 +++++-------------- 3 files changed, 18 insertions(+), 45 deletions(-) diff --git a/nodejs/test/e2e/rpc_surface_coverage.e2e.test.ts b/nodejs/test/e2e/rpc_surface_coverage.e2e.test.ts index f3b2e57342..c8d6fa39e0 100644 --- a/nodejs/test/e2e/rpc_surface_coverage.e2e.test.ts +++ b/nodejs/test/e2e/rpc_surface_coverage.e2e.test.ts @@ -313,7 +313,7 @@ describe("Generated RPC surface coverage", () => { ...collectRuntimeFunctions(session.rpc, "session"), ]); - expect(inventory).toHaveLength(359); + expect(inventory).toHaveLength(368); expect([...runtimeFunctions.keys()].sort()).toEqual( inventory.map((method) => `${method.scope}.${method.path}`) ); diff --git a/nodejs/test/rust-codegen.test.ts b/nodejs/test/rust-codegen.test.ts index 10362d1439..514f5f3cc6 100644 --- a/nodejs/test/rust-codegen.test.ts +++ b/nodejs/test/rust-codegen.test.ts @@ -3,6 +3,7 @@ import { normalizeSchemaBrandCasing, postProcessSchema, propagateInternalVisibility, + resolveCopilotSchemaPaths, stripBooleanLiterals, } from "../../scripts/codegen/utils.ts"; import type { JSONSchema7 } from "json-schema"; @@ -772,18 +773,14 @@ describe("Rust x-legacy-parameters", () => { ).toThrow(/Rust string enum Kind is requested for different values/); }); - it("keeps every const discriminator of the committed API schema distinct in Rust", () => { + it("keeps every const discriminator of the selected API schema distinct in Rust", async () => { + const { apiSchemaPath } = await resolveCopilotSchemaPaths(); // Mirror the generator's own schema preparation so emission order matches. const schema = propagateInternalVisibility( postProcessSchema( stripBooleanLiterals( normalizeSchemaBrandCasing( - JSON.parse( - readFileSync( - new URL("../../../../generated/api.schema.json", import.meta.url), - "utf8" - ) - ) as ApiSchema + JSON.parse(readFileSync(apiSchemaPath, "utf8")) as ApiSchema ) ) as JSONSchema7 ) diff --git a/rust/tests/e2e/rpc_workspace_checkpoints.rs b/rust/tests/e2e/rpc_workspace_checkpoints.rs index dab711b0f8..ffb1c923f2 100644 --- a/rust/tests/e2e/rpc_workspace_checkpoints.rs +++ b/rust/tests/e2e/rpc_workspace_checkpoints.rs @@ -1,9 +1,6 @@ use std::path::Path; use std::process::Command; -use std::sync::Arc; -use github_copilot_sdk::ResumeSessionConfig; -use github_copilot_sdk::handler::ApproveAllHandler; use github_copilot_sdk::rpc::{ WorkspaceDiffFileChangeType, WorkspaceDiffMode, WorkspacesDiffRequest, WorkspacesReadCheckpointRequest, WorkspacesReadFileRequest, WorkspacesSaveLargePasteRequest, @@ -187,6 +184,15 @@ fn normalize_path(path: &str) -> String { path.replace('\\', "/") } +fn canonical_normalized_path(path: &str) -> String { + normalize_path( + std::fs::canonicalize(path) + .expect("canonicalize workspace path") + .to_string_lossy() + .as_ref(), + ) +} + #[tokio::test] async fn should_record_git_context_in_a_new_session_workspace() { super::support::with_e2e_context_no_snapshot(|ctx| { @@ -233,11 +239,10 @@ async fn should_record_git_context_in_a_new_session_workspace() { // The repository was created exactly at the session's working // directory, so the recorded root is that directory and not an // ancestor of it. - let git_root = normalize_path(workspace.git_root.as_deref().expect("git root")); - assert_eq!( - Some(git_root.as_str()), - workspace.cwd.as_deref().map(normalize_path).as_deref() - ); + let git_root = + canonical_normalized_path(workspace.git_root.as_deref().expect("git root")); + let cwd = canonical_normalized_path(workspace.cwd.as_deref().expect("workspace cwd")); + assert_eq!(git_root, cwd); let work_dir_name = ctx .work_dir() .file_name() @@ -254,36 +259,7 @@ async fn should_record_git_context_in_a_new_session_workspace() { ); assert_eq!(workspace.client_name.as_deref(), Some("rust-e2e-client")); - // The recorded context survives a resume rather than being dropped - // or re-derived into something else. - let session_id = session.id().clone(); session.disconnect().await.expect("disconnect session"); - let resumed = client - .resume_session( - ResumeSessionConfig::new(session_id) - .with_permission_handler(Arc::new(ApproveAllHandler)) - .with_github_token(super::support::DEFAULT_TEST_TOKEN), - ) - .await - .expect("resume session"); - let resumed_workspace = resumed - .rpc() - .workspaces() - .get_workspace() - .await - .expect("get workspace after resume") - .workspace - .expect("workspace after resume"); - assert_eq!( - resumed_workspace.repository.as_deref(), - Some("test-org/test-repo") - ); - assert_eq!( - resumed_workspace.branch.as_deref(), - Some("feature/test-branch") - ); - - resumed.disconnect().await.expect("disconnect resumed"); client.stop().await.expect("stop client"); }) }) From a2660df948aefdad1cb6df782be596dfb73a584a Mon Sep 17 00:00:00 2001 From: Michael Dinh Date: Mon, 28 Sep 2026 18:10:30 -0400 Subject: [PATCH 24/25] Consume final worker causality revision 39 Apply the released runtime contract and fixture, preserve standalone SDK compatibility, and regenerate all six projections against CLI 1.0.89-7. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> --- dotnet/src/Generated/Rpc.cs | 35 ++- dotnet/src/Generated/SessionEvents.cs | 2 +- dotnet/test/Unit/WorkerCausalityTests.cs | 42 ++- .../diagnosticmetadata/worker_causality.go | 78 ++++- go/rpc/zrpc.go | 70 +++-- go/rpc/zsession_events.go | 2 +- go/worker_causality_test.go | 62 ++-- java/scripts/codegen/java.test.ts | 16 +- java/scripts/codegen/java.ts | 138 +++++---- .../generated/SessionFusionResolvedEvent.java | 2 +- .../generated/SystemNotificationEvent.java | 10 +- .../rpc/CatalogAiSkillCandidate.java | 4 +- .../rpc/CatalogAiSkillInstallability.java | 2 +- .../generated/rpc/CatalogSearchPage.java | 4 +- .../rpc/CatalogSearchPagination.java | 2 +- .../generated/rpc/PermissionModeSource.java | 2 +- .../generated/rpc/SandboxConfigSource.java | 2 +- .../generated/rpc/SessionOpenOptions.java | 2 +- .../rpc/SessionOptionsUpdateParams.java | 2 +- .../rpc/SessionPermissionsSetModeParams.java | 2 +- .../github/copilot/WorkerCausalityTest.java | 39 +++ nodejs/src/generated/rpc.ts | 31 +- nodejs/src/generated/session-events.ts | 2 +- nodejs/src/workerCausality.ts | 9 +- nodejs/test/worker-causality.test.ts | 37 ++- python/copilot/generated/rpc.py | 164 ++++++----- python/test_worker_causality.py | 38 +++ rust/src/generated/api_types.rs | 51 +--- rust/src/generated/rpc.rs | 4 +- rust/src/generated/session_events.rs | 2 +- rust/tests/api_types_test.rs | 2 +- test/worker-causality.json | 276 +++++++++++++++--- 32 files changed, 771 insertions(+), 363 deletions(-) diff --git a/dotnet/src/Generated/Rpc.cs b/dotnet/src/Generated/Rpc.cs index c232498a55..88f27f6591 100644 --- a/dotnet/src/Generated/Rpc.cs +++ b/dotnet/src/Generated/Rpc.cs @@ -4842,7 +4842,7 @@ public sealed class CatalogAiSkillCandidateProvenance public string ObservedAt { get; set; } = string.Empty; } -/// An inert AI skill catalog result. AI skills are discovery-only and cannot be represented as installable through this surface. +/// An inert AI skill catalog result. Verified Skill candidates may be installable only when the runtime reports installability and the selected session is permitted to plan installation. /// The ai-skill variant of . [Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] public partial class CatalogCandidateAiSkill : CatalogCandidate @@ -4874,7 +4874,7 @@ public partial class CatalogCandidateAiSkill : CatalogCandidate [JsonPropertyName("handleExpiresAt")] public required string HandleExpiresAt { get; set; } - /// AI skills are discovery-only and cannot be installed through this surface. + /// Whether this AI skill candidate can be planned for verified installation in the selected session. [JsonPropertyName("installability")] public required CatalogAiSkillInstallability Installability { get; set; } @@ -5026,7 +5026,7 @@ public sealed class CatalogSearchPagination [JsonPropertyName("pageSize")] public int PageSize { get; set; } - /// Opaque authority-issued pagination token. Only the runtime decodes it or changes targetPage; SDK consumers must not decode, modify or log it. It has no runtime-created expiry or cache. + /// Opaque pagination token. Session-bound search returns an expiring runtime-owned reference retaining the exact private authority token, original search and authority. Legacy unbound search returns the authority token unchanged, without a runtime-created expiry. Only the runtime unwraps tokens or changes targetPage; SDK consumers must not decode, modify or log them. [UnconditionalSuppressMessage("Trimming", "IL2026", Justification = "Safe for generated string properties: JSON Schema minLength/maxLength map to string length validation, not reflection over trimmed Count members")] [MinLength(1)] [MaxLength(4096)] @@ -5311,7 +5311,7 @@ public partial class CatalogSearchResultUnavailable : CatalogSearchResult public required CatalogUnavailableReason Reason { get; set; } } -/// An explicit numbered-page request. The SDK treats the token as opaque; only the runtime decodes it and changes its targetPage. Authority validation binds navigation to the original search. No snapshot stability or token TTL is promised. +/// An explicit numbered-page request. SDK consumers treat the token as opaque. For bound search, the runtime unwraps an expiring owner-bound reference to the private authority token; only the runtime changes the authority token's targetPage. Legacy unbound navigation keeps its authority-issued token semantics. No snapshot stability is promised. [Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] public sealed class CatalogSearchPage { @@ -5319,7 +5319,7 @@ public sealed class CatalogSearchPage [JsonPropertyName("number")] public int Number { get; set; } - /// Opaque authority-issued pagination token from an earlier response. Never decode, modify or log it in an SDK consumer. + /// Opaque pagination token from an earlier response, owner-bound when session-bound search was requested. Never decode, modify or log it in an SDK consumer. Expired or foreign bound references require a fresh bound search, not a legacy retry. [UnconditionalSuppressMessage("Trimming", "IL2026", Justification = "Safe for generated string properties: JSON Schema minLength/maxLength map to string length validation, not reflection over trimmed Count members")] [MinLength(1)] [MaxLength(4096)] @@ -17499,10 +17499,9 @@ internal sealed class SessionUpdateOptionsParams [JsonPropertyName("sandboxConfig")] public SandboxConfig? SandboxConfig { get; set; } - /// Origin of the sandbox choice. The runtime uses this only for internal telemetry provenance; managed policy is derived independently. - [JsonInclude] + /// Origin of the sandbox choice. Settings-derived origins (never_configured, user_enabled, user_disabled, repository_policy) let managed policy floor a host preference; explicit below-floor changes remain policy conflicts unless a session opt-out is authorized. Also used for telemetry provenance. [JsonPropertyName("sandboxConfigSource")] - internal SandboxConfigSource? SandboxConfigSource { get; set; } + public SandboxConfigSource? SandboxConfigSource { get; set; } /// Replaces the session's capability set with the given list. Use to enable or disable capabilities mid-session (e.g., remove `memory` for reproducible scripted runs). Omit the field to leave the existing capability set unchanged. [JsonPropertyName("sessionCapabilities")] @@ -20742,7 +20741,7 @@ internal sealed class PermissionsSetModeRequest [JsonPropertyName("sessionId")] public string SessionId { get; set; } = string.Empty; - /// Optional source for permission-mode telemetry. Defaults to `rpc` when omitted for SDK callers. + /// Optional source for permission-mode telemetry. `organization_targeting` is reserved for startup selection after the authenticated account matches an organization targeting policy; SDK callers default to `rpc` and cannot claim targeting provenance. [JsonPropertyName("source")] public PermissionModeSource? Source { get; set; } } @@ -28705,7 +28704,7 @@ public override void Write(Utf8JsonWriter writer, CatalogMcpServerInstallability } -/// Typed non-installable state for an AI skill candidate. +/// Typed installability state for an AI skill candidate. [Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] [JsonConverter(typeof(Converter))] [DebuggerDisplay("{Value,nq}")] @@ -28737,7 +28736,7 @@ public CatalogAiSkillInstallability(string value) /// Policy refuses Skill installation for the selected session or authority. public static CatalogAiSkillInstallability PolicyForbids { get; } = new("policy-forbids"); - /// AI skills are discovery-only on this surface. + /// Compatibility value for discovery-only callers that did not negotiate Skill installation. public static CatalogAiSkillInstallability NotInstallableKind { get; } = new("not-installable-kind"); /// Returns a value indicating whether two instances are equivalent. @@ -35920,7 +35919,7 @@ public override void Write(Utf8JsonWriter writer, OptionsUpdateReasoningSummary } -/// Origin of the sandbox choice supplied by an internal client. +/// Origin of the sandbox choice supplied by the host. Settings-derived origins let managed policy floor the host preference; do not tag explicit session overrides as settings-derived. [Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] [JsonConverter(typeof(Converter))] [DebuggerDisplay("{Value,nq}")] @@ -37579,7 +37578,7 @@ public override void Write(Utf8JsonWriter writer, PermissionsSetApproveAllSource } -/// Optional source for permission-mode telemetry. Defaults to `rpc` when omitted for SDK callers. +/// Optional source for permission-mode telemetry. `organization_targeting` is reserved for startup selection after the authenticated account matches an organization targeting policy; SDK callers default to `rpc` and cannot claim targeting provenance. [Experimental(global::GitHub.Copilot.Diagnostics.Experimental)] [JsonConverter(typeof(Converter))] [DebuggerDisplay("{Value,nq}")] @@ -37611,7 +37610,7 @@ public PermissionModeSource(string value) /// The mode was set at startup by the `defaultPermissionMode` user setting. public static PermissionModeSource UserSetting { get; } = new("user_setting"); - /// Historical compatibility value for runtimes that selected Assisted mode through organization targeting. Current runtimes do not produce this source. + /// The mode was set at startup because the authenticated account matched an organization targeting policy. public static PermissionModeSource OrganizationTargeting { get; } = new("organization_targeting"); /// The mode was set through an RPC caller. @@ -44063,7 +44062,7 @@ public async Task PrepareLoginAsync(string se return await CopilotClient.InvokeRpcAsync(_session.Rpc, "session.mcp.oauth.prepareLogin", [request], cancellationToken); } - /// Starts OAuth authentication for a remote MCP server. + /// Starts OAuth authentication for a remote MCP server. Owned servers require the original one-use prepareLogin handle and exact installation ID; manual servers retain the existing direct login behaviour. /// Name of the remote MCP server to authenticate. /// When true, clears any cached OAuth token for the server and runs a full new authorization. Use when the user explicitly wants to switch accounts or believes their session is stuck. /// Optional override for the OAuth client display name shown on the consent screen. Applies to newly registered dynamic clients only — existing registrations keep the name they were created with. When omitted, the runtime applies a neutral fallback; callers driving interactive auth should pass their own surface-specific label so the consent screen matches the product the user sees. @@ -44083,7 +44082,7 @@ public async Task LoginAsync(string serverName, bool? force return await CopilotClient.InvokeRpcAsync(_session.Rpc, "session.mcp.oauth.login", [request], cancellationToken); } - /// Starts OAuth authentication for a remote MCP server. + /// Starts OAuth authentication for a remote MCP server. Owned servers require the original one-use prepareLogin handle and exact installation ID; manual servers retain the existing direct login behaviour. /// Remote MCP server name and optional overrides controlling reauthentication, OAuth client display name, callback success-page copy, and static OAuth client selection. /// The to monitor for cancellation requests. The default is . /// OAuth authorization URL the caller should open, or empty when cached tokens already authenticated the server. @@ -44816,7 +44815,7 @@ internal OptionsApi(CopilotSession session) /// Use shell.initProfile instead. Shell init profile (`None` or `NonInteractive`). /// PowerShell process flags applied to built-in and user-requested shell commands. /// Resolved sandbox configuration. - /// Origin of the sandbox choice. The runtime uses this only for internal telemetry provenance; managed policy is derived independently. + /// Origin of the sandbox choice. Settings-derived origins (never_configured, user_enabled, user_disabled, repository_policy) let managed policy floor a host preference; explicit below-floor changes remain policy conflicts unless a session opt-out is authorized. Also used for telemetry provenance. /// Whether interactive shell sessions are logged. /// How env values are passed to MCP servers (`direct` inlines literal values; `indirect` resolves at launch). /// Whether to include instructions from every MCP server in the system prompt instead of only allowlisted servers. @@ -45454,7 +45453,7 @@ public async Task SetApproveAllAsync(bool enable /// Sets the permission mode for the session. `manual` follows the normal approval flow, `assisted` attaches LLM safety recommendations, and `allow-all` automatically approves permission requests. The result returns the authoritative post-mutation mode so callers can update local state without racing the `session.permissions_changed` notification. /// Permission mode to apply. /// Optional judge model id for assisted mode. When omitted, the session resolves the provider default: `gpt-5.5` for CAPI sessions and the active session model for BYOK sessions. - /// Optional source for permission-mode telemetry. Defaults to `rpc` when omitted for SDK callers. + /// Optional source for permission-mode telemetry. `organization_targeting` is reserved for startup selection after the authenticated account matches an organization targeting policy; SDK callers default to `rpc` and cannot claim targeting provenance. /// The to monitor for cancellation requests. The default is . /// Indicates whether the requested permission mode was applied and reports the authoritative post-mutation mode. public async Task SetModeAsync(PermissionMode mode, string? assistedApprovalModel = null, PermissionModeSource? source = null, CancellationToken cancellationToken = default) diff --git a/dotnet/src/Generated/SessionEvents.cs b/dotnet/src/Generated/SessionEvents.cs index b22c513549..082654ad7e 100644 --- a/dotnet/src/Generated/SessionEvents.cs +++ b/dotnet/src/Generated/SessionEvents.cs @@ -3469,7 +3469,7 @@ public sealed partial class SessionFusionResolvedData [JsonPropertyName("scores")] public FusionScores? Scores { get; set; } - /// Concrete model selected for the review or judge phase, when required. + /// Concrete model selected for Critique review, or the legacy Cascade judge/repair model when role-specific fields are absent. [JsonPropertyName("secondaryModel")] public string? SecondaryModel { get; set; } diff --git a/dotnet/test/Unit/WorkerCausalityTests.cs b/dotnet/test/Unit/WorkerCausalityTests.cs index 9bb5ea7aa7..54fc367dce 100644 --- a/dotnet/test/Unit/WorkerCausalityTests.cs +++ b/dotnet/test/Unit/WorkerCausalityTests.cs @@ -14,7 +14,7 @@ namespace GitHub.Copilot.Test.Unit; public class WorkerCausalityTests { private static readonly JsonNode Corpus = JsonNode.Parse(File.ReadAllText( - Path.Join(AppContext.BaseDirectory, "worker-causality.json")))!; + Path.Combine(AppContext.BaseDirectory, "worker-causality.json")))!; private static readonly JsonSerializerOptions RpcOptions = new() { PropertyNamingPolicy = JsonNamingPolicy.CamelCase, @@ -97,4 +97,44 @@ public void InvalidUnicodeScalarIdentitiesAreUnavailable() Assert.True(result.Sent); } } + + [Fact] + public void UppercaseWorkerUuidIdentitiesRoundTripExactly() + { + const string wire = """ + { + "sent": true, + "workerCausality": { + "version": 1, + "observationProvenance": "native", + "sources": [{ + "input": { + "queueItemId": "ABCDEFAB-CDEF-4ABC-8ABC-ABCDEFABCDEF", + "agentId": "worker", + "sender": { + "sessionId": "session-uppercase", + "eventId": "FEDCBAFE-DCBA-4FED-8FED-FEDCBAFEDCBA", + "eventType": "tool.execution_start", + "provenance": "native" + } + }, + "admissions": [{ + "kind": "queued_input", + "messageId": "message-uppercase", + "ahpTurnId": "AAAABBBB-CCCC-4DDD-8EEE-FFFFAAAABBBB" + }], + "captureComplete": true, + "notification": { + "deliveryId": "BBBBCCCC-DDDD-4EEE-8FFF-AAAABBBBCCCC", + "mode": "queued" + } + }], + "captureComplete": true + } + } + """; + var source = JsonNode.Parse(wire)!; + var observed = JsonNode.Parse(Decode(source, false))!; + Assert.True(JsonNode.DeepEquals(source["workerCausality"], observed["workerCausality"])); + } } diff --git a/go/internal/diagnosticmetadata/worker_causality.go b/go/internal/diagnosticmetadata/worker_causality.go index 1f6395cc32..ad4139c89d 100644 --- a/go/internal/diagnosticmetadata/worker_causality.go +++ b/go/internal/diagnosticmetadata/worker_causality.go @@ -5,9 +5,9 @@ package diagnosticmetadata import ( "encoding/json" - "errors" "log" "regexp" + "strconv" "unicode" "unicode/utf8" ) @@ -199,16 +199,70 @@ func source(value any) bool { return true } -type budget struct{ count int } +func compactJSONStringSize(value string) int { + size := 2 + for _, character := range value { + switch character { + case '"', '\\', '\b', '\f', '\n', '\r', '\t': + size += 2 + default: + if character < 0x20 { + size += 6 + } else { + size += utf8.RuneLen(character) + } + } + } + return size +} -func (b *budget) Write(p []byte) (int, error) { - b.count += len(p) - // Encoder adds one newline; the compact single-property field is limited to 4096. - if b.count > 4097 { - b.count = 4098 - return 0, errors.New("worker metadata budget") +func compactJSONSize(value any, depth int) (int, bool) { + if depth > 32 { + return 0, false + } + switch value := value.(type) { + case nil: + return 4, true + case bool: + if value { + return 4, true + } + return 5, true + case float64: + return len(strconv.AppendFloat(nil, value, 'g', -1, 64)), true + case string: + return compactJSONStringSize(value), true + case []any: + size := 2 + for index, item := range value { + itemSize, ok := compactJSONSize(item, depth+1) + if !ok { + return 0, false + } + if index > 0 { + size++ + } + size += itemSize + } + return size, true + case map[string]any: + size := 2 + index := 0 + for key, item := range value { + itemSize, ok := compactJSONSize(item, depth+1) + if !ok { + return 0, false + } + if index > 0 { + size++ + } + size += compactJSONStringSize(key) + 1 + itemSize + index++ + } + return size, true + default: + return 0, false } - return len(p), nil } func small(value any, remaining *int, depth int) bool { @@ -259,10 +313,8 @@ func ReadWorkerCausality(raw json.RawMessage, target any) bool { var value map[string]any remaining := 4096 if json.Unmarshal(raw, &value) == nil && small(value, &remaining, 0) && supported(value) { - writer := &budget{} - encoder := json.NewEncoder(writer) - encoder.SetEscapeHTML(false) - if encoder.Encode(map[string]any{"workerCausality": value}) == nil && json.Unmarshal(raw, target) == nil { + size, ok := compactJSONSize(map[string]any{"workerCausality": value}, 0) + if ok && size <= 4096 && json.Unmarshal(raw, target) == nil { return true } } diff --git a/go/rpc/zrpc.go b/go/rpc/zrpc.go index 2a7b94f0f7..ddbcd52b87 100644 --- a/go/rpc/zrpc.go +++ b/go/rpc/zrpc.go @@ -1949,8 +1949,9 @@ func (CatalogAgentPluginCandidate) Kind() CatalogCandidateKind { return CatalogCandidateKindPlugin } -// An inert AI skill catalog result. AI skills are discovery-only and cannot be represented -// as installable through this surface. +// An inert AI skill catalog result. Verified Skill candidates may be installable only when +// the runtime reports installability and the selected session is permitted to plan +// installation. // Experimental: CatalogAiSkillCandidate is part of an experimental API and may change or be // removed. type CatalogAiSkillCandidate struct { @@ -1964,7 +1965,8 @@ type CatalogAiSkillCandidate struct { Handle string `json:"handle"` // ISO 8601 timestamp after which the handle is stale and will be rejected. HandleExpiresAt string `json:"handleExpiresAt"` - // AI skills are discovery-only and cannot be installed through this surface + // Whether this AI skill candidate can be planned for verified installation in the selected + // session. Installability CatalogAiSkillInstallability `json:"installability"` // Media type of the underlying AI skill card MediaType CatalogAiSkillMediaType `json:"mediaType"` @@ -2152,9 +2154,10 @@ type CatalogResourceIdentity string // removed. type CatalogResourceVersion string -// An explicit numbered-page request. The SDK treats the token as opaque; only the runtime -// decodes it and changes its targetPage. Authority validation binds navigation to the -// original search. No snapshot stability or token TTL is promised. +// An explicit numbered-page request. SDK consumers treat the token as opaque. For bound +// search, the runtime unwraps an expiring owner-bound reference to the private authority +// token; only the runtime changes the authority token's targetPage. Legacy unbound +// navigation keeps its authority-issued token semantics. No snapshot stability is promised. // Experimental: CatalogSearchPage is part of an experimental API and may change or be // removed. type CatalogSearchPage struct { @@ -2162,8 +2165,9 @@ type CatalogSearchPage struct { // navigation window ceil(1000 / pageSize). Repeat the search without page to discover newly // available pages beyond that signed pageCount. Number int32 `json:"number"` - // Opaque authority-issued pagination token from an earlier response. Never decode, modify - // or log it in an SDK consumer. + // Opaque pagination token from an earlier response, owner-bound when session-bound search + // was requested. Never decode, modify or log it in an SDK consumer. Expired or foreign + // bound references require a fresh bound search, not a legacy retry. Token string `json:"token"` } @@ -2189,9 +2193,11 @@ type CatalogSearchPagination struct { PageCount int64 `json:"pageCount"` // Page size bound to the search, equal to the effective request limit. PageSize int32 `json:"pageSize"` - // Opaque authority-issued pagination token. Only the runtime decodes it or changes - // targetPage; SDK consumers must not decode, modify or log it. It has no runtime-created - // expiry or cache. + // Opaque pagination token. Session-bound search returns an expiring runtime-owned reference + // retaining the exact private authority token, original search and authority. Legacy + // unbound search returns the authority token unchanged, without a runtime-created expiry. + // Only the runtime unwraps tokens or changes targetPage; SDK consumers must not decode, + // modify or log them. Token string `json:"token"` // Backend-reported count for this response, not the number of returned candidates. Its // relationship to the full query result set is unknown. @@ -10751,8 +10757,9 @@ type PermissionsSetModeRequest struct { AssistedApprovalModel *string `json:"assistedApprovalModel,omitempty"` // Permission mode to apply Mode PermissionMode `json:"mode"` - // Optional source for permission-mode telemetry. Defaults to `rpc` when omitted for SDK - // callers. + // Optional source for permission-mode telemetry. `organization_targeting` is reserved for + // startup selection after the authenticated account matches an organization targeting + // policy; SDK callers default to `rpc` and cannot claim targeting provenance. Source *PermissionModeSource `json:"source,omitempty"` } @@ -14593,10 +14600,10 @@ type SessionOpenOptions struct { RunningInInteractiveMode *bool `json:"runningInInteractiveMode,omitempty"` // Resolved sandbox configuration. SandboxConfig *SandboxConfig `json:"sandboxConfig,omitempty"` - // Origin of the sandbox choice. The runtime uses this only for internal telemetry - // provenance; managed policy is derived independently. - // Internal: SandboxConfigSource is part of the SDK's internal API surface and is not - // intended for external use. + // Origin of the sandbox choice. Settings-derived origins (never_configured, user_enabled, + // user_disabled, repository_policy) let managed policy floor a host preference; explicit + // below-floor changes remain policy conflicts unless a session opt-out is authorized. Also + // used for telemetry provenance. SandboxConfigSource *SandboxConfigSource `json:"sandboxConfigSource,omitempty"` // Capabilities enabled for this session. SessionCapabilities []SessionCapability `json:"sessionCapabilities,omitzero"` @@ -15845,10 +15852,10 @@ type SessionUpdateOptionsParams struct { RunningInInteractiveMode *bool `json:"runningInInteractiveMode,omitempty"` // Resolved sandbox configuration. SandboxConfig *SandboxConfig `json:"sandboxConfig,omitempty"` - // Origin of the sandbox choice. The runtime uses this only for internal telemetry - // provenance; managed policy is derived independently. - // Internal: SandboxConfigSource is part of the SDK's internal API surface and is not - // intended for external use. + // Origin of the sandbox choice. Settings-derived origins (never_configured, user_enabled, + // user_disabled, repository_policy) let managed policy floor a host preference; explicit + // below-floor changes remain policy conflicts unless a session opt-out is authorized. Also + // used for telemetry provenance. SandboxConfigSource *SandboxConfigSource `json:"sandboxConfigSource,omitempty"` // Replaces the session's capability set with the given list. Use to enable or disable // capabilities mid-session (e.g., remove `memory` for reproducible scripted runs). Omit the @@ -20727,7 +20734,7 @@ const ( CatalogAiSkillCandidateKindAiSkill CatalogAiSkillCandidateKind = "ai-skill" ) -// Typed non-installable state for an AI skill candidate +// Typed installability state for an AI skill candidate // Experimental: CatalogAiSkillInstallability is part of an experimental API and may change // or be removed. type CatalogAiSkillInstallability string @@ -20740,7 +20747,7 @@ const ( CatalogAiSkillInstallabilityInstallable CatalogAiSkillInstallability = "installable" // The candidate lacks verified materialisation metadata required for installation. CatalogAiSkillInstallabilityMaterialisationUnavailable CatalogAiSkillInstallability = "materialisation-unavailable" - // AI skills are discovery-only on this surface. + // Compatibility value for discovery-only callers that did not negotiate Skill installation. CatalogAiSkillInstallabilityNotInstallableKind CatalogAiSkillInstallability = "not-installable-kind" // Policy refuses Skill installation for the selected session or authority. CatalogAiSkillInstallabilityPolicyForbids CatalogAiSkillInstallability = "policy-forbids" @@ -23264,8 +23271,9 @@ const ( PermissionModeManual PermissionMode = "manual" ) -// Optional source for permission-mode telemetry. Defaults to `rpc` when omitted for SDK -// callers. +// Optional source for permission-mode telemetry. `organization_targeting` is reserved for +// startup selection after the authenticated account matches an organization targeting +// policy; SDK callers default to `rpc` and cannot claim targeting provenance. // Experimental: PermissionModeSource is part of an experimental API and may change or be // removed. type PermissionModeSource string @@ -23275,8 +23283,8 @@ const ( PermissionModeSourceAutopilotConfirmation PermissionModeSource = "autopilot_confirmation" // The mode was set from a CLI command-line flag. PermissionModeSourceCLIFlag PermissionModeSource = "cli_flag" - // Historical compatibility value for runtimes that selected Assisted mode through - // organization targeting. Current runtimes do not produce this source. + // The mode was set at startup because the authenticated account matched an organization + // targeting policy. PermissionModeSourceOrganizationTargeting PermissionModeSource = "organization_targeting" // The mode was set through an RPC caller. PermissionModeSourceRPC PermissionModeSource = "rpc" @@ -23772,7 +23780,9 @@ const ( ResponseFormatTypeJSONSchema ResponseFormatType = "json_schema" ) -// Origin of the sandbox choice supplied by an internal client. +// Origin of the sandbox choice supplied by the host. Settings-derived origins let managed +// policy floor the host preference; do not tag explicit session overrides as +// settings-derived. // Experimental: SandboxConfigSource is part of an experimental API and may change or be // removed. type SandboxConfigSource string @@ -30080,7 +30090,9 @@ func (a *MCPOauthAPI) HandlePendingRequest(ctx context.Context, params *MCPOauth return &result, nil } -// Login starts OAuth authentication for a remote MCP server. +// Login starts OAuth authentication for a remote MCP server. Owned servers require the +// original one-use prepareLogin handle and exact installation ID; manual servers retain the +// existing direct login behaviour. // // RPC method: session.mcp.oauth.login. // diff --git a/go/rpc/zsession_events.go b/go/rpc/zsession_events.go index 143d1d9f37..a183050848 100644 --- a/go/rpc/zsession_events.go +++ b/go/rpc/zsession_events.go @@ -1245,7 +1245,7 @@ type SessionFusionResolvedData struct { RuleName *string `json:"ruleName,omitempty"` // Validated capability scores used to select the route. Scores *FusionScores `json:"scores,omitempty"` - // Concrete model selected for the review or judge phase, when required. + // Concrete model selected for Critique review, or the legacy Cascade judge/repair model when role-specific fields are absent. SecondaryModel *string `json:"secondaryModel"` // Synthetic HydraFusion model selected for the session. SyntheticModel string `json:"syntheticModel"` diff --git a/go/worker_causality_test.go b/go/worker_causality_test.go index e370a894d5..57ce4a2b30 100644 --- a/go/worker_causality_test.go +++ b/go/worker_causality_test.go @@ -111,36 +111,42 @@ func TestWorkerCausalityPublicReaders(t *testing.T) { } func TestAssistantTurnStartPreservesParentToolCallIDWithWorkerCausality(t *testing.T) { - for _, test := range workerCorpus(t).Valid { - if test.Name != "two_source_assistant_turn_start" { - continue - } - var event rpc.SessionEvent - if err := json.Unmarshal(test.Event, &event); err != nil { - t.Fatal(err) - } - data, ok := event.Data.(*rpc.AssistantTurnStartData) - if !ok { - t.Fatalf("data type = %T", event.Data) - } - if data.ParentToolCallID == nil || *data.ParentToolCallID != "parent-tool-call" { - t.Fatalf("parent tool call ID = %v", data.ParentToolCallID) - } - encoded, err := json.Marshal(event) - if err != nil { - t.Fatal(err) - } - var roundTrip map[string]any - if err := json.Unmarshal(encoded, &roundTrip); err != nil { - t.Fatal(err) - } - payload := roundTrip["data"].(map[string]any) - if payload["parentToolCallId"] != "parent-tool-call" { - t.Fatalf("round-trip parent tool call ID = %v", payload["parentToolCallId"]) + var event rpc.SessionEvent + wire := []byte(`{ + "type":"assistant.turn_start", + "data":{ + "turnId":"1", + "parentToolCallId":"parent-tool-call", + "workerCausality":{ + "version":1, + "observationProvenance":"native", + "sources":[], + "captureComplete":true + } } - return + }`) + if err := json.Unmarshal(wire, &event); err != nil { + t.Fatal(err) + } + data, ok := event.Data.(*rpc.AssistantTurnStartData) + if !ok { + t.Fatalf("data type = %T", event.Data) + } + if data.ParentToolCallID == nil || *data.ParentToolCallID != "parent-tool-call" { + t.Fatalf("parent tool call ID = %v", data.ParentToolCallID) + } + encoded, err := json.Marshal(event) + if err != nil { + t.Fatal(err) + } + var roundTrip map[string]any + if err := json.Unmarshal(encoded, &roundTrip); err != nil { + t.Fatal(err) + } + payload := roundTrip["data"].(map[string]any) + if payload["parentToolCallId"] != "parent-tool-call" { + t.Fatalf("round-trip parent tool call ID = %v", payload["parentToolCallId"]) } - t.Fatal("two_source_assistant_turn_start fixture not found") } func TestWorkerCausalityExactUTF8Budget(t *testing.T) { diff --git a/java/scripts/codegen/java.test.ts b/java/scripts/codegen/java.test.ts index e0f0a74488..10d87fd03c 100644 --- a/java/scripts/codegen/java.test.ts +++ b/java/scripts/codegen/java.test.ts @@ -82,7 +82,7 @@ test("admission correlation preserves existing Java record constructors", async }, {}); const code = [...files.values()].join("\n"); assert.match(code, /public SessionSendParams\(String prompt, String displayPrompt\)/); - assert.match(code, /this\(prompt, null, displayPrompt\);/); + assert.match(code, /this\(prompt, displayPrompt, null\);/); const pending = generateRpcClass("QueuePendingItems", { type: "object", @@ -97,7 +97,7 @@ test("admission correlation preserves existing Java record constructors", async pending, /public QueuePendingItems\(String id, String messageId, String source\)/ ); - assert.match(pending, /this\(id, messageId, null, source\);/); + assert.match(pending, /this\(id, messageId, source, null\);/); assert.match(pending, /public QueuePendingItems\(String id, String messageId\)/); assert.match(pending, /this\(id, messageId, null, null\);/); @@ -114,7 +114,7 @@ test("admission correlation preserves existing Java record constructors", async }, }, "com.github.copilot.generated"); assert.match(event, /public UserMessageEventData\(String content, String messageId\)/); - assert.match(event, /this\(content, null, messageId\);/); + assert.match(event, /this\(content, messageId, null\);/); }); test("worker causality preserves existing Java event and RPC record constructors", () => { @@ -133,15 +133,15 @@ test("worker causality preserves existing Java event and RPC record constructors }, "com.github.copilot.generated"); assert.match( event, - /public UserMessageEventData\(String content, String clientCorrelationId, String messageId\)/ + /public UserMessageEventData\(String content, String messageId, String clientCorrelationId\)/ ); assert.match( event, /@com\.fasterxml\.jackson\.databind\.annotation\.JsonDeserialize\(using = com\.github\.copilot\.WorkerCausalityDeserializer\.class\)/ ); - assert.match(event, /this\(null, content, clientCorrelationId, messageId\);/); + assert.match(event, /this\(content, messageId, null, clientCorrelationId\);/); assert.match(event, /public UserMessageEventData\(String content, String messageId\)/); - assert.match(event, /this\(null, content, null, messageId\);/); + assert.match(event, /this\(content, messageId, null, null\);/); const notification = renderEventVariantClass({ typeName: "system.notification", @@ -159,7 +159,7 @@ test("worker causality preserves existing Java event and RPC record constructors notification, /public SystemNotificationEventData\(String content, Map kind\)/ ); - assert.match(notification, /this\(null, content, kind\);/); + assert.match(notification, /this\(content, kind, null\);/); const result = generateRpcClass("SessionTasksSendMessageResult", { type: "object", @@ -174,7 +174,7 @@ test("worker causality preserves existing Java event and RPC record constructors /@com\.fasterxml\.jackson\.databind\.annotation\.JsonDeserialize\(using = com\.github\.copilot\.WorkerCausalityDeserializer\.class\)/ ); assert.match(result, /public SessionTasksSendMessageResult\(Boolean sent, String error\)/); - assert.match(result, /this\(null, sent, error\);/); + assert.match(result, /this\(sent, error, null\);/); }); test("worker UUID identities remain strings without changing ordinary UUID fields", () => { diff --git a/java/scripts/codegen/java.ts b/java/scripts/codegen/java.ts index d211cb52bd..9ff5e16fc7 100644 --- a/java/scripts/codegen/java.ts +++ b/java/scripts/codegen/java.ts @@ -247,7 +247,6 @@ let currentDefinitions: Record = {}; const pendingStandaloneTypes = new Map(); const promotedNestedUnionTypes = new Set(); const promotedSealedUnionTypes = new Set(); -const SESSION_EVENT_PROMOTED_UNION_TYPES = new Set(["SystemNotification"]); const generatedSessionEventTypeNames = new Set(); // Cross-schema definitions: keyed by schema filename (e.g. "session-events.schema.json"), @@ -669,42 +668,6 @@ export function collectNestedDiscriminatedUnionTypeNames( return promotedTypes; } -function collectReferencedDiscriminatedUnionTypeNames( - schema: JSONSchema7 | null, - definitions: Record -): Set { - const promotedTypes = new Set(); - const visitedDefinitions = new Set(); - const visit = (candidate: JSONSchema7): void => { - const name = referencedDefinitionName(candidate); - if (name) { - if (visitedDefinitions.has(name)) return; - visitedDefinitions.add(name); - const resolved = definitions[name]; - if (!resolved) return; - if (isSealedStandaloneUnionCandidate(resolved, definitions)) promotedTypes.add(name); - visit(resolved); - return; - } - for (const property of Object.values(candidate.properties ?? {})) { - if (property && typeof property === "object") visit(property as JSONSchema7); - } - if (candidate.items && !Array.isArray(candidate.items)) visit(candidate.items as JSONSchema7); - if (candidate.additionalProperties && typeof candidate.additionalProperties === "object") { - visit(candidate.additionalProperties as JSONSchema7); - } - for (const branch of [ - ...(candidate.anyOf ?? []), - ...(candidate.oneOf ?? []), - ...(candidate.allOf ?? []), - ]) { - if (branch && typeof branch === "object") visit(branch as JSONSchema7); - } - }; - if (schema) visit(schema); - return promotedTypes; -} - /** * Generate a polymorphic base class and variant subclasses for a discriminated union result type. */ @@ -1243,10 +1206,52 @@ const WORKER_STRING_UUID_PROPERTIES = new Set([ "WorkerNotificationReference.deliveryId", ]); +const COMPATIBILITY_FIELD_ORDER: Readonly> = { + AssistantTurnStartEventData: ["turnId", "model", "interactionId", "parentToolCallId", "workerCausality"], + SystemNotificationEventData: ["content", "kind", "responsesReasoning", "workerCausality"], + ToolExecutionStartEventData: [ + "toolCallId", "toolName", "toolTitle", "arguments", "shellToolInfo", "model", "rte", + "mcpServerName", "mcpConfigServerName", "mcpToolName", "mcpTransport", "mcpConfigSource", + "turnId", "displayVerbatim", "toolDescription", "parentToolCallId", "fusion", "traceparent", + "tracestate", + ], + UserMessageEventData: [ + "content", "responsesReasoning", "messageId", "transformedContent", "attachments", + "supportedNativeDocumentMimeTypes", "nativeDocumentPathFallbackPaths", "source", "delivery", + "agentMode", "isAutopilotContinuation", "interactionId", "turnId", "parentAgentTaskId", + "workerCausality", "clientCorrelationId", + ], + QueuePendingItems: ["id", "messageId", "kind", "displayText", "agentMode", "source", "clientCorrelationId"], + SendMessageItem: ["prompt", "displayPrompt", "attachments", "billable", "requiredTool", "source", "clientCorrelationId"], + SessionSendParams: [ + "sessionId", "prompt", "displayPrompt", "attachments", "mode", "prepend", "billable", + "requiredTool", "source", "agentMode", "requestHeaders", "responseFormat", "traceparent", + "tracestate", "wait", "clientCorrelationId", "jsonSchema", "type", + ], + SessionTasksSendMessageResult: ["sent", "error", "workerCausality"], +}; + function isWorkerStringUuidProperty(context: string, propName: string): boolean { return WORKER_STRING_UUID_PROPERTIES.has(`${context}.${propName}`); } +function orderCompatibilityFields( + className: string, + fields: T[], + name: (field: T) => string, +): T[] { + const order = COMPATIBILITY_FIELD_ORDER[className]; + if (!order) return fields; + const indexes = new Map(order.map((fieldName, index) => [fieldName, index])); + return fields + .map((field, originalIndex) => ({ field, originalIndex })) + .sort((left, right) => + (indexes.get(name(left.field)) ?? order.length + left.originalIndex) - + (indexes.get(name(right.field)) ?? order.length + right.originalIndex) + ) + .map(({ field }) => field); +} + export function schemaTypeToJava( schema: JSONSchema7, required: boolean, @@ -1453,21 +1458,9 @@ interface EventVariant { } const LEGACY_FACTORY_EVENT_VARIANTS: EventVariant[] = [ - { - typeName: "factory.run_updated", - className: "FactoryRunUpdatedEvent", - dataSchema: null, - }, - { - typeName: "factory.run_started", - className: "FactoryRunStartedEvent", - dataSchema: null, - }, - { - typeName: "factory.run_settled", - className: "FactoryRunSettledEvent", - dataSchema: null, - }, + { typeName: "factory.run_updated", className: "FactoryRunUpdatedEvent", dataSchema: null }, + { typeName: "factory.run_started", className: "FactoryRunStartedEvent", dataSchema: null }, + { typeName: "factory.run_settled", className: "FactoryRunSettledEvent", dataSchema: null }, ]; const LEGACY_FACTORY_EVENT_FILES = [ @@ -1523,6 +1516,9 @@ async function generateSessionEvents(schemaPath: string): Promise { pendingStandaloneTypes.clear(); promotedNestedUnionTypes.clear(); promotedSealedUnionTypes.clear(); + if (currentDefinitions.SystemNotification) { + promotedNestedUnionTypes.add("SystemNotification"); + } const variants = extractEventVariants(schema); const legacyVariants = variants.some((variant) => variant.typeName === "workflow.run_updated") @@ -1530,13 +1526,6 @@ async function generateSessionEvents(schemaPath: string): Promise { (legacy) => !variants.some((variant) => variant.typeName === legacy.typeName) ) : []; - for (const variant of variants) { - for (const typeName of collectReferencedDiscriminatedUnionTypeNames(variant.dataSchema, currentDefinitions)) { - if (SESSION_EVENT_PROMOTED_UNION_TYPES.has(typeName)) { - promotedNestedUnionTypes.add(typeName); - } - } - } const packageName = "com.github.copilot.generated"; const packageDir = `sdk/src/generated/java/com/github/copilot/generated`; @@ -1820,10 +1809,11 @@ export function renderEventVariantClass(variant: EventVariant, packageName: stri jsonName: string; javaName: string; javaType: string; + typedAccessorType?: string; description?: string; } - const dataFields: FieldInfo[] = []; + let dataFields: FieldInfo[] = []; if (!hasUnionData && variant.dataSchema?.properties) { for (const [propName, propSchema] of Object.entries(variant.dataSchema.properties)) { @@ -1832,14 +1822,24 @@ export function renderEventVariantClass(variant: EventVariant, packageName: stri // Record components are always boxed (nullable by design). const result = schemaTypeToJava(prop, false, `${variant.className}Data`, propName, nestedTypes); for (const imp of result.imports) allImports.add(imp); + const preserveSystemNotificationKindAbi = + variant.className === "SystemNotificationEvent" && + propName === "kind" && + result.javaType === "SystemNotification"; dataFields.push({ jsonName: propName, javaName: toCamelCase(propName), - javaType: result.javaType, + javaType: preserveSystemNotificationKindAbi ? "Object" : result.javaType, + typedAccessorType: preserveSystemNotificationKindAbi ? result.javaType : undefined, description: prop.description, }); } } + dataFields = orderCompatibilityFields( + `${variant.className}Data`, + dataFields, + (field) => field.jsonName, + ); // Whether a data record should be emitted (always when dataSchema is present) const hasDataSchema = variant.dataSchema !== null; @@ -1925,11 +1925,22 @@ export function renderEventVariantClass(variant: EventVariant, packageName: stri if (field.jsonName === "workerCausality") { lines.push(` @com.fasterxml.jackson.databind.annotation.JsonDeserialize(using = com.github.copilot.WorkerCausalityDeserializer.class)`); } + if (field.typedAccessorType) { + lines.push(` @com.fasterxml.jackson.databind.annotation.JsonDeserialize(as = ${field.typedAccessorType}.class)`); + } lines.push(` @JsonProperty("${field.jsonName}") ${field.javaType} ${field.javaName}${comma}`); } lines.push(` ) {`); } lines.push(...renderCompatibilityConstructors(`${variant.className}Data`, dataFields, " ")); + for (const field of dataFields) { + if (!field.typedAccessorType) continue; + lines.push(""); + lines.push(` /** Returns the typed ${field.javaName} value when decoded from the wire. */`); + lines.push(` public ${field.typedAccessorType} typed${toPascalCase(field.javaName)}() {`); + lines.push(` return ${field.javaName} instanceof ${field.typedAccessorType} typed ? typed : null;`); + lines.push(` }`); + } // Render nested types inside Data record for (const [, nested] of nestedTypes) { lines.push(...renderNestedType(nested, 2, nestedTypes, allImports)); @@ -2152,7 +2163,7 @@ function schemaAllowsNull(schema: JSONSchema7): boolean { ); } -/** Preserve public record descriptors while adding optional diagnostic components. */ +/** Preserve existing Java record descriptors when optional diagnostics are added. */ function renderCompatibilityConstructors( className: string, fields: { javaName: string; javaType: string }[], @@ -2218,7 +2229,7 @@ export function generateRpcClass( const properties = Object.entries(schema.properties || {}).filter(([propName]) => !omittedProperties.has(propName)); const required = new Set(schema.required || []); - const fields = properties.flatMap(([propName, propSchema]) => { + let fields = properties.flatMap(([propName, propSchema]) => { if (typeof propSchema !== "object") return []; const prop = propSchema as JSONSchema7; // Record components are always boxed (nullable by design). @@ -2232,6 +2243,7 @@ export function generateRpcClass( includeNull: preserveRequiredNulls && required.has(propName) && schemaAllowsNull(prop), }]; }); + fields = orderCompatibilityFields(className, fields, (field) => field.propName); lines.push(`@JsonInclude(JsonInclude.Include.NON_NULL)`); lines.push(`@JsonIgnoreProperties(ignoreUnknown = true)`); diff --git a/java/sdk/src/generated/java/com/github/copilot/generated/SessionFusionResolvedEvent.java b/java/sdk/src/generated/java/com/github/copilot/generated/SessionFusionResolvedEvent.java index 311d849628..8bb0a5069b 100644 --- a/java/sdk/src/generated/java/com/github/copilot/generated/SessionFusionResolvedEvent.java +++ b/java/sdk/src/generated/java/com/github/copilot/generated/SessionFusionResolvedEvent.java @@ -69,7 +69,7 @@ public record SessionFusionResolvedEventData( @JsonProperty("phasePlan") List phasePlan, /** Concrete model selected for the primary solver phase. */ @JsonProperty("primaryModel") String primaryModel, - /** Concrete model selected for the review or judge phase, when required. */ + /** Concrete model selected for Critique review, or the legacy Cascade judge/repair model when role-specific fields are absent. */ @JsonProperty("secondaryModel") String secondaryModel, /** Concrete model selected for Cascade escalation-gate calls, when required. */ @JsonProperty("judgeModel") String judgeModel, diff --git a/java/sdk/src/generated/java/com/github/copilot/generated/SystemNotificationEvent.java b/java/sdk/src/generated/java/com/github/copilot/generated/SystemNotificationEvent.java index cd10d40e73..923e5e07a8 100644 --- a/java/sdk/src/generated/java/com/github/copilot/generated/SystemNotificationEvent.java +++ b/java/sdk/src/generated/java/com/github/copilot/generated/SystemNotificationEvent.java @@ -37,7 +37,8 @@ public record SystemNotificationEventData( /** The notification text, typically wrapped in XML tags */ @JsonProperty("content") String content, /** Structured metadata identifying what triggered this notification */ - @JsonProperty("kind") SystemNotification kind, + @com.fasterxml.jackson.databind.annotation.JsonDeserialize(as = SystemNotification.class) + @JsonProperty("kind") Object kind, /** Provider reasoning settings anchored before this model-facing message for cache-stable replay; the historical responsesReasoning name is retained for compatibility */ @JsonProperty("responsesReasoning") ResponsesReasoning responsesReasoning, /** Optional owned worker notification observations; an omitted notification event refers only to this occurrence. */ @@ -45,8 +46,13 @@ public record SystemNotificationEventData( @JsonProperty("workerCausality") WorkerCausality workerCausality ) { /** Creates a value without optional worker diagnostics. */ - public SystemNotificationEventData(String content, SystemNotification kind, ResponsesReasoning responsesReasoning) { + public SystemNotificationEventData(String content, Object kind, ResponsesReasoning responsesReasoning) { this(content, kind, responsesReasoning, null); } + + /** Returns the typed kind value when decoded from the wire. */ + public SystemNotification typedKind() { + return kind instanceof SystemNotification typed ? typed : null; + } } } diff --git a/java/sdk/src/generated/java/com/github/copilot/generated/rpc/CatalogAiSkillCandidate.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/CatalogAiSkillCandidate.java index 1051020dd5..6d8eb53f2f 100644 --- a/java/sdk/src/generated/java/com/github/copilot/generated/rpc/CatalogAiSkillCandidate.java +++ b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/CatalogAiSkillCandidate.java @@ -13,7 +13,7 @@ import javax.annotation.processing.Generated; /** - * An inert AI skill catalog result. AI skills are discovery-only and cannot be represented as installable through this surface. + * An inert AI skill catalog result. Verified Skill candidates may be installable only when the runtime reports installability and the selected session is permitted to plan installation. * * @since 1.0.0 */ @@ -40,7 +40,7 @@ public final class CatalogAiSkillCandidate extends CatalogCandidate { @JsonProperty("mediaType") private CatalogAiSkillMediaType mediaType; - /** AI skills are discovery-only and cannot be installed through this surface */ + /** Whether this AI skill candidate can be planned for verified installation in the selected session. */ @JsonProperty("installability") private CatalogAiSkillInstallability installability; diff --git a/java/sdk/src/generated/java/com/github/copilot/generated/rpc/CatalogAiSkillInstallability.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/CatalogAiSkillInstallability.java index 47fe03a202..67df7b2023 100644 --- a/java/sdk/src/generated/java/com/github/copilot/generated/rpc/CatalogAiSkillInstallability.java +++ b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/CatalogAiSkillInstallability.java @@ -10,7 +10,7 @@ import javax.annotation.processing.Generated; /** - * Typed non-installable state for an AI skill candidate + * Typed installability state for an AI skill candidate * * @since 1.0.0 */ diff --git a/java/sdk/src/generated/java/com/github/copilot/generated/rpc/CatalogSearchPage.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/CatalogSearchPage.java index da5a2f13fa..6e47166edc 100644 --- a/java/sdk/src/generated/java/com/github/copilot/generated/rpc/CatalogSearchPage.java +++ b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/CatalogSearchPage.java @@ -13,7 +13,7 @@ import javax.annotation.processing.Generated; /** - * An explicit numbered-page request. The SDK treats the token as opaque; only the runtime decodes it and changes its targetPage. Authority validation binds navigation to the original search. No snapshot stability or token TTL is promised. + * An explicit numbered-page request. SDK consumers treat the token as opaque. For bound search, the runtime unwraps an expiring owner-bound reference to the private authority token; only the runtime changes the authority token's targetPage. Legacy unbound navigation keeps its authority-issued token semantics. No snapshot stability is promised. * * @since 1.0.0 */ @@ -21,7 +21,7 @@ @JsonInclude(JsonInclude.Include.NON_NULL) @JsonIgnoreProperties(ignoreUnknown = true) public record CatalogSearchPage( - /** Opaque authority-issued pagination token from an earlier response. Never decode, modify or log it in an SDK consumer. */ + /** Opaque pagination token from an earlier response, owner-bound when session-bound search was requested. Never decode, modify or log it in an SDK consumer. Expired or foreign bound references require a fresh bound search, not a legacy retry. */ @JsonProperty("token") String token, /** Requested one-based page. Must not exceed either the token's signed pageCount or the navigation window ceil(1000 / pageSize). Repeat the search without page to discover newly available pages beyond that signed pageCount. */ @JsonProperty("number") Long number diff --git a/java/sdk/src/generated/java/com/github/copilot/generated/rpc/CatalogSearchPagination.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/CatalogSearchPagination.java index 445ed51373..14b7543d0f 100644 --- a/java/sdk/src/generated/java/com/github/copilot/generated/rpc/CatalogSearchPagination.java +++ b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/CatalogSearchPagination.java @@ -21,7 +21,7 @@ @JsonInclude(JsonInclude.Include.NON_NULL) @JsonIgnoreProperties(ignoreUnknown = true) public record CatalogSearchPagination( - /** Opaque authority-issued pagination token. Only the runtime decodes it or changes targetPage; SDK consumers must not decode, modify or log it. It has no runtime-created expiry or cache. */ + /** Opaque pagination token. Session-bound search returns an expiring runtime-owned reference retaining the exact private authority token, original search and authority. Legacy unbound search returns the authority token unchanged, without a runtime-created expiry. Only the runtime unwraps tokens or changes targetPage; SDK consumers must not decode, modify or log them. */ @JsonProperty("token") String token, /** One-based page returned by the authority. */ @JsonProperty("currentPage") Long currentPage, diff --git a/java/sdk/src/generated/java/com/github/copilot/generated/rpc/PermissionModeSource.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/PermissionModeSource.java index 52f3e6cea3..c0f24f1157 100644 --- a/java/sdk/src/generated/java/com/github/copilot/generated/rpc/PermissionModeSource.java +++ b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/PermissionModeSource.java @@ -11,7 +11,7 @@ import javax.annotation.processing.Generated; /** - * Optional source for permission-mode telemetry. Defaults to `rpc` when omitted for SDK callers. + * Optional source for permission-mode telemetry. `organization_targeting` is reserved for startup selection after the authenticated account matches an organization targeting policy; SDK callers default to `rpc` and cannot claim targeting provenance. * * @apiNote This type is experimental and may change in a future version. * diff --git a/java/sdk/src/generated/java/com/github/copilot/generated/rpc/SandboxConfigSource.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/SandboxConfigSource.java index 73760b08a2..2e570b83d3 100644 --- a/java/sdk/src/generated/java/com/github/copilot/generated/rpc/SandboxConfigSource.java +++ b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/SandboxConfigSource.java @@ -10,7 +10,7 @@ import javax.annotation.processing.Generated; /** - * Origin of the sandbox choice supplied by an internal client. + * Origin of the sandbox choice supplied by the host. Settings-derived origins let managed policy floor the host preference; do not tag explicit session overrides as settings-derived. * * @since 1.0.0 */ diff --git a/java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionOpenOptions.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionOpenOptions.java index 3deb651e25..dbb3080cdb 100644 --- a/java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionOpenOptions.java +++ b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionOpenOptions.java @@ -101,7 +101,7 @@ public record SessionOpenOptions( @JsonProperty("shellProcessFlags") List shellProcessFlags, /** Resolved sandbox configuration. */ @JsonProperty("sandboxConfig") SandboxConfig sandboxConfig, - /** Origin of the sandbox choice. The runtime uses this only for internal telemetry provenance; managed policy is derived independently. */ + /** Origin of the sandbox choice. Settings-derived origins (never_configured, user_enabled, user_disabled, repository_policy) let managed policy floor a host preference; explicit below-floor changes remain policy conflicts unless a session opt-out is authorized. Also used for telemetry provenance. */ @JsonProperty("sandboxConfigSource") SandboxConfigSource sandboxConfigSource, /** Whether interactive shell sessions are logged. */ @JsonProperty("logInteractiveShells") Boolean logInteractiveShells, diff --git a/java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionOptionsUpdateParams.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionOptionsUpdateParams.java index b3c3c14d48..3a30a13527 100644 --- a/java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionOptionsUpdateParams.java +++ b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionOptionsUpdateParams.java @@ -74,7 +74,7 @@ public record SessionOptionsUpdateParams( @JsonProperty("shellProcessFlags") List shellProcessFlags, /** Resolved sandbox configuration. */ @JsonProperty("sandboxConfig") SandboxConfig sandboxConfig, - /** Origin of the sandbox choice. The runtime uses this only for internal telemetry provenance; managed policy is derived independently. */ + /** Origin of the sandbox choice. Settings-derived origins (never_configured, user_enabled, user_disabled, repository_policy) let managed policy floor a host preference; explicit below-floor changes remain policy conflicts unless a session opt-out is authorized. Also used for telemetry provenance. */ @JsonProperty("sandboxConfigSource") SandboxConfigSource sandboxConfigSource, /** Whether interactive shell sessions are logged. */ @JsonProperty("logInteractiveShells") Boolean logInteractiveShells, diff --git a/java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionPermissionsSetModeParams.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionPermissionsSetModeParams.java index 712d460243..57abc3257f 100644 --- a/java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionPermissionsSetModeParams.java +++ b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionPermissionsSetModeParams.java @@ -30,7 +30,7 @@ public record SessionPermissionsSetModeParams( @JsonProperty("mode") PermissionMode mode, /** Optional judge model id for assisted mode. When omitted, the session resolves the provider default: `gpt-5.5` for CAPI sessions and the active session model for BYOK sessions. */ @JsonProperty("assistedApprovalModel") String assistedApprovalModel, - /** Optional source for permission-mode telemetry. Defaults to `rpc` when omitted for SDK callers. */ + /** Optional source for permission-mode telemetry. `organization_targeting` is reserved for startup selection after the authenticated account matches an organization targeting policy; SDK callers default to `rpc` and cannot claim targeting provenance. */ @JsonProperty("source") PermissionModeSource source ) { } diff --git a/java/sdk/src/test/java/com/github/copilot/WorkerCausalityTest.java b/java/sdk/src/test/java/com/github/copilot/WorkerCausalityTest.java index 90d5163b7e..2b5a5097e9 100644 --- a/java/sdk/src/test/java/com/github/copilot/WorkerCausalityTest.java +++ b/java/sdk/src/test/java/com/github/copilot/WorkerCausalityTest.java @@ -75,6 +75,7 @@ void canonicalWorkflowCompletionDecodesTypedFields() throws Exception { SystemNotificationEvent notification = assertInstanceOf(SystemNotificationEvent.class, parsed); SystemNotificationWorkflowCompleted completion = assertInstanceOf(SystemNotificationWorkflowCompleted.class, notification.getData().kind()); + assertEquals(completion, notification.getData().typedKind()); assertEquals("fix-ci", completion.getWorkflowName()); assertEquals("run-1", completion.getRunId()); assertEquals(SystemNotificationWorkflowCompletedStatus.COMPLETED, completion.getStatus()); @@ -91,4 +92,42 @@ void invalidUnicodeScalarIdentitiesAreUnavailable() throws Exception { assertEquals(null, result.workerCausality(), test.path("name").asText()); } } + + @Test + void uppercaseWorkerUuidIdentitiesRoundTripExactly() throws Exception { + JsonNode source = MAPPER.readTree(""" + { + "sent": true, + "workerCausality": { + "version": 1, + "observationProvenance": "native", + "sources": [{ + "input": { + "queueItemId": "ABCDEFAB-CDEF-4ABC-8ABC-ABCDEFABCDEF", + "agentId": "worker", + "sender": { + "sessionId": "session-uppercase", + "eventId": "FEDCBAFE-DCBA-4FED-8FED-FEDCBAFEDCBA", + "eventType": "tool.execution_start", + "provenance": "native" + } + }, + "admissions": [{ + "kind": "queued_input", + "messageId": "message-uppercase", + "ahpTurnId": "AAAABBBB-CCCC-4DDD-8EEE-FFFFAAAABBBB" + }], + "captureComplete": true, + "notification": { + "deliveryId": "BBBBCCCC-DDDD-4EEE-8FFF-AAAABBBBCCCC", + "mode": "queued" + } + }], + "captureComplete": true + } + } + """); + JsonNode observed = MAPPER.readTree(decode(source, false)); + assertEquals(source.get("workerCausality"), observed.get("workerCausality")); + } } diff --git a/nodejs/src/generated/rpc.ts b/nodejs/src/generated/rpc.ts index 22d6fb5d8a..2fb795374e 100644 --- a/nodejs/src/generated/rpc.ts +++ b/nodejs/src/generated/rpc.ts @@ -798,7 +798,7 @@ export type CatalogAiSkillCandidateKind = /** An AI skill. */ "ai-skill"; /** @experimental */ export type CatalogAiSkillMediaType = /** An AI skill card. */ "application/ai-skill"; /** - * Typed non-installable state for an AI skill candidate + * Typed installability state for an AI skill candidate * * This interface was referenced by `_RpcSchemaRoot`'s JSON-Schema * via the `definition` "CatalogAiSkillInstallability". @@ -813,7 +813,7 @@ export type CatalogAiSkillInstallability = | "materialisation-unavailable" /** Policy refuses Skill installation for the selected session or authority. */ | "policy-forbids" - /** AI skills are discovery-only on this surface. */ + /** Compatibility value for discovery-only callers that did not negotiate Skill installation. */ | "not-installable-kind"; /** * Where a candidate's card came from. Exactly one of a URL or embedded data: the union has no variant carrying both, and no variant carrying neither, so the rule holds structurally rather than by validation. @@ -3949,7 +3949,7 @@ export type PermissionLocationType = /** The permission location is persisted at the working directory. */ | "dir"; /** - * Optional source for permission-mode telemetry. Defaults to `rpc` when omitted for SDK callers. + * Optional source for permission-mode telemetry. `organization_targeting` is reserved for startup selection after the authenticated account matches an organization targeting policy; SDK callers default to `rpc` and cannot claim targeting provenance. * * This interface was referenced by `_RpcSchemaRoot`'s JSON-Schema * via the `definition` "PermissionModeSource". @@ -3964,7 +3964,7 @@ export type PermissionModeSource = | "autopilot_confirmation" /** The mode was set at startup by the `defaultPermissionMode` user setting. */ | "user_setting" - /** Historical compatibility value for runtimes that selected Assisted mode through organization targeting. Current runtimes do not produce this source. */ + /** The mode was set at startup because the authenticated account matched an organization targeting policy. */ | "organization_targeting" /** The mode was set through an RPC caller. */ | "rpc"; @@ -4312,13 +4312,12 @@ export type ResponseFormat = { type: "json_schema"; }; /** - * Origin of the sandbox choice supplied by an internal client. + * Origin of the sandbox choice supplied by the host. Settings-derived origins let managed policy floor the host preference; do not tag explicit session overrides as settings-derived. * * This interface was referenced by `_RpcSchemaRoot`'s JSON-Schema * via the `definition` "SandboxConfigSource". */ /** @experimental */ -/** @internal */ export type SandboxConfigSource = /** The client applied the default because no sandbox preference was configured. */ | "never_configured" @@ -8208,7 +8207,7 @@ export interface CatalogTrustSnapshotMalformed { provenance: CatalogTrustProvenance; } /** - * An inert AI skill catalog result. AI skills are discovery-only and cannot be represented as installable through this surface. + * An inert AI skill catalog result. Verified Skill candidates may be installable only when the runtime reports installability and the selected session is permitted to plan installation. * * This interface was referenced by `_RpcSchemaRoot`'s JSON-Schema * via the `definition` "CatalogAiSkillCandidate". @@ -8572,7 +8571,7 @@ export interface CatalogPolicyRejectedError { message: string; } /** - * An explicit numbered-page request. The SDK treats the token as opaque; only the runtime decodes it and changes its targetPage. Authority validation binds navigation to the original search. No snapshot stability or token TTL is promised. + * An explicit numbered-page request. SDK consumers treat the token as opaque. For bound search, the runtime unwraps an expiring owner-bound reference to the private authority token; only the runtime changes the authority token's targetPage. Legacy unbound navigation keeps its authority-issued token semantics. No snapshot stability is promised. * * This interface was referenced by `_RpcSchemaRoot`'s JSON-Schema * via the `definition` "CatalogSearchPage". @@ -8580,7 +8579,7 @@ export interface CatalogPolicyRejectedError { /** @experimental */ export interface CatalogSearchPage { /** - * Opaque authority-issued pagination token from an earlier response. Never decode, modify or log it in an SDK consumer. + * Opaque pagination token from an earlier response, owner-bound when session-bound search was requested. Never decode, modify or log it in an SDK consumer. Expired or foreign bound references require a fresh bound search, not a legacy retry. */ token: string; /** @@ -8597,7 +8596,7 @@ export interface CatalogSearchPage { /** @experimental */ export interface CatalogSearchPagination { /** - * Opaque authority-issued pagination token. Only the runtime decodes it or changes targetPage; SDK consumers must not decode, modify or log it. It has no runtime-created expiry or cache. + * Opaque pagination token. Session-bound search returns an expiring runtime-owned reference retaining the exact private authority token, original search and authority. Legacy unbound search returns the authority token unchanged, without a runtime-created expiry. Only the runtime unwraps tokens or changes targetPage; SDK consumers must not decode, modify or log them. */ token: string; /** @@ -22032,11 +22031,6 @@ export interface SessionOpenOptions { */ shellProcessFlags?: string[]; sandboxConfig?: SandboxConfig; - /** - * Origin of the sandbox choice. The runtime uses this only for internal telemetry provenance; managed policy is derived independently. - * - * @internal - */ sandboxConfigSource?: SandboxConfigSource; /** * Whether interactive shell sessions are logged. @@ -23563,11 +23557,6 @@ export interface SessionUpdateOptionsParams { */ shellProcessFlags?: string[]; sandboxConfig?: SandboxConfig; - /** - * Origin of the sandbox choice. The runtime uses this only for internal telemetry provenance; managed policy is derived independently. - * - * @internal - */ sandboxConfigSource?: SandboxConfigSource; /** * Whether interactive shell sessions are logged. @@ -30521,7 +30510,7 @@ export function createSessionRpc(connection: MessageConnection, sessionId: strin prepareLogin: async (params: SessionMcpOauthPrepareLoginRequest): Promise => connection.sendRequest("session.mcp.oauth.prepareLogin", { sessionId, ...params }), /** - * Starts OAuth authentication for a remote MCP server. + * Starts OAuth authentication for a remote MCP server. Owned servers require the original one-use prepareLogin handle and exact installation ID; manual servers retain the existing direct login behaviour. * * @param params Remote MCP server name and optional overrides controlling reauthentication, OAuth client display name, callback success-page copy, and static OAuth client selection. * diff --git a/nodejs/src/generated/session-events.ts b/nodejs/src/generated/session-events.ts index 7d999a00ce..3aa53dc01b 100644 --- a/nodejs/src/generated/session-events.ts +++ b/nodejs/src/generated/session-events.ts @@ -4137,7 +4137,7 @@ export interface FusionResolvedData { ruleName?: string; scores?: FusionScores; /** - * Concrete model selected for the review or judge phase, when required. + * Concrete model selected for Critique review, or the legacy Cascade judge/repair model when role-specific fields are absent. */ secondaryModel: string | null; /** diff --git a/nodejs/src/workerCausality.ts b/nodejs/src/workerCausality.ts index 159025cc06..fe5490d251 100644 --- a/nodejs/src/workerCausality.ts +++ b/nodejs/src/workerCausality.ts @@ -155,12 +155,11 @@ export function withWorkerCausalityEvents(result: T): T { if (!("events" in result) || !Array.isArray(result.events)) return result; const originalEvents = result.events; const events = originalEvents.map((event) => { + if (!object(event) || !object(event.data)) return event; if ( - !object(event) || - !["user.message", "system.notification", "assistant.turn_start"].includes( - String(event.type) - ) || - !object(event.data) + event.type !== "user.message" && + event.type !== "system.notification" && + event.type !== "assistant.turn_start" ) { return event; } diff --git a/nodejs/test/worker-causality.test.ts b/nodejs/test/worker-causality.test.ts index 57a18b8819..7b57341ed2 100644 --- a/nodejs/test/worker-causality.test.ts +++ b/nodejs/test/worker-causality.test.ts @@ -110,19 +110,6 @@ function readers() { } describe("worker causality public readers", () => { - it("preserves unrelated event payloads that reuse the diagnostic field name", () => { - const result = { - events: [ - { - type: "session.start", - data: { workerCausality: { extensionPayload: true } }, - }, - ], - }; - expect(withWorkerCausalityEvents(result)).toBe(result); - expect(result.events[0].data.workerCausality).toEqual({ extensionPayload: true }); - }); - it.each([ ["absent events", { method: "session.eventLog.read", state: { phase: "covered" } }], ["non-array events", { events: { state: "covered" } }], @@ -130,6 +117,30 @@ describe("worker causality public readers", () => { expect(withWorkerCausalityEvents(result)).toBe(result); }); + it("preserves unrelated workerCausality product data on unsupported event types", async () => { + const reader = readers(); + const event = { + type: "session.start", + data: { + sessionId: "codec-session", + version: 1, + producer: "extension", + copilotVersion: "test", + startTime: "2026-01-02T03:04:05.000Z", + workerCausality: { + owner: "extension-product-data", + }, + }, + id: "10000000-0000-4000-8000-000000000001", + timestamp: "2026-01-02T03:04:05.000Z", + parentId: null, + }; + const expected = JSON.stringify(event); + for (const history of await reader.history(event)) { + expect(JSON.stringify(history.events[0]), history.name).toBe(expected); + } + }); + it.each(corpus.valid)( "$name preserves exact observations and historical product bytes", async (test) => { diff --git a/python/copilot/generated/rpc.py b/python/copilot/generated/rpc.py index ed7730ef36..eed155c3c5 100644 --- a/python/copilot/generated/rpc.py +++ b/python/copilot/generated/rpc.py @@ -1381,10 +1381,18 @@ class CatalogTrustTier(Enum): T1 = "T1" T2 = "T2" -class Installability(Enum): - """Typed non-installable state for an AI skill candidate""" +# Experimental: this type is part of an experimental API and may change or be removed. +class CatalogAISkillInstallability(Enum): + """Whether this AI skill candidate can be planned for verified installation in the selected + session. + Typed installability state for an AI skill candidate + """ + FEATURE_DISABLED = "feature-disabled" + INSTALLABLE = "installable" + MATERIALISATION_UNAVAILABLE = "materialisation-unavailable" NOT_INSTALLABLE_KIND = "not-installable-kind" + POLICY_FORBIDS = "policy-forbids" # Experimental: this type is part of an experimental API and may change or be removed. class CatalogAISkillCandidateKind(Enum): @@ -1406,16 +1414,6 @@ class CatalogCandidateSourceKind(Enum): EMBEDDED = "embedded" URL = "url" -# Experimental: this type is part of an experimental API and may change or be removed. -class CatalogAISkillInstallability(Enum): - """Typed non-installable state for an AI skill candidate""" - - FEATURE_DISABLED = "feature-disabled" - INSTALLABLE = "installable" - MATERIALISATION_UNAVAILABLE = "materialisation-unavailable" - NOT_INSTALLABLE_KIND = "not-installable-kind" - POLICY_FORBIDS = "policy-forbids" - class CatalogAuthenticationRequiredErrorKind(Enum): AUTHENTICATION_REQUIRED = "authentication-required" @@ -1430,16 +1428,22 @@ class CatalogAuthenticationRequiredReason(Enum): CREDENTIAL_REJECTED = "credential-rejected" NO_CREDENTIAL = "no-credential" -class CatalogCandidateInstallability(Enum): +class CatalogInstallability(Enum): """Whether this MCP server can be planned for installation, and if policy prevents it. Whether an MCP server candidate can be planned for installation - Typed non-installable state for an AI skill candidate + Whether this AI skill candidate can be planned for verified installation in the selected + session. + + Typed installability state for an AI skill candidate """ + FEATURE_DISABLED = "feature-disabled" INSTALLABLE = "installable" + MATERIALISATION_UNAVAILABLE = "materialisation-unavailable" NOT_INSTALLABLE_KIND = "not-installable-kind" NOT_INSTALLABLE_POLICY = "not-installable-policy" + POLICY_FORBIDS = "policy-forbids" # Experimental: this type is part of an experimental API and may change or be removed. class MCPServerCardEmbeddedKind(Enum): @@ -1552,7 +1556,7 @@ class CatalogMalformedCardReason(Enum): UNSUPPORTED_MEDIA_TYPE = "unsupported-media-type" # Experimental: this type is part of an experimental API and may change or be removed. -class CatalogMCPServerInstallabilityEnum(Enum): +class CatalogMCPServerInstallability(Enum): """Whether this MCP server can be planned for installation, and if policy prevents it. Whether an MCP server candidate can be planned for installation @@ -1645,9 +1649,11 @@ class MCPPlanPolicySource(Enum): # Experimental: this type is part of an experimental API and may change or be removed. @dataclass class CatalogSearchPage: - """An explicit numbered-page request. The SDK treats the token as opaque; only the runtime - decodes it and changes its targetPage. Authority validation binds navigation to the - original search. No snapshot stability or token TTL is promised. + """An explicit numbered-page request. SDK consumers treat the token as opaque. For bound + search, the runtime unwraps an expiring owner-bound reference to the private authority + token; only the runtime changes the authority token's targetPage. Legacy unbound + navigation keeps its authority-issued token semantics. No snapshot stability is + promised. Numbered navigation using metadata from an earlier response. Requires catalog-search-pagination and the same query, kinds and effective limit. Omit for a fresh @@ -1659,8 +1665,9 @@ class CatalogSearchPage: available pages beyond that signed pageCount. """ token: str - """Opaque authority-issued pagination token from an earlier response. Never decode, modify - or log it in an SDK consumer. + """Opaque pagination token from an earlier response, owner-bound when session-bound search + was requested. Never decode, modify or log it in an SDK consumer. Expired or foreign + bound references require a fresh bound search, not a legacy retry. """ @staticmethod @@ -10647,12 +10654,15 @@ def to_dict(self) -> dict: return result # Experimental: this type is part of an experimental API and may change or be removed. -# Internal: this type is an internal SDK API and is not part of the public surface. -class _SandboxConfigSource(Enum): - """Origin of the sandbox choice supplied by an internal client. +class SandboxConfigSource(Enum): + """Origin of the sandbox choice supplied by the host. Settings-derived origins let managed + policy floor the host preference; do not tag explicit session overrides as + settings-derived. - Origin of the sandbox choice. The runtime uses this only for internal telemetry - provenance; managed policy is derived independently. + Origin of the sandbox choice. Settings-derived origins (never_configured, user_enabled, + user_disabled, repository_policy) let managed policy floor a host preference; explicit + below-floor changes remain policy conflicts unless a session opt-out is authorized. Also + used for telemetry provenance. """ NEVER_CONFIGURED = "never_configured" REPOSITORY_POLICY = "repository_policy" @@ -18099,9 +18109,11 @@ class CatalogSearchPagination: """Page size bound to the search, equal to the effective request limit.""" token: str - """Opaque authority-issued pagination token. Only the runtime decodes it or changes - targetPage; SDK consumers must not decode, modify or log it. It has no runtime-created - expiry or cache. + """Opaque pagination token. Session-bound search returns an expiring runtime-owned reference + retaining the exact private authority token, original search and authority. Legacy + unbound search returns the authority token unchanged, without a runtime-created expiry. + Only the runtime unwraps tokens or changes targetPage; SDK consumers must not decode, + modify or log them. """ total_count: int """Backend-reported count for this response, not the number of returned candidates. Its @@ -30418,8 +30430,9 @@ def to_dict(self) -> dict: # Experimental: this type is part of an experimental API and may change or be removed. @dataclass class CatalogAISkillCandidate: - """An inert AI skill catalog result. AI skills are discovery-only and cannot be represented - as installable through this surface. + """An inert AI skill catalog result. Verified Skill candidates may be installable only when + the runtime reports installability and the selected session is permitted to plan + installation. """ display_name: str """Display name taken verbatim from the card. Inert untrusted text.""" @@ -30432,9 +30445,10 @@ class CatalogAISkillCandidate: handle_expires_at: str """ISO 8601 timestamp after which the handle is stale and will be rejected.""" - installability: Installability - """AI skills are discovery-only and cannot be installed through this surface""" - + installability: CatalogAISkillInstallability + """Whether this AI skill candidate can be planned for verified installation in the selected + session. + """ kind: CatalogAISkillCandidateKind """Discriminator: this candidate describes an AI skill""" @@ -30466,7 +30480,7 @@ def from_dict(obj: Any) -> 'CatalogAISkillCandidate': display_name = from_str(obj.get("displayName")) handle = from_str(obj.get("handle")) handle_expires_at = from_str(obj.get("handleExpiresAt")) - installability = Installability(obj.get("installability")) + installability = CatalogAISkillInstallability(obj.get("installability")) kind = CatalogAISkillCandidateKind(obj.get("kind")) media_type = CatalogAISkillMediaType(obj.get("mediaType")) provenance = CatalogAISkillCandidateProvenance.from_dict(obj.get("provenance")) @@ -30481,7 +30495,7 @@ def to_dict(self) -> dict: result["displayName"] = from_str(self.display_name) result["handle"] = from_str(self.handle) result["handleExpiresAt"] = from_str(self.handle_expires_at) - result["installability"] = to_enum(Installability, self.installability) + result["installability"] = to_enum(CatalogAISkillInstallability, self.installability) result["kind"] = to_enum(CatalogAISkillCandidateKind, self.kind) result["mediaType"] = to_enum(CatalogAISkillMediaType, self.media_type) result["provenance"] = to_class(CatalogAISkillCandidateProvenance, self.provenance) @@ -30512,7 +30526,7 @@ class CatalogMCPServerCandidate: handle_expires_at: str """ISO 8601 timestamp after which the handle is stale and will be rejected.""" - installability: CatalogMCPServerInstallabilityEnum + installability: CatalogMCPServerInstallability """Whether this MCP server can be planned for installation, and if policy prevents it.""" kind: CatalogMCPServerCandidateKind @@ -30546,7 +30560,7 @@ def from_dict(obj: Any) -> 'CatalogMCPServerCandidate': display_name = from_str(obj.get("displayName")) handle = from_str(obj.get("handle")) handle_expires_at = from_str(obj.get("handleExpiresAt")) - installability = CatalogMCPServerInstallabilityEnum(obj.get("installability")) + installability = CatalogMCPServerInstallability(obj.get("installability")) kind = CatalogMCPServerCandidateKind(obj.get("kind")) media_type = MCPServerCardMediaType(obj.get("mediaType")) provenance = CatalogMCPServerCandidateProvenance.from_dict(obj.get("provenance")) @@ -30561,7 +30575,7 @@ def to_dict(self) -> dict: result["displayName"] = from_str(self.display_name) result["handle"] = from_str(self.handle) result["handleExpiresAt"] = from_str(self.handle_expires_at) - result["installability"] = to_enum(CatalogMCPServerInstallabilityEnum, self.installability) + result["installability"] = to_enum(CatalogMCPServerInstallability, self.installability) result["kind"] = to_enum(CatalogMCPServerCandidateKind, self.kind) result["mediaType"] = to_enum(MCPServerCardMediaType, self.media_type) result["provenance"] = to_class(CatalogMCPServerCandidateProvenance, self.provenance) @@ -37569,8 +37583,9 @@ class CatalogCandidate: must never be treated as an instruction, and the handle is the only way to refer to the candidate in a later operation. - An inert AI skill catalog result. AI skills are discovery-only and cannot be represented - as installable through this surface. + An inert AI skill catalog result. Verified Skill candidates may be installable only when + the runtime reports installability and the selected session is permitted to plan + installation. An inert Agent Plugin catalog result. Its canonical catalog identity, declared version, repository source claim, and explicit compatibility tags are safe to correlate, while its @@ -37618,10 +37633,11 @@ class CatalogCandidate: handle_expires_at: str | None = None """ISO 8601 timestamp after which the handle is stale and will be rejected.""" - installability: CatalogCandidateInstallability | None = None + installability: CatalogInstallability | None = None """Whether this MCP server can be planned for installation, and if policy prevents it. - AI skills are discovery-only and cannot be installed through this surface + Whether this AI skill candidate can be planned for verified installation in the selected + session. """ publisher: str | None = None """Publisher taken verbatim from the card. Inert untrusted text.""" @@ -37660,7 +37676,7 @@ def from_dict(obj: Any) -> 'CatalogCandidate': description = from_union([from_str, from_none], obj.get("description")) handle = from_union([from_str, from_none], obj.get("handle")) handle_expires_at = from_union([from_str, from_none], obj.get("handleExpiresAt")) - installability = from_union([CatalogCandidateInstallability, from_none], obj.get("installability")) + installability = from_union([CatalogInstallability, from_none], obj.get("installability")) publisher = from_union([from_str, from_none], obj.get("publisher")) trust = from_union([_load_CatalogTrustSnapshot, from_none], obj.get("trust")) compatibility_tags = from_union([lambda x: from_list(CatalogAgentPluginCompatibilityTag, x), from_none], obj.get("compatibilityTags")) @@ -37682,7 +37698,7 @@ def to_dict(self) -> dict: if self.handle_expires_at is not None: result["handleExpiresAt"] = from_union([from_str, from_none], self.handle_expires_at) if self.installability is not None: - result["installability"] = from_union([lambda x: to_enum(CatalogCandidateInstallability, x), from_none], self.installability) + result["installability"] = from_union([lambda x: to_enum(CatalogInstallability, x), from_none], self.installability) if self.publisher is not None: result["publisher"] = from_union([from_str, from_none], self.publisher) if self.trust is not None: @@ -39156,10 +39172,11 @@ class SessionUpdateOptionsParams: sandbox_config: SandboxConfig | None = None """Resolved sandbox configuration.""" - # Internal: this field is an internal SDK API and is not part of the public surface. - sandbox_config_source: _SandboxConfigSource | None = None - """Origin of the sandbox choice. The runtime uses this only for internal telemetry - provenance; managed policy is derived independently. + sandbox_config_source: SandboxConfigSource | None = None + """Origin of the sandbox choice. Settings-derived origins (never_configured, user_enabled, + user_disabled, repository_policy) let managed policy floor a host preference; explicit + below-floor changes remain policy conflicts unless a session opt-out is authorized. Also + used for telemetry provenance. """ session_capabilities: list[SessionCapability] | None = None """Replaces the session's capability set with the given list. Use to enable or disable @@ -39254,7 +39271,7 @@ def from_dict(obj: Any) -> 'SessionUpdateOptionsParams': reasoning_summary = from_union([ReasoningSummary, from_none], obj.get("reasoningSummary")) running_in_interactive_mode = from_union([from_bool, from_none], obj.get("runningInInteractiveMode")) sandbox_config = from_union([SandboxConfig.from_dict, from_none], obj.get("sandboxConfig")) - sandbox_config_source = from_union([_SandboxConfigSource, from_none], obj.get("sandboxConfigSource")) + sandbox_config_source = from_union([SandboxConfigSource, from_none], obj.get("sandboxConfigSource")) session_capabilities = from_union([lambda x: from_list(SessionCapability, x), from_none], obj.get("sessionCapabilities")) session_limits = from_union([SessionLimitsConfig.from_dict, from_none], obj.get("sessionLimits")) shell = from_union([ShellOptions.from_dict, from_none], obj.get("shell")) @@ -39365,7 +39382,7 @@ def to_dict(self) -> dict: if self.sandbox_config is not None: result["sandboxConfig"] = from_union([lambda x: to_class(SandboxConfig, x), from_none], self.sandbox_config) if self.sandbox_config_source is not None: - result["sandboxConfigSource"] = from_union([lambda x: to_enum(_SandboxConfigSource, x), from_none], self.sandbox_config_source) + result["sandboxConfigSource"] = from_union([lambda x: to_enum(SandboxConfigSource, x), from_none], self.sandbox_config_source) if self.session_capabilities is not None: result["sessionCapabilities"] = from_union([lambda x: from_list(lambda x: to_enum(SessionCapability, x), x), from_none], self.session_capabilities) if self.session_limits is not None: @@ -42429,8 +42446,9 @@ def to_dict(self) -> dict: # Experimental: this type is part of an experimental API and may change or be removed. class PermissionModeSource(Enum): - """Optional source for permission-mode telemetry. Defaults to `rpc` when omitted for SDK - callers. + """Optional source for permission-mode telemetry. `organization_targeting` is reserved for + startup selection after the authenticated account matches an organization targeting + policy; SDK callers default to `rpc` and cannot claim targeting provenance. """ AUTOPILOT_CONFIRMATION = "autopilot_confirmation" CLI_FLAG = "cli_flag" @@ -42488,8 +42506,9 @@ class PermissionsSetModeRequest: sessions. """ source: PermissionModeSource | None = None - """Optional source for permission-mode telemetry. Defaults to `rpc` when omitted for SDK - callers. + """Optional source for permission-mode telemetry. `organization_targeting` is reserved for + startup selection after the authenticated account matches an organization targeting + policy; SDK callers default to `rpc` and cannot claim targeting provenance. """ @staticmethod @@ -43093,10 +43112,11 @@ class SessionOpenOptions: sandbox_config: SandboxConfig | None = None """Resolved sandbox configuration.""" - # Internal: this field is an internal SDK API and is not part of the public surface. - sandbox_config_source: _SandboxConfigSource | None = None - """Origin of the sandbox choice. The runtime uses this only for internal telemetry - provenance; managed policy is derived independently. + sandbox_config_source: SandboxConfigSource | None = None + """Origin of the sandbox choice. Settings-derived origins (never_configured, user_enabled, + user_disabled, repository_policy) let managed policy floor a host preference; explicit + below-floor changes remain policy conflicts unless a session opt-out is authorized. Also + used for telemetry provenance. """ session_capabilities: list[SessionCapability] | None = None """Capabilities enabled for this session.""" @@ -43199,7 +43219,7 @@ def from_dict(obj: Any) -> 'SessionOpenOptions': remote_steerable = from_union([from_bool, from_none], obj.get("remoteSteerable")) running_in_interactive_mode = from_union([from_bool, from_none], obj.get("runningInInteractiveMode")) sandbox_config = from_union([SandboxConfig.from_dict, from_none], obj.get("sandboxConfig")) - sandbox_config_source = from_union([_SandboxConfigSource, from_none], obj.get("sandboxConfigSource")) + sandbox_config_source = from_union([SandboxConfigSource, from_none], obj.get("sandboxConfigSource")) session_capabilities = from_union([lambda x: from_list(SessionCapability, x), from_none], obj.get("sessionCapabilities")) session_id = from_union([from_str, from_none], obj.get("sessionId")) session_limits = from_union([SessionLimitsConfig.from_dict, from_none], obj.get("sessionLimits")) @@ -43341,7 +43361,7 @@ def to_dict(self) -> dict: if self.sandbox_config is not None: result["sandboxConfig"] = from_union([lambda x: to_class(SandboxConfig, x), from_none], self.sandbox_config) if self.sandbox_config_source is not None: - result["sandboxConfigSource"] = from_union([lambda x: to_enum(_SandboxConfigSource, x), from_none], self.sandbox_config_source) + result["sandboxConfigSource"] = from_union([lambda x: to_enum(SandboxConfigSource, x), from_none], self.sandbox_config_source) if self.session_capabilities is not None: result["sessionCapabilities"] = from_union([lambda x: from_list(lambda x: to_enum(SessionCapability, x), x), from_none], self.session_capabilities) if self.session_id is not None: @@ -44574,7 +44594,7 @@ class RPC: catalog_mcp_server_candidate: CatalogMCPServerCandidate catalog_mcp_server_candidate_kind: CatalogMCPServerCandidateKind catalog_mcp_server_candidate_provenance: CatalogMCPServerCandidateProvenance - catalog_mcp_server_installability: CatalogMCPServerInstallabilityEnum + catalog_mcp_server_installability: CatalogMCPServerInstallability catalog_media_type: CatalogMediaType catalog_negotiated_contract: CatalogNegotiatedContract catalog_negotiation_refused_error: CatalogNegotiationRefusedError @@ -45365,7 +45385,7 @@ class RPC: response_format: ResponseFormat sandbox_config: SandboxConfig sandbox_config_auth: SandboxConfigAuth - sandbox_config_source: _SandboxConfigSource + sandbox_config_source: SandboxConfigSource sandbox_config_user_policy: SandboxConfigUserPolicy sandbox_config_user_policy_experimental: SandboxConfigUserPolicyExperimental sandbox_config_user_policy_experimental_seatbelt: SandboxConfigUserPolicyExperimentalSeatbelt @@ -46007,7 +46027,7 @@ def from_dict(obj: Any) -> 'RPC': catalog_mcp_server_candidate = CatalogMCPServerCandidate.from_dict(obj.get("CatalogMcpServerCandidate")) catalog_mcp_server_candidate_kind = CatalogMCPServerCandidateKind(obj.get("CatalogMcpServerCandidateKind")) catalog_mcp_server_candidate_provenance = CatalogMCPServerCandidateProvenance.from_dict(obj.get("CatalogMcpServerCandidateProvenance")) - catalog_mcp_server_installability = CatalogMCPServerInstallabilityEnum(obj.get("CatalogMcpServerInstallability")) + catalog_mcp_server_installability = CatalogMCPServerInstallability(obj.get("CatalogMcpServerInstallability")) catalog_media_type = CatalogMediaType(obj.get("CatalogMediaType")) catalog_negotiated_contract = CatalogNegotiatedContract.from_dict(obj.get("CatalogNegotiatedContract")) catalog_negotiation_refused_error = CatalogNegotiationRefusedError.from_dict(obj.get("CatalogNegotiationRefusedError")) @@ -46798,7 +46818,7 @@ def from_dict(obj: Any) -> 'RPC': response_format = ResponseFormat.from_dict(obj.get("ResponseFormat")) sandbox_config = SandboxConfig.from_dict(obj.get("SandboxConfig")) sandbox_config_auth = SandboxConfigAuth.from_dict(obj.get("SandboxConfigAuth")) - sandbox_config_source = _SandboxConfigSource(obj.get("SandboxConfigSource")) + sandbox_config_source = SandboxConfigSource(obj.get("SandboxConfigSource")) sandbox_config_user_policy = SandboxConfigUserPolicy.from_dict(obj.get("SandboxConfigUserPolicy")) sandbox_config_user_policy_experimental = SandboxConfigUserPolicyExperimental.from_dict(obj.get("SandboxConfigUserPolicyExperimental")) sandbox_config_user_policy_experimental_seatbelt = SandboxConfigUserPolicyExperimentalSeatbelt.from_dict(obj.get("SandboxConfigUserPolicyExperimentalSeatbelt")) @@ -47440,7 +47460,7 @@ def to_dict(self) -> dict: result["CatalogMcpServerCandidate"] = to_class(CatalogMCPServerCandidate, self.catalog_mcp_server_candidate) result["CatalogMcpServerCandidateKind"] = to_enum(CatalogMCPServerCandidateKind, self.catalog_mcp_server_candidate_kind) result["CatalogMcpServerCandidateProvenance"] = to_class(CatalogMCPServerCandidateProvenance, self.catalog_mcp_server_candidate_provenance) - result["CatalogMcpServerInstallability"] = to_enum(CatalogMCPServerInstallabilityEnum, self.catalog_mcp_server_installability) + result["CatalogMcpServerInstallability"] = to_enum(CatalogMCPServerInstallability, self.catalog_mcp_server_installability) result["CatalogMediaType"] = to_enum(CatalogMediaType, self.catalog_media_type) result["CatalogNegotiatedContract"] = to_class(CatalogNegotiatedContract, self.catalog_negotiated_contract) result["CatalogNegotiationRefusedError"] = to_class(CatalogNegotiationRefusedError, self.catalog_negotiation_refused_error) @@ -48231,7 +48251,7 @@ def to_dict(self) -> dict: result["ResponseFormat"] = to_class(ResponseFormat, self.response_format) result["SandboxConfig"] = to_class(SandboxConfig, self.sandbox_config) result["SandboxConfigAuth"] = to_class(SandboxConfigAuth, self.sandbox_config_auth) - result["SandboxConfigSource"] = to_enum(_SandboxConfigSource, self.sandbox_config_source) + result["SandboxConfigSource"] = to_enum(SandboxConfigSource, self.sandbox_config_source) result["SandboxConfigUserPolicy"] = to_class(SandboxConfigUserPolicy, self.sandbox_config_user_policy) result["SandboxConfigUserPolicyExperimental"] = to_class(SandboxConfigUserPolicyExperimental, self.sandbox_config_user_policy_experimental) result["SandboxConfigUserPolicyExperimentalSeatbelt"] = to_class(SandboxConfigUserPolicyExperimentalSeatbelt, self.sandbox_config_user_policy_experimental_seatbelt) @@ -49145,7 +49165,6 @@ def _load_TaskInfo(obj: Any) -> "TaskInfo": CanvasJsonSchema = Any CardDigestValue = str CatalogCapabilityId = str -CatalogMcpServerInstallability = CatalogMCPServerInstallabilityEnum CatalogResourceIdentity = str CatalogResourceVersion = str ClientMetadata = dict @@ -50695,7 +50714,7 @@ async def prepare_login(self, params: SessionMCPOauthPrepareLoginRequest, *, tim return SessionMCPOauthPrepareLoginResult.from_dict(await self._client.request("session.mcp.oauth.prepareLogin", params_dict, **_timeout_kwargs(timeout))) async def login(self, params: MCPOauthLoginRequest, *, timeout: float | None = None) -> MCPOauthLoginResult: - "Starts OAuth authentication for a remote MCP server.\n\nArgs:\n params: Remote MCP server name and optional overrides controlling reauthentication, OAuth client display name, callback success-page copy, and static OAuth client selection.\n\nReturns:\n OAuth authorization URL the caller should open, or empty when cached tokens already authenticated the server." + "Starts OAuth authentication for a remote MCP server. Owned servers require the original one-use prepareLogin handle and exact installation ID; manual servers retain the existing direct login behaviour.\n\nArgs:\n params: Remote MCP server name and optional overrides controlling reauthentication, OAuth client display name, callback success-page copy, and static OAuth client selection.\n\nReturns:\n OAuth authorization URL the caller should open, or empty when cached tokens already authenticated the server." params_dict: dict[str, Any] = {k: v for k, v in params.to_dict().items() if v is not None} params_dict["sessionId"] = self._session_id return MCPOauthLoginResult.from_dict(await self._client.request("session.mcp.oauth.login", params_dict, **_timeout_kwargs(timeout))) @@ -52554,6 +52573,11 @@ async def handle_installations_confirm(params: dict) -> dict | None: return result.to_dict() client.set_request_handler("installations.confirm", handle_installations_confirm) +# Backward-compatible public aliases retained across typed-review generation. +CatalogCandidateInstallability = CatalogInstallability +CatalogMCPServerInstallabilityEnum = CatalogMCPServerInstallability +CatalogMcpServerInstallability = CatalogMCPServerInstallability + __all__ = [ "APIKeyAuthInfo", "APIKeyAuthInfoType", @@ -52710,12 +52734,14 @@ async def handle_installations_confirm(params: dict) -> dict | None: "CatalogHandleRejectedErrorKind", "CatalogHandleRejectionReason", "CatalogHandleType", + "CatalogInstallability", "CatalogInvalidRequestError", "CatalogInvalidRequestErrorKind", "CatalogInvalidRequestField", "CatalogMCPServerCandidate", "CatalogMCPServerCandidateKind", "CatalogMCPServerCandidateProvenance", + "CatalogMCPServerInstallability", "CatalogMCPServerInstallabilityEnum", "CatalogMalformedCardError", "CatalogMalformedCardErrorKind", @@ -53010,7 +53036,6 @@ async def handle_installations_confirm(params: dict) -> dict | None: "Host", "HostType", "InstallMethod", - "Installability", "InstallationCatalogueIdentity", "InstallationConfirmationRequest", "InstallationConfirmationResponse", @@ -53687,6 +53712,7 @@ async def handle_installations_confirm(params: dict) -> dict | None: "SandboxApi", "SandboxConfig", "SandboxConfigAuth", + "SandboxConfigSource", "SandboxConfigUserPolicy", "SandboxConfigUserPolicyExperimental", "SandboxConfigUserPolicyExperimentalSeatbelt", diff --git a/python/test_worker_causality.py b/python/test_worker_causality.py index 2897a4c812..e9b119991c 100644 --- a/python/test_worker_causality.py +++ b/python/test_worker_causality.py @@ -68,3 +68,41 @@ def test_invalid_unicode_scalar_identities_are_unavailable(case): {"sent": True, "workerCausality": json.loads(case["json"])} ) assert result.to_dict() == {"sent": True} + + +def test_uppercase_worker_uuid_identities_round_trip_exactly(): + value = { + "sent": True, + "workerCausality": { + "version": 1, + "observationProvenance": "native", + "sources": [ + { + "input": { + "queueItemId": "ABCDEFAB-CDEF-4ABC-8ABC-ABCDEFABCDEF", + "agentId": "worker", + "sender": { + "sessionId": "session-uppercase", + "eventId": "FEDCBAFE-DCBA-4FED-8FED-FEDCBAFEDCBA", + "eventType": "tool.execution_start", + "provenance": "native", + }, + }, + "admissions": [ + { + "kind": "queued_input", + "messageId": "message-uppercase", + "ahpTurnId": "AAAABBBB-CCCC-4DDD-8EEE-FFFFAAAABBBB", + } + ], + "captureComplete": True, + "notification": { + "deliveryId": "BBBBCCCC-DDDD-4EEE-8FFF-AAAABBBBCCCC", + "mode": "queued", + }, + } + ], + "captureComplete": True, + }, + } + assert TasksSendMessageResult.from_dict(value).to_dict() == value diff --git a/rust/src/generated/api_types.rs b/rust/src/generated/api_types.rs index 55e9be22b6..a117578931 100644 --- a/rust/src/generated/api_types.rs +++ b/rust/src/generated/api_types.rs @@ -4011,7 +4011,7 @@ pub struct CatalogCandidateSourceEmbedded { pub kind: CatalogCandidateSourceEmbeddedKind, } -/// An inert AI skill catalog result. AI skills are discovery-only and cannot be represented as installable through this surface. +/// An inert AI skill catalog result. Verified Skill candidates may be installable only when the runtime reports installability and the selected session is permitted to plan installation. /// ///
/// @@ -4031,8 +4031,7 @@ pub struct CatalogAiSkillCandidate { pub handle: String, /// ISO 8601 timestamp after which the handle is stale and will be rejected. pub handle_expires_at: String, - /// AI skills are discovery-only and cannot be installed through this surface - #[serde(deserialize_with = "CatalogAiSkillCandidate::deserialize_installability")] + /// Whether this AI skill candidate can be planned for verified installation in the selected session. pub installability: CatalogAiSkillInstallability, /// Discriminator: this candidate describes an AI skill #[serde(deserialize_with = "CatalogAiSkillCandidate::deserialize_kind")] @@ -4053,24 +4052,6 @@ pub struct CatalogAiSkillCandidate { } impl CatalogAiSkillCandidate { - fn deserialize_installability<'de, D>( - deserializer: D, - ) -> Result - where - D: serde::Deserializer<'de>, - { - let value = String::deserialize(deserializer)?; - if value != "not-installable-kind" { - return Err(serde::de::Error::unknown_variant( - &value, - &["not-installable-kind"], - )); - } - ::deserialize( - serde::de::value::StringDeserializer::::new(value), - ) - } - fn deserialize_kind<'de, D>(deserializer: D) -> Result where D: serde::Deserializer<'de>, @@ -4398,7 +4379,7 @@ pub struct CatalogPolicyRejectedError { pub source: McpPlanPolicySource, } -/// An explicit numbered-page request. The SDK treats the token as opaque; only the runtime decodes it and changes its targetPage. Authority validation binds navigation to the original search. No snapshot stability or token TTL is promised. +/// An explicit numbered-page request. SDK consumers treat the token as opaque. For bound search, the runtime unwraps an expiring owner-bound reference to the private authority token; only the runtime changes the authority token's targetPage. Legacy unbound navigation keeps its authority-issued token semantics. No snapshot stability is promised. /// ///
/// @@ -4411,7 +4392,7 @@ pub struct CatalogPolicyRejectedError { pub struct CatalogSearchPage { /// Requested one-based page. Must not exceed either the token's signed pageCount or the navigation window ceil(1000 / pageSize). Repeat the search without page to discover newly available pages beyond that signed pageCount. pub number: i32, - /// Opaque authority-issued pagination token from an earlier response. Never decode, modify or log it in an SDK consumer. + /// Opaque pagination token from an earlier response, owner-bound when session-bound search was requested. Never decode, modify or log it in an SDK consumer. Expired or foreign bound references require a fresh bound search, not a legacy retry. pub token: String, } @@ -4436,7 +4417,7 @@ pub struct CatalogSearchPagination { pub page_count: i64, /// Page size bound to the search, equal to the effective request limit. pub page_size: i32, - /// Opaque authority-issued pagination token. Only the runtime decodes it or changes targetPage; SDK consumers must not decode, modify or log it. It has no runtime-created expiry or cache. + /// Opaque pagination token. Session-bound search returns an expiring runtime-owned reference retaining the exact private authority token, original search and authority. Legacy unbound search returns the authority token unchanged, without a runtime-created expiry. Only the runtime unwraps tokens or changes targetPage; SDK consumers must not decode, modify or log them. pub token: String, /// Backend-reported count for this response, not the number of returned candidates. Its relationship to the full query result set is unknown. pub total_count: i64, @@ -15514,7 +15495,7 @@ pub struct PermissionsSetModeRequest { pub assisted_approval_model: Option, /// Permission mode to apply pub mode: PermissionMode, - /// Optional source for permission-mode telemetry. Defaults to `rpc` when omitted for SDK callers. + /// Optional source for permission-mode telemetry. `organization_targeting` is reserved for startup selection after the authenticated account matches an organization targeting policy; SDK callers default to `rpc` and cannot claim targeting provenance. #[serde(skip_serializing_if = "Option::is_none")] pub source: Option, } @@ -20410,10 +20391,9 @@ pub struct SessionOpenOptions { /// Resolved sandbox configuration. #[serde(skip_serializing_if = "Option::is_none")] pub sandbox_config: Option, - /// Origin of the sandbox choice. The runtime uses this only for internal telemetry provenance; managed policy is derived independently. - #[doc(hidden)] + /// Origin of the sandbox choice. Settings-derived origins (never_configured, user_enabled, user_disabled, repository_policy) let managed policy floor a host preference; explicit below-floor changes remain policy conflicts unless a session opt-out is authorized. Also used for telemetry provenance. #[serde(skip_serializing_if = "Option::is_none")] - pub(crate) sandbox_config_source: Option, + pub sandbox_config_source: Option, /// Capabilities enabled for this session. #[serde(skip_serializing_if = "Option::is_none")] pub session_capabilities: Option>, @@ -21913,10 +21893,9 @@ pub struct SessionUpdateOptionsParams { /// Resolved sandbox configuration. #[serde(skip_serializing_if = "Option::is_none")] pub sandbox_config: Option, - /// Origin of the sandbox choice. The runtime uses this only for internal telemetry provenance; managed policy is derived independently. - #[doc(hidden)] + /// Origin of the sandbox choice. Settings-derived origins (never_configured, user_enabled, user_disabled, repository_policy) let managed policy floor a host preference; explicit below-floor changes remain policy conflicts unless a session opt-out is authorized. Also used for telemetry provenance. #[serde(skip_serializing_if = "Option::is_none")] - pub(crate) sandbox_config_source: Option, + pub sandbox_config_source: Option, /// Replaces the session's capability set with the given list. Use to enable or disable capabilities mid-session (e.g., remove `memory` for reproducible scripted runs). Omit the field to leave the existing capability set unchanged. #[serde(skip_serializing_if = "Option::is_none")] pub session_capabilities: Option>, @@ -34969,7 +34948,7 @@ pub enum CatalogAgentPluginMediaType { Unknown, } -/// Typed non-installable state for an AI skill candidate +/// Typed installability state for an AI skill candidate /// ///
/// @@ -34991,7 +34970,7 @@ pub enum CatalogAiSkillInstallability { /// Policy refuses Skill installation for the selected session or authority. #[serde(rename = "policy-forbids")] PolicyForbids, - /// AI skills are discovery-only on this surface. + /// Compatibility value for discovery-only callers that did not negotiate Skill installation. #[serde(rename = "not-installable-kind")] NotInstallableKind, /// Unknown variant for forward compatibility. @@ -40200,7 +40179,7 @@ pub enum PermissionLocationType { Unknown, } -/// Optional source for permission-mode telemetry. Defaults to `rpc` when omitted for SDK callers. +/// Optional source for permission-mode telemetry. `organization_targeting` is reserved for startup selection after the authenticated account matches an organization targeting policy; SDK callers default to `rpc` and cannot claim targeting provenance. /// ///
/// @@ -40222,7 +40201,7 @@ pub enum PermissionModeSource { /// The mode was set at startup by the `defaultPermissionMode` user setting. #[serde(rename = "user_setting")] UserSetting, - /// Historical compatibility value for runtimes that selected Assisted mode through organization targeting. Current runtimes do not produce this source. + /// The mode was set at startup because the authenticated account matched an organization targeting policy. #[serde(rename = "organization_targeting")] OrganizationTargeting, /// The mode was set through an RPC caller. @@ -40816,7 +40795,7 @@ pub enum ResponseFormatType { JsonSchema, } -/// Origin of the sandbox choice supplied by an internal client. +/// Origin of the sandbox choice supplied by the host. Settings-derived origins let managed policy floor the host preference; do not tag explicit session overrides as settings-derived. /// ///
/// diff --git a/rust/src/generated/rpc.rs b/rust/src/generated/rpc.rs index 2de7e85afc..2c915334c1 100644 --- a/rust/src/generated/rpc.rs +++ b/rust/src/generated/rpc.rs @@ -8191,7 +8191,7 @@ impl<'a> SessionRpcMcpOauth<'a> { Ok(serde_json::from_value(_value)?) } - /// Starts OAuth authentication for a remote MCP server. + /// Starts OAuth authentication for a remote MCP server. Owned servers require the original one-use prepareLogin handle and exact installation ID; manual servers retain the existing direct login behaviour. /// /// Wire method: `session.mcp.oauth.login`. /// @@ -8221,7 +8221,7 @@ impl<'a> SessionRpcMcpOauth<'a> { Ok(serde_json::from_value(_value)?) } - /// Starts OAuth authentication for a remote MCP server. + /// Starts OAuth authentication for a remote MCP server. Owned servers require the original one-use prepareLogin handle and exact installation ID; manual servers retain the existing direct login behaviour. /// /// Wire method: `session.mcp.oauth.login`. /// diff --git a/rust/src/generated/session_events.rs b/rust/src/generated/session_events.rs index 2cb5308506..fafbd5bc65 100644 --- a/rust/src/generated/session_events.rs +++ b/rust/src/generated/session_events.rs @@ -2593,7 +2593,7 @@ pub struct SessionFusionResolvedData { /// Validated capability scores used to select the route. #[serde(skip_serializing_if = "Option::is_none")] pub scores: Option, - /// Concrete model selected for the review or judge phase, when required. + /// Concrete model selected for Critique review, or the legacy Cascade judge/repair model when role-specific fields are absent. pub secondary_model: Option, /// Synthetic HydraFusion model selected for the session. pub synthetic_model: String, diff --git a/rust/tests/api_types_test.rs b/rust/tests/api_types_test.rs index e95ee479b3..04406dc7e0 100644 --- a/rust/tests/api_types_test.rs +++ b/rust/tests/api_types_test.rs @@ -380,11 +380,11 @@ fn permission_event_exposes_managed_approval_required() { fn queue_pending_item_metadata_uses_camel_case_wire_names() { let item = QueuePendingItems { agent_mode: SendAgentMode::Interactive, + client_correlation_id: None, display_text: "second message".to_string(), id: "batch-1".to_string(), kind: QueuePendingItemsKind::Message, message_id: Some("message-2".to_string()), - client_correlation_id: None, source: Some("api".to_string()), }; diff --git a/test/worker-causality.json b/test/worker-causality.json index 4e679bf2f8..ecd61c3694 100644 --- a/test/worker-causality.json +++ b/test/worker-causality.json @@ -268,7 +268,6 @@ "data": { "turnId": "1", "interactionId": "test-interaction", - "parentToolCallId": "parent-tool-call", "workerCausality": { "version": 1, "observationProvenance": "native", @@ -467,43 +466,6 @@ } } }, - { - "name": "uppercase_worker_uuid_identities", - "result": { - "sent": true, - "workerCausality": { - "version": 1, - "observationProvenance": "native", - "sources": [ - { - "input": { - "queueItemId": "ABCDEFAB-CDEF-4ABC-8ABC-ABCDEFABCDEF", - "agentId": "worker", - "sender": { - "sessionId": "session-uppercase", - "eventId": "FEDCBAFE-DCBA-4FED-8FED-FEDCBAFEDCBA", - "eventType": "tool.execution_start", - "provenance": "native" - } - }, - "admissions": [ - { - "kind": "queued_input", - "messageId": "message-uppercase", - "ahpTurnId": "AAAABBBB-CCCC-4DDD-8EEE-FFFFAAAABBBB" - } - ], - "captureComplete": true, - "notification": { - "deliveryId": "BBBBCCCC-DDDD-4EEE-8FFF-AAAABBBBCCCC", - "mode": "queued" - } - } - ], - "captureComplete": true - } - } - }, { "name": "agent_id_256_multibyte", "result": { @@ -1478,6 +1440,244 @@ }, "accepted": true }, + { + "name": "exact_4096_u2028", + "value": { + "version": 1, + "observationProvenance": "native", + "sources": [ + { + "input": { + "queueItemId": "46fc3a43-7f19-4b0a-9e32-d5ef4dc25552", + "agentId": "worker" + }, + "admissions": [ + { + "kind": "queued_input", + "messageId": "mmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmm", + "event": { + "sessionId": "s", + "eventId": "33333333-3333-4333-8333-333333333333", + "agentId": "worker", + "eventType": "user.message", + "provenance": "native" + } + }, + { + "kind": "queued_input", + "messageId": "mmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmm", + "event": { + "sessionId": "s", + "eventId": "33333333-3333-4333-8333-333333333333", + "agentId": "worker", + "eventType": "user.message", + "provenance": "native" + } + }, + { + "kind": "queued_input", + "messageId": "mmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmm", + "event": { + "sessionId": "s", + "eventId": "33333333-3333-4333-8333-333333333333", + "agentId": "worker", + "eventType": "user.message", + "provenance": "native" + } + }, + { + "kind": "queued_input", + "messageId": "mmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmm", + "event": { + "sessionId": "s", + "eventId": "33333333-3333-4333-8333-333333333333", + "agentId": "worker", + "eventType": "user.message", + "provenance": "native" + } + }, + { + "kind": "queued_input", + "messageId": "mmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmm", + "event": { + "sessionId": "s", + "eventId": "33333333-3333-4333-8333-333333333333", + "agentId": "worker", + "eventType": "user.message", + "provenance": "native" + } + }, + { + "kind": "queued_input", + "messageId": "mmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmm", + "event": { + "sessionId": "s", + "eventId": "33333333-3333-4333-8333-333333333333", + "agentId": "worker", + "eventType": "user.message", + "provenance": "native" + } + }, + { + "kind": "queued_input", + "messageId": "mmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmm", + "event": { + "sessionId": "s", + "eventId": "33333333-3333-4333-8333-333333333333", + "agentId": "worker", + "eventType": "user.message", + "provenance": "native" + } + }, + { + "kind": "queued_input", + "messageId": "mmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmm", + "event": { + "sessionId": "s", + "eventId": "33333333-3333-4333-8333-333333333333", + "agentId": "worker", + "eventType": "user.message", + "provenance": "native" + } + }, + { + "kind": "queued_input", + "messageId": "
xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx", + "event": { + "sessionId": "s", + "eventId": "33333333-3333-4333-8333-333333333333", + "agentId": "worker", + "eventType": "user.message", + "provenance": "native" + } + } + ], + "captureComplete": true + } + ], + "captureComplete": true + }, + "accepted": true + }, + { + "name": "exact_4096_u2029", + "value": { + "version": 1, + "observationProvenance": "native", + "sources": [ + { + "input": { + "queueItemId": "46fc3a43-7f19-4b0a-9e32-d5ef4dc25552", + "agentId": "worker" + }, + "admissions": [ + { + "kind": "queued_input", + "messageId": "mmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmm", + "event": { + "sessionId": "s", + "eventId": "33333333-3333-4333-8333-333333333333", + "agentId": "worker", + "eventType": "user.message", + "provenance": "native" + } + }, + { + "kind": "queued_input", + "messageId": "mmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmm", + "event": { + "sessionId": "s", + "eventId": "33333333-3333-4333-8333-333333333333", + "agentId": "worker", + "eventType": "user.message", + "provenance": "native" + } + }, + { + "kind": "queued_input", + "messageId": "mmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmm", + "event": { + "sessionId": "s", + "eventId": "33333333-3333-4333-8333-333333333333", + "agentId": "worker", + "eventType": "user.message", + "provenance": "native" + } + }, + { + "kind": "queued_input", + "messageId": "mmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmm", + "event": { + "sessionId": "s", + "eventId": "33333333-3333-4333-8333-333333333333", + "agentId": "worker", + "eventType": "user.message", + "provenance": "native" + } + }, + { + "kind": "queued_input", + "messageId": "mmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmm", + "event": { + "sessionId": "s", + "eventId": "33333333-3333-4333-8333-333333333333", + "agentId": "worker", + "eventType": "user.message", + "provenance": "native" + } + }, + { + "kind": "queued_input", + "messageId": "mmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmm", + "event": { + "sessionId": "s", + "eventId": "33333333-3333-4333-8333-333333333333", + "agentId": "worker", + "eventType": "user.message", + "provenance": "native" + } + }, + { + "kind": "queued_input", + "messageId": "mmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmm", + "event": { + "sessionId": "s", + "eventId": "33333333-3333-4333-8333-333333333333", + "agentId": "worker", + "eventType": "user.message", + "provenance": "native" + } + }, + { + "kind": "queued_input", + "messageId": "mmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmm", + "event": { + "sessionId": "s", + "eventId": "33333333-3333-4333-8333-333333333333", + "agentId": "worker", + "eventType": "user.message", + "provenance": "native" + } + }, + { + "kind": "queued_input", + "messageId": "
xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx", + "event": { + "sessionId": "s", + "eventId": "33333333-3333-4333-8333-333333333333", + "agentId": "worker", + "eventType": "user.message", + "provenance": "native" + } + } + ], + "captureComplete": true + } + ], + "captureComplete": true + }, + "accepted": true + }, { "name": "4097_ascii", "value": { From 364413ffcadf429b321bf56453fb3977c7448ec0 Mon Sep 17 00:00:00 2001 From: Michael Dinh Date: Mon, 28 Sep 2026 18:45:09 -0400 Subject: [PATCH 25/25] test: restore abort recovery replay variants Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> --- .../abort/should_abort_during_active_streaming.yaml | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/test/snapshots/abort/should_abort_during_active_streaming.yaml b/test/snapshots/abort/should_abort_during_active_streaming.yaml index 07ce7a6949..8556fec349 100644 --- a/test/snapshots/abort/should_abort_during_active_streaming.yaml +++ b/test/snapshots/abort/should_abort_during_active_streaming.yaml @@ -24,6 +24,17 @@ conversations: During this same period, the theoretical foundations were being laid by pioneers like Alan Turing and John von Neumann. Turing's concept of a universal machine and von Neumann's architecture for stored-program computers would shape the entire future of the field. + - role: user + content: Say 'abort_recovery_ok'. + - role: assistant + content: abort_recovery_ok + - messages: + - role: system + content: ${system} + - role: user + content: Say 'abort_recovery_ok'. + - role: assistant + content: abort_recovery_ok - messages: - role: system content: ${system}