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