@tambo-ai/react 0.73.0 → 0.74.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 +12 -12
- package/dist/hooks/use-component-state.d.ts +1 -1
- package/dist/hooks/use-component-state.js.map +1 -1
- package/dist/hooks/use-streaming-props.d.ts +1 -1
- package/dist/hooks/use-streaming-props.js +1 -1
- package/dist/hooks/use-streaming-props.js.map +1 -1
- package/dist/hooks/use-tambo-stream-status.d.ts +1 -1
- package/dist/hooks/use-tambo-stream-status.js +1 -1
- package/dist/hooks/use-tambo-stream-status.js.map +1 -1
- package/dist/mcp/mcp-hooks.d.ts +4 -0
- package/dist/mcp/mcp-hooks.d.ts.map +1 -1
- package/dist/mcp/mcp-hooks.js +4 -0
- package/dist/mcp/mcp-hooks.js.map +1 -1
- package/dist/providers/tambo-interactable-provider-partial-updates.test.js +3 -3
- package/dist/providers/tambo-interactable-provider-partial-updates.test.js.map +1 -1
- package/dist/providers/tambo-interactable-provider.js +2 -2
- package/dist/providers/tambo-interactable-provider.js.map +1 -1
- package/dist/providers/tambo-interactable-provider.test.js +3 -3
- package/dist/providers/tambo-interactable-provider.test.js.map +1 -1
- package/dist/providers/tambo-provider.d.ts +3 -0
- package/dist/providers/tambo-provider.d.ts.map +1 -1
- package/dist/providers/tambo-provider.js +3 -0
- package/dist/providers/tambo-provider.js.map +1 -1
- package/dist/providers/tambo-thread-input-provider.d.ts.map +1 -1
- package/dist/providers/tambo-thread-input-provider.js +1 -0
- package/dist/providers/tambo-thread-input-provider.js.map +1 -1
- package/dist/util/resource-content-resolver.d.ts.map +1 -1
- package/dist/util/resource-content-resolver.js +2 -0
- package/dist/util/resource-content-resolver.js.map +1 -1
- package/dist/v1/__tests__/v1-interactables.test.d.ts +2 -0
- package/dist/v1/__tests__/v1-interactables.test.d.ts.map +1 -0
- package/dist/v1/__tests__/v1-interactables.test.js +135 -0
- package/dist/v1/__tests__/v1-interactables.test.js.map +1 -0
- package/dist/v1/components/v1-component-renderer.d.ts +48 -0
- package/dist/v1/components/v1-component-renderer.d.ts.map +1 -0
- package/dist/v1/components/v1-component-renderer.js +137 -0
- package/dist/v1/components/v1-component-renderer.js.map +1 -0
- package/dist/v1/components/v1-component-renderer.test.d.ts +2 -0
- package/dist/v1/components/v1-component-renderer.test.d.ts.map +1 -0
- package/dist/v1/components/v1-component-renderer.test.js +270 -0
- package/dist/v1/components/v1-component-renderer.test.js.map +1 -0
- package/dist/v1/hooks/use-tambo-v1-component-state.d.ts.map +1 -1
- package/dist/v1/hooks/use-tambo-v1-component-state.js +2 -25
- package/dist/v1/hooks/use-tambo-v1-component-state.js.map +1 -1
- package/dist/v1/hooks/use-tambo-v1-component-state.test.js +2 -1
- package/dist/v1/hooks/use-tambo-v1-component-state.test.js.map +1 -1
- package/dist/v1/hooks/use-tambo-v1-messages.test.js +25 -1
- package/dist/v1/hooks/use-tambo-v1-messages.test.js.map +1 -1
- package/dist/v1/hooks/use-tambo-v1-send-message.d.ts +18 -0
- package/dist/v1/hooks/use-tambo-v1-send-message.d.ts.map +1 -1
- package/dist/v1/hooks/use-tambo-v1-send-message.js +204 -17
- package/dist/v1/hooks/use-tambo-v1-send-message.js.map +1 -1
- package/dist/v1/hooks/use-tambo-v1-send-message.test.js +261 -7
- package/dist/v1/hooks/use-tambo-v1-send-message.test.js.map +1 -1
- package/dist/v1/hooks/use-tambo-v1-stream-status.d.ts +90 -0
- package/dist/v1/hooks/use-tambo-v1-stream-status.d.ts.map +1 -0
- package/dist/v1/hooks/use-tambo-v1-stream-status.js +179 -0
- package/dist/v1/hooks/use-tambo-v1-stream-status.js.map +1 -0
- package/dist/v1/hooks/use-tambo-v1-stream-status.test.d.ts +2 -0
- package/dist/v1/hooks/use-tambo-v1-stream-status.test.d.ts.map +1 -0
- package/dist/v1/hooks/use-tambo-v1-stream-status.test.js +371 -0
- package/dist/v1/hooks/use-tambo-v1-stream-status.test.js.map +1 -0
- package/dist/v1/hooks/use-tambo-v1-suggestions.d.ts +78 -54
- package/dist/v1/hooks/use-tambo-v1-suggestions.d.ts.map +1 -1
- package/dist/v1/hooks/use-tambo-v1-suggestions.js +153 -87
- package/dist/v1/hooks/use-tambo-v1-suggestions.js.map +1 -1
- package/dist/v1/hooks/use-tambo-v1-suggestions.test.js +213 -134
- package/dist/v1/hooks/use-tambo-v1-suggestions.test.js.map +1 -1
- package/dist/v1/hooks/use-tambo-v1-thread-input.test.js +148 -13
- package/dist/v1/hooks/use-tambo-v1-thread-input.test.js.map +1 -1
- package/dist/v1/hooks/use-tambo-v1-thread-list.d.ts +8 -21
- package/dist/v1/hooks/use-tambo-v1-thread-list.d.ts.map +1 -1
- package/dist/v1/hooks/use-tambo-v1-thread-list.js +11 -10
- package/dist/v1/hooks/use-tambo-v1-thread-list.js.map +1 -1
- package/dist/v1/hooks/use-tambo-v1-thread-list.test.js +37 -2
- package/dist/v1/hooks/use-tambo-v1-thread-list.test.js.map +1 -1
- package/dist/v1/hooks/use-tambo-v1-thread.d.ts +1 -1
- package/dist/v1/hooks/use-tambo-v1-thread.d.ts.map +1 -1
- package/dist/v1/hooks/use-tambo-v1-thread.js +2 -7
- package/dist/v1/hooks/use-tambo-v1-thread.js.map +1 -1
- package/dist/v1/hooks/use-tambo-v1-thread.test.js +2 -0
- package/dist/v1/hooks/use-tambo-v1-thread.test.js.map +1 -1
- package/dist/v1/hooks/use-tambo-v1.d.ts +12 -28
- package/dist/v1/hooks/use-tambo-v1.d.ts.map +1 -1
- package/dist/v1/hooks/use-tambo-v1.js +164 -31
- package/dist/v1/hooks/use-tambo-v1.js.map +1 -1
- package/dist/v1/hooks/use-tambo-v1.test.js +891 -18
- package/dist/v1/hooks/use-tambo-v1.test.js.map +1 -1
- package/dist/v1/index.d.ts +7 -1
- package/dist/v1/index.d.ts.map +1 -1
- package/dist/v1/index.js +18 -1
- package/dist/v1/index.js.map +1 -1
- package/dist/v1/providers/tambo-v1-provider.d.ts +16 -6
- package/dist/v1/providers/tambo-v1-provider.d.ts.map +1 -1
- package/dist/v1/providers/tambo-v1-provider.js +14 -19
- package/dist/v1/providers/tambo-v1-provider.js.map +1 -1
- package/dist/v1/providers/tambo-v1-provider.test.js +34 -20
- package/dist/v1/providers/tambo-v1-provider.test.js.map +1 -1
- package/dist/v1/providers/tambo-v1-stream-context.d.ts +3 -3
- package/dist/v1/providers/tambo-v1-stream-context.d.ts.map +1 -1
- package/dist/v1/providers/tambo-v1-stream-context.js +60 -12
- package/dist/v1/providers/tambo-v1-stream-context.js.map +1 -1
- package/dist/v1/providers/tambo-v1-stream-context.test.js +49 -20
- package/dist/v1/providers/tambo-v1-stream-context.test.js.map +1 -1
- package/dist/v1/providers/tambo-v1-stub-provider.d.ts.map +1 -1
- package/dist/v1/providers/tambo-v1-stub-provider.js +2 -0
- package/dist/v1/providers/tambo-v1-stub-provider.js.map +1 -1
- package/dist/v1/providers/tambo-v1-stub-provider.test.js +7 -6
- package/dist/v1/providers/tambo-v1-stub-provider.test.js.map +1 -1
- package/dist/v1/providers/tambo-v1-thread-input-provider.d.ts +1 -6
- package/dist/v1/providers/tambo-v1-thread-input-provider.d.ts.map +1 -1
- package/dist/v1/providers/tambo-v1-thread-input-provider.js +14 -12
- package/dist/v1/providers/tambo-v1-thread-input-provider.js.map +1 -1
- package/dist/v1/types/event.d.ts +9 -1
- package/dist/v1/types/event.d.ts.map +1 -1
- package/dist/v1/types/event.js.map +1 -1
- package/dist/v1/types/event.test.js +5 -1
- package/dist/v1/types/event.test.js.map +1 -1
- package/dist/v1/types/message.d.ts +65 -7
- package/dist/v1/types/message.d.ts.map +1 -1
- package/dist/v1/types/message.js.map +1 -1
- package/dist/v1/types/thread.d.ts +4 -0
- package/dist/v1/types/thread.d.ts.map +1 -1
- package/dist/v1/types/thread.js.map +1 -1
- package/dist/v1/utils/event-accumulator.d.ts +40 -4
- package/dist/v1/utils/event-accumulator.d.ts.map +1 -1
- package/dist/v1/utils/event-accumulator.js +444 -35
- package/dist/v1/utils/event-accumulator.js.map +1 -1
- package/dist/v1/utils/event-accumulator.test.js +1041 -28
- package/dist/v1/utils/event-accumulator.test.js.map +1 -1
- package/dist/v1/utils/registry-conversion.d.ts +9 -9
- package/dist/v1/utils/registry-conversion.d.ts.map +1 -1
- package/dist/v1/utils/registry-conversion.js +10 -11
- package/dist/v1/utils/registry-conversion.js.map +1 -1
- package/dist/v1/utils/registry-conversion.test.js +39 -11
- package/dist/v1/utils/registry-conversion.test.js.map +1 -1
- package/dist/v1/utils/thread-utils.d.ts +16 -0
- package/dist/v1/utils/thread-utils.d.ts.map +1 -0
- package/dist/v1/utils/thread-utils.js +34 -0
- package/dist/v1/utils/thread-utils.js.map +1 -0
- package/dist/v1/utils/tool-executor.d.ts.map +1 -1
- package/dist/v1/utils/tool-executor.js +2 -0
- package/dist/v1/utils/tool-executor.js.map +1 -1
- package/dist/v1/utils/tool-executor.test.js +5 -0
- package/dist/v1/utils/tool-executor.test.js.map +1 -1
- package/esm/context-helpers/context-helpers-provider.test.js +2 -2
- package/esm/context-helpers/context-helpers.test.js +1 -1
- package/esm/context-helpers/current-interactables-context-helper.d.ts +1 -1
- package/esm/context-helpers/current-page-context-helper.d.ts +1 -1
- package/esm/context-helpers/current-time-context-helper.d.ts +1 -1
- package/esm/context-helpers/index.d.ts +4 -4
- package/esm/context-helpers/index.js +4 -4
- package/esm/hoc/with-tambo-interactable.d.ts +1 -1
- package/esm/hoc/with-tambo-interactable.js +2 -2
- package/esm/hoc/with-tambo-interactable.test.js +3 -3
- package/esm/hooks/index.d.ts +8 -8
- package/esm/hooks/index.js +8 -8
- package/esm/hooks/react-query-hooks.js +1 -1
- package/esm/hooks/use-component-state.d.ts +1 -1
- package/esm/hooks/use-component-state.js +3 -3
- package/esm/hooks/use-component-state.js.map +1 -1
- package/esm/hooks/use-component-state.test.js +5 -5
- package/esm/hooks/use-current-message.d.ts +1 -1
- package/esm/hooks/use-current-message.test.js +1 -1
- package/esm/hooks/use-message-images.test.js +1 -1
- package/esm/hooks/use-streaming-props.d.ts +1 -1
- package/esm/hooks/use-streaming-props.js +1 -1
- package/esm/hooks/use-streaming-props.js.map +1 -1
- package/esm/hooks/use-suggestions.d.ts +2 -2
- package/esm/hooks/use-suggestions.js +10 -10
- package/esm/hooks/use-suggestions.test.js +7 -7
- package/esm/hooks/use-tambo-stream-status.d.ts +1 -1
- package/esm/hooks/use-tambo-stream-status.js +4 -4
- package/esm/hooks/use-tambo-stream-status.js.map +1 -1
- package/esm/hooks/use-tambo-stream-status.test.js +4 -4
- package/esm/hooks/use-tambo-threads.js +3 -3
- package/esm/hooks/use-tambo-threads.test.js +3 -3
- package/esm/hooks/use-tambo-voice.js +2 -2
- package/esm/hooks/use-tambo-voice.test.js +3 -3
- package/esm/index.d.ts +22 -22
- package/esm/index.js +15 -15
- package/esm/mcp/elicitation.d.ts +1 -1
- package/esm/mcp/elicitation.test.js +1 -1
- package/esm/mcp/index.d.ts +7 -7
- package/esm/mcp/index.js +3 -3
- package/esm/mcp/mcp-client.d.ts +1 -1
- package/esm/mcp/mcp-client.js +1 -1
- package/esm/mcp/mcp-client.test.js +1 -1
- package/esm/mcp/mcp-hooks.d.ts +5 -1
- package/esm/mcp/mcp-hooks.d.ts.map +1 -1
- package/esm/mcp/mcp-hooks.js +8 -4
- package/esm/mcp/mcp-hooks.js.map +1 -1
- package/esm/mcp/mcp-hooks.test.js +6 -6
- package/esm/mcp/tambo-mcp-provider.d.ts +4 -4
- package/esm/mcp/tambo-mcp-provider.js +7 -7
- package/esm/mcp/tambo-mcp-provider.test.js +5 -5
- package/esm/mcp/use-mcp-servers.test.js +4 -4
- package/esm/model/generate-component-response.d.ts +1 -1
- package/esm/model/tambo-interactable.d.ts +1 -1
- package/esm/model/tambo-thread.d.ts +1 -1
- package/esm/providers/__tests__/thread-input-resource-resolution.test.js +3 -3
- package/esm/providers/hooks/use-tambo-session-token.test.js +1 -1
- package/esm/providers/index.d.ts +12 -12
- package/esm/providers/index.js +10 -10
- package/esm/providers/tambo-client-provider.js +1 -1
- package/esm/providers/tambo-client-provider.test.js +2 -2
- package/esm/providers/tambo-component-provider.d.ts +1 -1
- package/esm/providers/tambo-component-provider.js +2 -2
- package/esm/providers/tambo-context-attachment-provider.js +1 -1
- package/esm/providers/tambo-context-attachment-provider.test.js +2 -2
- package/esm/providers/tambo-context-helpers-provider.d.ts +1 -1
- package/esm/providers/tambo-context-helpers-provider.js +1 -1
- package/esm/providers/tambo-context-helpers-provider.test.js +2 -2
- package/esm/providers/tambo-interactable-provider-partial-updates.test.js +4 -4
- package/esm/providers/tambo-interactable-provider-partial-updates.test.js.map +1 -1
- package/esm/providers/tambo-interactable-provider.d.ts +5 -5
- package/esm/providers/tambo-interactable-provider.js +6 -6
- package/esm/providers/tambo-interactable-provider.js.map +1 -1
- package/esm/providers/tambo-interactable-provider.test.js +4 -4
- package/esm/providers/tambo-interactable-provider.test.js.map +1 -1
- package/esm/providers/tambo-interactables-additional-context-edge-cases.test.js +4 -4
- package/esm/providers/tambo-interactables-additional-context.test.js +4 -4
- package/esm/providers/tambo-mcp-token-provider.js +2 -2
- package/esm/providers/tambo-prop-stream-provider/index.d.ts +8 -8
- package/esm/providers/tambo-prop-stream-provider/index.js +9 -9
- package/esm/providers/tambo-prop-stream-provider/pending.d.ts +1 -1
- package/esm/providers/tambo-prop-stream-provider/pending.js +2 -2
- package/esm/providers/tambo-prop-stream-provider/provider.d.ts +1 -1
- package/esm/providers/tambo-prop-stream-provider/provider.js +2 -2
- package/esm/providers/tambo-prop-stream-provider/streaming.d.ts +1 -1
- package/esm/providers/tambo-prop-stream-provider/streaming.js +2 -2
- package/esm/providers/tambo-prop-stream-provider/success.d.ts +1 -1
- package/esm/providers/tambo-prop-stream-provider/success.js +2 -2
- package/esm/providers/tambo-prop-stream-provider/types.d.ts +1 -1
- package/esm/providers/tambo-prop-stream-provider.test.js +4 -4
- package/esm/providers/tambo-provider.d.ts +10 -7
- package/esm/providers/tambo-provider.d.ts.map +1 -1
- package/esm/providers/tambo-provider.js +13 -10
- package/esm/providers/tambo-provider.js.map +1 -1
- package/esm/providers/tambo-registry-provider.d.ts +3 -3
- package/esm/providers/tambo-registry-provider.js +3 -3
- package/esm/providers/tambo-registry-provider.test.js +2 -2
- package/esm/providers/tambo-registry-schema-compat.test.js +2 -2
- package/esm/providers/tambo-stubs.d.ts +4 -4
- package/esm/providers/tambo-stubs.js +9 -9
- package/esm/providers/tambo-stubs.test.js +2 -2
- package/esm/providers/tambo-thread-input-provider.d.ts +2 -2
- package/esm/providers/tambo-thread-input-provider.d.ts.map +1 -1
- package/esm/providers/tambo-thread-input-provider.js +11 -10
- package/esm/providers/tambo-thread-input-provider.js.map +1 -1
- package/esm/providers/tambo-thread-provider-initial-messages.test.js +6 -6
- package/esm/providers/tambo-thread-provider.d.ts +2 -2
- package/esm/providers/tambo-thread-provider.js +8 -8
- package/esm/providers/tambo-thread-provider.test.js +7 -7
- package/esm/schema/index.d.ts +4 -4
- package/esm/schema/index.js +4 -4
- package/esm/schema/json-schema.test.js +1 -1
- package/esm/schema/schema.d.ts +1 -1
- package/esm/schema/schema.js +2 -2
- package/esm/schema/schema.test.js +3 -3
- package/esm/schema/standard-schema.test.js +1 -1
- package/esm/schema/validate.js +2 -2
- package/esm/schema/validate.test.js +1 -1
- package/esm/testing/tools.d.ts +3 -3
- package/esm/testing/tools.js +2 -2
- package/esm/util/content-parts.test.js +1 -1
- package/esm/util/generate-component.d.ts +2 -2
- package/esm/util/generate-component.js +4 -4
- package/esm/util/generate-component.test.js +2 -2
- package/esm/util/is-promise.test.js +1 -1
- package/esm/util/mcp-server-utils.d.ts +1 -1
- package/esm/util/mcp-server-utils.js +1 -1
- package/esm/util/mcp-server-utils.test.js +2 -2
- package/esm/util/message-builder.d.ts +1 -1
- package/esm/util/message-builder.test.js +1 -1
- package/esm/util/query-utils.test.js +1 -1
- package/esm/util/registry-validators.d.ts +1 -1
- package/esm/util/registry-validators.js +2 -2
- package/esm/util/registry-validators.test.js +1 -1
- package/esm/util/registry.d.ts +1 -1
- package/esm/util/registry.js +1 -1
- package/esm/util/registry.test.js +2 -2
- package/esm/util/resource-content-resolver.d.ts +2 -2
- package/esm/util/resource-content-resolver.d.ts.map +1 -1
- package/esm/util/resource-content-resolver.js +3 -1
- package/esm/util/resource-content-resolver.js.map +1 -1
- package/esm/util/resource-content-resolver.test.js +3 -3
- package/esm/util/resource-validators.d.ts +1 -1
- package/esm/util/resource-validators.test.js +1 -1
- package/esm/util/tool-caller.d.ts +1 -1
- package/esm/util/tool-caller.js +1 -1
- package/esm/util/validate-component-name.test.js +1 -1
- package/esm/v1/__tests__/v1-interactables.test.d.ts +2 -0
- package/esm/v1/__tests__/v1-interactables.test.d.ts.map +1 -0
- package/esm/v1/__tests__/v1-interactables.test.js +130 -0
- package/esm/v1/__tests__/v1-interactables.test.js.map +1 -0
- package/esm/v1/components/v1-component-renderer.d.ts +48 -0
- package/esm/v1/components/v1-component-renderer.d.ts.map +1 -0
- package/esm/v1/components/v1-component-renderer.js +100 -0
- package/esm/v1/components/v1-component-renderer.js.map +1 -0
- package/esm/v1/components/v1-component-renderer.test.d.ts +2 -0
- package/esm/v1/components/v1-component-renderer.test.d.ts.map +1 -0
- package/esm/v1/components/v1-component-renderer.test.js +265 -0
- package/esm/v1/components/v1-component-renderer.test.js.map +1 -0
- package/esm/v1/hooks/use-tambo-v1-component-state.d.ts.map +1 -1
- package/esm/v1/hooks/use-tambo-v1-component-state.js +4 -27
- package/esm/v1/hooks/use-tambo-v1-component-state.js.map +1 -1
- package/esm/v1/hooks/use-tambo-v1-component-state.test.js +6 -5
- package/esm/v1/hooks/use-tambo-v1-component-state.test.js.map +1 -1
- package/esm/v1/hooks/use-tambo-v1-messages.d.ts +1 -1
- package/esm/v1/hooks/use-tambo-v1-messages.js +1 -1
- package/esm/v1/hooks/use-tambo-v1-messages.test.js +27 -3
- package/esm/v1/hooks/use-tambo-v1-messages.test.js.map +1 -1
- package/esm/v1/hooks/use-tambo-v1-send-message.d.ts +20 -2
- package/esm/v1/hooks/use-tambo-v1-send-message.d.ts.map +1 -1
- package/esm/v1/hooks/use-tambo-v1-send-message.js +213 -26
- package/esm/v1/hooks/use-tambo-v1-send-message.js.map +1 -1
- package/esm/v1/hooks/use-tambo-v1-send-message.test.js +266 -12
- package/esm/v1/hooks/use-tambo-v1-send-message.test.js.map +1 -1
- package/esm/v1/hooks/use-tambo-v1-stream-status.d.ts +90 -0
- package/esm/v1/hooks/use-tambo-v1-stream-status.d.ts.map +1 -0
- package/esm/v1/hooks/use-tambo-v1-stream-status.js +176 -0
- package/esm/v1/hooks/use-tambo-v1-stream-status.js.map +1 -0
- package/esm/v1/hooks/use-tambo-v1-stream-status.test.d.ts +2 -0
- package/esm/v1/hooks/use-tambo-v1-stream-status.test.d.ts.map +1 -0
- package/esm/v1/hooks/use-tambo-v1-stream-status.test.js +369 -0
- package/esm/v1/hooks/use-tambo-v1-stream-status.test.js.map +1 -0
- package/esm/v1/hooks/use-tambo-v1-suggestions.d.ts +78 -54
- package/esm/v1/hooks/use-tambo-v1-suggestions.d.ts.map +1 -1
- package/esm/v1/hooks/use-tambo-v1-suggestions.js +157 -91
- package/esm/v1/hooks/use-tambo-v1-suggestions.js.map +1 -1
- package/esm/v1/hooks/use-tambo-v1-suggestions.test.js +218 -139
- package/esm/v1/hooks/use-tambo-v1-suggestions.test.js.map +1 -1
- package/esm/v1/hooks/use-tambo-v1-thread-input.d.ts +1 -1
- package/esm/v1/hooks/use-tambo-v1-thread-input.js +1 -1
- package/esm/v1/hooks/use-tambo-v1-thread-input.test.js +151 -16
- package/esm/v1/hooks/use-tambo-v1-thread-input.test.js.map +1 -1
- package/esm/v1/hooks/use-tambo-v1-thread-list.d.ts +8 -21
- package/esm/v1/hooks/use-tambo-v1-thread-list.d.ts.map +1 -1
- package/esm/v1/hooks/use-tambo-v1-thread-list.js +12 -11
- package/esm/v1/hooks/use-tambo-v1-thread-list.js.map +1 -1
- package/esm/v1/hooks/use-tambo-v1-thread-list.test.js +39 -4
- package/esm/v1/hooks/use-tambo-v1-thread-list.test.js.map +1 -1
- package/esm/v1/hooks/use-tambo-v1-thread.d.ts +1 -1
- package/esm/v1/hooks/use-tambo-v1-thread.d.ts.map +1 -1
- package/esm/v1/hooks/use-tambo-v1-thread.js +3 -8
- package/esm/v1/hooks/use-tambo-v1-thread.js.map +1 -1
- package/esm/v1/hooks/use-tambo-v1-thread.test.js +4 -2
- package/esm/v1/hooks/use-tambo-v1-thread.test.js.map +1 -1
- package/esm/v1/hooks/use-tambo-v1.d.ts +15 -31
- package/esm/v1/hooks/use-tambo-v1.d.ts.map +1 -1
- package/esm/v1/hooks/use-tambo-v1.js +134 -34
- package/esm/v1/hooks/use-tambo-v1.js.map +1 -1
- package/esm/v1/hooks/use-tambo-v1.test.js +862 -19
- package/esm/v1/hooks/use-tambo-v1.test.js.map +1 -1
- package/esm/v1/index.d.ts +28 -22
- package/esm/v1/index.d.ts.map +1 -1
- package/esm/v1/index.js +30 -18
- package/esm/v1/index.js.map +1 -1
- package/esm/v1/providers/tambo-v1-provider.d.ts +21 -11
- package/esm/v1/providers/tambo-v1-provider.d.ts.map +1 -1
- package/esm/v1/providers/tambo-v1-provider.js +20 -25
- package/esm/v1/providers/tambo-v1-provider.js.map +1 -1
- package/esm/v1/providers/tambo-v1-provider.test.js +40 -26
- package/esm/v1/providers/tambo-v1-provider.test.js.map +1 -1
- package/esm/v1/providers/tambo-v1-stream-context.d.ts +4 -4
- package/esm/v1/providers/tambo-v1-stream-context.d.ts.map +1 -1
- package/esm/v1/providers/tambo-v1-stream-context.js +62 -14
- package/esm/v1/providers/tambo-v1-stream-context.js.map +1 -1
- package/esm/v1/providers/tambo-v1-stream-context.test.js +50 -21
- package/esm/v1/providers/tambo-v1-stream-context.test.js.map +1 -1
- package/esm/v1/providers/tambo-v1-stub-provider.d.ts +3 -3
- package/esm/v1/providers/tambo-v1-stub-provider.d.ts.map +1 -1
- package/esm/v1/providers/tambo-v1-stub-provider.js +7 -5
- package/esm/v1/providers/tambo-v1-stub-provider.js.map +1 -1
- package/esm/v1/providers/tambo-v1-stub-provider.test.js +12 -11
- package/esm/v1/providers/tambo-v1-stub-provider.test.js.map +1 -1
- package/esm/v1/providers/tambo-v1-thread-input-provider.d.ts +3 -8
- package/esm/v1/providers/tambo-v1-thread-input-provider.d.ts.map +1 -1
- package/esm/v1/providers/tambo-v1-thread-input-provider.js +18 -16
- package/esm/v1/providers/tambo-v1-thread-input-provider.js.map +1 -1
- package/esm/v1/types/event.d.ts +9 -1
- package/esm/v1/types/event.d.ts.map +1 -1
- package/esm/v1/types/event.js.map +1 -1
- package/esm/v1/types/event.test.js +6 -2
- package/esm/v1/types/event.test.js.map +1 -1
- package/esm/v1/types/message.d.ts +65 -7
- package/esm/v1/types/message.d.ts.map +1 -1
- package/esm/v1/types/message.js.map +1 -1
- package/esm/v1/types/thread.d.ts +5 -1
- package/esm/v1/types/thread.d.ts.map +1 -1
- package/esm/v1/types/thread.js.map +1 -1
- package/esm/v1/utils/component-renderer.test.js +1 -1
- package/esm/v1/utils/event-accumulator.d.ts +41 -5
- package/esm/v1/utils/event-accumulator.d.ts.map +1 -1
- package/esm/v1/utils/event-accumulator.js +444 -36
- package/esm/v1/utils/event-accumulator.js.map +1 -1
- package/esm/v1/utils/event-accumulator.test.js +1042 -29
- package/esm/v1/utils/event-accumulator.test.js.map +1 -1
- package/esm/v1/utils/json-patch.test.js +1 -1
- package/esm/v1/utils/registry-conversion.d.ts +9 -9
- package/esm/v1/utils/registry-conversion.d.ts.map +1 -1
- package/esm/v1/utils/registry-conversion.js +11 -12
- package/esm/v1/utils/registry-conversion.js.map +1 -1
- package/esm/v1/utils/registry-conversion.test.js +40 -12
- package/esm/v1/utils/registry-conversion.test.js.map +1 -1
- package/esm/v1/utils/stream-handler.test.js +1 -1
- package/esm/v1/utils/thread-utils.d.ts +16 -0
- package/esm/v1/utils/thread-utils.d.ts.map +1 -0
- package/esm/v1/utils/thread-utils.js +31 -0
- package/esm/v1/utils/thread-utils.js.map +1 -0
- package/esm/v1/utils/tool-call-tracker.d.ts +1 -1
- package/esm/v1/utils/tool-executor.d.ts +1 -1
- package/esm/v1/utils/tool-executor.d.ts.map +1 -1
- package/esm/v1/utils/tool-executor.js +2 -0
- package/esm/v1/utils/tool-executor.js.map +1 -1
- package/esm/v1/utils/tool-executor.test.js +6 -1
- package/esm/v1/utils/tool-executor.test.js.map +1 -1
- package/package.json +11 -10
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { EventType, } from "@ag-ui/core";
|
|
2
|
-
import { createInitialState, createInitialThreadState, streamReducer, } from "./event-accumulator";
|
|
2
|
+
import { createInitialState, createInitialThreadState, streamReducer, } from "./event-accumulator.js";
|
|
3
3
|
/**
|
|
4
4
|
* Helper to extract a ToolUseContent from a message content array.
|
|
5
5
|
* @param content - Content array from a message
|
|
@@ -27,6 +27,7 @@ function createTestThreadState(threadId) {
|
|
|
27
27
|
// Use fixed timestamps for snapshot stability
|
|
28
28
|
createdAt: "2024-01-01T00:00:00.000Z",
|
|
29
29
|
updatedAt: "2024-01-01T00:00:00.000Z",
|
|
30
|
+
lastRunCancelled: false,
|
|
30
31
|
},
|
|
31
32
|
};
|
|
32
33
|
}
|
|
@@ -51,10 +52,13 @@ describe("createInitialThreadState", () => {
|
|
|
51
52
|
});
|
|
52
53
|
});
|
|
53
54
|
describe("createInitialState", () => {
|
|
54
|
-
it("creates
|
|
55
|
+
it("creates initial state with placeholder thread", () => {
|
|
55
56
|
const state = createInitialState();
|
|
56
|
-
expect(state.
|
|
57
|
-
expect(state.
|
|
57
|
+
expect(state.currentThreadId).toBe("placeholder");
|
|
58
|
+
expect(state.threadMap.placeholder).toBeDefined();
|
|
59
|
+
expect(state.threadMap.placeholder.thread.id).toBe("placeholder");
|
|
60
|
+
expect(state.threadMap.placeholder.thread.messages).toEqual([]);
|
|
61
|
+
expect(state.threadMap.placeholder.streaming.status).toBe("idle");
|
|
58
62
|
});
|
|
59
63
|
});
|
|
60
64
|
describe("streamReducer", () => {
|
|
@@ -150,6 +154,24 @@ describe("streamReducer", () => {
|
|
|
150
154
|
expect(result.threadMap.thread_1.streaming.status).toBe("streaming");
|
|
151
155
|
expect(result.threadMap.thread_1.streaming.runId).toBe("run_123");
|
|
152
156
|
});
|
|
157
|
+
it("resets lastRunCancelled to false when a new run starts", () => {
|
|
158
|
+
// Start with a thread that was cancelled
|
|
159
|
+
const state = createTestStreamState("thread_1");
|
|
160
|
+
state.threadMap.thread_1.thread.lastRunCancelled = true;
|
|
161
|
+
const event = {
|
|
162
|
+
type: EventType.RUN_STARTED,
|
|
163
|
+
runId: "run_123",
|
|
164
|
+
threadId: "thread_1",
|
|
165
|
+
timestamp: 1704067200000,
|
|
166
|
+
};
|
|
167
|
+
const result = streamReducer(state, {
|
|
168
|
+
type: "EVENT",
|
|
169
|
+
event,
|
|
170
|
+
threadId: "thread_1",
|
|
171
|
+
});
|
|
172
|
+
// lastRunCancelled should be reset to false
|
|
173
|
+
expect(result.threadMap.thread_1.thread.lastRunCancelled).toBe(false);
|
|
174
|
+
});
|
|
153
175
|
it("uses provided timestamp for startTime", () => {
|
|
154
176
|
const state = createTestStreamState("thread_1");
|
|
155
177
|
const event = {
|
|
@@ -165,6 +187,198 @@ describe("streamReducer", () => {
|
|
|
165
187
|
});
|
|
166
188
|
expect(result.threadMap.thread_1.streaming.startTime).toBe(1704067200000);
|
|
167
189
|
});
|
|
190
|
+
it("does not switch currentThreadId when placeholder has no messages", () => {
|
|
191
|
+
const state = createInitialState();
|
|
192
|
+
const realThreadId = "thread_real_123";
|
|
193
|
+
const runStartedEvent = {
|
|
194
|
+
type: EventType.RUN_STARTED,
|
|
195
|
+
runId: "run_456",
|
|
196
|
+
threadId: realThreadId,
|
|
197
|
+
timestamp: 1704067200000,
|
|
198
|
+
};
|
|
199
|
+
const result = streamReducer(state, {
|
|
200
|
+
type: "EVENT",
|
|
201
|
+
event: runStartedEvent,
|
|
202
|
+
threadId: realThreadId,
|
|
203
|
+
});
|
|
204
|
+
expect(result.threadMap.placeholder.thread.messages).toHaveLength(0);
|
|
205
|
+
expect(result.currentThreadId).toBe("placeholder");
|
|
206
|
+
});
|
|
207
|
+
it("migrates messages from placeholder thread to real thread", () => {
|
|
208
|
+
// Start with initial state (which has placeholder thread)
|
|
209
|
+
const state = createInitialState();
|
|
210
|
+
// Verify placeholder thread exists
|
|
211
|
+
expect(state.currentThreadId).toBe("placeholder");
|
|
212
|
+
// Add a user message to the placeholder thread
|
|
213
|
+
const userMsgStart = {
|
|
214
|
+
type: EventType.TEXT_MESSAGE_START,
|
|
215
|
+
messageId: "user_msg_1",
|
|
216
|
+
role: "user",
|
|
217
|
+
};
|
|
218
|
+
const userMsgContent = {
|
|
219
|
+
type: EventType.TEXT_MESSAGE_CONTENT,
|
|
220
|
+
messageId: "user_msg_1",
|
|
221
|
+
delta: "Hello",
|
|
222
|
+
};
|
|
223
|
+
const userMsgEnd = {
|
|
224
|
+
type: EventType.TEXT_MESSAGE_END,
|
|
225
|
+
messageId: "user_msg_1",
|
|
226
|
+
};
|
|
227
|
+
let stateWithUserMsg = streamReducer(state, {
|
|
228
|
+
type: "EVENT",
|
|
229
|
+
event: userMsgStart,
|
|
230
|
+
threadId: "placeholder",
|
|
231
|
+
});
|
|
232
|
+
stateWithUserMsg = streamReducer(stateWithUserMsg, {
|
|
233
|
+
type: "EVENT",
|
|
234
|
+
event: userMsgContent,
|
|
235
|
+
threadId: "placeholder",
|
|
236
|
+
});
|
|
237
|
+
stateWithUserMsg = streamReducer(stateWithUserMsg, {
|
|
238
|
+
type: "EVENT",
|
|
239
|
+
event: userMsgEnd,
|
|
240
|
+
threadId: "placeholder",
|
|
241
|
+
});
|
|
242
|
+
// Verify placeholder thread has the message
|
|
243
|
+
expect(stateWithUserMsg.currentThreadId).toBe("placeholder");
|
|
244
|
+
expect(stateWithUserMsg.threadMap.placeholder.thread.messages).toHaveLength(1);
|
|
245
|
+
expect(stateWithUserMsg.threadMap.placeholder.thread.messages[0].content[0]).toEqual({
|
|
246
|
+
type: "text",
|
|
247
|
+
text: "Hello",
|
|
248
|
+
});
|
|
249
|
+
// Now RUN_STARTED arrives with the real thread ID
|
|
250
|
+
const realThreadId = "thread_real_123";
|
|
251
|
+
const runStartedEvent = {
|
|
252
|
+
type: EventType.RUN_STARTED,
|
|
253
|
+
runId: "run_456",
|
|
254
|
+
threadId: realThreadId,
|
|
255
|
+
timestamp: 1704067200000,
|
|
256
|
+
};
|
|
257
|
+
const finalState = streamReducer(stateWithUserMsg, {
|
|
258
|
+
type: "EVENT",
|
|
259
|
+
event: runStartedEvent,
|
|
260
|
+
threadId: realThreadId,
|
|
261
|
+
});
|
|
262
|
+
// Placeholder thread should be reset to empty (not removed)
|
|
263
|
+
expect(finalState.threadMap.placeholder).toBeDefined();
|
|
264
|
+
expect(finalState.threadMap.placeholder.thread.messages).toHaveLength(0);
|
|
265
|
+
// Real thread should have the migrated user message
|
|
266
|
+
expect(finalState.threadMap[realThreadId]).toBeDefined();
|
|
267
|
+
expect(finalState.threadMap[realThreadId].thread.messages).toHaveLength(1);
|
|
268
|
+
expect(finalState.threadMap[realThreadId].thread.messages[0].content[0]).toEqual({
|
|
269
|
+
type: "text",
|
|
270
|
+
text: "Hello",
|
|
271
|
+
});
|
|
272
|
+
// currentThreadId should be updated to real thread
|
|
273
|
+
expect(finalState.currentThreadId).toBe(realThreadId);
|
|
274
|
+
// Real thread should be in streaming state
|
|
275
|
+
expect(finalState.threadMap[realThreadId].thread.status).toBe("streaming");
|
|
276
|
+
expect(finalState.threadMap[realThreadId].streaming.runId).toBe("run_456");
|
|
277
|
+
});
|
|
278
|
+
it("migrates messages even if currentThreadId changes away from placeholder", () => {
|
|
279
|
+
let state = createInitialState();
|
|
280
|
+
state = streamReducer(state, {
|
|
281
|
+
type: "INIT_THREAD",
|
|
282
|
+
threadId: "thread_1",
|
|
283
|
+
});
|
|
284
|
+
state = streamReducer(state, {
|
|
285
|
+
type: "SET_CURRENT_THREAD",
|
|
286
|
+
threadId: "thread_1",
|
|
287
|
+
});
|
|
288
|
+
const userMsgStart = {
|
|
289
|
+
type: EventType.TEXT_MESSAGE_START,
|
|
290
|
+
messageId: "user_msg_1",
|
|
291
|
+
role: "user",
|
|
292
|
+
};
|
|
293
|
+
const userMsgContent = {
|
|
294
|
+
type: EventType.TEXT_MESSAGE_CONTENT,
|
|
295
|
+
messageId: "user_msg_1",
|
|
296
|
+
delta: "Hello",
|
|
297
|
+
};
|
|
298
|
+
const userMsgEnd = {
|
|
299
|
+
type: EventType.TEXT_MESSAGE_END,
|
|
300
|
+
messageId: "user_msg_1",
|
|
301
|
+
};
|
|
302
|
+
state = streamReducer(state, {
|
|
303
|
+
type: "EVENT",
|
|
304
|
+
event: userMsgStart,
|
|
305
|
+
threadId: "placeholder",
|
|
306
|
+
});
|
|
307
|
+
state = streamReducer(state, {
|
|
308
|
+
type: "EVENT",
|
|
309
|
+
event: userMsgContent,
|
|
310
|
+
threadId: "placeholder",
|
|
311
|
+
});
|
|
312
|
+
state = streamReducer(state, {
|
|
313
|
+
type: "EVENT",
|
|
314
|
+
event: userMsgEnd,
|
|
315
|
+
threadId: "placeholder",
|
|
316
|
+
});
|
|
317
|
+
const realThreadId = "thread_real_123";
|
|
318
|
+
const runStartedEvent = {
|
|
319
|
+
type: EventType.RUN_STARTED,
|
|
320
|
+
runId: "run_456",
|
|
321
|
+
threadId: realThreadId,
|
|
322
|
+
timestamp: 1704067200000,
|
|
323
|
+
};
|
|
324
|
+
const result = streamReducer(state, {
|
|
325
|
+
type: "EVENT",
|
|
326
|
+
event: runStartedEvent,
|
|
327
|
+
threadId: realThreadId,
|
|
328
|
+
});
|
|
329
|
+
expect(result.threadMap.placeholder.thread.messages).toHaveLength(0);
|
|
330
|
+
expect(result.threadMap[realThreadId].thread.messages).toHaveLength(1);
|
|
331
|
+
expect(result.currentThreadId).toBe("thread_1");
|
|
332
|
+
});
|
|
333
|
+
it("prefers event.threadId over action threadId when RUN_STARTED is dispatched", () => {
|
|
334
|
+
// Start with initial state (which has placeholder thread)
|
|
335
|
+
const state = createInitialState();
|
|
336
|
+
// Add a user message to the placeholder thread
|
|
337
|
+
const userMsgStart = {
|
|
338
|
+
type: EventType.TEXT_MESSAGE_START,
|
|
339
|
+
messageId: "user_msg_1",
|
|
340
|
+
role: "user",
|
|
341
|
+
};
|
|
342
|
+
const userMsgContent = {
|
|
343
|
+
type: EventType.TEXT_MESSAGE_CONTENT,
|
|
344
|
+
messageId: "user_msg_1",
|
|
345
|
+
delta: "Hello",
|
|
346
|
+
};
|
|
347
|
+
const userMsgEnd = {
|
|
348
|
+
type: EventType.TEXT_MESSAGE_END,
|
|
349
|
+
messageId: "user_msg_1",
|
|
350
|
+
};
|
|
351
|
+
let stateWithUserMsg = streamReducer(state, {
|
|
352
|
+
type: "EVENT",
|
|
353
|
+
event: userMsgStart,
|
|
354
|
+
threadId: "placeholder",
|
|
355
|
+
});
|
|
356
|
+
stateWithUserMsg = streamReducer(stateWithUserMsg, {
|
|
357
|
+
type: "EVENT",
|
|
358
|
+
event: userMsgContent,
|
|
359
|
+
threadId: "placeholder",
|
|
360
|
+
});
|
|
361
|
+
stateWithUserMsg = streamReducer(stateWithUserMsg, {
|
|
362
|
+
type: "EVENT",
|
|
363
|
+
event: userMsgEnd,
|
|
364
|
+
threadId: "placeholder",
|
|
365
|
+
});
|
|
366
|
+
const realThreadId = "thread_real_123";
|
|
367
|
+
const runStartedEvent = {
|
|
368
|
+
type: EventType.RUN_STARTED,
|
|
369
|
+
runId: "run_456",
|
|
370
|
+
threadId: realThreadId,
|
|
371
|
+
timestamp: 1704067200000,
|
|
372
|
+
};
|
|
373
|
+
const result = streamReducer(stateWithUserMsg, {
|
|
374
|
+
type: "EVENT",
|
|
375
|
+
event: runStartedEvent,
|
|
376
|
+
threadId: "placeholder",
|
|
377
|
+
});
|
|
378
|
+
expect(result.threadMap.placeholder.thread.messages).toHaveLength(0);
|
|
379
|
+
expect(result.threadMap[realThreadId].thread.messages).toHaveLength(1);
|
|
380
|
+
expect(result.currentThreadId).toBe(realThreadId);
|
|
381
|
+
});
|
|
168
382
|
});
|
|
169
383
|
describe("RUN_FINISHED event", () => {
|
|
170
384
|
it("updates thread status to complete", () => {
|
|
@@ -205,6 +419,26 @@ describe("streamReducer", () => {
|
|
|
205
419
|
code: "ERR_001",
|
|
206
420
|
});
|
|
207
421
|
});
|
|
422
|
+
it("sets lastRunCancelled and idle status when code is CANCELLED", () => {
|
|
423
|
+
const state = createTestStreamState("thread_1");
|
|
424
|
+
const event = {
|
|
425
|
+
type: EventType.RUN_ERROR,
|
|
426
|
+
message: "Run cancelled",
|
|
427
|
+
code: "CANCELLED",
|
|
428
|
+
};
|
|
429
|
+
const result = streamReducer(state, {
|
|
430
|
+
type: "EVENT",
|
|
431
|
+
event,
|
|
432
|
+
threadId: "thread_1",
|
|
433
|
+
});
|
|
434
|
+
// Cancelled runs should show as idle, not error
|
|
435
|
+
expect(result.threadMap.thread_1.thread.status).toBe("idle");
|
|
436
|
+
expect(result.threadMap.thread_1.streaming.status).toBe("idle");
|
|
437
|
+
// lastRunCancelled should be set
|
|
438
|
+
expect(result.threadMap.thread_1.thread.lastRunCancelled).toBe(true);
|
|
439
|
+
// No error should be stored for cancelled runs
|
|
440
|
+
expect(result.threadMap.thread_1.streaming.error).toBeUndefined();
|
|
441
|
+
});
|
|
208
442
|
});
|
|
209
443
|
describe("TEXT_MESSAGE_START event", () => {
|
|
210
444
|
it("creates new message in thread", () => {
|
|
@@ -459,7 +693,7 @@ describe("streamReducer", () => {
|
|
|
459
693
|
input: {},
|
|
460
694
|
});
|
|
461
695
|
});
|
|
462
|
-
it("
|
|
696
|
+
it("creates synthetic message when parentMessageId not found", () => {
|
|
463
697
|
const state = createTestStreamState("thread_1");
|
|
464
698
|
state.threadMap.thread_1.thread.messages = [
|
|
465
699
|
{
|
|
@@ -475,15 +709,25 @@ describe("streamReducer", () => {
|
|
|
475
709
|
toolCallName: "get_weather",
|
|
476
710
|
parentMessageId: "unknown_msg",
|
|
477
711
|
};
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
712
|
+
// When parentMessageId not found, creates a synthetic message
|
|
713
|
+
const result = streamReducer(state, {
|
|
714
|
+
type: "EVENT",
|
|
715
|
+
event,
|
|
716
|
+
threadId: "thread_1",
|
|
717
|
+
});
|
|
718
|
+
// Should create a synthetic message with the tool call
|
|
719
|
+
const messages = result.threadMap.thread_1.thread.messages;
|
|
720
|
+
expect(messages).toHaveLength(2); // Original + synthetic
|
|
721
|
+
expect(messages[1].id).toBe("unknown_msg");
|
|
722
|
+
expect(messages[1].role).toBe("assistant");
|
|
723
|
+
expect(messages[1].content).toHaveLength(1);
|
|
724
|
+
expect(messages[1].content[0]).toMatchObject({
|
|
725
|
+
type: "tool_use",
|
|
726
|
+
id: "tool_1",
|
|
727
|
+
name: "get_weather",
|
|
728
|
+
});
|
|
485
729
|
});
|
|
486
|
-
it("
|
|
730
|
+
it("creates synthetic message when no messages exist", () => {
|
|
487
731
|
const state = createTestStreamState("thread_1");
|
|
488
732
|
state.threadMap.thread_1.thread.messages = [];
|
|
489
733
|
const event = {
|
|
@@ -492,13 +736,22 @@ describe("streamReducer", () => {
|
|
|
492
736
|
toolCallName: "get_weather",
|
|
493
737
|
// No parentMessageId, no messages
|
|
494
738
|
};
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
|
|
739
|
+
// When no messages exist, creates a synthetic message
|
|
740
|
+
const result = streamReducer(state, {
|
|
741
|
+
type: "EVENT",
|
|
742
|
+
event,
|
|
743
|
+
threadId: "thread_1",
|
|
744
|
+
});
|
|
745
|
+
const messages = result.threadMap.thread_1.thread.messages;
|
|
746
|
+
expect(messages).toHaveLength(1);
|
|
747
|
+
expect(messages[0].id).toBe("msg_tool_tool_1");
|
|
748
|
+
expect(messages[0].role).toBe("assistant");
|
|
749
|
+
expect(messages[0].content).toHaveLength(1);
|
|
750
|
+
expect(messages[0].content[0]).toMatchObject({
|
|
751
|
+
type: "tool_use",
|
|
752
|
+
id: "tool_1",
|
|
753
|
+
name: "get_weather",
|
|
754
|
+
});
|
|
502
755
|
});
|
|
503
756
|
});
|
|
504
757
|
describe("TOOL_CALL_ARGS and TOOL_CALL_END events", () => {
|
|
@@ -573,6 +826,67 @@ describe("streamReducer", () => {
|
|
|
573
826
|
const toolContent = asToolUseContent(result.threadMap.thread_1.thread.messages[0].content, 0);
|
|
574
827
|
expect(toolContent.input).toEqual({ city: "NYC" });
|
|
575
828
|
});
|
|
829
|
+
it("optimistically parses partial tool args during streaming", () => {
|
|
830
|
+
const state = createTestStreamState("thread_1");
|
|
831
|
+
state.threadMap.thread_1.thread.messages = [
|
|
832
|
+
{
|
|
833
|
+
id: "msg_1",
|
|
834
|
+
role: "assistant",
|
|
835
|
+
content: [
|
|
836
|
+
{ type: "tool_use", id: "tool_1", name: "test", input: {} },
|
|
837
|
+
],
|
|
838
|
+
createdAt: "2024-01-01T00:00:00.000Z",
|
|
839
|
+
},
|
|
840
|
+
];
|
|
841
|
+
// First chunk: partial key — partial-json can parse this into { city: "" }
|
|
842
|
+
let result = streamReducer(state, {
|
|
843
|
+
type: "EVENT",
|
|
844
|
+
event: {
|
|
845
|
+
type: EventType.TOOL_CALL_ARGS,
|
|
846
|
+
toolCallId: "tool_1",
|
|
847
|
+
delta: '{"city": "N',
|
|
848
|
+
},
|
|
849
|
+
threadId: "thread_1",
|
|
850
|
+
});
|
|
851
|
+
let toolContent = asToolUseContent(result.threadMap.thread_1.thread.messages[0].content, 0);
|
|
852
|
+
// partial-json parses incomplete string values
|
|
853
|
+
expect(toolContent.input).toEqual({ city: "N" });
|
|
854
|
+
// Second chunk: complete city, start of units
|
|
855
|
+
result = streamReducer(result, {
|
|
856
|
+
type: "EVENT",
|
|
857
|
+
event: {
|
|
858
|
+
type: EventType.TOOL_CALL_ARGS,
|
|
859
|
+
toolCallId: "tool_1",
|
|
860
|
+
delta: 'YC", "units": "fahr',
|
|
861
|
+
},
|
|
862
|
+
threadId: "thread_1",
|
|
863
|
+
});
|
|
864
|
+
toolContent = asToolUseContent(result.threadMap.thread_1.thread.messages[0].content, 0);
|
|
865
|
+
expect(toolContent.input).toEqual({ city: "NYC", units: "fahr" });
|
|
866
|
+
// Final chunk + TOOL_CALL_END
|
|
867
|
+
result = streamReducer(result, {
|
|
868
|
+
type: "EVENT",
|
|
869
|
+
event: {
|
|
870
|
+
type: EventType.TOOL_CALL_ARGS,
|
|
871
|
+
toolCallId: "tool_1",
|
|
872
|
+
delta: 'enheit"}',
|
|
873
|
+
},
|
|
874
|
+
threadId: "thread_1",
|
|
875
|
+
});
|
|
876
|
+
result = streamReducer(result, {
|
|
877
|
+
type: "EVENT",
|
|
878
|
+
event: {
|
|
879
|
+
type: EventType.TOOL_CALL_END,
|
|
880
|
+
toolCallId: "tool_1",
|
|
881
|
+
},
|
|
882
|
+
threadId: "thread_1",
|
|
883
|
+
});
|
|
884
|
+
toolContent = asToolUseContent(result.threadMap.thread_1.thread.messages[0].content, 0);
|
|
885
|
+
expect(toolContent.input).toEqual({
|
|
886
|
+
city: "NYC",
|
|
887
|
+
units: "fahrenheit",
|
|
888
|
+
});
|
|
889
|
+
});
|
|
576
890
|
it("throws when tool arguments JSON is invalid", () => {
|
|
577
891
|
const state = createTestStreamState("thread_1");
|
|
578
892
|
state.threadMap.thread_1.thread.messages = [
|
|
@@ -712,7 +1026,12 @@ describe("streamReducer", () => {
|
|
|
712
1026
|
const event = {
|
|
713
1027
|
type: EventType.CUSTOM,
|
|
714
1028
|
name: "tambo.run.awaiting_input",
|
|
715
|
-
value: {
|
|
1029
|
+
value: {
|
|
1030
|
+
pendingToolCalls: [
|
|
1031
|
+
{ toolCallId: "tool_1", toolName: "test1", arguments: "{}" },
|
|
1032
|
+
{ toolCallId: "tool_2", toolName: "test2", arguments: "{}" },
|
|
1033
|
+
],
|
|
1034
|
+
},
|
|
716
1035
|
};
|
|
717
1036
|
const result = streamReducer(state, {
|
|
718
1037
|
type: "EVENT",
|
|
@@ -794,7 +1113,7 @@ describe("streamReducer", () => {
|
|
|
794
1113
|
streamingState: "done",
|
|
795
1114
|
});
|
|
796
1115
|
});
|
|
797
|
-
it("
|
|
1116
|
+
it("creates message on-demand when not found for tambo.component.start", () => {
|
|
798
1117
|
const state = createTestStreamState("thread_1");
|
|
799
1118
|
state.threadMap.thread_1.thread.messages = [
|
|
800
1119
|
{
|
|
@@ -813,13 +1132,24 @@ describe("streamReducer", () => {
|
|
|
813
1132
|
componentName: "Test",
|
|
814
1133
|
},
|
|
815
1134
|
};
|
|
816
|
-
|
|
817
|
-
|
|
818
|
-
|
|
819
|
-
|
|
820
|
-
|
|
821
|
-
|
|
822
|
-
|
|
1135
|
+
// Should create the message on-demand instead of throwing
|
|
1136
|
+
const result = streamReducer(state, {
|
|
1137
|
+
type: "EVENT",
|
|
1138
|
+
event,
|
|
1139
|
+
threadId: "thread_1",
|
|
1140
|
+
});
|
|
1141
|
+
// Verify the message was created
|
|
1142
|
+
const messages = result.threadMap.thread_1.thread.messages;
|
|
1143
|
+
expect(messages).toHaveLength(2);
|
|
1144
|
+
expect(messages[1].id).toBe("unknown_msg");
|
|
1145
|
+
expect(messages[1].role).toBe("assistant");
|
|
1146
|
+
// And the component was added to it
|
|
1147
|
+
expect(messages[1].content).toHaveLength(1);
|
|
1148
|
+
expect(messages[1].content[0]).toMatchObject({
|
|
1149
|
+
type: "component",
|
|
1150
|
+
id: "comp_1",
|
|
1151
|
+
name: "Test",
|
|
1152
|
+
});
|
|
823
1153
|
});
|
|
824
1154
|
it("throws when component not found for tambo.component.end", () => {
|
|
825
1155
|
const state = createTestStreamState("thread_1");
|
|
@@ -1199,5 +1529,688 @@ describe("streamReducer", () => {
|
|
|
1199
1529
|
expect(snapshot).toMatchSnapshot();
|
|
1200
1530
|
});
|
|
1201
1531
|
});
|
|
1532
|
+
describe("thinking events", () => {
|
|
1533
|
+
it("handles THINKING_TEXT_MESSAGE_START event", () => {
|
|
1534
|
+
const state = createTestStreamState("thread_1");
|
|
1535
|
+
state.threadMap.thread_1.thread.messages = [
|
|
1536
|
+
{
|
|
1537
|
+
id: "msg_1",
|
|
1538
|
+
role: "assistant",
|
|
1539
|
+
content: [],
|
|
1540
|
+
createdAt: "2024-01-01T00:00:00.000Z",
|
|
1541
|
+
},
|
|
1542
|
+
];
|
|
1543
|
+
state.threadMap.thread_1.streaming.messageId = "msg_1";
|
|
1544
|
+
const event = {
|
|
1545
|
+
type: EventType.THINKING_TEXT_MESSAGE_START,
|
|
1546
|
+
timestamp: 1704067200000,
|
|
1547
|
+
};
|
|
1548
|
+
const result = streamReducer(state, {
|
|
1549
|
+
type: "EVENT",
|
|
1550
|
+
event,
|
|
1551
|
+
threadId: "thread_1",
|
|
1552
|
+
});
|
|
1553
|
+
const message = result.threadMap.thread_1.thread.messages[0];
|
|
1554
|
+
expect(message.reasoning).toEqual([""]);
|
|
1555
|
+
expect(result.threadMap.thread_1.streaming.reasoningStartTime).toBeDefined();
|
|
1556
|
+
});
|
|
1557
|
+
it("handles THINKING_TEXT_MESSAGE_CONTENT event", () => {
|
|
1558
|
+
const state = createTestStreamState("thread_1");
|
|
1559
|
+
state.threadMap.thread_1.thread.messages = [
|
|
1560
|
+
{
|
|
1561
|
+
id: "msg_1",
|
|
1562
|
+
role: "assistant",
|
|
1563
|
+
content: [],
|
|
1564
|
+
createdAt: "2024-01-01T00:00:00.000Z",
|
|
1565
|
+
reasoning: [""],
|
|
1566
|
+
},
|
|
1567
|
+
];
|
|
1568
|
+
state.threadMap.thread_1.streaming.messageId = "msg_1";
|
|
1569
|
+
state.threadMap.thread_1.streaming.reasoningStartTime = 1704067200000;
|
|
1570
|
+
const event = {
|
|
1571
|
+
type: EventType.THINKING_TEXT_MESSAGE_CONTENT,
|
|
1572
|
+
delta: "Let me think about this...",
|
|
1573
|
+
};
|
|
1574
|
+
const result = streamReducer(state, {
|
|
1575
|
+
type: "EVENT",
|
|
1576
|
+
event,
|
|
1577
|
+
threadId: "thread_1",
|
|
1578
|
+
});
|
|
1579
|
+
const message = result.threadMap.thread_1.thread.messages[0];
|
|
1580
|
+
expect(message.reasoning).toEqual(["Let me think about this..."]);
|
|
1581
|
+
});
|
|
1582
|
+
it("accumulates multiple thinking content deltas", () => {
|
|
1583
|
+
const state = createTestStreamState("thread_1");
|
|
1584
|
+
state.threadMap.thread_1.thread.messages = [
|
|
1585
|
+
{
|
|
1586
|
+
id: "msg_1",
|
|
1587
|
+
role: "assistant",
|
|
1588
|
+
content: [],
|
|
1589
|
+
createdAt: "2024-01-01T00:00:00.000Z",
|
|
1590
|
+
reasoning: ["First part "],
|
|
1591
|
+
},
|
|
1592
|
+
];
|
|
1593
|
+
state.threadMap.thread_1.streaming.messageId = "msg_1";
|
|
1594
|
+
const event = {
|
|
1595
|
+
type: EventType.THINKING_TEXT_MESSAGE_CONTENT,
|
|
1596
|
+
delta: "second part",
|
|
1597
|
+
};
|
|
1598
|
+
const result = streamReducer(state, {
|
|
1599
|
+
type: "EVENT",
|
|
1600
|
+
event,
|
|
1601
|
+
threadId: "thread_1",
|
|
1602
|
+
});
|
|
1603
|
+
const message = result.threadMap.thread_1.thread.messages[0];
|
|
1604
|
+
expect(message.reasoning).toEqual(["First part second part"]);
|
|
1605
|
+
});
|
|
1606
|
+
it("handles THINKING_TEXT_MESSAGE_END event and calculates duration", () => {
|
|
1607
|
+
const startTime = 1704067200000; // Fixed start time
|
|
1608
|
+
const endTime = 1704067205000; // 5 seconds later
|
|
1609
|
+
const state = createTestStreamState("thread_1");
|
|
1610
|
+
state.threadMap.thread_1.thread.messages = [
|
|
1611
|
+
{
|
|
1612
|
+
id: "msg_1",
|
|
1613
|
+
role: "assistant",
|
|
1614
|
+
content: [],
|
|
1615
|
+
createdAt: "2024-01-01T00:00:00.000Z",
|
|
1616
|
+
reasoning: ["Some thinking content"],
|
|
1617
|
+
},
|
|
1618
|
+
];
|
|
1619
|
+
state.threadMap.thread_1.streaming.messageId = "msg_1";
|
|
1620
|
+
state.threadMap.thread_1.streaming.reasoningStartTime = startTime;
|
|
1621
|
+
const event = {
|
|
1622
|
+
type: EventType.THINKING_TEXT_MESSAGE_END,
|
|
1623
|
+
timestamp: endTime,
|
|
1624
|
+
};
|
|
1625
|
+
const result = streamReducer(state, {
|
|
1626
|
+
type: "EVENT",
|
|
1627
|
+
event,
|
|
1628
|
+
threadId: "thread_1",
|
|
1629
|
+
});
|
|
1630
|
+
const message = result.threadMap.thread_1.thread.messages[0];
|
|
1631
|
+
expect(message.reasoningDurationMS).toBe(5000);
|
|
1632
|
+
});
|
|
1633
|
+
it("handles multiple thinking chunks", () => {
|
|
1634
|
+
let state = createTestStreamState("thread_1");
|
|
1635
|
+
state.threadMap.thread_1.thread.messages = [
|
|
1636
|
+
{
|
|
1637
|
+
id: "msg_1",
|
|
1638
|
+
role: "assistant",
|
|
1639
|
+
content: [],
|
|
1640
|
+
createdAt: "2024-01-01T00:00:00.000Z",
|
|
1641
|
+
},
|
|
1642
|
+
];
|
|
1643
|
+
state.threadMap.thread_1.streaming.messageId = "msg_1";
|
|
1644
|
+
// First thinking chunk
|
|
1645
|
+
const start1 = {
|
|
1646
|
+
type: EventType.THINKING_TEXT_MESSAGE_START,
|
|
1647
|
+
};
|
|
1648
|
+
state = streamReducer(state, {
|
|
1649
|
+
type: "EVENT",
|
|
1650
|
+
event: start1,
|
|
1651
|
+
threadId: "thread_1",
|
|
1652
|
+
});
|
|
1653
|
+
const content1 = {
|
|
1654
|
+
type: EventType.THINKING_TEXT_MESSAGE_CONTENT,
|
|
1655
|
+
delta: "First thought",
|
|
1656
|
+
};
|
|
1657
|
+
state = streamReducer(state, {
|
|
1658
|
+
type: "EVENT",
|
|
1659
|
+
event: content1,
|
|
1660
|
+
threadId: "thread_1",
|
|
1661
|
+
});
|
|
1662
|
+
const end1 = {
|
|
1663
|
+
type: EventType.THINKING_TEXT_MESSAGE_END,
|
|
1664
|
+
};
|
|
1665
|
+
state = streamReducer(state, {
|
|
1666
|
+
type: "EVENT",
|
|
1667
|
+
event: end1,
|
|
1668
|
+
threadId: "thread_1",
|
|
1669
|
+
});
|
|
1670
|
+
// Second thinking chunk
|
|
1671
|
+
const start2 = {
|
|
1672
|
+
type: EventType.THINKING_TEXT_MESSAGE_START,
|
|
1673
|
+
};
|
|
1674
|
+
state = streamReducer(state, {
|
|
1675
|
+
type: "EVENT",
|
|
1676
|
+
event: start2,
|
|
1677
|
+
threadId: "thread_1",
|
|
1678
|
+
});
|
|
1679
|
+
const content2 = {
|
|
1680
|
+
type: EventType.THINKING_TEXT_MESSAGE_CONTENT,
|
|
1681
|
+
delta: "Second thought",
|
|
1682
|
+
};
|
|
1683
|
+
state = streamReducer(state, {
|
|
1684
|
+
type: "EVENT",
|
|
1685
|
+
event: content2,
|
|
1686
|
+
threadId: "thread_1",
|
|
1687
|
+
});
|
|
1688
|
+
const end2 = {
|
|
1689
|
+
type: EventType.THINKING_TEXT_MESSAGE_END,
|
|
1690
|
+
};
|
|
1691
|
+
state = streamReducer(state, {
|
|
1692
|
+
type: "EVENT",
|
|
1693
|
+
event: end2,
|
|
1694
|
+
threadId: "thread_1",
|
|
1695
|
+
});
|
|
1696
|
+
const message = state.threadMap.thread_1.thread.messages[0];
|
|
1697
|
+
expect(message.reasoning).toEqual(["First thought", "Second thought"]);
|
|
1698
|
+
});
|
|
1699
|
+
it("handles thinking content without explicit start event", () => {
|
|
1700
|
+
const state = createTestStreamState("thread_1");
|
|
1701
|
+
state.threadMap.thread_1.thread.messages = [
|
|
1702
|
+
{
|
|
1703
|
+
id: "msg_1",
|
|
1704
|
+
role: "assistant",
|
|
1705
|
+
content: [],
|
|
1706
|
+
createdAt: "2024-01-01T00:00:00.000Z",
|
|
1707
|
+
// No thinking array yet
|
|
1708
|
+
},
|
|
1709
|
+
];
|
|
1710
|
+
state.threadMap.thread_1.streaming.messageId = "msg_1";
|
|
1711
|
+
const event = {
|
|
1712
|
+
type: EventType.THINKING_TEXT_MESSAGE_CONTENT,
|
|
1713
|
+
delta: "Implicit start",
|
|
1714
|
+
};
|
|
1715
|
+
const result = streamReducer(state, {
|
|
1716
|
+
type: "EVENT",
|
|
1717
|
+
event,
|
|
1718
|
+
threadId: "thread_1",
|
|
1719
|
+
});
|
|
1720
|
+
const message = result.threadMap.thread_1.thread.messages[0];
|
|
1721
|
+
expect(message.reasoning).toEqual(["Implicit start"]);
|
|
1722
|
+
expect(result.threadMap.thread_1.streaming.reasoningStartTime).toBeDefined();
|
|
1723
|
+
});
|
|
1724
|
+
it("creates ephemeral message when no message exists", () => {
|
|
1725
|
+
const state = createTestStreamState("thread_1");
|
|
1726
|
+
state.threadMap.thread_1.thread.messages = [];
|
|
1727
|
+
// No messageId set
|
|
1728
|
+
const event = {
|
|
1729
|
+
type: EventType.THINKING_TEXT_MESSAGE_START,
|
|
1730
|
+
timestamp: 1704067200000,
|
|
1731
|
+
};
|
|
1732
|
+
const result = streamReducer(state, {
|
|
1733
|
+
type: "EVENT",
|
|
1734
|
+
event,
|
|
1735
|
+
threadId: "thread_1",
|
|
1736
|
+
});
|
|
1737
|
+
// Should have created an ephemeral assistant message with reasoning
|
|
1738
|
+
expect(result.threadMap.thread_1.thread.messages).toHaveLength(1);
|
|
1739
|
+
const message = result.threadMap.thread_1.thread.messages[0];
|
|
1740
|
+
expect(message.id).toMatch(/^ephemeral_/);
|
|
1741
|
+
expect(message.role).toBe("assistant");
|
|
1742
|
+
expect(message.reasoning).toEqual([""]);
|
|
1743
|
+
expect(result.threadMap.thread_1.streaming.reasoningStartTime).toBe(1704067200000);
|
|
1744
|
+
expect(result.threadMap.thread_1.streaming.messageId).toBe(message.id);
|
|
1745
|
+
});
|
|
1746
|
+
it("uses last message when no messageId in streaming state", () => {
|
|
1747
|
+
const state = createTestStreamState("thread_1");
|
|
1748
|
+
state.threadMap.thread_1.thread.messages = [
|
|
1749
|
+
{
|
|
1750
|
+
id: "msg_1",
|
|
1751
|
+
role: "assistant",
|
|
1752
|
+
content: [],
|
|
1753
|
+
createdAt: "2024-01-01T00:00:00.000Z",
|
|
1754
|
+
},
|
|
1755
|
+
];
|
|
1756
|
+
// No messageId set in streaming state
|
|
1757
|
+
const event = {
|
|
1758
|
+
type: EventType.THINKING_TEXT_MESSAGE_START,
|
|
1759
|
+
};
|
|
1760
|
+
const result = streamReducer(state, {
|
|
1761
|
+
type: "EVENT",
|
|
1762
|
+
event,
|
|
1763
|
+
threadId: "thread_1",
|
|
1764
|
+
});
|
|
1765
|
+
const message = result.threadMap.thread_1.thread.messages[0];
|
|
1766
|
+
expect(message.reasoning).toEqual([""]);
|
|
1767
|
+
});
|
|
1768
|
+
it("merges ephemeral reasoning message with subsequent TEXT_MESSAGE_START", () => {
|
|
1769
|
+
let state = createTestStreamState("thread_1");
|
|
1770
|
+
state.threadMap.thread_1.thread.messages = [];
|
|
1771
|
+
// Simulate reasoning events arriving before text message
|
|
1772
|
+
const thinkingStart = {
|
|
1773
|
+
type: EventType.THINKING_TEXT_MESSAGE_START,
|
|
1774
|
+
timestamp: 1704067200000,
|
|
1775
|
+
};
|
|
1776
|
+
state = streamReducer(state, {
|
|
1777
|
+
type: "EVENT",
|
|
1778
|
+
event: thinkingStart,
|
|
1779
|
+
threadId: "thread_1",
|
|
1780
|
+
});
|
|
1781
|
+
const thinkingContent = {
|
|
1782
|
+
type: EventType.THINKING_TEXT_MESSAGE_CONTENT,
|
|
1783
|
+
delta: "Let me think about this...",
|
|
1784
|
+
};
|
|
1785
|
+
state = streamReducer(state, {
|
|
1786
|
+
type: "EVENT",
|
|
1787
|
+
event: thinkingContent,
|
|
1788
|
+
threadId: "thread_1",
|
|
1789
|
+
});
|
|
1790
|
+
// Verify ephemeral message was created
|
|
1791
|
+
expect(state.threadMap.thread_1.thread.messages).toHaveLength(1);
|
|
1792
|
+
const ephemeralMessage = state.threadMap.thread_1.thread.messages[0];
|
|
1793
|
+
expect(ephemeralMessage.id).toMatch(/^ephemeral_/);
|
|
1794
|
+
expect(ephemeralMessage.reasoning).toEqual([
|
|
1795
|
+
"Let me think about this...",
|
|
1796
|
+
]);
|
|
1797
|
+
// Now TEXT_MESSAGE_START arrives - should merge with ephemeral message
|
|
1798
|
+
const textStart = {
|
|
1799
|
+
type: EventType.TEXT_MESSAGE_START,
|
|
1800
|
+
messageId: "msg_real_123",
|
|
1801
|
+
role: "assistant",
|
|
1802
|
+
};
|
|
1803
|
+
state = streamReducer(state, {
|
|
1804
|
+
type: "EVENT",
|
|
1805
|
+
event: textStart,
|
|
1806
|
+
threadId: "thread_1",
|
|
1807
|
+
});
|
|
1808
|
+
// Should still have only one message (merged)
|
|
1809
|
+
expect(state.threadMap.thread_1.thread.messages).toHaveLength(1);
|
|
1810
|
+
const mergedMessage = state.threadMap.thread_1.thread.messages[0];
|
|
1811
|
+
// The message should have the real ID now
|
|
1812
|
+
expect(mergedMessage.id).toBe("msg_real_123");
|
|
1813
|
+
// But should preserve the reasoning
|
|
1814
|
+
expect(mergedMessage.reasoning).toEqual(["Let me think about this..."]);
|
|
1815
|
+
expect(mergedMessage.role).toBe("assistant");
|
|
1816
|
+
// Streaming state should track the new message ID
|
|
1817
|
+
expect(state.threadMap.thread_1.streaming.messageId).toBe("msg_real_123");
|
|
1818
|
+
});
|
|
1819
|
+
it("matches snapshot for full thinking flow", () => {
|
|
1820
|
+
let state = createTestStreamState("thread_1");
|
|
1821
|
+
// Add an assistant message
|
|
1822
|
+
state.threadMap.thread_1.thread.messages = [
|
|
1823
|
+
{
|
|
1824
|
+
id: "msg_1",
|
|
1825
|
+
role: "assistant",
|
|
1826
|
+
content: [],
|
|
1827
|
+
createdAt: "2024-01-01T00:00:00.000Z",
|
|
1828
|
+
},
|
|
1829
|
+
];
|
|
1830
|
+
state.threadMap.thread_1.streaming.messageId = "msg_1";
|
|
1831
|
+
// THINKING_TEXT_MESSAGE_START
|
|
1832
|
+
const thinkingStart = {
|
|
1833
|
+
type: EventType.THINKING_TEXT_MESSAGE_START,
|
|
1834
|
+
timestamp: 1704067200000,
|
|
1835
|
+
};
|
|
1836
|
+
state = streamReducer(state, {
|
|
1837
|
+
type: "EVENT",
|
|
1838
|
+
event: thinkingStart,
|
|
1839
|
+
threadId: "thread_1",
|
|
1840
|
+
});
|
|
1841
|
+
// THINKING_TEXT_MESSAGE_CONTENT
|
|
1842
|
+
const thinkingContent = {
|
|
1843
|
+
type: EventType.THINKING_TEXT_MESSAGE_CONTENT,
|
|
1844
|
+
delta: "Let me analyze this step by step...",
|
|
1845
|
+
};
|
|
1846
|
+
state = streamReducer(state, {
|
|
1847
|
+
type: "EVENT",
|
|
1848
|
+
event: thinkingContent,
|
|
1849
|
+
threadId: "thread_1",
|
|
1850
|
+
});
|
|
1851
|
+
// THINKING_TEXT_MESSAGE_END
|
|
1852
|
+
const thinkingEnd = {
|
|
1853
|
+
type: EventType.THINKING_TEXT_MESSAGE_END,
|
|
1854
|
+
};
|
|
1855
|
+
state = streamReducer(state, {
|
|
1856
|
+
type: "EVENT",
|
|
1857
|
+
event: thinkingEnd,
|
|
1858
|
+
threadId: "thread_1",
|
|
1859
|
+
});
|
|
1860
|
+
// TEXT_MESSAGE_CONTENT (actual response after thinking)
|
|
1861
|
+
const msgContent = {
|
|
1862
|
+
type: EventType.TEXT_MESSAGE_CONTENT,
|
|
1863
|
+
messageId: "msg_1",
|
|
1864
|
+
delta: "Based on my analysis, here's what I think...",
|
|
1865
|
+
};
|
|
1866
|
+
state = streamReducer(state, {
|
|
1867
|
+
type: "EVENT",
|
|
1868
|
+
event: msgContent,
|
|
1869
|
+
threadId: "thread_1",
|
|
1870
|
+
});
|
|
1871
|
+
// Normalize for snapshot stability
|
|
1872
|
+
const snapshot = {
|
|
1873
|
+
...state,
|
|
1874
|
+
threadMap: {
|
|
1875
|
+
thread_1: {
|
|
1876
|
+
...state.threadMap.thread_1,
|
|
1877
|
+
thread: {
|
|
1878
|
+
...state.threadMap.thread_1.thread,
|
|
1879
|
+
messages: state.threadMap.thread_1.thread.messages.map((m) => ({
|
|
1880
|
+
...m,
|
|
1881
|
+
createdAt: "[TIMESTAMP]",
|
|
1882
|
+
// Keep reasoningDurationMS but normalize it for snapshot
|
|
1883
|
+
reasoningDurationMS: m.reasoningDurationMS
|
|
1884
|
+
? "[DURATION]"
|
|
1885
|
+
: undefined,
|
|
1886
|
+
})),
|
|
1887
|
+
createdAt: "[TIMESTAMP]",
|
|
1888
|
+
updatedAt: "[TIMESTAMP]",
|
|
1889
|
+
},
|
|
1890
|
+
streaming: {
|
|
1891
|
+
...state.threadMap.thread_1.streaming,
|
|
1892
|
+
reasoningStartTime: state.threadMap.thread_1.streaming
|
|
1893
|
+
.reasoningStartTime
|
|
1894
|
+
? "[TIMESTAMP]"
|
|
1895
|
+
: undefined,
|
|
1896
|
+
},
|
|
1897
|
+
},
|
|
1898
|
+
},
|
|
1899
|
+
};
|
|
1900
|
+
expect(snapshot).toMatchSnapshot();
|
|
1901
|
+
});
|
|
1902
|
+
});
|
|
1903
|
+
describe("LOAD_THREAD_MESSAGES action", () => {
|
|
1904
|
+
it("loads messages into empty thread", () => {
|
|
1905
|
+
const state = createTestStreamState("thread_1");
|
|
1906
|
+
const result = streamReducer(state, {
|
|
1907
|
+
type: "LOAD_THREAD_MESSAGES",
|
|
1908
|
+
threadId: "thread_1",
|
|
1909
|
+
messages: [
|
|
1910
|
+
{
|
|
1911
|
+
id: "msg_1",
|
|
1912
|
+
role: "user",
|
|
1913
|
+
content: [{ type: "text", text: "Hello" }],
|
|
1914
|
+
createdAt: "2024-01-01T00:00:00.000Z",
|
|
1915
|
+
},
|
|
1916
|
+
{
|
|
1917
|
+
id: "msg_2",
|
|
1918
|
+
role: "assistant",
|
|
1919
|
+
content: [{ type: "text", text: "Hi there!" }],
|
|
1920
|
+
createdAt: "2024-01-01T00:00:01.000Z",
|
|
1921
|
+
},
|
|
1922
|
+
],
|
|
1923
|
+
});
|
|
1924
|
+
expect(result.threadMap.thread_1.thread.messages).toHaveLength(2);
|
|
1925
|
+
expect(result.threadMap.thread_1.thread.messages[0].id).toBe("msg_1");
|
|
1926
|
+
expect(result.threadMap.thread_1.thread.messages[1].id).toBe("msg_2");
|
|
1927
|
+
});
|
|
1928
|
+
it("creates thread if it does not exist", () => {
|
|
1929
|
+
const state = createInitialState();
|
|
1930
|
+
const result = streamReducer(state, {
|
|
1931
|
+
type: "LOAD_THREAD_MESSAGES",
|
|
1932
|
+
threadId: "new_thread",
|
|
1933
|
+
messages: [
|
|
1934
|
+
{
|
|
1935
|
+
id: "msg_1",
|
|
1936
|
+
role: "user",
|
|
1937
|
+
content: [{ type: "text", text: "Hello" }],
|
|
1938
|
+
createdAt: "2024-01-01T00:00:00.000Z",
|
|
1939
|
+
},
|
|
1940
|
+
],
|
|
1941
|
+
});
|
|
1942
|
+
expect(result.threadMap.new_thread).toBeDefined();
|
|
1943
|
+
expect(result.threadMap.new_thread.thread.id).toBe("new_thread");
|
|
1944
|
+
expect(result.threadMap.new_thread.thread.messages).toHaveLength(1);
|
|
1945
|
+
});
|
|
1946
|
+
it("deduplicates by message ID, keeping existing messages", () => {
|
|
1947
|
+
const state = createTestStreamState("thread_1");
|
|
1948
|
+
// Add an existing message
|
|
1949
|
+
state.threadMap.thread_1.thread.messages = [
|
|
1950
|
+
{
|
|
1951
|
+
id: "msg_1",
|
|
1952
|
+
role: "user",
|
|
1953
|
+
content: [{ type: "text", text: "Existing content" }],
|
|
1954
|
+
createdAt: "2024-01-01T00:00:00.000Z",
|
|
1955
|
+
},
|
|
1956
|
+
];
|
|
1957
|
+
const result = streamReducer(state, {
|
|
1958
|
+
type: "LOAD_THREAD_MESSAGES",
|
|
1959
|
+
threadId: "thread_1",
|
|
1960
|
+
messages: [
|
|
1961
|
+
{
|
|
1962
|
+
id: "msg_1",
|
|
1963
|
+
role: "user",
|
|
1964
|
+
content: [{ type: "text", text: "New content" }], // Different content
|
|
1965
|
+
createdAt: "2024-01-01T00:00:00.000Z",
|
|
1966
|
+
},
|
|
1967
|
+
{
|
|
1968
|
+
id: "msg_2",
|
|
1969
|
+
role: "assistant",
|
|
1970
|
+
content: [{ type: "text", text: "Response" }],
|
|
1971
|
+
createdAt: "2024-01-01T00:00:01.000Z",
|
|
1972
|
+
},
|
|
1973
|
+
],
|
|
1974
|
+
});
|
|
1975
|
+
expect(result.threadMap.thread_1.thread.messages).toHaveLength(2);
|
|
1976
|
+
// Existing message is kept (not replaced)
|
|
1977
|
+
expect(result.threadMap.thread_1.thread.messages[0].content[0]).toEqual({
|
|
1978
|
+
type: "text",
|
|
1979
|
+
text: "Existing content",
|
|
1980
|
+
});
|
|
1981
|
+
// New message is added
|
|
1982
|
+
expect(result.threadMap.thread_1.thread.messages[1].id).toBe("msg_2");
|
|
1983
|
+
});
|
|
1984
|
+
it("skips merge when streaming and skipIfStreaming is true", () => {
|
|
1985
|
+
const state = createTestStreamState("thread_1");
|
|
1986
|
+
state.threadMap.thread_1.streaming.status = "streaming";
|
|
1987
|
+
state.threadMap.thread_1.thread.messages = [
|
|
1988
|
+
{
|
|
1989
|
+
id: "msg_1",
|
|
1990
|
+
role: "user",
|
|
1991
|
+
content: [{ type: "text", text: "Hello" }],
|
|
1992
|
+
createdAt: "2024-01-01T00:00:00.000Z",
|
|
1993
|
+
},
|
|
1994
|
+
];
|
|
1995
|
+
const result = streamReducer(state, {
|
|
1996
|
+
type: "LOAD_THREAD_MESSAGES",
|
|
1997
|
+
threadId: "thread_1",
|
|
1998
|
+
messages: [
|
|
1999
|
+
{
|
|
2000
|
+
id: "msg_2",
|
|
2001
|
+
role: "assistant",
|
|
2002
|
+
content: [{ type: "text", text: "Response" }],
|
|
2003
|
+
createdAt: "2024-01-01T00:00:01.000Z",
|
|
2004
|
+
},
|
|
2005
|
+
],
|
|
2006
|
+
skipIfStreaming: true,
|
|
2007
|
+
});
|
|
2008
|
+
// State should be unchanged
|
|
2009
|
+
expect(result).toBe(state);
|
|
2010
|
+
expect(result.threadMap.thread_1.thread.messages).toHaveLength(1);
|
|
2011
|
+
});
|
|
2012
|
+
it("does merge when streaming and skipIfStreaming is false", () => {
|
|
2013
|
+
const state = createTestStreamState("thread_1");
|
|
2014
|
+
state.threadMap.thread_1.streaming.status = "streaming";
|
|
2015
|
+
state.threadMap.thread_1.thread.messages = [];
|
|
2016
|
+
const result = streamReducer(state, {
|
|
2017
|
+
type: "LOAD_THREAD_MESSAGES",
|
|
2018
|
+
threadId: "thread_1",
|
|
2019
|
+
messages: [
|
|
2020
|
+
{
|
|
2021
|
+
id: "msg_1",
|
|
2022
|
+
role: "user",
|
|
2023
|
+
content: [{ type: "text", text: "Hello" }],
|
|
2024
|
+
createdAt: "2024-01-01T00:00:00.000Z",
|
|
2025
|
+
},
|
|
2026
|
+
],
|
|
2027
|
+
skipIfStreaming: false,
|
|
2028
|
+
});
|
|
2029
|
+
expect(result.threadMap.thread_1.thread.messages).toHaveLength(1);
|
|
2030
|
+
});
|
|
2031
|
+
it("sorts messages by createdAt", () => {
|
|
2032
|
+
const state = createTestStreamState("thread_1");
|
|
2033
|
+
state.threadMap.thread_1.thread.messages = [
|
|
2034
|
+
{
|
|
2035
|
+
id: "msg_2",
|
|
2036
|
+
role: "assistant",
|
|
2037
|
+
content: [{ type: "text", text: "Response" }],
|
|
2038
|
+
createdAt: "2024-01-01T00:00:02.000Z",
|
|
2039
|
+
},
|
|
2040
|
+
];
|
|
2041
|
+
const result = streamReducer(state, {
|
|
2042
|
+
type: "LOAD_THREAD_MESSAGES",
|
|
2043
|
+
threadId: "thread_1",
|
|
2044
|
+
messages: [
|
|
2045
|
+
{
|
|
2046
|
+
id: "msg_1",
|
|
2047
|
+
role: "user",
|
|
2048
|
+
content: [{ type: "text", text: "Hello" }],
|
|
2049
|
+
createdAt: "2024-01-01T00:00:01.000Z",
|
|
2050
|
+
},
|
|
2051
|
+
{
|
|
2052
|
+
id: "msg_3",
|
|
2053
|
+
role: "assistant",
|
|
2054
|
+
content: [{ type: "text", text: "Goodbye" }],
|
|
2055
|
+
createdAt: "2024-01-01T00:00:03.000Z",
|
|
2056
|
+
},
|
|
2057
|
+
],
|
|
2058
|
+
});
|
|
2059
|
+
expect(result.threadMap.thread_1.thread.messages).toHaveLength(3);
|
|
2060
|
+
// Should be sorted by createdAt
|
|
2061
|
+
expect(result.threadMap.thread_1.thread.messages[0].id).toBe("msg_1");
|
|
2062
|
+
expect(result.threadMap.thread_1.thread.messages[1].id).toBe("msg_2");
|
|
2063
|
+
expect(result.threadMap.thread_1.thread.messages[2].id).toBe("msg_3");
|
|
2064
|
+
});
|
|
2065
|
+
it("handles messages without createdAt (places them at the end)", () => {
|
|
2066
|
+
const state = createTestStreamState("thread_1");
|
|
2067
|
+
state.threadMap.thread_1.thread.messages = [];
|
|
2068
|
+
const result = streamReducer(state, {
|
|
2069
|
+
type: "LOAD_THREAD_MESSAGES",
|
|
2070
|
+
threadId: "thread_1",
|
|
2071
|
+
messages: [
|
|
2072
|
+
{
|
|
2073
|
+
id: "msg_no_date",
|
|
2074
|
+
role: "assistant",
|
|
2075
|
+
content: [{ type: "text", text: "No date" }],
|
|
2076
|
+
// No createdAt
|
|
2077
|
+
},
|
|
2078
|
+
{
|
|
2079
|
+
id: "msg_with_date",
|
|
2080
|
+
role: "user",
|
|
2081
|
+
content: [{ type: "text", text: "Has date" }],
|
|
2082
|
+
createdAt: "2024-01-01T00:00:00.000Z",
|
|
2083
|
+
},
|
|
2084
|
+
],
|
|
2085
|
+
});
|
|
2086
|
+
expect(result.threadMap.thread_1.thread.messages).toHaveLength(2);
|
|
2087
|
+
// Message with date comes first, no date goes to end
|
|
2088
|
+
expect(result.threadMap.thread_1.thread.messages[0].id).toBe("msg_with_date");
|
|
2089
|
+
expect(result.threadMap.thread_1.thread.messages[1].id).toBe("msg_no_date");
|
|
2090
|
+
});
|
|
2091
|
+
it("sets streamingState to 'done' on component content blocks without streamingState", () => {
|
|
2092
|
+
const state = createTestStreamState("thread_1");
|
|
2093
|
+
const result = streamReducer(state, {
|
|
2094
|
+
type: "LOAD_THREAD_MESSAGES",
|
|
2095
|
+
threadId: "thread_1",
|
|
2096
|
+
messages: [
|
|
2097
|
+
{
|
|
2098
|
+
id: "msg_1",
|
|
2099
|
+
role: "assistant",
|
|
2100
|
+
content: [
|
|
2101
|
+
{ type: "text", text: "Here is a component" },
|
|
2102
|
+
{
|
|
2103
|
+
type: "component",
|
|
2104
|
+
id: "comp_1",
|
|
2105
|
+
name: "WeatherCard",
|
|
2106
|
+
props: { city: "SF", temperature: 72 },
|
|
2107
|
+
// No streamingState — simulates API-loaded message
|
|
2108
|
+
},
|
|
2109
|
+
],
|
|
2110
|
+
createdAt: "2024-01-01T00:00:00.000Z",
|
|
2111
|
+
},
|
|
2112
|
+
],
|
|
2113
|
+
});
|
|
2114
|
+
const messages = result.threadMap.thread_1.thread.messages;
|
|
2115
|
+
expect(messages).toHaveLength(1);
|
|
2116
|
+
const componentBlock = messages[0].content.find((c) => c.type === "component");
|
|
2117
|
+
expect(componentBlock).toBeDefined();
|
|
2118
|
+
expect(componentBlock.type).toBe("component");
|
|
2119
|
+
expect(componentBlock.streamingState).toBe("done");
|
|
2120
|
+
});
|
|
2121
|
+
it("overwrites non-done streamingState and warns", () => {
|
|
2122
|
+
const state = createTestStreamState("thread_1");
|
|
2123
|
+
const warnSpy = jest.spyOn(console, "warn").mockImplementation();
|
|
2124
|
+
const result = streamReducer(state, {
|
|
2125
|
+
type: "LOAD_THREAD_MESSAGES",
|
|
2126
|
+
threadId: "thread_1",
|
|
2127
|
+
messages: [
|
|
2128
|
+
{
|
|
2129
|
+
id: "msg_1",
|
|
2130
|
+
role: "assistant",
|
|
2131
|
+
content: [
|
|
2132
|
+
{
|
|
2133
|
+
type: "component",
|
|
2134
|
+
id: "comp_1",
|
|
2135
|
+
name: "WeatherCard",
|
|
2136
|
+
props: { city: "SF" },
|
|
2137
|
+
streamingState: "streaming",
|
|
2138
|
+
},
|
|
2139
|
+
],
|
|
2140
|
+
createdAt: "2024-01-01T00:00:00.000Z",
|
|
2141
|
+
},
|
|
2142
|
+
],
|
|
2143
|
+
});
|
|
2144
|
+
const messages = result.threadMap.thread_1.thread.messages;
|
|
2145
|
+
const componentBlock = messages[0].content.find((c) => c.type === "component");
|
|
2146
|
+
// Always set to "done" for API-loaded messages
|
|
2147
|
+
expect(componentBlock.streamingState).toBe("done");
|
|
2148
|
+
expect(warnSpy).toHaveBeenCalledWith(expect.stringContaining('unexpected streamingState "streaming"'));
|
|
2149
|
+
warnSpy.mockRestore();
|
|
2150
|
+
});
|
|
2151
|
+
it("handles system role messages from API", () => {
|
|
2152
|
+
const state = createTestStreamState("thread_1");
|
|
2153
|
+
const result = streamReducer(state, {
|
|
2154
|
+
type: "LOAD_THREAD_MESSAGES",
|
|
2155
|
+
threadId: "thread_1",
|
|
2156
|
+
messages: [
|
|
2157
|
+
{
|
|
2158
|
+
id: "msg_system",
|
|
2159
|
+
role: "system",
|
|
2160
|
+
content: [{ type: "text", text: "System prompt" }],
|
|
2161
|
+
createdAt: "2024-01-01T00:00:00.000Z",
|
|
2162
|
+
},
|
|
2163
|
+
{
|
|
2164
|
+
id: "msg_user",
|
|
2165
|
+
role: "user",
|
|
2166
|
+
content: [{ type: "text", text: "Hello" }],
|
|
2167
|
+
createdAt: "2024-01-01T00:00:01.000Z",
|
|
2168
|
+
},
|
|
2169
|
+
],
|
|
2170
|
+
});
|
|
2171
|
+
expect(result.threadMap.thread_1.thread.messages).toHaveLength(2);
|
|
2172
|
+
expect(result.threadMap.thread_1.thread.messages[0].role).toBe("system");
|
|
2173
|
+
expect(result.threadMap.thread_1.thread.messages[1].role).toBe("user");
|
|
2174
|
+
});
|
|
2175
|
+
});
|
|
2176
|
+
describe("UPDATE_THREAD_TITLE action", () => {
|
|
2177
|
+
it("updates the title on an existing thread", () => {
|
|
2178
|
+
const state = createTestStreamState("thread_1");
|
|
2179
|
+
const result = streamReducer(state, {
|
|
2180
|
+
type: "UPDATE_THREAD_TITLE",
|
|
2181
|
+
threadId: "thread_1",
|
|
2182
|
+
title: "My Chat Thread",
|
|
2183
|
+
});
|
|
2184
|
+
expect(result.threadMap.thread_1.thread.title).toBe("My Chat Thread");
|
|
2185
|
+
});
|
|
2186
|
+
it("returns unchanged state when thread does not exist", () => {
|
|
2187
|
+
const state = createTestStreamState("thread_1");
|
|
2188
|
+
const result = streamReducer(state, {
|
|
2189
|
+
type: "UPDATE_THREAD_TITLE",
|
|
2190
|
+
threadId: "nonexistent_thread",
|
|
2191
|
+
title: "My Chat Thread",
|
|
2192
|
+
});
|
|
2193
|
+
expect(result).toBe(state);
|
|
2194
|
+
});
|
|
2195
|
+
it("preserves other thread properties when updating title", () => {
|
|
2196
|
+
const state = createTestStreamState("thread_1");
|
|
2197
|
+
state.threadMap.thread_1.thread.messages = [
|
|
2198
|
+
{
|
|
2199
|
+
id: "msg_1",
|
|
2200
|
+
role: "user",
|
|
2201
|
+
content: [{ type: "text", text: "Hello" }],
|
|
2202
|
+
createdAt: "2024-01-01T00:00:00.000Z",
|
|
2203
|
+
},
|
|
2204
|
+
];
|
|
2205
|
+
const result = streamReducer(state, {
|
|
2206
|
+
type: "UPDATE_THREAD_TITLE",
|
|
2207
|
+
threadId: "thread_1",
|
|
2208
|
+
title: "My Chat Thread",
|
|
2209
|
+
});
|
|
2210
|
+
expect(result.threadMap.thread_1.thread.title).toBe("My Chat Thread");
|
|
2211
|
+
expect(result.threadMap.thread_1.thread.messages).toHaveLength(1);
|
|
2212
|
+
expect(result.threadMap.thread_1.thread.id).toBe("thread_1");
|
|
2213
|
+
});
|
|
2214
|
+
});
|
|
1202
2215
|
});
|
|
1203
2216
|
//# sourceMappingURL=event-accumulator.test.js.map
|