10 Commits

Author SHA1 Message Date
b6308d2113 v1.3.0
Some checks failed
Default (tags) / security (push) Successful in 35s
Default (tags) / test (push) Failing after 45s
Default (tags) / release (push) Has been skipped
Default (tags) / metadata (push) Has been skipped
2026-01-20 01:12:03 +00:00
e7968a31b1 feat(smartagent): add JsonValidatorTool and support passing base64-encoded images with task runs (vision-capable models); bump @push.rocks/smartai to ^0.12.0 2026-01-20 01:12:03 +00:00
05e4f03061 v1.2.8 2026-01-20 00:38:41 +00:00
37d4069806 feat(streaming): add streaming support to DriverAgent and DualAgentOrchestrator
- Add onToken callback option to IDualAgentOptions interface
- Add onToken property and setOnToken method to DriverAgent
- Wire up streaming in startTask and continueWithMessage methods
- Pass source identifier ('driver'/'guardian') to onToken callback
2026-01-20 00:38:36 +00:00
fe0de36b1a v1.2.7
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 00:13:32 +00:00
e49f35e7de fix(deps(smartai)): bump @push.rocks/smartai to ^0.11.0 2026-01-20 00:13:32 +00:00
4fbffdd97d v1.2.6
Some checks failed
Default (tags) / security (push) Successful in 50s
Default (tags) / test (push) Failing after 1m12s
Default (tags) / release (push) Has been skipped
Default (tags) / metadata (push) Has been skipped
2026-01-20 00:05:42 +00:00
560838477f fix(deps): bump @push.rocks/smartai to ^0.10.1 2026-01-20 00:05:42 +00:00
7503fccbf2 1.2.5
Some checks failed
Default (tags) / security (push) Successful in 52s
Default (tags) / test (push) Failing after 1m8s
Default (tags) / release (push) Has been skipped
Default (tags) / metadata (push) Has been skipped
2025-12-15 15:30:13 +00:00
a76bd0d3e4 update 2025-12-15 15:29:56 +00:00
12 changed files with 403 additions and 51 deletions

View File

@@ -1,5 +1,26 @@
# Changelog
## 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
- Updated dependency @push.rocks/smartai from ^0.8.0 to ^0.10.1 in package.json
- No other code changes; dependency-only update
## 2025-12-15 - 1.1.1 - fix(ci)
Update CI/release config and bump devDependencies; enable verbose tests

View File

