@push.rocks/smartagent 1.8.0 → 3.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (87) hide show
  1. package/dist_ts/00_commitinfo_data.js +3 -3
  2. package/dist_ts/index.d.ts +8 -14
  3. package/dist_ts/index.js +8 -24
  4. package/dist_ts/plugins.d.ts +8 -9
  5. package/dist_ts/plugins.js +10 -12
  6. package/dist_ts/smartagent.classes.agent.d.ts +2 -0
  7. package/dist_ts/smartagent.classes.agent.js +173 -0
  8. package/dist_ts/smartagent.classes.toolregistry.d.ts +7 -70
  9. package/dist_ts/smartagent.classes.toolregistry.js +11 -155
  10. package/dist_ts/smartagent.interfaces.d.ts +47 -283
  11. package/dist_ts/smartagent.interfaces.js +6 -7
  12. package/dist_ts/smartagent.utils.truncation.d.ts +10 -0
  13. package/dist_ts/smartagent.utils.truncation.js +26 -0
  14. package/dist_ts_compaction/index.d.ts +1 -0
  15. package/dist_ts_compaction/index.js +2 -0
  16. package/dist_ts_compaction/plugins.d.ts +4 -0
  17. package/dist_ts_compaction/plugins.js +3 -0
  18. package/dist_ts_compaction/smartagent.compaction.d.ts +10 -0
  19. package/dist_ts_compaction/smartagent.compaction.js +46 -0
  20. package/dist_ts_tools/index.d.ts +8 -0
  21. package/dist_ts_tools/index.js +6 -0
  22. package/dist_ts_tools/plugins.d.ts +15 -0
  23. package/dist_ts_tools/plugins.js +19 -0
  24. package/dist_ts_tools/tool.filesystem.d.ts +6 -0
  25. package/dist_ts_tools/tool.filesystem.js +102 -0
  26. package/dist_ts_tools/tool.http.d.ts +2 -0
  27. package/dist_ts_tools/tool.http.js +65 -0
  28. package/dist_ts_tools/tool.json.d.ts +2 -0
  29. package/dist_ts_tools/tool.json.js +47 -0
  30. package/dist_ts_tools/tool.shell.d.ts +8 -0
  31. package/dist_ts_tools/tool.shell.js +40 -0
  32. package/npmextra.json +1 -1
  33. package/package.json +30 -18
  34. package/readme.hints.md +38 -84
  35. package/readme.md +254 -682
  36. package/ts/00_commitinfo_data.ts +2 -2
  37. package/ts/index.ts +10 -37
  38. package/ts/plugins.ts +22 -21
  39. package/ts/smartagent.classes.agent.ts +198 -0
  40. package/ts/smartagent.classes.toolregistry.ts +11 -179
  41. package/ts/smartagent.interfaces.ts +51 -363
  42. package/ts/smartagent.utils.truncation.ts +39 -0
  43. package/ts_compaction/index.ts +1 -0
  44. package/ts_compaction/plugins.ts +6 -0
  45. package/ts_compaction/smartagent.compaction.ts +51 -0
  46. package/ts_tools/index.ts +8 -0
  47. package/ts_tools/plugins.ts +30 -0
  48. package/ts_tools/tool.filesystem.ts +131 -0
  49. package/ts_tools/tool.http.ts +78 -0
  50. package/ts_tools/tool.json.ts +53 -0
  51. package/ts_tools/tool.shell.ts +62 -0
  52. package/dist_ts/smartagent.classes.driveragent.d.ts +0 -134
  53. package/dist_ts/smartagent.classes.driveragent.js +0 -671
  54. package/dist_ts/smartagent.classes.dualagent.d.ts +0 -93
  55. package/dist_ts/smartagent.classes.dualagent.js +0 -614
  56. package/dist_ts/smartagent.classes.guardianagent.d.ts +0 -46
  57. package/dist_ts/smartagent.classes.guardianagent.js +0 -201
  58. package/dist_ts/smartagent.tools.base.d.ts +0 -52
  59. package/dist_ts/smartagent.tools.base.js +0 -42
  60. package/dist_ts/smartagent.tools.browser.d.ts +0 -17
  61. package/dist_ts/smartagent.tools.browser.js +0 -229
  62. package/dist_ts/smartagent.tools.deno.d.ts +0 -21
  63. package/dist_ts/smartagent.tools.deno.js +0 -191
  64. package/dist_ts/smartagent.tools.expert.d.ts +0 -27
  65. package/dist_ts/smartagent.tools.expert.js +0 -126
  66. package/dist_ts/smartagent.tools.filesystem.d.ts +0 -40
  67. package/dist_ts/smartagent.tools.filesystem.js +0 -801
  68. package/dist_ts/smartagent.tools.http.d.ts +0 -16
  69. package/dist_ts/smartagent.tools.http.js +0 -264
  70. package/dist_ts/smartagent.tools.json.d.ts +0 -24
  71. package/dist_ts/smartagent.tools.json.js +0 -202
  72. package/dist_ts/smartagent.tools.search.d.ts +0 -29
  73. package/dist_ts/smartagent.tools.search.js +0 -215
  74. package/dist_ts/smartagent.tools.shell.d.ts +0 -17
  75. package/dist_ts/smartagent.tools.shell.js +0 -202
  76. package/ts/smartagent.classes.driveragent.ts +0 -775
  77. package/ts/smartagent.classes.dualagent.ts +0 -692
  78. package/ts/smartagent.classes.guardianagent.ts +0 -241
  79. package/ts/smartagent.tools.base.ts +0 -83
  80. package/ts/smartagent.tools.browser.ts +0 -253
  81. package/ts/smartagent.tools.deno.ts +0 -230
  82. package/ts/smartagent.tools.expert.ts +0 -144
  83. package/ts/smartagent.tools.filesystem.ts +0 -885
  84. package/ts/smartagent.tools.http.ts +0 -283
  85. package/ts/smartagent.tools.json.ts +0 -224
  86. package/ts/smartagent.tools.search.ts +0 -237
  87. package/ts/smartagent.tools.shell.ts +0 -230
