# UDiTH Companion Overview

UDiTH Companion is an AI-assistant add-on for the UDiTH viewer. It's source code is available on request, source usage limitations might apply. A .NET backend hosts the LLM conversation and tool-calling loop; a React frontend runs *inside* UDiTH and executes the resulting tool calls against the browser-hosted local API (`@caxperts/universal.api`) — e.g. querying/filtering 3D objects, P&ID data, sketches, layouts, and reports. The backend can also expose the running UDiTH session to **external MCP clients** (VS Code, Claude Desktop, Cursor) over streamable HTTP, routing their tool calls through the connected frontend.

## Architecture

```
┌──────────────┐   SignalR    ┌────────────────────┐   LLM/tools   ┌───────────┐
│ React frontend│◄───────────►│ .NET backend        │◄────────────►│ AI provider│
│ (inside UDiTH)│  /hubs/chat  │ (ASP.NET Core)      │              └───────────┘
│  = the bridge │              │  ChatHub, sessions  │
└──────┬───────┘              │  MCP server         │◄─── streamable HTTP ─── external
       │ local API            └────────────────────┘        /mcp/{bridgeId}   MCP clients
       ▼                                                     (VS Code, Claude, Cursor)
  UDiTH 3D/PID scene
```


## Backend

- SignalR configured with a 10 MB max message size (tool results can be large).
- `ToolDispatcher` routes AI tool calls to the owning frontend connection and awaits the result (default 60s timeout).
- AI provider is pluggable via `AiProviderOptions`: GitHub Copilot (via `CopilotClient`), or an `IChatClient` built by `ChatClientFactory` for OpenAI-compatible/Azure OpenAI/Azure AI Inference/Ollama/Anthropic/Google.
- `SessionStore` holds one AI session per SignalR connection plus cumulative session cost.
- **MCP support** (`BridgeRegistry` + `McpToolBridge`): modes are Passthrough (relay directly to the frontend), Agent (server-side LLM tool loop, tool name `ask_udith`, max 10 iterations, 60-message history cap per bridge/session), or Both.
- CORS is permissive in Development only (any origin/header/method + credentials), needed for `file://`/localhost SignalR connections.
- `/api/models` proxies the model list for OpenAI-compatible providers (frontend model picker); `MapFallbackToFile("index.html")` serves the SPA.
- Binds to `localhost` when `RunningLocally=true`, otherwise `0.0.0.0` (Docker); port from `Port` config (default 5000).

### Key files
| Path | Purpose |
|---|---|
| [Hubs/ChatHub.cs](CopilotChat.Server/Hubs/ChatHub.cs) | SignalR hub: `RegisterTools`, `SendMessage`, `SetModel`, `ToolCallResult` → `StatusUpdate`, `ChatDelta`, `ChatMessage`, `ChatDone`, `ToolCallRequest` |
| [Services/SessionStore.cs](CopilotChat.Server/Services) | Per-connection session registry + cost tracking |
| [Services/ToolDispatcher.cs](CopilotChat.Server/Services) | Forwards tool calls to the owning frontend |
| [Services/BridgeRegistry.cs](CopilotChat.Server/Services) | Active frontend bridges for MCP pairing |
| [Services/McpToolBridge.cs](CopilotChat.Server/Services) | MCP `tools/list` / `tools/call` handling (Passthrough/Agent) |
| [Services/ChatClientFactory.cs](CopilotChat.Server/Services) | Builds provider-specific `IChatClient` |
| [Services/PromptCachingPolicy.cs](CopilotChat.Server/Services) | Anthropic ephemeral prompt-caching for OpenRouter requests |

## Frontend (`frontend/src`)

React 18 + MUI + `@microsoft/signalr` + `@caxperts/universal.api` SPA. This is a chat/assistant UI embedded in UDiTH, not a general API browser.

- `App.tsx` / `Content.tsx` — layout: chat header, message list, input, settings dialog, or the MCP passthrough view.
- `hooks/useChatHub.ts` — SignalR client wrapper (connections, messages, models, tools, MCP bridge state); supports reverse-proxy deployments.
- `components/McpPassthroughView.tsx` — shown when the server runs headless in MCP Passthrough mode; renders bridge pairing info and copy-to-clipboard actions.
- `tools/` — tool definitions executed against UDiTH's local API: `tools.ts` (3D object queries), `pidTools.ts`, `sketchTools.ts`, `layoutTools.ts`, `reportTools.ts`.
- `flavours/` — assistant persona/configuration variants: `federatedData.ts`, `navigationEnhancer.ts`, `topology.ts`.
- `workflowDefaults.ts` / `workflowHandlers.ts` — guided multi-step AI workflows (accept/proceed/refine steps).

