Skip to content

Commit 1e6ab15

Browse files
committed
fix(chat): preserve adapterName on deserialized toJSON and add standalone reviver docs
1 parent 6e67499 commit 1e6ab15

5 files changed

Lines changed: 55 additions & 2 deletions

File tree

.changeset/six-dolls-smash.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
"chat": minor
3+
---
4+
5+
export standalone reviver for workflow-safe deserialization without adapter dependencies

apps/docs/content/docs/api/chat.mdx

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -475,3 +475,13 @@ Get a `JSON.parse` reviver that deserializes `Thread` and `Message` objects from
475475
const data = JSON.parse(payload, bot.reviver());
476476
await data.thread.post("Hello from workflow!");
477477
```
478+
479+
There is also a standalone `reviver` export that works without a `Chat` instance. This is useful in Vercel Workflow functions where importing the full Chat instance (with its adapter dependencies) is not possible:
480+
481+
```typescript
482+
import { reviver } from "chat";
483+
484+
const data = JSON.parse(payload, reviver) as { thread: Thread; message: Message };
485+
```
486+
487+
The standalone reviver uses lazy adapter resolution - the adapter is looked up from the Chat singleton when first accessed. Call `chat.registerSingleton()` before using thread methods like `post()` (typically inside a `"use step"` function).

packages/chat/src/channel.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -386,7 +386,7 @@ export class ChannelImpl<TState = Record<string, unknown>>
386386
return {
387387
_type: "chat:Channel",
388388
id: this.id,
389-
adapterName: this.adapter.name,
389+
adapterName: this._adapterName ?? this.adapter.name,
390390
isDM: this.isDM,
391391
};
392392
}

packages/chat/src/serialization.test.ts

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import { WORKFLOW_DESERIALIZE, WORKFLOW_SERIALIZE } from "@workflow/serde";
22
import { afterEach, beforeEach, describe, expect, it } from "vitest";
3+
import { ChannelImpl, type SerializedChannel } from "./channel";
34
import { Chat } from "./chat";
45
import { clearChatSingleton } from "./chat-singleton";
56
import { Message, type SerializedMessage } from "./message";
@@ -827,6 +828,43 @@ describe("Serialization", () => {
827828
expect(parsed.text).toBe("Direct usage");
828829
expect(parsed.metadata.dateSent).toBeInstanceOf(Date);
829830
});
831+
832+
it("should allow re-serialization of a revived Thread without singleton", () => {
833+
const json: SerializedThread = {
834+
_type: "chat:Thread",
835+
id: "slack:C123:1234.5678",
836+
channelId: "C123",
837+
isDM: false,
838+
adapterName: "slack",
839+
};
840+
841+
clearChatSingleton();
842+
843+
const thread = ThreadImpl.fromJSON(json);
844+
const reserialized = thread.toJSON();
845+
846+
expect(reserialized._type).toBe("chat:Thread");
847+
expect(reserialized.adapterName).toBe("slack");
848+
expect(reserialized.id).toBe("slack:C123:1234.5678");
849+
});
850+
851+
it("should allow re-serialization of a revived Channel without singleton", () => {
852+
const json: SerializedChannel = {
853+
_type: "chat:Channel",
854+
id: "C123",
855+
isDM: false,
856+
adapterName: "slack",
857+
};
858+
859+
clearChatSingleton();
860+
861+
const channel = ChannelImpl.fromJSON(json);
862+
const reserialized = channel.toJSON();
863+
864+
expect(reserialized._type).toBe("chat:Channel");
865+
expect(reserialized.adapterName).toBe("slack");
866+
expect(reserialized.id).toBe("C123");
867+
});
830868
});
831869

832870
describe("@workflow/serde integration", () => {

packages/chat/src/thread.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -729,7 +729,7 @@ export class ThreadImpl<TState = Record<string, unknown>>
729729
channelId: this.channelId,
730730
currentMessage: this._currentMessage?.toJSON(),
731731
isDM: this.isDM,
732-
adapterName: this.adapter.name,
732+
adapterName: this._adapterName ?? this.adapter.name,
733733
};
734734
}
735735

0 commit comments

Comments
 (0)