@inetafrica/open-claudia 2.6.27 → 2.6.29
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/channels/kazee/adapter.js +59 -9
- package/channels/kazee/format.js +22 -4
- package/core/runner.js +20 -3
- package/package.json +1 -1
|
@@ -12,6 +12,7 @@ const { TEMP_DIR, FILES_DIR } = require("../../core/config");
|
|
|
12
12
|
const { canonicalForChannel } = require("../../core/identity");
|
|
13
13
|
const { inlineKeyboardToPortable } = require("../types");
|
|
14
14
|
const { createKazeeSocket } = require("./socket");
|
|
15
|
+
const { normalize } = require("./format");
|
|
15
16
|
|
|
16
17
|
class KazeeAdapter {
|
|
17
18
|
constructor({ id = "kazee", url, token, ownerUserId, botUserId }) {
|
|
@@ -98,7 +99,7 @@ class KazeeAdapter {
|
|
|
98
99
|
});
|
|
99
100
|
}
|
|
100
101
|
|
|
101
|
-
_handleInboundMessage(msg) {
|
|
102
|
+
async _handleInboundMessage(msg) {
|
|
102
103
|
if (!msg) return;
|
|
103
104
|
const senderId = String(msg.sender?._id || msg.sender?.id || msg.senderId || "");
|
|
104
105
|
if (this.botUserId && senderId === String(this.botUserId)) return; // ignore our own echoes
|
|
@@ -169,6 +170,37 @@ class KazeeAdapter {
|
|
|
169
170
|
}
|
|
170
171
|
}
|
|
171
172
|
|
|
173
|
+
// Voice/audio distinction: chat-central has no "voice" type, so voice
|
|
174
|
+
// notes arrive as "audio". Detect them by mimeType (ogg/opus) and
|
|
175
|
+
// promote to "voice" so the router can transcribe them correctly.
|
|
176
|
+
if (envelope.type === "audio" && envelope.media?.length) {
|
|
177
|
+
const mime = (envelope.media[0].mimeType || "").toLowerCase();
|
|
178
|
+
if (mime.includes("ogg") || mime.includes("opus")) {
|
|
179
|
+
envelope.type = "voice";
|
|
180
|
+
envelope.media[0].type = "voice";
|
|
181
|
+
}
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
// Reply-to context: fetch the replied-to message content so the router
|
|
185
|
+
// has the same context Telegram provides. Best-effort — failure is silent.
|
|
186
|
+
if (msg.replyTo) {
|
|
187
|
+
try {
|
|
188
|
+
const data = await this._request("GET", `/message/${encodeURIComponent(String(msg.replyTo))}`);
|
|
189
|
+
const m = data?.message || data;
|
|
190
|
+
if (m && (m._id || m.id)) {
|
|
191
|
+
envelope.reply = {
|
|
192
|
+
text: m.content || "",
|
|
193
|
+
from: {
|
|
194
|
+
id: String(m.sender?._id || m.sender?.id || ""),
|
|
195
|
+
name: m.sender?.name || m.sender?.displayName || "",
|
|
196
|
+
username: m.sender?.username || "",
|
|
197
|
+
},
|
|
198
|
+
messageId: m._id || m.id,
|
|
199
|
+
};
|
|
200
|
+
}
|
|
201
|
+
} catch (e) { /* best-effort — envelope is still valid without reply context */ }
|
|
202
|
+
}
|
|
203
|
+
|
|
172
204
|
this._emit("message", envelope);
|
|
173
205
|
}
|
|
174
206
|
|
|
@@ -184,24 +216,42 @@ class KazeeAdapter {
|
|
|
184
216
|
const body = {
|
|
185
217
|
chat_id: channelId,
|
|
186
218
|
sender: this.botUserId,
|
|
187
|
-
content: text,
|
|
219
|
+
content: normalize(text),
|
|
188
220
|
type: buttons ? "interactive" : "text",
|
|
189
221
|
findMeCode: `oc-${Date.now()}-${Math.random().toString(36).slice(2, 8)}`,
|
|
190
222
|
};
|
|
191
223
|
if (opts.replyTo) body.replyTo = opts.replyTo;
|
|
192
224
|
if (buttons) body.interactive = { buttons };
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
225
|
+
|
|
226
|
+
for (let attempt = 0; attempt < 3; attempt++) {
|
|
227
|
+
try {
|
|
228
|
+
const res = await this._request("POST", `/chat/${encodeURIComponent(channelId)}/message`, body);
|
|
229
|
+
return res?.message?._id || res?.messageId || res?._id || null;
|
|
230
|
+
} catch (e) {
|
|
231
|
+
const errMsg = e.message || "";
|
|
232
|
+
// Don't retry on client errors (4xx) — they won't self-heal.
|
|
233
|
+
const statusMatch = errMsg.match(/→ (\d{3})/);
|
|
234
|
+
const status = statusMatch ? parseInt(statusMatch[1], 10) : 0;
|
|
235
|
+
if (status >= 400 && status < 500) {
|
|
236
|
+
console.error("Kazee send error (not retrying):", errMsg);
|
|
237
|
+
return null;
|
|
238
|
+
}
|
|
239
|
+
if (attempt < 2) {
|
|
240
|
+
const delay = (attempt + 1) * 1000;
|
|
241
|
+
console.error(`Kazee send error (attempt ${attempt + 1}/3, retrying in ${delay}ms):`, errMsg);
|
|
242
|
+
await new Promise((r) => setTimeout(r, delay));
|
|
243
|
+
continue;
|
|
244
|
+
}
|
|
245
|
+
console.error("Kazee send error (exhausted retries):", errMsg);
|
|
246
|
+
return null;
|
|
247
|
+
}
|
|
199
248
|
}
|
|
249
|
+
return null;
|
|
200
250
|
}
|
|
201
251
|
|
|
202
252
|
async edit(channelId, messageId, text, opts = {}) {
|
|
203
253
|
const buttons = this._normalizeKeyboard(opts.keyboard);
|
|
204
|
-
const body = { content: text };
|
|
254
|
+
const body = { content: normalize(text) };
|
|
205
255
|
if (buttons) {
|
|
206
256
|
body.type = "interactive";
|
|
207
257
|
body.interactive = { buttons };
|
package/channels/kazee/format.js
CHANGED
|
@@ -1,9 +1,27 @@
|
|
|
1
|
-
// Kazee
|
|
2
|
-
//
|
|
3
|
-
//
|
|
1
|
+
// Kazee renders standard markdown end-to-end. This module normalizes text
|
|
2
|
+
// before sending: converts any stray Telegram HTML tags (which the model may
|
|
3
|
+
// accidentally emit) into their markdown equivalents so they render correctly
|
|
4
|
+
// instead of appearing as raw tag noise.
|
|
5
|
+
|
|
6
|
+
function htmlToMarkdown(text) {
|
|
7
|
+
return String(text || "")
|
|
8
|
+
.replace(/<pre><code[^>]*>([\s\S]*?)<\/code><\/pre>/gi, (_, code) => "```\n" + code + "\n```")
|
|
9
|
+
.replace(/<pre>([\s\S]*?)<\/pre>/gi, (_, code) => "```\n" + code + "\n```")
|
|
10
|
+
.replace(/<code>([\s\S]*?)<\/code>/gi, "`$1`")
|
|
11
|
+
.replace(/<b>([\s\S]*?)<\/b>/gi, "**$1**")
|
|
12
|
+
.replace(/<strong>([\s\S]*?)<\/strong>/gi, "**$1**")
|
|
13
|
+
.replace(/<i>([\s\S]*?)<\/i>/gi, "_$1_")
|
|
14
|
+
.replace(/<em>([\s\S]*?)<\/em>/gi, "_$1_")
|
|
15
|
+
.replace(/<s>([\s\S]*?)<\/s>/gi, "~~$1~~")
|
|
16
|
+
.replace(/<strike>([\s\S]*?)<\/strike>/gi, "~~$1~~")
|
|
17
|
+
.replace(/<del>([\s\S]*?)<\/del>/gi, "~~$1~~")
|
|
18
|
+
.replace(/<a href="([^"]+)">([\s\S]*?)<\/a>/gi, "[$2]($1)")
|
|
19
|
+
.replace(/<[^>]+>/g, "")
|
|
20
|
+
.replace(/</g, "<").replace(/>/g, ">").replace(/&/g, "&").replace(/"/g, '"').replace(/&#(\d+);/g, (_, n) => String.fromCodePoint(Number(n)));
|
|
21
|
+
}
|
|
4
22
|
|
|
5
23
|
function normalize(text) {
|
|
6
|
-
return String(text || "");
|
|
24
|
+
return htmlToMarkdown(String(text || ""));
|
|
7
25
|
}
|
|
8
26
|
|
|
9
27
|
module.exports = { normalize };
|
package/core/runner.js
CHANGED
|
@@ -130,6 +130,11 @@ function logTurnUsage(state, usage, costUsd, announce, opts = {}) {
|
|
|
130
130
|
const backend = state.settings?.backend || "claude";
|
|
131
131
|
const parts = usageParts(usage, backend);
|
|
132
132
|
const usageScope = opts.usageScope || "turn_delta";
|
|
133
|
+
// `parts.context` is the per-turn SUM of every API call's prefix, inflated
|
|
134
|
+
// by re-counting the cached prefix once per tool round-trip. When the caller
|
|
135
|
+
// tracked the true peak single-call prefix, report that as contextTokens and
|
|
136
|
+
// keep the inflated sum as billedContextTokens for cost attribution.
|
|
137
|
+
const hasPeak = Number.isFinite(opts.liveContextTokens) && opts.liveContextTokens > 0;
|
|
133
138
|
const record = {
|
|
134
139
|
ts: new Date().toISOString(),
|
|
135
140
|
version: PKG_VERSION,
|
|
@@ -142,7 +147,8 @@ function logTurnUsage(state, usage, costUsd, announce, opts = {}) {
|
|
|
142
147
|
outputTokens: parts.output,
|
|
143
148
|
cacheReadTokens: parts.cacheRead,
|
|
144
149
|
cacheCreationTokens: parts.cacheCreation,
|
|
145
|
-
contextTokens: parts.context,
|
|
150
|
+
contextTokens: hasPeak ? opts.liveContextTokens : parts.context,
|
|
151
|
+
billedContextTokens: parts.context,
|
|
146
152
|
costUsd: typeof costUsd === "number" ? costUsd : 0,
|
|
147
153
|
};
|
|
148
154
|
if (opts.rawUsage && opts.rawUsage !== usage) {
|
|
@@ -785,6 +791,11 @@ async function runClaude(prompt, cwd, replyToMsgId, opts = {}) {
|
|
|
785
791
|
let toolUses = [];
|
|
786
792
|
let currentTool = null;
|
|
787
793
|
let currentToolDetail = "";
|
|
794
|
+
// True context-window occupancy is the largest single API call's prefix
|
|
795
|
+
// (input + cache_read + cache_creation), NOT the per-turn sum the result
|
|
796
|
+
// event reports — that sum re-counts the cached prefix once per tool
|
|
797
|
+
// round-trip and balloons to millions. Track the peak per-call prefix.
|
|
798
|
+
let peakContextTokens = 0;
|
|
788
799
|
|
|
789
800
|
// Hermes-style skill announcements: one chat line when a learned skill
|
|
790
801
|
// is invoked or its SKILL.md is written/patched. Deduped per turn.
|
|
@@ -897,6 +908,10 @@ async function runClaude(prompt, cwd, replyToMsgId, opts = {}) {
|
|
|
897
908
|
const lastNewline = state.streamBuffer.lastIndexOf("\n");
|
|
898
909
|
state.streamBuffer = lastNewline >= 0 ? state.streamBuffer.slice(lastNewline + 1) : state.streamBuffer;
|
|
899
910
|
for (const evt of events) {
|
|
911
|
+
if (evt.type === "assistant" && evt.message?.usage) {
|
|
912
|
+
const callPrefix = usageParts(evt.message.usage, settings.backend || "claude").context;
|
|
913
|
+
if (callPrefix > peakContextTokens) peakContextTokens = callPrefix;
|
|
914
|
+
}
|
|
900
915
|
if (evt.type === "assistant" && evt.message?.content) {
|
|
901
916
|
for (const block of evt.message.content) {
|
|
902
917
|
if (block.type === "text") assistantText += block.text;
|
|
@@ -1003,8 +1018,10 @@ async function runClaude(prompt, cwd, replyToMsgId, opts = {}) {
|
|
|
1003
1018
|
// Codex already reports usage at turn.completed. A result usage block,
|
|
1004
1019
|
// if present, is the same cumulative total and must not be counted again.
|
|
1005
1020
|
if (evt.usage && settings.backend !== "codex") {
|
|
1006
|
-
|
|
1007
|
-
|
|
1021
|
+
const peakOpts = { usageScope: "turn_delta" };
|
|
1022
|
+
if (peakContextTokens > 0) peakOpts.liveContextTokens = peakContextTokens;
|
|
1023
|
+
applyUsageToState(state, evt.usage, typeof evt.total_cost_usd === "number" ? evt.total_cost_usd : 0, peakOpts);
|
|
1024
|
+
logTurnUsage(state, evt.usage, typeof evt.total_cost_usd === "number" ? evt.total_cost_usd : 0, (text) => chatContext.run(store, () => send(text)), peakOpts);
|
|
1008
1025
|
}
|
|
1009
1026
|
if (typeof evt.total_cost_usd === "number" && settings.backend === "codex") state.sessionUsage.costUsd += evt.total_cost_usd;
|
|
1010
1027
|
saveState();
|
package/package.json
CHANGED