Skip to content

Conversation

@samschooler
Copy link
Contributor

@samschooler samschooler commented Sep 26, 2025

Description

refactor: initModel to sync
refactor: toolset-lookup/registry
refactor: useChat to user agenttype.root by default
refactor: tools to use tools for agenttype
feat: onlook-chat: implementation of AbstractChat from ai sdk
feat: SubAgent tool arch allowing subagents to call tools on the client within their own threads
feat: Example SubAgent -- allows to list files

  • add context to subagents
  • parent child thread relationship
  • clean up subagent completion
  • persist agentType in subsequent messages
  • type inputs/outputs of subagents

Type of Change

  • Bug fix
  • New feature
  • Documentation update
  • Release
  • Refactor
  • Other (please describe):

Important

Introduces SubAgent architecture for client-side tool calls, refactors model initialization, and updates chat and tool handling across the codebase.

  • Features:
    • Introduces SubAgent architecture allowing subagents to call tools on the client within their own threads.
    • Adds UserAgent and RootAgent classes in packages/ai/src/agents/classes.
    • Implements OnlookChat in packages/ai/src/agents/onlook-chat.ts for chat state management.
  • Refactors:
    • Changes initModel to synchronous in packages/ai/src/chat/providers.ts and other files.
    • Refactors toolset lookup and registry in tools and agents directories.
    • Updates useChat hook in apps/web/client/src/app/project/[id]/_hooks/use-chat/index.tsx to use agentType.
  • API Changes:
    • Modifies POST function in apps/web/client/src/app/api/chat/route.ts to handle different agentType.
    • Updates conversationRouter and suggestionsRouter in apps/web/client/src/server/api/routers/chat to support new agent types.
  • Database:
    • Adds agentType to conversations schema in packages/db/src/schema/chat/conversation.ts.
    • Updates mappers and defaults to include agentType in packages/db/src/mappers/chat/conversation.ts and packages/db/src/defaults/conversation.ts.

This description was created by Ellipsis for fdccec9. You can customize this summary. It will automatically update as commits are pushed.

Summary by CodeRabbit

  • New Features

    • Multi-agent chat: Root and new User agents, agentType persisted per conversation.
    • Agent-aware tools, nested tool calls, and a new user-facing tool ("doggo").
    • Observable chat state with optional throttled updates and public registration APIs.
  • Improvements

    • Faster model initialization and streaming; richer stream metadata (message IDs, timestamps, usage, finish reasons, errors).
    • Input validation for streaming requests and centralized tool discovery.
  • Bug Fixes

    • Upsert/update flow for conversations improved to reliably update timestamps and persist agentType.

refactor: toolset-lookup/registry
refactor: useChat to user agenttype.root by default
refactor: tools to use tools for agenttype
feat: onlook-chat: implementation of AbstractChat from ai sdk
feat: SubAgent tool arch allowing subagents to call tools on the client within their own threads
feat: Example SubAgent -- allows to list files
todo: add context to subagents
todo: parent child thread relationship
@vercel
Copy link

vercel bot commented Sep 26, 2025

@samschooler is attempting to deploy a commit to the Onlook Team on Vercel.

A member of the Team first needs to authorize it.

@coderabbitai
Copy link

coderabbitai bot commented Sep 26, 2025

Note

Other AI code review bot(s) detected

CodeRabbit has detected other AI code review bot(s) in this pull request and will avoid duplicating their findings in the review comments. This may lead to a less comprehensive review.

Walkthrough

Adds AgentType (ROOT/USER) across models, DB, API and client; introduces UserAgent and UserAgentTool plus sub-agent tooling core; refactors tool registry to be agent-aware; makes initModel and provider helpers synchronous; adds OnlookChat state/manager and updates streaming telemetry/metadata.

Changes

Cohort / File(s) Change Summary
Agent type & conversation plumbing
packages/models/src/chat/conversation/index.ts, packages/db/src/schema/chat/conversation.ts, packages/db/src/defaults/conversation.ts, packages/db/src/mappers/chat/conversation.ts, apps/web/client/src/app/project/[id]/_hooks/use-chat/index.tsx, apps/web/client/src/app/api/chat/route.ts, apps/web/client/src/app/api/chat/helpers/stream.ts
Introduces AgentType enum and threads agentType into models, DB schema, defaults, mappers, client hook, API route and streaming payloads; validates/parses agentType in route and includes it in payload/telemetry.
Agent classes & exports
packages/ai/src/agents/classes/root.ts, packages/ai/src/agents/classes/user.ts, packages/ai/src/agents/classes/index.ts, packages/ai/src/agents/index.ts
Adds UserAgent, updates RootAgent to use agentType, changes constructor/getter usage (tools getter), removes static create; re-exports UserAgent.
Sub-agent tooling core & client tool API
packages/ai/src/agents/tools/base.ts, packages/ai/src/agents/tools/user.ts, packages/ai/src/tools/models/client.ts, packages/ai/src/agents/tools/index.ts
Adds BaseSubAgentTool (streaming/tool-call orchestration), implements UserAgentTool, makes ClientTool generic and adds OnToolCallHandler; re-exports user tools.
Tool registry & utilities
packages/ai/src/agents/tool-lookup.ts, packages/ai/src/tools/toolset.ts, packages/ai/src/tools/toolset-utils.ts, packages/ai/src/tools/index.ts, apps/web/client/src/components/tools/tools.ts
Centralizes tool lists (allTools, TOOLS_MAP), adds createToolSet and getAvailableTools(agentType, chatType); client-side handleToolCall updated to be agent-aware and support nested on-tool calls.
Chat/core streaming & providers
packages/ai/src/agents/models/base.ts, packages/ai/src/agents/streamer.ts, packages/ai/src/agents/onlook-chat.ts, packages/ai/src/chat/providers.ts
BaseAgent shifts to agentType + tools array; adds OnlookChatState/OnlookChat with throttled callbacks; AgentStreamer.streamText is no longer async; initModel and provider helpers become synchronous and return ModelConfig/LanguageModel directly; streaming metadata enriched.
Server-side mutations & sync changes
apps/web/client/src/server/api/routers/chat/conversation.ts, apps/web/client/src/server/api/routers/chat/suggestion.ts, apps/web/client/src/server/api/routers/project/project.ts, apps/web/client/src/app/api/chat/helpers/stream.ts
Upsert/update SQL flows adjusted (onConflictDoUpdate/merged set); multiple generate/generateTitle/generateName flows changed to destructure initModel synchronously (removed awaits).
Chat metadata & storage helpers
packages/models/src/chat/message/message.ts, apps/web/client/src/components/store/editor/chat/conversation.ts
Adds optional error?: string to ChatMetadata; adds upsertConversationInStorage method to ConversationManager.

Sequence Diagram(s)

sequenceDiagram
  participant UI
  participant ClientHook as useChat
  participant API as ChatRoute
  participant AgentFactory
  participant RootAgent
  participant UserAgent
  participant Streamer as AgentStreamer
  participant ToolRegistry
  participant ToolInstance

  UI->>ClientHook: send chat (agentType + messages)
  ClientHook->>API: POST /chat (validated body includes agentType)
  API->>AgentFactory: instantiate agent for agentType
  AgentFactory-->>RootAgent: create RootAgent (if ROOT)
  AgentFactory-->>UserAgent: create UserAgent (if USER)
  API->>Streamer: streamText(agentInstance, messages)
  Streamer->>ToolRegistry: getAvailableTools(agentType, chatType)
  Streamer->>ToolInstance: tool.handle(..., getOnToolCall)
  ToolInstance->>Streamer: invoke nested on-tool calls via getOnToolCall
  Streamer->>UI: stream messages + metadata (agentType, usage, finishReason)
Loading
sequenceDiagram
  participant Caller
  participant initModel

  rect rgb(240,248,255)
    Caller->>initModel: initModel(opts)
    initModel-->>Caller: ModelConfig (synchronous return)
  end

  note right of Caller: Previously returned a Promise and was awaited
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

Possibly related PRs

Suggested reviewers

  • Kitenite

Poem

"I nibble on a change, so spry and smart,
Root and User agents hop and start.
Tools assemble, messages hum,
Models return — no await to come.
Hooray! — says the rabbit, with a joyful thump. 🐰"

Pre-merge checks and finishing touches

