@segmentstream/agent 0.5.1 → 0.5.2

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.
Files changed (34) hide show
  1. package/dist/extension/lib/confirmation-description.d.ts +25 -0
  2. package/dist/extension/lib/confirmation-description.mjs +81 -0
  3. package/dist/extension/lib/confirmations.d.ts +4 -0
  4. package/dist/extension/lib/confirmations.mjs +9 -2
  5. package/dist/extension/lib/conversations.d.ts +2 -0
  6. package/dist/extension/lib/conversations.mjs +15 -3
  7. package/dist/extension/lib/identities.d.ts +1 -0
  8. package/dist/extension/lib/identities.mjs +2 -1
  9. package/dist/extension/lib/ontology-connection.mjs +2 -2
  10. package/dist/extension/lib/ontology-tools.d.ts +6 -2
  11. package/dist/extension/lib/ontology-tools.mjs +95 -25
  12. package/dist/extension/lib/telegram/adapter.mjs +75 -40
  13. package/dist/extension/lib/telegram/attachments-send.d.ts +1 -0
  14. package/dist/extension/lib/telegram/attachments-send.mjs +24 -29
  15. package/dist/extension/lib/telegram/confirmation-text.d.ts +20 -0
  16. package/dist/extension/lib/telegram/confirmation-text.mjs +88 -0
  17. package/dist/extension/lib/telegram/confirmations.d.ts +2 -0
  18. package/dist/extension/lib/telegram/confirmations.mjs +173 -144
  19. package/dist/extension/lib/telegram/conversations.d.ts +1 -1
  20. package/dist/extension/lib/telegram/conversations.mjs +10 -5
  21. package/dist/extension/lib/telegram/customer-text.d.ts +20 -0
  22. package/dist/extension/lib/telegram/customer-text.mjs +120 -0
  23. package/dist/extension/lib/telegram/files.mjs +5 -4
  24. package/dist/extension/lib/telegram/inbound-request.d.ts +2 -0
  25. package/dist/extension/lib/telegram/inbound-request.mjs +2 -0
  26. package/dist/extension/lib/telegram/inbound.mjs +9 -3
  27. package/dist/extension/lib/telegram/principal.d.ts +3 -0
  28. package/dist/extension/lib/telegram/principal.mjs +11 -1
  29. package/dist/extension/lib/telegram/silent.mjs +5 -2
  30. package/dist/extension/lib/telegram/turn-errors.d.ts +2 -9
  31. package/dist/extension/lib/telegram/turn-errors.mjs +36 -10
  32. package/dist/extension/tools/attachments_promote.mjs +11 -2
  33. package/dist/extension/tools/attachments_send.d.ts +1 -0
  34. package/package.json +1 -1
