From c93d6cc2dbbf50a8ce6433f2349999f1e91a5c57 Mon Sep 17 00:00:00 2001 From: Paul Jakob Kroker Date: Mon, 22 Jun 2026 14:05:42 +0200 Subject: [PATCH 1/5] fix(bb-agent): add toJSON() to AgentStreamResult MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit AgentStreamResult contains a Promise and a function that don't survive JSON serialization. When returned from an API method, the client received { channelId, channel: {} } — a useless dead object. toJSON() ensures only { channelId } is serialized, making it safe to return agent.stream() directly from ApiNamespace methods. --- packages/bb-agent/src/agent.ts | 1 + packages/bb-agent/src/index.test.ts | 12 ++++++++++++ packages/bb-agent/src/types.ts | 9 ++++++++- 3 files changed, 21 insertions(+), 1 deletion(-) diff --git a/packages/bb-agent/src/agent.ts b/packages/bb-agent/src/agent.ts index 211c81d6..93e7db09 100644 --- a/packages/bb-agent/src/agent.ts +++ b/packages/bb-agent/src/agent.ts @@ -428,6 +428,7 @@ export class AgentBase extends Scope { } }); }), + toJSON() { return { channelId }; }, }; } diff --git a/packages/bb-agent/src/index.test.ts b/packages/bb-agent/src/index.test.ts index 4c053a58..fa962c09 100644 --- a/packages/bb-agent/src/index.test.ts +++ b/packages/bb-agent/src/index.test.ts @@ -115,6 +115,18 @@ describe('needsApproval and interrupt mutual exclusivity', () => { }); }); +// ── AgentStreamResult.toJSON() ─────────────────────────────────────────────── + +describe('AgentStreamResult.toJSON()', () => { + test('serializes to only { channelId }', async () => { + const scope = new Scope('test-tojson'); + const agent = new Agent(scope, 'tj', { systemPrompt: 'test', model: { deployed: { provider: 'canned' }, local: { provider: 'canned' } } }); + const result = await agent.stream('hello', { userId: 'test-user' }); + const serialized = JSON.parse(JSON.stringify(result)); + assert.deepStrictEqual(serialized, { channelId: result.channelId }); + }); +}); + // ── tool factory enforcement (compile-time) ────────────────────────────────── describe('tool factory enforcement', () => { diff --git a/packages/bb-agent/src/types.ts b/packages/bb-agent/src/types.ts index 39f632cd..20998a65 100644 --- a/packages/bb-agent/src/types.ts +++ b/packages/bb-agent/src/types.ts @@ -264,7 +264,12 @@ export interface StreamOptions { context?: TContext; } -/** Returned by stream(). Provides the channelId and server-side convenience methods. */ +/** + * Returned by stream(). Provides the channelId and server-side convenience methods. + * + * Safe to return directly from API methods — `toJSON()` ensures only `{ channelId }` + * is serialized over the wire. The `channel` and `complete()` helpers are server-side only. + */ export interface AgentStreamResult { /** Realtime channel ID where chunks are published. */ channelId: string; @@ -272,6 +277,8 @@ export interface AgentStreamResult { channel: Promise>; /** Wait for the complete response (server-side). Resolves when the done chunk arrives. */ complete: () => Promise; + /** Only `{ channelId }` is serialized when this object crosses the RPC boundary. */ + toJSON(): { channelId: string }; } export interface AgentStreamChunk { From 8d105dbf9d4b5a54e87bc5606d6aaaa714f0de12 Mon Sep 17 00:00:00 2001 From: Paul Jakob Kroker Date: Mon, 22 Jun 2026 17:15:33 +0200 Subject: [PATCH 2/5] chore: add changeset for AgentStreamResult toJSON --- .changeset/fix-bb-agent-stream-result-tojson.md | 7 +++++++ 1 file changed, 7 insertions(+) create mode 100644 .changeset/fix-bb-agent-stream-result-tojson.md diff --git a/.changeset/fix-bb-agent-stream-result-tojson.md b/.changeset/fix-bb-agent-stream-result-tojson.md new file mode 100644 index 00000000..b20f903f --- /dev/null +++ b/.changeset/fix-bb-agent-stream-result-tojson.md @@ -0,0 +1,7 @@ +--- +"@aws-blocks/bb-agent": patch +--- + +fix(bb-agent): add toJSON() to AgentStreamResult + +`AgentStreamResult` now serializes cleanly to `{ channelId }` when returned from API methods. The `channel` (Promise) and `complete()` (function) no longer produce unusable values on the client. From 030baffa447e6c333d4af84546c3755b14d3c34d Mon Sep 17 00:00:00 2001 From: Paul Jakob Kroker Date: Mon, 22 Jun 2026 17:20:23 +0200 Subject: [PATCH 3/5] fix(bb-agent): toJSON serializes channel as null for backward compat --- .changeset/fix-bb-agent-stream-result-tojson.md | 2 +- packages/bb-agent/API.md | 4 ++++ packages/bb-agent/src/agent.ts | 2 +- packages/bb-agent/src/index.test.ts | 2 +- packages/bb-agent/src/types.ts | 4 ++-- 5 files changed, 9 insertions(+), 5 deletions(-) diff --git a/.changeset/fix-bb-agent-stream-result-tojson.md b/.changeset/fix-bb-agent-stream-result-tojson.md index b20f903f..4a4cfc1f 100644 --- a/.changeset/fix-bb-agent-stream-result-tojson.md +++ b/.changeset/fix-bb-agent-stream-result-tojson.md @@ -4,4 +4,4 @@ fix(bb-agent): add toJSON() to AgentStreamResult -`AgentStreamResult` now serializes cleanly to `{ channelId }` when returned from API methods. The `channel` (Promise) and `complete()` (function) no longer produce unusable values on the client. +`AgentStreamResult` now serializes to `{ channelId, channel: null }` when returned from API methods. Previously `channel` serialized to an empty object `{}`; it is now explicitly `null` to signal it is server-side only. diff --git a/packages/bb-agent/API.md b/packages/bb-agent/API.md index 8b90feb1..85464c55 100644 --- a/packages/bb-agent/API.md +++ b/packages/bb-agent/API.md @@ -93,6 +93,10 @@ export interface AgentStreamResult { channel: Promise>; channelId: string; complete: () => Promise; + toJSON(): { + channelId: string; + channel: null; + }; } // @public diff --git a/packages/bb-agent/src/agent.ts b/packages/bb-agent/src/agent.ts index 93e7db09..b281118e 100644 --- a/packages/bb-agent/src/agent.ts +++ b/packages/bb-agent/src/agent.ts @@ -428,7 +428,7 @@ export class AgentBase extends Scope { } }); }), - toJSON() { return { channelId }; }, + toJSON() { return { channelId, channel: null }; }, }; } diff --git a/packages/bb-agent/src/index.test.ts b/packages/bb-agent/src/index.test.ts index fa962c09..e854b088 100644 --- a/packages/bb-agent/src/index.test.ts +++ b/packages/bb-agent/src/index.test.ts @@ -123,7 +123,7 @@ describe('AgentStreamResult.toJSON()', () => { const agent = new Agent(scope, 'tj', { systemPrompt: 'test', model: { deployed: { provider: 'canned' }, local: { provider: 'canned' } } }); const result = await agent.stream('hello', { userId: 'test-user' }); const serialized = JSON.parse(JSON.stringify(result)); - assert.deepStrictEqual(serialized, { channelId: result.channelId }); + assert.deepStrictEqual(serialized, { channelId: result.channelId, channel: null }); }); }); diff --git a/packages/bb-agent/src/types.ts b/packages/bb-agent/src/types.ts index 20998a65..d279e286 100644 --- a/packages/bb-agent/src/types.ts +++ b/packages/bb-agent/src/types.ts @@ -277,8 +277,8 @@ export interface AgentStreamResult { channel: Promise>; /** Wait for the complete response (server-side). Resolves when the done chunk arrives. */ complete: () => Promise; - /** Only `{ channelId }` is serialized when this object crosses the RPC boundary. */ - toJSON(): { channelId: string }; + /** Only `{ channelId, channel: null }` is serialized when this object crosses the RPC boundary. */ + toJSON(): { channelId: string; channel: null }; } export interface AgentStreamChunk { From a2b469566b0c4b55e16484c1300c88750e88c8b2 Mon Sep 17 00:00:00 2001 From: Paul Jakob Kroker Date: Thu, 25 Jun 2026 13:18:37 +0200 Subject: [PATCH 4/5] docs(bb-agent): clarify toJSON serialized shape and harden test Address review feedback on PR #70: - JSDoc now describes the real { channelId, channel: null } output - Document channel field is nulled and how clients reconstruct via useChat subscribe callback - Test title matches asserted shape; assert complete() is dropped from serialization --- packages/bb-agent/src/index.test.ts | 3 ++- packages/bb-agent/src/types.ts | 15 ++++++++++++--- 2 files changed, 14 insertions(+), 4 deletions(-) diff --git a/packages/bb-agent/src/index.test.ts b/packages/bb-agent/src/index.test.ts index e854b088..436f4ebc 100644 --- a/packages/bb-agent/src/index.test.ts +++ b/packages/bb-agent/src/index.test.ts @@ -118,12 +118,13 @@ describe('needsApproval and interrupt mutual exclusivity', () => { // ── AgentStreamResult.toJSON() ─────────────────────────────────────────────── describe('AgentStreamResult.toJSON()', () => { - test('serializes to only { channelId }', async () => { + test('serializes to { channelId, channel: null }', async () => { const scope = new Scope('test-tojson'); const agent = new Agent(scope, 'tj', { systemPrompt: 'test', model: { deployed: { provider: 'canned' }, local: { provider: 'canned' } } }); const result = await agent.stream('hello', { userId: 'test-user' }); const serialized = JSON.parse(JSON.stringify(result)); assert.deepStrictEqual(serialized, { channelId: result.channelId, channel: null }); + assert.strictEqual('complete' in serialized, false); }); }); diff --git a/packages/bb-agent/src/types.ts b/packages/bb-agent/src/types.ts index d279e286..84528ac7 100644 --- a/packages/bb-agent/src/types.ts +++ b/packages/bb-agent/src/types.ts @@ -267,13 +267,22 @@ export interface StreamOptions { /** * Returned by stream(). Provides the channelId and server-side convenience methods. * - * Safe to return directly from API methods — `toJSON()` ensures only `{ channelId }` - * is serialized over the wire. The `channel` and `complete()` helpers are server-side only. +what * Safe to return directly from API methods — `toJSON()` serializes to + * `{ channelId, channel: null }`. Only `channelId` is meaningful client-side; + * `channel` is explicitly `null` to signal the live handle is server-side only, + * and the `complete()` helper is dropped (functions don't serialize). */ export interface AgentStreamResult { /** Realtime channel ID where chunks are published. */ channelId: string; - /** Realtime channel handle — subscribe to streaming chunks or return to client as Transferable. */ + /** + * Realtime channel handle (server-side only). Nulled by `toJSON()` — clients subscribe from `channelId` instead. + * + * @remarks + * Unlike `RealtimeChannel.toJSON()` which produces a hydratable descriptor, this is nulled + * because it's a `Promise` that can't round-trip. Clients reconstruct a subscribe-only + * channel from `channelId` via the `useChat` `subscribe` callback. + */ channel: Promise>; /** Wait for the complete response (server-side). Resolves when the done chunk arrives. */ complete: () => Promise; From 9a1db0abb05b05d200f86e22b0fd61d5b7c1653e Mon Sep 17 00:00:00 2001 From: Paul Jakob Kroker Date: Thu, 25 Jun 2026 14:10:01 +0200 Subject: [PATCH 5/5] docs(bb-agent): remove stray token in AgentStreamResult JSDoc --- packages/bb-agent/src/types.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/bb-agent/src/types.ts b/packages/bb-agent/src/types.ts index 84528ac7..33ff49b7 100644 --- a/packages/bb-agent/src/types.ts +++ b/packages/bb-agent/src/types.ts @@ -267,7 +267,7 @@ export interface StreamOptions { /** * Returned by stream(). Provides the channelId and server-side convenience methods. * -what * Safe to return directly from API methods — `toJSON()` serializes to + * Safe to return directly from API methods — `toJSON()` serializes to * `{ channelId, channel: null }`. Only `channelId` is meaningful client-side; * `channel` is explicitly `null` to signal the live handle is server-side only, * and the `complete()` helper is dropped (functions don't serialize).