@lvx74/openrrouter-ai-agent 1.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.
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2025 AI Agent Toolkit
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,373 @@
1
+ # AI Agent Toolkit
2
+
3
+ Un toolkit completo per creare agenti AI con tool calling, compatibile con l'interfaccia `@openai/agents`. **Funziona principalmente con OpenRouter** per accesso a modelli multipli con una singola API key.
4
+
5
+ ## 🚀 Caratteristiche
6
+
7
+ - **Compatibile con @openai/agents**: Segue l'interfaccia standard per agenti AI
8
+ - **OpenRouter Ready**: Ottimizzato per OpenRouter con supporto per modelli multipli
9
+ - **Tool System Avanzato**: Supporto per tools con validazione Zod
10
+ - **Chat Interattiva**: Interfacce ready-to-use per console
11
+ - **NPM Package**: Utilizzabile come dipendenza in altri progetti
12
+ - **Debug & Verbose Mode**: Modalità dettagliate per sviluppo
13
+ - **Gestione Errori**: Gestione robusta degli errori nei tool calls
14
+
15
+ ## 📦 Installazione
16
+
17
+ ### Come pacchetto npm
18
+ ```bash
19
+ npm install ai-agent-toolkit
20
+ ```
21
+
22
+ ### Sviluppo locale
23
+ ```bash
24
+ # Clona il repository
25
+ git clone <repo-url>
26
+ cd simpleAgent
27
+
28
+ # Installa le dipendenze
29
+ npm install
30
+
31
+ # Configura le variabili d'ambiente
32
+ cp .env.example .env
33
+ # Modifica .env con la tua OpenRouter API key
34
+ ```
35
+
36
+ ## 🔧 Configurazione
37
+
38
+ ### OpenRouter (Raccomandato)
39
+ ```env
40
+ OPENROUTER_API_KEY=your_openrouter_api_key_here
41
+ ```
42
+
43
+ ### OpenAI (Alternativa)
44
+ ```env
45
+ OPENAI_API_KEY=your_openai_api_key_here
46
+ ```
47
+
48
+ > **💡 Perché OpenRouter?**
49
+ > - Accesso a modelli multipli (GPT-4, Claude, Gemini, etc.) con una singola API key
50
+ > - Costi ridotti rispetto alle API dirette
51
+ > - Stesso formato API di OpenAI
52
+ > - Failover automatico tra modelli
53
+
54
+ ## 🔧 Uso Base
55
+
56
+ ### Importa le classi principali
57
+
58
+ ```javascript
59
+ import { Agent, Tool } from 'ai-agent-toolkit'
60
+
61
+ // Crea un tool personalizzato
62
+ const myTool = new Tool({
63
+ name: 'get_time',
64
+ description: 'Ottiene l\'ora attuale',
65
+ parameters: {
66
+ type: 'object',
67
+ properties: {
68
+ timezone: { type: 'string', description: 'Timezone (opzionale)' }
69
+ }
70
+ },
71
+ handler: async ({ timezone = 'UTC' }) => {
72
+ return new Date().toLocaleString('it-IT', { timeZone: timezone })
73
+ }
74
+ })
75
+
76
+ // Crea l'agent
77
+ const agent = new Agent({
78
+ model: 'qwen/qwen3-coder:free', // Modello gratuito su OpenRouter
79
+ apiKey: process.env.OPENROUTER_API_KEY,
80
+ instructions: 'Sei un assistente utile.',
81
+ tools: [myTool]
82
+ })
83
+
84
+ // Usa l'agent
85
+ const response = await agent.run('Che ore sono?')
86
+ console.log(response.content)
87
+ ```
88
+
89
+ ### Chat interattiva con utilities
90
+
91
+ ```javascript
92
+ import { Agent, createChatInterface } from 'ai-agent-toolkit'
93
+
94
+ const agent = new Agent({
95
+ model: 'anthropic/claude-3-haiku', // Claude via OpenRouter
96
+ apiKey: process.env.OPENROUTER_API_KEY,
97
+ instructions: 'Sei un assistente AI utile e amichevole.',
98
+ verbose: true
99
+ })
100
+
101
+ // Avvia chat interattiva
102
+ createChatInterface(agent, {
103
+ welcomeMessage: '🤖 Ciao! Come posso aiutarti?',
104
+ prompt: 'Tu: '
105
+ })
106
+ ```
107
+
108
+ ## 🖥️ Modalità d'uso: CLI, API server e Web GUI
109
+
110
+ Puoi usare questo toolkit in tre modi principali:
111
+
112
+ ### 1. Interfaccia CLI (console)
113
+
114
+ Esegui la chat interattiva direttamente da terminale:
115
+
116
+ ```bash
117
+ node example/basic.js
118
+ ```
119
+
120
+ - Chatta con l'agente AI e prova i tool direttamente da console
121
+ - Ideale per sviluppo, debug e test rapidi
122
+
123
+ ### 2. API server compatibile OpenAI/OpenRouter
124
+
125
+ Avvia il server API compatibile OpenAI (con tool calling):
126
+
127
+ ```bash
128
+ node example/server.js
129
+ ```
130
+
131
+ - Espone endpoint `/v1/chat/completions` e `/v1/models` compatibili con OpenAI
132
+ - Puoi collegare qualsiasi client OpenAI, plugin, o frontend custom
133
+
134
+ ### 3. Web GUI (OpenWebUI)
135
+
136
+ Collega il server a una web UI moderna come [OpenWebUI](https://github.com/open-webui/open-webui):
137
+
138
+ - Interfaccia grafica user-friendly
139
+ - Supporta tool calling, history, modelli multipli
140
+ - **Guida dettagliata**: [openwebui.md](./openwebui.md)
141
+
142
+ > Consulta il file [`openwebui.md`](./openwebui.md) per istruzioni dettagliate su come collegare OpenWebUI al server e sfruttare tutte le funzionalità avanzate.
143
+
144
+ ## 🎯 Modelli Supportati
145
+
146
+ Tramite **OpenRouter**, puoi usare tutti questi modelli:
147
+
148
+ ### Gratuiti
149
+ - `qwen/qwen3-coder:free` - Ottimo per coding
150
+ - `microsoft/phi-3-mini-128k-instruct:free` - Veloce e leggero
151
+ - `mistralai/mistral-7b-instruct:free` - Bilanciato
152
+
153
+ ### A Pagamento (Costi Ridotti)
154
+ - `anthropic/claude-3-haiku` - Veloce ed economico
155
+ - `openai/gpt-4o-mini` - GPT-4 economico
156
+ - `google/gemini-pro` - Google Gemini
157
+ - `anthropic/claude-3-sonnet` - Claude bilanciato
158
+ - `openai/gpt-4` - GPT-4 completo
159
+
160
+ ### Enterprise
161
+ - `anthropic/claude-3-opus` - Claude più potente
162
+ - `openai/gpt-4-turbo` - GPT-4 avanzato
163
+
164
+ ## 🛠 Tools Disponibili
165
+
166
+ ### get_weather
167
+ Restituisce informazioni meteo per una città specifica.
168
+
169
+ **Parametri:**
170
+ - `city` (string): Nome della città
171
+ - `unit` (string, opzionale): Unità di temperatura ('celsius' o 'fahrenheit')
172
+
173
+ **Esempio:**
174
+ ```
175
+ Che tempo fa a Roma?
176
+ ```
177
+
178
+ ### echo
179
+ Ripete il testo fornito dall'utente.
180
+
181
+ **Parametri:**
182
+ - `text` (string): Testo da ripetere
183
+ - `repeat_count` (number, opzionale): Numero di ripetizioni
184
+
185
+ **Esempio:**
186
+ ```
187
+ Ripeti "Ciao mondo" 3 volte
188
+ ```
189
+
190
+ ### calculate
191
+ Esegue calcoli matematici semplici.
192
+
193
+ **Parametri:**
194
+ - `operation` (string): Operazione ('add', 'subtract', 'multiply', 'divide')
195
+ - `a` (number): Primo numero
196
+ - `b` (number): Secondo numero
197
+
198
+ **Esempio:**
199
+ ```
200
+ Calcola 15 + 25
201
+ ```
202
+
203
+ ## 🎮 Comandi della Chat
204
+
205
+ - `exit` / `quit` - Esci dalla chat
206
+ - `reset` - Resetta la conversazione
207
+ - `history` - Mostra cronologia messaggi
208
+ - `tools` - Lista dei tools disponibili
209
+ - `debug on/off` - Attiva/disattiva modalità debug
210
+ - `help` - Mostra l'aiuto
211
+
212
+ ## 🏗 Architettura
213
+
214
+ ### Struttura del Progetto
215
+
216
+ ```
217
+ simpleAgent/
218
+ ├── agent.js # Chat interattiva principale
219
+ ├── AgentClass.js # Classe Agent compatibile @openai/agents
220
+ ├── Tool.js # Classe base per tools
221
+ ├── functions.js # Definizione dei tools disponibili
222
+ ├── package.json # Configurazione npm
223
+ └── README.md # Documentazione
224
+ ```
225
+
226
+ ### Classe Agent
227
+
228
+ La classe `Agent` è compatibile con l'interfaccia `@openai/agents` e supporta:
229
+
230
+ ```javascript
231
+ // Inizializzazione con options object
232
+ const agent = new Agent({
233
+ model: 'anthropic/claude-3-haiku', // Qualsiasi modello OpenRouter
234
+ apiKey: process.env.OPENROUTER_API_KEY,
235
+ instructions: 'Sei un assistente AI...',
236
+ tools: availableTools,
237
+ temperature: 0.7,
238
+ maxIterations: 10,
239
+ debug: false
240
+ })
241
+
242
+ // Metodi principali
243
+ await agent.run(message) // Esegue una conversazione completa
244
+ await agent.step() // Esegue un singolo step
245
+ agent.reset() // Resetta la conversazione
246
+ agent.getHistory() // Ottiene la cronologia
247
+ agent.addTool(tool) // Aggiunge un tool
248
+ agent.setDebug(true) // Attiva debug
249
+ ```
250
+
251
+ ### Classe Tool
252
+
253
+ La classe `Tool` supporta sia schema Zod che JSON Schema tradizionali:
254
+
255
+ ```javascript
256
+ // Con schema Zod
257
+ const weatherTool = new Tool({
258
+ name: 'get_weather',
259
+ description: 'Restituisce il meteo per una città',
260
+ schema: z.object({
261
+ city: z.string().describe('Nome della città'),
262
+ unit: z.enum(['celsius', 'fahrenheit']).optional()
263
+ }),
264
+ handler: async ({ city, unit }) => {
265
+ // Implementazione del tool
266
+ }
267
+ })
268
+
269
+ // Con JSON Schema
270
+ const echoTool = new Tool({
271
+ name: 'echo',
272
+ description: 'Ripete un testo',
273
+ parameters: {
274
+ type: 'object',
275
+ properties: {
276
+ text: { type: 'string', description: 'Testo da ripetere' }
277
+ },
278
+ required: ['text']
279
+ },
280
+ handler: async ({ text }) => {
281
+ return `Echo: ${text}`
282
+ }
283
+ })
284
+ ```
285
+
286
+ ## 🔄 Aggiungere Nuovi Tools
287
+
288
+ 1. **Crea il tool:**
289
+
290
+ ```javascript
291
+ export const myTool = new Tool({
292
+ name: 'my_tool',
293
+ description: 'Descrizione del mio tool',
294
+ schema: z.object({
295
+ param1: z.string().describe('Descrizione parametro'),
296
+ param2: z.number().optional()
297
+ }),
298
+ handler: async ({ param1, param2 }) => {
299
+ // La tua logica qui
300
+ return 'Risultato del tool'
301
+ }
302
+ })
303
+
304
+ // Aggiungi all'array dei tools disponibili
305
+ export const availableTools = [weatherTool, echoTool, calculateTool, myTool]
306
+ ```
307
+
308
+ 2. **Il tool sarà automaticamente disponibile nella chat!**
309
+
310
+ ## 🔗 Setup OpenRouter
311
+
312
+ 1. **Registrati su [OpenRouter.ai](https://openrouter.ai)**
313
+ 2. **Ottieni la tua API key** dal dashboard
314
+ 3. **Aggiungi crediti** (anche solo $5 durano mesi)
315
+ 4. **Usa qualsiasi modello** con la stessa API key
316
+
317
+ ### Vantaggi di OpenRouter:
318
+ - ✅ **Una sola API key** per tutti i modelli
319
+ - ✅ **Costi ridotti** fino al 50% rispetto alle API dirette
320
+ - ✅ **Modelli gratuiti** disponibili per testing
321
+ - ✅ **Failover automatico** se un modello non è disponibile
322
+ - ✅ **Stesso formato** delle API OpenAI
323
+ - ✅ **Rate limiting** migliore
324
+
325
+ ## 🐛 Debug
326
+
327
+ Attiva la modalità debug per vedere i dettagli delle chiamate API:
328
+
329
+ ```bash
330
+ # Nella chat, digita:
331
+ debug on
332
+ ```
333
+
334
+ Questo mostrerà:
335
+ - Risposte complete del modello
336
+ - Tool calls con parametri
337
+ - Risultati dei tools
338
+ - Numero di iterazioni
339
+
340
+ ## 📚 Dipendenze
341
+
342
+ - `openai` - Client OpenAI (compatibile con OpenRouter)
343
+ - `dotenv` - Gestione variabili d'ambiente
344
+ - `zod` - Validazione schema e type safety
345
+ - `@openai/agents` - Interfaccia standard per agenti AI
346
+
347
+ ## 🌐 API Supportate
348
+
349
+ ### OpenRouter (Raccomandato)
350
+ - **URL**: `https://openrouter.ai/api/v1`
351
+ - **Modelli**: 50+ modelli disponibili
352
+ - **Costi**: Ridotti fino al 50%
353
+ - **Setup**: Una sola API key
354
+
355
+ ### OpenAI (Supporto diretto)
356
+ - **URL**: `https://api.openai.com/v1`
357
+ - **Modelli**: GPT-3.5, GPT-4 serie
358
+ - **Costi**: Listino OpenAI standard
359
+
360
+ ### Altri Provider
361
+ Il toolkit è compatibile con qualsiasi API che segue il formato OpenAI.
362
+
363
+ ## 🤝 Contribuire
364
+
365
+ 1. Fork del repository
366
+ 2. Crea un branch per la tua feature
367
+ 3. Commit delle modifiche
368
+ 4. Push al branch
369
+ 5. Apri una Pull Request
370
+
371
+ ## 📄 Licenza
372
+
373
+ ISC License - vedi file LICENSE per dettagli.
package/package.json ADDED
@@ -0,0 +1,82 @@
1
+ {
2
+ "name": "@lvx74/openrrouter-ai-agent",
3
+ "version": "1.0.0",
4
+ "description": "A powerful AI agent toolkit compatible with @openai/agents for building conversational AI with tool calling support using OpenRouter",
5
+ "type": "module",
6
+ "main": "src/index.js",
7
+ "exports": {
8
+ ".": {
9
+ "import": "./src/index.js",
10
+ "types": "./src/index.d.ts"
11
+ },
12
+ "./Agent": {
13
+ "import": "./src/Agent.js",
14
+ "types": "./src/Agent.d.ts"
15
+ },
16
+ "./Tool": {
17
+ "import": "./src/Tool.js",
18
+ "types": "./src/Tool.d.ts"
19
+ }
20
+ },
21
+ "files": [
22
+ "src/",
23
+ "README.md",
24
+ "LICENSE"
25
+ ],
26
+ "scripts": {
27
+ "start": "node example/index.js",
28
+ "dev": "node example/index.js",
29
+ "example": "node example/index.js",
30
+ "example:basic": "node example/basic.js",
31
+ "server": "node example/server.js",
32
+ "server:dev": "node --watch example/server.js",
33
+ "test": "node example/test.js",
34
+ "build": "echo \"No build step needed for pure ESM package\"",
35
+ "prepublishOnly": "npm test"
36
+ },
37
+ "keywords": [
38
+ "ai",
39
+ "agent",
40
+ "openai",
41
+ "openwebui",
42
+ "webserver",
43
+ "chatbot",
44
+ "tools",
45
+ "conversational-ai",
46
+ "llm",
47
+ "assistant",
48
+ "chat",
49
+ "openrouter"
50
+ ],
51
+ "author": {
52
+ "name": "Luca Saggese",
53
+ "email": "luca.saggese@gmail.com",
54
+ "url": "https://github.com/luca-saggese"
55
+ },
56
+ "license": "MIT",
57
+ "repository": {
58
+ "type": "git",
59
+ "url": "git+https://github.com/luca-saggese/ai-agent-toolkit.git"
60
+ },
61
+ "bugs": {
62
+ "url": "https://github.com/luca-saggese/ai-agent-toolkit/issues"
63
+ },
64
+ "homepage": "https://github.com/luca-saggese/ai-agent-toolkit#readme",
65
+ "engines": {
66
+ "node": ">=16.0.0"
67
+ },
68
+ "peerDependencies": {
69
+ "dotenv": "^16.0.0 || ^17.0.0"
70
+ },
71
+ "dependencies": {
72
+ "openai": "^5.10.2",
73
+ "zod": "^3.25.67",
74
+ "express": "^4.18.2",
75
+ "cors": "^2.8.5",
76
+ "express-rate-limit": "^7.1.5"
77
+ },
78
+ "devDependencies": {
79
+ "@openai/agents": "^0.0.13",
80
+ "dotenv": "^17.2.1"
81
+ }
82
+ }
package/src/Agent.js ADDED
@@ -0,0 +1,495 @@
1
+ import 'dotenv/config'
2
+ import OpenAI from 'openai'
3
+ import { EventEmitter } from 'events'
4
+
5
+ /**
6
+ * Classe Agent compatibile con @openai/agents per gestire conversazioni con AI e tool calls
7
+ * Supporta eventi per streaming di tutti i messaggi (user, assistant, tool, tool_calls)
8
+ */
9
+ export class Agent extends EventEmitter {
10
+ constructor(options = {}) {
11
+ super() // Inizializza EventEmitter
12
+
13
+ // Supporto per sia il formato nuovo che quello vecchio
14
+ if (typeof options === 'string') {
15
+ // Formato vecchio: Agent(apiKey, model, systemPrompt, tools)
16
+ this.openai = new OpenAI({
17
+ apiKey: arguments[0],
18
+ baseURL: 'https://openrouter.ai/api/v1',
19
+ })
20
+ this.model = arguments[1]
21
+ this.systemPrompt = arguments[2] || ''
22
+ this.tools = arguments[3] || []
23
+ } else {
24
+ // Formato nuovo: Agent({ model, tools, instructions, apiKey, ... })
25
+ this.openai = new OpenAI({
26
+ apiKey: options.apiKey || process.env.OPENROUTER_API_KEY,
27
+ baseURL: options.baseURL || 'https://openrouter.ai/api/v1',
28
+ })
29
+ this.model = options.model || 'qwen/qwen3-coder:free'
30
+ this.systemPrompt = (options.instructions || options.systemPrompt || '') + `\n\nALWAYS CALL ONLY 1 tool at a time.\n`
31
+ this.tools = options.tools || []
32
+ }
33
+
34
+ this.messages = options.messages || []
35
+
36
+ // Mappa dei tools per accesso rapido
37
+ this.toolMap = new Map()
38
+ this.tools.forEach(tool => {
39
+ this.toolMap.set(tool.name, tool)
40
+ })
41
+
42
+ // Inizializza con il system prompt se fornito
43
+ if (this.systemPrompt) {
44
+ this.messages.push({ role: 'system', content: this.systemPrompt })
45
+ }
46
+
47
+ // Configurazioni aggiuntive
48
+ this.maxIterations = options.maxIterations || 10
49
+ this.temperature = options.temperature || 0.7
50
+ this.debug = options.debug || true
51
+ this.verbose = options.verbose !== undefined ? options.verbose : true // Default attivo per mostrare thoughts
52
+ }
53
+
54
+ /**
55
+ * Emette un evento per il messaggio specificato
56
+ */
57
+ _emitMessage(message, eventType = 'message') {
58
+ this.emit(eventType, message)
59
+ this.emit('message', { ...message, eventType })
60
+ }
61
+
62
+ /**
63
+ * Aggiunge un messaggio utente alla conversazione
64
+ */
65
+ addMessage(content, role = 'user') {
66
+ const message = { role, content }
67
+ this.messages.push(message)
68
+
69
+ // Emetti evento per il messaggio utente
70
+ this._emitMessage(message, 'user_message')
71
+ }
72
+
73
+ /**
74
+ * Alias per compatibilità
75
+ */
76
+ addUserMessage(content) {
77
+ this.addMessage(content, 'user')
78
+ }
79
+
80
+ /**
81
+ * Ottiene le definizioni dei tools per OpenAI
82
+ */
83
+ getToolDefinitions() {
84
+ return this.tools.map(tool => tool.getDefinition())
85
+ }
86
+
87
+ /**
88
+ * Esegue una singola iterazione della conversazione
89
+ */
90
+ async step() {
91
+ const toolDefinitions = this.getToolDefinitions()
92
+
93
+ const res = await this.openai.chat.completions.create({
94
+ model: this.model,
95
+ messages: this.messages,
96
+ tools: toolDefinitions.length > 0 ? toolDefinitions : undefined,
97
+ tool_choice: toolDefinitions.length > 0 ? 'auto' : undefined,
98
+ temperature: this.temperature
99
+ })
100
+
101
+ const msg = res.choices[0].message
102
+
103
+ if (this.debug) {
104
+ console.log('🤖 Risposta del modello:', JSON.stringify(res, null, 2))
105
+ }
106
+
107
+ if (!msg.tool_calls) {
108
+ // Risposta finale
109
+ this.messages.push(msg)
110
+
111
+ // Emetti evento per la risposta assistant
112
+ this._emitMessage(msg, 'assistant_message')
113
+
114
+ // Log del pensiero finale dell'AI (solo se verbose)
115
+ if (this.verbose) {
116
+ console.log('💭 Thought (Pensiero finale):')
117
+ console.log(` "${msg.content}"`)
118
+ }
119
+
120
+ return {
121
+ type: 'response',
122
+ content: msg.content,
123
+ role: 'assistant'
124
+ }
125
+ }
126
+
127
+ // Log del reasoning dell'AI prima di chiamare i tools (solo se verbose)
128
+ if (this.verbose) {
129
+ if (msg.content) {
130
+ console.log('🧠 AI Reasoning (Ragionamento):')
131
+ console.log(` "${msg.content}"`)
132
+ }
133
+
134
+ console.log('🛠 Tool calls da eseguire:')
135
+ for (const call of msg.tool_calls) {
136
+ console.log(`🔧 Lanciando tool: ${call.function.name}`)
137
+ console.log(` 📋 Parametri: ${call.function.arguments}`)
138
+ }
139
+ }
140
+
141
+ // Aggiungi il messaggio assistant con le tool_calls
142
+ const assistantMessage = {
143
+ role: 'assistant',
144
+ content: null,
145
+ tool_calls: msg.tool_calls
146
+ }
147
+ this.messages.push(assistantMessage)
148
+
149
+ // Emetti evento per il messaggio assistant con tool_calls
150
+ this._emitMessage(assistantMessage, 'assistant_tool_calls')
151
+
152
+ // Esegui ogni tool call
153
+ const toolResults = []
154
+ for (const toolCall of msg.tool_calls) {
155
+ const { name, arguments: argsStr } = toolCall.function
156
+
157
+ try {
158
+ const args = JSON.parse(argsStr)
159
+ const tool = this.toolMap.get(name)
160
+
161
+ if (!tool) {
162
+ const errorMsg = `Tool '${name}' non trovato`
163
+ console.log(`⚠️ Tool Error: ${errorMsg}`)
164
+
165
+ const toolMessage = {
166
+ role: 'tool',
167
+ tool_call_id: toolCall.id,
168
+ name,
169
+ content: errorMsg
170
+ }
171
+ this.messages.push(toolMessage)
172
+
173
+ // Emetti evento per il messaggio tool
174
+ this._emitMessage(toolMessage, 'tool_message')
175
+
176
+ toolResults.push({ name, error: errorMsg })
177
+ continue
178
+ }
179
+
180
+ console.log(`⚡ Eseguendo tool: ${name}...`)
181
+ const startTime = Date.now()
182
+ const result = await tool.execute(args)
183
+
184
+ if (this.verbose) {
185
+ console.log(`📋 Observation (Osservazione da ${name}):`)
186
+ console.log(` "${result}"`)
187
+ }
188
+ toolCall.done = true;
189
+ toolCall.result = result;
190
+ toolCall.execution_time = Date.now() - startTime;
191
+
192
+ const toolMessage = {
193
+ role: 'tool',
194
+ tool_call_id: toolCall.id,
195
+ tool_calls: msg.tool_calls,
196
+ name,
197
+ content: typeof result === 'string' ? result : JSON.stringify(result)
198
+
199
+ }
200
+ this.messages.push(toolMessage)
201
+
202
+ // Emetti evento per il messaggio tool
203
+ this._emitMessage(toolMessage, 'tool_message')
204
+
205
+ toolResults.push({ name, result })
206
+
207
+ } catch (error) {
208
+ const errorMsg = `Errore nell'esecuzione di ${name}: ${error.message}`
209
+ console.log(`❌ Tool Error: ${errorMsg}`)
210
+
211
+ const toolMessage = {
212
+ role: 'tool',
213
+ tool_call_id: toolCall.id,
214
+ name,
215
+ content: errorMsg
216
+ }
217
+ this.messages.push(toolMessage)
218
+
219
+ // Emetti evento per il messaggio tool di errore
220
+ this._emitMessage(toolMessage, 'tool_message')
221
+
222
+ toolResults.push({ name, error: errorMsg })
223
+ }
224
+ }
225
+
226
+ return {
227
+ type: 'tool_calls',
228
+ tool_calls: msg.tool_calls,
229
+ results: toolResults
230
+ }
231
+ }
232
+
233
+ /**
234
+ * Versione streamable di run() che emette eventi per ogni messaggio
235
+ * Usa questa versione quando vuoi ricevere eventi in tempo reale
236
+ */
237
+ async runStream(userMessage) {
238
+ if (this.verbose) {
239
+ console.log('🚀 Iniziando elaborazione in streaming...')
240
+ console.log(`📝 User Input: "${userMessage}"`)
241
+ console.log('─'.repeat(60))
242
+ }
243
+
244
+ // Emetti evento di inizio
245
+ this.emit('start', { userMessage })
246
+
247
+ this.addUserMessage(userMessage)
248
+
249
+ let iterations = 0
250
+ while (iterations < this.maxIterations) {
251
+ iterations++
252
+ if (this.verbose) {
253
+ console.log(`\n🔄 Iterazione ${iterations}:`)
254
+ }
255
+
256
+ // Emetti evento di iterazione
257
+ this.emit('iteration', { iteration: iterations })
258
+
259
+ const result = await this.step()
260
+
261
+ if (result.type === 'response') {
262
+ if (this.verbose) {
263
+ console.log('─'.repeat(60))
264
+ console.log(`✅ Elaborazione completata in ${iterations} iterazione${iterations > 1 ? 'i' : ''}`)
265
+ }
266
+
267
+ // Emetti evento di completamento
268
+ this.emit('complete', {
269
+ content: result.content,
270
+ role: result.role,
271
+ iterations,
272
+ messages: this.getHistory()
273
+ })
274
+
275
+ return {
276
+ content: result.content,
277
+ role: result.role,
278
+ iterations,
279
+ messages: this.getHistory()
280
+ }
281
+ }
282
+
283
+ // Se ci sono stati tool calls, continua il loop
284
+ if (result.type === 'tool_calls' && this.verbose) {
285
+ console.log('↻ Continuando con la prossima iterazione...')
286
+ }
287
+ }
288
+
289
+ const error = new Error(`Raggiunto il limite massimo di iterazioni (${this.maxIterations})`)
290
+ this.emit('error', error)
291
+ throw error
292
+ }
293
+
294
+ /**
295
+ * Processa un messaggio utente completo con tutti i tool calls necessari
296
+ */
297
+ async run(userMessage) {
298
+ if (this.verbose) {
299
+ console.log('🚀 Iniziando elaborazione...')
300
+ console.log(`📝 User Input: "${userMessage}"`)
301
+ console.log('─'.repeat(60))
302
+ }
303
+
304
+ this.addUserMessage(userMessage)
305
+
306
+ let iterations = 0
307
+ while (iterations < this.maxIterations) {
308
+ iterations++
309
+ if (this.verbose) {
310
+ console.log(`\n🔄 Iterazione ${iterations}:`)
311
+ }
312
+
313
+ const result = await this.step()
314
+
315
+ if (result.type === 'response') {
316
+ if (this.verbose) {
317
+ console.log('─'.repeat(60))
318
+ console.log(`✅ Elaborazione completata in ${iterations} iterazione${iterations > 1 ? 'i' : ''}`)
319
+ }
320
+
321
+ return {
322
+ content: result.content,
323
+ role: result.role,
324
+ iterations,
325
+ messages: this.getHistory()
326
+ }
327
+ }
328
+
329
+ // Se ci sono stati tool calls, continua il loop
330
+ if (result.type === 'tool_calls' && this.verbose) {
331
+ console.log('↻ Continuando con la prossima iterazione...')
332
+ }
333
+ }
334
+
335
+ throw new Error(`Raggiunto il limite massimo di iterazioni (${this.maxIterations})`)
336
+ }
337
+
338
+ /**
339
+ * Alias per compatibilità con il codice esistente
340
+ */
341
+ async processMessage(userMessage) {
342
+ const result = await this.run(userMessage)
343
+ return result.content
344
+ }
345
+
346
+ /**
347
+ * Resetta la conversazione mantenendo solo il system prompt
348
+ */
349
+ reset() {
350
+ this.messages = []
351
+ if (this.systemPrompt) {
352
+ this.messages.push({ role: 'system', content: this.systemPrompt })
353
+ }
354
+ }
355
+
356
+ /**
357
+ * Ottiene la cronologia dei messaggi
358
+ */
359
+ getHistory() {
360
+ return [...this.messages]
361
+ }
362
+
363
+ /**
364
+ * Imposta la cronologia dei messaggi
365
+ */
366
+ setHistory(messages) {
367
+ if (!Array.isArray(messages)) {
368
+ throw new Error('La cronologia deve essere un array di messaggi')
369
+ }
370
+
371
+ // Validazione dei messaggi
372
+ for (const msg of messages) {
373
+ if (!msg.role || !['system', 'user', 'assistant', 'tool'].includes(msg.role)) {
374
+ throw new Error(`Ruolo del messaggio non valido: ${msg.role}`)
375
+ }
376
+ if (msg.content === undefined && msg.role !== 'assistant') {
377
+ throw new Error('Il messaggio deve avere un contenuto')
378
+ }
379
+ }
380
+
381
+ this.messages = [...messages]
382
+
383
+ // Ricostruisci la mappa dei tools se ci sono tool calls nella cronologia
384
+ this.toolMap.clear()
385
+ this.tools.forEach(tool => {
386
+ this.toolMap.set(tool.name, tool)
387
+ })
388
+ }
389
+
390
+ /**
391
+ * Aggiunge messaggi alla cronologia esistente
392
+ */
393
+ appendToHistory(messages) {
394
+ if (!Array.isArray(messages)) {
395
+ throw new Error('I messaggi devono essere un array')
396
+ }
397
+
398
+ for (const msg of messages) {
399
+ if (!msg.role || !['system', 'user', 'assistant', 'tool'].includes(msg.role)) {
400
+ throw new Error(`Ruolo del messaggio non valido: ${msg.role}`)
401
+ }
402
+ }
403
+
404
+ this.messages.push(...messages)
405
+ }
406
+
407
+ /**
408
+ * Ottiene una versione leggibile della cronologia
409
+ */
410
+ getReadableHistory() {
411
+ return this.messages
412
+ .filter(msg => msg.role !== 'tool') // Filtra i messaggi dei tools per leggibilità
413
+ .map(msg => {
414
+ const role = msg.role === 'user' ? '👤 Utente' :
415
+ msg.role === 'assistant' ? '🤖 Assistant' :
416
+ msg.role === 'system' ? '⚙️ System' : msg.role
417
+ return `${role}: ${msg.content || '[Tool calls]'}`
418
+ })
419
+ .join('\n\n')
420
+ }
421
+
422
+ /**
423
+ * Ottiene statistiche sulla cronologia
424
+ */
425
+ getHistoryStats() {
426
+ const stats = {
427
+ total: this.messages.length,
428
+ user: 0,
429
+ assistant: 0,
430
+ system: 0,
431
+ tool: 0,
432
+ toolCalls: 0
433
+ }
434
+
435
+ this.messages.forEach(msg => {
436
+ stats[msg.role] = (stats[msg.role] || 0) + 1
437
+ if (msg.tool_calls) {
438
+ stats.toolCalls += msg.tool_calls.length
439
+ }
440
+ })
441
+
442
+ return stats
443
+ }
444
+
445
+ /**
446
+ * Ottiene l'ultimo messaggio
447
+ */
448
+ getLastMessage() {
449
+ return this.messages[this.messages.length - 1] || null
450
+ }
451
+
452
+ /**
453
+ * Aggiunge un tool alla lista
454
+ */
455
+ addTool(tool) {
456
+ this.tools.push(tool)
457
+ this.toolMap.set(tool.name, tool)
458
+ }
459
+
460
+ /**
461
+ * Rimuove un tool dalla lista
462
+ */
463
+ removeTool(toolName) {
464
+ this.tools = this.tools.filter(tool => tool.name !== toolName)
465
+ this.toolMap.delete(toolName)
466
+ }
467
+
468
+ /**
469
+ * Ottiene la lista dei tools disponibili
470
+ */
471
+ getTools() {
472
+ return [...this.tools]
473
+ }
474
+
475
+ /**
476
+ * Imposta la modalità debug
477
+ */
478
+ setDebug(debug) {
479
+ this.debug = debug
480
+ }
481
+
482
+ /**
483
+ * Imposta la modalità verbose (mostra thoughts e observations)
484
+ */
485
+ setVerbose(verbose) {
486
+ this.verbose = verbose
487
+ }
488
+
489
+ /**
490
+ * Ottiene lo stato della modalità verbose
491
+ */
492
+ isVerbose() {
493
+ return this.verbose
494
+ }
495
+ }
package/src/Tool.js ADDED
@@ -0,0 +1,94 @@
1
+ import { z } from 'zod'
2
+
3
+ /**
4
+ * Classe base per definire un tool compatibile con @openai/agents
5
+ */
6
+ export class Tool {
7
+ constructor(options) {
8
+ this.name = options.name
9
+ this.description = options.description
10
+ this.parameters = options.parameters
11
+ this.handler = options.handler
12
+
13
+ // Supporto per schema Zod (opzionale)
14
+ if (options.schema) {
15
+ this.schema = options.schema
16
+ this.parameters = this.zodToJsonSchema(options.schema)
17
+ }
18
+ }
19
+
20
+ /**
21
+ * Converte uno schema Zod in JSON Schema per OpenAI
22
+ */
23
+ zodToJsonSchema(schema) {
24
+ // Implementazione semplificata per gli schemi di base
25
+ if (schema._def.typeName === 'ZodObject') {
26
+ const properties = {}
27
+ const required = []
28
+
29
+ for (const [key, value] of Object.entries(schema.shape)) {
30
+ if (value._def.typeName === 'ZodString') {
31
+ properties[key] = {
32
+ type: 'string',
33
+ description: value._def.description || `${key} parameter`
34
+ }
35
+ } else if (value._def.typeName === 'ZodNumber') {
36
+ properties[key] = {
37
+ type: 'number',
38
+ description: value._def.description || `${key} parameter`
39
+ }
40
+ }
41
+
42
+ if (!value.isOptional()) {
43
+ required.push(key)
44
+ }
45
+ }
46
+
47
+ return {
48
+ type: 'object',
49
+ properties,
50
+ required
51
+ }
52
+ }
53
+
54
+ return this.parameters
55
+ }
56
+
57
+ /**
58
+ * Restituisce la definizione del tool per OpenAI (compatibile con @openai/agents)
59
+ */
60
+ getDefinition() {
61
+ return {
62
+ type: 'function',
63
+ function: {
64
+ name: this.name,
65
+ description: this.description,
66
+ parameters: this.parameters
67
+ }
68
+ }
69
+ }
70
+
71
+ /**
72
+ * Esegue il tool con gli argomenti forniti
73
+ */
74
+ async execute(args) {
75
+ // Validazione con schema Zod se disponibile
76
+ if (this.schema) {
77
+ try {
78
+ const validatedArgs = this.schema.parse(args)
79
+ return await this.handler(validatedArgs)
80
+ } catch (error) {
81
+ throw new Error(`Validation error: ${error.message}`)
82
+ }
83
+ }
84
+
85
+ return await this.handler(args)
86
+ }
87
+
88
+ /**
89
+ * Alias per compatibilità con @openai/agents
90
+ */
91
+ async run(args) {
92
+ return await this.execute(args)
93
+ }
94
+ }
package/src/cli.js ADDED
@@ -0,0 +1,155 @@
1
+ /**
2
+ * Utility functions for AI Agent Toolkit
3
+ */
4
+
5
+ import readline from 'readline'
6
+ import fs from 'fs'
7
+
8
+ /**
9
+ * Creates a complete chat interface with readline
10
+ */
11
+ export function createChatInterface(agent, options = {}) {
12
+ const {
13
+ prompt = '💬 Tu: ',
14
+ welcomeMessage = '🤖 Assistant: Ciao! Come posso aiutarti oggi?',
15
+ exitCommands = ['/exit', '/quit'],
16
+ showHelp = true
17
+ } = options
18
+
19
+ const rl = readline.createInterface({
20
+ input: process.stdin,
21
+ output: process.stdout,
22
+ prompt: `\n${prompt}`
23
+ })
24
+
25
+ // Special commands handler
26
+ function handleSpecialCommands(input) {
27
+ const command = input.toLowerCase().trim()
28
+
29
+ if (exitCommands.includes(command)) {
30
+ console.log('\n👋 Arrivederci!')
31
+ rl.close()
32
+ return true
33
+ }
34
+
35
+ switch (command) {
36
+ case '/reset':
37
+ agent.reset()
38
+ console.log('\n🔄 Conversazione resettata!')
39
+ return true
40
+
41
+ case '/history':
42
+ console.log('\n📚 Cronologia:')
43
+ console.log(agent.getReadableHistory?.() || JSON.stringify(agent.getHistory(), null, 2))
44
+ return true
45
+
46
+ case '/tools':
47
+ console.log('\n🛠 Tools disponibili:')
48
+ agent.getTools().forEach(tool => {
49
+ console.log(` • ${tool.name}: ${tool.description}`)
50
+ })
51
+ return true
52
+
53
+ case '/verbose on':
54
+ if (agent.setVerbose) {
55
+ agent.setVerbose(true)
56
+ console.log('\n📢 Modalità verbose attivata')
57
+ }
58
+ return true
59
+
60
+ case '/verbose off':
61
+ if (agent.setVerbose) {
62
+ agent.setVerbose(false)
63
+ console.log('\n🔇 Modalità verbose disattivata')
64
+ }
65
+ return true
66
+
67
+ case '/help':
68
+ showHelpMessage()
69
+ return true
70
+
71
+ default:
72
+ return false
73
+ }
74
+ }
75
+
76
+ function showHelpMessage() {
77
+ console.log('\n📖 Comandi disponibili:')
78
+ console.log(` • ${exitCommands.join('/')} - Esci dalla chat`)
79
+ console.log(' • /reset - Resetta la conversazione')
80
+ console.log(' • /history - Mostra cronologia')
81
+ console.log(' • /tools - Lista dei tools disponibili')
82
+ console.log(' • /verbose on/off - Attiva/disattiva modalità verbose')
83
+ console.log(' • /help - Mostra questo aiuto')
84
+ }
85
+
86
+ // Main input handler
87
+ async function handleInput(input) {
88
+ if (handleSpecialCommands(input)) {
89
+ return
90
+ }
91
+
92
+ if (input.trim() === '') {
93
+ return
94
+ }
95
+
96
+ try {
97
+ const result = await agent.run(input)
98
+ console.log(`\n🤖 Assistant: ${result.content}`)
99
+ } catch (error) {
100
+ console.error('\n❌ Errore:', error.message)
101
+ }
102
+ }
103
+
104
+ // Event listeners
105
+ rl.on('line', async (input) => {
106
+ await handleInput(input)
107
+ rl.prompt()
108
+ })
109
+
110
+ rl.on('close', () => {
111
+ console.log('\n👋 Sessione terminata!')
112
+ process.exit(0)
113
+ })
114
+
115
+ // Start the chat
116
+ console.log(welcomeMessage)
117
+ if (showHelp) {
118
+ showHelpMessage()
119
+ }
120
+ rl.prompt()
121
+
122
+ return {
123
+ rl,
124
+ close: () => rl.close(),
125
+ handleInput
126
+ }
127
+ }
128
+
129
+ /**
130
+ * Save conversation history to file
131
+ */
132
+ export function saveHistory(agent, filename) {
133
+ if (!filename) {
134
+ filename = `chat_history_${new Date().toISOString().slice(0, 19).replace(/:/g, '-')}.json`
135
+ }
136
+
137
+ const history = agent.getHistory()
138
+ fs.writeFileSync(filename, JSON.stringify(history, null, 2))
139
+ return filename
140
+ }
141
+
142
+ /**
143
+ * Load conversation history from file
144
+ */
145
+ export function loadHistory(agent, filename) {
146
+ if (!fs.existsSync(filename)) {
147
+ throw new Error(`File not found: ${filename}`)
148
+ }
149
+
150
+ const historyData = fs.readFileSync(filename, 'utf8')
151
+ const history = JSON.parse(historyData)
152
+
153
+ agent.setHistory(history)
154
+ return history
155
+ }
package/src/index.js ADDED
@@ -0,0 +1,59 @@
1
+ /**
2
+ * AI Agent Toolkit - Main Entry Point
3
+ *
4
+ * A powerful AI agent toolkit compatible with @openai/agents
5
+ * for building conversational AI with tool calling support.
6
+ *
7
+ * @example
8
+ * ```javascript
9
+ * import { Agent, Tool } from '@yourname/ai-agent-toolkit'
10
+ *
11
+ * // Create a simple tool
12
+ * const weatherTool = new Tool({
13
+ * name: 'get_weather',
14
+ * description: 'Get weather information for a city',
15
+ * schema: z.object({
16
+ * city: z.string().describe('City name')
17
+ * }),
18
+ * handler: async ({ city }) => `Weather in ${city}: 25°C, sunny`
19
+ * })
20
+ *
21
+ * // Create an agent
22
+ * const agent = new Agent({
23
+ * model: 'gpt-4',
24
+ * apiKey: process.env.OPENAI_API_KEY,
25
+ * instructions: 'You are a helpful weather assistant.',
26
+ * tools: [weatherTool]
27
+ * })
28
+ *
29
+ * // Use the agent
30
+ * const response = await agent.run('What\'s the weather in Rome?')
31
+ * console.log(response.content)
32
+ * ```
33
+ */
34
+
35
+ // Core exports
36
+ export { Agent } from './Agent.js'
37
+ export { Tool } from './Tool.js'
38
+
39
+ // Utility functions
40
+ export { createChatInterface } from './cli.js'
41
+
42
+ /**
43
+ * Quick setup function for common use cases
44
+ */
45
+ export function createAgent(options) {
46
+ return new Agent(options)
47
+ }
48
+
49
+ /**
50
+ * Quick tool creation function
51
+ */
52
+ export function createTool(options) {
53
+ return new Tool(options)
54
+ }
55
+
56
+ /**
57
+ * Version information
58
+ */
59
+ export const version = '1.0.0'