@@ -0,0 +1,25 @@
1
+ /** Only human-facing facts cross into the confirmation renderer; identifiers stay in the gate. */
2
+ export interface ConfirmationDescription {
3
+ readonly action: string;
4
+ readonly entity?: string;
5
+ readonly description?: string;
6
+ readonly fields: readonly {
7
+ readonly label: string;
8
+ readonly value: string;
9
+ }[];
10
+ }
11
+ export declare function object(value: unknown): Record<string, unknown>;
12
+ export declare function humanize(value: string): string;
13
+ /** Reject machine identifiers and structured data even in authored labels or resolved titles. */
14
+ export declare function humanText(value: unknown, maximum?: number): string | undefined;
15
+ /** Resolve local schema references and wrappers without following remote references. */
16
+ export declare function fieldSchema(value: unknown, root: Record<string, unknown>, depth?: number): Record<string, unknown>;
17
+ export declare function describeConfirmation(input: {
18
+ readonly title?: string;
19
+ readonly description?: string;
20
+ readonly schema: Record<string, unknown>;
21
+ readonly entity?: Record<string, unknown>;
22
+ readonly entities: readonly Record<string, unknown>[];
23
+ readonly values: Readonly<Record<string, unknown>>;
24
+ readonly read: (entity: string, id: string) => Promise<Record<string, unknown> | undefined>;
25
+ }): Promise<ConfirmationDescription>;
@@ -0,0 +1,81 @@
1
+ import { fileURLToPath as __eveFileURLToPath } from "node:url";
2
+ import { dirname as __eveDirname } from "node:path";
3
+ import { createRequire as __eveCreateRequire } from "node:module";
4
+ const __filename = __eveFileURLToPath(import.meta.url);
5
+ __eveDirname(__filename);
6
+ __eveCreateRequire(import.meta.url);
7
+ function object(value) {
8
+ return typeof value === "object" && value !== null && !Array.isArray(value) ? value : {};
9
+ }
10
+ function humanize(value) {
11
+ return value.replace(/([a-z0-9])([A-Z])/g, "$1 $2").replace(/[_-]+/g, " ").trim();
12
+ }
13
+ function humanText(value, maximum = 180) {
14
+ if (typeof value !== "string") return;
15
+ const text = value.replace(/\s+/g, " ").trim();
16
+ if (text === "" || /[{}\[\]]|__|\b[0-9a-f]{8}-[0-9a-f-]{27,}\b|\b[0-9A-HJKMNP-TV-Z]{26}\b|\b[a-z0-9]{24,}\b/iu.test(text)) return;
17
+ return text.slice(0, maximum);
18
+ }
19
+ function fieldSchema(value, root, depth = 0) {
20
+ const schema = object(value);
21
+ if (depth > 12) return {};
22
+ const ref = schema.$ref;
23
+ const referenced = typeof ref === "string" && ref.startsWith("#/") ? ref.slice(2).split("/").reduce((at, key) => object(at)[key.replace(/~1/g, "/").replace(/~0/g, "~")], root) : {};
24
+ const branches = [
25
+ schema.allOf,
26
+ schema.anyOf,
27
+ schema.oneOf
28
+ ].flatMap((items) => Array.isArray(items) ? items.filter((item) => object(item).type !== "null") : []);
29
+ return Object.assign({}, ...ref === void 0 ? [] : [fieldSchema(referenced, root, depth + 1)], ...branches.map((branch) => fieldSchema(branch, root, depth + 1)), schema);
30
+ }
31
+ async function describeConfirmation(input) {
32
+ const entity = input.entity ?? {};
33
+ const properties = Array.isArray(entity.properties) ? entity.properties.map(object) : [];
34
+ const fields = [];
35
+ const read = new Map();
36
+ for (const [key, value] of Object.entries(input.values).slice(0, 20)) {
37
+ const declared = object(input.schema.properties)[key];
38
+ if (declared === void 0 || /password|secret|token|credential|authorization/i.test(key)) continue;
39
+ const schema = fieldSchema(declared, input.schema);
40
+ const itemSchema = schema.type === "array" ? fieldSchema(schema.items, input.schema) : schema;
41
+ const property = properties.find((candidate) => candidate.name === key);
42
+ const reference = itemSchema["x-ontology-ref"];
43
+ const target = input.entities.find((candidate) => candidate.name === reference);
44
+ const label = humanText(schema.title) ?? humanText(property?.label) ?? humanText(humanize(key));
45
+ if (label === void 0 || schema.writeOnly === true || schema.format === "password") continue;
46
+ const options = Array.isArray(property?.options) ? property.options.map(object) : [];
47
+ const choices = [itemSchema.oneOf, itemSchema.anyOf].flatMap((items) => Array.isArray(items) ? items.map(object) : []);
48
+ const values = [];
49
+ for (const item of (Array.isArray(value) ? value : [value]).slice(0, 5)) {
50
+ if (typeof reference === "string") {
51
+ let title;
52
+ if (target !== void 0 && typeof item === "string" && typeof target.title === "string") {
53
+ const cacheKey = `${reference}:${item}`;
54
+ if (!read.has(cacheKey)) read.set(cacheKey, input.read(reference, item));
55
+ const value = (await read.get(cacheKey))?.[target.title];
56
+ title = value === item ? void 0 : humanText(value);
57
+ }
58
+ values.push(title ?? humanText(target?.label) ?? label);
59
+ continue;
60
+ }
61
+ if (/^(id|.*_id)$/i.test(key) || itemSchema.format === "uuid" || typeof item === "object") continue;
62
+ const option = options.find((candidate) => candidate.value === item);
63
+ const choice = choices.find((candidate) => candidate.const === item);
64
+ const enumeration = Array.isArray(itemSchema.enum) || choices.length > 0 || options.length > 0;
65
+ const text = humanText(option?.label) ?? humanText(choice?.title) ?? (enumeration ? void 0 : typeof item === "string" ? humanText(humanize(item)) : typeof item === "number" || typeof item === "boolean" ? String(item) : void 0);
66
+ if (text !== void 0) values.push(text);
67
+ }
68
+ if (values.length > 0) fields.push({
69
+ label,
70
+ value: values.join(", ")
71
+ });
72
+ if (fields.length === 6) break;
73
+ }
74
+ return {
75
+ action: humanText(input.title) ?? "the requested action",
76
+ ...humanText(entity.label) === void 0 ? {} : { entity: humanText(entity.label) },
77
+ ...humanText(input.description?.split("\n\n")[0]) === void 0 ? {} : { description: humanText(input.description?.split("\n\n")[0]) },
78
+ fields
79
+ };
80
+ }
81
+ export { describeConfirmation, fieldSchema, humanText, humanize, object };
@@ -1,11 +1,13 @@
1
1
  import { type StateHandle } from "eve/context";
