@lvx74/openrrouter-ai-agent 1.0.8 β†’ 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.8",
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
@@ -95,6 +96,8 @@ export class Agent extends EventEmitter {
95
96
  console.log('Ultimo messaggio:', this.getLastMessage())
96
97
  console.log('Tools:', toolDefinitions.map(t => t.name).join(', '))
97
98
  }
99
+ this.messages = await checkAndCompressHistory(this.messages)
100
+
98
101
  const res = await this.openai.chat.completions.create({
99
102
  model: this.model,
100
103
  messages: this.messages,
@@ -102,7 +105,9 @@ export class Agent extends EventEmitter {
102
105
  tool_choice: toolDefinitions.length > 0 ? 'auto' : undefined,
103
106
  temperature: this.temperature
104
107
  })
105
-
108
+ if (res.error) {
109
+ throw new Error(`OpenAI API Error: ${res.error || 'Unknown error'}`)
110
+ }
106
111
  const msg = res.choices[0].message
107
112
 
108
113
  if (this.debug) {
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
+ ]