From 060b846d9b038840c3b158e863192ac6223f493a Mon Sep 17 00:00:00 2001 From: Rene Fichtmueller Date: Fri, 1 May 2026 10:17:21 +0200 Subject: [PATCH] feat: publish llm gateway v2 dashboard alongside restored workbench --- AI_CONTROL_PLANE_SYSTEM_DESIGN.md | 426 +++ OPEN_SOURCE_BLUEPRINT.md | 1270 +++++++ OPEN_SOURCE_FEATURE_MATRIX.md | 66 + OPEN_SOURCE_GAP_ANALYSIS.md | 133 + OPEN_SOURCE_IMPLEMENTATION_ROADMAP.md | 212 ++ packages/gateway/public/dashboard-v2.html | 728 ++++ packages/gateway/public/dashboard.html | 3489 +++++++++++++++---- packages/gateway/src/routes/dashboard.ts | 338 +- packages/gateway/src/routes/health.ts | 3 +- packages/gateway/src/security/tls-config.ts | 4 +- packages/gateway/src/server.ts | 1 + 11 files changed, 6006 insertions(+), 664 deletions(-) create mode 100644 AI_CONTROL_PLANE_SYSTEM_DESIGN.md create mode 100644 OPEN_SOURCE_BLUEPRINT.md create mode 100644 OPEN_SOURCE_FEATURE_MATRIX.md create mode 100644 OPEN_SOURCE_GAP_ANALYSIS.md create mode 100644 OPEN_SOURCE_IMPLEMENTATION_ROADMAP.md create mode 100644 packages/gateway/public/dashboard-v2.html diff --git a/AI_CONTROL_PLANE_SYSTEM_DESIGN.md b/AI_CONTROL_PLANE_SYSTEM_DESIGN.md new file mode 100644 index 0000000..9be7898 --- /dev/null +++ b/AI_CONTROL_PLANE_SYSTEM_DESIGN.md @@ -0,0 +1,426 @@ +# AI Control Plane System Design + +## 1. Purpose + +LLM Gateway is a deterministic, observable, policy-driven routing layer for AI execution with memory and cost control. + +It routes requests from clients to the right model, provider, agent, or tool based on: + +- policy +- cost +- availability +- context +- memory +- trust level +- historical route success + +It also provides: + +- full observability through immutable receipts +- reproducible AI runs +- shared memory persistence +- route memory +- token and cost optimization + +## 2. High-Level Architecture + +```text +Input Layer + clients, APIs, MCP, internal connectors + | + v +Control Plane + trust routing, policy, compression, memory, provider routing + | + v +Execution Layer + local models, external providers, tools, services + | + v +Output + response to caller + | + v +Receipts + Memory Update + +Side System: + Memory Layer + global memory, project memory, route memory, semantic cache +``` + +## 3. Components + +### 3.1 Client Entry + +Clients connect via API, MCP, OpenAI-compatible endpoints, or internal connectors. + +Supported client targets: + +- Codex +- Claude Code +- ChatGPT +- Cursor +- VS Code and Continue-style IDEs +- automation pipelines +- n8n +- internal services + +Each request should include: + +- payload: prompt, input, files, tool call, or task +- metadata: user, project, agent, task type +- optional routing hints +- optional policy hints + +### 3.2 Trust Router + +The Trust Router is the first decision point. + +Responsibilities: + +- validate client identity +- assign trust level +- classify request type +- classify data sensitivity +- apply initial routing hints +- attach enriched request context + +Example classification labels: + +- code +- infra +- legal +- security +- general +- document +- automation + +Output: + +- enriched request context +- trust score +- sensitivity label +- classification label + +### 3.3 Policy Engine + +The Policy Engine is the core decision system. + +It evaluates: + +- data sensitivity +- allowed providers +- allowed models +- allowed tools +- cost constraints +- project rules +- compliance rules +- offline/simulation/live mode + +Example policies: + +- never send legal data to public APIs +- prefer local models for internal code +- use external models only if confidence is below a threshold +- block requests containing secrets +- require admin override for production deployment tools + +Output: + +- allowed routes +- blocked routes +- required redactions +- execution constraints +- policy decision log + +### 3.4 Memory Query + +Memory is queried before compression and execution. + +Memory sources: + +- project memory +- global memory +- route memory +- semantic cache +- handoffs +- receipts +- reproducible runs + +Output: + +- relevant memory context +- prior decisions +- route hints +- cache candidates + +### 3.5 Compression Engine + +The Compression Engine optimizes request and memory context before execution. + +Functions: + +- token reduction +- context deduplication +- semantic summarization +- cache lookup +- prompt/context packaging +- token budget enforcement + +Input: + +- raw request +- policy constraints +- memory context +- target model context budget + +Output: + +- compressed payload +- token metrics before and after +- cache hit or miss +- compression receipt data + +### 3.6 Provider Router + +The Provider Router makes the final execution decision. + +It selects: + +- local model +- external provider +- AI client/agent +- tool execution +- fallback route + +Criteria: + +- policy constraints +- trust level +- cost +- latency +- availability +- model capability +- route memory +- benchmark results +- agent reputation + +Output: + +- selected execution target +- fallback routes +- route explanation + +### 3.7 Execution Layer + +The Execution Layer handles actual processing. + +Execution target types: + +- local models such as Ollama, LM Studio, LocalAI, llama.cpp, vLLM +- external APIs such as OpenAI, Anthropic, Mistral, Groq, OpenRouter +- AI clients such as Claude Code, Codex, Cursor, ChatGPT adapters +- tools, scripts, workflows, and internal services + +Execution returns: + +- raw response +- latency +- token usage +- provider metadata +- errors +- tool call results + +### 3.8 Receipt Engine + +The Receipt Engine creates an immutable trace for each request. + +Receipts include: + +- request id +- input summary or redacted input +- trust decisions +- policy decisions +- memory refs +- compression results +- selected model/provider/tool +- fallback chain +- response summary or full response depending on policy +- token usage +- cost estimate +- timestamps +- errors +- blocked routes + +Receipts are immutable and stored. + +### 3.9 Memory Layer + +Memory is separate from execution but connected to routing and compression. + +Memory types: + +1. Project memory + - task history + - decisions + - context + - handoffs + +2. Global memory + - shared knowledge + - user/team preferences + - reusable runbooks + +3. Route memory + - routing decisions + - success and failure patterns + - optimization feedback + +4. Semantic cache + - previous responses + - embedding lookup + - prompt/result reuse + +Memory is: + +- append-only by default +- queryable +- versioned where possible +- used during routing and compression + +### 3.10 Route Reflector Memory + +Route Reflector Memory is specialized route memory inspired by BGP route reflectors. + +Functions: + +- learns optimal AI routes +- shares routing knowledge across clients +- improves future routing decisions +- records fallback success and failures +- contributes to Provider Router decisions + +Examples: + +- code debugging works best through Codex plus local validation +- private infra diagnostics should route to local models +- long-form reasoning performs better on selected external models +- JSON extraction for project X has best success on model Y + +## 4. Data Flow + +1. Client sends request. +2. Trust Router classifies request and assigns trust. +3. Policy Engine filters allowed routes. +4. Memory Layer is queried for context and prior route knowledge. +5. Compression Engine optimizes payload. +6. Provider Router selects execution target and fallback chain. +7. Execution Layer processes request. +8. Response is returned to client. +9. Receipt Engine generates immutable receipt. +10. Memory Layer is updated with outcome. +11. Route Reflector Memory updates routing knowledge. + +## 5. Modes Of Operation + +### Live Mode + +- real execution +- full routing active +- receipts stored +- memory updated + +### Simulation Mode + +- no real execution +- shows trust decisions +- shows policy decisions +- shows selected route and fallbacks +- estimates cost and tokens +- useful for testing policies + +### Offline Mode + +- only local models allowed +- no external provider calls +- remote sync disabled unless explicitly allowed +- receipts marked as offline + +## 6. Control Functions + +The system supports: + +- trace request +- replay request +- force route +- override policy as admin +- inspect receipts +- inspect memory +- simulate routing +- compare routes +- inspect provider availability +- inspect route memory + +## 7. Storage + +Required storage components: + +- receipts database: immutable logs +- memory database: structured + vector +- policy definitions +- routing history +- route reflector memory +- semantic cache +- reproducible run artifacts + +Recommended default: + +- SQLite for personal mode +- Postgres plus pgvector for team/server mode +- Git/Gitea as durable memory sync and audit transport + +## 8. Metrics + +System tracks: + +- token usage +- compression ratio +- cache hit rate +- latency per provider +- cost per request +- routing success rate +- fallback rate +- trust level distribution +- blocked route count +- policy override count +- agent reputation +- benchmark scores + +## 9. Security Model + +- strict policy enforcement before external calls +- data classification at entry +- local-first routing possible +- no sensitive data leaves system if blocked by policy +- no secret sync to memory +- audit trail via receipts +- consent ledger for tool, memory, and provider permissions +- safe config writer for external tool setup + +## 10. Extensibility + +The system supports: + +- new providers +- new local models +- new tools +- new MCP resources +- new policy rules +- custom routing logic +- custom memory backends +- custom benchmarks +- custom data source connectors + +## 11. Core Idea + +LLM Gateway is a deterministic, observable, policy-driven routing layer for AI execution with memory and cost control. diff --git a/OPEN_SOURCE_BLUEPRINT.md b/OPEN_SOURCE_BLUEPRINT.md new file mode 100644 index 0000000..d8ab650 --- /dev/null +++ b/OPEN_SOURCE_BLUEPRINT.md @@ -0,0 +1,1270 @@ +# Open Source Blueprint: Adaptive LLM Gateway + +Companion documents: + +- `AI_CONTROL_PLANE_SYSTEM_DESIGN.md` — canonical control-plane architecture +- `OPEN_SOURCE_GAP_ANALYSIS.md` — current gateway vs. OSS target +- `OPEN_SOURCE_FEATURE_MATRIX.md` — feature state and priority +- `OPEN_SOURCE_IMPLEMENTATION_ROADMAP.md` — phase-by-phase build plan + +## Vision + +Turn the Context-X LLM Gateway into an open-source, self-adapting LLM control plane that can run on a user's own machine or server, discover the local AI/dev environment, and expose it through a secure MCP server plus OpenAI-compatible APIs. + +The open-source version should not assume Context-X infrastructure. It should install cleanly, detect what is available, ask before using sensitive integrations, and then wire local models, hosted providers, tools, documents, and developer environments into one gateway. + +## Product Shape + +Working name: **Adaptive LLM Gateway** + +Core promise: + +- Bring your own local or hosted models. +- Run a private MCP server with an optional local LLM. +- Detect common tools and runtimes automatically. +- Expose one unified API for apps, agents, IDEs, and automations. +- Keep secrets and private data local by default. + +## Differentiating Core Modules + +The open-source project should lead with four features that make it more than a model proxy: + +1. **Trust Router** +2. **Context Receipt** +3. **Shared Gitea Memory** +4. **AI Handoff Protocol** + +The second core layer should add learning, accountability, and repeatability: + +5. **Capability Benchmark Lab** +6. **Agent Reputation Score** +7. **Local Consent Ledger** +8. **Reproducible AI Runs** + +The execution pipeline should be: + +```text +Client Entry + -> Trust Router + -> Policy Engine + -> Memory Query + -> Compression Engine + -> Provider Router + -> Execution Layer + -> Receipt Engine + -> Memory Update + -> Route Reflector Memory +``` + +Together they create a trusted coordination layer for all AI clients and agents on a user's system. + +```text +Request + | + v +Trust Router + - validate client identity + - assign trust level + - classify request type and sensitivity + | + v +Policy Engine + - enforce provider/model/tool permissions + - apply cost, compliance, and project rules + | + v +Context Builder + - memory + - files + - retrieved docs + - compressed history + | + v +LLM / Agent / MCP Tool + | + v +Context Receipt + Shared Memory Update + Route Reflector Learning +``` + +## Trust Router + +The Trust Router decides which model, provider, agent, and tool chain may handle a request. + +It should classify every request by: + +- data sensitivity +- task type +- required capabilities +- allowed tools +- user/team policy +- cost and latency budget +- local model availability + +Suggested trust levels: + +| Trust Level | Meaning | Allowed Routing | +|---|---|---| +| `public` | Safe public/non-sensitive content | Any enabled provider | +| `internal` | Project context, private notes, normal code | Local or approved providers | +| `confidential` | Customer data, private business data, security findings | Local-only or explicitly trusted provider | +| `secret` | API keys, credentials, tokens, private keys | Block, redact, or local security scanner only | + +Policy example: + +```yaml +trust_router: + default_mode: hybrid-safe + rules: + - match: + contains_secret: true + action: block + - match: + sensitivity: confidential + route: local-only + - match: + task_type: code_generation + sensitivity: internal + route: [claude-code, codex, local-code-model] + - match: + task_type: brainstorming + sensitivity: public + route: [openai, anthropic, local] +``` + +The Trust Router should always explain its decision internally and optionally expose it to users. + +## Policy Engine + +The Policy Engine evaluates what is allowed after the Trust Router has classified the request. + +It should evaluate: + +- allowed providers +- allowed models +- allowed tools +- data sensitivity +- project policy +- compliance rules +- cost limits +- offline/simulation/live mode + +Example policies: + +- never send legal data to public APIs +- prefer local models for internal code +- use external models only if confidence is below a threshold +- block requests containing secrets +- require admin override for production deployment tools + +The output is a route constraint set: + +```yaml +allowed_routes: [ollama, claude-code] +blocked_routes: + - provider: openai + reason: confidential data policy +required_redactions: [] +max_request_cost_usd: 0.10 +mode: live +``` + +## Provider Router + +The Provider Router makes the final execution decision after policy and compression. + +It chooses: + +- local model +- external provider +- AI agent/client +- MCP tool +- fallback chain + +Inputs: + +- policy constraints +- model availability +- provider health +- latency +- cost +- benchmark scores +- agent reputation +- Route Reflector Memory + +The Provider Router should support live, simulation, and offline modes. + +## Context Receipt + +Every answer should be able to produce a receipt that shows what context was used and what was protected. + +Example: + +```yaml +receipt_id: ctxr_2026_05_01_001 +request_id: req_abc123 +model: qwen2.5:14b +provider: ollama +trust_level: internal +route_reason: + - local model selected because project memory was private + - external providers skipped by policy +context_used: + - type: memory + ref: projects/adaptive-llm-gateway/PROJECT.md + - type: file + ref: OPEN_SOURCE_BLUEPRINT.md + - type: retrieval + ref: memory/decisions/2026-05-01-gitea-memory.md +context_blocked: + - type: file + ref: .env + reason: secret pattern + - type: provider + ref: openai + reason: confidential policy +tokens: + input: 4200 + output: 900 + compressed_from: 13200 +cost: + estimated_usd: 0 +``` + +Receipts can be stored locally, pushed to shared memory, or attached to audit logs. + +## AI Handoff Protocol + +Define a simple handoff format so Claude Code, Codex, ChatGPT, Cursor, n8n, and other agents can pass work to each other without losing context. + +Handoff files should be plain Markdown with YAML frontmatter or pure YAML/JSON. + +Example: + +```yaml +handoff_version: 1 +id: handoff_2026_05_01_001 +project: adaptive-llm-gateway +from_agent: claude-code +to_agent: codex +created_at: 2026-05-01T12:00:00Z +status: ready +goal: Implement MCP memory tools. +current_state: + summary: Blueprint exists. Need package scaffold and safe tool definitions. + branch: main + files_changed: + - OPEN_SOURCE_BLUEPRINT.md +constraints: + - Do not expose shell tools by default. + - Do not sync secrets. +next_actions: + - Create packages/mcp-server. + - Add memory.search and memory.write tools. + - Add tests for policy enforcement. +context_refs: + - memory/projects/adaptive-llm-gateway/PROJECT.md + - memory/decisions/2026-05-01-shared-gitea-memory.md +open_questions: + - Should SQLite be mandatory for personal mode? +confidence: 0.82 +``` + +Recommended folders: + +```text +memory/projects//handoffs/ +memory/agents//sessions/ +memory/decisions/ +``` + +The protocol should be append-first and easy for humans to read. + +## Capability Benchmark Lab + +The gateway should benchmark every detected model, provider, and major agent integration before trusting it for routing. + +Benchmarks should be local, transparent, and repeatable. + +Test dimensions: + +- JSON/schema reliability +- code generation +- code patch quality +- instruction following +- German/English quality +- summarization +- tool-call readiness +- latency +- cost +- context length behavior +- private-data safety +- refusal/guardrail behavior + +Example benchmark result: + +```yaml +model: qwen2.5:14b +provider: ollama +benchmarked_at: 2026-05-01T12:00:00Z +scores: + json_schema: 0.84 + code_generation: 0.71 + german: 0.88 + summarization: 0.91 + latency: 0.76 + privacy: 1.00 +recommended_for: + - private_summarization + - german_drafts + - internal_qa +not_recommended_for: + - complex_code_patch +``` + +The Trust Router should use benchmark results instead of static assumptions. + +## Agent Reputation Score + +Track how well each connected AI client or agent performs on real tasks. + +Agents can include: + +- Codex +- Claude Code +- ChatGPT +- Cursor +- VS Code assistants +- n8n workflows +- local autonomous agents + +Metrics: + +- task success rate +- test pass rate +- human approval rate +- rollback rate +- average latency +- average token/cost usage +- policy violation count +- handoff quality +- reproducibility score + +Example: + +```yaml +agent: codex +period: 30d +score: 0.91 +strengths: + - code_patches + - test_fixes + - small_refactors +weaknesses: + - broad_product_strategy +metrics: + test_pass_rate: 0.94 + rollback_rate: 0.03 + avg_handoff_quality: 0.87 +``` + +Agent scores should guide routing: + +- send code patches to agents with high patch/test scores +- send long analysis to agents with high synthesis scores +- keep private tasks with local agents/models when policy requires it + +## Local Consent Ledger + +Store user permissions as an auditable local ledger. + +The consent ledger answers: + +- Which agents can read which memory? +- Which agents can write memory? +- Which tools can be called? +- Which folders can be indexed? +- Which providers can receive which trust levels? +- Which actions require confirmation? + +Example: + +```yaml +consent_version: 1 +updated_at: 2026-05-01T12:00:00Z +agents: + codex: + memory: + read: [project, decisions, runbooks] + write: [sessions, handoffs, tasks] + tools: + allowed: [repo.search, memory.write, tests.run] + confirm: [git.push, file.delete] + denied: [secrets.read, deploy.production] + providers: + public_llm_allowed: false + claude-code: + memory: + read: [project, decisions, architecture] + write: [sessions, decisions] + tools: + allowed: [repo.search, memory.write] + confirm: [file.write] +``` + +Consent changes should be append-only: + +```text +memory/consent/ledger.jsonl +``` + +The gateway may generate config snippets from consent, but it should ask before editing external tool settings. + +## Reproducible AI Runs + +Every important AI run should be replayable. + +Store: + +- request id +- agent id +- model/provider +- prompt template version +- context receipt +- trust policy version +- memory refs +- retrieval refs +- tool calls +- redaction decisions +- output +- human feedback + +Example run folder: + +```text +memory/runs/2026/05/01/req_abc123/ + request.yaml + context-receipt.yaml + prompt.md + output.md + toolcalls.jsonl + feedback.yaml +``` + +Replay modes: + +- `exact`: same context refs and same model/provider where possible +- `compare`: same input against several models +- `policy-replay`: rerun trust routing with a newer policy +- `compression-replay`: test different compression settings + +This makes the gateway debuggable, auditable, and useful for evaluation. + +## Visual Topology Map + +The UI should include a live topology view of the user's AI infrastructure. + +It should show: + +- detected AI clients +- active MCP servers +- local model runtimes +- hosted providers +- memory backend +- vector index +- enabled tools +- blocked or disabled integrations +- routing paths +- cost-producing paths + +Example: + +```text +Claude Code ── MCP ─┐ +Codex ─────── LSP ──┼── Adaptive LLM Gateway ── Trust Router ── Ollama +Cursor ───── OpenAI ┘ │ │ + │ ├── OpenAI (public only) + │ └── Anthropic (approved) + │ + ├── Shared Memory ── Gitea + └── Knowledge Index ── SQLite/Qdrant +``` + +Each node should expose status, permissions, latency, cost, and recent receipts. + +## Setup Doctor + +Add a diagnostic command: + +```bash +adaptive-llm-gateway doctor +``` + +Checks: + +- gateway health +- MCP server health +- Ollama/LM Studio/vLLM/LocalAI availability +- hosted provider credentials +- Gitea sync status +- vector index health +- database migrations +- port conflicts +- Docker status +- Claude Code/Codex/Cursor/VS Code integration status +- policy and consent ledger validity + +The doctor should produce direct fix suggestions: + +```text +Issue: Ollama detected but no models installed. +Fix: ollama pull qwen2.5:7b + +Issue: Claude Code detected but MCP config not installed. +Fix: adaptive-llm-gateway integrate claude-code --write-config +``` + +## AI Cost Governor + +The gateway should actively control cost, not only report it. + +Features: + +- daily/weekly/monthly budgets +- per-provider budgets +- per-agent budgets +- per-project budgets +- max-cost-per-request +- auto-fallback from paid to local models +- warnings before expensive runs +- hard stop when budget is exhausted + +Example: + +```yaml +cost_governor: + weekly_budget_usd: 25 + max_request_usd: 0.25 + agents: + codex: + weekly_budget_usd: 5 + chatgpt: + weekly_budget_usd: 10 + fallback_when_budget_low: local-only +``` + +## Offline Mode + +Provide a strict local-only mode: + +```bash +adaptive-llm-gateway mode offline +``` + +Offline mode: + +- disables hosted providers +- disables external telemetry +- routes only to local models +- uses local memory only +- blocks remote sync unless explicitly allowed +- marks receipts as `offline_mode: true` + +This is important for security work, customer data, travel, and privacy-focused users. + +## Integration Marketplace + +Add a local integration catalog, not a SaaS marketplace. + +Examples: + +- Claude Code integration +- Codex integration +- Cursor integration +- VS Code integration +- Continue.dev integration +- ChatGPT export importer +- GitHub Copilot bridge +- n8n workflow pack +- Gitea memory backend +- GitHub memory backend +- Obsidian connector +- Open WebUI connector +- Home Assistant connector +- Slack/Teams connector +- Jira/Linear/GitHub Issues connector + +Each integration should declare: + +- permissions required +- tools exposed +- data read/write scope +- setup method +- config files touched +- risk level +- rollback instructions + +## Data Source Connectors + +Support user-approved knowledge sources: + +- local folders +- Git repos +- Obsidian vaults +- Markdown notes +- PDFs +- browser bookmarks +- ChatGPT exports +- Claude/Codex handoffs +- Notion +- Google Drive +- OneDrive +- email +- calendar +- tickets/issues +- logs +- databases + +All connectors must use explicit scope and consent. + +## Team Mode + +Team mode should support small organizations without requiring cloud SaaS. + +Features: + +- shared Gitea memory +- shared provider configuration +- per-user budgets +- per-project policies +- role-based permissions +- audit logs +- admin dashboard +- project onboarding +- policy templates +- team-wide benchmark results + +Suggested roles: + +- owner +- admin +- developer +- analyst +- viewer + +## Prompt and Agent Versioning + +Version everything that changes AI behavior: + +- prompts +- prompt packs +- routing rules +- policies +- consent ledger changes +- agent profiles +- benchmark suites +- benchmark results +- eval datasets +- compression strategies + +Store versions in Git/Gitea where possible. + +## Safe Config Writer + +The gateway should be able to configure other tools, but only through reviewable diffs. + +Flow: + +```text +1. Detect target config. +2. Generate proposed diff. +3. Explain impact. +4. Ask user approval. +5. Write config. +6. Store receipt and rollback entry. +``` + +Example: + +```diff ++ "mcpServers": { ++ "adaptive-llm-gateway": { ++ "command": "adaptive-llm-gateway-mcp", ++ "args": ["--config", "~/.adaptive-llm-gateway/config.yaml"] ++ } ++ } +``` + +## Migration and Import Wizard + +Help users consolidate existing AI chaos: + +```bash +adaptive-llm-gateway import +``` + +Import targets: + +- existing `.env` provider keys +- Ollama model list +- Open WebUI config +- LM Studio local server settings +- ChatGPT exports +- Claude Code handoffs +- Codex session notes +- existing project READMEs/docs +- n8n workflows +- previous vector indexes where supported + +The import wizard should never move or delete original data. It creates normalized memory entries, config snippets, and receipts. + +## UI Direction + +The open-source UI can inherit the spirit of the current LLM Gateway dashboard, but it should be productized into a neutral, reusable interface. + +Keep from the current gateway: + +- operational dashboard feel +- live health/status cards +- request/cost/token visibility +- provider and fallback visibility +- logs/metrics orientation +- dashboard as first screen, not a marketing page + +Improve for OSS: + +- first-run setup wizard +- topology map as the home view +- integration catalog +- trust policy editor +- memory browser +- context receipts viewer +- consent ledger viewer +- benchmark lab +- team/admin mode + +Recommended main navigation: + +```text +Topology +Models +Agents +Memory +Policies +Receipts +Benchmarks +Costs +Integrations +Doctor +Settings +``` + +Visual style: + +- dense, operational, and scannable +- dark/light mode +- no marketing hero as the app entry +- no Context-X-specific branding in OSS defaults +- optional theme pack for Context-X/internal deployments + + + +## Target Users + +- Developers running Ollama, LM Studio, Open WebUI, Claude Code, Codex, Cursor, VS Code, n8n, or custom agents. +- Small teams that want one internal AI gateway instead of scattered API keys. +- Homelab and self-hosting users who want MCP tools, local models, and remote fallback models in one stack. +- Security-conscious teams that want audit logs, budgets, routing rules, and local-first behavior. + +## Open Source Boundary + +The OSS release should remove or isolate Context-X-specific assumptions: + +- Hardcoded domains such as `context-x.org`, `fichtmueller.org`, and Erik host paths. +- Private project templates for TIP, MAGATAMA, SwitchBlade, PeerCortex, etc. +- Private credentials, server names, and internal service assumptions. +- Context-X-specific training data unless explicitly sanitized and licensed. + +Keep as generic features: + +- Fastify gateway service. +- TypeScript client. +- Health checks. +- Provider routing. +- OpenAI-compatible adapter. +- MCP server. +- Local model discovery. +- Audit logging. +- Cost and token tracking. +- Prompt template system. +- Optional learning engine. + +## Adaptive System Discovery + +Add a first-run discovery command: + +```bash +npx adaptive-llm-gateway init +``` + +It should detect: + +- OS: macOS, Linux, Windows/WSL. +- Runtime: Node.js, Python, Docker, Docker Compose, pnpm/npm/yarn. +- Local LLM servers: + - Ollama on `localhost:11434` + - LM Studio on `localhost:1234` + - LocalAI + - Open WebUI + - llama.cpp server +- Hosted provider credentials from environment only after consent: + - OpenAI + - Anthropic + - Mistral + - Groq + - Cerebras + - OpenRouter + - Cloudflare Workers AI +- Developer tools: + - VS Code + - Cursor + - Claude Code + - Codex CLI/Desktop + - GitHub Copilot + - n8n + - Git remotes and local repos +- Local knowledge sources: + - selected folders + - docs + - markdown notes + - code repositories + - optional browser/exported bookmarks + +Discovery must produce a local config file, not silently mutate user systems: + +```yaml +gateway: + port: 3103 + mode: local-first + +models: + local: + ollama: + detected: true + url: http://localhost:11434 + models: [] + +providers: + openai: + enabled: false + env_key: OPENAI_API_KEY + +mcp: + enabled: true + port: 3104 + +tools: + filesystem: + enabled: false + allowed_roots: [] + git: + enabled: true + shell: + enabled: false +``` + +## AI Client and Agent Detection + +The gateway should detect AI clients and agent runtimes as integration targets, but it should treat each one differently depending on what is technically and legally possible. + +Detection is not the same as control. Some tools expose APIs, config files, MCP settings, or proxy configuration. Others are closed consumer apps where the safe integration path is an adapter, browser extension, exported data import, or a documented manual setup step. + +### Integration Levels + +Use four integration levels: + +| Level | Meaning | Example | +|---|---|---| +| `detected` | Tool exists, but no automatic binding yet | ChatGPT desktop app installed | +| `configurable` | Gateway can write or suggest config | Claude Code MCP config | +| `proxyable` | Tool can point to OpenAI-compatible gateway URL | OpenAI SDK, Continue, many IDE plugins | +| `native` | Gateway has a dedicated adapter/package | Codex LSP adapter, Claude Code bridge | + +### Tool Matrix + +| Tool | Detect | Best Integration Path | Notes | +|---|---|---|---| +| Codex CLI/Desktop | CLI path, config folder, running process | MCP server, LSP adapter, OpenAI-compatible endpoint | Provide `codex-lsp-adapter` and MCP setup instructions. | +| Claude Code | CLI path, MCP/config files, shell env | MCP server + Claude Code bridge | Best path is first-class MCP tools/resources. | +| ChatGPT Desktop/Web | App/process/browser profile, exported chats | OpenAI-compatible adapter where supported, browser extension, import/export | Do not scrape private chats silently. Ask before importing exports. | +| OpenAI SDK users | Env vars, package manifests, code search | Replace `baseURL` with gateway URL | Very easy and safe to automate per repo. | +| Cursor | App/config detection | MCP server, OpenAI-compatible proxy if configured | Needs explicit user approval before editing settings. | +| VS Code | Extensions + settings.json | MCP/LSP adapter, Continue/Copilot-compatible config | Offer snippets instead of blind mutation. | +| GitHub Copilot | gh auth, extension, copilot bridge | copilot-bridge where available | Subscription/auth belongs to user; gateway should not extract tokens. | +| Continue.dev | config files | OpenAI-compatible endpoint | Good OSS integration target. | +| Open WebUI | local port/container detection | Register gateway as provider or upstream | Can also use Open WebUI as discovered model frontend. | +| n8n | local port/container/env | HTTP node templates + credentials guidance | Detect workflows only with allowed path/API access. | +| LangChain/LlamaIndex apps | package manifests/code search | Generated integration patch | Per-project opt-in. | + +### Detection Sources + +Safe discovery sources: + +- process list +- common install paths +- package manifests +- shell PATH +- Docker containers +- local ports +- explicit config directories +- user-selected project folders + +Sensitive sources that require consent: + +- browser profiles +- chat exports +- API keys +- IDE settings writes +- MCP config writes +- repo-wide code modifications +- shell command execution tools + +### Binding Strategy + +The first-run wizard should present findings like this: + +```text +Detected AI tools: + +✓ Claude Code CLI + Integration: MCP server + Action: add Adaptive LLM Gateway MCP config + +✓ Codex + Integration: MCP + LSP adapter + Action: generate config snippet + +✓ ChatGPT desktop + Integration: detected only + Action: optional import of exported chats, optional browser extension + +✓ Cursor + Integration: MCP/OpenAI-compatible endpoint + Action: generate settings snippet + +Enable integrations now? [select] +``` + +Default behavior should be conservative: + +- Generate config snippets first. +- Ask before writing settings. +- Ask before indexing chat exports or repo contents. +- Never extract tokens from apps. +- Prefer official APIs, MCP, LSP, or documented config surfaces. + +## MCP Server With Own LLM + +The MCP server should be a first-class package: + +```text +packages/mcp-server +``` + +Responsibilities: + +- Expose tools for gateway completion, model listing, health, routing, embeddings, and document lookup. +- Expose resources for discovered docs/repos when the user allows them. +- Use the gateway's local-first model routing by default. +- Allow a dedicated local model for tool reasoning, for example `qwen2.5:7b` or another detected local model. +- Never expose shell or filesystem tools until the user explicitly enables allowed scopes. + +Suggested MCP tools: + +- `gateway.complete` +- `gateway.chat` +- `gateway.classify` +- `gateway.models` +- `gateway.health` +- `gateway.route_preview` +- `knowledge.search` +- `repo.search` +- `repo.summarize` +- `config.get` +- `config.update` + +## Embedding Everything + +"Embed everything" should mean controlled, user-approved indexing: + +- Scan allowed roots only. +- Chunk and embed text/code/docs. +- Store embeddings locally by default. +- Support SQLite + sqlite-vec for simple installs. +- Support Postgres + pgvector for team/server installs. +- Optional Qdrant for larger deployments. + +Default modes: + +- `personal`: SQLite, local-only, one user. +- `team`: Postgres, API keys, audit logging. +- `server`: Docker Compose, reverse proxy, persistence, MCP enabled. + +## Shared AI Memory Sync + +Add a shared memory layer for all connected AI clients and agents. The goal is to make Claude Code, Codex, ChatGPT exports, Cursor, IDE assistants, MCP tools, and automation agents work from the same durable project memory instead of each assistant living in an isolated context bubble. + +Working name: **Memory Sync Backend**. + +### Why Git/Gitea + +Git is a strong default backend for portable AI memory: + +- auditable history +- human-readable Markdown/JSON/YAML files +- offline-first local clone +- easy sync across machines +- branchable experiments +- reviewable diffs +- self-hostable with Gitea +- no mandatory SaaS dependency + +Gitea can act as the team/server backend: + +```text +Claude Code ─┐ +Codex ──┼── Adaptive LLM Gateway ── Memory Sync ── Git/Gitea repo +Cursor ──┤ │ +ChatGPT ──┘ └── local vector index for fast retrieval +``` + +### Memory Types + +Store memory in typed folders: + +```text +memory/ + projects/ + my-project/ + PROJECT.md + decisions/ + tasks/ + architecture/ + runbooks/ + sync/ + agents/ + codex/ + claude-code/ + chatgpt/ + cursor/ + facts/ + preferences/ + credentials-notes/ + incidents/ + evals/ +``` + +Use plain files for durable truth and an embedding index for fast lookup. + +### Memory Records + +Each memory entry should include provenance: + +```yaml +id: mem_2026_05_01_001 +type: decision +project: adaptive-llm-gateway +source_agent: codex +created_at: 2026-05-01T12:00:00Z +visibility: team +sensitivity: internal +tags: [mcp, memory, gitea] +summary: Use Gitea-backed memory sync as the shared durable backend. +links: + - file: OPEN_SOURCE_BLUEPRINT.md +``` + +### Sync Modes + +- `local`: file-based memory in `~/.adaptive-llm-gateway/memory`. +- `git`: local Git repo, user pushes manually. +- `gitea`: automatic push/pull to self-hosted Gitea. +- `github`: optional public/private GitHub backend. +- `s3`: optional artifact backup, not source of truth. + +### Agent Integration + +Each agent gets a memory adapter: + +- Claude Code: MCP resources + memory write tools. +- Codex: MCP resources + session handoff writer. +- ChatGPT: import exported chats; optional browser extension later. +- Cursor/VS Code: repo memory + generated context snippets. +- n8n: workflow memory and execution summaries. + +Suggested MCP memory tools: + +- `memory.search` +- `memory.read` +- `memory.write` +- `memory.append_session` +- `memory.summarize_project` +- `memory.record_decision` +- `memory.record_task` +- `memory.sync_status` +- `memory.pull` +- `memory.push` + +### Conflict Handling + +Memory should be append-first. Avoid agents overwriting each other. + +Rules: + +- Session logs are append-only. +- Decisions can supersede earlier decisions but should not delete them. +- Project summaries are regenerated from source logs and committed as derived files. +- Conflicts create review entries instead of automatic destructive merges. + +### Privacy and Safety + +- Never sync secrets. +- Secret-looking values are redacted before commit. +- Sensitive memory can stay local-only. +- Users can mark folders as `private`, `team`, or `public`. +- Chat imports require explicit approval. +- Every memory entry records source agent and timestamp. + +### Gitea Default Layout + +For self-hosted users: + +```text +gitea.example.local/user/ai-memory.git +gitea.example.local/user/project-a.git +gitea.example.local/user/project-b.git +``` + +The gateway can either: + +- use one central `ai-memory` repo, or +- add a `.ai-memory/` folder to each project repo. + +Recommended default: + +- personal mode: one central memory repo +- team mode: one memory repo plus per-project links +- open-source project mode: `.ai-memory/` inside the project + +## Architecture + +```text +User apps / agents / IDEs + | + | OpenAI API / MCP / SDK + v +Adaptive LLM Gateway + - routing + - prompt templates + - confidence gates + - budgets + - audit logs + - local knowledge lookup + | + +--> Local models: Ollama, LM Studio, LocalAI, llama.cpp + +--> Hosted providers: OpenAI, Anthropic, Groq, Mistral, etc. + +--> MCP tools/resources + +--> Local vector store +``` + +## Installation Targets + +Simple local install: + +```bash +npx adaptive-llm-gateway init +npx adaptive-llm-gateway start +``` + +Docker install: + +```bash +docker compose up -d +``` + +Team/server install: + +```bash +npx adaptive-llm-gateway init --mode team +npx adaptive-llm-gateway deploy-config +``` + +## Security Defaults + +- Local-first. +- No secrets in config files. +- Read env vars only after consent. +- No filesystem indexing without allowed roots. +- No shell tool by default. +- No telemetry by default. +- Audit logs redact prompts by default unless user opts in. +- MCP dangerous tools disabled until explicitly enabled. +- Provider API keys remain in env, system keychain, or configured secret backend. + +## Refactor Plan + +Phase 1: Extract Context-X assumptions + +- Move Context-X routing templates into optional example pack. +- Rename packages from `@llm-gateway/*` or prepare a neutral scope. +- Replace hardcoded domains and ports with generated config. +- Add `.env.example` for OSS. + +Phase 2: First-run discovery + +- Add `packages/discovery`. +- Detect local models, runtimes, repos, and common agent tools. +- Generate `gateway.config.yaml`. + +Phase 3: MCP server + +- Add `packages/mcp-server`. +- Expose gateway tools and resources. +- Add local model-backed tool reasoning. + +Phase 4: Embeddings and knowledge + +- Add `packages/knowledge`. +- Support SQLite default and Postgres/Qdrant optional backends. +- Add chunking, indexing, search, and repo/doc ingestion. + +Phase 5: OSS release hardening + +- Secret scan. +- License audit. +- Remove private data. +- Add quickstart docs. +- Add GitHub Actions CI. +- Add Docker Compose starter. + +## Minimum Viable OSS Release + +The first public version should include: + +- Gateway server. +- Client SDK. +- OpenAI-compatible adapter. +- Local Ollama/LM Studio detection. +- MCP server with safe tools. +- SQLite config and audit store. +- Docker Compose. +- One generic prompt template pack. +- Documentation for local, team, and server modes. + +## Name Ideas + +- Adaptive LLM Gateway +- Open LLM Gateway +- LocalMesh Gateway +- ModelRouter +- GatewayKit +- AIDE Gateway diff --git a/OPEN_SOURCE_FEATURE_MATRIX.md b/OPEN_SOURCE_FEATURE_MATRIX.md new file mode 100644 index 0000000..581fcfa --- /dev/null +++ b/OPEN_SOURCE_FEATURE_MATRIX.md @@ -0,0 +1,66 @@ +# Open Source Feature Matrix + +## Legend + +- `ready`: exists and is usable with cleanup +- `partial`: exists but needs extraction/hardening +- `missing`: must be built + +| Feature | Current | OSS Target | Priority | +|---|---|---|---:| +| Fastify gateway | ready | keep | P0 | +| Client SDK | ready | keep + docs | P0 | +| Health checks | ready | keep + doctor | P0 | +| Dashboard | partial | topology-first app | P1 | +| Ollama routing | ready | generic local provider | P0 | +| LM Studio detection | missing | discovery provider | P0 | +| LocalAI/llama.cpp/vLLM detection | missing | discovery provider | P0 | +| Hosted provider registry | partial | provider adapters + consent | P0 | +| OpenAI-compatible API | partial | first-class adapter | P0 | +| MCP server | missing | first-class | P0 | +| Claude Code integration | partial | MCP + bridge | P0 | +| Codex integration | partial | MCP + LSP | P0 | +| ChatGPT integration | missing | exports/import + adapter docs | P1 | +| Cursor/VS Code integration | missing | safe config writer | P1 | +| n8n integration | missing | workflow pack | P1 | +| Trust Router | missing | core | P0 | +| Policy Engine | missing | provider/model/tool constraints | P0 | +| Provider Router | partial | final route + fallback decision | P0 | +| Context Receipt | missing | core | P0 | +| Shared Gitea Memory | missing | core | P0 | +| Route Reflector Memory | missing | routing memory | P0 | +| AI Handoff Protocol | partial | core | P0 | +| Consent Ledger | missing | core | P0 | +| Setup Doctor | missing | CLI + UI | P0 | +| Safe Config Writer | missing | CLI + UI | P0 | +| Offline Mode | missing | policy mode | P0 | +| Simulation Mode | missing | dry-run routing decisions | P0 | +| Compression/token saving | partial | first-class engine | P1 | +| Semantic cache | missing | optional | P1 | +| Capability Benchmark Lab | missing | routing input | P1 | +| Agent Reputation Score | missing | routing input | P1 | +| Reproducible Runs | missing | audit/eval | P1 | +| Integration Marketplace | missing | local catalog | P1 | +| Data connectors | missing | scoped connectors | P1 | +| Team Mode | missing | RBAC/admin | P2 | +| Prompt/agent versioning | partial | Git-backed | P2 | +| Import wizard | missing | guided migration | P2 | + +## Public Positioning + +Do not position this as another LiteLLM clone. + +Positioning: + +> Adaptive LLM Gateway discovers your local and hosted AI stack, connects it through a secure MCP and OpenAI-compatible control plane, and gives every agent shared memory, policy, receipts, compression, and routing. + +Core differentiators: + +- AI environment discovery +- Trust Router +- Context Receipts +- Shared Git/Gitea Memory +- AI Handoff Protocol +- Consent Ledger +- Reproducible AI Runs +- model and agent benchmark learning diff --git a/OPEN_SOURCE_GAP_ANALYSIS.md b/OPEN_SOURCE_GAP_ANALYSIS.md new file mode 100644 index 0000000..cf48c79 --- /dev/null +++ b/OPEN_SOURCE_GAP_ANALYSIS.md @@ -0,0 +1,133 @@ +# Open Source Gap Analysis + +This document maps the current Context-X LLM Gateway to the planned open-source Adaptive LLM Gateway. + +## Current Strengths + +Already present in the repository: + +| Area | Current State | Notes | +|---|---|---| +| Gateway API | Present | Fastify gateway in `packages/gateway`. | +| Completion API | Present | Main route: `/v1/completion`. | +| Classification | Present | `/v1/classify` and pre-classifier pipeline. | +| Batch jobs | Present | `/v1/batch` and PgBoss queue integration. | +| Health checks | Present | `/health`, `/health/live`, `/health/ready`. | +| Metrics | Present | Prometheus metrics and dashboard metrics. | +| Dashboard | Present | Operational dashboard exists in `packages/gateway/public`. | +| Routing rules | Present | YAML routing rules and model tiers. | +| Local model routing | Present | Ollama-based routing and fallback chains. | +| Hosted providers | Partial | External provider registry exists. Needs OSS cleanup and discovery. | +| Cost tracking | Present | Cost analytics, token tracking, cost stream. | +| Compression accounting | Partial | TokenVault/cost hooks exist. Needs first-class compression engine. | +| Learning engine | Present | Learning cycles, model performance tracking, fine-tuner package. | +| Client SDK | Present | `@llm-gateway/client`. | +| OpenAI compatibility | Partial | `chatgpt-api-adapter` and `openai-bridge` exist. Needs clean OSS path. | +| Codex integration | Partial | `packages/codex-lsp-adapter` exists. Needs production hardening. | +| Claude Code integration | Partial | `packages/claude-code-bridge` exists. Needs MCP-first flow. | +| LightRAG/RAG | Present | LightRAG sidecar exists. Needs generic connector story. | +| Handoff sync | Partial | `sync/` handoff folder exists. Needs protocol and tools. | +| Gitea use | Present internally | Needs generic Gitea memory backend. | + +## Missing For Open Source + +These features need to be added or extracted: + +| Feature | Status | Priority | Target Package/Area | +|---|---|---:|---| +| First-run setup wizard | Missing | P0 | `packages/cli`, `packages/discovery` | +| Local AI discovery | Missing | P0 | `packages/discovery` | +| Public provider discovery | Partial | P0 | `packages/discovery`, `packages/providers` | +| AI client detection | Missing | P0 | `packages/discovery` | +| MCP server | Missing | P0 | `packages/mcp-server` | +| Trust Router | Missing | P0 | `packages/trust-router` | +| Consent Ledger | Missing | P0 | `packages/consent-ledger` | +| Shared Gitea Memory | Missing | P0 | `packages/memory-sync` | +| Context Receipt | Missing | P0 | `packages/context-receipts` | +| AI Handoff Protocol | Partial | P0 | `packages/handoff` | +| Safe Config Writer | Missing | P0 | `packages/config-writer` | +| Setup Doctor | Missing | P0 | `packages/doctor` | +| Offline Mode | Missing | P0 | gateway config/policy | +| Capability Benchmark Lab | Missing | P1 | `packages/benchmark-lab` | +| Agent Reputation Score | Missing | P1 | `packages/agent-reputation` | +| Reproducible Runs | Missing | P1 | `packages/run-ledger` | +| Visual Topology Map | Missing | P1 | dashboard UI/API | +| Integration Marketplace | Missing | P1 | `packages/integrations` + UI | +| Data source connectors | Missing | P1 | `packages/connectors` | +| Context Compression Engine | Partial | P1 | `packages/context-compression` | +| Semantic cache | Missing/mentioned | P1 | `packages/cache` | +| Team mode | Missing | P2 | auth/policy/admin UI | +| Prompt/agent versioning | Partial | P2 | memory/git/prompt registry | +| Migration/import wizard | Missing | P2 | `packages/import-wizard` | + +## Context-X Assumptions To Remove + +Before public release, remove or move behind an example profile: + +- hardcoded `context-x.org` domains +- hardcoded `fichtmueller.org` Ollama endpoint +- Erik-specific paths such as `/opt/llm-gateway` +- private project callers and templates as defaults +- internal IP assumptions +- private training data +- private bridge assumptions +- secret-looking examples +- Context-X branding as default OSS UI + +Keep them as: + +```text +examples/profiles/context-x/ +``` + +or as a private deployment overlay. + +## Proposed New Packages + +```text +packages/ + cli/ # init, doctor, integrate, import, mode + discovery/ # detects models, clients, runtimes, providers + mcp-server/ # MCP tools/resources + trust-router/ # sensitivity + policy routing + consent-ledger/ # append-only permissions ledger + memory-sync/ # local/git/gitea memory backend + handoff/ # AI Handoff Protocol schema + helpers + context-receipts/ # receipts and audit artifacts + config-writer/ # safe config diffs and rollback + benchmark-lab/ # model/agent benchmark suite + agent-reputation/ # agent scorecards + run-ledger/ # reproducible AI runs + context-compression/ # compression + token budget manager + integrations/ # integration catalog manifests + connectors/ # data source connectors + import-wizard/ # migration/import helpers +``` + +## MVP Cut + +The first useful OSS release should not try to ship everything. + +MVP must include: + +- CLI with `init`, `doctor`, `start`, `integrate` +- local AI discovery: Ollama + LM Studio + OpenAI-compatible `/v1/models` +- provider env discovery with consent +- MCP server with safe gateway and memory tools +- Trust Router with four trust levels +- Gitea/Git memory backend +- Context Receipts +- AI Handoff Protocol +- Safe Config Writer +- Offline Mode +- basic topology dashboard + +MVP can defer: + +- full benchmark lab +- team RBAC +- all data connectors +- full import wizard +- advanced compression comparisons +- agent reputation automation + diff --git a/OPEN_SOURCE_IMPLEMENTATION_ROADMAP.md b/OPEN_SOURCE_IMPLEMENTATION_ROADMAP.md new file mode 100644 index 0000000..75f787e --- /dev/null +++ b/OPEN_SOURCE_IMPLEMENTATION_ROADMAP.md @@ -0,0 +1,212 @@ +# Open Source Implementation Roadmap + +## Phase 0: Sanitize And Productize + +Goal: make the current codebase safe to publish and understandable outside Context-X. + +Tasks: + +- Add OSS name and package naming decision. +- Move Context-X-only files into `examples/profiles/context-x/`. +- Add `.env.example` without private domains or secrets. +- Replace hardcoded defaults with generated config. +- Add license, contributing guide, security policy, and public README. +- Run secret scan and dependency/license audit. +- Decide which training data can be published. + +Exit criteria: + +- Fresh clone can install without private services. +- No private domains or internal IPs are required for default startup. +- Public README explains local-only setup. + +## Phase 1: Adaptive Init + +Goal: detect the user's AI environment and create config. + +Packages: + +- `packages/cli` +- `packages/discovery` +- `packages/config-writer` + +Commands: + +```bash +adaptive-llm-gateway init +adaptive-llm-gateway doctor +adaptive-llm-gateway integrate +adaptive-llm-gateway mode offline +adaptive-llm-gateway simulate +``` + +Detection targets: + +- Ollama +- LM Studio +- LocalAI +- llama.cpp server +- vLLM +- Open WebUI +- OpenAI-compatible endpoints +- OpenAI/Anthropic/Groq/Mistral/OpenRouter env keys +- Claude Code +- Codex +- Cursor +- VS Code +- Continue.dev +- n8n +- Docker containers +- Git/Gitea availability + +Exit criteria: + +- `init` writes `~/.adaptive-llm-gateway/config.yaml`. +- No external integration is enabled without approval. +- `doctor` reports actionable health and setup status. + +## Phase 2: Trust, Consent, Receipts + +Goal: every request goes through policy and produces an audit artifact. + +Packages: + +- `packages/trust-router` +- `packages/policy-engine` +- `packages/consent-ledger` +- `packages/context-receipts` +- `packages/run-ledger` +- `packages/provider-router` + +Features: + +- four trust levels: public, internal, confidential, secret +- local-only/offline routing mode +- simulation mode with no execution +- provider router route constraints and fallbacks +- append-only consent ledger +- receipt for context used, blocked, redacted, routed +- reproducible run folder + +Exit criteria: + +- External providers are blocked for confidential/secret data by default. +- Receipts can be viewed from CLI and dashboard. +- Consent changes are append-only and reversible. + +## Phase 3: Shared Memory And MCP + +Goal: make the gateway the shared memory and tool layer for all AI clients. + +Packages: + +- `packages/memory-sync` +- `packages/handoff` +- `packages/mcp-server` +- `packages/route-reflector-memory` + +Features: + +- local memory repo +- Git/Gitea sync +- typed memory folders +- MCP tools for memory and gateway calls +- AI Handoff Protocol +- Route Reflector Memory for routing outcomes +- conflict-safe append-first writes + +MCP tools: + +- `gateway.complete` +- `gateway.chat` +- `gateway.health` +- `gateway.route_preview` +- `memory.search` +- `memory.read` +- `memory.write` +- `memory.append_session` +- `memory.record_decision` +- `memory.record_task` +- `memory.pull` +- `memory.push` + +Exit criteria: + +- Claude Code and Codex can access the same memory through MCP. +- Handoffs are stored in Git/Gitea. +- Memory sync refuses to commit secrets. + +## Phase 4: Compression And Knowledge + +Goal: reduce token use and retrieve only the right context. + +Packages: + +- `packages/context-compression` +- `packages/connectors` +- `packages/cache` + +Features: + +- token budget manager +- session compaction +- repo/doc summarization +- memory dedupe +- semantic cache +- SQLite vector default +- Postgres/Qdrant optional +- approved data source connectors + +Exit criteria: + +- Context packages include budget, source refs, and compression stats. +- Receipts show compressed-from and final token counts. +- Indexing requires explicit allowed roots. + +## Phase 5: Benchmarking And Reputation + +Goal: route based on evidence instead of static assumptions. + +Packages: + +- `packages/benchmark-lab` +- `packages/agent-reputation` + +Features: + +- model capability tests +- agent scorecards +- latency/cost/quality tracking +- JSON reliability test +- code patch/test benchmark +- local vs hosted comparison + +Exit criteria: + +- Trust Router can use benchmark scores. +- Dashboard shows model and agent strengths. +- Routing decisions explain benchmark influence. + +## Phase 6: Product UI + +Goal: turn the operational dashboard into a usable OSS app. + +UI areas: + +- Topology +- Models +- Agents +- Memory +- Policies +- Receipts +- Benchmarks +- Costs +- Integrations +- Doctor +- Settings + +Exit criteria: + +- First screen is topology/status. +- User can enable integrations from UI with diff preview. +- User can inspect receipts and memory sync status. diff --git a/packages/gateway/public/dashboard-v2.html b/packages/gateway/public/dashboard-v2.html new file mode 100644 index 0000000..56b6768 --- /dev/null +++ b/packages/gateway/public/dashboard-v2.html @@ -0,0 +1,728 @@ + + + + + + llm.gateway Workbench + + + +
+
+
+ +