2
2
  import type { ApprovalConfiguration } from "eve/tools/approval";
3
+ import type { ConfirmationDescription } from "./confirmation-description.js";
3
4
  export declare const CONFIRMATION_TTL_MS: number;
4
5
  export interface ActionAnnotations {
5
6
  readonly readOnlyHint?: boolean;
6
7
  readonly destructiveHint?: boolean;
7
8
  readonly irreversibleHint?: boolean;
8
9
  readonly idempotentHint?: boolean;
10
+ readonly openWorldHint?: boolean;
9
11
  }
10
12
  /** Safety comes from the authenticated ontology, never from the name or model prose. */
11
13
  export declare function needsConfirmation(labels: ActionAnnotations): boolean;
@@ -16,11 +18,13 @@ export interface Confirmation {
16
18
  readonly inputHash: string;
17
19
  readonly expiresAt: number;
18
20
  readonly status: "pending" | "approved" | "consumed" | "cancelled";
21
+ readonly description?: ConfirmationDescription;
19
22
  }
20
23
  export declare const confirmations: StateHandle<Record<string, Confirmation>>;
21
24
  /** Eve pauses the original call and rechecks this policy immediately before executing it. */
22
25
  export declare function actionApproval(options: {
23
26
  readonly labels: (memberId: string, action: string, input: Readonly<Record<string, unknown>>) => Promise<ActionAnnotations>;
27
+ readonly describe?: (memberId: string, action: string, input: Readonly<Record<string, unknown>>) => Promise<ConfirmationDescription>;
24
28
  readonly state?: StateHandle<Record<string, Confirmation>>;
25
29
  readonly now?: () => number;
26
30
  }): ApprovalConfiguration;
@@ -9,7 +9,7 @@ import { createHash } from "node:crypto";
9
9
  import { defineState } from "eve/context";
10
10
  const CONFIRMATION_TTL_MS = 6e5;
11
11
  function needsConfirmation(labels) {
12
- return labels.readOnlyHint !== true && (labels.destructiveHint === true || labels.irreversibleHint === true || labels.destructiveHint === void 0 && labels.irreversibleHint === void 0);
12
+ return labels.readOnlyHint !== true && (labels.destructiveHint === true || labels.irreversibleHint === true || labels.openWorldHint === true || labels.destructiveHint === void 0 && labels.irreversibleHint === void 0);
13
13
  }
14
14
  function canonical(value) {
15
15
  if (Array.isArray(value)) return `[${value.map(canonical).join(",")}]`;
@@ -59,6 +59,12 @@ function actionApproval(options) {
59
59
  }
60
60
  if (saved !== void 0) return denied("The original call is waiting for its Confirm / Cancel buttons.");
61
61
  if (!needsConfirmation(labels)) return "approved";
62
+ let description;
63
+ try {
64
+ description = await options.describe?.(memberId, ctx.toolName, ctx.toolInput ?? {});
65
+ } catch (error) {
66
+ console.warn("The confirmation description could not be read.", error);
67
+ }
62
68
  state.update((all) => ({
63
69
  ...all,
64
70
  [ctx.callId]: {
@@ -66,7 +72,8 @@ function actionApproval(options) {
66
72
  action: ctx.toolName,
67
73
  inputHash: hash,
68
74
  expiresAt: now() + CONFIRMATION_TTL_MS,
69
- status: "pending"
75
+ status: "pending",
76
+ ...description === void 0 ? {} : { description }
70
77
  }
71
78
  }));
72
79
  return "user-approval";
@@ -49,6 +49,8 @@ export interface Conversations {
49
49
  readonly limit?: number;
50
50
  }): Promise<ConversationPage>;
51
51
  }
