arisa 4.0.0 → 4.0.3
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
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
import { createPiRuntime, hasProviderAuth, supportsProviderOAuth } from "./pi-runtime.js";
|
|
2
|
+
|
|
3
|
+
const authInvalidatedPatterns = [
|
|
4
|
+
/authentication token has been invalidated/i,
|
|
5
|
+
/token (?:has been )?invalidated/i,
|
|
6
|
+
/try signing in again/i,
|
|
7
|
+
/auth(?:entication)? token (?:expired|revoked|invalid)/i
|
|
8
|
+
];
|
|
9
|
+
|
|
10
|
+
const missingAuthPatterns = [
|
|
11
|
+
/no auth found/i,
|
|
12
|
+
/auth(?:entication)? .*missing/i
|
|
13
|
+
];
|
|
14
|
+
|
|
15
|
+
export function getErrorMessage(error) {
|
|
16
|
+
return error instanceof Error ? error.message : String(error);
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
export function getPiAuthIssue(error) {
|
|
20
|
+
const message = getErrorMessage(error);
|
|
21
|
+
if (!message) return null;
|
|
22
|
+
|
|
23
|
+
if (authInvalidatedPatterns.some((pattern) => pattern.test(message))) {
|
|
24
|
+
return { kind: "invalidated-token", message };
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
if (missingAuthPatterns.some((pattern) => pattern.test(message))) {
|
|
28
|
+
return { kind: "missing-auth", message };
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
return null;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
export function getPiAuthStatus(config) {
|
|
35
|
+
const runtime = createPiRuntime({
|
|
36
|
+
provider: config.pi.provider,
|
|
37
|
+
apiKey: config.pi.apiKey
|
|
38
|
+
});
|
|
39
|
+
|
|
40
|
+
return {
|
|
41
|
+
provider: config.pi.provider,
|
|
42
|
+
model: config.pi.model,
|
|
43
|
+
hasApiKey: Boolean(config.pi.apiKey),
|
|
44
|
+
hasStoredAuth: hasProviderAuth(config.pi.provider, runtime),
|
|
45
|
+
supportsOAuth: supportsProviderOAuth(config.pi.provider, runtime)
|
|
46
|
+
};
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
export function buildPiAuthTelegramMessage({ config, issue = null, verified = false }) {
|
|
50
|
+
const status = getPiAuthStatus(config);
|
|
51
|
+
let title = `Pi authentication status for ${status.provider}/${status.model}.`;
|
|
52
|
+
if (issue) {
|
|
53
|
+
title = `Pi authentication needs attention for ${status.provider}/${status.model}.`;
|
|
54
|
+
} else if (verified) {
|
|
55
|
+
title = `Pi authentication is working for ${status.provider}/${status.model}.`;
|
|
56
|
+
}
|
|
57
|
+
const lines = [title];
|
|
58
|
+
|
|
59
|
+
if (issue?.kind === "invalidated-token") {
|
|
60
|
+
lines.push("The provider says the current authentication token was invalidated.");
|
|
61
|
+
} else if (issue?.kind === "missing-auth") {
|
|
62
|
+
lines.push("Arisa could not find usable authentication for this provider.");
|
|
63
|
+
} else if (issue?.kind === "validation-failed") {
|
|
64
|
+
lines.push("Arisa could not validate the current Pi authentication.");
|
|
65
|
+
} else if (verified) {
|
|
66
|
+
lines.push(status.hasApiKey
|
|
67
|
+
? "The configured Pi API key was accepted by the provider."
|
|
68
|
+
: "The stored auth was accepted by the provider.");
|
|
69
|
+
} else {
|
|
70
|
+
lines.push(`Stored auth record: ${status.hasStoredAuth ? "detected" : "not detected"}.`);
|
|
71
|
+
lines.push("This only checks whether credentials are stored, not whether the provider will accept them.");
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
if (issue?.message) {
|
|
75
|
+
lines.push(`Details: ${issue.message}`);
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
if (verified) {
|
|
79
|
+
lines.push("No action needed.");
|
|
80
|
+
} else if (!issue) {
|
|
81
|
+
lines.push("Run `/auth` to validate these credentials against the provider.");
|
|
82
|
+
} else if (status.hasApiKey) {
|
|
83
|
+
lines.push("A Pi API key is configured, but the provider rejected the current request. Update the key and restart Arisa.");
|
|
84
|
+
} else if (status.supportsOAuth) {
|
|
85
|
+
lines.push("For now, re-run `arisa --bootstrap` on the host and complete Pi login again.");
|
|
86
|
+
} else {
|
|
87
|
+
lines.push("This provider needs a Pi API key. Re-run `arisa --bootstrap`, provide a key, and restart Arisa.");
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
if (issue) {
|
|
91
|
+
lines.push("Telegram-based renewal is not wired yet, but this /auth path is ready for that flow.");
|
|
92
|
+
}
|
|
93
|
+
return lines.join("\n");
|
|
94
|
+
}
|
|
@@ -64,7 +64,12 @@ export async function createApp({ logger, runtimeOverrides, webhookUrl, setHttpR
|
|
|
64
64
|
return {
|
|
65
65
|
async start() {
|
|
66
66
|
logger?.log("app", `validating Pi model ${config.pi.provider}/${config.pi.model}`);
|
|
67
|
-
|
|
67
|
+
try {
|
|
68
|
+
await agentManager.validatePiAgent();
|
|
69
|
+
} catch (error) {
|
|
70
|
+
await bot.notifyPiAuthIssue?.(error);
|
|
71
|
+
throw error;
|
|
72
|
+
}
|
|
68
73
|
await toolProcessSupervisor.start();
|
|
69
74
|
logger?.log("app", "starting Telegram bot");
|
|
70
75
|
try {
|
|
@@ -3,6 +3,7 @@ import path from "node:path";
|
|
|
3
3
|
import { authorizeChat } from "./auth.js";
|
|
4
4
|
import { captureIncomingArtifact } from "./media.js";
|
|
5
5
|
import { renderTelegramHtml } from "./text-format.js";
|
|
6
|
+
import { buildPiAuthTelegramMessage, getErrorMessage, getPiAuthIssue } from "../../core/agent/auth-flow.js";
|
|
6
7
|
import { normalizeArtifactForReasoning, shouldNormalizeArtifactToText } from "../../core/artifacts/normalize-for-reasoning.js";
|
|
7
8
|
|
|
8
9
|
const slowPromptNoticeMs = 300_000;
|
|
@@ -185,6 +186,7 @@ function sessionEventLogMessage(event) {
|
|
|
185
186
|
|
|
186
187
|
async function collectText(session, prompt, { logger, chatId, onSlowPrompt } = {}) {
|
|
187
188
|
let text = "";
|
|
189
|
+
let assistantErrorMessage = "";
|
|
188
190
|
let shouldSeparateAssistantMessage = false;
|
|
189
191
|
let slowPromptTimer = null;
|
|
190
192
|
const unsubscribe = session.subscribe((event) => {
|
|
@@ -198,6 +200,9 @@ async function collectText(session, prompt, { logger, chatId, onSlowPrompt } = {
|
|
|
198
200
|
}
|
|
199
201
|
text += event.assistantMessageEvent.delta;
|
|
200
202
|
}
|
|
203
|
+
if (event.type === "message_end" && event.message?.stopReason === "error") {
|
|
204
|
+
assistantErrorMessage = event.message.errorMessage || "assistant message ended with error";
|
|
205
|
+
}
|
|
201
206
|
const logMessage = sessionEventLogMessage(event);
|
|
202
207
|
if (logMessage) logger?.log("agent", `chat ${chatId} ${logMessage}`);
|
|
203
208
|
});
|
|
@@ -218,6 +223,10 @@ async function collectText(session, prompt, { logger, chatId, onSlowPrompt } = {
|
|
|
218
223
|
unsubscribe();
|
|
219
224
|
}
|
|
220
225
|
|
|
226
|
+
if (assistantErrorMessage) {
|
|
227
|
+
throw new Error(assistantErrorMessage);
|
|
228
|
+
}
|
|
229
|
+
|
|
221
230
|
return text.trim();
|
|
222
231
|
}
|
|
223
232
|
|
|
@@ -237,8 +246,31 @@ async function withTyping(ctx, work) {
|
|
|
237
246
|
export async function createTelegramBot({ config, artifactStore, toolRegistry, taskStore, agentManager, saveConfig, updateConfig, logger, webhookUrl, setHttpRequestHandler }) {
|
|
238
247
|
const bot = new Bot(config.telegram.token);
|
|
239
248
|
const perChatState = new Map();
|
|
249
|
+
const notifiedPromptErrors = new WeakSet();
|
|
240
250
|
let taskTimer = null;
|
|
241
251
|
|
|
252
|
+
function wasPromptErrorNotified(error) {
|
|
253
|
+
return error instanceof Error && notifiedPromptErrors.has(error);
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
function markPromptErrorNotified(error) {
|
|
257
|
+
if (error instanceof Error) notifiedPromptErrors.add(error);
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
async function notifyPiAuthIssueIfNeeded(chatId, error) {
|
|
261
|
+
const issue = getPiAuthIssue(error);
|
|
262
|
+
if (!issue) return false;
|
|
263
|
+
|
|
264
|
+
try {
|
|
265
|
+
await bot.api.sendMessage(chatId, buildPiAuthTelegramMessage({ config, issue }));
|
|
266
|
+
markPromptErrorNotified(error);
|
|
267
|
+
return true;
|
|
268
|
+
} catch (notifyError) {
|
|
269
|
+
logger?.error("telegram", `auth issue notice failed for chat ${chatId}: ${getErrorMessage(notifyError)}`);
|
|
270
|
+
return false;
|
|
271
|
+
}
|
|
272
|
+
}
|
|
273
|
+
|
|
242
274
|
function getIncomingChatMeta(ctx) {
|
|
243
275
|
return {
|
|
244
276
|
languageCode: ctx.from?.language_code || "",
|
|
@@ -358,8 +390,9 @@ export async function createTelegramBot({ config, artifactStore, toolRegistry, t
|
|
|
358
390
|
logger?.log("telegram", `prompt dispatch for chat ${chatId}`);
|
|
359
391
|
await processPromptForChat({ chatId, prompt: currentPrompt, ctx: currentCtx });
|
|
360
392
|
} catch (error) {
|
|
361
|
-
const message =
|
|
393
|
+
const message = getErrorMessage(error);
|
|
362
394
|
logger?.error("telegram", `${label} failed for chat ${chatId}: ${message}`);
|
|
395
|
+
await notifyPiAuthIssueIfNeeded(chatId, error);
|
|
363
396
|
throw error;
|
|
364
397
|
} finally {
|
|
365
398
|
currentCtx = null;
|
|
@@ -493,6 +526,20 @@ export async function createTelegramBot({ config, artifactStore, toolRegistry, t
|
|
|
493
526
|
await handleNewCommand(ctx);
|
|
494
527
|
});
|
|
495
528
|
|
|
529
|
+
bot.command("auth", async (ctx) => {
|
|
530
|
+
const auth = await authorizeChat({ config, chatId: ctx.chat.id, saveConfig, chatMeta: getIncomingChatMeta(ctx) });
|
|
531
|
+
if (!auth.ok) return;
|
|
532
|
+
await withTyping(ctx, async () => {
|
|
533
|
+
try {
|
|
534
|
+
await agentManager.validatePiAgent();
|
|
535
|
+
await ctx.reply(buildPiAuthTelegramMessage({ config, verified: true }));
|
|
536
|
+
} catch (error) {
|
|
537
|
+
const issue = getPiAuthIssue(error) || { kind: "validation-failed", message: getErrorMessage(error) };
|
|
538
|
+
await ctx.reply(buildPiAuthTelegramMessage({ config, issue }));
|
|
539
|
+
}
|
|
540
|
+
});
|
|
541
|
+
});
|
|
542
|
+
|
|
496
543
|
bot.on("message", async (ctx) => {
|
|
497
544
|
const auth = await authorizeChat({ config, chatId: ctx.chat.id, saveConfig, chatMeta: getIncomingChatMeta(ctx) });
|
|
498
545
|
if (!auth.ok) return;
|
|
@@ -505,8 +552,11 @@ export async function createTelegramBot({ config, artifactStore, toolRegistry, t
|
|
|
505
552
|
} catch (error) {
|
|
506
553
|
const chatState = getChatState(ctx.chat.id);
|
|
507
554
|
chatState.processing = false;
|
|
508
|
-
|
|
509
|
-
|
|
555
|
+
if (wasPromptErrorNotified(error)) return;
|
|
556
|
+
const issue = getPiAuthIssue(error);
|
|
557
|
+
await ctx.reply(issue
|
|
558
|
+
? buildPiAuthTelegramMessage({ config, issue })
|
|
559
|
+
: getErrorMessage(error));
|
|
510
560
|
}
|
|
511
561
|
});
|
|
512
562
|
|
|
@@ -534,7 +584,8 @@ export async function createTelegramBot({ config, artifactStore, toolRegistry, t
|
|
|
534
584
|
}
|
|
535
585
|
}
|
|
536
586
|
await bot.api.setMyCommands([
|
|
537
|
-
{ command: "new", description: "Start a new chat context" }
|
|
587
|
+
{ command: "new", description: "Start a new chat context" },
|
|
588
|
+
{ command: "auth", description: "Show Pi authentication status" }
|
|
538
589
|
]);
|
|
539
590
|
if (!taskTimer) {
|
|
540
591
|
taskTimer = setInterval(() => {
|
|
@@ -572,6 +623,14 @@ export async function createTelegramBot({ config, artifactStore, toolRegistry, t
|
|
|
572
623
|
try {
|
|
573
624
|
bot.stop();
|
|
574
625
|
} catch {}
|
|
626
|
+
},
|
|
627
|
+
|
|
628
|
+
async notifyPiAuthIssue(error) {
|
|
629
|
+
let notified = false;
|
|
630
|
+
for (const chatId of config.telegram.authorizedChatIds || []) {
|
|
631
|
+
notified = await notifyPiAuthIssueIfNeeded(chatId, error) || notified;
|
|
632
|
+
}
|
|
633
|
+
return notified;
|
|
575
634
|
}
|
|
576
635
|
};
|
|
577
636
|
}
|