obol-ai 0.2.18 → 0.2.20
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/.claude/settings.local.json +3 -2
- package/CHANGELOG.md +8 -0
- package/package.json +1 -1
- package/src/claude/chat.js +4 -4
- package/src/claude/client.js +7 -2
- package/src/claude/constants.js +1 -1
- package/src/telegram/bot.js +2 -0
- package/src/telegram/handlers/special.js +165 -0
package/CHANGELOG.md
CHANGED
|
@@ -1,3 +1,11 @@
|
|
|
1
|
+
## 0.2.20
|
|
2
|
+
- increase tool iterations to 100 and max tokens to 128K
|
|
3
|
+
- update changelog
|
|
4
|
+
|
|
5
|
+
## 0.2.19
|
|
6
|
+
- add location, venue, contact, poll message support
|
|
7
|
+
- update changelog
|
|
8
|
+
|
|
1
9
|
## 0.2.18
|
|
2
10
|
- remove evolution progress bar from status UI
|
|
3
11
|
- bidirectional bridge with reply button + memory_remove tool
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "obol-ai",
|
|
3
|
-
"version": "0.2.
|
|
3
|
+
"version": "0.2.20",
|
|
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": {
|
package/src/claude/chat.js
CHANGED
|
@@ -120,7 +120,7 @@ function createClaude(anthropicConfig, { personality, memory, userDir = OBOL_DIR
|
|
|
120
120
|
const toolDefs = runnableTools.map(({ run, ...def }) => def);
|
|
121
121
|
const probe = await client.messages.create({
|
|
122
122
|
model: activeModel,
|
|
123
|
-
max_tokens:
|
|
123
|
+
max_tokens: 131072,
|
|
124
124
|
system: systemPrompt,
|
|
125
125
|
messages: withCacheBreakpoints([...history]),
|
|
126
126
|
tools: toolDefs,
|
|
@@ -141,7 +141,7 @@ function createClaude(anthropicConfig, { personality, memory, userDir = OBOL_DIR
|
|
|
141
141
|
|
|
142
142
|
const runner = client.beta.messages.toolRunner({
|
|
143
143
|
model: activeModel,
|
|
144
|
-
max_tokens:
|
|
144
|
+
max_tokens: 131072,
|
|
145
145
|
system: systemPrompt,
|
|
146
146
|
messages: withCacheBreakpoints([...history]),
|
|
147
147
|
tools: runnableTools.length > 0 ? runnableTools : undefined,
|
|
@@ -168,7 +168,7 @@ function createClaude(anthropicConfig, { personality, memory, userDir = OBOL_DIR
|
|
|
168
168
|
{ type: 'text', text: 'You have used too many tool calls. Please provide a final response now based on what you have so far.' },
|
|
169
169
|
]);
|
|
170
170
|
const bailoutResponse = await client.messages.create({
|
|
171
|
-
model: activeModel, max_tokens:
|
|
171
|
+
model: activeModel, max_tokens: 131072, system: systemPrompt, messages: withCacheBreakpoints([...histories.get(chatId)]),
|
|
172
172
|
}, { signal: abortController.signal });
|
|
173
173
|
histories.pushAssistant(chatId, bailoutResponse.content);
|
|
174
174
|
trackUsage(bailoutResponse.usage);
|
|
@@ -182,7 +182,7 @@ function createClaude(anthropicConfig, { personality, memory, userDir = OBOL_DIR
|
|
|
182
182
|
vlog('[claude] No text in final response after tool use — forcing summary');
|
|
183
183
|
histories.pushUser(chatId, 'Provide a concise response to the user based on the tool results above.');
|
|
184
184
|
const summaryResponse = await client.messages.create({
|
|
185
|
-
model: activeModel, max_tokens:
|
|
185
|
+
model: activeModel, max_tokens: 131072, system: systemPrompt, messages: withCacheBreakpoints([...histories.get(chatId)]),
|
|
186
186
|
}, { signal: abortController.signal });
|
|
187
187
|
histories.pushAssistant(chatId, summaryResponse.content);
|
|
188
188
|
trackUsage(summaryResponse.usage);
|
package/src/claude/client.js
CHANGED
|
@@ -9,12 +9,17 @@ function createAnthropicClient(anthropicConfig, { useOAuth = true } = {}) {
|
|
|
9
9
|
authToken: anthropicConfig.oauth.accessToken,
|
|
10
10
|
defaultHeaders: {
|
|
11
11
|
'anthropic-dangerous-direct-browser-access': 'true',
|
|
12
|
-
'anthropic-beta': 'claude-code-20250219,oauth-2025-04-20',
|
|
12
|
+
'anthropic-beta': 'claude-code-20250219,oauth-2025-04-20,output-128k-2025-02-19',
|
|
13
13
|
},
|
|
14
14
|
});
|
|
15
15
|
}
|
|
16
16
|
if (anthropicConfig.apiKey) {
|
|
17
|
-
return new Anthropic({
|
|
17
|
+
return new Anthropic({
|
|
18
|
+
apiKey: anthropicConfig.apiKey,
|
|
19
|
+
defaultHeaders: {
|
|
20
|
+
'anthropic-beta': 'output-128k-2025-02-19',
|
|
21
|
+
},
|
|
22
|
+
});
|
|
18
23
|
}
|
|
19
24
|
throw new Error('No Anthropic credentials configured. Run: obol config');
|
|
20
25
|
}
|
package/src/claude/constants.js
CHANGED
package/src/telegram/bot.js
CHANGED
|
@@ -15,6 +15,7 @@ const secretsCommands = require('./commands/secrets');
|
|
|
15
15
|
const toolsCommands = require('./commands/tools');
|
|
16
16
|
const { registerTextHandler } = require('./handlers/text');
|
|
17
17
|
const { registerMediaHandler } = require('./handlers/media');
|
|
18
|
+
const { registerSpecialHandler } = require('./handlers/special');
|
|
18
19
|
const { registerCallbackHandler } = require('./handlers/callbacks');
|
|
19
20
|
|
|
20
21
|
function createBot(telegramConfig, config) {
|
|
@@ -107,6 +108,7 @@ function createBot(telegramConfig, config) {
|
|
|
107
108
|
const deps = { config, allowedUsers, bot, createAsk };
|
|
108
109
|
registerTextHandler(bot, deps);
|
|
109
110
|
registerMediaHandler(bot, telegramConfig, deps);
|
|
111
|
+
registerSpecialHandler(bot, deps);
|
|
110
112
|
registerCallbackHandler(bot, { config, pendingAsks, getTenant });
|
|
111
113
|
|
|
112
114
|
bot.catch((err) => {
|
|
@@ -0,0 +1,165 @@
|
|
|
1
|
+
const { getTenant } = require('../../tenant');
|
|
2
|
+
const { describeToolCall } = require('../../status');
|
|
3
|
+
const { sendHtml, startTyping, splitMessage } = require('../utils');
|
|
4
|
+
const { createChatContext, createStatusTracker } = require('./text');
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* @param {import('grammy').Context} ctx
|
|
8
|
+
* @returns {string}
|
|
9
|
+
*/
|
|
10
|
+
function buildLocationPrompt(ctx) {
|
|
11
|
+
const { latitude, longitude, live_period, heading, horizontal_accuracy } = ctx.message.location;
|
|
12
|
+
const parts = [`[User shared their location: lat ${latitude}, lng ${longitude}`];
|
|
13
|
+
if (live_period) parts.push(`live for ${live_period}s`);
|
|
14
|
+
if (heading !== undefined) parts.push(`heading ${heading}°`);
|
|
15
|
+
if (horizontal_accuracy !== undefined) parts.push(`accuracy ±${horizontal_accuracy}m`);
|
|
16
|
+
return parts.join(', ') + ']';
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* @param {import('grammy').Context} ctx
|
|
21
|
+
* @returns {string}
|
|
22
|
+
*/
|
|
23
|
+
function buildVenuePrompt(ctx) {
|
|
24
|
+
const { location, title, address, foursquare_id, google_place_id } = ctx.message.venue;
|
|
25
|
+
let prompt = `[User shared a venue: "${title}" at ${address} (${location.latitude}, ${location.longitude})`;
|
|
26
|
+
if (foursquare_id) prompt += `, Foursquare: ${foursquare_id}`;
|
|
27
|
+
if (google_place_id) prompt += `, Google: ${google_place_id}`;
|
|
28
|
+
return prompt + ']';
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
/**
|
|
32
|
+
* @param {import('grammy').Context} ctx
|
|
33
|
+
* @returns {string}
|
|
34
|
+
*/
|
|
35
|
+
function buildContactPrompt(ctx) {
|
|
36
|
+
const { phone_number, first_name, last_name, vcard } = ctx.message.contact;
|
|
37
|
+
const name = [first_name, last_name].filter(Boolean).join(' ');
|
|
38
|
+
let prompt = `[User shared a contact: ${name}, ${phone_number}`;
|
|
39
|
+
if (vcard) prompt += ` (vCard data included)`;
|
|
40
|
+
return prompt + ']';
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
/**
|
|
44
|
+
* @param {import('grammy').Context} ctx
|
|
45
|
+
* @returns {string}
|
|
46
|
+
*/
|
|
47
|
+
function buildPollPrompt(ctx) {
|
|
48
|
+
const { question, options, type, is_anonymous, allows_multiple_answers } = ctx.message.poll;
|
|
49
|
+
const opts = options.map((o, i) => `${i + 1}. ${o.text}`).join(', ');
|
|
50
|
+
let prompt = `[User shared a ${type || 'regular'} poll: "${question}" — Options: ${opts}`;
|
|
51
|
+
if (!is_anonymous) prompt += ', non-anonymous';
|
|
52
|
+
if (allows_multiple_answers) prompt += ', multiple answers allowed';
|
|
53
|
+
return prompt + ']';
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
/**
|
|
57
|
+
* @param {import('grammy').Context} ctx
|
|
58
|
+
* @returns {string | null}
|
|
59
|
+
*/
|
|
60
|
+
function buildSpecialPrompt(ctx) {
|
|
61
|
+
if (ctx.message.location && !ctx.message.venue) return buildLocationPrompt(ctx);
|
|
62
|
+
if (ctx.message.venue) return buildVenuePrompt(ctx);
|
|
63
|
+
if (ctx.message.contact) return buildContactPrompt(ctx);
|
|
64
|
+
if (ctx.message.poll) return buildPollPrompt(ctx);
|
|
65
|
+
return null;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
/**
|
|
69
|
+
* @param {import('grammy').Context} ctx
|
|
70
|
+
* @param {string} prompt
|
|
71
|
+
* @param {{ config: object, allowedUsers: Set<number>, bot: import('grammy').Bot, createAsk: Function }} deps
|
|
72
|
+
*/
|
|
73
|
+
async function processSpecial(ctx, prompt, deps) {
|
|
74
|
+
if (!ctx.from) return;
|
|
75
|
+
const userId = ctx.from.id;
|
|
76
|
+
const stopTyping = startTyping(ctx);
|
|
77
|
+
const status = createStatusTracker(ctx);
|
|
78
|
+
|
|
79
|
+
try {
|
|
80
|
+
const tenant = await getTenant(userId, deps.config);
|
|
81
|
+
const chatCtx = createChatContext(ctx, tenant, deps.config, deps);
|
|
82
|
+
|
|
83
|
+
chatCtx._onRouteDecision = (info) => {
|
|
84
|
+
status.setRouteInfo(info);
|
|
85
|
+
status.start();
|
|
86
|
+
};
|
|
87
|
+
chatCtx._onRouteUpdate = (update) => {
|
|
88
|
+
const ri = status.routeInfo;
|
|
89
|
+
if (!ri) return;
|
|
90
|
+
if (update.memoryCount !== undefined) ri.memoryCount = update.memoryCount;
|
|
91
|
+
if (update.model) ri.model = update.model;
|
|
92
|
+
};
|
|
93
|
+
chatCtx._onToolStart = (toolName, inputSummary) => {
|
|
94
|
+
status.setStatusText('Processing');
|
|
95
|
+
describeToolCall(tenant.claude.client, toolName, inputSummary).then(desc => {
|
|
96
|
+
if (desc) status.setStatusText(desc);
|
|
97
|
+
});
|
|
98
|
+
status.start();
|
|
99
|
+
};
|
|
100
|
+
|
|
101
|
+
const { text: response, usage, model } = await tenant.claude.chat(prompt, chatCtx);
|
|
102
|
+
|
|
103
|
+
status.stopTimer();
|
|
104
|
+
status.updateFormatting();
|
|
105
|
+
stopTyping();
|
|
106
|
+
|
|
107
|
+
if (!response?.trim()) {
|
|
108
|
+
status.deleteMsg();
|
|
109
|
+
await ctx.reply('⏹ Stopped.').catch(() => {});
|
|
110
|
+
return;
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
tenant.messageLog?.log(ctx.chat.id, 'user', prompt);
|
|
114
|
+
tenant.messageLog?.log(ctx.chat.id, 'assistant', response, { model, tokensIn: usage?.input_tokens, tokensOut: usage?.output_tokens });
|
|
115
|
+
|
|
116
|
+
if (response.length > 4096) {
|
|
117
|
+
for (const chunk of splitMessage(response, 4096)) await sendHtml(ctx, chunk).catch(() => {});
|
|
118
|
+
} else {
|
|
119
|
+
await sendHtml(ctx, response).catch(() => {});
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
if (usage && model) {
|
|
123
|
+
const tag = model.includes('opus') ? 'opus' : model.includes('haiku') ? 'haiku' : 'sonnet';
|
|
124
|
+
const tokIn = usage.input_tokens >= 1000 ? `${(usage.input_tokens / 1000).toFixed(1)}k` : usage.input_tokens;
|
|
125
|
+
const tokOut = usage.output_tokens >= 1000 ? `${(usage.output_tokens / 1000).toFixed(1)}k` : usage.output_tokens;
|
|
126
|
+
const dur = status.statusStart ? ((Date.now() - status.statusStart) / 1000).toFixed(1) : null;
|
|
127
|
+
const parts = [`◈ ${tag}`, `${tokIn} in`, `${tokOut} out`];
|
|
128
|
+
if (dur) parts.push(`${dur}s`);
|
|
129
|
+
await ctx.reply(`<code>${parts.join(' ▪ ')}</code>`, { parse_mode: 'HTML' }).catch(() => {});
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
status.deleteMsg();
|
|
133
|
+
} catch (e) {
|
|
134
|
+
status.clear();
|
|
135
|
+
stopTyping();
|
|
136
|
+
console.error('Special message handling error:', e.message);
|
|
137
|
+
await ctx.reply('Failed to process that message. Check logs.').catch(() => {});
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
/**
|
|
142
|
+
* @param {import('grammy').Bot} bot
|
|
143
|
+
* @param {{ config: object, allowedUsers: Set<number>, bot: import('grammy').Bot, createAsk: Function }} deps
|
|
144
|
+
*/
|
|
145
|
+
function registerSpecialHandler(bot, deps) {
|
|
146
|
+
async function handleSpecial(ctx) {
|
|
147
|
+
if (!ctx.from) return;
|
|
148
|
+
const userId = ctx.from.id;
|
|
149
|
+
const { createRateLimiter } = require('../rate-limit');
|
|
150
|
+
if (!bot._rateLimiter) bot._rateLimiter = createRateLimiter();
|
|
151
|
+
if (bot._rateLimiter.check(userId)) return;
|
|
152
|
+
|
|
153
|
+
const prompt = buildSpecialPrompt(ctx);
|
|
154
|
+
if (!prompt) return;
|
|
155
|
+
|
|
156
|
+
await processSpecial(ctx, prompt, deps);
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
bot.on('message:location', handleSpecial);
|
|
160
|
+
bot.on('message:venue', handleSpecial);
|
|
161
|
+
bot.on('message:contact', handleSpecial);
|
|
162
|
+
bot.on('message:poll', handleSpecial);
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
module.exports = { registerSpecialHandler };
|