chat 4.18.0 → 4.19.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 +2 -0
- package/dist/index.d.ts +87 -9
- package/dist/index.js +57 -10
- package/dist/index.js.map +1 -1
- package/docs/adapters.mdx +56 -0
- package/docs/error-handling.mdx +1 -1
- package/docs/guides/code-review-hono.mdx +3 -3
- package/docs/guides/discord-nuxt.mdx +3 -2
- package/docs/guides/slack-nextjs.mdx +3 -2
- package/docs/meta.json +3 -4
- package/docs/slash-commands.mdx +4 -4
- package/docs/{state/index.mdx → state.mdx} +24 -12
- package/docs/usage.mdx +5 -0
- package/package.json +1 -1
- package/docs/adapters/discord.mdx +0 -217
- package/docs/adapters/gchat.mdx +0 -237
- package/docs/adapters/github.mdx +0 -222
- package/docs/adapters/index.mdx +0 -129
- package/docs/adapters/linear.mdx +0 -206
- package/docs/adapters/meta.json +0 -13
- package/docs/adapters/slack.mdx +0 -314
- package/docs/adapters/teams.mdx +0 -287
- package/docs/adapters/telegram.mdx +0 -161
- package/docs/state/ioredis.mdx +0 -81
- package/docs/state/memory.mdx +0 -52
- package/docs/state/meta.json +0 -4
- package/docs/state/postgres.mdx +0 -98
- package/docs/state/redis.mdx +0 -100
package/README.md
CHANGED
|
@@ -40,6 +40,8 @@ bot.onSubscribedMessage(async (thread, message) => {
|
|
|
40
40
|
});
|
|
41
41
|
```
|
|
42
42
|
|
|
43
|
+
> **Tip:** PostgreSQL and ioredis adapters are also available for production. See [State Adapters](https://chat-sdk.dev/docs/state) for all options.
|
|
44
|
+
|
|
43
45
|
## Configuration
|
|
44
46
|
|
|
45
47
|
| Option | Type | Default | Description |
|
package/dist/index.d.ts
CHANGED
|
@@ -77,6 +77,18 @@ interface ChatConfig<TAdapters extends Record<string, Adapter> = Record<string,
|
|
|
77
77
|
* Pass "silent" to disable all logging.
|
|
78
78
|
*/
|
|
79
79
|
logger?: Logger | LogLevel;
|
|
80
|
+
/**
|
|
81
|
+
* Behavior when a thread lock cannot be acquired (another handler is processing).
|
|
82
|
+
* - `'drop'` (default) — throw `LockError`, preserving current behavior
|
|
83
|
+
* - `'force'` — force-release the existing lock and re-acquire
|
|
84
|
+
* - callback — custom logic receiving `(threadId, message)`, return `'force'` or `'drop'`
|
|
85
|
+
*
|
|
86
|
+
* When `'force'` is used, the previous handler continues executing — only the lock
|
|
87
|
+
* is released, not the handler itself. This means two handlers may run concurrently
|
|
88
|
+
* on the same thread. The old handler's `releaseLock()` call becomes a no-op since
|
|
89
|
+
* the token no longer matches.
|
|
90
|
+
*/
|
|
91
|
+
onLockConflict?: "force" | "drop" | ((threadId: string, message: Message) => "force" | "drop" | Promise<"force" | "drop">);
|
|
80
92
|
/** State adapter for subscriptions and locking */
|
|
81
93
|
state: StateAdapter;
|
|
82
94
|
/**
|
|
@@ -122,7 +134,7 @@ interface Adapter<TThreadId = unknown, TRawMessage = unknown> {
|
|
|
122
134
|
* Default fallback: first two colon-separated parts (e.g., "slack:C123").
|
|
123
135
|
* Adapters with different structures should override this.
|
|
124
136
|
*/
|
|
125
|
-
channelIdFromThreadId
|
|
137
|
+
channelIdFromThreadId(threadId: string): string;
|
|
126
138
|
/** Decode thread ID string back to platform-specific data */
|
|
127
139
|
decodeThreadId(threadId: string): TThreadId;
|
|
128
140
|
/** Delete a message */
|
|
@@ -248,13 +260,27 @@ interface Adapter<TThreadId = unknown, TRawMessage = unknown> {
|
|
|
248
260
|
* @param message - The message content
|
|
249
261
|
* @returns EphemeralMessage with usedFallback: false
|
|
250
262
|
*/
|
|
251
|
-
postEphemeral?(threadId: string, userId: string, message: AdapterPostableMessage): Promise<EphemeralMessage
|
|
263
|
+
postEphemeral?(threadId: string, userId: string, message: AdapterPostableMessage): Promise<EphemeralMessage<TRawMessage>>;
|
|
252
264
|
/** Post a message to a thread */
|
|
253
265
|
postMessage(threadId: string, message: AdapterPostableMessage): Promise<RawMessage<TRawMessage>>;
|
|
254
266
|
/** Remove a reaction from a message */
|
|
255
267
|
removeReaction(threadId: string, messageId: string, emoji: EmojiValue | string): Promise<void>;
|
|
256
268
|
/** Render formatted content to platform-specific string */
|
|
257
269
|
renderFormatted(content: FormattedContent): string;
|
|
270
|
+
/**
|
|
271
|
+
* Schedule a message for future delivery.
|
|
272
|
+
*
|
|
273
|
+
* Optional — only supported by adapters with native scheduling APIs (e.g., Slack).
|
|
274
|
+
* Thread.schedule() will throw NotImplementedError if this method is absent.
|
|
275
|
+
*
|
|
276
|
+
* @param threadId - The thread to post in
|
|
277
|
+
* @param message - The message content
|
|
278
|
+
* @param options - Scheduling options including the target delivery time
|
|
279
|
+
* @returns A ScheduledMessage with cancel() capability
|
|
280
|
+
*/
|
|
281
|
+
scheduleMessage?(threadId: string, message: AdapterPostableMessage, options: {
|
|
282
|
+
postAt: Date;
|
|
283
|
+
}): Promise<ScheduledMessage<TRawMessage>>;
|
|
258
284
|
/** Show typing indicator */
|
|
259
285
|
startTyping(threadId: string, status?: string): Promise<void>;
|
|
260
286
|
/**
|
|
@@ -406,6 +432,12 @@ interface StateAdapter {
|
|
|
406
432
|
disconnect(): Promise<void>;
|
|
407
433
|
/** Extend a lock's TTL */
|
|
408
434
|
extendLock(lock: Lock, ttlMs: number): Promise<boolean>;
|
|
435
|
+
/**
|
|
436
|
+
* Force-release a lock on a thread, regardless of ownership token.
|
|
437
|
+
* The previous lock holder's handler continues running — only the lock is released.
|
|
438
|
+
* The old handler's `releaseLock()` becomes a no-op (token mismatch).
|
|
439
|
+
*/
|
|
440
|
+
forceReleaseLock(threadId: string): Promise<void>;
|
|
409
441
|
/** Get a cached value by key */
|
|
410
442
|
get<T = unknown>(key: string): Promise<T | null>;
|
|
411
443
|
/** Check if subscribed to a thread */
|
|
@@ -456,7 +488,30 @@ interface Postable<TState = Record<string, unknown>, TRawMessage = unknown> {
|
|
|
456
488
|
/**
|
|
457
489
|
* Post an ephemeral message visible only to a specific user.
|
|
458
490
|
*/
|
|
459
|
-
postEphemeral(user: string | Author, message: AdapterPostableMessage | ChatElement, options: PostEphemeralOptions): Promise<EphemeralMessage | null>;
|
|
491
|
+
postEphemeral(user: string | Author, message: AdapterPostableMessage | ChatElement, options: PostEphemeralOptions): Promise<EphemeralMessage<TRawMessage> | null>;
|
|
492
|
+
/**
|
|
493
|
+
* Schedule a message for future delivery.
|
|
494
|
+
*
|
|
495
|
+
* Currently only supported by the Slack adapter. Other adapters
|
|
496
|
+
* will throw NotImplementedError.
|
|
497
|
+
*
|
|
498
|
+
* @param message - The message content (streaming not supported)
|
|
499
|
+
* @param options - Scheduling options including the target delivery time
|
|
500
|
+
* @returns A ScheduledMessage with cancel() capability
|
|
501
|
+
*
|
|
502
|
+
* @example
|
|
503
|
+
* ```typescript
|
|
504
|
+
* const scheduled = await thread.schedule("Reminder: standup!", {
|
|
505
|
+
* postAt: new Date("2026-03-09T09:00:00Z"),
|
|
506
|
+
* });
|
|
507
|
+
*
|
|
508
|
+
* // Cancel before it's sent
|
|
509
|
+
* await scheduled.cancel();
|
|
510
|
+
* ```
|
|
511
|
+
*/
|
|
512
|
+
schedule(message: AdapterPostableMessage | ChatElement, options: {
|
|
513
|
+
postAt: Date;
|
|
514
|
+
}): Promise<ScheduledMessage<TRawMessage>>;
|
|
460
515
|
/**
|
|
461
516
|
* Set the state. Merges with existing state by default.
|
|
462
517
|
*/
|
|
@@ -630,7 +685,7 @@ interface Thread<TState = Record<string, unknown>, TRawMessage = unknown> extend
|
|
|
630
685
|
* }
|
|
631
686
|
* ```
|
|
632
687
|
*/
|
|
633
|
-
postEphemeral(user: string | Author, message: AdapterPostableMessage | ChatElement, options: PostEphemeralOptions): Promise<EphemeralMessage | null>;
|
|
688
|
+
postEphemeral(user: string | Author, message: AdapterPostableMessage | ChatElement, options: PostEphemeralOptions): Promise<EphemeralMessage<TRawMessage> | null>;
|
|
634
689
|
/** Recently fetched messages (cached) */
|
|
635
690
|
recentMessages: Message<TRawMessage>[];
|
|
636
691
|
/**
|
|
@@ -796,11 +851,11 @@ interface SentMessage<TRawMessage = unknown> extends Message<TRawMessage> {
|
|
|
796
851
|
* Ephemeral messages are visible only to a specific user and typically
|
|
797
852
|
* cannot be edited or deleted (platform-dependent).
|
|
798
853
|
*/
|
|
799
|
-
interface EphemeralMessage {
|
|
854
|
+
interface EphemeralMessage<TRawMessage = unknown> {
|
|
800
855
|
/** Message ID (may be empty for some platforms) */
|
|
801
856
|
id: string;
|
|
802
857
|
/** Platform-specific raw response */
|
|
803
|
-
raw:
|
|
858
|
+
raw: TRawMessage;
|
|
804
859
|
/** Thread ID where message was sent (or DM thread if fallback was used) */
|
|
805
860
|
threadId: string;
|
|
806
861
|
/** Whether this used native ephemeral or fell back to DM */
|
|
@@ -818,6 +873,24 @@ interface PostEphemeralOptions {
|
|
|
818
873
|
*/
|
|
819
874
|
fallbackToDM: boolean;
|
|
820
875
|
}
|
|
876
|
+
/**
|
|
877
|
+
* Result of scheduling a message for future delivery.
|
|
878
|
+
*
|
|
879
|
+
* Currently only supported by the Slack adapter via `chat.scheduleMessage`.
|
|
880
|
+
* Other adapters will throw `NotImplementedError` when `schedule()` is called.
|
|
881
|
+
*/
|
|
882
|
+
interface ScheduledMessage<TRawMessage = unknown> {
|
|
883
|
+
/** Cancel the scheduled message before it's sent */
|
|
884
|
+
cancel(): Promise<void>;
|
|
885
|
+
/** Channel ID where the message will be posted */
|
|
886
|
+
channelId: string;
|
|
887
|
+
/** When the message will be sent */
|
|
888
|
+
postAt: Date;
|
|
889
|
+
/** Platform-specific raw response */
|
|
890
|
+
raw: TRawMessage;
|
|
891
|
+
/** Platform-specific scheduled message ID */
|
|
892
|
+
scheduledMessageId: string;
|
|
893
|
+
}
|
|
821
894
|
/**
|
|
822
895
|
* Input type for adapter postMessage/editMessage methods.
|
|
823
896
|
* This excludes streams since adapters handle content synchronously.
|
|
@@ -1576,6 +1649,9 @@ declare class ChannelImpl<TState = Record<string, unknown>> implements Channel<T
|
|
|
1576
1649
|
post(message: string | PostableMessage | ChatElement): Promise<SentMessage>;
|
|
1577
1650
|
private postSingleMessage;
|
|
1578
1651
|
postEphemeral(user: string | Author, message: AdapterPostableMessage | ChatElement, options: PostEphemeralOptions): Promise<EphemeralMessage | null>;
|
|
1652
|
+
schedule(message: AdapterPostableMessage | ChatElement, options: {
|
|
1653
|
+
postAt: Date;
|
|
1654
|
+
}): Promise<ScheduledMessage>;
|
|
1579
1655
|
startTyping(status?: string): Promise<void>;
|
|
1580
1656
|
mentionUser(userId: string): string;
|
|
1581
1657
|
toJSON(): SerializedChannel;
|
|
@@ -1586,8 +1662,6 @@ declare class ChannelImpl<TState = Record<string, unknown>> implements Channel<T
|
|
|
1586
1662
|
}
|
|
1587
1663
|
/**
|
|
1588
1664
|
* Derive the channel ID from a thread ID.
|
|
1589
|
-
* Uses adapter.channelIdFromThreadId if available, otherwise defaults to
|
|
1590
|
-
* first two colon-separated parts.
|
|
1591
1665
|
*/
|
|
1592
1666
|
declare function deriveChannelId(adapter: Adapter, threadId: string): string;
|
|
1593
1667
|
|
|
@@ -1662,6 +1736,7 @@ declare class Chat<TAdapters extends Record<string, Adapter> = Record<string, Ad
|
|
|
1662
1736
|
private readonly _streamingUpdateIntervalMs;
|
|
1663
1737
|
private readonly _fallbackStreamingPlaceholderText;
|
|
1664
1738
|
private readonly _dedupeTtlMs;
|
|
1739
|
+
private readonly _onLockConflict;
|
|
1665
1740
|
private readonly mentionHandlers;
|
|
1666
1741
|
private readonly messagePatterns;
|
|
1667
1742
|
private readonly subscribedMessageHandlers;
|
|
@@ -2203,6 +2278,9 @@ declare class ThreadImpl<TState = Record<string, unknown>> implements Thread<TSt
|
|
|
2203
2278
|
unsubscribe(): Promise<void>;
|
|
2204
2279
|
post(message: string | PostableMessage | ChatElement): Promise<SentMessage>;
|
|
2205
2280
|
postEphemeral(user: string | Author, message: AdapterPostableMessage | ChatElement, options: PostEphemeralOptions): Promise<EphemeralMessage | null>;
|
|
2281
|
+
schedule(message: AdapterPostableMessage | ChatElement, options: {
|
|
2282
|
+
postAt: Date;
|
|
2283
|
+
}): Promise<ScheduledMessage>;
|
|
2206
2284
|
/**
|
|
2207
2285
|
* Handle streaming from an AsyncIterable.
|
|
2208
2286
|
* Normalizes the stream (supports both textStream and fullStream from AI SDK),
|
|
@@ -2697,4 +2775,4 @@ declare const Select: SelectComponent;
|
|
|
2697
2775
|
declare const SelectOption: SelectOptionComponent;
|
|
2698
2776
|
declare const TextInput: TextInputComponent;
|
|
2699
2777
|
|
|
2700
|
-
export { type ActionEvent, type ActionHandler, Actions, ActionsComponent, type Adapter, type AdapterPostableMessage, type AppHomeOpenedEvent, type AppHomeOpenedHandler, type AssistantContextChangedEvent, type AssistantContextChangedHandler, type AssistantThreadStartedEvent, type AssistantThreadStartedHandler, type Attachment, type Author, BaseFormatConverter, Button, ButtonComponent, Card, CardChild, CardComponent, CardElement, CardLink, CardLinkComponent, CardText, type Channel, ChannelImpl, type ChannelInfo, Chat, type ChatConfig, ChatElement, ChatError, type ChatInstance, ConsoleLogger, type CustomEmojiMap, DEFAULT_EMOJI_MAP, Divider, DividerComponent, type Emoji, type EmojiFormats, type EmojiMapConfig, EmojiResolver, type EmojiValue, type EphemeralMessage, type FetchDirection, type FetchOptions, type FetchResult, Field, FieldComponent, Fields, FieldsComponent, type FileUpload, type FormatConverter, type FormattedContent, Image, ImageComponent, LinkButton, LinkButtonComponent, type ListThreadsOptions, type ListThreadsResult, type Lock, LockError, type LogLevel, type Logger, type MarkdownConverter, type MarkdownTextChunk, type MemberJoinedChannelEvent, type MemberJoinedChannelHandler, type MentionHandler, Message, type MessageData, type MessageHandler, type MessageMetadata, Modal, type ModalCloseEvent, type ModalCloseHandler, type ModalCloseResponse, ModalComponent, ModalElement, type ModalErrorsResponse, type ModalPushResponse, type ModalResponse, type ModalSubmitEvent, type ModalSubmitHandler, type ModalUpdateResponse, NotImplementedError, type PlanUpdateChunk, type PostEphemeralOptions, type Postable, type PostableAst, type PostableCard, type PostableMarkdown, type PostableMessage, type PostableRaw, RadioSelect, RadioSelectComponent, RateLimitError, type RawMessage, type ReactionEvent, type ReactionHandler, Section, SectionComponent, Select, SelectComponent, SelectOption, SelectOptionComponent, type SentMessage, type SerializedChannel, type SerializedMessage, type SerializedThread, type SlashCommandEvent, type SlashCommandHandler, type StateAdapter, type StreamChunk, type StreamEvent, type StreamOptions, StreamingMarkdownRenderer, type SubscribedMessageHandler, THREAD_STATE_TTL_MS, Table, type TaskUpdateChunk, TextComponent, TextInput, TextInputComponent, type Thread, ThreadImpl, type ThreadInfo, type ThreadSummary, type WebhookOptions, type WellKnownEmoji, blockquote, cardChildToFallbackText, codeBlock, convertEmojiPlaceholders, createEmoji, defaultEmojiResolver, deriveChannelId, emoji, emphasis, fromFullStream, fromReactElement, fromReactModalElement, getEmoji, getNodeChildren, getNodeValue, inlineCode, isBlockquoteNode, isCardElement, isCodeNode, isDeleteNode, isEmphasisNode, isInlineCodeNode, isJSX, isLinkNode, isListItemNode, isListNode, isModalElement, isParagraphNode, isStrongNode, isTableCellNode, isTableNode, isTableRowNode, isTextNode, link, markdownToPlainText, paragraph, parseMarkdown, root, strikethrough, stringifyMarkdown, strong, tableElementToAscii, tableToAscii, text, toCardElement, toModalElement, toPlainText, walkAst };
|
|
2778
|
+
export { type ActionEvent, type ActionHandler, Actions, ActionsComponent, type Adapter, type AdapterPostableMessage, type AppHomeOpenedEvent, type AppHomeOpenedHandler, type AssistantContextChangedEvent, type AssistantContextChangedHandler, type AssistantThreadStartedEvent, type AssistantThreadStartedHandler, type Attachment, type Author, BaseFormatConverter, Button, ButtonComponent, Card, CardChild, CardComponent, CardElement, CardLink, CardLinkComponent, CardText, type Channel, ChannelImpl, type ChannelInfo, Chat, type ChatConfig, ChatElement, ChatError, type ChatInstance, ConsoleLogger, type CustomEmojiMap, DEFAULT_EMOJI_MAP, Divider, DividerComponent, type Emoji, type EmojiFormats, type EmojiMapConfig, EmojiResolver, type EmojiValue, type EphemeralMessage, type FetchDirection, type FetchOptions, type FetchResult, Field, FieldComponent, Fields, FieldsComponent, type FileUpload, type FormatConverter, type FormattedContent, Image, ImageComponent, LinkButton, LinkButtonComponent, type ListThreadsOptions, type ListThreadsResult, type Lock, LockError, type LogLevel, type Logger, type MarkdownConverter, type MarkdownTextChunk, type MemberJoinedChannelEvent, type MemberJoinedChannelHandler, type MentionHandler, Message, type MessageData, type MessageHandler, type MessageMetadata, Modal, type ModalCloseEvent, type ModalCloseHandler, type ModalCloseResponse, ModalComponent, ModalElement, type ModalErrorsResponse, type ModalPushResponse, type ModalResponse, type ModalSubmitEvent, type ModalSubmitHandler, type ModalUpdateResponse, NotImplementedError, type PlanUpdateChunk, type PostEphemeralOptions, type Postable, type PostableAst, type PostableCard, type PostableMarkdown, type PostableMessage, type PostableRaw, RadioSelect, RadioSelectComponent, RateLimitError, type RawMessage, type ReactionEvent, type ReactionHandler, type ScheduledMessage, Section, SectionComponent, Select, SelectComponent, SelectOption, SelectOptionComponent, type SentMessage, type SerializedChannel, type SerializedMessage, type SerializedThread, type SlashCommandEvent, type SlashCommandHandler, type StateAdapter, type StreamChunk, type StreamEvent, type StreamOptions, StreamingMarkdownRenderer, type SubscribedMessageHandler, THREAD_STATE_TTL_MS, Table, type TaskUpdateChunk, TextComponent, TextInput, TextInputComponent, type Thread, ThreadImpl, type ThreadInfo, type ThreadSummary, type WebhookOptions, type WellKnownEmoji, blockquote, cardChildToFallbackText, codeBlock, convertEmojiPlaceholders, createEmoji, defaultEmojiResolver, deriveChannelId, emoji, emphasis, fromFullStream, fromReactElement, fromReactModalElement, getEmoji, getNodeChildren, getNodeValue, inlineCode, isBlockquoteNode, isCardElement, isCodeNode, isDeleteNode, isEmphasisNode, isInlineCodeNode, isJSX, isLinkNode, isListItemNode, isListNode, isModalElement, isParagraphNode, isStrongNode, isTableCellNode, isTableNode, isTableRowNode, isTextNode, link, markdownToPlainText, paragraph, parseMarkdown, root, strikethrough, stringifyMarkdown, strong, tableElementToAscii, tableToAscii, text, toCardElement, toModalElement, toPlainText, walkAst };
|
package/dist/index.js
CHANGED
|
@@ -472,6 +472,25 @@ var ChannelImpl = class _ChannelImpl {
|
|
|
472
472
|
}
|
|
473
473
|
return null;
|
|
474
474
|
}
|
|
475
|
+
async schedule(message, options) {
|
|
476
|
+
let postable;
|
|
477
|
+
if (isJSX(message)) {
|
|
478
|
+
const card = toCardElement(message);
|
|
479
|
+
if (!card) {
|
|
480
|
+
throw new Error("Invalid JSX element: must be a Card element");
|
|
481
|
+
}
|
|
482
|
+
postable = card;
|
|
483
|
+
} else {
|
|
484
|
+
postable = message;
|
|
485
|
+
}
|
|
486
|
+
if (!this.adapter.scheduleMessage) {
|
|
487
|
+
throw new NotImplementedError(
|
|
488
|
+
"Scheduled messages are not supported by this adapter",
|
|
489
|
+
"scheduling"
|
|
490
|
+
);
|
|
491
|
+
}
|
|
492
|
+
return this.adapter.scheduleMessage(this.id, postable, options);
|
|
493
|
+
}
|
|
475
494
|
async startTyping(status) {
|
|
476
495
|
await this.adapter.startTyping(this.id, status);
|
|
477
496
|
}
|
|
@@ -555,11 +574,7 @@ var ChannelImpl = class _ChannelImpl {
|
|
|
555
574
|
}
|
|
556
575
|
};
|
|
557
576
|
function deriveChannelId(adapter, threadId) {
|
|
558
|
-
|
|
559
|
-
return adapter.channelIdFromThreadId(threadId);
|
|
560
|
-
}
|
|
561
|
-
const parts = threadId.split(":");
|
|
562
|
-
return parts.slice(0, 2).join(":");
|
|
577
|
+
return adapter.channelIdFromThreadId(threadId);
|
|
563
578
|
}
|
|
564
579
|
function extractMessageContent(message) {
|
|
565
580
|
if (typeof message === "string") {
|
|
@@ -1114,6 +1129,25 @@ var ThreadImpl = class _ThreadImpl {
|
|
|
1114
1129
|
}
|
|
1115
1130
|
return null;
|
|
1116
1131
|
}
|
|
1132
|
+
async schedule(message, options) {
|
|
1133
|
+
let postable;
|
|
1134
|
+
if (isJSX(message)) {
|
|
1135
|
+
const card = toCardElement(message);
|
|
1136
|
+
if (!card) {
|
|
1137
|
+
throw new Error("Invalid JSX element: must be a Card element");
|
|
1138
|
+
}
|
|
1139
|
+
postable = card;
|
|
1140
|
+
} else {
|
|
1141
|
+
postable = message;
|
|
1142
|
+
}
|
|
1143
|
+
if (!this.adapter.scheduleMessage) {
|
|
1144
|
+
throw new NotImplementedError(
|
|
1145
|
+
"Scheduled messages are not supported by this adapter",
|
|
1146
|
+
"scheduling"
|
|
1147
|
+
);
|
|
1148
|
+
}
|
|
1149
|
+
return this.adapter.scheduleMessage(this.id, postable, options);
|
|
1150
|
+
}
|
|
1117
1151
|
/**
|
|
1118
1152
|
* Handle streaming from an AsyncIterable.
|
|
1119
1153
|
* Normalizes the stream (supports both textStream and fullStream from AI SDK),
|
|
@@ -1529,6 +1563,7 @@ var Chat = class {
|
|
|
1529
1563
|
_streamingUpdateIntervalMs;
|
|
1530
1564
|
_fallbackStreamingPlaceholderText;
|
|
1531
1565
|
_dedupeTtlMs;
|
|
1566
|
+
_onLockConflict;
|
|
1532
1567
|
mentionHandlers = [];
|
|
1533
1568
|
messagePatterns = [];
|
|
1534
1569
|
subscribedMessageHandlers = [];
|
|
@@ -1557,6 +1592,7 @@ var Chat = class {
|
|
|
1557
1592
|
this._streamingUpdateIntervalMs = config.streamingUpdateIntervalMs ?? 500;
|
|
1558
1593
|
this._fallbackStreamingPlaceholderText = config.fallbackStreamingPlaceholderText !== void 0 ? config.fallbackStreamingPlaceholderText : "...";
|
|
1559
1594
|
this._dedupeTtlMs = config.dedupeTtlMs ?? DEDUPE_TTL_MS;
|
|
1595
|
+
this._onLockConflict = config.onLockConflict;
|
|
1560
1596
|
if (typeof config.logger === "string") {
|
|
1561
1597
|
this.logger = new ConsoleLogger(config.logger);
|
|
1562
1598
|
} else {
|
|
@@ -2489,15 +2525,26 @@ var Chat = class {
|
|
|
2489
2525
|
});
|
|
2490
2526
|
return;
|
|
2491
2527
|
}
|
|
2492
|
-
|
|
2528
|
+
let lock = await this._stateAdapter.acquireLock(
|
|
2493
2529
|
threadId,
|
|
2494
2530
|
DEFAULT_LOCK_TTL_MS
|
|
2495
2531
|
);
|
|
2496
2532
|
if (!lock) {
|
|
2497
|
-
this.
|
|
2498
|
-
|
|
2499
|
-
|
|
2500
|
-
|
|
2533
|
+
const resolution = typeof this._onLockConflict === "function" ? await this._onLockConflict(threadId, message) : this._onLockConflict ?? "drop";
|
|
2534
|
+
if (resolution === "force") {
|
|
2535
|
+
this.logger.info("Force-releasing lock on thread", { threadId });
|
|
2536
|
+
await this._stateAdapter.forceReleaseLock(threadId);
|
|
2537
|
+
lock = await this._stateAdapter.acquireLock(
|
|
2538
|
+
threadId,
|
|
2539
|
+
DEFAULT_LOCK_TTL_MS
|
|
2540
|
+
);
|
|
2541
|
+
}
|
|
2542
|
+
if (!lock) {
|
|
2543
|
+
this.logger.warn("Could not acquire lock on thread", { threadId });
|
|
2544
|
+
throw new LockError(
|
|
2545
|
+
`Could not acquire lock on thread ${threadId}. Another instance may be processing.`
|
|
2546
|
+
);
|
|
2547
|
+
}
|
|
2501
2548
|
}
|
|
2502
2549
|
this.logger.debug("Lock acquired", { threadId, token: lock.token });
|
|
2503
2550
|
try {
|