@effect-uai/telegram 0.14.0

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/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Betalyra Sociedade Unipessoal Lda.
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in
13
+ all copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
21
+ THE SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,45 @@
1
+ # @effect-uai/telegram
2
+
3
+ Telegram provider for [`@effect-uai/core`](https://www.npmjs.com/package/@effect-uai/core).
4
+
5
+ Implements the `Messenger` contract over the Bot API, so your agent runs as
6
+ a Telegram bot: DMs, group mentions and commands in, streamed HTML replies,
7
+ media and reactions out. Long-polling, so no public URL, and no SDK.
8
+
9
+ ## Install
10
+
11
+ ```sh
12
+ pnpm add @effect-uai/telegram @effect-uai/core effect
13
+ ```
14
+
15
+ ESM-only. Requires `effect@4.x` and `@effect-uai/core` as peers.
16
+
17
+ ## Usage
18
+
19
+ ```ts
20
+ import { Effect, Redacted } from "effect"
21
+ import { NodeHttpClient } from "@effect/platform-node"
22
+ import { layer as telegramLayer } from "@effect-uai/telegram/Telegram"
23
+
24
+ const provider = telegramLayer({ token: Redacted.make(process.env.TELEGRAM_BOT_TOKEN!) })
25
+
26
+ await Effect.runPromise(
27
+ program.pipe(Effect.scoped, Effect.provide(provider), Effect.provide(NodeHttpClient.layerUndici)),
28
+ )
29
+ ```
30
+
31
+ The layer registers both the provider-typed `Telegram` tag and the generic
32
+ `Messenger` tag. It polls until the scope closes; run one instance per token.
33
+
34
+ ## Markup
35
+
36
+ Text is sent verbatim with `parseMode: "HTML"` by default. Tell the model to
37
+ write Telegram HTML; a message Telegram cannot parse is resent plain.
38
+
39
+ ## Docs
40
+
41
+ <https://effect-uai.betalyra.com/messenger/providers/telegram/>
42
+
43
+ ## License
44
+
45
+ MIT
@@ -0,0 +1,44 @@
1
+ import { r as Config$1 } from "./api-CnvvfWKq.mjs";
2
+ import { t as BotIdentity } from "./events-BkljYfG6.mjs";
3
+ import { Context, Duration, Effect, Layer, Scope } from "effect";
4
+ import { HttpClient } from "effect/unstable/http";
5
+ import { Messenger, MessengerLimits, MessengerService } from "@effect-uai/core/Messenger";
6
+ import { StreamViaEditsOptions } from "@effect-uai/core/MessengerAdapter";
7
+ import * as MessengerError from "@effect-uai/core/MessengerError";
8
+ declare namespace Telegram_d_exports {
9
+ export { BotIdentity, Config, ParseMode, Telegram, TelegramService, layer, limits, make };
10
+ }
11
+ /** Telegram's own markup modes. `Text` bodies are sent verbatim under one. */
12
+ type ParseMode = "HTML" | "MarkdownV2" | "Markdown";
13
+ type Config = Config$1 & {
14
+ /**
15
+ * Markup the model is prompted to write. Default `"HTML"`; `"plain"` sends
16
+ * text with no `parse_mode` at all.
17
+ */
18
+ readonly parseMode?: ParseMode | "plain";
19
+ /** Long-poll wait per `getUpdates` call. Default 30 seconds. */
20
+ readonly pollTimeout?: Duration.Input;
21
+ /** Tuning for `stream`, which is post-then-edit on Telegram. */
22
+ readonly stream?: StreamViaEditsOptions;
23
+ };
24
+ type TelegramService = MessengerService & {
25
+ /** From `getMe` at layer build: the identity behind `addressed`. */
26
+ readonly bot: BotIdentity;
27
+ };
28
+ declare const Telegram_base: Context.ServiceClass<Telegram, "@betalyra/effect-uai/providers/telegram/Telegram", TelegramService>;
29
+ /**
30
+ * Provider-typed tag. Yield this for the bot identity; yield the generic
31
+ * `Messenger` tag for provider-portable code. Both are registered by {@link layer}.
32
+ */
33
+ declare class Telegram extends Telegram_base {}
34
+ declare const limits: MessengerLimits;
35
+ declare const make: (cfg: Config) => Effect.Effect<TelegramService, MessengerError.MessengerConnectFailed, HttpClient.HttpClient | Scope.Scope>;
36
+ /**
37
+ * One poller, registered under both the `Telegram` and `Messenger` tags. The
38
+ * long-poll loop starts when the layer is built and stops when its scope
39
+ * closes; `events` ends with it. Telegram allows one poller per token.
40
+ */
41
+ declare const layer: (cfg: Config) => Layer.Layer<Telegram | Messenger, MessengerError.MessengerConnectFailed, HttpClient.HttpClient>;
42
+ //#endregion
43
+ export { type BotIdentity, Config, ParseMode, Telegram, TelegramService, layer, limits, make, Telegram_d_exports as t };
44
+ //# sourceMappingURL=Telegram.d.mts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"Telegram.d.mts","names":[],"sources":["../src/Telegram.ts"],"mappings":";;;;;;;;;;;KA8CY;KAEA,SAAS;;;;;WAKV,YAAY;;WAEZ,cAAc,SAAS;;WAEvB,SAAS;;KAGR,kBAAkB;;WAEnB,KAAK;;;;;;;cAOH,iBAAiB;cAIjB,QAAQ;cA2GR,OAAI,KACV,WACJ,OAAO,OACR,iBACA,eAAe,wBACf,WAAW,aAAa,MAAM;;;;;;cA4NnB,QAAK,KACX,WACJ,MAAM,MACP,WAAW,WACX,eAAe,wBACf,WAAW"}
@@ -0,0 +1,208 @@
1
+ import { t as __exportAll } from "./rolldown-runtime-D7D4PA-g.mjs";
2
+ import { call, decoded, provider, upload } from "./internal/api.mjs";
3
+ import { Update, allowedUpdates, toEvents } from "./internal/events.mjs";
4
+ import { Array, Context, Duration, Effect, Encoding, Layer, Match, Option, Queue, Ref, Schedule, Schema, Stream } from "effect";
5
+ import { HttpClient } from "effect/unstable/http";
6
+ import { CurrentConversation, MessageId, Messenger } from "@effect-uai/core/Messenger";
7
+ import { splitForLimit, streamViaEdits } from "@effect-uai/core/MessengerAdapter";
8
+ import * as MessengerError from "@effect-uai/core/MessengerError";
9
+ //#region src/Telegram.ts
10
+ var Telegram_exports = /* @__PURE__ */ __exportAll({
11
+ Telegram: () => Telegram,
12
+ layer: () => layer,
13
+ limits: () => limits,
14
+ make: () => make
15
+ });
16
+ /**
17
+ * Provider-typed tag. Yield this for the bot identity; yield the generic
18
+ * `Messenger` tag for provider-portable code. Both are registered by {@link layer}.
19
+ */
20
+ var Telegram = class extends Context.Service()("@betalyra/effect-uai/providers/telegram/Telegram") {};
21
+ const limits = {
22
+ maxText: 4096,
23
+ maxCaption: 1024
24
+ };
25
+ const Me = Schema.Struct({
26
+ id: Schema.Number,
27
+ username: Schema.String
28
+ });
29
+ const Sent = Schema.Struct({ message_id: Schema.Number });
30
+ const Updates = Schema.Array(Update);
31
+ const RawCall = Schema.Struct({
32
+ method: Schema.String,
33
+ params: Schema.optional(Schema.Record(Schema.String, Schema.Unknown))
34
+ });
35
+ const isApiFailure = (e) => e._tag === "TelegramApiFailure";
36
+ const describes = (needle) => (e) => isApiFailure(e) && e.description.includes(needle);
37
+ const reasonOf = (e) => isApiFailure(e) ? e.description : "rate limited";
38
+ /** An API rejection becomes the verb's `MessengerRequestFailed`; typed errors pass through. */
39
+ const requestFailed = (operation) => (e) => Match.value(e).pipe(Match.tag("TelegramApiFailure", (failure) => new MessengerError.MessengerRequestFailed({
40
+ provider,
41
+ operation,
42
+ reason: failure.description,
43
+ raw: failure.raw
44
+ })), Match.orElse((typed) => typed));
45
+ const unsupported = (capability, reason) => new MessengerError.MessengerUnsupported({
46
+ provider,
47
+ capability,
48
+ reason
49
+ });
50
+ const isFatalPoll = (e) => isApiFailure(e) && (e.code === 401 || e.code === 409);
51
+ const pollBackoff = Schedule.forever.pipe(Schedule.setInputType(), Schedule.modifyDelay(({ input }) => Effect.succeed(input._tag === "MessengerRateLimited" ? input.retryAfter : Duration.seconds(5))));
52
+ const target = (at) => ({
53
+ chat_id: at.channel,
54
+ ...at.thread !== void 0 && { message_thread_id: Number(at.thread) }
55
+ });
56
+ const replyParams = (msg) => msg.replyTo === void 0 ? {} : { reply_parameters: { message_id: Number(msg.replyTo) } };
57
+ const parseParams = (cfg) => cfg.parseMode === "plain" ? {} : { parse_mode: cfg.parseMode ?? "HTML" };
58
+ /** The endpoint a `MediaSource` goes through, decided by its MIME type. */
59
+ const mediaRoute = (media) => Match.value(media.mimeType ?? "").pipe(Match.when((m) => m.startsWith("image/"), () => ({
60
+ method: "sendPhoto",
61
+ field: "photo"
62
+ })), Match.when((m) => m.startsWith("audio/"), () => ({
63
+ method: "sendAudio",
64
+ field: "audio"
65
+ })), Match.when((m) => m.startsWith("video/"), () => ({
66
+ method: "sendVideo",
67
+ field: "video"
68
+ })), Match.orElse(() => ({
69
+ method: "sendDocument",
70
+ field: "document"
71
+ })));
72
+ const defaultFilename = (media) => `file.${media.mimeType?.split("/")[1] ?? "bin"}`;
73
+ const invalidMedia = (raw) => new MessengerError.MessengerRequestFailed({
74
+ provider,
75
+ operation: "post",
76
+ reason: "invalid base64 media",
77
+ raw
78
+ });
79
+ const make = (cfg) => Effect.gen(function* () {
80
+ const client = yield* HttpClient.HttpClient;
81
+ const withClient = Effect.provideService(HttpClient.HttpClient, client);
82
+ const call$1 = (method, params) => withClient(call(cfg)(method, params));
83
+ const upload$1 = (method, params, file) => withClient(upload(cfg)(method, params, file));
84
+ const bot = yield* call$1("getMe").pipe(decoded("getMe", Me), Effect.mapError((e) => new MessengerError.MessengerConnectFailed({
85
+ provider,
86
+ reason: reasonOf(e),
87
+ raw: e.raw
88
+ })));
89
+ const inbox = yield* Queue.unbounded();
90
+ const offset = yield* Ref.make(0);
91
+ const toEvents$1 = toEvents(bot);
92
+ const acknowledge = (update) => Match.value(update).pipe(Match.when({ callback_query: Match.defined }, ({ callback_query }) => call$1("answerCallbackQuery", { callback_query_id: callback_query.id }).pipe(Effect.ignore)), Match.orElse(() => Effect.void));
93
+ yield* Effect.gen(function* () {
94
+ const updates = yield* call$1("getUpdates", {
95
+ offset: yield* Ref.get(offset),
96
+ timeout: Duration.toSeconds(cfg.pollTimeout ?? "30 seconds"),
97
+ allowed_updates: allowedUpdates
98
+ }).pipe(decoded("getUpdates", Updates));
99
+ yield* Effect.forEach(updates, (update) => Effect.gen(function* () {
100
+ yield* Ref.set(offset, update.update_id + 1);
101
+ yield* acknowledge(update);
102
+ yield* Queue.offerAll(inbox, toEvents$1(update));
103
+ }));
104
+ }).pipe(Effect.retry({
105
+ while: (e) => !isFatalPoll(e),
106
+ schedule: pollBackoff
107
+ }), Effect.forever, Effect.catch((e) => Queue.fail(inbox, new MessengerError.MessengerTransportClosed({
108
+ provider,
109
+ reason: reasonOf(e),
110
+ raw: e.raw
111
+ }))), Effect.ensuring(Queue.end(inbox)), Effect.forkScoped);
112
+ const sendText = (method, params) => call$1(method, {
113
+ ...params,
114
+ ...parseParams(cfg)
115
+ }).pipe(Effect.catchIf(describes("can't parse entities"), () => call$1(method, params)));
116
+ const sendMedia = (at, msg, body) => {
117
+ const { method, field } = mediaRoute(body.media);
118
+ const params = {
119
+ ...target(at),
120
+ ...replyParams(msg),
121
+ ...body.caption !== void 0 && {
122
+ caption: body.caption,
123
+ ...parseParams(cfg)
124
+ }
125
+ };
126
+ const send = (bytes, mimeType) => upload$1(method, params, {
127
+ field,
128
+ bytes,
129
+ filename: body.filename ?? defaultFilename(body.media),
130
+ mimeType
131
+ });
132
+ return Match.value(body.media).pipe(Match.tag("url", ({ url }) => call$1(method, {
133
+ ...params,
134
+ [field]: url
135
+ })), Match.tag("bytes", ({ bytes, mimeType }) => send(bytes, mimeType)), Match.tag("base64", ({ base64, mimeType }) => Effect.fromResult(Encoding.decodeBase64(base64)).pipe(Effect.mapError(invalidMedia), Effect.flatMap((bytes) => send(bytes, mimeType)))), Match.exhaustive);
136
+ };
137
+ const sendRaw = (payload) => Effect.gen(function* () {
138
+ const { method, params } = yield* Schema.decodeUnknownEffect(RawCall)(payload).pipe(Effect.mapError(() => new MessengerError.MessengerRequestFailed({
139
+ provider,
140
+ operation: "post",
141
+ reason: "raw payload must be { method, params? }",
142
+ raw: payload
143
+ })));
144
+ return yield* call$1(method, params);
145
+ });
146
+ const sendChunks = (at, msg, text) => Effect.gen(function* () {
147
+ const [first, ...rest] = splitForLimit(text, limits.maxText);
148
+ const send = (chunk, extra) => sendText("sendMessage", {
149
+ ...target(at),
150
+ ...extra,
151
+ text: chunk
152
+ }).pipe(decoded("sendMessage", Sent));
153
+ const head = yield* send(first, replyParams(msg));
154
+ const tail = yield* Effect.forEach(rest, (chunk) => send(chunk, {}));
155
+ return Option.getOrElse(Array.last(tail), () => head);
156
+ });
157
+ const post = (msg) => Effect.gen(function* () {
158
+ const at = yield* CurrentConversation;
159
+ const sent = yield* Match.value(msg.body).pipe(Match.tag("Text", ({ text }) => sendChunks(at, msg, text)), Match.tag("Media", (body) => sendMedia(at, msg, body).pipe(decoded("sendMedia", Sent))), Match.tag("Raw", ({ payload }) => sendRaw(payload).pipe(decoded("raw", Sent))), Match.exhaustive);
160
+ return MessageId(String(sent.message_id));
161
+ }).pipe(Effect.mapError(requestFailed("post")));
162
+ const edit = (ref, next) => Match.value(next.body).pipe(Match.tag("Text", ({ text }) => sendText("editMessageText", {
163
+ chat_id: ref.conversation.channel,
164
+ message_id: Number(ref.id),
165
+ text
166
+ }).pipe(Effect.asVoid, Effect.mapError(requestFailed("edit")))), Match.tag("Raw", ({ payload }) => sendRaw(payload).pipe(Effect.asVoid, Effect.mapError(requestFailed("edit")))), Match.tag("Media", () => Effect.fail(unsupported("media edits", "send a new message instead"))), Match.exhaustive);
167
+ const react = (ref, emoji) => call$1("setMessageReaction", {
168
+ chat_id: ref.conversation.channel,
169
+ message_id: Number(ref.id),
170
+ reaction: [{
171
+ type: "emoji",
172
+ emoji
173
+ }]
174
+ }).pipe(Effect.asVoid, Effect.catchIf(describes("REACTION_INVALID"), () => Effect.fail(unsupported("reaction", `${emoji} is not in Telegram's reaction set`))), Effect.mapError(requestFailed("react")));
175
+ const typing = Effect.gen(function* () {
176
+ const at = yield* CurrentConversation;
177
+ const once = call$1("sendChatAction", {
178
+ ...target(at),
179
+ action: "typing"
180
+ });
181
+ yield* once.pipe(Effect.mapError(requestFailed("typing")));
182
+ yield* once.pipe(Effect.ignore, Effect.schedule(Schedule.spaced("4 seconds")), Effect.forkScoped);
183
+ });
184
+ return {
185
+ bot,
186
+ events: Stream.fromQueue(inbox),
187
+ post,
188
+ edit,
189
+ react,
190
+ typing,
191
+ stream: streamViaEdits({
192
+ post,
193
+ edit,
194
+ limits
195
+ }, cfg.stream),
196
+ limits
197
+ };
198
+ });
199
+ /**
200
+ * One poller, registered under both the `Telegram` and `Messenger` tags. The
201
+ * long-poll loop starts when the layer is built and stops when its scope
202
+ * closes; `events` ends with it. Telegram allows one poller per token.
203
+ */
204
+ const layer = (cfg) => Layer.effectContext(Effect.map(make(cfg), (service) => Context.make(Telegram, service).pipe(Context.add(Messenger, service))));
205
+ //#endregion
206
+ export { Telegram, layer, limits, make, Telegram_exports as t };
207
+
208
+ //# sourceMappingURL=Telegram.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"Telegram.mjs","names":["Events.Update","Api.provider","call","Api.call","upload","Api.upload","Api.decoded","toEvents","Events.toEvents","Events.allowedUpdates","Arr"],"sources":["../src/Telegram.ts"],"sourcesContent":["import {\n Array as Arr,\n type Cause,\n Context,\n Duration,\n Effect,\n Encoding,\n Layer,\n Match,\n Option,\n Queue,\n Ref,\n Schedule,\n Schema,\n type Scope,\n Stream,\n} from \"effect\"\nimport { HttpClient } from \"effect/unstable/http\"\nimport type { MediaSource } from \"@effect-uai/core/Media\"\nimport {\n CurrentConversation,\n type ConversationRef,\n type InboundEvent,\n MessageId,\n Messenger,\n type MessengerLimits,\n type MessengerService,\n type Outbound,\n type OutboundBody,\n} from \"@effect-uai/core/Messenger\"\nimport {\n type StreamViaEditsOptions,\n splitForLimit,\n streamViaEdits,\n} from \"@effect-uai/core/MessengerAdapter\"\nimport * as MessengerError from \"@effect-uai/core/MessengerError\"\nimport * as Api from \"./internal/api.js\"\nimport * as Events from \"./internal/events.js\"\n\nexport type { BotIdentity } from \"./internal/events.js\"\n\n// ---------------------------------------------------------------------------\n// Public types\n// ---------------------------------------------------------------------------\n\n/** Telegram's own markup modes. `Text` bodies are sent verbatim under one. */\nexport type ParseMode = \"HTML\" | \"MarkdownV2\" | \"Markdown\"\n\nexport type Config = Api.Config & {\n /**\n * Markup the model is prompted to write. Default `\"HTML\"`; `\"plain\"` sends\n * text with no `parse_mode` at all.\n */\n readonly parseMode?: ParseMode | \"plain\"\n /** Long-poll wait per `getUpdates` call. Default 30 seconds. */\n readonly pollTimeout?: Duration.Input\n /** Tuning for `stream`, which is post-then-edit on Telegram. */\n readonly stream?: StreamViaEditsOptions\n}\n\nexport type TelegramService = MessengerService & {\n /** From `getMe` at layer build: the identity behind `addressed`. */\n readonly bot: Events.BotIdentity\n}\n\n/**\n * Provider-typed tag. Yield this for the bot identity; yield the generic\n * `Messenger` tag for provider-portable code. Both are registered by {@link layer}.\n */\nexport class Telegram extends Context.Service<Telegram, TelegramService>()(\n \"@betalyra/effect-uai/providers/telegram/Telegram\",\n) {}\n\nexport const limits: MessengerLimits = { maxText: 4096, maxCaption: 1024 }\n\n// ---------------------------------------------------------------------------\n// Wire results\n// ---------------------------------------------------------------------------\n\nconst Me = Schema.Struct({ id: Schema.Number, username: Schema.String })\nconst Sent = Schema.Struct({ message_id: Schema.Number })\nconst Updates = Schema.Array(Events.Update)\nconst RawCall = Schema.Struct({\n method: Schema.String,\n params: Schema.optional(Schema.Record(Schema.String, Schema.Unknown)),\n})\n\n// ---------------------------------------------------------------------------\n// Errors\n// ---------------------------------------------------------------------------\n\nconst isApiFailure = (e: Api.ApiError): e is Api.ApiFailure => e._tag === \"TelegramApiFailure\"\n\nconst describes = (needle: string) => (e: Api.ApiError) =>\n isApiFailure(e) && e.description.includes(needle)\n\nconst reasonOf = (e: Api.ApiError): string => (isApiFailure(e) ? e.description : \"rate limited\")\n\n/** An API rejection becomes the verb's `MessengerRequestFailed`; typed errors pass through. */\nconst requestFailed =\n (operation: MessengerError.MessengerOperation) =>\n (e: Api.ApiError | MessengerError.MessengerError): MessengerError.MessengerError =>\n Match.value(e).pipe(\n Match.tag(\n \"TelegramApiFailure\",\n (failure) =>\n new MessengerError.MessengerRequestFailed({\n provider: Api.provider,\n operation,\n reason: failure.description,\n raw: failure.raw,\n }),\n ),\n Match.orElse((typed) => typed),\n )\n\nconst unsupported = (capability: string, reason: string) =>\n new MessengerError.MessengerUnsupported({ provider: Api.provider, capability, reason })\n\n// 401 is a dead token and 409 a second poller on it; nothing else ends the stream.\nconst isFatalPoll = (e: Api.ApiError) => isApiFailure(e) && (e.code === 401 || e.code === 409)\n\n// Transient poll failures wait five seconds, or exactly what a 429 asked for.\nconst pollBackoff: Schedule.Schedule<number, Api.ApiError> = Schedule.forever.pipe(\n Schedule.setInputType<Api.ApiError>(),\n Schedule.modifyDelay(({ input }) =>\n Effect.succeed(input._tag === \"MessengerRateLimited\" ? input.retryAfter : Duration.seconds(5)),\n ),\n)\n\n// ---------------------------------------------------------------------------\n// Outbound params\n// ---------------------------------------------------------------------------\n\nconst target = (at: ConversationRef): Api.Params => ({\n chat_id: at.channel,\n ...(at.thread !== undefined && { message_thread_id: Number(at.thread) }),\n})\n\nconst replyParams = (msg: Outbound): Api.Params =>\n msg.replyTo === undefined ? {} : { reply_parameters: { message_id: Number(msg.replyTo) } }\n\nconst parseParams = (cfg: Config): Api.Params =>\n cfg.parseMode === \"plain\" ? {} : { parse_mode: cfg.parseMode ?? \"HTML\" }\n\ntype MediaRoute = { readonly method: string; readonly field: string }\n\n/** The endpoint a `MediaSource` goes through, decided by its MIME type. */\nconst mediaRoute = (media: MediaSource): MediaRoute =>\n Match.value(media.mimeType ?? \"\").pipe(\n Match.when(\n (m) => m.startsWith(\"image/\"),\n (): MediaRoute => ({ method: \"sendPhoto\", field: \"photo\" }),\n ),\n Match.when(\n (m) => m.startsWith(\"audio/\"),\n (): MediaRoute => ({ method: \"sendAudio\", field: \"audio\" }),\n ),\n Match.when(\n (m) => m.startsWith(\"video/\"),\n (): MediaRoute => ({ method: \"sendVideo\", field: \"video\" }),\n ),\n Match.orElse((): MediaRoute => ({ method: \"sendDocument\", field: \"document\" })),\n )\n\nconst defaultFilename = (media: MediaSource): string =>\n `file.${media.mimeType?.split(\"/\")[1] ?? \"bin\"}`\n\nconst invalidMedia = (raw: unknown) =>\n new MessengerError.MessengerRequestFailed({\n provider: Api.provider,\n operation: \"post\",\n reason: \"invalid base64 media\",\n raw,\n })\n\n// ---------------------------------------------------------------------------\n// Service\n// ---------------------------------------------------------------------------\n\nexport const make = (\n cfg: Config,\n): Effect.Effect<\n TelegramService,\n MessengerError.MessengerConnectFailed,\n HttpClient.HttpClient | Scope.Scope\n> =>\n Effect.gen(function* () {\n // Captured once so the verbs need no `HttpClient` of their own.\n const client = yield* HttpClient.HttpClient\n const withClient = Effect.provideService(HttpClient.HttpClient, client)\n const call = (method: string, params?: Api.Params) => withClient(Api.call(cfg)(method, params))\n const upload = (method: string, params: Api.Params, file: Api.Upload) =>\n withClient(Api.upload(cfg)(method, params, file))\n\n const bot = yield* call(\"getMe\").pipe(\n Api.decoded(\"getMe\", Me),\n Effect.mapError(\n (e) =>\n new MessengerError.MessengerConnectFailed({\n provider: Api.provider,\n reason: reasonOf(e),\n raw: e.raw,\n }),\n ),\n )\n\n // -- inbound -----------------------------------------------------------\n\n const inbox = yield* Queue.unbounded<InboundEvent, MessengerError.MessengerError | Cause.Done>()\n const offset = yield* Ref.make(0)\n const toEvents = Events.toEvents(bot)\n\n // Answered before the event is offered, so the client's spinner never\n // waits on the recipe.\n const acknowledge = (update: Events.Update) =>\n Match.value(update).pipe(\n Match.when({ callback_query: Match.defined }, ({ callback_query }) =>\n call(\"answerCallbackQuery\", { callback_query_id: callback_query.id }).pipe(Effect.ignore),\n ),\n Match.orElse(() => Effect.void),\n )\n\n const poll = Effect.gen(function* () {\n const updates = yield* call(\"getUpdates\", {\n offset: yield* Ref.get(offset),\n timeout: Duration.toSeconds(cfg.pollTimeout ?? \"30 seconds\"),\n allowed_updates: Events.allowedUpdates,\n }).pipe(Api.decoded(\"getUpdates\", Updates))\n yield* Effect.forEach(updates, (update) =>\n Effect.gen(function* () {\n yield* Ref.set(offset, update.update_id + 1)\n yield* acknowledge(update)\n yield* Queue.offerAll(inbox, toEvents(update))\n }),\n )\n })\n\n // Transient failures (network, 5xx, 429) back off and poll again; fatal\n // ones end the stream with `MessengerTransportClosed`.\n yield* poll.pipe(\n Effect.retry({ while: (e) => !isFatalPoll(e), schedule: pollBackoff }),\n Effect.forever,\n Effect.catch((e) =>\n Queue.fail(\n inbox,\n new MessengerError.MessengerTransportClosed({\n provider: Api.provider,\n reason: reasonOf(e),\n raw: e.raw,\n }),\n ),\n ),\n Effect.ensuring(Queue.end(inbox)),\n Effect.forkScoped,\n )\n\n // -- outbound ----------------------------------------------------------\n\n // Telegram rejects markup it cannot parse; the same text then goes out plain.\n const sendText = (method: string, params: Api.Params) =>\n call(method, { ...params, ...parseParams(cfg) }).pipe(\n Effect.catchIf(describes(\"can't parse entities\"), () => call(method, params)),\n )\n\n const sendMedia = (\n at: ConversationRef,\n msg: Outbound,\n body: Extract<OutboundBody, { _tag: \"Media\" }>,\n ) => {\n const { method, field } = mediaRoute(body.media)\n const params = {\n ...target(at),\n ...replyParams(msg),\n ...(body.caption !== undefined && { caption: body.caption, ...parseParams(cfg) }),\n }\n const send = (bytes: Uint8Array, mimeType: string) =>\n upload(method, params, {\n field,\n bytes,\n filename: body.filename ?? defaultFilename(body.media),\n mimeType,\n })\n return Match.value(body.media).pipe(\n Match.tag(\"url\", ({ url }) => call(method, { ...params, [field]: url })),\n Match.tag(\"bytes\", ({ bytes, mimeType }) => send(bytes, mimeType)),\n Match.tag(\"base64\", ({ base64, mimeType }) =>\n Effect.fromResult(Encoding.decodeBase64(base64)).pipe(\n Effect.mapError(invalidMedia),\n Effect.flatMap((bytes) => send(bytes, mimeType)),\n ),\n ),\n Match.exhaustive,\n )\n }\n\n const sendRaw = (payload: unknown) =>\n Effect.gen(function* () {\n const { method, params } = yield* Schema.decodeUnknownEffect(RawCall)(payload).pipe(\n Effect.mapError(\n () =>\n new MessengerError.MessengerRequestFailed({\n provider: Api.provider,\n operation: \"post\",\n reason: \"raw payload must be { method, params? }\",\n raw: payload,\n }),\n ),\n )\n return yield* call(method, params)\n })\n\n // The reply lands on the first chunk; the id is the last one's.\n const sendChunks = (at: ConversationRef, msg: Outbound, text: string) =>\n Effect.gen(function* () {\n const [first, ...rest] = splitForLimit(text, limits.maxText)\n const send = (chunk: string, extra: Api.Params) =>\n sendText(\"sendMessage\", { ...target(at), ...extra, text: chunk }).pipe(\n Api.decoded(\"sendMessage\", Sent),\n )\n const head = yield* send(first, replyParams(msg))\n const tail = yield* Effect.forEach(rest, (chunk) => send(chunk, {}))\n return Option.getOrElse(Arr.last(tail), () => head)\n })\n\n const post: MessengerService[\"post\"] = (msg) =>\n Effect.gen(function* () {\n const at = yield* CurrentConversation\n const sent = yield* Match.value(msg.body).pipe(\n Match.tag(\"Text\", ({ text }) => sendChunks(at, msg, text)),\n Match.tag(\"Media\", (body) =>\n sendMedia(at, msg, body).pipe(Api.decoded(\"sendMedia\", Sent)),\n ),\n Match.tag(\"Raw\", ({ payload }) => sendRaw(payload).pipe(Api.decoded(\"raw\", Sent))),\n Match.exhaustive,\n )\n return MessageId(String(sent.message_id))\n }).pipe(Effect.mapError(requestFailed(\"post\")))\n\n const edit: MessengerService[\"edit\"] = (ref, next) =>\n Match.value(next.body).pipe(\n Match.tag(\"Text\", ({ text }) =>\n sendText(\"editMessageText\", {\n chat_id: ref.conversation.channel,\n message_id: Number(ref.id),\n text,\n }).pipe(Effect.asVoid, Effect.mapError(requestFailed(\"edit\"))),\n ),\n Match.tag(\"Raw\", ({ payload }) =>\n sendRaw(payload).pipe(Effect.asVoid, Effect.mapError(requestFailed(\"edit\"))),\n ),\n Match.tag(\"Media\", () =>\n Effect.fail(unsupported(\"media edits\", \"send a new message instead\")),\n ),\n Match.exhaustive,\n )\n\n // Telegram allows a fixed emoji set; anything else is REACTION_INVALID.\n const react: MessengerService[\"react\"] = (ref, emoji) =>\n call(\"setMessageReaction\", {\n chat_id: ref.conversation.channel,\n message_id: Number(ref.id),\n reaction: [{ type: \"emoji\", emoji }],\n }).pipe(\n Effect.asVoid,\n Effect.catchIf(describes(\"REACTION_INVALID\"), () =>\n Effect.fail(unsupported(\"reaction\", `${emoji} is not in Telegram's reaction set`)),\n ),\n Effect.mapError(requestFailed(\"react\")),\n )\n\n // The indicator lasts about five seconds, so it is re-sent every four\n // until the scope closes. The first send is awaited so a bad chat fails here.\n const typing: MessengerService[\"typing\"] = Effect.gen(function* () {\n const at = yield* CurrentConversation\n const once = call(\"sendChatAction\", { ...target(at), action: \"typing\" })\n yield* once.pipe(Effect.mapError(requestFailed(\"typing\")))\n yield* once.pipe(\n Effect.ignore,\n Effect.schedule(Schedule.spaced(\"4 seconds\")),\n Effect.forkScoped,\n )\n })\n\n return {\n bot,\n events: Stream.fromQueue(inbox),\n post,\n edit,\n react,\n typing,\n stream: streamViaEdits({ post, edit, limits }, cfg.stream),\n limits,\n }\n })\n\n// ---------------------------------------------------------------------------\n// Layer\n// ---------------------------------------------------------------------------\n\n/**\n * One poller, registered under both the `Telegram` and `Messenger` tags. The\n * long-poll loop starts when the layer is built and stops when its scope\n * closes; `events` ends with it. Telegram allows one poller per token.\n */\nexport const layer = (\n cfg: Config,\n): Layer.Layer<\n Telegram | Messenger,\n MessengerError.MessengerConnectFailed,\n HttpClient.HttpClient\n> =>\n Layer.effectContext(\n Effect.map(make(cfg), (service) =>\n Context.make(Telegram, service).pipe(Context.add(Messenger, service)),\n ),\n )\n"],"mappings":"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"}
@@ -0,0 +1,42 @@
1
+ import { Effect, Redacted, Schema } from "effect";
2
+ import { HttpClient } from "effect/unstable/http";
3
+ import * as MessengerError from "@effect-uai/core/MessengerError";
4
+ //#region src/internal/api.d.ts
5
+ declare const provider = "telegram";
6
+ type Config = {
7
+ readonly token: Redacted.Redacted;
8
+ /** Defaults to `https://api.telegram.org`. */
9
+ readonly baseUrl?: string;
10
+ };
11
+ declare const ApiFailure_base: new <A extends Record<string, any> = {}>(args: import("effect/Types").VoidIfEmpty<{ readonly [P in keyof A as P extends "_tag" ? never : P]: A[P]; }>) => import("effect/Cause").YieldableError & {
12
+ readonly _tag: "TelegramApiFailure";
13
+ } & Readonly<A>;
14
+ /**
15
+ * A Bot API call that came back `ok: false` or never came back at all.
16
+ * `code` mirrors the HTTP status Telegram puts in `error_code`; each verb
17
+ * maps this onto its own `MessengerError`.
18
+ */
19
+ declare class ApiFailure extends ApiFailure_base<{
20
+ readonly method: string;
21
+ readonly code?: number;
22
+ readonly description: string;
23
+ readonly raw: unknown;
24
+ }> {}
25
+ type ApiError = ApiFailure | MessengerError.MessengerRateLimited;
26
+ type Params = Readonly<Record<string, unknown>>;
27
+ /** The file part of a multipart upload. */
28
+ type Upload = {
29
+ readonly field: string;
30
+ readonly bytes: Uint8Array;
31
+ readonly filename: string;
32
+ readonly mimeType: string;
33
+ };
34
+ /** One JSON-bodied method call. `result` comes back undecoded. */
35
+ declare const call: (cfg: Config) => (method: string, params?: Params) => Effect.Effect<unknown, ApiError, HttpClient.HttpClient>;
36
+ /** One multipart method call: string fields plus a single file part. */
37
+ declare const upload: (cfg: Config) => (method: string, params: Params, file: Upload) => Effect.Effect<unknown, ApiError, HttpClient.HttpClient>;
38
+ /** Decode a call's `result`; a shape mismatch is a malformed response, not a defect. */
39
+ declare const decoded: <A>(method: string, schema: Schema.Decoder<A>) => <E, R>(result: Effect.Effect<unknown, E, R>) => Effect.Effect<A, E | ApiFailure, R>;
40
+ //#endregion
41
+ export { Upload as a, provider as c, Params as i, upload as l, ApiFailure as n, call as o, Config as r, decoded as s, ApiError as t };
42
+ //# sourceMappingURL=api-CnvvfWKq.d.mts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"api-CnvvfWKq.d.mts","names":[],"sources":["../src/internal/api.ts"],"mappings":";;;;cAKa;KAED;WACD,OAAO,SAAS;;WAEhB;;;;;;;;;;cAQE,mBAAmB;WACrB;WACA;WACA;WACA;;KAGC,WAAW,aAAa,eAAe;KAUvC,SAAS,SAAS;;KAGlB;WACD;WACA,OAAO;WACP;WACA;;;cAwCE,OAAI,KACT,YAAM,gBACG,SAAU,WAAc,OAAO,gBAAgB,UAAU,WAAW;;cAyBxE,SAAM,KACX,YAAM,gBAEI,QACN,QAAM,MACR,WACL,OAAO,gBAAgB,UAAU,WAAW;;cASpC,UACV,GAAC,gBAAgB,QAAU,OAAO,QAAQ,QAC1C,GAAG,GAAC,QAAU,OAAO,gBAAgB,GAAG,OAAK,OAAO,OAAO,GAAG,IAAI,YAAY"}
@@ -0,0 +1,221 @@
1
+ import { Schema } from "effect";
2
+ import { InboundEvent } from "@effect-uai/core/Messenger";
3
+ //#region src/internal/events.d.ts
4
+ declare const User: Schema.Struct<{
5
+ readonly id: Schema.Number;
6
+ readonly is_bot: Schema.Boolean;
7
+ readonly username: Schema.optional<Schema.String>;
8
+ }>;
9
+ type User = typeof User.Type;
10
+ declare const Chat: Schema.Struct<{
11
+ readonly id: Schema.Number;
12
+ readonly type: Schema.String;
13
+ }>;
14
+ type Chat = typeof Chat.Type;
15
+ declare const Entity: Schema.Struct<{
16
+ readonly type: Schema.String;
17
+ readonly offset: Schema.Number;
18
+ readonly length: Schema.Number;
19
+ }>;
20
+ type Entity = typeof Entity.Type;
21
+ /** Where a message sits: its chat, and its forum topic when it is in one. */
22
+ declare const Placement: Schema.Struct<{
23
+ readonly chat: Schema.Struct<{
24
+ readonly id: Schema.Number;
25
+ readonly type: Schema.String;
26
+ }>;
27
+ readonly message_thread_id: Schema.optional<Schema.Number>;
28
+ readonly is_topic_message: Schema.optional<Schema.Boolean>;
29
+ }>;
30
+ type Placement = typeof Placement.Type;
31
+ declare const ReplyTarget: Schema.Struct<{
32
+ readonly message_id: Schema.Number;
33
+ readonly from: Schema.optional<Schema.Struct<{
34
+ readonly id: Schema.Number;
35
+ readonly is_bot: Schema.Boolean;
36
+ readonly username: Schema.optional<Schema.String>;
37
+ }>>;
38
+ }>;
39
+ type ReplyTarget = typeof ReplyTarget.Type;
40
+ declare const Message: Schema.Struct<{
41
+ readonly chat: Schema.Struct<{
42
+ readonly id: Schema.Number;
43
+ readonly type: Schema.String;
44
+ }>;
45
+ readonly message_thread_id: Schema.optional<Schema.Number>;
46
+ readonly is_topic_message: Schema.optional<Schema.Boolean>;
47
+ readonly message_id: Schema.Number;
48
+ readonly from: Schema.optional<Schema.Struct<{
49
+ readonly id: Schema.Number;
50
+ readonly is_bot: Schema.Boolean;
51
+ readonly username: Schema.optional<Schema.String>;
52
+ }>>;
53
+ readonly text: Schema.optional<Schema.String>;
54
+ readonly caption: Schema.optional<Schema.String>;
55
+ readonly entities: Schema.optional<Schema.$Array<Schema.Struct<{
56
+ readonly type: Schema.String;
57
+ readonly offset: Schema.Number;
58
+ readonly length: Schema.Number;
59
+ }>>>;
60
+ readonly reply_to_message: Schema.optional<Schema.Struct<{
61
+ readonly message_id: Schema.Number;
62
+ readonly from: Schema.optional<Schema.Struct<{
63
+ readonly id: Schema.Number;
64
+ readonly is_bot: Schema.Boolean;
65
+ readonly username: Schema.optional<Schema.String>;
66
+ }>>;
67
+ }>>;
68
+ }>;
69
+ type Message = typeof Message.Type;
70
+ declare const Reaction: Schema.Struct<{
71
+ readonly type: Schema.String;
72
+ readonly emoji: Schema.optional<Schema.String>;
73
+ }>;
74
+ type Reaction = typeof Reaction.Type;
75
+ declare const MessageReaction: Schema.Struct<{
76
+ readonly chat: Schema.Struct<{
77
+ readonly id: Schema.Number;
78
+ readonly type: Schema.String;
79
+ }>;
80
+ readonly message_id: Schema.Number;
81
+ readonly user: Schema.optional<Schema.Struct<{
82
+ readonly id: Schema.Number;
83
+ readonly is_bot: Schema.Boolean;
84
+ readonly username: Schema.optional<Schema.String>;
85
+ }>>;
86
+ readonly new_reaction: Schema.$Array<Schema.Struct<{
87
+ readonly type: Schema.String;
88
+ readonly emoji: Schema.optional<Schema.String>;
89
+ }>>;
90
+ }>;
91
+ type MessageReaction = typeof MessageReaction.Type;
92
+ declare const CallbackQuery: Schema.Struct<{
93
+ readonly id: Schema.String;
94
+ readonly from: Schema.Struct<{
95
+ readonly id: Schema.Number;
96
+ readonly is_bot: Schema.Boolean;
97
+ readonly username: Schema.optional<Schema.String>;
98
+ }>;
99
+ readonly data: Schema.optional<Schema.String>;
100
+ readonly message: Schema.optional<Schema.Struct<{
101
+ readonly chat: Schema.Struct<{
102
+ readonly id: Schema.Number;
103
+ readonly type: Schema.String;
104
+ }>;
105
+ readonly message_thread_id: Schema.optional<Schema.Number>;
106
+ readonly is_topic_message: Schema.optional<Schema.Boolean>;
107
+ readonly message_id: Schema.Number;
108
+ }>>;
109
+ }>;
110
+ type CallbackQuery = typeof CallbackQuery.Type;
111
+ declare const Update: Schema.Struct<{
112
+ readonly update_id: Schema.Number;
113
+ readonly message: Schema.optional<Schema.Struct<{
114
+ readonly chat: Schema.Struct<{
115
+ readonly id: Schema.Number;
116
+ readonly type: Schema.String;
117
+ }>;
118
+ readonly message_thread_id: Schema.optional<Schema.Number>;
119
+ readonly is_topic_message: Schema.optional<Schema.Boolean>;
120
+ readonly message_id: Schema.Number;
121
+ readonly from: Schema.optional<Schema.Struct<{
122
+ readonly id: Schema.Number;
123
+ readonly is_bot: Schema.Boolean;
124
+ readonly username: Schema.optional<Schema.String>;
125
+ }>>;
126
+ readonly text: Schema.optional<Schema.String>;
127
+ readonly caption: Schema.optional<Schema.String>;
128
+ readonly entities: Schema.optional<Schema.$Array<Schema.Struct<{
129
+ readonly type: Schema.String;
130
+ readonly offset: Schema.Number;
131
+ readonly length: Schema.Number;
132
+ }>>>;
133
+ readonly reply_to_message: Schema.optional<Schema.Struct<{
134
+ readonly message_id: Schema.Number;
135
+ readonly from: Schema.optional<Schema.Struct<{
136
+ readonly id: Schema.Number;
137
+ readonly is_bot: Schema.Boolean;
138
+ readonly username: Schema.optional<Schema.String>;
139
+ }>>;
140
+ }>>;
141
+ }>>;
142
+ readonly edited_message: Schema.optional<Schema.Struct<{
143
+ readonly chat: Schema.Struct<{
144
+ readonly id: Schema.Number;
145
+ readonly type: Schema.String;
146
+ }>;
147
+ readonly message_thread_id: Schema.optional<Schema.Number>;
148
+ readonly is_topic_message: Schema.optional<Schema.Boolean>;
149
+ readonly message_id: Schema.Number;
150
+ readonly from: Schema.optional<Schema.Struct<{
151
+ readonly id: Schema.Number;
152
+ readonly is_bot: Schema.Boolean;
153
+ readonly username: Schema.optional<Schema.String>;
154
+ }>>;
155
+ readonly text: Schema.optional<Schema.String>;
156
+ readonly caption: Schema.optional<Schema.String>;
157
+ readonly entities: Schema.optional<Schema.$Array<Schema.Struct<{
158
+ readonly type: Schema.String;
159
+ readonly offset: Schema.Number;
160
+ readonly length: Schema.Number;
161
+ }>>>;
162
+ readonly reply_to_message: Schema.optional<Schema.Struct<{
163
+ readonly message_id: Schema.Number;
164
+ readonly from: Schema.optional<Schema.Struct<{
165
+ readonly id: Schema.Number;
166
+ readonly is_bot: Schema.Boolean;
167
+ readonly username: Schema.optional<Schema.String>;
168
+ }>>;
169
+ }>>;
170
+ }>>;
171
+ readonly message_reaction: Schema.optional<Schema.Struct<{
172
+ readonly chat: Schema.Struct<{
173
+ readonly id: Schema.Number;
174
+ readonly type: Schema.String;
175
+ }>;
176
+ readonly message_id: Schema.Number;
177
+ readonly user: Schema.optional<Schema.Struct<{
178
+ readonly id: Schema.Number;
179
+ readonly is_bot: Schema.Boolean;
180
+ readonly username: Schema.optional<Schema.String>;
181
+ }>>;
182
+ readonly new_reaction: Schema.$Array<Schema.Struct<{
183
+ readonly type: Schema.String;
184
+ readonly emoji: Schema.optional<Schema.String>;
185
+ }>>;
186
+ }>>;
187
+ readonly callback_query: Schema.optional<Schema.Struct<{
188
+ readonly id: Schema.String;
189
+ readonly from: Schema.Struct<{
190
+ readonly id: Schema.Number;
191
+ readonly is_bot: Schema.Boolean;
192
+ readonly username: Schema.optional<Schema.String>;
193
+ }>;
194
+ readonly data: Schema.optional<Schema.String>;
195
+ readonly message: Schema.optional<Schema.Struct<{
196
+ readonly chat: Schema.Struct<{
197
+ readonly id: Schema.Number;
198
+ readonly type: Schema.String;
199
+ }>;
200
+ readonly message_thread_id: Schema.optional<Schema.Number>;
201
+ readonly is_topic_message: Schema.optional<Schema.Boolean>;
202
+ readonly message_id: Schema.Number;
203
+ }>>;
204
+ }>>;
205
+ }>;
206
+ type Update = typeof Update.Type;
207
+ /** Requested on every `getUpdates`; without it Telegram never delivers reactions. */
208
+ declare const allowedUpdates: readonly ["message", "edited_message", "callback_query", "message_reaction"];
209
+ type BotIdentity = {
210
+ readonly id: number;
211
+ readonly username: string;
212
+ };
213
+ /**
214
+ * Everything one `Update` says to the agent. Edited messages are received
215
+ * (so the offset advances past them) but not re-delivered: a turn already
216
+ * answered them.
217
+ */
218
+ declare const toEvents: (bot: BotIdentity) => (update: Update) => ReadonlyArray<InboundEvent>;
219
+ //#endregion
220
+ export { Message as a, Reaction as c, User as d, allowedUpdates as f, Entity as i, ReplyTarget as l, CallbackQuery as n, MessageReaction as o, toEvents as p, Chat as r, Placement as s, BotIdentity as t, Update as u };
221
+ //# sourceMappingURL=events-BkljYfG6.d.mts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"events-BkljYfG6.d.mts","names":[],"sources":["../src/internal/events.ts"],"mappings":";;;cAca,MAAI,OAAA;;;;;KAKL,cAAc,KAAK;cAElB,MAAI,OAAA;;;;KAIL,cAAc,KAAK;cAElB,QAAM,OAAA;;;;;KAKP,gBAAgB,OAAO;;cAGtB,WAAS,OAAA;;;;;;;;KAKV,mBAAmB,UAAU;cAE5B,aAAW,OAAA;;;;;;;;KAIZ,qBAAqB,YAAY;cAEhC,SAAO,OAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;KASR,iBAAiB,QAAQ;cAExB,UAAQ,OAAA;;;;KAIT,kBAAkB,SAAS;cAE1B,iBAAe,OAAA;;;;;;;;;;;;;;;;KAMhB,yBAAyB,gBAAgB;cAExC,eAAa,OAAA;;;;;;;;;;;;;;;;;;KAMd,uBAAuB,cAAc;cAEpC,QAAM,OAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;KAOP,gBAAgB,OAAO;;cAGtB;KAOD;WACD;WACA;;;;;;;cAqHE,WAAQ,KACb,iBAAW,QACR,WAAS,cAAc"}
@@ -0,0 +1,2 @@
1
+ import { t as Telegram_d_exports } from "./Telegram.mjs";
2
+ export { Telegram_d_exports as Telegram };
package/dist/index.mjs ADDED
@@ -0,0 +1,2 @@
1
+ import { t as Telegram_exports } from "./Telegram.mjs";
2
+ export { Telegram_exports as Telegram };
@@ -0,0 +1,2 @@
1
+ import { a as Upload, c as provider, i as Params, l as upload, n as ApiFailure, o as call, r as Config, s as decoded, t as ApiError } from "../api-CnvvfWKq.mjs";
2
+ export { ApiError, ApiFailure, Config, Params, Upload, call, decoded, provider, upload };