Compare commits
38 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 903de44644 | |||
| 5aa69cc998 | |||
| 5ca0c80ea9 | |||
| 940bf3d3ef | |||
| c1b269f301 | |||
| 7cb970f9e2 | |||
| 1fbcf8bb8b | |||
| 4a8789019a | |||
| 0da85a5dcd | |||
| 121e216eea | |||
| eb1058bfb5 | |||
| ecdc125a43 | |||
| 73657be550 | |||
| 4e4d3c0e08 | |||
| 79efe8f6b8 | |||
| 8bcf3257e2 | |||
| 6753553394 | |||
| a46dbd0da6 | |||
| 7379daf4c5 | |||
| 83422b4b0e | |||
| 4310c8086b | |||
| 472a8ed7f8 | |||
| 44137a8710 | |||
| c12a6a7be9 | |||
| 49dcc7a1a1 | |||
| e649e9caab | |||
| c39e7e76b8 | |||
| c24a4306d9 | |||
| 9718048dff | |||
| b1deccaa26 | |||
| 52d1d128c7 | |||
| 60f8bbe1b6 | |||
| b6308d2113 | |||
| e7968a31b1 | |||
| 05e4f03061 | |||
| 37d4069806 | |||
| fe0de36b1a | |||
| e49f35e7de |
116
changelog.md
116
changelog.md
@@ -1,5 +1,121 @@
|
||||
# Changelog
|
||||
|
||||
## 2026-01-20 - 1.8.0 - feat(tools)
|
||||
add ToolRegistry, ToolSearchTool and ExpertTool to support on-demand tool visibility, discovery, activation, and expert/subagent tooling; extend DualAgentOrchestrator API and interfaces to manage tool lifecycle
|
||||
|
||||
- Introduce ToolRegistry to manage tool registration, visibility (initial vs on-demand), activation, initialization, cleanup, and search
|
||||
- Add ToolSearchTool providing search/list/activate/details actions for discovering and enabling on-demand tools
|
||||
- Add ExpertTool to wrap a DualAgentOrchestrator as a sub-agent (expert) tool and the IExpertConfig interface
|
||||
- Extend DualAgentOrchestrator API: registerTool(tool, options?), registerExpert(config), enableToolSearch(), getRegistry(); registerStandardTools/start now initialize visible tools via registry
|
||||
- Add IToolRegistrationOptions and IToolMetadata/IExpertConfig types to smartagent.interfaces and export ToolRegistry/ToolSearchTool/ExpertTool in public entry points
|
||||
- Documentation updates (readme) describing tool visibility, tool search, and expert/subagent system
|
||||
|
||||
## 2026-01-20 - 1.7.0 - feat(docs)
|
||||
document native tool calling support and update README to clarify standard and additional tools
|
||||
|
||||
- Add 'Native Tool Calling' section documenting useNativeToolCalling option and behavior for providers (e.g., Ollama).
|
||||
- Explain tool name mapping when native tool calling is enabled (toolName_actionName) and streaming markers ([THINKING], [OUTPUT]).
|
||||
- Add example showing enabling useNativeToolCalling and note ollamaToken config option (Ollama endpoint).
|
||||
- Clarify that registerStandardTools() registers five tools (Filesystem, HTTP, Shell, Browser, Deno) and that JsonValidatorTool must be registered manually as an additional tool.
|
||||
- Documentation-only changes (README updates) — no code functionality changed in this diff.
|
||||
|
||||
## 2026-01-20 - 1.6.2 - fix(release)
|
||||
bump version to 1.6.2
|
||||
|
||||
- No source changes detected in the diff
|
||||
- Current package.json version is 1.6.1
|
||||
- Recommend a patch bump to 1.6.2 for a release
|
||||
|
||||
## 2026-01-20 - 1.6.1 - fix(driveragent)
|
||||
include full message history for tool results and use a continuation prompt when invoking provider.collectStreamResponse
|
||||
|
||||
- When toolName is provided, include the full messageHistory (do not slice off the last message) so tool result messages are preserved.
|
||||
- Set userMessage to a continuation prompt ('Continue with the task. The tool result has been provided above.') when handling tool results to avoid repeating the tool output.
|
||||
- Keeps existing maxHistoryMessages trimming and validates provider.collectStreamResponse is available before streaming.
|
||||
|
||||
## 2026-01-20 - 1.6.0 - feat(smartagent)
|
||||
record native tool results in message history by adding optional toolName to continueWithNativeTools and passing tool identifier from DualAgent
|
||||
|
||||
- continueWithNativeTools(message, toolName?) now accepts an optional toolName; when provided the message is stored with role 'tool' and includes a toolName property (cast to ChatMessage)
|
||||
- DualAgent constructs a toolNameForHistory as `${proposal.toolName}_${proposal.action}` and forwards it to continueWithNativeTools in both normal and error flows
|
||||
- Preserves tool-origin information in the conversation history to support native tool calling and tracking
|
||||
|
||||
## 2026-01-20 - 1.5.4 - fix(driveragent)
|
||||
prevent duplicate thinking/output markers during token streaming and mark transitions
|
||||
|
||||
- Add isInThinkingMode flag to track thinking vs output state
|
||||
- Emit "\n[THINKING] " only when transitioning into thinking mode (avoids repeated thinking markers)
|
||||
- Emit "\n[OUTPUT] " when transitioning out of thinking mode to mark content output
|
||||
- Reset thinking state after response completes to ensure correct markers for subsequent responses
|
||||
- Applied the same streaming marker logic to both response handling paths
|
||||
|
||||
## 2026-01-20 - 1.5.3 - fix(driveragent)
|
||||
prefix thinking tokens with [THINKING] when forwarding streaming chunks to onToken
|
||||
|
||||
- Wraps chunk.thinking with '[THINKING] ' before calling onToken to mark thinking tokens
|
||||
- Forwards chunk.content unchanged
|
||||
- Change applied in ts/smartagent.classes.driveragent.ts for both initial and subsequent assistant streaming responses
|
||||
- No API signature changes; only the token payloads sent to onToken are altered
|
||||
|
||||
## 2026-01-20 - 1.5.2 - fix()
|
||||
no changes in this diff; nothing to release
|
||||
|
||||
- No files changed; no release required
|
||||
- No code or dependency changes detected
|
||||
|
||||
## 2026-01-20 - 1.5.1 - fix(smartagent)
|
||||
bump patch version to 1.5.1 (no changes in diff)
|
||||
|
||||
- No code changes detected in the provided diff
|
||||
- Current package.json version is 1.5.0
|
||||
- Recommended semantic version bump: patch -> 1.5.1
|
||||
|
||||
## 2026-01-20 - 1.5.0 - feat(driveragent)
|
||||
preserve assistant reasoning in message history and update @push.rocks/smartai dependency to ^0.13.0
|
||||
|
||||
- Store response.reasoning in messageHistory for assistant responses (two places in driveragent)
|
||||
- Bump dependency @push.rocks/smartai from ^0.12.0 to ^0.13.0
|
||||
|
||||
## 2026-01-20 - 1.4.2 - fix(repo)
|
||||
no changes detected in diff
|
||||
|
||||
- No files changed in diff; no code or metadata updates were made.
|
||||
- No version bump required.
|
||||
|
||||
## 2026-01-20 - 1.4.1 - fix()
|
||||
no changes detected (empty diff)
|
||||
|
||||
- No files changed in this commit
|
||||
- No release required
|
||||
|
||||
## 2026-01-20 - 1.4.0 - feat(docs)
|
||||
document Dual-Agent Driver/Guardian architecture, new standard tools, streaming/vision support, progress events, and updated API/export docs
|
||||
|
||||
- Add DualAgentOrchestrator concept and describe Driver/Guardian agents and BaseToolWrapper
|
||||
- Document six standard tools including new JsonValidatorTool and expanded descriptions for Filesystem, Http, Shell, Browser, Deno
|
||||
- Add examples for scoped filesystem with exclusion patterns and line-range reads
|
||||
- Add token streaming (onToken) and progress events (onProgress) examples and event types
|
||||
- Document vision support for passing images as base64 and example usage
|
||||
- Expose additional config options in docs: name, verbose, maxResultChars, maxHistoryMessages, onProgress, onToken, logPrefix
|
||||
- Document additional result fields: toolCallCount, rejectionCount, toolLog, and error
|
||||
- Update API signatures in docs: run(task, options?) and registerScopedFilesystemTool(basePath, excludePatterns?)
|
||||
- Update re-exports to include IFilesystemToolOptions, TDenoPermission, JsonValidatorTool and re-export several smartai types
|
||||
|
||||
## 2026-01-20 - 1.3.0 - feat(smartagent)
|
||||
add JsonValidatorTool and support passing base64-encoded images with task runs (vision-capable models); bump @push.rocks/smartai to ^0.12.0
|
||||
|
||||
- Add JsonValidatorTool (validate/format actions) implemented in ts/smartagent.tools.json.ts
|
||||
- Export JsonValidatorTool from ts/index.ts
|
||||
- Add ITaskRunOptions interface (images?: string[]) in smartagent.interfaces.ts
|
||||
- DualAgent.run and Driver.startTask accept optional images and pass them to provider.chat/provider.chatStreaming; assistant responses added to message history
|
||||
- Bump dependency @push.rocks/smartai from ^0.11.1 to ^0.12.0 in package.json
|
||||
|
||||
## 2026-01-20 - 1.2.7 - fix(deps(smartai))
|
||||
bump @push.rocks/smartai to ^0.11.0
|
||||
|
||||
- package.json: @push.rocks/smartai updated from ^0.10.1 to ^0.11.0
|
||||
- Recommend a patch release since this is a dependency update with no breaking API changes: 1.2.7
|
||||
|
||||
## 2026-01-20 - 1.2.6 - fix(deps)
|
||||
bump @push.rocks/smartai to ^0.10.1
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@push.rocks/smartagent",
|
||||
"version": "1.2.6",
|
||||
"version": "1.8.0",
|
||||
"private": false,
|
||||
"description": "an agentic framework built on top of @push.rocks/smartai",
|
||||
"main": "dist_ts/index.js",
|
||||
@@ -21,7 +21,7 @@
|
||||
"@types/node": "^25.0.2"
|
||||
},
|
||||
"dependencies": {
|
||||
"@push.rocks/smartai": "^0.10.1",
|
||||
"@push.rocks/smartai": "^0.13.3",
|
||||
"@push.rocks/smartbrowser": "^2.0.8",
|
||||
"@push.rocks/smartdeno": "^1.2.0",
|
||||
"@push.rocks/smartfs": "^1.2.0",
|
||||
|
||||
10
pnpm-lock.yaml
generated
10
pnpm-lock.yaml
generated
@@ -9,8 +9,8 @@ importers:
|
||||
.:
|
||||
dependencies:
|
||||
'@push.rocks/smartai':
|
||||
specifier: ^0.10.1
|
||||
version: 0.10.1(typescript@5.9.3)(ws@8.18.3)(zod@3.25.76)
|
||||
specifier: ^0.13.3
|
||||
version: 0.13.3(typescript@5.9.3)(ws@8.18.3)(zod@3.25.76)
|
||||
'@push.rocks/smartbrowser':
|
||||
specifier: ^2.0.8
|
||||
version: 2.0.8(typescript@5.9.3)
|
||||
@@ -844,8 +844,8 @@ packages:
|
||||
'@push.rocks/qenv@6.1.3':
|
||||
resolution: {integrity: sha512-+z2hsAU/7CIgpYLFqvda8cn9rUBMHqLdQLjsFfRn5jPoD7dJ5rFlpkbhfM4Ws8mHMniwWaxGKo+q/YBhtzRBLg==}
|
||||
|
||||
'@push.rocks/smartai@0.10.1':
|
||||
resolution: {integrity: sha512-Q/Jp0i3lQrCHa4URfUfg7e2S4vZOcTpC0U2iB2l5s2J1F0VLN1VUFQmpWaoFrsUdarqVEq8lp0E2z6HrvpcqAQ==}
|
||||
'@push.rocks/smartai@0.13.3':
|
||||
resolution: {integrity: sha512-VDZzHs101hpGMmUaectuLfcME4kHpuOS7o5ffuGk5lYl383foyAN71+5v441jpk/gLDNf2KhDACR/d2O4n90Ag==}
|
||||
|
||||
'@push.rocks/smartarchive@4.2.4':
|
||||
resolution: {integrity: sha512-uiqVAXPxmr8G5rv3uZvZFMOCt8l7cZC3nzvsy4YQqKf/VkPhKIEX+b7LkAeNlxPSYUiBQUkNRoawg9+5BaMcHg==}
|
||||
@@ -5172,7 +5172,7 @@ snapshots:
|
||||
'@push.rocks/smartlog': 3.1.10
|
||||
'@push.rocks/smartpath': 6.0.0
|
||||
|
||||
'@push.rocks/smartai@0.10.1(typescript@5.9.3)(ws@8.18.3)(zod@3.25.76)':
|
||||
'@push.rocks/smartai@0.13.3(typescript@5.9.3)(ws@8.18.3)(zod@3.25.76)':
|
||||
dependencies:
|
||||
'@anthropic-ai/sdk': 0.71.2(zod@3.25.76)
|
||||
'@mistralai/mistralai': 1.12.0
|
||||
|
||||
@@ -1,15 +1,96 @@
|
||||
# Project Readme Hints
|
||||
|
||||
## Overview
|
||||
`@push.rocks/smartagent` is an agentic framework built on top of `@push.rocks/smartai`. It provides autonomous AI agent capabilities including tool use, multi-step reasoning, and conversation memory.
|
||||
`@push.rocks/smartagent` is a dual-agent agentic framework built on top of `@push.rocks/smartai`. It implements a Driver/Guardian architecture where the Driver proposes tool calls and the Guardian evaluates them against security policies.
|
||||
|
||||
## Architecture
|
||||
- **SmartAgent**: Main class that wraps SmartAi and adds agentic behaviors
|
||||
- **plugins.ts**: Imports and re-exports smartai
|
||||
- **index.ts**: Main entry point, exports SmartAgent class and relevant types
|
||||
- **DualAgentOrchestrator**: Main entry point, coordinates Driver and Guardian agents
|
||||
- **DriverAgent**: Reasons about tasks, plans steps, proposes tool calls (supports both XML and native tool calling)
|
||||
- **GuardianAgent**: Evaluates tool calls against configured policies
|
||||
- **ToolRegistry**: Manages tool lifecycle, visibility, and discovery
|
||||
- **BaseToolWrapper**: Base class for creating custom tools
|
||||
- **plugins.ts**: Imports and re-exports smartai and other dependencies
|
||||
|
||||
## Standard Tools (via registerStandardTools)
|
||||
1. **FilesystemTool** - File operations with scoping and exclusion patterns
|
||||
2. **HttpTool** - HTTP requests
|
||||
3. **ShellTool** - Secure shell commands (no injection possible)
|
||||
4. **BrowserTool** - Web page interaction via Puppeteer
|
||||
5. **DenoTool** - Sandboxed TypeScript/JavaScript execution
|
||||
|
||||
## Additional Tools (must register manually)
|
||||
6. **JsonValidatorTool** - JSON validation and formatting (NOT in registerStandardTools)
|
||||
7. **ToolSearchTool** - AI-facing interface for tool discovery and activation
|
||||
8. **ExpertTool** - Wraps a DualAgentOrchestrator as a specialized expert tool
|
||||
|
||||
## Tool Visibility System
|
||||
Tools can be registered with visibility modes:
|
||||
- **initial**: Always visible to Driver, included in system prompt (default)
|
||||
- **on-demand**: Only discoverable via search, must be activated before use
|
||||
|
||||
```typescript
|
||||
// Register with visibility options
|
||||
orchestrator.registerTool(myTool, {
|
||||
visibility: 'on-demand',
|
||||
tags: ['database', 'sql'],
|
||||
category: 'data'
|
||||
});
|
||||
```
|
||||
|
||||
## Expert/SubAgent System
|
||||
Experts are specialized agents wrapped as tools, enabling hierarchical agent architectures:
|
||||
|
||||
```typescript
|
||||
orchestrator.registerExpert({
|
||||
name: 'code_reviewer',
|
||||
description: 'Reviews code for quality and best practices',
|
||||
systemMessage: 'You are a code review expert...',
|
||||
guardianPolicy: 'Allow read-only file access',
|
||||
tools: [new FilesystemTool()],
|
||||
visibility: 'on-demand',
|
||||
tags: ['code', 'review']
|
||||
});
|
||||
```
|
||||
|
||||
## Tool Search
|
||||
Enable tool discovery for the Driver:
|
||||
|
||||
```typescript
|
||||
orchestrator.enableToolSearch();
|
||||
// Driver can now use:
|
||||
// - tools.search({"query": "database"})
|
||||
// - tools.list({})
|
||||
// - tools.activate({"name": "database_expert"})
|
||||
// - tools.details({"name": "filesystem"})
|
||||
```
|
||||
|
||||
## Key Features
|
||||
- Token streaming support (`onToken` callback)
|
||||
- Vision support (pass images as base64)
|
||||
- Progress events (`onProgress` callback)
|
||||
- Scoped filesystem with exclusion patterns
|
||||
- Result truncation with configurable limits
|
||||
- History windowing to manage token usage
|
||||
- **Native tool calling mode** (`useNativeToolCalling: true`) for providers like Ollama
|
||||
- **Tool visibility system** (initial vs on-demand)
|
||||
- **Expert/SubAgent system** for hierarchical agents
|
||||
- **Tool search and discovery** via ToolSearchTool
|
||||
|
||||
## Native Tool Calling
|
||||
When `useNativeToolCalling` is enabled:
|
||||
- Uses provider's built-in tool calling API instead of XML parsing
|
||||
- Tool names become `toolName_actionName` (e.g., `json_validate`)
|
||||
- Streaming includes `[THINKING]` and `[OUTPUT]` markers
|
||||
- More efficient for models that support it
|
||||
|
||||
## Key Dependencies
|
||||
- `@push.rocks/smartai`: Provides the underlying multi-modal AI provider interface
|
||||
- `@push.rocks/smartai`: Multi-provider AI interface
|
||||
- `@push.rocks/smartfs`: Filesystem operations
|
||||
- `@push.rocks/smartshell`: Shell command execution
|
||||
- `@push.rocks/smartbrowser`: Browser automation
|
||||
- `@push.rocks/smartdeno`: Deno code execution
|
||||
- `@push.rocks/smartrequest`: HTTP requests
|
||||
- `minimatch`: Glob pattern matching for exclusions
|
||||
|
||||
## Test Structure
|
||||
- Tests use `@git.zone/tstest/tapbundle`
|
||||
|
||||
364
readme.md
364
readme.md
@@ -37,19 +37,19 @@ flowchart TB
|
||||
end
|
||||
|
||||
subgraph Orchestrator["DualAgentOrchestrator"]
|
||||
Registry["ToolRegistry<br/><i>Visibility & Lifecycle</i>"]
|
||||
Driver["Driver Agent<br/><i>Reason + Plan</i>"]
|
||||
Guardian["Guardian Agent<br/><i>Evaluate against policy</i>"]
|
||||
|
||||
Driver -->|"tool call proposal"| Guardian
|
||||
Guardian -->|"approve / reject + feedback"| Driver
|
||||
Registry -->|"visible tools"| Driver
|
||||
end
|
||||
|
||||
subgraph Tools["Standard Tools"]
|
||||
FS["Filesystem"]
|
||||
HTTP["HTTP"]
|
||||
Shell["Shell"]
|
||||
Browser["Browser"]
|
||||
Deno["Deno"]
|
||||
subgraph Tools["Tools"]
|
||||
Initial["Initial Tools<br/><i>Always visible</i>"]
|
||||
OnDemand["On-Demand Tools<br/><i>Discoverable via search</i>"]
|
||||
Experts["Expert SubAgents<br/><i>Specialized agents as tools</i>"]
|
||||
end
|
||||
|
||||
Task --> Orchestrator
|
||||
@@ -99,7 +99,7 @@ await orchestrator.stop();
|
||||
|
||||
## Standard Tools
|
||||
|
||||
SmartAgent comes with five battle-tested tools out of the box:
|
||||
SmartAgent comes with five battle-tested tools out of the box via `registerStandardTools()`:
|
||||
|
||||
### 🗂️ FilesystemTool
|
||||
|
||||
@@ -117,11 +117,29 @@ File and directory operations powered by `@push.rocks/smartfs`.
|
||||
</tool_call>
|
||||
```
|
||||
|
||||
**Scoped Filesystem**: Lock file operations to a specific directory:
|
||||
**Scoped Filesystem**: Lock file operations to a specific directory with optional exclusion patterns:
|
||||
|
||||
```typescript
|
||||
// Only allow access within a specific directory
|
||||
orchestrator.registerScopedFilesystemTool('/home/user/workspace');
|
||||
|
||||
// With exclusion patterns (glob syntax)
|
||||
orchestrator.registerScopedFilesystemTool('/home/user/workspace', [
|
||||
'.nogit/**',
|
||||
'node_modules/**',
|
||||
'*.secret',
|
||||
]);
|
||||
```
|
||||
|
||||
**Line-range Reading**: Read specific portions of large files:
|
||||
|
||||
```typescript
|
||||
<tool_call>
|
||||
<tool>filesystem</tool>
|
||||
<action>read</action>
|
||||
<params>{"path": "/var/log/app.log", "startLine": 100, "endLine": 150}</params>
|
||||
<reasoning>Reading only the relevant log section to avoid token overload</reasoning>
|
||||
</tool_call>
|
||||
```
|
||||
|
||||
### 🌐 HttpTool
|
||||
@@ -212,6 +230,288 @@ By default, code runs **fully sandboxed with no permissions**. Permissions must
|
||||
</tool_call>
|
||||
```
|
||||
|
||||
## Additional Tools
|
||||
|
||||
### 📋 JsonValidatorTool
|
||||
|
||||
Validate and format JSON data. Perfect for agents to self-check their JSON output before completing tasks.
|
||||
|
||||
**Actions**: `validate`, `format`
|
||||
|
||||
```typescript
|
||||
import { JsonValidatorTool } from '@push.rocks/smartagent';
|
||||
|
||||
// Register the JSON validator tool (not included in registerStandardTools)
|
||||
orchestrator.registerTool(new JsonValidatorTool());
|
||||
```
|
||||
|
||||
```typescript
|
||||
// Validate JSON with required field checking
|
||||
<tool_call>
|
||||
<tool>json</tool>
|
||||
<action>validate</action>
|
||||
<params>{
|
||||
"jsonString": "{\"name\": \"test\", \"version\": \"1.0.0\"}",
|
||||
"requiredFields": ["name", "version", "description"]
|
||||
}</params>
|
||||
<reasoning>Ensuring the config has all required fields before saving</reasoning>
|
||||
</tool_call>
|
||||
|
||||
// Pretty-print JSON
|
||||
<tool_call>
|
||||
<tool>json</tool>
|
||||
<action>format</action>
|
||||
<params>{"jsonString": "{\"compact\":true,\"data\":[1,2,3]}"}</params>
|
||||
<reasoning>Formatting JSON for readable output</reasoning>
|
||||
</tool_call>
|
||||
```
|
||||
|
||||
### 🔍 ToolSearchTool
|
||||
|
||||
Enable the Driver to discover and activate on-demand tools at runtime.
|
||||
|
||||
**Actions**: `search`, `list`, `activate`, `details`
|
||||
|
||||
```typescript
|
||||
// Enable tool search (adds the 'tools' tool)
|
||||
orchestrator.enableToolSearch();
|
||||
```
|
||||
|
||||
```typescript
|
||||
// Search for tools by capability
|
||||
<tool_call>
|
||||
<tool>tools</tool>
|
||||
<action>search</action>
|
||||
<params>{"query": "database"}</params>
|
||||
</tool_call>
|
||||
|
||||
// List all available tools
|
||||
<tool_call>
|
||||
<tool>tools</tool>
|
||||
<action>list</action>
|
||||
<params>{}</params>
|
||||
</tool_call>
|
||||
|
||||
// Activate an on-demand tool
|
||||
<tool_call>
|
||||
<tool>tools</tool>
|
||||
<action>activate</action>
|
||||
<params>{"name": "database_expert"}</params>
|
||||
</tool_call>
|
||||
|
||||
// Get detailed information about a tool
|
||||
<tool_call>
|
||||
<tool>tools</tool>
|
||||
<action>details</action>
|
||||
<params>{"name": "filesystem"}</params>
|
||||
</tool_call>
|
||||
```
|
||||
|
||||
### 🧠 ExpertTool (SubAgents)
|
||||
|
||||
Create specialized sub-agents that can be invoked as tools. Experts are complete `DualAgentOrchestrator` instances wrapped as tools, enabling hierarchical agent architectures.
|
||||
|
||||
**Actions**: `consult`
|
||||
|
||||
```typescript
|
||||
// Register an expert for code review
|
||||
orchestrator.registerExpert({
|
||||
name: 'code_reviewer',
|
||||
description: 'Reviews code for quality, bugs, and best practices',
|
||||
systemMessage: `You are an expert code reviewer. Analyze code for:
|
||||
- Bugs and potential issues
|
||||
- Code style and best practices
|
||||
- Performance concerns
|
||||
- Security vulnerabilities`,
|
||||
guardianPolicy: 'Allow read-only file access within the workspace',
|
||||
tools: [new FilesystemTool()],
|
||||
visibility: 'on-demand', // Only available via tool search
|
||||
tags: ['code', 'review', 'quality'],
|
||||
category: 'expert',
|
||||
});
|
||||
```
|
||||
|
||||
```typescript
|
||||
// Consult an expert
|
||||
<tool_call>
|
||||
<tool>code_reviewer</tool>
|
||||
<action>consult</action>
|
||||
<params>{
|
||||
"task": "Review this function for potential issues",
|
||||
"context": "This is a user authentication handler"
|
||||
}</params>
|
||||
</tool_call>
|
||||
```
|
||||
|
||||
## 🎯 Tool Visibility System
|
||||
|
||||
SmartAgent supports **tool visibility modes** for scalable agent architectures:
|
||||
|
||||
- **`initial`** (default): Tool is visible to the Driver from the start, included in the system prompt
|
||||
- **`on-demand`**: Tool is hidden until explicitly activated via `tools.activate()`
|
||||
|
||||
This enables you to have many specialized tools/experts without overwhelming the Driver's context.
|
||||
|
||||
```typescript
|
||||
// Register a tool with on-demand visibility
|
||||
orchestrator.registerTool(new MySpecializedTool(), {
|
||||
visibility: 'on-demand',
|
||||
tags: ['specialized', 'database'],
|
||||
category: 'data',
|
||||
});
|
||||
|
||||
// Enable tool search so Driver can discover and activate on-demand tools
|
||||
orchestrator.enableToolSearch();
|
||||
|
||||
// The Driver can now:
|
||||
// 1. tools.search({"query": "database"}) -> finds MySpecializedTool
|
||||
// 2. tools.activate({"name": "myspecialized"}) -> enables it
|
||||
// 3. myspecialized.action({...}) -> use the tool
|
||||
```
|
||||
|
||||
### Expert SubAgent Example
|
||||
|
||||
```typescript
|
||||
const orchestrator = new DualAgentOrchestrator({
|
||||
openaiToken: 'sk-...',
|
||||
defaultProvider: 'openai',
|
||||
guardianPolicyPrompt: 'Allow safe operations...',
|
||||
});
|
||||
|
||||
orchestrator.registerStandardTools();
|
||||
orchestrator.enableToolSearch();
|
||||
|
||||
// Initial expert (always visible)
|
||||
orchestrator.registerExpert({
|
||||
name: 'code_assistant',
|
||||
description: 'Helps with coding tasks and code generation',
|
||||
systemMessage: 'You are a helpful coding assistant...',
|
||||
guardianPolicy: 'Allow read-only file access',
|
||||
tools: [new FilesystemTool()],
|
||||
});
|
||||
|
||||
// On-demand experts (discoverable via search)
|
||||
orchestrator.registerExpert({
|
||||
name: 'database_expert',
|
||||
description: 'Database design, optimization, and query analysis',
|
||||
systemMessage: 'You are a database expert...',
|
||||
guardianPolicy: 'Allow read-only operations',
|
||||
visibility: 'on-demand',
|
||||
tags: ['database', 'sql', 'optimization'],
|
||||
});
|
||||
|
||||
orchestrator.registerExpert({
|
||||
name: 'security_auditor',
|
||||
description: 'Security vulnerability assessment and best practices',
|
||||
systemMessage: 'You are a security expert...',
|
||||
guardianPolicy: 'Allow read-only file access',
|
||||
visibility: 'on-demand',
|
||||
tags: ['security', 'audit', 'vulnerabilities'],
|
||||
});
|
||||
|
||||
await orchestrator.start();
|
||||
|
||||
// Now the Driver can:
|
||||
// - Use code_assistant directly
|
||||
// - Search for "database" and activate database_expert when needed
|
||||
// - Search for "security" and activate security_auditor when needed
|
||||
```
|
||||
|
||||
## 🎥 Streaming Support
|
||||
|
||||
SmartAgent supports token-by-token streaming for real-time output during LLM generation:
|
||||
|
||||
```typescript
|
||||
const orchestrator = new DualAgentOrchestrator({
|
||||
openaiToken: 'sk-...',
|
||||
defaultProvider: 'openai',
|
||||
guardianPolicyPrompt: '...',
|
||||
|
||||
// Token streaming callback
|
||||
onToken: (token, source) => {
|
||||
// source is 'driver' or 'guardian'
|
||||
process.stdout.write(token);
|
||||
},
|
||||
});
|
||||
```
|
||||
|
||||
This is perfect for CLI applications or UIs that need to show progress as the agent thinks.
|
||||
|
||||
## 🖼️ Vision Support
|
||||
|
||||
Pass images to vision-capable models for multimodal tasks:
|
||||
|
||||
```typescript
|
||||
import { readFileSync } from 'fs';
|
||||
|
||||
// Load image as base64
|
||||
const imageBase64 = readFileSync('screenshot.png').toString('base64');
|
||||
|
||||
// Run task with images
|
||||
const result = await orchestrator.run(
|
||||
'Analyze this UI screenshot and describe any usability issues',
|
||||
{ images: [imageBase64] }
|
||||
);
|
||||
```
|
||||
|
||||
## 📊 Progress Events
|
||||
|
||||
Get real-time feedback on task execution with the `onProgress` callback:
|
||||
|
||||
```typescript
|
||||
const orchestrator = new DualAgentOrchestrator({
|
||||
openaiToken: 'sk-...',
|
||||
guardianPolicyPrompt: '...',
|
||||
logPrefix: '[MyAgent]', // Optional prefix for log messages
|
||||
|
||||
onProgress: (event) => {
|
||||
// Pre-formatted log message ready for output
|
||||
console.log(event.logMessage);
|
||||
|
||||
// Or handle specific event types
|
||||
switch (event.type) {
|
||||
case 'tool_proposed':
|
||||
console.log(`Proposing: ${event.toolName}.${event.action}`);
|
||||
break;
|
||||
case 'tool_approved':
|
||||
console.log(`✓ Approved`);
|
||||
break;
|
||||
case 'tool_rejected':
|
||||
console.log(`✗ Rejected: ${event.reason}`);
|
||||
break;
|
||||
case 'task_completed':
|
||||
console.log(`Done in ${event.iteration} iterations`);
|
||||
break;
|
||||
}
|
||||
},
|
||||
});
|
||||
```
|
||||
|
||||
**Event Types**: `task_started`, `iteration_started`, `tool_proposed`, `guardian_evaluating`, `tool_approved`, `tool_rejected`, `tool_executing`, `tool_completed`, `task_completed`, `clarification_needed`, `max_iterations`, `max_rejections`
|
||||
|
||||
## 🔧 Native Tool Calling
|
||||
|
||||
For providers that support native tool calling (like Ollama with certain models), SmartAgent can use the provider's built-in tool calling API instead of XML parsing:
|
||||
|
||||
```typescript
|
||||
const orchestrator = new DualAgentOrchestrator({
|
||||
ollamaToken: 'http://localhost:11434', // Ollama endpoint
|
||||
defaultProvider: 'ollama',
|
||||
guardianPolicyPrompt: '...',
|
||||
|
||||
// Enable native tool calling
|
||||
useNativeToolCalling: true,
|
||||
});
|
||||
```
|
||||
|
||||
When `useNativeToolCalling` is enabled:
|
||||
- Tools are converted to JSON schema format automatically
|
||||
- The provider handles tool call parsing natively
|
||||
- Streaming still works with `[THINKING]` and `[OUTPUT]` markers for supported models
|
||||
- Tool calls appear as `toolName_actionName` (e.g., `json_validate`)
|
||||
|
||||
This is more efficient for models that support it and avoids potential XML parsing issues.
|
||||
|
||||
## Guardian Policy Examples
|
||||
|
||||
The Guardian's power comes from your policy. Here are battle-tested examples:
|
||||
@@ -283,6 +583,7 @@ interface IDualAgentOptions {
|
||||
perplexityToken?: string;
|
||||
groqToken?: string;
|
||||
xaiToken?: string;
|
||||
ollamaToken?: string; // URL for Ollama endpoint
|
||||
|
||||
// Use existing SmartAi instance (optional - avoids duplicate providers)
|
||||
smartAiInstance?: SmartAi;
|
||||
@@ -294,10 +595,22 @@ interface IDualAgentOptions {
|
||||
// Agent configuration
|
||||
driverSystemMessage?: string; // Custom system message for Driver
|
||||
guardianPolicyPrompt: string; // REQUIRED: Policy for Guardian to enforce
|
||||
name?: string; // Agent system name
|
||||
verbose?: boolean; // Enable verbose logging
|
||||
|
||||
// Native tool calling
|
||||
useNativeToolCalling?: boolean; // Use provider's native tool calling API (default: false)
|
||||
|
||||
// Limits
|
||||
maxIterations?: number; // Max task iterations (default: 20)
|
||||
maxConsecutiveRejections?: number; // Abort after N rejections (default: 3)
|
||||
maxResultChars?: number; // Max chars for tool results before truncation (default: 15000)
|
||||
maxHistoryMessages?: number; // Max history messages for API (default: 20)
|
||||
|
||||
// Callbacks
|
||||
onProgress?: (event: IProgressEvent) => void; // Progress event callback
|
||||
onToken?: (token: string, source: 'driver' | 'guardian') => void; // Streaming callback
|
||||
logPrefix?: string; // Prefix for log messages
|
||||
}
|
||||
```
|
||||
|
||||
@@ -311,6 +624,10 @@ interface IDualAgentRunResult {
|
||||
iterations: number; // Number of iterations taken
|
||||
history: IAgentMessage[]; // Full conversation history
|
||||
status: TDualAgentRunStatus; // 'completed' | 'max_iterations_reached' | etc.
|
||||
toolCallCount?: number; // Number of tool calls made
|
||||
rejectionCount?: number; // Number of Guardian rejections
|
||||
toolLog?: IToolExecutionLog[]; // Detailed tool execution log
|
||||
error?: string; // Error message if status is 'error'
|
||||
}
|
||||
|
||||
type TDualAgentRunStatus =
|
||||
@@ -365,6 +682,7 @@ class MyCustomTool extends BaseToolWrapper {
|
||||
return {
|
||||
success: true,
|
||||
result: { processed: params.input },
|
||||
summary: `Processed input: ${params.input}`, // Optional human-readable summary
|
||||
};
|
||||
}
|
||||
|
||||
@@ -439,14 +757,17 @@ const orchestrator = new DualAgentOrchestrator({
|
||||
|--------|-------------|
|
||||
| `start()` | Initialize all tools and AI providers |
|
||||
| `stop()` | Cleanup all tools and resources |
|
||||
| `run(task: string)` | Execute a task and return result |
|
||||
| `continueTask(input: string)` | Continue a task with user input |
|
||||
| `registerTool(tool)` | Register a custom tool |
|
||||
| `registerStandardTools()` | Register all built-in tools |
|
||||
| `registerScopedFilesystemTool(basePath)` | Register filesystem tool with path restriction |
|
||||
| `run(task, options?)` | Execute a task with optional images for vision |
|
||||
| `continueTask(input)` | Continue a task with user input |
|
||||
| `registerTool(tool, options?)` | Register a custom tool with optional visibility settings |
|
||||
| `registerStandardTools()` | Register all built-in tools (Filesystem, HTTP, Shell, Browser, Deno) |
|
||||
| `registerScopedFilesystemTool(basePath, excludePatterns?)` | Register filesystem tool with path restriction |
|
||||
| `registerExpert(config)` | Register a specialized sub-agent as a tool |
|
||||
| `enableToolSearch()` | Enable tool discovery and activation for the Driver |
|
||||
| `setGuardianPolicy(policy)` | Update Guardian policy at runtime |
|
||||
| `getHistory()` | Get conversation history |
|
||||
| `getToolNames()` | Get list of registered tool names |
|
||||
| `getRegistry()` | Get the ToolRegistry for advanced operations |
|
||||
| `isActive()` | Check if orchestrator is running |
|
||||
|
||||
### Exports
|
||||
@@ -457,16 +778,25 @@ export { DualAgentOrchestrator } from '@push.rocks/smartagent';
|
||||
export { DriverAgent } from '@push.rocks/smartagent';
|
||||
export { GuardianAgent } from '@push.rocks/smartagent';
|
||||
|
||||
// Tool Registry
|
||||
export { ToolRegistry } from '@push.rocks/smartagent';
|
||||
|
||||
// Tools
|
||||
export { BaseToolWrapper } from '@push.rocks/smartagent';
|
||||
export { FilesystemTool } from '@push.rocks/smartagent';
|
||||
export { FilesystemTool, type IFilesystemToolOptions } from '@push.rocks/smartagent';
|
||||
export { HttpTool } from '@push.rocks/smartagent';
|
||||
export { ShellTool } from '@push.rocks/smartagent';
|
||||
export { BrowserTool } from '@push.rocks/smartagent';
|
||||
export { DenoTool } from '@push.rocks/smartagent';
|
||||
export { DenoTool, type TDenoPermission } from '@push.rocks/smartagent';
|
||||
export { JsonValidatorTool } from '@push.rocks/smartagent';
|
||||
export { ToolSearchTool } from '@push.rocks/smartagent';
|
||||
export { ExpertTool } from '@push.rocks/smartagent';
|
||||
|
||||
// Types and interfaces
|
||||
export * from '@push.rocks/smartagent'; // All interfaces
|
||||
export * from '@push.rocks/smartagent'; // All interfaces (IExpertConfig, IToolMetadata, etc.)
|
||||
|
||||
// Re-exported from @push.rocks/smartai
|
||||
export { type ISmartAiOptions, type TProvider, type ChatMessage, type ChatOptions, type ChatResponse };
|
||||
```
|
||||
|
||||
## License and Legal Information
|
||||
@@ -483,7 +813,7 @@ Use of these trademarks must comply with Task Venture Capital GmbH's Trademark G
|
||||
|
||||
### Company Information
|
||||
|
||||
Task Venture Capital GmbH
|
||||
Task Venture Capital GmbH
|
||||
Registered at District Court Bremen HRB 35230 HB, Germany
|
||||
|
||||
For any legal inquiries or further information, please contact us via email at hello@task.vc.
|
||||
|
||||
@@ -3,6 +3,6 @@
|
||||
*/
|
||||
export const commitinfo = {
|
||||
name: '@push.rocks/smartagent',
|
||||
version: '1.2.6',
|
||||
version: '1.8.0',
|
||||
description: 'an agentic framework built on top of @push.rocks/smartai'
|
||||
}
|
||||
|
||||
@@ -7,6 +7,9 @@ export { DualAgentOrchestrator } from './smartagent.classes.dualagent.js';
|
||||
export { DriverAgent } from './smartagent.classes.driveragent.js';
|
||||
export { GuardianAgent } from './smartagent.classes.guardianagent.js';
|
||||
|
||||
// Export tool registry and related classes
|
||||
export { ToolRegistry } from './smartagent.classes.toolregistry.js';
|
||||
|
||||
// Export base tool class for custom tool creation
|
||||
export { BaseToolWrapper } from './smartagent.tools.base.js';
|
||||
|
||||
@@ -16,6 +19,11 @@ export { HttpTool } from './smartagent.tools.http.js';
|
||||
export { ShellTool } from './smartagent.tools.shell.js';
|
||||
export { BrowserTool } from './smartagent.tools.browser.js';
|
||||
export { DenoTool, type TDenoPermission } from './smartagent.tools.deno.js';
|
||||
export { JsonValidatorTool } from './smartagent.tools.json.js';
|
||||
|
||||
// Export tool search and expert tools
|
||||
export { ToolSearchTool } from './smartagent.tools.search.js';
|
||||
export { ExpertTool } from './smartagent.tools.expert.js';
|
||||
|
||||
// Export all interfaces
|
||||
export * from './smartagent.interfaces.js';
|
||||
|
||||
@@ -10,6 +10,8 @@ export interface IDriverAgentOptions {
|
||||
systemMessage?: string;
|
||||
/** Maximum history messages to pass to API (default: 20). Set to 0 for unlimited. */
|
||||
maxHistoryMessages?: number;
|
||||
/** Callback fired for each token during LLM generation */
|
||||
onToken?: (token: string) => void;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -22,6 +24,8 @@ export class DriverAgent {
|
||||
private maxHistoryMessages: number;
|
||||
private messageHistory: plugins.smartai.ChatMessage[] = [];
|
||||
private tools: Map<string, BaseToolWrapper> = new Map();
|
||||
private onToken?: (token: string) => void;
|
||||
private isInThinkingMode = false; // Track thinking/content state for markers
|
||||
|
||||
constructor(
|
||||
provider: plugins.smartai.MultiModalModel,
|
||||
@@ -36,9 +40,18 @@ export class DriverAgent {
|
||||
} else {
|
||||
this.systemMessage = options?.systemMessage || this.getDefaultSystemMessage();
|
||||
this.maxHistoryMessages = options?.maxHistoryMessages ?? 20;
|
||||
this.onToken = options?.onToken;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the token callback for streaming mode
|
||||
* @param callback Function to call for each generated token
|
||||
*/
|
||||
public setOnToken(callback: (token: string) => void): void {
|
||||
this.onToken = callback;
|
||||
}
|
||||
|
||||
/**
|
||||
* Register a tool for use by the driver
|
||||
*/
|
||||
@@ -55,8 +68,10 @@ export class DriverAgent {
|
||||
|
||||
/**
|
||||
* Initialize a new conversation for a task
|
||||
* @param task The task description
|
||||
* @param images Optional base64-encoded images for vision tasks
|
||||
*/
|
||||
public async startTask(task: string): Promise<interfaces.IAgentMessage> {
|
||||
public async startTask(task: string, images?: string[]): Promise<interfaces.IAgentMessage> {
|
||||
// Reset message history
|
||||
this.messageHistory = [];
|
||||
|
||||
@@ -85,18 +100,35 @@ export class DriverAgent {
|
||||
fullSystemMessage = this.getNoToolsSystemMessage();
|
||||
}
|
||||
|
||||
// Get response from provider
|
||||
const response = await this.provider.chat({
|
||||
systemMessage: fullSystemMessage,
|
||||
userMessage: userMessage,
|
||||
messageHistory: [],
|
||||
});
|
||||
// Get response from provider - use streaming if available and callback is set
|
||||
let response: plugins.smartai.ChatResponse;
|
||||
|
||||
// Add assistant response to history
|
||||
this.messageHistory.push({
|
||||
if (this.onToken && typeof (this.provider as any).chatStreaming === 'function') {
|
||||
// Use streaming mode with token callback
|
||||
response = await (this.provider as any).chatStreaming({
|
||||
systemMessage: fullSystemMessage,
|
||||
userMessage: userMessage,
|
||||
messageHistory: [],
|
||||
images: images,
|
||||
onToken: this.onToken,
|
||||
});
|
||||
} else {
|
||||
// Fallback to non-streaming mode
|
||||
response = await this.provider.chat({
|
||||
systemMessage: fullSystemMessage,
|
||||
userMessage: userMessage,
|
||||
messageHistory: [],
|
||||
images: images,
|
||||
});
|
||||
}
|
||||
|
||||
// Add assistant response to history (store images if provided, preserve reasoning for GPT-OSS)
|
||||
const historyMessage: plugins.smartai.ChatMessage = {
|
||||
role: 'assistant',
|
||||
content: response.message,
|
||||
});
|
||||
reasoning: response.reasoning,
|
||||
};
|
||||
this.messageHistory.push(historyMessage);
|
||||
|
||||
return {
|
||||
role: 'assistant',
|
||||
@@ -139,16 +171,31 @@ export class DriverAgent {
|
||||
historyForChat = fullHistory;
|
||||
}
|
||||
|
||||
const response = await this.provider.chat({
|
||||
systemMessage: fullSystemMessage,
|
||||
userMessage: message,
|
||||
messageHistory: historyForChat,
|
||||
});
|
||||
// Get response from provider - use streaming if available and callback is set
|
||||
let response: plugins.smartai.ChatResponse;
|
||||
|
||||
// Add assistant response to history
|
||||
if (this.onToken && typeof (this.provider as any).chatStreaming === 'function') {
|
||||
// Use streaming mode with token callback
|
||||
response = await (this.provider as any).chatStreaming({
|
||||
systemMessage: fullSystemMessage,
|
||||
userMessage: message,
|
||||
messageHistory: historyForChat,
|
||||
onToken: this.onToken,
|
||||
});
|
||||
} else {
|
||||
// Fallback to non-streaming mode
|
||||
response = await this.provider.chat({
|
||||
systemMessage: fullSystemMessage,
|
||||
userMessage: message,
|
||||
messageHistory: historyForChat,
|
||||
});
|
||||
}
|
||||
|
||||
// Add assistant response to history (preserve reasoning for GPT-OSS)
|
||||
this.messageHistory.push({
|
||||
role: 'assistant',
|
||||
content: response.message,
|
||||
reasoning: response.reasoning,
|
||||
});
|
||||
|
||||
return {
|
||||
@@ -331,33 +378,33 @@ export class DriverAgent {
|
||||
## Your Role
|
||||
You analyze tasks, break them down into steps, and use tools to accomplish goals.
|
||||
|
||||
## Tool Usage Format
|
||||
When you need to use a tool, output a tool call proposal in this format:
|
||||
## CRITICAL: Tool Usage Format
|
||||
To use a tool, you MUST literally write out the XML tags in your response. The system parses your output looking for these exact tags. Do NOT just describe or mention the tool call - you must OUTPUT the actual XML.
|
||||
|
||||
CORRECT (the XML is in the output):
|
||||
<tool_call>
|
||||
<tool>tool_name</tool>
|
||||
<action>action_name</action>
|
||||
<params>
|
||||
{"param1": "value1", "param2": "value2"}
|
||||
</params>
|
||||
<reasoning>Brief explanation of why this action is needed</reasoning>
|
||||
<tool>json</tool>
|
||||
<action>validate</action>
|
||||
<params>{"jsonString": "{\\"key\\":\\"value\\"}"}</params>
|
||||
</tool_call>
|
||||
|
||||
WRONG (just describing, no actual XML):
|
||||
"I will call json.validate now" or "Let me use the tool"
|
||||
|
||||
## Guidelines
|
||||
1. Think step by step about what needs to be done
|
||||
2. Use only the tools that are available to you
|
||||
3. Provide clear reasoning for each tool call
|
||||
4. If a tool call is rejected, adapt your approach based on the feedback
|
||||
5. When the task is complete, indicate this clearly:
|
||||
2. When you need a tool, OUTPUT the <tool_call> XML tags - do not just mention them
|
||||
3. Only propose ONE tool call at a time
|
||||
4. Wait for the result before proposing the next action
|
||||
5. When the task is complete, OUTPUT:
|
||||
|
||||
<task_complete>
|
||||
Brief summary of what was accomplished
|
||||
Your final result here
|
||||
</task_complete>
|
||||
|
||||
## Important
|
||||
- Only propose ONE tool call at a time
|
||||
- Wait for the result before proposing the next action
|
||||
- If you encounter an error, analyze it and try an alternative approach
|
||||
- The <tool_call> and <task_complete> tags MUST appear literally in your response
|
||||
- If you just say "I'll call the tool" without the actual XML, it will NOT work
|
||||
- If you need clarification, ask using <needs_clarification>your question</needs_clarification>`;
|
||||
}
|
||||
|
||||
@@ -396,4 +443,333 @@ Your complete output here
|
||||
public reset(): void {
|
||||
this.messageHistory = [];
|
||||
}
|
||||
|
||||
// ================================
|
||||
// Native Tool Calling Support
|
||||
// ================================
|
||||
|
||||
/**
|
||||
* Start a task with native tool calling support
|
||||
* Uses Ollama's native tool calling API instead of XML parsing
|
||||
* @param task The task description
|
||||
* @param images Optional base64-encoded images for vision tasks
|
||||
* @returns Response with content, reasoning, and any tool calls
|
||||
*/
|
||||
public async startTaskWithNativeTools(
|
||||
task: string,
|
||||
images?: string[]
|
||||
): Promise<{ message: interfaces.IAgentMessage; toolCalls?: interfaces.INativeToolCall[] }> {
|
||||
// Reset message history
|
||||
this.messageHistory = [];
|
||||
|
||||
// Build simple user message (no XML instructions needed for native tool calling)
|
||||
const userMessage = `TASK: ${task}\n\nComplete this task using the available tools. When done, provide your final output.`;
|
||||
|
||||
// Add to history
|
||||
this.messageHistory.push({
|
||||
role: 'user',
|
||||
content: userMessage,
|
||||
});
|
||||
|
||||
// Build system message for native tool calling
|
||||
const fullSystemMessage = this.getNativeToolsSystemMessage();
|
||||
|
||||
// Get tools in JSON schema format
|
||||
const tools = this.getToolsAsJsonSchema();
|
||||
|
||||
// Check if provider supports native tool calling (Ollama)
|
||||
const provider = this.provider as any;
|
||||
if (typeof provider.collectStreamResponse !== 'function') {
|
||||
throw new Error('Provider does not support native tool calling. Use startTask() instead.');
|
||||
}
|
||||
|
||||
// Use collectStreamResponse for streaming support with tools
|
||||
const response = await provider.collectStreamResponse(
|
||||
{
|
||||
systemMessage: fullSystemMessage,
|
||||
userMessage: userMessage,
|
||||
messageHistory: [],
|
||||
images: images,
|
||||
tools: tools.length > 0 ? tools : undefined,
|
||||
},
|
||||
// Pass onToken callback through onChunk for streaming with thinking markers
|
||||
this.onToken ? (chunk: any) => {
|
||||
if (chunk.thinking && this.onToken) {
|
||||
// Add marker only when transitioning INTO thinking mode
|
||||
if (!this.isInThinkingMode) {
|
||||
this.onToken('\n[THINKING] ');
|
||||
this.isInThinkingMode = true;
|
||||
}
|
||||
this.onToken(chunk.thinking);
|
||||
}
|
||||
if (chunk.content && this.onToken) {
|
||||
// Add marker when transitioning OUT of thinking mode
|
||||
if (this.isInThinkingMode) {
|
||||
this.onToken('\n[OUTPUT] ');
|
||||
this.isInThinkingMode = false;
|
||||
}
|
||||
this.onToken(chunk.content);
|
||||
}
|
||||
} : undefined
|
||||
);
|
||||
|
||||
// Reset thinking state after response completes
|
||||
this.isInThinkingMode = false;
|
||||
|
||||
// Add assistant response to history
|
||||
const historyMessage: any = {
|
||||
role: 'assistant',
|
||||
content: response.message || '',
|
||||
reasoning: response.thinking || response.reasoning,
|
||||
};
|
||||
|
||||
// CRITICAL: Preserve tool_calls in history for native tool calling
|
||||
// Without this, the model doesn't know it already called a tool and loops forever
|
||||
if (response.toolCalls && response.toolCalls.length > 0) {
|
||||
historyMessage.tool_calls = response.toolCalls.map((tc: any) => ({
|
||||
function: {
|
||||
name: tc.function.name,
|
||||
arguments: tc.function.arguments,
|
||||
},
|
||||
}));
|
||||
}
|
||||
|
||||
this.messageHistory.push(historyMessage as unknown as plugins.smartai.ChatMessage);
|
||||
|
||||
// Convert Ollama tool calls to our format
|
||||
let toolCalls: interfaces.INativeToolCall[] | undefined;
|
||||
if (response.toolCalls && response.toolCalls.length > 0) {
|
||||
toolCalls = response.toolCalls.map((tc: any) => ({
|
||||
function: {
|
||||
name: tc.function.name,
|
||||
arguments: tc.function.arguments,
|
||||
index: tc.function.index,
|
||||
},
|
||||
}));
|
||||
}
|
||||
|
||||
return {
|
||||
message: {
|
||||
role: 'assistant',
|
||||
content: response.message || '',
|
||||
},
|
||||
toolCalls,
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Continue conversation with native tool calling support
|
||||
* @param message The message to continue with (e.g., tool result)
|
||||
* @param toolName Optional tool name - when provided, message is added as role: 'tool' instead of 'user'
|
||||
* @returns Response with content, reasoning, and any tool calls
|
||||
*/
|
||||
public async continueWithNativeTools(
|
||||
message: string,
|
||||
toolName?: string
|
||||
): Promise<{ message: interfaces.IAgentMessage; toolCalls?: interfaces.INativeToolCall[] }> {
|
||||
// Add the new message to history
|
||||
if (toolName) {
|
||||
// Tool result - must use role: 'tool' for native tool calling
|
||||
// The 'tool' role is supported by providers but not in the ChatMessage type
|
||||
this.messageHistory.push({
|
||||
role: 'tool',
|
||||
content: message,
|
||||
toolName: toolName,
|
||||
} as unknown as plugins.smartai.ChatMessage);
|
||||
} else {
|
||||
// Regular user message
|
||||
this.messageHistory.push({
|
||||
role: 'user',
|
||||
content: message,
|
||||
});
|
||||
}
|
||||
|
||||
// Build system message
|
||||
const fullSystemMessage = this.getNativeToolsSystemMessage();
|
||||
|
||||
// Get tools in JSON schema format
|
||||
const tools = this.getToolsAsJsonSchema();
|
||||
|
||||
// Get response from provider with history windowing
|
||||
// For tool results, include the full history (with tool message)
|
||||
// For regular user messages, exclude the last message (it becomes userMessage)
|
||||
let historyForChat: plugins.smartai.ChatMessage[];
|
||||
const fullHistory = toolName
|
||||
? this.messageHistory // Include tool result in history
|
||||
: this.messageHistory.slice(0, -1); // Exclude last user message
|
||||
|
||||
if (this.maxHistoryMessages > 0 && fullHistory.length > this.maxHistoryMessages) {
|
||||
historyForChat = [
|
||||
fullHistory[0],
|
||||
...fullHistory.slice(-(this.maxHistoryMessages - 1)),
|
||||
];
|
||||
} else {
|
||||
historyForChat = fullHistory;
|
||||
}
|
||||
|
||||
// Check if provider supports native tool calling
|
||||
const provider = this.provider as any;
|
||||
if (typeof provider.collectStreamResponse !== 'function') {
|
||||
throw new Error('Provider does not support native tool calling. Use continueWithMessage() instead.');
|
||||
}
|
||||
|
||||
// For tool results, use a continuation prompt instead of repeating the result
|
||||
const userMessage = toolName
|
||||
? 'Continue with the task. The tool result has been provided above.'
|
||||
: message;
|
||||
|
||||
// Use collectStreamResponse for streaming support with tools
|
||||
const response = await provider.collectStreamResponse(
|
||||
{
|
||||
systemMessage: fullSystemMessage,
|
||||
userMessage: userMessage,
|
||||
messageHistory: historyForChat,
|
||||
tools: tools.length > 0 ? tools : undefined,
|
||||
},
|
||||
// Pass onToken callback through onChunk for streaming with thinking markers
|
||||
this.onToken ? (chunk: any) => {
|
||||
if (chunk.thinking && this.onToken) {
|
||||
// Add marker only when transitioning INTO thinking mode
|
||||
if (!this.isInThinkingMode) {
|
||||
this.onToken('\n[THINKING] ');
|
||||
this.isInThinkingMode = true;
|
||||
}
|
||||
this.onToken(chunk.thinking);
|
||||
}
|
||||
if (chunk.content && this.onToken) {
|
||||
// Add marker when transitioning OUT of thinking mode
|
||||
if (this.isInThinkingMode) {
|
||||
this.onToken('\n[OUTPUT] ');
|
||||
this.isInThinkingMode = false;
|
||||
}
|
||||
this.onToken(chunk.content);
|
||||
}
|
||||
} : undefined
|
||||
);
|
||||
|
||||
// Reset thinking state after response completes
|
||||
this.isInThinkingMode = false;
|
||||
|
||||
// Add assistant response to history
|
||||
const historyMessage: any = {
|
||||
role: 'assistant',
|
||||
content: response.message || '',
|
||||
reasoning: response.thinking || response.reasoning,
|
||||
};
|
||||
|
||||
// CRITICAL: Preserve tool_calls in history for native tool calling
|
||||
// Without this, the model doesn't know it already called a tool and loops forever
|
||||
if (response.toolCalls && response.toolCalls.length > 0) {
|
||||
historyMessage.tool_calls = response.toolCalls.map((tc: any) => ({
|
||||
function: {
|
||||
name: tc.function.name,
|
||||
arguments: tc.function.arguments,
|
||||
},
|
||||
}));
|
||||
}
|
||||
|
||||
this.messageHistory.push(historyMessage as unknown as plugins.smartai.ChatMessage);
|
||||
|
||||
// Convert Ollama tool calls to our format
|
||||
let toolCalls: interfaces.INativeToolCall[] | undefined;
|
||||
if (response.toolCalls && response.toolCalls.length > 0) {
|
||||
toolCalls = response.toolCalls.map((tc: any) => ({
|
||||
function: {
|
||||
name: tc.function.name,
|
||||
arguments: tc.function.arguments,
|
||||
index: tc.function.index,
|
||||
},
|
||||
}));
|
||||
}
|
||||
|
||||
return {
|
||||
message: {
|
||||
role: 'assistant',
|
||||
content: response.message || '',
|
||||
},
|
||||
toolCalls,
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Get system message for native tool calling mode
|
||||
* Simplified prompt that lets the model use tools naturally
|
||||
*/
|
||||
private getNativeToolsSystemMessage(): string {
|
||||
return `You are an AI assistant that executes tasks by using available tools.
|
||||
|
||||
## Your Role
|
||||
You analyze tasks, break them down into steps, and use tools to accomplish goals.
|
||||
|
||||
## Guidelines
|
||||
1. Think step by step about what needs to be done
|
||||
2. Use the available tools to complete the task
|
||||
3. Process tool results and continue until the task is complete
|
||||
4. When the task is complete, provide a final summary
|
||||
|
||||
## Important
|
||||
- Use tools when needed to gather information or perform actions
|
||||
- If you need clarification, ask the user
|
||||
- Always verify your work before marking the task complete`;
|
||||
}
|
||||
|
||||
/**
|
||||
* Convert registered tools to Ollama JSON Schema format for native tool calling
|
||||
* Each tool action becomes a separate function with name format: "toolName_actionName"
|
||||
* @returns Array of IOllamaTool compatible tool definitions
|
||||
*/
|
||||
public getToolsAsJsonSchema(): plugins.smartai.IOllamaTool[] {
|
||||
const tools: plugins.smartai.IOllamaTool[] = [];
|
||||
|
||||
for (const tool of this.tools.values()) {
|
||||
for (const action of tool.actions) {
|
||||
// Build the tool definition in Ollama format
|
||||
const toolDef: plugins.smartai.IOllamaTool = {
|
||||
type: 'function',
|
||||
function: {
|
||||
name: `${tool.name}_${action.name}`, // e.g., "json_validate"
|
||||
description: `[${tool.name}] ${action.description}`,
|
||||
parameters: action.parameters as plugins.smartai.IOllamaTool['function']['parameters'],
|
||||
},
|
||||
};
|
||||
tools.push(toolDef);
|
||||
}
|
||||
}
|
||||
|
||||
return tools;
|
||||
}
|
||||
|
||||
/**
|
||||
* Parse native tool calls from provider response into IToolCallProposal format
|
||||
* @param toolCalls Array of native tool calls from the provider
|
||||
* @returns Array of IToolCallProposal ready for execution
|
||||
*/
|
||||
public parseNativeToolCalls(
|
||||
toolCalls: interfaces.INativeToolCall[]
|
||||
): interfaces.IToolCallProposal[] {
|
||||
return toolCalls.map(tc => {
|
||||
// Split "json_validate" -> toolName="json", action="validate"
|
||||
const fullName = tc.function.name;
|
||||
const underscoreIndex = fullName.indexOf('_');
|
||||
|
||||
let toolName: string;
|
||||
let action: string;
|
||||
|
||||
if (underscoreIndex > 0) {
|
||||
toolName = fullName.substring(0, underscoreIndex);
|
||||
action = fullName.substring(underscoreIndex + 1);
|
||||
} else {
|
||||
// Fallback: treat entire name as tool name with empty action
|
||||
toolName = fullName;
|
||||
action = '';
|
||||
}
|
||||
|
||||
return {
|
||||
proposalId: this.generateProposalId(),
|
||||
toolName,
|
||||
action,
|
||||
params: tc.function.arguments,
|
||||
};
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -8,6 +8,9 @@ import { HttpTool } from './smartagent.tools.http.js';
|
||||
import { ShellTool } from './smartagent.tools.shell.js';
|
||||
import { BrowserTool } from './smartagent.tools.browser.js';
|
||||
import { DenoTool } from './smartagent.tools.deno.js';
|
||||
import { ToolRegistry } from './smartagent.classes.toolregistry.js';
|
||||
import { ToolSearchTool } from './smartagent.tools.search.js';
|
||||
import { ExpertTool } from './smartagent.tools.expert.js';
|
||||
|
||||
/**
|
||||
* DualAgentOrchestrator - Coordinates Driver and Guardian agents
|
||||
@@ -20,7 +23,7 @@ export class DualAgentOrchestrator {
|
||||
private guardianProvider: plugins.smartai.MultiModalModel;
|
||||
private driver: DriverAgent;
|
||||
private guardian: GuardianAgent;
|
||||
private tools: Map<string, BaseToolWrapper> = new Map();
|
||||
private registry: ToolRegistry = new ToolRegistry();
|
||||
private isRunning = false;
|
||||
private conversationHistory: interfaces.IAgentMessage[] = [];
|
||||
private ownsSmartAi = true; // true if we created the SmartAi instance, false if it was provided
|
||||
@@ -125,19 +128,55 @@ export class DualAgentOrchestrator {
|
||||
}
|
||||
|
||||
/**
|
||||
* Register a custom tool
|
||||
* Register a custom tool with optional visibility settings
|
||||
*/
|
||||
public registerTool(tool: BaseToolWrapper): void {
|
||||
this.tools.set(tool.name, tool);
|
||||
// Register with agents if they exist (they're created in start())
|
||||
if (this.driver) {
|
||||
this.driver.registerTool(tool);
|
||||
}
|
||||
if (this.guardian) {
|
||||
this.guardian.registerTool(tool);
|
||||
public registerTool(
|
||||
tool: BaseToolWrapper,
|
||||
options?: interfaces.IToolRegistrationOptions
|
||||
): void {
|
||||
this.registry.register(tool, options);
|
||||
|
||||
// If initial visibility and agents exist, register with them
|
||||
const visibility = options?.visibility ?? 'initial';
|
||||
if (visibility === 'initial') {
|
||||
if (this.driver) {
|
||||
this.driver.registerTool(tool);
|
||||
}
|
||||
if (this.guardian) {
|
||||
this.guardian.registerTool(tool);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Register an expert (subagent) as a tool
|
||||
*/
|
||||
public registerExpert(config: interfaces.IExpertConfig): void {
|
||||
const expert = new ExpertTool(config, this.smartai);
|
||||
this.registerTool(expert, {
|
||||
visibility: config.visibility,
|
||||
tags: config.tags,
|
||||
category: config.category ?? 'expert',
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Enable tool search functionality
|
||||
* This adds a 'tools' tool that allows the Driver to discover and activate on-demand tools
|
||||
*/
|
||||
public enableToolSearch(): void {
|
||||
const searchTool = new ToolSearchTool(this.registry, (tool) => {
|
||||
// Callback when an on-demand tool is activated
|
||||
if (this.driver) {
|
||||
this.driver.registerTool(tool);
|
||||
}
|
||||
if (this.guardian) {
|
||||
this.guardian.registerTool(tool);
|
||||
}
|
||||
});
|
||||
this.registerTool(searchTool); // Always initial visibility
|
||||
}
|
||||
|
||||
/**
|
||||
* Register all standard tools
|
||||
*/
|
||||
@@ -181,25 +220,26 @@ export class DualAgentOrchestrator {
|
||||
: this.driverProvider;
|
||||
|
||||
// NOW create agents with initialized providers
|
||||
// Set up token callback wrapper if streaming is enabled
|
||||
const driverOnToken = this.options.onToken
|
||||
? (token: string) => this.options.onToken!(token, 'driver')
|
||||
: undefined;
|
||||
|
||||
this.driver = new DriverAgent(this.driverProvider, {
|
||||
systemMessage: this.options.driverSystemMessage,
|
||||
maxHistoryMessages: this.options.maxHistoryMessages,
|
||||
onToken: driverOnToken,
|
||||
});
|
||||
this.guardian = new GuardianAgent(this.guardianProvider, this.options.guardianPolicyPrompt);
|
||||
|
||||
// Register any tools that were added before start() with the agents
|
||||
for (const tool of this.tools.values()) {
|
||||
// Register visible tools with agents
|
||||
for (const tool of this.registry.getVisibleTools()) {
|
||||
this.driver.registerTool(tool);
|
||||
this.guardian.registerTool(tool);
|
||||
}
|
||||
|
||||
// Initialize all tools
|
||||
const initPromises: Promise<void>[] = [];
|
||||
for (const tool of this.tools.values()) {
|
||||
initPromises.push(tool.initialize());
|
||||
}
|
||||
|
||||
await Promise.all(initPromises);
|
||||
// Initialize visible tools
|
||||
await this.registry.initializeVisibleTools();
|
||||
this.isRunning = true;
|
||||
}
|
||||
|
||||
@@ -207,13 +247,7 @@ export class DualAgentOrchestrator {
|
||||
* Cleanup all tools
|
||||
*/
|
||||
public async stop(): Promise<void> {
|
||||
const cleanupPromises: Promise<void>[] = [];
|
||||
|
||||
for (const tool of this.tools.values()) {
|
||||
cleanupPromises.push(tool.cleanup());
|
||||
}
|
||||
|
||||
await Promise.all(cleanupPromises);
|
||||
await this.registry.cleanup();
|
||||
|
||||
// Only stop smartai if we created it (don't stop external instances)
|
||||
if (this.ownsSmartAi) {
|
||||
@@ -228,26 +262,47 @@ export class DualAgentOrchestrator {
|
||||
|
||||
/**
|
||||
* Run a task through the dual-agent system
|
||||
* @param task The task description
|
||||
* @param options Optional task run options (e.g., images for vision tasks)
|
||||
*/
|
||||
public async run(task: string): Promise<interfaces.IDualAgentRunResult> {
|
||||
public async run(task: string, options?: interfaces.ITaskRunOptions): Promise<interfaces.IDualAgentRunResult> {
|
||||
if (!this.isRunning) {
|
||||
throw new Error('Orchestrator not started. Call start() first.');
|
||||
}
|
||||
|
||||
// Use native tool calling if enabled
|
||||
const useNativeTools = this.options.useNativeToolCalling === true;
|
||||
|
||||
this.conversationHistory = [];
|
||||
let iterations = 0;
|
||||
let consecutiveRejections = 0;
|
||||
let completed = false;
|
||||
let finalResult: string | null = null;
|
||||
|
||||
// Track pending native tool calls
|
||||
let pendingNativeToolCalls: interfaces.INativeToolCall[] | undefined;
|
||||
|
||||
// Extract images from options
|
||||
const images = options?.images;
|
||||
|
||||
// Add initial task to history
|
||||
this.conversationHistory.push({
|
||||
role: 'user',
|
||||
content: task,
|
||||
});
|
||||
|
||||
// Start the driver with the task
|
||||
let driverResponse = await this.driver.startTask(task);
|
||||
// Start the driver with the task and optional images
|
||||
let driverResponse: interfaces.IAgentMessage;
|
||||
|
||||
if (useNativeTools) {
|
||||
// Native tool calling mode
|
||||
const result = await this.driver.startTaskWithNativeTools(task, images);
|
||||
driverResponse = result.message;
|
||||
pendingNativeToolCalls = result.toolCalls;
|
||||
} else {
|
||||
// XML parsing mode
|
||||
driverResponse = await this.driver.startTask(task, images);
|
||||
}
|
||||
this.conversationHistory.push(driverResponse);
|
||||
|
||||
// Emit task started event
|
||||
@@ -270,10 +325,16 @@ export class DualAgentOrchestrator {
|
||||
maxIterations: this.options.maxIterations,
|
||||
});
|
||||
|
||||
// Check if task is complete
|
||||
if (this.driver.isTaskComplete(driverResponse.content)) {
|
||||
// Check if task is complete (for native mode, no pending tool calls and has content)
|
||||
const isComplete = useNativeTools
|
||||
? (!pendingNativeToolCalls || pendingNativeToolCalls.length === 0) && driverResponse.content.length > 0
|
||||
: this.driver.isTaskComplete(driverResponse.content);
|
||||
|
||||
if (isComplete) {
|
||||
completed = true;
|
||||
finalResult = this.driver.extractTaskResult(driverResponse.content) || driverResponse.content;
|
||||
finalResult = useNativeTools
|
||||
? driverResponse.content
|
||||
: (this.driver.extractTaskResult(driverResponse.content) || driverResponse.content);
|
||||
|
||||
// Emit task completed event
|
||||
this.emitProgress({
|
||||
@@ -304,16 +365,56 @@ export class DualAgentOrchestrator {
|
||||
};
|
||||
}
|
||||
|
||||
// Parse tool call proposals
|
||||
const proposals = this.driver.parseToolCallProposals(driverResponse.content);
|
||||
// Parse tool call proposals - native mode uses pendingNativeToolCalls, XML mode parses content
|
||||
let proposals: interfaces.IToolCallProposal[];
|
||||
|
||||
if (useNativeTools && pendingNativeToolCalls && pendingNativeToolCalls.length > 0) {
|
||||
// Native tool calling mode - convert native tool calls to proposals
|
||||
proposals = this.driver.parseNativeToolCalls(pendingNativeToolCalls);
|
||||
pendingNativeToolCalls = undefined; // Clear after processing
|
||||
} else if (!useNativeTools) {
|
||||
// XML parsing mode
|
||||
proposals = this.driver.parseToolCallProposals(driverResponse.content);
|
||||
} else {
|
||||
proposals = [];
|
||||
}
|
||||
|
||||
if (proposals.length === 0) {
|
||||
// No tool calls, continue the conversation
|
||||
driverResponse = await this.driver.continueWithMessage(
|
||||
'Please either use a tool to make progress on the task, or indicate that the task is complete with <task_complete>summary</task_complete>.'
|
||||
);
|
||||
this.conversationHistory.push(driverResponse);
|
||||
continue;
|
||||
if (useNativeTools) {
|
||||
// Native mode: no tool calls and no content means we should continue
|
||||
const result = await this.driver.continueWithNativeTools(
|
||||
'Please continue with the task. Use the available tools or provide your final output.'
|
||||
);
|
||||
driverResponse = result.message;
|
||||
pendingNativeToolCalls = result.toolCalls;
|
||||
this.conversationHistory.push(driverResponse);
|
||||
continue;
|
||||
} else {
|
||||
// XML mode: remind the model of the exact XML format
|
||||
driverResponse = await this.driver.continueWithMessage(
|
||||
`No valid tool call was found in your response. To use a tool, you MUST output the exact XML format:
|
||||
|
||||
<tool_call>
|
||||
<tool>tool_name</tool>
|
||||
<action>action_name</action>
|
||||
<params>{"param1": "value1"}</params>
|
||||
</tool_call>
|
||||
|
||||
For example, to validate JSON:
|
||||
<tool_call>
|
||||
<tool>json</tool>
|
||||
<action>validate</action>
|
||||
<params>{"jsonString": "{\\"key\\":\\"value\\"}", "requiredFields": ["key"]}</params>
|
||||
</tool_call>
|
||||
|
||||
Or to complete the task:
|
||||
<task_complete>your final JSON output here</task_complete>
|
||||
|
||||
Please output the exact XML format above.`
|
||||
);
|
||||
this.conversationHistory.push(driverResponse);
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
// Process the first proposal (one at a time)
|
||||
@@ -359,7 +460,7 @@ export class DualAgentOrchestrator {
|
||||
});
|
||||
|
||||
// Execute the tool
|
||||
const tool = this.tools.get(proposal.toolName);
|
||||
const tool = this.registry.getTool(proposal.toolName);
|
||||
if (!tool) {
|
||||
const errorMessage = `Tool "${proposal.toolName}" not found.`;
|
||||
driverResponse = await this.driver.continueWithMessage(
|
||||
@@ -420,13 +521,31 @@ export class DualAgentOrchestrator {
|
||||
toolResult: result,
|
||||
});
|
||||
|
||||
driverResponse = await this.driver.continueWithMessage(resultMessage);
|
||||
// Continue with appropriate method based on mode
|
||||
if (useNativeTools) {
|
||||
const toolNameForHistory = `${proposal.toolName}_${proposal.action}`;
|
||||
const continueResult = await this.driver.continueWithNativeTools(resultMessage, toolNameForHistory);
|
||||
driverResponse = continueResult.message;
|
||||
pendingNativeToolCalls = continueResult.toolCalls;
|
||||
} else {
|
||||
driverResponse = await this.driver.continueWithMessage(resultMessage);
|
||||
}
|
||||
this.conversationHistory.push(driverResponse);
|
||||
} catch (error) {
|
||||
const errorMessage = `Tool execution failed: ${error instanceof Error ? error.message : String(error)}`;
|
||||
driverResponse = await this.driver.continueWithMessage(
|
||||
`TOOL ERROR: ${errorMessage}\n\nPlease try a different approach.`
|
||||
);
|
||||
if (useNativeTools) {
|
||||
const toolNameForHistory = `${proposal.toolName}_${proposal.action}`;
|
||||
const continueResult = await this.driver.continueWithNativeTools(
|
||||
`TOOL ERROR: ${errorMessage}\n\nPlease try a different approach.`,
|
||||
toolNameForHistory
|
||||
);
|
||||
driverResponse = continueResult.message;
|
||||
pendingNativeToolCalls = continueResult.toolCalls;
|
||||
} else {
|
||||
driverResponse = await this.driver.continueWithMessage(
|
||||
`TOOL ERROR: ${errorMessage}\n\nPlease try a different approach.`
|
||||
);
|
||||
}
|
||||
this.conversationHistory.push(driverResponse);
|
||||
}
|
||||
} else {
|
||||
@@ -463,7 +582,14 @@ export class DualAgentOrchestrator {
|
||||
guardianDecision: decision,
|
||||
});
|
||||
|
||||
driverResponse = await this.driver.continueWithMessage(feedback);
|
||||
// Continue with appropriate method based on mode
|
||||
if (useNativeTools) {
|
||||
const continueResult = await this.driver.continueWithNativeTools(feedback);
|
||||
driverResponse = continueResult.message;
|
||||
pendingNativeToolCalls = continueResult.toolCalls;
|
||||
} else {
|
||||
driverResponse = await this.driver.continueWithMessage(feedback);
|
||||
}
|
||||
this.conversationHistory.push(driverResponse);
|
||||
}
|
||||
}
|
||||
@@ -554,6 +680,13 @@ export class DualAgentOrchestrator {
|
||||
* Get registered tool names
|
||||
*/
|
||||
public getToolNames(): string[] {
|
||||
return Array.from(this.tools.keys());
|
||||
return this.registry.getAllMetadata().map((m) => m.name);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the tool registry for advanced operations
|
||||
*/
|
||||
public getRegistry(): ToolRegistry {
|
||||
return this.registry;
|
||||
}
|
||||
}
|
||||
|
||||
188
ts/smartagent.classes.toolregistry.ts
Normal file
188
ts/smartagent.classes.toolregistry.ts
Normal file
@@ -0,0 +1,188 @@
|
||||
import * as interfaces from './smartagent.interfaces.js';
|
||||
import { BaseToolWrapper } from './smartagent.tools.base.js';
|
||||
|
||||
/**
|
||||
* ToolRegistry - Manages tool registration, visibility, and lifecycle
|
||||
*
|
||||
* Responsibilities:
|
||||
* - Track all registered tools with their metadata
|
||||
* - Manage visibility (initial vs on-demand)
|
||||
* - Handle activation of on-demand tools
|
||||
* - Provide search functionality
|
||||
*/
|
||||
export class ToolRegistry {
|
||||
private tools: Map<string, BaseToolWrapper> = new Map();
|
||||
private metadata: Map<string, interfaces.IToolMetadata> = new Map();
|
||||
private activated: Set<string> = new Set();
|
||||
|
||||
/**
|
||||
* Register a tool with optional visibility settings
|
||||
*/
|
||||
register(tool: BaseToolWrapper, options: interfaces.IToolRegistrationOptions = {}): void {
|
||||
const visibility = options.visibility ?? 'initial';
|
||||
|
||||
this.tools.set(tool.name, tool);
|
||||
this.metadata.set(tool.name, {
|
||||
name: tool.name,
|
||||
description: tool.description,
|
||||
actions: tool.actions,
|
||||
visibility,
|
||||
isActivated: visibility === 'initial',
|
||||
isInitialized: false,
|
||||
tags: options.tags,
|
||||
category: options.category,
|
||||
});
|
||||
|
||||
if (visibility === 'initial') {
|
||||
this.activated.add(tool.name);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get tools visible to the Driver (initial + activated on-demand)
|
||||
*/
|
||||
getVisibleTools(): BaseToolWrapper[] {
|
||||
return Array.from(this.tools.entries())
|
||||
.filter(([name]) => this.activated.has(name))
|
||||
.map(([, tool]) => tool);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get all tools (for search results)
|
||||
*/
|
||||
getAllTools(): BaseToolWrapper[] {
|
||||
return Array.from(this.tools.values());
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a specific tool by name
|
||||
*/
|
||||
getTool(name: string): BaseToolWrapper | undefined {
|
||||
return this.tools.get(name);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get metadata for a tool
|
||||
*/
|
||||
getMetadata(name: string): interfaces.IToolMetadata | undefined {
|
||||
return this.metadata.get(name);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get all metadata
|
||||
*/
|
||||
getAllMetadata(): interfaces.IToolMetadata[] {
|
||||
return Array.from(this.metadata.values());
|
||||
}
|
||||
|
||||
/**
|
||||
* Search tools by query (matches name, description, tags, action names)
|
||||
*/
|
||||
search(query: string): interfaces.IToolMetadata[] {
|
||||
const q = query.toLowerCase();
|
||||
return this.getAllMetadata().filter((meta) => {
|
||||
if (meta.name.toLowerCase().includes(q)) return true;
|
||||
if (meta.description.toLowerCase().includes(q)) return true;
|
||||
if (meta.tags?.some((t) => t.toLowerCase().includes(q))) return true;
|
||||
if (meta.category?.toLowerCase().includes(q)) return true;
|
||||
if (
|
||||
meta.actions.some(
|
||||
(a) => a.name.toLowerCase().includes(q) || a.description.toLowerCase().includes(q)
|
||||
)
|
||||
)
|
||||
return true;
|
||||
return false;
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Activate an on-demand tool
|
||||
*/
|
||||
async activate(name: string): Promise<{ success: boolean; error?: string }> {
|
||||
const tool = this.tools.get(name);
|
||||
const meta = this.metadata.get(name);
|
||||
|
||||
if (!tool || !meta) {
|
||||
return { success: false, error: `Tool "${name}" not found` };
|
||||
}
|
||||
|
||||
if (this.activated.has(name)) {
|
||||
return { success: true }; // Already activated
|
||||
}
|
||||
|
||||
// Initialize if not already initialized
|
||||
if (!meta.isInitialized) {
|
||||
await tool.initialize();
|
||||
meta.isInitialized = true;
|
||||
}
|
||||
|
||||
this.activated.add(name);
|
||||
meta.isActivated = true;
|
||||
|
||||
return { success: true };
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if a tool is activated
|
||||
*/
|
||||
isActivated(name: string): boolean {
|
||||
return this.activated.has(name);
|
||||
}
|
||||
|
||||
/**
|
||||
* Initialize all initial (visible) tools
|
||||
*/
|
||||
async initializeVisibleTools(): Promise<void> {
|
||||
const promises: Promise<void>[] = [];
|
||||
|
||||
for (const [name, tool] of this.tools) {
|
||||
const meta = this.metadata.get(name);
|
||||
if (meta && this.activated.has(name) && !meta.isInitialized) {
|
||||
promises.push(
|
||||
tool.initialize().then(() => {
|
||||
meta.isInitialized = true;
|
||||
})
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
await Promise.all(promises);
|
||||
}
|
||||
|
||||
/**
|
||||
* Cleanup all initialized tools
|
||||
*/
|
||||
async cleanup(): Promise<void> {
|
||||
const promises: Promise<void>[] = [];
|
||||
|
||||
for (const [name, tool] of this.tools) {
|
||||
const meta = this.metadata.get(name);
|
||||
if (meta?.isInitialized) {
|
||||
promises.push(tool.cleanup());
|
||||
}
|
||||
}
|
||||
|
||||
await Promise.all(promises);
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if a tool exists in the registry
|
||||
*/
|
||||
has(name: string): boolean {
|
||||
return this.tools.has(name);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the number of registered tools
|
||||
*/
|
||||
get size(): number {
|
||||
return this.tools.size;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the number of activated tools
|
||||
*/
|
||||
get activatedCount(): number {
|
||||
return this.activated.size;
|
||||
}
|
||||
}
|
||||
@@ -1,5 +1,77 @@
|
||||
import * as plugins from './plugins.js';
|
||||
|
||||
// ================================
|
||||
// Tool Visibility & Registry Types
|
||||
// ================================
|
||||
|
||||
/**
|
||||
* Tool visibility mode
|
||||
* - 'initial': Conveyed to model in system prompt AND discoverable via search
|
||||
* - 'on-demand': Only discoverable via search, must be activated before use
|
||||
*/
|
||||
export type TToolVisibility = 'initial' | 'on-demand';
|
||||
|
||||
/**
|
||||
* Tool metadata for discovery and management
|
||||
*/
|
||||
export interface IToolMetadata {
|
||||
name: string;
|
||||
description: string;
|
||||
actions: IToolAction[];
|
||||
visibility: TToolVisibility;
|
||||
isActivated: boolean;
|
||||
isInitialized: boolean;
|
||||
tags?: string[];
|
||||
category?: string;
|
||||
}
|
||||
|
||||
/**
|
||||
* Options when registering a tool
|
||||
*/
|
||||
export interface IToolRegistrationOptions {
|
||||
visibility?: TToolVisibility;
|
||||
tags?: string[];
|
||||
category?: string;
|
||||
}
|
||||
|
||||
/**
|
||||
* Configuration for creating an Expert (SubAgent)
|
||||
*/
|
||||
export interface IExpertConfig {
|
||||
/** Unique name for the expert */
|
||||
name: string;
|
||||
/** Description of the expert's capabilities */
|
||||
description: string;
|
||||
/** System message defining expert behavior */
|
||||
systemMessage: string;
|
||||
/** Guardian policy for the expert's inner agent */
|
||||
guardianPolicy: string;
|
||||
/** AI provider (defaults to parent's provider) */
|
||||
provider?: plugins.smartai.TProvider;
|
||||
/** Tools available to this expert */
|
||||
tools?: IAgentToolWrapper[];
|
||||
/** Max iterations for expert tasks (default: 10) */
|
||||
maxIterations?: number;
|
||||
/** Visibility mode (default: 'initial') */
|
||||
visibility?: TToolVisibility;
|
||||
/** Searchable tags */
|
||||
tags?: string[];
|
||||
/** Category for grouping */
|
||||
category?: string;
|
||||
}
|
||||
|
||||
// ================================
|
||||
// Task Run Options
|
||||
// ================================
|
||||
|
||||
/**
|
||||
* Options for running a task with the DualAgentOrchestrator
|
||||
*/
|
||||
export interface ITaskRunOptions {
|
||||
/** Base64-encoded images to include with the task (for vision-capable models) */
|
||||
images?: string[];
|
||||
}
|
||||
|
||||
// ================================
|
||||
// Agent Configuration Interfaces
|
||||
// ================================
|
||||
@@ -34,6 +106,14 @@ export interface IDualAgentOptions extends plugins.smartai.ISmartAiOptions {
|
||||
onProgress?: (event: IProgressEvent) => void;
|
||||
/** Prefix for log messages (e.g., "[README]", "[Commit]"). Default: empty */
|
||||
logPrefix?: string;
|
||||
/** Callback fired for each token during LLM generation (streaming mode) */
|
||||
onToken?: (token: string, source: 'driver' | 'guardian') => void;
|
||||
/**
|
||||
* Enable native tool calling mode (default: false)
|
||||
* When enabled, uses Ollama's native tool calling API instead of XML parsing
|
||||
* This is more efficient for models that support it (e.g., GPT-OSS with Harmony format)
|
||||
*/
|
||||
useNativeToolCalling?: boolean;
|
||||
}
|
||||
|
||||
// ================================
|
||||
@@ -69,6 +149,18 @@ export interface IToolAction {
|
||||
parameters: Record<string, unknown>;
|
||||
}
|
||||
|
||||
/**
|
||||
* Native tool call from provider (matches Ollama's tool calling format)
|
||||
* Format: function name is "toolName_actionName" (e.g., "json_validate")
|
||||
*/
|
||||
export interface INativeToolCall {
|
||||
function: {
|
||||
name: string; // Format: "toolName_actionName"
|
||||
arguments: Record<string, unknown>;
|
||||
index?: number;
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Proposed tool call from the Driver
|
||||
*/
|
||||
|
||||
@@ -35,6 +35,13 @@ export abstract class BaseToolWrapper implements interfaces.IAgentToolWrapper {
|
||||
*/
|
||||
abstract getCallSummary(action: string, params: Record<string, unknown>): string;
|
||||
|
||||
/**
|
||||
* Get a comprehensive explanation of this tool for LLM consumption.
|
||||
* Tools should implement this to provide detailed usage instructions with examples.
|
||||
* This includes parameter schemas and concrete <tool_call> XML examples.
|
||||
*/
|
||||
abstract getToolExplanation(): string;
|
||||
|
||||
/**
|
||||
* Validate that an action exists for this tool
|
||||
* @throws Error if the action is not valid
|
||||
@@ -60,14 +67,10 @@ export abstract class BaseToolWrapper implements interfaces.IAgentToolWrapper {
|
||||
|
||||
/**
|
||||
* Get the full tool description including all actions
|
||||
* Used for Driver's tool awareness
|
||||
* Used for Driver's tool awareness - now delegates to getToolExplanation()
|
||||
*/
|
||||
public getFullDescription(): string {
|
||||
const actionDescriptions = this.actions
|
||||
.map((a) => ` - ${a.name}: ${a.description}`)
|
||||
.join('\n');
|
||||
|
||||
return `${this.name}: ${this.description}\nActions:\n${actionDescriptions}`;
|
||||
return this.getToolExplanation();
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -176,6 +176,59 @@ export class BrowserTool extends BaseToolWrapper {
|
||||
}
|
||||
}
|
||||
|
||||
public getToolExplanation(): string {
|
||||
return `## Tool: browser
|
||||
Interact with web pages - take screenshots, generate PDFs, and execute JavaScript on pages.
|
||||
|
||||
### Actions:
|
||||
|
||||
**screenshot** - Take a screenshot of a webpage
|
||||
Parameters:
|
||||
- url (required): URL of the page to screenshot
|
||||
|
||||
Example:
|
||||
<tool_call>
|
||||
<tool>browser</tool>
|
||||
<action>screenshot</action>
|
||||
<params>{"url": "https://example.com"}</params>
|
||||
</tool_call>
|
||||
|
||||
**pdf** - Generate a PDF from a webpage
|
||||
Parameters:
|
||||
- url (required): URL of the page to convert to PDF
|
||||
|
||||
Example:
|
||||
<tool_call>
|
||||
<tool>browser</tool>
|
||||
<action>pdf</action>
|
||||
<params>{"url": "https://example.com/report"}</params>
|
||||
</tool_call>
|
||||
|
||||
**evaluate** - Execute JavaScript code on a webpage and return the result
|
||||
Parameters:
|
||||
- url (required): URL of the page to run the script on
|
||||
- script (required): JavaScript code to execute (must return a value)
|
||||
|
||||
Example:
|
||||
<tool_call>
|
||||
<tool>browser</tool>
|
||||
<action>evaluate</action>
|
||||
<params>{"url": "https://example.com", "script": "document.querySelectorAll('a').length"}</params>
|
||||
</tool_call>
|
||||
|
||||
**getPageContent** - Get the text content and title of a webpage
|
||||
Parameters:
|
||||
- url (required): URL of the page to get content from
|
||||
|
||||
Example:
|
||||
<tool_call>
|
||||
<tool>browser</tool>
|
||||
<action>getPageContent</action>
|
||||
<params>{"url": "https://example.com"}</params>
|
||||
</tool_call>
|
||||
`;
|
||||
}
|
||||
|
||||
public getCallSummary(action: string, params: Record<string, unknown>): string {
|
||||
switch (action) {
|
||||
case 'screenshot':
|
||||
|
||||
@@ -164,6 +164,45 @@ export class DenoTool extends BaseToolWrapper {
|
||||
}
|
||||
}
|
||||
|
||||
public getToolExplanation(): string {
|
||||
return `## Tool: deno
|
||||
Execute TypeScript/JavaScript code in a sandboxed Deno environment with fine-grained permission control.
|
||||
|
||||
### Actions:
|
||||
|
||||
**execute** - Execute TypeScript/JavaScript code and return stdout/stderr
|
||||
Parameters:
|
||||
- code (required): TypeScript/JavaScript code to execute
|
||||
- permissions (optional): Array of Deno permissions to grant. Options: "all", "env", "net", "read", "write", "run", "sys", "ffi", "hrtime". Default: none (fully sandboxed)
|
||||
|
||||
Example - Simple execution:
|
||||
<tool_call>
|
||||
<tool>deno</tool>
|
||||
<action>execute</action>
|
||||
<params>{"code": "console.log('Hello from Deno!');"}</params>
|
||||
</tool_call>
|
||||
|
||||
Example - With network permission:
|
||||
<tool_call>
|
||||
<tool>deno</tool>
|
||||
<action>execute</action>
|
||||
<params>{"code": "const resp = await fetch('https://api.example.com/data');\\nconsole.log(await resp.text());", "permissions": ["net"]}</params>
|
||||
</tool_call>
|
||||
|
||||
**executeWithResult** - Execute code that outputs JSON on the last line of stdout
|
||||
Parameters:
|
||||
- code (required): Code that console.logs a JSON value on the final line
|
||||
- permissions (optional): Array of Deno permissions to grant
|
||||
|
||||
Example:
|
||||
<tool_call>
|
||||
<tool>deno</tool>
|
||||
<action>executeWithResult</action>
|
||||
<params>{"code": "const result = { sum: 1 + 2, product: 2 * 3 };\\nconsole.log(JSON.stringify(result));"}</params>
|
||||
</tool_call>
|
||||
`;
|
||||
}
|
||||
|
||||
public getCallSummary(action: string, params: Record<string, unknown>): string {
|
||||
const code = params.code as string;
|
||||
const permissions = (params.permissions as string[]) || [];
|
||||
|
||||
144
ts/smartagent.tools.expert.ts
Normal file
144
ts/smartagent.tools.expert.ts
Normal file
@@ -0,0 +1,144 @@
|
||||
import * as plugins from './plugins.js';
|
||||
import * as interfaces from './smartagent.interfaces.js';
|
||||
import { BaseToolWrapper } from './smartagent.tools.base.js';
|
||||
|
||||
// Forward declaration to avoid circular import at module load time
|
||||
// The actual import happens lazily in initialize()
|
||||
let DualAgentOrchestrator: typeof import('./smartagent.classes.dualagent.js').DualAgentOrchestrator;
|
||||
|
||||
/**
|
||||
* ExpertTool - A specialized agent wrapped as a tool
|
||||
*
|
||||
* Enables hierarchical agent architectures where the Driver can delegate
|
||||
* complex tasks to specialized experts with their own tools and policies.
|
||||
*/
|
||||
export class ExpertTool extends BaseToolWrapper {
|
||||
public name: string;
|
||||
public description: string;
|
||||
public actions: interfaces.IToolAction[] = [
|
||||
{
|
||||
name: 'consult',
|
||||
description: 'Delegate a task or question to this expert',
|
||||
parameters: {
|
||||
type: 'object',
|
||||
properties: {
|
||||
task: { type: 'string', description: 'The task or question for the expert' },
|
||||
context: { type: 'string', description: 'Additional context to help the expert' },
|
||||
},
|
||||
required: ['task'],
|
||||
},
|
||||
},
|
||||
];
|
||||
|
||||
private config: interfaces.IExpertConfig;
|
||||
private smartAi: plugins.smartai.SmartAi;
|
||||
private inner?: InstanceType<typeof DualAgentOrchestrator>;
|
||||
|
||||
constructor(config: interfaces.IExpertConfig, smartAi: plugins.smartai.SmartAi) {
|
||||
super();
|
||||
this.config = config;
|
||||
this.smartAi = smartAi;
|
||||
this.name = config.name;
|
||||
this.description = config.description;
|
||||
}
|
||||
|
||||
async initialize(): Promise<void> {
|
||||
// Lazy import to avoid circular dependency
|
||||
if (!DualAgentOrchestrator) {
|
||||
const module = await import('./smartagent.classes.dualagent.js');
|
||||
DualAgentOrchestrator = module.DualAgentOrchestrator;
|
||||
}
|
||||
|
||||
this.inner = new DualAgentOrchestrator({
|
||||
smartAiInstance: this.smartAi, // Share SmartAi instance
|
||||
defaultProvider: this.config.provider,
|
||||
driverSystemMessage: this.config.systemMessage,
|
||||
guardianPolicyPrompt: this.config.guardianPolicy,
|
||||
maxIterations: this.config.maxIterations ?? 10,
|
||||
});
|
||||
|
||||
// Register expert's tools
|
||||
if (this.config.tools) {
|
||||
for (const tool of this.config.tools) {
|
||||
// Tools in the config are IAgentToolWrapper, but we need BaseToolWrapper
|
||||
// Since all our tools extend BaseToolWrapper, this cast is safe
|
||||
this.inner.registerTool(tool as BaseToolWrapper);
|
||||
}
|
||||
}
|
||||
|
||||
await this.inner.start();
|
||||
this.isInitialized = true;
|
||||
}
|
||||
|
||||
async cleanup(): Promise<void> {
|
||||
if (this.inner) {
|
||||
await this.inner.stop();
|
||||
this.inner = undefined;
|
||||
}
|
||||
this.isInitialized = false;
|
||||
}
|
||||
|
||||
async execute(
|
||||
action: string,
|
||||
params: Record<string, unknown>
|
||||
): Promise<interfaces.IToolExecutionResult> {
|
||||
this.validateAction(action);
|
||||
this.ensureInitialized();
|
||||
|
||||
const task = params.task as string;
|
||||
const context = params.context as string | undefined;
|
||||
|
||||
const fullTask = context ? `Context: ${context}\n\nTask: ${task}` : task;
|
||||
|
||||
try {
|
||||
const result = await this.inner!.run(fullTask);
|
||||
|
||||
return {
|
||||
success: result.success,
|
||||
result: {
|
||||
response: result.result,
|
||||
iterations: result.iterations,
|
||||
status: result.status,
|
||||
},
|
||||
summary: result.success
|
||||
? `Expert "${this.name}" completed (${result.iterations} iterations)`
|
||||
: `Expert "${this.name}" failed: ${result.status}`,
|
||||
};
|
||||
} catch (error) {
|
||||
return {
|
||||
success: false,
|
||||
error: `Expert error: ${error instanceof Error ? error.message : String(error)}`,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
getCallSummary(action: string, params: Record<string, unknown>): string {
|
||||
const task = params.task as string;
|
||||
const preview = task.length > 60 ? task.substring(0, 60) + '...' : task;
|
||||
return `Consult ${this.name}: "${preview}"`;
|
||||
}
|
||||
|
||||
getToolExplanation(): string {
|
||||
return `## Expert: ${this.name}
|
||||
${this.description}
|
||||
|
||||
### Usage:
|
||||
Delegate tasks to this expert when you need specialized help.
|
||||
|
||||
\`\`\`
|
||||
<tool_call>
|
||||
<tool>${this.name}</tool>
|
||||
<action>consult</action>
|
||||
<params>{"task": "Your question or task", "context": "Optional background"}</params>
|
||||
</tool_call>
|
||||
\`\`\`
|
||||
`;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the expert's configuration
|
||||
*/
|
||||
getConfig(): interfaces.IExpertConfig {
|
||||
return this.config;
|
||||
}
|
||||
}
|
||||
@@ -666,6 +666,170 @@ export class FilesystemTool extends BaseToolWrapper {
|
||||
}
|
||||
}
|
||||
|
||||
public getToolExplanation(): string {
|
||||
return `## Tool: filesystem
|
||||
Read, write, list, and delete files and directories.
|
||||
|
||||
### Actions:
|
||||
|
||||
**read** - Read file contents (full or specific line range)
|
||||
Parameters:
|
||||
- path (required): Path to the file
|
||||
- encoding (optional): File encoding - "utf8" (default), "binary", or "base64"
|
||||
- startLine (optional): First line to read (1-indexed, inclusive)
|
||||
- endLine (optional): Last line to read (1-indexed, inclusive)
|
||||
|
||||
Example:
|
||||
<tool_call>
|
||||
<tool>filesystem</tool>
|
||||
<action>read</action>
|
||||
<params>{"path": "/path/to/file.txt"}</params>
|
||||
</tool_call>
|
||||
|
||||
Example with line range:
|
||||
<tool_call>
|
||||
<tool>filesystem</tool>
|
||||
<action>read</action>
|
||||
<params>{"path": "/path/to/file.txt", "startLine": 10, "endLine": 20}</params>
|
||||
</tool_call>
|
||||
|
||||
**write** - Write content to a file (creates or overwrites)
|
||||
Parameters:
|
||||
- path (required): Absolute path to the file
|
||||
- content (required): Content to write
|
||||
- encoding (optional): File encoding - "utf8" (default), "binary", or "base64"
|
||||
|
||||
Example:
|
||||
<tool_call>
|
||||
<tool>filesystem</tool>
|
||||
<action>write</action>
|
||||
<params>{"path": "/path/to/output.txt", "content": "Hello, World!"}</params>
|
||||
</tool_call>
|
||||
|
||||
**list** - List files and directories in a path
|
||||
Parameters:
|
||||
- path (required): Directory path to list
|
||||
- recursive (optional): List recursively (default: false)
|
||||
- filter (optional): Glob pattern to filter results (e.g., "*.ts")
|
||||
|
||||
Example:
|
||||
<tool_call>
|
||||
<tool>filesystem</tool>
|
||||
<action>list</action>
|
||||
<params>{"path": "/path/to/dir", "recursive": true, "filter": "*.ts"}</params>
|
||||
</tool_call>
|
||||
|
||||
**exists** - Check if a file or directory exists
|
||||
Parameters:
|
||||
- path (required): Path to check
|
||||
|
||||
Example:
|
||||
<tool_call>
|
||||
<tool>filesystem</tool>
|
||||
<action>exists</action>
|
||||
<params>{"path": "/path/to/check"}</params>
|
||||
</tool_call>
|
||||
|
||||
**mkdir** - Create a directory
|
||||
Parameters:
|
||||
- path (required): Directory path to create
|
||||
- recursive (optional): Create parent directories if needed (default: true)
|
||||
|
||||
Example:
|
||||
<tool_call>
|
||||
<tool>filesystem</tool>
|
||||
<action>mkdir</action>
|
||||
<params>{"path": "/path/to/new/dir"}</params>
|
||||
</tool_call>
|
||||
|
||||
**delete** - Delete a file or directory
|
||||
Parameters:
|
||||
- path (required): Path to delete
|
||||
- recursive (optional): For directories, delete recursively (default: false)
|
||||
|
||||
Example:
|
||||
<tool_call>
|
||||
<tool>filesystem</tool>
|
||||
<action>delete</action>
|
||||
<params>{"path": "/path/to/delete", "recursive": true}</params>
|
||||
</tool_call>
|
||||
|
||||
**copy** - Copy a file to a new location
|
||||
Parameters:
|
||||
- source (required): Source file path
|
||||
- destination (required): Destination file path
|
||||
|
||||
Example:
|
||||
<tool_call>
|
||||
<tool>filesystem</tool>
|
||||
<action>copy</action>
|
||||
<params>{"source": "/path/to/source.txt", "destination": "/path/to/dest.txt"}</params>
|
||||
</tool_call>
|
||||
|
||||
**move** - Move a file to a new location
|
||||
Parameters:
|
||||
- source (required): Source file path
|
||||
- destination (required): Destination file path
|
||||
|
||||
Example:
|
||||
<tool_call>
|
||||
<tool>filesystem</tool>
|
||||
<action>move</action>
|
||||
<params>{"source": "/path/to/old.txt", "destination": "/path/to/new.txt"}</params>
|
||||
</tool_call>
|
||||
|
||||
**stat** - Get file or directory statistics (size, dates, etc.)
|
||||
Parameters:
|
||||
- path (required): Path to get stats for
|
||||
|
||||
Example:
|
||||
<tool_call>
|
||||
<tool>filesystem</tool>
|
||||
<action>stat</action>
|
||||
<params>{"path": "/path/to/file.txt"}</params>
|
||||
</tool_call>
|
||||
|
||||
**append** - Append content to a file
|
||||
Parameters:
|
||||
- path (required): Absolute path to the file
|
||||
- content (required): Content to append
|
||||
|
||||
Example:
|
||||
<tool_call>
|
||||
<tool>filesystem</tool>
|
||||
<action>append</action>
|
||||
<params>{"path": "/path/to/log.txt", "content": "New log entry\\n"}</params>
|
||||
</tool_call>
|
||||
|
||||
**tree** - Show directory structure as a tree
|
||||
Parameters:
|
||||
- path (required): Root directory path
|
||||
- maxDepth (optional): Maximum depth to traverse (default: 3)
|
||||
- filter (optional): Glob pattern to filter files
|
||||
- showSizes (optional): Include file sizes in output (default: false)
|
||||
- format (optional): Output format - "string" (default) or "json"
|
||||
|
||||
Example:
|
||||
<tool_call>
|
||||
<tool>filesystem</tool>
|
||||
<action>tree</action>
|
||||
<params>{"path": "/path/to/dir", "maxDepth": 2}</params>
|
||||
</tool_call>
|
||||
|
||||
**glob** - Find files matching a glob pattern
|
||||
Parameters:
|
||||
- pattern (required): Glob pattern (e.g., "**/*.ts", "src/**/*.js")
|
||||
- path (optional): Base path to search from
|
||||
|
||||
Example:
|
||||
<tool_call>
|
||||
<tool>filesystem</tool>
|
||||
<action>glob</action>
|
||||
<params>{"pattern": "**/*.ts", "path": "/path/to/project"}</params>
|
||||
</tool_call>
|
||||
`;
|
||||
}
|
||||
|
||||
public getCallSummary(action: string, params: Record<string, unknown>): string {
|
||||
switch (action) {
|
||||
case 'read': {
|
||||
|
||||
@@ -180,6 +180,84 @@ export class HttpTool extends BaseToolWrapper {
|
||||
}
|
||||
}
|
||||
|
||||
public getToolExplanation(): string {
|
||||
return `## Tool: http
|
||||
Make HTTP requests to web APIs and services.
|
||||
|
||||
### Actions:
|
||||
|
||||
**get** - Make a GET request
|
||||
Parameters:
|
||||
- url (required): URL to request
|
||||
- headers (optional): Request headers (key-value object)
|
||||
- query (optional): Query parameters (key-value object)
|
||||
- timeout (optional): Timeout in milliseconds
|
||||
|
||||
Example:
|
||||
<tool_call>
|
||||
<tool>http</tool>
|
||||
<action>get</action>
|
||||
<params>{"url": "https://api.example.com/data", "headers": {"Authorization": "Bearer token123"}}</params>
|
||||
</tool_call>
|
||||
|
||||
**post** - Make a POST request with JSON body
|
||||
Parameters:
|
||||
- url (required): URL to request
|
||||
- body (optional): JSON body to send
|
||||
- headers (optional): Request headers (key-value object)
|
||||
- query (optional): Query parameters (key-value object)
|
||||
- timeout (optional): Timeout in milliseconds
|
||||
|
||||
Example:
|
||||
<tool_call>
|
||||
<tool>http</tool>
|
||||
<action>post</action>
|
||||
<params>{"url": "https://api.example.com/submit", "body": {"name": "test", "value": 123}}</params>
|
||||
</tool_call>
|
||||
|
||||
**put** - Make a PUT request with JSON body
|
||||
Parameters:
|
||||
- url (required): URL to request
|
||||
- body (required): JSON body to send
|
||||
- headers (optional): Request headers (key-value object)
|
||||
- timeout (optional): Timeout in milliseconds
|
||||
|
||||
Example:
|
||||
<tool_call>
|
||||
<tool>http</tool>
|
||||
<action>put</action>
|
||||
<params>{"url": "https://api.example.com/resource/1", "body": {"name": "updated"}}</params>
|
||||
</tool_call>
|
||||
|
||||
**patch** - Make a PATCH request with JSON body
|
||||
Parameters:
|
||||
- url (required): URL to request
|
||||
- body (required): JSON body to send
|
||||
- headers (optional): Request headers (key-value object)
|
||||
- timeout (optional): Timeout in milliseconds
|
||||
|
||||
Example:
|
||||
<tool_call>
|
||||
<tool>http</tool>
|
||||
<action>patch</action>
|
||||
<params>{"url": "https://api.example.com/resource/1", "body": {"status": "active"}}</params>
|
||||
</tool_call>
|
||||
|
||||
**delete** - Make a DELETE request
|
||||
Parameters:
|
||||
- url (required): URL to request
|
||||
- headers (optional): Request headers (key-value object)
|
||||
- timeout (optional): Timeout in milliseconds
|
||||
|
||||
Example:
|
||||
<tool_call>
|
||||
<tool>http</tool>
|
||||
<action>delete</action>
|
||||
<params>{"url": "https://api.example.com/resource/1"}</params>
|
||||
</tool_call>
|
||||
`;
|
||||
}
|
||||
|
||||
public getCallSummary(action: string, params: Record<string, unknown>): string {
|
||||
const method = action.toUpperCase();
|
||||
let summary = `${method} request to "${params.url}"`;
|
||||
|
||||
224
ts/smartagent.tools.json.ts
Normal file
224
ts/smartagent.tools.json.ts
Normal file
@@ -0,0 +1,224 @@
|
||||
import * as interfaces from './smartagent.interfaces.js';
|
||||
import { BaseToolWrapper } from './smartagent.tools.base.js';
|
||||
|
||||
/**
|
||||
* JsonValidatorTool - Validates and formats JSON data
|
||||
* Useful for agents to self-validate their JSON output before completing a task
|
||||
*/
|
||||
export class JsonValidatorTool extends BaseToolWrapper {
|
||||
public name = 'json';
|
||||
public description = 'Validate and format JSON data. Use this to verify your JSON output is valid before completing a task.';
|
||||
|
||||
public actions: interfaces.IToolAction[] = [
|
||||
{
|
||||
name: 'validate',
|
||||
description: 'Validate that a string is valid JSON and optionally check required fields',
|
||||
parameters: {
|
||||
type: 'object',
|
||||
properties: {
|
||||
jsonString: {
|
||||
type: 'string',
|
||||
description: 'The JSON string to validate',
|
||||
},
|
||||
requiredFields: {
|
||||
type: 'array',
|
||||
items: { type: 'string' },
|
||||
description: 'Optional list of field names that must be present at the root level',
|
||||
},
|
||||
},
|
||||
required: ['jsonString'],
|
||||
},
|
||||
},
|
||||
{
|
||||
name: 'format',
|
||||
description: 'Parse and pretty-print JSON string',
|
||||
parameters: {
|
||||
type: 'object',
|
||||
properties: {
|
||||
jsonString: {
|
||||
type: 'string',
|
||||
description: 'The JSON string to format',
|
||||
},
|
||||
},
|
||||
required: ['jsonString'],
|
||||
},
|
||||
},
|
||||
];
|
||||
|
||||
async initialize(): Promise<void> {
|
||||
this.isInitialized = true;
|
||||
}
|
||||
|
||||
async cleanup(): Promise<void> {
|
||||
this.isInitialized = false;
|
||||
}
|
||||
|
||||
async execute(
|
||||
action: string,
|
||||
params: Record<string, unknown>
|
||||
): Promise<interfaces.IToolExecutionResult> {
|
||||
this.validateAction(action);
|
||||
|
||||
switch (action) {
|
||||
case 'validate':
|
||||
return this.validateJson(params);
|
||||
case 'format':
|
||||
return this.formatJson(params);
|
||||
default:
|
||||
return { success: false, error: `Unknown action: ${action}` };
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Validate JSON string and optionally check for required fields
|
||||
*/
|
||||
private validateJson(params: Record<string, unknown>): interfaces.IToolExecutionResult {
|
||||
const jsonString = params.jsonString as string;
|
||||
const requiredFields = params.requiredFields as string[] | undefined;
|
||||
|
||||
if (!jsonString || typeof jsonString !== 'string') {
|
||||
return {
|
||||
success: false,
|
||||
error: 'jsonString parameter is required and must be a string',
|
||||
};
|
||||
}
|
||||
|
||||
try {
|
||||
const parsed = JSON.parse(jsonString);
|
||||
|
||||
// Check required fields if specified
|
||||
if (requiredFields && Array.isArray(requiredFields)) {
|
||||
const missingFields = requiredFields.filter((field) => {
|
||||
if (typeof parsed !== 'object' || parsed === null) {
|
||||
return true;
|
||||
}
|
||||
return !(field in parsed);
|
||||
});
|
||||
|
||||
if (missingFields.length > 0) {
|
||||
return {
|
||||
success: false,
|
||||
error: `Missing required fields: ${missingFields.join(', ')}`,
|
||||
result: {
|
||||
valid: false,
|
||||
missingFields,
|
||||
presentFields: Object.keys(parsed || {}),
|
||||
},
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
success: true,
|
||||
result: {
|
||||
valid: true,
|
||||
parsed,
|
||||
type: Array.isArray(parsed) ? 'array' : typeof parsed,
|
||||
fieldCount: typeof parsed === 'object' && parsed !== null ? Object.keys(parsed).length : undefined,
|
||||
},
|
||||
summary: `JSON is valid (${Array.isArray(parsed) ? 'array' : typeof parsed})`,
|
||||
};
|
||||
} catch (error) {
|
||||
const errorMessage = (error as Error).message;
|
||||
|
||||
// Extract position from error message if available
|
||||
const posMatch = errorMessage.match(/position\s*(\d+)/i);
|
||||
const position = posMatch ? parseInt(posMatch[1]) : undefined;
|
||||
|
||||
// Provide context around the error position
|
||||
let context: string | undefined;
|
||||
if (position !== undefined) {
|
||||
const start = Math.max(0, position - 20);
|
||||
const end = Math.min(jsonString.length, position + 20);
|
||||
context = jsonString.substring(start, end);
|
||||
}
|
||||
|
||||
return {
|
||||
success: false,
|
||||
error: `Invalid JSON: ${errorMessage}`,
|
||||
result: {
|
||||
valid: false,
|
||||
errorPosition: position,
|
||||
errorContext: context,
|
||||
},
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Format/pretty-print JSON string
|
||||
*/
|
||||
private formatJson(params: Record<string, unknown>): interfaces.IToolExecutionResult {
|
||||
const jsonString = params.jsonString as string;
|
||||
|
||||
if (!jsonString || typeof jsonString !== 'string') {
|
||||
return {
|
||||
success: false,
|
||||
error: 'jsonString parameter is required and must be a string',
|
||||
};
|
||||
}
|
||||
|
||||
try {
|
||||
const parsed = JSON.parse(jsonString);
|
||||
const formatted = JSON.stringify(parsed, null, 2);
|
||||
|
||||
return {
|
||||
success: true,
|
||||
result: formatted,
|
||||
summary: `Formatted JSON (${formatted.length} chars)`,
|
||||
};
|
||||
} catch (error) {
|
||||
return {
|
||||
success: false,
|
||||
error: `Cannot format invalid JSON: ${(error as Error).message}`,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
public getToolExplanation(): string {
|
||||
return `## Tool: json
|
||||
Validate and format JSON data. Use this to verify your JSON output is valid before completing a task.
|
||||
|
||||
### Actions:
|
||||
|
||||
**validate** - Validate that a string is valid JSON and optionally check required fields
|
||||
Parameters:
|
||||
- jsonString (required): The JSON string to validate
|
||||
- requiredFields (optional): Array of field names that must be present
|
||||
|
||||
Example:
|
||||
<tool_call>
|
||||
<tool>json</tool>
|
||||
<action>validate</action>
|
||||
<params>{"jsonString": "{\\"invoice_number\\":\\"INV-001\\",\\"total\\":99.99}", "requiredFields": ["invoice_number", "total"]}</params>
|
||||
</tool_call>
|
||||
|
||||
**format** - Parse and pretty-print JSON string
|
||||
Parameters:
|
||||
- jsonString (required): The JSON string to format
|
||||
|
||||
Example:
|
||||
<tool_call>
|
||||
<tool>json</tool>
|
||||
<action>format</action>
|
||||
<params>{"jsonString": "{\\"name\\":\\"test\\",\\"value\\":123}"}</params>
|
||||
</tool_call>
|
||||
`;
|
||||
}
|
||||
|
||||
getCallSummary(action: string, params: Record<string, unknown>): string {
|
||||
const jsonStr = (params.jsonString as string) || '';
|
||||
const preview = jsonStr.length > 50 ? jsonStr.substring(0, 50) + '...' : jsonStr;
|
||||
|
||||
switch (action) {
|
||||
case 'validate':
|
||||
const fields = params.requiredFields as string[] | undefined;
|
||||
const fieldInfo = fields ? ` (checking fields: ${fields.join(', ')})` : '';
|
||||
return `Validate JSON: ${preview}${fieldInfo}`;
|
||||
case 'format':
|
||||
return `Format JSON: ${preview}`;
|
||||
default:
|
||||
return `JSON ${action}: ${preview}`;
|
||||
}
|
||||
}
|
||||
}
|
||||
237
ts/smartagent.tools.search.ts
Normal file
237
ts/smartagent.tools.search.ts
Normal file
@@ -0,0 +1,237 @@
|
||||
import * as interfaces from './smartagent.interfaces.js';
|
||||
import { BaseToolWrapper } from './smartagent.tools.base.js';
|
||||
import { ToolRegistry } from './smartagent.classes.toolregistry.js';
|
||||
|
||||
/**
|
||||
* ToolSearchTool - AI-facing interface for discovering and activating tools
|
||||
*
|
||||
* This tool enables the Driver to:
|
||||
* - Search for tools by capability
|
||||
* - List all available tools
|
||||
* - Activate on-demand tools
|
||||
* - Get detailed information about specific tools
|
||||
*/
|
||||
export class ToolSearchTool extends BaseToolWrapper {
|
||||
public name = 'tools';
|
||||
public description =
|
||||
'Search for and activate available tools and experts. Use this to discover specialized capabilities.';
|
||||
|
||||
public actions: interfaces.IToolAction[] = [
|
||||
{
|
||||
name: 'search',
|
||||
description: 'Search for tools by name, description, tags, or capabilities',
|
||||
parameters: {
|
||||
type: 'object',
|
||||
properties: {
|
||||
query: { type: 'string', description: 'Search query' },
|
||||
},
|
||||
required: ['query'],
|
||||
},
|
||||
},
|
||||
{
|
||||
name: 'list',
|
||||
description: 'List all available tools grouped by visibility',
|
||||
parameters: { type: 'object', properties: {} },
|
||||
},
|
||||
{
|
||||
name: 'activate',
|
||||
description: 'Activate an on-demand tool to make it available for use',
|
||||
parameters: {
|
||||
type: 'object',
|
||||
properties: {
|
||||
name: { type: 'string', description: 'Name of the tool to activate' },
|
||||
},
|
||||
required: ['name'],
|
||||
},
|
||||
},
|
||||
{
|
||||
name: 'details',
|
||||
description: 'Get detailed information about a specific tool',
|
||||
parameters: {
|
||||
type: 'object',
|
||||
properties: {
|
||||
name: { type: 'string', description: 'Name of the tool' },
|
||||
},
|
||||
required: ['name'],
|
||||
},
|
||||
},
|
||||
];
|
||||
|
||||
private registry: ToolRegistry;
|
||||
private onToolActivated?: (tool: BaseToolWrapper) => void;
|
||||
|
||||
constructor(registry: ToolRegistry, onToolActivated?: (tool: BaseToolWrapper) => void) {
|
||||
super();
|
||||
this.registry = registry;
|
||||
this.onToolActivated = onToolActivated;
|
||||
}
|
||||
|
||||
async initialize(): Promise<void> {
|
||||
this.isInitialized = true;
|
||||
}
|
||||
|
||||
async cleanup(): Promise<void> {
|
||||
this.isInitialized = false;
|
||||
}
|
||||
|
||||
async execute(
|
||||
action: string,
|
||||
params: Record<string, unknown>
|
||||
): Promise<interfaces.IToolExecutionResult> {
|
||||
this.validateAction(action);
|
||||
|
||||
switch (action) {
|
||||
case 'search':
|
||||
return this.handleSearch(params.query as string);
|
||||
case 'list':
|
||||
return this.handleList();
|
||||
case 'activate':
|
||||
return this.handleActivate(params.name as string);
|
||||
case 'details':
|
||||
return this.handleDetails(params.name as string);
|
||||
default:
|
||||
return { success: false, error: `Unknown action: ${action}` };
|
||||
}
|
||||
}
|
||||
|
||||
private handleSearch(query: string): interfaces.IToolExecutionResult {
|
||||
const results = this.registry.search(query);
|
||||
return {
|
||||
success: true,
|
||||
result: results.map((m) => ({
|
||||
name: m.name,
|
||||
description: m.description,
|
||||
visibility: m.visibility,
|
||||
isActivated: m.isActivated,
|
||||
category: m.category,
|
||||
tags: m.tags,
|
||||
actionCount: m.actions.length,
|
||||
})),
|
||||
summary: `Found ${results.length} tools matching "${query}"`,
|
||||
};
|
||||
}
|
||||
|
||||
private handleList(): interfaces.IToolExecutionResult {
|
||||
const all = this.registry.getAllMetadata();
|
||||
const initial = all.filter((m) => m.visibility === 'initial');
|
||||
const onDemand = all.filter((m) => m.visibility === 'on-demand');
|
||||
|
||||
return {
|
||||
success: true,
|
||||
result: {
|
||||
initial: initial.map((m) => ({
|
||||
name: m.name,
|
||||
description: m.description,
|
||||
category: m.category,
|
||||
})),
|
||||
onDemand: onDemand.map((m) => ({
|
||||
name: m.name,
|
||||
description: m.description,
|
||||
category: m.category,
|
||||
isActivated: m.isActivated,
|
||||
})),
|
||||
summary: `${initial.length} initial, ${onDemand.length} on-demand`,
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
private async handleActivate(name: string): Promise<interfaces.IToolExecutionResult> {
|
||||
const result = await this.registry.activate(name);
|
||||
|
||||
if (result.success && this.onToolActivated) {
|
||||
const tool = this.registry.getTool(name);
|
||||
if (tool) {
|
||||
this.onToolActivated(tool);
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
success: result.success,
|
||||
result: result.success ? { name, message: `Tool "${name}" is now available` } : undefined,
|
||||
error: result.error,
|
||||
summary: result.success ? `Activated: ${name}` : result.error,
|
||||
};
|
||||
}
|
||||
|
||||
private handleDetails(name: string): interfaces.IToolExecutionResult {
|
||||
const tool = this.registry.getTool(name);
|
||||
const meta = this.registry.getMetadata(name);
|
||||
|
||||
if (!tool || !meta) {
|
||||
return { success: false, error: `Tool "${name}" not found` };
|
||||
}
|
||||
|
||||
return {
|
||||
success: true,
|
||||
result: {
|
||||
name: meta.name,
|
||||
description: meta.description,
|
||||
visibility: meta.visibility,
|
||||
isActivated: meta.isActivated,
|
||||
category: meta.category,
|
||||
tags: meta.tags,
|
||||
actions: meta.actions,
|
||||
fullExplanation: tool.getToolExplanation(),
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
getCallSummary(action: string, params: Record<string, unknown>): string {
|
||||
switch (action) {
|
||||
case 'search':
|
||||
return `Search tools: "${params.query}"`;
|
||||
case 'list':
|
||||
return 'List all tools';
|
||||
case 'activate':
|
||||
return `Activate tool: ${params.name}`;
|
||||
case 'details':
|
||||
return `Get details: ${params.name}`;
|
||||
default:
|
||||
return `tools.${action}`;
|
||||
}
|
||||
}
|
||||
|
||||
getToolExplanation(): string {
|
||||
return `## Tool: tools
|
||||
Search for and manage available tools and experts.
|
||||
|
||||
### Actions:
|
||||
|
||||
**search** - Find tools by capability
|
||||
\`\`\`
|
||||
<tool_call>
|
||||
<tool>tools</tool>
|
||||
<action>search</action>
|
||||
<params>{"query": "database"}</params>
|
||||
</tool_call>
|
||||
\`\`\`
|
||||
|
||||
**list** - List all tools grouped by visibility
|
||||
\`\`\`
|
||||
<tool_call>
|
||||
<tool>tools</tool>
|
||||
<action>list</action>
|
||||
<params>{}</params>
|
||||
</tool_call>
|
||||
\`\`\`
|
||||
|
||||
**activate** - Activate an on-demand tool
|
||||
\`\`\`
|
||||
<tool_call>
|
||||
<tool>tools</tool>
|
||||
<action>activate</action>
|
||||
<params>{"name": "database_expert"}</params>
|
||||
</tool_call>
|
||||
\`\`\`
|
||||
|
||||
**details** - Get full information about a tool
|
||||
\`\`\`
|
||||
<tool_call>
|
||||
<tool>tools</tool>
|
||||
<action>details</action>
|
||||
<params>{"name": "filesystem"}</params>
|
||||
</tool_call>
|
||||
\`\`\`
|
||||
`;
|
||||
}
|
||||
}
|
||||
@@ -148,6 +148,54 @@ export class ShellTool extends BaseToolWrapper {
|
||||
}
|
||||
}
|
||||
|
||||
public getToolExplanation(): string {
|
||||
return `## Tool: shell
|
||||
Execute shell commands securely. Uses execSpawn (shell:false) to prevent command injection.
|
||||
|
||||
### Actions:
|
||||
|
||||
**execute** - Execute a command with arguments (secure, no shell injection possible)
|
||||
Parameters:
|
||||
- command (required): The command to execute (e.g., "ls", "cat", "grep", "node")
|
||||
- args (optional): Array of arguments (each argument is properly escaped)
|
||||
- cwd (optional): Working directory for the command
|
||||
- timeout (optional): Timeout in milliseconds
|
||||
- env (optional): Additional environment variables (key-value object)
|
||||
|
||||
Example - List files:
|
||||
<tool_call>
|
||||
<tool>shell</tool>
|
||||
<action>execute</action>
|
||||
<params>{"command": "ls", "args": ["-la", "/path/to/dir"]}</params>
|
||||
</tool_call>
|
||||
|
||||
Example - Run Node script:
|
||||
<tool_call>
|
||||
<tool>shell</tool>
|
||||
<action>execute</action>
|
||||
<params>{"command": "node", "args": ["script.js"], "cwd": "/path/to/project"}</params>
|
||||
</tool_call>
|
||||
|
||||
Example - Search in files:
|
||||
<tool_call>
|
||||
<tool>shell</tool>
|
||||
<action>execute</action>
|
||||
<params>{"command": "grep", "args": ["-r", "pattern", "src/"]}</params>
|
||||
</tool_call>
|
||||
|
||||
**which** - Check if a command exists and get its path
|
||||
Parameters:
|
||||
- command (required): Command name to look up (e.g., "node", "git")
|
||||
|
||||
Example:
|
||||
<tool_call>
|
||||
<tool>shell</tool>
|
||||
<action>which</action>
|
||||
<params>{"command": "node"}</params>
|
||||
</tool_call>
|
||||
`;
|
||||
}
|
||||
|
||||
public getCallSummary(action: string, params: Record<string, unknown>): string {
|
||||
switch (action) {
|
||||
case 'execute': {
|
||||
|
||||
Reference in New Issue
Block a user