@artooi/agents-web-components 0.0.0-stage → 0.1.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/CHANGELOG.md +68 -0
- package/LICENSE +21 -0
- package/README.md +4397 -2
- package/dist/agents-web-components.bundle.js +5708 -0
- package/dist/agents-web-components.bundle.js.map +7 -0
- package/dist/bundle.d.ts +3 -0
- package/dist/bundle.d.ts.map +1 -0
- package/dist/chat.d.ts +3 -0
- package/dist/chat.d.ts.map +1 -0
- package/dist/chat.js +16373 -0
- package/dist/chat.js.map +7 -0
- package/dist/chunk-ARNLFGCC.js +7715 -0
- package/dist/chunk-ARNLFGCC.js.map +7 -0
- package/dist/constants.d.ts +575 -0
- package/dist/constants.d.ts.map +1 -0
- package/dist/core/activity_registration.d.ts +19 -0
- package/dist/core/activity_registration.d.ts.map +1 -0
- package/dist/core/activity_registry.d.ts +46 -0
- package/dist/core/activity_registry.d.ts.map +1 -0
- package/dist/core/activity_renderer.d.ts +20 -0
- package/dist/core/activity_renderer.d.ts.map +1 -0
- package/dist/core/ag_ui_chat.d.ts +836 -0
- package/dist/core/ag_ui_chat.d.ts.map +1 -0
- package/dist/core/agui_client.d.ts +370 -0
- package/dist/core/agui_client.d.ts.map +1 -0
- package/dist/core/attachment.d.ts +42 -0
- package/dist/core/attachment.d.ts.map +1 -0
- package/dist/core/chat_registry.d.ts +23 -0
- package/dist/core/chat_registry.d.ts.map +1 -0
- package/dist/core/client_seed.d.ts +27 -0
- package/dist/core/client_seed.d.ts.map +1 -0
- package/dist/core/conversation_store.d.ts +195 -0
- package/dist/core/conversation_store.d.ts.map +1 -0
- package/dist/core/create_http_agent.d.ts +57 -0
- package/dist/core/create_http_agent.d.ts.map +1 -0
- package/dist/core/define_chat.d.ts +13 -0
- package/dist/core/define_chat.d.ts.map +1 -0
- package/dist/core/events/attachments_detail.d.ts +9 -0
- package/dist/core/events/attachments_detail.d.ts.map +1 -0
- package/dist/core/events/bar_height_detail.d.ts +6 -0
- package/dist/core/events/bar_height_detail.d.ts.map +1 -0
- package/dist/core/events/custom_agent_detail.d.ts +8 -0
- package/dist/core/events/custom_agent_detail.d.ts.map +1 -0
- package/dist/core/events/dock_width_detail.d.ts +8 -0
- package/dist/core/events/dock_width_detail.d.ts.map +1 -0
- package/dist/core/events/feedback_detail.d.ts +28 -0
- package/dist/core/events/feedback_detail.d.ts.map +1 -0
- package/dist/core/events/invalidate_detail.d.ts +16 -0
- package/dist/core/events/invalidate_detail.d.ts.map +1 -0
- package/dist/core/events/maximize_detail.d.ts +5 -0
- package/dist/core/events/maximize_detail.d.ts.map +1 -0
- package/dist/core/events/run_finished_detail.d.ts +36 -0
- package/dist/core/events/run_finished_detail.d.ts.map +1 -0
- package/dist/core/events/state_detail.d.ts +5 -0
- package/dist/core/events/state_detail.d.ts.map +1 -0
- package/dist/core/events/submit_detail.d.ts +8 -0
- package/dist/core/events/submit_detail.d.ts.map +1 -0
- package/dist/core/events/thread_detail.d.ts +27 -0
- package/dist/core/events/thread_detail.d.ts.map +1 -0
- package/dist/core/events/thread_title_detail.d.ts +8 -0
- package/dist/core/events/thread_title_detail.d.ts.map +1 -0
- package/dist/core/events/toggle_detail.d.ts +5 -0
- package/dist/core/events/toggle_detail.d.ts.map +1 -0
- package/dist/core/events/tool_run.d.ts +11 -0
- package/dist/core/events/tool_run.d.ts.map +1 -0
- package/dist/core/events/unread_detail.d.ts +11 -0
- package/dist/core/events/unread_detail.d.ts.map +1 -0
- package/dist/core/message_role.d.ts +4 -0
- package/dist/core/message_role.d.ts.map +1 -0
- package/dist/core/read_flag.d.ts +11 -0
- package/dist/core/read_flag.d.ts.map +1 -0
- package/dist/core/read_json_attribute.d.ts +15 -0
- package/dist/core/read_json_attribute.d.ts.map +1 -0
- package/dist/core/read_max_tool_rounds.d.ts +9 -0
- package/dist/core/read_max_tool_rounds.d.ts.map +1 -0
- package/dist/core/remote_conversation_store.d.ts +71 -0
- package/dist/core/remote_conversation_store.d.ts.map +1 -0
- package/dist/core/run_handlers.d.ts +85 -0
- package/dist/core/run_handlers.d.ts.map +1 -0
- package/dist/core/run_index.d.ts +58 -0
- package/dist/core/run_index.d.ts.map +1 -0
- package/dist/core/storage_scope.d.ts +118 -0
- package/dist/core/storage_scope.d.ts.map +1 -0
- package/dist/core/tool_outcome.d.ts +27 -0
- package/dist/core/tool_outcome.d.ts.map +1 -0
- package/dist/core/transcribe_audio.d.ts +32 -0
- package/dist/core/transcribe_audio.d.ts.map +1 -0
- package/dist/core/upload_attachment.d.ts +44 -0
- package/dist/core/upload_attachment.d.ts.map +1 -0
- package/dist/core/utils.d.ts +117 -0
- package/dist/core/utils.d.ts.map +1 -0
- package/dist/dom/animations.d.ts +122 -0
- package/dist/dom/animations.d.ts.map +1 -0
- package/dist/dom/dom_driver.d.ts +28 -0
- package/dist/dom/dom_driver.d.ts.map +1 -0
- package/dist/dom/highlight_overlay.d.ts +39 -0
- package/dist/dom/highlight_overlay.d.ts.map +1 -0
- package/dist/dom/native_setter.d.ts +5 -0
- package/dist/dom/native_setter.d.ts.map +1 -0
- package/dist/dom/ring_color.d.ts +16 -0
- package/dist/dom/ring_color.d.ts.map +1 -0
- package/dist/dom/utils.d.ts +3 -0
- package/dist/dom/utils.d.ts.map +1 -0
- package/dist/index.d.ts +62 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +223 -0
- package/dist/index.js.map +7 -0
- package/dist/skills/fill_template.d.ts +16 -0
- package/dist/skills/fill_template.d.ts.map +1 -0
- package/dist/skills/parse_skills.d.ts +8 -0
- package/dist/skills/parse_skills.d.ts.map +1 -0
- package/dist/skills/skill.d.ts +33 -0
- package/dist/skills/skill.d.ts.map +1 -0
- package/dist/skills/skill_catalog.d.ts +69 -0
- package/dist/skills/skill_catalog.d.ts.map +1 -0
- package/dist/skills/skill_name_from.d.ts +12 -0
- package/dist/skills/skill_name_from.d.ts.map +1 -0
- package/dist/tools/chat_surface_tools.d.ts +77 -0
- package/dist/tools/chat_surface_tools.d.ts.map +1 -0
- package/dist/tools/client_tool_registry.d.ts +58 -0
- package/dist/tools/client_tool_registry.d.ts.map +1 -0
- package/dist/tools/is_destructive.d.ts +14 -0
- package/dist/tools/is_destructive.d.ts.map +1 -0
- package/dist/tools/is_navigates.d.ts +9 -0
- package/dist/tools/is_navigates.d.ts.map +1 -0
- package/dist/tools/page_action_tools.d.ts +30 -0
- package/dist/tools/page_action_tools.d.ts.map +1 -0
- package/dist/tools/page_map.d.ts +16 -0
- package/dist/tools/page_map.d.ts.map +1 -0
- package/dist/tools/page_state.d.ts +27 -0
- package/dist/tools/page_state.d.ts.map +1 -0
- package/dist/tools/parse_tool_catalog.d.ts +21 -0
- package/dist/tools/parse_tool_catalog.d.ts.map +1 -0
- package/dist/tools/route_map.d.ts +37 -0
- package/dist/tools/route_map.d.ts.map +1 -0
- package/dist/tools/tool_catalog.d.ts +86 -0
- package/dist/tools/tool_catalog.d.ts.map +1 -0
- package/dist/tools/tool_dispatch.d.ts +82 -0
- package/dist/tools/tool_dispatch.d.ts.map +1 -0
- package/dist/ui/charts/chart_block.d.ts +57 -0
- package/dist/ui/charts/chart_block.d.ts.map +1 -0
- package/dist/ui/charts/chart_spec_from.d.ts +12 -0
- package/dist/ui/charts/chart_spec_from.d.ts.map +1 -0
- package/dist/ui/charts/chart_styles.d.ts +2 -0
- package/dist/ui/charts/chart_styles.d.ts.map +1 -0
- package/dist/ui/charts/chart_tool.d.ts +15 -0
- package/dist/ui/charts/chart_tool.d.ts.map +1 -0
- package/dist/ui/composer/attachment_styles.d.ts +2 -0
- package/dist/ui/composer/attachment_styles.d.ts.map +1 -0
- package/dist/ui/composer/attachment_tray.d.ts +53 -0
- package/dist/ui/composer/attachment_tray.d.ts.map +1 -0
- package/dist/ui/composer/auto_grow.d.ts +10 -0
- package/dist/ui/composer/auto_grow.d.ts.map +1 -0
- package/dist/ui/composer/build_composer.d.ts +32 -0
- package/dist/ui/composer/build_composer.d.ts.map +1 -0
- package/dist/ui/composer/composer_attachments.d.ts +57 -0
- package/dist/ui/composer/composer_attachments.d.ts.map +1 -0
- package/dist/ui/composer/composer_styles.d.ts +2 -0
- package/dist/ui/composer/composer_styles.d.ts.map +1 -0
- package/dist/ui/composer/composer_voice.d.ts +48 -0
- package/dist/ui/composer/composer_voice.d.ts.map +1 -0
- package/dist/ui/composer/skill_styles.d.ts +2 -0
- package/dist/ui/composer/skill_styles.d.ts.map +1 -0
- package/dist/ui/composer/skills_menu.d.ts +34 -0
- package/dist/ui/composer/skills_menu.d.ts.map +1 -0
- package/dist/ui/composer/voice_input.d.ts +46 -0
- package/dist/ui/composer/voice_input.d.ts.map +1 -0
- package/dist/ui/excerpts/attach_copy_buttons.d.ts +13 -0
- package/dist/ui/excerpts/attach_copy_buttons.d.ts.map +1 -0
- package/dist/ui/excerpts/copy_payload.d.ts +30 -0
- package/dist/ui/excerpts/copy_payload.d.ts.map +1 -0
- package/dist/ui/excerpts/page_quote_offer.d.ts +33 -0
- package/dist/ui/excerpts/page_quote_offer.d.ts.map +1 -0
- package/dist/ui/excerpts/place_offer.d.ts +9 -0
- package/dist/ui/excerpts/place_offer.d.ts.map +1 -0
- package/dist/ui/excerpts/quote_selection.d.ts +66 -0
- package/dist/ui/excerpts/quote_selection.d.ts.map +1 -0
- package/dist/ui/excerpts/transcript_quote_offer.d.ts +57 -0
- package/dist/ui/excerpts/transcript_quote_offer.d.ts.map +1 -0
- package/dist/ui/fill_ui_string.d.ts +32 -0
- package/dist/ui/fill_ui_string.d.ts.map +1 -0
- package/dist/ui/history/checkpoint_menu.d.ts +41 -0
- package/dist/ui/history/checkpoint_menu.d.ts.map +1 -0
- package/dist/ui/history/conversation_history.d.ts +317 -0
- package/dist/ui/history/conversation_history.d.ts.map +1 -0
- package/dist/ui/history/history_styles.d.ts +2 -0
- package/dist/ui/history/history_styles.d.ts.map +1 -0
- package/dist/ui/history/relative_time.d.ts +23 -0
- package/dist/ui/history/relative_time.d.ts.map +1 -0
- package/dist/ui/history/thread_drawer.d.ts +64 -0
- package/dist/ui/history/thread_drawer.d.ts.map +1 -0
- package/dist/ui/history/utils.d.ts +2 -0
- package/dist/ui/history/utils.d.ts.map +1 -0
- package/dist/ui/interrupts/approval_card.d.ts +64 -0
- package/dist/ui/interrupts/approval_card.d.ts.map +1 -0
- package/dist/ui/interrupts/approval_styles.d.ts +2 -0
- package/dist/ui/interrupts/approval_styles.d.ts.map +1 -0
- package/dist/ui/interrupts/confirmation_card.d.ts +51 -0
- package/dist/ui/interrupts/confirmation_card.d.ts.map +1 -0
- package/dist/ui/interrupts/confirmation_styles.d.ts +2 -0
- package/dist/ui/interrupts/confirmation_styles.d.ts.map +1 -0
- package/dist/ui/interrupts/pending_decision.d.ts +22 -0
- package/dist/ui/interrupts/pending_decision.d.ts.map +1 -0
- package/dist/ui/interrupts/question_card.d.ts +49 -0
- package/dist/ui/interrupts/question_card.d.ts.map +1 -0
- package/dist/ui/interrupts/question_styles.d.ts +2 -0
- package/dist/ui/interrupts/question_styles.d.ts.map +1 -0
- package/dist/ui/place/place.d.ts +17 -0
- package/dist/ui/place/place.d.ts.map +1 -0
- package/dist/ui/place/place_chip.d.ts +26 -0
- package/dist/ui/place/place_chip.d.ts.map +1 -0
- package/dist/ui/place/place_from_route.d.ts +9 -0
- package/dist/ui/place/place_from_route.d.ts.map +1 -0
- package/dist/ui/place/place_styles.d.ts +2 -0
- package/dist/ui/place/place_styles.d.ts.map +1 -0
- package/dist/ui/placement/clamp_launcher.d.ts +20 -0
- package/dist/ui/placement/clamp_launcher.d.ts.map +1 -0
- package/dist/ui/placement/clamp_panel.d.ts +13 -0
- package/dist/ui/placement/clamp_panel.d.ts.map +1 -0
- package/dist/ui/placement/collapse_styles.d.ts +2 -0
- package/dist/ui/placement/collapse_styles.d.ts.map +1 -0
- package/dist/ui/placement/dock_styles.d.ts +2 -0
- package/dist/ui/placement/dock_styles.d.ts.map +1 -0
- package/dist/ui/placement/embedded_styles.d.ts +2 -0
- package/dist/ui/placement/embedded_styles.d.ts.map +1 -0
- package/dist/ui/placement/launcher_drag.d.ts +49 -0
- package/dist/ui/placement/launcher_drag.d.ts.map +1 -0
- package/dist/ui/placement/launcher_placement.d.ts +77 -0
- package/dist/ui/placement/launcher_placement.d.ts.map +1 -0
- package/dist/ui/placement/launcher_styles.d.ts +2 -0
- package/dist/ui/placement/launcher_styles.d.ts.map +1 -0
- package/dist/ui/placement/page_styles.d.ts +2 -0
- package/dist/ui/placement/page_styles.d.ts.map +1 -0
- package/dist/ui/placement/panel_drag.d.ts +40 -0
- package/dist/ui/placement/panel_drag.d.ts.map +1 -0
- package/dist/ui/placement/panel_placement.d.ts +173 -0
- package/dist/ui/placement/panel_placement.d.ts.map +1 -0
- package/dist/ui/placement/place_widget.d.ts +31 -0
- package/dist/ui/placement/place_widget.d.ts.map +1 -0
- package/dist/ui/placement/preset_styles.d.ts +2 -0
- package/dist/ui/placement/preset_styles.d.ts.map +1 -0
- package/dist/ui/placement/resize_handle.d.ts +78 -0
- package/dist/ui/placement/resize_handle.d.ts.map +1 -0
- package/dist/ui/placement/resize_styles.d.ts +2 -0
- package/dist/ui/placement/resize_styles.d.ts.map +1 -0
- package/dist/ui/placement/sidebar_styles.d.ts +2 -0
- package/dist/ui/placement/sidebar_styles.d.ts.map +1 -0
- package/dist/ui/placement/small_viewport_styles.d.ts +2 -0
- package/dist/ui/placement/small_viewport_styles.d.ts.map +1 -0
- package/dist/ui/placement/surface_for.d.ts +93 -0
- package/dist/ui/placement/surface_for.d.ts.map +1 -0
- package/dist/ui/progress/prettify_tool_name.d.ts +11 -0
- package/dist/ui/progress/prettify_tool_name.d.ts.map +1 -0
- package/dist/ui/progress/run_announcer.d.ts +41 -0
- package/dist/ui/progress/run_announcer.d.ts.map +1 -0
- package/dist/ui/progress/run_notice.d.ts +21 -0
- package/dist/ui/progress/run_notice.d.ts.map +1 -0
- package/dist/ui/progress/run_notice_styles.d.ts +2 -0
- package/dist/ui/progress/run_notice_styles.d.ts.map +1 -0
- package/dist/ui/progress/subagent_panel.d.ts +101 -0
- package/dist/ui/progress/subagent_panel.d.ts.map +1 -0
- package/dist/ui/progress/subagent_progress.d.ts +57 -0
- package/dist/ui/progress/subagent_progress.d.ts.map +1 -0
- package/dist/ui/progress/subagent_update.d.ts +28 -0
- package/dist/ui/progress/subagent_update.d.ts.map +1 -0
- package/dist/ui/progress/thoughts_block.d.ts +57 -0
- package/dist/ui/progress/thoughts_block.d.ts.map +1 -0
- package/dist/ui/progress/thoughts_styles.d.ts +2 -0
- package/dist/ui/progress/thoughts_styles.d.ts.map +1 -0
- package/dist/ui/progress/tool_call_card.d.ts +167 -0
- package/dist/ui/progress/tool_call_card.d.ts.map +1 -0
- package/dist/ui/progress/tool_group_styles.d.ts +2 -0
- package/dist/ui/progress/tool_group_styles.d.ts.map +1 -0
- package/dist/ui/progress/tool_step_group.d.ts +50 -0
- package/dist/ui/progress/tool_step_group.d.ts.map +1 -0
- package/dist/ui/progress/tool_step_styles.d.ts +2 -0
- package/dist/ui/progress/tool_step_styles.d.ts.map +1 -0
- package/dist/ui/shell/adopt_styles.d.ts +28 -0
- package/dist/ui/shell/adopt_styles.d.ts.map +1 -0
- package/dist/ui/shell/build_header.d.ts +45 -0
- package/dist/ui/shell/build_header.d.ts.map +1 -0
- package/dist/ui/shell/build_launcher.d.ts +33 -0
- package/dist/ui/shell/build_launcher.d.ts.map +1 -0
- package/dist/ui/shell/collapse_glyph.d.ts +14 -0
- package/dist/ui/shell/collapse_glyph.d.ts.map +1 -0
- package/dist/ui/shell/frame_styles.d.ts +2 -0
- package/dist/ui/shell/frame_styles.d.ts.map +1 -0
- package/dist/ui/shell/glyph_slot.d.ts +7 -0
- package/dist/ui/shell/glyph_slot.d.ts.map +1 -0
- package/dist/ui/shell/header_button.d.ts +11 -0
- package/dist/ui/shell/header_button.d.ts.map +1 -0
- package/dist/ui/shell/icon_element.d.ts +12 -0
- package/dist/ui/shell/icon_element.d.ts.map +1 -0
- package/dist/ui/shell/is_unread_badge_enabled.d.ts +10 -0
- package/dist/ui/shell/is_unread_badge_enabled.d.ts.map +1 -0
- package/dist/ui/shell/read_launcher_icon_url.d.ts +8 -0
- package/dist/ui/shell/read_launcher_icon_url.d.ts.map +1 -0
- package/dist/ui/shell/theme_styles.d.ts +2 -0
- package/dist/ui/shell/theme_styles.d.ts.map +1 -0
- package/dist/ui/shell/token_styles.d.ts +2 -0
- package/dist/ui/shell/token_styles.d.ts.map +1 -0
- package/dist/ui/styles.d.ts +2 -0
- package/dist/ui/styles.d.ts.map +1 -0
- package/dist/ui/themes/classic_styles.d.ts +2 -0
- package/dist/ui/themes/classic_styles.d.ts.map +1 -0
- package/dist/ui/themes/classic_tokens.d.ts +2 -0
- package/dist/ui/themes/classic_tokens.d.ts.map +1 -0
- package/dist/ui/themes/code_tokens.d.ts +2 -0
- package/dist/ui/themes/code_tokens.d.ts.map +1 -0
- package/dist/ui/themes/ledger_styles.d.ts +2 -0
- package/dist/ui/themes/ledger_styles.d.ts.map +1 -0
- package/dist/ui/themes/ledger_tokens.d.ts +2 -0
- package/dist/ui/themes/ledger_tokens.d.ts.map +1 -0
- package/dist/ui/themes/modern_styles.d.ts +2 -0
- package/dist/ui/themes/modern_styles.d.ts.map +1 -0
- package/dist/ui/themes/quiet_styles.d.ts +2 -0
- package/dist/ui/themes/quiet_styles.d.ts.map +1 -0
- package/dist/ui/themes/scheme_styles.d.ts +2 -0
- package/dist/ui/themes/scheme_styles.d.ts.map +1 -0
- package/dist/ui/themes/soft_styles.d.ts +2 -0
- package/dist/ui/themes/soft_styles.d.ts.map +1 -0
- package/dist/ui/themes/soft_tokens.d.ts +2 -0
- package/dist/ui/themes/soft_tokens.d.ts.map +1 -0
- package/dist/ui/themes/theme_hosts.d.ts +15 -0
- package/dist/ui/themes/theme_hosts.d.ts.map +1 -0
- package/dist/ui/transcript/answer_actions.d.ts +50 -0
- package/dist/ui/transcript/answer_actions.d.ts.map +1 -0
- package/dist/ui/transcript/answer_stream.d.ts +56 -0
- package/dist/ui/transcript/answer_stream.d.ts.map +1 -0
- package/dist/ui/transcript/attachment_chip_styles.d.ts +2 -0
- package/dist/ui/transcript/attachment_chip_styles.d.ts.map +1 -0
- package/dist/ui/transcript/attachment_chips.d.ts +21 -0
- package/dist/ui/transcript/attachment_chips.d.ts.map +1 -0
- package/dist/ui/transcript/build_transcript.d.ts +21 -0
- package/dist/ui/transcript/build_transcript.d.ts.map +1 -0
- package/dist/ui/transcript/greeting_styles.d.ts +2 -0
- package/dist/ui/transcript/greeting_styles.d.ts.map +1 -0
- package/dist/ui/transcript/groups_tool_steps.d.ts +13 -0
- package/dist/ui/transcript/groups_tool_steps.d.ts.map +1 -0
- package/dist/ui/transcript/message_action_styles.d.ts +2 -0
- package/dist/ui/transcript/message_action_styles.d.ts.map +1 -0
- package/dist/ui/transcript/message_actions.d.ts +74 -0
- package/dist/ui/transcript/message_actions.d.ts.map +1 -0
- package/dist/ui/transcript/message_styles.d.ts +2 -0
- package/dist/ui/transcript/message_styles.d.ts.map +1 -0
- package/dist/ui/transcript/render_markdown.d.ts +40 -0
- package/dist/ui/transcript/render_markdown.d.ts.map +1 -0
- package/dist/ui/transcript/render_or_warn.d.ts +16 -0
- package/dist/ui/transcript/render_or_warn.d.ts.map +1 -0
- package/dist/ui/transcript/reveal_words.d.ts +8 -0
- package/dist/ui/transcript/reveal_words.d.ts.map +1 -0
- package/dist/ui/transcript/starter_chips.d.ts +21 -0
- package/dist/ui/transcript/starter_chips.d.ts.map +1 -0
- package/dist/ui/transcript/stick_to_bottom.d.ts +55 -0
- package/dist/ui/transcript/stick_to_bottom.d.ts.map +1 -0
- package/dist/ui/transcript/suggestion_chips.d.ts +29 -0
- package/dist/ui/transcript/suggestion_chips.d.ts.map +1 -0
- package/dist/ui/transcript/transcript.d.ts +196 -0
- package/dist/ui/transcript/transcript.d.ts.map +1 -0
- package/dist/ui/transcript/transcript_styles.d.ts +2 -0
- package/dist/ui/transcript/transcript_styles.d.ts.map +1 -0
- package/dist/ui/ui_strings.d.ts +352 -0
- package/dist/ui/ui_strings.d.ts.map +1 -0
- package/dist/version.d.ts +2 -0
- package/dist/version.d.ts.map +1 -0
- package/package.json +86 -3
- package/src/bundle.ts +2 -0
- package/src/chat.ts +2 -0
- package/src/constants.ts +650 -0
- package/src/core/activity_registration.ts +19 -0
- package/src/core/activity_registry.ts +133 -0
- package/src/core/activity_renderer.ts +19 -0
- package/src/core/ag_ui_chat.ts +3966 -0
- package/src/core/agui_client.ts +1028 -0
- package/src/core/attachment.ts +62 -0
- package/src/core/chat_registry.ts +53 -0
- package/src/core/client_seed.ts +27 -0
- package/src/core/conversation_store.ts +478 -0
- package/src/core/create_http_agent.ts +97 -0
- package/src/core/define_chat.ts +25 -0
- package/src/core/events/attachments_detail.ts +9 -0
- package/src/core/events/bar_height_detail.ts +5 -0
- package/src/core/events/custom_agent_detail.ts +7 -0
- package/src/core/events/dock_width_detail.ts +7 -0
- package/src/core/events/feedback_detail.ts +27 -0
- package/src/core/events/invalidate_detail.ts +15 -0
- package/src/core/events/maximize_detail.ts +4 -0
- package/src/core/events/run_finished_detail.ts +36 -0
- package/src/core/events/state_detail.ts +4 -0
- package/src/core/events/submit_detail.ts +8 -0
- package/src/core/events/thread_detail.ts +26 -0
- package/src/core/events/thread_title_detail.ts +7 -0
- package/src/core/events/toggle_detail.ts +4 -0
- package/src/core/events/tool_run.ts +10 -0
- package/src/core/events/unread_detail.ts +10 -0
- package/src/core/message_role.ts +4 -0
- package/src/core/read_flag.ts +13 -0
- package/src/core/read_json_attribute.ts +30 -0
- package/src/core/read_max_tool_rounds.ts +13 -0
- package/src/core/remote_conversation_store.ts +279 -0
- package/src/core/run_handlers.ts +524 -0
- package/src/core/run_index.ts +114 -0
- package/src/core/storage_scope.ts +291 -0
- package/src/core/tool_outcome.ts +39 -0
- package/src/core/transcribe_audio.ts +76 -0
- package/src/core/upload_attachment.ts +130 -0
- package/src/core/utils.ts +230 -0
- package/src/dom/animations.ts +341 -0
- package/src/dom/dom_driver.ts +91 -0
- package/src/dom/highlight_overlay.ts +204 -0
- package/src/dom/native_setter.ts +40 -0
- package/src/dom/ring_color.ts +206 -0
- package/src/dom/utils.ts +5 -0
- package/src/index.ts +220 -0
- package/src/skills/fill_template.ts +31 -0
- package/src/skills/parse_skills.ts +31 -0
- package/src/skills/skill.ts +32 -0
- package/src/skills/skill_catalog.ts +172 -0
- package/src/skills/skill_name_from.ts +19 -0
- package/src/tools/chat_surface_tools.ts +183 -0
- package/src/tools/client_tool_registry.ts +80 -0
- package/src/tools/is_destructive.ts +17 -0
- package/src/tools/is_navigates.ts +12 -0
- package/src/tools/page_action_tools.ts +123 -0
- package/src/tools/page_map.ts +26 -0
- package/src/tools/page_state.ts +57 -0
- package/src/tools/parse_tool_catalog.ts +41 -0
- package/src/tools/route_map.ts +132 -0
- package/src/tools/tool_catalog.ts +334 -0
- package/src/tools/tool_dispatch.ts +453 -0
- package/src/ui/charts/chart_block.ts +519 -0
- package/src/ui/charts/chart_spec_from.ts +125 -0
- package/src/ui/charts/chart_styles.ts +63 -0
- package/src/ui/charts/chart_tool.ts +72 -0
- package/src/ui/composer/attachment_styles.ts +71 -0
- package/src/ui/composer/attachment_tray.ts +295 -0
- package/src/ui/composer/auto_grow.ts +12 -0
- package/src/ui/composer/build_composer.ts +114 -0
- package/src/ui/composer/composer_attachments.ts +324 -0
- package/src/ui/composer/composer_styles.ts +177 -0
- package/src/ui/composer/composer_voice.ts +93 -0
- package/src/ui/composer/skill_styles.ts +120 -0
- package/src/ui/composer/skills_menu.ts +188 -0
- package/src/ui/composer/voice_input.ts +282 -0
- package/src/ui/excerpts/attach_copy_buttons.ts +73 -0
- package/src/ui/excerpts/copy_payload.ts +156 -0
- package/src/ui/excerpts/page_quote_offer.ts +203 -0
- package/src/ui/excerpts/place_offer.ts +24 -0
- package/src/ui/excerpts/quote_selection.ts +345 -0
- package/src/ui/excerpts/transcript_quote_offer.ts +175 -0
- package/src/ui/fill_ui_string.ts +41 -0
- package/src/ui/history/checkpoint_menu.ts +300 -0
- package/src/ui/history/conversation_history.ts +1169 -0
- package/src/ui/history/history_styles.ts +492 -0
- package/src/ui/history/relative_time.ts +50 -0
- package/src/ui/history/thread_drawer.ts +471 -0
- package/src/ui/history/utils.ts +11 -0
- package/src/ui/interrupts/approval_card.ts +202 -0
- package/src/ui/interrupts/approval_styles.ts +52 -0
- package/src/ui/interrupts/confirmation_card.ts +138 -0
- package/src/ui/interrupts/confirmation_styles.ts +114 -0
- package/src/ui/interrupts/pending_decision.ts +33 -0
- package/src/ui/interrupts/question_card.ts +213 -0
- package/src/ui/interrupts/question_styles.ts +73 -0
- package/src/ui/place/place.ts +16 -0
- package/src/ui/place/place_chip.ts +146 -0
- package/src/ui/place/place_from_route.ts +27 -0
- package/src/ui/place/place_styles.ts +108 -0
- package/src/ui/placement/clamp_launcher.ts +38 -0
- package/src/ui/placement/clamp_panel.ts +30 -0
- package/src/ui/placement/collapse_styles.ts +472 -0
- package/src/ui/placement/dock_styles.ts +116 -0
- package/src/ui/placement/embedded_styles.ts +14 -0
- package/src/ui/placement/launcher_drag.ts +206 -0
- package/src/ui/placement/launcher_placement.ts +133 -0
- package/src/ui/placement/launcher_styles.ts +189 -0
- package/src/ui/placement/page_styles.ts +47 -0
- package/src/ui/placement/panel_drag.ts +138 -0
- package/src/ui/placement/panel_placement.ts +1975 -0
- package/src/ui/placement/place_widget.ts +64 -0
- package/src/ui/placement/preset_styles.ts +49 -0
- package/src/ui/placement/resize_handle.ts +229 -0
- package/src/ui/placement/resize_styles.ts +201 -0
- package/src/ui/placement/sidebar_styles.ts +113 -0
- package/src/ui/placement/small_viewport_styles.ts +70 -0
- package/src/ui/placement/surface_for.ts +183 -0
- package/src/ui/progress/prettify_tool_name.ts +16 -0
- package/src/ui/progress/run_announcer.ts +67 -0
- package/src/ui/progress/run_notice.ts +61 -0
- package/src/ui/progress/run_notice_styles.ts +52 -0
- package/src/ui/progress/subagent_panel.ts +227 -0
- package/src/ui/progress/subagent_progress.ts +200 -0
- package/src/ui/progress/subagent_update.ts +89 -0
- package/src/ui/progress/thoughts_block.ts +165 -0
- package/src/ui/progress/thoughts_styles.ts +74 -0
- package/src/ui/progress/tool_call_card.ts +429 -0
- package/src/ui/progress/tool_group_styles.ts +313 -0
- package/src/ui/progress/tool_step_group.ts +317 -0
- package/src/ui/progress/tool_step_styles.ts +486 -0
- package/src/ui/shell/adopt_styles.ts +34 -0
- package/src/ui/shell/build_header.ts +121 -0
- package/src/ui/shell/build_launcher.ts +79 -0
- package/src/ui/shell/collapse_glyph.ts +42 -0
- package/src/ui/shell/frame_styles.ts +147 -0
- package/src/ui/shell/glyph_slot.ts +12 -0
- package/src/ui/shell/header_button.ts +22 -0
- package/src/ui/shell/icon_element.ts +33 -0
- package/src/ui/shell/is_unread_badge_enabled.ts +11 -0
- package/src/ui/shell/read_launcher_icon_url.ts +9 -0
- package/src/ui/shell/theme_styles.ts +11 -0
- package/src/ui/shell/token_styles.ts +480 -0
- package/src/ui/styles.ts +110 -0
- package/src/ui/themes/classic_styles.ts +32 -0
- package/src/ui/themes/classic_tokens.ts +51 -0
- package/src/ui/themes/code_tokens.ts +21 -0
- package/src/ui/themes/ledger_styles.ts +169 -0
- package/src/ui/themes/ledger_tokens.ts +33 -0
- package/src/ui/themes/modern_styles.ts +80 -0
- package/src/ui/themes/quiet_styles.ts +118 -0
- package/src/ui/themes/scheme_styles.ts +26 -0
- package/src/ui/themes/soft_styles.ts +149 -0
- package/src/ui/themes/soft_tokens.ts +29 -0
- package/src/ui/themes/theme_hosts.ts +14 -0
- package/src/ui/transcript/answer_actions.ts +165 -0
- package/src/ui/transcript/answer_stream.ts +123 -0
- package/src/ui/transcript/attachment_chip_styles.ts +116 -0
- package/src/ui/transcript/attachment_chips.ts +82 -0
- package/src/ui/transcript/build_transcript.ts +67 -0
- package/src/ui/transcript/greeting_styles.ts +171 -0
- package/src/ui/transcript/groups_tool_steps.ts +16 -0
- package/src/ui/transcript/message_action_styles.ts +155 -0
- package/src/ui/transcript/message_actions.ts +234 -0
- package/src/ui/transcript/message_styles.ts +262 -0
- package/src/ui/transcript/render_markdown.ts +181 -0
- package/src/ui/transcript/render_or_warn.ts +22 -0
- package/src/ui/transcript/reveal_words.ts +41 -0
- package/src/ui/transcript/starter_chips.ts +42 -0
- package/src/ui/transcript/stick_to_bottom.ts +126 -0
- package/src/ui/transcript/suggestion_chips.ts +73 -0
- package/src/ui/transcript/transcript.ts +555 -0
- package/src/ui/transcript/transcript_styles.ts +153 -0
- package/src/ui/ui_strings.ts +550 -0
- package/src/version.ts +1 -0
|
@@ -0,0 +1,1028 @@
|
|
|
1
|
+
import {
|
|
2
|
+
type AbstractAgent,
|
|
3
|
+
type AgentSubscriber,
|
|
4
|
+
buildResumeArray,
|
|
5
|
+
type RunAgentParameters,
|
|
6
|
+
randomUUID,
|
|
7
|
+
} from "@ag-ui/client";
|
|
8
|
+
import {
|
|
9
|
+
type Context,
|
|
10
|
+
contentToText,
|
|
11
|
+
type Interrupt,
|
|
12
|
+
type Message,
|
|
13
|
+
type ResumeEntry,
|
|
14
|
+
type Tool,
|
|
15
|
+
} from "@ag-ui/core";
|
|
16
|
+
import { MAX_TOOL_ROUNDS, TOOL_OUTCOME } from "../constants.js";
|
|
17
|
+
import type { AttachmentRef } from "./attachment.js";
|
|
18
|
+
import type { ToolOutcome } from "./tool_outcome.js";
|
|
19
|
+
import { answerUnansweredCalls } from "./utils.js";
|
|
20
|
+
|
|
21
|
+
/** A tool call surfaced to the host by {@link AgUiClient}. */
|
|
22
|
+
export interface AgUiToolCall {
|
|
23
|
+
readonly id: string;
|
|
24
|
+
readonly name: string;
|
|
25
|
+
readonly args: Record<string, unknown>;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
/**
|
|
29
|
+
* Which message an answer surfaced by {@link AgUiClient} is, as the stream
|
|
30
|
+
* named it.
|
|
31
|
+
*/
|
|
32
|
+
export interface AgUiMessageIds {
|
|
33
|
+
/** The id the message streamed under, on its `TEXT_MESSAGE_START`/`END`. */
|
|
34
|
+
readonly messageId: string;
|
|
35
|
+
/**
|
|
36
|
+
* The run that streamed it, as that run's `RUN_STARTED` declared it.
|
|
37
|
+
*
|
|
38
|
+
* Absent when no `RUN_STARTED` was seen. `@ag-ui/client` 1.0 rejects a
|
|
39
|
+
* stream whose first event is anything else, so over a real agent this is
|
|
40
|
+
* always present; nothing else names the run the server recorded, so
|
|
41
|
+
* nothing is substituted when it is not.
|
|
42
|
+
*
|
|
43
|
+
* Text a sub-agent streams through the parent run carries the parent's run
|
|
44
|
+
* id: that is the stream it arrived on, and the delegation's
|
|
45
|
+
* `subagentRunId` names the child's work rather than a run the answer is
|
|
46
|
+
* recorded under.
|
|
47
|
+
*/
|
|
48
|
+
readonly runId?: string;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
/** The result of executing a frontend tool, destined for a tool-result message. */
|
|
52
|
+
export interface ToolExecution {
|
|
53
|
+
/** String content for the AG-UI tool-result message. */
|
|
54
|
+
content: string;
|
|
55
|
+
/** Present when the handler failed; surfaced for logging. */
|
|
56
|
+
error?: string;
|
|
57
|
+
/**
|
|
58
|
+
* How the call ended, in the same vocabulary a server states on
|
|
59
|
+
* `TOOL_CALL_RESULT`. Omit for a success -- absent *is* success, here for the
|
|
60
|
+
* same reason it is on the wire.
|
|
61
|
+
*
|
|
62
|
+
* Distinct from {@link error}, which is a *message* and only exists for the
|
|
63
|
+
* one failure shape that produces one. A refusal has no error and is still
|
|
64
|
+
* not a success, and that is the case this field exists for.
|
|
65
|
+
*/
|
|
66
|
+
outcome?: ToolOutcome;
|
|
67
|
+
/**
|
|
68
|
+
* When `true`, a navigating tool triggered a page reload. The loop stops
|
|
69
|
+
* without appending a result — the result is supplied after the next mount
|
|
70
|
+
* (see the resume path). Mutually exclusive with a usable `content`.
|
|
71
|
+
*/
|
|
72
|
+
halt?: boolean;
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
/**
|
|
76
|
+
* Executes a frontend tool call.
|
|
77
|
+
*
|
|
78
|
+
* Returns the {@link ToolExecution} to post back to the agent, or `null` when
|
|
79
|
+
* there is nothing to post: the call is not a frontend tool the host owns (a
|
|
80
|
+
* server-side tool the server already executed, which the client must not
|
|
81
|
+
* re-run), or its run was stopped or handed over while the call waited. A
|
|
82
|
+
* stopped call is left unanswered here, like the calls after it in the round,
|
|
83
|
+
* and the next request answers it as not finished.
|
|
84
|
+
*/
|
|
85
|
+
export type ExecuteTool = (call: AgUiToolCall) => Promise<ToolExecution | null>;
|
|
86
|
+
|
|
87
|
+
/**
|
|
88
|
+
* One user decision for a server-side-tool approval interrupt. Structurally
|
|
89
|
+
* matches `@ag-ui/client`'s (non-exported) `ResumeResponse`, the payload
|
|
90
|
+
* {@link buildResumeArray} turns into a `ResumeEntry`: `resolved` approves (with
|
|
91
|
+
* an optional `payload`, e.g. `{ approved: true }`), `cancelled` denies.
|
|
92
|
+
*/
|
|
93
|
+
export type InterruptResponse = { status: "resolved"; payload?: unknown } | { status: "cancelled" };
|
|
94
|
+
|
|
95
|
+
/**
|
|
96
|
+
* Resolves the approval interrupts a run finished on, keyed by interrupt id.
|
|
97
|
+
*
|
|
98
|
+
* A gated server-side tool defers instead of executing, so the run finishes on
|
|
99
|
+
* an interrupt outcome; the host collects one decision per interrupt and the
|
|
100
|
+
* loop resumes with the answers. Omit for agents that never gate server-side
|
|
101
|
+
* tools — an unresolved interrupt then ends the loop.
|
|
102
|
+
*/
|
|
103
|
+
export type ResolveInterrupts = (
|
|
104
|
+
interrupts: readonly Interrupt[],
|
|
105
|
+
) => Promise<Record<string, InterruptResponse>>;
|
|
106
|
+
|
|
107
|
+
/**
|
|
108
|
+
* Callbacks the {@link AgUiClient} invokes as a run progresses. The host
|
|
109
|
+
* (the `<ag-ui-chat>` element) implements these to render streaming text and
|
|
110
|
+
* tool activity into the chat.
|
|
111
|
+
*/
|
|
112
|
+
export interface AgUiClientHandlers {
|
|
113
|
+
onRunStart(): void;
|
|
114
|
+
/** Fired on every streamed token; `buffer` is the full text so far. */
|
|
115
|
+
onTextDelta(buffer: string): void;
|
|
116
|
+
/**
|
|
117
|
+
* Fired when the assistant message completes; `buffer` is the final text,
|
|
118
|
+
* and `message` names it as the stream did.
|
|
119
|
+
*/
|
|
120
|
+
onTextEnd(buffer: string, message: AgUiMessageIds): void;
|
|
121
|
+
/** Fired when the agent finishes calling a tool (server- or frontend-side). */
|
|
122
|
+
onToolCall(call: AgUiToolCall): void;
|
|
123
|
+
/**
|
|
124
|
+
* Fired when a server-side tool's result streams back (AG-UI's
|
|
125
|
+
* `TOOL_CALL_RESULT`). Frontend tools don't emit this — the client supplies
|
|
126
|
+
* their result itself — so this is the channel for server-executed output.
|
|
127
|
+
*
|
|
128
|
+
* `outcome` is the `outcome` key of the event's `metadata`, forwarded raw.
|
|
129
|
+
* It is `unknown` rather than {@link ToolOutcome} because metadata is open by
|
|
130
|
+
* key: the protocol declares the object and validates nothing inside it, so
|
|
131
|
+
* neither can this signature honestly claim to. Read it with
|
|
132
|
+
* `toolStatusFromOutcome`, which treats `undefined` and anything unrecognised
|
|
133
|
+
* as a success.
|
|
134
|
+
*
|
|
135
|
+
* Only the metadata is read. A server written against `@ag-ui/client` 0.x
|
|
136
|
+
* states the outcome at the top level of the event, and 1.0 strips that key
|
|
137
|
+
* before any subscriber runs, so from such a server this is `undefined` and
|
|
138
|
+
* the card settles as done.
|
|
139
|
+
*/
|
|
140
|
+
onToolResult(toolCallId: string, content: string, outcome?: unknown): void;
|
|
141
|
+
/**
|
|
142
|
+
* Fired for AG-UI activity events — ambient notices about what the *run* did,
|
|
143
|
+
* as opposed to work the agent asked for. `django-ag-ui` emits one with
|
|
144
|
+
* `activityType: "compaction"` when it condensed the history.
|
|
145
|
+
*/
|
|
146
|
+
onActivity(activityType: string, content: unknown, messageId: string): void;
|
|
147
|
+
/**
|
|
148
|
+
* An activity's content changed in place — a snapshot re-sent under the same
|
|
149
|
+
* `messageId` with `replace`, or an `ACTIVITY_DELTA` whose JSON patch
|
|
150
|
+
* `@ag-ui/client` has already applied.
|
|
151
|
+
*
|
|
152
|
+
* Reported after the client has updated its own message, so `content` is the
|
|
153
|
+
* result rather than the instruction. That is the whole reason this is a
|
|
154
|
+
* separate callback: the raw delta event fires *before* the patch lands, and
|
|
155
|
+
* a subscriber acting on it would redraw from stale content.
|
|
156
|
+
*/
|
|
157
|
+
onActivityChanged(messageId: string, activityType: string, content: unknown): void;
|
|
158
|
+
/** Fired when a reasoning model starts emitting its chain-of-thought. */
|
|
159
|
+
onReasoningStart(): void;
|
|
160
|
+
/**
|
|
161
|
+
* Fired on every reasoning token, and once more when the block ends.
|
|
162
|
+
*
|
|
163
|
+
* ``buffer`` is the text accumulated *before* the token that triggered the
|
|
164
|
+
* call, which is what the protocol client passes -- so the stream trails by
|
|
165
|
+
* one delta and the final call, at the end of the block, is what completes
|
|
166
|
+
* it. Render the buffer wholesale rather than appending it.
|
|
167
|
+
*/
|
|
168
|
+
onReasoningDelta(buffer: string): void;
|
|
169
|
+
/** Fired when the reasoning block ends (before the answer text streams). */
|
|
170
|
+
onReasoningEnd(): void;
|
|
171
|
+
onRunEnd(): void;
|
|
172
|
+
/**
|
|
173
|
+
* The server replaced the whole conversation with `MESSAGES_SNAPSHOT`.
|
|
174
|
+
*
|
|
175
|
+
* `@ag-ui/client` applies the event before any subscriber sees it, so by the
|
|
176
|
+
* time this fires `agent.messages` **is** the server's list -- and the run
|
|
177
|
+
* loop persists `agent.messages`. The replacement therefore reaches the
|
|
178
|
+
* conversation store whatever the host does; this hook exists so the host can
|
|
179
|
+
* stop that being invisible.
|
|
180
|
+
*/
|
|
181
|
+
onMessagesSnapshot(messages: readonly Message[]): void;
|
|
182
|
+
/**
|
|
183
|
+
* The agent sent a `CUSTOM` event.
|
|
184
|
+
*
|
|
185
|
+
* Forwarded whole and uninterpreted: `name` is an open string the protocol
|
|
186
|
+
* does not enumerate, so a client that decided which names were legal would
|
|
187
|
+
* be the thing the open field exists to avoid.
|
|
188
|
+
*/
|
|
189
|
+
onCustomEvent(name: string, value: unknown): void;
|
|
190
|
+
/**
|
|
191
|
+
* A delegated sub-agent started, inside the run.
|
|
192
|
+
*
|
|
193
|
+
* The protocol's own event, unlike the steps that follow it on
|
|
194
|
+
* {@link SUBAGENT_CUSTOM_NAME}. `parentToolCallId` links the delegation to the
|
|
195
|
+
* `delegate_task` call that spawned it -- the field AG-UI provides for the
|
|
196
|
+
* "agents as tools" shape -- and is `null` when the server did not send one,
|
|
197
|
+
* which a host reads as a delegation it has nothing to attach to.
|
|
198
|
+
*
|
|
199
|
+
* `subagentRunId` names the child run and is the only id the two closing
|
|
200
|
+
* events carry, so a host that wants to close what it opened has to remember
|
|
201
|
+
* the pairing.
|
|
202
|
+
*/
|
|
203
|
+
onSubAgentStarted(subagentRunId: string, name: string, parentToolCallId: string | null): void;
|
|
204
|
+
/** The delegation named by `subagentRunId` completed. */
|
|
205
|
+
onSubAgentFinished(subagentRunId: string): void;
|
|
206
|
+
/**
|
|
207
|
+
* The delegation named by `subagentRunId` failed.
|
|
208
|
+
*
|
|
209
|
+
* `message` is server text and required by the protocol, but it is not an
|
|
210
|
+
* exception's words: `django-ag-ui` sends only which sub-agent failed, on the
|
|
211
|
+
* same reasoning that redacts a `RUN_ERROR`. Render it as text, never as
|
|
212
|
+
* markup.
|
|
213
|
+
*/
|
|
214
|
+
onSubAgentError(subagentRunId: string, message: string): void;
|
|
215
|
+
onError(message: string): void;
|
|
216
|
+
/**
|
|
217
|
+
* Fired when the user cancelled the run ({@link AgUiClient.cancel}) — the
|
|
218
|
+
* deliberate-stop sibling of `onError`. Partial assistant text already
|
|
219
|
+
* streamed stays valid, so the host keeps the bubble and shows a stopped
|
|
220
|
+
* affordance rather than an error. `onSettled` still follows.
|
|
221
|
+
*/
|
|
222
|
+
onCancelled(): void;
|
|
223
|
+
/**
|
|
224
|
+
* Fired exactly once when the interaction settles, however the run loop ended
|
|
225
|
+
* — server-only round, rounds exhausted, cancellation, or error. The terminal
|
|
226
|
+
* guarantee that the UI returns to rest.
|
|
227
|
+
*/
|
|
228
|
+
onSettled(): void;
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
/**
|
|
232
|
+
* Provider of the per-run frontend tool catalog and context. Both are read
|
|
233
|
+
* fresh on every {@link AgUiClient.send} so the catalog reflects the current
|
|
234
|
+
* page state.
|
|
235
|
+
*/
|
|
236
|
+
export interface AgUiRunInputs {
|
|
237
|
+
getTools?: () => Tool[];
|
|
238
|
+
getContext?: () => Context[];
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
export interface AgUiClientConfig extends AgUiRunInputs {
|
|
242
|
+
/** The AG-UI agent to drive. Injected so it can be faked in tests. */
|
|
243
|
+
agent: AbstractAgent;
|
|
244
|
+
handlers: AgUiClientHandlers;
|
|
245
|
+
/** Executes frontend tool calls. Omit for server-only tool sets. */
|
|
246
|
+
executeTool?: ExecuteTool;
|
|
247
|
+
/**
|
|
248
|
+
* Resolves server-side-tool approval interrupts. Omit when no server-side
|
|
249
|
+
* tool is gated for approval — an interrupt then ends the loop unanswered.
|
|
250
|
+
*/
|
|
251
|
+
resolveInterrupts?: ResolveInterrupts;
|
|
252
|
+
/**
|
|
253
|
+
* Invoked with the latest history whenever it changes, so the host can
|
|
254
|
+
* persist it for durability across page reloads. Omit to keep the
|
|
255
|
+
* conversation in-memory only.
|
|
256
|
+
*/
|
|
257
|
+
onPersist?: (messages: readonly Message[]) => void;
|
|
258
|
+
/**
|
|
259
|
+
* Called whenever AG-UI shared state changes — a streamed `STATE_SNAPSHOT` /
|
|
260
|
+
* `STATE_DELTA`, or {@link AgUiClient.setState}. `@ag-ui/client` owns applying
|
|
261
|
+
* those events; this only forwards the result.
|
|
262
|
+
*/
|
|
263
|
+
onStateChanged?: (state: Readonly<Record<string, unknown>>) => void;
|
|
264
|
+
/**
|
|
265
|
+
* Error text for {@link AgUiClientHandlers.onError} when a run's stream closes
|
|
266
|
+
* without a terminal AG-UI event. Defaults to `"Connection lost"`; the host
|
|
267
|
+
* passes its localized string.
|
|
268
|
+
*/
|
|
269
|
+
connectionLostMessage?: string;
|
|
270
|
+
/**
|
|
271
|
+
* Result content for a tool call the conversation moved past without one: a
|
|
272
|
+
* Stop while the stream was arriving, a round that ended on `RUN_ERROR`, a
|
|
273
|
+
* call no {@link executeTool} answered, or history restored with a call still
|
|
274
|
+
* open. Sent to the agent, so it says only that the call did not finish --
|
|
275
|
+
* nobody refused it. Defaults to an English sentence; the host passes its
|
|
276
|
+
* localized string.
|
|
277
|
+
*/
|
|
278
|
+
unfinishedMessage?: string;
|
|
279
|
+
/**
|
|
280
|
+
* Result content for a server-side approval that Stop closed while it was
|
|
281
|
+
* open, which the host's {@link resolveInterrupts} answers as declined.
|
|
282
|
+
* Defaults to `"User declined the action."`; the host passes its localized
|
|
283
|
+
* string.
|
|
284
|
+
*/
|
|
285
|
+
declinedMessage?: string;
|
|
286
|
+
/**
|
|
287
|
+
* Upper bound on frontend tool-call to re-run rounds within one
|
|
288
|
+
* {@link AgUiClient.send}. Defaults to {@link MAX_TOOL_ROUNDS}.
|
|
289
|
+
*
|
|
290
|
+
* The default suits a chat whose tools answer questions. A page-driving
|
|
291
|
+
* deployment reaches it legitimately -- filling a form field by field is one
|
|
292
|
+
* round each -- and the symptom is not an error but an answer that stops
|
|
293
|
+
* mid-task, which reads as the model giving up. Raise it where a turn is
|
|
294
|
+
* expected to take many small steps.
|
|
295
|
+
*
|
|
296
|
+
* Read once, when the client is built. The bound is a property of the
|
|
297
|
+
* deployment rather than of a run, so re-reading it per round would only make
|
|
298
|
+
* a mid-run change possible, and a mid-run change to how long the run may
|
|
299
|
+
* last is not a thing a host has any way to reason about.
|
|
300
|
+
*/
|
|
301
|
+
maxToolRounds?: number;
|
|
302
|
+
}
|
|
303
|
+
|
|
304
|
+
/**
|
|
305
|
+
* Raised when a run's stream closes cleanly at the transport level but never
|
|
306
|
+
* emits a terminal AG-UI event, so the run neither finished nor errored. Routed
|
|
307
|
+
* to {@link AgUiClientHandlers.onError} (it is not an abort), turning a silent
|
|
308
|
+
* "stuck pending" into a visible "connection lost".
|
|
309
|
+
*/
|
|
310
|
+
export class ConnectionLostError extends Error {
|
|
311
|
+
constructor(message: string) {
|
|
312
|
+
super(message);
|
|
313
|
+
this.name = "ConnectionLostError";
|
|
314
|
+
}
|
|
315
|
+
}
|
|
316
|
+
|
|
317
|
+
/**
|
|
318
|
+
* Thin orchestration layer over an AG-UI {@link AbstractAgent}.
|
|
319
|
+
*
|
|
320
|
+
* Translates AG-UI's subscriber callbacks into the host's
|
|
321
|
+
* {@link AgUiClientHandlers}, and appends the user message + current frontend
|
|
322
|
+
* tool catalog + context to each run.
|
|
323
|
+
*/
|
|
324
|
+
export class AgUiClient {
|
|
325
|
+
readonly #agent: AbstractAgent;
|
|
326
|
+
readonly #handlers: AgUiClientHandlers;
|
|
327
|
+
readonly #getTools: () => Tool[];
|
|
328
|
+
readonly #getContext: () => Context[];
|
|
329
|
+
readonly #executeTool: ExecuteTool | null;
|
|
330
|
+
readonly #resolveInterrupts: ResolveInterrupts | null;
|
|
331
|
+
readonly #onPersist: (messages: readonly Message[]) => void;
|
|
332
|
+
/**
|
|
333
|
+
* Message ids the server has already closed, so a reuse can be reported. Per
|
|
334
|
+
* client rather than per run — the merge happens across runs, which a per-run
|
|
335
|
+
* set would miss entirely.
|
|
336
|
+
*/
|
|
337
|
+
readonly #closedMessageIds = new Set<string>();
|
|
338
|
+
readonly #connectionLostMessage: string;
|
|
339
|
+
readonly #unfinishedMessage: string;
|
|
340
|
+
readonly #declinedMessage: string;
|
|
341
|
+
readonly #maxToolRounds: number;
|
|
342
|
+
// Set by cancel(); reset as send() or resume() begins, before either runs
|
|
343
|
+
// any host code. Checked by the loop so a cancel between frontend-tool
|
|
344
|
+
// rounds doesn't start another round, and again just before each request,
|
|
345
|
+
// so a cancel from host code the round itself runs is not sent anyway.
|
|
346
|
+
#cancelled = false;
|
|
347
|
+
|
|
348
|
+
constructor(config: AgUiClientConfig) {
|
|
349
|
+
this.#agent = config.agent;
|
|
350
|
+
this.#handlers = config.handlers;
|
|
351
|
+
this.#getTools = config.getTools ?? (() => []);
|
|
352
|
+
this.#getContext = config.getContext ?? (() => []);
|
|
353
|
+
this.#executeTool = config.executeTool ?? null;
|
|
354
|
+
this.#resolveInterrupts = config.resolveInterrupts ?? null;
|
|
355
|
+
this.#onPersist = config.onPersist ?? (() => {});
|
|
356
|
+
this.#connectionLostMessage = config.connectionLostMessage ?? "Connection lost";
|
|
357
|
+
this.#unfinishedMessage =
|
|
358
|
+
config.unfinishedMessage ??
|
|
359
|
+
"Not finished: the run ended or moved on before this tool call returned a result.";
|
|
360
|
+
this.#declinedMessage = config.declinedMessage ?? "User declined the action.";
|
|
361
|
+
// Validated here rather than at each caller, so the element's attribute and
|
|
362
|
+
// a direct consumer get the same answer. A bound below one -- or a NaN from
|
|
363
|
+
// an unparseable attribute -- is not a smaller budget but a send that runs
|
|
364
|
+
// the agent zero times, which would look exactly like a broken endpoint.
|
|
365
|
+
const rounds = config.maxToolRounds ?? MAX_TOOL_ROUNDS;
|
|
366
|
+
this.#maxToolRounds = rounds >= 1 ? Math.floor(rounds) : MAX_TOOL_ROUNDS;
|
|
367
|
+
this.#moveSeededKeysIntoMetadata();
|
|
368
|
+
const onStateChanged = config.onStateChanged;
|
|
369
|
+
if (onStateChanged !== undefined) {
|
|
370
|
+
// The agent applies STATE_SNAPSHOT / STATE_DELTA itself; subscribing is
|
|
371
|
+
// how the result is learned rather than re-derived from the event stream.
|
|
372
|
+
// Lives for the agent's lifetime, which is this client's.
|
|
373
|
+
this.#agent.subscribe({
|
|
374
|
+
onStateChanged: ({ state }) => {
|
|
375
|
+
onStateChanged(state as Record<string, unknown>);
|
|
376
|
+
},
|
|
377
|
+
});
|
|
378
|
+
}
|
|
379
|
+
}
|
|
380
|
+
|
|
381
|
+
/** The current AG-UI shared state, as the agent last applied it. */
|
|
382
|
+
get state(): Readonly<Record<string, unknown>> {
|
|
383
|
+
return this.#agent.state as Record<string, unknown>;
|
|
384
|
+
}
|
|
385
|
+
|
|
386
|
+
/** Replace the shared state; the next run sends it as `RunAgentInput.state`. */
|
|
387
|
+
setState(state: Readonly<Record<string, unknown>>): void {
|
|
388
|
+
this.#agent.setState({ ...state });
|
|
389
|
+
}
|
|
390
|
+
|
|
391
|
+
/** Whether a run is currently in flight. */
|
|
392
|
+
get running(): boolean {
|
|
393
|
+
return this.#agent.isRunning;
|
|
394
|
+
}
|
|
395
|
+
|
|
396
|
+
/** The current conversation history (for persistence / rehydration). */
|
|
397
|
+
get messages(): readonly Message[] {
|
|
398
|
+
return this.#agent.messages;
|
|
399
|
+
}
|
|
400
|
+
|
|
401
|
+
/**
|
|
402
|
+
* Append a user message and run the agent to completion.
|
|
403
|
+
*
|
|
404
|
+
* When the agent calls frontend tools, this executes them and re-runs the
|
|
405
|
+
* agent with the results, looping until the agent stops calling frontend
|
|
406
|
+
* tools (bounded by {@link AgUiClientConfig.maxToolRounds}, which defaults to
|
|
407
|
+
* {@link MAX_TOOL_ROUNDS}).
|
|
408
|
+
*
|
|
409
|
+
* `attachments` ride in the user message's `metadata`, under
|
|
410
|
+
* `attachments`, which is how the server learns which files the agent can
|
|
411
|
+
* read and how the default store round-trips them for history replay; see
|
|
412
|
+
* {@link messageAttachments}.
|
|
413
|
+
*/
|
|
414
|
+
async send(content: string, attachments: readonly AttachmentRef[] = []): Promise<void> {
|
|
415
|
+
// A new interaction, so the Stop of the one before it is forgotten here,
|
|
416
|
+
// ahead of the save below: that save is the host's store, and a Stop it
|
|
417
|
+
// makes (a new chat started from the save) has to stop this turn.
|
|
418
|
+
this.#cancelled = false;
|
|
419
|
+
// In `metadata` because it is the one field on a message `@ag-ui/client`
|
|
420
|
+
// declares open by key. 1.0 strips every undeclared key from the input it
|
|
421
|
+
// sends, so refs at the top level of the message are removed with a
|
|
422
|
+
// console warning and the server is never told a file was attached. No metadata at all
|
|
423
|
+
// without refs: an empty object would put a field on every plain message that says nothing.
|
|
424
|
+
const message: Message = {
|
|
425
|
+
id: randomUUID(),
|
|
426
|
+
role: "user",
|
|
427
|
+
content,
|
|
428
|
+
...(attachments.length > 0 ? { metadata: { attachments } } : {}),
|
|
429
|
+
};
|
|
430
|
+
this.#agent.addMessage(message);
|
|
431
|
+
this.#persist();
|
|
432
|
+
await this.#run();
|
|
433
|
+
}
|
|
434
|
+
|
|
435
|
+
/**
|
|
436
|
+
* Drop everything after the most recent user message, so the same question
|
|
437
|
+
* can be asked again.
|
|
438
|
+
*
|
|
439
|
+
* Returns the retained history, or `null` when there is nothing to retry (no
|
|
440
|
+
* user message has been sent yet). The shortened history is saved before it
|
|
441
|
+
* replaces the current one, so a save that throws changes nothing and the
|
|
442
|
+
* throw propagates.
|
|
443
|
+
*
|
|
444
|
+
* **Truncates only** -- the caller re-renders from the returned list and then
|
|
445
|
+
* calls {@link resume}, because the transcript belongs to the element and a
|
|
446
|
+
* client that reached into it would own two things. Running here instead
|
|
447
|
+
* would stream the new answer in underneath the old one.
|
|
448
|
+
*
|
|
449
|
+
* Re-running answers the question the agent was last asked, rather than
|
|
450
|
+
* telling it its answer was wrong, which is what makes the result a
|
|
451
|
+
* *different* answer instead of a conversation about the previous one.
|
|
452
|
+
*
|
|
453
|
+
* **A retried turn re-runs its tools.** For a page-driving agent that is not
|
|
454
|
+
* neutral: the previous attempt already clicked what it clicked, and this
|
|
455
|
+
* does not undo it.
|
|
456
|
+
*/
|
|
457
|
+
truncateToLastUser(): readonly Message[] | null {
|
|
458
|
+
const messages = [...this.#agent.messages];
|
|
459
|
+
// Forward, keeping the last match, rather than a reverse scan with an
|
|
460
|
+
// index lookup: `noUncheckedIndexedAccess` makes the latter reach for an
|
|
461
|
+
// optional chain whose null arm cannot happen and cannot be covered.
|
|
462
|
+
let lastUser = -1;
|
|
463
|
+
for (const [index, message] of messages.entries()) {
|
|
464
|
+
if (message.role === "user") {
|
|
465
|
+
lastUser = index;
|
|
466
|
+
}
|
|
467
|
+
}
|
|
468
|
+
if (lastUser === -1) {
|
|
469
|
+
return null;
|
|
470
|
+
}
|
|
471
|
+
const kept = messages.slice(0, lastUser + 1);
|
|
472
|
+
// Saved before the history is shortened, so a store that throws leaves it
|
|
473
|
+
// as it was and the throw reaches the caller, as a refused save does from
|
|
474
|
+
// send(), rather than leaving this client without an answer the caller's
|
|
475
|
+
// transcript still shows.
|
|
476
|
+
// Handed over directly rather than through #persist, which saves the list
|
|
477
|
+
// the agent holds, because the agent does not hold this one yet.
|
|
478
|
+
this.#onPersist(kept);
|
|
479
|
+
this.#agent.setMessages(kept);
|
|
480
|
+
return kept;
|
|
481
|
+
}
|
|
482
|
+
|
|
483
|
+
/**
|
|
484
|
+
* Resume the run loop after a navigating tool's result was supplied
|
|
485
|
+
* post-reload (via {@link addToolResult}). Unlike {@link send}, adds no user
|
|
486
|
+
* message; it continues the conversation already in history.
|
|
487
|
+
*/
|
|
488
|
+
async resume(): Promise<void> {
|
|
489
|
+
// A new interaction, as in send(); no host code runs between here and the loop.
|
|
490
|
+
this.#cancelled = false;
|
|
491
|
+
await this.#run();
|
|
492
|
+
}
|
|
493
|
+
|
|
494
|
+
/** Append a frontend tool result to history (used by the resume path). */
|
|
495
|
+
addToolResult(toolCallId: string, content: string): void {
|
|
496
|
+
this.#agent.addMessage({ id: randomUUID(), role: "tool", content, toolCallId });
|
|
497
|
+
this.#persist();
|
|
498
|
+
}
|
|
499
|
+
|
|
500
|
+
/**
|
|
501
|
+
* Cancel the in-flight run (AG-UI has no server cancel route — aborting the
|
|
502
|
+
* streaming request is the protocol's cancel; the server observes the
|
|
503
|
+
* disconnect). Safe to call with no run in flight. Partial text already
|
|
504
|
+
* streamed stays in history; {@link AgUiClientHandlers.onCancelled} fires
|
|
505
|
+
* instead of `onError`, and `onSettled` still follows.
|
|
506
|
+
*
|
|
507
|
+
* Also stops a {@link send} or {@link resume} whose request has not been made
|
|
508
|
+
* yet, when host code it runs on the way calls this: the save of the turn,
|
|
509
|
+
* the tool and context providers, the save answering a call left open. No
|
|
510
|
+
* request is made then, and `onCancelled` and `onSettled` fire as for any
|
|
511
|
+
* other Stop. A cancel with nothing in flight is forgotten when the next
|
|
512
|
+
* send or resume begins.
|
|
513
|
+
*/
|
|
514
|
+
cancel(): void {
|
|
515
|
+
this.#cancelled = true;
|
|
516
|
+
this.#agent.abortRun();
|
|
517
|
+
}
|
|
518
|
+
|
|
519
|
+
async #run(): Promise<void> {
|
|
520
|
+
try {
|
|
521
|
+
await this.#runLoop();
|
|
522
|
+
// `@ag-ui/client` filters abort errors inside `runAgent` (it resolves
|
|
523
|
+
// rather than rejects on a cancelled fetch), so a cancelled run usually
|
|
524
|
+
// ends here, not in the catch.
|
|
525
|
+
if (this.#cancelled) {
|
|
526
|
+
this.#onCancelled();
|
|
527
|
+
}
|
|
528
|
+
} catch (error) {
|
|
529
|
+
if (this.#cancelled || isAbortError(error)) {
|
|
530
|
+
this.#onCancelled();
|
|
531
|
+
} else {
|
|
532
|
+
this.#handlers.onError(error instanceof Error ? error.message : String(error));
|
|
533
|
+
}
|
|
534
|
+
} finally {
|
|
535
|
+
this.#handlers.onSettled();
|
|
536
|
+
}
|
|
537
|
+
}
|
|
538
|
+
|
|
539
|
+
#onCancelled(): void {
|
|
540
|
+
// Persist so the truncated exchange, partial assistant text included,
|
|
541
|
+
// survives a reload.
|
|
542
|
+
this.#persist();
|
|
543
|
+
this.#handlers.onCancelled();
|
|
544
|
+
}
|
|
545
|
+
|
|
546
|
+
/**
|
|
547
|
+
* Move the keys a history stored by an earlier release carries at the top
|
|
548
|
+
* level of its messages into their `metadata`, where this release writes them.
|
|
549
|
+
*
|
|
550
|
+
* Earlier releases wrote a tool message's `outcome` and a user message's
|
|
551
|
+
* `attachments` beside the protocol's own fields. `@ag-ui/client` 1.0 strips
|
|
552
|
+
* every undeclared key from the input it sends, so a restored conversation
|
|
553
|
+
* sent as it was stored would lose both on its first request: the server
|
|
554
|
+
* would no longer be told about any file attached before the upgrade, and
|
|
555
|
+
* each strip would print a warning. Moved rather than dropped, so the next
|
|
556
|
+
* save writes the conversation in the shape this release reads first and a
|
|
557
|
+
* reload after it still replays a declined card as declined.
|
|
558
|
+
*
|
|
559
|
+
* Merged into the metadata a message already has, with a key already there
|
|
560
|
+
* winning: a message carrying both was last written by the newer writer.
|
|
561
|
+
* `ag_ui_chat_restored_outcome_wire.test.ts` reads the result off the body of
|
|
562
|
+
* a real request. The check before `setMessages` only spares a copy when
|
|
563
|
+
* nothing moves, which is every restore of a store this release wrote;
|
|
564
|
+
* nothing observable depends on it.
|
|
565
|
+
*/
|
|
566
|
+
#moveSeededKeysIntoMetadata(): void {
|
|
567
|
+
const seeded = this.#agent.messages;
|
|
568
|
+
const moved = seeded.map(withTopLevelKeysInMetadata);
|
|
569
|
+
if (moved.some((message, index) => message !== seeded[index])) {
|
|
570
|
+
this.#agent.setMessages(moved);
|
|
571
|
+
}
|
|
572
|
+
}
|
|
573
|
+
|
|
574
|
+
/**
|
|
575
|
+
* Give every tool call in history a result, before a request carries it.
|
|
576
|
+
*
|
|
577
|
+
* Called ahead of every `runAgent`, which makes it the one place the rule is
|
|
578
|
+
* kept for every way a call can be left open -- a live run's Stop or
|
|
579
|
+
* `RUN_ERROR`, a name no tool here owns, a server that sent no result -- and
|
|
580
|
+
* for a client seeded with a restored history just the same. The answer says
|
|
581
|
+
* the call did not finish rather than that anyone declined it: a person
|
|
582
|
+
* declined only where they were asked, and those answers are already here by
|
|
583
|
+
* the time this runs (see {@link #answerStoppedApprovals}).
|
|
584
|
+
*
|
|
585
|
+
* `resuming` names the calls the request is itself answering: a resumed
|
|
586
|
+
* approval runs or denies them server-side, and a result here would answer
|
|
587
|
+
* them twice.
|
|
588
|
+
*
|
|
589
|
+
* The outcome rides in the result's `metadata`, where the client folds a
|
|
590
|
+
* server's own, so the next request carries it and a reload replays it. A
|
|
591
|
+
* server that does not read it ignores it: `metadata` is a declared field.
|
|
592
|
+
*/
|
|
593
|
+
#answerUnansweredCalls(resuming: ReadonlySet<string>): void {
|
|
594
|
+
const messages = this.#agent.messages;
|
|
595
|
+
const answered = answerUnansweredCalls(
|
|
596
|
+
messages,
|
|
597
|
+
(toolCallId) => ({
|
|
598
|
+
id: randomUUID(),
|
|
599
|
+
role: "tool",
|
|
600
|
+
content: this.#unfinishedMessage,
|
|
601
|
+
toolCallId,
|
|
602
|
+
metadata: { outcome: TOOL_OUTCOME.INTERRUPTED },
|
|
603
|
+
}),
|
|
604
|
+
resuming,
|
|
605
|
+
);
|
|
606
|
+
if (answered !== messages) {
|
|
607
|
+
this.#agent.setMessages([...answered]);
|
|
608
|
+
this.#persist();
|
|
609
|
+
}
|
|
610
|
+
}
|
|
611
|
+
|
|
612
|
+
/**
|
|
613
|
+
* Answer, as declined, every approval that Stop closed as declined.
|
|
614
|
+
*
|
|
615
|
+
* Stop cancels the resume that would have told the server, so the decline the
|
|
616
|
+
* approval recorded reaches no one unless history carries it. Only calls the
|
|
617
|
+
* host's resolver answered `cancelled` are declined: a person who approved a
|
|
618
|
+
* call and then pressed Stop did not decline it, and
|
|
619
|
+
* {@link #answerUnansweredCalls} answers that one on the next request as not
|
|
620
|
+
* finished.
|
|
621
|
+
*
|
|
622
|
+
* The status check is held by "does not call an approved call declined, since
|
|
623
|
+
* nobody declined it" in `ag_ui_chat_unanswered_tool_calls.test.ts`; the
|
|
624
|
+
* `toolCallId` check is held by the type checker, since a result needs an id
|
|
625
|
+
* to answer.
|
|
626
|
+
*/
|
|
627
|
+
#answerStoppedApprovals(
|
|
628
|
+
interrupts: readonly Interrupt[],
|
|
629
|
+
responses: Readonly<Record<string, InterruptResponse>>,
|
|
630
|
+
): void {
|
|
631
|
+
for (const interrupt of interrupts) {
|
|
632
|
+
const toolCallId = interrupt.toolCallId;
|
|
633
|
+
if (toolCallId !== undefined && responses[interrupt.id]?.status === "cancelled") {
|
|
634
|
+
this.#agent.addMessage({
|
|
635
|
+
id: randomUUID(),
|
|
636
|
+
role: "tool",
|
|
637
|
+
content: this.#declinedMessage,
|
|
638
|
+
toolCallId,
|
|
639
|
+
metadata: { outcome: TOOL_OUTCOME.DENIED },
|
|
640
|
+
});
|
|
641
|
+
}
|
|
642
|
+
}
|
|
643
|
+
this.#persist();
|
|
644
|
+
}
|
|
645
|
+
|
|
646
|
+
/**
|
|
647
|
+
* Hand the transcript to the host's store.
|
|
648
|
+
*
|
|
649
|
+
* The list as the agent holds it, with nothing added: how each tool call
|
|
650
|
+
* ended rides in its result's `metadata`, which the client folds in from a
|
|
651
|
+
* server's `TOOL_CALL_RESULT` and this class writes for the results it makes
|
|
652
|
+
* itself, so the store sees exactly what the next request sends.
|
|
653
|
+
*/
|
|
654
|
+
#persist(): void {
|
|
655
|
+
this.#onPersist(this.#agent.messages);
|
|
656
|
+
}
|
|
657
|
+
|
|
658
|
+
async #runLoop(): Promise<void> {
|
|
659
|
+
// Carries resolved approval answers into the next run when a round finished
|
|
660
|
+
// on a server-side-tool interrupt. Distinct from the public resume(), which
|
|
661
|
+
// continues an unfinished frontend-tool round after a page load; this stays
|
|
662
|
+
// inside one #run().
|
|
663
|
+
let resume: ResumeEntry[] | undefined;
|
|
664
|
+
let resuming: ReadonlySet<string> = new Set();
|
|
665
|
+
for (let round = 0; round < this.#maxToolRounds; round += 1) {
|
|
666
|
+
// A cancel during the previous round's tool execution lands here: the
|
|
667
|
+
// running handler completed, but no further round starts. The check
|
|
668
|
+
// before the request below would stop the round as well; this one keeps
|
|
669
|
+
// a stopped loop from running the providers and the store for a round
|
|
670
|
+
// it will not send.
|
|
671
|
+
if (this.#cancelled) {
|
|
672
|
+
return;
|
|
673
|
+
}
|
|
674
|
+
const pending: AgUiToolCall[] = [];
|
|
675
|
+
const runState: RunState = { terminal: false, errored: false, interrupts: [] };
|
|
676
|
+
const params: RunAgentParameters = {
|
|
677
|
+
tools: this.#getTools(),
|
|
678
|
+
context: this.#getContext(),
|
|
679
|
+
};
|
|
680
|
+
if (resume !== undefined) {
|
|
681
|
+
params.resume = resume;
|
|
682
|
+
}
|
|
683
|
+
this.#answerUnansweredCalls(resuming);
|
|
684
|
+
// Everything above since the check at the top is host code -- the tool
|
|
685
|
+
// and context providers, and the store saving the answers just made -- and
|
|
686
|
+
// any of it can be what stopped the run, by starting a new chat or
|
|
687
|
+
// changing the principal. `@ag-ui/client` makes a fresh abort controller
|
|
688
|
+
// for each run, so the abort `cancel()` made in that time reached
|
|
689
|
+
// nothing, and only this check keeps the request from going out.
|
|
690
|
+
if (this.#cancelled) {
|
|
691
|
+
return;
|
|
692
|
+
}
|
|
693
|
+
await this.#agent.runAgent(params, this.#buildSubscriber(pending, runState));
|
|
694
|
+
resume = undefined;
|
|
695
|
+
resuming = new Set();
|
|
696
|
+
this.#persist();
|
|
697
|
+
// Cancelled mid-stream: don't execute the tool calls collected before the
|
|
698
|
+
// abort.
|
|
699
|
+
if (this.#cancelled) {
|
|
700
|
+
return;
|
|
701
|
+
}
|
|
702
|
+
// The stream resolved without RUN_FINISHED / RUN_ERROR, so the transport
|
|
703
|
+
// dropped mid-run. Surface it as an error (via #run) rather than resting
|
|
704
|
+
// silently with a stuck pending indicator.
|
|
705
|
+
if (!runState.terminal) {
|
|
706
|
+
throw new ConnectionLostError(this.#connectionLostMessage);
|
|
707
|
+
}
|
|
708
|
+
// RUN_ERROR is terminal and the agent already reported it via onError.
|
|
709
|
+
// Executing the calls collected before it, or starting another round,
|
|
710
|
+
// would run into a broken context and raise a confusing second error.
|
|
711
|
+
// Pending tool cards are swept at onSettled.
|
|
712
|
+
if (runState.errored) {
|
|
713
|
+
return;
|
|
714
|
+
}
|
|
715
|
+
// A gated server-side tool deferred, so the run finished on an interrupt
|
|
716
|
+
// outcome: resolve each interrupt, then re-enter carrying the answers so
|
|
717
|
+
// the follow-up run executes or denies the tool. Must precede the
|
|
718
|
+
// frontend-tool sweep below — a server-side tool isn't ours to run.
|
|
719
|
+
if (runState.interrupts.length > 0) {
|
|
720
|
+
if (this.#resolveInterrupts === null) {
|
|
721
|
+
return;
|
|
722
|
+
}
|
|
723
|
+
const responses = await this.#resolveInterrupts(runState.interrupts);
|
|
724
|
+
if (this.#cancelled) {
|
|
725
|
+
this.#answerStoppedApprovals(runState.interrupts, responses);
|
|
726
|
+
return;
|
|
727
|
+
}
|
|
728
|
+
resume = buildResumeArray(runState.interrupts, responses);
|
|
729
|
+
resuming = new Set(runState.interrupts.flatMap(({ toolCallId }) => toolCallId ?? []));
|
|
730
|
+
continue;
|
|
731
|
+
}
|
|
732
|
+
if (this.#executeTool === null || pending.length === 0) {
|
|
733
|
+
return;
|
|
734
|
+
}
|
|
735
|
+
let executed = false;
|
|
736
|
+
for (const call of pending) {
|
|
737
|
+
// A cancel while an earlier call in this round ran lands here. That
|
|
738
|
+
// handler could not be aborted and its result is kept, but the calls
|
|
739
|
+
// after it have not started, and a person who pressed Stop during the
|
|
740
|
+
// first page action did not ask for the second. They are answered as not
|
|
741
|
+
// finished before the next request.
|
|
742
|
+
if (this.#cancelled) {
|
|
743
|
+
return;
|
|
744
|
+
}
|
|
745
|
+
const result = await this.#executeTool(call);
|
|
746
|
+
if (result === null) {
|
|
747
|
+
continue;
|
|
748
|
+
}
|
|
749
|
+
if (result.halt === true) {
|
|
750
|
+
// A navigating tool reloaded the page; the result arrives on the
|
|
751
|
+
// next mount. Stop here rather than re-running into a dead context.
|
|
752
|
+
return;
|
|
753
|
+
}
|
|
754
|
+
// A frontend tool's refusal or failure is stated by no server, because
|
|
755
|
+
// no server ran the call, so the client writes it where the client
|
|
756
|
+
// folds a server's: the result's `metadata`. Written with the message,
|
|
757
|
+
// so the very first save of it already carries how it ended, and a
|
|
758
|
+
// reload reads the card off it. A success writes no metadata at all,
|
|
759
|
+
// as a server's success states none.
|
|
760
|
+
this.#agent.addMessage({
|
|
761
|
+
id: randomUUID(),
|
|
762
|
+
role: "tool",
|
|
763
|
+
content: result.content,
|
|
764
|
+
toolCallId: call.id,
|
|
765
|
+
...(result.outcome === undefined ? {} : { metadata: { outcome: result.outcome } }),
|
|
766
|
+
});
|
|
767
|
+
this.#persist();
|
|
768
|
+
executed = true;
|
|
769
|
+
}
|
|
770
|
+
if (!executed) {
|
|
771
|
+
return;
|
|
772
|
+
}
|
|
773
|
+
}
|
|
774
|
+
}
|
|
775
|
+
|
|
776
|
+
#buildSubscriber(pending: AgUiToolCall[], runState: RunState): AgentSubscriber {
|
|
777
|
+
const h = this.#handlers;
|
|
778
|
+
const closed = this.#closedMessageIds;
|
|
779
|
+
// Read at event time, not captured now: the flag flips mid-run, and the
|
|
780
|
+
// subscriber is built before the run that a later `cancel()` stops.
|
|
781
|
+
const cancelled = (): boolean => this.#cancelled;
|
|
782
|
+
// Charts whose patch has been dispatched but not yet applied. Scoped to the
|
|
783
|
+
// subscriber, so it cannot outlive the run that created it.
|
|
784
|
+
const pendingDeltas = new Set<string>();
|
|
785
|
+
// The run this round's stream declared. Per subscriber, so per round: a
|
|
786
|
+
// turn that calls a tool is two runs, and its answer belongs to the second.
|
|
787
|
+
//
|
|
788
|
+
// Taken from `RUN_STARTED` rather than the `runId` the request carried.
|
|
789
|
+
// The client mints that one, and a server is free to record the run under
|
|
790
|
+
// the id it streams back; the event is the server's own statement of
|
|
791
|
+
// which run this is, so it is the id a host can find again.
|
|
792
|
+
let runId: string | undefined;
|
|
793
|
+
return {
|
|
794
|
+
onRunInitialized() {
|
|
795
|
+
h.onRunStart();
|
|
796
|
+
},
|
|
797
|
+
onRunStartedEvent({ event }) {
|
|
798
|
+
runId = event.runId;
|
|
799
|
+
},
|
|
800
|
+
onTextMessageStartEvent({ event }) {
|
|
801
|
+
// A reused message id merges two answers into one persisted transcript
|
|
802
|
+
// entry. The protocol has no rule to enforce and refusing the event
|
|
803
|
+
// would be worse than the merge, so warn and continue — but do not stay
|
|
804
|
+
// silent, since the corruption outlives the session.
|
|
805
|
+
if (closed.has(event.messageId)) {
|
|
806
|
+
console.warn(
|
|
807
|
+
`<ag-ui-chat>: the server reused message id "${event.messageId}", which was ` +
|
|
808
|
+
"already closed. Its content will be appended to that earlier message rather " +
|
|
809
|
+
"than starting a new one, and the merged result is what gets persisted. " +
|
|
810
|
+
"Issue a fresh id per message.",
|
|
811
|
+
);
|
|
812
|
+
}
|
|
813
|
+
},
|
|
814
|
+
onTextMessageContentEvent({ textMessageBuffer }) {
|
|
815
|
+
h.onTextDelta(textMessageBuffer);
|
|
816
|
+
},
|
|
817
|
+
onTextMessageEndEvent({ event, textMessageBuffer }) {
|
|
818
|
+
closed.add(event.messageId);
|
|
819
|
+
h.onTextEnd(textMessageBuffer, {
|
|
820
|
+
messageId: event.messageId,
|
|
821
|
+
...(runId === undefined ? {} : { runId }),
|
|
822
|
+
});
|
|
823
|
+
},
|
|
824
|
+
onToolCallEndEvent({ event, toolCallName, toolCallArgs }) {
|
|
825
|
+
const call: AgUiToolCall = {
|
|
826
|
+
id: event.toolCallId,
|
|
827
|
+
name: toolCallName,
|
|
828
|
+
args: toolCallArgs,
|
|
829
|
+
};
|
|
830
|
+
pending.push(call);
|
|
831
|
+
h.onToolCall(call);
|
|
832
|
+
},
|
|
833
|
+
onToolCallResultEvent({ event }) {
|
|
834
|
+
// In the event's `metadata`, the one place on it `@ag-ui/client` 1.0
|
|
835
|
+
// declares open by key. Its enforcement stage strips every undeclared
|
|
836
|
+
// key before a subscriber runs, so an `outcome` stated at the top level
|
|
837
|
+
// of the event never gets here.
|
|
838
|
+
//
|
|
839
|
+
// Nothing is recorded for the transcript: after this returns, the
|
|
840
|
+
// client builds the tool message and folds the event's metadata onto
|
|
841
|
+
// it, so the outcome is already on the message a save writes, word for
|
|
842
|
+
// word as the server said it. A reload reads it back through the same
|
|
843
|
+
// mapping as the live path, which is what keeps the two agreeing.
|
|
844
|
+
const outcome: unknown = event.metadata?.["outcome"];
|
|
845
|
+
// Content may arrive as parts since the protocol's 1.0, so a tool can
|
|
846
|
+
// return an image beside its text. A card shows text, so the text parts
|
|
847
|
+
// are joined and the rest left to the host store, which keeps the
|
|
848
|
+
// message whole; a plain string passes through unchanged.
|
|
849
|
+
h.onToolResult(event.toolCallId, contentToText(event.content), outcome);
|
|
850
|
+
},
|
|
851
|
+
onActivitySnapshotEvent({ event, messages }) {
|
|
852
|
+
// A snapshot for an id already in the list is a replacement, not a new
|
|
853
|
+
// activity: the client has swapped its content in place, and a second
|
|
854
|
+
// append would leave the superseded one on screen.
|
|
855
|
+
const known = messages.some(
|
|
856
|
+
(message) => message.id === event.messageId && message.role === "activity",
|
|
857
|
+
);
|
|
858
|
+
if (known) {
|
|
859
|
+
h.onActivityChanged(event.messageId, event.activityType, event.content);
|
|
860
|
+
return;
|
|
861
|
+
}
|
|
862
|
+
h.onActivity(event.activityType, event.content, event.messageId);
|
|
863
|
+
},
|
|
864
|
+
// Deliberately does *not* read the message here. `@ag-ui/client`
|
|
865
|
+
// dispatches this subscriber **before** applying the patch, so the
|
|
866
|
+
// message still holds its previous content: redrawing from it would leave
|
|
867
|
+
// the chart one revision behind for the life of the run, and disagreeing
|
|
868
|
+
// with what a reload shows. Note which chart moved and read the result on
|
|
869
|
+
// the change that follows.
|
|
870
|
+
onActivityDeltaEvent({ event }) {
|
|
871
|
+
pendingDeltas.add(event.messageId);
|
|
872
|
+
},
|
|
873
|
+
onCustomEvent({ event }) {
|
|
874
|
+
h.onCustomEvent(event.name, event.value);
|
|
875
|
+
},
|
|
876
|
+
// The delegation lifecycle. Forwarded rather than interpreted here, for
|
|
877
|
+
// the reason every other subscriber in this block is thin: this file
|
|
878
|
+
// adapts the protocol to the handler surface, and what a delegation looks
|
|
879
|
+
// like on screen is the element's business.
|
|
880
|
+
//
|
|
881
|
+
// `parentToolCallId` is optional on the wire and normalised to `null` so
|
|
882
|
+
// the handler has one absent-value to check rather than two.
|
|
883
|
+
onSubagentStartedEvent({ event }) {
|
|
884
|
+
h.onSubAgentStarted(event.subagentRunId, event.name, event.parentToolCallId ?? null);
|
|
885
|
+
},
|
|
886
|
+
onSubagentFinishedEvent({ event }) {
|
|
887
|
+
h.onSubAgentFinished(event.subagentRunId);
|
|
888
|
+
},
|
|
889
|
+
onSubagentErrorEvent({ event }) {
|
|
890
|
+
h.onSubAgentError(event.subagentRunId, event.message);
|
|
891
|
+
},
|
|
892
|
+
onMessagesSnapshotEvent({ event }) {
|
|
893
|
+
h.onMessagesSnapshot(event.messages as readonly Message[]);
|
|
894
|
+
},
|
|
895
|
+
// Emitted after the client has written the patched messages, which is the
|
|
896
|
+
// first moment the result exists. Only the ids marked above are looked at,
|
|
897
|
+
// so an ordinary text delta does not walk the transcript.
|
|
898
|
+
onMessagesChanged({ messages }) {
|
|
899
|
+
for (const id of pendingDeltas) {
|
|
900
|
+
const message = messages.find((entry) => entry.id === id);
|
|
901
|
+
if (message !== undefined && message.role === "activity") {
|
|
902
|
+
h.onActivityChanged(id, message.activityType, message.content);
|
|
903
|
+
}
|
|
904
|
+
}
|
|
905
|
+
pendingDeltas.clear();
|
|
906
|
+
},
|
|
907
|
+
// THINKING_* left the protocol in its 1.0, but `@ag-ui/client` still
|
|
908
|
+
// converts a server's THINKING_* events onto these REASONING_* callbacks,
|
|
909
|
+
// with a console warning, for a deprecation window it sets. Nothing in the
|
|
910
|
+
// family emits THINKING_*: pydantic-ai has sent REASONING_* at every
|
|
911
|
+
// protocol version django-ag-ui admits.
|
|
912
|
+
onReasoningStartEvent() {
|
|
913
|
+
h.onReasoningStart();
|
|
914
|
+
},
|
|
915
|
+
onReasoningMessageContentEvent({ reasoningMessageBuffer }) {
|
|
916
|
+
h.onReasoningDelta(reasoningMessageBuffer);
|
|
917
|
+
},
|
|
918
|
+
// The delta callback reports the buffer as it stood *before* the announced
|
|
919
|
+
// delta was appended, so on its own it always trails the stream by one and
|
|
920
|
+
// renders nothing at all for a block that arrives as a single delta. The
|
|
921
|
+
// answer text is spared that because its own end event carries the whole
|
|
922
|
+
// message; this is the reasoning counterpart, and it has to be
|
|
923
|
+
// REASONING_MESSAGE_END rather than REASONING_END, because only the former
|
|
924
|
+
// carries a buffer.
|
|
925
|
+
onReasoningMessageEndEvent({ reasoningMessageBuffer }) {
|
|
926
|
+
h.onReasoningDelta(reasoningMessageBuffer);
|
|
927
|
+
},
|
|
928
|
+
onReasoningEndEvent() {
|
|
929
|
+
h.onReasoningEnd();
|
|
930
|
+
},
|
|
931
|
+
onRunFinishedEvent(params) {
|
|
932
|
+
// RUN_FINISHED is terminal for both a normal finish and an interrupt.
|
|
933
|
+
// Capture the interrupts here rather than reading the agent's
|
|
934
|
+
// `pendingInterrupts` afterwards, to stay independent of that field's
|
|
935
|
+
// cross-run clearing semantics.
|
|
936
|
+
runState.terminal = true;
|
|
937
|
+
if (params.outcome === "interrupt") {
|
|
938
|
+
runState.interrupts = params.interrupts;
|
|
939
|
+
}
|
|
940
|
+
},
|
|
941
|
+
onRunErrorEvent({ event }) {
|
|
942
|
+
runState.terminal = true;
|
|
943
|
+
runState.errored = true;
|
|
944
|
+
// Cancelling aborts the response mid-read, and the browser's own words
|
|
945
|
+
// for that can arrive here as a RUN_ERROR — Chrome's is
|
|
946
|
+
// "BodyStreamBuffer was aborted". The run is over either way, but a
|
|
947
|
+
// deliberate stop is not a failure, and reporting it would raise a
|
|
948
|
+
// warning bubble above the stopped note saying the same thing twice.
|
|
949
|
+
// The promise route in `#run` reports the cancellation.
|
|
950
|
+
if (cancelled()) {
|
|
951
|
+
return;
|
|
952
|
+
}
|
|
953
|
+
h.onError(event.message);
|
|
954
|
+
},
|
|
955
|
+
onRunFinalized() {
|
|
956
|
+
runState.terminal = true;
|
|
957
|
+
h.onRunEnd();
|
|
958
|
+
},
|
|
959
|
+
};
|
|
960
|
+
}
|
|
961
|
+
}
|
|
962
|
+
|
|
963
|
+
/** Per-run mutable flags the subscriber writes and {@link AgUiClient} reads. */
|
|
964
|
+
interface RunState {
|
|
965
|
+
terminal: boolean;
|
|
966
|
+
errored: boolean;
|
|
967
|
+
/** Approval interrupts a run finished on (empty for a normal finish). */
|
|
968
|
+
interrupts: Interrupt[];
|
|
969
|
+
}
|
|
970
|
+
|
|
971
|
+
/**
|
|
972
|
+
* Whether a rejection came from aborting the run's fetch. Belt-and-suspenders
|
|
973
|
+
* with the `#cancelled` flag: some `@ag-ui/client` versions re-throw the
|
|
974
|
+
* `AbortError` instead of filtering it.
|
|
975
|
+
*
|
|
976
|
+
* Aborting a fetch whose body is mid-read does not always surface as an
|
|
977
|
+
* `AbortError`. Chrome raises `TypeError: BodyStreamBuffer was aborted`, which
|
|
978
|
+
* is the same event wearing a different name, so a message naming the abort is
|
|
979
|
+
* read as one too. Narrow on purpose: only a `TypeError`, and only when it says
|
|
980
|
+
* so — a genuine type error carries no such word, and misreading one as a
|
|
981
|
+
* cancellation would hide a real failure behind a stopped note.
|
|
982
|
+
*/
|
|
983
|
+
function isAbortError(error: unknown): boolean {
|
|
984
|
+
if (!(error instanceof Error)) {
|
|
985
|
+
return false;
|
|
986
|
+
}
|
|
987
|
+
return (
|
|
988
|
+
error.name === "AbortError" || (error instanceof TypeError && /abort/i.test(error.message))
|
|
989
|
+
);
|
|
990
|
+
}
|
|
991
|
+
|
|
992
|
+
/**
|
|
993
|
+
* `message` with the keys an earlier release wrote at its top level -- a tool
|
|
994
|
+
* message's `outcome`, a user message's `attachments` -- moved into its
|
|
995
|
+
* `metadata`, or `message` itself when it carries neither.
|
|
996
|
+
*
|
|
997
|
+
* Both keys move whatever the role: no message the protocol defines declares
|
|
998
|
+
* either, so a top-level one can only be this component's own. A key already in
|
|
999
|
+
* the metadata wins over the one being moved, and a `metadata` that is not an
|
|
1000
|
+
* object is replaced rather than spread -- a store is not trusted to hold the
|
|
1001
|
+
* shape anything here wrote, and a spread string would scatter into numbered
|
|
1002
|
+
* keys.
|
|
1003
|
+
*
|
|
1004
|
+
* Held by "carries history stored by an earlier release into metadata" in
|
|
1005
|
+
* `agui_client.test.ts`, which seeds a message with only `attachments`, one
|
|
1006
|
+
* with only `outcome`, one whose metadata already has the key, and one whose
|
|
1007
|
+
* metadata is a string: deleting either half of the early return, or the
|
|
1008
|
+
* object check, fails it.
|
|
1009
|
+
*/
|
|
1010
|
+
function withTopLevelKeysInMetadata(message: Message): Message {
|
|
1011
|
+
const { outcome, attachments, ...wire } = message as Message & {
|
|
1012
|
+
outcome?: unknown;
|
|
1013
|
+
attachments?: unknown;
|
|
1014
|
+
};
|
|
1015
|
+
if (outcome === undefined && attachments === undefined) {
|
|
1016
|
+
return message;
|
|
1017
|
+
}
|
|
1018
|
+
const existing = (wire as { metadata?: unknown }).metadata;
|
|
1019
|
+
return {
|
|
1020
|
+
...wire,
|
|
1021
|
+
metadata: {
|
|
1022
|
+
...(outcome === undefined ? {} : { outcome }),
|
|
1023
|
+
...(attachments === undefined ? {} : { attachments }),
|
|
1024
|
+
// `null` passes the check and spreads as nothing, which is what it means.
|
|
1025
|
+
...(typeof existing === "object" ? existing : {}),
|
|
1026
|
+
},
|
|
1027
|
+
} as Message;
|
|
1028
|
+
}
|