@pi-unipi/notify 0.1.5 → 0.1.7

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/summarize.ts ADDED
@@ -0,0 +1,149 @@
1
+ /**
2
+ * @pi-unipi/notify — Recap summarization
3
+ *
4
+ * Calls an LLM to summarize the last assistant message for push notifications.
5
+ * Supports multiple API formats based on the model's api type.
6
+ */
7
+
8
+ const SYSTEM_PROMPT =
9
+ "Summarize this in one concise sentence for a push notification. Reply with ONLY the summary.";
10
+ const MAX_INPUT_CHARS = 2000;
11
+ const MAX_TOKENS = 100;
12
+ const TIMEOUT_MS = 10_000;
13
+ const FALLBACK_TRUNCATE_CHARS = 100;
14
+
15
+ /**
16
+ * Summarize a message using an LLM.
17
+ *
18
+ * @param messageText - The assistant message text to summarize
19
+ * @param apiKey - API key for the provider
20
+ * @param baseUrl - Provider base URL (from Model.baseUrl)
21
+ * @param api - API type (from Model.api, e.g. "openai-completions")
22
+ * @param modelId - Model ID to use
23
+ * @returns Summarized text, or truncated original on failure
24
+ */
25
+ export async function summarizeLastMessage(
26
+ messageText: string,
27
+ apiKey: string,
28
+ baseUrl: string,
29
+ api: string,
30
+ modelId: string,
31
+ ): Promise<string> {
32
+ // Truncate input if too long
33
+ const input =
34
+ messageText.length > MAX_INPUT_CHARS
35
+ ? messageText.slice(0, MAX_INPUT_CHARS) + "..."
36
+ : messageText;
37
+
38
+ try {
39
+ // Route to the correct API format
40
+ if (api === "anthropic-messages") {
41
+ return await callAnthropic(baseUrl, apiKey, modelId, input);
42
+ }
43
+ // Default: OpenAI-compatible (covers openai-completions, openai-responses, etc.)
44
+ return await callOpenAICompatible(baseUrl, apiKey, modelId, input);
45
+ } catch {
46
+ return fallbackSummary(messageText);
47
+ }
48
+ }
49
+
50
+ /** Call an OpenAI-compatible API (most providers) */
51
+ async function callOpenAICompatible(
52
+ baseUrl: string,
53
+ apiKey: string,
54
+ modelId: string,
55
+ input: string,
56
+ ): Promise<string> {
57
+ const url = `${baseUrl.replace(/\/$/, "")}/chat/completions`;
58
+ const controller = new AbortController();
59
+ const timeout = setTimeout(() => controller.abort(), TIMEOUT_MS);
60
+
61
+ try {
62
+ const response = await fetch(url, {
63
+ method: "POST",
64
+ headers: {
65
+ Authorization: `Bearer ${apiKey}`,
66
+ "Content-Type": "application/json",
67
+ },
68
+ body: JSON.stringify({
69
+ model: modelId,
70
+ max_tokens: MAX_TOKENS,
71
+ messages: [
72
+ { role: "system", content: SYSTEM_PROMPT },
73
+ { role: "user", content: input },
74
+ ],
75
+ }),
76
+ signal: controller.signal,
77
+ });
78
+
79
+ clearTimeout(timeout);
80
+
81
+ if (!response.ok) {
82
+ return fallbackSummary(input);
83
+ }
84
+
85
+ const data = (await response.json()) as {
86
+ choices?: Array<{ message?: { content?: string } }>;
87
+ };
88
+
89
+ const summary = data.choices?.[0]?.message?.content?.trim();
90
+ return summary && summary.length > 0 ? summary : fallbackSummary(input);
91
+ } catch {
92
+ clearTimeout(timeout);
93
+ return fallbackSummary(input);
94
+ }
95
+ }
96
+
97
+ /** Call the Anthropic Messages API */
98
+ async function callAnthropic(
99
+ baseUrl: string,
100
+ apiKey: string,
101
+ modelId: string,
102
+ input: string,
103
+ ): Promise<string> {
104
+ const url = `${baseUrl.replace(/\/$/, "")}/messages`;
105
+ const controller = new AbortController();
106
+ const timeout = setTimeout(() => controller.abort(), TIMEOUT_MS);
107
+
108
+ try {
109
+ const response = await fetch(url, {
110
+ method: "POST",
111
+ headers: {
112
+ "x-api-key": apiKey,
113
+ "anthropic-version": "2023-06-01",
114
+ "Content-Type": "application/json",
115
+ },
116
+ body: JSON.stringify({
117
+ model: modelId,
118
+ max_tokens: MAX_TOKENS,
119
+ system: SYSTEM_PROMPT,
120
+ messages: [{ role: "user", content: input }],
121
+ }),
122
+ signal: controller.signal,
123
+ });
124
+
125
+ clearTimeout(timeout);
126
+
127
+ if (!response.ok) {
128
+ return fallbackSummary(input);
129
+ }
130
+
131
+ const data = (await response.json()) as {
132
+ content?: Array<{ type?: string; text?: string }>;
133
+ };
134
+
135
+ const textBlock = data.content?.find((b) => b.type === "text");
136
+ const summary = textBlock?.text?.trim();
137
+ return summary && summary.length > 0 ? summary : fallbackSummary(input);
138
+ } catch {
139
+ clearTimeout(timeout);
140
+ return fallbackSummary(input);
141
+ }
142
+ }
143
+
144
+ /** Truncate message as fallback when summarization fails */
145
+ function fallbackSummary(messageText: string): string {
146
+ const trimmed = messageText.trim();
147
+ if (trimmed.length <= FALLBACK_TRUNCATE_CHARS) return trimmed;
148
+ return trimmed.slice(0, FALLBACK_TRUNCATE_CHARS) + "...";
149
+ }
package/tools.ts CHANGED
@@ -26,7 +26,7 @@ const NotifyUserSchema = Type.Object({
26
26
  ),
27
27
  platforms: Type.Optional(
28
28
  Type.Array(
29
- Type.String({ enum: ["native", "gotify", "telegram"] }),
29
+ Type.String({ enum: ["native", "gotify", "telegram", "ntfy"] }),
30
30
  { description: "Override platforms for this notification" }
31
31
  )
32
32
  ),
@@ -40,7 +40,7 @@ export function registerNotifyTools(pi: ExtensionAPI): void {
40
40
  name: NOTIFY_TOOLS.NOTIFY_USER,
41
41
  label: "Notify User",
42
42
  description:
43
- "Send a notification to the user's configured platforms (native OS, Gotify, Telegram). " +
43
+ "Send a notification to the user's configured platforms (native OS, Gotify, Telegram, ntfy). " +
44
44
  "Use for critical errors, completion of long-running tasks, or when the user explicitly asked to be notified.",
45
45
  parameters: NotifyUserSchema,
46
46
  async execute(_toolCallId, params, _signal, _onUpdate, ctx: ExtensionContext) {
@@ -53,7 +53,7 @@ export function registerNotifyTools(pi: ExtensionAPI): void {
53
53
  message: string;
54
54
  title?: string;
55
55
  priority?: "low" | "normal" | "high";
56
- platforms?: Array<"native" | "gotify" | "telegram">;
56
+ platforms?: Array<"native" | "gotify" | "telegram" | "ntfy">;
57
57
  };
58
58
 
59
59
  const config = loadConfig();