mirror of
https://github.com/windmill-labs/windmill.git
synced 2026-10-14 16:02:57 +00:00
* feat: run turns in several flow chat conversations at once Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * fix: keep finished turns finished and cached chats current in the flow chat pool Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * fix: attribute a turn's rows by job id as well as sequence Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * fix: count only real stream updates and retry the job-id read Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * fix: keep a chat that holds an unsent draft, and take one back when its first turn is withdrawn Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * fix: ignore a stale running-turn snapshot, keep a withdrawn chat's draft, poll after clean stream ends Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * fix: follow the turn running now when the listing named one already over Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * fix: keep replacement turns and SSE fallback moving * fix: keep replacement turn handoffs active * fix: preserve unread badge line height * fix: settle local fallback handoffs * fix: settle refused turn handoffs * fix: scope turn handoffs to conversation * fix: drop stale turn handoffs * refactor: move the queued message and 409 handling into per-conversation turns Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * fix: address cubic's review of the parallel flow chat turns Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * fix: clear a stale failure on refresh, and tighten the docs and test waits Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * fix: recover running rows past the first page, and drop the failure a re-read disproves Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * refactor: check the running-turn query at compile time, and narrow what a refresh clears Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * fix: settle a failed turn only from an answer that turn wrote Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * fix: settle a failed turn from its own answer, and only while it is still the failure shown Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * fix: drop a failure whose answer arrived even when a newer turn owns the error Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * fix: free an answered failure whatever the turn that started meanwhile is doing Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * fix: drop a rows read that a turn outran, rather than merging it under newer messages Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * fix: drop a rows read whose conversation was left and opened again Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * fix: hand over a file still being read when its composer goes Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * fix: count a drop's routing as work in flight, so its file is handed over too Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * fix: hold the send until every file a conversation is owed has landed Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * refactor: keep a panel mounted per conversation instead of handing its draft over Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * fix: keep the withdrawn chat whose composer was written in, not the empty one Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * fix: keep the chat in front of the reader when both withdrawn composers were written in Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * fix: keep a retry's own run arguments when a turn elsewhere refuses it Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * fix: name panels apart across pools, and read a flow's inputs when its chat is built Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
126 lines
4.6 KiB
Rust
126 lines
4.6 KiB
Rust
/*
|
|
* Author: Ruben Fiszel
|
|
* Copyright: Windmill Labs, Inc 2022
|
|
* This file and its contents are licensed under the AGPLv3 License.
|
|
* Please see the included NOTICE for copyright information and
|
|
* LICENSE-AGPL for a copy of the license.
|
|
*/
|
|
|
|
use crate::s3_log_batching::{record_s3_log, S3ProxyRequest};
|
|
use ::tracing::{field, Span};
|
|
use axum::extract::Request;
|
|
use axum::middleware::Next;
|
|
use axum::response::Response as AxumResponse;
|
|
use hyper::Response;
|
|
use tower_http::trace::{MakeSpan, OnFailure, OnResponse};
|
|
use uuid::Uuid;
|
|
use windmill_api_workspaces::remote_deploy::RedactedUri;
|
|
use windmill_common::log_context::{with_log_context, LogContext};
|
|
|
|
lazy_static::lazy_static! {
|
|
static ref LOG_REQUESTS: bool = std::env::var("LOG_REQUESTS")
|
|
.ok()
|
|
.and_then(|x| x.parse::<bool>().ok())
|
|
.unwrap_or(true);
|
|
}
|
|
|
|
#[derive(Clone)]
|
|
pub struct MyOnResponse {}
|
|
|
|
impl<B> OnResponse<B> for MyOnResponse {
|
|
fn on_response(
|
|
self,
|
|
response: &Response<B>,
|
|
latency: std::time::Duration,
|
|
_span: &tracing::Span,
|
|
) {
|
|
if *LOG_REQUESTS {
|
|
if let Some(s3req) = response.extensions().get::<S3ProxyRequest>() {
|
|
let status = response.status().as_u16();
|
|
if response.status().is_success() || response.status().is_redirection() {
|
|
record_s3_log(&s3req.uri, &s3req.method, status, latency.as_millis());
|
|
return;
|
|
}
|
|
// 4xx/5xx errors are rare and shouldn't be batched
|
|
}
|
|
|
|
let latency = latency.as_millis();
|
|
let status = response.status().as_u16();
|
|
if response.status().is_success() || response.status().is_redirection() {
|
|
tracing::info!(latency = latency, status = status, "response")
|
|
} else if status == 404 || status == 409 {
|
|
// A refused turn is as expected as a miss: the flow chat takes the turn that
|
|
// refused it and sends its message after it.
|
|
tracing::warn!(latency = latency, status = status, "response")
|
|
} else {
|
|
tracing::error!(latency = latency, status = status, "response")
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
#[derive(Clone)]
|
|
pub struct MyOnFailure {}
|
|
|
|
impl<B> OnFailure<B> for MyOnFailure {
|
|
fn on_failure(&mut self, _b: B, latency: std::time::Duration, _span: &tracing::Span) {
|
|
tracing::error!(latency = latency.as_millis(), "response failure")
|
|
}
|
|
}
|
|
|
|
lazy_static::lazy_static! {
|
|
static ref TRACING_HEADER: String = std::env::var("TRACING_HEADER")
|
|
.ok().unwrap_or_else(|| "x-tracing-id".to_string());
|
|
}
|
|
#[derive(Clone)]
|
|
pub struct MyMakeSpan {}
|
|
|
|
impl<B> MakeSpan<B> for MyMakeSpan {
|
|
fn make_span(&mut self, request: &hyper::Request<B>) -> Span {
|
|
let tracing_id = request
|
|
.headers()
|
|
.get(TRACING_HEADER.as_str())
|
|
.and_then(|x| x.to_str().map(|x| x.to_string()).ok())
|
|
.unwrap_or(Uuid::new_v4().to_string());
|
|
// ERROR level, not INFO: a span is only recorded by a layer whose filter
|
|
// enables it, so under RUST_LOG=error/warn an INFO span is dropped and the
|
|
// 4xx/5xx `response` events in `MyOnResponse` print with no uri, method or
|
|
// traceId.
|
|
tracing::error_span!(
|
|
"request",
|
|
method = %request.method(),
|
|
uri = %RedactedUri(request.uri()),
|
|
username = field::Empty,
|
|
workspace_id = field::Empty,
|
|
traceId = tracing_id,
|
|
email = field::Empty,
|
|
)
|
|
}
|
|
}
|
|
|
|
/// Axum middleware that seeds a per-request `LogContext` with method/uri/
|
|
/// traceId and wraps the downstream chain in a task-local scope. Auth and
|
|
/// workspace-resolution code later mutate this context (via
|
|
/// `update_log_context`) as email/username/workspace_id become known.
|
|
///
|
|
/// Registered at the top of the router layer stack in `windmill-api/src/lib.rs`
|
|
/// so every route — and critically, the `MyOnResponse::on_response` callback
|
|
/// that TraceLayer invokes on the way out — runs inside the scope and thus
|
|
/// flows through to exported OTEL LogRecords via the EE LogContextBridge.
|
|
pub async fn log_context_middleware(request: Request, next: Next) -> AxumResponse {
|
|
let trace_id = request
|
|
.headers()
|
|
.get(TRACING_HEADER.as_str())
|
|
.and_then(|x| x.to_str().ok())
|
|
.map(|s| s.to_string());
|
|
|
|
let ctx = LogContext {
|
|
method: Some(request.method().to_string()),
|
|
uri: Some(RedactedUri(request.uri()).to_string()),
|
|
trace_id,
|
|
..Default::default()
|
|
};
|
|
|
|
with_log_context(ctx, next.run(request)).await
|
|
}
|