llm.gateway

+ / gateway workbench · open source preview +
+ +
+ +
+
+
DB checking
+
Poll live
+
Interval 15s
+
+
Mode auto
+
+ + + +
+
+
gateway coverage
+
existing adapters plus open-source targets
+
+
+
+ +
+
+
gateway metrics
+
traffic · providers · savings · readiness
+
+
+
+ +
+
+
+ request pipeline + gateway core +
+
+
+
+
+
+
+ open-source extensions + roadmap +
+
+
+
+
+
+ +
+
+
recent requests
+
live polling
+
+
+ + + +
+ + + + + + + + + + + + + + + + + + +
request idcallermodelstatusctx beforectx sentsavedcompressioncostlatency
loading gateway traffic
+
+
+ +
connected
+ + + + diff --git a/packages/gateway/public/dashboard.html b/packages/gateway/public/dashboard.html index a1d3502..5edf410 100644 --- a/packages/gateway/public/dashboard.html +++ b/packages/gateway/public/dashboard.html @@ -3,539 +3,1842 @@ - LLM Gateway Dashboard + llm.gateway / workbench + + + -
-
-

LLM Gateway Dashboard

-
-
- - Checking database... -
-
- - Connecting to stream... -
-
- 0 SSE listeners -
-
-
+
-
-
-
Total Requests
-
0
-
+ +
+
+ llm.gateway + gateway workbench · v1.0
- -
-
Success Rate
-
0%
-
-
- -
-
Avg Latency
-
0ms
-
-
- -
-
Total Cost
-
$0.00
-
-
- -
-
Avg Confidence
-
0%
-
-
- -
-
Fallback Usage
-
0%
-
+
+
-

