@polpo-ai/channels 0.15.63

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,13 @@
1
+ Copyright 2026-present Lumea Labs
2
+
3
+ Licensed under the Apache License, Version 2.0 (the "License");
4
+ you may not use this file except in compliance with the License.
5
+ You may obtain a copy of the License at
6
+
7
+ http://www.apache.org/licenses/LICENSE-2.0
8
+
9
+ Unless required by applicable law or agreed to in writing, software
10
+ distributed under the License is distributed on an "AS IS" BASIS,
11
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12
+ See the License for the specific language governing permissions and
13
+ limitations under the License.
package/README.md ADDED
@@ -0,0 +1,205 @@
1
+ # @polpo-ai/channels
2
+
3
+ Provider-neutral messaging channels for the Polpo runtime, built on the official
4
+ Vercel Chat SDK adapters for Slack, Telegram, Discord, and WhatsApp.
5
+
6
+ The package owns transport concerns: webhook verification, provider event
7
+ normalization, deduplication, typing indicators, response delivery, attachments,
8
+ and provider message limits. Your host owns credentials, durable state, routing,
9
+ agent execution, sessions, billing, and rollout policy.
10
+
11
+ ## Install
12
+
13
+ ```bash
14
+ pnpm add @polpo-ai/channels ai
15
+ ```
16
+
17
+ Node.js 20 or later is required.
18
+
19
+ ## Runtime
20
+
21
+ ```ts
22
+ import {
23
+ ChannelRuntime,
24
+ dispatchChannelWebhook,
25
+ type ChannelInstallation,
26
+ } from "@polpo-ai/channels";
27
+
28
+ const runtime = new ChannelRuntime({
29
+ handleEvent: async (event) => {
30
+ if (event.type === "message") {
31
+ return { text: `Received ${event.messages.length} message(s)` };
32
+ }
33
+ if (event.type === "action") {
34
+ return { text: `Selected ${event.actionId}` };
35
+ }
36
+ },
37
+ });
38
+
39
+ async function resolveInstallation(
40
+ routeKey: string | undefined,
41
+ ): Promise<ChannelInstallation | null> {
42
+ // Resolve an opaque route key to scoped credentials in your own store.
43
+ return null;
44
+ }
45
+
46
+ export async function webhook(request: Request, provider: string, routeKey: string) {
47
+ return dispatchChannelWebhook({
48
+ provider,
49
+ request,
50
+ routeKey,
51
+ resolveInstallation: ({ routeKey }) => resolveInstallation(routeKey),
52
+ runtime,
53
+ });
54
+ }
55
+ ```
56
+
57
+ Never select an installation from an unverified workspace, chat, team, or phone
58
+ identifier contained in the request body. Resolve only an opaque route key from
59
+ the webhook URL, then let the official adapter verify the untouched request.
60
+
61
+ ## Installations
62
+
63
+ Each installation has a stable `id` and a `credentialRevision`. Change the
64
+ revision whenever credentials rotate; the runtime will evict the old adapter.
65
+
66
+ ```ts
67
+ const telegram = {
68
+ id: "channel_123",
69
+ provider: "telegram",
70
+ credentialRevision: "sha256-of-secret-version",
71
+ credentials: {
72
+ botToken: process.env.TELEGRAM_BOT_TOKEN!,
73
+ secretToken: process.env.TELEGRAM_WEBHOOK_SECRET,
74
+ },
75
+ } as const;
76
+ ```
77
+
78
+ Equivalent typed installation shapes exist for Slack, Discord, and WhatsApp.
79
+ Secrets stay in the host credential store and are never added to normalized
80
+ turns.
81
+
82
+ ## Response delivery
83
+
84
+ Responses remain one logical provider message by default and are split only when
85
+ the provider hard limit requires it. Messaging products can opt into shorter,
86
+ semantic conversational messages per installation:
87
+
88
+ ```ts
89
+ const installation = {
90
+ // credentials, id, provider, and revision omitted
91
+ responseDelivery: {
92
+ style: "conversational",
93
+ targetCharacters: 900,
94
+ maxMessages: 6,
95
+ },
96
+ };
97
+ ```
98
+
99
+ The runtime prefers paragraphs, sentences, and whitespace, preserves the exact
100
+ output, never splits a Unicode surrogate pair, and always honors provider hard
101
+ limits. `maxMessages` is a conversational preference rather than permission to
102
+ truncate; additional technical segments are emitted when a provider limit makes
103
+ them unavoidable.
104
+
105
+ ## Durable state
106
+
107
+ The default state adapter is in-memory and is appropriate for local development
108
+ or one process. Production hosts should provide a shared `stateFactory` backed
109
+ by Redis or another atomic store so webhook deduplication, queues, subscriptions,
110
+ and locks work across replicas.
111
+
112
+ ```ts
113
+ const runtime = new ChannelRuntime({
114
+ stateFactory: (installation) => createRedisState(installation.id),
115
+ handleTurn,
116
+ });
117
+ ```
118
+
119
+ ## Events and native output
120
+
121
+ `handleEvent` receives a discriminated union for messages, slash commands,
122
+ actions, reactions, modal submit/close events, and dynamic option loads. This
123
+ keeps provider payloads available in `raw` while exposing a provider-neutral
124
+ contract for normal application logic.
125
+
126
+ Handlers can return `text`, files, a Chat SDK stream, or native
127
+ `PostableMessage` objects in `posts`. Native posts support cards, actions, and
128
+ other rich content without flattening it to text. Native posts cannot be mixed
129
+ with convenience output in the same result, which prevents duplicate delivery.
130
+
131
+ Use `channelProviderCapabilities(provider)` before exposing provider-specific
132
+ controls. The returned immutable matrix distinguishes native, partial, buffered,
133
+ fallback, file-fallback, and unsupported behavior.
134
+
135
+ ## Event coordination
136
+
137
+ The runtime serializes turns for the same installation and thread in one process.
138
+ Distributed hosts can replace that behavior with `coordinateEvent` to implement
139
+ a durable active-run policy such as queueing, steering, or rejection:
140
+
141
+ ```ts
142
+ const runtime = new ChannelRuntime({
143
+ coordinateEvent: (event, execute) =>
144
+ activeRuns.coordinate(event.installationId, event.threadId, execute),
145
+ handleEvent,
146
+ });
147
+ ```
148
+
149
+ Return `executed` after awaiting `execute()` exactly once. Return `queued`,
150
+ `steered`, or `rejected` without executing inline after durably recording that
151
+ decision. The runtime rejects contradictory dispositions and duplicate calls to
152
+ `execute()`. `coordinateTurn` remains available for hosts using the legacy
153
+ message-only handler.
154
+
155
+ ## Polpo conversation bridge
156
+
157
+ `@polpo-ai/server` exports `createConversationChannelTurnHandler`. It maps a
158
+ normalized channel turn onto Polpo's canonical conversation runtime, keeps one
159
+ stable Session per external user and thread, loads bounded history, and supports
160
+ host-defined attachment resolution.
161
+
162
+ ```ts
163
+ import { createConversationChannelTurnHandler } from "@polpo-ai/server";
164
+
165
+ const handleTurn = createConversationChannelTurnHandler(serverDeps, {
166
+ agent: (turn) => resolveAgentForInstallation(turn.installationId),
167
+ resolveAttachment: processChannelAttachment,
168
+ });
169
+ ```
170
+
171
+ Chat SDK thread history is transport state. Polpo Sessions remain the canonical
172
+ conversation history used by the model.
173
+
174
+ ## Self-hosted webhook routes
175
+
176
+ `@polpo-ai/node` can mount the runtime directly:
177
+
178
+ ```ts
179
+ await server.start({
180
+ channels: {
181
+ runtime,
182
+ resolveInstallation: async ({ routeKey }) => loadInstallation(routeKey),
183
+ },
184
+ });
185
+ ```
186
+
187
+ This exposes:
188
+
189
+ - `POST /v1/channel-webhooks/:provider/:routeKey`
190
+ - `POST /v1/channel-webhooks/:provider`
191
+
192
+ The route without a key is useful only when the resolver has another trusted,
193
+ host-controlled installation binding.
194
+
195
+ ## Provider behavior
196
+
197
+ - Streams are delegated to the official Chat SDK adapter. Slack streams
198
+ natively, Discord and Telegram use their adapter-specific streaming strategy,
199
+ and WhatsApp buffers until completion.
200
+ - Long non-streaming text is split at semantic boundaries without silently
201
+ truncating output.
202
+ - Typing indicator failures are observable but do not fail an agent turn.
203
+ - Inbound files remain lazy when the provider supports authenticated fetching.
204
+ - Discord HTTP interactions are supported by the official webhook adapter;
205
+ Gateway-only event flows require a separate gateway process.
@@ -0,0 +1,6 @@
1
+ export { ChannelRuntime } from "./runtime.js";
2
+ export { channelProviderCapabilities, createOfficialChannelAdapter, } from "./providers.js";
3
+ export { channelMessageHardLimit, normalizeChannelResponseDeliveryPolicy, segmentChannelText, } from "./response.js";
4
+ export { dispatchChannelWebhook, isChannelProviderId, type DispatchChannelWebhookInput, } from "./webhook.js";
5
+ export { CHANNEL_PROVIDER_IDS, type ChannelActionEvent, type ChannelAdapterFactory, type ChannelAttachment, type ChannelAuthor, type ChannelCapabilitySupport, type ChannelConcurrencyPolicy, type ChannelEventCoordinator, type ChannelEventHandler, type ChannelEventResult, type ChannelInboundMessage, type ChannelInboundEvent, type ChannelInboundTurn, type ChannelInstallation, type ChannelInstallationResolver, type ChannelInstallationResolverInput, type ChannelMessageEvent, type ChannelModalCloseEvent, type ChannelModalSubmitEvent, type ChannelNativePost, type ChannelOptionsLoadEvent, type ChannelOutputFile, type ChannelOutputStream, type ChannelProviderId, type ChannelProviderCapabilities, type ChannelReactionEvent, type ChannelResponseDeliveryPolicy, type ChannelRuntimeEvent, type ChannelRuntimeOptions, type ChannelStateFactory, type ChannelStateAdapter, type ChannelStateLock, type ChannelStateQueueEntry, type ChannelTurnHandler, type ChannelTurnCoordinator, type ChannelTurnResult, type ChannelWebhookOptions, type ChannelSlashCommandEvent, type DiscordChannelInstallation, type SlackChannelInstallation, type TelegramChannelInstallation, type WhatsAppChannelInstallation, } from "./types.js";
6
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,cAAc,EAAE,MAAM,cAAc,CAAC;AAC9C,OAAO,EACL,2BAA2B,EAC3B,4BAA4B,GAC7B,MAAM,gBAAgB,CAAC;AACxB,OAAO,EACL,uBAAuB,EACvB,sCAAsC,EACtC,kBAAkB,GACnB,MAAM,eAAe,CAAC;AACvB,OAAO,EACL,sBAAsB,EACtB,mBAAmB,EACnB,KAAK,2BAA2B,GACjC,MAAM,cAAc,CAAC;AACtB,OAAO,EACL,oBAAoB,EACpB,KAAK,kBAAkB,EACvB,KAAK,qBAAqB,EAC1B,KAAK,iBAAiB,EACtB,KAAK,aAAa,EAClB,KAAK,wBAAwB,EAC7B,KAAK,wBAAwB,EAC7B,KAAK,uBAAuB,EAC5B,KAAK,mBAAmB,EACxB,KAAK,kBAAkB,EACvB,KAAK,qBAAqB,EAC1B,KAAK,mBAAmB,EACxB,KAAK,kBAAkB,EACvB,KAAK,mBAAmB,EACxB,KAAK,2BAA2B,EAChC,KAAK,gCAAgC,EACrC,KAAK,mBAAmB,EACxB,KAAK,sBAAsB,EAC3B,KAAK,uBAAuB,EAC5B,KAAK,iBAAiB,EACtB,KAAK,uBAAuB,EAC5B,KAAK,iBAAiB,EACtB,KAAK,mBAAmB,EACxB,KAAK,iBAAiB,EACtB,KAAK,2BAA2B,EAChC,KAAK,oBAAoB,EACzB,KAAK,6BAA6B,EAClC,KAAK,mBAAmB,EACxB,KAAK,qBAAqB,EAC1B,KAAK,mBAAmB,EACxB,KAAK,mBAAmB,EACxB,KAAK,gBAAgB,EACrB,KAAK,sBAAsB,EAC3B,KAAK,kBAAkB,EACvB,KAAK,sBAAsB,EAC3B,KAAK,iBAAiB,EACtB,KAAK,qBAAqB,EAC1B,KAAK,wBAAwB,EAC7B,KAAK,0BAA0B,EAC/B,KAAK,wBAAwB,EAC7B,KAAK,2BAA2B,EAChC,KAAK,2BAA2B,GACjC,MAAM,YAAY,CAAC"}
package/dist/index.js ADDED
@@ -0,0 +1,6 @@
1
+ export { ChannelRuntime } from "./runtime.js";
2
+ export { channelProviderCapabilities, createOfficialChannelAdapter, } from "./providers.js";
3
+ export { channelMessageHardLimit, normalizeChannelResponseDeliveryPolicy, segmentChannelText, } from "./response.js";
4
+ export { dispatchChannelWebhook, isChannelProviderId, } from "./webhook.js";
5
+ export { CHANNEL_PROVIDER_IDS, } from "./types.js";
6
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,cAAc,EAAE,MAAM,cAAc,CAAC;AAC9C,OAAO,EACL,2BAA2B,EAC3B,4BAA4B,GAC7B,MAAM,gBAAgB,CAAC;AACxB,OAAO,EACL,uBAAuB,EACvB,sCAAsC,EACtC,kBAAkB,GACnB,MAAM,eAAe,CAAC;AACvB,OAAO,EACL,sBAAsB,EACtB,mBAAmB,GAEpB,MAAM,cAAc,CAAC;AACtB,OAAO,EACL,oBAAoB,GA0CrB,MAAM,YAAY,CAAC"}
@@ -0,0 +1,5 @@
1
+ import type { Adapter } from "chat";
2
+ import type { ChannelInstallation, ChannelProviderCapabilities, ChannelProviderId } from "./types.js";
3
+ export declare function channelProviderCapabilities(provider: ChannelProviderId): ChannelProviderCapabilities;
4
+ export declare function createOfficialChannelAdapter(installation: ChannelInstallation): Adapter;
5
+ //# sourceMappingURL=providers.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"providers.d.ts","sourceRoot":"","sources":["../src/providers.ts"],"names":[],"mappings":"AAIA,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,MAAM,CAAC;AACpC,OAAO,KAAK,EACV,mBAAmB,EACnB,2BAA2B,EAC3B,iBAAiB,EAClB,MAAM,YAAY,CAAC;AA6DpB,wBAAgB,2BAA2B,CACzC,QAAQ,EAAE,iBAAiB,GAC1B,2BAA2B,CAE7B;AAED,wBAAgB,4BAA4B,CAC1C,YAAY,EAAE,mBAAmB,GAChC,OAAO,CA0CT"}
@@ -0,0 +1,109 @@
1
+ import { createDiscordAdapter } from "@chat-adapter/discord";
2
+ import { createSlackAdapter } from "@chat-adapter/slack";
3
+ import { createTelegramAdapter } from "@chat-adapter/telegram";
4
+ import { createWhatsAppAdapter } from "@chat-adapter/whatsapp";
5
+ const PROVIDER_CAPABILITIES = {
6
+ slack: Object.freeze({
7
+ actions: "native",
8
+ audioAttachments: "native",
9
+ cards: "native",
10
+ files: "native",
11
+ formattedText: "native",
12
+ modals: "native",
13
+ reactions: "native",
14
+ streaming: "native",
15
+ structuredStreaming: "native",
16
+ typing: "native",
17
+ videoAttachments: "native",
18
+ voiceReplies: "file-fallback",
19
+ }),
20
+ telegram: Object.freeze({
21
+ actions: "native",
22
+ audioAttachments: "native",
23
+ cards: "partial",
24
+ files: "native",
25
+ formattedText: "native",
26
+ modals: "unsupported",
27
+ reactions: "native",
28
+ streaming: "native",
29
+ structuredStreaming: "fallback",
30
+ typing: "native",
31
+ videoAttachments: "native",
32
+ voiceReplies: "native",
33
+ }),
34
+ discord: Object.freeze({
35
+ actions: "native",
36
+ audioAttachments: "native",
37
+ cards: "native",
38
+ files: "native",
39
+ formattedText: "native",
40
+ modals: "unsupported",
41
+ reactions: "native",
42
+ streaming: "fallback",
43
+ structuredStreaming: "fallback",
44
+ typing: "native",
45
+ videoAttachments: "native",
46
+ voiceReplies: "file-fallback",
47
+ }),
48
+ whatsapp: Object.freeze({
49
+ actions: "native",
50
+ audioAttachments: "native",
51
+ cards: "partial",
52
+ files: "native",
53
+ formattedText: "native",
54
+ modals: "unsupported",
55
+ reactions: "native",
56
+ streaming: "buffered",
57
+ structuredStreaming: "fallback",
58
+ typing: "native",
59
+ videoAttachments: "native",
60
+ voiceReplies: "native",
61
+ }),
62
+ };
63
+ export function channelProviderCapabilities(provider) {
64
+ return PROVIDER_CAPABILITIES[provider];
65
+ }
66
+ export function createOfficialChannelAdapter(installation) {
67
+ let adapter;
68
+ switch (installation.provider) {
69
+ case "slack":
70
+ adapter = createSlackAdapter({
71
+ botToken: installation.credentials.botToken,
72
+ botUserId: installation.credentials.botUserId,
73
+ mode: "webhook",
74
+ signingSecret: installation.credentials.signingSecret,
75
+ userName: installation.userName,
76
+ });
77
+ break;
78
+ case "telegram":
79
+ adapter = createTelegramAdapter({
80
+ botToken: installation.credentials.botToken,
81
+ mode: "webhook",
82
+ secretToken: installation.credentials.secretToken,
83
+ userName: installation.userName,
84
+ });
85
+ break;
86
+ case "discord":
87
+ adapter = createDiscordAdapter({
88
+ applicationId: installation.credentials.applicationId,
89
+ botToken: installation.credentials.botToken,
90
+ publicKey: installation.credentials.publicKey,
91
+ userName: installation.userName,
92
+ });
93
+ break;
94
+ case "whatsapp":
95
+ adapter = createWhatsAppAdapter({
96
+ accessToken: installation.credentials.accessToken,
97
+ appSecret: installation.credentials.appSecret,
98
+ phoneNumberId: installation.credentials.phoneNumberId,
99
+ userName: installation.userName,
100
+ verifyToken: installation.credentials.verifyToken,
101
+ });
102
+ break;
103
+ }
104
+ if (installation.typingEnabled === false) {
105
+ adapter.startTyping = async () => { };
106
+ }
107
+ return adapter;
108
+ }
109
+ //# sourceMappingURL=providers.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"providers.js","sourceRoot":"","sources":["../src/providers.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,5 @@
1
+ import type { ChannelProviderId, ChannelResponseDeliveryPolicy } from "./types.js";
2
+ export declare function channelMessageHardLimit(provider: ChannelProviderId): number;
3
+ export declare function segmentChannelText(provider: ChannelProviderId, text: string, responseDelivery?: ChannelResponseDeliveryPolicy): string[];
4
+ export declare function normalizeChannelResponseDeliveryPolicy(input: unknown): ChannelResponseDeliveryPolicy | undefined;
5
+ //# sourceMappingURL=response.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"response.d.ts","sourceRoot":"","sources":["../src/response.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EACV,iBAAiB,EACjB,6BAA6B,EAC9B,MAAM,YAAY,CAAC;AAgBpB,wBAAgB,uBAAuB,CAAC,QAAQ,EAAE,iBAAiB,GAAG,MAAM,CAE3E;AAED,wBAAgB,kBAAkB,CAChC,QAAQ,EAAE,iBAAiB,EAC3B,IAAI,EAAE,MAAM,EACZ,gBAAgB,CAAC,EAAE,6BAA6B,GAC/C,MAAM,EAAE,CAoCV;AAED,wBAAgB,sCAAsC,CACpD,KAAK,EAAE,OAAO,GACb,6BAA6B,GAAG,SAAS,CAmC3C"}
@@ -0,0 +1,115 @@
1
+ const HARD_LIMITS = {
2
+ discord: 2_000,
3
+ slack: 40_000,
4
+ telegram: 4_096,
5
+ whatsapp: 4_096,
6
+ };
7
+ const SOFT_LIMITS = {
8
+ discord: 1_850,
9
+ slack: 8_000,
10
+ telegram: 3_800,
11
+ whatsapp: 3_800,
12
+ };
13
+ export function channelMessageHardLimit(provider) {
14
+ return HARD_LIMITS[provider];
15
+ }
16
+ export function segmentChannelText(provider, text, responseDelivery) {
17
+ if (!text.trim())
18
+ return [];
19
+ const hardLimit = HARD_LIMITS[provider];
20
+ const softLimit = Math.min(SOFT_LIMITS[provider], hardLimit);
21
+ if (responseDelivery?.style !== "conversational") {
22
+ return segmentWithLimits(text, softLimit, hardLimit);
23
+ }
24
+ const targetCharacters = Math.min(responseDelivery.targetCharacters ?? 900, softLimit);
25
+ const maxMessages = responseDelivery.maxMessages ?? 6;
26
+ if (text.length <= targetCharacters)
27
+ return [text];
28
+ const segments = [];
29
+ let remaining = text;
30
+ const preferredMaximum = Math.min(hardLimit, Math.max(targetCharacters + 1, Math.floor(targetCharacters * 1.35)));
31
+ while (remaining.length > preferredMaximum
32
+ && segments.length < maxMessages - 1) {
33
+ const splitAt = findSemanticSplit(remaining, targetCharacters, preferredMaximum);
34
+ const part = remaining.slice(0, splitAt);
35
+ if (part)
36
+ segments.push(part);
37
+ remaining = remaining.slice(splitAt);
38
+ }
39
+ return [...segments, ...segmentWithLimits(remaining, softLimit, hardLimit)];
40
+ }
41
+ export function normalizeChannelResponseDeliveryPolicy(input) {
42
+ if (input === undefined || input === null)
43
+ return undefined;
44
+ if (!input || typeof input !== "object" || Array.isArray(input)) {
45
+ throw new Error("responseDelivery must be an object");
46
+ }
47
+ const value = input;
48
+ const style = value.style;
49
+ if (style !== "single" && style !== "conversational") {
50
+ throw new Error('responseDelivery.style must be "single" or "conversational"');
51
+ }
52
+ const targetCharacters = boundedInteger(value.targetCharacters, "responseDelivery.targetCharacters", 200, 4_000);
53
+ const maxMessages = boundedInteger(value.maxMessages, "responseDelivery.maxMessages", 2, 20);
54
+ if (style === "single"
55
+ && (targetCharacters !== undefined || maxMessages !== undefined)) {
56
+ throw new Error("responseDelivery targetCharacters and maxMessages are only valid for conversational style");
57
+ }
58
+ return {
59
+ style,
60
+ ...(targetCharacters === undefined ? {} : { targetCharacters }),
61
+ ...(maxMessages === undefined ? {} : { maxMessages }),
62
+ };
63
+ }
64
+ function segmentWithLimits(text, softLimit, hardLimit) {
65
+ if (!text)
66
+ return [];
67
+ if (text.length <= hardLimit)
68
+ return [text];
69
+ const segments = [];
70
+ let remaining = text;
71
+ while (remaining.length > hardLimit) {
72
+ const splitAt = findSemanticSplit(remaining, softLimit, hardLimit);
73
+ segments.push(remaining.slice(0, splitAt));
74
+ remaining = remaining.slice(splitAt);
75
+ }
76
+ if (remaining)
77
+ segments.push(remaining);
78
+ return segments;
79
+ }
80
+ function boundedInteger(value, label, min, max) {
81
+ if (value === undefined || value === null)
82
+ return undefined;
83
+ if (!Number.isInteger(value) || Number(value) < min || Number(value) > max) {
84
+ throw new Error(`${label} must be an integer between ${min} and ${max}`);
85
+ }
86
+ return Number(value);
87
+ }
88
+ function findSemanticSplit(text, softLimit, hardLimit) {
89
+ const window = text.slice(0, hardLimit + 1);
90
+ const preferred = ["\n\n", "\n", ". ", "! ", "? ", "; ", ", ", " "];
91
+ for (const delimiter of preferred) {
92
+ const index = window.lastIndexOf(delimiter, hardLimit);
93
+ if (index >= softLimit)
94
+ return index + delimiter.length;
95
+ }
96
+ // Prefer a coherent earlier boundary over a hard cut when no good split
97
+ // exists in the ideal window. Avoid producing a tiny leading segment.
98
+ const minimumUsefulLength = Math.floor(hardLimit * 0.35);
99
+ for (const delimiter of preferred) {
100
+ const index = window.lastIndexOf(delimiter, softLimit);
101
+ if (index >= minimumUsefulLength)
102
+ return index + delimiter.length;
103
+ }
104
+ return avoidSplittingSurrogatePair(text, hardLimit);
105
+ }
106
+ function avoidSplittingSurrogatePair(text, index) {
107
+ if (index <= 0 || index >= text.length)
108
+ return index;
109
+ const before = text.charCodeAt(index - 1);
110
+ const after = text.charCodeAt(index);
111
+ const splitsPair = before >= 0xd800 && before <= 0xdbff
112
+ && after >= 0xdc00 && after <= 0xdfff;
113
+ return splitsPair ? index - 1 : index;
114
+ }
115
+ //# sourceMappingURL=response.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"response.js","sourceRoot":"","sources":["../src/response.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,32 @@
1
+ import type { ChannelInstallation, ChannelRuntimeOptions, ChannelTurnResult, ChannelWebhookOptions } from "./types.js";
2
+ export declare class ChannelRuntime {
3
+ private readonly entries;
4
+ private readonly pendingInstallations;
5
+ private readonly pendingTurns;
6
+ private readonly options;
7
+ constructor(options: ChannelRuntimeOptions);
8
+ handleWebhook(installation: ChannelInstallation, request: Request, options?: ChannelWebhookOptions): Promise<Response>;
9
+ post(installation: ChannelInstallation, threadId: string, result: ChannelTurnResult | string): Promise<void>;
10
+ invalidate(installationId: string): Promise<void>;
11
+ shutdown(): Promise<void>;
12
+ get size(): number;
13
+ private getOrCreate;
14
+ private createEntry;
15
+ private handleMessage;
16
+ private handleSlashCommand;
17
+ private handleAction;
18
+ private handleReaction;
19
+ private handleModalSubmit;
20
+ private handleModalClose;
21
+ private handleOptionsLoad;
22
+ private acceptEvent;
23
+ private executeTurn;
24
+ private executeEvent;
25
+ private coordinateEventLocally;
26
+ private coordinateLocally;
27
+ private deliver;
28
+ private prune;
29
+ private evict;
30
+ private emit;
31
+ }
32
+ //# sourceMappingURL=runtime.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"runtime.d.ts","sourceRoot":"","sources":["../src/runtime.ts"],"names":[],"mappings":"AAkBA,OAAO,KAAK,EAMV,mBAAmB,EAEnB,qBAAqB,EAErB,iBAAiB,EACjB,qBAAqB,EACtB,MAAM,YAAY,CAAC;AAwBpB,qBAAa,cAAc;IACzB,OAAO,CAAC,QAAQ,CAAC,OAAO,CAAmC;IAC3D,OAAO,CAAC,QAAQ,CAAC,oBAAoB,CAA4C;IACjF,OAAO,CAAC,QAAQ,CAAC,YAAY,CAAoC;IACjE,OAAO,CAAC,QAAQ,CAAC,OAAO,CAEE;gBAEd,OAAO,EAAE,qBAAqB;IAWpC,aAAa,CACjB,YAAY,EAAE,mBAAmB,EACjC,OAAO,EAAE,OAAO,EAChB,OAAO,CAAC,EAAE,qBAAqB,GAC9B,OAAO,CAAC,QAAQ,CAAC;IAiBd,IAAI,CACR,YAAY,EAAE,mBAAmB,EACjC,QAAQ,EAAE,MAAM,EAChB,MAAM,EAAE,iBAAiB,GAAG,MAAM,GACjC,OAAO,CAAC,IAAI,CAAC;IAeV,UAAU,CAAC,cAAc,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IAOjD,QAAQ,IAAI,OAAO,CAAC,IAAI,CAAC;IAK/B,IAAI,IAAI,IAAI,MAAM,CAEjB;YAEa,WAAW;YAyBX,WAAW;YA2DX,aAAa;YAsCb,kBAAkB;YA8ElB,YAAY;YAqCZ,cAAc;YAkCd,iBAAiB;YAsCjB,gBAAgB;YAiChB,iBAAiB;YAsBjB,WAAW;YAaX,WAAW;YAuFX,YAAY;YA+EZ,sBAAsB;YAetB,iBAAiB;YAiBjB,OAAO;YAiFP,KAAK;YAgBL,KAAK;YAUL,IAAI;CAGnB"}