refactor(backend): split chatTools.ts into per-tool registry#2
Merged
Conversation
Move every tool the LLM can call into its own file under
backend/src/lib/tools/, dispatched through a typed registry. chatTools.ts
shrinks from 3,284 to 976 lines and contains only orchestration
(SYSTEM_PROMPT, runLLMStream, citation parsing, message-building, doc
context, workflow store).
Why: a single 3K-line monolith made every tool change risky, every new
tool an exercise in pattern-matching against existing branches, and the
schema/dispatch split (TOOLS/PROJECT_EXTRA_TOOLS/TABULAR_TOOLS/WORKFLOW_TOOLS
arrays composed by callers, mirrored by a giant if/else in runToolCalls)
left two places that had to stay in sync. Adding a tool now requires
one new file plus one line in the registry — no edits to the monolith.
Structure:
tools/types.ts ToolDefinition / ToolContext / ToolExecutionResult,
plus the tool-result types (EditAnnotation,
DocCreatedResult, DocReplicatedResult,
DocEditedResult, TurnEditState)
tools/registry.ts TOOL_REGISTRY (as const, typed-literal names),
buildAvailableToolSchemas, runToolCalls dispatcher
tools/<toolName>.ts One ToolDefinition per file (10 tools)
tools/shared/ Helpers used by multiple tools
(documentReading, generateDocx, runEditDocument)
Per-tool availability is declared via `availableWhen(ctx)` predicates,
replacing the previous route-shaped tool composition. The registry
filters schemas per turn, so the model never sees tools that can't run.
The legacy arrays TOOLS, PROJECT_EXTRA_TOOLS, TABULAR_TOOLS,
WORKFLOW_TOOLS are deleted (callers in routes/tabular.ts and
routes/projectChat.ts lose the `extraTools:` pass-through accordingly).
Verified: tsc --noEmit clean, 57/57 unit tests pass. Behavior-preserving:
schemas reproduced verbatim, side-effect aggregation shape unchanged,
SSE events emitted identically.
TECHDEBT.md picks up two e2e findings surfaced (not caused) by this
work: the log-out test now times out, and the test Supabase project is
missing the user_profiles migration.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Owner
Author
|
Summary — 3,284-line monolith → 976 lines (-70%); per-tool files under tools/; legacy schema arrays + giant runToolCalls if/else deleted |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
backend/src/lib/chatTools.tsmonolith into a typed tool registry underbackend/src/lib/tools/. chatTools.ts shrinks to 976 lines (-70%) and contains only orchestration:SYSTEM_PROMPT,runLLMStream, citation parsing, message building, doc/workflow context builders.ToolDefinitionexport. Adding a new tool is now one new file + one line intools/registry.ts— no edits to the monolith.TOOLS,PROJECT_EXTRA_TOOLS,TABULAR_TOOLS,WORKFLOW_TOOLS) and the giantrunToolCallsif/else chain. Callers inroutes/projectChat.tsandroutes/tabular.tslose theirextraTools:pass-through — the registry'savailableWhen(ctx)predicates handle gating per-tool.Structure
Behavior preservation
availableWhenpredicates reproduce existing route-shaped gating (list_documents / fetch_documents / replicate_document require projectId; read_table_cells requires tabularStore; edit_document / replicate_document require docIndex).const colCount = headers.lengthwas removed from generateDocx. Behavior unchanged.Test plan
tsc --noEmitcleannpm test— 57/57 backend unit tests passnpm run test:e2e— 3/4 active specs pass. Caveat: chat/documents/projects/tabular specs are pre-skipped (test.describe.skip) due to drifted UI selectors — they were already skipped on main and would be the actual end-to-end validation of the dispatcher. See follow-up below.Tech debt added (surfaced, not caused, by this work)
Two e2e findings logged in
TECHDEBT.md:auth › log-out returns the user to the marketing rootnow times out — log-out lands at/logininstead of/. Likely the existing auth-guard race documented just below it in TECHDEBT.public.user_profilesmigration — signup logs a schema-cache error but still succeeds.A follow-up task has been spawned to re-enable the four skipped Playwright specs so the e2e suite can validate the dispatcher end-to-end again.
🤖 Generated with Claude Code