@push.rocks/smartagent 1.7.0 → 3.0.0

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 (81) hide show
  1. package/dist_ts/00_commitinfo_data.js +3 -3
  2. package/dist_ts/index.d.ts +9 -12
  3. package/dist_ts/index.js +9 -20
  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 +12 -0
  9. package/dist_ts/smartagent.classes.toolregistry.js +17 -0
  10. package/dist_ts/smartagent.interfaces.d.ts +47 -231
  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 +43 -42
  35. package/readme.md +257 -526
  36. package/ts/00_commitinfo_data.ts +2 -2
  37. package/ts/index.ts +11 -31
  38. package/ts/plugins.ts +22 -21
  39. package/ts/smartagent.classes.agent.ts +198 -0
  40. package/ts/smartagent.classes.toolregistry.ts +20 -0
  41. package/ts/smartagent.interfaces.ts +51 -303
  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 -79
  55. package/dist_ts/smartagent.classes.dualagent.js +0 -583
  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.filesystem.d.ts +0 -40
  65. package/dist_ts/smartagent.tools.filesystem.js +0 -801
  66. package/dist_ts/smartagent.tools.http.d.ts +0 -16
  67. package/dist_ts/smartagent.tools.http.js +0 -264
  68. package/dist_ts/smartagent.tools.json.d.ts +0 -24
  69. package/dist_ts/smartagent.tools.json.js +0 -202
  70. package/dist_ts/smartagent.tools.shell.d.ts +0 -17
  71. package/dist_ts/smartagent.tools.shell.js +0 -202
  72. package/ts/smartagent.classes.driveragent.ts +0 -775
  73. package/ts/smartagent.classes.dualagent.ts +0 -657
  74. package/ts/smartagent.classes.guardianagent.ts +0 -241
  75. package/ts/smartagent.tools.base.ts +0 -83
  76. package/ts/smartagent.tools.browser.ts +0 -253
  77. package/ts/smartagent.tools.deno.ts +0 -230
  78. package/ts/smartagent.tools.filesystem.ts +0 -885
  79. package/ts/smartagent.tools.http.ts +0 -283
  80. package/ts/smartagent.tools.json.ts +0 -224
  81. package/ts/smartagent.tools.shell.ts +0 -230