Top Models

-
-
Loading models...
+ +
+
+ + db + connecting +
+
+ + poll + starting +
+
+ interval + 3s +
+
+ mode + auto +
-

Top Callers

-
-
Loading callers...
-
+ + -

Available Providers & Models

-
-
-

Local

-
-
Loading providers...
+ +
+ + +
+ +
+
summoning buddy
+
+ + +
+
total tokens saved · all layers · all-time
+
0tokens
+
+
⚡ Gateway (LLM calls)0
+ +
+
+
+ cost saved + $0.00 +
+
+ cache hits + 0 +
+
+ savings rate + 0% +
+
+
+ + +
+
cost analysis · last 24h · USD
+
+
+
without gateway
+
$0.00
+
+
+
+
with gateway
+
$0.00
+
+
+
you saved $0.00 · 0% reduction
-
-

Subscription

-
-
Loading providers...
+ + +

Savings Sources we measure 5 axes — Lean-CTX measures 1

+
+
loading
+
+ + +

Live Metrics last 24h

+
+
+
requests
+
0
+
routed
+
+
+
success rate
+
0%
+
approved/total
+
+
+
avg latency
+
0ms
+
end-to-end
+
+
+
spent today
+
$0.00
+
actual usd
+
+
+
confidence
+
0/10
+
post-val
+
+
+
fallback usage
+
0%
+
primary→fallback
-
-

