@tambo-ai/react 0.73.1 → 0.74.1
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.map +1 -1
- package/dist/mcp/mcp-hooks.js +70 -16
- 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-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/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 +1 -1
- package/esm/mcp/mcp-hooks.d.ts.map +1 -1
- package/esm/mcp/mcp-hooks.js +74 -20
- 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 +7 -7
- package/esm/providers/tambo-provider.js +10 -10
- 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.js +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
|
@@ -0,0 +1,130 @@
|
|
|
1
|
+
import { act, render, renderHook, screen } from "@testing-library/react";
|
|
2
|
+
import React from "react";
|
|
3
|
+
import { z } from "zod/v3";
|
|
4
|
+
import { withTamboInteractable } from "../../hoc/with-tambo-interactable.js";
|
|
5
|
+
import { useTamboInteractable } from "../../providers/tambo-interactable-provider.js";
|
|
6
|
+
import { useTamboContextHelpers } from "../../providers/tambo-context-helpers-provider.js";
|
|
7
|
+
import { TamboRegistryContext, } from "../../providers/tambo-registry-provider.js";
|
|
8
|
+
import { TamboContextHelpersProvider } from "../../providers/tambo-context-helpers-provider.js";
|
|
9
|
+
import { TamboInteractableProvider } from "../../providers/tambo-interactable-provider.js";
|
|
10
|
+
// Minimal registry mock that captures registered tools
|
|
11
|
+
function createMockRegistry() {
|
|
12
|
+
const toolRegistry = {};
|
|
13
|
+
return {
|
|
14
|
+
value: {
|
|
15
|
+
componentList: {},
|
|
16
|
+
toolRegistry,
|
|
17
|
+
componentToolAssociations: {},
|
|
18
|
+
mcpServerInfos: [],
|
|
19
|
+
resources: [],
|
|
20
|
+
resourceSource: null,
|
|
21
|
+
onCallUnregisteredTool: undefined,
|
|
22
|
+
registerComponent: jest.fn(),
|
|
23
|
+
registerTool: jest.fn((tool) => {
|
|
24
|
+
toolRegistry[tool.name] = tool;
|
|
25
|
+
}),
|
|
26
|
+
registerTools: jest.fn(),
|
|
27
|
+
addToolAssociation: jest.fn(),
|
|
28
|
+
registerMcpServer: jest.fn(),
|
|
29
|
+
registerMcpServers: jest.fn(),
|
|
30
|
+
registerResource: jest.fn(),
|
|
31
|
+
registerResources: jest.fn(),
|
|
32
|
+
registerResourceSource: jest.fn(),
|
|
33
|
+
},
|
|
34
|
+
getRegisteredToolNames: () => Object.keys(toolRegistry),
|
|
35
|
+
};
|
|
36
|
+
}
|
|
37
|
+
/**
|
|
38
|
+
* Wrapper that provides the minimal V1-compatible provider tree for interactables:
|
|
39
|
+
* TamboRegistryContext > TamboContextHelpersProvider > TamboInteractableProvider
|
|
40
|
+
*/
|
|
41
|
+
function V1InteractableWrapper({ children, registry, }) {
|
|
42
|
+
return (React.createElement(TamboRegistryContext.Provider, { value: registry },
|
|
43
|
+
React.createElement(TamboContextHelpersProvider, null,
|
|
44
|
+
React.createElement(TamboInteractableProvider, null, children))));
|
|
45
|
+
}
|
|
46
|
+
describe("V1 Interactables Integration", () => {
|
|
47
|
+
it("registers update_component_props and update_component_state tools when an interactable is added", () => {
|
|
48
|
+
const mockRegistry = createMockRegistry();
|
|
49
|
+
const { result } = renderHook(() => useTamboInteractable(), {
|
|
50
|
+
wrapper: ({ children }) => (React.createElement(V1InteractableWrapper, { registry: mockRegistry.value }, children)),
|
|
51
|
+
});
|
|
52
|
+
act(() => {
|
|
53
|
+
result.current.addInteractableComponent({
|
|
54
|
+
name: "TestWidget",
|
|
55
|
+
description: "A test widget",
|
|
56
|
+
component: () => React.createElement("div", null, "widget"),
|
|
57
|
+
props: { label: "hello" },
|
|
58
|
+
propsSchema: z.object({ label: z.string() }),
|
|
59
|
+
});
|
|
60
|
+
});
|
|
61
|
+
const toolNames = mockRegistry.getRegisteredToolNames();
|
|
62
|
+
expect(toolNames.some((n) => n.startsWith("update_component_props_TestWidget"))).toBe(true);
|
|
63
|
+
expect(toolNames.some((n) => n.startsWith("update_component_state_TestWidget"))).toBe(true);
|
|
64
|
+
});
|
|
65
|
+
it("registers interactables context helper that includes component info", async () => {
|
|
66
|
+
const mockRegistry = createMockRegistry();
|
|
67
|
+
const { result } = renderHook(() => ({
|
|
68
|
+
interactable: useTamboInteractable(),
|
|
69
|
+
helpers: useTamboContextHelpers(),
|
|
70
|
+
}), {
|
|
71
|
+
wrapper: ({ children }) => (React.createElement(V1InteractableWrapper, { registry: mockRegistry.value }, children)),
|
|
72
|
+
});
|
|
73
|
+
// Add an interactable component
|
|
74
|
+
act(() => {
|
|
75
|
+
result.current.interactable.addInteractableComponent({
|
|
76
|
+
name: "InfoCard",
|
|
77
|
+
description: "An info card",
|
|
78
|
+
component: () => React.createElement("div", null, "card"),
|
|
79
|
+
props: { title: "Test" },
|
|
80
|
+
});
|
|
81
|
+
});
|
|
82
|
+
// Get additional context - should include interactable info
|
|
83
|
+
const contexts = await act(async () => {
|
|
84
|
+
return await result.current.helpers.getAdditionalContext();
|
|
85
|
+
});
|
|
86
|
+
const interactablesContext = contexts.find((c) => c.name === "interactables");
|
|
87
|
+
expect(interactablesContext).toBeDefined();
|
|
88
|
+
expect(interactablesContext?.context).toBeDefined();
|
|
89
|
+
});
|
|
90
|
+
it("renders an interactable component via withTamboInteractable HOC", () => {
|
|
91
|
+
const mockRegistry = createMockRegistry();
|
|
92
|
+
const Card = ({ title }) => (React.createElement("div", { "data-testid": "card-title" }, title));
|
|
93
|
+
const InteractableCard = withTamboInteractable(Card, {
|
|
94
|
+
componentName: "Card",
|
|
95
|
+
description: "A card component",
|
|
96
|
+
propsSchema: z.object({ title: z.string() }),
|
|
97
|
+
});
|
|
98
|
+
render(React.createElement(V1InteractableWrapper, { registry: mockRegistry.value },
|
|
99
|
+
React.createElement(InteractableCard, { title: "Hello V1" })));
|
|
100
|
+
expect(screen.getByTestId("card-title")).toHaveTextContent("Hello V1");
|
|
101
|
+
});
|
|
102
|
+
it("updates component props via the interactable provider", () => {
|
|
103
|
+
const mockRegistry = createMockRegistry();
|
|
104
|
+
const Counter = ({ count }) => (React.createElement("div", { "data-testid": "count" }, count));
|
|
105
|
+
const InteractableCounter = withTamboInteractable(Counter, {
|
|
106
|
+
componentName: "Counter",
|
|
107
|
+
description: "A counter",
|
|
108
|
+
propsSchema: z.object({ count: z.number() }),
|
|
109
|
+
});
|
|
110
|
+
// Inner component that triggers prop updates
|
|
111
|
+
function TestHarness() {
|
|
112
|
+
const { interactableComponents, updateInteractableComponentProps } = useTamboInteractable();
|
|
113
|
+
const component = interactableComponents[0];
|
|
114
|
+
return (React.createElement("div", null,
|
|
115
|
+
React.createElement(InteractableCounter, { count: 0 }),
|
|
116
|
+
component && (React.createElement("button", { "data-testid": "update-btn", onClick: () => updateInteractableComponentProps(component.id, { count: 42 }) }, "Update"))));
|
|
117
|
+
}
|
|
118
|
+
render(React.createElement(V1InteractableWrapper, { registry: mockRegistry.value },
|
|
119
|
+
React.createElement(TestHarness, null)));
|
|
120
|
+
// Initial render
|
|
121
|
+
expect(screen.getByTestId("count")).toHaveTextContent("0");
|
|
122
|
+
// Update props via the interactable provider
|
|
123
|
+
act(() => {
|
|
124
|
+
screen.getByTestId("update-btn").click();
|
|
125
|
+
});
|
|
126
|
+
// The interactable should reflect updated props
|
|
127
|
+
expect(screen.getByTestId("count")).toHaveTextContent("42");
|
|
128
|
+
});
|
|
129
|
+
});
|
|
130
|
+
//# sourceMappingURL=v1-interactables.test.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"v1-interactables.test.js","sourceRoot":"","sources":["../../../src/v1/__tests__/v1-interactables.test.tsx"],"names":[],"mappings":"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","sourcesContent":["import { act, render, renderHook, screen } from \"@testing-library/react\";\nimport React from \"react\";\nimport { z } from \"zod/v3\";\nimport { withTamboInteractable } from \"../../hoc/with-tambo-interactable\";\nimport { useTamboInteractable } from \"../../providers/tambo-interactable-provider\";\nimport { useTamboContextHelpers } from \"../../providers/tambo-context-helpers-provider\";\nimport {\n TamboRegistryContext,\n type TamboRegistryContext as TamboRegistryContextType,\n} from \"../../providers/tambo-registry-provider\";\nimport { TamboContextHelpersProvider } from \"../../providers/tambo-context-helpers-provider\";\nimport { TamboInteractableProvider } from \"../../providers/tambo-interactable-provider\";\n\n// Minimal registry mock that captures registered tools\nfunction createMockRegistry() {\n const toolRegistry: Record<string, unknown> = {};\n return {\n value: {\n componentList: {},\n toolRegistry,\n componentToolAssociations: {},\n mcpServerInfos: [],\n resources: [],\n resourceSource: null,\n onCallUnregisteredTool: undefined,\n registerComponent: jest.fn(),\n registerTool: jest.fn((tool: { name: string }) => {\n toolRegistry[tool.name] = tool;\n }),\n registerTools: jest.fn(),\n addToolAssociation: jest.fn(),\n registerMcpServer: jest.fn(),\n registerMcpServers: jest.fn(),\n registerResource: jest.fn(),\n registerResources: jest.fn(),\n registerResourceSource: jest.fn(),\n } as unknown as TamboRegistryContextType,\n getRegisteredToolNames: () => Object.keys(toolRegistry),\n };\n}\n\n/**\n * Wrapper that provides the minimal V1-compatible provider tree for interactables:\n * TamboRegistryContext > TamboContextHelpersProvider > TamboInteractableProvider\n */\nfunction V1InteractableWrapper({\n children,\n registry,\n}: {\n children: React.ReactNode;\n registry: TamboRegistryContextType;\n}) {\n return (\n <TamboRegistryContext.Provider value={registry}>\n <TamboContextHelpersProvider>\n <TamboInteractableProvider>{children}</TamboInteractableProvider>\n </TamboContextHelpersProvider>\n </TamboRegistryContext.Provider>\n );\n}\n\ndescribe(\"V1 Interactables Integration\", () => {\n it(\"registers update_component_props and update_component_state tools when an interactable is added\", () => {\n const mockRegistry = createMockRegistry();\n\n const { result } = renderHook(() => useTamboInteractable(), {\n wrapper: ({ children }) => (\n <V1InteractableWrapper registry={mockRegistry.value}>\n {children}\n </V1InteractableWrapper>\n ),\n });\n\n act(() => {\n result.current.addInteractableComponent({\n name: \"TestWidget\",\n description: \"A test widget\",\n component: () => <div>widget</div>,\n props: { label: \"hello\" },\n propsSchema: z.object({ label: z.string() }),\n });\n });\n\n const toolNames = mockRegistry.getRegisteredToolNames();\n expect(\n toolNames.some((n) => n.startsWith(\"update_component_props_TestWidget\")),\n ).toBe(true);\n expect(\n toolNames.some((n) => n.startsWith(\"update_component_state_TestWidget\")),\n ).toBe(true);\n });\n\n it(\"registers interactables context helper that includes component info\", async () => {\n const mockRegistry = createMockRegistry();\n\n const { result } = renderHook(\n () => ({\n interactable: useTamboInteractable(),\n helpers: useTamboContextHelpers(),\n }),\n {\n wrapper: ({ children }) => (\n <V1InteractableWrapper registry={mockRegistry.value}>\n {children}\n </V1InteractableWrapper>\n ),\n },\n );\n\n // Add an interactable component\n act(() => {\n result.current.interactable.addInteractableComponent({\n name: \"InfoCard\",\n description: \"An info card\",\n component: () => <div>card</div>,\n props: { title: \"Test\" },\n });\n });\n\n // Get additional context - should include interactable info\n const contexts = await act(async () => {\n return await result.current.helpers.getAdditionalContext();\n });\n\n const interactablesContext = contexts.find(\n (c) => c.name === \"interactables\",\n );\n expect(interactablesContext).toBeDefined();\n expect(interactablesContext?.context).toBeDefined();\n });\n\n it(\"renders an interactable component via withTamboInteractable HOC\", () => {\n const mockRegistry = createMockRegistry();\n\n interface CardProps {\n title: string;\n }\n\n const Card: React.FC<CardProps> = ({ title }) => (\n <div data-testid=\"card-title\">{title}</div>\n );\n\n const InteractableCard = withTamboInteractable(Card, {\n componentName: \"Card\",\n description: \"A card component\",\n propsSchema: z.object({ title: z.string() }),\n });\n\n render(\n <V1InteractableWrapper registry={mockRegistry.value}>\n <InteractableCard title=\"Hello V1\" />\n </V1InteractableWrapper>,\n );\n\n expect(screen.getByTestId(\"card-title\")).toHaveTextContent(\"Hello V1\");\n });\n\n it(\"updates component props via the interactable provider\", () => {\n const mockRegistry = createMockRegistry();\n\n interface CounterProps {\n count: number;\n }\n\n const Counter: React.FC<CounterProps> = ({ count }) => (\n <div data-testid=\"count\">{count}</div>\n );\n\n const InteractableCounter = withTamboInteractable(Counter, {\n componentName: \"Counter\",\n description: \"A counter\",\n propsSchema: z.object({ count: z.number() }),\n });\n\n // Inner component that triggers prop updates\n function TestHarness() {\n const { interactableComponents, updateInteractableComponentProps } =\n useTamboInteractable();\n const component = interactableComponents[0];\n\n return (\n <div>\n <InteractableCounter count={0} />\n {component && (\n <button\n data-testid=\"update-btn\"\n onClick={() =>\n updateInteractableComponentProps(component.id, { count: 42 })\n }\n >\n Update\n </button>\n )}\n </div>\n );\n }\n\n render(\n <V1InteractableWrapper registry={mockRegistry.value}>\n <TestHarness />\n </V1InteractableWrapper>,\n );\n\n // Initial render\n expect(screen.getByTestId(\"count\")).toHaveTextContent(\"0\");\n\n // Update props via the interactable provider\n act(() => {\n screen.getByTestId(\"update-btn\").click();\n });\n\n // The interactable should reflect updated props\n expect(screen.getByTestId(\"count\")).toHaveTextContent(\"42\");\n });\n});\n"]}
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
import React, { type FC } from "react";
|
|
2
|
+
import type { V1ComponentContent } from "../types/message.js";
|
|
3
|
+
export interface V1ComponentRendererProps {
|
|
4
|
+
/**
|
|
5
|
+
* The component content block from a v1 message
|
|
6
|
+
*/
|
|
7
|
+
content: V1ComponentContent;
|
|
8
|
+
/**
|
|
9
|
+
* The thread ID the component belongs to
|
|
10
|
+
*/
|
|
11
|
+
threadId: string;
|
|
12
|
+
/**
|
|
13
|
+
* The message ID the component belongs to
|
|
14
|
+
*/
|
|
15
|
+
messageId: string;
|
|
16
|
+
/**
|
|
17
|
+
* Optional fallback to render if component is not found in registry
|
|
18
|
+
*/
|
|
19
|
+
fallback?: React.ReactNode;
|
|
20
|
+
}
|
|
21
|
+
/**
|
|
22
|
+
* Renders a component from the registry based on component content block data.
|
|
23
|
+
*
|
|
24
|
+
* Use this component in your message renderer to display AI-generated components.
|
|
25
|
+
* The component instance is preserved across re-renders as long as React's
|
|
26
|
+
* reconciliation keeps this wrapper mounted (use content.id as key).
|
|
27
|
+
*
|
|
28
|
+
* Wraps the rendered component with V1ComponentContentProvider so that hooks
|
|
29
|
+
* like useTamboV1ComponentState can access component context.
|
|
30
|
+
* @returns The rendered component wrapped in V1ComponentContentProvider, or fallback if not found
|
|
31
|
+
* @example
|
|
32
|
+
* ```tsx
|
|
33
|
+
* function MessageContent({ content }: { content: Content }) {
|
|
34
|
+
* if (content.type === 'component') {
|
|
35
|
+
* return (
|
|
36
|
+
* <V1ComponentRenderer
|
|
37
|
+
* key={content.id}
|
|
38
|
+
* content={content}
|
|
39
|
+
* fallback={<div>Unknown component: {content.name}</div>}
|
|
40
|
+
* />
|
|
41
|
+
* );
|
|
42
|
+
* }
|
|
43
|
+
* // ... handle other content types
|
|
44
|
+
* }
|
|
45
|
+
* ```
|
|
46
|
+
*/
|
|
47
|
+
export declare const V1ComponentRenderer: FC<V1ComponentRendererProps>;
|
|
48
|
+
//# sourceMappingURL=v1-component-renderer.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"v1-component-renderer.d.ts","sourceRoot":"","sources":["../../../src/v1/components/v1-component-renderer.tsx"],"names":[],"mappings":"AAeA,OAAO,KAAK,EAAE,EAAE,KAAK,EAAE,EAAuB,MAAM,OAAO,CAAC;AAK5D,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,kBAAkB,CAAC;AAG3D,MAAM,WAAW,wBAAwB;IACvC;;OAEG;IACH,OAAO,EAAE,kBAAkB,CAAC;IAE5B;;OAEG;IACH,QAAQ,EAAE,MAAM,CAAC;IAEjB;;OAEG;IACH,SAAS,EAAE,MAAM,CAAC;IAElB;;OAEG;IACH,QAAQ,CAAC,EAAE,KAAK,CAAC,SAAS,CAAC;CAC5B;AAED;;;;;;;;;;;;;;;;;;;;;;;;;GAyBG;AACH,eAAO,MAAM,mBAAmB,EAAE,EAAE,CAAC,wBAAwB,CAoF5D,CAAC"}
|
|
@@ -0,0 +1,100 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
/**
|
|
3
|
+
* V1 Component Renderer
|
|
4
|
+
*
|
|
5
|
+
* A wrapper component that renders a component from the registry based on
|
|
6
|
+
* component content block data. Uses React's normal reconciliation to maintain
|
|
7
|
+
* component identity - as long as the key stays stable, the component instance
|
|
8
|
+
* is preserved.
|
|
9
|
+
*
|
|
10
|
+
* Wraps the component with V1ComponentContentProvider so that hooks like
|
|
11
|
+
* useTamboV1ComponentState can access component context.
|
|
12
|
+
*/
|
|
13
|
+
import { parse } from "partial-json";
|
|
14
|
+
import React, { useMemo, useContext } from "react";
|
|
15
|
+
import { TamboRegistryContext } from "../../providers/tambo-registry-provider.js";
|
|
16
|
+
import { isStandardSchema } from "../../schema/index.js";
|
|
17
|
+
import { isPromise } from "../../util/is-promise.js";
|
|
18
|
+
import { getComponentFromRegistry } from "../../util/registry.js";
|
|
19
|
+
import { V1ComponentContentProvider } from "../utils/component-renderer.js";
|
|
20
|
+
/**
|
|
21
|
+
* Renders a component from the registry based on component content block data.
|
|
22
|
+
*
|
|
23
|
+
* Use this component in your message renderer to display AI-generated components.
|
|
24
|
+
* The component instance is preserved across re-renders as long as React's
|
|
25
|
+
* reconciliation keeps this wrapper mounted (use content.id as key).
|
|
26
|
+
*
|
|
27
|
+
* Wraps the rendered component with V1ComponentContentProvider so that hooks
|
|
28
|
+
* like useTamboV1ComponentState can access component context.
|
|
29
|
+
* @returns The rendered component wrapped in V1ComponentContentProvider, or fallback if not found
|
|
30
|
+
* @example
|
|
31
|
+
* ```tsx
|
|
32
|
+
* function MessageContent({ content }: { content: Content }) {
|
|
33
|
+
* if (content.type === 'component') {
|
|
34
|
+
* return (
|
|
35
|
+
* <V1ComponentRenderer
|
|
36
|
+
* key={content.id}
|
|
37
|
+
* content={content}
|
|
38
|
+
* fallback={<div>Unknown component: {content.name}</div>}
|
|
39
|
+
* />
|
|
40
|
+
* );
|
|
41
|
+
* }
|
|
42
|
+
* // ... handle other content types
|
|
43
|
+
* }
|
|
44
|
+
* ```
|
|
45
|
+
*/
|
|
46
|
+
export const V1ComponentRenderer = ({ content, threadId, messageId, fallback = null, }) => {
|
|
47
|
+
const registry = useContext(TamboRegistryContext);
|
|
48
|
+
// Memoize the rendered element - only recreates when props change
|
|
49
|
+
const element = useMemo(() => {
|
|
50
|
+
try {
|
|
51
|
+
const registeredComponent = getComponentFromRegistry(content.name, registry.componentList);
|
|
52
|
+
// Parse props (handles partial JSON during streaming)
|
|
53
|
+
const propsJson = JSON.stringify(content.props ?? {});
|
|
54
|
+
const parsedProps = parse(propsJson);
|
|
55
|
+
let validatedProps = parsedProps;
|
|
56
|
+
// Validate props if schema is present
|
|
57
|
+
if (isStandardSchema(registeredComponent.props)) {
|
|
58
|
+
const result = registeredComponent.props["~standard"].validate(parsedProps);
|
|
59
|
+
if (isPromise(result)) {
|
|
60
|
+
// Async validation not supported - skip validation
|
|
61
|
+
console.warn(`Async schema validation not supported for component ${content.name}`);
|
|
62
|
+
}
|
|
63
|
+
else if ("value" in result) {
|
|
64
|
+
validatedProps = result.value;
|
|
65
|
+
}
|
|
66
|
+
else {
|
|
67
|
+
// Validation failed - log warning but still render with raw props
|
|
68
|
+
console.warn(`Props validation failed for component ${content.name}:`, result.issues?.[0]?.message);
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
return React.createElement(registeredComponent.component, validatedProps);
|
|
72
|
+
}
|
|
73
|
+
catch (error) {
|
|
74
|
+
console.error("[V1ComponentRenderer] Failed to render component", {
|
|
75
|
+
threadId,
|
|
76
|
+
messageId,
|
|
77
|
+
componentId: content.id,
|
|
78
|
+
componentName: content.name,
|
|
79
|
+
streamingState: content.streamingState,
|
|
80
|
+
props: content.props,
|
|
81
|
+
error,
|
|
82
|
+
});
|
|
83
|
+
return null;
|
|
84
|
+
}
|
|
85
|
+
}, [
|
|
86
|
+
content.id,
|
|
87
|
+
content.name,
|
|
88
|
+
content.props,
|
|
89
|
+
content.streamingState,
|
|
90
|
+
messageId,
|
|
91
|
+
threadId,
|
|
92
|
+
registry.componentList,
|
|
93
|
+
]);
|
|
94
|
+
if (element === null) {
|
|
95
|
+
return React.createElement(React.Fragment, null, fallback);
|
|
96
|
+
}
|
|
97
|
+
// Wrap with provider so hooks like useTamboV1ComponentState work
|
|
98
|
+
return (React.createElement(V1ComponentContentProvider, { componentId: content.id, threadId: threadId, messageId: messageId, componentName: content.name }, element));
|
|
99
|
+
};
|
|
100
|
+
//# sourceMappingURL=v1-component-renderer.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"v1-component-renderer.js","sourceRoot":"","sources":["../../../src/v1/components/v1-component-renderer.tsx"],"names":[],"mappings":"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","sourcesContent":["\"use client\";\n\n/**\n * V1 Component Renderer\n *\n * A wrapper component that renders a component from the registry based on\n * component content block data. Uses React's normal reconciliation to maintain\n * component identity - as long as the key stays stable, the component instance\n * is preserved.\n *\n * Wraps the component with V1ComponentContentProvider so that hooks like\n * useTamboV1ComponentState can access component context.\n */\n\nimport { parse } from \"partial-json\";\nimport React, { type FC, useMemo, useContext } from \"react\";\nimport { TamboRegistryContext } from \"../../providers/tambo-registry-provider\";\nimport { isStandardSchema } from \"../../schema\";\nimport { isPromise } from \"../../util/is-promise\";\nimport { getComponentFromRegistry } from \"../../util/registry\";\nimport type { V1ComponentContent } from \"../types/message\";\nimport { V1ComponentContentProvider } from \"../utils/component-renderer\";\n\nexport interface V1ComponentRendererProps {\n /**\n * The component content block from a v1 message\n */\n content: V1ComponentContent;\n\n /**\n * The thread ID the component belongs to\n */\n threadId: string;\n\n /**\n * The message ID the component belongs to\n */\n messageId: string;\n\n /**\n * Optional fallback to render if component is not found in registry\n */\n fallback?: React.ReactNode;\n}\n\n/**\n * Renders a component from the registry based on component content block data.\n *\n * Use this component in your message renderer to display AI-generated components.\n * The component instance is preserved across re-renders as long as React's\n * reconciliation keeps this wrapper mounted (use content.id as key).\n *\n * Wraps the rendered component with V1ComponentContentProvider so that hooks\n * like useTamboV1ComponentState can access component context.\n * @returns The rendered component wrapped in V1ComponentContentProvider, or fallback if not found\n * @example\n * ```tsx\n * function MessageContent({ content }: { content: Content }) {\n * if (content.type === 'component') {\n * return (\n * <V1ComponentRenderer\n * key={content.id}\n * content={content}\n * fallback={<div>Unknown component: {content.name}</div>}\n * />\n * );\n * }\n * // ... handle other content types\n * }\n * ```\n */\nexport const V1ComponentRenderer: FC<V1ComponentRendererProps> = ({\n content,\n threadId,\n messageId,\n fallback = null,\n}) => {\n const registry = useContext(TamboRegistryContext);\n\n // Memoize the rendered element - only recreates when props change\n const element = useMemo(() => {\n try {\n const registeredComponent = getComponentFromRegistry(\n content.name,\n registry.componentList,\n );\n\n // Parse props (handles partial JSON during streaming)\n const propsJson = JSON.stringify(content.props ?? {});\n const parsedProps = parse(propsJson);\n\n let validatedProps: Record<string, unknown> = parsedProps as Record<\n string,\n unknown\n >;\n\n // Validate props if schema is present\n if (isStandardSchema(registeredComponent.props)) {\n const result =\n registeredComponent.props[\"~standard\"].validate(parsedProps);\n\n if (isPromise(result)) {\n // Async validation not supported - skip validation\n console.warn(\n `Async schema validation not supported for component ${content.name}`,\n );\n } else if (\"value\" in result) {\n validatedProps = result.value as Record<string, unknown>;\n } else {\n // Validation failed - log warning but still render with raw props\n console.warn(\n `Props validation failed for component ${content.name}:`,\n result.issues?.[0]?.message,\n );\n }\n }\n\n return React.createElement(registeredComponent.component, validatedProps);\n } catch (error) {\n console.error(\"[V1ComponentRenderer] Failed to render component\", {\n threadId,\n messageId,\n componentId: content.id,\n componentName: content.name,\n streamingState: content.streamingState,\n props: content.props,\n error,\n });\n return null;\n }\n }, [\n content.id,\n content.name,\n content.props,\n content.streamingState,\n messageId,\n threadId,\n registry.componentList,\n ]);\n\n if (element === null) {\n return <>{fallback}</>;\n }\n\n // Wrap with provider so hooks like useTamboV1ComponentState work\n return (\n <V1ComponentContentProvider\n componentId={content.id}\n threadId={threadId}\n messageId={messageId}\n componentName={content.name}\n >\n {element}\n </V1ComponentContentProvider>\n );\n};\n"]}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"v1-component-renderer.test.d.ts","sourceRoot":"","sources":["../../../src/v1/components/v1-component-renderer.test.tsx"],"names":[],"mappings":""}
|
|
@@ -0,0 +1,265 @@
|
|
|
1
|
+
import React from "react";
|
|
2
|
+
import { render, screen } from "@testing-library/react";
|
|
3
|
+
import { z } from "zod";
|
|
4
|
+
import { V1ComponentRenderer } from "./v1-component-renderer.js";
|
|
5
|
+
import { TamboRegistryContext } from "../../providers/tambo-registry-provider.js";
|
|
6
|
+
// Simple test component
|
|
7
|
+
const TestComponent = ({ title, count, }) => (React.createElement("div", { "data-testid": "test-component" },
|
|
8
|
+
React.createElement("span", { "data-testid": "title" }, title),
|
|
9
|
+
count !== undefined && React.createElement("span", { "data-testid": "count" }, count)));
|
|
10
|
+
// Component with Zod schema for validation
|
|
11
|
+
const ValidatedComponent = ({ name, age, }) => (React.createElement("div", { "data-testid": "validated-component" },
|
|
12
|
+
React.createElement("span", { "data-testid": "name" }, name),
|
|
13
|
+
React.createElement("span", { "data-testid": "age" }, age)));
|
|
14
|
+
const validatedComponentSchema = z.object({
|
|
15
|
+
name: z.string(),
|
|
16
|
+
age: z.number(),
|
|
17
|
+
});
|
|
18
|
+
// Create a mock registry
|
|
19
|
+
function createMockRegistry(componentList = {}) {
|
|
20
|
+
return {
|
|
21
|
+
componentList,
|
|
22
|
+
toolRegistry: {},
|
|
23
|
+
componentToolAssociations: {},
|
|
24
|
+
mcpServerInfos: [],
|
|
25
|
+
resources: [],
|
|
26
|
+
resourceSource: null,
|
|
27
|
+
registerComponent: jest.fn(),
|
|
28
|
+
registerTool: jest.fn(),
|
|
29
|
+
registerTools: jest.fn(),
|
|
30
|
+
addToolAssociation: jest.fn(),
|
|
31
|
+
registerMcpServer: jest.fn(),
|
|
32
|
+
registerMcpServers: jest.fn(),
|
|
33
|
+
registerResource: jest.fn(),
|
|
34
|
+
registerResources: jest.fn(),
|
|
35
|
+
registerResourceSource: jest.fn(),
|
|
36
|
+
};
|
|
37
|
+
}
|
|
38
|
+
describe("V1ComponentRenderer", () => {
|
|
39
|
+
function withMockedConsoleError(fn) {
|
|
40
|
+
const consoleErrorSpy = jest.spyOn(console, "error").mockImplementation();
|
|
41
|
+
try {
|
|
42
|
+
return fn(consoleErrorSpy);
|
|
43
|
+
}
|
|
44
|
+
finally {
|
|
45
|
+
consoleErrorSpy.mockRestore();
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
const baseContent = {
|
|
49
|
+
type: "component",
|
|
50
|
+
id: "comp_123",
|
|
51
|
+
name: "TestComponent",
|
|
52
|
+
props: { title: "Hello World" },
|
|
53
|
+
streamingState: "done",
|
|
54
|
+
};
|
|
55
|
+
it("renders component from registry with props", () => {
|
|
56
|
+
const registry = createMockRegistry({
|
|
57
|
+
TestComponent: {
|
|
58
|
+
name: "TestComponent",
|
|
59
|
+
description: "A test component",
|
|
60
|
+
component: TestComponent,
|
|
61
|
+
props: { type: "object" },
|
|
62
|
+
contextTools: [],
|
|
63
|
+
},
|
|
64
|
+
});
|
|
65
|
+
render(React.createElement(TamboRegistryContext.Provider, { value: registry },
|
|
66
|
+
React.createElement(V1ComponentRenderer, { content: baseContent, threadId: "thread_123", messageId: "msg_456" })));
|
|
67
|
+
expect(screen.getByTestId("test-component")).toBeInTheDocument();
|
|
68
|
+
expect(screen.getByTestId("title")).toHaveTextContent("Hello World");
|
|
69
|
+
});
|
|
70
|
+
it("renders fallback when component not found in registry", () => {
|
|
71
|
+
const registry = createMockRegistry({});
|
|
72
|
+
withMockedConsoleError((consoleErrorSpy) => {
|
|
73
|
+
render(React.createElement(TamboRegistryContext.Provider, { value: registry },
|
|
74
|
+
React.createElement(V1ComponentRenderer, { content: baseContent, threadId: "thread_123", messageId: "msg_456", fallback: React.createElement("div", { "data-testid": "fallback" }, "Not found") })));
|
|
75
|
+
expect(screen.getByTestId("fallback")).toBeInTheDocument();
|
|
76
|
+
expect(screen.queryByTestId("test-component")).not.toBeInTheDocument();
|
|
77
|
+
expect(consoleErrorSpy).toHaveBeenCalledWith("[V1ComponentRenderer] Failed to render component", expect.objectContaining({
|
|
78
|
+
componentId: baseContent.id,
|
|
79
|
+
componentName: baseContent.name,
|
|
80
|
+
}));
|
|
81
|
+
});
|
|
82
|
+
});
|
|
83
|
+
it("renders nothing (null fallback) when component not found and no fallback provided", () => {
|
|
84
|
+
const registry = createMockRegistry({});
|
|
85
|
+
withMockedConsoleError((consoleErrorSpy) => {
|
|
86
|
+
const { container } = render(React.createElement(TamboRegistryContext.Provider, { value: registry },
|
|
87
|
+
React.createElement(V1ComponentRenderer, { content: baseContent, threadId: "thread_123", messageId: "msg_456" })));
|
|
88
|
+
expect(container.firstChild).toBeNull();
|
|
89
|
+
expect(consoleErrorSpy).toHaveBeenCalledWith("[V1ComponentRenderer] Failed to render component", expect.objectContaining({
|
|
90
|
+
componentId: baseContent.id,
|
|
91
|
+
componentName: baseContent.name,
|
|
92
|
+
}));
|
|
93
|
+
});
|
|
94
|
+
});
|
|
95
|
+
it("handles props with undefined values", () => {
|
|
96
|
+
const registry = createMockRegistry({
|
|
97
|
+
TestComponent: {
|
|
98
|
+
name: "TestComponent",
|
|
99
|
+
description: "A test component",
|
|
100
|
+
component: TestComponent,
|
|
101
|
+
props: { type: "object" },
|
|
102
|
+
contextTools: [],
|
|
103
|
+
},
|
|
104
|
+
});
|
|
105
|
+
const content = {
|
|
106
|
+
type: "component",
|
|
107
|
+
id: "comp_123",
|
|
108
|
+
name: "TestComponent",
|
|
109
|
+
props: { title: "Test", count: undefined },
|
|
110
|
+
streamingState: "done",
|
|
111
|
+
};
|
|
112
|
+
render(React.createElement(TamboRegistryContext.Provider, { value: registry },
|
|
113
|
+
React.createElement(V1ComponentRenderer, { content: content, threadId: "thread_123", messageId: "msg_456" })));
|
|
114
|
+
expect(screen.getByTestId("title")).toHaveTextContent("Test");
|
|
115
|
+
expect(screen.queryByTestId("count")).not.toBeInTheDocument();
|
|
116
|
+
});
|
|
117
|
+
it("handles null props", () => {
|
|
118
|
+
const registry = createMockRegistry({
|
|
119
|
+
TestComponent: {
|
|
120
|
+
name: "TestComponent",
|
|
121
|
+
description: "A test component",
|
|
122
|
+
component: TestComponent,
|
|
123
|
+
props: { type: "object" },
|
|
124
|
+
contextTools: [],
|
|
125
|
+
},
|
|
126
|
+
});
|
|
127
|
+
const content = {
|
|
128
|
+
type: "component",
|
|
129
|
+
id: "comp_123",
|
|
130
|
+
name: "TestComponent",
|
|
131
|
+
props: null,
|
|
132
|
+
streamingState: "done",
|
|
133
|
+
};
|
|
134
|
+
render(React.createElement(TamboRegistryContext.Provider, { value: registry },
|
|
135
|
+
React.createElement(V1ComponentRenderer, { content: content, threadId: "thread_123", messageId: "msg_456" })));
|
|
136
|
+
// Component should render with empty props
|
|
137
|
+
expect(screen.getByTestId("test-component")).toBeInTheDocument();
|
|
138
|
+
});
|
|
139
|
+
it("validates props with StandardSchema and uses validated values", () => {
|
|
140
|
+
const registry = createMockRegistry({
|
|
141
|
+
ValidatedComponent: {
|
|
142
|
+
name: "ValidatedComponent",
|
|
143
|
+
description: "A validated component",
|
|
144
|
+
component: ValidatedComponent,
|
|
145
|
+
// Cast as unknown to satisfy TypeScript while still providing a Zod schema
|
|
146
|
+
props: validatedComponentSchema,
|
|
147
|
+
contextTools: [],
|
|
148
|
+
},
|
|
149
|
+
});
|
|
150
|
+
const content = {
|
|
151
|
+
type: "component",
|
|
152
|
+
id: "comp_123",
|
|
153
|
+
name: "ValidatedComponent",
|
|
154
|
+
props: { name: "Alice", age: 30 },
|
|
155
|
+
streamingState: "done",
|
|
156
|
+
};
|
|
157
|
+
render(React.createElement(TamboRegistryContext.Provider, { value: registry },
|
|
158
|
+
React.createElement(V1ComponentRenderer, { content: content, threadId: "thread_123", messageId: "msg_456" })));
|
|
159
|
+
expect(screen.getByTestId("name")).toHaveTextContent("Alice");
|
|
160
|
+
expect(screen.getByTestId("age")).toHaveTextContent("30");
|
|
161
|
+
});
|
|
162
|
+
it("logs warning and renders with raw props when schema validation fails", () => {
|
|
163
|
+
const consoleSpy = jest.spyOn(console, "warn").mockImplementation();
|
|
164
|
+
const registry = createMockRegistry({
|
|
165
|
+
ValidatedComponent: {
|
|
166
|
+
name: "ValidatedComponent",
|
|
167
|
+
description: "A validated component",
|
|
168
|
+
component: ValidatedComponent,
|
|
169
|
+
// Cast as unknown to satisfy TypeScript while still providing a Zod schema
|
|
170
|
+
props: validatedComponentSchema,
|
|
171
|
+
contextTools: [],
|
|
172
|
+
},
|
|
173
|
+
});
|
|
174
|
+
const content = {
|
|
175
|
+
type: "component",
|
|
176
|
+
id: "comp_123",
|
|
177
|
+
name: "ValidatedComponent",
|
|
178
|
+
props: { name: "Bob", age: "not a number" }, // Invalid: age should be number
|
|
179
|
+
streamingState: "done",
|
|
180
|
+
};
|
|
181
|
+
render(React.createElement(TamboRegistryContext.Provider, { value: registry },
|
|
182
|
+
React.createElement(V1ComponentRenderer, { content: content, threadId: "thread_123", messageId: "msg_456" })));
|
|
183
|
+
// Should still render with raw props
|
|
184
|
+
expect(screen.getByTestId("name")).toHaveTextContent("Bob");
|
|
185
|
+
expect(screen.getByTestId("age")).toHaveTextContent("not a number");
|
|
186
|
+
expect(consoleSpy).toHaveBeenCalledWith(expect.stringContaining("Props validation failed"), expect.any(String));
|
|
187
|
+
consoleSpy.mockRestore();
|
|
188
|
+
});
|
|
189
|
+
it("logs warning for async schema validation", () => {
|
|
190
|
+
const consoleSpy = jest.spyOn(console, "warn").mockImplementation();
|
|
191
|
+
// Create a mock async schema
|
|
192
|
+
const asyncSchema = {
|
|
193
|
+
"~standard": {
|
|
194
|
+
version: 1,
|
|
195
|
+
vendor: "test",
|
|
196
|
+
validate: async () => {
|
|
197
|
+
return await Promise.resolve({ value: {} });
|
|
198
|
+
},
|
|
199
|
+
},
|
|
200
|
+
};
|
|
201
|
+
const registry = createMockRegistry({
|
|
202
|
+
TestComponent: {
|
|
203
|
+
name: "TestComponent",
|
|
204
|
+
description: "A test component",
|
|
205
|
+
component: TestComponent,
|
|
206
|
+
props: asyncSchema,
|
|
207
|
+
contextTools: [],
|
|
208
|
+
},
|
|
209
|
+
});
|
|
210
|
+
render(React.createElement(TamboRegistryContext.Provider, { value: registry },
|
|
211
|
+
React.createElement(V1ComponentRenderer, { content: baseContent, threadId: "thread_123", messageId: "msg_456" })));
|
|
212
|
+
expect(consoleSpy).toHaveBeenCalledWith(expect.stringContaining("Async schema validation not supported"));
|
|
213
|
+
consoleSpy.mockRestore();
|
|
214
|
+
});
|
|
215
|
+
it("handles partial JSON during streaming", () => {
|
|
216
|
+
const registry = createMockRegistry({
|
|
217
|
+
TestComponent: {
|
|
218
|
+
name: "TestComponent",
|
|
219
|
+
description: "A test component",
|
|
220
|
+
component: TestComponent,
|
|
221
|
+
props: { type: "object" },
|
|
222
|
+
contextTools: [],
|
|
223
|
+
},
|
|
224
|
+
});
|
|
225
|
+
// partial-json library handles incomplete JSON gracefully
|
|
226
|
+
const content = {
|
|
227
|
+
type: "component",
|
|
228
|
+
id: "comp_123",
|
|
229
|
+
name: "TestComponent",
|
|
230
|
+
props: { title: "Partial" },
|
|
231
|
+
streamingState: "streaming",
|
|
232
|
+
};
|
|
233
|
+
render(React.createElement(TamboRegistryContext.Provider, { value: registry },
|
|
234
|
+
React.createElement(V1ComponentRenderer, { content: content, threadId: "thread_123", messageId: "msg_456" })));
|
|
235
|
+
expect(screen.getByTestId("title")).toHaveTextContent("Partial");
|
|
236
|
+
});
|
|
237
|
+
it("provides component context to rendered components via V1ComponentContentProvider", () => {
|
|
238
|
+
// Create a component that uses the context
|
|
239
|
+
const ContextAwareComponent = () => {
|
|
240
|
+
// We can't directly test the context without importing useV1ComponentContent
|
|
241
|
+
// but we can verify the component renders which means the provider works
|
|
242
|
+
return React.createElement("div", { "data-testid": "context-aware" }, "Rendered");
|
|
243
|
+
};
|
|
244
|
+
const registry = createMockRegistry({
|
|
245
|
+
ContextAwareComponent: {
|
|
246
|
+
name: "ContextAwareComponent",
|
|
247
|
+
description: "A context aware component",
|
|
248
|
+
component: ContextAwareComponent,
|
|
249
|
+
props: { type: "object" },
|
|
250
|
+
contextTools: [],
|
|
251
|
+
},
|
|
252
|
+
});
|
|
253
|
+
const content = {
|
|
254
|
+
type: "component",
|
|
255
|
+
id: "comp_789",
|
|
256
|
+
name: "ContextAwareComponent",
|
|
257
|
+
props: {},
|
|
258
|
+
streamingState: "done",
|
|
259
|
+
};
|
|
260
|
+
render(React.createElement(TamboRegistryContext.Provider, { value: registry },
|
|
261
|
+
React.createElement(V1ComponentRenderer, { content: content, threadId: "thread_abc", messageId: "msg_def" })));
|
|
262
|
+
expect(screen.getByTestId("context-aware")).toBeInTheDocument();
|
|
263
|
+
});
|
|
264
|
+
});
|
|
265
|
+
//# sourceMappingURL=v1-component-renderer.test.js.map
|