29 Commits

Author SHA1 Message Date
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
8bcf3257e2 v1.5.2
Some checks failed
Default (tags) / security (push) Successful in 33s
Default (tags) / test (push) Failing after 35s
Default (tags) / release (push) Has been skipped
Default (tags) / metadata (push) Has been skipped
2026-01-20 02:54:58 +00:00
6753553394 fix(): no changes in this diff; nothing to release 2026-01-20 02:54:58 +00:00
a46dbd0da6 fix(driveragent): enable streaming for native tool calling methods 2026-01-20 02:54:45 +00:00
7379daf4c5 v1.5.1
Some checks failed
Default (tags) / security (push) Successful in 37s
Default (tags) / test (push) Failing after 35s
Default (tags) / release (push) Has been skipped
Default (tags) / metadata (push) Has been skipped
2026-01-20 02:45:41 +00:00
83422b4b0e fix(smartagent): bump patch version to 1.5.1 (no changes in diff) 2026-01-20 02:45:41 +00:00
4310c8086b feat(native-tools): add native tool calling support for Ollama models
- Add INativeToolCall interface for native tool call format
- Add useNativeToolCalling option to IDualAgentOptions
- Add getToolsAsJsonSchema() to convert tools to Ollama JSON Schema format
- Add parseNativeToolCalls() to convert native tool calls to proposals
- Add startTaskWithNativeTools() and continueWithNativeTools() to DriverAgent
- Update DualAgentOrchestrator to support both XML parsing and native tool calling modes

Native tool calling is more efficient for models like GPT-OSS that use Harmony format,
as it activates Ollama's built-in tool parser instead of requiring XML generation.
2026-01-20 02:44:54 +00:00
472a8ed7f8 v1.5.0
Some checks failed
Default (tags) / security (push) Successful in 37s
Default (tags) / test (push) Failing after 35s
Default (tags) / release (push) Has been skipped
Default (tags) / metadata (push) Has been skipped
2026-01-20 02:05:12 +00:00
44137a8710 feat(driveragent): preserve assistant reasoning in message history and update @push.rocks/smartai dependency to ^0.13.0 2026-01-20 02:05:12 +00:00
c12a6a7be9 v1.4.2
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 01:41:18 +00:00
49dcc7a1a1 fix(repo): no changes detected in diff 2026-01-20 01:41:18 +00:00
e649e9caab fix(driver): make tool call format instructions explicit about literal XML output
The system message now clearly states that the <tool_call> XML tags MUST
be literally written in the response, not just described. Includes examples
of CORRECT vs WRONG usage to help smaller models understand.
2026-01-20 01:40:57 +00:00
c39e7e76b8 v1.4.1
Some checks failed
Default (tags) / security (push) Successful in 36s
Default (tags) / test (push) Failing after 36s
Default (tags) / release (push) Has been skipped
Default (tags) / metadata (push) Has been skipped
2026-01-20 01:36:30 +00:00
c24a4306d9 fix(): no changes detected (empty diff) 2026-01-20 01:36:30 +00:00
9718048dff fix(dualagent): improve no-tool-call feedback with explicit XML format reminder
When the LLM fails to emit a tool_call XML block, the feedback now includes
the exact XML format expected with a concrete example for json.validate.
This helps smaller models understand the exact output format required.
2026-01-20 01:36:03 +00:00
b1deccaa26 v1.4.0
Some checks failed
Default (tags) / security (push) Successful in 32s
Default (tags) / test (push) Failing after 36s
Default (tags) / release (push) Has been skipped
Default (tags) / metadata (push) Has been skipped
2026-01-20 01:30:26 +00:00
52d1d128c7 feat(docs): document Dual-Agent Driver/Guardian architecture, new standard tools, streaming/vision support, progress events, and updated API/export docs 2026-01-20 01:30:26 +00:00
60f8bbe1b6 feat(tools): add getToolExplanation() method with XML examples for LLM tool calling
Each tool now provides comprehensive documentation including parameter
schemas and concrete <tool_call> XML examples. This helps smaller LLMs
understand the exact format required for tool invocation.
2026-01-20 01:30:03 +00:00
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
18 changed files with 1458 additions and 104 deletions

View File