## Configuration (`CopilotChat.Server/appsettings.json`)

- `RunningLocally` — controls bind host and local-only behavior.
- `AiProvider.Provider` + per-provider section: `Copilot`, `OpenAI`, `AzureOpenAI`, `AzureAIInference`, `Ollama`, `Anthropic`, `Google`. All overridable via env vars, e.g. `AiProvider__OpenAI__ApiKey`.
- `Mcp.Enabled`, `Mcp.Mode` (`Passthrough`/`Agent`/`Both`), `Mcp.AgentToolName` (default `ask_udith`), `Mcp.ToolTimeoutSeconds`.
- `Logging.LogLevel`, `Port` (env var, default 5000).

## Required environment variables

Config binds through the standard ASP.NET Core double-underscore convention (`Section__Key`), so any `appsettings.json` value can be overridden this way. None are strictly mandatory to *boot* the app (each has a code default or an empty-string fallback), but the following are needed for a working deployment:

| Variable | Required when | Notes |
|---|---|---|
| `Port` | Always (Docker) | Must match the container's exposed/mapped port. Defaults to `5000` if unset. |
| `RunningLocally` | Always (Docker) | Set to `false` in containers so the server binds `0.0.0.0` instead of `localhost`. |
| `AiProvider__Provider` | Always | Selects the active provider: `Copilot`, `OpenAI`, `AzureOpenAI`, `AzureAIInference`, `Ollama`, `Anthropic`, `Google`. |
| `AiProvider__OpenAI__Endpoint`, `AiProvider__OpenAI__ApiKey`, `AiProvider__OpenAI__Model` | `Provider=OpenAI` | Also used for any OpenAI-compatible endpoint (OpenRouter, local llama.cpp/vLLM, Google's OpenAI-compatible endpoint). `ApiKey` can be any placeholder for keyless local servers. |
| `AiProvider__OpenAI__EnablePromptCaching` | Optional, `Provider=OpenAI` | Injects Anthropic ephemeral `cache_control` when routing Claude models through OpenRouter. |
| `AiProvider__AzureOpenAI__Endpoint`, `AiProvider__AzureOpenAI__ApiKey`, `AiProvider__AzureOpenAI__DeploymentName` | `Provider=AzureOpenAI` | |
| `AiProvider__AzureAIInference__Endpoint`, `AiProvider__AzureAIInference__ApiKey`, `AiProvider__AzureAIInference__Model` | `Provider=AzureAIInference` | |
| `AiProvider__Ollama__Endpoint`, `AiProvider__Ollama__Model` | `Provider=Ollama` | No API key needed. |
| `AiProvider__Anthropic__ApiKey`, `AiProvider__Anthropic__Model` | `Provider=Anthropic` | |
| `AiProvider__Google__Endpoint`, `AiProvider__Google__ApiKey`, `AiProvider__Google__Model` | `Provider=Google` | |
| `AiProvider__Copilot__Model` | `Provider=Copilot` | Requires the GitHub Copilot CLI to be authenticated in the container/host. |
| `Mcp__Enabled` | Optional | Set `true` to expose `/mcp/{bridgeId}` to external MCP clients. |
| `Mcp__Mode` | If `Mcp__Enabled=true` | `Passthrough`, `Agent`, or `Both`. |
| `Mcp__AgentToolName` | Optional | Tool name exposed in Agent mode (default `ask_udith`). |
| `Mcp__ToolTimeoutSeconds` | Optional | Timeout for a dispatched tool call (default 60s). |

Un-set provider sections are harmless as long as they're not the selected `Provider` — the factory only reads the section matching `AiProvider__Provider`.

## Deployment

- **Dockerfile**: two-stage build — SDK stage restores npm/dotnet deps and runs `dotnet publish` (which triggers the frontend build); runtime stage (`aspnet:10.0`) exposes port 5000, entrypoint `dotnet CopilotChat.Server.dll`.
- **docker-compose.yml**: image `quay.io/caxperts/companion:2026.3.2`, port `5000:5000`, `RunningLocally=false`, MCP enabled with `Mode=Both` and a 60s tool timeout.

## Recent development focus

Multi-bridge MCP support, headless MCP Passthrough mode, reverse-proxy compatibility for `useChatHub.ts`, Google AI provider, cost accumulation/display across sessions, guided AI workflows, and a tool-traffic visibility flyout for local debugging.

*Note: The "API-Explorer" (`https://api-explorer.udith.io/`) can be helpful when extending the companion.*
