@effect-uai/slack 0.16.0 → 0.18.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/README.md +1 -1
- package/dist/Slack.d.mts +3 -3
- package/dist/Slack.d.mts.map +1 -1
- package/dist/Slack.mjs +7 -5
- package/dist/Slack.mjs.map +1 -1
- package/dist/{api-BBgliKo7.d.mts → api-CbBW1iF0.d.mts} +2 -2
- package/dist/{api-BBgliKo7.d.mts.map → api-CbBW1iF0.d.mts.map} +1 -1
- package/dist/dist-DHer1cql.mjs +1533 -0
- package/dist/dist-DHer1cql.mjs.map +1 -0
- package/dist/{test.DNmyFkvJ-N3QqMRwZ.mjs → index.C4tKZ0yW-CUBm3Bmz.mjs} +9446 -5818
- package/dist/index.C4tKZ0yW-CUBm3Bmz.mjs.map +1 -0
- package/dist/internal/api.d.mts +1 -1
- package/dist/internal/api.mjs +1 -1
- package/dist/internal/api.mjs.map +1 -1
- package/dist/internal/events.test.mjs +1 -1
- package/dist/internal/socket.d.mts +1 -1
- package/dist/internal/socket.mjs +10 -7
- package/dist/internal/socket.mjs.map +1 -1
- package/dist/internal/socket.test.mjs +252 -6
- package/dist/internal/socket.test.mjs.map +1 -1
- package/dist/{rolldown-runtime-D7D4PA-g.mjs → rolldown-runtime-EAsCxrDo.mjs} +2 -1
- package/dist/{socket-DJgdt_ka.d.mts → socket-qV7yc2q7.d.mts} +3 -2
- package/dist/socket-qV7yc2q7.d.mts.map +1 -0
- package/package.json +4 -4
- package/src/Slack.ts +8 -4
- package/src/internal/api.ts +1 -6
- package/src/internal/socket.test.ts +107 -2
- package/src/internal/socket.ts +23 -14
- package/dist/magic-string.es-CUAnjKYC.mjs +0 -1014
- package/dist/magic-string.es-CUAnjKYC.mjs.map +0 -1
- package/dist/socket-DJgdt_ka.d.mts.map +0 -1
- package/dist/test.DNmyFkvJ-N3QqMRwZ.mjs.map +0 -1
package/README.md
CHANGED
|
@@ -13,7 +13,7 @@ websocket, so no request URL, and no Bolt.
|
|
|
13
13
|
pnpm add @effect-uai/slack @effect-uai/core effect
|
|
14
14
|
```
|
|
15
15
|
|
|
16
|
-
ESM-only. Requires `effect
|
|
16
|
+
ESM-only. Requires `effect@>=4.0.1` and `@effect-uai/core` as peers.
|
|
17
17
|
|
|
18
18
|
## Usage
|
|
19
19
|
|
package/dist/Slack.d.mts
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
|
-
import { r as Config$1 } from "./api-
|
|
1
|
+
import { r as Config$1 } from "./api-CbBW1iF0.mjs";
|
|
2
2
|
import { m as ReplyIn, r as BotIdentity } from "./events-Cj5RRjK1.mjs";
|
|
3
|
-
import { n as DisconnectAction, s as classifyDisconnect } from "./socket-
|
|
3
|
+
import { n as DisconnectAction, s as classifyDisconnect } from "./socket-qV7yc2q7.mjs";
|
|
4
4
|
import { Context, Effect, Layer, Scope } from "effect";
|
|
5
|
-
import { HttpClient } from "effect/
|
|
5
|
+
import { HttpClient } from "effect/http";
|
|
6
6
|
import { Messenger, MessengerLimits, MessengerService } from "@effect-uai/core/Messenger";
|
|
7
7
|
import { StreamViaEditsOptions } from "@effect-uai/core/MessengerAdapter";
|
|
8
8
|
import * as MessengerError from "@effect-uai/core/MessengerError";
|
package/dist/Slack.d.mts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"Slack.d.mts","names":[],"sources":["../src/Slack.ts"],"mappings":";;;;;;;;;;;
|
|
1
|
+
{"version":3,"file":"Slack.d.mts","names":[],"sources":["../src/Slack.ts"],"mappings":";;;;;;;;;;;KA6CY,SAAS;;;;;WAKV,UAAU;;WAEV,SAAS;;KAGR,eAAe;;WAEhB,KAAK;;;;;;;cAOH,cAAc;;cAKd,QAAQ;cA8GR,OAAI,KACV,WACJ,OAAO,OACR,cACA,eAAe,wBACf,WAAW,aAAa,MAAM;;;;;;;cAuOnB,QAAK,KACX,WACJ,MAAM,MAAM,QAAQ,WAAW,eAAe,wBAAwB,WAAW"}
|
package/dist/Slack.mjs
CHANGED
|
@@ -1,9 +1,11 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { n as __exportAll } from "./rolldown-runtime-EAsCxrDo.mjs";
|
|
2
2
|
import { call, callAsApp, decoded, provider, query, upload } from "./internal/api.mjs";
|
|
3
3
|
import { toEvents } from "./internal/events.mjs";
|
|
4
4
|
import { classifyDisconnect, connect } from "./internal/socket.mjs";
|
|
5
|
-
import { Array, Context, Effect,
|
|
6
|
-
import {
|
|
5
|
+
import { Array, Context, Effect, Layer, Match, Option, Queue, Schema, Stream } from "effect";
|
|
6
|
+
import { Base64 } from "effect/encoding";
|
|
7
|
+
import { HttpClient } from "effect/http";
|
|
8
|
+
import * as Socket from "effect/socket/Socket";
|
|
7
9
|
import { CurrentConversation, MessageId, Messenger } from "@effect-uai/core/Messenger";
|
|
8
10
|
import { splitForLimit, streamViaEdits } from "@effect-uai/core/MessengerAdapter";
|
|
9
11
|
import * as MessengerError from "@effect-uai/core/MessengerError";
|
|
@@ -103,7 +105,7 @@ const make = (cfg) => Effect.gen(function* () {
|
|
|
103
105
|
reason: reasonOf(e),
|
|
104
106
|
raw: isApiFailure(e) ? e.raw : e
|
|
105
107
|
})));
|
|
106
|
-
const session = yield* connect({ open });
|
|
108
|
+
const session = yield* connect({ open }).pipe(Effect.provide(Socket.layerWebSocketConstructorGlobal));
|
|
107
109
|
const toEvents$1 = toEvents({
|
|
108
110
|
bot,
|
|
109
111
|
replyIn: cfg.replyIn ?? "thread"
|
|
@@ -145,7 +147,7 @@ const make = (cfg) => Effect.gen(function* () {
|
|
|
145
147
|
}).pipe(decoded("files.completeUploadExternal", Completed));
|
|
146
148
|
return Option.getOrElse(shareTs(uploaded), () => reserved.file_id);
|
|
147
149
|
});
|
|
148
|
-
const sendMedia = (at, msg, body) => Match.value(body.media).pipe(Match.tag("url", ({ url }) => sendUrl(at, msg, body, url)), Match.tag("bytes", ({ bytes, mimeType }) => sendBytes(at, msg, body, bytes, mimeType)), Match.tag("base64", ({ base64, mimeType }) => Effect.fromResult(
|
|
150
|
+
const sendMedia = (at, msg, body) => Match.value(body.media).pipe(Match.tag("url", ({ url }) => sendUrl(at, msg, body, url)), Match.tag("bytes", ({ bytes, mimeType }) => sendBytes(at, msg, body, bytes, mimeType)), Match.tag("base64", ({ base64, mimeType }) => Effect.fromResult(Base64.decode(base64)).pipe(Effect.mapError(invalidMedia), Effect.flatMap((bytes) => sendBytes(at, msg, body, bytes, mimeType)))), Match.exhaustive);
|
|
149
151
|
const sendChunks = (at, msg, body) => Effect.gen(function* () {
|
|
150
152
|
const [first, ...rest] = splitForLimit(body, limits.maxText);
|
|
151
153
|
const send = (chunk) => call$1("chat.postMessage", {
|
package/dist/Slack.mjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"Slack.mjs","names":["Arr","Api.provider","call","Api.call","query","Api.query","Api.decoded","Api.callAsApp","SocketMode.connect","toEvents","Events.toEvents","Api.upload"],"sources":["../src/Slack.ts"],"sourcesContent":["import {\n Array as Arr,\n type Cause,\n Context,\n Effect,\n Encoding,\n Layer,\n Match,\n Option,\n Queue,\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\"\nimport * as SocketMode from \"./internal/socket.js\"\n\nexport type { BotIdentity, ReplyIn } from \"./internal/events.js\"\nexport { classifyDisconnect, type DisconnectAction } from \"./internal/socket.js\"\n\n// ---------------------------------------------------------------------------\n// Public types\n// ---------------------------------------------------------------------------\n\nexport type Config = Api.Config & {\n /**\n * Where a top-level mention is answered. Default `\"thread\"`, which keeps a\n * conversation out of the channel and lets follow-ups inside it map back.\n */\n readonly replyIn?: Events.ReplyIn\n /** Tuning for `stream`, which is post-then-edit on Slack. */\n readonly stream?: StreamViaEditsOptions\n}\n\nexport type SlackService = MessengerService & {\n /** From `auth.test` 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 Slack extends Context.Service<Slack, SlackService>()(\n \"@betalyra/effect-uai/providers/slack/Slack\",\n) {}\n\n/** `markdown_text` allows far more, but a chat message past this is unreadable. */\nexport const limits: MessengerLimits = { maxText: 4000, maxCaption: 4000 }\n\n// `chat.update` is Tier 3, so an edit a second is comfortably inside it.\nconst defaultStream: StreamViaEditsOptions = { every: \"1200 millis\" }\n\n// ---------------------------------------------------------------------------\n// Wire results\n// ---------------------------------------------------------------------------\n\nconst AuthTest = Schema.Struct({\n user_id: Schema.String,\n bot_id: Schema.String,\n team_id: Schema.String,\n})\nconst Opened = Schema.Struct({ url: Schema.String })\nconst Posted = Schema.Struct({ ts: Schema.String })\nconst UploadUrl = Schema.Struct({ upload_url: Schema.String, file_id: Schema.String })\n\n// `shares` nests visibility, then channel, then the messages the file appears in.\nconst Shares = Schema.Record(\n Schema.String,\n Schema.Record(Schema.String, Schema.Array(Schema.Struct({ ts: Schema.optional(Schema.String) }))),\n)\nconst Completed = Schema.Struct({\n files: Schema.Array(Schema.Struct({ id: Schema.String, shares: Schema.optional(Shares) })),\n})\ntype Completed = typeof Completed.Type\n\nconst RawCall = Schema.Struct({\n method: Schema.String,\n params: Schema.optional(Schema.Record(Schema.String, Schema.Unknown)),\n})\n\n/** The message a file was shared as, when Slack reports one. */\nconst shareTs = (uploaded: Completed): Option.Option<string> =>\n Arr.head(uploaded.files).pipe(\n Option.flatMap((file) =>\n Arr.findFirst(\n Object.values(file.shares ?? {}).flatMap((byChannel) => Object.values(byChannel).flat()),\n (share) => share.ts !== undefined,\n ),\n ),\n Option.flatMap((share) => Option.fromNullishOr(share.ts)),\n )\n\n// ---------------------------------------------------------------------------\n// Errors\n// ---------------------------------------------------------------------------\n\nconst isApiFailure = (e: Api.ApiError): e is Api.ApiFailure => e._tag === \"SlackApiFailure\"\n\nconst fails = (needle: string) => (e: Api.ApiError) => isApiFailure(e) && e.error === needle\n\nconst reasonOf = (e: Api.ApiError): string => (isApiFailure(e) ? e.error : \"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 \"SlackApiFailure\",\n (failure) =>\n new MessengerError.MessengerRequestFailed({\n provider: Api.provider,\n operation,\n reason: failure.error,\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// ---------------------------------------------------------------------------\n// Outbound params\n// ---------------------------------------------------------------------------\n\ntype MediaFields = Extract<OutboundBody, { _tag: \"Media\" }>\n\n/**\n * Slack's \"reply\" is posting in the thread, so a `replyTo` outside one opens a\n * thread on that message and inside one is already named by the conversation.\n */\nconst target = (at: ConversationRef, msg: Outbound): Api.Params => {\n const thread = at.thread ?? msg.replyTo\n return { channel: at.channel, ...(thread !== undefined && { thread_ts: thread }) }\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// `:eyes:` and `eyes` name the same reaction; Slack wants it bare. Only the\n// outer colons go, so a skin tone modifier survives.\nconst shortcode = (emoji: string): string => emoji.replace(/^:|:$/g, \"\")\n\n// ---------------------------------------------------------------------------\n// Service\n// ---------------------------------------------------------------------------\n\nexport const make = (\n cfg: Config,\n): Effect.Effect<\n SlackService,\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 query = (method: string, params: Api.Params) => withClient(Api.query(cfg)(method, params))\n\n const connectFailed = (e: Api.ApiError) =>\n new MessengerError.MessengerConnectFailed({\n provider: Api.provider,\n reason: reasonOf(e),\n raw: isApiFailure(e) ? e.raw : e,\n })\n\n // A bad bot token fails here rather than as a socket close a moment later.\n const identity = yield* call(\"auth.test\").pipe(\n Api.decoded(\"auth.test\", AuthTest),\n Effect.mapError(connectFailed),\n )\n const bot: Events.BotIdentity = {\n userId: identity.user_id,\n botId: identity.bot_id,\n teamId: identity.team_id,\n }\n\n // -- inbound -----------------------------------------------------------\n\n // Each connection needs its own single-use URL, so this runs again on\n // every reconnect. The app token is used nowhere else.\n const open = withClient(Api.callAsApp(cfg)(\"apps.connections.open\")).pipe(\n Api.decoded(\"apps.connections.open\", Opened),\n Effect.map(({ url }) => url),\n Effect.mapError(\n (e): MessengerError.MessengerError =>\n new MessengerError.MessengerConnectFailed({\n provider: Api.provider,\n reason: reasonOf(e),\n raw: isApiFailure(e) ? e.raw : e,\n }),\n ),\n )\n\n const session = yield* SocketMode.connect({ open })\n const toEvents = Events.toEvents({ bot, replyIn: cfg.replyIn ?? \"thread\" })\n const inbox = yield* Queue.unbounded<InboundEvent, MessengerError.MessengerError | Cause.Done>()\n\n yield* Stream.fromQueue(session.envelopes).pipe(\n Stream.runForEach(({ envelope, raw }) => Queue.offerAll(inbox, toEvents(envelope, raw))),\n Effect.catch((closed) => Queue.fail(inbox, closed)),\n Effect.ensuring(Queue.end(inbox)),\n Effect.forkScoped,\n )\n\n // -- outbound ----------------------------------------------------------\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 // A URL is not a Slack upload, so it goes as the link and Slack unfurls it.\n const sendUrl = (at: ConversationRef, msg: Outbound, body: MediaFields, url: string) =>\n call(\"chat.postMessage\", {\n ...target(at, msg),\n markdown_text: Arr.getSomes([Option.fromNullishOr(body.caption), Option.some(url)]).join(\n \"\\n\",\n ),\n }).pipe(\n Api.decoded(\"chat.postMessage\", Posted),\n Effect.map(({ ts }) => ts),\n )\n\n // Three steps: reserve a URL, put the bytes there, then share the file into\n // the conversation with the caption as its comment.\n const sendBytes = (\n at: ConversationRef,\n msg: Outbound,\n body: MediaFields,\n bytes: Uint8Array,\n mimeType: string,\n ) =>\n Effect.gen(function* () {\n const filename = body.filename ?? defaultFilename(body.media)\n const reserved = yield* query(\"files.getUploadURLExternal\", {\n filename,\n length: bytes.length,\n }).pipe(Api.decoded(\"files.getUploadURLExternal\", UploadUrl))\n yield* withClient(Api.upload(reserved.upload_url, { bytes, filename, mimeType }))\n const uploaded = yield* call(\"files.completeUploadExternal\", {\n files: [{ id: reserved.file_id, title: filename }],\n channel_id: at.channel,\n ...target(at, msg),\n ...(body.caption !== undefined && { initial_comment: body.caption }),\n }).pipe(Api.decoded(\"files.completeUploadExternal\", Completed))\n // A share reports the message it became; without one the file id is\n // the only handle there is, and a file message cannot be edited anyway.\n return Option.getOrElse(shareTs(uploaded), () => reserved.file_id)\n })\n\n const sendMedia = (at: ConversationRef, msg: Outbound, body: MediaFields) =>\n Match.value(body.media).pipe(\n Match.tag(\"url\", ({ url }) => sendUrl(at, msg, body, url)),\n Match.tag(\"bytes\", ({ bytes, mimeType }) => sendBytes(at, msg, body, bytes, mimeType)),\n Match.tag(\"base64\", ({ base64, mimeType }) =>\n Effect.fromResult(Encoding.decodeBase64(base64)).pipe(\n Effect.mapError(invalidMedia),\n Effect.flatMap((bytes) => sendBytes(at, msg, body, bytes, mimeType)),\n ),\n ),\n Match.exhaustive,\n )\n\n // Every chunk lands in the same thread, so only the id of the last matters.\n const sendChunks = (at: ConversationRef, msg: Outbound, body: string) =>\n Effect.gen(function* () {\n const [first, ...rest] = splitForLimit(body, limits.maxText)\n const send = (chunk: string) =>\n call(\"chat.postMessage\", { ...target(at, msg), markdown_text: chunk }).pipe(\n Api.decoded(\"chat.postMessage\", Posted),\n Effect.map(({ ts }) => ts),\n )\n const head = yield* send(first)\n const tail = yield* Effect.forEach(rest, send)\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 ts = yield* Match.value(msg.body).pipe(\n Match.tag(\"Text\", ({ text }) => sendChunks(at, msg, text)),\n Match.tag(\"Media\", (body) => sendMedia(at, msg, body)),\n Match.tag(\"Raw\", ({ payload }) =>\n sendRaw(payload).pipe(\n Api.decoded(\"raw\", Posted),\n Effect.map(({ ts }) => ts),\n ),\n ),\n Match.exhaustive,\n )\n return MessageId(ts)\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 call(\"chat.update\", {\n channel: ref.conversation.channel,\n ts: ref.id,\n markdown_text: 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 // The shortcode is the same spelling a `Reaction` event delivers.\n const react: MessengerService[\"react\"] = (ref, emoji) =>\n call(\"reactions.add\", {\n channel: ref.conversation.channel,\n timestamp: ref.id,\n name: shortcode(emoji),\n }).pipe(\n Effect.asVoid,\n Effect.catchIf(fails(\"invalid_name\"), () =>\n Effect.fail(unsupported(\"reaction\", `${emoji} is not an emoji this workspace has`)),\n ),\n Effect.mapError(requestFailed(\"react\")),\n )\n\n /**\n * Best effort: Slack shows a session status on its agent surfaces only, so\n * a rejection is logged and swallowed rather than taking a turn down.\n */\n const typing: MessengerService[\"typing\"] = Effect.gen(function* () {\n const at = yield* CurrentConversation\n const status = (state: string) =>\n call(\"agents.sessions.setStatus\", {\n channel_id: at.channel,\n ...(at.thread !== undefined && { thread_ts: at.thread }),\n status: state,\n }).pipe(\n Effect.asVoid,\n Effect.catch((e) => Effect.logDebug(\"slack status not set\", { reason: reasonOf(e) })),\n )\n yield* Effect.acquireRelease(status(\"processing\"), () => status(\"active\"))\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 }, { ...defaultStream, ...cfg.stream }),\n limits,\n }\n })\n\n// ---------------------------------------------------------------------------\n// Layer\n// ---------------------------------------------------------------------------\n\n/**\n * One Socket Mode connection, registered under both the `Slack` and\n * `Messenger` tags. Building the layer authenticates and waits for `hello`, so\n * a rejected token fails here; the connection lives until the scope closes and\n * `events` ends with it.\n */\nexport const layer = (\n cfg: Config,\n): Layer.Layer<Slack | Messenger, MessengerError.MessengerConnectFailed, HttpClient.HttpClient> =>\n Layer.effectContext(\n Effect.map(make(cfg), (service) =>\n Context.make(Slack, service).pipe(Context.add(Messenger, service)),\n ),\n )\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"Slack.mjs","names":["Arr","Api.provider","call","Api.call","query","Api.query","Api.decoded","Api.callAsApp","SocketMode.connect","toEvents","Events.toEvents","Api.upload"],"sources":["../src/Slack.ts"],"sourcesContent":["import {\n Array as Arr,\n type Cause,\n Context,\n Effect,\n Layer,\n Match,\n Option,\n Queue,\n Schema,\n type Scope,\n Stream,\n} from \"effect\"\nimport { Base64 } from \"effect/encoding\"\nimport { HttpClient } from \"effect/http\"\nimport * as Socket from \"effect/socket/Socket\"\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\"\nimport * as SocketMode from \"./internal/socket.js\"\n\nexport type { BotIdentity, ReplyIn } from \"./internal/events.js\"\nexport { classifyDisconnect, type DisconnectAction } from \"./internal/socket.js\"\n\n// ---------------------------------------------------------------------------\n// Public types\n// ---------------------------------------------------------------------------\n\nexport type Config = Api.Config & {\n /**\n * Where a top-level mention is answered. Default `\"thread\"`, which keeps a\n * conversation out of the channel and lets follow-ups inside it map back.\n */\n readonly replyIn?: Events.ReplyIn\n /** Tuning for `stream`, which is post-then-edit on Slack. */\n readonly stream?: StreamViaEditsOptions\n}\n\nexport type SlackService = MessengerService & {\n /** From `auth.test` 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 Slack extends Context.Service<Slack, SlackService>()(\n \"@betalyra/effect-uai/providers/slack/Slack\",\n) {}\n\n/** `markdown_text` allows far more, but a chat message past this is unreadable. */\nexport const limits: MessengerLimits = { maxText: 4000, maxCaption: 4000 }\n\n// `chat.update` is Tier 3, so an edit a second is comfortably inside it.\nconst defaultStream: StreamViaEditsOptions = { every: \"1200 millis\" }\n\n// ---------------------------------------------------------------------------\n// Wire results\n// ---------------------------------------------------------------------------\n\nconst AuthTest = Schema.Struct({\n user_id: Schema.String,\n bot_id: Schema.String,\n team_id: Schema.String,\n})\nconst Opened = Schema.Struct({ url: Schema.String })\nconst Posted = Schema.Struct({ ts: Schema.String })\nconst UploadUrl = Schema.Struct({ upload_url: Schema.String, file_id: Schema.String })\n\n// `shares` nests visibility, then channel, then the messages the file appears in.\nconst Shares = Schema.Record(\n Schema.String,\n Schema.Record(Schema.String, Schema.Array(Schema.Struct({ ts: Schema.optional(Schema.String) }))),\n)\nconst Completed = Schema.Struct({\n files: Schema.Array(Schema.Struct({ id: Schema.String, shares: Schema.optional(Shares) })),\n})\ntype Completed = typeof Completed.Type\n\nconst RawCall = Schema.Struct({\n method: Schema.String,\n params: Schema.optional(Schema.Record(Schema.String, Schema.Unknown)),\n})\n\n/** The message a file was shared as, when Slack reports one. */\nconst shareTs = (uploaded: Completed): Option.Option<string> =>\n Arr.head(uploaded.files).pipe(\n Option.flatMap((file) =>\n Arr.findFirst(\n Object.values(file.shares ?? {}).flatMap((byChannel) => Object.values(byChannel).flat()),\n (share) => share.ts !== undefined,\n ),\n ),\n Option.flatMap((share) => Option.fromNullishOr(share.ts)),\n )\n\n// ---------------------------------------------------------------------------\n// Errors\n// ---------------------------------------------------------------------------\n\nconst isApiFailure = (e: Api.ApiError): e is Api.ApiFailure => e._tag === \"SlackApiFailure\"\n\nconst fails = (needle: string) => (e: Api.ApiError) => isApiFailure(e) && e.error === needle\n\nconst reasonOf = (e: Api.ApiError): string => (isApiFailure(e) ? e.error : \"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 \"SlackApiFailure\",\n (failure) =>\n new MessengerError.MessengerRequestFailed({\n provider: Api.provider,\n operation,\n reason: failure.error,\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// ---------------------------------------------------------------------------\n// Outbound params\n// ---------------------------------------------------------------------------\n\ntype MediaFields = Extract<OutboundBody, { _tag: \"Media\" }>\n\n/**\n * Slack's \"reply\" is posting in the thread, so a `replyTo` outside one opens a\n * thread on that message and inside one is already named by the conversation.\n */\nconst target = (at: ConversationRef, msg: Outbound): Api.Params => {\n const thread = at.thread ?? msg.replyTo\n return { channel: at.channel, ...(thread !== undefined && { thread_ts: thread }) }\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// `:eyes:` and `eyes` name the same reaction; Slack wants it bare. Only the\n// outer colons go, so a skin tone modifier survives.\nconst shortcode = (emoji: string): string => emoji.replace(/^:|:$/g, \"\")\n\n// ---------------------------------------------------------------------------\n// Service\n// ---------------------------------------------------------------------------\n\nexport const make = (\n cfg: Config,\n): Effect.Effect<\n SlackService,\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 query = (method: string, params: Api.Params) => withClient(Api.query(cfg)(method, params))\n\n const connectFailed = (e: Api.ApiError) =>\n new MessengerError.MessengerConnectFailed({\n provider: Api.provider,\n reason: reasonOf(e),\n raw: isApiFailure(e) ? e.raw : e,\n })\n\n // A bad bot token fails here rather than as a socket close a moment later.\n const identity = yield* call(\"auth.test\").pipe(\n Api.decoded(\"auth.test\", AuthTest),\n Effect.mapError(connectFailed),\n )\n const bot: Events.BotIdentity = {\n userId: identity.user_id,\n botId: identity.bot_id,\n teamId: identity.team_id,\n }\n\n // -- inbound -----------------------------------------------------------\n\n // Each connection needs its own single-use URL, so this runs again on\n // every reconnect. The app token is used nowhere else.\n const open = withClient(Api.callAsApp(cfg)(\"apps.connections.open\")).pipe(\n Api.decoded(\"apps.connections.open\", Opened),\n Effect.map(({ url }) => url),\n Effect.mapError(\n (e): MessengerError.MessengerError =>\n new MessengerError.MessengerConnectFailed({\n provider: Api.provider,\n reason: reasonOf(e),\n raw: isApiFailure(e) ? e.raw : e,\n }),\n ),\n )\n\n // The ticket is in the URL, so every runtime's global `WebSocket` is enough.\n const session = yield* SocketMode.connect({ open }).pipe(\n Effect.provide(Socket.layerWebSocketConstructorGlobal),\n )\n const toEvents = Events.toEvents({ bot, replyIn: cfg.replyIn ?? \"thread\" })\n const inbox = yield* Queue.unbounded<InboundEvent, MessengerError.MessengerError | Cause.Done>()\n\n yield* Stream.fromQueue(session.envelopes).pipe(\n Stream.runForEach(({ envelope, raw }) => Queue.offerAll(inbox, toEvents(envelope, raw))),\n Effect.catch((closed) => Queue.fail(inbox, closed)),\n Effect.ensuring(Queue.end(inbox)),\n Effect.forkScoped,\n )\n\n // -- outbound ----------------------------------------------------------\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 // A URL is not a Slack upload, so it goes as the link and Slack unfurls it.\n const sendUrl = (at: ConversationRef, msg: Outbound, body: MediaFields, url: string) =>\n call(\"chat.postMessage\", {\n ...target(at, msg),\n markdown_text: Arr.getSomes([Option.fromNullishOr(body.caption), Option.some(url)]).join(\n \"\\n\",\n ),\n }).pipe(\n Api.decoded(\"chat.postMessage\", Posted),\n Effect.map(({ ts }) => ts),\n )\n\n // Three steps: reserve a URL, put the bytes there, then share the file into\n // the conversation with the caption as its comment.\n const sendBytes = (\n at: ConversationRef,\n msg: Outbound,\n body: MediaFields,\n bytes: Uint8Array,\n mimeType: string,\n ) =>\n Effect.gen(function* () {\n const filename = body.filename ?? defaultFilename(body.media)\n const reserved = yield* query(\"files.getUploadURLExternal\", {\n filename,\n length: bytes.length,\n }).pipe(Api.decoded(\"files.getUploadURLExternal\", UploadUrl))\n yield* withClient(Api.upload(reserved.upload_url, { bytes, filename, mimeType }))\n const uploaded = yield* call(\"files.completeUploadExternal\", {\n files: [{ id: reserved.file_id, title: filename }],\n channel_id: at.channel,\n ...target(at, msg),\n ...(body.caption !== undefined && { initial_comment: body.caption }),\n }).pipe(Api.decoded(\"files.completeUploadExternal\", Completed))\n // A share reports the message it became; without one the file id is\n // the only handle there is, and a file message cannot be edited anyway.\n return Option.getOrElse(shareTs(uploaded), () => reserved.file_id)\n })\n\n const sendMedia = (at: ConversationRef, msg: Outbound, body: MediaFields) =>\n Match.value(body.media).pipe(\n Match.tag(\"url\", ({ url }) => sendUrl(at, msg, body, url)),\n Match.tag(\"bytes\", ({ bytes, mimeType }) => sendBytes(at, msg, body, bytes, mimeType)),\n Match.tag(\"base64\", ({ base64, mimeType }) =>\n Effect.fromResult(Base64.decode(base64)).pipe(\n Effect.mapError(invalidMedia),\n Effect.flatMap((bytes) => sendBytes(at, msg, body, bytes, mimeType)),\n ),\n ),\n Match.exhaustive,\n )\n\n // Every chunk lands in the same thread, so only the id of the last matters.\n const sendChunks = (at: ConversationRef, msg: Outbound, body: string) =>\n Effect.gen(function* () {\n const [first, ...rest] = splitForLimit(body, limits.maxText)\n const send = (chunk: string) =>\n call(\"chat.postMessage\", { ...target(at, msg), markdown_text: chunk }).pipe(\n Api.decoded(\"chat.postMessage\", Posted),\n Effect.map(({ ts }) => ts),\n )\n const head = yield* send(first)\n const tail = yield* Effect.forEach(rest, send)\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 ts = yield* Match.value(msg.body).pipe(\n Match.tag(\"Text\", ({ text }) => sendChunks(at, msg, text)),\n Match.tag(\"Media\", (body) => sendMedia(at, msg, body)),\n Match.tag(\"Raw\", ({ payload }) =>\n sendRaw(payload).pipe(\n Api.decoded(\"raw\", Posted),\n Effect.map(({ ts }) => ts),\n ),\n ),\n Match.exhaustive,\n )\n return MessageId(ts)\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 call(\"chat.update\", {\n channel: ref.conversation.channel,\n ts: ref.id,\n markdown_text: 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 // The shortcode is the same spelling a `Reaction` event delivers.\n const react: MessengerService[\"react\"] = (ref, emoji) =>\n call(\"reactions.add\", {\n channel: ref.conversation.channel,\n timestamp: ref.id,\n name: shortcode(emoji),\n }).pipe(\n Effect.asVoid,\n Effect.catchIf(fails(\"invalid_name\"), () =>\n Effect.fail(unsupported(\"reaction\", `${emoji} is not an emoji this workspace has`)),\n ),\n Effect.mapError(requestFailed(\"react\")),\n )\n\n /**\n * Best effort: Slack shows a session status on its agent surfaces only, so\n * a rejection is logged and swallowed rather than taking a turn down.\n */\n const typing: MessengerService[\"typing\"] = Effect.gen(function* () {\n const at = yield* CurrentConversation\n const status = (state: string) =>\n call(\"agents.sessions.setStatus\", {\n channel_id: at.channel,\n ...(at.thread !== undefined && { thread_ts: at.thread }),\n status: state,\n }).pipe(\n Effect.asVoid,\n Effect.catch((e) => Effect.logDebug(\"slack status not set\", { reason: reasonOf(e) })),\n )\n yield* Effect.acquireRelease(status(\"processing\"), () => status(\"active\"))\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 }, { ...defaultStream, ...cfg.stream }),\n limits,\n }\n })\n\n// ---------------------------------------------------------------------------\n// Layer\n// ---------------------------------------------------------------------------\n\n/**\n * One Socket Mode connection, registered under both the `Slack` and\n * `Messenger` tags. Building the layer authenticates and waits for `hello`, so\n * a rejected token fails here; the connection lives until the scope closes and\n * `events` ends with it.\n */\nexport const layer = (\n cfg: Config,\n): Layer.Layer<Slack | Messenger, MessengerError.MessengerConnectFailed, HttpClient.HttpClient> =>\n Layer.effectContext(\n Effect.map(make(cfg), (service) =>\n Context.make(Slack, service).pipe(Context.add(Messenger, service)),\n ),\n )\n"],"mappings":"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"}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { Effect, Redacted, Schema } from "effect";
|
|
2
|
-
import { HttpClient } from "effect/
|
|
2
|
+
import { HttpClient } from "effect/http";
|
|
3
3
|
import * as MessengerError from "@effect-uai/core/MessengerError";
|
|
4
4
|
//#region src/internal/api.d.ts
|
|
5
5
|
declare const provider = "slack";
|
|
@@ -48,4 +48,4 @@ declare const upload: (url: string, file: Upload) => Effect.Effect<void, ApiFail
|
|
|
48
48
|
declare const decoded: <A>(method: string, schema: Schema.Decoder<A>) => <E, R>(result: Effect.Effect<unknown, E, R>) => Effect.Effect<A, E | ApiFailure, R>;
|
|
49
49
|
//#endregion
|
|
50
50
|
export { Upload as a, decoded as c, upload as d, Params as i, provider as l, ApiFailure as n, call as o, Config as r, callAsApp as s, ApiError as t, query as u };
|
|
51
|
-
//# sourceMappingURL=api-
|
|
51
|
+
//# sourceMappingURL=api-CbBW1iF0.d.mts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"api-
|
|
1
|
+
{"version":3,"file":"api-CbBW1iF0.d.mts","names":[],"sources":["../src/internal/api.ts"],"mappings":";;;;cAIa;KAED;;WAED,UAAU,SAAS;;WAEnB,UAAU,SAAS;;WAEnB;;;;;;;;;;cAQE,mBAAmB;WACrB;WACA;WACA;WACA;;KAGC,WAAW,aAAa,eAAe;KAQvC,SAAS,SAAS;;KAGlB;WACD,OAAO;WACP;WACA;;;cA4DE,OAAI,KACT,YAAM,gBACG,SAAW,WAAS,OAAO,gBAAgB,UAAU,WAAW;;cAIpE,YAAS,KACd,YAAM,mBACM,OAAO,gBAAgB,UAAU,WAAW;;cAInD,QAAK,KACV,YAAM,gBACG,QAAU,WAAS,OAAO,gBAAgB,UAAU,WAAW;;;;;cAyBnE,SAAM,aACN,MACL,WACL,OAAO,aAAa,YAAY,WAAW;;cAcjC,UACV,GAAC,gBAAgB,QAAU,OAAO,QAAQ,QAC1C,GAAG,GAAC,QAAU,OAAO,gBAAgB,GAAG,OAAK,OAAO,OAAO,GAAG,IAAI,YAAY"}
|