@@ -1,6 +1,6 @@
{
"name": "@push.rocks/smartagent",
"version": "1.2.4",
"version": "1.3.0",
"private": false,
"description": "an agentic framework built on top of @push.rocks/smartai",
"main": "dist_ts/index.js",
@@ -21,12 +21,13 @@
"@types/node": "^25.0.2"
},
"dependencies": {
"@push.rocks/smartai": "^0.8.0",
"@push.rocks/smartai": "^0.12.0",
"@push.rocks/smartbrowser": "^2.0.8",
"@push.rocks/smartdeno": "^1.2.0",
"@push.rocks/smartfs": "^1.2.0",
"@push.rocks/smartrequest": "^5.0.1",
"@push.rocks/smartshell": "^3.3.0"
"@push.rocks/smartshell": "^3.3.0",
"minimatch": "^10.1.1"
},
"packageManager": "pnpm@10.18.1+sha512.77a884a165cbba2d8d1c19e3b4880eee6d2fcabd0d879121e282196b80042351d5eb3ca0935fa599da1dc51265cc68816ad2bddd2a2de5ea9fdf92adbec7cd34",
"repository": {

69
pnpm-lock.yaml generated
View File

@@ -9,8 +9,8 @@ importers:
.:
dependencies:
'@push.rocks/smartai':
specifier: ^0.8.0
version: 0.8.0(typescript@5.9.3)(ws@8.18.3)(zod@3.25.76)
specifier: ^0.12.0
version: 0.12.0(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)
@@ -26,6 +26,9 @@ importers:
'@push.rocks/smartshell':
specifier: ^3.3.0
version: 3.3.0
minimatch:
specifier: ^10.1.1
version: 10.1.1
devDependencies:
'@git.zone/tsbuild':
specifier: ^4.0.2
@@ -45,8 +48,8 @@ importers:
packages:
'@anthropic-ai/sdk@0.65.0':
resolution: {integrity: sha512-zIdPOcrCVEI8t3Di40nH4z9EoeyGZfXbYSvWdDLsB/KkaSYMnEgC7gmcgWu83g2NTn1ZTpbMvpdttWDGGIk6zw==}
'@anthropic-ai/sdk@0.71.2':
resolution: {integrity: sha512-TGNDEUuEstk/DKu0/TflXAEt+p+p/WhTlFzEnoosvbaDU2LTjm42igSdlL0VijrKpWejtOKxX0b8A7uc+XiSAQ==}
hasBin: true
peerDependencies:
zod: ^3.25.0 || ^4.0.0
@@ -717,6 +720,9 @@ packages:
'@lit/reactive-element@2.1.1':
resolution: {integrity: sha512-N+dm5PAYdQ8e6UlywyyrgI2t++wFGXfHx+dSJ1oBrg6FAxUj40jId++EaRm80MKX5JnlH1sBsyZ5h0bcZKemCg==}
'@mistralai/mistralai@1.12.0':
resolution: {integrity: sha512-oDr1hcS3wsIT/QupBG93TNiA5kilwBYoAIyl5BNYqMM2Ix/xsNq+wT8b++uhp/GTUMx44n+8Bn1mkATbwxe6bQ==}
'@mixmark-io/domino@2.2.0':
resolution: {integrity: sha512-Y28PR25bHXUg88kCV7nivXrP2Nj2RueZ3/l/jdx6J9f8J4nsEGcgX0Qe6lt7Pa+J79+kPiJU3LguR6O/6zrLOw==}
@@ -831,8 +837,8 @@ packages:
'@push.rocks/qenv@6.1.3':
resolution: {integrity: sha512-+z2hsAU/7CIgpYLFqvda8cn9rUBMHqLdQLjsFfRn5jPoD7dJ5rFlpkbhfM4Ws8mHMniwWaxGKo+q/YBhtzRBLg==}
'@push.rocks/smartai@0.8.0':
resolution: {integrity: sha512-guzi28meUDc3mydC8kpoA+4pzExRQqygXYFDD4qQSWPpIRHQ7qhpeNqJzrrGezT1yOH5Gb9taPEGwT56hI+nwQ==}
'@push.rocks/smartai@0.12.0':
resolution: {integrity: sha512-T4HRaSSxO6TQGGXlQeswX2eYkB+gMu0FbKF9qCUri6FdRlYzmPDn19jgPrPJxyg5m3oj6TzflvfYwcBCFlWo/A==}
'@push.rocks/smartarchive@4.2.4':
resolution: {integrity: sha512-uiqVAXPxmr8G5rv3uZvZFMOCt8l7cZC3nzvsy4YQqKf/VkPhKIEX+b7LkAeNlxPSYUiBQUkNRoawg9+5BaMcHg==}
@@ -916,6 +922,9 @@ packages:
'@push.rocks/smartfs@1.2.0':
resolution: {integrity: sha512-1R47jJZwX869z7DYgKeAZKTU1SbGnM7W/ZmgsI7AkQQhiascNqY3/gF4V5kIprmuf1WhpRbCbZyum8s7J1LDdg==}
'@push.rocks/smartfs@1.3.1':
resolution: {integrity: sha512-ZSduVS8tM+/erbyCTvRRvc9gLWwbpqN5xdIIkMr+gub7fowSeJb7tR2rnGwySa63DyimU0q2KTp79VV9YqGLeg==}
'@push.rocks/smartguard@3.1.0':
resolution: {integrity: sha512-J23q84f1O+TwFGmd4lrO9XLHUh2DaLXo9PN/9VmTWYzTkQDv5JehmifXVI0esophXcCIfbdIu6hbt7/aHlDF4A==}
@@ -2972,12 +2981,12 @@ packages:
resolution: {integrity: sha512-7x81NCL719oNbsq/3mh+hVrAWmFuEYUqrq/Iw3kUzH8ReypT9QQ0BLoJS7/G9k6N81XjW4qHWtjWwe/9eLy1EQ==}
engines: {node: '>=12'}
openai@5.23.2:
resolution: {integrity: sha512-MQBzmTulj+MM5O8SKEk/gL8a7s5mktS9zUtAkU257WjvobGc9nKcBuVwjyEEcb9SI8a8Y2G/mzn3vm9n1Jlleg==}
openai@6.16.0:
resolution: {integrity: sha512-fZ1uBqjFUjXzbGc35fFtYKEOxd20kd9fDpFeqWtsOZWiubY8CZ1NAlXHW3iathaFvqmNtCWMIsosCuyeI7Joxg==}
hasBin: true
peerDependencies:
ws: ^8.18.0
zod: ^3.23.8
zod: ^3.25 || ^4.0
peerDependenciesMeta:
ws:
optional: true
@@ -3293,8 +3302,9 @@ packages:
safer-buffer@2.1.2:
resolution: {integrity: sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==}
sax@1.4.3:
resolution: {integrity: sha512-yqYn1JhPczigF94DMS+shiDMjDowYO6y9+wB/4WgO0Y19jWYk0lQ4tuG5KI7kj4FTp1wxPj5IFfcrz/s1c3jjQ==}
sax@1.4.4:
resolution: {integrity: sha512-1n3r/tGXO6b6VXMdFT54SHzT9ytu9yr7TaELowdYpMqY/Ao7EnlQGmAQ1+RatX7Tkkdm6hONI2owqNx2aZj5Sw==}
engines: {node: '>=11.0.0'}
semver@6.3.1:
resolution: {integrity: sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==}
@@ -3732,6 +3742,11 @@ packages:
resolution: {integrity: sha512-Ow9nuGZE+qp1u4JIPvg+uCiUr7xGQWdff7JQSk5VGYTAZMDe2q8lxJ10ygv10qmSj031Ty/6FNJpLO4o1Sgc+w==}
engines: {node: '>=12'}
zod-to-json-schema@3.25.1:
resolution: {integrity: sha512-pM/SU9d3YAggzi6MtR4h7ruuQlqKtad8e9S0fmxcMi+ueAK5Korys/aWcV9LIIHTVbj01NdzxcnXSN+O74ZIVA==}
peerDependencies:
zod: ^3.25 || ^4
zod@3.25.76:
resolution: {integrity: sha512-gzUt/qt81nXsFGKIFcC3YnfEAx5NkunCfnDlvuBSSFS02bcXu4Lmea0AFIUwbLWxWPx3d9p8S5QoaujKcNQxcQ==}
@@ -3740,7 +3755,7 @@ packages:
snapshots:
'@anthropic-ai/sdk@0.65.0(zod@3.25.76)':
'@anthropic-ai/sdk@0.71.2(zod@3.25.76)':
dependencies:
json-schema-to-ts: 3.1.1
optionalDependencies:
@@ -4891,6 +4906,11 @@ snapshots:
dependencies:
'@lit-labs/ssr-dom-shim': 1.4.0
'@mistralai/mistralai@1.12.0':
dependencies:
zod: 3.25.76
zod-to-json-schema: 3.25.1(zod@3.25.76)
'@mixmark-io/domino@2.2.0': {}
'@module-federation/error-codes@0.21.6': {}
@@ -5138,17 +5158,18 @@ snapshots:
'@push.rocks/smartlog': 3.1.10
'@push.rocks/smartpath': 6.0.0
'@push.rocks/smartai@0.8.0(typescript@5.9.3)(ws@8.18.3)(zod@3.25.76)':
'@push.rocks/smartai@0.12.0(typescript@5.9.3)(ws@8.18.3)(zod@3.25.76)':
dependencies:
'@anthropic-ai/sdk': 0.65.0(zod@3.25.76)
'@anthropic-ai/sdk': 0.71.2(zod@3.25.76)
'@mistralai/mistralai': 1.12.0
'@push.rocks/smartarray': 1.1.0
'@push.rocks/smartfile': 11.2.7
'@push.rocks/smartfs': 1.3.1
'@push.rocks/smartpath': 6.0.0
'@push.rocks/smartpdf': 4.1.1(typescript@5.9.3)
'@push.rocks/smartpromise': 4.2.3
'@push.rocks/smartrequest': 4.4.2
'@push.rocks/smartrequest': 5.0.1
'@push.rocks/webstream': 1.0.10
openai: 5.23.2(ws@8.18.3)(zod@3.25.76)
openai: 6.16.0(ws@8.18.3)(zod@3.25.76)
transitivePeerDependencies:
- '@nuxt/kit'
- aws-crt
@@ -5444,6 +5465,10 @@ snapshots:
dependencies:
'@push.rocks/smartpath': 6.0.0
'@push.rocks/smartfs@1.3.1':
dependencies:
'@push.rocks/smartpath': 6.0.0
'@push.rocks/smartguard@3.1.0':
dependencies:
'@push.rocks/smartpromise': 4.2.3
@@ -8151,7 +8176,7 @@ snapshots:
is-docker: 2.2.1
is-wsl: 2.2.0
openai@5.23.2(ws@8.18.3)(zod@3.25.76):
openai@6.16.0(ws@8.18.3)(zod@3.25.76):
optionalDependencies:
ws: 8.18.3
zod: 3.25.76
@@ -8522,7 +8547,7 @@ snapshots:
safer-buffer@2.1.2: {}
sax@1.4.3: {}
sax@1.4.4: {}
semver@6.3.1: {}
@@ -8999,7 +9024,7 @@ snapshots:
xml2js@0.5.0:
dependencies:
sax: 1.4.3
sax: 1.4.4
xmlbuilder: 11.0.1
xmlbuilder@11.0.1: {}
@@ -9034,6 +9059,10 @@ snapshots:
buffer-crc32: 0.2.13
pend: 1.2.0
zod-to-json-schema@3.25.1(zod@3.25.76):
dependencies:
zod: 3.25.76
zod@3.25.76: {}
zwitch@2.0.4: {}

View File

@@ -483,7 +483,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.

View File

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

View File

@@ -16,6 +16,7 @@ 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 all interfaces
export * from './smartagent.interfaces.js';

View File

@@ -3,6 +3,11 @@ import * as path from 'path';
export { path };
// third party
import { minimatch } from 'minimatch';
export { minimatch };
// @push.rocks scope
import * as smartai from '@push.rocks/smartai';
import * as smartdeno from '@push.rocks/smartdeno';

View File

@@ -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,7 @@ export class DriverAgent {
private maxHistoryMessages: number;
private messageHistory: plugins.smartai.ChatMessage[] = [];
private tools: Map<string, BaseToolWrapper> = new Map();
private onToken?: (token: string) => void;
constructor(
provider: plugins.smartai.MultiModalModel,
@@ -36,9 +39,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 +67,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 +99,34 @@ 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)
const historyMessage: plugins.smartai.ChatMessage = {
role: 'assistant',
content: response.message,
});
};
this.messageHistory.push(historyMessage);
return {
role: 'assistant',
@@ -139,11 +169,25 @@ 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;
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
this.messageHistory.push({

View File

@@ -158,9 +158,10 @@ export class DualAgentOrchestrator {
/**
* Register a scoped filesystem tool that can only access files within the specified directory
* @param basePath The directory to scope filesystem operations to
* @param excludePatterns Optional glob patterns to exclude from listings (e.g., ['.nogit/**', 'node_modules/**'])
*/
public registerScopedFilesystemTool(basePath: string): void {
const scopedTool = new FilesystemTool({ basePath });
public registerScopedFilesystemTool(basePath: string, excludePatterns?: string[]): void {
const scopedTool = new FilesystemTool({ basePath, excludePatterns });
this.registerTool(scopedTool);
}
@@ -180,9 +181,15 @@ 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);
@@ -227,8 +234,10 @@ 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.');
}
@@ -239,14 +248,17 @@ export class DualAgentOrchestrator {
let completed = false;
let finalResult: string | null = null;
// 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 = await this.driver.startTask(task, images);
this.conversationHistory.push(driverResponse);
// Emit task started event

View File

@@ -1,5 +1,17 @@
import * as plugins from './plugins.js';
// ================================
// 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 +46,8 @@ 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;
}
// ================================

View File

@@ -8,6 +8,8 @@ import { BaseToolWrapper } from './smartagent.tools.base.js';
export interface IFilesystemToolOptions {
/** Base path to scope all operations to. If set, all paths must be within this directory. */
basePath?: string;
/** Glob patterns to exclude from listings (e.g., ['.nogit/**', 'node_modules/**']) */
excludePatterns?: string[];
}
/**
@@ -20,12 +22,25 @@ export class FilesystemTool extends BaseToolWrapper {
/** Base path to scope all operations to */
private basePath?: string;
/** Glob patterns to exclude from listings */
private excludePatterns: string[];
constructor(options?: IFilesystemToolOptions) {
super();
if (options?.basePath) {
this.basePath = plugins.path.resolve(options.basePath);
}
this.excludePatterns = options?.excludePatterns || [];
}
/**
* Check if a relative path should be excluded based on exclude patterns
*/
private isExcluded(relativePath: string): boolean {
if (this.excludePatterns.length === 0) return false;
return this.excludePatterns.some(pattern =>
plugins.minimatch(relativePath, pattern, { dot: true })
);
}
/**
@@ -361,7 +376,16 @@ export class FilesystemTool extends BaseToolWrapper {
if (params.filter) {
dir = dir.filter(params.filter as string);
}
const entries = await dir.list();
let entries = await dir.list();
// Filter out excluded paths
if (this.excludePatterns.length > 0) {
entries = entries.filter(entry => {
const relativePath = plugins.path.relative(validatedPath, entry.path);
return !this.isExcluded(relativePath) && !this.isExcluded(entry.name);
});
}
return {
success: true,
result: {
@@ -499,6 +523,11 @@ export class FilesystemTool extends BaseToolWrapper {
const itemRelPath = relativePath ? `${relativePath}/${item.name}` : item.name;
const isDir = item.isDirectory;
// Skip excluded paths
if (this.isExcluded(itemRelPath) || this.isExcluded(item.name)) {
continue;
}
const entry: ITreeEntry = {
path: itemPath,
relativePath: itemRelPath,
@@ -603,11 +632,14 @@ export class FilesystemTool extends BaseToolWrapper {
const matches = await dir.list();
// Return file paths relative to base path for readability
const files = matches.map((entry) => ({
path: entry.path,
relativePath: plugins.path.relative(basePath, entry.path),
isDirectory: entry.isDirectory,
}));
// Filter out excluded paths
const files = matches
.map((entry) => ({
path: entry.path,
relativePath: plugins.path.relative(basePath, entry.path),
isDirectory: entry.isDirectory,
}))
.filter((file) => !this.isExcluded(file.relativePath));
return {
success: true,

193
ts/smartagent.tools.json.ts Normal file
View File

@@ -0,0 +1,193 @@
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}`,
};
}
}
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}`;
}
}
}