chat 4.16.0 → 4.17.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/dist/index.js CHANGED
@@ -59,7 +59,7 @@ import {
59
59
  toModalElement,
60
60
  toPlainText,
61
61
  walkAst
62
- } from "./chunk-7S5DLTN2.js";
62
+ } from "./chunk-Q32GJ2PR.js";
63
63
 
64
64
  // src/channel.ts
65
65
  import { WORKFLOW_DESERIALIZE as WORKFLOW_DESERIALIZE2, WORKFLOW_SERIALIZE as WORKFLOW_SERIALIZE2 } from "@workflow/serde";
@@ -613,6 +613,41 @@ function extractMessageContent(message) {
613
613
  // src/thread.ts
614
614
  import { WORKFLOW_DESERIALIZE as WORKFLOW_DESERIALIZE3, WORKFLOW_SERIALIZE as WORKFLOW_SERIALIZE3 } from "@workflow/serde";
615
615
 
616
+ // src/from-full-stream.ts
617
+ var STREAM_CHUNK_TYPES = /* @__PURE__ */ new Set([
618
+ "markdown_text",
619
+ "task_update",
620
+ "plan_update"
621
+ ]);
622
+ async function* fromFullStream(stream) {
623
+ let needsSeparator = false;
624
+ let hasEmittedText = false;
625
+ for await (const event of stream) {
626
+ if (typeof event === "string") {
627
+ yield event;
628
+ continue;
629
+ }
630
+ if (event === null || typeof event !== "object" || !("type" in event)) {
631
+ continue;
632
+ }
633
+ const typed = event;
634
+ if (STREAM_CHUNK_TYPES.has(typed.type)) {
635
+ yield event;
636
+ continue;
637
+ }
638
+ if (typed.type === "text-delta" && typeof typed.textDelta === "string") {
639
+ if (needsSeparator && hasEmittedText) {
640
+ yield "\n\n";
641
+ }
642
+ needsSeparator = false;
643
+ hasEmittedText = true;
644
+ yield typed.textDelta;
645
+ } else if (typed.type === "step-finish") {
646
+ needsSeparator = true;
647
+ }
648
+ }
649
+ }
650
+
616
651
  // src/streaming-markdown.ts
617
652
  import remend from "remend";
618
653
  var StreamingMarkdownRenderer = class {
@@ -1080,9 +1115,11 @@ var ThreadImpl = class _ThreadImpl {
1080
1115
  }
1081
1116
  /**
1082
1117
  * Handle streaming from an AsyncIterable.
1083
- * Uses adapter's native streaming if available, otherwise falls back to post+edit.
1118
+ * Normalizes the stream (supports both textStream and fullStream from AI SDK),
1119
+ * then uses adapter's native streaming if available, otherwise falls back to post+edit.
1084
1120
  */
1085
- async handleStream(textStream) {
1121
+ async handleStream(rawStream) {
1122
+ const textStream = fromFullStream(rawStream);
1086
1123
  const options = {};
1087
1124
  if (this._currentMessage) {
1088
1125
  options.recipientUserId = this._currentMessage.author.userId;
@@ -1098,7 +1135,12 @@ var ThreadImpl = class _ThreadImpl {
1098
1135
  async next() {
1099
1136
  const result = await iterator.next();
1100
1137
  if (!result.done) {
1101
- accumulated += result.value;
1138
+ const value = result.value;
1139
+ if (typeof value === "string") {
1140
+ accumulated += value;
1141
+ } else if (value.type === "markdown_text") {
1142
+ accumulated += value.text;
1143
+ }
1102
1144
  }
1103
1145
  return result;
1104
1146
  }
@@ -1112,7 +1154,29 @@ var ThreadImpl = class _ThreadImpl {
1112
1154
  raw.threadId
1113
1155
  );
1114
1156
  }
1115
- return this.fallbackStream(textStream, options);
1157
+ const textOnlyStream = {
1158
+ [Symbol.asyncIterator]: () => {
1159
+ const iterator = textStream[Symbol.asyncIterator]();
1160
+ return {
1161
+ async next() {
1162
+ while (true) {
1163
+ const result = await iterator.next();
1164
+ if (result.done) {
1165
+ return { value: void 0, done: true };
1166
+ }
1167
+ const value = result.value;
1168
+ if (typeof value === "string") {
1169
+ return { value, done: false };
1170
+ }
1171
+ if (value.type === "markdown_text") {
1172
+ return { value: value.text, done: false };
1173
+ }
1174
+ }
1175
+ }
1176
+ };
1177
+ }
1178
+ };
1179
+ return this.fallbackStream(textOnlyStream, options);
1116
1180
  }
1117
1181
  async startTyping(status) {
1118
1182
  await this.adapter.startTyping(this.id, status);
@@ -2131,30 +2195,32 @@ var Chat = class {
2131
2195
  }
2132
2196
  modalElement = converted;
2133
2197
  }
2134
- const isEphemeralMessage = event.messageId?.startsWith("ephemeral:");
2135
2198
  let message;
2136
- if (isEphemeralMessage) {
2137
- const recentMessage = thread.recentMessages[0];
2138
- if (recentMessage && typeof recentMessage.toJSON === "function") {
2139
- message = recentMessage;
2140
- }
2141
- } else if (event.messageId && event.adapter.fetchMessage) {
2142
- const fetched = await event.adapter.fetchMessage(event.threadId, event.messageId).catch(() => null);
2143
- if (fetched) {
2144
- message = new Message(fetched);
2145
- } else {
2199
+ if (thread) {
2200
+ const isEphemeralMessage = event.messageId?.startsWith("ephemeral:");
2201
+ if (isEphemeralMessage) {
2146
2202
  const recentMessage = thread.recentMessages[0];
2147
2203
  if (recentMessage && typeof recentMessage.toJSON === "function") {
2148
2204
  message = recentMessage;
2149
2205
  }
2206
+ } else if (event.messageId && event.adapter.fetchMessage) {
2207
+ const fetched = await event.adapter.fetchMessage(event.threadId, event.messageId).catch(() => null);
2208
+ if (fetched) {
2209
+ message = new Message(fetched);
2210
+ } else {
2211
+ const recentMessage = thread.recentMessages[0];
2212
+ if (recentMessage && typeof recentMessage.toJSON === "function") {
2213
+ message = recentMessage;
2214
+ }
2215
+ }
2150
2216
  }
2151
2217
  }
2152
2218
  const contextId = crypto.randomUUID();
2153
- const channel = thread.channel;
2219
+ const channel = thread ? thread.channel : void 0;
2154
2220
  this.storeModalContext(
2155
2221
  event.adapter.name,
2156
2222
  contextId,
2157
- thread,
2223
+ thread ? thread : void 0,
2158
2224
  message,
2159
2225
  channel
2160
2226
  );
@@ -2410,15 +2476,18 @@ var Chat = class {
2410
2476
  return;
2411
2477
  }
2412
2478
  const dedupeKey = `dedupe:${adapter.name}:${message.id}`;
2413
- const alreadyProcessed = await this._stateAdapter.get(dedupeKey);
2414
- if (alreadyProcessed) {
2479
+ const isFirstProcess = await this._stateAdapter.setIfNotExists(
2480
+ dedupeKey,
2481
+ true,
2482
+ this._dedupeTtlMs
2483
+ );
2484
+ if (!isFirstProcess) {
2415
2485
  this.logger.debug("Skipping duplicate message", {
2416
2486
  adapter: adapter.name,
2417
2487
  messageId: message.id
2418
2488
  });
2419
2489
  return;
2420
2490
  }
2421
- await this._stateAdapter.set(dedupeKey, true, this._dedupeTtlMs);
2422
2491
  const lock = await this._stateAdapter.acquireLock(
2423
2492
  threadId,
2424
2493
  DEFAULT_LOCK_TTL_MS
@@ -3005,6 +3074,7 @@ export {
3005
3074
  deriveChannelId,
3006
3075
  emoji,
3007
3076
  emphasis,
3077
+ fromFullStream,
3008
3078
  fromReactElement2 as fromReactElement,
3009
3079
  fromReactModalElement2 as fromReactModalElement,
3010
3080
  getEmoji,