diff --git a/.claude/skills/release/SKILL.md b/.claude/skills/release/SKILL.md
index 80510004..ba101bd3 100644
--- a/.claude/skills/release/SKILL.md
+++ b/.claude/skills/release/SKILL.md
@@ -25,4 +25,6 @@ Update the version string in ALL of these files:
Do NOT change version strings in `*.lock` files or `Cargo.lock`.
+After updating all version strings, run `cd cli && uv lock` to update the lock file with the new version.
+
After making changes, show a summary of all files modified and the old → new version.
diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml
index 25e6f99d..2d844498 100644
--- a/.github/workflows/ci.yml
+++ b/.github/workflows/ci.yml
@@ -133,13 +133,13 @@ jobs:
load: true
tags: |
${{ env.PLANO_DOCKER_IMAGE }}
- ${{ env.DOCKER_IMAGE }}:0.4.11
+ ${{ env.DOCKER_IMAGE }}:0.4.14
${{ env.DOCKER_IMAGE }}:latest
cache-from: type=gha
cache-to: type=gha,mode=max
- name: Save image as artifact
- run: docker save ${{ env.PLANO_DOCKER_IMAGE }} ${{ env.DOCKER_IMAGE }}:0.4.11 ${{ env.DOCKER_IMAGE }}:latest -o /tmp/plano-image.tar
+ run: docker save ${{ env.PLANO_DOCKER_IMAGE }} ${{ env.DOCKER_IMAGE }}:0.4.14 ${{ env.DOCKER_IMAGE }}:latest -o /tmp/plano-image.tar
- name: Upload image artifact
uses: actions/upload-artifact@v6
diff --git a/.gitignore b/.gitignore
index af706ea4..391c17fa 100644
--- a/.gitignore
+++ b/.gitignore
@@ -152,3 +152,4 @@ apps/*/dist/
.cursor/
.agents
+docs/do/
diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml
index 84001c45..22a18416 100644
--- a/.pre-commit-config.yaml
+++ b/.pre-commit-config.yaml
@@ -4,6 +4,7 @@ repos:
hooks:
- id: check-yaml
exclude: config/envoy.template*
+ args: [--allow-multiple-documents]
- id: end-of-file-fixer
- id: trailing-whitespace
- repo: local
diff --git a/Dockerfile b/Dockerfile
index 1e27f8ed..ad0ca707 100644
--- a/Dockerfile
+++ b/Dockerfile
@@ -49,7 +49,8 @@ FROM python:3.14-slim AS arch
RUN set -eux; \
apt-get update; \
- apt-get install -y --no-install-recommends gettext-base curl; \
+ apt-get upgrade -y; \
+ apt-get install -y --no-install-recommends gettext-base curl procps; \
apt-get clean; rm -rf /var/lib/apt/lists/*
RUN pip install --no-cache-dir supervisor
diff --git a/apps/www/src/components/Hero.tsx b/apps/www/src/components/Hero.tsx
index 7952c68f..d61fad2c 100644
--- a/apps/www/src/components/Hero.tsx
+++ b/apps/www/src/components/Hero.tsx
@@ -24,7 +24,7 @@ export function Hero() {
>
- v0.4.11
+ v0.4.14
—
diff --git a/build_filter_image.sh b/build_filter_image.sh
index 8e041894..dcbf32f5 100644
--- a/build_filter_image.sh
+++ b/build_filter_image.sh
@@ -1 +1 @@
-docker build -f Dockerfile . -t katanemo/plano -t katanemo/plano:0.4.11
+docker build -f Dockerfile . -t katanemo/plano -t katanemo/plano:0.4.14
diff --git a/cli/planoai/__init__.py b/cli/planoai/__init__.py
index b94eadc2..71def36d 100644
--- a/cli/planoai/__init__.py
+++ b/cli/planoai/__init__.py
@@ -1,3 +1,3 @@
"""Plano CLI - Intelligent Prompt Gateway."""
-__version__ = "0.4.11"
+__version__ = "0.4.14"
diff --git a/cli/planoai/config_generator.py b/cli/planoai/config_generator.py
index 522968c9..929b7657 100644
--- a/cli/planoai/config_generator.py
+++ b/cli/planoai/config_generator.py
@@ -3,18 +3,17 @@ import os
from planoai.utils import convert_legacy_listeners
from jinja2 import Environment, FileSystemLoader
import yaml
-from jsonschema import validate
+from jsonschema import validate, ValidationError
from urllib.parse import urlparse
from copy import deepcopy
from planoai.consts import DEFAULT_OTEL_TRACING_GRPC_ENDPOINT
-
SUPPORTED_PROVIDERS_WITH_BASE_URL = [
"azure_openai",
"ollama",
"qwen",
"amazon_bedrock",
- "arch",
+ "plano",
]
SUPPORTED_PROVIDERS_WITHOUT_BASE_URL = [
@@ -368,47 +367,52 @@ def validate_and_render_schema():
llms_with_endpoint.append(model_provider)
llms_with_endpoint_cluster_names.add(cluster_name)
- if len(model_usage_name_keys) > 0:
- routing_model_provider = config_yaml.get("routing", {}).get(
- "model_provider", None
+ overrides_config = config_yaml.get("overrides", {})
+ # Build lookup of model names (already prefix-stripped by config processing)
+ model_name_set = {mp.get("model") for mp in updated_model_providers}
+
+ # Auto-add arch-router provider if routing preferences exist and no provider matches the router model
+ router_model = overrides_config.get("llm_routing_model", "Arch-Router")
+ # Strip provider prefix for comparison since config processing strips prefixes from model names
+ router_model_id = (
+ router_model.split("/", 1)[1] if "/" in router_model else router_model
+ )
+ if len(model_usage_name_keys) > 0 and router_model_id not in model_name_set:
+ updated_model_providers.append(
+ {
+ "name": "arch-router",
+ "provider_interface": "plano",
+ "model": router_model_id,
+ "internal": True,
+ }
)
- if (
- routing_model_provider
- and routing_model_provider not in model_provider_name_set
- ):
- raise Exception(
- f"Routing model_provider {routing_model_provider} is not defined in model_providers"
- )
- if (
- routing_model_provider is None
- and "arch-router" not in model_provider_name_set
- ):
- updated_model_providers.append(
- {
- "name": "arch-router",
- "provider_interface": "arch",
- "model": config_yaml.get("routing", {}).get("model", "Arch-Router"),
- "internal": True,
- }
- )
# Always add arch-function model provider if not already defined
if "arch-function" not in model_provider_name_set:
updated_model_providers.append(
{
"name": "arch-function",
- "provider_interface": "arch",
+ "provider_interface": "plano",
"model": "Arch-Function",
"internal": True,
}
)
- if "plano-orchestrator" not in model_provider_name_set:
+ # Auto-add plano-orchestrator provider if no provider matches the orchestrator model
+ orchestrator_model = overrides_config.get(
+ "agent_orchestration_model", "Plano-Orchestrator"
+ )
+ orchestrator_model_id = (
+ orchestrator_model.split("/", 1)[1]
+ if "/" in orchestrator_model
+ else orchestrator_model
+ )
+ if orchestrator_model_id not in model_name_set:
updated_model_providers.append(
{
- "name": "plano-orchestrator",
- "provider_interface": "arch",
- "model": "Plano-Orchestrator",
+ "name": "plano/orchestrator",
+ "provider_interface": "plano",
+ "model": orchestrator_model_id,
"internal": True,
}
)
@@ -426,6 +430,16 @@ def validate_and_render_schema():
"Please provide model_providers either under listeners or at root level, not both. Currently we don't support multiple listeners with model_providers"
)
+ # Validate input_filters IDs on listeners reference valid agent/filter IDs
+ for listener in listeners:
+ listener_input_filters = listener.get("input_filters", [])
+ for fc_id in listener_input_filters:
+ if fc_id not in agent_id_keys:
+ raise Exception(
+ f"Listener '{listener.get('name', 'unknown')}' references input_filters id '{fc_id}' "
+ f"which is not defined in agents or filters. Available ids: {', '.join(sorted(agent_id_keys))}"
+ )
+
# Validate model aliases if present
if "model_aliases" in config_yaml:
model_aliases = config_yaml["model_aliases"]
@@ -503,11 +517,15 @@ def validate_prompt_config(plano_config_file, plano_config_schema_file):
try:
validate(config_yaml, config_schema_yaml)
- except Exception as e:
- print(
- f"Error validating plano_config file: {plano_config_file}, schema file: {plano_config_schema_file}, error: {e}"
+ except ValidationError as e:
+ path = (
+ " → ".join(str(p) for p in e.absolute_path) if e.absolute_path else "root"
)
- raise e
+ raise ValidationError(
+ f"{e.message}\n Location: {path}\n Value: {e.instance}"
+ ) from None
+ except Exception as e:
+ raise
if __name__ == "__main__":
diff --git a/cli/planoai/consts.py b/cli/planoai/consts.py
index 145fb640..e80c8b72 100644
--- a/cli/planoai/consts.py
+++ b/cli/planoai/consts.py
@@ -5,7 +5,7 @@ PLANO_COLOR = "#969FF4"
SERVICE_NAME_ARCHGW = "plano"
PLANO_DOCKER_NAME = "plano"
-PLANO_DOCKER_IMAGE = os.getenv("PLANO_DOCKER_IMAGE", "katanemo/plano:0.4.11")
+PLANO_DOCKER_IMAGE = os.getenv("PLANO_DOCKER_IMAGE", "katanemo/plano:0.4.14")
DEFAULT_OTEL_TRACING_GRPC_ENDPOINT = "http://localhost:4317"
# Native mode constants
diff --git a/cli/planoai/core.py b/cli/planoai/core.py
index 174f37c0..54a181d6 100644
--- a/cli/planoai/core.py
+++ b/cli/planoai/core.py
@@ -165,7 +165,7 @@ def _resolve_cli_agent_endpoint(plano_config_yaml: dict) -> tuple[str, int]:
if isinstance(listeners, list):
for listener in listeners:
- if listener.get("type") in ["model", "model_listener"]:
+ if listener.get("type") == "model":
host = listener.get("host") or listener.get("address") or "0.0.0.0"
port = listener.get("port", 12000)
return host, port
diff --git a/cli/planoai/native_runner.py b/cli/planoai/native_runner.py
index 0e39a1fd..ed44e8ad 100644
--- a/cli/planoai/native_runner.py
+++ b/cli/planoai/native_runner.py
@@ -420,9 +420,16 @@ def native_validate_config(plano_config_file):
with _temporary_env(overrides):
from planoai.config_generator import validate_and_render_schema
- # Suppress verbose print output from config_generator
- with contextlib.redirect_stdout(io.StringIO()):
- validate_and_render_schema()
+ # Suppress verbose print output from config_generator but capture errors
+ captured = io.StringIO()
+ try:
+ with contextlib.redirect_stdout(captured):
+ validate_and_render_schema()
+ except SystemExit:
+ # validate_and_render_schema calls exit(1) on failure after
+ # printing to stdout; re-raise so the caller gets a useful message.
+ output = captured.getvalue().strip()
+ raise Exception(output) if output else Exception("Config validation failed")
def native_logs(debug=False, follow=False):
diff --git a/cli/planoai/utils.py b/cli/planoai/utils.py
index 171006f1..822d6e3c 100644
--- a/cli/planoai/utils.py
+++ b/cli/planoai/utils.py
@@ -89,7 +89,7 @@ def convert_legacy_listeners(
) -> tuple[list, dict | None, dict | None]:
llm_gateway_listener = {
"name": "egress_traffic",
- "type": "model_listener",
+ "type": "model",
"port": 12000,
"address": "0.0.0.0",
"timeout": "30s",
@@ -98,7 +98,7 @@ def convert_legacy_listeners(
prompt_gateway_listener = {
"name": "ingress_traffic",
- "type": "prompt_listener",
+ "type": "prompt",
"port": 10000,
"address": "0.0.0.0",
"timeout": "30s",
diff --git a/cli/pyproject.toml b/cli/pyproject.toml
index 3f9be272..61e08562 100644
--- a/cli/pyproject.toml
+++ b/cli/pyproject.toml
@@ -1,6 +1,6 @@
[project]
name = "planoai"
-version = "0.4.11"
+version = "0.4.14"
description = "Python-based CLI tool to manage Plano."
authors = [{name = "Katanemo Labs, Inc."}]
readme = "README.md"
diff --git a/cli/test/test_config_generator.py b/cli/test/test_config_generator.py
index b3e3ab62..17fa56cc 100644
--- a/cli/test/test_config_generator.py
+++ b/cli/test/test_config_generator.py
@@ -104,13 +104,13 @@ listeners:
agents:
- id: simple_tmobile_rag_agent
description: t-mobile virtual assistant for device contracts.
- filter_chain:
+ input_filters:
- query_rewriter
- context_builder
- response_generator
- id: research_agent
description: agent to research and gather information from various sources.
- filter_chain:
+ input_filters:
- research_agent
- response_generator
port: 8000
@@ -376,7 +376,7 @@ def test_convert_legacy_llm_providers():
assert updated_providers == [
{
"name": "egress_traffic",
- "type": "model_listener",
+ "type": "model",
"port": 12000,
"address": "0.0.0.0",
"timeout": "30s",
@@ -384,7 +384,7 @@ def test_convert_legacy_llm_providers():
},
{
"name": "ingress_traffic",
- "type": "prompt_listener",
+ "type": "prompt",
"port": 10000,
"address": "0.0.0.0",
"timeout": "30s",
@@ -400,7 +400,7 @@ def test_convert_legacy_llm_providers():
},
],
"name": "egress_traffic",
- "type": "model_listener",
+ "type": "model",
"port": 12000,
"timeout": "30s",
}
@@ -410,7 +410,7 @@ def test_convert_legacy_llm_providers():
"name": "ingress_traffic",
"port": 10000,
"timeout": "30s",
- "type": "prompt_listener",
+ "type": "prompt",
}
@@ -449,7 +449,7 @@ def test_convert_legacy_llm_providers_no_prompt_gateway():
"name": "egress_traffic",
"port": 12000,
"timeout": "30s",
- "type": "model_listener",
+ "type": "model",
}
]
assert llm_gateway == {
@@ -461,7 +461,7 @@ def test_convert_legacy_llm_providers_no_prompt_gateway():
},
],
"name": "egress_traffic",
- "type": "model_listener",
+ "type": "model",
"port": 12000,
"timeout": "30s",
}
diff --git a/cli/uv.lock b/cli/uv.lock
index 9d85bf85..23c24c5b 100644
--- a/cli/uv.lock
+++ b/cli/uv.lock
@@ -337,7 +337,7 @@ wheels = [
[[package]]
name = "planoai"
-version = "0.4.9"
+version = "0.4.14"
source = { editable = "." }
dependencies = [
{ name = "click" },
diff --git a/config/envoy.template.yaml b/config/envoy.template.yaml
index a780c3f1..c2dd5ed0 100644
--- a/config/envoy.template.yaml
+++ b/config/envoy.template.yaml
@@ -594,13 +594,13 @@ static_resources:
clusters:
- - name: arch
+ - name: plano
connect_timeout: {{ upstream_connect_timeout | default('5s') }}
type: LOGICAL_DNS
dns_lookup_family: V4_ONLY
lb_policy: ROUND_ROBIN
load_assignment:
- cluster_name: arch
+ cluster_name: plano
endpoints:
- lb_endpoints:
- endpoint:
diff --git a/config/plano_config_schema.yaml b/config/plano_config_schema.yaml
index 9fd439b1..7b18eb02 100644
--- a/config/plano_config_schema.yaml
+++ b/config/plano_config_schema.yaml
@@ -85,7 +85,7 @@ properties:
type: string
default:
type: boolean
- filter_chain:
+ input_filters:
type: array
items:
type: string
@@ -93,6 +93,14 @@ properties:
required:
- id
- description
+ input_filters:
+ type: array
+ items:
+ type: string
+ output_filters:
+ type: array
+ items:
+ type: string
additionalProperties: false
required:
- type
@@ -173,7 +181,7 @@ properties:
provider_interface:
type: string
enum:
- - arch
+ - plano
- claude
- deepseek
- groq
@@ -220,7 +228,7 @@ properties:
provider_interface:
type: string
enum:
- - arch
+ - plano
- claude
- deepseek
- groq
@@ -271,6 +279,12 @@ properties:
upstream_tls_ca_path:
type: string
description: "Path to the trusted CA bundle for upstream TLS verification. Default is '/etc/ssl/certs/ca-certificates.crt'."
+ llm_routing_model:
+ type: string
+ description: "Model name for the LLM router (e.g., 'Arch-Router'). Must match a model in model_providers."
+ agent_orchestration_model:
+ type: string
+ description: "Model name for the agent orchestrator (e.g., 'Plano-Orchestrator'). Must match a model in model_providers."
system_prompt:
type: string
prompt_targets:
diff --git a/config/supervisord.conf b/config/supervisord.conf
index 00c1594c..a2869136 100644
--- a/config/supervisord.conf
+++ b/config/supervisord.conf
@@ -1,14 +1,33 @@
[supervisord]
nodaemon=true
+pidfile=/var/run/supervisord.pid
+
+[program:config_generator]
+command=/bin/sh -c "\
+ uv run python -m planoai.config_generator && \
+ envsubst < /app/plano_config_rendered.yaml > /app/plano_config_rendered.env_sub.yaml && \
+ envsubst < /etc/envoy/envoy.yaml > /etc/envoy.env_sub.yaml && \
+ touch /tmp/config_ready || \
+ (echo 'Config generation failed, shutting down'; kill -15 $(cat /var/run/supervisord.pid))"
+priority=10
+autorestart=false
+startsecs=0
+stdout_logfile=/dev/stdout
+redirect_stderr=true
+stdout_logfile_maxbytes=0
+stderr_logfile_maxbytes=0
[program:brightstaff]
command=sh -c "\
- envsubst < /app/plano_config_rendered.yaml > /app/plano_config_rendered.env_sub.yaml && \
+ while [ ! -f /tmp/config_ready ]; do echo '[brightstaff] Waiting for config generation...'; sleep 0.5; done && \
RUST_LOG=${LOG_LEVEL:-info} \
PLANO_CONFIG_PATH_RENDERED=/app/plano_config_rendered.env_sub.yaml \
/app/brightstaff 2>&1 | \
tee /var/log/brightstaff.log | \
- while IFS= read -r line; do echo '[brightstaff]' \"$line\"; done"
+ while IFS= read -r line; do echo '[brightstaff]' \"$line\"; done; \
+ echo '[brightstaff] Process exited, shutting down'; kill -15 $(cat /var/run/supervisord.pid)"
+priority=20
+autorestart=false
stdout_logfile=/dev/stdout
redirect_stderr=true
stdout_logfile_maxbytes=0
@@ -16,13 +35,15 @@ stderr_logfile_maxbytes=0
[program:envoy]
command=/bin/sh -c "\
- uv run python -m planoai.config_generator && \
- envsubst < /etc/envoy/envoy.yaml > /etc/envoy.env_sub.yaml && \
- envoy -c /etc/envoy.env_sub.yaml \
- --component-log-level wasm:${LOG_LEVEL:-info} \
- --log-format '[%%Y-%%m-%%d %%T.%%e][%%l] %%v' 2>&1 | \
+ while [ ! -f /tmp/config_ready ]; do echo '[plano_logs] Waiting for config generation...'; sleep 0.5; done && \
+ envoy -c /etc/envoy.env_sub.yaml \
+ --component-log-level wasm:${LOG_LEVEL:-info} \
+ --log-format '[%%Y-%%m-%%d %%T.%%e][%%l] %%v' 2>&1 | \
tee /var/log/envoy.log | \
- while IFS= read -r line; do echo '[plano_logs]' \"$line\"; done"
+ while IFS= read -r line; do echo '[plano_logs]' \"$line\"; done; \
+ echo '[plano_logs] Process exited, shutting down'; kill -15 $(cat /var/run/supervisord.pid)"
+priority=20
+autorestart=false
stdout_logfile=/dev/stdout
redirect_stderr=true
stdout_logfile_maxbytes=0
diff --git a/crates/brightstaff/src/app_state.rs b/crates/brightstaff/src/app_state.rs
new file mode 100644
index 00000000..57707f6e
--- /dev/null
+++ b/crates/brightstaff/src/app_state.rs
@@ -0,0 +1,29 @@
+use std::collections::HashMap;
+use std::sync::Arc;
+
+use common::configuration::{Agent, FilterPipeline, Listener, ModelAlias, SpanAttributes};
+use common::llm_providers::LlmProviders;
+use tokio::sync::RwLock;
+
+use crate::router::llm::RouterService;
+use crate::router::orchestrator::OrchestratorService;
+use crate::state::StateStorage;
+
+/// Shared application state bundled into a single Arc-wrapped struct.
+///
+/// Instead of cloning 8+ individual `Arc`s per connection, a single
+/// `Arc` is cloned once and passed to the request handler.
+pub struct AppState {
+ pub router_service: Arc,
+ pub orchestrator_service: Arc,
+ pub model_aliases: Option>,
+ pub llm_providers: Arc>,
+ pub agents_list: Option>,
+ pub listeners: Vec,
+ pub state_storage: Option>,
+ pub llm_provider_url: String,
+ pub span_attributes: Option,
+ /// Shared HTTP client for upstream LLM requests (connection pooling / keep-alive).
+ pub http_client: reqwest::Client,
+ pub filter_pipeline: Arc,
+}
diff --git a/crates/brightstaff/src/handlers/agents/errors.rs b/crates/brightstaff/src/handlers/agents/errors.rs
new file mode 100644
index 00000000..478b4380
--- /dev/null
+++ b/crates/brightstaff/src/handlers/agents/errors.rs
@@ -0,0 +1,41 @@
+use bytes::Bytes;
+use http_body_util::combinators::BoxBody;
+use hyper::Response;
+use serde_json::json;
+use tracing::{info, warn};
+
+use crate::handlers::response::ResponseHandler;
+
+/// Build a JSON error response from an `AgentFilterChainError`, logging the
+/// full error chain along the way.
+///
+/// Returns `Ok(Response)` so it can be used directly as a handler return value.
+pub fn build_error_chain_response(
+ err: &E,
+) -> Result>, hyper::Error> {
+ let mut error_chain = Vec::new();
+ let mut current: &dyn std::error::Error = err;
+ loop {
+ error_chain.push(current.to_string());
+ match current.source() {
+ Some(source) => current = source,
+ None => break,
+ }
+ }
+
+ warn!(error_chain = ?error_chain, "agent chat error chain");
+ warn!(root_error = ?err, "root error");
+
+ let error_json = json!({
+ "error": {
+ "type": "AgentFilterChainError",
+ "message": err.to_string(),
+ "error_chain": error_chain,
+ "debug_info": format!("{:?}", err)
+ }
+ });
+
+ info!(error = %error_json, "structured error info");
+
+ Ok(ResponseHandler::create_json_error_response(&error_json))
+}
diff --git a/crates/brightstaff/src/handlers/jsonrpc.rs b/crates/brightstaff/src/handlers/agents/jsonrpc.rs
similarity index 100%
rename from crates/brightstaff/src/handlers/jsonrpc.rs
rename to crates/brightstaff/src/handlers/agents/jsonrpc.rs
diff --git a/crates/brightstaff/src/handlers/agents/mod.rs b/crates/brightstaff/src/handlers/agents/mod.rs
new file mode 100644
index 00000000..5b507907
--- /dev/null
+++ b/crates/brightstaff/src/handlers/agents/mod.rs
@@ -0,0 +1,5 @@
+pub mod errors;
+pub mod jsonrpc;
+pub mod orchestrator;
+pub mod pipeline;
+pub mod selector;
diff --git a/crates/brightstaff/src/handlers/agent_chat_completions.rs b/crates/brightstaff/src/handlers/agents/orchestrator.rs
similarity index 50%
rename from crates/brightstaff/src/handlers/agent_chat_completions.rs
rename to crates/brightstaff/src/handlers/agents/orchestrator.rs
index 513e0ef2..8ece914e 100644
--- a/crates/brightstaff/src/handlers/agent_chat_completions.rs
+++ b/crates/brightstaff/src/handlers/agents/orchestrator.rs
@@ -2,63 +2,56 @@ use std::sync::Arc;
use std::time::Instant;
use bytes::Bytes;
-use common::configuration::SpanAttributes;
-use common::errors::BrightStaffError;
-use common::llm_providers::LlmProviders;
use hermesllm::apis::OpenAIMessage;
use hermesllm::clients::SupportedAPIsFromClient;
use hermesllm::providers::request::ProviderRequest;
use hermesllm::ProviderRequestType;
use http_body_util::combinators::BoxBody;
use http_body_util::BodyExt;
-use hyper::{Request, Response, StatusCode};
+use hyper::{Request, Response};
use opentelemetry::trace::get_active_span;
-use serde::ser::Error as SerError;
-use tokio::sync::RwLock;
use tracing::{debug, info, info_span, warn, Instrument};
-use super::agent_selector::{AgentSelectionError, AgentSelector};
-use super::pipeline_processor::{PipelineError, PipelineProcessor};
-use super::response_handler::ResponseHandler;
-use crate::router::plano_orchestrator::OrchestratorService;
+use super::errors::build_error_chain_response;
+use super::pipeline::{PipelineError, PipelineProcessor};
+use super::selector::{AgentSelectionError, AgentSelector};
+use crate::app_state::AppState;
+use crate::handlers::extract_request_id;
+use crate::handlers::response::ResponseHandler;
use crate::tracing::{collect_custom_trace_attributes, operation_component, set_service_name};
/// Main errors for agent chat completions
#[derive(Debug, thiserror::Error)]
pub enum AgentFilterChainError {
- #[error("Forwarded error: {0}")]
- Brightstaff(#[from] BrightStaffError),
#[error("Agent selection error: {0}")]
Selection(#[from] AgentSelectionError),
#[error("Pipeline processing error: {0}")]
Pipeline(#[from] PipelineError),
+ #[error("Response handling error: {0}")]
+ Response(#[from] common::errors::BrightStaffError),
#[error("Request parsing error: {0}")]
- RequestParsing(#[from] serde_json::Error),
+ RequestParsing(String),
#[error("HTTP error: {0}")]
Http(#[from] hyper::Error),
+ #[error("Unsupported endpoint: {0}")]
+ UnsupportedEndpoint(String),
+ #[error("No agents configured")]
+ NoAgentsConfigured,
+ #[error("Agent '{0}' not found in configuration")]
+ AgentNotFound(String),
+ #[error("No messages in conversation history")]
+ EmptyHistory,
+ #[error("Agent chain completed without producing a response")]
+ IncompleteChain,
}
pub async fn agent_chat(
request: Request,
- orchestrator_service: Arc,
- _: String,
- agents_list: Arc>>>,
- listeners: Arc>>,
- span_attributes: Arc