@@ -3,6 +3,6 @@
3
3
  */
4
4
  export const commitinfo = {
5
5
  name: '@push.rocks/smartagent',
6
- version: '1.8.0',
7
- description: 'an agentic framework built on top of @push.rocks/smartai'
6
+ version: '3.0.1',
7
+ description: 'Agentic loop for ai-sdk (Vercel AI SDK). Wraps streamText with stopWhen for parallel multi-step tool execution. Built on @push.rocks/smartai.'
8
8
  }
package/ts/index.ts CHANGED
@@ -1,38 +1,11 @@
1
- import * as plugins from './plugins.js';
2
-
3
- // Export the dual-agent orchestrator (main entry point)
4
- export { DualAgentOrchestrator } from './smartagent.classes.dualagent.js';
5
-
6
- // Export individual agents
7
- export { DriverAgent } from './smartagent.classes.driveragent.js';
8
- export { GuardianAgent } from './smartagent.classes.guardianagent.js';
9
-
10
- // Export tool registry and related classes
1
+ export { runAgent } from './smartagent.classes.agent.js';
11
2
  export { ToolRegistry } from './smartagent.classes.toolregistry.js';
12
-
13
- // Export base tool class for custom tool creation
14
- export { BaseToolWrapper } from './smartagent.tools.base.js';
15
-
16
- // Export standard tools
17
- export { FilesystemTool, type IFilesystemToolOptions } from './smartagent.tools.filesystem.js';
18
- export { HttpTool } from './smartagent.tools.http.js';
19
- export { ShellTool } from './smartagent.tools.shell.js';
20
- export { BrowserTool } from './smartagent.tools.browser.js';
21
- export { DenoTool, type TDenoPermission } from './smartagent.tools.deno.js';
22
- export { JsonValidatorTool } from './smartagent.tools.json.js';
23
-
24
- // Export tool search and expert tools
25
- export { ToolSearchTool } from './smartagent.tools.search.js';
26
- export { ExpertTool } from './smartagent.tools.expert.js';
27
-
28
- // Export all interfaces
29
- export * from './smartagent.interfaces.js';
30
-
31
- // Re-export useful types from smartai
32
- export {
33
- type ISmartAiOptions,
34
- type TProvider,
35
- type ChatMessage,
36
- type ChatOptions,
37
- type ChatResponse,
38
- } from '@push.rocks/smartai';
3
+ export { truncateOutput } from './smartagent.utils.truncation.js';
4
+ export type { ITruncateResult } from './smartagent.utils.truncation.js';
5
+ export { ContextOverflowError } from './smartagent.interfaces.js';
6
+ export type { IAgentRunOptions, IAgentRunResult } from './smartagent.interfaces.js';
7
+
8
+ // Re-export tool() and z so consumers can define tools without extra imports
9
+ export { tool, jsonSchema } from '@push.rocks/smartai';
10
+ export { z } from 'zod';
11
+ export { stepCountIs } from 'ai';
package/ts/plugins.ts CHANGED
@@ -3,24 +3,25 @@ import * as path from 'path';
3
3
 