@@ -1,5 +1,78 @@
# Changelog
## 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
- 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.5.3",
"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.13.1",
"@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": {

87
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.13.1
version: 0.13.1(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
@@ -240,6 +243,10 @@ packages:
resolution: {integrity: sha512-Q/N6JNWvIvPnLDvjlE1OUBLPQHH6l3CltCEsHIujp45zQUSSh8K+gHnaEX45yAT1nyngnINhvWtzN+Nb9D8RAQ==}
engines: {node: '>=6.9.0'}
'@babel/runtime@7.28.6':
resolution: {integrity: sha512-05WQkdpL9COIMz4LjTxGpPNCdlpyimKppYNoJ5Di5EUObifl8t4tuLuUBBZEpoLYOmfvIWrsp9fCl0HoPRVTdA==}
engines: {node: '>=6.9.0'}
'@borewit/text-codec@0.1.1':
resolution: {integrity: sha512-5L/uBxmjaCIX5h8Z+uu+kA9BQLkc/Wl06UGR5ajNRxu+/XjonB5i8JpgFMrPj3LXTCPA0pv8yxUvbUi+QthGGA==}
@@ -264,6 +271,9 @@ packages:
'@emnapi/runtime@1.7.1':
resolution: {integrity: sha512-PVtJr5CmLwYAU9PZDMITZoR5iAOShYREoR45EyyLrbntV50mdePTgUn4AmOw90Ifcj+x2kRjdzr1HP3RrNiHGA==}
'@emnapi/runtime@1.8.1':
resolution: {integrity: sha512-mehfKSMWjjNol8659Z8KxEMrdSJDDot5SXMq00dM8BN4o+CLNXQ0xH2V7EchNHV4RmbZLmmPdEaXZc5H2FXmDg==}
'@emnapi/wasi-threads@1.1.0':
resolution: {integrity: sha512-WI0DdZ8xFSbgMjR1sFsKABJ/C5OnRrjT06JXbZKexJGrDuPTzZdDYfFlsgcCXCyf+suG5QU2e/y1Wo2V/OapLQ==}
@@ -717,6 +727,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 +844,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.13.1':
resolution: {integrity: sha512-V9J6a+rjBkFpdFnC6OBm8CbEtqCfJnEsUmNKfRUOiTa+VIVtD4OOceraZah6kGHWltUhZ1XV4eLWwFf4+YO3NA==}
'@push.rocks/smartarchive@4.2.4':
resolution: {integrity: sha512-uiqVAXPxmr8G5rv3uZvZFMOCt8l7cZC3nzvsy4YQqKf/VkPhKIEX+b7LkAeNlxPSYUiBQUkNRoawg9+5BaMcHg==}
@@ -916,6 +929,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 +2988,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 +3309,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 +3749,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 +3762,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:
@@ -4316,6 +4338,8 @@ snapshots:
'@babel/runtime@7.28.4': {}
'@babel/runtime@7.28.6': {}
'@borewit/text-codec@0.1.1': {}
'@cloudflare/workers-types@4.20251202.0': {}
@@ -4380,6 +4404,11 @@ snapshots:
tslib: 2.8.1
optional: true
'@emnapi/runtime@1.8.1':
dependencies:
tslib: 2.8.1
optional: true
'@emnapi/wasi-threads@1.1.0':
dependencies:
tslib: 2.8.1
@@ -4667,7 +4696,7 @@ snapshots:
'@img/sharp-wasm32@0.34.5':
dependencies:
'@emnapi/runtime': 1.7.1
'@emnapi/runtime': 1.8.1
optional: true
'@img/sharp-win32-arm64@0.34.5':
@@ -4891,6 +4920,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 +5172,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.13.1(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 +5479,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
@@ -7563,7 +7602,7 @@ snapshots:
json-schema-to-ts@3.1.1:
dependencies:
'@babel/runtime': 7.28.4
'@babel/runtime': 7.28.6
ts-algebra: 2.0.0
jsonfile@6.2.0:
@@ -8151,7 +8190,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 +8561,7 @@ snapshots:
safer-buffer@2.1.2: {}
sax@1.4.3: {}
sax@1.4.4: {}
semver@6.3.1: {}
@@ -8999,7 +9038,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 +9073,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

@@ -1,15 +1,39 @@
# 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
- **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
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
## 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
## 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`

152
readme.md
View File

@@ -50,6 +50,7 @@ flowchart TB
Shell["Shell"]
Browser["Browser"]
Deno["Deno"]
JSON["JSON Validator"]
end
Task --> Orchestrator
@@ -99,7 +100,7 @@ await orchestrator.stop();
## Standard Tools
SmartAgent comes with five battle-tested tools out of the box:
SmartAgent comes with six battle-tested tools out of the box:
### 🗂️ FilesystemTool
@@ -117,11 +118,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 +231,105 @@ By default, code runs **fully sandboxed with no permissions**. Permissions must
</tool_call>
```
### 📋 JsonValidatorTool
Validate and format JSON data. Perfect for agents to self-check their JSON output before completing tasks.
**Actions**: `validate`, `format`
```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>
```
## 🎥 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`
## Guardian Policy Examples
The Guardian's power comes from your policy. Here are battle-tested examples:
@@ -294,10 +412,19 @@ 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
// 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 +438,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 +496,7 @@ class MyCustomTool extends BaseToolWrapper {
return {
success: true,
result: { processed: params.input },
summary: `Processed input: ${params.input}`, // Optional human-readable summary
};
}
@@ -439,11 +571,11 @@ 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 |
| `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 |
| `registerScopedFilesystemTool(basePath)` | Register filesystem tool with path restriction |
| `registerScopedFilesystemTool(basePath, excludePatterns?)` | Register filesystem tool with path restriction |
| `setGuardianPolicy(policy)` | Update Guardian policy at runtime |
| `getHistory()` | Get conversation history |
| `getToolNames()` | Get list of registered tool names |
@@ -459,14 +591,18 @@ export { GuardianAgent } 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';
// Types and interfaces
export * from '@push.rocks/smartagent'; // All interfaces
export * from '@push.rocks/smartagent'; // All interfaces
// Re-exported from @push.rocks/smartai
export { type ISmartAiOptions, type TProvider, type ChatMessage, type ChatOptions, type ChatResponse };
```
## License and Legal Information

View File

@@ -3,6 +3,6 @@
*/
export const commitinfo = {
name: '@push.rocks/smartagent',
version: '1.1.1',
version: '1.5.3',
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,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 +170,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 +377,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 +442,260 @@ 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) {
this.onToken(`[THINKING] ${chunk.thinking}`);
}
if (chunk.content && this.onToken) {
this.onToken(chunk.content);
}
} : undefined
);
// Add assistant response to history
const historyMessage: plugins.smartai.ChatMessage = {
role: 'assistant',
content: response.message || '',
reasoning: response.thinking || response.reasoning,
};
this.messageHistory.push(historyMessage);
// 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)
* @returns Response with content, reasoning, and any tool calls
*/
public async continueWithNativeTools(
message: string
): Promise<{ message: interfaces.IAgentMessage; toolCalls?: interfaces.INativeToolCall[] }> {
// Add the new message to history
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
let historyForChat: plugins.smartai.ChatMessage[];
const fullHistory = this.messageHistory.slice(0, -1);
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.');
}
// Use collectStreamResponse for streaming support with tools
const response = await provider.collectStreamResponse(
{
systemMessage: fullSystemMessage,
userMessage: message,
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) {
this.onToken(`[THINKING] ${chunk.thinking}`);
}
if (chunk.content && this.onToken) {
this.onToken(chunk.content);
}
} : undefined
);
// Add assistant response to history
this.messageHistory.push({
role: 'assistant',
content: response.message || '',
reasoning: response.thinking || response.reasoning,
});
// 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,
};
});
}
}

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,26 +234,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
@@ -269,10 +297,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({
@@ -303,16 +337,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)
@@ -419,13 +493,28 @@ export class DualAgentOrchestrator {
toolResult: result,
});
driverResponse = await this.driver.continueWithMessage(resultMessage);
// Continue with appropriate method based on mode
if (useNativeTools) {
const continueResult = await this.driver.continueWithNativeTools(resultMessage);
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 continueResult = await this.driver.continueWithNativeTools(
`TOOL ERROR: ${errorMessage}\n\nPlease try a different approach.`
);
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 {
@@ -462,7 +551,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);
}
}

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,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 +89,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
*/

View File

@@ -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();
}
/**

View File

@@ -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':

View File

@@ -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[]) || [];

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,
@@ -634,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': {

View File

@@ -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
View 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}`;
}
}
}

View File

@@ -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': {