Merge pull request #321 from rowboatlabs/dev

Dev
This commit is contained in:
Tushar 2025-12-16 15:22:21 +05:30 committed by GitHub
commit ae877e70ae
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
19 changed files with 568 additions and 144 deletions

View file

@ -1,12 +1,12 @@
{
"name": "@rowboatlabs/rowboatx",
"version": "0.10.0",
"version": "0.15.0",
"lockfileVersion": 3,
"requires": true,
"packages": {
"": {
"name": "@rowboatlabs/rowboatx",
"version": "0.10.0",
"version": "0.15.0",
"license": "Apache-2.0",
"dependencies": {
"@ai-sdk/anthropic": "^2.0.44",
@ -15,8 +15,8 @@
"@ai-sdk/openai-compatible": "^1.0.27",
"@ai-sdk/provider": "^2.0.0",
"@modelcontextprotocol/sdk": "^1.20.2",
"@openrouter/ai-sdk-provider": "^1.2.3",
"ai": "^5.0.78",
"@openrouter/ai-sdk-provider": "^1.2.6",
"ai": "^5.0.102",
"json-schema-to-zod": "^2.6.1",
"nanoid": "^5.1.6",
"ollama-ai-provider-v2": "^1.5.4",
@ -66,14 +66,31 @@
}
},
"node_modules/@ai-sdk/gateway": {
"version": "2.0.1",
"resolved": "https://registry.npmjs.org/@ai-sdk/gateway/-/gateway-2.0.1.tgz",
"integrity": "sha512-vPVIbnP35ZnayS937XLo85vynR85fpBQWHCdUweq7apzqFOTU2YkUd4V3msebEHbQ2Zro60ZShDDy9SMiyWTqA==",
"version": "2.0.15",
"resolved": "https://registry.npmjs.org/@ai-sdk/gateway/-/gateway-2.0.15.tgz",
"integrity": "sha512-i1YVKzC1dg9LGvt+GthhD7NlRhz9J4+ZRj3KELU14IZ/MHPsOBiFeEoCCIDLR+3tqT8/+5nIsK3eZ7DFRfMfdw==",
"license": "Apache-2.0",
"dependencies": {
"@ai-sdk/provider": "2.0.0",
"@ai-sdk/provider-utils": "3.0.12",
"@vercel/oidc": "3.0.3"
"@ai-sdk/provider-utils": "3.0.17",
"@vercel/oidc": "3.0.5"
},
"engines": {
"node": ">=18"
},
"peerDependencies": {
"zod": "^3.25.76 || ^4.1.8"
}
},
"node_modules/@ai-sdk/gateway/node_modules/@ai-sdk/provider-utils": {
"version": "3.0.17",
"resolved": "https://registry.npmjs.org/@ai-sdk/provider-utils/-/provider-utils-3.0.17.tgz",
"integrity": "sha512-TR3Gs4I3Tym4Ll+EPdzRdvo/rc8Js6c4nVhFLuvGLX/Y4V9ZcQMa/HTiYsHEgmYrf1zVi6Q145UEZUfleOwOjw==",
"license": "Apache-2.0",
"dependencies": {
"@ai-sdk/provider": "2.0.0",
"@standard-schema/spec": "^1.0.0",
"eventsource-parser": "^3.0.6"
},
"engines": {
"node": ">=18"
@ -276,9 +293,9 @@
}
},
"node_modules/@openrouter/ai-sdk-provider": {
"version": "1.2.3",
"resolved": "https://registry.npmjs.org/@openrouter/ai-sdk-provider/-/ai-sdk-provider-1.2.3.tgz",
"integrity": "sha512-a6Nc8dPRHakRH9966YJ/HZJhLOds7DuPTscNZDoAr+Aw+tEFUlacSJMvb/b3gukn74mgbuaJRji9YOn62ipfVg==",
"version": "1.2.6",
"resolved": "https://registry.npmjs.org/@openrouter/ai-sdk-provider/-/ai-sdk-provider-1.2.6.tgz",
"integrity": "sha512-DExO4FXod5vEdLFpQGsyNva8u3FWHj2IPaP8to+zEGsBEUY7lu5t24uIMxmmLKZ0sYYWAtmTLSV4Y9uOVqQoAg==",
"license": "Apache-2.0",
"dependencies": {
"@openrouter/sdk": "^0.1.8"
@ -370,9 +387,9 @@
}
},
"node_modules/@vercel/oidc": {
"version": "3.0.3",
"resolved": "https://registry.npmjs.org/@vercel/oidc/-/oidc-3.0.3.tgz",
"integrity": "sha512-yNEQvPcVrK9sIe637+I0jD6leluPxzwJKx/Haw6F4H77CdDsszUn5V3o96LPziXkSNE2B83+Z3mjqGKBK/R6Gg==",
"version": "3.0.5",
"resolved": "https://registry.npmjs.org/@vercel/oidc/-/oidc-3.0.5.tgz",
"integrity": "sha512-fnYhv671l+eTTp48gB4zEsTW/YtRgRPnkI2nT7x6qw5rkI1Lq2hTmQIpHPgyThI0znLK+vX2n9XxKdXZ7BUbbw==",
"license": "Apache-2.0",
"engines": {
"node": ">= 20"
@ -418,14 +435,14 @@
}
},
"node_modules/ai": {
"version": "5.0.78",
"resolved": "https://registry.npmjs.org/ai/-/ai-5.0.78.tgz",
"integrity": "sha512-ec77fmQwJGLduswMrW4AAUGSOiu8dZaIwMmWHHGKsrMUFFS6ugfkTyx0srtuKYHNRRLRC2dT7cPirnUl98VnxA==",
"version": "5.0.102",
"resolved": "https://registry.npmjs.org/ai/-/ai-5.0.102.tgz",
"integrity": "sha512-snRK3nS5DESOjjpq7S74g8YszWVMzjagfHqlJWZsbtl9PyOS+2XUd8dt2wWg/jdaq/jh0aU66W1mx5qFjUQyEg==",
"license": "Apache-2.0",
"dependencies": {
"@ai-sdk/gateway": "2.0.1",
"@ai-sdk/gateway": "2.0.15",
"@ai-sdk/provider": "2.0.0",
"@ai-sdk/provider-utils": "3.0.12",
"@ai-sdk/provider-utils": "3.0.17",
"@opentelemetry/api": "1.9.0"
},
"engines": {
@ -435,6 +452,23 @@
"zod": "^3.25.76 || ^4.1.8"
}
},
"node_modules/ai/node_modules/@ai-sdk/provider-utils": {
"version": "3.0.17",
"resolved": "https://registry.npmjs.org/@ai-sdk/provider-utils/-/provider-utils-3.0.17.tgz",
"integrity": "sha512-TR3Gs4I3Tym4Ll+EPdzRdvo/rc8Js6c4nVhFLuvGLX/Y4V9ZcQMa/HTiYsHEgmYrf1zVi6Q145UEZUfleOwOjw==",
"license": "Apache-2.0",
"dependencies": {
"@ai-sdk/provider": "2.0.0",
"@standard-schema/spec": "^1.0.0",
"eventsource-parser": "^3.0.6"
},
"engines": {
"node": ">=18"
},
"peerDependencies": {
"zod": "^3.25.76 || ^4.1.8"
}
},
"node_modules/ajv": {
"version": "6.12.6",
"resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz",
@ -483,23 +517,27 @@
"license": "MIT"
},
"node_modules/body-parser": {
"version": "2.2.0",
"resolved": "https://registry.npmjs.org/body-parser/-/body-parser-2.2.0.tgz",
"integrity": "sha512-02qvAaxv8tp7fBa/mw1ga98OGm+eCbqzJOKoRt70sLmfEEi+jyBYVTDGfCL/k06/4EMk/z01gCe7HoCH/f2LTg==",
"version": "2.2.1",
"resolved": "https://registry.npmjs.org/body-parser/-/body-parser-2.2.1.tgz",
"integrity": "sha512-nfDwkulwiZYQIGwxdy0RUmowMhKcFVcYXUU7m4QlKYim1rUtg83xm2yjZ40QjDuc291AJjjeSc9b++AWHSgSHw==",
"license": "MIT",
"dependencies": {
"bytes": "^3.1.2",
"content-type": "^1.0.5",
"debug": "^4.4.0",
"debug": "^4.4.3",
"http-errors": "^2.0.0",
"iconv-lite": "^0.6.3",
"iconv-lite": "^0.7.0",
"on-finished": "^2.4.1",
"qs": "^6.14.0",
"raw-body": "^3.0.0",
"type-is": "^2.0.0"
"raw-body": "^3.0.1",
"type-is": "^2.0.1"
},
"engines": {
"node": ">=18"
},
"funding": {
"type": "opencollective",
"url": "https://opencollective.com/express"
}
},
"node_modules/bytes": {
@ -1006,15 +1044,19 @@
}
},
"node_modules/iconv-lite": {
"version": "0.6.3",
"resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.6.3.tgz",
"integrity": "sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw==",
"version": "0.7.0",
"resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.7.0.tgz",
"integrity": "sha512-cf6L2Ds3h57VVmkZe+Pn+5APsT7FpqJtEhhieDCvrE2MK5Qk9MyffgQyuxQTm6BChfeZNtcOLHp9IcWRVcIcBQ==",
"license": "MIT",
"dependencies": {
"safer-buffer": ">= 2.1.2 < 3.0.0"
},
"engines": {
"node": ">=0.10.0"
},
"funding": {
"type": "opencollective",
"url": "https://opencollective.com/express"
}
},
"node_modules/inherits": {
@ -1329,22 +1371,6 @@
"node": ">= 0.10"
}
},
"node_modules/raw-body/node_modules/iconv-lite": {
"version": "0.7.0",
"resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.7.0.tgz",
"integrity": "sha512-cf6L2Ds3h57VVmkZe+Pn+5APsT7FpqJtEhhieDCvrE2MK5Qk9MyffgQyuxQTm6BChfeZNtcOLHp9IcWRVcIcBQ==",
"license": "MIT",
"dependencies": {
"safer-buffer": ">= 2.1.2 < 3.0.0"
},
"engines": {
"node": ">=0.10.0"
},
"funding": {
"type": "opencollective",
"url": "https://opencollective.com/express"
}
},
"node_modules/router": {
"version": "2.2.0",
"resolved": "https://registry.npmjs.org/router/-/router-2.2.0.tgz",

View file

@ -1,6 +1,6 @@
{
"name": "@rowboatlabs/rowboatx",
"version": "0.15.0",
"version": "0.16.0",
"main": "index.js",
"type": "module",
"scripts": {
@ -31,8 +31,8 @@
"@ai-sdk/openai-compatible": "^1.0.27",
"@ai-sdk/provider": "^2.0.0",
"@modelcontextprotocol/sdk": "^1.20.2",
"@openrouter/ai-sdk-provider": "^1.2.3",
"ai": "^5.0.78",
"@openrouter/ai-sdk-provider": "^1.2.6",
"ai": "^5.0.102",
"json-schema-to-zod": "^2.6.1",
"nanoid": "^5.1.6",
"ollama-ai-provider-v2": "^1.5.4",

View file

@ -14,6 +14,7 @@ import { Example } from "./application/entities/example.js";
import { z } from "zod";
import { Flavor } from "./application/entities/models.js";
import { examples } from "./examples/index.js";
import { modelMessageSchema } from "ai";
export async function updateState(agent: string, runId: string) {
const state = new AgentState(agent, runId);
@ -225,6 +226,7 @@ export async function modelConfig() {
const defaultApiKeyEnvVars: Record<z.infer<typeof Flavor>, string> = {
"rowboat [free]": "",
openai: "OPENAI_API_KEY",
aigateway: "AI_GATEWAY_API_KEY",
anthropic: "ANTHROPIC_API_KEY",
google: "GOOGLE_GENERATIVE_AI_API_KEY",
ollama: "",
@ -234,6 +236,7 @@ export async function modelConfig() {
const defaultBaseUrls: Record<z.infer<typeof Flavor>, string> = {
"rowboat [free]": "",
openai: "https://api.openai.com/v1",
aigateway: "https://ai-gateway.vercel.sh/v1/ai",
anthropic: "https://api.anthropic.com/v1",
google: "https://generativelanguage.googleapis.com/v1beta",
ollama: "http://localhost:11434",
@ -243,6 +246,7 @@ export async function modelConfig() {
const defaultModels: Record<z.infer<typeof Flavor>, string> = {
"rowboat [free]": "google/gemini-3-pro-preview",
openai: "gpt-5.1",
aigateway: "gpt-5.1",
anthropic: "claude-sonnet-4-5",
google: "gemini-2.5-pro",
ollama: "llama3.1",

View file

@ -1,7 +1,11 @@
import { skillCatalog } from "./skills/index.js";
import { WorkDir as BASE_DIR } from "../config/config.js";
export const CopilotInstructions = `You are an intelligent workflow assistant helping users manage their workflows in ${BASE_DIR}
export const CopilotInstructions = `You are an intelligent workflow assistant helping users manage their workflows in ${BASE_DIR}. You can also help the user with general tasks.
## General Capabilities
In addition to Rowboat-specific workflow management, you can help users with general tasks like answering questions, explaining concepts, brainstorming ideas, solving problems, writing and debugging code, analyzing information, and providing explanations on a wide range of topics. Be conversational, helpful, and engaging. For tasks requiring external capabilities (web search, APIs, etc.), use MCP tools as described below.
Use the catalog below to decide which skills to load for each user request. Before acting:
- Call the \`loadSkill\` tool with the skill's name or path so you can read its guidance string.
@ -22,6 +26,14 @@ Always consult this catalog first so you load the right skills before taking act
- Summarize completed work and suggest logical next steps at the end of a task.
- Always ask for confirmation before taking destructive actions.
## MCP Tool Discovery (CRITICAL)
**ALWAYS check for MCP tools BEFORE saying you can't do something.**
When a user asks for ANY task that might require external capabilities (web search, internet access, APIs, data fetching, etc.), check MCP tools first using \`listMcpServers\` and \`listMcpTools\`. Load the "mcp-integration" skill for detailed guidance on discovering and executing MCP tools.
**DO NOT** immediately respond with "I can't access the internet" or "I don't have that capability" without checking MCP tools first!
## Execution reminders
- Explore existing files and structure before creating new assets.
- Use relative paths (no \${BASE_DIR} prefixes) when running commands or referencing files.
@ -38,7 +50,7 @@ Always consult this catalog first so you load the right skills before taking act
- \`deleteFile\`, \`createFile\`, \`updateFile\`, \`readFile\` - File operations
- \`listFiles\`, \`exploreDirectory\` - Directory exploration
- \`analyzeAgent\` - Agent analysis
- \`addMcpServer\`, \`listMcpServers\`, \`listMcpTools\` - MCP server management
- \`addMcpServer\`, \`listMcpServers\`, \`listMcpTools\`, \`executeMcpTool\` - MCP server management and execution
- \`loadSkill\` - Skill loading
These tools work directly and are NOT filtered by \`.rowboat/config/security.json\`.

View file

@ -150,6 +150,37 @@ Agents can call other agents as tools to create complex multi-step workflows. Th
While \`executeCommand\` is the most versatile, other builtin tools exist for specific Rowboat operations (file management, agent inspection, etc.). These are primarily used by the Rowboat copilot itself and are not typically needed in user agents. If you need file operations, consider using bash commands like \`cat\`, \`echo\`, \`tee\`, etc. through \`executeCommand\`.
### Copilot-Specific Builtin Tools
The Rowboat copilot has access to special builtin tools that regular agents don't typically use. These tools help the copilot assist users with workspace management and MCP integration:
#### File & Directory Operations
- \`exploreDirectory\` - Recursively explore directory structure
- \`readFile\` - Read and parse file contents
- \`createFile\` - Create a new file with content
- \`updateFile\` - Update or overwrite existing file contents
- \`deleteFile\` - Delete a file
- \`listFiles\` - List all files and directories
#### Agent Operations
- \`analyzeAgent\` - Read and analyze an agent file structure
- \`loadSkill\` - Load a Rowboat skill definition into context
#### MCP Operations
- \`addMcpServer\` - Add or update an MCP server configuration (with validation)
- \`listMcpServers\` - List all available MCP servers
- \`listMcpTools\` - List all available tools from a specific MCP server
- \`executeMcpTool\` - **Execute a specific MCP tool on behalf of the user**
#### Using executeMcpTool as Copilot
The \`executeMcpTool\` builtin allows the copilot to directly execute MCP tools without creating an agent. Load the "mcp-integration" skill for complete guidance on discovering and executing MCP tools, including workflows, schema matching, and examples.
**When to use executeMcpTool vs creating an agent:**
- Use \`executeMcpTool\` for immediate, one-time tasks
- Create an agent when the user needs repeated use or autonomous operation
- Create an agent for complex multi-step workflows involving multiple tools
## Best Practices
1. **Give agents clear examples** in their instructions showing exact bash commands to run

View file

@ -43,7 +43,7 @@ const definitions: SkillDefinition[] = [
id: "mcp-integration",
title: "MCP Integration Guidance",
folder: "mcp-integration",
summary: "Listing MCP servers/tools and embedding their schemas in agent definitions.",
summary: "Discovering, executing, and integrating MCP tools. Use this to check what external capabilities are available and execute MCP tools on behalf of users.",
content: mcpIntegrationSkill,
},
{

View file

@ -1,7 +1,31 @@
export const skill = String.raw`
# MCP Integration Guidance
Load this skill whenever a user asks about external tools, MCP servers, or how to extend an agent's capabilities.
**Load this skill proactively** when a user asks for ANY task that might require external capabilities (web search, internet access, APIs, data fetching, time/date, etc.). This skill provides complete guidance on discovering and executing MCP tools.
## CRITICAL: Always Check MCP Tools First
**IMPORTANT**: When a user asks for ANY task that might require external capabilities (web search, API calls, data fetching, etc.), ALWAYS:
1. **First check**: Call \`listMcpServers\` to see what's available
2. **Then list tools**: Call \`listMcpTools\` on relevant servers
3. **Execute if possible**: Use \`executeMcpTool\` if a tool matches the need
4. **Only then decline**: If no MCP tool can help, explain what's not possible
**DO NOT** immediately say "I can't do that" or "I don't have internet access" without checking MCP tools first!
### Common User Requests and MCP Tools
| User Request | Check For | Likely Tool |
|--------------|-----------|-------------|
| "Search the web/internet" | firecrawl, composio, fetch | \`firecrawl_search\`, \`COMPOSIO_SEARCH_WEB\` |
| "Scrape this website" | firecrawl | \`firecrawl_scrape\` |
| "Read/write files" | filesystem | \`read_file\`, \`write_file\` |
| "Get current time/date" | time | \`get_current_time\` |
| "Make HTTP request" | fetch | \`fetch\`, \`post\` |
| "GitHub operations" | github | \`create_issue\`, \`search_repos\` |
| "Generate audio/speech" | elevenLabs | \`text_to_speech\` |
| "Tweet/social media" | twitter, composio | Various social tools |
## Key concepts
- MCP servers expose tools (web scraping, APIs, databases, etc.) declared in \`config/mcp.json\`.
@ -185,8 +209,153 @@ Error: Cannot have both \`command\` and \`url\`
1. Use \`listMcpServers\` to enumerate configured servers.
2. Use \`addMcpServer\` to add or update MCP server configurations (with validation).
3. Use \`listMcpTools\` for a server to understand the available operations and schemas.
4. Explain which MCP tools match the user's needs before editing agent definitions.
5. When adding a tool to an agent, document what it does and ensure the schema mirrors the MCP definition.
4. Use \`executeMcpTool\` to run MCP tools directly on behalf of the user.
5. Explain which MCP tools match the user's needs before editing agent definitions.
6. When adding a tool to an agent, document what it does and ensure the schema mirrors the MCP definition.
## Executing MCP Tools Directly (Copilot)
As the copilot, you can execute MCP tools directly on behalf of the user using the \`executeMcpTool\` builtin. This allows you to use MCP tools without creating an agent.
### When to Execute MCP Tools Directly
- User asks you to perform a task that an MCP tool can handle (web search, file operations, API calls, etc.)
- User wants immediate results from an MCP tool without setting up an agent
- You need to test or demonstrate an MCP tool's functionality
- You're helping the user accomplish a one-time task
### Workflow for Executing MCP Tools
1. **Discover available servers**: Use \`listMcpServers\` to see what MCP servers are configured
2. **List tools from a server**: Use \`listMcpTools\` with the server name to see available tools and their schemas
3. **CAREFULLY EXAMINE THE SCHEMA**: Look at the \`inputSchema\` to understand exactly what parameters are required
4. **Execute the tool**: Use \`executeMcpTool\` with the server name, tool name, and required arguments (matching the schema exactly)
5. **Return results**: Present the results to the user in a helpful format
### CRITICAL: Schema Matching
**ALWAYS** examine the \`inputSchema\` from \`listMcpTools\` before calling \`executeMcpTool\`.
The schema tells you:
- What parameters are required (check the \`"required"\` array)
- What type each parameter should be (string, number, boolean, object, array)
- Parameter descriptions and examples
**Example schema from listMcpTools:**
\`\`\`json
{
"name": "COMPOSIO_SEARCH_WEB",
"inputSchema": {
"type": "object",
"properties": {
"query": {
"type": "string",
"description": "The search query"
},
"limit": {
"type": "number",
"description": "Number of results"
}
},
"required": ["query"]
}
}
\`\`\`
**Correct executeMcpTool call:**
\`\`\`json
{
"serverName": "composio",
"toolName": "COMPOSIO_SEARCH_WEB",
"arguments": {
"query": "elon musk latest news"
}
}
\`\`\`
**WRONG - Missing arguments:**
\`\`\`json
{
"serverName": "composio",
"toolName": "COMPOSIO_SEARCH_WEB"
}
\`\`\`
**WRONG - Wrong parameter name:**
\`\`\`json
{
"serverName": "composio",
"toolName": "COMPOSIO_SEARCH_WEB",
"arguments": {
"search": "elon musk" // Wrong! Should be "query"
}
}
\`\`\`
### Example: Using Firecrawl to Search the Web
**Step 1: List servers**
\`\`\`json
// Call: listMcpServers
// Response: { "servers": [{"name": "firecrawl", "type": "stdio", ...}] }
\`\`\`
**Step 2: List tools**
\`\`\`json
// Call: listMcpTools with serverName: "firecrawl"
// Response: { "tools": [{"name": "firecrawl_search", "description": "Search the web", "inputSchema": {...}}] }
\`\`\`
**Step 3: Execute the tool**
\`\`\`json
{
"serverName": "firecrawl",
"toolName": "firecrawl_search",
"arguments": {
"query": "latest AI news",
"limit": 5
}
}
\`\`\`
### Example: Using Filesystem Tool
**Execute a filesystem read operation:**
\`\`\`json
{
"serverName": "filesystem",
"toolName": "read_file",
"arguments": {
"path": "/path/to/file.txt"
}
}
\`\`\`
### Tips for Executing MCP Tools
- Always check the \`inputSchema\` from \`listMcpTools\` to know what arguments are required
- Match argument types exactly (string, number, boolean, object, array)
- Provide helpful context to the user about what the tool is doing
- Handle errors gracefully and suggest alternatives if a tool fails
- For complex tasks, consider creating an agent instead of one-off tool calls
### Discovery Pattern (Recommended)
When a user asks for something that might be accomplished with an MCP tool:
1. **Identify the need**: "You want to search the web? Let me check what MCP tools are available..."
2. **List servers**: Call \`listMcpServers\`
3. **Check for relevant tools**: If you find a relevant server (e.g., "firecrawl" for web search), call \`listMcpTools\`
4. **Execute the tool**: Once you find the right tool and understand its schema, call \`executeMcpTool\`
5. **Present results**: Format and explain the results to the user
### Common MCP Servers and Their Tools
Based on typical configurations, you might find:
- **firecrawl**: Web scraping, search, crawling (\`firecrawl_search\`, \`firecrawl_scrape\`, \`firecrawl_crawl\`)
- **filesystem**: File operations (\`read_file\`, \`write_file\`, \`list_directory\`)
- **github**: GitHub operations (\`create_issue\`, \`create_pr\`, \`search_repositories\`)
- **fetch**: HTTP requests (\`fetch\`, \`post\`)
- **time**: Time/date operations (\`get_current_time\`, \`convert_timezone\`)
Always use \`listMcpServers\` and \`listMcpTools\` to discover what's actually available rather than assuming.
## Adding MCP Tools to Agents

View file

@ -1,40 +1,46 @@
import { z } from "zod";
import { ProviderOptions } from "./message.js";
export const LlmStepStreamReasoningStartEvent = z.object({
const BaseEvent = z.object({
providerOptions: ProviderOptions.optional(),
})
export const LlmStepStreamReasoningStartEvent = BaseEvent.extend({
type: z.literal("reasoning-start"),
});
export const LlmStepStreamReasoningDeltaEvent = z.object({
export const LlmStepStreamReasoningDeltaEvent = BaseEvent.extend({
type: z.literal("reasoning-delta"),
delta: z.string(),
});
export const LlmStepStreamReasoningEndEvent = z.object({
export const LlmStepStreamReasoningEndEvent = BaseEvent.extend({
type: z.literal("reasoning-end"),
});
export const LlmStepStreamTextStartEvent = z.object({
export const LlmStepStreamTextStartEvent = BaseEvent.extend({
type: z.literal("text-start"),
});
export const LlmStepStreamTextDeltaEvent = z.object({
export const LlmStepStreamTextDeltaEvent = BaseEvent.extend({
type: z.literal("text-delta"),
delta: z.string(),
});
export const LlmStepStreamTextEndEvent = z.object({
export const LlmStepStreamTextEndEvent = BaseEvent.extend({
type: z.literal("text-end"),
});
export const LlmStepStreamToolCallEvent = z.object({
export const LlmStepStreamToolCallEvent = BaseEvent.extend({
type: z.literal("tool-call"),
toolCallId: z.string(),
toolName: z.string(),
input: z.any(),
});
export const LlmStepStreamUsageEvent = z.object({
type: z.literal("usage"),
export const LlmStepStreamFinishStepEvent = z.object({
type: z.literal("finish-step"),
finishReason: z.enum(["stop", "tool-calls", "length", "content-filter", "error", "other", "unknown"]),
usage: z.object({
inputTokens: z.number().optional(),
outputTokens: z.number().optional(),
@ -42,6 +48,7 @@ export const LlmStepStreamUsageEvent = z.object({
reasoningTokens: z.number().optional(),
cachedInputTokens: z.number().optional(),
}),
providerOptions: ProviderOptions.optional(),
});
export const LlmStepStreamEvent = z.union([
@ -52,5 +59,5 @@ export const LlmStepStreamEvent = z.union([
LlmStepStreamTextDeltaEvent,
LlmStepStreamTextEndEvent,
LlmStepStreamToolCallEvent,
LlmStepStreamUsageEvent,
LlmStepStreamFinishStepEvent,
]);

View file

@ -1,13 +1,17 @@
import { z } from "zod";
export const ProviderOptions = z.record(z.string(), z.record(z.string(), z.json()));
export const TextPart = z.object({
type: z.literal("text"),
text: z.string(),
providerOptions: ProviderOptions.optional(),
});
export const ReasoningPart = z.object({
type: z.literal("reasoning"),
text: z.string(),
providerOptions: ProviderOptions.optional(),
});
export const ToolCallPart = z.object({
@ -15,6 +19,7 @@ export const ToolCallPart = z.object({
toolCallId: z.string(),
toolName: z.string(),
arguments: z.any(),
providerOptions: ProviderOptions.optional(),
});
export const AssistantContentPart = z.union([
@ -26,6 +31,7 @@ export const AssistantContentPart = z.union([
export const UserMessage = z.object({
role: z.literal("user"),
content: z.string(),
providerOptions: ProviderOptions.optional(),
});
export const AssistantMessage = z.object({
@ -34,11 +40,13 @@ export const AssistantMessage = z.object({
z.string(),
z.array(AssistantContentPart),
]),
providerOptions: ProviderOptions.optional(),
});
export const SystemMessage = z.object({
role: z.literal("system"),
content: z.string(),
providerOptions: ProviderOptions.optional(),
});
export const ToolMessage = z.object({
@ -46,6 +54,7 @@ export const ToolMessage = z.object({
content: z.string(),
toolCallId: z.string(),
toolName: z.string(),
providerOptions: ProviderOptions.optional(),
});
export const Message = z.discriminatedUnion("role", [

View file

@ -2,6 +2,7 @@ import z from "zod";
export const Flavor = z.enum([
"rowboat [free]",
"aigateway",
"anthropic",
"google",
"ollama",

View file

@ -1,9 +1,9 @@
import { jsonSchema, ModelMessage } from "ai";
import { jsonSchema, ModelMessage, modelMessageSchema } from "ai";
import fs from "fs";
import path from "path";
import { getModelConfig, WorkDir } from "../config/config.js";
import { Agent, ToolAttachment } from "../entities/agent.js";
import { AssistantContentPart, AssistantMessage, Message, MessageList, ToolCallPart, ToolMessage, UserMessage } from "../entities/message.js";
import { AssistantContentPart, AssistantMessage, Message, MessageList, ProviderOptions, ToolCallPart, ToolMessage, UserMessage } from "../entities/message.js";
import { runIdGenerator } from "./run-id-gen.js";
import { LanguageModel, stepCountIs, streamText, tool, Tool, ToolSet } from "ai";
import { z } from "zod";
@ -90,6 +90,7 @@ export class StreamStepMessageBuilder {
private parts: z.infer<typeof AssistantContentPart>[] = [];
private textBuffer: string = "";
private reasoningBuffer: string = "";
private providerOptions: z.infer<typeof ProviderOptions> | undefined = undefined;
flushBuffers() {
// skip reasoning
@ -123,8 +124,12 @@ export class StreamStepMessageBuilder {
toolCallId: event.toolCallId,
toolName: event.toolName,
arguments: event.input,
providerOptions: event.providerOptions,
});
break;
case "finish-step":
this.providerOptions = event.providerOptions;
break;
}
}
@ -133,6 +138,7 @@ export class StreamStepMessageBuilder {
return {
role: "assistant",
content: this.parts,
providerOptions: this.providerOptions,
};
}
}
@ -173,12 +179,14 @@ export async function loadAgent(id: string): Promise<z.infer<typeof Agent>> {
export function convertFromMessages(messages: z.infer<typeof Message>[]): ModelMessage[] {
const result: ModelMessage[] = [];
for (const msg of messages) {
const { providerOptions } = msg;
switch (msg.role) {
case "assistant":
if (typeof msg.content === 'string') {
result.push({
role: "assistant",
content: msg.content,
providerOptions,
});
} else {
result.push({
@ -195,9 +203,11 @@ export function convertFromMessages(messages: z.infer<typeof Message>[]): ModelM
toolCallId: part.toolCallId,
toolName: part.toolName,
input: part.arguments,
providerOptions: part.providerOptions,
};
}
}),
providerOptions,
});
}
break;
@ -205,12 +215,14 @@ export function convertFromMessages(messages: z.infer<typeof Message>[]): ModelM
result.push({
role: "system",
content: msg.content,
providerOptions,
});
break;
case "user":
result.push({
role: "user",
content: msg.content,
providerOptions,
});
break;
case "tool":
@ -227,11 +239,13 @@ export function convertFromMessages(messages: z.infer<typeof Message>[]): ModelM
},
},
],
providerOptions,
});
break;
}
}
return result;
// doing this because: https://github.com/OpenRouterTeam/ai-sdk-provider/issues/262
return JSON.parse(JSON.stringify(result));
}
async function buildTools(agent: z.infer<typeof Agent>): Promise<ToolSet> {
@ -446,7 +460,7 @@ export async function* streamAgent(state: AgentState): AsyncGenerator<z.infer<ty
}
// if tool has been denied, deny
if (state.deniedToolCallIds[toolCallId]) {
if (state.deniedToolCallIds[toolCallId]) {
yield* state.ingestAndLogAndYield({
type: "message",
message: {
@ -561,7 +575,7 @@ export async function* streamAgent(state: AgentState): AsyncGenerator<z.infer<ty
if (underlyingTool.type === "builtin" && underlyingTool.name === "executeCommand") {
// if command is blocked, then seek permission
if (isBlocked(part.arguments.command)) {
yield *state.ingestAndLogAndYield({
yield* state.ingestAndLogAndYield({
type: "tool-permission-request",
toolCall: part,
subflow: [],
@ -609,28 +623,33 @@ async function* streamLlm(
case "reasoning-start":
yield {
type: "reasoning-start",
providerOptions: event.providerMetadata,
};
break;
case "reasoning-delta":
yield {
type: "reasoning-delta",
delta: event.text,
providerOptions: event.providerMetadata,
};
break;
case "reasoning-end":
yield {
type: "reasoning-end",
providerOptions: event.providerMetadata,
};
break;
case "text-start":
yield {
type: "text-start",
providerOptions: event.providerMetadata,
};
break;
case "text-delta":
yield {
type: "text-delta",
delta: event.text,
providerOptions: event.providerMetadata,
};
break;
case "tool-call":
@ -639,12 +658,15 @@ async function* streamLlm(
toolCallId: event.toolCallId,
toolName: event.toolName,
input: event.input,
providerOptions: event.providerMetadata,
};
break;
case "finish":
case "finish-step":
yield {
type: "usage",
usage: event.totalUsage,
type: "finish-step",
usage: event.usage,
finishReason: event.finishReason,
providerOptions: event.providerMetadata,
};
break;
default:

View file

@ -539,6 +539,122 @@ export const BuiltinTools: z.infer<typeof BuiltinToolsSchema> = {
},
},
executeMcpTool: {
description: 'Execute a specific tool from an MCP server. Use this to run MCP tools on behalf of the user. IMPORTANT: Always use listMcpTools first to get the tool\'s inputSchema, then match the required parameters exactly in the arguments field.',
inputSchema: z.object({
serverName: z.string().describe('Name of the MCP server that provides the tool'),
toolName: z.string().describe('Name of the tool to execute'),
arguments: z.record(z.string(), z.any()).optional().describe('Arguments to pass to the tool (as key-value pairs matching the tool\'s input schema). MUST include all required parameters from the tool\'s inputSchema.'),
}),
execute: async ({ serverName, toolName, arguments: args = {} }: { serverName: string, toolName: string, arguments?: Record<string, any> }) => {
let transport: any;
let client: any;
try {
const configPath = path.join(BASE_DIR, 'config', 'mcp.json');
const content = await fs.readFile(configPath, 'utf-8');
const config = JSON.parse(content);
const mcpConfig = config.mcpServers[serverName];
if (!mcpConfig) {
return {
success: false,
message: `MCP server '${serverName}' not found in configuration. Use listMcpServers to see available servers.`,
};
}
// Create transport based on config type
if ('command' in mcpConfig) {
transport = new StdioClientTransport({
command: mcpConfig.command,
args: mcpConfig.args || [],
env: mcpConfig.env || {},
});
} else {
try {
transport = new StreamableHTTPClientTransport(new URL(mcpConfig.url));
} catch {
transport = new SSEClientTransport(new URL(mcpConfig.url));
}
}
// Create and connect client
client = new Client({
name: 'rowboat-copilot',
version: '1.0.0',
});
await client.connect(transport);
// Get tool list to validate the tool exists and check schema
const toolsList = await client.listTools();
const toolDef = toolsList.tools.find((t: any) => t.name === toolName);
if (!toolDef) {
await client.close();
transport.close();
return {
success: false,
message: `Tool '${toolName}' not found in server '${serverName}'. Use listMcpTools to see available tools.`,
availableTools: toolsList.tools.map((t: any) => t.name),
};
}
// Validate required parameters
const inputSchema = toolDef.inputSchema;
if (inputSchema && inputSchema.required && Array.isArray(inputSchema.required)) {
const missingParams = inputSchema.required.filter((param: string) => !(param in args));
if (missingParams.length > 0) {
await client.close();
transport.close();
return {
success: false,
message: `Missing required parameters: ${missingParams.join(', ')}`,
requiredParameters: inputSchema.required,
providedArguments: Object.keys(args),
toolSchema: inputSchema,
hint: `Use listMcpTools to see the full schema for '${toolName}' and ensure all required parameters are included in the arguments field.`,
};
}
}
// Call the tool
const result = await client.callTool({
name: toolName,
arguments: args,
});
// Close connection
await client.close();
transport.close();
return {
success: true,
serverName,
toolName,
result: result.content,
message: `Successfully executed tool '${toolName}' from server '${serverName}'`,
};
} catch (error) {
// Ensure cleanup
try {
if (client) await client.close();
if (transport) transport.close();
} catch (cleanupError) {
// Ignore cleanup errors
}
return {
success: false,
message: `Failed to execute MCP tool: ${error instanceof Error ? error.message : 'Unknown error'}`,
serverName,
toolName,
hint: 'Use listMcpTools to verify the tool exists and check its schema. Ensure all required parameters are provided in the arguments field.',
};
}
},
},
executeCommand: {
description: 'Execute a shell command and return the output. Use this to run bash/shell commands.',
inputSchema: z.object({

View file

@ -1,4 +1,5 @@
import { ProviderV2 } from "@ai-sdk/provider";
import { createGateway } from "ai";
import { createOpenAI } from "@ai-sdk/openai";
import { createGoogleGenerativeAI } from "@ai-sdk/google";
import { createAnthropic } from "@ai-sdk/anthropic";
@ -28,9 +29,9 @@ export async function getProvider(name: string = ""): Promise<ProviderV2> {
const { apiKey, baseURL, headers } = providerConfig;
switch (providerConfig.flavor) {
case "rowboat [free]":
providerMap[name] = createOpenAICompatible({
name: "rowboat [free]",
baseURL: "https://ai-gateway.rowboatlabs.com/v1",
providerMap[name] = createGateway({
apiKey: "rowboatx",
baseURL: "https://ai-gateway.rowboatlabs.com/v1/ai",
});
break;
case "openai":
@ -40,6 +41,13 @@ export async function getProvider(name: string = ""): Promise<ProviderV2> {
headers,
});
break;
case "aigateway":
providerMap[name] = createGateway({
apiKey,
baseURL,
headers
});
break;
case "anthropic":
providerMap[name] = createAnthropic({
apiKey,
@ -65,7 +73,7 @@ export async function getProvider(name: string = ""): Promise<ProviderV2> {
name,
apiKey,
baseURL : baseURL || "",
headers
headers,
});
break;
case "openrouter":

View file

@ -77,8 +77,8 @@ export class StreamRenderer {
case "tool-call":
this.onToolCall(event.toolCallId, event.toolName, event.input);
break;
case "usage":
this.onUsage(event.usage);
case "finish-step":
this.onFinishStep(event.finishReason, event.usage);
break;
}
}
@ -219,13 +219,15 @@ export class StreamRenderer {
this.write("\n");
}
private onUsage(usage: {
inputTokens?: number;
outputTokens?: number;
totalTokens?: number;
reasoningTokens?: number;
cachedInputTokens?: number;
}) {
private onFinishStep(
finishReason: "stop" | "tool-calls" | "length" | "content-filter" | "error" | "other" | "unknown",
usage: {
inputTokens?: number;
outputTokens?: number;
totalTokens?: number;
reasoningTokens?: number;
cachedInputTokens?: number;
}) {
const parts: string[] = [];
if (usage.inputTokens !== undefined) parts.push(`${this.dim("in:")} ${usage.inputTokens}`);
if (usage.outputTokens !== undefined) parts.push(`${this.dim("out:")} ${usage.outputTokens}`);
@ -234,8 +236,13 @@ export class StreamRenderer {
if (usage.totalTokens !== undefined) parts.push(`${this.dim("total:")} ${this.bold(usage.totalTokens.toString())}`);
const line = parts.join(this.dim(" | "));
this.write("\n");
this.write(this.dim("╭─ Usage\n"));
this.write(this.dim("│ ") + line);
this.write(this.bold("╭─ ") + this.bold("Finish"));
this.write("\n");
this.write(this.dim("│ ") + this.dim("reason: ") + finishReason);
if (line.length) {
this.write("\n");
this.write(this.dim("│ ") + line);
}
this.write("\n");
this.write(this.dim("╰─────────────\n"));
}

View file

@ -312,6 +312,8 @@ export function ToolkitAuthModal({
return 'Bearer Token';
case 'BASIC':
return 'Basic Auth';
case 'SAML':
return 'SAML';
default:
return authScheme.toLowerCase().replace('_', ' ');
}

View file

@ -43,7 +43,7 @@
"mermaid": "^11.9.0",
"mongodb": "^6.8.0",
"nanoid": "^5.1.5",
"next": "15.3.4",
"next": "15.3.8",
"posthog-js": "^1.260.1",
"quill": "^2.0.3",
"quill-mention": "^6.0.2",
@ -4201,9 +4201,9 @@
}
},
"node_modules/@next/env": {
"version": "15.3.4",
"resolved": "https://registry.npmjs.org/@next/env/-/env-15.3.4.tgz",
"integrity": "sha512-ZkdYzBseS6UjYzz6ylVKPOK+//zLWvD6Ta+vpoye8cW11AjiQjGYVibF0xuvT4L0iJfAPfZLFidaEzAOywyOAQ==",
"version": "15.3.8",
"resolved": "https://registry.npmjs.org/@next/env/-/env-15.3.8.tgz",
"integrity": "sha512-SAfHg0g91MQVMPioeFeDjE+8UPF3j3BvHjs8ZKJAUz1BG7eMPvfCKOAgNWJ6s1MLNeP6O2InKQRTNblxPWuq+Q==",
"license": "MIT"
},
"node_modules/@next/eslint-plugin-next": {
@ -4247,9 +4247,9 @@
}
},
"node_modules/@next/swc-darwin-arm64": {
"version": "15.3.4",
"resolved": "https://registry.npmjs.org/@next/swc-darwin-arm64/-/swc-darwin-arm64-15.3.4.tgz",
"integrity": "sha512-z0qIYTONmPRbwHWvpyrFXJd5F9YWLCsw3Sjrzj2ZvMYy9NPQMPZ1NjOJh4ojr4oQzcGYwgJKfidzehaNa1BpEg==",
"version": "15.3.5",
"resolved": "https://registry.npmjs.org/@next/swc-darwin-arm64/-/swc-darwin-arm64-15.3.5.tgz",
"integrity": "sha512-lM/8tilIsqBq+2nq9kbTW19vfwFve0NR7MxfkuSUbRSgXlMQoJYg+31+++XwKVSXk4uT23G2eF/7BRIKdn8t8w==",
"cpu": [
"arm64"
],
@ -4263,9 +4263,9 @@
}
},
"node_modules/@next/swc-darwin-x64": {
"version": "15.3.4",
"resolved": "https://registry.npmjs.org/@next/swc-darwin-x64/-/swc-darwin-x64-15.3.4.tgz",
"integrity": "sha512-Z0FYJM8lritw5Wq+vpHYuCIzIlEMjewG2aRkc3Hi2rcbULknYL/xqfpBL23jQnCSrDUGAo/AEv0Z+s2bff9Zkw==",
"version": "15.3.5",
"resolved": "https://registry.npmjs.org/@next/swc-darwin-x64/-/swc-darwin-x64-15.3.5.tgz",
"integrity": "sha512-WhwegPQJ5IfoUNZUVsI9TRAlKpjGVK0tpJTL6KeiC4cux9774NYE9Wu/iCfIkL/5J8rPAkqZpG7n+EfiAfidXA==",
"cpu": [
"x64"
],
@ -4279,9 +4279,9 @@
}
},
"node_modules/@next/swc-linux-arm64-gnu": {
"version": "15.3.4",
"resolved": "https://registry.npmjs.org/@next/swc-linux-arm64-gnu/-/swc-linux-arm64-gnu-15.3.4.tgz",
"integrity": "sha512-l8ZQOCCg7adwmsnFm8m5q9eIPAHdaB2F3cxhufYtVo84pymwKuWfpYTKcUiFcutJdp9xGHC+F1Uq3xnFU1B/7g==",
"version": "15.3.5",
"resolved": "https://registry.npmjs.org/@next/swc-linux-arm64-gnu/-/swc-linux-arm64-gnu-15.3.5.tgz",
"integrity": "sha512-LVD6uMOZ7XePg3KWYdGuzuvVboxujGjbcuP2jsPAN3MnLdLoZUXKRc6ixxfs03RH7qBdEHCZjyLP/jBdCJVRJQ==",
"cpu": [
"arm64"
],
@ -4295,9 +4295,9 @@
}
},
"node_modules/@next/swc-linux-arm64-musl": {
"version": "15.3.4",
"resolved": "https://registry.npmjs.org/@next/swc-linux-arm64-musl/-/swc-linux-arm64-musl-15.3.4.tgz",
"integrity": "sha512-wFyZ7X470YJQtpKot4xCY3gpdn8lE9nTlldG07/kJYexCUpX1piX+MBfZdvulo+t1yADFVEuzFfVHfklfEx8kw==",
"version": "15.3.5",
"resolved": "https://registry.npmjs.org/@next/swc-linux-arm64-musl/-/swc-linux-arm64-musl-15.3.5.tgz",
"integrity": "sha512-k8aVScYZ++BnS2P69ClK7v4nOu702jcF9AIHKu6llhHEtBSmM2zkPGl9yoqbSU/657IIIb0QHpdxEr0iW9z53A==",
"cpu": [
"arm64"
],
@ -4311,9 +4311,9 @@
}
},
"node_modules/@next/swc-linux-x64-gnu": {
"version": "15.3.4",
"resolved": "https://registry.npmjs.org/@next/swc-linux-x64-gnu/-/swc-linux-x64-gnu-15.3.4.tgz",
"integrity": "sha512-gEbH9rv9o7I12qPyvZNVTyP/PWKqOp8clvnoYZQiX800KkqsaJZuOXkWgMa7ANCCh/oEN2ZQheh3yH8/kWPSEg==",
"version": "15.3.5",
"resolved": "https://registry.npmjs.org/@next/swc-linux-x64-gnu/-/swc-linux-x64-gnu-15.3.5.tgz",
"integrity": "sha512-2xYU0DI9DGN/bAHzVwADid22ba5d/xrbrQlr2U+/Q5WkFUzeL0TDR963BdrtLS/4bMmKZGptLeg6282H/S2i8A==",
"cpu": [
"x64"
],
@ -4327,9 +4327,9 @@
}
},
"node_modules/@next/swc-linux-x64-musl": {
"version": "15.3.4",
"resolved": "https://registry.npmjs.org/@next/swc-linux-x64-musl/-/swc-linux-x64-musl-15.3.4.tgz",
"integrity": "sha512-Cf8sr0ufuC/nu/yQ76AnarbSAXcwG/wj+1xFPNbyNo8ltA6kw5d5YqO8kQuwVIxk13SBdtgXrNyom3ZosHAy4A==",
"version": "15.3.5",
"resolved": "https://registry.npmjs.org/@next/swc-linux-x64-musl/-/swc-linux-x64-musl-15.3.5.tgz",
"integrity": "sha512-TRYIqAGf1KCbuAB0gjhdn5Ytd8fV+wJSM2Nh2is/xEqR8PZHxfQuaiNhoF50XfY90sNpaRMaGhF6E+qjV1b9Tg==",
"cpu": [
"x64"
],
@ -4343,9 +4343,9 @@
}
},
"node_modules/@next/swc-win32-arm64-msvc": {
"version": "15.3.4",
"resolved": "https://registry.npmjs.org/@next/swc-win32-arm64-msvc/-/swc-win32-arm64-msvc-15.3.4.tgz",
"integrity": "sha512-ay5+qADDN3rwRbRpEhTOreOn1OyJIXS60tg9WMYTWCy3fB6rGoyjLVxc4dR9PYjEdR2iDYsaF5h03NA+XuYPQQ==",
"version": "15.3.5",
"resolved": "https://registry.npmjs.org/@next/swc-win32-arm64-msvc/-/swc-win32-arm64-msvc-15.3.5.tgz",
"integrity": "sha512-h04/7iMEUSMY6fDGCvdanKqlO1qYvzNxntZlCzfE8i5P0uqzVQWQquU1TIhlz0VqGQGXLrFDuTJVONpqGqjGKQ==",
"cpu": [
"arm64"
],
@ -4359,9 +4359,9 @@
}
},
"node_modules/@next/swc-win32-x64-msvc": {
"version": "15.3.4",
"resolved": "https://registry.npmjs.org/@next/swc-win32-x64-msvc/-/swc-win32-x64-msvc-15.3.4.tgz",
"integrity": "sha512-4kDt31Bc9DGyYs41FTL1/kNpDeHyha2TC0j5sRRoKCyrhNcfZ/nRQkAUlF27mETwm8QyHqIjHJitfcza2Iykfg==",
"version": "15.3.5",
"resolved": "https://registry.npmjs.org/@next/swc-win32-x64-msvc/-/swc-win32-x64-msvc-15.3.5.tgz",
"integrity": "sha512-5fhH6fccXxnX2KhllnGhkYMndhOiLOLEiVGYjP2nizqeGWkN10sA9taATlXwake2E2XMvYZjjz0Uj7T0y+z1yw==",
"cpu": [
"x64"
],
@ -13952,12 +13952,12 @@
}
},
"node_modules/next": {
"version": "15.3.4",
"resolved": "https://registry.npmjs.org/next/-/next-15.3.4.tgz",
"integrity": "sha512-mHKd50C+mCjam/gcnwqL1T1vPx/XQNFlXqFIVdgQdVAFY9iIQtY0IfaVflEYzKiqjeA7B0cYYMaCrmAYFjs4rA==",
"version": "15.3.8",
"resolved": "https://registry.npmjs.org/next/-/next-15.3.8.tgz",
"integrity": "sha512-L+4c5Hlr84fuaNADZbB9+ceRX9/CzwxJ+obXIGHupboB/Q1OLbSUapFs4bO8hnS/E6zV/JDX7sG1QpKVR2bguA==",
"license": "MIT",
"dependencies": {
"@next/env": "15.3.4",
"@next/env": "15.3.8",
"@swc/counter": "0.1.3",
"@swc/helpers": "0.5.15",
"busboy": "1.6.0",
@ -13972,14 +13972,14 @@
"node": "^18.18.0 || ^19.8.0 || >= 20.0.0"
},
"optionalDependencies": {
"@next/swc-darwin-arm64": "15.3.4",
"@next/swc-darwin-x64": "15.3.4",
"@next/swc-linux-arm64-gnu": "15.3.4",
"@next/swc-linux-arm64-musl": "15.3.4",
"@next/swc-linux-x64-gnu": "15.3.4",
"@next/swc-linux-x64-musl": "15.3.4",
"@next/swc-win32-arm64-msvc": "15.3.4",
"@next/swc-win32-x64-msvc": "15.3.4",
"@next/swc-darwin-arm64": "15.3.5",
"@next/swc-darwin-x64": "15.3.5",
"@next/swc-linux-arm64-gnu": "15.3.5",
"@next/swc-linux-arm64-musl": "15.3.5",
"@next/swc-linux-x64-gnu": "15.3.5",
"@next/swc-linux-x64-musl": "15.3.5",
"@next/swc-win32-arm64-msvc": "15.3.5",
"@next/swc-win32-x64-msvc": "15.3.5",
"sharp": "^0.34.1"
},
"peerDependencies": {

View file

@ -51,7 +51,7 @@
"mermaid": "^11.9.0",
"mongodb": "^6.8.0",
"nanoid": "^5.1.5",
"next": "15.3.4",
"next": "15.3.8",
"posthog-js": "^1.260.1",
"quill": "^2.0.3",
"quill-mention": "^6.0.2",

View file

@ -15,6 +15,7 @@ export const ZAuthScheme = z.enum([
'NO_AUTH',
'OAUTH1',
'OAUTH2',
'SAML',
]);
export const ZConnectedAccountStatus = z.enum([

View file

@ -36,16 +36,10 @@ const openai = createOpenAI({
compatibility: "strict",
});
const composioToolSearchToolSuggestion = z.object({
toolkit: z.string(),
tool_slug: z.string(),
description: z.string(),
});
const composioToolSearchResponseSchema = z.object({
main_tools: z.array(composioToolSearchToolSuggestion).optional(),
related_tools: z.array(composioToolSearchToolSuggestion).optional(),
results: z.array(composioToolSearchToolSuggestion).optional(), // Keep for backward compatibility
results: z.array(z.object({
primary_tool_slugs: z.array(z.string()).optional(),
}).passthrough()).optional(),
}).passthrough();
function getContextPrompt(context: z.infer<typeof CopilotChatContext> | null): string {
@ -182,17 +176,19 @@ async function searchRelevantTools(usageTracker: UsageTracker, query: string): P
const result = composioToolSearchResponseSchema.safeParse(searchResult.data);
if (!result.success) {
logger.log(`tool search response is invalid: ${JSON.stringify(result.error)}`);
logger.log(`expected schema: results (array), got: ${JSON.stringify(Object.keys(searchResult.data || {}))}`);
return 'No tools found!';
}
const tools = result.data.main_tools || result.data.results || [];
if (!tools.length) {
// Extract tool slugs from results[].primary_tool_slugs[]
const toolSlugs = (result.data.results || [])
.flatMap((item: any) => item.primary_tool_slugs || [])
.filter((slug: string) => slug);
if (!toolSlugs.length) {
logger.log(`tool search yielded no results`);
return 'No tools found!';
}
const toolSlugs = tools.map((item) => item.tool_slug);
logger.log(`found tool slugs: ${toolSlugs.join(', ')}`);
console.log("✅ TOOL CALL SUCCESS: COMPOSIO_SEARCH_TOOLS", {
toolSlugs,
@ -201,7 +197,20 @@ async function searchRelevantTools(usageTracker: UsageTracker, query: string): P
// Enrich tools with full details
console.log("🔧 TOOL CALL: getTool (multiple calls)", { toolSlugs });
const composioTools = await Promise.all(toolSlugs.map(slug => getTool(slug)));
const composioToolsResults = await Promise.allSettled(
toolSlugs.map(slug => getTool(slug))
);
// Filter out failed tool fetches
const composioTools = composioToolsResults
.filter((result): result is PromiseFulfilledResult<any> => result.status === 'fulfilled')
.map(result => result.value);
if (composioTools.length === 0) {
logger.log('all tool fetches failed');
return 'No tools found!';
}
const workflowTools: z.infer<typeof WorkflowTool>[] = composioTools.map(tool => ({
name: tool.name,
description: tool.description,