13 Commits

Author SHA1 Message Date
5ca0c80ea9 v1.7.0
Some checks failed
Default (tags) / security (push) Successful in 36s
Default (tags) / test (push) Failing after 35s
Default (tags) / release (push) Has been skipped
Default (tags) / metadata (push) Has been skipped
2026-01-20 12:01:07 +00:00
940bf3d3ef feat(docs): document native tool calling support and update README to clarify standard and additional tools 2026-01-20 12:01:07 +00:00
c1b269f301 v1.6.2
Some checks failed
Default (tags) / security (push) Successful in 34s
Default (tags) / test (push) Failing after 35s
Default (tags) / release (push) Has been skipped
Default (tags) / metadata (push) Has been skipped
2026-01-20 03:56:44 +00:00
7cb970f9e2 fix(release): bump version to 1.6.2 2026-01-20 03:56:44 +00:00
1fbcf8bb8b fix(driveragent): save tool_calls in message history for native tool calling
When using native tool calling, the assistant's tool_calls must be saved
in message history. Without this, the model doesn't know it already called
a tool and loops indefinitely calling the same tool.

This fix saves tool_calls in both startTaskWithNativeTools and
continueWithNativeTools methods.

Also updates @push.rocks/smartai to v0.13.3 for tool_calls forwarding support.
2026-01-20 03:56:10 +00:00
4a8789019a v1.6.1
Some checks failed
Default (tags) / security (push) Successful in 38s
Default (tags) / test (push) Failing after 35s
Default (tags) / release (push) Has been skipped
Default (tags) / metadata (push) Has been skipped
2026-01-20 03:38:07 +00:00
0da85a5dcd fix(driveragent): include full message history for tool results and use a continuation prompt when invoking provider.collectStreamResponse 2026-01-20 03:38:07 +00:00
121e216eea v1.6.0
Some checks failed
Default (tags) / security (push) Successful in 33s
Default (tags) / test (push) Failing after 36s
Default (tags) / release (push) Has been skipped
Default (tags) / metadata (push) Has been skipped
2026-01-20 03:28:59 +00:00
eb1058bfb5 feat(smartagent): record native tool results in message history by adding optional toolName to continueWithNativeTools and passing tool identifier from DualAgent 2026-01-20 03:28:59 +00:00
ecdc125a43 v1.5.4
Some checks failed
Default (tags) / security (push) Successful in 33s
Default (tags) / test (push) Failing after 36s
Default (tags) / release (push) Has been skipped
Default (tags) / metadata (push) Has been skipped
2026-01-20 03:16:02 +00:00
73657be550 fix(driveragent): prevent duplicate thinking/output markers during token streaming and mark transitions 2026-01-20 03:16:02 +00:00
4e4d3c0e08 v1.5.3
Some checks failed
Default (tags) / security (push) Successful in 38s
Default (tags) / test (push) Failing after 40s
Default (tags) / release (push) Has been skipped
Default (tags) / metadata (push) Has been skipped
2026-01-20 03:10:53 +00:00
79efe8f6b8 fix(driveragent): prefix thinking tokens with [THINKING] when forwarding streaming chunks to onToken 2026-01-20 03:10:53 +00:00
8 changed files with 210 additions and 33 deletions

View File

@@ -1,5 +1,52 @@
# Changelog
## 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

View File