4
4
  export { path };
5
5
 
6
- // third party
7
- import { minimatch } from 'minimatch';
8
-
9
- export { minimatch };
10
-
11
- // @push.rocks scope
12
- import * as smartai from '@push.rocks/smartai';
13
- import * as smartdeno from '@push.rocks/smartdeno';
14
- import * as smartfs from '@push.rocks/smartfs';
15
- import * as smartrequest from '@push.rocks/smartrequest';
16
- import * as smartbrowser from '@push.rocks/smartbrowser';
17
- import * as smartshell from '@push.rocks/smartshell';
18
-
19
- export {
20
- smartai,
21
- smartdeno,
22
- smartfs,
23
- smartrequest,
24
- smartbrowser,
25
- smartshell,
26
- };
6
+ // ai-sdk core
7
+ import { streamText, generateText, stepCountIs } from 'ai';
8
+
9
+ export { streamText, generateText, stepCountIs };
10
+
11
+ export type {
12
+ ModelMessage,
13
+ ToolSet,
14
+ StreamTextResult,
15
+ } from 'ai';
16
+
17
+ // @push.rocks/smartai
18
+ import { tool, jsonSchema } from '@push.rocks/smartai';
19
+
20
+ export { tool, jsonSchema };
21
+
22
+ export type { LanguageModelV3 } from '@push.rocks/smartai';
23
+
24
+ // zod
25
+ import { z } from 'zod';
26
+
27
+ export { z };
@@ -0,0 +1,198 @@
1
+ // Retry backoff and context overflow logic derived from opencode (MIT) — https://github.com/sst/opencode
2
+
3
+ import * as plugins from './plugins.js';
4
+ import type { IAgentRunOptions, IAgentRunResult } from './smartagent.interfaces.js';
5
+ import { ContextOverflowError } from './smartagent.interfaces.js';
6
+
7
+ // Retry constants
8
+ const RETRY_INITIAL_DELAY = 2000;
9
+ const RETRY_BACKOFF_FACTOR = 2;
10
+ const RETRY_MAX_DELAY = 30_000;
11
+ const MAX_RETRY_ATTEMPTS = 8;
12
+
13
+ function retryDelay(attempt: number, headers?: Record<string, string>): number {
14
+ if (headers) {
15
+ const ms = headers['retry-after-ms'];
16
+ if (ms) {
17
+ const n = parseFloat(ms);
18
+ if (!isNaN(n)) return n;
19
+ }
20
+ const after = headers['retry-after'];
21
+ if (after) {
22
+ const secs = parseFloat(after);
23
+ if (!isNaN(secs)) return Math.ceil(secs * 1000);
24
+ const date = Date.parse(after) - Date.now();
25
+ if (!isNaN(date) && date > 0) return Math.ceil(date);
26
+ }
27
+ }
28
+ return Math.min(
29
+ RETRY_INITIAL_DELAY * Math.pow(RETRY_BACKOFF_FACTOR, attempt - 1),
30
+ RETRY_MAX_DELAY,
31
+ );
32
+ }
33
+
34
+ async function sleep(ms: number, signal?: AbortSignal): Promise<void> {
35
+ return new Promise((resolve, reject) => {
36
+ if (signal?.aborted) {
37
+ reject(new DOMException('Aborted', 'AbortError'));
38
+ return;
39
+ }
40
+ const t = setTimeout(resolve, ms);
41
+ signal?.addEventListener(
42
+ 'abort',
43
+ () => {
44
+ clearTimeout(t);
45
+ reject(new DOMException('Aborted', 'AbortError'));
46
+ },
47
+ { once: true },
48
+ );
49
+ });
50
+ }
51
+
52
+ function isRetryableError(err: unknown): boolean {
53
+ const status = (err as any)?.status ?? (err as any)?.statusCode;
54
+ if (status === 429 || status === 529 || status === 503) return true;
55
+ if (err instanceof Error) {
56
+ const msg = err.message.toLowerCase();
57
+ if (msg.includes('rate limit') || msg.includes('overloaded') || msg.includes('too many requests')) {
58
+ return true;
59
+ }
60
+ }
61
+ return false;
62
+ }
63
+
64
+ function isContextOverflow(err: unknown): boolean {
65
+ if (err instanceof Error) {
66
+ const msg = err.message.toLowerCase();
67
+ return (
68
+ msg.includes('context_length_exceeded') ||
69
+ msg.includes('context window') ||
70
+ msg.includes('maximum context length') ||
71
+ msg.includes('too many tokens') ||
72
+ msg.includes('input is too long') ||
73
+ (err as any)?.name === 'AI_ContextWindowExceededError'
74
+ );
75
+ }
76
+ return false;
77
+ }
78
+
79
+ export async function runAgent(options: IAgentRunOptions): Promise<IAgentRunResult> {
80
+ let stepCount = 0;
81
+ let attempt = 0;
82
+ let totalInput = 0;
83
+ let totalOutput = 0;
84
+
85
+ const tools = options.tools ?? {};
86
+
87
+ // Add a no-op sink for repaired-but-unrecognised tool calls
88
+ const allTools: plugins.ToolSet = {
89
+ ...tools,
90
+ invalid: plugins.tool({
91
+ description: 'Sink for unrecognised tool calls — returns an error message to the model',
92
+ inputSchema: plugins.z.object({
93
+ tool: plugins.z.string(),
94
+ error: plugins.z.string(),
95
+ }),
96
+ execute: async ({ tool, error }: { tool: string; error: string }) =>
97
+ `Unknown tool "${tool}": ${error}`,
98
+ }),
99
+ };
100
+
101
+ // Build messages — streamText requires either prompt OR messages, not both
102
+ let messages: plugins.ModelMessage[] = options.messages
103
+ ? [...options.messages, { role: 'user' as const, content: options.prompt }]
104
+ : [{ role: 'user' as const, content: options.prompt }];
105
+
106
+ while (true) {
107
+ try {
108
+ const result = plugins.streamText({
109
+ model: options.model,
110
+ system: options.system,
111
+ messages,
112
+ tools: allTools,
113
+ stopWhen: plugins.stepCountIs(options.maxSteps ?? 20),
114
+ maxRetries: 0, // handled manually below
115
+ abortSignal: options.abort,
116
+
117
+ experimental_repairToolCall: async ({ toolCall, tools: availableTools, error }) => {
118
+ const lower = toolCall.toolName.toLowerCase();
119
+ if (lower !== toolCall.toolName && (availableTools as any)[lower]) {
120
+ return { ...toolCall, toolName: lower };
121
+ }
122
+ return {
123
+ ...toolCall,
124
+ toolName: 'invalid',
125
+ args: JSON.stringify({
126
+ tool: toolCall.toolName,
127
+ error: String(error),
128
+ }),
129
+ };
130
+ },
131
+
132
+ onChunk: ({ chunk }) => {
133
+ if (chunk.type === 'text-delta' && options.onToken) {
134
+ options.onToken((chunk as any).textDelta ?? (chunk as any).text ?? '');
135
+ }
136
+ },
137
+
138
+ experimental_onToolCallStart: options.onToolCall
139
+ ? ({ toolCall }) => {
140
+ options.onToolCall!(toolCall.toolName, (toolCall as any).input ?? (toolCall as any).args);
141
+ }
142
+ : undefined,
143
+
144
+ experimental_onToolCallFinish: options.onToolResult
145
+ ? ({ toolCall }) => {
146
+ options.onToolResult!(toolCall.toolName, (toolCall as any).result);
147
+ }
148
+ : undefined,
149
+
150
+ onStepFinish: ({ usage }) => {
151
+ stepCount++;
152
+ totalInput += usage?.inputTokens ?? 0;
153
+ totalOutput += usage?.outputTokens ?? 0;
154
+ },
155
+ });
156
+
157
+ // Consume the stream and collect results
158
+ const text = await result.text;
159
+ const finishReason = await result.finishReason;
160
+ const responseData = await result.response;
161
+
162
+ attempt = 0; // reset on success
163
+
164
+ return {
165
+ text,
166
+ messages: responseData.messages as plugins.ModelMessage[],
167
+ steps: stepCount,
168
+ finishReason,
169
+ usage: {
170
+ inputTokens: totalInput,
171
+ outputTokens: totalOutput,
172
+ totalTokens: totalInput + totalOutput,
173
+ },
174
+ };
175
+ } catch (err: unknown) {
176
+ // Abort — don't retry
177
+ if (err instanceof DOMException && err.name === 'AbortError') throw err;
178
+
179
+ // Rate limit / overload — retry with backoff
180
+ if (isRetryableError(err) && attempt < MAX_RETRY_ATTEMPTS) {
181
+ attempt++;
182
+ const headers = (err as any)?.responseHeaders ?? (err as any)?.headers;
183
+ const delay = retryDelay(attempt, headers);
184
+ await sleep(delay, options.abort);
185
+ continue;
186
+ }
187
+
188
+ // Context overflow — compact and retry if handler provided
189
+ if (isContextOverflow(err)) {
190
+ if (!options.onContextOverflow) throw new ContextOverflowError();
191
+ messages = await options.onContextOverflow(messages);
192
+ continue;
193
+ }
194
+
195
+ throw err;
196
+ }
197
+ }
198
+ }
@@ -1,188 +1,20 @@
1
- import * as interfaces from './smartagent.interfaces.js';
2
- import { BaseToolWrapper } from './smartagent.tools.base.js';
1
+ import type { ToolSet } from './plugins.js';
3
2
 