Free Tier

-
-
Loading providers...
+ + +
+
+

Activity · last 365 days streak 0d

+
loading activity
+
+
+

Forecast based on recent trend

+
computing forecast
+
+
+ + +
+
+

Live Activity most recent first

+
listening
+
+
+

Top Models last 24h

+
analyzing routing
+ +

Top Callers

+
analyzing callers
+
+
+ + +

Achievements 0/0

+
checking quests
+
+ + +
+
+ + +
+
+
discovering installed subscriptions
+
+
+ + +
+
+
+

Local on-host inference

+
+
enumerating local models
+
+
+
+

Subscription paid plans via bridges

+
+
enumerating subscription providers
+
+
+
+

Free Tier api-key authenticated

+
+
enumerating free-tier endpoints
+
+
+
+
+ + +
+

Desktop AI Coverage only gateway traffic is counted

+
+
checking connected clients
+
+

Recent Requests live polling

+
+ + + +
+
+
+
request id
+
caller
+
model
+
status
+
ctx before
+
ctx sent
+
saved
+
compression
+
cost
+
latency
+
+
+
no requests yet
+
+
+
+ + +
+
+
+
cumulative savings · last 24h
+
$0.00
+
— · — tokens prevented · — cache hits
+
+
+ +
+ $ saved per hour + hit rate — +
+
+
+ +
+
+
cache entries
+
0
+
distinct cached responses
+
+
+
tokens prevented
+
0
+
never sent to LLM
+
+
+
cache hit rate
+
0%
+
hits ÷ total req
+
+
+ +