@@ -1,6 +1,6 @@
{
"name": "@push.rocks/smartagent",
"version": "1.5.2",
"version": "1.7.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.13.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
View File

@@ -9,8 +9,8 @@ importers:
.:
dependencies:
'@push.rocks/smartai':
specifier: ^0.13.1
version: 0.13.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.13.1':
resolution: {integrity: sha512-V9J6a+rjBkFpdFnC6OBm8CbEtqCfJnEsUmNKfRUOiTa+VIVtD4OOceraZah6kGHWltUhZ1XV4eLWwFf4+YO3NA==}
'@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.13.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

View File

@@ -5,18 +5,20 @@
## Architecture
- **DualAgentOrchestrator**: Main entry point, coordinates Driver and Guardian agents
- **DriverAgent**: Reasons about tasks, plans steps, proposes tool calls
- **DriverAgent**: Reasons about tasks, plans steps, proposes tool calls (supports both XML and native tool calling)
- **GuardianAgent**: Evaluates tool calls against configured policies
- **BaseToolWrapper**: Base class for creating custom tools
- **plugins.ts**: Imports and re-exports smartai and other dependencies
## Standard Tools
## 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
6. **JsonValidatorTool** - JSON validation and formatting
## Additional Tools (must register manually)
6. **JsonValidatorTool** - JSON validation and formatting (NOT in registerStandardTools)
## Key Features
- Token streaming support (`onToken` callback)
@@ -25,6 +27,14 @@
- 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
## 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`: Multi-provider AI interface

View File

@@ -50,7 +50,6 @@ flowchart TB
Shell["Shell"]
Browser["Browser"]
Deno["Deno"]
JSON["JSON Validator"]
end
Task --> Orchestrator
@@ -100,7 +99,7 @@ await orchestrator.stop();
## Standard Tools
SmartAgent comes with six battle-tested tools out of the box:
SmartAgent comes with five battle-tested tools out of the box via `registerStandardTools()`:
### 🗂️ FilesystemTool
@@ -231,12 +230,21 @@ 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>
@@ -330,6 +338,29 @@ const orchestrator = new DualAgentOrchestrator({
**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:
@@ -401,6 +432,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;
@@ -415,6 +447,9 @@ interface IDualAgentOptions {
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)
@@ -574,7 +609,7 @@ const orchestrator = new DualAgentOrchestrator({
| `run(task, options?)` | Execute a task with optional images for vision |
| `continueTask(input)` | Continue a task with user input |
| `registerTool(tool)` | Register a custom tool |
| `registerStandardTools()` | Register all built-in tools |
| `registerStandardTools()` | Register all built-in tools (Filesystem, HTTP, Shell, Browser, Deno) |
| `registerScopedFilesystemTool(basePath, excludePatterns?)` | Register filesystem tool with path restriction |
| `setGuardianPolicy(policy)` | Update Guardian policy at runtime |
| `getHistory()` | Get conversation history |

View File

@@ -3,6 +3,6 @@
*/
export const commitinfo = {
name: '@push.rocks/smartagent',
version: '1.5.2',
version: '1.7.0',
description: 'an agentic framework built on top of @push.rocks/smartai'
}

View File

@@ -25,6 +25,7 @@ export class DriverAgent {
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,
@@ -491,20 +492,49 @@ Your complete output here
images: images,
tools: tools.length > 0 ? tools : undefined,
},
// Pass onToken callback through onChunk for streaming
// Pass onToken callback through onChunk for streaming with thinking markers
this.onToken ? (chunk: any) => {
if (chunk.thinking && this.onToken) this.onToken(chunk.thinking);
if (chunk.content && this.onToken) this.onToken(chunk.content);
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: plugins.smartai.ChatMessage = {
const historyMessage: any = {
role: 'assistant',
content: response.message || '',
reasoning: response.thinking || response.reasoning,
};
this.messageHistory.push(historyMessage);
// 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;
@@ -530,16 +560,29 @@ Your complete output here
/**
* 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
message: string,
toolName?: string
): Promise<{ message: interfaces.IAgentMessage; toolCalls?: interfaces.INativeToolCall[] }> {
// Add the new message to history
this.messageHistory.push({
role: 'user',
content: message,
});
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();
@@ -548,8 +591,12 @@ Your complete output here
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 = this.messageHistory.slice(0, -1);
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 = [
@@ -566,27 +613,62 @@ Your complete output here
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: message,
userMessage: userMessage,
messageHistory: historyForChat,
tools: tools.length > 0 ? tools : undefined,
},
// Pass onToken callback through onChunk for streaming
// Pass onToken callback through onChunk for streaming with thinking markers
this.onToken ? (chunk: any) => {
if (chunk.thinking && this.onToken) this.onToken(chunk.thinking);
if (chunk.content && this.onToken) this.onToken(chunk.content);
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
this.messageHistory.push({
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;

View File

@@ -495,7 +495,8 @@ Please output the exact XML format above.`
// Continue with appropriate method based on mode
if (useNativeTools) {
const continueResult = await this.driver.continueWithNativeTools(resultMessage);
const toolNameForHistory = `${proposal.toolName}_${proposal.action}`;
const continueResult = await this.driver.continueWithNativeTools(resultMessage, toolNameForHistory);
driverResponse = continueResult.message;
pendingNativeToolCalls = continueResult.toolCalls;
} else {
@@ -505,8 +506,10 @@ Please output the exact XML format above.`
} catch (error) {
const errorMessage = `Tool execution failed: ${error instanceof Error ? error.message : String(error)}`;
if (useNativeTools) {
const toolNameForHistory = `${proposal.toolName}_${proposal.action}`;
const continueResult = await this.driver.continueWithNativeTools(
`TOOL ERROR: ${errorMessage}\n\nPlease try a different approach.`
`TOOL ERROR: ${errorMessage}\n\nPlease try a different approach.`,
toolNameForHistory
);
driverResponse = continueResult.message;
pendingNativeToolCalls = continueResult.toolCalls;