4
- /**
5
- * ToolRegistry - Manages tool registration, visibility, and lifecycle
6
- *
7
- * Responsibilities:
8
- * - Track all registered tools with their metadata
9
- * - Manage visibility (initial vs on-demand)
10
- * - Handle activation of on-demand tools
11
- * - Provide search functionality
12
- */
13
3
  export class ToolRegistry {
14
- private tools: Map<string, BaseToolWrapper> = new Map();
15
- private metadata: Map<string, interfaces.IToolMetadata> = new Map();
16
- private activated: Set<string> = new Set();
4
+ private tools: ToolSet = {};
17
5
 
18
6
  /**
19
- * Register a tool with optional visibility settings
7
+ * Register a tool.
8
+ * @param name Tool name (must be unique, snake_case recommended)
9
+ * @param def Tool definition created with ai-sdk's tool() helper
20
10
  */
21
- register(tool: BaseToolWrapper, options: interfaces.IToolRegistrationOptions = {}): void {
22
- const visibility = options.visibility ?? 'initial';
23
-
24
- this.tools.set(tool.name, tool);
25
- this.metadata.set(tool.name, {
26
- name: tool.name,
27
- description: tool.description,
28
- actions: tool.actions,
29
- visibility,
30
- isActivated: visibility === 'initial',
31
- isInitialized: false,
32
- tags: options.tags,
33
- category: options.category,
34
- });
35
-
36
- if (visibility === 'initial') {
37
- this.activated.add(tool.name);
38
- }
39
- }
40
-
41
- /**
42
- * Get tools visible to the Driver (initial + activated on-demand)
43
- */
44
- getVisibleTools(): BaseToolWrapper[] {
45
- return Array.from(this.tools.entries())
46
- .filter(([name]) => this.activated.has(name))
47
- .map(([, tool]) => tool);
48
- }
49
-
50
- /**
51
- * Get all tools (for search results)
52
- */
53
- getAllTools(): BaseToolWrapper[] {
54
- return Array.from(this.tools.values());
55
- }
56
-
57
- /**
58
- * Get a specific tool by name
59
- */
60
- getTool(name: string): BaseToolWrapper | undefined {
61
- return this.tools.get(name);
11
+ public register(name: string, def: ToolSet[string]): this {
12
+ this.tools[name] = def;
13
+ return this;
62
14
  }
63
15
 
64
- /**
65
- * Get metadata for a tool
66
- */
67
- getMetadata(name: string): interfaces.IToolMetadata | undefined {
68
- return this.metadata.get(name);
69
- }
70
-
71
- /**
72
- * Get all metadata
73
- */
74
- getAllMetadata(): interfaces.IToolMetadata[] {
75
- return Array.from(this.metadata.values());
76
- }
77
-
78
- /**
79
- * Search tools by query (matches name, description, tags, action names)
80
- */
81
- search(query: string): interfaces.IToolMetadata[] {
82
- const q = query.toLowerCase();
83
- return this.getAllMetadata().filter((meta) => {
84
- if (meta.name.toLowerCase().includes(q)) return true;
85
- if (meta.description.toLowerCase().includes(q)) return true;
86
- if (meta.tags?.some((t) => t.toLowerCase().includes(q))) return true;
87
- if (meta.category?.toLowerCase().includes(q)) return true;
88
- if (
89
- meta.actions.some(
90
- (a) => a.name.toLowerCase().includes(q) || a.description.toLowerCase().includes(q)
91
- )
92
- )
93
- return true;
94
- return false;
95
- });
96
- }
97
-
98
- /**
99
- * Activate an on-demand tool
100
- */
101
- async activate(name: string): Promise<{ success: boolean; error?: string }> {
102
- const tool = this.tools.get(name);
103
- const meta = this.metadata.get(name);
104
-
105
- if (!tool || !meta) {
106
- return { success: false, error: `Tool "${name}" not found` };
107
- }
108
-
109
- if (this.activated.has(name)) {
110
- return { success: true }; // Already activated
111
- }
112
-
113
- // Initialize if not already initialized
114
- if (!meta.isInitialized) {
115
- await tool.initialize();
116
- meta.isInitialized = true;
117
- }
118
-
119
- this.activated.add(name);
120
- meta.isActivated = true;
121
-
122
- return { success: true };
123
- }
124
-
125
- /**
126
- * Check if a tool is activated
127
- */
128
- isActivated(name: string): boolean {
129
- return this.activated.has(name);
130
- }
131
-
132
- /**
133
- * Initialize all initial (visible) tools
134
- */
135
- async initializeVisibleTools(): Promise<void> {
136
- const promises: Promise<void>[] = [];
137
-
138
- for (const [name, tool] of this.tools) {
139
- const meta = this.metadata.get(name);
140
- if (meta && this.activated.has(name) && !meta.isInitialized) {
141
- promises.push(
142
- tool.initialize().then(() => {
143
- meta.isInitialized = true;
144
- })
145
- );
146
- }
147
- }
148
-
149
- await Promise.all(promises);
150
- }
151
-
152
- /**
153
- * Cleanup all initialized tools
154
- */
155
- async cleanup(): Promise<void> {
156
- const promises: Promise<void>[] = [];
157
-
158
- for (const [name, tool] of this.tools) {
159
- const meta = this.metadata.get(name);
160
- if (meta?.isInitialized) {
161
- promises.push(tool.cleanup());
162
- }
163
- }
164
-
165
- await Promise.all(promises);
166
- }
167
-
168
- /**
169
- * Check if a tool exists in the registry
170
- */
171
- has(name: string): boolean {
172
- return this.tools.has(name);
173
- }
174
-
175
- /**
176
- * Get the number of registered tools
177
- */
178
- get size(): number {
179
- return this.tools.size;
180
- }
181
-
182
- /**
183
- * Get the number of activated tools
184
- */
185
- get activatedCount(): number {
186
- return this.activated.size;
16
+ /** Get the full ToolSet for passing to runAgent */
17
+ public getTools(): ToolSet {
18
+ return { ...this.tools };
187
19
  }
188
20
  }