@danieltmn/tamnora 0.6.1 → 0.6.2

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/CHANGELOG.md CHANGED
@@ -5,6 +5,12 @@ Todos los cambios relevantes de este proyecto se documentan en este archivo.
5
5
  El formato sigue [Keep a Changelog](https://keepachangelog.com/es-ES/1.1.0/)
6
6
  y el proyecto usa [Versionado Semantico](https://semver.org/lang/es/).
7
7
 
8
+ ## [0.6.2] - 2026-10-05
9
+
10
+ ### Cambios
11
+
12
+ - fix(telegram): proveedor de transcripcion dedicado y error claro en la voz
13
+
8
14
  ## [0.6.1] - 2026-10-05
9
15
 
10
16
  ### Cambios
@@ -2399,6 +2399,7 @@ const skillForm = $('#skill-form');
2399
2399
  const tgTokenEl = $('#tg-token');
2400
2400
  const tgChatsEl = $('#tg-chats');
2401
2401
  const tgTranscribeEl = $('#tg-transcribe');
2402
+ const tgTranscribeProviderEl = $('#tg-transcribe-provider');
2402
2403
  const tgStatusEl = $('#tg-status');
2403
2404
 
2404
2405
  function setStatus(node, text, cls = '') {
@@ -2631,6 +2632,9 @@ async function refreshChannels() {
2631
2632
  if (tgTranscribeEl && document.activeElement !== tgTranscribeEl) {
2632
2633
  tgTranscribeEl.value = tg.transcribeModel ?? 'whisper-1';
2633
2634
  }
2635
+ if (tgTranscribeProviderEl && document.activeElement !== tgTranscribeProviderEl) {
2636
+ tgTranscribeProviderEl.value = tg.transcribeProvider ?? '';
2637
+ }
2634
2638
  if (tgTokenEl && document.activeElement !== tgTokenEl) {
2635
2639
  tgTokenEl.value = '';
2636
2640
  tgTokenEl.placeholder = tg.tokenHint ? `${tg.tokenHint} (dejar vacío para no cambiar)` : '123456:ABC…';
@@ -2643,6 +2647,7 @@ async function submitChannel() {
2643
2647
  const body = {
2644
2648
  allowedChatIds: tgChatsEl?.value ?? '',
2645
2649
  transcribeModel: tgTranscribeEl?.value.trim() || 'whisper-1',
2650
+ transcribeProvider: tgTranscribeProviderEl?.value.trim() || '',
2646
2651
  };
2647
2652
  const token = tgTokenEl.value.trim();
2648
2653
  if (token) body.token = token;
@@ -33,7 +33,7 @@
33
33
  </head>
34
34
  <body>
35
35
  <header class="topbar">
36
- <div class="brand"><span class="logo"><span class="logo-t">T</span><span class="logo-h">H</span></span> <span class="ver">v0.6.1</span></div>
36
+ <div class="brand"><span class="logo"><span class="logo-t">T</span><span class="logo-h">H</span></span> <span class="ver">v0.6.2</span></div>
37
37
  <nav class="tabs">
38
38
  <button class="tab active" data-tab="chat">chat</button>
39
39
  <button class="tab" data-tab="providers">proveedores</button>
@@ -394,6 +394,10 @@
394
394
  <label class="field"><span>chats autorizados (ids separados por coma)</span>
395
395
  <input id="tg-chats" spellcheck="false" placeholder="123456789, -1001234567890" />
396
396
  </label>
397
+ <label class="field"><span>proveedor de transcripción (voz, opcional)</span>
398
+ <input id="tg-transcribe-provider" spellcheck="false"
399
+ placeholder="openai · groq — vacío = proveedor activo" />
400
+ </label>
397
401
  <label class="field"><span>modelo de transcripción (voz)</span>
398
402
  <input id="tg-transcribe" spellcheck="false" placeholder="whisper-1" />
399
403
  </label>
@@ -36,6 +36,7 @@ export function createChannelsManager(ctx, { log = () => {}, enabled = true } =
36
36
  if (cfg.token) tg.token = cfg.token;
37
37
  if (Array.isArray(cfg.allowedChatIds) && cfg.allowedChatIds.length) tg.allowedChatIds = cfg.allowedChatIds;
38
38
  if (cfg.transcribeModel) tg.transcribeModel = cfg.transcribeModel;
39
+ if (cfg.transcribeProvider) tg.transcribeProvider = cfg.transcribeProvider;
39
40
  return tg;
40
41
  };
41
42
 
@@ -43,7 +44,12 @@ export function createChannelsManager(ctx, { log = () => {}, enabled = true } =
43
44
  const tg = readConfig();
44
45
  const token = tg.token || process.env.TAMNORA_TELEGRAM_TOKEN || '';
45
46
  const allowedChatIds = parseAllowed(tg.allowedChatIds ?? process.env.TAMNORA_TELEGRAM_CHAT_IDS ?? []);
46
- return { token, allowedChatIds, transcribeModel: tg.transcribeModel };
47
+ return {
48
+ token,
49
+ allowedChatIds,
50
+ transcribeModel: tg.transcribeModel,
51
+ transcribeProvider: tg.transcribeProvider,
52
+ };
47
53
  }
48
54
 
49
55
  function stop() {
@@ -56,9 +62,9 @@ export function createChannelsManager(ctx, { log = () => {}, enabled = true } =
56
62
  function load() {
57
63
  stop();
58
64
  if (!enabled) return { enabled: false, channels: [] };
59
- const { token, allowedChatIds, transcribeModel } = effective();
65
+ const { token, allowedChatIds, transcribeModel, transcribeProvider } = effective();
60
66
  const channels = [];
61
- const telegram = startTelegramChannel(ctx, { token, allowedChatIds, transcribeModel, log });
67
+ const telegram = startTelegramChannel(ctx, { token, allowedChatIds, transcribeModel, transcribeProvider, log });
62
68
  if (telegram) channels.push(telegram);
63
69
  const unsubscribe = ctx.runs?.subscribeAll?.((sessionId, event) => {
64
70
  for (const channel of channels) {
@@ -75,12 +81,13 @@ export function createChannelsManager(ctx, { log = () => {}, enabled = true } =
75
81
  }
76
82
 
77
83
  function status() {
78
- const { token, allowedChatIds, transcribeModel } = effective();
84
+ const { token, allowedChatIds, transcribeModel, transcribeProvider } = effective();
79
85
  return {
80
86
  tokenSet: Boolean(token),
81
87
  tokenHint: tokenHint(token),
82
88
  allowedChatIds,
83
89
  transcribeModel: transcribeModel || 'whisper-1',
90
+ transcribeProvider: transcribeProvider || '',
84
91
  running: Boolean(active && active.channels.length),
85
92
  };
86
93
  }
@@ -82,7 +82,10 @@ export function completionFooter(stopReason, rounds, toolCalls, durationMs) {
82
82
  /**
83
83
  * Arranca el canal de Telegram. Devuelve `{ stop, notify }` o `null` sin token.
84
84
  */
85
- export function startTelegramChannel(ctx, { token, allowedChatIds = [], transcribeModel = 'whisper-1', log = () => {} } = {}) {
85
+ export function startTelegramChannel(
86
+ ctx,
87
+ { token, allowedChatIds = [], transcribeModel = 'whisper-1', transcribeProvider = '', log = () => {} } = {}
88
+ ) {
86
89
  if (!token) return null;
87
90
  const allowed = new Set(allowedChatIds.map(String));
88
91
  const controllers = new Map();
@@ -130,10 +133,11 @@ export function startTelegramChannel(ctx, { token, allowedChatIds = [], transcri
130
133
  }
131
134
 
132
135
  async function transcribe(buffer, filename) {
133
- const providerId = ctx.effectiveProviderId();
134
- const baseUrl = (ctx.providerSettings(providerId).baseUrl || '').replace(/\/+$/, '');
136
+ const providerId = transcribeProvider || ctx.effectiveProviderId();
137
+ const settings = ctx.providerSettings(providerId) ?? {};
138
+ const baseUrl = (settings.baseUrl || '').replace(/\/+$/, '');
135
139
  const apiKey = ctx.apiKeyFor(providerId);
136
- if (!baseUrl) throw new Error('el proveedor no tiene baseUrl para transcribir');
140
+ if (!baseUrl) throw new Error(`el proveedor "${providerId}" no tiene baseUrl para transcribir`);
137
141
  const form = new FormData();
138
142
  form.append('file', new Blob([buffer]), filename);
139
143
  form.append('model', transcribeModel);
@@ -142,7 +146,15 @@ export function startTelegramChannel(ctx, { token, allowedChatIds = [], transcri
142
146
  headers: apiKey ? { Authorization: `Bearer ${apiKey}` } : {},
143
147
  body: form,
144
148
  });
145
- if (!res.ok) throw new Error(`transcripción falló (HTTP ${res.status})`);
149
+ if (!res.ok) {
150
+ const hint =
151
+ res.status === 404
152
+ ? ` — "${providerId}" no expone /audio/transcriptions`
153
+ : '';
154
+ throw new Error(
155
+ `transcripción falló (HTTP ${res.status})${hint}. Elegí un proveedor con voz (OpenAI, Groq…) en Ajustes → Telegram.`
156
+ );
157
+ }
146
158
  const data = await res.json();
147
159
  return String(data.text ?? '').trim();
148
160
  }
@@ -30,6 +30,7 @@ export function registerChannels(route, ctx) {
30
30
  }
31
31
  if ('allowedChatIds' in body) next.allowedChatIds = sanitizeAllowed(body.allowedChatIds);
32
32
  if ('transcribeModel' in body) next.transcribeModel = String(body.transcribeModel ?? '').trim() || 'whisper-1';
33
+ if ('transcribeProvider' in body) next.transcribeProvider = String(body.transcribeProvider ?? '').trim();
33
34
 
34
35
  app.telegram = next;
35
36
  writeApp(app);
@@ -287,10 +287,11 @@ test('configures Telegram from the console (masked token)', async () => {
287
287
  res = await fetch(`${base}/api/channels/telegram`, {
288
288
  method: 'PUT',
289
289
  headers: json,
290
- body: JSON.stringify({ token: '123456:ABCDEFghij' }),
290
+ body: JSON.stringify({ token: '123456:ABCDEFghij', transcribeProvider: 'openai' }),
291
291
  });
292
292
  data = await res.json();
293
293
  assert.equal(data.telegram.tokenSet, true);
294
+ assert.equal(data.telegram.transcribeProvider, 'openai');
294
295
  assert.match(data.telegram.tokenHint, /ghij$/);
295
296
  assert.doesNotMatch(JSON.stringify(data), /ABCDEF/);
296
297
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@danieltmn/tamnora",
3
- "version": "0.6.1",
3
+ "version": "0.6.2",
4
4
  "description": "Harness local para IAs: agente CLI-first con TUI, consola web y evaluacion. Node puro, sin dependencias.",
5
5
  "type": "module",
6
6
  "packageManager": "pnpm@11.25.0",