@assistant-ui/mcp-docs-server 0.2.3 → 0.3.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 +16 -43
- package/dist/index.d.ts +2 -3
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +3 -91
- package/dist/index.js.map +1 -1
- package/dist/proxy.d.ts +9 -0
- package/dist/proxy.d.ts.map +1 -0
- package/dist/proxy.js +91 -0
- package/dist/proxy.js.map +1 -0
- package/package.json +8 -12
- package/src/index.ts +3 -115
- package/src/proxy.test.ts +385 -0
- package/src/proxy.ts +126 -0
- package/.docs/organized/code-examples/waterfall.md +0 -807
- package/.docs/organized/code-examples/with-a2a.md +0 -675
- package/.docs/organized/code-examples/with-ag-ui.md +0 -553
- package/.docs/organized/code-examples/with-ai-sdk-v7.md +0 -466
- package/.docs/organized/code-examples/with-artifacts.md +0 -815
- package/.docs/organized/code-examples/with-assistant-transport.md +0 -576
- package/.docs/organized/code-examples/with-browser-extension.md +0 -371
- package/.docs/organized/code-examples/with-chain-of-thought.md +0 -962
- package/.docs/organized/code-examples/with-cloud-standalone.md +0 -681
- package/.docs/organized/code-examples/with-cloud.md +0 -439
- package/.docs/organized/code-examples/with-custom-thread-list.md +0 -569
- package/.docs/organized/code-examples/with-elevenlabs-conversational.md +0 -527
- package/.docs/organized/code-examples/with-elevenlabs-scribe.md +0 -697
- package/.docs/organized/code-examples/with-eve.md +0 -405
- package/.docs/organized/code-examples/with-expo.md +0 -2246
- package/.docs/organized/code-examples/with-external-store.md +0 -421
- package/.docs/organized/code-examples/with-ffmpeg.md +0 -829
- package/.docs/organized/code-examples/with-generative-ui.md +0 -1810
- package/.docs/organized/code-examples/with-google-adk.md +0 -368
- package/.docs/organized/code-examples/with-heat-graph.md +0 -304
- package/.docs/organized/code-examples/with-image-generation.md +0 -463
- package/.docs/organized/code-examples/with-interactables.md +0 -733
- package/.docs/organized/code-examples/with-langchain.md +0 -446
- package/.docs/organized/code-examples/with-langgraph.md +0 -855
- package/.docs/organized/code-examples/with-livekit.md +0 -643
- package/.docs/organized/code-examples/with-mcp.md +0 -782
- package/.docs/organized/code-examples/with-nuxt.md +0 -2428
- package/.docs/organized/code-examples/with-opencode.md +0 -1974
- package/.docs/organized/code-examples/with-openui.md +0 -450
- package/.docs/organized/code-examples/with-pi.md +0 -2084
- package/.docs/organized/code-examples/with-react-hook-form.md +0 -727
- package/.docs/organized/code-examples/with-react-ink-web.md +0 -740
- package/.docs/organized/code-examples/with-react-ink.md +0 -473
- package/.docs/organized/code-examples/with-react-router.md +0 -936
- package/.docs/organized/code-examples/with-resumable-stream.md +0 -668
- package/.docs/organized/code-examples/with-store.md +0 -679
- package/.docs/organized/code-examples/with-svelte.md +0 -415
- package/.docs/organized/code-examples/with-sveltekit.md +0 -1061
- package/.docs/organized/code-examples/with-tanstack.md +0 -812
- package/.docs/organized/code-examples/with-tap-runtime.md +0 -813
- package/.docs/organized/code-examples/with-virtualized-thread.md +0 -681
- package/.docs/organized/code-examples/with-vue.md +0 -408
- package/.docs/raw/docs/(getting-started)/architecture.mdx +0 -146
- package/.docs/raw/docs/(getting-started)/base-ui.mdx +0 -39
- package/.docs/raw/docs/(getting-started)/cli.mdx +0 -542
- package/.docs/raw/docs/(getting-started)/devtools.mdx +0 -79
- package/.docs/raw/docs/(getting-started)/index.mdx +0 -23
- package/.docs/raw/docs/(getting-started)/installation.mdx +0 -483
- package/.docs/raw/docs/(getting-started)/llm.mdx +0 -211
- package/.docs/raw/docs/(getting-started)/rtl.mdx +0 -78
- package/.docs/raw/docs/(reference)/api-reference/adapters/attachments.mdx +0 -36
- package/.docs/raw/docs/(reference)/api-reference/adapters/feedback.mdx +0 -20
- package/.docs/raw/docs/(reference)/api-reference/adapters/index.mdx +0 -34
- package/.docs/raw/docs/(reference)/api-reference/adapters/model.mdx +0 -44
- package/.docs/raw/docs/(reference)/api-reference/adapters/persistence.mdx +0 -59
- package/.docs/raw/docs/(reference)/api-reference/adapters/runtime.mdx +0 -20
- package/.docs/raw/docs/(reference)/api-reference/adapters/suggestions.mdx +0 -26
- package/.docs/raw/docs/(reference)/api-reference/context-providers/assistant-runtime-provider.mdx +0 -84
- package/.docs/raw/docs/(reference)/api-reference/context-providers/index.mdx +0 -22
- package/.docs/raw/docs/(reference)/api-reference/context-providers/scoped-providers.mdx +0 -64
- package/.docs/raw/docs/(reference)/api-reference/external-store/index.mdx +0 -22
- package/.docs/raw/docs/(reference)/api-reference/external-store/message-conversion.mdx +0 -57
- package/.docs/raw/docs/(reference)/api-reference/external-store/runtime.mdx +0 -49
- package/.docs/raw/docs/(reference)/api-reference/generative-ui/a2ui.mdx +0 -40
- package/.docs/raw/docs/(reference)/api-reference/generative-ui/actions.mdx +0 -56
- package/.docs/raw/docs/(reference)/api-reference/generative-ui/components.mdx +0 -86
- package/.docs/raw/docs/(reference)/api-reference/generative-ui/index.mdx +0 -39
- package/.docs/raw/docs/(reference)/api-reference/generative-ui/json-generative-ui.mdx +0 -42
- package/.docs/raw/docs/(reference)/api-reference/generative-ui/rendering.mdx +0 -84
- package/.docs/raw/docs/(reference)/api-reference/generative-ui/slack.mdx +0 -85
- package/.docs/raw/docs/(reference)/api-reference/generative-ui/spec.mdx +0 -45
- package/.docs/raw/docs/(reference)/api-reference/generative-ui/teams.mdx +0 -93
- package/.docs/raw/docs/(reference)/api-reference/generative-ui/tokens.mdx +0 -62
- package/.docs/raw/docs/(reference)/api-reference/hooks/composer-triggers.mdx +0 -129
- package/.docs/raw/docs/(reference)/api-reference/hooks/index.mdx +0 -31
- package/.docs/raw/docs/(reference)/api-reference/hooks/model-context.mdx +0 -34
- package/.docs/raw/docs/(reference)/api-reference/hooks/primitives.mdx +0 -350
- package/.docs/raw/docs/(reference)/api-reference/hooks/runtimes.mdx +0 -28
- package/.docs/raw/docs/(reference)/api-reference/hooks/state.mdx +0 -97
- package/.docs/raw/docs/(reference)/api-reference/integrations/ai-sdk.mdx +0 -200
- package/.docs/raw/docs/(reference)/api-reference/integrations/cloud-ai-sdk.mdx +0 -24
- package/.docs/raw/docs/(reference)/api-reference/integrations/eve.mdx +0 -118
- package/.docs/raw/docs/(reference)/api-reference/integrations/index.mdx +0 -28
- package/.docs/raw/docs/(reference)/api-reference/integrations/react-data-stream.mdx +0 -37
- package/.docs/raw/docs/(reference)/api-reference/model-context/context.mdx +0 -50
- package/.docs/raw/docs/(reference)/api-reference/model-context/index.mdx +0 -22
- package/.docs/raw/docs/(reference)/api-reference/model-context/registry.mdx +0 -20
- package/.docs/raw/docs/(reference)/api-reference/overview.mdx +0 -566
- package/.docs/raw/docs/(reference)/api-reference/primitives/action-bar-more.mdx +0 -152
- package/.docs/raw/docs/(reference)/api-reference/primitives/action-bar.mdx +0 -239
- package/.docs/raw/docs/(reference)/api-reference/primitives/assistant-if.mdx +0 -208
- package/.docs/raw/docs/(reference)/api-reference/primitives/assistant-modal.mdx +0 -108
- package/.docs/raw/docs/(reference)/api-reference/primitives/attachment.mdx +0 -99
- package/.docs/raw/docs/(reference)/api-reference/primitives/branch-picker.mdx +0 -145
- package/.docs/raw/docs/(reference)/api-reference/primitives/chain-of-thought.mdx +0 -82
- package/.docs/raw/docs/(reference)/api-reference/primitives/composer.mdx +0 -637
- package/.docs/raw/docs/(reference)/api-reference/primitives/composition.mdx +0 -22
- package/.docs/raw/docs/(reference)/api-reference/primitives/error.mdx +0 -65
- package/.docs/raw/docs/(reference)/api-reference/primitives/index.mdx +0 -73
- package/.docs/raw/docs/(reference)/api-reference/primitives/message-part.mdx +0 -117
- package/.docs/raw/docs/(reference)/api-reference/primitives/message.mdx +0 -339
- package/.docs/raw/docs/(reference)/api-reference/primitives/queue-item.mdx +0 -77
- package/.docs/raw/docs/(reference)/api-reference/primitives/selection-toolbar.mdx +0 -88
- package/.docs/raw/docs/(reference)/api-reference/primitives/suggestion.mdx +0 -81
- package/.docs/raw/docs/(reference)/api-reference/primitives/thread-list-item-more.mdx +0 -129
- package/.docs/raw/docs/(reference)/api-reference/primitives/thread-list-item.mdx +0 -138
- package/.docs/raw/docs/(reference)/api-reference/primitives/thread-list.mdx +0 -122
- package/.docs/raw/docs/(reference)/api-reference/primitives/thread.mdx +0 -289
- package/.docs/raw/docs/(reference)/api-reference/runtimes/assistant-runtime.mdx +0 -20
- package/.docs/raw/docs/(reference)/api-reference/runtimes/attachment-runtime.mdx +0 -24
- package/.docs/raw/docs/(reference)/api-reference/runtimes/composer-runtime.mdx +0 -32
- package/.docs/raw/docs/(reference)/api-reference/runtimes/index.mdx +0 -43
- package/.docs/raw/docs/(reference)/api-reference/runtimes/message-part-runtime.mdx +0 -43
- package/.docs/raw/docs/(reference)/api-reference/runtimes/message-runtime.mdx +0 -24
- package/.docs/raw/docs/(reference)/api-reference/runtimes/queue-state.mdx +0 -20
- package/.docs/raw/docs/(reference)/api-reference/runtimes/thread-list-item-runtime.mdx +0 -24
- package/.docs/raw/docs/(reference)/api-reference/runtimes/thread-list-runtime.mdx +0 -24
- package/.docs/raw/docs/(reference)/api-reference/runtimes/thread-runtime.mdx +0 -36
- package/.docs/raw/docs/(reference)/api-reference/tools/component-tools.mdx +0 -85
- package/.docs/raw/docs/(reference)/api-reference/tools/index.mdx +0 -45
- package/.docs/raw/docs/(reference)/api-reference/tools/interactables-legacy.mdx +0 -55
- package/.docs/raw/docs/(reference)/api-reference/tools/interactables.mdx +0 -151
- package/.docs/raw/docs/(reference)/api-reference/tools/rendering.mdx +0 -106
- package/.docs/raw/docs/(reference)/api-reference/tools/status.mdx +0 -62
- package/.docs/raw/docs/(reference)/api-reference/tools/toolkits.mdx +0 -178
- package/.docs/raw/docs/(reference)/api-reference/transport/assistant-transport.mdx +0 -48
- package/.docs/raw/docs/(reference)/api-reference/transport/frame.mdx +0 -62
- package/.docs/raw/docs/(reference)/api-reference/transport/index.mdx +0 -22
- package/.docs/raw/docs/(reference)/api-reference/utilities/index.mdx +0 -19
- package/.docs/raw/docs/(reference)/api-reference/utilities/miscellaneous.mdx +0 -152
- package/.docs/raw/docs/(reference)/api-reference/voice/index.mdx +0 -22
- package/.docs/raw/docs/(reference)/api-reference/voice/session.mdx +0 -54
- package/.docs/raw/docs/(reference)/api-reference/voice/speech-dictation.mdx +0 -36
- package/.docs/raw/docs/cloud/ai-sdk-assistant-ui.mdx +0 -527
- package/.docs/raw/docs/cloud/ai-sdk.mdx +0 -604
- package/.docs/raw/docs/cloud/authorization.mdx +0 -252
- package/.docs/raw/docs/cloud/index.mdx +0 -46
- package/.docs/raw/docs/cloud/langgraph.mdx +0 -554
- package/.docs/raw/docs/copilots/assistant-frame.mdx +0 -411
- package/.docs/raw/docs/copilots/make-assistant-visible.mdx +0 -82
- package/.docs/raw/docs/copilots/model-context.mdx +0 -156
- package/.docs/raw/docs/copilots/motivation.mdx +0 -202
- package/.docs/raw/docs/copilots/use-assistant-instructions.mdx +0 -64
- package/.docs/raw/docs/guides/attachments.mdx +0 -619
- package/.docs/raw/docs/guides/branching.mdx +0 -76
- package/.docs/raw/docs/guides/chain-of-thought.mdx +0 -164
- package/.docs/raw/docs/guides/chatgpt-subscription.mdx +0 -108
- package/.docs/raw/docs/guides/context-api.mdx +0 -619
- package/.docs/raw/docs/guides/dictation.mdx +0 -298
- package/.docs/raw/docs/guides/editing.mdx +0 -102
- package/.docs/raw/docs/guides/electron.mdx +0 -369
- package/.docs/raw/docs/guides/headless-composer-input.mdx +0 -113
- package/.docs/raw/docs/guides/image-generation.mdx +0 -74
- package/.docs/raw/docs/guides/index.mdx +0 -117
- package/.docs/raw/docs/guides/input-history.mdx +0 -55
- package/.docs/raw/docs/guides/latex.mdx +0 -158
- package/.docs/raw/docs/guides/mentions.mdx +0 -575
- package/.docs/raw/docs/guides/message-timing.mdx +0 -215
- package/.docs/raw/docs/guides/quoting.mdx +0 -169
- package/.docs/raw/docs/guides/resumable-stream-deployment.mdx +0 -212
- package/.docs/raw/docs/guides/resumable-stream-stores.mdx +0 -152
- package/.docs/raw/docs/guides/resumable-streams.mdx +0 -292
- package/.docs/raw/docs/guides/slash-commands.mdx +0 -358
- package/.docs/raw/docs/guides/speech.mdx +0 -172
- package/.docs/raw/docs/guides/suggestions.mdx +0 -384
- package/.docs/raw/docs/guides/virtualization.mdx +0 -133
- package/.docs/raw/docs/guides/voice.mdx +0 -310
- package/.docs/raw/docs/ink/adapters.mdx +0 -99
- package/.docs/raw/docs/ink/custom-backend.mdx +0 -254
- package/.docs/raw/docs/ink/hooks.mdx +0 -469
- package/.docs/raw/docs/ink/index.mdx +0 -237
- package/.docs/raw/docs/ink/migration.mdx +0 -138
- package/.docs/raw/docs/ink/primitives.mdx +0 -1259
- package/.docs/raw/docs/integrations/attachments/custom-adapter.mdx +0 -528
- package/.docs/raw/docs/integrations/auth/better-auth.mdx +0 -188
- package/.docs/raw/docs/integrations/auth/clerk.mdx +0 -166
- package/.docs/raw/docs/integrations/auth/next-auth.mdx +0 -191
- package/.docs/raw/docs/integrations/frameworks/ai-sdk.mdx +0 -88
- package/.docs/raw/docs/integrations/frameworks/cloudflare-agents.mdx +0 -283
- package/.docs/raw/docs/integrations/frameworks/mastra/full-stack.mdx +0 -188
- package/.docs/raw/docs/integrations/frameworks/mastra/overview.mdx +0 -57
- package/.docs/raw/docs/integrations/frameworks/mastra/separate-server.mdx +0 -201
- package/.docs/raw/docs/integrations/gateways/index.mdx +0 -162
- package/.docs/raw/docs/integrations/index.mdx +0 -178
- package/.docs/raw/docs/integrations/observability/helicone.mdx +0 -129
- package/.docs/raw/docs/integrations/observability/langfuse.mdx +0 -161
- package/.docs/raw/docs/integrations/observability/langsmith.mdx +0 -151
- package/.docs/raw/docs/integrations/persistence/custom-adapter.mdx +0 -773
- package/.docs/raw/docs/migrations/deprecation-policy.mdx +0 -42
- package/.docs/raw/docs/migrations/index.mdx +0 -50
- package/.docs/raw/docs/migrations/react-compatibility.mdx +0 -74
- package/.docs/raw/docs/migrations/react-langgraph-v0-7.mdx +0 -328
- package/.docs/raw/docs/migrations/toolkit-tools.mdx +0 -236
- package/.docs/raw/docs/migrations/v0-11.mdx +0 -172
- package/.docs/raw/docs/migrations/v0-12.mdx +0 -302
- package/.docs/raw/docs/migrations/v0-14.mdx +0 -297
- package/.docs/raw/docs/migrations/v0-15.mdx +0 -270
- package/.docs/raw/docs/primitives/action-bar.mdx +0 -352
- package/.docs/raw/docs/primitives/assistant-modal.mdx +0 -216
- package/.docs/raw/docs/primitives/attachment.mdx +0 -217
- package/.docs/raw/docs/primitives/branch-picker.mdx +0 -222
- package/.docs/raw/docs/primitives/chain-of-thought.mdx +0 -315
- package/.docs/raw/docs/primitives/composer.mdx +0 -659
- package/.docs/raw/docs/primitives/error.mdx +0 -142
- package/.docs/raw/docs/primitives/index.mdx +0 -99
- package/.docs/raw/docs/primitives/message.mdx +0 -618
- package/.docs/raw/docs/primitives/selection-toolbar.mdx +0 -191
- package/.docs/raw/docs/primitives/suggestion.mdx +0 -254
- package/.docs/raw/docs/primitives/thread-list.mdx +0 -467
- package/.docs/raw/docs/primitives/thread.mdx +0 -509
- package/.docs/raw/docs/react-native/adapters.mdx +0 -94
- package/.docs/raw/docs/react-native/custom-backend.mdx +0 -207
- package/.docs/raw/docs/react-native/hooks.mdx +0 -339
- package/.docs/raw/docs/react-native/index.mdx +0 -289
- package/.docs/raw/docs/react-native/migration.mdx +0 -142
- package/.docs/raw/docs/react-native/primitives.mdx +0 -976
- package/.docs/raw/docs/runtimes/a2a/client-and-hooks.mdx +0 -399
- package/.docs/raw/docs/runtimes/a2a/overview.mdx +0 -60
- package/.docs/raw/docs/runtimes/a2a/quickstart.mdx +0 -216
- package/.docs/raw/docs/runtimes/ag-ui/agent-state.mdx +0 -124
- package/.docs/raw/docs/runtimes/ag-ui/overview.mdx +0 -70
- package/.docs/raw/docs/runtimes/ag-ui/quickstart.mdx +0 -243
- package/.docs/raw/docs/runtimes/ag-ui/runtime-options.mdx +0 -267
- package/.docs/raw/docs/runtimes/ai-sdk/overview.mdx +0 -61
- package/.docs/raw/docs/runtimes/ai-sdk/v4-legacy.mdx +0 -122
- package/.docs/raw/docs/runtimes/ai-sdk/v5-legacy.mdx +0 -135
- package/.docs/raw/docs/runtimes/ai-sdk/v6-legacy.mdx +0 -636
- package/.docs/raw/docs/runtimes/ai-sdk/v7.mdx +0 -731
- package/.docs/raw/docs/runtimes/claude-managed-agents.mdx +0 -118
- package/.docs/raw/docs/runtimes/concepts/adapters.mdx +0 -265
- package/.docs/raw/docs/runtimes/concepts/architecture.mdx +0 -167
- package/.docs/raw/docs/runtimes/concepts/stability.mdx +0 -68
- package/.docs/raw/docs/runtimes/concepts/threads.mdx +0 -513
- package/.docs/raw/docs/runtimes/custom/assistant-transport.mdx +0 -763
- package/.docs/raw/docs/runtimes/custom/data-stream.mdx +0 -339
- package/.docs/raw/docs/runtimes/custom/external-store.mdx +0 -957
- package/.docs/raw/docs/runtimes/custom/local-runtime.mdx +0 -902
- package/.docs/raw/docs/runtimes/custom/overview.mdx +0 -71
- package/.docs/raw/docs/runtimes/eve/overview.mdx +0 -151
- package/.docs/raw/docs/runtimes/eve/quickstart.mdx +0 -192
- package/.docs/raw/docs/runtimes/google-adk/api.mdx +0 -256
- package/.docs/raw/docs/runtimes/google-adk/hooks.mdx +0 -784
- package/.docs/raw/docs/runtimes/google-adk/overview.mdx +0 -69
- package/.docs/raw/docs/runtimes/google-adk/quickstart.mdx +0 -229
- package/.docs/raw/docs/runtimes/langchain.mdx +0 -901
- package/.docs/raw/docs/runtimes/langgraph/agent-state.mdx +0 -181
- package/.docs/raw/docs/runtimes/langgraph/generative-ui.mdx +0 -305
- package/.docs/raw/docs/runtimes/langgraph/interrupts.mdx +0 -104
- package/.docs/raw/docs/runtimes/langgraph/overview.mdx +0 -84
- package/.docs/raw/docs/runtimes/langgraph/quickstart.mdx +0 -496
- package/.docs/raw/docs/runtimes/langgraph/streaming.mdx +0 -144
- package/.docs/raw/docs/runtimes/langgraph/threads.mdx +0 -113
- package/.docs/raw/docs/runtimes/langgraph/tutorial/introduction.mdx +0 -29
- package/.docs/raw/docs/runtimes/langgraph/tutorial/part-1.mdx +0 -93
- package/.docs/raw/docs/runtimes/langgraph/tutorial/part-2.mdx +0 -347
- package/.docs/raw/docs/runtimes/langgraph/tutorial/part-3.mdx +0 -393
- package/.docs/raw/docs/runtimes/opencode/hooks.mdx +0 -194
- package/.docs/raw/docs/runtimes/opencode/overview.mdx +0 -58
- package/.docs/raw/docs/runtimes/opencode/quickstart.mdx +0 -119
- package/.docs/raw/docs/runtimes/pick-a-runtime.mdx +0 -133
- package/.docs/raw/docs/tools/a2ui.mdx +0 -107
- package/.docs/raw/docs/tools/backend.mdx +0 -147
- package/.docs/raw/docs/tools/defining-tools.mdx +0 -574
- package/.docs/raw/docs/tools/dynamic-tools.mdx +0 -112
- package/.docs/raw/docs/tools/generative-ui-primitive.mdx +0 -180
- package/.docs/raw/docs/tools/generative-ui-slack.mdx +0 -167
- package/.docs/raw/docs/tools/generative-ui-teams.mdx +0 -160
- package/.docs/raw/docs/tools/generative-ui.mdx +0 -322
- package/.docs/raw/docs/tools/index.mdx +0 -72
- package/.docs/raw/docs/tools/interactables.mdx +0 -1084
- package/.docs/raw/docs/tools/mcp-apps.mdx +0 -363
- package/.docs/raw/docs/tools/mcp.mdx +0 -466
- package/.docs/raw/docs/tools/multi-agent.mdx +0 -237
- package/.docs/raw/docs/tools/openui.mdx +0 -175
- package/.docs/raw/docs/tools/tool-ui.mdx +0 -1020
- package/.docs/raw/docs/tools/user-managed-mcp.mdx +0 -428
- package/.docs/raw/docs/ui/assistant-modal.mdx +0 -166
- package/.docs/raw/docs/ui/assistant-sidebar.mdx +0 -89
- package/.docs/raw/docs/ui/attachment.mdx +0 -279
- package/.docs/raw/docs/ui/composer-trigger-popover.mdx +0 -247
- package/.docs/raw/docs/ui/context-display.mdx +0 -148
- package/.docs/raw/docs/ui/directive-text.mdx +0 -114
- package/.docs/raw/docs/ui/file.mdx +0 -159
- package/.docs/raw/docs/ui/follow-up-suggestions.mdx +0 -82
- package/.docs/raw/docs/ui/image.mdx +0 -102
- package/.docs/raw/docs/ui/markdown.mdx +0 -103
- package/.docs/raw/docs/ui/mcp-config.mdx +0 -107
- package/.docs/raw/docs/ui/mermaid.mdx +0 -92
- package/.docs/raw/docs/ui/message-timing.mdx +0 -93
- package/.docs/raw/docs/ui/model-selector.mdx +0 -455
- package/.docs/raw/docs/ui/part-grouping.mdx +0 -614
- package/.docs/raw/docs/ui/quote.mdx +0 -211
- package/.docs/raw/docs/ui/reasoning.mdx +0 -214
- package/.docs/raw/docs/ui/scrollbar.mdx +0 -77
- package/.docs/raw/docs/ui/sources.mdx +0 -186
- package/.docs/raw/docs/ui/streamdown.mdx +0 -392
- package/.docs/raw/docs/ui/syntax-highlighting.mdx +0 -205
- package/.docs/raw/docs/ui/thread-list.mdx +0 -411
- package/.docs/raw/docs/ui/thread.mdx +0 -559
- package/.docs/raw/docs/ui/tool-fallback.mdx +0 -141
- package/.docs/raw/docs/ui/tool-group.mdx +0 -240
- package/.docs/raw/docs/ui/voice.mdx +0 -173
- package/.docs/raw/docs/utilities/heat-graph.mdx +0 -237
- package/.docs/raw/docs/utilities/react-o11y.mdx +0 -362
- package/.docs/raw/docs/utilities/tw-shimmer.mdx +0 -212
- package/dist/constants.d.ts +0 -14
- package/dist/constants.d.ts.map +0 -1
- package/dist/constants.js +0 -19
- package/dist/constants.js.map +0 -1
- package/dist/prepare-docs/code-examples.d.ts +0 -5
- package/dist/prepare-docs/code-examples.d.ts.map +0 -1
- package/dist/prepare-docs/code-examples.js +0 -117
- package/dist/prepare-docs/code-examples.js.map +0 -1
- package/dist/prepare-docs/copy-raw.d.ts +0 -5
- package/dist/prepare-docs/copy-raw.d.ts.map +0 -1
- package/dist/prepare-docs/copy-raw.js +0 -49
- package/dist/prepare-docs/copy-raw.js.map +0 -1
- package/dist/prepare-docs/prepare.d.ts +0 -1
- package/dist/prepare-docs/prepare.js +0 -22
- package/dist/prepare-docs/prepare.js.map +0 -1
- package/dist/prompts/xulux-playground.d.ts +0 -12
- package/dist/prompts/xulux-playground.d.ts.map +0 -1
- package/dist/prompts/xulux-playground.js +0 -33
- package/dist/prompts/xulux-playground.js.map +0 -1
- package/dist/tools/docs.d.ts +0 -22
- package/dist/tools/docs.d.ts.map +0 -1
- package/dist/tools/docs.js +0 -177
- package/dist/tools/docs.js.map +0 -1
- package/dist/tools/examples.d.ts +0 -24
- package/dist/tools/examples.d.ts.map +0 -1
- package/dist/tools/examples.js +0 -87
- package/dist/tools/examples.js.map +0 -1
- package/dist/tools/resources.d.ts +0 -6
- package/dist/tools/resources.d.ts.map +0 -1
- package/dist/tools/resources.js +0 -75
- package/dist/tools/resources.js.map +0 -1
- package/dist/tools/search.d.ts +0 -24
- package/dist/tools/search.d.ts.map +0 -1
- package/dist/tools/search.js +0 -39
- package/dist/tools/search.js.map +0 -1
- package/dist/tools/tests/mcp-test-client.d.ts +0 -15
- package/dist/tools/tests/mcp-test-client.d.ts.map +0 -1
- package/dist/tools/tests/mcp-test-client.js +0 -68
- package/dist/tools/tests/mcp-test-client.js.map +0 -1
- package/dist/tools/tests/test-setup.d.ts +0 -7
- package/dist/tools/tests/test-setup.d.ts.map +0 -1
- package/dist/tools/tests/test-setup.js +0 -36
- package/dist/tools/tests/test-setup.js.map +0 -1
- package/dist/tools/xulux-templates.d.ts +0 -58
- package/dist/tools/xulux-templates.d.ts.map +0 -1
- package/dist/tools/xulux-templates.js +0 -84
- package/dist/tools/xulux-templates.js.map +0 -1
- package/dist/utils/cache.d.ts +0 -5
- package/dist/utils/cache.d.ts.map +0 -1
- package/dist/utils/cache.js +0 -18
- package/dist/utils/cache.js.map +0 -1
- package/dist/utils/logger.d.ts +0 -10
- package/dist/utils/logger.d.ts.map +0 -1
- package/dist/utils/logger.js +0 -20
- package/dist/utils/logger.js.map +0 -1
- package/dist/utils/mcp-format.d.ts +0 -11
- package/dist/utils/mcp-format.d.ts.map +0 -1
- package/dist/utils/mcp-format.js +0 -14
- package/dist/utils/mcp-format.js.map +0 -1
- package/dist/utils/mdx.d.ts +0 -12
- package/dist/utils/mdx.d.ts.map +0 -1
- package/dist/utils/mdx.js +0 -45
- package/dist/utils/mdx.js.map +0 -1
- package/dist/utils/paths.d.ts +0 -12
- package/dist/utils/paths.d.ts.map +0 -1
- package/dist/utils/paths.js +0 -93
- package/dist/utils/paths.js.map +0 -1
- package/dist/utils/search.d.ts +0 -10
- package/dist/utils/search.d.ts.map +0 -1
- package/dist/utils/search.js +0 -97
- package/dist/utils/search.js.map +0 -1
- package/dist/utils/security.d.ts +0 -5
- package/dist/utils/security.d.ts.map +0 -1
- package/dist/utils/security.js +0 -22
- package/dist/utils/security.js.map +0 -1
- package/dist/xulux/catalog-client.d.ts +0 -14
- package/dist/xulux/catalog-client.d.ts.map +0 -1
- package/dist/xulux/catalog-client.js +0 -112
- package/dist/xulux/catalog-client.js.map +0 -1
- package/dist/xulux/fallback-catalog.d.ts +0 -7
- package/dist/xulux/fallback-catalog.d.ts.map +0 -1
- package/dist/xulux/fallback-catalog.js +0 -47
- package/dist/xulux/fallback-catalog.js.map +0 -1
- package/dist/xulux/fetch-sandbox.d.ts +0 -5
- package/dist/xulux/fetch-sandbox.d.ts.map +0 -1
- package/dist/xulux/fetch-sandbox.js +0 -40
- package/dist/xulux/fetch-sandbox.js.map +0 -1
- package/dist/xulux/template-service.d.ts +0 -84
- package/dist/xulux/template-service.d.ts.map +0 -1
- package/dist/xulux/template-service.js +0 -223
- package/dist/xulux/template-service.js.map +0 -1
- package/dist/xulux/types.d.ts +0 -55
- package/dist/xulux/types.d.ts.map +0 -1
- package/dist/xulux/types.js +0 -6
- package/dist/xulux/types.js.map +0 -1
- package/src/constants.ts +0 -24
- package/src/prepare-docs/code-examples.ts +0 -158
- package/src/prepare-docs/copy-raw.ts +0 -50
- package/src/prepare-docs/prepare.ts +0 -24
- package/src/prompts/xulux-playground.ts +0 -36
- package/src/tools/docs.ts +0 -255
- package/src/tools/examples.ts +0 -114
- package/src/tools/resources.ts +0 -111
- package/src/tools/search.ts +0 -46
- package/src/tools/tests/completions.test.ts +0 -60
- package/src/tools/tests/directory-size-cap.test.ts +0 -50
- package/src/tools/tests/docs.test.ts +0 -147
- package/src/tools/tests/examples.test.ts +0 -94
- package/src/tools/tests/integration.test.ts +0 -45
- package/src/tools/tests/json-parsing.test.ts +0 -23
- package/src/tools/tests/listings-cache.test.ts +0 -19
- package/src/tools/tests/mcp-protocol.test.ts +0 -216
- package/src/tools/tests/mcp-test-client.ts +0 -111
- package/src/tools/tests/path-traversal.test.ts +0 -84
- package/src/tools/tests/resources.test.ts +0 -133
- package/src/tools/tests/search.test.ts +0 -37
- package/src/tools/tests/test-setup.ts +0 -50
- package/src/tools/tests/xulux-templates.test.ts +0 -325
- package/src/tools/xulux-templates.ts +0 -141
- package/src/utils/cache.ts +0 -20
- package/src/utils/logger.ts +0 -20
- package/src/utils/mcp-format.ts +0 -14
- package/src/utils/mdx.ts +0 -59
- package/src/utils/paths.ts +0 -139
- package/src/utils/search.ts +0 -131
- package/src/utils/security.ts +0 -52
- package/src/utils/tests/cache.test.ts +0 -51
- package/src/utils/tests/mcp-format.test.ts +0 -22
- package/src/utils/tests/security.test.ts +0 -119
- package/src/xulux/catalog-client.ts +0 -150
- package/src/xulux/fallback-catalog.ts +0 -63
- package/src/xulux/fetch-sandbox.ts +0 -56
- package/src/xulux/template-service.ts +0 -406
- package/src/xulux/types.ts +0 -64
|
@@ -1,1974 +0,0 @@
|
|
|
1
|
-
# Example: with-opencode
|
|
2
|
-
|
|
3
|
-
## app/globals.css
|
|
4
|
-
|
|
5
|
-
```css
|
|
6
|
-
@import "tailwindcss";
|
|
7
|
-
@import "tw-animate-css";
|
|
8
|
-
|
|
9
|
-
@source "../../../packages/ui/src";
|
|
10
|
-
|
|
11
|
-
@custom-variant dark (&:is(.dark *));
|
|
12
|
-
|
|
13
|
-
@theme inline {
|
|
14
|
-
--color-background: var(--background);
|
|
15
|
-
--color-foreground: var(--foreground);
|
|
16
|
-
--font-sans: var(--font-geist-sans);
|
|
17
|
-
--font-mono: var(--font-geist-mono);
|
|
18
|
-
--color-sidebar-ring: var(--sidebar-ring);
|
|
19
|
-
--color-sidebar-border: var(--sidebar-border);
|
|
20
|
-
--color-sidebar-accent-foreground: var(--sidebar-accent-foreground);
|
|
21
|
-
--color-sidebar-accent: var(--sidebar-accent);
|
|
22
|
-
--color-sidebar-primary-foreground: var(--sidebar-primary-foreground);
|
|
23
|
-
--color-sidebar-primary: var(--sidebar-primary);
|
|
24
|
-
--color-sidebar-foreground: var(--sidebar-foreground);
|
|
25
|
-
--color-sidebar: var(--sidebar);
|
|
26
|
-
--color-chart-5: var(--chart-5);
|
|
27
|
-
--color-chart-4: var(--chart-4);
|
|
28
|
-
--color-chart-3: var(--chart-3);
|
|
29
|
-
--color-chart-2: var(--chart-2);
|
|
30
|
-
--color-chart-1: var(--chart-1);
|
|
31
|
-
--color-ring: var(--ring);
|
|
32
|
-
--color-input: var(--input);
|
|
33
|
-
--color-border: var(--border);
|
|
34
|
-
--color-destructive: var(--destructive);
|
|
35
|
-
--color-accent-foreground: var(--accent-foreground);
|
|
36
|
-
--color-accent: var(--accent);
|
|
37
|
-
--color-muted-foreground: var(--muted-foreground);
|
|
38
|
-
--color-muted: var(--muted);
|
|
39
|
-
--color-secondary-foreground: var(--secondary-foreground);
|
|
40
|
-
--color-secondary: var(--secondary);
|
|
41
|
-
--color-primary-foreground: var(--primary-foreground);
|
|
42
|
-
--color-primary: var(--primary);
|
|
43
|
-
--color-popover-foreground: var(--popover-foreground);
|
|
44
|
-
--color-popover: var(--popover);
|
|
45
|
-
--color-card-foreground: var(--card-foreground);
|
|
46
|
-
--color-card: var(--card);
|
|
47
|
-
--radius-sm: calc(var(--radius) - 4px);
|
|
48
|
-
--radius-md: calc(var(--radius) - 2px);
|
|
49
|
-
--radius-lg: var(--radius);
|
|
50
|
-
--radius-xl: calc(var(--radius) + 4px);
|
|
51
|
-
}
|
|
52
|
-
|
|
53
|
-
:root {
|
|
54
|
-
--radius: 0.625rem;
|
|
55
|
-
--background: oklch(1 0 0);
|
|
56
|
-
--foreground: oklch(0.141 0.005 285.823);
|
|
57
|
-
--card: oklch(1 0 0);
|
|
58
|
-
--card-foreground: oklch(0.141 0.005 285.823);
|
|
59
|
-
--popover: oklch(1 0 0);
|
|
60
|
-
--popover-foreground: oklch(0.141 0.005 285.823);
|
|
61
|
-
--primary: oklch(0.21 0.006 285.885);
|
|
62
|
-
--primary-foreground: oklch(0.985 0 0);
|
|
63
|
-
--secondary: oklch(0.967 0.001 286.375);
|
|
64
|
-
--secondary-foreground: oklch(0.21 0.006 285.885);
|
|
65
|
-
--muted: oklch(0.967 0.001 286.375);
|
|
66
|
-
--muted-foreground: oklch(0.552 0.016 285.938);
|
|
67
|
-
--accent: oklch(0.967 0.001 286.375);
|
|
68
|
-
--accent-foreground: oklch(0.21 0.006 285.885);
|
|
69
|
-
--destructive: oklch(0.577 0.245 27.325);
|
|
70
|
-
--border: oklch(0.92 0.004 286.32);
|
|
71
|
-
--input: oklch(0.92 0.004 286.32);
|
|
72
|
-
--ring: oklch(0.705 0.015 286.067);
|
|
73
|
-
--chart-1: oklch(0.646 0.222 41.116);
|
|
74
|
-
--chart-2: oklch(0.6 0.118 184.704);
|
|
75
|
-
--chart-3: oklch(0.398 0.07 227.392);
|
|
76
|
-
--chart-4: oklch(0.828 0.189 84.429);
|
|
77
|
-
--chart-5: oklch(0.769 0.188 70.08);
|
|
78
|
-
--sidebar: oklch(0.985 0 0);
|
|
79
|
-
--sidebar-foreground: oklch(0.141 0.005 285.823);
|
|
80
|
-
--sidebar-primary: oklch(0.21 0.006 285.885);
|
|
81
|
-
--sidebar-primary-foreground: oklch(0.985 0 0);
|
|
82
|
-
--sidebar-accent: oklch(0.967 0.001 286.375);
|
|
83
|
-
--sidebar-accent-foreground: oklch(0.21 0.006 285.885);
|
|
84
|
-
--sidebar-border: oklch(0.92 0.004 286.32);
|
|
85
|
-
--sidebar-ring: oklch(0.705 0.015 286.067);
|
|
86
|
-
}
|
|
87
|
-
|
|
88
|
-
:root.dark,
|
|
89
|
-
html.dark,
|
|
90
|
-
body.dark,
|
|
91
|
-
.dark {
|
|
92
|
-
--background: oklch(0.141 0.005 285.823);
|
|
93
|
-
--foreground: oklch(0.985 0 0);
|
|
94
|
-
--card: oklch(0.21 0.006 285.885);
|
|
95
|
-
--card-foreground: oklch(0.985 0 0);
|
|
96
|
-
--popover: oklch(0.21 0.006 285.885);
|
|
97
|
-
--popover-foreground: oklch(0.985 0 0);
|
|
98
|
-
--primary: oklch(0.92 0.004 286.32);
|
|
99
|
-
--primary-foreground: oklch(0.21 0.006 285.885);
|
|
100
|
-
--secondary: oklch(0.274 0.006 286.033);
|
|
101
|
-
--secondary-foreground: oklch(0.985 0 0);
|
|
102
|
-
--muted: oklch(0.274 0.006 286.033);
|
|
103
|
-
--muted-foreground: oklch(0.705 0.015 286.067);
|
|
104
|
-
--accent: oklch(0.274 0.006 286.033);
|
|
105
|
-
--accent-foreground: oklch(0.985 0 0);
|
|
106
|
-
--destructive: oklch(0.704 0.191 22.216);
|
|
107
|
-
--border: oklch(1 0 0 / 10%);
|
|
108
|
-
--input: oklch(1 0 0 / 15%);
|
|
109
|
-
--ring: oklch(0.552 0.016 285.938);
|
|
110
|
-
--chart-1: oklch(0.488 0.243 264.376);
|
|
111
|
-
--chart-2: oklch(0.696 0.17 162.48);
|
|
112
|
-
--chart-3: oklch(0.769 0.188 70.08);
|
|
113
|
-
--chart-4: oklch(0.627 0.265 303.9);
|
|
114
|
-
--chart-5: oklch(0.645 0.246 16.439);
|
|
115
|
-
--sidebar: oklch(0.21 0.006 285.885);
|
|
116
|
-
--sidebar-foreground: oklch(0.985 0 0);
|
|
117
|
-
--sidebar-primary: oklch(0.488 0.243 264.376);
|
|
118
|
-
--sidebar-primary-foreground: oklch(0.985 0 0);
|
|
119
|
-
--sidebar-accent: oklch(0.274 0.006 286.033);
|
|
120
|
-
--sidebar-accent-foreground: oklch(0.985 0 0);
|
|
121
|
-
--sidebar-border: oklch(1 0 0 / 10%);
|
|
122
|
-
--sidebar-ring: oklch(0.552 0.016 285.938);
|
|
123
|
-
}
|
|
124
|
-
|
|
125
|
-
@layer base {
|
|
126
|
-
* {
|
|
127
|
-
@apply border-border outline-ring/50;
|
|
128
|
-
}
|
|
129
|
-
|
|
130
|
-
:root {
|
|
131
|
-
color-scheme: light;
|
|
132
|
-
}
|
|
133
|
-
|
|
134
|
-
:root.dark {
|
|
135
|
-
color-scheme: dark;
|
|
136
|
-
}
|
|
137
|
-
|
|
138
|
-
body {
|
|
139
|
-
@apply bg-background text-foreground;
|
|
140
|
-
}
|
|
141
|
-
}
|
|
142
|
-
|
|
143
|
-
```
|
|
144
|
-
|
|
145
|
-
## app/layout.tsx
|
|
146
|
-
|
|
147
|
-
```tsx
|
|
148
|
-
import type { Metadata } from "next";
|
|
149
|
-
import { Geist, Geist_Mono } from "next/font/google";
|
|
150
|
-
import { TooltipProvider } from "@/components/ui/tooltip";
|
|
151
|
-
import "./globals.css";
|
|
152
|
-
|
|
153
|
-
const geistSans = Geist({
|
|
154
|
-
variable: "--font-geist-sans",
|
|
155
|
-
subsets: ["latin"],
|
|
156
|
-
});
|
|
157
|
-
|
|
158
|
-
const geistMono = Geist_Mono({
|
|
159
|
-
variable: "--font-geist-mono",
|
|
160
|
-
subsets: ["latin"],
|
|
161
|
-
});
|
|
162
|
-
|
|
163
|
-
export const metadata: Metadata = {
|
|
164
|
-
title: "assistant-ui OpenCode Example",
|
|
165
|
-
description: "An OpenCode runtime example built with assistant-ui",
|
|
166
|
-
};
|
|
167
|
-
|
|
168
|
-
export default function RootLayout({
|
|
169
|
-
children,
|
|
170
|
-
}: Readonly<{
|
|
171
|
-
children: React.ReactNode;
|
|
172
|
-
}>) {
|
|
173
|
-
return (
|
|
174
|
-
<html lang="en" className="dark h-full">
|
|
175
|
-
<body
|
|
176
|
-
className={`${geistSans.variable} ${geistMono.variable} dark bg-background text-foreground h-full overflow-hidden antialiased`}
|
|
177
|
-
>
|
|
178
|
-
<TooltipProvider>{children}</TooltipProvider>
|
|
179
|
-
</body>
|
|
180
|
-
</html>
|
|
181
|
-
);
|
|
182
|
-
}
|
|
183
|
-
|
|
184
|
-
```
|
|
185
|
-
|
|
186
|
-
## app/page.tsx
|
|
187
|
-
|
|
188
|
-
```tsx
|
|
189
|
-
"use client";
|
|
190
|
-
|
|
191
|
-
import {
|
|
192
|
-
AssistantRuntimeProvider,
|
|
193
|
-
AuiConfig,
|
|
194
|
-
Tools,
|
|
195
|
-
useAui,
|
|
196
|
-
} from "@assistant-ui/react";
|
|
197
|
-
import {
|
|
198
|
-
SidebarInset,
|
|
199
|
-
SidebarProvider,
|
|
200
|
-
SidebarTrigger,
|
|
201
|
-
} from "@/components/ui/radix/sidebar";
|
|
202
|
-
import { Separator } from "@/components/ui/radix/separator";
|
|
203
|
-
import { ThreadListSidebar } from "@/components/assistant-ui/threadlist-sidebar.radix";
|
|
204
|
-
import {
|
|
205
|
-
useOpenCodeRuntime,
|
|
206
|
-
useOpenCodeSession,
|
|
207
|
-
} from "@assistant-ui/react-opencode";
|
|
208
|
-
import {
|
|
209
|
-
Thread,
|
|
210
|
-
type ThreadComponents,
|
|
211
|
-
} from "@/components/assistant-ui/thread";
|
|
212
|
-
import { OpenCodeDataPart } from "@/components/opencode-data-part";
|
|
213
|
-
import { FallbackTool } from "@/components/tools/opencode-tools";
|
|
214
|
-
import { ReasoningGroup } from "@/components/tools/reasoning-ghost";
|
|
215
|
-
import { ToolGroup } from "@/components/tools/tool-group";
|
|
216
|
-
import toolkit from "@/components/tools/toolkit";
|
|
217
|
-
import { useEffect } from "react";
|
|
218
|
-
|
|
219
|
-
const SetFallbackDataUI = () => {
|
|
220
|
-
const aui = useAui();
|
|
221
|
-
useEffect(() => aui.dataRenderers.setFallbackDataUI(OpenCodeDataPart), [aui]);
|
|
222
|
-
return null;
|
|
223
|
-
};
|
|
224
|
-
|
|
225
|
-
const THREAD_COMPONENTS: ThreadComponents = {
|
|
226
|
-
ToolFallback: FallbackTool,
|
|
227
|
-
ToolGroup,
|
|
228
|
-
ReasoningGroup,
|
|
229
|
-
};
|
|
230
|
-
|
|
231
|
-
export default function Home() {
|
|
232
|
-
const runtime = useOpenCodeRuntime({
|
|
233
|
-
baseUrl:
|
|
234
|
-
process.env.NEXT_PUBLIC_OPENCODE_BASE_URL ?? "http://localhost:4096",
|
|
235
|
-
});
|
|
236
|
-
|
|
237
|
-
const config = AuiConfig({
|
|
238
|
-
tools: Tools({ toolkit }),
|
|
239
|
-
});
|
|
240
|
-
|
|
241
|
-
return (
|
|
242
|
-
<AssistantRuntimeProvider config={config} runtime={runtime}>
|
|
243
|
-
<SetFallbackDataUI />
|
|
244
|
-
<SidebarProvider>
|
|
245
|
-
<div className="flex h-dvh w-full overflow-hidden pr-0.5">
|
|
246
|
-
<ThreadListSidebar />
|
|
247
|
-
<SidebarInset>
|
|
248
|
-
<header className="flex h-16 shrink-0 items-center gap-2 border-b px-4">
|
|
249
|
-
<SidebarTrigger />
|
|
250
|
-
<Separator orientation="vertical" className="mr-2 h-4" />
|
|
251
|
-
<div className="min-w-0">
|
|
252
|
-
<div className="text-sm font-medium">OpenCode</div>
|
|
253
|
-
<SelectedSessionTitle />
|
|
254
|
-
</div>
|
|
255
|
-
</header>
|
|
256
|
-
<div className="min-h-0 flex-1 overflow-hidden">
|
|
257
|
-
<Thread components={THREAD_COMPONENTS} />
|
|
258
|
-
</div>
|
|
259
|
-
</SidebarInset>
|
|
260
|
-
</div>
|
|
261
|
-
</SidebarProvider>
|
|
262
|
-
</AssistantRuntimeProvider>
|
|
263
|
-
);
|
|
264
|
-
}
|
|
265
|
-
|
|
266
|
-
function SelectedSessionTitle() {
|
|
267
|
-
const session = useOpenCodeSession();
|
|
268
|
-
const title = session?.title?.trim();
|
|
269
|
-
|
|
270
|
-
if (!title) return null;
|
|
271
|
-
|
|
272
|
-
return (
|
|
273
|
-
<div className="text-muted-foreground truncate text-xs leading-tight">
|
|
274
|
-
{title}
|
|
275
|
-
</div>
|
|
276
|
-
);
|
|
277
|
-
}
|
|
278
|
-
|
|
279
|
-
```
|
|
280
|
-
|
|
281
|
-
## components.json
|
|
282
|
-
|
|
283
|
-
```json
|
|
284
|
-
{
|
|
285
|
-
"$schema": "https://ui.shadcn.com/schema.json",
|
|
286
|
-
"style": "new-york",
|
|
287
|
-
"rsc": true,
|
|
288
|
-
"tsx": true,
|
|
289
|
-
"tailwind": {
|
|
290
|
-
"config": "",
|
|
291
|
-
"css": "app/globals.css",
|
|
292
|
-
"baseColor": "zinc",
|
|
293
|
-
"cssVariables": true,
|
|
294
|
-
"prefix": ""
|
|
295
|
-
},
|
|
296
|
-
"aliases": {
|
|
297
|
-
"components": "@/components",
|
|
298
|
-
"utils": "@/lib/utils",
|
|
299
|
-
"ui": "@/components/ui",
|
|
300
|
-
"lib": "@/lib",
|
|
301
|
-
"hooks": "@/hooks"
|
|
302
|
-
},
|
|
303
|
-
"iconLibrary": "lucide",
|
|
304
|
-
"registries": {
|
|
305
|
-
"@assistant-ui": "https://r.assistant-ui.com/{name}.json"
|
|
306
|
-
}
|
|
307
|
-
}
|
|
308
|
-
|
|
309
|
-
```
|
|
310
|
-
|
|
311
|
-
## components/opencode-data-part.tsx
|
|
312
|
-
|
|
313
|
-
```tsx
|
|
314
|
-
"use client";
|
|
315
|
-
|
|
316
|
-
import { WrenchIcon } from "lucide-react";
|
|
317
|
-
import type { FC } from "react";
|
|
318
|
-
|
|
319
|
-
type OpenCodeDataPartProps = {
|
|
320
|
-
name: string;
|
|
321
|
-
data: unknown;
|
|
322
|
-
};
|
|
323
|
-
|
|
324
|
-
const getSummary = (name: string, data: unknown) => {
|
|
325
|
-
if (
|
|
326
|
-
name === "opencode-unsupported-part" &&
|
|
327
|
-
typeof data === "object" &&
|
|
328
|
-
data !== null &&
|
|
329
|
-
"type" in data &&
|
|
330
|
-
typeof data.type === "string"
|
|
331
|
-
) {
|
|
332
|
-
return `Unsupported part: ${data.type}`;
|
|
333
|
-
}
|
|
334
|
-
|
|
335
|
-
if (name === "opencode-snapshot") return "Snapshot";
|
|
336
|
-
if (name === "opencode-retry") return "Retry";
|
|
337
|
-
if (name === "opencode-compaction") return "Compaction";
|
|
338
|
-
if (name === "opencode-agent") return "Agent event";
|
|
339
|
-
if (name === "opencode-subtask") return "Subtask";
|
|
340
|
-
|
|
341
|
-
return name.replace(/^opencode-/, "").replace(/-/g, " ");
|
|
342
|
-
};
|
|
343
|
-
|
|
344
|
-
export const OpenCodeDataPart: FC<OpenCodeDataPartProps> = ({ name, data }) => {
|
|
345
|
-
const shouldHide =
|
|
346
|
-
name === "opencode-step-start" ||
|
|
347
|
-
name === "opencode-step-finish" ||
|
|
348
|
-
name === "opencode-patch";
|
|
349
|
-
|
|
350
|
-
if (shouldHide) {
|
|
351
|
-
return null;
|
|
352
|
-
}
|
|
353
|
-
|
|
354
|
-
const summary = getSummary(name, data);
|
|
355
|
-
|
|
356
|
-
return (
|
|
357
|
-
<div className="bg-muted/30 text-muted-foreground my-3 flex w-min items-center gap-2 rounded-full border px-2 py-1 text-xs text-nowrap">
|
|
358
|
-
<WrenchIcon className="size-3" />
|
|
359
|
-
{summary}
|
|
360
|
-
</div>
|
|
361
|
-
);
|
|
362
|
-
};
|
|
363
|
-
|
|
364
|
-
```
|
|
365
|
-
|
|
366
|
-
## components/tools/opencode-permission-card.tsx
|
|
367
|
-
|
|
368
|
-
```tsx
|
|
369
|
-
"use client";
|
|
370
|
-
|
|
371
|
-
import { useState } from "react";
|
|
372
|
-
import {
|
|
373
|
-
type OpenCodePermissionRequest,
|
|
374
|
-
type OpenCodePermissionResponse,
|
|
375
|
-
useOpenCodeRuntimeExtras,
|
|
376
|
-
} from "@assistant-ui/react-opencode";
|
|
377
|
-
import {
|
|
378
|
-
CheckCircle2Icon,
|
|
379
|
-
LoaderIcon,
|
|
380
|
-
ShieldAlertIcon,
|
|
381
|
-
XCircleIcon,
|
|
382
|
-
} from "lucide-react";
|
|
383
|
-
import { Badge } from "@/components/ui/badge";
|
|
384
|
-
import { Button } from "@/components/ui/button";
|
|
385
|
-
import {
|
|
386
|
-
Card,
|
|
387
|
-
CardContent,
|
|
388
|
-
CardDescription,
|
|
389
|
-
CardFooter,
|
|
390
|
-
CardHeader,
|
|
391
|
-
CardTitle,
|
|
392
|
-
} from "@/components/ui/card";
|
|
393
|
-
|
|
394
|
-
const formatPermissionReply = (reply: OpenCodePermissionResponse) => {
|
|
395
|
-
switch (reply) {
|
|
396
|
-
case "always":
|
|
397
|
-
return "Always allow";
|
|
398
|
-
case "once":
|
|
399
|
-
return "Allowed once";
|
|
400
|
-
case "reject":
|
|
401
|
-
return "Rejected";
|
|
402
|
-
}
|
|
403
|
-
};
|
|
404
|
-
|
|
405
|
-
export const OpenCodePermissionCard = ({
|
|
406
|
-
request,
|
|
407
|
-
state,
|
|
408
|
-
reply,
|
|
409
|
-
}: {
|
|
410
|
-
request: OpenCodePermissionRequest;
|
|
411
|
-
state: "pending" | "resolved";
|
|
412
|
-
reply?: OpenCodePermissionResponse;
|
|
413
|
-
}) => {
|
|
414
|
-
const { replyToPermission } = useOpenCodeRuntimeExtras();
|
|
415
|
-
const [submittingReply, setSubmittingReply] =
|
|
416
|
-
useState<OpenCodePermissionResponse | null>(null);
|
|
417
|
-
const [error, setError] = useState<string | null>(null);
|
|
418
|
-
|
|
419
|
-
const submitReply = async (nextReply: OpenCodePermissionResponse) => {
|
|
420
|
-
setSubmittingReply(nextReply);
|
|
421
|
-
setError(null);
|
|
422
|
-
|
|
423
|
-
try {
|
|
424
|
-
await replyToPermission(request.id, nextReply);
|
|
425
|
-
} catch (err) {
|
|
426
|
-
setError(err instanceof Error ? err.message : "Failed to submit reply.");
|
|
427
|
-
setSubmittingReply(null);
|
|
428
|
-
}
|
|
429
|
-
};
|
|
430
|
-
|
|
431
|
-
const isPending = state === "pending";
|
|
432
|
-
|
|
433
|
-
return (
|
|
434
|
-
<Card className="gap-4 py-4">
|
|
435
|
-
<CardHeader className="px-4">
|
|
436
|
-
<div className="flex items-start gap-3">
|
|
437
|
-
<div className="rounded-md bg-amber-500/10 p-2 text-amber-700">
|
|
438
|
-
{isPending ? (
|
|
439
|
-
<ShieldAlertIcon className="size-4" />
|
|
440
|
-
) : reply === "reject" ? (
|
|
441
|
-
<XCircleIcon className="size-4" />
|
|
442
|
-
) : (
|
|
443
|
-
<CheckCircle2Icon className="size-4" />
|
|
444
|
-
)}
|
|
445
|
-
</div>
|
|
446
|
-
<div className="min-w-0 flex-1">
|
|
447
|
-
<CardTitle className="text-sm">
|
|
448
|
-
{isPending ? "Approval required" : formatPermissionReply(reply!)}
|
|
449
|
-
</CardTitle>
|
|
450
|
-
<CardDescription className="mt-1 text-xs">
|
|
451
|
-
{request.title ??
|
|
452
|
-
`OpenCode requested approval for ${request.toolName ?? request.permission}.`}
|
|
453
|
-
</CardDescription>
|
|
454
|
-
</div>
|
|
455
|
-
<Badge variant={isPending ? "secondary" : "outline"}>
|
|
456
|
-
{request.toolName ?? request.permission}
|
|
457
|
-
</Badge>
|
|
458
|
-
</div>
|
|
459
|
-
</CardHeader>
|
|
460
|
-
|
|
461
|
-
{(request.patterns.length > 0 || request.toolInput !== undefined) && (
|
|
462
|
-
<CardContent className="space-y-3 px-4">
|
|
463
|
-
{request.patterns.length > 0 ? (
|
|
464
|
-
<div className="flex flex-wrap gap-2">
|
|
465
|
-
{request.patterns.map((pattern) => (
|
|
466
|
-
<Badge key={pattern} variant="outline" className="font-mono">
|
|
467
|
-
{pattern}
|
|
468
|
-
</Badge>
|
|
469
|
-
))}
|
|
470
|
-
</div>
|
|
471
|
-
) : null}
|
|
472
|
-
{request.toolInput !== undefined ? (
|
|
473
|
-
<pre className="bg-muted overflow-x-auto rounded-md p-3 text-xs">
|
|
474
|
-
{JSON.stringify(request.toolInput, null, 2)}
|
|
475
|
-
</pre>
|
|
476
|
-
) : null}
|
|
477
|
-
</CardContent>
|
|
478
|
-
)}
|
|
479
|
-
|
|
480
|
-
{isPending ? (
|
|
481
|
-
<CardFooter className="flex flex-wrap gap-2 px-4">
|
|
482
|
-
<Button
|
|
483
|
-
size="sm"
|
|
484
|
-
onClick={() => submitReply("once")}
|
|
485
|
-
disabled={submittingReply !== null}
|
|
486
|
-
>
|
|
487
|
-
{submittingReply === "once" && (
|
|
488
|
-
<LoaderIcon className="size-4 animate-spin" />
|
|
489
|
-
)}
|
|
490
|
-
Allow once
|
|
491
|
-
</Button>
|
|
492
|
-
<Button
|
|
493
|
-
size="sm"
|
|
494
|
-
variant="secondary"
|
|
495
|
-
onClick={() => submitReply("always")}
|
|
496
|
-
disabled={submittingReply !== null}
|
|
497
|
-
>
|
|
498
|
-
{submittingReply === "always" && (
|
|
499
|
-
<LoaderIcon className="size-4 animate-spin" />
|
|
500
|
-
)}
|
|
501
|
-
Always allow
|
|
502
|
-
</Button>
|
|
503
|
-
<Button
|
|
504
|
-
size="sm"
|
|
505
|
-
variant="outline"
|
|
506
|
-
onClick={() => submitReply("reject")}
|
|
507
|
-
disabled={submittingReply !== null}
|
|
508
|
-
>
|
|
509
|
-
{submittingReply === "reject" && (
|
|
510
|
-
<LoaderIcon className="size-4 animate-spin" />
|
|
511
|
-
)}
|
|
512
|
-
Reject
|
|
513
|
-
</Button>
|
|
514
|
-
{error ? (
|
|
515
|
-
<p className="text-destructive w-full text-xs">{error}</p>
|
|
516
|
-
) : null}
|
|
517
|
-
</CardFooter>
|
|
518
|
-
) : null}
|
|
519
|
-
</Card>
|
|
520
|
-
);
|
|
521
|
-
};
|
|
522
|
-
|
|
523
|
-
```
|
|
524
|
-
|
|
525
|
-
## components/tools/opencode-question-card.tsx
|
|
526
|
-
|
|
527
|
-
```tsx
|
|
528
|
-
"use client";
|
|
529
|
-
|
|
530
|
-
import { useMemo, useState } from "react";
|
|
531
|
-
import {
|
|
532
|
-
type OpenCodeQuestionRequest,
|
|
533
|
-
type QuestionAnswer,
|
|
534
|
-
useOpenCodeRuntimeExtras,
|
|
535
|
-
} from "@assistant-ui/react-opencode";
|
|
536
|
-
import {
|
|
537
|
-
AlertCircleIcon,
|
|
538
|
-
CheckCircle2Icon,
|
|
539
|
-
CircleHelpIcon,
|
|
540
|
-
LoaderIcon,
|
|
541
|
-
XCircleIcon,
|
|
542
|
-
} from "lucide-react";
|
|
543
|
-
import { Badge } from "@/components/ui/badge";
|
|
544
|
-
import { Button } from "@/components/ui/button";
|
|
545
|
-
import {
|
|
546
|
-
Card,
|
|
547
|
-
CardContent,
|
|
548
|
-
CardDescription,
|
|
549
|
-
CardFooter,
|
|
550
|
-
CardHeader,
|
|
551
|
-
CardTitle,
|
|
552
|
-
} from "@/components/ui/card";
|
|
553
|
-
import { Checkbox } from "@/components/ui/checkbox";
|
|
554
|
-
import { Label } from "@/components/ui/label";
|
|
555
|
-
import { Textarea } from "@/components/ui/textarea";
|
|
556
|
-
import { cn } from "@/lib/utils";
|
|
557
|
-
|
|
558
|
-
type PendingQuestionState = {
|
|
559
|
-
selectedLabels: string[];
|
|
560
|
-
customAnswer: string;
|
|
561
|
-
};
|
|
562
|
-
|
|
563
|
-
const buildEmptyQuestionState = (
|
|
564
|
-
request: OpenCodeQuestionRequest,
|
|
565
|
-
): PendingQuestionState[] =>
|
|
566
|
-
request.questions.map(() => ({
|
|
567
|
-
selectedLabels: [],
|
|
568
|
-
customAnswer: "",
|
|
569
|
-
}));
|
|
570
|
-
|
|
571
|
-
const encodeQuestionAnswers = (
|
|
572
|
-
request: OpenCodeQuestionRequest,
|
|
573
|
-
formState: PendingQuestionState[],
|
|
574
|
-
): QuestionAnswer[] =>
|
|
575
|
-
request.questions.map((question, index) => {
|
|
576
|
-
const current = formState[index];
|
|
577
|
-
if (!current) return [];
|
|
578
|
-
|
|
579
|
-
const answers = [...current.selectedLabels];
|
|
580
|
-
const customValue = current.customAnswer.trim();
|
|
581
|
-
const customAllowed = question.custom !== false;
|
|
582
|
-
|
|
583
|
-
if (customAllowed && customValue) {
|
|
584
|
-
answers.push(`Other: ${customValue}`);
|
|
585
|
-
}
|
|
586
|
-
|
|
587
|
-
return answers;
|
|
588
|
-
});
|
|
589
|
-
|
|
590
|
-
export const getQuestionSummary = (request?: OpenCodeQuestionRequest) => {
|
|
591
|
-
if (!request || request.questions.length === 0) return "Waiting for input";
|
|
592
|
-
if (request.questions.length === 1) {
|
|
593
|
-
return request.questions[0]?.header || "1 question";
|
|
594
|
-
}
|
|
595
|
-
return `${request.questions.length} questions`;
|
|
596
|
-
};
|
|
597
|
-
|
|
598
|
-
export const OpenCodeQuestionCard = ({
|
|
599
|
-
request,
|
|
600
|
-
state,
|
|
601
|
-
answers,
|
|
602
|
-
}: {
|
|
603
|
-
request: OpenCodeQuestionRequest;
|
|
604
|
-
state: "pending" | "answered" | "rejected";
|
|
605
|
-
answers?: readonly QuestionAnswer[];
|
|
606
|
-
}) => {
|
|
607
|
-
const { replyToQuestion, rejectQuestion } = useOpenCodeRuntimeExtras();
|
|
608
|
-
const [formState, setFormState] = useState(() =>
|
|
609
|
-
buildEmptyQuestionState(request),
|
|
610
|
-
);
|
|
611
|
-
const [submissionState, setSubmissionState] = useState<
|
|
612
|
-
"reply" | "reject" | null
|
|
613
|
-
>(null);
|
|
614
|
-
const [error, setError] = useState<string | null>(null);
|
|
615
|
-
const isPending = state === "pending";
|
|
616
|
-
|
|
617
|
-
const encodedAnswers = useMemo(
|
|
618
|
-
() => encodeQuestionAnswers(request, formState),
|
|
619
|
-
[formState, request],
|
|
620
|
-
);
|
|
621
|
-
|
|
622
|
-
const canSubmit =
|
|
623
|
-
isPending && encodedAnswers.every((answer) => answer.length > 0);
|
|
624
|
-
|
|
625
|
-
const updateQuestionState = (
|
|
626
|
-
index: number,
|
|
627
|
-
updater: (current: PendingQuestionState) => PendingQuestionState,
|
|
628
|
-
) => {
|
|
629
|
-
setFormState((current) =>
|
|
630
|
-
current.map((item, itemIndex) =>
|
|
631
|
-
itemIndex === index ? updater(item) : item,
|
|
632
|
-
),
|
|
633
|
-
);
|
|
634
|
-
};
|
|
635
|
-
|
|
636
|
-
const submitAnswers = async () => {
|
|
637
|
-
if (!canSubmit) return;
|
|
638
|
-
setSubmissionState("reply");
|
|
639
|
-
setError(null);
|
|
640
|
-
|
|
641
|
-
try {
|
|
642
|
-
await replyToQuestion(request.id, encodedAnswers);
|
|
643
|
-
} catch (err) {
|
|
644
|
-
setError(
|
|
645
|
-
err instanceof Error ? err.message : "Failed to submit answers.",
|
|
646
|
-
);
|
|
647
|
-
setSubmissionState(null);
|
|
648
|
-
}
|
|
649
|
-
};
|
|
650
|
-
|
|
651
|
-
const rejectRequest = async () => {
|
|
652
|
-
setSubmissionState("reject");
|
|
653
|
-
setError(null);
|
|
654
|
-
|
|
655
|
-
try {
|
|
656
|
-
await rejectQuestion(request.id);
|
|
657
|
-
} catch (err) {
|
|
658
|
-
setError(
|
|
659
|
-
err instanceof Error ? err.message : "Failed to reject request.",
|
|
660
|
-
);
|
|
661
|
-
setSubmissionState(null);
|
|
662
|
-
}
|
|
663
|
-
};
|
|
664
|
-
|
|
665
|
-
return (
|
|
666
|
-
<Card className="gap-4 py-4">
|
|
667
|
-
<CardHeader className="px-4">
|
|
668
|
-
<div className="flex items-start gap-3">
|
|
669
|
-
<div
|
|
670
|
-
className={cn(
|
|
671
|
-
"rounded-md p-2",
|
|
672
|
-
isPending
|
|
673
|
-
? "bg-sky-500/10 text-sky-700"
|
|
674
|
-
: state === "rejected"
|
|
675
|
-
? "bg-muted text-muted-foreground"
|
|
676
|
-
: "bg-emerald-500/10 text-emerald-700",
|
|
677
|
-
)}
|
|
678
|
-
>
|
|
679
|
-
{isPending ? (
|
|
680
|
-
<CircleHelpIcon className="size-4" />
|
|
681
|
-
) : state === "rejected" ? (
|
|
682
|
-
<XCircleIcon className="size-4" />
|
|
683
|
-
) : (
|
|
684
|
-
<CheckCircle2Icon className="size-4" />
|
|
685
|
-
)}
|
|
686
|
-
</div>
|
|
687
|
-
<div className="min-w-0 flex-1">
|
|
688
|
-
<CardTitle className="text-sm">
|
|
689
|
-
{isPending
|
|
690
|
-
? "Answer required"
|
|
691
|
-
: state === "rejected"
|
|
692
|
-
? "Question dismissed"
|
|
693
|
-
: "Answers submitted"}
|
|
694
|
-
</CardTitle>
|
|
695
|
-
<CardDescription className="mt-1 text-xs">
|
|
696
|
-
{getQuestionSummary(request)}
|
|
697
|
-
</CardDescription>
|
|
698
|
-
</div>
|
|
699
|
-
<Badge variant={isPending ? "secondary" : "outline"}>
|
|
700
|
-
{request.questions.length === 1
|
|
701
|
-
? "1 prompt"
|
|
702
|
-
: `${request.questions.length} prompts`}
|
|
703
|
-
</Badge>
|
|
704
|
-
</div>
|
|
705
|
-
</CardHeader>
|
|
706
|
-
|
|
707
|
-
<CardContent className="space-y-4 px-4">
|
|
708
|
-
{request.questions.map((question, index) => {
|
|
709
|
-
const current = formState[index] ?? {
|
|
710
|
-
selectedLabels: [],
|
|
711
|
-
customAnswer: "",
|
|
712
|
-
};
|
|
713
|
-
const answeredValues = answers?.[index] ?? [];
|
|
714
|
-
const customAllowed = question.custom !== false;
|
|
715
|
-
|
|
716
|
-
return (
|
|
717
|
-
<section key={`${request.id}-${index}`} className="space-y-3">
|
|
718
|
-
<div className="space-y-1">
|
|
719
|
-
<p className="text-sm font-medium">{question.header}</p>
|
|
720
|
-
<p className="text-muted-foreground text-xs">
|
|
721
|
-
{question.question}
|
|
722
|
-
</p>
|
|
723
|
-
</div>
|
|
724
|
-
|
|
725
|
-
{isPending ? (
|
|
726
|
-
<>
|
|
727
|
-
{question.options.length > 0 ? (
|
|
728
|
-
question.multiple ? (
|
|
729
|
-
<div className="space-y-2">
|
|
730
|
-
{question.options.map((option) => {
|
|
731
|
-
const checked = current.selectedLabels.includes(
|
|
732
|
-
option.label,
|
|
733
|
-
);
|
|
734
|
-
const optionId = `${request.id}-${index}-${option.label}`;
|
|
735
|
-
|
|
736
|
-
return (
|
|
737
|
-
<div
|
|
738
|
-
key={option.label}
|
|
739
|
-
className="flex items-start gap-3 rounded-lg border p-3 text-sm"
|
|
740
|
-
>
|
|
741
|
-
<Checkbox
|
|
742
|
-
id={optionId}
|
|
743
|
-
checked={checked}
|
|
744
|
-
onCheckedChange={(nextChecked) => {
|
|
745
|
-
updateQuestionState(index, (item) => ({
|
|
746
|
-
...item,
|
|
747
|
-
selectedLabels:
|
|
748
|
-
nextChecked === true
|
|
749
|
-
? [...item.selectedLabels, option.label]
|
|
750
|
-
: item.selectedLabels.filter(
|
|
751
|
-
(label) => label !== option.label,
|
|
752
|
-
),
|
|
753
|
-
}));
|
|
754
|
-
}}
|
|
755
|
-
/>
|
|
756
|
-
<div className="space-y-1">
|
|
757
|
-
<Label
|
|
758
|
-
htmlFor={optionId}
|
|
759
|
-
className="text-sm font-medium"
|
|
760
|
-
>
|
|
761
|
-
{option.label}
|
|
762
|
-
</Label>
|
|
763
|
-
<p className="text-muted-foreground text-xs">
|
|
764
|
-
{option.description}
|
|
765
|
-
</p>
|
|
766
|
-
</div>
|
|
767
|
-
</div>
|
|
768
|
-
);
|
|
769
|
-
})}
|
|
770
|
-
</div>
|
|
771
|
-
) : (
|
|
772
|
-
<div className="flex flex-wrap gap-2">
|
|
773
|
-
{question.options.map((option) => {
|
|
774
|
-
const selected = current.selectedLabels.includes(
|
|
775
|
-
option.label,
|
|
776
|
-
);
|
|
777
|
-
|
|
778
|
-
return (
|
|
779
|
-
<Button
|
|
780
|
-
key={option.label}
|
|
781
|
-
type="button"
|
|
782
|
-
size="sm"
|
|
783
|
-
variant={selected ? "default" : "outline"}
|
|
784
|
-
onClick={() => {
|
|
785
|
-
updateQuestionState(index, (item) => ({
|
|
786
|
-
...item,
|
|
787
|
-
selectedLabels: [option.label],
|
|
788
|
-
customAnswer: "",
|
|
789
|
-
}));
|
|
790
|
-
}}
|
|
791
|
-
>
|
|
792
|
-
{option.label}
|
|
793
|
-
</Button>
|
|
794
|
-
);
|
|
795
|
-
})}
|
|
796
|
-
</div>
|
|
797
|
-
)
|
|
798
|
-
) : null}
|
|
799
|
-
|
|
800
|
-
{customAllowed ? (
|
|
801
|
-
<div className="space-y-2">
|
|
802
|
-
<Label htmlFor={`${request.id}-${index}-custom`}>
|
|
803
|
-
Other
|
|
804
|
-
</Label>
|
|
805
|
-
<Textarea
|
|
806
|
-
id={`${request.id}-${index}-custom`}
|
|
807
|
-
value={current.customAnswer}
|
|
808
|
-
onChange={(event) => {
|
|
809
|
-
const nextValue = event.target.value;
|
|
810
|
-
updateQuestionState(index, (item) => ({
|
|
811
|
-
...item,
|
|
812
|
-
customAnswer: nextValue,
|
|
813
|
-
selectedLabels:
|
|
814
|
-
question.multiple || nextValue.trim().length === 0
|
|
815
|
-
? item.selectedLabels
|
|
816
|
-
: [],
|
|
817
|
-
}));
|
|
818
|
-
}}
|
|
819
|
-
placeholder="Add your answer"
|
|
820
|
-
rows={3}
|
|
821
|
-
/>
|
|
822
|
-
</div>
|
|
823
|
-
) : null}
|
|
824
|
-
</>
|
|
825
|
-
) : state === "rejected" ? (
|
|
826
|
-
<p className="text-muted-foreground text-xs">
|
|
827
|
-
No answer was submitted.
|
|
828
|
-
</p>
|
|
829
|
-
) : (
|
|
830
|
-
<div className="flex flex-wrap gap-2">
|
|
831
|
-
{answeredValues.map((answer) => (
|
|
832
|
-
<Badge key={answer} variant="outline">
|
|
833
|
-
{answer}
|
|
834
|
-
</Badge>
|
|
835
|
-
))}
|
|
836
|
-
</div>
|
|
837
|
-
)}
|
|
838
|
-
</section>
|
|
839
|
-
);
|
|
840
|
-
})}
|
|
841
|
-
</CardContent>
|
|
842
|
-
|
|
843
|
-
{isPending ? (
|
|
844
|
-
<CardFooter className="flex flex-wrap gap-2 px-4">
|
|
845
|
-
<Button
|
|
846
|
-
size="sm"
|
|
847
|
-
onClick={submitAnswers}
|
|
848
|
-
disabled={!canSubmit || submissionState !== null}
|
|
849
|
-
>
|
|
850
|
-
{submissionState === "reply" && (
|
|
851
|
-
<LoaderIcon className="size-4 animate-spin" />
|
|
852
|
-
)}
|
|
853
|
-
Submit answers
|
|
854
|
-
</Button>
|
|
855
|
-
<Button
|
|
856
|
-
size="sm"
|
|
857
|
-
variant="outline"
|
|
858
|
-
onClick={rejectRequest}
|
|
859
|
-
disabled={submissionState !== null}
|
|
860
|
-
>
|
|
861
|
-
{submissionState === "reject" && (
|
|
862
|
-
<LoaderIcon className="size-4 animate-spin" />
|
|
863
|
-
)}
|
|
864
|
-
Dismiss
|
|
865
|
-
</Button>
|
|
866
|
-
{error ? (
|
|
867
|
-
<div className="text-destructive flex w-full items-center gap-2 text-xs">
|
|
868
|
-
<AlertCircleIcon className="size-3.5" />
|
|
869
|
-
<span>{error}</span>
|
|
870
|
-
</div>
|
|
871
|
-
) : null}
|
|
872
|
-
</CardFooter>
|
|
873
|
-
) : null}
|
|
874
|
-
</Card>
|
|
875
|
-
);
|
|
876
|
-
};
|
|
877
|
-
|
|
878
|
-
```
|
|
879
|
-
|
|
880
|
-
## components/tools/opencode-tool-interactions.tsx
|
|
881
|
-
|
|
882
|
-
```tsx
|
|
883
|
-
"use client";
|
|
884
|
-
|
|
885
|
-
import { memo, useMemo } from "react";
|
|
886
|
-
import type { ToolCallMessagePartComponent } from "@assistant-ui/react";
|
|
887
|
-
import {
|
|
888
|
-
type OpenCodePermissionRequest,
|
|
889
|
-
type OpenCodePermissionResponse,
|
|
890
|
-
type OpenCodeQuestionRequest,
|
|
891
|
-
type QuestionAnswer,
|
|
892
|
-
useOpenCodeThreadState,
|
|
893
|
-
} from "@assistant-ui/react-opencode";
|
|
894
|
-
import {
|
|
895
|
-
CheckCircle2Icon,
|
|
896
|
-
CircleHelpIcon,
|
|
897
|
-
LoaderIcon,
|
|
898
|
-
XCircleIcon,
|
|
899
|
-
} from "lucide-react";
|
|
900
|
-
import { OpenCodePermissionCard } from "@/components/tools/opencode-permission-card";
|
|
901
|
-
import {
|
|
902
|
-
OpenCodeQuestionCard,
|
|
903
|
-
getQuestionSummary,
|
|
904
|
-
} from "@/components/tools/opencode-question-card";
|
|
905
|
-
|
|
906
|
-
type ToolPermissionInteraction = {
|
|
907
|
-
request: OpenCodePermissionRequest;
|
|
908
|
-
state: "pending" | "resolved";
|
|
909
|
-
reply?: OpenCodePermissionResponse;
|
|
910
|
-
};
|
|
911
|
-
|
|
912
|
-
type ToolQuestionInteraction = {
|
|
913
|
-
request: OpenCodeQuestionRequest;
|
|
914
|
-
state: "pending" | "answered" | "rejected";
|
|
915
|
-
answers?: readonly QuestionAnswer[];
|
|
916
|
-
};
|
|
917
|
-
|
|
918
|
-
const ASK_QUESTION_TOOL_NAMES = [
|
|
919
|
-
"ask_question",
|
|
920
|
-
"request_user_input",
|
|
921
|
-
"requestUserInput",
|
|
922
|
-
] as const;
|
|
923
|
-
|
|
924
|
-
const useOpenCodeToolInteractions = (toolCallId: string) => {
|
|
925
|
-
const interactions = useOpenCodeThreadState((state) => state.interactions);
|
|
926
|
-
|
|
927
|
-
return useMemo(() => {
|
|
928
|
-
const permissions: ToolPermissionInteraction[] = [];
|
|
929
|
-
const questions: ToolQuestionInteraction[] = [];
|
|
930
|
-
|
|
931
|
-
for (const request of Object.values(interactions.permissions.pending)) {
|
|
932
|
-
if (request.tool?.callID === toolCallId) {
|
|
933
|
-
permissions.push({ request, state: "pending" });
|
|
934
|
-
}
|
|
935
|
-
}
|
|
936
|
-
|
|
937
|
-
for (const resolved of Object.values(interactions.permissions.resolved)) {
|
|
938
|
-
if (resolved.request.tool?.callID === toolCallId) {
|
|
939
|
-
permissions.push({
|
|
940
|
-
request: resolved.request,
|
|
941
|
-
state: "resolved",
|
|
942
|
-
reply: resolved.reply,
|
|
943
|
-
});
|
|
944
|
-
}
|
|
945
|
-
}
|
|
946
|
-
|
|
947
|
-
for (const request of Object.values(interactions.questions.pending)) {
|
|
948
|
-
if (request.tool?.callID === toolCallId) {
|
|
949
|
-
questions.push({ request, state: "pending" });
|
|
950
|
-
}
|
|
951
|
-
}
|
|
952
|
-
|
|
953
|
-
for (const answered of Object.values(interactions.questions.answered)) {
|
|
954
|
-
if (answered.request.tool?.callID === toolCallId) {
|
|
955
|
-
questions.push({
|
|
956
|
-
request: answered.request,
|
|
957
|
-
state: "answered",
|
|
958
|
-
answers: answered.answers,
|
|
959
|
-
});
|
|
960
|
-
}
|
|
961
|
-
}
|
|
962
|
-
|
|
963
|
-
for (const rejected of Object.values(interactions.questions.rejected)) {
|
|
964
|
-
if (rejected.request.tool?.callID === toolCallId) {
|
|
965
|
-
questions.push({
|
|
966
|
-
request: rejected.request,
|
|
967
|
-
state: "rejected",
|
|
968
|
-
});
|
|
969
|
-
}
|
|
970
|
-
}
|
|
971
|
-
|
|
972
|
-
return { permissions, questions };
|
|
973
|
-
}, [interactions, toolCallId]);
|
|
974
|
-
};
|
|
975
|
-
|
|
976
|
-
const ToolInteractionStack = ({ toolCallId }: { toolCallId: string }) => {
|
|
977
|
-
const { permissions, questions } = useOpenCodeToolInteractions(toolCallId);
|
|
978
|
-
|
|
979
|
-
if (permissions.length === 0 && questions.length === 0) {
|
|
980
|
-
return null;
|
|
981
|
-
}
|
|
982
|
-
|
|
983
|
-
return (
|
|
984
|
-
<div className="space-y-3">
|
|
985
|
-
{permissions.map((permission) => (
|
|
986
|
-
<OpenCodePermissionCard
|
|
987
|
-
key={`${permission.request.id}-${permission.state}`}
|
|
988
|
-
request={permission.request}
|
|
989
|
-
state={permission.state}
|
|
990
|
-
{...(permission.reply ? { reply: permission.reply } : {})}
|
|
991
|
-
/>
|
|
992
|
-
))}
|
|
993
|
-
{questions.map((question) => (
|
|
994
|
-
<OpenCodeQuestionCard
|
|
995
|
-
key={`${question.request.id}-${question.state}`}
|
|
996
|
-
request={question.request}
|
|
997
|
-
state={question.state}
|
|
998
|
-
{...(question.answers ? { answers: question.answers } : {})}
|
|
999
|
-
/>
|
|
1000
|
-
))}
|
|
1001
|
-
</div>
|
|
1002
|
-
);
|
|
1003
|
-
};
|
|
1004
|
-
|
|
1005
|
-
export const withOpenCodeToolInteractions = <TArgs = any, TResult = any>(
|
|
1006
|
-
BaseComponent: ToolCallMessagePartComponent<TArgs, TResult>,
|
|
1007
|
-
): ToolCallMessagePartComponent<TArgs, TResult> => {
|
|
1008
|
-
const WrappedComponent: ToolCallMessagePartComponent<TArgs, TResult> = memo(
|
|
1009
|
-
(props) => {
|
|
1010
|
-
return (
|
|
1011
|
-
<div className="space-y-3">
|
|
1012
|
-
<BaseComponent {...props} />
|
|
1013
|
-
<ToolInteractionStack toolCallId={props.toolCallId} />
|
|
1014
|
-
</div>
|
|
1015
|
-
);
|
|
1016
|
-
},
|
|
1017
|
-
);
|
|
1018
|
-
|
|
1019
|
-
WrappedComponent.displayName = `withOpenCodeToolInteractions(${
|
|
1020
|
-
BaseComponent.displayName || BaseComponent.name || "Tool"
|
|
1021
|
-
})`;
|
|
1022
|
-
|
|
1023
|
-
return WrappedComponent;
|
|
1024
|
-
};
|
|
1025
|
-
|
|
1026
|
-
export const AskQuestionInline: ToolCallMessagePartComponent = memo(
|
|
1027
|
-
({ toolCallId, toolName, status }) => {
|
|
1028
|
-
const { questions } = useOpenCodeToolInteractions(toolCallId);
|
|
1029
|
-
const primaryQuestion = questions[0]?.request;
|
|
1030
|
-
const label = getQuestionSummary(primaryQuestion);
|
|
1031
|
-
const statusType = status?.type ?? "complete";
|
|
1032
|
-
|
|
1033
|
-
return (
|
|
1034
|
-
<div className="text-muted-foreground flex items-center gap-2 py-0.5 text-sm">
|
|
1035
|
-
{statusType === "requires-action" ? (
|
|
1036
|
-
<CircleHelpIcon className="size-3.5 shrink-0" />
|
|
1037
|
-
) : statusType === "running" ? (
|
|
1038
|
-
<LoaderIcon className="size-3.5 shrink-0 animate-spin" />
|
|
1039
|
-
) : statusType === "incomplete" ? (
|
|
1040
|
-
<XCircleIcon className="text-destructive size-3.5 shrink-0" />
|
|
1041
|
-
) : (
|
|
1042
|
-
<CheckCircle2Icon className="size-3.5 shrink-0" />
|
|
1043
|
-
)}
|
|
1044
|
-
|
|
1045
|
-
<span className="flex items-center gap-1.5 truncate">
|
|
1046
|
-
<span className="font-medium">
|
|
1047
|
-
{ASK_QUESTION_TOOL_NAMES.includes(
|
|
1048
|
-
toolName as (typeof ASK_QUESTION_TOOL_NAMES)[number],
|
|
1049
|
-
)
|
|
1050
|
-
? "ask_question"
|
|
1051
|
-
: toolName}
|
|
1052
|
-
</span>
|
|
1053
|
-
<span className="opacity-60">{label}</span>
|
|
1054
|
-
</span>
|
|
1055
|
-
</div>
|
|
1056
|
-
);
|
|
1057
|
-
},
|
|
1058
|
-
);
|
|
1059
|
-
AskQuestionInline.displayName = "AskQuestionInline";
|
|
1060
|
-
|
|
1061
|
-
```
|
|
1062
|
-
|
|
1063
|
-
## components/tools/opencode-tools.tsx
|
|
1064
|
-
|
|
1065
|
-
```tsx
|
|
1066
|
-
"use client";
|
|
1067
|
-
|
|
1068
|
-
import {
|
|
1069
|
-
AskQuestionInline,
|
|
1070
|
-
withOpenCodeToolInteractions,
|
|
1071
|
-
} from "./opencode-tool-interactions";
|
|
1072
|
-
import { ApplyPatchDiff } from "./tool-ui-apply-patch";
|
|
1073
|
-
import { BashTerminal } from "./tool-ui-bash";
|
|
1074
|
-
import {
|
|
1075
|
-
EditInline,
|
|
1076
|
-
GlobInline,
|
|
1077
|
-
GrepInline,
|
|
1078
|
-
ReadInline,
|
|
1079
|
-
ToolCallFallback,
|
|
1080
|
-
WebFetchInline,
|
|
1081
|
-
WebSearchInline,
|
|
1082
|
-
WriteInline,
|
|
1083
|
-
} from "./tool-ui-inline";
|
|
1084
|
-
|
|
1085
|
-
export const ReadTool = withOpenCodeToolInteractions(ReadInline);
|
|
1086
|
-
export const EditTool = withOpenCodeToolInteractions(EditInline);
|
|
1087
|
-
export const WriteTool = withOpenCodeToolInteractions(WriteInline);
|
|
1088
|
-
export const BashTool = withOpenCodeToolInteractions(BashTerminal);
|
|
1089
|
-
export const GrepTool = withOpenCodeToolInteractions(GrepInline);
|
|
1090
|
-
export const GlobTool = withOpenCodeToolInteractions(GlobInline);
|
|
1091
|
-
export const WebSearchTool = withOpenCodeToolInteractions(WebSearchInline);
|
|
1092
|
-
export const WebFetchTool = withOpenCodeToolInteractions(WebFetchInline);
|
|
1093
|
-
export const ApplyPatchTool = withOpenCodeToolInteractions(ApplyPatchDiff);
|
|
1094
|
-
export const AskQuestionTool = withOpenCodeToolInteractions(AskQuestionInline);
|
|
1095
|
-
export const FallbackTool = withOpenCodeToolInteractions(ToolCallFallback);
|
|
1096
|
-
|
|
1097
|
-
```
|
|
1098
|
-
|
|
1099
|
-
## components/tools/reasoning-ghost.tsx
|
|
1100
|
-
|
|
1101
|
-
```tsx
|
|
1102
|
-
"use client";
|
|
1103
|
-
|
|
1104
|
-
import { memo, type FC, type PropsWithChildren } from "react";
|
|
1105
|
-
import { BrainIcon } from "lucide-react";
|
|
1106
|
-
import { useAuiState } from "@assistant-ui/react";
|
|
1107
|
-
import { cn } from "@/lib/utils";
|
|
1108
|
-
|
|
1109
|
-
type GroupedPartsGroup = {
|
|
1110
|
-
indices: readonly number[];
|
|
1111
|
-
};
|
|
1112
|
-
|
|
1113
|
-
const ReasoningGroupImpl: FC<
|
|
1114
|
-
PropsWithChildren<{ group: GroupedPartsGroup }>
|
|
1115
|
-
> = ({ children, group }) => {
|
|
1116
|
-
const startIndex = group.indices[0]!;
|
|
1117
|
-
const endIndex = group.indices.at(-1)!;
|
|
1118
|
-
|
|
1119
|
-
const isMultiLine = useAuiState((s) => {
|
|
1120
|
-
let totalLength = 0;
|
|
1121
|
-
for (let i = startIndex; i <= endIndex; i++) {
|
|
1122
|
-
const part = s.message.parts[i];
|
|
1123
|
-
if (part?.type === "reasoning") {
|
|
1124
|
-
if (part.text.includes("\n\n")) return true;
|
|
1125
|
-
totalLength += part.text.length;
|
|
1126
|
-
}
|
|
1127
|
-
}
|
|
1128
|
-
return totalLength > 120;
|
|
1129
|
-
});
|
|
1130
|
-
|
|
1131
|
-
const textAfter = useAuiState((s) => {
|
|
1132
|
-
for (let i = endIndex + 1; i < s.message.parts.length; i++) {
|
|
1133
|
-
const type = s.message.parts[i]?.type;
|
|
1134
|
-
if (type === "data") continue;
|
|
1135
|
-
return type === "text";
|
|
1136
|
-
}
|
|
1137
|
-
return false;
|
|
1138
|
-
});
|
|
1139
|
-
|
|
1140
|
-
return (
|
|
1141
|
-
<div
|
|
1142
|
-
data-slot="reasoning-group"
|
|
1143
|
-
className={cn(
|
|
1144
|
-
"text-muted-foreground mt-4 mb-2 flex items-start gap-2 text-sm leading-relaxed first:mt-0",
|
|
1145
|
-
isMultiLine && "mt-6 mb-4",
|
|
1146
|
-
textAfter && "mb-4",
|
|
1147
|
-
)}
|
|
1148
|
-
>
|
|
1149
|
-
<BrainIcon className="mt-[3.25px] size-3.5 shrink-0" />
|
|
1150
|
-
<div className="space-y-4.5">{children}</div>
|
|
1151
|
-
</div>
|
|
1152
|
-
);
|
|
1153
|
-
};
|
|
1154
|
-
|
|
1155
|
-
export const ReasoningGroup = memo(ReasoningGroupImpl);
|
|
1156
|
-
ReasoningGroup.displayName = "ReasoningGroup";
|
|
1157
|
-
|
|
1158
|
-
```
|
|
1159
|
-
|
|
1160
|
-
## components/tools/tool-group.tsx
|
|
1161
|
-
|
|
1162
|
-
```tsx
|
|
1163
|
-
import { cn } from "@/lib/utils";
|
|
1164
|
-
import { useAuiState } from "@assistant-ui/react";
|
|
1165
|
-
import { memo, type FC, type PropsWithChildren } from "react";
|
|
1166
|
-
|
|
1167
|
-
type GroupedPartsGroup = {
|
|
1168
|
-
indices: readonly number[];
|
|
1169
|
-
};
|
|
1170
|
-
|
|
1171
|
-
export const ToolGroupImpl: FC<
|
|
1172
|
-
PropsWithChildren<{ group: GroupedPartsGroup }>
|
|
1173
|
-
> = ({ children, group }) => {
|
|
1174
|
-
const startIndex = group.indices[0]!;
|
|
1175
|
-
const endIndex = group.indices.at(-1)!;
|
|
1176
|
-
|
|
1177
|
-
const textBefore = useAuiState((s) => {
|
|
1178
|
-
for (let i = startIndex - 1; i >= 0; i--) {
|
|
1179
|
-
const type = s.message.parts[i]?.type;
|
|
1180
|
-
if (type === "data") continue;
|
|
1181
|
-
return type === "text";
|
|
1182
|
-
}
|
|
1183
|
-
return false;
|
|
1184
|
-
});
|
|
1185
|
-
const textAfter = useAuiState((s) => {
|
|
1186
|
-
for (let i = endIndex + 1; i < s.message.parts.length; i++) {
|
|
1187
|
-
const type = s.message.parts[i]?.type;
|
|
1188
|
-
if (type === "data") continue;
|
|
1189
|
-
return type === "text";
|
|
1190
|
-
}
|
|
1191
|
-
return false;
|
|
1192
|
-
});
|
|
1193
|
-
|
|
1194
|
-
return (
|
|
1195
|
-
<div
|
|
1196
|
-
data-slot="tool-group"
|
|
1197
|
-
className={cn(textBefore && "mt-4", textAfter && "mb-4")}
|
|
1198
|
-
>
|
|
1199
|
-
{children}
|
|
1200
|
-
</div>
|
|
1201
|
-
);
|
|
1202
|
-
};
|
|
1203
|
-
|
|
1204
|
-
export const ToolGroup = memo(ToolGroupImpl);
|
|
1205
|
-
|
|
1206
|
-
```
|
|
1207
|
-
|
|
1208
|
-
## components/tools/tool-ui-apply-patch.tsx
|
|
1209
|
-
|
|
1210
|
-
```tsx
|
|
1211
|
-
"use client";
|
|
1212
|
-
|
|
1213
|
-
import { memo, useMemo, useState } from "react";
|
|
1214
|
-
import type { ToolCallMessagePartComponent } from "@assistant-ui/react";
|
|
1215
|
-
import { CheckIcon, ChevronRightIcon } from "lucide-react";
|
|
1216
|
-
import {
|
|
1217
|
-
Collapsible,
|
|
1218
|
-
CollapsibleContent,
|
|
1219
|
-
CollapsibleTrigger,
|
|
1220
|
-
} from "@/components/ui/radix/collapsible";
|
|
1221
|
-
import { cn } from "@/lib/utils";
|
|
1222
|
-
import { PatchDiff } from "@pierre/diffs/react";
|
|
1223
|
-
import {
|
|
1224
|
-
ToolStatusIcon,
|
|
1225
|
-
getPatchInfo,
|
|
1226
|
-
isCancelledToolStatus,
|
|
1227
|
-
str,
|
|
1228
|
-
} from "@/components/tools/tool-ui-shared";
|
|
1229
|
-
|
|
1230
|
-
// ── Patch format conversion ──────────────────────────────────────────────
|
|
1231
|
-
// Claude Code's apply_patch uses a custom "v2" format:
|
|
1232
|
-
// *** Begin Patch
|
|
1233
|
-
// *** Update File: path/to/file.ts
|
|
1234
|
-
// @@ context search text @@
|
|
1235
|
-
// context line
|
|
1236
|
-
// -removed
|
|
1237
|
-
// +added
|
|
1238
|
-
// *** End Patch
|
|
1239
|
-
//
|
|
1240
|
-
// The @@ markers are context-search strings, NOT standard unified diff
|
|
1241
|
-
// hunk headers. We convert to proper unified diff for @pierre/diffs.
|
|
1242
|
-
|
|
1243
|
-
interface PatchBlock {
|
|
1244
|
-
type: string;
|
|
1245
|
-
path: string;
|
|
1246
|
-
body: string;
|
|
1247
|
-
}
|
|
1248
|
-
|
|
1249
|
-
function parseClaudePatchBlocks(patchText: string): PatchBlock[] {
|
|
1250
|
-
// Strip wrapper lines
|
|
1251
|
-
const cleaned = patchText
|
|
1252
|
-
.replace(/^\*\*\*\s*Begin Patch\s*$/gm, "")
|
|
1253
|
-
.replace(/^\*\*\*\s*End Patch\s*$/gm, "")
|
|
1254
|
-
.trim();
|
|
1255
|
-
|
|
1256
|
-
const headerRegex = /^\*\*\*\s+(Update|Add|Delete)\s+File:\s+(.+)$/gm;
|
|
1257
|
-
const blocks: PatchBlock[] = [];
|
|
1258
|
-
let lastIndex = 0;
|
|
1259
|
-
for (const match of cleaned.matchAll(headerRegex)) {
|
|
1260
|
-
if (blocks.length > 0) {
|
|
1261
|
-
blocks[blocks.length - 1]!.body = cleaned.slice(lastIndex, match.index);
|
|
1262
|
-
}
|
|
1263
|
-
blocks.push({ type: match[1]!, path: match[2]!.trim(), body: "" });
|
|
1264
|
-
lastIndex = match.index + match[0].length;
|
|
1265
|
-
}
|
|
1266
|
-
if (blocks.length > 0) {
|
|
1267
|
-
blocks[blocks.length - 1]!.body = cleaned.slice(lastIndex);
|
|
1268
|
-
}
|
|
1269
|
-
|
|
1270
|
-
return blocks;
|
|
1271
|
-
}
|
|
1272
|
-
|
|
1273
|
-
function buildHunkHeader(
|
|
1274
|
-
lines: string[],
|
|
1275
|
-
oldStart: number,
|
|
1276
|
-
newStart: number,
|
|
1277
|
-
): { header: string; oldCount: number; newCount: number } {
|
|
1278
|
-
let oldCount = 0;
|
|
1279
|
-
let newCount = 0;
|
|
1280
|
-
for (const line of lines) {
|
|
1281
|
-
if (line.startsWith("+")) newCount++;
|
|
1282
|
-
else if (line.startsWith("-")) oldCount++;
|
|
1283
|
-
else {
|
|
1284
|
-
// context line (space prefix or empty)
|
|
1285
|
-
oldCount++;
|
|
1286
|
-
newCount++;
|
|
1287
|
-
}
|
|
1288
|
-
}
|
|
1289
|
-
return {
|
|
1290
|
-
header: `@@ -${oldStart},${oldCount} +${newStart},${newCount} @@`,
|
|
1291
|
-
oldCount,
|
|
1292
|
-
newCount,
|
|
1293
|
-
};
|
|
1294
|
-
}
|
|
1295
|
-
|
|
1296
|
-
function claudeBlockToUnifiedDiff(block: PatchBlock): string {
|
|
1297
|
-
const { type, path, body } = block;
|
|
1298
|
-
const bodyLines = body
|
|
1299
|
-
.split("\n")
|
|
1300
|
-
.filter((l) => l.trim() !== "" || l.startsWith(" "));
|
|
1301
|
-
|
|
1302
|
-
// Filter out empty trailing lines but keep intentional blank context lines
|
|
1303
|
-
const diffLines: string[] = [];
|
|
1304
|
-
for (const line of bodyLines) {
|
|
1305
|
-
// Skip Claude's @@ context search @@ markers (e.g. "@@ function foo() { @@")
|
|
1306
|
-
// These are NOT standard unified diff hunk headers (@@ -N,M +N,M @@)
|
|
1307
|
-
if (/^@@.*@@\s*$/.test(line)) continue;
|
|
1308
|
-
// Keep actual diff lines: +, -, or space-prefixed context
|
|
1309
|
-
if (line.startsWith("+") || line.startsWith("-") || line.startsWith(" ")) {
|
|
1310
|
-
diffLines.push(line);
|
|
1311
|
-
}
|
|
1312
|
-
}
|
|
1313
|
-
|
|
1314
|
-
let gitHeader: string;
|
|
1315
|
-
let fileHeader: string;
|
|
1316
|
-
switch (type) {
|
|
1317
|
-
case "Add":
|
|
1318
|
-
gitHeader = `diff --git a/${path} b/${path}\nnew file mode 100644`;
|
|
1319
|
-
fileHeader = `--- /dev/null\n+++ b/${path}`;
|
|
1320
|
-
break;
|
|
1321
|
-
case "Delete":
|
|
1322
|
-
gitHeader = `diff --git a/${path} b/${path}\ndeleted file mode 100644`;
|
|
1323
|
-
fileHeader = `--- a/${path}\n+++ /dev/null`;
|
|
1324
|
-
break;
|
|
1325
|
-
default:
|
|
1326
|
-
gitHeader = `diff --git a/${path} b/${path}`;
|
|
1327
|
-
fileHeader = `--- a/${path}\n+++ b/${path}`;
|
|
1328
|
-
}
|
|
1329
|
-
|
|
1330
|
-
if (diffLines.length === 0) return `${gitHeader}\n${fileHeader}`;
|
|
1331
|
-
|
|
1332
|
-
// For Add files, all lines should be additions
|
|
1333
|
-
if (type === "Add") {
|
|
1334
|
-
const addLines = diffLines.map((l) => (l.startsWith("+") ? l : `+${l}`));
|
|
1335
|
-
const count = addLines.length;
|
|
1336
|
-
return `${gitHeader}\n${fileHeader}\n@@ -0,0 +1,${count} @@\n${addLines.join("\n")}`;
|
|
1337
|
-
}
|
|
1338
|
-
|
|
1339
|
-
// For Delete files, all lines should be deletions
|
|
1340
|
-
if (type === "Delete") {
|
|
1341
|
-
const delLines = diffLines.map((l) => (l.startsWith("-") ? l : `-${l}`));
|
|
1342
|
-
const count = delLines.length;
|
|
1343
|
-
return `${gitHeader}\n${fileHeader}\n@@ -1,${count} +0,0 @@\n${delLines.join("\n")}`;
|
|
1344
|
-
}
|
|
1345
|
-
|
|
1346
|
-
// For Update files, synthesize proper hunk headers
|
|
1347
|
-
const { header: hunkHeader } = buildHunkHeader(diffLines, 1, 1);
|
|
1348
|
-
return `${gitHeader}\n${fileHeader}\n${hunkHeader}\n${diffLines.join("\n")}`;
|
|
1349
|
-
}
|
|
1350
|
-
|
|
1351
|
-
// ── Component ────────────────────────────────────────────────────────────
|
|
1352
|
-
|
|
1353
|
-
export const ApplyPatchDiff: ToolCallMessagePartComponent = memo(
|
|
1354
|
-
({ toolName, args, status }) => {
|
|
1355
|
-
const [open, setOpen] = useState(false);
|
|
1356
|
-
const patchText = str(args?.patchText);
|
|
1357
|
-
|
|
1358
|
-
const patchInfo = useMemo(() => getPatchInfo(patchText), [patchText]);
|
|
1359
|
-
|
|
1360
|
-
const unifiedPatch = useMemo(() => {
|
|
1361
|
-
if (!patchText) return "";
|
|
1362
|
-
const blocks = parseClaudePatchBlocks(patchText);
|
|
1363
|
-
return blocks.map(claudeBlockToUnifiedDiff).join("\n");
|
|
1364
|
-
}, [patchText]);
|
|
1365
|
-
|
|
1366
|
-
const isRunning = status?.type === "running";
|
|
1367
|
-
const isCancelled = isCancelledToolStatus(status);
|
|
1368
|
-
const hasDiff = unifiedPatch.length > 0 && !isRunning;
|
|
1369
|
-
|
|
1370
|
-
return (
|
|
1371
|
-
<Collapsible open={open} onOpenChange={setOpen}>
|
|
1372
|
-
<CollapsibleTrigger asChild>
|
|
1373
|
-
<button
|
|
1374
|
-
type="button"
|
|
1375
|
-
className="group text-muted-foreground hover:text-foreground flex w-full items-center gap-2 py-0.5 text-sm transition-colors"
|
|
1376
|
-
>
|
|
1377
|
-
<ToolStatusIcon
|
|
1378
|
-
status={status}
|
|
1379
|
-
completeIcon={<CheckIcon className="size-3 shrink-0" />}
|
|
1380
|
-
/>
|
|
1381
|
-
|
|
1382
|
-
<span
|
|
1383
|
-
className={cn(
|
|
1384
|
-
"flex items-center gap-1.5 truncate",
|
|
1385
|
-
isCancelled && "line-through opacity-50",
|
|
1386
|
-
)}
|
|
1387
|
-
>
|
|
1388
|
-
<span className="font-medium">{toolName}</span>
|
|
1389
|
-
{patchInfo.files.length > 0 && (
|
|
1390
|
-
<span className="opacity-60">
|
|
1391
|
-
{patchInfo.files.length === 1
|
|
1392
|
-
? patchInfo.files[0]
|
|
1393
|
-
: `${patchInfo.files.length} files`}
|
|
1394
|
-
</span>
|
|
1395
|
-
)}
|
|
1396
|
-
{(patchInfo.added > 0 || patchInfo.removed > 0) && !isRunning && (
|
|
1397
|
-
<span className="ml-0.5 flex items-center gap-1 font-mono text-xs">
|
|
1398
|
-
{patchInfo.added > 0 && (
|
|
1399
|
-
<span className="text-green-500">+{patchInfo.added}</span>
|
|
1400
|
-
)}
|
|
1401
|
-
{patchInfo.removed > 0 && (
|
|
1402
|
-
<span className="text-red-500">-{patchInfo.removed}</span>
|
|
1403
|
-
)}
|
|
1404
|
-
</span>
|
|
1405
|
-
)}
|
|
1406
|
-
</span>
|
|
1407
|
-
|
|
1408
|
-
{hasDiff && (
|
|
1409
|
-
<>
|
|
1410
|
-
<span className="border-muted-foreground/20 group-hover:border-muted-foreground/50 mt-0.5 min-w-4 flex-1 self-center border-b-[0.5px] transition-colors" />
|
|
1411
|
-
<ChevronRightIcon
|
|
1412
|
-
className={cn(
|
|
1413
|
-
"stroke-muted-foreground/60 group-hover:stroke-foreground/60 mt-0.5 size-3.75 shrink-0 transition-[transform,stroke]",
|
|
1414
|
-
open && "rotate-90",
|
|
1415
|
-
)}
|
|
1416
|
-
/>
|
|
1417
|
-
</>
|
|
1418
|
-
)}
|
|
1419
|
-
</button>
|
|
1420
|
-
</CollapsibleTrigger>
|
|
1421
|
-
|
|
1422
|
-
{hasDiff && (
|
|
1423
|
-
<CollapsibleContent className="data-[state=closed]:animate-collapsible-up data-[state=open]:animate-collapsible-down overflow-hidden data-[state=closed]:ease-out data-[state=open]:ease-in">
|
|
1424
|
-
<div className="mt-1 ml-5 overflow-hidden rounded-md border">
|
|
1425
|
-
<PatchDiff
|
|
1426
|
-
patch={unifiedPatch}
|
|
1427
|
-
options={{
|
|
1428
|
-
theme: { dark: "pierre-dark", light: "pierre-light" },
|
|
1429
|
-
diffStyle: "unified",
|
|
1430
|
-
overflow: "wrap",
|
|
1431
|
-
}}
|
|
1432
|
-
/>
|
|
1433
|
-
</div>
|
|
1434
|
-
</CollapsibleContent>
|
|
1435
|
-
)}
|
|
1436
|
-
</Collapsible>
|
|
1437
|
-
);
|
|
1438
|
-
},
|
|
1439
|
-
);
|
|
1440
|
-
ApplyPatchDiff.displayName = "ApplyPatchDiff";
|
|
1441
|
-
|
|
1442
|
-
```
|
|
1443
|
-
|
|
1444
|
-
## components/tools/tool-ui-bash.tsx
|
|
1445
|
-
|
|
1446
|
-
```tsx
|
|
1447
|
-
"use client";
|
|
1448
|
-
|
|
1449
|
-
import { memo, useState } from "react";
|
|
1450
|
-
import type { ToolCallMessagePartComponent } from "@assistant-ui/react";
|
|
1451
|
-
import { ChevronRightIcon, DollarSignIcon, XCircleIcon } from "lucide-react";
|
|
1452
|
-
import {
|
|
1453
|
-
Collapsible,
|
|
1454
|
-
CollapsibleContent,
|
|
1455
|
-
CollapsibleTrigger,
|
|
1456
|
-
} from "@/components/ui/radix/collapsible";
|
|
1457
|
-
import { cn } from "@/lib/utils";
|
|
1458
|
-
import {
|
|
1459
|
-
ToolStatusIcon,
|
|
1460
|
-
isCancelledToolStatus,
|
|
1461
|
-
truncate,
|
|
1462
|
-
} from "@/components/tools/tool-ui-shared";
|
|
1463
|
-
|
|
1464
|
-
const parseResult = (result: unknown) => {
|
|
1465
|
-
if (!result) return {};
|
|
1466
|
-
if (typeof result === "string") {
|
|
1467
|
-
try {
|
|
1468
|
-
const parsed = JSON.parse(result);
|
|
1469
|
-
if (typeof parsed === "object" && parsed !== null) return parsed;
|
|
1470
|
-
} catch {
|
|
1471
|
-
// plain text output
|
|
1472
|
-
}
|
|
1473
|
-
return { stdout: result };
|
|
1474
|
-
}
|
|
1475
|
-
if (typeof result === "object") return result as Record<string, unknown>;
|
|
1476
|
-
return {};
|
|
1477
|
-
};
|
|
1478
|
-
|
|
1479
|
-
export const BashTerminal: ToolCallMessagePartComponent = memo(
|
|
1480
|
-
({ args, result, status }) => {
|
|
1481
|
-
const [open, setOpen] = useState(false);
|
|
1482
|
-
|
|
1483
|
-
const command = typeof args?.command === "string" ? args.command : "";
|
|
1484
|
-
const description =
|
|
1485
|
-
typeof args?.description === "string" ? args.description : "";
|
|
1486
|
-
const isRunning = status?.type === "running";
|
|
1487
|
-
const isCancelled = isCancelledToolStatus(status);
|
|
1488
|
-
|
|
1489
|
-
const parsed = isRunning ? {} : parseResult(result);
|
|
1490
|
-
const stdout =
|
|
1491
|
-
typeof parsed.stdout === "string" ? parsed.stdout : undefined;
|
|
1492
|
-
const stderr =
|
|
1493
|
-
typeof parsed.stderr === "string" ? parsed.stderr : undefined;
|
|
1494
|
-
const exitCode = typeof parsed.exitCode === "number" ? parsed.exitCode : 0;
|
|
1495
|
-
const hasOutput = Boolean(stdout || stderr);
|
|
1496
|
-
const isError = !isRunning && exitCode !== 0;
|
|
1497
|
-
|
|
1498
|
-
const summaryText = description || truncate(command);
|
|
1499
|
-
|
|
1500
|
-
return (
|
|
1501
|
-
<Collapsible open={open} onOpenChange={setOpen}>
|
|
1502
|
-
<CollapsibleTrigger asChild>
|
|
1503
|
-
<button
|
|
1504
|
-
type="button"
|
|
1505
|
-
className="group text-muted-foreground hover:text-foreground flex w-full items-center gap-2 py-0.5 text-sm transition-colors"
|
|
1506
|
-
>
|
|
1507
|
-
{isRunning ? (
|
|
1508
|
-
<ToolStatusIcon status={status} />
|
|
1509
|
-
) : isError ? (
|
|
1510
|
-
<XCircleIcon className="text-destructive size-3 shrink-0" />
|
|
1511
|
-
) : (
|
|
1512
|
-
<ToolStatusIcon
|
|
1513
|
-
status={status}
|
|
1514
|
-
completeIcon={<DollarSignIcon className="size-3.5 shrink-0" />}
|
|
1515
|
-
/>
|
|
1516
|
-
)}
|
|
1517
|
-
|
|
1518
|
-
<span
|
|
1519
|
-
className={cn(
|
|
1520
|
-
"flex items-center gap-1.5 truncate",
|
|
1521
|
-
isCancelled && "line-through opacity-50",
|
|
1522
|
-
)}
|
|
1523
|
-
>
|
|
1524
|
-
<span className="font-medium">bash</span>
|
|
1525
|
-
{summaryText && <span className="opacity-60">{summaryText}</span>}
|
|
1526
|
-
</span>
|
|
1527
|
-
|
|
1528
|
-
{hasOutput && (
|
|
1529
|
-
<>
|
|
1530
|
-
<span className="border-muted-foreground/20 group-hover:border-muted-foreground/50 mt-0.5 min-w-4 flex-1 self-center border-b-[0.5px] transition-colors" />
|
|
1531
|
-
<ChevronRightIcon
|
|
1532
|
-
className={cn(
|
|
1533
|
-
"stroke-muted-foreground/60 group-hover:stroke-foreground/60 mt-0.5 size-3.75 shrink-0 transition-[transform,stroke]",
|
|
1534
|
-
open && "rotate-90",
|
|
1535
|
-
)}
|
|
1536
|
-
/>
|
|
1537
|
-
</>
|
|
1538
|
-
)}
|
|
1539
|
-
</button>
|
|
1540
|
-
</CollapsibleTrigger>
|
|
1541
|
-
|
|
1542
|
-
{hasOutput && (
|
|
1543
|
-
<CollapsibleContent className="data-[state=closed]:animate-collapsible-up data-[state=open]:animate-collapsible-down overflow-hidden data-[state=closed]:ease-out data-[state=open]:ease-in">
|
|
1544
|
-
<div className="bg-muted/50 mt-1 ml-5 max-h-96 overflow-y-auto rounded-md border p-3 font-mono text-xs">
|
|
1545
|
-
{command && (
|
|
1546
|
-
<div className="text-muted-foreground mb-2">$ {command}</div>
|
|
1547
|
-
)}
|
|
1548
|
-
{stdout && (
|
|
1549
|
-
<pre className="text-foreground wrap-break-word whitespace-pre-wrap">
|
|
1550
|
-
{stdout}
|
|
1551
|
-
</pre>
|
|
1552
|
-
)}
|
|
1553
|
-
{stderr && (
|
|
1554
|
-
<pre
|
|
1555
|
-
className={cn(
|
|
1556
|
-
"text-destructive wrap-break-word whitespace-pre-wrap",
|
|
1557
|
-
stdout && "mt-2",
|
|
1558
|
-
)}
|
|
1559
|
-
>
|
|
1560
|
-
{stderr}
|
|
1561
|
-
</pre>
|
|
1562
|
-
)}
|
|
1563
|
-
</div>
|
|
1564
|
-
</CollapsibleContent>
|
|
1565
|
-
)}
|
|
1566
|
-
</Collapsible>
|
|
1567
|
-
);
|
|
1568
|
-
},
|
|
1569
|
-
);
|
|
1570
|
-
BashTerminal.displayName = "BashTerminal";
|
|
1571
|
-
|
|
1572
|
-
```
|
|
1573
|
-
|
|
1574
|
-
## components/tools/tool-ui-inline.tsx
|
|
1575
|
-
|
|
1576
|
-
```tsx
|
|
1577
|
-
"use client";
|
|
1578
|
-
|
|
1579
|
-
import { memo, useMemo } from "react";
|
|
1580
|
-
import type { ToolCallMessagePartComponent } from "@assistant-ui/react";
|
|
1581
|
-
import { cn } from "@/lib/utils";
|
|
1582
|
-
import {
|
|
1583
|
-
ToolStatusIcon,
|
|
1584
|
-
basename,
|
|
1585
|
-
getPatchInfo,
|
|
1586
|
-
isCancelledToolStatus,
|
|
1587
|
-
str,
|
|
1588
|
-
truncate,
|
|
1589
|
-
type ToolCallStatusLike,
|
|
1590
|
-
} from "@/components/tools/tool-ui-shared";
|
|
1591
|
-
|
|
1592
|
-
// ── Helpers ────────────────────────────────────────────────────────────
|
|
1593
|
-
|
|
1594
|
-
const shortenPath = (filepath: string, depth = 2): string => {
|
|
1595
|
-
const parts = filepath.split("/").filter(Boolean);
|
|
1596
|
-
if (parts.length <= depth) return filepath;
|
|
1597
|
-
return parts.slice(-depth).join("/");
|
|
1598
|
-
};
|
|
1599
|
-
|
|
1600
|
-
// ── ToolCallShell ──────────────────────────────────────────────────────
|
|
1601
|
-
|
|
1602
|
-
const ToolCallShell = ({
|
|
1603
|
-
toolName,
|
|
1604
|
-
status,
|
|
1605
|
-
children,
|
|
1606
|
-
}: {
|
|
1607
|
-
toolName: string;
|
|
1608
|
-
status?: ToolCallStatusLike;
|
|
1609
|
-
children?: React.ReactNode;
|
|
1610
|
-
}) => {
|
|
1611
|
-
const isCancelled = isCancelledToolStatus(status);
|
|
1612
|
-
|
|
1613
|
-
return (
|
|
1614
|
-
<div className="text-muted-foreground flex items-center gap-2 py-0.5 text-sm">
|
|
1615
|
-
<ToolStatusIcon status={status} />
|
|
1616
|
-
<span
|
|
1617
|
-
className={cn(
|
|
1618
|
-
"flex items-center gap-1.5 truncate",
|
|
1619
|
-
isCancelled && "line-through opacity-50",
|
|
1620
|
-
)}
|
|
1621
|
-
>
|
|
1622
|
-
<span className="font-medium">{toolName}</span>
|
|
1623
|
-
{children}
|
|
1624
|
-
</span>
|
|
1625
|
-
</div>
|
|
1626
|
-
);
|
|
1627
|
-
};
|
|
1628
|
-
|
|
1629
|
-
// ── Factory for simple inline tools ────────────────────────────────────
|
|
1630
|
-
|
|
1631
|
-
const inlineTool = (
|
|
1632
|
-
argKeys: string | string[],
|
|
1633
|
-
format: (v: string) => string = truncate,
|
|
1634
|
-
): ToolCallMessagePartComponent => {
|
|
1635
|
-
const keys = Array.isArray(argKeys) ? argKeys : [argKeys];
|
|
1636
|
-
const Component: ToolCallMessagePartComponent = memo(
|
|
1637
|
-
({ toolName, args, status }) => {
|
|
1638
|
-
const value = keys.reduce<string>(
|
|
1639
|
-
(acc, key) => acc || str((args as Record<string, unknown>)?.[key]),
|
|
1640
|
-
"",
|
|
1641
|
-
);
|
|
1642
|
-
return (
|
|
1643
|
-
<ToolCallShell toolName={toolName} status={status}>
|
|
1644
|
-
{value && <span className="opacity-60">{format(value)}</span>}
|
|
1645
|
-
</ToolCallShell>
|
|
1646
|
-
);
|
|
1647
|
-
},
|
|
1648
|
-
);
|
|
1649
|
-
return Component;
|
|
1650
|
-
};
|
|
1651
|
-
|
|
1652
|
-
// ── Per-tool inline components ─────────────────────────────────────────
|
|
1653
|
-
|
|
1654
|
-
export const ReadInline = inlineTool(
|
|
1655
|
-
["file_path", "filePath", "path", "file"],
|
|
1656
|
-
(v) => truncate(shortenPath(v)),
|
|
1657
|
-
);
|
|
1658
|
-
ReadInline.displayName = "ReadInline";
|
|
1659
|
-
|
|
1660
|
-
export const EditInline = inlineTool("file_path", basename);
|
|
1661
|
-
EditInline.displayName = "EditInline";
|
|
1662
|
-
|
|
1663
|
-
export const WriteInline = inlineTool("file_path", basename);
|
|
1664
|
-
WriteInline.displayName = "WriteInline";
|
|
1665
|
-
|
|
1666
|
-
export const GrepInline = inlineTool("pattern");
|
|
1667
|
-
GrepInline.displayName = "GrepInline";
|
|
1668
|
-
|
|
1669
|
-
export const GlobInline = inlineTool("pattern");
|
|
1670
|
-
GlobInline.displayName = "GlobInline";
|
|
1671
|
-
|
|
1672
|
-
export const WebSearchInline = inlineTool("query");
|
|
1673
|
-
WebSearchInline.displayName = "WebSearchInline";
|
|
1674
|
-
|
|
1675
|
-
export const WebFetchInline = inlineTool("url");
|
|
1676
|
-
WebFetchInline.displayName = "WebFetchInline";
|
|
1677
|
-
|
|
1678
|
-
// Kept as a compact reference implementation even though the example
|
|
1679
|
-
// currently uses the richer diff viewer from tool-ui-apply-patch.tsx.
|
|
1680
|
-
export const ApplyPatchInline: ToolCallMessagePartComponent = memo(
|
|
1681
|
-
({ toolName, args, status }) => {
|
|
1682
|
-
const patchText = str(args?.patchText);
|
|
1683
|
-
const patchInfo = useMemo(() => getPatchInfo(patchText), [patchText]);
|
|
1684
|
-
const isRunning = status?.type === "running";
|
|
1685
|
-
|
|
1686
|
-
return (
|
|
1687
|
-
<ToolCallShell toolName={toolName} status={status}>
|
|
1688
|
-
{patchInfo.files.length > 0 && (
|
|
1689
|
-
<span className="opacity-60">
|
|
1690
|
-
{patchInfo.files.length === 1
|
|
1691
|
-
? patchInfo.files[0]
|
|
1692
|
-
: `${patchInfo.files.length} files`}
|
|
1693
|
-
</span>
|
|
1694
|
-
)}
|
|
1695
|
-
{(patchInfo.added > 0 || patchInfo.removed > 0) && !isRunning && (
|
|
1696
|
-
<span className="ml-0.5 flex items-center gap-1 font-mono text-xs">
|
|
1697
|
-
{patchInfo.added > 0 && (
|
|
1698
|
-
<span className="text-green-500">+{patchInfo.added}</span>
|
|
1699
|
-
)}
|
|
1700
|
-
{patchInfo.removed > 0 && (
|
|
1701
|
-
<span className="text-red-500">-{patchInfo.removed}</span>
|
|
1702
|
-
)}
|
|
1703
|
-
</span>
|
|
1704
|
-
)}
|
|
1705
|
-
</ToolCallShell>
|
|
1706
|
-
);
|
|
1707
|
-
},
|
|
1708
|
-
);
|
|
1709
|
-
ApplyPatchInline.displayName = "ApplyPatchInline";
|
|
1710
|
-
|
|
1711
|
-
// ── Fallback — generic, for unknown/MCP tools ─────────────────────────
|
|
1712
|
-
|
|
1713
|
-
const SUMMARY_KEYS = [
|
|
1714
|
-
"file_path",
|
|
1715
|
-
"path",
|
|
1716
|
-
"pattern",
|
|
1717
|
-
"command",
|
|
1718
|
-
"query",
|
|
1719
|
-
"glob",
|
|
1720
|
-
"url",
|
|
1721
|
-
] as const;
|
|
1722
|
-
|
|
1723
|
-
const ToolCallFallbackImpl: ToolCallMessagePartComponent = ({
|
|
1724
|
-
toolName,
|
|
1725
|
-
args,
|
|
1726
|
-
status,
|
|
1727
|
-
}) => {
|
|
1728
|
-
const summary = useMemo(() => {
|
|
1729
|
-
if (!args || typeof args !== "object") return "";
|
|
1730
|
-
for (const key of SUMMARY_KEYS) {
|
|
1731
|
-
const v = (args as Record<string, unknown>)[key];
|
|
1732
|
-
if (typeof v === "string" && v) return truncate(v);
|
|
1733
|
-
}
|
|
1734
|
-
return "";
|
|
1735
|
-
}, [args]);
|
|
1736
|
-
|
|
1737
|
-
return (
|
|
1738
|
-
<ToolCallShell toolName={toolName} status={status}>
|
|
1739
|
-
{summary && <span className="opacity-60">{summary}</span>}
|
|
1740
|
-
</ToolCallShell>
|
|
1741
|
-
);
|
|
1742
|
-
};
|
|
1743
|
-
|
|
1744
|
-
export const ToolCallFallback = memo(ToolCallFallbackImpl);
|
|
1745
|
-
ToolCallFallback.displayName = "ToolCallFallback";
|
|
1746
|
-
|
|
1747
|
-
```
|
|
1748
|
-
|
|
1749
|
-
## components/tools/tool-ui-shared.tsx
|
|
1750
|
-
|
|
1751
|
-
```tsx
|
|
1752
|
-
"use client";
|
|
1753
|
-
|
|
1754
|
-
import type { ReactNode } from "react";
|
|
1755
|
-
import {
|
|
1756
|
-
AlertCircleIcon,
|
|
1757
|
-
CheckIcon,
|
|
1758
|
-
LoaderIcon,
|
|
1759
|
-
XCircleIcon,
|
|
1760
|
-
} from "lucide-react";
|
|
1761
|
-
import { cn } from "@/lib/utils";
|
|
1762
|
-
|
|
1763
|
-
export type ToolCallStatusLike =
|
|
1764
|
-
| {
|
|
1765
|
-
type: string;
|
|
1766
|
-
reason?: string;
|
|
1767
|
-
}
|
|
1768
|
-
| undefined;
|
|
1769
|
-
|
|
1770
|
-
export const truncate = (value: string, max = 80): string =>
|
|
1771
|
-
value.length > max ? `${value.slice(0, max - 3)}...` : value;
|
|
1772
|
-
|
|
1773
|
-
export const str = (value: unknown): string =>
|
|
1774
|
-
typeof value === "string" ? value : "";
|
|
1775
|
-
|
|
1776
|
-
export const basename = (filepath: string): string => {
|
|
1777
|
-
const parts = filepath.split("/").filter(Boolean);
|
|
1778
|
-
return parts[parts.length - 1] ?? filepath;
|
|
1779
|
-
};
|
|
1780
|
-
|
|
1781
|
-
const unique = <T,>(items: readonly T[]) => [...new Set(items)];
|
|
1782
|
-
|
|
1783
|
-
export const getPatchInfo = (patchText: string) => {
|
|
1784
|
-
if (!patchText) return { files: [] as string[], added: 0, removed: 0 };
|
|
1785
|
-
|
|
1786
|
-
const files = unique(
|
|
1787
|
-
[...patchText.matchAll(/^\*\*\*\s+(?:Update|Add|Delete)\s+File:\s+(.+)$/gm)]
|
|
1788
|
-
.map((match) => basename(match[1]!.trim()))
|
|
1789
|
-
.filter(Boolean),
|
|
1790
|
-
);
|
|
1791
|
-
|
|
1792
|
-
let added = 0;
|
|
1793
|
-
let removed = 0;
|
|
1794
|
-
for (const line of patchText.split("\n")) {
|
|
1795
|
-
if (line.startsWith("+")) added += 1;
|
|
1796
|
-
else if (line.startsWith("-")) removed += 1;
|
|
1797
|
-
}
|
|
1798
|
-
|
|
1799
|
-
return { files, added, removed };
|
|
1800
|
-
};
|
|
1801
|
-
|
|
1802
|
-
export const isCancelledToolStatus = (status?: ToolCallStatusLike) =>
|
|
1803
|
-
status?.type === "incomplete" && status.reason === "cancelled";
|
|
1804
|
-
|
|
1805
|
-
export const ToolStatusIcon = ({
|
|
1806
|
-
status,
|
|
1807
|
-
completeIcon,
|
|
1808
|
-
}: {
|
|
1809
|
-
status?: ToolCallStatusLike;
|
|
1810
|
-
completeIcon?: ReactNode;
|
|
1811
|
-
}) => {
|
|
1812
|
-
const statusType = status?.type ?? "complete";
|
|
1813
|
-
const isCancelled = isCancelledToolStatus(status);
|
|
1814
|
-
|
|
1815
|
-
if (statusType === "running") {
|
|
1816
|
-
return <LoaderIcon className="size-3 shrink-0 animate-spin" />;
|
|
1817
|
-
}
|
|
1818
|
-
|
|
1819
|
-
if (statusType === "requires-action") {
|
|
1820
|
-
return <AlertCircleIcon className="size-3 shrink-0 text-amber-600" />;
|
|
1821
|
-
}
|
|
1822
|
-
|
|
1823
|
-
if (statusType === "incomplete") {
|
|
1824
|
-
return (
|
|
1825
|
-
<XCircleIcon
|
|
1826
|
-
className={cn("size-3 shrink-0", !isCancelled && "text-destructive")}
|
|
1827
|
-
/>
|
|
1828
|
-
);
|
|
1829
|
-
}
|
|
1830
|
-
|
|
1831
|
-
return completeIcon ?? <CheckIcon className="size-3 shrink-0" />;
|
|
1832
|
-
};
|
|
1833
|
-
|
|
1834
|
-
```
|
|
1835
|
-
|
|
1836
|
-
## components/tools/toolkit.tsx
|
|
1837
|
-
|
|
1838
|
-
```tsx
|
|
1839
|
-
"use generative";
|
|
1840
|
-
|
|
1841
|
-
import { defineToolkit, externalTool } from "@assistant-ui/react";
|
|
1842
|
-
import {
|
|
1843
|
-
ApplyPatchTool,
|
|
1844
|
-
AskQuestionTool,
|
|
1845
|
-
BashTool,
|
|
1846
|
-
EditTool,
|
|
1847
|
-
GlobTool,
|
|
1848
|
-
GrepTool,
|
|
1849
|
-
ReadTool,
|
|
1850
|
-
WebFetchTool,
|
|
1851
|
-
WebSearchTool,
|
|
1852
|
-
WriteTool,
|
|
1853
|
-
} from "./opencode-tools";
|
|
1854
|
-
|
|
1855
|
-
export default defineToolkit({
|
|
1856
|
-
read: { execute: externalTool(), render: ReadTool },
|
|
1857
|
-
edit: { execute: externalTool(), render: EditTool },
|
|
1858
|
-
write: { execute: externalTool(), render: WriteTool },
|
|
1859
|
-
bash: { execute: externalTool(), render: BashTool },
|
|
1860
|
-
grep: { execute: externalTool(), render: GrepTool },
|
|
1861
|
-
glob: { execute: externalTool(), render: GlobTool },
|
|
1862
|
-
webSearch: { execute: externalTool(), render: WebSearchTool },
|
|
1863
|
-
webFetch: { execute: externalTool(), render: WebFetchTool },
|
|
1864
|
-
apply_patch: { execute: externalTool(), render: ApplyPatchTool },
|
|
1865
|
-
ask_question: { execute: externalTool(), render: AskQuestionTool },
|
|
1866
|
-
request_user_input: { execute: externalTool(), render: AskQuestionTool },
|
|
1867
|
-
requestUserInput: { execute: externalTool(), render: AskQuestionTool },
|
|
1868
|
-
});
|
|
1869
|
-
|
|
1870
|
-
```
|
|
1871
|
-
|
|
1872
|
-
## next.config.ts
|
|
1873
|
-
|
|
1874
|
-
```typescript
|
|
1875
|
-
import { withAui } from "@assistant-ui/next";
|
|
1876
|
-
import type { NextConfig } from "next";
|
|
1877
|
-
|
|
1878
|
-
const nextConfig: NextConfig = {/* config options here */};
|
|
1879
|
-
|
|
1880
|
-
export default withAui(nextConfig);
|
|
1881
|
-
|
|
1882
|
-
```
|
|
1883
|
-
|
|
1884
|
-
## package.json
|
|
1885
|
-
|
|
1886
|
-
```json
|
|
1887
|
-
{
|
|
1888
|
-
"name": "with-opencode",
|
|
1889
|
-
"version": "0.0.0",
|
|
1890
|
-
"private": true,
|
|
1891
|
-
"type": "module",
|
|
1892
|
-
"scripts": {
|
|
1893
|
-
"dev": "next dev",
|
|
1894
|
-
"build": "next build",
|
|
1895
|
-
"start": "next start"
|
|
1896
|
-
},
|
|
1897
|
-
"dependencies": {
|
|
1898
|
-
"@assistant-ui/react": "workspace:*",
|
|
1899
|
-
"@assistant-ui/react-markdown": "workspace:*",
|
|
1900
|
-
"@assistant-ui/react-opencode": "workspace:*",
|
|
1901
|
-
"@assistant-ui/ui": "workspace:*",
|
|
1902
|
-
"@pierre/diffs": "^1.3.5",
|
|
1903
|
-
"class-variance-authority": "^0.7.1",
|
|
1904
|
-
"clsx": "^2.1.1",
|
|
1905
|
-
"lucide-react": "^1.33.0",
|
|
1906
|
-
"next": "^16.3.2",
|
|
1907
|
-
"react": "^19.2.8",
|
|
1908
|
-
"react-dom": "^19.2.8",
|
|
1909
|
-
"tailwind-merge": "^3.6.0",
|
|
1910
|
-
"zod": "^4.4.3"
|
|
1911
|
-
},
|
|
1912
|
-
"devDependencies": {
|
|
1913
|
-
"@assistant-ui/next": "workspace:*",
|
|
1914
|
-
"@assistant-ui/x-buildutils": "workspace:*",
|
|
1915
|
-
"@tailwindcss/postcss": "^4.3.3",
|
|
1916
|
-
"@types/node": "^26.2.0",
|
|
1917
|
-
"@types/react": "^19.2.18",
|
|
1918
|
-
"@types/react-dom": "^19.2.5",
|
|
1919
|
-
"postcss": "^8.5.26",
|
|
1920
|
-
"tailwindcss": "^4.3.3",
|
|
1921
|
-
"tw-animate-css": "^1.4.0",
|
|
1922
|
-
"typescript": "^7.0.2"
|
|
1923
|
-
}
|
|
1924
|
-
}
|
|
1925
|
-
|
|
1926
|
-
```
|
|
1927
|
-
|
|
1928
|
-
## README.md
|
|
1929
|
-
|
|
1930
|
-
```markdown
|
|
1931
|
-
# with-opencode
|
|
1932
|
-
|
|
1933
|
-
> [!NOTE] This integration/example is experimental, there may be bugs or issues.
|
|
1934
|
-
|
|
1935
|
-
Next.js example for the OpenCode runtime adapter built with assistant-ui.
|
|
1936
|
-
|
|
1937
|
-
Set `NEXT_PUBLIC_OPENCODE_BASE_URL` if your OpenCode server is not running on
|
|
1938
|
-
`http://localhost:4096`.
|
|
1939
|
-
|
|
1940
|
-
```
|
|
1941
|
-
|
|
1942
|
-
## tsconfig.json
|
|
1943
|
-
|
|
1944
|
-
```json
|
|
1945
|
-
{
|
|
1946
|
-
"extends": "@assistant-ui/x-buildutils/ts/next",
|
|
1947
|
-
"compilerOptions": {
|
|
1948
|
-
"paths": {
|
|
1949
|
-
"@/*": ["./*"],
|
|
1950
|
-
"@assistant-ui/react-opencode": [
|
|
1951
|
-
"../../packages/react-opencode/src/index.ts"
|
|
1952
|
-
],
|
|
1953
|
-
"@/components/assistant-ui/*": [
|
|
1954
|
-
"../../packages/ui/src/components/assistant-ui/*"
|
|
1955
|
-
],
|
|
1956
|
-
"@/components/icons/*": ["../../packages/ui/src/components/icons/*"],
|
|
1957
|
-
"@/components/ui/*": [
|
|
1958
|
-
"../../packages/ui/src/components/ui/base/*",
|
|
1959
|
-
"../../packages/ui/src/components/ui/radix/*"
|
|
1960
|
-
],
|
|
1961
|
-
"@/components/ui/radix/*": [
|
|
1962
|
-
"../../packages/ui/src/components/ui/radix/*"
|
|
1963
|
-
],
|
|
1964
|
-
"@/hooks/*": ["../../packages/ui/src/hooks/*"],
|
|
1965
|
-
"@/lib/utils": ["../../packages/ui/src/lib/utils"],
|
|
1966
|
-
"@assistant-ui/ui/*": ["../../packages/ui/src/*"]
|
|
1967
|
-
}
|
|
1968
|
-
},
|
|
1969
|
-
"include": ["**/*.ts", "**/*.tsx", ".next/types/**/*.ts"],
|
|
1970
|
-
"exclude": ["node_modules"]
|
|
1971
|
-
}
|
|
1972
|
-
|
|
1973
|
-
```
|
|
1974
|
-
|