From 39980b5404fd5bc493aed1858d535d95aeefa7c1 Mon Sep 17 00:00:00 2001 From: Bryan Helmkamp Date: Sat, 4 Apr 2026 15:37:33 -0400 Subject: [PATCH] refactor: add run store HTTP endpoints Define the new run-store contract in the OpenAPI spec, regenerate the Rust and TypeScript clients, and implement the matching store and server support for run state, event access, blobs, and stage artifacts. --- docs/api-reference/fabro-api.yaml | 560 ++++++++++++- lib/crates/fabro-server/src/server.rs | 617 +++++++++++++- lib/crates/fabro-server/tests/it/api.rs | 4 +- lib/crates/fabro-store/src/run_state.rs | 4 +- lib/crates/fabro-store/src/slate/mod.rs | 51 ++ lib/crates/fabro-store/src/slate/run_store.rs | 66 ++ .../src/.openapi-generator/FILES | 15 + .../src/api/run-internals-api.ts | 791 +++++++++++++++++- .../fabro-api-client/src/api/runs-api.ts | 75 -- .../src/models/append-event-response.ts | 26 + .../src/models/artifact-entry.ts | 26 + .../src/models/artifact-list-response.ts | 26 + .../src/models/event-envelope.ts | 30 + .../fabro-api-client/src/models/index.ts | 15 + .../src/models/internal-run-status.ts | 35 + .../src/models/internal-stage-status.ts | 32 + .../fabro-api-client/src/models/node-state.ts | 35 + .../src/models/node-status-record.ts | 31 + .../src/models/paginated-event-list.ts | 30 + .../fabro-api-client/src/models/run-event.ts | 36 + .../run-projection-checkpoints-inner-inner.ts | 25 + .../src/models/run-projection.ts | 54 ++ .../src/models/run-status-record.ts | 33 + .../src/models/status-reason.ts | 38 + .../src/models/write-blob-response.ts | 26 + 25 files changed, 2554 insertions(+), 127 deletions(-) create mode 100644 lib/packages/fabro-api-client/src/models/append-event-response.ts create mode 100644 lib/packages/fabro-api-client/src/models/artifact-entry.ts create mode 100644 lib/packages/fabro-api-client/src/models/artifact-list-response.ts create mode 100644 lib/packages/fabro-api-client/src/models/event-envelope.ts create mode 100644 lib/packages/fabro-api-client/src/models/internal-run-status.ts create mode 100644 lib/packages/fabro-api-client/src/models/internal-stage-status.ts create mode 100644 lib/packages/fabro-api-client/src/models/node-state.ts create mode 100644 lib/packages/fabro-api-client/src/models/node-status-record.ts create mode 100644 lib/packages/fabro-api-client/src/models/paginated-event-list.ts create mode 100644 lib/packages/fabro-api-client/src/models/run-event.ts create mode 100644 lib/packages/fabro-api-client/src/models/run-projection-checkpoints-inner-inner.ts create mode 100644 lib/packages/fabro-api-client/src/models/run-projection.ts create mode 100644 lib/packages/fabro-api-client/src/models/run-status-record.ts create mode 100644 lib/packages/fabro-api-client/src/models/status-reason.ts create mode 100644 lib/packages/fabro-api-client/src/models/write-blob-response.ts diff --git a/docs/api-reference/fabro-api.yaml b/docs/api-reference/fabro-api.yaml index 044a7aa7f..9632d9dca 100644 --- a/docs/api-reference/fabro-api.yaml +++ b/docs/api-reference/fabro-api.yaml @@ -335,14 +335,93 @@ paths: schema: $ref: "#/components/schemas/ErrorResponse" - /api/v1/runs/{id}/events: + /api/v1/runs/{id}/state: get: - operationId: streamRunEvents - tags: [Runs] - summary: Stream Run Events - description: Opens a server-sent event (SSE) stream for real-time run updates. Returns 410 if the stream has been closed. + operationId: getRunState + tags: [Run Internals] + summary: Get Run State + description: Returns the internal event-sourced run projection. This is not a stable public contract. parameters: - $ref: "#/components/parameters/RunId" + responses: + "200": + description: Current run projection + content: + application/json: + schema: + $ref: "#/components/schemas/RunProjection" + "404": + description: Run not found + content: + application/json: + schema: + $ref: "#/components/schemas/ErrorResponse" + + /api/v1/runs/{id}/events: + get: + operationId: listRunEvents + tags: [Run Internals] + summary: List Run Events + description: Returns a paginated JSON list of stored run events. + parameters: + - $ref: "#/components/parameters/RunId" + - $ref: "#/components/parameters/SinceSeq" + - $ref: "#/components/parameters/EventLimit" + responses: + "200": + description: Paginated list of run events + content: + application/json: + schema: + $ref: "#/components/schemas/PaginatedEventList" + "404": + description: Run not found + content: + application/json: + schema: + $ref: "#/components/schemas/ErrorResponse" + post: + operationId: appendRunEvent + tags: [Run Internals] + summary: Append Run Event + description: Appends a validated event to the run event log. Intended for trusted internal callers. + parameters: + - $ref: "#/components/parameters/RunId" + requestBody: + required: true + content: + application/json: + schema: + $ref: "#/components/schemas/RunEvent" + responses: + "200": + description: Event appended + content: + application/json: + schema: + $ref: "#/components/schemas/AppendEventResponse" + "400": + description: Invalid event payload + content: + application/json: + schema: + $ref: "#/components/schemas/ErrorResponse" + "404": + description: Run not found + content: + application/json: + schema: + $ref: "#/components/schemas/ErrorResponse" + + /api/v1/runs/{id}/attach: + get: + operationId: attachRunEvents + tags: [Run Internals] + summary: Attach Run Events + description: Opens a server-sent event stream for a live run. Optionally replays stored events from `since_seq` before switching to live updates. + parameters: + - $ref: "#/components/parameters/RunId" + - $ref: "#/components/parameters/SinceSeq" responses: "200": description: Server-sent event stream @@ -357,7 +436,60 @@ paths: schema: $ref: "#/components/schemas/ErrorResponse" "410": - description: Event stream closed + description: Run is not live on this server + content: + application/json: + schema: + $ref: "#/components/schemas/ErrorResponse" + + /api/v1/runs/{id}/blobs: + post: + operationId: writeRunBlob + tags: [Run Internals] + summary: Write Run Blob + description: Writes an opaque binary blob and returns its content-addressed blob identifier. + parameters: + - $ref: "#/components/parameters/RunId" + requestBody: + required: true + content: + application/octet-stream: + schema: + type: string + format: binary + responses: + "200": + description: Blob written + content: + application/json: + schema: + $ref: "#/components/schemas/WriteBlobResponse" + "404": + description: Run not found + content: + application/json: + schema: + $ref: "#/components/schemas/ErrorResponse" + + /api/v1/runs/{id}/blobs/{blobId}: + get: + operationId: readRunBlob + tags: [Run Internals] + summary: Read Run Blob + description: Reads a previously stored blob by identifier. + parameters: + - $ref: "#/components/parameters/RunId" + - $ref: "#/components/parameters/BlobId" + responses: + "200": + description: Blob contents + content: + application/octet-stream: + schema: + type: string + format: binary + "404": + description: Run or blob not found content: application/json: schema: @@ -497,6 +629,91 @@ paths: schema: $ref: "#/components/schemas/ErrorResponse" + /api/v1/runs/{id}/stages/{stageId}/artifacts: + get: + operationId: listStageArtifacts + tags: [Run Internals] + summary: List Stage Artifacts + description: Lists artifact filenames stored for a stage. + parameters: + - $ref: "#/components/parameters/RunId" + - $ref: "#/components/parameters/StageId" + responses: + "200": + description: Artifact filenames for the stage + content: + application/json: + schema: + $ref: "#/components/schemas/ArtifactListResponse" + "404": + description: Run not found + content: + application/json: + schema: + $ref: "#/components/schemas/ErrorResponse" + post: + operationId: putStageArtifact + tags: [Run Internals] + summary: Put Stage Artifact + description: Uploads an artifact for a stage. Intended for trusted internal callers. + parameters: + - $ref: "#/components/parameters/RunId" + - $ref: "#/components/parameters/StageId" + - $ref: "#/components/parameters/ArtifactFilename" + requestBody: + required: true + content: + application/octet-stream: + schema: + type: string + format: binary + responses: + "204": + description: Artifact written + "400": + description: Missing filename + content: + application/json: + schema: + $ref: "#/components/schemas/ErrorResponse" + "404": + description: Run not found + content: + application/json: + schema: + $ref: "#/components/schemas/ErrorResponse" + + /api/v1/runs/{id}/stages/{stageId}/artifacts/download: + get: + operationId: getStageArtifact + tags: [Run Internals] + summary: Get Stage Artifact + description: Downloads an artifact by filename. + parameters: + - $ref: "#/components/parameters/RunId" + - $ref: "#/components/parameters/StageId" + - $ref: "#/components/parameters/ArtifactFilename" + responses: + "200": + description: Artifact contents + content: + application/octet-stream: + schema: + type: string + format: binary + "400": + description: Missing filename + content: + application/json: + schema: + $ref: "#/components/schemas/ErrorResponse" + "404": + description: Run, stage, or artifact not found + content: + application/json: + schema: + $ref: "#/components/schemas/ErrorResponse" + /api/v1/runs/{id}/files: get: operationId: retrieveRunFiles @@ -1318,10 +1535,52 @@ components: name: stageId in: path required: true - description: Identifier of a stage within a run's workflow graph. + description: Identifier of a stage within a run's workflow graph, serialized as `node_id@visit`. schema: type: string - example: propose-changes + example: code@2 + + BlobId: + name: blobId + in: path + required: true + description: Content-addressed blob identifier. + schema: + type: string + format: uuid + example: 550e8400-e29b-41d4-a716-446655440000 + + ArtifactFilename: + name: filename + in: query + required: true + description: Artifact filename. May contain path separators. + schema: + type: string + example: src/lib.rs + + SinceSeq: + name: since_seq + in: query + required: false + description: First event sequence number to include. + schema: + type: integer + minimum: 1 + default: 1 + example: 42 + + EventLimit: + name: limit + in: query + required: false + description: Maximum number of events to return. + schema: + type: integer + minimum: 1 + maximum: 1000 + default: 100 + example: 100 QuestionId: name: qid @@ -2076,6 +2335,291 @@ components: items: $ref: "#/components/schemas/ErrorResponseEntry" + RunEvent: + description: > + Internal RunEvent-compatible JSON payload. The server validates this + body by deserializing into the typed RunEvent struct. + type: object + required: + - id + - ts + - run_id + - event + properties: + id: + type: string + ts: + type: string + format: date-time + run_id: + type: string + node_id: + type: string + nullable: true + node_label: + type: string + nullable: true + session_id: + type: string + nullable: true + parent_session_id: + type: string + nullable: true + event: + type: string + description: Event type discriminator. + example: stage.started + properties: + type: object + additionalProperties: true + 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" + + PaginatedEventList: + description: Paginated list of stored run events. + type: object + required: + - data + - meta + properties: + data: + type: array + items: + $ref: "#/components/schemas/EventEnvelope" + meta: + $ref: "#/components/schemas/PaginationMeta" + + AppendEventResponse: + description: Assigned sequence number for an appended event. + type: object + required: + - seq + properties: + seq: + type: integer + description: Assigned event sequence number. + example: 42 + + WriteBlobResponse: + description: Content-addressed identifier for a stored blob. + type: object + required: + - id + properties: + id: + type: string + description: Blob identifier. + example: 550e8400-e29b-41d4-a716-446655440000 + + ArtifactEntry: + description: A single artifact filename. + type: object + required: + - filename + properties: + filename: + type: string + description: Artifact filename. + example: src/lib.rs + + ArtifactListResponse: + description: List of artifact filenames for a stage. + type: object + required: + - data + properties: + data: + type: array + items: + $ref: "#/components/schemas/ArtifactEntry" + + InternalRunStatus: + description: Internal event-sourced run status. + type: string + enum: + - submitted + - starting + - running + - paused + - removing + - succeeded + - failed + - dead + + StatusReason: + description: Optional reason attached to a run status transition. + type: string + enum: + - completed + - partial_success + - workflow_error + - cancelled + - terminated + - transient_infra + - budget_exhausted + - launch_failed + - bootstrap_failed + - sandbox_init_failed + - sandbox_initializing + + RunStatusRecord: + description: Internal run status record from the event projection. + type: object + required: + - status + - updated_at + properties: + status: + $ref: "#/components/schemas/InternalRunStatus" + reason: + oneOf: + - $ref: "#/components/schemas/StatusReason" + - type: "null" + updated_at: + type: string + format: date-time + + InternalStageStatus: + description: Internal stage status from outcomes and node status records. + type: string + enum: + - success + - fail + - skipped + - partial_success + - retry + + NodeStatusRecord: + description: Internal node status record. + type: object + required: + - status + - timestamp + properties: + status: + $ref: "#/components/schemas/InternalStageStatus" + notes: + type: string + nullable: true + failure_reason: + type: string + nullable: true + timestamp: + type: string + format: date-time + + NodeState: + description: Internal node projection state. + type: object + properties: + prompt: + type: string + nullable: true + response: + type: string + nullable: true + status: + oneOf: + - $ref: "#/components/schemas/NodeStatusRecord" + - type: "null" + provider_used: + nullable: true + diff: + type: string + nullable: true + script_invocation: + nullable: true + script_timing: + nullable: true + parallel_results: + nullable: true + stdout: + type: string + nullable: true + stderr: + type: string + nullable: true + + RunProjection: + description: Raw internal run projection derived from the event log. + type: object + required: + - nodes + properties: + run: + type: object + additionalProperties: true + nullable: true + graph_source: + type: string + nullable: true + start: + type: object + additionalProperties: true + nullable: true + status: + oneOf: + - $ref: "#/components/schemas/RunStatusRecord" + - type: "null" + checkpoint: + oneOf: + - $ref: "#/components/schemas/RunCheckpoint" + - type: "null" + checkpoints: + type: array + description: Sequence-tagged checkpoint history entries as `[seq, checkpoint]`. + items: + type: array + minItems: 2 + maxItems: 2 + items: + oneOf: + - type: integer + - $ref: "#/components/schemas/RunCheckpoint" + conclusion: + type: object + additionalProperties: true + nullable: true + retro: + type: object + additionalProperties: true + nullable: true + retro_prompt: + type: string + nullable: true + retro_response: + type: string + nullable: true + sandbox: + type: object + additionalProperties: true + nullable: true + final_patch: + type: string + nullable: true + pull_request: + type: object + additionalProperties: true + nullable: true + nodes: + type: object + description: Map from StageId (`node_id@visit`) to NodeState. + additionalProperties: + $ref: "#/components/schemas/NodeState" + # ── Run Board Schemas ──────────────────────────────────────────────── BoardColumn: diff --git a/lib/crates/fabro-server/src/server.rs b/lib/crates/fabro-server/src/server.rs index 23048571d..b15f68cc8 100644 --- a/lib/crates/fabro-server/src/server.rs +++ b/lib/crates/fabro-server/src/server.rs @@ -1,4 +1,5 @@ use std::collections::HashMap; +use std::str::FromStr; use std::sync::atomic::{AtomicBool, Ordering}; use std::sync::{Arc, Mutex, RwLock}; use std::time::Duration; @@ -13,14 +14,15 @@ use axum::response::{IntoResponse, Response}; use axum::routing::{get, post}; use axum::{Json, Router}; use axum_extra::extract::cookie::Key; +use bytes::Bytes; use fabro_llm::client::Client as LlmClient; use fabro_llm::generate::{GenerateParams, generate, generate_object}; use fabro_llm::types::{ ContentPart, FinishReason, Message as LlmMessage, Request as LlmRequest, Response as LlmResponse, Role, StreamEvent, ToolChoice, ToolDefinition, Usage, }; -use fabro_store::StoreHandle; -use fabro_types::{RunEvent, RunId, Settings}; +use fabro_store::{EventEnvelope, EventPayload, StageId, StoreHandle}; +use fabro_types::{RunBlobId, RunEvent, RunId, Settings}; use fabro_util::redact::redact_jsonl_line; use fabro_workflow::error::FabroError; use fabro_workflow::handler::HandlerRegistry; @@ -32,7 +34,6 @@ use tokio::sync::{Notify, OnceCell}; use tokio::task::spawn_blocking; use tokio::time::{sleep, timeout}; use tokio_stream::StreamExt; -use tokio_stream::wrappers::BroadcastStream; use tower::{ServiceExt, service_fn}; use ulid::Ulid; @@ -53,11 +54,13 @@ use fabro_workflow::records::Checkpoint; use fabro_api::types::AggregateUsageTotals; pub use fabro_api::types::{ - AggregateUsage, ApiQuestion, ApiQuestionOption, CompletionContentPart, CompletionMessage, - CompletionMessageRole, CompletionResponse, CompletionToolChoiceMode, CompletionUsage, - CreateCompletionRequest, CreateRunRequest, ModelReference, PaginatedRunList, PaginationMeta, - QuestionType as ApiQuestionType, RunError, RunStatus, RunStatusResponse, SubmitAnswerRequest, - TokenUsage, UsageByModel, + AggregateUsage, ApiQuestion, ApiQuestionOption, AppendEventResponse, ArtifactEntry, + ArtifactListResponse, CompletionContentPart, CompletionMessage, CompletionMessageRole, + CompletionResponse, CompletionToolChoiceMode, CompletionUsage, CreateCompletionRequest, + CreateRunRequest, EventEnvelope as ApiEventEnvelope, ModelReference, PaginatedEventList, + PaginatedRunList, PaginationMeta, QuestionType as ApiQuestionType, RunError, RunEvent as ApiRunEvent, + RunStatus, RunStatusResponse, SubmitAnswerRequest, TokenUsage, UsageByModel, + WriteBlobResponse, }; pub fn default_page_limit() -> u32 { @@ -72,6 +75,36 @@ pub struct PaginationParams { pub offset: u32, } +#[derive(serde::Deserialize)] +struct EventListParams { + #[serde(default)] + since_seq: Option, + #[serde(default)] + limit: Option, +} + +impl EventListParams { + fn since_seq(&self) -> u32 { + self.since_seq.unwrap_or(1).max(1) + } + + fn limit(&self) -> usize { + self.limit.unwrap_or(100).clamp(1, 1000) + } +} + +#[derive(serde::Deserialize)] +struct AttachParams { + #[serde(default)] + since_seq: Option, +} + +#[derive(serde::Deserialize)] +struct ArtifactFilenameParams { + #[serde(default)] + filename: Option, +} + /// Non-paginated list response wrapper with `has_more: false`. #[derive(serde::Serialize)] pub struct ListResponse { @@ -199,7 +232,11 @@ fn demo_routes() -> Router> { .route("/runs/{id}", get(demo::get_run_status)) .route("/runs/{id}/questions", get(demo::get_questions_stub)) .route("/runs/{id}/questions/{qid}/answer", post(demo::answer_stub)) - .route("/runs/{id}/events", get(demo::run_events_stub)) + .route("/runs/{id}/state", get(not_implemented)) + .route("/runs/{id}/events", get(not_implemented).post(not_implemented)) + .route("/runs/{id}/attach", get(demo::run_events_stub)) + .route("/runs/{id}/blobs", post(not_implemented)) + .route("/runs/{id}/blobs/{blobId}", get(not_implemented)) .route("/runs/{id}/checkpoint", get(demo::checkpoint_stub)) .route("/runs/{id}/cancel", post(demo::cancel_stub)) .route("/runs/{id}/start", post(demo::start_run_stub)) @@ -212,6 +249,14 @@ fn demo_routes() -> Router> { "/runs/{id}/stages/{stageId}/turns", get(demo::get_stage_turns), ) + .route( + "/runs/{id}/stages/{stageId}/artifacts", + get(not_implemented).post(not_implemented), + ) + .route( + "/runs/{id}/stages/{stageId}/artifacts/download", + get(not_implemented), + ) .route("/runs/{id}/files", get(demo::get_run_files)) .route("/runs/{id}/usage", get(demo::get_run_usage)) .route("/runs/{id}/verification", get(demo::get_run_verification)) @@ -275,7 +320,11 @@ fn real_routes() -> Router> { .route("/runs/{id}", get(get_run_status)) .route("/runs/{id}/questions", get(get_questions)) .route("/runs/{id}/questions/{qid}/answer", post(submit_answer)) - .route("/runs/{id}/events", get(get_events)) + .route("/runs/{id}/state", get(get_run_state)) + .route("/runs/{id}/events", get(list_run_events).post(append_run_event)) + .route("/runs/{id}/attach", get(attach_run_events)) + .route("/runs/{id}/blobs", post(write_run_blob)) + .route("/runs/{id}/blobs/{blobId}", get(read_run_blob)) .route("/runs/{id}/checkpoint", get(get_checkpoint)) .route("/runs/{id}/cancel", post(cancel_run)) .route("/runs/{id}/start", post(start_run)) @@ -285,6 +334,14 @@ fn real_routes() -> Router> { .route("/runs/{id}/retro", get(get_retro)) .route("/runs/{id}/stages", get(not_implemented)) .route("/runs/{id}/stages/{stageId}/turns", get(not_implemented)) + .route( + "/runs/{id}/stages/{stageId}/artifacts", + get(list_stage_artifacts).post(put_stage_artifact), + ) + .route( + "/runs/{id}/stages/{stageId}/artifacts/download", + get(get_stage_artifact), + ) .route("/runs/{id}/files", get(not_implemented)) .route("/runs/{id}/usage", get(not_implemented)) .route("/runs/{id}/verification", get(not_implemented)) @@ -521,6 +578,54 @@ fn parse_run_id_path(id: &str) -> Result { .map_err(|_| ApiError::bad_request("Invalid run ID.").into_response()) } +#[allow(clippy::result_large_err)] +fn parse_stage_id_path(stage_id: &str) -> Result { + StageId::from_str(stage_id) + .map_err(|_| ApiError::bad_request("Invalid stage ID.").into_response()) +} + +#[allow(clippy::result_large_err)] +fn parse_blob_id_path(blob_id: &str) -> Result { + RunBlobId::from_str(blob_id) + .map_err(|_| ApiError::bad_request("Invalid blob ID.").into_response()) +} + +#[allow(clippy::result_large_err)] +fn required_filename(params: ArtifactFilenameParams) -> Result { + match params.filename { + Some(filename) if !filename.is_empty() => Ok(filename), + _ => Err(ApiError::bad_request("Missing filename query parameter.").into_response()), + } +} + +fn octet_stream_response(bytes: Bytes) -> Response { + ( + StatusCode::OK, + [("content-type", "application/octet-stream")], + bytes, + ) + .into_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| { + ApiError::new( + StatusCode::INTERNAL_SERVER_ERROR, + format!("Failed to serialize stored event: {err}"), + ) + .into_response() + }) +} + +#[allow(clippy::result_large_err)] +fn api_event_envelope_from_store(event: &EventEnvelope) -> 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.interviewer = None; run.event_tx = None; @@ -1033,7 +1138,7 @@ async fn submit_answer( } } -async fn get_events( +async fn get_run_state( _auth: AuthenticatedService, State(state): State>, Path(id): Path, @@ -1042,28 +1147,144 @@ async fn get_events( Ok(id) => id, Err(response) => return response, }; - let rx = { - let runs = state.runs.lock().expect("runs lock poisoned"); - match runs.get(&id) { - Some(managed_run) => match &managed_run.event_tx { - Some(tx) => tx.subscribe(), - None => { - return ApiError::new(StatusCode::GONE, "Event stream closed.").into_response(); - } - }, - None => return ApiError::not_found("Run not found.").into_response(), - } + match state.store.open_run_reader(&id).await { + Ok(run_store) => match run_store.state().await { + Ok(run_state) => Json(run_state).into_response(), + Err(err) => ApiError::new(StatusCode::INTERNAL_SERVER_ERROR, err.to_string()) + .into_response(), + }, + Err(_) => ApiError::not_found("Run not found.").into_response(), + } +} + +async fn append_run_event( + _auth: AuthenticatedService, + State(state): State>, + Path(id): Path, + Json(value): Json, +) -> Response { + let id = match parse_run_id_path(&id) { + Ok(id) => id, + Err(response) => return response, + }; + let event = match RunEvent::from_value(value.clone()) { + Ok(event) => event, + Err(err) => return ApiError::bad_request(format!("Invalid run event: {err}")).into_response(), + }; + if event.run_id != id { + return ApiError::bad_request("Event run_id does not match path run ID.").into_response(); + } + let payload = match EventPayload::new(value, &id) { + Ok(payload) => payload, + Err(err) => return ApiError::bad_request(err.to_string()).into_response(), }; - let stream = BroadcastStream::new(rx).filter_map(|result| match result { - Ok(event) => { - let data = serde_json::to_string(&event).unwrap_or_default(); - let data = redact_jsonl_line(&data); - Some(Ok::( - Event::default().data(data), - )) + match state.store.open_run(&id).await { + Ok(run_store) => match run_store.append_event(&payload).await { + Ok(seq) => Json(AppendEventResponse { seq: i64::from(seq) }).into_response(), + Err(err) => ApiError::new(StatusCode::INTERNAL_SERVER_ERROR, err.to_string()) + .into_response(), + }, + Err(_) => ApiError::not_found("Run not found.").into_response(), + } +} + +async fn list_run_events( + _auth: AuthenticatedService, + State(state): State>, + Path(id): Path, + Query(params): Query, +) -> Response { + let id = match parse_run_id_path(&id) { + Ok(id) => id, + Err(response) => return response, + }; + let since_seq = params.since_seq(); + let limit = params.limit(); + + match state.store.open_run_reader(&id).await { + Ok(run_store) => match run_store.list_events_from_with_limit(since_seq, limit).await { + Ok(mut events) => { + let has_more = events.len() > limit; + events.truncate(limit); + let mut data = Vec::with_capacity(events.len()); + for event in events { + let event = match api_event_envelope_from_store(&event) { + Ok(event) => event, + Err(response) => return response, + }; + data.push(event); + } + Json(PaginatedEventList { + data, + meta: PaginationMeta { has_more }, + }) + .into_response() + } + Err(err) => ApiError::new(StatusCode::INTERNAL_SERVER_ERROR, err.to_string()) + .into_response(), + }, + Err(_) => ApiError::not_found("Run not found.").into_response(), + } +} + +async fn attach_run_events( + _auth: AuthenticatedService, + State(state): State>, + Path(id): Path, + Query(params): Query, +) -> Response { + let id = match parse_run_id_path(&id) { + Ok(id) => id, + Err(response) => return response, + }; + { + let runs = state.runs.lock().expect("runs lock poisoned"); + let Some(managed_run) = runs.get(&id) else { + return ApiError::not_found("Run not found.").into_response(); + }; + if !matches!( + managed_run.status, + RunStatus::Queued | RunStatus::Starting | RunStatus::Running | RunStatus::Paused + ) { + return ApiError::new(StatusCode::GONE, "Run is not live on this server.") + .into_response(); + } + } + + let Ok(run_store) = state.store.open_run_reader(&id).await else { + return ApiError::not_found("Run not found.").into_response(); + }; + let start_seq = match params.since_seq { + Some(seq) if seq >= 1 => seq, + Some(_) => 1, + None => match run_store.list_events().await { + Ok(events) => events.last().map_or(1, |event| event.seq.saturating_add(1)), + Err(err) => { + return ApiError::new(StatusCode::INTERNAL_SERVER_ERROR, err.to_string()) + .into_response(); + } + }, + }; + let stream = match run_store.watch_events_from(start_seq) { + Ok(stream) => stream, + Err(err) => { + return ApiError::new(StatusCode::INTERNAL_SERVER_ERROR, err.to_string()) + .into_response(); + } + }; + let stream = stream.filter_map(|result| { + match result { + Ok(event) => { + let event = api_event_envelope_from_store(&event).ok()?; + let data = serde_json::to_string(&event).ok()?; + let data = redact_jsonl_line(&data); + Some(Ok::( + Event::default().data(data), + )) + } + Err(_) => None, } - Err(_) => None, }); Sse::new(stream).into_response() @@ -1107,6 +1328,140 @@ async fn get_checkpoint( } } +async fn write_run_blob( + _auth: AuthenticatedService, + State(state): State>, + Path(id): Path, + body: Bytes, +) -> Response { + let id = match parse_run_id_path(&id) { + Ok(id) => id, + Err(response) => return response, + }; + match state.store.open_run(&id).await { + Ok(run_store) => match run_store.write_blob(&body).await { + Ok(blob_id) => Json(WriteBlobResponse { + id: blob_id.to_string(), + }) + .into_response(), + Err(err) => ApiError::new(StatusCode::INTERNAL_SERVER_ERROR, err.to_string()) + .into_response(), + }, + Err(_) => ApiError::not_found("Run not found.").into_response(), + } +} + +async fn read_run_blob( + _auth: AuthenticatedService, + State(state): State>, + Path((id, blob_id)): Path<(String, String)>, +) -> Response { + let id = match parse_run_id_path(&id) { + Ok(id) => id, + Err(response) => return response, + }; + let blob_id = match parse_blob_id_path(&blob_id) { + Ok(blob_id) => blob_id, + Err(response) => return response, + }; + match state.store.open_run_reader(&id).await { + Ok(run_store) => match run_store.read_blob(&blob_id).await { + Ok(Some(bytes)) => octet_stream_response(bytes), + Ok(None) => ApiError::not_found("Blob not found.").into_response(), + Err(err) => ApiError::new(StatusCode::INTERNAL_SERVER_ERROR, err.to_string()) + .into_response(), + }, + Err(_) => ApiError::not_found("Run not found.").into_response(), + } +} + +async fn list_stage_artifacts( + _auth: AuthenticatedService, + State(state): State>, + Path((id, stage_id)): Path<(String, String)>, +) -> Response { + let id = match parse_run_id_path(&id) { + Ok(id) => id, + Err(response) => return response, + }; + let stage_id = match parse_stage_id_path(&stage_id) { + Ok(stage_id) => stage_id, + Err(response) => return response, + }; + match state.store.open_run_reader(&id).await { + Ok(run_store) => match run_store.list_artifacts_for_stage(&stage_id).await { + Ok(filenames) => Json(ArtifactListResponse { + data: filenames + .into_iter() + .map(|filename| ArtifactEntry { filename }) + .collect(), + }) + .into_response(), + Err(err) => ApiError::new(StatusCode::INTERNAL_SERVER_ERROR, err.to_string()) + .into_response(), + }, + Err(_) => ApiError::not_found("Run not found.").into_response(), + } +} + +async fn put_stage_artifact( + _auth: AuthenticatedService, + State(state): State>, + Path((id, stage_id)): Path<(String, String)>, + Query(params): Query, + body: Bytes, +) -> Response { + let id = match parse_run_id_path(&id) { + Ok(id) => id, + Err(response) => return response, + }; + let stage_id = match parse_stage_id_path(&stage_id) { + Ok(stage_id) => stage_id, + Err(response) => return response, + }; + let filename = match required_filename(params) { + Ok(filename) => filename, + Err(response) => return response, + }; + match state.store.open_run(&id).await { + Ok(run_store) => match run_store.put_artifact(&stage_id, &filename, &body).await { + Ok(()) => StatusCode::NO_CONTENT.into_response(), + Err(err) => ApiError::new(StatusCode::INTERNAL_SERVER_ERROR, err.to_string()) + .into_response(), + }, + Err(_) => ApiError::not_found("Run not found.").into_response(), + } +} + +async fn get_stage_artifact( + _auth: AuthenticatedService, + State(state): State>, + Path((id, stage_id)): Path<(String, String)>, + Query(params): Query, +) -> Response { + let id = match parse_run_id_path(&id) { + Ok(id) => id, + Err(response) => return response, + }; + let stage_id = match parse_stage_id_path(&stage_id) { + Ok(stage_id) => stage_id, + Err(response) => return response, + }; + let filename = match required_filename(params) { + Ok(filename) => filename, + Err(response) => return response, + }; + match state.store.open_run_reader(&id).await { + Ok(run_store) => match run_store.get_artifact(&stage_id, &filename).await { + Ok(Some(bytes)) => octet_stream_response(bytes), + Ok(None) => ApiError::not_found("Artifact not found.").into_response(), + Err(err) => ApiError::new(StatusCode::INTERNAL_SERVER_ERROR, err.to_string()) + .into_response(), + }, + Err(_) => ApiError::not_found("Run not found.").into_response(), + } +} + async fn cancel_run( _auth: AuthenticatedService, State(state): State>, @@ -2031,6 +2386,105 @@ mod tests { assert_eq!(response.status(), StatusCode::NOT_FOUND); } + #[tokio::test] + async fn get_run_state_returns_projection() { + let state = create_app_state(); + let app = build_router(Arc::clone(&state), AuthMode::Disabled); + + let req = Request::builder() + .method("POST") + .uri(api("/runs")) + .header("content-type", "application/json") + .body(Body::from( + serde_json::to_string(&serde_json::json!({"dot_source": MINIMAL_DOT})).unwrap(), + )) + .unwrap(); + + let response = app.clone().oneshot(req).await.unwrap(); + let body = body_json(response.into_body()).await; + let run_id = body["id"].as_str().unwrap(); + + let req = Request::builder() + .method("GET") + .uri(api(&format!("/runs/{run_id}/state"))) + .body(Body::empty()) + .unwrap(); + + let response = app.oneshot(req).await.unwrap(); + assert_eq!(response.status(), StatusCode::OK); + let body = body_json(response.into_body()).await; + assert!(body["nodes"].is_object()); + } + + #[tokio::test] + async fn list_run_events_returns_paginated_json() { + let state = create_app_state(); + let app = build_router(Arc::clone(&state), AuthMode::Disabled); + + let req = Request::builder() + .method("POST") + .uri(api("/runs")) + .header("content-type", "application/json") + .body(Body::from( + serde_json::to_string(&serde_json::json!({"dot_source": MINIMAL_DOT})).unwrap(), + )) + .unwrap(); + + let response = app.clone().oneshot(req).await.unwrap(); + let body = body_json(response.into_body()).await; + let run_id = body["id"].as_str().unwrap(); + + let req = Request::builder() + .method("GET") + .uri(api(&format!("/runs/{run_id}/events?since_seq=1&limit=5"))) + .body(Body::empty()) + .unwrap(); + + let response = app.oneshot(req).await.unwrap(); + assert_eq!(response.status(), StatusCode::OK); + let body = body_json(response.into_body()).await; + assert!(body["data"].is_array()); + assert!(body["meta"]["has_more"].is_boolean()); + } + + #[tokio::test] + async fn append_run_event_rejects_run_id_mismatch() { + let state = create_app_state(); + let app = build_router(Arc::clone(&state), AuthMode::Disabled); + + let req = Request::builder() + .method("POST") + .uri(api("/runs")) + .header("content-type", "application/json") + .body(Body::from( + serde_json::to_string(&serde_json::json!({"dot_source": MINIMAL_DOT})).unwrap(), + )) + .unwrap(); + + let response = app.clone().oneshot(req).await.unwrap(); + let body = body_json(response.into_body()).await; + let run_id = body["id"].as_str().unwrap(); + + let req = Request::builder() + .method("POST") + .uri(api(&format!("/runs/{run_id}/events"))) + .header("content-type", "application/json") + .body(Body::from( + serde_json::json!({ + "id": "evt-test", + "ts": "2026-03-27T12:00:00Z", + "run_id": fixtures::RUN_64.to_string(), + "event": "run.submitted", + "properties": {} + }) + .to_string(), + )) + .unwrap(); + + let response = app.oneshot(req).await.unwrap(); + assert_eq!(response.status(), StatusCode::BAD_REQUEST); + } + #[tokio::test] async fn get_checkpoint_returns_null_initially() { let state = create_app_state(); @@ -2061,6 +2515,99 @@ mod tests { assert_eq!(response.status(), StatusCode::OK); } + #[tokio::test] + async fn write_and_read_run_blob_round_trip() { + let state = create_app_state(); + let app = build_router(Arc::clone(&state), AuthMode::Disabled); + + let req = Request::builder() + .method("POST") + .uri(api("/runs")) + .header("content-type", "application/json") + .body(Body::from( + serde_json::to_string(&serde_json::json!({"dot_source": MINIMAL_DOT})).unwrap(), + )) + .unwrap(); + + let response = app.clone().oneshot(req).await.unwrap(); + let body = body_json(response.into_body()).await; + let run_id = body["id"].as_str().unwrap(); + + let req = Request::builder() + .method("POST") + .uri(api(&format!("/runs/{run_id}/blobs"))) + .header("content-type", "application/octet-stream") + .body(Body::from("hello blob")) + .unwrap(); + let response = app.clone().oneshot(req).await.unwrap(); + assert_eq!(response.status(), StatusCode::OK); + let body = body_json(response.into_body()).await; + let blob_id = body["id"].as_str().unwrap(); + + let req = Request::builder() + .method("GET") + .uri(api(&format!("/runs/{run_id}/blobs/{blob_id}"))) + .body(Body::empty()) + .unwrap(); + let response = app.oneshot(req).await.unwrap(); + assert_eq!(response.status(), StatusCode::OK); + let bytes = to_bytes(response.into_body(), usize::MAX).await.unwrap(); + assert_eq!(&bytes[..], b"hello blob"); + } + + #[tokio::test] + async fn stage_artifacts_round_trip() { + let state = create_app_state(); + let app = build_router(Arc::clone(&state), AuthMode::Disabled); + + let req = Request::builder() + .method("POST") + .uri(api("/runs")) + .header("content-type", "application/json") + .body(Body::from( + serde_json::to_string(&serde_json::json!({"dot_source": MINIMAL_DOT})).unwrap(), + )) + .unwrap(); + + let response = app.clone().oneshot(req).await.unwrap(); + let body = body_json(response.into_body()).await; + let run_id = body["id"].as_str().unwrap(); + let stage_id = "code@2"; + + let req = Request::builder() + .method("POST") + .uri(api(&format!( + "/runs/{run_id}/stages/{stage_id}/artifacts?filename=src/lib.rs" + ))) + .header("content-type", "application/octet-stream") + .body(Body::from("fn main() {}")) + .unwrap(); + let response = app.clone().oneshot(req).await.unwrap(); + assert_eq!(response.status(), StatusCode::NO_CONTENT); + + let req = Request::builder() + .method("GET") + .uri(api(&format!("/runs/{run_id}/stages/{stage_id}/artifacts"))) + .body(Body::empty()) + .unwrap(); + let response = app.clone().oneshot(req).await.unwrap(); + assert_eq!(response.status(), StatusCode::OK); + let body = body_json(response.into_body()).await; + assert_eq!(body["data"][0]["filename"], "src/lib.rs"); + + let req = Request::builder() + .method("GET") + .uri(api(&format!( + "/runs/{run_id}/stages/{stage_id}/artifacts/download?filename=src/lib.rs" + ))) + .body(Body::empty()) + .unwrap(); + let response = app.oneshot(req).await.unwrap(); + assert_eq!(response.status(), StatusCode::OK); + let bytes = to_bytes(response.into_body(), usize::MAX).await.unwrap(); + assert_eq!(&bytes[..], b"fn main() {}"); + } + #[tokio::test] async fn create_run_returns_submitted() { let state = create_app_state(); @@ -2198,25 +2745,23 @@ mod tests { } #[tokio::test(flavor = "multi_thread", worker_threads = 2)] - async fn get_events_returns_sse_stream() { + async fn attach_run_events_returns_sse_stream() { let state = create_app_state(); let app = test_app_with_scheduler(state); let run_id_str = create_and_start_run(&app, MINIMAL_DOT).await; let run_id = run_id_str.parse::().unwrap(); - // Wait for scheduler to promote run (creates event_tx) + // Wait for scheduler to promote run. tokio::time::sleep(std::time::Duration::from_millis(100)).await; - // Request the SSE stream let req = Request::builder() .method("GET") - .uri(api(&format!("/runs/{run_id}/events"))) + .uri(api(&format!("/runs/{run_id}/attach"))) .body(Body::empty()) .unwrap(); let response = app.oneshot(req).await.unwrap(); - // May be 200 (stream open) or 410 (run completed before we connect) let status = response.status(); assert!( status == StatusCode::OK || status == StatusCode::GONE, @@ -2714,7 +3259,7 @@ mod tests { let req = Request::builder() .method("GET") - .uri(api(&format!("/runs/{run_id}/events"))) + .uri(api(&format!("/runs/{run_id}/attach"))) .body(Body::empty()) .unwrap(); let response = app.oneshot(req).await.unwrap(); diff --git a/lib/crates/fabro-server/tests/it/api.rs b/lib/crates/fabro-server/tests/it/api.rs index 9e1381f18..eb1aca048 100644 --- a/lib/crates/fabro-server/tests/it/api.rs +++ b/lib/crates/fabro-server/tests/it/api.rs @@ -757,7 +757,7 @@ mod sse_events { // Get SSE stream let req = Request::builder() .method("GET") - .uri(api(&format!("/runs/{run_id}/events"))) + .uri(api(&format!("/runs/{run_id}/attach"))) .body(Body::empty()) .unwrap(); let response = app.clone().oneshot(req).await.unwrap(); @@ -796,7 +796,7 @@ mod sse_events { 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["event"].as_str() { + if let Some(event_name) = event["payload"]["event"].as_str() { event_types.push(event_name.to_string()); } } diff --git a/lib/crates/fabro-store/src/run_state.rs b/lib/crates/fabro-store/src/run_state.rs index eae6a5ab1..f813b5e69 100644 --- a/lib/crates/fabro-store/src/run_state.rs +++ b/lib/crates/fabro-store/src/run_state.rs @@ -16,7 +16,7 @@ use fabro_types::{ SandboxRecord, StageStatus, StageUsage, StartRecord, StatusReason, TokenUsage, }; -#[derive(Debug, Clone, Default)] +#[derive(Debug, Clone, Default, serde::Serialize)] pub struct RunProjection { pub run: Option, pub graph_source: Option, @@ -34,7 +34,7 @@ pub struct RunProjection { nodes: HashMap, } -#[derive(Debug, Clone, Default)] +#[derive(Debug, Clone, Default, serde::Serialize)] pub struct NodeState { pub prompt: Option, pub response: Option, diff --git a/lib/crates/fabro-store/src/slate/mod.rs b/lib/crates/fabro-store/src/slate/mod.rs index e3e9f885c..8e1b66ebd 100644 --- a/lib/crates/fabro-store/src/slate/mod.rs +++ b/lib/crates/fabro-store/src/slate/mod.rs @@ -1231,6 +1231,57 @@ mod tests { ); } + #[tokio::test] + async fn slate_run_store_lists_events_with_limit() { + let (_object_store, store) = make_store(); + let run = store.create_run(&test_run_id("run-1")).await.unwrap(); + + for (idx, ts) in [ + "2026-03-27T12:00:00Z", + "2026-03-27T12:00:01Z", + "2026-03-27T12:00:02Z", + ] + .into_iter() + .enumerate() + { + run.append_event(&event_payload( + "run-1", + ts, + "run.submitted", + None, + serde_json::json!({"index": idx}), + )) + .await + .unwrap(); + } + + let events = run.list_events_from_with_limit(2, 1).await.unwrap(); + assert_eq!(events.iter().map(|event| event.seq).collect::>(), vec![2, 3]); + } + + #[tokio::test] + async fn slate_run_store_lists_artifacts_for_stage_only() { + let (_object_store, store) = make_store(); + let run = store.create_run(&test_run_id("run-1")).await.unwrap(); + let code_stage = StageId::new("code", 2); + let build_stage = StageId::new("build", 1); + + run.put_artifact(&code_stage, "src/lib.rs", b"fn main() {}") + .await + .unwrap(); + run.put_artifact(&code_stage, "src/main.rs", b"fn main() {}") + .await + .unwrap(); + run.put_artifact(&build_stage, "target/output.txt", b"ok") + .await + .unwrap(); + + assert_eq!( + run.list_artifacts_for_stage(&code_stage).await.unwrap(), + vec!["src/lib.rs".to_string(), "src/main.rs".to_string()] + ); + } + #[tokio::test] async fn create_run_state_and_node_storage_round_trip() { let (_object_store, store) = make_store(); diff --git a/lib/crates/fabro-store/src/slate/run_store.rs b/lib/crates/fabro-store/src/slate/run_store.rs index fe5b8a500..9d70a78b4 100644 --- a/lib/crates/fabro-store/src/slate/run_store.rs +++ b/lib/crates/fabro-store/src/slate/run_store.rs @@ -175,6 +175,17 @@ impl SlateRunStore { self.inner.db.list_events_from(1).await } + pub async fn list_events_from_with_limit( + &self, + start_seq: u32, + limit: usize, + ) -> Result> { + self.inner + .db + .list_events_from_with_limit(start_seq, limit) + .await + } + pub fn watch_events_from( &self, seq: u32, @@ -245,6 +256,10 @@ impl SlateRunStore { self.inner.db.list_all_artifacts().await } + pub async fn list_artifacts_for_stage(&self, stage_id: &StageId) -> Result> { + self.inner.db.list_artifacts_for_stage(stage_id).await + } + pub async fn state(&self) -> Result { self.projected_state().await } @@ -287,6 +302,17 @@ impl SlateRunDb { } } + async fn list_events_from_with_limit( + &self, + start_seq: u32, + limit: usize, + ) -> Result> { + match self { + Self::Writer(db) => list_events_from_with_limit(db, start_seq, limit).await, + Self::Reader(db) => list_events_from_with_limit(db.as_ref(), start_seq, limit).await, + } + } + async fn list_blobs(&self) -> Result> { match self { Self::Writer(db) => list_blobs(db).await, @@ -300,6 +326,13 @@ impl SlateRunDb { Self::Reader(db) => list_all_artifacts(db.as_ref()).await, } } + + async fn list_artifacts_for_stage(&self, stage_id: &StageId) -> Result> { + match self { + Self::Writer(db) => list_artifacts_for_stage(db, stage_id).await, + Self::Reader(db) => list_artifacts_for_stage(db.as_ref(), stage_id).await, + } + } } async fn put_json(db: &slatedb::Db, key: &str, value: &T) -> Result<()> { @@ -366,6 +399,19 @@ where Ok(events) } +async fn list_events_from_with_limit( + db: &R, + start_seq: u32, + limit: usize, +) -> Result> +where + R: DbRead + Sync, +{ + let mut events = list_events_from(db, start_seq).await?; + events.truncate(limit.saturating_add(1)); + Ok(events) +} + async fn list_blobs(db: &R) -> Result> where R: DbRead + Sync, @@ -402,6 +448,26 @@ where Ok(assets) } +async fn list_artifacts_for_stage(db: &R, stage_id: &StageId) -> Result> +where + R: DbRead + Sync, +{ + let prefix = keys::node_artifact_prefix(stage_id); + let mut iter = db.scan_prefix(prefix.as_bytes()).await?; + let mut filenames = Vec::new(); + while let Some(entry) = iter.next().await? { + let key = key_to_string(&entry.key)?; + let Some((node, filename)) = keys::parse_node_artifact_key(&key) else { + continue; + }; + if &node == stage_id { + filenames.push(filename); + } + } + filenames.sort(); + Ok(filenames) +} + fn key_to_string(key: &Bytes) -> Result { String::from_utf8(key.to_vec()) .map_err(|err| StoreError::Other(format!("stored key is not valid UTF-8: {err}"))) diff --git a/lib/packages/fabro-api-client/src/.openapi-generator/FILES b/lib/packages/fabro-api-client/src/.openapi-generator/FILES index d29350288..4c33c32bf 100644 --- a/lib/packages/fabro-api-client/src/.openapi-generator/FILES +++ b/lib/packages/fabro-api-client/src/.openapi-generator/FILES @@ -22,6 +22,9 @@ models/aggregate-usage.ts models/api-question-option.ts models/api-question.ts models/api-settings.ts +models/append-event-response.ts +models/artifact-entry.ts +models/artifact-list-response.ts models/artifacts-settings.ts models/assistant-stage-turn.ts models/assistant-turn.ts @@ -55,6 +58,7 @@ models/diff-file.ts models/diff-stats.ts models/error-response-entry.ts models/error-response.ts +models/event-envelope.ts models/execute-query-request.ts models/execute-query-response-rows-inner-inner.ts models/execute-query-response.ts @@ -70,6 +74,8 @@ models/health-response.ts models/history-entry.ts models/hook-definition.ts models/index.ts +models/internal-run-status.ts +models/internal-stage-status.ts models/learning-category.ts models/learning.ts models/llm-settings.ts @@ -82,9 +88,12 @@ models/model-limits.ts models/model-reference.ts models/model-test-result.ts models/model.ts +models/node-state.ts +models/node-status-record.ts models/open-item-kind.ts models/open-item.ts models/paginated-api-question-list.ts +models/paginated-event-list.ts models/paginated-history-entry-list.ts models/paginated-model-list.ts models/paginated-retro-list.ts @@ -113,13 +122,17 @@ models/root-response-urls.ts models/root-response.ts models/run-checkpoint.ts models/run-error.ts +models/run-event.ts models/run-list-item.ts +models/run-projection-checkpoints-inner-inner.ts +models/run-projection.ts models/run-pull-request.ts models/run-question.ts models/run-reference.ts models/run-sandbox.ts models/run-settings.ts models/run-stage.ts +models/run-status-record.ts models/run-status-response.ts models/run-status.ts models/run-timings.ts @@ -144,6 +157,7 @@ models/smoothness-rating.ts models/stage-retro.ts models/stage-status.ts models/stage-turn.ts +models/status-reason.ts models/steer-request.ts models/submit-answer-request.ts models/system-stage-turn.ts @@ -173,3 +187,4 @@ models/workflow-last-run.ts models/workflow-list-item.ts models/workflow-reference.ts models/workflow-schedule.ts +models/write-blob-response.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 70bed7fb7..535b5ea76 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 @@ -22,20 +22,268 @@ import { DUMMY_BASE_URL, assertParamExists, setApiKeyToObject, setBasicAuthToObj // @ts-ignore import { BASE_PATH, COLLECTION_FORMATS, type RequestArgs, BaseAPI, RequiredError, operationServerMap } from '../base'; // @ts-ignore +import type { AppendEventResponse } from '../models'; +// @ts-ignore +import type { ArtifactListResponse } from '../models'; +// @ts-ignore import type { ErrorResponse } from '../models'; // @ts-ignore +import type { PaginatedEventList } from '../models'; +// @ts-ignore import type { PaginatedRunStageList } from '../models'; // @ts-ignore import type { PaginatedStageTurnList } from '../models'; // @ts-ignore import type { RunCheckpoint } from '../models'; // @ts-ignore +import type { RunEvent } from '../models'; +// @ts-ignore +import type { RunProjection } from '../models'; +// @ts-ignore import type { RunSettings } from '../models'; +// @ts-ignore +import type { WriteBlobResponse } from '../models'; /** * RunInternalsApi - axios parameter creator */ export const RunInternalsApiAxiosParamCreator = function (configuration?: Configuration) { return { + /** + * Appends a validated event to the run event log. Intended for trusted internal callers. + * @summary Append Run Event + * @param {string} id Unique run identifier (ULID). + * @param {RunEvent} runEvent + * @param {*} [options] Override http request option. + * @throws {RequiredError} + */ + appendRunEvent: async (id: string, runEvent: RunEvent, options: RawAxiosRequestConfig = {}): Promise => { + // verify required parameter 'id' is not null or undefined + assertParamExists('appendRunEvent', 'id', id) + // verify required parameter 'runEvent' is not null or undefined + assertParamExists('appendRunEvent', 'runEvent', runEvent) + const localVarPath = `/api/v1/runs/{id}/events` + .replace(`{${"id"}}`, encodeURIComponent(String(id))); + // use dummy base URL string because the URL constructor only accepts absolute URLs. + const localVarUrlObj = new URL(localVarPath, DUMMY_BASE_URL); + let baseOptions; + if (configuration) { + baseOptions = configuration.baseOptions; + } + + const localVarRequestOptions = { method: 'POST', ...baseOptions, ...options}; + const localVarHeaderParameter = {} as any; + const localVarQueryParameter = {} as any; + + // authentication mTLS required + await setApiKeyToObject(localVarHeaderParameter, "X-mTLS-Client-CN", configuration) + + // authentication BearerAuth required + // http bearer authentication required + await setBearerAuthToObject(localVarHeaderParameter, configuration) + + localVarHeaderParameter['Content-Type'] = 'application/json'; + localVarHeaderParameter['Accept'] = 'application/json'; + + setSearchParams(localVarUrlObj, localVarQueryParameter); + let headersFromBaseOptions = baseOptions && baseOptions.headers ? baseOptions.headers : {}; + localVarRequestOptions.headers = {...localVarHeaderParameter, ...headersFromBaseOptions, ...options.headers}; + localVarRequestOptions.data = serializeDataIfNeeded(runEvent, localVarRequestOptions, configuration) + + return { + url: toPathString(localVarUrlObj), + options: localVarRequestOptions, + }; + }, + /** + * Opens a server-sent event stream for a live run. Optionally replays stored events from `since_seq` before switching to live updates. + * @summary Attach Run Events + * @param {string} id Unique run identifier (ULID). + * @param {number} [sinceSeq] First event sequence number to include. + * @param {*} [options] Override http request option. + * @throws {RequiredError} + */ + attachRunEvents: async (id: string, sinceSeq?: number, options: RawAxiosRequestConfig = {}): Promise => { + // verify required parameter 'id' is not null or undefined + assertParamExists('attachRunEvents', 'id', id) + const localVarPath = `/api/v1/runs/{id}/attach` + .replace(`{${"id"}}`, encodeURIComponent(String(id))); + // use dummy base URL string because the URL constructor only accepts absolute URLs. + const localVarUrlObj = new URL(localVarPath, DUMMY_BASE_URL); + let baseOptions; + if (configuration) { + baseOptions = configuration.baseOptions; + } + + const localVarRequestOptions = { method: 'GET', ...baseOptions, ...options}; + const localVarHeaderParameter = {} as any; + const localVarQueryParameter = {} as any; + + // authentication mTLS required + await setApiKeyToObject(localVarHeaderParameter, "X-mTLS-Client-CN", configuration) + + // authentication BearerAuth required + // http bearer authentication required + await setBearerAuthToObject(localVarHeaderParameter, configuration) + + if (sinceSeq !== undefined) { + localVarQueryParameter['since_seq'] = sinceSeq; + } + + localVarHeaderParameter['Accept'] = 'text/event-stream,application/json'; + + setSearchParams(localVarUrlObj, localVarQueryParameter); + let headersFromBaseOptions = baseOptions && baseOptions.headers ? baseOptions.headers : {}; + localVarRequestOptions.headers = {...localVarHeaderParameter, ...headersFromBaseOptions, ...options.headers}; + + return { + url: toPathString(localVarUrlObj), + options: localVarRequestOptions, + }; + }, + /** + * Returns the internal event-sourced run projection. This is not a stable public contract. + * @summary Get Run State + * @param {string} id Unique run identifier (ULID). + * @param {*} [options] Override http request option. + * @throws {RequiredError} + */ + getRunState: async (id: string, options: RawAxiosRequestConfig = {}): Promise => { + // verify required parameter 'id' is not null or undefined + assertParamExists('getRunState', 'id', id) + const localVarPath = `/api/v1/runs/{id}/state` + .replace(`{${"id"}}`, encodeURIComponent(String(id))); + // use dummy base URL string because the URL constructor only accepts absolute URLs. + const localVarUrlObj = new URL(localVarPath, DUMMY_BASE_URL); + let baseOptions; + if (configuration) { + baseOptions = configuration.baseOptions; + } + + const localVarRequestOptions = { method: 'GET', ...baseOptions, ...options}; + const localVarHeaderParameter = {} as any; + const localVarQueryParameter = {} as any; + + // authentication mTLS required + await setApiKeyToObject(localVarHeaderParameter, "X-mTLS-Client-CN", configuration) + + // authentication BearerAuth required + // http bearer authentication required + await setBearerAuthToObject(localVarHeaderParameter, configuration) + + localVarHeaderParameter['Accept'] = 'application/json'; + + setSearchParams(localVarUrlObj, localVarQueryParameter); + let headersFromBaseOptions = baseOptions && baseOptions.headers ? baseOptions.headers : {}; + localVarRequestOptions.headers = {...localVarHeaderParameter, ...headersFromBaseOptions, ...options.headers}; + + return { + url: toPathString(localVarUrlObj), + options: localVarRequestOptions, + }; + }, + /** + * Downloads an artifact by filename. + * @summary Get 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`. + * @param {string} filename Artifact filename. May contain path separators. + * @param {*} [options] Override http request option. + * @throws {RequiredError} + */ + getStageArtifact: async (id: string, stageId: string, filename: string, options: RawAxiosRequestConfig = {}): Promise => { + // verify required parameter 'id' is not null or undefined + assertParamExists('getStageArtifact', 'id', id) + // verify required parameter 'stageId' is not null or undefined + assertParamExists('getStageArtifact', 'stageId', stageId) + // verify required parameter 'filename' is not null or undefined + assertParamExists('getStageArtifact', 'filename', filename) + const localVarPath = `/api/v1/runs/{id}/stages/{stageId}/artifacts/download` + .replace(`{${"id"}}`, encodeURIComponent(String(id))) + .replace(`{${"stageId"}}`, encodeURIComponent(String(stageId))); + // use dummy base URL string because the URL constructor only accepts absolute URLs. + const localVarUrlObj = new URL(localVarPath, DUMMY_BASE_URL); + let baseOptions; + if (configuration) { + baseOptions = configuration.baseOptions; + } + + const localVarRequestOptions = { method: 'GET', ...baseOptions, ...options}; + const localVarHeaderParameter = {} as any; + const localVarQueryParameter = {} as any; + + // authentication mTLS required + await setApiKeyToObject(localVarHeaderParameter, "X-mTLS-Client-CN", configuration) + + // authentication BearerAuth required + // http bearer authentication required + await setBearerAuthToObject(localVarHeaderParameter, configuration) + + if (filename !== undefined) { + localVarQueryParameter['filename'] = filename; + } + + localVarHeaderParameter['Accept'] = 'application/octet-stream,application/json'; + + setSearchParams(localVarUrlObj, localVarQueryParameter); + let headersFromBaseOptions = baseOptions && baseOptions.headers ? baseOptions.headers : {}; + localVarRequestOptions.headers = {...localVarHeaderParameter, ...headersFromBaseOptions, ...options.headers}; + + return { + url: toPathString(localVarUrlObj), + options: localVarRequestOptions, + }; + }, + /** + * Returns a paginated JSON list of stored run events. + * @summary List Run Events + * @param {string} id Unique run identifier (ULID). + * @param {number} [sinceSeq] First event sequence number to include. + * @param {number} [limit] Maximum number of events to return. + * @param {*} [options] Override http request option. + * @throws {RequiredError} + */ + listRunEvents: async (id: string, sinceSeq?: number, limit?: number, options: RawAxiosRequestConfig = {}): Promise => { + // verify required parameter 'id' is not null or undefined + assertParamExists('listRunEvents', 'id', id) + const localVarPath = `/api/v1/runs/{id}/events` + .replace(`{${"id"}}`, encodeURIComponent(String(id))); + // use dummy base URL string because the URL constructor only accepts absolute URLs. + const localVarUrlObj = new URL(localVarPath, DUMMY_BASE_URL); + let baseOptions; + if (configuration) { + baseOptions = configuration.baseOptions; + } + + const localVarRequestOptions = { method: 'GET', ...baseOptions, ...options}; + const localVarHeaderParameter = {} as any; + const localVarQueryParameter = {} as any; + + // authentication mTLS required + await setApiKeyToObject(localVarHeaderParameter, "X-mTLS-Client-CN", configuration) + + // authentication BearerAuth required + // http bearer authentication required + await setBearerAuthToObject(localVarHeaderParameter, configuration) + + if (sinceSeq !== undefined) { + localVarQueryParameter['since_seq'] = sinceSeq; + } + + if (limit !== undefined) { + localVarQueryParameter['limit'] = limit; + } + + localVarHeaderParameter['Accept'] = 'application/json'; + + setSearchParams(localVarUrlObj, localVarQueryParameter); + let headersFromBaseOptions = baseOptions && baseOptions.headers ? baseOptions.headers : {}; + localVarRequestOptions.headers = {...localVarHeaderParameter, ...headersFromBaseOptions, ...options.headers}; + + return { + url: toPathString(localVarUrlObj), + options: localVarRequestOptions, + }; + }, /** * Returns the ordered list of stages in a run\'s workflow graph with their current status and timing. Stages are bounded by the workflow graph size, typically fewer than 20. * @summary List Run Stages @@ -87,11 +335,56 @@ export const RunInternalsApiAxiosParamCreator = function (configuration?: Config options: localVarRequestOptions, }; }, + /** + * Lists artifact filenames stored for a stage. + * @summary List Stage Artifacts + * @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`. + * @param {*} [options] Override http request option. + * @throws {RequiredError} + */ + listStageArtifacts: async (id: string, stageId: string, options: RawAxiosRequestConfig = {}): Promise => { + // verify required parameter 'id' is not null or undefined + assertParamExists('listStageArtifacts', 'id', id) + // verify required parameter 'stageId' is not null or undefined + assertParamExists('listStageArtifacts', 'stageId', stageId) + const localVarPath = `/api/v1/runs/{id}/stages/{stageId}/artifacts` + .replace(`{${"id"}}`, encodeURIComponent(String(id))) + .replace(`{${"stageId"}}`, encodeURIComponent(String(stageId))); + // use dummy base URL string because the URL constructor only accepts absolute URLs. + const localVarUrlObj = new URL(localVarPath, DUMMY_BASE_URL); + let baseOptions; + if (configuration) { + baseOptions = configuration.baseOptions; + } + + const localVarRequestOptions = { method: 'GET', ...baseOptions, ...options}; + const localVarHeaderParameter = {} as any; + const localVarQueryParameter = {} as any; + + // authentication mTLS required + await setApiKeyToObject(localVarHeaderParameter, "X-mTLS-Client-CN", configuration) + + // authentication BearerAuth required + // http bearer authentication required + await setBearerAuthToObject(localVarHeaderParameter, configuration) + + localVarHeaderParameter['Accept'] = 'application/json'; + + setSearchParams(localVarUrlObj, localVarQueryParameter); + let headersFromBaseOptions = baseOptions && baseOptions.headers ? baseOptions.headers : {}; + localVarRequestOptions.headers = {...localVarHeaderParameter, ...headersFromBaseOptions, ...options.headers}; + + return { + url: toPathString(localVarUrlObj), + options: localVarRequestOptions, + }; + }, /** * Returns a paginated list of conversation turns within a specific stage, including system prompts, assistant responses, and tool invocations. * @summary List Stage Turns * @param {string} id Unique run identifier (ULID). - * @param {string} stageId Identifier of a stage within a run\'s workflow graph. + * @param {string} stageId Identifier of a stage within a run\'s workflow graph, serialized as `node_id@visit`. * @param {number} [pageLimit] Maximum number of items to return per page. * @param {number} [pageOffset] Number of items to skip before returning results. * @param {*} [options] Override http request option. @@ -142,6 +435,108 @@ export const RunInternalsApiAxiosParamCreator = function (configuration?: Config options: localVarRequestOptions, }; }, + /** + * Uploads an artifact for a stage. Intended for trusted internal callers. + * @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`. + * @param {string} filename Artifact filename. May contain path separators. + * @param {File} body + * @param {*} [options] Override http request option. + * @throws {RequiredError} + */ + putStageArtifact: async (id: string, stageId: string, filename: string, body: File, options: RawAxiosRequestConfig = {}): Promise => { + // verify required parameter 'id' is not null or undefined + assertParamExists('putStageArtifact', 'id', id) + // verify required parameter 'stageId' is not null or undefined + assertParamExists('putStageArtifact', 'stageId', stageId) + // verify required parameter 'filename' is not null or undefined + assertParamExists('putStageArtifact', 'filename', filename) + // verify required parameter 'body' is not null or undefined + assertParamExists('putStageArtifact', 'body', body) + const localVarPath = `/api/v1/runs/{id}/stages/{stageId}/artifacts` + .replace(`{${"id"}}`, encodeURIComponent(String(id))) + .replace(`{${"stageId"}}`, encodeURIComponent(String(stageId))); + // use dummy base URL string because the URL constructor only accepts absolute URLs. + const localVarUrlObj = new URL(localVarPath, DUMMY_BASE_URL); + let baseOptions; + if (configuration) { + baseOptions = configuration.baseOptions; + } + + const localVarRequestOptions = { method: 'POST', ...baseOptions, ...options}; + const localVarHeaderParameter = {} as any; + const localVarQueryParameter = {} as any; + + // authentication mTLS required + await setApiKeyToObject(localVarHeaderParameter, "X-mTLS-Client-CN", configuration) + + // authentication BearerAuth required + // http bearer authentication required + await setBearerAuthToObject(localVarHeaderParameter, configuration) + + if (filename !== undefined) { + localVarQueryParameter['filename'] = filename; + } + + localVarHeaderParameter['Content-Type'] = 'application/octet-stream'; + localVarHeaderParameter['Accept'] = 'application/json'; + + setSearchParams(localVarUrlObj, localVarQueryParameter); + let headersFromBaseOptions = baseOptions && baseOptions.headers ? baseOptions.headers : {}; + localVarRequestOptions.headers = {...localVarHeaderParameter, ...headersFromBaseOptions, ...options.headers}; + localVarRequestOptions.data = serializeDataIfNeeded(body, localVarRequestOptions, configuration) + + return { + url: toPathString(localVarUrlObj), + options: localVarRequestOptions, + }; + }, + /** + * Reads a previously stored blob by identifier. + * @summary Read Run Blob + * @param {string} id Unique run identifier (ULID). + * @param {string} blobId Content-addressed blob identifier. + * @param {*} [options] Override http request option. + * @throws {RequiredError} + */ + readRunBlob: async (id: string, blobId: string, options: RawAxiosRequestConfig = {}): Promise => { + // verify required parameter 'id' is not null or undefined + assertParamExists('readRunBlob', 'id', id) + // verify required parameter 'blobId' is not null or undefined + assertParamExists('readRunBlob', 'blobId', blobId) + const localVarPath = `/api/v1/runs/{id}/blobs/{blobId}` + .replace(`{${"id"}}`, encodeURIComponent(String(id))) + .replace(`{${"blobId"}}`, encodeURIComponent(String(blobId))); + // use dummy base URL string because the URL constructor only accepts absolute URLs. + const localVarUrlObj = new URL(localVarPath, DUMMY_BASE_URL); + let baseOptions; + if (configuration) { + baseOptions = configuration.baseOptions; + } + + const localVarRequestOptions = { method: 'GET', ...baseOptions, ...options}; + const localVarHeaderParameter = {} as any; + const localVarQueryParameter = {} as any; + + // authentication mTLS required + await setApiKeyToObject(localVarHeaderParameter, "X-mTLS-Client-CN", configuration) + + // authentication BearerAuth required + // http bearer authentication required + await setBearerAuthToObject(localVarHeaderParameter, configuration) + + localVarHeaderParameter['Accept'] = 'application/octet-stream,application/json'; + + setSearchParams(localVarUrlObj, localVarQueryParameter); + let headersFromBaseOptions = baseOptions && baseOptions.headers ? baseOptions.headers : {}; + localVarRequestOptions.headers = {...localVarHeaderParameter, ...headersFromBaseOptions, ...options.headers}; + + return { + url: toPathString(localVarUrlObj), + options: localVarRequestOptions, + }; + }, /** * Returns the latest checkpoint data for a run, or null if no checkpoint has been recorded yet. * @summary Retrieve Run Checkpoint @@ -219,6 +614,52 @@ export const RunInternalsApiAxiosParamCreator = function (configuration?: Config let headersFromBaseOptions = baseOptions && baseOptions.headers ? baseOptions.headers : {}; localVarRequestOptions.headers = {...localVarHeaderParameter, ...headersFromBaseOptions, ...options.headers}; + return { + url: toPathString(localVarUrlObj), + options: localVarRequestOptions, + }; + }, + /** + * Writes an opaque binary blob and returns its content-addressed blob identifier. + * @summary Write Run Blob + * @param {string} id Unique run identifier (ULID). + * @param {File} body + * @param {*} [options] Override http request option. + * @throws {RequiredError} + */ + writeRunBlob: async (id: string, body: File, options: RawAxiosRequestConfig = {}): Promise => { + // verify required parameter 'id' is not null or undefined + assertParamExists('writeRunBlob', 'id', id) + // verify required parameter 'body' is not null or undefined + assertParamExists('writeRunBlob', 'body', body) + const localVarPath = `/api/v1/runs/{id}/blobs` + .replace(`{${"id"}}`, encodeURIComponent(String(id))); + // use dummy base URL string because the URL constructor only accepts absolute URLs. + const localVarUrlObj = new URL(localVarPath, DUMMY_BASE_URL); + let baseOptions; + if (configuration) { + baseOptions = configuration.baseOptions; + } + + const localVarRequestOptions = { method: 'POST', ...baseOptions, ...options}; + const localVarHeaderParameter = {} as any; + const localVarQueryParameter = {} as any; + + // authentication mTLS required + await setApiKeyToObject(localVarHeaderParameter, "X-mTLS-Client-CN", configuration) + + // authentication BearerAuth required + // http bearer authentication required + await setBearerAuthToObject(localVarHeaderParameter, configuration) + + localVarHeaderParameter['Content-Type'] = 'application/octet-stream'; + localVarHeaderParameter['Accept'] = 'application/json'; + + setSearchParams(localVarUrlObj, localVarQueryParameter); + let headersFromBaseOptions = baseOptions && baseOptions.headers ? baseOptions.headers : {}; + localVarRequestOptions.headers = {...localVarHeaderParameter, ...headersFromBaseOptions, ...options.headers}; + localVarRequestOptions.data = serializeDataIfNeeded(body, localVarRequestOptions, configuration) + return { url: toPathString(localVarUrlObj), options: localVarRequestOptions, @@ -233,6 +674,77 @@ export const RunInternalsApiAxiosParamCreator = function (configuration?: Config export const RunInternalsApiFp = function(configuration?: Configuration) { const localVarAxiosParamCreator = RunInternalsApiAxiosParamCreator(configuration) return { + /** + * Appends a validated event to the run event log. Intended for trusted internal callers. + * @summary Append Run Event + * @param {string} id Unique run identifier (ULID). + * @param {RunEvent} runEvent + * @param {*} [options] Override http request option. + * @throws {RequiredError} + */ + async appendRunEvent(id: string, runEvent: RunEvent, options?: RawAxiosRequestConfig): Promise<(axios?: AxiosInstance, basePath?: string) => AxiosPromise> { + const localVarAxiosArgs = await localVarAxiosParamCreator.appendRunEvent(id, runEvent, options); + const localVarOperationServerIndex = configuration?.serverIndex ?? 0; + const localVarOperationServerBasePath = operationServerMap['RunInternalsApi.appendRunEvent']?.[localVarOperationServerIndex]?.url; + return (axios, basePath) => createRequestFunction(localVarAxiosArgs, globalAxios, BASE_PATH, configuration)(axios, localVarOperationServerBasePath || basePath); + }, + /** + * Opens a server-sent event stream for a live run. Optionally replays stored events from `since_seq` before switching to live updates. + * @summary Attach Run Events + * @param {string} id Unique run identifier (ULID). + * @param {number} [sinceSeq] First event sequence number to include. + * @param {*} [options] Override http request option. + * @throws {RequiredError} + */ + async attachRunEvents(id: string, sinceSeq?: number, options?: RawAxiosRequestConfig): Promise<(axios?: AxiosInstance, basePath?: string) => AxiosPromise> { + const localVarAxiosArgs = await localVarAxiosParamCreator.attachRunEvents(id, sinceSeq, options); + const localVarOperationServerIndex = configuration?.serverIndex ?? 0; + const localVarOperationServerBasePath = operationServerMap['RunInternalsApi.attachRunEvents']?.[localVarOperationServerIndex]?.url; + return (axios, basePath) => createRequestFunction(localVarAxiosArgs, globalAxios, BASE_PATH, configuration)(axios, localVarOperationServerBasePath || basePath); + }, + /** + * Returns the internal event-sourced run projection. This is not a stable public contract. + * @summary Get Run State + * @param {string} id Unique run identifier (ULID). + * @param {*} [options] Override http request option. + * @throws {RequiredError} + */ + async getRunState(id: string, options?: RawAxiosRequestConfig): Promise<(axios?: AxiosInstance, basePath?: string) => AxiosPromise> { + const localVarAxiosArgs = await localVarAxiosParamCreator.getRunState(id, options); + const localVarOperationServerIndex = configuration?.serverIndex ?? 0; + const localVarOperationServerBasePath = operationServerMap['RunInternalsApi.getRunState']?.[localVarOperationServerIndex]?.url; + return (axios, basePath) => createRequestFunction(localVarAxiosArgs, globalAxios, BASE_PATH, configuration)(axios, localVarOperationServerBasePath || basePath); + }, + /** + * Downloads an artifact by filename. + * @summary Get 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`. + * @param {string} filename Artifact filename. May contain path separators. + * @param {*} [options] Override http request option. + * @throws {RequiredError} + */ + async getStageArtifact(id: string, stageId: string, filename: string, options?: RawAxiosRequestConfig): Promise<(axios?: AxiosInstance, basePath?: string) => AxiosPromise> { + const localVarAxiosArgs = await localVarAxiosParamCreator.getStageArtifact(id, stageId, filename, options); + const localVarOperationServerIndex = configuration?.serverIndex ?? 0; + const localVarOperationServerBasePath = operationServerMap['RunInternalsApi.getStageArtifact']?.[localVarOperationServerIndex]?.url; + return (axios, basePath) => createRequestFunction(localVarAxiosArgs, globalAxios, BASE_PATH, configuration)(axios, localVarOperationServerBasePath || basePath); + }, + /** + * Returns a paginated JSON list of stored run events. + * @summary List Run Events + * @param {string} id Unique run identifier (ULID). + * @param {number} [sinceSeq] First event sequence number to include. + * @param {number} [limit] Maximum number of events to return. + * @param {*} [options] Override http request option. + * @throws {RequiredError} + */ + async listRunEvents(id: string, sinceSeq?: number, limit?: number, options?: RawAxiosRequestConfig): Promise<(axios?: AxiosInstance, basePath?: string) => AxiosPromise> { + const localVarAxiosArgs = await localVarAxiosParamCreator.listRunEvents(id, sinceSeq, limit, options); + const localVarOperationServerIndex = configuration?.serverIndex ?? 0; + const localVarOperationServerBasePath = operationServerMap['RunInternalsApi.listRunEvents']?.[localVarOperationServerIndex]?.url; + return (axios, basePath) => createRequestFunction(localVarAxiosArgs, globalAxios, BASE_PATH, configuration)(axios, localVarOperationServerBasePath || basePath); + }, /** * Returns the ordered list of stages in a run\'s workflow graph with their current status and timing. Stages are bounded by the workflow graph size, typically fewer than 20. * @summary List Run Stages @@ -248,11 +760,25 @@ export const RunInternalsApiFp = function(configuration?: Configuration) { const localVarOperationServerBasePath = operationServerMap['RunInternalsApi.listRunStages']?.[localVarOperationServerIndex]?.url; return (axios, basePath) => createRequestFunction(localVarAxiosArgs, globalAxios, BASE_PATH, configuration)(axios, localVarOperationServerBasePath || basePath); }, + /** + * Lists artifact filenames stored for a stage. + * @summary List Stage Artifacts + * @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`. + * @param {*} [options] Override http request option. + * @throws {RequiredError} + */ + async listStageArtifacts(id: string, stageId: string, options?: RawAxiosRequestConfig): Promise<(axios?: AxiosInstance, basePath?: string) => AxiosPromise> { + const localVarAxiosArgs = await localVarAxiosParamCreator.listStageArtifacts(id, stageId, options); + const localVarOperationServerIndex = configuration?.serverIndex ?? 0; + const localVarOperationServerBasePath = operationServerMap['RunInternalsApi.listStageArtifacts']?.[localVarOperationServerIndex]?.url; + return (axios, basePath) => createRequestFunction(localVarAxiosArgs, globalAxios, BASE_PATH, configuration)(axios, localVarOperationServerBasePath || basePath); + }, /** * Returns a paginated list of conversation turns within a specific stage, including system prompts, assistant responses, and tool invocations. * @summary List Stage Turns * @param {string} id Unique run identifier (ULID). - * @param {string} stageId Identifier of a stage within a run\'s workflow graph. + * @param {string} stageId Identifier of a stage within a run\'s workflow graph, serialized as `node_id@visit`. * @param {number} [pageLimit] Maximum number of items to return per page. * @param {number} [pageOffset] Number of items to skip before returning results. * @param {*} [options] Override http request option. @@ -264,6 +790,36 @@ export const RunInternalsApiFp = function(configuration?: Configuration) { const localVarOperationServerBasePath = operationServerMap['RunInternalsApi.listStageTurns']?.[localVarOperationServerIndex]?.url; return (axios, basePath) => createRequestFunction(localVarAxiosArgs, globalAxios, BASE_PATH, configuration)(axios, localVarOperationServerBasePath || basePath); }, + /** + * Uploads an artifact for a stage. Intended for trusted internal callers. + * @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`. + * @param {string} filename Artifact filename. May contain path separators. + * @param {File} body + * @param {*} [options] Override http request option. + * @throws {RequiredError} + */ + async putStageArtifact(id: string, stageId: string, filename: string, body: File, options?: RawAxiosRequestConfig): Promise<(axios?: AxiosInstance, basePath?: string) => AxiosPromise> { + const localVarAxiosArgs = await localVarAxiosParamCreator.putStageArtifact(id, stageId, filename, body, options); + const localVarOperationServerIndex = configuration?.serverIndex ?? 0; + const localVarOperationServerBasePath = operationServerMap['RunInternalsApi.putStageArtifact']?.[localVarOperationServerIndex]?.url; + return (axios, basePath) => createRequestFunction(localVarAxiosArgs, globalAxios, BASE_PATH, configuration)(axios, localVarOperationServerBasePath || basePath); + }, + /** + * Reads a previously stored blob by identifier. + * @summary Read Run Blob + * @param {string} id Unique run identifier (ULID). + * @param {string} blobId Content-addressed blob identifier. + * @param {*} [options] Override http request option. + * @throws {RequiredError} + */ + async readRunBlob(id: string, blobId: string, options?: RawAxiosRequestConfig): Promise<(axios?: AxiosInstance, basePath?: string) => AxiosPromise> { + const localVarAxiosArgs = await localVarAxiosParamCreator.readRunBlob(id, blobId, options); + const localVarOperationServerIndex = configuration?.serverIndex ?? 0; + const localVarOperationServerBasePath = operationServerMap['RunInternalsApi.readRunBlob']?.[localVarOperationServerIndex]?.url; + return (axios, basePath) => createRequestFunction(localVarAxiosArgs, globalAxios, BASE_PATH, configuration)(axios, localVarOperationServerBasePath || basePath); + }, /** * Returns the latest checkpoint data for a run, or null if no checkpoint has been recorded yet. * @summary Retrieve Run Checkpoint @@ -290,6 +846,20 @@ export const RunInternalsApiFp = function(configuration?: Configuration) { const localVarOperationServerBasePath = operationServerMap['RunInternalsApi.retrieveRunSettings']?.[localVarOperationServerIndex]?.url; return (axios, basePath) => createRequestFunction(localVarAxiosArgs, globalAxios, BASE_PATH, configuration)(axios, localVarOperationServerBasePath || basePath); }, + /** + * Writes an opaque binary blob and returns its content-addressed blob identifier. + * @summary Write Run Blob + * @param {string} id Unique run identifier (ULID). + * @param {File} body + * @param {*} [options] Override http request option. + * @throws {RequiredError} + */ + async writeRunBlob(id: string, body: File, options?: RawAxiosRequestConfig): Promise<(axios?: AxiosInstance, basePath?: string) => AxiosPromise> { + const localVarAxiosArgs = await localVarAxiosParamCreator.writeRunBlob(id, body, options); + const localVarOperationServerIndex = configuration?.serverIndex ?? 0; + const localVarOperationServerBasePath = operationServerMap['RunInternalsApi.writeRunBlob']?.[localVarOperationServerIndex]?.url; + return (axios, basePath) => createRequestFunction(localVarAxiosArgs, globalAxios, BASE_PATH, configuration)(axios, localVarOperationServerBasePath || basePath); + }, } }; @@ -299,6 +869,62 @@ export const RunInternalsApiFp = function(configuration?: Configuration) { export const RunInternalsApiFactory = function (configuration?: Configuration, basePath?: string, axios?: AxiosInstance) { const localVarFp = RunInternalsApiFp(configuration) return { + /** + * Appends a validated event to the run event log. Intended for trusted internal callers. + * @summary Append Run Event + * @param {string} id Unique run identifier (ULID). + * @param {RunEvent} runEvent + * @param {*} [options] Override http request option. + * @throws {RequiredError} + */ + appendRunEvent(id: string, runEvent: RunEvent, options?: RawAxiosRequestConfig): AxiosPromise { + return localVarFp.appendRunEvent(id, runEvent, options).then((request) => request(axios, basePath)); + }, + /** + * Opens a server-sent event stream for a live run. Optionally replays stored events from `since_seq` before switching to live updates. + * @summary Attach Run Events + * @param {string} id Unique run identifier (ULID). + * @param {number} [sinceSeq] First event sequence number to include. + * @param {*} [options] Override http request option. + * @throws {RequiredError} + */ + attachRunEvents(id: string, sinceSeq?: number, options?: RawAxiosRequestConfig): AxiosPromise { + return localVarFp.attachRunEvents(id, sinceSeq, options).then((request) => request(axios, basePath)); + }, + /** + * Returns the internal event-sourced run projection. This is not a stable public contract. + * @summary Get Run State + * @param {string} id Unique run identifier (ULID). + * @param {*} [options] Override http request option. + * @throws {RequiredError} + */ + getRunState(id: string, options?: RawAxiosRequestConfig): AxiosPromise { + return localVarFp.getRunState(id, options).then((request) => request(axios, basePath)); + }, + /** + * Downloads an artifact by filename. + * @summary Get 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`. + * @param {string} filename Artifact filename. May contain path separators. + * @param {*} [options] Override http request option. + * @throws {RequiredError} + */ + getStageArtifact(id: string, stageId: string, filename: string, options?: RawAxiosRequestConfig): AxiosPromise { + return localVarFp.getStageArtifact(id, stageId, filename, options).then((request) => request(axios, basePath)); + }, + /** + * Returns a paginated JSON list of stored run events. + * @summary List Run Events + * @param {string} id Unique run identifier (ULID). + * @param {number} [sinceSeq] First event sequence number to include. + * @param {number} [limit] Maximum number of events to return. + * @param {*} [options] Override http request option. + * @throws {RequiredError} + */ + listRunEvents(id: string, sinceSeq?: number, limit?: number, options?: RawAxiosRequestConfig): AxiosPromise { + return localVarFp.listRunEvents(id, sinceSeq, limit, options).then((request) => request(axios, basePath)); + }, /** * Returns the ordered list of stages in a run\'s workflow graph with their current status and timing. Stages are bounded by the workflow graph size, typically fewer than 20. * @summary List Run Stages @@ -311,11 +937,22 @@ export const RunInternalsApiFactory = function (configuration?: Configuration, b listRunStages(id: string, pageLimit?: number, pageOffset?: number, options?: RawAxiosRequestConfig): AxiosPromise { return localVarFp.listRunStages(id, pageLimit, pageOffset, options).then((request) => request(axios, basePath)); }, + /** + * Lists artifact filenames stored for a stage. + * @summary List Stage Artifacts + * @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`. + * @param {*} [options] Override http request option. + * @throws {RequiredError} + */ + listStageArtifacts(id: string, stageId: string, options?: RawAxiosRequestConfig): AxiosPromise { + return localVarFp.listStageArtifacts(id, stageId, options).then((request) => request(axios, basePath)); + }, /** * Returns a paginated list of conversation turns within a specific stage, including system prompts, assistant responses, and tool invocations. * @summary List Stage Turns * @param {string} id Unique run identifier (ULID). - * @param {string} stageId Identifier of a stage within a run\'s workflow graph. + * @param {string} stageId Identifier of a stage within a run\'s workflow graph, serialized as `node_id@visit`. * @param {number} [pageLimit] Maximum number of items to return per page. * @param {number} [pageOffset] Number of items to skip before returning results. * @param {*} [options] Override http request option. @@ -324,6 +961,30 @@ export const RunInternalsApiFactory = function (configuration?: Configuration, b listStageTurns(id: string, stageId: string, pageLimit?: number, pageOffset?: number, options?: RawAxiosRequestConfig): AxiosPromise { return localVarFp.listStageTurns(id, stageId, pageLimit, pageOffset, options).then((request) => request(axios, basePath)); }, + /** + * Uploads an artifact for a stage. Intended for trusted internal callers. + * @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`. + * @param {string} filename Artifact filename. May contain path separators. + * @param {File} body + * @param {*} [options] Override http request option. + * @throws {RequiredError} + */ + putStageArtifact(id: string, stageId: string, filename: string, body: File, options?: RawAxiosRequestConfig): AxiosPromise { + return localVarFp.putStageArtifact(id, stageId, filename, body, options).then((request) => request(axios, basePath)); + }, + /** + * Reads a previously stored blob by identifier. + * @summary Read Run Blob + * @param {string} id Unique run identifier (ULID). + * @param {string} blobId Content-addressed blob identifier. + * @param {*} [options] Override http request option. + * @throws {RequiredError} + */ + readRunBlob(id: string, blobId: string, options?: RawAxiosRequestConfig): AxiosPromise { + return localVarFp.readRunBlob(id, blobId, options).then((request) => request(axios, basePath)); + }, /** * Returns the latest checkpoint data for a run, or null if no checkpoint has been recorded yet. * @summary Retrieve Run Checkpoint @@ -344,6 +1005,17 @@ export const RunInternalsApiFactory = function (configuration?: Configuration, b retrieveRunSettings(id: string, options?: RawAxiosRequestConfig): AxiosPromise { return localVarFp.retrieveRunSettings(id, options).then((request) => request(axios, basePath)); }, + /** + * Writes an opaque binary blob and returns its content-addressed blob identifier. + * @summary Write Run Blob + * @param {string} id Unique run identifier (ULID). + * @param {File} body + * @param {*} [options] Override http request option. + * @throws {RequiredError} + */ + writeRunBlob(id: string, body: File, options?: RawAxiosRequestConfig): AxiosPromise { + return localVarFp.writeRunBlob(id, body, options).then((request) => request(axios, basePath)); + }, }; }; @@ -351,6 +1023,67 @@ export const RunInternalsApiFactory = function (configuration?: Configuration, b * RunInternalsApi - object-oriented interface */ export class RunInternalsApi extends BaseAPI { + /** + * Appends a validated event to the run event log. Intended for trusted internal callers. + * @summary Append Run Event + * @param {string} id Unique run identifier (ULID). + * @param {RunEvent} runEvent + * @param {*} [options] Override http request option. + * @throws {RequiredError} + */ + public appendRunEvent(id: string, runEvent: RunEvent, options?: RawAxiosRequestConfig) { + return RunInternalsApiFp(this.configuration).appendRunEvent(id, runEvent, options).then((request) => request(this.axios, this.basePath)); + } + + /** + * Opens a server-sent event stream for a live run. Optionally replays stored events from `since_seq` before switching to live updates. + * @summary Attach Run Events + * @param {string} id Unique run identifier (ULID). + * @param {number} [sinceSeq] First event sequence number to include. + * @param {*} [options] Override http request option. + * @throws {RequiredError} + */ + public attachRunEvents(id: string, sinceSeq?: number, options?: RawAxiosRequestConfig) { + return RunInternalsApiFp(this.configuration).attachRunEvents(id, sinceSeq, options).then((request) => request(this.axios, this.basePath)); + } + + /** + * Returns the internal event-sourced run projection. This is not a stable public contract. + * @summary Get Run State + * @param {string} id Unique run identifier (ULID). + * @param {*} [options] Override http request option. + * @throws {RequiredError} + */ + public getRunState(id: string, options?: RawAxiosRequestConfig) { + return RunInternalsApiFp(this.configuration).getRunState(id, options).then((request) => request(this.axios, this.basePath)); + } + + /** + * Downloads an artifact by filename. + * @summary Get 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`. + * @param {string} filename Artifact filename. May contain path separators. + * @param {*} [options] Override http request option. + * @throws {RequiredError} + */ + public getStageArtifact(id: string, stageId: string, filename: string, options?: RawAxiosRequestConfig) { + return RunInternalsApiFp(this.configuration).getStageArtifact(id, stageId, filename, options).then((request) => request(this.axios, this.basePath)); + } + + /** + * Returns a paginated JSON list of stored run events. + * @summary List Run Events + * @param {string} id Unique run identifier (ULID). + * @param {number} [sinceSeq] First event sequence number to include. + * @param {number} [limit] Maximum number of events to return. + * @param {*} [options] Override http request option. + * @throws {RequiredError} + */ + public listRunEvents(id: string, sinceSeq?: number, limit?: number, options?: RawAxiosRequestConfig) { + return RunInternalsApiFp(this.configuration).listRunEvents(id, sinceSeq, limit, options).then((request) => request(this.axios, this.basePath)); + } + /** * Returns the ordered list of stages in a run\'s workflow graph with their current status and timing. Stages are bounded by the workflow graph size, typically fewer than 20. * @summary List Run Stages @@ -364,11 +1097,23 @@ export class RunInternalsApi extends BaseAPI { return RunInternalsApiFp(this.configuration).listRunStages(id, pageLimit, pageOffset, options).then((request) => request(this.axios, this.basePath)); } + /** + * Lists artifact filenames stored for a stage. + * @summary List Stage Artifacts + * @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`. + * @param {*} [options] Override http request option. + * @throws {RequiredError} + */ + public listStageArtifacts(id: string, stageId: string, options?: RawAxiosRequestConfig) { + return RunInternalsApiFp(this.configuration).listStageArtifacts(id, stageId, options).then((request) => request(this.axios, this.basePath)); + } + /** * Returns a paginated list of conversation turns within a specific stage, including system prompts, assistant responses, and tool invocations. * @summary List Stage Turns * @param {string} id Unique run identifier (ULID). - * @param {string} stageId Identifier of a stage within a run\'s workflow graph. + * @param {string} stageId Identifier of a stage within a run\'s workflow graph, serialized as `node_id@visit`. * @param {number} [pageLimit] Maximum number of items to return per page. * @param {number} [pageOffset] Number of items to skip before returning results. * @param {*} [options] Override http request option. @@ -378,6 +1123,32 @@ export class RunInternalsApi extends BaseAPI { return RunInternalsApiFp(this.configuration).listStageTurns(id, stageId, pageLimit, pageOffset, options).then((request) => request(this.axios, this.basePath)); } + /** + * Uploads an artifact for a stage. Intended for trusted internal callers. + * @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`. + * @param {string} filename Artifact filename. May contain path separators. + * @param {File} body + * @param {*} [options] Override http request option. + * @throws {RequiredError} + */ + public putStageArtifact(id: string, stageId: string, filename: string, body: File, options?: RawAxiosRequestConfig) { + return RunInternalsApiFp(this.configuration).putStageArtifact(id, stageId, filename, body, options).then((request) => request(this.axios, this.basePath)); + } + + /** + * Reads a previously stored blob by identifier. + * @summary Read Run Blob + * @param {string} id Unique run identifier (ULID). + * @param {string} blobId Content-addressed blob identifier. + * @param {*} [options] Override http request option. + * @throws {RequiredError} + */ + public readRunBlob(id: string, blobId: string, options?: RawAxiosRequestConfig) { + return RunInternalsApiFp(this.configuration).readRunBlob(id, blobId, options).then((request) => request(this.axios, this.basePath)); + } + /** * Returns the latest checkpoint data for a run, or null if no checkpoint has been recorded yet. * @summary Retrieve Run Checkpoint @@ -399,5 +1170,17 @@ export class RunInternalsApi extends BaseAPI { public retrieveRunSettings(id: string, options?: RawAxiosRequestConfig) { return RunInternalsApiFp(this.configuration).retrieveRunSettings(id, options).then((request) => request(this.axios, this.basePath)); } + + /** + * Writes an opaque binary blob and returns its content-addressed blob identifier. + * @summary Write Run Blob + * @param {string} id Unique run identifier (ULID). + * @param {File} body + * @param {*} [options] Override http request option. + * @throws {RequiredError} + */ + public writeRunBlob(id: string, body: File, options?: RawAxiosRequestConfig) { + return RunInternalsApiFp(this.configuration).writeRunBlob(id, body, options).then((request) => request(this.axios, this.basePath)); + } } diff --git a/lib/packages/fabro-api-client/src/api/runs-api.ts b/lib/packages/fabro-api-client/src/api/runs-api.ts index b3e37a471..25f8d089e 100644 --- a/lib/packages/fabro-api-client/src/api/runs-api.ts +++ b/lib/packages/fabro-api-client/src/api/runs-api.ts @@ -328,47 +328,6 @@ export const RunsApiAxiosParamCreator = function (configuration?: Configuration) options: localVarRequestOptions, }; }, - /** - * Opens a server-sent event (SSE) stream for real-time run updates. Returns 410 if the stream has been closed. - * @summary Stream Run Events - * @param {string} id Unique run identifier (ULID). - * @param {*} [options] Override http request option. - * @throws {RequiredError} - */ - streamRunEvents: async (id: string, options: RawAxiosRequestConfig = {}): Promise => { - // verify required parameter 'id' is not null or undefined - assertParamExists('streamRunEvents', 'id', id) - const localVarPath = `/api/v1/runs/{id}/events` - .replace(`{${"id"}}`, encodeURIComponent(String(id))); - // use dummy base URL string because the URL constructor only accepts absolute URLs. - const localVarUrlObj = new URL(localVarPath, DUMMY_BASE_URL); - let baseOptions; - if (configuration) { - baseOptions = configuration.baseOptions; - } - - const localVarRequestOptions = { method: 'GET', ...baseOptions, ...options}; - const localVarHeaderParameter = {} as any; - const localVarQueryParameter = {} as any; - - // authentication mTLS required - await setApiKeyToObject(localVarHeaderParameter, "X-mTLS-Client-CN", configuration) - - // authentication BearerAuth required - // http bearer authentication required - await setBearerAuthToObject(localVarHeaderParameter, configuration) - - localVarHeaderParameter['Accept'] = 'text/event-stream,application/json'; - - setSearchParams(localVarUrlObj, localVarQueryParameter); - let headersFromBaseOptions = baseOptions && baseOptions.headers ? baseOptions.headers : {}; - localVarRequestOptions.headers = {...localVarHeaderParameter, ...headersFromBaseOptions, ...options.headers}; - - return { - url: toPathString(localVarUrlObj), - options: localVarRequestOptions, - }; - }, /** * Resumes a paused run. Returns 409 if the run is not paused. * @summary Unpause Run @@ -511,19 +470,6 @@ export const RunsApiFp = function(configuration?: Configuration) { const localVarOperationServerBasePath = operationServerMap['RunsApi.startRun']?.[localVarOperationServerIndex]?.url; return (axios, basePath) => createRequestFunction(localVarAxiosArgs, globalAxios, BASE_PATH, configuration)(axios, localVarOperationServerBasePath || basePath); }, - /** - * Opens a server-sent event (SSE) stream for real-time run updates. Returns 410 if the stream has been closed. - * @summary Stream Run Events - * @param {string} id Unique run identifier (ULID). - * @param {*} [options] Override http request option. - * @throws {RequiredError} - */ - async streamRunEvents(id: string, options?: RawAxiosRequestConfig): Promise<(axios?: AxiosInstance, basePath?: string) => AxiosPromise> { - const localVarAxiosArgs = await localVarAxiosParamCreator.streamRunEvents(id, options); - const localVarOperationServerIndex = configuration?.serverIndex ?? 0; - const localVarOperationServerBasePath = operationServerMap['RunsApi.streamRunEvents']?.[localVarOperationServerIndex]?.url; - return (axios, basePath) => createRequestFunction(localVarAxiosArgs, globalAxios, BASE_PATH, configuration)(axios, localVarOperationServerBasePath || basePath); - }, /** * Resumes a paused run. Returns 409 if the run is not paused. * @summary Unpause Run @@ -617,16 +563,6 @@ export const RunsApiFactory = function (configuration?: Configuration, basePath? startRun(id: string, options?: RawAxiosRequestConfig): AxiosPromise { return localVarFp.startRun(id, options).then((request) => request(axios, basePath)); }, - /** - * Opens a server-sent event (SSE) stream for real-time run updates. Returns 410 if the stream has been closed. - * @summary Stream Run Events - * @param {string} id Unique run identifier (ULID). - * @param {*} [options] Override http request option. - * @throws {RequiredError} - */ - streamRunEvents(id: string, options?: RawAxiosRequestConfig): AxiosPromise { - return localVarFp.streamRunEvents(id, options).then((request) => request(axios, basePath)); - }, /** * Resumes a paused run. Returns 409 if the run is not paused. * @summary Unpause Run @@ -722,17 +658,6 @@ export class RunsApi extends BaseAPI { return RunsApiFp(this.configuration).startRun(id, options).then((request) => request(this.axios, this.basePath)); } - /** - * Opens a server-sent event (SSE) stream for real-time run updates. Returns 410 if the stream has been closed. - * @summary Stream Run Events - * @param {string} id Unique run identifier (ULID). - * @param {*} [options] Override http request option. - * @throws {RequiredError} - */ - public streamRunEvents(id: string, options?: RawAxiosRequestConfig) { - return RunsApiFp(this.configuration).streamRunEvents(id, options).then((request) => request(this.axios, this.basePath)); - } - /** * Resumes a paused run. Returns 409 if the run is not paused. * @summary Unpause Run diff --git a/lib/packages/fabro-api-client/src/models/append-event-response.ts b/lib/packages/fabro-api-client/src/models/append-event-response.ts new file mode 100644 index 000000000..e49253d37 --- /dev/null +++ b/lib/packages/fabro-api-client/src/models/append-event-response.ts @@ -0,0 +1,26 @@ +/* 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. + */ + + + +/** + * Assigned sequence number for an appended event. + */ +export interface AppendEventResponse { + /** + * Assigned event sequence number. + */ + 'seq': number; +} + diff --git a/lib/packages/fabro-api-client/src/models/artifact-entry.ts b/lib/packages/fabro-api-client/src/models/artifact-entry.ts new file mode 100644 index 000000000..6fbd0e851 --- /dev/null +++ b/lib/packages/fabro-api-client/src/models/artifact-entry.ts @@ -0,0 +1,26 @@ +/* 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. + */ + + + +/** + * A single artifact filename. + */ +export interface ArtifactEntry { + /** + * Artifact filename. + */ + 'filename': string; +} + diff --git a/lib/packages/fabro-api-client/src/models/artifact-list-response.ts b/lib/packages/fabro-api-client/src/models/artifact-list-response.ts new file mode 100644 index 000000000..708bb4ad5 --- /dev/null +++ b/lib/packages/fabro-api-client/src/models/artifact-list-response.ts @@ -0,0 +1,26 @@ +/* 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 { ArtifactEntry } from './artifact-entry'; + +/** + * List of artifact filenames for a stage. + */ +export interface ArtifactListResponse { + 'data': 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 new file mode 100644 index 000000000..fd7d04aea --- /dev/null +++ b/lib/packages/fabro-api-client/src/models/event-envelope.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. + */ + + +// May contain unused imports in some cases +// @ts-ignore +import type { RunEvent } from './run-event'; + +/** + * Stored event envelope with assigned sequence number. + */ +export interface EventEnvelope { + /** + * Assigned event sequence number. + */ + 'seq': number; + 'payload': RunEvent; +} + diff --git a/lib/packages/fabro-api-client/src/models/index.ts b/lib/packages/fabro-api-client/src/models/index.ts index 87b45a553..6eeb32e06 100644 --- a/lib/packages/fabro-api-client/src/models/index.ts +++ b/lib/packages/fabro-api-client/src/models/index.ts @@ -3,6 +3,9 @@ export * from './aggregate-usage-totals'; export * from './api-question'; export * from './api-question-option'; export * from './api-settings'; +export * from './append-event-response'; +export * from './artifact-entry'; +export * from './artifact-list-response'; export * from './artifacts-settings'; export * from './assistant-stage-turn'; export * from './assistant-turn'; @@ -36,6 +39,7 @@ export * from './diff-file'; export * from './diff-stats'; export * from './error-response'; export * from './error-response-entry'; +export * from './event-envelope'; export * from './execute-query-request'; export * from './execute-query-response'; export * from './execute-query-response-rows-inner-inner'; @@ -50,6 +54,8 @@ export * from './git-settings'; export * from './health-response'; export * from './history-entry'; export * from './hook-definition'; +export * from './internal-run-status'; +export * from './internal-stage-status'; export * from './learning'; export * from './learning-category'; export * from './llm-settings'; @@ -62,9 +68,12 @@ export * from './model-features'; export * from './model-limits'; export * from './model-reference'; export * from './model-test-result'; +export * from './node-state'; +export * from './node-status-record'; export * from './open-item'; export * from './open-item-kind'; export * from './paginated-api-question-list'; +export * from './paginated-event-list'; export * from './paginated-history-entry-list'; export * from './paginated-model-list'; export * from './paginated-retro-list'; @@ -93,7 +102,10 @@ export * from './root-response'; export * from './root-response-urls'; export * from './run-checkpoint'; export * from './run-error'; +export * from './run-event'; export * from './run-list-item'; +export * from './run-projection'; +export * from './run-projection-checkpoints-inner-inner'; export * from './run-pull-request'; export * from './run-question'; export * from './run-reference'; @@ -101,6 +113,7 @@ export * from './run-sandbox'; export * from './run-settings'; export * from './run-stage'; export * from './run-status'; +export * from './run-status-record'; export * from './run-status-response'; export * from './run-timings'; export * from './run-usage'; @@ -124,6 +137,7 @@ export * from './smoothness-rating'; export * from './stage-retro'; export * from './stage-status'; export * from './stage-turn'; +export * from './status-reason'; export * from './steer-request'; export * from './submit-answer-request'; export * from './system-stage-turn'; @@ -153,3 +167,4 @@ export * from './workflow-last-run'; export * from './workflow-list-item'; export * from './workflow-reference'; export * from './workflow-schedule'; +export * from './write-blob-response'; diff --git a/lib/packages/fabro-api-client/src/models/internal-run-status.ts b/lib/packages/fabro-api-client/src/models/internal-run-status.ts new file mode 100644 index 000000000..e54bd35da --- /dev/null +++ b/lib/packages/fabro-api-client/src/models/internal-run-status.ts @@ -0,0 +1,35 @@ +/* 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. + */ + + + +/** + * Internal event-sourced run status. + */ + +export const InternalRunStatus = { + SUBMITTED: 'submitted', + STARTING: 'starting', + RUNNING: 'running', + PAUSED: 'paused', + REMOVING: 'removing', + SUCCEEDED: 'succeeded', + FAILED: 'failed', + DEAD: 'dead' +} as const; + +export type InternalRunStatus = typeof InternalRunStatus[keyof typeof InternalRunStatus]; + + + diff --git a/lib/packages/fabro-api-client/src/models/internal-stage-status.ts b/lib/packages/fabro-api-client/src/models/internal-stage-status.ts new file mode 100644 index 000000000..98077797f --- /dev/null +++ b/lib/packages/fabro-api-client/src/models/internal-stage-status.ts @@ -0,0 +1,32 @@ +/* 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. + */ + + + +/** + * Internal stage status from outcomes and node status records. + */ + +export const InternalStageStatus = { + SUCCESS: 'success', + FAIL: 'fail', + SKIPPED: 'skipped', + PARTIAL_SUCCESS: 'partial_success', + RETRY: 'retry' +} as const; + +export type InternalStageStatus = typeof InternalStageStatus[keyof typeof InternalStageStatus]; + + + diff --git a/lib/packages/fabro-api-client/src/models/node-state.ts b/lib/packages/fabro-api-client/src/models/node-state.ts new file mode 100644 index 000000000..f25ce1f8d --- /dev/null +++ b/lib/packages/fabro-api-client/src/models/node-state.ts @@ -0,0 +1,35 @@ +/* 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 { NodeStatusRecord } from './node-status-record'; + +/** + * Internal node projection state. + */ +export interface NodeState { + 'prompt'?: string; + 'response'?: string; + 'status'?: NodeStatusRecord | null; + 'provider_used'?: any; + 'diff'?: string; + 'script_invocation'?: any; + 'script_timing'?: any; + 'parallel_results'?: any; + 'stdout'?: string; + 'stderr'?: string; +} + diff --git a/lib/packages/fabro-api-client/src/models/node-status-record.ts b/lib/packages/fabro-api-client/src/models/node-status-record.ts new file mode 100644 index 000000000..a899a5116 --- /dev/null +++ b/lib/packages/fabro-api-client/src/models/node-status-record.ts @@ -0,0 +1,31 @@ +/* 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 { InternalStageStatus } from './internal-stage-status'; + +/** + * Internal node status record. + */ +export interface NodeStatusRecord { + 'status': InternalStageStatus; + 'notes'?: string; + 'failure_reason'?: string; + 'timestamp': string; +} + + + diff --git a/lib/packages/fabro-api-client/src/models/paginated-event-list.ts b/lib/packages/fabro-api-client/src/models/paginated-event-list.ts new file mode 100644 index 000000000..3e4ac8c72 --- /dev/null +++ b/lib/packages/fabro-api-client/src/models/paginated-event-list.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. + */ + + +// May contain unused imports in some cases +// @ts-ignore +import type { EventEnvelope } from './event-envelope'; +// May contain unused imports in some cases +// @ts-ignore +import type { PaginationMeta } from './pagination-meta'; + +/** + * Paginated list of stored run events. + */ +export interface PaginatedEventList { + 'data': Array; + 'meta': PaginationMeta; +} + diff --git a/lib/packages/fabro-api-client/src/models/run-event.ts b/lib/packages/fabro-api-client/src/models/run-event.ts new file mode 100644 index 000000000..c9090d435 --- /dev/null +++ b/lib/packages/fabro-api-client/src/models/run-event.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. + */ + + + +/** + * Internal RunEvent-compatible JSON payload. The server validates this body by deserializing into the typed RunEvent struct. + */ +export interface RunEvent { + [key: string]: any; + + 'id': string; + 'ts': string; + 'run_id': string; + 'node_id'?: string; + 'node_label'?: string; + 'session_id'?: string; + 'parent_session_id'?: string; + /** + * Event type discriminator. + */ + 'event': string; + 'properties'?: { [key: string]: any; }; +} + diff --git a/lib/packages/fabro-api-client/src/models/run-projection-checkpoints-inner-inner.ts b/lib/packages/fabro-api-client/src/models/run-projection-checkpoints-inner-inner.ts new file mode 100644 index 000000000..30f5384aa --- /dev/null +++ b/lib/packages/fabro-api-client/src/models/run-projection-checkpoints-inner-inner.ts @@ -0,0 +1,25 @@ +/* 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 { RunCheckpoint } from './run-checkpoint'; + +/** + * @type RunProjectionCheckpointsInnerInner + */ +export type RunProjectionCheckpointsInnerInner = RunCheckpoint | number; + + diff --git a/lib/packages/fabro-api-client/src/models/run-projection.ts b/lib/packages/fabro-api-client/src/models/run-projection.ts new file mode 100644 index 000000000..5ea6b401b --- /dev/null +++ b/lib/packages/fabro-api-client/src/models/run-projection.ts @@ -0,0 +1,54 @@ +/* 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 { NodeState } from './node-state'; +// May contain unused imports in some cases +// @ts-ignore +import type { RunCheckpoint } from './run-checkpoint'; +// May contain unused imports in some cases +// @ts-ignore +import type { RunProjectionCheckpointsInnerInner } from './run-projection-checkpoints-inner-inner'; +// May contain unused imports in some cases +// @ts-ignore +import type { RunStatusRecord } from './run-status-record'; + +/** + * Raw internal run projection derived from the event log. + */ +export interface RunProjection { + 'run'?: { [key: string]: any; }; + 'graph_source'?: string; + 'start'?: { [key: string]: any; }; + 'status'?: RunStatusRecord | null; + 'checkpoint'?: RunCheckpoint | null; + /** + * Sequence-tagged checkpoint history entries as `[seq, checkpoint]`. + */ + 'checkpoints'?: Array>; + 'conclusion'?: { [key: string]: any; }; + 'retro'?: { [key: string]: any; }; + 'retro_prompt'?: string; + 'retro_response'?: string; + 'sandbox'?: { [key: string]: any; }; + 'final_patch'?: string; + 'pull_request'?: { [key: string]: any; }; + /** + * Map from StageId (`node_id@visit`) to NodeState. + */ + 'nodes': { [key: string]: NodeState; }; +} + diff --git a/lib/packages/fabro-api-client/src/models/run-status-record.ts b/lib/packages/fabro-api-client/src/models/run-status-record.ts new file mode 100644 index 000000000..8a74e7576 --- /dev/null +++ b/lib/packages/fabro-api-client/src/models/run-status-record.ts @@ -0,0 +1,33 @@ +/* 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 { InternalRunStatus } from './internal-run-status'; +// May contain unused imports in some cases +// @ts-ignore +import type { StatusReason } from './status-reason'; + +/** + * Internal run status record from the event projection. + */ +export interface RunStatusRecord { + 'status': InternalRunStatus; + 'reason'?: StatusReason | null; + 'updated_at': string; +} + + + diff --git a/lib/packages/fabro-api-client/src/models/status-reason.ts b/lib/packages/fabro-api-client/src/models/status-reason.ts new file mode 100644 index 000000000..d85a5e363 --- /dev/null +++ b/lib/packages/fabro-api-client/src/models/status-reason.ts @@ -0,0 +1,38 @@ +/* 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. + */ + + + +/** + * Optional reason attached to a run status transition. + */ + +export const StatusReason = { + COMPLETED: 'completed', + PARTIAL_SUCCESS: 'partial_success', + WORKFLOW_ERROR: 'workflow_error', + CANCELLED: 'cancelled', + TERMINATED: 'terminated', + TRANSIENT_INFRA: 'transient_infra', + BUDGET_EXHAUSTED: 'budget_exhausted', + LAUNCH_FAILED: 'launch_failed', + BOOTSTRAP_FAILED: 'bootstrap_failed', + SANDBOX_INIT_FAILED: 'sandbox_init_failed', + SANDBOX_INITIALIZING: 'sandbox_initializing' +} as const; + +export type StatusReason = typeof StatusReason[keyof typeof StatusReason]; + + + diff --git a/lib/packages/fabro-api-client/src/models/write-blob-response.ts b/lib/packages/fabro-api-client/src/models/write-blob-response.ts new file mode 100644 index 000000000..6cff79991 --- /dev/null +++ b/lib/packages/fabro-api-client/src/models/write-blob-response.ts @@ -0,0 +1,26 @@ +/* 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. + */ + + + +/** + * Content-addressed identifier for a stored blob. + */ +export interface WriteBlobResponse { + /** + * Blob identifier. + */ + 'id': string; +} +