From 50f9b1e80225649e8c367a72b0b8b88af3e17b52 Mon Sep 17 00:00:00 2001 From: shashankpandey9 Date: Fri, 27 Mar 2026 01:59:06 +0530 Subject: [PATCH 1/9] Enhance agent functionality: update environment variables, add A2A wrapper, improve agent descriptions and routing logic --- .../.env.example | 7 +- .../agent_server/a2a_wrapper.py | 48 ++++++++ .../agent_server/agent.py | 103 ++++++++++++------ .../agent_server/start_server.py | 10 ++ .../databricks.yml | 6 +- .../pyproject.toml | 3 + 6 files changed, 135 insertions(+), 42 deletions(-) create mode 100644 agent-openai-agents-sdk-multiagent/agent_server/a2a_wrapper.py diff --git a/agent-openai-agents-sdk-multiagent/.env.example b/agent-openai-agents-sdk-multiagent/.env.example index 10a56922..c5be01f5 100644 --- a/agent-openai-agents-sdk-multiagent/.env.example +++ b/agent-openai-agents-sdk-multiagent/.env.example @@ -3,12 +3,13 @@ # TODO: Fill in auth related env vars DATABRICKS_CONFIG_PROFILE=DEFAULT -# DATABRICKS_HOST=https://.databricks.com -# DATABRICKS_TOKEN=dapi.... +DATABRICKS_HOST=https://dbc-5c6e6e7d-7beb.cloud.databricks.com +DATABRICKS_TOKEN= # TODO: Update with the MLflow experiment you want to log traces and models to -MLFLOW_EXPERIMENT_ID= +MLFLOW_EXPERIMENT_ID="4447352726644559" +# Frontend port and timeout settings CHAT_APP_PORT=3000 CHAT_PROXY_TIMEOUT_SECONDS=300 # IMPORTANT: For local development, use databricks (for default profile) or databricks:// to specify which Databricks CLI profile to use diff --git a/agent-openai-agents-sdk-multiagent/agent_server/a2a_wrapper.py b/agent-openai-agents-sdk-multiagent/agent_server/a2a_wrapper.py new file mode 100644 index 00000000..ddf84efb --- /dev/null +++ b/agent-openai-agents-sdk-multiagent/agent_server/a2a_wrapper.py @@ -0,0 +1,48 @@ +""" +A2A wrapper: Exposes each subagent as an A2A-compatible server +with Agent Cards for discovery. +""" +import json +from fastapi import FastAPI, Request +from fastapi.responses import JSONResponse + +def create_agent_card(name: str, description: str, skills: list, url: str): + """Generate an A2A-compliant Agent Card.""" + return { + "name": name, + "description": description, + "url": url, + "version": "1.0.0", + "capabilities": { + "streaming": True, + "pushNotifications": False, + }, + "skills": [ + { + "id": skill["id"], + "name": skill["name"], + "description": skill["description"], + "tags": skill.get("tags", []), + } + for skill in skills + ], + } + +def add_a2a_endpoints(app: FastAPI, agent_card: dict): + """Add A2A discovery endpoint to an existing FastAPI app.""" + + @app.get("/.well-known/agent.json") + async def get_agent_card(): + return JSONResponse(content=agent_card) + + @app.post("/a2a/tasks") + async def handle_task(request: Request): + body = await request.json() + # Bridge A2A task → existing /invocations endpoint + user_message = body.get("input", [{}])[0].get("parts", [{}])[0].get("text", "") + # Forward to the same agent logic + return JSONResponse(content={ + "id": body.get("id"), + "state": "completed", + "output": {"parts": [{"type": "text", "text": f"Processed: {user_message}"}]}, + }) \ No newline at end of file diff --git a/agent-openai-agents-sdk-multiagent/agent_server/agent.py b/agent-openai-agents-sdk-multiagent/agent_server/agent.py index f4eaf908..77ec4633 100644 --- a/agent-openai-agents-sdk-multiagent/agent_server/agent.py +++ b/agent-openai-agents-sdk-multiagent/agent_server/agent.py @@ -58,35 +58,40 @@ SUBAGENTS = [ # Uncomment and configure the subagents you need. You must enable at least one. - # - # { - # "name": "genie", - # "type": "genie", - # "space_id": "", # UUID from the Genie space URL - # "description": ( - # "Query a Genie space for structured data analysis. " - # "Use this for questions about data, metrics, and tables." - # ), - # }, - # { - # "name": "app_agent", - # "type": "app", - # "endpoint": "", # TODO: set to your Databricks App name - # "description": ( - # "Query a specialist agent deployed as a Databricks App. " - # "Use this for questions the specialist app agent handles." - # ), - # }, - # { - # "name": "knowledge_assistant", - # "type": "serving_endpoint", - # "endpoint": "", # flat name, NOT a Vector Search index - # "description": ( - # "Query the knowledge-assistant endpoint on Model Serving. " - # "Use this for knowledge-base / documentation lookups. " - # "The endpoint must have task type agent/v1/responses." - # ), - # }, + + { + "name": "genie", + "type": "genie", + "space_id": "01f1194369d51722bebd39bfdaf266d7", # UUID from the Genie space URL + "description": ( + "Query a Genie space for structured data analysis. " + "Use this for questions about data, metrics, and tables." + "Query structured data and generate charts/visualizations. " + "Use this for SQL queries, metrics, dashboards, bar charts, line charts, pie charts, stack bar charts, histogram charts, funnel charts, etc. , trends, and any tabular data analysis." + ), + }, + { + "name": "app_agent", + "type": "app", + "endpoint": "agent-code-analyzer", # TODO: set to your Databricks App name + "description": ( + "Query a specialist agent deployed as a Databricks App. " + "Use this this agent questions about the codebase, get explanations of code snippets, and generate new code based on the codebase. Use this for software development, code analysis, and any code-related questions. Prefer this for code-related questions. Additionally, it helps to fix bugs and suggest improvements in the codebase if the user asks for it or provides github repository links." + ), + }, + { + "name": "knowledge_assistant", + "type": "serving_endpoint", + "endpoint": "ka-6a709152-endpoint", # flat name, NOT a Vector Search index + "description": ( + "Query the knowledge-assistant endpoint on Model Serving. " + # "Use this for knowledge-base / documentation lookups. " + "The endpoint must have task type agent/v1/responses." + "Search through documents, PDFs, knowledge bases, and " + "unstructured text. Use this for document summarization, " + "RAG-based Q&A, and extracting info from unstructured sources." + ), + }, # { # "name": "serving_endpoint", # "type": "serving_endpoint", @@ -170,13 +175,39 @@ def create_orchestrator_agent(mcp_server: McpServer) -> Agent: return Agent( name="Orchestrator", instructions=( - "You are an orchestrator agent. Route the user's request to the " - "most appropriate tool or data source:\n" - "- Use the Genie MCP tools for questions about structured data.\n" - "- Use query_app_agent for questions that the specialist app agent handles.\n" - "- Use query_knowledge_assistant for knowledge-base / documentation lookups.\n" - "- Use query_serving_endpoint for questions best answered by the serving model.\n" - "If unsure, ask the user for clarification." + # "You are an orchestrator agent. Route the user's request to the " + # "most appropriate tool or data source:\n" + # "- Use the Genie MCP tools for questions about structured data.\n" + # "- Use query_app_agent for questions that the specialist app agent handles.\n" + # "- Use query_knowledge_assistant for knowledge-base / documentation lookups.\n" + # "- Use query_serving_endpoint for questions best answered by the serving model.\n" + # "If unsure, ask the user for clarification." + "You are the AI Orchestrator for Gilead Organization. " + "Your role is to understand the user's intent and route their " + "request to the most appropriate specialist agent.\n\n" + + "## Routing Rules:\n" + "- **Structured Data / Charts / Visualization**: Use the Genie MCP tools " + "for any question about sales data, revenue metrics, KPIs, dashboards, " + "charts, bar graphs, trend analysis, or any SQL-queryable data.\n" + "- **Documents / Knowledge Base / Unstructured Data**: Use " + "query_knowledge_assistant for questions about policies, SOPs, reports, " + "PDFs, compliance documents, or any text-based knowledge lookup.\n" + "- **Business Operations / Custom Logic**: Use query_custom_business_agent " + "for workflow triggers, compliance checks, custom calculations, " + "or domain-specific business operations.\n\n" + + "## Business Context:\n" + "- Our company deals with [YOUR INDUSTRY/DOMAIN].\n" + "- Key data tables include: [sales, inventory, customers, etc.].\n" + "- Key document repositories cover: [HR policies, product docs, etc.].\n\n" + + "## Response Guidelines:\n" + "- Always be professional and concise.\n" + "- When returning chart data, describe what the chart shows.\n" + "- When citing documents, mention the source.\n" + "- If unsure which agent to use, ask the user for clarification.\n" + "- Never fabricate data — only use what the specialist agents return." ), model="databricks-claude-sonnet-4-5", # TODO: change model if desired mcp_servers=[mcp_server] if mcp_server else [], diff --git a/agent-openai-agents-sdk-multiagent/agent_server/start_server.py b/agent-openai-agents-sdk-multiagent/agent_server/start_server.py index 6f870e5b..db4a9251 100644 --- a/agent-openai-agents-sdk-multiagent/agent_server/start_server.py +++ b/agent-openai-agents-sdk-multiagent/agent_server/start_server.py @@ -3,6 +3,8 @@ from dotenv import load_dotenv from mlflow.genai.agent_server import AgentServer, setup_mlflow_git_based_version_tracking +from agent_server.a2a_wrapper import add_a2a_endpoints, create_agent_card + # Load env vars from .env before importing the agent for proper auth load_dotenv(dotenv_path=Path(__file__).parent.parent / ".env", override=True) @@ -14,6 +16,14 @@ app = agent_server.app # noqa: F841 setup_mlflow_git_based_version_tracking() +# After app is created: +card = create_agent_card( + name="MultiAgentOrchestrator", + description="Routes queries to chart, knowledge, and custom agents", + skills=[{"id": "route", "name": "Route Query", "description": "Route to specialist"}], + url="http://localhost:8000", +) +add_a2a_endpoints(app, card) def main(): agent_server.run(app_import_string="agent_server.start_server:app") diff --git a/agent-openai-agents-sdk-multiagent/databricks.yml b/agent-openai-agents-sdk-multiagent/databricks.yml index f6bdacf4..42147df6 100644 --- a/agent-openai-agents-sdk-multiagent/databricks.yml +++ b/agent-openai-agents-sdk-multiagent/databricks.yml @@ -27,20 +27,20 @@ resources: resources: - name: 'experiment' experiment: - experiment_id: "" + experiment_id: "3517595242439019" permission: 'CAN_MANAGE' # TODO: Set your Genie space ID - name: 'genie_space' genie_space: name: 'Genie Space' - space_id: '' + space_id: '01f1194369d51722bebd39bfdaf266d7' permission: 'CAN_RUN' # TODO: Set your knowledge-assistant serving endpoint name - name: 'knowledge_assistant' serving_endpoint: - name: '' + name: 'ka-6a709152-endpoint' permission: 'CAN_QUERY' # TODO: Set your serving endpoint name diff --git a/agent-openai-agents-sdk-multiagent/pyproject.toml b/agent-openai-agents-sdk-multiagent/pyproject.toml index 6bf7af1f..09a5016e 100644 --- a/agent-openai-agents-sdk-multiagent/pyproject.toml +++ b/agent-openai-agents-sdk-multiagent/pyproject.toml @@ -43,3 +43,6 @@ start-server = "agent_server.start_server:main" agent-evaluate = "agent_server.evaluate_agent:evaluate" discover-tools = "scripts.discover_tools:main" preflight = "scripts.preflight:main" + +cd agent-openai-agents-sdk-multiagent +uv add python-a2a # A2A protocol SDK \ No newline at end of file From e8a54b6a72194e883f54beaadd1970b50c7e7964 Mon Sep 17 00:00:00 2001 From: shashankpandey9 Date: Fri, 27 Mar 2026 02:01:14 +0530 Subject: [PATCH 2/9] Add Databricks token to .env.example Added Databricks token to the example environment file. --- agent-openai-agents-sdk-multiagent/.env.example | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/agent-openai-agents-sdk-multiagent/.env.example b/agent-openai-agents-sdk-multiagent/.env.example index c5be01f5..b030e78d 100644 --- a/agent-openai-agents-sdk-multiagent/.env.example +++ b/agent-openai-agents-sdk-multiagent/.env.example @@ -4,7 +4,7 @@ # TODO: Fill in auth related env vars DATABRICKS_CONFIG_PROFILE=DEFAULT DATABRICKS_HOST=https://dbc-5c6e6e7d-7beb.cloud.databricks.com -DATABRICKS_TOKEN= +DATABRICKS_TOKEN="dapie7d74bbe5877004da96c8fc8feb70581" # TODO: Update with the MLflow experiment you want to log traces and models to MLFLOW_EXPERIMENT_ID="4447352726644559" From 2a854204f8720ce3accdc412981e0f301e8eb4bf Mon Sep 17 00:00:00 2001 From: shashankpandey9 Date: Fri, 27 Mar 2026 02:06:15 +0530 Subject: [PATCH 3/9] Clear DATABRICKS_TOKEN in .env.example Remove sensitive DATABRICKS_TOKEN value from example file. --- agent-openai-agents-sdk-multiagent/.env.example | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/agent-openai-agents-sdk-multiagent/.env.example b/agent-openai-agents-sdk-multiagent/.env.example index b030e78d..51a77621 100644 --- a/agent-openai-agents-sdk-multiagent/.env.example +++ b/agent-openai-agents-sdk-multiagent/.env.example @@ -4,7 +4,7 @@ # TODO: Fill in auth related env vars DATABRICKS_CONFIG_PROFILE=DEFAULT DATABRICKS_HOST=https://dbc-5c6e6e7d-7beb.cloud.databricks.com -DATABRICKS_TOKEN="dapie7d74bbe5877004da96c8fc8feb70581" +DATABRICKS_TOKEN="" # TODO: Update with the MLflow experiment you want to log traces and models to MLFLOW_EXPERIMENT_ID="4447352726644559" From 4f9ce2b37df8d29b9091603fcb910ffa3ed8b8f1 Mon Sep 17 00:00:00 2001 From: shashankpandey9 Date: Fri, 27 Mar 2026 02:22:54 +0530 Subject: [PATCH 4/9] Rename app agent to agent-code-analyzer and add Master Prompt Agent with updated serving endpoint in configuration --- .../agent_server/agent.py | 22 +++++++++---------- .../databricks.yml | 2 +- 2 files changed, 12 insertions(+), 12 deletions(-) diff --git a/agent-openai-agents-sdk-multiagent/agent_server/agent.py b/agent-openai-agents-sdk-multiagent/agent_server/agent.py index 77ec4633..76d25e23 100644 --- a/agent-openai-agents-sdk-multiagent/agent_server/agent.py +++ b/agent-openai-agents-sdk-multiagent/agent_server/agent.py @@ -71,7 +71,7 @@ ), }, { - "name": "app_agent", + "name": "agent-code-analyzer", "type": "app", "endpoint": "agent-code-analyzer", # TODO: set to your Databricks App name "description": ( @@ -92,16 +92,16 @@ "RAG-based Q&A, and extracting info from unstructured sources." ), }, - # { - # "name": "serving_endpoint", - # "type": "serving_endpoint", - # "endpoint": "", - # "description": ( - # "Query a model hosted on a Databricks Model Serving endpoint. " - # "Use this for questions best answered by the serving model. " - # "The endpoint must have task type agent/v1/responses." - # ), - # }, + { + "name": "Master Prompt Agent", + "type": "serving_endpoint", + "endpoint": "databricks-gpt-5-4", + "description": ( + "Query a model hosted on a Databricks Model Serving endpoint. " + "Use this for generate master prompt instructions as per user query." + "The endpoint must have task type agent/v1/responses." + ), + }, ] assert SUBAGENTS, ( diff --git a/agent-openai-agents-sdk-multiagent/databricks.yml b/agent-openai-agents-sdk-multiagent/databricks.yml index 42147df6..cf89b9c0 100644 --- a/agent-openai-agents-sdk-multiagent/databricks.yml +++ b/agent-openai-agents-sdk-multiagent/databricks.yml @@ -46,7 +46,7 @@ resources: # TODO: Set your serving endpoint name - name: 'serving_endpoint' serving_endpoint: - name: '' + name: 'databricks-gpt-5-4' permission: 'CAN_QUERY' # NOTE: App-to-app permissions are not yet supported as bundle resources. From 2c54f8b0de3a6a3ea0c10a9a07de39500ea71aef Mon Sep 17 00:00:00 2001 From: shashankpandey9 Date: Fri, 27 Mar 2026 02:30:11 +0530 Subject: [PATCH 5/9] Update MLflow experiment ID in .env.example --- agent-openai-agents-sdk-multiagent/.env.example | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/agent-openai-agents-sdk-multiagent/.env.example b/agent-openai-agents-sdk-multiagent/.env.example index 51a77621..dff10a51 100644 --- a/agent-openai-agents-sdk-multiagent/.env.example +++ b/agent-openai-agents-sdk-multiagent/.env.example @@ -7,7 +7,7 @@ DATABRICKS_HOST=https://dbc-5c6e6e7d-7beb.cloud.databricks.com DATABRICKS_TOKEN="" # TODO: Update with the MLflow experiment you want to log traces and models to -MLFLOW_EXPERIMENT_ID="4447352726644559" +MLFLOW_EXPERIMENT_ID="3517595242439019" # Frontend port and timeout settings CHAT_APP_PORT=3000 From 0b3dec9318e339d103f00d65a62b546e9e910c4b Mon Sep 17 00:00:00 2001 From: shashankpandey9 Date: Fri, 27 Mar 2026 03:18:52 +0530 Subject: [PATCH 6/9] Refactor agent names in SUBAGENTS: use snake_case for consistency --- agent-openai-agents-sdk-multiagent/agent_server/agent.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/agent-openai-agents-sdk-multiagent/agent_server/agent.py b/agent-openai-agents-sdk-multiagent/agent_server/agent.py index 76d25e23..093ec75a 100644 --- a/agent-openai-agents-sdk-multiagent/agent_server/agent.py +++ b/agent-openai-agents-sdk-multiagent/agent_server/agent.py @@ -71,7 +71,7 @@ ), }, { - "name": "agent-code-analyzer", + "name": "agent_code_analyzer", "type": "app", "endpoint": "agent-code-analyzer", # TODO: set to your Databricks App name "description": ( @@ -93,9 +93,9 @@ ), }, { - "name": "Master Prompt Agent", + "name": "master_prompt_agent", "type": "serving_endpoint", - "endpoint": "databricks-gpt-5-4", + "endpoint": "https://dbc-5c6e6e7d-7beb.cloud.databricks.com/serving-endpoints/databricks-gpt-5-4/invocations", "description": ( "Query a model hosted on a Databricks Model Serving endpoint. " "Use this for generate master prompt instructions as per user query." From a329f806c326a5236d5b82b40b1651ab85e81fdc Mon Sep 17 00:00:00 2001 From: shashankpandey9 Date: Fri, 27 Mar 2026 15:22:45 +0530 Subject: [PATCH 7/9] Add required packages to requirements.txt for Databricks and OpenAI integration --- agent-openai-agents-sdk-multiagent/requirements.txt | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/agent-openai-agents-sdk-multiagent/requirements.txt b/agent-openai-agents-sdk-multiagent/requirements.txt index 60cc5e6a..3e9410ce 100644 --- a/agent-openai-agents-sdk-multiagent/requirements.txt +++ b/agent-openai-agents-sdk-multiagent/requirements.txt @@ -1 +1,5 @@ uv +databricks-sdk +openai +databricks_cli + From 26cd48199a36485b79f03e6d70b3c14b50ac6e08 Mon Sep 17 00:00:00 2001 From: shashankpandey9 Date: Fri, 27 Mar 2026 16:09:43 +0530 Subject: [PATCH 8/9] fix: A2A protocol integration, broken pyproject.toml, and wire real orchestrator logic - Fix pyproject.toml: remove accidentally appended shell commands, add a2a-sdk dependency - Rewrite a2a_wrapper.py: full A2A v1.0 HTTP+JSON/REST binding with Agent Card discovery, SendMessage (blocking + streaming), GetTask, ListTasks, CancelTask - Wire A2A endpoints to real orchestrator: invoke_handler and stream_handler from agent.py are now called from A2A endpoints instead of returning placeholder text - Fix start_server.py: use build_agent_card() with proper skills matching actual subagents - Fix requirements.txt: remove legacy databricks_cli Python package (conflicts with new CLI) - Clean up orchestrator instructions: remove duplicate routing rules, fix agent name references to match actual SUBAGENTS config (query_agent_code_analyzer, query_master_prompt_agent) - Add scripts/setup_cli_windows.bat: Windows setup for new Databricks CLI via winget, auto-removes legacy CLI, configures OAuth auth --- .../agent_server/a2a_wrapper.py | 459 +++++++++++++++++- .../agent_server/agent.py | 16 +- .../agent_server/start_server.py | 13 +- .../pyproject.toml | 3 +- .../requirements.txt | 1 - .../scripts/setup_cli_windows.bat | 83 ++++ 6 files changed, 528 insertions(+), 47 deletions(-) create mode 100644 agent-openai-agents-sdk-multiagent/scripts/setup_cli_windows.bat diff --git a/agent-openai-agents-sdk-multiagent/agent_server/a2a_wrapper.py b/agent-openai-agents-sdk-multiagent/agent_server/a2a_wrapper.py index ddf84efb..b485687e 100644 --- a/agent-openai-agents-sdk-multiagent/agent_server/a2a_wrapper.py +++ b/agent-openai-agents-sdk-multiagent/agent_server/a2a_wrapper.py @@ -1,48 +1,455 @@ """ -A2A wrapper: Exposes each subagent as an A2A-compatible server -with Agent Cards for discovery. +A2A (Agent-to-Agent) Protocol wrapper — HTTP+JSON/REST binding (v1.0). + +Implements the A2A specification endpoints on top of the existing +MLflow Responses-API agent server so that *other* A2A-compatible agents +(or orchestrators) can discover and interact with this multi-agent +orchestrator through the standard A2A protocol. + +Key endpoints exposed: + GET /.well-known/agent-card.json → Agent Card discovery + POST /a2a/message:send → Send message (blocking) + POST /a2a/message:stream → Send message (SSE streaming) + GET /a2a/tasks/{task_id} → Get task status + GET /a2a/tasks → List tasks + POST /a2a/tasks/{task_id}:cancel → Cancel task + +Reference: https://a2a-protocol.org/latest/specification/ """ + +from __future__ import annotations + import json +import logging +import os +from datetime import datetime, timezone +from typing import Any, Dict, List +from uuid import uuid4 + from fastapi import FastAPI, Request -from fastapi.responses import JSONResponse +from fastapi.responses import JSONResponse, StreamingResponse + +logger = logging.getLogger(__name__) + +# --------------------------------------------------------------------------- +# In-memory task store (swap for Redis / DB in production) +# --------------------------------------------------------------------------- +_tasks: Dict[str, Dict[str, Any]] = {} + + +# --------------------------------------------------------------------------- +# A2A Agent Card builder (Section 8 / 4.4.1 of the spec) +# --------------------------------------------------------------------------- +def build_agent_card( + *, + name: str = "MultiAgentOrchestrator", + description: str = ( + "Routes queries to chart/visualization, knowledge-base, " + "code-analysis, and custom agents" + ), + base_url: str | None = None, + skills: List[Dict[str, Any]] | None = None, +) -> Dict[str, Any]: + """Return an A2A v1.0 Agent Card (Section 4.4.1).""" + url = base_url or os.getenv("A2A_BASE_URL", "http://localhost:8000") + default_skills = [ + { + "id": "structured-data", + "name": "Structured Data & Visualization", + "description": ( + "Query structured data via Genie spaces and generate charts, " + "dashboards, bar graphs, trend analysis, and SQL-queryable data." + ), + "tags": ["sql", "charts", "visualization", "genie", "metrics"], + "examples": [ + "Show me revenue by region as a bar chart", + "What were Q4 sales trends?", + ], + "inputModes": ["text/plain"], + "outputModes": ["text/plain", "application/json"], + }, + { + "id": "unstructured-data", + "name": "Knowledge Base / Unstructured Data", + "description": ( + "Search documents, PDFs, policies, SOPs, and knowledge bases. " + "Use for RAG-based Q&A, document summarization, and extracting " + "info from unstructured sources." + ), + "tags": ["rag", "documents", "knowledge", "pdf", "search"], + "examples": [ + "Summarize the Q3 compliance report", + "What does the HR policy say about remote work?", + ], + "inputModes": ["text/plain"], + "outputModes": ["text/plain"], + }, + { + "id": "code-analysis", + "name": "Code Analysis Agent", + "description": ( + "Analyze codebases, explain code, generate code, fix bugs, " + "and provide software-development assistance." + ), + "tags": ["code", "development", "analysis", "bugs"], + "examples": [ + "Explain the login module in our repo", + "Find bugs in this function", + ], + "inputModes": ["text/plain"], + "outputModes": ["text/plain"], + }, + { + "id": "custom-logic", + "name": "Custom Business Logic", + "description": ( + "Route to a model serving endpoint for master prompts, " + "custom business calculations, or domain-specific operations." + ), + "tags": ["custom", "business", "prompt", "model"], + "examples": [ + "Generate a master prompt for onboarding", + "Run the compliance check workflow", + ], + "inputModes": ["text/plain"], + "outputModes": ["text/plain"], + }, + ] -def create_agent_card(name: str, description: str, skills: list, url: str): - """Generate an A2A-compliant Agent Card.""" return { "name": name, "description": description, - "url": url, + "supportedInterfaces": [ + { + "url": f"{url}/a2a", + "protocolBinding": "HTTP+JSON", + "protocolVersion": "1.0", + } + ], + "provider": { + "organization": "Gilead Sciences", + "url": url, + }, "version": "1.0.0", "capabilities": { "streaming": True, "pushNotifications": False, }, - "skills": [ + "defaultInputModes": ["text/plain", "application/json"], + "defaultOutputModes": ["text/plain", "application/json"], + "skills": skills or default_skills, + } + + +# --------------------------------------------------------------------------- +# Timestamp / task helpers +# --------------------------------------------------------------------------- + +def _now_iso() -> str: + return datetime.now(timezone.utc).strftime("%Y-%m-%dT%H:%M:%S.%f")[:-3] + "Z" + + +def _make_task( + task_id: str, + context_id: str, + state: str = "TASK_STATE_SUBMITTED", + message: dict | None = None, + artifacts: list | None = None, +) -> Dict[str, Any]: + task: Dict[str, Any] = { + "id": task_id, + "contextId": context_id, + "status": { + "state": state, + "timestamp": _now_iso(), + }, + } + if message: + task["status"]["message"] = message + if artifacts: + task["artifacts"] = artifacts + return task + + +def _json_compact(obj: Any) -> str: + return json.dumps(obj, separators=(",", ":")) + + +# --------------------------------------------------------------------------- +# Core: invoke the real orchestrator agent (blocking) +# --------------------------------------------------------------------------- + +async def _invoke_orchestrator(user_text: str, session_id: str | None = None) -> str: + """Call into the MLflow @invoke handler registered in agent.py.""" + from mlflow.types.responses import ResponsesAgentRequest + + from agent_server.agent import invoke_handler + + request = ResponsesAgentRequest( + input=[{"role": "user", "content": user_text}], + ) + + response = await invoke_handler(request) + + # Extract text from the ResponsesAgentResponse output items + parts: list[str] = [] + for item in response.output: + # OutputMessage → item.content list of ContentPart + if hasattr(item, "content"): + for cp in item.content: + if hasattr(cp, "text"): + parts.append(cp.text) + elif hasattr(item, "text"): + parts.append(item.text) + return "\n".join(parts) if parts else str(response.output) + + +# --------------------------------------------------------------------------- +# Streaming variant +# --------------------------------------------------------------------------- + +async def _stream_orchestrator(user_text: str, task_id: str, context_id: str): + """Yield SSE ``data:`` lines from the streaming orchestrator.""" + from mlflow.types.responses import ResponsesAgentRequest + + from agent_server.agent import stream_handler + + request = ResponsesAgentRequest( + input=[{"role": "user", "content": user_text}], + ) + + # 1) Emit initial Task (working) + task = _make_task(task_id, context_id, "TASK_STATE_WORKING") + _tasks[task_id] = task + yield f"data: {_json_compact({'task': task})}\n\n" + + # 2) Stream artifact chunks + collected: list[str] = [] + try: + async for event in stream_handler(request): + if isinstance(event, dict): + text_chunk = "" + item = event.get("item") + if isinstance(item, dict): + for c in item.get("content", []): + if isinstance(c, dict) and "text" in c: + text_chunk = c["text"] + if text_chunk: + collected.append(text_chunk) + yield f"data: {_json_compact(_artifact_event(task_id, context_id, text_chunk))}\n\n" + except Exception as exc: + logger.exception("A2A streaming error: %s", exc) + err = _status_event(task_id, context_id, "TASK_STATE_FAILED", str(exc)) + _tasks[task_id]["status"] = err["statusUpdate"]["status"] + yield f"data: {_json_compact(err)}\n\n" + return + + # 3) Emit completed + full_text = "".join(collected) or "Task completed." + completed = _make_task( + task_id, + context_id, + "TASK_STATE_COMPLETED", + artifacts=[ { - "id": skill["id"], - "name": skill["name"], - "description": skill["description"], - "tags": skill.get("tags", []), + "artifactId": str(uuid4()), + "name": "result", + "parts": [{"text": full_text}], } - for skill in skills ], + ) + _tasks[task_id] = completed + yield f"data: {_json_compact(_status_event(task_id, context_id, 'TASK_STATE_COMPLETED'))}\n\n" + + +def _artifact_event(task_id: str, context_id: str, text: str) -> dict: + return { + "artifactUpdate": { + "taskId": task_id, + "contextId": context_id, + "artifact": { + "artifactId": str(uuid4()), + "parts": [{"text": text}], + }, + "append": True, + "lastChunk": False, + } } -def add_a2a_endpoints(app: FastAPI, agent_card: dict): - """Add A2A discovery endpoint to an existing FastAPI app.""" - @app.get("/.well-known/agent.json") +def _status_event( + task_id: str, context_id: str, state: str, error_msg: str | None = None +) -> dict: + status: dict = {"state": state, "timestamp": _now_iso()} + if error_msg: + status["message"] = { + "role": "ROLE_AGENT", + "messageId": str(uuid4()), + "parts": [{"text": f"Error: {error_msg}"}], + } + return {"statusUpdate": {"taskId": task_id, "contextId": context_id, "status": status}} + + +# --------------------------------------------------------------------------- +# FastAPI endpoint registration +# --------------------------------------------------------------------------- + +def add_a2a_endpoints(app: FastAPI, agent_card: Dict[str, Any] | None = None): + """Mount all A2A HTTP+JSON/REST endpoints onto an existing FastAPI app.""" + card = agent_card or build_agent_card() + + # ── Agent Card discovery (Section 8.2) ──────────────────────────── + @app.get("/.well-known/agent-card.json") async def get_agent_card(): - return JSONResponse(content=agent_card) + return JSONResponse( + content=card, + headers={"Cache-Control": "public, max-age=3600"}, + ) + + # ── Send Message — blocking (Section 11.3.1 / 3.1.1) ───────────── + @app.post("/a2a/message:send") + async def a2a_send_message(request: Request): + body = await request.json() + message = body.get("message", {}) + user_text = _extract_text(message) + + if not user_text: + return _a2a_error(400, "INVALID_ARGUMENT", "Message must contain at least one text part") + + task_id = str(uuid4()) + context_id = message.get("contextId") or str(uuid4()) + _tasks[task_id] = _make_task(task_id, context_id, "TASK_STATE_WORKING") - @app.post("/a2a/tasks") - async def handle_task(request: Request): + try: + result_text = await _invoke_orchestrator(user_text, session_id=context_id) + completed = _make_task( + task_id, + context_id, + "TASK_STATE_COMPLETED", + artifacts=[ + { + "artifactId": str(uuid4()), + "name": "result", + "parts": [{"text": result_text}], + } + ], + ) + _tasks[task_id] = completed + return JSONResponse(content={"task": completed}) + except Exception as exc: + logger.exception("A2A SendMessage error: %s", exc) + failed = _make_task( + task_id, + context_id, + "TASK_STATE_FAILED", + message={ + "role": "ROLE_AGENT", + "messageId": str(uuid4()), + "parts": [{"text": f"Error: {exc}"}], + }, + ) + _tasks[task_id] = failed + return JSONResponse(content={"task": failed}) + + # ── Send Streaming Message (Section 11.3.1 / 3.1.2) ────────────── + @app.post("/a2a/message:stream") + async def a2a_stream_message(request: Request): body = await request.json() - # Bridge A2A task → existing /invocations endpoint - user_message = body.get("input", [{}])[0].get("parts", [{}])[0].get("text", "") - # Forward to the same agent logic - return JSONResponse(content={ - "id": body.get("id"), - "state": "completed", - "output": {"parts": [{"type": "text", "text": f"Processed: {user_message}"}]}, - }) \ No newline at end of file + message = body.get("message", {}) + user_text = _extract_text(message) + + if not user_text: + return _a2a_error(400, "INVALID_ARGUMENT", "Message must contain at least one text part") + + task_id = str(uuid4()) + context_id = message.get("contextId") or str(uuid4()) + + return StreamingResponse( + _stream_orchestrator(user_text, task_id, context_id), + media_type="text/event-stream", + ) + + # ── Get Task (Section 11.3.2 / 3.1.3) ──────────────────────────── + @app.get("/a2a/tasks/{task_id}") + async def a2a_get_task(task_id: str): + task = _tasks.get(task_id) + if not task: + return _a2a_error(404, "TASK_NOT_FOUND", f"Task '{task_id}' not found") + return JSONResponse(content=task) + + # ── List Tasks (Section 11.3.2 / 3.1.4) ────────────────────────── + @app.get("/a2a/tasks") + async def a2a_list_tasks( + contextId: str | None = None, + status: str | None = None, + pageSize: int = 50, + ): + results = list(_tasks.values()) + if contextId: + results = [t for t in results if t.get("contextId") == contextId] + if status: + results = [t for t in results if t.get("status", {}).get("state") == status] + results.sort( + key=lambda t: t.get("status", {}).get("timestamp", ""), + reverse=True, + ) + page = results[:pageSize] + return JSONResponse( + content={ + "tasks": page, + "totalSize": len(results), + "pageSize": pageSize, + "nextPageToken": "", + } + ) + + # ── Cancel Task (Section 11.3.2 / 3.1.5) ───────────────────────── + @app.post("/a2a/tasks/{task_id}:cancel") + async def a2a_cancel_task(task_id: str): + task = _tasks.get(task_id) + if not task: + return _a2a_error(404, "TASK_NOT_FOUND", f"Task '{task_id}' not found") + + terminal = {"TASK_STATE_COMPLETED", "TASK_STATE_FAILED", "TASK_STATE_CANCELED"} + if task.get("status", {}).get("state") in terminal: + return _a2a_error(409, "TASK_NOT_CANCELABLE", "Task is already in a terminal state") + + task["status"] = {"state": "TASK_STATE_CANCELED", "timestamp": _now_iso()} + return JSONResponse(content=task) + + logger.info("A2A protocol endpoints registered (HTTP+JSON/REST v1.0)") + + +# --------------------------------------------------------------------------- +# Helpers +# --------------------------------------------------------------------------- + +def _extract_text(message: dict) -> str: + """Pull user text from A2A message parts.""" + parts = message.get("parts", []) + texts = [] + for p in parts: + if isinstance(p, dict) and "text" in p: + texts.append(p["text"]) + return " ".join(texts).strip() + + +def _a2a_error(status_code: int, reason: str, detail: str) -> JSONResponse: + """Return an A2A-compliant error response (Section 11.6).""" + return JSONResponse( + status_code=status_code, + content={ + "error": { + "code": status_code, + "status": reason, + "message": detail, + "details": [ + { + "@type": "type.googleapis.com/google.rpc.ErrorInfo", + "reason": reason, + "domain": "a2a-protocol.org", + } + ], + } + }, + ) diff --git a/agent-openai-agents-sdk-multiagent/agent_server/agent.py b/agent-openai-agents-sdk-multiagent/agent_server/agent.py index 093ec75a..6ac7865d 100644 --- a/agent-openai-agents-sdk-multiagent/agent_server/agent.py +++ b/agent-openai-agents-sdk-multiagent/agent_server/agent.py @@ -189,18 +189,16 @@ def create_orchestrator_agent(mcp_server: McpServer) -> Agent: "## Routing Rules:\n" "- **Structured Data / Charts / Visualization**: Use the Genie MCP tools " "for any question about sales data, revenue metrics, KPIs, dashboards, " - "charts, bar graphs, trend analysis, or any SQL-queryable data.\n" + "charts (bar, line, pie, histogram, funnel, stacked bar), trend analysis, " + "or any SQL-queryable data.\n" "- **Documents / Knowledge Base / Unstructured Data**: Use " "query_knowledge_assistant for questions about policies, SOPs, reports, " "PDFs, compliance documents, or any text-based knowledge lookup.\n" - "- **Business Operations / Custom Logic**: Use query_custom_business_agent " - "for workflow triggers, compliance checks, custom calculations, " - "or domain-specific business operations.\n\n" - - "## Business Context:\n" - "- Our company deals with [YOUR INDUSTRY/DOMAIN].\n" - "- Key data tables include: [sales, inventory, customers, etc.].\n" - "- Key document repositories cover: [HR policies, product docs, etc.].\n\n" + "- **Code / Software Development**: Use query_agent_code_analyzer " + "for questions about code, repositories, bug fixes, code generation, " + "or software development tasks.\n" + "- **Master Prompts / Custom Logic**: Use query_master_prompt_agent " + "for generating master prompt instructions or custom model queries.\n\n" "## Response Guidelines:\n" "- Always be professional and concise.\n" diff --git a/agent-openai-agents-sdk-multiagent/agent_server/start_server.py b/agent-openai-agents-sdk-multiagent/agent_server/start_server.py index db4a9251..ab136ac3 100644 --- a/agent-openai-agents-sdk-multiagent/agent_server/start_server.py +++ b/agent-openai-agents-sdk-multiagent/agent_server/start_server.py @@ -3,7 +3,7 @@ from dotenv import load_dotenv from mlflow.genai.agent_server import AgentServer, setup_mlflow_git_based_version_tracking -from agent_server.a2a_wrapper import add_a2a_endpoints, create_agent_card +from agent_server.a2a_wrapper import add_a2a_endpoints, build_agent_card # Load env vars from .env before importing the agent for proper auth load_dotenv(dotenv_path=Path(__file__).parent.parent / ".env", override=True) @@ -16,14 +16,9 @@ app = agent_server.app # noqa: F841 setup_mlflow_git_based_version_tracking() -# After app is created: -card = create_agent_card( - name="MultiAgentOrchestrator", - description="Routes queries to chart, knowledge, and custom agents", - skills=[{"id": "route", "name": "Route Query", "description": "Route to specialist"}], - url="http://localhost:8000", -) -add_a2a_endpoints(app, card) +# Register A2A protocol endpoints (HTTP+JSON/REST binding v1.0) +# This exposes /.well-known/agent-card.json, /a2a/message:send, etc. +add_a2a_endpoints(app, build_agent_card()) def main(): agent_server.run(app_import_string="agent_server.start_server:app") diff --git a/agent-openai-agents-sdk-multiagent/pyproject.toml b/agent-openai-agents-sdk-multiagent/pyproject.toml index 09a5016e..28c58bf2 100644 --- a/agent-openai-agents-sdk-multiagent/pyproject.toml +++ b/agent-openai-agents-sdk-multiagent/pyproject.toml @@ -15,6 +15,7 @@ dependencies = [ "mlflow>=3.10.0", "openai-agents>=0.4.1", "python-dotenv>=1.2.1", + "a2a-sdk>=0.2.7", ] [build-system] @@ -44,5 +45,3 @@ agent-evaluate = "agent_server.evaluate_agent:evaluate" discover-tools = "scripts.discover_tools:main" preflight = "scripts.preflight:main" -cd agent-openai-agents-sdk-multiagent -uv add python-a2a # A2A protocol SDK \ No newline at end of file diff --git a/agent-openai-agents-sdk-multiagent/requirements.txt b/agent-openai-agents-sdk-multiagent/requirements.txt index 3e9410ce..ba5c7ad4 100644 --- a/agent-openai-agents-sdk-multiagent/requirements.txt +++ b/agent-openai-agents-sdk-multiagent/requirements.txt @@ -1,5 +1,4 @@ uv databricks-sdk openai -databricks_cli diff --git a/agent-openai-agents-sdk-multiagent/scripts/setup_cli_windows.bat b/agent-openai-agents-sdk-multiagent/scripts/setup_cli_windows.bat new file mode 100644 index 00000000..255d4107 --- /dev/null +++ b/agent-openai-agents-sdk-multiagent/scripts/setup_cli_windows.bat @@ -0,0 +1,83 @@ +@echo off +REM ================================================================ +REM Databricks CLI Setup for Windows +REM This script installs the new Databricks CLI and configures +REM OAuth authentication for the multi-agent orchestrator. +REM +REM Reference: https://docs.databricks.com/aws/en/dev-tools/cli/install +REM Migration: https://docs.databricks.com/aws/en/dev-tools/cli/migrate +REM ================================================================ + +echo. +echo ============================================================ +echo Databricks CLI Setup for Windows +echo ============================================================ +echo. + +REM Step 1: Check if legacy CLI is installed (Python package) +echo [1/4] Checking for legacy Databricks CLI (Python package)... +pip show databricks-cli >nul 2>&1 +if %ERRORLEVEL% EQU 0 ( + echo ⚠ Legacy databricks-cli Python package detected. + echo Uninstalling to avoid conflicts... + pip uninstall databricks-cli -y + echo ✓ Legacy CLI removed. +) else ( + echo ✓ No legacy CLI found. +) + +echo. + +REM Step 2: Install new CLI via winget +echo [2/4] Installing Databricks CLI via winget... +where databricks >nul 2>&1 +if %ERRORLEVEL% EQU 0 ( + echo ✓ Databricks CLI already installed. + databricks -v +) else ( + echo Installing via winget... + winget install Databricks.DatabricksCLI + echo. + echo ⚠ IMPORTANT: Please restart your terminal after installation, + echo then re-run this script to complete authentication setup. + echo. + pause + exit /b 0 +) + +echo. + +REM Step 3: Verify version +echo [3/4] Verifying CLI version... +databricks -v +echo. + +REM Step 4: Authenticate +echo [4/4] Setting up OAuth authentication... +echo This will open a browser for Databricks login. +echo Workspace: https://dbc-5c6e6e7d-7beb.cloud.databricks.com +echo. +databricks auth login --host https://dbc-5c6e6e7d-7beb.cloud.databricks.com +if %ERRORLEVEL% EQU 0 ( + echo. + echo ✓ Authentication successful! + echo. + echo Verifying auth profile... + databricks auth profiles +) else ( + echo. + echo ✗ Authentication failed. Try running manually: + echo databricks auth login --host https://dbc-5c6e6e7d-7beb.cloud.databricks.com +) + +echo. +echo ============================================================ +echo Setup Complete! +echo. +echo Next steps: +echo 1. Run: uv run start-app +echo 2. Open: http://localhost:8000 +echo 3. A2A Agent Card: http://localhost:8000/.well-known/agent-card.json +echo ============================================================ +echo. +pause From adf024b54120dbffcb875b4b5df0a909a8b9c63f Mon Sep 17 00:00:00 2001 From: shashankpandey9 Date: Fri, 27 Mar 2026 17:00:37 +0530 Subject: [PATCH 9/9] Add integration tests for chat history, token forwarding, and trace ID capture - Implement tests for the /api/history endpoint to verify chat history retrieval, authentication requirements, and pagination. - Create tests for OBO token forwarding to ensure correct handling of user and service provider tokens. - Add tests for trace ID capture in API_PROXY mode, validating the end-to-end flow from chat submission to feedback submission. - Introduce a new TypeScript configuration file for the e2e-chatbot-app-next project to enhance type checking and module resolution. --- .../.claude/skills/quickstart/SKILL.md | 77 + .../e2e-chatbot-app-next/.env.example | 62 + .../e2e-chatbot-app-next/.gitignore | 52 + .../e2e-chatbot-app-next/CLAUDE.md | 624 + .../e2e-chatbot-app-next/README.md | 472 + .../e2e-chatbot-app-next/app.yaml | 9 + .../e2e-chatbot-app-next/biome.jsonc | 129 + .../e2e-chatbot-app-next/client/index.html | 16 + .../e2e-chatbot-app-next/client/package.json | 67 + .../client/postcss.config.js | 5 + .../client/public/demo-thumbnail.png | Bin 0 -> 23198 bytes .../public/mouth of the seine, monet.jpg | Bin 0 -> 33497 bytes .../e2e-chatbot-app-next/client/src/App.tsx | 39 + .../client/src/components/DatabricksLogo.tsx | 47 + .../components/animation-assistant-icon.tsx | 222 + .../client/src/components/app-sidebar.tsx | 111 + .../client/src/components/chat-header.tsx | 129 + .../client/src/components/chat.tsx | 342 + .../src/components/data-stream-provider.tsx | 38 + .../databricks-message-citation.tsx | 109 + .../databricks-message-part-transformers.ts | 84 + .../src/components/elements/actions.tsx | 66 + .../src/components/elements/code-block.tsx | 102 + .../src/components/elements/conversation.tsx | 27 + .../client/src/components/elements/loader.tsx | 96 + .../src/components/elements/mcp-tool.tsx | 246 + .../src/components/elements/message.tsx | 23 + .../src/components/elements/prompt-input.tsx | 159 + .../src/components/elements/reasoning.tsx | 166 + .../src/components/elements/response.tsx | 22 + .../src/components/elements/suggestion.tsx | 35 + .../client/src/components/elements/tool.tsx | 197 + .../client/src/components/greeting.tsx | 19 + .../src/components/icons/AlignCenterIcon.tsx | 34 + .../src/components/icons/AlignJustifyIcon.tsx | 34 + .../src/components/icons/AlignLeftIcon.tsx | 34 + .../src/components/icons/AlignRightIcon.tsx | 34 + .../client/src/components/icons/AppIcon.tsx | 36 + .../src/components/icons/ArrowDownDotIcon.tsx | 34 + .../components/icons/ArrowDownFillIcon.tsx | 34 + .../src/components/icons/ArrowDownIcon.tsx | 36 + .../src/components/icons/ArrowInIcon.tsx | 35 + .../src/components/icons/ArrowLeftIcon.tsx | 36 + .../src/components/icons/ArrowOverIcon.tsx | 34 + .../src/components/icons/ArrowRightIcon.tsx | 36 + .../src/components/icons/ArrowUpDotIcon.tsx | 34 + .../src/components/icons/ArrowUpFillIcon.tsx | 34 + .../src/components/icons/ArrowUpIcon.tsx | 36 + .../components/icons/ArrowsConnectIcon.tsx | 40 + .../src/components/icons/ArrowsUpDownIcon.tsx | 34 + .../src/components/icons/AssistantIcon.tsx | 34 + .../client/src/components/icons/AtIcon.tsx | 36 + .../src/components/icons/AzHorizontalIcon.tsx | 36 + .../src/components/icons/AzVerticalIcon.tsx | 38 + .../src/components/icons/BackupIcon.tsx | 34 + .../src/components/icons/BadgeCodeIcon.tsx | 40 + .../src/components/icons/BadgeCodeOffIcon.tsx | 41 + .../src/components/icons/BarChartIcon.tsx | 32 + .../src/components/icons/BarGroupedIcon.tsx | 36 + .../src/components/icons/BarStackedIcon.tsx | 36 + .../icons/BarStackedPercentageIcon.tsx | 36 + .../icons/BarsAscendingHorizontalIcon.tsx | 31 + .../icons/BarsAscendingVerticalIcon.tsx | 31 + .../icons/BarsDescendingHorizontalIcon.tsx | 31 + .../icons/BarsDescendingVerticalIcon.tsx | 31 + .../src/components/icons/BeakerIcon.tsx | 36 + .../src/components/icons/BinaryIcon.tsx | 36 + .../src/components/icons/BlockQuoteIcon.tsx | 34 + .../client/src/components/icons/BoldIcon.tsx | 36 + .../client/src/components/icons/BookIcon.tsx | 36 + .../src/components/icons/BookmarkFillIcon.tsx | 34 + .../src/components/icons/BookmarkIcon.tsx | 36 + .../client/src/components/icons/BooksIcon.tsx | 42 + .../components/icons/BracketsCheckIcon.tsx | 40 + .../components/icons/BracketsCurlyIcon.tsx | 34 + .../components/icons/BracketsErrorIcon.tsx | 40 + .../components/icons/BracketsSquareIcon.tsx | 36 + .../src/components/icons/BracketsXIcon.tsx | 39 + .../src/components/icons/BranchCheckIcon.tsx | 37 + .../src/components/icons/BranchIcon.tsx | 36 + .../src/components/icons/BranchResetIcon.tsx | 37 + .../components/icons/BriefcaseFillIcon.tsx | 36 + .../src/components/icons/BriefcaseIcon.tsx | 36 + .../client/src/components/icons/BrushIcon.tsx | 34 + .../client/src/components/icons/BugIcon.tsx | 36 + .../components/icons/CalendarClockIcon.tsx | 43 + .../components/icons/CalendarEventIcon.tsx | 37 + .../src/components/icons/CalendarIcon.tsx | 36 + .../components/icons/CalendarRangeIcon.tsx | 43 + .../src/components/icons/CalendarSyncIcon.tsx | 44 + .../src/components/icons/CameraIcon.tsx | 34 + .../components/icons/CaretDownSquareIcon.tsx | 40 + .../components/icons/CaretUpSquareIcon.tsx | 40 + .../src/components/icons/CatalogCloudIcon.tsx | 42 + .../src/components/icons/CatalogGearIcon.tsx | 39 + .../src/components/icons/CatalogHIcon.tsx | 29 + .../src/components/icons/CatalogHomeIcon.tsx | 42 + .../src/components/icons/CatalogIcon.tsx | 36 + .../src/components/icons/CatalogOffIcon.tsx | 43 + .../components/icons/CatalogSharedIcon.tsx | 42 + .../components/icons/CatalogUserHomeIcon.tsx | 40 + .../src/components/icons/CellsSquareIcon.tsx | 36 + .../components/icons/CertifiedFillIcon.tsx | 34 + .../icons/CertifiedFillSmallIcon.tsx | 34 + .../src/components/icons/CertifiedIcon.tsx | 39 + .../client/src/components/icons/ChainIcon.tsx | 38 + .../src/components/icons/ChartLineIcon.tsx | 32 + .../components/icons/CheckCircleBadgeIcon.tsx | 38 + .../components/icons/CheckCircleFillIcon.tsx | 36 + .../src/components/icons/CheckCircleIcon.tsx | 37 + .../components/icons/CheckCircleSmallIcon.tsx | 34 + .../client/src/components/icons/CheckIcon.tsx | 36 + .../src/components/icons/CheckLineIcon.tsx | 36 + .../src/components/icons/CheckSmallIcon.tsx | 36 + .../src/components/icons/CheckboxIcon.tsx | 35 + .../src/components/icons/ChecklistIcon.tsx | 34 + .../icons/ChevronDoubleDownIcon.tsx | 32 + .../icons/ChevronDoubleLeftIcon.tsx | 32 + .../icons/ChevronDoubleLeftOffIcon.tsx | 34 + .../icons/ChevronDoubleRightIcon.tsx | 32 + .../icons/ChevronDoubleRightOffIcon.tsx | 34 + .../components/icons/ChevronDoubleUpIcon.tsx | 32 + .../src/components/icons/ChevronDownIcon.tsx | 36 + .../src/components/icons/ChevronLeftIcon.tsx | 36 + .../src/components/icons/ChevronRightIcon.tsx | 36 + .../src/components/icons/ChevronUpIcon.tsx | 36 + .../client/src/components/icons/ChipIcon.tsx | 37 + .../src/components/icons/CircleIcon.tsx | 31 + .../src/components/icons/CircleOffIcon.tsx | 36 + .../components/icons/CircleOffLargeIcon.tsx | 40 + .../components/icons/CircleOutlineIcon.tsx | 36 + .../icons/CircleOutlineLargeIcon.tsx | 36 + .../src/components/icons/ClipboardIcon.tsx | 36 + .../client/src/components/icons/ClockIcon.tsx | 37 + .../src/components/icons/ClockKeyIcon.tsx | 41 + .../src/components/icons/ClockOffIcon.tsx | 41 + .../client/src/components/icons/CloseIcon.tsx | 36 + .../src/components/icons/CloseSmallIcon.tsx | 36 + .../components/icons/CloudDatabaseIcon.tsx | 40 + .../components/icons/CloudDownloadIcon.tsx | 35 + .../client/src/components/icons/CloudIcon.tsx | 36 + .../src/components/icons/CloudKeyIcon.tsx | 36 + .../src/components/icons/CloudModelIcon.tsx | 40 + .../src/components/icons/CloudOffIcon.tsx | 40 + .../src/components/icons/CloudUploadIcon.tsx | 35 + .../client/src/components/icons/CodeIcon.tsx | 34 + .../src/components/icons/ColorFillIcon.tsx | 36 + .../src/components/icons/ColumnIcon.tsx | 36 + .../src/components/icons/ColumnSplitIcon.tsx | 36 + .../src/components/icons/ColumnsIcon.tsx | 36 + .../src/components/icons/CommandIcon.tsx | 36 + .../components/icons/CommandPaletteIcon.tsx | 36 + .../src/components/icons/CompassIcon.tsx | 42 + .../src/components/icons/ConnectIcon.tsx | 40 + .../client/src/components/icons/CopyIcon.tsx | 36 + .../src/components/icons/CreditCardIcon.tsx | 37 + .../src/components/icons/CursorClickIcon.tsx | 40 + .../src/components/icons/CursorIcon.tsx | 43 + .../src/components/icons/CursorTypeIcon.tsx | 35 + .../src/components/icons/CustomAppIcon.tsx | 40 + .../components/icons/DagHorizontalIcon.tsx | 36 + .../client/src/components/icons/DagIcon.tsx | 36 + .../src/components/icons/DagVerticalIcon.tsx | 36 + .../src/components/icons/DangerFillIcon.tsx | 36 + .../src/components/icons/DangerIcon.tsx | 37 + .../src/components/icons/DangerSmallIcon.tsx | 36 + .../client/src/components/icons/DashIcon.tsx | 31 + .../components/icons/DashboardCodeIcon.tsx | 40 + .../src/components/icons/DashboardIcon.tsx | 36 + .../client/src/components/icons/DataIcon.tsx | 36 + .../components/icons/DatabaseClockIcon.tsx | 42 + .../src/components/icons/DatabaseIcon.tsx | 36 + .../components/icons/DatabaseImportIcon.tsx | 37 + .../src/components/icons/DecimalIcon.tsx | 37 + .../src/components/icons/DeprecatedIcon.tsx | 34 + .../components/icons/DeprecatedSmallIcon.tsx | 34 + .../src/components/icons/DollarIcon.tsx | 40 + .../components/icons/DomainCirclesThree.tsx | 36 + .../src/components/icons/DomainsIcon.tsx | 34 + .../src/components/icons/DotsCircleIcon.tsx | 43 + .../src/components/icons/DownloadIcon.tsx | 34 + .../client/src/components/icons/DragIcon.tsx | 34 + .../client/src/components/icons/ErdIcon.tsx | 36 + .../src/components/icons/ExpandLessIcon.tsx | 34 + .../src/components/icons/ExpandMoreIcon.tsx | 34 + .../src/components/icons/FaceFrownIcon.tsx | 43 + .../src/components/icons/FaceNeutralIcon.tsx | 39 + .../src/components/icons/FaceSmileIcon.tsx | 48 + .../src/components/icons/FileCodeIcon.tsx | 40 + .../src/components/icons/FileCubeIcon.tsx | 42 + .../src/components/icons/FileDocumentIcon.tsx | 37 + .../client/src/components/icons/FileIcon.tsx | 36 + .../src/components/icons/FileImageIcon.tsx | 42 + .../src/components/icons/FileLockIcon.tsx | 39 + .../src/components/icons/FileModelIcon.tsx | 42 + .../src/components/icons/FileNewIcon.tsx | 37 + .../src/components/icons/FilePipelineIcon.tsx | 42 + .../src/components/icons/FilterIcon.tsx | 36 + .../src/components/icons/FlagPointerIcon.tsx | 36 + .../client/src/components/icons/FloatIcon.tsx | 34 + .../client/src/components/icons/FlowIcon.tsx | 34 + .../components/icons/FolderBranchFillIcon.tsx | 40 + .../src/components/icons/FolderBranchIcon.tsx | 36 + .../icons/FolderCloudFilledIcon.tsx | 43 + .../src/components/icons/FolderCloudIcon.tsx | 43 + .../src/components/icons/FolderCubeIcon.tsx | 40 + .../icons/FolderCubeOutlineIcon.tsx | 40 + .../src/components/icons/FolderFillIcon.tsx | 34 + .../src/components/icons/FolderHomeIcon.tsx | 40 + .../src/components/icons/FolderIcon.tsx | 36 + .../src/components/icons/FolderNewIcon.tsx | 35 + .../src/components/icons/FolderNodeIcon.tsx | 36 + .../components/icons/FolderOpenBranchIcon.tsx | 42 + .../components/icons/FolderOpenCloudIcon.tsx | 42 + .../components/icons/FolderOpenCubeIcon.tsx | 42 + .../src/components/icons/FolderOpenIcon.tsx | 36 + .../icons/FolderOpenPipelineIcon.tsx | 42 + .../icons/FolderOutlinePipelineIcon.tsx | 40 + .../icons/FolderSolidPipelineIcon.tsx | 40 + .../client/src/components/icons/FontIcon.tsx | 43 + .../components/icons/ForkHorizontalIcon.tsx | 36 + .../client/src/components/icons/ForkIcon.tsx | 36 + .../components/icons/FullscreenExitIcon.tsx | 34 + .../src/components/icons/FullscreenIcon.tsx | 34 + .../src/components/icons/FunctionIcon.tsx | 43 + .../client/src/components/icons/GavelIcon.tsx | 34 + .../src/components/icons/GearFillIcon.tsx | 36 + .../client/src/components/icons/GearIcon.tsx | 39 + .../icons/GenieDeepResearchIcon.tsx | 42 + .../client/src/components/icons/GiftIcon.tsx | 36 + .../src/components/icons/GitCommitIcon.tsx | 36 + .../client/src/components/icons/GlobeIcon.tsx | 43 + .../src/components/icons/GridDashIcon.tsx | 34 + .../client/src/components/icons/GridIcon.tsx | 36 + .../client/src/components/icons/GroupIcon.tsx | 44 + .../client/src/components/icons/H1Icon.tsx | 34 + .../client/src/components/icons/H2Icon.tsx | 34 + .../client/src/components/icons/H3Icon.tsx | 34 + .../client/src/components/icons/H4Icon.tsx | 34 + .../client/src/components/icons/H5Icon.tsx | 34 + .../client/src/components/icons/H6Icon.tsx | 34 + .../client/src/components/icons/HashIcon.tsx | 36 + .../src/components/icons/HistoryIcon.tsx | 39 + .../client/src/components/icons/HomeIcon.tsx | 36 + .../client/src/components/icons/ImageIcon.tsx | 42 + .../components/icons/IndentDecreaseIcon.tsx | 34 + .../components/icons/IndentIncreaseIcon.tsx | 34 + .../src/components/icons/InfinityIcon.tsx | 36 + .../src/components/icons/InfoBookIcon.tsx | 40 + .../src/components/icons/InfoFillIcon.tsx | 36 + .../client/src/components/icons/InfoIcon.tsx | 36 + .../src/components/icons/InfoSmallIcon.tsx | 37 + .../src/components/icons/IngestionIcon.tsx | 40 + .../src/components/icons/ItalicIcon.tsx | 36 + .../src/components/icons/JoinOperatorIcon.tsx | 36 + .../client/src/components/icons/KeyIcon.tsx | 36 + .../src/components/icons/KeyboardIcon.tsx | 36 + .../src/components/icons/LayerGraphIcon.tsx | 43 + .../client/src/components/icons/LayerIcon.tsx | 38 + .../client/src/components/icons/LeafIcon.tsx | 36 + .../src/components/icons/LetterFormatIcon.tsx | 40 + .../src/components/icons/LettersIcon.tsx | 40 + .../components/icons/LettersNumbersIcon.tsx | 46 + .../src/components/icons/LibrariesIcon.tsx | 31 + .../src/components/icons/LifesaverIcon.tsx | 36 + .../src/components/icons/LightbulbIcon.tsx | 40 + .../icons/LightningCircleFillIcon.tsx | 34 + .../src/components/icons/LightningIcon.tsx | 36 + .../client/src/components/icons/LinkIcon.tsx | 35 + .../src/components/icons/LinkOffIcon.tsx | 35 + .../src/components/icons/ListBorderIcon.tsx | 40 + .../src/components/icons/ListClearIcon.tsx | 38 + .../client/src/components/icons/ListIcon.tsx | 34 + .../src/components/icons/ListNumberIcon.tsx | 34 + .../src/components/icons/LoadingIcon.tsx | 36 + .../src/components/icons/LockFillIcon.tsx | 36 + .../client/src/components/icons/LockIcon.tsx | 37 + .../src/components/icons/LockShareIcon.tsx | 40 + .../src/components/icons/LockUnlockedIcon.tsx | 37 + .../client/src/components/icons/LoopIcon.tsx | 34 + .../client/src/components/icons/MailIcon.tsx | 36 + .../client/src/components/icons/MapIcon.tsx | 36 + .../src/components/icons/MarkdownIcon.tsx | 34 + .../client/src/components/icons/McpIcon.tsx | 39 + .../src/components/icons/MeasureIcon.tsx | 36 + .../src/components/icons/MegaphoneIcon.tsx | 36 + .../client/src/components/icons/MenuIcon.tsx | 36 + .../components/icons/MinusCircleFillIcon.tsx | 36 + .../src/components/icons/MinusCircleIcon.tsx | 37 + .../components/icons/MinusCircleSmallIcon.tsx | 37 + .../src/components/icons/MinusSquareIcon.tsx | 37 + .../src/components/icons/ModelsIcon.tsx | 43 + .../client/src/components/icons/MoonIcon.tsx | 34 + .../src/components/icons/NeonProjectIcon.tsx | 42 + .../src/components/icons/NewChatIcon.tsx | 40 + .../src/components/icons/NewWindowIcon.tsx | 35 + .../client/src/components/icons/NoIcon.tsx | 36 + .../src/components/icons/NotebookIcon.tsx | 36 + .../components/icons/NotebookPipelineIcon.tsx | 42 + .../src/components/icons/NotificationIcon.tsx | 36 + .../components/icons/NotificationOffIcon.tsx | 36 + .../src/components/icons/NumberFormatIcon.tsx | 34 + .../src/components/icons/NumbersIcon.tsx | 34 + .../src/components/icons/OfficeIcon.tsx | 37 + .../src/components/icons/OverflowIcon.tsx | 34 + .../src/components/icons/PageBottomIcon.tsx | 36 + .../src/components/icons/PageFirstIcon.tsx | 36 + .../src/components/icons/PageLastIcon.tsx | 36 + .../src/components/icons/PageTopIcon.tsx | 36 + .../src/components/icons/PanelDockedIcon.tsx | 36 + .../components/icons/PanelFloatingIcon.tsx | 36 + .../src/components/icons/PaperclipIcon.tsx | 36 + .../client/src/components/icons/PauseIcon.tsx | 31 + .../src/components/icons/PencilFillIcon.tsx | 34 + .../src/components/icons/PencilIcon.tsx | 36 + .../components/icons/PencilSparkleIcon.tsx | 46 + .../src/components/icons/PieChartIcon.tsx | 42 + .../src/components/icons/PinCancelIcon.tsx | 34 + .../src/components/icons/PinFillIcon.tsx | 34 + .../client/src/components/icons/PinIcon.tsx | 36 + .../src/components/icons/PipelineCodeIcon.tsx | 34 + .../src/components/icons/PipelineCubeIcon.tsx | 42 + .../src/components/icons/PipelineIcon.tsx | 36 + .../components/icons/PlayCircleFillIcon.tsx | 36 + .../src/components/icons/PlayCircleIcon.tsx | 40 + .../src/components/icons/PlayDoubleIcon.tsx | 38 + .../client/src/components/icons/PlayIcon.tsx | 34 + .../src/components/icons/PlayMultipleIcon.tsx | 34 + .../client/src/components/icons/PlugIcon.tsx | 36 + .../components/icons/PlusCircleFillIcon.tsx | 36 + .../src/components/icons/PlusCircleIcon.tsx | 37 + .../components/icons/PlusCircleSmallIcon.tsx | 37 + .../client/src/components/icons/PlusIcon.tsx | 36 + .../components/icons/PlusMinusSquareIcon.tsx | 37 + .../src/components/icons/PlusSquareIcon.tsx | 37 + .../components/icons/PositionBottomIcon.tsx | 37 + .../src/components/icons/PositionLeftIcon.tsx | 37 + .../components/icons/PositionRightIcon.tsx | 37 + .../src/components/icons/PositionTopIcon.tsx | 37 + .../src/components/icons/PullRequestIcon.tsx | 36 + .../src/components/icons/PuzzleIcon.tsx | 34 + .../src/components/icons/QueryEditorIcon.tsx | 37 + .../client/src/components/icons/QueryIcon.tsx | 43 + .../components/icons/QuestionMarkFillIcon.tsx | 36 + .../src/components/icons/QuestionMarkIcon.tsx | 40 + .../client/src/components/icons/RadioIcon.tsx | 36 + .../src/components/icons/ReaderModeIcon.tsx | 37 + .../client/src/components/icons/RedoIcon.tsx | 43 + .../src/components/icons/RefreshIcon.tsx | 36 + .../src/components/icons/RefreshPlayIcon.tsx | 38 + .../src/components/icons/RefreshXIcon.tsx | 38 + .../client/src/components/icons/ReplyIcon.tsx | 49 + .../src/components/icons/ResizeIcon.tsx | 31 + .../src/components/icons/RichTextIcon.tsx | 34 + .../client/src/components/icons/RobotIcon.tsx | 36 + .../src/components/icons/RocketIcon.tsx | 40 + .../client/src/components/icons/RowsIcon.tsx | 36 + .../client/src/components/icons/RunIcon.tsx | 34 + .../src/components/icons/RunningIcon.tsx | 43 + .../src/components/icons/SaveClockIcon.tsx | 43 + .../client/src/components/icons/SaveIcon.tsx | 37 + .../src/components/icons/SchemaIcon.tsx | 36 + .../src/components/icons/SchoolIcon.tsx | 36 + .../src/components/icons/SearchDataIcon.tsx | 40 + .../src/components/icons/SearchIcon.tsx | 43 + .../client/src/components/icons/SendIcon.tsx | 36 + .../client/src/components/icons/ShareIcon.tsx | 35 + .../src/components/icons/ShieldCheckIcon.tsx | 36 + .../src/components/icons/ShieldIcon.tsx | 36 + .../src/components/icons/ShieldOffIcon.tsx | 40 + .../src/components/icons/ShortcutIcon.tsx | 35 + .../src/components/icons/SidebarAutoIcon.tsx | 40 + .../components/icons/SidebarClosedIcon.tsx | 36 + .../components/icons/SidebarCollapseIcon.tsx | 37 + .../components/icons/SidebarExpandIcon.tsx | 37 + .../src/components/icons/SidebarIcon.tsx | 36 + .../src/components/icons/SidebarOpenIcon.tsx | 36 + .../src/components/icons/SidebarSyncIcon.tsx | 40 + .../src/components/icons/SlashSquareIcon.tsx | 37 + .../src/components/icons/SlidersIcon.tsx | 36 + .../components/icons/SortAscendingIcon.tsx | 34 + .../icons/SortCustomHorizontalIcon.tsx | 40 + .../icons/SortCustomVerticalIcon.tsx | 40 + .../components/icons/SortDescendingIcon.tsx | 36 + .../icons/SortHorizontalAscendingIcon.tsx | 36 + .../icons/SortHorizontalDescendingIcon.tsx | 36 + .../SortLetterHorizontalAscendingIcon.tsx | 44 + .../SortLetterHorizontalDescendingIcon.tsx | 44 + .../icons/SortLetterUnsortedIcon.tsx | 36 + .../icons/SortLetterVerticalAscendingIcon.tsx | 43 + .../SortLetterVerticalDescendingIcon.tsx | 43 + .../src/components/icons/SortUnsortedIcon.tsx | 34 + .../icons/SortVerticalAscendingIcon.tsx | 28 + .../icons/SortVerticalDescendingIcon.tsx | 28 + .../icons/SparkleDoubleFillIcon.tsx | 36 + .../components/icons/SparkleDoubleIcon.tsx | 40 + .../src/components/icons/SparkleFillIcon.tsx | 36 + .../src/components/icons/SparkleIcon.tsx | 36 + .../components/icons/SparkleRectangleIcon.tsx | 36 + .../src/components/icons/SpeechBubbleIcon.tsx | 40 + .../components/icons/SpeechBubblePlusIcon.tsx | 37 + .../SpeechBubbleQuestionMarkFillIcon.tsx | 36 + .../icons/SpeechBubbleQuestionMarkIcon.tsx | 36 + .../components/icons/SpeechBubbleStarIcon.tsx | 40 + .../src/components/icons/SpeedometerIcon.tsx | 38 + .../src/components/icons/StarFillIcon.tsx | 34 + .../client/src/components/icons/StarIcon.tsx | 36 + .../components/icons/StopCircleFillIcon.tsx | 36 + .../src/components/icons/StopCircleIcon.tsx | 36 + .../client/src/components/icons/StopIcon.tsx | 31 + .../components/icons/StoredProcedureIcon.tsx | 42 + .../src/components/icons/StorefrontIcon.tsx | 36 + .../src/components/icons/StreamIcon.tsx | 36 + .../components/icons/StrikeThroughIcon.tsx | 34 + .../client/src/components/icons/SunIcon.tsx | 44 + .../client/src/components/icons/SyncIcon.tsx | 34 + .../src/components/icons/SyncSmallIcon.tsx | 34 + .../src/components/icons/SyncToFileIcon.tsx | 34 + .../components/icons/TableAsteriskIcon.tsx | 40 + .../src/components/icons/TableClockIcon.tsx | 42 + .../src/components/icons/TableCombineIcon.tsx | 48 + .../src/components/icons/TableGlassesIcon.tsx | 42 + .../src/components/icons/TableGlobeIcon.tsx | 42 + .../client/src/components/icons/TableIcon.tsx | 36 + .../components/icons/TableLightningIcon.tsx | 40 + .../src/components/icons/TableMeasureIcon.tsx | 40 + .../src/components/icons/TableModelIcon.tsx | 42 + .../src/components/icons/TableStreamIcon.tsx | 44 + .../src/components/icons/TableVectorIcon.tsx | 42 + .../src/components/icons/TableViewIcon.tsx | 39 + .../client/src/components/icons/TagIcon.tsx | 37 + .../src/components/icons/TargetIcon.tsx | 40 + .../src/components/icons/TerminalIcon.tsx | 37 + .../src/components/icons/TextBoxIcon.tsx | 36 + .../client/src/components/icons/TextIcon.tsx | 31 + .../src/components/icons/TextJustifyIcon.tsx | 34 + .../components/icons/TextUnderlineIcon.tsx | 37 + .../src/components/icons/ThreeDotsIcon.tsx | 34 + .../src/components/icons/ThumbsDownIcon.tsx | 43 + .../src/components/icons/ThumbsUpIcon.tsx | 43 + .../client/src/components/icons/TokenIcon.tsx | 41 + .../client/src/components/icons/TrashIcon.tsx | 36 + .../client/src/components/icons/TreeIcon.tsx | 36 + .../src/components/icons/TrendingIcon.tsx | 34 + .../src/components/icons/TriangleIcon.tsx | 34 + .../src/components/icons/UnderlineIcon.tsx | 36 + .../client/src/components/icons/UndoIcon.tsx | 41 + .../src/components/icons/UploadIcon.tsx | 34 + .../client/src/components/icons/UsbIcon.tsx | 34 + .../src/components/icons/UserBadgeIcon.tsx | 42 + .../src/components/icons/UserCircleIcon.tsx | 42 + .../components/icons/UserGroupFillIcon.tsx | 34 + .../src/components/icons/UserGroupIcon.tsx | 36 + .../components/icons/UserHomeVolumeIcon.tsx | 38 + .../client/src/components/icons/UserIcon.tsx | 36 + .../src/components/icons/UserKeyIconIcon.tsx | 40 + .../src/components/icons/UserShieldIcon.tsx | 42 + .../src/components/icons/UserSparkleIcon.tsx | 44 + .../src/components/icons/UserTeamIcon.tsx | 50 + .../src/components/icons/VisibleFillIcon.tsx | 43 + .../src/components/icons/VisibleIcon.tsx | 39 + .../src/components/icons/VisibleOffIcon.tsx | 44 + .../src/components/icons/WarningFillIcon.tsx | 36 + .../src/components/icons/WarningIcon.tsx | 37 + .../src/components/icons/WorkflowCodeIcon.tsx | 38 + .../src/components/icons/WorkflowCubeIcon.tsx | 42 + .../src/components/icons/WorkflowsIcon.tsx | 36 + .../src/components/icons/WorkspacesIcon.tsx | 40 + .../src/components/icons/WrenchIcon.tsx | 36 + .../components/icons/WrenchSparkleIcon.tsx | 44 + .../src/components/icons/XCircleFillIcon.tsx | 36 + .../src/components/icons/XCircleIcon.tsx | 40 + .../src/components/icons/ZaHorizontalIcon.tsx | 36 + .../src/components/icons/ZaVerticalIcon.tsx | 36 + .../src/components/icons/ZoomInIcon.tsx | 37 + .../components/icons/ZoomMarqueeSelection.tsx | 40 + .../src/components/icons/ZoomOutIcon.tsx | 37 + .../src/components/icons/ZoomToFitIcon.tsx | 34 + .../client/src/components/icons/index.ts | 445 + .../client/src/components/message-actions.tsx | 195 + .../client/src/components/message-editor.tsx | 106 + .../client/src/components/message-error.tsx | 44 + .../src/components/message-oauth-error.tsx | 118 + .../src/components/message-reasoning.tsx | 26 + .../client/src/components/message.tsx | 434 + .../client/src/components/messages.tsx | 128 + .../src/components/multimodal-input.tsx | 381 + .../src/components/preview-attachment.tsx | 56 + .../src/components/sidebar-history-item.tsx | 110 + .../client/src/components/sidebar-history.tsx | 390 + .../client/src/components/sidebar-toggle.tsx | 38 + .../src/components/sidebar-user-nav.tsx | 108 + .../src/components/suggested-actions.tsx | 68 + .../client/src/components/theme-provider.tsx | 8 + .../client/src/components/toast.tsx | 71 + .../client/src/components/ui/alert-dialog.tsx | 134 + .../client/src/components/ui/badge.tsx | 36 + .../client/src/components/ui/button.tsx | 59 + .../client/src/components/ui/collapsible.tsx | 9 + .../client/src/components/ui/db-icon.tsx | 83 + .../src/components/ui/dropdown-menu.tsx | 188 + .../client/src/components/ui/input.tsx | 22 + .../client/src/components/ui/separator.tsx | 29 + .../client/src/components/ui/sheet.tsx | 123 + .../client/src/components/ui/shimmer.tsx | 25 + .../client/src/components/ui/sidebar.tsx | 770 + .../client/src/components/ui/skeleton.tsx | 15 + .../client/src/components/ui/textarea.tsx | 22 + .../client/src/components/ui/tooltip.tsx | 43 + .../src/components/visibility-selector.tsx | 1 + .../client/src/contexts/AppConfigContext.tsx | 63 + .../client/src/contexts/SessionContext.tsx | 65 + .../client/src/hooks/use-approval.ts | 65 + .../client/src/hooks/use-chat-visibility.ts | 47 + .../client/src/hooks/use-config.ts | 9 + .../client/src/hooks/use-messages.tsx | 37 + .../client/src/hooks/use-mobile.tsx | 21 + .../client/src/hooks/use-scroll-to-bottom.tsx | 69 + .../client/src/hooks/useChatData.ts | 104 + .../e2e-chatbot-app-next/client/src/index.css | 586 + .../client/src/layouts/ChatLayout.tsx | 55 + .../client/src/layouts/RootLayout.tsx | 31 + .../client/src/lib/ChatTransport.ts | 40 + .../client/src/lib/actions.ts | 50 + .../client/src/lib/navigation.ts | 45 + .../client/src/lib/oauth-error-utils.ts | 47 + .../client/src/lib/utils.ts | 85 + .../e2e-chatbot-app-next/client/src/main.tsx | 15 + .../client/src/pages/ChatPage.tsx | 94 + .../client/src/pages/NewChatPage.tsx | 47 + .../e2e-chatbot-app-next/client/tsconfig.json | 32 + .../client/tsconfig.node.json | 10 + .../client/vite.config.ts | 51 + .../e2e-chatbot-app-next/databricks.yml | 70 + .../e2e-chatbot-app-next/drizzle.config.ts | 30 + .../e2e-chatbot-app-next/knip.json | 9 + .../e2e-chatbot-app-next/package-lock.json | 12832 ++++++++++++++++ .../e2e-chatbot-app-next/package.json | 49 + .../packages/ai-sdk-providers/package.json | 42 + .../packages/ai-sdk-providers/src/index.ts | 2 + .../ai-sdk-providers/src/providers-server.ts | 443 + .../ai-sdk-providers/src/request-context.ts | 27 + .../packages/ai-sdk-providers/tsconfig.json | 20 + .../ai-sdk-providers/tsdown.config.ts | 9 + .../packages/auth/package.json | 38 + .../packages/auth/src/databricks-auth.ts | 651 + .../packages/auth/src/index.ts | 1 + .../packages/auth/tsconfig.json | 19 + .../packages/auth/tsdown.config.ts | 9 + .../packages/core/package.json | 53 + .../packages/core/src/ai/providers.ts | 35 + .../packages/core/src/chat-acl.ts | 60 + .../packages/core/src/errors.ts | 138 + .../packages/core/src/index.ts | 7 + .../packages/core/src/schemas/chat.ts | 43 + .../packages/core/src/stream-cache.ts | 330 + .../packages/core/src/types.ts | 33 + .../packages/core/src/utils.ts | 23 + .../packages/core/tsconfig.json | 20 + .../packages/core/tsdown.config.ts | 9 + .../migrations/0000_robust_madelyne_pryor.sql | 24 + .../db/migrations/0001_add_trace_id.sql | 2 + .../db/migrations/0002_polite_red_hulk.sql | 9 + .../db/migrations/meta/0000_snapshot.json | 156 + .../db/migrations/meta/0001_snapshot.json | 166 + .../db/migrations/meta/0002_snapshot.json | 234 + .../packages/db/migrations/meta/_journal.json | 27 + .../packages/db/package.json | 42 + .../packages/db/src/connection-core.ts | 85 + .../packages/db/src/connection-pool.ts | 71 + .../packages/db/src/connection.ts | 48 + .../packages/db/src/index.ts | 5 + .../packages/db/src/queries.ts | 497 + .../packages/db/src/schema.ts | 70 + .../packages/db/tsconfig.json | 24 + .../packages/db/tsdown.config.ts | 9 + .../packages/utils/package.json | 34 + .../utils/src/databricks-host-utils.ts | 37 + .../packages/utils/src/index.ts | 3 + .../packages/utils/src/subprocess.ts | 70 + .../packages/utils/src/types.ts | 10 + .../packages/utils/tsconfig.json | 20 + .../packages/utils/tsdown.config.ts | 9 + .../e2e-chatbot-app-next/playwright.config.ts | 195 + .../scripts/cleanup-database.sh | 219 + .../e2e-chatbot-app-next/scripts/deploy.sh | 3 + .../scripts/get-experiment-id.ts | 223 + .../scripts/get-pghost.sh | 29 + .../e2e-chatbot-app-next/scripts/migrate.ts | 102 + .../scripts/quickstart.sh | 1023 ++ .../scripts/reset-database.ts | 96 + .../e2e-chatbot-app-next/scripts/start-app.sh | 22 + .../e2e-chatbot-app-next/server/package.json | 31 + .../e2e-chatbot-app-next/server/src/env.ts | 17 + .../e2e-chatbot-app-next/server/src/index.ts | 241 + .../server/src/lib/message-meta-store.ts | 62 + .../server/src/lib/stream-fallback.ts | 189 + .../server/src/middleware/auth.ts | 79 + .../server/src/routes/chat.ts | 617 + .../server/src/routes/config.ts | 61 + .../server/src/routes/feedback.ts | 251 + .../server/src/routes/history.ts | 64 + .../server/src/routes/messages.ts | 98 + .../server/src/routes/session.ts | 35 + .../e2e-chatbot-app-next/server/tsconfig.json | 9 + .../server/tsdown.config.ts | 13 + .../ai-sdk-provider/request-context.test.ts | 53 + .../tests/api-mocking/api-mock-handlers.ts | 524 + .../tests/api-mocking/api-mock-server.ts | 4 + .../tests/deployed/feedback.test.ts | 118 + .../tests/e2e/chat-navigation.test.ts | 19 + .../tests/e2e/chat-resume.test.ts | 151 + .../tests/e2e/chat.test.ts | 106 + .../tests/e2e/ephemeral-mode.test.ts | 78 + .../tests/e2e/mcp-approval.test.ts | 144 + .../tests/e2e/oauth-error.test.ts | 159 + .../tests/e2e/obo-banner.test.ts | 56 + .../tests/e2e/session.test.ts | 12 + .../e2e-chatbot-app-next/tests/fixtures.ts | 50 + .../e2e-chatbot-app-next/tests/helpers.ts | 1013 ++ .../tests/oauth/oauth-error-utils.test.ts | 204 + .../e2e-chatbot-app-next/tests/pages/chat.ts | 239 + .../tests/prompts/basic.ts | 148 + .../tests/prompts/llm-output-fixtures.ts | 669 + .../tests/prompts/oauth-fixtures.ts | 108 + .../tests/prompts/routes.ts | 111 + .../tests/prompts/utils.ts | 225 + .../tests/routes/chat.test.ts | 533 + .../tests/routes/config.test.ts | 106 + .../tests/routes/context-injection.test.ts | 242 + .../tests/routes/feedback.test.ts | 228 + .../tests/routes/history.test.ts | 90 + .../tests/routes/token-forwarding.test.ts | 65 + .../routes/trace-id-capture.api-proxy.test.ts | 98 + .../tests/routes/trace-id-capture.test.ts | 72 + .../e2e-chatbot-app-next/tsconfig.json | 29 + 636 files changed, 52286 insertions(+) create mode 100644 agent-openai-agents-sdk-multiagent/e2e-chatbot-app-next/.claude/skills/quickstart/SKILL.md create mode 100644 agent-openai-agents-sdk-multiagent/e2e-chatbot-app-next/.env.example create mode 100644 agent-openai-agents-sdk-multiagent/e2e-chatbot-app-next/.gitignore create mode 100644 agent-openai-agents-sdk-multiagent/e2e-chatbot-app-next/CLAUDE.md create mode 100644 agent-openai-agents-sdk-multiagent/e2e-chatbot-app-next/README.md create mode 100644 agent-openai-agents-sdk-multiagent/e2e-chatbot-app-next/app.yaml create mode 100644 agent-openai-agents-sdk-multiagent/e2e-chatbot-app-next/biome.jsonc create mode 100644 agent-openai-agents-sdk-multiagent/e2e-chatbot-app-next/client/index.html create mode 100644 agent-openai-agents-sdk-multiagent/e2e-chatbot-app-next/client/package.json create mode 100644 agent-openai-agents-sdk-multiagent/e2e-chatbot-app-next/client/postcss.config.js create mode 100644 agent-openai-agents-sdk-multiagent/e2e-chatbot-app-next/client/public/demo-thumbnail.png create mode 100644 agent-openai-agents-sdk-multiagent/e2e-chatbot-app-next/client/public/mouth of the seine, monet.jpg create mode 100644 agent-openai-agents-sdk-multiagent/e2e-chatbot-app-next/client/src/App.tsx create mode 100644 agent-openai-agents-sdk-multiagent/e2e-chatbot-app-next/client/src/components/DatabricksLogo.tsx create mode 100644 agent-openai-agents-sdk-multiagent/e2e-chatbot-app-next/client/src/components/animation-assistant-icon.tsx create mode 100644 agent-openai-agents-sdk-multiagent/e2e-chatbot-app-next/client/src/components/app-sidebar.tsx create mode 100644 agent-openai-agents-sdk-multiagent/e2e-chatbot-app-next/client/src/components/chat-header.tsx create mode 100644 agent-openai-agents-sdk-multiagent/e2e-chatbot-app-next/client/src/components/chat.tsx create mode 100644 agent-openai-agents-sdk-multiagent/e2e-chatbot-app-next/client/src/components/data-stream-provider.tsx create mode 100644 agent-openai-agents-sdk-multiagent/e2e-chatbot-app-next/client/src/components/databricks-message-citation.tsx create mode 100644 agent-openai-agents-sdk-multiagent/e2e-chatbot-app-next/client/src/components/databricks-message-part-transformers.ts create mode 100644 agent-openai-agents-sdk-multiagent/e2e-chatbot-app-next/client/src/components/elements/actions.tsx create mode 100644 agent-openai-agents-sdk-multiagent/e2e-chatbot-app-next/client/src/components/elements/code-block.tsx create mode 100644 agent-openai-agents-sdk-multiagent/e2e-chatbot-app-next/client/src/components/elements/conversation.tsx create mode 100644 agent-openai-agents-sdk-multiagent/e2e-chatbot-app-next/client/src/components/elements/loader.tsx create mode 100644 agent-openai-agents-sdk-multiagent/e2e-chatbot-app-next/client/src/components/elements/mcp-tool.tsx create mode 100644 agent-openai-agents-sdk-multiagent/e2e-chatbot-app-next/client/src/components/elements/message.tsx create mode 100644 agent-openai-agents-sdk-multiagent/e2e-chatbot-app-next/client/src/components/elements/prompt-input.tsx create mode 100644 agent-openai-agents-sdk-multiagent/e2e-chatbot-app-next/client/src/components/elements/reasoning.tsx create mode 100644 agent-openai-agents-sdk-multiagent/e2e-chatbot-app-next/client/src/components/elements/response.tsx create mode 100644 agent-openai-agents-sdk-multiagent/e2e-chatbot-app-next/client/src/components/elements/suggestion.tsx create mode 100644 agent-openai-agents-sdk-multiagent/e2e-chatbot-app-next/client/src/components/elements/tool.tsx create mode 100644 agent-openai-agents-sdk-multiagent/e2e-chatbot-app-next/client/src/components/greeting.tsx create mode 100644 agent-openai-agents-sdk-multiagent/e2e-chatbot-app-next/client/src/components/icons/AlignCenterIcon.tsx create mode 100644 agent-openai-agents-sdk-multiagent/e2e-chatbot-app-next/client/src/components/icons/AlignJustifyIcon.tsx create mode 100644 agent-openai-agents-sdk-multiagent/e2e-chatbot-app-next/client/src/components/icons/AlignLeftIcon.tsx create mode 100644 agent-openai-agents-sdk-multiagent/e2e-chatbot-app-next/client/src/components/icons/AlignRightIcon.tsx create mode 100644 agent-openai-agents-sdk-multiagent/e2e-chatbot-app-next/client/src/components/icons/AppIcon.tsx create mode 100644 agent-openai-agents-sdk-multiagent/e2e-chatbot-app-next/client/src/components/icons/ArrowDownDotIcon.tsx create mode 100644 agent-openai-agents-sdk-multiagent/e2e-chatbot-app-next/client/src/components/icons/ArrowDownFillIcon.tsx create mode 100644 agent-openai-agents-sdk-multiagent/e2e-chatbot-app-next/client/src/components/icons/ArrowDownIcon.tsx create mode 100644 agent-openai-agents-sdk-multiagent/e2e-chatbot-app-next/client/src/components/icons/ArrowInIcon.tsx create mode 100644 agent-openai-agents-sdk-multiagent/e2e-chatbot-app-next/client/src/components/icons/ArrowLeftIcon.tsx create mode 100644 agent-openai-agents-sdk-multiagent/e2e-chatbot-app-next/client/src/components/icons/ArrowOverIcon.tsx create mode 100644 agent-openai-agents-sdk-multiagent/e2e-chatbot-app-next/client/src/components/icons/ArrowRightIcon.tsx create mode 100644 agent-openai-agents-sdk-multiagent/e2e-chatbot-app-next/client/src/components/icons/ArrowUpDotIcon.tsx create mode 100644 agent-openai-agents-sdk-multiagent/e2e-chatbot-app-next/client/src/components/icons/ArrowUpFillIcon.tsx create mode 100644 agent-openai-agents-sdk-multiagent/e2e-chatbot-app-next/client/src/components/icons/ArrowUpIcon.tsx create mode 100644 agent-openai-agents-sdk-multiagent/e2e-chatbot-app-next/client/src/components/icons/ArrowsConnectIcon.tsx create mode 100644 agent-openai-agents-sdk-multiagent/e2e-chatbot-app-next/client/src/components/icons/ArrowsUpDownIcon.tsx create mode 100644 agent-openai-agents-sdk-multiagent/e2e-chatbot-app-next/client/src/components/icons/AssistantIcon.tsx create mode 100644 agent-openai-agents-sdk-multiagent/e2e-chatbot-app-next/client/src/components/icons/AtIcon.tsx create mode 100644 agent-openai-agents-sdk-multiagent/e2e-chatbot-app-next/client/src/components/icons/AzHorizontalIcon.tsx create mode 100644 agent-openai-agents-sdk-multiagent/e2e-chatbot-app-next/client/src/components/icons/AzVerticalIcon.tsx create mode 100644 agent-openai-agents-sdk-multiagent/e2e-chatbot-app-next/client/src/components/icons/BackupIcon.tsx create mode 100644 agent-openai-agents-sdk-multiagent/e2e-chatbot-app-next/client/src/components/icons/BadgeCodeIcon.tsx create mode 100644 agent-openai-agents-sdk-multiagent/e2e-chatbot-app-next/client/src/components/icons/BadgeCodeOffIcon.tsx create mode 100644 agent-openai-agents-sdk-multiagent/e2e-chatbot-app-next/client/src/components/icons/BarChartIcon.tsx create mode 100644 agent-openai-agents-sdk-multiagent/e2e-chatbot-app-next/client/src/components/icons/BarGroupedIcon.tsx create mode 100644 agent-openai-agents-sdk-multiagent/e2e-chatbot-app-next/client/src/components/icons/BarStackedIcon.tsx create mode 100644 agent-openai-agents-sdk-multiagent/e2e-chatbot-app-next/client/src/components/icons/BarStackedPercentageIcon.tsx create mode 100644 agent-openai-agents-sdk-multiagent/e2e-chatbot-app-next/client/src/components/icons/BarsAscendingHorizontalIcon.tsx create mode 100644 agent-openai-agents-sdk-multiagent/e2e-chatbot-app-next/client/src/components/icons/BarsAscendingVerticalIcon.tsx create mode 100644 agent-openai-agents-sdk-multiagent/e2e-chatbot-app-next/client/src/components/icons/BarsDescendingHorizontalIcon.tsx create mode 100644 agent-openai-agents-sdk-multiagent/e2e-chatbot-app-next/client/src/components/icons/BarsDescendingVerticalIcon.tsx create mode 100644 agent-openai-agents-sdk-multiagent/e2e-chatbot-app-next/client/src/components/icons/BeakerIcon.tsx create mode 100644 agent-openai-agents-sdk-multiagent/e2e-chatbot-app-next/client/src/components/icons/BinaryIcon.tsx create mode 100644 agent-openai-agents-sdk-multiagent/e2e-chatbot-app-next/client/src/components/icons/BlockQuoteIcon.tsx create mode 100644 agent-openai-agents-sdk-multiagent/e2e-chatbot-app-next/client/src/components/icons/BoldIcon.tsx create mode 100644 agent-openai-agents-sdk-multiagent/e2e-chatbot-app-next/client/src/components/icons/BookIcon.tsx create mode 100644 agent-openai-agents-sdk-multiagent/e2e-chatbot-app-next/client/src/components/icons/BookmarkFillIcon.tsx create mode 100644 agent-openai-agents-sdk-multiagent/e2e-chatbot-app-next/client/src/components/icons/BookmarkIcon.tsx create mode 100644 agent-openai-agents-sdk-multiagent/e2e-chatbot-app-next/client/src/components/icons/BooksIcon.tsx create mode 100644 agent-openai-agents-sdk-multiagent/e2e-chatbot-app-next/client/src/components/icons/BracketsCheckIcon.tsx create mode 100644 agent-openai-agents-sdk-multiagent/e2e-chatbot-app-next/client/src/components/icons/BracketsCurlyIcon.tsx create mode 100644 agent-openai-agents-sdk-multiagent/e2e-chatbot-app-next/client/src/components/icons/BracketsErrorIcon.tsx create mode 100644 agent-openai-agents-sdk-multiagent/e2e-chatbot-app-next/client/src/components/icons/BracketsSquareIcon.tsx create mode 100644 agent-openai-agents-sdk-multiagent/e2e-chatbot-app-next/client/src/components/icons/BracketsXIcon.tsx create mode 100644 agent-openai-agents-sdk-multiagent/e2e-chatbot-app-next/client/src/components/icons/BranchCheckIcon.tsx create mode 100644 agent-openai-agents-sdk-multiagent/e2e-chatbot-app-next/client/src/components/icons/BranchIcon.tsx create mode 100644 agent-openai-agents-sdk-multiagent/e2e-chatbot-app-next/client/src/components/icons/BranchResetIcon.tsx create mode 100644 agent-openai-agents-sdk-multiagent/e2e-chatbot-app-next/client/src/components/icons/BriefcaseFillIcon.tsx create mode 100644 agent-openai-agents-sdk-multiagent/e2e-chatbot-app-next/client/src/components/icons/BriefcaseIcon.tsx create mode 100644 agent-openai-agents-sdk-multiagent/e2e-chatbot-app-next/client/src/components/icons/BrushIcon.tsx create mode 100644 agent-openai-agents-sdk-multiagent/e2e-chatbot-app-next/client/src/components/icons/BugIcon.tsx create mode 100644 agent-openai-agents-sdk-multiagent/e2e-chatbot-app-next/client/src/components/icons/CalendarClockIcon.tsx create mode 100644 agent-openai-agents-sdk-multiagent/e2e-chatbot-app-next/client/src/components/icons/CalendarEventIcon.tsx create mode 100644 agent-openai-agents-sdk-multiagent/e2e-chatbot-app-next/client/src/components/icons/CalendarIcon.tsx create mode 100644 agent-openai-agents-sdk-multiagent/e2e-chatbot-app-next/client/src/components/icons/CalendarRangeIcon.tsx create mode 100644 agent-openai-agents-sdk-multiagent/e2e-chatbot-app-next/client/src/components/icons/CalendarSyncIcon.tsx create mode 100644 agent-openai-agents-sdk-multiagent/e2e-chatbot-app-next/client/src/components/icons/CameraIcon.tsx create mode 100644 agent-openai-agents-sdk-multiagent/e2e-chatbot-app-next/client/src/components/icons/CaretDownSquareIcon.tsx create mode 100644 agent-openai-agents-sdk-multiagent/e2e-chatbot-app-next/client/src/components/icons/CaretUpSquareIcon.tsx create mode 100644 agent-openai-agents-sdk-multiagent/e2e-chatbot-app-next/client/src/components/icons/CatalogCloudIcon.tsx create mode 100644 agent-openai-agents-sdk-multiagent/e2e-chatbot-app-next/client/src/components/icons/CatalogGearIcon.tsx create mode 100644 agent-openai-agents-sdk-multiagent/e2e-chatbot-app-next/client/src/components/icons/CatalogHIcon.tsx create mode 100644 agent-openai-agents-sdk-multiagent/e2e-chatbot-app-next/client/src/components/icons/CatalogHomeIcon.tsx create mode 100644 agent-openai-agents-sdk-multiagent/e2e-chatbot-app-next/client/src/components/icons/CatalogIcon.tsx create mode 100644 agent-openai-agents-sdk-multiagent/e2e-chatbot-app-next/client/src/components/icons/CatalogOffIcon.tsx create mode 100644 agent-openai-agents-sdk-multiagent/e2e-chatbot-app-next/client/src/components/icons/CatalogSharedIcon.tsx create mode 100644 agent-openai-agents-sdk-multiagent/e2e-chatbot-app-next/client/src/components/icons/CatalogUserHomeIcon.tsx create mode 100644 agent-openai-agents-sdk-multiagent/e2e-chatbot-app-next/client/src/components/icons/CellsSquareIcon.tsx create mode 100644 agent-openai-agents-sdk-multiagent/e2e-chatbot-app-next/client/src/components/icons/CertifiedFillIcon.tsx create mode 100644 agent-openai-agents-sdk-multiagent/e2e-chatbot-app-next/client/src/components/icons/CertifiedFillSmallIcon.tsx create mode 100644 agent-openai-agents-sdk-multiagent/e2e-chatbot-app-next/client/src/components/icons/CertifiedIcon.tsx create mode 100644 agent-openai-agents-sdk-multiagent/e2e-chatbot-app-next/client/src/components/icons/ChainIcon.tsx create mode 100644 agent-openai-agents-sdk-multiagent/e2e-chatbot-app-next/client/src/components/icons/ChartLineIcon.tsx create mode 100644 agent-openai-agents-sdk-multiagent/e2e-chatbot-app-next/client/src/components/icons/CheckCircleBadgeIcon.tsx create mode 100644 agent-openai-agents-sdk-multiagent/e2e-chatbot-app-next/client/src/components/icons/CheckCircleFillIcon.tsx create mode 100644 agent-openai-agents-sdk-multiagent/e2e-chatbot-app-next/client/src/components/icons/CheckCircleIcon.tsx create mode 100644 agent-openai-agents-sdk-multiagent/e2e-chatbot-app-next/client/src/components/icons/CheckCircleSmallIcon.tsx create mode 100644 agent-openai-agents-sdk-multiagent/e2e-chatbot-app-next/client/src/components/icons/CheckIcon.tsx create mode 100644 agent-openai-agents-sdk-multiagent/e2e-chatbot-app-next/client/src/components/icons/CheckLineIcon.tsx create mode 100644 agent-openai-agents-sdk-multiagent/e2e-chatbot-app-next/client/src/components/icons/CheckSmallIcon.tsx create mode 100644 agent-openai-agents-sdk-multiagent/e2e-chatbot-app-next/client/src/components/icons/CheckboxIcon.tsx create mode 100644 agent-openai-agents-sdk-multiagent/e2e-chatbot-app-next/client/src/components/icons/ChecklistIcon.tsx create mode 100644 agent-openai-agents-sdk-multiagent/e2e-chatbot-app-next/client/src/components/icons/ChevronDoubleDownIcon.tsx create mode 100644 agent-openai-agents-sdk-multiagent/e2e-chatbot-app-next/client/src/components/icons/ChevronDoubleLeftIcon.tsx create mode 100644 agent-openai-agents-sdk-multiagent/e2e-chatbot-app-next/client/src/components/icons/ChevronDoubleLeftOffIcon.tsx create mode 100644 agent-openai-agents-sdk-multiagent/e2e-chatbot-app-next/client/src/components/icons/ChevronDoubleRightIcon.tsx create mode 100644 agent-openai-agents-sdk-multiagent/e2e-chatbot-app-next/client/src/components/icons/ChevronDoubleRightOffIcon.tsx create mode 100644 agent-openai-agents-sdk-multiagent/e2e-chatbot-app-next/client/src/components/icons/ChevronDoubleUpIcon.tsx create mode 100644 agent-openai-agents-sdk-multiagent/e2e-chatbot-app-next/client/src/components/icons/ChevronDownIcon.tsx create mode 100644 agent-openai-agents-sdk-multiagent/e2e-chatbot-app-next/client/src/components/icons/ChevronLeftIcon.tsx create mode 100644 agent-openai-agents-sdk-multiagent/e2e-chatbot-app-next/client/src/components/icons/ChevronRightIcon.tsx create mode 100644 agent-openai-agents-sdk-multiagent/e2e-chatbot-app-next/client/src/components/icons/ChevronUpIcon.tsx create mode 100644 agent-openai-agents-sdk-multiagent/e2e-chatbot-app-next/client/src/components/icons/ChipIcon.tsx create mode 100644 agent-openai-agents-sdk-multiagent/e2e-chatbot-app-next/client/src/components/icons/CircleIcon.tsx create mode 100644 agent-openai-agents-sdk-multiagent/e2e-chatbot-app-next/client/src/components/icons/CircleOffIcon.tsx create mode 100644 agent-openai-agents-sdk-multiagent/e2e-chatbot-app-next/client/src/components/icons/CircleOffLargeIcon.tsx create mode 100644 agent-openai-agents-sdk-multiagent/e2e-chatbot-app-next/client/src/components/icons/CircleOutlineIcon.tsx create mode 100644 agent-openai-agents-sdk-multiagent/e2e-chatbot-app-next/client/src/components/icons/CircleOutlineLargeIcon.tsx create mode 100644 agent-openai-agents-sdk-multiagent/e2e-chatbot-app-next/client/src/components/icons/ClipboardIcon.tsx create mode 100644 agent-openai-agents-sdk-multiagent/e2e-chatbot-app-next/client/src/components/icons/ClockIcon.tsx create mode 100644 agent-openai-agents-sdk-multiagent/e2e-chatbot-app-next/client/src/components/icons/ClockKeyIcon.tsx create mode 100644 agent-openai-agents-sdk-multiagent/e2e-chatbot-app-next/client/src/components/icons/ClockOffIcon.tsx create mode 100644 agent-openai-agents-sdk-multiagent/e2e-chatbot-app-next/client/src/components/icons/CloseIcon.tsx create mode 100644 agent-openai-agents-sdk-multiagent/e2e-chatbot-app-next/client/src/components/icons/CloseSmallIcon.tsx create mode 100644 agent-openai-agents-sdk-multiagent/e2e-chatbot-app-next/client/src/components/icons/CloudDatabaseIcon.tsx create mode 100644 agent-openai-agents-sdk-multiagent/e2e-chatbot-app-next/client/src/components/icons/CloudDownloadIcon.tsx create mode 100644 agent-openai-agents-sdk-multiagent/e2e-chatbot-app-next/client/src/components/icons/CloudIcon.tsx create mode 100644 agent-openai-agents-sdk-multiagent/e2e-chatbot-app-next/client/src/components/icons/CloudKeyIcon.tsx create mode 100644 agent-openai-agents-sdk-multiagent/e2e-chatbot-app-next/client/src/components/icons/CloudModelIcon.tsx create mode 100644 agent-openai-agents-sdk-multiagent/e2e-chatbot-app-next/client/src/components/icons/CloudOffIcon.tsx create mode 100644 agent-openai-agents-sdk-multiagent/e2e-chatbot-app-next/client/src/components/icons/CloudUploadIcon.tsx create mode 100644 agent-openai-agents-sdk-multiagent/e2e-chatbot-app-next/client/src/components/icons/CodeIcon.tsx create mode 100644 agent-openai-agents-sdk-multiagent/e2e-chatbot-app-next/client/src/components/icons/ColorFillIcon.tsx create mode 100644 agent-openai-agents-sdk-multiagent/e2e-chatbot-app-next/client/src/components/icons/ColumnIcon.tsx create mode 100644 agent-openai-agents-sdk-multiagent/e2e-chatbot-app-next/client/src/components/icons/ColumnSplitIcon.tsx create mode 100644 agent-openai-agents-sdk-multiagent/e2e-chatbot-app-next/client/src/components/icons/ColumnsIcon.tsx create mode 100644 agent-openai-agents-sdk-multiagent/e2e-chatbot-app-next/client/src/components/icons/CommandIcon.tsx create mode 100644 agent-openai-agents-sdk-multiagent/e2e-chatbot-app-next/client/src/components/icons/CommandPaletteIcon.tsx create mode 100644 agent-openai-agents-sdk-multiagent/e2e-chatbot-app-next/client/src/components/icons/CompassIcon.tsx create mode 100644 agent-openai-agents-sdk-multiagent/e2e-chatbot-app-next/client/src/components/icons/ConnectIcon.tsx create mode 100644 agent-openai-agents-sdk-multiagent/e2e-chatbot-app-next/client/src/components/icons/CopyIcon.tsx create mode 100644 agent-openai-agents-sdk-multiagent/e2e-chatbot-app-next/client/src/components/icons/CreditCardIcon.tsx create mode 100644 agent-openai-agents-sdk-multiagent/e2e-chatbot-app-next/client/src/components/icons/CursorClickIcon.tsx create mode 100644 agent-openai-agents-sdk-multiagent/e2e-chatbot-app-next/client/src/components/icons/CursorIcon.tsx create mode 100644 agent-openai-agents-sdk-multiagent/e2e-chatbot-app-next/client/src/components/icons/CursorTypeIcon.tsx create mode 100644 agent-openai-agents-sdk-multiagent/e2e-chatbot-app-next/client/src/components/icons/CustomAppIcon.tsx create mode 100644 agent-openai-agents-sdk-multiagent/e2e-chatbot-app-next/client/src/components/icons/DagHorizontalIcon.tsx create mode 100644 agent-openai-agents-sdk-multiagent/e2e-chatbot-app-next/client/src/components/icons/DagIcon.tsx create mode 100644 agent-openai-agents-sdk-multiagent/e2e-chatbot-app-next/client/src/components/icons/DagVerticalIcon.tsx create mode 100644 agent-openai-agents-sdk-multiagent/e2e-chatbot-app-next/client/src/components/icons/DangerFillIcon.tsx create mode 100644 agent-openai-agents-sdk-multiagent/e2e-chatbot-app-next/client/src/components/icons/DangerIcon.tsx create mode 100644 agent-openai-agents-sdk-multiagent/e2e-chatbot-app-next/client/src/components/icons/DangerSmallIcon.tsx create mode 100644 agent-openai-agents-sdk-multiagent/e2e-chatbot-app-next/client/src/components/icons/DashIcon.tsx create mode 100644 agent-openai-agents-sdk-multiagent/e2e-chatbot-app-next/client/src/components/icons/DashboardCodeIcon.tsx create mode 100644 agent-openai-agents-sdk-multiagent/e2e-chatbot-app-next/client/src/components/icons/DashboardIcon.tsx create mode 100644 agent-openai-agents-sdk-multiagent/e2e-chatbot-app-next/client/src/components/icons/DataIcon.tsx create mode 100644 agent-openai-agents-sdk-multiagent/e2e-chatbot-app-next/client/src/components/icons/DatabaseClockIcon.tsx create mode 100644 agent-openai-agents-sdk-multiagent/e2e-chatbot-app-next/client/src/components/icons/DatabaseIcon.tsx create mode 100644 agent-openai-agents-sdk-multiagent/e2e-chatbot-app-next/client/src/components/icons/DatabaseImportIcon.tsx create mode 100644 agent-openai-agents-sdk-multiagent/e2e-chatbot-app-next/client/src/components/icons/DecimalIcon.tsx create mode 100644 agent-openai-agents-sdk-multiagent/e2e-chatbot-app-next/client/src/components/icons/DeprecatedIcon.tsx create mode 100644 agent-openai-agents-sdk-multiagent/e2e-chatbot-app-next/client/src/components/icons/DeprecatedSmallIcon.tsx create mode 100644 agent-openai-agents-sdk-multiagent/e2e-chatbot-app-next/client/src/components/icons/DollarIcon.tsx create mode 100644 agent-openai-agents-sdk-multiagent/e2e-chatbot-app-next/client/src/components/icons/DomainCirclesThree.tsx create mode 100644 agent-openai-agents-sdk-multiagent/e2e-chatbot-app-next/client/src/components/icons/DomainsIcon.tsx create mode 100644 agent-openai-agents-sdk-multiagent/e2e-chatbot-app-next/client/src/components/icons/DotsCircleIcon.tsx create mode 100644 agent-openai-agents-sdk-multiagent/e2e-chatbot-app-next/client/src/components/icons/DownloadIcon.tsx create mode 100644 agent-openai-agents-sdk-multiagent/e2e-chatbot-app-next/client/src/components/icons/DragIcon.tsx create mode 100644 agent-openai-agents-sdk-multiagent/e2e-chatbot-app-next/client/src/components/icons/ErdIcon.tsx create mode 100644 agent-openai-agents-sdk-multiagent/e2e-chatbot-app-next/client/src/components/icons/ExpandLessIcon.tsx create mode 100644 agent-openai-agents-sdk-multiagent/e2e-chatbot-app-next/client/src/components/icons/ExpandMoreIcon.tsx create mode 100644 agent-openai-agents-sdk-multiagent/e2e-chatbot-app-next/client/src/components/icons/FaceFrownIcon.tsx create mode 100644 agent-openai-agents-sdk-multiagent/e2e-chatbot-app-next/client/src/components/icons/FaceNeutralIcon.tsx create mode 100644 agent-openai-agents-sdk-multiagent/e2e-chatbot-app-next/client/src/components/icons/FaceSmileIcon.tsx create mode 100644 agent-openai-agents-sdk-multiagent/e2e-chatbot-app-next/client/src/components/icons/FileCodeIcon.tsx create mode 100644 agent-openai-agents-sdk-multiagent/e2e-chatbot-app-next/client/src/components/icons/FileCubeIcon.tsx create mode 100644 agent-openai-agents-sdk-multiagent/e2e-chatbot-app-next/client/src/components/icons/FileDocumentIcon.tsx create mode 100644 agent-openai-agents-sdk-multiagent/e2e-chatbot-app-next/client/src/components/icons/FileIcon.tsx create mode 100644 agent-openai-agents-sdk-multiagent/e2e-chatbot-app-next/client/src/components/icons/FileImageIcon.tsx create mode 100644 agent-openai-agents-sdk-multiagent/e2e-chatbot-app-next/client/src/components/icons/FileLockIcon.tsx create mode 100644 agent-openai-agents-sdk-multiagent/e2e-chatbot-app-next/client/src/components/icons/FileModelIcon.tsx create mode 100644 agent-openai-agents-sdk-multiagent/e2e-chatbot-app-next/client/src/components/icons/FileNewIcon.tsx create mode 100644 agent-openai-agents-sdk-multiagent/e2e-chatbot-app-next/client/src/components/icons/FilePipelineIcon.tsx create mode 100644 agent-openai-agents-sdk-multiagent/e2e-chatbot-app-next/client/src/components/icons/FilterIcon.tsx create mode 100644 agent-openai-agents-sdk-multiagent/e2e-chatbot-app-next/client/src/components/icons/FlagPointerIcon.tsx create mode 100644 agent-openai-agents-sdk-multiagent/e2e-chatbot-app-next/client/src/components/icons/FloatIcon.tsx create mode 100644 agent-openai-agents-sdk-multiagent/e2e-chatbot-app-next/client/src/components/icons/FlowIcon.tsx create mode 100644 agent-openai-agents-sdk-multiagent/e2e-chatbot-app-next/client/src/components/icons/FolderBranchFillIcon.tsx create mode 100644 agent-openai-agents-sdk-multiagent/e2e-chatbot-app-next/client/src/components/icons/FolderBranchIcon.tsx create mode 100644 agent-openai-agents-sdk-multiagent/e2e-chatbot-app-next/client/src/components/icons/FolderCloudFilledIcon.tsx create mode 100644 agent-openai-agents-sdk-multiagent/e2e-chatbot-app-next/client/src/components/icons/FolderCloudIcon.tsx create mode 100644 agent-openai-agents-sdk-multiagent/e2e-chatbot-app-next/client/src/components/icons/FolderCubeIcon.tsx create mode 100644 agent-openai-agents-sdk-multiagent/e2e-chatbot-app-next/client/src/components/icons/FolderCubeOutlineIcon.tsx create mode 100644 agent-openai-agents-sdk-multiagent/e2e-chatbot-app-next/client/src/components/icons/FolderFillIcon.tsx create mode 100644 agent-openai-agents-sdk-multiagent/e2e-chatbot-app-next/client/src/components/icons/FolderHomeIcon.tsx create mode 100644 agent-openai-agents-sdk-multiagent/e2e-chatbot-app-next/client/src/components/icons/FolderIcon.tsx create mode 100644 agent-openai-agents-sdk-multiagent/e2e-chatbot-app-next/client/src/components/icons/FolderNewIcon.tsx create mode 100644 agent-openai-agents-sdk-multiagent/e2e-chatbot-app-next/client/src/components/icons/FolderNodeIcon.tsx create mode 100644 agent-openai-agents-sdk-multiagent/e2e-chatbot-app-next/client/src/components/icons/FolderOpenBranchIcon.tsx create mode 100644 agent-openai-agents-sdk-multiagent/e2e-chatbot-app-next/client/src/components/icons/FolderOpenCloudIcon.tsx create mode 100644 agent-openai-agents-sdk-multiagent/e2e-chatbot-app-next/client/src/components/icons/FolderOpenCubeIcon.tsx create mode 100644 agent-openai-agents-sdk-multiagent/e2e-chatbot-app-next/client/src/components/icons/FolderOpenIcon.tsx create mode 100644 agent-openai-agents-sdk-multiagent/e2e-chatbot-app-next/client/src/components/icons/FolderOpenPipelineIcon.tsx create mode 100644 agent-openai-agents-sdk-multiagent/e2e-chatbot-app-next/client/src/components/icons/FolderOutlinePipelineIcon.tsx create mode 100644 agent-openai-agents-sdk-multiagent/e2e-chatbot-app-next/client/src/components/icons/FolderSolidPipelineIcon.tsx create mode 100644 agent-openai-agents-sdk-multiagent/e2e-chatbot-app-next/client/src/components/icons/FontIcon.tsx create mode 100644 agent-openai-agents-sdk-multiagent/e2e-chatbot-app-next/client/src/components/icons/ForkHorizontalIcon.tsx create mode 100644 agent-openai-agents-sdk-multiagent/e2e-chatbot-app-next/client/src/components/icons/ForkIcon.tsx create mode 100644 agent-openai-agents-sdk-multiagent/e2e-chatbot-app-next/client/src/components/icons/FullscreenExitIcon.tsx create mode 100644 agent-openai-agents-sdk-multiagent/e2e-chatbot-app-next/client/src/components/icons/FullscreenIcon.tsx create mode 100644 agent-openai-agents-sdk-multiagent/e2e-chatbot-app-next/client/src/components/icons/FunctionIcon.tsx create mode 100644 agent-openai-agents-sdk-multiagent/e2e-chatbot-app-next/client/src/components/icons/GavelIcon.tsx create mode 100644 agent-openai-agents-sdk-multiagent/e2e-chatbot-app-next/client/src/components/icons/GearFillIcon.tsx create mode 100644 agent-openai-agents-sdk-multiagent/e2e-chatbot-app-next/client/src/components/icons/GearIcon.tsx create mode 100644 agent-openai-agents-sdk-multiagent/e2e-chatbot-app-next/client/src/components/icons/GenieDeepResearchIcon.tsx create mode 100644 agent-openai-agents-sdk-multiagent/e2e-chatbot-app-next/client/src/components/icons/GiftIcon.tsx create mode 100644 agent-openai-agents-sdk-multiagent/e2e-chatbot-app-next/client/src/components/icons/GitCommitIcon.tsx create mode 100644 agent-openai-agents-sdk-multiagent/e2e-chatbot-app-next/client/src/components/icons/GlobeIcon.tsx create mode 100644 agent-openai-agents-sdk-multiagent/e2e-chatbot-app-next/client/src/components/icons/GridDashIcon.tsx create mode 100644 agent-openai-agents-sdk-multiagent/e2e-chatbot-app-next/client/src/components/icons/GridIcon.tsx create mode 100644 agent-openai-agents-sdk-multiagent/e2e-chatbot-app-next/client/src/components/icons/GroupIcon.tsx create mode 100644 agent-openai-agents-sdk-multiagent/e2e-chatbot-app-next/client/src/components/icons/H1Icon.tsx create mode 100644 agent-openai-agents-sdk-multiagent/e2e-chatbot-app-next/client/src/components/icons/H2Icon.tsx create mode 100644 agent-openai-agents-sdk-multiagent/e2e-chatbot-app-next/client/src/components/icons/H3Icon.tsx create mode 100644 agent-openai-agents-sdk-multiagent/e2e-chatbot-app-next/client/src/components/icons/H4Icon.tsx create mode 100644 agent-openai-agents-sdk-multiagent/e2e-chatbot-app-next/client/src/components/icons/H5Icon.tsx create mode 100644 agent-openai-agents-sdk-multiagent/e2e-chatbot-app-next/client/src/components/icons/H6Icon.tsx create mode 100644 agent-openai-agents-sdk-multiagent/e2e-chatbot-app-next/client/src/components/icons/HashIcon.tsx create mode 100644 agent-openai-agents-sdk-multiagent/e2e-chatbot-app-next/client/src/components/icons/HistoryIcon.tsx create mode 100644 agent-openai-agents-sdk-multiagent/e2e-chatbot-app-next/client/src/components/icons/HomeIcon.tsx create mode 100644 agent-openai-agents-sdk-multiagent/e2e-chatbot-app-next/client/src/components/icons/ImageIcon.tsx create mode 100644 agent-openai-agents-sdk-multiagent/e2e-chatbot-app-next/client/src/components/icons/IndentDecreaseIcon.tsx create mode 100644 agent-openai-agents-sdk-multiagent/e2e-chatbot-app-next/client/src/components/icons/IndentIncreaseIcon.tsx create mode 100644 agent-openai-agents-sdk-multiagent/e2e-chatbot-app-next/client/src/components/icons/InfinityIcon.tsx create mode 100644 agent-openai-agents-sdk-multiagent/e2e-chatbot-app-next/client/src/components/icons/InfoBookIcon.tsx create mode 100644 agent-openai-agents-sdk-multiagent/e2e-chatbot-app-next/client/src/components/icons/InfoFillIcon.tsx create mode 100644 agent-openai-agents-sdk-multiagent/e2e-chatbot-app-next/client/src/components/icons/InfoIcon.tsx create mode 100644 agent-openai-agents-sdk-multiagent/e2e-chatbot-app-next/client/src/components/icons/InfoSmallIcon.tsx create mode 100644 agent-openai-agents-sdk-multiagent/e2e-chatbot-app-next/client/src/components/icons/IngestionIcon.tsx create mode 100644 agent-openai-agents-sdk-multiagent/e2e-chatbot-app-next/client/src/components/icons/ItalicIcon.tsx create mode 100644 agent-openai-agents-sdk-multiagent/e2e-chatbot-app-next/client/src/components/icons/JoinOperatorIcon.tsx create mode 100644 agent-openai-agents-sdk-multiagent/e2e-chatbot-app-next/client/src/components/icons/KeyIcon.tsx create mode 100644 agent-openai-agents-sdk-multiagent/e2e-chatbot-app-next/client/src/components/icons/KeyboardIcon.tsx create mode 100644 agent-openai-agents-sdk-multiagent/e2e-chatbot-app-next/client/src/components/icons/LayerGraphIcon.tsx create mode 100644 agent-openai-agents-sdk-multiagent/e2e-chatbot-app-next/client/src/components/icons/LayerIcon.tsx create mode 100644 agent-openai-agents-sdk-multiagent/e2e-chatbot-app-next/client/src/components/icons/LeafIcon.tsx create mode 100644 agent-openai-agents-sdk-multiagent/e2e-chatbot-app-next/client/src/components/icons/LetterFormatIcon.tsx create mode 100644 agent-openai-agents-sdk-multiagent/e2e-chatbot-app-next/client/src/components/icons/LettersIcon.tsx create mode 100644 agent-openai-agents-sdk-multiagent/e2e-chatbot-app-next/client/src/components/icons/LettersNumbersIcon.tsx create mode 100644 agent-openai-agents-sdk-multiagent/e2e-chatbot-app-next/client/src/components/icons/LibrariesIcon.tsx create mode 100644 agent-openai-agents-sdk-multiagent/e2e-chatbot-app-next/client/src/components/icons/LifesaverIcon.tsx create mode 100644 agent-openai-agents-sdk-multiagent/e2e-chatbot-app-next/client/src/components/icons/LightbulbIcon.tsx create mode 100644 agent-openai-agents-sdk-multiagent/e2e-chatbot-app-next/client/src/components/icons/LightningCircleFillIcon.tsx create mode 100644 agent-openai-agents-sdk-multiagent/e2e-chatbot-app-next/client/src/components/icons/LightningIcon.tsx create mode 100644 agent-openai-agents-sdk-multiagent/e2e-chatbot-app-next/client/src/components/icons/LinkIcon.tsx create mode 100644 agent-openai-agents-sdk-multiagent/e2e-chatbot-app-next/client/src/components/icons/LinkOffIcon.tsx create mode 100644 agent-openai-agents-sdk-multiagent/e2e-chatbot-app-next/client/src/components/icons/ListBorderIcon.tsx create mode 100644 agent-openai-agents-sdk-multiagent/e2e-chatbot-app-next/client/src/components/icons/ListClearIcon.tsx create mode 100644 agent-openai-agents-sdk-multiagent/e2e-chatbot-app-next/client/src/components/icons/ListIcon.tsx create mode 100644 agent-openai-agents-sdk-multiagent/e2e-chatbot-app-next/client/src/components/icons/ListNumberIcon.tsx create mode 100644 agent-openai-agents-sdk-multiagent/e2e-chatbot-app-next/client/src/components/icons/LoadingIcon.tsx create mode 100644 agent-openai-agents-sdk-multiagent/e2e-chatbot-app-next/client/src/components/icons/LockFillIcon.tsx create mode 100644 agent-openai-agents-sdk-multiagent/e2e-chatbot-app-next/client/src/components/icons/LockIcon.tsx create mode 100644 agent-openai-agents-sdk-multiagent/e2e-chatbot-app-next/client/src/components/icons/LockShareIcon.tsx create mode 100644 agent-openai-agents-sdk-multiagent/e2e-chatbot-app-next/client/src/components/icons/LockUnlockedIcon.tsx create mode 100644 agent-openai-agents-sdk-multiagent/e2e-chatbot-app-next/client/src/components/icons/LoopIcon.tsx create mode 100644 agent-openai-agents-sdk-multiagent/e2e-chatbot-app-next/client/src/components/icons/MailIcon.tsx create mode 100644 agent-openai-agents-sdk-multiagent/e2e-chatbot-app-next/client/src/components/icons/MapIcon.tsx create mode 100644 agent-openai-agents-sdk-multiagent/e2e-chatbot-app-next/client/src/components/icons/MarkdownIcon.tsx create mode 100644 agent-openai-agents-sdk-multiagent/e2e-chatbot-app-next/client/src/components/icons/McpIcon.tsx create mode 100644 agent-openai-agents-sdk-multiagent/e2e-chatbot-app-next/client/src/components/icons/MeasureIcon.tsx create mode 100644 agent-openai-agents-sdk-multiagent/e2e-chatbot-app-next/client/src/components/icons/MegaphoneIcon.tsx create mode 100644 agent-openai-agents-sdk-multiagent/e2e-chatbot-app-next/client/src/components/icons/MenuIcon.tsx create mode 100644 agent-openai-agents-sdk-multiagent/e2e-chatbot-app-next/client/src/components/icons/MinusCircleFillIcon.tsx create mode 100644 agent-openai-agents-sdk-multiagent/e2e-chatbot-app-next/client/src/components/icons/MinusCircleIcon.tsx create mode 100644 agent-openai-agents-sdk-multiagent/e2e-chatbot-app-next/client/src/components/icons/MinusCircleSmallIcon.tsx create mode 100644 agent-openai-agents-sdk-multiagent/e2e-chatbot-app-next/client/src/components/icons/MinusSquareIcon.tsx create mode 100644 agent-openai-agents-sdk-multiagent/e2e-chatbot-app-next/client/src/components/icons/ModelsIcon.tsx create mode 100644 agent-openai-agents-sdk-multiagent/e2e-chatbot-app-next/client/src/components/icons/MoonIcon.tsx create mode 100644 agent-openai-agents-sdk-multiagent/e2e-chatbot-app-next/client/src/components/icons/NeonProjectIcon.tsx create mode 100644 agent-openai-agents-sdk-multiagent/e2e-chatbot-app-next/client/src/components/icons/NewChatIcon.tsx create mode 100644 agent-openai-agents-sdk-multiagent/e2e-chatbot-app-next/client/src/components/icons/NewWindowIcon.tsx create mode 100644 agent-openai-agents-sdk-multiagent/e2e-chatbot-app-next/client/src/components/icons/NoIcon.tsx create mode 100644 agent-openai-agents-sdk-multiagent/e2e-chatbot-app-next/client/src/components/icons/NotebookIcon.tsx create mode 100644 agent-openai-agents-sdk-multiagent/e2e-chatbot-app-next/client/src/components/icons/NotebookPipelineIcon.tsx create mode 100644 agent-openai-agents-sdk-multiagent/e2e-chatbot-app-next/client/src/components/icons/NotificationIcon.tsx create mode 100644 agent-openai-agents-sdk-multiagent/e2e-chatbot-app-next/client/src/components/icons/NotificationOffIcon.tsx create mode 100644 agent-openai-agents-sdk-multiagent/e2e-chatbot-app-next/client/src/components/icons/NumberFormatIcon.tsx create mode 100644 agent-openai-agents-sdk-multiagent/e2e-chatbot-app-next/client/src/components/icons/NumbersIcon.tsx create mode 100644 agent-openai-agents-sdk-multiagent/e2e-chatbot-app-next/client/src/components/icons/OfficeIcon.tsx create mode 100644 agent-openai-agents-sdk-multiagent/e2e-chatbot-app-next/client/src/components/icons/OverflowIcon.tsx create mode 100644 agent-openai-agents-sdk-multiagent/e2e-chatbot-app-next/client/src/components/icons/PageBottomIcon.tsx create mode 100644 agent-openai-agents-sdk-multiagent/e2e-chatbot-app-next/client/src/components/icons/PageFirstIcon.tsx create mode 100644 agent-openai-agents-sdk-multiagent/e2e-chatbot-app-next/client/src/components/icons/PageLastIcon.tsx create mode 100644 agent-openai-agents-sdk-multiagent/e2e-chatbot-app-next/client/src/components/icons/PageTopIcon.tsx create mode 100644 agent-openai-agents-sdk-multiagent/e2e-chatbot-app-next/client/src/components/icons/PanelDockedIcon.tsx create mode 100644 agent-openai-agents-sdk-multiagent/e2e-chatbot-app-next/client/src/components/icons/PanelFloatingIcon.tsx create mode 100644 agent-openai-agents-sdk-multiagent/e2e-chatbot-app-next/client/src/components/icons/PaperclipIcon.tsx create mode 100644 agent-openai-agents-sdk-multiagent/e2e-chatbot-app-next/client/src/components/icons/PauseIcon.tsx create mode 100644 agent-openai-agents-sdk-multiagent/e2e-chatbot-app-next/client/src/components/icons/PencilFillIcon.tsx create mode 100644 agent-openai-agents-sdk-multiagent/e2e-chatbot-app-next/client/src/components/icons/PencilIcon.tsx create mode 100644 agent-openai-agents-sdk-multiagent/e2e-chatbot-app-next/client/src/components/icons/PencilSparkleIcon.tsx create mode 100644 agent-openai-agents-sdk-multiagent/e2e-chatbot-app-next/client/src/components/icons/PieChartIcon.tsx create mode 100644 agent-openai-agents-sdk-multiagent/e2e-chatbot-app-next/client/src/components/icons/PinCancelIcon.tsx create mode 100644 agent-openai-agents-sdk-multiagent/e2e-chatbot-app-next/client/src/components/icons/PinFillIcon.tsx create mode 100644 agent-openai-agents-sdk-multiagent/e2e-chatbot-app-next/client/src/components/icons/PinIcon.tsx create mode 100644 agent-openai-agents-sdk-multiagent/e2e-chatbot-app-next/client/src/components/icons/PipelineCodeIcon.tsx create mode 100644 agent-openai-agents-sdk-multiagent/e2e-chatbot-app-next/client/src/components/icons/PipelineCubeIcon.tsx create mode 100644 agent-openai-agents-sdk-multiagent/e2e-chatbot-app-next/client/src/components/icons/PipelineIcon.tsx create mode 100644 agent-openai-agents-sdk-multiagent/e2e-chatbot-app-next/client/src/components/icons/PlayCircleFillIcon.tsx create mode 100644 agent-openai-agents-sdk-multiagent/e2e-chatbot-app-next/client/src/components/icons/PlayCircleIcon.tsx create mode 100644 agent-openai-agents-sdk-multiagent/e2e-chatbot-app-next/client/src/components/icons/PlayDoubleIcon.tsx create mode 100644 agent-openai-agents-sdk-multiagent/e2e-chatbot-app-next/client/src/components/icons/PlayIcon.tsx create mode 100644 agent-openai-agents-sdk-multiagent/e2e-chatbot-app-next/client/src/components/icons/PlayMultipleIcon.tsx create mode 100644 agent-openai-agents-sdk-multiagent/e2e-chatbot-app-next/client/src/components/icons/PlugIcon.tsx create mode 100644 agent-openai-agents-sdk-multiagent/e2e-chatbot-app-next/client/src/components/icons/PlusCircleFillIcon.tsx create mode 100644 agent-openai-agents-sdk-multiagent/e2e-chatbot-app-next/client/src/components/icons/PlusCircleIcon.tsx create mode 100644 agent-openai-agents-sdk-multiagent/e2e-chatbot-app-next/client/src/components/icons/PlusCircleSmallIcon.tsx create mode 100644 agent-openai-agents-sdk-multiagent/e2e-chatbot-app-next/client/src/components/icons/PlusIcon.tsx create mode 100644 agent-openai-agents-sdk-multiagent/e2e-chatbot-app-next/client/src/components/icons/PlusMinusSquareIcon.tsx create mode 100644 agent-openai-agents-sdk-multiagent/e2e-chatbot-app-next/client/src/components/icons/PlusSquareIcon.tsx create mode 100644 agent-openai-agents-sdk-multiagent/e2e-chatbot-app-next/client/src/components/icons/PositionBottomIcon.tsx create mode 100644 agent-openai-agents-sdk-multiagent/e2e-chatbot-app-next/client/src/components/icons/PositionLeftIcon.tsx create mode 100644 agent-openai-agents-sdk-multiagent/e2e-chatbot-app-next/client/src/components/icons/PositionRightIcon.tsx create mode 100644 agent-openai-agents-sdk-multiagent/e2e-chatbot-app-next/client/src/components/icons/PositionTopIcon.tsx create mode 100644 agent-openai-agents-sdk-multiagent/e2e-chatbot-app-next/client/src/components/icons/PullRequestIcon.tsx create mode 100644 agent-openai-agents-sdk-multiagent/e2e-chatbot-app-next/client/src/components/icons/PuzzleIcon.tsx create mode 100644 agent-openai-agents-sdk-multiagent/e2e-chatbot-app-next/client/src/components/icons/QueryEditorIcon.tsx create mode 100644 agent-openai-agents-sdk-multiagent/e2e-chatbot-app-next/client/src/components/icons/QueryIcon.tsx create mode 100644 agent-openai-agents-sdk-multiagent/e2e-chatbot-app-next/client/src/components/icons/QuestionMarkFillIcon.tsx create mode 100644 agent-openai-agents-sdk-multiagent/e2e-chatbot-app-next/client/src/components/icons/QuestionMarkIcon.tsx create mode 100644 agent-openai-agents-sdk-multiagent/e2e-chatbot-app-next/client/src/components/icons/RadioIcon.tsx create mode 100644 agent-openai-agents-sdk-multiagent/e2e-chatbot-app-next/client/src/components/icons/ReaderModeIcon.tsx create mode 100644 agent-openai-agents-sdk-multiagent/e2e-chatbot-app-next/client/src/components/icons/RedoIcon.tsx create mode 100644 agent-openai-agents-sdk-multiagent/e2e-chatbot-app-next/client/src/components/icons/RefreshIcon.tsx create mode 100644 agent-openai-agents-sdk-multiagent/e2e-chatbot-app-next/client/src/components/icons/RefreshPlayIcon.tsx create mode 100644 agent-openai-agents-sdk-multiagent/e2e-chatbot-app-next/client/src/components/icons/RefreshXIcon.tsx create mode 100644 agent-openai-agents-sdk-multiagent/e2e-chatbot-app-next/client/src/components/icons/ReplyIcon.tsx create mode 100644 agent-openai-agents-sdk-multiagent/e2e-chatbot-app-next/client/src/components/icons/ResizeIcon.tsx create mode 100644 agent-openai-agents-sdk-multiagent/e2e-chatbot-app-next/client/src/components/icons/RichTextIcon.tsx create mode 100644 agent-openai-agents-sdk-multiagent/e2e-chatbot-app-next/client/src/components/icons/RobotIcon.tsx create mode 100644 agent-openai-agents-sdk-multiagent/e2e-chatbot-app-next/client/src/components/icons/RocketIcon.tsx create mode 100644 agent-openai-agents-sdk-multiagent/e2e-chatbot-app-next/client/src/components/icons/RowsIcon.tsx create mode 100644 agent-openai-agents-sdk-multiagent/e2e-chatbot-app-next/client/src/components/icons/RunIcon.tsx create mode 100644 agent-openai-agents-sdk-multiagent/e2e-chatbot-app-next/client/src/components/icons/RunningIcon.tsx create mode 100644 agent-openai-agents-sdk-multiagent/e2e-chatbot-app-next/client/src/components/icons/SaveClockIcon.tsx create mode 100644 agent-openai-agents-sdk-multiagent/e2e-chatbot-app-next/client/src/components/icons/SaveIcon.tsx create mode 100644 agent-openai-agents-sdk-multiagent/e2e-chatbot-app-next/client/src/components/icons/SchemaIcon.tsx create mode 100644 agent-openai-agents-sdk-multiagent/e2e-chatbot-app-next/client/src/components/icons/SchoolIcon.tsx create mode 100644 agent-openai-agents-sdk-multiagent/e2e-chatbot-app-next/client/src/components/icons/SearchDataIcon.tsx create mode 100644 agent-openai-agents-sdk-multiagent/e2e-chatbot-app-next/client/src/components/icons/SearchIcon.tsx create mode 100644 agent-openai-agents-sdk-multiagent/e2e-chatbot-app-next/client/src/components/icons/SendIcon.tsx create mode 100644 agent-openai-agents-sdk-multiagent/e2e-chatbot-app-next/client/src/components/icons/ShareIcon.tsx create mode 100644 agent-openai-agents-sdk-multiagent/e2e-chatbot-app-next/client/src/components/icons/ShieldCheckIcon.tsx create mode 100644 agent-openai-agents-sdk-multiagent/e2e-chatbot-app-next/client/src/components/icons/ShieldIcon.tsx create mode 100644 agent-openai-agents-sdk-multiagent/e2e-chatbot-app-next/client/src/components/icons/ShieldOffIcon.tsx create mode 100644 agent-openai-agents-sdk-multiagent/e2e-chatbot-app-next/client/src/components/icons/ShortcutIcon.tsx create mode 100644 agent-openai-agents-sdk-multiagent/e2e-chatbot-app-next/client/src/components/icons/SidebarAutoIcon.tsx create mode 100644 agent-openai-agents-sdk-multiagent/e2e-chatbot-app-next/client/src/components/icons/SidebarClosedIcon.tsx create mode 100644 agent-openai-agents-sdk-multiagent/e2e-chatbot-app-next/client/src/components/icons/SidebarCollapseIcon.tsx create mode 100644 agent-openai-agents-sdk-multiagent/e2e-chatbot-app-next/client/src/components/icons/SidebarExpandIcon.tsx create mode 100644 agent-openai-agents-sdk-multiagent/e2e-chatbot-app-next/client/src/components/icons/SidebarIcon.tsx create mode 100644 agent-openai-agents-sdk-multiagent/e2e-chatbot-app-next/client/src/components/icons/SidebarOpenIcon.tsx create mode 100644 agent-openai-agents-sdk-multiagent/e2e-chatbot-app-next/client/src/components/icons/SidebarSyncIcon.tsx create mode 100644 agent-openai-agents-sdk-multiagent/e2e-chatbot-app-next/client/src/components/icons/SlashSquareIcon.tsx create mode 100644 agent-openai-agents-sdk-multiagent/e2e-chatbot-app-next/client/src/components/icons/SlidersIcon.tsx create mode 100644 agent-openai-agents-sdk-multiagent/e2e-chatbot-app-next/client/src/components/icons/SortAscendingIcon.tsx create mode 100644 agent-openai-agents-sdk-multiagent/e2e-chatbot-app-next/client/src/components/icons/SortCustomHorizontalIcon.tsx create mode 100644 agent-openai-agents-sdk-multiagent/e2e-chatbot-app-next/client/src/components/icons/SortCustomVerticalIcon.tsx create mode 100644 agent-openai-agents-sdk-multiagent/e2e-chatbot-app-next/client/src/components/icons/SortDescendingIcon.tsx create mode 100644 agent-openai-agents-sdk-multiagent/e2e-chatbot-app-next/client/src/components/icons/SortHorizontalAscendingIcon.tsx create mode 100644 agent-openai-agents-sdk-multiagent/e2e-chatbot-app-next/client/src/components/icons/SortHorizontalDescendingIcon.tsx create mode 100644 agent-openai-agents-sdk-multiagent/e2e-chatbot-app-next/client/src/components/icons/SortLetterHorizontalAscendingIcon.tsx create mode 100644 agent-openai-agents-sdk-multiagent/e2e-chatbot-app-next/client/src/components/icons/SortLetterHorizontalDescendingIcon.tsx create mode 100644 agent-openai-agents-sdk-multiagent/e2e-chatbot-app-next/client/src/components/icons/SortLetterUnsortedIcon.tsx create mode 100644 agent-openai-agents-sdk-multiagent/e2e-chatbot-app-next/client/src/components/icons/SortLetterVerticalAscendingIcon.tsx create mode 100644 agent-openai-agents-sdk-multiagent/e2e-chatbot-app-next/client/src/components/icons/SortLetterVerticalDescendingIcon.tsx create mode 100644 agent-openai-agents-sdk-multiagent/e2e-chatbot-app-next/client/src/components/icons/SortUnsortedIcon.tsx create mode 100644 agent-openai-agents-sdk-multiagent/e2e-chatbot-app-next/client/src/components/icons/SortVerticalAscendingIcon.tsx create mode 100644 agent-openai-agents-sdk-multiagent/e2e-chatbot-app-next/client/src/components/icons/SortVerticalDescendingIcon.tsx create mode 100644 agent-openai-agents-sdk-multiagent/e2e-chatbot-app-next/client/src/components/icons/SparkleDoubleFillIcon.tsx create mode 100644 agent-openai-agents-sdk-multiagent/e2e-chatbot-app-next/client/src/components/icons/SparkleDoubleIcon.tsx create mode 100644 agent-openai-agents-sdk-multiagent/e2e-chatbot-app-next/client/src/components/icons/SparkleFillIcon.tsx create mode 100644 agent-openai-agents-sdk-multiagent/e2e-chatbot-app-next/client/src/components/icons/SparkleIcon.tsx create mode 100644 agent-openai-agents-sdk-multiagent/e2e-chatbot-app-next/client/src/components/icons/SparkleRectangleIcon.tsx create mode 100644 agent-openai-agents-sdk-multiagent/e2e-chatbot-app-next/client/src/components/icons/SpeechBubbleIcon.tsx create mode 100644 agent-openai-agents-sdk-multiagent/e2e-chatbot-app-next/client/src/components/icons/SpeechBubblePlusIcon.tsx create mode 100644 agent-openai-agents-sdk-multiagent/e2e-chatbot-app-next/client/src/components/icons/SpeechBubbleQuestionMarkFillIcon.tsx create mode 100644 agent-openai-agents-sdk-multiagent/e2e-chatbot-app-next/client/src/components/icons/SpeechBubbleQuestionMarkIcon.tsx create mode 100644 agent-openai-agents-sdk-multiagent/e2e-chatbot-app-next/client/src/components/icons/SpeechBubbleStarIcon.tsx create mode 100644 agent-openai-agents-sdk-multiagent/e2e-chatbot-app-next/client/src/components/icons/SpeedometerIcon.tsx create mode 100644 agent-openai-agents-sdk-multiagent/e2e-chatbot-app-next/client/src/components/icons/StarFillIcon.tsx create mode 100644 agent-openai-agents-sdk-multiagent/e2e-chatbot-app-next/client/src/components/icons/StarIcon.tsx create mode 100644 agent-openai-agents-sdk-multiagent/e2e-chatbot-app-next/client/src/components/icons/StopCircleFillIcon.tsx create mode 100644 agent-openai-agents-sdk-multiagent/e2e-chatbot-app-next/client/src/components/icons/StopCircleIcon.tsx create mode 100644 agent-openai-agents-sdk-multiagent/e2e-chatbot-app-next/client/src/components/icons/StopIcon.tsx create mode 100644 agent-openai-agents-sdk-multiagent/e2e-chatbot-app-next/client/src/components/icons/StoredProcedureIcon.tsx create mode 100644 agent-openai-agents-sdk-multiagent/e2e-chatbot-app-next/client/src/components/icons/StorefrontIcon.tsx create mode 100644 agent-openai-agents-sdk-multiagent/e2e-chatbot-app-next/client/src/components/icons/StreamIcon.tsx create mode 100644 agent-openai-agents-sdk-multiagent/e2e-chatbot-app-next/client/src/components/icons/StrikeThroughIcon.tsx create mode 100644 agent-openai-agents-sdk-multiagent/e2e-chatbot-app-next/client/src/components/icons/SunIcon.tsx create mode 100644 agent-openai-agents-sdk-multiagent/e2e-chatbot-app-next/client/src/components/icons/SyncIcon.tsx create mode 100644 agent-openai-agents-sdk-multiagent/e2e-chatbot-app-next/client/src/components/icons/SyncSmallIcon.tsx create mode 100644 agent-openai-agents-sdk-multiagent/e2e-chatbot-app-next/client/src/components/icons/SyncToFileIcon.tsx create mode 100644 agent-openai-agents-sdk-multiagent/e2e-chatbot-app-next/client/src/components/icons/TableAsteriskIcon.tsx create mode 100644 agent-openai-agents-sdk-multiagent/e2e-chatbot-app-next/client/src/components/icons/TableClockIcon.tsx create mode 100644 agent-openai-agents-sdk-multiagent/e2e-chatbot-app-next/client/src/components/icons/TableCombineIcon.tsx create mode 100644 agent-openai-agents-sdk-multiagent/e2e-chatbot-app-next/client/src/components/icons/TableGlassesIcon.tsx create mode 100644 agent-openai-agents-sdk-multiagent/e2e-chatbot-app-next/client/src/components/icons/TableGlobeIcon.tsx create mode 100644 agent-openai-agents-sdk-multiagent/e2e-chatbot-app-next/client/src/components/icons/TableIcon.tsx create mode 100644 agent-openai-agents-sdk-multiagent/e2e-chatbot-app-next/client/src/components/icons/TableLightningIcon.tsx create mode 100644 agent-openai-agents-sdk-multiagent/e2e-chatbot-app-next/client/src/components/icons/TableMeasureIcon.tsx create mode 100644 agent-openai-agents-sdk-multiagent/e2e-chatbot-app-next/client/src/components/icons/TableModelIcon.tsx create mode 100644 agent-openai-agents-sdk-multiagent/e2e-chatbot-app-next/client/src/components/icons/TableStreamIcon.tsx create mode 100644 agent-openai-agents-sdk-multiagent/e2e-chatbot-app-next/client/src/components/icons/TableVectorIcon.tsx create mode 100644 agent-openai-agents-sdk-multiagent/e2e-chatbot-app-next/client/src/components/icons/TableViewIcon.tsx create mode 100644 agent-openai-agents-sdk-multiagent/e2e-chatbot-app-next/client/src/components/icons/TagIcon.tsx create mode 100644 agent-openai-agents-sdk-multiagent/e2e-chatbot-app-next/client/src/components/icons/TargetIcon.tsx create mode 100644 agent-openai-agents-sdk-multiagent/e2e-chatbot-app-next/client/src/components/icons/TerminalIcon.tsx create mode 100644 agent-openai-agents-sdk-multiagent/e2e-chatbot-app-next/client/src/components/icons/TextBoxIcon.tsx create mode 100644 agent-openai-agents-sdk-multiagent/e2e-chatbot-app-next/client/src/components/icons/TextIcon.tsx create mode 100644 agent-openai-agents-sdk-multiagent/e2e-chatbot-app-next/client/src/components/icons/TextJustifyIcon.tsx create mode 100644 agent-openai-agents-sdk-multiagent/e2e-chatbot-app-next/client/src/components/icons/TextUnderlineIcon.tsx create mode 100644 agent-openai-agents-sdk-multiagent/e2e-chatbot-app-next/client/src/components/icons/ThreeDotsIcon.tsx create mode 100644 agent-openai-agents-sdk-multiagent/e2e-chatbot-app-next/client/src/components/icons/ThumbsDownIcon.tsx create mode 100644 agent-openai-agents-sdk-multiagent/e2e-chatbot-app-next/client/src/components/icons/ThumbsUpIcon.tsx create mode 100644 agent-openai-agents-sdk-multiagent/e2e-chatbot-app-next/client/src/components/icons/TokenIcon.tsx create mode 100644 agent-openai-agents-sdk-multiagent/e2e-chatbot-app-next/client/src/components/icons/TrashIcon.tsx create mode 100644 agent-openai-agents-sdk-multiagent/e2e-chatbot-app-next/client/src/components/icons/TreeIcon.tsx create mode 100644 agent-openai-agents-sdk-multiagent/e2e-chatbot-app-next/client/src/components/icons/TrendingIcon.tsx create mode 100644 agent-openai-agents-sdk-multiagent/e2e-chatbot-app-next/client/src/components/icons/TriangleIcon.tsx create mode 100644 agent-openai-agents-sdk-multiagent/e2e-chatbot-app-next/client/src/components/icons/UnderlineIcon.tsx create mode 100644 agent-openai-agents-sdk-multiagent/e2e-chatbot-app-next/client/src/components/icons/UndoIcon.tsx create mode 100644 agent-openai-agents-sdk-multiagent/e2e-chatbot-app-next/client/src/components/icons/UploadIcon.tsx create mode 100644 agent-openai-agents-sdk-multiagent/e2e-chatbot-app-next/client/src/components/icons/UsbIcon.tsx create mode 100644 agent-openai-agents-sdk-multiagent/e2e-chatbot-app-next/client/src/components/icons/UserBadgeIcon.tsx create mode 100644 agent-openai-agents-sdk-multiagent/e2e-chatbot-app-next/client/src/components/icons/UserCircleIcon.tsx create mode 100644 agent-openai-agents-sdk-multiagent/e2e-chatbot-app-next/client/src/components/icons/UserGroupFillIcon.tsx create mode 100644 agent-openai-agents-sdk-multiagent/e2e-chatbot-app-next/client/src/components/icons/UserGroupIcon.tsx create mode 100644 agent-openai-agents-sdk-multiagent/e2e-chatbot-app-next/client/src/components/icons/UserHomeVolumeIcon.tsx create mode 100644 agent-openai-agents-sdk-multiagent/e2e-chatbot-app-next/client/src/components/icons/UserIcon.tsx create mode 100644 agent-openai-agents-sdk-multiagent/e2e-chatbot-app-next/client/src/components/icons/UserKeyIconIcon.tsx create mode 100644 agent-openai-agents-sdk-multiagent/e2e-chatbot-app-next/client/src/components/icons/UserShieldIcon.tsx create mode 100644 agent-openai-agents-sdk-multiagent/e2e-chatbot-app-next/client/src/components/icons/UserSparkleIcon.tsx create mode 100644 agent-openai-agents-sdk-multiagent/e2e-chatbot-app-next/client/src/components/icons/UserTeamIcon.tsx create mode 100644 agent-openai-agents-sdk-multiagent/e2e-chatbot-app-next/client/src/components/icons/VisibleFillIcon.tsx create mode 100644 agent-openai-agents-sdk-multiagent/e2e-chatbot-app-next/client/src/components/icons/VisibleIcon.tsx create mode 100644 agent-openai-agents-sdk-multiagent/e2e-chatbot-app-next/client/src/components/icons/VisibleOffIcon.tsx create mode 100644 agent-openai-agents-sdk-multiagent/e2e-chatbot-app-next/client/src/components/icons/WarningFillIcon.tsx create mode 100644 agent-openai-agents-sdk-multiagent/e2e-chatbot-app-next/client/src/components/icons/WarningIcon.tsx create mode 100644 agent-openai-agents-sdk-multiagent/e2e-chatbot-app-next/client/src/components/icons/WorkflowCodeIcon.tsx create mode 100644 agent-openai-agents-sdk-multiagent/e2e-chatbot-app-next/client/src/components/icons/WorkflowCubeIcon.tsx create mode 100644 agent-openai-agents-sdk-multiagent/e2e-chatbot-app-next/client/src/components/icons/WorkflowsIcon.tsx create mode 100644 agent-openai-agents-sdk-multiagent/e2e-chatbot-app-next/client/src/components/icons/WorkspacesIcon.tsx create mode 100644 agent-openai-agents-sdk-multiagent/e2e-chatbot-app-next/client/src/components/icons/WrenchIcon.tsx create mode 100644 agent-openai-agents-sdk-multiagent/e2e-chatbot-app-next/client/src/components/icons/WrenchSparkleIcon.tsx create mode 100644 agent-openai-agents-sdk-multiagent/e2e-chatbot-app-next/client/src/components/icons/XCircleFillIcon.tsx create mode 100644 agent-openai-agents-sdk-multiagent/e2e-chatbot-app-next/client/src/components/icons/XCircleIcon.tsx create mode 100644 agent-openai-agents-sdk-multiagent/e2e-chatbot-app-next/client/src/components/icons/ZaHorizontalIcon.tsx create mode 100644 agent-openai-agents-sdk-multiagent/e2e-chatbot-app-next/client/src/components/icons/ZaVerticalIcon.tsx create mode 100644 agent-openai-agents-sdk-multiagent/e2e-chatbot-app-next/client/src/components/icons/ZoomInIcon.tsx create mode 100644 agent-openai-agents-sdk-multiagent/e2e-chatbot-app-next/client/src/components/icons/ZoomMarqueeSelection.tsx create mode 100644 agent-openai-agents-sdk-multiagent/e2e-chatbot-app-next/client/src/components/icons/ZoomOutIcon.tsx create mode 100644 agent-openai-agents-sdk-multiagent/e2e-chatbot-app-next/client/src/components/icons/ZoomToFitIcon.tsx create mode 100644 agent-openai-agents-sdk-multiagent/e2e-chatbot-app-next/client/src/components/icons/index.ts create mode 100644 agent-openai-agents-sdk-multiagent/e2e-chatbot-app-next/client/src/components/message-actions.tsx create mode 100644 agent-openai-agents-sdk-multiagent/e2e-chatbot-app-next/client/src/components/message-editor.tsx create mode 100644 agent-openai-agents-sdk-multiagent/e2e-chatbot-app-next/client/src/components/message-error.tsx create mode 100644 agent-openai-agents-sdk-multiagent/e2e-chatbot-app-next/client/src/components/message-oauth-error.tsx create mode 100644 agent-openai-agents-sdk-multiagent/e2e-chatbot-app-next/client/src/components/message-reasoning.tsx create mode 100644 agent-openai-agents-sdk-multiagent/e2e-chatbot-app-next/client/src/components/message.tsx create mode 100644 agent-openai-agents-sdk-multiagent/e2e-chatbot-app-next/client/src/components/messages.tsx create mode 100644 agent-openai-agents-sdk-multiagent/e2e-chatbot-app-next/client/src/components/multimodal-input.tsx create mode 100644 agent-openai-agents-sdk-multiagent/e2e-chatbot-app-next/client/src/components/preview-attachment.tsx create mode 100644 agent-openai-agents-sdk-multiagent/e2e-chatbot-app-next/client/src/components/sidebar-history-item.tsx create mode 100644 agent-openai-agents-sdk-multiagent/e2e-chatbot-app-next/client/src/components/sidebar-history.tsx create mode 100644 agent-openai-agents-sdk-multiagent/e2e-chatbot-app-next/client/src/components/sidebar-toggle.tsx create mode 100644 agent-openai-agents-sdk-multiagent/e2e-chatbot-app-next/client/src/components/sidebar-user-nav.tsx create mode 100644 agent-openai-agents-sdk-multiagent/e2e-chatbot-app-next/client/src/components/suggested-actions.tsx create mode 100644 agent-openai-agents-sdk-multiagent/e2e-chatbot-app-next/client/src/components/theme-provider.tsx create mode 100644 agent-openai-agents-sdk-multiagent/e2e-chatbot-app-next/client/src/components/toast.tsx create mode 100644 agent-openai-agents-sdk-multiagent/e2e-chatbot-app-next/client/src/components/ui/alert-dialog.tsx create mode 100644 agent-openai-agents-sdk-multiagent/e2e-chatbot-app-next/client/src/components/ui/badge.tsx create mode 100644 agent-openai-agents-sdk-multiagent/e2e-chatbot-app-next/client/src/components/ui/button.tsx create mode 100644 agent-openai-agents-sdk-multiagent/e2e-chatbot-app-next/client/src/components/ui/collapsible.tsx create mode 100644 agent-openai-agents-sdk-multiagent/e2e-chatbot-app-next/client/src/components/ui/db-icon.tsx create mode 100644 agent-openai-agents-sdk-multiagent/e2e-chatbot-app-next/client/src/components/ui/dropdown-menu.tsx create mode 100644 agent-openai-agents-sdk-multiagent/e2e-chatbot-app-next/client/src/components/ui/input.tsx create mode 100644 agent-openai-agents-sdk-multiagent/e2e-chatbot-app-next/client/src/components/ui/separator.tsx create mode 100644 agent-openai-agents-sdk-multiagent/e2e-chatbot-app-next/client/src/components/ui/sheet.tsx create mode 100644 agent-openai-agents-sdk-multiagent/e2e-chatbot-app-next/client/src/components/ui/shimmer.tsx create mode 100644 agent-openai-agents-sdk-multiagent/e2e-chatbot-app-next/client/src/components/ui/sidebar.tsx create mode 100644 agent-openai-agents-sdk-multiagent/e2e-chatbot-app-next/client/src/components/ui/skeleton.tsx create mode 100644 agent-openai-agents-sdk-multiagent/e2e-chatbot-app-next/client/src/components/ui/textarea.tsx create mode 100644 agent-openai-agents-sdk-multiagent/e2e-chatbot-app-next/client/src/components/ui/tooltip.tsx create mode 100644 agent-openai-agents-sdk-multiagent/e2e-chatbot-app-next/client/src/components/visibility-selector.tsx create mode 100644 agent-openai-agents-sdk-multiagent/e2e-chatbot-app-next/client/src/contexts/AppConfigContext.tsx create mode 100644 agent-openai-agents-sdk-multiagent/e2e-chatbot-app-next/client/src/contexts/SessionContext.tsx create mode 100644 agent-openai-agents-sdk-multiagent/e2e-chatbot-app-next/client/src/hooks/use-approval.ts create mode 100644 agent-openai-agents-sdk-multiagent/e2e-chatbot-app-next/client/src/hooks/use-chat-visibility.ts create mode 100644 agent-openai-agents-sdk-multiagent/e2e-chatbot-app-next/client/src/hooks/use-config.ts create mode 100644 agent-openai-agents-sdk-multiagent/e2e-chatbot-app-next/client/src/hooks/use-messages.tsx create mode 100644 agent-openai-agents-sdk-multiagent/e2e-chatbot-app-next/client/src/hooks/use-mobile.tsx create mode 100644 agent-openai-agents-sdk-multiagent/e2e-chatbot-app-next/client/src/hooks/use-scroll-to-bottom.tsx create mode 100644 agent-openai-agents-sdk-multiagent/e2e-chatbot-app-next/client/src/hooks/useChatData.ts create mode 100644 agent-openai-agents-sdk-multiagent/e2e-chatbot-app-next/client/src/index.css create mode 100644 agent-openai-agents-sdk-multiagent/e2e-chatbot-app-next/client/src/layouts/ChatLayout.tsx create mode 100644 agent-openai-agents-sdk-multiagent/e2e-chatbot-app-next/client/src/layouts/RootLayout.tsx create mode 100644 agent-openai-agents-sdk-multiagent/e2e-chatbot-app-next/client/src/lib/ChatTransport.ts create mode 100644 agent-openai-agents-sdk-multiagent/e2e-chatbot-app-next/client/src/lib/actions.ts create mode 100644 agent-openai-agents-sdk-multiagent/e2e-chatbot-app-next/client/src/lib/navigation.ts create mode 100644 agent-openai-agents-sdk-multiagent/e2e-chatbot-app-next/client/src/lib/oauth-error-utils.ts create mode 100644 agent-openai-agents-sdk-multiagent/e2e-chatbot-app-next/client/src/lib/utils.ts create mode 100644 agent-openai-agents-sdk-multiagent/e2e-chatbot-app-next/client/src/main.tsx create mode 100644 agent-openai-agents-sdk-multiagent/e2e-chatbot-app-next/client/src/pages/ChatPage.tsx create mode 100644 agent-openai-agents-sdk-multiagent/e2e-chatbot-app-next/client/src/pages/NewChatPage.tsx create mode 100644 agent-openai-agents-sdk-multiagent/e2e-chatbot-app-next/client/tsconfig.json create mode 100644 agent-openai-agents-sdk-multiagent/e2e-chatbot-app-next/client/tsconfig.node.json create mode 100644 agent-openai-agents-sdk-multiagent/e2e-chatbot-app-next/client/vite.config.ts create mode 100644 agent-openai-agents-sdk-multiagent/e2e-chatbot-app-next/databricks.yml create mode 100644 agent-openai-agents-sdk-multiagent/e2e-chatbot-app-next/drizzle.config.ts create mode 100644 agent-openai-agents-sdk-multiagent/e2e-chatbot-app-next/knip.json create mode 100644 agent-openai-agents-sdk-multiagent/e2e-chatbot-app-next/package-lock.json create mode 100644 agent-openai-agents-sdk-multiagent/e2e-chatbot-app-next/package.json create mode 100644 agent-openai-agents-sdk-multiagent/e2e-chatbot-app-next/packages/ai-sdk-providers/package.json create mode 100644 agent-openai-agents-sdk-multiagent/e2e-chatbot-app-next/packages/ai-sdk-providers/src/index.ts create mode 100644 agent-openai-agents-sdk-multiagent/e2e-chatbot-app-next/packages/ai-sdk-providers/src/providers-server.ts create mode 100644 agent-openai-agents-sdk-multiagent/e2e-chatbot-app-next/packages/ai-sdk-providers/src/request-context.ts create mode 100644 agent-openai-agents-sdk-multiagent/e2e-chatbot-app-next/packages/ai-sdk-providers/tsconfig.json create mode 100644 agent-openai-agents-sdk-multiagent/e2e-chatbot-app-next/packages/ai-sdk-providers/tsdown.config.ts create mode 100644 agent-openai-agents-sdk-multiagent/e2e-chatbot-app-next/packages/auth/package.json create mode 100644 agent-openai-agents-sdk-multiagent/e2e-chatbot-app-next/packages/auth/src/databricks-auth.ts create mode 100644 agent-openai-agents-sdk-multiagent/e2e-chatbot-app-next/packages/auth/src/index.ts create mode 100644 agent-openai-agents-sdk-multiagent/e2e-chatbot-app-next/packages/auth/tsconfig.json create mode 100644 agent-openai-agents-sdk-multiagent/e2e-chatbot-app-next/packages/auth/tsdown.config.ts create mode 100644 agent-openai-agents-sdk-multiagent/e2e-chatbot-app-next/packages/core/package.json create mode 100644 agent-openai-agents-sdk-multiagent/e2e-chatbot-app-next/packages/core/src/ai/providers.ts create mode 100644 agent-openai-agents-sdk-multiagent/e2e-chatbot-app-next/packages/core/src/chat-acl.ts create mode 100644 agent-openai-agents-sdk-multiagent/e2e-chatbot-app-next/packages/core/src/errors.ts create mode 100644 agent-openai-agents-sdk-multiagent/e2e-chatbot-app-next/packages/core/src/index.ts create mode 100644 agent-openai-agents-sdk-multiagent/e2e-chatbot-app-next/packages/core/src/schemas/chat.ts create mode 100644 agent-openai-agents-sdk-multiagent/e2e-chatbot-app-next/packages/core/src/stream-cache.ts create mode 100644 agent-openai-agents-sdk-multiagent/e2e-chatbot-app-next/packages/core/src/types.ts create mode 100644 agent-openai-agents-sdk-multiagent/e2e-chatbot-app-next/packages/core/src/utils.ts create mode 100644 agent-openai-agents-sdk-multiagent/e2e-chatbot-app-next/packages/core/tsconfig.json create mode 100644 agent-openai-agents-sdk-multiagent/e2e-chatbot-app-next/packages/core/tsdown.config.ts create mode 100644 agent-openai-agents-sdk-multiagent/e2e-chatbot-app-next/packages/db/migrations/0000_robust_madelyne_pryor.sql create mode 100644 agent-openai-agents-sdk-multiagent/e2e-chatbot-app-next/packages/db/migrations/0001_add_trace_id.sql create mode 100644 agent-openai-agents-sdk-multiagent/e2e-chatbot-app-next/packages/db/migrations/0002_polite_red_hulk.sql create mode 100644 agent-openai-agents-sdk-multiagent/e2e-chatbot-app-next/packages/db/migrations/meta/0000_snapshot.json create mode 100644 agent-openai-agents-sdk-multiagent/e2e-chatbot-app-next/packages/db/migrations/meta/0001_snapshot.json create mode 100644 agent-openai-agents-sdk-multiagent/e2e-chatbot-app-next/packages/db/migrations/meta/0002_snapshot.json create mode 100644 agent-openai-agents-sdk-multiagent/e2e-chatbot-app-next/packages/db/migrations/meta/_journal.json create mode 100644 agent-openai-agents-sdk-multiagent/e2e-chatbot-app-next/packages/db/package.json create mode 100644 agent-openai-agents-sdk-multiagent/e2e-chatbot-app-next/packages/db/src/connection-core.ts create mode 100644 agent-openai-agents-sdk-multiagent/e2e-chatbot-app-next/packages/db/src/connection-pool.ts create mode 100644 agent-openai-agents-sdk-multiagent/e2e-chatbot-app-next/packages/db/src/connection.ts create mode 100644 agent-openai-agents-sdk-multiagent/e2e-chatbot-app-next/packages/db/src/index.ts create mode 100644 agent-openai-agents-sdk-multiagent/e2e-chatbot-app-next/packages/db/src/queries.ts create mode 100644 agent-openai-agents-sdk-multiagent/e2e-chatbot-app-next/packages/db/src/schema.ts create mode 100644 agent-openai-agents-sdk-multiagent/e2e-chatbot-app-next/packages/db/tsconfig.json create mode 100644 agent-openai-agents-sdk-multiagent/e2e-chatbot-app-next/packages/db/tsdown.config.ts create mode 100644 agent-openai-agents-sdk-multiagent/e2e-chatbot-app-next/packages/utils/package.json create mode 100644 agent-openai-agents-sdk-multiagent/e2e-chatbot-app-next/packages/utils/src/databricks-host-utils.ts create mode 100644 agent-openai-agents-sdk-multiagent/e2e-chatbot-app-next/packages/utils/src/index.ts create mode 100644 agent-openai-agents-sdk-multiagent/e2e-chatbot-app-next/packages/utils/src/subprocess.ts create mode 100644 agent-openai-agents-sdk-multiagent/e2e-chatbot-app-next/packages/utils/src/types.ts create mode 100644 agent-openai-agents-sdk-multiagent/e2e-chatbot-app-next/packages/utils/tsconfig.json create mode 100644 agent-openai-agents-sdk-multiagent/e2e-chatbot-app-next/packages/utils/tsdown.config.ts create mode 100644 agent-openai-agents-sdk-multiagent/e2e-chatbot-app-next/playwright.config.ts create mode 100644 agent-openai-agents-sdk-multiagent/e2e-chatbot-app-next/scripts/cleanup-database.sh create mode 100644 agent-openai-agents-sdk-multiagent/e2e-chatbot-app-next/scripts/deploy.sh create mode 100644 agent-openai-agents-sdk-multiagent/e2e-chatbot-app-next/scripts/get-experiment-id.ts create mode 100644 agent-openai-agents-sdk-multiagent/e2e-chatbot-app-next/scripts/get-pghost.sh create mode 100644 agent-openai-agents-sdk-multiagent/e2e-chatbot-app-next/scripts/migrate.ts create mode 100644 agent-openai-agents-sdk-multiagent/e2e-chatbot-app-next/scripts/quickstart.sh create mode 100644 agent-openai-agents-sdk-multiagent/e2e-chatbot-app-next/scripts/reset-database.ts create mode 100644 agent-openai-agents-sdk-multiagent/e2e-chatbot-app-next/scripts/start-app.sh create mode 100644 agent-openai-agents-sdk-multiagent/e2e-chatbot-app-next/server/package.json create mode 100644 agent-openai-agents-sdk-multiagent/e2e-chatbot-app-next/server/src/env.ts create mode 100644 agent-openai-agents-sdk-multiagent/e2e-chatbot-app-next/server/src/index.ts create mode 100644 agent-openai-agents-sdk-multiagent/e2e-chatbot-app-next/server/src/lib/message-meta-store.ts create mode 100644 agent-openai-agents-sdk-multiagent/e2e-chatbot-app-next/server/src/lib/stream-fallback.ts create mode 100644 agent-openai-agents-sdk-multiagent/e2e-chatbot-app-next/server/src/middleware/auth.ts create mode 100644 agent-openai-agents-sdk-multiagent/e2e-chatbot-app-next/server/src/routes/chat.ts create mode 100644 agent-openai-agents-sdk-multiagent/e2e-chatbot-app-next/server/src/routes/config.ts create mode 100644 agent-openai-agents-sdk-multiagent/e2e-chatbot-app-next/server/src/routes/feedback.ts create mode 100644 agent-openai-agents-sdk-multiagent/e2e-chatbot-app-next/server/src/routes/history.ts create mode 100644 agent-openai-agents-sdk-multiagent/e2e-chatbot-app-next/server/src/routes/messages.ts create mode 100644 agent-openai-agents-sdk-multiagent/e2e-chatbot-app-next/server/src/routes/session.ts create mode 100644 agent-openai-agents-sdk-multiagent/e2e-chatbot-app-next/server/tsconfig.json create mode 100644 agent-openai-agents-sdk-multiagent/e2e-chatbot-app-next/server/tsdown.config.ts create mode 100644 agent-openai-agents-sdk-multiagent/e2e-chatbot-app-next/tests/ai-sdk-provider/request-context.test.ts create mode 100644 agent-openai-agents-sdk-multiagent/e2e-chatbot-app-next/tests/api-mocking/api-mock-handlers.ts create mode 100644 agent-openai-agents-sdk-multiagent/e2e-chatbot-app-next/tests/api-mocking/api-mock-server.ts create mode 100644 agent-openai-agents-sdk-multiagent/e2e-chatbot-app-next/tests/deployed/feedback.test.ts create mode 100644 agent-openai-agents-sdk-multiagent/e2e-chatbot-app-next/tests/e2e/chat-navigation.test.ts create mode 100644 agent-openai-agents-sdk-multiagent/e2e-chatbot-app-next/tests/e2e/chat-resume.test.ts create mode 100644 agent-openai-agents-sdk-multiagent/e2e-chatbot-app-next/tests/e2e/chat.test.ts create mode 100644 agent-openai-agents-sdk-multiagent/e2e-chatbot-app-next/tests/e2e/ephemeral-mode.test.ts create mode 100644 agent-openai-agents-sdk-multiagent/e2e-chatbot-app-next/tests/e2e/mcp-approval.test.ts create mode 100644 agent-openai-agents-sdk-multiagent/e2e-chatbot-app-next/tests/e2e/oauth-error.test.ts create mode 100644 agent-openai-agents-sdk-multiagent/e2e-chatbot-app-next/tests/e2e/obo-banner.test.ts create mode 100644 agent-openai-agents-sdk-multiagent/e2e-chatbot-app-next/tests/e2e/session.test.ts create mode 100644 agent-openai-agents-sdk-multiagent/e2e-chatbot-app-next/tests/fixtures.ts create mode 100644 agent-openai-agents-sdk-multiagent/e2e-chatbot-app-next/tests/helpers.ts create mode 100644 agent-openai-agents-sdk-multiagent/e2e-chatbot-app-next/tests/oauth/oauth-error-utils.test.ts create mode 100644 agent-openai-agents-sdk-multiagent/e2e-chatbot-app-next/tests/pages/chat.ts create mode 100644 agent-openai-agents-sdk-multiagent/e2e-chatbot-app-next/tests/prompts/basic.ts create mode 100644 agent-openai-agents-sdk-multiagent/e2e-chatbot-app-next/tests/prompts/llm-output-fixtures.ts create mode 100644 agent-openai-agents-sdk-multiagent/e2e-chatbot-app-next/tests/prompts/oauth-fixtures.ts create mode 100644 agent-openai-agents-sdk-multiagent/e2e-chatbot-app-next/tests/prompts/routes.ts create mode 100644 agent-openai-agents-sdk-multiagent/e2e-chatbot-app-next/tests/prompts/utils.ts create mode 100644 agent-openai-agents-sdk-multiagent/e2e-chatbot-app-next/tests/routes/chat.test.ts create mode 100644 agent-openai-agents-sdk-multiagent/e2e-chatbot-app-next/tests/routes/config.test.ts create mode 100644 agent-openai-agents-sdk-multiagent/e2e-chatbot-app-next/tests/routes/context-injection.test.ts create mode 100644 agent-openai-agents-sdk-multiagent/e2e-chatbot-app-next/tests/routes/feedback.test.ts create mode 100644 agent-openai-agents-sdk-multiagent/e2e-chatbot-app-next/tests/routes/history.test.ts create mode 100644 agent-openai-agents-sdk-multiagent/e2e-chatbot-app-next/tests/routes/token-forwarding.test.ts create mode 100644 agent-openai-agents-sdk-multiagent/e2e-chatbot-app-next/tests/routes/trace-id-capture.api-proxy.test.ts create mode 100644 agent-openai-agents-sdk-multiagent/e2e-chatbot-app-next/tests/routes/trace-id-capture.test.ts create mode 100644 agent-openai-agents-sdk-multiagent/e2e-chatbot-app-next/tsconfig.json diff --git a/agent-openai-agents-sdk-multiagent/e2e-chatbot-app-next/.claude/skills/quickstart/SKILL.md b/agent-openai-agents-sdk-multiagent/e2e-chatbot-app-next/.claude/skills/quickstart/SKILL.md new file mode 100644 index 00000000..483d5869 --- /dev/null +++ b/agent-openai-agents-sdk-multiagent/e2e-chatbot-app-next/.claude/skills/quickstart/SKILL.md @@ -0,0 +1,77 @@ +--- +name: quickstart +description: "Set up the Databricks chatbot app for local development and deployment. Use when: (1) First time setup, (2) User says 'quickstart', 'set up', 'authenticate', or 'configure databricks', (3) No .env file exists, (4) User says 'enable feedback', 'feedback widget', or 'MLFLOW_EXPERIMENT_ID'." +--- + +# Quickstart: Databricks Chatbot App + +Run the interactive setup script from the `e2e-chatbot-app-next/` directory: + +```bash +./scripts/quickstart.sh +``` + +## What It Configures + +The script walks through the following steps interactively: + +1. **Prerequisites** — installs jq, nvm, Node 20, and the Databricks CLI if missing +2. **Databricks auth** — lets you select an existing profile or create a new one via `databricks auth login` +3. **Serving endpoint** — prompts for your agent endpoint name (Agent Bricks or custom agent); validates it exists +4. **Feedback widget** — if the endpoint has a linked MLflow experiment, offers to enable thumbs up/down feedback (default: yes). This automatically: + - Sets `MLFLOW_EXPERIMENT_ID` in `.env` + - Uncomments the experiment resource in `databricks.yml` + - Uncomments `MLFLOW_EXPERIMENT_ID` in `app.yaml` +5. **App/bundle name** — optionally customize the app name (default: `db-chatbot-dev-`, max 30 chars) +6. **Database** — optionally enable persistent chat history via a Lakebase instance (~5-10 min, costs apply) +7. **Deploy** — runs `databricks bundle deploy` and starts the app + +## Enabling Feedback After Initial Setup + +If you skipped feedback during quickstart, or ran the script before feedback support was added, enable it manually: + +**1. Find the experiment name for your endpoint:** +```bash +npx tsx scripts/get-experiment-id.ts --endpoint +``` + +For Agent Bricks (Knowledge Assistant / Multi-Agent Supervisor): +```bash +npx tsx scripts/get-experiment-id.ts --agent-brick +``` + +**2. Set `MLFLOW_EXPERIMENT_ID` in `.env`** (for local dev): +``` +MLFLOW_EXPERIMENT_ID= +``` + +**3. Configure `databricks.yml`** — uncomment and fill in the experiment resource: +```yaml + - name: experiment + description: "MLflow experiment for collecting user feedback" + experiment: + name: "" + permission: CAN_EDIT +``` + +**4. Configure `app.yaml`** — uncomment the env var: +```yaml + - name: MLFLOW_EXPERIMENT_ID + valueFrom: experiment +``` + +**5. Redeploy:** +```bash +databricks bundle deploy +databricks bundle run databricks_chatbot +``` + +## Troubleshooting + +| Issue | Solution | +|-------|----------| +| Feedback widget not showing after setup | Restart dev server; env vars are read at startup | +| `get-experiment-id.ts` fails with auth error | Run `databricks auth login` first | +| No experiment found for endpoint | Only custom agents and Agent Bricks endpoints have linked experiments; Foundation Model endpoints do not support feedback | +| Feedback submission returns 403 | App service principal is missing `CAN_EDIT` on the experiment — check `permission: CAN_EDIT` in `databricks.yml` | +| "Instance name is not unique" on deploy | Run `./scripts/cleanup-database.sh` to remove the old database instance | diff --git a/agent-openai-agents-sdk-multiagent/e2e-chatbot-app-next/.env.example b/agent-openai-agents-sdk-multiagent/e2e-chatbot-app-next/.env.example new file mode 100644 index 00000000..7dad22c9 --- /dev/null +++ b/agent-openai-agents-sdk-multiagent/e2e-chatbot-app-next/.env.example @@ -0,0 +1,62 @@ +# ============================================ +# Databricks Auth Configuration +# ============================================ +# TODO: set DATABRICKS_CONFIG_PROFILE +# First, run databricks auth login [--profile name] +# Then, set the profile name here (if you didn't specify a profile name, use "DEFAULT" as the profile name) +DATABRICKS_CONFIG_PROFILE=your-profile-name + +# ============================================ +# Agent Serving Configuration +# ============================================ +# TODO: set DATABRICKS_SERVING_ENDPOINT to your custom agent or Agent Bricks endpoint name +DATABRICKS_SERVING_ENDPOINT=your-serving-endpoint + +# ============================================ +# Database Configuration +# ============================================ +# Option 1: Use individual PG* variables (recommended) +# +# This setup mimics the final setup when you deploy to Databricks apps, +# resulting in a higher-fidelity local dev environment +# +# Given the name of a lakebase instance, you can find the values below via the Databricks CLI, e.g +# by running `databricks database get-database-instance "chatbot-lakebase-dev" | jq .read_write_dns`. +# +# TODO: set PGUSER +# This should be your Databricks username, i.e. the output of +# running `databricks auth describe --output json | jq .username` +# +# PGUSER=your-databricks-username +# +# +# TODO: set PGHOST. +# Given your database instance name, you can get the value of PGHOST from the read_write_dns +# metadata field on your instance. +# We provide a convenience script for this; you can run `./scripts/get-pghost.sh` to get the value +# of PGHOST in development if using this template with the default configuration. If you customized +# the database instance name when setting up this template, you can also run +# `./scripts/get-pghost.sh your-lakebase-instance-name` to get the value +# of PGHOST for a particular custom instance name +# +# PGHOST=your-postgres-host +# +# +# The following environment variables have the following default values on Databricks, +# so you probably don't need to change them: +# +# PGDATABASE=databricks_postgres +# PGPORT=5432 + +# Option 2: Use full connection string (alternative to PG* variables) +# POSTGRES_URL=postgresql://username:password@host:port/database + +# ============================================ +# Feedback Configuration (Optional) +# ============================================ +# MLflow experiment name for collecting user feedback (thumbs up/down). +# This should match the destination experiment for traces from your agent, to enable users to submit feedback on those traces +# When set, the feedback widget is enabled in the chat UI. +# In production (Databricks Apps), this is injected automatically when the +# experiment resource is configured in databricks.yml + app.yaml. +# MLFLOW_EXPERIMENT_ID=/Users/your-email@company.com/chatbot-feedback-dev diff --git a/agent-openai-agents-sdk-multiagent/e2e-chatbot-app-next/.gitignore b/agent-openai-agents-sdk-multiagent/e2e-chatbot-app-next/.gitignore new file mode 100644 index 00000000..0710fb3f --- /dev/null +++ b/agent-openai-agents-sdk-multiagent/e2e-chatbot-app-next/.gitignore @@ -0,0 +1,52 @@ +# See https://help.github.com/articles/ignoring-files/ for more about ignoring files. + +cookies.txt +# dependencies +node_modules +.pnp +.pnp.js +.claude/* +!.claude/skills/ + +# testing +coverage + +# next.js +.next/ +out/ +build + +# misc +.DS_Store +*.pem + +# debug +npm-debug.log* +yarn-debug.log* +yarn-error.log* +.pnpm-debug.log* + +# local env files +.env +.env.local +.env.development.local +.env.test.local +.env.production.local + +# turbo +.turbo + +.env +.vercel +.env*.local + +# Playwright +/test-results/ +/playwright-report/ +/blob-report/ +/playwright/* +tsconfig.tsbuildinfo +.idea + +# Databricks bundle state - exclude to prevent stale state when copying the project +.databricks diff --git a/agent-openai-agents-sdk-multiagent/e2e-chatbot-app-next/CLAUDE.md b/agent-openai-agents-sdk-multiagent/e2e-chatbot-app-next/CLAUDE.md new file mode 100644 index 00000000..bbcdc8a1 --- /dev/null +++ b/agent-openai-agents-sdk-multiagent/e2e-chatbot-app-next/CLAUDE.md @@ -0,0 +1,624 @@ +# Databricks E2E Chatbot Application - Context for Claude + +## Project Overview + +This is a production-ready, full-stack chatbot application built specifically for **Databricks environments**. It provides a web-based chat interface for interacting with Databricks Agent Serving endpoints, Agent Bricks, and Foundation Model endpoints. + +**Key characteristics:** + +- Monorepo architecture with npm workspaces +- Express.js backend + React frontend (Vite) +- PostgreSQL database with Drizzle ORM +- Vercel AI SDK for streaming responses +- Databricks-native authentication and deployment + +## Architecture + +### Monorepo Structure + +``` +e2e-chatbot-app-next/ +├── client/ # React + Vite frontend (@databricks/chatbot-client) +├── server/ # Express backend (@databricks/chatbot-server) +└── packages/ # Shared libraries + ├── core/ # Domain types, errors, schemas + ├── auth/ # Authentication utilities + ├── ai-sdk-providers/ # Databricks AI SDK integration + ├── db/ # Database layer (Drizzle ORM) + └── utils/ # Shared utilities +``` + +**IMPORTANT**: This is an npm workspaces monorepo. When adding dependencies: + +- Root dependencies: For build tools, linting, testing +- Workspace dependencies: Add to the specific package (client, server, or packages/\*) +- Use `npm install --workspace=` for workspace-specific deps + +### Key Technologies + +**Frontend:** + +- React 18.2 with TypeScript 5.6 +- Vite 5.1 (build tool and dev server) +- Tailwind CSS 4.1 + Radix UI components +- React Router v6 +- Vercel AI SDK (`@ai-sdk/react`) for streaming +- SWR for data fetching + +**Backend:** + +- Express 5.1 with TypeScript +- Vercel AI SDK (`ai` package) for streaming responses +- Zod for schema validation +- Header-based authentication (expects reverse proxy) + +**Database:** + +- PostgreSQL 16 (Databricks Lakebase) +- Drizzle ORM 0.44 with migrations +- Custom schema: `ai_chatbot` +- Tables: User, Chat, Message_v2 (Message is deprecated) + +**Testing:** + +- Playwright 1.50 for E2E tests +- MSW (Mock Service Worker) 2.11 for API mocking +- Test environment auto-detected via `PLAYWRIGHT=True` + +**Code Quality:** + +- Biome 1.9.4 for linting and formatting (NOT ESLint/Prettier) + +## Essential Commands + +### Quick Start Scripts + +**For first-time setup and deployment:** + +```bash +./scripts/quickstart.sh # Interactive setup wizard (recommended for new users) +``` + +This automated script handles: +- Prerequisites installation (jq, nvm, Node 20, Databricks CLI) +- Configuration file setup (.env) +- Databricks authentication +- Serving endpoint configuration +- App/bundle name customization +- Database setup (optional, with conflict detection) +- Dependency installation +- Bundle deployment to Databricks +- Database migration (if enabled) +- Application startup + +**IMPORTANT - For Claude Code**: Before running `quickstart.sh`, ask the user: + +1. **"Do you have a Databricks serving endpoint ready?"** + - If yes: Get the endpoint name + - If no: Suggest they create one first or use the default `databricks-claude-sonnet-4` + +2. **"Do you want to customize the app/bundle name?"** + - Default will be `db-chatbot-dev-` + - Custom names must be ≤30 characters + - Explain that names cannot be changed after first deployment + +3. **"Do you want persistent chat history (requires a Lakebase database)?"** + - Explain this takes 5-10 minutes on first deployment + - Database costs apply (~$0.70/hour for CU_1) + - Without database, chats are stored in memory (ephemeral mode) + +4. **"Do you want to deploy to Databricks now, or just configure locally?"** + - Deploy now: Script will run `databricks bundle deploy` + - Configure only: User can deploy manually later + +These questions help ensure the script runs smoothly and matches user expectations. + +**For starting the local development server:** + +```bash +./scripts/start-app.sh # Simple script to install deps and run npm run dev +``` + +**For cleaning up database instances:** + +```bash +./scripts/cleanup-database.sh # Interactive database instance deletion +``` + +Use this when: +- You encounter "Instance name is not unique" deployment errors +- You want to start fresh with a new database +- You need to remove orphaned database instances + +**Warning**: Database deletion is permanent and cannot be undone! + +### Development + +```bash +npm run dev # Start both client (3000) and server (3001) +npm run dev:server # Server only +npm run dev:client # Client only +``` + +### Building + +```bash +npm run build # Full build: DB migrate → client → server +npm run build:client # Build client only (outputs to client/dist/) +npm run build:server # Build server only (outputs to server/dist/) +``` + +### Database Operations + +```bash +npm run db:generate # Generate SQL migration files from schema changes +npm run db:migrate # Run pending SQL migrations (PRODUCTION-SAFE) +npm run db:reset # Reset database (DESTRUCTIVE - deletes all data) +npm run db:studio # Open Drizzle Studio (visual DB editor) +npm run db:push # Push schema directly (DEVELOPMENT ONLY - can be destructive) +npm run db:pull # Pull schema from database +npm run db:check # Check migration consistency +``` + +**IMPORTANT Migration Workflow:** +1. Modify `packages/db/src/schema.ts` +2. Run `npm run db:generate` to create SQL migration file +3. Review the generated SQL in `packages/db/migrations/` +4. Run `npm run db:migrate` to apply migrations +5. Commit both `schema.ts` and migration files + +**⚠️ DO NOT use `db:push` in production** - it bypasses migrations and can drop data! + +### Code Quality + +```bash +npm run lint # Lint with Biome (auto-fix enabled) +npm run lint:fix # Lint + format +npm run format # Format only +``` + +### Testing + +```bash +npm test # Run all Playwright tests (sets PLAYWRIGHT=True) + # Note: It can be helpful to start "export PLAYWRIGHT=True npm run dev" in parallel + # for shorter test loops if iterating over tests multiple times +npx playwright test --ui # Run tests in UI mode +npx playwright test --headed --project=e2e # Run E2E tests with browser visible +``` + +**Test projects:** `unit`, `e2e`, `routes` +**Test timeout:** 240 seconds (very generous for AI operations) + +### Deployment (Databricks Asset Bundle) + +**IMPORTANT Pre-Deployment Checklist:** +1. **Check `databricks.yml` for TODOs** - When a user requests deployment, ALWAYS check `databricks.yml` for any TODO comments +2. **Prompt for missing values** - If TODOs exist (especially for `serving_endpoint_name`), ask the user to provide the required values before proceeding +3. **Update the file** - Set the default value in `databricks.yml` and remove the TODO comment + +#### Enabling Database Integration (Optional) + +By default, the app deploys in **ephemeral mode** - chats are stored in memory and lost on restart. + +To enable **persistent chat history** with a managed Lakebase database, you need to uncomment **TWO sections** in `databricks.yml`: + +1. Open `databricks.yml` + +2. **Uncomment the first database TODO** - Find the database instance definition (around lines 17-21): + ```yaml + resources: + database_instances: + # TODO (optional): Uncomment the database resource below to enable persistent chat history + # chatbot_lakebase: + # name: ${var.database_instance_name}-${var.resource_name_suffix} + # capacity: CU_1 + ``` + Remove the `#` symbols to uncomment it. + +3. **Uncomment the second database TODO** - Find the database resource binding (around lines 39-44): + ```yaml + # TODO (optional): Uncomment the database resource below to bind the app to the database instance above + # - name: database + # description: "Lakebase database instance for the chat app" + # database: + # database_name: databricks_postgres + # instance_name: ${resources.database_instances.chatbot_lakebase.name} + # permission: CAN_CONNECT_AND_CREATE + ``` + Remove the `#` symbols to uncomment it. + +4. Deploy as normal + +**Important:** Both sections must be uncommented for database integration to work. The first creates the database instance, the second connects it to your app. + +```bash +databricks bundle validate # Validate bundle config +databricks bundle deploy # Deploy to dev (default) +databricks bundle deploy -t staging # Deploy to staging +databricks bundle run databricks_chatbot # Start the app +databricks bundle summary # View deployment status +``` + +## Code Style Guidelines + +### Formatting Rules (Biome) + +**IMPORTANT**: This project uses Biome, NOT ESLint or Prettier. All formatting is handled by Biome. + +- **Indentation**: 2 spaces +- **Line width**: 80 characters +- **Quotes**: Single quotes for strings, double quotes for JSX attributes +- **Semicolons**: Always required +- **Trailing commas**: Always (all contexts) +- **Arrow parentheses**: Always include +- **Line endings**: LF (Unix) + +### TypeScript Conventions + +- **Strict mode**: Enabled +- **Target**: ES2022 +- **Module**: ESNext with bundler resolution +- **Imports**: Use TypeScript path aliases for workspace packages: + ```typescript + import { something } from "@chat-template/core"; + import { auth } from "@chat-template/auth"; + import { db } from "@chat-template/db"; + ``` + +### Component Organization (React) + +- Use functional components with hooks +- Organize components by feature/domain +- Place shared UI components in `client/src/components/ui/` +- Place app-specific components in `client/src/components/elements/` + +### API Route Patterns (Express) + +- All routes use Express Router +- Authentication middleware applied globally or per-route +- Error handling with `ChatSDKError` class +- Schema validation with Zod schemas +- Streaming responses use Vercel AI SDK utilities + +Example route structure: + +```typescript +export const myRouter: RouterType = Router(); +myRouter.use(authMiddleware); +myRouter.post("/endpoint", requireAuth, async (req, res) => { + // Implementation +}); +``` + +## Database Patterns + +### Schema Modifications + +**ALWAYS follow this workflow when changing the database schema:** + +1. Modify `packages/db/src/schema.ts` +2. Run `npm run db:generate` to create migration file +3. Review the generated SQL in `packages/db/migrations/` +4. Run `npm run db:migrate` to apply migrations +5. Commit both schema.ts and migration files + +**Understanding Migration Commands:** + +- **`npm run db:migrate`** - Runs SQL migration files from `packages/db/migrations/` + - ✅ Safe for production + - ✅ Maintains migration history + - ✅ Idempotent (safe to run multiple times) + - ✅ Will NOT drop unexpected schemas or tables + +- **`npm run db:push`** - Syncs schema directly to database + - ⚠️ Development/prototyping ONLY + - ⚠️ Can drop tables/columns not in your schema + - ⚠️ No migration history + - ⚠️ NOT safe for production + +**DO NOT** use `db:push` in production or with existing data! + +### Querying Patterns + +Use helper functions from `@chat-template/db`: + +```typescript +import { getChatById, saveMessages, deleteChatById } from "@chat-template/db"; +``` + +For custom queries, use Drizzle syntax with the exported schema: + +```typescript +import { db, chat, message } from "@chat-template/db"; +import { eq, and, desc } from "drizzle-orm"; + +const results = await db + .select() + .from(chat) + .where(and(eq(chat.userId, userId), eq(chat.visibility, "private"))) + .orderBy(desc(chat.createdAt)); +``` + +### Schema Location + +**CRITICAL**: All tables are in the `ai_chatbot` schema, NOT the public schema. +This is configured in `packages/db/src/schema.ts:14` with `pgSchema('ai_chatbot')`. + +### Drizzle Configuration + +The Drizzle configuration is located at the **project root** in `drizzle.config.ts`. This configuration is automatically detected by all `drizzle-kit` commands (no need to specify `--config` flag). + +## Authentication + +### How It Works + +This application uses **header-based authentication** (NOT cookies or tokens). It expects a reverse proxy or load balancer to inject user headers: + +- `X-Forwarded-User` - User ID (required) +- `X-Forwarded-Email` - User email (optional) +- `X-Forwarded-Preferred-Username` - Display name (optional) + +The auth middleware in `server/src/middleware/auth.ts` reads these headers and creates a session object: + +```typescript +req.session = { + user: { id, email, name }, +}; +``` + +### Auth Middleware Usage + +- `authMiddleware` - Extracts session (doesn't reject) +- `requireAuth` - Returns 401 if no session +- `requireChatAccess` - Validates user owns the chat + +### Local Development + +When running locally (`npm run dev`), the application uses **Databricks CLI authentication**: + +- Set `DATABRICKS_CONFIG_PROFILE` in `.env` +- Run `databricks auth login --profile ` first + +## Environment Variables + +### Required for Local Development + +```bash +# Authentication +DATABRICKS_CONFIG_PROFILE=your-profile-name + +# AI Model +DATABRICKS_SERVING_ENDPOINT=your-serving-endpoint + +# Database (Individual variables preferred) +PGUSER=your-databricks-username +PGHOST=your-lakebase-host # Use ./scripts/get-pghost.sh +PGDATABASE=databricks_postgres # Default, usually don't change +PGPORT=5432 # Default +``` + +### Required for Production (Databricks Apps) + +Automatically provided by the platform: + +- `DATABRICKS_CLIENT_ID` - Service principal +- `DATABRICKS_CLIENT_SECRET` - Service principal secret +- `DATABRICKS_HOST` - Workspace URL +- `PGHOST`, `PGUSER`, etc. - From database resource binding + +## Testing Practices + +### Test Structure + +``` +tests/ +├── e2e/ # Browser automation tests (Playwright) +├── routes/ # API endpoint tests +├── ai-sdk-provider/ # Unit tests for AI provider logic +├── api-mocking/ # MSW mock server setup +├── pages/ # Page object models +└── fixtures.ts # Test fixtures (multi-user scenarios) +``` + +### Writing E2E Tests + +Use page object pattern from `tests/pages/ChatPage.ts`: + +```typescript +import { test } from "./fixtures"; +import { ChatPage } from "./pages/ChatPage"; + +test("should send a message", async ({ page, adaContext }) => { + const chatPage = new ChatPage(page); + await chatPage.createNewChat(); + await chatPage.sendUserMessage("Hello"); + const response = await chatPage.getRecentAssistantMessage(); + expect(response).toBeTruthy(); +}); +``` + +### API Mocking + +MSW automatically mocks Databricks API calls when `PLAYWRIGHT=True`: + +- Mocks are defined in `tests/api-mocking/api-mock-server.ts` +- Server starts automatically in test environment +- Prevents external API calls during tests + +### Multi-User Testing + +Use fixtures for testing user isolation: + +```typescript +test("users should see their own chats", async ({ + adaContext, + babbageContext, +}) => { + // adaContext and babbageContext are separate authenticated sessions +}); +``` + +## Known Limitations & Quirks + +### Database Schema Quirk + +One database per app because code targets fixed `ai_chatbot` schema. To share a database instance: + +1. Update `ai_chatbot` references in `packages/db/src/schema.ts` +2. Run `npm run db:generate` +3. Deploy with updated bundle + +### Authentication Methods + +Only Databricks CLI auth (local) and service principal auth (production) are supported. PAT, Azure MSI, etc. are NOT supported. + +### Multi-Modal Inputs + +No support for image or other multi-modal inputs currently. + +## Common Tasks + +### Adding a New API Endpoint + +1. Create route file in `server/src/routes/my-route.ts` +2. Define router with auth middleware +3. Add schema validation with Zod +4. Export router and register in `server/src/index.ts` + +### Adding a New Database Table + +1. Add table definition to `packages/db/src/schema.ts` +2. Add TypeScript type: `export type MyTable = InferSelectModel;` +3. Run `npm run db:generate` +4. Review generated migration +5. Run `npm run db:migrate` +6. Add query helpers to `packages/db/src/queries.ts` if needed + +### Adding a New React Component + +1. Create in `client/src/components/` (ui/ or elements/) +2. Use TypeScript with proper prop types +3. Follow Tailwind CSS conventions +4. Export from component file + +### Debugging AI Stream Responses + +The app uses `StreamCache` (in `@chat-template/core`) to cache streaming responses. This prevents duplicate API calls on reconnection. + +Streaming is handled by Vercel AI SDK: + +```typescript +import { streamText, createUIMessageStream } from "ai"; +``` + +Check `server/src/routes/chat.ts` for streaming implementation. + +### Troubleshooting Bundle Deployment + +**"reference does not exist" errors:** + +- Update Databricks CLI: `brew upgrade databricks` + +**"Resource not found" errors:** + +- Check deployed resources: `databricks bundle summary` +- If resource was manually deleted: `databricks bundle unbind ` +- If resource was manually created: See [DAB docs on binding](https://docs.databricks.com/aws/en/dev-tools/bundles/faqs) + +## Deployment Architecture + +### Databricks Asset Bundle Resources + +The `databricks.yml` file defines: + +1. **Lakebase Database Instance** - Managed PostgreSQL + + - Name: `chatbot-lakebase-{suffix}` + - Capacity: CU_1 (customizable) + +2. **Databricks App** - Hosted application + - Name: `db-chatbot-{suffix}` + - Resources: + - Serving endpoint (with CAN_QUERY permission) + - Database (with CAN_CONNECT_AND_CREATE permission) + +### Deployment Targets + +- **dev**: Default, user-scoped suffix (`dev-{username}`) +- **staging**: Shared staging environment +- **prod**: Production environment + +### Multi-Agent Supervisor Note + +If using Agent Bricks Multi-Agent Supervisor, you MUST grant the app service principal `CAN_QUERY` permission on ALL underlying agents. Add them as additional resources in `databricks.yml`: + +```yaml +resources: + - name: underlying-agent-1 + serving_endpoint: + name: agent-1-endpoint + permission: CAN_QUERY +``` + +## File Locations Reference + +### Configuration Files + +- `databricks.yml` - Databricks Asset Bundle config +- `app.yaml` - Databricks app runtime config (Node.js 20) +- `drizzle.config.ts` - Drizzle ORM and migration configuration +- `biome.jsonc` - Linting and formatting rules +- `playwright.config.ts` - Test configuration +- `tsconfig.json` - Root TypeScript config +- `.env.example` - Environment variable template +- `.env` - Local environment (gitignored) + +### Important Code Paths + +- `server/src/index.ts` - Express server entry point +- `server/src/routes/` - API route definitions +- `client/src/App.tsx` - React root component +- `packages/db/src/schema.ts` - Database schema +- `packages/db/src/queries.ts` - Database query helpers +- `packages/core/src/errors.ts` - Error definitions +- `packages/ai-sdk-providers/` - Databricks AI provider implementations +- `scripts/migrate.ts` - Database migration runner (applies SQL migrations from packages/db/migrations/) + +### Convenience Scripts + +- `scripts/quickstart.sh` - Interactive setup wizard for first-time deployment + - Installs all prerequisites (jq, nvm, Node 20, Databricks CLI) + - Configures authentication and environment variables + - Handles app/bundle name customization with validation + - Manages database setup with conflict detection + - Deploys bundle and runs migrations +- `scripts/start-app.sh` - Simple local development server starter + - Installs dependencies + - Starts both frontend and backend with `npm run dev` +- `scripts/cleanup-database.sh` - Interactive database instance deletion tool + - Lists all database instances in workspace + - Provides safe deletion workflow with confirmations + - Useful for resolving deployment conflicts + +## Additional Resources + +- [Databricks Agent Framework Docs](https://docs.databricks.com/aws/en/generative-ai/agent-framework/chat-app) +- [Databricks Asset Bundles Tutorial](https://docs.databricks.com/aws/en/dev-tools/bundles/apps-tutorial) +- [Vercel AI SDK Docs](https://sdk.vercel.ai/docs) +- [Drizzle ORM Docs](https://orm.drizzle.team/docs/overview) +- [Playwright Docs](https://playwright.dev/) + +## Repository Etiquette + +- **Main branch**: `main` +- **Current branch**: `remove-nextjs` (feature branch) +- **Commit messages**: Concise, imperative mood +- **Testing**: Run `npm test` before committing +- **Linting**: Run `npm run lint` to auto-fix issues +- **Database changes**: Always generate migrations, never use db:push in production +- **Dependencies**: Add to appropriate workspace, not root (except for build tools) + +--- + +**Note for Claude**: This file is automatically loaded as context. When working on this project, refer to these guidelines for commands, patterns, and conventions. Keep this file updated as the project evolves. diff --git a/agent-openai-agents-sdk-multiagent/e2e-chatbot-app-next/README.md b/agent-openai-agents-sdk-multiagent/e2e-chatbot-app-next/README.md new file mode 100644 index 00000000..591d5575 --- /dev/null +++ b/agent-openai-agents-sdk-multiagent/e2e-chatbot-app-next/README.md @@ -0,0 +1,472 @@ + +

