AI Refactoring: Task Checklist
Phase 1: Dependencies & Setup
- Install
ai,@ai-sdk/openai-compatible,@ai-sdk/reactinapps/web/pnpm add ai @ai-sdk/openai-compatible @ai-sdk/react --filter ever-works-web - Verify version alignment with
packages/plugins/agent-pipeline/(ai@^6.0.85) - Run
pnpm installfrom root to ensure lockfile is updated
Phase 2: Enable Tool Calling in AiOperations
2.1 Update Plugin Contracts
- Add
toolCallId?: stringfield toChatMessageinterface- File:
packages/plugin/src/contracts/capabilities/ai-provider.interface.ts
- File:
- Make
createStreamingChatCompletionrequired (not optional) inIAiProviderPlugin - Verify
ToolDefinition,ToolCall,ChatCompletionOptions.toolsalready exist (they do)
2.2 Update AiOperations Class
- Import
ToolMessage,BaseMessagefrom@langchain/core/messages- File:
packages/plugin/src/ai/ai-operations.ts
- File:
- Update
toLangChainMessages()to handle:-
role: 'tool'->ToolMessagewithtool_call_id -
role: 'assistant'withtoolCalls->AIMessagewithtool_calls
-
- Add
bindTools()private method using LangChain'sbindToolsAPI:- If
options.toolspresent, callllm.bindTools(tools, { tool_choice }) - Add
'tools'to parameter rejection/retry logic inparseRejectedParam()
- If
- Update
createChatCompletion()to parse tool calls:- Extract
response.tool_callsfromAIMessage - Map to
ChatCompletionResponse.choices[].message.toolCalls - Set
finishReason: 'tool_calls'when tool calls present
- Extract
- Update
createStreamingChatCompletion()to parse tool call chunks:- Extract
chunk.tool_call_chunksfromAIMessageChunk - Map to
delta.toolCallsin chunk format - Handle
finishReason: 'tool_calls'
- Extract
- Update
parseRejectedParam()to handle'tools'rejection
2.3 Additional Changes
- Remove optional streaming guard from
AiFacadeService.createStreamingChatCompletion()- File:
packages/agent/src/facades/ai.facade.ts
- File:
2.4 Verify
- Run
cd packages/plugin && pnpm type-check— passes - Run type-check on all 8 AI provider plugins (openai, anthropic, groq, google, mistral, openrouter, ollama, vercel-ai-gateway) — all pass
- Run
cd packages/agent && pnpm test— 33 suites, 892 tests pass - Note:
langfuse-pluginhas pre-existing type-check failure (unrelated to our changes)
Phase 3: OpenAI-Compatible NestJS Endpoint
3.1 Create DTOs
- Create
apps/api/src/ai-conversation/dto/openai-compat.dto.ts-
OpenAiChatCompletionRequestDtowith class-validator decorators -
OpenAiMessageDto,OpenAiToolDefinitionDto,OpenAiFunctionDtoclasses -
OpenAiChatCompletionResponseinterface (non-streaming) -
OpenAiChatCompletionChunkResponseinterface (streaming) -
OpenAiToolCallResponseinterface
-
3.2 Create Service
- Create
apps/api/src/ai-conversation/openai-compat.service.ts- Constructor: inject
AiFacadeService,WorkRepository -
handleCompletion(dto, facadeOptions)- non-streaming path -
handleStreamingCompletion(dto, facadeOptions, res)- SSE streaming path -
mapToInternalMessages(messages)- OpenAI -> internal ChatMessage format (with tool_calls + tool_call_id) -
mapToInternalOptions(dto)- Full request mapping (snake_case -> camelCase) -
mapToOpenAiResponse(response)- Internal -> OpenAI response format (camelCase -> snake_case) -
mapToOpenAiStreamChunk(chunk, toolCallBaseIndex)- Internal -> OpenAI SSE chunk format with indexed tool_calls -
resolveWorkContext(options)- Reuses same logic as old service
- Constructor: inject
3.3 Create Controller
- Create
apps/api/src/ai-conversation/openai-compat.controller.ts-
POST /api/v1/chat/completions - JWT auth via
@CurrentUser() - Read
X-Provider-OverrideandX-Work-Idheaders - Route to streaming (SSE) vs non-streaming (JSON) based on
body.stream - Proper SSE headers:
text/event-stream,no-cache,keep-alive,X-Accel-Buffering: no
-
3.4 Clean Up Old Code
- Deleted
apps/api/src/ai-conversation/ai-conversation.controller.ts(old NDJSON controller) - Deleted
apps/api/src/ai-conversation/ai-conversation.service.ts(old streaming service) - Updated
ai-conversation.module.ts— only registersOpenAiCompatController+OpenAiCompatService - Fixed
ai.facade.spec.ts— addedcreateStreamingChatCompletionto mock (required after interface change)
3.5 Verify
-
npx turbo build --filter=ever-works-api— 0 TSC issues, all 6 tasks pass -
cd packages/agent && pnpm test— 33 suites, 892 tests pass - Manual curl testing (deferred to integration testing)
Phase 4: Custom Vercel AI SDK Provider + Next.js Route Handler
Provider Selection Requirements
X-Provider-Overrideis always passed — user always has an active AI provider- Default:
openrouter(auto-selected viaresolveEffectiveDefault()in ChatProvider) - Auth: encrypted JWT cookie →
getAuthAccessCookie()→ Bearer token to backend
4.1 Custom Provider
- Create
apps/web/src/lib/ai/provider.ts-
createBackendProvider(options)function - Uses
createOpenAICompatiblefrom@ai-sdk/openai-compatible -
providerOverrideis required (not optional) — always passed asX-Provider-Overrideheader -
apiKey= JWT token (sent asAuthorization: Bearerautomatically) -
baseURL=${API_URL}/v1(API_URL already includes/apisuffix) - Marked
server-only— no client-side usage
-
- Create
apps/web/src/lib/ai/index.tsbarrel export
4.2 Next.js Route Handler
- Create
apps/web/src/app/api/chat/route.ts-
POSThandler withmaxDuration = 60 - Auth:
getAuthAccessCookie()+refreshAccessToken()fallback (same asserverFetch) - Parse body: extract
messages(UIMessage[]),providerOverride(required),workId - Returns 400 if
providerOverridemissing - Returns 401 if no auth token after refresh attempt
- Creates provider with
createBackendProvider()pointing to${API_URL}/v1 - Calls
streamText()withprovider('default')+convertToModelMessages(messages) - Returns
result.toUIMessageStreamResponse()
-
- Test end-to-end: web -> Next.js route -> NestJS -> plugin -> LangChain
Phase 6: Frontend Chat UI Refactor
6.1 Update ChatProvider
- Rewrite
apps/web/src/components/ai/ChatProvider.tsx- Replace
useChatHistorywithuseChatfrom@ai-sdk/react - Configure
useChatwithDefaultChatTransport({ api: '/api/chat' }) - Pass
providerOverrideviabodyoption (always required, defaults to'openrouter') - Set
messageswith welcome message as initial - Expose
sendMessage,setMessages,status,error,stop,regeneratethrough context - Add
resetChat()method - Keep provider fetching logic (unchanged)
- Replace
6.2 Split into Clean Components
- Create
apps/web/src/components/ai/ChatMessage.tsx— Single message rendering (user/assistant) - Create
apps/web/src/components/ai/ChatMessageContent.tsx— Message parts renderer (text, tool calls, streaming dots) - Create
apps/web/src/components/ai/ChatMessageEdit.tsx— Inline editing textarea with save/cancel - Create
apps/web/src/components/ai/ChatProviderSelector.tsx— Provider selection pill buttons
6.3 Rewrite ChatInterface
- Rewrite
apps/web/src/components/ai/ChatInterface.tsx- Uses split sub-components (ChatMessage, ChatProviderSelector)
- Uses
useChatContext()for all state -
isStreaming = status === 'streaming' || status === 'submitted' - Form submit calls
sendMessage(text)from context - Message editing:
setMessagesto truncate +regenerate()(v6 API, notreload) - Reset calls
resetChat() - Error from
error.message - Removed:
pendingMessageRef,updatePendingMessage,clearPending, all manual streaming
6.4 Refactor Sidebar — Chat as Sidebar-of-Sidebar
- Rewrite
apps/web/src/components/dashboard/DashboardSidebar.tsx- Removed menu/chat mode toggle (no more
activeModestate) - Sidebar always shows navigation — AI Chat button is a nav item
- Chat slides out as a secondary panel (positioned at
left: sidebarWidth) - Panel has its own close button, drag-to-resize handle, and backdrop
- Works with both collapsed and expanded sidebar states
- Removed
LayoutListimport (unused after mode toggle removal)
- Removed menu/chat mode toggle (no more
6.5 Verify
-
npx turbo build --filter=ever-works-web— build passes, 0 TypeScript errors - Old
/api/ai-conversations/chat/streamroute removed from build output - New
/api/chatroute present in build output
Phase 7: Cleanup
7.1 Remove Old Files
- Deleted
apps/web/src/lib/hooks/use-ai-stream.ts - Deleted
apps/web/src/lib/hooks/use-chat-history.ts - Deleted
apps/web/src/lib/api/ai-conversation.ts - Deleted
apps/web/src/app/api/ai-conversations/(entire work tree) - Deleted
apps/web/src/lib/utils/next-api.ts - Deleted
apps/api/src/ai-conversation/ai-conversation.controller.ts(old NDJSON controller) - Deleted
apps/api/src/ai-conversation/ai-conversation.service.ts(old streaming service)
7.2 Update References
- Removed
export * from './ai-conversation'fromapps/web/src/lib/api/index.ts - Removed
API_AI_CONVERSATIONS_CHAT_STREAMfrom constants, addedAPI_CHAT - Removed re-exports of deleted types from
apps/web/src/lib/api/types-only.ts
7.3 Verify No Broken Imports
- Search for
use-ai-stream-> 0 results - Search for
use-chat-history-> 0 results - Search for
aiConversationAPI-> 0 results - Search for
nextApiResponseStreaming-> 0 results - Search for
API_AI_CONVERSATIONS_CHAT_STREAM-> 0 results
7.4 Final Verification
-
npx turbo build --filter=ever-works-webpasses -
npx turbo build --filter=ever-works-apipasses -
cd packages/agent && pnpm test— 892 tests pass - Manual E2E test of chat feature (requires running servers)
- Manual test of work generation (verify pipelines still work)