Skip to content

feat: AI-powered VRL and pipeline suggestions#86

Merged
TerrifiedBug merged 23 commits intomainfrom
vector-assistant-350
Mar 10, 2026
Merged

feat: AI-powered VRL and pipeline suggestions#86
TerrifiedBug merged 23 commits intomainfrom
vector-assistant-350

Conversation

@TerrifiedBug
Copy link
Owner

Summary

  • Add per-team AI provider configuration (OpenAI-compatible API) with encrypted credentials (AES-256-GCM)
  • Add VRL code assistant: inline AI panel in the VRL editor that generates VRL code from natural language
  • Add pipeline builder AI: dialog in the flow toolbar that generates Vector YAML from descriptions or reviews existing pipelines
  • SSE streaming endpoints for real-time token delivery to the UI
  • AI settings page for team admins to configure provider, model, API key, and test the connection
  • Rate limiting (60 req/hour per team) and EDITOR+ role enforcement on all AI endpoints

Changes

Database & Service Layer

  • Prisma schema: 5 new fields on Team (aiProvider, aiBaseUrl, aiApiKey, aiModel, aiEnabled)
  • src/server/services/ai.ts: streaming completions service with config loading, key decryption, rate limiting
  • src/lib/ai/rate-limiter.ts: in-memory fixed-window rate limiter
  • src/lib/ai/prompts.ts: system prompt builders for VRL assistant and pipeline builder
  • src/lib/ai/vrl-reference.txt: compact VRL function reference for LLM context

API & tRPC

  • src/app/api/ai/vrl/route.ts: SSE endpoint for VRL code generation
  • src/app/api/ai/pipeline/route.ts: SSE endpoint for pipeline generation/review
  • src/server/routers/team.ts: getAiConfig, updateAiConfig, testAiConnection procedures; aiApiKey stripped from team.get and team.list responses

UI Components

  • src/components/vrl-editor/ai-input.tsx: streaming AI input with Insert/Replace/Regenerate actions
  • src/components/flow/ai-pipeline-dialog.tsx: tabbed Generate/Review dialog with Apply to Canvas
  • src/app/(dashboard)/settings/_components/ai-settings.tsx: provider config form with test connection
  • VRL editor and flow toolbar modified to show AI buttons when aiEnabled is true

Security

  • API keys encrypted at rest with enc: prefix via existing crypto.ts
  • aiApiKey added to audit middleware SENSITIVE_KEYS for redaction
  • aiApiKey stripped from both team.get and team.list tRPC responses
  • SSE endpoints verify session + EDITOR role (or superAdmin)

Test plan

  • Configure AI provider in Settings → AI with valid OpenAI-compatible credentials
  • Test Connection button shows green "Connected" badge
  • Open VRL editor on a remap transform — AI button appears when team has AI enabled
  • Type a prompt (e.g. "parse JSON from .message and extract status code") and verify streaming output
  • Insert and Replace actions work correctly in the editor
  • Open pipeline editor — sparkle icon appears in toolbar when AI enabled
  • Generate mode: describe a pipeline and verify YAML streams, Apply to Canvas creates nodes
  • Review mode: ask for review of existing pipeline and verify text response
  • Verify AI buttons do not appear when aiEnabled is false
  • Verify VIEWER role users cannot access AI endpoints (403)
  • Verify aiApiKey is not present in team.get or team.list API responses

Add aiProvider, aiBaseUrl, aiApiKey, aiModel, and aiEnabled columns
to support per-team AI provider configuration for VRL assistant
and pipeline builder features.
- Strip aiApiKey from team.list response (critical security fix)
- Add runtime validation for body.mode in pipeline endpoint
- Fix rate limiter comment (fixed window, not sliding)
- Add runtime = "nodejs" to SSE route handlers
- Use __dirname instead of process.cwd() in prompts.ts
- Only render AiPipelineDialog when AI is enabled
- Create missing ai-suggestions.md doc page
@github-actions github-actions bot added feature documentation Improvements or additions to documentation labels Mar 10, 2026
@greptile-apps
Copy link
Contributor

greptile-apps bot commented Mar 10, 2026

Greptile Summary