Top Caching Callers most savings

+
+
loading
+
+ +

Cache Controls manual invalidation

+
+ + + +
+
+ + +
+
+
+ Subscription Pool Wallet — tracks API calls + (not tokens) against each Pro plan's quota window. Numbers here are + messages remaining, not tokens. For token savings via cache, + see the Savings tab. +
+
+
+
loading wallet
+
+
+ + +
+
+ + + + + + +
+
+
enter a caller id and click load
+
+ +

Knowledge Graph all callers + facts

+
+ +
+ caller + fact key + value +
+
+
+ + +
+
+
computing standings
+
+

Race Leaderboard last 7 days

+
loading
+
+ + +
+

Public Share Card embeddable SVG · OG-card sized · no auth required

+ + + + +
+ + +
+

Monthly Report save as PDF via browser print

+ + +
+ + + -

Recent Requests

-
- - - + + -
-
-
Request ID
-
Caller
-
Model
-
Status
-
Tokens In
-
Cost
-
Latency
-
-
-
No requests yet
-
-
-
-
- Connected +
+ + connected
- \ No newline at end of file + diff --git a/packages/gateway/src/routes/dashboard.ts b/packages/gateway/src/routes/dashboard.ts index 40725aa..7840e54 100644 --- a/packages/gateway/src/routes/dashboard.ts +++ b/packages/gateway/src/routes/dashboard.ts @@ -58,6 +58,233 @@ interface AlertData { }; } +interface TopologyNode { + id: string; + label: string; + kind: 'client' | 'gateway' | 'policy' | 'memory' | 'model' | 'provider' | 'tool' | 'planned'; + status: 'online' | 'ready' | 'detected' | 'planned' | 'blocked' | 'offline'; + trust: 'public' | 'internal' | 'confidential' | 'secret' | 'n/a'; + description: string; + metrics?: Record; +} + +interface TopologyEdge { + from: string; + to: string; + label: string; + status: 'active' | 'ready' | 'planned' | 'blocked'; +} + +interface DashboardTopology { + product: string; + tagline: string; + mode: 'online' | 'offline' | 'hybrid-safe'; + generatedAt: string; + summary: { + detectedClients: number; + localModels: number; + providersConfigured: number; + trustPolicies: number; + memoryBackends: number; + plannedModules: number; + }; + nodes: TopologyNode[]; + edges: TopologyEdge[]; + trustLevels: Array<{ level: string; route: string; action: string }>; + receipts: Array<{ id: string; trust: string; route: string; protected: string; tokens: string }>; + roadmap: Array<{ module: string; status: string; priority: string }>; +} + +async function getDashboardTopology(): Promise { + let availableProviders: ReturnType = []; + try { + availableProviders = getAvailableProviders(); + } catch (err) { + logger.warn({ err }, 'Provider discovery failed while building topology'); + } + + const configuredProviders = availableProviders.filter((provider) => { + if (!provider.enabled) return false; + if (provider.name === 'claude-bridge') { + return process.env['CLAUDE_BRIDGE_ENABLED'] === 'true' && Boolean(process.env['CLAUDE_BRIDGE_URL']); + } + if (['claude-code', 'openai-bridge', 'chatgpt-bridge', 'copilot-bridge', 'codex'].includes(provider.name)) { + return Boolean(process.env[provider.envKey]) || Boolean(process.env[`${provider.envKey.replace(/_KEY|_TOKEN/, '')}_URL`]); + } + return Boolean(process.env[provider.envKey]); + }); + + const providerNodes: TopologyNode[] = availableProviders.slice(0, 8).map((provider) => ({ + id: `provider-${provider.name}`, + label: provider.name, + kind: provider.name.includes('bridge') || provider.name === 'codex' ? 'provider' : 'provider', + status: configuredProviders.some((p) => p.name === provider.name) ? 'ready' : 'detected', + trust: provider.name.includes('bridge') ? 'internal' : 'public', + description: `${provider.models.length} model routes, ${provider.rateLimitRpm} rpm`, + metrics: { + models: provider.models.length, + rpm: provider.rateLimitRpm, + }, + })); + + const nodes: TopologyNode[] = [ + { + id: 'openai-api', + label: 'OpenAI-compatible API', + kind: 'client', + status: 'online', + trust: 'internal', + description: 'Existing gateway entrypoint for apps that can use a custom base URL.', + }, + { + id: 'client-claude-code', + label: 'Claude Code', + kind: 'client', + status: 'planned', + trust: 'internal', + description: 'Optional MCP bridge for code work.', + }, + { + id: 'client-codex', + label: 'Codex', + kind: 'client', + status: 'planned', + trust: 'internal', + description: 'Optional MCP helper and OpenAI-compatible client flow.', + }, + { + id: 'client-chatgpt', + label: 'ChatGPT', + kind: 'client', + status: 'planned', + trust: 'public', + description: 'Export importer and optional browser/adapter flow.', + }, + { + id: 'client-cursor', + label: 'Cursor / VS Code', + kind: 'client', + status: 'planned', + trust: 'internal', + description: 'Works through OpenAI-compatible base URL where supported.', + }, + { + id: 'gateway', + label: 'LLM Gateway', + kind: 'gateway', + status: 'online', + trust: 'n/a', + description: 'Existing core: OpenAI-compatible API, routing, completions, metrics, fallback.', + }, + { + id: 'trust-router', + label: 'Trust Router', + kind: 'policy', + status: 'planned', + trust: 'n/a', + description: 'Small policy layer for local-first routing and provider allowlists.', + }, + { + id: 'context-receipts', + label: 'Context Receipts', + kind: 'policy', + status: 'planned', + trust: 'n/a', + description: 'Shows what context was used, compressed, redacted, and routed.', + }, + { + id: 'memory-gitea', + label: 'Shared Git Memory', + kind: 'memory', + status: 'planned', + trust: 'confidential', + description: 'Optional Git/Gitea-backed memory for AI handoffs and project decisions.', + }, + { + id: 'mcp-server', + label: 'MCP Control Plane', + kind: 'tool', + status: 'planned', + trust: 'internal', + description: 'Gateway, memory, repo, and policy tools exposed through MCP.', + }, + { + id: 'ollama', + label: 'Ollama / Local Models', + kind: 'model', + status: 'ready', + trust: 'confidential', + description: 'Local-first model runtime for private and offline work.', + }, + ...providerNodes, + ]; + + const edges: TopologyEdge[] = [ + { from: 'openai-api', to: 'gateway', label: 'OpenAI-compatible API', status: 'ready' }, + { from: 'client-claude-code', to: 'mcp-server', label: 'MCP tools/resources', status: 'planned' }, + { from: 'client-codex', to: 'mcp-server', label: 'MCP tools/resources', status: 'planned' }, + { from: 'client-chatgpt', to: 'gateway', label: 'export/import + OpenAI adapter', status: 'planned' }, + { from: 'client-cursor', to: 'gateway', label: 'custom base URL', status: 'planned' }, + { from: 'mcp-server', to: 'gateway', label: 'tool calls', status: 'planned' }, + { from: 'gateway', to: 'trust-router', label: 'policy decision', status: 'planned' }, + { from: 'trust-router', to: 'ollama', label: 'confidential/local route', status: 'ready' }, + { from: 'trust-router', to: 'memory-gitea', label: 'memory read/write', status: 'planned' }, + { from: 'gateway', to: 'context-receipts', label: 'audit artifact', status: 'planned' }, + ...providerNodes.map((node) => ({ + from: 'trust-router', + to: node.id, + label: node.trust === 'public' ? 'public route' : 'approved route', + status: node.status === 'ready' ? 'ready' : 'planned', + } as TopologyEdge)), + ]; + + const plannedModules = [ + 'Trust Router', + 'Context Receipts', + 'Shared Gitea Memory', + 'AI Handoff Protocol', + 'Consent Ledger', + 'Setup Doctor', + 'Safe Config Writer', + 'Benchmark Lab', + 'Agent Reputation', + 'Compression Engine', + ]; + + return { + product: 'llm.gateway', + tagline: 'OpenAI-compatible LLM Gateway with routing, savings, receipts, and optional shared memory', + mode: process.env['BLACKHOLE_OFFLINE_MODE'] === 'true' ? 'offline' : 'hybrid-safe', + generatedAt: new Date().toISOString(), + summary: { + detectedClients: 6, + localModels: 1, + providersConfigured: configuredProviders.length, + trustPolicies: 4, + memoryBackends: 1, + plannedModules: plannedModules.length, + }, + nodes, + edges, + trustLevels: [ + { level: 'public', route: 'any enabled provider', action: 'allow' }, + { level: 'internal', route: 'local or approved provider', action: 'route with receipt' }, + { level: 'confidential', route: 'local-first', action: 'block public providers' }, + { level: 'secret', route: 'none', action: 'redact or block' }, + ], + receipts: [ + { id: 'ctxr-demo-001', trust: 'internal', route: 'Claude Code -> MCP -> local model', protected: '.env, tokens, private keys', tokens: '13.2k -> 4.2k' }, + { id: 'ctxr-demo-002', trust: 'confidential', route: 'Codex -> Gateway -> Ollama', protected: 'customer names, internal hosts', tokens: '8.4k -> 3.1k' }, + { id: 'ctxr-demo-003', trust: 'public', route: 'OpenAI-compatible app -> Gateway -> hosted model', protected: 'none detected', tokens: '2.0k -> 1.8k' }, + ], + roadmap: plannedModules.map((module, index) => ({ + module, + status: index < 4 ? 'next' : 'planned', + priority: index < 7 ? 'P0' : 'P1', + })), + }; +} + /** * Get dashboard summary stats for a time window */ @@ -306,6 +533,24 @@ async function getAlerts(): Promise { } export async function dashboardRoute(fastify: FastifyInstance): Promise { + fastify.get('/api/dashboard/topology', async (_request: FastifyRequest, reply: FastifyReply) => { + try { + return reply.send({ + success: true, + data: await getDashboardTopology(), + meta: { + timestamp: new Date().toISOString(), + }, + }); + } catch (error) { + logger.error({ error }, 'Failed to fetch topology'); + return reply.status(500).send({ + success: false, + error: 'Failed to fetch topology', + }); + } + }); + // Dashboard summary endpoint fastify.get('/api/dashboard/summary', async (request: FastifyRequest, reply: FastifyReply) => { const hours = (request.query as any).hours ?? 24; @@ -618,8 +863,7 @@ export async function dashboardRoute(fastify: FastifyInstance): Promise { } }); - // Dashboard UI endpoint (served at /api/dashboard/index for Cloudflare tunnel compatibility) - fastify.get('/api/dashboard/index', async (_request: FastifyRequest, reply: FastifyReply) => { + async function serveDashboardHtml(reply: FastifyReply, filename = 'dashboard.html', endpoint = '/dashboard') { try { const { fileURLToPath } = await import('url'); const { dirname, join } = await import('path'); @@ -628,84 +872,52 @@ export async function dashboardRoute(fastify: FastifyInstance): Promise { const __filename = fileURLToPath(import.meta.url); const __dirname = dirname(__filename); const publicDir = join(__dirname, '..', '..', 'public'); - const dashboardPath = join(publicDir, 'dashboard.html'); + const dashboardPath = join(publicDir, filename); if (!existsSync(dashboardPath)) { - logger.warn({ path: dashboardPath }, 'dashboard.html not found'); - return reply.status(404).send({ error: 'dashboard.html not found' }); + logger.warn({ path: dashboardPath, endpoint }, 'dashboard html not found'); + return reply.status(404).send({ error: `${filename} not found` }); } const content = readFileSync(dashboardPath, 'utf-8'); - logger.info({ size: content.length }, 'Serving dashboard from /api/dashboard/ui'); - return reply.type('text/html').send(content); - } catch (error) { - logger.error({ error }, 'Failed to serve dashboard UI'); - return reply.status(500).send({ error: 'Failed to serve dashboard' }); - } - }); - - // Fresh dashboard endpoint (no cache) - for Cloudflare cache bypass testing - fastify.get('/dashboard', async (_request: FastifyRequest, reply: FastifyReply) => { - try { - const { fileURLToPath } = await import('url'); - const { dirname, join } = await import('path'); - const { readFileSync, existsSync } = await import('fs'); - - const __filename = fileURLToPath(import.meta.url); - const __dirname = dirname(__filename); - const publicDir = join(__dirname, '..', '..', 'public'); - const dashboardPath = join(publicDir, 'dashboard.html'); - - if (!existsSync(dashboardPath)) { - logger.warn({ path: dashboardPath }, 'dashboard.html not found'); - return reply.status(404).send({ error: 'dashboard.html not found' }); - } - - const content = readFileSync(dashboardPath, 'utf-8'); - logger.info({ size: content.length }, 'Serving dashboard from /dashboard'); + logger.info({ size: content.length, endpoint, filename }, 'Serving dashboard html'); return reply .header('Cache-Control', 'no-cache, no-store, must-revalidate, max-age=0') .header('Pragma', 'no-cache') .header('Expires', '0') - .type('text/html') + .type('text/html; charset=utf-8') .send(content); } catch (error) { - logger.error({ error }, 'Failed to serve dashboard'); + logger.error({ error, endpoint, filename }, 'Failed to serve dashboard html'); return reply.status(500).send({ error: 'Failed to serve dashboard' }); } + } + + // Dashboard UI endpoint (served at /api/dashboard/index for Cloudflare tunnel compatibility) + fastify.get('/api/dashboard/index', async (_request: FastifyRequest, reply: FastifyReply) => { + return serveDashboardHtml(reply, 'dashboard.html', '/api/dashboard/index'); + }); + + // Fresh dashboard endpoint (no cache) - keeps the original Version 1 dashboard online. + fastify.get('/dashboard', async (_request: FastifyRequest, reply: FastifyReply) => { + return serveDashboardHtml(reply, 'dashboard.html', '/dashboard'); + }); + + // Version 2 dashboard preview - open-source workbench without replacing Version 1. + fastify.get('/v2/dashboard', async (_request: FastifyRequest, reply: FastifyReply) => { + return serveDashboardHtml(reply, 'dashboard-v2.html', '/v2/dashboard'); + }); + + fastify.get('/dashboard-v2', async (_request: FastifyRequest, reply: FastifyReply) => { + return serveDashboardHtml(reply, 'dashboard-v2.html', '/dashboard-v2'); }); // Cloudflare cache bypass endpoint - new URL that won't be cached by Cloudflare fastify.get('/api/dashboard/ui', async (_request: FastifyRequest, reply: FastifyReply) => { - try { - const { fileURLToPath } = await import('url'); - const { dirname, join } = await import('path'); - const { readFileSync, existsSync } = await import('fs'); + return serveDashboardHtml(reply, 'dashboard.html', '/api/dashboard/ui'); + }); - const __filename = fileURLToPath(import.meta.url); - const __dirname = dirname(__filename); - const publicDir = join(__dirname, '..', '..', 'public'); - const dashboardPath = join(publicDir, 'dashboard.html'); - - if (!existsSync(dashboardPath)) { - logger.warn({ path: dashboardPath }, 'dashboard.html not found at /api/dashboard/ui'); - return reply.status(404).send({ error: 'dashboard.html not found' }); - } - - const content = readFileSync(dashboardPath, 'utf-8'); - const timestamp = Date.now(); - logger.info({ size: content.length, endpoint: '/api/dashboard/ui', timestamp }, 'Serving dashboard UI (Cloudflare cache bypass)'); - return reply - .header('Cache-Control', 'no-cache, no-store, must-revalidate, max-age=0, public') - .header('Pragma', 'no-cache') - .header('Expires', '0') - .header('ETag', `"ui-${timestamp}"`) - .header('X-Cache-Bypass', 'true') - .type('text/html; charset=utf-8') - .send(content); - } catch (error) { - logger.error({ error }, 'Failed to serve dashboard UI'); - return reply.status(500).send({ error: 'Failed to serve dashboard UI' }); - } + fastify.get('/api/dashboard/v2', async (_request: FastifyRequest, reply: FastifyReply) => { + return serveDashboardHtml(reply, 'dashboard-v2.html', '/api/dashboard/v2'); }); } diff --git a/packages/gateway/src/routes/health.ts b/packages/gateway/src/routes/health.ts index be8b3fd..9aa93b0 100644 --- a/packages/gateway/src/routes/health.ts +++ b/packages/gateway/src/routes/health.ts @@ -78,8 +78,9 @@ export async function healthRoute(fastify: FastifyInstance): Promise { // Check if this is a dashboard UI request with ?ui=1 or ?dashboard=1 const query = request.query as any; const isDashboardRequest = query.ui || query.dashboard; + const acceptsHtml = String(request.headers.accept ?? '').includes('text/html'); - if (isDashboardRequest) { + if (isDashboardRequest || acceptsHtml) { try { const __filename = fileURLToPath(import.meta.url); const __dirname = dirname(__filename); diff --git a/packages/gateway/src/security/tls-config.ts b/packages/gateway/src/security/tls-config.ts index d732779..caa73cd 100644 --- a/packages/gateway/src/security/tls-config.ts +++ b/packages/gateway/src/security/tls-config.ts @@ -126,10 +126,10 @@ export async function registerHTTPSRedirectMiddleware(server: FastifyInstance) { */ export async function registerSecurityHeadersMiddleware(server: FastifyInstance) { server.addHook('onSend', async (request, reply) => { - // Content Security Policy - strict, no inline scripts + // Content Security Policy for the self-contained dashboard UI. reply.header( 'Content-Security-Policy', - "default-src 'self'; script-src 'self'; object-src 'none'; frame-ancestors 'none'; base-uri 'self'; form-action 'self'" + "default-src 'self'; script-src 'self' 'unsafe-inline'; style-src 'self' 'unsafe-inline'; object-src 'none'; frame-ancestors 'none'; base-uri 'self'; form-action 'self'" ); // Prevent clickjacking diff --git a/packages/gateway/src/server.ts b/packages/gateway/src/server.ts index e7878d2..85a67ac 100644 --- a/packages/gateway/src/server.ts +++ b/packages/gateway/src/server.ts @@ -77,6 +77,7 @@ async function buildServer() { directives: { defaultSrc: ["'self'"], scriptSrc: ["'self'", "'unsafe-inline'"], + styleSrc: ["'self'", "'unsafe-inline'"], objectSrc: ["'none'"], }, },