@lvx74/openrrouter-ai-agent 1.0.6 β†’ 1.0.9

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@lvx74/openrrouter-ai-agent",
3
- "version": "1.0.6",
3
+ "version": "1.0.9",
4
4
  "description": "A powerful AI agent toolkit compatible with @openai/agents for building conversational AI with tool calling support using OpenRouter",
5
5
  "type": "module",
6
6
  "main": "src/index.js",
package/src/Agent.js CHANGED
@@ -1,6 +1,7 @@
1
1
  import 'dotenv/config'
2
2
  import OpenAI from 'openai'
3
3
  import { EventEmitter } from 'events'
4
+ import { checkAndCompressHistory } from './lib/utils.js'
4
5
 
5
6
  /**
6
7
  * Classe Agent compatibile con @openai/agents per gestire conversazioni con AI e tool calls
@@ -90,6 +91,12 @@ export class Agent extends EventEmitter {
90
91
  */
91
92
  async step() {
92
93
  const toolDefinitions = this.getToolDefinitions()
94
+ if (this.verbose) {
95
+ console.log('Esecuzione step con', this.messages.length, 'messaggi e', toolDefinitions.length, 'tools')
96
+ console.log('Ultimo messaggio:', this.getLastMessage())
97
+ console.log('Tools:', toolDefinitions.map(t => t.name).join(', '))
98
+ }
99
+ this.messages = await checkAndCompressHistory(this.messages)
93
100
 
94
101
  const res = await this.openai.chat.completions.create({
95
102
  model: this.model,
@@ -98,7 +105,9 @@ export class Agent extends EventEmitter {
98
105
  tool_choice: toolDefinitions.length > 0 ? 'auto' : undefined,
99
106
  temperature: this.temperature
100
107
  })
101
-
108
+ if (res.error) {
109
+ throw new Error(`OpenAI API Error: ${res.error || 'Unknown error'}`)
110
+ }
102
111
  const msg = res.choices[0].message
103
112
 
104
113
  if (this.debug) {
package/src/Tool.js CHANGED
@@ -83,7 +83,7 @@ export class Tool {
83
83
  }
84
84
  }
85
85
 
86
- return await this.handler(args)
86
+ return await this.handler(args, session)
87
87
  }
88
88
 
89
89
  /**
package/src/cli.js CHANGED
@@ -18,14 +18,16 @@ export function createChatInterface(agent, options = {}) {
18
18
  historyFile = null
19
19
  } = options
20
20
 
21
- if (historyFile && !fs.existsSync(historyFile)) {
21
+ if (historyFile && fs.existsSync(historyFile)) {
22
22
  try {
23
- agent.setHistory(fs.readFileSync(historyFile, 'utf8'))
23
+ agent.setHistory(JSON.parse(fs.readFileSync(historyFile, 'utf8')))
24
24
  console.log(`\nπŸ“œ Cronologia caricata da ${historyFile}`)
25
25
  } catch (error) {
26
26
  console.error(`\n❌ Errore nel caricamento della cronologia: ${error.message}`)
27
27
  return
28
28
  }
29
+ }else if (historyFile) {
30
+ console.log(`\nπŸ“œ Cronologia non trovata, ne verrΓ  creata una nuova in ${historyFile}`)
29
31
  }
30
32
 
31
33
  const rl = readline.createInterface({
package/src/index.js CHANGED
@@ -39,6 +39,9 @@ export { Tool } from './Tool.js'
39
39
  // Utility functions
40
40
  export { createChatInterface } from './cli.js'
41
41
 
42
+ export { parseJSON, checkAndCompressHistory } from './lib/utils.js'
43
+ export { callAI } from './lib/ai-client.js'
44
+
42
45
  /**
43
46
  * Quick setup function for common use cases
44
47
  */
@@ -0,0 +1,27 @@
1
+ import OpenAI from 'openai'
2
+
3
+ export async function callAI(prompt, temperature = 0.7, model = process.env.MODEL ||'qwen/qwen3-4b:free', systemPrompt = 'You are a helpful assistant.', retry = 10, stream = false) {
4
+ const openai = new OpenAI({
5
+ apiKey: process.env.OPENROUTER_API_KEY,
6
+ baseURL: 'https://openrouter.ai/api/v1',
7
+ })
8
+ try {
9
+ const res = await openai.chat.completions.create({
10
+ model,
11
+ prompt: `${systemPrompt}\n\nUser: ${prompt}`,
12
+ temperature: temperature
13
+ })
14
+ if(res.error) {
15
+ throw new Error(`OpenAI API error: ${res.error.message}`);
16
+ }
17
+ const msg = res.choices[0].text.trim();
18
+ return msg
19
+ } catch (error) {
20
+ // Mostra il body della risposta se disponibile (es. 400)
21
+ if (error.response && error.response.data) {
22
+ console.error('API error response:', error.response.data);
23
+ }
24
+ console.error(error);
25
+ throw error;
26
+ }
27
+ }
@@ -0,0 +1,83 @@
1
+ import { callAI } from './ai-client.js';
2
+ import fs from 'fs';
3
+ import { fileURLToPath } from 'url';
4
+ import { dirname } from 'path';
5
+
6
+ const __filename = fileURLToPath(import.meta.url);
7
+ const __dirname = dirname(__filename);
8
+
9
+ export async function parseJSON(content, tryAgain = true, logger = console) {
10
+
11
+ logger.log('πŸ” parseJSON - Contenuto ricevuto (primi 200 caratteri):', content.substring(0, 200));
12
+
13
+ try {
14
+ // Tenta di trovare il primo blocco JSON valido
15
+ if (content.startsWith('\\boxed{')) {
16
+ content = content.replace('\\boxed{', '')
17
+ //rimouvi l'ultimo carttere
18
+ content = content.slice(0, -1);
19
+ }
20
+
21
+ const start = content.indexOf('{');
22
+ const end = content.lastIndexOf('}');
23
+
24
+ logger.log('πŸ” parseJSON - Posizioni JSON:', { start, end, contentLength: content.length });
25
+
26
+ if (start === -1 || end === -1) {
27
+ console.error('❌ Nessun blocco JSON trovato nel contenuto');
28
+ logger.log('πŸ“„ Contenuto completo:', content);
29
+ throw new Error('Blocco JSON non trovato');
30
+ }
31
+
32
+ let jsonString = content.substring(start, end + 1);
33
+ logger.log('πŸ” parseJSON - JSON estratto (primi 200 caratteri):', jsonString.substring(0, 200));
34
+
35
+ // Escape virgolette interne per evitare crash
36
+ jsonString = jsonString.replace(/:\s*"([^"]*?)"(?=\s*,|\s*})/g, (match, group) => {
37
+ const escaped = group.replace(/"/g, '\\"');
38
+ return `: "${escaped}"`;
39
+ });
40
+
41
+ try {
42
+ const parsed = JSON.parse(jsonString);
43
+ logger.log('βœ… parseJSON - JSON parsato con successo');
44
+ return parsed;
45
+ } catch (error) {
46
+ if (!tryAgain) {
47
+ console.error('⚠️ Errore nel parsing JSON (nessun retry):', error.message);
48
+ logger.log('πŸ“„ JSON fallito:', jsonString);
49
+ throw error; // Rilancia l'errore se non si vuole riprovare
50
+ }
51
+ logger.log('⚠️ Errore nel parsing JSON, tentativo di correzione:', error.message);
52
+ logger.log('πŸ“„ JSON malformato:', jsonString);
53
+
54
+ //cerco di correggerlo con ai
55
+ const prompt = `Correggi il seguente JSON malformato. Assicurati che sia un JSON valido e restituisci SOLO il JSON corretto, senza alcuna introduzione o spiegazione.\n\nJSON da correggere:\n${jsonString}`;
56
+ const corrected = await callAI(prompt, 0.2, process.env.MODEL_CORREZIONE_JSON || process.env.SMALL_MODEL || 'qwen/qwen3-4b:free', 2, false);
57
+ return parseJSON(corrected, false); // Riprova senza ulteriori tentativi
58
+ }
59
+ } catch (e) {
60
+ console.error('❌ Errore fatale nel parsing JSON:', e.message);
61
+ logger.log('πŸ“„ Contenuto originale completo:', content);
62
+ throw e;
63
+ }
64
+ }
65
+
66
+ export async function checkAndCompressHistory(history) {
67
+ if (!Array.isArray(history)) {
68
+ throw new Error('La cronologia deve essere un array');
69
+ }
70
+
71
+ if (history.length > (process.env.MAX_HISTORY_LENGTH || 50)) {
72
+ const latest = history.slice(-4);
73
+ const data = history.slice(0, -4).filter(m=>m.role !== 'system');
74
+ const prompt = fs.readFileSync(join(__dirname, '../prompts/compress_history_prompt.txt'), 'utf-8') + '\n' + JSON.stringify(data, null, 2);
75
+ const compressed = await callAI(prompt, 0.2, process.env.SMALL_MODEL );
76
+ const parsed = await parseJSON(compressed);
77
+ if (!parsed || !Array.isArray(parsed)) {
78
+ throw new Error('La risposta compressa non Γ¨ un array valido');
79
+ }
80
+ return [...parsed, ...latest];
81
+ }
82
+ return history;
83
+ }
@@ -0,0 +1,37 @@
1
+ Sei un assistente AI incaricato di riassumere la conversazione tra utente e assistente.
2
+ Riceverai una lista di messaggi in formato JSON (con campi role e content).
3
+ Il tuo compito Γ¨ comprimere la history mantenendo solo le informazioni essenziali, eliminando ridondanze e dettagli superflui, ma preservando il senso e il contesto della conversazione.
4
+ Restituisci sempre e solo un JSON valido, con la stessa struttura (array di messaggi, ciascuno con role e content), pronto per essere usato come nuova history.
5
+ Non aggiungere testo fuori dal JSON, nΓ© spiegazioni.
6
+ Se necessario, accorpa piΓΉ messaggi simili in uno solo, mantenendo la coerenza.
7
+ Esempio output:
8
+ [
9
+ {"role": "user", "content": "L'utente chiede informazioni su X."},
10
+ {"role": "assistant", "content": "Risposta sintetica su X."},
11
+ {
12
+ "role": "assistant",
13
+ "content": null,
14
+ "tool_calls": [
15
+ {
16
+ "id": "call_ieIgcfAISt6wlqizZPMiuQ",
17
+ "index": 11,
18
+ "type": "function",
19
+ "function": {
20
+ "name": "open_chapter",
21
+ "arguments": "{\"number\":1}"
22
+ },
23
+ "done": true,
24
+ "result": {
25
+ "success": true,
26
+ "message": "Adesso aggiungi contenuto sviluppando: bit mancante utilizzando append_to_chapter",
27
+ "next": {
28
+ "action": "append_to_chapter",
29
+ "number": 1,
30
+ "bit": 0
31
+ }
32
+ },
33
+ "execution_time": 1
34
+ }
35
+ ]
36
+ }
37
+ ]