This PR adds an optional AI assistant feature to VectorFlow, including per-team OpenAI-compatible provider configuration with AES-256-GCM encrypted credentials, SSE streaming endpoints for VRL code generation and pipeline YAML generation/review, and corresponding UI panels in the VRL editor and pipeline toolbar. The previously flagged SSRF vulnerability, __dirname production-build issue, and missing withAudit on testAiConnection have all been addressed.

Key findings:

  • testAiConnection is broken for new setups — the service function calls getTeamAiConfig, which rejects requests when aiEnabled is false. Since the expected workflow is "configure credentials → test → enable", the test button effectively does nothing useful until the team has already enabled AI. This needs to be fixed before merge.
  • In-memory rate limiter is not durable — the buckets Map in rate-limiter.ts resets on server restart and is per-process, meaning each replica enforces its own independent 60 req/hour limit rather than a shared one. In any multi-replica or blue/green deployment this limit provides little real protection.

Confidence Score: 3/5

  • Safe to merge after fixing the testAiConnection / aiEnabled bug; the rate limiter issue is a known limitation worth addressing.
  • The security fundamentals are sound (encryption at rest, SSRF validation, role enforcement, audit logging, key stripping). One correctness bug (testAiConnection unusable before enabling AI) will manifest for every new team configuring AI for the first time. The rate limiter limitation is architectural and matters in production multi-replica deployments.
  • src/server/services/ai.tstestAiConnection function; src/lib/ai/rate-limiter.ts — in-memory state.

Important Files Changed

Filename Overview
src/server/services/ai.ts Core AI streaming service with solid SSRF validation and encryption handling. testAiConnection incorrectly calls getTeamAiConfig which blocks testing when AI is disabled (the normal pre-enable workflow).
src/lib/ai/rate-limiter.ts In-memory fixed-window rate limiter. State is lost on restart and is not shared across replicas, making the 60 req/hour limit ineffective in multi-process or multi-replica deployments.
src/app/api/ai/vrl/route.ts SSE endpoint for VRL generation. Authentication, role check (EDITOR+), and SSE streaming are all correctly implemented.
src/app/api/ai/pipeline/route.ts SSE endpoint for pipeline generation/review. Auth, role guard, and mode validation look correct.
src/server/routers/team.ts New getAiConfig, updateAiConfig, and testAiConnection procedures all have correct withTeamAccess(ADMIN) and withAudit middleware. aiApiKey is properly stripped from team.get and team.list responses.
src/server/middleware/audit.ts aiApiKey correctly added to SENSITIVE_KEYS for automatic redaction in audit log diffs.
src/components/flow/ai-pipeline-dialog.tsx Dialog for AI pipeline generation and review. SSE streaming, abort handling, and Apply to Canvas logic look correct.
src/app/(dashboard)/settings/_components/ai-settings.tsx AI settings form with provider selection, URL pre-fill, and test connection badge. Looks correct; API key is write-only (form shows hasApiKey flag, never the raw value).

Sequence Diagram

sequenceDiagram
    participant Browser
    participant NextJS as Next.js Route Handler
    participant AI_Service as ai.ts service
    participant RateLimiter as rate-limiter.ts
    participant DB as PostgreSQL (Prisma)
    participant Provider as AI Provider (OpenAI-compat)

    Browser->>NextJS: POST /api/ai/vrl (teamId, prompt, ...)
    NextJS->>DB: TeamMember lookup (session + teamId)
    DB-->>NextJS: membership (role check ≥ EDITOR)
    NextJS->>AI_Service: streamCompletion(teamId, prompts)
    AI_Service->>RateLimiter: checkRateLimit(teamId)
    RateLimiter-->>AI_Service: { allowed, remaining, resetAt }
    AI_Service->>DB: team.findUnique (aiEnabled, aiBaseUrl, aiApiKey, aiModel)
    DB-->>AI_Service: encrypted config
    AI_Service->>AI_Service: decryptApiKey() + validateBaseUrl()
    AI_Service->>Provider: POST /chat/completions (stream: true)
    Provider-->>AI_Service: SSE token stream
    AI_Service-->>NextJS: onToken callback per chunk
    NextJS-->>Browser: SSE data: {token} frames
    NextJS-->>Browser: SSE data: {done: true}
Loading