52
+ /** Collapse delivery snapshots from older archives before building a user/model transcript. */
53
+ export declare function collapseConversationEvents(events: ConversationPage["events"]): ConversationPage["events"];
52
54
  export declare function conversationEvent(input: Pick<ConversationEvent, "eventId" | "provider" | "chatId" | "chatType" | "kind"> & Partial<ConversationEvent>): ConversationEvent;
53
55
  export declare function conversations(input?: {
54
56
  readonly environment?: Environment;
@@ -6,6 +6,18 @@ __eveDirname(__filename);
6
6
  __eveCreateRequire(import.meta.url);
7
7
  import { proxyEnvironment } from "./environment.mjs";
8
8
  import { runtimeToken } from "./runtime-token.mjs";
9
+ function collapseConversationEvents(events) {
10
+ const latest = new Map();
11
+ for (const event of events) {
12
+ const key = event.kind === "outbound" && /^telegram:(send|edit):/u.test(event.eventId) ? event.eventId.replace(/:result$/u, "") : event.eventId;
13
+ const previous = latest.get(key);
14
+ if (previous === void 0 || (/^\d+$/u.test(event.sequence) && /^\d+$/u.test(previous.sequence) ? BigInt(event.sequence) > BigInt(previous.sequence) : event.storedAt > previous.storedAt)) latest.set(key, {
15
+ ...event,
16
+ eventId: key
17
+ });
18
+ }
19
+ return [...latest.values()];
20
+ }
9
21
  function conversationEvent(input) {
10
22
  return {
11
23
  threadId: null,
@@ -78,12 +90,12 @@ function summarizeArguments(value) {
78
90
  return (JSON.stringify(visit(value, 0)) ?? "{}").slice(0, 4e3);
79
91
  }
80
92
  async function storedConversationContext(archive, coordinates) {
81
- const messages = (await archive.context({
93
+ const messages = collapseConversationEvents((await archive.context({
82
94
  provider: coordinates.provider,
83
95
  chatId: coordinates.chatId,
84
96
  ...coordinates.threadId === null ? {} : { threadId: coordinates.threadId },
85
97
  limit: 100
86
- })).events.filter((event) => event.kind === "inbound" || event.kind === "outbound" && event.status === "completed");
98
+ })).events).filter((event) => event.kind === "inbound" || event.kind === "outbound" && event.status === "completed");
87
99
  const selected = [];
88
100
  let characters = 0;
89
101
  for (const event of messages) {
@@ -104,4 +116,4 @@ async function storedConversationContext(archive, coordinates) {
104
116
  JSON.stringify(selected.reverse())
105
117
  ].join("\n");
106
118
  }
107
- export { conversationEvent, conversations, storedConversationContext, summarizeArguments };
119
+ export { collapseConversationEvents, conversationEvent, conversations, storedConversationContext, summarizeArguments };
@@ -11,6 +11,7 @@ export interface PairedMember {
11
11
  readonly id: string;
12
12
  readonly name: string | null;
13
13
  readonly roles: ReadonlyArray<string>;
14
+ readonly locale?: string;
14
15
  }
15
16
  export type IdentityLookup = {
16
17
  readonly status: "paired";
@@ -44,7 +44,8 @@ function pairedMember(value) {
44
44
  return {
45
45
  id: user.id,
46
46
  name: typeof user.name === "string" ? user.name : null,
47
- roles
47
+ roles,
48
+ ...typeof user.locale === "string" ? { locale: user.locale } : {}
48
49
  };
49
50
  }
50
51
  function pairing(value) {
@@ -8,7 +8,7 @@ import { appOrigin } from "./environment.mjs";
8
8
  import { memberIdOf } from "./telegram/principal.mjs";
9
9
  import { actionApproval } from "./confirmations.mjs";
10
10
  import { runtimeToken, tokenExpiry } from "./runtime-token.mjs";
11
- import { ontologyToolLabels } from "./ontology-tools.mjs";
11
+ import { ontologyTools } from "./ontology-tools.mjs";
12
12
  import { defineMcpClientConnection } from "eve/connections";
13
13
  const ACT_AS_HEADER = "X-SegmentStream-Act-As";
14
14
  const DESCRIPTION = "The company's ontology: its records, their history, and the actions that change them, narrowed to what the member you act for may read and do. Call describe for the catalog and me for the member.";
@@ -30,7 +30,7 @@ function ontologyConnection(input) {
30
30
  url: `${origin}/mcp`,
31
31
  description: DESCRIPTION,
32
32
  instanceKey: `${origin}/mcp#member:${memberId}`,
33
- approval: actionApproval({ labels: ontologyToolLabels(input) }),
33
+ approval: actionApproval(ontologyTools(input)),
34
34
  auth: {
35
35
  credentialOwner: "app",
36
36
  getToken: async () => {
@@ -2,10 +2,14 @@ import type { ActionAnnotations } from "./confirmations.js";
2
2
  import { type Environment } from "./environment.js";
3
3
  import { type RuntimeToken } from "./runtime-token.js";
4
4
  /** A fresh, permission-filtered catalog. Listing tools cannot run an action's prepare step. */
5
- export declare function ontologyToolLabels(options?: {
5
+ export declare function ontologyTools(options?: {
6
6
  readonly environment?: Environment;
7
7
  readonly token?: RuntimeToken;
8
8
  readonly fetch?: typeof fetch;
9
- }): (memberId: string, action: string) => Promise<ActionAnnotations>;
9
+ }): {
10
+ labels(memberId: string, action: string): Promise<ActionAnnotations>;
11
+ describe(memberId: string, action: string, input: Readonly<Record<string, unknown>>): Promise<import("./confirmation-description.js").ConfirmationDescription>;
12
+ };
13
+ export declare function ontologyToolLabels(options?: Parameters<typeof ontologyTools>[0]): (memberId: string, action: string) => Promise<ActionAnnotations>;
10
14
  /** MCP normalizes an authored camelCase action to snake_case. */
11
15
  export declare function actionToolName(entity: string, action: string): string;
@@ -5,37 +5,107 @@ const __filename = __eveFileURLToPath(import.meta.url);
5
5
  __eveDirname(__filename);
6
6
  __eveCreateRequire(import.meta.url);
7
7
  import { appOrigin } from "./environment.mjs";
8
+ import { describeConfirmation, object } from "./confirmation-description.mjs";
9
+ import { redactDiagnostic } from "./diagnostics.mjs";
8
10
  import { runtimeToken } from "./runtime-token.mjs";
9
- function ontologyToolLabels(options = {}) {
10
- return async (memberId, action) => {
11
- const response = await (options.fetch ?? fetch)(`${appOrigin(options.environment ?? process.env)}/mcp`, {
12
- method: "POST",
13
- headers: {
14
- authorization: `Bearer ${await (options.token ?? runtimeToken)()}`,
15
- "X-SegmentStream-Act-As": memberId,
16
- "content-type": "application/json",
17
- accept: "application/json, text/event-stream",
18
- "mcp-protocol-version": "2025-11-25"
19
- },
20
- body: JSON.stringify({
21
- jsonrpc: "2.0",
22
- id: "approval-tools",
23
- method: "tools/list",
24
- params: {}
25
- }),
26
- signal: AbortSignal.timeout(15e3),
27
- redirect: "error"
28
- });
29
- if (!response.ok) throw new Error("Cannot check action permission.");
30
- const body = await response.json();
11
+ function ontologyTools(options = {}) {
12
+ async function rpc(memberId, method, params, signal) {
13
+ const credential = await (options.token ?? runtimeToken)();
14
+ const secrets = new Set([credential]);
15
+ try {
16
+ const response = await (options.fetch ?? fetch)(`${appOrigin(options.environment ?? process.env)}/mcp`, {
17
+ method: "POST",
18
+ headers: {
19
+ authorization: `Bearer ${credential}`,
20
+ "X-SegmentStream-Act-As": memberId,
21
+ "content-type": "application/json",
22
+ accept: "application/json, text/event-stream",
23
+ "mcp-protocol-version": "2025-11-25"
24
+ },
25
+ body: JSON.stringify({
26
+ jsonrpc: "2.0",
27
+ id: "approval-tools",
28
+ method,
29
+ params
30
+ }),
31
+ signal: signal ?? AbortSignal.timeout(15e3),
32
+ redirect: "error"
33
+ });
34
+ if (!response.ok) throw new Error(`Cannot check action permission (HTTP ${response.status}).`);
35
+ const body = object(await response.json());
36
+ if (body.error !== void 0) throw new Error("Ontology metadata unavailable.", { cause: body.error });
37
+ return object(body.result);
38
+ } catch (error) {
39
+ throw new Error("The ontology metadata request failed.", { cause: redactDiagnostic(error, secrets) });
40
+ }
41
+ }
42
+ async function toolFor(memberId, action, signal) {
43
+ const result = await rpc(memberId, "tools/list", {}, signal);
44
+ const tools = Array.isArray(result.tools) ? result.tools.map(object) : [];
31
45
  const bareName = action.split("__").at(-1);
32
- const tool = body.result?.tools?.find((candidate) => candidate.name === bareName);
46
+ const tool = tools.find((candidate) => candidate.name === bareName);
33
47
  if (tool === void 0) throw new Error("Action not permitted.");
34
- return tool.annotations ?? {};
48
+ return {
49
+ tool,
50
+ tools
51
+ };
52
+ }
53
+ return {
54
+ async labels(memberId, action) {
55
+ return object((await toolFor(memberId, action)).tool.annotations);
56
+ },
57
+ async describe(memberId, action, input) {
58
+ const signal = AbortSignal.timeout(15e3);
59
+ const { tool, tools } = await toolFor(memberId, action, signal);
60
+ let entities = [];
61
+ try {
62
+ const catalog = await rpc(memberId, "resources/read", { uri: "ontology://catalog" }, signal);
63
+ const text = object(Array.isArray(catalog.contents) ? catalog.contents[0] : void 0).text;
64
+ const manifest = object(typeof text === "string" ? JSON.parse(text) : void 0);
65
+ entities = Array.isArray(manifest.entities) ? manifest.entities.map(object) : [];
66
+ } catch (error) {
67
+ console.warn("The confirmation catalog could not be read.", error);
68
+ }
69
+ const entity = entities.find((candidate) => {
70
+ return [
71
+ "create",
72
+ "update",
73
+ "delete",
74
+ "link",
75
+ "unlink",
76
+ ...Array.isArray(candidate.actions) ? candidate.actions.map((item) => object(item).name) : []
77
+ ].some((name) => typeof name === "string" && actionToolName(String(candidate.name), name) === tool.name);
78
+ });
79
+ return describeConfirmation({
80
+ title: typeof tool.title === "string" ? tool.title : void 0,
81
+ description: typeof tool.description === "string" ? tool.description : void 0,
82
+ schema: object(tool.inputSchema),
83
+ entity,
84
+ entities,
85
+ values: input,
86
+ async read(entityName, id) {
87
+ const name = actionToolName(entityName, "get");
88
+ if (!tools.some((candidate) => candidate.name === name && object(candidate.annotations).readOnlyHint === true)) return;
89
+ try {
90
+ const result = await rpc(memberId, "tools/call", {
91
+ name,
92
+ arguments: { id }
93
+ }, signal);
94
+ return result.isError === true ? void 0 : object(result.structuredContent);
95
+ } catch (error) {
96
+ console.warn("A confirmation record title could not be read.", error);
97
+ return;
98
+ }
99
+ }
100
+ });
101
+ }
35
102
  };
36
103
  }
104
+ function ontologyToolLabels(options = {}) {
105
+ return ontologyTools(options).labels;
106
+ }
37
107
  function actionToolName(entity, action) {
38
108
  const snake = (name) => name.replace(/([A-Z])([A-Z][a-z])/g, "$1_$2").replace(/([a-z0-9])([A-Z])/g, "$1_$2").toLowerCase();
39
109
  return `${snake(entity)}_${snake(action)}`;
40
110
  }
41
- export { actionToolName, ontologyToolLabels };
111
+ export { actionToolName, ontologyToolLabels, ontologyTools };
@@ -5,6 +5,7 @@ const __filename = __eveFileURLToPath(import.meta.url);
5
5
  __eveDirname(__filename);
6
6
  __eveCreateRequire(import.meta.url);
7
7
  import { appOrigin, channelsEnabled, proxyEnvironment, runtimeIdentityEnvironment, webhookSecret } from "../environment.mjs";
8
+ import { conversationLanguage } from "./principal.mjs";
8
9
  import { MAXIMUM_DOWNLOAD_BYTES, connectionProxy } from "../connection-proxy.mjs";
9
10
  import { runtimeToken } from "../runtime-token.mjs";
10
11
  import { agentAttachments, parseAttachmentUrl } from "../attachments.mjs";
@@ -12,11 +13,13 @@ import { agentIdentities } from "../identities.mjs";
12
13
  import { conversations } from "../conversations.mjs";
13
14
  import { observedMessages } from "./observed.mjs";
14
15
  import { InboundRequest, currentInboundRequest, runInboundRequest, withReplacedText } from "./inbound-request.mjs";
16
+ import { failureText, reportTelegramError, safeTelegramFetch, withDeliveryLocale } from "./customer-text.mjs";
15
17
  import { captureRouteDecider, captureTelegramFetch, conversationIdentities, telegramConversationCapture } from "./conversations.mjs";
16
18
  import { runtimeTokenVerifier } from "../runtime-identity.mjs";
17
19
  import { botIdentityCache } from "./bot.mjs";
20
+ import { confirmationLocale, confirmationRenderer } from "./confirmation-text.mjs";
18
21
  import { scopedAddress, sessionPolicy } from "./sessions.mjs";
19
- import { confirmationEvents, handleConfirmationCallback } from "./confirmations.mjs";
22
+ import { handleConfirmationCallback, telegramConfirmationEvents } from "./confirmations.mjs";
20
23
  import { agentChats } from "./chats.mjs";
21
24
  import { decisionRouter } from "./router.mjs";
22
25
  import { replyDelivery } from "./silent.mjs";
@@ -25,7 +28,7 @@ import { handleInboundMessage } from "./inbound.mjs";
25
28
  import { TELEGRAM_PLACEHOLDER_TOKEN, TELEGRAM_PROXY_ORIGIN, telegramProxyFetch } from "./proxy-fetch.mjs";
26
29
  import { messagesRoute } from "./requests.mjs";
27
30
  import { healthRoute } from "./health.mjs";
28
- import { withTurnErrorLogging } from "./turn-errors.mjs";
31
+ import { telegramFailureEvents } from "./turn-errors.mjs";
29
32
  import { telegramWebhookVerifier } from "./webhook.mjs";
30
33
  import { telegramChannel } from "eve/channels/telegram";
31
34
  const eveHistory = async (sessionId) => {
@@ -121,56 +124,81 @@ function segmentstreamTelegram(options) {
121
124
  api: {
122
125
  apiBaseUrl: TELEGRAM_PROXY_ORIGIN,
123
126
  fileBaseUrl: TELEGRAM_PROXY_ORIGIN,
124
- fetch: (input, init) => captureTelegramFetch(telegramProxyFetch(proxy()), archive, pairing)(input, init)
127
+ fetch: (input, init) => safeTelegramFetch(captureTelegramFetch(telegramProxyFetch(proxy()), archive, pairing))(input, init)
125
128
  },
126
129
  credentials: {
127
130
  botToken: TELEGRAM_PLACEHOLDER_TOKEN,
128
131
  webhookVerifier: telegramWebhookVerifier(() => webhookSecret(environment))
129
132
  },
130
133
  uploadPolicy: { maxBytes: MAXIMUM_DOWNLOAD_BYTES },
131
- onMessage: (context, message) => handleInboundMessage({
132
- context,
133
- message,
134
- request: currentInboundRequest(),
135
- dependencies: {
136
- policy: settings().policy,
137
- sessions: options.sessions,
138
- sessionPolicy: policy,
139
- sttModel: options.models().stt,
140
- identities: conversationIdentities(pairing()),
141
- bot,
142
- decide: captureRouteDecider(decisionRouter({
143
- decide: options.decide,
144
- model: options.models().decision
145
- })),
146
- transcribe: options.transcribe ?? gatewayTranscriber,
147
- download: (fileId) => downloadTelegramFile({
148
- proxy: proxy(),
149
- fileId
150
- }),
151
- attachments: attachments(),
152
- observed,
153
- readHistory,
154
- history: readInteractions,
155
- chats,
156
- connectUrl: (code) => `${appOrigin(environment)}/agent/connect?code=${encodeURIComponent(code)}`
134
+ onMessage: async (context, message) => {
135
+ const inbound = currentInboundRequest();
136
+ if (inbound !== void 0) {
137
+ inbound.locale = conversationLanguage([message.text || message.caption]) ?? message.from?.languageCode ?? "en";
138
+ inbound.threadId = message.messageThreadId === void 0 ? void 0 : String(message.messageThreadId);
157
139
  }
158
- }),
159
- onCallbackQuery: (context, query) => handleConfirmationCallback({
160
- context,
161
- query,
162
- identities: pairing(),
163
- read: readInteractions,
164
- scope: options.sessions().scope
165
- }),
140
+ try {
141
+ const result = await handleInboundMessage({
142
+ context,
143
+ message,
144
+ request: currentInboundRequest(),
145
+ dependencies: {
146
+ policy: settings().policy,
147
+ sessions: options.sessions,
148
+ sessionPolicy: policy,
149
+ sttModel: options.models().stt,
150
+ identities: conversationIdentities(pairing()),
151
+ bot,
152
+ decide: captureRouteDecider(decisionRouter({
153
+ decide: options.decide,
154
+ model: options.models().decision
155
+ })),
156
+ transcribe: options.transcribe ?? gatewayTranscriber,
157
+ download: (fileId) => downloadTelegramFile({
158
+ proxy: proxy(),
159
+ fileId
160
+ }),
161
+ attachments: attachments(),
162
+ observed,
163
+ readHistory,
164
+ history: readInteractions,
165
+ chats,
166
+ connectUrl: (code) => `${appOrigin(environment)}/agent/connect?code=${encodeURIComponent(code)}`
167
+ }
168
+ });
169
+ if (inbound !== void 0 && result !== null) inbound.locale = confirmationLocale(result.auth);
170
+ return result;
171
+ } catch (error) {
172
+ const errorId = reportTelegramError("inbound_message_failed", error, { chatId: message.chat.id });
173
+ await context.telegram.post(failureText(errorId));
174
+ return null;
175
+ }
176
+ },
177
+ onCallbackQuery: async (context, query) => {
178
+ try {
179
+ await handleConfirmationCallback({
180
+ context,
181
+ query,
182
+ identities: pairing(),
183
+ read: readInteractions,
184
+ scope: options.sessions().scope
185
+ });
186
+ } catch (error) {
187
+ const errorId = reportTelegramError("confirmation_callback_failed", error);
188
+ await context.telegram.answerCallbackQuery({
189
+ callbackQueryId: query.id,
190
+ text: failureText(errorId)
191
+ });
192
+ }
193
+ },
166
194
  events: {
167
- ...confirmationEvents,
195
+ ...telegramFailureEvents,
196
+ ...telegramConfirmationEvents(confirmationRenderer({ model: () => options.models().decision })),
168
197
  "message.completed": replyDelivery(observed)
169
198
  }
170
199
  });
171
200
  const adapter = channel.adapter;
172
201
  const fetchTelegramFile = adapter?.fetchFile;
173
- const turnFailed = adapter?.["turn.failed"];
174
202
  async function fetchFile(url, context) {
175
203
  const reference = parseAttachmentUrl(url);
176
204
  if (reference === void 0) return fetchTelegramFile === void 0 ? null : fetchTelegramFile(url, context);
@@ -191,7 +219,14 @@ function segmentstreamTelegram(options) {
191
219
  ...adapter === void 0 ? {} : { adapter: {
192
220
  ...adapter,
193
221
  fetchFile,
194
- ...turnFailed === void 0 ? {} : { "turn.failed": withTurnErrorLogging(turnFailed) }
222
+ ...Object.fromEntries(Object.entries(adapter).filter(([name, handler]) => name.includes(".") && typeof handler === "function").map(([name, handler]) => [name, async (event, context) => {
223
+ const principal = context.session?.auth.current;
224
+ const locale = confirmationLocale(principal ?? null, context.state.customerLocale ?? "en");
225
+ context.state.customerLocale = locale;
226
+ await withDeliveryLocale(locale, async () => {
227
+ await handler(event, context);
228
+ }, context.state.messageThreadId === null ? void 0 : String(context.state.messageThreadId));
229
+ }]))
195
230
  } },
196
231
  routes: [
197
232
  ...channel.routes.map((route) => gatedRoute({
@@ -37,4 +37,5 @@ export declare function sendAttachments(options: {
37
37
  } | {
38
38
  readonly error: string;
39
39
  readonly detail: string;
40
+ readonly reference: string;
40
41
  }>;