❌ Failed checks (1 warning)
Check name Status Explanation Resolution
Description Check ⚠️ Warning The pull request description includes a detailed list of refactors and features but omits several required template sections such as Related Issues, Testing, Screenshots, and Additional Notes, leaving key contextual information and verification steps incomplete. Please add a Related Issues section linking relevant issues, expand the description with a Testing section outlining verification steps, and optionally fill in the Screenshots and Additional Notes sections to conform to the repository’s PR template.
✅ Passed checks (2 passed)
Check name Status Explanation
Title Check ✅ Passed The title succinctly describes the primary addition of subagents in a clear, concise manner and aligns with the main objective of the pull request by highlighting the new subagent feature.
Docstring Coverage ✅ Passed No functions found in the changes. Docstring coverage check skipped.
✨ Finishing touches
  • 📝 Generate Docstrings
🧪 Generate unit tests
  • Create PR with unit tests
  • Post copyable unit tests in a comment

📜 Recent review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 0595cd5 and b9e91b5.

📒 Files selected for processing (3)
  • apps/web/client/src/app/api/chat/helpers/stream.ts (2 hunks)
  • packages/ai/src/agents/tool-lookup.ts (1 hunks)
  • packages/ai/src/agents/tools/base.ts (1 hunks)
🧰 Additional context used
📓 Path-based instructions (4)
**/*.{ts,tsx}

📄 CodeRabbit inference engine (AGENTS.md)

Do not use the any type unless necessary

Files:

  • packages/ai/src/agents/tool-lookup.ts
  • packages/ai/src/agents/tools/base.ts
  • apps/web/client/src/app/api/chat/helpers/stream.ts
{apps,packages}/**/*.{ts,tsx}

📄 CodeRabbit inference engine (CLAUDE.md)

Avoid using the any type unless absolutely necessary

Files:

  • packages/ai/src/agents/tool-lookup.ts
  • packages/ai/src/agents/tools/base.ts
  • apps/web/client/src/app/api/chat/helpers/stream.ts
apps/web/client/src/**/*.{ts,tsx}

📄 CodeRabbit inference engine (AGENTS.md)

apps/web/client/src/**/*.{ts,tsx}: Use path aliases @/* and ~/* for imports that map to apps/web/client/src/*
Avoid hardcoded user-facing text; use next-intl messages/hooks instead

Use path aliases @/* and ~/* for imports mapping to src/*

Files:

  • apps/web/client/src/app/api/chat/helpers/stream.ts
apps/web/client/src/app/**/*.{ts,tsx}

📄 CodeRabbit inference engine (CLAUDE.md)

Default to Server Components; add 'use client' only when using events, state/effects, browser APIs, or client-only libs

Files:

  • apps/web/client/src/app/api/chat/helpers/stream.ts
🧬 Code graph analysis (2)
packages/ai/src/agents/tools/base.ts (4)
packages/models/src/chat/message/message.ts (1)
  • ChatMessage (19-19)
apps/web/client/src/components/store/editor/engine.ts (1)
  • EditorEngine (34-140)
packages/ai/src/tools/models/client.ts (1)
  • OnToolCallHandler (6-6)
packages/ai/src/agents/onlook-chat.ts (3)
  • OnlookChat (114-136)
  • error (34-36)
  • error (38-41)
apps/web/client/src/app/api/chat/helpers/stream.ts (1)
packages/ai/src/chat/providers.ts (1)
  • initModel (14-51)
🔇 Additional comments (2)
apps/web/client/src/app/api/chat/helpers/stream.ts (1)

2-26: Synchronous initModel usage looks correct

Removing the await and destructuring the model directly matches the new synchronous initModel contract in @onlook/ai, so this call site stays consistent with the provider refactor.

packages/ai/src/agents/tool-lookup.ts (1)

24-59: Expose UserAgentTool so subagents are runnable

Keeping UserAgentTool commented out in allTools, readOnlyRootTools, editOnlyRootTools, and omitting it from userTools means no agent ever gets the tool that actually spawns subagents. As shipped, the new subagent architecture is unreachable at runtime. Please wire the tool into the exported sets so agents can use it.

 export const allTools = [
@@
-    TerminalCommandTool,
-    // UserAgentTool,
+    TerminalCommandTool,
+    UserAgentTool,
 ];
@@
 export const readOnlyRootTools = [
@@
-    CheckErrorsTool,
-    // UserAgentTool,
+    CheckErrorsTool,
+    UserAgentTool,
 ]
 const editOnlyRootTools = [
@@
-    TerminalCommandTool,
-    // UserAgentTool
+    TerminalCommandTool,
+    UserAgentTool
 ]
@@
 export const userTools = [
     ListBranchesTool,
     ListFilesTool,
+    UserAgentTool,
 ]

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 9

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (2)
apps/web/client/src/server/api/routers/chat/suggestion.ts (1)

21-44: Pass providerOptions from initModel to generateObject.

You compute providerOptions (e.g., OpenRouter transforms, Anthropic cache control) but don’t pass them, so those settings are ignored.

Apply this diff:

-            const { model, headers } = initModel({
+            const { model, headers, providerOptions } = initModel({
                 provider: LLMProvider.OPENROUTER,
                 model: OPENROUTER_MODELS.OPEN_AI_GPT_5_NANO,
             });
             const { object } = await generateObject({
                 model,
                 headers,
+                providerOptions,
                 schema: ChatSuggestionsSchema,
                 messages: [
apps/web/client/src/app/project/[id]/_hooks/use-chat/index.tsx (1)

140-145: Pass agentType on edited-message regenerate requests

The new chat pipeline expects agentType on every regenerate call. The sendMessage path was updated, but the edit flow still omits it, so edited messages now retry without an agent type—causing the server-side schema/agent lookup to fail. Please include the agent type here as well.

             void regenerate({
                 body: {
                     chatType,
                     conversationId,
+                    agentType,
                 },
             });
🧹 Nitpick comments (4)
packages/ai/src/agents/tool-lookup.ts (1)

4-25: Make tool arrays readonly to prevent accidental mutation.

Mark arrays as const (or use ReadonlyArray) to avoid runtime mutation and to preserve literal types.

Apply this diff (example for all arrays):

-export const allTools = [
+export const allTools = [
   /* ... */
-];
+] as const;
 
-export const readOnlyRootTools = [
+export const readOnlyRootTools = [
   /* ... */
-]
+] as const;
 
-const editOnlyRootTools = [
+const editOnlyRootTools = [
   /* ... */
-]
+] as const;
 
-export const rootTools = [...readOnlyRootTools, ...editOnlyRootTools];
+export const rootTools = [...readOnlyRootTools, ...editOnlyRootTools] as const;
 
-export const userTools = [
+export const userTools = [
   ListBranchesTool,
   ListFilesTool,
-]
+] as const;

Also applies to: 28-42, 43-52, 54-59

packages/ai/src/chat/providers.ts (1)

53-64: Optional: mirror API key check for Anthropic for early failure.

For parity with OpenRouter, consider validating ANTHROPIC_API_KEY and throwing a clear error.

packages/ai/src/agents/tools/user.ts (1)

16-18: Handle empty tool labels gracefully

When the tool hasn’t received an input yet, this returns Doggo: undefined, which leaks straight into the UI. Let’s fall back to a clean label until a message is actually available.

-    getLabel(input?: { message: string }): string {
-        return `Doggo: ${input?.message}`;
+    getLabel(input?: { message: string }): string {
+        const message = input?.message?.trim();
+        return message ? `Doggo: ${message}` : 'Doggo';
     }
packages/ai/src/agents/onlook-chat.ts (1)

21-23: Clone initial messages to avoid shared mutations

Right now we stash the caller’s initialMessages array by reference. If the caller keeps that array (common when rehydrating from previously cached chat state) and mutates it later, we silently mutate our internal state without firing any callbacks. Cloning up front keeps the state private and consistent with the deep-clone semantics you already rely on in replaceMessage.

     constructor(initialMessages: UI_MESSAGE[] = []) {
-        this.#messages = initialMessages;
+        this.#messages = initialMessages.map(message =>
+            structuredClone(message),
+        );
     }
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between bab9d85 and fdccec9.

📒 Files selected for processing (28)
  • apps/web/client/src/app/api/chat/helpers/stream.ts (2 hunks)
  • apps/web/client/src/app/api/chat/route.ts (4 hunks)
  • apps/web/client/src/app/project/[id]/_hooks/use-chat/index.tsx (5 hunks)
  • apps/web/client/src/components/tools/tools.ts (2 hunks)
  • apps/web/client/src/server/api/routers/chat/conversation.ts (4 hunks)
  • apps/web/client/src/server/api/routers/chat/suggestion.ts (1 hunks)
  • apps/web/client/src/server/api/routers/project/project.ts (1 hunks)
  • packages/ai/src/agents/classes/index.ts (1 hunks)
  • packages/ai/src/agents/classes/root.ts (2 hunks)
  • packages/ai/src/agents/classes/user.ts (1 hunks)
  • packages/ai/src/agents/index.ts (1 hunks)
  • packages/ai/src/agents/models/base.ts (2 hunks)
  • packages/ai/src/agents/onlook-chat.ts (1 hunks)
  • packages/ai/src/agents/streamer.ts (2 hunks)
  • packages/ai/src/agents/tool-lookup.ts (1 hunks)
  • packages/ai/src/agents/tools/base.ts (1 hunks)
  • packages/ai/src/agents/tools/index.ts (1 hunks)
  • packages/ai/src/agents/tools/user.ts (1 hunks)
  • packages/ai/src/chat/providers.ts (2 hunks)
  • packages/ai/src/tools/index.ts (1 hunks)
  • packages/ai/src/tools/models/client.ts (1 hunks)
  • packages/ai/src/tools/toolset-utils.ts (1 hunks)
  • packages/ai/src/tools/toolset.ts (1 hunks)
  • packages/db/src/defaults/conversation.ts (2 hunks)
  • packages/db/src/mappers/chat/conversation.ts (2 hunks)
  • packages/db/src/schema/chat/conversation.ts (1 hunks)
  • packages/models/src/chat/conversation/index.ts (1 hunks)
  • packages/models/src/chat/message/message.ts (1 hunks)
🧰 Additional context used
📓 Path-based instructions (7)
**/*.{ts,tsx}

📄 CodeRabbit inference engine (AGENTS.md)

Do not use the any type unless necessary

Files:

  • packages/ai/src/agents/tools/index.ts
  • packages/models/src/chat/message/message.ts
  • packages/ai/src/agents/classes/index.ts
  • packages/ai/src/tools/toolset-utils.ts
  • apps/web/client/src/app/api/chat/helpers/stream.ts
  • packages/ai/src/agents/streamer.ts
  • packages/db/src/mappers/chat/conversation.ts
  • packages/ai/src/tools/index.ts
  • packages/ai/src/agents/tool-lookup.ts
  • apps/web/client/src/server/api/routers/chat/conversation.ts
  • packages/ai/src/tools/models/client.ts
  • apps/web/client/src/components/tools/tools.ts
  • packages/ai/src/agents/models/base.ts
  • apps/web/client/src/app/project/[id]/_hooks/use-chat/index.tsx
  • packages/ai/src/agents/tools/base.ts
  • packages/models/src/chat/conversation/index.ts
  • packages/ai/src/agents/index.ts
  • packages/db/src/schema/chat/conversation.ts
  • packages/ai/src/agents/classes/user.ts
  • packages/ai/src/agents/classes/root.ts
  • packages/db/src/defaults/conversation.ts
  • packages/ai/src/agents/onlook-chat.ts
  • packages/ai/src/tools/toolset.ts
  • apps/web/client/src/server/api/routers/chat/suggestion.ts
  • apps/web/client/src/server/api/routers/project/project.ts
  • apps/web/client/src/app/api/chat/route.ts
  • packages/ai/src/agents/tools/user.ts
  • packages/ai/src/chat/providers.ts
{apps,packages}/**/*.{ts,tsx}

📄 CodeRabbit inference engine (CLAUDE.md)

Avoid using the any type unless absolutely necessary

Files:

  • packages/ai/src/agents/tools/index.ts
  • packages/models/src/chat/message/message.ts
  • packages/ai/src/agents/classes/index.ts
  • packages/ai/src/tools/toolset-utils.ts
  • apps/web/client/src/app/api/chat/helpers/stream.ts
  • packages/ai/src/agents/streamer.ts
  • packages/db/src/mappers/chat/conversation.ts
  • packages/ai/src/tools/index.ts
  • packages/ai/src/agents/tool-lookup.ts
  • apps/web/client/src/server/api/routers/chat/conversation.ts
  • packages/ai/src/tools/models/client.ts
  • apps/web/client/src/components/tools/tools.ts
  • packages/ai/src/agents/models/base.ts
  • apps/web/client/src/app/project/[id]/_hooks/use-chat/index.tsx
  • packages/ai/src/agents/tools/base.ts
  • packages/models/src/chat/conversation/index.ts
  • packages/ai/src/agents/index.ts
  • packages/db/src/schema/chat/conversation.ts
  • packages/ai/src/agents/classes/user.ts
  • packages/ai/src/agents/classes/root.ts
  • packages/db/src/defaults/conversation.ts
  • packages/ai/src/agents/onlook-chat.ts
  • packages/ai/src/tools/toolset.ts
  • apps/web/client/src/server/api/routers/chat/suggestion.ts
  • apps/web/client/src/server/api/routers/project/project.ts
  • apps/web/client/src/app/api/chat/route.ts
  • packages/ai/src/agents/tools/user.ts
  • packages/ai/src/chat/providers.ts
apps/web/client/src/**/*.{ts,tsx}

📄 CodeRabbit inference engine (AGENTS.md)

apps/web/client/src/**/*.{ts,tsx}: Use path aliases @/* and ~/* for imports that map to apps/web/client/src/*
Avoid hardcoded user-facing text; use next-intl messages/hooks instead

Use path aliases @/* and ~/* for imports mapping to src/*

Files:

  • apps/web/client/src/app/api/chat/helpers/stream.ts
  • apps/web/client/src/server/api/routers/chat/conversation.ts
  • apps/web/client/src/components/tools/tools.ts
  • apps/web/client/src/app/project/[id]/_hooks/use-chat/index.tsx
  • apps/web/client/src/server/api/routers/chat/suggestion.ts
  • apps/web/client/src/server/api/routers/project/project.ts
  • apps/web/client/src/app/api/chat/route.ts
apps/web/client/src/app/**/*.{ts,tsx}

📄 CodeRabbit inference engine (CLAUDE.md)

Default to Server Components; add 'use client' only when using events, state/effects, browser APIs, or client-only libs

Files:

  • apps/web/client/src/app/api/chat/helpers/stream.ts
  • apps/web/client/src/app/project/[id]/_hooks/use-chat/index.tsx
  • apps/web/client/src/app/api/chat/route.ts
apps/web/client/src/server/api/routers/**/*.ts

📄 CodeRabbit inference engine (AGENTS.md)

apps/web/client/src/server/api/routers/**/*.ts: Place tRPC routers under apps/web/client/src/server/api/routers/**
Use publicProcedure/protectedProcedure from apps/web/client/src/server/api/trpc.ts and validate inputs with Zod
Return plain objects/arrays; rely on SuperJSON for serialization in tRPC procedures

apps/web/client/src/server/api/routers/**/*.ts: Place tRPC routers under src/server/api/routers/**
Use publicProcedure/protectedProcedure from src/server/api/trpc.ts and validate inputs with Zod
Return plain objects/arrays; rely on SuperJSON for serialization

Files:

  • apps/web/client/src/server/api/routers/chat/conversation.ts
  • apps/web/client/src/server/api/routers/chat/suggestion.ts
  • apps/web/client/src/server/api/routers/project/project.ts
apps/web/client/src/app/**/*.tsx

📄 CodeRabbit inference engine (AGENTS.md)

apps/web/client/src/app/**/*.tsx: Default to Server Components; add 'use client' when using events, state/effects, browser APIs, or client‑only libraries
Do not use process.env in client code; import env from @/env instead

Avoid hardcoded user-facing text; use next-intl messages/hooks

Files:

  • apps/web/client/src/app/project/[id]/_hooks/use-chat/index.tsx
apps/web/client/src/**/*.tsx

📄 CodeRabbit inference engine (AGENTS.md)

apps/web/client/src/**/*.tsx: Create MobX store instances with useState(() => new Store()) for stable references across renders
Keep the active MobX store in a useRef and perform async cleanup with setTimeout(() => storeRef.current?.clear(), 0) to avoid route-change races
Avoid useMemo for creating MobX store instances
Avoid putting the MobX store instance in effect dependency arrays if it causes loops; split concerns by domain

apps/web/client/src/**/*.tsx: Create MobX store instances with useState(() => new Store()) for stable identities across renders
Keep the active MobX store in a useRef and clean up asynchronously with setTimeout(() => storeRef.current?.clear(), 0)
Do not use useMemo to create MobX stores
Avoid placing MobX store instances in effect dependency arrays if it causes loops; split concerns instead
observer components must be client components; place a single client boundary at the feature entry; child observers need not repeat 'use client'

Files:

  • apps/web/client/src/app/project/[id]/_hooks/use-chat/index.tsx
🧠 Learnings (2)
📚 Learning: 2025-09-14T01:44:21.209Z
Learnt from: CR
PR: onlook-dev/onlook#0
File: AGENTS.md:0-0
Timestamp: 2025-09-14T01:44:21.209Z
Learning: Applies to apps/web/client/src/server/api/root.ts : Export all tRPC routers from apps/web/client/src/server/api/root.ts

Applied to files:

  • packages/ai/src/agents/classes/index.ts
📚 Learning: 2025-09-16T19:22:52.461Z
Learnt from: CR
PR: onlook-dev/onlook#0
File: CLAUDE.md:0-0
Timestamp: 2025-09-16T19:22:52.461Z
Learning: Applies to apps/web/client/src/server/api/root.ts : Export all tRPC routers from src/server/api/root.ts

Applied to files:

  • packages/ai/src/agents/classes/index.ts
🧬 Code graph analysis (17)
apps/web/client/src/app/api/chat/helpers/stream.ts (1)
packages/ai/src/chat/providers.ts (1)
  • initModel (14-51)
packages/ai/src/agents/streamer.ts (1)
packages/ai/src/stream/index.ts (1)
  • convertToStreamMessages (5-23)
packages/db/src/mappers/chat/conversation.ts (1)
packages/models/src/chat/conversation/index.ts (1)
  • ChatConversation (8-16)
packages/ai/src/agents/tool-lookup.ts (1)
packages/ai/src/agents/tools/user.ts (1)
  • UserAgentTool (5-19)
apps/web/client/src/server/api/routers/chat/conversation.ts (2)
packages/db/src/schema/chat/conversation.ts (1)
  • conversations (13-23)
packages/ai/src/chat/providers.ts (1)
  • initModel (14-51)
packages/ai/src/tools/models/client.ts (2)
packages/models/src/chat/message/message.ts (1)
  • ChatMessage (19-19)
apps/web/client/src/components/store/editor/engine.ts (1)
  • EditorEngine (34-140)
apps/web/client/src/components/tools/tools.ts (3)
apps/web/client/src/components/store/editor/engine.ts (1)
  • EditorEngine (34-140)
packages/ai/src/tools/toolset.ts (1)
  • getAvailableTools (8-17)
packages/ai/src/tools/models/client.ts (1)
  • OnToolCallHandler (6-6)
packages/ai/src/agents/models/base.ts (2)
packages/models/src/llm/index.ts (1)
  • ModelConfig (34-39)
packages/ai/src/tools/toolset-utils.ts (1)
  • createToolSet (4-9)
apps/web/client/src/app/project/[id]/_hooks/use-chat/index.tsx (2)
packages/db/src/schema/chat/conversation.ts (1)
  • agentType (11-11)
apps/web/client/src/components/tools/tools.ts (1)
  • handleToolCall (8-46)
packages/ai/src/agents/tools/base.ts (4)
packages/models/src/chat/message/message.ts (1)
  • ChatMessage (19-19)
apps/web/client/src/components/store/editor/engine.ts (1)
  • EditorEngine (34-140)
packages/ai/src/tools/models/client.ts (1)
  • OnToolCallHandler (6-6)
packages/ai/src/agents/onlook-chat.ts (7)
  • OnlookChat (114-136)
  • messages (43-45)
  • messages (47-50)
  • status (25-27)
  • status (29-32)
  • error (34-36)
  • error (38-41)
packages/ai/src/agents/classes/user.ts (3)
packages/models/src/llm/index.ts (1)
  • ModelConfig (34-39)
packages/ai/src/chat/providers.ts (1)
  • initModel (14-51)
packages/ai/src/agents/tool-lookup.ts (1)
  • userTools (56-59)
packages/ai/src/agents/classes/root.ts (3)
packages/ai/src/agents/tool-lookup.ts (2)
  • readOnlyRootTools (28-42)
  • rootTools (54-54)
packages/models/src/llm/index.ts (1)
  • ModelConfig (34-39)
packages/ai/src/chat/providers.ts (1)
  • initModel (14-51)
packages/ai/src/tools/toolset.ts (2)
packages/ai/src/agents/tool-lookup.ts (3)
  • rootTools (54-54)
  • userTools (56-59)
  • allTools (4-25)
packages/ai/src/tools/toolset-utils.ts (1)
  • createToolSet (4-9)
apps/web/client/src/server/api/routers/chat/suggestion.ts (1)
packages/ai/src/chat/providers.ts (1)
  • initModel (14-51)
apps/web/client/src/server/api/routers/project/project.ts (1)
packages/ai/src/chat/providers.ts (1)
  • initModel (14-51)
apps/web/client/src/app/api/chat/route.ts (4)
packages/db/src/schema/chat/conversation.ts (1)
  • agentType (11-11)
apps/web/client/src/app/api/chat/helpers/usage.ts (1)
  • incrementUsage (43-65)
packages/ai/src/agents/classes/root.ts (1)
  • RootAgent (10-58)
packages/ai/src/agents/classes/user.ts (1)
  • UserAgent (6-17)
packages/ai/src/chat/providers.ts (1)
packages/models/src/llm/index.ts (3)
  • InitialModelPayload (27-32)
  • ModelConfig (34-39)
  • MODEL_MAX_TOKENS (41-49)
🪛 Biome (2.1.2)
packages/ai/src/agents/tools/base.ts

[error] 71-151: Promise executor functions should not be async.

(lint/suspicious/noAsyncPromiseExecutor)

🔇 Additional comments (6)
packages/ai/src/tools/index.ts (1)

4-4: LGTM — re-export looks good.

packages/ai/src/agents/tools/index.ts (1)

1-1: LGTM — exposing user tools via barrel is fine.

packages/ai/src/chat/providers.ts (1)

14-51: LGTM — sync initModel and provider getters are consistent.

Signatures and return types align with downstream usage; OpenRouter key check is good.

packages/ai/src/agents/index.ts (1)

3-3: LGTM — exporting tools from agents barrel is fine.

packages/ai/src/agents/classes/index.ts (1)

1-2: LGTM — public UserAgent export added.

packages/ai/src/agents/streamer.ts (1)

19-21: Confirm agent.streamText return type
Ensure this.agent.streamText(...) returns a synchronous result object exposing .toUIMessageStreamResponse, not a Promise.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 6

🧹 Nitpick comments (6)
apps/web/client/src/app/api/chat/route.ts (2)

102-106: Move usage increment logic after agent instantiation for consistency.

The current code increments usage before creating the agent, but the agent creation could fail (e.g., if chatType is undefined despite validation). Consider moving the usage increment after successful agent creation to maintain logical consistency.

Apply this diff to move usage increment after agent creation:

         let agent: BaseAgent;
         if (agentType === AgentType.ROOT) {
-            if (chatType === ChatType.EDIT) {
-                usageRecord = await incrementUsage(req, traceId);
-            }
-
             agent = new RootAgent(chatType!);
+            
+            if (chatType === ChatType.EDIT) {
+                usageRecord = await incrementUsage(req, traceId);
+            }
         } else if (agentType === AgentType.USER) {

123-124: Redundant nullish coalescing due to schema defaults.

The agentType ?? AgentType.ROOT is redundant since the schema already provides AgentType.ROOT as the default value. The chatType ?? "null" is appropriate for telemetry purposes when chatType is undefined for non-ROOT agents.

Apply this diff to remove the redundant nullish coalescing:

-                        agentType: agentType ?? AgentType.ROOT,
+                        agentType: agentType,
packages/ai/src/agents/tools/base.ts (4)

36-38: Prefer sending the user’s message, not a JSON blob, by default

Defaulting to JSON.stringify(input) makes the sub-agent read JSON text. Use the message field; derived tools can override if needed.

-    createMessage(input: TInput): string {
-        return JSON.stringify(input, null, 2);
-    }
+    createMessage(input: TInput): string {
+        return input.message;
+    }

95-109: Remove any casts; narrow with type guards

Multiple (part as any) casts bypass type safety. Use in-guards or type predicates for state and tool parts.

Example (no behavior change):

  • Define once near the top:
type MaybeState = { state?: string };
const hasState = (p: unknown): p is MaybeState =>
  !!p && typeof p === 'object' && 'state' in (p as object);
  • Then:
const toolCallParts = message.parts.filter(p => String(p.type).startsWith('tool-'));
const hasStreamingText = message.parts.some(
  p => p.type === 'text' && hasState(p) && p.state !== 'done',
);
const hasIncompleteToolCalls =
  message.role === 'assistant' &&
  toolCallParts.some(p => p.type === 'tool-call' || (hasState(p) && p.state !== 'output-available' && p.state !== 'output-error'));

As per coding guidelines


29-31: Instance state makes concurrency unsafe

conversationId and messages are mutable on the tool instance. Reusing a tool concurrently can interleave messages. Document single-flight usage or isolate state per call (e.g., clone messages snapshot for the chat session, or create a new tool instance per run).


62-64: Hook up context from editorEngine (TODO)

You noted this TODO. I can wire in editor-derived context (e.g., current file, selection, git branch) into metadata.context.

Want me to draft a minimal context collector and integrate it here?

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between fdccec9 and 1b60d64.

📒 Files selected for processing (5)
  • apps/web/client/src/app/api/chat/route.ts (4 hunks)
  • packages/ai/src/agents/streamer.ts (2 hunks)
  • packages/ai/src/agents/tool-lookup.ts (1 hunks)
  • packages/ai/src/agents/tools/base.ts (1 hunks)
  • packages/ai/src/tools/toolset.ts (1 hunks)
🚧 Files skipped from review as they are similar to previous changes (2)
  • packages/ai/src/agents/tool-lookup.ts
  • packages/ai/src/agents/streamer.ts
🧰 Additional context used
📓 Path-based instructions (4)
apps/web/client/src/**/*.{ts,tsx}

📄 CodeRabbit inference engine (AGENTS.md)

apps/web/client/src/**/*.{ts,tsx}: Use path aliases @/* and ~/* for imports that map to apps/web/client/src/*
Avoid hardcoded user-facing text; use next-intl messages/hooks instead

Use path aliases @/* and ~/* for imports mapping to src/*

Files:

  • apps/web/client/src/app/api/chat/route.ts
**/*.{ts,tsx}

📄 CodeRabbit inference engine (AGENTS.md)

Do not use the any type unless necessary

Files:

  • apps/web/client/src/app/api/chat/route.ts
  • packages/ai/src/tools/toolset.ts
  • packages/ai/src/agents/tools/base.ts
apps/web/client/src/app/**/*.{ts,tsx}

📄 CodeRabbit inference engine (CLAUDE.md)

Default to Server Components; add 'use client' only when using events, state/effects, browser APIs, or client-only libs

Files:

  • apps/web/client/src/app/api/chat/route.ts
{apps,packages}/**/*.{ts,tsx}

📄 CodeRabbit inference engine (CLAUDE.md)

Avoid using the any type unless absolutely necessary

Files:

  • apps/web/client/src/app/api/chat/route.ts
  • packages/ai/src/tools/toolset.ts
  • packages/ai/src/agents/tools/base.ts
🧬 Code graph analysis (3)
apps/web/client/src/app/api/chat/route.ts (4)
packages/db/src/schema/chat/conversation.ts (1)
  • agentType (11-11)
apps/web/client/src/app/api/chat/helpers/usage.ts (1)
  • incrementUsage (43-65)
packages/ai/src/agents/classes/root.ts (1)
  • RootAgent (10-58)
packages/ai/src/agents/classes/user.ts (1)
  • UserAgent (6-17)
packages/ai/src/tools/toolset.ts (2)
packages/ai/src/agents/tool-lookup.ts (4)
  • readOnlyRootTools (28-42)
  • rootTools (54-54)
  • userTools (56-59)
  • allTools (4-25)
packages/ai/src/tools/toolset-utils.ts (1)
  • createToolSet (4-9)
packages/ai/src/agents/tools/base.ts (5)
packages/models/src/chat/message/message.ts (1)
  • ChatMessage (19-19)
apps/web/client/src/components/store/editor/engine.ts (1)
  • EditorEngine (34-140)
packages/ai/src/tools/models/client.ts (1)
  • OnToolCallHandler (6-6)
packages/ai/src/agents/onlook-chat.ts (5)
  • OnlookChat (114-136)
  • messages (43-45)
  • messages (47-50)
  • error (34-36)
  • error (38-41)
apps/web/client/src/trpc/react.tsx (1)
  • api (23-23)
🔇 Additional comments (7)
packages/ai/src/tools/toolset.ts (1)

6-13: ASK sessions correctly gated to read-only tools

Line 9 now returns readOnlyRootTools for ChatType.ASK, so ASK conversations stay on the safe, read-only toolset while other ROOT sessions keep edit tools. Thanks for restoring the guardrail.

apps/web/client/src/app/api/chat/route.ts (3)

61-81: The schema validation properly addresses the previous review feedback.

The superRefine implementation correctly ensures that chatType is required when agentType is ROOT and forbidden otherwise. This prevents the undefined chatType issue that was flagged in the previous review.


106-106: Non-null assertion is safe due to schema validation.

The chatType! non-null assertion is justified here because the schema validation ensures that chatType is defined when agentType is ROOT. The superRefine validation prevents this path from being reached with undefined chatType.


66-67: Use proper ZodIssueCode in Zod 4.

In Zod 4, ZodIssueCode is no longer an enum and the proper way to specify custom error codes has changed. The string 'custom' should be used directly instead of ZodIssueCode.custom.

Apply this diff to use the correct error code format:

-                code: 'custom',
+                code: z.ZodIssueCode.custom,

Or alternatively, use the string directly:

                code: 'custom',

The current implementation is actually correct for Zod 4. No change needed.

packages/ai/src/agents/tools/base.ts (3)

83-86: Correct binding for addToolResult

Passing onlookChat.addToolResult.bind(onlookChat) fixes the previous this-loss bug. Good catch.


88-90: Reduce noisy console logs or gate behind a debug flag

Replace ad-hoc console.log/error with a configurable logger or guard with NODE_ENV/debug toggle to avoid leaking internal state in production.

Example:

const debug = process.env.NODE_ENV !== 'production' && process.env.ONLOOK_DEBUG === '1';
if (debug) console.log('OnlookChat - Finish:', options);

Also applies to: 96-96, 113-116, 131-131, 134-134, 153-156


144-151: Ensure onFinish never fires before promise handlers are set
Handlers are bound before calling sendMessage(), but if OnlookChat’s constructor or AbstractChat ever invokes onFinish synchronously it would reference uninitialized promiseResolve/promiseReject and throw—verify the provider never triggers onFinish in the constructor.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 0

🧹 Nitpick comments (1)
packages/ai/src/tools/toolset-utils.ts (1)

5-8: Prefer reduce generics over assertion

We can drop the as ToolSet by letting reduce carry the accumulator type, which tightens typing without the cast.

-export function createToolSet(toolClasses: Array<{ toolName: string; getAITool: () => ToolSet[string] }>): ToolSet {
-    return toolClasses.reduce((acc, toolClass) => {
+export function createToolSet(toolClasses: Array<{ toolName: string; getAITool: () => ToolSet[string] }>): ToolSet {
+    return toolClasses.reduce<ToolSet>((acc, toolClass) => {
         acc[toolClass.toolName] = toolClass.getAITool();
         return acc;
-    }, {} as ToolSet);
+    }, {});
}
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 1b60d64 and 41eb10b.

📒 Files selected for processing (2)
  • apps/web/client/src/app/project/[id]/_hooks/use-chat/index.tsx (6 hunks)
  • packages/ai/src/tools/toolset-utils.ts (1 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
  • apps/web/client/src/app/project/[id]/_hooks/use-chat/index.tsx
🧰 Additional context used
📓 Path-based instructions (2)
**/*.{ts,tsx}

📄 CodeRabbit inference engine (AGENTS.md)

Do not use the any type unless necessary

Files:

  • packages/ai/src/tools/toolset-utils.ts
{apps,packages}/**/*.{ts,tsx}

📄 CodeRabbit inference engine (CLAUDE.md)

Avoid using the any type unless absolutely necessary

Files:

  • packages/ai/src/tools/toolset-utils.ts

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 41eb10b and 6f4ca6a.

📒 Files selected for processing (3)
  • apps/web/client/src/app/api/chat/route.ts (4 hunks)
  • packages/ai/src/agents/tools/base.ts (1 hunks)
  • packages/scripts/src/backend.ts (5 hunks)
🧰 Additional context used
📓 Path-based instructions (4)
**/*.{ts,tsx}

📄 CodeRabbit inference engine (AGENTS.md)

Do not use the any type unless necessary

Files:

  • packages/ai/src/agents/tools/base.ts
  • packages/scripts/src/backend.ts
  • apps/web/client/src/app/api/chat/route.ts
{apps,packages}/**/*.{ts,tsx}

📄 CodeRabbit inference engine (CLAUDE.md)

Avoid using the any type unless absolutely necessary

Files:

  • packages/ai/src/agents/tools/base.ts
  • packages/scripts/src/backend.ts
  • apps/web/client/src/app/api/chat/route.ts
apps/web/client/src/**/*.{ts,tsx}

📄 CodeRabbit inference engine (AGENTS.md)

apps/web/client/src/**/*.{ts,tsx}: Use path aliases @/* and ~/* for imports that map to apps/web/client/src/*
Avoid hardcoded user-facing text; use next-intl messages/hooks instead

Use path aliases @/* and ~/* for imports mapping to src/*

Files:

  • apps/web/client/src/app/api/chat/route.ts
apps/web/client/src/app/**/*.{ts,tsx}

📄 CodeRabbit inference engine (CLAUDE.md)

Default to Server Components; add 'use client' only when using events, state/effects, browser APIs, or client-only libs

Files:

  • apps/web/client/src/app/api/chat/route.ts
🧠 Learnings (3)
📚 Learning: 2025-09-16T19:22:52.461Z
Learnt from: CR
PR: onlook-dev/onlook#0
File: CLAUDE.md:0-0
Timestamp: 2025-09-16T19:22:52.461Z
Learning: Applies to apps/web/client/src/env.ts : Define and validate environment variables in src/env.ts via t3-oss/env-nextjs

Applied to files:

  • packages/scripts/src/backend.ts
📚 Learning: 2025-09-14T01:44:21.209Z
Learnt from: CR
PR: onlook-dev/onlook#0
File: AGENTS.md:0-0
Timestamp: 2025-09-14T01:44:21.209Z
Learning: Applies to apps/web/client/src/env.ts : Define and validate environment variables via t3-oss/env-nextjs in apps/web/client/src/env.ts

Applied to files:

  • packages/scripts/src/backend.ts
📚 Learning: 2025-09-16T19:22:52.461Z
Learnt from: CR
PR: onlook-dev/onlook#0
File: CLAUDE.md:0-0
Timestamp: 2025-09-16T19:22:52.461Z
Learning: Applies to apps/web/client/src/env.ts : Expose browser environment variables with NEXT_PUBLIC_* and declare them in the client schema

Applied to files:

  • packages/scripts/src/backend.ts
🧬 Code graph analysis (3)
packages/ai/src/agents/tools/base.ts (5)
packages/models/src/chat/message/message.ts (1)
  • ChatMessage (19-19)
apps/web/client/src/components/store/editor/engine.ts (1)
  • EditorEngine (34-140)
packages/ai/src/tools/models/client.ts (1)
  • OnToolCallHandler (6-6)
packages/ai/src/agents/onlook-chat.ts (5)
  • OnlookChat (114-136)
  • messages (43-45)
  • messages (47-50)
  • error (34-36)
  • error (38-41)
apps/web/client/src/trpc/react.tsx (1)
  • api (23-23)
packages/scripts/src/backend.ts (3)
.vscode/.debug.script.mjs (1)
  • __dirname (8-8)
packages/scripts/src/index.ts (1)
  • rootDir (12-12)
packages/scripts/src/helpers.ts (1)
  • writeEnvFile (116-176)
apps/web/client/src/app/api/chat/route.ts (4)
packages/db/src/schema/chat/conversation.ts (1)
  • agentType (11-11)
apps/web/client/src/app/api/chat/helpers/usage.ts (1)
  • incrementUsage (43-65)
packages/ai/src/agents/classes/root.ts (1)
  • RootAgent (10-58)
packages/ai/src/agents/classes/user.ts (1)
  • UserAgent (6-17)
🔇 Additional comments (4)
apps/web/client/src/app/api/chat/route.ts (1)

55-67: Restore stricter chatType validation for ROOT agents

With the current schema, a ROOT request can omit chatType, slip through validation, and we end up calling new RootAgent(chatType!) with undefined. That silently downgrades to the EDIT defaults and, more importantly, bypasses the usage increment because the later guard chatType === ChatType.EDIT never fires. This is the same issue called out in the earlier review and still needs to be fixed. Please require chatType whenever agentType is ROOT and continue forbidding it for non-root agents.

-}).refine((data) => {
-    // Only allow chatType if agentType is ROOT
-    if (data.chatType !== undefined && data.agentType !== AgentType.ROOT) {
-        return false;
-    }
-    return true;
-}, { message: "chatType is only allowed if agentType is root" });
+}).superRefine((data, ctx) => {
+    if (data.agentType === AgentType.ROOT) {
+        if (data.chatType === undefined) {
+            ctx.addIssue({
+                code: z.ZodIssueCode.custom,
+                path: ['chatType'],
+                message: 'chatType is required when agentType is root',
+            });
+        }
+        return;
+    }
+    if (data.chatType !== undefined) {
+        ctx.addIssue({
+            code: z.ZodIssueCode.custom,
+            path: ['chatType'],
+            message: 'chatType is only allowed if agentType is root',
+        });
+    }
+});
packages/ai/src/agents/tools/base.ts (3)

188-191: Assert conversationId is non-null instead of hiding it behind a cast

At Line 189 we still widen conversationId to string | null and then erase the mismatch with as TOutput. Because _conversationId is guaranteed earlier, assert it explicitly (or throw) so TypeScript can enforce the invariant and we keep the cast focused on the generic.

-        return {
-            conversationId: this.conversationId,
+        return {
+            conversationId: this.conversationId!,
             response,
         } as TOutput;

122-134: Resolve completion when the final part is a tool result

Line 122 currently insists the final part be text with state === 'done', so any completion that ends with a tool output (tool-result, tool-error, etc.) never flips lastPartComplete to true. In that case promiseResolve is never called and the awaiting sub-agent hangs indefinitely. Please treat completed tool outputs as final (or drop the check entirely) so we resolve once isIncomplete is false.

-                const lastPart = message.parts[message.parts.length - 1];
-                const lastPartComplete = lastPart &&
-                    (lastPart.type === 'text' && (lastPart as any).state === 'done');
+                const lastPart = message.parts[message.parts.length - 1];
+                const lastPartState = (lastPart as any)?.state;
+                const lastPartComplete =
+                    (lastPart?.type === 'text' && lastPartState === 'done') ||
+                    (typeof lastPart?.type === 'string' &&
+                        lastPart.type.startsWith('tool-') &&
+                        (lastPartState === 'output-available' || lastPartState === 'output-error'));

176-178: Fix metadata error handling to avoid returning undefined

Line 176 treats metadata.error !== null as the guard, so when the value is undefined we still try to JSON.stringify it; the result is undefined, which then propagates to response if no later branch overwrites it. When the message has no parts (or parsing a tool-only response) we end up returning { response: undefined }, violating the declared contract. Guard with a nullish check and preserve the empty-string fallback.

-        if (agentResponse.metadata?.error !== null) {
-            response = JSON.stringify(agentResponse.metadata!.error, null, 2);
-        }
+        const errorMetadata = agentResponse.metadata?.error;
+        if (errorMetadata != null) {
+            response = JSON.stringify(errorMetadata, null, 2) ?? '';
+        }

@Kitenite Kitenite changed the title Add Subagents feat: add subagents Sep 26, 2025
Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
packages/ai/src/agents/streamer.ts (1)

16-24: Reintroduce awaiting of agent.streamText result

BaseAgent.streamText still resolves a Promise from ai.streamText. Dropping async/await means result is a Promise, so result.toUIMessageStreamResponse(...) will throw (Promise has no such method). This is a hard runtime break for every stream. Please restore the async function and await the call.

-    streamText(messages: ChatMessage[], { streamTextConfig, toUIMessageStreamResponseConfig }: { streamTextConfig: Omit<Partial<Parameters<typeof streamText>[0]>, 'model' | 'headers' | 'tools' | 'stopWhen' | 'messages' | 'prompt'>, toUIMessageStreamResponseConfig: UIMessageStreamOptions<ChatMessage> }) {
+    async streamText(messages: ChatMessage[], { streamTextConfig, toUIMessageStreamResponseConfig }: { streamTextConfig: Omit<Partial<Parameters<typeof streamText>[0]>, 'model' | 'headers' | 'tools' | 'stopWhen' | 'messages' | 'prompt'>, toUIMessageStreamResponseConfig: UIMessageStreamOptions<ChatMessage> }) {
         const conversationId = this.conversationId;
 
-        const result = this.agent.streamText(convertToStreamMessages(messages), streamTextConfig);
+        const result = await this.agent.streamText(convertToStreamMessages(messages), streamTextConfig);
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 5aa588f and fac9d29.

⛔ Files ignored due to path filters (1)
  • bun.lock is excluded by !**/*.lock
📒 Files selected for processing (6)
  • apps/web/client/src/app/api/chat/route.ts (4 hunks)
  • apps/web/client/src/server/api/routers/chat/conversation.ts (2 hunks)
  • packages/ai/src/agents/streamer.ts (2 hunks)
  • packages/ai/src/agents/tools/base.ts (1 hunks)
  • packages/db/src/schema/chat/conversation.ts (2 hunks)
  • packages/scripts/src/backend.ts (12 hunks)
🧰 Additional context used
📓 Path-based instructions (5)
**/*.{ts,tsx}

📄 CodeRabbit inference engine (AGENTS.md)

Do not use the any type unless necessary

Files:

  • packages/db/src/schema/chat/conversation.ts
  • packages/ai/src/agents/tools/base.ts
  • apps/web/client/src/server/api/routers/chat/conversation.ts
  • packages/scripts/src/backend.ts
  • packages/ai/src/agents/streamer.ts
  • apps/web/client/src/app/api/chat/route.ts
{apps,packages}/**/*.{ts,tsx}

📄 CodeRabbit inference engine (CLAUDE.md)

Avoid using the any type unless absolutely necessary

Files:

  • packages/db/src/schema/chat/conversation.ts
  • packages/ai/src/agents/tools/base.ts
  • apps/web/client/src/server/api/routers/chat/conversation.ts
  • packages/scripts/src/backend.ts
  • packages/ai/src/agents/streamer.ts
  • apps/web/client/src/app/api/chat/route.ts
apps/web/client/src/server/api/routers/**/*.ts

📄 CodeRabbit inference engine (AGENTS.md)

apps/web/client/src/server/api/routers/**/*.ts: Place tRPC routers under apps/web/client/src/server/api/routers/**
Use publicProcedure/protectedProcedure from apps/web/client/src/server/api/trpc.ts and validate inputs with Zod
Return plain objects/arrays; rely on SuperJSON for serialization in tRPC procedures

apps/web/client/src/server/api/routers/**/*.ts: Place tRPC routers under src/server/api/routers/**
Use publicProcedure/protectedProcedure from src/server/api/trpc.ts and validate inputs with Zod
Return plain objects/arrays; rely on SuperJSON for serialization

Files:

  • apps/web/client/src/server/api/routers/chat/conversation.ts
apps/web/client/src/**/*.{ts,tsx}

📄 CodeRabbit inference engine (AGENTS.md)

apps/web/client/src/**/*.{ts,tsx}: Use path aliases @/* and ~/* for imports that map to apps/web/client/src/*
Avoid hardcoded user-facing text; use next-intl messages/hooks instead

Use path aliases @/* and ~/* for imports mapping to src/*

Files:

  • apps/web/client/src/server/api/routers/chat/conversation.ts
  • apps/web/client/src/app/api/chat/route.ts
apps/web/client/src/app/**/*.{ts,tsx}

📄 CodeRabbit inference engine (CLAUDE.md)

Default to Server Components; add 'use client' only when using events, state/effects, browser APIs, or client-only libs

Files:

  • apps/web/client/src/app/api/chat/route.ts
🧠 Learnings (5)
📚 Learning: 2025-09-14T01:44:21.209Z
Learnt from: CR
PR: onlook-dev/onlook#0
File: AGENTS.md:0-0
Timestamp: 2025-09-14T01:44:21.209Z
Learning: Applies to apps/web/client/src/env.ts : Expose browser environment variables with NEXT_PUBLIC_* and declare them in the client schema

Applied to files:

  • packages/scripts/src/backend.ts
📚 Learning: 2025-09-16T19:22:52.461Z
Learnt from: CR
PR: onlook-dev/onlook#0
File: CLAUDE.md:0-0
Timestamp: 2025-09-16T19:22:52.461Z
Learning: Applies to apps/web/client/src/env.ts : Define and validate environment variables in src/env.ts via t3-oss/env-nextjs

Applied to files:

  • packages/scripts/src/backend.ts
📚 Learning: 2025-09-14T01:44:21.209Z
Learnt from: CR
PR: onlook-dev/onlook#0
File: AGENTS.md:0-0
Timestamp: 2025-09-14T01:44:21.209Z
Learning: Applies to apps/web/client/src/env.ts : Define and validate environment variables via t3-oss/env-nextjs in apps/web/client/src/env.ts

Applied to files:

  • packages/scripts/src/backend.ts
📚 Learning: 2025-09-14T01:44:21.209Z
Learnt from: CR
PR: onlook-dev/onlook#0
File: AGENTS.md:0-0
Timestamp: 2025-09-14T01:44:21.209Z
Learning: Applies to apps/web/client/next.config.ts : Import ./src/env in Next.js config to enforce env validation at build time

Applied to files:

  • packages/scripts/src/backend.ts
📚 Learning: 2025-09-16T19:22:52.461Z
Learnt from: CR
PR: onlook-dev/onlook#0
File: CLAUDE.md:0-0
Timestamp: 2025-09-16T19:22:52.461Z
Learning: Applies to apps/web/client/next.config.ts : Import ./src/env in next.config.ts to enforce env validation at build time

Applied to files:

  • packages/scripts/src/backend.ts
🧬 Code graph analysis (5)
packages/ai/src/agents/tools/base.ts (5)
packages/models/src/chat/message/message.ts (1)
  • ChatMessage (19-19)
apps/web/client/src/components/store/editor/engine.ts (1)
  • EditorEngine (34-140)
packages/ai/src/tools/models/client.ts (1)
  • OnToolCallHandler (6-6)
packages/ai/src/agents/onlook-chat.ts (5)
  • OnlookChat (114-136)
  • messages (43-45)
  • messages (47-50)
  • error (34-36)
  • error (38-41)
apps/web/client/src/trpc/react.tsx (1)
  • api (23-23)
apps/web/client/src/server/api/routers/chat/conversation.ts (2)
packages/db/src/schema/chat/conversation.ts (1)
  • conversations (13-23)
packages/ai/src/chat/providers.ts (1)
  • initModel (14-51)
packages/scripts/src/backend.ts (2)
packages/scripts/src/helpers.ts (1)
  • writeEnvFile (116-176)
packages/scripts/src/index.ts (1)
  • rootDir (12-12)
packages/ai/src/agents/streamer.ts (3)
packages/models/src/chat/message/message.ts (1)
  • ChatMessage (19-19)
packages/ai/src/agents/models/base.ts (1)
  • streamText (28-43)
packages/ai/src/stream/index.ts (1)
  • convertToStreamMessages (5-23)
apps/web/client/src/app/api/chat/route.ts (4)
packages/db/src/schema/chat/conversation.ts (1)
  • agentType (11-11)
apps/web/client/src/app/api/chat/helpers/usage.ts (1)
  • incrementUsage (43-65)
packages/ai/src/agents/classes/root.ts (1)
  • RootAgent (10-58)
packages/ai/src/agents/classes/user.ts (1)
  • UserAgent (6-17)
🔇 Additional comments (5)
packages/scripts/src/backend.ts (1)

16-45: Still returning the wrong repo root when .git is missing

We’re still hanging onto the first package.json we encounter, which makes rootDir point to packages/scripts (or any nested workspace) if the checkout lacks .git—exactly the failure called out in the earlier review. Keep walking upward and remember the last package.json so we fall back to the real repository root in ZIP/CI environments.

-    let firstPackageJsonDir: string | null = null;
+    let lastPackageJsonDir: string | null = null;-        if (fs.existsSync(packageJsonPath) && !firstPackageJsonDir) {
-            firstPackageJsonDir = currentDir;
+        if (fs.existsSync(packageJsonPath)) {
+            lastPackageJsonDir = currentDir;
         }
…
-    if (firstPackageJsonDir) {
-        return firstPackageJsonDir;
+    if (lastPackageJsonDir) {
+        return lastPackageJsonDir;
     }
packages/ai/src/agents/tools/base.ts (2)

117-135: Fix unresolved completion hang for tool outputs

We still only resolve when the final part is text with state done. When the assistant finishes with a completed tool payload (e.g., tool-result in output-available/output-error), this branch never runs and the promise never settles, stalling sub-agent flows. Please accept completed tool outputs as final before resolving.

Apply this diff:

-                const lastPart = message.parts[message.parts.length - 1];
-                const lastPartComplete = lastPart &&
-                    (lastPart.type === 'text' && (lastPart as any).state === 'done');
-
-                console.log("OnlookChat - Last part:", { type: lastPart?.type, state: (lastPart as any)?.state });
-                console.log("OnlookChat - Last part complete:", lastPartComplete);
-
-                if (!isIncomplete && lastPartComplete) {
+                const lastPart = message.parts[message.parts.length - 1];
+                const lastPartState = (lastPart as any)?.state;
+                const lastPartComplete =
+                    (lastPart?.type === 'text' && lastPartState === 'done') ||
+                    (lastPart?.type?.startsWith('tool-') &&
+                        (lastPartState === 'output-available' || lastPartState === 'output-error'));
+
+                console.log("OnlookChat - Last part:", { type: lastPart?.type, state: lastPartState });
+                console.log("OnlookChat - Last part complete:", lastPartComplete);
+
+                if (!isIncomplete && lastPartComplete) {

176-178: Guard metadata.error before serializing

metadata.error is often undefined, so the current !== null check fires, JSON.stringify(undefined) returns undefined, and response stops being a string if the message has no text parts. Please null-check the error payload first.

Apply this diff:

-        if (agentResponse.metadata?.error !== null) {
-            response = JSON.stringify(agentResponse.metadata!.error, null, 2);
-        }
+        const metadataError = agentResponse.metadata?.error;
+        if (metadataError != null) {
+            response = JSON.stringify(metadataError, null, 2) ?? '';
+        }
packages/db/src/schema/chat/conversation.ts (1)

11-11: Fix pgEnum initialization to use string values

pgEnum expects an iterable/tuple of string literals. Passing the enum object causes Drizzle to blow up when it tries to iterate (e.g., .map) over the enum definition at runtime. Please supply the string values, e.g. Object.values(AgentType) as [AgentType, ...AgentType[]], before calling pgEnum.

-export const agentType = pgEnum("agent_type", AgentType);
+const agentTypeValues = Object.values(AgentType) as [AgentType, ...AgentType[]];
+export const agentType = pgEnum("agent_type", agentTypeValues);
apps/web/client/src/app/api/chat/route.ts (1)

55-67: Require chatType whenever agentType is ROOT

Right now the schema lets a ROOT request omit chatType, so chatType! later becomes undefined. That silently instantiates RootAgent in the EDIT branch, skips the usage increment, and ships "null" telemetry. Please hard-require chatType for ROOT (while still forbidding it for other agent types) so we never hydrate a root agent with an undefined mode.

-}).refine((data) => {
-    // Only allow chatType if agentType is ROOT
-    if (data.chatType !== undefined && data.agentType !== AgentType.ROOT) {
-        return false;
-    }
-    return true;
-}, { message: "chatType is only allowed if agentType is root" });
+}).superRefine((data, ctx) => {
+    if (data.agentType === AgentType.ROOT && data.chatType === undefined) {
+        ctx.addIssue({
+            code: z.ZodIssueCode.custom,
+            path: ['chatType'],
+            message: 'chatType is required when agentType is root',
+        });
+    }
+    if (data.chatType !== undefined && data.agentType !== AgentType.ROOT) {
+        ctx.addIssue({
+            code: z.ZodIssueCode.custom,
+            path: ['chatType'],
+            message: 'chatType is only allowed if agentType is root',
+        });
+    }
+});

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between c11e7dc and 0595cd5.

📒 Files selected for processing (3)
  • apps/web/client/src/components/store/editor/chat/conversation.ts (1 hunks)
  • apps/web/client/src/server/api/routers/chat/conversation.ts (3 hunks)
  • packages/ai/src/agents/tools/base.ts (1 hunks)
🧰 Additional context used
📓 Path-based instructions (4)
**/*.{ts,tsx}

📄 CodeRabbit inference engine (AGENTS.md)

Do not use the any type unless necessary

Files:

  • packages/ai/src/agents/tools/base.ts
  • apps/web/client/src/components/store/editor/chat/conversation.ts
  • apps/web/client/src/server/api/routers/chat/conversation.ts
{apps,packages}/**/*.{ts,tsx}

📄 CodeRabbit inference engine (CLAUDE.md)

Avoid using the any type unless absolutely necessary

Files:

  • packages/ai/src/agents/tools/base.ts
  • apps/web/client/src/components/store/editor/chat/conversation.ts
  • apps/web/client/src/server/api/routers/chat/conversation.ts
apps/web/client/src/**/*.{ts,tsx}

📄 CodeRabbit inference engine (AGENTS.md)

apps/web/client/src/**/*.{ts,tsx}: Use path aliases @/* and ~/* for imports that map to apps/web/client/src/*
Avoid hardcoded user-facing text; use next-intl messages/hooks instead

Use path aliases @/* and ~/* for imports mapping to src/*

Files:

  • apps/web/client/src/components/store/editor/chat/conversation.ts
  • apps/web/client/src/server/api/routers/chat/conversation.ts
apps/web/client/src/server/api/routers/**/*.ts

📄 CodeRabbit inference engine (AGENTS.md)

apps/web/client/src/server/api/routers/**/*.ts: Place tRPC routers under apps/web/client/src/server/api/routers/**
Use publicProcedure/protectedProcedure from apps/web/client/src/server/api/trpc.ts and validate inputs with Zod
Return plain objects/arrays; rely on SuperJSON for serialization in tRPC procedures

apps/web/client/src/server/api/routers/**/*.ts: Place tRPC routers under src/server/api/routers/**
Use publicProcedure/protectedProcedure from src/server/api/trpc.ts and validate inputs with Zod
Return plain objects/arrays; rely on SuperJSON for serialization

Files:

  • apps/web/client/src/server/api/routers/chat/conversation.ts
🧬 Code graph analysis (3)
packages/ai/src/agents/tools/base.ts (4)
packages/models/src/chat/message/message.ts (1)
  • ChatMessage (19-19)
apps/web/client/src/components/store/editor/engine.ts (1)
  • EditorEngine (34-140)
packages/ai/src/tools/models/client.ts (1)
  • OnToolCallHandler (6-6)
packages/ai/src/agents/onlook-chat.ts (5)
  • OnlookChat (114-136)
  • messages (43-45)
  • messages (47-50)
  • error (34-36)
  • error (38-41)
apps/web/client/src/components/store/editor/chat/conversation.ts (1)
packages/models/src/chat/conversation/index.ts (1)
  • ChatConversation (8-16)
apps/web/client/src/server/api/routers/chat/conversation.ts (2)
packages/db/src/schema/chat/conversation.ts (1)
  • conversations (13-23)
packages/ai/src/chat/providers.ts (1)
  • initModel (14-51)
🔇 Additional comments (2)
packages/ai/src/agents/tools/base.ts (1)

121-134: Unblock completion when the final chunk is a tool output

We still only resolve after a text part ends with state "done". When the assistant finishes on a completed tool output (tool-result with output-available / output-error), the promise never resolves and sub-agent calls hang. Treat completed tool outputs as terminal (or rely solely on isIncomplete) before resolving. See earlier feedback—the bug remains.

Apply something like:

-                const lastPart = message.parts[message.parts.length - 1];
-                const lastPartComplete = lastPart &&
-                    (lastPart.type === 'text' && (lastPart as any).state === 'done');
+                const lastPart = message.parts[message.parts.length - 1];
+                const lastPartState = (lastPart as any)?.state;
+                const lastPartComplete =
+                    lastPart &&
+                    (
+                        (lastPart.type === 'text' && lastPartState === 'done') ||
+                        (lastPart.type.startsWith('tool-') &&
+                            (lastPartState === 'output-available' || lastPartState === 'output-error'))
+                    );
apps/web/client/src/server/api/routers/chat/conversation.ts (1)

54-58: Filter undefined fields before update

input is a partial payload, so spreading it directly into .set(...) can persist NULLs for any keys that happen to be undefined. Please reuse the filtered map from the upsert fix when building the update payload, keeping updatedAt forced.

-            const [conversation] = await ctx.db.update(conversations)
-                .set({
-                    ...input,
-                    updatedAt: new Date(),
-                })
+            const updateValues = Object.fromEntries(
+                Object.entries(input).filter(([key, value]) => key !== 'id' && value !== undefined),
+            );
+            const [conversation] = await ctx.db.update(conversations)
+                .set({
+                    ...updateValues,
+                    updatedAt: new Date(),
+                })

@Kitenite Kitenite merged commit 49d5dfa into onlook-dev:main Sep 27, 2025
2 of 4 checks passed
Kitenite added a commit that referenced this pull request Oct 2, 2025
@coderabbitai coderabbitai bot mentioned this pull request Oct 3, 2025
5 tasks
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants