Skip to content

Commit fba5642

Browse files
committed
feat(adapter): enhance error handling in state subscription by logging errors from listeners
1 parent e5e28db commit fba5642

2 files changed

Lines changed: 50 additions & 2 deletions

File tree

packages/kit/src/message/adapters/shared.ts

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,14 @@ const normalizeKinds = (kindsOrListener: MessageUpdateKinds | ((currentState: Pu
2020
export const createStateSubscriptionController = (getState: () => PublicMessageState) => {
2121
const listeners = new Set<ListenerEntry>()
2222

23+
const dispatch = (entry: ListenerEntry, snapshot: PublicMessageState) => {
24+
try {
25+
entry.listener(snapshot)
26+
} catch (error) {
27+
console.error('Error in message state subscriber:', error)
28+
}
29+
}
30+
2331
const notify = (kindsOrKind: MessageUpdateKinds) => {
2432
const kinds = new Set(Array.isArray(kindsOrKind) ? kindsOrKind : [kindsOrKind])
2533
const snapshot = getState()
@@ -40,7 +48,7 @@ export const createStateSubscriptionController = (getState: () => PublicMessageS
4048
}
4149
}
4250

43-
entry.listener(snapshot)
51+
dispatch(entry, snapshot)
4452
}
4553
}
4654

@@ -60,7 +68,7 @@ export const createStateSubscriptionController = (getState: () => PublicMessageS
6068
}
6169

6270
listeners.add(entry)
63-
listener(getState())
71+
dispatch(entry, getState())
6472

6573
return () => {
6674
listeners.delete(entry)

packages/kit/src/message/test/native.test.ts

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -145,6 +145,46 @@ describe('createMessageEngine', () => {
145145
expect(snapshotsForRequestState).toHaveLength(expectedRequestStateSequence.length)
146146
})
147147

148+
it('keeps notifying other subscribers when one subscriber throws', () => {
149+
const adapter = createNativeMessageAdapter()
150+
adapter.initialize({
151+
requestState: 'idle',
152+
processingState: undefined,
153+
messages: [],
154+
})
155+
156+
const errorSpy = vi.spyOn(console, 'error').mockImplementation(() => {})
157+
const healthyListener = vi.fn()
158+
159+
adapter.subscribe('requestState', () => {
160+
throw new Error('subscriber failed')
161+
})
162+
adapter.subscribe('requestState', healthyListener)
163+
164+
adapter.mutate('requestState', (draft) => {
165+
draft.requestState = 'processing'
166+
draft.processingState = 'requesting'
167+
})
168+
169+
expect(healthyListener).toHaveBeenNthCalledWith(
170+
1,
171+
expect.objectContaining({
172+
requestState: 'idle',
173+
processingState: undefined,
174+
}),
175+
)
176+
expect(healthyListener).toHaveBeenNthCalledWith(
177+
2,
178+
expect.objectContaining({
179+
requestState: 'processing',
180+
processingState: 'requesting',
181+
}),
182+
)
183+
expect(errorSpy).toHaveBeenCalled()
184+
185+
errorSpy.mockRestore()
186+
})
187+
148188
it('runs onBeforeRequest with current request body before assistant is appended', async () => {
149189
const onBeforeRequest = vi.fn()
150190
const engine = createTestMessageEngine({

0 commit comments

Comments
 (0)