Comments Outside Diff (2)

  1. src/lib/ai/rate-limiter.ts, line 8-13 (link)

    In-memory rate limiter resets on server restart and is per-process

    buckets is a module-level Map that lives only in the current Node.js process. Two consequences:

    1. Restart bypass — restarting the Next.js server clears all counters, resetting every team's rate limit window instantly.
    2. Multi-replica bypass — if the app runs behind multiple replicas (Kubernetes, Docker Swarm, multiple PM2 workers), each replica maintains its own independent Map. A team gets 60 × num_replicas requests per hour.

    For production correctness, rate limit state should be persisted in a shared store (Redis, or at minimum the existing PostgreSQL database). A simple approach is a database row per team with windowStart and requestCount columns updated atomically.

  2. src/server/services/ai.ts, line 83-90 (link)

    testAiConnection blocked when AI is disabled

    testAiConnection delegates to getTeamAiConfig, which throws "AI is not enabled for this team" when team.aiEnabled is false. This makes the Test Connection button non-functional during the typical admin workflow of configure credentials → test → enable. The error is silently caught and surfaced as a failed test, giving no signal that the credentials themselves might be valid.

    The fix is to have testAiConnection load the raw DB fields directly rather than going through getTeamAiConfig, bypassing the aiEnabled guard:

    export async function testAiConnection(teamId: string): Promise<{ ok: boolean; error?: string }> {
      try {
        const team = await prisma.team.findUnique({
          where: { id: teamId },
          select: { aiProvider: true, aiBaseUrl: true, aiModel: true, aiApiKey: true },
        });
        if (!team) throw new Error("Team not found");
        if (!team.aiApiKey) throw new Error("AI credentials are not configured");
    
        const baseUrl = team.aiBaseUrl || getDefaultBaseUrl(team.aiProvider);
        validateBaseUrl(baseUrl);
        const model = team.aiModel ?? "gpt-4o";
        const decrypted = decryptApiKey(team.aiApiKey);
    
        const response = await fetch(`${baseUrl}/chat/completions`, {
          method: "POST",
          headers: {
            "Content-Type": "application/json",
            Authorization: `Bearer ${decrypted}`,
          },
          body: JSON.stringify({ model, max_tokens: 5, messages: [{ role: "user", content: "Hi" }] }),
        });
        // ...rest of response handling
      } catch (err) {
        return { ok: false, error: err instanceof Error ? err.message : String(err) };
      }
    }

Last reviewed commit: 4620191

- Refactor ai-settings.tsx to eliminate useEffect setState anti-pattern
- Add SSRF validation for admin-configurable AI base URLs
- Inline VRL reference as TS module (fixes __dirname in production builds)
- Add withAudit middleware to testAiConnection mutation
- Fix unused variable ESLint warnings in team.ts
@TerrifiedBug
Copy link
Owner Author

@greptile fixed

  • CI error: ai-settings.tsx — eliminated useEffect setState
  • CI warning: team.ts — fixed unused variable lint warnings
  • Greptile: prompts.ts — inlined VRL reference as TS module
  • Greptile + CodeQL: ai.ts — SSRF validation on all fetch paths
  • Greptile: team.ts — added withAudit to testAiConnection

- Block full 169.254.0.0/16 link-local range, IPv6 private/link-local
  prefixes (fe80::, fc00::, fd00::, ::ffff:), and unspecified address
- Merge imported global config when applying AI nodes to existing canvas
  instead of silently dropping it
@TerrifiedBug
Copy link
Owner Author

@greptile fixed the last issues.

  1. SSRF blocklist — now covers full 169.254.0.0/16 link-local range, IPv6 private/link-local prefixes
    (fe80::, fc00::, fd00::, ::ffff:), and unspecified address (::)
  2. Global config dropped — AI-generated global config is now merged with existing canvas config (existing
    settings take precedence)
  3. added add && !user?.isSuperAdmin to both endpoints.

The test-connection workflow (configure → test → enable) was broken
because getTeamAiConfig rejected requests when aiEnabled was false.
@TerrifiedBug TerrifiedBug merged commit d9df258 into main Mar 10, 2026
10 checks passed
@TerrifiedBug TerrifiedBug deleted the vector-assistant-350 branch March 10, 2026 20:04
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

documentation Improvements or additions to documentation feature

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant