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) <noreply@anthropic.com>
This commit is contained in:
Bryan Helmkamp 2026-04-09 10:40:21 -04:00
parent b6eb462a05
commit 51dc4350a5
No known key found for this signature in database
22 changed files with 346 additions and 114 deletions

View file

@ -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.

View file

@ -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<EventEnvelope> {
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<EventEnvelope> {
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<ServerStoreClient> {
@ -407,7 +441,7 @@ impl ServerStoreClient {
let page_events = parsed
.data
.into_iter()
.map(convert_type)
.map(wire_event_envelope_from_generated)
.collect::<Result<Vec<EventEnvelope>>>()?;
let next_page_since_seq = page_events.last().map(|event| event.seq.saturating_add(1));
all_events.extend(page_events);

View file

@ -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]"
},
{

View file

@ -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]"
},
{

View file

@ -661,7 +661,37 @@ pub(crate) fn run_events(run_dir: &Path) -> Vec<EventEnvelope> {
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::<Result<Vec<_>, _>>()
.expect("wire event envelope list should parse")
}
fn wire_event_envelope_value_into_store(value: serde_json::Value) -> Result<EventEnvelope, String> {
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]) {

View file

@ -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 }
})

View file

@ -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(),
),

View file

@ -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<ApiRunEvent, Response> {
serde_json::from_value(payload.as_value().clone()).map_err(|err| {
fn api_event_envelope_from_store(event: &EventEnvelope) -> Result<ApiEventEnvelope, Response> {
// 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<ApiRunEvent, Respo
})
}
#[allow(clippy::result_large_err)]
fn api_event_envelope_from_store(event: &EventEnvelope) -> Result<ApiEventEnvelope, Response> {
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();

View file

@ -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),
)
})
})

View file

@ -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::<serde_json::Value>(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::<Vec<_>>();
assert!(

View file

@ -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::<serde_json::Value>(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());
}
}

View file

@ -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

View file

@ -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\&#39;s workflow graph, serialized as &#x60;node_id@visit&#x60;.
@ -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\&#39;s workflow graph, serialized as &#x60;node_id@visit&#x60;.
@ -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\&#39;s workflow graph, serialized as &#x60;node_id@visit&#x60;.
@ -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\&#39;s workflow graph, serialized as &#x60;node_id@visit&#x60;.
@ -1260,3 +1260,4 @@ export class RunInternalsApi extends BaseAPI {
return RunInternalsApiFp(this.configuration).writeRunBlob(id, body, options).then((request) => request(this.axios, this.basePath));
}
}

View file

@ -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];

View file

@ -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;
}

View file

@ -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;
}

View file

@ -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;
}

View file

@ -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<ArtifactBatchUploadEntry>;
}

View file

@ -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;

View file

@ -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';

View file

@ -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.
*/

View file

@ -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.
*/