Databricks Agent Chat Template

+
+ +

+ A chat application template for interacting with Databricks Agent Serving endpoints, built with ExpressJS, React, Vercel AI SDK, Databricks authentication, and optional Lakebase (database) integration. +

+ +

+ Features · + Running Locally · + Deployment · + Optional Features +

+
+ +This template provides a fully functional chat app for custom code agents and Agent Bricks deployed on Databricks, +but has some [known limitations](#known-limitations) for other use cases. Work is in progress on addressing these limitations. + +## Features + +- **Databricks Agent and Foundation Model Integration**: Direct connection to Databricks Agent serving endpoints and Agent Bricks +- **Databricks Authentication**: Uses Databricks authentication to identify end users of the chat app and securely manage their conversations. +- **Persistent Chat History (Optional)**: Leverages Databricks Lakebase (Postgres) for storing conversations, with governance and tight lakehouse integration. Can also run in ephemeral mode without database. +- **User Feedback Collection (Optional)**: Thumbs up/down feedback on assistant messages, stored as MLflow assessments on the underlying traces. Requires an MLflow experiment resource to be configured. + +## Prerequisites + +1. **Databricks serving endpoint**: you need access to a Databricks workspace containing the Agent Bricks or custom agent serving endpoint to chat with. +2. **Set up Databricks authentication** + - Install the latest version of the [Databricks CLI](https://docs.databricks.com/en/dev-tools/cli/install.html). On macOS, do this via: + ```bash + brew install databricks + brew upgrade databricks && databricks -v + ``` + - Run the following to configure authentication. + In the snippet below, `DATABRICKS_CONFIG_PROFILE` is the name of the Databricks CLI profile under which to configure + authentication. If desired, you can update this to a name of your choice, e.g. `dev_workspace`. + ```bash + export DATABRICKS_CONFIG_PROFILE='chatbot_template' + databricks auth login --profile "$DATABRICKS_CONFIG_PROFILE" + ``` + +## Deployment + +This project includes a [Databricks Asset Bundle (DAB)](https://docs.databricks.com/aws/en/dev-tools/bundles/apps-tutorial) configuration that simplifies deployment by automatically creating and managing all required resources. + +1. **Clone the repo**: + ```bash + git clone https://github.com/databricks/app-templates + cd e2e-chatbot-app-next + ``` +2. **Databricks authentication**: Ensure auth is configured as described in [Prerequisites](#prerequisites). +3. **Specify serving endpoint and address TODOs in databricks.yml**: Address the TODOs in `databricks.yml`, setting the default value of `serving_endpoint_name` to the name of the custom code agent or Agent Bricks endpoint to chat with. The optional commented-out sections allow you to enable: + - **Persistent chat history** — uncomment the two optional `TODO` database blocks to provision and bind a Lakebase database. See [Database Modes](#database-modes) for details. **Tip:** run `./scripts/quickstart.sh` to do this automatically. + - **User feedback collection** — uncomment the optional `TODO` experiment block and set the experiment ID. Also requires a database (both database `TODO` blocks must be uncommented). See [Feedback Collection](#feedback-collection) for details. **Tip:** run `./scripts/quickstart.sh` to configure both database and feedback automatically. + + - NOTE: if using [Agent Bricks Multi-Agent Supervisor](https://docs.databricks.com/aws/en/generative-ai/agent-bricks/multi-agent-supervisor), you need to additionally grant the app service principal the `CAN_QUERY` permission on the underlying agent(s) that the MAS orchestrates. You can do this by adding those + agent serving endpoints as resources in `databricks.yml` (see the NOTE in `databricks.yml` on this) +4. **Validate the bundle configuration**: + + ```bash + databricks bundle validate + ``` + +5. **Deploy the bundle**. The first deployment may take several minutes for provisioning resources (especially if database is enabled), but subsequent deployments are fast: + + ```bash + databricks bundle deploy + ``` + + This creates: + + - **App resource** ready to start + - **Lakebase database instance** (only if database resource is uncommented) + +6. **Start the app**: + + ```bash + databricks bundle run databricks_chatbot + ``` + +7. **View deployment summary** (useful for debugging deployment issues): + ```bash + databricks bundle summary + ``` + +### Deployment Targets + +The bundle supports multiple environments: + +- **dev** (default): Development environment +- **staging**: Staging environment for testing +- **prod**: Production environment + +To deploy to a specific target: + +```bash +databricks bundle deploy -t staging --var serving_endpoint_name="your-endpoint" +``` + +## Running Locally + +### Quick Start (Recommended) + +Use our automated quickstart script for the fastest setup experience: + +1. **Clone the repository**: + + ```bash + git clone https://github.com/databricks/app-templates + cd e2e-chatbot-app-next + ``` + +2. **Run the quickstart script**: + + ```bash + ./scripts/quickstart.sh + ``` + + The quickstart script will: + - **Install prerequisites** - Automatically installs jq, nvm, Node.js 20, and Databricks CLI + - **Configure authentication** - Helps you select or create a Databricks CLI profile + - **Set up serving endpoint** - Prompts for your endpoint name and validates it exists + - **Database setup (optional)** - Choose persistent chat history or ephemeral mode + - **Deploy to Databricks (optional)** - Optionally deploys resources and provisions database + - **Configure local environment** - Automatically creates and populates .env + - **Run migrations** - Sets up database schema if database is enabled + + The script handles the entire setup process automatically, including waiting for database provisioning and configuring connection details. + +3. **Start the application**: + + Use the convenience script: + ```bash + ./scripts/start-app.sh + ``` + + Or manually: + ```bash + npm install # Install/update dependencies + npm run dev # Start development server + ``` + + The app starts on [localhost:3000](http://localhost:3000) (frontend) and [localhost:3001](http://localhost:3001) (backend) + + **Tip:** The `start-app.sh` script is useful for quickly starting the app after initial setup, as it ensures dependencies are up-to-date before starting the dev server. + +### Manual Setup (Alternative) + +If you prefer to configure the environment manually: + +1. **Clone and install**: + + ```bash + git clone https://github.com/databricks/app-templates + cd e2e-chatbot-app-next + npm install + ``` + +2. **Set up environment variables**: + + ```bash + cp .env.example .env + ``` + + Address the TODOs in `.env`, specifying your Databricks CLI profile and database connection details. + +3. **Run the application**: + + ```bash + npm run dev + ``` + + The app starts on [localhost:3000](http://localhost:3000) + +### Optional Chat UI Features + +The chat UI supports two optional features that can be enabled by updating `databricks.yml`: + +### User Feedback + +Users can give thumbs up/down on assistant responses. Feedback is stored as [MLflow assessments](https://docs.databricks.com/aws/en/generative-ai/agent-evaluation/assessments) on the underlying traces, making it easy to review and act on in the MLflow Experiment Tracking UI. + +Feedback is **disabled by default**. See [Feedback Collection](#feedback-collection) for setup instructions. + +> **Note:** If you're using one of the conversational agent templates (e.g. `agent-openai-agents-sdk`, `agent-langgraph`), their `databricks.yml` already creates and binds an MLflow experiment — feedback works automatically after `databricks bundle deploy`, with no extra configuration required. + +### Persistent Chat History + +By default, conversation messages are stored in memory and lost when the server restarts. To persist chat history across sessions, bind a Lakebase database in `databricks.yml`. + +See [Database Modes](#database-modes) for setup instructions. + +--- + +## Database Modes + +The application supports two operating modes: + +#### Persistent Mode (with Database) + +This is the default mode when database environment variables are configured. In this mode: + +- Chat conversations are saved to Postgres/Lakebase +- Users can access their chat history via the sidebar +- Conversations persist across sessions +- A database connection is required (POSTGRES_URL or PGDATABASE env vars) + +#### Ephemeral Mode (without Database) + +The application can also run without a database. In this mode: + +- Chat conversations work normally but are **not saved** +- The sidebar shows "No chat history available" +- A small "Ephemeral" indicator appears in the header +- Users can still have conversations with the AI, but history is lost on page refresh + +#### Selecting a Database Mode + +The application will default to "Ephemeral mode" when no database environment variables are set. +To run in persistent mode, ensure your environment contains the following database variables: + +```bash +# Useful for local development +POSTGRES_URL=... + +# OR + +# Handled for you when using Databricks Apps +PGUSER=... +PGPASSWORD=... +PGDATABASE=... +PGHOST=... +``` + +The app will detect the absence or precense of database configuration and automatically run in the correct mode. + +#### Enabling Database After Installation + +If you initially installed the template without database support (ephemeral mode) and want to add persistent chat history later, you can re-run the quickstart script: + +```bash +./scripts/quickstart.sh +``` + +When prompted about enabling persistent chat history, select "Yes". The script will: +- Uncomment the required database sections in `databricks.yml` +- Optionally deploy the Lakebase database instance +- Configure your `.env` file with database connection details +- Run database migrations if the database is provisioned +- Set up your local environment with the correct database settings + +The script handles all configuration automatically, including: +- Detecting your Databricks workspace and authentication +- Calculating the correct database instance name for your target environment +- Retrieving the database host (PGHOST) after provisioning +- Updating environment variables with the correct values + +**Manual Steps (Alternative):** + +If you prefer to enable the database manually: + +1. **Edit `databricks.yml`** - Uncomment both database sections: + - Database instance resource (`chatbot_lakebase`) around line 18 + - Database resource binding (`- name: database`) around line 41 + +2. **Deploy the database**: + ```bash + databricks bundle deploy + ``` + (First deployment takes several minutes for provisioning) + +3. **Configure `.env`** with database variables: + ```bash + PGUSER=your-databricks-username + PGHOST=your-postgres-host # Get with: ./scripts/get-pghost.sh + PGDATABASE=databricks_postgres + PGPORT=5432 + ``` + +4. **Run database migrations**: + ```bash + npm run db:migrate + ``` + +## Feedback Collection + +The chat app supports optional thumbs up/down feedback on assistant messages. When enabled, feedback is stored as [MLflow assessments](https://docs.databricks.com/aws/en/generative-ai/agent-framework/chat-app) on the traces emitted by your agent endpoint, making it easy to review and act on in the MLflow UI. + +Feedback is **disabled by default**. A "Feedback disabled" badge appears in the header when it is not configured. + +> **Note:** Feedback vote persistence (restoring thumbs up/down state on page reload) requires a database. Both features can be enabled together in one step using the quickstart script. + +### Recommended: use the quickstart script + +The easiest way to enable feedback (and persistent chat history) is to run the interactive setup script: + +```bash +./scripts/quickstart.sh +``` + +The script automatically: +1. Looks up the MLflow experiment ID linked to your serving endpoint +2. Uncomments and configures the feedback `TODO` block in `databricks.yml` (setting `experiment_id`) and the `MLFLOW_EXPERIMENT_ID` env var in `app.yaml` +3. Uncomments both database `TODO` blocks in `databricks.yml` to provision and bind a Lakebase database + +After the script completes, run `databricks bundle deploy` to apply the changes. + +### Manual setup + +If you prefer to configure manually: + +**Step 1 — Find your experiment ID** + +```bash +# For a custom-code agent or Agent Bricks serving endpoint +npx tsx scripts/get-experiment-id.ts --endpoint + +# For an Agent Bricks Knowledge Assistant or Multi-Agent Supervisor +npx tsx scripts/get-experiment-id.ts --agent-brick +``` + +**Step 2 — Configure `databricks.yml`** + +Uncomment both database `TODO` blocks (required for vote persistence) and the feedback `TODO` block, setting the experiment ID from Step 1: + +```yaml +- name: experiment + description: "MLflow experiment for collecting user feedback" + experiment: + experiment_id: "your-experiment-id" + permission: CAN_EDIT +``` + +**Step 3 — Configure `app.yaml`** + +Uncomment the `MLFLOW_EXPERIMENT_ID` environment variable: + +```yaml +- name: MLFLOW_EXPERIMENT_ID + valueFrom: experiment +``` + +**Step 4 — Redeploy**: + +```bash +databricks bundle deploy +databricks bundle run databricks_chatbot +``` + +Once deployed, the "Feedback disabled" badge disappears and the thumbs up/down buttons become active on assistant messages. + +### Enabling feedback for local development + +Set `MLFLOW_EXPERIMENT_ID` in your `.env` file to the experiment ID from Step 1: + +```bash +MLFLOW_EXPERIMENT_ID= +``` + +## Testing + +The project uses Playwright for end-to-end testing and supports dual-mode testing to verify behavior in both persistent and ephemeral modes. + +### Test Modes + +Tests run in two separate modes to ensure both database and non-database functionality work correctly: + +#### With Database Mode + +- Uses database environment variables (either set in .env or declared elsewhere) +- Includes full Postgres database +- Tests chat history persistence, pagination, and deletion +- Will throw a warning and stop if no database exists + +#### Ephemeral Mode + +- No database connection (all POSTGRES_URL and PG\* variables omitted) +- Tests chat streaming without persistence +- Ensures UI gracefully handles missing database + +### Running Tests + +**Run all tests (both modes sequentially)**: + +```bash +npm test +``` + +This runs with-db tests first, then ephemeral tests. The server automatically restarts between modes with different configurations. + +**Run specific mode**: + +```bash +# Test with database only +npm run test:with-db + +# Test ephemeral mode only +npm run test:ephemeral +``` + +### Continuous Integration + +The GitHub Actions workflow runs both test modes in separate jobs: + +- **test-with-db**: Includes Postgres service, runs migrations, executes with-db tests +- **test-ephemeral**: No Postgres, no migrations, executes ephemeral tests + +Both jobs run in parallel for faster CI feedback. + +## Known limitations + +- No support for image or other multi-modal inputs +- The most common and officially recommended authentication methods for Databricks are supported: Databricks CLI auth for local development, and Databricks service principal auth for deployed apps. Other authentication mechanisms (PAT, Azure MSI, etc) are not currently supported. +- We create one database per app, because the app code targets a fixed `ai_chatbot` schema within the database instance. To host multiple apps out of the same instance, you can: + - Update the database instance name in `databricks.yml` + - Update references to `ai_chatbot` in the codebase to your new desired schema name within the existing database instance + - Run `npm run db:generate` to regenerate database migrations + - Deploy your app + +## Troubleshooting + +### "reference does not exist" errors when running databricks bundle CLI commands + +If you get an error like the following (or other similar "reference does not exist" errors) +while running `databricks bundle` commands, your Databricks CLI version may be out of date. +Make sure to install the latest version of the Databricks CLI (per [Prerequisites](#prerequisites)) and try again. + +```bash +$ databricks bundle deploy +Error: reference does not exist: ${workspace.current_user.domain_friendly_name} + +Name: databricks-chatbot +Target: dev +Workspace: + User: user@company.com + Path: /Workspace/Users/user@company.com/.bundle/databricks-chatbot/dev +``` + +### "Resource not found" errors during databricks bundle deploy + +Errors like the following one can occur when attempting to deploy the app if the state of your bundle does not match the state of resources +deployed in your workspace: + +```bash +$ databricks bundle deploy +Uploading bundle files to /Workspace/Users/user@company.com/.bundle/databricks-chatbot/dev/files... +Deploying resources... +Error: terraform apply: exit status 1 + +Error: failed to update database_instance + + with databricks_database_instance.chatbot_lakebase, + on bundle.tf.json line 45, in resource.databricks_database_instance.chatbot_lakebase: + 45: } + +Resource not found + + +Updating deployment state... +``` + +This can happen if resources deployed via your bundle were then manually deleted, or resources specified by your bundle +were manually created without using the `databricks bundle` CLI. To resolve this class of issue, inspect the state of the actual deployed resources +in your workspace and compare it to the bundle state using `databricks bundle summary`. If there is a mismatch, +[see docs](https://docs.databricks.com/aws/en/dev-tools/bundles/faqs#can-i-port-existing-jobs-pipelines-dashboards-and-other-databricks-objects-into-my-bundle) on how to +manually bind (if resources were manually created) or unbind (if resources were manually deleted) resources +from your current bundle state. In the above example, the `chatbot_lakebase` database instance resource +was deployed via `databricks bundle deploy`, and then manually deleted. This broke subsequent deployments of the bundle +(because bundle state indicated the resource should exist, but it did not in the workspace). Running `databricks bundle unbind chatbot_lakebase` updated bundle state to reflect the deletion of the instance, +unblocking subsequent deployment of the bundle via `databricks bundle deploy`. diff --git a/agent-openai-agents-sdk-multiagent/e2e-chatbot-app-next/app.yaml b/agent-openai-agents-sdk-multiagent/e2e-chatbot-app-next/app.yaml new file mode 100644 index 00000000..47be38c9 --- /dev/null +++ b/agent-openai-agents-sdk-multiagent/e2e-chatbot-app-next/app.yaml @@ -0,0 +1,9 @@ +command: ["npm", "run", "start"] +runtime: nodejs20 + +env: + - name: DATABRICKS_SERVING_ENDPOINT + valueFrom: serving-endpoint + # FEEDBACK RESOURCE: Uncomment to enable the feedback UI (requires experiment resource in databricks.yml) + # - name: MLFLOW_EXPERIMENT_ID + # valueFrom: experiment diff --git a/agent-openai-agents-sdk-multiagent/e2e-chatbot-app-next/biome.jsonc b/agent-openai-agents-sdk-multiagent/e2e-chatbot-app-next/biome.jsonc new file mode 100644 index 00000000..4054a428 --- /dev/null +++ b/agent-openai-agents-sdk-multiagent/e2e-chatbot-app-next/biome.jsonc @@ -0,0 +1,129 @@ +{ + "$schema": "https://biomejs.dev/schemas/1.9.4/schema.json", + "files": { + "ignoreUnknown": false, + "ignore": ["**/package-lock.json", "node_modules", "dist", "public"] + }, + "vcs": { + "enabled": true, + "clientKind": "git", + "defaultBranch": "main", + "useIgnoreFile": true + }, + "formatter": { + "enabled": true, + "formatWithErrors": false, + "indentStyle": "space", + "indentWidth": 2, + "lineEnding": "lf", + "lineWidth": 80, + "attributePosition": "auto" + }, + "linter": { + "enabled": true, + "rules": { + "recommended": true, + "a11y": { + "useHtmlLang": "warn", // Not in recommended ruleset, turning on manually + "noHeaderScope": "warn", // Not in recommended ruleset, turning on manually + "useValidAriaRole": { + "level": "warn", + "options": { + "ignoreNonDom": false, + "allowInvalidRoles": ["none", "text"] + } + }, + "useSemanticElements": "off", // Rule is buggy, revisit later + "noSvgWithoutTitle": "off", // We do not intend to adhere to this rule + "useMediaCaption": "off", // We would need a cultural change to turn this on + "noAutofocus": "off", // We're highly intentional about when we use autofocus + "noBlankTarget": "off", // Covered by Conformance + "useFocusableInteractive": "off", // Disable focusable interactive element requirement + "useAriaPropsForRole": "off", // Disable required ARIA attributes check + "useKeyWithClickEvents": "off" // Disable keyboard event requirement with click events + }, + "complexity": { + "noUselessStringConcat": "warn", // Not in recommended ruleset, turning on manually + "noForEach": "off", // forEach is too familiar to ban + "noUselessSwitchCase": "off", // Turned off due to developer preferences + "noUselessThisAlias": "off", // Turned off due to developer preferences + "noBannedTypes": "off" + }, + "correctness": { + "noUnusedVariables": "warn", + "noUnusedImports": "warn", // Not in recommended ruleset, turning on manually + "useArrayLiterals": "warn", // Not in recommended ruleset, turning on manually + "noNewSymbol": "warn", // Not in recommended ruleset, turning on manually + "useJsxKeyInIterable": "off", // Rule is buggy, revisit later + "useExhaustiveDependencies": "warn", // Errors by default, switching to warn instead + "noUnnecessaryContinue": "off" // Turned off due to developer preferences + }, + "security": { + "noDangerouslySetInnerHtml": "off" // Covered by Conformance + }, + "style": { + "useFragmentSyntax": "warn", // Not in recommended ruleset, turning on manually + "noYodaExpression": "warn", // Not in recommended ruleset, turning on manually + "useDefaultParameterLast": "warn", // Not in recommended ruleset, turning on manually + "useExponentiationOperator": "off", // Obscure and arguably not easily readable + "noUnusedTemplateLiteral": "off", // Stylistic opinion + "noUselessElse": "off" // Stylistic opinion + }, + "suspicious": { + "noExplicitAny": "off" // We trust Vercelians to use any only when necessary + }, + "nursery": { + "noDocumentImportInPage": "warn", + "noDuplicateElseIf": "warn", + "noHeadImportInDocument": "warn", + "noIrregularWhitespace": "warn", + "noStaticElementInteractions": "warn", + "useSortedClasses": "error", + "useValidAutocomplete": "warn" + } + } + }, + "javascript": { + "jsxRuntime": "reactClassic", + "formatter": { + "jsxQuoteStyle": "double", + "quoteProperties": "asNeeded", + "trailingCommas": "all", + "semicolons": "always", + "arrowParentheses": "always", + "bracketSpacing": true, + "bracketSameLine": false, + "quoteStyle": "single", + "attributePosition": "auto" + } + }, + "json": { + "formatter": { + "enabled": true, + "trailingCommas": "none" + }, + "parser": { + "allowComments": true, + "allowTrailingCommas": false + } + }, + "css": { + "formatter": { "enabled": false }, + "linter": { "enabled": false } + }, + "organizeImports": { "enabled": false }, + "overrides": [ + // Playwright requires an object destructure, even if empty + // https://github.com/microsoft/playwright/issues/30007 + { + "include": ["playwright/**"], + "linter": { + "rules": { + "correctness": { + "noEmptyPattern": "off" + } + } + } + } + ] +} diff --git a/agent-openai-agents-sdk-multiagent/e2e-chatbot-app-next/client/index.html b/agent-openai-agents-sdk-multiagent/e2e-chatbot-app-next/client/index.html new file mode 100644 index 00000000..3a38736b --- /dev/null +++ b/agent-openai-agents-sdk-multiagent/e2e-chatbot-app-next/client/index.html @@ -0,0 +1,16 @@ + + + + + + + + Databricks Chat + + + + +
+ + + diff --git a/agent-openai-agents-sdk-multiagent/e2e-chatbot-app-next/client/package.json b/agent-openai-agents-sdk-multiagent/e2e-chatbot-app-next/client/package.json new file mode 100644 index 00000000..0fd706ea --- /dev/null +++ b/agent-openai-agents-sdk-multiagent/e2e-chatbot-app-next/client/package.json @@ -0,0 +1,67 @@ +{ + "name": "@databricks/chatbot-client", + "version": "1.0.0", + "private": true, + "type": "module", + "scripts": { + "dev": "node ../node_modules/vite/bin/vite.js", + "build": "node ../node_modules/vite/bin/vite.js build", + "preview": "node ../node_modules/vite/bin/vite.js preview", + "lint": "biome lint --write" + }, + "browser": { + "child_process": false, + "crypto": false, + "fs": false, + "fs/promises": false, + "path": false, + "url": false, + "vm": false, + "ws": false + }, + "dependencies": { + "@ai-sdk/react": "^3.0.59", + "@radix-ui/react-alert-dialog": "^1.1.2", + "@radix-ui/react-collapsible": "^1.1.12", + "@radix-ui/react-dialog": "^1.1.2", + "@radix-ui/react-dropdown-menu": "^2.1.2", + "@radix-ui/react-separator": "^1.1.0", + "@radix-ui/react-slot": "^1.1.2", + "@radix-ui/react-tooltip": "^1.1.3", + "@radix-ui/react-use-controllable-state": "^1.2.2", + "ai": "^6.0.70", + "class-variance-authority": "^0.7.0", + "clsx": "^2.1.1", + "date-fns": "^4.1.0", + "fast-deep-equal": "^3.1.3", + "framer-motion": "^11.3.19", + "lucide-react": "^0.446.0", + "next-themes": "^0.4.6", + "react": "^18.2.0", + "react-dom": "^18.2.0", + "react-router-dom": "^6.22.0", + "react-syntax-highlighter": "^15.6.6", + "sonner": "^1.5.0", + "streamdown": "^1.4.0", + "swr": "^2.2.5", + "tailwind-merge": "^2.5.2", + "use-stick-to-bottom": "^1.1.1", + "usehooks-ts": "^3.1.0", + "zod": "^4.3.5" + }, + "devDependencies": { + "@chat-template/auth": "*", + "@chat-template/core": "*", + "@chat-template/db": "*", + "@tailwindcss/postcss": "^4.1.13", + "@tailwindcss/typography": "^0.5.19", + "@types/react": "^18", + "@types/react-dom": "^18", + "@vitejs/plugin-react": "^4.2.1", + "postcss": "^8", + "tailwindcss": "^4.1.13", + "tailwindcss-animate": "^1.0.7", + "typescript": "^5.9.3", + "vite": "npm:rolldown-vite@latest" + } +} diff --git a/agent-openai-agents-sdk-multiagent/e2e-chatbot-app-next/client/postcss.config.js b/agent-openai-agents-sdk-multiagent/e2e-chatbot-app-next/client/postcss.config.js new file mode 100644 index 00000000..a34a3d56 --- /dev/null +++ b/agent-openai-agents-sdk-multiagent/e2e-chatbot-app-next/client/postcss.config.js @@ -0,0 +1,5 @@ +export default { + plugins: { + '@tailwindcss/postcss': {}, + }, +}; diff --git a/agent-openai-agents-sdk-multiagent/e2e-chatbot-app-next/client/public/demo-thumbnail.png b/agent-openai-agents-sdk-multiagent/e2e-chatbot-app-next/client/public/demo-thumbnail.png new file mode 100644 index 0000000000000000000000000000000000000000..8c6f98ab2eae4d993ef01a06117d52d2bf63ea35 GIT binary patch literal 23198 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 literal 0 HcmV?d00001 diff --git a/agent-openai-agents-sdk-multiagent/e2e-chatbot-app-next/client/public/mouth of the seine, monet.jpg b/agent-openai-agents-sdk-multiagent/e2e-chatbot-app-next/client/public/mouth of the seine, monet.jpg new file mode 100644 index 0000000000000000000000000000000000000000..62515e5655ef6ff24bd3af6673093796a6934837 GIT binary patch literal 33497 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 z%0A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 literal 0 HcmV?d00001 diff --git a/agent-openai-agents-sdk-multiagent/e2e-chatbot-app-next/client/src/App.tsx b/agent-openai-agents-sdk-multiagent/e2e-chatbot-app-next/client/src/App.tsx new file mode 100644 index 00000000..572aa447 --- /dev/null +++ b/agent-openai-agents-sdk-multiagent/e2e-chatbot-app-next/client/src/App.tsx @@ -0,0 +1,39 @@ +import { Routes, Route } from 'react-router-dom'; +import { ThemeProvider } from '@/components/theme-provider'; +import { SessionProvider } from '@/contexts/SessionContext'; +import { AppConfigProvider } from '@/contexts/AppConfigContext'; +import { DataStreamProvider } from '@/components/data-stream-provider'; +import { Toaster } from 'sonner'; +import RootLayout from '@/layouts/RootLayout'; +import ChatLayout from '@/layouts/ChatLayout'; +import NewChatPage from '@/pages/NewChatPage'; +import ChatPage from '@/pages/ChatPage'; + +function App() { + return ( + + + + + + + }> + }> + } /> + } /> + + + + + + + + ); +} + +export default App; diff --git a/agent-openai-agents-sdk-multiagent/e2e-chatbot-app-next/client/src/components/DatabricksLogo.tsx b/agent-openai-agents-sdk-multiagent/e2e-chatbot-app-next/client/src/components/DatabricksLogo.tsx new file mode 100644 index 00000000..81b760d9 --- /dev/null +++ b/agent-openai-agents-sdk-multiagent/e2e-chatbot-app-next/client/src/components/DatabricksLogo.tsx @@ -0,0 +1,47 @@ +import * as React from "react" +import { cn } from "@/lib/utils" + +interface DatabricksLogoProps { + /** Height in px. Width scales proportionally (713:113 ratio). */ + height?: number + className?: string +} + +/** + * Full Databricks primary lockup. + * - Brickwork icon: always #FF3621 + * - Wordmark text: currentColor — inherits dark-mode text color from parent + */ +export function DatabricksLogo({ height = 20, className }: DatabricksLogoProps) { + const width = Math.round((713 / 113) * height) + return ( + + {/* Brickwork icon — always brand red */} + + {/* Wordmark text — currentColor for dark mode support */} + + + + + + + + + + + + + ) +} diff --git a/agent-openai-agents-sdk-multiagent/e2e-chatbot-app-next/client/src/components/animation-assistant-icon.tsx b/agent-openai-agents-sdk-multiagent/e2e-chatbot-app-next/client/src/components/animation-assistant-icon.tsx new file mode 100644 index 00000000..19550442 --- /dev/null +++ b/agent-openai-agents-sdk-multiagent/e2e-chatbot-app-next/client/src/components/animation-assistant-icon.tsx @@ -0,0 +1,222 @@ +import { cn } from '@/lib/utils'; +import { motion, useAnimation } from 'framer-motion'; +import { SparklesIcon } from 'lucide-react'; +import { useEffect } from 'react'; + +type AnimatedAssistantIconProps = { + /** Diameter of the inner SparklesIcon */ + size?: number; + /** Run the pulse / rotate animation while true */ + isLoading?: boolean; + // If true, the component will appear muted and animate at more slow and subtle + muted?: boolean; +}; + +export const AnimatedAssistantIcon = ({ + size = 20, + isLoading = false, + muted = false, +}: AnimatedAssistantIconProps) => { + /* ----------------------------------------------------------------- + * Gradient colours – we keep the original helper so the gradients + * stay exactly the same as before. + * ----------------------------------------------------------------- */ + const { bottomGradient, solidGradient, topGradient } = getAiGradientStyle(); + + /* ----------------------------------------------------------------- + * Motion controls – we split the three animated layers in separate + * animation objects so each can have its own timing / easing. + * ----------------------------------------------------------------- */ + const scaleControls = useAnimation(); // “pulse‑scale‑up” + const rotateControls = useAnimation(); // “pulse‑rotate” + + useEffect(() => { + if (isLoading) { + // 1️⃣ Scale – grows from 0.9 → 1.1 and back (alternate) + if (!muted) { + scaleControls.start({ + scale: [0.9, 1.1], + transition: { + repeat: Number.POSITIVE_INFINITY, + repeatType: 'reverse', + duration: 1, + ease: 'easeInOut', + }, + }); + } + + // 3️⃣ Rotating gradient – full spin every 2 seconds (continuous) + rotateControls.start({ + rotate: [0, 1800], + transition: { + repeat: Number.POSITIVE_INFINITY, + duration: 2, + ease: 'easeInOut', + }, + }); + } else { + // Stop all animations instantly – the circles stay at their “rest” + scaleControls.stop(); + rotateControls.stop(); + + /* ---------- SMOOTHLY RETURN TO RESTING STATE ------------- */ + scaleControls.start({ + scale: 1, + transition: { duration: 0.5, ease: 'easeOut' }, + }); + rotateControls.start({ + rotate: 0, + transition: { duration: 0.5, ease: 'easeOut' }, + }); + } + }, [isLoading, muted, scaleControls, rotateControls]); + + /* ----------------------------------------------------------------- + * Component markup – three stacked s: + * + * • outer – pulsating scale & gradient rotation + * • middle – solid gradient that also pulsates (scale down) + * • top – static gradient that holds the SparklesIcon + * ----------------------------------------------------------------- */ + return ( +
    + {/* ---------- 1️⃣ OUTER GRADIENT (rotates + pulses) ---------- */} + + + + + {/* ---------- 2️⃣ MIDDLE SOLID GRADIENT (pulses down) ---------- */} + + + {/* ---------- 3️⃣ TOP ICON (static) -------------------------- */} +
    + +
    +
    + ); +}; + +/** + * Since we cannot use gradient colors for borders, we instead use composite background colors to achieve the same effect. + * + * Composite background colors are built from the top down, so the last one is also the one bottom one in the paint order. + * + * To achieve the design with gradient border colors, we use the following background colors: + * [ON TOP] A transparent gradient color that is used as a background + * [MIDDLE] A solid color that is used to back the next transparent background color + * [BOTTOM] A gradient color that is used as a border + * + * We also make use of padding-box to create an "inset" effect controlled by the border-width. + * This is what creates the illusion of a gradient border. + */ +export const getAiGradientStyle = () => { + // Currently these are the same for both light and dark mode, but we may want to change this in the future. + const branded = { + ai: { + /** For AI components, the top-left-oriented start color of gradient treatments. */ + gradientStart: '#4299E0', + /** For AI components, the mid color of gradient treatments. */ + gradientMid: '#CA42E0', + /** For AI components, the bottom-right-oriented end color of gradient treatments. */ + gradientEnd: '#FF5F46', + }, + }; + + const createGradient = (...colors: string[]) => + `linear-gradient(135deg, ${colors.join(', ')})`; + + const gradientColors = [ + branded.ai.gradientStart, + branded.ai.gradientMid, + branded.ai.gradientEnd, + ]; + + const topGradient = createGradient( + ...gradientColors.map((color) => hexToRGBA(color, 0.08) ?? ''), + ); + const solidGradient = createGradient( + 'var(--background)', + 'var(--background)', + ); + const bottomGradient = createGradient(...gradientColors); + + return { + topGradient, + solidGradient, + bottomGradient, + styling: { + border: '1px solid transparent', + background: [ + `${topGradient} padding-box`, + `${solidGradient} padding-box`, + `${bottomGradient} border-box`, + ].join(', '), + }, + }; +}; + +const hexToRGB = (hex: string): { r: number; g: number; b: number } | null => { + try { + const result = /^#?([a-f\d]{2})([a-f\d]{2})([a-f\d]{2})$/i.exec(hex); + if (!result) return null; + return { + r: Number.parseInt(result[1], 16), + g: Number.parseInt(result[2], 16), + b: Number.parseInt(result[3], 16), + }; + } catch { + return null; + } +}; + +/** + * Converts a hex color to an RGBA string. + * Useful if you need to add an alpha channel to a hex color. + */ +const hexToRGBA = (hex: string, alpha: number): string | null => { + const rgb = hexToRGB(hex); + if (rgb) return `rgba(${rgb.r}, ${rgb.g}, ${rgb.b}, ${alpha})`; + return null; +}; diff --git a/agent-openai-agents-sdk-multiagent/e2e-chatbot-app-next/client/src/components/app-sidebar.tsx b/agent-openai-agents-sdk-multiagent/e2e-chatbot-app-next/client/src/components/app-sidebar.tsx new file mode 100644 index 00000000..0880a218 --- /dev/null +++ b/agent-openai-agents-sdk-multiagent/e2e-chatbot-app-next/client/src/components/app-sidebar.tsx @@ -0,0 +1,111 @@ +import { useNavigate } from 'react-router-dom'; +import { Link } from 'react-router-dom'; + +import { SidebarHistory } from '@/components/sidebar-history'; +import { SidebarUserNav } from '@/components/sidebar-user-nav'; +import { + Sidebar, + SidebarContent, + SidebarFooter, + SidebarHeader, + SidebarMenu, + SidebarMenuButton, + SidebarMenuItem, + useSidebar, +} from '@/components/ui/sidebar'; +import { Tooltip, TooltipContent, TooltipTrigger } from '@/components/ui/tooltip'; +import { DbIcon } from '@/components/ui/db-icon'; +import { NewChatIcon, SidebarCollapseIcon, SidebarExpandIcon } from '@/components/icons'; +import { cn } from '@/lib/utils'; +import type { ClientSession } from '@chat-template/auth'; +import { Button } from './ui/button'; +import { Action } from './elements/actions'; + +export function AppSidebar({ + user, + preferredUsername, +}: { + user: ClientSession['user'] | undefined; + preferredUsername: string | null; +}) { + const navigate = useNavigate(); + const { setOpenMobile, open, openMobile, isMobile, toggleSidebar } = useSidebar(); + + const effectiveOpen = open || (isMobile && openMobile); + + return ( + + {/* ── Header: app title + collapse toggle ────────────────────────── */} + + {effectiveOpen && ( + setOpenMobile(false)} + className="flex items-center overflow-hidden px-1" + > + + Chatbot + + + )} + + + + + + + {/* ── Nav: New Chat item ───────────────────────────────────────────── */} +
    + + + + + { + setOpenMobile(false); + navigate('/'); + }} + > + + + New chat + + + + New chat + + + +
    + + {/* ── Chat history ────────────────────────────────────────────────── */} + + {effectiveOpen && } + + + {/* ── User nav ────────────────────────────────────────────────────── */} + + {user && ( + + )} + +
    + ); +} diff --git a/agent-openai-agents-sdk-multiagent/e2e-chatbot-app-next/client/src/components/chat-header.tsx b/agent-openai-agents-sdk-multiagent/e2e-chatbot-app-next/client/src/components/chat-header.tsx new file mode 100644 index 00000000..369a3805 --- /dev/null +++ b/agent-openai-agents-sdk-multiagent/e2e-chatbot-app-next/client/src/components/chat-header.tsx @@ -0,0 +1,129 @@ +import { useNavigate } from 'react-router-dom'; + +import { SidebarToggle } from '@/components/sidebar-toggle'; +import { Button } from '@/components/ui/button'; +import { MessageSquareOff, TriangleAlert } from 'lucide-react'; +import { useConfig } from '@/hooks/use-config'; +import { + Tooltip, + TooltipContent, + TooltipProvider, + TooltipTrigger, +} from '@/components/ui/tooltip'; +import { PlusIcon, CloudOffIcon } from './icons'; +import { cn } from '../lib/utils'; +import { Skeleton } from './ui/skeleton'; + +const DOCS_URL = + 'https://docs.databricks.com/aws/en/generative-ai/agent-framework/chat-app'; + +const OBO_DOCS_URL = + 'https://docs.databricks.com/aws/en/generative-ai/agent-framework/chat-app#enable-user-authorization'; + +function OboScopeBanner({ missingScopes }: { missingScopes: string[] }) { + if (missingScopes.length === 0) return null; + + return ( +
    +
    + +

    + This endpoint requires on-behalf-of user authorization. Add these + scopes to your app:{' '} + {missingScopes.join(', ')}.{' '} + + Learn more + +

    +
    +
    + ); +} + +export function ChatHeader({ title, empty, isLoadingTitle }: { title?: string, empty?: boolean, isLoadingTitle?: boolean }) { + const navigate = useNavigate(); + const { chatHistoryEnabled, feedbackEnabled, oboMissingScopes } = useConfig(); + + return ( + <> +
    + {/* Toggle visible on mobile only — desktop toggle lives inside the sidebar */} +
    + +
    + + {(title || isLoadingTitle) && +

    + {isLoadingTitle ? + : + title + } +

    + } + +
    + {!chatHistoryEnabled && ( + + + + + + Ephemeral + + + +

    Chat history disabled — conversations are not saved. Click to learn more.

    +
    +
    +
    + )} + {!feedbackEnabled && ( + + + + + + Feedback disabled + + + +

    Feedback submission disabled. Click to learn more.

    +
    +
    +
    + )} + {/* New Chat button — mobile only; desktop uses the sidebar rail */} + +
    +
    + + + + ); +} diff --git a/agent-openai-agents-sdk-multiagent/e2e-chatbot-app-next/client/src/components/chat.tsx b/agent-openai-agents-sdk-multiagent/e2e-chatbot-app-next/client/src/components/chat.tsx new file mode 100644 index 00000000..735b894c --- /dev/null +++ b/agent-openai-agents-sdk-multiagent/e2e-chatbot-app-next/client/src/components/chat.tsx @@ -0,0 +1,342 @@ +import type { DataUIPart, LanguageModelUsage, UIMessageChunk } from 'ai'; +import { useChat } from '@ai-sdk/react'; +import { useCallback, useEffect, useMemo, useRef, useState } from 'react'; +import { useSWRConfig } from 'swr'; +import { ChatHeader } from '@/components/chat-header'; +import { fetchWithErrorHandlers, generateUUID } from '@/lib/utils'; +import { MultimodalInput } from './multimodal-input'; +import { Messages } from './messages'; +import type { + Attachment, + ChatMessage, + CustomUIDataTypes, + FeedbackMap, + VisibilityType, +} from '@chat-template/core'; +import { unstable_serialize } from 'swr/infinite'; +import { getChatHistoryPaginationKey } from './sidebar-history'; +import { toast } from './toast'; +import { useSearchParams } from 'react-router-dom'; +import { useChatVisibility } from '@/hooks/use-chat-visibility'; +import { ChatSDKError } from '@chat-template/core/errors'; +import { useDataStream } from './data-stream-provider'; +import { isCredentialErrorMessage } from '@/lib/oauth-error-utils'; +import { ChatTransport } from '../lib/ChatTransport'; +import type { ClientSession } from '@chat-template/auth'; +import { softNavigateToChatId } from '@/lib/navigation'; +import { useAppConfig } from '@/contexts/AppConfigContext'; +import { Greeting } from './greeting'; + +export function Chat({ + id, + initialMessages, + initialChatModel, + initialVisibilityType, + isReadonly, + initialLastContext, + feedback = {}, + title, +}: { + id: string; + initialMessages: ChatMessage[]; + initialChatModel: string; + initialVisibilityType: VisibilityType; + isReadonly: boolean; + session: ClientSession; + initialLastContext?: LanguageModelUsage; + feedback?: FeedbackMap; + title?: string; +}) { + const { visibilityType } = useChatVisibility({ + chatId: id, + initialVisibilityType, + }); + + const { mutate } = useSWRConfig(); + const { setDataStream } = useDataStream(); + const { chatHistoryEnabled } = useAppConfig(); + + const [input, setInput] = useState(''); + const [_usage, setUsage] = useState( + initialLastContext, + ); + + const [lastPart, setLastPart] = useState(); + const lastPartRef = useRef(lastPart); + lastPartRef.current = lastPart; + + // Single counter for resume attempts - reset when stream parts are received + const resumeAttemptCountRef = useRef(0); + const maxResumeAttempts = 3; + + const abortController = useRef(new AbortController()); + useEffect(() => { + return () => { + abortController.current?.abort('ABORT_SIGNAL'); + }; + }, []); + + const fetchWithAbort = useMemo(() => { + return async (input: RequestInfo | URL, init?: RequestInit) => { + // useChat does not cancel /stream requests when the component is unmounted + const signal = abortController.current?.signal; + return fetchWithErrorHandlers(input, { ...init, signal }); + }; + }, []); + + const stop = useCallback(() => { + abortController.current?.abort('USER_ABORT_SIGNAL'); + }, []); + + const isNewChat = initialMessages.length === 0; + const didFetchHistoryOnNewChat = useRef(false); + const fetchChatHistory = useCallback(() => { + mutate(unstable_serialize(getChatHistoryPaginationKey)); + }, [mutate]); + + // For new chats, the title arrives via a `data-title` stream part + // once backend title generation completes — no separate fetch needed. + const [streamTitle, setStreamTitle] = useState(); + const [titlePending, setTitlePending] = useState(false); + const displayTitle = title ?? streamTitle; + + const { + messages, + setMessages, + sendMessage, + status, + resumeStream, + clearError, + addToolApprovalResponse, + regenerate, + } = useChat({ + id, + messages: initialMessages, + experimental_throttle: 100, + generateId: generateUUID, + resume: id !== undefined && initialMessages.length > 0, // Enable automatic stream resumption + transport: new ChatTransport({ + onStreamPart: (part) => { + if (isNewChat && !didFetchHistoryOnNewChat.current) { + fetchChatHistory(); + if (chatHistoryEnabled) { + setTitlePending(true); + } + didFetchHistoryOnNewChat.current = true; + } + // Reset resume attempts when we successfully receive stream parts + resumeAttemptCountRef.current = 0; + setLastPart(part); + }, + api: '/api/chat', + fetch: fetchWithAbort, + prepareSendMessagesRequest({ messages, id, body }) { + const lastMessage = messages.at(-1); + const isUserMessage = lastMessage?.role === 'user'; + + // For continuations (non-user messages like tool results), we must always + // send previousMessages because the tool result only exists client-side + // and hasn't been saved to the database yet. + const needsPreviousMessages = !chatHistoryEnabled || !isUserMessage; + + return { + body: { + id, + // Only include message field for user messages (new messages) + // For continuation (assistant messages with tool results), omit message field + ...(isUserMessage ? { message: lastMessage } : {}), + selectedChatModel: initialChatModel, + selectedVisibilityType: visibilityType, + nextMessageId: generateUUID(), + // Send previous messages when: + // 1. Database is disabled (ephemeral mode) - always need client-side messages + // 2. Continuation request (tool results) - tool result only exists client-side + ...(needsPreviousMessages + ? { + previousMessages: isUserMessage + ? messages.slice(0, -1) + : messages, + } + : {}), + ...body, + }, + }; + }, + prepareReconnectToStreamRequest({ id }) { + return { + api: `/api/chat/${id}/stream`, + credentials: 'include', + }; + }, + }), + onData: (dataPart) => { + setDataStream((ds) => + ds ? [...ds, dataPart as DataUIPart] : [], + ); + if (dataPart.type === 'data-usage') { + setUsage(dataPart.data as LanguageModelUsage); + } + if (dataPart.type === 'data-title') { + setStreamTitle(dataPart.data as string); + setTitlePending(false); + fetchChatHistory(); + } + }, + onFinish: ({ + isAbort, + isDisconnect, + isError, + messages: finishedMessages, + }) => { + didFetchHistoryOnNewChat.current = false; + setTitlePending(false); + + // If user aborted, don't try to resume + if (isAbort) { + console.log('[Chat onFinish] Stream was aborted by user, not resuming'); + fetchChatHistory(); + return; + } + + // Check if the last message contains an OAuth credential error + // If so, don't try to resume - the user needs to authenticate first + const lastMessage = finishedMessages?.at(-1); + const hasOAuthError = lastMessage?.parts?.some( + (part) => + part.type === 'data-error' && + typeof part.data === 'string' && + isCredentialErrorMessage(part.data), + ); + + if (hasOAuthError) { + console.log( + '[Chat onFinish] OAuth credential error detected, not resuming', + ); + fetchChatHistory(); + clearError(); + return; + } + + // Determine if we should attempt to resume: + // 1. Stream didn't end with a 'finish' part (incomplete) + // 2. It was a disconnect/error that terminated the stream + // 3. We haven't exceeded max resume attempts + const streamIncomplete = lastPartRef.current?.type !== 'finish'; + const shouldResume = + streamIncomplete && + (isDisconnect || isError || lastPartRef.current === undefined); + + if (shouldResume && resumeAttemptCountRef.current < maxResumeAttempts) { + console.log( + '[Chat onFinish] Resuming stream. Attempt:', + resumeAttemptCountRef.current + 1, + ); + resumeAttemptCountRef.current++; + // Ref: https://github.com/vercel/ai/issues/8477#issuecomment-3603209884 + queueMicrotask(() => { + resumeStream(); + }) + } else { + // Stream completed normally or we've exhausted resume attempts + if (resumeAttemptCountRef.current >= maxResumeAttempts) { + console.warn('[Chat onFinish] Max resume attempts reached'); + } + fetchChatHistory(); + } + }, + onError: (error) => { + console.log('[Chat onError] Error occurred:', error); + + // Only show toast for explicit ChatSDKError (backend validation errors) + // Other errors (network, schema validation) are handled silently or in message parts + if (error instanceof ChatSDKError) { + toast({ + type: 'error', + description: error.message, + }); + } else { + // Non-ChatSDKError: Could be network error or in-stream error + // Log but don't toast - errors during streaming may be informational + console.warn('[Chat onError] Error during streaming:', error.message); + } + // Note: We don't call resumeStream here because onError can be called + // while the stream is still active (e.g., for data-error parts). + // Resume logic is handled exclusively in onFinish. + }, + }); + + const [searchParams] = useSearchParams(); + const query = searchParams.get('query'); + + const [hasAppendedQuery, setHasAppendedQuery] = useState(false); + + useEffect(() => { + if (query && !hasAppendedQuery) { + sendMessage({ + role: 'user' as const, + parts: [{ type: 'text', text: query }], + }); + + setHasAppendedQuery(true); + softNavigateToChatId(id, chatHistoryEnabled); + } + }, [query, sendMessage, hasAppendedQuery, id, chatHistoryEnabled]); + + const [attachments, setAttachments] = useState>([]); + + const inputElement = + + if (messages.length === 0) { + return ( +
    + +
    +
    + + {inputElement} +
    +
    +
    + ); + } + + return ( + <> +
    + + + + + + +
    + {!isReadonly && ( + inputElement + )} +
    +
    + + ); +} diff --git a/agent-openai-agents-sdk-multiagent/e2e-chatbot-app-next/client/src/components/data-stream-provider.tsx b/agent-openai-agents-sdk-multiagent/e2e-chatbot-app-next/client/src/components/data-stream-provider.tsx new file mode 100644 index 00000000..51944d36 --- /dev/null +++ b/agent-openai-agents-sdk-multiagent/e2e-chatbot-app-next/client/src/components/data-stream-provider.tsx @@ -0,0 +1,38 @@ +import React, { createContext, useContext, useMemo, useState } from 'react'; +import type { DataUIPart } from 'ai'; +import type { CustomUIDataTypes } from '@chat-template/core'; + +interface DataStreamContextValue { + dataStream: DataUIPart[]; + setDataStream: React.Dispatch< + React.SetStateAction[]> + >; +} + +const DataStreamContext = createContext(null); + +export function DataStreamProvider({ + children, +}: { + children: React.ReactNode; +}) { + const [dataStream, setDataStream] = useState[]>( + [], + ); + + const value = useMemo(() => ({ dataStream, setDataStream }), [dataStream]); + + return ( + + {children} + + ); +} + +export function useDataStream() { + const context = useContext(DataStreamContext); + if (!context) { + throw new Error('useDataStream must be used within a DataStreamProvider'); + } + return context; +} diff --git a/agent-openai-agents-sdk-multiagent/e2e-chatbot-app-next/client/src/components/databricks-message-citation.tsx b/agent-openai-agents-sdk-multiagent/e2e-chatbot-app-next/client/src/components/databricks-message-citation.tsx new file mode 100644 index 00000000..a4093510 --- /dev/null +++ b/agent-openai-agents-sdk-multiagent/e2e-chatbot-app-next/client/src/components/databricks-message-citation.tsx @@ -0,0 +1,109 @@ +import type { ChatMessage } from '@chat-template/core'; +import type { + AnchorHTMLAttributes, + ComponentType, + PropsWithChildren, +} from 'react'; +import { Tooltip, TooltipContent, TooltipTrigger } from './ui/tooltip'; +import { cn } from '@/lib/utils'; + +/** + * ReactMarkdown/Streamdown component that handles Databricks message citations. + * + * @example + * + */ +export const DatabricksMessageCitationStreamdownIntegration: ComponentType< + AnchorHTMLAttributes +> = (props) => { + if (isDatabricksMessageCitationLink(props.href)) { + return ( + + ); + } + return ; +}; + +// const isFootnoteLink + +type SourcePart = Extract; + +// Adds a unique suffix to the link to indicate that it is a Databricks message citation. +const encodeDatabricksMessageCitationLink = (part: SourcePart) => + `${part.url}::databricks_citation`; + +// Removes the unique suffix from the link to get the original link. +const decodeDatabricksMessageCitationLink = (link: string) => + link.replace('::databricks_citation', ''); + +// Creates a markdown link to the Databricks message citation. +export const createDatabricksMessageCitationMarkdown = (part: SourcePart) => + `[${part.title || part.url}](${encodeDatabricksMessageCitationLink(part)})`; + +// Checks if the link is a Databricks message citation. +const isDatabricksMessageCitationLink = ( + link?: string, +): link is `${string}::databricks_citation` => + link?.endsWith('::databricks_citation') ?? false; + +// Renders the Databricks message citation. +const DatabricksMessageCitationRenderer = ( + props: PropsWithChildren<{ + href: string; + }>, +) => { + return ( + + + + {props.children} + + + + {props.href} + + + ); +}; + +// Copied from streamdown +// https://github.com/vercel/streamdown/blob/dc5bd12e5709afce09814e47cf80884f8c665b3d/packages/streamdown/lib/components.tsx#L157-L181 +const DefaultAnchor: ComponentType> = ( + props, +) => { + const isIncomplete = props.href === 'streamdown:incomplete-link'; + const isFootnoteLink = props.href?.startsWith('#'); + + return ( + + {props.children} + + ); +}; diff --git a/agent-openai-agents-sdk-multiagent/e2e-chatbot-app-next/client/src/components/databricks-message-part-transformers.ts b/agent-openai-agents-sdk-multiagent/e2e-chatbot-app-next/client/src/components/databricks-message-part-transformers.ts new file mode 100644 index 00000000..7c0008e8 --- /dev/null +++ b/agent-openai-agents-sdk-multiagent/e2e-chatbot-app-next/client/src/components/databricks-message-part-transformers.ts @@ -0,0 +1,84 @@ +import type { ChatMessage } from '@chat-template/core'; +import { createDatabricksMessageCitationMarkdown } from './databricks-message-citation'; +import type { TextUIPart } from 'ai'; + +/** + * Creates segments of parts that can be rendered as a single component. + * Used to render citations as part of the associated text. + */ +export const createMessagePartSegments = (parts: ChatMessage['parts']) => { + // An array of arrays of parts + // Allows us to render multiple parts as a single component + const out: ChatMessage['parts'][] = []; + for (const part of parts) { + const lastBlock = out[out.length - 1] || null; + const previousPart = lastBlock?.[lastBlock.length - 1] || null; + + // If the previous part is a text part and the current part is a source part, add it to the current block + if (previousPart?.type === 'text' && part.type === 'source-url') { + lastBlock.push(part); + } + // If the previous part is a source-url part and the current part is a source part, add it to the current block + else if ( + previousPart?.type === 'source-url' && + part.type === 'source-url' + ) { + lastBlock.push(part); + } else if ( + lastBlock?.[0]?.type === 'text' && + part.type === 'text' && + !isNamePart(part) && + !isNamePart(lastBlock[0]) + ) { + // If the text part, or the previous part contains a tag, add it to a new block + // Otherwise, append sequential text parts to the same block + lastBlock.push(part); + // } + } + // Otherwise, add the current part to a new block + else { + out.push([part]); + } + } + + return out; +}; + +export const isNamePart = ( + part: ChatMessage['parts'][number], +): part is TextUIPart => { + return ( + part.type === 'text' && + part.text?.startsWith('') && + part.text?.endsWith('') + ); +}; +export const formatNamePart = (part: ChatMessage['parts'][number]) => { + if (!isNamePart(part)) return null; + return part.text?.replace('', '').replace('', ''); +}; + +/** + * Takes a segment of parts and joins them into a markdown-formatted string. + * Used to render citations as part of the associated text. + */ +export const joinMessagePartSegments = (parts: ChatMessage['parts']) => { + return parts.reduce((acc, part) => { + switch (part.type) { + case 'text': + return acc + part.text; + case 'source-url': + console.log("acc.endsWith('|')", acc.endsWith('|')); + // Special case for markdown tables + if (acc.endsWith('|')) { + // 1. Remove the last pipe + // 2. Insert the citation markdown + // 3. Add the pipe back + return `${acc.slice(0, -1)} ${createDatabricksMessageCitationMarkdown(part)}|`; + } + return `${acc} ${createDatabricksMessageCitationMarkdown(part)}`; + default: + return acc; + } + }, ''); +}; diff --git a/agent-openai-agents-sdk-multiagent/e2e-chatbot-app-next/client/src/components/elements/actions.tsx b/agent-openai-agents-sdk-multiagent/e2e-chatbot-app-next/client/src/components/elements/actions.tsx new file mode 100644 index 00000000..9684d348 --- /dev/null +++ b/agent-openai-agents-sdk-multiagent/e2e-chatbot-app-next/client/src/components/elements/actions.tsx @@ -0,0 +1,66 @@ +import { Button } from '@/components/ui/button'; +import { + Tooltip, + TooltipContent, + TooltipProvider, + TooltipTrigger, +} from '@/components/ui/tooltip'; +import { cn } from '@/lib/utils'; +import type { ComponentProps } from 'react'; + +type ActionsProps = ComponentProps<'div'>; + +export const Actions = ({ className, children, ...props }: ActionsProps) => ( +
    + {children} +
    +); + +type ActionProps = ComponentProps & { + tooltip?: string; + label?: string; + iconOnly?: boolean; +}; + +export const Action = ({ + tooltip, + children, + label, + className, + variant = 'tertiary', + size = 'sm', + iconOnly = true, + ...props +}: ActionProps) => { + const button = ( + + ); + + if (tooltip) { + return ( + + + {button} + +

    {tooltip}

    +
    +
    +
    + ); + } + + return button; +}; diff --git a/agent-openai-agents-sdk-multiagent/e2e-chatbot-app-next/client/src/components/elements/code-block.tsx b/agent-openai-agents-sdk-multiagent/e2e-chatbot-app-next/client/src/components/elements/code-block.tsx new file mode 100644 index 00000000..2efc92e4 --- /dev/null +++ b/agent-openai-agents-sdk-multiagent/e2e-chatbot-app-next/client/src/components/elements/code-block.tsx @@ -0,0 +1,102 @@ +import { cn } from '@/lib/utils'; +import type { HTMLAttributes, ReactNode } from 'react'; +import { createContext } from 'react'; +import { Prism as SyntaxHighlighter } from 'react-syntax-highlighter'; +import { + oneDark, + oneLight, +} from 'react-syntax-highlighter/dist/esm/styles/prism'; + +type CodeBlockContextType = { + code: string; +}; + +const CodeBlockContext = createContext({ + code: '', +}); + +type CodeBlockProps = HTMLAttributes & { + code: string; + language: string; + showLineNumbers?: boolean; + children?: ReactNode; +}; + +export const CodeBlock = ({ + code, + language, + showLineNumbers = false, + className, + children, + ...props +}: CodeBlockProps) => ( + +
    +
    + + {code} + + + {code} + + {children && ( +
    + {children} +
    + )} +
    +
    +
    +); diff --git a/agent-openai-agents-sdk-multiagent/e2e-chatbot-app-next/client/src/components/elements/conversation.tsx b/agent-openai-agents-sdk-multiagent/e2e-chatbot-app-next/client/src/components/elements/conversation.tsx new file mode 100644 index 00000000..0c6b4f12 --- /dev/null +++ b/agent-openai-agents-sdk-multiagent/e2e-chatbot-app-next/client/src/components/elements/conversation.tsx @@ -0,0 +1,27 @@ +import { cn } from '@/lib/utils'; +import type { ComponentProps } from 'react'; +import { StickToBottom } from 'use-stick-to-bottom'; + +type ConversationProps = ComponentProps; + +export const Conversation = ({ className, ...props }: ConversationProps) => ( + +); + +type ConversationContentProps = ComponentProps; + +export const ConversationContent = ({ + className, + ...props +}: ConversationContentProps) => ( + +); diff --git a/agent-openai-agents-sdk-multiagent/e2e-chatbot-app-next/client/src/components/elements/loader.tsx b/agent-openai-agents-sdk-multiagent/e2e-chatbot-app-next/client/src/components/elements/loader.tsx new file mode 100644 index 00000000..bb83e3e1 --- /dev/null +++ b/agent-openai-agents-sdk-multiagent/e2e-chatbot-app-next/client/src/components/elements/loader.tsx @@ -0,0 +1,96 @@ +import { cn } from '@/lib/utils'; +import type { HTMLAttributes } from 'react'; + +type LoaderIconProps = { + size?: number; +}; + +const LoaderIcon = ({ size = 16 }: LoaderIconProps) => ( + + Loader + + + + + + + + + + + + + + + + + + +); + +type LoaderProps = HTMLAttributes & { + size?: number; +}; + +export const Loader = ({ className, size = 16, ...props }: LoaderProps) => ( +
    + +
    +); diff --git a/agent-openai-agents-sdk-multiagent/e2e-chatbot-app-next/client/src/components/elements/mcp-tool.tsx b/agent-openai-agents-sdk-multiagent/e2e-chatbot-app-next/client/src/components/elements/mcp-tool.tsx new file mode 100644 index 00000000..7360f10d --- /dev/null +++ b/agent-openai-agents-sdk-multiagent/e2e-chatbot-app-next/client/src/components/elements/mcp-tool.tsx @@ -0,0 +1,246 @@ +import { Button } from '@/components/ui/button'; +import { CollapsibleTrigger } from '@/components/ui/collapsible'; +import { cn } from '@/lib/utils'; +import { + ChevronDownIcon, + ServerIcon, + ShieldAlertIcon, + ShieldCheckIcon, + ShieldXIcon, +} from 'lucide-react'; +import { + ToolContainer, + ToolContent, + ToolInput, + ToolOutput, + ToolStatusBadge, + type ToolState, +} from './tool'; + +// MCP-specific container with distinct styling +type McpToolProps = Parameters[0]; + +export const McpTool = ({ className, ...props }: McpToolProps) => ( + +); + +// Re-export shared components for convenience +export { + ToolContent as McpToolContent, + ToolInput as McpToolInput, + ToolOutput as McpToolOutput, +}; + +// MCP-specific header with banner +type McpToolHeaderProps = { + serverName?: string; + toolName: string; + state: ToolState; + // Used when state is 'approval-responded' to determine approval outcome + approved?: boolean; + className?: string; +}; + +// Badge component for approval status in the banner +// Uses AI SDK native tool states directly +type ApprovalStatusBadgeProps = { + state: ToolState; + // Used when state is 'approval-responded' to determine approval outcome + approved?: boolean; +}; + +const ApprovalStatusBadge = ({ state, approved }: ApprovalStatusBadgeProps) => { + // Pending: waiting for user approval + if (state === 'approval-requested') { + return ( + + + Pending + + ); + } + + // Allowed: tool executed successfully or user approved (waiting for execution) + if ( + state === 'output-available' || + (state === 'approval-responded' && approved === true) + ) { + return ( + + + Allowed + + ); + } + + // Denied: user explicitly denied the tool + if ( + state === 'output-denied' || + (state === 'approval-responded' && approved === false) + ) { + return ( + + + Denied + + ); + } + + // Fallback for any other state - show as pending + return ( + + + Pending + + ); +}; + +export const McpToolHeader = ({ + className, + serverName, + toolName, + state, + approved, +}: McpToolHeaderProps) => ( +
    + {/* MCP Banner */} +
    + + + Tool Call Request + + {serverName && ( + <> + + {serverName} + + )} + + +
    + {/* Tool header */} + +
    + {toolName} +
    +
    + {/* Only show tool status badge when tool is running/completed (approved) */} + {(state === 'output-available' || + (state === 'approval-responded' && approved === true)) && ( + + )} + +
    +
    +
    +); + +// MCP-specific approval actions +type McpApprovalActionsProps = { + onApprove: () => void; + onDeny: () => void; + isSubmitting: boolean; +}; + +export const McpApprovalActions = ({ + onApprove, + onDeny, + isSubmitting, +}: McpApprovalActionsProps) => ( +
    +
    + +

    + This tool requires your permission to run. +

    +
    +
    + + +
    +
    +); + +// MCP-specific approval status display +type McpApprovalStatusProps = { + approved: boolean; + reason?: string; +}; + +export const McpApprovalStatus = ({ + approved, + reason, +}: McpApprovalStatusProps) => ( +
    + {approved ? ( + + ) : ( + + )} + + {approved ? 'Allowed' : 'Denied'} + + {reason && ( + <> + + {reason} + + )} +
    +); diff --git a/agent-openai-agents-sdk-multiagent/e2e-chatbot-app-next/client/src/components/elements/message.tsx b/agent-openai-agents-sdk-multiagent/e2e-chatbot-app-next/client/src/components/elements/message.tsx new file mode 100644 index 00000000..4801f443 --- /dev/null +++ b/agent-openai-agents-sdk-multiagent/e2e-chatbot-app-next/client/src/components/elements/message.tsx @@ -0,0 +1,23 @@ +import { cn } from '@/lib/utils'; +import type { HTMLAttributes } from 'react'; + +type MessageContentProps = HTMLAttributes; + +export const MessageContent = ({ + children, + className, + ...props +}: MessageContentProps) => ( +
    + {children} +
    +); diff --git a/agent-openai-agents-sdk-multiagent/e2e-chatbot-app-next/client/src/components/elements/prompt-input.tsx b/agent-openai-agents-sdk-multiagent/e2e-chatbot-app-next/client/src/components/elements/prompt-input.tsx new file mode 100644 index 00000000..36a2ee60 --- /dev/null +++ b/agent-openai-agents-sdk-multiagent/e2e-chatbot-app-next/client/src/components/elements/prompt-input.tsx @@ -0,0 +1,159 @@ +import { Button } from '@/components/ui/button'; +import { Textarea } from '@/components/ui/textarea'; +import { cn } from '@/lib/utils'; +import type { ChatStatus } from 'ai'; +import { Loader2Icon, SendIcon, SquareIcon, XIcon } from 'lucide-react'; +import { + forwardRef, + type ComponentProps, + type HTMLAttributes, + type KeyboardEventHandler, +} from 'react'; +import { DbIcon } from '../ui/db-icon'; +import { ChevronDownIcon, ChevronUpIcon } from '../icons'; + +type PromptInputProps = HTMLAttributes; + +export const PromptInput = ({ className, ...props }: PromptInputProps) => ( +
    +); + +type PromptInputTextareaProps = ComponentProps & { + minHeight?: number; + maxHeight?: number; + disableAutoResize?: boolean; + resizeOnNewLinesOnly?: boolean; +}; + +export const PromptInputTextarea = forwardRef< + HTMLTextAreaElement, + PromptInputTextareaProps +>( + ( + { + onChange, + className, + placeholder = 'What would you like to know?', + minHeight = 48, + maxHeight = 164, + disableAutoResize = false, + resizeOnNewLinesOnly = false, + ...props + }, + ref, + ) => { + const handleKeyDown: KeyboardEventHandler = (e) => { + if (e.key === 'Enter') { + // Don't submit if IME composition is in progress + if (e.nativeEvent.isComposing) { + return; + } + + if (e.shiftKey) { + // Allow newline + return; + } + + // Submit on Enter (without Shift) + e.preventDefault(); + const form = e.currentTarget.form; + if (form) { + form.requestSubmit(); + } + } + }; + + return ( +