@segmentstream/agent 0.6.6 → 0.6.8

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.
@@ -6,8 +6,10 @@ __eveDirname(__filename);
6
6
  __eveCreateRequire(import.meta.url);
7
7
  import { object } from "./confirmation-description.mjs";
8
8
  import { createHash } from "node:crypto";
9
+ import { TelegramFormatConverter, escapeMarkdownV2 } from "@chat-adapter/telegram";
9
10
  const CHAT_POST_PROTOCOL = "segmentstream/chat-post";
10
11
  const CHAT_POST_ENVELOPE = "_segmentstream";
12
+ const converter = new TelegramFormatConverter();
11
13
  function isChatPost(action) {
12
14
  return action.split("__").at(-1) === "chat_post_message";
13
15
  }
@@ -16,6 +18,7 @@ function supportsChatPost(tool) {
16
18
  }
17
19
  function chatPostDigest(preview) {
18
20
  return createHash("sha256").update(JSON.stringify([
21
+ "v2",
19
22
  preview.chatId,
20
23
  preview.provider,
21
24
  preview.externalId,
@@ -43,6 +46,7 @@ function describeChatPost(input) {
43
46
  target.external_id,
44
47
  caller.attribution
45
48
  ].every((value) => typeof value === "string" && value.length > 0) || target.provider !== "telegram" || target.id !== values.chat && target.title !== values.chat) throw new Error("An unambiguous destination and a complete exact chat preview are required.");
49
+ const footer = escapeMarkdownV2(caller.attribution);
46
50
  return {
47
51
  action: "Post message",
48
52
  fields: [],
@@ -53,7 +57,7 @@ function describeChatPost(input) {
53
57
  title: target.title,
54
58
  attribution: caller.attribution,
55
59
  text: values.text,
56
- finalText: `${caller.attribution}: ${values.text}`,
60
+ finalText: `${converter.fromMarkdown(values.text)}\n\n──────────\n_${footer}_`,
57
61
  replyTo: typeof values.reply_to === "string" ? values.reply_to : null
58
62
  }
59
63
  };
@@ -12,4 +12,6 @@ export declare function chatPostCopy(locale: string): {
12
12
  expired: string;
13
13
  };
14
14
  export declare function chatPostCard(preview: ChatPostPreview, locale: string): ConfirmationText;
15
+ /** Status/receipt strings are data outside the already rendered preview. */
16
+ export declare function chatPostCardStatus(text: string, status: string): string;
15
17
  export declare function chatPostReceipt(output: unknown, locale: string): string | undefined;
@@ -4,6 +4,7 @@ import { createRequire as __eveCreateRequire } from "node:module";
4
4
  const __filename = __eveFileURLToPath(import.meta.url);
5
5
  __eveDirname(__filename);
6
6
  __eveCreateRequire(import.meta.url);
7
+ import { escapeMarkdownV2 } from "@chat-adapter/telegram";
7
8
  import { AsyncLocalStorage } from "node:async_hooks";
8
9
  const MAX_PREVIEW_LENGTH = 3584;
9
10
  const exactText = new AsyncLocalStorage();
@@ -37,9 +38,8 @@ function chatPostCard(preview, locale) {
37
38
  russian ? "Отправить сообщение?" : "Send message?",
38
39
  `${russian ? "Чат" : "Chat"}: ${preview.title} (${preview.provider})`,
39
40
  ...preview.replyTo === null ? [] : [`${russian ? "В ответ на" : "Reply to"}: ${preview.replyTo}`],
40
- "",
41
- preview.finalText
42
- ].join("\n");
41
+ ""
42
+ ].map(escapeMarkdownV2).join("\n") + `\n${preview.finalText}`;
43
43
  if (text.length > MAX_PREVIEW_LENGTH) throw new Error("The full message and receipt cannot fit in one Telegram confirmation. Make a shorter draft.");
44
44
  return {
45
45
  text,
@@ -51,6 +51,9 @@ function chatPostCard(preview, locale) {
51
51
  failed: russian ? "Ошибка отправки сообщения." : "Chat posting failed."
52
52
  };
53
53
  }
54
+ function chatPostCardStatus(text, status) {
55
+ return `${text}\n\n${escapeMarkdownV2(status)}`;
56
+ }
54
57
  function chatPostReceipt(output, locale) {
55
58
  const value = typeof output === "object" && output !== null ? output : {};
56
59
  if (value.status !== "sent" && value.status !== "uncertain" || typeof value.history_recorded !== "boolean" || typeof value.duplicate !== "boolean" || value.message_id !== null && typeof value.message_id !== "string" || value.url !== null && typeof value.url !== "string") return;
@@ -62,4 +65,4 @@ function chatPostReceipt(output, locale) {
62
65
  ...value.history_recorded ? [] : [copy.history]
63
66
  ].join("\n");
64
67
  }
65
- export { chatPostCard, chatPostCopy, chatPostReceipt, isExactChatPostCard, withChatPostCard };
68
+ export { chatPostCard, chatPostCardStatus, chatPostCopy, chatPostReceipt, isExactChatPostCard, withChatPostCard };
@@ -6,7 +6,7 @@ __eveDirname(__filename);
6
6
  __eveCreateRequire(import.meta.url);
7
7
  import { conversationLanguage, memberIdOf, telegramPrincipal } from "./principal.mjs";
8
8
  import { chatPostOperationId, isChatPost } from "../chat-post-confirmation.mjs";
9
- import { chatPostCard, chatPostCopy, chatPostReceipt, withChatPostCard } from "./chat-post-card.mjs";
9
+ import { chatPostCard, chatPostCardStatus, chatPostCopy, chatPostReceipt, withChatPostCard } from "./chat-post-card.mjs";
10
10
  import { confirmations } from "../confirmations.mjs";
11
11
  import { conversationEvent } from "../conversations.mjs";
12
12
  import { currentInboundRequest } from "./inbound-request.mjs";
@@ -302,7 +302,7 @@ function telegramConfirmationEvents(render = async (description, locale) => fall
302
302
  const copy = message.copy ?? confirmationCopy(message.locale ?? confirmationLocale(ctx.session.auth.current, currentLocale()));
303
303
  const reason = confirmations.get()[callId]?.cancellationReason;
304
304
  const status = resolution.outcome === "approved" ? copy.executing : reason === "expired" ? chatPostCopy(message.locale ?? "en").expired : copy.cancelled;
305
- const text = message.exactPost ? `${message.text}\n\n${status}` : status;
305
+ const text = message.exactPost ? chatPostCardStatus(message.text, status) : status;
306
306
  const edit = () => channel.telegram.editMessageText({
307
307
  messageId: message.messageId,
308
308
  replyMarkup: { inline_keyboard: [] },
@@ -390,7 +390,7 @@ function telegramConfirmationEvents(render = async (description, locale) => fall
390
390
  const receipt = !failed && data.result.kind === "tool-result" ? chatPostReceipt(data.result.output, locale) : void 0;
391
391
  const errorId = failed ? reportTelegramError("confirmed_chat_post_failed", data, { callId: data.result.callId }) : void 0;
392
392
  const status = receipt ?? (failed ? `${message.copy?.failed ?? "Chat posting failed."} ${errorId}\n${chatPostCopy(locale).uncertain}` : chatPostCopy(locale).uncertain);
393
- const text = `${message.text}\n\n${status}`;
393
+ const text = chatPostCardStatus(message.text, status);
394
394
  await withChatPostCard(text, () => channel.telegram.editMessageText({
395
395
  messageId: message.messageId,
396
396
  replyMarkup: { inline_keyboard: [] },
@@ -140,17 +140,20 @@ function safeTelegramFetch(fetchImpl, scope = "telegram") {
140
140
  const url = typeof input === "string" ? input : input instanceof URL ? input.href : input.url;
141
141
  if (!methods.has(url.split("/").at(-1) ?? "") || typeof init?.body !== "string") return fetchImpl(input, init);
142
142
  const body = JSON.parse(init.body);
143
- for (const field of ["text", "caption"]) if (typeof body[field] === "string" && !isExactChatPostCard(body[field])) {
144
- const entityField = field === "text" ? "entities" : "caption_entities";
145
- const existing = body[entityField];
146
- const message = Array.isArray(existing) ? {
147
- text: body[field],
148
- entities: existing
149
- } : customerMessage(body[field]);
150
- const guarded = customerText(message.text);
151
- body[field] = message.entities.some((entity) => typeof entity.url === "string" && isTechnicalText(entity.url)) ? failureText(reportTelegramError("unsafe_entity_link", new Error("Credential-bearing link refused.")), "response") : guarded;
152
- if (!url.endsWith("/answerCallbackQuery")) body[entityField] = body[field] === message.text ? message.entities : [];
153
- delete body.parse_mode;
143
+ for (const field of ["text", "caption"]) {
144
+ if (typeof body[field] === "string" && isExactChatPostCard(body[field])) body.parse_mode = "MarkdownV2";
145
+ if (typeof body[field] === "string" && !isExactChatPostCard(body[field])) {
146
+ const entityField = field === "text" ? "entities" : "caption_entities";
147
+ const existing = body[entityField];
148
+ const message = Array.isArray(existing) ? {
149
+ text: body[field],
150
+ entities: existing
151
+ } : customerMessage(body[field]);
152
+ const guarded = customerText(message.text);
153
+ body[field] = message.entities.some((entity) => typeof entity.url === "string" && isTechnicalText(entity.url)) ? failureText(reportTelegramError("unsafe_entity_link", new Error("Credential-bearing link refused.")), "response") : guarded;
154
+ if (!url.endsWith("/answerCallbackQuery")) body[entityField] = body[field] === message.text ? message.entities : [];
155
+ delete body.parse_mode;
156
+ }
154
157
  }
155
158
  if (Array.isArray(body.media)) body.media = body.media.map((item) => ({
156
159
  ...item,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@segmentstream/agent",
3
- "version": "0.6.6",
3
+ "version": "0.6.8",
4
4
  "description": "Mount a SegmentStream workspace's agent on eve: the ontology as tools, acting as the member who writes, and Telegram through the workspace's connection.",
5
5
  "license": "Apache-2.0",
6
6
  "author": "SegmentStream",
@@ -60,6 +60,7 @@
60
60
  "prepack": "npm run build"
61
61
  },
62
62
  "dependencies": {
63
+ "@chat-adapter/telegram": "4.41.0",
63
64
  "@vercel/oidc": "^3.8.4",
64
65
  "ai": "^7.0.128",
65
66
  "csv-parse": "^7.0.3",