Module: routes/chat

AI chat endpoint — proxies multi-turn conversations through the configured AI provider (Anthropic / OpenAI / Google / Ollama). Mounted at /api/v1 (INF-005).

The system prompt includes a live workspace snapshot (projects, tests, recent runs, failures) so the AI can answer questions about the user's actual data without extra API calls from the frontend.

Endpoints

Method Path Description
POST /api/v1/chat Send a message and receive an AI reply (SSE)

Request body: { messages: [{ role: "user"|"assistant", content: string }] }

Response: Server-Sent Events stream of token deltas, then a [DONE] event.

Source:

Methods

(inner) buildEntityContext(ctx, userMessage, optsopt) → {string}

Scan the user's message for entity references (TC-, RUN-, PRJ-*) and fetch detailed context for each.

Parameters:
Name Type Attributes Description
ctx Object

{ projects, tests, runs, projectsById, testsById, runsById }

userMessage string

The latest user message text.

opts Object <optional>
Properties
Name Type Attributes Default Description
compact boolean <optional>
false

When true, trim heavy fields (code, errors) to fit local models with small context windows.

Source:
Returns:

Detailed entity context block, or empty string.

Type
string

(inner) buildWorkspaceContext(ctx) → {string}

Build a compact workspace snapshot for the system prompt. Kept small to avoid wasting tokens — only includes actionable data.

Parameters:
Name Type Description
ctx Object

{ projects, tests, runs, projectsById, testsById, runsById }

Source:
Returns:

Workspace context block, or empty string if no data.

Type
string

(inner) findLatestTestResult(ctx, testId) → {Object|null}

Find the most recent run result for a specific test ID.

Parameters:
Name Type Description
ctx Object

{ runs }

testId string

The test ID to search for.

Source:
Returns:

{ runId, status, error, duration } or null.

Type
Object | null

(inner) trimConversationHistory(messages) → {Array.<{role: string, content: string}>}

Trim a conversation to fit within MAX_CONVERSATION_TURNS.

Strategy: keep the first message (initial context) and the most recent turns. Walk forward from the cut point to find a safe boundary at an assistant message — never split a user message from its assistant reply.

This is pure truncation — no extra LLM calls. The same single streamText() call is made regardless of whether trimming occurred.

Parameters:
Name Type Description
messages Array.<{role: string, content: string}>
Source:
Returns:

Trimmed copy (or original if short enough).

Type
Array.<{role: string, content: string}>