@@ -1,775 +0,0 @@
1
- import * as plugins from './plugins.js';
2
- import * as interfaces from './smartagent.interfaces.js';
3
- import type { BaseToolWrapper } from './smartagent.tools.base.js';
4
-
5
- /**
6
- * Options for configuring the DriverAgent
7
- */
8
- export interface IDriverAgentOptions {
9
- /** Custom system message for the driver */
10
- systemMessage?: string;
11
- /** Maximum history messages to pass to API (default: 20). Set to 0 for unlimited. */
12
- maxHistoryMessages?: number;
13
- /** Callback fired for each token during LLM generation */
14
- onToken?: (token: string) => void;
15
- }
16
-
17
- /**
18
- * DriverAgent - Executes tasks by reasoning and proposing tool calls
19
- * Works in conjunction with GuardianAgent for approval
20
- */
21
- export class DriverAgent {
22
- private provider: plugins.smartai.MultiModalModel;
23
- private systemMessage: string;
24
- private maxHistoryMessages: number;
25
- private messageHistory: plugins.smartai.ChatMessage[] = [];
26
- private tools: Map<string, BaseToolWrapper> = new Map();
27
- private onToken?: (token: string) => void;
28
- private isInThinkingMode = false; // Track thinking/content state for markers
29
-
30
- constructor(
31
- provider: plugins.smartai.MultiModalModel,
32
- options?: IDriverAgentOptions | string
33
- ) {
34
- this.provider = provider;
35
-
36
- // Support both legacy string systemMessage and new options object
37
- if (typeof options === 'string') {
38
- this.systemMessage = options || this.getDefaultSystemMessage();
39
- this.maxHistoryMessages = 20;
40
- } else {
41
- this.systemMessage = options?.systemMessage || this.getDefaultSystemMessage();
42
- this.maxHistoryMessages = options?.maxHistoryMessages ?? 20;
43
- this.onToken = options?.onToken;
44
- }
45
- }
46
-
47
- /**
48
- * Set the token callback for streaming mode
49
- * @param callback Function to call for each generated token
50
- */
51
- public setOnToken(callback: (token: string) => void): void {
52
- this.onToken = callback;
53
- }
54
-
55
- /**
56
- * Register a tool for use by the driver
57
- */
58
- public registerTool(tool: BaseToolWrapper): void {
59
- this.tools.set(tool.name, tool);
60
- }
61
-
62
- /**
63
- * Get all registered tools
64
- */
65
- public getTools(): Map<string, BaseToolWrapper> {
66
- return this.tools;
67
- }
68
-
69
- /**
70
- * Initialize a new conversation for a task
71
- * @param task The task description
72
- * @param images Optional base64-encoded images for vision tasks
73
- */
74
- public async startTask(task: string, images?: string[]): Promise<interfaces.IAgentMessage> {
75
- // Reset message history
76
- this.messageHistory = [];
77
-
78
- // Build the user message based on available tools
79
- const hasTools = this.tools.size > 0;
80
- let userMessage: string;
81
- if (hasTools) {
82
- userMessage = `TASK: ${task}\n\nAnalyze this task and determine what actions are needed. If you need to use a tool, provide a tool call proposal.`;
83
- } else {
84
- userMessage = `TASK: ${task}\n\nComplete this task directly. When done, wrap your final output in <task_complete>your output here</task_complete> tags.`;
85
- }
86
-
87
- // Add to history
88
- this.messageHistory.push({
89
- role: 'user',
90
- content: userMessage,
91
- });
92
-
93
- // Build the system message - adapt based on available tools
94
- let fullSystemMessage: string;
95
- if (hasTools) {
96
- const toolDescriptions = this.buildToolDescriptions();
97
- fullSystemMessage = `${this.systemMessage}\n\n## Available Tools\n${toolDescriptions}`;
98
- } else {
99
- // Use a simpler system message when no tools are available
100
- fullSystemMessage = this.getNoToolsSystemMessage();
101
- }
102
-
103
- // Get response from provider - use streaming if available and callback is set
104
- let response: plugins.smartai.ChatResponse;
105
-
106
- if (this.onToken && typeof (this.provider as any).chatStreaming === 'function') {
107
- // Use streaming mode with token callback
108
- response = await (this.provider as any).chatStreaming({
109
- systemMessage: fullSystemMessage,
110
- userMessage: userMessage,
111
- messageHistory: [],
112
- images: images,
113
- onToken: this.onToken,
114
- });
115
- } else {
116
- // Fallback to non-streaming mode
117
- response = await this.provider.chat({
118
- systemMessage: fullSystemMessage,
119
- userMessage: userMessage,
120
- messageHistory: [],
121
- images: images,
122
- });
123
- }
124
-
125
- // Add assistant response to history (store images if provided, preserve reasoning for GPT-OSS)
126
- const historyMessage: plugins.smartai.ChatMessage = {
127
- role: 'assistant',
128
- content: response.message,
129
- reasoning: response.reasoning,
130
- };
131
- this.messageHistory.push(historyMessage);
132
-
133
- return {
134
- role: 'assistant',
135
- content: response.message,
136
- };
137
- }
138
-
139
- /**
140
- * Continue the conversation with feedback or results
141
- */
142
- public async continueWithMessage(message: string): Promise<interfaces.IAgentMessage> {
143
- // Add the new message to history
144
- this.messageHistory.push({
145
- role: 'user',
146
- content: message,
147
- });
148
-
149
- // Build the system message - adapt based on available tools
150
- const hasTools = this.tools.size > 0;
151
- let fullSystemMessage: string;
152
- if (hasTools) {
153
- const toolDescriptions = this.buildToolDescriptions();
154
- fullSystemMessage = `${this.systemMessage}\n\n## Available Tools\n${toolDescriptions}`;
155
- } else {
156
- fullSystemMessage = this.getNoToolsSystemMessage();
157
- }
158
-
159
- // Get response from provider with history windowing
160
- // Keep original task and most recent messages to avoid token explosion
161
- let historyForChat: plugins.smartai.ChatMessage[];
162
- const fullHistory = this.messageHistory.slice(0, -1); // Exclude the just-added message
163
-
164
- if (this.maxHistoryMessages > 0 && fullHistory.length > this.maxHistoryMessages) {
165
- // Keep the original task (first message) and most recent messages
166
- historyForChat = [
167
- fullHistory[0], // Original task
168
- ...fullHistory.slice(-(this.maxHistoryMessages - 1)), // Recent messages
169
- ];
170
- } else {
171
- historyForChat = fullHistory;
172
- }
173
-
174
- // Get response from provider - use streaming if available and callback is set
175
- let response: plugins.smartai.ChatResponse;
176
-
177
- if (this.onToken && typeof (this.provider as any).chatStreaming === 'function') {
178
- // Use streaming mode with token callback
179
- response = await (this.provider as any).chatStreaming({
180
- systemMessage: fullSystemMessage,
181
- userMessage: message,
182
- messageHistory: historyForChat,
183
- onToken: this.onToken,
184
- });
185
- } else {
186
- // Fallback to non-streaming mode
187
- response = await this.provider.chat({
188
- systemMessage: fullSystemMessage,
189
- userMessage: message,
190
- messageHistory: historyForChat,
191
- });
192
- }
193
-
194
- // Add assistant response to history (preserve reasoning for GPT-OSS)
195
- this.messageHistory.push({
196
- role: 'assistant',
197
- content: response.message,
198
- reasoning: response.reasoning,
199
- });
200
-
201
- return {
202
- role: 'assistant',
203
- content: response.message,
204
- };
205
- }
206
-
207
- /**
208
- * Parse tool call proposals from assistant response
209
- */
210
- public parseToolCallProposals(response: string): interfaces.IToolCallProposal[] {
211
- const proposals: interfaces.IToolCallProposal[] = [];
212
-
213
- // Match <tool_call>...</tool_call> blocks
214
- const toolCallRegex = /<tool_call>([\s\S]*?)<\/tool_call>/g;
215
- let match;
216
-
217
- while ((match = toolCallRegex.exec(response)) !== null) {
218
- const content = match[1];
219
-
220
- try {
221
- const proposal = this.parseToolCallContent(content);
222
- if (proposal) {
223
- proposals.push(proposal);
224
- }
225
- } catch (error) {
226
- // Skip malformed tool calls
227
- console.warn('Failed to parse tool call:', error);
228
- }
229
- }
230
-
231
- return proposals;
232
- }
233
-
234
- /**
235
- * Parse the content inside a tool_call block
236
- */
237
- private parseToolCallContent(content: string): interfaces.IToolCallProposal | null {
238
- // Extract tool name
239
- const toolMatch = content.match(/<tool>(.*?)<\/tool>/s);
240
- if (!toolMatch) return null;
241
- const toolName = toolMatch[1].trim();
242
-
243
- // Extract action
244
- const actionMatch = content.match(/<action>(.*?)<\/action>/s);
245
- if (!actionMatch) return null;
246
- const action = actionMatch[1].trim();
247
-
248
- // Extract params (JSON)
249
- const paramsMatch = content.match(/<params>([\s\S]*?)<\/params>/);
250
- let params: Record<string, unknown> = {};
251
- if (paramsMatch) {
252
- try {
253
- params = JSON.parse(paramsMatch[1].trim());
254
- } catch {
255
- // Try to extract individual parameters if JSON fails
256
- params = this.extractParamsFromXml(paramsMatch[1]);
257
- }
258
- }
259
-
260
- // Extract reasoning (optional)
261
- const reasoningMatch = content.match(/<reasoning>([\s\S]*?)<\/reasoning>/);
262
- const reasoning = reasoningMatch ? reasoningMatch[1].trim() : undefined;
263
-
264
- return {
265
- proposalId: this.generateProposalId(),
266
- toolName,
267
- action,
268
- params,
269
- reasoning,
270
- };
271
- }
272
-
273
- /**
274
- * Extract parameters from XML-like format when JSON parsing fails
275
- */
276
- private extractParamsFromXml(content: string): Record<string, unknown> {
277
- const params: Record<string, unknown> = {};
278
- const paramRegex = /<(\w+)>([\s\S]*?)<\/\1>/g;
279
- let match;
280
-
281
- while ((match = paramRegex.exec(content)) !== null) {
282
- const key = match[1];
283
- let value: unknown = match[2].trim();
284
-
285
- // Try to parse as JSON for arrays/objects
286
- try {
287
- value = JSON.parse(value as string);
288
- } catch {
289
- // Keep as string if not valid JSON
290
- }
291
-
292
- params[key] = value;
293
- }
294
-
295
- return params;
296
- }
297
-
298
- /**
299
- * Check if the response indicates task completion
300
- */
301
- public isTaskComplete(response: string): boolean {
302
- // Check for explicit completion markers
303
- const completionMarkers = [
304
- '<task_complete>',
305
- '<task_completed>',
306
- 'TASK COMPLETE',
307
- 'Task completed successfully',
308
- ];
309
-
310
- const lowerResponse = response.toLowerCase();
311
- return completionMarkers.some(marker =>
312
- lowerResponse.includes(marker.toLowerCase())
313
- );
314
- }
315
-
316
- /**
317
- * Check if the response needs clarification or user input
318
- */
319
- public needsClarification(response: string): boolean {
320
- const clarificationMarkers = [
321
- '<needs_clarification>',
322
- '<question>',
323
- 'please clarify',
324
- 'could you specify',
325
- 'what do you mean by',
326
- ];
327
-
328
- const lowerResponse = response.toLowerCase();
329
- return clarificationMarkers.some(marker =>
330
- lowerResponse.includes(marker.toLowerCase())
331
- );
332
- }
333
-
334
- /**
335
- * Extract the final result from a completed task
336
- */
337
- public extractTaskResult(response: string): string | null {
338
- // Try to extract from result tags
339
- const resultMatch = response.match(/<task_result>([\s\S]*?)<\/task_result>/);
340
- if (resultMatch) {
341
- return resultMatch[1].trim();
342
- }
343
-
344
- const completeMatch = response.match(/<task_complete>([\s\S]*?)<\/task_complete>/);
345
- if (completeMatch) {
346
- return completeMatch[1].trim();
347
- }
348
-
349
- return null;
350
- }
351
-
352
- /**
353
- * Build tool descriptions for the system message
354
- */
355
- private buildToolDescriptions(): string {
356
- const descriptions: string[] = [];
357
-
358
- for (const tool of this.tools.values()) {
359
- descriptions.push(tool.getFullDescription());
360
- }
361
-
362
- return descriptions.join('\n\n');
363
- }
364
-
365
- /**
366
- * Generate a unique proposal ID
367
- */
368
- private generateProposalId(): string {
369
- return `prop_${Date.now()}_${Math.random().toString(36).substring(2, 8)}`;
370
- }
371
-
372
- /**
373
- * Get the default system message for the driver
374
- */
375
- private getDefaultSystemMessage(): string {
376
- return `You are an AI assistant that executes tasks by using available tools.
377
-
378
- ## Your Role
379
- You analyze tasks, break them down into steps, and use tools to accomplish goals.
380
-
381
- ## CRITICAL: Tool Usage Format
382
- 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.
383
-
384
- CORRECT (the XML is in the output):
385
- <tool_call>
386
- <tool>json</tool>
387
- <action>validate</action>
388
- <params>{"jsonString": "{\\"key\\":\\"value\\"}"}</params>
389
- </tool_call>
390
-
391
- WRONG (just describing, no actual XML):
392
- "I will call json.validate now" or "Let me use the tool"
393
-
394
- ## Guidelines
395
- 1. Think step by step about what needs to be done
396
- 2. When you need a tool, OUTPUT the <tool_call> XML tags - do not just mention them
397
- 3. Only propose ONE tool call at a time
398
- 4. Wait for the result before proposing the next action
399
- 5. When the task is complete, OUTPUT:
400
-
401
- <task_complete>
402
- Your final result here
403
- </task_complete>
404
-
405
- ## Important
406
- - The <tool_call> and <task_complete> tags MUST appear literally in your response
407
- - If you just say "I'll call the tool" without the actual XML, it will NOT work
408
- - If you need clarification, ask using <needs_clarification>your question</needs_clarification>`;
409
- }
410
-
411
- /**
412
- * Get the system message when no tools are available
413
- * Used for direct task completion without tool usage
414
- */
415
- private getNoToolsSystemMessage(): string {
416
- // Use custom system message if provided, otherwise use a simple default
417
- if (this.systemMessage && this.systemMessage !== this.getDefaultSystemMessage()) {
418
- return this.systemMessage;
419
- }
420
-
421
- return `You are an AI assistant that completes tasks directly.
422
-
423
- ## Your Role
424
- You analyze tasks and provide complete, high-quality outputs.
425
-
426
- ## Output Format
427
- When you have completed the task, wrap your final output in task_complete tags:
428
-
429
- <task_complete>
430
- Your complete output here
431
- </task_complete>
432
-
433
- ## Guidelines
434
- 1. Analyze the task requirements carefully
435
- 2. Provide a complete and accurate response
436
- 3. Always wrap your final output in <task_complete></task_complete> tags
437
- 4. If you need clarification, ask using <needs_clarification>your question</needs_clarification>`;
438
- }
439
-
440
- /**
441
- * Reset the conversation state
442
- */
443
- public reset(): void {
444
- this.messageHistory = [];
445
- }
446
-
447
- // ================================
448
- // Native Tool Calling Support
449
- // ================================
450
-
451
- /**
452
- * Start a task with native tool calling support
453
- * Uses Ollama's native tool calling API instead of XML parsing
454
- * @param task The task description
455
- * @param images Optional base64-encoded images for vision tasks
456
- * @returns Response with content, reasoning, and any tool calls
457
- */
458
- public async startTaskWithNativeTools(
459
- task: string,
460
- images?: string[]
461
- ): Promise<{ message: interfaces.IAgentMessage; toolCalls?: interfaces.INativeToolCall[] }> {
462
- // Reset message history
463
- this.messageHistory = [];
464
-
465
- // Build simple user message (no XML instructions needed for native tool calling)
466
- const userMessage = `TASK: ${task}\n\nComplete this task using the available tools. When done, provide your final output.`;
467
-
468
- // Add to history
469
- this.messageHistory.push({
470
- role: 'user',
471
- content: userMessage,
472
- });
473
-
474
- // Build system message for native tool calling
475
- const fullSystemMessage = this.getNativeToolsSystemMessage();
476
-
477
- // Get tools in JSON schema format
478
- const tools = this.getToolsAsJsonSchema();
479
-
480
- // Check if provider supports native tool calling (Ollama)
481
- const provider = this.provider as any;
482
- if (typeof provider.collectStreamResponse !== 'function') {
483
- throw new Error('Provider does not support native tool calling. Use startTask() instead.');
484
- }
485
-
486
- // Use collectStreamResponse for streaming support with tools
487
- const response = await provider.collectStreamResponse(
488
- {
489
- systemMessage: fullSystemMessage,
490
- userMessage: userMessage,
491
- messageHistory: [],
492
- images: images,
493
- tools: tools.length > 0 ? tools : undefined,
494
- },
495
- // Pass onToken callback through onChunk for streaming with thinking markers
496
- this.onToken ? (chunk: any) => {
497
- if (chunk.thinking && this.onToken) {
498
- // Add marker only when transitioning INTO thinking mode
499
- if (!this.isInThinkingMode) {
500
- this.onToken('\n[THINKING] ');
501
- this.isInThinkingMode = true;
502
- }
503
- this.onToken(chunk.thinking);
504
- }
505
- if (chunk.content && this.onToken) {
506
- // Add marker when transitioning OUT of thinking mode
507
- if (this.isInThinkingMode) {
508
- this.onToken('\n[OUTPUT] ');
509
- this.isInThinkingMode = false;
510
- }
511
- this.onToken(chunk.content);
512
- }
513
- } : undefined
514
- );
515
-
516
- // Reset thinking state after response completes
517
- this.isInThinkingMode = false;
518
-
519
- // Add assistant response to history
520
- const historyMessage: any = {
521
- role: 'assistant',
522
- content: response.message || '',
523
- reasoning: response.thinking || response.reasoning,
524
- };
525
-
526
- // CRITICAL: Preserve tool_calls in history for native tool calling
527
- // Without this, the model doesn't know it already called a tool and loops forever
528
- if (response.toolCalls && response.toolCalls.length > 0) {
529
- historyMessage.tool_calls = response.toolCalls.map((tc: any) => ({
530
- function: {
531
- name: tc.function.name,
532
- arguments: tc.function.arguments,
533
- },
534
- }));
535
- }
536
-
537
- this.messageHistory.push(historyMessage as unknown as plugins.smartai.ChatMessage);
538
-
539
- // Convert Ollama tool calls to our format
540
- let toolCalls: interfaces.INativeToolCall[] | undefined;
541
- if (response.toolCalls && response.toolCalls.length > 0) {
542
- toolCalls = response.toolCalls.map((tc: any) => ({
543
- function: {
544
- name: tc.function.name,
545
- arguments: tc.function.arguments,
546
- index: tc.function.index,
547
- },
548
- }));
549
- }
550
-
551
- return {
552
- message: {
553
- role: 'assistant',
554
- content: response.message || '',
555
- },
556
- toolCalls,
557
- };
558
- }
559
-
560
- /**
561
- * Continue conversation with native tool calling support
562
- * @param message The message to continue with (e.g., tool result)
563
- * @param toolName Optional tool name - when provided, message is added as role: 'tool' instead of 'user'
564
- * @returns Response with content, reasoning, and any tool calls
565
- */
566
- public async continueWithNativeTools(
567
- message: string,
568
- toolName?: string
569
- ): Promise<{ message: interfaces.IAgentMessage; toolCalls?: interfaces.INativeToolCall[] }> {
570
- // Add the new message to history
571
- if (toolName) {
572
- // Tool result - must use role: 'tool' for native tool calling
573
- // The 'tool' role is supported by providers but not in the ChatMessage type
574
- this.messageHistory.push({
575
- role: 'tool',
576
- content: message,
577
- toolName: toolName,
578
- } as unknown as plugins.smartai.ChatMessage);
579
- } else {
580
- // Regular user message
581
- this.messageHistory.push({
582
- role: 'user',
583
- content: message,
584
- });
585
- }
586
-
587
- // Build system message
588
- const fullSystemMessage = this.getNativeToolsSystemMessage();
589
-
590
- // Get tools in JSON schema format
591
- const tools = this.getToolsAsJsonSchema();
592
-
593
- // Get response from provider with history windowing
594
- // For tool results, include the full history (with tool message)
595
- // For regular user messages, exclude the last message (it becomes userMessage)
596
- let historyForChat: plugins.smartai.ChatMessage[];
597
- const fullHistory = toolName
598
- ? this.messageHistory // Include tool result in history
599
- : this.messageHistory.slice(0, -1); // Exclude last user message
600
-
601
- if (this.maxHistoryMessages > 0 && fullHistory.length > this.maxHistoryMessages) {
602
- historyForChat = [
603
- fullHistory[0],
604
- ...fullHistory.slice(-(this.maxHistoryMessages - 1)),
605
- ];
606
- } else {
607
- historyForChat = fullHistory;
608
- }
609
-
610
- // Check if provider supports native tool calling
611
- const provider = this.provider as any;
612
- if (typeof provider.collectStreamResponse !== 'function') {
613
- throw new Error('Provider does not support native tool calling. Use continueWithMessage() instead.');
614
- }
615
-
616
- // For tool results, use a continuation prompt instead of repeating the result
617
- const userMessage = toolName
618
- ? 'Continue with the task. The tool result has been provided above.'
619
- : message;
620
-
621
- // Use collectStreamResponse for streaming support with tools
622
- const response = await provider.collectStreamResponse(
623
- {
624
- systemMessage: fullSystemMessage,
625
- userMessage: userMessage,
626
- messageHistory: historyForChat,
627
- tools: tools.length > 0 ? tools : undefined,
628
- },
629
- // Pass onToken callback through onChunk for streaming with thinking markers
630
- this.onToken ? (chunk: any) => {
631
- if (chunk.thinking && this.onToken) {
632
- // Add marker only when transitioning INTO thinking mode
633
- if (!this.isInThinkingMode) {
634
- this.onToken('\n[THINKING] ');
635
- this.isInThinkingMode = true;
636
- }
637
- this.onToken(chunk.thinking);
638
- }
639
- if (chunk.content && this.onToken) {
640
- // Add marker when transitioning OUT of thinking mode
641
- if (this.isInThinkingMode) {
642
- this.onToken('\n[OUTPUT] ');
643
- this.isInThinkingMode = false;
644
- }
645
- this.onToken(chunk.content);
646
- }
647
- } : undefined
648
- );
649
-
650
- // Reset thinking state after response completes
651
- this.isInThinkingMode = false;
652
-
653
- // Add assistant response to history
654
- const historyMessage: any = {
655
- role: 'assistant',
656
- content: response.message || '',
657
- reasoning: response.thinking || response.reasoning,
658
- };
659
-
660
- // CRITICAL: Preserve tool_calls in history for native tool calling
661
- // Without this, the model doesn't know it already called a tool and loops forever
662
- if (response.toolCalls && response.toolCalls.length > 0) {
663
- historyMessage.tool_calls = response.toolCalls.map((tc: any) => ({
664
- function: {
665
- name: tc.function.name,
666
- arguments: tc.function.arguments,
667
- },
668
- }));
669
- }
670
-
671
- this.messageHistory.push(historyMessage as unknown as plugins.smartai.ChatMessage);
672
-
673
- // Convert Ollama tool calls to our format
674
- let toolCalls: interfaces.INativeToolCall[] | undefined;
675
- if (response.toolCalls && response.toolCalls.length > 0) {
676
- toolCalls = response.toolCalls.map((tc: any) => ({
677
- function: {
678
- name: tc.function.name,
679
- arguments: tc.function.arguments,
680
- index: tc.function.index,
681
- },
682
- }));
683
- }
684
-
685
- return {
686
- message: {
687
- role: 'assistant',
688
- content: response.message || '',
689
- },
690
- toolCalls,
691
- };
692
- }
693
-
694
- /**
695
- * Get system message for native tool calling mode
696
- * Simplified prompt that lets the model use tools naturally
697
- */
698
- private getNativeToolsSystemMessage(): string {
699
- return `You are an AI assistant that executes tasks by using available tools.
700
-
701
- ## Your Role
702
- You analyze tasks, break them down into steps, and use tools to accomplish goals.
703
-
704
- ## Guidelines
705
- 1. Think step by step about what needs to be done
706
- 2. Use the available tools to complete the task
707
- 3. Process tool results and continue until the task is complete
708
- 4. When the task is complete, provide a final summary
709
-
710
- ## Important
711
- - Use tools when needed to gather information or perform actions
712
- - If you need clarification, ask the user
713
- - Always verify your work before marking the task complete`;
714
- }
715
-
716
- /**
717
- * Convert registered tools to Ollama JSON Schema format for native tool calling
718
- * Each tool action becomes a separate function with name format: "toolName_actionName"
719
- * @returns Array of IOllamaTool compatible tool definitions
720
- */
721
- public getToolsAsJsonSchema(): plugins.smartai.IOllamaTool[] {
722
- const tools: plugins.smartai.IOllamaTool[] = [];
723
-
724
- for (const tool of this.tools.values()) {
725
- for (const action of tool.actions) {
726
- // Build the tool definition in Ollama format
727
- const toolDef: plugins.smartai.IOllamaTool = {
728
- type: 'function',
729
- function: {
730
- name: `${tool.name}_${action.name}`, // e.g., "json_validate"
731
- description: `[${tool.name}] ${action.description}`,
732
- parameters: action.parameters as plugins.smartai.IOllamaTool['function']['parameters'],
733
- },
734
- };
735
- tools.push(toolDef);
736
- }
737
- }
738
-
739
- return tools;
740
- }
741
-
742
- /**
743
- * Parse native tool calls from provider response into IToolCallProposal format
744
- * @param toolCalls Array of native tool calls from the provider
745
- * @returns Array of IToolCallProposal ready for execution
746
- */
747
- public parseNativeToolCalls(
748
- toolCalls: interfaces.INativeToolCall[]
749
- ): interfaces.IToolCallProposal[] {
750
- return toolCalls.map(tc => {
751
- // Split "json_validate" -> toolName="json", action="validate"
752
- const fullName = tc.function.name;
753
- const underscoreIndex = fullName.indexOf('_');
754
-
755
- let toolName: string;
756
- let action: string;
757
-
758
- if (underscoreIndex > 0) {
759
- toolName = fullName.substring(0, underscoreIndex);
760
- action = fullName.substring(underscoreIndex + 1);
761
- } else {
762
- // Fallback: treat entire name as tool name with empty action
763
- toolName = fullName;
764
- action = '';
765
- }
766
-
767
- return {
768
- proposalId: this.generateProposalId(),
769
- toolName,
770
- action,
771
- params: tc.function.arguments,
772
- };
773
- });
774
- }
775
- }