obol-ai 0.3.15 → 0.3.17
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
|
@@ -1,3 +1,9 @@
|
|
|
1
|
+
## 0.3.16
|
|
2
|
+
- Merge pull request #7 from jestersimpps/fix/stt-whisper-transcribe
|
|
3
|
+
- fix: STT pipeline - add missing whisper_transcribe.py and fix media handler
|
|
4
|
+
- Merge pull request #6 from jestersimpps/fix/status-instant-tool-labels
|
|
5
|
+
- fix: replace async haiku status labels with instant sync formatToolCall
|
|
6
|
+
|
|
1
7
|
## 0.3.15
|
|
2
8
|
- replace impulse with news system, exact datetime for analysis follow-ups
|
|
3
9
|
- rewrite impulse prompt to sound like a friend, not an assistant
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "obol-ai",
|
|
3
|
-
"version": "0.3.
|
|
3
|
+
"version": "0.3.17",
|
|
4
4
|
"description": "Self-evolving AI assistant that learns, remembers, and acts on its own. Persistent vector memory, self-rewriting personality, proactive heartbeats.",
|
|
5
5
|
"main": "src/index.js",
|
|
6
6
|
"bin": {
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
"""
|
|
3
|
+
whisper_transcribe.py — Transcribe audio files using faster-whisper.
|
|
4
|
+
|
|
5
|
+
Called by stt.js as: python3 whisper_transcribe.py <audio_file_path>
|
|
6
|
+
|
|
7
|
+
Uses the 'tiny' model for fast CPU inference (~140MB).
|
|
8
|
+
Outputs the transcribed text to stdout.
|
|
9
|
+
Exits with code 1 on failure.
|
|
10
|
+
"""
|
|
11
|
+
|
|
12
|
+
import sys
|
|
13
|
+
|
|
14
|
+
def transcribe(file_path):
|
|
15
|
+
"""Transcribe an audio file and return the text."""
|
|
16
|
+
try:
|
|
17
|
+
from faster_whisper import WhisperModel
|
|
18
|
+
except ImportError:
|
|
19
|
+
print("ERROR: faster-whisper not installed. Run: pip3 install faster-whisper", file=sys.stderr)
|
|
20
|
+
sys.exit(1)
|
|
21
|
+
|
|
22
|
+
try:
|
|
23
|
+
model = WhisperModel("tiny", device="cpu", compute_type="int8")
|
|
24
|
+
segments, info = model.transcribe(file_path)
|
|
25
|
+
text = " ".join(segment.text.strip() for segment in segments).strip()
|
|
26
|
+
return text
|
|
27
|
+
except Exception as e:
|
|
28
|
+
print(f"ERROR: Transcription failed: {e}", file=sys.stderr)
|
|
29
|
+
sys.exit(1)
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
if __name__ == "__main__":
|
|
33
|
+
if len(sys.argv) < 2:
|
|
34
|
+
print("Usage: python3 whisper_transcribe.py <audio_file_path>", file=sys.stderr)
|
|
35
|
+
sys.exit(1)
|
|
36
|
+
|
|
37
|
+
file_path = sys.argv[1]
|
|
38
|
+
result = transcribe(file_path)
|
|
39
|
+
if result:
|
|
40
|
+
print(result)
|
|
41
|
+
else:
|
|
42
|
+
sys.exit(1)
|
package/src/status.js
CHANGED
|
@@ -1,5 +1,4 @@
|
|
|
1
1
|
const TERM_WIDTH = 25;
|
|
2
|
-
const _toolDescriptionCache = new Map();
|
|
3
2
|
|
|
4
3
|
function buildStatusHtml({ route, elapsed, toolStatus, title = 'OBOL' }) {
|
|
5
4
|
const pad = Math.max(0, TERM_WIDTH - title.length - 3);
|
|
@@ -28,25 +27,10 @@ function buildStatusHtml({ route, elapsed, toolStatus, title = 'OBOL' }) {
|
|
|
28
27
|
return `<pre>${lines.join('\n')}</pre>`;
|
|
29
28
|
}
|
|
30
29
|
|
|
31
|
-
function
|
|
32
|
-
|
|
33
|
-
const
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
return client.messages.create({
|
|
37
|
-
model: 'claude-haiku-4-5',
|
|
38
|
-
max_tokens: 30,
|
|
39
|
-
system: 'Describe this tool call in 3-8 words from the user\'s perspective. Present participle. No quotes, period, or emoji.',
|
|
40
|
-
messages: [{ role: 'user', content: `${toolName}: ${inputSummary}` }],
|
|
41
|
-
}).then(r => {
|
|
42
|
-
const desc = r.content[0]?.text?.trim() || null;
|
|
43
|
-
if (desc) _toolDescriptionCache.set(key, desc);
|
|
44
|
-
if (_toolDescriptionCache.size > 200) {
|
|
45
|
-
const first = _toolDescriptionCache.keys().next().value;
|
|
46
|
-
_toolDescriptionCache.delete(first);
|
|
47
|
-
}
|
|
48
|
-
return desc;
|
|
49
|
-
}).catch(() => null);
|
|
30
|
+
function formatToolCall(toolName, inputSummary) {
|
|
31
|
+
if (!inputSummary) return toolName;
|
|
32
|
+
const truncated = inputSummary.length > 40 ? inputSummary.slice(0, 37) + '...' : inputSummary;
|
|
33
|
+
return `${toolName} "${truncated}"`;
|
|
50
34
|
}
|
|
51
35
|
|
|
52
|
-
module.exports = { buildStatusHtml,
|
|
36
|
+
module.exports = { buildStatusHtml, formatToolCall, TERM_WIDTH };
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
const path = require('path');
|
|
2
2
|
const { getTenant } = require('../../tenant');
|
|
3
|
-
const { buildStatusHtml,
|
|
3
|
+
const { buildStatusHtml, formatToolCall } = require('../../status');
|
|
4
4
|
const media = require('../../media');
|
|
5
5
|
const { sendHtml, startTyping, splitMessage } = require('../utils');
|
|
6
6
|
const { MAX_MEDIA_SIZE, MEDIA_GROUP_DELAY_MS } = require('../constants');
|
|
@@ -43,9 +43,9 @@ async function processMediaItems(ctx, items, { config, allowedUsers, bot, create
|
|
|
43
43
|
imageBlocks.push(media.bufferToImageBlock(item.buffer, item.fileInfo.mimeType));
|
|
44
44
|
} else if (
|
|
45
45
|
(item.fileInfo.mediaType === 'voice' || item.fileInfo.mediaType === 'audio') &&
|
|
46
|
-
tenant.toolPrefs?.get?.('speech_to_text')?.enabled
|
|
46
|
+
tenant.toolPrefs?.get?.('speech_to_text')?.enabled !== false
|
|
47
47
|
) {
|
|
48
|
-
const { transcribe } = require('../../stt');
|
|
48
|
+
const { transcribe } = require('../../media/stt');
|
|
49
49
|
const transcription = await transcribe(savedPath);
|
|
50
50
|
nonImageParts.push(transcription
|
|
51
51
|
? `[Voice message transcription: ${transcription}]`
|
|
@@ -80,11 +80,9 @@ async function processMediaItems(ctx, items, { config, allowedUsers, bot, create
|
|
|
80
80
|
if (update.model) ri.model = update.model;
|
|
81
81
|
};
|
|
82
82
|
mediaChatCtx._onToolStart = (toolName, inputSummary) => {
|
|
83
|
-
status.setStatusText(
|
|
84
|
-
describeToolCall(tenant.claude.client, toolName, inputSummary).then(desc => {
|
|
85
|
-
if (desc) status.setStatusText(desc);
|
|
86
|
-
});
|
|
83
|
+
status.setStatusText(formatToolCall(toolName, inputSummary));
|
|
87
84
|
status.start();
|
|
85
|
+
status.pushUpdate();
|
|
88
86
|
};
|
|
89
87
|
mediaChatCtx._onLockTimeout = () => {
|
|
90
88
|
status.clear();
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
const { getTenant } = require('../../tenant');
|
|
2
|
-
const {
|
|
2
|
+
const { formatToolCall } = require('../../status');
|
|
3
3
|
const { sendHtml, startTyping, splitMessage } = require('../utils');
|
|
4
4
|
const { createChatContext, createStatusTracker } = require('./text');
|
|
5
5
|
|
|
@@ -92,11 +92,9 @@ async function processSpecial(ctx, prompt, deps) {
|
|
|
92
92
|
if (update.model) ri.model = update.model;
|
|
93
93
|
};
|
|
94
94
|
chatCtx._onToolStart = (toolName, inputSummary) => {
|
|
95
|
-
status.setStatusText(
|
|
96
|
-
describeToolCall(tenant.claude.client, toolName, inputSummary).then(desc => {
|
|
97
|
-
if (desc) status.setStatusText(desc);
|
|
98
|
-
});
|
|
95
|
+
status.setStatusText(formatToolCall(toolName, inputSummary));
|
|
99
96
|
status.start();
|
|
97
|
+
status.pushUpdate();
|
|
100
98
|
};
|
|
101
99
|
|
|
102
100
|
const { text: response, usage, model } = await tenant.claude.chat(prompt, chatCtx);
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
const { InlineKeyboard } = require('grammy');
|
|
2
2
|
const { getTenant } = require('../../tenant');
|
|
3
|
-
const { buildStatusHtml,
|
|
3
|
+
const { buildStatusHtml, formatToolCall } = require('../../status');
|
|
4
4
|
const { sendHtml, startTyping, splitMessage } = require('../utils');
|
|
5
5
|
const { TEXT_BUFFER_GAP_MS, TEXT_BUFFER_MAX_PARTS, TEXT_BUFFER_MAX_CHARS, TEXT_BUFFER_THRESHOLD } = require('../constants');
|
|
6
6
|
|
|
@@ -133,6 +133,12 @@ function createStatusTracker(ctx, botName) {
|
|
|
133
133
|
const html = buildStatusHtml({ route: routeInfo, elapsed, toolStatus: 'Formatting output', title });
|
|
134
134
|
ctx.api.editMessageText(ctx.chat.id, statusMsgId, html, { parse_mode: 'HTML' }).catch(() => {});
|
|
135
135
|
},
|
|
136
|
+
pushUpdate() {
|
|
137
|
+
if (!statusMsgId) return;
|
|
138
|
+
const elapsed = statusStart ? Math.round((Date.now() - statusStart) / 1000) : 0;
|
|
139
|
+
const html = buildStatusHtml({ route: routeInfo, elapsed, toolStatus: statusText, title });
|
|
140
|
+
ctx.api.editMessageText(ctx.chat.id, statusMsgId, html, { parse_mode: 'HTML', reply_markup: stopBtn }).catch(() => {});
|
|
141
|
+
},
|
|
136
142
|
deleteMsg() {
|
|
137
143
|
if (statusMsgId) ctx.api.deleteMessage(ctx.chat.id, statusMsgId).catch(() => {});
|
|
138
144
|
},
|
|
@@ -185,11 +191,9 @@ async function processTextMessage(ctx, fullMessage, { config, allowedUsers, bot,
|
|
|
185
191
|
if (update.model) ri.model = update.model;
|
|
186
192
|
};
|
|
187
193
|
chatContext._onToolStart = (toolName, inputSummary) => {
|
|
188
|
-
status.setStatusText(
|
|
189
|
-
describeToolCall(tenant.claude.client, toolName, inputSummary).then(desc => {
|
|
190
|
-
if (desc) status.setStatusText(desc);
|
|
191
|
-
});
|
|
194
|
+
status.setStatusText(formatToolCall(toolName, inputSummary));
|
|
192
195
|
status.start();
|
|
196
|
+
status.pushUpdate();
|
|
193
197
|
};
|
|
194
198
|
chatContext._onLockTimeout = () => {
|
|
195
199
|
status.clear();
|