From 51dc4350a526c84ca2c35f1eddd556371cc02f43 Mon Sep 17 00:00:00 2001 From: Bryan Helmkamp Date: Thu, 9 Apr 2026 10:40:21 -0400 Subject: [PATCH] feat(api): flatten EventEnvelope wire JSON (schema v2) Wire EventEnvelope now inlines the RunEvent payload fields alongside seq at the top level of the JSON object. The internal Rust EventEnvelope { seq, payload } stays structurally unchanged; only the API/SSE serialization layer flattens for clients. - OpenAPI spec: add stage_id, parallel_group_id, parallel_branch_id, tool_call_id, actor to RunEvent; model EventEnvelope as allOf(seq, RunEvent); introduce ActorRef/ActorKind schemas. - fabro-server: rewrite api_event_envelope_from_store to merge seq into the payload JSON value before returning the generated flat type; remove the now-unused nested ApiRunEvent conversion helper. - fabro-cli server_client: add wire_event_envelope_into_store helper that turns flat wire JSON back into fabro_store::EventEnvelope { seq, payload } for internal consumers. - Regenerate progenitor Rust types and typescript-axios client. - Update demo stubs, SSE tests, CLI test helpers, and insta snapshots to expect the flattened shape and the new stage_id field. Incidental: the typescript regeneration also picked up prior-merged spec fields (ApiQuestion stage/timeout/context, upload manifest batches, web-settings) that were stale in the TS client. Co-Authored-By: Claude Opus 4.6 (1M context) --- docs/api-reference/fabro-api.yaml | 79 ++++++++++++++++--- lib/crates/fabro-cli/src/server_client.rs | 40 +++++++++- lib/crates/fabro-cli/tests/it/cmd/attach.rs | 3 + lib/crates/fabro-cli/tests/it/cmd/run.rs | 38 +++++---- lib/crates/fabro-cli/tests/it/cmd/support.rs | 32 +++++++- .../fabro-cli/tests/it/scenario/smoke.rs | 42 +++++----- lib/crates/fabro-server/src/demo/mod.rs | 38 ++++----- lib/crates/fabro-server/src/server.rs | 40 ++++++---- .../tests/it/scenario/lifecycle.rs | 8 +- .../tests/it/scenario/run_completion.rs | 2 +- .../fabro-server/tests/it/scenario/sse.rs | 2 +- .../src/.openapi-generator/FILES | 2 + .../src/api/run-internals-api.ts | 9 ++- .../fabro-api-client/src/models/actor-kind.ts | 30 +++++++ .../fabro-api-client/src/models/actor-ref.ts | 36 +++++++++ .../src/models/api-question.ts | 12 +++ .../src/models/artifact-batch-upload-entry.ts | 3 +- .../models/artifact-batch-upload-manifest.ts | 3 +- .../src/models/event-envelope.ts | 15 ++-- .../fabro-api-client/src/models/index.ts | 2 + .../fabro-api-client/src/models/run-event.ts | 20 +++++ .../src/models/web-settings.ts | 4 + 22 files changed, 346 insertions(+), 114 deletions(-) create mode 100644 lib/packages/fabro-api-client/src/models/actor-kind.ts create mode 100644 lib/packages/fabro-api-client/src/models/actor-ref.ts diff --git a/docs/api-reference/fabro-api.yaml b/docs/api-reference/fabro-api.yaml index 76cbc8ffe..d12a17ce9 100644 --- a/docs/api-reference/fabro-api.yaml +++ b/docs/api-reference/fabro-api.yaml @@ -2620,6 +2620,32 @@ components: items: $ref: "#/components/schemas/ErrorResponseEntry" + ActorKind: + description: High-level category of an event actor. + type: string + enum: + - user + - agent + - system + + ActorRef: + description: > + Optional primary actor associated with a run event. Present on control + actions and durable agent output where a stable user or agent identity + matters; omitted on routine runtime lifecycle events. + type: object + required: + - kind + properties: + kind: + $ref: "#/components/schemas/ActorKind" + id: + type: string + description: Stable actor identifier when available. + display: + type: string + description: Display-friendly label for the actor. + RunEvent: description: > Internal RunEvent-compatible JSON payload. The server validates this @@ -2644,12 +2670,39 @@ components: node_label: type: string nullable: true + stage_id: + type: string + nullable: true + description: Stage execution identity, formatted as "{node_id}@{visit}". + parallel_group_id: + type: string + nullable: true + description: > + Durable identity of one execution of a parallel node, formatted as + "{node_id}@{visit}". + parallel_branch_id: + type: string + nullable: true + description: > + Durable identity of one branch within a parallel execution, + formatted as "{parallel_group_id}:{index}". session_id: type: string nullable: true parent_session_id: type: string nullable: true + tool_call_id: + type: string + nullable: true + description: > + Stable identifier for a tool call, present on agent.tool.* events + and other durable events that directly describe the same tool + call. + actor: + allOf: + - $ref: "#/components/schemas/ActorRef" + nullable: true event: type: string description: Event type discriminator. @@ -2660,18 +2713,20 @@ components: additionalProperties: true EventEnvelope: - description: Stored event envelope with assigned sequence number. - type: object - required: - - seq - - payload - properties: - seq: - type: integer - description: Assigned event sequence number. - example: 42 - payload: - $ref: "#/components/schemas/RunEvent" + description: > + Stored event envelope with assigned sequence number. On the wire the + envelope is flattened: seq sits alongside the RunEvent payload fields + at the top level of the JSON object. + allOf: + - type: object + required: + - seq + properties: + seq: + type: integer + description: Assigned event sequence number. + example: 42 + - $ref: "#/components/schemas/RunEvent" PaginatedEventList: description: Paginated list of stored run events. diff --git a/lib/crates/fabro-cli/src/server_client.rs b/lib/crates/fabro-cli/src/server_client.rs index 410331208..b5fc56bc0 100644 --- a/lib/crates/fabro-cli/src/server_client.rs +++ b/lib/crates/fabro-cli/src/server_client.rs @@ -72,13 +72,47 @@ impl RunAttachEventStream { fn buffer_sse_events(&mut self, finalize: bool) -> Result<()> { for payload in sse::drain_sse_payloads(&mut self.pending_bytes, finalize) { - let event: types::EventEnvelope = serde_json::from_str(&payload)?; - self.buffered_events.push_back(convert_type(event)?); + let value: serde_json::Value = serde_json::from_str(&payload)?; + self.buffered_events + .push_back(wire_event_envelope_into_store(value)?); } Ok(()) } } +/// Converts a flattened wire `EventEnvelope` JSON value (seq alongside the +/// RunEvent payload fields at the top level) into the internal +/// `fabro_store::EventEnvelope` which keeps seq and payload separate. +fn wire_event_envelope_into_store(value: serde_json::Value) -> Result { + let serde_json::Value::Object(mut obj) = value else { + bail!("expected wire EventEnvelope JSON object"); + }; + let seq_value = obj + .remove("seq") + .context("wire EventEnvelope missing seq field")?; + let seq: u32 = match seq_value { + serde_json::Value::Number(n) => n + .as_u64() + .and_then(|v| u32::try_from(v).ok()) + .context("wire EventEnvelope seq is out of u32 range")?, + _ => bail!("wire EventEnvelope seq is not a number"), + }; + let run_id_str = obj + .get("run_id") + .and_then(|v| v.as_str()) + .context("wire EventEnvelope missing run_id")?; + let run_id: RunId = run_id_str.parse().context("invalid run_id in wire event")?; + let payload = fabro_store::EventPayload::new(serde_json::Value::Object(obj), &run_id) + .map_err(|err| anyhow!("wire EventEnvelope payload failed store validation: {err}"))?; + Ok(EventEnvelope { seq, payload }) +} + +fn wire_event_envelope_from_generated(value: types::EventEnvelope) -> Result { + let value = + serde_json::to_value(value).context("failed to serialize generated EventEnvelope")?; + wire_event_envelope_into_store(value) +} + pub(crate) use fabro_store::RunProjection; pub(crate) async fn connect_server(storage_dir: &Path) -> Result { @@ -407,7 +441,7 @@ impl ServerStoreClient { let page_events = parsed .data .into_iter() - .map(convert_type) + .map(wire_event_envelope_from_generated) .collect::>>()?; let next_page_since_seq = page_events.last().map(|event| event.seq.saturating_add(1)); all_events.extend(page_events); diff --git a/lib/crates/fabro-cli/tests/it/cmd/attach.rs b/lib/crates/fabro-cli/tests/it/cmd/attach.rs index 4233ba8d3..cedc4882d 100644 --- a/lib/crates/fabro-cli/tests/it/cmd/attach.rs +++ b/lib/crates/fabro-cli/tests/it/cmd/attach.rs @@ -647,6 +647,7 @@ fn attach_json_errors_without_prompting_for_human_input() { "max_attempts": 1 }, "run_id": "[ULID]", + "stage_id": "start@1", "ts": "[TIMESTAMP]" }, { @@ -674,6 +675,7 @@ fn attach_json_errors_without_prompting_for_human_input() { "status": "success" }, "run_id": "[ULID]", + "stage_id": "start@1", "ts": "[TIMESTAMP]" }, { @@ -738,6 +740,7 @@ fn attach_json_errors_without_prompting_for_human_input() { "max_attempts": 1 }, "run_id": "[ULID]", + "stage_id": "approve@1", "ts": "[TIMESTAMP]" }, { diff --git a/lib/crates/fabro-cli/tests/it/cmd/run.rs b/lib/crates/fabro-cli/tests/it/cmd/run.rs index ce90bd159..606e6ccfa 100644 --- a/lib/crates/fabro-cli/tests/it/cmd/run.rs +++ b/lib/crates/fabro-cli/tests/it/cmd/run.rs @@ -74,16 +74,14 @@ fn remote_run_state_response() -> serde_json::Value { fn run_completed_event(run_id: &str) -> serde_json::Value { serde_json::json!({ "seq": 1, - "payload": { - "event": "run.completed", - "id": "evt-run-completed", - "run_id": run_id, - "ts": "2026-04-05T12:00:01Z", - "properties": { - "duration_ms": 12, - "artifact_count": 0, - "status": "success" - } + "event": "run.completed", + "id": "evt-run-completed", + "run_id": run_id, + "ts": "2026-04-05T12:00:01Z", + "properties": { + "duration_ms": 12, + "artifact_count": 0, + "status": "success" } }) } @@ -91,13 +89,11 @@ fn run_completed_event(run_id: &str) -> serde_json::Value { fn run_running_event(run_id: &str, seq: u32) -> serde_json::Value { serde_json::json!({ "seq": seq, - "payload": { - "event": "run.running", - "id": format!("evt-run-running-{seq}"), - "run_id": run_id, - "ts": "2026-04-05T12:00:00Z", - "properties": {} - } + "event": "run.running", + "id": format!("evt-run-running-{seq}"), + "run_id": run_id, + "ts": "2026-04-05T12:00:00Z", + "properties": {} }) } @@ -956,6 +952,7 @@ fn json_run_implies_auto_approve_for_human_gates() { "max_attempts": 1 }, "run_id": "[ULID]", + "stage_id": "start@1", "ts": "[TIMESTAMP]" }, { @@ -983,6 +980,7 @@ fn json_run_implies_auto_approve_for_human_gates() { "status": "success" }, "run_id": "[ULID]", + "stage_id": "start@1", "ts": "[TIMESTAMP]" }, { @@ -1047,6 +1045,7 @@ fn json_run_implies_auto_approve_for_human_gates() { "max_attempts": 1 }, "run_id": "[ULID]", + "stage_id": "approve@1", "ts": "[TIMESTAMP]" }, { @@ -1125,6 +1124,7 @@ fn json_run_implies_auto_approve_for_human_gates() { ] }, "run_id": "[ULID]", + "stage_id": "approve@1", "ts": "[TIMESTAMP]" }, { @@ -1213,6 +1213,7 @@ fn json_run_implies_auto_approve_for_human_gates() { "max_attempts": 1 }, "run_id": "[ULID]", + "stage_id": "ship@1", "ts": "[TIMESTAMP]" }, { @@ -1285,6 +1286,7 @@ fn json_run_implies_auto_approve_for_human_gates() { "status": "success" }, "run_id": "[ULID]", + "stage_id": "ship@1", "ts": "[TIMESTAMP]" }, { @@ -1383,6 +1385,7 @@ fn json_run_implies_auto_approve_for_human_gates() { "max_attempts": 1 }, "run_id": "[ULID]", + "stage_id": "exit@1", "ts": "[TIMESTAMP]" }, { @@ -1398,6 +1401,7 @@ fn json_run_implies_auto_approve_for_human_gates() { "status": "success" }, "run_id": "[ULID]", + "stage_id": "exit@1", "ts": "[TIMESTAMP]" }, { diff --git a/lib/crates/fabro-cli/tests/it/cmd/support.rs b/lib/crates/fabro-cli/tests/it/cmd/support.rs index 2f9a98713..5823a60de 100644 --- a/lib/crates/fabro-cli/tests/it/cmd/support.rs +++ b/lib/crates/fabro-cli/tests/it/cmd/support.rs @@ -661,7 +661,37 @@ pub(crate) fn run_events(run_dir: &Path) -> Vec { run_dir, &format!("/api/v1/runs/{run_id}/events"), )); - serde_json::from_value(response["data"].clone()).expect("event list should parse") + let items = response["data"] + .as_array() + .cloned() + .expect("event list response should contain a data array"); + items + .into_iter() + .map(wire_event_envelope_value_into_store) + .collect::, _>>() + .expect("wire event envelope list should parse") +} + +fn wire_event_envelope_value_into_store(value: serde_json::Value) -> Result { + let mut obj = match value { + serde_json::Value::Object(obj) => obj, + _ => return Err("wire envelope is not an object".to_string()), + }; + let seq = obj + .remove("seq") + .and_then(|v| v.as_u64()) + .and_then(|v| u32::try_from(v).ok()) + .ok_or_else(|| "wire envelope missing valid seq".to_string())?; + let run_id_str = obj + .get("run_id") + .and_then(|v| v.as_str()) + .ok_or_else(|| "wire envelope missing run_id".to_string())?; + let run_id: RunId = run_id_str + .parse() + .map_err(|err| format!("invalid run_id in wire envelope: {err}"))?; + let payload = fabro_store::EventPayload::new(serde_json::Value::Object(obj), &run_id) + .map_err(|err| format!("wire envelope payload failed store validation: {err}"))?; + Ok(EventEnvelope { seq, payload }) } pub(crate) fn wait_for_event_names(run_dir: &Path, expected: &[&str]) { diff --git a/lib/crates/fabro-cli/tests/it/scenario/smoke.rs b/lib/crates/fabro-cli/tests/it/scenario/smoke.rs index 1f1ee67f5..1deb67413 100644 --- a/lib/crates/fabro-cli/tests/it/scenario/smoke.rs +++ b/lib/crates/fabro-cli/tests/it/scenario/smoke.rs @@ -29,16 +29,14 @@ fn live_run_state_response() -> serde_json::Value { fn run_sse_body(run_id: &str) -> String { let completed = serde_json::json!({ "seq": 2, - "payload": { - "event": "run.completed", - "id": "evt-run-completed", - "run_id": run_id, - "ts": "2026-04-05T12:00:01Z", - "properties": { - "duration_ms": 12, - "artifact_count": 0, - "status": "success" - } + "event": "run.completed", + "id": "evt-run-completed", + "run_id": run_id, + "ts": "2026-04-05T12:00:01Z", + "properties": { + "duration_ms": 12, + "artifact_count": 0, + "status": "success" } }); @@ -267,13 +265,11 @@ fn attach_smoke_covers_arg_validation_and_remote_server_behaviors() { serde_json::json!({ "data": [{ "seq": 1, - "payload": { - "event": "run.running", - "id": "evt-run-running", - "run_id": success_run_id, - "ts": "2026-04-05T12:00:00Z", - "properties": {} - } + "event": "run.running", + "id": "evt-run-running", + "run_id": success_run_id, + "ts": "2026-04-05T12:00:00Z", + "properties": {} }], "meta": { "has_more": false } }) @@ -367,13 +363,11 @@ fn attach_smoke_covers_arg_validation_and_remote_server_behaviors() { serde_json::json!({ "data": [{ "seq": 1, - "payload": { - "event": "run.running", - "id": "evt-run-running", - "run_id": eof_run_id, - "ts": "2026-04-05T12:00:00Z", - "properties": {} - } + "event": "run.running", + "id": "evt-run-running", + "run_id": eof_run_id, + "ts": "2026-04-05T12:00:00Z", + "properties": {} }], "meta": { "has_more": false } }) diff --git a/lib/crates/fabro-server/src/demo/mod.rs b/lib/crates/fabro-server/src/demo/mod.rs index 60ff9c54a..c1144a1dd 100644 --- a/lib/crates/fabro-server/src/demo/mod.rs +++ b/lib/crates/fabro-server/src/demo/mod.rs @@ -240,16 +240,14 @@ pub(crate) async fn run_events_stub( Event::default().data( json!({ "seq": 2, - "payload": { - "id": "evt_demo_attach_completed", - "ts": "2026-04-06T15:00:02Z", - "run_id": "01JQ0000000000000000000001", - "event": "run.completed", - "properties": { - "duration_ms": 42, - "artifact_count": 0, - "status": "success" - } + "id": "evt_demo_attach_completed", + "ts": "2026-04-06T15:00:02Z", + "run_id": "01JQ0000000000000000000001", + "event": "run.completed", + "properties": { + "duration_ms": 42, + "artifact_count": 0, + "status": "success" } }) .to_string(), @@ -507,12 +505,10 @@ pub(crate) async fn attach_events_stub( Event::default().data( json!({ "seq": 1, - "payload": { - "id": "evt_demo_1", - "ts": "2026-04-06T15:00:00Z", - "run_id": "01JQ0000000000000000000001", - "event": "run.started" - } + "id": "evt_demo_1", + "ts": "2026-04-06T15:00:00Z", + "run_id": "01JQ0000000000000000000001", + "event": "run.started" }) .to_string(), ), @@ -521,12 +517,10 @@ pub(crate) async fn attach_events_stub( Event::default().data( json!({ "seq": 2, - "payload": { - "id": "evt_demo_2", - "ts": "2026-04-06T15:00:01Z", - "run_id": "01JQ0000000000000000000001", - "event": "stage.started" - } + "id": "evt_demo_2", + "ts": "2026-04-06T15:00:01Z", + "run_id": "01JQ0000000000000000000001", + "event": "stage.started" }) .to_string(), ), diff --git a/lib/crates/fabro-server/src/server.rs b/lib/crates/fabro-server/src/server.rs index 94d158599..76e8b5e13 100644 --- a/lib/crates/fabro-server/src/server.rs +++ b/lib/crates/fabro-server/src/server.rs @@ -111,10 +111,10 @@ pub use fabro_api::types::{ QuestionType as ApiQuestionType, RenderWorkflowGraphDirection, RenderWorkflowGraphFormat, RenderWorkflowGraphRequest, RunArtifactEntry, RunArtifactListResponse, RunBilling, RunBillingStage, RunBillingTotals, RunControlAction as ApiRunControlAction, RunError, - RunEvent as ApiRunEvent, RunManifest, RunStatus, RunStatusResponse, SandboxFileEntry, - SandboxFileListResponse, ServerSettings, SetSecretRequest, SshAccessRequest, SshAccessResponse, - StartRunRequest, StatusReason as ApiStatusReason, SubmitAnswerRequest, SystemInfoResponse, - SystemRunCounts, WriteBlobResponse, + RunManifest, RunStatus, RunStatusResponse, SandboxFileEntry, SandboxFileListResponse, + ServerSettings, SetSecretRequest, SshAccessRequest, SshAccessResponse, StartRunRequest, + StatusReason as ApiStatusReason, SubmitAnswerRequest, SystemInfoResponse, SystemRunCounts, + WriteBlobResponse, }; use fabro_graphviz::render::GraphFormat; @@ -2380,8 +2380,28 @@ fn octet_stream_response(bytes: Bytes) -> Response { } #[allow(clippy::result_large_err)] -fn api_run_event_from_store(payload: &EventPayload) -> Result { - serde_json::from_value(payload.as_value().clone()).map_err(|err| { +fn api_event_envelope_from_store(event: &EventEnvelope) -> Result { + // Wire EventEnvelope is flattened: seq sits alongside the RunEvent + // payload fields at the top level. The progenitor-generated type + // reflects that shape, so we merge seq into the payload value and + // deserialize directly. + let mut value = event.payload.as_value().clone(); + match value.as_object_mut() { + Some(map) => { + map.insert( + "seq".to_string(), + serde_json::Value::Number(i64::from(event.seq).into()), + ); + } + None => { + return Err(ApiError::new( + StatusCode::INTERNAL_SERVER_ERROR, + "stored event payload is not a JSON object".to_string(), + ) + .into_response()); + } + } + serde_json::from_value(value).map_err(|err| { ApiError::new( StatusCode::INTERNAL_SERVER_ERROR, format!("Failed to serialize stored event: {err}"), @@ -2390,14 +2410,6 @@ fn api_run_event_from_store(payload: &EventPayload) -> Result Result { - Ok(ApiEventEnvelope { - payload: api_run_event_from_store(&event.payload)?, - seq: i64::from(event.seq), - }) -} - fn clear_live_run_state(run: &mut ManagedRun) { run.answer_transport = None; run.accepted_questions.clear(); diff --git a/lib/crates/fabro-server/tests/it/scenario/lifecycle.rs b/lib/crates/fabro-server/tests/it/scenario/lifecycle.rs index ea0a4cc24..4a5d062f6 100644 --- a/lib/crates/fabro-server/tests/it/scenario/lifecycle.rs +++ b/lib/crates/fabro-server/tests/it/scenario/lifecycle.rs @@ -270,14 +270,12 @@ async fn cancel_at_human_gate_persists_cancelled_terminal_event() { .unwrap() .iter() .filter_map(|event| { - (event["payload"]["event"] == "run.failed").then(|| { + (event["event"] == "run.failed").then(|| { ( - event["payload"]["properties"]["reason"] - .as_str() - .map(ToOwned::to_owned), - event["payload"]["properties"]["error"] + event["properties"]["reason"] .as_str() .map(ToOwned::to_owned), + event["properties"]["error"].as_str().map(ToOwned::to_owned), ) }) }) diff --git a/lib/crates/fabro-server/tests/it/scenario/run_completion.rs b/lib/crates/fabro-server/tests/it/scenario/run_completion.rs index 7b2dfb915..c8952956a 100644 --- a/lib/crates/fabro-server/tests/it/scenario/run_completion.rs +++ b/lib/crates/fabro-server/tests/it/scenario/run_completion.rs @@ -73,7 +73,7 @@ async fn attach_run_events_replays_terminal_event_after_completion() { .lines() .filter_map(|line| line.strip_prefix("data:")) .filter_map(|line| serde_json::from_str::(line.trim()).ok()) - .filter_map(|event| event["payload"]["event"].as_str().map(ToString::to_string)) + .filter_map(|event| event["event"].as_str().map(ToString::to_string)) .collect::>(); assert!( diff --git a/lib/crates/fabro-server/tests/it/scenario/sse.rs b/lib/crates/fabro-server/tests/it/scenario/sse.rs index 2f67b1685..cb2cb16a3 100644 --- a/lib/crates/fabro-server/tests/it/scenario/sse.rs +++ b/lib/crates/fabro-server/tests/it/scenario/sse.rs @@ -84,7 +84,7 @@ async fn sse_stream_contains_expected_event_types() { if let Some(json_str) = line.strip_prefix("data:") { let json_str = json_str.trim(); if let Ok(event) = serde_json::from_str::(json_str) { - if let Some(event_name) = event["payload"]["event"].as_str() { + if let Some(event_name) = event["event"].as_str() { event_types.push(event_name.to_string()); } } diff --git a/lib/packages/fabro-api-client/src/.openapi-generator/FILES b/lib/packages/fabro-api-client/src/.openapi-generator/FILES index f894e9b11..88db0f6d7 100644 --- a/lib/packages/fabro-api-client/src/.openapi-generator/FILES +++ b/lib/packages/fabro-api-client/src/.openapi-generator/FILES @@ -16,6 +16,8 @@ base.ts common.ts configuration.ts index.ts +models/actor-kind.ts +models/actor-ref.ts models/aggregate-billing-totals.ts models/aggregate-billing.ts models/api-question-option.ts diff --git a/lib/packages/fabro-api-client/src/api/run-internals-api.ts b/lib/packages/fabro-api-client/src/api/run-internals-api.ts index 12509d35c..a23b543a9 100644 --- a/lib/packages/fabro-api-client/src/api/run-internals-api.ts +++ b/lib/packages/fabro-api-client/src/api/run-internals-api.ts @@ -481,7 +481,7 @@ export const RunInternalsApiAxiosParamCreator = function (configuration?: Config }; }, /** - * Uploads one or more artifacts for a stage. Intended for trusted internal callers. The server accepts both: - `application/octet-stream` for single-file uploads with the `filename` query parameter - strict manifest-first `multipart/form-data` uploads documented by `ArtifactBatchUploadManifest` The generated Rust client currently exposes the octet-stream variant because the OpenAPI code generator in this repo does not support multiple request media types on one operation. + * Uploads one or more artifacts for a stage. Intended for trusted internal callers. The server accepts both: - `application/octet-stream` for single-file uploads with the `filename` query parameter - strict manifest-first `multipart/form-data` uploads documented by `ArtifactBatchUploadManifest` The generated Rust client currently exposes the octet-stream variant because the OpenAPI code generator in this repo does not support multiple request media types on one operation. * @summary Put Stage Artifact * @param {string} id Unique run identifier (ULID). * @param {string} stageId Identifier of a stage within a run\'s workflow graph, serialized as `node_id@visit`. @@ -847,7 +847,7 @@ export const RunInternalsApiFp = function(configuration?: Configuration) { return (axios, basePath) => createRequestFunction(localVarAxiosArgs, globalAxios, BASE_PATH, configuration)(axios, localVarOperationServerBasePath || basePath); }, /** - * Uploads one or more artifacts for a stage. Intended for trusted internal callers. The server accepts both: - `application/octet-stream` for single-file uploads with the `filename` query parameter - strict manifest-first `multipart/form-data` uploads documented by `ArtifactBatchUploadManifest` The generated Rust client currently exposes the octet-stream variant because the OpenAPI code generator in this repo does not support multiple request media types on one operation. + * Uploads one or more artifacts for a stage. Intended for trusted internal callers. The server accepts both: - `application/octet-stream` for single-file uploads with the `filename` query parameter - strict manifest-first `multipart/form-data` uploads documented by `ArtifactBatchUploadManifest` The generated Rust client currently exposes the octet-stream variant because the OpenAPI code generator in this repo does not support multiple request media types on one operation. * @summary Put Stage Artifact * @param {string} id Unique run identifier (ULID). * @param {string} stageId Identifier of a stage within a run\'s workflow graph, serialized as `node_id@visit`. @@ -1028,7 +1028,7 @@ export const RunInternalsApiFactory = function (configuration?: Configuration, b return localVarFp.listStageTurns(id, stageId, pageLimit, pageOffset, options).then((request) => request(axios, basePath)); }, /** - * Uploads one or more artifacts for a stage. Intended for trusted internal callers. The server accepts both: - `application/octet-stream` for single-file uploads with the `filename` query parameter - strict manifest-first `multipart/form-data` uploads documented by `ArtifactBatchUploadManifest` The generated Rust client currently exposes the octet-stream variant because the OpenAPI code generator in this repo does not support multiple request media types on one operation. + * Uploads one or more artifacts for a stage. Intended for trusted internal callers. The server accepts both: - `application/octet-stream` for single-file uploads with the `filename` query parameter - strict manifest-first `multipart/form-data` uploads documented by `ArtifactBatchUploadManifest` The generated Rust client currently exposes the octet-stream variant because the OpenAPI code generator in this repo does not support multiple request media types on one operation. * @summary Put Stage Artifact * @param {string} id Unique run identifier (ULID). * @param {string} stageId Identifier of a stage within a run\'s workflow graph, serialized as `node_id@visit`. @@ -1201,7 +1201,7 @@ export class RunInternalsApi extends BaseAPI { } /** - * Uploads one or more artifacts for a stage. Intended for trusted internal callers. The server accepts both: - `application/octet-stream` for single-file uploads with the `filename` query parameter - strict manifest-first `multipart/form-data` uploads documented by `ArtifactBatchUploadManifest` The generated Rust client currently exposes the octet-stream variant because the OpenAPI code generator in this repo does not support multiple request media types on one operation. + * Uploads one or more artifacts for a stage. Intended for trusted internal callers. The server accepts both: - `application/octet-stream` for single-file uploads with the `filename` query parameter - strict manifest-first `multipart/form-data` uploads documented by `ArtifactBatchUploadManifest` The generated Rust client currently exposes the octet-stream variant because the OpenAPI code generator in this repo does not support multiple request media types on one operation. * @summary Put Stage Artifact * @param {string} id Unique run identifier (ULID). * @param {string} stageId Identifier of a stage within a run\'s workflow graph, serialized as `node_id@visit`. @@ -1260,3 +1260,4 @@ export class RunInternalsApi extends BaseAPI { return RunInternalsApiFp(this.configuration).writeRunBlob(id, body, options).then((request) => request(this.axios, this.basePath)); } } + diff --git a/lib/packages/fabro-api-client/src/models/actor-kind.ts b/lib/packages/fabro-api-client/src/models/actor-kind.ts new file mode 100644 index 000000000..abdc2fb3b --- /dev/null +++ b/lib/packages/fabro-api-client/src/models/actor-kind.ts @@ -0,0 +1,30 @@ +/* tslint:disable */ +/* eslint-disable */ +/** + * Fabro Run API + * HTTP API for managing Fabro workflow run executions. + * + * The version of the OpenAPI document: 0.1.0 + * + * + * NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech). + * https://openapi-generator.tech + * Do not edit the class manually. + */ + + + +/** + * High-level category of an event actor. + */ + +export const ActorKind = { + USER: 'user', + AGENT: 'agent', + SYSTEM: 'system' +} as const; + +export type ActorKind = typeof ActorKind[keyof typeof ActorKind]; + + + diff --git a/lib/packages/fabro-api-client/src/models/actor-ref.ts b/lib/packages/fabro-api-client/src/models/actor-ref.ts new file mode 100644 index 000000000..491cd29e4 --- /dev/null +++ b/lib/packages/fabro-api-client/src/models/actor-ref.ts @@ -0,0 +1,36 @@ +/* tslint:disable */ +/* eslint-disable */ +/** + * Fabro Run API + * HTTP API for managing Fabro workflow run executions. + * + * The version of the OpenAPI document: 0.1.0 + * + * + * NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech). + * https://openapi-generator.tech + * Do not edit the class manually. + */ + + +// May contain unused imports in some cases +// @ts-ignore +import type { ActorKind } from './actor-kind'; + +/** + * Optional primary actor associated with a run event. Present on control actions and durable agent output where a stable user or agent identity matters; omitted on routine runtime lifecycle events. + */ +export interface ActorRef { + 'kind': ActorKind; + /** + * Stable actor identifier when available. + */ + 'id'?: string; + /** + * Display-friendly label for the actor. + */ + 'display'?: string; +} + + + diff --git a/lib/packages/fabro-api-client/src/models/api-question.ts b/lib/packages/fabro-api-client/src/models/api-question.ts index fa10acb0f..78ea79cd3 100644 --- a/lib/packages/fabro-api-client/src/models/api-question.ts +++ b/lib/packages/fabro-api-client/src/models/api-question.ts @@ -32,6 +32,10 @@ export interface ApiQuestion { * The question text displayed to the user. */ 'text': string; + /** + * Workflow stage identifier that produced the question. + */ + 'stage': string; 'question_type': QuestionType; /** * Available options for selection-based questions. Empty for freeform questions. @@ -41,6 +45,14 @@ export interface ApiQuestion { * Whether the user may provide freeform text in addition to selecting options. */ 'allow_freeform': boolean; + /** + * Timeout for the question when configured by the workflow. + */ + 'timeout_seconds'?: number; + /** + * Optional contextual text shown alongside the question. + */ + 'context_display'?: string; } diff --git a/lib/packages/fabro-api-client/src/models/artifact-batch-upload-entry.ts b/lib/packages/fabro-api-client/src/models/artifact-batch-upload-entry.ts index 8a42ae05a..3a4545f4e 100644 --- a/lib/packages/fabro-api-client/src/models/artifact-batch-upload-entry.ts +++ b/lib/packages/fabro-api-client/src/models/artifact-batch-upload-entry.ts @@ -5,7 +5,7 @@ * HTTP API for managing Fabro workflow run executions. * * The version of the OpenAPI document: 0.1.0 - * + * * * NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech). * https://openapi-generator.tech @@ -39,3 +39,4 @@ export interface ArtifactBatchUploadEntry { */ 'content_type'?: string; } + diff --git a/lib/packages/fabro-api-client/src/models/artifact-batch-upload-manifest.ts b/lib/packages/fabro-api-client/src/models/artifact-batch-upload-manifest.ts index ad483a824..8b0d60046 100644 --- a/lib/packages/fabro-api-client/src/models/artifact-batch-upload-manifest.ts +++ b/lib/packages/fabro-api-client/src/models/artifact-batch-upload-manifest.ts @@ -5,7 +5,7 @@ * HTTP API for managing Fabro workflow run executions. * * The version of the OpenAPI document: 0.1.0 - * + * * * NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech). * https://openapi-generator.tech @@ -23,3 +23,4 @@ import type { ArtifactBatchUploadEntry } from './artifact-batch-upload-entry'; export interface ArtifactBatchUploadManifest { 'entries': Array; } + diff --git a/lib/packages/fabro-api-client/src/models/event-envelope.ts b/lib/packages/fabro-api-client/src/models/event-envelope.ts index fd7d04aea..3b99be9e1 100644 --- a/lib/packages/fabro-api-client/src/models/event-envelope.ts +++ b/lib/packages/fabro-api-client/src/models/event-envelope.ts @@ -13,18 +13,17 @@ */ +// May contain unused imports in some cases +// @ts-ignore +import type { ActorRef } from './actor-ref'; // May contain unused imports in some cases // @ts-ignore import type { RunEvent } from './run-event'; /** - * Stored event envelope with assigned sequence number. + * @type EventEnvelope + * Stored event envelope with assigned sequence number. On the wire the envelope is flattened: seq sits alongside the RunEvent payload fields at the top level of the JSON object. */ -export interface EventEnvelope { - /** - * Assigned event sequence number. - */ - 'seq': number; - 'payload': RunEvent; -} +export type EventEnvelope = RunEvent; + diff --git a/lib/packages/fabro-api-client/src/models/index.ts b/lib/packages/fabro-api-client/src/models/index.ts index aecd845cf..adc64cfd0 100644 --- a/lib/packages/fabro-api-client/src/models/index.ts +++ b/lib/packages/fabro-api-client/src/models/index.ts @@ -1,3 +1,5 @@ +export * from './actor-kind'; +export * from './actor-ref'; export * from './aggregate-billing'; export * from './aggregate-billing-totals'; export * from './api-question'; diff --git a/lib/packages/fabro-api-client/src/models/run-event.ts b/lib/packages/fabro-api-client/src/models/run-event.ts index c9090d435..f7319b815 100644 --- a/lib/packages/fabro-api-client/src/models/run-event.ts +++ b/lib/packages/fabro-api-client/src/models/run-event.ts @@ -13,6 +13,9 @@ */ +// May contain unused imports in some cases +// @ts-ignore +import type { ActorRef } from './actor-ref'; /** * Internal RunEvent-compatible JSON payload. The server validates this body by deserializing into the typed RunEvent struct. @@ -25,8 +28,25 @@ export interface RunEvent { 'run_id': string; 'node_id'?: string; 'node_label'?: string; + /** + * Stage execution identity, formatted as \"{node_id}@{visit}\". + */ + 'stage_id'?: string; + /** + * Durable identity of one execution of a parallel node, formatted as \"{node_id}@{visit}\". + */ + 'parallel_group_id'?: string; + /** + * Durable identity of one branch within a parallel execution, formatted as \"{parallel_group_id}:{index}\". + */ + 'parallel_branch_id'?: string; 'session_id'?: string; 'parent_session_id'?: string; + /** + * Stable identifier for a tool call, present on agent.tool.* events and other durable events that directly describe the same tool call. + */ + 'tool_call_id'?: string; + 'actor'?: ActorRef; /** * Event type discriminator. */ diff --git a/lib/packages/fabro-api-client/src/models/web-settings.ts b/lib/packages/fabro-api-client/src/models/web-settings.ts index a1bd5dd22..cbe3dee56 100644 --- a/lib/packages/fabro-api-client/src/models/web-settings.ts +++ b/lib/packages/fabro-api-client/src/models/web-settings.ts @@ -21,6 +21,10 @@ import type { AuthSettings } from './auth-settings'; * Web UI configuration. */ export interface WebSettings { + /** + * Whether the embedded web UI and browser-oriented routes are enabled. + */ + 'enabled'?: boolean; /** * Web UI URL. */