FE-1448: Let Petrinaut hosts render interactive chat tools - #9249
FE-1448: Let Petrinaut hosts render interactive chat tools#9249lunelson wants to merge 2 commits into
Conversation
Exercise the real AiAssistantPanel and AI SDK addToolOutput path, including exactly one automatic follow-up after duplicate submits. Keep rejected submissions retryable, use Panda styles in the visible story, and delimit the synthetic transport steps to prevent repeated follow-ups.
|
The latest updates on your projects. Learn more about Vercel for GitHub.
1 Skipped Deployment
|
PR SummaryMedium Risk Overview A registered tool is a name, structural input/output parsers, and a React widget. Dynamic calls are validated, rendered in chat ( Docs, registry/panel tests, and the Reviewed by Cursor Bugbot for commit 720c6b1. Bugbot is set up for automated code reviews on this repo. Configure here. |
There was a problem hiding this comment.
Cursor Bugbot has reviewed your changes and found 2 potential issues.
❌ Bugbot Autofix is OFF. To automatically fix reported issues with cloud agents, enable autofix in the Cursor dashboard.
Want reviews to match your repository better? Bugbot Learning can learn team-specific rules from PR activity. A team admin can enable Learning in the Cursor dashboard.
Reviewed by Cursor Bugbot for commit 720c6b1. Configure here.
| export const AiAssistantContents = ({ | ||
| error, | ||
| input, | ||
| interactiveTools = [], |
There was a problem hiding this comment.
Empty default busts message memo
Medium Severity
interactiveTools = [] allocates a new array whenever the prop is omitted, which is the common no-host-tools path. That new reference is passed into memoized AiAssistantMessage, so completed transcript rows re-render on every throttled stream chunk and undo the memoization this panel relies on to stay responsive.
Additional Locations (1)
Reviewed by Cursor Bugbot for commit 720c6b1. Configure here.
| const submitted = tool.state === "output-available"; | ||
| const submittedOnceRef = useRef(submitted); | ||
| const Widget = definition.Widget; | ||
| const typedInput = definition.parseInput(input); |
There was a problem hiding this comment.
Host parse crashes mid-stream
High Severity
Host interactive tools always shouldHandle, and InteractiveToolItem now calls parseInput during render with no fallback. While a dynamic tool call is still input-streaming (or otherwise has incomplete input), schema parse throws and can take down the chat panel before onToolCall validation runs.
Additional Locations (1)
Reviewed by Cursor Bugbot for commit 720c6b1. Configure here.



🌟 What is the purpose of this PR?
Brunch — the elicitation agent being integrated with Petrinaut — needs to ask its questions inline in Petrinaut's chat panel, but the panel could only render its own built-in interactive tool, and
@hashintel/petrinautmust stay free of Brunch-specific code. This PR adds a public, typed extension point through which an embedding application registers its own interactive chat tools: a registered tool renders inline, collects exactly one submitted answer, and returns it to the assistant through the same follow-up path the built-in tools use.🔗 Related links
🚫 Blocked by
Nothing.
🔍 What does this change?
Before this change, the chat panel's interactive-tool registry was private and hard-coded to Petrinaut's own
applyAutoLayoutconfirmation widget, so any other dynamic tool call was rejected before a host could render or handle it. The panel now accepts host-registered tools through the publicaiAssistantconfiguration: a host defines a tool withdefinePetrinautAiInteractiveTool— a tool name, input and output parsers, and a React component — and the panel renders it inline, keeps it visible as a read-only record after submission, and sends exactly one output back to the assistant. Built-in tools keep their behavior, and a dynamic tool that no host registered still fails loudly.🏗️ Agent notes
Public API and type erasure
definePetrinautAiInteractiveTooland thePetrinautAiInteractiveTool*types are exported from@hashintel/petrinaut; hosts pass registrations throughaiAssistant.interactiveTools.any.{ parse(value: unknown): Value }), so Zod, Valibot, or hand-written parsers all fit without a validation-library choice leaking into the public API.Lifecycle
state: "awaiting" | "submitted", with a stable AI SDKtoolCallId;submitaccepts exactly one output — duplicate submissions are ignored and produce exactly one automatic follow-up through the existingaddToolOutputpath.Verification
AiAssistantPanelthrough submission, duplicate submission, and rejected-then-retried submission, asserting exactly one follow-up request; the synthetic test transport's steps are delimited to prevent repeated follow-ups.Petrinaut / WithHostInteractiveAiToolstory registers a visible synthetic host tool, styled with Panda.Commits
57913da2bbadds the host interactive-tool API and registry routing.720c6b1097proves the follow-up lifecycle against the real panel and adds the visible story.Pre-Merge Checklist 🚀
🚢 Has this modified a publishable library?
This PR:
@hashintel/petrinautis npm-publishable and this branch carries no changeset; the changeset for this extension point currently lives on #9274, so whether this branch needs its own depends on merge order — see Known issues.📜 Does this require a change to the docs?
The changes in this PR:
The end-user AI assistant guide now describes host-provided inline tools, and the package README documents the public API.
🕸️ Does this require a change to the Turbo Graph?
The changes in this PR:
PR #9274 (FE-1437, the Brunch monorepo import) carries an evolved variant of this extension point together with a real host tool (
brunch_askinapps/petrinaut-website) and the@hashintel/petrinautchangeset. The two branches' Petrinaut trees have diverged, so whichever merges second must be reconciled against the first; merge order is an open sequencing decision.🐾 Next steps
FE-1449 (already done) built on this mechanism to prove that a structured Brunch question suspends and resumes through the real panel; that work now travels with the FE-1437 import branch (#9274). The remaining step for this PR is the reconciliation described under Known issues.
🛡 What tests cover this?
AiAssistantPanellifecycle tests: submission, duplicate submission, and rejected-then-retried submission, with exactly one follow-up request.WithHostInteractiveAiToolStorybook story exercising a synthetic host tool.❓ How to test this?
turbo run test:unit --filter '@hashintel/petrinaut'.Petrinaut / WithHostInteractiveAiTool.📹 Demo
The
WithHostInteractiveAiToolstory is the runnable demo surface: it shows the synthetic host tool inline in the chat panel, before and after submission.🤖 Generated with Claude Code