commit e30e75b5d410f02154c305000118cb6212831f19 Author: wehub-resource-sync Date: Mon Jul 13 13:40:13 2026 +0800 chore: import upstream snapshot with attribution diff --git a/.changeset/README.md b/.changeset/README.md new file mode 100644 index 0000000..e5b6d8d --- /dev/null +++ b/.changeset/README.md @@ -0,0 +1,8 @@ +# Changesets + +Hello and welcome! This folder has been automatically generated by `@changesets/cli`, a build tool that works +with multi-package repos, or single-package repos to help you version and publish your code. You can +find the full documentation for it [in our repository](https://github.com/changesets/changesets) + +We have a quick list of common questions to get you started engaging with this project in +[our documentation](https://github.com/changesets/changesets/blob/main/docs/common-questions.md) diff --git a/.changeset/adk-invalid-json-error.md b/.changeset/adk-invalid-json-error.md new file mode 100644 index 0000000..11c91dc --- /dev/null +++ b/.changeset/adk-invalid-json-error.md @@ -0,0 +1,5 @@ +--- +"@assistant-ui/react-google-adk": patch +--- + +fix: clarify invalid ADK proxy request body errors diff --git a/.changeset/ag-ui-auto-cancel-pending-tool-calls.md b/.changeset/ag-ui-auto-cancel-pending-tool-calls.md new file mode 100644 index 0000000..021ae27 --- /dev/null +++ b/.changeset/ag-ui-auto-cancel-pending-tool-calls.md @@ -0,0 +1,5 @@ +--- +"@assistant-ui/react-ag-ui": patch +--- + +feat: auto-cancel pending client-side tool calls on send, edit, and reload (opt out with autoCancelPendingToolCalls: false) diff --git a/.changeset/ai-sdk-direct-file-parts.md b/.changeset/ai-sdk-direct-file-parts.md new file mode 100644 index 0000000..b54c550 --- /dev/null +++ b/.changeset/ai-sdk-direct-file-parts.md @@ -0,0 +1,5 @@ +--- +"@assistant-ui/react-ai-sdk": patch +--- + +fix: preserve direct file parts in AI SDK message creation diff --git a/.changeset/ai-sdk-image-media-type.md b/.changeset/ai-sdk-image-media-type.md new file mode 100644 index 0000000..ca129ac --- /dev/null +++ b/.changeset/ai-sdk-image-media-type.md @@ -0,0 +1,5 @@ +--- +"@assistant-ui/react-ai-sdk": patch +--- + +fix: preserve image media types when creating AI SDK messages diff --git a/.changeset/api-ref-resumable-jsdoc-links.md b/.changeset/api-ref-resumable-jsdoc-links.md new file mode 100644 index 0000000..87d5db7 --- /dev/null +++ b/.changeset/api-ref-resumable-jsdoc-links.md @@ -0,0 +1,5 @@ +--- +"@assistant-ui/react-ai-sdk": patch +--- + +docs: move the resumable streams cross-links into jsdoc so the api-reference generator preserves them diff --git a/.changeset/assistant-modal-trigger-render.md b/.changeset/assistant-modal-trigger-render.md new file mode 100644 index 0000000..c557bda --- /dev/null +++ b/.changeset/assistant-modal-trigger-render.md @@ -0,0 +1,5 @@ +--- +"@assistant-ui/react": patch +--- + +feat: render prop support for AssistantModalPrimitive Trigger, Anchor, and Content diff --git a/.changeset/attachment-upload-failure-reason.md b/.changeset/attachment-upload-failure-reason.md new file mode 100644 index 0000000..18f81e3 --- /dev/null +++ b/.changeset/attachment-upload-failure-reason.md @@ -0,0 +1,5 @@ +--- +"@assistant-ui/core": patch +--- + +feat: surface the upload failure reason on failed attachment status diff --git a/.changeset/cache-doc-listings.md b/.changeset/cache-doc-listings.md new file mode 100644 index 0000000..a6606f8 --- /dev/null +++ b/.changeset/cache-doc-listings.md @@ -0,0 +1,5 @@ +--- +"@assistant-ui/mcp-docs-server": patch +--- + +perf: cache the docs and examples file listings diff --git a/.changeset/chat-runtime-resume-error-hook.md b/.changeset/chat-runtime-resume-error-hook.md new file mode 100644 index 0000000..80b1c59 --- /dev/null +++ b/.changeset/chat-runtime-resume-error-hook.md @@ -0,0 +1,5 @@ +--- +"@assistant-ui/react-ai-sdk": patch +--- + +feat: expose chat runtime resume errors diff --git a/.changeset/cli-mcp-config-error.md b/.changeset/cli-mcp-config-error.md new file mode 100644 index 0000000..0072992 --- /dev/null +++ b/.changeset/cli-mcp-config-error.md @@ -0,0 +1,5 @@ +--- +"assistant-ui": patch +--- + +fix: clarify MCP install config parse errors diff --git a/.changeset/cli-resumable-stream-example.md b/.changeset/cli-resumable-stream-example.md new file mode 100644 index 0000000..13f6eec --- /dev/null +++ b/.changeset/cli-resumable-stream-example.md @@ -0,0 +1,5 @@ +--- +"assistant-ui": patch +--- + +cli: register the `with-resumable-stream` example so `npx assistant-ui create -e with-resumable-stream` resolves instead of failing with "Unknown example". diff --git a/.changeset/cli-strip-workspace-paths.md b/.changeset/cli-strip-workspace-paths.md new file mode 100644 index 0000000..c52e0ad --- /dev/null +++ b/.changeset/cli-strip-workspace-paths.md @@ -0,0 +1,7 @@ +--- +"assistant-ui": patch +--- + +fix: strip every workspace path alias when materializing a template + +the create flow removed monorepo tsconfig aliases by a fixed key list, so newer keys such as @/components/ui/base/* survived into scaffolded projects as dangling entries; any entry whose target points into packages/ui/ is now stripped as well. diff --git a/.changeset/cli-update-package-json-errors.md b/.changeset/cli-update-package-json-errors.md new file mode 100644 index 0000000..fb5114d --- /dev/null +++ b/.changeset/cli-update-package-json-errors.md @@ -0,0 +1,5 @@ +--- +"assistant-ui": patch +--- + +fix: clarify assistant-ui update package.json parse errors diff --git a/.changeset/cloud-ai-sdk-v7.md b/.changeset/cloud-ai-sdk-v7.md new file mode 100644 index 0000000..8ef1f7b --- /dev/null +++ b/.changeset/cloud-ai-sdk-v7.md @@ -0,0 +1,5 @@ +--- +"@assistant-ui/cloud-ai-sdk": patch +--- + +feat: widen AI SDK peer range to allow v7 alongside v6 (ai@^6 || ^7, @ai-sdk/react@^3 || ^4) diff --git a/.changeset/cloud-api-error-status.md b/.changeset/cloud-api-error-status.md new file mode 100644 index 0000000..89feec1 --- /dev/null +++ b/.changeset/cloud-api-error-status.md @@ -0,0 +1,5 @@ +--- +"assistant-cloud": patch +--- + +feat: expose CloudAPIError with the HTTP status on failed requests diff --git a/.changeset/cloud-attachment-upload-error-log.md b/.changeset/cloud-attachment-upload-error-log.md new file mode 100644 index 0000000..513f69c --- /dev/null +++ b/.changeset/cloud-attachment-upload-error-log.md @@ -0,0 +1,5 @@ +--- +"@assistant-ui/core": patch +--- + +fix(core): log the swallowed CloudFileAttachmentAdapter upload error instead of discarding it diff --git a/.changeset/cloud-attachment-upload-status.md b/.changeset/cloud-attachment-upload-status.md new file mode 100644 index 0000000..997728b --- /dev/null +++ b/.changeset/cloud-attachment-upload-status.md @@ -0,0 +1,5 @@ +--- +"@assistant-ui/core": patch +--- + +fix(core): CloudFileAttachmentAdapter now fails uploads that return an HTTP error status instead of attaching a dead link diff --git a/.changeset/cloud-empty-success-responses.md b/.changeset/cloud-empty-success-responses.md new file mode 100644 index 0000000..b03c7fd --- /dev/null +++ b/.changeset/cloud-empty-success-responses.md @@ -0,0 +1,5 @@ +--- +"assistant-cloud": patch +--- + +fix: handle empty successful Cloud API responses diff --git a/.changeset/cloud-history-attachments.md b/.changeset/cloud-history-attachments.md new file mode 100644 index 0000000..69584e8 --- /dev/null +++ b/.changeset/cloud-history-attachments.md @@ -0,0 +1,6 @@ +--- +"@assistant-ui/core": patch +"@assistant-ui/react": patch +--- + +fix: preserve user attachments in Cloud history diff --git a/.changeset/cloud-initialize-auth.md b/.changeset/cloud-initialize-auth.md new file mode 100644 index 0000000..641e0a0 --- /dev/null +++ b/.changeset/cloud-initialize-auth.md @@ -0,0 +1,5 @@ +--- +"assistant-cloud": patch +--- + +fix: await auth headers when initializing Assistant Cloud auth diff --git a/.changeset/composer-compact-mode.md b/.changeset/composer-compact-mode.md new file mode 100644 index 0000000..5fb0e90 --- /dev/null +++ b/.changeset/composer-compact-mode.md @@ -0,0 +1,5 @@ +--- +"@assistant-ui/react": patch +--- + +feat: add compact mode to ComposerPrimitive.Root diff --git a/.changeset/composer-send-restore-on-upload-failure.md b/.changeset/composer-send-restore-on-upload-failure.md new file mode 100644 index 0000000..efb95c7 --- /dev/null +++ b/.changeset/composer-send-restore-on-upload-failure.md @@ -0,0 +1,5 @@ +--- +"@assistant-ui/core": patch +--- + +fix: restore composer text and attachments when an attachment upload fails during send diff --git a/.changeset/config.json b/.changeset/config.json new file mode 100644 index 0000000..44eefd4 --- /dev/null +++ b/.changeset/config.json @@ -0,0 +1,16 @@ +{ + "$schema": "https://unpkg.com/@changesets/config@3.0.1/schema.json", + "changelog": [ + "@assistant-ui/x-changelog", + { + "repo": "assistant-ui/assistant-ui" + } + ], + "access": "public", + "privatePackages": { + "version": false + }, + "___experimentalUnsafeOptions_WILL_CHANGE_IN_PATCH": { + "onlyUpdatePeerDependentsWhenOutOfRange": true + } +} diff --git a/.changeset/data-stream-protocol-detection.md b/.changeset/data-stream-protocol-detection.md new file mode 100644 index 0000000..470a133 --- /dev/null +++ b/.changeset/data-stream-protocol-detection.md @@ -0,0 +1,6 @@ +--- +"@assistant-ui/react-data-stream": patch +"assistant-stream": patch +--- + +feat: detect data stream protocol from response headers diff --git a/.changeset/devtools-windows-css-build.md b/.changeset/devtools-windows-css-build.md new file mode 100644 index 0000000..7d184f0 --- /dev/null +++ b/.changeset/devtools-windows-css-build.md @@ -0,0 +1,5 @@ +--- +"@assistant-ui/react-devtools": patch +--- + +fix: resolve the tailwind cli entry explicitly so pnpm install completes on windows diff --git a/.changeset/diff-hunk-index.md b/.changeset/diff-hunk-index.md new file mode 100644 index 0000000..78c33fd --- /dev/null +++ b/.changeset/diff-hunk-index.md @@ -0,0 +1,5 @@ +--- +"@assistant-ui/react-ink": patch +--- + +feat: track hunkIndex on parsed diff lines diff --git a/.changeset/doctor-pnpm-duplicates.md b/.changeset/doctor-pnpm-duplicates.md new file mode 100644 index 0000000..2c3f2de --- /dev/null +++ b/.changeset/doctor-pnpm-duplicates.md @@ -0,0 +1,5 @@ +--- +"assistant-ui": patch +--- + +fix(cli): doctor now detects duplicate assistant-ui packages in pnpm node_modules layouts diff --git a/.changeset/exported-toolkit-fragments.md b/.changeset/exported-toolkit-fragments.md new file mode 100644 index 0000000..48556a2 --- /dev/null +++ b/.changeset/exported-toolkit-fragments.md @@ -0,0 +1,5 @@ +--- +"@assistant-ui/x-generative-compiler": patch +--- + +fix: recognize exported same-file toolkit fragments in toolkit spreads diff --git a/.changeset/fix-data-stream-tool.md b/.changeset/fix-data-stream-tool.md new file mode 100644 index 0000000..bdf9faa --- /dev/null +++ b/.changeset/fix-data-stream-tool.md @@ -0,0 +1,4 @@ +--- +"@assistant-ui/react-data-stream": patch +--- +fix: include current assistant message in toLanguageModelMessages to properly send frontend tool results \ No newline at end of file diff --git a/.changeset/fix-ui-message-stream-wire-normalization.md b/.changeset/fix-ui-message-stream-wire-normalization.md new file mode 100644 index 0000000..8e58ada --- /dev/null +++ b/.changeset/fix-ui-message-stream-wire-normalization.md @@ -0,0 +1,5 @@ +--- +"assistant-stream": patch +--- + +fix: accept the AI SDK v5+ `delta` text chunks, `source-url`, flat `file`, and bare lifecycle chunks in UIMessageStreamDecoder diff --git a/.changeset/frontend-tools-type.md b/.changeset/frontend-tools-type.md new file mode 100644 index 0000000..4df317c --- /dev/null +++ b/.changeset/frontend-tools-type.md @@ -0,0 +1,5 @@ +--- +"@assistant-ui/react-ai-sdk": patch +--- + +feat: export a FrontendTools type for typing uploaded frontend tool definitions. diff --git a/.changeset/fuzzy-olives-see.md b/.changeset/fuzzy-olives-see.md new file mode 100644 index 0000000..64e6e9f --- /dev/null +++ b/.changeset/fuzzy-olives-see.md @@ -0,0 +1,5 @@ +--- +"@assistant-ui/react-native": patch +--- + +fix(react-native): show composer scrollbar at max height diff --git a/.changeset/generative-ui-key-jsx.md b/.changeset/generative-ui-key-jsx.md new file mode 100644 index 0000000..ae4a288 --- /dev/null +++ b/.changeset/generative-ui-key-jsx.md @@ -0,0 +1,5 @@ +--- +"@assistant-ui/react-generative-ui": patch +--- + +fix: serialize a model-provided $key as the JSX key attribute diff --git a/.changeset/generative-ui-key-schema.md b/.changeset/generative-ui-key-schema.md new file mode 100644 index 0000000..d3dec0a --- /dev/null +++ b/.changeset/generative-ui-key-schema.md @@ -0,0 +1,5 @@ +--- +"@assistant-ui/react-generative-ui": patch +--- + +fix: expose stable generative UI keys in the present schema diff --git a/.changeset/green-walls-protect.md b/.changeset/green-walls-protect.md new file mode 100644 index 0000000..55f82ce --- /dev/null +++ b/.changeset/green-walls-protect.md @@ -0,0 +1,5 @@ +--- +"@assistant-ui/react-data-stream": patch +--- + +feat: detect UI message stream protocol from response headers diff --git a/.changeset/langchain-file-url-source-blocks.md b/.changeset/langchain-file-url-source-blocks.md new file mode 100644 index 0000000..bf5c3b7 --- /dev/null +++ b/.changeset/langchain-file-url-source-blocks.md @@ -0,0 +1,6 @@ +--- +"@assistant-ui/react-langgraph": patch +"@assistant-ui/react-langchain": patch +--- + +fix: emit url source blocks for http(s) file attachment data instead of mislabeling it base64 diff --git a/.changeset/langgraph-serialize-runs.md b/.changeset/langgraph-serialize-runs.md new file mode 100644 index 0000000..bc8c1bb --- /dev/null +++ b/.changeset/langgraph-serialize-runs.md @@ -0,0 +1,5 @@ +--- +"@assistant-ui/react-langgraph": patch +--- + +fix: serialize runs in useLangGraphRuntime so a frontend tool result resume never overlaps the run that emitted the tool call. isRunning stays true across the seam, cancel stops the whole turn and drops the queued resume, and a run that aborts or errors drops the sends queued behind it. diff --git a/.changeset/local-thread-storage-validation.md b/.changeset/local-thread-storage-validation.md new file mode 100644 index 0000000..796518c --- /dev/null +++ b/.changeset/local-thread-storage-validation.md @@ -0,0 +1,5 @@ +--- +"@assistant-ui/core": patch +--- + +fix: ignore malformed local thread storage diff --git a/.changeset/mcp-app-host-error-context.md b/.changeset/mcp-app-host-error-context.md new file mode 100644 index 0000000..3c3e279 --- /dev/null +++ b/.changeset/mcp-app-host-error-context.md @@ -0,0 +1,5 @@ +--- +"@assistant-ui/react": patch +--- + +fix: add MCP App host request error context diff --git a/.changeset/mcp-custom-server-storage.md b/.changeset/mcp-custom-server-storage.md new file mode 100644 index 0000000..8374e9e --- /dev/null +++ b/.changeset/mcp-custom-server-storage.md @@ -0,0 +1,5 @@ +--- +"@assistant-ui/react-mcp": patch +--- + +fix: ignore malformed persisted MCP custom servers diff --git a/.changeset/mcp-docs-structured-content.md b/.changeset/mcp-docs-structured-content.md new file mode 100644 index 0000000..0674baf --- /dev/null +++ b/.changeset/mcp-docs-structured-content.md @@ -0,0 +1,5 @@ +--- +"@assistant-ui/mcp-docs-server": patch +--- + +feat: return structuredContent from the docs, examples, and search tools diff --git a/.changeset/mcp-docs-title-excerpt.md b/.changeset/mcp-docs-title-excerpt.md new file mode 100644 index 0000000..fe26f5e --- /dev/null +++ b/.changeset/mcp-docs-title-excerpt.md @@ -0,0 +1,5 @@ +--- +"@assistant-ui/mcp-docs-server": patch +--- + +feat: surface doc title and excerpt on assistantUIDocs responses diff --git a/.changeset/mcp-oauth-callback-errors.md b/.changeset/mcp-oauth-callback-errors.md new file mode 100644 index 0000000..bc74597 --- /dev/null +++ b/.changeset/mcp-oauth-callback-errors.md @@ -0,0 +1,5 @@ +--- +"@assistant-ui/react-mcp": patch +--- + +fix: clarify MCP OAuth callback failures diff --git a/.changeset/mcp-tool-name-prefixes.md b/.changeset/mcp-tool-name-prefixes.md new file mode 100644 index 0000000..cb088da --- /dev/null +++ b/.changeset/mcp-tool-name-prefixes.md @@ -0,0 +1,9 @@ +--- +"@assistant-ui/core": patch +"@assistant-ui/react": patch +"@assistant-ui/react-ai-sdk": patch +"@assistant-ui/react-ink": patch +"@assistant-ui/react-native": patch +--- + +feat: support prefixed MCP toolkit tool names diff --git a/.changeset/mcp-toolkit-disabled-entries.md b/.changeset/mcp-toolkit-disabled-entries.md new file mode 100644 index 0000000..fc8023e --- /dev/null +++ b/.changeset/mcp-toolkit-disabled-entries.md @@ -0,0 +1,8 @@ +--- +"@assistant-ui/core": patch +"@assistant-ui/react": patch +"@assistant-ui/react-native": patch +"@assistant-ui/react-ink": patch +--- + +feat: support disabled MCP toolkit entries diff --git a/.changeset/mcp-toolkit-disabled-tools.md b/.changeset/mcp-toolkit-disabled-tools.md new file mode 100644 index 0000000..004a286 --- /dev/null +++ b/.changeset/mcp-toolkit-disabled-tools.md @@ -0,0 +1,9 @@ +--- +"@assistant-ui/core": patch +"@assistant-ui/react": patch +"@assistant-ui/react-ai-sdk": patch +"@assistant-ui/react-ink": patch +"@assistant-ui/react-native": patch +--- + +feat: support disabled MCP toolkit tools diff --git a/.changeset/mcp-toolkit-error-context.md b/.changeset/mcp-toolkit-error-context.md new file mode 100644 index 0000000..f44ead3 --- /dev/null +++ b/.changeset/mcp-toolkit-error-context.md @@ -0,0 +1,5 @@ +--- +"@assistant-ui/react-ai-sdk": patch +--- + +feat: add MCP toolkit entry names to connection and tool-listing errors diff --git a/.changeset/pink-bugs-tap.md b/.changeset/pink-bugs-tap.md new file mode 100644 index 0000000..834d854 --- /dev/null +++ b/.changeset/pink-bugs-tap.md @@ -0,0 +1,38 @@ +--- +"@assistant-ui/agent-launcher": patch +"assistant-stream": patch +"assistant-ui": patch +"@assistant-ui/cloud-ai-sdk": patch +"assistant-cloud": patch +"@assistant-ui/core": patch +"@assistant-ui/eve": patch +"heat-graph": patch +"@assistant-ui/mcp-docs-server": patch +"@assistant-ui/metro": patch +"@assistant-ui/next": patch +"@assistant-ui/react-a2a": patch +"@assistant-ui/react-data-stream": patch +"@assistant-ui/react-devtools": patch +"@assistant-ui/react-google-adk": patch +"@assistant-ui/react-hook-form": patch +"@assistant-ui/react-ink-markdown": patch +"@assistant-ui/react-ink": patch +"@assistant-ui/react-langchain": patch +"@assistant-ui/react-langgraph": patch +"@assistant-ui/react-lexical": patch +"@assistant-ui/react-markdown": patch +"@assistant-ui/react-mcp": patch +"@assistant-ui/react-native": patch +"@assistant-ui/react-o11y": patch +"@assistant-ui/react-opencode": patch +"@assistant-ui/react-pi": patch +"@assistant-ui/react": patch +"safe-content-frame": patch +"@assistant-ui/tap": patch +"tw-shimmer": patch +"@assistant-ui/vite": patch +"@assistant-ui/x-buildutils": patch +"@assistant-ui/x-generative-compiler": patch +--- + +chore: update dependencies diff --git a/.changeset/preserve-provider-metadata-parts.md b/.changeset/preserve-provider-metadata-parts.md new file mode 100644 index 0000000..a227238 --- /dev/null +++ b/.changeset/preserve-provider-metadata-parts.md @@ -0,0 +1,12 @@ +--- +"@assistant-ui/core": patch +"@assistant-ui/react": patch +"@assistant-ui/react-ai-sdk": patch +--- + +feat: preserve providerMetadata on text, reasoning, and tool-call message parts + +The AI SDK message converter already kept `providerMetadata` on source parts but +dropped it on text, reasoning, and tool-call parts (where the AI SDK surfaces it +as `callProviderMetadata`). Provider- or app-scoped metadata such as agent +attribution now survives the conversion to assistant-ui messages. diff --git a/.changeset/react-ai-sdk-add-tool-output.md b/.changeset/react-ai-sdk-add-tool-output.md new file mode 100644 index 0000000..e3c548f --- /dev/null +++ b/.changeset/react-ai-sdk-add-tool-output.md @@ -0,0 +1,5 @@ +--- +"@assistant-ui/react-ai-sdk": patch +--- + +refactor: use addToolOutput instead of the deprecated addToolResult for tool results diff --git a/.changeset/react-ai-sdk-converter-guards.md b/.changeset/react-ai-sdk-converter-guards.md new file mode 100644 index 0000000..8d7590a --- /dev/null +++ b/.changeset/react-ai-sdk-converter-guards.md @@ -0,0 +1,5 @@ +--- +"@assistant-ui/react-ai-sdk": patch +--- + +fix: guard converters against missing parts and mediaType on non-spec payloads diff --git a/.changeset/react-ai-sdk-v7.md b/.changeset/react-ai-sdk-v7.md new file mode 100644 index 0000000..5378f73 --- /dev/null +++ b/.changeset/react-ai-sdk-v7.md @@ -0,0 +1,5 @@ +--- +"@assistant-ui/react-ai-sdk": patch +--- + +feat: upgrade to AI SDK v7 (ai@^7, @ai-sdk/react@^4, @ai-sdk/mcp@^2); pin the previous release to stay on v6 diff --git a/.changeset/react-data-stream-v7.md b/.changeset/react-data-stream-v7.md new file mode 100644 index 0000000..f8b6090 --- /dev/null +++ b/.changeset/react-data-stream-v7.md @@ -0,0 +1,5 @@ +--- +"@assistant-ui/react-data-stream": patch +--- + +feat: widen @ai-sdk/provider range to allow the v7 line alongside v6 (^3 || ^4) diff --git a/.changeset/react-langgraph-drop-late-tool-result.md b/.changeset/react-langgraph-drop-late-tool-result.md new file mode 100644 index 0000000..d76a92c --- /dev/null +++ b/.changeset/react-langgraph-drop-late-tool-result.md @@ -0,0 +1,5 @@ +--- +"@assistant-ui/react-langgraph": patch +--- + +fix: drop a frontend tool result for a call that was already answered instead of releasing it as a resume run. A slow frontend tool resolving after a new turn auto-cancelled its dangling call no longer sends a second tool message to the graph. \ No newline at end of file diff --git a/.changeset/react-langgraph-preserve-attachments.md b/.changeset/react-langgraph-preserve-attachments.md new file mode 100644 index 0000000..19efb93 --- /dev/null +++ b/.changeset/react-langgraph-preserve-attachments.md @@ -0,0 +1,5 @@ +--- +"@assistant-ui/react-langgraph": patch +--- + +fix: preserve attachments on the user message state in useLangGraphRuntime \ No newline at end of file diff --git a/.changeset/react-mcp-auth-state-validation.md b/.changeset/react-mcp-auth-state-validation.md new file mode 100644 index 0000000..dc40194 --- /dev/null +++ b/.changeset/react-mcp-auth-state-validation.md @@ -0,0 +1,5 @@ +--- +"@assistant-ui/react-mcp": patch +--- + +fix: ignore malformed persisted MCP auth state diff --git a/.changeset/react-mcp-list-resources.md b/.changeset/react-mcp-list-resources.md new file mode 100644 index 0000000..6980353 --- /dev/null +++ b/.changeset/react-mcp-list-resources.md @@ -0,0 +1,5 @@ +--- +"@assistant-ui/react-mcp": patch +--- + +feat: expose MCP resource listing on connected servers diff --git a/.changeset/react-native-thread-messages-ref.md b/.changeset/react-native-thread-messages-ref.md new file mode 100644 index 0000000..0c608ec --- /dev/null +++ b/.changeset/react-native-thread-messages-ref.md @@ -0,0 +1,5 @@ +--- +"@assistant-ui/react-native": patch +--- + +fix: forward ThreadPrimitive.Messages refs to the underlying FlatList diff --git a/.changeset/selection-toolbar-quote-regions.md b/.changeset/selection-toolbar-quote-regions.md new file mode 100644 index 0000000..053f666 --- /dev/null +++ b/.changeset/selection-toolbar-quote-regions.md @@ -0,0 +1,5 @@ +--- +"@assistant-ui/react": patch +--- + +feat: support opt-in quote regions for the selection toolbar diff --git a/.changeset/share-attachment-data-url.md b/.changeset/share-attachment-data-url.md new file mode 100644 index 0000000..de15eef --- /dev/null +++ b/.changeset/share-attachment-data-url.md @@ -0,0 +1,6 @@ +--- +"@assistant-ui/core": patch +"@assistant-ui/react-ai-sdk": patch +--- + +fix: make the default attachment adapter work without FileReader (Node, react-ink, SSR) by sharing a single getFileDataURL from @assistant-ui/core/internal, whose base64 fallback chunks large inputs and works on runtimes without Buffer diff --git a/.changeset/soft-cameras-pull.md b/.changeset/soft-cameras-pull.md new file mode 100644 index 0000000..5d53128 --- /dev/null +++ b/.changeset/soft-cameras-pull.md @@ -0,0 +1,5 @@ +--- +"@assistant-ui/mcp-docs-server": patch +--- + +feat: expose playground template tools through the MCP docs server diff --git a/.changeset/steer-away-tool-calls.md b/.changeset/steer-away-tool-calls.md new file mode 100644 index 0000000..41cad56 --- /dev/null +++ b/.changeset/steer-away-tool-calls.md @@ -0,0 +1,5 @@ +--- +"@assistant-ui/react-ag-ui": patch +--- + +feat: steer-away cancels pending client-side tool calls diff --git a/.changeset/textbuffer-relocation.md b/.changeset/textbuffer-relocation.md new file mode 100644 index 0000000..5cb9fbc --- /dev/null +++ b/.changeset/textbuffer-relocation.md @@ -0,0 +1,5 @@ +--- +"@assistant-ui/react-ink": patch +--- + +refactor: move useTextBuffer beside TextInput diff --git a/.changeset/textinput-cursor-preserving-sync.md b/.changeset/textinput-cursor-preserving-sync.md new file mode 100644 index 0000000..b04eb6a --- /dev/null +++ b/.changeset/textinput-cursor-preserving-sync.md @@ -0,0 +1,5 @@ +--- +"@assistant-ui/react-ink": patch +--- + +fix: preserve the TextInput cursor when a controlled owner corrects an edit diff --git a/.changeset/textinput-primitive.md b/.changeset/textinput-primitive.md new file mode 100644 index 0000000..046614b --- /dev/null +++ b/.changeset/textinput-primitive.md @@ -0,0 +1,5 @@ +--- +"@assistant-ui/react-ink": patch +--- + +feat: add standalone TextInput primitive diff --git a/.changeset/thread-list-error-context.md b/.changeset/thread-list-error-context.md new file mode 100644 index 0000000..018b655 --- /dev/null +++ b/.changeset/thread-list-error-context.md @@ -0,0 +1,5 @@ +--- +"@assistant-ui/core": patch +--- + +fix: include thread ids in thread list runtime errors diff --git a/.changeset/typescript-seven.md b/.changeset/typescript-seven.md new file mode 100644 index 0000000..65cb97a --- /dev/null +++ b/.changeset/typescript-seven.md @@ -0,0 +1,6 @@ +--- +"@assistant-ui/x-buildutils": patch +"@assistant-ui/metro": patch +--- + +chore: update TypeScript to 7 diff --git a/.changeset/validate-frontend-tool-schemas.md b/.changeset/validate-frontend-tool-schemas.md new file mode 100644 index 0000000..cbdda54 --- /dev/null +++ b/.changeset/validate-frontend-tool-schemas.md @@ -0,0 +1,5 @@ +--- +"@assistant-ui/react-ai-sdk": patch +--- + +fix: validate frontend tool schemas before conversion diff --git a/.changeset/weak-zoos-ran.md b/.changeset/weak-zoos-ran.md new file mode 100644 index 0000000..5e5df4e --- /dev/null +++ b/.changeset/weak-zoos-ran.md @@ -0,0 +1,41 @@ +--- +"@assistant-ui/agent-launcher": patch +"assistant-stream": patch +"assistant-ui": patch +"@assistant-ui/cloud-ai-sdk": patch +"assistant-cloud": patch +"@assistant-ui/core": patch +"@assistant-ui/eve": patch +"@assistant-ui/gorp": patch +"heat-graph": patch +"@assistant-ui/mcp-docs-server": patch +"@assistant-ui/metro": patch +"@assistant-ui/next": patch +"@assistant-ui/react-a2a": patch +"@assistant-ui/react-ag-ui": patch +"@assistant-ui/react-ai-sdk": patch +"@assistant-ui/react-devtools": patch +"@assistant-ui/react-generative-ui": patch +"@assistant-ui/react-google-adk": patch +"@assistant-ui/react-ink-markdown": patch +"@assistant-ui/react-ink": patch +"@assistant-ui/react-langchain": patch +"@assistant-ui/react-langgraph": patch +"@assistant-ui/react-lexical": patch +"@assistant-ui/react-markdown": patch +"@assistant-ui/react-mcp": patch +"@assistant-ui/react-native": patch +"@assistant-ui/react-o11y": patch +"@assistant-ui/react-opencode": patch +"@assistant-ui/react-pi": patch +"@assistant-ui/react-streamdown": patch +"@assistant-ui/react": patch +"safe-content-frame": patch +"@assistant-ui/store": patch +"@assistant-ui/tap": patch +"@assistant-ui/vite": patch +"@assistant-ui/x-buildutils": patch +"@assistant-ui/x-generative-compiler": patch +--- + +chore: update dependencies diff --git a/.claude/commands/butflow.md b/.claude/commands/butflow.md new file mode 100644 index 0000000..becdd4c --- /dev/null +++ b/.claude/commands/butflow.md @@ -0,0 +1,5 @@ +--- +description: "GitButler CI review flow: implement, PR, monitor CI, address reviews, merge" +--- + +Invoke the `butflow` skill. diff --git a/.claude/skills/butflow/SKILL.md b/.claude/skills/butflow/SKILL.md new file mode 100644 index 0000000..0360d06 --- /dev/null +++ b/.claude/skills/butflow/SKILL.md @@ -0,0 +1,60 @@ +--- +name: butflow +description: Use when the user asks for butflow mode, wants GitButler-driven implementation flow, or asks to implement a change through PR creation, CI monitoring, review-thread resolution, and merge in the assistant-ui monorepo. +--- + +# Butflow + +Implement the change, open a PR through GitButler, monitor CI and reviews, address feedback, and merge when allowed. Multiple agents may run concurrently, so keep one branch per change group and avoid mixing unrelated work. + +## Flow + +1. Run `but pull`. +2. Implement the requested change. +3. Validate with the repo-appropriate lint/build/test commands. For assistant-ui, default to `pnpm lint` and `pnpm build` unless the task clearly warrants narrower checks. +4. Create a GitButler branch with `but branch new `. +5. Stage only the intended files into that branch with `but stage `. If a stage id is ambiguous, run `but status -j` and use the longer id or file path. +6. Commit with `but commit --only -m ""`. +7. Push with `but push `. +8. Open the PR with `gh pr create --title "" --body "<body>"` or another non-interactive form such as `--fill`. +9. Schedule a 2-minute recurring monitor using the environment's native automation mechanism. In Claude Code, use the available `schedule` or `loop` skill. +10. Monitor checks and review threads until the merge gate is satisfied, then merge with `gh pr merge <n> --squash --admin`. + +Add a patch changeset only if a published package changed. Private packages such as `@assistant-ui/docs` and `@assistant-ui/shadcn-registry` are exempt. + +## Monitor Cycle + +Run: + +```bash +gh pr checks <n> +gh pr view <n> --json reviews +gh api graphql -f query='query { repository(owner:"assistant-ui",name:"assistant-ui") { pullRequest(number:<n>) { reviewThreads(first:100) { nodes { id isResolved isOutdated comments(first:50) { nodes { databaseId body author { login } } } } } } } }' +``` + +In review threads, `id` is the GraphQL node id for resolving the thread. `databaseId` on each comment is the REST integer for replies. + +## Addressing Threads + +Every unresolved thread must get a reply and be resolved. + +- Valid: fix in a follow-up commit, reply with the fix SHA, then resolve. +- Invalid: reply with a short rationale, then resolve. +- Outdated: if `isOutdated: true`, reply that the diff moved, then resolve. + +Use judgment on bot nits. Common-sense suggestions that duplicate what a competent agent already knows are usually reply-and-resolve. Scope creep from long bot-feedback loops is a signal to cut and merge. + +Do not add comments or changeset prose that only exist to acknowledge review feedback. Test: would you write it if no reviewer had flagged the code? If no, drop it. + +```bash +gh api /repos/assistant-ui/assistant-ui/pulls/<n>/comments/<databaseId>/replies -f body='...' +gh api graphql -f query='mutation($id:ID!){resolveReviewThread(input:{threadId:$id}){thread{isResolved}}}' -f id=<threadId> +``` + +## Merge Gate + +- All non-cubic CI checks pass. +- Every review thread is resolved. +- No non-cubic reviewer has a current `CHANGES_REQUESTED` state. Address and wait for re-approval instead of dismissing. + +Cubic is optional; do not wait for it if the other gates are clear. diff --git a/.claude/skills/butflow/agents/openai.yaml b/.claude/skills/butflow/agents/openai.yaml new file mode 100644 index 0000000..d8dbde5 --- /dev/null +++ b/.claude/skills/butflow/agents/openai.yaml @@ -0,0 +1,4 @@ +interface: + display_name: "Butflow" + short_description: "GitButler PR, CI, review, and merge flow" + default_prompt: "Use $butflow to implement this change, open a PR, monitor CI and reviews, and merge when ready." diff --git a/.claude/skills/trusted-publishing/SKILL.md b/.claude/skills/trusted-publishing/SKILL.md new file mode 100644 index 0000000..34d55f8 --- /dev/null +++ b/.claude/skills/trusted-publishing/SKILL.md @@ -0,0 +1,78 @@ +--- +name: trusted-publishing +description: Configure npm trusted publishing (OIDC) and lock down publishing access for a newly added publishable package in the assistant-ui monorepo. Use whenever a new public package is created under packages/* (a new package.json with `"private": false` / a new name on npm), when a release fails a package with an npm trusted-publishing / OIDC authorization error, or when the user asks to set up OIDC / trusted publishing / disallow tokens for a package. +--- + +# trusted-publishing + +Every public package in this monorepo publishes to npm through **trusted publishing (OIDC)** from the `npm-publish.yaml` GitHub Actions workflow — there are no long-lived npm tokens. Trusted publishing is configured **per package name** on the npm registry. Adding a new package to `packages/*` does **not** automatically grant it OIDC publish rights: until you configure it, the release run fails that package's publish with an OIDC authorization error from npm. + +So: whenever you add a new publishable package (or notice one missing its config), set up trusted publishing **and** harden its publishing access before the first release. + +## When this applies + +The release workflow only scans `packages/*` and publishes each package there whose `package.json` is not `"private": true`. So a new package under `packages/` needs this setup unless you mark it `"private": true` (as `@assistant-ui/ui` and `@assistant-ui/x-changelog` are). Anything outside `packages/` is never published regardless — e.g. `@assistant-ui/docs` and `@assistant-ui/shadcn-registry` live under `apps/`, so they're excluded by location. + +## The config values (identical for every package in this repo) + +| Field | Value | +|---|---| +| Publisher | GitHub Actions | +| Organization or user | `assistant-ui` | +| Repository | `assistant-ui` | +| Workflow filename | `npm-publish.yaml` | +| Environment name | `npm Publish` | +| Allowed actions | ✅ Allow `npm publish` **and** ✅ Allow `npm stage publish` | + +These match the publish job in `.github/workflows/npm-publish.yaml`, which already runs in the `npm Publish` environment with `id-token: write`. No workflow change is needed per package — only the npm-side config below. + +## Do it via CLI (preferred) + +Requires **npm ≥ 11.10.0** (`npm trust` was added then; the repo's pinned npm is fine — check `npm --version`). Both commands need an **interactive 2FA OTP** and cannot be driven by an automation token, so they can't run unattended. Have the user run them — they can use the `! <command>` prompt prefix so output lands in this session. + +Replace `<PKG>` with the published name (e.g. `@assistant-ui/react`, or an unscoped name like `assistant-stream`). + +```bash +# 1. Create the trusted-publisher relationship (OIDC) +npm trust github <PKG> \ + --repository assistant-ui/assistant-ui \ + --file npm-publish.yaml \ + --environment "npm Publish" \ + --allow-publish \ + --allow-stage-publish + +# 2. Lock publishing access: require 2FA, disallow tokens (the "recommended" option) +npm access set mfa=publish <PKG> +``` + +`mfa=publish` is npm's "**Require two-factor authentication and disallow tokens (recommended)**" setting — it forbids token-based publishes while leaving OIDC trusted publishing working. (`mfa=automation` is the weaker "allow tokens with bypass 2FA" option; don't use it.) + +> **Brand-new package?** `npm access set mfa=publish` (and the website's Publishing-access toggle) only work once the package exists on the registry — for a name that has never published, run step 1 (`npm trust`) now and apply this mfa lockdown right after the first release. Step 1 works pre-publish; see [Brand-new package names](#brand-new-package-names-first-publish-chicken-and-egg) below. + +Verify afterward: + +```bash +npm trust list <PKG> # shows the GitHub Actions trust entry +npm access get status <PKG> # only works post-first-publish; skip for a brand-new name +``` + +## Or via the website + +If the CLI path is blocked (older npm, auth issues), configure both in the npm UI: + +- **Trusted publishing:** npmjs.com → Packages → `<PKG>` → **Settings → Trusted publishing** → fill in the table above (allowed actions: tick **both** `npm publish` and `npm stage publish`) → Save. +- **Publishing access:** same Settings page → **Publishing access** → select **"Require two-factor authentication and disallow tokens (recommended)"**. + +## Brand-new package names (first-publish chicken-and-egg) + +The npm **website** only lets you edit a package's settings once the package already exists on the registry — so for a brand-new name, configure with **`npm trust` first** (it can establish the trust relationship without publishing a placeholder version), and then let the `npm Publish` workflow ship the initial version via OIDC. If you hit trouble publishing the very first version through OIDC, the fallback is a one-time manual publish, then configure on the website, then all subsequent releases flow through the workflow. Don't introduce a long-lived `NPM_TOKEN` to work around it. + +Lock down publishing access (`npm access set mfa=publish`, or the website's Publishing-access toggle) **after** that first version exists — it can't be set on a name that hasn't published yet. + +## Checklist for a new package + +1. `package.json` has `"private": false` and the correct public `name`. +2. Run the two `npm trust` / `npm access` commands above (or the website equivalents). +3. `npm trust list <PKG>` shows the GitHub Actions entry; `npm access get status` confirms mfa. +4. Add a `patch` changeset (per `AGENTS.md`) that **names the new package explicitly** in its frontmatter — changesets only releases packages listed in a changeset entry, so a brand-new package won't ship unless it's named. Nothing else in the workflow needs editing. +5. Ship the first version as a **stable semver** (no `-` pre-release suffix like `0.1.0-alpha.0`). `npm-publish.yaml` calls `setFailed` on prerelease versions and fails the **entire** release run, not just that package. diff --git a/.claude/skills/update-deps/SKILL.md b/.claude/skills/update-deps/SKILL.md new file mode 100644 index 0000000..2872b6d --- /dev/null +++ b/.claude/skills/update-deps/SKILL.md @@ -0,0 +1,121 @@ +--- +name: update-deps +description: Update dependencies across the assistant-ui monorepo (npm via pnpm + taze, Expo SDK-pinned packages, Python packages via uv, and GitHub Actions). Use when the user asks to bump, upgrade, or update dependencies (root, packages, examples, templates, python/*, .github/workflows/*), refresh the pnpm lockfile or uv.lock files, repin GitHub Actions, or run the dependency-update workflow before a release. +--- + +# update-deps + +Update every package's dependencies across the monorepo (packages, apps, examples, templates, `python/*`, and `.github/workflows/*`), regenerate lockfiles, and create a `chore: update dependencies` changeset for the JS side. + +## JS / TS (pnpm workspaces) + +Preview what would change without writing anything: + +```bash +pnpm deps:check +``` + +Run the full update (writes package.json files, reinstalls, dedupes, generates the changeset): + +```bash +pnpm deps:update +``` + +Both are defined in the root `package.json`. `deps:update` performs, in order: + +1. `npx taze major -f -w -r` — bump every dependency (incl. major) recursively. +2. `cd examples/with-expo && npx expo install --fix` — **required**: taze does not know about Expo's SDK compatibility matrix and will bump `expo-*` / `react-native-*` / `react` / `react-dom` to versions that crash at runtime. `expo install --fix` re-pins them to the versions sanctioned by the current `expo` SDK. Do not skip this step, and do not commit Expo-related bumps without it. +3. Wipe every `node_modules` and `pnpm-lock.yaml`, then `pnpm install` + `pnpm dedupe`. +4. `bash scripts/generate-deps-changeset.sh` — write a patch changeset for each published package whose `package.json` changed. + +### Expo notes + +- If you bump the `expo` major in `examples/with-expo` (e.g. SDK 55 → 56), `expo install --fix` will rewrite the matching `react`, `react-dom`, `react-native`, `react-native-*`, and `expo-*` versions. Eyeball the diff in `examples/with-expo/package.json` to confirm everything snapped to the expected SDK line. +- If you intentionally want to hold Expo back, run `pnpm deps:update`, then `git checkout examples/with-expo/package.json` and re-run `pnpm install` + the changeset script manually. + +### Workflow + +1. From a clean working tree on a feature branch, run `pnpm deps:update`. It takes several minutes (lockfile is regenerated from scratch). +2. `git status` to confirm the changeset file appeared under `.changeset/` and that only `package.json` / `pnpm-lock.yaml` files changed. +3. Validate before committing: + ```bash + pnpm build + pnpm lint + pnpm test + ``` +4. If a package breaks on a major bump, pin that one dep back in the offending `package.json` and re-run `pnpm install`; the changeset script does **not** need to re-run. +5. Commit as `chore: update dependencies` and push. + +### Notes + +- Do **not** hand-edit the generated changeset's bump levels — `generate-deps-changeset.sh` correctly emits `patch` for every published package whose `package.json` changed and skips private packages (`@assistant-ui/docs`, `@assistant-ui/shadcn-registry`, etc.). Per `AGENTS.md`, dependency updates are always patch. +- The script detects changes via `git diff HEAD`, so run it with the package.json edits still unstaged (or staged — it checks both). Don't commit before it runs. +- `pnpm-lock.yaml` will have a huge diff; that's expected since step 3 deletes it. +- Node `>=24` and `pnpm@11.3.0` are required (see root `package.json` `engines` / `packageManager`). + +## Python (uv) + +Python packages live under `python/` and each has its own `pyproject.toml` + `uv.lock`. They are **not** touched by `pnpm deps:update`. + +Packages: + +- `python/assistant-stream` +- `python/assistant-ui-sync-server-api` +- `python/assistant-transport-backend` +- `python/assistant-transport-backend-langgraph` +- `python/state-test` +- `python/assistant-stream-hello-world` (no lockfile — example) + +For each package with a `uv.lock`, upgrade with: + +```bash +cd python/<package> +uv lock --upgrade +uv sync +uv run pytest # if tests exist +``` + +Or in one pass from the repo root: + +```bash +for d in python/*/uv.lock; do + (cd "$(dirname "$d")" && uv lock --upgrade && uv sync) +done +``` + +Notes: + +- Python bumps do **not** require a changeset — Python packages are versioned manually in their `pyproject.toml` and published via `.github/workflows/pypi-publish.yaml`, independent of the JS changesets pipeline. +- Bumping a published Python package's own version (e.g. `assistant-stream`) is a separate release decision; `uv lock --upgrade` only touches transitive deps. +- Commit Python and JS dep updates separately if the diff is large, or as one `chore: update dependencies` commit if both are clean. + +## GitHub Actions + +Workflows under `.github/workflows/*.{yml,yaml}` use a mix of styles: + +- **SHA-pinned** (preferred for security — supply-chain hardening): + `uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6` +- **Tag-pinned** (still in some files): + `uses: actions/checkout@v6` + +There is **no Dependabot config** (`.github/dependabot.yml` does not exist), so these don't update themselves. `pnpm deps:update` does not touch them either. + +To refresh both styles in one shot, use [`ratchet`](https://github.com/sethvargo/ratchet) (or `pinact`): + +```bash +# pin any remaining tag refs to SHAs (one-time per file) +ratchet pin .github/workflows/*.yml .github/workflows/*.yaml + +# bump every SHA-pinned action to the latest release SHA for its major +ratchet update .github/workflows/*.yml .github/workflows/*.yaml +``` + +Both leave the `# v6`-style comment intact so reviewers can read the human version. If `ratchet` isn't available, fall back to manually checking each `uses:` against the action's releases page and updating the SHA + comment together. + +After updating, sanity-check on a branch by pushing and watching the affected workflows actually run (most are PR-triggered: `code-quality`, `autofix`, `changeset`, `changeset-semver-check`, `expo`, `devtools-frame`, `registry`). Release workflows (`npm-publish`, `pypi-publish`, `traction`) can't be tested without a release tag — eyeball those diffs extra carefully. + +Notes: + +- GH Actions updates do not need a changeset (they don't ship in any npm package). +- Commit as `chore: update github actions` (or roll into `chore: update dependencies` if landing alongside the JS/Python bumps). +- If a major bump changes inputs/outputs, check the action's release notes — `ratchet` will happily move you from `v4` to `v6` without warning about breaking changes. diff --git a/.devcontainer/devcontainer.json b/.devcontainer/devcontainer.json new file mode 100644 index 0000000..5e4fa6b --- /dev/null +++ b/.devcontainer/devcontainer.json @@ -0,0 +1,26 @@ +{ + "name": "assistant-ui Development", + "image": "mcr.microsoft.com/devcontainers/typescript-node:1-22-bookworm", + "features": { + "ghcr.io/devcontainers/features/git:1": {}, + "ghcr.io/devcontainers/features/github-cli:1": {}, + "ghcr.io/devcontainers-contrib/features/pnpm:2": {} + }, + "customizations": { + "vscode": { + "extensions": [ + "oxc.oxc-vscode", + "ms-vscode.vscode-typescript-next", + "bradlc.vscode-tailwindcss", + "anthropics.claude-code" + ], + "settings": { + "editor.formatOnSave": true, + "editor.defaultFormatter": "oxc.oxc-vscode" + } + } + }, + "forwardPorts": [3000, 3001], + "postCreateCommand": "pnpm install && npm install -g @anthropic-ai/claude-code", + "remoteUser": "node" +} diff --git a/.gitattributes b/.gitattributes new file mode 100644 index 0000000..b9100da --- /dev/null +++ b/.gitattributes @@ -0,0 +1,3 @@ +* text=auto eol=lf + +pnpm-lock.yaml linguist-generated diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS new file mode 100644 index 0000000..3e72498 --- /dev/null +++ b/.github/CODEOWNERS @@ -0,0 +1,7 @@ +# CI and repository policy changes require maintainer review. +/.github/CODEOWNERS @Yonom @okisdev +/.github/actions/ @assistant-ui/maintainers +/.github/workflows/ @assistant-ui/maintainers + +# Public API surface snapshots require maintainer review. +/api-surface/ @Yonom @okisdev diff --git a/.github/ISSUE_TEMPLATE/bug_report.yml b/.github/ISSUE_TEMPLATE/bug_report.yml new file mode 100644 index 0000000..2522dba --- /dev/null +++ b/.github/ISSUE_TEMPLATE/bug_report.yml @@ -0,0 +1,108 @@ +name: Bug report +description: Report an issue to help us improve assistant-ui +labels: ["bug"] + +body: + - type: checkboxes + attributes: + label: Checklist + description: Please confirm before submitting. + options: + - label: I have searched existing issues and this is not a duplicate + required: true + - label: This is a reproducible bug, not a usage question (use [Discord](https://discord.gg/S9dwgCNEFs) for questions) + required: true + + - type: textarea + attributes: + label: Environment info + description: Run `npx assistant-ui info` in your project and paste the output here. + placeholder: | + Environment: + OS: macOS 15.3 (arm64) + Node.js: v22.14.0 + Package Manager: pnpm 10.32.1 + Framework: Next.js 15.3.1 + + Packages: + @assistant-ui/react 0.12.15 + ... + validations: + required: true + + - type: dropdown + attributes: + label: Affected area(s) + multiple: true + options: + - Runtime / Core + - Thread Management + - Streaming (assistant-stream) + - Attachments / Files + - Cloud (assistant-cloud) + - React / UI Components + - React Native + - Store / Tap + - Content Rendering (Markdown, Syntax Highlighting) + - Rich Text / Composer (Lexical) + - Provider Adapters + - Performance + - CLI / Scaffolding + - DevTools / Observability + - Documentation + - Unsure + + - type: dropdown + attributes: + label: Runtime adapter in use + description: Which runtime hook are you using? + options: + - useLocalRuntime + - useExternalStoreRuntime + - useCloudRuntime + - useAISDKRuntime / useChatRuntime + - useLangGraphRuntime + - useAgUiRuntime + - useA2ARuntime + - useAdkRuntime (Google ADK) + - useDataStreamRuntime + - useAssistantTransportRuntime + - Other / Custom + - Not applicable + - Unsure + + - type: textarea + attributes: + label: Steps to reproduce + description: Minimal steps or a link to a reproduction repo. + placeholder: | + 1. Set up runtime with `useXxxRuntime(...)` + 2. Send a message with ... + 3. Observe ... + validations: + required: true + + - type: textarea + attributes: + label: Expected vs actual behavior + placeholder: | + Expected: ... + Actual: ... + validations: + required: true + + - type: input + attributes: + label: Browser (if applicable) + placeholder: "e.g. Chrome 131, Firefox 135, Safari 18" + + - type: textarea + attributes: + label: Code snippet or error log + render: tsx + description: Relevant code, stack traces, or console output. Please remove any secrets. + + - type: textarea + attributes: + label: Additional context + description: Screenshots, related issues, or anything else that might help. diff --git a/.github/ISSUE_TEMPLATE/config.yml b/.github/ISSUE_TEMPLATE/config.yml new file mode 100644 index 0000000..b98493b --- /dev/null +++ b/.github/ISSUE_TEMPLATE/config.yml @@ -0,0 +1,8 @@ +blank_issues_enabled: false +contact_links: + - name: Question or help + url: https://discord.gg/S9dwgCNEFs + about: Ask questions and get help from the community on Discord. + - name: Documentation + url: https://www.assistant-ui.com/docs + about: Check the docs — your question might already be answered. diff --git a/.github/ISSUE_TEMPLATE/feature_request.yml b/.github/ISSUE_TEMPLATE/feature_request.yml new file mode 100644 index 0000000..1de0d35 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/feature_request.yml @@ -0,0 +1,48 @@ +name: Feature request +description: Suggest an improvement or new capability for assistant-ui +labels: ["enhancement"] + +body: + - type: textarea + attributes: + label: Problem statement + description: What problem are you trying to solve? + placeholder: I'm trying to ... but currently ... + validations: + required: true + + - type: textarea + attributes: + label: Proposed solution + description: If you have an idea for an API, component, or behavior change, describe it here. + + - type: dropdown + attributes: + label: Area + options: + - Runtime / Core + - Thread Management + - Streaming (assistant-stream) + - Attachments / Files + - Cloud (assistant-cloud) + - React / UI Components + - React Native + - Store / Tap + - Content Rendering (Markdown, Syntax Highlighting) + - Rich Text / Composer (Lexical) + - Provider Adapters + - Performance + - CLI / Scaffolding + - DevTools / Observability + - Documentation + - Unsure + + - type: textarea + attributes: + label: Alternatives considered + description: Other approaches or workarounds you've tried. + + - type: textarea + attributes: + label: Additional context + description: Any extra details, links, mockups, or examples. diff --git a/.github/assets/assistant-ui-starter.gif b/.github/assets/assistant-ui-starter.gif new file mode 100644 index 0000000..c822173 Binary files /dev/null and b/.github/assets/assistant-ui-starter.gif differ diff --git a/.github/assets/components.png b/.github/assets/components.png new file mode 100644 index 0000000..90414d0 Binary files /dev/null and b/.github/assets/components.png differ diff --git a/.github/assets/devtoolsmodal.png b/.github/assets/devtoolsmodal.png new file mode 100644 index 0000000..8ae63c7 Binary files /dev/null and b/.github/assets/devtoolsmodal.png differ diff --git a/.github/assets/devtoolsui.png b/.github/assets/devtoolsui.png new file mode 100644 index 0000000..835f45e Binary files /dev/null and b/.github/assets/devtoolsui.png differ diff --git a/.github/assets/header-dark.svg b/.github/assets/header-dark.svg new file mode 100644 index 0000000..571b574 --- /dev/null +++ b/.github/assets/header-dark.svg @@ -0,0 +1 @@ +<svg xmlns="http://www.w3.org/2000/svg" width="453" height="124" fill="none" viewBox="0 0 453 124"><rect width="453" height="124" fill="#161b22" rx="10"/><path fill="#9198a1" d="M89.42 85v-8.52h-3.36V74.5h9.15v1.98h-3.36V85zm7.47 3.03q-.63 0-1.246-.195-.615-.195-1.005-.54l.855-1.665a2.03 2.03 0 0 0 1.32.51q.495 0 .78-.24.3-.225.54-.765l.42-.99.18-.255 2.895-6.96h2.25l-3.645 8.565q-.39.975-.9 1.53-.495.555-1.11.78-.6.225-1.335.225m1.38-2.715-3.6-8.385h2.414l2.79 6.75zm11.334-.195q-1.02 0-1.785-.45t-1.2-1.365q-.42-.93-.42-2.34 0-1.425.405-2.34t1.17-1.365 1.83-.45q1.14 0 2.04.525.915.51 1.44 1.44.54.93.54 2.19 0 1.275-.54 2.205-.525.93-1.44 1.44-.9.51-2.04.51m-4.77 2.79V76.93h2.235v1.65l-.045 2.4.15 2.385v4.545zm4.365-4.71q.585 0 1.035-.27.465-.27.735-.765.285-.51.285-1.2 0-.705-.285-1.2a1.94 1.94 0 0 0-.735-.765q-.45-.27-1.035-.27t-1.05.27-.735.765-.27 1.2q0 .69.27 1.2.27.495.735.765t1.05.27m9.996 1.92q-1.38 0-2.43-.54-1.035-.54-1.605-1.47-.57-.945-.57-2.145 0-1.215.555-2.145a3.97 3.97 0 0 1 1.545-1.47q.975-.54 2.205-.54 1.185 0 2.13.51.96.495 1.515 1.44.555.93.555 2.235 0 .135-.015.315l-.03.315h-6.555V80.26h5.325l-.9.405q0-.63-.255-1.095a1.8 1.8 0 0 0-.705-.72q-.45-.27-1.05-.27t-1.065.27q-.45.255-.705.735-.255.465-.255 1.11v.36q0 .66.285 1.17.3.495.825.765.54.255 1.26.255.645 0 1.125-.195.495-.195.9-.585l1.245 1.35q-.555.63-1.395.975-.84.33-1.935.33m8.173 0q-1.035 0-1.995-.24-.945-.255-1.5-.63l.78-1.68q.555.345 1.305.57.765.21 1.5.21.81 0 1.14-.195.345-.195.345-.54 0-.285-.27-.42-.255-.15-.69-.225t-.96-.15q-.51-.075-1.035-.195-.525-.135-.96-.39t-.705-.69q-.255-.435-.255-1.125 0-.765.435-1.35.45-.585 1.29-.915.84-.345 2.01-.345.825 0 1.68.18t1.425.525l-.78 1.665a3.8 3.8 0 0 0-1.185-.465q-.585-.135-1.14-.135-.78 0-1.14.21t-.36.54q0 .3.255.45.27.15.705.24t.945.165q.525.06 1.05.195t.945.39q.435.24.705.675.27.42.27 1.11 0 .75-.45 1.335-.45.57-1.305.9-.84.33-2.055.33m9.154 0q-1.305 0-2.325-.525a4.1 4.1 0 0 1-1.605-1.485q-.57-.945-.57-2.145 0-1.215.57-2.145a4 4 0 0 1 1.605-1.47q1.02-.54 2.325-.54 1.275 0 2.22.54.945.525 1.395 1.515l-1.815.975q-.315-.57-.795-.84-.465-.27-1.02-.27-.6 0-1.08.27t-.765.765q-.27.495-.27 1.2t.27 1.2q.285.495.765.765t1.08.27q.555 0 1.02-.255.48-.27.795-.855l1.815.99q-.45.975-1.395 1.515-.945.525-2.22.525m4.962-.12v-8.07h2.235v2.28l-.315-.66q.36-.855 1.155-1.29.795-.45 1.935-.45v2.16a3 3 0 0 0-.27-.015 2 2 0 0 0-.255-.015q-.96 0-1.56.555-.585.54-.585 1.695V85zm6.416 0v-8.07h2.34V85zm1.17-9.195q-.645 0-1.05-.375t-.405-.93.405-.93 1.05-.375 1.05.36q.405.345.405.9 0 .585-.405.975-.39.375-1.05.375m8.112 9.315q-1.02 0-1.785-.45t-1.2-1.365q-.42-.93-.42-2.34 0-1.425.405-2.34t1.17-1.365 1.83-.45q1.14 0 2.04.525.915.51 1.44 1.44.54.93.54 2.19 0 1.275-.54 2.205-.525.93-1.44 1.44-.9.51-2.04.51m-4.77 2.79V76.93h2.235v1.65l-.045 2.4.15 2.385v4.545zm4.365-4.71q.585 0 1.035-.27.465-.27.735-.765.285-.51.285-1.2 0-.705-.285-1.2a1.94 1.94 0 0 0-.735-.765q-.45-.27-1.035-.27t-1.05.27-.735.765-.27 1.2q0 .69.27 1.2.27.495.735.765t1.05.27m9.291 1.92q-1.425 0-2.22-.72-.795-.735-.795-2.175v-7.08h2.34v7.05q0 .51.27.795.27.27.735.27.555 0 .945-.3l.63 1.65q-.36.255-.87.39-.495.12-1.035.12m-4.26-6.21v-1.8h5.595v1.8zm5.904 7.59 4.95-14.13h2.085l-4.95 14.13zm7.614-1.5V74.5h4.545q1.41 0 2.43.465 1.02.45 1.575 1.305t.555 2.04q0 1.17-.555 2.025-.555.84-1.575 1.29t-2.43.45h-3.195l1.08-1.065V85zm6.675 0-2.625-3.81h2.595l2.655 3.81zm-4.245-3.72-1.08-1.14h3.06q1.125 0 1.68-.48.555-.495.555-1.35 0-.87-.555-1.35t-1.68-.48h-3.06l1.08-1.155zm12.44 3.84q-1.38 0-2.43-.54-1.035-.54-1.605-1.47-.57-.945-.57-2.145 0-1.215.555-2.145a3.97 3.97 0 0 1 1.545-1.47q.975-.54 2.205-.54 1.185 0 2.13.51.96.495 1.515 1.44.555.93.555 2.235 0 .135-.015.315l-.03.315h-6.555V80.26h5.325l-.9.405q0-.63-.255-1.095a1.8 1.8 0 0 0-.705-.72q-.45-.27-1.05-.27t-1.065.27q-.45.255-.705.735-.255.465-.255 1.11v.36q0 .66.285 1.17.3.495.825.765.54.255 1.26.255.645 0 1.125-.195.495-.195.9-.585l1.245 1.35q-.555.63-1.395.975-.84.33-1.935.33m10.188-.12v-1.575l-.15-.345v-2.82q0-.75-.465-1.17-.45-.42-1.395-.42-.645 0-1.275.21-.615.195-1.05.54l-.84-1.635q.66-.465 1.59-.72t1.89-.255q1.845 0 2.865.87t1.02 2.715V85zm-2.46.12q-.945 0-1.62-.315-.675-.33-1.035-.885t-.36-1.245q0-.72.345-1.26.36-.54 1.125-.84.765-.315 1.995-.315h2.145v1.365h-1.89q-.825 0-1.14.27a.87.87 0 0 0-.3.675q0 .45.345.72.36.255.975.255.585 0 1.05-.27.465-.285.675-.825l.36 1.08q-.255.78-.93 1.185t-1.74.405m10.727 0q-1.305 0-2.325-.525a4.1 4.1 0 0 1-1.605-1.485q-.57-.945-.57-2.145 0-1.215.57-2.145a4 4 0 0 1 1.605-1.47q1.02-.54 2.325-.54 1.275 0 2.22.54.945.525 1.395 1.515l-1.815.975q-.315-.57-.795-.84-.465-.27-1.02-.27-.6 0-1.08.27t-.765.765q-.27.495-.27 1.2t.27 1.2q.285.495.765.765t1.08.27q.555 0 1.02-.255.48-.27.795-.855l1.815.99q-.45.975-1.395 1.515-.945.525-2.22.525m8.379 0q-1.425 0-2.22-.72-.795-.735-.795-2.175v-7.08h2.34v7.05q0 .51.27.795.27.27.735.27.555 0 .945-.3l.63 1.65q-.36.255-.87.39-.495.12-1.035.12m-4.26-6.21v-1.8h5.595v1.8zM224.662 85V74.5h2.43v8.52h5.265V85zm8.888 0v-8.07h2.34V85zm1.17-9.195q-.645 0-1.05-.375t-.405-.93.405-.93 1.05-.375 1.05.36q.405.345.405.9 0 .585-.405.975-.39.375-1.05.375m8.111 9.315q-1.065 0-1.83-.45t-1.17-1.365q-.405-.93-.405-2.34 0-1.425.42-2.34.435-.915 1.2-1.365t1.785-.45q1.14 0 2.04.51.915.51 1.44 1.44.54.93.54 2.205 0 1.26-.54 2.19-.525.93-1.44 1.455-.9.51-2.04.51m-4.77-.12V73.87h2.34v4.695l-.15 2.385.045 2.4V85zm4.365-1.8q.585 0 1.035-.27.465-.27.735-.765.285-.51.285-1.2 0-.705-.285-1.2a1.94 1.94 0 0 0-.735-.765q-.45-.27-1.035-.27t-1.05.27-.735.765-.27 1.2q0 .69.27 1.2.27.495.735.765t1.05.27m5.992 1.8v-8.07h2.235v2.28l-.315-.66q.36-.855 1.155-1.29.795-.45 1.935-.45v2.16a3 3 0 0 0-.27-.015 2 2 0 0 0-.255-.015q-.96 0-1.56.555-.585.54-.585 1.695V85zm10.824 0v-1.575l-.15-.345v-2.82q0-.75-.465-1.17-.45-.42-1.395-.42-.645 0-1.275.21-.615.195-1.05.54l-.84-1.635q.66-.465 1.59-.72t1.89-.255q1.845 0 2.865.87t1.02 2.715V85zm-2.46.12q-.945 0-1.62-.315-.675-.33-1.035-.885t-.36-1.245q0-.72.345-1.26.36-.54 1.125-.84.765-.315 1.995-.315h2.145v1.365h-1.89q-.825 0-1.14.27a.87.87 0 0 0-.3.675q0 .45.345.72.36.255.975.255.585 0 1.05-.27.465-.285.675-.825l.36 1.08q-.255.78-.93 1.185t-1.74.405m6.768-.12v-8.07h2.235v2.28l-.315-.66q.36-.855 1.155-1.29.795-.45 1.935-.45v2.16a3 3 0 0 0-.27-.015 2 2 0 0 0-.255-.015q-.96 0-1.56.555-.585.54-.585 1.695V85zm7.699 3.03q-.63 0-1.245-.195t-1.005-.54l.855-1.665a2.03 2.03 0 0 0 1.32.51q.495 0 .78-.24.3-.225.54-.765l.42-.99.18-.255 2.895-6.96h2.25l-3.645 8.565q-.39.975-.9 1.53-.495.555-1.11.78-.6.225-1.335.225m1.38-2.715-3.6-8.385h2.415l2.79 6.75zM283.727 85v-8.25q0-1.365.81-2.175.81-.825 2.31-.825.51 0 .975.105.48.105.81.33l-.615 1.695q-.195-.135-.435-.21a1.7 1.7 0 0 0-.51-.075q-.51 0-.795.3-.27.285-.27.87v.75l.06 1.005V85zm-1.245-6.09v-1.8h5.595v1.8zm10.499 6.21q-1.29 0-2.295-.54a4.2 4.2 0 0 1-1.575-1.47q-.57-.945-.57-2.145 0-1.215.57-2.145.585-.945 1.575-1.47 1.005-.54 2.295-.54 1.275 0 2.28.54 1.005.525 1.575 1.455t.57 2.16q0 1.2-.57 2.145a4.06 4.06 0 0 1-1.575 1.47q-1.005.54-2.28.54m0-1.92q.585 0 1.05-.27t.735-.765q.27-.51.27-1.2 0-.705-.27-1.2a1.94 1.94 0 0 0-.735-.765q-.465-.27-1.05-.27t-1.05.27-.75.765q-.27.495-.27 1.2 0 .69.27 1.2.285.495.75.765t1.05.27m5.989 1.8v-8.07h2.235v2.28l-.315-.66q.36-.855 1.155-1.29.795-.45 1.935-.45v2.16a3 3 0 0 0-.27-.015 2 2 0 0 0-.255-.015q-.96 0-1.56.555-.585.54-.585 1.695V85zm9.493 0 4.68-10.5h2.4l4.695 10.5h-2.55l-3.84-9.27h.96L310.953 85zm2.34-2.25.645-1.845h5.4l.66 1.845zM321.327 85V74.5h2.43V85zm14.195.18q-1.215 0-2.265-.39-1.035-.405-1.8-1.14a5.4 5.4 0 0 1-1.2-1.725 5.5 5.5 0 0 1-.42-2.175q0-1.185.42-2.175.435-.99 1.2-1.725a5.4 5.4 0 0 1 1.815-1.125 6.2 6.2 0 0 1 2.265-.405q1.365 0 2.46.48 1.11.465 1.86 1.38l-1.56 1.44a3.6 3.6 0 0 0-1.2-.915 3.3 3.3 0 0 0-1.44-.315q-.735 0-1.35.24t-1.065.69-.705 1.065q-.24.615-.24 1.365t.24 1.365q.255.615.705 1.065t1.065.69 1.35.24q.78 0 1.44-.3.66-.315 1.2-.945l1.56 1.44q-.75.915-1.86 1.395-1.095.48-2.475.48m10.716-8.37q.96 0 1.71.39.765.375 1.2 1.17.435.78.435 2.01V85h-2.34v-4.26q0-.975-.435-1.44-.42-.465-1.2-.465-.555 0-1.005.24-.435.225-.69.705-.24.48-.24 1.23V85h-2.34V73.87h2.34v5.295l-.525-.675q.435-.81 1.245-1.245t1.845-.435M356.654 85v-1.575l-.15-.345v-2.82q0-.75-.465-1.17-.45-.42-1.395-.42-.645 0-1.275.21-.615.195-1.05.54l-.84-1.635q.66-.465 1.59-.72t1.89-.255q1.845 0 2.865.87t1.02 2.715V85zm-2.46.12q-.945 0-1.62-.315-.675-.33-1.035-.885t-.36-1.245q0-.72.345-1.26.36-.54 1.125-.84.765-.315 1.995-.315h2.145v1.365h-1.89q-.825 0-1.14.27a.87.87 0 0 0-.3.675q0 .45.345.72.36.255.975.255.585 0 1.05-.27.465-.285.675-.825l.36 1.08q-.255.78-.93 1.185t-1.74.405m10.068 0q-1.425 0-2.22-.72-.795-.735-.795-2.175v-7.08h2.34v7.05q0 .51.27.795.27.27.735.27.555 0 .945-.3l.63 1.65q-.36.255-.87.39-.495.12-1.035.12m-4.26-6.21v-1.8h5.595v1.8z"/><g stroke="#fff" stroke-linecap="round" stroke-linejoin="round" stroke-width="3" clip-path="url(#a)"><path d="M165 47a2 2 0 0 1-2 2h-6l-4 4V42c0-1.1.9-2 2-2h8a2 2 0 0 1 2 2zM169 47h2a2 2 0 0 1 2 2v11l-4-4h-6a2 2 0 0 1-2-2v-1"/></g><path fill="#fff" d="M185 49.64q.32-1.66 1.54-2.56 1.24-.92 3.2-.92 2.28 0 3.46 1.16 1.2 1.16 1.2 3.4v3.64q0 .44.16.62.18.16.5.16h.42V57l-.6.02h-.22q-.92.04-1.64-.28-.72-.34-.84-1.36-.4.84-1.32 1.36-.9.5-2.22.5-1.64 0-2.74-.82-1.08-.82-1.08-2.2 0-1 .46-1.62.48-.62 1.34-.96.86-.36 2.24-.64l2.96-.58q0-1.24-.52-1.82-.52-.6-1.56-.6-.86 0-1.4.46-.54.44-.72 1.32zm2.48 4.5q0 .58.48.94t1.38.36q.74 0 1.3-.36.58-.36.9-1.06.34-.72.34-1.72v-.14l-2.02.36q-.14.02-.3.04-.72.14-1.16.3a1.44 1.44 0 0 0-.68.46q-.24.3-.24.82m16.114-4.34a1.9 1.9 0 0 0-.76-1.24 2.23 2.23 0 0 0-1.4-.48q-.76 0-1.28.38a1.14 1.14 0 0 0-.48 1q.02.6.6.92t1.54.5q1.54.26 2.5.64.98.38 1.48 1.02.52.64.52 1.62 0 1.02-.6 1.72-.6.68-1.62 1.02t-2.3.34q-1.44 0-2.56-.42-1.1-.44-1.76-1.26a3.45 3.45 0 0 1-.72-1.92l2.62-.14q.1.56.4.96t.8.62 1.16.22q.78 0 1.36-.3t.56-.9q0-.42-.26-.68-.24-.26-.62-.38a7 7 0 0 0-.98-.28 5 5 0 0 0-.4-.08q-1.48-.3-2.4-.66t-1.46-.96q-.52-.62-.52-1.56 0-1.04.54-1.78.56-.76 1.58-1.16 1.04-.4 2.44-.4 1.88 0 3.1.94 1.24.94 1.52 2.58zm11.191 0a1.9 1.9 0 0 0-.76-1.24q-.62-.48-1.4-.48-.76 0-1.28.38a1.15 1.15 0 0 0-.48 1q.02.6.6.92t1.54.5q1.54.26 2.5.64.98.38 1.48 1.02.52.64.52 1.62 0 1.02-.6 1.72-.6.68-1.62 1.02t-2.3.34q-1.44 0-2.56-.42-1.1-.44-1.76-1.26a3.45 3.45 0 0 1-.72-1.92l2.62-.14q.1.56.4.96t.8.62 1.16.22q.78 0 1.36-.3t.56-.9q0-.42-.26-.68-.24-.26-.62-.38a7 7 0 0 0-.98-.28 5 5 0 0 0-.4-.08q-1.48-.3-2.4-.66t-1.46-.96q-.52-.62-.52-1.56 0-1.04.54-1.78.56-.76 1.58-1.16 1.04-.4 2.44-.4 1.88 0 3.1.94 1.24.94 1.52 2.58zm7.179-3.4V57h-2.56V46.4zm.06-3.58v2.12h-2.66v-2.12zm8.952 6.98a1.9 1.9 0 0 0-.76-1.24q-.62-.48-1.4-.48-.76 0-1.28.38a1.15 1.15 0 0 0-.48 1q.021.6.6.92.58.32 1.54.5 1.54.26 2.5.64.98.38 1.48 1.02.52.64.52 1.62 0 1.02-.6 1.72-.6.68-1.62 1.02t-2.3.34q-1.44 0-2.56-.42-1.1-.44-1.76-1.26a3.46 3.46 0 0 1-.72-1.92l2.62-.14q.1.56.4.96t.8.62 1.16.22q.78 0 1.36-.3t.56-.9q0-.42-.26-.68-.24-.26-.62-.38a7 7 0 0 0-.98-.28 5 5 0 0 0-.4-.08q-1.479-.3-2.4-.66t-1.46-.96q-.52-.62-.52-1.56 0-1.04.54-1.78.56-.76 1.58-1.16 1.04-.4 2.44-.4 1.88 0 3.1.94 1.24.94 1.52 2.58zm8.125-5.8v9.74q0 .62.3.96.32.32.9.32h1.6V57h-2.02q-1.64 0-2.5-.82-.84-.82-.84-2.44V44zm2.8 2.4v1.98h-7.04V46.4zm1.654 3.24q.32-1.66 1.54-2.56 1.24-.92 3.2-.92 2.28 0 3.46 1.16 1.2 1.16 1.2 3.4v3.64q0 .44.16.62.18.16.5.16h.42V57l-.6.02h-.22q-.92.04-1.64-.28-.72-.34-.84-1.36-.4.84-1.32 1.36-.9.5-2.22.5-1.64 0-2.74-.82-1.08-.82-1.08-2.2 0-1 .46-1.62.48-.62 1.34-.96.86-.36 2.24-.64l2.96-.58q0-1.24-.52-1.82-.52-.6-1.56-.6-.86 0-1.4.46-.54.44-.72 1.32zm2.48 4.5q0 .58.48.94t1.38.36q.74 0 1.3-.36.579-.36.9-1.06.34-.72.34-1.72v-.14l-2.02.36q-.141.02-.3.04-.72.14-1.16.3a1.44 1.44 0 0 0-.68.46q-.24.3-.24.82m12.212-7.74.12 2.98-.32-.16q.22-1.58 1.14-2.32.94-.74 2.32-.74 1.72 0 2.68 1.1.96 1.08.96 2.92V57h-2.58v-6q0-1.4-.42-2.08t-1.42-.68q-1.06 0-1.66.72-.58.7-.58 2.04v6h-2.56V46.4zm13.041-2.4v9.74q0 .62.3.96.321.32.9.32h1.6V57h-2.02q-1.64 0-2.5-.82-.84-.82-.84-2.44V44zm2.8 2.4v1.98h-7.04V46.4zm8.274 3.68v2.16h-6.8v-2.16zM291.34 57l-.06-2.92.32.16q-.24 1.56-1.16 2.28-.921.72-2.28.72-1.68 0-2.64-1.1-.94-1.1-.94-2.92V46.4h2.56v5.98q0 1.42.42 2.1t1.4.68q1.04 0 1.6-.7.58-.72.58-2.08V46.4h2.56V57zm7.518-10.6V57h-2.56V46.4zm.06-3.58v2.12h-2.66v-2.12z"/><defs><clipPath id="a"><path fill="#161b22" d="M151 38h24v24h-24z"/></clipPath></defs></svg> \ No newline at end of file diff --git a/.github/assets/header.svg b/.github/assets/header.svg new file mode 100644 index 0000000..465150a --- /dev/null +++ b/.github/assets/header.svg @@ -0,0 +1 @@ +<svg xmlns="http://www.w3.org/2000/svg" width="453" height="124" fill="none" viewBox="0 0 453 124"><rect width="453" height="124" fill="#fff" rx="10"/><path fill="#6a6868" d="M89.42 85v-8.52h-3.36V74.5h9.15v1.98h-3.36V85zm7.47 3.03q-.63 0-1.246-.195-.615-.195-1.005-.54l.855-1.665a2.03 2.03 0 0 0 1.32.51q.495 0 .78-.24.3-.225.54-.765l.42-.99.18-.255 2.895-6.96h2.25l-3.645 8.565q-.39.975-.9 1.53-.495.555-1.11.78-.6.225-1.335.225m1.38-2.715-3.6-8.385h2.414l2.79 6.75zm11.334-.195q-1.02 0-1.785-.45t-1.2-1.365q-.42-.93-.42-2.34 0-1.425.405-2.34t1.17-1.365 1.83-.45q1.14 0 2.04.525.915.51 1.44 1.44.54.93.54 2.19 0 1.275-.54 2.205-.525.93-1.44 1.44-.9.51-2.04.51m-4.77 2.79V76.93h2.235v1.65l-.045 2.4.15 2.385v4.545zm4.365-4.71q.585 0 1.035-.27.465-.27.735-.765.285-.51.285-1.2 0-.705-.285-1.2a1.94 1.94 0 0 0-.735-.765q-.45-.27-1.035-.27t-1.05.27-.735.765-.27 1.2q0 .69.27 1.2.27.495.735.765t1.05.27m9.996 1.92q-1.38 0-2.43-.54-1.035-.54-1.605-1.47-.57-.945-.57-2.145 0-1.215.555-2.145a3.97 3.97 0 0 1 1.545-1.47q.975-.54 2.205-.54 1.185 0 2.13.51.96.495 1.515 1.44.555.93.555 2.235 0 .135-.015.315l-.03.315h-6.555V80.26h5.325l-.9.405q0-.63-.255-1.095a1.8 1.8 0 0 0-.705-.72q-.45-.27-1.05-.27t-1.065.27q-.45.255-.705.735-.255.465-.255 1.11v.36q0 .66.285 1.17.3.495.825.765.54.255 1.26.255.645 0 1.125-.195.495-.195.9-.585l1.245 1.35q-.555.63-1.395.975-.84.33-1.935.33m8.173 0q-1.035 0-1.995-.24-.945-.255-1.5-.63l.78-1.68q.555.345 1.305.57.765.21 1.5.21.81 0 1.14-.195.345-.195.345-.54 0-.285-.27-.42-.255-.15-.69-.225t-.96-.15q-.51-.075-1.035-.195-.525-.135-.96-.39t-.705-.69q-.255-.435-.255-1.125 0-.765.435-1.35.45-.585 1.29-.915.84-.345 2.01-.345.825 0 1.68.18t1.425.525l-.78 1.665a3.8 3.8 0 0 0-1.185-.465q-.585-.135-1.14-.135-.78 0-1.14.21t-.36.54q0 .3.255.45.27.15.705.24t.945.165q.525.06 1.05.195t.945.39q.435.24.705.675.27.42.27 1.11 0 .75-.45 1.335-.45.57-1.305.9-.84.33-2.055.33m9.154 0q-1.305 0-2.325-.525a4.1 4.1 0 0 1-1.605-1.485q-.57-.945-.57-2.145 0-1.215.57-2.145a4 4 0 0 1 1.605-1.47q1.02-.54 2.325-.54 1.275 0 2.22.54.945.525 1.395 1.515l-1.815.975q-.315-.57-.795-.84-.465-.27-1.02-.27-.6 0-1.08.27t-.765.765q-.27.495-.27 1.2t.27 1.2q.285.495.765.765t1.08.27q.555 0 1.02-.255.48-.27.795-.855l1.815.99q-.45.975-1.395 1.515-.945.525-2.22.525m4.962-.12v-8.07h2.235v2.28l-.315-.66q.36-.855 1.155-1.29.795-.45 1.935-.45v2.16a3 3 0 0 0-.27-.015 2 2 0 0 0-.255-.015q-.96 0-1.56.555-.585.54-.585 1.695V85zm6.416 0v-8.07h2.34V85zm1.17-9.195q-.645 0-1.05-.375t-.405-.93.405-.93 1.05-.375 1.05.36q.405.345.405.9 0 .585-.405.975-.39.375-1.05.375m8.112 9.315q-1.02 0-1.785-.45t-1.2-1.365q-.42-.93-.42-2.34 0-1.425.405-2.34t1.17-1.365 1.83-.45q1.14 0 2.04.525.915.51 1.44 1.44.54.93.54 2.19 0 1.275-.54 2.205-.525.93-1.44 1.44-.9.51-2.04.51m-4.77 2.79V76.93h2.235v1.65l-.045 2.4.15 2.385v4.545zm4.365-4.71q.585 0 1.035-.27.465-.27.735-.765.285-.51.285-1.2 0-.705-.285-1.2a1.94 1.94 0 0 0-.735-.765q-.45-.27-1.035-.27t-1.05.27-.735.765-.27 1.2q0 .69.27 1.2.27.495.735.765t1.05.27m9.291 1.92q-1.425 0-2.22-.72-.795-.735-.795-2.175v-7.08h2.34v7.05q0 .51.27.795.27.27.735.27.555 0 .945-.3l.63 1.65q-.36.255-.87.39-.495.12-1.035.12m-4.26-6.21v-1.8h5.595v1.8zm5.904 7.59 4.95-14.13h2.085l-4.95 14.13zm7.614-1.5V74.5h4.545q1.41 0 2.43.465 1.02.45 1.575 1.305t.555 2.04q0 1.17-.555 2.025-.555.84-1.575 1.29t-2.43.45h-3.195l1.08-1.065V85zm6.675 0-2.625-3.81h2.595l2.655 3.81zm-4.245-3.72-1.08-1.14h3.06q1.125 0 1.68-.48.555-.495.555-1.35 0-.87-.555-1.35t-1.68-.48h-3.06l1.08-1.155zm12.44 3.84q-1.38 0-2.43-.54-1.035-.54-1.605-1.47-.57-.945-.57-2.145 0-1.215.555-2.145a3.97 3.97 0 0 1 1.545-1.47q.975-.54 2.205-.54 1.185 0 2.13.51.96.495 1.515 1.44.555.93.555 2.235 0 .135-.015.315l-.03.315h-6.555V80.26h5.325l-.9.405q0-.63-.255-1.095a1.8 1.8 0 0 0-.705-.72q-.45-.27-1.05-.27t-1.065.27q-.45.255-.705.735-.255.465-.255 1.11v.36q0 .66.285 1.17.3.495.825.765.54.255 1.26.255.645 0 1.125-.195.495-.195.9-.585l1.245 1.35q-.555.63-1.395.975-.84.33-1.935.33m10.188-.12v-1.575l-.15-.345v-2.82q0-.75-.465-1.17-.45-.42-1.395-.42-.645 0-1.275.21-.615.195-1.05.54l-.84-1.635q.66-.465 1.59-.72t1.89-.255q1.845 0 2.865.87t1.02 2.715V85zm-2.46.12q-.945 0-1.62-.315-.675-.33-1.035-.885t-.36-1.245q0-.72.345-1.26.36-.54 1.125-.84.765-.315 1.995-.315h2.145v1.365h-1.89q-.825 0-1.14.27a.87.87 0 0 0-.3.675q0 .45.345.72.36.255.975.255.585 0 1.05-.27.465-.285.675-.825l.36 1.08q-.255.78-.93 1.185t-1.74.405m10.727 0q-1.305 0-2.325-.525a4.1 4.1 0 0 1-1.605-1.485q-.57-.945-.57-2.145 0-1.215.57-2.145a4 4 0 0 1 1.605-1.47q1.02-.54 2.325-.54 1.275 0 2.22.54.945.525 1.395 1.515l-1.815.975q-.315-.57-.795-.84-.465-.27-1.02-.27-.6 0-1.08.27t-.765.765q-.27.495-.27 1.2t.27 1.2q.285.495.765.765t1.08.27q.555 0 1.02-.255.48-.27.795-.855l1.815.99q-.45.975-1.395 1.515-.945.525-2.22.525m8.379 0q-1.425 0-2.22-.72-.795-.735-.795-2.175v-7.08h2.34v7.05q0 .51.27.795.27.27.735.27.555 0 .945-.3l.63 1.65q-.36.255-.87.39-.495.12-1.035.12m-4.26-6.21v-1.8h5.595v1.8zM224.662 85V74.5h2.43v8.52h5.265V85zm8.888 0v-8.07h2.34V85zm1.17-9.195q-.645 0-1.05-.375t-.405-.93.405-.93 1.05-.375 1.05.36q.405.345.405.9 0 .585-.405.975-.39.375-1.05.375m8.111 9.315q-1.065 0-1.83-.45t-1.17-1.365q-.405-.93-.405-2.34 0-1.425.42-2.34.435-.915 1.2-1.365t1.785-.45q1.14 0 2.04.51.915.51 1.44 1.44.54.93.54 2.205 0 1.26-.54 2.19-.525.93-1.44 1.455-.9.51-2.04.51m-4.77-.12V73.87h2.34v4.695l-.15 2.385.045 2.4V85zm4.365-1.8q.585 0 1.035-.27.465-.27.735-.765.285-.51.285-1.2 0-.705-.285-1.2a1.94 1.94 0 0 0-.735-.765q-.45-.27-1.035-.27t-1.05.27-.735.765-.27 1.2q0 .69.27 1.2.27.495.735.765t1.05.27m5.992 1.8v-8.07h2.235v2.28l-.315-.66q.36-.855 1.155-1.29.795-.45 1.935-.45v2.16a3 3 0 0 0-.27-.015 2 2 0 0 0-.255-.015q-.96 0-1.56.555-.585.54-.585 1.695V85zm10.824 0v-1.575l-.15-.345v-2.82q0-.75-.465-1.17-.45-.42-1.395-.42-.645 0-1.275.21-.615.195-1.05.54l-.84-1.635q.66-.465 1.59-.72t1.89-.255q1.845 0 2.865.87t1.02 2.715V85zm-2.46.12q-.945 0-1.62-.315-.675-.33-1.035-.885t-.36-1.245q0-.72.345-1.26.36-.54 1.125-.84.765-.315 1.995-.315h2.145v1.365h-1.89q-.825 0-1.14.27a.87.87 0 0 0-.3.675q0 .45.345.72.36.255.975.255.585 0 1.05-.27.465-.285.675-.825l.36 1.08q-.255.78-.93 1.185t-1.74.405m6.768-.12v-8.07h2.235v2.28l-.315-.66q.36-.855 1.155-1.29.795-.45 1.935-.45v2.16a3 3 0 0 0-.27-.015 2 2 0 0 0-.255-.015q-.96 0-1.56.555-.585.54-.585 1.695V85zm7.699 3.03q-.63 0-1.245-.195t-1.005-.54l.855-1.665a2.03 2.03 0 0 0 1.32.51q.495 0 .78-.24.3-.225.54-.765l.42-.99.18-.255 2.895-6.96h2.25l-3.645 8.565q-.39.975-.9 1.53-.495.555-1.11.78-.6.225-1.335.225m1.38-2.715-3.6-8.385h2.415l2.79 6.75zM283.727 85v-8.25q0-1.365.81-2.175.81-.825 2.31-.825.51 0 .975.105.48.105.81.33l-.615 1.695q-.195-.135-.435-.21a1.7 1.7 0 0 0-.51-.075q-.51 0-.795.3-.27.285-.27.87v.75l.06 1.005V85zm-1.245-6.09v-1.8h5.595v1.8zm10.499 6.21q-1.29 0-2.295-.54a4.2 4.2 0 0 1-1.575-1.47q-.57-.945-.57-2.145 0-1.215.57-2.145.585-.945 1.575-1.47 1.005-.54 2.295-.54 1.275 0 2.28.54 1.005.525 1.575 1.455t.57 2.16q0 1.2-.57 2.145a4.06 4.06 0 0 1-1.575 1.47q-1.005.54-2.28.54m0-1.92q.585 0 1.05-.27t.735-.765q.27-.51.27-1.2 0-.705-.27-1.2a1.94 1.94 0 0 0-.735-.765q-.465-.27-1.05-.27t-1.05.27-.75.765q-.27.495-.27 1.2 0 .69.27 1.2.285.495.75.765t1.05.27m5.989 1.8v-8.07h2.235v2.28l-.315-.66q.36-.855 1.155-1.29.795-.45 1.935-.45v2.16a3 3 0 0 0-.27-.015 2 2 0 0 0-.255-.015q-.96 0-1.56.555-.585.54-.585 1.695V85zm9.493 0 4.68-10.5h2.4l4.695 10.5h-2.55l-3.84-9.27h.96L310.953 85zm2.34-2.25.645-1.845h5.4l.66 1.845zM321.327 85V74.5h2.43V85zm14.195.18q-1.215 0-2.265-.39-1.035-.405-1.8-1.14a5.4 5.4 0 0 1-1.2-1.725 5.5 5.5 0 0 1-.42-2.175q0-1.185.42-2.175.435-.99 1.2-1.725a5.4 5.4 0 0 1 1.815-1.125 6.2 6.2 0 0 1 2.265-.405q1.365 0 2.46.48 1.11.465 1.86 1.38l-1.56 1.44a3.6 3.6 0 0 0-1.2-.915 3.3 3.3 0 0 0-1.44-.315q-.735 0-1.35.24t-1.065.69-.705 1.065q-.24.615-.24 1.365t.24 1.365q.255.615.705 1.065t1.065.69 1.35.24q.78 0 1.44-.3.66-.315 1.2-.945l1.56 1.44q-.75.915-1.86 1.395-1.095.48-2.475.48m10.716-8.37q.96 0 1.71.39.765.375 1.2 1.17.435.78.435 2.01V85h-2.34v-4.26q0-.975-.435-1.44-.42-.465-1.2-.465-.555 0-1.005.24-.435.225-.69.705-.24.48-.24 1.23V85h-2.34V73.87h2.34v5.295l-.525-.675q.435-.81 1.245-1.245t1.845-.435M356.654 85v-1.575l-.15-.345v-2.82q0-.75-.465-1.17-.45-.42-1.395-.42-.645 0-1.275.21-.615.195-1.05.54l-.84-1.635q.66-.465 1.59-.72t1.89-.255q1.845 0 2.865.87t1.02 2.715V85zm-2.46.12q-.945 0-1.62-.315-.675-.33-1.035-.885t-.36-1.245q0-.72.345-1.26.36-.54 1.125-.84.765-.315 1.995-.315h2.145v1.365h-1.89q-.825 0-1.14.27a.87.87 0 0 0-.3.675q0 .45.345.72.36.255.975.255.585 0 1.05-.27.465-.285.675-.825l.36 1.08q-.255.78-.93 1.185t-1.74.405m10.068 0q-1.425 0-2.22-.72-.795-.735-.795-2.175v-7.08h2.34v7.05q0 .51.27.795.27.27.735.27.555 0 .945-.3l.63 1.65q-.36.255-.87.39-.495.12-1.035.12m-4.26-6.21v-1.8h5.595v1.8z"/><g stroke="#000" stroke-linecap="round" stroke-linejoin="round" stroke-width="3" clip-path="url(#a)"><path d="M165 47a2 2 0 0 1-2 2h-6l-4 4V42c0-1.1.9-2 2-2h8a2 2 0 0 1 2 2zM169 47h2a2 2 0 0 1 2 2v11l-4-4h-6a2 2 0 0 1-2-2v-1"/></g><path fill="#000" d="M185 49.64q.32-1.66 1.54-2.56 1.24-.92 3.2-.92 2.28 0 3.46 1.16 1.2 1.16 1.2 3.4v3.64q0 .44.16.62.18.16.5.16h.42V57l-.6.02h-.22q-.92.04-1.64-.28-.72-.34-.84-1.36-.4.84-1.32 1.36-.9.5-2.22.5-1.64 0-2.74-.82-1.08-.82-1.08-2.2 0-1 .46-1.62.48-.62 1.34-.96.86-.36 2.24-.64l2.96-.58q0-1.24-.52-1.82-.52-.6-1.56-.6-.86 0-1.4.46-.54.44-.72 1.32zm2.48 4.5q0 .58.48.94t1.38.36q.74 0 1.3-.36.58-.36.9-1.06.34-.72.34-1.72v-.14l-2.02.36q-.14.02-.3.04-.72.14-1.16.3a1.44 1.44 0 0 0-.68.46q-.24.3-.24.82m16.114-4.34a1.9 1.9 0 0 0-.76-1.24 2.23 2.23 0 0 0-1.4-.48q-.76 0-1.28.38a1.14 1.14 0 0 0-.48 1q.02.6.6.92t1.54.5q1.54.26 2.5.64.98.38 1.48 1.02.52.64.52 1.62 0 1.02-.6 1.72-.6.68-1.62 1.02t-2.3.34q-1.44 0-2.56-.42-1.1-.44-1.76-1.26a3.45 3.45 0 0 1-.72-1.92l2.62-.14q.1.56.4.96t.8.62 1.16.22q.78 0 1.36-.3t.56-.9q0-.42-.26-.68-.24-.26-.62-.38a7 7 0 0 0-.98-.28 5 5 0 0 0-.4-.08q-1.48-.3-2.4-.66t-1.46-.96q-.52-.62-.52-1.56 0-1.04.54-1.78.56-.76 1.58-1.16 1.04-.4 2.44-.4 1.88 0 3.1.94 1.24.94 1.52 2.58zm11.191 0a1.9 1.9 0 0 0-.76-1.24q-.62-.48-1.4-.48-.76 0-1.28.38a1.15 1.15 0 0 0-.48 1q.02.6.6.92t1.54.5q1.54.26 2.5.64.98.38 1.48 1.02.52.64.52 1.62 0 1.02-.6 1.72-.6.68-1.62 1.02t-2.3.34q-1.44 0-2.56-.42-1.1-.44-1.76-1.26a3.45 3.45 0 0 1-.72-1.92l2.62-.14q.1.56.4.96t.8.62 1.16.22q.78 0 1.36-.3t.56-.9q0-.42-.26-.68-.24-.26-.62-.38a7 7 0 0 0-.98-.28 5 5 0 0 0-.4-.08q-1.48-.3-2.4-.66t-1.46-.96q-.52-.62-.52-1.56 0-1.04.54-1.78.56-.76 1.58-1.16 1.04-.4 2.44-.4 1.88 0 3.1.94 1.24.94 1.52 2.58zm7.179-3.4V57h-2.56V46.4zm.06-3.58v2.12h-2.66v-2.12zm8.952 6.98a1.9 1.9 0 0 0-.76-1.24q-.62-.48-1.4-.48-.76 0-1.28.38a1.15 1.15 0 0 0-.48 1q.021.6.6.92.58.32 1.54.5 1.54.26 2.5.64.98.38 1.48 1.02.52.64.52 1.62 0 1.02-.6 1.72-.6.68-1.62 1.02t-2.3.34q-1.44 0-2.56-.42-1.1-.44-1.76-1.26a3.46 3.46 0 0 1-.72-1.92l2.62-.14q.1.56.4.96t.8.62 1.16.22q.78 0 1.36-.3t.56-.9q0-.42-.26-.68-.24-.26-.62-.38a7 7 0 0 0-.98-.28 5 5 0 0 0-.4-.08q-1.479-.3-2.4-.66t-1.46-.96q-.52-.62-.52-1.56 0-1.04.54-1.78.56-.76 1.58-1.16 1.04-.4 2.44-.4 1.88 0 3.1.94 1.24.94 1.52 2.58zm8.125-5.8v9.74q0 .62.3.96.32.32.9.32h1.6V57h-2.02q-1.64 0-2.5-.82-.84-.82-.84-2.44V44zm2.8 2.4v1.98h-7.04V46.4zm1.654 3.24q.32-1.66 1.54-2.56 1.24-.92 3.2-.92 2.28 0 3.46 1.16 1.2 1.16 1.2 3.4v3.64q0 .44.16.62.18.16.5.16h.42V57l-.6.02h-.22q-.92.04-1.64-.28-.72-.34-.84-1.36-.4.84-1.32 1.36-.9.5-2.22.5-1.64 0-2.74-.82-1.08-.82-1.08-2.2 0-1 .46-1.62.48-.62 1.34-.96.86-.36 2.24-.64l2.96-.58q0-1.24-.52-1.82-.52-.6-1.56-.6-.86 0-1.4.46-.54.44-.72 1.32zm2.48 4.5q0 .58.48.94t1.38.36q.74 0 1.3-.36.579-.36.9-1.06.34-.72.34-1.72v-.14l-2.02.36q-.141.02-.3.04-.72.14-1.16.3a1.44 1.44 0 0 0-.68.46q-.24.3-.24.82m12.212-7.74.12 2.98-.32-.16q.22-1.58 1.14-2.32.94-.74 2.32-.74 1.72 0 2.68 1.1.96 1.08.96 2.92V57h-2.58v-6q0-1.4-.42-2.08t-1.42-.68q-1.06 0-1.66.72-.58.7-.58 2.04v6h-2.56V46.4zm13.041-2.4v9.74q0 .62.3.96.321.32.9.32h1.6V57h-2.02q-1.64 0-2.5-.82-.84-.82-.84-2.44V44zm2.8 2.4v1.98h-7.04V46.4zm8.274 3.68v2.16h-6.8v-2.16zM291.34 57l-.06-2.92.32.16q-.24 1.56-1.16 2.28-.921.72-2.28.72-1.68 0-2.64-1.1-.94-1.1-.94-2.92V46.4h2.56v5.98q0 1.42.42 2.1t1.4.68q1.04 0 1.6-.7.58-.72.58-2.08V46.4h2.56V57zm7.518-10.6V57h-2.56V46.4zm.06-3.58v2.12h-2.66v-2.12z"/><defs><clipPath id="a"><path fill="#fff" d="M151 38h24v24h-24z"/></clipPath></defs></svg> \ No newline at end of file diff --git a/.github/assets/logos/Athena-Intelligence.svg b/.github/assets/logos/Athena-Intelligence.svg new file mode 100644 index 0000000..3a5b286 --- /dev/null +++ b/.github/assets/logos/Athena-Intelligence.svg @@ -0,0 +1 @@ +<svg xmlns="http://www.w3.org/2000/svg" height="20" viewBox="0 0 1658 438"><path fill="#666" d="M445.436 346.283H44.123v.115l-.058-.115-41.229 68.62h483.772l-41.172-68.505zM76.774 334.847h63.703V183.768H76.774zm91.665 0h63.646V183.768h-63.646zm91.665 0h63.645V183.768h-63.587zm91.665 0h63.645V183.768H351.77zM244.15 73.347l104.074 52.38H140.076zm0-52.494-.171-.115-.058.229L35.774 125.784h-.515v41.573h21.33l.171.343.686-.343h373.408l.686.343.172-.343h21.158v-41.115l.171-.23-.114-.056v-.172h-.4L244.379 20.967l-.114-.229zm566.116 143.244H795.34L838.4 46.871h14.638l43.06 117.226H881.23L846.177 65.4h-.915zm5.432-45.804h60.043v12.58h-60.043zm129.578-30.707H899.7V76.149h45.575zm-32.309-32.538h13.553v83.831q0 5.719 1.658 8.578a8.58 8.58 0 0 0 4.346 3.831q2.688.915 5.718.915 2.173 0 3.603-.229l2.287-.457 2.745 12.123a29 29 0 0 1-3.831 1.029 27 27 0 0 1-6.233.572q-5.719 0-11.208-2.46a22.9 22.9 0 0 1-9.035-7.49q-3.546-5.09-3.546-12.752zm68.048 56.154v52.895H967.52V46.871h13.495v43.002h1.144q3.088-6.805 9.264-10.865 6.29-4.003 16.583-4.003 9.035 0 15.783 3.603 6.747 3.545 10.464 10.922 3.774 7.377 3.774 18.699v55.868h-13.495v-54.953q0-10.465-5.432-16.24-5.376-5.719-14.925-5.719-6.634 0-11.952 2.745a20.2 20.2 0 0 0-8.234 8.177q-2.974 5.376-2.974 13.095m118.656 54.725q-12.752 0-21.958-5.604a37.74 37.74 0 0 1-14.125-15.783q-4.917-10.235-4.917-23.73t4.917-23.846a38.9 38.9 0 0 1 13.839-16.126q8.92-5.833 20.872-5.833 6.862 0 13.552 2.288t12.18 7.434 8.75 13.495q3.316 8.463 3.316 20.757v5.719h-67.82v-11.666h54.039q0-7.434-2.974-13.266a22.47 22.47 0 0 0-8.349-9.264 23.45 23.45 0 0 0-12.694-3.374q-8.12 0-14.01 4.003-5.833 4.003-8.978 10.293a30.3 30.3 0 0 0-3.145 13.667v7.777q0 9.95 3.43 16.869 3.489 6.862 9.722 10.465 6.176 3.602 14.296 3.602 5.375 0 9.721-1.487a20 20 0 0 0 7.434-4.574q3.145-3.088 4.86-7.663l13.095 3.66q-2.058 6.633-6.976 11.665a33.7 33.7 0 0 1-12.008 7.777 44.6 44.6 0 0 1-16.069 2.745m70.507-54.725v52.895h-13.552V76.15h13.038v13.724h1.143q3.145-6.69 9.435-10.75 6.29-4.118 16.24-4.118 8.921 0 15.669 3.66 6.69 3.603 10.407 10.98t3.717 18.584v55.868h-13.495v-54.953q0-10.35-5.375-16.183-5.433-5.833-14.81-5.833-6.463 0-11.552 2.86-5.032 2.744-8.006 8.177-2.859 5.375-2.859 13.037m106.704 54.954a36.3 36.3 0 0 1-15.21-3.145 26.1 26.1 0 0 1-10.865-9.207q-4.003-6.061-4.003-14.696 0-7.548 3.03-12.237 2.974-4.747 7.95-7.434t10.978-4.003q6.062-1.372 12.238-2.173 8.005-1.03 12.98-1.544 5.032-.572 7.32-1.887 2.344-1.315 2.344-4.575v-.457q0-8.463-4.574-13.152-4.632-4.747-13.953-4.747-9.721 0-15.211 4.232t-7.72 9.035l-12.809-4.575q3.431-8.005 9.15-12.466 5.775-4.574 12.58-6.29a53.8 53.8 0 0 1 13.552-1.83q4.232 0 9.722 1.03 5.546.972 10.693 4.06 5.204 3.088 8.635 9.32 3.43 6.29 3.43 16.698v57.984h-13.495v-11.894h-.686a24 24 0 0 1-4.575 6.119 26.9 26.9 0 0 1-8.577 5.546q-5.261 2.288-12.924 2.288m2.059-12.123q8.006 0 13.495-3.145 5.547-3.145 8.35-8.12 2.858-5.032 2.858-10.522v-12.352q-.858 1.03-3.774 1.887-2.859.8-6.633 1.43a280 280 0 0 1-12.924 1.715 54 54 0 0 0-9.892 2.23 17.5 17.5 0 0 0-7.32 4.575q-2.745 2.917-2.745 8.063 0 7.09 5.204 10.693 5.261 3.546 13.381 3.546M819.587 379.107h-14.182V261.88h14.182zm40.2-52.895v52.895h-13.553v-87.948h13.095v13.724h1.144a25 25 0 0 1 9.378-10.75q6.29-4.118 16.24-4.118 8.92 0 15.668 3.66 6.69 3.602 10.408 10.979 3.717 7.32 3.717 18.584v55.869h-13.496v-54.954q0-10.35-5.375-16.182-5.432-5.833-14.81-5.833-6.462 0-11.551 2.859-5.033 2.745-8.006 8.12t-2.86 13.095m119.057-23.617h-45.576V291.16h45.576zm-32.31-32.537h13.496v83.888q0 5.718 1.716 8.577a8.58 8.58 0 0 0 4.288 3.832q2.688.915 5.719.915 2.23 0 3.66-.229l2.287-.458 2.745 12.123a29 29 0 0 1-3.832 1.03 27 27 0 0 1-6.29.572q-5.718 0-11.208-2.46a22.9 22.9 0 0 1-9.035-7.49q-3.545-5.09-3.545-12.752zm88.578 110.879q-12.752 0-21.958-5.604a37.74 37.74 0 0 1-14.125-15.783q-4.918-10.236-4.918-23.731t4.918-23.846a38.9 38.9 0 0 1 13.839-16.125q8.977-5.833 20.872-5.833 6.862 0 13.552 2.287t12.18 7.434q5.547 5.147 8.75 13.495 3.316 8.464 3.316 20.758v5.718h-67.82v-11.665h54.039q0-7.434-2.917-13.267a22.47 22.47 0 0 0-8.406-9.263 23.45 23.45 0 0 0-12.694-3.374q-8.12 0-14.01 4.003-5.833 4.002-8.978 10.293a30.3 30.3 0 0 0-3.145 13.666v7.777q0 9.95 3.43 16.87 3.489 6.862 9.722 10.464 6.176 3.545 14.353 3.603 5.318 0 9.607-1.487a20 20 0 0 0 7.49-4.575q3.146-3.088 4.919-7.662l13.037 3.602q-2.058 6.633-6.976 11.723a33.7 33.7 0 0 1-12.008 7.777 44.6 44.6 0 0 1-16.069 2.745m70.421-1.91h-13.438v-117.18h13.438zm38.256 0h-13.438v-117.18h13.438zm24.846-87.868h13.496v87.948h-13.496zm6.862-14.697a9.72 9.72 0 0 1-6.862-2.687 8.58 8.58 0 0 1-2.745-6.462q0-3.774 2.802-6.462a9.55 9.55 0 0 1 6.805-2.687 9.44 9.44 0 0 1 6.748 2.687 8.58 8.58 0 0 1 2.859 6.462 8.58 8.58 0 0 1-2.86 6.462 9.44 9.44 0 0 1-6.747 2.687m66.905 137.47q-9.779 0-16.87-2.517a35.63 35.63 0 0 1-19.099-15.153l10.75-7.549q1.83 2.402 4.576 5.49 2.859 3.145 7.72 5.432 4.917 2.345 12.923 2.345 10.636 0 17.555-5.146t6.92-16.126v-17.841h-1.144q-1.487 2.401-4.232 5.947a24 24 0 0 1-7.777 6.233q-5.032 2.687-13.61 2.687a36.6 36.6 0 0 1-19.156-5.032q-8.406-5.032-13.324-14.639-4.86-9.664-4.86-23.445 0-13.495 4.746-23.445 4.746-10.122 13.21-15.611a34.9 34.9 0 0 1 19.556-5.547q8.578 0 13.667 2.86 5.146 2.801 7.777 6.404 2.745 3.545 4.231 5.832h1.373V291.16h13.095v90.464q0 11.322-5.147 18.413a29.56 29.56 0 0 1-13.78 10.522 50.9 50.9 0 0 1-19.1 3.373m-.458-48.32q8.12 0 13.724-3.717t8.578-10.75 2.859-16.698q0-9.492-2.86-16.811a25.16 25.16 0 0 0-8.462-11.38q-5.604-4.117-13.839-4.117-8.577 0-14.296 4.346a26.5 26.5 0 0 0-8.577 11.665 45.8 45.8 0 0 0-2.802 16.297q0 9.15 2.86 16.183 2.916 7.034 8.577 11.037 5.718 3.945 14.238 3.945m100.071 15.326q-12.694 0-21.958-5.604a37.74 37.74 0 0 1-14.124-15.783q-4.918-10.236-4.918-23.731t4.918-23.846a38.9 38.9 0 0 1 13.895-16.125q8.92-5.833 20.815-5.833 6.862 0 13.552 2.287 6.748 2.288 12.238 7.434t8.749 13.495q3.26 8.464 3.26 20.758v5.718h-67.82v-11.665h54.038a28.9 28.9 0 0 0-2.916-13.267 22.87 22.87 0 0 0-8.406-9.263 23.45 23.45 0 0 0-12.695-3.374q-8.063 0-13.953 4.003t-9.035 10.293a30.3 30.3 0 0 0-3.145 13.666v7.777q0 9.95 3.431 16.87 3.488 6.862 9.721 10.464 6.176 3.603 14.353 3.603 5.318 0 9.607-1.487a20 20 0 0 0 7.548-4.575q3.145-3.088 4.86-7.662l13.039 3.602q-2.059 6.633-6.92 11.723a33.7 33.7 0 0 1-12.008 7.777 44.6 44.6 0 0 1-16.126 2.745m70.507-54.725v52.895h-13.495V291.16h13.038v13.724h1.144q3.088-6.69 9.378-10.75 6.29-4.118 16.297-4.118 8.92 0 15.611 3.66 6.69 3.602 10.407 10.979 3.717 7.32 3.717 18.584v55.869h-13.495v-54.954q0-10.35-5.375-16.182t-14.81-5.833q-6.462 0-11.552 2.859-5.032 2.745-7.948 8.12t-2.917 13.095m116.54 54.725q-12.409 0-21.33-5.833a38.03 38.03 0 0 1-13.723-16.126q-4.804-10.236-4.804-23.445 0-13.381 4.918-23.617a38.9 38.9 0 0 1 13.839-16.068q8.977-5.833 20.871-5.833 9.264 0 16.698 3.43t12.237 9.608q4.746 6.176 5.89 14.41h-13.552a20.8 20.8 0 0 0-6.862-10.636q-5.261-4.69-14.182-4.69-7.891 0-13.838 4.118a26.9 26.9 0 0 0-9.264 11.551q-3.26 7.377-3.26 17.327 0 10.178 3.203 17.726 3.26 7.549 9.15 11.78 6.004 4.175 14.01 4.175 5.26 0 9.549-1.83a19.61 19.61 0 0 0 11.494-13.553h13.552a30.9 30.9 0 0 1-5.718 14.067 31.45 31.45 0 0 1-11.837 9.836q-7.32 3.603-17.04 3.603m91.207 0q-12.695 0-21.959-5.604a37.74 37.74 0 0 1-14.124-15.783q-4.918-10.236-4.918-23.731t4.918-23.846a38.9 38.9 0 0 1 13.838-16.125q8.978-5.833 20.872-5.833 6.862 0 13.553 2.287t12.237 7.434q5.49 5.147 8.75 13.495 3.259 8.464 3.259 20.758v5.718h-67.82v-11.722h54.038a28.9 28.9 0 0 0-2.916-13.267 22.87 22.87 0 0 0-8.406-9.15 23.45 23.45 0 0 0-12.695-3.43q-8.063 0-13.952 4.003t-9.035 10.293a30.3 30.3 0 0 0-3.145 13.666v7.777q0 9.95 3.43 16.87 3.489 6.862 9.722 10.464 6.175 3.545 14.353 3.603 5.318 0 9.664-1.487a20 20 0 0 0 7.434-4.575q3.202-3.088 4.917-7.662l13.038 3.602q-2.058 6.633-6.919 11.723a33.7 33.7 0 0 1-12.009 7.777 44.6 44.6 0 0 1-16.125 2.745M629.967 2.84h17.898v430.02h-17.898z"/></svg> \ No newline at end of file diff --git a/.github/assets/logos/Browser-Use.svg b/.github/assets/logos/Browser-Use.svg new file mode 100644 index 0000000..471badb --- /dev/null +++ b/.github/assets/logos/Browser-Use.svg @@ -0,0 +1 @@ +<svg xmlns="http://www.w3.org/2000/svg" height="20" fill="none" viewBox="0 0 224 39"><path fill="#666" d="M35.512 14.185c-5.541-2.124-.97-10.255-8.59-10.049-9.313.252-23.906 14.92-22.415 24.45.74 4.718 4.42 2.737 6.149 3.425 1.147.454 2.505 2.504 3.972 2.83-6.491 3.738-14.83.179-14.624-7.833C.3 15.208 17.32-1.91 29.454.173c6.775 1.165 7.617 8.38 6.057 14.012"/><path fill="#666" d="m9 20.981 5.368 5.073c3.42 3.077 12.499 8.448 16.613 4.806 4.85-4.293-5.408-19.12-10.13-21.486l4.146-1.867c6.46 4.728 16.28 20.097 8.28 26.788-5.162 4.316-13.483.66-18.374-2.554C11.177 29.29 5.792 25.779 9 20.977zm5.633-19.253c-1.737.046-3.095 2.312-4.37 2.71-1.134.354-2.702-.614-4.408.666-1.847 1.385-1.216 7.998-4.759 9.645C-1.084 8.332.273.683 8.15.027c.608-.05 7.37.399 6.483 1.697z"/><path fill="#666" d="M27.607 20.981c2.908 4.825-2.363 10.338-6.752 5.94zM51.3 29.93H42V8.63h8.28q3.96 0 6.06 1.4 2.1 1.39 2.1 4.39 0 1.32-.54 2.29-.54.98-1.58 1.55-1.03.57-2.5.69v-.06q2.67.18 4.09 1.54 1.43 1.37 1.43 3.62 0 2.97-2.09 4.43-2.08 1.45-5.95 1.45m-4.74-9.18v5.46h4.53q1.65 0 2.66-.68 1-.67 1-2.02t-1-2.06q-1.01-.7-2.66-.7zm0-8.4v5.1h3.6q1.68 0 2.68-.66 1.01-.66 1.01-1.89 0-1.32-.97-1.94-.98-.61-2.72-.61zm20.49 17.58h-4.5V13.85h4.26l.15 4.68-.33-.09q.39-2.43 1.38-3.51t2.73-1.08h1.47v3.72h-1.47q-1.26 0-2.07.33t-1.22 1.05q-.4.72-.4 1.95zm14.34.36q-2.46 0-4.29-1.04-1.83-1.03-2.85-2.92t-1.02-4.44 1.02-4.43q1.02-1.87 2.85-2.92t4.29-1.05 4.29 1.05 2.85 2.92q1.02 1.88 1.02 4.43t-1.02 4.44-2.85 2.92q-1.83 1.04-4.29 1.04m0-3.45q1.68 0 2.61-1.31.93-1.3.93-3.64T84 18.24q-.93-1.3-2.61-1.3t-2.61 1.3q-.93 1.31-.93 3.65t.93 3.64q.93 1.31 2.61 1.31m18.15 3.09h-4.35l-4.77-16.08h4.53l2.76 10.89 2.97-10.89h3.66l2.97 10.89 2.79-10.89h4.53l-4.77 16.08h-4.38l-2.97-10.05zm23.73.36q-2.49 0-4.17-.71-1.68-.7-2.57-1.92-.88-1.21-1-2.8l4.56-.15q.21 1.14.96 1.77t2.22.63q1.2 0 1.87-.39.68-.39.68-1.2 0-.51-.24-.86-.24-.34-.95-.6-.7-.25-2.08-.46-2.55-.45-3.99-1.05t-2.04-1.56-.6-2.34q0-2.31 1.78-3.74 1.79-1.42 5.36-1.42 2.28 0 3.82.72 1.55.72 2.4 1.96.86 1.25 1.07 2.84l-4.53.18q-.09-.78-.47-1.34-.37-.55-.94-.87-.57-.31-1.38-.31-1.2 0-1.85.49-.64.5-.64 1.31 0 .6.28.97.29.38.95.62t1.77.39q2.58.36 4.09 1 1.52.65 2.18 1.61t.66 2.37q0 1.56-.9 2.64t-2.51 1.65q-1.6.57-3.79.57m17.19 0q-2.46 0-4.28-1.04-1.81-1.03-2.82-2.92-1-1.89-1-4.44t1-4.43q1.01-1.87 2.81-2.92t4.26-1.05q2.4 0 4.17 1.05t2.74 3q.98 1.95.98 4.65v.9h-11.31q.09 1.92 1.03 2.85.95.93 2.51.93 1.14 0 1.9-.48.77-.48 1.1-1.47l4.53.27q-.63 2.4-2.67 3.75t-4.95 1.35m-3.45-9.96h6.69q-.09-1.77-.98-2.64-.88-.87-2.29-.87t-2.33.91q-.91.92-1.09 2.6m18.57 9.6h-4.5V13.85h4.26l.15 4.68-.33-.09q.39-2.43 1.38-3.51t2.73-1.08h1.47v3.72h-1.47q-1.26 0-2.07.33t-1.22 1.05q-.4.72-.4 1.95zm23.67.48q-2.7 0-4.67-.99-1.96-.99-3.04-2.84-1.08-1.84-1.08-4.33V8.6h4.56v13.65q0 2.07 1.09 3.19 1.1 1.13 3.14 1.13t3.12-1.13q1.08-1.12 1.08-3.19V8.6h4.56v13.65q0 2.49-1.07 4.33-1.06 1.85-3.04 2.84t-4.65.99m19.38-.12q-2.49 0-4.17-.71-1.68-.7-2.57-1.92-.88-1.21-1-2.8l4.56-.15q.21 1.14.96 1.77t2.22.63q1.2 0 1.87-.39.68-.39.68-1.2 0-.51-.24-.86-.24-.34-.95-.6-.7-.25-2.08-.46-2.55-.45-3.99-1.05t-2.04-1.56-.6-2.34q0-2.31 1.78-3.74 1.79-1.42 5.36-1.42 2.28 0 3.82.72 1.55.72 2.4 1.96.86 1.25 1.07 2.84l-4.53.18q-.09-.78-.47-1.34-.37-.55-.94-.87-.57-.31-1.38-.31-1.2 0-1.85.49-.64.5-.64 1.31 0 .6.28.97.29.38.95.62t1.77.39q2.58.36 4.09 1 1.52.65 2.18 1.61t.66 2.37q0 1.56-.9 2.64t-2.51 1.65q-1.6.57-3.79.57m17.19 0q-2.46 0-4.28-1.04-1.81-1.03-2.82-2.92-1-1.89-1-4.44t1-4.43q1.01-1.87 2.81-2.92t4.26-1.05q2.4 0 4.17 1.05t2.74 3q.98 1.95.98 4.65v.9h-11.31q.09 1.92 1.03 2.85.95.93 2.51.93 1.14 0 1.9-.48.77-.48 1.1-1.47l4.53.27q-.63 2.4-2.67 3.75t-4.95 1.35m-3.45-9.96h6.69q-.09-1.77-.98-2.64-.88-.87-2.29-.87t-2.33.91q-.91.92-1.09 2.6"/></svg> \ No newline at end of file diff --git a/.github/assets/logos/Coreviz.svg b/.github/assets/logos/Coreviz.svg new file mode 100644 index 0000000..c4dc86a --- /dev/null +++ b/.github/assets/logos/Coreviz.svg @@ -0,0 +1 @@ +<svg xmlns="http://www.w3.org/2000/svg" height="20" viewBox="0 0 309 93"><g fill="#666"><path fill-rule="evenodd" d="M31.527 4.343 12.677 15.12A22.55 22.55 0 0 0 1.343 34.693v23.164a22.55 22.55 0 0 0 10.733 19.21l19.256 11.83a22.55 22.55 0 0 0 23.374.151l20.083-12.01a22.55 22.55 0 0 0 10.988-19.362v-21.51a22.55 22.55 0 0 0-10.973-19.362L54.3 4.554a22.55 22.55 0 0 0-22.773-.211m12.687 17.151-7.651 4.42a12.03 12.03 0 0 0-5.998 10.417v9.861a12.03 12.03 0 0 0 5.682 10.207l7.862 4.885a12.03 12.03 0 0 0 12.567.075l8.237-4.975a12.03 12.03 0 0 0 5.817-10.297v-9.065a12.03 12.03 0 0 0-5.817-10.296l-8.463-5.111a12.03 12.03 0 0 0-12.236-.12" clip-rule="evenodd"/><path d="M120.18 70.71h-7.63q-5.53 0-8.54-2.94T101 59.3V37.11q0-5.53 3.01-8.47t8.54-2.94h7.63q5.46 0 8.51 2.98 3.04 2.97 3.04 8.43v3.29q0 1.54-1.54 1.54h-5.88q-1.47 0-1.47-1.54v-2.87q0-2.24-.95-3.18-.94-.95-3.18-.95h-4.76q-2.17 0-3.11.95-.95.94-.95 3.18v21.35q0 2.24.95 3.19.94.94 3.11.94h4.76q2.24 0 3.18-.94.95-.95.95-3.19v-2.87q0-1.54 1.47-1.54h5.88q1.54 0 1.54 1.54v3.29q0 5.46-3.04 8.44-3.05 2.97-8.51 2.97m35.56 0h-6.23q-5.53 0-8.54-2.94t-3.01-8.47V46.42q0-5.53 3.01-8.47t8.54-2.94h6.23q5.46 0 8.51 2.98 3.04 2.97 3.04 8.43V59.3q0 5.46-3.04 8.44-3.05 2.97-8.51 2.97m-4.9-7.63h3.57q2.24 0 3.19-.91.94-.91.94-3.15V46.7q0-2.24-.94-3.15-.95-.91-3.19-.91h-3.57q-2.24 0-3.15.91t-.91 3.15v12.32q0 2.24.91 3.15t3.15.91m30.87 7.63h-5.95q-1.47 0-1.47-1.47V36.48q0-1.47 1.47-1.47h5.95q1.47 0 1.47 1.47v2.38h.28q.77-1.89 2.73-2.87t4.13-.98h2.52q1.4 0 1.4 1.47v4.83q0 .84-.32 1.16-.31.31-1.08.24h-4.83q-4.83 0-4.83 5.46v21.07q0 1.47-1.47 1.47m43.33 0h-15.47q-5.53 0-8.54-2.94t-3.01-8.47V46.42q0-5.53 3.01-8.47t8.54-2.94h5.53q5.53 0 8.54 2.94t3.01 8.47v8.89q0 1.47-1.47 1.47h-17.57q-.84 0-.84.77v1.54q0 2.17.95 3.12.94.94 3.18.94h14.14q1.47 0 1.47 1.54v4.62q0 .77-.31 1.09-.32.31-1.16.31m-17.43-19.88h9.52q.77 0 .77-.77V46.7q0-2.24-.91-3.15t-3.15-.91h-2.94q-2.24 0-3.15.91t-.91 3.15v3.36q0 .77.77.77m24.15-15.82h6.16q1.4 0 1.68 1.47l6.02 26.32h.49l5.88-26.32q.28-1.47 1.75-1.47h6.16q1.47 0 1.05 1.47l-8.82 32.69q-.28 1.54-1.89 1.54h-8.89q-1.47 0-1.89-1.54l-8.75-32.69q-.42-1.47 1.05-1.47m34.65-6.02V22.9q0-1.4 1.4-1.4h6.09q1.4 0 1.4 1.33v6.16q0 1.4-1.4 1.4h-6.09q-1.4 0-1.4-1.4m0 40.25V36.48q0-1.47 1.4-1.47h6.02q1.47 0 1.47 1.47v32.76q0 .91-.31 1.19-.32.28-1.09.28h-6.02q-1.47 0-1.47-1.47m15.26 0v-2.31q0-2.38 1.33-4.06l14.84-19.95v-.28h-14.07q-1.47 0-1.47-1.54v-4.62q0-1.47 1.47-1.47h22.82q1.47 0 1.47 1.47v4.2q0 2.24-1.12 3.64L293.22 62.8v.28h14.14q1.47 0 1.47 1.54v4.62q0 1.47-1.47 1.47h-24.22q-1.47 0-1.47-1.47"/></g></svg> \ No newline at end of file diff --git a/.github/assets/logos/Inconvo.svg b/.github/assets/logos/Inconvo.svg new file mode 100644 index 0000000..6796545 --- /dev/null +++ b/.github/assets/logos/Inconvo.svg @@ -0,0 +1 @@ +<svg xmlns="http://www.w3.org/2000/svg" height="20" viewBox="0 0 3179 713"><path fill="#666" d="M874.8 143.68h102.18v425.23H874.8zm482.12 220.26V568.9h-102.18V384.13c0-34.88-23.86-55.06-54.45-55.06s-55.68 22.03-55.68 51.39V568.9h-102.17V246.47h96.05v25.7c22.03-20.19 53.84-33.65 93-33.65 75.87 0 125.42 51.4 125.42 125.43Zm41.62 44.66c0-94.83 72.81-169.48 171.32-169.48 91.16 0 145 59.96 157.85 107.07l-90.55 36.1c-7.96-30.59-30.6-52.62-64.85-52.62-39.77 0-70.98 29.98-70.98 78.32s31.21 78.32 70.98 78.32c34.87 0 58.74-22.03 66.69-51.39l90.55 36.1c-12.85 45.28-67.3 105.85-159.08 105.85-97.89 0-171.93-72.81-171.93-168.26Zm352.41-.61c0-96.67 73.42-168.87 176.21-168.87s176.82 72.2 176.82 168.87-73.42 168.87-176.82 168.87-176.21-72.81-176.21-168.87m250.24-.61c0-45.89-28.76-77.71-73.42-77.71s-74.04 31.82-74.04 78.32 29.37 78.32 74.04 78.32 73.42-32.43 73.42-78.92Zm461.95-43.44V568.9h-102.18V384.13c0-34.88-23.86-55.06-54.45-55.06s-55.68 22.03-55.68 51.39V568.9h-102.17V246.47h96.05v25.7c22.03-20.19 53.84-33.65 93-33.65 75.87 0 125.42 51.4 125.42 125.43Zm362.23-117.47-114.41 322.44h-119.31l-114.41-322.44h105.24l68.52 222.1 69.14-222.1h105.24Zm0 161.52c0-96.67 73.42-168.87 176.21-168.87s176.82 72.2 176.82 168.87-73.42 168.87-176.82 168.87-176.21-72.81-176.21-168.87m250.24-.61c0-45.89-28.76-77.71-73.42-77.71s-74.04 31.82-74.04 78.32 29.37 78.32 74.04 78.32 73.42-32.43 73.42-78.92ZM356.04 0C159.41 0 0 159.41 0 356.04s159.41 356.04 356.04 356.04 356.04-159.41 356.04-356.04S552.68 0 356.04 0m-165 424.56h-32.16V287.52h32.16zm140.27 68.76h-62.76V218.77h62.76zm175.19 73.52h-98.1V145.25h98.1z"/></svg> \ No newline at end of file diff --git a/.github/assets/logos/Iterable.svg b/.github/assets/logos/Iterable.svg new file mode 100644 index 0000000..7d87c53 --- /dev/null +++ b/.github/assets/logos/Iterable.svg @@ -0,0 +1 @@ +<svg xmlns="http://www.w3.org/2000/svg" height="20" fill="none" viewBox="0 0 219 48"><path fill="#666" fill-rule="evenodd" d="m39.873 24.108-4.215 4.261-4.087-4.132-8.504 8.598L27.1 36.91l-4.016 4.06 4.397 4.445 16.734-16.918z" clip-rule="evenodd"/><path fill="#666" fill-rule="evenodd" d="m6.624 23.774-4.687 4.739L18.67 45.43l4.412-4.461-4.031-4.076 4.016-4.06-8.556-8.649-3.74 3.782zM27.353 2.93l-3.939 3.982 4.092 4.137-4.49 4.538 8.556 8.65 4.214-4.26 4.087 4.131 4.214-4.26z" clip-rule="evenodd"/><path fill="#666" fill-rule="evenodd" d="M19.077 2.528 2.343 19.446l4.28 4.328 3.741-3.782 4.147 4.193 8.505-8.598-4.092-4.136 4.49-4.539z" clip-rule="evenodd"/><path fill="#999" fill-rule="evenodd" d="m23.068 32.834-4.016 4.06 4.03 4.075 4.017-4.06zm12.718-12.857-4.215 4.26 4.087 4.132 4.215-4.26zm-25.421.015-3.74 3.782 4.147 4.193 3.74-3.782zM23.414 6.913l-4.49 4.538 4.092 4.137 4.49-4.538z" clip-rule="evenodd"/><path fill="#aaa" fill-rule="evenodd" d="M18.8 11.35c-2.341-2.368-2.341-6.207 0-8.574a5.95 5.95 0 0 1 8.48 0c2.343 2.367 2.343 6.206 0 8.573a5.95 5.95 0 0 1-8.48 0m25.436 17.186a5.95 5.95 0 0 1-8.48 0c-2.342-2.368-2.342-6.206 0-8.574a5.95 5.95 0 0 1 8.48 0c2.342 2.368 2.342 6.206 0 8.574M18.802 45.678c-2.341-2.367-2.341-6.206 0-8.573a5.95 5.95 0 0 1 8.48 0c2.343 2.367 2.343 6.206 0 8.573a5.95 5.95 0 0 1-8.48 0M1.85 28.48c-2.343-2.367-2.343-6.206 0-8.573a5.95 5.95 0 0 1 8.48 0c2.341 2.367 2.341 6.206 0 8.573a5.95 5.95 0 0 1-8.48 0" clip-rule="evenodd"/><path fill="#666" fill-rule="evenodd" d="M76.282 29.994h-3.187a.705.705 0 0 1-.698-.705h-.001v-11.16c0-.388.312-.705.697-.705h3.21c.2 0 .363-.165.363-.367v-3.11a.366.366 0 0 0-.364-.367H64.366a.366.366 0 0 0-.364.367v3.11c0 .202.163.367.364.367h3.383c.383 0 .697.317.697.705V29.31a.7.7 0 0 1-.696.684h-3.375a.37.37 0 0 0-.363.368v3.109c0 .203.165.368.365.368h4.335c.032.009.064.02.098.02h3.221c.035 0 .066-.011.098-.02h4.153a.365.365 0 0 0 .363-.368V30.36a.366.366 0 0 0-.364-.367M96.769 13.58h-12.63a.366.366 0 0 0-.364.367v3.11c0 .202.161.367.362.367h3.718c.383 0 .697.317.697.705v15.362c0 .202.164.368.365.368h3.22a.367.367 0 0 0 .365-.369V18.13c0-.39.313-.706.697-.706h3.57a.367.367 0 0 0 .365-.367v-3.11a.37.37 0 0 0-.365-.367m18.676 0h-10.987a.375.375 0 0 0-.369.373v19.532c0 .207.165.375.369.375h10.987a.375.375 0 0 0 .371-.375V30.39a.37.37 0 0 0-.37-.375h-6.706a.705.705 0 0 1-.703-.711V26.09c0-.392.315-.711.705-.711h6.144a.375.375 0 0 0 .37-.375V22.2a.373.373 0 0 0-.37-.375h-6.145a.705.705 0 0 1-.704-.71v-2.98c0-.392.315-.711.705-.711h6.705a.375.375 0 0 0 .369-.375v-3.096a.373.373 0 0 0-.37-.375m16.794 8.706c-.563.516-1.301.78-2.195.78h-1.594a.705.705 0 0 1-.7-.708v-4.225a.705.705 0 0 1 .7-.705h1.594c.893 0 1.632.267 2.196.795.567.532.855 1.213.855 2.023 0 .831-.288 1.518-.857 2.04m1.637 4.559a.705.705 0 0 1 .24-.959 6 6 0 0 0 2.107-2.146 6.9 6.9 0 0 0 .91-3.495 6.75 6.75 0 0 0-.839-3.261 6.45 6.45 0 0 0-2.4-2.468c-1.038-.621-2.255-.934-3.617-.934h-6.109a.37.37 0 0 0-.366.369v19.54c0 .204.165.37.366.37h3.217a.367.367 0 0 0 .366-.37v-5.902a.705.705 0 0 1 .7-.707h.95c.261 0 .5.144.62.378l3.315 6.404a.36.36 0 0 0 .324.198h3.643a.37.37 0 0 0 .315-.555zm20.523-.179a.7.7 0 0 1-.574.297H150.8a.705.705 0 0 1-.665-.939l1.512-4.269a.7.7 0 0 1 .665-.468c.301 0 .562.183.663.468l1.512 4.269a.7.7 0 0 1-.089.642m6.621 7.195c.125 0 .236-.06.306-.163a.36.36 0 0 0 .038-.345l-7.731-19.539a.38.38 0 0 0-.345-.234h-1.98a.36.36 0 0 0-.344.234l-7.731 19.54a.36.36 0 0 0 .038.345.36.36 0 0 0 .306.162h3.522a.375.375 0 0 0 .348-.246l.969-2.7a.705.705 0 0 1 .663-.465h6.495a.705.705 0 0 1 .664.471l.942 2.691a.375.375 0 0 0 .35.25zm10.681-8.083c0-.333.269-.606.599-.606h2.656c.797 0 1.452.234 1.947.693a2.25 2.25 0 0 1 .753 1.715 2.36 2.36 0 0 1-.735 1.756c-.488.471-1.147.71-1.965.71h-2.62a.64.64 0 0 1-.635-.642zm0-7.74c0-.337.27-.612.603-.612h1.749c.774 0 1.395.203 1.844.603q.57.51.664 1.292a2.2 2.2 0 0 1-.661 1.875c-.47.439-1.101.661-1.877.661h-1.723a.6.6 0 0 1-.599-.606zm-3.558 15.824h7.57c1.304 0 2.438-.277 3.37-.818a5.55 5.55 0 0 0 2.12-2.13 5.76 5.76 0 0 0 .724-2.805c0-1.139-.276-2.154-.82-3.015a5.55 5.55 0 0 0-2.216-1.994.21.21 0 0 1-.11-.159l-.01-.09.079-.088c1.152-.998 1.737-2.289 1.737-3.843 0-.966-.223-1.865-.664-2.672-.44-.802-1.118-1.452-2.016-1.935-.903-.486-2.024-.732-3.33-.732h-6.423a.405.405 0 0 0-.402.405v19.481c0 .217.175.396.391.396m31.154-.001a.375.375 0 0 0 .37-.376V30.39a.375.375 0 0 0-.37-.375h-5.714a.705.705 0 0 1-.703-.711V13.953a.375.375 0 0 0-.371-.375H189.3a.375.375 0 0 0-.371.375v19.532c0 .206.167.374.37.374zm18.696-3.846h-6.707a.705.705 0 0 1-.702-.711v-3.218a.705.705 0 0 1 .702-.71h6.147a.37.37 0 0 0 .369-.373V22.2a.37.37 0 0 0-.369-.372h-6.147a.705.705 0 0 1-.702-.711v-2.982a.705.705 0 0 1 .702-.71h6.707a.37.37 0 0 0 .369-.372V13.95a.37.37 0 0 0-.37-.372h-10.989a.37.37 0 0 0-.369.372v19.535a.37.37 0 0 0 .37.372h10.989a.37.37 0 0 0 .369-.372v-3.1a.37.37 0 0 0-.37-.373" clip-rule="evenodd"/></svg> \ No newline at end of file diff --git a/.github/assets/logos/LangChain.svg b/.github/assets/logos/LangChain.svg new file mode 100644 index 0000000..83d288d --- /dev/null +++ b/.github/assets/logos/LangChain.svg @@ -0,0 +1 @@ +<svg xmlns="http://www.w3.org/2000/svg" height="20" fill="none" viewBox="0 0 240 41"><g fill="#666"><path d="M61.514 11.157a3.94 3.94 0 0 0-2.806 1.158l-3.018 3.01a3.95 3.95 0 0 0-1.147 3.095l.019.191a3.9 3.9 0 0 0 1.128 2.314 3.6 3.6 0 0 0 1.496.9q.046.263.047.53c0 .797-.31 1.546-.874 2.107l-.186.186c-1.008-.344-1.848-.847-2.607-1.604a6.9 6.9 0 0 1-1.927-3.67l-.034-.193-.153.124a4 4 0 0 0-.294.265l-3.018 3.01a3.957 3.957 0 0 0 2.807 6.757 3.96 3.96 0 0 0 2.806-1.158l3.019-3.01a3.96 3.96 0 0 0 0-5.599 3.9 3.9 0 0 0-1.462-.92 3.25 3.25 0 0 1 .924-2.855 6.9 6.9 0 0 1 2.664 1.656 6.9 6.9 0 0 1 1.926 3.67l.035.193.153-.124q.155-.125.296-.267l3.018-3.01a3.956 3.956 0 0 0-2.808-6.756z"/><path d="M59.897.149h-39.49C9.153.149 0 9.279 0 20.5c0 11.222 9.154 20.351 20.406 20.351h39.49c11.253 0 20.407-9.13 20.407-20.35C80.303 9.277 71.149.148 59.897.148M40.419 32.056c-.651.134-1.384.158-1.882-.36-.183.42-.612.199-.943.144-.03.085-.057.16-.085.246-1.1.073-1.925-1.046-2.449-1.89-1.04-.562-2.222-.904-3.285-1.492-.062.968.15 2.17-.774 2.794-.047 1.862 2.824.22 3.088 1.608-.204.022-.43-.033-.594.124-.749.726-1.608-.55-2.471-.022-1.16.581-1.276 1.058-2.71 1.178-.08-.12-.047-.2.02-.273.404-.468.433-1.02 1.122-1.219-.71-.112-1.303.279-1.901.588-.778.318-.772-.716-1.968.055-.132-.108-.069-.205.007-.289.304-.37.704-.425 1.155-.405-2.219-1.233-3.263 1.508-4.288.145-.308.081-.424.358-.618.553-.167-.183-.04-.405-.033-.62-.2-.094-.453-.139-.394-.458-.391-.133-.665.1-.957.32-.263-.204.178-.502.26-.713.234-.407.769-.084 1.04-.377.772-.437 1.847.273 2.729.153.68.085 1.52-.61 1.179-1.305-.726-.925-.598-2.136-.614-3.244-.09-.645-1.643-1.467-2.092-2.163-.555-.627-.987-1.353-1.42-2.067-1.561-3.015-1.07-6.887-3.037-9.686-.89.49-2.048.259-2.816-.399-.414.377-.432.87-.465 1.393-.994-.992-.87-2.864-.075-3.967a5.3 5.3 0 0 1 1.144-1.11c.098-.07.131-.14.129-.25.786-3.524 6.144-2.845 7.838-.348 1.229 1.537 1.6 3.57 2.994 4.997 1.875 2.047 4.012 3.85 5.742 6.03 1.637 1.992 2.806 4.328 3.826 6.683.416.782.42 1.74 1.037 2.408.304.403 1.79 1.5 1.467 1.888.186.403 1.573.959 1.092 1.35zm26.026-12.024-3.018 3.01a6.96 6.96 0 0 1-2.875 1.728l-.056.016-.02.053a6.9 6.9 0 0 1-1.585 2.446l-3.019 3.01a6.94 6.94 0 0 1-4.932 2.035 6.94 6.94 0 0 1-4.932-2.035 6.95 6.95 0 0 1 0-9.838l3.018-3.01a6.9 6.9 0 0 1 2.871-1.721l.055-.017.02-.052a6.9 6.9 0 0 1 1.59-2.455l3.019-3.01a6.94 6.94 0 0 1 4.932-2.035c1.865 0 3.616.723 4.932 2.035a6.9 6.9 0 0 1 2.04 4.92c0 1.86-.724 3.607-2.04 4.918z"/><path d="M28.142 28.413c-.265 1.03-.35 2.782-1.694 2.832-.11.595.413.819.89.627.472-.215.696.171.855.556.729.106 1.806-.242 1.847-1.103-1.088-.625-1.424-1.813-1.896-2.914zm71.067-17.597h-3.585v21.746H111v-3.464H99.209zm29.812 21.746h3.585v-.038h.047l.007-.16c.001-.047.014-.482-.052-1.152v-8.094c0-3.045 2.22-4.43 4.283-4.43 2.219 0 3.299 1.195 3.299 3.657v10.217h3.585V21.867c0-4.062-2.581-6.586-6.734-6.586-1.765 0-3.34.502-4.577 1.454l-.033-1.156h-3.405v16.983zm29.344-15.735c-1.246-1.012-2.848-1.546-4.655-1.546-4.834 0-7.837 3.334-7.837 8.7s3.003 8.73 7.837 8.73c1.705 0 3.227-.465 4.426-1.348-.103 2.632-1.752 4.196-4.455 4.196-2.273 0-3.559-.724-3.823-2.15l-.033-.178-3.483 1.063.031.143c.588 2.835 3.241 4.528 7.1 4.528 2.618 0 4.671-.713 6.104-2.12 1.446-1.418 2.178-3.462 2.178-6.073V15.579h-3.316l-.072 1.248zm-.224 7.304c0 3.24-1.578 5.175-4.223 5.175-2.835 0-4.46-1.94-4.46-5.324s1.626-5.295 4.46-5.295c2.581 0 4.197 1.926 4.223 5.026zm21.277 1.536c-.743 2.403-2.651 3.728-5.371 3.728-3.889 0-6.307-2.953-6.307-7.707s2.439-7.706 6.367-7.706c2.718 0 4.284 1.065 5.081 3.453l.372 1.117 3.385-1.59-.318-.894c-1.289-3.632-4.266-5.55-8.609-5.55-2.951 0-5.456 1.065-7.245 3.08-1.77 1.996-2.707 4.792-2.707 8.092 0 6.786 3.917 11.172 9.981 11.172 4.273 0 7.583-2.236 8.853-5.982l.324-.957-3.477-1.322-.331 1.067zm13.388-10.386c-1.712 0-3.235.47-4.431 1.36V8.733h-3.585v23.832h3.585V23.12c0-3.064 2.219-4.46 4.283-4.46 2.219 0 3.299 1.196 3.299 3.657v10.249h3.585V21.84c0-3.983-2.643-6.555-6.734-6.555zm27.69-7.061c-1.332 0-2.299.967-2.299 2.298s.967 2.299 2.299 2.299 2.298-.968 2.298-2.299-.967-2.299-2.298-2.299m12.766 7.062c-1.765 0-3.339.502-4.576 1.454l-.034-1.156h-3.404v16.983h3.585v-9.444c0-3.045 2.219-4.43 4.283-4.43 2.219 0 3.299 1.195 3.299 3.657v10.217H240V21.867c0-4.062-2.581-6.586-6.734-6.586zm-11.025.299h-3.567v8.418c-.99-.831-2.156-1.46-3.472-1.87v-.857c0-3.75-2.462-5.99-6.587-5.99-3.351 0-5.857 1.574-6.878 4.315l-.275.74 2.874 2.118.493-1.285c.65-1.694 1.854-2.483 3.786-2.483 1.933 0 3.002.93 3.002 2.762v.096a12 12 0 0 0-.347-.01c-3.838-.061-6.638.837-8.322 2.664-1.723 1.87-1.572 3.97-1.549 4.202l.016.162h.016c.268 2.616 2.553 4.295 5.874 4.295 1.829 0 3.519-.509 4.797-1.44l.014 1.144h3.086v-5.313l-.067-.048c-.436-.32-1.197-.732-2.342-.877a7 7 0 0 0-1.018-.061h-.16v.488c0 1.124-1.291 2.705-4.163 2.705-2.12 0-2.436-.892-2.436-1.424v-.054c.016-.239.116-.823.643-1.366.669-.691 2.175-1.5 5.552-1.449 2.445.039 4.321.722 5.577 2.033 1.541 1.608 1.831 3.81 1.884 4.673v.694h3.567V15.58zm-102.485-.373c-3.351 0-5.857 1.573-6.878 4.315l-.275.74 2.874 2.118.493-1.285c.65-1.694 1.854-2.483 3.786-2.483 1.933 0 3.002.93 3.002 2.762v.43l-3.692.652c-4.328.765-6.523 2.616-6.523 5.498s2.316 4.83 5.901 4.83c1.83 0 3.519-.51 4.797-1.44l.015 1.143h3.085v-11.29c0-3.75-2.461-5.99-6.585-5.99m3.002 9.957v1.513c0 1.124-1.292 2.705-4.164 2.705-2.12 0-2.435-.891-2.435-1.423 0-.472 0-1.578 3.755-2.274z"/></g></svg> \ No newline at end of file diff --git a/.github/assets/logos/Mastra.svg b/.github/assets/logos/Mastra.svg new file mode 100644 index 0000000..fb8ec11 --- /dev/null +++ b/.github/assets/logos/Mastra.svg @@ -0,0 +1 @@ +<svg xmlns="http://www.w3.org/2000/svg" height="20" fill="none" viewBox="0 0 130 22"><path fill="#666" d="M5 12.3a4.5 4.5 0 1 1 0 9 4.5 4.5 0 0 1 0-9M10.9.7a4.5 4.5 0 0 1 4.4 5.4c-.3 1.4-.6 3 .2 4.2l1.3 1.9.3.2.3-.2 1.3-1.9c.8-1.2.5-2.7.2-4.1a4.5 4.5 0 1 1 8.8.1c-.3 1.3-.6 2.7 0 3.9l1.3 2v.1a4.5 4.5 0 1 1-4.3 3.4c.3-1.3.6-2.7 0-3.9l-1.2-2h-.2L22 11.5c-.8 1.2-.5 2.8-.2 4.2a4.5 4.5 0 1 1-8.8.3q.5-2-.4-3.8l-.9-1.3q-.9-1.2-2.4-1.6A4.5 4.5 0 0 1 10.9.6M56.6 17v-6.9q0-1-.7-1.7t-1.7-.7q-1.3 0-2.1 1T51 11v6h-2.8V5.6h2.9v2.2q.6-1 1.6-1.8 1-.6 2.5-.7 1.2 0 2.3.7T59 7.8q.6-1.2 1.7-1.8a5 5 0 0 1 2.5-.7q2 0 3.1 1.2 1.3 1.2 1.3 3.2V17h-3v-6.6q0-1.4-.6-2t-1.7-.7q-1.2 0-2.1.9t-.9 2.3V17zm18.6.3q-1.5 0-3-.7a6 6 0 0 1-2-2.2q-.7-1.5-.7-3 0-1.8.7-3.2a6 6 0 0 1 5-3q1.4 0 2.6.7t1.8 1.6V5.6h3V17h-3v-2q-.6 1.2-1.8 1.8-1.2.5-2.6.5M76 15q1.6 0 2.6-1t1-2.7-1-2.7-2.6-1-2.6 1-1 2.7 1 2.7 2.6 1m14 2.3a7 7 0 0 1-4.1-1q-1.5-1.2-1.6-3L87 13q0 1 .8 1.6t2.2.7a3 3 0 0 0 1.7-.5q.7-.4.7-1a1 1 0 0 0-.6-1l-1.4-.6-3.8-.9q-.8-.3-1.4-.9t-.6-1.7q0-1.6 1.4-2.6t3.8-1 3.6 1A3 3 0 0 1 95 8.6l-2.8.1q0-.6-.6-1.2T89.8 7q-1 0-1.7.4t-.6 1 .6 1l1.5.4 3.7.9q.8.3 1.5 1 .5.6.5 1.7 0 1.8-1.4 2.8-1.5 1-4 1m12.6 0q-1.9 0-3-1a4 4 0 0 1-1.2-2.8V7.7h-2.5V5.5h2.5V2.2h2.9v3.4h3.7v2.2h-3.7V13q0 1 .4 1.4.5.5 1.3.5l1-.2.9-.6.4 2.4q-.3.3-1.1.5-.7.2-1.6.2m4.3-.3V5.6h2.9V8q.5-1.2 1.5-2a4 4 0 0 1 4.2-.4l-.3 2.8-.9-.4-1-.2-1 .2q-.7.2-1.2.6t-1 1.2q-.3.7-.3 2V17zm14.7.3q-1.6 0-3-.7a6 6 0 0 1-2-2.2q-.8-1.5-.8-3 0-1.8.7-3.2a6 6 0 0 1 5-3q1.5 0 2.6.7t1.9 1.6V5.5h2.9V17h-3v-2q-.6 1.2-1.8 1.8-1.1.5-2.5.5m.7-2.3q1.6 0 2.6-1t1-2.7-1-2.7-2.6-1-2.6 1-1 2.7 1 2.7 2.6 1"/></svg> \ No newline at end of file diff --git a/.github/assets/logos/Stack.svg b/.github/assets/logos/Stack.svg new file mode 100644 index 0000000..305da9b --- /dev/null +++ b/.github/assets/logos/Stack.svg @@ -0,0 +1 @@ +<svg xmlns="http://www.w3.org/2000/svg" height="20" viewBox="0 0 164 37"><path fill="#666" d="M14.42 2.582c-2.267 1.207-4.118 2.22-4.107 2.242.01.036 8.141 4.306 8.259 4.337.047.016 8.21-4.938 8.21-4.98 0-.047-8.093-3.815-8.178-3.81-.032.005-1.921.998-4.183 2.21m59.629-.543-2.907 1.61-.016 1.311-.01 1.312h-4.482v5.017h4.476l.021 5.733c.016 6.114.016 6.015.283 7.175.688 2.99 2.673 4.97 5.623 5.613 1.665.36 3.996.277 5.874-.204.395-.104 1.67-.523 1.745-.575.021-.02-1.595-4.86-1.643-4.907-.027-.026-.235.031-.491.136-1.84.768-3.66.648-4.615-.308-.363-.366-.688-1.004-.79-1.558-.064-.345-.074-1.374-.08-5.759v-5.346l3.324-.01 3.319-.016.016-2.498.01-2.493h-6.615V3.345c0-2.31-.016-2.927-.064-2.922-.038.005-1.377.732-2.977 1.615m70.439.005-2.951 1.626v26.067h5.976l.01-5.3.016-5.304 4.674 5.289 4.668 5.289 3.558.015 3.559.01v-.5l-5.522-5.959c-3.036-3.271-5.516-5.978-5.506-6.015.006-.031 2.204-2.466 4.882-5.403 4.071-4.468 4.866-5.362 4.866-5.488v-.15h-7.144l-4.017 4.693-4.018 4.698-.016-7.599c-.005-4.175-.026-7.593-.053-7.593-.021 0-1.366.732-2.982 1.625M23.048 7.52l-4.349 2.707-.032 2.948c-.016 1.62-.037 6.02-.042 9.772l-.006 6.826 4.375 2.466a736 736 0 0 0 4.402 2.462c.016 0 .026-2.232.026-4.955v-4.954l-4.375-2.32c-2.406-1.28-4.374-2.336-4.374-2.352s1.974-1.113 4.39-2.44l4.386-2.41.016-5.23c.005-2.88-.005-5.232-.027-5.227-.026 0-2 1.218-4.39 2.707m30.169-1.95c-.987.085-2.491.372-3.387.654-.46.146-1.356.528-1.804.763-2.118 1.129-3.393 2.827-3.697 4.918-.09.632-.037 1.902.107 2.508.256 1.082.715 1.934 1.467 2.713a7.2 7.2 0 0 0 1.873 1.41c1.995 1.025 3.547 1.401 7.122 1.715 2.95.261 4.06.92 4.06 2.404 0 .261-.032.527-.09.684-.465 1.349-2.412 2.268-4.53 2.138-2.465-.152-5.522-1.25-7.219-2.592a7 7 0 0 1-.56-.502c-.133-.146-.17-.167-.23-.11-.037.037-.703.972-1.488 2.08l-1.419 2.012.427.397c2.177 2.038 4.743 3.235 7.773 3.627.934.12 3.153.194 4.023.13 3.243-.24 5.602-1.16 7.234-2.832q2.312-2.361 1.942-5.874c-.117-1.15-.565-2.357-1.179-3.161-.341-.455-1.051-1.119-1.547-1.443-1.66-1.097-3.708-1.636-7.33-1.923-1.66-.136-2.535-.313-3.335-.69-1.771-.83-1.755-2.937.032-3.726 1.232-.543 3.33-.674 5.196-.313 1.286.245 2.364.747 3.41 1.583.389.313.394.313.48.204.048-.058.779-.904 1.627-1.882.843-.977 1.542-1.797 1.542-1.818.005-.068-.843-.727-1.409-1.092a11.5 11.5 0 0 0-3.617-1.563c-1.2-.298-2.15-.402-3.793-.423a49 49 0 0 0-1.68.005m75.09.021c-4.47.413-8.024 2.587-9.811 6.004-.805 1.537-1.227 3.058-1.398 5.017-.08.915-.037 2.755.086 3.611.768 5.467 4.444 9.14 10.051 10.03 2.086.334 4.444.234 6.263-.257 1.062-.287 2.246-.8 3.132-1.348.736-.46 2.401-1.777 2.401-1.902 0-.027-.56-.586-1.238-1.234-.683-.648-1.574-1.5-1.974-1.886l-.731-.7-.192.15c-.667.534-.976.738-1.547 1.015-.742.36-1.11.48-1.921.627-1.798.33-3.788.063-5.148-.695-1.937-1.072-3.062-2.906-3.254-5.315-.272-3.36 1.392-6.161 4.294-7.238 1.163-.428 2.7-.554 3.9-.314 1.158.236 2.364.79 3.228 1.5.133.11.256.199.272.199.064 0 3.697-3.773 3.697-3.836 0-.037-.165-.22-.373-.402-1.862-1.673-3.799-2.603-6.162-2.948-.715-.104-2.785-.151-3.575-.078m-30.601.11c-5.832.548-9.828 4.19-10.687 9.725a16.6 16.6 0 0 0 .075 5.608c.88 4.672 3.815 7.849 8.301 8.983 1.852.465 4.018.56 5.57.24 1.601-.329 3.004-.935 4.194-1.813.672-.491 1.387-1.29 1.707-1.897.096-.178.128-.209.139-.13.016.125.282 3.203.282 3.27 0 .027 1.174.048 2.801.048h2.801V6.219h-5.708v.115c0 .125-.155 2.744-.176 2.968-.011.11-.048.073-.273-.261-.352-.523-1.221-1.359-1.835-1.766-1.264-.847-2.838-1.411-4.396-1.573-.587-.063-2.14-.063-2.795 0m3.531 5.252c1.483.22 2.945 1.013 3.868 2.095.406.47.72.92.918 1.307 1.456 2.817 1.04 6.402-.992 8.623-1.276 1.395-2.983 2.106-5.09 2.111-2.091.005-3.804-.638-5.116-1.928-1.355-1.328-2.05-3.1-2.05-5.242 0-3.533 2.055-6.177 5.357-6.898.934-.204 2.044-.23 3.105-.068M5.72 17.93a2785 2785 0 0 0-4.312 2.619c-.016.015 1.409.81 3.164 1.766 1.755.961 3.756 2.054 4.45 2.43l1.259.69L14.41 22.8c2.273-1.448 4.135-2.65 4.145-2.676.01-.036-2.32-1.343-8.045-4.51l-.507-.282zm27.053 4.296-4.39 2.67v4.934c0 2.712.02 4.923.042 4.912.027-.005 2.006-1.165 4.402-2.57l4.358-2.556v-5.033a628 628 0 0 0-.01-5.033c-.01 0-1.99 1.202-4.402 2.676M1.227 26.694v4.954l4.353 2.347a567 567 0 0 0 4.402 2.367c.027.01.043-2.137.037-4.954l-.016-4.975-4.348-2.346c-2.39-1.291-4.364-2.347-4.385-2.347-.027 0-.043 2.226-.043 4.954"/></svg> \ No newline at end of file diff --git a/.github/assets/logos/gram.svg b/.github/assets/logos/gram.svg new file mode 100644 index 0000000..22584ce --- /dev/null +++ b/.github/assets/logos/gram.svg @@ -0,0 +1 @@ +<svg xmlns="http://www.w3.org/2000/svg" height="20" fill="none" viewBox="0 0 200 57"><path fill="#666" d="M157.076 36.609V8.354h3.934l.609 3.767c1.33-1.994 3.934-4.1 8.255-4.1 3.934 0 7.313 1.773 8.92 5.375 1.717-3.048 4.764-5.374 9.806-5.374 5.873 0 10.692 3.213 10.692 11.523V36.61h-4.542V19.822c0-4.93-2.438-7.645-6.704-7.645-4.709 0-7.59 3.324-7.59 8.532v15.9h-4.543V19.822c0-4.93-2.493-7.645-6.814-7.645-4.654 0-7.48 3.656-7.48 8.753v15.68z"/><path fill="#666" d="M139.069 36.941c-6.205 0-10.416-3.102-10.416-8.255 0-5.65 3.934-8.808 11.358-8.808h8.31v-1.94c0-3.822-2.604-5.872-7.036-5.872-4.155 0-6.648 1.884-7.202 4.764h-4.543c.665-5.54 5.152-8.808 11.966-8.808 7.203 0 11.358 3.6 11.358 10.193V30.57c0 1.496.554 1.884 1.828 1.884h5.945v4.155h-6.998c-3.601 0-5.097-1.662-5.097-4.266v-.11c-1.662 2.548-4.598 4.708-9.473 4.708m.332-3.878c5.651 0 8.92-3.269 8.92-7.978V23.7h-8.754c-4.044 0-6.315 1.496-6.315 4.71 0 2.991 2.382 4.653 6.149 4.653m-24.233 3.547V8.355h3.934l.609 4.266c1.33-2.161 3.49-4.266 8.532-4.266h1.274v4.432h-2.271c-5.928 0-7.535 4.93-7.535 9.418V36.61zM97.295 48.022c-7.202 0-12.244-3.38-12.964-9.585h4.654c.775 3.546 3.933 5.54 8.642 5.54 5.928 0 9.142-2.77 9.142-8.92v-2.991c-1.884 2.604-4.931 4.875-9.696 4.875-7.811 0-13.573-5.263-13.573-14.46 0-8.642 5.762-14.46 13.573-14.46 4.765 0 7.978 1.94 9.696 4.654l.609-4.321h3.934v26.87c0 8.144-4.322 12.798-14.017 12.798m.222-15.125c5.54 0 9.307-4.266 9.307-10.305 0-6.205-3.767-10.526-9.307-10.526-5.596 0-9.363 4.266-9.363 10.415 0 6.15 3.767 10.416 9.363 10.416"/><path fill="#666" fill-rule="evenodd" d="M26.467.431a3.08 3.08 0 0 1 3.075.005L54.367 13.48l.028.014.01.007.021.013a3.29 3.29 0 0 1 1.572 2.813v2.751c0 1.133-.58 2.218-1.56 2.808l-.03.018-.009.005-.03.016-1.834.935 1.846.996.023.012.01.006.017.012A3.3 3.3 0 0 1 56 26.697v3.437c0 1.132-.578 2.213-1.558 2.804l.002.002-.022.014-.016.01-.025.012-.002-.002-1.737.913 1.732.91.025.013.01.007.024.014A3.28 3.28 0 0 1 56 37.643v2.063c0 1.133-.579 2.218-1.56 2.808l-.023.014-.009.007-.025.01-24.85 13.06-.113.061-.005-.004a3 3 0 0 1-.645.246l-.005.012-.047.102h-.716c-.527 0-1.06-.138-1.542-.42L1.626 42.558l-.025-.014-.01-.004-.022-.014A3.29 3.29 0 0 1 0 39.711V16.328c0-1.133.579-2.216 1.56-2.806l.023-.016.009-.004.025-.014zm1.381 2.229a.5.5 0 0 0-.113.052l-.027.015-.007.003-.025.013-24.802 13.03a.67.67 0 0 0-.283.56v23.382c0 .249.13.46.294.564L27.667 53.3l.002.003.023.011.007.005.025.013a.51.51 0 0 0 .53.007l.022-.016.037-.018L53.11 40.272a.67.67 0 0 0 .287-.557V37.65a.67.67 0 0 0-.285-.56l-3.28-1.723L29.527 46.04l-.002-.002a3.08 3.08 0 0 1-3.062.002L7.384 36.312l-.031-.016-.012-.007-.027-.018a2.3 2.3 0 0 1-1.105-1.966v-.516c0-.792.405-1.543 1.09-1.96l.022-.015.014-.009.027-.013 6.868-3.612-6.857-3.62-.025-.012-.016-.01-.022-.015a2.3 2.3 0 0 1-1.1-1.963v-.517c0-.791.405-1.542 1.09-1.96l.034-.021.013-.01.036-.015 19.094-9.579a3.07 3.07 0 0 1 3.069.014L49.75 21.366l3.379-1.73a.67.67 0 0 0 .276-.55v-2.75a.68.68 0 0 0-.292-.567L28.33 2.75l-.028-.013-.01-.007-.026-.016a.53.53 0 0 0-.27-.075h-.033l-.116.02ZM8.802 33.975v.127l18.852 9.62h.004l.03.015.006.004.03.019c.178.108.379.1.534.006l.025-.013.007-.005.027-.013 18.716-9.837-7.921-4.162-9.597 4.911-.002-.002a3.07 3.07 0 0 1-3.062-.014l-9.425-4.979zm19.472-9.941a.52.52 0 0 0-.528-.01l-.022.017-.016.009-.03.013-7.846 4.122 7.837 4.139.041.02.023.014a.5.5 0 0 0 .527.005l.028-.019.01-.006.033-.014 7.961-4.076-7.961-4.183-.003-.002-.025-.011-.004-.002zm13.68 4.246 7.88 4.141 3.292-1.73a.67.67 0 0 0 .277-.55v-3.437c0-.25-.13-.463-.295-.567l-3.394-1.831zM28.27 12.74a.51.51 0 0 0-.53-.01l.002.003-.032.02-.018.01-.034.013-18.852 9.459v.127l8.227 4.345 9.432-4.957a3.07 3.07 0 0 1 1.537-.41c.54.001 1.068.148 1.531.415l9.597 5.04 7.8-3.99-18.613-10.038-.002-.002-.02-.011-.005-.003z" clip-rule="evenodd"/></svg> \ No newline at end of file diff --git a/.github/assets/logos/helicone.svg b/.github/assets/logos/helicone.svg new file mode 100644 index 0000000..227187d --- /dev/null +++ b/.github/assets/logos/helicone.svg @@ -0,0 +1 @@ +<svg xmlns="http://www.w3.org/2000/svg" height="20" fill-rule="evenodd" clip-rule="evenodd" viewBox="0 0 2952 592"><g fill="#666"><path d="M291.5 4.5a33.7 33.7 0 0 1 11 1.5c93.33 41 186.67 82 280 123a30 30 0 0 1 2.5 4.5q1.005 161.5 0 323c-2.46 9.131-8.3 14.965-17.5 17.5A34503 34503 0 0 1 295 587.5 24719 24719 0 0 1 12.5 469q-5.822-4.392-7.5-11.5-1-162 0-324l2-4a35144 35144 0 0 1 284.5-125m272 235a5686 5686 0 0 1 0 213 10199 10199 0 0 1-179 74.5c59.8-95.777 119.47-191.611 179-287.5m-330 4a2293 2293 0 0 0 50 22 43.2 43.2 0 0 1 .5 16 51403 51403 0 0 1-246 176 83 83 0 0 1-11.5-5 561 561 0 0 1-.5-58 85298 85298 0 0 1 207.5-151m50 64a8010 8010 0 0 1 0 253 32889 32889 0 0 1-222-93.5 38522 38522 0 0 0 222-159.5"/><path d="M292.5 25.5a39.9 39.9 0 0 1 14 4.5 39680 39680 0 0 1 243 107 42686 42686 0 0 0-253 111 21215 21215 0 0 1-256-111 106681 106681 0 0 0 252-111.5m596 50h75c.17 36.002 0 72.002-.5 108a838 838 0 0 1-2.5 34q2.835.305 4.5-2c15.06-21.794 35.9-34.294 62.5-37.5 28.82-3.904 55.49 1.763 80 17 21.17 18.17 32.33 41.337 33.5 69.5.5 63.666.67 127.332.5 191h-75c.17-54.668 0-109.334-.5-164-.41-43.09-21.57-60.257-63.5-51.5-18.34 7.01-29.5 20.177-33.5 39.5a201.2 201.2 0 0 0-5 42c-.5 44.665-.67 89.332-.5 134h-75zm624 0h75v380h-75zm179 0q22.65-3.284 42 8.5c5.12 4.74 8.29 10.573 9.5 17.5q1.005 10 0 20c-1 8.834-5.16 15.668-12.5 20.5-15.13 7.038-30.8 8.371-47 4-15.6-5.851-22.94-17.185-22-34q.03-30.893 30-36.5m-1666 77q-.499 108.254 1 216-.09.814-1 1a5893 5893 0 0 1 0-217"/><path d="M25.5 152.5a23074 23074 0 0 1 186 81.5 35133 35133 0 0 1-185 134.5 11663 11663 0 0 1-1-216m537 0h3c.17 15.67 0 31.337-.5 47l-214.5 343a1880 1880 0 0 0-45 19 938 938 0 0 1-.5-75 47420 47420 0 0 0 201-308 6746 6746 0 0 1 56.5-26m749 25c26.53-2.476 52.19 1.024 77 10.5 31.88 15.226 52.05 40.06 60.5 74.5a173 173 0 0 1 4 34c.5 13.663.67 27.329.5 41h-176q2.85 67.555 70.5 67a200 200 0 0 0 50.5-6.5 490 490 0 0 0 39-14.5c.17 19.336 0 38.67-.5 58q-33.375 16.158-70.5 18.5c-36.4 4.441-71.07-1.226-104-17-33.65-20.425-53.49-50.258-59.5-89.5-5.49-36.652-1.82-72.319 11-107q29.715-61.246 97.5-69m17 53c27.12-.04 44.28 12.96 51.5 39a166 166 0 0 1 2.5 18h-104c.98-15.107 5.81-28.774 14.5-41 9.88-9.86 21.71-15.193 35.5-16m587-53c31.63-2.713 62.3 1.454 92 12.5 4.68 1.672 9.01 3.839 13 6.5a1382 1382 0 0 1-22 56.5 810 810 0 0 0-43-13c-14.54-3.633-28.87-2.966-43 2-15.21 6.88-25.04 18.38-29.5 34.5-7.61 29.436-7.27 58.769 1 88 7.52 19.679 21.69 31.179 42.5 34.5 16.63 1.263 32.97-.404 49-5 12.93-4.821 25.27-10.654 37-17.5.17 21.336 0 42.669-.5 64-17.57 11.078-36.73 17.578-57.5 19.5-102.05 9.666-153.38-36.667-154-139-.31-24.967 3.86-49.133 12.5-72.5 16.34-36.601 44.17-59.101 83.5-67.5 6.47-1.187 12.81-2.354 19-3.5m256 0c34.93-3.963 67.26 3.204 97 21.5 28.21 20.889 45.37 48.723 51.5 83.5 7.09 39.841 2.43 78.174-14 115q-26.76 49.722-82.5 60.5a235.4 235.4 0 0 1-60 1c-47.05-8.441-79.22-34.941-96.5-79.5-13.18-42.891-12.52-85.558 2-128 20.04-44.203 54.21-68.869 102.5-74m16 60q47.55.145 55.5 47 8.505 44.84-6 88-14.4 28.336-46.5 28c-27.35-.255-44.52-13.588-51.5-40q-10.215-46.428 3-92c8.71-19.93 23.88-30.264 45.5-31m336-60c25.75-2.821 50.08 1.679 73 13.5 25.45 16.718 38.62 40.551 39.5 71.5.5 64.332.67 128.666.5 193h-75c.17-55.668 0-111.334-.5-167-1.78-41.439-22.95-57.606-63.5-48.5-17.82 5.824-28.65 17.991-32.5 36.5a159 159 0 0 0-5 28c-.5 50.332-.67 100.666-.5 151h-76v-273c19.34-.167 38.67 0 58 .5a786 786 0 0 1 9.5 32.5c.39.928 1.05 1.595 2 2q26.85-34.558 70.5-40m283 0c26.55-2.612 52.22.888 77 10.5 31.89 15.234 52.06 40.067 60.5 74.5 2.27 9.875 3.6 19.875 4 30 .5 14.996.67 29.996.5 45h-176q3.495 66.876 70.5 67a200 200 0 0 0 50.5-6.5 490 490 0 0 0 39-14.5c.17 19.336 0 38.67-.5 58-18.78 9.258-38.61 15.091-59.5 17.5a303.4 303.4 0 0 1-76-2c-51.54-11.208-83.71-42.375-96.5-93.5q-10.755-55.28 6-109 27.6-67.938 100.5-77m17 53c25.46-.537 42.29 11.129 50.5 35a100.6 100.6 0 0 1 3.5 22h-104c.77-19.037 8.11-34.87 22-47.5 8.66-5.498 18-8.665 28-9.5m-1159-48h76v273h-76zm-1358 266a8376 8376 0 0 1-1-183 7723 7723 0 0 1 168-72.5 61878 61878 0 0 0-167 255.5"/><path d="M305.5 265.5a8376 8376 0 0 0 1 183q-.425 1.184-1.5 2a5711 5711 0 0 1 .5-185"/></g></svg> \ No newline at end of file diff --git a/.github/assets/perplexity.gif b/.github/assets/perplexity.gif new file mode 100644 index 0000000..c0fd028 Binary files /dev/null and b/.github/assets/perplexity.gif differ diff --git a/.github/assets/traction.png b/.github/assets/traction.png new file mode 100644 index 0000000..9c62883 Binary files /dev/null and b/.github/assets/traction.png differ diff --git a/.github/prompts/simplify-review.md b/.github/prompts/simplify-review.md new file mode 100644 index 0000000..8bf1798 --- /dev/null +++ b/.github/prompts/simplify-review.md @@ -0,0 +1,78 @@ +# Simplify Review + +You are a code simplification specialist. Your job is to analyze a PR diff for simplification opportunities, then return a structured summary of findings. You do NOT post any GitHub comments — just return your findings to the caller. + +## Phase 1: Get Context + +1. Run `gh pr diff <PR_NUMBER>` to get the full diff (replace `<PR_NUMBER>` with the PR number from your prompt) +2. Read `AGENTS.md` and `CLAUDE.md` for project conventions +3. Store the diff text for analysis + +## Phase 2: Analyze for Simplification + +Review the changed code for three categories. For each, search the codebase for context — read surrounding files, check for existing patterns, and verify your findings against the actual code. + +### Code Reuse + +For each change: + +1. **Search for existing utilities and helpers** that could replace newly written code. Look for similar patterns elsewhere in the codebase — common locations are utility directories, shared modules, and files adjacent to the changed ones. +2. **Flag any new function that duplicates existing functionality.** Suggest the existing function to use instead. +3. **Flag any inline logic that could use an existing utility** — hand-rolled string manipulation, manual path handling, custom environment checks, ad-hoc type guards, and similar patterns are common candidates. + +This is a monorepo. Search across `packages/*/src/` for existing utilities. + +### Code Quality + +Review the same changes for hacky patterns: + +1. **Redundant state**: state that duplicates existing state, cached values that could be derived, observers/effects that could be direct calls +2. **Parameter sprawl**: adding new parameters to a function instead of generalizing or restructuring existing ones +3. **Copy-paste with slight variation**: near-duplicate code blocks that should be unified with a shared abstraction +4. **Leaky abstractions**: exposing internal details that should be encapsulated, or breaking existing abstraction boundaries +5. **Unclear naming**: vague variable/function names that obscure intent — look for names that don't clearly convey purpose or type +6. **Stringly-typed code**: using raw strings where constants, enums (string unions), or branded types already exist in the codebase +7. **Unnecessary JSX nesting**: wrapper Boxes/elements that add no layout value — check if inner component props (flexShrink, alignItems, etc.) already provide the needed behavior +8. **Project convention deviations**: check AGENTS.md and CLAUDE.md for project conventions. Do not flag issues that oxlint/oxfmt already enforce (formatting, Tailwind class order). + +### Efficiency + +Review the same changes for efficiency: + +1. **Unnecessary work**: redundant computations, repeated file reads, duplicate network/API calls, N+1 patterns +2. **Missed concurrency**: independent operations run sequentially when they could run in parallel +3. **Hot-path bloat**: new blocking work added to startup or per-request/per-render hot paths +4. **Unnecessary existence checks**: pre-checking file/resource existence before operating (TOCTOU anti-pattern) — operate directly and handle the error +5. **Memory**: unbounded data structures, missing cleanup, event listener leaks +6. **Overly broad operations**: reading entire files when only a portion is needed, loading all items when filtering for one +7. **Missing subscription cleanup**: `subscribe()` without corresponding `unsubscribe` in effect or cleanup patterns + +## Phase 3: Filter + +For each finding: + +- **Keep** if it's genuinely worth changing — a real improvement, not nitpicking +- **Filter out** if it's a false positive, too minor, or stylistic preference already handled by the linter + +Maintain balance — do not suggest over-simplification that would: +- Create overly clever solutions harder to understand +- Combine too many concerns into single functions +- Remove helpful abstractions that improve organization +- Prioritize "fewer lines" over readability + +## Return Format + +Return your findings in this format: + +``` +### Reuse (N issues) +- `file:line` — description of what exists and what it could replace + +### Quality (N issues) +- `file:line` — description of the pattern and suggested fix + +### Efficiency (N issues) +- `file:line` — description of the inefficiency and improvement +``` + +If a category has no issues, omit it. If no issues found at all, return: "No simplification opportunities found. Code looks clean." diff --git a/.github/workflows/autofix.yaml b/.github/workflows/autofix.yaml new file mode 100644 index 0000000..8636847 --- /dev/null +++ b/.github/workflows/autofix.yaml @@ -0,0 +1,39 @@ +name: autofix.ci + +on: [pull_request] + +concurrency: + group: ${{ github.workflow }}-${{ github.event.pull_request.number }} + cancel-in-progress: true + +permissions: + contents: read + +jobs: + autofix: + runs-on: blacksmith-4vcpu-ubuntu-2404 + steps: + - name: Checkout code repository + uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # ratchet:actions/checkout@v7 + with: + fetch-depth: 0 + + - name: Install pnpm + uses: pnpm/action-setup@0ebf47130e4866e96fce0953f49152a61190b271 # ratchet:pnpm/action-setup@v6.0.9 + + - name: Setup node.js + uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # ratchet:actions/setup-node@v6 + with: + node-version: 24 + cache: "pnpm" + + - name: Install dependencies + run: pnpm install --ignore-scripts + + - name: Lint fix + run: pnpm lint:fix + + - name: Update API surface snapshots + run: pnpm api-surface + + - uses: autofix-ci/action@c5b2d67aa2274e7b5a18224e8171550871fc7e4a # v1.3.4 diff --git a/.github/workflows/changeset-semver-check.yaml b/.github/workflows/changeset-semver-check.yaml new file mode 100644 index 0000000..3adb430 --- /dev/null +++ b/.github/workflows/changeset-semver-check.yaml @@ -0,0 +1,302 @@ +name: Changeset Semver Check + +on: + pull_request: + branches: [main] + types: [opened, synchronize, reopened, labeled] + +permissions: + contents: read + pull-requests: read + +concurrency: + group: ${{ github.workflow }}-${{ github.event.pull_request.number }} + cancel-in-progress: true + +jobs: + analyze: + name: Semver Check + runs-on: blacksmith-4vcpu-ubuntu-2404 + timeout-minutes: 2 + steps: + - name: Checkout PR head + uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # ratchet:actions/checkout@v7 + with: + ref: ${{ github.event.pull_request.head.sha }} + fetch-depth: 0 + + - name: Check changeset bump types against package versions + id: check + uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0 + env: + BASE_SHA: ${{ github.event.pull_request.base.sha }} + HEAD_SHA: ${{ github.event.pull_request.head.sha }} + with: + script: | + const fs = require("fs"); + const path = require("path"); + const { execFileSync } = require("child_process"); + + // --- Helpers --- + + function bumpVersion(version, bumpType) { + const [major, minor, patch] = version.split(".").map(Number); + if (bumpType === "major") return `${major + 1}.0.0`; + if (bumpType === "minor") return `${major}.${minor + 1}.0`; + return `${major}.${minor}.${patch + 1}`; + } + + function isOutsideCaretRange(rangeVersion, newVersion) { + const [rMajor, rMinor, rPatch] = rangeVersion.split(".").map(Number); + const [nMajor, nMinor, nPatch] = newVersion.split(".").map(Number); + // ^0.0.Z — exact match only + if (rMajor === 0 && rMinor === 0) { + return nMajor !== 0 || nMinor !== 0 || nPatch !== rPatch; + } + // ^0.X.Z (X > 0) — same major and minor + if (rMajor === 0) { + return nMajor !== 0 || nMinor !== rMinor; + } + // ^X.Y.Z (X > 0) — same major + return nMajor !== rMajor; + } + + function computeCascade(bumps, pkgMap, revDeps) { + const cascade = []; + const visited = new Set(bumps.map((v) => v.pkg)); + const queue = bumps.map((v) => ({ + pkg: v.pkg, + newVersion: bumpVersion(v.version, v.bumpType), + })); + + let qi = 0; + while (qi < queue.length) { + const { pkg, newVersion } = queue[qi++]; + const dependents = revDeps.get(pkg) || []; + for (const dep of dependents) { + if (visited.has(dep.name)) continue; + // Extract version from the ^ range (e.g., "^0.12.15" → "0.12.15") + const rangeVersion = dep.range.replace(/^\^/, ""); + if (!isOutsideCaretRange(rangeVersion, newVersion)) continue; + visited.add(dep.name); + // This dependent gets a cascade patch + const depInfo = pkgMap.get(dep.name); + const depVersion = depInfo?.version ?? dep.version; + const cascadeNewVersion = bumpVersion(depVersion, "patch"); + const isBreaking = isOutsideCaretRange(depVersion, cascadeNewVersion); + cascade.push({ + name: dep.name, + version: depVersion, + newVersion: cascadeNewVersion, + isBreaking, + }); + // If the cascade patch is itself range-breaking (0.0.x), + // recurse to check further downstream + if (isBreaking) { + queue.push({ pkg: dep.name, newVersion: cascadeNewVersion }); + } + } + } + return cascade; + } + + // --- Scope to PR-changed changeset files --- + + const { BASE_SHA, HEAD_SHA } = process.env; + let prChangedFiles; + try { + const diff = execFileSync( + "git", + ["diff", "--name-only", "--diff-filter=ACM", BASE_SHA, HEAD_SHA, "--", ".changeset/*.md"], + { encoding: "utf8" }, + ).trim(); + prChangedFiles = new Set( + diff ? diff.split("\n").map((f) => path.basename(f)) : [], + ); + } catch { + // If git diff fails (e.g., shallow clone), fall back to all files + core.warning("Could not diff against base — checking all changeset files"); + prChangedFiles = null; + } + + const changesetDir = ".changeset"; + const allFiles = fs.readdirSync(changesetDir).filter( + (f) => f.endsWith(".md") && f !== "README.md", + ); + const files = prChangedFiles + ? allFiles.filter((f) => prChangedFiles.has(f)) + : allFiles; + + if (files.length === 0) { + core.info("No changeset files changed in this PR."); + return; + } + + // --- Build package map and reverse dependency graph --- + + const pkgMap = new Map(); + const revDeps = new Map(); + const packageManifests = []; + const packagesDir = "packages"; + for (const entry of fs.readdirSync(packagesDir, { withFileTypes: true })) { + if (!entry.isDirectory()) continue; + const dir = entry.name; + const pkgJsonPath = path.join(packagesDir, dir, "package.json"); + let pkgJson; + try { + pkgJson = JSON.parse(fs.readFileSync(pkgJsonPath, "utf8")); + } catch { + continue; + } + if (pkgJson.private) continue; + packageManifests.push(pkgJson); + const major = parseInt(pkgJson.version.split(".")[0], 10); + pkgMap.set(pkgJson.name, { major, version: pkgJson.version }); + } + + for (const pkgJson of packageManifests) { + const allDeps = { + ...pkgJson.dependencies, + ...pkgJson.peerDependencies, + }; + for (const [depName, range] of Object.entries(allDeps)) { + if (typeof range !== "string" || !range.startsWith("^")) continue; + if (!pkgMap.has(depName)) continue; + if (!revDeps.has(depName)) revDeps.set(depName, []); + revDeps.get(depName).push({ + name: pkgJson.name, + version: pkgJson.version, + range, + }); + } + } + + // --- Parse changeset files --- + + const allBumps = []; + const violations = []; + + for (const file of files) { + const content = fs.readFileSync( + path.join(changesetDir, file), + "utf8", + ); + + const match = content.match(/^---\n([\s\S]*?)\n---/); + if (!match) continue; + + const frontmatter = match[1]; + for (const line of frontmatter.split("\n")) { + const trimmed = line.trim(); + if (!trimmed) continue; + + const bumpMatch = trimmed.match( + /^"([^"]+)":\s*(patch|minor|major)$/, + ); + if (!bumpMatch) continue; + + const [, pkg, bumpType] = bumpMatch; + const pkgInfo = pkgMap.get(pkg); + + // Skip unknown or private packages (not in pkgMap) + if (!pkgInfo) continue; + + const { major, version } = pkgInfo; + + allBumps.push({ file, pkg, bumpType, version }); + + // Rules: + // 0.0.x → patch technically breaks ^0.0.x (exact match), but not + // flagged because these packages are pre-stable with no + // internal dependents. Only minor/major are flagged. + // 0.x → only patch is safe (minor/major break ^) + // 1.x+ → patch and minor are safe (only major breaks ^) + const needsConfirmation = + (major === 0 && bumpType !== "patch") || + (major >= 1 && bumpType === "major"); + + if (needsConfirmation) { + const reason = + major === 0 + ? `0.x package — ${bumpType} bump breaks \`^\` caret range` + : `major bump breaks \`^\` caret range`; + violations.push({ file, pkg, bumpType, version, reason }); + } + } + } + + if (allBumps.length === 0) { + core.info("No package bumps found in changesets."); + return; + } + + // --- Compute cascade impact for ALL bumps --- + + const cascade = computeCascade(allBumps, pkgMap, revDeps); + const breakingCascade = cascade.filter((c) => c.isBreaking); + + // --- Build cascade table (shared between both summary paths) --- + + function renderCascadeSection() { + if (cascade.length === 0) return ""; + let section = `### Cascade Impact (${cascade.length} packages)\n\n`; + section += "| Package | Version | Cascade Bump | Breaking |\n"; + section += "| --- | --- | --- | --- |\n"; + for (const c of cascade) { + const indicator = c.isBreaking + ? `⛔ YES — \`^${c.version}\` breaks` + : "✅ no"; + section += `| \`${c.name}\` | ${c.version} → ${c.newVersion} | patch | ${indicator} |\n`; + } + section += "\n"; + if (breakingCascade.length > 0) { + section += `> **${breakingCascade.length} downstream package(s)** will also break their consumers' \`^\` ranges.\n\n`; + } + return section; + } + + // --- Build summary --- + + let summary; + + if (violations.length === 0) { + // Informational summary for safe PRs + summary = "## Changeset Impact Summary\n\n"; + summary += "| File | Package | Version | Bump |\n"; + summary += "| --- | --- | --- | --- |\n"; + for (const b of allBumps) { + summary += `| \`${b.file}\` | \`${b.pkg}\` | ${b.version} | ${b.bumpType} |\n`; + } + summary += "\n"; + summary += renderCascadeSection(); + + await core.summary.addRaw(summary).write(); + core.info("All changeset bump types are safe for current package versions. ✓"); + } else { + // Violation summary + summary = "## ⚠️ Semver-Breaking Changeset Detected\n\n"; + summary += "| File | Package | Version | Bump | Why |\n"; + summary += "| --- | --- | --- | --- | --- |\n"; + for (const v of violations) { + summary += `| \`${v.file}\` | \`${v.pkg}\` | ${v.version} | **${v.bumpType}** | ${v.reason} |\n`; + } + summary += "\n"; + summary += renderCascadeSection(); + summary += "### What this means\n\n"; + summary += "- **0.x packages**: `^0.12.15` only matches `>=0.12.15 <0.13.0` — "; + summary += "a minor bump is effectively a breaking change for all consumers.\n"; + summary += "- **1.x+ packages**: `^1.3.12` matches `>=1.3.12 <2.0.0` — "; + summary += "minor/patch are safe, but a major bump breaks all consumers.\n\n"; + summary += "This check is informational — the PR can still be merged if this is intentional.\n"; + + await core.summary.addRaw(summary).write(); + + // Emit warnings for breaking cascade entries (visible in PR checks tab) + for (const c of breakingCascade) { + core.warning( + `Cascade breaks ${c.name} (${c.version} → ${c.newVersion}, ^${c.version} consumers affected)`, + ); + } + + core.setFailed(`Semver-breaking bumps detected: ${violations.map((v) => `${v.pkg}@${v.bumpType}`).join(", ")}`); + } diff --git a/.github/workflows/changeset.yaml b/.github/workflows/changeset.yaml new file mode 100644 index 0000000..0a1b850 --- /dev/null +++ b/.github/workflows/changeset.yaml @@ -0,0 +1,139 @@ +name: Changesets + +on: + push: + branches: + - main + +permissions: + contents: write + pull-requests: write + +concurrency: + group: ${{ github.workflow }}-${{ github.ref }} + cancel-in-progress: false + +env: + CI: true + +jobs: + version: + name: Create Version PR + runs-on: blacksmith-4vcpu-ubuntu-2404 + steps: + - name: Checkout code repository + uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0 + with: + fetch-depth: 0 + + - name: Install pnpm + uses: pnpm/action-setup@0ebf47130e4866e96fce0953f49152a61190b271 # v6.0.9 + + - name: Setup node.js + uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6.4.0 + with: + node-version: 24 + cache: "pnpm" + + - name: Install dependencies + run: pnpm install --frozen-lockfile + + - name: Create version PR + id: changesets + uses: changesets/action@a45c4d594aa4e2c509dc14a9f2b3b67ba3780d0d # v1.9.0 + with: + commit: "chore: update versions" + title: "chore: update versions" + version: pnpm ci:version + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + + - name: Detect release packages + id: release_plan + if: steps.changesets.outputs.hasChangesets == 'false' + uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0 + with: + script: | + const { execFileSync } = require("node:child_process"); + const { existsSync, readFileSync } = require("node:fs"); + + const before = context.payload.before; + const after = context.sha; + const isZeroBefore = typeof before === "string" && /^0+$/.test(before); + + if (isZeroBefore) { + core.warning( + "Skipping release dispatch on zero before SHA (no baseline to diff)", + ); + core.setOutput("hasReleasePackages", "false"); + return; + } + + const runGit = (args) => + execFileSync("git", args, { encoding: "utf8" }).trim(); + + try { + runGit(["cat-file", "-e", `${before}^{commit}`]); + } catch { + core.warning( + `Skipping release dispatch because before SHA is missing: ${before}`, + ); + core.setOutput("hasReleasePackages", "false"); + return; + } + + const changedFilesOutput = runGit([ + "diff", + "--name-only", + before, + after, + "--", + "packages/*/package.json", + ]); + + const changedFiles = changedFilesOutput + .split("\n") + .map((line) => line.trim()) + .filter(Boolean); + + let hasReleasePackages = false; + + for (const filePath of changedFiles) { + if (!existsSync(filePath)) continue; + + const current = JSON.parse(readFileSync(filePath, "utf8")); + if (current.private === true) continue; + + let previousVersion = null; + try { + const previousRaw = runGit(["show", `${before}:${filePath}`]); + previousVersion = JSON.parse(previousRaw).version ?? null; + } catch { + previousVersion = null; + } + + if (previousVersion !== current.version) { + hasReleasePackages = true; + core.info(`Release candidate: ${current.name}@${current.version}`); + } + } + + if (!hasReleasePackages) { + core.info("Release candidates: none"); + } + + core.setOutput("hasReleasePackages", hasReleasePackages ? "true" : "false"); + + - name: Trigger npm publish + if: steps.changesets.outputs.hasChangesets == 'false' && steps.release_plan.outputs.hasReleasePackages == 'true' + uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0 + with: + script: | + await github.rest.repos.createDispatchEvent({ + owner: context.repo.owner, + repo: context.repo.repo, + event_type: 'npm-publish', + client_payload: { + releaseSha: context.sha, + }, + }); diff --git a/.github/workflows/claude-code-review.yml b/.github/workflows/claude-code-review.yml new file mode 100644 index 0000000..e200c7a --- /dev/null +++ b/.github/workflows/claude-code-review.yml @@ -0,0 +1,159 @@ +name: Claude Code Review + +on: + pull_request: + types: [opened, synchronize, ready_for_review, reopened] + # Optional: Only run on specific file changes + # paths: + # - "src/**/*.ts" + # - "src/**/*.tsx" + # - "src/**/*.js" + # - "src/**/*.jsx" + +concurrency: + group: claude-code-review-${{ github.event.pull_request.number }} + cancel-in-progress: true + +env: + # Shared action configuration + TRACK_PROGRESS: 'true' + USE_STICKY_COMMENT: 'true' + ADDITIONAL_PERMISSIONS: | + actions: read + + REVIEW_PROMPT: | + REPO: ${{ github.repository }} + PR NUMBER: ${{ github.event.pull_request.number }} + TITLE: ${{ github.event.pull_request.title }} + BODY: ${{ github.event.pull_request.body }} + AUTHOR: ${{ github.event.pull_request.user.login }} + COMMIT: ${{ github.event.pull_request.head.sha }} + Note: The PR branch is already checked out in the current working directory. + + FIRST: Immediately launch a simplification subagent in the background using the Agent tool (with run_in_background: true) with this prompt: + + "Read the file .github/prompts/simplify-review.md for guidance on what to look for. The PR number is ${{ github.event.pull_request.number }}. Wherever the file says <PR_NUMBER>, use ${{ github.event.pull_request.number }}. The repository is checked out in the current working directory. Return your findings as a structured summary — do NOT post any GitHub comments yourself." + + THEN: While the simplify agent runs in the background, proceed with the review: + + Gather Context (complete these steps silently - don't report on this process): + 1. Check CI status: Use mcp__github_ci__get_ci_status to see if tests passed/failed + 2. Get previous reviews: Run gh pr view ${{ github.event.pull_request.number }} --comments + 3. Identify previous bot reviews and track which issues were raised before + 4. If CI failed: Use mcp__github_ci__get_workflow_run_details for failure details + + Review Approach: + - If first review: Comprehensive analysis of all changes + - If previous reviews exist: Focus on changes since last review, track issue status, understand how changes impact pr + - Incorporate CI/test failure context into your feedback if relevant + - Use inline comments (mcp__github_inline_comment__create_inline_comment) for specific code issues + + Do not include in your review: + - Task completion statements ("I completed the review", "Review finished") + - Process descriptions ("First I checked X, then I analyzed Y") + - Generic meta-commentary about reviewing + - Progress tracking + + Review this pull request by individually analysing each of these thoroughly: + - Code quality and best practices + - Potential bugs or issues + - Security implications + - Performance considerations + - Test coverage + - assistant-ui specific requirements: + - The PR has a changeset if updating source code of published packages (packages/*) + - The PR includes documentation if API surface changes + + Provide a comprehensive review build on previous reviews including: + - Summary of changes since last review + - Critical issues found (be thorough) + - Suggested improvements (be thorough) + - Good practices observed (be concise - list only the most notable items without elaboration) + - Leverage collapsible <details> sections where appropriate for lengthy explanations or code snippets to enhance human readability + + When reviewing subsequent commits: + - Track status of previously identified issues + - Identify new problems introduced since last review + - Note if fixes introduced new issues + + IMPORTANT: Be comprehensive about issues and improvements. For good practices, be brief - just note what was done well without explaining why or praising excessively. NO meta-commentary about the review process itself. + + Use `mcp__github_inline_comment__create_inline_comment` only for critical code issues or relevant suggestions. Check existing inline comments first to avoid duplicates. Do NOT use inline comments for good practices or positive observations. Each inline comment must be well thought out, precise, and actionable. + + You have been provided with the mcp__github_comment__update_claude_comment tool to update your comment. This tool automatically handles PR comments. Only the body parameter is required - the tool automatically knows which comment to update. + + FINALLY: Once your review is complete, wait for the background simplify agent to finish. Collect its structured findings, then: + 1. For each simplify finding that you agree is worth flagging, post an inline comment using mcp__github_inline_comment__create_inline_comment. Skip findings that overlap with issues you already commented on during your review. + 2. Submit one final comment using mcp__github_comment__update_claude_comment that includes your full review followed by a `## Simplify` section with the agent's summary. If no simplification issues were found, note that the code looks clean. + + # Allowed tools: + # - mcp__github_inline_comment__create_inline_comment: Create inline code comments + # - Bash(gh ...): Read-only GitHub CLI commands for PR/issue info + # Note: gh pr comment is NOT allowed - Claude must use update_claude_comment only + # Temporarily on Opus 4.8 while Fable usage credits are exhausted; swap back to + # `--model fable` when access returns. The Fable-specific machinery below + # (Opus-fallback flagging) stays in place for that switch-back. + CLAUDE_ARGS: '--model claude-opus-4-8 --effort high --allowedTools "Agent,mcp__github_inline_comment__create_inline_comment,Bash(gh issue view:*),Bash(gh search:*),Bash(gh issue list:*),Bash(gh pr diff:*),Bash(gh pr view:*),Bash(gh pr list:*)"' + +jobs: + claude-review: + if: github.event.pull_request.draft == false && github.event.pull_request.head.repo.full_name == github.repository + runs-on: blacksmith-4vcpu-ubuntu-2404 + timeout-minutes: 15 + permissions: + contents: read + pull-requests: write + issues: read + id-token: write + actions: read + + steps: + - name: Checkout repository + uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # ratchet:actions/checkout@v7 + with: + ref: refs/pull/${{ github.event.pull_request.number }}/head + fetch-depth: 1 + + - name: Review + id: review + uses: anthropics/claude-code-action@536f2c32a39763739000b0e1ac69ca2647d97ce9 # ratchet:anthropics/claude-code-action@v1 + with: + claude_code_oauth_token: ${{ secrets.CLAUDE_CODE_OAUTH_TOKEN }} + prompt: ${{ env.REVIEW_PROMPT }} + use_sticky_comment: ${{ fromJSON(env.USE_STICKY_COMMENT) }} + track_progress: ${{ fromJSON(env.TRACK_PROGRESS) }} + claude_args: ${{ env.CLAUDE_ARGS }} + additional_permissions: ${{ env.ADDITIONAL_PERMISSIONS }} + + # The action reports success even when the Claude execution errors out + # (e.g. expired OAuth token or exhausted usage limit), leaving PRs with a + # green check and no review. Fail loudly and surface the error text, + # which the action redacts from its own logs. + - name: Fail on Claude execution error + if: always() + run: | + FILE='${{ steps.review.outputs.execution_file }}' + [ -f "$FILE" ] || exit 0 + jq -e 'last(.[] | select(.type == "result")) | .is_error == true' "$FILE" >/dev/null || exit 0 + echo "::error::Claude review failed: $(jq -r 'last(.[] | select(.type == "result")) | .result // "no error text"' "$FILE" | tr '\n' ' ' | head -c 500)" + exit 1 + + # Fable's cybersecurity classifier can silently swap the session to Opus + # mid-run; the model's own context still claims Fable, so the transcript + # is the only reliable signal of the downgrade. + - name: Flag Opus fallback + if: always() + env: + GH_TOKEN: ${{ github.token }} + run: | + FILE='${{ steps.review.outputs.execution_file }}' + [ -f "$FILE" ] && grep -q '"model_refusal_fallback"' "$FILE" || exit 0 + echo "::warning::Fable safeguard triggered — review served by Opus fallback" + COMMENT=$(gh api --paginate "repos/${{ github.repository }}/issues/${{ github.event.pull_request.number }}/comments" \ + --jq '.[] | select(.user.login == "claude[bot]")' | jq -s 'last') + [ -n "$COMMENT" ] && [ "$COMMENT" != "null" ] || exit 0 + gh api --method PATCH "repos/${{ github.repository }}/issues/comments/$(jq -r .id <<<"$COMMENT")" \ + -f body="> [!WARNING] + > **Fable's cybersecurity safeguard triggered — this review was served by Opus 4.8.** Ignore it and re-review with Fable locally. + + $(jq -r .body <<<"$COMMENT")" diff --git a/.github/workflows/code-quality.yaml b/.github/workflows/code-quality.yaml new file mode 100644 index 0000000..68d68f9 --- /dev/null +++ b/.github/workflows/code-quality.yaml @@ -0,0 +1,182 @@ +name: Code Quality + +on: + push: + branches: [main] + paths: + - "packages/**" + - "api-surface/**" + - "apps/**" + - "examples/**" + - "templates/**" + - "scripts/generate-api-surface.mjs" + - "scripts/check-api-surface.mjs" + - "scripts/lib/script-options.mjs" + - "scripts/sync-templates.sh" + - "turbo.json" + - "package.json" + - "pnpm-lock.yaml" + - ".github/workflows/code-quality.yaml" + pull_request: + branches: [main] + paths: + - "packages/**" + - "api-surface/**" + - "apps/**" + - "examples/**" + - "templates/**" + - "scripts/generate-api-surface.mjs" + - "scripts/check-api-surface.mjs" + - "scripts/lib/script-options.mjs" + - "scripts/sync-templates.sh" + - "turbo.json" + - "package.json" + - "pnpm-lock.yaml" + - ".github/workflows/code-quality.yaml" + +permissions: + contents: read + +concurrency: + group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }} + cancel-in-progress: true + +jobs: + lint: + name: Oxlint + Oxfmt + runs-on: blacksmith-4vcpu-ubuntu-2404 + timeout-minutes: 5 + steps: + - name: Checkout code repository + uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # ratchet:actions/checkout@v7 + with: + fetch-depth: 0 + + - name: Install pnpm + uses: pnpm/action-setup@0ebf47130e4866e96fce0953f49152a61190b271 # ratchet:pnpm/action-setup@v6.0.9 + + - name: Setup node.js + uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # ratchet:actions/setup-node@v6 + with: + node-version: 24 + cache: "pnpm" + + - name: Install dependencies + run: pnpm install --frozen-lockfile + + - name: Run oxlint + oxfmt + run: pnpm lint + + - name: Check resource memoization (React Compiler) + run: pnpm check:resource-memo + + template-sync: + name: Template Sync + runs-on: blacksmith-4vcpu-ubuntu-2404 + timeout-minutes: 2 + steps: + - name: Checkout code repository + uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # ratchet:actions/checkout@v7 + with: + fetch-depth: 1 + + - name: Verify templates match packages/ui source + run: bash scripts/sync-templates.sh + + build: + name: Build Changed Packages + runs-on: blacksmith-4vcpu-ubuntu-2404 + timeout-minutes: 10 + steps: + - name: Checkout code repository + uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # ratchet:actions/checkout@v7 + with: + fetch-depth: 0 + + - name: Install pnpm + uses: pnpm/action-setup@0ebf47130e4866e96fce0953f49152a61190b271 # ratchet:pnpm/action-setup@v6.0.9 + + - name: Setup node.js + uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # ratchet:actions/setup-node@v6 + with: + node-version: 24 + cache: "pnpm" + + - name: Cache turbo build setup + uses: actions/cache@caa296126883cff596d87d8935842f9db880ef25 # ratchet:actions/cache@v5 + with: + path: .turbo + key: ${{ runner.os }}-turbo-build-${{ github.ref }}-${{ github.sha }} + restore-keys: | + ${{ runner.os }}-turbo-build-${{ github.ref }}- + ${{ runner.os }}-turbo-build- + ${{ runner.os }}-turbo- + + - name: Install dependencies + run: pnpm install --frozen-lockfile + + - name: Build packages + run: | + if [ "${{ github.event_name }}" = "pull_request" ]; then + # PR: Build packages changed compared to base branch + pnpm turbo build --filter="...[origin/${{ github.base_ref }}]" + else + # Push to main: Build packages changed in last commit + pnpm turbo build --filter="...[HEAD^1]" + fi + + - name: Check API surface + run: | + if [ "${{ github.event_name }}" = "pull_request" ]; then + # PR: Check API surface for packages changed compared to base branch + pnpm run api-surface:check -- --skip-build --filter="...[origin/${{ github.base_ref }}]" + else + # Push to main: Check API surface for packages changed in last commit + pnpm run api-surface:check -- --skip-build --filter="...[HEAD^1]" + fi + + - name: Test API surface generator + run: pnpm run api-surface:test + + test: + name: Test Changed Packages + runs-on: blacksmith-4vcpu-ubuntu-2404 + timeout-minutes: 5 + # Note: No dependency on 'build' job - Turbo handles test->build dependencies internally via dependsOn + steps: + - name: Checkout code repository + uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # ratchet:actions/checkout@v7 + with: + fetch-depth: 0 + + - name: Install pnpm + uses: pnpm/action-setup@0ebf47130e4866e96fce0953f49152a61190b271 # ratchet:pnpm/action-setup@v6.0.9 + + - name: Setup node.js + uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # ratchet:actions/setup-node@v6 + with: + node-version: 24 + cache: "pnpm" + + - name: Cache turbo build setup + uses: actions/cache@caa296126883cff596d87d8935842f9db880ef25 # ratchet:actions/cache@v5 + with: + path: .turbo + key: ${{ runner.os }}-turbo-test-${{ github.ref }}-${{ github.sha }} + restore-keys: | + ${{ runner.os }}-turbo-test-${{ github.ref }}- + ${{ runner.os }}-turbo-test- + ${{ runner.os }}-turbo- + + - name: Install dependencies + run: pnpm install --frozen-lockfile + + - name: Run tests + run: | + if [ "${{ github.event_name }}" = "pull_request" ]; then + # PR: Test packages changed compared to base branch + pnpm turbo test --filter="...[origin/${{ github.base_ref }}]" + else + # Push to main: Test packages changed in last commit + pnpm turbo test --filter="...[HEAD^1]" + fi diff --git a/.github/workflows/expo.yaml b/.github/workflows/expo.yaml new file mode 100644 index 0000000..8f54682 --- /dev/null +++ b/.github/workflows/expo.yaml @@ -0,0 +1,70 @@ +name: Deploy Expo Example + +on: + push: + branches: + - main + paths: + - examples/with-expo/** + - packages/react-native/** + - packages/react-ai-sdk/** + - packages/core/** + - .github/workflows/expo.yaml + +permissions: + contents: read + +concurrency: + group: ${{ github.workflow }}-${{ github.ref }} + cancel-in-progress: false + +env: + VERCEL_ORG_ID: ${{ secrets.VERCEL_ORG_ID }} + VERCEL_PROJECT_ID: ${{ secrets.VERCEL_EXPO_PROJECT_ID }} + EXPO_PUBLIC_CHAT_ENDPOINT_URL: https://www.assistant-ui.com/api/chat + +jobs: + deploy-production: + name: Deploy Production + runs-on: blacksmith-4vcpu-ubuntu-2404 + steps: + - name: Checkout code repository + uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # ratchet:actions/checkout@v7 + with: + fetch-depth: 1 + + - name: Install pnpm + uses: pnpm/action-setup@0ebf47130e4866e96fce0953f49152a61190b271 # ratchet:pnpm/action-setup@v6.0.9 + + - name: Setup node.js + uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # ratchet:actions/setup-node@v6 + with: + node-version: 24 + cache: "pnpm" + + - name: Cache turbo build setup + uses: actions/cache@caa296126883cff596d87d8935842f9db880ef25 # ratchet:actions/cache@v5 + with: + path: .turbo + key: ${{ runner.os }}-turbo-${{ github.ref }}-${{ github.sha }} + restore-keys: | + ${{ runner.os }}-turbo-${{ github.ref }}- + ${{ runner.os }}-turbo- + + - name: Install Vercel CLI + run: npm install --global vercel@latest + + - name: Install dependencies + run: pnpm install --frozen-lockfile + + - name: Pull Vercel Environment Information + working-directory: examples/with-expo + run: vercel pull --yes --prod --token=${{ secrets.VERCEL_TOKEN }} + + - name: Build Project Artifacts + working-directory: examples/with-expo + run: vercel build --prod --token=${{ secrets.VERCEL_TOKEN }} + + - name: Deploy Project Artifacts to Vercel + working-directory: examples/with-expo + run: vercel deploy --prebuilt --prod --token=${{ secrets.VERCEL_TOKEN }} diff --git a/.github/workflows/ink.yaml b/.github/workflows/ink.yaml new file mode 100644 index 0000000..2e11e2f --- /dev/null +++ b/.github/workflows/ink.yaml @@ -0,0 +1,70 @@ +name: Deploy Ink Example + +on: + push: + branches: + - main + paths: + - examples/with-react-ink-web/** + - packages/react-ink/** + - packages/react-ink-markdown/** + - packages/react-ai-sdk/** + - packages/core/** + - .github/workflows/ink.yaml + +permissions: + contents: read + +concurrency: + group: ${{ github.workflow }}-${{ github.ref }} + cancel-in-progress: false + +env: + VERCEL_ORG_ID: ${{ secrets.VERCEL_ORG_ID }} + VERCEL_PROJECT_ID: ${{ secrets.VERCEL_INK_PROJECT_ID }} + +jobs: + deploy-production: + name: Deploy Production + runs-on: blacksmith-4vcpu-ubuntu-2404 + steps: + - name: Checkout code repository + uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # ratchet:actions/checkout@v7 + with: + fetch-depth: 1 + + - name: Install pnpm + uses: pnpm/action-setup@0ebf47130e4866e96fce0953f49152a61190b271 # ratchet:pnpm/action-setup@v6.0.9 + + - name: Setup node.js + uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # ratchet:actions/setup-node@v6 + with: + node-version: 24 + cache: "pnpm" + + - name: Cache turbo build setup + uses: actions/cache@caa296126883cff596d87d8935842f9db880ef25 # ratchet:actions/cache@v5 + with: + path: .turbo + key: ${{ runner.os }}-turbo-${{ github.ref }}-${{ github.sha }} + restore-keys: | + ${{ runner.os }}-turbo-${{ github.ref }}- + ${{ runner.os }}-turbo- + + - name: Install Vercel CLI + run: npm install --global vercel@latest + + - name: Install dependencies + run: pnpm install --frozen-lockfile + + - name: Pull Vercel Environment Information + working-directory: examples/with-react-ink-web + run: vercel pull --yes --prod --token=${{ secrets.VERCEL_TOKEN }} + + - name: Build Project Artifacts + working-directory: examples/with-react-ink-web + run: vercel build --prod --token=${{ secrets.VERCEL_TOKEN }} + + - name: Deploy Project Artifacts to Vercel + working-directory: examples/with-react-ink-web + run: vercel deploy --prebuilt --prod --token=${{ secrets.VERCEL_TOKEN }} diff --git a/.github/workflows/npm-publish.yaml b/.github/workflows/npm-publish.yaml new file mode 100644 index 0000000..baa05e7 --- /dev/null +++ b/.github/workflows/npm-publish.yaml @@ -0,0 +1,816 @@ +name: npm Publish + +on: + repository_dispatch: + types: [npm-publish] + workflow_dispatch: + inputs: + releaseSha: + description: "Release commit SHA to publish (defaults to latest default-branch commit)" + required: false + type: string + +permissions: + contents: read + +env: + CI: true + +jobs: + approve: + name: Prepare npm publish + runs-on: ubuntu-24.04 + concurrency: + group: npm-publish-approval + cancel-in-progress: true + outputs: + release_sha: ${{ steps.release-sha.outputs.value }} + default_branch: ${{ steps.release-sha.outputs.default_branch }} + steps: + - name: Resolve release commit SHA + id: release-sha + uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0 + with: + script: | + const dispatchReleaseShaRaw = context.payload?.client_payload?.releaseSha; + const manualReleaseShaRaw = context.payload?.inputs?.releaseSha; + const payloadDefaultBranchRaw = + context.payload?.repository?.default_branch; + const dispatchReleaseSha = + typeof dispatchReleaseShaRaw === "string" + ? dispatchReleaseShaRaw.trim() + : ""; + const manualReleaseSha = + typeof manualReleaseShaRaw === "string" + ? manualReleaseShaRaw.trim() + : ""; + const payloadDefaultBranch = + typeof payloadDefaultBranchRaw === "string" + ? payloadDefaultBranchRaw.trim() + : ""; + + let releaseSha = dispatchReleaseSha || manualReleaseSha; + let defaultBranch = payloadDefaultBranch; + + if (!defaultBranch) { + const repoInfo = await github.rest.repos.get(context.repo); + defaultBranch = repoInfo.data.default_branch; + } + + if (!defaultBranch) { + core.setFailed("Unable to determine repository default branch"); + return; + } + + if (!releaseSha) { + if (context.eventName === "repository_dispatch") { + core.setFailed("repository_dispatch payload is missing releaseSha"); + return; + } + + const ref = await github.rest.git.getRef({ + ...context.repo, + ref: `heads/${defaultBranch}`, + }); + + releaseSha = ref.data.object.sha; + core.info( + `No release SHA provided; using ${defaultBranch} HEAD ${releaseSha}`, + ); + } + + if (!/^[0-9a-f]{40}$/i.test(releaseSha)) { + core.setFailed( + `releaseSha must be a full 40-character commit SHA: ${releaseSha}`, + ); + return; + } + + core.setOutput("value", releaseSha); + core.setOutput("default_branch", defaultBranch); + + cancel_pending: + name: Cancel stale pending publishes + needs: approve + runs-on: ubuntu-24.04 + permissions: + actions: write + contents: read + env: + DEFAULT_BRANCH: ${{ needs.approve.outputs.default_branch }} + steps: + - name: Cancel older queued/waiting runs + continue-on-error: true + uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0 + with: + script: | + const owner = context.repo.owner; + const repo = context.repo.repo; + const currentRunId = context.runId; + const currentRunNumber = context.runNumber; + const defaultBranch = (process.env.DEFAULT_BRANCH || "").trim(); + const workflowRef = process.env.GITHUB_WORKFLOW_REF || ""; + + const workflowPathMatch = workflowRef.match( + /^[^/]+\/[^/]+\/(.+)@/, + ); + const workflowPath = workflowPathMatch?.[1] ?? ""; + const workflowId = workflowPath.split("/").pop() || ""; + + let runs = []; + + if (workflowId) { + runs = await github.paginate(github.rest.actions.listWorkflowRuns, { + owner, + repo, + workflow_id: workflowId, + per_page: 100, + }); + } else { + core.warning( + "Unable to resolve workflow filename; using repository-wide workflow run list", + ); + runs = await github.paginate( + github.rest.actions.listWorkflowRunsForRepo, + { + owner, + repo, + per_page: 100, + }, + ); + } + + const candidates = runs.filter((run) => { + if (run.id === currentRunId) return false; + if (run.run_number >= currentRunNumber) return false; + if (run.status === "completed") return false; + if (workflowPath && run.path !== workflowPath) return false; + if (defaultBranch && run.head_branch && run.head_branch !== defaultBranch) { + return false; + } + if ( + run.event !== "repository_dispatch" && + run.event !== "workflow_dispatch" + ) { + return false; + } + + return true; + }); + + let canceledCount = 0; + + for (const run of candidates) { + const jobs = await github.paginate( + github.rest.actions.listJobsForWorkflowRun, + { + owner, + repo, + run_id: run.id, + per_page: 100, + }, + ); + + const publishJob = jobs.find((job) => job.name === "Publish to npm"); + + if (publishJob?.status === "in_progress") { + core.info( + `Keeping run ${run.id} because Publish to npm is in progress`, + ); + continue; + } + + try { + await github.rest.actions.cancelWorkflowRun({ + owner, + repo, + run_id: run.id, + }); + canceledCount += 1; + core.info(`Canceled stale pending run ${run.id}`); + } catch (error) { + core.warning( + `Unable to cancel run ${run.id}: ${error.message}`, + ); + } + } + + core.info(`Canceled ${canceledCount} stale pending run(s)`); + + summary: + name: Release summary + needs: + - approve + runs-on: ubuntu-24.04 + outputs: + target_count: ${{ steps.targets.outputs.targetCount }} + targets_json: ${{ steps.targets.outputs.targetsJson }} + env: + RELEASE_SHA: ${{ needs.approve.outputs.release_sha }} + DEFAULT_BRANCH: ${{ needs.approve.outputs.default_branch }} + steps: + + - name: Checkout code repository + uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0 + with: + fetch-depth: 0 + ref: ${{ env.RELEASE_SHA }} + + - name: Validate release commit is on default branch + run: | + if [ -z "$RELEASE_SHA" ]; then + echo "Missing releaseSha" >&2 + exit 1 + fi + + if [ -z "$DEFAULT_BRANCH" ]; then + echo "Missing default branch" >&2 + exit 1 + fi + + head_sha=$(git rev-parse HEAD) + if [ "$head_sha" != "$RELEASE_SHA" ]; then + echo "Checked out commit does not match releaseSha payload" >&2 + echo "HEAD=$head_sha" >&2 + echo "releaseSha=$RELEASE_SHA" >&2 + exit 1 + fi + + git fetch origin "$DEFAULT_BRANCH" + origin_default_sha=$(git rev-parse "origin/$DEFAULT_BRANCH") + if ! git merge-base --is-ancestor "$RELEASE_SHA" "origin/$DEFAULT_BRANCH"; then + echo "releaseSha is not an ancestor of origin/$DEFAULT_BRANCH; refusing to publish" >&2 + echo "releaseSha=$RELEASE_SHA" >&2 + echo "origin/$DEFAULT_BRANCH SHA=$origin_default_sha" >&2 + exit 1 + fi + + - name: Validate no pending changesets + run: | + pending=$(find .changeset -name '*.md' ! -name 'README.md' | sort) + if [ -n "$pending" ]; then + echo "::error::Release SHA has pending changesets, so changesets/action would update the version PR instead of publishing." + echo "::error::Merge the version PR first, then dispatch with its merge commit as releaseSha." + echo "Pending changesets:" + echo "$pending" + exit 1 + fi + + - name: Resolve publish targets + id: targets + uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0 + with: + script: | + const fs = require("node:fs"); + const path = require("node:path"); + + const fetchNpmMetadata = async (packageName) => { + const encodedName = encodeURIComponent(packageName); + const url = `https://registry.npmjs.org/${encodedName}`; + + const controller = new AbortController(); + const timeout = setTimeout(() => controller.abort(), 15_000); + + try { + const response = await fetch(url, { + signal: controller.signal, + }); + + if (response.ok) { + return await response.json(); + } + + if (response.status === 404) { + return null; + } + + throw new Error(`HTTP ${response.status}`); + } finally { + clearTimeout(timeout); + } + }; + + const parseStableSemver = (value) => { + if (typeof value !== "string") return null; + const match = value.match(/^(\d+)\.(\d+)\.(\d+)$/); + if (!match) return null; + return match.slice(1).map((part) => Number(part)); + }; + + const compareStableSemver = (a, b) => { + const parsedA = parseStableSemver(a); + const parsedB = parseStableSemver(b); + if (!parsedA || !parsedB) return null; + + for (let index = 0; index < 3; index += 1) { + if (parsedA[index] > parsedB[index]) return 1; + if (parsedA[index] < parsedB[index]) return -1; + } + + return 0; + }; + + const packagesDir = path.join(process.cwd(), "packages"); + const entries = fs.readdirSync(packagesDir, { withFileTypes: true }); + + const prereleasePackages = []; + const downgradePackages = []; + const targets = []; + + for (const entry of entries) { + if (!entry.isDirectory()) continue; + + const manifestPath = path.join(packagesDir, entry.name, "package.json"); + if (!fs.existsSync(manifestPath)) continue; + + const manifest = JSON.parse(fs.readFileSync(manifestPath, "utf8")); + if (manifest.private === true) continue; + + if (typeof manifest.version !== "string" || manifest.version.length === 0) { + core.setFailed(`Package ${manifest.name} is missing a version`); + return; + } + + if (manifest.version.includes("-")) { + prereleasePackages.push(`${manifest.name}@${manifest.version}`); + continue; + } + + const pkgName = manifest.name; + const pkgVersion = manifest.version; + let metadata = null; + try { + metadata = await fetchNpmMetadata(pkgName); + } catch (error) { + core.setFailed( + `Failed to query npm metadata for ${pkgName}: ${error.message}`, + ); + return; + } + + const latestTag = metadata?.["dist-tags"]?.latest ?? null; + const hasVersion = Boolean(metadata?.versions?.[pkgVersion]); + + if (latestTag !== null) { + const comparison = compareStableSemver(pkgVersion, latestTag); + if (comparison === null) { + core.setFailed( + `Unsupported semver for ${pkgName}: local=${pkgVersion} latest=${latestTag}`, + ); + return; + } + + if (comparison < 0) { + downgradePackages.push( + `${pkgName} local=${pkgVersion} latest=${latestTag}`, + ); + continue; + } + } + + if (hasVersion) { + if (latestTag !== pkgVersion) { + core.warning( + `${pkgName}@${pkgVersion} is on npm but latest is ${latestTag ?? "unset"}; run: npm dist-tag add ${pkgName}@${pkgVersion} latest --registry https://registry.npmjs.org`, + ); + } + } else { + targets.push({ + name: pkgName, + version: pkgVersion, + latestVersion: latestTag, + dir: entry.name, + }); + } + } + + if (prereleasePackages.length > 0) { + core.setFailed( + `Prerelease versions are not allowed on default-branch releases: ${prereleasePackages.join(", ")}`, + ); + return; + } + + if (downgradePackages.length > 0) { + core.setFailed( + `Refusing to retag older versions as latest: ${downgradePackages.join(", ")}`, + ); + return; + } + + targets.sort((a, b) => a.name.localeCompare(b.name)); + + core.info( + `Targets: ${targets.length === 0 ? "none" : targets.map((target) => `${target.name}@${target.version}`).join(", ")}`, + ); + + core.setOutput("targetCount", String(targets.length)); + core.setOutput("targetsJson", JSON.stringify(targets)); + + - name: Write release summary + if: steps.targets.outputs.targetCount != '0' + uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0 + env: + TARGETS_JSON: ${{ steps.targets.outputs.targetsJson }} + with: + script: | + const fs = require("node:fs"); + const path = require("node:path"); + const { execFileSync } = require("node:child_process"); + + const targets = JSON.parse(process.env.TARGETS_JSON || "[]"); + const releaseSha = process.env.RELEASE_SHA; + + const runGit = (args) => { + try { + return execFileSync("git", args, { encoding: "utf8" }).trim(); + } catch { + return ""; + } + }; + + const readJsonFromGit = (gitRef, filePath) => { + try { + return JSON.parse( + execFileSync( + "git", + ["show", `${gitRef}:${filePath}`], + { encoding: "utf8" }, + ), + ); + } catch { + return null; + } + }; + + function classifyVersionBump(previousVersion, newVersion) { + const [pMajor, pMinor, pPatch] = previousVersion.split(".").map(Number); + const [nMajor, nMinor, nPatch] = newVersion.split(".").map(Number); + if (nMajor !== pMajor) return "major"; + if (nMinor !== pMinor) return "minor"; + if (nPatch !== pPatch) return "patch"; + return "none"; + } + + function isOutsideCaretRange(rangeVersion, newVersion) { + const [rMajor, rMinor, rPatch] = rangeVersion.split(".").map(Number); + const [nMajor, nMinor, nPatch] = newVersion.split(".").map(Number); + if (rMajor === 0 && rMinor === 0) { + return nMajor !== 0 || nMinor !== 0 || nPatch !== rPatch; + } + if (rMajor === 0) return nMajor !== 0 || nMinor !== rMinor; + return nMajor !== rMajor; + } + + // --- 1. Version changes table --- + + let summary = "## Release Summary\n\n"; + summary += `**Release SHA:** \`${releaseSha.slice(0, 12)}\`\n`; + summary += `**Packages:** ${targets.length}\n\n`; + summary += "### Version Changes\n\n"; + summary += "| Package | Previous | New |\n"; + summary += "| --- | --- | --- |\n"; + for (const t of targets) { + const prev = t.latestVersion ?? "_new_"; + summary += `| \`${t.name}\` | ${prev} | **${t.version}** |\n`; + } + summary += "\n"; + + const breakingTargets = targets + .filter((target) => target.latestVersion) + .map((target) => ({ + ...target, + previousVersion: target.latestVersion, + bumpType: classifyVersionBump(target.latestVersion, target.version), + })) + .filter((target) => isOutsideCaretRange(target.previousVersion, target.version)) + .sort((a, b) => a.name.localeCompare(b.name)); + + summary += "### Breaking Version Changes\n\n"; + if (breakingTargets.length > 0) { + summary += "These published versions move outside the previous `^` range and would be breaking for caret consumers if this release is published:\n\n"; + summary += "| Package | Previous | New | Bump |\n"; + summary += "| --- | --- | --- | --- |\n"; + for (const target of breakingTargets) { + summary += `| \`${target.name}\` | ${target.previousVersion} | **${target.version}** | ${target.bumpType} |\n`; + } + } else { + summary += "No published package versions move outside their previous `^` range.\n"; + } + summary += "\n"; + + // --- 2. Cascade impact --- + + const packagesDir = path.join(process.cwd(), "packages"); + const currentPackages = new Map(); + const previousPackages = new Map(); + + for (const entry of fs.readdirSync(packagesDir, { withFileTypes: true })) { + if (!entry.isDirectory()) continue; + const dir = entry.name; + const pkgJsonPath = path.join(packagesDir, dir, "package.json"); + let pkgJson; + try { + pkgJson = JSON.parse(fs.readFileSync(pkgJsonPath, "utf8")); + } catch { + continue; + } + if (pkgJson.private) continue; + + currentPackages.set(pkgJson.name, { + dir, + ...pkgJson, + }); + + const previousPkgJson = readJsonFromGit( + `${releaseSha}^`, + path.join("packages", dir, "package.json"), + ); + + if (!previousPkgJson || previousPkgJson.private) continue; + + previousPackages.set(previousPkgJson.name, { + dir, + ...previousPkgJson, + }); + } + + const targetVersions = new Map( + targets.map((target) => [target.name, target.version]), + ); + const cascadeByConsumer = new Map(); + + for (const previousPkg of previousPackages.values()) { + const allDeps = { + ...previousPkg.dependencies, + ...previousPkg.peerDependencies, + }; + + for (const [depName, range] of Object.entries(allDeps)) { + if (typeof range !== "string" || !range.startsWith("^")) continue; + + const targetVersion = targetVersions.get(depName); + if (!targetVersion) continue; + + const rangeVersion = range.replace(/^\^/, ""); + if (!isOutsideCaretRange(rangeVersion, targetVersion)) continue; + + const currentPkg = currentPackages.get(previousPkg.name); + + const existing = cascadeByConsumer.get(previousPkg.name); + + if (existing) { + existing.triggers.push({ + name: depName, + range, + version: targetVersion, + }); + continue; + } + + cascadeByConsumer.set(previousPkg.name, { + name: previousPkg.name, + previousVersion: previousPkg.version, + newVersion: currentPkg?.version ?? previousPkg.version, + triggers: [ + { + name: depName, + range, + version: targetVersion, + }, + ], + }); + } + } + + const cascadeEntries = Array.from(cascadeByConsumer.values()).sort( + (a, b) => a.name.localeCompare(b.name), + ); + + if (cascadeEntries.length > 0) { + summary += "### Downstream Impact\n\n"; + summary += `${cascadeEntries.length} internal consumer(s) have \`^\` ranges that will be broken by these version changes:\n\n`; + summary += "| Consumer | Version | Broken Dependencies |\n"; + summary += "| --- | --- | --- |\n"; + for (const c of cascadeEntries) { + const brokenDeps = c.triggers + .map((trigger) => `\`${trigger.name}\` (${trigger.range} -> ${trigger.version})`) + .join(", "); + summary += `| \`${c.name}\` | ${c.previousVersion} -> ${c.newVersion} | ${brokenDeps} |\n`; + } + summary += "\n"; + } + + // --- 3. Changelog entries --- + + summary += "### Changelog\n\n"; + for (const t of targets) { + const changelogPath = path.join(packagesDir, t.dir, "CHANGELOG.md"); + let changelogEntry = ""; + try { + const content = fs.readFileSync(changelogPath, "utf8"); + const versionEscaped = t.version.replace(/\./g, "\\."); + const headingRe = new RegExp("^## " + versionEscaped + "\\b", "m"); + const headingMatch = content.match(headingRe); + if (headingMatch) { + const headingEnd = content.indexOf("\n", headingMatch.index); + const afterHeading = headingEnd === -1 ? content.length : headingEnd + 1; + const rest = content.slice(afterHeading); + const nextIdx = rest.search(/^## /m); + changelogEntry = (nextIdx === -1 ? rest : rest.slice(0, nextIdx)).trim(); + } + } catch { + // No changelog file + } + + if (changelogEntry) { + summary += `<details><summary><code>${t.name}@${t.version}</code></summary>\n\n`; + summary += changelogEntry + "\n"; + summary += "</details>\n\n"; + } else { + summary += `- \`${t.name}@${t.version}\` — _no changelog entry_\n`; + } + } + summary += "\n"; + + // --- 4. Commit history --- + + const prevVersionCommit = runGit([ + "log", "--format=%H", "--fixed-strings", "--grep=chore: update versions", + `${releaseSha}^`, "--max-count=1", + ]); + + if (prevVersionCommit) { + const commitLog = runGit([ + "log", "--oneline", "--no-merges", + `${prevVersionCommit}..${releaseSha}`, + ]); + if (commitLog) { + summary += "### Commits in This Release\n\n"; + summary += "```\n" + commitLog + "\n```\n\n"; + } + } + + await core.summary.addRaw(summary).write(); + core.info("Release summary written to job summary."); + + - name: No targets + if: steps.targets.outputs.targetCount == '0' + run: echo "No packages require publishing for this release SHA." + + publish: + name: Publish to npm + needs: + - approve + - cancel_pending + - summary + if: needs.summary.outputs.target_count != '0' + environment: npm Publish + runs-on: ubuntu-24.04 + permissions: + contents: write + # Required for npm trusted publishing (OIDC). + id-token: write + concurrency: + group: npm-publish-exec + cancel-in-progress: false + env: + RELEASE_SHA: ${{ needs.approve.outputs.release_sha }} + steps: + + - name: Checkout code repository + uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0 + with: + fetch-depth: 0 + ref: ${{ env.RELEASE_SHA }} + + - name: Install pnpm + uses: pnpm/action-setup@0ebf47130e4866e96fce0953f49152a61190b271 # v6.0.9 + + - name: Setup node.js + uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6.4.0 + with: + node-version: 24 + # No cache - hardened against cache poisoning for sensitive publish operations + + - name: Install dependencies + run: pnpm install --frozen-lockfile + + - name: Verify npm trusted publishing + # Temporarily disabled: npm appears to reject the subsequent CLI OIDC + # exchange after this pre-flight call within the same job, causing + # "OIDC publish authorize: Invalid token" on every package. + if: false + uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0 + env: + TARGETS_JSON: ${{ needs.summary.outputs.targets_json }} + with: + script: | + const targets = JSON.parse(process.env.TARGETS_JSON || "[]"); + const oidcRequestUrl = process.env.ACTIONS_ID_TOKEN_REQUEST_URL; + const oidcRequestToken = process.env.ACTIONS_ID_TOKEN_REQUEST_TOKEN; + + if (!oidcRequestUrl || !oidcRequestToken) { + core.setFailed("OIDC token request context is unavailable"); + return; + } + + const audience = "npm:registry.npmjs.org"; + const oidcUrl = `${oidcRequestUrl}${oidcRequestUrl.includes("?") ? "&" : "?"}audience=${encodeURIComponent(audience)}`; + + const fetchOidcToken = async () => { + const controller = new AbortController(); + const timeout = setTimeout(() => controller.abort(), 15_000); + try { + const response = await fetch(oidcUrl, { + headers: { Authorization: `Bearer ${oidcRequestToken}` }, + signal: controller.signal, + }); + if (!response.ok) { + throw new Error(`HTTP ${response.status}`); + } + const payload = await response.json(); + if (!payload?.value) { + throw new Error("OIDC response is missing token value"); + } + return payload.value; + } finally { + clearTimeout(timeout); + } + }; + + const failures = []; + + for (const target of targets) { + let githubOidcToken; + try { + githubOidcToken = await fetchOidcToken(); + } catch (error) { + core.setFailed( + `Failed to fetch GitHub OIDC token: ${error.message}`, + ); + return; + } + + const encodedName = encodeURIComponent(target.name); + const exchangeUrl = + `https://registry.npmjs.org/-/npm/v1/oidc/token/exchange/package/${encodedName}`; + + const controller = new AbortController(); + const timeout = setTimeout(() => controller.abort(), 15_000); + let response; + try { + response = await fetch(exchangeUrl, { + method: "POST", + headers: { Authorization: `Bearer ${githubOidcToken}` }, + signal: controller.signal, + }); + } catch (error) { + failures.push(`${target.name}: network error (${error.message})`); + continue; + } finally { + clearTimeout(timeout); + } + + if (!response.ok) { + const hint = + response.status === 404 + ? "not configured for npm trusted publishing" + : `HTTP ${response.status}`; + failures.push(`${target.name}: ${hint}`); + } + } + + if (failures.length > 0) { + core.setFailed( + [ + `Pre-flight OIDC verification failed for ${failures.length} package(s):`, + ...failures.map((f) => ` - ${f}`), + "", + "Configure trusted publishing for each failed package at:", + " https://docs.npmjs.com/trusted-publishers", + ].join("\n"), + ); + return; + } + + core.info( + `All ${targets.length} package(s) verified for npm trusted publishing.`, + ); + + - name: Publish to npm and create GitHub releases + id: changesets-publish + uses: changesets/action@a45c4d594aa4e2c509dc14a9f2b3b67ba3780d0d # v1.9.0 + with: + publish: pnpm ci:publish + createGithubReleases: true + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + # The action's fallback version path commits without ci:version's lockfile refresh, so the pre-commit hook's pnpm run fails on the stale lockfile. + HUSKY: 0 + + - name: Recovery guidance + if: failure() + run: | + echo "::error::Publish failed for release SHA ${RELEASE_SHA}." + echo "::error::Rerun this workflow run, or run workflow_dispatch with releaseSha=${RELEASE_SHA}." diff --git a/.github/workflows/pypi-publish.yaml b/.github/workflows/pypi-publish.yaml new file mode 100644 index 0000000..5e27320 --- /dev/null +++ b/.github/workflows/pypi-publish.yaml @@ -0,0 +1,309 @@ +name: PyPI Publish + +on: + repository_dispatch: + types: [pypi-publish] + workflow_dispatch: + inputs: + releaseSha: + description: "Release commit SHA to publish (defaults to latest default-branch commit)" + required: false + type: string + +permissions: + contents: read + +env: + CI: true + PYTHON_PACKAGE_DIR: python/assistant-stream + PYTHON_PACKAGE_NAME: assistant-stream + +jobs: + approve: + name: Resolve release SHA + runs-on: ubuntu-24.04 + concurrency: + group: pypi-publish-approval + cancel-in-progress: true + outputs: + release_sha: ${{ steps.release-sha.outputs.value }} + default_branch: ${{ steps.release-sha.outputs.default_branch }} + steps: + - name: Resolve release commit SHA + id: release-sha + uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0 + with: + script: | + const dispatchReleaseShaRaw = context.payload?.client_payload?.releaseSha; + const manualReleaseShaRaw = context.payload?.inputs?.releaseSha; + const payloadDefaultBranchRaw = + context.payload?.repository?.default_branch; + const dispatchReleaseSha = + typeof dispatchReleaseShaRaw === "string" + ? dispatchReleaseShaRaw.trim() + : ""; + const manualReleaseSha = + typeof manualReleaseShaRaw === "string" + ? manualReleaseShaRaw.trim() + : ""; + const payloadDefaultBranch = + typeof payloadDefaultBranchRaw === "string" + ? payloadDefaultBranchRaw.trim() + : ""; + + let releaseSha = dispatchReleaseSha || manualReleaseSha; + let defaultBranch = payloadDefaultBranch; + + if (!defaultBranch) { + const repoInfo = await github.rest.repos.get(context.repo); + defaultBranch = repoInfo.data.default_branch; + } + + if (!defaultBranch) { + core.setFailed("Unable to determine repository default branch"); + return; + } + + if (!releaseSha) { + if (context.eventName === "repository_dispatch") { + core.setFailed("repository_dispatch payload is missing releaseSha"); + return; + } + + const ref = await github.rest.git.getRef({ + ...context.repo, + ref: `heads/${defaultBranch}`, + }); + + releaseSha = ref.data.object.sha; + core.info( + `No release SHA provided; using ${defaultBranch} HEAD ${releaseSha}`, + ); + } + + if (!/^[0-9a-f]{40}$/i.test(releaseSha)) { + core.setFailed( + `releaseSha must be a full 40-character commit SHA: ${releaseSha}`, + ); + return; + } + + core.setOutput("value", releaseSha); + core.setOutput("default_branch", defaultBranch); + + summary: + name: Resolve publish target + needs: approve + runs-on: ubuntu-24.04 + outputs: + should_publish: ${{ steps.target.outputs.shouldPublish }} + version: ${{ steps.target.outputs.version }} + env: + RELEASE_SHA: ${{ needs.approve.outputs.release_sha }} + DEFAULT_BRANCH: ${{ needs.approve.outputs.default_branch }} + steps: + - name: Checkout code repository + uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0 + with: + fetch-depth: 0 + ref: ${{ env.RELEASE_SHA }} + + - name: Validate release commit is on default branch + run: | + if [ -z "$RELEASE_SHA" ]; then + echo "Missing releaseSha" >&2 + exit 1 + fi + + if [ -z "$DEFAULT_BRANCH" ]; then + echo "Missing default branch" >&2 + exit 1 + fi + + head_sha=$(git rev-parse HEAD) + if [ "$head_sha" != "$RELEASE_SHA" ]; then + echo "Checked out commit does not match releaseSha payload" >&2 + echo "HEAD=$head_sha" >&2 + echo "releaseSha=$RELEASE_SHA" >&2 + exit 1 + fi + + git fetch origin "$DEFAULT_BRANCH" + origin_default_sha=$(git rev-parse "origin/$DEFAULT_BRANCH") + if ! git merge-base --is-ancestor "$RELEASE_SHA" "origin/$DEFAULT_BRANCH"; then + echo "releaseSha is not an ancestor of origin/$DEFAULT_BRANCH; refusing to publish" >&2 + echo "releaseSha=$RELEASE_SHA" >&2 + echo "origin/$DEFAULT_BRANCH SHA=$origin_default_sha" >&2 + exit 1 + fi + + - name: Resolve target version + PyPI status + id: target + run: | + set -euo pipefail + + version=$(python3 - <<'PY' "$PYTHON_PACKAGE_DIR/pyproject.toml" + import sys, tomllib + with open(sys.argv[1], "rb") as f: + data = tomllib.load(f) + v = data.get("project", {}).get("version") + if not v: + sys.exit("[project].version missing from pyproject.toml") + print(v) + PY + ) + + if ! [[ "$version" =~ ^[0-9]+\.[0-9]+\.[0-9]+$ ]]; then + echo "Refusing to publish non-stable version: $version" >&2 + exit 1 + fi + + echo "version=$version" >> "$GITHUB_OUTPUT" + + pypi_body=$(mktemp) + trap 'rm -f "$pypi_body"' EXIT + status=$(curl -s --max-time 30 -o "$pypi_body" -w "%{http_code}" \ + "https://pypi.org/pypi/$PYTHON_PACKAGE_NAME/json") + + case "$status" in + 200) + latest=$(python3 -c 'import sys, json; print(json.load(sys.stdin).get("info", {}).get("version") or "")' < "$pypi_body") + exists=$(python3 -c 'import sys, json; d=json.load(sys.stdin); print("true" if sys.argv[1] in d.get("releases", {}) else "false")' "$version" < "$pypi_body") + ;; + 404) + latest="" + exists="false" + ;; + *) + echo "Unexpected PyPI status $status for $PYTHON_PACKAGE_NAME" >&2 + exit 1 + ;; + esac + + if [ -n "$latest" ] && [ "$version" != "$latest" ]; then + highest=$(printf '%s\n%s\n' "$latest" "$version" | sort -V | tail -n1) + if [ "$highest" != "$version" ]; then + echo "Refusing to publish $PYTHON_PACKAGE_NAME@$version: lower than current PyPI latest ($latest)" >&2 + exit 1 + fi + fi + + if [ "$exists" = "true" ]; then + echo "$PYTHON_PACKAGE_NAME@$version already on PyPI; skipping publish." + echo "shouldPublish=false" >> "$GITHUB_OUTPUT" + else + echo "$PYTHON_PACKAGE_NAME@$version not yet on PyPI; will publish." + echo "shouldPublish=true" >> "$GITHUB_OUTPUT" + fi + + - name: Write release summary + if: steps.target.outputs.shouldPublish == 'true' + run: | + { + echo "## PyPI Release Summary" + echo + echo "**Release SHA:** \`${RELEASE_SHA:0:12}\`" + echo "**Package:** \`$PYTHON_PACKAGE_NAME\`" + echo "**Version:** **${{ steps.target.outputs.version }}**" + } >> "$GITHUB_STEP_SUMMARY" + + - name: No targets + if: steps.target.outputs.shouldPublish == 'false' + run: echo "$PYTHON_PACKAGE_NAME@${{ steps.target.outputs.version }} is already published; nothing to do." + + publish: + name: Publish to PyPI + needs: + - approve + - summary + if: needs.summary.outputs.should_publish == 'true' + environment: pypi publish + runs-on: ubuntu-24.04 + permissions: + # Required to create GitHub releases. + contents: write + # Required for PyPI trusted publishing (OIDC). + id-token: write + concurrency: + group: pypi-publish-exec + cancel-in-progress: false + env: + RELEASE_SHA: ${{ needs.approve.outputs.release_sha }} + VERSION: ${{ needs.summary.outputs.version }} + steps: + - name: Checkout code repository + uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0 + with: + fetch-depth: 1 + fetch-tags: true + ref: ${{ env.RELEASE_SHA }} + + - name: Install uv + uses: astral-sh/setup-uv@11f9893b081a58869d3b5fccaea48c9e9e46f990 # v8.3.2 + with: + # No cache - hardened against cache poisoning for sensitive publish operations + enable-cache: false + + - name: Build sdist + wheel + working-directory: ${{ env.PYTHON_PACKAGE_DIR }} + run: | + set -euo pipefail + rm -rf dist + uv build + ls -la dist + + - name: Verify built version matches target + working-directory: ${{ env.PYTHON_PACKAGE_DIR }} + run: | + set -euo pipefail + dist_name="${PYTHON_PACKAGE_NAME//-/_}-${VERSION}" + shopt -s nullglob + wheels=( "dist/${dist_name}"-*.whl ) + if [ "${#wheels[@]}" -eq 0 ] || [ ! -f "dist/${dist_name}.tar.gz" ]; then + echo "Expected build artifacts for ${dist_name} are missing." >&2 + ls dist >&2 || true + exit 1 + fi + + # PyPI trusted publishing requires this workflow filename and the + # `pypi publish` environment to match the project's trusted-publisher config. + - name: Publish to PyPI via OIDC + uses: pypa/gh-action-pypi-publish@cef221092ed1bacb1cc03d23a2d87d1d172e277b # v1.14.0 + with: + packages-dir: ${{ env.PYTHON_PACKAGE_DIR }}/dist + + - name: Create GitHub release + env: + GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} + run: | + set -euo pipefail + tag="pypi/${PYTHON_PACKAGE_NAME}@${VERSION}" + + if gh release view "$tag" >/dev/null 2>&1; then + echo "Release $tag already exists; skipping." + exit 0 + fi + + prev=$(git tag --list "pypi/${PYTHON_PACKAGE_NAME}@*" \ + | sort -V \ + | awk -v t="$tag" '$0 != t' \ + | tail -n1) + + if [ -n "$prev" ]; then + gh release create "$tag" \ + --target "$RELEASE_SHA" \ + --title "$tag" \ + --notes-start-tag "$prev" \ + --generate-notes + else + gh release create "$tag" \ + --target "$RELEASE_SHA" \ + --title "$tag" \ + --notes "First $PYTHON_PACKAGE_NAME release published via this workflow." + fi + + - name: Recovery guidance + if: failure() + run: | + echo "::error::Publish failed for $PYTHON_PACKAGE_NAME@${VERSION} on release SHA ${RELEASE_SHA}." + echo "::error::Rerun this workflow run, or workflow_dispatch with releaseSha=${RELEASE_SHA}." diff --git a/.github/workflows/python-tests.yaml b/.github/workflows/python-tests.yaml new file mode 100644 index 0000000..9f2175c --- /dev/null +++ b/.github/workflows/python-tests.yaml @@ -0,0 +1,51 @@ +name: Python Tests + +on: + push: + branches: [main] + paths: + - "python/**" + - ".github/workflows/python-tests.yaml" + pull_request: + branches: [main] + paths: + - "python/**" + - ".github/workflows/python-tests.yaml" + +permissions: + contents: read + +concurrency: + group: ${{ github.workflow }}-${{ github.ref }} + cancel-in-progress: true + +jobs: + test: + name: pytest (${{ matrix.package }}, ${{ matrix.python-version }}) + runs-on: blacksmith-4vcpu-ubuntu-2404 + timeout-minutes: 10 + strategy: + fail-fast: false + matrix: + package: + - assistant-stream + - assistant-ui-sync-server-api + python-version: ["3.10", "3.12"] + defaults: + run: + working-directory: python/${{ matrix.package }} + steps: + - name: Checkout code repository + uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0 + + - name: Install uv + uses: astral-sh/setup-uv@11f9893b081a58869d3b5fccaea48c9e9e46f990 # v8.3.2 + with: + python-version: ${{ matrix.python-version }} + enable-cache: true + + - name: Install dependencies + run: uv sync --all-extras + + - name: Run tests + run: uv run pytest diff --git a/.github/workflows/registry.yaml b/.github/workflows/registry.yaml new file mode 100644 index 0000000..c45ff3a --- /dev/null +++ b/.github/workflows/registry.yaml @@ -0,0 +1,67 @@ +name: Deploy Shadcn Registry + +on: + push: + branches: + - main + paths: + - apps/registry/** + - packages/ui/** + - .github/workflows/registry.yaml + +permissions: + contents: read + +concurrency: + group: ${{ github.workflow }}-${{ github.ref }} + cancel-in-progress: false + +env: + VERCEL_ORG_ID: ${{ secrets.VERCEL_ORG_ID }} + VERCEL_PROJECT_ID: ${{ secrets.VERCEL_REGISTRY_PROJECT_ID }} + +jobs: + deploy-production: + name: Deploy Production + runs-on: blacksmith-4vcpu-ubuntu-2404 + steps: + - name: Checkout code repository + uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # ratchet:actions/checkout@v7 + with: + fetch-depth: 1 + + - name: Install pnpm + uses: pnpm/action-setup@0ebf47130e4866e96fce0953f49152a61190b271 # ratchet:pnpm/action-setup@v6.0.9 + + - name: Setup node.js + uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # ratchet:actions/setup-node@v6 + with: + node-version: 24 + cache: "pnpm" + + - name: Cache turbo build setup + uses: actions/cache@caa296126883cff596d87d8935842f9db880ef25 # ratchet:actions/cache@v5 + with: + path: .turbo + key: ${{ runner.os }}-turbo-${{ github.ref }}-${{ github.sha }} + restore-keys: | + ${{ runner.os }}-turbo-${{ github.ref }}- + ${{ runner.os }}-turbo- + + - name: Install Vercel CLI + run: npm install --global vercel@latest + + - name: Install dependencies + run: pnpm install --frozen-lockfile + + - name: Pull Vercel Environment Information + working-directory: apps/registry + run: vercel pull --yes --prod --token=${{ secrets.VERCEL_TOKEN }} + + - name: Build Project Artifacts + working-directory: apps/registry + run: vercel build --prod --token=${{ secrets.VERCEL_TOKEN }} + + - name: Deploy Project Artifacts to Vercel + working-directory: apps/registry + run: vercel deploy --prebuilt --prod --token=${{ secrets.VERCEL_TOKEN }} diff --git a/.github/workflows/template-metrics.yaml b/.github/workflows/template-metrics.yaml new file mode 100644 index 0000000..d9d2665 --- /dev/null +++ b/.github/workflows/template-metrics.yaml @@ -0,0 +1,157 @@ +name: Template Metrics + +on: + push: + branches: [main] + paths: + - "templates/**" + - "packages/ui/**" + pull_request: + branches: [main] + paths: + - "templates/**" + - "packages/ui/**" + +permissions: + contents: read + pull-requests: write + +concurrency: + group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }} + cancel-in-progress: true + +env: + # Templates to build for bundle-size measurement. LOC is measured for all + # templates regardless; only these are built (next build is the expensive part). + BUNDLE_TEMPLATES: "default minimal" + # Per-template regression limits. A template that already exists on the main + # baseline fails the gate if it grows past either limit. + MAX_LOC_INCREASE: "50" + MAX_BUNDLE_INCREASE_KB: "10" + +jobs: + metrics: + name: LOC + Bundle Size + runs-on: blacksmith-4vcpu-ubuntu-2404 + timeout-minutes: 20 + steps: + - name: Checkout code repository + uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # ratchet:actions/checkout@v6 + with: + fetch-depth: 0 + + - name: Install pnpm + uses: pnpm/action-setup@b906affcce14559ad1aafd4ab0e942779e9f58b1 # ratchet:pnpm/action-setup@v4 + + - name: Setup node.js + uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # ratchet:actions/setup-node@v6 + with: + node-version: 24 + cache: "pnpm" + + - name: Cache turbo build setup + uses: actions/cache@0057852bfaa89a56745cba8c7296529d2fc39830 # ratchet:actions/cache@v4 + with: + path: .turbo + key: ${{ runner.os }}-turbo-${{ github.ref }}-${{ github.sha }} + restore-keys: | + ${{ runner.os }}-turbo-${{ github.ref }}- + ${{ runner.os }}-turbo- + + # Baseline is main's metrics, cached by the last run on main. PRs restore + # the newest one via the restore-keys prefix to compute deltas against main. + - name: Restore main baseline + uses: actions/cache/restore@0057852bfaa89a56745cba8c7296529d2fc39830 # ratchet:actions/cache/restore@v4 + with: + path: install-footprint/branch/main/data.json + key: install-footprint-main-${{ github.sha }} + restore-keys: install-footprint-main- + + - name: Install dependencies + run: pnpm install --frozen-lockfile + + - name: Build templates (bundle subset) + env: + # next build needs these public vars defined; values are placeholders. + NEXT_PUBLIC_ASSISTANT_BASE_URL: "https://example.com" + NEXT_PUBLIC_CLERK_PUBLISHABLE_KEY: "pk_test_placeholder" + NEXT_TELEMETRY_DISABLED: "1" + run: | + filters="" + for t in $BUNDLE_TEMPLATES; do + filters="$filters --filter=./templates/$t" + done + # turbo pulls in the @assistant-ui/* workspace deps each template needs. + pnpm turbo build $filters + + - name: Measure metrics + run: node scripts/template-metrics.mjs measure . metrics-head.json + + - name: Build report + run: | + node scripts/template-metrics.mjs report install-footprint/branch/main/data.json metrics-head.json metrics-report.md metrics-gate.txt metrics-comment.txt + cat metrics-report.md + + # continue-on-error: a fork PR's read-only token makes commenting 403; that + # must not fail the check or skip the gate below. + - name: Post / update PR comment + if: github.event_name == 'pull_request' + continue-on-error: true + uses: actions/github-script@f28e40c7f34bde8b3046d885e986cb6290c5673b # ratchet:actions/github-script@v7 + with: + script: | + const fs = require('fs'); + const body = fs.readFileSync('metrics-report.md', 'utf8'); + const marker = '<!-- template-metrics -->'; + // paginate: a long PR thread can push the sticky comment past the + // first 30, and missing it would post a duplicate on every re-run. + const comments = await github.paginate(github.rest.issues.listComments, { + owner: context.repo.owner, + repo: context.repo.repo, + issue_number: context.issue.number, + }); + const existing = comments.find((c) => c.body?.includes(marker)); + const shouldCreate = fs.readFileSync('metrics-comment.txt', 'utf8').trim() === 'post'; + if (!existing && !shouldCreate) { + console.log('No LOC changes; skipping template metrics comment.'); + return; + } + if (existing) { + await github.rest.issues.updateComment({ + owner: context.repo.owner, + repo: context.repo.repo, + comment_id: existing.id, + body, + }); + } else { + await github.rest.issues.createComment({ + owner: context.repo.owner, + repo: context.repo.repo, + issue_number: context.issue.number, + body, + }); + } + + # always(): run even if the comment step failed (e.g. fork 403), so the + # gate verdict is never lost. Only blocks PRs; pushes refresh the baseline. + - name: Enforce regression gate + if: always() && github.event_name == 'pull_request' + run: | + if [ "$(cat metrics-gate.txt 2>/dev/null)" = "fail" ]; then + echo "::error::Template metrics regressed past the configured limits. See the PR comment." + exit 1 + fi + echo "Template metrics within limits." + + - name: Persist baseline (main only) + if: github.event_name == 'push' + run: | + mkdir -p install-footprint/branch/main + cp metrics-head.json install-footprint/branch/main/data.json + + - name: Save baseline cache (main only) + if: github.event_name == 'push' + uses: actions/cache/save@0057852bfaa89a56745cba8c7296529d2fc39830 # ratchet:actions/cache/save@v4 + with: + path: install-footprint/branch/main/data.json + key: install-footprint-main-${{ github.sha }} diff --git a/.github/workflows/traction.yaml b/.github/workflows/traction.yaml new file mode 100644 index 0000000..9260470 --- /dev/null +++ b/.github/workflows/traction.yaml @@ -0,0 +1,399 @@ +name: Update README Traction Chart + +on: + schedule: + - cron: "0 14 * * 1" # Monday 8 AM Central + workflow_dispatch: + +permissions: + contents: write + pull-requests: write + +jobs: + generate-chart: + runs-on: ubuntu-latest + timeout-minutes: 15 + + steps: + - name: Checkout + uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0 + + - name: Setup Node.js + uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6.4.0 + with: + node-version: 24 + + - name: Install dependencies + run: | + mkdir -p /tmp/sharp-deps + cd /tmp/sharp-deps + npm init -y > /dev/null + npm install sharp + + - name: Generate chart + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + NODE_PATH: /tmp/sharp-deps/node_modules + run: | + node << 'SCRIPT' + const sharp = require('sharp'); + const fs = require('fs'); + + const PACKAGE_NAME = '@assistant-ui/react'; + const GITHUB_REPO = 'assistant-ui/assistant-ui'; + const OUTPUT_PATH = '.github/assets/traction.png'; + + const COLORS = { primary: '#ffffff', text: '#ffffff', textMuted: '#888888', grid: '#333333' }; + + const ASSISTANT_UI_LOGO = `<path d="M14 9a2 2 0 0 1-2 2H6l-4 4V4c0-1.1.9-2 2-2h8a2 2 0 0 1 2 2z" fill="none" stroke="${COLORS.text}" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/><path d="M18 9h2a2 2 0 0 1 2 2v11l-4-4h-6a2 2 0 0 1-2-2v-1" fill="none" stroke="${COLORS.text}" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>`; + const GITHUB_LOGO = `<path d="M12 2C6.477 2 2 6.477 2 12c0 4.42 2.865 8.17 6.839 9.49.5.092.682-.217.682-.482 0-.237-.008-.866-.013-1.7-2.782.604-3.369-1.34-3.369-1.34-.454-1.156-1.11-1.463-1.11-1.463-.908-.62.069-.608.069-.608 1.003.07 1.531 1.03 1.531 1.03.892 1.529 2.341 1.087 2.91.831.092-.646.35-1.086.636-1.336-2.22-.253-4.555-1.11-4.555-4.943 0-1.091.39-1.984 1.029-2.683-.103-.253-.446-1.27.098-2.647 0 0 .84-.269 2.75 1.025A9.578 9.578 0 0112 6.836c.85.004 1.705.114 2.504.336 1.909-1.294 2.747-1.025 2.747-1.025.546 1.377.203 2.394.1 2.647.64.699 1.028 1.592 1.028 2.683 0 3.842-2.339 4.687-4.566 4.935.359.309.678.919.678 1.852 0 1.336-.012 2.415-.012 2.743 0 .267.18.578.688.48C19.138 20.167 22 16.418 22 12c0-5.523-4.477-10-10-10z" fill="${COLORS.textMuted}"/>`; + const NPM_LOGO = `<path d="M0,0 H24 V24 H0 Z M4.5,4.5 V19.5 H12 V7.5 H16.5 V19.5 H19.5 V4.5 Z" fill="${COLORS.textMuted}" fill-rule="evenodd"/>`; + + function getStartDate() { + const d = new Date(); + d.setMonth(d.getMonth() - 18); + return d.toISOString().split('T')[0]; + } + + const START_DATE = getStartDate(); + + async function fetchNpmStats(packageName) { + const end = new Date().toISOString().split('T')[0]; + const url = `https://npm-stat.com/api/download-counts?package=${encodeURIComponent(packageName)}&from=${START_DATE}&until=${end}`; + const response = await fetch(url); + if (!response.ok) throw new Error(`npm stats fetch failed: ${response.status}`); + const data = await response.json(); + return data[packageName] || {}; + } + + async function fetchCurrentStars(repo) { + const [owner, name] = repo.split('/'); + const headers = { 'Accept': 'application/vnd.github.v3+json' }; + if (process.env.GITHUB_TOKEN) { + headers['Authorization'] = `token ${process.env.GITHUB_TOKEN}`; + } + const response = await fetch(`https://api.github.com/repos/${owner}/${name}`, { headers }); + if (!response.ok) throw new Error(`GitHub repo fetch failed: ${response.status}`); + const data = await response.json(); + return data.stargazers_count; + } + + async function fetchStarHistory(repo, currentStars) { + const [owner, name] = repo.split('/'); + const stars = []; + let page = 1; + const headers = { 'Accept': 'application/vnd.github.star+json' }; + if (process.env.GITHUB_TOKEN) { + headers['Authorization'] = `token ${process.env.GITHUB_TOKEN}`; + } + + // Paginate through all stars (no hard limit) with delays to avoid rate limits + while (true) { + if (page > 1) await new Promise(r => setTimeout(r, 100)); + let response = await fetch( + `https://api.github.com/repos/${owner}/${name}/stargazers?per_page=100&page=${page}`, + { headers } + ); + if (response.status === 403 || response.status === 429) { + console.warn(`Rate limited on page ${page}, waiting 3 minutes before retry...`); + await new Promise(r => setTimeout(r, 180000)); + response = await fetch( + `https://api.github.com/repos/${owner}/${name}/stargazers?per_page=100&page=${page}`, + { headers } + ); + } + if (!response.ok) { + console.warn(`Stopped at page ${page}: HTTP ${response.status} (fetched ${stars.length} stars)`); + break; + } + const data = await response.json(); + if (data.length === 0) break; + stars.push(...data.map(s => s.starred_at)); + console.log(`Fetched page ${page}, ${stars.length} stars total`); + if (data.length < 100) break; + page++; + } + + const startDate = new Date(START_DATE); + const starsBeforeStart = stars.filter(d => new Date(d) < startDate).length; + + const weeks = {}; + for (const date of stars) { + const d = new Date(date); + if (d < startDate) continue; + const weekStart = getWeekStart(d.toISOString().split('T')[0]); + weeks[weekStart] = (weeks[weekStart] || 0) + 1; + } + + const result = []; + const now = new Date(); + const currentWeekStart = getWeekStart(now.toISOString().split('T')[0]); + let d = new Date(START_DATE); + const dayOfWeek = d.getDay(); + const daysToMonday = dayOfWeek === 0 ? 1 : (dayOfWeek === 1 ? 0 : 8 - dayOfWeek); + d.setDate(d.getDate() + daysToMonday); + let cumulative = starsBeforeStart; + + while (d <= now) { + const key = d.toISOString().split('T')[0]; + if (key >= currentWeekStart) break; + cumulative += (weeks[key] || 0); + result.push({ date: key, value: cumulative }); + d.setDate(d.getDate() + 7); + } + + result.push({ date: now.toISOString().split('T')[0], value: currentStars }); + return result; + } + + function getWeekStart(date) { + const d = new Date(date); + const day = d.getDay(); + const diff = d.getDate() - day + (day === 0 ? -6 : 1); + d.setDate(diff); + return d.toISOString().split('T')[0]; + } + + function aggregateByWeek(dailyData) { + const sorted = Object.entries(dailyData) + .filter(([, count]) => count > 0) + .sort(([a], [b]) => a.localeCompare(b)); + + if (sorted.length < 7) return sorted.map(([date, value]) => ({ date, value })); + + const result = []; + for (let i = 3; i < sorted.length - 4; i += 3) { + let sum = 0; + for (let j = i - 3; j <= i + 3; j++) { + sum += sorted[j][1]; + } + result.push({ date: sorted[i][0], value: sum }); + } + return result; + } + + function smoothData(data) { + if (data.length < 3) return data; + const avg = []; + for (let i = 0; i < data.length; i++) { + const s = Math.max(0, i - 2), e = Math.min(data.length, i + 3); + const w = data.slice(s, e).map(d => d.value); + avg.push({ date: data[i].date, value: w.reduce((a, b) => a + b, 0) / w.length }); + } + const out = [avg[0]]; + let h = avg[0].value; + for (let i = 1; i < avg.length; i++) { + const p = out[i - 1].value, c = avg[i].value; + h = c > h ? c : h * 0.99; + const v = c > p ? p * 0.3 + c * 0.7 : c < p * 0.8 ? p * 0.85 + h * 0.1 + c * 0.05 : p * 0.95 + c * 0.05; + out.push({ date: avg[i].date, value: Math.round(v) }); + } + return out; + } + + function getMonthlyRate(dailyData) { + const entries = Object.entries(dailyData).sort(([a], [b]) => a.localeCompare(b)); + const recent = entries.slice(-60); + if (recent.length < 30) return Math.round(recent.reduce((s, [, v]) => s + v, 0) / recent.length * 30); + let max = 0; + for (let i = 0; i <= recent.length - 30; i++) { + const sum = recent.slice(i, i + 30).reduce((s, [, v]) => s + v, 0); + if (sum > max) max = sum; + } + return max; + } + + function formatNumber(num) { + if (num >= 1000000) return (num / 1000000).toFixed(1) + 'M'; + if (num >= 1000) return (num / 1000).toFixed(0) + 'K'; + return num.toString(); + } + + function smoothPath(pts) { + if (pts.length < 2) return ''; + if (pts.length === 2) return `M ${pts[0].x.toFixed(1)} ${pts[0].y.toFixed(1)} L ${pts[1].x.toFixed(1)} ${pts[1].y.toFixed(1)}`; + let path = `M ${pts[0].x.toFixed(1)} ${pts[0].y.toFixed(1)}`; + for (let i = 0; i < pts.length - 1; i++) { + const p0 = pts[Math.max(0, i - 1)]; + const p1 = pts[i]; + const p2 = pts[i + 1]; + const p3 = pts[Math.min(pts.length - 1, i + 2)]; + const cp1x = p1.x + (p2.x - p0.x) / 6; + const cp1y = p1.y + (p2.y - p0.y) / 6; + const cp2x = p2.x - (p3.x - p1.x) / 6; + const cp2y = p2.y - (p3.y - p1.y) / 6; + path += ` C ${cp1x.toFixed(1)} ${cp1y.toFixed(1)}, ${cp2x.toFixed(1)} ${cp2y.toFixed(1)}, ${p2.x.toFixed(1)} ${p2.y.toFixed(1)}`; + } + return path; + } + + function generateDualSVG(starsData, downloadsData, monthlyDownloads, currentStars, totalDownloads) { + const width = 800; + const height = 380; + const margin = 20; + const gap = 24; + const cardWidth = (width - margin * 2 - gap) / 2; + const cardHeight = height - margin * 2; + const chartPadding = { top: 50, right: 20, bottom: 35, left: 45 }; + const innerW = cardWidth - chartPadding.left - chartPadding.right; + const innerH = cardHeight - chartPadding.top - chartPadding.bottom; + + function chartCard(data, cardX, cardY, packageName, metricName, subtitle, gradId, clipId, logoSvg, subtitle2 = null, yAxisLabel = null) { + if (!data.length) return ''; + const maxVal = Math.max(...data.map(d => d.value)); + const chartLeft = cardX + chartPadding.left; + const chartRight = cardX + cardWidth - chartPadding.right; + const chartTop = cardY + chartPadding.top; + const chartBottom = cardY + chartPadding.top + innerH; + + const niceStep = (max) => { + const rough = max / 6; + const magnitude = Math.pow(10, Math.floor(Math.log10(rough))); + const normalized = rough / magnitude; + let nice; + if (normalized <= 1) nice = 1; + else if (normalized <= 2) nice = 2; + else if (normalized <= 5) nice = 5; + else nice = 10; + return nice * magnitude; + }; + const step = niceStep(maxVal); + const niceMax = Math.ceil(maxVal / step) * step; + const yVals = []; + for (let v = 0; v <= niceMax; v += step) { + yVals.push(v); + } + + const pts = data.map((d, i) => ({ + x: chartLeft + (data.length > 1 ? (i / (data.length - 1)) * innerW : innerW / 2), + y: chartTop + innerH - (d.value / niceMax) * innerH + })); + const line = smoothPath(pts); + const areaPath = line + ` L ${pts[pts.length - 1].x.toFixed(1)} ${chartBottom} L ${chartLeft} ${chartBottom} Z`; + + const xLabels = []; + const numLabels = 8; + const labelStep = Math.max(1, Math.floor(data.length / (numLabels - 1))); + + let lastYear = null; + for (let labelNum = 0; labelNum < numLabels - 1; labelNum++) { + const i = Math.min(labelNum * labelStep, data.length - 2); + const x = pts[i].x; + const d = new Date(data[i].date); + const year = d.getFullYear(); + const month = d.toLocaleDateString('en-US', { month: 'short' }); + + let label; + if (year !== lastYear && lastYear !== null) { + label = String(year); + } else { + label = month; + } + lastYear = year; + xLabels.push({ x, label }); + } + xLabels.push({ x: pts[data.length - 1].x, label: 'Today' }); + + return ` + <clipPath id="${clipId}"> + <rect x="${chartLeft}" y="${chartTop}" width="${innerW}" height="${innerH + 1}"/> + </clipPath> + <rect x="${cardX}" y="${cardY}" width="${cardWidth}" height="${cardHeight}" fill="#111111" rx="12"/> + <g transform="translate(${cardX + 12}, ${cardY + 10}) scale(0.6)"> + ${ASSISTANT_UI_LOGO} + </g> + <text x="${cardX + 38}" y="${cardY + 24}" fill="${COLORS.text}" font-family="system-ui,-apple-system,sans-serif" font-size="14" font-weight="600">${metricName}</text> + <text x="${cardX + cardWidth - chartPadding.right}" y="${cardY + 26}" fill="${COLORS.text}" font-family="system-ui,sans-serif" font-size="13" font-weight="600" text-anchor="end">${subtitle}</text> + ${subtitle2 ? `<text x="${cardX + cardWidth - chartPadding.right}" y="${cardY + 40}" fill="${COLORS.textMuted}" font-family="system-ui,sans-serif" font-size="10" text-anchor="end">${subtitle2}</text>` : ''} + <rect x="${chartLeft + 4}" y="${chartTop + 4}" width="${packageName.length * 6 + 32}" height="20" fill="#222222" rx="4" opacity="0.9"/> + <g transform="translate(${chartLeft + 8}, ${chartTop + 6}) scale(0.65)"> + ${logoSvg} + </g> + <text x="${chartLeft + 28}" y="${chartTop + 18}" fill="${COLORS.textMuted}" font-family="system-ui,-apple-system,sans-serif" font-size="10">${packageName}</text> + ${yVals.slice(1).map(v => { + const y = chartTop + innerH - (v / niceMax) * innerH; + return `<line x1="${chartLeft}" y1="${y.toFixed(1)}" x2="${chartRight}" y2="${y.toFixed(1)}" stroke="${COLORS.grid}" stroke-width="1" stroke-dasharray="2,4"/>`; + }).join('')} + <g clip-path="url(#${clipId})"> + <path d="${areaPath}" fill="url(#${gradId})"/> + <path d="${line}" fill="none" stroke="${COLORS.primary}" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" opacity="0.3" filter="blur(3px)"/> + <path d="${line}" fill="none" stroke="${COLORS.primary}" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/> + </g> + ${yVals.map(v => { + const y = chartTop + innerH - (v / niceMax) * innerH; + return `<text x="${chartLeft - 6}" y="${(y + 3).toFixed(1)}" fill="${COLORS.textMuted}" font-family="system-ui,sans-serif" font-size="9" text-anchor="end">${formatNumber(v)}</text>`; + }).join('')} + ${yAxisLabel ? `<text x="${chartLeft - 35}" y="${chartTop + innerH / 2}" fill="${COLORS.textMuted}" font-family="system-ui,-apple-system,sans-serif" font-size="11" text-anchor="middle" dominant-baseline="middle" transform="rotate(-90, ${chartLeft - 35}, ${chartTop + innerH / 2})">${yAxisLabel}</text>` : ''} + ${xLabels.map(({ x, label }) => { + return `<text x="${x.toFixed(1)}" y="${cardY + cardHeight - 10}" fill="${COLORS.textMuted}" font-family="system-ui,sans-serif" font-size="9" text-anchor="middle">${label}</text>`; + }).join('')} + `; + } + + const cardY = margin; + const card1X = margin; + const card2X = margin + cardWidth + gap; + + return `<svg xmlns="http://www.w3.org/2000/svg" width="${width}" height="${height}" viewBox="0 0 ${width} ${height}"> + <defs> + <linearGradient id="grad" x1="0%" y1="0%" x2="0%" y2="100%"> + <stop offset="0%" stop-color="${COLORS.primary}" stop-opacity="0.2"/> + <stop offset="50%" stop-color="${COLORS.primary}" stop-opacity="0.08"/> + <stop offset="100%" stop-color="${COLORS.primary}" stop-opacity="0"/> + </linearGradient> + </defs> + ${chartCard(starsData, card1X, cardY, 'assistant-ui/assistant-ui', ' GitHub Stars', formatNumber(currentStars) + ' total', 'grad', 'clip1', GITHUB_LOGO, null, 'Total')} + ${chartCard(downloadsData, card2X, cardY, '@assistant-ui/react', ' npm Downloads', formatNumber(monthlyDownloads) + '/month', 'grad', 'clip2', NPM_LOGO, formatNumber(totalDownloads) + ' total', 'Weekly')} + </svg>`; + } + + async function main() { + const [dailyData, stars] = await Promise.all([fetchNpmStats(PACKAGE_NAME), fetchCurrentStars(GITHUB_REPO)]); + const monthlyDownloads = getMonthlyRate(dailyData); + const totalDownloads = Object.values(dailyData).reduce((sum, v) => sum + v, 0); + const downloadsData = smoothData(aggregateByWeek(dailyData)).slice(0, -1); + const currentStars = stars || 0; + const starsData = currentStars ? await fetchStarHistory(GITHUB_REPO, currentStars) : []; + + const svg = generateDualSVG(starsData, downloadsData, monthlyDownloads, currentStars, totalDownloads); + const png = await sharp(Buffer.from(svg), { density: 300 }).png().toBuffer(); + fs.mkdirSync('.github/assets', { recursive: true }); + fs.writeFileSync(OUTPUT_PATH, png); + console.log('Done'); + } + + main().catch(err => { console.error(err.message); process.exit(1); }); + SCRIPT + + - name: Commit and push changes + run: | + git config user.name "github-actions[bot]" + git config user.email "41898282+github-actions[bot]@users.noreply.github.com" + git add .github/assets/traction.png + if git diff --staged --quiet; then + echo "No changes to commit" + exit 0 + fi + git checkout -B update-traction-chart + git commit -m "Update traction chart [skip ci]" + git push -f origin update-traction-chart + + - name: Create or update Pull Request + env: + GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} + run: | + PREVIEW_URL="https://raw.githubusercontent.com/${{ github.repository }}/update-traction-chart/.github/assets/traction.png" + BODY="Automated weekly update of README traction chart. + ![Traction Chart](${PREVIEW_URL})" + + existing_pr=$(gh pr list --head update-traction-chart --json number --jq '.[0].number' || echo "") + if [ -n "$existing_pr" ]; then + gh pr edit "$existing_pr" --body "$BODY" + echo "Updated PR #$existing_pr" + else + gh pr create \ + --title "Update README traction chart" \ + --body "$BODY" \ + --head update-traction-chart \ + --base main + fi diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..e5243a0 --- /dev/null +++ b/.gitignore @@ -0,0 +1,91 @@ +# dependencies +node_modules +/.pnp +.pnp.* +.pnpm-store/ + +# misc +.DS_Store +*.pem +/.worktrees/ + +# debug +.pnpm-debug.log* +npm-debug.log* +yarn-debug.log* +yarn-error.log* + +# env files +.env*.local +.env + +# typescript +*.tsbuildinfo +next-env.d.ts + +# vercel +.vercel + +# testing +coverage +.stryker-tmp/ +reports/mutation/ + +# next.js +.next/ +out/ +.workflow-data +.eve + +# nitro +.output/ + +# build +build/ + +# fumadocs +.source + +# production +dist +apps/docs/generated +packages/*/generated + +# compiled by packages/react-devtools build:css, regenerated on install/build +packages/react-devtools/src/styles/panel.generated.ts + +# turbo +.turbo +.api-surface-tmp/ + +# python +__pycache__/ +.venv/ +venv/ +.eggs/ +*.egg-info/ +.pytest_cache/ +.mypy_cache/ +.ruff_cache/ +.tox/ +.nox/ +htmlcov/ +.coverage + +.langgraph_api + +# Cursor +.cursor + +# Husky +.husky/_ + +# Claude +**/.claude/scheduled_tasks.lock +apps/docs/app/api/xulux/README.md +apps/docs/app/api/xulux/CLAUDE.md + +# GitHub Spec Kit - https://github.com/github/spec-kit +.specify/ +specs/* +.test-output/ diff --git a/.husky/pre-commit b/.husky/pre-commit new file mode 100644 index 0000000..9decfaa --- /dev/null +++ b/.husky/pre-commit @@ -0,0 +1 @@ +CI=true pnpm exec lint-staged diff --git a/.oxfmtrc.json b/.oxfmtrc.json new file mode 100644 index 0000000..2eac102 --- /dev/null +++ b/.oxfmtrc.json @@ -0,0 +1,46 @@ +{ + "$schema": "./node_modules/oxfmt/configuration_schema.json", + "printWidth": 80, + "tabWidth": 2, + "useTabs": false, + "semi": true, + "singleQuote": false, + "jsxSingleQuote": false, + "trailingComma": "all", + "bracketSpacing": true, + "bracketSameLine": false, + "arrowParens": "always", + "endOfLine": "lf", + "quoteProps": "as-needed", + "sortTailwindcss": { + "functions": ["clsx", "cva", "tw", "twMerge", "cn", "twJoin", "tv"] + }, + "sortPackageJson": false, + "ignorePatterns": [ + "**/dist/**", + "**/node_modules/**", + "**/.next/**", + "**/.vercel/**", + "**/out/**", + "**/.source/**", + "**/next-env.d.ts", + "**/.tanstack/**", + "**/.output/**", + "**/routeTree.gen.ts", + "**/build/**", + "**/.turbo/**", + "**/coverage/**", + "**/CHANGELOG.md", + "**/pnpm-lock.yaml", + "**/*.html", + "**/*.md", + "**/*.mdx", + "api-surface/**", + "**/*.yaml", + "**/*.yml", + "**/*.toml", + "python/**", + "apps/docs/components/tool-ui/weather-widget/generated/**", + "**/*.generated.ts" + ] +} diff --git a/.oxlintrc.json b/.oxlintrc.json new file mode 100644 index 0000000..c11078d --- /dev/null +++ b/.oxlintrc.json @@ -0,0 +1,96 @@ +{ + "$schema": "./node_modules/oxlint/configuration_schema.json", + "plugins": ["typescript", "react", "import", "oxc"], + "categories": { + "correctness": "error", + "perf": "off", + "suspicious": "off", + "pedantic": "off", + "style": "off", + "restriction": "off", + "nursery": "off" + }, + "rules": { + "no-explicit-any": "off", + "no-array-index-key": "off", + "no-non-null-assertion": "off", + "no-namespace": "off", + "react/forward-ref-uses-ref": "off", + "react/no-array-index-key": "off", + "react/react-in-jsx-scope": "off", + "react/exhaustive-deps": "warn", + "react/rules-of-hooks": "warn", + "no-unused-vars": "off", + "typescript/no-unused-vars": [ + "warn", + { + "argsIgnorePattern": "^_", + "varsIgnorePattern": "^_", + "ignoreRestSiblings": true + } + ], + "no-labels": "error", + "no-empty-pattern": "warn", + "no-prototype-builtins": "off", + "import/no-duplicates": "off", + "typescript/triple-slash-reference": "off", + "no-unsafe-optional-chaining": "warn", + "require-yield": "off", + "no-eval": "off", + "no-unused-expressions": "off", + "typescript/no-this-alias": "off", + "typescript/no-unnecessary-parameter-property-assignment": "off", + "oxc/const-comparisons": "warn" + }, + "ignorePatterns": [ + "**/dist/**", + "**/node_modules/**", + "**/.next/**", + "**/.vercel/**", + "**/out/**", + "**/.source/**", + "**/next-env.d.ts", + "**/.tanstack/**", + "**/.output/**", + "**/routeTree.gen.ts", + "**/*.html", + "**/build/**", + "**/.turbo/**", + "**/coverage/**", + "api-surface/**", + "apps/docs/components/tool-ui/weather-widget/generated/**" + ], + "overrides": [ + { + "files": [ + "packages/ui/src/components/ui/**", + "packages/ui/src/components/assistant-ui/**", + "packages/ui/src/components/icons/**", + "templates/*/components/ui/**", + "templates/*/components/assistant-ui/**", + "templates/*/components/icons/**" + ], + "rules": { + "react/exhaustive-deps": "off" + } + }, + { + "files": [ + "packages/core/src/react/runtimes/RemoteThreadListThreadListRuntimeCore.tsx", + "packages/core/src/react/runtimes/RemoteThreadListHookInstanceManager.tsx", + "packages/react/src/unstable/**", + "packages/react/src/context/react/utils/createStateHookForRuntime.ts" + ], + "rules": { + "react/rules-of-hooks": "off" + } + }, + { + "files": ["packages/tap/src/__tests__/**"], + "rules": { + "react/exhaustive-deps": "off", + "react/rules-of-hooks": "off" + } + } + ] +} diff --git a/.vscode/settings.json b/.vscode/settings.json new file mode 100644 index 0000000..c1d3c80 --- /dev/null +++ b/.vscode/settings.json @@ -0,0 +1,24 @@ +{ + "typescript.tsdk": "node_modules/typescript/lib", + "editor.defaultFormatter": "oxc.oxc-vscode", + "editor.formatOnSave": true, + "editor.formatOnPaste": true, + "editor.codeActionsOnSave": { + "source.fixAll.oxc": "explicit" + }, + "[typescript]": { + "editor.defaultFormatter": "oxc.oxc-vscode" + }, + "[json]": { + "editor.defaultFormatter": "oxc.oxc-vscode" + }, + "[javascript]": { + "editor.defaultFormatter": "oxc.oxc-vscode" + }, + "[jsonc]": { + "editor.defaultFormatter": "oxc.oxc-vscode" + }, + "[typescriptreact]": { + "editor.defaultFormatter": "oxc.oxc-vscode" + } +} diff --git a/AGENTS.md b/AGENTS.md new file mode 100644 index 0000000..5736c0b --- /dev/null +++ b/AGENTS.md @@ -0,0 +1,101 @@ +## Architecture + +``` +@assistant-ui/tap → Reactive primitives that run React hooks as headless resources (resource, useResource, createTapRoot, useTapRoot) +@assistant-ui/store → Bridges tap to React (useAui, useAuiState, AuiProvider) +@assistant-ui/core → Framework-agnostic chat runtime (useExternalStoreRuntime, useLocalRuntime, useRemoteThreadListRuntime, ChatModelAdapter, createRuntimeExtras); ./react subpath holds the React-coupled runtime +@assistant-ui/react → Web distribution: re-exports core (+ ./react) and adds Radix primitives +@assistant-ui/react-native → React Native distribution: re-exports core (+ ./react) and adds RN primitives +@assistant-ui/react-ink → Ink/terminal distribution +@assistant-ui/react-* → Framework adapters that map a provider SDK onto a core runtime (see Adapter orchestration) +@assistant-ui/ui → Private shadcn-style component kit; the canonical source copied into user projects via the registry +@assistant-ui/x-buildutils → Shared build tooling (aui-build); see Build & release +``` + +## Package boundaries & public surface + +`@assistant-ui/core` holds the framework-agnostic runtime; its `./react` sub-path holds the React-coupled runtime that `@assistant-ui/react` and `@assistant-ui/react-native` re-export. Customers never install core directly; they use one of the three distribution packages (react, react-native, react-ink). Platform runtimes stay in the distribution packages; framework-agnostic logic goes in core. + +`@assistant-ui/ui` is a private shadcn-style kit. We use it directly in the monorepo and copy it into user projects through the registry; it is not published as a dependency. + +The public surface of any published package is **append-only**. Re-point a moved export to its new file, but never remove an export that has shipped. The in-repo audit cannot see npm consumers, so even an unused-looking type is a breaking change if removed. Ship a real behavior change as its own deliberate PR. + +An ongoing migration is replacing the legacy runtime (`packages/react/src/legacy-runtime/`) with the tap-only architecture in `core/src/react`. During the migration the `@assistant-ui/react` barrel re-exports both; keep it append-only. + +## Adapter orchestration + +A framework adapter maps a provider onto a core runtime through one `use<Name>Runtime` entry hook, accessor hooks in `hooks.ts`, and pure converters. The rule of thumb: the runtime file orchestrates, pure modules convert, the controller (if any) reduces, hooks read. + +- **Core runtime.** Build on `useExternalStoreRuntime` (messages derived from an external source) or `useLocalRuntime` plus a `ChatModelAdapter` (no provider-side thread state, like `react-data-stream`), and wrap it in `useRemoteThreadListRuntime` for multi-thread support. Reuse core's runtime cores; do not create a `*ThreadRuntimeCore` state holder. +- **State exposure.** Expose runtime state to accessor hooks with `createRuntimeExtras` from `@assistant-ui/core/internal`, not a hand-rolled `Symbol` brand and guard. +- **Standard files.** `use<Name>Runtime.ts` (orchestration only), `<name>Extras.ts` (the `createRuntimeExtras` instance), `hooks.ts` (accessor and action hooks), a pure `convertMessages.ts` (both directions), and `types.ts`; add a `<Name>ThreadController.ts` plus a pure `reduce<Name>ThreadState` reducer when the adapter owns thread state, and a `./server` or `./node` subpath entry when the protocol owns the wire. +- **Server-only code.** Keep server-only or provider-SDK code in the `./server` or `./node` subpath, out of the default and React Native entries. +- **Tests.** Colocate them beside each module, covering the converter both ways, the reducer or controller, and each accessor hook. + +Keep provider-driven choices flexible: core-primitive choice, thin wrapper vs accumulator vs controller, bespoke transports, HITL richness, and thread-list depth. `@assistant-ui/react-langchain` is the reference for the external-store plus converter plus `createRuntimeExtras` shape. + +**Don't introduce.** A `*ThreadRuntimeCore` state holder, a `notifyUpdate` plus version-counter re-render hack (bridging non-React state with `useSyncExternalStore`), `Object.create` method grafting, or monkeypatching the caller's objects. + +**Migration discipline.** During the `createRuntimeExtras` migration, add new behavior as a hook on the canonical surface, not on the deprecated path; reuse the shared core primitive (`getAutoStatus`, `useStreamingTiming`, etc.) instead of a per-adapter copy, and prefer reuse over introducing a new opt-out flag. + +**Cross-runtime parity.** Keep runtime features at parity across adapters: when you add a capability (isDisabled, joinStrategy, onResume, dynamic suggestions, metadata mutation) to one runtime, expose it on every runtime that supports the concept, or write down why one diverges. A parity gap becomes a breaking expectation for users who switch adapters. + +**Defensive converters.** Converters and content-block renderers must not throw on undefined or missing fields, or on unavailable platform APIs. Defend against non-spec provider payloads (missing `summary` or `text`), and keep browser-only APIs (FileReader, etc.) out of code paths that run in Node, react-ink, or React Native. + +## Build & release + +Every publishable package builds with `aui-build` (`@assistant-ui/x-buildutils`). Do not add a per-package build config or use tsup, unbuild, swc, or the tsc CLI. Exports maps are ESM-only and types-first (`"types"` before `"default"`), with `type: module` and `sideEffects: false`. + +`packages/ui/src/components/assistant-ui` is the canonical UI source. Templates and examples alias it through tsconfig (`@/components/*`, `@/hooks/*`, `@/lib/utils`) and carry no byte-equal copies of it — except `minimal`, which ships its own (examples may still hold intentional forks). `pnpm sync-templates` keeps minimal's copies byte-equal with the source; declare intentional divergence in the `OVERRIDES` array in `scripts/sync-templates.sh`. + +Run `pnpm check:resource-memo` when bumping `@babel/core`, `babel-plugin-react-compiler`, or `react-compiler`; a green build does not prove the compiler toolchain is intact. A package the published dist imports at runtime belongs in `dependencies`, not `devDependencies`, so the bundler externalizes it (a devDep gets inlined and drags unresolvable transitive imports into consumer builds). A registry item must be self-contained: enumerate every `@/components/*` import and CSS `@import` as `registryDependencies`, so `shadcn add` never lands a file with an unresolvable import. + +Every PR that changes a published package needs a changeset. Always use **patch**; minor and major require maintainer approval. Private packages (`private: true` in package.json) are exempt. + +```md +--- +"@assistant-ui/react": patch +--- + +feat: description of the change +``` + +## Lint, format, and comments + +Lint with `pnpm lint`, autofix with `pnpm lint:fix`. Backed by `.oxlintrc.json` (oxlint) and `.oxfmtrc.json` (oxfmt). oxfmt owns formatting; do not hand-format. + +Resources use React's hooks, so dependency arrays and hook rules are checked by oxlint's native `react/exhaustive-deps` and `react/rules-of-hooks`. For these to lint a body, the hook must be named so React recognizes it: extract resources as a `use`-prefixed hook (`const useFoo = () => {…}; const Foo = resource(useFoo)`), and pass `useTapRoot`/`createTapRoot` a named function expression (`createTapRoot(function FooRoot() {…})`) rather than an arrow. + +Default to zero code comments. Delete any comment that restates the code, records its history, or references the current PR or issue. Keep a comment only when it documents a *why* no future reader could recover from the code (a hidden invariant, a non-obvious constraint, an upstream workaround), and write it as a neutral declarative sentence. + +## Testing + +Tests are vitest, colocated beside the module under test and importing it by relative path (never by package name). Cover the converter both ways, the reducer or controller, and each accessor hook in its own `.test.tsx`. Mock with `vi.hoisted` and always spread `...await importOriginal()`; do not use `toMatchSnapshot`. + +## Do's and don'ts + +Do: + +- **One concern per PR.** Break a large or multi-purpose change into separate PRs so each is small enough to review, can be approved on its own merits, and reverts cleanly when it regresses. +- **Write a real PR description, not a bot-generated placeholder.** Say what the change is, why it is needed, and how you did it, so a reviewer has the context to judge it without reverse-engineering the diff. The title names the change in one line; trade-offs and divergences belong in the body. A bot-generated badge is not a description. +- **Justify any divergence from how the repo already does it.** If the codebase already solves a problem one way, follow it; someone chose that approach deliberately, so a different one needs a real reason written down, not personal preference. +- **Keep unrelated changes out of the diff.** Drive-by deletions, refactors, formatting, and "while I'm here" additions bury the real change and slow review. Open a separate PR, or an issue when you only want to flag something. +- **Open an issue before a non-trivial feature PR.** A one-paragraph issue lets a maintainer confirm the direction is wanted before you sink time into code; trivial fixes (typo, small docs) need no issue. +- **Attach a minimal reproduction.** Every bug report or fix PR needs a cloneable repro (a repo, a sandbox, or a minimal snippet on the exact version). If a maintainer cannot reproduce the reported behavior, the issue or PR is closed. +- **Confirm the fix resolves the root cause.** Before opening, verify the reported error is gone with your change and reappears without it, and say why the change fixes the bug. A change that masks the symptom (swapping a value, dropping a feature, widening a type) without explaining the mechanism is rejected. +- **Keep docs and api-reference in lockstep with code in the same PR.** Regenerate api-reference and touch every doc page whose claims your change invalidates (hooks that shipped, templates that exist, tables that compare runtimes). CI should fail on docs drift, not silently drop APIs or launder the diff into the next PR. + +Don't: + +- **Reinvent what the framework already provides.** Build on the shared primitives instead of re-implementing runtime state, reactivity, or other plumbing; a parallel mechanism drifts from the rest of the codebase and costs everyone to maintain. +- **Add heavy or non-OSS dependencies casually.** assistant-ui is built on open source, so commercial-licensed libraries are off-limits; heavy dependencies and new build steps affect every contributor and need justification and maintainer sign-off. +- **Don't ship a new example app that duplicates one we already ship.** Scan `examples/` first; if with-cloud, with-langgraph, or with-mcp already covers the integration, your example adds no ground. New examples belong in a separate repo on your own account unless a maintainer asked for it in-repo. +- **Don't fix a bug by introducing a UX regression.** Disabling a feature, dropping an animation, or widening an API to mask a jitter or rendering bug is not an acceptable fix. Diagnose and fix the root cause; a regression is rejected even when the original report is real. +- **Don't add defensive checks the toolchain already enforces, or comment on formatting.** The repo runs `@tsconfig/strictest` with `exactOptionalPropertyTypes`, so nullability and optional-property guards are redundant; do not add "ensure x is defined" guards the compiler already catches. Formatting is automated (oxfmt), so do not raise spacing or formatting nits in review. +- **Don't `--admin` merge a PR until `gh pr checks` shows every row passing or explicitly skipped.** Filter out `pass` and `skipping` and confirm the remainder is empty, because the truncated tail once hid a failing Template Sync. If a repo-specific check (template-sync, api-surface, check:resource-memo, changeset-semver) is failing or pending, resolve it first rather than overriding. + +## GitButler + +If the current branch is `gitbutler/workspace`, the user uses GitButler, not Git, as version control. Do not create branches, stage files, commit, or rewrite history with Git commands unless the user explicitly asks. + +Assume other coding agents are working alongside you. Before editing, check the current worktree state and avoid overwriting changes you did not make. Keep your changes scoped so GitButler can separate concurrent work cleanly. \ No newline at end of file diff --git a/CLAUDE.md b/CLAUDE.md new file mode 100644 index 0000000..43c994c --- /dev/null +++ b/CLAUDE.md @@ -0,0 +1 @@ +@AGENTS.md diff --git a/CODE_OF_CONDUCT.md b/CODE_OF_CONDUCT.md new file mode 100644 index 0000000..baa0af3 --- /dev/null +++ b/CODE_OF_CONDUCT.md @@ -0,0 +1,84 @@ +# Contributor Covenant 3.0 Code of Conduct + +## Our Pledge + +We pledge to make our community welcoming, safe, and equitable for all. + +We are committed to fostering an environment that respects and promotes the dignity, rights, and contributions of all individuals, regardless of characteristics including race, ethnicity, caste, color, age, physical characteristics, neurodiversity, disability, sex or gender, gender identity or expression, sexual orientation, language, philosophy or religion, national or social origin, socio-economic position, level of education, or other status. The same privileges of participation are extended to everyone who participates in good faith and in accordance with this Covenant. + +## Encouraged Behaviors + +While acknowledging differences in social norms, we all strive to meet our community's expectations for positive behavior. We also understand that our words and actions may be interpreted differently than we intend based on culture, background, or native language. + +With these considerations in mind, we agree to behave mindfully toward each other and act in ways that center our shared values, including: + +1. Respecting the **purpose of our community**, our activities, and our ways of gathering. +2. Engaging **kindly and honestly** with others. +3. Respecting **different viewpoints** and experiences. +4. **Taking responsibility** for our actions and contributions. +5. Gracefully giving and accepting **constructive feedback**. +6. Committing to **repairing harm** when it occurs. +7. Behaving in other ways that promote and sustain the **well-being of our community**. + +## Restricted Behaviors + +We agree to restrict the following behaviors in our community. Instances, threats, and promotion of these behaviors are violations of this Code of Conduct. + +1. **Harassment.** Violating explicitly expressed boundaries or engaging in unnecessary personal attention after any clear request to stop. +2. **Character attacks.** Making insulting, demeaning, or pejorative comments directed at a community member or group of people. +3. **Stereotyping or discrimination.** Characterizing anyone's personality or behavior on the basis of immutable identities or traits. +4. **Sexualization.** Behaving in a way that would generally be considered inappropriately intimate in the context or purpose of the community. +5. **Violating confidentiality.** Sharing or acting on someone's personal or private information without their permission. +6. **Endangerment.** Causing, encouraging, or threatening violence or other harm toward any person or group. +7. Behaving in other ways that **threaten the well-being** of our community. + +### Other Restrictions + +1. **Misleading identity.** Impersonating someone else for any reason, or pretending to be someone else to evade enforcement actions. +2. **Failing to credit sources.** Not properly crediting the sources of content you contribute. +3. **Promotional materials.** Sharing marketing or other commercial content in a way that is outside the norms of the community. +4. **Irresponsible communication.** Failing to responsibly present content which includes, links or describes any other restricted behaviors. + +## Reporting an Issue + +Tensions can occur between community members even when they are trying their best to collaborate. Not every conflict represents a code of conduct violation, and this Code of Conduct reinforces encouraged behaviors and norms that can help avoid conflicts and minimize harm. + +When an incident does occur, it is important to report it promptly. To report a possible violation, please open an issue on our [GitHub repository](https://github.com/assistant-ui/assistant-ui/issues) or email [assistant-ui](mailto:contact@assistant-ui.com) directly. + +Community Moderators take reports of violations seriously and will make every effort to respond in a timely manner. They will investigate all reports of code of conduct violations, reviewing messages, logs, and recordings, or interviewing witnesses and other participants. Community Moderators will keep investigation and enforcement actions as transparent as possible while prioritizing safety and confidentiality. In order to honor these values, enforcement actions are carried out in private with the involved parties, but communicating to the whole community may be part of a mutually agreed upon resolution. + +## Addressing and Repairing Harm + +If an investigation by the Community Moderators finds that this Code of Conduct has been violated, the following enforcement ladder may be used to determine how best to repair harm, based on the incident's impact on the individuals involved and the community as a whole. Depending on the severity of a violation, lower rungs on the ladder may be skipped. + +1. **Warning** + - Event: A violation involving a single incident or series of incidents. + - Consequence: A private, written warning from the Community Moderators. + - Repair: Examples of repair include a private written apology, acknowledgement of responsibility, and seeking clarification on expectations. + +2. **Temporarily Limited Activities** + - Event: A repeated incidence of a violation that previously resulted in a warning, or the first incidence of a more serious violation. + - Consequence: A private, written warning with a time-limited cooldown period designed to underscore the seriousness of the situation and give the community members involved time to process the incident. The cooldown period may be limited to particular communication channels or interactions with particular community members. + - Repair: Examples of repair may include making an apology, using the cooldown period to reflect on actions and impact, and being thoughtful about re-entering community spaces after the period is over. + +3. **Temporary Suspension** + - Event: A pattern of repeated violation which the Community Moderators have tried to address with warnings, or a single serious violation. + - Consequence: A private written warning with conditions for return from suspension. In general, temporary suspensions give the person being suspended time to reflect upon their behavior and possible corrective actions. + - Repair: Examples of repair include respecting the spirit of the suspension, meeting the specified conditions for return, and being thoughtful about how to reintegrate with the community when the suspension is lifted. + +4. **Permanent Ban** + - Event: A pattern of repeated code of conduct violations that other steps on the ladder have failed to resolve, or a violation so serious that the Community Moderators determine there is no way to keep the community safe with this person as a member. + - Consequence: Access to all community spaces, tools, and communication channels is removed. In general, permanent bans should be rarely used, should have strong reasoning behind them, and should only be resorted to if working through other remedies has failed to change the behavior. + - Repair: There is no possible repair in cases of this severity. + +This enforcement ladder is intended as a guideline. It does not limit the ability of Community Managers to use their discretion and judgment, in keeping with the best interests of our community. + +## Scope + +This Code of Conduct applies within all community spaces, and also applies when an individual is officially representing the community in public or other spaces. Examples of representing our community include using an official email address, posting via an official social media account, or acting as an appointed representative at an online or offline event. + +## Attribution + +This Code of Conduct is adapted from the [Contributor Covenant](https://www.contributor-covenant.org), version 3.0, permanently available at <https://www.contributor-covenant.org/version/3/0/> + +Contributor Covenant is stewarded by the [Organization for Ethical Source](https://ethicalsource.dev) and licensed under [CC BY-SA 4.0](https://creativecommons.org/licenses/by-sa/4.0/). diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md new file mode 100644 index 0000000..6301698 --- /dev/null +++ b/CONTRIBUTING.md @@ -0,0 +1,82 @@ +## CONTRIBUTING + +A big welcome and thank you for considering contributing to assistant-ui! It’s people like you that make it a reality for users in our community. + +You can contribute by opening an issue, or by making a pull request. For large pull requests, we ask that you open an issue first to discuss the changes before submitting a pull request. + +Project conventions (architecture, package layout, changesets, and how to author a runtime adapter) live in [`AGENTS.md`](./AGENTS.md); please read and follow them. + +### Setting up your environment + +You need to have Node.js installed on your computer. We develop with the latest LTS version of Node.js. + +Install the dependencies: + +```sh +pnpm install +``` + +Make an initial build: + +```sh +pnpm turbo build +``` + +(some packages rely on build outputs from other packages, even if you want to start the project in development mode) + +### Running the project + +To run the docs project in development mode: + +```sh +cd apps/docs +pnpm dev +``` + +To run the examples project in development mode: + +```sh +cd examples/<your-example> +pnpm dev +``` + +### Adding a changeset + +Every pull request that changes packages must include a changeset, otherwise your changes won't be published to npm. + +Note, this does not apply to packages like `@assistant-ui/docs` or `@assistant-ui/shadcn-registry` which are not published to npm, they are deployed on Vercel. + +Create a changeset by running: + +```sh +pnpm changeset +``` + +This will detect which packages changed and prompt you to select type (major, minor, patch) and a description of your changes. + +#### Which type to pick + +**Almost always `patch`** — even for new features and new exports. Here's why: + +Most assistant-ui packages are at `0.x` versions (e.g. `0.12.15`). In semver, the caret range `^` behaves differently for `0.x` than for `1.x+`: + +| Range | Allows | Example | +|-------|--------|---------| +| `^1.3.12` | any minor or patch (`>=1.3.12 <2.0.0`) | `1.4.0` is fine | +| `^0.12.15` | only patches (`>=0.12.15 <0.13.0`) | `0.13.0` is **out of range** | + +This means a **minor bump on a `0.x` package breaks every dependent's caret range**, causing changesets to cascade patch bumps across the entire dependency graph. That creates version churn and noisy changelogs for no real benefit. + +**The rules:** + +- **patch**: Use for all changes — bug fixes, new features, refactors, new exports +- **minor**: Only when a maintainer explicitly requests it (causes cascading patch bumps across all dependent packages) +- **major**: Only for planned stable releases (`1.0`, `2.0`) — never without maintainer approval + +If you forget to add a changeset before merging, create a new PR and run `pnpm changeset` locally to create a changeset. You'll be prompted to manually select the packages that were changed, set update type, and add description. Commit the changeset file, push the changes, and merge the PR. + +You can also add changesets on open PRs directly from GitHub using the changeset bot's link in PR comments. + +### Releasing + +Our CI checks for changesets in `.changeset/` on `main` and will create an "update versions" PR which versions the packages, updates the changelog, and publishes the packages to npm on merge. diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..1be0da0 --- /dev/null +++ b/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2025 AgentbaseAI Inc. + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/README.md b/README.md new file mode 100644 index 0000000..e78be2c --- /dev/null +++ b/README.md @@ -0,0 +1,136 @@ +<a href="https://www.assistant-ui.com"> + <picture> + <source media="(prefers-color-scheme: dark)" srcset="https://raw.githubusercontent.com/assistant-ui/assistant-ui/main/.github/assets/header-dark.svg" /> + <source media="(prefers-color-scheme: light)" srcset="https://raw.githubusercontent.com/assistant-ui/assistant-ui/main/.github/assets/header.svg" /> + <img src="https://raw.githubusercontent.com/assistant-ui/assistant-ui/main/.github/assets/header.svg" alt="assistant-ui Header" width="100%" /> + </picture> +</a> + +<p align="center"> + <a href="https://www.assistant-ui.com">Product</a> · + <a href="https://www.assistant-ui.com/docs">Documentation</a> · + <a href="https://www.assistant-ui.com/examples">Examples</a> · + <a href="https://discord.gg/S9dwgCNEFs">Discord</a> · + <a href="https://cal.com/simon-farshid/assistant-ui">Contact Sales</a> +</p> + +[![npm version](https://img.shields.io/npm/v/@assistant-ui/react)](https://www.npmjs.com/package/@assistant-ui/react) +[![npm downloads](https://img.shields.io/npm/dm/@assistant-ui/react)](https://www.npmjs.com/package/@assistant-ui/react) +[![Ask DeepWiki](https://deepwiki.com/badge.svg)](https://deepwiki.com/assistant-ui/assistant-ui) +[![Weave Badge](https://img.shields.io/endpoint?url=https%3A%2F%2Fapp.workweave.ai%2Fapi%2Frepository%2Fbadge%2Forg_GhSIrtWo37b5B3Mv0At3wQ1Q%2F722184017&cacheSeconds=3600)](https://app.workweave.ai/reports/repository/org_GhSIrtWo37b5B3Mv0At3wQ1Q/722184017) +![GitHub License](https://img.shields.io/github/license/assistant-ui/assistant-ui) +[![GitHub stars](https://img.shields.io/github/stars/assistant-ui/assistant-ui)](https://github.com/assistant-ui/assistant-ui) +![Backed by Y Combinator](https://img.shields.io/badge/Backed_by-Y_Combinator-orange) + +## The UX of ChatGPT in your React app 💬🚀 + +**assistant-ui** is an open-source TypeScript/React library to build production-grade AI chat experiences fast. + +## Installation + +The fastest path is the CLI, which scaffolds a Next.js app or adds the styled components to an existing project: + +```bash +npx assistant-ui@latest create # new project +npx assistant-ui@latest init # add to existing project +``` + +Or install the packages directly: + +```bash +npm install @assistant-ui/react @assistant-ui/react-ai-sdk +``` + +## Usage + +```tsx +"use client"; + +import { AssistantRuntimeProvider } from "@assistant-ui/react"; +import { useChatRuntime } from "@assistant-ui/react-ai-sdk"; +import { Thread } from "@/components/assistant-ui/thread"; + +export function Chat() { + const runtime = useChatRuntime(); + return ( + <AssistantRuntimeProvider runtime={runtime}> + <Thread /> + </AssistantRuntimeProvider> + ); +} +``` + +`useChatRuntime` connects to the Vercel AI SDK out of the box. Swap it for `useLangGraphRuntime`, `useDataStreamRuntime`, or any custom runtime to integrate with your own backend. + +[![assistant-ui starter template](https://raw.githubusercontent.com/assistant-ui/assistant-ui/main/.github/assets/assistant-ui-starter.gif)](https://youtu.be/k6Dc8URmLjk) + +## What you get + +- **Composable primitives**: build any chat UX from `Thread`, `Message`, `Composer`, `ThreadList`, `ActionBar`, and friends. Style every pixel yourself, or start from a polished shadcn/ui theme that the CLI copies into your project. +- **Production UX out of the box**: streaming, auto-scroll, retries, attachments, markdown, code highlighting, voice dictation, keyboard shortcuts, and accessibility. +- **Generative UI**: render tool calls and JSON as React components, collect inline human approvals, and expose safe frontend actions to the model. +- **Strong TypeScript**: typed runtime APIs, tool schemas, message parts, and adapters end to end. + +## Backends + +| Integration | Package | +| -------------------------------------- | ---------------------------------------------------------------- | +| Vercel AI SDK | `@assistant-ui/react-ai-sdk` | +| LangGraph / LangChain | `@assistant-ui/react-langgraph`, `@assistant-ui/react-langchain` | +| AG-UI / A2A protocols | `@assistant-ui/react-ag-ui`, `@assistant-ui/react-a2a` | +| Google ADK / OpenCode | `@assistant-ui/react-google-adk`, `@assistant-ui/react-opencode` | +| Custom data-stream backend | `@assistant-ui/react-data-stream` | +| Managed thread history, telemetry, and file storage | `assistant-cloud` | + +Broad model support out of the box (OpenAI, Anthropic, Google Gemini, Mistral, Perplexity, AWS Bedrock, Azure, Fireworks, Ollama) plus community providers via the AI SDK, and easy extension to any custom HTTP backend. + +## Customization + +Instead of a single monolithic chat component, you compose primitives and bring your own styles. The CLI ships a great starter in your choice of Base UI (the default) or Radix UI flavor; you control everything else. + +![Overview of components](https://raw.githubusercontent.com/assistant-ui/assistant-ui/main/.github/assets/components.png) + +Sample customization to make a Perplexity lookalike: + +![Perplexity clone created with assistant-ui](https://raw.githubusercontent.com/assistant-ui/assistant-ui/main/.github/assets/perplexity.gif) + +## Used in production by + +<a href="https://mastra.ai/?ref=assistant-ui" target="_blank" rel="noopener noreferrer"><img src="https://raw.githubusercontent.com/assistant-ui/assistant-ui/main/.github/assets/logos/Mastra.svg" height="20" alt="Mastra"></a>, <a href="https://langchain.com/?ref=assistant-ui" target="_blank" rel="noopener noreferrer"><img src="https://raw.githubusercontent.com/assistant-ui/assistant-ui/main/.github/assets/logos/LangChain.svg" height="20" alt="LangChain"></a>, <a href="https://athenaintelligence.ai/?ref=assistant-ui" target="_blank" rel="noopener noreferrer"><img src="https://raw.githubusercontent.com/assistant-ui/assistant-ui/main/.github/assets/logos/Athena-Intelligence.svg" height="20" alt="Athena Intelligence"></a>, <a href="https://browser-use.com/?ref=assistant-ui" target="_blank" rel="noopener noreferrer"><img src="https://raw.githubusercontent.com/assistant-ui/assistant-ui/main/.github/assets/logos/Browser-Use.svg" height="20" alt="Browser Use"></a>, <a href="https://stack-ai.com/?ref=assistant-ui" target="_blank" rel="noopener noreferrer"><img src="https://raw.githubusercontent.com/assistant-ui/assistant-ui/main/.github/assets/logos/Stack.svg" height="20" alt="Stack"></a>, <a href="https://inconvo.com/?ref=assistant-ui" target="_blank" rel="noopener noreferrer"><img src="https://raw.githubusercontent.com/assistant-ui/assistant-ui/main/.github/assets/logos/Inconvo.svg" height="20" alt="Inconvo"></a>, <a href="https://iterable.com/?ref=assistant-ui" target="_blank" rel="noopener noreferrer"><img src="https://raw.githubusercontent.com/assistant-ui/assistant-ui/main/.github/assets/logos/Iterable.svg" height="20" alt="Iterable"></a>, <a href="https://helicone.ai/?ref=assistant-ui" target="_blank" rel="noopener noreferrer"><img src="https://raw.githubusercontent.com/assistant-ui/assistant-ui/main/.github/assets/logos/helicone.svg" height="20" alt="Helicone"></a>, <a href="https://getgram.ai/?ref=assistant-ui" target="_blank" rel="noopener noreferrer"><img src="https://raw.githubusercontent.com/assistant-ui/assistant-ui/main/.github/assets/logos/gram.svg" height="20" alt="Gram"></a>, <a href="https://coreviz.io/?ref=assistant-ui" target="_blank" rel="noopener noreferrer"><img src="https://raw.githubusercontent.com/assistant-ui/assistant-ui/main/.github/assets/logos/Coreviz.svg" height="20" alt="Coreviz"></a>, and many more. + +![Chart of assistant-ui's traction](https://raw.githubusercontent.com/assistant-ui/assistant-ui/main/.github/assets/traction.png) + +## Demos + +<table> + <tr> + <td align="center"> + <a href="https://youtu.be/ZW56UHlqTCQ"> + <img src="https://img.youtube.com/vi/ZW56UHlqTCQ/hqdefault.jpg" alt="Short Demo" /> + </a> + </td> + <td align="center"> + <a href="https://youtu.be/9eLKs9AM4tU"> + <img src="https://img.youtube.com/vi/9eLKs9AM4tU/hqdefault.jpg" alt="Long Demo" /> + </a> + </td> + </tr> +</table> + +## Community & Support + +- [Examples](https://www.assistant-ui.com/examples) +- [Documentation](https://www.assistant-ui.com/docs/) +- [Discord](https://discord.com/invite/S9dwgCNEFs) +- [Book a sales call](https://cal.com/simon-farshid/assistant-ui) + +## For other platforms + +- React Native: [`@assistant-ui/react-native`](https://www.npmjs.com/package/@assistant-ui/react-native) +- Terminal (Ink): [`@assistant-ui/react-ink`](https://www.npmjs.com/package/@assistant-ui/react-ink) + +## License + +MIT, with optional Assistant Cloud for managed thread persistence and analytics. + +Backed by Y Combinator. diff --git a/README.wehub.md b/README.wehub.md new file mode 100644 index 0000000..ba02857 --- /dev/null +++ b/README.wehub.md @@ -0,0 +1,7 @@ +# WeHub 来源说明 + +- 原始项目:`assistant-ui/assistant-ui` +- 原始仓库:https://github.com/assistant-ui/assistant-ui +- 导入方式:上游默认分支的最新快照 +- 原作者、版权和许可证信息以原始仓库及本仓库 LICENSE 为准 +- 本文件仅用于记录来源,不代表 WeHub 是原项目作者 diff --git a/SECURITY.md b/SECURITY.md new file mode 100644 index 0000000..e016c27 --- /dev/null +++ b/SECURITY.md @@ -0,0 +1,7 @@ +# Reporting Security Issues + +We take security bugs in assistant-ui seriously. We appreciate your efforts to responsibly disclose your findings, and will make every effort to acknowledge your contributions. + +To report a security issue, please use the GitHub Security Advisory ["Report a Vulnerability"](https://github.com/assistant-ui/assistant-ui/security/advisories/new) tab. + +Our team will send a response indicating the next steps in handling your report. After the initial reply to your report, the security team will keep you informed of the progress towards a fix and full announcement, and may ask for additional information or guidance. diff --git a/api-surface/assistant-cloud.ts b/api-surface/assistant-cloud.ts new file mode 100644 index 0000000..ec97236 --- /dev/null +++ b/api-surface/assistant-cloud.ts @@ -0,0 +1,477 @@ +import "@standard-schema/spec"; + +declare class AssistantCloud { + readonly threads: AssistantCloudThreads; + readonly auth: { + tokens: AssistantCloudAuthTokens; + }; + readonly runs: AssistantCloudRuns; + readonly files: AssistantCloudFiles; + readonly telemetry: AssistantCloudTelemetryConfig; + constructor(config: AssistantCloudConfig); +} + +declare class AssistantCloudAPI { + _auth: AssistantCloudAuthStrategy; + _baseUrl: string; + constructor(config: AssistantCloudConfig); + initializeAuth(): Promise<boolean>; + makeRawRequest(endpoint: string, options?: MakeRequestOptions): Promise<Response>; + makeRequest(endpoint: string, options?: MakeRequestOptions): Promise<any>; +} + +type AssistantCloudAuthStrategy = { + readonly strategy: "anon" | "api-key" | "jwt"; + getAuthHeaders(): Promise<Record<string, string> | false>; + readAuthHeaders(headers: Headers): void; +}; + +declare class AssistantCloudAuthTokens { + private cloud; + constructor(cloud: AssistantCloudAPI); + create(): Promise<AssistantCloudAuthTokensCreateResponse>; +} + +type AssistantCloudAuthTokensCreateResponse = { + token: string; +}; + +type AssistantCloudConfig = ({ + baseUrl: string; + authToken: () => Promise<string | null>; +} | { + baseUrl?: string; + apiKey: string; + userId: string; + workspaceId: string; +} | { + baseUrl: string; + anonymous: true; +}) & { + telemetry?: boolean | AssistantCloudTelemetryConfig; +}; + +declare class AssistantCloudFiles { + private cloud; + constructor(cloud: AssistantCloudAPI); + pdfToImages(body: PdfToImagesRequestBody): Promise<PdfToImagesResponse>; + generatePresignedUploadUrl(body: GeneratePresignedUploadUrlRequestBody): Promise<GeneratePresignedUploadUrlResponse>; +} + +type AssistantCloudMessageCreateResponse = { + message_id: string; +}; + +type AssistantCloudRunReport = { + thread_id: string; + status: "completed" | "error" | "incomplete"; + total_steps?: number; + tool_calls?: ReportToolCall[]; + steps?: { + input_tokens?: number; + output_tokens?: number; + reasoning_tokens?: number; + cached_input_tokens?: number; + tool_calls?: ReportToolCall[]; + start_ms?: number; + end_ms?: number; + }[]; + input_tokens?: number; + output_tokens?: number; + reasoning_tokens?: number; + cached_input_tokens?: number; + model_id?: string; + provider_type?: string; + duration_ms?: number; + output_text?: string; + metadata?: Record<string, unknown>; +}; + +declare class AssistantCloudRuns { + private cloud; + constructor(cloud: AssistantCloudAPI); + __internal_getAssistantOptions(assistantId: string): { + api: string; + headers: () => Promise<{ + Accept: string; + }>; + body: { + assistant_id: string; + response_format: string; + thread_id: string; + }; + }; + stream(body: AssistantCloudRunsStreamBody): Promise<AssistantStream>; + report(body: AssistantCloudRunReport): Promise<{ + run_id: string; + }>; +} + +type AssistantCloudRunsStreamBody = { + thread_id: string; + assistant_id: "system/thread_title"; + messages: readonly unknown[]; +}; + +type AssistantCloudTelemetryConfig = { + enabled?: boolean; + beforeReport?: (report: AssistantCloudRunReport) => AssistantCloudRunReport | null; +}; + +type AssistantCloudThreadMessageCreateBody = { + parent_id: string | null; + format: "aui/v0" | string; + content: ReadonlyJSONObject; +}; + +type AssistantCloudThreadMessageListQuery = { + format?: string; +}; + +type AssistantCloudThreadMessageListResponse = { + messages: CloudMessage[]; +}; + +type AssistantCloudThreadMessageUpdateBody = { + content: ReadonlyJSONObject; +}; + +declare class AssistantCloudThreadMessages { + private cloud; + constructor(cloud: AssistantCloudAPI); + list(threadId: string, query?: AssistantCloudThreadMessageListQuery): Promise<AssistantCloudThreadMessageListResponse>; + create(threadId: string, body: AssistantCloudThreadMessageCreateBody): Promise<AssistantCloudMessageCreateResponse>; + update(threadId: string, messageId: string, body: AssistantCloudThreadMessageUpdateBody): Promise<void>; +} + +declare class AssistantCloudThreads { + private cloud; + readonly messages: AssistantCloudThreadMessages; + constructor(cloud: AssistantCloudAPI); + list(query?: AssistantCloudThreadsListQuery): Promise<AssistantCloudThreadsListResponse>; + get(threadId: string): Promise<CloudThread>; + create(body: AssistantCloudThreadsCreateBody): Promise<AssistantCloudThreadsCreateResponse>; + update(threadId: string, body: AssistantCloudThreadsUpdateBody): Promise<void>; + delete(threadId: string): Promise<void>; +} + +type AssistantCloudThreadsCreateBody = { + title?: string | undefined; + last_message_at: Date; + metadata?: unknown | undefined; + external_id?: string | undefined; +}; + +type AssistantCloudThreadsCreateResponse = { + thread_id: string; +}; + +type AssistantCloudThreadsListQuery = { + is_archived?: boolean; + limit?: number; + after?: string; +}; + +type AssistantCloudThreadsListResponse = { + threads: CloudThread[]; +}; + +type AssistantCloudThreadsUpdateBody = { + title?: string | undefined; + last_message_at?: Date | undefined; + metadata?: unknown | undefined; + is_archived?: boolean | undefined; +}; + +type AssistantStream = ReadableStream<AssistantStreamChunk>; + +declare const AssistantStream: { + toResponse(stream: AssistantStream, transformer: AssistantStreamEncoder): Response; + fromResponse(response: Response, transformer: ReadableWritablePair<AssistantStreamChunk, Uint8Array<ArrayBuffer>>): ReadableStream<AssistantStreamChunk>; + toByteStream(stream: AssistantStream, transformer: ReadableWritablePair<Uint8Array<ArrayBuffer>, AssistantStreamChunk>): ReadableStream<Uint8Array<ArrayBuffer>>; + fromByteStream(readable: ReadableStream<Uint8Array<ArrayBuffer>>, transformer: ReadableWritablePair<AssistantStreamChunk, Uint8Array<ArrayBuffer>>): ReadableStream<AssistantStreamChunk>; +}; + +type AssistantStreamChunk = { + readonly path: readonly number[]; +} & ({ + readonly type: "part-start"; + readonly part: PartInit; +} | { + readonly type: "part-finish"; +} | { + readonly type: "tool-call-args-text-finish"; +} | { + readonly type: "text-delta"; + readonly textDelta: string; +} | { + readonly type: "annotations"; + readonly annotations: ReadonlyJSONValue[]; +} | { + readonly type: "data"; + readonly data: ReadonlyJSONValue[]; +} | { + readonly type: "step-start"; + readonly messageId: string; +} | { + readonly type: "step-finish"; + readonly finishReason: "content-filter" | "error" | "length" | "other" | "stop" | "tool-calls" | "unknown"; + readonly usage: { + readonly inputTokens: number; + readonly outputTokens: number; + }; + readonly isContinued: boolean; +} | { + readonly type: "message-finish"; + readonly finishReason: "content-filter" | "error" | "length" | "other" | "stop" | "tool-calls" | "unknown"; + readonly usage: { + readonly inputTokens: number; + readonly outputTokens: number; + }; +} | { + readonly type: "result"; + readonly artifact?: ReadonlyJSONValue; + readonly result: ReadonlyJSONValue; + readonly isError: boolean; + readonly modelContent?: readonly ToolModelContentPart[]; + readonly messages?: ReadonlyJSONValue; +} | { + readonly type: "error"; + readonly error: string; +} | { + readonly type: "update-state"; + readonly operations: ObjectStreamOperation[]; +}); + +type AssistantStreamEncoder = ReadableWritablePair<Uint8Array<ArrayBuffer>, AssistantStreamChunk> & { + headers?: Headers; +}; + +declare class CloudAPIError extends Error { + readonly status: number; + constructor(message: string, status: number); +} + +type CloudMessage = { + id: string; + parent_id: string | null; + height: number; + created_at: Date; + updated_at: Date; + format: "aui/v0" | string; + content: ReadonlyJSONObject; +}; + +declare class CloudMessagePersistence { + private cloud; + private idMapping; + constructor(cloud: AssistantCloud); + append(threadId: string, messageId: string, parentId: string | null, format: string, content: ReadonlyJSONObject): Promise<void>; + update(threadId: string, messageId: string, _format: string, content: ReadonlyJSONObject): Promise<void>; + isPersisted(messageId: string): boolean; + getRemoteId(messageId: string): Promise<string | undefined>; + load(threadId: string, format?: string): Promise<CloudMessage[]>; + reset(): void; +} + +type CloudThread = { + title: string; + last_message_at: Date; + metadata: unknown; + external_id: string | null; + id: string; + project_id: string; + created_at: Date; + updated_at: Date; + workspace_id: string; + is_archived: boolean; +}; + +type GeneratePresignedUploadUrlRequestBody = { + filename: string; +}; + +type GeneratePresignedUploadUrlResponse = { + success: boolean; + signedUrl: string; + expiresAt: string; + publicUrl: string; +}; + +type MakeRequestOptions = { + method?: "POST" | "PUT" | "DELETE" | undefined; + headers?: Record<string, string> | undefined; + query?: Record<string, string | number | boolean> | undefined; + body?: object | undefined; +}; + +type McpSamplingHandler = (request: McpSamplingRequest) => Promise<McpSamplingResponse>; + +type McpSamplingRequest = { + method: "sampling/createMessage"; + params: { + messages: unknown[]; + modelPreferences?: { + hints?: { + name?: string; + }[]; + }; + maxTokens?: number; + [key: string]: unknown; + }; +}; + +type McpSamplingResponse = { + model?: string; + content: unknown; + usage?: { + inputTokens?: number; + outputTokens?: number; + promptTokens?: number; + completionTokens?: number; + reasoningTokens?: number; + cachedInputTokens?: number; + }; + [key: string]: unknown; +}; + +type MessageFormatAdapter<TMessage, TStorageFormat> = { + format: string; + encode(item: { + parentId: string | null; + message: TMessage; + }): TStorageFormat; + decode(stored: { + id: string; + parent_id: string | null; + format: string; + content: TStorageFormat; + }): { + parentId: string | null; + message: TMessage; + }; + getId(message: TMessage): string; +}; + +type ObjectStreamOperation = { + readonly type: "set"; + readonly path: readonly string[]; + readonly value: ReadonlyJSONValue; +} | { + readonly type: "append-text"; + readonly path: readonly string[]; + readonly value: string; +}; + +type PartInit = { + readonly type: "reasoning" | "text"; + readonly parentId?: string; +} | { + readonly type: "tool-call"; + readonly toolCallId: string; + readonly toolName: string; + readonly parentId?: string; +} | { + readonly type: "source"; + readonly sourceType: "url"; + readonly id: string; + readonly url: string; + readonly title?: string; + readonly parentId?: string; +} | { + readonly type: "file"; + readonly data: string; + readonly mimeType: string; + readonly parentId?: string; +} | { + readonly type: "data"; + readonly name: string; + readonly data: ReadonlyJSONValue; + readonly parentId?: string; +}; + +type PdfToImagesRequestBody = { + file_blob?: string | undefined; + file_url?: string | undefined; +}; + +type PdfToImagesResponse = { + success: boolean; + urls: string[]; + message: string; +}; + +type ReadonlyJSONArray = readonly ReadonlyJSONValue[]; + +type ReadonlyJSONObject = { + readonly [key: string]: ReadonlyJSONValue; +}; + +type ReadonlyJSONValue = null | string | number | boolean | ReadonlyJSONObject | ReadonlyJSONArray; + +type ReportToolCall = { + tool_name: string; + tool_call_id: string; + tool_args?: string; + tool_result?: string; + tool_source?: "backend" | "frontend" | "mcp"; + start_ms?: number; + end_ms?: number; + sampling_calls?: SamplingCallData[]; +}; + +type SamplingCallData = { + model_id?: string; + input_tokens?: number; + output_tokens?: number; + reasoning_tokens?: number; + cached_input_tokens?: number; + duration_ms?: number; +}; + +type ToolModelContentPart = { + readonly type: "text"; + readonly text: string; +} | { + readonly type: "file"; + readonly data: string; + readonly mediaType: string; + readonly filename?: string; +}; + +declare const createFormattedPersistence: <TMessage, TStorageFormat>(persistence: { + append: (threadId: string, messageId: string, parentId: string | null, format: string, content: ReadonlyJSONObject) => Promise<void>; + load: (threadId: string, format?: string) => Promise<any[]>; + isPersisted: (messageId: string) => boolean; + update?: (threadId: string, messageId: string, format: string, content: ReadonlyJSONObject) => Promise<void>; +}, adapter: MessageFormatAdapter<TMessage, TStorageFormat>) => { + append: (threadId: string, item: { + parentId: string | null; + message: TMessage; + }) => Promise<void>; + update: ((threadId: string, item: { + parentId: string | null; + message: TMessage; + }, messageId: string) => Promise<void>) | undefined; + load: (threadId: string) => Promise<{ + messages: { + parentId: string | null; + message: TMessage; + }[]; + }>; + isPersisted: (messageId: string) => boolean; +}; + +declare function createSamplingCollector(): { + collect: (data: SamplingCallData) => number; + getCalls: () => SamplingCallData[]; + reset: () => void; +}; + +declare namespace entry_root_exports { + export { AssistantCloud, AssistantCloudRunReport, AssistantCloudTelemetryConfig, CloudAPIError, CloudMessage, CloudMessagePersistence, McpSamplingHandler, MessageFormatAdapter, SamplingCallData, createFormattedPersistence, createSamplingCollector, wrapSamplingHandler }; +} + +declare function wrapSamplingHandler(handler: McpSamplingHandler, onSamplingCall: (data: SamplingCallData) => void): McpSamplingHandler; + +export { entry_root_exports as entry_root }; diff --git a/api-surface/assistant-stream.ts b/api-surface/assistant-stream.ts new file mode 100644 index 0000000..abc62c4 --- /dev/null +++ b/api-surface/assistant-stream.ts @@ -0,0 +1,11954 @@ +import { StandardSchemaV1 } from "@standard-schema/spec"; + +import Deque from "denque"; + +import { SrvRecord } from "dns"; + +import { EventEmitter } from "events"; + +import { IpcNetConnectOpts, Socket, TcpNetConnectOpts } from "net"; + +import { Readable, ReadableOptions } from "stream"; + +import { ConnectionOptions, TLSSocket } from "tls"; + +declare abstract class AbstractConnector { + firstError?: Error; + protected connecting: boolean; + protected stream: NetStream; + private disconnectTimeout; + constructor(disconnectTimeout: number); + check(info: any): boolean; + disconnect(): void; + abstract connect(_: ErrorEmitter): Promise<NetStream>; +} + +declare type AddSet = CommandNameFlags["ENTER_SUBSCRIBER_MODE"][number]; + +interface AddressFromResponse { + port: string; + ip: string; + flags?: string | undefined; +} + +declare type ArgumentTransformer = (args: any[]) => any[]; + +declare type ArgumentType = string | Buffer | number | (string | Buffer | number | any[])[]; + +type AsNumber<K> = K extends `${infer N extends number}` ? N | K : never; + +type AssistantMessage = { + role: "assistant"; + status: AssistantMessageStatus; + parts: AssistantMessagePart[]; + content: AssistantMessagePart[]; + metadata: { + unstable_state: ReadonlyJSONValue; + unstable_data: ReadonlyJSONValue[]; + unstable_annotations: ReadonlyJSONValue[]; + steps: AssistantMessageStepMetadata[]; + custom: Record<string, unknown>; + timing?: AssistantMessageTiming; + }; +}; + +declare class AssistantMessageAccumulator extends TransformStream<AssistantStreamChunk, AssistantMessage> { + constructor(_param0?: { + initialMessage?: AssistantMessage; + throttle?: boolean; + onError?: (error: string) => void; + }); +} + +type AssistantMessagePart = TextPart | ReasoningPart | ToolCallPart | SourcePart | FilePart | DataPart; + +type AssistantMessageStatus = { + type: "running"; +} | { + type: "requires-action"; + reason: "tool-calls"; +} | { + type: "complete"; + reason: "stop" | "unknown"; +} | { + type: "incomplete"; + reason: "cancelled" | "content-filter" | "error" | "length" | "other" | "tool-calls"; + error?: ReadonlyJSONValue; +}; + +type AssistantMessageStepMetadata = { + state: "started"; + messageId: string; +} | { + state: "finished"; + messageId: string; + finishReason: "content-filter" | "error" | "length" | "other" | "stop" | "tool-calls" | "unknown"; + usage?: AssistantMessageStepUsage; + isContinued: boolean; +}; + +type AssistantMessageStepUsage = { + inputTokens: number; + outputTokens: number; +}; + +declare class AssistantMessageStream { + readonly readable: ReadableStream<AssistantMessage>; + constructor(readable: ReadableStream<AssistantMessage>); + static fromAssistantStream(stream: AssistantStream): AssistantMessageStream; + unstable_result(): Promise<AssistantMessage>; + [Symbol.asyncIterator](): { + next(): Promise<IteratorResult<AssistantMessage, undefined>>; + }; + tee(): [ + AssistantMessageStream, + AssistantMessageStream + ]; +} + +type AssistantMessageTiming = { + streamStartTime: number; + firstTokenTime?: number; + totalStreamTime?: number; + tokenCount?: number; + tokensPerSecond?: number; + totalChunks: number; + toolCallCount: number; +}; + +type AssistantMetaStreamChunk = (AssistantStreamChunk & { + type: "part-finish" | "text-delta"; + meta: PartInit; +}) | (AssistantStreamChunk & { + type: "result" | "tool-call-args-text-finish"; + meta: PartInit & { + type: "tool-call"; + }; +}) | (AssistantStreamChunk & { + type: Exclude<AssistantStreamChunk["type"], "part-finish" | "result" | "text-delta" | "tool-call-args-text-finish">; +}); + +declare class AssistantMetaTransformStream extends TransformStream<AssistantStreamChunk, AssistantMetaStreamChunk> { + constructor(); +} + +type AssistantStream = ReadableStream<AssistantStreamChunk>; + +declare const AssistantStream: { + toResponse(stream: AssistantStream, transformer: AssistantStreamEncoder): Response; + fromResponse(response: Response, transformer: ReadableWritablePair<AssistantStreamChunk, Uint8Array<ArrayBuffer>>): ReadableStream<AssistantStreamChunk>; + toByteStream(stream: AssistantStream, transformer: ReadableWritablePair<Uint8Array<ArrayBuffer>, AssistantStreamChunk>): ReadableStream<Uint8Array<ArrayBuffer>>; + fromByteStream(readable: ReadableStream<Uint8Array<ArrayBuffer>>, transformer: ReadableWritablePair<AssistantStreamChunk, Uint8Array<ArrayBuffer>>): ReadableStream<AssistantStreamChunk>; +}; + +type AssistantStreamChunk = { + readonly path: readonly number[]; +} & ({ + readonly type: "part-start"; + readonly part: PartInit; +} | { + readonly type: "part-finish"; +} | { + readonly type: "tool-call-args-text-finish"; +} | { + readonly type: "text-delta"; + readonly textDelta: string; +} | { + readonly type: "annotations"; + readonly annotations: ReadonlyJSONValue[]; +} | { + readonly type: "data"; + readonly data: ReadonlyJSONValue[]; +} | { + readonly type: "step-start"; + readonly messageId: string; +} | { + readonly type: "step-finish"; + readonly finishReason: "content-filter" | "error" | "length" | "other" | "stop" | "tool-calls" | "unknown"; + readonly usage: { + readonly inputTokens: number; + readonly outputTokens: number; + }; + readonly isContinued: boolean; +} | { + readonly type: "message-finish"; + readonly finishReason: "content-filter" | "error" | "length" | "other" | "stop" | "tool-calls" | "unknown"; + readonly usage: { + readonly inputTokens: number; + readonly outputTokens: number; + }; +} | { + readonly type: "result"; + readonly artifact?: ReadonlyJSONValue; + readonly result: ReadonlyJSONValue; + readonly isError: boolean; + readonly modelContent?: readonly ToolModelContentPart[]; + readonly messages?: ReadonlyJSONValue; +} | { + readonly type: "error"; + readonly error: string; +} | { + readonly type: "update-state"; + readonly operations: ObjectStreamOperation[]; +}); + +type AssistantStreamController = { + appendText(textDelta: string): void; + appendReasoning(reasoningDelta: string): void; + appendSource(options: SourcePart): void; + appendFile(options: FilePart): void; + appendData(options: DataPart): void; + addTextPart(): TextStreamController; + addToolCallPart(options: string): ToolCallStreamController; + addToolCallPart(options: ToolCallPartInit): ToolCallStreamController; + enqueue(chunk: AssistantStreamChunk): void; + merge(stream: AssistantStream): void; + close(): void; + withParentId(parentId: string): AssistantStreamController; +}; + +type AssistantStreamEncoder = ReadableWritablePair<Uint8Array<ArrayBuffer>, AssistantStreamChunk> & { + headers?: Headers; +}; + +declare class AssistantTransformStream<I> extends TransformStream<I, AssistantStreamChunk> { + constructor(transformer: AssistantTransformer<I>, writableStrategy?: QueuingStrategy<I>, readableStrategy?: QueuingStrategy<AssistantStreamChunk>); +} + +type AssistantTransformer<I> = { + flush?: AssistantTransformerFlushCallback; + start?: AssistantTransformerStartCallback; + transform?: AssistantTransformerTransformCallback<I>; +}; + +type AssistantTransformerFlushCallback = (controller: AssistantStreamController) => void | PromiseLike<void>; + +type AssistantTransformerStartCallback = (controller: AssistantStreamController) => void | PromiseLike<void>; + +type AssistantTransformerTransformCallback<I> = (chunk: I, controller: AssistantStreamController) => void | PromiseLike<void>; + +declare class AssistantTransportDecoder extends PipeableTransformStream<Uint8Array<ArrayBuffer>, AssistantStreamChunk> { + constructor(); +} + +declare class AssistantTransportEncoder extends PipeableTransformStream<AssistantStreamChunk, Uint8Array<ArrayBuffer>> implements AssistantStreamEncoder { + headers: Headers; + constructor(); +} + +type AsyncIterableStream<T> = AsyncIterable<T> & ReadableStream<T>; + +type AttachmentLike = { + content: readonly MessagePartLike[]; +}; + +type BackendTool<TArgs extends Record<string, unknown> = Record<string, unknown>, TResult = unknown> = ToolBase<TArgs, TResult> & { + type: "backend"; + description?: undefined; + parameters?: undefined; + disabled?: undefined; + execute?: undefined; + toModelOutput?: undefined; + experimental_onSchemaValidationError?: undefined; + providerOptions?: undefined; +}; + +type BackendToolDeclaration<TArgs extends Record<string, unknown> = Record<string, unknown>, TResult = unknown> = ToolBase<TArgs, TResult> & { + type: "backend"; + description?: string | undefined; + parameters?: StandardSchemaV1<TArgs> | JSONSchema7 | undefined; + disabled?: boolean; + execute?: ToolExecuteFunction<TArgs, TResult>; + toModelOutput?: ToolModelOutputFunction<TArgs, TResult>; + experimental_onSchemaValidationError?: OnSchemaValidationErrorFunction<TResult>; + providerOptions?: ProviderOptions; +}; + +interface BatchOperationContext { + batchMode: "MULTI"; + batchSize: number; + database: number; + serverAddress: string; + serverPort: number | undefined; +} + +declare type Callback<T = any> = (err?: Error | null, result?: T) => void; + +interface ChainableCommander extends RedisCommander<{ + type: "pipeline"; +}> { + length: number; +} + +declare type ClientContext = { + type: keyof ResultTypes<unknown, unknown>; +}; + +declare class Cluster extends Commander { + options: ClusterOptions; + slots: NodeKey[][]; + status: ClusterStatus; + _groupsIds: { + [key: string]: number; + }; + _groupsBySlot: number[]; + isCluster: boolean; + private startupNodes; + private connectionPool; + private manuallyClosing; + private retryAttempts; + private delayQueue; + private offlineQueue; + private subscriber; + private shardedSubscribers; + private slotsTimer; + private reconnectTimeout; + private isRefreshing; + private _refreshSlotsCacheCallbacks; + private _autoPipelines; + private _runningAutoPipelines; + private _readyDelayedCallbacks; + private subscriberGroupEmitter; + private connectionEpoch; + constructor(startupNodes: ClusterNode[], options?: ClusterOptions); + connect(): Promise<void>; + disconnect(reconnect?: boolean): void; + quit(callback?: Callback<"OK">): Promise<"OK">; + duplicate(overrideStartupNodes?: any[], overrideOptions?: {}): Cluster; + nodes(role?: NodeRole): Redis[]; + delayUntilReady(callback: Callback): void; + get autoPipelineQueueSize(): number; + refreshSlotsCache(callback?: Callback<void>): void; + sendCommand(command: Command, stream?: WriteableStream, node?: any): unknown; + sscanStream(key: string, options?: ScanStreamOptions): ScanStream; + sscanBufferStream(key: string, options?: ScanStreamOptions): ScanStream; + hscanStream(key: string, options?: ScanStreamOptions): ScanStream; + hscanBufferStream(key: string, options?: ScanStreamOptions): ScanStream; + zscanStream(key: string, options?: ScanStreamOptions): ScanStream; + zscanBufferStream(key: string, options?: ScanStreamOptions): ScanStream; + handleError(error: Error, ttl: { + value?: any; + }, handlers: any): void; + private resetOfflineQueue; + private clearNodesRefreshInterval; + private resetNodesRefreshInterval; + private setStatus; + private handleCloseEvent; + private flushQueue; + private executeOfflineCommands; + private natMapper; + private getInfoFromNode; + private invokeReadyDelayedCallbacks; + private readyCheck; + private resolveSrv; + private dnsLookup; + private resolveStartupNodeHostnames; + private createScanStream; + private createShardedSubscriberGroup; +} + +interface Cluster extends EventEmitter { +} + +interface Cluster extends Transaction { +} + +declare type ClusterNode = string | number | { + host?: string | undefined; + port?: number | undefined; +}; + +declare type ClusterNodeRetryStrategy = RetryStrategy; + +interface ClusterOptions extends CommanderOptions { + clusterRetryStrategy?: ((times: number, reason?: Error) => number | void | null) | null | undefined; + enableOfflineQueue?: boolean | undefined; + enableReadyCheck?: boolean | undefined; + scaleReads?: NodeRole | Function | undefined; + maxRedirections?: number | undefined; + retryDelayOnFailover?: number | undefined; + retryDelayOnClusterDown?: number | undefined; + retryDelayOnTryAgain?: number | undefined; + retryDelayOnMoved?: number | undefined; + slotsRefreshTimeout?: number | undefined; + slotsRefreshInterval?: number | undefined; + shardedSubscribers?: boolean | undefined; + clusterNodeRetryStrategy?: ClusterNodeRetryStrategy; + redisOptions?: Omit<RedisOptions, "enableOfflineQueue" | "host" | "path" | "port" | "readOnly" | "retryStrategy" | "sentinels"> | undefined; + lazyConnect?: boolean | undefined; + useSRVRecords?: boolean | undefined; + resolveSrv?: DNSResolveSrvFunction | undefined; + dnsLookup?: DNSLookupFunction | undefined; + natMap?: NatMap | undefined; + enableAutoPipelining?: boolean | undefined; + autoPipeliningIgnoredCommands?: string[] | undefined; + scripts?: Record<string, { + lua: string; + numberOfKeys?: number; + readOnly?: boolean; + }> | undefined; +} + +declare type ClusterStatus = "close" | "connect" | "connecting" | "disconnecting" | "end" | "ready" | "reconnecting" | "wait"; + +declare class Command implements Respondable { + name: string; + static FLAGS: { + [key in keyof CommandNameFlags]: CommandNameFlags[key]; + }; + private static flagMap?; + private static _transformer; + static checkFlag<T extends keyof CommandNameFlags>(flagName: T, commandName: string): commandName is CommandNameFlags[T][number]; + static setArgumentTransformer(name: string, func: ArgumentTransformer): void; + static setReplyTransformer(name: string, func: ReplyTransformer): void; + private static getFlagMap; + ignore?: boolean; + isReadOnly?: boolean; + args: CommandParameter[]; + inTransaction: boolean; + pipelineIndex?: number; + isTraced: boolean; + isResolved: boolean; + reject: (err: Error) => void; + resolve: (result: any) => void; + promise: Promise<any>; + private replyEncoding; + private errorStack; + private bufferMode; + private callback; + private transformed; + private _commandTimeoutTimer?; + private _blockingTimeoutTimer?; + private _blockingDeadline?; + private slot?; + private keys?; + constructor(name: string, args?: Array<ArgumentType>, options?: CommandOptions, callback?: Callback); + getSlot(): number; + getKeys(): Array<string | Buffer>; + toWritable(_socket: object): string | Buffer; + stringifyArguments(): void; + transformReply(result: Buffer | Buffer[]): string | string[] | Buffer | Buffer[]; + setTimeout(ms: number): void; + setBlockingTimeout(ms: number): void; + extractBlockingTimeout(): number | null | undefined; + private _clearTimers; + private initPromise; + private _iterateKeys; + private _convertValue; +} + +interface CommandItem { + command: Respondable; + stream: WriteableStream; + select: number; +} + +interface CommandNameFlags { + VALID_IN_SUBSCRIBER_MODE: [ + "subscribe", + "psubscribe", + "unsubscribe", + "punsubscribe", + "ssubscribe", + "sunsubscribe", + "ping", + "quit" + ]; + VALID_IN_MONITOR_MODE: [ + "monitor", + "auth" + ]; + ENTER_SUBSCRIBER_MODE: [ + "subscribe", + "psubscribe", + "ssubscribe" + ]; + EXIT_SUBSCRIBER_MODE: [ + "unsubscribe", + "punsubscribe", + "sunsubscribe" + ]; + WILL_DISCONNECT: [ + "quit" + ]; + HANDSHAKE_COMMANDS: [ + "auth", + "select", + "client", + "readonly", + "info" + ]; + IGNORE_RECONNECT_ON_ERROR: [ + "client" + ]; + BLOCKING_COMMANDS: [ + "blpop", + "brpop", + "brpoplpush", + "blmove", + "bzpopmin", + "bzpopmax", + "bzmpop", + "blmpop", + "xread", + "xreadgroup" + ]; + LAST_ARG_TIMEOUT_COMMANDS: [ + "blpop", + "brpop", + "brpoplpush", + "blmove", + "bzpopmin", + "bzpopmax" + ]; + FIRST_ARG_TIMEOUT_COMMANDS: [ + "bzmpop", + "blmpop" + ]; + BLOCK_OPTION_COMMANDS: [ + "xread", + "xreadgroup" + ]; +} + +interface CommandOptions { + replyEncoding?: BufferEncoding | null; + errorStack?: Error; + keyPrefix?: string; + readOnly?: boolean; +} + +declare type CommandParameter = string | Buffer | number | any[]; + +declare class Commander<Context extends ClientContext = { + type: "default"; +}> { + options: CommanderOptions; + scriptsSet: {}; + addedBuiltinSet: Set<string>; + getBuiltinCommands(): string[]; + createBuiltinCommand(commandName: string): { + string: any; + buffer: any; + }; + addBuiltinCommand(commandName: string): void; + defineCommand(name: string, definition: { + lua: string; + numberOfKeys?: number; + readOnly?: boolean; + }): void; + sendCommand(command: Command, stream?: WriteableStream, node?: unknown): unknown; +} + +interface Commander<Context> extends RedisCommander<Context> { +} + +interface CommanderOptions { + keyPrefix?: string | undefined; + showFriendlyErrorStack?: boolean | undefined; +} + +interface CommonRedisOptions extends CommanderOptions { + Connector?: ConnectorConstructor | undefined; + retryStrategy?: RetryStrategy; + commandTimeout?: number | undefined; + blockingTimeout?: number | undefined; + blockingTimeoutGrace?: number | undefined; + socketTimeout?: number | undefined; + keepAlive?: number | undefined; + noDelay?: boolean | undefined; + connectionName?: string | undefined; + disableClientInfo?: boolean | undefined; + clientInfoTag?: string | undefined; + username?: string | undefined; + password?: string | undefined; + db?: number | undefined; + autoResubscribe?: boolean | undefined; + autoResendUnfulfilledCommands?: boolean | undefined; + reconnectOnError?: ReconnectOnError | null | undefined; + readOnly?: boolean | undefined; + stringNumbers?: boolean | undefined; + connectTimeout?: number | undefined; + monitor?: boolean | undefined; + maxRetriesPerRequest?: number | null | undefined; + maxLoadingRetryTime?: number | undefined; + enableAutoPipelining?: boolean | undefined; + autoPipeliningIgnoredCommands?: string[] | undefined; + offlineQueue?: boolean | undefined; + commandQueue?: boolean | undefined; + enableOfflineQueue?: boolean | undefined; + enableReadyCheck?: boolean | undefined; + lazyConnect?: boolean | undefined; + scripts?: Record<string, { + lua: string; + numberOfKeys?: number | undefined; + readOnly?: boolean | undefined; + }> | undefined; +} + +interface Condition { + select: number; + auth?: string | [ + string, + string + ]; + subscriber: false | SubscriptionSet; +} + +interface ConnectorConstructor { + new (options: unknown): AbstractConnector; +} + +type CreateObjectStreamOptions = { + execute: (controller: ObjectStreamController) => void | PromiseLike<void>; + defaultValue?: ReadonlyJSONValue; +}; + +type CreateResumableAssistantStreamResponseOptions = { + readonly context: ResumableStreamContext; + readonly streamId: string; + readonly callback: (controller: AssistantStreamController) => PromiseLike<void> | void; + readonly encoder?: () => AssistantStreamEncoder; + readonly headers?: HeadersInit; +}; + +type CreateResumeAssistantStreamResponseOptions = { + readonly context: ResumableStreamContext; + readonly streamId: string; + readonly encoder?: () => AssistantStreamEncoder; + readonly headers?: HeadersInit; + readonly missingResponse?: () => Response; +}; + +declare type DNSLookupFunction = (hostname: string, callback: (err: NodeJS.ErrnoException | null | undefined, address: string, family?: number) => void) => void; + +declare type DNSResolveSrvFunction = (hostname: string, callback: (err: NodeJS.ErrnoException | null | undefined, records?: SrvRecord[]) => void) => void; + +interface DataHandledable extends EventEmitter { + stream: NetStream; + status: string; + condition: Condition | null; + commandQueue: Deque<CommandItem>; + disconnect(reconnect: boolean): void; + recoverFromFatalError(commandError: Error, err: Error, options: FlushQueueOptions): void; + handleReconnection(err: Error, item: CommandItem): void; +} + +type DataPart = { + type: "data"; + name: string; + data: ReadonlyJSONValue; + parentId?: string; +}; + +declare class DataStreamDecoder extends PipeableTransformStream<Uint8Array<ArrayBuffer>, AssistantStreamChunk> { + constructor(); +} + +declare class DataStreamEncoder extends PipeableTransformStream<AssistantStreamChunk, Uint8Array<ArrayBuffer>> implements AssistantStreamEncoder { + headers: Headers; + constructor(); +} + +type DeepPartial<T> = T extends readonly any[] ? readonly DeepPartial<T[number]>[] : T extends { + [key: string]: any; +} ? { + readonly [K in keyof T]?: DeepPartial<T[K]>; +} : T; + +declare type DelSet = CommandNameFlags["EXIT_SUBSCRIBER_MODE"][number]; + +declare type ErrorEmitter = (type: string, err: Error) => void; + +type FieldState = "complete" | "partial"; + +type FilePart = { + type: "file"; + data: string; + mimeType: string; + parentId?: string; +}; + +type FinishReason = "content-filter" | "error" | "length" | "other" | "stop" | "tool-calls" | "unknown"; + +declare type FlushQueueOptions = { + offlineQueue?: boolean; + commandQueue?: boolean; +}; + +type FrontendTool<TArgs extends Record<string, unknown> = Record<string, unknown>, TResult = unknown> = ToolBase<TArgs, TResult> & { + type: "frontend"; + description?: string | undefined; + parameters: StandardSchemaV1<TArgs> | JSONSchema7; + disabled?: boolean; + execute?: ToolExecuteFunction<TArgs, TResult>; + toModelOutput?: ToolModelOutputFunction<TArgs, TResult>; + experimental_onSchemaValidationError?: OnSchemaValidationErrorFunction<TResult>; + providerOptions?: ProviderOptions; +}; + +type GenericAssistantMessage = { + role: "assistant"; + content: (GenericTextPart | GenericToolCallPart)[]; +}; + +type GenericFilePart = { + type: "file"; + data: string | URL; + mediaType: string; +}; + +type GenericMessage = GenericSystemMessage | GenericUserMessage | GenericAssistantMessage | GenericToolMessage; + +type GenericSystemMessage = { + role: "system"; + content: string; +}; + +type GenericTextPart = { + type: "text"; + text: string; +}; + +type GenericToolCallPart = { + type: "tool-call"; + toolCallId: string; + toolName: string; + args: Record<string, unknown>; +}; + +type GenericToolMessage = { + role: "tool"; + content: GenericToolResultPart[]; +}; + +type GenericToolResultPart = { + type: "tool-result"; + toolCallId: string; + toolName: string; + result: unknown; + isError?: boolean; +}; + +type GenericUserMessage = { + role: "user"; + content: (GenericTextPart | GenericFilePart)[]; +}; + +type HumanTool<TArgs extends Record<string, unknown> = Record<string, unknown>, TResult = unknown> = ToolBase<TArgs, TResult> & { + type: "human"; + description?: string | undefined; + parameters: StandardSchemaV1<TArgs> | JSONSchema7; + disabled?: boolean; + display?: "standalone"; + execute?: undefined; + toModelOutput?: undefined; + experimental_onSchemaValidationError?: undefined; + providerOptions?: ProviderOptions; +}; + +type InMemoryResumableStreamStoreOptions = { + readonly defaultTtlMs?: number; + readonly now?: () => number; + readonly maxChunkBytes?: number; + readonly maxEntriesPerStream?: number; + readonly maxStreams?: number; + readonly gcIntervalMs?: number; +}; + +type IoRedisLike = Redis | Cluster; + +declare type IpcOptions = Pick<IpcNetConnectOpts, "path">; + +interface JSONSchema7 { + $id?: string | undefined; + $ref?: string | undefined; + $schema?: JSONSchema7Version | undefined; + $comment?: string | undefined; + $defs?: { + [key: string]: JSONSchema7Definition; + } | undefined; + type?: JSONSchema7TypeName | JSONSchema7TypeName[] | undefined; + enum?: JSONSchema7Type[] | undefined; + const?: JSONSchema7Type | undefined; + multipleOf?: number | undefined; + maximum?: number | undefined; + exclusiveMaximum?: number | undefined; + minimum?: number | undefined; + exclusiveMinimum?: number | undefined; + maxLength?: number | undefined; + minLength?: number | undefined; + pattern?: string | undefined; + items?: JSONSchema7Definition | JSONSchema7Definition[] | undefined; + additionalItems?: JSONSchema7Definition | undefined; + maxItems?: number | undefined; + minItems?: number | undefined; + uniqueItems?: boolean | undefined; + contains?: JSONSchema7Definition | undefined; + maxProperties?: number | undefined; + minProperties?: number | undefined; + required?: string[] | undefined; + properties?: { + [key: string]: JSONSchema7Definition; + } | undefined; + patternProperties?: { + [key: string]: JSONSchema7Definition; + } | undefined; + additionalProperties?: JSONSchema7Definition | undefined; + dependencies?: { + [key: string]: JSONSchema7Definition | string[]; + } | undefined; + propertyNames?: JSONSchema7Definition | undefined; + if?: JSONSchema7Definition | undefined; + then?: JSONSchema7Definition | undefined; + else?: JSONSchema7Definition | undefined; + allOf?: JSONSchema7Definition[] | undefined; + anyOf?: JSONSchema7Definition[] | undefined; + oneOf?: JSONSchema7Definition[] | undefined; + not?: JSONSchema7Definition | undefined; + format?: string | undefined; + contentMediaType?: string | undefined; + contentEncoding?: string | undefined; + definitions?: { + [key: string]: JSONSchema7Definition; + } | undefined; + title?: string | undefined; + description?: string | undefined; + default?: JSONSchema7Type | undefined; + readOnly?: boolean | undefined; + writeOnly?: boolean | undefined; + examples?: JSONSchema7Type | undefined; +} + +interface JSONSchema7Array extends Array<JSONSchema7Type> { +} + +type JSONSchema7Definition = JSONSchema7 | boolean; + +interface JSONSchema7Object { + [key: string]: JSONSchema7Type; +} + +type JSONSchema7Type = string | number | boolean | JSONSchema7Object | JSONSchema7Array | null; + +type JSONSchema7TypeName = "array" | "boolean" | "integer" | "null" | "number" | "object" | "string"; + +type JSONSchema7Version = string; + +type McpServerConfig = { + type: "http" | "sse"; + url: string; + headers?: Record<string, string>; + redirect?: "error" | "follow"; + connectionTimeout?: number | undefined; +} | { + type: "stdio"; + command: string; + args?: readonly string[]; + env?: Record<string, string>; + cwd?: string; + connectionTimeout?: number | undefined; +}; + +type McpTool = ToolBase<Record<string, unknown>, unknown> & { + type: "mcp"; + server: McpServerConfig; + description?: undefined; + parameters?: undefined; + disabled?: boolean; + execute?: undefined; + toModelOutput?: undefined; + experimental_onSchemaValidationError?: undefined; + providerOptions?: undefined; +}; + +type MessagePartLike = { + type: string; + text?: string; + image?: string; + data?: string; + mimeType?: string; + toolCallId?: string; + toolName?: string; + args?: Record<string, unknown>; + result?: unknown; + isError?: boolean; +}; + +declare type NatMap = { + [key: string]: { + host: string; + port: number; + }; +} | NatMapFunction; + +declare type NatMapFunction = (key: string) => { + host: string; + port: number; +} | null; + +declare type NetStream = Socket | TLSSocket; + +declare type NodeKey = string; + +type NodeRedisFields = Record<string, string | Buffer>; + +interface NodeRedisLike { + set(key: string, value: string, options: { + NX: true; + EX: number; + }): Promise<string | null>; + set(key: string, value: string, options: { + EX: number; + }): Promise<string | null>; + get(key: string): Promise<string | null>; + expire(key: string, seconds: number): Promise<unknown>; + exists(key: string): Promise<number>; + del(keys: string | string[]): Promise<unknown>; + xAdd(key: string, id: string, fields: NodeRedisFields): Promise<string>; + sendCommand<T = unknown>(args: ReadonlyArray<string | Buffer>, options?: { + typeMapping?: Record<number, unknown>; + }): Promise<T>; + multi(): NodeRedisMultiCommand; +} + +interface NodeRedisMultiCommand { + xAdd(key: string, id: string, fields: NodeRedisFields): NodeRedisMultiCommand; + expire(key: string, seconds: number): NodeRedisMultiCommand; + set(key: string, value: string, options: { + EX: number; + }): NodeRedisMultiCommand; + execAsPipeline(): Promise<unknown>; + exec(): Promise<unknown>; +} + +declare type NodeRole = "all" | "master" | "slave"; + +type ObjectKey<T> = keyof T & (string | number); + +type ObjectStreamChunk = { + readonly snapshot: ReadonlyJSONValue; + readonly operations: readonly ObjectStreamOperation[]; +}; + +type ObjectStreamController = { + readonly abortSignal: AbortSignal; + enqueue(operations: readonly ObjectStreamOperation[]): void; +}; + +type ObjectStreamOperation = { + readonly type: "set"; + readonly path: readonly string[]; + readonly value: ReadonlyJSONValue; +} | { + readonly type: "append-text"; + readonly path: readonly string[]; + readonly value: string; +}; + +declare class ObjectStreamResponse extends Response { + constructor(body: ReadableStream<ObjectStreamChunk>); +} + +type OnSchemaValidationErrorFunction<TResult> = ToolExecuteFunction<unknown, TResult>; + +interface Options extends ReadableOptions { + key?: string; + match?: string; + type?: string; + command: string; + redis: any; + count?: string | number; + noValues?: boolean; +} + +declare const PARTIAL_JSON_OBJECT_META_SYMBOL: unique symbol; + +type PartInit = { + readonly type: "reasoning" | "text"; + readonly parentId?: string; +} | { + readonly type: "tool-call"; + readonly toolCallId: string; + readonly toolName: string; + readonly parentId?: string; +} | { + readonly type: "source"; + readonly sourceType: "url"; + readonly id: string; + readonly url: string; + readonly title?: string; + readonly parentId?: string; +} | { + readonly type: "file"; + readonly data: string; + readonly mimeType: string; + readonly parentId?: string; +} | { + readonly type: "data"; + readonly name: string; + readonly data: ReadonlyJSONValue; + readonly parentId?: string; +}; + +type PartialJsonObjectMeta = { + state: "complete" | "partial"; + partialPath: string[]; +}; + +declare class PipeableTransformStream<I, O> extends TransformStream<I, O> { + constructor(transform: (readable: ReadableStream<I>) => ReadableStream<O>); +} + +type PipelineCommand = { + readonly type: "xAdd"; + readonly key: string; + readonly fields: Record<string, string | Uint8Array>; +} | { + readonly type: "expire"; + readonly key: string; + readonly ttlSec: number; +} | { + readonly type: "set"; + readonly key: string; + readonly value: string; + readonly ttlSec: number; +}; + +interface PipelineWriteableStream { + isPipeline: true; + write(data: string | Buffer): unknown; + destination: { + redis: { + stream: NetStream; + }; + }; +} + +declare class PlainTextDecoder extends PipeableTransformStream<Uint8Array<ArrayBuffer>, AssistantStreamChunk> { + constructor(); +} + +declare class PlainTextEncoder extends PipeableTransformStream<AssistantStreamChunk, Uint8Array<ArrayBuffer>> implements AssistantStreamEncoder { + headers: Headers; + constructor(); +} + +declare type PreferredSlaves = ((slaves: AddressFromResponse[]) => AddressFromResponse | null) | Array<{ + port: string; + ip: string; + prio?: number | undefined; +}> | { + port: string; + ip: string; + prio?: number | undefined; +}; + +type ProviderOptions = Record<string, Record<string, unknown>>; + +type ProviderTool<TArgs extends Record<string, unknown> = Record<string, unknown>, TResult = unknown> = ToolBase<TArgs, TResult> & { + type: "provider"; + providerId: `${string}.${string}`; + parameters?: StandardSchemaV1<TArgs> | JSONSchema7 | undefined; + args: Record<string, unknown>; + supportsDeferredResults?: boolean; + description?: undefined; + disabled?: boolean; + execute?: undefined; + toModelOutput?: undefined; + experimental_onSchemaValidationError?: undefined; + providerOptions?: ProviderOptions; +}; + +declare const RESUMABLE_STREAM_ID_HEADER = "x-resumable-stream-id"; + +type ReadonlyJSONArray = readonly ReadonlyJSONValue[]; + +type ReadonlyJSONObject = { + readonly [key: string]: ReadonlyJSONValue; +}; + +type ReadonlyJSONValue = null | string | number | boolean | ReadonlyJSONObject | ReadonlyJSONArray; + +type ReasoningPart = { + type: "reasoning"; + text: string; + status: TextStatus; + parentId?: string; +}; + +declare type ReconnectOnError = (err: Error) => boolean | 1 | 2; + +declare class Redis extends Commander implements DataHandledable { + static Cluster: typeof Cluster; + static Command: typeof Command; + private static defaultOptions; + static createClient(...args: ConstructorParameters<typeof Redis>): Redis; + options: RedisOptions; + status: RedisStatus; + stream: NetStream; + isCluster: boolean; + condition: Condition | null; + commandQueue: Deque<CommandItem>; + private connector; + private reconnectTimeout; + private offlineQueue; + private connectionEpoch; + private retryAttempts; + private manuallyClosing; + private socketTimeoutTimer; + private _autoPipelines; + private _runningAutoPipelines; + constructor(port: number, host: string, options: RedisOptions); + constructor(path: string, options: RedisOptions); + constructor(port: number, options: RedisOptions); + constructor(port: number, host: string); + constructor(options: RedisOptions); + constructor(port: number); + constructor(path: string); + constructor(); + get autoPipelineQueueSize(): number; + connect(callback?: Callback<void>): Promise<void>; + private _connect; + disconnect(reconnect?: boolean): void; + end(): void; + duplicate(override?: Partial<RedisOptions>): Redis; + get mode(): "monitor" | "normal" | "subscriber"; + monitor(callback?: Callback<Redis>): Promise<Redis>; + sendCommand(command: Command, stream?: WriteableStream): unknown; + private getBlockingTimeoutInMs; + private getConfiguredBlockingTimeout; + private setSocketTimeout; + scanStream(options?: ScanStreamOptions): ScanStream; + scanBufferStream(options?: ScanStreamOptions): ScanStream; + sscanStream(key: string, options?: ScanStreamOptions): ScanStream; + sscanBufferStream(key: string, options?: ScanStreamOptions): ScanStream; + hscanStream(key: string, options?: ScanStreamOptions): ScanStream; + hscanBufferStream(key: string, options?: ScanStreamOptions): ScanStream; + zscanStream(key: string, options?: ScanStreamOptions): ScanStream; + zscanBufferStream(key: string, options?: ScanStreamOptions): ScanStream; + silentEmit(eventName: string, arg?: unknown): boolean; + recoverFromFatalError(_commandError: Error, err: Error, options: FlushQueueOptions): void; + handleReconnection(err: Error, item: CommandItem): void; + _getServerAddress(): { + address: string; + port: number | undefined; + }; + private _buildCommandContext; + _buildBatchContext(batchSize: number): BatchOperationContext; + private _getDescription; + private resetCommandQueue; + private resetOfflineQueue; + private parseOptions; + private setStatus; + private createScanStream; + private flushQueue; + private _readyCheck; +} + +interface Redis extends EventEmitter { + on(event: "message", cb: (channel: string, message: string) => void): this; + once(event: "message", cb: (channel: string, message: string) => void): this; + on(event: "messageBuffer", cb: (channel: Buffer, message: Buffer) => void): this; + once(event: "messageBuffer", cb: (channel: Buffer, message: Buffer) => void): this; + on(event: "pmessage", cb: (pattern: string, channel: string, message: string) => void): this; + once(event: "pmessage", cb: (pattern: string, channel: string, message: string) => void): this; + on(event: "pmessageBuffer", cb: (pattern: string, channel: Buffer, message: Buffer) => void): this; + once(event: "pmessageBuffer", cb: (pattern: string, channel: Buffer, message: Buffer) => void): this; + on(event: "error", cb: (error: Error) => void): this; + once(event: "error", cb: (error: Error) => void): this; + on(event: RedisStatus, cb: () => void): this; + once(event: RedisStatus, cb: () => void): this; + on(event: string | symbol, listener: (...args: any[]) => void): this; + once(event: string | symbol, listener: (...args: any[]) => void): this; +} + +interface Redis extends Transaction { +} + +interface RedisCommander<Context extends ClientContext = { + type: "default"; +}> { + call(command: string, callback?: Callback<unknown>): Result<unknown, Context>; + call(command: string, args: (string | Buffer | number)[], callback?: Callback<unknown>): Result<unknown, Context>; + call(...args: [ + command: string, + ...args: (string | Buffer | number)[], + callback: Callback<unknown> + ]): Result<unknown, Context>; + call(...args: [ + command: string, + ...args: (string | Buffer | number)[] + ]): Result<unknown, Context>; + callBuffer(command: string, callback?: Callback<unknown>): Result<unknown, Context>; + callBuffer(command: string, args: (string | Buffer | number)[], callback?: Callback<unknown>): Result<unknown, Context>; + callBuffer(...args: [ + command: string, + ...args: (string | Buffer | number)[], + callback: Callback<unknown> + ]): Result<unknown, Context>; + callBuffer(...args: [ + command: string, + ...args: (string | Buffer | number)[] + ]): Result<unknown, Context>; + acl(subcommand: "CAT", callback?: Callback<unknown>): Result<unknown, Context>; + acl(subcommand: "CAT", categoryname: string | Buffer, callback?: Callback<unknown>): Result<unknown, Context>; + acl(...args: [ + subcommand: "DELUSER", + ...usernames: (string | Buffer)[], + callback: Callback<number> + ]): Result<number, Context>; + acl(...args: [ + subcommand: "DELUSER", + ...usernames: (string | Buffer)[] + ]): Result<number, Context>; + acl(subcommand: "DRYRUN", username: string | Buffer, command: string | Buffer, callback?: Callback<string>): Result<string, Context>; + aclBuffer(subcommand: "DRYRUN", username: string | Buffer, command: string | Buffer, callback?: Callback<Buffer>): Result<Buffer, Context>; + acl(...args: [ + subcommand: "DRYRUN", + username: string | Buffer, + command: string | Buffer, + ...args: (string | Buffer | number)[], + callback: Callback<string> + ]): Result<string, Context>; + aclBuffer(...args: [ + subcommand: "DRYRUN", + username: string | Buffer, + command: string | Buffer, + ...args: (string | Buffer | number)[], + callback: Callback<Buffer> + ]): Result<Buffer, Context>; + acl(...args: [ + subcommand: "DRYRUN", + username: string | Buffer, + command: string | Buffer, + ...args: (string | Buffer | number)[] + ]): Result<string, Context>; + aclBuffer(...args: [ + subcommand: "DRYRUN", + username: string | Buffer, + command: string | Buffer, + ...args: (string | Buffer | number)[] + ]): Result<Buffer, Context>; + acl(subcommand: "GENPASS", callback?: Callback<string>): Result<string, Context>; + aclBuffer(subcommand: "GENPASS", callback?: Callback<Buffer>): Result<Buffer, Context>; + acl(subcommand: "GENPASS", bits: number | string, callback?: Callback<string>): Result<string, Context>; + aclBuffer(subcommand: "GENPASS", bits: number | string, callback?: Callback<Buffer>): Result<Buffer, Context>; + acl(subcommand: "GETUSER", username: string | Buffer, callback?: Callback<string[] | null>): Result<string[] | null, Context>; + aclBuffer(subcommand: "GETUSER", username: string | Buffer, callback?: Callback<Buffer[] | null>): Result<Buffer[] | null, Context>; + acl(subcommand: "HELP", callback?: Callback<unknown>): Result<unknown, Context>; + acl(subcommand: "LIST", callback?: Callback<string[]>): Result<string[], Context>; + aclBuffer(subcommand: "LIST", callback?: Callback<Buffer[]>): Result<Buffer[], Context>; + acl(subcommand: "LOAD", callback?: Callback<"OK">): Result<"OK", Context>; + acl(subcommand: "LOG", callback?: Callback<unknown>): Result<unknown, Context>; + acl(subcommand: "LOG", count: number | string, callback?: Callback<unknown>): Result<unknown, Context>; + acl(subcommand: "LOG", reset: "RESET", callback?: Callback<unknown>): Result<unknown, Context>; + acl(subcommand: "SAVE", callback?: Callback<"OK">): Result<"OK", Context>; + acl(subcommand: "SETUSER", username: string | Buffer, callback?: Callback<"OK">): Result<"OK", Context>; + acl(...args: [ + subcommand: "SETUSER", + username: string | Buffer, + ...rules: (string | Buffer)[], + callback: Callback<"OK"> + ]): Result<"OK", Context>; + acl(...args: [ + subcommand: "SETUSER", + username: string | Buffer, + ...rules: (string | Buffer)[] + ]): Result<"OK", Context>; + acl(subcommand: "USERS", callback?: Callback<string[]>): Result<string[], Context>; + aclBuffer(subcommand: "USERS", callback?: Callback<Buffer[]>): Result<Buffer[], Context>; + acl(subcommand: "WHOAMI", callback?: Callback<string>): Result<string, Context>; + aclBuffer(subcommand: "WHOAMI", callback?: Callback<Buffer>): Result<Buffer, Context>; + append(key: RedisKey, value: string | Buffer | number, callback?: Callback<number>): Result<number, Context>; + arcount(key: RedisKey, callback?: Callback<number>): Result<number, Context>; + ardel(...args: [ + key: RedisKey, + ...indices: (number | string)[], + callback: Callback<number> + ]): Result<number, Context>; + ardel(...args: [ + key: RedisKey, + ...indices: (number | string)[] + ]): Result<number, Context>; + ardelrange(...args: [ + key: RedisKey, + ...ranges: (string | number)[], + callback: Callback<number> + ]): Result<number, Context>; + ardelrange(...args: [ + key: RedisKey, + ...ranges: (string | number)[] + ]): Result<number, Context>; + arget(key: RedisKey, index: number | string, callback?: Callback<string | null>): Result<string | null, Context>; + argetBuffer(key: RedisKey, index: number | string, callback?: Callback<Buffer | null>): Result<Buffer | null, Context>; + argetrange(key: RedisKey, start: number | string, end: number | string, callback?: Callback<(string | null)[]>): Result<(string | null)[], Context>; + argetrangeBuffer(key: RedisKey, start: number | string, end: number | string, callback?: Callback<(Buffer | null)[]>): Result<(Buffer | null)[], Context>; + argrep(key: RedisKey, start: number | string, end: number | string, predicate: 'EXACT' | 'GLOB' | 'MATCH' | 'RE', value: RedisValue, callback: Callback<number[]>): Result<number[], Context>; + argrep(...args: [ + key: RedisKey, + start: number | string, + end: number | string, + predicate: 'EXACT' | 'GLOB' | 'MATCH' | 'RE', + value: RedisValue, + ...args: RedisValue[], + callback: Callback<number[] | Array<[ + index: number, + value: string + ]>> + ]): Result<number[] | Array<[ + index: number, + value: string + ]>, Context>; + argrep<T extends RedisValue[]>(...args: [ + key: RedisKey, + start: number | string, + end: number | string, + predicate: 'EXACT' | 'GLOB' | 'MATCH' | 'RE', + value: RedisValue, + ...args: T + ]): Result<'WITHVALUES' extends T[number] ? Array<[ + index: number, + value: string + ]> : number[], Context>; + argrepBuffer(key: RedisKey, start: number | string, end: number | string, predicate: 'EXACT' | 'GLOB' | 'MATCH' | 'RE', value: RedisValue, callback: Callback<number[]>): Result<number[], Context>; + argrepBuffer(...args: [ + key: RedisKey, + start: number | string, + end: number | string, + predicate: 'EXACT' | 'GLOB' | 'MATCH' | 'RE', + value: RedisValue, + ...args: RedisValue[], + callback: Callback<number[] | Array<[ + index: number, + value: Buffer + ]>> + ]): Result<number[] | Array<[ + index: number, + value: Buffer + ]>, Context>; + argrepBuffer<T extends RedisValue[]>(...args: [ + key: RedisKey, + start: number | string, + end: number | string, + predicate: 'EXACT' | 'GLOB' | 'MATCH' | 'RE', + value: RedisValue, + ...args: T + ]): Result<'WITHVALUES' extends T[number] ? Array<[ + index: number, + value: Buffer + ]> : number[], Context>; + arinfo(key: RedisKey, callback?: Callback<(string | number)[]>): Result<(string | number)[], Context>; + arinfoBuffer(key: RedisKey, callback?: Callback<(Buffer | number)[]>): Result<(Buffer | number)[], Context>; + arinfo(key: RedisKey, full: 'FULL', callback?: Callback<(string | number)[]>): Result<(string | number)[], Context>; + arinfoBuffer(key: RedisKey, full: 'FULL', callback?: Callback<(Buffer | number)[]>): Result<(Buffer | number)[], Context>; + arinsert(...args: [ + key: RedisKey, + ...values: (string | Buffer | number)[], + callback: Callback<number> + ]): Result<number, Context>; + arinsert(...args: [ + key: RedisKey, + ...values: (string | Buffer | number)[] + ]): Result<number, Context>; + arlastitems(key: RedisKey, count: number | string, callback?: Callback<(string | null)[]>): Result<(string | null)[], Context>; + arlastitemsBuffer(key: RedisKey, count: number | string, callback?: Callback<(Buffer | null)[]>): Result<(Buffer | null)[], Context>; + arlastitems(key: RedisKey, count: number | string, rev: 'REV', callback?: Callback<(string | null)[]>): Result<(string | null)[], Context>; + arlastitemsBuffer(key: RedisKey, count: number | string, rev: 'REV', callback?: Callback<(Buffer | null)[]>): Result<(Buffer | null)[], Context>; + arlen(key: RedisKey, callback?: Callback<number>): Result<number, Context>; + armget(...args: [ + key: RedisKey, + ...indices: (number | string)[], + callback: Callback<(string | null)[]> + ]): Result<(string | null)[], Context>; + armgetBuffer(...args: [ + key: RedisKey, + ...indices: (number | string)[], + callback: Callback<(Buffer | null)[]> + ]): Result<(Buffer | null)[], Context>; + armget(...args: [ + key: RedisKey, + ...indices: (number | string)[] + ]): Result<(string | null)[], Context>; + armgetBuffer(...args: [ + key: RedisKey, + ...indices: (number | string)[] + ]): Result<(Buffer | null)[], Context>; + armset(...args: [ + key: RedisKey, + ...data: (string | Buffer | number)[], + callback: Callback<number> + ]): Result<number, Context>; + armset(...args: [ + key: RedisKey, + ...data: (string | Buffer | number)[] + ]): Result<number, Context>; + arnext(key: RedisKey, callback?: Callback<number | null>): Result<number | null, Context>; + arop(key: RedisKey, start: number | string, end: number | string, sum: 'SUM', callback?: Callback<string | null>): Result<string | null, Context>; + aropBuffer(key: RedisKey, start: number | string, end: number | string, sum: 'SUM', callback?: Callback<Buffer | null>): Result<Buffer | null, Context>; + arop(key: RedisKey, start: number | string, end: number | string, min: 'MIN', callback?: Callback<string | null>): Result<string | null, Context>; + aropBuffer(key: RedisKey, start: number | string, end: number | string, min: 'MIN', callback?: Callback<Buffer | null>): Result<Buffer | null, Context>; + arop(key: RedisKey, start: number | string, end: number | string, max: 'MAX', callback?: Callback<string | null>): Result<string | null, Context>; + aropBuffer(key: RedisKey, start: number | string, end: number | string, max: 'MAX', callback?: Callback<Buffer | null>): Result<Buffer | null, Context>; + arop(key: RedisKey, start: number | string, end: number | string, and: 'AND', callback?: Callback<number | null>): Result<number | null, Context>; + arop(key: RedisKey, start: number | string, end: number | string, or: 'OR', callback?: Callback<number | null>): Result<number | null, Context>; + arop(key: RedisKey, start: number | string, end: number | string, xor: 'XOR', callback?: Callback<number | null>): Result<number | null, Context>; + arop(key: RedisKey, start: number | string, end: number | string, match: 'MATCH', value: string | Buffer | number, callback?: Callback<number>): Result<number, Context>; + arop(key: RedisKey, start: number | string, end: number | string, used: 'USED', callback?: Callback<number>): Result<number, Context>; + arring(...args: [ + key: RedisKey, + size: number | string, + ...values: (string | Buffer | number)[], + callback: Callback<number> + ]): Result<number, Context>; + arring(...args: [ + key: RedisKey, + size: number | string, + ...values: (string | Buffer | number)[] + ]): Result<number, Context>; + arscan(key: RedisKey, start: number | string, end: number | string, callback?: Callback<Array<[ + index: number, + value: string + ]>>): Result<Array<[ + index: number, + value: string + ]>, Context>; + arscanBuffer(key: RedisKey, start: number | string, end: number | string, callback?: Callback<Array<[ + index: number, + value: Buffer + ]>>): Result<Array<[ + index: number, + value: Buffer + ]>, Context>; + arscan(key: RedisKey, start: number | string, end: number | string, limitToken: 'LIMIT', limit: number | string, callback?: Callback<Array<[ + index: number, + value: string + ]>>): Result<Array<[ + index: number, + value: string + ]>, Context>; + arscanBuffer(key: RedisKey, start: number | string, end: number | string, limitToken: 'LIMIT', limit: number | string, callback?: Callback<Array<[ + index: number, + value: Buffer + ]>>): Result<Array<[ + index: number, + value: Buffer + ]>, Context>; + arseek(key: RedisKey, index: number | string, callback?: Callback<number>): Result<number, Context>; + arset(...args: [ + key: RedisKey, + index: number | string, + ...values: (string | Buffer | number)[], + callback: Callback<number> + ]): Result<number, Context>; + arset(...args: [ + key: RedisKey, + index: number | string, + ...values: (string | Buffer | number)[] + ]): Result<number, Context>; + asking(callback?: Callback<"OK">): Result<"OK", Context>; + auth(password: string | Buffer, callback?: Callback<"OK">): Result<"OK", Context>; + auth(username: string | Buffer, password: string | Buffer, callback?: Callback<"OK">): Result<"OK", Context>; + bgrewriteaof(callback?: Callback<string>): Result<string, Context>; + bgrewriteaofBuffer(callback?: Callback<Buffer>): Result<Buffer, Context>; + bgsave(callback?: Callback<"OK">): Result<"OK", Context>; + bgsave(schedule: "SCHEDULE", callback?: Callback<"OK">): Result<"OK", Context>; + bitcount(key: RedisKey, callback?: Callback<number>): Result<number, Context>; + bitcount(key: RedisKey, start: number | string, end: number | string, callback?: Callback<number>): Result<number, Context>; + bitcount(key: RedisKey, start: number | string, end: number | string, byte: "BYTE", callback?: Callback<number>): Result<number, Context>; + bitcount(key: RedisKey, start: number | string, end: number | string, bit: "BIT", callback?: Callback<number>): Result<number, Context>; + bitfield(key: RedisKey, encodingOffsetToken: "GET", encoding: string | Buffer, offset: number | string, callback?: Callback<unknown>): Result<unknown, Context>; + bitfield(key: RedisKey, encodingOffsetValueToken: "SET", encoding: string | Buffer, offset: number | string, value: number | string, callback?: Callback<unknown>): Result<unknown, Context>; + bitfield(key: RedisKey, encodingOffsetIncrementToken: "INCRBY", encoding: string | Buffer, offset: number | string, increment: number | string, callback?: Callback<unknown>): Result<unknown, Context>; + bitfield(key: RedisKey, overflow: "OVERFLOW", wrap: "WRAP", encodingOffsetValueToken: "SET", encoding: string | Buffer, offset: number | string, value: number | string, callback?: Callback<unknown>): Result<unknown, Context>; + bitfield(key: RedisKey, overflow: "OVERFLOW", wrap: "WRAP", encodingOffsetIncrementToken: "INCRBY", encoding: string | Buffer, offset: number | string, increment: number | string, callback?: Callback<unknown>): Result<unknown, Context>; + bitfield(key: RedisKey, overflow: "OVERFLOW", sat: "SAT", encodingOffsetValueToken: "SET", encoding: string | Buffer, offset: number | string, value: number | string, callback?: Callback<unknown>): Result<unknown, Context>; + bitfield(key: RedisKey, overflow: "OVERFLOW", sat: "SAT", encodingOffsetIncrementToken: "INCRBY", encoding: string | Buffer, offset: number | string, increment: number | string, callback?: Callback<unknown>): Result<unknown, Context>; + bitfield(key: RedisKey, overflow: "OVERFLOW", fail: "FAIL", encodingOffsetValueToken: "SET", encoding: string | Buffer, offset: number | string, value: number | string, callback?: Callback<unknown>): Result<unknown, Context>; + bitfield(key: RedisKey, overflow: "OVERFLOW", fail: "FAIL", encodingOffsetIncrementToken: "INCRBY", encoding: string | Buffer, offset: number | string, increment: number | string, callback?: Callback<unknown>): Result<unknown, Context>; + bitfield_ro(...args: [ + key: RedisKey, + encodingOffsetToken: "GET", + ...encodingOffsets: (string | Buffer | number)[], + callback: Callback<unknown[]> + ]): Result<unknown[], Context>; + bitfield_ro(...args: [ + key: RedisKey, + encodingOffsetToken: "GET", + ...encodingOffsets: (string | Buffer | number)[] + ]): Result<unknown[], Context>; + bitop(...args: [ + operation: string | Buffer, + destkey: RedisKey, + ...keys: RedisKey[], + callback: Callback<number> + ]): Result<number, Context>; + bitop(...args: [ + operation: string | Buffer, + destkey: RedisKey, + keys: RedisKey[], + callback: Callback<number> + ]): Result<number, Context>; + bitop(...args: [ + operation: string | Buffer, + destkey: RedisKey, + ...keys: RedisKey[] + ]): Result<number, Context>; + bitop(...args: [ + operation: string | Buffer, + destkey: RedisKey, + keys: RedisKey[] + ]): Result<number, Context>; + bitpos(key: RedisKey, bit: number | string, callback?: Callback<number>): Result<number, Context>; + bitpos(key: RedisKey, bit: number | string, start: number | string, callback?: Callback<number>): Result<number, Context>; + bitpos(key: RedisKey, bit: number | string, start: number | string, end: number | string, callback?: Callback<number>): Result<number, Context>; + bitpos(key: RedisKey, bit: number | string, start: number | string, end: number | string, byte: "BYTE", callback?: Callback<number>): Result<number, Context>; + bitpos(key: RedisKey, bit: number | string, start: number | string, end: number | string, bit1: "BIT", callback?: Callback<number>): Result<number, Context>; + blmove(source: RedisKey, destination: RedisKey, left: "LEFT", left1: "LEFT", timeout: number | string, callback?: Callback<string | null>): Result<string | null, Context>; + blmoveBuffer(source: RedisKey, destination: RedisKey, left: "LEFT", left1: "LEFT", timeout: number | string, callback?: Callback<Buffer | null>): Result<Buffer | null, Context>; + blmove(source: RedisKey, destination: RedisKey, left: "LEFT", right: "RIGHT", timeout: number | string, callback?: Callback<string | null>): Result<string | null, Context>; + blmoveBuffer(source: RedisKey, destination: RedisKey, left: "LEFT", right: "RIGHT", timeout: number | string, callback?: Callback<Buffer | null>): Result<Buffer | null, Context>; + blmove(source: RedisKey, destination: RedisKey, right: "RIGHT", left: "LEFT", timeout: number | string, callback?: Callback<string | null>): Result<string | null, Context>; + blmoveBuffer(source: RedisKey, destination: RedisKey, right: "RIGHT", left: "LEFT", timeout: number | string, callback?: Callback<Buffer | null>): Result<Buffer | null, Context>; + blmove(source: RedisKey, destination: RedisKey, right: "RIGHT", right1: "RIGHT", timeout: number | string, callback?: Callback<string | null>): Result<string | null, Context>; + blmoveBuffer(source: RedisKey, destination: RedisKey, right: "RIGHT", right1: "RIGHT", timeout: number | string, callback?: Callback<Buffer | null>): Result<Buffer | null, Context>; + blmpop(...args: [ + timeout: number | string, + numkeys: number | string, + ...keys: RedisKey[], + left: "LEFT", + callback: Callback<[ + key: string, + members: string[] + ] | null> + ]): Result<[ + key: string, + members: string[] + ] | null, Context>; + blmpopBuffer(...args: [ + timeout: number | string, + numkeys: number | string, + ...keys: RedisKey[], + left: "LEFT", + callback: Callback<[ + key: Buffer, + members: Buffer[] + ] | null> + ]): Result<[ + key: Buffer, + members: Buffer[] + ] | null, Context>; + blmpop(...args: [ + timeout: number | string, + numkeys: number | string, + keys: RedisKey[], + left: "LEFT", + callback: Callback<[ + key: string, + members: string[] + ] | null> + ]): Result<[ + key: string, + members: string[] + ] | null, Context>; + blmpopBuffer(...args: [ + timeout: number | string, + numkeys: number | string, + keys: RedisKey[], + left: "LEFT", + callback: Callback<[ + key: Buffer, + members: Buffer[] + ] | null> + ]): Result<[ + key: Buffer, + members: Buffer[] + ] | null, Context>; + blmpop(...args: [ + timeout: number | string, + numkeys: number | string, + ...keys: RedisKey[], + left: "LEFT" + ]): Result<[ + key: string, + members: string[] + ] | null, Context>; + blmpopBuffer(...args: [ + timeout: number | string, + numkeys: number | string, + ...keys: RedisKey[], + left: "LEFT" + ]): Result<[ + key: Buffer, + members: Buffer[] + ] | null, Context>; + blmpop(...args: [ + timeout: number | string, + numkeys: number | string, + keys: RedisKey[], + left: "LEFT" + ]): Result<[ + key: string, + members: string[] + ] | null, Context>; + blmpopBuffer(...args: [ + timeout: number | string, + numkeys: number | string, + keys: RedisKey[], + left: "LEFT" + ]): Result<[ + key: Buffer, + members: Buffer[] + ] | null, Context>; + blmpop(...args: [ + timeout: number | string, + numkeys: number | string, + ...keys: RedisKey[], + left: "LEFT", + countToken: "COUNT", + count: number | string, + callback: Callback<[ + key: string, + members: string[] + ] | null> + ]): Result<[ + key: string, + members: string[] + ] | null, Context>; + blmpopBuffer(...args: [ + timeout: number | string, + numkeys: number | string, + ...keys: RedisKey[], + left: "LEFT", + countToken: "COUNT", + count: number | string, + callback: Callback<[ + key: Buffer, + members: Buffer[] + ] | null> + ]): Result<[ + key: Buffer, + members: Buffer[] + ] | null, Context>; + blmpop(...args: [ + timeout: number | string, + numkeys: number | string, + keys: RedisKey[], + left: "LEFT", + countToken: "COUNT", + count: number | string, + callback: Callback<[ + key: string, + members: string[] + ] | null> + ]): Result<[ + key: string, + members: string[] + ] | null, Context>; + blmpopBuffer(...args: [ + timeout: number | string, + numkeys: number | string, + keys: RedisKey[], + left: "LEFT", + countToken: "COUNT", + count: number | string, + callback: Callback<[ + key: Buffer, + members: Buffer[] + ] | null> + ]): Result<[ + key: Buffer, + members: Buffer[] + ] | null, Context>; + blmpop(...args: [ + timeout: number | string, + numkeys: number | string, + ...keys: RedisKey[], + left: "LEFT", + countToken: "COUNT", + count: number | string + ]): Result<[ + key: string, + members: string[] + ] | null, Context>; + blmpopBuffer(...args: [ + timeout: number | string, + numkeys: number | string, + ...keys: RedisKey[], + left: "LEFT", + countToken: "COUNT", + count: number | string + ]): Result<[ + key: Buffer, + members: Buffer[] + ] | null, Context>; + blmpop(...args: [ + timeout: number | string, + numkeys: number | string, + keys: RedisKey[], + left: "LEFT", + countToken: "COUNT", + count: number | string + ]): Result<[ + key: string, + members: string[] + ] | null, Context>; + blmpopBuffer(...args: [ + timeout: number | string, + numkeys: number | string, + keys: RedisKey[], + left: "LEFT", + countToken: "COUNT", + count: number | string + ]): Result<[ + key: Buffer, + members: Buffer[] + ] | null, Context>; + blmpop(...args: [ + timeout: number | string, + numkeys: number | string, + ...keys: RedisKey[], + right: "RIGHT", + callback: Callback<[ + key: string, + members: string[] + ] | null> + ]): Result<[ + key: string, + members: string[] + ] | null, Context>; + blmpopBuffer(...args: [ + timeout: number | string, + numkeys: number | string, + ...keys: RedisKey[], + right: "RIGHT", + callback: Callback<[ + key: Buffer, + members: Buffer[] + ] | null> + ]): Result<[ + key: Buffer, + members: Buffer[] + ] | null, Context>; + blmpop(...args: [ + timeout: number | string, + numkeys: number | string, + keys: RedisKey[], + right: "RIGHT", + callback: Callback<[ + key: string, + members: string[] + ] | null> + ]): Result<[ + key: string, + members: string[] + ] | null, Context>; + blmpopBuffer(...args: [ + timeout: number | string, + numkeys: number | string, + keys: RedisKey[], + right: "RIGHT", + callback: Callback<[ + key: Buffer, + members: Buffer[] + ] | null> + ]): Result<[ + key: Buffer, + members: Buffer[] + ] | null, Context>; + blmpop(...args: [ + timeout: number | string, + numkeys: number | string, + ...keys: RedisKey[], + right: "RIGHT" + ]): Result<[ + key: string, + members: string[] + ] | null, Context>; + blmpopBuffer(...args: [ + timeout: number | string, + numkeys: number | string, + ...keys: RedisKey[], + right: "RIGHT" + ]): Result<[ + key: Buffer, + members: Buffer[] + ] | null, Context>; + blmpop(...args: [ + timeout: number | string, + numkeys: number | string, + keys: RedisKey[], + right: "RIGHT" + ]): Result<[ + key: string, + members: string[] + ] | null, Context>; + blmpopBuffer(...args: [ + timeout: number | string, + numkeys: number | string, + keys: RedisKey[], + right: "RIGHT" + ]): Result<[ + key: Buffer, + members: Buffer[] + ] | null, Context>; + blmpop(...args: [ + timeout: number | string, + numkeys: number | string, + ...keys: RedisKey[], + right: "RIGHT", + countToken: "COUNT", + count: number | string, + callback: Callback<[ + key: string, + members: string[] + ] | null> + ]): Result<[ + key: string, + members: string[] + ] | null, Context>; + blmpopBuffer(...args: [ + timeout: number | string, + numkeys: number | string, + ...keys: RedisKey[], + right: "RIGHT", + countToken: "COUNT", + count: number | string, + callback: Callback<[ + key: Buffer, + members: Buffer[] + ] | null> + ]): Result<[ + key: Buffer, + members: Buffer[] + ] | null, Context>; + blmpop(...args: [ + timeout: number | string, + numkeys: number | string, + keys: RedisKey[], + right: "RIGHT", + countToken: "COUNT", + count: number | string, + callback: Callback<[ + key: string, + members: string[] + ] | null> + ]): Result<[ + key: string, + members: string[] + ] | null, Context>; + blmpopBuffer(...args: [ + timeout: number | string, + numkeys: number | string, + keys: RedisKey[], + right: "RIGHT", + countToken: "COUNT", + count: number | string, + callback: Callback<[ + key: Buffer, + members: Buffer[] + ] | null> + ]): Result<[ + key: Buffer, + members: Buffer[] + ] | null, Context>; + blmpop(...args: [ + timeout: number | string, + numkeys: number | string, + ...keys: RedisKey[], + right: "RIGHT", + countToken: "COUNT", + count: number | string + ]): Result<[ + key: string, + members: string[] + ] | null, Context>; + blmpopBuffer(...args: [ + timeout: number | string, + numkeys: number | string, + ...keys: RedisKey[], + right: "RIGHT", + countToken: "COUNT", + count: number | string + ]): Result<[ + key: Buffer, + members: Buffer[] + ] | null, Context>; + blmpop(...args: [ + timeout: number | string, + numkeys: number | string, + keys: RedisKey[], + right: "RIGHT", + countToken: "COUNT", + count: number | string + ]): Result<[ + key: string, + members: string[] + ] | null, Context>; + blmpopBuffer(...args: [ + timeout: number | string, + numkeys: number | string, + keys: RedisKey[], + right: "RIGHT", + countToken: "COUNT", + count: number | string + ]): Result<[ + key: Buffer, + members: Buffer[] + ] | null, Context>; + blpop(...args: [ + ...keys: RedisKey[], + timeout: number | string, + callback: Callback<[ + string, + string + ] | null> + ]): Result<[ + string, + string + ] | null, Context>; + blpopBuffer(...args: [ + ...keys: RedisKey[], + timeout: number | string, + callback: Callback<[ + Buffer, + Buffer + ] | null> + ]): Result<[ + Buffer, + Buffer + ] | null, Context>; + blpop(...args: [ + keys: RedisKey[], + timeout: number | string, + callback: Callback<[ + string, + string + ] | null> + ]): Result<[ + string, + string + ] | null, Context>; + blpopBuffer(...args: [ + keys: RedisKey[], + timeout: number | string, + callback: Callback<[ + Buffer, + Buffer + ] | null> + ]): Result<[ + Buffer, + Buffer + ] | null, Context>; + blpop(...args: [ + ...keys: RedisKey[], + timeout: number | string + ]): Result<[ + string, + string + ] | null, Context>; + blpopBuffer(...args: [ + ...keys: RedisKey[], + timeout: number | string + ]): Result<[ + Buffer, + Buffer + ] | null, Context>; + blpop(...args: [ + keys: RedisKey[], + timeout: number | string + ]): Result<[ + string, + string + ] | null, Context>; + blpopBuffer(...args: [ + keys: RedisKey[], + timeout: number | string + ]): Result<[ + Buffer, + Buffer + ] | null, Context>; + brpop(...args: [ + ...keys: RedisKey[], + timeout: number | string, + callback: Callback<[ + string, + string + ] | null> + ]): Result<[ + string, + string + ] | null, Context>; + brpopBuffer(...args: [ + ...keys: RedisKey[], + timeout: number | string, + callback: Callback<[ + Buffer, + Buffer + ] | null> + ]): Result<[ + Buffer, + Buffer + ] | null, Context>; + brpop(...args: [ + keys: RedisKey[], + timeout: number | string, + callback: Callback<[ + string, + string + ] | null> + ]): Result<[ + string, + string + ] | null, Context>; + brpopBuffer(...args: [ + keys: RedisKey[], + timeout: number | string, + callback: Callback<[ + Buffer, + Buffer + ] | null> + ]): Result<[ + Buffer, + Buffer + ] | null, Context>; + brpop(...args: [ + ...keys: RedisKey[], + timeout: number | string + ]): Result<[ + string, + string + ] | null, Context>; + brpopBuffer(...args: [ + ...keys: RedisKey[], + timeout: number | string + ]): Result<[ + Buffer, + Buffer + ] | null, Context>; + brpop(...args: [ + keys: RedisKey[], + timeout: number | string + ]): Result<[ + string, + string + ] | null, Context>; + brpopBuffer(...args: [ + keys: RedisKey[], + timeout: number | string + ]): Result<[ + Buffer, + Buffer + ] | null, Context>; + brpoplpush(source: RedisKey, destination: RedisKey, timeout: number | string, callback?: Callback<string | null>): Result<string | null, Context>; + brpoplpushBuffer(source: RedisKey, destination: RedisKey, timeout: number | string, callback?: Callback<Buffer | null>): Result<Buffer | null, Context>; + bzmpop(...args: [ + timeout: number | string, + numkeys: number | string, + ...keys: RedisKey[], + min: "MIN", + callback: Callback<unknown> + ]): Result<unknown, Context>; + bzmpop(...args: [ + timeout: number | string, + numkeys: number | string, + keys: RedisKey[], + min: "MIN", + callback: Callback<unknown> + ]): Result<unknown, Context>; + bzmpop(...args: [ + timeout: number | string, + numkeys: number | string, + ...keys: RedisKey[], + min: "MIN" + ]): Result<unknown, Context>; + bzmpop(...args: [ + timeout: number | string, + numkeys: number | string, + keys: RedisKey[], + min: "MIN" + ]): Result<unknown, Context>; + bzmpop(...args: [ + timeout: number | string, + numkeys: number | string, + ...keys: RedisKey[], + min: "MIN", + countToken: "COUNT", + count: number | string, + callback: Callback<unknown> + ]): Result<unknown, Context>; + bzmpop(...args: [ + timeout: number | string, + numkeys: number | string, + keys: RedisKey[], + min: "MIN", + countToken: "COUNT", + count: number | string, + callback: Callback<unknown> + ]): Result<unknown, Context>; + bzmpop(...args: [ + timeout: number | string, + numkeys: number | string, + ...keys: RedisKey[], + min: "MIN", + countToken: "COUNT", + count: number | string + ]): Result<unknown, Context>; + bzmpop(...args: [ + timeout: number | string, + numkeys: number | string, + keys: RedisKey[], + min: "MIN", + countToken: "COUNT", + count: number | string + ]): Result<unknown, Context>; + bzmpop(...args: [ + timeout: number | string, + numkeys: number | string, + ...keys: RedisKey[], + max: "MAX", + callback: Callback<unknown> + ]): Result<unknown, Context>; + bzmpop(...args: [ + timeout: number | string, + numkeys: number | string, + keys: RedisKey[], + max: "MAX", + callback: Callback<unknown> + ]): Result<unknown, Context>; + bzmpop(...args: [ + timeout: number | string, + numkeys: number | string, + ...keys: RedisKey[], + max: "MAX" + ]): Result<unknown, Context>; + bzmpop(...args: [ + timeout: number | string, + numkeys: number | string, + keys: RedisKey[], + max: "MAX" + ]): Result<unknown, Context>; + bzmpop(...args: [ + timeout: number | string, + numkeys: number | string, + ...keys: RedisKey[], + max: "MAX", + countToken: "COUNT", + count: number | string, + callback: Callback<unknown> + ]): Result<unknown, Context>; + bzmpop(...args: [ + timeout: number | string, + numkeys: number | string, + keys: RedisKey[], + max: "MAX", + countToken: "COUNT", + count: number | string, + callback: Callback<unknown> + ]): Result<unknown, Context>; + bzmpop(...args: [ + timeout: number | string, + numkeys: number | string, + ...keys: RedisKey[], + max: "MAX", + countToken: "COUNT", + count: number | string + ]): Result<unknown, Context>; + bzmpop(...args: [ + timeout: number | string, + numkeys: number | string, + keys: RedisKey[], + max: "MAX", + countToken: "COUNT", + count: number | string + ]): Result<unknown, Context>; + bzpopmax(...args: [ + ...keys: RedisKey[], + timeout: number | string, + callback: Callback<[ + key: string, + member: string, + score: string + ] | null> + ]): Result<[ + key: string, + member: string, + score: string + ] | null, Context>; + bzpopmaxBuffer(...args: [ + ...keys: RedisKey[], + timeout: number | string, + callback: Callback<[ + key: Buffer, + member: Buffer, + score: Buffer + ] | null> + ]): Result<[ + key: Buffer, + member: Buffer, + score: Buffer + ] | null, Context>; + bzpopmax(...args: [ + keys: RedisKey[], + timeout: number | string, + callback: Callback<[ + key: string, + member: string, + score: string + ] | null> + ]): Result<[ + key: string, + member: string, + score: string + ] | null, Context>; + bzpopmaxBuffer(...args: [ + keys: RedisKey[], + timeout: number | string, + callback: Callback<[ + key: Buffer, + member: Buffer, + score: Buffer + ] | null> + ]): Result<[ + key: Buffer, + member: Buffer, + score: Buffer + ] | null, Context>; + bzpopmax(...args: [ + ...keys: RedisKey[], + timeout: number | string + ]): Result<[ + key: string, + member: string, + score: string + ] | null, Context>; + bzpopmaxBuffer(...args: [ + ...keys: RedisKey[], + timeout: number | string + ]): Result<[ + key: Buffer, + member: Buffer, + score: Buffer + ] | null, Context>; + bzpopmax(...args: [ + keys: RedisKey[], + timeout: number | string + ]): Result<[ + key: string, + member: string, + score: string + ] | null, Context>; + bzpopmaxBuffer(...args: [ + keys: RedisKey[], + timeout: number | string + ]): Result<[ + key: Buffer, + member: Buffer, + score: Buffer + ] | null, Context>; + bzpopmin(...args: [ + ...keys: RedisKey[], + timeout: number | string, + callback: Callback<[ + key: string, + member: string, + score: string + ] | null> + ]): Result<[ + key: string, + member: string, + score: string + ] | null, Context>; + bzpopminBuffer(...args: [ + ...keys: RedisKey[], + timeout: number | string, + callback: Callback<[ + key: Buffer, + member: Buffer, + score: Buffer + ] | null> + ]): Result<[ + key: Buffer, + member: Buffer, + score: Buffer + ] | null, Context>; + bzpopmin(...args: [ + keys: RedisKey[], + timeout: number | string, + callback: Callback<[ + key: string, + member: string, + score: string + ] | null> + ]): Result<[ + key: string, + member: string, + score: string + ] | null, Context>; + bzpopminBuffer(...args: [ + keys: RedisKey[], + timeout: number | string, + callback: Callback<[ + key: Buffer, + member: Buffer, + score: Buffer + ] | null> + ]): Result<[ + key: Buffer, + member: Buffer, + score: Buffer + ] | null, Context>; + bzpopmin(...args: [ + ...keys: RedisKey[], + timeout: number | string + ]): Result<[ + key: string, + member: string, + score: string + ] | null, Context>; + bzpopminBuffer(...args: [ + ...keys: RedisKey[], + timeout: number | string + ]): Result<[ + key: Buffer, + member: Buffer, + score: Buffer + ] | null, Context>; + bzpopmin(...args: [ + keys: RedisKey[], + timeout: number | string + ]): Result<[ + key: string, + member: string, + score: string + ] | null, Context>; + bzpopminBuffer(...args: [ + keys: RedisKey[], + timeout: number | string + ]): Result<[ + key: Buffer, + member: Buffer, + score: Buffer + ] | null, Context>; + client(subcommand: "CACHING", yes: "YES", callback?: Callback<"OK">): Result<"OK", Context>; + client(subcommand: "CACHING", no: "NO", callback?: Callback<"OK">): Result<"OK", Context>; + client(subcommand: "GETNAME", callback?: Callback<string | null>): Result<string | null, Context>; + clientBuffer(subcommand: "GETNAME", callback?: Callback<Buffer | null>): Result<Buffer | null, Context>; + client(subcommand: "GETREDIR", callback?: Callback<number>): Result<number, Context>; + client(subcommand: "HELP", callback?: Callback<unknown>): Result<unknown, Context>; + client(subcommand: "ID", callback?: Callback<number>): Result<number, Context>; + client(subcommand: "INFO", callback?: Callback<string>): Result<string, Context>; + clientBuffer(subcommand: "INFO", callback?: Callback<Buffer>): Result<Buffer, Context>; + client(...args: [ + subcommand: "KILL", + ...args: RedisValue[], + callback: Callback<unknown> + ]): Result<unknown, Context>; + client(...args: [ + subcommand: "KILL", + ...args: RedisValue[] + ]): Result<unknown, Context>; + client(subcommand: "LIST", callback?: Callback<unknown>): Result<unknown, Context>; + client(...args: [ + subcommand: "LIST", + idToken: "ID", + ...clientIds: (number | string)[], + callback: Callback<unknown> + ]): Result<unknown, Context>; + client(...args: [ + subcommand: "LIST", + idToken: "ID", + ...clientIds: (number | string)[] + ]): Result<unknown, Context>; + client(subcommand: "LIST", type: "TYPE", normal: "NORMAL", callback?: Callback<unknown>): Result<unknown, Context>; + client(...args: [ + subcommand: "LIST", + type: "TYPE", + normal: "NORMAL", + idToken: "ID", + ...clientIds: (number | string)[], + callback: Callback<unknown> + ]): Result<unknown, Context>; + client(...args: [ + subcommand: "LIST", + type: "TYPE", + normal: "NORMAL", + idToken: "ID", + ...clientIds: (number | string)[] + ]): Result<unknown, Context>; + client(subcommand: "LIST", type: "TYPE", master: "MASTER", callback?: Callback<unknown>): Result<unknown, Context>; + client(...args: [ + subcommand: "LIST", + type: "TYPE", + master: "MASTER", + idToken: "ID", + ...clientIds: (number | string)[], + callback: Callback<unknown> + ]): Result<unknown, Context>; + client(...args: [ + subcommand: "LIST", + type: "TYPE", + master: "MASTER", + idToken: "ID", + ...clientIds: (number | string)[] + ]): Result<unknown, Context>; + client(subcommand: "LIST", type: "TYPE", replica: "REPLICA", callback?: Callback<unknown>): Result<unknown, Context>; + client(...args: [ + subcommand: "LIST", + type: "TYPE", + replica: "REPLICA", + idToken: "ID", + ...clientIds: (number | string)[], + callback: Callback<unknown> + ]): Result<unknown, Context>; + client(...args: [ + subcommand: "LIST", + type: "TYPE", + replica: "REPLICA", + idToken: "ID", + ...clientIds: (number | string)[] + ]): Result<unknown, Context>; + client(subcommand: "LIST", type: "TYPE", pubsub: "PUBSUB", callback?: Callback<unknown>): Result<unknown, Context>; + client(...args: [ + subcommand: "LIST", + type: "TYPE", + pubsub: "PUBSUB", + idToken: "ID", + ...clientIds: (number | string)[], + callback: Callback<unknown> + ]): Result<unknown, Context>; + client(...args: [ + subcommand: "LIST", + type: "TYPE", + pubsub: "PUBSUB", + idToken: "ID", + ...clientIds: (number | string)[] + ]): Result<unknown, Context>; + client(subcommand: "NO-EVICT", on: "ON", callback?: Callback<unknown>): Result<unknown, Context>; + client(subcommand: "NO-EVICT", off: "OFF", callback?: Callback<unknown>): Result<unknown, Context>; + client(subcommand: "PAUSE", timeout: number | string, callback?: Callback<"OK">): Result<"OK", Context>; + client(subcommand: "PAUSE", timeout: number | string, write: "WRITE", callback?: Callback<"OK">): Result<"OK", Context>; + client(subcommand: "PAUSE", timeout: number | string, all: "ALL", callback?: Callback<"OK">): Result<"OK", Context>; + client(subcommand: "REPLY", on: "ON", callback?: Callback<unknown>): Result<unknown, Context>; + client(subcommand: "REPLY", off: "OFF", callback?: Callback<unknown>): Result<unknown, Context>; + client(subcommand: "REPLY", skip: "SKIP", callback?: Callback<unknown>): Result<unknown, Context>; + client(subcommand: "SETNAME", connectionName: string | Buffer, callback?: Callback<"OK">): Result<"OK", Context>; + client(...args: [ + subcommand: "TRACKING", + ...args: RedisValue[], + callback: Callback<unknown> + ]): Result<unknown, Context>; + client(...args: [ + subcommand: "TRACKING", + ...args: RedisValue[] + ]): Result<unknown, Context>; + client(subcommand: "TRACKINGINFO", callback?: Callback<string>): Result<string, Context>; + clientBuffer(subcommand: "TRACKINGINFO", callback?: Callback<Buffer>): Result<Buffer, Context>; + client(subcommand: "UNBLOCK", clientId: number | string, callback?: Callback<unknown>): Result<unknown, Context>; + client(subcommand: "UNBLOCK", clientId: number | string, timeout: "TIMEOUT", callback?: Callback<unknown>): Result<unknown, Context>; + client(subcommand: "UNBLOCK", clientId: number | string, error: "ERROR", callback?: Callback<unknown>): Result<unknown, Context>; + client(subcommand: "UNPAUSE", callback?: Callback<"OK">): Result<"OK", Context>; + cluster(...args: [ + subcommand: "ADDSLOTS", + ...slots: (number | string)[], + callback: Callback<[ + startSlotRange: number, + endSlotRange: number, + ...nodes: [ + host: string, + port: number, + nodeId: string, + info: unknown[] + ][] + ][]> + ]): Result<[ + startSlotRange: number, + endSlotRange: number, + ...nodes: [ + host: string, + port: number, + nodeId: string, + info: unknown[] + ][] + ][], Context>; + cluster(...args: [ + subcommand: "ADDSLOTS", + slots: (number | string)[], + callback: Callback<[ + startSlotRange: number, + endSlotRange: number, + ...nodes: [ + host: string, + port: number, + nodeId: string, + info: unknown[] + ][] + ][]> + ]): Result<[ + startSlotRange: number, + endSlotRange: number, + ...nodes: [ + host: string, + port: number, + nodeId: string, + info: unknown[] + ][] + ][], Context>; + cluster(...args: [ + subcommand: "ADDSLOTS", + ...slots: (number | string)[] + ]): Result<[ + startSlotRange: number, + endSlotRange: number, + ...nodes: [ + host: string, + port: number, + nodeId: string, + info: unknown[] + ][] + ][], Context>; + cluster(...args: [ + subcommand: "ADDSLOTS", + slots: (number | string)[] + ]): Result<[ + startSlotRange: number, + endSlotRange: number, + ...nodes: [ + host: string, + port: number, + nodeId: string, + info: unknown[] + ][] + ][], Context>; + cluster(...args: [ + subcommand: "ADDSLOTSRANGE", + ...startSlotEndSlots: (string | number)[], + callback: Callback<[ + startSlotRange: number, + endSlotRange: number, + ...nodes: [ + host: string, + port: number, + nodeId: string, + info: unknown[] + ][] + ][]> + ]): Result<[ + startSlotRange: number, + endSlotRange: number, + ...nodes: [ + host: string, + port: number, + nodeId: string, + info: unknown[] + ][] + ][], Context>; + cluster(...args: [ + subcommand: "ADDSLOTSRANGE", + ...startSlotEndSlots: (string | number)[] + ]): Result<[ + startSlotRange: number, + endSlotRange: number, + ...nodes: [ + host: string, + port: number, + nodeId: string, + info: unknown[] + ][] + ][], Context>; + cluster(subcommand: "BUMPEPOCH", callback?: Callback<"BUMPED" | "STILL">): Result<"BUMPED" | "STILL", Context>; + cluster(subcommand: "COUNT-FAILURE-REPORTS", nodeId: string | Buffer | number, callback?: Callback<number>): Result<number, Context>; + cluster(subcommand: "COUNTKEYSINSLOT", slot: number | string, callback?: Callback<number>): Result<number, Context>; + cluster(...args: [ + subcommand: "DELSLOTS", + ...slots: (number | string)[], + callback: Callback<[ + startSlotRange: number, + endSlotRange: number, + ...nodes: [ + host: string, + port: number, + nodeId: string, + info: unknown[] + ][] + ][]> + ]): Result<[ + startSlotRange: number, + endSlotRange: number, + ...nodes: [ + host: string, + port: number, + nodeId: string, + info: unknown[] + ][] + ][], Context>; + cluster(...args: [ + subcommand: "DELSLOTS", + slots: (number | string)[], + callback: Callback<[ + startSlotRange: number, + endSlotRange: number, + ...nodes: [ + host: string, + port: number, + nodeId: string, + info: unknown[] + ][] + ][]> + ]): Result<[ + startSlotRange: number, + endSlotRange: number, + ...nodes: [ + host: string, + port: number, + nodeId: string, + info: unknown[] + ][] + ][], Context>; + cluster(...args: [ + subcommand: "DELSLOTS", + ...slots: (number | string)[] + ]): Result<[ + startSlotRange: number, + endSlotRange: number, + ...nodes: [ + host: string, + port: number, + nodeId: string, + info: unknown[] + ][] + ][], Context>; + cluster(...args: [ + subcommand: "DELSLOTS", + slots: (number | string)[] + ]): Result<[ + startSlotRange: number, + endSlotRange: number, + ...nodes: [ + host: string, + port: number, + nodeId: string, + info: unknown[] + ][] + ][], Context>; + cluster(...args: [ + subcommand: "DELSLOTSRANGE", + ...startSlotEndSlots: (string | number)[], + callback: Callback<[ + startSlotRange: number, + endSlotRange: number, + ...nodes: [ + host: string, + port: number, + nodeId: string, + info: unknown[] + ][] + ][]> + ]): Result<[ + startSlotRange: number, + endSlotRange: number, + ...nodes: [ + host: string, + port: number, + nodeId: string, + info: unknown[] + ][] + ][], Context>; + cluster(...args: [ + subcommand: "DELSLOTSRANGE", + ...startSlotEndSlots: (string | number)[] + ]): Result<[ + startSlotRange: number, + endSlotRange: number, + ...nodes: [ + host: string, + port: number, + nodeId: string, + info: unknown[] + ][] + ][], Context>; + cluster(subcommand: "FAILOVER", callback?: Callback<"OK">): Result<"OK", Context>; + cluster(subcommand: "FAILOVER", force: "FORCE", callback?: Callback<"OK">): Result<"OK", Context>; + cluster(subcommand: "FAILOVER", takeover: "TAKEOVER", callback?: Callback<"OK">): Result<"OK", Context>; + cluster(subcommand: "FLUSHSLOTS", callback?: Callback<[ + startSlotRange: number, + endSlotRange: number, + ...nodes: [ + host: string, + port: number, + nodeId: string, + info: unknown[] + ][] + ][]>): Result<[ + startSlotRange: number, + endSlotRange: number, + ...nodes: [ + host: string, + port: number, + nodeId: string, + info: unknown[] + ][] + ][], Context>; + cluster(subcommand: "FORGET", nodeId: string | Buffer | number, callback?: Callback<"OK">): Result<"OK", Context>; + cluster(subcommand: "GETKEYSINSLOT", slot: number | string, count: number | string, callback?: Callback<string[]>): Result<string[], Context>; + cluster(subcommand: "HELP", callback?: Callback<unknown>): Result<unknown, Context>; + cluster(subcommand: "INFO", callback?: Callback<string>): Result<string, Context>; + cluster(subcommand: "KEYSLOT", key: string | Buffer, callback?: Callback<number>): Result<number, Context>; + cluster(subcommand: "LINKS", callback?: Callback<unknown[]>): Result<unknown[], Context>; + cluster(subcommand: "MEET", ip: string | Buffer, port: number | string, callback?: Callback<"OK">): Result<"OK", Context>; + cluster(subcommand: "MYID", callback?: Callback<string>): Result<string, Context>; + cluster(subcommand: "NODES", callback?: Callback<unknown>): Result<unknown, Context>; + cluster(subcommand: "REPLICAS", nodeId: string | Buffer | number, callback?: Callback<unknown>): Result<unknown, Context>; + cluster(subcommand: "REPLICATE", nodeId: string | Buffer | number, callback?: Callback<"OK">): Result<"OK", Context>; + cluster(subcommand: "RESET", callback?: Callback<"OK">): Result<"OK", Context>; + cluster(subcommand: "RESET", hard: "HARD", callback?: Callback<"OK">): Result<"OK", Context>; + cluster(subcommand: "RESET", soft: "SOFT", callback?: Callback<"OK">): Result<"OK", Context>; + cluster(subcommand: "SAVECONFIG", callback?: Callback<"OK">): Result<"OK", Context>; + cluster(subcommand: "SET-CONFIG-EPOCH", configEpoch: number | string, callback?: Callback<"OK">): Result<"OK", Context>; + cluster(subcommand: "SETSLOT", slot: number | string, nodeIdToken: "IMPORTING", nodeId: string | Buffer | number, callback?: Callback<"OK">): Result<"OK", Context>; + cluster(subcommand: "SETSLOT", slot: number | string, nodeIdToken: "MIGRATING", nodeId: string | Buffer | number, callback?: Callback<"OK">): Result<"OK", Context>; + cluster(subcommand: "SETSLOT", slot: number | string, nodeIdToken: "NODE", nodeId: string | Buffer | number, callback?: Callback<"OK">): Result<"OK", Context>; + cluster(subcommand: "SETSLOT", slot: number | string, stable: "STABLE", callback?: Callback<"OK">): Result<"OK", Context>; + cluster(subcommand: "SHARDS", callback?: Callback<unknown>): Result<unknown, Context>; + cluster(subcommand: "SLAVES", nodeId: string | Buffer | number, callback?: Callback<unknown>): Result<unknown, Context>; + cluster(subcommand: "SLOTS", callback?: Callback<[ + startSlotRange: number, + endSlotRange: number, + ...nodes: [ + host: string, + port: number, + nodeId: string, + info: unknown[] + ][] + ][]>): Result<[ + startSlotRange: number, + endSlotRange: number, + ...nodes: [ + host: string, + port: number, + nodeId: string, + info: unknown[] + ][] + ][], Context>; + command(subcommand: "COUNT", callback?: Callback<unknown[]>): Result<unknown[], Context>; + command(subcommand: "DOCS", callback?: Callback<unknown[]>): Result<unknown[], Context>; + command(...args: [ + subcommand: "DOCS", + ...commandNames: (string | Buffer)[], + callback: Callback<unknown[]> + ]): Result<unknown[], Context>; + command(...args: [ + subcommand: "DOCS", + ...commandNames: (string | Buffer)[] + ]): Result<unknown[], Context>; + command(subcommand: "GETKEYS", callback?: Callback<unknown[]>): Result<unknown[], Context>; + command(subcommand: "GETKEYSANDFLAGS", callback?: Callback<unknown[]>): Result<unknown[], Context>; + command(subcommand: "HELP", callback?: Callback<unknown[]>): Result<unknown[], Context>; + command(subcommand: "INFO", callback?: Callback<unknown[]>): Result<unknown[], Context>; + command(...args: [ + subcommand: "INFO", + ...commandNames: (string | Buffer)[], + callback: Callback<unknown[]> + ]): Result<unknown[], Context>; + command(...args: [ + subcommand: "INFO", + ...commandNames: (string | Buffer)[] + ]): Result<unknown[], Context>; + command(subcommand: "LIST", callback?: Callback<unknown[]>): Result<unknown[], Context>; + command(subcommand: "LIST", filterby: "FILTERBY", moduleNameToken: "MODULE", moduleName: string | Buffer, callback?: Callback<unknown[]>): Result<unknown[], Context>; + command(subcommand: "LIST", filterby: "FILTERBY", categoryToken: "ACLCAT", category: string | Buffer, callback?: Callback<unknown[]>): Result<unknown[], Context>; + command(subcommand: "LIST", filterby: "FILTERBY", patternToken: "PATTERN", pattern: string, callback?: Callback<unknown[]>): Result<unknown[], Context>; + config(...args: [ + subcommand: "GET", + ...parameters: (string | Buffer)[], + callback: Callback<unknown> + ]): Result<unknown, Context>; + config(...args: [ + subcommand: "GET", + ...parameters: (string | Buffer)[] + ]): Result<unknown, Context>; + config(subcommand: "HELP", callback?: Callback<unknown>): Result<unknown, Context>; + config(subcommand: "RESETSTAT", callback?: Callback<unknown>): Result<unknown, Context>; + config(subcommand: "REWRITE", callback?: Callback<unknown>): Result<unknown, Context>; + config(...args: [ + subcommand: "SET", + ...parameterValues: (string | Buffer | number)[], + callback: Callback<unknown> + ]): Result<unknown, Context>; + config(...args: [ + subcommand: "SET", + ...parameterValues: (string | Buffer | number)[] + ]): Result<unknown, Context>; + copy(source: RedisKey, destination: RedisKey, callback?: Callback<number>): Result<number, Context>; + copy(source: RedisKey, destination: RedisKey, replace: "REPLACE", callback?: Callback<number>): Result<number, Context>; + copy(source: RedisKey, destination: RedisKey, destinationDbToken: "DB", destinationDb: number | string, callback?: Callback<number>): Result<number, Context>; + copy(source: RedisKey, destination: RedisKey, destinationDbToken: "DB", destinationDb: number | string, replace: "REPLACE", callback?: Callback<number>): Result<number, Context>; + dbsize(callback?: Callback<number>): Result<number, Context>; + debug(subcommand: string, callback?: Callback<unknown>): Result<unknown, Context>; + debug(...args: [ + subcommand: string, + ...args: (string | Buffer | number)[], + callback: Callback<unknown> + ]): Result<unknown, Context>; + debug(...args: [ + subcommand: string, + ...args: (string | Buffer | number)[] + ]): Result<unknown, Context>; + decr(key: RedisKey, callback?: Callback<number>): Result<number, Context>; + decrby(key: RedisKey, decrement: number | string, callback?: Callback<number>): Result<number, Context>; + del(...args: [ + ...keys: RedisKey[], + callback: Callback<number> + ]): Result<number, Context>; + del(...args: [ + keys: RedisKey[], + callback: Callback<number> + ]): Result<number, Context>; + del(...args: [ + ...keys: RedisKey[] + ]): Result<number, Context>; + del(...args: [ + keys: RedisKey[] + ]): Result<number, Context>; + discard(callback?: Callback<"OK">): Result<"OK", Context>; + dump(key: RedisKey, callback?: Callback<string>): Result<string, Context>; + dumpBuffer(key: RedisKey, callback?: Callback<Buffer>): Result<Buffer, Context>; + echo(message: string | Buffer, callback?: Callback<string>): Result<string, Context>; + echoBuffer(message: string | Buffer, callback?: Callback<Buffer>): Result<Buffer, Context>; + eval(script: string | Buffer, numkeys: number | string, callback?: Callback<unknown>): Result<unknown, Context>; + eval(...args: [ + script: string | Buffer, + numkeys: number | string, + ...args: (string | Buffer | number)[], + callback: Callback<unknown> + ]): Result<unknown, Context>; + eval(...args: [ + script: string | Buffer, + numkeys: number | string, + ...args: (string | Buffer | number)[] + ]): Result<unknown, Context>; + eval(...args: [ + script: string | Buffer, + numkeys: number | string, + ...keys: RedisKey[], + callback: Callback<unknown> + ]): Result<unknown, Context>; + eval(...args: [ + script: string | Buffer, + numkeys: number | string, + keys: RedisKey[], + callback: Callback<unknown> + ]): Result<unknown, Context>; + eval(...args: [ + script: string | Buffer, + numkeys: number | string, + ...keys: RedisKey[] + ]): Result<unknown, Context>; + eval(...args: [ + script: string | Buffer, + numkeys: number | string, + keys: RedisKey[] + ]): Result<unknown, Context>; + eval(...args: [ + script: string | Buffer, + numkeys: number | string, + ...args: RedisValue[], + callback: Callback<unknown> + ]): Result<unknown, Context>; + eval(...args: [ + script: string | Buffer, + numkeys: number | string, + ...args: RedisValue[] + ]): Result<unknown, Context>; + eval_ro(...args: [ + script: string | Buffer, + numkeys: number | string, + ...args: RedisValue[], + callback: Callback<unknown> + ]): Result<unknown, Context>; + eval_ro(...args: [ + script: string | Buffer, + numkeys: number | string, + ...args: RedisValue[] + ]): Result<unknown, Context>; + evalsha(sha1: string | Buffer, numkeys: number | string, callback?: Callback<unknown>): Result<unknown, Context>; + evalsha(...args: [ + sha1: string | Buffer, + numkeys: number | string, + ...args: (string | Buffer | number)[], + callback: Callback<unknown> + ]): Result<unknown, Context>; + evalsha(...args: [ + sha1: string | Buffer, + numkeys: number | string, + ...args: (string | Buffer | number)[] + ]): Result<unknown, Context>; + evalsha(...args: [ + sha1: string | Buffer, + numkeys: number | string, + ...keys: RedisKey[], + callback: Callback<unknown> + ]): Result<unknown, Context>; + evalsha(...args: [ + sha1: string | Buffer, + numkeys: number | string, + keys: RedisKey[], + callback: Callback<unknown> + ]): Result<unknown, Context>; + evalsha(...args: [ + sha1: string | Buffer, + numkeys: number | string, + ...keys: RedisKey[] + ]): Result<unknown, Context>; + evalsha(...args: [ + sha1: string | Buffer, + numkeys: number | string, + keys: RedisKey[] + ]): Result<unknown, Context>; + evalsha(...args: [ + sha1: string | Buffer, + numkeys: number | string, + ...args: RedisValue[], + callback: Callback<unknown> + ]): Result<unknown, Context>; + evalsha(...args: [ + sha1: string | Buffer, + numkeys: number | string, + ...args: RedisValue[] + ]): Result<unknown, Context>; + evalsha_ro(...args: [ + sha1: string | Buffer, + numkeys: number | string, + ...args: RedisValue[], + callback: Callback<unknown> + ]): Result<unknown, Context>; + evalsha_ro(...args: [ + sha1: string | Buffer, + numkeys: number | string, + ...args: RedisValue[] + ]): Result<unknown, Context>; + exec(callback?: Callback<[ + error: Error | null, + result: unknown + ][] | null>): Promise<[ + error: Error | null, + result: unknown + ][] | null>; + exists(...args: [ + ...keys: RedisKey[], + callback: Callback<number> + ]): Result<number, Context>; + exists(...args: [ + keys: RedisKey[], + callback: Callback<number> + ]): Result<number, Context>; + exists(...args: [ + ...keys: RedisKey[] + ]): Result<number, Context>; + exists(...args: [ + keys: RedisKey[] + ]): Result<number, Context>; + expire(key: RedisKey, seconds: number | string, callback?: Callback<number>): Result<number, Context>; + expire(key: RedisKey, seconds: number | string, nx: "NX", callback?: Callback<number>): Result<number, Context>; + expire(key: RedisKey, seconds: number | string, xx: "XX", callback?: Callback<number>): Result<number, Context>; + expire(key: RedisKey, seconds: number | string, gt: "GT", callback?: Callback<number>): Result<number, Context>; + expire(key: RedisKey, seconds: number | string, lt: "LT", callback?: Callback<number>): Result<number, Context>; + expireat(key: RedisKey, unixTimeSeconds: number | string, callback?: Callback<number>): Result<number, Context>; + expireat(key: RedisKey, unixTimeSeconds: number | string, nx: "NX", callback?: Callback<number>): Result<number, Context>; + expireat(key: RedisKey, unixTimeSeconds: number | string, xx: "XX", callback?: Callback<number>): Result<number, Context>; + expireat(key: RedisKey, unixTimeSeconds: number | string, gt: "GT", callback?: Callback<number>): Result<number, Context>; + expireat(key: RedisKey, unixTimeSeconds: number | string, lt: "LT", callback?: Callback<number>): Result<number, Context>; + expiretime(key: RedisKey, callback?: Callback<number>): Result<number, Context>; + failover(callback?: Callback<"OK">): Result<"OK", Context>; + failover(millisecondsToken: "TIMEOUT", milliseconds: number | string, callback?: Callback<"OK">): Result<"OK", Context>; + failover(abort: "ABORT", callback?: Callback<"OK">): Result<"OK", Context>; + failover(abort: "ABORT", millisecondsToken: "TIMEOUT", milliseconds: number | string, callback?: Callback<"OK">): Result<"OK", Context>; + failover(targetToken: "TO", host: string | Buffer, port: number | string, callback?: Callback<"OK">): Result<"OK", Context>; + failover(targetToken: "TO", host: string | Buffer, port: number | string, millisecondsToken: "TIMEOUT", milliseconds: number | string, callback?: Callback<"OK">): Result<"OK", Context>; + failover(targetToken: "TO", host: string | Buffer, port: number | string, abort: "ABORT", callback?: Callback<"OK">): Result<"OK", Context>; + failover(targetToken: "TO", host: string | Buffer, port: number | string, abort: "ABORT", millisecondsToken: "TIMEOUT", milliseconds: number | string, callback?: Callback<"OK">): Result<"OK", Context>; + failover(targetToken: "TO", host: string | Buffer, port: number | string, force: "FORCE", callback?: Callback<"OK">): Result<"OK", Context>; + failover(targetToken: "TO", host: string | Buffer, port: number | string, force: "FORCE", millisecondsToken: "TIMEOUT", milliseconds: number | string, callback?: Callback<"OK">): Result<"OK", Context>; + failover(targetToken: "TO", host: string | Buffer, port: number | string, force: "FORCE", abort: "ABORT", callback?: Callback<"OK">): Result<"OK", Context>; + failover(targetToken: "TO", host: string | Buffer, port: number | string, force: "FORCE", abort: "ABORT", millisecondsToken: "TIMEOUT", milliseconds: number | string, callback?: Callback<"OK">): Result<"OK", Context>; + fcall(...args: [ + function: string | Buffer, + numkeys: number | string, + ...args: RedisValue[], + callback: Callback<unknown> + ]): Result<unknown, Context>; + fcall(...args: [ + function: string | Buffer, + numkeys: number | string, + ...args: RedisValue[] + ]): Result<unknown, Context>; + fcall_ro(...args: [ + function: string | Buffer, + numkeys: number | string, + ...args: RedisValue[], + callback: Callback<unknown> + ]): Result<unknown, Context>; + fcall_ro(...args: [ + function: string | Buffer, + numkeys: number | string, + ...args: RedisValue[] + ]): Result<unknown, Context>; + flushall(callback?: Callback<"OK">): Result<"OK", Context>; + flushall(async: "ASYNC", callback?: Callback<"OK">): Result<"OK", Context>; + flushall(sync: "SYNC", callback?: Callback<"OK">): Result<"OK", Context>; + flushdb(callback?: Callback<"OK">): Result<"OK", Context>; + flushdb(async: "ASYNC", callback?: Callback<"OK">): Result<"OK", Context>; + flushdb(sync: "SYNC", callback?: Callback<"OK">): Result<"OK", Context>; + function(subcommand: "DELETE", libraryName: string | Buffer, callback?: Callback<string>): Result<string, Context>; + functionBuffer(subcommand: "DELETE", libraryName: string | Buffer, callback?: Callback<Buffer>): Result<Buffer, Context>; + function(subcommand: "DUMP", callback?: Callback<string>): Result<string, Context>; + functionBuffer(subcommand: "DUMP", callback?: Callback<Buffer>): Result<Buffer, Context>; + function(subcommand: "FLUSH", callback?: Callback<string>): Result<string, Context>; + functionBuffer(subcommand: "FLUSH", callback?: Callback<Buffer>): Result<Buffer, Context>; + function(subcommand: "FLUSH", async: "ASYNC", callback?: Callback<string>): Result<string, Context>; + functionBuffer(subcommand: "FLUSH", async: "ASYNC", callback?: Callback<Buffer>): Result<Buffer, Context>; + function(subcommand: "FLUSH", sync: "SYNC", callback?: Callback<string>): Result<string, Context>; + functionBuffer(subcommand: "FLUSH", sync: "SYNC", callback?: Callback<Buffer>): Result<Buffer, Context>; + function(subcommand: "HELP", callback?: Callback<unknown>): Result<unknown, Context>; + function(subcommand: "KILL", callback?: Callback<string>): Result<string, Context>; + functionBuffer(subcommand: "KILL", callback?: Callback<Buffer>): Result<Buffer, Context>; + function(subcommand: "LIST", callback?: Callback<unknown[]>): Result<unknown[], Context>; + function(subcommand: "LIST", withcode: "WITHCODE", callback?: Callback<unknown[]>): Result<unknown[], Context>; + function(subcommand: "LIST", libraryNamePatternToken: "LIBRARYNAME", libraryNamePattern: string | Buffer, callback?: Callback<unknown[]>): Result<unknown[], Context>; + function(subcommand: "LIST", libraryNamePatternToken: "LIBRARYNAME", libraryNamePattern: string | Buffer, withcode: "WITHCODE", callback?: Callback<unknown[]>): Result<unknown[], Context>; + function(subcommand: "LOAD", functionCode: string | Buffer, callback?: Callback<string>): Result<string, Context>; + functionBuffer(subcommand: "LOAD", functionCode: string | Buffer, callback?: Callback<Buffer>): Result<Buffer, Context>; + function(subcommand: "LOAD", replace: "REPLACE", functionCode: string | Buffer, callback?: Callback<string>): Result<string, Context>; + functionBuffer(subcommand: "LOAD", replace: "REPLACE", functionCode: string | Buffer, callback?: Callback<Buffer>): Result<Buffer, Context>; + function(subcommand: "RESTORE", serializedValue: string | Buffer | number, callback?: Callback<string>): Result<string, Context>; + functionBuffer(subcommand: "RESTORE", serializedValue: string | Buffer | number, callback?: Callback<Buffer>): Result<Buffer, Context>; + function(subcommand: "RESTORE", serializedValue: string | Buffer | number, flush: "FLUSH", callback?: Callback<string>): Result<string, Context>; + functionBuffer(subcommand: "RESTORE", serializedValue: string | Buffer | number, flush: "FLUSH", callback?: Callback<Buffer>): Result<Buffer, Context>; + function(subcommand: "RESTORE", serializedValue: string | Buffer | number, append: "APPEND", callback?: Callback<string>): Result<string, Context>; + functionBuffer(subcommand: "RESTORE", serializedValue: string | Buffer | number, append: "APPEND", callback?: Callback<Buffer>): Result<Buffer, Context>; + function(subcommand: "RESTORE", serializedValue: string | Buffer | number, replace: "REPLACE", callback?: Callback<string>): Result<string, Context>; + functionBuffer(subcommand: "RESTORE", serializedValue: string | Buffer | number, replace: "REPLACE", callback?: Callback<Buffer>): Result<Buffer, Context>; + function(subcommand: "STATS", callback?: Callback<unknown>): Result<unknown, Context>; + geoadd(...args: [ + key: RedisKey, + ...longitudeLatitudeMembers: (string | Buffer | number)[], + callback: Callback<number> + ]): Result<number, Context>; + geoadd(...args: [ + key: RedisKey, + ...longitudeLatitudeMembers: (string | Buffer | number)[] + ]): Result<number, Context>; + geoadd(...args: [ + key: RedisKey, + ch: "CH", + ...longitudeLatitudeMembers: (string | Buffer | number)[], + callback: Callback<number> + ]): Result<number, Context>; + geoadd(...args: [ + key: RedisKey, + ch: "CH", + ...longitudeLatitudeMembers: (string | Buffer | number)[] + ]): Result<number, Context>; + geoadd(...args: [ + key: RedisKey, + nx: "NX", + ...longitudeLatitudeMembers: (string | Buffer | number)[], + callback: Callback<number> + ]): Result<number, Context>; + geoadd(...args: [ + key: RedisKey, + nx: "NX", + ...longitudeLatitudeMembers: (string | Buffer | number)[] + ]): Result<number, Context>; + geoadd(...args: [ + key: RedisKey, + nx: "NX", + ch: "CH", + ...longitudeLatitudeMembers: (string | Buffer | number)[], + callback: Callback<number> + ]): Result<number, Context>; + geoadd(...args: [ + key: RedisKey, + nx: "NX", + ch: "CH", + ...longitudeLatitudeMembers: (string | Buffer | number)[] + ]): Result<number, Context>; + geoadd(...args: [ + key: RedisKey, + xx: "XX", + ...longitudeLatitudeMembers: (string | Buffer | number)[], + callback: Callback<number> + ]): Result<number, Context>; + geoadd(...args: [ + key: RedisKey, + xx: "XX", + ...longitudeLatitudeMembers: (string | Buffer | number)[] + ]): Result<number, Context>; + geoadd(...args: [ + key: RedisKey, + xx: "XX", + ch: "CH", + ...longitudeLatitudeMembers: (string | Buffer | number)[], + callback: Callback<number> + ]): Result<number, Context>; + geoadd(...args: [ + key: RedisKey, + xx: "XX", + ch: "CH", + ...longitudeLatitudeMembers: (string | Buffer | number)[] + ]): Result<number, Context>; + geodist(key: RedisKey, member1: string | Buffer | number, member2: string | Buffer | number, callback?: Callback<string | null>): Result<string | null, Context>; + geodistBuffer(key: RedisKey, member1: string | Buffer | number, member2: string | Buffer | number, callback?: Callback<Buffer | null>): Result<Buffer | null, Context>; + geodist(key: RedisKey, member1: string | Buffer | number, member2: string | Buffer | number, m: "M", callback?: Callback<string | null>): Result<string | null, Context>; + geodistBuffer(key: RedisKey, member1: string | Buffer | number, member2: string | Buffer | number, m: "M", callback?: Callback<Buffer | null>): Result<Buffer | null, Context>; + geodist(key: RedisKey, member1: string | Buffer | number, member2: string | Buffer | number, km: "KM", callback?: Callback<string | null>): Result<string | null, Context>; + geodistBuffer(key: RedisKey, member1: string | Buffer | number, member2: string | Buffer | number, km: "KM", callback?: Callback<Buffer | null>): Result<Buffer | null, Context>; + geodist(key: RedisKey, member1: string | Buffer | number, member2: string | Buffer | number, ft: "FT", callback?: Callback<string | null>): Result<string | null, Context>; + geodistBuffer(key: RedisKey, member1: string | Buffer | number, member2: string | Buffer | number, ft: "FT", callback?: Callback<Buffer | null>): Result<Buffer | null, Context>; + geodist(key: RedisKey, member1: string | Buffer | number, member2: string | Buffer | number, mi: "MI", callback?: Callback<string | null>): Result<string | null, Context>; + geodistBuffer(key: RedisKey, member1: string | Buffer | number, member2: string | Buffer | number, mi: "MI", callback?: Callback<Buffer | null>): Result<Buffer | null, Context>; + geohash(...args: [ + key: RedisKey, + ...members: (string | Buffer | number)[], + callback: Callback<string[]> + ]): Result<string[], Context>; + geohashBuffer(...args: [ + key: RedisKey, + ...members: (string | Buffer | number)[], + callback: Callback<Buffer[]> + ]): Result<Buffer[], Context>; + geohash(...args: [ + key: RedisKey, + members: (string | Buffer | number)[], + callback: Callback<string[]> + ]): Result<string[], Context>; + geohashBuffer(...args: [ + key: RedisKey, + members: (string | Buffer | number)[], + callback: Callback<Buffer[]> + ]): Result<Buffer[], Context>; + geohash(...args: [ + key: RedisKey, + ...members: (string | Buffer | number)[] + ]): Result<string[], Context>; + geohashBuffer(...args: [ + key: RedisKey, + ...members: (string | Buffer | number)[] + ]): Result<Buffer[], Context>; + geohash(...args: [ + key: RedisKey, + members: (string | Buffer | number)[] + ]): Result<string[], Context>; + geohashBuffer(...args: [ + key: RedisKey, + members: (string | Buffer | number)[] + ]): Result<Buffer[], Context>; + geopos(...args: [ + key: RedisKey, + ...members: (string | Buffer | number)[], + callback: Callback<([ + longitude: string, + latitude: string + ] | null)[]> + ]): Result<([ + longitude: string, + latitude: string + ] | null)[], Context>; + geopos(...args: [ + key: RedisKey, + members: (string | Buffer | number)[], + callback: Callback<([ + longitude: string, + latitude: string + ] | null)[]> + ]): Result<([ + longitude: string, + latitude: string + ] | null)[], Context>; + geopos(...args: [ + key: RedisKey, + ...members: (string | Buffer | number)[] + ]): Result<([ + longitude: string, + latitude: string + ] | null)[], Context>; + geopos(...args: [ + key: RedisKey, + members: (string | Buffer | number)[] + ]): Result<([ + longitude: string, + latitude: string + ] | null)[], Context>; + georadius(...args: [ + key: RedisKey, + longitude: number | string, + latitude: number | string, + radius: number | string, + ...args: RedisValue[], + callback: Callback<unknown[]> + ]): Result<unknown[], Context>; + georadius(...args: [ + key: RedisKey, + longitude: number | string, + latitude: number | string, + radius: number | string, + ...args: RedisValue[] + ]): Result<unknown[], Context>; + georadius_ro(...args: [ + key: RedisKey, + longitude: number | string, + latitude: number | string, + radius: number | string, + ...args: RedisValue[], + callback: Callback<unknown> + ]): Result<unknown, Context>; + georadius_ro(...args: [ + key: RedisKey, + longitude: number | string, + latitude: number | string, + radius: number | string, + ...args: RedisValue[] + ]): Result<unknown, Context>; + georadiusbymember(...args: [ + key: RedisKey, + member: string | Buffer | number, + radius: number | string, + ...args: RedisValue[], + callback: Callback<unknown> + ]): Result<unknown, Context>; + georadiusbymember(...args: [ + key: RedisKey, + member: string | Buffer | number, + radius: number | string, + ...args: RedisValue[] + ]): Result<unknown, Context>; + georadiusbymember_ro(...args: [ + key: RedisKey, + member: string | Buffer | number, + radius: number | string, + ...args: RedisValue[], + callback: Callback<unknown> + ]): Result<unknown, Context>; + georadiusbymember_ro(...args: [ + key: RedisKey, + member: string | Buffer | number, + radius: number | string, + ...args: RedisValue[] + ]): Result<unknown, Context>; + geosearch(...args: [ + key: RedisKey, + ...args: RedisValue[], + callback: Callback<unknown[]> + ]): Result<unknown[], Context>; + geosearch(...args: [ + key: RedisKey, + ...args: RedisValue[] + ]): Result<unknown[], Context>; + geosearchstore(...args: [ + destination: RedisKey, + source: RedisKey, + ...args: RedisValue[], + callback: Callback<number> + ]): Result<number, Context>; + geosearchstore(...args: [ + destination: RedisKey, + source: RedisKey, + ...args: RedisValue[] + ]): Result<number, Context>; + get(key: RedisKey, callback?: Callback<string | null>): Result<string | null, Context>; + getBuffer(key: RedisKey, callback?: Callback<Buffer | null>): Result<Buffer | null, Context>; + getbit(key: RedisKey, offset: number | string, callback?: Callback<number>): Result<number, Context>; + getdel(key: RedisKey, callback?: Callback<string | null>): Result<string | null, Context>; + getdelBuffer(key: RedisKey, callback?: Callback<Buffer | null>): Result<Buffer | null, Context>; + getex(key: RedisKey, callback?: Callback<string | null>): Result<string | null, Context>; + getexBuffer(key: RedisKey, callback?: Callback<Buffer | null>): Result<Buffer | null, Context>; + getex(key: RedisKey, secondsToken: "EX", seconds: number | string, callback?: Callback<string | null>): Result<string | null, Context>; + getexBuffer(key: RedisKey, secondsToken: "EX", seconds: number | string, callback?: Callback<Buffer | null>): Result<Buffer | null, Context>; + getex(key: RedisKey, millisecondsToken: "PX", milliseconds: number | string, callback?: Callback<string | null>): Result<string | null, Context>; + getexBuffer(key: RedisKey, millisecondsToken: "PX", milliseconds: number | string, callback?: Callback<Buffer | null>): Result<Buffer | null, Context>; + getex(key: RedisKey, unixTimeSecondsToken: "EXAT", unixTimeSeconds: number | string, callback?: Callback<string | null>): Result<string | null, Context>; + getexBuffer(key: RedisKey, unixTimeSecondsToken: "EXAT", unixTimeSeconds: number | string, callback?: Callback<Buffer | null>): Result<Buffer | null, Context>; + getex(key: RedisKey, unixTimeMillisecondsToken: "PXAT", unixTimeMilliseconds: number | string, callback?: Callback<string | null>): Result<string | null, Context>; + getexBuffer(key: RedisKey, unixTimeMillisecondsToken: "PXAT", unixTimeMilliseconds: number | string, callback?: Callback<Buffer | null>): Result<Buffer | null, Context>; + getex(key: RedisKey, persist: "PERSIST", callback?: Callback<string | null>): Result<string | null, Context>; + getexBuffer(key: RedisKey, persist: "PERSIST", callback?: Callback<Buffer | null>): Result<Buffer | null, Context>; + getrange(key: RedisKey, start: number | string, end: number | string, callback?: Callback<string>): Result<string, Context>; + getrangeBuffer(key: RedisKey, start: number | string, end: number | string, callback?: Callback<Buffer>): Result<Buffer, Context>; + getset(key: RedisKey, value: string | Buffer | number, callback?: Callback<string | null>): Result<string | null, Context>; + getsetBuffer(key: RedisKey, value: string | Buffer | number, callback?: Callback<Buffer | null>): Result<Buffer | null, Context>; + hdel(...args: [ + key: RedisKey, + ...fields: (string | Buffer)[], + callback: Callback<number> + ]): Result<number, Context>; + hdel(...args: [ + key: RedisKey, + ...fields: (string | Buffer)[] + ]): Result<number, Context>; + hello(callback?: Callback<unknown[]>): Result<unknown[], Context>; + hello(protover: number | string, callback?: Callback<unknown[]>): Result<unknown[], Context>; + hello(protover: number | string, clientnameToken: "SETNAME", clientname: string | Buffer, callback?: Callback<unknown[]>): Result<unknown[], Context>; + hello(protover: number | string, usernamePasswordToken: "AUTH", username: string | Buffer, password: string | Buffer, callback?: Callback<unknown[]>): Result<unknown[], Context>; + hello(protover: number | string, usernamePasswordToken: "AUTH", username: string | Buffer, password: string | Buffer, clientnameToken: "SETNAME", clientname: string | Buffer, callback?: Callback<unknown[]>): Result<unknown[], Context>; + hexists(key: RedisKey, field: string | Buffer, callback?: Callback<number>): Result<number, Context>; + hexpire(...args: [ + key: RedisKey, + seconds: number | string, + fieldsToken: 'FIELDS', + numfields: number | string, + ...fields: (string | Buffer)[], + callback: Callback<number[]> + ]): Result<number[], Context>; + hexpire(...args: [ + key: RedisKey, + seconds: number | string, + fieldsToken: 'FIELDS', + numfields: number | string, + ...fields: (string | Buffer)[] + ]): Result<number[], Context>; + hexpire(...args: [ + key: RedisKey, + seconds: number | string, + nx: 'NX', + fieldsToken: 'FIELDS', + numfields: number | string, + ...fields: (string | Buffer)[], + callback: Callback<number[]> + ]): Result<number[], Context>; + hexpire(...args: [ + key: RedisKey, + seconds: number | string, + nx: 'NX', + fieldsToken: 'FIELDS', + numfields: number | string, + ...fields: (string | Buffer)[] + ]): Result<number[], Context>; + hexpire(...args: [ + key: RedisKey, + seconds: number | string, + xx: 'XX', + fieldsToken: 'FIELDS', + numfields: number | string, + ...fields: (string | Buffer)[], + callback: Callback<number[]> + ]): Result<number[], Context>; + hexpire(...args: [ + key: RedisKey, + seconds: number | string, + xx: 'XX', + fieldsToken: 'FIELDS', + numfields: number | string, + ...fields: (string | Buffer)[] + ]): Result<number[], Context>; + hexpire(...args: [ + key: RedisKey, + seconds: number | string, + gt: 'GT', + fieldsToken: 'FIELDS', + numfields: number | string, + ...fields: (string | Buffer)[], + callback: Callback<number[]> + ]): Result<number[], Context>; + hexpire(...args: [ + key: RedisKey, + seconds: number | string, + gt: 'GT', + fieldsToken: 'FIELDS', + numfields: number | string, + ...fields: (string | Buffer)[] + ]): Result<number[], Context>; + hexpire(...args: [ + key: RedisKey, + seconds: number | string, + lt: 'LT', + fieldsToken: 'FIELDS', + numfields: number | string, + ...fields: (string | Buffer)[], + callback: Callback<number[]> + ]): Result<number[], Context>; + hexpire(...args: [ + key: RedisKey, + seconds: number | string, + lt: 'LT', + fieldsToken: 'FIELDS', + numfields: number | string, + ...fields: (string | Buffer)[] + ]): Result<number[], Context>; + hexpireat(...args: [ + key: RedisKey, + unixTimeSeconds: number | string, + fieldsToken: 'FIELDS', + numfields: number | string, + ...fields: (string | Buffer)[], + callback: Callback<number[]> + ]): Result<number[], Context>; + hexpireat(...args: [ + key: RedisKey, + unixTimeSeconds: number | string, + fieldsToken: 'FIELDS', + numfields: number | string, + ...fields: (string | Buffer)[] + ]): Result<number[], Context>; + hexpireat(...args: [ + key: RedisKey, + unixTimeSeconds: number | string, + nx: 'NX', + fieldsToken: 'FIELDS', + numfields: number | string, + ...fields: (string | Buffer)[], + callback: Callback<number[]> + ]): Result<number[], Context>; + hexpireat(...args: [ + key: RedisKey, + unixTimeSeconds: number | string, + nx: 'NX', + fieldsToken: 'FIELDS', + numfields: number | string, + ...fields: (string | Buffer)[] + ]): Result<number[], Context>; + hexpireat(...args: [ + key: RedisKey, + unixTimeSeconds: number | string, + xx: 'XX', + fieldsToken: 'FIELDS', + numfields: number | string, + ...fields: (string | Buffer)[], + callback: Callback<number[]> + ]): Result<number[], Context>; + hexpireat(...args: [ + key: RedisKey, + unixTimeSeconds: number | string, + xx: 'XX', + fieldsToken: 'FIELDS', + numfields: number | string, + ...fields: (string | Buffer)[] + ]): Result<number[], Context>; + hexpireat(...args: [ + key: RedisKey, + unixTimeSeconds: number | string, + gt: 'GT', + fieldsToken: 'FIELDS', + numfields: number | string, + ...fields: (string | Buffer)[], + callback: Callback<number[]> + ]): Result<number[], Context>; + hexpireat(...args: [ + key: RedisKey, + unixTimeSeconds: number | string, + gt: 'GT', + fieldsToken: 'FIELDS', + numfields: number | string, + ...fields: (string | Buffer)[] + ]): Result<number[], Context>; + hexpireat(...args: [ + key: RedisKey, + unixTimeSeconds: number | string, + lt: 'LT', + fieldsToken: 'FIELDS', + numfields: number | string, + ...fields: (string | Buffer)[], + callback: Callback<number[]> + ]): Result<number[], Context>; + hexpireat(...args: [ + key: RedisKey, + unixTimeSeconds: number | string, + lt: 'LT', + fieldsToken: 'FIELDS', + numfields: number | string, + ...fields: (string | Buffer)[] + ]): Result<number[], Context>; + hexpiretime(...args: [ + key: RedisKey, + fieldsToken: 'FIELDS', + numfields: number | string, + ...fields: (string | Buffer)[], + callback: Callback<number[]> + ]): Result<number[], Context>; + hexpiretime(...args: [ + key: RedisKey, + fieldsToken: 'FIELDS', + numfields: number | string, + ...fields: (string | Buffer)[] + ]): Result<number[], Context>; + hget(key: RedisKey, field: string | Buffer, callback?: Callback<string | null>): Result<string | null, Context>; + hgetBuffer(key: RedisKey, field: string | Buffer, callback?: Callback<Buffer | null>): Result<Buffer | null, Context>; + hgetall(key: RedisKey, callback?: Callback<Record<string, string>>): Result<Record<string, string>, Context>; + hgetallBuffer(key: RedisKey, callback?: Callback<Record<string, Buffer>>): Result<Record<string, Buffer>, Context>; + hgetdel(...args: [ + key: RedisKey, + fieldsToken: "FIELDS", + numfields: number | string, + ...fields: (string | Buffer)[], + callback: Callback<(string | null)[]> + ]): Result<(string | null)[], Context>; + hgetdelBuffer(...args: [ + key: RedisKey, + fieldsToken: "FIELDS", + numfields: number | string, + ...fields: (string | Buffer)[], + callback: Callback<(Buffer | null)[]> + ]): Result<(Buffer | null)[], Context>; + hgetdel(...args: [ + key: RedisKey, + fieldsToken: "FIELDS", + numfields: number | string, + ...fields: (string | Buffer)[] + ]): Result<(string | null)[], Context>; + hgetdelBuffer(...args: [ + key: RedisKey, + fieldsToken: "FIELDS", + numfields: number | string, + ...fields: (string | Buffer)[] + ]): Result<(Buffer | null)[], Context>; + hgetex(...args: [ + key: RedisKey, + fieldsToken: "FIELDS", + numfields: number | string, + ...fields: (string | Buffer)[], + callback: Callback<(string | null)[]> + ]): Result<(string | null)[], Context>; + hgetexBuffer(...args: [ + key: RedisKey, + fieldsToken: "FIELDS", + numfields: number | string, + ...fields: (string | Buffer)[], + callback: Callback<(Buffer | null)[]> + ]): Result<(Buffer | null)[], Context>; + hgetex(...args: [ + key: RedisKey, + fieldsToken: "FIELDS", + numfields: number | string, + ...fields: (string | Buffer)[] + ]): Result<(string | null)[], Context>; + hgetexBuffer(...args: [ + key: RedisKey, + fieldsToken: "FIELDS", + numfields: number | string, + ...fields: (string | Buffer)[] + ]): Result<(Buffer | null)[], Context>; + hgetex(...args: [ + key: RedisKey, + secondsToken: "EX", + seconds: number | string, + fieldsToken: "FIELDS", + numfields: number | string, + ...fields: (string | Buffer)[], + callback: Callback<(string | null)[]> + ]): Result<(string | null)[], Context>; + hgetexBuffer(...args: [ + key: RedisKey, + secondsToken: "EX", + seconds: number | string, + fieldsToken: "FIELDS", + numfields: number | string, + ...fields: (string | Buffer)[], + callback: Callback<(Buffer | null)[]> + ]): Result<(Buffer | null)[], Context>; + hgetex(...args: [ + key: RedisKey, + secondsToken: "EX", + seconds: number | string, + fieldsToken: "FIELDS", + numfields: number | string, + ...fields: (string | Buffer)[] + ]): Result<(string | null)[], Context>; + hgetexBuffer(...args: [ + key: RedisKey, + secondsToken: "EX", + seconds: number | string, + fieldsToken: "FIELDS", + numfields: number | string, + ...fields: (string | Buffer)[] + ]): Result<(Buffer | null)[], Context>; + hgetex(...args: [ + key: RedisKey, + millisecondsToken: "PX", + milliseconds: number | string, + fieldsToken: "FIELDS", + numfields: number | string, + ...fields: (string | Buffer)[], + callback: Callback<(string | null)[]> + ]): Result<(string | null)[], Context>; + hgetexBuffer(...args: [ + key: RedisKey, + millisecondsToken: "PX", + milliseconds: number | string, + fieldsToken: "FIELDS", + numfields: number | string, + ...fields: (string | Buffer)[], + callback: Callback<(Buffer | null)[]> + ]): Result<(Buffer | null)[], Context>; + hgetex(...args: [ + key: RedisKey, + millisecondsToken: "PX", + milliseconds: number | string, + fieldsToken: "FIELDS", + numfields: number | string, + ...fields: (string | Buffer)[] + ]): Result<(string | null)[], Context>; + hgetexBuffer(...args: [ + key: RedisKey, + millisecondsToken: "PX", + milliseconds: number | string, + fieldsToken: "FIELDS", + numfields: number | string, + ...fields: (string | Buffer)[] + ]): Result<(Buffer | null)[], Context>; + hgetex(...args: [ + key: RedisKey, + unixTimeSecondsToken: "EXAT", + unixTimeSeconds: number | string, + fieldsToken: "FIELDS", + numfields: number | string, + ...fields: (string | Buffer)[], + callback: Callback<(string | null)[]> + ]): Result<(string | null)[], Context>; + hgetexBuffer(...args: [ + key: RedisKey, + unixTimeSecondsToken: "EXAT", + unixTimeSeconds: number | string, + fieldsToken: "FIELDS", + numfields: number | string, + ...fields: (string | Buffer)[], + callback: Callback<(Buffer | null)[]> + ]): Result<(Buffer | null)[], Context>; + hgetex(...args: [ + key: RedisKey, + unixTimeSecondsToken: "EXAT", + unixTimeSeconds: number | string, + fieldsToken: "FIELDS", + numfields: number | string, + ...fields: (string | Buffer)[] + ]): Result<(string | null)[], Context>; + hgetexBuffer(...args: [ + key: RedisKey, + unixTimeSecondsToken: "EXAT", + unixTimeSeconds: number | string, + fieldsToken: "FIELDS", + numfields: number | string, + ...fields: (string | Buffer)[] + ]): Result<(Buffer | null)[], Context>; + hgetex(...args: [ + key: RedisKey, + unixTimeMillisecondsToken: "PXAT", + unixTimeMilliseconds: number | string, + fieldsToken: "FIELDS", + numfields: number | string, + ...fields: (string | Buffer)[], + callback: Callback<(string | null)[]> + ]): Result<(string | null)[], Context>; + hgetexBuffer(...args: [ + key: RedisKey, + unixTimeMillisecondsToken: "PXAT", + unixTimeMilliseconds: number | string, + fieldsToken: "FIELDS", + numfields: number | string, + ...fields: (string | Buffer)[], + callback: Callback<(Buffer | null)[]> + ]): Result<(Buffer | null)[], Context>; + hgetex(...args: [ + key: RedisKey, + unixTimeMillisecondsToken: "PXAT", + unixTimeMilliseconds: number | string, + fieldsToken: "FIELDS", + numfields: number | string, + ...fields: (string | Buffer)[] + ]): Result<(string | null)[], Context>; + hgetexBuffer(...args: [ + key: RedisKey, + unixTimeMillisecondsToken: "PXAT", + unixTimeMilliseconds: number | string, + fieldsToken: "FIELDS", + numfields: number | string, + ...fields: (string | Buffer)[] + ]): Result<(Buffer | null)[], Context>; + hgetex(...args: [ + key: RedisKey, + persist: "PERSIST", + fieldsToken: "FIELDS", + numfields: number | string, + ...fields: (string | Buffer)[], + callback: Callback<(string | null)[]> + ]): Result<(string | null)[], Context>; + hgetexBuffer(...args: [ + key: RedisKey, + persist: "PERSIST", + fieldsToken: "FIELDS", + numfields: number | string, + ...fields: (string | Buffer)[], + callback: Callback<(Buffer | null)[]> + ]): Result<(Buffer | null)[], Context>; + hgetex(...args: [ + key: RedisKey, + persist: "PERSIST", + fieldsToken: "FIELDS", + numfields: number | string, + ...fields: (string | Buffer)[] + ]): Result<(string | null)[], Context>; + hgetexBuffer(...args: [ + key: RedisKey, + persist: "PERSIST", + fieldsToken: "FIELDS", + numfields: number | string, + ...fields: (string | Buffer)[] + ]): Result<(Buffer | null)[], Context>; + hincrby(key: RedisKey, field: string | Buffer, increment: number | string, callback?: Callback<number>): Result<number, Context>; + hincrbyfloat(key: RedisKey, field: string | Buffer, increment: number | string, callback?: Callback<string>): Result<string, Context>; + hincrbyfloatBuffer(key: RedisKey, field: string | Buffer, increment: number | string, callback?: Callback<Buffer>): Result<Buffer, Context>; + hkeys(key: RedisKey, callback?: Callback<string[]>): Result<string[], Context>; + hkeysBuffer(key: RedisKey, callback?: Callback<Buffer[]>): Result<Buffer[], Context>; + hlen(key: RedisKey, callback?: Callback<number>): Result<number, Context>; + hmget(...args: [ + key: RedisKey, + ...fields: (string | Buffer)[], + callback: Callback<(string | null)[]> + ]): Result<(string | null)[], Context>; + hmgetBuffer(...args: [ + key: RedisKey, + ...fields: (string | Buffer)[], + callback: Callback<(Buffer | null)[]> + ]): Result<(Buffer | null)[], Context>; + hmget(...args: [ + key: RedisKey, + ...fields: (string | Buffer)[] + ]): Result<(string | null)[], Context>; + hmgetBuffer(...args: [ + key: RedisKey, + ...fields: (string | Buffer)[] + ]): Result<(Buffer | null)[], Context>; + hmset(key: RedisKey, object: object, callback?: Callback<"OK">): Result<"OK", Context>; + hmset(key: RedisKey, map: Map<string | Buffer | number, string | Buffer | number>, callback?: Callback<"OK">): Result<"OK", Context>; + hmset(...args: [ + key: RedisKey, + ...fieldValues: (string | Buffer | number)[], + callback: Callback<"OK"> + ]): Result<"OK", Context>; + hmset(...args: [ + key: RedisKey, + ...fieldValues: (string | Buffer | number)[] + ]): Result<"OK", Context>; + hpersist(...args: [ + key: RedisKey, + fieldsToken: "FIELDS", + numfields: number | string, + ...fields: (string | Buffer)[], + callback: Callback<number[]> + ]): Result<number[], Context>; + hpersist(...args: [ + key: RedisKey, + fieldsToken: "FIELDS", + numfields: number | string, + ...fields: (string | Buffer)[] + ]): Result<number[], Context>; + hpexpire(...args: [ + key: RedisKey, + milliseconds: number | string, + fieldsToken: 'FIELDS', + numfields: number | string, + ...fields: (string | Buffer)[], + callback: Callback<number[]> + ]): Result<number[], Context>; + hpexpire(...args: [ + key: RedisKey, + milliseconds: number | string, + fieldsToken: 'FIELDS', + numfields: number | string, + ...fields: (string | Buffer)[] + ]): Result<number[], Context>; + hpexpire(...args: [ + key: RedisKey, + milliseconds: number | string, + nx: 'NX', + fieldsToken: 'FIELDS', + numfields: number | string, + ...fields: (string | Buffer)[], + callback: Callback<number[]> + ]): Result<number[], Context>; + hpexpire(...args: [ + key: RedisKey, + milliseconds: number | string, + nx: 'NX', + fieldsToken: 'FIELDS', + numfields: number | string, + ...fields: (string | Buffer)[] + ]): Result<number[], Context>; + hpexpire(...args: [ + key: RedisKey, + milliseconds: number | string, + xx: 'XX', + fieldsToken: 'FIELDS', + numfields: number | string, + ...fields: (string | Buffer)[], + callback: Callback<number[]> + ]): Result<number[], Context>; + hpexpire(...args: [ + key: RedisKey, + milliseconds: number | string, + xx: 'XX', + fieldsToken: 'FIELDS', + numfields: number | string, + ...fields: (string | Buffer)[] + ]): Result<number[], Context>; + hpexpire(...args: [ + key: RedisKey, + milliseconds: number | string, + gt: 'GT', + fieldsToken: 'FIELDS', + numfields: number | string, + ...fields: (string | Buffer)[], + callback: Callback<number[]> + ]): Result<number[], Context>; + hpexpire(...args: [ + key: RedisKey, + milliseconds: number | string, + gt: 'GT', + fieldsToken: 'FIELDS', + numfields: number | string, + ...fields: (string | Buffer)[] + ]): Result<number[], Context>; + hpexpire(...args: [ + key: RedisKey, + milliseconds: number | string, + lt: 'LT', + fieldsToken: 'FIELDS', + numfields: number | string, + ...fields: (string | Buffer)[], + callback: Callback<number[]> + ]): Result<number[], Context>; + hpexpire(...args: [ + key: RedisKey, + milliseconds: number | string, + lt: 'LT', + fieldsToken: 'FIELDS', + numfields: number | string, + ...fields: (string | Buffer)[] + ]): Result<number[], Context>; + hpexpireat(...args: [ + key: RedisKey, + unixTimeMilliseconds: number | string, + fieldsToken: "FIELDS", + numfields: number | string, + ...fields: (string | Buffer)[], + callback: Callback<number[]> + ]): Result<number[], Context>; + hpexpireat(...args: [ + key: RedisKey, + unixTimeMilliseconds: number | string, + fieldsToken: "FIELDS", + numfields: number | string, + ...fields: (string | Buffer)[] + ]): Result<number[], Context>; + hpexpireat(...args: [ + key: RedisKey, + unixTimeMilliseconds: number | string, + nx: "NX", + fieldsToken: "FIELDS", + numfields: number | string, + ...fields: (string | Buffer)[], + callback: Callback<number[]> + ]): Result<number[], Context>; + hpexpireat(...args: [ + key: RedisKey, + unixTimeMilliseconds: number | string, + nx: "NX", + fieldsToken: "FIELDS", + numfields: number | string, + ...fields: (string | Buffer)[] + ]): Result<number[], Context>; + hpexpireat(...args: [ + key: RedisKey, + unixTimeMilliseconds: number | string, + xx: "XX", + fieldsToken: "FIELDS", + numfields: number | string, + ...fields: (string | Buffer)[], + callback: Callback<number[]> + ]): Result<number[], Context>; + hpexpireat(...args: [ + key: RedisKey, + unixTimeMilliseconds: number | string, + xx: "XX", + fieldsToken: "FIELDS", + numfields: number | string, + ...fields: (string | Buffer)[] + ]): Result<number[], Context>; + hpexpireat(...args: [ + key: RedisKey, + unixTimeMilliseconds: number | string, + gt: "GT", + fieldsToken: "FIELDS", + numfields: number | string, + ...fields: (string | Buffer)[], + callback: Callback<number[]> + ]): Result<number[], Context>; + hpexpireat(...args: [ + key: RedisKey, + unixTimeMilliseconds: number | string, + gt: "GT", + fieldsToken: "FIELDS", + numfields: number | string, + ...fields: (string | Buffer)[] + ]): Result<number[], Context>; + hpexpireat(...args: [ + key: RedisKey, + unixTimeMilliseconds: number | string, + lt: "LT", + fieldsToken: "FIELDS", + numfields: number | string, + ...fields: (string | Buffer)[], + callback: Callback<number[]> + ]): Result<number[], Context>; + hpexpireat(...args: [ + key: RedisKey, + unixTimeMilliseconds: number | string, + lt: "LT", + fieldsToken: "FIELDS", + numfields: number | string, + ...fields: (string | Buffer)[] + ]): Result<number[], Context>; + hpexpiretime(...args: [ + key: RedisKey, + fieldsToken: "FIELDS", + numfields: number | string, + ...fields: (string | Buffer)[], + callback: Callback<number[]> + ]): Result<number[], Context>; + hpexpiretime(...args: [ + key: RedisKey, + fieldsToken: "FIELDS", + numfields: number | string, + ...fields: (string | Buffer)[] + ]): Result<number[], Context>; + hpttl(...args: [ + key: RedisKey, + fieldsToken: "FIELDS", + numfields: number | string, + ...fields: (string | Buffer)[], + callback: Callback<number[]> + ]): Result<number[], Context>; + hpttl(...args: [ + key: RedisKey, + fieldsToken: "FIELDS", + numfields: number | string, + ...fields: (string | Buffer)[] + ]): Result<number[], Context>; + hrandfield(key: RedisKey, callback?: Callback<string | unknown[] | null>): Result<string | unknown[] | null, Context>; + hrandfieldBuffer(key: RedisKey, callback?: Callback<Buffer | unknown[] | null>): Result<Buffer | unknown[] | null, Context>; + hrandfield(key: RedisKey, count: number | string, callback?: Callback<string | unknown[] | null>): Result<string | unknown[] | null, Context>; + hrandfieldBuffer(key: RedisKey, count: number | string, callback?: Callback<Buffer | unknown[] | null>): Result<Buffer | unknown[] | null, Context>; + hrandfield(key: RedisKey, count: number | string, withvalues: "WITHVALUES", callback?: Callback<string | unknown[] | null>): Result<string | unknown[] | null, Context>; + hrandfieldBuffer(key: RedisKey, count: number | string, withvalues: "WITHVALUES", callback?: Callback<Buffer | unknown[] | null>): Result<Buffer | unknown[] | null, Context>; + hscan(key: RedisKey, cursor: number | string, callback?: Callback<[ + cursor: string, + elements: string[] + ]>): Result<[ + cursor: string, + elements: string[] + ], Context>; + hscanBuffer(key: RedisKey, cursor: number | string, callback?: Callback<[ + cursor: Buffer, + elements: Buffer[] + ]>): Result<[ + cursor: Buffer, + elements: Buffer[] + ], Context>; + hscan(key: RedisKey, cursor: number | string, countToken: "COUNT", count: number | string, callback?: Callback<[ + cursor: string, + elements: string[] + ]>): Result<[ + cursor: string, + elements: string[] + ], Context>; + hscanBuffer(key: RedisKey, cursor: number | string, countToken: "COUNT", count: number | string, callback?: Callback<[ + cursor: Buffer, + elements: Buffer[] + ]>): Result<[ + cursor: Buffer, + elements: Buffer[] + ], Context>; + hscan(key: RedisKey, cursor: number | string, patternToken: "MATCH", pattern: string, callback?: Callback<[ + cursor: string, + elements: string[] + ]>): Result<[ + cursor: string, + elements: string[] + ], Context>; + hscanBuffer(key: RedisKey, cursor: number | string, patternToken: "MATCH", pattern: string, callback?: Callback<[ + cursor: Buffer, + elements: Buffer[] + ]>): Result<[ + cursor: Buffer, + elements: Buffer[] + ], Context>; + hscan(key: RedisKey, cursor: number | string, patternToken: "MATCH", pattern: string, countToken: "COUNT", count: number | string, callback?: Callback<[ + cursor: string, + elements: string[] + ]>): Result<[ + cursor: string, + elements: string[] + ], Context>; + hscanBuffer(key: RedisKey, cursor: number | string, patternToken: "MATCH", pattern: string, countToken: "COUNT", count: number | string, callback?: Callback<[ + cursor: Buffer, + elements: Buffer[] + ]>): Result<[ + cursor: Buffer, + elements: Buffer[] + ], Context>; + hset(key: RedisKey, object: object, callback?: Callback<number>): Result<number, Context>; + hset(key: RedisKey, map: Map<string | Buffer | number, string | Buffer | number>, callback?: Callback<number>): Result<number, Context>; + hset(...args: [ + key: RedisKey, + ...fieldValues: (string | Buffer | number)[], + callback: Callback<number> + ]): Result<number, Context>; + hset(...args: [ + key: RedisKey, + ...fieldValues: (string | Buffer | number)[] + ]): Result<number, Context>; + hsetex(...args: [ + key: RedisKey, + fieldsToken: "FIELDS", + numfields: number | string, + ...data: (string | Buffer | number)[], + callback: Callback<number> + ]): Result<number, Context>; + hsetex(...args: [ + key: RedisKey, + fieldsToken: "FIELDS", + numfields: number | string, + ...data: (string | Buffer | number)[] + ]): Result<number, Context>; + hsetex(...args: [ + key: RedisKey, + secondsToken: "EX", + seconds: number | string, + fieldsToken: "FIELDS", + numfields: number | string, + ...data: (string | Buffer | number)[], + callback: Callback<number> + ]): Result<number, Context>; + hsetex(...args: [ + key: RedisKey, + secondsToken: "EX", + seconds: number | string, + fieldsToken: "FIELDS", + numfields: number | string, + ...data: (string | Buffer | number)[] + ]): Result<number, Context>; + hsetex(...args: [ + key: RedisKey, + millisecondsToken: "PX", + milliseconds: number | string, + fieldsToken: "FIELDS", + numfields: number | string, + ...data: (string | Buffer | number)[], + callback: Callback<number> + ]): Result<number, Context>; + hsetex(...args: [ + key: RedisKey, + millisecondsToken: "PX", + milliseconds: number | string, + fieldsToken: "FIELDS", + numfields: number | string, + ...data: (string | Buffer | number)[] + ]): Result<number, Context>; + hsetex(...args: [ + key: RedisKey, + unixTimeSecondsToken: "EXAT", + unixTimeSeconds: number | string, + fieldsToken: "FIELDS", + numfields: number | string, + ...data: (string | Buffer | number)[], + callback: Callback<number> + ]): Result<number, Context>; + hsetex(...args: [ + key: RedisKey, + unixTimeSecondsToken: "EXAT", + unixTimeSeconds: number | string, + fieldsToken: "FIELDS", + numfields: number | string, + ...data: (string | Buffer | number)[] + ]): Result<number, Context>; + hsetex(...args: [ + key: RedisKey, + unixTimeMillisecondsToken: "PXAT", + unixTimeMilliseconds: number | string, + fieldsToken: "FIELDS", + numfields: number | string, + ...data: (string | Buffer | number)[], + callback: Callback<number> + ]): Result<number, Context>; + hsetex(...args: [ + key: RedisKey, + unixTimeMillisecondsToken: "PXAT", + unixTimeMilliseconds: number | string, + fieldsToken: "FIELDS", + numfields: number | string, + ...data: (string | Buffer | number)[] + ]): Result<number, Context>; + hsetex(...args: [ + key: RedisKey, + keepttl: "KEEPTTL", + fieldsToken: "FIELDS", + numfields: number | string, + ...data: (string | Buffer | number)[], + callback: Callback<number> + ]): Result<number, Context>; + hsetex(...args: [ + key: RedisKey, + keepttl: "KEEPTTL", + fieldsToken: "FIELDS", + numfields: number | string, + ...data: (string | Buffer | number)[] + ]): Result<number, Context>; + hsetex(...args: [ + key: RedisKey, + fnx: "FNX", + fieldsToken: "FIELDS", + numfields: number | string, + ...data: (string | Buffer | number)[], + callback: Callback<number> + ]): Result<number, Context>; + hsetex(...args: [ + key: RedisKey, + fnx: "FNX", + fieldsToken: "FIELDS", + numfields: number | string, + ...data: (string | Buffer | number)[] + ]): Result<number, Context>; + hsetex(...args: [ + key: RedisKey, + fnx: "FNX", + secondsToken: "EX", + seconds: number | string, + fieldsToken: "FIELDS", + numfields: number | string, + ...data: (string | Buffer | number)[], + callback: Callback<number> + ]): Result<number, Context>; + hsetex(...args: [ + key: RedisKey, + fnx: "FNX", + secondsToken: "EX", + seconds: number | string, + fieldsToken: "FIELDS", + numfields: number | string, + ...data: (string | Buffer | number)[] + ]): Result<number, Context>; + hsetex(...args: [ + key: RedisKey, + fnx: "FNX", + millisecondsToken: "PX", + milliseconds: number | string, + fieldsToken: "FIELDS", + numfields: number | string, + ...data: (string | Buffer | number)[], + callback: Callback<number> + ]): Result<number, Context>; + hsetex(...args: [ + key: RedisKey, + fnx: "FNX", + millisecondsToken: "PX", + milliseconds: number | string, + fieldsToken: "FIELDS", + numfields: number | string, + ...data: (string | Buffer | number)[] + ]): Result<number, Context>; + hsetex(...args: [ + key: RedisKey, + fnx: "FNX", + unixTimeSecondsToken: "EXAT", + unixTimeSeconds: number | string, + fieldsToken: "FIELDS", + numfields: number | string, + ...data: (string | Buffer | number)[], + callback: Callback<number> + ]): Result<number, Context>; + hsetex(...args: [ + key: RedisKey, + fnx: "FNX", + unixTimeSecondsToken: "EXAT", + unixTimeSeconds: number | string, + fieldsToken: "FIELDS", + numfields: number | string, + ...data: (string | Buffer | number)[] + ]): Result<number, Context>; + hsetex(...args: [ + key: RedisKey, + fnx: "FNX", + unixTimeMillisecondsToken: "PXAT", + unixTimeMilliseconds: number | string, + fieldsToken: "FIELDS", + numfields: number | string, + ...data: (string | Buffer | number)[], + callback: Callback<number> + ]): Result<number, Context>; + hsetex(...args: [ + key: RedisKey, + fnx: "FNX", + unixTimeMillisecondsToken: "PXAT", + unixTimeMilliseconds: number | string, + fieldsToken: "FIELDS", + numfields: number | string, + ...data: (string | Buffer | number)[] + ]): Result<number, Context>; + hsetex(...args: [ + key: RedisKey, + fnx: "FNX", + keepttl: "KEEPTTL", + fieldsToken: "FIELDS", + numfields: number | string, + ...data: (string | Buffer | number)[], + callback: Callback<number> + ]): Result<number, Context>; + hsetex(...args: [ + key: RedisKey, + fnx: "FNX", + keepttl: "KEEPTTL", + fieldsToken: "FIELDS", + numfields: number | string, + ...data: (string | Buffer | number)[] + ]): Result<number, Context>; + hsetex(...args: [ + key: RedisKey, + fxx: "FXX", + fieldsToken: "FIELDS", + numfields: number | string, + ...data: (string | Buffer | number)[], + callback: Callback<number> + ]): Result<number, Context>; + hsetex(...args: [ + key: RedisKey, + fxx: "FXX", + fieldsToken: "FIELDS", + numfields: number | string, + ...data: (string | Buffer | number)[] + ]): Result<number, Context>; + hsetex(...args: [ + key: RedisKey, + fxx: "FXX", + secondsToken: "EX", + seconds: number | string, + fieldsToken: "FIELDS", + numfields: number | string, + ...data: (string | Buffer | number)[], + callback: Callback<number> + ]): Result<number, Context>; + hsetex(...args: [ + key: RedisKey, + fxx: "FXX", + secondsToken: "EX", + seconds: number | string, + fieldsToken: "FIELDS", + numfields: number | string, + ...data: (string | Buffer | number)[] + ]): Result<number, Context>; + hsetex(...args: [ + key: RedisKey, + fxx: "FXX", + millisecondsToken: "PX", + milliseconds: number | string, + fieldsToken: "FIELDS", + numfields: number | string, + ...data: (string | Buffer | number)[], + callback: Callback<number> + ]): Result<number, Context>; + hsetex(...args: [ + key: RedisKey, + fxx: "FXX", + millisecondsToken: "PX", + milliseconds: number | string, + fieldsToken: "FIELDS", + numfields: number | string, + ...data: (string | Buffer | number)[] + ]): Result<number, Context>; + hsetex(...args: [ + key: RedisKey, + fxx: "FXX", + unixTimeSecondsToken: "EXAT", + unixTimeSeconds: number | string, + fieldsToken: "FIELDS", + numfields: number | string, + ...data: (string | Buffer | number)[], + callback: Callback<number> + ]): Result<number, Context>; + hsetex(...args: [ + key: RedisKey, + fxx: "FXX", + unixTimeSecondsToken: "EXAT", + unixTimeSeconds: number | string, + fieldsToken: "FIELDS", + numfields: number | string, + ...data: (string | Buffer | number)[] + ]): Result<number, Context>; + hsetex(...args: [ + key: RedisKey, + fxx: "FXX", + unixTimeMillisecondsToken: "PXAT", + unixTimeMilliseconds: number | string, + fieldsToken: "FIELDS", + numfields: number | string, + ...data: (string | Buffer | number)[], + callback: Callback<number> + ]): Result<number, Context>; + hsetex(...args: [ + key: RedisKey, + fxx: "FXX", + unixTimeMillisecondsToken: "PXAT", + unixTimeMilliseconds: number | string, + fieldsToken: "FIELDS", + numfields: number | string, + ...data: (string | Buffer | number)[] + ]): Result<number, Context>; + hsetex(...args: [ + key: RedisKey, + fxx: "FXX", + keepttl: "KEEPTTL", + fieldsToken: "FIELDS", + numfields: number | string, + ...data: (string | Buffer | number)[], + callback: Callback<number> + ]): Result<number, Context>; + hsetex(...args: [ + key: RedisKey, + fxx: "FXX", + keepttl: "KEEPTTL", + fieldsToken: "FIELDS", + numfields: number | string, + ...data: (string | Buffer | number)[] + ]): Result<number, Context>; + hsetnx(key: RedisKey, field: string | Buffer, value: string | Buffer | number, callback?: Callback<number>): Result<number, Context>; + hstrlen(key: RedisKey, field: string | Buffer, callback?: Callback<number>): Result<number, Context>; + httl(...args: [ + key: RedisKey, + fieldsToken: "FIELDS", + numfields: number | string, + ...fields: (string | Buffer)[], + callback: Callback<number[]> + ]): Result<number[], Context>; + httl(...args: [ + key: RedisKey, + fieldsToken: "FIELDS", + numfields: number | string, + ...fields: (string | Buffer)[] + ]): Result<number[], Context>; + hvals(key: RedisKey, callback?: Callback<string[]>): Result<string[], Context>; + hvalsBuffer(key: RedisKey, callback?: Callback<Buffer[]>): Result<Buffer[], Context>; + incr(key: RedisKey, callback?: Callback<number>): Result<number, Context>; + incrby(key: RedisKey, increment: number | string, callback?: Callback<number>): Result<number, Context>; + incrbyfloat(key: RedisKey, increment: number | string, callback?: Callback<string>): Result<string, Context>; + increx(...args: [ + key: RedisKey, + ...args: (RedisValue)[], + callback: Callback<[ + value: number, + increment: number + ] | [ + value: string, + increment: string + ]> + ]): Result<[ + value: number, + increment: number + ] | [ + value: string, + increment: string + ], Context>; + increxBuffer(...args: [ + key: RedisKey, + ...args: (RedisValue)[], + callback: Callback<[ + value: number, + increment: number + ] | [ + value: Buffer, + increment: Buffer + ]> + ]): Result<[ + value: number, + increment: number + ] | [ + value: Buffer, + increment: Buffer + ], Context>; + increx(...args: [ + key: RedisKey, + ...args: (RedisValue)[] + ]): Result<[ + value: number, + increment: number + ] | [ + value: string, + increment: string + ], Context>; + increxBuffer(...args: [ + key: RedisKey, + ...args: (RedisValue)[] + ]): Result<[ + value: number, + increment: number + ] | [ + value: Buffer, + increment: Buffer + ], Context>; + info(callback?: Callback<string>): Result<string, Context>; + info(...args: [ + ...sections: (string | Buffer)[], + callback: Callback<string> + ]): Result<string, Context>; + info(...args: [ + ...sections: (string | Buffer)[] + ]): Result<string, Context>; + keys(pattern: string, callback?: Callback<string[]>): Result<string[], Context>; + keysBuffer(pattern: string, callback?: Callback<Buffer[]>): Result<Buffer[], Context>; + lastsave(callback?: Callback<number>): Result<number, Context>; + latency(subcommand: "DOCTOR", callback?: Callback<string>): Result<string, Context>; + latency(subcommand: "GRAPH", event: string | Buffer, callback?: Callback<string>): Result<string, Context>; + latency(subcommand: "HELP", callback?: Callback<unknown[]>): Result<unknown[], Context>; + latency(subcommand: "HISTOGRAM", callback?: Callback<unknown>): Result<unknown, Context>; + latency(...args: [ + subcommand: "HISTOGRAM", + ...commands: (string | Buffer)[], + callback: Callback<unknown> + ]): Result<unknown, Context>; + latency(...args: [ + subcommand: "HISTOGRAM", + ...commands: (string | Buffer)[] + ]): Result<unknown, Context>; + latency(subcommand: "HISTORY", event: string | Buffer, callback?: Callback<unknown[]>): Result<unknown[], Context>; + latency(subcommand: "LATEST", callback?: Callback<unknown[]>): Result<unknown[], Context>; + latency(subcommand: "RESET", callback?: Callback<number>): Result<number, Context>; + latency(...args: [ + subcommand: "RESET", + ...events: (string | Buffer)[], + callback: Callback<number> + ]): Result<number, Context>; + latency(...args: [ + subcommand: "RESET", + ...events: (string | Buffer)[] + ]): Result<number, Context>; + lcs(key1: RedisKey, key2: RedisKey, callback?: Callback<unknown>): Result<unknown, Context>; + lcs(key1: RedisKey, key2: RedisKey, withmatchlen: "WITHMATCHLEN", callback?: Callback<unknown>): Result<unknown, Context>; + lcs(key1: RedisKey, key2: RedisKey, lenToken: "MINMATCHLEN", len: number | string, callback?: Callback<unknown>): Result<unknown, Context>; + lcs(key1: RedisKey, key2: RedisKey, lenToken: "MINMATCHLEN", len: number | string, withmatchlen: "WITHMATCHLEN", callback?: Callback<unknown>): Result<unknown, Context>; + lcs(key1: RedisKey, key2: RedisKey, idx: "IDX", callback?: Callback<unknown>): Result<unknown, Context>; + lcs(key1: RedisKey, key2: RedisKey, idx: "IDX", withmatchlen: "WITHMATCHLEN", callback?: Callback<unknown>): Result<unknown, Context>; + lcs(key1: RedisKey, key2: RedisKey, idx: "IDX", lenToken: "MINMATCHLEN", len: number | string, callback?: Callback<unknown>): Result<unknown, Context>; + lcs(key1: RedisKey, key2: RedisKey, idx: "IDX", lenToken: "MINMATCHLEN", len: number | string, withmatchlen: "WITHMATCHLEN", callback?: Callback<unknown>): Result<unknown, Context>; + lcs(key1: RedisKey, key2: RedisKey, len: "LEN", callback?: Callback<unknown>): Result<unknown, Context>; + lcs(key1: RedisKey, key2: RedisKey, len: "LEN", withmatchlen: "WITHMATCHLEN", callback?: Callback<unknown>): Result<unknown, Context>; + lcs(key1: RedisKey, key2: RedisKey, len: "LEN", lenToken: "MINMATCHLEN", len1: number | string, callback?: Callback<unknown>): Result<unknown, Context>; + lcs(key1: RedisKey, key2: RedisKey, len: "LEN", lenToken: "MINMATCHLEN", len1: number | string, withmatchlen: "WITHMATCHLEN", callback?: Callback<unknown>): Result<unknown, Context>; + lcs(key1: RedisKey, key2: RedisKey, len: "LEN", idx: "IDX", callback?: Callback<unknown>): Result<unknown, Context>; + lcs(key1: RedisKey, key2: RedisKey, len: "LEN", idx: "IDX", withmatchlen: "WITHMATCHLEN", callback?: Callback<unknown>): Result<unknown, Context>; + lcs(key1: RedisKey, key2: RedisKey, len: "LEN", idx: "IDX", lenToken: "MINMATCHLEN", len1: number | string, callback?: Callback<unknown>): Result<unknown, Context>; + lcs(key1: RedisKey, key2: RedisKey, len: "LEN", idx: "IDX", lenToken: "MINMATCHLEN", len1: number | string, withmatchlen: "WITHMATCHLEN", callback?: Callback<unknown>): Result<unknown, Context>; + lindex(key: RedisKey, index: number | string, callback?: Callback<string | null>): Result<string | null, Context>; + lindexBuffer(key: RedisKey, index: number | string, callback?: Callback<Buffer | null>): Result<Buffer | null, Context>; + linsert(key: RedisKey, before: "BEFORE", pivot: string | Buffer | number, element: string | Buffer | number, callback?: Callback<number>): Result<number, Context>; + linsert(key: RedisKey, after: "AFTER", pivot: string | Buffer | number, element: string | Buffer | number, callback?: Callback<number>): Result<number, Context>; + llen(key: RedisKey, callback?: Callback<number>): Result<number, Context>; + lmove(source: RedisKey, destination: RedisKey, left: "LEFT", left1: "LEFT", callback?: Callback<string>): Result<string, Context>; + lmoveBuffer(source: RedisKey, destination: RedisKey, left: "LEFT", left1: "LEFT", callback?: Callback<Buffer>): Result<Buffer, Context>; + lmove(source: RedisKey, destination: RedisKey, left: "LEFT", right: "RIGHT", callback?: Callback<string>): Result<string, Context>; + lmoveBuffer(source: RedisKey, destination: RedisKey, left: "LEFT", right: "RIGHT", callback?: Callback<Buffer>): Result<Buffer, Context>; + lmove(source: RedisKey, destination: RedisKey, right: "RIGHT", left: "LEFT", callback?: Callback<string>): Result<string, Context>; + lmoveBuffer(source: RedisKey, destination: RedisKey, right: "RIGHT", left: "LEFT", callback?: Callback<Buffer>): Result<Buffer, Context>; + lmove(source: RedisKey, destination: RedisKey, right: "RIGHT", right1: "RIGHT", callback?: Callback<string>): Result<string, Context>; + lmoveBuffer(source: RedisKey, destination: RedisKey, right: "RIGHT", right1: "RIGHT", callback?: Callback<Buffer>): Result<Buffer, Context>; + lmpop(...args: [ + numkeys: number | string, + ...keys: RedisKey[], + left: "LEFT", + callback: Callback<[ + key: string, + members: string[] + ] | null> + ]): Result<[ + key: string, + members: string[] + ] | null, Context>; + lmpopBuffer(...args: [ + numkeys: number | string, + ...keys: RedisKey[], + left: "LEFT", + callback: Callback<[ + key: Buffer, + members: Buffer[] + ] | null> + ]): Result<[ + key: Buffer, + members: Buffer[] + ] | null, Context>; + lmpop(...args: [ + numkeys: number | string, + keys: RedisKey[], + left: "LEFT", + callback: Callback<[ + key: string, + members: string[] + ] | null> + ]): Result<[ + key: string, + members: string[] + ] | null, Context>; + lmpopBuffer(...args: [ + numkeys: number | string, + keys: RedisKey[], + left: "LEFT", + callback: Callback<[ + key: Buffer, + members: Buffer[] + ] | null> + ]): Result<[ + key: Buffer, + members: Buffer[] + ] | null, Context>; + lmpop(...args: [ + numkeys: number | string, + ...keys: RedisKey[], + left: "LEFT" + ]): Result<[ + key: string, + members: string[] + ] | null, Context>; + lmpopBuffer(...args: [ + numkeys: number | string, + ...keys: RedisKey[], + left: "LEFT" + ]): Result<[ + key: Buffer, + members: Buffer[] + ] | null, Context>; + lmpop(...args: [ + numkeys: number | string, + keys: RedisKey[], + left: "LEFT" + ]): Result<[ + key: string, + members: string[] + ] | null, Context>; + lmpopBuffer(...args: [ + numkeys: number | string, + keys: RedisKey[], + left: "LEFT" + ]): Result<[ + key: Buffer, + members: Buffer[] + ] | null, Context>; + lmpop(...args: [ + numkeys: number | string, + ...keys: RedisKey[], + left: "LEFT", + countToken: "COUNT", + count: number | string, + callback: Callback<[ + key: string, + members: string[] + ] | null> + ]): Result<[ + key: string, + members: string[] + ] | null, Context>; + lmpopBuffer(...args: [ + numkeys: number | string, + ...keys: RedisKey[], + left: "LEFT", + countToken: "COUNT", + count: number | string, + callback: Callback<[ + key: Buffer, + members: Buffer[] + ] | null> + ]): Result<[ + key: Buffer, + members: Buffer[] + ] | null, Context>; + lmpop(...args: [ + numkeys: number | string, + keys: RedisKey[], + left: "LEFT", + countToken: "COUNT", + count: number | string, + callback: Callback<[ + key: string, + members: string[] + ] | null> + ]): Result<[ + key: string, + members: string[] + ] | null, Context>; + lmpopBuffer(...args: [ + numkeys: number | string, + keys: RedisKey[], + left: "LEFT", + countToken: "COUNT", + count: number | string, + callback: Callback<[ + key: Buffer, + members: Buffer[] + ] | null> + ]): Result<[ + key: Buffer, + members: Buffer[] + ] | null, Context>; + lmpop(...args: [ + numkeys: number | string, + ...keys: RedisKey[], + left: "LEFT", + countToken: "COUNT", + count: number | string + ]): Result<[ + key: string, + members: string[] + ] | null, Context>; + lmpopBuffer(...args: [ + numkeys: number | string, + ...keys: RedisKey[], + left: "LEFT", + countToken: "COUNT", + count: number | string + ]): Result<[ + key: Buffer, + members: Buffer[] + ] | null, Context>; + lmpop(...args: [ + numkeys: number | string, + keys: RedisKey[], + left: "LEFT", + countToken: "COUNT", + count: number | string + ]): Result<[ + key: string, + members: string[] + ] | null, Context>; + lmpopBuffer(...args: [ + numkeys: number | string, + keys: RedisKey[], + left: "LEFT", + countToken: "COUNT", + count: number | string + ]): Result<[ + key: Buffer, + members: Buffer[] + ] | null, Context>; + lmpop(...args: [ + numkeys: number | string, + ...keys: RedisKey[], + right: "RIGHT", + callback: Callback<[ + key: string, + members: string[] + ] | null> + ]): Result<[ + key: string, + members: string[] + ] | null, Context>; + lmpopBuffer(...args: [ + numkeys: number | string, + ...keys: RedisKey[], + right: "RIGHT", + callback: Callback<[ + key: Buffer, + members: Buffer[] + ] | null> + ]): Result<[ + key: Buffer, + members: Buffer[] + ] | null, Context>; + lmpop(...args: [ + numkeys: number | string, + keys: RedisKey[], + right: "RIGHT", + callback: Callback<[ + key: string, + members: string[] + ] | null> + ]): Result<[ + key: string, + members: string[] + ] | null, Context>; + lmpopBuffer(...args: [ + numkeys: number | string, + keys: RedisKey[], + right: "RIGHT", + callback: Callback<[ + key: Buffer, + members: Buffer[] + ] | null> + ]): Result<[ + key: Buffer, + members: Buffer[] + ] | null, Context>; + lmpop(...args: [ + numkeys: number | string, + ...keys: RedisKey[], + right: "RIGHT" + ]): Result<[ + key: string, + members: string[] + ] | null, Context>; + lmpopBuffer(...args: [ + numkeys: number | string, + ...keys: RedisKey[], + right: "RIGHT" + ]): Result<[ + key: Buffer, + members: Buffer[] + ] | null, Context>; + lmpop(...args: [ + numkeys: number | string, + keys: RedisKey[], + right: "RIGHT" + ]): Result<[ + key: string, + members: string[] + ] | null, Context>; + lmpopBuffer(...args: [ + numkeys: number | string, + keys: RedisKey[], + right: "RIGHT" + ]): Result<[ + key: Buffer, + members: Buffer[] + ] | null, Context>; + lmpop(...args: [ + numkeys: number | string, + ...keys: RedisKey[], + right: "RIGHT", + countToken: "COUNT", + count: number | string, + callback: Callback<[ + key: string, + members: string[] + ] | null> + ]): Result<[ + key: string, + members: string[] + ] | null, Context>; + lmpopBuffer(...args: [ + numkeys: number | string, + ...keys: RedisKey[], + right: "RIGHT", + countToken: "COUNT", + count: number | string, + callback: Callback<[ + key: Buffer, + members: Buffer[] + ] | null> + ]): Result<[ + key: Buffer, + members: Buffer[] + ] | null, Context>; + lmpop(...args: [ + numkeys: number | string, + keys: RedisKey[], + right: "RIGHT", + countToken: "COUNT", + count: number | string, + callback: Callback<[ + key: string, + members: string[] + ] | null> + ]): Result<[ + key: string, + members: string[] + ] | null, Context>; + lmpopBuffer(...args: [ + numkeys: number | string, + keys: RedisKey[], + right: "RIGHT", + countToken: "COUNT", + count: number | string, + callback: Callback<[ + key: Buffer, + members: Buffer[] + ] | null> + ]): Result<[ + key: Buffer, + members: Buffer[] + ] | null, Context>; + lmpop(...args: [ + numkeys: number | string, + ...keys: RedisKey[], + right: "RIGHT", + countToken: "COUNT", + count: number | string + ]): Result<[ + key: string, + members: string[] + ] | null, Context>; + lmpopBuffer(...args: [ + numkeys: number | string, + ...keys: RedisKey[], + right: "RIGHT", + countToken: "COUNT", + count: number | string + ]): Result<[ + key: Buffer, + members: Buffer[] + ] | null, Context>; + lmpop(...args: [ + numkeys: number | string, + keys: RedisKey[], + right: "RIGHT", + countToken: "COUNT", + count: number | string + ]): Result<[ + key: string, + members: string[] + ] | null, Context>; + lmpopBuffer(...args: [ + numkeys: number | string, + keys: RedisKey[], + right: "RIGHT", + countToken: "COUNT", + count: number | string + ]): Result<[ + key: Buffer, + members: Buffer[] + ] | null, Context>; + lolwut(callback?: Callback<string>): Result<string, Context>; + lolwut(versionToken: "VERSION", version: number | string, callback?: Callback<string>): Result<string, Context>; + lpop(key: RedisKey, callback?: Callback<string | null>): Result<string | null, Context>; + lpopBuffer(key: RedisKey, callback?: Callback<Buffer | null>): Result<Buffer | null, Context>; + lpop(key: RedisKey, count: number | string, callback?: Callback<string[] | null>): Result<string[] | null, Context>; + lpopBuffer(key: RedisKey, count: number | string, callback?: Callback<Buffer[] | null>): Result<Buffer[] | null, Context>; + lpos(key: RedisKey, element: string | Buffer | number, callback?: Callback<number | null>): Result<number | null, Context>; + lpos(key: RedisKey, element: string | Buffer | number, lenToken: "MAXLEN", len: number | string, callback?: Callback<number | null>): Result<number | null, Context>; + lpos(key: RedisKey, element: string | Buffer | number, numMatchesToken: "COUNT", numMatches: number | string, callback?: Callback<number[]>): Result<number[], Context>; + lpos(key: RedisKey, element: string | Buffer | number, numMatchesToken: "COUNT", numMatches: number | string, lenToken: "MAXLEN", len: number | string, callback?: Callback<number[]>): Result<number[], Context>; + lpos(key: RedisKey, element: string | Buffer | number, rankToken: "RANK", rank: number | string, callback?: Callback<number | null>): Result<number | null, Context>; + lpos(key: RedisKey, element: string | Buffer | number, rankToken: "RANK", rank: number | string, lenToken: "MAXLEN", len: number | string, callback?: Callback<number | null>): Result<number | null, Context>; + lpos(key: RedisKey, element: string | Buffer | number, rankToken: "RANK", rank: number | string, numMatchesToken: "COUNT", numMatches: number | string, callback?: Callback<number[]>): Result<number[], Context>; + lpos(key: RedisKey, element: string | Buffer | number, rankToken: "RANK", rank: number | string, numMatchesToken: "COUNT", numMatches: number | string, lenToken: "MAXLEN", len: number | string, callback?: Callback<number[]>): Result<number[], Context>; + lpush(...args: [ + key: RedisKey, + ...elements: (string | Buffer | number)[], + callback: Callback<number> + ]): Result<number, Context>; + lpush(...args: [ + key: RedisKey, + ...elements: (string | Buffer | number)[] + ]): Result<number, Context>; + lpushx(...args: [ + key: RedisKey, + ...elements: (string | Buffer | number)[], + callback: Callback<number> + ]): Result<number, Context>; + lpushx(...args: [ + key: RedisKey, + ...elements: (string | Buffer | number)[] + ]): Result<number, Context>; + lrange(key: RedisKey, start: number | string, stop: number | string, callback?: Callback<string[]>): Result<string[], Context>; + lrangeBuffer(key: RedisKey, start: number | string, stop: number | string, callback?: Callback<Buffer[]>): Result<Buffer[], Context>; + lrem(key: RedisKey, count: number | string, element: string | Buffer | number, callback?: Callback<number>): Result<number, Context>; + lset(key: RedisKey, index: number | string, element: string | Buffer | number, callback?: Callback<"OK">): Result<"OK", Context>; + ltrim(key: RedisKey, start: number | string, stop: number | string, callback?: Callback<"OK">): Result<"OK", Context>; + memory(subcommand: "DOCTOR", callback?: Callback<string>): Result<string, Context>; + memory(subcommand: "HELP", callback?: Callback<unknown[]>): Result<unknown[], Context>; + memory(subcommand: "MALLOC-STATS", callback?: Callback<string>): Result<string, Context>; + memory(subcommand: "PURGE", callback?: Callback<"OK">): Result<"OK", Context>; + memory(subcommand: "STATS", callback?: Callback<unknown[]>): Result<unknown[], Context>; + memory(subcommand: "USAGE", key: RedisKey, callback?: Callback<number | null>): Result<number | null, Context>; + memory(subcommand: "USAGE", key: RedisKey, countToken: "SAMPLES", count: number | string, callback?: Callback<number | null>): Result<number | null, Context>; + mget(...args: [ + ...keys: RedisKey[], + callback: Callback<(string | null)[]> + ]): Result<(string | null)[], Context>; + mgetBuffer(...args: [ + ...keys: RedisKey[], + callback: Callback<(Buffer | null)[]> + ]): Result<(Buffer | null)[], Context>; + mget(...args: [ + keys: RedisKey[], + callback: Callback<(string | null)[]> + ]): Result<(string | null)[], Context>; + mgetBuffer(...args: [ + keys: RedisKey[], + callback: Callback<(Buffer | null)[]> + ]): Result<(Buffer | null)[], Context>; + mget(...args: [ + ...keys: RedisKey[] + ]): Result<(string | null)[], Context>; + mgetBuffer(...args: [ + ...keys: RedisKey[] + ]): Result<(Buffer | null)[], Context>; + mget(...args: [ + keys: RedisKey[] + ]): Result<(string | null)[], Context>; + mgetBuffer(...args: [ + keys: RedisKey[] + ]): Result<(Buffer | null)[], Context>; + migrate(...args: [ + host: string | Buffer, + port: number | string, + ...args: RedisValue[], + callback: Callback<"OK"> + ]): Result<"OK", Context>; + migrate(...args: [ + host: string | Buffer, + port: number | string, + ...args: RedisValue[] + ]): Result<"OK", Context>; + module(subcommand: "HELP", callback?: Callback<unknown>): Result<unknown, Context>; + module(subcommand: "LIST", callback?: Callback<unknown>): Result<unknown, Context>; + module(subcommand: "LOAD", path: string | Buffer, callback?: Callback<unknown>): Result<unknown, Context>; + module(...args: [ + subcommand: "LOAD", + path: string | Buffer, + ...args: (string | Buffer | number)[], + callback: Callback<unknown> + ]): Result<unknown, Context>; + module(...args: [ + subcommand: "LOAD", + path: string | Buffer, + ...args: (string | Buffer | number)[] + ]): Result<unknown, Context>; + module(subcommand: "LOADEX", path: string | Buffer, callback?: Callback<unknown>): Result<unknown, Context>; + module(...args: [ + subcommand: "LOADEX", + path: string | Buffer, + argsToken: "ARGS", + ...args: (string | Buffer | number)[], + callback: Callback<unknown> + ]): Result<unknown, Context>; + module(...args: [ + subcommand: "LOADEX", + path: string | Buffer, + argsToken: "ARGS", + ...args: (string | Buffer | number)[] + ]): Result<unknown, Context>; + module(...args: [ + subcommand: "LOADEX", + path: string | Buffer, + configsToken: "CONFIG", + ...configs: (string | Buffer | number)[], + callback: Callback<unknown> + ]): Result<unknown, Context>; + module(...args: [ + subcommand: "LOADEX", + path: string | Buffer, + configsToken: "CONFIG", + ...configs: (string | Buffer | number)[] + ]): Result<unknown, Context>; + module(...args: [ + subcommand: "LOADEX", + path: string | Buffer, + configsToken: "CONFIG", + ...args: RedisValue[], + callback: Callback<unknown> + ]): Result<unknown, Context>; + module(...args: [ + subcommand: "LOADEX", + path: string | Buffer, + configsToken: "CONFIG", + ...args: RedisValue[] + ]): Result<unknown, Context>; + module(subcommand: "UNLOAD", name: string | Buffer, callback?: Callback<unknown>): Result<unknown, Context>; + move(key: RedisKey, db: number | string, callback?: Callback<number>): Result<number, Context>; + mset(object: object, callback?: Callback<"OK">): Result<"OK", Context>; + mset(map: Map<string | Buffer | number, string | Buffer | number>, callback?: Callback<"OK">): Result<"OK", Context>; + mset(...args: [ + ...keyValues: (RedisKey | string | Buffer | number)[], + callback: Callback<"OK"> + ]): Result<"OK", Context>; + mset(...args: [ + ...keyValues: (RedisKey | string | Buffer | number)[] + ]): Result<"OK", Context>; + msetex(...args: [ + numkeys: number | string, + ...data: (RedisKey | string | Buffer | number)[], + callback: Callback<number> + ]): Result<number, Context>; + msetex(...args: [ + numkeys: number | string, + ...data: (RedisKey | string | Buffer | number)[] + ]): Result<number, Context>; + msetex(...args: [ + numkeys: number | string, + ...data: (RedisKey | string | Buffer | number)[], + secondsToken: 'EX', + seconds: number | string, + callback: Callback<number> + ]): Result<number, Context>; + msetex(...args: [ + numkeys: number | string, + ...data: (RedisKey | string | Buffer | number)[], + secondsToken: 'EX', + seconds: number | string + ]): Result<number, Context>; + msetex(...args: [ + numkeys: number | string, + ...data: (RedisKey | string | Buffer | number)[], + millisecondsToken: 'PX', + milliseconds: number | string, + callback: Callback<number> + ]): Result<number, Context>; + msetex(...args: [ + numkeys: number | string, + ...data: (RedisKey | string | Buffer | number)[], + millisecondsToken: 'PX', + milliseconds: number | string + ]): Result<number, Context>; + msetex(...args: [ + numkeys: number | string, + ...data: (RedisKey | string | Buffer | number)[], + unixTimeSecondsToken: 'EXAT', + unixTimeSeconds: number | string, + callback: Callback<number> + ]): Result<number, Context>; + msetex(...args: [ + numkeys: number | string, + ...data: (RedisKey | string | Buffer | number)[], + unixTimeSecondsToken: 'EXAT', + unixTimeSeconds: number | string + ]): Result<number, Context>; + msetex(...args: [ + numkeys: number | string, + ...data: (RedisKey | string | Buffer | number)[], + unixTimeMillisecondsToken: 'PXAT', + unixTimeMilliseconds: number | string, + callback: Callback<number> + ]): Result<number, Context>; + msetex(...args: [ + numkeys: number | string, + ...data: (RedisKey | string | Buffer | number)[], + unixTimeMillisecondsToken: 'PXAT', + unixTimeMilliseconds: number | string + ]): Result<number, Context>; + msetex(...args: [ + numkeys: number | string, + ...data: (RedisKey | string | Buffer | number)[], + keepttl: 'KEEPTTL', + callback: Callback<number> + ]): Result<number, Context>; + msetex(...args: [ + numkeys: number | string, + ...data: (RedisKey | string | Buffer | number)[], + keepttl: 'KEEPTTL' + ]): Result<number, Context>; + msetex(...args: [ + numkeys: number | string, + ...data: (RedisKey | string | Buffer | number)[], + nx: 'NX', + callback: Callback<number> + ]): Result<number, Context>; + msetex(...args: [ + numkeys: number | string, + ...data: (RedisKey | string | Buffer | number)[], + nx: 'NX' + ]): Result<number, Context>; + msetex(...args: [ + numkeys: number | string, + ...data: (RedisKey | string | Buffer | number)[], + nx: 'NX', + secondsToken: 'EX', + seconds: number | string, + callback: Callback<number> + ]): Result<number, Context>; + msetex(...args: [ + numkeys: number | string, + ...data: (RedisKey | string | Buffer | number)[], + nx: 'NX', + secondsToken: 'EX', + seconds: number | string + ]): Result<number, Context>; + msetex(...args: [ + numkeys: number | string, + ...data: (RedisKey | string | Buffer | number)[], + nx: 'NX', + millisecondsToken: 'PX', + milliseconds: number | string, + callback: Callback<number> + ]): Result<number, Context>; + msetex(...args: [ + numkeys: number | string, + ...data: (RedisKey | string | Buffer | number)[], + nx: 'NX', + millisecondsToken: 'PX', + milliseconds: number | string + ]): Result<number, Context>; + msetex(...args: [ + numkeys: number | string, + ...data: (RedisKey | string | Buffer | number)[], + nx: 'NX', + unixTimeSecondsToken: 'EXAT', + unixTimeSeconds: number | string, + callback: Callback<number> + ]): Result<number, Context>; + msetex(...args: [ + numkeys: number | string, + ...data: (RedisKey | string | Buffer | number)[], + nx: 'NX', + unixTimeSecondsToken: 'EXAT', + unixTimeSeconds: number | string + ]): Result<number, Context>; + msetex(...args: [ + numkeys: number | string, + ...data: (RedisKey | string | Buffer | number)[], + nx: 'NX', + unixTimeMillisecondsToken: 'PXAT', + unixTimeMilliseconds: number | string, + callback: Callback<number> + ]): Result<number, Context>; + msetex(...args: [ + numkeys: number | string, + ...data: (RedisKey | string | Buffer | number)[], + nx: 'NX', + unixTimeMillisecondsToken: 'PXAT', + unixTimeMilliseconds: number | string + ]): Result<number, Context>; + msetex(...args: [ + numkeys: number | string, + ...data: (RedisKey | string | Buffer | number)[], + nx: 'NX', + keepttl: 'KEEPTTL', + callback: Callback<number> + ]): Result<number, Context>; + msetex(...args: [ + numkeys: number | string, + ...data: (RedisKey | string | Buffer | number)[], + nx: 'NX', + keepttl: 'KEEPTTL' + ]): Result<number, Context>; + msetex(...args: [ + numkeys: number | string, + ...data: (RedisKey | string | Buffer | number)[], + xx: 'XX', + callback: Callback<number> + ]): Result<number, Context>; + msetex(...args: [ + numkeys: number | string, + ...data: (RedisKey | string | Buffer | number)[], + xx: 'XX' + ]): Result<number, Context>; + msetex(...args: [ + numkeys: number | string, + ...data: (RedisKey | string | Buffer | number)[], + xx: 'XX', + secondsToken: 'EX', + seconds: number | string, + callback: Callback<number> + ]): Result<number, Context>; + msetex(...args: [ + numkeys: number | string, + ...data: (RedisKey | string | Buffer | number)[], + xx: 'XX', + secondsToken: 'EX', + seconds: number | string + ]): Result<number, Context>; + msetex(...args: [ + numkeys: number | string, + ...data: (RedisKey | string | Buffer | number)[], + xx: 'XX', + millisecondsToken: 'PX', + milliseconds: number | string, + callback: Callback<number> + ]): Result<number, Context>; + msetex(...args: [ + numkeys: number | string, + ...data: (RedisKey | string | Buffer | number)[], + xx: 'XX', + millisecondsToken: 'PX', + milliseconds: number | string + ]): Result<number, Context>; + msetex(...args: [ + numkeys: number | string, + ...data: (RedisKey | string | Buffer | number)[], + xx: 'XX', + unixTimeSecondsToken: 'EXAT', + unixTimeSeconds: number | string, + callback: Callback<number> + ]): Result<number, Context>; + msetex(...args: [ + numkeys: number | string, + ...data: (RedisKey | string | Buffer | number)[], + xx: 'XX', + unixTimeSecondsToken: 'EXAT', + unixTimeSeconds: number | string + ]): Result<number, Context>; + msetex(...args: [ + numkeys: number | string, + ...data: (RedisKey | string | Buffer | number)[], + xx: 'XX', + unixTimeMillisecondsToken: 'PXAT', + unixTimeMilliseconds: number | string, + callback: Callback<number> + ]): Result<number, Context>; + msetex(...args: [ + numkeys: number | string, + ...data: (RedisKey | string | Buffer | number)[], + xx: 'XX', + unixTimeMillisecondsToken: 'PXAT', + unixTimeMilliseconds: number | string + ]): Result<number, Context>; + msetex(...args: [ + numkeys: number | string, + ...data: (RedisKey | string | Buffer | number)[], + xx: 'XX', + keepttl: 'KEEPTTL', + callback: Callback<number> + ]): Result<number, Context>; + msetex(...args: [ + numkeys: number | string, + ...data: (RedisKey | string | Buffer | number)[], + xx: 'XX', + keepttl: 'KEEPTTL' + ]): Result<number, Context>; + msetnx(object: object, callback?: Callback<"OK">): Result<"OK", Context>; + msetnx(map: Map<string | Buffer | number, string | Buffer | number>, callback?: Callback<"OK">): Result<"OK", Context>; + msetnx(...args: [ + ...keyValues: (RedisKey | string | Buffer | number)[], + callback: Callback<number> + ]): Result<number, Context>; + msetnx(...args: [ + ...keyValues: (RedisKey | string | Buffer | number)[] + ]): Result<number, Context>; + object(subcommand: "ENCODING", key: RedisKey, callback?: Callback<unknown>): Result<unknown, Context>; + object(subcommand: "FREQ", key: RedisKey, callback?: Callback<unknown>): Result<unknown, Context>; + object(subcommand: "HELP", callback?: Callback<unknown>): Result<unknown, Context>; + object(subcommand: "IDLETIME", key: RedisKey, callback?: Callback<unknown>): Result<unknown, Context>; + object(subcommand: "REFCOUNT", key: RedisKey, callback?: Callback<unknown>): Result<unknown, Context>; + persist(key: RedisKey, callback?: Callback<number>): Result<number, Context>; + pexpire(key: RedisKey, milliseconds: number | string, callback?: Callback<number>): Result<number, Context>; + pexpire(key: RedisKey, milliseconds: number | string, nx: "NX", callback?: Callback<number>): Result<number, Context>; + pexpire(key: RedisKey, milliseconds: number | string, xx: "XX", callback?: Callback<number>): Result<number, Context>; + pexpire(key: RedisKey, milliseconds: number | string, gt: "GT", callback?: Callback<number>): Result<number, Context>; + pexpire(key: RedisKey, milliseconds: number | string, lt: "LT", callback?: Callback<number>): Result<number, Context>; + pexpireat(key: RedisKey, unixTimeMilliseconds: number | string, callback?: Callback<number>): Result<number, Context>; + pexpireat(key: RedisKey, unixTimeMilliseconds: number | string, nx: "NX", callback?: Callback<number>): Result<number, Context>; + pexpireat(key: RedisKey, unixTimeMilliseconds: number | string, xx: "XX", callback?: Callback<number>): Result<number, Context>; + pexpireat(key: RedisKey, unixTimeMilliseconds: number | string, gt: "GT", callback?: Callback<number>): Result<number, Context>; + pexpireat(key: RedisKey, unixTimeMilliseconds: number | string, lt: "LT", callback?: Callback<number>): Result<number, Context>; + pexpiretime(key: RedisKey, callback?: Callback<number>): Result<number, Context>; + pfadd(key: RedisKey, callback?: Callback<number>): Result<number, Context>; + pfadd(...args: [ + key: RedisKey, + ...elements: (string | Buffer | number)[], + callback: Callback<number> + ]): Result<number, Context>; + pfadd(...args: [ + key: RedisKey, + ...elements: (string | Buffer | number)[] + ]): Result<number, Context>; + pfcount(...args: [ + ...keys: RedisKey[], + callback: Callback<number> + ]): Result<number, Context>; + pfcount(...args: [ + keys: RedisKey[], + callback: Callback<number> + ]): Result<number, Context>; + pfcount(...args: [ + ...keys: RedisKey[] + ]): Result<number, Context>; + pfcount(...args: [ + keys: RedisKey[] + ]): Result<number, Context>; + pfdebug(subcommand: string | Buffer, key: RedisKey, callback?: Callback<unknown>): Result<unknown, Context>; + pfmerge(...args: [ + destkey: RedisKey, + ...sourcekeys: RedisKey[], + callback: Callback<"OK"> + ]): Result<"OK", Context>; + pfmerge(...args: [ + destkey: RedisKey, + sourcekeys: RedisKey[], + callback: Callback<"OK"> + ]): Result<"OK", Context>; + pfmerge(...args: [ + destkey: RedisKey, + ...sourcekeys: RedisKey[] + ]): Result<"OK", Context>; + pfmerge(...args: [ + destkey: RedisKey, + sourcekeys: RedisKey[] + ]): Result<"OK", Context>; + pfselftest(callback?: Callback<unknown>): Result<unknown, Context>; + ping(callback?: Callback<"PONG">): Result<"PONG", Context>; + ping(message: string | Buffer, callback?: Callback<string>): Result<string, Context>; + pingBuffer(message: string | Buffer, callback?: Callback<Buffer>): Result<Buffer, Context>; + psetex(key: RedisKey, milliseconds: number | string, value: string | Buffer | number, callback?: Callback<unknown>): Result<unknown, Context>; + psubscribe(...args: [ + ...patterns: string[], + callback: Callback<unknown> + ]): Result<unknown, Context>; + psubscribe(...args: [ + ...patterns: string[] + ]): Result<unknown, Context>; + psync(replicationid: string | Buffer | number, offset: number | string, callback?: Callback<unknown>): Result<unknown, Context>; + pttl(key: RedisKey, callback?: Callback<number>): Result<number, Context>; + publish(channel: string | Buffer, message: string | Buffer, callback?: Callback<number>): Result<number, Context>; + pubsub(subcommand: "CHANNELS", callback?: Callback<unknown[]>): Result<unknown[], Context>; + pubsub(subcommand: "CHANNELS", pattern: string, callback?: Callback<unknown[]>): Result<unknown[], Context>; + pubsub(subcommand: "HELP", callback?: Callback<unknown[]>): Result<unknown[], Context>; + pubsub(subcommand: "NUMPAT", callback?: Callback<unknown[]>): Result<unknown[], Context>; + pubsub(subcommand: "NUMSUB", callback?: Callback<unknown[]>): Result<unknown[], Context>; + pubsub(...args: [ + subcommand: "NUMSUB", + ...channels: (string | Buffer)[], + callback: Callback<unknown[]> + ]): Result<unknown[], Context>; + pubsub(...args: [ + subcommand: "NUMSUB", + ...channels: (string | Buffer)[] + ]): Result<unknown[], Context>; + pubsub(subcommand: "SHARDCHANNELS", callback?: Callback<unknown[]>): Result<unknown[], Context>; + pubsub(subcommand: "SHARDCHANNELS", pattern: string, callback?: Callback<unknown[]>): Result<unknown[], Context>; + pubsub(subcommand: "SHARDNUMSUB", callback?: Callback<unknown[]>): Result<unknown[], Context>; + pubsub(...args: [ + subcommand: "SHARDNUMSUB", + ...shardchannels: (string | Buffer)[], + callback: Callback<unknown[]> + ]): Result<unknown[], Context>; + pubsub(...args: [ + subcommand: "SHARDNUMSUB", + ...shardchannels: (string | Buffer)[] + ]): Result<unknown[], Context>; + punsubscribe(callback?: Callback<unknown>): Result<unknown, Context>; + punsubscribe(...args: [ + ...patterns: string[], + callback: Callback<unknown> + ]): Result<unknown, Context>; + punsubscribe(...args: [ + ...patterns: string[] + ]): Result<unknown, Context>; + quit(callback?: Callback<"OK">): Result<"OK", Context>; + randomkey(callback?: Callback<string | null>): Result<string | null, Context>; + randomkeyBuffer(callback?: Callback<Buffer | null>): Result<Buffer | null, Context>; + readonly(callback?: Callback<"OK">): Result<"OK", Context>; + readwrite(callback?: Callback<"OK">): Result<"OK", Context>; + rename(key: RedisKey, newkey: RedisKey, callback?: Callback<"OK">): Result<"OK", Context>; + renamenx(key: RedisKey, newkey: RedisKey, callback?: Callback<number>): Result<number, Context>; + replconf(callback?: Callback<unknown>): Result<unknown, Context>; + replicaof(host: string | Buffer, port: number | string, callback?: Callback<"OK">): Result<"OK", Context>; + reset(callback?: Callback<"OK">): Result<"OK", Context>; + restore(key: RedisKey, ttl: number | string, serializedValue: string | Buffer | number, callback?: Callback<"OK">): Result<"OK", Context>; + restore(key: RedisKey, ttl: number | string, serializedValue: string | Buffer | number, frequencyToken: "FREQ", frequency: number | string, callback?: Callback<"OK">): Result<"OK", Context>; + restore(key: RedisKey, ttl: number | string, serializedValue: string | Buffer | number, secondsToken: "IDLETIME", seconds: number | string, callback?: Callback<"OK">): Result<"OK", Context>; + restore(key: RedisKey, ttl: number | string, serializedValue: string | Buffer | number, secondsToken: "IDLETIME", seconds: number | string, frequencyToken: "FREQ", frequency: number | string, callback?: Callback<"OK">): Result<"OK", Context>; + restore(key: RedisKey, ttl: number | string, serializedValue: string | Buffer | number, absttl: "ABSTTL", callback?: Callback<"OK">): Result<"OK", Context>; + restore(key: RedisKey, ttl: number | string, serializedValue: string | Buffer | number, absttl: "ABSTTL", frequencyToken: "FREQ", frequency: number | string, callback?: Callback<"OK">): Result<"OK", Context>; + restore(key: RedisKey, ttl: number | string, serializedValue: string | Buffer | number, absttl: "ABSTTL", secondsToken: "IDLETIME", seconds: number | string, callback?: Callback<"OK">): Result<"OK", Context>; + restore(key: RedisKey, ttl: number | string, serializedValue: string | Buffer | number, absttl: "ABSTTL", secondsToken: "IDLETIME", seconds: number | string, frequencyToken: "FREQ", frequency: number | string, callback?: Callback<"OK">): Result<"OK", Context>; + restore(key: RedisKey, ttl: number | string, serializedValue: string | Buffer | number, replace: "REPLACE", callback?: Callback<"OK">): Result<"OK", Context>; + restore(key: RedisKey, ttl: number | string, serializedValue: string | Buffer | number, replace: "REPLACE", frequencyToken: "FREQ", frequency: number | string, callback?: Callback<"OK">): Result<"OK", Context>; + restore(key: RedisKey, ttl: number | string, serializedValue: string | Buffer | number, replace: "REPLACE", secondsToken: "IDLETIME", seconds: number | string, callback?: Callback<"OK">): Result<"OK", Context>; + restore(key: RedisKey, ttl: number | string, serializedValue: string | Buffer | number, replace: "REPLACE", secondsToken: "IDLETIME", seconds: number | string, frequencyToken: "FREQ", frequency: number | string, callback?: Callback<"OK">): Result<"OK", Context>; + restore(key: RedisKey, ttl: number | string, serializedValue: string | Buffer | number, replace: "REPLACE", absttl: "ABSTTL", callback?: Callback<"OK">): Result<"OK", Context>; + restore(key: RedisKey, ttl: number | string, serializedValue: string | Buffer | number, replace: "REPLACE", absttl: "ABSTTL", frequencyToken: "FREQ", frequency: number | string, callback?: Callback<"OK">): Result<"OK", Context>; + restore(key: RedisKey, ttl: number | string, serializedValue: string | Buffer | number, replace: "REPLACE", absttl: "ABSTTL", secondsToken: "IDLETIME", seconds: number | string, callback?: Callback<"OK">): Result<"OK", Context>; + restore(key: RedisKey, ttl: number | string, serializedValue: string | Buffer | number, replace: "REPLACE", absttl: "ABSTTL", secondsToken: "IDLETIME", seconds: number | string, frequencyToken: "FREQ", frequency: number | string, callback?: Callback<"OK">): Result<"OK", Context>; + ["restore-asking"](key: RedisKey, ttl: number | string, serializedValue: string | Buffer | number, callback?: Callback<unknown>): Result<unknown, Context>; + ["restore-asking"](key: RedisKey, ttl: number | string, serializedValue: string | Buffer | number, frequencyToken: "FREQ", frequency: number | string, callback?: Callback<unknown>): Result<unknown, Context>; + ["restore-asking"](key: RedisKey, ttl: number | string, serializedValue: string | Buffer | number, secondsToken: "IDLETIME", seconds: number | string, callback?: Callback<unknown>): Result<unknown, Context>; + ["restore-asking"](key: RedisKey, ttl: number | string, serializedValue: string | Buffer | number, secondsToken: "IDLETIME", seconds: number | string, frequencyToken: "FREQ", frequency: number | string, callback?: Callback<unknown>): Result<unknown, Context>; + ["restore-asking"](key: RedisKey, ttl: number | string, serializedValue: string | Buffer | number, absttl: "ABSTTL", callback?: Callback<unknown>): Result<unknown, Context>; + ["restore-asking"](key: RedisKey, ttl: number | string, serializedValue: string | Buffer | number, absttl: "ABSTTL", frequencyToken: "FREQ", frequency: number | string, callback?: Callback<unknown>): Result<unknown, Context>; + ["restore-asking"](key: RedisKey, ttl: number | string, serializedValue: string | Buffer | number, absttl: "ABSTTL", secondsToken: "IDLETIME", seconds: number | string, callback?: Callback<unknown>): Result<unknown, Context>; + ["restore-asking"](key: RedisKey, ttl: number | string, serializedValue: string | Buffer | number, absttl: "ABSTTL", secondsToken: "IDLETIME", seconds: number | string, frequencyToken: "FREQ", frequency: number | string, callback?: Callback<unknown>): Result<unknown, Context>; + ["restore-asking"](key: RedisKey, ttl: number | string, serializedValue: string | Buffer | number, replace: "REPLACE", callback?: Callback<unknown>): Result<unknown, Context>; + ["restore-asking"](key: RedisKey, ttl: number | string, serializedValue: string | Buffer | number, replace: "REPLACE", frequencyToken: "FREQ", frequency: number | string, callback?: Callback<unknown>): Result<unknown, Context>; + ["restore-asking"](key: RedisKey, ttl: number | string, serializedValue: string | Buffer | number, replace: "REPLACE", secondsToken: "IDLETIME", seconds: number | string, callback?: Callback<unknown>): Result<unknown, Context>; + ["restore-asking"](key: RedisKey, ttl: number | string, serializedValue: string | Buffer | number, replace: "REPLACE", secondsToken: "IDLETIME", seconds: number | string, frequencyToken: "FREQ", frequency: number | string, callback?: Callback<unknown>): Result<unknown, Context>; + ["restore-asking"](key: RedisKey, ttl: number | string, serializedValue: string | Buffer | number, replace: "REPLACE", absttl: "ABSTTL", callback?: Callback<unknown>): Result<unknown, Context>; + ["restore-asking"](key: RedisKey, ttl: number | string, serializedValue: string | Buffer | number, replace: "REPLACE", absttl: "ABSTTL", frequencyToken: "FREQ", frequency: number | string, callback?: Callback<unknown>): Result<unknown, Context>; + ["restore-asking"](key: RedisKey, ttl: number | string, serializedValue: string | Buffer | number, replace: "REPLACE", absttl: "ABSTTL", secondsToken: "IDLETIME", seconds: number | string, callback?: Callback<unknown>): Result<unknown, Context>; + ["restore-asking"](key: RedisKey, ttl: number | string, serializedValue: string | Buffer | number, replace: "REPLACE", absttl: "ABSTTL", secondsToken: "IDLETIME", seconds: number | string, frequencyToken: "FREQ", frequency: number | string, callback?: Callback<unknown>): Result<unknown, Context>; + role(callback?: Callback<unknown[]>): Result<unknown[], Context>; + rpop(key: RedisKey, callback?: Callback<string | null>): Result<string | null, Context>; + rpopBuffer(key: RedisKey, callback?: Callback<Buffer | null>): Result<Buffer | null, Context>; + rpop(key: RedisKey, count: number | string, callback?: Callback<string[] | null>): Result<string[] | null, Context>; + rpopBuffer(key: RedisKey, count: number | string, callback?: Callback<Buffer[] | null>): Result<Buffer[] | null, Context>; + rpoplpush(source: RedisKey, destination: RedisKey, callback?: Callback<string>): Result<string, Context>; + rpoplpushBuffer(source: RedisKey, destination: RedisKey, callback?: Callback<Buffer>): Result<Buffer, Context>; + rpush(...args: [ + key: RedisKey, + ...elements: (string | Buffer | number)[], + callback: Callback<number> + ]): Result<number, Context>; + rpush(...args: [ + key: RedisKey, + ...elements: (string | Buffer | number)[] + ]): Result<number, Context>; + rpushx(...args: [ + key: RedisKey, + ...elements: (string | Buffer | number)[], + callback: Callback<number> + ]): Result<number, Context>; + rpushx(...args: [ + key: RedisKey, + ...elements: (string | Buffer | number)[] + ]): Result<number, Context>; + sadd(...args: [ + key: RedisKey, + ...members: (string | Buffer | number)[], + callback: Callback<number> + ]): Result<number, Context>; + sadd(...args: [ + key: RedisKey, + members: (string | Buffer | number)[], + callback: Callback<number> + ]): Result<number, Context>; + sadd(...args: [ + key: RedisKey, + ...members: (string | Buffer | number)[] + ]): Result<number, Context>; + sadd(...args: [ + key: RedisKey, + members: (string | Buffer | number)[] + ]): Result<number, Context>; + save(callback?: Callback<"OK">): Result<"OK", Context>; + scan(cursor: number | string, callback?: Callback<[ + cursor: string, + elements: string[] + ]>): Result<[ + cursor: string, + elements: string[] + ], Context>; + scanBuffer(cursor: number | string, callback?: Callback<[ + cursor: Buffer, + elements: Buffer[] + ]>): Result<[ + cursor: Buffer, + elements: Buffer[] + ], Context>; + scan(cursor: number | string, typeToken: "TYPE", type: string | Buffer, callback?: Callback<[ + cursor: string, + elements: string[] + ]>): Result<[ + cursor: string, + elements: string[] + ], Context>; + scanBuffer(cursor: number | string, typeToken: "TYPE", type: string | Buffer, callback?: Callback<[ + cursor: Buffer, + elements: Buffer[] + ]>): Result<[ + cursor: Buffer, + elements: Buffer[] + ], Context>; + scan(cursor: number | string, countToken: "COUNT", count: number | string, callback?: Callback<[ + cursor: string, + elements: string[] + ]>): Result<[ + cursor: string, + elements: string[] + ], Context>; + scanBuffer(cursor: number | string, countToken: "COUNT", count: number | string, callback?: Callback<[ + cursor: Buffer, + elements: Buffer[] + ]>): Result<[ + cursor: Buffer, + elements: Buffer[] + ], Context>; + scan(cursor: number | string, countToken: "COUNT", count: number | string, typeToken: "TYPE", type: string | Buffer, callback?: Callback<[ + cursor: string, + elements: string[] + ]>): Result<[ + cursor: string, + elements: string[] + ], Context>; + scanBuffer(cursor: number | string, countToken: "COUNT", count: number | string, typeToken: "TYPE", type: string | Buffer, callback?: Callback<[ + cursor: Buffer, + elements: Buffer[] + ]>): Result<[ + cursor: Buffer, + elements: Buffer[] + ], Context>; + scan(cursor: number | string, patternToken: "MATCH", pattern: string, callback?: Callback<[ + cursor: string, + elements: string[] + ]>): Result<[ + cursor: string, + elements: string[] + ], Context>; + scanBuffer(cursor: number | string, patternToken: "MATCH", pattern: string, callback?: Callback<[ + cursor: Buffer, + elements: Buffer[] + ]>): Result<[ + cursor: Buffer, + elements: Buffer[] + ], Context>; + scan(cursor: number | string, patternToken: "MATCH", pattern: string, typeToken: "TYPE", type: string | Buffer, callback?: Callback<[ + cursor: string, + elements: string[] + ]>): Result<[ + cursor: string, + elements: string[] + ], Context>; + scanBuffer(cursor: number | string, patternToken: "MATCH", pattern: string, typeToken: "TYPE", type: string | Buffer, callback?: Callback<[ + cursor: Buffer, + elements: Buffer[] + ]>): Result<[ + cursor: Buffer, + elements: Buffer[] + ], Context>; + scan(cursor: number | string, patternToken: "MATCH", pattern: string, countToken: "COUNT", count: number | string, callback?: Callback<[ + cursor: string, + elements: string[] + ]>): Result<[ + cursor: string, + elements: string[] + ], Context>; + scanBuffer(cursor: number | string, patternToken: "MATCH", pattern: string, countToken: "COUNT", count: number | string, callback?: Callback<[ + cursor: Buffer, + elements: Buffer[] + ]>): Result<[ + cursor: Buffer, + elements: Buffer[] + ], Context>; + scan(cursor: number | string, patternToken: "MATCH", pattern: string, countToken: "COUNT", count: number | string, typeToken: "TYPE", type: string | Buffer, callback?: Callback<[ + cursor: string, + elements: string[] + ]>): Result<[ + cursor: string, + elements: string[] + ], Context>; + scanBuffer(cursor: number | string, patternToken: "MATCH", pattern: string, countToken: "COUNT", count: number | string, typeToken: "TYPE", type: string | Buffer, callback?: Callback<[ + cursor: Buffer, + elements: Buffer[] + ]>): Result<[ + cursor: Buffer, + elements: Buffer[] + ], Context>; + scard(key: RedisKey, callback?: Callback<number>): Result<number, Context>; + script(subcommand: "DEBUG", yes: "YES", callback?: Callback<unknown>): Result<unknown, Context>; + script(subcommand: "DEBUG", sync: "SYNC", callback?: Callback<unknown>): Result<unknown, Context>; + script(subcommand: "DEBUG", no: "NO", callback?: Callback<unknown>): Result<unknown, Context>; + script(...args: [ + subcommand: "EXISTS", + ...sha1s: (string | Buffer)[], + callback: Callback<unknown> + ]): Result<unknown, Context>; + script(...args: [ + subcommand: "EXISTS", + ...sha1s: (string | Buffer)[] + ]): Result<unknown, Context>; + script(subcommand: "FLUSH", callback?: Callback<unknown>): Result<unknown, Context>; + script(subcommand: "FLUSH", async: "ASYNC", callback?: Callback<unknown>): Result<unknown, Context>; + script(subcommand: "FLUSH", sync: "SYNC", callback?: Callback<unknown>): Result<unknown, Context>; + script(subcommand: "HELP", callback?: Callback<unknown>): Result<unknown, Context>; + script(subcommand: "KILL", callback?: Callback<unknown>): Result<unknown, Context>; + script(subcommand: "LOAD", script: string | Buffer, callback?: Callback<unknown>): Result<unknown, Context>; + sdiff(...args: [ + ...keys: RedisKey[], + callback: Callback<string[]> + ]): Result<string[], Context>; + sdiffBuffer(...args: [ + ...keys: RedisKey[], + callback: Callback<Buffer[]> + ]): Result<Buffer[], Context>; + sdiff(...args: [ + keys: RedisKey[], + callback: Callback<string[]> + ]): Result<string[], Context>; + sdiffBuffer(...args: [ + keys: RedisKey[], + callback: Callback<Buffer[]> + ]): Result<Buffer[], Context>; + sdiff(...args: [ + ...keys: RedisKey[] + ]): Result<string[], Context>; + sdiffBuffer(...args: [ + ...keys: RedisKey[] + ]): Result<Buffer[], Context>; + sdiff(...args: [ + keys: RedisKey[] + ]): Result<string[], Context>; + sdiffBuffer(...args: [ + keys: RedisKey[] + ]): Result<Buffer[], Context>; + sdiffstore(...args: [ + destination: RedisKey, + ...keys: RedisKey[], + callback: Callback<number> + ]): Result<number, Context>; + sdiffstore(...args: [ + destination: RedisKey, + keys: RedisKey[], + callback: Callback<number> + ]): Result<number, Context>; + sdiffstore(...args: [ + destination: RedisKey, + ...keys: RedisKey[] + ]): Result<number, Context>; + sdiffstore(...args: [ + destination: RedisKey, + keys: RedisKey[] + ]): Result<number, Context>; + select(index: number | string, callback?: Callback<"OK">): Result<"OK", Context>; + set(key: RedisKey, value: string | Buffer | number, callback?: Callback<"OK">): Result<"OK", Context>; + set(key: RedisKey, value: string | Buffer | number, get: "GET", callback?: Callback<string | null>): Result<string | null, Context>; + setBuffer(key: RedisKey, value: string | Buffer | number, get: "GET", callback?: Callback<Buffer | null>): Result<Buffer | null, Context>; + set(key: RedisKey, value: string | Buffer | number, nx: "NX", callback?: Callback<"OK" | null>): Result<"OK" | null, Context>; + set(key: RedisKey, value: string | Buffer | number, nx: "NX", get: "GET", callback?: Callback<string | null>): Result<string | null, Context>; + setBuffer(key: RedisKey, value: string | Buffer | number, nx: "NX", get: "GET", callback?: Callback<Buffer | null>): Result<Buffer | null, Context>; + set(key: RedisKey, value: string | Buffer | number, xx: "XX", callback?: Callback<"OK" | null>): Result<"OK" | null, Context>; + set(key: RedisKey, value: string | Buffer | number, xx: "XX", get: "GET", callback?: Callback<string | null>): Result<string | null, Context>; + setBuffer(key: RedisKey, value: string | Buffer | number, xx: "XX", get: "GET", callback?: Callback<Buffer | null>): Result<Buffer | null, Context>; + set(key: RedisKey, value: string | Buffer | number, secondsToken: "EX", seconds: number | string, callback?: Callback<"OK">): Result<"OK", Context>; + set(key: RedisKey, value: string | Buffer | number, secondsToken: "EX", seconds: number | string, get: "GET", callback?: Callback<string | null>): Result<string | null, Context>; + setBuffer(key: RedisKey, value: string | Buffer | number, secondsToken: "EX", seconds: number | string, get: "GET", callback?: Callback<Buffer | null>): Result<Buffer | null, Context>; + set(key: RedisKey, value: string | Buffer | number, secondsToken: "EX", seconds: number | string, nx: "NX", callback?: Callback<"OK" | null>): Result<"OK" | null, Context>; + set(key: RedisKey, value: string | Buffer | number, secondsToken: "EX", seconds: number | string, nx: "NX", get: "GET", callback?: Callback<string | null>): Result<string | null, Context>; + setBuffer(key: RedisKey, value: string | Buffer | number, secondsToken: "EX", seconds: number | string, nx: "NX", get: "GET", callback?: Callback<Buffer | null>): Result<Buffer | null, Context>; + set(key: RedisKey, value: string | Buffer | number, secondsToken: "EX", seconds: number | string, xx: "XX", callback?: Callback<"OK" | null>): Result<"OK" | null, Context>; + set(key: RedisKey, value: string | Buffer | number, secondsToken: "EX", seconds: number | string, xx: "XX", get: "GET", callback?: Callback<string | null>): Result<string | null, Context>; + setBuffer(key: RedisKey, value: string | Buffer | number, secondsToken: "EX", seconds: number | string, xx: "XX", get: "GET", callback?: Callback<Buffer | null>): Result<Buffer | null, Context>; + set(key: RedisKey, value: string | Buffer | number, millisecondsToken: "PX", milliseconds: number | string, callback?: Callback<"OK">): Result<"OK", Context>; + set(key: RedisKey, value: string | Buffer | number, millisecondsToken: "PX", milliseconds: number | string, get: "GET", callback?: Callback<string | null>): Result<string | null, Context>; + setBuffer(key: RedisKey, value: string | Buffer | number, millisecondsToken: "PX", milliseconds: number | string, get: "GET", callback?: Callback<Buffer | null>): Result<Buffer | null, Context>; + set(key: RedisKey, value: string | Buffer | number, millisecondsToken: "PX", milliseconds: number | string, nx: "NX", callback?: Callback<"OK" | null>): Result<"OK" | null, Context>; + set(key: RedisKey, value: string | Buffer | number, millisecondsToken: "PX", milliseconds: number | string, nx: "NX", get: "GET", callback?: Callback<string | null>): Result<string | null, Context>; + setBuffer(key: RedisKey, value: string | Buffer | number, millisecondsToken: "PX", milliseconds: number | string, nx: "NX", get: "GET", callback?: Callback<Buffer | null>): Result<Buffer | null, Context>; + set(key: RedisKey, value: string | Buffer | number, millisecondsToken: "PX", milliseconds: number | string, xx: "XX", callback?: Callback<"OK" | null>): Result<"OK" | null, Context>; + set(key: RedisKey, value: string | Buffer | number, millisecondsToken: "PX", milliseconds: number | string, xx: "XX", get: "GET", callback?: Callback<string | null>): Result<string | null, Context>; + setBuffer(key: RedisKey, value: string | Buffer | number, millisecondsToken: "PX", milliseconds: number | string, xx: "XX", get: "GET", callback?: Callback<Buffer | null>): Result<Buffer | null, Context>; + set(key: RedisKey, value: string | Buffer | number, unixTimeSecondsToken: "EXAT", unixTimeSeconds: number | string, callback?: Callback<"OK">): Result<"OK", Context>; + set(key: RedisKey, value: string | Buffer | number, unixTimeSecondsToken: "EXAT", unixTimeSeconds: number | string, get: "GET", callback?: Callback<string | null>): Result<string | null, Context>; + setBuffer(key: RedisKey, value: string | Buffer | number, unixTimeSecondsToken: "EXAT", unixTimeSeconds: number | string, get: "GET", callback?: Callback<Buffer | null>): Result<Buffer | null, Context>; + set(key: RedisKey, value: string | Buffer | number, unixTimeSecondsToken: "EXAT", unixTimeSeconds: number | string, nx: "NX", callback?: Callback<"OK" | null>): Result<"OK" | null, Context>; + set(key: RedisKey, value: string | Buffer | number, unixTimeSecondsToken: "EXAT", unixTimeSeconds: number | string, nx: "NX", get: "GET", callback?: Callback<string | null>): Result<string | null, Context>; + setBuffer(key: RedisKey, value: string | Buffer | number, unixTimeSecondsToken: "EXAT", unixTimeSeconds: number | string, nx: "NX", get: "GET", callback?: Callback<Buffer | null>): Result<Buffer | null, Context>; + set(key: RedisKey, value: string | Buffer | number, unixTimeSecondsToken: "EXAT", unixTimeSeconds: number | string, xx: "XX", callback?: Callback<"OK" | null>): Result<"OK" | null, Context>; + set(key: RedisKey, value: string | Buffer | number, unixTimeSecondsToken: "EXAT", unixTimeSeconds: number | string, xx: "XX", get: "GET", callback?: Callback<string | null>): Result<string | null, Context>; + setBuffer(key: RedisKey, value: string | Buffer | number, unixTimeSecondsToken: "EXAT", unixTimeSeconds: number | string, xx: "XX", get: "GET", callback?: Callback<Buffer | null>): Result<Buffer | null, Context>; + set(key: RedisKey, value: string | Buffer | number, unixTimeMillisecondsToken: "PXAT", unixTimeMilliseconds: number | string, callback?: Callback<"OK">): Result<"OK", Context>; + set(key: RedisKey, value: string | Buffer | number, unixTimeMillisecondsToken: "PXAT", unixTimeMilliseconds: number | string, get: "GET", callback?: Callback<string | null>): Result<string | null, Context>; + setBuffer(key: RedisKey, value: string | Buffer | number, unixTimeMillisecondsToken: "PXAT", unixTimeMilliseconds: number | string, get: "GET", callback?: Callback<Buffer | null>): Result<Buffer | null, Context>; + set(key: RedisKey, value: string | Buffer | number, unixTimeMillisecondsToken: "PXAT", unixTimeMilliseconds: number | string, nx: "NX", callback?: Callback<"OK" | null>): Result<"OK" | null, Context>; + set(key: RedisKey, value: string | Buffer | number, unixTimeMillisecondsToken: "PXAT", unixTimeMilliseconds: number | string, nx: "NX", get: "GET", callback?: Callback<string | null>): Result<string | null, Context>; + setBuffer(key: RedisKey, value: string | Buffer | number, unixTimeMillisecondsToken: "PXAT", unixTimeMilliseconds: number | string, nx: "NX", get: "GET", callback?: Callback<Buffer | null>): Result<Buffer | null, Context>; + set(key: RedisKey, value: string | Buffer | number, unixTimeMillisecondsToken: "PXAT", unixTimeMilliseconds: number | string, xx: "XX", callback?: Callback<"OK" | null>): Result<"OK" | null, Context>; + set(key: RedisKey, value: string | Buffer | number, unixTimeMillisecondsToken: "PXAT", unixTimeMilliseconds: number | string, xx: "XX", get: "GET", callback?: Callback<string | null>): Result<string | null, Context>; + setBuffer(key: RedisKey, value: string | Buffer | number, unixTimeMillisecondsToken: "PXAT", unixTimeMilliseconds: number | string, xx: "XX", get: "GET", callback?: Callback<Buffer | null>): Result<Buffer | null, Context>; + set(key: RedisKey, value: string | Buffer | number, keepttl: "KEEPTTL", callback?: Callback<"OK">): Result<"OK", Context>; + set(key: RedisKey, value: string | Buffer | number, keepttl: "KEEPTTL", get: "GET", callback?: Callback<string | null>): Result<string | null, Context>; + setBuffer(key: RedisKey, value: string | Buffer | number, keepttl: "KEEPTTL", get: "GET", callback?: Callback<Buffer | null>): Result<Buffer | null, Context>; + set(key: RedisKey, value: string | Buffer | number, keepttl: "KEEPTTL", nx: "NX", callback?: Callback<"OK" | null>): Result<"OK" | null, Context>; + set(key: RedisKey, value: string | Buffer | number, keepttl: "KEEPTTL", nx: "NX", get: "GET", callback?: Callback<string | null>): Result<string | null, Context>; + setBuffer(key: RedisKey, value: string | Buffer | number, keepttl: "KEEPTTL", nx: "NX", get: "GET", callback?: Callback<Buffer | null>): Result<Buffer | null, Context>; + set(key: RedisKey, value: string | Buffer | number, keepttl: "KEEPTTL", xx: "XX", callback?: Callback<"OK" | null>): Result<"OK" | null, Context>; + set(key: RedisKey, value: string | Buffer | number, keepttl: "KEEPTTL", xx: "XX", get: "GET", callback?: Callback<string | null>): Result<string | null, Context>; + setBuffer(key: RedisKey, value: string | Buffer | number, keepttl: "KEEPTTL", xx: "XX", get: "GET", callback?: Callback<Buffer | null>): Result<Buffer | null, Context>; + setbit(key: RedisKey, offset: number | string, value: number | string, callback?: Callback<number>): Result<number, Context>; + setex(key: RedisKey, seconds: number | string, value: string | Buffer | number, callback?: Callback<"OK">): Result<"OK", Context>; + setnx(key: RedisKey, value: string | Buffer | number, callback?: Callback<number>): Result<number, Context>; + setrange(key: RedisKey, offset: number | string, value: string | Buffer | number, callback?: Callback<number>): Result<number, Context>; + shutdown(callback?: Callback<"OK">): Result<"OK", Context>; + shutdown(abort: "ABORT", callback?: Callback<"OK">): Result<"OK", Context>; + shutdown(force: "FORCE", callback?: Callback<"OK">): Result<"OK", Context>; + shutdown(force: "FORCE", abort: "ABORT", callback?: Callback<"OK">): Result<"OK", Context>; + shutdown(now: "NOW", callback?: Callback<"OK">): Result<"OK", Context>; + shutdown(now: "NOW", abort: "ABORT", callback?: Callback<"OK">): Result<"OK", Context>; + shutdown(now: "NOW", force: "FORCE", callback?: Callback<"OK">): Result<"OK", Context>; + shutdown(now: "NOW", force: "FORCE", abort: "ABORT", callback?: Callback<"OK">): Result<"OK", Context>; + shutdown(nosave: "NOSAVE", callback?: Callback<"OK">): Result<"OK", Context>; + shutdown(nosave: "NOSAVE", abort: "ABORT", callback?: Callback<"OK">): Result<"OK", Context>; + shutdown(nosave: "NOSAVE", force: "FORCE", callback?: Callback<"OK">): Result<"OK", Context>; + shutdown(nosave: "NOSAVE", force: "FORCE", abort: "ABORT", callback?: Callback<"OK">): Result<"OK", Context>; + shutdown(nosave: "NOSAVE", now: "NOW", callback?: Callback<"OK">): Result<"OK", Context>; + shutdown(nosave: "NOSAVE", now: "NOW", abort: "ABORT", callback?: Callback<"OK">): Result<"OK", Context>; + shutdown(nosave: "NOSAVE", now: "NOW", force: "FORCE", callback?: Callback<"OK">): Result<"OK", Context>; + shutdown(nosave: "NOSAVE", now: "NOW", force: "FORCE", abort: "ABORT", callback?: Callback<"OK">): Result<"OK", Context>; + shutdown(save: "SAVE", callback?: Callback<"OK">): Result<"OK", Context>; + shutdown(save: "SAVE", abort: "ABORT", callback?: Callback<"OK">): Result<"OK", Context>; + shutdown(save: "SAVE", force: "FORCE", callback?: Callback<"OK">): Result<"OK", Context>; + shutdown(save: "SAVE", force: "FORCE", abort: "ABORT", callback?: Callback<"OK">): Result<"OK", Context>; + shutdown(save: "SAVE", now: "NOW", callback?: Callback<"OK">): Result<"OK", Context>; + shutdown(save: "SAVE", now: "NOW", abort: "ABORT", callback?: Callback<"OK">): Result<"OK", Context>; + shutdown(save: "SAVE", now: "NOW", force: "FORCE", callback?: Callback<"OK">): Result<"OK", Context>; + shutdown(save: "SAVE", now: "NOW", force: "FORCE", abort: "ABORT", callback?: Callback<"OK">): Result<"OK", Context>; + sinter(...args: [ + ...keys: RedisKey[], + callback: Callback<string[]> + ]): Result<string[], Context>; + sinterBuffer(...args: [ + ...keys: RedisKey[], + callback: Callback<Buffer[]> + ]): Result<Buffer[], Context>; + sinter(...args: [ + keys: RedisKey[], + callback: Callback<string[]> + ]): Result<string[], Context>; + sinterBuffer(...args: [ + keys: RedisKey[], + callback: Callback<Buffer[]> + ]): Result<Buffer[], Context>; + sinter(...args: [ + ...keys: RedisKey[] + ]): Result<string[], Context>; + sinterBuffer(...args: [ + ...keys: RedisKey[] + ]): Result<Buffer[], Context>; + sinter(...args: [ + keys: RedisKey[] + ]): Result<string[], Context>; + sinterBuffer(...args: [ + keys: RedisKey[] + ]): Result<Buffer[], Context>; + sintercard(...args: [ + numkeys: number | string, + ...keys: RedisKey[], + callback: Callback<number> + ]): Result<number, Context>; + sintercard(...args: [ + numkeys: number | string, + keys: RedisKey[], + callback: Callback<number> + ]): Result<number, Context>; + sintercard(...args: [ + numkeys: number | string, + ...keys: RedisKey[] + ]): Result<number, Context>; + sintercard(...args: [ + numkeys: number | string, + keys: RedisKey[] + ]): Result<number, Context>; + sintercard(...args: [ + numkeys: number | string, + ...keys: RedisKey[], + limitToken: "LIMIT", + limit: number | string, + callback: Callback<number> + ]): Result<number, Context>; + sintercard(...args: [ + numkeys: number | string, + keys: RedisKey[], + limitToken: "LIMIT", + limit: number | string, + callback: Callback<number> + ]): Result<number, Context>; + sintercard(...args: [ + numkeys: number | string, + ...keys: RedisKey[], + limitToken: "LIMIT", + limit: number | string + ]): Result<number, Context>; + sintercard(...args: [ + numkeys: number | string, + keys: RedisKey[], + limitToken: "LIMIT", + limit: number | string + ]): Result<number, Context>; + sinterstore(...args: [ + destination: RedisKey, + ...keys: RedisKey[], + callback: Callback<number> + ]): Result<number, Context>; + sinterstore(...args: [ + destination: RedisKey, + keys: RedisKey[], + callback: Callback<number> + ]): Result<number, Context>; + sinterstore(...args: [ + destination: RedisKey, + ...keys: RedisKey[] + ]): Result<number, Context>; + sinterstore(...args: [ + destination: RedisKey, + keys: RedisKey[] + ]): Result<number, Context>; + sismember(key: RedisKey, member: string | Buffer | number, callback?: Callback<number>): Result<number, Context>; + slaveof(host: string | Buffer, port: number | string, callback?: Callback<"OK">): Result<"OK", Context>; + slowlog(subcommand: "GET", callback?: Callback<unknown>): Result<unknown, Context>; + slowlog(subcommand: "GET", count: number | string, callback?: Callback<unknown>): Result<unknown, Context>; + slowlog(subcommand: "HELP", callback?: Callback<unknown>): Result<unknown, Context>; + slowlog(subcommand: "LEN", callback?: Callback<unknown>): Result<unknown, Context>; + slowlog(subcommand: "RESET", callback?: Callback<unknown>): Result<unknown, Context>; + smembers(key: RedisKey, callback?: Callback<string[]>): Result<string[], Context>; + smembersBuffer(key: RedisKey, callback?: Callback<Buffer[]>): Result<Buffer[], Context>; + smismember(...args: [ + key: RedisKey, + ...members: (string | Buffer | number)[], + callback: Callback<number[]> + ]): Result<number[], Context>; + smismember(...args: [ + key: RedisKey, + members: (string | Buffer | number)[], + callback: Callback<number[]> + ]): Result<number[], Context>; + smismember(...args: [ + key: RedisKey, + ...members: (string | Buffer | number)[] + ]): Result<number[], Context>; + smismember(...args: [ + key: RedisKey, + members: (string | Buffer | number)[] + ]): Result<number[], Context>; + smove(source: RedisKey, destination: RedisKey, member: string | Buffer | number, callback?: Callback<number>): Result<number, Context>; + sort(...args: [ + key: RedisKey, + ...args: RedisValue[], + callback: Callback<unknown> + ]): Result<unknown, Context>; + sort(...args: [ + key: RedisKey, + ...args: RedisValue[] + ]): Result<unknown, Context>; + sort_ro(key: RedisKey, callback?: Callback<unknown>): Result<unknown, Context>; + sort_ro(key: RedisKey, alpha: "ALPHA", callback?: Callback<unknown>): Result<unknown, Context>; + sort_ro(key: RedisKey, asc: "ASC", callback?: Callback<unknown>): Result<unknown, Context>; + sort_ro(key: RedisKey, asc: "ASC", alpha: "ALPHA", callback?: Callback<unknown>): Result<unknown, Context>; + sort_ro(key: RedisKey, desc: "DESC", callback?: Callback<unknown>): Result<unknown, Context>; + sort_ro(key: RedisKey, desc: "DESC", alpha: "ALPHA", callback?: Callback<unknown>): Result<unknown, Context>; + sort_ro(...args: [ + key: RedisKey, + patternToken: "GET", + ...patterns: string[], + callback: Callback<unknown> + ]): Result<unknown, Context>; + sort_ro(...args: [ + key: RedisKey, + patternToken: "GET", + ...patterns: string[] + ]): Result<unknown, Context>; + sort_ro(...args: [ + key: RedisKey, + patternToken: "GET", + ...patterns: string[], + alpha: "ALPHA", + callback: Callback<unknown> + ]): Result<unknown, Context>; + sort_ro(...args: [ + key: RedisKey, + patternToken: "GET", + ...patterns: string[], + alpha: "ALPHA" + ]): Result<unknown, Context>; + sort_ro(...args: [ + key: RedisKey, + patternToken: "GET", + ...patterns: string[], + asc: "ASC", + callback: Callback<unknown> + ]): Result<unknown, Context>; + sort_ro(...args: [ + key: RedisKey, + patternToken: "GET", + ...patterns: string[], + asc: "ASC" + ]): Result<unknown, Context>; + sort_ro(...args: [ + key: RedisKey, + patternToken: "GET", + ...patterns: string[], + asc: "ASC", + alpha: "ALPHA", + callback: Callback<unknown> + ]): Result<unknown, Context>; + sort_ro(...args: [ + key: RedisKey, + patternToken: "GET", + ...patterns: string[], + asc: "ASC", + alpha: "ALPHA" + ]): Result<unknown, Context>; + sort_ro(...args: [ + key: RedisKey, + patternToken: "GET", + ...patterns: string[], + desc: "DESC", + callback: Callback<unknown> + ]): Result<unknown, Context>; + sort_ro(...args: [ + key: RedisKey, + patternToken: "GET", + ...patterns: string[], + desc: "DESC" + ]): Result<unknown, Context>; + sort_ro(...args: [ + key: RedisKey, + patternToken: "GET", + ...patterns: string[], + desc: "DESC", + alpha: "ALPHA", + callback: Callback<unknown> + ]): Result<unknown, Context>; + sort_ro(...args: [ + key: RedisKey, + patternToken: "GET", + ...patterns: string[], + desc: "DESC", + alpha: "ALPHA" + ]): Result<unknown, Context>; + sort_ro(key: RedisKey, offsetCountToken: "LIMIT", offset: number | string, count: number | string, callback?: Callback<unknown>): Result<unknown, Context>; + sort_ro(key: RedisKey, offsetCountToken: "LIMIT", offset: number | string, count: number | string, alpha: "ALPHA", callback?: Callback<unknown>): Result<unknown, Context>; + sort_ro(key: RedisKey, offsetCountToken: "LIMIT", offset: number | string, count: number | string, asc: "ASC", callback?: Callback<unknown>): Result<unknown, Context>; + sort_ro(key: RedisKey, offsetCountToken: "LIMIT", offset: number | string, count: number | string, asc: "ASC", alpha: "ALPHA", callback?: Callback<unknown>): Result<unknown, Context>; + sort_ro(key: RedisKey, offsetCountToken: "LIMIT", offset: number | string, count: number | string, desc: "DESC", callback?: Callback<unknown>): Result<unknown, Context>; + sort_ro(key: RedisKey, offsetCountToken: "LIMIT", offset: number | string, count: number | string, desc: "DESC", alpha: "ALPHA", callback?: Callback<unknown>): Result<unknown, Context>; + sort_ro(...args: [ + key: RedisKey, + offsetCountToken: "LIMIT", + offset: number | string, + count: number | string, + patternToken: "GET", + ...patterns: string[], + callback: Callback<unknown> + ]): Result<unknown, Context>; + sort_ro(...args: [ + key: RedisKey, + offsetCountToken: "LIMIT", + offset: number | string, + count: number | string, + patternToken: "GET", + ...patterns: string[] + ]): Result<unknown, Context>; + sort_ro(...args: [ + key: RedisKey, + offsetCountToken: "LIMIT", + offset: number | string, + count: number | string, + patternToken: "GET", + ...patterns: string[], + alpha: "ALPHA", + callback: Callback<unknown> + ]): Result<unknown, Context>; + sort_ro(...args: [ + key: RedisKey, + offsetCountToken: "LIMIT", + offset: number | string, + count: number | string, + patternToken: "GET", + ...patterns: string[], + alpha: "ALPHA" + ]): Result<unknown, Context>; + sort_ro(...args: [ + key: RedisKey, + offsetCountToken: "LIMIT", + offset: number | string, + count: number | string, + patternToken: "GET", + ...patterns: string[], + asc: "ASC", + callback: Callback<unknown> + ]): Result<unknown, Context>; + sort_ro(...args: [ + key: RedisKey, + offsetCountToken: "LIMIT", + offset: number | string, + count: number | string, + patternToken: "GET", + ...patterns: string[], + asc: "ASC" + ]): Result<unknown, Context>; + sort_ro(...args: [ + key: RedisKey, + offsetCountToken: "LIMIT", + offset: number | string, + count: number | string, + patternToken: "GET", + ...patterns: string[], + asc: "ASC", + alpha: "ALPHA", + callback: Callback<unknown> + ]): Result<unknown, Context>; + sort_ro(...args: [ + key: RedisKey, + offsetCountToken: "LIMIT", + offset: number | string, + count: number | string, + patternToken: "GET", + ...patterns: string[], + asc: "ASC", + alpha: "ALPHA" + ]): Result<unknown, Context>; + sort_ro(...args: [ + key: RedisKey, + offsetCountToken: "LIMIT", + offset: number | string, + count: number | string, + patternToken: "GET", + ...patterns: string[], + desc: "DESC", + callback: Callback<unknown> + ]): Result<unknown, Context>; + sort_ro(...args: [ + key: RedisKey, + offsetCountToken: "LIMIT", + offset: number | string, + count: number | string, + patternToken: "GET", + ...patterns: string[], + desc: "DESC" + ]): Result<unknown, Context>; + sort_ro(...args: [ + key: RedisKey, + offsetCountToken: "LIMIT", + offset: number | string, + count: number | string, + patternToken: "GET", + ...patterns: string[], + desc: "DESC", + alpha: "ALPHA", + callback: Callback<unknown> + ]): Result<unknown, Context>; + sort_ro(...args: [ + key: RedisKey, + offsetCountToken: "LIMIT", + offset: number | string, + count: number | string, + patternToken: "GET", + ...patterns: string[], + desc: "DESC", + alpha: "ALPHA" + ]): Result<unknown, Context>; + sort_ro(key: RedisKey, patternToken: "BY", pattern: string, callback?: Callback<unknown>): Result<unknown, Context>; + sort_ro(key: RedisKey, patternToken: "BY", pattern: string, alpha: "ALPHA", callback?: Callback<unknown>): Result<unknown, Context>; + sort_ro(key: RedisKey, patternToken: "BY", pattern: string, asc: "ASC", callback?: Callback<unknown>): Result<unknown, Context>; + sort_ro(key: RedisKey, patternToken: "BY", pattern: string, asc: "ASC", alpha: "ALPHA", callback?: Callback<unknown>): Result<unknown, Context>; + sort_ro(key: RedisKey, patternToken: "BY", pattern: string, desc: "DESC", callback?: Callback<unknown>): Result<unknown, Context>; + sort_ro(key: RedisKey, patternToken: "BY", pattern: string, desc: "DESC", alpha: "ALPHA", callback?: Callback<unknown>): Result<unknown, Context>; + sort_ro(...args: [ + key: RedisKey, + patternToken: "BY", + pattern: string, + patternToken1: "GET", + ...pattern1s: string[], + callback: Callback<unknown> + ]): Result<unknown, Context>; + sort_ro(...args: [ + key: RedisKey, + patternToken: "BY", + pattern: string, + patternToken1: "GET", + ...pattern1s: string[] + ]): Result<unknown, Context>; + sort_ro(...args: [ + key: RedisKey, + patternToken: "BY", + pattern: string, + patternToken1: "GET", + ...pattern1s: string[], + alpha: "ALPHA", + callback: Callback<unknown> + ]): Result<unknown, Context>; + sort_ro(...args: [ + key: RedisKey, + patternToken: "BY", + pattern: string, + patternToken1: "GET", + ...pattern1s: string[], + alpha: "ALPHA" + ]): Result<unknown, Context>; + sort_ro(...args: [ + key: RedisKey, + patternToken: "BY", + pattern: string, + patternToken1: "GET", + ...pattern1s: string[], + asc: "ASC", + callback: Callback<unknown> + ]): Result<unknown, Context>; + sort_ro(...args: [ + key: RedisKey, + patternToken: "BY", + pattern: string, + patternToken1: "GET", + ...pattern1s: string[], + asc: "ASC" + ]): Result<unknown, Context>; + sort_ro(...args: [ + key: RedisKey, + patternToken: "BY", + pattern: string, + patternToken1: "GET", + ...pattern1s: string[], + asc: "ASC", + alpha: "ALPHA", + callback: Callback<unknown> + ]): Result<unknown, Context>; + sort_ro(...args: [ + key: RedisKey, + patternToken: "BY", + pattern: string, + patternToken1: "GET", + ...pattern1s: string[], + asc: "ASC", + alpha: "ALPHA" + ]): Result<unknown, Context>; + sort_ro(...args: [ + key: RedisKey, + patternToken: "BY", + pattern: string, + patternToken1: "GET", + ...pattern1s: string[], + desc: "DESC", + callback: Callback<unknown> + ]): Result<unknown, Context>; + sort_ro(...args: [ + key: RedisKey, + patternToken: "BY", + pattern: string, + patternToken1: "GET", + ...pattern1s: string[], + desc: "DESC" + ]): Result<unknown, Context>; + sort_ro(...args: [ + key: RedisKey, + patternToken: "BY", + pattern: string, + patternToken1: "GET", + ...pattern1s: string[], + desc: "DESC", + alpha: "ALPHA", + callback: Callback<unknown> + ]): Result<unknown, Context>; + sort_ro(...args: [ + key: RedisKey, + patternToken: "BY", + pattern: string, + patternToken1: "GET", + ...pattern1s: string[], + desc: "DESC", + alpha: "ALPHA" + ]): Result<unknown, Context>; + sort_ro(key: RedisKey, patternToken: "BY", pattern: string, offsetCountToken: "LIMIT", offset: number | string, count: number | string, callback?: Callback<unknown>): Result<unknown, Context>; + sort_ro(key: RedisKey, patternToken: "BY", pattern: string, offsetCountToken: "LIMIT", offset: number | string, count: number | string, alpha: "ALPHA", callback?: Callback<unknown>): Result<unknown, Context>; + sort_ro(key: RedisKey, patternToken: "BY", pattern: string, offsetCountToken: "LIMIT", offset: number | string, count: number | string, asc: "ASC", callback?: Callback<unknown>): Result<unknown, Context>; + sort_ro(key: RedisKey, patternToken: "BY", pattern: string, offsetCountToken: "LIMIT", offset: number | string, count: number | string, asc: "ASC", alpha: "ALPHA", callback?: Callback<unknown>): Result<unknown, Context>; + sort_ro(key: RedisKey, patternToken: "BY", pattern: string, offsetCountToken: "LIMIT", offset: number | string, count: number | string, desc: "DESC", callback?: Callback<unknown>): Result<unknown, Context>; + sort_ro(key: RedisKey, patternToken: "BY", pattern: string, offsetCountToken: "LIMIT", offset: number | string, count: number | string, desc: "DESC", alpha: "ALPHA", callback?: Callback<unknown>): Result<unknown, Context>; + sort_ro(...args: [ + key: RedisKey, + patternToken: "BY", + pattern: string, + offsetCountToken: "LIMIT", + offset: number | string, + count: number | string, + patternToken1: "GET", + ...pattern1s: string[], + callback: Callback<unknown> + ]): Result<unknown, Context>; + sort_ro(...args: [ + key: RedisKey, + patternToken: "BY", + pattern: string, + offsetCountToken: "LIMIT", + offset: number | string, + count: number | string, + patternToken1: "GET", + ...pattern1s: string[] + ]): Result<unknown, Context>; + sort_ro(...args: [ + key: RedisKey, + patternToken: "BY", + pattern: string, + offsetCountToken: "LIMIT", + offset: number | string, + count: number | string, + patternToken1: "GET", + ...pattern1s: string[], + alpha: "ALPHA", + callback: Callback<unknown> + ]): Result<unknown, Context>; + sort_ro(...args: [ + key: RedisKey, + patternToken: "BY", + pattern: string, + offsetCountToken: "LIMIT", + offset: number | string, + count: number | string, + patternToken1: "GET", + ...pattern1s: string[], + alpha: "ALPHA" + ]): Result<unknown, Context>; + sort_ro(...args: [ + key: RedisKey, + patternToken: "BY", + pattern: string, + offsetCountToken: "LIMIT", + offset: number | string, + count: number | string, + patternToken1: "GET", + ...pattern1s: string[], + asc: "ASC", + callback: Callback<unknown> + ]): Result<unknown, Context>; + sort_ro(...args: [ + key: RedisKey, + patternToken: "BY", + pattern: string, + offsetCountToken: "LIMIT", + offset: number | string, + count: number | string, + patternToken1: "GET", + ...pattern1s: string[], + asc: "ASC" + ]): Result<unknown, Context>; + sort_ro(...args: [ + key: RedisKey, + patternToken: "BY", + pattern: string, + offsetCountToken: "LIMIT", + offset: number | string, + count: number | string, + patternToken1: "GET", + ...pattern1s: string[], + asc: "ASC", + alpha: "ALPHA", + callback: Callback<unknown> + ]): Result<unknown, Context>; + sort_ro(...args: [ + key: RedisKey, + patternToken: "BY", + pattern: string, + offsetCountToken: "LIMIT", + offset: number | string, + count: number | string, + patternToken1: "GET", + ...pattern1s: string[], + asc: "ASC", + alpha: "ALPHA" + ]): Result<unknown, Context>; + sort_ro(...args: [ + key: RedisKey, + patternToken: "BY", + pattern: string, + offsetCountToken: "LIMIT", + offset: number | string, + count: number | string, + patternToken1: "GET", + ...pattern1s: string[], + desc: "DESC", + callback: Callback<unknown> + ]): Result<unknown, Context>; + sort_ro(...args: [ + key: RedisKey, + patternToken: "BY", + pattern: string, + offsetCountToken: "LIMIT", + offset: number | string, + count: number | string, + patternToken1: "GET", + ...pattern1s: string[], + desc: "DESC" + ]): Result<unknown, Context>; + sort_ro(...args: [ + key: RedisKey, + patternToken: "BY", + pattern: string, + offsetCountToken: "LIMIT", + offset: number | string, + count: number | string, + patternToken1: "GET", + ...pattern1s: string[], + desc: "DESC", + alpha: "ALPHA", + callback: Callback<unknown> + ]): Result<unknown, Context>; + sort_ro(...args: [ + key: RedisKey, + patternToken: "BY", + pattern: string, + offsetCountToken: "LIMIT", + offset: number | string, + count: number | string, + patternToken1: "GET", + ...pattern1s: string[], + desc: "DESC", + alpha: "ALPHA" + ]): Result<unknown, Context>; + spop(key: RedisKey, callback?: Callback<string | null>): Result<string | null, Context>; + spopBuffer(key: RedisKey, callback?: Callback<Buffer | null>): Result<Buffer | null, Context>; + spop(key: RedisKey, count: number | string, callback?: Callback<string[]>): Result<string[], Context>; + spopBuffer(key: RedisKey, count: number | string, callback?: Callback<Buffer[]>): Result<Buffer[], Context>; + spublish(shardchannel: string | Buffer, message: string | Buffer, callback?: Callback<unknown>): Result<unknown, Context>; + srandmember(key: RedisKey, callback?: Callback<string | null>): Result<string | null, Context>; + srandmemberBuffer(key: RedisKey, callback?: Callback<Buffer | null>): Result<Buffer | null, Context>; + srandmember(key: RedisKey, count: number | string, callback?: Callback<string[]>): Result<string[], Context>; + srandmemberBuffer(key: RedisKey, count: number | string, callback?: Callback<Buffer[]>): Result<Buffer[], Context>; + srem(...args: [ + key: RedisKey, + ...members: (string | Buffer | number)[], + callback: Callback<number> + ]): Result<number, Context>; + srem(...args: [ + key: RedisKey, + members: (string | Buffer | number)[], + callback: Callback<number> + ]): Result<number, Context>; + srem(...args: [ + key: RedisKey, + ...members: (string | Buffer | number)[] + ]): Result<number, Context>; + srem(...args: [ + key: RedisKey, + members: (string | Buffer | number)[] + ]): Result<number, Context>; + sscan(key: RedisKey, cursor: number | string, callback?: Callback<[ + cursor: string, + elements: string[] + ]>): Result<[ + cursor: string, + elements: string[] + ], Context>; + sscanBuffer(key: RedisKey, cursor: number | string, callback?: Callback<[ + cursor: Buffer, + elements: Buffer[] + ]>): Result<[ + cursor: Buffer, + elements: Buffer[] + ], Context>; + sscan(key: RedisKey, cursor: number | string, countToken: "COUNT", count: number | string, callback?: Callback<[ + cursor: string, + elements: string[] + ]>): Result<[ + cursor: string, + elements: string[] + ], Context>; + sscanBuffer(key: RedisKey, cursor: number | string, countToken: "COUNT", count: number | string, callback?: Callback<[ + cursor: Buffer, + elements: Buffer[] + ]>): Result<[ + cursor: Buffer, + elements: Buffer[] + ], Context>; + sscan(key: RedisKey, cursor: number | string, patternToken: "MATCH", pattern: string, callback?: Callback<[ + cursor: string, + elements: string[] + ]>): Result<[ + cursor: string, + elements: string[] + ], Context>; + sscanBuffer(key: RedisKey, cursor: number | string, patternToken: "MATCH", pattern: string, callback?: Callback<[ + cursor: Buffer, + elements: Buffer[] + ]>): Result<[ + cursor: Buffer, + elements: Buffer[] + ], Context>; + sscan(key: RedisKey, cursor: number | string, patternToken: "MATCH", pattern: string, countToken: "COUNT", count: number | string, callback?: Callback<[ + cursor: string, + elements: string[] + ]>): Result<[ + cursor: string, + elements: string[] + ], Context>; + sscanBuffer(key: RedisKey, cursor: number | string, patternToken: "MATCH", pattern: string, countToken: "COUNT", count: number | string, callback?: Callback<[ + cursor: Buffer, + elements: Buffer[] + ]>): Result<[ + cursor: Buffer, + elements: Buffer[] + ], Context>; + ssubscribe(...args: [ + ...shardchannels: (string | Buffer)[], + callback: Callback<unknown> + ]): Result<unknown, Context>; + ssubscribe(...args: [ + ...shardchannels: (string | Buffer)[] + ]): Result<unknown, Context>; + strlen(key: RedisKey, callback?: Callback<number>): Result<number, Context>; + subscribe(...args: [ + ...channels: (string | Buffer)[], + callback: Callback<unknown> + ]): Result<unknown, Context>; + subscribe(...args: [ + ...channels: (string | Buffer)[] + ]): Result<unknown, Context>; + substr(key: RedisKey, start: number | string, end: number | string, callback?: Callback<unknown>): Result<unknown, Context>; + sunion(...args: [ + ...keys: RedisKey[], + callback: Callback<string[]> + ]): Result<string[], Context>; + sunionBuffer(...args: [ + ...keys: RedisKey[], + callback: Callback<Buffer[]> + ]): Result<Buffer[], Context>; + sunion(...args: [ + keys: RedisKey[], + callback: Callback<string[]> + ]): Result<string[], Context>; + sunionBuffer(...args: [ + keys: RedisKey[], + callback: Callback<Buffer[]> + ]): Result<Buffer[], Context>; + sunion(...args: [ + ...keys: RedisKey[] + ]): Result<string[], Context>; + sunionBuffer(...args: [ + ...keys: RedisKey[] + ]): Result<Buffer[], Context>; + sunion(...args: [ + keys: RedisKey[] + ]): Result<string[], Context>; + sunionBuffer(...args: [ + keys: RedisKey[] + ]): Result<Buffer[], Context>; + sunionstore(...args: [ + destination: RedisKey, + ...keys: RedisKey[], + callback: Callback<number> + ]): Result<number, Context>; + sunionstore(...args: [ + destination: RedisKey, + keys: RedisKey[], + callback: Callback<number> + ]): Result<number, Context>; + sunionstore(...args: [ + destination: RedisKey, + ...keys: RedisKey[] + ]): Result<number, Context>; + sunionstore(...args: [ + destination: RedisKey, + keys: RedisKey[] + ]): Result<number, Context>; + sunsubscribe(callback?: Callback<unknown>): Result<unknown, Context>; + sunsubscribe(...args: [ + ...shardchannels: (string | Buffer)[], + callback: Callback<unknown> + ]): Result<unknown, Context>; + sunsubscribe(...args: [ + ...shardchannels: (string | Buffer)[] + ]): Result<unknown, Context>; + swapdb(index1: number | string, index2: number | string, callback?: Callback<"OK">): Result<"OK", Context>; + sync(callback?: Callback<unknown>): Result<unknown, Context>; + time(callback?: Callback<number[]>): Result<number[], Context>; + touch(...args: [ + ...keys: RedisKey[], + callback: Callback<number> + ]): Result<number, Context>; + touch(...args: [ + keys: RedisKey[], + callback: Callback<number> + ]): Result<number, Context>; + touch(...args: [ + ...keys: RedisKey[] + ]): Result<number, Context>; + touch(...args: [ + keys: RedisKey[] + ]): Result<number, Context>; + ttl(key: RedisKey, callback?: Callback<number>): Result<number, Context>; + type(key: RedisKey, callback?: Callback<string>): Result<string, Context>; + unlink(...args: [ + ...keys: RedisKey[], + callback: Callback<number> + ]): Result<number, Context>; + unlink(...args: [ + keys: RedisKey[], + callback: Callback<number> + ]): Result<number, Context>; + unlink(...args: [ + ...keys: RedisKey[] + ]): Result<number, Context>; + unlink(...args: [ + keys: RedisKey[] + ]): Result<number, Context>; + unsubscribe(callback?: Callback<unknown>): Result<unknown, Context>; + unsubscribe(...args: [ + ...channels: (string | Buffer)[], + callback: Callback<unknown> + ]): Result<unknown, Context>; + unsubscribe(...args: [ + ...channels: (string | Buffer)[] + ]): Result<unknown, Context>; + unwatch(callback?: Callback<"OK">): Result<"OK", Context>; + vadd(...args: [ + key: RedisKey, + ...args: (RedisValue)[], + callback: Callback<number> + ]): Result<number, Context>; + vadd(...args: [ + key: RedisKey, + ...args: (RedisValue)[] + ]): Result<number, Context>; + vcard(key: RedisKey, callback?: Callback<number>): Result<number, Context>; + vdim(key: RedisKey, callback?: Callback<number>): Result<number, Context>; + vemb(key: RedisKey, element: string | Buffer | number, callback?: Callback<string[] | null>): Result<string[] | null, Context>; + vembBuffer(key: RedisKey, element: string | Buffer | number, callback?: Callback<Buffer[] | null>): Result<Buffer[] | null, Context>; + vemb(key: RedisKey, element: string | Buffer | number, raw: 'RAW', callback?: Callback<string[] | null>): Result<string[] | null, Context>; + vembBuffer(key: RedisKey, element: string | Buffer | number, raw: 'RAW', callback?: Callback<Buffer[] | null>): Result<Buffer[] | null, Context>; + vgetattr(key: RedisKey, element: string | Buffer | number, callback?: Callback<string | null>): Result<string | null, Context>; + vgetattrBuffer(key: RedisKey, element: string | Buffer | number, callback?: Callback<Buffer | null>): Result<Buffer | null, Context>; + vinfo(key: RedisKey, callback?: Callback<(string | number)[] | null>): Result<(string | number)[] | null, Context>; + vinfoBuffer(key: RedisKey, callback?: Callback<(Buffer | number)[] | null>): Result<(Buffer | number)[] | null, Context>; + vismember(key: RedisKey, element: string | Buffer | number, callback?: Callback<number>): Result<number, Context>; + vlinks(key: RedisKey, element: string | Buffer | number, callback?: Callback<string[][] | null>): Result<string[][] | null, Context>; + vlinksBuffer(key: RedisKey, element: string | Buffer | number, callback?: Callback<Buffer[][] | null>): Result<Buffer[][] | null, Context>; + vlinks(key: RedisKey, element: string | Buffer | number, withscores: 'WITHSCORES', callback?: Callback<string[][] | null>): Result<string[][] | null, Context>; + vlinksBuffer(key: RedisKey, element: string | Buffer | number, withscores: 'WITHSCORES', callback?: Callback<Buffer[][] | null>): Result<Buffer[][] | null, Context>; + vrandmember(key: RedisKey, callback?: Callback<string | null>): Result<string | null, Context>; + vrandmemberBuffer(key: RedisKey, callback?: Callback<Buffer | null>): Result<Buffer | null, Context>; + vrandmember(key: RedisKey, count: number | string, callback?: Callback<string[]>): Result<string[], Context>; + vrandmemberBuffer(key: RedisKey, count: number | string, callback?: Callback<Buffer[]>): Result<Buffer[], Context>; + vrange(key: RedisKey, start: string | Buffer | number, end: string | Buffer | number, callback?: Callback<string[]>): Result<string[], Context>; + vrangeBuffer(key: RedisKey, start: string | Buffer | number, end: string | Buffer | number, callback?: Callback<Buffer[]>): Result<Buffer[], Context>; + vrange(key: RedisKey, start: string | Buffer | number, end: string | Buffer | number, count: number | string, callback?: Callback<string[]>): Result<string[], Context>; + vrangeBuffer(key: RedisKey, start: string | Buffer | number, end: string | Buffer | number, count: number | string, callback?: Callback<Buffer[]>): Result<Buffer[], Context>; + vrem(key: RedisKey, element: string | Buffer | number, callback?: Callback<number>): Result<number, Context>; + vsetattr(key: RedisKey, element: string | Buffer | number, json: string | Buffer, callback?: Callback<number>): Result<number, Context>; + vsim(...args: [ + key: RedisKey, + ...args: (RedisValue)[], + callback: Callback<(string | null)[]> + ]): Result<(string | null)[], Context>; + vsimBuffer(...args: [ + key: RedisKey, + ...args: (RedisValue)[], + callback: Callback<(Buffer | null)[]> + ]): Result<(Buffer | null)[], Context>; + vsim(...args: [ + key: RedisKey, + ...args: (RedisValue)[] + ]): Result<(string | null)[], Context>; + vsimBuffer(...args: [ + key: RedisKey, + ...args: (RedisValue)[] + ]): Result<(Buffer | null)[], Context>; + wait(numreplicas: number | string, timeout: number | string, callback?: Callback<number>): Result<number, Context>; + watch(...args: [ + ...keys: RedisKey[], + callback: Callback<"OK"> + ]): Result<"OK", Context>; + watch(...args: [ + keys: RedisKey[], + callback: Callback<"OK"> + ]): Result<"OK", Context>; + watch(...args: [ + ...keys: RedisKey[] + ]): Result<"OK", Context>; + watch(...args: [ + keys: RedisKey[] + ]): Result<"OK", Context>; + xack(...args: [ + key: RedisKey, + group: string | Buffer, + ...ids: (string | Buffer | number)[], + callback: Callback<number> + ]): Result<number, Context>; + xack(...args: [ + key: RedisKey, + group: string | Buffer, + ...ids: (string | Buffer | number)[] + ]): Result<number, Context>; + xadd(...args: [ + key: RedisKey, + ...args: RedisValue[], + callback: Callback<string | null> + ]): Result<string | null, Context>; + xaddBuffer(...args: [ + key: RedisKey, + ...args: RedisValue[], + callback: Callback<Buffer | null> + ]): Result<Buffer | null, Context>; + xadd(...args: [ + key: RedisKey, + ...args: RedisValue[] + ]): Result<string | null, Context>; + xaddBuffer(...args: [ + key: RedisKey, + ...args: RedisValue[] + ]): Result<Buffer | null, Context>; + xautoclaim(key: RedisKey, group: string | Buffer, consumer: string | Buffer, minIdleTime: string | Buffer | number, start: string | Buffer | number, callback?: Callback<unknown[]>): Result<unknown[], Context>; + xautoclaim(key: RedisKey, group: string | Buffer, consumer: string | Buffer, minIdleTime: string | Buffer | number, start: string | Buffer | number, justid: "JUSTID", callback?: Callback<unknown[]>): Result<unknown[], Context>; + xautoclaim(key: RedisKey, group: string | Buffer, consumer: string | Buffer, minIdleTime: string | Buffer | number, start: string | Buffer | number, countToken: "COUNT", count: number | string, callback?: Callback<unknown[]>): Result<unknown[], Context>; + xautoclaim(key: RedisKey, group: string | Buffer, consumer: string | Buffer, minIdleTime: string | Buffer | number, start: string | Buffer | number, countToken: "COUNT", count: number | string, justid: "JUSTID", callback?: Callback<unknown[]>): Result<unknown[], Context>; + xclaim(...args: [ + key: RedisKey, + group: string | Buffer, + consumer: string | Buffer, + minIdleTime: string | Buffer | number, + ...ids: (string | Buffer | number)[], + callback: Callback<unknown[]> + ]): Result<unknown[], Context>; + xclaim(...args: [ + key: RedisKey, + group: string | Buffer, + consumer: string | Buffer, + minIdleTime: string | Buffer | number, + ...ids: (string | Buffer | number)[] + ]): Result<unknown[], Context>; + xclaim(...args: [ + key: RedisKey, + group: string | Buffer, + consumer: string | Buffer, + minIdleTime: string | Buffer | number, + ...ids: (string | Buffer | number)[], + justid: "JUSTID", + callback: Callback<unknown[]> + ]): Result<unknown[], Context>; + xclaim(...args: [ + key: RedisKey, + group: string | Buffer, + consumer: string | Buffer, + minIdleTime: string | Buffer | number, + ...ids: (string | Buffer | number)[], + justid: "JUSTID" + ]): Result<unknown[], Context>; + xclaim(...args: [ + key: RedisKey, + group: string | Buffer, + consumer: string | Buffer, + minIdleTime: string | Buffer | number, + ...ids: (string | Buffer | number)[], + force: "FORCE", + callback: Callback<unknown[]> + ]): Result<unknown[], Context>; + xclaim(...args: [ + key: RedisKey, + group: string | Buffer, + consumer: string | Buffer, + minIdleTime: string | Buffer | number, + ...ids: (string | Buffer | number)[], + force: "FORCE" + ]): Result<unknown[], Context>; + xclaim(...args: [ + key: RedisKey, + group: string | Buffer, + consumer: string | Buffer, + minIdleTime: string | Buffer | number, + ...ids: (string | Buffer | number)[], + force: "FORCE", + justid: "JUSTID", + callback: Callback<unknown[]> + ]): Result<unknown[], Context>; + xclaim(...args: [ + key: RedisKey, + group: string | Buffer, + consumer: string | Buffer, + minIdleTime: string | Buffer | number, + ...ids: (string | Buffer | number)[], + force: "FORCE", + justid: "JUSTID" + ]): Result<unknown[], Context>; + xclaim(...args: [ + key: RedisKey, + group: string | Buffer, + consumer: string | Buffer, + minIdleTime: string | Buffer | number, + ...ids: (string | Buffer | number)[], + countToken: "RETRYCOUNT", + count: number | string, + callback: Callback<unknown[]> + ]): Result<unknown[], Context>; + xclaim(...args: [ + key: RedisKey, + group: string | Buffer, + consumer: string | Buffer, + minIdleTime: string | Buffer | number, + ...ids: (string | Buffer | number)[], + countToken: "RETRYCOUNT", + count: number | string + ]): Result<unknown[], Context>; + xclaim(...args: [ + key: RedisKey, + group: string | Buffer, + consumer: string | Buffer, + minIdleTime: string | Buffer | number, + ...ids: (string | Buffer | number)[], + countToken: "RETRYCOUNT", + count: number | string, + justid: "JUSTID", + callback: Callback<unknown[]> + ]): Result<unknown[], Context>; + xclaim(...args: [ + key: RedisKey, + group: string | Buffer, + consumer: string | Buffer, + minIdleTime: string | Buffer | number, + ...ids: (string | Buffer | number)[], + countToken: "RETRYCOUNT", + count: number | string, + justid: "JUSTID" + ]): Result<unknown[], Context>; + xclaim(...args: [ + key: RedisKey, + group: string | Buffer, + consumer: string | Buffer, + minIdleTime: string | Buffer | number, + ...ids: (string | Buffer | number)[], + countToken: "RETRYCOUNT", + count: number | string, + force: "FORCE", + callback: Callback<unknown[]> + ]): Result<unknown[], Context>; + xclaim(...args: [ + key: RedisKey, + group: string | Buffer, + consumer: string | Buffer, + minIdleTime: string | Buffer | number, + ...ids: (string | Buffer | number)[], + countToken: "RETRYCOUNT", + count: number | string, + force: "FORCE" + ]): Result<unknown[], Context>; + xclaim(...args: [ + key: RedisKey, + group: string | Buffer, + consumer: string | Buffer, + minIdleTime: string | Buffer | number, + ...ids: (string | Buffer | number)[], + countToken: "RETRYCOUNT", + count: number | string, + force: "FORCE", + justid: "JUSTID", + callback: Callback<unknown[]> + ]): Result<unknown[], Context>; + xclaim(...args: [ + key: RedisKey, + group: string | Buffer, + consumer: string | Buffer, + minIdleTime: string | Buffer | number, + ...ids: (string | Buffer | number)[], + countToken: "RETRYCOUNT", + count: number | string, + force: "FORCE", + justid: "JUSTID" + ]): Result<unknown[], Context>; + xclaim(...args: [ + key: RedisKey, + group: string | Buffer, + consumer: string | Buffer, + minIdleTime: string | Buffer | number, + ...ids: (string | Buffer | number)[], + unixTimeMillisecondsToken: "TIME", + unixTimeMilliseconds: number | string, + callback: Callback<unknown[]> + ]): Result<unknown[], Context>; + xclaim(...args: [ + key: RedisKey, + group: string | Buffer, + consumer: string | Buffer, + minIdleTime: string | Buffer | number, + ...ids: (string | Buffer | number)[], + unixTimeMillisecondsToken: "TIME", + unixTimeMilliseconds: number | string + ]): Result<unknown[], Context>; + xclaim(...args: [ + key: RedisKey, + group: string | Buffer, + consumer: string | Buffer, + minIdleTime: string | Buffer | number, + ...ids: (string | Buffer | number)[], + unixTimeMillisecondsToken: "TIME", + unixTimeMilliseconds: number | string, + justid: "JUSTID", + callback: Callback<unknown[]> + ]): Result<unknown[], Context>; + xclaim(...args: [ + key: RedisKey, + group: string | Buffer, + consumer: string | Buffer, + minIdleTime: string | Buffer | number, + ...ids: (string | Buffer | number)[], + unixTimeMillisecondsToken: "TIME", + unixTimeMilliseconds: number | string, + justid: "JUSTID" + ]): Result<unknown[], Context>; + xclaim(...args: [ + key: RedisKey, + group: string | Buffer, + consumer: string | Buffer, + minIdleTime: string | Buffer | number, + ...ids: (string | Buffer | number)[], + unixTimeMillisecondsToken: "TIME", + unixTimeMilliseconds: number | string, + force: "FORCE", + callback: Callback<unknown[]> + ]): Result<unknown[], Context>; + xclaim(...args: [ + key: RedisKey, + group: string | Buffer, + consumer: string | Buffer, + minIdleTime: string | Buffer | number, + ...ids: (string | Buffer | number)[], + unixTimeMillisecondsToken: "TIME", + unixTimeMilliseconds: number | string, + force: "FORCE" + ]): Result<unknown[], Context>; + xclaim(...args: [ + key: RedisKey, + group: string | Buffer, + consumer: string | Buffer, + minIdleTime: string | Buffer | number, + ...ids: (string | Buffer | number)[], + unixTimeMillisecondsToken: "TIME", + unixTimeMilliseconds: number | string, + force: "FORCE", + justid: "JUSTID", + callback: Callback<unknown[]> + ]): Result<unknown[], Context>; + xclaim(...args: [ + key: RedisKey, + group: string | Buffer, + consumer: string | Buffer, + minIdleTime: string | Buffer | number, + ...ids: (string | Buffer | number)[], + unixTimeMillisecondsToken: "TIME", + unixTimeMilliseconds: number | string, + force: "FORCE", + justid: "JUSTID" + ]): Result<unknown[], Context>; + xclaim(...args: [ + key: RedisKey, + group: string | Buffer, + consumer: string | Buffer, + minIdleTime: string | Buffer | number, + ...ids: (string | Buffer | number)[], + unixTimeMillisecondsToken: "TIME", + unixTimeMilliseconds: number | string, + countToken: "RETRYCOUNT", + count: number | string, + callback: Callback<unknown[]> + ]): Result<unknown[], Context>; + xclaim(...args: [ + key: RedisKey, + group: string | Buffer, + consumer: string | Buffer, + minIdleTime: string | Buffer | number, + ...ids: (string | Buffer | number)[], + unixTimeMillisecondsToken: "TIME", + unixTimeMilliseconds: number | string, + countToken: "RETRYCOUNT", + count: number | string + ]): Result<unknown[], Context>; + xclaim(...args: [ + key: RedisKey, + group: string | Buffer, + consumer: string | Buffer, + minIdleTime: string | Buffer | number, + ...ids: (string | Buffer | number)[], + unixTimeMillisecondsToken: "TIME", + unixTimeMilliseconds: number | string, + countToken: "RETRYCOUNT", + count: number | string, + justid: "JUSTID", + callback: Callback<unknown[]> + ]): Result<unknown[], Context>; + xclaim(...args: [ + key: RedisKey, + group: string | Buffer, + consumer: string | Buffer, + minIdleTime: string | Buffer | number, + ...ids: (string | Buffer | number)[], + unixTimeMillisecondsToken: "TIME", + unixTimeMilliseconds: number | string, + countToken: "RETRYCOUNT", + count: number | string, + justid: "JUSTID" + ]): Result<unknown[], Context>; + xclaim(...args: [ + key: RedisKey, + group: string | Buffer, + consumer: string | Buffer, + minIdleTime: string | Buffer | number, + ...ids: (string | Buffer | number)[], + unixTimeMillisecondsToken: "TIME", + unixTimeMilliseconds: number | string, + countToken: "RETRYCOUNT", + count: number | string, + force: "FORCE", + callback: Callback<unknown[]> + ]): Result<unknown[], Context>; + xclaim(...args: [ + key: RedisKey, + group: string | Buffer, + consumer: string | Buffer, + minIdleTime: string | Buffer | number, + ...ids: (string | Buffer | number)[], + unixTimeMillisecondsToken: "TIME", + unixTimeMilliseconds: number | string, + countToken: "RETRYCOUNT", + count: number | string, + force: "FORCE" + ]): Result<unknown[], Context>; + xclaim(...args: [ + key: RedisKey, + group: string | Buffer, + consumer: string | Buffer, + minIdleTime: string | Buffer | number, + ...ids: (string | Buffer | number)[], + unixTimeMillisecondsToken: "TIME", + unixTimeMilliseconds: number | string, + countToken: "RETRYCOUNT", + count: number | string, + force: "FORCE", + justid: "JUSTID", + callback: Callback<unknown[]> + ]): Result<unknown[], Context>; + xclaim(...args: [ + key: RedisKey, + group: string | Buffer, + consumer: string | Buffer, + minIdleTime: string | Buffer | number, + ...ids: (string | Buffer | number)[], + unixTimeMillisecondsToken: "TIME", + unixTimeMilliseconds: number | string, + countToken: "RETRYCOUNT", + count: number | string, + force: "FORCE", + justid: "JUSTID" + ]): Result<unknown[], Context>; + xclaim(...args: [ + key: RedisKey, + group: string | Buffer, + consumer: string | Buffer, + minIdleTime: string | Buffer | number, + ...ids: (string | Buffer | number)[], + msToken: "IDLE", + ms: number | string, + callback: Callback<unknown[]> + ]): Result<unknown[], Context>; + xclaim(...args: [ + key: RedisKey, + group: string | Buffer, + consumer: string | Buffer, + minIdleTime: string | Buffer | number, + ...ids: (string | Buffer | number)[], + msToken: "IDLE", + ms: number | string + ]): Result<unknown[], Context>; + xclaim(...args: [ + key: RedisKey, + group: string | Buffer, + consumer: string | Buffer, + minIdleTime: string | Buffer | number, + ...ids: (string | Buffer | number)[], + msToken: "IDLE", + ms: number | string, + justid: "JUSTID", + callback: Callback<unknown[]> + ]): Result<unknown[], Context>; + xclaim(...args: [ + key: RedisKey, + group: string | Buffer, + consumer: string | Buffer, + minIdleTime: string | Buffer | number, + ...ids: (string | Buffer | number)[], + msToken: "IDLE", + ms: number | string, + justid: "JUSTID" + ]): Result<unknown[], Context>; + xclaim(...args: [ + key: RedisKey, + group: string | Buffer, + consumer: string | Buffer, + minIdleTime: string | Buffer | number, + ...ids: (string | Buffer | number)[], + msToken: "IDLE", + ms: number | string, + force: "FORCE", + callback: Callback<unknown[]> + ]): Result<unknown[], Context>; + xclaim(...args: [ + key: RedisKey, + group: string | Buffer, + consumer: string | Buffer, + minIdleTime: string | Buffer | number, + ...ids: (string | Buffer | number)[], + msToken: "IDLE", + ms: number | string, + force: "FORCE" + ]): Result<unknown[], Context>; + xclaim(...args: [ + key: RedisKey, + group: string | Buffer, + consumer: string | Buffer, + minIdleTime: string | Buffer | number, + ...ids: (string | Buffer | number)[], + msToken: "IDLE", + ms: number | string, + force: "FORCE", + justid: "JUSTID", + callback: Callback<unknown[]> + ]): Result<unknown[], Context>; + xclaim(...args: [ + key: RedisKey, + group: string | Buffer, + consumer: string | Buffer, + minIdleTime: string | Buffer | number, + ...ids: (string | Buffer | number)[], + msToken: "IDLE", + ms: number | string, + force: "FORCE", + justid: "JUSTID" + ]): Result<unknown[], Context>; + xclaim(...args: [ + key: RedisKey, + group: string | Buffer, + consumer: string | Buffer, + minIdleTime: string | Buffer | number, + ...ids: (string | Buffer | number)[], + msToken: "IDLE", + ms: number | string, + countToken: "RETRYCOUNT", + count: number | string, + callback: Callback<unknown[]> + ]): Result<unknown[], Context>; + xclaim(...args: [ + key: RedisKey, + group: string | Buffer, + consumer: string | Buffer, + minIdleTime: string | Buffer | number, + ...ids: (string | Buffer | number)[], + msToken: "IDLE", + ms: number | string, + countToken: "RETRYCOUNT", + count: number | string + ]): Result<unknown[], Context>; + xclaim(...args: [ + key: RedisKey, + group: string | Buffer, + consumer: string | Buffer, + minIdleTime: string | Buffer | number, + ...ids: (string | Buffer | number)[], + msToken: "IDLE", + ms: number | string, + countToken: "RETRYCOUNT", + count: number | string, + justid: "JUSTID", + callback: Callback<unknown[]> + ]): Result<unknown[], Context>; + xclaim(...args: [ + key: RedisKey, + group: string | Buffer, + consumer: string | Buffer, + minIdleTime: string | Buffer | number, + ...ids: (string | Buffer | number)[], + msToken: "IDLE", + ms: number | string, + countToken: "RETRYCOUNT", + count: number | string, + justid: "JUSTID" + ]): Result<unknown[], Context>; + xclaim(...args: [ + key: RedisKey, + group: string | Buffer, + consumer: string | Buffer, + minIdleTime: string | Buffer | number, + ...ids: (string | Buffer | number)[], + msToken: "IDLE", + ms: number | string, + countToken: "RETRYCOUNT", + count: number | string, + force: "FORCE", + callback: Callback<unknown[]> + ]): Result<unknown[], Context>; + xclaim(...args: [ + key: RedisKey, + group: string | Buffer, + consumer: string | Buffer, + minIdleTime: string | Buffer | number, + ...ids: (string | Buffer | number)[], + msToken: "IDLE", + ms: number | string, + countToken: "RETRYCOUNT", + count: number | string, + force: "FORCE" + ]): Result<unknown[], Context>; + xclaim(...args: [ + key: RedisKey, + group: string | Buffer, + consumer: string | Buffer, + minIdleTime: string | Buffer | number, + ...ids: (string | Buffer | number)[], + msToken: "IDLE", + ms: number | string, + countToken: "RETRYCOUNT", + count: number | string, + force: "FORCE", + justid: "JUSTID", + callback: Callback<unknown[]> + ]): Result<unknown[], Context>; + xclaim(...args: [ + key: RedisKey, + group: string | Buffer, + consumer: string | Buffer, + minIdleTime: string | Buffer | number, + ...ids: (string | Buffer | number)[], + msToken: "IDLE", + ms: number | string, + countToken: "RETRYCOUNT", + count: number | string, + force: "FORCE", + justid: "JUSTID" + ]): Result<unknown[], Context>; + xclaim(...args: [ + key: RedisKey, + group: string | Buffer, + consumer: string | Buffer, + minIdleTime: string | Buffer | number, + ...ids: (string | Buffer | number)[], + msToken: "IDLE", + ms: number | string, + unixTimeMillisecondsToken: "TIME", + unixTimeMilliseconds: number | string, + callback: Callback<unknown[]> + ]): Result<unknown[], Context>; + xclaim(...args: [ + key: RedisKey, + group: string | Buffer, + consumer: string | Buffer, + minIdleTime: string | Buffer | number, + ...ids: (string | Buffer | number)[], + msToken: "IDLE", + ms: number | string, + unixTimeMillisecondsToken: "TIME", + unixTimeMilliseconds: number | string + ]): Result<unknown[], Context>; + xclaim(...args: [ + key: RedisKey, + group: string | Buffer, + consumer: string | Buffer, + minIdleTime: string | Buffer | number, + ...ids: (string | Buffer | number)[], + msToken: "IDLE", + ms: number | string, + unixTimeMillisecondsToken: "TIME", + unixTimeMilliseconds: number | string, + justid: "JUSTID", + callback: Callback<unknown[]> + ]): Result<unknown[], Context>; + xclaim(...args: [ + key: RedisKey, + group: string | Buffer, + consumer: string | Buffer, + minIdleTime: string | Buffer | number, + ...ids: (string | Buffer | number)[], + msToken: "IDLE", + ms: number | string, + unixTimeMillisecondsToken: "TIME", + unixTimeMilliseconds: number | string, + justid: "JUSTID" + ]): Result<unknown[], Context>; + xclaim(...args: [ + key: RedisKey, + group: string | Buffer, + consumer: string | Buffer, + minIdleTime: string | Buffer | number, + ...ids: (string | Buffer | number)[], + msToken: "IDLE", + ms: number | string, + unixTimeMillisecondsToken: "TIME", + unixTimeMilliseconds: number | string, + force: "FORCE", + callback: Callback<unknown[]> + ]): Result<unknown[], Context>; + xclaim(...args: [ + key: RedisKey, + group: string | Buffer, + consumer: string | Buffer, + minIdleTime: string | Buffer | number, + ...ids: (string | Buffer | number)[], + msToken: "IDLE", + ms: number | string, + unixTimeMillisecondsToken: "TIME", + unixTimeMilliseconds: number | string, + force: "FORCE" + ]): Result<unknown[], Context>; + xclaim(...args: [ + key: RedisKey, + group: string | Buffer, + consumer: string | Buffer, + minIdleTime: string | Buffer | number, + ...ids: (string | Buffer | number)[], + msToken: "IDLE", + ms: number | string, + unixTimeMillisecondsToken: "TIME", + unixTimeMilliseconds: number | string, + force: "FORCE", + justid: "JUSTID", + callback: Callback<unknown[]> + ]): Result<unknown[], Context>; + xclaim(...args: [ + key: RedisKey, + group: string | Buffer, + consumer: string | Buffer, + minIdleTime: string | Buffer | number, + ...ids: (string | Buffer | number)[], + msToken: "IDLE", + ms: number | string, + unixTimeMillisecondsToken: "TIME", + unixTimeMilliseconds: number | string, + force: "FORCE", + justid: "JUSTID" + ]): Result<unknown[], Context>; + xclaim(...args: [ + key: RedisKey, + group: string | Buffer, + consumer: string | Buffer, + minIdleTime: string | Buffer | number, + ...ids: (string | Buffer | number)[], + msToken: "IDLE", + ms: number | string, + unixTimeMillisecondsToken: "TIME", + unixTimeMilliseconds: number | string, + countToken: "RETRYCOUNT", + count: number | string, + callback: Callback<unknown[]> + ]): Result<unknown[], Context>; + xclaim(...args: [ + key: RedisKey, + group: string | Buffer, + consumer: string | Buffer, + minIdleTime: string | Buffer | number, + ...ids: (string | Buffer | number)[], + msToken: "IDLE", + ms: number | string, + unixTimeMillisecondsToken: "TIME", + unixTimeMilliseconds: number | string, + countToken: "RETRYCOUNT", + count: number | string + ]): Result<unknown[], Context>; + xclaim(...args: [ + key: RedisKey, + group: string | Buffer, + consumer: string | Buffer, + minIdleTime: string | Buffer | number, + ...ids: (string | Buffer | number)[], + msToken: "IDLE", + ms: number | string, + unixTimeMillisecondsToken: "TIME", + unixTimeMilliseconds: number | string, + countToken: "RETRYCOUNT", + count: number | string, + justid: "JUSTID", + callback: Callback<unknown[]> + ]): Result<unknown[], Context>; + xclaim(...args: [ + key: RedisKey, + group: string | Buffer, + consumer: string | Buffer, + minIdleTime: string | Buffer | number, + ...ids: (string | Buffer | number)[], + msToken: "IDLE", + ms: number | string, + unixTimeMillisecondsToken: "TIME", + unixTimeMilliseconds: number | string, + countToken: "RETRYCOUNT", + count: number | string, + justid: "JUSTID" + ]): Result<unknown[], Context>; + xclaim(...args: [ + key: RedisKey, + group: string | Buffer, + consumer: string | Buffer, + minIdleTime: string | Buffer | number, + ...ids: (string | Buffer | number)[], + msToken: "IDLE", + ms: number | string, + unixTimeMillisecondsToken: "TIME", + unixTimeMilliseconds: number | string, + countToken: "RETRYCOUNT", + count: number | string, + force: "FORCE", + callback: Callback<unknown[]> + ]): Result<unknown[], Context>; + xclaim(...args: [ + key: RedisKey, + group: string | Buffer, + consumer: string | Buffer, + minIdleTime: string | Buffer | number, + ...ids: (string | Buffer | number)[], + msToken: "IDLE", + ms: number | string, + unixTimeMillisecondsToken: "TIME", + unixTimeMilliseconds: number | string, + countToken: "RETRYCOUNT", + count: number | string, + force: "FORCE" + ]): Result<unknown[], Context>; + xclaim(...args: [ + key: RedisKey, + group: string | Buffer, + consumer: string | Buffer, + minIdleTime: string | Buffer | number, + ...ids: (string | Buffer | number)[], + msToken: "IDLE", + ms: number | string, + unixTimeMillisecondsToken: "TIME", + unixTimeMilliseconds: number | string, + countToken: "RETRYCOUNT", + count: number | string, + force: "FORCE", + justid: "JUSTID", + callback: Callback<unknown[]> + ]): Result<unknown[], Context>; + xclaim(...args: [ + key: RedisKey, + group: string | Buffer, + consumer: string | Buffer, + minIdleTime: string | Buffer | number, + ...ids: (string | Buffer | number)[], + msToken: "IDLE", + ms: number | string, + unixTimeMillisecondsToken: "TIME", + unixTimeMilliseconds: number | string, + countToken: "RETRYCOUNT", + count: number | string, + force: "FORCE", + justid: "JUSTID" + ]): Result<unknown[], Context>; + xdel(...args: [ + key: RedisKey, + ...ids: (string | Buffer | number)[], + callback: Callback<number> + ]): Result<number, Context>; + xdel(...args: [ + key: RedisKey, + ...ids: (string | Buffer | number)[] + ]): Result<number, Context>; + xdelex(...args: [ + key: RedisKey, + idsToken: "IDS", + numids: number | string, + ...ids: (string | Buffer | number)[], + callback: Callback<unknown> + ]): Result<unknown, Context>; + xdelex(...args: [ + key: RedisKey, + idsToken: "IDS", + numids: number | string, + ...ids: (string | Buffer | number)[] + ]): Result<unknown, Context>; + xdelex(...args: [ + key: RedisKey, + keepref: "KEEPREF", + idsToken: "IDS", + numids: number | string, + ...ids: (string | Buffer | number)[], + callback: Callback<unknown> + ]): Result<unknown, Context>; + xdelex(...args: [ + key: RedisKey, + keepref: "KEEPREF", + idsToken: "IDS", + numids: number | string, + ...ids: (string | Buffer | number)[] + ]): Result<unknown, Context>; + xdelex(...args: [ + key: RedisKey, + delref: "DELREF", + idsToken: "IDS", + numids: number | string, + ...ids: (string | Buffer | number)[], + callback: Callback<unknown> + ]): Result<unknown, Context>; + xdelex(...args: [ + key: RedisKey, + delref: "DELREF", + idsToken: "IDS", + numids: number | string, + ...ids: (string | Buffer | number)[] + ]): Result<unknown, Context>; + xdelex(...args: [ + key: RedisKey, + acked: "ACKED", + idsToken: "IDS", + numids: number | string, + ...ids: (string | Buffer | number)[], + callback: Callback<unknown> + ]): Result<unknown, Context>; + xdelex(...args: [ + key: RedisKey, + acked: "ACKED", + idsToken: "IDS", + numids: number | string, + ...ids: (string | Buffer | number)[] + ]): Result<unknown, Context>; + xgroup(subcommand: "CREATE", key: RedisKey, groupname: string | Buffer, id: string | Buffer | number, callback?: Callback<unknown>): Result<unknown, Context>; + xgroup(subcommand: "CREATE", key: RedisKey, groupname: string | Buffer, id: string | Buffer | number, entriesReadToken: "ENTRIESREAD", entriesRead: number | string, callback?: Callback<unknown>): Result<unknown, Context>; + xgroup(subcommand: "CREATE", key: RedisKey, groupname: string | Buffer, id: string | Buffer | number, mkstream: "MKSTREAM", callback?: Callback<unknown>): Result<unknown, Context>; + xgroup(subcommand: "CREATE", key: RedisKey, groupname: string | Buffer, id: string | Buffer | number, mkstream: "MKSTREAM", entriesReadToken: "ENTRIESREAD", entriesRead: number | string, callback?: Callback<unknown>): Result<unknown, Context>; + xgroup(subcommand: "CREATE", key: RedisKey, groupname: string | Buffer, newId: "$", callback?: Callback<unknown>): Result<unknown, Context>; + xgroup(subcommand: "CREATE", key: RedisKey, groupname: string | Buffer, newId: "$", entriesReadToken: "ENTRIESREAD", entriesRead: number | string, callback?: Callback<unknown>): Result<unknown, Context>; + xgroup(subcommand: "CREATE", key: RedisKey, groupname: string | Buffer, newId: "$", mkstream: "MKSTREAM", callback?: Callback<unknown>): Result<unknown, Context>; + xgroup(subcommand: "CREATE", key: RedisKey, groupname: string | Buffer, newId: "$", mkstream: "MKSTREAM", entriesReadToken: "ENTRIESREAD", entriesRead: number | string, callback?: Callback<unknown>): Result<unknown, Context>; + xgroup(subcommand: "CREATECONSUMER", key: RedisKey, groupname: string | Buffer, consumername: string | Buffer, callback?: Callback<unknown>): Result<unknown, Context>; + xgroup(subcommand: "DELCONSUMER", key: RedisKey, groupname: string | Buffer, consumername: string | Buffer, callback?: Callback<unknown>): Result<unknown, Context>; + xgroup(subcommand: "DESTROY", key: RedisKey, groupname: string | Buffer, callback?: Callback<unknown>): Result<unknown, Context>; + xgroup(subcommand: "HELP", callback?: Callback<unknown>): Result<unknown, Context>; + xgroup(subcommand: "SETID", key: RedisKey, groupname: string | Buffer, id: string | Buffer | number, callback?: Callback<unknown>): Result<unknown, Context>; + xgroup(subcommand: "SETID", key: RedisKey, groupname: string | Buffer, id: string | Buffer | number, entriesReadToken: "ENTRIESREAD", entriesRead: number | string, callback?: Callback<unknown>): Result<unknown, Context>; + xgroup(subcommand: "SETID", key: RedisKey, groupname: string | Buffer, newId: "$", callback?: Callback<unknown>): Result<unknown, Context>; + xgroup(subcommand: "SETID", key: RedisKey, groupname: string | Buffer, newId: "$", entriesReadToken: "ENTRIESREAD", entriesRead: number | string, callback?: Callback<unknown>): Result<unknown, Context>; + xinfo(subcommand: "CONSUMERS", key: RedisKey, groupname: string | Buffer, callback?: Callback<unknown>): Result<unknown, Context>; + xinfo(subcommand: "GROUPS", key: RedisKey, callback?: Callback<unknown>): Result<unknown, Context>; + xinfo(subcommand: "HELP", callback?: Callback<unknown>): Result<unknown, Context>; + xinfo(subcommand: "STREAM", key: RedisKey, callback?: Callback<unknown>): Result<unknown, Context>; + xinfo(subcommand: "STREAM", key: RedisKey, fullToken: "FULL", callback?: Callback<unknown>): Result<unknown, Context>; + xinfo(subcommand: "STREAM", key: RedisKey, fullToken: "FULL", countToken: "COUNT", count: number | string, callback?: Callback<unknown>): Result<unknown, Context>; + xlen(key: RedisKey, callback?: Callback<number>): Result<number, Context>; + xnack(...args: [ + key: RedisKey, + group: string | Buffer, + silent: 'SILENT', + idsToken: 'IDS', + numids: number | string, + ...ids: (string | Buffer | number)[], + callback: Callback<number> + ]): Result<number, Context>; + xnack(...args: [ + key: RedisKey, + group: string | Buffer, + silent: 'SILENT', + idsToken: 'IDS', + numids: number | string, + ...ids: (string | Buffer | number)[] + ]): Result<number, Context>; + xnack(...args: [ + key: RedisKey, + group: string | Buffer, + silent: 'SILENT', + idsToken: 'IDS', + numids: number | string, + ...ids: (string | Buffer | number)[], + force: 'FORCE', + callback: Callback<number> + ]): Result<number, Context>; + xnack(...args: [ + key: RedisKey, + group: string | Buffer, + silent: 'SILENT', + idsToken: 'IDS', + numids: number | string, + ...ids: (string | Buffer | number)[], + force: 'FORCE' + ]): Result<number, Context>; + xnack(...args: [ + key: RedisKey, + group: string | Buffer, + silent: 'SILENT', + idsToken: 'IDS', + numids: number | string, + ...ids: (string | Buffer | number)[], + countToken: 'RETRYCOUNT', + count: number | string, + callback: Callback<number> + ]): Result<number, Context>; + xnack(...args: [ + key: RedisKey, + group: string | Buffer, + silent: 'SILENT', + idsToken: 'IDS', + numids: number | string, + ...ids: (string | Buffer | number)[], + countToken: 'RETRYCOUNT', + count: number | string + ]): Result<number, Context>; + xnack(...args: [ + key: RedisKey, + group: string | Buffer, + silent: 'SILENT', + idsToken: 'IDS', + numids: number | string, + ...ids: (string | Buffer | number)[], + countToken: 'RETRYCOUNT', + count: number | string, + force: 'FORCE', + callback: Callback<number> + ]): Result<number, Context>; + xnack(...args: [ + key: RedisKey, + group: string | Buffer, + silent: 'SILENT', + idsToken: 'IDS', + numids: number | string, + ...ids: (string | Buffer | number)[], + countToken: 'RETRYCOUNT', + count: number | string, + force: 'FORCE' + ]): Result<number, Context>; + xnack(...args: [ + key: RedisKey, + group: string | Buffer, + fail: 'FAIL', + idsToken: 'IDS', + numids: number | string, + ...ids: (string | Buffer | number)[], + callback: Callback<number> + ]): Result<number, Context>; + xnack(...args: [ + key: RedisKey, + group: string | Buffer, + fail: 'FAIL', + idsToken: 'IDS', + numids: number | string, + ...ids: (string | Buffer | number)[] + ]): Result<number, Context>; + xnack(...args: [ + key: RedisKey, + group: string | Buffer, + fail: 'FAIL', + idsToken: 'IDS', + numids: number | string, + ...ids: (string | Buffer | number)[], + force: 'FORCE', + callback: Callback<number> + ]): Result<number, Context>; + xnack(...args: [ + key: RedisKey, + group: string | Buffer, + fail: 'FAIL', + idsToken: 'IDS', + numids: number | string, + ...ids: (string | Buffer | number)[], + force: 'FORCE' + ]): Result<number, Context>; + xnack(...args: [ + key: RedisKey, + group: string | Buffer, + fail: 'FAIL', + idsToken: 'IDS', + numids: number | string, + ...ids: (string | Buffer | number)[], + countToken: 'RETRYCOUNT', + count: number | string, + callback: Callback<number> + ]): Result<number, Context>; + xnack(...args: [ + key: RedisKey, + group: string | Buffer, + fail: 'FAIL', + idsToken: 'IDS', + numids: number | string, + ...ids: (string | Buffer | number)[], + countToken: 'RETRYCOUNT', + count: number | string + ]): Result<number, Context>; + xnack(...args: [ + key: RedisKey, + group: string | Buffer, + fail: 'FAIL', + idsToken: 'IDS', + numids: number | string, + ...ids: (string | Buffer | number)[], + countToken: 'RETRYCOUNT', + count: number | string, + force: 'FORCE', + callback: Callback<number> + ]): Result<number, Context>; + xnack(...args: [ + key: RedisKey, + group: string | Buffer, + fail: 'FAIL', + idsToken: 'IDS', + numids: number | string, + ...ids: (string | Buffer | number)[], + countToken: 'RETRYCOUNT', + count: number | string, + force: 'FORCE' + ]): Result<number, Context>; + xnack(...args: [ + key: RedisKey, + group: string | Buffer, + fatal: 'FATAL', + idsToken: 'IDS', + numids: number | string, + ...ids: (string | Buffer | number)[], + callback: Callback<number> + ]): Result<number, Context>; + xnack(...args: [ + key: RedisKey, + group: string | Buffer, + fatal: 'FATAL', + idsToken: 'IDS', + numids: number | string, + ...ids: (string | Buffer | number)[] + ]): Result<number, Context>; + xnack(...args: [ + key: RedisKey, + group: string | Buffer, + fatal: 'FATAL', + idsToken: 'IDS', + numids: number | string, + ...ids: (string | Buffer | number)[], + force: 'FORCE', + callback: Callback<number> + ]): Result<number, Context>; + xnack(...args: [ + key: RedisKey, + group: string | Buffer, + fatal: 'FATAL', + idsToken: 'IDS', + numids: number | string, + ...ids: (string | Buffer | number)[], + force: 'FORCE' + ]): Result<number, Context>; + xnack(...args: [ + key: RedisKey, + group: string | Buffer, + fatal: 'FATAL', + idsToken: 'IDS', + numids: number | string, + ...ids: (string | Buffer | number)[], + countToken: 'RETRYCOUNT', + count: number | string, + callback: Callback<number> + ]): Result<number, Context>; + xnack(...args: [ + key: RedisKey, + group: string | Buffer, + fatal: 'FATAL', + idsToken: 'IDS', + numids: number | string, + ...ids: (string | Buffer | number)[], + countToken: 'RETRYCOUNT', + count: number | string + ]): Result<number, Context>; + xnack(...args: [ + key: RedisKey, + group: string | Buffer, + fatal: 'FATAL', + idsToken: 'IDS', + numids: number | string, + ...ids: (string | Buffer | number)[], + countToken: 'RETRYCOUNT', + count: number | string, + force: 'FORCE', + callback: Callback<number> + ]): Result<number, Context>; + xnack(...args: [ + key: RedisKey, + group: string | Buffer, + fatal: 'FATAL', + idsToken: 'IDS', + numids: number | string, + ...ids: (string | Buffer | number)[], + countToken: 'RETRYCOUNT', + count: number | string, + force: 'FORCE' + ]): Result<number, Context>; + xpending(key: RedisKey, group: string | Buffer, callback?: Callback<unknown[]>): Result<unknown[], Context>; + xpending(key: RedisKey, group: string | Buffer, start: string | Buffer | number, end: string | Buffer | number, count: number | string, callback?: Callback<unknown[]>): Result<unknown[], Context>; + xpending(key: RedisKey, group: string | Buffer, start: string | Buffer | number, end: string | Buffer | number, count: number | string, consumer: string | Buffer, callback?: Callback<unknown[]>): Result<unknown[], Context>; + xpending(key: RedisKey, group: string | Buffer, minIdleTimeToken: "IDLE", minIdleTime: number | string, start: string | Buffer | number, end: string | Buffer | number, count: number | string, callback?: Callback<unknown[]>): Result<unknown[], Context>; + xpending(key: RedisKey, group: string | Buffer, minIdleTimeToken: "IDLE", minIdleTime: number | string, start: string | Buffer | number, end: string | Buffer | number, count: number | string, consumer: string | Buffer, callback?: Callback<unknown[]>): Result<unknown[], Context>; + xrange(key: RedisKey, start: string | Buffer | number, end: string | Buffer | number, callback?: Callback<[ + id: string, + fields: string[] + ][]>): Result<[ + id: string, + fields: string[] + ][], Context>; + xrangeBuffer(key: RedisKey, start: string | Buffer | number, end: string | Buffer | number, callback?: Callback<[ + id: Buffer, + fields: Buffer[] + ][]>): Result<[ + id: Buffer, + fields: Buffer[] + ][], Context>; + xrange(key: RedisKey, start: string | Buffer | number, end: string | Buffer | number, countToken: "COUNT", count: number | string, callback?: Callback<[ + id: string, + fields: string[] + ][]>): Result<[ + id: string, + fields: string[] + ][], Context>; + xrangeBuffer(key: RedisKey, start: string | Buffer | number, end: string | Buffer | number, countToken: "COUNT", count: number | string, callback?: Callback<[ + id: Buffer, + fields: Buffer[] + ][]>): Result<[ + id: Buffer, + fields: Buffer[] + ][], Context>; + xread(...args: [ + streamsToken: "STREAMS", + ...args: RedisValue[], + callback: Callback<[ + key: string, + items: [ + id: string, + fields: string[] + ][] + ][] | null> + ]): Result<[ + key: string, + items: [ + id: string, + fields: string[] + ][] + ][] | null, Context>; + xreadBuffer(...args: [ + streamsToken: "STREAMS", + ...args: RedisValue[], + callback: Callback<[ + key: Buffer, + items: [ + id: Buffer, + fields: Buffer[] + ][] + ][] | null> + ]): Result<[ + key: Buffer, + items: [ + id: Buffer, + fields: Buffer[] + ][] + ][] | null, Context>; + xread(...args: [ + streamsToken: "STREAMS", + ...args: RedisValue[] + ]): Result<[ + key: string, + items: [ + id: string, + fields: string[] + ][] + ][] | null, Context>; + xreadBuffer(...args: [ + streamsToken: "STREAMS", + ...args: RedisValue[] + ]): Result<[ + key: Buffer, + items: [ + id: Buffer, + fields: Buffer[] + ][] + ][] | null, Context>; + xread(...args: [ + millisecondsToken: "BLOCK", + milliseconds: number | string, + streamsToken: "STREAMS", + ...args: RedisValue[], + callback: Callback<[ + key: string, + items: [ + id: string, + fields: string[] + ][] + ][] | null> + ]): Result<[ + key: string, + items: [ + id: string, + fields: string[] + ][] + ][] | null, Context>; + xreadBuffer(...args: [ + millisecondsToken: "BLOCK", + milliseconds: number | string, + streamsToken: "STREAMS", + ...args: RedisValue[], + callback: Callback<[ + key: Buffer, + items: [ + id: Buffer, + fields: Buffer[] + ][] + ][] | null> + ]): Result<[ + key: Buffer, + items: [ + id: Buffer, + fields: Buffer[] + ][] + ][] | null, Context>; + xread(...args: [ + millisecondsToken: "BLOCK", + milliseconds: number | string, + streamsToken: "STREAMS", + ...args: RedisValue[] + ]): Result<[ + key: string, + items: [ + id: string, + fields: string[] + ][] + ][] | null, Context>; + xreadBuffer(...args: [ + millisecondsToken: "BLOCK", + milliseconds: number | string, + streamsToken: "STREAMS", + ...args: RedisValue[] + ]): Result<[ + key: Buffer, + items: [ + id: Buffer, + fields: Buffer[] + ][] + ][] | null, Context>; + xread(...args: [ + countToken: "COUNT", + count: number | string, + streamsToken: "STREAMS", + ...args: RedisValue[], + callback: Callback<[ + key: string, + items: [ + id: string, + fields: string[] + ][] + ][] | null> + ]): Result<[ + key: string, + items: [ + id: string, + fields: string[] + ][] + ][] | null, Context>; + xreadBuffer(...args: [ + countToken: "COUNT", + count: number | string, + streamsToken: "STREAMS", + ...args: RedisValue[], + callback: Callback<[ + key: Buffer, + items: [ + id: Buffer, + fields: Buffer[] + ][] + ][] | null> + ]): Result<[ + key: Buffer, + items: [ + id: Buffer, + fields: Buffer[] + ][] + ][] | null, Context>; + xread(...args: [ + countToken: "COUNT", + count: number | string, + streamsToken: "STREAMS", + ...args: RedisValue[] + ]): Result<[ + key: string, + items: [ + id: string, + fields: string[] + ][] + ][] | null, Context>; + xreadBuffer(...args: [ + countToken: "COUNT", + count: number | string, + streamsToken: "STREAMS", + ...args: RedisValue[] + ]): Result<[ + key: Buffer, + items: [ + id: Buffer, + fields: Buffer[] + ][] + ][] | null, Context>; + xread(...args: [ + countToken: "COUNT", + count: number | string, + millisecondsToken: "BLOCK", + milliseconds: number | string, + streamsToken: "STREAMS", + ...args: RedisValue[], + callback: Callback<[ + key: string, + items: [ + id: string, + fields: string[] + ][] + ][] | null> + ]): Result<[ + key: string, + items: [ + id: string, + fields: string[] + ][] + ][] | null, Context>; + xreadBuffer(...args: [ + countToken: "COUNT", + count: number | string, + millisecondsToken: "BLOCK", + milliseconds: number | string, + streamsToken: "STREAMS", + ...args: RedisValue[], + callback: Callback<[ + key: Buffer, + items: [ + id: Buffer, + fields: Buffer[] + ][] + ][] | null> + ]): Result<[ + key: Buffer, + items: [ + id: Buffer, + fields: Buffer[] + ][] + ][] | null, Context>; + xread(...args: [ + countToken: "COUNT", + count: number | string, + millisecondsToken: "BLOCK", + milliseconds: number | string, + streamsToken: "STREAMS", + ...args: RedisValue[] + ]): Result<[ + key: string, + items: [ + id: string, + fields: string[] + ][] + ][] | null, Context>; + xreadBuffer(...args: [ + countToken: "COUNT", + count: number | string, + millisecondsToken: "BLOCK", + milliseconds: number | string, + streamsToken: "STREAMS", + ...args: RedisValue[] + ]): Result<[ + key: Buffer, + items: [ + id: Buffer, + fields: Buffer[] + ][] + ][] | null, Context>; + xreadgroup(...args: [ + groupConsumerToken: "GROUP", + group: string | Buffer, + consumer: string | Buffer, + streamsToken: "STREAMS", + ...args: RedisValue[], + callback: Callback<unknown[]> + ]): Result<unknown[], Context>; + xreadgroup(...args: [ + groupConsumerToken: "GROUP", + group: string | Buffer, + consumer: string | Buffer, + streamsToken: "STREAMS", + ...args: RedisValue[] + ]): Result<unknown[], Context>; + xreadgroup(...args: [ + groupConsumerToken: "GROUP", + group: string | Buffer, + consumer: string | Buffer, + noack: "NOACK", + streamsToken: "STREAMS", + ...args: RedisValue[], + callback: Callback<unknown[]> + ]): Result<unknown[], Context>; + xreadgroup(...args: [ + groupConsumerToken: "GROUP", + group: string | Buffer, + consumer: string | Buffer, + noack: "NOACK", + streamsToken: "STREAMS", + ...args: RedisValue[] + ]): Result<unknown[], Context>; + xreadgroup(...args: [ + groupConsumerToken: "GROUP", + group: string | Buffer, + consumer: string | Buffer, + millisecondsToken: "BLOCK", + milliseconds: number | string, + streamsToken: "STREAMS", + ...args: RedisValue[], + callback: Callback<unknown[]> + ]): Result<unknown[], Context>; + xreadgroup(...args: [ + groupConsumerToken: "GROUP", + group: string | Buffer, + consumer: string | Buffer, + millisecondsToken: "BLOCK", + milliseconds: number | string, + streamsToken: "STREAMS", + ...args: RedisValue[] + ]): Result<unknown[], Context>; + xreadgroup(...args: [ + groupConsumerToken: "GROUP", + group: string | Buffer, + consumer: string | Buffer, + millisecondsToken: "BLOCK", + milliseconds: number | string, + noack: "NOACK", + streamsToken: "STREAMS", + ...args: RedisValue[], + callback: Callback<unknown[]> + ]): Result<unknown[], Context>; + xreadgroup(...args: [ + groupConsumerToken: "GROUP", + group: string | Buffer, + consumer: string | Buffer, + millisecondsToken: "BLOCK", + milliseconds: number | string, + noack: "NOACK", + streamsToken: "STREAMS", + ...args: RedisValue[] + ]): Result<unknown[], Context>; + xreadgroup(...args: [ + groupConsumerToken: "GROUP", + group: string | Buffer, + consumer: string | Buffer, + countToken: "COUNT", + count: number | string, + streamsToken: "STREAMS", + ...args: RedisValue[], + callback: Callback<unknown[]> + ]): Result<unknown[], Context>; + xreadgroup(...args: [ + groupConsumerToken: "GROUP", + group: string | Buffer, + consumer: string | Buffer, + countToken: "COUNT", + count: number | string, + streamsToken: "STREAMS", + ...args: RedisValue[] + ]): Result<unknown[], Context>; + xreadgroup(...args: [ + groupConsumerToken: "GROUP", + group: string | Buffer, + consumer: string | Buffer, + countToken: "COUNT", + count: number | string, + noack: "NOACK", + streamsToken: "STREAMS", + ...args: RedisValue[], + callback: Callback<unknown[]> + ]): Result<unknown[], Context>; + xreadgroup(...args: [ + groupConsumerToken: "GROUP", + group: string | Buffer, + consumer: string | Buffer, + countToken: "COUNT", + count: number | string, + noack: "NOACK", + streamsToken: "STREAMS", + ...args: RedisValue[] + ]): Result<unknown[], Context>; + xreadgroup(...args: [ + groupConsumerToken: "GROUP", + group: string | Buffer, + consumer: string | Buffer, + countToken: "COUNT", + count: number | string, + millisecondsToken: "BLOCK", + milliseconds: number | string, + streamsToken: "STREAMS", + ...args: RedisValue[], + callback: Callback<unknown[]> + ]): Result<unknown[], Context>; + xreadgroup(...args: [ + groupConsumerToken: "GROUP", + group: string | Buffer, + consumer: string | Buffer, + countToken: "COUNT", + count: number | string, + millisecondsToken: "BLOCK", + milliseconds: number | string, + streamsToken: "STREAMS", + ...args: RedisValue[] + ]): Result<unknown[], Context>; + xreadgroup(...args: [ + groupConsumerToken: "GROUP", + group: string | Buffer, + consumer: string | Buffer, + countToken: "COUNT", + count: number | string, + millisecondsToken: "BLOCK", + milliseconds: number | string, + noack: "NOACK", + streamsToken: "STREAMS", + ...args: RedisValue[], + callback: Callback<unknown[]> + ]): Result<unknown[], Context>; + xreadgroup(...args: [ + groupConsumerToken: "GROUP", + group: string | Buffer, + consumer: string | Buffer, + countToken: "COUNT", + count: number | string, + millisecondsToken: "BLOCK", + milliseconds: number | string, + noack: "NOACK", + streamsToken: "STREAMS", + ...args: RedisValue[] + ]): Result<unknown[], Context>; + xrevrange(key: RedisKey, end: string | Buffer | number, start: string | Buffer | number, callback?: Callback<[ + id: string, + fields: string[] + ][]>): Result<[ + id: string, + fields: string[] + ][], Context>; + xrevrangeBuffer(key: RedisKey, end: string | Buffer | number, start: string | Buffer | number, callback?: Callback<[ + id: Buffer, + fields: Buffer[] + ][]>): Result<[ + id: Buffer, + fields: Buffer[] + ][], Context>; + xrevrange(key: RedisKey, end: string | Buffer | number, start: string | Buffer | number, countToken: "COUNT", count: number | string, callback?: Callback<[ + id: string, + fields: string[] + ][]>): Result<[ + id: string, + fields: string[] + ][], Context>; + xrevrangeBuffer(key: RedisKey, end: string | Buffer | number, start: string | Buffer | number, countToken: "COUNT", count: number | string, callback?: Callback<[ + id: Buffer, + fields: Buffer[] + ][]>): Result<[ + id: Buffer, + fields: Buffer[] + ][], Context>; + xsetid(key: RedisKey, lastId: string | Buffer | number, callback?: Callback<unknown>): Result<unknown, Context>; + xsetid(key: RedisKey, lastId: string | Buffer | number, maxDeletedEntryIdToken: "MAXDELETEDID", maxDeletedEntryId: string | Buffer | number, callback?: Callback<unknown>): Result<unknown, Context>; + xsetid(key: RedisKey, lastId: string | Buffer | number, entriesAddedToken: "ENTRIESADDED", entriesAdded: number | string, callback?: Callback<unknown>): Result<unknown, Context>; + xsetid(key: RedisKey, lastId: string | Buffer | number, entriesAddedToken: "ENTRIESADDED", entriesAdded: number | string, maxDeletedEntryIdToken: "MAXDELETEDID", maxDeletedEntryId: string | Buffer | number, callback?: Callback<unknown>): Result<unknown, Context>; + xtrim(key: RedisKey, maxlen: "MAXLEN", threshold: string | Buffer | number, callback?: Callback<number>): Result<number, Context>; + xtrim(key: RedisKey, maxlen: "MAXLEN", threshold: string | Buffer | number, keepref: "KEEPREF", callback?: Callback<number>): Result<number, Context>; + xtrim(key: RedisKey, maxlen: "MAXLEN", threshold: string | Buffer | number, delref: "DELREF", callback?: Callback<number>): Result<number, Context>; + xtrim(key: RedisKey, maxlen: "MAXLEN", threshold: string | Buffer | number, acked: "ACKED", callback?: Callback<number>): Result<number, Context>; + xtrim(key: RedisKey, maxlen: "MAXLEN", threshold: string | Buffer | number, countToken: "LIMIT", count: number | string, callback?: Callback<number>): Result<number, Context>; + xtrim(key: RedisKey, maxlen: "MAXLEN", threshold: string | Buffer | number, countToken: "LIMIT", count: number | string, keepref: "KEEPREF", callback?: Callback<number>): Result<number, Context>; + xtrim(key: RedisKey, maxlen: "MAXLEN", threshold: string | Buffer | number, countToken: "LIMIT", count: number | string, delref: "DELREF", callback?: Callback<number>): Result<number, Context>; + xtrim(key: RedisKey, maxlen: "MAXLEN", threshold: string | Buffer | number, countToken: "LIMIT", count: number | string, acked: "ACKED", callback?: Callback<number>): Result<number, Context>; + xtrim(key: RedisKey, maxlen: "MAXLEN", equal: "=", threshold: string | Buffer | number, callback?: Callback<number>): Result<number, Context>; + xtrim(key: RedisKey, maxlen: "MAXLEN", equal: "=", threshold: string | Buffer | number, keepref: "KEEPREF", callback?: Callback<number>): Result<number, Context>; + xtrim(key: RedisKey, maxlen: "MAXLEN", equal: "=", threshold: string | Buffer | number, delref: "DELREF", callback?: Callback<number>): Result<number, Context>; + xtrim(key: RedisKey, maxlen: "MAXLEN", equal: "=", threshold: string | Buffer | number, acked: "ACKED", callback?: Callback<number>): Result<number, Context>; + xtrim(key: RedisKey, maxlen: "MAXLEN", equal: "=", threshold: string | Buffer | number, countToken: "LIMIT", count: number | string, callback?: Callback<number>): Result<number, Context>; + xtrim(key: RedisKey, maxlen: "MAXLEN", equal: "=", threshold: string | Buffer | number, countToken: "LIMIT", count: number | string, keepref: "KEEPREF", callback?: Callback<number>): Result<number, Context>; + xtrim(key: RedisKey, maxlen: "MAXLEN", equal: "=", threshold: string | Buffer | number, countToken: "LIMIT", count: number | string, delref: "DELREF", callback?: Callback<number>): Result<number, Context>; + xtrim(key: RedisKey, maxlen: "MAXLEN", equal: "=", threshold: string | Buffer | number, countToken: "LIMIT", count: number | string, acked: "ACKED", callback?: Callback<number>): Result<number, Context>; + xtrim(key: RedisKey, maxlen: "MAXLEN", approximately: "~", threshold: string | Buffer | number, callback?: Callback<number>): Result<number, Context>; + xtrim(key: RedisKey, maxlen: "MAXLEN", approximately: "~", threshold: string | Buffer | number, keepref: "KEEPREF", callback?: Callback<number>): Result<number, Context>; + xtrim(key: RedisKey, maxlen: "MAXLEN", approximately: "~", threshold: string | Buffer | number, delref: "DELREF", callback?: Callback<number>): Result<number, Context>; + xtrim(key: RedisKey, maxlen: "MAXLEN", approximately: "~", threshold: string | Buffer | number, acked: "ACKED", callback?: Callback<number>): Result<number, Context>; + xtrim(key: RedisKey, maxlen: "MAXLEN", approximately: "~", threshold: string | Buffer | number, countToken: "LIMIT", count: number | string, callback?: Callback<number>): Result<number, Context>; + xtrim(key: RedisKey, maxlen: "MAXLEN", approximately: "~", threshold: string | Buffer | number, countToken: "LIMIT", count: number | string, keepref: "KEEPREF", callback?: Callback<number>): Result<number, Context>; + xtrim(key: RedisKey, maxlen: "MAXLEN", approximately: "~", threshold: string | Buffer | number, countToken: "LIMIT", count: number | string, delref: "DELREF", callback?: Callback<number>): Result<number, Context>; + xtrim(key: RedisKey, maxlen: "MAXLEN", approximately: "~", threshold: string | Buffer | number, countToken: "LIMIT", count: number | string, acked: "ACKED", callback?: Callback<number>): Result<number, Context>; + xtrim(key: RedisKey, minid: "MINID", threshold: string | Buffer | number, callback?: Callback<number>): Result<number, Context>; + xtrim(key: RedisKey, minid: "MINID", threshold: string | Buffer | number, keepref: "KEEPREF", callback?: Callback<number>): Result<number, Context>; + xtrim(key: RedisKey, minid: "MINID", threshold: string | Buffer | number, delref: "DELREF", callback?: Callback<number>): Result<number, Context>; + xtrim(key: RedisKey, minid: "MINID", threshold: string | Buffer | number, acked: "ACKED", callback?: Callback<number>): Result<number, Context>; + xtrim(key: RedisKey, minid: "MINID", threshold: string | Buffer | number, countToken: "LIMIT", count: number | string, callback?: Callback<number>): Result<number, Context>; + xtrim(key: RedisKey, minid: "MINID", threshold: string | Buffer | number, countToken: "LIMIT", count: number | string, keepref: "KEEPREF", callback?: Callback<number>): Result<number, Context>; + xtrim(key: RedisKey, minid: "MINID", threshold: string | Buffer | number, countToken: "LIMIT", count: number | string, delref: "DELREF", callback?: Callback<number>): Result<number, Context>; + xtrim(key: RedisKey, minid: "MINID", threshold: string | Buffer | number, countToken: "LIMIT", count: number | string, acked: "ACKED", callback?: Callback<number>): Result<number, Context>; + xtrim(key: RedisKey, minid: "MINID", equal: "=", threshold: string | Buffer | number, callback?: Callback<number>): Result<number, Context>; + xtrim(key: RedisKey, minid: "MINID", equal: "=", threshold: string | Buffer | number, keepref: "KEEPREF", callback?: Callback<number>): Result<number, Context>; + xtrim(key: RedisKey, minid: "MINID", equal: "=", threshold: string | Buffer | number, delref: "DELREF", callback?: Callback<number>): Result<number, Context>; + xtrim(key: RedisKey, minid: "MINID", equal: "=", threshold: string | Buffer | number, acked: "ACKED", callback?: Callback<number>): Result<number, Context>; + xtrim(key: RedisKey, minid: "MINID", equal: "=", threshold: string | Buffer | number, countToken: "LIMIT", count: number | string, callback?: Callback<number>): Result<number, Context>; + xtrim(key: RedisKey, minid: "MINID", equal: "=", threshold: string | Buffer | number, countToken: "LIMIT", count: number | string, keepref: "KEEPREF", callback?: Callback<number>): Result<number, Context>; + xtrim(key: RedisKey, minid: "MINID", equal: "=", threshold: string | Buffer | number, countToken: "LIMIT", count: number | string, delref: "DELREF", callback?: Callback<number>): Result<number, Context>; + xtrim(key: RedisKey, minid: "MINID", equal: "=", threshold: string | Buffer | number, countToken: "LIMIT", count: number | string, acked: "ACKED", callback?: Callback<number>): Result<number, Context>; + xtrim(key: RedisKey, minid: "MINID", approximately: "~", threshold: string | Buffer | number, callback?: Callback<number>): Result<number, Context>; + xtrim(key: RedisKey, minid: "MINID", approximately: "~", threshold: string | Buffer | number, keepref: "KEEPREF", callback?: Callback<number>): Result<number, Context>; + xtrim(key: RedisKey, minid: "MINID", approximately: "~", threshold: string | Buffer | number, delref: "DELREF", callback?: Callback<number>): Result<number, Context>; + xtrim(key: RedisKey, minid: "MINID", approximately: "~", threshold: string | Buffer | number, acked: "ACKED", callback?: Callback<number>): Result<number, Context>; + xtrim(key: RedisKey, minid: "MINID", approximately: "~", threshold: string | Buffer | number, countToken: "LIMIT", count: number | string, callback?: Callback<number>): Result<number, Context>; + xtrim(key: RedisKey, minid: "MINID", approximately: "~", threshold: string | Buffer | number, countToken: "LIMIT", count: number | string, keepref: "KEEPREF", callback?: Callback<number>): Result<number, Context>; + xtrim(key: RedisKey, minid: "MINID", approximately: "~", threshold: string | Buffer | number, countToken: "LIMIT", count: number | string, delref: "DELREF", callback?: Callback<number>): Result<number, Context>; + xtrim(key: RedisKey, minid: "MINID", approximately: "~", threshold: string | Buffer | number, countToken: "LIMIT", count: number | string, acked: "ACKED", callback?: Callback<number>): Result<number, Context>; + zadd(...args: [ + key: RedisKey, + ...scoreMembers: (string | Buffer | number)[], + callback: Callback<number> + ]): Result<number, Context>; + zadd(...args: [ + key: RedisKey, + ...scoreMembers: (string | Buffer | number)[] + ]): Result<number, Context>; + zadd(...args: [ + key: RedisKey, + incr: "INCR", + ...scoreMembers: (string | Buffer | number)[], + callback: Callback<string> + ]): Result<string, Context>; + zaddBuffer(...args: [ + key: RedisKey, + incr: "INCR", + ...scoreMembers: (string | Buffer | number)[], + callback: Callback<Buffer> + ]): Result<Buffer, Context>; + zadd(...args: [ + key: RedisKey, + incr: "INCR", + ...scoreMembers: (string | Buffer | number)[] + ]): Result<string, Context>; + zaddBuffer(...args: [ + key: RedisKey, + incr: "INCR", + ...scoreMembers: (string | Buffer | number)[] + ]): Result<Buffer, Context>; + zadd(...args: [ + key: RedisKey, + ch: "CH", + ...scoreMembers: (string | Buffer | number)[], + callback: Callback<number> + ]): Result<number, Context>; + zadd(...args: [ + key: RedisKey, + ch: "CH", + ...scoreMembers: (string | Buffer | number)[] + ]): Result<number, Context>; + zadd(...args: [ + key: RedisKey, + ch: "CH", + incr: "INCR", + ...scoreMembers: (string | Buffer | number)[], + callback: Callback<string> + ]): Result<string, Context>; + zaddBuffer(...args: [ + key: RedisKey, + ch: "CH", + incr: "INCR", + ...scoreMembers: (string | Buffer | number)[], + callback: Callback<Buffer> + ]): Result<Buffer, Context>; + zadd(...args: [ + key: RedisKey, + ch: "CH", + incr: "INCR", + ...scoreMembers: (string | Buffer | number)[] + ]): Result<string, Context>; + zaddBuffer(...args: [ + key: RedisKey, + ch: "CH", + incr: "INCR", + ...scoreMembers: (string | Buffer | number)[] + ]): Result<Buffer, Context>; + zadd(...args: [ + key: RedisKey, + gt: "GT", + ...scoreMembers: (string | Buffer | number)[], + callback: Callback<number> + ]): Result<number, Context>; + zadd(...args: [ + key: RedisKey, + gt: "GT", + ...scoreMembers: (string | Buffer | number)[] + ]): Result<number, Context>; + zadd(...args: [ + key: RedisKey, + gt: "GT", + incr: "INCR", + ...scoreMembers: (string | Buffer | number)[], + callback: Callback<string> + ]): Result<string, Context>; + zaddBuffer(...args: [ + key: RedisKey, + gt: "GT", + incr: "INCR", + ...scoreMembers: (string | Buffer | number)[], + callback: Callback<Buffer> + ]): Result<Buffer, Context>; + zadd(...args: [ + key: RedisKey, + gt: "GT", + incr: "INCR", + ...scoreMembers: (string | Buffer | number)[] + ]): Result<string, Context>; + zaddBuffer(...args: [ + key: RedisKey, + gt: "GT", + incr: "INCR", + ...scoreMembers: (string | Buffer | number)[] + ]): Result<Buffer, Context>; + zadd(...args: [ + key: RedisKey, + gt: "GT", + ch: "CH", + ...scoreMembers: (string | Buffer | number)[], + callback: Callback<number> + ]): Result<number, Context>; + zadd(...args: [ + key: RedisKey, + gt: "GT", + ch: "CH", + ...scoreMembers: (string | Buffer | number)[] + ]): Result<number, Context>; + zadd(...args: [ + key: RedisKey, + gt: "GT", + ch: "CH", + incr: "INCR", + ...scoreMembers: (string | Buffer | number)[], + callback: Callback<string> + ]): Result<string, Context>; + zaddBuffer(...args: [ + key: RedisKey, + gt: "GT", + ch: "CH", + incr: "INCR", + ...scoreMembers: (string | Buffer | number)[], + callback: Callback<Buffer> + ]): Result<Buffer, Context>; + zadd(...args: [ + key: RedisKey, + gt: "GT", + ch: "CH", + incr: "INCR", + ...scoreMembers: (string | Buffer | number)[] + ]): Result<string, Context>; + zaddBuffer(...args: [ + key: RedisKey, + gt: "GT", + ch: "CH", + incr: "INCR", + ...scoreMembers: (string | Buffer | number)[] + ]): Result<Buffer, Context>; + zadd(...args: [ + key: RedisKey, + lt: "LT", + ...scoreMembers: (string | Buffer | number)[], + callback: Callback<number> + ]): Result<number, Context>; + zadd(...args: [ + key: RedisKey, + lt: "LT", + ...scoreMembers: (string | Buffer | number)[] + ]): Result<number, Context>; + zadd(...args: [ + key: RedisKey, + lt: "LT", + incr: "INCR", + ...scoreMembers: (string | Buffer | number)[], + callback: Callback<string> + ]): Result<string, Context>; + zaddBuffer(...args: [ + key: RedisKey, + lt: "LT", + incr: "INCR", + ...scoreMembers: (string | Buffer | number)[], + callback: Callback<Buffer> + ]): Result<Buffer, Context>; + zadd(...args: [ + key: RedisKey, + lt: "LT", + incr: "INCR", + ...scoreMembers: (string | Buffer | number)[] + ]): Result<string, Context>; + zaddBuffer(...args: [ + key: RedisKey, + lt: "LT", + incr: "INCR", + ...scoreMembers: (string | Buffer | number)[] + ]): Result<Buffer, Context>; + zadd(...args: [ + key: RedisKey, + lt: "LT", + ch: "CH", + ...scoreMembers: (string | Buffer | number)[], + callback: Callback<number> + ]): Result<number, Context>; + zadd(...args: [ + key: RedisKey, + lt: "LT", + ch: "CH", + ...scoreMembers: (string | Buffer | number)[] + ]): Result<number, Context>; + zadd(...args: [ + key: RedisKey, + lt: "LT", + ch: "CH", + incr: "INCR", + ...scoreMembers: (string | Buffer | number)[], + callback: Callback<string> + ]): Result<string, Context>; + zaddBuffer(...args: [ + key: RedisKey, + lt: "LT", + ch: "CH", + incr: "INCR", + ...scoreMembers: (string | Buffer | number)[], + callback: Callback<Buffer> + ]): Result<Buffer, Context>; + zadd(...args: [ + key: RedisKey, + lt: "LT", + ch: "CH", + incr: "INCR", + ...scoreMembers: (string | Buffer | number)[] + ]): Result<string, Context>; + zaddBuffer(...args: [ + key: RedisKey, + lt: "LT", + ch: "CH", + incr: "INCR", + ...scoreMembers: (string | Buffer | number)[] + ]): Result<Buffer, Context>; + zadd(...args: [ + key: RedisKey, + nx: "NX", + ...scoreMembers: (string | Buffer | number)[], + callback: Callback<number> + ]): Result<number, Context>; + zadd(...args: [ + key: RedisKey, + nx: "NX", + ...scoreMembers: (string | Buffer | number)[] + ]): Result<number, Context>; + zadd(...args: [ + key: RedisKey, + nx: "NX", + incr: "INCR", + ...scoreMembers: (string | Buffer | number)[], + callback: Callback<string | null> + ]): Result<string | null, Context>; + zaddBuffer(...args: [ + key: RedisKey, + nx: "NX", + incr: "INCR", + ...scoreMembers: (string | Buffer | number)[], + callback: Callback<Buffer | null> + ]): Result<Buffer | null, Context>; + zadd(...args: [ + key: RedisKey, + nx: "NX", + incr: "INCR", + ...scoreMembers: (string | Buffer | number)[] + ]): Result<string | null, Context>; + zaddBuffer(...args: [ + key: RedisKey, + nx: "NX", + incr: "INCR", + ...scoreMembers: (string | Buffer | number)[] + ]): Result<Buffer | null, Context>; + zadd(...args: [ + key: RedisKey, + nx: "NX", + ch: "CH", + ...scoreMembers: (string | Buffer | number)[], + callback: Callback<number> + ]): Result<number, Context>; + zadd(...args: [ + key: RedisKey, + nx: "NX", + ch: "CH", + ...scoreMembers: (string | Buffer | number)[] + ]): Result<number, Context>; + zadd(...args: [ + key: RedisKey, + nx: "NX", + ch: "CH", + incr: "INCR", + ...scoreMembers: (string | Buffer | number)[], + callback: Callback<string | null> + ]): Result<string | null, Context>; + zaddBuffer(...args: [ + key: RedisKey, + nx: "NX", + ch: "CH", + incr: "INCR", + ...scoreMembers: (string | Buffer | number)[], + callback: Callback<Buffer | null> + ]): Result<Buffer | null, Context>; + zadd(...args: [ + key: RedisKey, + nx: "NX", + ch: "CH", + incr: "INCR", + ...scoreMembers: (string | Buffer | number)[] + ]): Result<string | null, Context>; + zaddBuffer(...args: [ + key: RedisKey, + nx: "NX", + ch: "CH", + incr: "INCR", + ...scoreMembers: (string | Buffer | number)[] + ]): Result<Buffer | null, Context>; + zadd(...args: [ + key: RedisKey, + nx: "NX", + gt: "GT", + ...scoreMembers: (string | Buffer | number)[], + callback: Callback<number> + ]): Result<number, Context>; + zadd(...args: [ + key: RedisKey, + nx: "NX", + gt: "GT", + ...scoreMembers: (string | Buffer | number)[] + ]): Result<number, Context>; + zadd(...args: [ + key: RedisKey, + nx: "NX", + gt: "GT", + incr: "INCR", + ...scoreMembers: (string | Buffer | number)[], + callback: Callback<string | null> + ]): Result<string | null, Context>; + zaddBuffer(...args: [ + key: RedisKey, + nx: "NX", + gt: "GT", + incr: "INCR", + ...scoreMembers: (string | Buffer | number)[], + callback: Callback<Buffer | null> + ]): Result<Buffer | null, Context>; + zadd(...args: [ + key: RedisKey, + nx: "NX", + gt: "GT", + incr: "INCR", + ...scoreMembers: (string | Buffer | number)[] + ]): Result<string | null, Context>; + zaddBuffer(...args: [ + key: RedisKey, + nx: "NX", + gt: "GT", + incr: "INCR", + ...scoreMembers: (string | Buffer | number)[] + ]): Result<Buffer | null, Context>; + zadd(...args: [ + key: RedisKey, + nx: "NX", + gt: "GT", + ch: "CH", + ...scoreMembers: (string | Buffer | number)[], + callback: Callback<number> + ]): Result<number, Context>; + zadd(...args: [ + key: RedisKey, + nx: "NX", + gt: "GT", + ch: "CH", + ...scoreMembers: (string | Buffer | number)[] + ]): Result<number, Context>; + zadd(...args: [ + key: RedisKey, + nx: "NX", + gt: "GT", + ch: "CH", + incr: "INCR", + ...scoreMembers: (string | Buffer | number)[], + callback: Callback<string | null> + ]): Result<string | null, Context>; + zaddBuffer(...args: [ + key: RedisKey, + nx: "NX", + gt: "GT", + ch: "CH", + incr: "INCR", + ...scoreMembers: (string | Buffer | number)[], + callback: Callback<Buffer | null> + ]): Result<Buffer | null, Context>; + zadd(...args: [ + key: RedisKey, + nx: "NX", + gt: "GT", + ch: "CH", + incr: "INCR", + ...scoreMembers: (string | Buffer | number)[] + ]): Result<string | null, Context>; + zaddBuffer(...args: [ + key: RedisKey, + nx: "NX", + gt: "GT", + ch: "CH", + incr: "INCR", + ...scoreMembers: (string | Buffer | number)[] + ]): Result<Buffer | null, Context>; + zadd(...args: [ + key: RedisKey, + nx: "NX", + lt: "LT", + ...scoreMembers: (string | Buffer | number)[], + callback: Callback<number> + ]): Result<number, Context>; + zadd(...args: [ + key: RedisKey, + nx: "NX", + lt: "LT", + ...scoreMembers: (string | Buffer | number)[] + ]): Result<number, Context>; + zadd(...args: [ + key: RedisKey, + nx: "NX", + lt: "LT", + incr: "INCR", + ...scoreMembers: (string | Buffer | number)[], + callback: Callback<string | null> + ]): Result<string | null, Context>; + zaddBuffer(...args: [ + key: RedisKey, + nx: "NX", + lt: "LT", + incr: "INCR", + ...scoreMembers: (string | Buffer | number)[], + callback: Callback<Buffer | null> + ]): Result<Buffer | null, Context>; + zadd(...args: [ + key: RedisKey, + nx: "NX", + lt: "LT", + incr: "INCR", + ...scoreMembers: (string | Buffer | number)[] + ]): Result<string | null, Context>; + zaddBuffer(...args: [ + key: RedisKey, + nx: "NX", + lt: "LT", + incr: "INCR", + ...scoreMembers: (string | Buffer | number)[] + ]): Result<Buffer | null, Context>; + zadd(...args: [ + key: RedisKey, + nx: "NX", + lt: "LT", + ch: "CH", + ...scoreMembers: (string | Buffer | number)[], + callback: Callback<number> + ]): Result<number, Context>; + zadd(...args: [ + key: RedisKey, + nx: "NX", + lt: "LT", + ch: "CH", + ...scoreMembers: (string | Buffer | number)[] + ]): Result<number, Context>; + zadd(...args: [ + key: RedisKey, + nx: "NX", + lt: "LT", + ch: "CH", + incr: "INCR", + ...scoreMembers: (string | Buffer | number)[], + callback: Callback<string | null> + ]): Result<string | null, Context>; + zaddBuffer(...args: [ + key: RedisKey, + nx: "NX", + lt: "LT", + ch: "CH", + incr: "INCR", + ...scoreMembers: (string | Buffer | number)[], + callback: Callback<Buffer | null> + ]): Result<Buffer | null, Context>; + zadd(...args: [ + key: RedisKey, + nx: "NX", + lt: "LT", + ch: "CH", + incr: "INCR", + ...scoreMembers: (string | Buffer | number)[] + ]): Result<string | null, Context>; + zaddBuffer(...args: [ + key: RedisKey, + nx: "NX", + lt: "LT", + ch: "CH", + incr: "INCR", + ...scoreMembers: (string | Buffer | number)[] + ]): Result<Buffer | null, Context>; + zadd(...args: [ + key: RedisKey, + xx: "XX", + ...scoreMembers: (string | Buffer | number)[], + callback: Callback<number> + ]): Result<number, Context>; + zadd(...args: [ + key: RedisKey, + xx: "XX", + ...scoreMembers: (string | Buffer | number)[] + ]): Result<number, Context>; + zadd(...args: [ + key: RedisKey, + xx: "XX", + incr: "INCR", + ...scoreMembers: (string | Buffer | number)[], + callback: Callback<string | null> + ]): Result<string | null, Context>; + zaddBuffer(...args: [ + key: RedisKey, + xx: "XX", + incr: "INCR", + ...scoreMembers: (string | Buffer | number)[], + callback: Callback<Buffer | null> + ]): Result<Buffer | null, Context>; + zadd(...args: [ + key: RedisKey, + xx: "XX", + incr: "INCR", + ...scoreMembers: (string | Buffer | number)[] + ]): Result<string | null, Context>; + zaddBuffer(...args: [ + key: RedisKey, + xx: "XX", + incr: "INCR", + ...scoreMembers: (string | Buffer | number)[] + ]): Result<Buffer | null, Context>; + zadd(...args: [ + key: RedisKey, + xx: "XX", + ch: "CH", + ...scoreMembers: (string | Buffer | number)[], + callback: Callback<number> + ]): Result<number, Context>; + zadd(...args: [ + key: RedisKey, + xx: "XX", + ch: "CH", + ...scoreMembers: (string | Buffer | number)[] + ]): Result<number, Context>; + zadd(...args: [ + key: RedisKey, + xx: "XX", + ch: "CH", + incr: "INCR", + ...scoreMembers: (string | Buffer | number)[], + callback: Callback<string | null> + ]): Result<string | null, Context>; + zaddBuffer(...args: [ + key: RedisKey, + xx: "XX", + ch: "CH", + incr: "INCR", + ...scoreMembers: (string | Buffer | number)[], + callback: Callback<Buffer | null> + ]): Result<Buffer | null, Context>; + zadd(...args: [ + key: RedisKey, + xx: "XX", + ch: "CH", + incr: "INCR", + ...scoreMembers: (string | Buffer | number)[] + ]): Result<string | null, Context>; + zaddBuffer(...args: [ + key: RedisKey, + xx: "XX", + ch: "CH", + incr: "INCR", + ...scoreMembers: (string | Buffer | number)[] + ]): Result<Buffer | null, Context>; + zadd(...args: [ + key: RedisKey, + xx: "XX", + gt: "GT", + ...scoreMembers: (string | Buffer | number)[], + callback: Callback<number> + ]): Result<number, Context>; + zadd(...args: [ + key: RedisKey, + xx: "XX", + gt: "GT", + ...scoreMembers: (string | Buffer | number)[] + ]): Result<number, Context>; + zadd(...args: [ + key: RedisKey, + xx: "XX", + gt: "GT", + incr: "INCR", + ...scoreMembers: (string | Buffer | number)[], + callback: Callback<string | null> + ]): Result<string | null, Context>; + zaddBuffer(...args: [ + key: RedisKey, + xx: "XX", + gt: "GT", + incr: "INCR", + ...scoreMembers: (string | Buffer | number)[], + callback: Callback<Buffer | null> + ]): Result<Buffer | null, Context>; + zadd(...args: [ + key: RedisKey, + xx: "XX", + gt: "GT", + incr: "INCR", + ...scoreMembers: (string | Buffer | number)[] + ]): Result<string | null, Context>; + zaddBuffer(...args: [ + key: RedisKey, + xx: "XX", + gt: "GT", + incr: "INCR", + ...scoreMembers: (string | Buffer | number)[] + ]): Result<Buffer | null, Context>; + zadd(...args: [ + key: RedisKey, + xx: "XX", + gt: "GT", + ch: "CH", + ...scoreMembers: (string | Buffer | number)[], + callback: Callback<number> + ]): Result<number, Context>; + zadd(...args: [ + key: RedisKey, + xx: "XX", + gt: "GT", + ch: "CH", + ...scoreMembers: (string | Buffer | number)[] + ]): Result<number, Context>; + zadd(...args: [ + key: RedisKey, + xx: "XX", + gt: "GT", + ch: "CH", + incr: "INCR", + ...scoreMembers: (string | Buffer | number)[], + callback: Callback<string | null> + ]): Result<string | null, Context>; + zaddBuffer(...args: [ + key: RedisKey, + xx: "XX", + gt: "GT", + ch: "CH", + incr: "INCR", + ...scoreMembers: (string | Buffer | number)[], + callback: Callback<Buffer | null> + ]): Result<Buffer | null, Context>; + zadd(...args: [ + key: RedisKey, + xx: "XX", + gt: "GT", + ch: "CH", + incr: "INCR", + ...scoreMembers: (string | Buffer | number)[] + ]): Result<string | null, Context>; + zaddBuffer(...args: [ + key: RedisKey, + xx: "XX", + gt: "GT", + ch: "CH", + incr: "INCR", + ...scoreMembers: (string | Buffer | number)[] + ]): Result<Buffer | null, Context>; + zadd(...args: [ + key: RedisKey, + xx: "XX", + lt: "LT", + ...scoreMembers: (string | Buffer | number)[], + callback: Callback<number> + ]): Result<number, Context>; + zadd(...args: [ + key: RedisKey, + xx: "XX", + lt: "LT", + ...scoreMembers: (string | Buffer | number)[] + ]): Result<number, Context>; + zadd(...args: [ + key: RedisKey, + xx: "XX", + lt: "LT", + incr: "INCR", + ...scoreMembers: (string | Buffer | number)[], + callback: Callback<string | null> + ]): Result<string | null, Context>; + zaddBuffer(...args: [ + key: RedisKey, + xx: "XX", + lt: "LT", + incr: "INCR", + ...scoreMembers: (string | Buffer | number)[], + callback: Callback<Buffer | null> + ]): Result<Buffer | null, Context>; + zadd(...args: [ + key: RedisKey, + xx: "XX", + lt: "LT", + incr: "INCR", + ...scoreMembers: (string | Buffer | number)[] + ]): Result<string | null, Context>; + zaddBuffer(...args: [ + key: RedisKey, + xx: "XX", + lt: "LT", + incr: "INCR", + ...scoreMembers: (string | Buffer | number)[] + ]): Result<Buffer | null, Context>; + zadd(...args: [ + key: RedisKey, + xx: "XX", + lt: "LT", + ch: "CH", + ...scoreMembers: (string | Buffer | number)[], + callback: Callback<number> + ]): Result<number, Context>; + zadd(...args: [ + key: RedisKey, + xx: "XX", + lt: "LT", + ch: "CH", + ...scoreMembers: (string | Buffer | number)[] + ]): Result<number, Context>; + zadd(...args: [ + key: RedisKey, + xx: "XX", + lt: "LT", + ch: "CH", + incr: "INCR", + ...scoreMembers: (string | Buffer | number)[], + callback: Callback<string | null> + ]): Result<string | null, Context>; + zaddBuffer(...args: [ + key: RedisKey, + xx: "XX", + lt: "LT", + ch: "CH", + incr: "INCR", + ...scoreMembers: (string | Buffer | number)[], + callback: Callback<Buffer | null> + ]): Result<Buffer | null, Context>; + zadd(...args: [ + key: RedisKey, + xx: "XX", + lt: "LT", + ch: "CH", + incr: "INCR", + ...scoreMembers: (string | Buffer | number)[] + ]): Result<string | null, Context>; + zaddBuffer(...args: [ + key: RedisKey, + xx: "XX", + lt: "LT", + ch: "CH", + incr: "INCR", + ...scoreMembers: (string | Buffer | number)[] + ]): Result<Buffer | null, Context>; + zcard(key: RedisKey, callback?: Callback<number>): Result<number, Context>; + zcount(key: RedisKey, min: number | string, max: number | string, callback?: Callback<number>): Result<number, Context>; + zdiff(...args: [ + numkeys: number | string, + ...keys: RedisKey[], + callback: Callback<string[]> + ]): Result<string[], Context>; + zdiffBuffer(...args: [ + numkeys: number | string, + ...keys: RedisKey[], + callback: Callback<Buffer[]> + ]): Result<Buffer[], Context>; + zdiff(...args: [ + numkeys: number | string, + keys: RedisKey[], + callback: Callback<string[]> + ]): Result<string[], Context>; + zdiffBuffer(...args: [ + numkeys: number | string, + keys: RedisKey[], + callback: Callback<Buffer[]> + ]): Result<Buffer[], Context>; + zdiff(...args: [ + numkeys: number | string, + ...keys: RedisKey[] + ]): Result<string[], Context>; + zdiffBuffer(...args: [ + numkeys: number | string, + ...keys: RedisKey[] + ]): Result<Buffer[], Context>; + zdiff(...args: [ + numkeys: number | string, + keys: RedisKey[] + ]): Result<string[], Context>; + zdiffBuffer(...args: [ + numkeys: number | string, + keys: RedisKey[] + ]): Result<Buffer[], Context>; + zdiff(...args: [ + numkeys: number | string, + ...keys: RedisKey[], + withscores: "WITHSCORES", + callback: Callback<string[]> + ]): Result<string[], Context>; + zdiffBuffer(...args: [ + numkeys: number | string, + ...keys: RedisKey[], + withscores: "WITHSCORES", + callback: Callback<Buffer[]> + ]): Result<Buffer[], Context>; + zdiff(...args: [ + numkeys: number | string, + keys: RedisKey[], + withscores: "WITHSCORES", + callback: Callback<string[]> + ]): Result<string[], Context>; + zdiffBuffer(...args: [ + numkeys: number | string, + keys: RedisKey[], + withscores: "WITHSCORES", + callback: Callback<Buffer[]> + ]): Result<Buffer[], Context>; + zdiff(...args: [ + numkeys: number | string, + ...keys: RedisKey[], + withscores: "WITHSCORES" + ]): Result<string[], Context>; + zdiffBuffer(...args: [ + numkeys: number | string, + ...keys: RedisKey[], + withscores: "WITHSCORES" + ]): Result<Buffer[], Context>; + zdiff(...args: [ + numkeys: number | string, + keys: RedisKey[], + withscores: "WITHSCORES" + ]): Result<string[], Context>; + zdiffBuffer(...args: [ + numkeys: number | string, + keys: RedisKey[], + withscores: "WITHSCORES" + ]): Result<Buffer[], Context>; + zdiffstore(...args: [ + destination: RedisKey, + numkeys: number | string, + ...keys: RedisKey[], + callback: Callback<number> + ]): Result<number, Context>; + zdiffstore(...args: [ + destination: RedisKey, + numkeys: number | string, + keys: RedisKey[], + callback: Callback<number> + ]): Result<number, Context>; + zdiffstore(...args: [ + destination: RedisKey, + numkeys: number | string, + ...keys: RedisKey[] + ]): Result<number, Context>; + zdiffstore(...args: [ + destination: RedisKey, + numkeys: number | string, + keys: RedisKey[] + ]): Result<number, Context>; + zincrby(key: RedisKey, increment: number | string, member: string | Buffer | number, callback?: Callback<string>): Result<string, Context>; + zincrbyBuffer(key: RedisKey, increment: number | string, member: string | Buffer | number, callback?: Callback<Buffer>): Result<Buffer, Context>; + zinter(...args: [ + numkeys: number | string, + ...keys: RedisKey[], + callback: Callback<string[]> + ]): Result<string[], Context>; + zinterBuffer(...args: [ + numkeys: number | string, + ...keys: RedisKey[], + callback: Callback<Buffer[]> + ]): Result<Buffer[], Context>; + zinter(...args: [ + numkeys: number | string, + keys: RedisKey[], + callback: Callback<string[]> + ]): Result<string[], Context>; + zinterBuffer(...args: [ + numkeys: number | string, + keys: RedisKey[], + callback: Callback<Buffer[]> + ]): Result<Buffer[], Context>; + zinter(...args: [ + numkeys: number | string, + ...keys: RedisKey[] + ]): Result<string[], Context>; + zinterBuffer(...args: [ + numkeys: number | string, + ...keys: RedisKey[] + ]): Result<Buffer[], Context>; + zinter(...args: [ + numkeys: number | string, + keys: RedisKey[] + ]): Result<string[], Context>; + zinterBuffer(...args: [ + numkeys: number | string, + keys: RedisKey[] + ]): Result<Buffer[], Context>; + zinter(...args: [ + numkeys: number | string, + ...keys: RedisKey[], + withscores: "WITHSCORES", + callback: Callback<string[]> + ]): Result<string[], Context>; + zinterBuffer(...args: [ + numkeys: number | string, + ...keys: RedisKey[], + withscores: "WITHSCORES", + callback: Callback<Buffer[]> + ]): Result<Buffer[], Context>; + zinter(...args: [ + numkeys: number | string, + keys: RedisKey[], + withscores: "WITHSCORES", + callback: Callback<string[]> + ]): Result<string[], Context>; + zinterBuffer(...args: [ + numkeys: number | string, + keys: RedisKey[], + withscores: "WITHSCORES", + callback: Callback<Buffer[]> + ]): Result<Buffer[], Context>; + zinter(...args: [ + numkeys: number | string, + ...keys: RedisKey[], + withscores: "WITHSCORES" + ]): Result<string[], Context>; + zinterBuffer(...args: [ + numkeys: number | string, + ...keys: RedisKey[], + withscores: "WITHSCORES" + ]): Result<Buffer[], Context>; + zinter(...args: [ + numkeys: number | string, + keys: RedisKey[], + withscores: "WITHSCORES" + ]): Result<string[], Context>; + zinterBuffer(...args: [ + numkeys: number | string, + keys: RedisKey[], + withscores: "WITHSCORES" + ]): Result<Buffer[], Context>; + zinter(...args: [ + numkeys: number | string, + ...keys: RedisKey[], + aggregate: "AGGREGATE", + sum: "SUM", + callback: Callback<string[]> + ]): Result<string[], Context>; + zinterBuffer(...args: [ + numkeys: number | string, + ...keys: RedisKey[], + aggregate: "AGGREGATE", + sum: "SUM", + callback: Callback<Buffer[]> + ]): Result<Buffer[], Context>; + zinter(...args: [ + numkeys: number | string, + keys: RedisKey[], + aggregate: "AGGREGATE", + sum: "SUM", + callback: Callback<string[]> + ]): Result<string[], Context>; + zinterBuffer(...args: [ + numkeys: number | string, + keys: RedisKey[], + aggregate: "AGGREGATE", + sum: "SUM", + callback: Callback<Buffer[]> + ]): Result<Buffer[], Context>; + zinter(...args: [ + numkeys: number | string, + ...keys: RedisKey[], + aggregate: "AGGREGATE", + sum: "SUM" + ]): Result<string[], Context>; + zinterBuffer(...args: [ + numkeys: number | string, + ...keys: RedisKey[], + aggregate: "AGGREGATE", + sum: "SUM" + ]): Result<Buffer[], Context>; + zinter(...args: [ + numkeys: number | string, + keys: RedisKey[], + aggregate: "AGGREGATE", + sum: "SUM" + ]): Result<string[], Context>; + zinterBuffer(...args: [ + numkeys: number | string, + keys: RedisKey[], + aggregate: "AGGREGATE", + sum: "SUM" + ]): Result<Buffer[], Context>; + zinter(...args: [ + numkeys: number | string, + ...keys: RedisKey[], + aggregate: "AGGREGATE", + sum: "SUM", + withscores: "WITHSCORES", + callback: Callback<string[]> + ]): Result<string[], Context>; + zinterBuffer(...args: [ + numkeys: number | string, + ...keys: RedisKey[], + aggregate: "AGGREGATE", + sum: "SUM", + withscores: "WITHSCORES", + callback: Callback<Buffer[]> + ]): Result<Buffer[], Context>; + zinter(...args: [ + numkeys: number | string, + keys: RedisKey[], + aggregate: "AGGREGATE", + sum: "SUM", + withscores: "WITHSCORES", + callback: Callback<string[]> + ]): Result<string[], Context>; + zinterBuffer(...args: [ + numkeys: number | string, + keys: RedisKey[], + aggregate: "AGGREGATE", + sum: "SUM", + withscores: "WITHSCORES", + callback: Callback<Buffer[]> + ]): Result<Buffer[], Context>; + zinter(...args: [ + numkeys: number | string, + ...keys: RedisKey[], + aggregate: "AGGREGATE", + sum: "SUM", + withscores: "WITHSCORES" + ]): Result<string[], Context>; + zinterBuffer(...args: [ + numkeys: number | string, + ...keys: RedisKey[], + aggregate: "AGGREGATE", + sum: "SUM", + withscores: "WITHSCORES" + ]): Result<Buffer[], Context>; + zinter(...args: [ + numkeys: number | string, + keys: RedisKey[], + aggregate: "AGGREGATE", + sum: "SUM", + withscores: "WITHSCORES" + ]): Result<string[], Context>; + zinterBuffer(...args: [ + numkeys: number | string, + keys: RedisKey[], + aggregate: "AGGREGATE", + sum: "SUM", + withscores: "WITHSCORES" + ]): Result<Buffer[], Context>; + zinter(...args: [ + numkeys: number | string, + ...keys: RedisKey[], + aggregate: "AGGREGATE", + min: "MIN", + callback: Callback<string[]> + ]): Result<string[], Context>; + zinterBuffer(...args: [ + numkeys: number | string, + ...keys: RedisKey[], + aggregate: "AGGREGATE", + min: "MIN", + callback: Callback<Buffer[]> + ]): Result<Buffer[], Context>; + zinter(...args: [ + numkeys: number | string, + keys: RedisKey[], + aggregate: "AGGREGATE", + min: "MIN", + callback: Callback<string[]> + ]): Result<string[], Context>; + zinterBuffer(...args: [ + numkeys: number | string, + keys: RedisKey[], + aggregate: "AGGREGATE", + min: "MIN", + callback: Callback<Buffer[]> + ]): Result<Buffer[], Context>; + zinter(...args: [ + numkeys: number | string, + ...keys: RedisKey[], + aggregate: "AGGREGATE", + min: "MIN" + ]): Result<string[], Context>; + zinterBuffer(...args: [ + numkeys: number | string, + ...keys: RedisKey[], + aggregate: "AGGREGATE", + min: "MIN" + ]): Result<Buffer[], Context>; + zinter(...args: [ + numkeys: number | string, + keys: RedisKey[], + aggregate: "AGGREGATE", + min: "MIN" + ]): Result<string[], Context>; + zinterBuffer(...args: [ + numkeys: number | string, + keys: RedisKey[], + aggregate: "AGGREGATE", + min: "MIN" + ]): Result<Buffer[], Context>; + zinter(...args: [ + numkeys: number | string, + ...keys: RedisKey[], + aggregate: "AGGREGATE", + min: "MIN", + withscores: "WITHSCORES", + callback: Callback<string[]> + ]): Result<string[], Context>; + zinterBuffer(...args: [ + numkeys: number | string, + ...keys: RedisKey[], + aggregate: "AGGREGATE", + min: "MIN", + withscores: "WITHSCORES", + callback: Callback<Buffer[]> + ]): Result<Buffer[], Context>; + zinter(...args: [ + numkeys: number | string, + keys: RedisKey[], + aggregate: "AGGREGATE", + min: "MIN", + withscores: "WITHSCORES", + callback: Callback<string[]> + ]): Result<string[], Context>; + zinterBuffer(...args: [ + numkeys: number | string, + keys: RedisKey[], + aggregate: "AGGREGATE", + min: "MIN", + withscores: "WITHSCORES", + callback: Callback<Buffer[]> + ]): Result<Buffer[], Context>; + zinter(...args: [ + numkeys: number | string, + ...keys: RedisKey[], + aggregate: "AGGREGATE", + min: "MIN", + withscores: "WITHSCORES" + ]): Result<string[], Context>; + zinterBuffer(...args: [ + numkeys: number | string, + ...keys: RedisKey[], + aggregate: "AGGREGATE", + min: "MIN", + withscores: "WITHSCORES" + ]): Result<Buffer[], Context>; + zinter(...args: [ + numkeys: number | string, + keys: RedisKey[], + aggregate: "AGGREGATE", + min: "MIN", + withscores: "WITHSCORES" + ]): Result<string[], Context>; + zinterBuffer(...args: [ + numkeys: number | string, + keys: RedisKey[], + aggregate: "AGGREGATE", + min: "MIN", + withscores: "WITHSCORES" + ]): Result<Buffer[], Context>; + zinter(...args: [ + numkeys: number | string, + ...keys: RedisKey[], + aggregate: "AGGREGATE", + max: "MAX", + callback: Callback<string[]> + ]): Result<string[], Context>; + zinterBuffer(...args: [ + numkeys: number | string, + ...keys: RedisKey[], + aggregate: "AGGREGATE", + max: "MAX", + callback: Callback<Buffer[]> + ]): Result<Buffer[], Context>; + zinter(...args: [ + numkeys: number | string, + keys: RedisKey[], + aggregate: "AGGREGATE", + max: "MAX", + callback: Callback<string[]> + ]): Result<string[], Context>; + zinterBuffer(...args: [ + numkeys: number | string, + keys: RedisKey[], + aggregate: "AGGREGATE", + max: "MAX", + callback: Callback<Buffer[]> + ]): Result<Buffer[], Context>; + zinter(...args: [ + numkeys: number | string, + ...keys: RedisKey[], + aggregate: "AGGREGATE", + max: "MAX" + ]): Result<string[], Context>; + zinterBuffer(...args: [ + numkeys: number | string, + ...keys: RedisKey[], + aggregate: "AGGREGATE", + max: "MAX" + ]): Result<Buffer[], Context>; + zinter(...args: [ + numkeys: number | string, + keys: RedisKey[], + aggregate: "AGGREGATE", + max: "MAX" + ]): Result<string[], Context>; + zinterBuffer(...args: [ + numkeys: number | string, + keys: RedisKey[], + aggregate: "AGGREGATE", + max: "MAX" + ]): Result<Buffer[], Context>; + zinter(...args: [ + numkeys: number | string, + ...keys: RedisKey[], + aggregate: "AGGREGATE", + max: "MAX", + withscores: "WITHSCORES", + callback: Callback<string[]> + ]): Result<string[], Context>; + zinterBuffer(...args: [ + numkeys: number | string, + ...keys: RedisKey[], + aggregate: "AGGREGATE", + max: "MAX", + withscores: "WITHSCORES", + callback: Callback<Buffer[]> + ]): Result<Buffer[], Context>; + zinter(...args: [ + numkeys: number | string, + keys: RedisKey[], + aggregate: "AGGREGATE", + max: "MAX", + withscores: "WITHSCORES", + callback: Callback<string[]> + ]): Result<string[], Context>; + zinterBuffer(...args: [ + numkeys: number | string, + keys: RedisKey[], + aggregate: "AGGREGATE", + max: "MAX", + withscores: "WITHSCORES", + callback: Callback<Buffer[]> + ]): Result<Buffer[], Context>; + zinter(...args: [ + numkeys: number | string, + ...keys: RedisKey[], + aggregate: "AGGREGATE", + max: "MAX", + withscores: "WITHSCORES" + ]): Result<string[], Context>; + zinterBuffer(...args: [ + numkeys: number | string, + ...keys: RedisKey[], + aggregate: "AGGREGATE", + max: "MAX", + withscores: "WITHSCORES" + ]): Result<Buffer[], Context>; + zinter(...args: [ + numkeys: number | string, + keys: RedisKey[], + aggregate: "AGGREGATE", + max: "MAX", + withscores: "WITHSCORES" + ]): Result<string[], Context>; + zinterBuffer(...args: [ + numkeys: number | string, + keys: RedisKey[], + aggregate: "AGGREGATE", + max: "MAX", + withscores: "WITHSCORES" + ]): Result<Buffer[], Context>; + zinter(...args: [ + numkeys: number | string, + ...keys: RedisKey[], + aggregate: "AGGREGATE", + count: "COUNT", + callback: Callback<string[]> + ]): Result<string[], Context>; + zinterBuffer(...args: [ + numkeys: number | string, + ...keys: RedisKey[], + aggregate: "AGGREGATE", + count: "COUNT", + callback: Callback<Buffer[]> + ]): Result<Buffer[], Context>; + zinter(...args: [ + numkeys: number | string, + keys: RedisKey[], + aggregate: "AGGREGATE", + count: "COUNT", + callback: Callback<string[]> + ]): Result<string[], Context>; + zinterBuffer(...args: [ + numkeys: number | string, + keys: RedisKey[], + aggregate: "AGGREGATE", + count: "COUNT", + callback: Callback<Buffer[]> + ]): Result<Buffer[], Context>; + zinter(...args: [ + numkeys: number | string, + ...keys: RedisKey[], + aggregate: "AGGREGATE", + count: "COUNT" + ]): Result<string[], Context>; + zinterBuffer(...args: [ + numkeys: number | string, + ...keys: RedisKey[], + aggregate: "AGGREGATE", + count: "COUNT" + ]): Result<Buffer[], Context>; + zinter(...args: [ + numkeys: number | string, + keys: RedisKey[], + aggregate: "AGGREGATE", + count: "COUNT" + ]): Result<string[], Context>; + zinterBuffer(...args: [ + numkeys: number | string, + keys: RedisKey[], + aggregate: "AGGREGATE", + count: "COUNT" + ]): Result<Buffer[], Context>; + zinter(...args: [ + numkeys: number | string, + ...keys: RedisKey[], + aggregate: "AGGREGATE", + count: "COUNT", + withscores: "WITHSCORES", + callback: Callback<string[]> + ]): Result<string[], Context>; + zinterBuffer(...args: [ + numkeys: number | string, + ...keys: RedisKey[], + aggregate: "AGGREGATE", + count: "COUNT", + withscores: "WITHSCORES", + callback: Callback<Buffer[]> + ]): Result<Buffer[], Context>; + zinter(...args: [ + numkeys: number | string, + keys: RedisKey[], + aggregate: "AGGREGATE", + count: "COUNT", + withscores: "WITHSCORES", + callback: Callback<string[]> + ]): Result<string[], Context>; + zinterBuffer(...args: [ + numkeys: number | string, + keys: RedisKey[], + aggregate: "AGGREGATE", + count: "COUNT", + withscores: "WITHSCORES", + callback: Callback<Buffer[]> + ]): Result<Buffer[], Context>; + zinter(...args: [ + numkeys: number | string, + ...keys: RedisKey[], + aggregate: "AGGREGATE", + count: "COUNT", + withscores: "WITHSCORES" + ]): Result<string[], Context>; + zinterBuffer(...args: [ + numkeys: number | string, + ...keys: RedisKey[], + aggregate: "AGGREGATE", + count: "COUNT", + withscores: "WITHSCORES" + ]): Result<Buffer[], Context>; + zinter(...args: [ + numkeys: number | string, + keys: RedisKey[], + aggregate: "AGGREGATE", + count: "COUNT", + withscores: "WITHSCORES" + ]): Result<string[], Context>; + zinterBuffer(...args: [ + numkeys: number | string, + keys: RedisKey[], + aggregate: "AGGREGATE", + count: "COUNT", + withscores: "WITHSCORES" + ]): Result<Buffer[], Context>; + zinter(...args: [ + numkeys: number | string, + ...args: RedisValue[], + callback: Callback<string[]> + ]): Result<string[], Context>; + zinterBuffer(...args: [ + numkeys: number | string, + ...args: RedisValue[], + callback: Callback<Buffer[]> + ]): Result<Buffer[], Context>; + zinter(...args: [ + numkeys: number | string, + ...args: RedisValue[] + ]): Result<string[], Context>; + zinterBuffer(...args: [ + numkeys: number | string, + ...args: RedisValue[] + ]): Result<Buffer[], Context>; + zinter(...args: [ + numkeys: number | string, + ...args: RedisValue[], + withscores: "WITHSCORES", + callback: Callback<string[]> + ]): Result<string[], Context>; + zinterBuffer(...args: [ + numkeys: number | string, + ...args: RedisValue[], + withscores: "WITHSCORES", + callback: Callback<Buffer[]> + ]): Result<Buffer[], Context>; + zinter(...args: [ + numkeys: number | string, + ...args: RedisValue[], + withscores: "WITHSCORES" + ]): Result<string[], Context>; + zinterBuffer(...args: [ + numkeys: number | string, + ...args: RedisValue[], + withscores: "WITHSCORES" + ]): Result<Buffer[], Context>; + zinter(...args: [ + numkeys: number | string, + ...args: RedisValue[], + aggregate: "AGGREGATE", + sum: "SUM", + callback: Callback<string[]> + ]): Result<string[], Context>; + zinterBuffer(...args: [ + numkeys: number | string, + ...args: RedisValue[], + aggregate: "AGGREGATE", + sum: "SUM", + callback: Callback<Buffer[]> + ]): Result<Buffer[], Context>; + zinter(...args: [ + numkeys: number | string, + ...args: RedisValue[], + aggregate: "AGGREGATE", + sum: "SUM" + ]): Result<string[], Context>; + zinterBuffer(...args: [ + numkeys: number | string, + ...args: RedisValue[], + aggregate: "AGGREGATE", + sum: "SUM" + ]): Result<Buffer[], Context>; + zinter(...args: [ + numkeys: number | string, + ...args: RedisValue[], + aggregate: "AGGREGATE", + sum: "SUM", + withscores: "WITHSCORES", + callback: Callback<string[]> + ]): Result<string[], Context>; + zinterBuffer(...args: [ + numkeys: number | string, + ...args: RedisValue[], + aggregate: "AGGREGATE", + sum: "SUM", + withscores: "WITHSCORES", + callback: Callback<Buffer[]> + ]): Result<Buffer[], Context>; + zinter(...args: [ + numkeys: number | string, + ...args: RedisValue[], + aggregate: "AGGREGATE", + sum: "SUM", + withscores: "WITHSCORES" + ]): Result<string[], Context>; + zinterBuffer(...args: [ + numkeys: number | string, + ...args: RedisValue[], + aggregate: "AGGREGATE", + sum: "SUM", + withscores: "WITHSCORES" + ]): Result<Buffer[], Context>; + zinter(...args: [ + numkeys: number | string, + ...args: RedisValue[], + aggregate: "AGGREGATE", + min: "MIN", + callback: Callback<string[]> + ]): Result<string[], Context>; + zinterBuffer(...args: [ + numkeys: number | string, + ...args: RedisValue[], + aggregate: "AGGREGATE", + min: "MIN", + callback: Callback<Buffer[]> + ]): Result<Buffer[], Context>; + zinter(...args: [ + numkeys: number | string, + ...args: RedisValue[], + aggregate: "AGGREGATE", + min: "MIN" + ]): Result<string[], Context>; + zinterBuffer(...args: [ + numkeys: number | string, + ...args: RedisValue[], + aggregate: "AGGREGATE", + min: "MIN" + ]): Result<Buffer[], Context>; + zinter(...args: [ + numkeys: number | string, + ...args: RedisValue[], + aggregate: "AGGREGATE", + min: "MIN", + withscores: "WITHSCORES", + callback: Callback<string[]> + ]): Result<string[], Context>; + zinterBuffer(...args: [ + numkeys: number | string, + ...args: RedisValue[], + aggregate: "AGGREGATE", + min: "MIN", + withscores: "WITHSCORES", + callback: Callback<Buffer[]> + ]): Result<Buffer[], Context>; + zinter(...args: [ + numkeys: number | string, + ...args: RedisValue[], + aggregate: "AGGREGATE", + min: "MIN", + withscores: "WITHSCORES" + ]): Result<string[], Context>; + zinterBuffer(...args: [ + numkeys: number | string, + ...args: RedisValue[], + aggregate: "AGGREGATE", + min: "MIN", + withscores: "WITHSCORES" + ]): Result<Buffer[], Context>; + zinter(...args: [ + numkeys: number | string, + ...args: RedisValue[], + aggregate: "AGGREGATE", + max: "MAX", + callback: Callback<string[]> + ]): Result<string[], Context>; + zinterBuffer(...args: [ + numkeys: number | string, + ...args: RedisValue[], + aggregate: "AGGREGATE", + max: "MAX", + callback: Callback<Buffer[]> + ]): Result<Buffer[], Context>; + zinter(...args: [ + numkeys: number | string, + ...args: RedisValue[], + aggregate: "AGGREGATE", + max: "MAX" + ]): Result<string[], Context>; + zinterBuffer(...args: [ + numkeys: number | string, + ...args: RedisValue[], + aggregate: "AGGREGATE", + max: "MAX" + ]): Result<Buffer[], Context>; + zinter(...args: [ + numkeys: number | string, + ...args: RedisValue[], + aggregate: "AGGREGATE", + max: "MAX", + withscores: "WITHSCORES", + callback: Callback<string[]> + ]): Result<string[], Context>; + zinterBuffer(...args: [ + numkeys: number | string, + ...args: RedisValue[], + aggregate: "AGGREGATE", + max: "MAX", + withscores: "WITHSCORES", + callback: Callback<Buffer[]> + ]): Result<Buffer[], Context>; + zinter(...args: [ + numkeys: number | string, + ...args: RedisValue[], + aggregate: "AGGREGATE", + max: "MAX", + withscores: "WITHSCORES" + ]): Result<string[], Context>; + zinterBuffer(...args: [ + numkeys: number | string, + ...args: RedisValue[], + aggregate: "AGGREGATE", + max: "MAX", + withscores: "WITHSCORES" + ]): Result<Buffer[], Context>; + zinter(...args: [ + numkeys: number | string, + ...args: RedisValue[], + aggregate: "AGGREGATE", + count: "COUNT", + callback: Callback<string[]> + ]): Result<string[], Context>; + zinterBuffer(...args: [ + numkeys: number | string, + ...args: RedisValue[], + aggregate: "AGGREGATE", + count: "COUNT", + callback: Callback<Buffer[]> + ]): Result<Buffer[], Context>; + zinter(...args: [ + numkeys: number | string, + ...args: RedisValue[], + aggregate: "AGGREGATE", + count: "COUNT" + ]): Result<string[], Context>; + zinterBuffer(...args: [ + numkeys: number | string, + ...args: RedisValue[], + aggregate: "AGGREGATE", + count: "COUNT" + ]): Result<Buffer[], Context>; + zinter(...args: [ + numkeys: number | string, + ...args: RedisValue[], + aggregate: "AGGREGATE", + count: "COUNT", + withscores: "WITHSCORES", + callback: Callback<string[]> + ]): Result<string[], Context>; + zinterBuffer(...args: [ + numkeys: number | string, + ...args: RedisValue[], + aggregate: "AGGREGATE", + count: "COUNT", + withscores: "WITHSCORES", + callback: Callback<Buffer[]> + ]): Result<Buffer[], Context>; + zinter(...args: [ + numkeys: number | string, + ...args: RedisValue[], + aggregate: "AGGREGATE", + count: "COUNT", + withscores: "WITHSCORES" + ]): Result<string[], Context>; + zinterBuffer(...args: [ + numkeys: number | string, + ...args: RedisValue[], + aggregate: "AGGREGATE", + count: "COUNT", + withscores: "WITHSCORES" + ]): Result<Buffer[], Context>; + zintercard(...args: [ + numkeys: number | string, + ...keys: RedisKey[], + callback: Callback<number> + ]): Result<number, Context>; + zintercard(...args: [ + numkeys: number | string, + keys: RedisKey[], + callback: Callback<number> + ]): Result<number, Context>; + zintercard(...args: [ + numkeys: number | string, + ...keys: RedisKey[] + ]): Result<number, Context>; + zintercard(...args: [ + numkeys: number | string, + keys: RedisKey[] + ]): Result<number, Context>; + zintercard(...args: [ + numkeys: number | string, + ...keys: RedisKey[], + limitToken: "LIMIT", + limit: number | string, + callback: Callback<number> + ]): Result<number, Context>; + zintercard(...args: [ + numkeys: number | string, + keys: RedisKey[], + limitToken: "LIMIT", + limit: number | string, + callback: Callback<number> + ]): Result<number, Context>; + zintercard(...args: [ + numkeys: number | string, + ...keys: RedisKey[], + limitToken: "LIMIT", + limit: number | string + ]): Result<number, Context>; + zintercard(...args: [ + numkeys: number | string, + keys: RedisKey[], + limitToken: "LIMIT", + limit: number | string + ]): Result<number, Context>; + zinterstore(...args: [ + destination: RedisKey, + numkeys: number | string, + ...keys: RedisKey[], + callback: Callback<number> + ]): Result<number, Context>; + zinterstore(...args: [ + destination: RedisKey, + numkeys: number | string, + keys: RedisKey[], + callback: Callback<number> + ]): Result<number, Context>; + zinterstore(...args: [ + destination: RedisKey, + numkeys: number | string, + ...keys: RedisKey[] + ]): Result<number, Context>; + zinterstore(...args: [ + destination: RedisKey, + numkeys: number | string, + keys: RedisKey[] + ]): Result<number, Context>; + zinterstore(...args: [ + destination: RedisKey, + numkeys: number | string, + ...keys: RedisKey[], + aggregate: "AGGREGATE", + sum: "SUM", + callback: Callback<number> + ]): Result<number, Context>; + zinterstore(...args: [ + destination: RedisKey, + numkeys: number | string, + keys: RedisKey[], + aggregate: "AGGREGATE", + sum: "SUM", + callback: Callback<number> + ]): Result<number, Context>; + zinterstore(...args: [ + destination: RedisKey, + numkeys: number | string, + ...keys: RedisKey[], + aggregate: "AGGREGATE", + sum: "SUM" + ]): Result<number, Context>; + zinterstore(...args: [ + destination: RedisKey, + numkeys: number | string, + keys: RedisKey[], + aggregate: "AGGREGATE", + sum: "SUM" + ]): Result<number, Context>; + zinterstore(...args: [ + destination: RedisKey, + numkeys: number | string, + ...keys: RedisKey[], + aggregate: "AGGREGATE", + min: "MIN", + callback: Callback<number> + ]): Result<number, Context>; + zinterstore(...args: [ + destination: RedisKey, + numkeys: number | string, + keys: RedisKey[], + aggregate: "AGGREGATE", + min: "MIN", + callback: Callback<number> + ]): Result<number, Context>; + zinterstore(...args: [ + destination: RedisKey, + numkeys: number | string, + ...keys: RedisKey[], + aggregate: "AGGREGATE", + min: "MIN" + ]): Result<number, Context>; + zinterstore(...args: [ + destination: RedisKey, + numkeys: number | string, + keys: RedisKey[], + aggregate: "AGGREGATE", + min: "MIN" + ]): Result<number, Context>; + zinterstore(...args: [ + destination: RedisKey, + numkeys: number | string, + ...keys: RedisKey[], + aggregate: "AGGREGATE", + max: "MAX", + callback: Callback<number> + ]): Result<number, Context>; + zinterstore(...args: [ + destination: RedisKey, + numkeys: number | string, + keys: RedisKey[], + aggregate: "AGGREGATE", + max: "MAX", + callback: Callback<number> + ]): Result<number, Context>; + zinterstore(...args: [ + destination: RedisKey, + numkeys: number | string, + ...keys: RedisKey[], + aggregate: "AGGREGATE", + max: "MAX" + ]): Result<number, Context>; + zinterstore(...args: [ + destination: RedisKey, + numkeys: number | string, + keys: RedisKey[], + aggregate: "AGGREGATE", + max: "MAX" + ]): Result<number, Context>; + zinterstore(...args: [ + destination: RedisKey, + numkeys: number | string, + ...keys: RedisKey[], + aggregate: "AGGREGATE", + count: "COUNT", + callback: Callback<number> + ]): Result<number, Context>; + zinterstore(...args: [ + destination: RedisKey, + numkeys: number | string, + keys: RedisKey[], + aggregate: "AGGREGATE", + count: "COUNT", + callback: Callback<number> + ]): Result<number, Context>; + zinterstore(...args: [ + destination: RedisKey, + numkeys: number | string, + ...keys: RedisKey[], + aggregate: "AGGREGATE", + count: "COUNT" + ]): Result<number, Context>; + zinterstore(...args: [ + destination: RedisKey, + numkeys: number | string, + keys: RedisKey[], + aggregate: "AGGREGATE", + count: "COUNT" + ]): Result<number, Context>; + zinterstore(...args: [ + destination: RedisKey, + numkeys: number | string, + ...args: RedisValue[], + callback: Callback<number> + ]): Result<number, Context>; + zinterstore(...args: [ + destination: RedisKey, + numkeys: number | string, + ...args: RedisValue[] + ]): Result<number, Context>; + zinterstore(...args: [ + destination: RedisKey, + numkeys: number | string, + ...args: RedisValue[], + aggregate: "AGGREGATE", + sum: "SUM", + callback: Callback<number> + ]): Result<number, Context>; + zinterstore(...args: [ + destination: RedisKey, + numkeys: number | string, + ...args: RedisValue[], + aggregate: "AGGREGATE", + sum: "SUM" + ]): Result<number, Context>; + zinterstore(...args: [ + destination: RedisKey, + numkeys: number | string, + ...args: RedisValue[], + aggregate: "AGGREGATE", + min: "MIN", + callback: Callback<number> + ]): Result<number, Context>; + zinterstore(...args: [ + destination: RedisKey, + numkeys: number | string, + ...args: RedisValue[], + aggregate: "AGGREGATE", + min: "MIN" + ]): Result<number, Context>; + zinterstore(...args: [ + destination: RedisKey, + numkeys: number | string, + ...args: RedisValue[], + aggregate: "AGGREGATE", + max: "MAX", + callback: Callback<number> + ]): Result<number, Context>; + zinterstore(...args: [ + destination: RedisKey, + numkeys: number | string, + ...args: RedisValue[], + aggregate: "AGGREGATE", + max: "MAX" + ]): Result<number, Context>; + zinterstore(...args: [ + destination: RedisKey, + numkeys: number | string, + ...args: RedisValue[], + aggregate: "AGGREGATE", + count: "COUNT", + callback: Callback<number> + ]): Result<number, Context>; + zinterstore(...args: [ + destination: RedisKey, + numkeys: number | string, + ...args: RedisValue[], + aggregate: "AGGREGATE", + count: "COUNT" + ]): Result<number, Context>; + zlexcount(key: RedisKey, min: string | Buffer | number, max: string | Buffer | number, callback?: Callback<number>): Result<number, Context>; + zmpop(...args: [ + numkeys: number | string, + ...keys: RedisKey[], + min: "MIN", + callback: Callback<unknown> + ]): Result<unknown, Context>; + zmpop(...args: [ + numkeys: number | string, + keys: RedisKey[], + min: "MIN", + callback: Callback<unknown> + ]): Result<unknown, Context>; + zmpop(...args: [ + numkeys: number | string, + ...keys: RedisKey[], + min: "MIN" + ]): Result<unknown, Context>; + zmpop(...args: [ + numkeys: number | string, + keys: RedisKey[], + min: "MIN" + ]): Result<unknown, Context>; + zmpop(...args: [ + numkeys: number | string, + ...keys: RedisKey[], + min: "MIN", + countToken: "COUNT", + count: number | string, + callback: Callback<unknown> + ]): Result<unknown, Context>; + zmpop(...args: [ + numkeys: number | string, + keys: RedisKey[], + min: "MIN", + countToken: "COUNT", + count: number | string, + callback: Callback<unknown> + ]): Result<unknown, Context>; + zmpop(...args: [ + numkeys: number | string, + ...keys: RedisKey[], + min: "MIN", + countToken: "COUNT", + count: number | string + ]): Result<unknown, Context>; + zmpop(...args: [ + numkeys: number | string, + keys: RedisKey[], + min: "MIN", + countToken: "COUNT", + count: number | string + ]): Result<unknown, Context>; + zmpop(...args: [ + numkeys: number | string, + ...keys: RedisKey[], + max: "MAX", + callback: Callback<unknown> + ]): Result<unknown, Context>; + zmpop(...args: [ + numkeys: number | string, + keys: RedisKey[], + max: "MAX", + callback: Callback<unknown> + ]): Result<unknown, Context>; + zmpop(...args: [ + numkeys: number | string, + ...keys: RedisKey[], + max: "MAX" + ]): Result<unknown, Context>; + zmpop(...args: [ + numkeys: number | string, + keys: RedisKey[], + max: "MAX" + ]): Result<unknown, Context>; + zmpop(...args: [ + numkeys: number | string, + ...keys: RedisKey[], + max: "MAX", + countToken: "COUNT", + count: number | string, + callback: Callback<unknown> + ]): Result<unknown, Context>; + zmpop(...args: [ + numkeys: number | string, + keys: RedisKey[], + max: "MAX", + countToken: "COUNT", + count: number | string, + callback: Callback<unknown> + ]): Result<unknown, Context>; + zmpop(...args: [ + numkeys: number | string, + ...keys: RedisKey[], + max: "MAX", + countToken: "COUNT", + count: number | string + ]): Result<unknown, Context>; + zmpop(...args: [ + numkeys: number | string, + keys: RedisKey[], + max: "MAX", + countToken: "COUNT", + count: number | string + ]): Result<unknown, Context>; + zmscore(...args: [ + key: RedisKey, + ...members: (string | Buffer | number)[], + callback: Callback<(string | null)[]> + ]): Result<(string | null)[], Context>; + zmscoreBuffer(...args: [ + key: RedisKey, + ...members: (string | Buffer | number)[], + callback: Callback<(Buffer | null)[]> + ]): Result<(Buffer | null)[], Context>; + zmscore(...args: [ + key: RedisKey, + members: (string | Buffer | number)[], + callback: Callback<(string | null)[]> + ]): Result<(string | null)[], Context>; + zmscoreBuffer(...args: [ + key: RedisKey, + members: (string | Buffer | number)[], + callback: Callback<(Buffer | null)[]> + ]): Result<(Buffer | null)[], Context>; + zmscore(...args: [ + key: RedisKey, + ...members: (string | Buffer | number)[] + ]): Result<(string | null)[], Context>; + zmscoreBuffer(...args: [ + key: RedisKey, + ...members: (string | Buffer | number)[] + ]): Result<(Buffer | null)[], Context>; + zmscore(...args: [ + key: RedisKey, + members: (string | Buffer | number)[] + ]): Result<(string | null)[], Context>; + zmscoreBuffer(...args: [ + key: RedisKey, + members: (string | Buffer | number)[] + ]): Result<(Buffer | null)[], Context>; + zpopmax(key: RedisKey, callback?: Callback<string[]>): Result<string[], Context>; + zpopmaxBuffer(key: RedisKey, callback?: Callback<Buffer[]>): Result<Buffer[], Context>; + zpopmax(key: RedisKey, count: number | string, callback?: Callback<string[]>): Result<string[], Context>; + zpopmaxBuffer(key: RedisKey, count: number | string, callback?: Callback<Buffer[]>): Result<Buffer[], Context>; + zpopmin(key: RedisKey, callback?: Callback<string[]>): Result<string[], Context>; + zpopminBuffer(key: RedisKey, callback?: Callback<Buffer[]>): Result<Buffer[], Context>; + zpopmin(key: RedisKey, count: number | string, callback?: Callback<string[]>): Result<string[], Context>; + zpopminBuffer(key: RedisKey, count: number | string, callback?: Callback<Buffer[]>): Result<Buffer[], Context>; + zrandmember(key: RedisKey, callback?: Callback<string | null>): Result<string | null, Context>; + zrandmemberBuffer(key: RedisKey, callback?: Callback<Buffer | null>): Result<Buffer | null, Context>; + zrandmember(key: RedisKey, count: number | string, callback?: Callback<string[]>): Result<string[], Context>; + zrandmemberBuffer(key: RedisKey, count: number | string, callback?: Callback<Buffer[]>): Result<Buffer[], Context>; + zrandmember(key: RedisKey, count: number | string, withscores: "WITHSCORES", callback?: Callback<string[]>): Result<string[], Context>; + zrandmemberBuffer(key: RedisKey, count: number | string, withscores: "WITHSCORES", callback?: Callback<Buffer[]>): Result<Buffer[], Context>; + zrange(key: RedisKey, min: string | Buffer | number, max: string | Buffer | number, callback?: Callback<string[]>): Result<string[], Context>; + zrangeBuffer(key: RedisKey, min: string | Buffer | number, max: string | Buffer | number, callback?: Callback<Buffer[]>): Result<Buffer[], Context>; + zrange(key: RedisKey, min: string | Buffer | number, max: string | Buffer | number, withscores: "WITHSCORES", callback?: Callback<string[]>): Result<string[], Context>; + zrangeBuffer(key: RedisKey, min: string | Buffer | number, max: string | Buffer | number, withscores: "WITHSCORES", callback?: Callback<Buffer[]>): Result<Buffer[], Context>; + zrange(key: RedisKey, min: string | Buffer | number, max: string | Buffer | number, offsetCountToken: "LIMIT", offset: number | string, count: number | string, callback?: Callback<string[]>): Result<string[], Context>; + zrangeBuffer(key: RedisKey, min: string | Buffer | number, max: string | Buffer | number, offsetCountToken: "LIMIT", offset: number | string, count: number | string, callback?: Callback<Buffer[]>): Result<Buffer[], Context>; + zrange(key: RedisKey, min: string | Buffer | number, max: string | Buffer | number, offsetCountToken: "LIMIT", offset: number | string, count: number | string, withscores: "WITHSCORES", callback?: Callback<string[]>): Result<string[], Context>; + zrangeBuffer(key: RedisKey, min: string | Buffer | number, max: string | Buffer | number, offsetCountToken: "LIMIT", offset: number | string, count: number | string, withscores: "WITHSCORES", callback?: Callback<Buffer[]>): Result<Buffer[], Context>; + zrange(key: RedisKey, min: string | Buffer | number, max: string | Buffer | number, rev: "REV", callback?: Callback<string[]>): Result<string[], Context>; + zrangeBuffer(key: RedisKey, min: string | Buffer | number, max: string | Buffer | number, rev: "REV", callback?: Callback<Buffer[]>): Result<Buffer[], Context>; + zrange(key: RedisKey, min: string | Buffer | number, max: string | Buffer | number, rev: "REV", withscores: "WITHSCORES", callback?: Callback<string[]>): Result<string[], Context>; + zrangeBuffer(key: RedisKey, min: string | Buffer | number, max: string | Buffer | number, rev: "REV", withscores: "WITHSCORES", callback?: Callback<Buffer[]>): Result<Buffer[], Context>; + zrange(key: RedisKey, min: string | Buffer | number, max: string | Buffer | number, rev: "REV", offsetCountToken: "LIMIT", offset: number | string, count: number | string, callback?: Callback<string[]>): Result<string[], Context>; + zrangeBuffer(key: RedisKey, min: string | Buffer | number, max: string | Buffer | number, rev: "REV", offsetCountToken: "LIMIT", offset: number | string, count: number | string, callback?: Callback<Buffer[]>): Result<Buffer[], Context>; + zrange(key: RedisKey, min: string | Buffer | number, max: string | Buffer | number, rev: "REV", offsetCountToken: "LIMIT", offset: number | string, count: number | string, withscores: "WITHSCORES", callback?: Callback<string[]>): Result<string[], Context>; + zrangeBuffer(key: RedisKey, min: string | Buffer | number, max: string | Buffer | number, rev: "REV", offsetCountToken: "LIMIT", offset: number | string, count: number | string, withscores: "WITHSCORES", callback?: Callback<Buffer[]>): Result<Buffer[], Context>; + zrange(key: RedisKey, min: string | Buffer | number, max: string | Buffer | number, byscore: "BYSCORE", callback?: Callback<string[]>): Result<string[], Context>; + zrangeBuffer(key: RedisKey, min: string | Buffer | number, max: string | Buffer | number, byscore: "BYSCORE", callback?: Callback<Buffer[]>): Result<Buffer[], Context>; + zrange(key: RedisKey, min: string | Buffer | number, max: string | Buffer | number, byscore: "BYSCORE", withscores: "WITHSCORES", callback?: Callback<string[]>): Result<string[], Context>; + zrangeBuffer(key: RedisKey, min: string | Buffer | number, max: string | Buffer | number, byscore: "BYSCORE", withscores: "WITHSCORES", callback?: Callback<Buffer[]>): Result<Buffer[], Context>; + zrange(key: RedisKey, min: string | Buffer | number, max: string | Buffer | number, byscore: "BYSCORE", offsetCountToken: "LIMIT", offset: number | string, count: number | string, callback?: Callback<string[]>): Result<string[], Context>; + zrangeBuffer(key: RedisKey, min: string | Buffer | number, max: string | Buffer | number, byscore: "BYSCORE", offsetCountToken: "LIMIT", offset: number | string, count: number | string, callback?: Callback<Buffer[]>): Result<Buffer[], Context>; + zrange(key: RedisKey, min: string | Buffer | number, max: string | Buffer | number, byscore: "BYSCORE", offsetCountToken: "LIMIT", offset: number | string, count: number | string, withscores: "WITHSCORES", callback?: Callback<string[]>): Result<string[], Context>; + zrangeBuffer(key: RedisKey, min: string | Buffer | number, max: string | Buffer | number, byscore: "BYSCORE", offsetCountToken: "LIMIT", offset: number | string, count: number | string, withscores: "WITHSCORES", callback?: Callback<Buffer[]>): Result<Buffer[], Context>; + zrange(key: RedisKey, min: string | Buffer | number, max: string | Buffer | number, byscore: "BYSCORE", rev: "REV", callback?: Callback<string[]>): Result<string[], Context>; + zrangeBuffer(key: RedisKey, min: string | Buffer | number, max: string | Buffer | number, byscore: "BYSCORE", rev: "REV", callback?: Callback<Buffer[]>): Result<Buffer[], Context>; + zrange(key: RedisKey, min: string | Buffer | number, max: string | Buffer | number, byscore: "BYSCORE", rev: "REV", withscores: "WITHSCORES", callback?: Callback<string[]>): Result<string[], Context>; + zrangeBuffer(key: RedisKey, min: string | Buffer | number, max: string | Buffer | number, byscore: "BYSCORE", rev: "REV", withscores: "WITHSCORES", callback?: Callback<Buffer[]>): Result<Buffer[], Context>; + zrange(key: RedisKey, min: string | Buffer | number, max: string | Buffer | number, byscore: "BYSCORE", rev: "REV", offsetCountToken: "LIMIT", offset: number | string, count: number | string, callback?: Callback<string[]>): Result<string[], Context>; + zrangeBuffer(key: RedisKey, min: string | Buffer | number, max: string | Buffer | number, byscore: "BYSCORE", rev: "REV", offsetCountToken: "LIMIT", offset: number | string, count: number | string, callback?: Callback<Buffer[]>): Result<Buffer[], Context>; + zrange(key: RedisKey, min: string | Buffer | number, max: string | Buffer | number, byscore: "BYSCORE", rev: "REV", offsetCountToken: "LIMIT", offset: number | string, count: number | string, withscores: "WITHSCORES", callback?: Callback<string[]>): Result<string[], Context>; + zrangeBuffer(key: RedisKey, min: string | Buffer | number, max: string | Buffer | number, byscore: "BYSCORE", rev: "REV", offsetCountToken: "LIMIT", offset: number | string, count: number | string, withscores: "WITHSCORES", callback?: Callback<Buffer[]>): Result<Buffer[], Context>; + zrange(key: RedisKey, min: string | Buffer | number, max: string | Buffer | number, bylex: "BYLEX", callback?: Callback<string[]>): Result<string[], Context>; + zrangeBuffer(key: RedisKey, min: string | Buffer | number, max: string | Buffer | number, bylex: "BYLEX", callback?: Callback<Buffer[]>): Result<Buffer[], Context>; + zrange(key: RedisKey, min: string | Buffer | number, max: string | Buffer | number, bylex: "BYLEX", withscores: "WITHSCORES", callback?: Callback<string[]>): Result<string[], Context>; + zrangeBuffer(key: RedisKey, min: string | Buffer | number, max: string | Buffer | number, bylex: "BYLEX", withscores: "WITHSCORES", callback?: Callback<Buffer[]>): Result<Buffer[], Context>; + zrange(key: RedisKey, min: string | Buffer | number, max: string | Buffer | number, bylex: "BYLEX", offsetCountToken: "LIMIT", offset: number | string, count: number | string, callback?: Callback<string[]>): Result<string[], Context>; + zrangeBuffer(key: RedisKey, min: string | Buffer | number, max: string | Buffer | number, bylex: "BYLEX", offsetCountToken: "LIMIT", offset: number | string, count: number | string, callback?: Callback<Buffer[]>): Result<Buffer[], Context>; + zrange(key: RedisKey, min: string | Buffer | number, max: string | Buffer | number, bylex: "BYLEX", offsetCountToken: "LIMIT", offset: number | string, count: number | string, withscores: "WITHSCORES", callback?: Callback<string[]>): Result<string[], Context>; + zrangeBuffer(key: RedisKey, min: string | Buffer | number, max: string | Buffer | number, bylex: "BYLEX", offsetCountToken: "LIMIT", offset: number | string, count: number | string, withscores: "WITHSCORES", callback?: Callback<Buffer[]>): Result<Buffer[], Context>; + zrange(key: RedisKey, min: string | Buffer | number, max: string | Buffer | number, bylex: "BYLEX", rev: "REV", callback?: Callback<string[]>): Result<string[], Context>; + zrangeBuffer(key: RedisKey, min: string | Buffer | number, max: string | Buffer | number, bylex: "BYLEX", rev: "REV", callback?: Callback<Buffer[]>): Result<Buffer[], Context>; + zrange(key: RedisKey, min: string | Buffer | number, max: string | Buffer | number, bylex: "BYLEX", rev: "REV", withscores: "WITHSCORES", callback?: Callback<string[]>): Result<string[], Context>; + zrangeBuffer(key: RedisKey, min: string | Buffer | number, max: string | Buffer | number, bylex: "BYLEX", rev: "REV", withscores: "WITHSCORES", callback?: Callback<Buffer[]>): Result<Buffer[], Context>; + zrange(key: RedisKey, min: string | Buffer | number, max: string | Buffer | number, bylex: "BYLEX", rev: "REV", offsetCountToken: "LIMIT", offset: number | string, count: number | string, callback?: Callback<string[]>): Result<string[], Context>; + zrangeBuffer(key: RedisKey, min: string | Buffer | number, max: string | Buffer | number, bylex: "BYLEX", rev: "REV", offsetCountToken: "LIMIT", offset: number | string, count: number | string, callback?: Callback<Buffer[]>): Result<Buffer[], Context>; + zrange(key: RedisKey, min: string | Buffer | number, max: string | Buffer | number, bylex: "BYLEX", rev: "REV", offsetCountToken: "LIMIT", offset: number | string, count: number | string, withscores: "WITHSCORES", callback?: Callback<string[]>): Result<string[], Context>; + zrangeBuffer(key: RedisKey, min: string | Buffer | number, max: string | Buffer | number, bylex: "BYLEX", rev: "REV", offsetCountToken: "LIMIT", offset: number | string, count: number | string, withscores: "WITHSCORES", callback?: Callback<Buffer[]>): Result<Buffer[], Context>; + zrangebylex(key: RedisKey, min: string | Buffer | number, max: string | Buffer | number, callback?: Callback<string[]>): Result<string[], Context>; + zrangebylexBuffer(key: RedisKey, min: string | Buffer | number, max: string | Buffer | number, callback?: Callback<Buffer[]>): Result<Buffer[], Context>; + zrangebylex(key: RedisKey, min: string | Buffer | number, max: string | Buffer | number, offsetCountToken: "LIMIT", offset: number | string, count: number | string, callback?: Callback<string[]>): Result<string[], Context>; + zrangebylexBuffer(key: RedisKey, min: string | Buffer | number, max: string | Buffer | number, offsetCountToken: "LIMIT", offset: number | string, count: number | string, callback?: Callback<Buffer[]>): Result<Buffer[], Context>; + zrangebyscore(key: RedisKey, min: number | string, max: number | string, callback?: Callback<string[]>): Result<string[], Context>; + zrangebyscoreBuffer(key: RedisKey, min: number | string, max: number | string, callback?: Callback<Buffer[]>): Result<Buffer[], Context>; + zrangebyscore(key: RedisKey, min: number | string, max: number | string, offsetCountToken: "LIMIT", offset: number | string, count: number | string, callback?: Callback<string[]>): Result<string[], Context>; + zrangebyscoreBuffer(key: RedisKey, min: number | string, max: number | string, offsetCountToken: "LIMIT", offset: number | string, count: number | string, callback?: Callback<Buffer[]>): Result<Buffer[], Context>; + zrangebyscore(key: RedisKey, min: number | string, max: number | string, withscores: "WITHSCORES", callback?: Callback<string[]>): Result<string[], Context>; + zrangebyscoreBuffer(key: RedisKey, min: number | string, max: number | string, withscores: "WITHSCORES", callback?: Callback<Buffer[]>): Result<Buffer[], Context>; + zrangebyscore(key: RedisKey, min: number | string, max: number | string, withscores: "WITHSCORES", offsetCountToken: "LIMIT", offset: number | string, count: number | string, callback?: Callback<string[]>): Result<string[], Context>; + zrangebyscoreBuffer(key: RedisKey, min: number | string, max: number | string, withscores: "WITHSCORES", offsetCountToken: "LIMIT", offset: number | string, count: number | string, callback?: Callback<Buffer[]>): Result<Buffer[], Context>; + zrangestore(dst: RedisKey, src: RedisKey, min: string | Buffer | number, max: string | Buffer | number, callback?: Callback<number>): Result<number, Context>; + zrangestore(dst: RedisKey, src: RedisKey, min: string | Buffer | number, max: string | Buffer | number, offsetCountToken: "LIMIT", offset: number | string, count: number | string, callback?: Callback<number>): Result<number, Context>; + zrangestore(dst: RedisKey, src: RedisKey, min: string | Buffer | number, max: string | Buffer | number, rev: "REV", callback?: Callback<number>): Result<number, Context>; + zrangestore(dst: RedisKey, src: RedisKey, min: string | Buffer | number, max: string | Buffer | number, rev: "REV", offsetCountToken: "LIMIT", offset: number | string, count: number | string, callback?: Callback<number>): Result<number, Context>; + zrangestore(dst: RedisKey, src: RedisKey, min: string | Buffer | number, max: string | Buffer | number, byscore: "BYSCORE", callback?: Callback<number>): Result<number, Context>; + zrangestore(dst: RedisKey, src: RedisKey, min: string | Buffer | number, max: string | Buffer | number, byscore: "BYSCORE", offsetCountToken: "LIMIT", offset: number | string, count: number | string, callback?: Callback<number>): Result<number, Context>; + zrangestore(dst: RedisKey, src: RedisKey, min: string | Buffer | number, max: string | Buffer | number, byscore: "BYSCORE", rev: "REV", callback?: Callback<number>): Result<number, Context>; + zrangestore(dst: RedisKey, src: RedisKey, min: string | Buffer | number, max: string | Buffer | number, byscore: "BYSCORE", rev: "REV", offsetCountToken: "LIMIT", offset: number | string, count: number | string, callback?: Callback<number>): Result<number, Context>; + zrangestore(dst: RedisKey, src: RedisKey, min: string | Buffer | number, max: string | Buffer | number, bylex: "BYLEX", callback?: Callback<number>): Result<number, Context>; + zrangestore(dst: RedisKey, src: RedisKey, min: string | Buffer | number, max: string | Buffer | number, bylex: "BYLEX", offsetCountToken: "LIMIT", offset: number | string, count: number | string, callback?: Callback<number>): Result<number, Context>; + zrangestore(dst: RedisKey, src: RedisKey, min: string | Buffer | number, max: string | Buffer | number, bylex: "BYLEX", rev: "REV", callback?: Callback<number>): Result<number, Context>; + zrangestore(dst: RedisKey, src: RedisKey, min: string | Buffer | number, max: string | Buffer | number, bylex: "BYLEX", rev: "REV", offsetCountToken: "LIMIT", offset: number | string, count: number | string, callback?: Callback<number>): Result<number, Context>; + zrank(key: RedisKey, member: string | Buffer | number, callback?: Callback<number | null>): Result<number | null, Context>; + zrem(...args: [ + key: RedisKey, + ...members: (string | Buffer | number)[], + callback: Callback<number> + ]): Result<number, Context>; + zrem(...args: [ + key: RedisKey, + members: (string | Buffer | number)[], + callback: Callback<number> + ]): Result<number, Context>; + zrem(...args: [ + key: RedisKey, + ...members: (string | Buffer | number)[] + ]): Result<number, Context>; + zrem(...args: [ + key: RedisKey, + members: (string | Buffer | number)[] + ]): Result<number, Context>; + zremrangebylex(key: RedisKey, min: string | Buffer | number, max: string | Buffer | number, callback?: Callback<number>): Result<number, Context>; + zremrangebyrank(key: RedisKey, start: number | string, stop: number | string, callback?: Callback<number>): Result<number, Context>; + zremrangebyscore(key: RedisKey, min: number | string, max: number | string, callback?: Callback<number>): Result<number, Context>; + zrevrange(key: RedisKey, start: number | string, stop: number | string, callback?: Callback<string[]>): Result<string[], Context>; + zrevrangeBuffer(key: RedisKey, start: number | string, stop: number | string, callback?: Callback<Buffer[]>): Result<Buffer[], Context>; + zrevrange(key: RedisKey, start: number | string, stop: number | string, withscores: "WITHSCORES", callback?: Callback<string[]>): Result<string[], Context>; + zrevrangeBuffer(key: RedisKey, start: number | string, stop: number | string, withscores: "WITHSCORES", callback?: Callback<Buffer[]>): Result<Buffer[], Context>; + zrevrangebylex(key: RedisKey, max: string | Buffer | number, min: string | Buffer | number, callback?: Callback<string[]>): Result<string[], Context>; + zrevrangebylexBuffer(key: RedisKey, max: string | Buffer | number, min: string | Buffer | number, callback?: Callback<Buffer[]>): Result<Buffer[], Context>; + zrevrangebylex(key: RedisKey, max: string | Buffer | number, min: string | Buffer | number, offsetCountToken: "LIMIT", offset: number | string, count: number | string, callback?: Callback<string[]>): Result<string[], Context>; + zrevrangebylexBuffer(key: RedisKey, max: string | Buffer | number, min: string | Buffer | number, offsetCountToken: "LIMIT", offset: number | string, count: number | string, callback?: Callback<Buffer[]>): Result<Buffer[], Context>; + zrevrangebyscore(key: RedisKey, max: number | string, min: number | string, callback?: Callback<string[]>): Result<string[], Context>; + zrevrangebyscoreBuffer(key: RedisKey, max: number | string, min: number | string, callback?: Callback<Buffer[]>): Result<Buffer[], Context>; + zrevrangebyscore(key: RedisKey, max: number | string, min: number | string, offsetCountToken: "LIMIT", offset: number | string, count: number | string, callback?: Callback<string[]>): Result<string[], Context>; + zrevrangebyscoreBuffer(key: RedisKey, max: number | string, min: number | string, offsetCountToken: "LIMIT", offset: number | string, count: number | string, callback?: Callback<Buffer[]>): Result<Buffer[], Context>; + zrevrangebyscore(key: RedisKey, max: number | string, min: number | string, withscores: "WITHSCORES", callback?: Callback<string[]>): Result<string[], Context>; + zrevrangebyscoreBuffer(key: RedisKey, max: number | string, min: number | string, withscores: "WITHSCORES", callback?: Callback<Buffer[]>): Result<Buffer[], Context>; + zrevrangebyscore(key: RedisKey, max: number | string, min: number | string, withscores: "WITHSCORES", offsetCountToken: "LIMIT", offset: number | string, count: number | string, callback?: Callback<string[]>): Result<string[], Context>; + zrevrangebyscoreBuffer(key: RedisKey, max: number | string, min: number | string, withscores: "WITHSCORES", offsetCountToken: "LIMIT", offset: number | string, count: number | string, callback?: Callback<Buffer[]>): Result<Buffer[], Context>; + zrevrank(key: RedisKey, member: string | Buffer | number, callback?: Callback<number | null>): Result<number | null, Context>; + zscan(key: RedisKey, cursor: number | string, callback?: Callback<[ + cursor: string, + elements: string[] + ]>): Result<[ + cursor: string, + elements: string[] + ], Context>; + zscanBuffer(key: RedisKey, cursor: number | string, callback?: Callback<[ + cursor: Buffer, + elements: Buffer[] + ]>): Result<[ + cursor: Buffer, + elements: Buffer[] + ], Context>; + zscan(key: RedisKey, cursor: number | string, countToken: "COUNT", count: number | string, callback?: Callback<[ + cursor: string, + elements: string[] + ]>): Result<[ + cursor: string, + elements: string[] + ], Context>; + zscanBuffer(key: RedisKey, cursor: number | string, countToken: "COUNT", count: number | string, callback?: Callback<[ + cursor: Buffer, + elements: Buffer[] + ]>): Result<[ + cursor: Buffer, + elements: Buffer[] + ], Context>; + zscan(key: RedisKey, cursor: number | string, patternToken: "MATCH", pattern: string, callback?: Callback<[ + cursor: string, + elements: string[] + ]>): Result<[ + cursor: string, + elements: string[] + ], Context>; + zscanBuffer(key: RedisKey, cursor: number | string, patternToken: "MATCH", pattern: string, callback?: Callback<[ + cursor: Buffer, + elements: Buffer[] + ]>): Result<[ + cursor: Buffer, + elements: Buffer[] + ], Context>; + zscan(key: RedisKey, cursor: number | string, patternToken: "MATCH", pattern: string, countToken: "COUNT", count: number | string, callback?: Callback<[ + cursor: string, + elements: string[] + ]>): Result<[ + cursor: string, + elements: string[] + ], Context>; + zscanBuffer(key: RedisKey, cursor: number | string, patternToken: "MATCH", pattern: string, countToken: "COUNT", count: number | string, callback?: Callback<[ + cursor: Buffer, + elements: Buffer[] + ]>): Result<[ + cursor: Buffer, + elements: Buffer[] + ], Context>; + zscore(key: RedisKey, member: string | Buffer | number, callback?: Callback<string | null>): Result<string | null, Context>; + zscoreBuffer(key: RedisKey, member: string | Buffer | number, callback?: Callback<Buffer | null>): Result<Buffer | null, Context>; + zunion(...args: [ + numkeys: number | string, + ...keys: RedisKey[], + callback: Callback<string[]> + ]): Result<string[], Context>; + zunionBuffer(...args: [ + numkeys: number | string, + ...keys: RedisKey[], + callback: Callback<Buffer[]> + ]): Result<Buffer[], Context>; + zunion(...args: [ + numkeys: number | string, + keys: RedisKey[], + callback: Callback<string[]> + ]): Result<string[], Context>; + zunionBuffer(...args: [ + numkeys: number | string, + keys: RedisKey[], + callback: Callback<Buffer[]> + ]): Result<Buffer[], Context>; + zunion(...args: [ + numkeys: number | string, + ...keys: RedisKey[] + ]): Result<string[], Context>; + zunionBuffer(...args: [ + numkeys: number | string, + ...keys: RedisKey[] + ]): Result<Buffer[], Context>; + zunion(...args: [ + numkeys: number | string, + keys: RedisKey[] + ]): Result<string[], Context>; + zunionBuffer(...args: [ + numkeys: number | string, + keys: RedisKey[] + ]): Result<Buffer[], Context>; + zunion(...args: [ + numkeys: number | string, + ...keys: RedisKey[], + withscores: "WITHSCORES", + callback: Callback<string[]> + ]): Result<string[], Context>; + zunionBuffer(...args: [ + numkeys: number | string, + ...keys: RedisKey[], + withscores: "WITHSCORES", + callback: Callback<Buffer[]> + ]): Result<Buffer[], Context>; + zunion(...args: [ + numkeys: number | string, + keys: RedisKey[], + withscores: "WITHSCORES", + callback: Callback<string[]> + ]): Result<string[], Context>; + zunionBuffer(...args: [ + numkeys: number | string, + keys: RedisKey[], + withscores: "WITHSCORES", + callback: Callback<Buffer[]> + ]): Result<Buffer[], Context>; + zunion(...args: [ + numkeys: number | string, + ...keys: RedisKey[], + withscores: "WITHSCORES" + ]): Result<string[], Context>; + zunionBuffer(...args: [ + numkeys: number | string, + ...keys: RedisKey[], + withscores: "WITHSCORES" + ]): Result<Buffer[], Context>; + zunion(...args: [ + numkeys: number | string, + keys: RedisKey[], + withscores: "WITHSCORES" + ]): Result<string[], Context>; + zunionBuffer(...args: [ + numkeys: number | string, + keys: RedisKey[], + withscores: "WITHSCORES" + ]): Result<Buffer[], Context>; + zunion(...args: [ + numkeys: number | string, + ...keys: RedisKey[], + aggregate: "AGGREGATE", + sum: "SUM", + callback: Callback<string[]> + ]): Result<string[], Context>; + zunionBuffer(...args: [ + numkeys: number | string, + ...keys: RedisKey[], + aggregate: "AGGREGATE", + sum: "SUM", + callback: Callback<Buffer[]> + ]): Result<Buffer[], Context>; + zunion(...args: [ + numkeys: number | string, + keys: RedisKey[], + aggregate: "AGGREGATE", + sum: "SUM", + callback: Callback<string[]> + ]): Result<string[], Context>; + zunionBuffer(...args: [ + numkeys: number | string, + keys: RedisKey[], + aggregate: "AGGREGATE", + sum: "SUM", + callback: Callback<Buffer[]> + ]): Result<Buffer[], Context>; + zunion(...args: [ + numkeys: number | string, + ...keys: RedisKey[], + aggregate: "AGGREGATE", + sum: "SUM" + ]): Result<string[], Context>; + zunionBuffer(...args: [ + numkeys: number | string, + ...keys: RedisKey[], + aggregate: "AGGREGATE", + sum: "SUM" + ]): Result<Buffer[], Context>; + zunion(...args: [ + numkeys: number | string, + keys: RedisKey[], + aggregate: "AGGREGATE", + sum: "SUM" + ]): Result<string[], Context>; + zunionBuffer(...args: [ + numkeys: number | string, + keys: RedisKey[], + aggregate: "AGGREGATE", + sum: "SUM" + ]): Result<Buffer[], Context>; + zunion(...args: [ + numkeys: number | string, + ...keys: RedisKey[], + aggregate: "AGGREGATE", + sum: "SUM", + withscores: "WITHSCORES", + callback: Callback<string[]> + ]): Result<string[], Context>; + zunionBuffer(...args: [ + numkeys: number | string, + ...keys: RedisKey[], + aggregate: "AGGREGATE", + sum: "SUM", + withscores: "WITHSCORES", + callback: Callback<Buffer[]> + ]): Result<Buffer[], Context>; + zunion(...args: [ + numkeys: number | string, + keys: RedisKey[], + aggregate: "AGGREGATE", + sum: "SUM", + withscores: "WITHSCORES", + callback: Callback<string[]> + ]): Result<string[], Context>; + zunionBuffer(...args: [ + numkeys: number | string, + keys: RedisKey[], + aggregate: "AGGREGATE", + sum: "SUM", + withscores: "WITHSCORES", + callback: Callback<Buffer[]> + ]): Result<Buffer[], Context>; + zunion(...args: [ + numkeys: number | string, + ...keys: RedisKey[], + aggregate: "AGGREGATE", + sum: "SUM", + withscores: "WITHSCORES" + ]): Result<string[], Context>; + zunionBuffer(...args: [ + numkeys: number | string, + ...keys: RedisKey[], + aggregate: "AGGREGATE", + sum: "SUM", + withscores: "WITHSCORES" + ]): Result<Buffer[], Context>; + zunion(...args: [ + numkeys: number | string, + keys: RedisKey[], + aggregate: "AGGREGATE", + sum: "SUM", + withscores: "WITHSCORES" + ]): Result<string[], Context>; + zunionBuffer(...args: [ + numkeys: number | string, + keys: RedisKey[], + aggregate: "AGGREGATE", + sum: "SUM", + withscores: "WITHSCORES" + ]): Result<Buffer[], Context>; + zunion(...args: [ + numkeys: number | string, + ...keys: RedisKey[], + aggregate: "AGGREGATE", + min: "MIN", + callback: Callback<string[]> + ]): Result<string[], Context>; + zunionBuffer(...args: [ + numkeys: number | string, + ...keys: RedisKey[], + aggregate: "AGGREGATE", + min: "MIN", + callback: Callback<Buffer[]> + ]): Result<Buffer[], Context>; + zunion(...args: [ + numkeys: number | string, + keys: RedisKey[], + aggregate: "AGGREGATE", + min: "MIN", + callback: Callback<string[]> + ]): Result<string[], Context>; + zunionBuffer(...args: [ + numkeys: number | string, + keys: RedisKey[], + aggregate: "AGGREGATE", + min: "MIN", + callback: Callback<Buffer[]> + ]): Result<Buffer[], Context>; + zunion(...args: [ + numkeys: number | string, + ...keys: RedisKey[], + aggregate: "AGGREGATE", + min: "MIN" + ]): Result<string[], Context>; + zunionBuffer(...args: [ + numkeys: number | string, + ...keys: RedisKey[], + aggregate: "AGGREGATE", + min: "MIN" + ]): Result<Buffer[], Context>; + zunion(...args: [ + numkeys: number | string, + keys: RedisKey[], + aggregate: "AGGREGATE", + min: "MIN" + ]): Result<string[], Context>; + zunionBuffer(...args: [ + numkeys: number | string, + keys: RedisKey[], + aggregate: "AGGREGATE", + min: "MIN" + ]): Result<Buffer[], Context>; + zunion(...args: [ + numkeys: number | string, + ...keys: RedisKey[], + aggregate: "AGGREGATE", + min: "MIN", + withscores: "WITHSCORES", + callback: Callback<string[]> + ]): Result<string[], Context>; + zunionBuffer(...args: [ + numkeys: number | string, + ...keys: RedisKey[], + aggregate: "AGGREGATE", + min: "MIN", + withscores: "WITHSCORES", + callback: Callback<Buffer[]> + ]): Result<Buffer[], Context>; + zunion(...args: [ + numkeys: number | string, + keys: RedisKey[], + aggregate: "AGGREGATE", + min: "MIN", + withscores: "WITHSCORES", + callback: Callback<string[]> + ]): Result<string[], Context>; + zunionBuffer(...args: [ + numkeys: number | string, + keys: RedisKey[], + aggregate: "AGGREGATE", + min: "MIN", + withscores: "WITHSCORES", + callback: Callback<Buffer[]> + ]): Result<Buffer[], Context>; + zunion(...args: [ + numkeys: number | string, + ...keys: RedisKey[], + aggregate: "AGGREGATE", + min: "MIN", + withscores: "WITHSCORES" + ]): Result<string[], Context>; + zunionBuffer(...args: [ + numkeys: number | string, + ...keys: RedisKey[], + aggregate: "AGGREGATE", + min: "MIN", + withscores: "WITHSCORES" + ]): Result<Buffer[], Context>; + zunion(...args: [ + numkeys: number | string, + keys: RedisKey[], + aggregate: "AGGREGATE", + min: "MIN", + withscores: "WITHSCORES" + ]): Result<string[], Context>; + zunionBuffer(...args: [ + numkeys: number | string, + keys: RedisKey[], + aggregate: "AGGREGATE", + min: "MIN", + withscores: "WITHSCORES" + ]): Result<Buffer[], Context>; + zunion(...args: [ + numkeys: number | string, + ...keys: RedisKey[], + aggregate: "AGGREGATE", + max: "MAX", + callback: Callback<string[]> + ]): Result<string[], Context>; + zunionBuffer(...args: [ + numkeys: number | string, + ...keys: RedisKey[], + aggregate: "AGGREGATE", + max: "MAX", + callback: Callback<Buffer[]> + ]): Result<Buffer[], Context>; + zunion(...args: [ + numkeys: number | string, + keys: RedisKey[], + aggregate: "AGGREGATE", + max: "MAX", + callback: Callback<string[]> + ]): Result<string[], Context>; + zunionBuffer(...args: [ + numkeys: number | string, + keys: RedisKey[], + aggregate: "AGGREGATE", + max: "MAX", + callback: Callback<Buffer[]> + ]): Result<Buffer[], Context>; + zunion(...args: [ + numkeys: number | string, + ...keys: RedisKey[], + aggregate: "AGGREGATE", + max: "MAX" + ]): Result<string[], Context>; + zunionBuffer(...args: [ + numkeys: number | string, + ...keys: RedisKey[], + aggregate: "AGGREGATE", + max: "MAX" + ]): Result<Buffer[], Context>; + zunion(...args: [ + numkeys: number | string, + keys: RedisKey[], + aggregate: "AGGREGATE", + max: "MAX" + ]): Result<string[], Context>; + zunionBuffer(...args: [ + numkeys: number | string, + keys: RedisKey[], + aggregate: "AGGREGATE", + max: "MAX" + ]): Result<Buffer[], Context>; + zunion(...args: [ + numkeys: number | string, + ...keys: RedisKey[], + aggregate: "AGGREGATE", + max: "MAX", + withscores: "WITHSCORES", + callback: Callback<string[]> + ]): Result<string[], Context>; + zunionBuffer(...args: [ + numkeys: number | string, + ...keys: RedisKey[], + aggregate: "AGGREGATE", + max: "MAX", + withscores: "WITHSCORES", + callback: Callback<Buffer[]> + ]): Result<Buffer[], Context>; + zunion(...args: [ + numkeys: number | string, + keys: RedisKey[], + aggregate: "AGGREGATE", + max: "MAX", + withscores: "WITHSCORES", + callback: Callback<string[]> + ]): Result<string[], Context>; + zunionBuffer(...args: [ + numkeys: number | string, + keys: RedisKey[], + aggregate: "AGGREGATE", + max: "MAX", + withscores: "WITHSCORES", + callback: Callback<Buffer[]> + ]): Result<Buffer[], Context>; + zunion(...args: [ + numkeys: number | string, + ...keys: RedisKey[], + aggregate: "AGGREGATE", + max: "MAX", + withscores: "WITHSCORES" + ]): Result<string[], Context>; + zunionBuffer(...args: [ + numkeys: number | string, + ...keys: RedisKey[], + aggregate: "AGGREGATE", + max: "MAX", + withscores: "WITHSCORES" + ]): Result<Buffer[], Context>; + zunion(...args: [ + numkeys: number | string, + keys: RedisKey[], + aggregate: "AGGREGATE", + max: "MAX", + withscores: "WITHSCORES" + ]): Result<string[], Context>; + zunionBuffer(...args: [ + numkeys: number | string, + keys: RedisKey[], + aggregate: "AGGREGATE", + max: "MAX", + withscores: "WITHSCORES" + ]): Result<Buffer[], Context>; + zunion(...args: [ + numkeys: number | string, + ...keys: RedisKey[], + aggregate: "AGGREGATE", + count: "COUNT", + callback: Callback<string[]> + ]): Result<string[], Context>; + zunionBuffer(...args: [ + numkeys: number | string, + ...keys: RedisKey[], + aggregate: "AGGREGATE", + count: "COUNT", + callback: Callback<Buffer[]> + ]): Result<Buffer[], Context>; + zunion(...args: [ + numkeys: number | string, + keys: RedisKey[], + aggregate: "AGGREGATE", + count: "COUNT", + callback: Callback<string[]> + ]): Result<string[], Context>; + zunionBuffer(...args: [ + numkeys: number | string, + keys: RedisKey[], + aggregate: "AGGREGATE", + count: "COUNT", + callback: Callback<Buffer[]> + ]): Result<Buffer[], Context>; + zunion(...args: [ + numkeys: number | string, + ...keys: RedisKey[], + aggregate: "AGGREGATE", + count: "COUNT" + ]): Result<string[], Context>; + zunionBuffer(...args: [ + numkeys: number | string, + ...keys: RedisKey[], + aggregate: "AGGREGATE", + count: "COUNT" + ]): Result<Buffer[], Context>; + zunion(...args: [ + numkeys: number | string, + keys: RedisKey[], + aggregate: "AGGREGATE", + count: "COUNT" + ]): Result<string[], Context>; + zunionBuffer(...args: [ + numkeys: number | string, + keys: RedisKey[], + aggregate: "AGGREGATE", + count: "COUNT" + ]): Result<Buffer[], Context>; + zunion(...args: [ + numkeys: number | string, + ...keys: RedisKey[], + aggregate: "AGGREGATE", + count: "COUNT", + withscores: "WITHSCORES", + callback: Callback<string[]> + ]): Result<string[], Context>; + zunionBuffer(...args: [ + numkeys: number | string, + ...keys: RedisKey[], + aggregate: "AGGREGATE", + count: "COUNT", + withscores: "WITHSCORES", + callback: Callback<Buffer[]> + ]): Result<Buffer[], Context>; + zunion(...args: [ + numkeys: number | string, + keys: RedisKey[], + aggregate: "AGGREGATE", + count: "COUNT", + withscores: "WITHSCORES", + callback: Callback<string[]> + ]): Result<string[], Context>; + zunionBuffer(...args: [ + numkeys: number | string, + keys: RedisKey[], + aggregate: "AGGREGATE", + count: "COUNT", + withscores: "WITHSCORES", + callback: Callback<Buffer[]> + ]): Result<Buffer[], Context>; + zunion(...args: [ + numkeys: number | string, + ...keys: RedisKey[], + aggregate: "AGGREGATE", + count: "COUNT", + withscores: "WITHSCORES" + ]): Result<string[], Context>; + zunionBuffer(...args: [ + numkeys: number | string, + ...keys: RedisKey[], + aggregate: "AGGREGATE", + count: "COUNT", + withscores: "WITHSCORES" + ]): Result<Buffer[], Context>; + zunion(...args: [ + numkeys: number | string, + keys: RedisKey[], + aggregate: "AGGREGATE", + count: "COUNT", + withscores: "WITHSCORES" + ]): Result<string[], Context>; + zunionBuffer(...args: [ + numkeys: number | string, + keys: RedisKey[], + aggregate: "AGGREGATE", + count: "COUNT", + withscores: "WITHSCORES" + ]): Result<Buffer[], Context>; + zunion(...args: [ + numkeys: number | string, + ...args: RedisValue[], + callback: Callback<string[]> + ]): Result<string[], Context>; + zunionBuffer(...args: [ + numkeys: number | string, + ...args: RedisValue[], + callback: Callback<Buffer[]> + ]): Result<Buffer[], Context>; + zunion(...args: [ + numkeys: number | string, + ...args: RedisValue[] + ]): Result<string[], Context>; + zunionBuffer(...args: [ + numkeys: number | string, + ...args: RedisValue[] + ]): Result<Buffer[], Context>; + zunion(...args: [ + numkeys: number | string, + ...args: RedisValue[], + withscores: "WITHSCORES", + callback: Callback<string[]> + ]): Result<string[], Context>; + zunionBuffer(...args: [ + numkeys: number | string, + ...args: RedisValue[], + withscores: "WITHSCORES", + callback: Callback<Buffer[]> + ]): Result<Buffer[], Context>; + zunion(...args: [ + numkeys: number | string, + ...args: RedisValue[], + withscores: "WITHSCORES" + ]): Result<string[], Context>; + zunionBuffer(...args: [ + numkeys: number | string, + ...args: RedisValue[], + withscores: "WITHSCORES" + ]): Result<Buffer[], Context>; + zunion(...args: [ + numkeys: number | string, + ...args: RedisValue[], + aggregate: "AGGREGATE", + sum: "SUM", + callback: Callback<string[]> + ]): Result<string[], Context>; + zunionBuffer(...args: [ + numkeys: number | string, + ...args: RedisValue[], + aggregate: "AGGREGATE", + sum: "SUM", + callback: Callback<Buffer[]> + ]): Result<Buffer[], Context>; + zunion(...args: [ + numkeys: number | string, + ...args: RedisValue[], + aggregate: "AGGREGATE", + sum: "SUM" + ]): Result<string[], Context>; + zunionBuffer(...args: [ + numkeys: number | string, + ...args: RedisValue[], + aggregate: "AGGREGATE", + sum: "SUM" + ]): Result<Buffer[], Context>; + zunion(...args: [ + numkeys: number | string, + ...args: RedisValue[], + aggregate: "AGGREGATE", + sum: "SUM", + withscores: "WITHSCORES", + callback: Callback<string[]> + ]): Result<string[], Context>; + zunionBuffer(...args: [ + numkeys: number | string, + ...args: RedisValue[], + aggregate: "AGGREGATE", + sum: "SUM", + withscores: "WITHSCORES", + callback: Callback<Buffer[]> + ]): Result<Buffer[], Context>; + zunion(...args: [ + numkeys: number | string, + ...args: RedisValue[], + aggregate: "AGGREGATE", + sum: "SUM", + withscores: "WITHSCORES" + ]): Result<string[], Context>; + zunionBuffer(...args: [ + numkeys: number | string, + ...args: RedisValue[], + aggregate: "AGGREGATE", + sum: "SUM", + withscores: "WITHSCORES" + ]): Result<Buffer[], Context>; + zunion(...args: [ + numkeys: number | string, + ...args: RedisValue[], + aggregate: "AGGREGATE", + min: "MIN", + callback: Callback<string[]> + ]): Result<string[], Context>; + zunionBuffer(...args: [ + numkeys: number | string, + ...args: RedisValue[], + aggregate: "AGGREGATE", + min: "MIN", + callback: Callback<Buffer[]> + ]): Result<Buffer[], Context>; + zunion(...args: [ + numkeys: number | string, + ...args: RedisValue[], + aggregate: "AGGREGATE", + min: "MIN" + ]): Result<string[], Context>; + zunionBuffer(...args: [ + numkeys: number | string, + ...args: RedisValue[], + aggregate: "AGGREGATE", + min: "MIN" + ]): Result<Buffer[], Context>; + zunion(...args: [ + numkeys: number | string, + ...args: RedisValue[], + aggregate: "AGGREGATE", + min: "MIN", + withscores: "WITHSCORES", + callback: Callback<string[]> + ]): Result<string[], Context>; + zunionBuffer(...args: [ + numkeys: number | string, + ...args: RedisValue[], + aggregate: "AGGREGATE", + min: "MIN", + withscores: "WITHSCORES", + callback: Callback<Buffer[]> + ]): Result<Buffer[], Context>; + zunion(...args: [ + numkeys: number | string, + ...args: RedisValue[], + aggregate: "AGGREGATE", + min: "MIN", + withscores: "WITHSCORES" + ]): Result<string[], Context>; + zunionBuffer(...args: [ + numkeys: number | string, + ...args: RedisValue[], + aggregate: "AGGREGATE", + min: "MIN", + withscores: "WITHSCORES" + ]): Result<Buffer[], Context>; + zunion(...args: [ + numkeys: number | string, + ...args: RedisValue[], + aggregate: "AGGREGATE", + max: "MAX", + callback: Callback<string[]> + ]): Result<string[], Context>; + zunionBuffer(...args: [ + numkeys: number | string, + ...args: RedisValue[], + aggregate: "AGGREGATE", + max: "MAX", + callback: Callback<Buffer[]> + ]): Result<Buffer[], Context>; + zunion(...args: [ + numkeys: number | string, + ...args: RedisValue[], + aggregate: "AGGREGATE", + max: "MAX" + ]): Result<string[], Context>; + zunionBuffer(...args: [ + numkeys: number | string, + ...args: RedisValue[], + aggregate: "AGGREGATE", + max: "MAX" + ]): Result<Buffer[], Context>; + zunion(...args: [ + numkeys: number | string, + ...args: RedisValue[], + aggregate: "AGGREGATE", + max: "MAX", + withscores: "WITHSCORES", + callback: Callback<string[]> + ]): Result<string[], Context>; + zunionBuffer(...args: [ + numkeys: number | string, + ...args: RedisValue[], + aggregate: "AGGREGATE", + max: "MAX", + withscores: "WITHSCORES", + callback: Callback<Buffer[]> + ]): Result<Buffer[], Context>; + zunion(...args: [ + numkeys: number | string, + ...args: RedisValue[], + aggregate: "AGGREGATE", + max: "MAX", + withscores: "WITHSCORES" + ]): Result<string[], Context>; + zunionBuffer(...args: [ + numkeys: number | string, + ...args: RedisValue[], + aggregate: "AGGREGATE", + max: "MAX", + withscores: "WITHSCORES" + ]): Result<Buffer[], Context>; + zunion(...args: [ + numkeys: number | string, + ...args: RedisValue[], + aggregate: "AGGREGATE", + count: "COUNT", + callback: Callback<string[]> + ]): Result<string[], Context>; + zunionBuffer(...args: [ + numkeys: number | string, + ...args: RedisValue[], + aggregate: "AGGREGATE", + count: "COUNT", + callback: Callback<Buffer[]> + ]): Result<Buffer[], Context>; + zunion(...args: [ + numkeys: number | string, + ...args: RedisValue[], + aggregate: "AGGREGATE", + count: "COUNT" + ]): Result<string[], Context>; + zunionBuffer(...args: [ + numkeys: number | string, + ...args: RedisValue[], + aggregate: "AGGREGATE", + count: "COUNT" + ]): Result<Buffer[], Context>; + zunion(...args: [ + numkeys: number | string, + ...args: RedisValue[], + aggregate: "AGGREGATE", + count: "COUNT", + withscores: "WITHSCORES", + callback: Callback<string[]> + ]): Result<string[], Context>; + zunionBuffer(...args: [ + numkeys: number | string, + ...args: RedisValue[], + aggregate: "AGGREGATE", + count: "COUNT", + withscores: "WITHSCORES", + callback: Callback<Buffer[]> + ]): Result<Buffer[], Context>; + zunion(...args: [ + numkeys: number | string, + ...args: RedisValue[], + aggregate: "AGGREGATE", + count: "COUNT", + withscores: "WITHSCORES" + ]): Result<string[], Context>; + zunionBuffer(...args: [ + numkeys: number | string, + ...args: RedisValue[], + aggregate: "AGGREGATE", + count: "COUNT", + withscores: "WITHSCORES" + ]): Result<Buffer[], Context>; + zunionstore(...args: [ + destination: RedisKey, + numkeys: number | string, + ...keys: RedisKey[], + callback: Callback<number> + ]): Result<number, Context>; + zunionstore(...args: [ + destination: RedisKey, + numkeys: number | string, + keys: RedisKey[], + callback: Callback<number> + ]): Result<number, Context>; + zunionstore(...args: [ + destination: RedisKey, + numkeys: number | string, + ...keys: RedisKey[] + ]): Result<number, Context>; + zunionstore(...args: [ + destination: RedisKey, + numkeys: number | string, + keys: RedisKey[] + ]): Result<number, Context>; + zunionstore(...args: [ + destination: RedisKey, + numkeys: number | string, + ...keys: RedisKey[], + aggregate: "AGGREGATE", + sum: "SUM", + callback: Callback<number> + ]): Result<number, Context>; + zunionstore(...args: [ + destination: RedisKey, + numkeys: number | string, + keys: RedisKey[], + aggregate: "AGGREGATE", + sum: "SUM", + callback: Callback<number> + ]): Result<number, Context>; + zunionstore(...args: [ + destination: RedisKey, + numkeys: number | string, + ...keys: RedisKey[], + aggregate: "AGGREGATE", + sum: "SUM" + ]): Result<number, Context>; + zunionstore(...args: [ + destination: RedisKey, + numkeys: number | string, + keys: RedisKey[], + aggregate: "AGGREGATE", + sum: "SUM" + ]): Result<number, Context>; + zunionstore(...args: [ + destination: RedisKey, + numkeys: number | string, + ...keys: RedisKey[], + aggregate: "AGGREGATE", + min: "MIN", + callback: Callback<number> + ]): Result<number, Context>; + zunionstore(...args: [ + destination: RedisKey, + numkeys: number | string, + keys: RedisKey[], + aggregate: "AGGREGATE", + min: "MIN", + callback: Callback<number> + ]): Result<number, Context>; + zunionstore(...args: [ + destination: RedisKey, + numkeys: number | string, + ...keys: RedisKey[], + aggregate: "AGGREGATE", + min: "MIN" + ]): Result<number, Context>; + zunionstore(...args: [ + destination: RedisKey, + numkeys: number | string, + keys: RedisKey[], + aggregate: "AGGREGATE", + min: "MIN" + ]): Result<number, Context>; + zunionstore(...args: [ + destination: RedisKey, + numkeys: number | string, + ...keys: RedisKey[], + aggregate: "AGGREGATE", + max: "MAX", + callback: Callback<number> + ]): Result<number, Context>; + zunionstore(...args: [ + destination: RedisKey, + numkeys: number | string, + keys: RedisKey[], + aggregate: "AGGREGATE", + max: "MAX", + callback: Callback<number> + ]): Result<number, Context>; + zunionstore(...args: [ + destination: RedisKey, + numkeys: number | string, + ...keys: RedisKey[], + aggregate: "AGGREGATE", + max: "MAX" + ]): Result<number, Context>; + zunionstore(...args: [ + destination: RedisKey, + numkeys: number | string, + keys: RedisKey[], + aggregate: "AGGREGATE", + max: "MAX" + ]): Result<number, Context>; + zunionstore(...args: [ + destination: RedisKey, + numkeys: number | string, + ...keys: RedisKey[], + aggregate: "AGGREGATE", + count: "COUNT", + callback: Callback<number> + ]): Result<number, Context>; + zunionstore(...args: [ + destination: RedisKey, + numkeys: number | string, + keys: RedisKey[], + aggregate: "AGGREGATE", + count: "COUNT", + callback: Callback<number> + ]): Result<number, Context>; + zunionstore(...args: [ + destination: RedisKey, + numkeys: number | string, + ...keys: RedisKey[], + aggregate: "AGGREGATE", + count: "COUNT" + ]): Result<number, Context>; + zunionstore(...args: [ + destination: RedisKey, + numkeys: number | string, + keys: RedisKey[], + aggregate: "AGGREGATE", + count: "COUNT" + ]): Result<number, Context>; + zunionstore(...args: [ + destination: RedisKey, + numkeys: number | string, + ...args: RedisValue[], + callback: Callback<number> + ]): Result<number, Context>; + zunionstore(...args: [ + destination: RedisKey, + numkeys: number | string, + ...args: RedisValue[] + ]): Result<number, Context>; + zunionstore(...args: [ + destination: RedisKey, + numkeys: number | string, + ...args: RedisValue[], + aggregate: "AGGREGATE", + sum: "SUM", + callback: Callback<number> + ]): Result<number, Context>; + zunionstore(...args: [ + destination: RedisKey, + numkeys: number | string, + ...args: RedisValue[], + aggregate: "AGGREGATE", + sum: "SUM" + ]): Result<number, Context>; + zunionstore(...args: [ + destination: RedisKey, + numkeys: number | string, + ...args: RedisValue[], + aggregate: "AGGREGATE", + min: "MIN", + callback: Callback<number> + ]): Result<number, Context>; + zunionstore(...args: [ + destination: RedisKey, + numkeys: number | string, + ...args: RedisValue[], + aggregate: "AGGREGATE", + min: "MIN" + ]): Result<number, Context>; + zunionstore(...args: [ + destination: RedisKey, + numkeys: number | string, + ...args: RedisValue[], + aggregate: "AGGREGATE", + max: "MAX", + callback: Callback<number> + ]): Result<number, Context>; + zunionstore(...args: [ + destination: RedisKey, + numkeys: number | string, + ...args: RedisValue[], + aggregate: "AGGREGATE", + max: "MAX" + ]): Result<number, Context>; + zunionstore(...args: [ + destination: RedisKey, + numkeys: number | string, + ...args: RedisValue[], + aggregate: "AGGREGATE", + count: "COUNT", + callback: Callback<number> + ]): Result<number, Context>; + zunionstore(...args: [ + destination: RedisKey, + numkeys: number | string, + ...args: RedisValue[], + aggregate: "AGGREGATE", + count: "COUNT" + ]): Result<number, Context>; +} + +declare type RedisKey = string | Buffer; + +interface RedisLikeClient { + setNX(key: string, value: string, ttlSec: number): Promise<boolean>; + set(key: string, value: string, ttlSec: number): Promise<void>; + get(key: string): Promise<string | null>; + expire(key: string, ttlSec: number): Promise<void>; + exists(key: string): Promise<boolean>; + del(keys: string[]): Promise<void>; + xAdd(key: string, fields: Record<string, string | Uint8Array>): Promise<string>; + xRange(key: string, start: string, end: string): Promise<Array<{ + id: string; + fields: Record<string, string | Uint8Array>; + }>>; + pipeline(commands: readonly PipelineCommand[]): Promise<void>; +} + +declare type RedisOptions = CommonRedisOptions & SentinelConnectionOptions & StandaloneConnectionOptions; + +type RedisResumableStreamStoreOptions = { + readonly keyPrefix?: string; + readonly defaultTtlMs?: number; + readonly pollIntervalMs?: number; + readonly maxChunkBytes?: number; +}; + +declare type RedisStatus = "close" | "connect" | "connecting" | "end" | "ready" | "reconnecting" | "wait"; + +declare type RedisValue = string | Buffer | number; + +declare type ReplyTransformer = (reply: any) => any; + +interface Respondable { + name: string; + args: CommandParameter[]; + resolve(result: any): void; + reject(error: Error): void; +} + +declare type Result<T, Context extends ClientContext> = ResultTypes<T, Context>[Context["type"]]; + +interface ResultTypes<Result, Context> { + default: Promise<Result>; + pipeline: ChainableCommander; +} + +type ResumableStreamAcquireOptions = { + readonly ttlMs?: number; +}; + +interface ResumableStreamContext { + run(streamId: string, makeStream: () => ReadableStream<Uint8Array>): Promise<ReadableStream<Uint8Array>>; + resume(streamId: string): Promise<ReadableStream<Uint8Array> | null>; + requireResume(streamId: string): Promise<ReadableStream<Uint8Array>>; + status(streamId: string): Promise<ResumableStreamStatus>; + delete(streamId: string): Promise<void>; +} + +type ResumableStreamContextOptions = { + readonly store: ResumableStreamStore; + readonly ttlMs?: number; + readonly waitUntil?: (promise: Promise<unknown>) => void; + readonly onAcquire?: (streamId: string, role: ResumableStreamRole) => void; + readonly onAppend?: (streamId: string, byteLength: number) => void; + readonly onFinalize?: (streamId: string, status: "done" | "error", error?: string) => void; + readonly onError?: (streamId: string, error: unknown) => void; +}; + +type ResumableStreamEntry = { + readonly cursor: string; + readonly chunk: Uint8Array; +}; + +declare class ResumableStreamError extends Error { + readonly code: ResumableStreamErrorCode; + constructor(code: ResumableStreamErrorCode, message: string); +} + +type ResumableStreamErrorCode = "exists" | "finalized" | "invalid-id" | "missing"; + +type ResumableStreamRole = "consumer" | "producer"; + +type ResumableStreamStatus = "done" | "error" | "missing" | "streaming"; + +interface ResumableStreamStore { + acquire(streamId: string, options?: ResumableStreamAcquireOptions): Promise<ResumableStreamRole>; + append(streamId: string, chunk: Uint8Array): Promise<void>; + finalize(streamId: string, status: "done" | "error", error?: string): Promise<void>; + read(streamId: string, cursor: string, signal: AbortSignal): AsyncIterable<ResumableStreamEntry>; + status(streamId: string): Promise<ResumableStreamStatus>; + delete(streamId: string): Promise<void>; +} + +declare type RetryStrategy = ((times: number) => number | void | null) | null | undefined; + +declare class ScanStream extends Readable { + private opt; + private _redisCursor; + private _redisDrained; + constructor(opt: Options); + _read(): void; + close(): void; +} + +interface ScanStreamOptions { + match?: string; + type?: string; + count?: number; + noValues?: boolean; +} + +interface SentinelAddress { + port: number; + host: string; + family?: number; +} + +interface SentinelConnectionOptions { + name?: string | undefined; + role?: "master" | "slave" | undefined; + tls?: ConnectionOptions | undefined; + sentinelUsername?: string | undefined; + sentinelPassword?: string | undefined; + sentinels?: Array<Partial<SentinelAddress>> | undefined; + sentinelRetryStrategy?: ((retryAttempts: number) => number | void | null) | undefined; + sentinelReconnectStrategy?: ((retryAttempts: number) => number | void | null) | undefined; + preferredSlaves?: PreferredSlaves | undefined; + connectTimeout?: number | undefined; + disconnectTimeout?: number | undefined; + sentinelCommandTimeout?: number | undefined; + enableTLSForSentinelMode?: boolean | undefined; + sentinelTLS?: ConnectionOptions | undefined; + natMap?: NatMap | undefined; + updateSentinels?: boolean | undefined; + sentinelMaxConnections?: number | undefined; + failoverDetector?: boolean | undefined; +} + +type SourcePart = { + type: "source"; + sourceType: "url"; + id: string; + url: string; + title?: string; + parentId?: string; +}; + +declare type StandaloneConnectionOptions = Partial<TcpOptions & IpcOptions> & { + disconnectTimeout?: number | undefined; + tls?: ConnectionOptions | undefined; +}; + +declare class SubscriptionSet { + private set; + add(set: AddSet, channel: string): void; + del(set: DelSet, channel: string): void; + channels(set: AddSet | DelSet): string[]; + isEmpty(): boolean; +} + +declare const TOOL_RESPONSE_SYMBOL: unique symbol; + +declare type TcpOptions = Pick<TcpNetConnectOpts, "family" | "host" | "port">; + +type TextPart = { + type: "text"; + text: string; + status: TextStatus; + parentId?: string; +}; + +type TextStatus = { + type: "running"; +} | { + type: "complete"; + reason: "stop" | "unknown"; +} | { + type: "incomplete"; + reason: "cancelled" | "content-filter" | "length" | "other"; +}; + +type TextStreamController = { + append(textDelta: string): void; + close(): void; +}; + +type ThreadMessageLike = { + role: "assistant" | "system" | "user"; + content: readonly MessagePartLike[]; + attachments?: readonly AttachmentLike[]; +}; + +type ToToolsJSONSchemaOptions = { + filter?: (name: string, tool: Tool) => boolean; +}; + +type Tool<TArgs extends Record<string, unknown> = Record<string, unknown>, TResult = unknown> = FrontendTool<TArgs, TResult> | BackendTool<TArgs, TResult> | HumanTool<TArgs, TResult> | ProviderTool<TArgs, TResult> | McpTool | ToolWithoutType<TArgs, TResult>; + +type ToolBase<TArgs extends Record<string, unknown> = Record<string, unknown>, TResult = unknown> = { + streamCall?: ToolStreamCallFunction<TArgs, TResult>; + display?: ToolDisplay; +}; + +interface ToolCallArgsReader<TArgs extends Record<string, unknown>> { + get<PathT extends TypePath<TArgs>>(...fieldPath: PathT): Promise<TypeAtPath<TArgs, PathT>>; + streamValues<PathT extends TypePath<TArgs>>(...fieldPath: PathT): AsyncIterableStream<DeepPartial<TypeAtPath<TArgs, PathT>>>; + streamText<PathT extends TypePath<TArgs>>(...fieldPath: PathT): TypeAtPath<TArgs, PathT> extends string & (infer U) ? AsyncIterableStream<U> : never; + forEach<PathT extends TypePath<TArgs>>(...fieldPath: PathT): NonNullable<TypeAtPath<TArgs, PathT>> extends Array<infer U> ? AsyncIterableStream<U> : never; +} + +type ToolCallPart = ToolCallPartWithoutResult | ToolCallPartWithResult; + +type ToolCallPartBase = { + type: "tool-call"; + status: ToolCallStatus; + toolCallId: string; + toolName: string; + argsText: string; + args: ReadonlyJSONObject; + timing?: ToolCallTiming; + artifact?: ReadonlyJSONValue; + result?: ReadonlyJSONValue; + modelContent?: readonly ToolModelContentPart[]; + isError?: boolean; + parentId?: string; +}; + +type ToolCallPartInit = { + toolCallId?: string; + toolName: string; + argsText?: string; + args?: ReadonlyJSONObject; + response?: ToolResponseLike<ReadonlyJSONValue>; +}; + +type ToolCallPartWithResult = ToolCallPartBase & { + state: "result"; + result: ReadonlyJSONValue; + artifact?: ReadonlyJSONValue; + modelContent?: readonly ToolModelContentPart[]; + isError?: boolean; +}; + +type ToolCallPartWithoutResult = ToolCallPartBase & { + state: "call" | "partial-call"; + result?: undefined; + modelContent?: undefined; +}; + +interface ToolCallReader<TArgs extends Record<string, unknown> = Record<string, unknown>, TResult = unknown> { + args: ToolCallArgsReader<TArgs>; + response: ToolCallResponseReader<TResult>; + result: { + get: () => Promise<TResult>; + }; +} + +interface ToolCallResponseReader<TResult> { + get: () => Promise<ToolResponse<TResult>>; +} + +type ToolCallStatus = { + type: "running"; + isArgsComplete: boolean; +} | { + type: "requires-action"; + reason: "tool-call-result"; +} | { + type: "complete"; + reason: "stop" | "unknown"; +} | { + type: "incomplete"; + reason: "cancelled" | "content-filter" | "length" | "other"; +}; + +type ToolCallStreamController = { + argsText: TextStreamController; + setResponse(response: ToolResponseLike<ReadonlyJSONValue>): void; + close(): void; +}; + +type ToolCallTiming = { + readonly startedAt: number; + readonly completedAt?: number; +}; + +type ToolCallback = (toolCall: { + toolCallId: string; + toolName: string; + args: ReadonlyJSONObject; +}) => Promise<ToolResponse<ReadonlyJSONValue>> | ToolResponse<ReadonlyJSONValue> | undefined; + +type ToolDeclaration<TArgs extends Record<string, unknown> = Record<string, unknown>, TResult = unknown> = FrontendTool<TArgs, TResult> | BackendToolDeclaration<TArgs, TResult> | HumanTool<TArgs, TResult> | ProviderTool<TArgs, TResult> | McpTool | ToolWithoutType<TArgs, TResult>; + +type ToolDisplay = "inline" | "standalone"; + +type ToolExecuteFunction<TArgs, TResult> = (args: TArgs, context: ToolExecutionContext) => TResult | Promise<TResult>; + +type ToolExecutionContext = { + toolCallId: string; + abortSignal: AbortSignal; + human: (payload: unknown) => Promise<unknown>; +}; + +type ToolExecutionOptions = { + execute: ToolCallback; + streamCall: ToolStreamCallback; + onExecutionStart?: ((toolCallId: string, toolName: string) => void) | undefined; + onExecutionEnd?: ((toolCallId: string, toolName: string) => void) | undefined; +}; + +declare class ToolExecutionStream extends PipeableTransformStream<AssistantStreamChunk, AssistantStreamChunk> { + constructor(options: ToolExecutionOptions); +} + +type ToolJSONSchema = { + description?: string; + parameters: JSONSchema7; + providerOptions?: ProviderOptions; +}; + +type ToolModelContentPart = { + readonly type: "text"; + readonly text: string; +} | { + readonly type: "file"; + readonly data: string; + readonly mediaType: string; + readonly filename?: string; +}; + +type ToolModelOutputFunction<TArgs, TResult> = (options: { + toolCallId: string; + input: TArgs; + output: TResult; +}) => readonly ToolModelContentPart[] | Promise<readonly ToolModelContentPart[]>; + +declare class ToolResponse<TResult> { + get [TOOL_RESPONSE_SYMBOL](): boolean; + readonly artifact?: ReadonlyJSONValue; + readonly result: TResult; + readonly isError: boolean; + readonly modelContent?: readonly ToolModelContentPart[]; + readonly messages?: ReadonlyJSONValue; + constructor(options: ToolResponseLike<TResult>); + static [Symbol.hasInstance](obj: unknown): obj is ToolResponse<ReadonlyJSONValue>; + static toResponse(result: any | ToolResponse<any>): ToolResponse<any>; +} + +type ToolResponseLike<TResult> = { + result: TResult; + artifact?: ReadonlyJSONValue | undefined; + isError?: boolean | undefined; + modelContent?: readonly ToolModelContentPart[] | undefined; + messages?: ReadonlyJSONValue | undefined; +}; + +type ToolResultStreamOptions = { + onExecutionStart?: (toolCallId: string, toolName: string) => void; + onExecutionEnd?: (toolCallId: string, toolName: string) => void; +}; + +type ToolStreamCallFunction<TArgs extends Record<string, unknown> = Record<string, unknown>, TResult = unknown> = (reader: ToolCallReader<TArgs, TResult>, context: ToolExecutionContext) => void; + +type ToolStreamCallback = <TArgs extends ReadonlyJSONObject = ReadonlyJSONObject, TResult extends ReadonlyJSONValue = ReadonlyJSONValue>(toolCall: { + reader: ToolCallReader<TArgs, TResult>; + toolCallId: string; + toolName: string; +}) => void; + +type ToolWithoutType<TArgs extends Record<string, unknown> = Record<string, unknown>, TResult = unknown> = (Omit<FrontendTool<TArgs, TResult>, "type"> | Omit<BackendTool<TArgs, TResult>, "type"> | Omit<HumanTool<TArgs, TResult>, "type"> | Omit<ProviderTool<TArgs, TResult>, "type">) & { + type?: undefined; +}; + +interface Transaction { + pipeline(commands?: unknown[][]): ChainableCommander; + multi(options: { + pipeline: false; + }): Promise<"OK">; + multi(): ChainableCommander; + multi(options: { + pipeline: true; + }): ChainableCommander; + multi(commands?: unknown[][]): ChainableCommander; +} + +type TupleIndex<T extends readonly any[]> = Exclude<keyof T, keyof any[]>; + +type TypeAtPath<T, P extends readonly any[]> = P extends [ + infer Head, + ...infer Rest +] ? Head extends keyof T ? TypeAtPath<T[Head], Rest> : never : T; + +type TypePath<T> = [ +] | (0 extends 1 & T ? any[] : T extends object ? T extends readonly any[] ? number extends T["length"] ? { + [K in TupleIndex<T>]: [ + AsNumber<K>, + ...TypePath<T[K]> + ]; +}[TupleIndex<T>] : [ + number, + ...TypePath<T[number]> +] : { + [K in ObjectKey<T>]: [ + K, + ...TypePath<T[K]> + ]; +}[ObjectKey<T>] : [ +]); + +type UIMessageStreamChunk = { + type: "start"; + messageId: string; +} | { + type: "text-start"; + id: string; +} | { + type: "text-delta"; + textDelta: string; +} | { + type: "text-end"; +} | { + type: "reasoning-start"; + id: string; +} | { + type: "reasoning-delta"; + delta: string; +} | { + type: "reasoning-end"; +} | { + type: "source"; + source: { + sourceType: "url"; + id: string; + url: string; + title?: string; + }; +} | { + type: "file"; + file: { + mimeType: string; + data: string; + }; +} | { + type: "tool-call-start"; + id: string; + toolCallId: string; + toolName: string; +} | { + type: "tool-call-delta"; + argsText: string; +} | { + type: "tool-call-end"; +} | { + type: "tool-result"; + toolCallId: string; + result: ReadonlyJSONValue; + isError?: boolean; + messages?: ReadonlyJSONValue; +} | { + type: "start-step"; + messageId?: string; +} | { + type: "finish-step"; + finishReason: FinishReason; + usage: Usage; + isContinued: boolean; +} | { + type: "finish"; + finishReason: FinishReason; + usage: Usage; +} | { + type: "error"; + errorText: string; +} | UIMessageStreamDataChunk; + +type UIMessageStreamDataChunk = { + type: `data-${string}`; + id?: string; + data: ReadonlyJSONValue; + transient?: boolean; +}; + +declare class UIMessageStreamDecoder extends PipeableTransformStream<Uint8Array<ArrayBuffer>, AssistantStreamChunk> { + constructor(options?: UIMessageStreamDecoderOptions); +} + +type UIMessageStreamDecoderOptions = { + onData?: (data: { + type: string; + name: string; + data: unknown; + transient?: boolean; + }) => void; +}; + +type Usage = { + inputTokens: number; + outputTokens: number; +}; + +declare type WriteableStream = NetStream | PipelineWriteableStream; + +declare function asAsyncIterableStream<T>(source: ReadableStream<T>): AsyncIterableStream<T>; + +declare function createAssistantStream(callback: (controller: AssistantStreamController) => PromiseLike<void> | void): AssistantStream; + +declare function createAssistantStreamController(): readonly [ + AssistantStream, + AssistantStreamController +]; + +declare function createAssistantStreamResponse(callback: (controller: AssistantStreamController) => PromiseLike<void> | void): Response; + +declare function createInMemoryResumableStreamStore(options?: InMemoryResumableStreamStoreOptions): ResumableStreamStore & { + dispose: () => void; +}; + +declare const createInitialMessage: (_param1?: { + unstable_state?: ReadonlyJSONValue; +}) => AssistantMessage; + +declare function createIoredisResumableStreamStore(client: IoRedisLike, options?: RedisResumableStreamStoreOptions): ResumableStreamStore; + +declare const createObjectStream: (_param2: CreateObjectStreamOptions) => ReadableStream<ObjectStreamChunk>; + +declare function createRedisResumableStreamStore(client: NodeRedisLike, options?: RedisResumableStreamStoreOptions): ResumableStreamStore; + +declare function createResumableAssistantStreamResponse(options: CreateResumableAssistantStreamResponseOptions): Promise<Response>; + +declare function createResumableStreamContext(options: ResumableStreamContextOptions): ResumableStreamContext; + +declare function createResumeAssistantStreamResponse(options: CreateResumeAssistantStreamResponseOptions): Promise<Response>; + +declare const fromObjectStreamResponse: (response: Response) => ReadableStream<ObjectStreamChunk>; + +declare const getPartialJsonObjectFieldState: (obj: Record<string, unknown>, fieldPath: (string | number)[]) => FieldState; + +declare const getPartialJsonObjectMeta: (obj: Record<symbol, unknown>) => PartialJsonObjectMeta | undefined; + +declare namespace entry_resumable_exports { + export { CreateResumableAssistantStreamResponseOptions, CreateResumeAssistantStreamResponseOptions, InMemoryResumableStreamStoreOptions, RESUMABLE_STREAM_ID_HEADER, RedisLikeClient, RedisResumableStreamStoreOptions, ResumableStreamAcquireOptions, ResumableStreamContext, ResumableStreamContextOptions, ResumableStreamEntry, ResumableStreamError, ResumableStreamErrorCode, ResumableStreamRole, ResumableStreamStatus, ResumableStreamStore, createInMemoryResumableStreamStore, createResumableAssistantStreamResponse, createResumableStreamContext, createResumeAssistantStreamResponse }; +} + +declare namespace entry_root_exports { + export { AssistantMessage, AssistantMessageAccumulator, AssistantMessageStream, AssistantMessageTiming, AssistantStream, AssistantStreamChunk, AssistantStreamController, AssistantTransportDecoder, AssistantTransportEncoder, DataPart, DataStreamDecoder, DataStreamEncoder, GenericAssistantMessage, GenericFilePart, GenericMessage, GenericSystemMessage, GenericTextPart, GenericToolCallPart, GenericToolMessage, GenericToolResultPart, GenericUserMessage, McpServerConfig, ObjectStreamChunk, ObjectStreamResponse, PlainTextDecoder, PlainTextEncoder, ProviderOptions, TextStreamController, ToToolsJSONSchemaOptions, Tool, ToolCallReader, ToolCallStreamController, ToolCallTiming, ToolDeclaration, ToolExecutionStream, ToolJSONSchema, ToolModelContentPart, ToolModelOutputFunction, ToolResponse, ToolResponseLike, ToolResultStreamOptions, UIMessageStreamChunk, UIMessageStreamDataChunk, UIMessageStreamDecoder, UIMessageStreamDecoderOptions, createAssistantStream, createAssistantStreamController, createAssistantStreamResponse, createObjectStream, fromObjectStreamResponse, toGenericMessages, toJSONSchema, toPartialJSONSchema, toToolsJSONSchema, createInitialMessage as unstable_createInitialMessage, unstable_runPendingTools, toolResultStream as unstable_toolResultStream }; +} + +declare namespace entry_resumable_ioredis_exports { + export { IoRedisLike, createIoredisResumableStreamStore }; +} + +declare const parsePartialJsonObject: (json: string) => (ReadonlyJSONObject & { + [PARTIAL_JSON_OBJECT_META_SYMBOL]: PartialJsonObjectMeta; +}) | undefined; + +declare namespace entry_resumable_redis_exports { + export { NodeRedisLike, createRedisResumableStreamStore }; +} + +declare function toGenericMessages(messages: readonly ThreadMessageLike[]): GenericMessage[]; + +declare function toJSONSchema(schema: StandardSchemaV1 | JSONSchema7): JSONSchema7; + +declare function toPartialJSONSchema(schema: JSONSchema7): JSONSchema7; + +declare function toToolsJSONSchema(tools: Record<string, Tool> | undefined, options?: ToToolsJSONSchemaOptions): Record<string, ToolJSONSchema>; + +declare function toolResultStream(tools: Record<string, Tool> | (() => Record<string, Tool> | undefined) | undefined, abortSignal: AbortSignal | (() => AbortSignal), human: (toolCallId: string, payload: unknown) => Promise<unknown>, options?: ToolResultStreamOptions): ToolExecutionStream; + +declare function unstable_runPendingTools(message: AssistantMessage, tools: Record<string, Tool> | undefined, abortSignal: AbortSignal, human: (toolCallId: string, payload: unknown) => Promise<unknown>): Promise<AssistantMessage>; + +declare namespace entry_utils_exports { + export { AssistantMetaTransformStream, AssistantTransformStream, AsyncIterableStream, ReadonlyJSONArray, ReadonlyJSONObject, ReadonlyJSONValue, asAsyncIterableStream, getPartialJsonObjectFieldState, getPartialJsonObjectMeta, parsePartialJsonObject }; +} + +export { entry_resumable_exports as entry_resumable, entry_resumable_ioredis_exports as entry_resumable_ioredis, entry_resumable_redis_exports as entry_resumable_redis, entry_root_exports as entry_root, entry_utils_exports as entry_utils }; diff --git a/api-surface/assistant-ui.ts b/api-surface/assistant-ui.ts new file mode 100644 index 0000000..e4cabbd --- /dev/null +++ b/api-surface/assistant-ui.ts @@ -0,0 +1,362 @@ +type CliSurfaceSnapshot = Record<string, unknown>; + +export const cliSurface: CliSurfaceSnapshot = { + "name": "assistant-ui", + "description": "add components and dependencies to your project", + "usage": "[options] [command]", + "arguments": [], + "options": [], + "commands": [ + { + "name": "add", + "description": "add a component to your project", + "usage": "[options] <components...>", + "arguments": [ + { + "syntax": "<components...>", + "description": "the components to add", + "required": true, + "variadic": true + } + ], + "options": [ + { + "flags": "-y, --yes", + "description": "skip confirmation prompt.", + "defaultValue": true + }, + { + "flags": "-o, --overwrite", + "description": "overwrite existing files.", + "defaultValue": false + }, + { + "flags": "-c, --cwd <cwd>", + "description": "the working directory. defaults to the current directory.", + "required": true, + "defaultValue": "<cwd>" + }, + { + "flags": "-p, --path <path>", + "description": "the path to add the component to.", + "required": true + }, + { + "flags": "--use-npm", + "description": "explicitly use npm" + }, + { + "flags": "--use-pnpm", + "description": "explicitly use pnpm" + }, + { + "flags": "--use-yarn", + "description": "explicitly use yarn" + }, + { + "flags": "--use-bun", + "description": "explicitly use bun" + } + ], + "commands": [] + }, + { + "name": "create", + "description": "create a new project", + "usage": "[project-directory] [options]", + "arguments": [ + { + "syntax": "[project-directory]", + "description": "", + "required": false + } + ], + "options": [ + { + "flags": "-t, --template <template>", + "description": "template to use (default, minimal, cloud, cloud-clerk, langchain, mcp, eve)", + "required": true + }, + { + "flags": "-e, --example <example>", + "description": "create from an example (with-ag-ui, with-google-adk, with-ai-sdk-v7, with-eve, with-artifacts, with-assistant-transport, with-chain-of-thought, with-cloud, with-custom-thread-list, with-elevenlabs-conversational, with-elevenlabs-scribe, with-livekit, with-expo, with-interactables, with-external-store, with-ffmpeg, with-langgraph, with-react-hook-form, with-react-ink, with-react-router, with-tanstack, with-resumable-stream)", + "required": true + }, + { + "flags": "-p, --preset <name-or-url>", + "description": "preset name or URL (e.g., chatgpt or https://www.assistant-ui.com/playground/init?preset=chatgpt)", + "required": true + }, + { + "flags": "--use-npm", + "description": "explicitly use npm" + }, + { + "flags": "--use-pnpm", + "description": "explicitly use pnpm" + }, + { + "flags": "--use-yarn", + "description": "explicitly use yarn" + }, + { + "flags": "--use-bun", + "description": "explicitly use bun" + }, + { + "flags": "--native", + "description": "create an Expo / React Native project" + }, + { + "flags": "--ink", + "description": "create a React Ink terminal project" + }, + { + "flags": "--skip-install", + "description": "skip installing packages" + }, + { + "flags": "--skills", + "description": "add assistant-ui agent skills for AI coding assistants" + }, + { + "flags": "--no-skills", + "description": "skip adding assistant-ui agent skills" + }, + { + "flags": "--debug-source-root <path>", + "description": "copy templates/examples from a local assistant-ui repo root", + "required": true, + "hidden": true + } + ], + "commands": [] + }, + { + "name": "init", + "description": "initialize assistant-ui in an existing project", + "usage": "[options] [project-directory]", + "arguments": [ + { + "syntax": "[project-directory]", + "description": "directory for the new project", + "required": false + } + ], + "options": [ + { + "flags": "-y, --yes", + "description": "skip confirmation prompt.", + "defaultValue": false + }, + { + "flags": "-o, --overwrite", + "description": "overwrite existing files.", + "defaultValue": false + }, + { + "flags": "-c, --cwd <cwd>", + "description": "the working directory. defaults to the current directory.", + "required": true, + "defaultValue": "<cwd>" + }, + { + "flags": "-p, --preset <name-or-url>", + "description": "preset name or URL (forwarded to 'assistant-ui create')", + "required": true, + "hidden": true + }, + { + "flags": "--use-npm", + "description": "explicitly use npm" + }, + { + "flags": "--use-pnpm", + "description": "explicitly use pnpm" + }, + { + "flags": "--use-yarn", + "description": "explicitly use yarn" + }, + { + "flags": "--use-bun", + "description": "explicitly use bun" + }, + { + "flags": "--skip-install", + "description": "skip installing packages" + } + ], + "commands": [] + }, + { + "name": "mcp", + "description": "install assistant-ui MCP docs server for your IDE", + "usage": "[options]", + "arguments": [], + "options": [ + { + "flags": "--cursor", + "description": "install for Cursor" + }, + { + "flags": "--windsurf", + "description": "install for Windsurf" + }, + { + "flags": "--vscode", + "description": "install for VSCode" + }, + { + "flags": "--zed", + "description": "install for Zed" + }, + { + "flags": "--claude-code", + "description": "install for Claude Code" + }, + { + "flags": "--claude-desktop", + "description": "install for Claude Desktop" + } + ], + "commands": [] + }, + { + "name": "codemod", + "description": "CLI tool for running codemods", + "usage": "[options] <codemod> <source>", + "arguments": [ + { + "syntax": "<codemod>", + "description": "Codemod to run (e.g., rewrite-framework-imports)", + "required": true + }, + { + "syntax": "<source>", + "description": "Path to source files or directory to transform", + "required": true + } + ], + "options": [ + { + "flags": "-d, --dry", + "description": "Dry run (no changes are made to files)" + }, + { + "flags": "-p, --print", + "description": "Print transformed files to stdout" + }, + { + "flags": "--verbose", + "description": "Show more information about the transform process" + }, + { + "flags": "-j, --jscodeshift <options>", + "description": "Pass options directly to jscodeshift", + "required": true + } + ], + "commands": [] + }, + { + "name": "upgrade", + "description": "Upgrade ai package dependencies and apply codemods", + "usage": "[options]", + "arguments": [], + "options": [ + { + "flags": "-d, --dry", + "description": "Dry run (no changes are made to files)" + }, + { + "flags": "-p, --print", + "description": "Print transformed files to stdout" + }, + { + "flags": "--verbose", + "description": "Show more information about the transform process" + }, + { + "flags": "-j, --jscodeshift <options>", + "description": "Pass options directly to jscodeshift", + "required": true + } + ], + "commands": [] + }, + { + "name": "update", + "description": "Update all '@assistant-ui/*' and 'assistant-*' packages in package.json to latest versions using your package manager.", + "usage": "[options]", + "arguments": [], + "options": [ + { + "flags": "--dry", + "description": "Print the package manager command instead of running it." + }, + { + "flags": "-c, --cwd <cwd>", + "description": "the working directory. defaults to the current directory.", + "required": true, + "defaultValue": "<cwd>" + } + ], + "commands": [] + }, + { + "name": "agent", + "description": "launch Claude Code with assistant-ui skills", + "usage": "[options] <prompt...>", + "arguments": [ + { + "syntax": "<prompt...>", + "description": "prompt for the agent", + "required": true, + "variadic": true + } + ], + "options": [ + { + "flags": "--dry", + "description": "print the command instead of running it" + } + ], + "commands": [] + }, + { + "name": "info", + "description": "Print environment and package information for bug reports.", + "usage": "[options]", + "arguments": [], + "options": [ + { + "flags": "-c, --cwd <cwd>", + "description": "the working directory. defaults to the current directory.", + "required": true, + "defaultValue": "<cwd>" + } + ], + "commands": [] + }, + { + "name": "doctor", + "description": "Diagnose mismatched or outdated assistant-ui packages (including transitive ones).", + "usage": "[options]", + "arguments": [], + "options": [ + { + "flags": "-c, --cwd <cwd>", + "description": "the working directory. defaults to the current directory.", + "required": true, + "defaultValue": "<cwd>" + }, + { + "flags": "--no-network", + "description": "Skip the npm registry check for latest versions." + } + ], + "commands": [] + } + ] +}; diff --git a/api-surface/assistant-ui__agent-launcher.ts b/api-surface/assistant-ui__agent-launcher.ts new file mode 100644 index 0000000..39c667f --- /dev/null +++ b/api-surface/assistant-ui__agent-launcher.ts @@ -0,0 +1,14 @@ +interface LaunchOptions { + pluginDir: string; + skillName?: string; + prompt: string; + dry?: boolean; +} + +declare namespace entry_root_exports { + export { LaunchOptions, launch }; +} + +declare function launch(options: LaunchOptions): void; + +export { entry_root_exports as entry_root }; diff --git a/api-surface/assistant-ui__cloud-ai-sdk.ts b/api-surface/assistant-ui__cloud-ai-sdk.ts new file mode 100644 index 0000000..f28823d --- /dev/null +++ b/api-surface/assistant-ui__cloud-ai-sdk.ts @@ -0,0 +1,436 @@ +import { UIMessage, UseChatHelpers } from "@ai-sdk/react"; + +import "@standard-schema/spec"; + +import { ChatInit } from "ai"; + +declare class AssistantCloud { + readonly threads: AssistantCloudThreads; + readonly auth: { + tokens: AssistantCloudAuthTokens; + }; + readonly runs: AssistantCloudRuns; + readonly files: AssistantCloudFiles; + readonly telemetry: AssistantCloudTelemetryConfig; + constructor(config: AssistantCloudConfig); +} + +declare class AssistantCloudAPI { + _auth: AssistantCloudAuthStrategy; + _baseUrl: string; + constructor(config: AssistantCloudConfig); + initializeAuth(): Promise<boolean>; + makeRawRequest(endpoint: string, options?: MakeRequestOptions): Promise<Response>; + makeRequest(endpoint: string, options?: MakeRequestOptions): Promise<any>; +} + +type AssistantCloudAuthStrategy = { + readonly strategy: "anon" | "api-key" | "jwt"; + getAuthHeaders(): Promise<Record<string, string> | false>; + readAuthHeaders(headers: Headers): void; +}; + +declare class AssistantCloudAuthTokens { + private cloud; + constructor(cloud: AssistantCloudAPI); + create(): Promise<AssistantCloudAuthTokensCreateResponse>; +} + +type AssistantCloudAuthTokensCreateResponse = { + token: string; +}; + +type AssistantCloudConfig = ({ + baseUrl: string; + authToken: () => Promise<string | null>; +} | { + baseUrl?: string; + apiKey: string; + userId: string; + workspaceId: string; +} | { + baseUrl: string; + anonymous: true; +}) & { + telemetry?: boolean | AssistantCloudTelemetryConfig; +}; + +declare class AssistantCloudFiles { + private cloud; + constructor(cloud: AssistantCloudAPI); + pdfToImages(body: PdfToImagesRequestBody): Promise<PdfToImagesResponse>; + generatePresignedUploadUrl(body: GeneratePresignedUploadUrlRequestBody): Promise<GeneratePresignedUploadUrlResponse>; +} + +type AssistantCloudMessageCreateResponse = { + message_id: string; +}; + +type AssistantCloudRunReport = { + thread_id: string; + status: "completed" | "error" | "incomplete"; + total_steps?: number; + tool_calls?: ReportToolCall[]; + steps?: { + input_tokens?: number; + output_tokens?: number; + reasoning_tokens?: number; + cached_input_tokens?: number; + tool_calls?: ReportToolCall[]; + start_ms?: number; + end_ms?: number; + }[]; + input_tokens?: number; + output_tokens?: number; + reasoning_tokens?: number; + cached_input_tokens?: number; + model_id?: string; + provider_type?: string; + duration_ms?: number; + output_text?: string; + metadata?: Record<string, unknown>; +}; + +declare class AssistantCloudRuns { + private cloud; + constructor(cloud: AssistantCloudAPI); + __internal_getAssistantOptions(assistantId: string): { + api: string; + headers: () => Promise<{ + Accept: string; + }>; + body: { + assistant_id: string; + response_format: string; + thread_id: string; + }; + }; + stream(body: AssistantCloudRunsStreamBody): Promise<AssistantStream>; + report(body: AssistantCloudRunReport): Promise<{ + run_id: string; + }>; +} + +type AssistantCloudRunsStreamBody = { + thread_id: string; + assistant_id: "system/thread_title"; + messages: readonly unknown[]; +}; + +type AssistantCloudTelemetryConfig = { + enabled?: boolean; + beforeReport?: (report: AssistantCloudRunReport) => AssistantCloudRunReport | null; +}; + +type AssistantCloudThreadMessageCreateBody = { + parent_id: string | null; + format: "aui/v0" | string; + content: ReadonlyJSONObject; +}; + +type AssistantCloudThreadMessageListQuery = { + format?: string; +}; + +type AssistantCloudThreadMessageListResponse = { + messages: CloudMessage[]; +}; + +type AssistantCloudThreadMessageUpdateBody = { + content: ReadonlyJSONObject; +}; + +declare class AssistantCloudThreadMessages { + private cloud; + constructor(cloud: AssistantCloudAPI); + list(threadId: string, query?: AssistantCloudThreadMessageListQuery): Promise<AssistantCloudThreadMessageListResponse>; + create(threadId: string, body: AssistantCloudThreadMessageCreateBody): Promise<AssistantCloudMessageCreateResponse>; + update(threadId: string, messageId: string, body: AssistantCloudThreadMessageUpdateBody): Promise<void>; +} + +declare class AssistantCloudThreads { + private cloud; + readonly messages: AssistantCloudThreadMessages; + constructor(cloud: AssistantCloudAPI); + list(query?: AssistantCloudThreadsListQuery): Promise<AssistantCloudThreadsListResponse>; + get(threadId: string): Promise<CloudThread$1>; + create(body: AssistantCloudThreadsCreateBody): Promise<AssistantCloudThreadsCreateResponse>; + update(threadId: string, body: AssistantCloudThreadsUpdateBody): Promise<void>; + delete(threadId: string): Promise<void>; +} + +type AssistantCloudThreadsCreateBody = { + title?: string | undefined; + last_message_at: Date; + metadata?: unknown | undefined; + external_id?: string | undefined; +}; + +type AssistantCloudThreadsCreateResponse = { + thread_id: string; +}; + +type AssistantCloudThreadsListQuery = { + is_archived?: boolean; + limit?: number; + after?: string; +}; + +type AssistantCloudThreadsListResponse = { + threads: CloudThread$1[]; +}; + +type AssistantCloudThreadsUpdateBody = { + title?: string | undefined; + last_message_at?: Date | undefined; + metadata?: unknown | undefined; + is_archived?: boolean | undefined; +}; + +type AssistantStream = ReadableStream<AssistantStreamChunk>; + +declare const AssistantStream: { + toResponse(stream: AssistantStream, transformer: AssistantStreamEncoder): Response; + fromResponse(response: Response, transformer: ReadableWritablePair<AssistantStreamChunk, Uint8Array<ArrayBuffer>>): ReadableStream<AssistantStreamChunk>; + toByteStream(stream: AssistantStream, transformer: ReadableWritablePair<Uint8Array<ArrayBuffer>, AssistantStreamChunk>): ReadableStream<Uint8Array<ArrayBuffer>>; + fromByteStream(readable: ReadableStream<Uint8Array<ArrayBuffer>>, transformer: ReadableWritablePair<AssistantStreamChunk, Uint8Array<ArrayBuffer>>): ReadableStream<AssistantStreamChunk>; +}; + +type AssistantStreamChunk = { + readonly path: readonly number[]; +} & ({ + readonly type: "part-start"; + readonly part: PartInit; +} | { + readonly type: "part-finish"; +} | { + readonly type: "tool-call-args-text-finish"; +} | { + readonly type: "text-delta"; + readonly textDelta: string; +} | { + readonly type: "annotations"; + readonly annotations: ReadonlyJSONValue[]; +} | { + readonly type: "data"; + readonly data: ReadonlyJSONValue[]; +} | { + readonly type: "step-start"; + readonly messageId: string; +} | { + readonly type: "step-finish"; + readonly finishReason: "content-filter" | "error" | "length" | "other" | "stop" | "tool-calls" | "unknown"; + readonly usage: { + readonly inputTokens: number; + readonly outputTokens: number; + }; + readonly isContinued: boolean; +} | { + readonly type: "message-finish"; + readonly finishReason: "content-filter" | "error" | "length" | "other" | "stop" | "tool-calls" | "unknown"; + readonly usage: { + readonly inputTokens: number; + readonly outputTokens: number; + }; +} | { + readonly type: "result"; + readonly artifact?: ReadonlyJSONValue; + readonly result: ReadonlyJSONValue; + readonly isError: boolean; + readonly modelContent?: readonly ToolModelContentPart[]; + readonly messages?: ReadonlyJSONValue; +} | { + readonly type: "error"; + readonly error: string; +} | { + readonly type: "update-state"; + readonly operations: ObjectStreamOperation[]; +}); + +type AssistantStreamEncoder = ReadableWritablePair<Uint8Array<ArrayBuffer>, AssistantStreamChunk> & { + headers?: Headers; +}; + +type CloudMessage = { + id: string; + parent_id: string | null; + height: number; + created_at: Date; + updated_at: Date; + format: "aui/v0" | string; + content: ReadonlyJSONObject; +}; + +type CloudThread = { + id: string; + title: string; + status: ThreadStatus; + externalId: string | null; + lastMessageAt: Date; + createdAt: Date; + updatedAt: Date; +}; + +type CloudThread$1 = { + title: string; + last_message_at: Date; + metadata: unknown; + external_id: string | null; + id: string; + project_id: string; + created_at: Date; + updated_at: Date; + workspace_id: string; + is_archived: boolean; +}; + +type GeneratePresignedUploadUrlRequestBody = { + filename: string; +}; + +type GeneratePresignedUploadUrlResponse = { + success: boolean; + signedUrl: string; + expiresAt: string; + publicUrl: string; +}; + +type MakeRequestOptions = { + method?: "POST" | "PUT" | "DELETE" | undefined; + headers?: Record<string, string> | undefined; + query?: Record<string, string | number | boolean> | undefined; + body?: object | undefined; +}; + +type ObjectStreamOperation = { + readonly type: "set"; + readonly path: readonly string[]; + readonly value: ReadonlyJSONValue; +} | { + readonly type: "append-text"; + readonly path: readonly string[]; + readonly value: string; +}; + +type PartInit = { + readonly type: "reasoning" | "text"; + readonly parentId?: string; +} | { + readonly type: "tool-call"; + readonly toolCallId: string; + readonly toolName: string; + readonly parentId?: string; +} | { + readonly type: "source"; + readonly sourceType: "url"; + readonly id: string; + readonly url: string; + readonly title?: string; + readonly parentId?: string; +} | { + readonly type: "file"; + readonly data: string; + readonly mimeType: string; + readonly parentId?: string; +} | { + readonly type: "data"; + readonly name: string; + readonly data: ReadonlyJSONValue; + readonly parentId?: string; +}; + +type PdfToImagesRequestBody = { + file_blob?: string | undefined; + file_url?: string | undefined; +}; + +type PdfToImagesResponse = { + success: boolean; + urls: string[]; + message: string; +}; + +type ReadonlyJSONArray = readonly ReadonlyJSONValue[]; + +type ReadonlyJSONObject = { + readonly [key: string]: ReadonlyJSONValue; +}; + +type ReadonlyJSONValue = null | string | number | boolean | ReadonlyJSONObject | ReadonlyJSONArray; + +type ReportToolCall = { + tool_name: string; + tool_call_id: string; + tool_args?: string; + tool_result?: string; + tool_source?: "backend" | "frontend" | "mcp"; + start_ms?: number; + end_ms?: number; + sampling_calls?: SamplingCallData[]; +}; + +type SamplingCallData = { + model_id?: string; + input_tokens?: number; + output_tokens?: number; + reasoning_tokens?: number; + cached_input_tokens?: number; + duration_ms?: number; +}; + +type ThreadStatus = "archived" | "regular"; + +type ToolModelContentPart = { + readonly type: "text"; + readonly text: string; +} | { + readonly type: "file"; + readonly data: string; + readonly mediaType: string; + readonly filename?: string; +}; + +type UseCloudChatOptions = ChatInit<UIMessage> & { + threads?: UseThreadsResult; + cloud?: AssistantCloud; + onSyncError?: (error: Error) => void; +}; + +type UseCloudChatResult = UseChatHelpers<UIMessage> & { + threads: UseThreadsResult; +}; + +type UseThreadsOptions = { + cloud: AssistantCloud; + includeArchived?: boolean; + enabled?: boolean; +}; + +type UseThreadsResult = { + cloud: AssistantCloud; + threads: CloudThread[]; + isLoading: boolean; + error: Error | null; + refresh: () => Promise<boolean>; + get: (id: string) => Promise<CloudThread | null>; + create: (options?: { + externalId?: string; + }) => Promise<CloudThread | null>; + delete: (id: string) => Promise<boolean>; + rename: (id: string, title: string) => Promise<boolean>; + archive: (id: string) => Promise<boolean>; + unarchive: (id: string) => Promise<boolean>; + threadId: string | null; + selectThread: (id: string | null) => void; + generateTitle: (threadId: string) => Promise<string | null>; +}; + +declare namespace entry_root_exports { + export { CloudThread, ThreadStatus, UseCloudChatOptions, UseCloudChatResult, UseThreadsOptions, UseThreadsResult, useCloudChat, useThreads }; +} + +declare function useCloudChat(options?: UseCloudChatOptions): UseCloudChatResult; + +declare function useThreads(options: UseThreadsOptions): UseThreadsResult; + +export { entry_root_exports as entry_root }; diff --git a/api-surface/assistant-ui__core.ts b/api-surface/assistant-ui__core.ts new file mode 100644 index 0000000..4e56735 --- /dev/null +++ b/api-surface/assistant-ui__core.ts @@ -0,0 +1,5831 @@ +import { StandardSchemaV1 } from "@standard-schema/spec"; + +import { ComponentType, FC, PropsWithChildren, ReactNode, RefObject } from "react"; + +interface ScopeRegistry { + threads: ThreadsClientSchema; + threadListItem: ThreadListItemClientSchema; + thread: ThreadClientSchema; + message: MessageClientSchema; + part: PartClientSchema; + composer: ComposerClientSchema; + attachment: AttachmentClientSchema; + modelContext: ModelContextClientSchema; + suggestions: SuggestionsClientSchema; + suggestion: SuggestionClientSchema; + chainOfThought: ChainOfThoughtClientSchema; + queueItem: QueueItemClientSchema; +} + +interface ScopeRegistry { + tools: ToolsClientSchema; + dataRenderers: DataRenderersClientSchema; + interactables: InteractablesClientSchema; + unstable_interactables: Unstable_InteractablesClientSchema; +} + +type AddToolResultOptions = { + messageId: string; + toolName: string; + toolCallId: string; + result: ReadonlyJSONValue; + isError: boolean; + artifact?: ReadonlyJSONValue | undefined; + modelContent?: readonly ToolModelContentPart[] | undefined; +}; + +type AncestorsOf<K extends ClientNames, Seen extends ClientNames = never> = K extends Seen ? never : ParentOf<K> extends never ? never : ParentOf<K> | AncestorsOf<ParentOf<K>, Seen | K>; + +type AppendMessage = Omit<ThreadMessage, "id"> & { + parentId: string | null; + sourceId: string | null; + runConfig: RunConfig | undefined; + startRun?: boolean | undefined; + steer?: boolean | undefined; +}; + +type AsNumber<K> = K extends `${infer N extends number}` ? N | K : never; + +type AssistantClient = { + [K in ClientNames]: AssistantClientAccessor<K>; +} & { + subscribe(listener: () => void): Unsubscribe; + on<TEvent extends AssistantEventName>(selector: AssistantEventSelector<TEvent>, callback: AssistantEventCallback<TEvent>): Unsubscribe; +}; + +type AssistantClientAccessor<K extends ClientNames> = (() => ClientSchemas[K]["methods"]) & (ClientMeta<K> | { + source: "root"; + query: Record<string, never>; +} | { + source: null; + query: null; +}) & { + name: K; +}; + +declare class AssistantCloud { + readonly threads: AssistantCloudThreads; + readonly auth: { + tokens: AssistantCloudAuthTokens; + }; + readonly runs: AssistantCloudRuns; + readonly files: AssistantCloudFiles; + readonly telemetry: AssistantCloudTelemetryConfig; + constructor(config: AssistantCloudConfig); +} + +declare class AssistantCloudAPI { + _auth: AssistantCloudAuthStrategy; + _baseUrl: string; + constructor(config: AssistantCloudConfig); + initializeAuth(): Promise<boolean>; + makeRawRequest(endpoint: string, options?: MakeRequestOptions): Promise<Response>; + makeRequest(endpoint: string, options?: MakeRequestOptions): Promise<any>; +} + +type AssistantCloudAuthStrategy = { + readonly strategy: "anon" | "api-key" | "jwt"; + getAuthHeaders(): Promise<Record<string, string> | false>; + readAuthHeaders(headers: Headers): void; +}; + +declare class AssistantCloudAuthTokens { + private cloud; + constructor(cloud: AssistantCloudAPI); + create(): Promise<AssistantCloudAuthTokensCreateResponse>; +} + +type AssistantCloudAuthTokensCreateResponse = { + token: string; +}; + +type AssistantCloudConfig = ({ + baseUrl: string; + authToken: () => Promise<string | null>; +} | { + baseUrl?: string; + apiKey: string; + userId: string; + workspaceId: string; +} | { + baseUrl: string; + anonymous: true; +}) & { + telemetry?: boolean | AssistantCloudTelemetryConfig; +}; + +declare class AssistantCloudFiles { + private cloud; + constructor(cloud: AssistantCloudAPI); + pdfToImages(body: PdfToImagesRequestBody): Promise<PdfToImagesResponse>; + generatePresignedUploadUrl(body: GeneratePresignedUploadUrlRequestBody): Promise<GeneratePresignedUploadUrlResponse>; +} + +type AssistantCloudMessageCreateResponse = { + message_id: string; +}; + +type AssistantCloudRunReport = { + thread_id: string; + status: "completed" | "error" | "incomplete"; + total_steps?: number; + tool_calls?: ReportToolCall[]; + steps?: { + input_tokens?: number; + output_tokens?: number; + reasoning_tokens?: number; + cached_input_tokens?: number; + tool_calls?: ReportToolCall[]; + start_ms?: number; + end_ms?: number; + }[]; + input_tokens?: number; + output_tokens?: number; + reasoning_tokens?: number; + cached_input_tokens?: number; + model_id?: string; + provider_type?: string; + duration_ms?: number; + output_text?: string; + metadata?: Record<string, unknown>; +}; + +declare class AssistantCloudRuns { + private cloud; + constructor(cloud: AssistantCloudAPI); + __internal_getAssistantOptions(assistantId: string): { + api: string; + headers: () => Promise<{ + Accept: string; + }>; + body: { + assistant_id: string; + response_format: string; + thread_id: string; + }; + }; + stream(body: AssistantCloudRunsStreamBody): Promise<AssistantStream>; + report(body: AssistantCloudRunReport): Promise<{ + run_id: string; + }>; +} + +type AssistantCloudRunsStreamBody = { + thread_id: string; + assistant_id: "system/thread_title"; + messages: readonly unknown[]; +}; + +type AssistantCloudTelemetryConfig = { + enabled?: boolean; + beforeReport?: (report: AssistantCloudRunReport) => AssistantCloudRunReport | null; +}; + +type AssistantCloudThreadMessageCreateBody = { + parent_id: string | null; + format: "aui/v0" | string; + content: ReadonlyJSONObject; +}; + +type AssistantCloudThreadMessageListQuery = { + format?: string; +}; + +type AssistantCloudThreadMessageListResponse = { + messages: CloudMessage[]; +}; + +type AssistantCloudThreadMessageUpdateBody = { + content: ReadonlyJSONObject; +}; + +declare class AssistantCloudThreadMessages { + private cloud; + constructor(cloud: AssistantCloudAPI); + list(threadId: string, query?: AssistantCloudThreadMessageListQuery): Promise<AssistantCloudThreadMessageListResponse>; + create(threadId: string, body: AssistantCloudThreadMessageCreateBody): Promise<AssistantCloudMessageCreateResponse>; + update(threadId: string, messageId: string, body: AssistantCloudThreadMessageUpdateBody): Promise<void>; +} + +declare class AssistantCloudThreads { + private cloud; + readonly messages: AssistantCloudThreadMessages; + constructor(cloud: AssistantCloudAPI); + list(query?: AssistantCloudThreadsListQuery): Promise<AssistantCloudThreadsListResponse>; + get(threadId: string): Promise<CloudThread>; + create(body: AssistantCloudThreadsCreateBody): Promise<AssistantCloudThreadsCreateResponse>; + update(threadId: string, body: AssistantCloudThreadsUpdateBody): Promise<void>; + delete(threadId: string): Promise<void>; +} + +type AssistantCloudThreadsCreateBody = { + title?: string | undefined; + last_message_at: Date; + metadata?: unknown | undefined; + external_id?: string | undefined; +}; + +type AssistantCloudThreadsCreateResponse = { + thread_id: string; +}; + +type AssistantCloudThreadsListQuery = { + is_archived?: boolean; + limit?: number; + after?: string; +}; + +type AssistantCloudThreadsListResponse = { + threads: CloudThread[]; +}; + +type AssistantCloudThreadsUpdateBody = { + title?: string | undefined; + last_message_at?: Date | undefined; + metadata?: unknown | undefined; + is_archived?: boolean | undefined; +}; + +type AssistantContextConfig = { + getContext: () => string; + disabled?: boolean | undefined; +}; + +type AssistantDataUI = FC & { + unstable_data: AssistantDataUIProps; +}; + +type AssistantDataUIProps<T = any> = { + name: string; + render: DataMessagePartComponent<T>; +}; + +type AssistantEventCallback<TEvent extends AssistantEventName> = (payload: AssistantEventPayload[TEvent]) => void; + +type AssistantEventName = keyof AssistantEventPayload; + +type AssistantEventPayload = ClientEventMap & { + "*": WildcardPayload; +}; + +type AssistantEventScope<TEvent extends AssistantEventName> = "*" | EventSource<TEvent> | (EventSource<TEvent> extends ClientNames ? AncestorsOf<EventSource<TEvent>> : never); + +type AssistantEventSelector<TEvent extends AssistantEventName> = TEvent | { + scope: AssistantEventScope<TEvent>; + event: TEvent; +}; + +declare class AssistantFrameHost implements ModelContextProvider { + private _context; + private _subscribers; + private _pendingRequests; + private _requestCounter; + private _iframeWindow; + private _targetOrigin; + constructor(iframeWindow: Window, targetOrigin?: string); + private handleMessage; + private updateContext; + private callTool; + private sendRequest; + private requestContext; + private notifySubscribers; + getModelContext(): ModelContext$1; + subscribe(callback: () => void): Unsubscribe$1; + dispose(): void; +} + +declare class AssistantFrameProvider { + private static _instance; + private _providers; + private _providerUnsubscribes; + private _targetOrigin; + private constructor(); + private static getInstance; + private handleMessage; + private handleToolCall; + private sendMessage; + private getModelContext; + private broadcastUpdate; + static addModelContextProvider(provider: ModelContextProvider, targetOrigin?: string): Unsubscribe$1; + static dispose(): void; +} + +type AssistantInstructionsConfig = { + disabled?: boolean | undefined; + instruction: string; +}; + +type AssistantInteractableProps = { + description: string; + stateSchema: InteractableStateSchema; + initialState: unknown; + id?: string; + selected?: boolean; +}; + +declare const AssistantProviderBase: FC<AssistantProviderBaseProps>; + +type AssistantProviderBaseProps = PropsWithChildren<{ + runtime: AssistantRuntime; + aui?: AssistantClient | null; +}>; + +type AssistantRuntime = { + readonly threads: ThreadListRuntime; + readonly thread: ThreadRuntime; + registerModelContextProvider(provider: ModelContextProvider): Unsubscribe$1; +}; + +type AssistantRuntimeCore = { + readonly threads: ThreadListRuntimeCore; + registerModelContextProvider: (provider: ModelContextProvider) => Unsubscribe$1; + getModelContextProvider: () => ModelContextProvider; + readonly RenderComponent?: ((...args: any[]) => unknown) | undefined; +}; + +declare class AssistantRuntimeImpl implements AssistantRuntime { + private readonly _core; + readonly threads: ThreadListRuntimeImpl; + readonly _thread: ThreadRuntime; + constructor(_core: AssistantRuntimeCore); + protected __internal_bindMethods(): void; + get thread(): ThreadRuntime; + registerModelContextProvider(provider: ModelContextProvider): Unsubscribe$1; +} + +declare const AssistantRuntimeProvider: import("react").MemoExoticComponent<(_param0: { + runtime: AssistantRuntime; + aui?: AssistantClient | null; + children: ReactNode; +}) => import("react").JSX.Element>; + +type AssistantStream = ReadableStream<AssistantStreamChunk>; + +declare const AssistantStream: { + toResponse(stream: AssistantStream, transformer: AssistantStreamEncoder): Response; + fromResponse(response: Response, transformer: ReadableWritablePair<AssistantStreamChunk, Uint8Array<ArrayBuffer>>): ReadableStream<AssistantStreamChunk>; + toByteStream(stream: AssistantStream, transformer: ReadableWritablePair<Uint8Array<ArrayBuffer>, AssistantStreamChunk>): ReadableStream<Uint8Array<ArrayBuffer>>; + fromByteStream(readable: ReadableStream<Uint8Array<ArrayBuffer>>, transformer: ReadableWritablePair<AssistantStreamChunk, Uint8Array<ArrayBuffer>>): ReadableStream<AssistantStreamChunk>; +}; + +type AssistantStreamChunk = { + readonly path: readonly number[]; +} & ({ + readonly type: "part-start"; + readonly part: PartInit; +} | { + readonly type: "part-finish"; +} | { + readonly type: "tool-call-args-text-finish"; +} | { + readonly type: "text-delta"; + readonly textDelta: string; +} | { + readonly type: "annotations"; + readonly annotations: ReadonlyJSONValue[]; +} | { + readonly type: "data"; + readonly data: ReadonlyJSONValue[]; +} | { + readonly type: "step-start"; + readonly messageId: string; +} | { + readonly type: "step-finish"; + readonly finishReason: "content-filter" | "error" | "length" | "other" | "stop" | "tool-calls" | "unknown"; + readonly usage: { + readonly inputTokens: number; + readonly outputTokens: number; + }; + readonly isContinued: boolean; +} | { + readonly type: "message-finish"; + readonly finishReason: "content-filter" | "error" | "length" | "other" | "stop" | "tool-calls" | "unknown"; + readonly usage: { + readonly inputTokens: number; + readonly outputTokens: number; + }; +} | { + readonly type: "result"; + readonly artifact?: ReadonlyJSONValue; + readonly result: ReadonlyJSONValue; + readonly isError: boolean; + readonly modelContent?: readonly ToolModelContentPart[]; + readonly messages?: ReadonlyJSONValue; +} | { + readonly type: "error"; + readonly error: string; +} | { + readonly type: "update-state"; + readonly operations: ObjectStreamOperation[]; +}); + +type AssistantStreamEncoder = ReadableWritablePair<Uint8Array<ArrayBuffer>, AssistantStreamChunk> & { + headers?: Headers; +}; + +type AssistantTool = FC & { + unstable_tool: AssistantToolProps<any, any>; +}; + +type AssistantToolProps<TArgs extends Record<string, unknown>, TResult> = AssistantToolProps$1<TArgs, TResult> & { + render?: ToolCallMessagePartComponent<TArgs, TResult> | undefined; + renderText?: ToolCallText<TArgs, TResult> | undefined; +}; + +type AssistantToolProps$1<TArgs extends Record<string, unknown>, TResult> = Tool<TArgs, TResult> & { + toolName: string; + render?: unknown; +}; + +type AssistantToolUI = FC & { + unstable_tool: AssistantToolUIProps<any, any>; +}; + +type AssistantToolUIProps<TArgs, TResult> = { + toolName: string; + render: ToolCallMessagePartComponent<TArgs, TResult>; + display?: "inline" | "standalone"; +}; + +type AsyncIterableStream<T> = AsyncIterable<T> & ReadableStream<T>; + +type AsyncStorageLike = { + getItem(key: string): Promise<string | null>; + setItem(key: string, value: string): Promise<void>; + removeItem(key: string): Promise<void>; +}; + +type Attachment = PendingAttachment | CompleteAttachment; + +type AttachmentAdapter = { + accept: string; + add(state: { + file: File; + }): Promise<PendingAttachment> | AsyncGenerator<PendingAttachment, void>; + remove(attachment: Attachment): Promise<void>; + send(attachment: PendingAttachment): Promise<CompleteAttachment>; +}; + +type AttachmentAddErrorEvent = { + readonly reason: AttachmentAddErrorReason; + readonly message: string; + readonly attachmentId?: string; + readonly error?: Error; +}; + +type AttachmentAddErrorReason = "adapter-error" | "no-adapter" | "not-accepted"; + +type AttachmentClientSchema = { + methods: AttachmentMethods; + meta: AttachmentMeta; +}; + +type AttachmentMeta = { + source: "composer" | "message"; + query: { + type: "index"; + index: number; + } | { + type: "id"; + id: string; + }; +}; + +type AttachmentMethods = { + getState(): AttachmentState; + remove(): Promise<void>; + __internal_getRuntime?(): AttachmentRuntime; +}; + +type AttachmentRuntime<TSource extends AttachmentRuntimeSource = AttachmentRuntimeSource> = { + readonly path: AttachmentRuntimePath & { + attachmentSource: TSource; + }; + readonly source: TSource; + getState(): AttachmentState$1 & { + source: TSource; + }; + remove(): Promise<void>; + subscribe(callback: () => void): Unsubscribe$1; +}; + +declare const AttachmentRuntimeClient: Resource<ClientOutput<"attachment">, [ + { + runtime: AttachmentRuntime; + } +]>; + +declare abstract class AttachmentRuntimeImpl<Source extends AttachmentRuntimeSource = AttachmentRuntimeSource> implements AttachmentRuntime { + private _core; + get path(): AttachmentRuntimePath & { + attachmentSource: Source; + }; + abstract get source(): Source; + constructor(_core: AttachmentSnapshotBinding<Source>); + protected __internal_bindMethods(): void; + getState(): AttachmentState$1 & { + source: Source; + }; + abstract remove(): Promise<void>; + subscribe(callback: () => void): Unsubscribe$1; +} + +type AttachmentRuntimePath = ((MessageRuntimePath & { + readonly attachmentSource: "edit-composer" | "message"; +}) | (ThreadRuntimePath & { + readonly attachmentSource: "thread-composer"; +})) & { + readonly attachmentSelector: { + readonly type: "index"; + readonly index: number; + } | { + readonly type: "index"; + readonly index: number; + } | { + readonly type: "index"; + readonly index: number; + }; +}; + +type AttachmentRuntimeSource = AttachmentState$1["source"]; + +type AttachmentSnapshotBinding<Source extends AttachmentRuntimeSource> = SubscribableWithState<AttachmentState$1 & { + source: Source; +}, AttachmentRuntimePath & { + attachmentSource: Source; +}>; + +type AttachmentState = Attachment; + +type AttachmentState$1 = ThreadComposerAttachmentState | EditComposerAttachmentState | MessageAttachmentState; + +type AttachmentStatus = PendingAttachmentStatus | CompleteAttachmentStatus; + +type AuiToolOverride<TArgs extends Record<string, unknown> = Record<string, unknown>, TResult = unknown> = Partial<Tool<TArgs, TResult>>; + +type AuiToolOverrides = Record<string, AuiToolOverride<any, any>>; + +type BackendTool<TArgs extends Record<string, unknown> = Record<string, unknown>, TResult = unknown> = ToolBase<TArgs, TResult> & { + type: "backend"; + description?: undefined; + parameters?: undefined; + disabled?: undefined; + execute?: undefined; + toModelOutput?: undefined; + experimental_onSchemaValidationError?: undefined; + providerOptions?: undefined; +}; + +type BackendToolDeclaration<TArgs extends Record<string, unknown> = Record<string, unknown>, TResult = unknown> = ToolBase<TArgs, TResult> & { + type: "backend"; + description?: string | undefined; + parameters?: StandardSchemaV1<TArgs> | JSONSchema7 | undefined; + disabled?: boolean; + execute?: ToolExecuteFunction<TArgs, TResult>; + toModelOutput?: ToolModelOutputFunction<TArgs, TResult>; + experimental_onSchemaValidationError?: OnSchemaValidationErrorFunction<TResult>; + providerOptions?: ProviderOptions; +}; + +declare abstract class BaseAssistantRuntimeCore implements AssistantRuntimeCore { + protected readonly _contextProvider: CompositeContextProvider; + abstract get threads(): ThreadListRuntimeCore; + registerModelContextProvider(provider: ModelContextProvider): Unsubscribe$1; + getModelContextProvider(): ModelContextProvider; +} + +type BaseAttachment = { + id: string; + type: "image" | "document" | "file" | (string & {}); + name: string; + contentType?: string | undefined; + file?: File; + content?: ThreadUserMessagePart[]; +}; + +declare abstract class BaseComposerRuntimeCore extends BaseSubscribable implements ComposerRuntimeCore { + readonly isEditing = true; + protected abstract getAttachmentAdapter(): AttachmentAdapter | undefined; + protected abstract getDictationAdapter(): DictationAdapter | undefined; + protected enrichWithComposerMetadata<T extends { + metadata?: { + custom?: Record<string, unknown>; + }; + }>(message: T, composerMetadata: Record<string, unknown> | undefined): T; + get attachmentAccept(): string; + private _attachments; + get attachments(): readonly Attachment[]; + protected setAttachments(value: readonly Attachment[]): void; + abstract get canCancel(): boolean; + abstract get canSend(): boolean; + get isEmpty(): boolean; + private _text; + get text(): string; + private _role; + get role(): "assistant" | "system" | "user"; + private _runConfig; + get runConfig(): RunConfig; + private _quote; + get quote(): QuoteInfo | undefined; + setQuote(quote: QuoteInfo | undefined): void; + setText(value: string): void; + setRole(role: MessageRole): void; + setRunConfig(runConfig: RunConfig): void; + private _emptyTextAndAttachments; + private _onClearAttachments; + reset(): Promise<void>; + clearAttachments(): Promise<void>; + send(options?: SendOptions): Promise<void>; + cancel(): void; + get queue(): readonly QueueItemState[]; + steerQueueItem(_queueItemId: string): void; + removeQueueItem(_queueItemId: string): void; + protected abstract handleSend(message: Omit<AppendMessage, "parentId" | "sourceId">, options?: SendOptions): void; + protected abstract handleCancel(): void; + addAttachment(fileOrAttachment: File | CreateAttachment): Promise<void>; + private _safeEmitAttachmentAddError; + removeAttachment(attachmentId: string): Promise<void>; + private _dictation; + private _dictationSession; + private _dictationUnsubscribes; + private _dictationBaseText; + private _currentInterimText; + private _dictationSessionIdCounter; + private _activeDictationSessionId; + private _isCleaningDictation; + get dictation(): DictationState | undefined; + private _isActiveSession; + startDictation(): void; + stopDictation(): void; + private _cleanupDictation; + private _eventSubscribers; + protected _notifyEventSubscribers<E extends ComposerRuntimeEventType>(event: E, payload: ComposerRuntimeEventPayload[E]): void; + unstable_on<E extends ComposerRuntimeEventType>(event: E, callback: ComposerRuntimeEventCallback<E>): () => void; +} + +type BaseComposerState = { + readonly canCancel: boolean; + readonly canSend: boolean; + readonly isEditing: boolean; + readonly isEmpty: boolean; + readonly text: string; + readonly role: MessageRole; + readonly attachments: readonly Attachment[]; + readonly runConfig: RunConfig; + readonly attachmentAccept: string; + readonly dictation: DictationState | undefined; + readonly quote: QuoteInfo | undefined; + readonly queue: readonly QueueItemState[]; +}; + +declare abstract class BaseSubject { + private _subscriptions; + private _connection; + protected get isConnected(): boolean; + protected abstract _connect(): Unsubscribe$1; + protected notifySubscribers(payload?: unknown): void; + private _updateConnection; + subscribe(callback: (payload?: unknown) => void): () => void; +} + +declare class BaseSubscribable { + private _subscribers; + subscribe(callback: () => void): Unsubscribe$1; + waitForUpdate(): Promise<void>; + protected _notifySubscribers(): void; +} + +type BaseThreadAdapters = { + speech?: SpeechSynthesisAdapter | undefined; + feedback?: FeedbackAdapter | undefined; + attachments?: AttachmentAdapter | undefined; + voice?: RealtimeVoiceAdapter | undefined; +}; + +type BaseThreadMessage = { + readonly status?: ThreadAssistantMessage["status"]; + readonly metadata: { + readonly unstable_state?: ReadonlyJSONValue; + readonly unstable_annotations?: readonly ReadonlyJSONValue[]; + readonly unstable_data?: readonly ReadonlyJSONValue[]; + readonly steps?: readonly ThreadStep[]; + readonly submittedFeedback?: { + readonly type: "negative" | "positive"; + }; + readonly timing?: MessageTiming; + readonly isOptimistic?: boolean; + readonly custom: Record<string, unknown>; + }; + readonly attachments?: ThreadUserMessage["attachments"]; +}; + +declare abstract class BaseThreadRuntimeCore implements ThreadRuntimeCore { + private readonly _contextProvider; + private _subscriptions; + private _isInitialized; + protected readonly repository: MessageRepository; + abstract get adapters(): BaseThreadAdapters | undefined; + abstract get isDisabled(): boolean; + abstract get isSendDisabled(): boolean; + abstract get isLoading(): boolean; + abstract get suggestions(): readonly ThreadSuggestion[]; + abstract get extras(): unknown; + abstract get capabilities(): RuntimeCapabilities; + abstract append(message: AppendMessage): void; + abstract deleteMessage(messageId: string): void | Promise<void>; + abstract startRun(config: StartRunConfig): void; + abstract resumeRun(config: ResumeRunConfig): void; + abstract addToolResult(options: AddToolResultOptions): void; + abstract resumeToolCall(options: ResumeToolCallOptions): void; + abstract respondToToolApproval(options: RespondToToolApprovalOptions): void; + abstract cancelRun(): void; + abstract exportExternalState(): any; + abstract importExternalState(state: any): void; + protected _voiceMessages: ThreadMessage[]; + protected _voiceGeneration: number; + private _cachedMergedMessages; + private _cachedVoiceGeneration; + private _cachedMergedBase; + protected _markVoiceMessagesDirty(): void; + protected _getBaseMessages(): readonly ThreadMessage[]; + get messages(): readonly ThreadMessage[]; + get state(): string | number | boolean | ReadonlyJSONObject | ReadonlyJSONArray | null; + readonly composer: DefaultThreadComposerRuntimeCore; + constructor(_contextProvider: ModelContextProvider); + getModelContext(): ModelContext$1; + private _editComposers; + getEditComposer(messageId: string): DefaultEditComposerRuntimeCore | undefined; + beginEdit(messageId: string): void; + getMessageById(messageId: string): { + parentId: string | null; + message: ThreadMessage; + index: number; + } | undefined; + getBranches(messageId: string): string[]; + switchToBranch(branchId: string): void; + protected _notifySubscribers(): void; + _notifyEventSubscribers<E extends ThreadRuntimeEventType>(event: E, payload: ThreadRuntimeEventPayload[E]): void; + subscribe(callback: () => void): Unsubscribe$1; + submitFeedback(_param1: SubmitFeedbackOptions): void; + private _stopSpeaking; + speech: SpeechState | undefined; + speak(messageId: string): void; + stopSpeaking(): void; + private _voiceSession; + private _voiceUnsubs; + voice: VoiceSessionState | undefined; + private _voiceVolume; + private _voiceVolumeSubscribers; + getVoiceVolume: () => number; + subscribeVoiceVolume: (callback: () => void) => Unsubscribe$1; + connectVoice(): void; + private _currentAssistantMsg; + private _handleVoiceTranscript; + private _finishVoiceAssistantMessage; + disconnectVoice(): void; + muteVoice(): void; + unmuteVoice(): void; + protected ensureInitialized(): void; + export(): ExportedMessageRepository; + import(data: ExportedMessageRepository): void; + reset(initialMessages?: readonly ThreadMessageLike[]): void; + private _eventSubscribers; + unstable_on<E extends ThreadRuntimeEventType>(event: E, callback: ThreadRuntimeEventCallback<E>): Unsubscribe$1; +} + +declare const ChainOfThoughtByIndicesProvider: FC<PropsWithChildren<{ + startIndex: number; + endIndex: number; +}>>; + +declare const ChainOfThoughtClient: Resource<ClientOutput<"chainOfThought">, [ + { + parts: readonly ChainOfThoughtPart[]; + getMessagePart: (selector: { + index: number; + }) => PartMethods; + } +]>; + +type ChainOfThoughtClientSchema = { + methods: ChainOfThoughtMethods; + meta: ChainOfThoughtMeta; +}; + +type ChainOfThoughtMeta = { + source: "message"; + query: { + type: "chainOfThought"; + }; +}; + +type ChainOfThoughtMethods = { + getState(): ChainOfThoughtState; + setCollapsed(collapsed: boolean): void; + part(selector: { + index: number; + }): PartMethods; +}; + +type ChainOfThoughtPart = Extract<PartState, { + type: "tool-call"; +} | { + type: "reasoning"; +}>; + +declare const ChainOfThoughtPartByIndexProvider: FC<PropsWithChildren<{ + index: number; +}>>; + +type ChainOfThoughtPartsComponentConfig = { + Reasoning?: ReasoningMessagePartComponent | undefined; + tools?: { + Fallback?: ToolCallMessagePartComponent | undefined; + }; + Layout?: ComponentType<PropsWithChildren> | undefined; +}; + +declare namespace ChainOfThoughtPrimitiveParts { + type Props = { + components?: ChainOfThoughtPartsComponentConfig; + children?: never; + } | { + children: (value: { + part: PartState; + }) => ReactNode; + components?: never; + }; +} + +declare const ChainOfThoughtPrimitiveParts: FC<ChainOfThoughtPrimitiveParts.Props>; + +type ChainOfThoughtState = { + readonly parts: readonly ChainOfThoughtPart[]; + readonly collapsed: boolean; + readonly status: MessagePartStatus | ToolCallMessagePartStatus; +}; + +type ChatModelAdapter = { + run(options: ChatModelRunOptions): Promise<ChatModelRunResult> | AsyncGenerator<ChatModelRunResult, void>; +}; + +type ChatModelRunOptions = { + readonly messages: readonly ThreadMessage[]; + readonly runConfig: RunConfig; + readonly abortSignal: AbortSignal; + readonly context: ModelContext$1; + readonly unstable_assistantMessageId?: string | undefined; + readonly unstable_threadId?: string | undefined; + readonly unstable_parentId?: string | null | undefined; + unstable_getMessage(): ThreadMessage; +}; + +type ChatModelRunResult = { + readonly content?: readonly ThreadAssistantMessagePart[] | undefined; + readonly status?: MessageStatus | undefined; + readonly metadata?: { + readonly unstable_state?: ReadonlyJSONValue; + readonly unstable_annotations?: readonly ReadonlyJSONValue[] | undefined; + readonly unstable_data?: readonly ReadonlyJSONValue[] | undefined; + readonly steps?: readonly ThreadStep[] | undefined; + readonly timing?: MessageTiming | undefined; + readonly custom?: Record<string, unknown> | undefined; + }; +}; + +type ChatModelRunUpdate = { + readonly content: readonly ThreadAssistantMessagePart[]; + readonly metadata?: Record<string, unknown>; +}; + +type ClientElement<K extends ClientNames> = ResourceElement<ClientOutput<K>>; + +type ClientError<E extends string> = { + methods: Record<E, () => E>; + meta: { + source: ClientNames; + query: Record<E, E>; + }; + events: Record<`${E}.`, E>; +}; + +type ClientEventMap = UnionToIntersection<{ + [K in ClientNames]: ClientEvents<K>; +}[ClientNames]>; + +type ClientEvents<K extends ClientNames> = "events" extends keyof ClientSchemas[K] ? ClientSchemas[K]["events"] extends ClientEventsType<K> ? ClientSchemas[K]["events"] : never : never; + +type ClientEventsType<K extends ClientNames> = Record<`${K}.${string}`, unknown>; + +type ClientMeta<K extends ClientNames> = "meta" extends keyof ClientSchemas[K] ? Pick<ClientSchemas[K]["meta"] extends ClientMetaType ? ClientSchemas[K]["meta"] : never, "query" | "source"> : never; + +type ClientMetaType = { + source: ClientNames; + query: Record<string, unknown>; +}; + +interface ClientMethods { + [key: string | symbol]: (...args: any[]) => any; +} + +type ClientNames = keyof ClientSchemas extends (infer U) ? U : never; + +type ClientOutput<K extends ClientNames> = ClientSchemas[K]["methods"] & ClientMethods; + +type ClientSchemas = keyof ScopeRegistry extends never ? { + "ERROR: No clients were defined": ClientError<"ERROR: No clients were defined">; +} : { + [K in keyof ScopeRegistry]: ValidateClient<K>; +}; + +declare class CloudFileAttachmentAdapter implements AttachmentAdapter { + private cloud; + accept: string; + constructor(cloud: AssistantCloud); + private uploadedUrls; + add(_param2: { + file: File; + }): AsyncGenerator<PendingAttachment, void>; + remove(attachment: Attachment): Promise<void>; + send(attachment: PendingAttachment): Promise<CompleteAttachment>; +} + +type CloudMessage = { + id: string; + parent_id: string | null; + height: number; + created_at: Date; + updated_at: Date; + format: "aui/v0" | string; + content: ReadonlyJSONObject; +}; + +type CloudThread = { + title: string; + last_message_at: Date; + metadata: unknown; + external_id: string | null; + id: string; + project_id: string; + created_at: Date; + updated_at: Date; + workspace_id: string; + is_archived: boolean; +}; + +type CloudThreadListAdapterOptions = { + cloud?: AssistantCloud | undefined; + create?: (() => Promise<ThreadData>) | undefined; + delete?: ((threadId: string) => Promise<void>) | undefined; +}; + +type CompleteAttachment = BaseAttachment & { + status: CompleteAttachmentStatus; + content: ThreadUserMessagePart[]; +}; + +type CompleteAttachmentStatus = { + type: "complete"; +}; + +declare const ComposerAttachmentByIndexProvider: FC<PropsWithChildren<{ + index: number; +}>>; + +declare abstract class ComposerAttachmentRuntime<Source extends "edit-composer" | "thread-composer"> extends AttachmentRuntimeImpl<Source> { + private _composerApi; + constructor(core: AttachmentSnapshotBinding<Source>, _composerApi: ComposerRuntimeCoreBinding); + remove(): Promise<void>; +} + +type ComposerAttachmentsComponentConfig = { + Image?: ComponentType | undefined; + Document?: ComponentType | undefined; + File?: ComponentType | undefined; + Attachment?: ComponentType | undefined; +}; + +declare const ComposerClient: Resource<ClientOutput<"composer">, [ + { + threadIdRef: { + current: string; + }; + messageIdRef?: { + current: string; + }; + runtime: ComposerRuntime; + } +]>; + +type ComposerClientSchema = { + methods: ComposerMethods; + meta: ComposerMeta; + events: ComposerEvents; +}; + +type ComposerEvents = { + "composer.send": { + threadId: string; + messageId?: string; + }; + "composer.attachmentAdd": { + threadId: string; + messageId?: string; + }; + "composer.attachmentAddError": { + threadId: string; + messageId?: string; + attachmentId?: string; + reason: AttachmentAddErrorReason; + message: string; + }; +}; + +type ComposerIfFilters = { + editing: boolean | undefined; + dictation: boolean | undefined; +}; + +type ComposerMeta = { + source: "message" | "thread"; + query: Record<string, never>; +}; + +type ComposerMethods = { + getState(): ComposerState; + setText(text: string): void; + setRole(role: MessageRole): void; + setRunConfig(runConfig: RunConfig): void; + addAttachment(fileOrAttachment: File | CreateAttachment): Promise<void>; + clearAttachments(): Promise<void>; + attachment(selector: { + index: number; + } | { + id: string; + }): AttachmentMethods; + reset(): Promise<void>; + send(opts?: ComposerSendOptions): void; + cancel(): void; + beginEdit(): void; + startDictation(): void; + stopDictation(): void; + setQuote(quote: QuoteInfo | undefined): void; + queueItem(selector: { + index: number; + }): QueueItemMethods; + __internal_getRuntime?(): ComposerRuntime; +}; + +declare namespace ComposerPrimitiveAttachmentByIndex { + type Props = { + index: number; + components?: ComposerAttachmentsComponentConfig; + }; +} + +declare const ComposerPrimitiveAttachmentByIndex: FC<ComposerPrimitiveAttachmentByIndex.Props>; + +declare namespace ComposerPrimitiveAttachments { + type Props = { + components: ComposerAttachmentsComponentConfig; + children?: never; + } | { + children: (value: { + attachment: Attachment; + }) => ReactNode; + components?: never; + }; +} + +declare const ComposerPrimitiveAttachments: FC<ComposerPrimitiveAttachments.Props>; + +declare namespace ComposerPrimitiveIf { + type Props = PropsWithChildren<UseComposerIfProps>; +} + +declare const ComposerPrimitiveIf: FC<ComposerPrimitiveIf.Props>; + +declare namespace ComposerPrimitiveQueue { + type Props = { + children: (value: { + queueItem: QueueItemState; + }) => ReactNode; + }; +} + +declare const ComposerPrimitiveQueue: import("react").NamedExoticComponent<{ + children: (value: { + queueItem: QueueItemState; + }) => ReactNode; +}>; + +type ComposerRuntime = { + readonly path: ComposerRuntimePath; + readonly type: "edit" | "thread"; + getState(): ComposerState$1; + addAttachment(fileOrAttachment: File | CreateAttachment): Promise<void>; + setText(text: string): void; + setRole(role: MessageRole): void; + setRunConfig(runConfig: RunConfig): void; + reset(): Promise<void>; + clearAttachments(): Promise<void>; + send(options?: SendOptions): void; + cancel(): void; + steerQueueItem(queueItemId: string): void; + removeQueueItem(queueItemId: string): void; + subscribe(callback: () => void): Unsubscribe$1; + getAttachmentByIndex(idx: number): AttachmentRuntime; + startDictation(): void; + stopDictation(): void; + setQuote(quote: QuoteInfo | undefined): void; + unstable_on<E extends ComposerRuntimeEventType>(event: E, callback: ComposerRuntimeEventCallback<E>): Unsubscribe$1; +}; + +type ComposerRuntimeCore = Readonly<{ + isEditing: boolean; + canCancel: boolean; + canSend: boolean; + isEmpty: boolean; + attachments: readonly Attachment[]; + attachmentAccept: string; + addAttachment: (fileOrAttachment: File | CreateAttachment) => Promise<void>; + removeAttachment: (attachmentId: string) => Promise<void>; + text: string; + setText: (value: string) => void; + role: MessageRole; + setRole: (role: MessageRole) => void; + runConfig: RunConfig; + setRunConfig: (runConfig: RunConfig) => void; + quote: QuoteInfo | undefined; + setQuote: (quote: QuoteInfo | undefined) => void; + reset: () => Promise<void>; + clearAttachments: () => Promise<void>; + send: (options?: SendOptions) => void; + cancel: () => void; + queue: readonly QueueItemState[]; + steerQueueItem: (queueItemId: string) => void; + removeQueueItem: (queueItemId: string) => void; + dictation: DictationState | undefined; + startDictation: () => void; + stopDictation: () => void; + subscribe: (callback: () => void) => Unsubscribe$1; + unstable_on: <E extends ComposerRuntimeEventType>(event: E, callback: ComposerRuntimeEventCallback<E>) => Unsubscribe$1; +}>; + +type ComposerRuntimeCoreBinding = SubscribableWithState<ComposerRuntimeCore | undefined, ComposerRuntimePath>; + +type ComposerRuntimeEventCallback<E extends ComposerRuntimeEventType> = (payload: ComposerRuntimeEventPayload[E]) => void; + +type ComposerRuntimeEventPayload = { + send: Record<string, never>; + attachmentAdd: Record<string, never>; + attachmentAddError: AttachmentAddErrorEvent; +}; + +type ComposerRuntimeEventType = keyof ComposerRuntimeEventPayload; + +declare abstract class ComposerRuntimeImpl implements ComposerRuntime { + protected _core: ComposerRuntimeCoreBinding; + get path(): ComposerRuntimePath; + abstract get type(): "edit" | "thread"; + constructor(_core: ComposerRuntimeCoreBinding); + protected __internal_bindMethods(): void; + abstract getState(): ComposerState$1; + setText(text: string): void; + setRunConfig(runConfig: RunConfig): void; + addAttachment(fileOrAttachment: File | CreateAttachment): Promise<void>; + reset(): Promise<void>; + clearAttachments(): Promise<void>; + send(options?: SendOptions): void; + cancel(): void; + steerQueueItem(queueItemId: string): void; + removeQueueItem(queueItemId: string): void; + setRole(role: MessageRole): void; + startDictation(): void; + stopDictation(): void; + setQuote(quote: QuoteInfo | undefined): void; + subscribe(callback: () => void): Unsubscribe$1; + private _eventSubscriptionSubjects; + unstable_on<E extends ComposerRuntimeEventType>(event: E, callback: ComposerRuntimeEventCallback<E>): Unsubscribe$1; + abstract getAttachmentByIndex(idx: number): AttachmentRuntime; +} + +type ComposerRuntimePath = (ThreadRuntimePath & { + readonly composerSource: "thread"; +}) | (MessageRuntimePath & { + readonly composerSource: "edit"; +}); + +type ComposerSendOptions = SendOptions & { + steer?: boolean; +}; + +type ComposerState = { + readonly text: string; + readonly role: MessageRole; + readonly attachments: readonly Attachment[]; + readonly runConfig: RunConfig; + readonly isEditing: boolean; + readonly canCancel: boolean; + readonly canSend: boolean; + readonly attachmentAccept: string; + readonly isEmpty: boolean; + readonly type: "edit" | "thread"; + readonly dictation: DictationState | undefined; + readonly quote: QuoteInfo | undefined; + readonly queue: readonly QueueItemState[]; +}; + +type ComposerState$1 = ThreadComposerState | EditComposerState; + +declare class CompositeAttachmentAdapter implements AttachmentAdapter { + private _adapters; + accept: string; + constructor(adapters: AttachmentAdapter[]); + add(state: { + file: File; + }): Promise<PendingAttachment> | AsyncGenerator<PendingAttachment, void, any>; + send(attachment: PendingAttachment): Promise<CompleteAttachment>; + remove(attachment: Attachment): Promise<void>; +} + +declare class CompositeContextProvider implements ModelContextProvider { + private _providers; + getModelContext(): ModelContext$1; + registerModelContextProvider(provider: ModelContextProvider): () => void; + private _subscribers; + notifySubscribers(): void; + subscribe(callback: () => void): () => void; +} + +type ConverterCallback<TIn> = (cache: ThreadMessage | undefined, message: TIn, idx: number) => ThreadMessage; + +type CoreChatModelRunResult = Omit<ChatModelRunResult, "content"> & { + readonly content: readonly (TextMessagePart | ReasoningMessagePart | ToolCallMessagePart | SourceMessagePart | FileMessagePart)[]; +}; + +type CreateAppendMessage = string | { + parentId?: string | null | undefined; + sourceId?: string | null | undefined; + role?: AppendMessage["role"] | undefined; + content: AppendMessage["content"]; + attachments?: AppendMessage["attachments"] | undefined; + metadata?: AppendMessage["metadata"] | undefined; + createdAt?: Date | undefined; + runConfig?: AppendMessage["runConfig"] | undefined; + startRun?: boolean | undefined; +}; + +type CreateAttachment = { + id?: string; + type?: "image" | "document" | "file" | (string & {}); + name: string; + contentType?: string; + content: ThreadUserMessagePart[]; +}; + +type CreateResumeRunConfig = CreateStartRunConfig & { + stream?: (options: ChatModelRunOptions) => AsyncGenerator<ChatModelRunResult, void, unknown>; +}; + +type CreateStartRunConfig = { + parentId: string | null; + sourceId?: string | null | undefined; + runConfig?: RunConfig | undefined; +}; + +type DataMessagePart<T = any> = { + readonly type: "data"; + readonly name: string; + readonly data: T; +}; + +type DataMessagePartComponent<T = any> = ComponentType<DataMessagePartProps<T>>; + +type DataMessagePartProps<T = any> = MessagePartState & DataMessagePart<T>; + +type DataPrefixedPart = { + readonly type: `data-${string}`; + readonly data: any; +}; + +declare const DataRenderers: Resource<ClientOutput<"dataRenderers">, [ +]>; + +type DataRenderersClientSchema = { + methods: DataRenderersMethods; +}; + +type DataRenderersMethods = { + getState(): DataRenderersState; + setDataUI(name: string, render: DataMessagePartComponent): Unsubscribe$1; + setFallbackDataUI(render: DataMessagePartComponent): Unsubscribe$1; +}; + +type DataRenderersState = { + renderers: Record<string, DataMessagePartComponent[]>; + fallbacks: DataMessagePartComponent[]; +}; + +type DeepPartial<T> = T extends readonly any[] ? readonly DeepPartial<T[number]>[] : T extends { + [key: string]: any; +} ? { + readonly [K in keyof T]?: DeepPartial<T[K]>; +} : T; + +declare class DefaultEditComposerRuntimeCore extends BaseComposerRuntimeCore { + private runtime; + private endEditCallback; + get canCancel(): boolean; + get canSend(): boolean; + protected getAttachmentAdapter(): AttachmentAdapter | undefined; + protected getDictationAdapter(): DictationAdapter | undefined; + private _previousText; + private _previousAttachments; + private _nonTextPassthrough; + private _parentId; + private _sourceId; + constructor(runtime: ThreadRuntimeCore & { + adapters?: { + attachments?: AttachmentAdapter | undefined; + dictation?: DictationAdapter | undefined; + } | undefined; + }, endEditCallback: () => void, _param3: { + parentId: string | null; + message: ThreadMessage; + }); + get parentId(): string | null; + get sourceId(): string | null; + handleSend(message: Omit<AppendMessage, "parentId" | "sourceId">, options?: SendOptions): Promise<void>; + handleCancel(): void; +} + +declare class DefaultThreadComposerRuntimeCore extends BaseComposerRuntimeCore implements ThreadComposerRuntimeCore { + private runtime; + private _canCancel; + get canCancel(): boolean; + get canSend(): boolean; + get queue(): readonly QueueItemState[]; + steerQueueItem(queueItemId: string): void; + removeQueueItem(queueItemId: string): void; + protected getAttachmentAdapter(): AttachmentAdapter | undefined; + protected getDictationAdapter(): DictationAdapter | undefined; + constructor(runtime: Omit<ThreadRuntimeCore, "composer"> & { + adapters?: { + attachments?: AttachmentAdapter | undefined; + dictation?: DictationAdapter | undefined; + } | undefined; + }); + connect(): Unsubscribe$1; + handleSend(message: Omit<AppendMessage, "parentId" | "sourceId">, options?: SendOptions): Promise<void>; + handleCancel(): Promise<void>; +} + +declare const Derived: <K extends ClientNames>(_config: Derived.Props<K>) => ResourceElement<null, [ + _config: Derived.Props<K> +]>; + +declare namespace Derived { + type Props<K extends ClientNames> = { + get: (client: AssistantClient) => ReturnType<AssistantClientAccessor<K>>; + } & ClientMeta<K>; +} + +type DerivedElement<K extends ClientNames> = ResourceElement<null, [ + Derived.Props<K> +]>; + +declare namespace DictationAdapter { + type Status = { + type: "running" | "starting"; + } | { + type: "ended"; + reason: "cancelled" | "error" | "stopped"; + }; + type Result = { + transcript: string; + isFinal?: boolean; + }; + type Session = { + status: Status; + stop: () => Promise<void>; + cancel: () => void; + onSpeechStart: (callback: () => void) => Unsubscribe$1; + onSpeechEnd: (callback: (result: Result) => void) => Unsubscribe$1; + onSpeech: (callback: (result: Result) => void) => Unsubscribe$1; + }; +} + +type DictationAdapter = { + listen: () => DictationAdapter.Session; + disableInputDuringDictation?: boolean; +}; + +type DictationState = { + readonly status: DictationAdapter.Status; + readonly transcript?: string; + readonly inputDisabled?: boolean; +}; + +declare const EMPTY_THREAD_CORE: ThreadRuntimeCore; + +declare class EditComposerAttachmentRuntimeImpl extends ComposerAttachmentRuntime<"edit-composer"> { + get source(): "edit-composer"; +} + +type EditComposerAttachmentState = Attachment & { + readonly source: "edit-composer"; +}; + +type EditComposerRuntime = Omit<ComposerRuntime, "getAttachmentByIndex" | "getState"> & { + readonly path: ComposerRuntimePath & { + composerSource: "edit"; + }; + readonly type: "edit"; + getState(): EditComposerState; + beginEdit(): void; + getAttachmentByIndex(idx: number): AttachmentRuntime & { + source: "edit-composer"; + }; +}; + +type EditComposerRuntimeCore = ComposerRuntimeCore & Readonly<{ + parentId: string | null; + sourceId: string | null; +}>; + +type EditComposerRuntimeCoreBinding = SubscribableWithState<EditComposerRuntimeCore | undefined, ComposerRuntimePath & { + composerSource: "edit"; +}>; + +declare class EditComposerRuntimeImpl extends ComposerRuntimeImpl implements EditComposerRuntime { + private _beginEdit; + get path(): ComposerRuntimePath & { + composerSource: "edit"; + }; + get type(): "edit"; + private _getState; + constructor(core: EditComposerRuntimeCoreBinding, _beginEdit: () => void); + __internal_bindMethods(): void; + getState(): EditComposerState; + beginEdit(): void; + getAttachmentByIndex(idx: number): EditComposerAttachmentRuntimeImpl; +} + +type EditComposerState = BaseComposerState & { + readonly type: "edit"; + readonly parentId: string | null; + readonly sourceId: string | null; +}; + +type EmptyMessagePartComponent = ComponentType<EmptyMessagePartProps>; + +type EmptyMessagePartProps = { + status: MessagePartStatus; +}; + +type EnrichedPartState = (Extract<PartState, { + type: "tool-call"; +}> & { + readonly toolUI: ReactNode; + addResult: ToolCallMessagePartProps["addResult"]; + resume: ToolCallMessagePartProps["resume"]; + respondToApproval: ToolCallMessagePartProps["respondToApproval"]; +}) | (Extract<PartState, { + type: "data"; +}> & { + readonly dataRendererUI: ReactNode; +}) | Exclude<PartState, { + type: "tool-call"; +} | { + type: "data"; +}>; + +type EventSource<T extends AssistantEventName> = T extends `${infer Source}.${string}` ? Source : never; + +type EventSubscribable<TEvent extends string> = { + event: TEvent; + binding: SubscribableWithState<{ + unstable_on: (event: TEvent, callback: (payload?: unknown) => void) => Unsubscribe$1; + } | undefined, unknown>; +}; + +declare class EventSubscriptionSubject<TEvent extends string> extends BaseSubject { + private config; + constructor(config: EventSubscribable<TEvent>); + getState(): { + unstable_on: (event: TEvent, callback: (payload?: unknown) => void) => Unsubscribe$1; + } | undefined; + outerSubscribe(callback: () => void): Unsubscribe$1; + protected _connect(): Unsubscribe$1; +} + +type ExportedMessageRepository = { + headId?: string | null; + messages: Array<{ + message: ThreadMessage; + parentId: string | null; + runConfig?: RunConfig; + }>; +}; + +declare const ExportedMessageRepository: { + fromArray: (messages: readonly ThreadMessageLike[]) => ExportedMessageRepository; + fromBranchableArray: (items: readonly { + message: ThreadMessageLike; + parentId: string | null; + }[], options?: { + headId?: string | null; + }) => ExportedMessageRepository; +}; + +type ExportedMessageRepositoryItem = { + message: ThreadMessage; + parentId: string | null; + runConfig?: RunConfig; +}; + +type ExternalStoreAdapter<T = ThreadMessage> = ExternalStoreAdapterBase<T> & (T extends ThreadMessage ? object : ExternalStoreMessageConverterAdapter<T>); + +type ExternalStoreAdapterBase<T> = { + isDisabled?: boolean | undefined; + isSendDisabled?: boolean | undefined; + isRunning?: boolean | undefined; + isLoading?: boolean | undefined; + messages?: readonly T[]; + messageRepository?: ExportedMessageRepository; + suggestions?: readonly ThreadSuggestion[] | undefined; + state?: ReadonlyJSONValue | undefined; + extras?: unknown; + setMessages?: ((messages: readonly T[]) => void) | undefined; + unstable_onBranchChange?: ((event: ExternalStoreBranchChange) => void) | undefined; + onImport?: ((messages: readonly ThreadMessage[]) => void) | undefined; + onExportExternalState?: (() => any) | undefined; + onLoadExternalState?: ((state: any) => void) | undefined; + onNew: (message: AppendMessage) => Promise<void>; + queue?: ExternalThreadQueueAdapter | undefined; + onEdit?: ((message: AppendMessage) => Promise<void>) | undefined; + onDelete?: ((messageId: string) => Promise<void> | void) | undefined; + onReload?: ((parentId: string | null, config: StartRunConfig) => Promise<void>) | undefined; + onResume?: ((config: ResumeRunConfig) => Promise<void>) | undefined; + onCancel?: (() => Promise<void>) | undefined; + onAddToolResult?: ((options: AddToolResultOptions) => Promise<void> | void) | undefined; + onResumeToolCall?: ((options: { + toolCallId: string; + payload: unknown; + }) => void) | undefined; + onRespondToToolApproval?: ((options: RespondToToolApprovalOptions) => Promise<void> | void) | undefined; + convertMessage?: ExternalStoreMessageConverter<T> | undefined; + adapters?: { + attachments?: AttachmentAdapter | undefined; + speech?: SpeechSynthesisAdapter | undefined; + dictation?: DictationAdapter | undefined; + voice?: RealtimeVoiceAdapter | undefined; + feedback?: FeedbackAdapter | undefined; + threadList?: ExternalStoreThreadListAdapter | undefined; + } | undefined; + unstable_capabilities?: { + copy?: boolean | undefined; + } | undefined; + unstable_enableToolInvocations?: boolean | undefined; + setToolStatuses?: ((statuses: Record<string, ToolExecutionStatus>) => void) | undefined; +}; + +type ExternalStoreBranchChange = { + headId: string | null; + visibleMessageIds: readonly string[]; +}; + +type ExternalStoreMessageConverter<T> = (message: T, idx: number) => ThreadMessageLike; + +type ExternalStoreMessageConverterAdapter<T> = { + convertMessage: ExternalStoreMessageConverter<T>; +}; + +declare class ExternalStoreRuntimeCore extends BaseAssistantRuntimeCore { + readonly threads: ExternalStoreThreadListRuntimeCore; + constructor(adapter: ExternalStoreAdapter<any>); + setAdapter(adapter: ExternalStoreAdapter<any>): void; +} + +type ExternalStoreSharedOptions = Pick<ExternalStoreAdapter, "isDisabled" | "isSendDisabled" | "suggestions" | "unstable_capabilities">; + +type ExternalStoreThreadData<TState extends "archived" | "regular"> = { + status: TState; + id: string; + remoteId?: string | undefined; + externalId?: string | undefined; + title?: string | undefined; + custom?: Record<string, unknown> | undefined; +}; + +type ExternalStoreThreadFactory = () => ExternalStoreThreadRuntimeCore; + +type ExternalStoreThreadListAdapter = { + threadId?: string | undefined; + isLoading?: boolean | undefined; + threads?: readonly ExternalStoreThreadData<"regular">[] | undefined; + archivedThreads?: readonly ExternalStoreThreadData<"archived">[] | undefined; + onSwitchToNewThread?: (() => Promise<void> | void) | undefined; + onSwitchToThread?: ((threadId: string) => Promise<void> | void) | undefined; + onRename?: (threadId: string, newTitle: string) => (Promise<void> | void) | undefined; + onUpdateCustom?: ((threadId: string, custom: Record<string, unknown> | undefined) => Promise<void> | void) | undefined; + onArchive?: ((threadId: string) => Promise<void> | void) | undefined; + onUnarchive?: ((threadId: string) => Promise<void> | void) | undefined; + onDelete?: ((threadId: string) => Promise<void> | void) | undefined; +}; + +declare class ExternalStoreThreadListRuntimeCore implements ThreadListRuntimeCore { + private threadFactory; + private _mainThreadId; + private _threads; + private _archivedThreads; + private _threadData; + private adapter; + get isLoading(): boolean; + get newThreadId(): undefined; + get threadIds(): readonly string[]; + get archivedThreadIds(): readonly string[]; + get threadItems(): Readonly<Record<string, ThreadListItemCoreState>>; + getLoadThreadsPromise(): Promise<void>; + private _mainThread; + get mainThreadId(): string; + constructor(adapter: ExternalStoreThreadListAdapter | undefined, threadFactory: ExternalStoreThreadFactory); + getMainThreadRuntimeCore(): ExternalStoreThreadRuntimeCore; + getThreadRuntimeCore(): never; + getItemById(threadId: string): ThreadListItemCoreState | undefined; + __internal_setAdapter(adapter: ExternalStoreThreadListAdapter, initialLoad?: boolean): void; + switchToThread(threadId: string, _options?: { + unarchive?: boolean; + }): Promise<void>; + switchToNewThread(): Promise<void>; + rename(threadId: string, newTitle: string): Promise<void>; + updateCustom(threadId: string, custom: Record<string, unknown> | undefined): Promise<void>; + detach(): Promise<void>; + archive(threadId: string): Promise<void>; + unarchive(threadId: string): Promise<void>; + delete(threadId: string): Promise<void>; + initialize(threadId: string): Promise<{ + remoteId: string; + externalId: string | undefined; + }>; + generateTitle(): never; + private _subscriptions; + subscribe(callback: () => void): Unsubscribe$1; + private _notifySubscribers; +} + +declare class ExternalStoreThreadRuntimeCore extends BaseThreadRuntimeCore implements ThreadRuntimeCore { + private _capabilities; + get capabilities(): RuntimeCapabilities; + private _messages; + isDisabled: boolean; + isSendDisabled: boolean; + get isLoading(): boolean; + get isRunning(): boolean | undefined; + protected _getBaseMessages(): readonly ThreadMessage[]; + get state(): string | number | boolean | ReadonlyJSONObject | ReadonlyJSONArray | null; + get adapters(): { + attachments?: AttachmentAdapter | undefined; + speech?: SpeechSynthesisAdapter | undefined; + dictation?: DictationAdapter | undefined; + voice?: RealtimeVoiceAdapter | undefined; + feedback?: FeedbackAdapter | undefined; + threadList?: ExternalStoreThreadListAdapter | undefined; + } | undefined; + suggestions: readonly ThreadSuggestion[]; + extras: unknown; + private _converter; + private _store; + private _toolInvocations; + beginEdit(messageId: string): void; + constructor(contextProvider: ModelContextProvider, store: ExternalStoreAdapter<any>); + __internal_setAdapter(store: ExternalStoreAdapter<any>): void; + private _driveToolInvocations; + private _toolCallToMessageId; + private _messagesForToolCallIndex; + private _findMessageIdForToolCall; + switchToBranch(branchId: string): void; + private _notifyBranchChange; + append(message: AppendMessage): Promise<void>; + deleteMessage(messageId: string): Promise<void>; + getQueueItems(): readonly QueueItemState[]; + steerQueueItem(queueItemId: string): void; + removeQueueItem(queueItemId: string): void; + startRun(config: StartRunConfig): Promise<void>; + resumeRun(config: ResumeRunConfig): Promise<void>; + exportExternalState(): any; + importExternalState(state: any): void; + cancelRun(): void; + addToolResult(options: AddToolResultOptions): void; + resumeToolCall(options: ResumeToolCallOptions): void; + respondToToolApproval(options: RespondToToolApprovalOptions): void; + reset(initialMessages?: readonly ThreadMessageLike[]): void; + import(data: ExportedMessageRepository): void; + private updateMessages; +} + +type ExternalThreadBranchAdapter = { + getBranches: (messageId: string) => readonly string[]; + switchToBranch: (branchId: string) => void; +}; + +type ExternalThreadQueueAdapter = { + items: readonly QueueItemState[]; + enqueue: (message: AppendMessage, options: { + steer: boolean; + }) => void; + steer: (queueItemId: string) => void; + remove: (queueItemId: string) => void; + clear: (reason: "cancel-run" | "edit" | "reload") => void; +}; + +declare const FRAME_MESSAGE_CHANNEL = "assistant-ui-frame"; + +type FeedbackAdapter = { + submit: (feedback: FeedbackAdapterFeedback) => void; +}; + +type FeedbackAdapterFeedback = { + message: ThreadMessage; + type: "negative" | "positive"; +}; + +type FileMessagePart = { + readonly type: "file"; + readonly filename?: string; + readonly data: string; + readonly mimeType: string; + readonly parentId?: string; +}; + +type FileMessagePartComponent = ComponentType<FileMessagePartProps>; + +type FileMessagePartProps = MessagePartState & FileMessagePart; + +type FrameMessage = { + type: "model-context-request"; +} | { + type: "model-context-update"; + context: SerializedModelContext; +} | { + type: "tool-call"; + id: string; + toolName: string; + args: unknown; +} | { + type: "tool-result"; + id: string; + result?: unknown; + error?: string; +}; + +type FrameMessageType = "model-context-request" | "model-context-update" | "tool-call" | "tool-result"; + +type FrontendTool<TArgs extends Record<string, unknown> = Record<string, unknown>, TResult = unknown> = ToolBase<TArgs, TResult> & { + type: "frontend"; + description?: string | undefined; + parameters: StandardSchemaV1<TArgs> | JSONSchema7; + disabled?: boolean; + execute?: ToolExecuteFunction<TArgs, TResult>; + toModelOutput?: ToolModelOutputFunction<TArgs, TResult>; + experimental_onSchemaValidationError?: OnSchemaValidationErrorFunction<TResult>; + providerOptions?: ProviderOptions; +}; + +type GeneratePresignedUploadUrlRequestBody = { + filename: string; +}; + +type GeneratePresignedUploadUrlResponse = { + success: boolean; + signedUrl: string; + expiresAt: string; + publicUrl: string; +}; + +type GenerativeUIComponentRegistry = Record<string, ComponentType<any>>; + +type GenerativeUIMessagePart = { + readonly type: "generative-ui"; + readonly spec: GenerativeUISpec; + readonly id?: string; + readonly parentId?: string; +}; + +type GenerativeUIMessagePartComponent = ComponentType<GenerativeUIMessagePartProps>; + +type GenerativeUIMessagePartProps = MessagePartState & GenerativeUIMessagePart; + +type GenerativeUINode = string | { + readonly component: string; + readonly props?: Record<string, unknown>; + readonly children?: readonly GenerativeUINode[]; + readonly key?: string; +}; + +declare const GenerativeUIRender: FC<GenerativeUIRenderProps>; + +declare class GenerativeUIRenderError extends Error { + readonly componentName: string; + constructor(componentName: string, message?: string); +} + +type GenerativeUIRenderProps = { + spec: GenerativeUISpec; + components: GenerativeUIComponentRegistry; + Fallback?: ComponentType<{ + component: string; + props?: unknown; + }> | undefined; +}; + +type GenerativeUISpec = { + readonly root: GenerativeUINode | readonly GenerativeUINode[]; +}; + +type GenericThreadHistoryAdapter<TMessage> = { + load(): Promise<MessageFormatRepository<TMessage>>; + append(item: MessageFormatItem<TMessage>): Promise<void>; + update?(item: MessageFormatItem<TMessage>, localMessageId: string): Promise<void>; + delete?(items: MessageFormatItem<TMessage>[]): Promise<void>; + reportTelemetry?(items: MessageFormatItem<TMessage>[], options?: { + durationMs?: number; + stepTimestamps?: { + start_ms: number; + end_ms: number; + }[]; + }): void; +}; + +type GroupByContext = { + readonly toolUIs?: ToolsState["toolUIs"]; +}; + +type GroupPartType = PartState["type"] | "standalone-tool-call" | "mcp-app"; + +type HumanTool<TArgs extends Record<string, unknown> = Record<string, unknown>, TResult = unknown> = ToolBase<TArgs, TResult> & { + type: "human"; + description?: string | undefined; + parameters: StandardSchemaV1<TArgs> | JSONSchema7; + disabled?: boolean; + display?: "standalone"; + execute?: undefined; + toModelOutput?: undefined; + experimental_onSchemaValidationError?: undefined; + providerOptions?: ProviderOptions; +}; + +type ImageMessagePart = { + readonly type: "image"; + readonly image: string; + readonly filename?: string; +}; + +type ImageMessagePartComponent = ComponentType<ImageMessagePartProps>; + +type ImageMessagePartProps = MessagePartState & ImageMessagePart; + +declare class InMemoryThreadListAdapter implements RemoteThreadListAdapter { + list(): Promise<RemoteThreadListResponse>; + rename(): Promise<void>; + updateCustom(): Promise<void>; + archive(): Promise<void>; + unarchive(): Promise<void>; + delete(): Promise<void>; + initialize(threadId: string): Promise<RemoteThreadInitializeResponse>; + generateTitle(): Promise<AssistantStream>; + fetch(threadId: string): Promise<RemoteThreadMetadata>; +} + +type InteractableDefinition = { + id: string; + name: string; + description: string; + stateSchema: InteractableStateSchema; + state: unknown; + selected?: boolean | undefined; +}; + +type InteractablePersistedState = Record<string, { + name: string; + state: unknown; +}>; + +type InteractablePersistenceAdapter = { + save(state: InteractablePersistedState): void | Promise<void>; +}; + +type InteractablePersistenceStatus = { + isPending: boolean; + error: unknown; +}; + +type InteractableRegistration = { + id: string; + name: string; + description: string; + stateSchema: InteractableStateSchema; + initialState: unknown; + selected?: boolean | undefined; +}; + +type InteractableScope = "app" | "thread"; + +type InteractableStateSchema = NonNullable<Extract<Tool, { + parameters: unknown; +}>["parameters"]>; + +declare const Interactables: Resource<ClientOutput<"interactables">, [ +]>; + +type InteractablesClientSchema = { + methods: InteractablesMethods; +}; + +type InteractablesMethods = { + getState(): InteractablesState; + register(def: InteractableRegistration): Unsubscribe$1; + setState(id: string, updater: (prev: unknown) => unknown): void; + setSelected(id: string, selected: boolean): void; + exportState(): InteractablePersistedState; + importState(saved: InteractablePersistedState): void; + setPersistenceAdapter(adapter: InteractablePersistenceAdapter | undefined): void; + flush(): Promise<void>; +}; + +type InteractablesState = { + definitions: Record<string, InteractableDefinition>; + persistence: Record<string, InteractablePersistenceStatus>; +}; + +interface JSONSchema7 { + $id?: string | undefined; + $ref?: string | undefined; + $schema?: JSONSchema7Version | undefined; + $comment?: string | undefined; + $defs?: { + [key: string]: JSONSchema7Definition; + } | undefined; + type?: JSONSchema7TypeName | JSONSchema7TypeName[] | undefined; + enum?: JSONSchema7Type[] | undefined; + const?: JSONSchema7Type | undefined; + multipleOf?: number | undefined; + maximum?: number | undefined; + exclusiveMaximum?: number | undefined; + minimum?: number | undefined; + exclusiveMinimum?: number | undefined; + maxLength?: number | undefined; + minLength?: number | undefined; + pattern?: string | undefined; + items?: JSONSchema7Definition | JSONSchema7Definition[] | undefined; + additionalItems?: JSONSchema7Definition | undefined; + maxItems?: number | undefined; + minItems?: number | undefined; + uniqueItems?: boolean | undefined; + contains?: JSONSchema7Definition | undefined; + maxProperties?: number | undefined; + minProperties?: number | undefined; + required?: string[] | undefined; + properties?: { + [key: string]: JSONSchema7Definition; + } | undefined; + patternProperties?: { + [key: string]: JSONSchema7Definition; + } | undefined; + additionalProperties?: JSONSchema7Definition | undefined; + dependencies?: { + [key: string]: JSONSchema7Definition | string[]; + } | undefined; + propertyNames?: JSONSchema7Definition | undefined; + if?: JSONSchema7Definition | undefined; + then?: JSONSchema7Definition | undefined; + else?: JSONSchema7Definition | undefined; + allOf?: JSONSchema7Definition[] | undefined; + anyOf?: JSONSchema7Definition[] | undefined; + oneOf?: JSONSchema7Definition[] | undefined; + not?: JSONSchema7Definition | undefined; + format?: string | undefined; + contentMediaType?: string | undefined; + contentEncoding?: string | undefined; + definitions?: { + [key: string]: JSONSchema7Definition; + } | undefined; + title?: string | undefined; + description?: string | undefined; + default?: JSONSchema7Type | undefined; + readOnly?: boolean | undefined; + writeOnly?: boolean | undefined; + examples?: JSONSchema7Type | undefined; +} + +interface JSONSchema7Array extends Array<JSONSchema7Type> { +} + +type JSONSchema7Definition = JSONSchema7 | boolean; + +interface JSONSchema7Object { + [key: string]: JSONSchema7Type; +} + +type JSONSchema7Type = string | number | boolean | JSONSchema7Object | JSONSchema7Array | null; + +type JSONSchema7TypeName = "array" | "boolean" | "integer" | "null" | "number" | "object" | "string"; + +type JSONSchema7Version = string; + +type JoinStrategy = "concat-content" | "none"; + +type LanguageModelConfig = { + apiKey?: string; + baseUrl?: string; + modelName?: string; + reasoningEffort?: string; +}; + +type LanguageModelV1CallSettings = { + maxTokens?: number; + temperature?: number; + topP?: number; + presencePenalty?: number; + frequencyPenalty?: number; + seed?: number; + headers?: Record<string, string | undefined>; +}; + +declare class LazyMemoizeSubject<TState extends object, TPath> extends BaseSubject implements SubscribableWithState<TState, TPath> { + private binding; + get path(): TPath; + constructor(binding: SubscribableWithState<TState | SKIP_UPDATE, TPath>); + private _previousStateDirty; + private _previousState; + getState: () => TState; + protected _connect(): Unsubscribe$1; +} + +declare class LocalRuntimeCore extends BaseAssistantRuntimeCore { + readonly threads: LocalThreadListRuntimeCore; + readonly Provider: undefined; + private _options; + constructor(options: LocalRuntimeOptionsBase, initialMessages: readonly ThreadMessageLike[] | undefined); +} + +type LocalRuntimeOptions = Omit<LocalRuntimeOptionsBase, "adapters"> & { + cloud?: AssistantCloud | undefined; + initialMessages?: readonly ThreadMessageLike[] | undefined; + adapters?: Omit<LocalRuntimeOptionsBase["adapters"], "chatModel"> | undefined; +}; + +type LocalRuntimeOptionsBase = { + maxSteps?: number | undefined; + adapters: { + chatModel: ChatModelAdapter; + history?: ThreadHistoryAdapter | undefined; + attachments?: AttachmentAdapter | undefined; + speech?: SpeechSynthesisAdapter | undefined; + dictation?: DictationAdapter | undefined; + voice?: RealtimeVoiceAdapter | undefined; + feedback?: FeedbackAdapter | undefined; + suggestion?: SuggestionAdapter | undefined; + }; + unstable_humanToolNames?: string[] | undefined; + unstable_enableMessageQueue?: boolean | undefined; +}; + +type LocalStorageAdapterOptions = { + storage: AsyncStorageLike; + prefix?: string | undefined; + titleGenerator?: TitleGenerationAdapter | undefined; +}; + +type LocalThreadFactory = () => LocalThreadRuntimeCore; + +declare class LocalThreadListRuntimeCore extends BaseSubscribable implements ThreadListRuntimeCore { + private _mainThread; + constructor(_threadFactory: LocalThreadFactory); + get isLoading(): boolean; + getMainThreadRuntimeCore(): LocalThreadRuntimeCore; + get newThreadId(): string | undefined; + get threadIds(): readonly string[]; + get archivedThreadIds(): readonly string[]; + get mainThreadId(): string; + get threadItems(): Readonly<{ + __DEFAULT_ID__: { + id: string; + remoteId: undefined; + externalId: undefined; + status: "regular"; + title: undefined; + }; + }>; + getThreadRuntimeCore(): never; + getLoadThreadsPromise(): Promise<void>; + getItemById(threadId: string): { + status: "regular"; + id: string; + remoteId: string; + externalId: undefined; + title: undefined; + isMain: boolean; + }; + switchToThread(): Promise<void>; + switchToNewThread(): Promise<void>; + rename(): Promise<void>; + archive(): Promise<void>; + detach(): Promise<void>; + unarchive(): Promise<void>; + delete(): Promise<void>; + initialize(threadId: string): Promise<{ + remoteId: string; + externalId: string | undefined; + }>; + generateTitle(): never; +} + +declare class LocalThreadRuntimeCore extends BaseThreadRuntimeCore implements ThreadRuntimeCore { + readonly capabilities: { + switchToBranch: boolean; + switchBranchDuringRun: boolean; + edit: boolean; + delete: boolean; + reload: boolean; + cancel: boolean; + unstable_copy: boolean; + speech: boolean; + dictation: boolean; + voice: boolean; + attachments: boolean; + feedback: boolean; + queue: boolean; + }; + private abortController; + private _queue; + private _queueRunInFlight; + readonly isDisabled = false; + readonly isSendDisabled = false; + private _isLoading; + get isLoading(): boolean; + private _suggestions; + private _suggestionsController; + get suggestions(): readonly ThreadSuggestion[]; + get adapters(): { + chatModel: ChatModelAdapter; + history?: ThreadHistoryAdapter | undefined; + attachments?: AttachmentAdapter | undefined; + speech?: SpeechSynthesisAdapter | undefined; + dictation?: DictationAdapter | undefined; + voice?: RealtimeVoiceAdapter | undefined; + feedback?: FeedbackAdapter | undefined; + suggestion?: SuggestionAdapter | undefined; + }; + constructor(contextProvider: ModelContextProvider, options: LocalRuntimeOptionsBase); + private _options; + private _lastRunConfig; + private _getThreadId?; + __internal_setGetThreadId(getThreadId: () => string | undefined): void; + private _getInitializePromise?; + __internal_setGetInitializePromise(getPromise: () => Promise<unknown> | undefined): void; + get extras(): undefined; + __internal_setOptions(options: LocalRuntimeOptionsBase): void; + private _loadPromise; + __internal_load(): Promise<void>; + append(message: AppendMessage): Promise<void>; + getQueueItems(): readonly QueueItemState[]; + steerQueueItem(queueItemId: string): void; + removeQueueItem(queueItemId: string): void; + private _runAppend; + deleteMessage(messageId: string): Promise<void>; + resumeRun(_param4: ResumeRunConfig): Promise<void>; + exportExternalState(): any; + importExternalState(): void; + startRun(_param5: StartRunConfig, runCallback?: ChatModelAdapter["run"]): Promise<void>; + private _runLoop; + private performRoundtrip; + detach(): void; + cancelRun(): void; + addToolResult(_param6: AddToolResultOptions): void; + resumeToolCall(_options: ResumeToolCallOptions): void; + respondToToolApproval(_param7: RespondToToolApprovalOptions): void; +} + +declare const MCP_APP_URI_SCHEME = "ui://"; + +type MakeRequestOptions = { + method?: "POST" | "PUT" | "DELETE" | undefined; + headers?: Record<string, string> | undefined; + query?: Record<string, string | number | boolean> | undefined; + body?: object | undefined; +}; + +type McpAppMetadata = { + readonly resourceUri: string; + readonly mimeType?: string; + readonly visibility?: readonly ("app" | "model")[]; +}; + +type McpAppResourceOutput = { + readonly render: ToolCallMessagePartComponent; +}; + +type McpServerConfig = { + type: "http" | "sse"; + url: string; + headers?: Record<string, string>; + redirect?: "error" | "follow"; + connectionTimeout?: number | undefined; +} | { + type: "stdio"; + command: string; + args?: readonly string[]; + env?: Record<string, string>; + cwd?: string; + connectionTimeout?: number | undefined; +}; + +type McpTool = ToolBase<Record<string, unknown>, unknown> & { + type: "mcp"; + server: McpServerConfig; + description?: undefined; + parameters?: undefined; + disabled?: boolean; + execute?: undefined; + toModelOutput?: undefined; + experimental_onSchemaValidationError?: undefined; + providerOptions?: undefined; +}; + +type McpToolkitDefinition = Record<string, McpToolkitEntry>; + +type McpToolkitEntry = McpServerConfig | { + server: McpServerConfig; + disabled?: boolean | undefined; + prefix?: string | undefined; + tools?: Record<string, McpToolkitToolConfig> | undefined; +}; + +type McpToolkitToolConfig = { + disabled?: boolean | undefined; +}; + +declare const MessageAttachmentByIndexProvider: FC<PropsWithChildren<{ + index: number; +}>>; + +declare class MessageAttachmentRuntimeImpl extends AttachmentRuntimeImpl<"message"> { + get source(): "message"; + remove(): never; +} + +type MessageAttachmentState = CompleteAttachment & { + readonly source: "message"; +}; + +type MessageAttachmentsComponentConfig = { + Image?: ComponentType | undefined; + Document?: ComponentType | undefined; + File?: ComponentType | undefined; + Attachment?: ComponentType | undefined; +}; + +declare const MessageByIndexProvider: FC<PropsWithChildren<{ + index: number; +}>>; + +declare const MessageClient: Resource<ClientOutput<"message">, [ + { + runtime: MessageRuntime; + threadIdRef: { + current: string; + }; + } +]>; + +type MessageClientSchema = { + methods: MessageMethods; + meta: MessageMeta; +}; + +type MessageCommonProps = { + readonly id: string; + readonly createdAt: Date; +}; + +interface MessageFormatAdapter<TMessage, TStorageFormat extends Record<string, unknown>> { + format: string; + encode(item: MessageFormatItem<TMessage>): TStorageFormat; + decode(stored: MessageStorageEntry<TStorageFormat>): MessageFormatItem<TMessage>; + getId(message: TMessage): string; +} + +interface MessageFormatItem<TMessage> { + parentId: string | null; + message: TMessage; +} + +interface MessageFormatRepository<TMessage> { + headId?: string | null; + messages: MessageFormatItem<TMessage>[]; +} + +type MessageMeta = { + source: "thread"; + query: { + type: "id"; + id: string; + } | { + type: "index"; + index: number; + }; +}; + +type MessageMethods = { + getState(): MessageState; + composer(): ComposerMethods; + delete(): void | Promise<void>; + reload(config?: { + runConfig?: RunConfig; + }): void; + speak(): void; + stopSpeaking(): void; + submitFeedback(feedback: { + type: "negative" | "positive"; + }): void; + switchToBranch(options: { + position?: "next" | "previous"; + branchId?: string; + }): void; + getCopyText(): string; + part(selector: { + index: number; + } | { + toolCallId: string; + }): PartMethods; + attachment(selector: { + index: number; + } | { + id: string; + }): AttachmentMethods; + setIsCopied(value: boolean): void; + setIsHovering(value: boolean): void; + __internal_getRuntime?(): MessageRuntime; +}; + +declare const MessagePartClient: Resource<ClientOutput<"part">, [ + { + runtime: MessagePartRuntime; + } +]>; + +declare const MessagePartComponent: FC<MessagePartComponentProps>; + +type MessagePartComponentProps = { + components: MessagePrimitiveParts.Props["components"]; +}; + +declare namespace MessagePartPrimitiveInProgress { + type Props = PropsWithChildren; +} + +declare const MessagePartPrimitiveInProgress: FC<MessagePartPrimitiveInProgress.Props>; + +type MessagePartRuntime = { + addToolResult(result: any | ToolResponse<any>): void; + resumeToolCall(payload: unknown): void; + respondToToolApproval(response: ToolApprovalResponse): void; + readonly path: MessagePartRuntimePath; + getState(): MessagePartState; + subscribe(callback: () => void): Unsubscribe$1; +}; + +declare class MessagePartRuntimeImpl implements MessagePartRuntime { + private contentBinding; + private messageApi?; + private threadApi?; + get path(): MessagePartRuntimePath; + constructor(contentBinding: MessagePartSnapshotBinding, messageApi?: MessageStateBinding | undefined, threadApi?: ThreadRuntimeCoreBinding | undefined); + protected __internal_bindMethods(): void; + getState(): MessagePartState; + addToolResult(result: any | ToolResponse<any>): void; + resumeToolCall(payload: unknown): void; + respondToToolApproval(response: ToolApprovalResponse): void; + subscribe(callback: () => void): Unsubscribe$1; +} + +type MessagePartRuntimePath = MessageRuntimePath & { + readonly messagePartSelector: { + readonly type: "index"; + readonly index: number; + } | { + readonly type: "toolCallId"; + readonly toolCallId: string; + }; +}; + +type MessagePartSnapshotBinding = SubscribableWithState<MessagePartState, MessagePartRuntimePath>; + +type MessagePartState = (ThreadUserMessagePart | ThreadAssistantMessagePart) & { + readonly status: MessagePartStatus | ToolCallMessagePartStatus; +}; + +type MessagePartStatus = { + readonly type: "running"; +} | { + readonly type: "complete"; +} | { + readonly type: "incomplete"; + readonly reason: "cancelled" | "content-filter" | "error" | "length" | "other"; + readonly error?: unknown; +}; + +declare namespace MessagePrimitiveAttachmentByIndex { + type Props = { + index: number; + components?: MessageAttachmentsComponentConfig; + }; +} + +declare const MessagePrimitiveAttachmentByIndex: FC<MessagePrimitiveAttachmentByIndex.Props>; + +declare namespace MessagePrimitiveAttachments { + type Props = { + components: MessageAttachmentsComponentConfig; + children?: never; + } | { + children: (value: { + attachment: CompleteAttachment; + }) => ReactNode; + components?: never; + }; +} + +declare const MessagePrimitiveAttachments: FC<MessagePrimitiveAttachments.Props>; + +declare namespace MessagePrimitiveGenerativeUI { + type Props = { + components: GenerativeUIComponentRegistry; + spec?: GenerativeUISpec | undefined; + Fallback?: ComponentType<{ + component: string; + props?: unknown; + }> | undefined; + }; +} + +declare const MessagePrimitiveGenerativeUI: FC<MessagePrimitiveGenerativeUI.Props>; + +declare namespace MessagePrimitiveGroupedParts { + type GroupPart<TKey extends `group-${string}` = `group-${string}`> = { + readonly type: TKey; + readonly status: MessagePartStatus | ToolCallMessagePartStatus; + readonly indices: readonly number[]; + }; + type IndicatorPart = { + readonly type: "indicator"; + }; + type IndicatorMode = "always" | "empty" | "never" | "no-text"; + type RenderInfo<TKey extends `group-${string}` = `group-${string}`> = { + readonly part: GroupPart<TKey> | EnrichedPartState | IndicatorPart; + readonly children: ReactNode; + }; + type Props<TKey extends `group-${string}` = `group-${string}`> = { + readonly groupBy: (part: PartState, context: GroupByContext) => readonly TKey[] | null; + readonly indicator?: IndicatorMode; + readonly children: (info: RenderInfo<TKey>) => ReactNode; + }; +} + +declare const MessagePrimitiveGroupedParts: { + <TKey extends `group-${string}`>(_param8: MessagePrimitiveGroupedParts.Props<TKey>): ReactNode; + displayName: string; +}; + +declare namespace MessagePrimitivePartByIndex { + type Props = { + index: number; + components: MessagePrimitiveParts.Props["components"]; + }; +} + +declare const MessagePrimitivePartByIndex: FC<MessagePrimitivePartByIndex.Props>; + +declare namespace MessagePrimitiveParts { + type DataConfig = { + by_name?: Record<string, DataMessagePartComponent | undefined> | undefined; + Fallback?: DataMessagePartComponent | undefined; + }; + type BaseComponents = { + Empty?: EmptyMessagePartComponent | undefined; + Text?: TextMessagePartComponent | undefined; + Source?: SourceMessagePartComponent | undefined; + Image?: ImageMessagePartComponent | undefined; + File?: FileMessagePartComponent | undefined; + Unstable_Audio?: Unstable_AudioMessagePartComponent | undefined; + data?: DataConfig | undefined; + Quote?: QuoteMessagePartComponent | undefined; + generativeUI?: { + components: GenerativeUIComponentRegistry; + Fallback?: ComponentType<{ + component: string; + props?: unknown; + }> | undefined; + } | undefined; + }; + type ToolsConfig = { + by_name?: Record<string, ToolCallMessagePartComponent | undefined> | undefined; + Fallback?: ComponentType<ToolCallMessagePartProps> | undefined; + } | { + Override: ComponentType<ToolCallMessagePartProps>; + }; + type StandardComponents = BaseComponents & { + Reasoning?: ReasoningMessagePartComponent | undefined; + tools?: ToolsConfig | undefined; + ToolGroup?: ComponentType<PropsWithChildren<{ + startIndex: number; + endIndex: number; + }>>; + ReasoningGroup?: ReasoningGroupComponent; + ChainOfThought?: never; + }; + type ChainOfThoughtComponents = BaseComponents & { + ChainOfThought: ComponentType; + Reasoning?: never; + tools?: never; + ToolGroup?: never; + ReasoningGroup?: never; + }; + export type Props = { + components?: StandardComponents | ChainOfThoughtComponents | undefined; + unstable_showEmptyOnNonTextEnd?: boolean | undefined; + children?: never; + } | { + children: (value: { + part: EnrichedPartState; + }) => ReactNode; + components?: never; + unstable_showEmptyOnNonTextEnd?: never; + }; + export {}; +} + +declare const MessagePrimitiveParts: FC<MessagePrimitiveParts.Props>; + +declare namespace MessagePrimitiveQuote { + type Props = { + children: (value: QuoteInfo) => ReactNode; + }; +} + +declare const MessagePrimitiveQuote: import("react").NamedExoticComponent<MessagePrimitiveQuote.Props>; + +type MessageQueueController = { + readonly adapter: ExternalThreadQueueAdapter; + notifyBusy: () => void; + notifyIdle: () => void; + subscribe: (callback: () => void) => () => void; +}; + +type MessageQueueDriver = { + run: (message: AppendMessage, options: { + steer: boolean; + }) => void; + cancel?: (() => void) | undefined; +}; + +type MessageQuoteState = { + message: { + metadata?: unknown; + }; +}; + +declare class MessageRepository { + private messages; + private head; + private root; + private updateLevels; + private performOp; + private _messages; + get headId(): string | null; + get canonicalHeadId(): string | null; + getMessages(headId?: string): readonly ThreadMessage[]; + addOrUpdateMessage(parentId: string | null, message: ThreadMessage): void; + getMessage(messageId: string): { + parentId: string | null; + message: ThreadMessage; + index: number; + }; + deleteMessage(messageId: string, replacementId?: string | null | undefined): void; + getBranches(messageId: string): string[]; + private evictOffBranchOptimisticMessages; + switchToBranch(messageId: string): void; + resetHead(messageId: string | null): void; + clear(): void; + export(): ExportedMessageRepository; + import(_param9: ExportedMessageRepository): void; +} + +type MessageRole = ThreadMessage["role"]; + +type MessageRuntime = { + readonly path: MessageRuntimePath; + readonly composer: EditComposerRuntime; + getState(): MessageState$1; + delete(): void | Promise<void>; + reload(config?: ReloadConfig): void; + speak(): void; + stopSpeaking(): void; + submitFeedback(_param10: { + type: "positive" | "negative"; + }): void; + switchToBranch(_param11: { + position?: "previous" | "next" | undefined; + branchId?: string | undefined; + }): void; + unstable_getCopyText(): string; + subscribe(callback: () => void): Unsubscribe$1; + getMessagePartByIndex(idx: number): MessagePartRuntime; + getMessagePartByToolCallId(toolCallId: string): MessagePartRuntime; + getAttachmentByIndex(idx: number): AttachmentRuntime & { + source: "message"; + }; +}; + +declare class MessageRuntimeImpl implements MessageRuntime { + private _core; + private _threadBinding; + get path(): MessageRuntimePath; + constructor(_core: MessageStateBinding, _threadBinding: ThreadRuntimeCoreBinding); + protected __internal_bindMethods(): void; + readonly composer: EditComposerRuntimeImpl; + private _getEditComposerRuntimeCore; + getState(): ThreadMessage & { + readonly parentId: string | null; + readonly index: number; + readonly isLast: boolean; + readonly branchNumber: number; + readonly branchCount: number; + readonly speech: SpeechState | undefined; + }; + delete(): void | Promise<void>; + reload(reloadConfig?: ReloadConfig): void; + speak(): void; + stopSpeaking(): void; + submitFeedback(_param12: { + type: "positive" | "negative"; + }): void; + switchToBranch(_param13: { + position?: "previous" | "next" | undefined; + branchId?: string | undefined; + }): void; + unstable_getCopyText(): string; + subscribe(callback: () => void): Unsubscribe$1; + getMessagePartByIndex(idx: number): MessagePartRuntimeImpl; + getMessagePartByToolCallId(toolCallId: string): MessagePartRuntimeImpl; + getAttachmentByIndex(idx: number): MessageAttachmentRuntimeImpl; +} + +type MessageRuntimePath = ThreadRuntimePath & { + readonly messageSelector: { + readonly type: "messageId"; + readonly messageId: string; + } | { + readonly type: "index"; + readonly index: number; + }; +}; + +type MessageState = ThreadMessage & { + readonly parentId: string | null; + readonly isLast: boolean; + readonly branchNumber: number; + readonly branchCount: number; + readonly speech: SpeechState | undefined; + readonly composer: ComposerState; + readonly parts: readonly PartState[]; + readonly isCopied: boolean; + readonly isHovering: boolean; + readonly index: number; +}; + +type MessageState$1 = ThreadMessage & { + readonly parentId: string | null; + readonly index: number; + readonly isLast: boolean; + readonly branchNumber: number; + readonly branchCount: number; + readonly speech: SpeechState | undefined; +}; + +type MessageStateBinding = SubscribableWithState<ThreadMessage & { + readonly parentId: string | null; + readonly index: number; + readonly isLast: boolean; + readonly branchNumber: number; + readonly branchCount: number; + readonly speech: SpeechState | undefined; +}, MessageRuntimePath>; + +type MessageStatus = { + readonly type: "running"; +} | { + readonly type: "requires-action"; + readonly reason: "interrupt" | "tool-calls"; +} | { + readonly type: "complete"; + readonly reason: "stop" | "unknown"; +} | { + readonly type: "incomplete"; + readonly reason: "cancelled" | "content-filter" | "error" | "length" | "other" | "tool-calls"; + readonly error?: ReadonlyJSONValue; +}; + +interface MessageStorageEntry<TPayload> { + id: string; + parent_id: string | null; + format: string; + content: TPayload; +} + +type MessageTiming = { + readonly streamStartTime: number; + readonly firstTokenTime?: number; + readonly totalStreamTime?: number; + readonly tokenCount?: number; + readonly tokensPerSecond?: number; + readonly totalChunks: number; + readonly toolCallCount: number; +}; + +type MessagesComponentConfig = { + Message: ComponentType; + EditComposer?: ComponentType | undefined; + UserEditComposer?: ComponentType | undefined; + AssistantEditComposer?: ComponentType | undefined; + SystemEditComposer?: ComponentType | undefined; + UserMessage?: ComponentType | undefined; + AssistantMessage?: ComponentType | undefined; + SystemMessage?: ComponentType | undefined; +} | { + Message?: ComponentType | undefined; + EditComposer?: ComponentType | undefined; + UserEditComposer?: ComponentType | undefined; + AssistantEditComposer?: ComponentType | undefined; + SystemEditComposer?: ComponentType | undefined; + UserMessage: ComponentType; + AssistantMessage: ComponentType; + SystemMessage?: ComponentType | undefined; +}; + +declare const ModelContext: Resource<ClientOutput<"modelContext">, [ +]>; + +type ModelContext$1 = { + priority?: number | undefined; + system?: string | undefined; + tools?: Record<string, Tool<any, any>> | undefined; + callSettings?: LanguageModelV1CallSettings | undefined; + config?: LanguageModelConfig | undefined; + unstable_composerMetadata?: Record<string, unknown> | undefined; +}; + +type ModelContextClientSchema = { + methods: ModelContextMethods; +}; + +type ModelContextMethods = ModelContextProvider & { + getState(): ModelContextState; + register: (provider: ModelContextProvider) => Unsubscribe$1; +}; + +type ModelContextProvider = { + getModelContext: () => ModelContext$1; + subscribe?: (callback: () => void) => Unsubscribe$1; +}; + +declare class ModelContextRegistry implements ModelContextProvider { + private _tools; + private _instructions; + private _providers; + private _subscribers; + private _providerUnsubscribes; + getModelContext(): ModelContext$1; + subscribe(callback: () => void): Unsubscribe$1; + private notifySubscribers; + addTool<TArgs extends Record<string, unknown>, TResult>(tool: AssistantToolProps$1<TArgs, TResult>): ModelContextRegistryToolHandle<TArgs, TResult>; + addInstruction(config: string | AssistantInstructionsConfig): ModelContextRegistryInstructionHandle; + addProvider(provider: ModelContextProvider): ModelContextRegistryProviderHandle; +} + +interface ModelContextRegistryInstructionHandle { + update(config: string | AssistantInstructionsConfig): void; + remove(): void; +} + +interface ModelContextRegistryProviderHandle { + remove(): void; +} + +interface ModelContextRegistryToolHandle<TArgs extends Record<string, unknown> = any, TResult = any> { + update(tool: AssistantToolProps$1<TArgs, TResult>): void; + remove(): void; +} + +type ModelContextState = { + readonly modelName?: string | undefined; + readonly toolNames: readonly string[]; +}; + +type NestedSubscribable<TState extends Subscribable | undefined, TPath> = SubscribableWithState<TState, TPath>; + +declare class NestedSubscriptionSubject<TState extends Subscribable | undefined, TPath> extends BaseSubject implements SubscribableWithState<TState, TPath>, NestedSubscribable<TState, TPath> { + private binding; + get path(): TPath; + constructor(binding: NestedSubscribable<TState, TPath>); + getState(): TState; + outerSubscribe(callback: () => void): Unsubscribe$1; + protected _connect(): Unsubscribe$1; +} + +declare const NoOpComposerClient: Resource<ClientOutput<"composer">, [ + { + type: "edit" | "thread"; + } +]>; + +type ObjectKey<T> = keyof T & (string | number); + +type ObjectStreamOperation = { + readonly type: "set"; + readonly path: readonly string[]; + readonly value: ReadonlyJSONValue; +} | { + readonly type: "append-text"; + readonly path: readonly string[]; + readonly value: string; +}; + +type OnSchemaValidationErrorFunction<TResult> = ToolExecuteFunction<unknown, TResult>; + +declare class OptimisticState<TState> extends BaseSubscribable { + private readonly _pendingTransforms; + private readonly _completedOptimistics; + private _baseValue; + private _cachedValue; + constructor(initialState: TState); + private _updateState; + get baseValue(): TState; + get value(): TState; + update(state: TState): void; + optimisticUpdate<TResult>(transform: Transform<TState, TResult>): Promise<TResult>; +} + +type OverrideOptionalField<T, TKey extends keyof T, TValue> = undefined extends T[TKey] ? Exclude<T[TKey], undefined> extends never ? { + [K in TKey]?: undefined; +} : { + [K in TKey]?: TValue | undefined; +} : { + [K in TKey]: TValue; +}; + +type OverrideToolDeclarationCallbacks<T extends { + streamCall?: unknown; +}, TArgs extends Record<string, unknown>, TResult> = Omit<T, "execute" | "experimental_onSchemaValidationError" | "streamCall" | "toModelOutput" | "type"> & { + type?: never; +} & ("execute" extends keyof T ? OverrideOptionalField<T, "execute", ToolExecute<NoInfer<TArgs>, TResult>> : {}) & ("toModelOutput" extends keyof T ? OverrideOptionalField<T, "toModelOutput", ToolModelOutputFunction<NoInfer<TArgs>, NoInfer<TResult>>> : {}) & ("experimental_onSchemaValidationError" extends keyof T ? OverrideOptionalField<T, "experimental_onSchemaValidationError", (args: unknown, context: ToolExecuteContext) => NoInfer<TResult> | Promise<NoInfer<TResult>>> : {}) & OverrideOptionalField<T, "streamCall", ToolStreamCall<TArgs, unknown>>; + +type ParentOf<K extends ClientNames> = AssistantClientAccessor<K> extends { + source: infer S; +} ? S extends ClientNames ? S : never : never; + +declare const PartByIndexProvider: FC<PropsWithChildren<{ + index: number; +}>>; + +type PartClientSchema = { + methods: PartMethods; + meta: PartMeta; +}; + +type PartInit = { + readonly type: "reasoning" | "text"; + readonly parentId?: string; +} | { + readonly type: "tool-call"; + readonly toolCallId: string; + readonly toolName: string; + readonly parentId?: string; +} | { + readonly type: "source"; + readonly sourceType: "url"; + readonly id: string; + readonly url: string; + readonly title?: string; + readonly parentId?: string; +} | { + readonly type: "file"; + readonly data: string; + readonly mimeType: string; + readonly parentId?: string; +} | { + readonly type: "data"; + readonly name: string; + readonly data: ReadonlyJSONValue; + readonly parentId?: string; +}; + +type PartMeta = { + source: "message"; + query: { + type: "index"; + index: number; + } | { + type: "toolCallId"; + toolCallId: string; + }; +} | { + source: "chainOfThought"; + query: { + type: "index"; + index: number; + }; +}; + +type PartMethods = { + getState(): PartState; + addToolResult(result: unknown | ToolResponse<unknown>): void; + resumeToolCall(payload: unknown): void; + respondToToolApproval(response: ToolApprovalResponse): void; + __internal_getRuntime?(): MessagePartRuntime; +}; + +declare namespace PartPrimitiveMessages { + type Props = { + components: NonNullable<ThreadPrimitiveMessages.Props["components"]>; + children?: never; + } | { + children: (value: { + message: ThreadMessage; + }) => ReactNode; + components?: never; + }; +} + +declare const PartPrimitiveMessages: import("react").NamedExoticComponent<PartPrimitiveMessages.Props>; + +declare const PartPrimitiveMessagesImpl: FC<PartPrimitiveMessages.Props>; + +type PartProviderMetadata = { + readonly [providerName: string]: ReadonlyJSONObject; +}; + +type PartState = (ThreadUserMessagePart | ThreadAssistantMessagePart) & { + readonly status: MessagePartStatus | ToolCallMessagePartStatus; +}; + +type PdfToImagesRequestBody = { + file_blob?: string | undefined; + file_url?: string | undefined; +}; + +type PdfToImagesResponse = { + success: boolean; + urls: string[]; + message: string; +}; + +type PendingAttachment = BaseAttachment & { + status: PendingAttachmentStatus; + file: File; +}; + +type PendingAttachmentStatus = { + type: "running"; + reason: "uploading"; + progress: number; +} | { + type: "requires-action"; + reason: "composer-send"; +} | { + type: "incomplete"; + reason: "error" | "upload-paused"; + message?: string; +}; + +type PropFieldStatus = "complete" | "streaming"; + +type ProviderOptions = Record<string, Record<string, unknown>>; + +type ProviderTool<TArgs extends Record<string, unknown> = Record<string, unknown>, TResult = unknown> = ToolBase<TArgs, TResult> & { + type: "provider"; + providerId: `${string}.${string}`; + parameters?: StandardSchemaV1<TArgs> | JSONSchema7 | undefined; + args: Record<string, unknown>; + supportsDeferredResults?: boolean; + description?: undefined; + disabled?: boolean; + execute?: undefined; + toModelOutput?: undefined; + experimental_onSchemaValidationError?: undefined; + providerOptions?: ProviderOptions; +}; + +type ProviderToolConfig<TArgs extends Record<string, unknown> = Record<string, unknown>> = Pick<ProviderToolDefinition<TArgs>, "args" | "parameters" | "providerId" | "providerOptions" | "supportsDeferredResults">; + +type ProviderToolDefinition<TArgs extends Record<string, unknown>> = Extract<Tool<TArgs, unknown>, { + type: "provider"; +}>; + +declare const QueueItemByIndexProvider: FC<QueueItemByIndexProviderProps>; + +type QueueItemByIndexProviderProps = PropsWithChildren<{ + index: number; +}>; + +type QueueItemClientSchema = { + methods: QueueItemMethods; + meta: QueueItemMeta; +}; + +type QueueItemMeta = { + source: "composer"; + query: { + index: number; + }; +}; + +type QueueItemMethods = { + getState(): QueueItemState; + steer(): void; + remove(): void; +}; + +type QueueItemState = { + readonly id: string; + readonly prompt: string; +}; + +type QuoteInfo = { + readonly text: string; + readonly messageId: string; +}; + +type QuoteMessagePartComponent = ComponentType<QuoteMessagePartProps>; + +type QuoteMessagePartProps = QuoteInfo; + +type ReadonlyJSONArray = readonly ReadonlyJSONValue[]; + +type ReadonlyJSONObject = { + readonly [key: string]: ReadonlyJSONValue; +}; + +type ReadonlyJSONValue = null | string | number | boolean | ReadonlyJSONObject | ReadonlyJSONArray; + +declare namespace ReadonlyThreadProvider { + type Props = PropsWithChildren<{ + messages: readonly ThreadMessage[]; + }>; +} + +declare const ReadonlyThreadProvider: FC<ReadonlyThreadProvider.Props>; + +declare class ReadonlyThreadRuntimeCore extends BaseSubscribable implements ThreadRuntimeCore { + private _messages; + get messages(): readonly ThreadMessage[]; + setMessages(messages: readonly ThreadMessage[]): void; + getMessageById(messageId: string): { + parentId: string | null; + message: ThreadMessage; + index: number; + } | undefined; + getBranches(messageId: string): string[]; + switchToBranch(): void; + append(): void; + deleteMessage(): void; + startRun(): void; + resumeRun(): void; + cancelRun(): void; + addToolResult(): void; + resumeToolCall(): void; + respondToToolApproval(): void; + speak(): void; + stopSpeaking(): void; + connectVoice(): void; + disconnectVoice(): void; + getVoiceVolume: () => number; + subscribeVoiceVolume: () => Unsubscribe$1; + muteVoice(): void; + unmuteVoice(): void; + submitFeedback(): void; + getModelContext(): {}; + exportExternalState(): void; + importExternalState(): void; + composer: { + attachments: never[]; + attachmentAccept: string; + addAttachment(): Promise<never>; + removeAttachment(): Promise<never>; + isEditing: false; + canCancel: boolean; + canSend: boolean; + isEmpty: boolean; + text: string; + setText(): never; + role: "user"; + setRole(): never; + runConfig: {}; + setRunConfig(): never; + reset(): Promise<void>; + clearAttachments(): Promise<void>; + send(): never; + cancel(): void; + queue: never[]; + steerQueueItem(): void; + removeQueueItem(): void; + dictation: undefined; + startDictation(): never; + stopDictation(): void; + quote: undefined; + setQuote(): never; + subscribe(): () => void; + unstable_on(): () => void; + }; + getEditComposer(): undefined; + beginEdit(): void; + speech: undefined; + voice: undefined; + capabilities: { + readonly switchToBranch: false; + readonly switchBranchDuringRun: false; + readonly edit: false; + readonly delete: false; + readonly reload: false; + readonly cancel: false; + readonly unstable_copy: false; + readonly speech: false; + readonly dictation: false; + readonly voice: false; + readonly attachments: false; + readonly feedback: false; + readonly queue: false; + }; + isDisabled: boolean; + isSendDisabled: boolean; + isLoading: boolean; + state: null; + suggestions: never[]; + extras: undefined; + import(): void; + export(): { + messages: { + message: ThreadMessage; + parentId: string | null; + }[]; + }; + reset(): void; + unstable_on(): Unsubscribe$1; +} + +declare namespace RealtimeVoiceAdapter { + type Status = { + type: "running" | "starting"; + } | { + type: "ended"; + reason: "cancelled" | "error" | "finished"; + error?: unknown; + }; + type Mode = "listening" | "speaking"; + type TranscriptItem = { + role: "assistant" | "user"; + text: string; + isFinal?: boolean; + }; + type Session = { + status: Status; + isMuted: boolean; + disconnect: () => void; + mute: () => void; + unmute: () => void; + onStatusChange: (callback: (status: Status) => void) => Unsubscribe$1; + onTranscript: (callback: (transcript: TranscriptItem) => void) => Unsubscribe$1; + onModeChange: (callback: (mode: Mode) => void) => Unsubscribe$1; + onVolumeChange: (callback: (volume: number) => void) => Unsubscribe$1; + }; +} + +type RealtimeVoiceAdapter = { + connect: (options: { + abortSignal?: AbortSignal; + }) => RealtimeVoiceAdapter.Session; +}; + +type ReasoningGroupComponent = ComponentType<ReasoningGroupProps>; + +type ReasoningGroupProps = PropsWithChildren<{ + startIndex: number; + endIndex: number; +}>; + +type ReasoningMessagePart = { + readonly type: "reasoning"; + readonly text: string; + readonly providerMetadata?: PartProviderMetadata; + readonly parentId?: string; +}; + +type ReasoningMessagePartComponent = ComponentType<ReasoningMessagePartProps>; + +type ReasoningMessagePartProps = MessagePartState & ReasoningMessagePart; + +type ReloadConfig = { + runConfig?: RunConfig; +}; + +type RemoteThreadData = { + readonly id: string; + readonly remoteId: undefined; + readonly externalId: undefined; + readonly status: "new"; + readonly title: undefined; + readonly custom: undefined; +} | { + readonly id: string; + readonly initializeTask: Promise<RemoteThreadInitializeResponse>; + readonly remoteId: undefined; + readonly externalId: undefined; + readonly status: "archived" | "regular"; + readonly title?: string | undefined; + readonly custom: undefined; +} | { + readonly id: string; + readonly initializeTask: Promise<RemoteThreadInitializeResponse>; + readonly remoteId: string; + readonly externalId: string | undefined; + readonly status: "archived" | "regular"; + readonly title?: string | undefined; + readonly lastMessageAt?: Date | undefined; + readonly custom?: Record<string, unknown> | undefined; +}; + +type RemoteThreadInitializeResponse = { + remoteId: string; + externalId: string | undefined; +}; + +type RemoteThreadListAdapter = { + list(params?: RemoteThreadListPageOptions): Promise<RemoteThreadListResponse>; + rename(remoteId: string, newTitle: string): Promise<void>; + updateCustom?(remoteId: string, custom: Record<string, unknown> | undefined): Promise<void>; + archive(remoteId: string): Promise<void>; + unarchive(remoteId: string): Promise<void>; + delete(remoteId: string): Promise<void>; + initialize(threadId: string): Promise<RemoteThreadInitializeResponse>; + generateTitle(remoteId: string, unstable_messages: readonly ThreadMessage[]): Promise<AssistantStream>; + fetch(threadId: string): Promise<RemoteThreadMetadata>; + unstable_Provider?: ComponentType<PropsWithChildren> | undefined; +}; + +type RemoteThreadListHook = () => AssistantRuntime; + +declare class RemoteThreadListHookInstanceManager extends BaseSubscribable { + private useRuntimeHook; + private instances; + private useAliveThreadsKeysChanged; + private parent; + constructor(runtimeHook: RemoteThreadListHook, parent: ThreadListRuntimeCore); + startThreadRuntime(threadId: string): Promise<Readonly<{ + getMessageById: (messageId: string) => { + parentId: string | null; + message: ThreadMessage; + index: number; + } | undefined; + getBranches: (messageId: string) => readonly string[]; + switchToBranch: (branchId: string) => void; + append: (message: AppendMessage) => void; + deleteMessage: (messageId: string) => void | Promise<void>; + startRun: (config: StartRunConfig) => void; + resumeRun: (config: ResumeRunConfig) => void; + cancelRun: () => void; + addToolResult: (options: AddToolResultOptions) => void; + resumeToolCall: (options: ResumeToolCallOptions) => void; + respondToToolApproval: (options: RespondToToolApprovalOptions) => void; + speak: (messageId: string) => void; + stopSpeaking: () => void; + connectVoice: () => void; + disconnectVoice: () => void; + muteVoice: () => void; + unmuteVoice: () => void; + submitFeedback: (feedback: SubmitFeedbackOptions) => void; + getModelContext: () => ModelContext$1; + composer: ThreadComposerRuntimeCore; + getEditComposer: (messageId: string) => EditComposerRuntimeCore | undefined; + beginEdit: (messageId: string) => void; + getQueueItems?: () => readonly QueueItemState[]; + steerQueueItem?: (queueItemId: string) => void; + removeQueueItem?: (queueItemId: string) => void; + speech: SpeechState | undefined; + voice: VoiceSessionState | undefined; + capabilities: Readonly<RuntimeCapabilities>; + isDisabled: boolean; + isSendDisabled: boolean; + isLoading: boolean; + isRunning?: boolean | undefined; + messages: readonly ThreadMessage[]; + state: ReadonlyJSONValue; + suggestions: readonly ThreadSuggestion[]; + extras: unknown; + subscribe: (callback: () => void) => Unsubscribe$1; + getVoiceVolume: () => number; + subscribeVoiceVolume: (callback: () => void) => Unsubscribe$1; + import(repository: ExportedMessageRepository): void; + export(): ExportedMessageRepository; + exportExternalState(): any; + importExternalState(state: any): void; + reset(initialMessages?: readonly ThreadMessageLike[]): void; + unstable_on<E extends ThreadRuntimeEventType>(event: E, callback: ThreadRuntimeEventCallback<E>): Unsubscribe$1; + }>>; + getThreadRuntimeCore(threadId: string): Readonly<{ + getMessageById: (messageId: string) => { + parentId: string | null; + message: ThreadMessage; + index: number; + } | undefined; + getBranches: (messageId: string) => readonly string[]; + switchToBranch: (branchId: string) => void; + append: (message: AppendMessage) => void; + deleteMessage: (messageId: string) => void | Promise<void>; + startRun: (config: StartRunConfig) => void; + resumeRun: (config: ResumeRunConfig) => void; + cancelRun: () => void; + addToolResult: (options: AddToolResultOptions) => void; + resumeToolCall: (options: ResumeToolCallOptions) => void; + respondToToolApproval: (options: RespondToToolApprovalOptions) => void; + speak: (messageId: string) => void; + stopSpeaking: () => void; + connectVoice: () => void; + disconnectVoice: () => void; + muteVoice: () => void; + unmuteVoice: () => void; + submitFeedback: (feedback: SubmitFeedbackOptions) => void; + getModelContext: () => ModelContext$1; + composer: ThreadComposerRuntimeCore; + getEditComposer: (messageId: string) => EditComposerRuntimeCore | undefined; + beginEdit: (messageId: string) => void; + getQueueItems?: () => readonly QueueItemState[]; + steerQueueItem?: (queueItemId: string) => void; + removeQueueItem?: (queueItemId: string) => void; + speech: SpeechState | undefined; + voice: VoiceSessionState | undefined; + capabilities: Readonly<RuntimeCapabilities>; + isDisabled: boolean; + isSendDisabled: boolean; + isLoading: boolean; + isRunning?: boolean | undefined; + messages: readonly ThreadMessage[]; + state: ReadonlyJSONValue; + suggestions: readonly ThreadSuggestion[]; + extras: unknown; + subscribe: (callback: () => void) => Unsubscribe$1; + getVoiceVolume: () => number; + subscribeVoiceVolume: (callback: () => void) => Unsubscribe$1; + import(repository: ExportedMessageRepository): void; + export(): ExportedMessageRepository; + exportExternalState(): any; + importExternalState(state: any): void; + reset(initialMessages?: readonly ThreadMessageLike[]): void; + unstable_on<E extends ThreadRuntimeEventType>(event: E, callback: ThreadRuntimeEventCallback<E>): Unsubscribe$1; + }> | undefined; + stopThreadRuntime(threadId: string): void; + setRuntimeHook(newRuntimeHook: RemoteThreadListHook): void; + private _RuntimeBinder; + private _OuterActiveThreadProvider; + __internal_RenderThreadRuntimes: FC<{ + provider: ComponentType<PropsWithChildren>; + }>; +} + +type RemoteThreadListOptions = { + runtimeHook: () => AssistantRuntime; + adapter: RemoteThreadListAdapter; + initialThreadId?: string | undefined; + threadId?: string | undefined; + onThreadIdChange?: ((threadId: string | undefined) => void) | undefined; + allowNesting?: boolean | undefined; +}; + +type RemoteThreadListPageOptions = { + after?: string | undefined; +}; + +type RemoteThreadListResponse = { + threads: RemoteThreadMetadata[]; + nextCursor?: string | undefined; +}; + +declare class RemoteThreadListThreadListRuntimeCore extends BaseSubscribable implements ThreadListRuntimeCore { + private _options; + private readonly _hookManager; + private _loadThreadsPromise; + private _loadMorePromise; + private _loadGeneration; + private _mainThreadId; + private readonly _state; + get threadItems(): Readonly<Record<THREAD_MAPPING_ID, RemoteThreadData>>; + getLoadThreadsPromise(): Promise<void>; + loadMore(): Promise<void>; + private readonly contextProvider; + constructor(options: RemoteThreadListOptions, contextProvider: ModelContextProvider); + private _initialThreadLoaded; + private useProvider; + __internal_setOptions(options: RemoteThreadListOptions): void; + __internal_load(): void; + reload(): Promise<void>; + get isLoading(): boolean; + get isLoadingMore(): boolean; + get hasMore(): boolean; + get threadIds(): readonly string[]; + get archivedThreadIds(): readonly string[]; + get newThreadId(): string | undefined; + get mainThreadId(): string; + private get _mainThreadRemoteId(); + private _lastNotifiedThreadId; + private _notifyThreadIdChange; + getMainThreadRuntimeCore(): Readonly<{ + getMessageById: (messageId: string) => { + parentId: string | null; + message: ThreadMessage; + index: number; + } | undefined; + getBranches: (messageId: string) => readonly string[]; + switchToBranch: (branchId: string) => void; + append: (message: AppendMessage) => void; + deleteMessage: (messageId: string) => void | Promise<void>; + startRun: (config: StartRunConfig) => void; + resumeRun: (config: ResumeRunConfig) => void; + cancelRun: () => void; + addToolResult: (options: AddToolResultOptions) => void; + resumeToolCall: (options: ResumeToolCallOptions) => void; + respondToToolApproval: (options: RespondToToolApprovalOptions) => void; + speak: (messageId: string) => void; + stopSpeaking: () => void; + connectVoice: () => void; + disconnectVoice: () => void; + muteVoice: () => void; + unmuteVoice: () => void; + submitFeedback: (feedback: SubmitFeedbackOptions) => void; + getModelContext: () => ModelContext$1; + composer: ThreadComposerRuntimeCore; + getEditComposer: (messageId: string) => EditComposerRuntimeCore | undefined; + beginEdit: (messageId: string) => void; + getQueueItems?: () => readonly QueueItemState[]; + steerQueueItem?: (queueItemId: string) => void; + removeQueueItem?: (queueItemId: string) => void; + speech: SpeechState | undefined; + voice: VoiceSessionState | undefined; + capabilities: Readonly<RuntimeCapabilities>; + isDisabled: boolean; + isSendDisabled: boolean; + isLoading: boolean; + isRunning?: boolean | undefined; + messages: readonly ThreadMessage[]; + state: ReadonlyJSONValue; + suggestions: readonly ThreadSuggestion[]; + extras: unknown; + subscribe: (callback: () => void) => Unsubscribe$1; + getVoiceVolume: () => number; + subscribeVoiceVolume: (callback: () => void) => Unsubscribe$1; + import(repository: ExportedMessageRepository): void; + export(): ExportedMessageRepository; + exportExternalState(): any; + importExternalState(state: any): void; + reset(initialMessages?: readonly ThreadMessageLike[]): void; + unstable_on<E extends ThreadRuntimeEventType>(event: E, callback: ThreadRuntimeEventCallback<E>): Unsubscribe$1; + }>; + getThreadRuntimeCore(threadIdOrRemoteId: string): Readonly<{ + getMessageById: (messageId: string) => { + parentId: string | null; + message: ThreadMessage; + index: number; + } | undefined; + getBranches: (messageId: string) => readonly string[]; + switchToBranch: (branchId: string) => void; + append: (message: AppendMessage) => void; + deleteMessage: (messageId: string) => void | Promise<void>; + startRun: (config: StartRunConfig) => void; + resumeRun: (config: ResumeRunConfig) => void; + cancelRun: () => void; + addToolResult: (options: AddToolResultOptions) => void; + resumeToolCall: (options: ResumeToolCallOptions) => void; + respondToToolApproval: (options: RespondToToolApprovalOptions) => void; + speak: (messageId: string) => void; + stopSpeaking: () => void; + connectVoice: () => void; + disconnectVoice: () => void; + muteVoice: () => void; + unmuteVoice: () => void; + submitFeedback: (feedback: SubmitFeedbackOptions) => void; + getModelContext: () => ModelContext$1; + composer: ThreadComposerRuntimeCore; + getEditComposer: (messageId: string) => EditComposerRuntimeCore | undefined; + beginEdit: (messageId: string) => void; + getQueueItems?: () => readonly QueueItemState[]; + steerQueueItem?: (queueItemId: string) => void; + removeQueueItem?: (queueItemId: string) => void; + speech: SpeechState | undefined; + voice: VoiceSessionState | undefined; + capabilities: Readonly<RuntimeCapabilities>; + isDisabled: boolean; + isSendDisabled: boolean; + isLoading: boolean; + isRunning?: boolean | undefined; + messages: readonly ThreadMessage[]; + state: ReadonlyJSONValue; + suggestions: readonly ThreadSuggestion[]; + extras: unknown; + subscribe: (callback: () => void) => Unsubscribe$1; + getVoiceVolume: () => number; + subscribeVoiceVolume: (callback: () => void) => Unsubscribe$1; + import(repository: ExportedMessageRepository): void; + export(): ExportedMessageRepository; + exportExternalState(): any; + importExternalState(state: any): void; + reset(initialMessages?: readonly ThreadMessageLike[]): void; + unstable_on<E extends ThreadRuntimeEventType>(event: E, callback: ThreadRuntimeEventCallback<E>): Unsubscribe$1; + }>; + getItemById(threadIdOrRemoteId: string): RemoteThreadData | undefined; + switchToThread(threadIdOrRemoteId: string, options?: { + unarchive?: boolean; + }): Promise<void>; + switchToNewThread(): Promise<void>; + initialize: (threadId: string) => Promise<RemoteThreadInitializeResponse>; + generateTitle: (threadId: string) => Promise<void>; + rename(threadIdOrRemoteId: string, newTitle: string): Promise<void>; + updateCustom(threadIdOrRemoteId: string, custom: Record<string, unknown> | undefined): Promise<void>; + private _ensureThreadIsNotMain; + archive(threadIdOrRemoteId: string): Promise<void>; + unarchive(threadIdOrRemoteId: string): Promise<void>; + delete(threadIdOrRemoteId: string): Promise<void>; + detach(threadIdOrRemoteId: string): Promise<void>; + private useBoundIds; + __internal_RenderComponent: FC; +} + +type RemoteThreadMetadata = { + readonly status: "archived" | "regular"; + readonly remoteId: string; + readonly externalId?: string | undefined; + readonly title?: string | undefined; + readonly lastMessageAt?: Date | undefined; + readonly custom?: Record<string, unknown> | undefined; +}; + +type RemoteThreadState = { + readonly isLoading: boolean; + readonly isLoadingMore: boolean; + readonly cursor: string | undefined; + readonly newThreadId: string | undefined; + readonly threadIds: readonly string[]; + readonly archivedThreadIds: readonly string[]; + readonly threadIdMap: Readonly<Record<string, THREAD_MAPPING_ID>>; + readonly threadData: Readonly<Record<THREAD_MAPPING_ID, RemoteThreadData>>; +}; + +type ReportToolCall = { + tool_name: string; + tool_call_id: string; + tool_args?: string; + tool_result?: string; + tool_source?: "backend" | "frontend" | "mcp"; + start_ms?: number; + end_ms?: number; + sampling_calls?: SamplingCallData[]; +}; + +type RequireAtLeastOne<T, Keys extends keyof T = keyof T> = Pick<T, Exclude<keyof T, Keys>> & { + [K in Keys]-?: Required<Pick<T, K>> & Partial<Pick<T, Exclude<Keys, K>>>; +}[Keys]; + +type Resource<R, A extends readonly unknown[] = any[]> = (...args: A) => ResourceElement<R, A>; + +type ResourceElement<R, A extends readonly unknown[] = any[]> = { + readonly hook: (...args: A) => R; + readonly args: Readonly<A>; + readonly key?: string | number; + readonly deps?: readonly unknown[]; +}; + +type RespondToToolApprovalOptions = { + approvalId: string; + approved: boolean; + optionId?: string; + reason?: string; +}; + +type ResumeRunConfig = StartRunConfig & { + stream?: (options: ChatModelRunOptions) => AsyncGenerator<ChatModelRunResult, void, unknown>; +}; + +type ResumeToolCallOptions = { + toolCallId: string; + payload: unknown; +}; + +type RunConfig = { + readonly custom?: Record<string, unknown>; +}; + +declare const RuntimeAdapter: Resource<ClientOutput<"threads">, [ + runtime: AssistantRuntime +]>; + +declare namespace RuntimeAdapterProvider { + type Props = { + adapters: RuntimeAdapters; + children: ReactNode; + }; +} + +declare const RuntimeAdapterProvider: FC<RuntimeAdapterProvider.Props>; + +type RuntimeAdapters = { + modelContext?: ModelContextProvider | undefined; + history?: ThreadHistoryAdapter | undefined; + attachments?: AttachmentAdapter | undefined; +}; + +type RuntimeCapabilities = { + readonly switchToBranch: boolean; + readonly switchBranchDuringRun: boolean; + readonly edit: boolean; + readonly reload: boolean; + readonly delete: boolean; + readonly cancel: boolean; + readonly unstable_copy: boolean; + readonly speech: boolean; + readonly dictation: boolean; + readonly voice: boolean; + readonly attachments: boolean; + readonly feedback: boolean; + readonly queue: boolean; +}; + +type RuntimeExtras<T extends object> = { + provide: (value: T) => T; + is: (extras: unknown) => extras is T; + tryGet: (extras: unknown) => T | undefined; + get: (client: AssistantClient) => T; + use: { + (): T; + <S>(select: (extras: T) => S): S; + <S>(select: (extras: T) => S, fallback: S): S; + }; +}; + +declare const SKIP_UPDATE: unique symbol; + +type SKIP_UPDATE = typeof SKIP_UPDATE; + +type SamplingCallData = { + model_id?: string; + input_tokens?: number; + output_tokens?: number; + reasoning_tokens?: number; + cached_input_tokens?: number; + duration_ms?: number; +}; + +interface ScopeRegistry { + [key: string]: { methods: any; meta?: any; events?: any }; +} + +type ScopesConfig = { + [K in ClientNames]?: ClientElement<K> | DerivedElement<K>; +}; + +type SendOptions = { + startRun?: boolean; + steer?: boolean; +}; + +type SerializedModelContext = { + system?: string; + tools?: Record<string, SerializedTool>; +}; + +type SerializedTool = { + description?: string; + parameters: any; + disabled?: boolean; + type?: string; +}; + +declare class ShallowMemoizeSubject<TState extends object, TPath> extends BaseSubject implements SubscribableWithState<TState, TPath> { + private binding; + get path(): TPath; + constructor(binding: SubscribableWithState<TState | SKIP_UPDATE, TPath>); + private _previousState; + getState: () => TState; + private _syncState; + protected _connect(): Unsubscribe$1; +} + +declare class SimpleImageAttachmentAdapter implements AttachmentAdapter { + accept: string; + add(state: { + file: File; + }): Promise<PendingAttachment>; + send(attachment: PendingAttachment): Promise<CompleteAttachment>; + remove(): Promise<void>; +} + +declare class SimpleTextAttachmentAdapter implements AttachmentAdapter { + accept: string; + add(state: { + file: File; + }): Promise<PendingAttachment>; + send(attachment: PendingAttachment): Promise<CompleteAttachment>; + remove(): Promise<void>; +} + +type SnapshotCarrierMessage = { + role: string; + metadata?: unknown; + content?: readonly unknown[] | undefined; +}; + +type SourceMessagePart = { + readonly type: "source"; + readonly sourceType: "url"; + readonly id: string; + readonly url: string; + readonly title?: string; + readonly providerMetadata?: SourceProviderMetadata; + readonly parentId?: string; +} | { + readonly type: "source"; + readonly sourceType: "document"; + readonly id: string; + readonly url?: undefined; + readonly title: string; + readonly mediaType: string; + readonly filename?: string; + readonly providerMetadata?: SourceProviderMetadata; + readonly parentId?: string; +}; + +type SourceMessagePartComponent = ComponentType<SourceMessagePartProps>; + +type SourceMessagePartProps = MessagePartState & SourceMessagePart; + +type SourceProviderMetadata = PartProviderMetadata; + +interface SpeechRecognitionConstructor { + new (): SpeechRecognitionInstance; +} + +interface SpeechRecognitionInstance extends EventTarget { + lang: string; + continuous: boolean; + interimResults: boolean; + start(): void; + stop(): void; + abort(): void; +} + +type SpeechState = { + readonly messageId: string; + readonly status: SpeechSynthesisAdapter.Status; +}; + +declare namespace SpeechSynthesisAdapter { + type Status = { + type: "running" | "starting"; + } | { + type: "ended"; + reason: "cancelled" | "error" | "finished"; + error?: unknown; + }; + type Utterance = { + status: Status; + cancel: () => void; + subscribe: (callback: () => void) => Unsubscribe$1; + }; +} + +type SpeechSynthesisAdapter = { + speak: (text: string) => SpeechSynthesisAdapter.Utterance; +}; + +type StartRunConfig = { + parentId: string | null; + sourceId: string | null; + runConfig: RunConfig; +}; + +type StateUpdater<TState> = TState | ((prev: TState) => TState); + +type StateUpdater$1<TState> = TState | ((prev: TState) => TState); + +type StreamingTimingAccessors<TMessage> = { + readonly getAssistantMessageId: (messages: readonly TMessage[]) => string | undefined; + readonly getTextLength: (messages: readonly TMessage[], messageId: string) => number; + readonly getToolCallCount: (messages: readonly TMessage[], messageId: string) => number; +}; + +type StreamingTimingOptions = { + readonly estimateTokens?: (textLength: number) => number; +}; + +type StreamingTimingState = { + readonly messageId: string; + readonly startTime: number; + readonly firstTokenTime?: number; + readonly lastContentLength: number; + readonly totalChunks: number; +}; + +type SubmitFeedbackOptions = { + messageId: string; + type: "negative" | "positive"; +}; + +type SubmittedFeedback = { + readonly type: "negative" | "positive"; +}; + +type Subscribable = { + subscribe: (callback: () => void) => Unsubscribe$1; +}; + +type SubscribableWithState<TState, TPath> = Subscribable & { + path: TPath; + getState: () => TState; +}; + +type Suggestion = { + title: string; + label: string; + prompt: string; +}; + +type SuggestionAdapter = { + generate: (options: SuggestionAdapterGenerateOptions) => Promise<readonly ThreadSuggestion[]> | AsyncGenerator<readonly ThreadSuggestion[], void>; +}; + +type SuggestionAdapterGenerateOptions = { + messages: readonly ThreadMessage[]; +}; + +declare const SuggestionByIndexProvider: FC<SuggestionByIndexProviderProps>; + +type SuggestionByIndexProviderProps = PropsWithChildren<{ + index: number; +}>; + +type SuggestionClientSchema = { + methods: SuggestionMethods; + meta: SuggestionMeta; +}; + +type SuggestionConfig = string | { + title: string; + label: string; + prompt: string; +}; + +type SuggestionMeta = { + source: "suggestions"; + query: { + index: number; + }; +}; + +type SuggestionMethods = { + getState(): SuggestionState; +}; + +type SuggestionState = { + title: string; + label: string; + prompt: string; +}; + +declare const Suggestions: Resource<ClientOutput<"suggestions">, [ + suggestions?: SuggestionConfig[] | undefined +]>; + +type SuggestionsClientSchema = { + methods: SuggestionsMethods; +}; + +type SuggestionsComponentConfig = { + Suggestion: ComponentType; +}; + +type SuggestionsMethods = { + getState(): SuggestionsState; + suggestion(query: { + index: number; + }): SuggestionMethods; +}; + +type SuggestionsState = { + suggestions: Suggestion[]; +}; + +type THREAD_MAPPING_ID = string & { + __brand: "THREAD_MAPPING_ID"; +}; + +declare const TOOL_RESPONSE_SYMBOL: unique symbol; + +type TextMessagePart = { + readonly type: "text"; + readonly text: string; + readonly providerMetadata?: PartProviderMetadata; + readonly parentId?: string; +}; + +type TextMessagePartComponent = ComponentType<TextMessagePartProps>; + +type TextMessagePartProps = MessagePartState & TextMessagePart; + +declare const TextMessagePartProvider: FC<PropsWithChildren<{ + text: string; + isRunning?: boolean; +}>>; + +type ThreadAssistantMessage = MessageCommonProps & { + readonly role: "assistant"; + readonly content: readonly ThreadAssistantMessagePart[]; + readonly status: MessageStatus; + readonly metadata: { + readonly unstable_state: ReadonlyJSONValue; + readonly unstable_annotations: readonly ReadonlyJSONValue[]; + readonly unstable_data: readonly ReadonlyJSONValue[]; + readonly steps: readonly ThreadStep[]; + readonly submittedFeedback?: { + readonly type: "negative" | "positive"; + }; + readonly timing?: MessageTiming; + readonly isOptimistic?: boolean; + readonly custom: Record<string, unknown>; + }; +}; + +type ThreadAssistantMessagePart = TextMessagePart | ReasoningMessagePart | ToolCallMessagePart | SourceMessagePart | FileMessagePart | ImageMessagePart | DataMessagePart | GenerativeUIMessagePart; + +declare const ThreadClient: Resource<ClientOutput<"thread">, [ + { + runtime: ThreadRuntime; + } +]>; + +type ThreadClientSchema = { + methods: ThreadMethods; + meta: ThreadMeta; + events: ThreadEvents; +}; + +declare class ThreadComposerAttachmentRuntimeImpl extends ComposerAttachmentRuntime<"thread-composer"> { + get source(): "thread-composer"; +} + +type ThreadComposerAttachmentState = Attachment & { + readonly source: "thread-composer"; +}; + +type ThreadComposerRuntime = Omit<ComposerRuntime, "getAttachmentByIndex" | "getState"> & { + readonly path: ComposerRuntimePath & { + composerSource: "thread"; + }; + readonly type: "thread"; + getState(): ThreadComposerState; + getAttachmentByIndex(idx: number): AttachmentRuntime & { + source: "thread-composer"; + }; +}; + +type ThreadComposerRuntimeCore = ComposerRuntimeCore; + +type ThreadComposerRuntimeCoreBinding = SubscribableWithState<ThreadComposerRuntimeCore | undefined, ComposerRuntimePath & { + composerSource: "thread"; +}>; + +declare class ThreadComposerRuntimeImpl extends ComposerRuntimeImpl implements ThreadComposerRuntime { + get path(): ComposerRuntimePath & { + composerSource: "thread"; + }; + get type(): "thread"; + private _getState; + constructor(core: ThreadComposerRuntimeCoreBinding); + getState(): ThreadComposerState; + getAttachmentByIndex(idx: number): ThreadComposerAttachmentRuntimeImpl; +} + +type ThreadComposerState = BaseComposerState & { + readonly type: "thread"; +}; + +type ThreadData = { + externalId: string | undefined; +}; + +type ThreadEvents = { + "thread.runStart": { + threadId: string; + }; + "thread.runEnd": { + threadId: string; + }; + "thread.initialize": { + threadId: string; + }; + "thread.modelContextUpdate": { + threadId: string; + }; +}; + +type ThreadHistoryAdapter = { + load(): Promise<ExportedMessageRepository & { + state?: ReadonlyJSONValue; + unstable_resume?: boolean; + }>; + resume?(options: ChatModelRunOptions): AsyncGenerator<ChatModelRunResult, void, unknown>; + append(item: ExportedMessageRepositoryItem): Promise<void>; + delete?(items: ExportedMessageRepositoryItem[]): Promise<void>; + withFormat?<TMessage, TStorageFormat extends Record<string, unknown>>(formatAdapter: MessageFormatAdapter<TMessage, TStorageFormat>): GenericThreadHistoryAdapter<TMessage>; +}; + +declare const ThreadListClient: Resource<ClientOutput<"threads">, [ + { + runtime: ThreadListRuntime; + __internal_assistantRuntime: AssistantRuntime; + } +]>; + +declare const ThreadListItemByIndexProvider: FC<PropsWithChildren<{ + index: number; + archived: boolean; +}>>; + +declare const ThreadListItemClient: Resource<ClientOutput<"threadListItem">, [ + { + runtime: ThreadListItemRuntime; + } +]>; + +type ThreadListItemClientSchema = { + methods: ThreadListItemMethods; + meta: ThreadListItemMeta; + events: ThreadListItemEvents; +}; + +type ThreadListItemCoreState = { + readonly id: string; + readonly remoteId: string | undefined; + readonly externalId: string | undefined; + readonly status: ThreadListItemStatus; + readonly title?: string | undefined; + readonly lastMessageAt?: Date | undefined; + readonly custom?: Record<string, unknown> | undefined; + readonly runtime?: ThreadRuntimeCore | undefined; +}; + +type ThreadListItemEventCallback<E extends ThreadListItemEventType> = (payload: ThreadListItemEventPayload[E]) => void; + +type ThreadListItemEventPayload = { + switchedTo: Record<string, never>; + switchedAway: Record<string, never>; +}; + +type ThreadListItemEventType = keyof ThreadListItemEventPayload; + +type ThreadListItemEvents = { + "threadListItem.switchedTo": { + threadId: string; + }; + "threadListItem.switchedAway": { + threadId: string; + }; +}; + +type ThreadListItemMeta = { + source: "threads"; + query: { + type: "main"; + } | { + type: "id"; + id: string; + } | { + type: "index"; + index: number; + archived?: boolean; + }; +}; + +type ThreadListItemMethods = { + getState(): ThreadListItemState; + switchTo(options?: { + unarchive?: boolean; + }): void; + rename(newTitle: string): void; + updateCustom(custom: Record<string, unknown> | undefined): void; + archive(): void; + unarchive(): void; + delete(): void; + generateTitle(): void; + initialize(): Promise<{ + remoteId: string; + externalId: string | undefined; + }>; + detach(): void; + __internal_getRuntime?(): ThreadListItemRuntime; +}; + +declare namespace ThreadListItemPrimitiveTitle { + type Props = { + fallback?: ReactNode; + }; +} + +declare const ThreadListItemPrimitiveTitle: FC<ThreadListItemPrimitiveTitle.Props>; + +type ThreadListItemRuntime = { + readonly path: ThreadListItemRuntimePath; + getState(): ThreadListItemState$1; + initialize(): Promise<{ + remoteId: string; + externalId: string | undefined; + }>; + generateTitle(): Promise<void>; + switchTo(options?: { + unarchive?: boolean; + }): Promise<void>; + rename(newTitle: string): Promise<void>; + updateCustom(custom: Record<string, unknown> | undefined): Promise<void>; + archive(): Promise<void>; + unarchive(): Promise<void>; + delete(): Promise<void>; + detach(): void; + subscribe(callback: () => void): Unsubscribe$1; + unstable_on<E extends ThreadListItemEventType>(event: E, callback: ThreadListItemEventCallback<E>): Unsubscribe$1; + __internal_getRuntime(): ThreadListItemRuntime; +}; + +type ThreadListItemRuntimeBinding = SubscribableWithState<ThreadListItemState$1, ThreadListItemRuntimePath>; + +declare class ThreadListItemRuntimeImpl implements ThreadListItemRuntime { + private _core; + private _threadListBinding; + get path(): ThreadListItemRuntimePath; + constructor(_core: ThreadListItemStateBinding, _threadListBinding: ThreadListRuntimeCoreBinding); + protected __internal_bindMethods(): void; + getState(): ThreadListItemState$1; + switchTo(options?: { + unarchive?: boolean; + }): Promise<void>; + rename(newTitle: string): Promise<void>; + updateCustom(custom: Record<string, unknown> | undefined): Promise<void>; + archive(): Promise<void>; + unarchive(): Promise<void>; + delete(): Promise<void>; + initialize(): Promise<{ + remoteId: string; + externalId: string | undefined; + }>; + generateTitle(): Promise<void>; + unstable_on<E extends ThreadListItemEventType>(event: E, callback: ThreadListItemEventCallback<E>): Unsubscribe$1; + subscribe(callback: () => void): Unsubscribe$1; + detach(): void; + __internal_getRuntime(): ThreadListItemRuntime; +} + +type ThreadListItemRuntimePath = { + readonly ref: string; + readonly threadSelector: { + readonly type: "main"; + } | { + readonly type: "index"; + readonly index: number; + } | { + readonly type: "archiveIndex"; + readonly index: number; + } | { + readonly type: "threadId"; + readonly threadId: string; + }; +}; + +declare const ThreadListItemRuntimeProvider: FC<PropsWithChildren<{ + runtime: ThreadListItemRuntime; +}>>; + +type ThreadListItemState = { + readonly id: string; + readonly remoteId: string | undefined; + readonly externalId: string | undefined; + readonly title?: string | undefined; + readonly lastMessageAt?: Date | undefined; + readonly status: ThreadListItemStatus; + readonly custom?: Record<string, unknown> | undefined; +}; + +type ThreadListItemState$1 = { + readonly isMain: boolean; + readonly id: string; + readonly remoteId: string | undefined; + readonly externalId: string | undefined; + readonly status: ThreadListItemStatus; + readonly title?: string | undefined; + readonly lastMessageAt?: Date | undefined; + readonly custom?: Record<string, unknown> | undefined; +}; + +type ThreadListItemStateBinding = SubscribableWithState<ThreadListItemState$1, ThreadListItemRuntimePath>; + +type ThreadListItemStatus = "archived" | "deleted" | "new" | "regular"; + +type ThreadListItemsComponentConfig = { + ThreadListItem: ComponentType; +}; + +declare namespace ThreadListPrimitiveItemByIndex { + type Props = { + index: number; + archived?: boolean | undefined; + components: ThreadListItemsComponentConfig; + }; +} + +declare const ThreadListPrimitiveItemByIndex: FC<ThreadListPrimitiveItemByIndex.Props>; + +declare namespace ThreadListPrimitiveItems { + type Props = { + archived?: boolean | undefined; + } & ({ + components: ThreadListItemsComponentConfig; + children?: never; + } | { + children: (value: { + threadListItem: ThreadListItemState; + }) => ReactNode; + components?: never; + }); +} + +declare const ThreadListPrimitiveItems: FC<ThreadListPrimitiveItems.Props>; + +type ThreadListRuntime = { + getState(): ThreadListState; + subscribe(callback: () => void): Unsubscribe$1; + readonly main: ThreadRuntime; + getById(threadId: string): ThreadRuntime; + readonly mainItem: ThreadListItemRuntime; + getItemById(threadId: string): ThreadListItemRuntime; + getItemByIndex(idx: number): ThreadListItemRuntime; + getArchivedItemByIndex(idx: number): ThreadListItemRuntime; + switchToThread(threadId: string, options?: { + unarchive?: boolean; + }): Promise<void>; + switchToNewThread(): Promise<void>; + getLoadThreadsPromise(): Promise<void>; + reload(): Promise<void>; + loadMore(): Promise<void>; +}; + +type ThreadListRuntimeCore = { + readonly isLoading: boolean; + readonly isLoadingMore?: boolean; + readonly hasMore?: boolean; + mainThreadId: string; + newThreadId: string | undefined; + threadIds: readonly string[]; + archivedThreadIds: readonly string[]; + readonly threadItems: Readonly<Record<string, ThreadListItemCoreState>>; + getMainThreadRuntimeCore(): ThreadRuntimeCore; + getThreadRuntimeCore(threadId: string): ThreadRuntimeCore; + getItemById(threadId: string): ThreadListItemCoreState | undefined; + switchToThread(threadId: string, options?: { + unarchive?: boolean; + }): Promise<void>; + switchToNewThread(): Promise<void>; + getLoadThreadsPromise(): Promise<void>; + reload?(): Promise<void>; + loadMore?(): Promise<void>; + detach(threadId: string): Promise<void>; + rename(threadId: string, newTitle: string): Promise<void>; + updateCustom?(threadId: string, custom: Record<string, unknown> | undefined): Promise<void>; + archive(threadId: string): Promise<void>; + unarchive(threadId: string): Promise<void>; + delete(threadId: string): Promise<void>; + initialize(threadId: string): Promise<{ + remoteId: string; + externalId: string | undefined; + }>; + generateTitle(threadId: string): Promise<void>; + subscribe(callback: () => void): Unsubscribe$1; +}; + +type ThreadListRuntimeCoreBinding = ThreadListRuntimeCore; + +declare class ThreadListRuntimeImpl implements ThreadListRuntime { + private _core; + private _runtimeFactory; + private _getState; + constructor(_core: ThreadListRuntimeCoreBinding, _runtimeFactory?: new (binding: ThreadRuntimeCoreBinding, threadListItemBinding: ThreadListItemRuntimeBinding) => ThreadRuntime); + protected __internal_bindMethods(): void; + switchToThread(threadId: string, options?: { + unarchive?: boolean; + }): Promise<void>; + switchToNewThread(): Promise<void>; + getLoadThreadsPromise(): Promise<void>; + reload(): Promise<void>; + loadMore(): Promise<void>; + getState(): ThreadListState; + subscribe(callback: () => void): Unsubscribe$1; + private _mainThreadListItemRuntime; + readonly main: ThreadRuntime; + get mainItem(): ThreadListItemRuntimeImpl; + getById(threadId: string): ThreadRuntime; + getItemByIndex(idx: number): ThreadListItemRuntimeImpl; + getArchivedItemByIndex(idx: number): ThreadListItemRuntimeImpl; + getItemById(threadId: string): ThreadListItemRuntimeImpl; +} + +type ThreadListState = { + readonly mainThreadId: string; + readonly newThreadId: string | undefined; + readonly threadIds: readonly string[]; + readonly archivedThreadIds: readonly string[]; + readonly isLoading: boolean; + readonly isLoadingMore: boolean; + readonly hasMore: boolean; + readonly threadItems: Readonly<Record<string, Omit<ThreadListItemState$1, "isMain" | "threadId">>>; +}; + +type ThreadMessage = BaseThreadMessage & (ThreadSystemMessage | ThreadUserMessage | ThreadAssistantMessage); + +declare const ThreadMessageClient: Resource<ClientOutput<"message">, [ + ThreadMessageClientProps +]>; + +type ThreadMessageClientProps = { + message: ThreadMessage; + index: number; + isLast?: boolean; + branchNumber?: number; + branchCount?: number; +}; + +declare class ThreadMessageConverter { + private readonly cache; + convertMessages<TIn extends WeakKey>(messages: readonly TIn[], converter: ConverterCallback<TIn>): ThreadMessage[]; +} + +type ThreadMessageLike = { + readonly role: "assistant" | "system" | "user"; + readonly content: string | readonly (TextMessagePart | ReasoningMessagePart | SourceMessagePart | ImageMessagePart | FileMessagePart | DataMessagePart | GenerativeUIMessagePart | Unstable_AudioMessagePart | DataPrefixedPart | { + readonly type: "tool-call"; + readonly toolCallId?: string; + readonly toolName: string; + readonly args?: ReadonlyJSONObject; + readonly argsText?: string; + readonly artifact?: any; + readonly result?: any | undefined; + readonly isError?: boolean | undefined; + readonly parentId?: string | undefined; + readonly messages?: readonly ThreadMessage[] | undefined; + readonly interrupt?: { + type: "human"; + payload: unknown; + }; + readonly timing?: ToolCallTiming; + readonly providerMetadata?: PartProviderMetadata; + readonly approval?: { + readonly id: string; + readonly approved?: boolean; + readonly reason?: string; + readonly isAutomatic?: boolean; + readonly options?: readonly ToolApprovalOption[]; + readonly optionId?: string; + readonly resolution?: "cancelled" | "expired"; + }; + })[]; + readonly id?: string | undefined; + readonly createdAt?: Date | undefined; + readonly status?: MessageStatus | undefined; + readonly attachments?: readonly (Omit<CompleteAttachment, "content"> & { + readonly content: readonly (ThreadUserMessagePart | DataPrefixedPart)[]; + })[] | undefined; + readonly metadata?: { + readonly unstable_state?: ReadonlyJSONValue; + readonly unstable_annotations?: readonly ReadonlyJSONValue[] | undefined; + readonly unstable_data?: readonly ReadonlyJSONValue[] | undefined; + readonly steps?: readonly ThreadStep[] | undefined; + readonly timing?: MessageTiming | undefined; + readonly submittedFeedback?: { + readonly type: "negative" | "positive"; + }; + readonly isOptimistic?: boolean | undefined; + readonly custom?: Record<string, unknown> | undefined; + } | undefined; +}; + +type ThreadMeta = { + source: "threads"; + query: { + type: "main"; + }; +}; + +type ThreadMethods = { + getState(): ThreadState$1; + composer(): ComposerMethods; + append(message: CreateAppendMessage): void; + deleteMessage(messageId: string): void | Promise<void>; + startRun(config: CreateStartRunConfig): void; + resumeRun(config: CreateResumeRunConfig): void; + cancelRun(): void; + getModelContext(): ModelContext$1; + export(): ExportedMessageRepository; + import(repository: ExportedMessageRepository): void; + reset(initialMessages?: readonly ThreadMessageLike[]): void; + message(selector: { + id: string; + } | { + index: number; + }): MessageMethods; + stopSpeaking(): void; + connectVoice(): void; + disconnectVoice(): void; + getVoiceVolume(): number; + subscribeVoiceVolume(callback: () => void): Unsubscribe$1; + muteVoice(): void; + unmuteVoice(): void; + __internal_getRuntime?(): ThreadRuntime; +}; + +declare namespace ThreadPrimitiveMessageByIndex { + type Props = { + index: number; + components: MessagesComponentConfig; + }; +} + +declare const ThreadPrimitiveMessageByIndex: FC<ThreadPrimitiveMessageByIndex.Props>; + +declare namespace ThreadPrimitiveMessages { + type Props = { + components: MessagesComponentConfig; + children?: never; + } | { + children: (value: { + message: MessageState; + }) => ReactNode; + components?: never; + }; +} + +declare const ThreadPrimitiveMessages: import("react").NamedExoticComponent<ThreadPrimitiveMessages.Props>; + +declare const ThreadPrimitiveMessagesImpl: FC<ThreadPrimitiveMessages.Props>; + +declare namespace ThreadPrimitiveSuggestionByIndex { + type Props = { + index: number; + components: SuggestionsComponentConfig; + }; +} + +declare const ThreadPrimitiveSuggestionByIndex: FC<ThreadPrimitiveSuggestionByIndex.Props>; + +declare namespace ThreadPrimitiveSuggestions { + type Props = { + components: SuggestionsComponentConfig; + children?: never; + } | { + children: (value: { + suggestion: SuggestionState; + }) => ReactNode; + components?: never; + }; +} + +declare const ThreadPrimitiveSuggestions: import("react").NamedExoticComponent<ThreadPrimitiveSuggestions.Props>; + +declare const ThreadPrimitiveSuggestionsImpl: FC<ThreadPrimitiveSuggestions.Props>; + +declare namespace ThreadPrimitiveUnstable_MessageById { + type Props = { + messageId: string; + components: MessagesComponentConfig; + }; +} + +declare const ThreadPrimitiveUnstable_MessageById: FC<ThreadPrimitiveUnstable_MessageById.Props>; + +type ThreadRuntime = { + readonly path: ThreadRuntimePath; + readonly composer: ThreadComposerRuntime; + getState(): ThreadState; + append(message: CreateAppendMessage): void; + deleteMessage(messageId: string): void | Promise<void>; + startRun(config: CreateStartRunConfig): void; + resumeRun(config: CreateResumeRunConfig): void; + exportExternalState(): any; + importExternalState(state: any): void; + subscribe(callback: () => void): Unsubscribe$1; + cancelRun(): void; + getModelContext(): ModelContext$1; + export(): ExportedMessageRepository; + import(repository: ExportedMessageRepository): void; + reset(initialMessages?: readonly ThreadMessageLike[]): void; + getMessageByIndex(idx: number): MessageRuntime; + getMessageById(messageId: string): MessageRuntime; + stopSpeaking(): void; + connectVoice(): void; + disconnectVoice(): void; + getVoiceVolume(): number; + subscribeVoiceVolume(callback: () => void): Unsubscribe$1; + muteVoice(): void; + unmuteVoice(): void; + unstable_on<E extends ThreadRuntimeEventType>(event: E, callback: ThreadRuntimeEventCallback<E>): Unsubscribe$1; +}; + +type ThreadRuntimeCore = Readonly<{ + getMessageById: (messageId: string) => { + parentId: string | null; + message: ThreadMessage; + index: number; + } | undefined; + getBranches: (messageId: string) => readonly string[]; + switchToBranch: (branchId: string) => void; + append: (message: AppendMessage) => void; + deleteMessage: (messageId: string) => void | Promise<void>; + startRun: (config: StartRunConfig) => void; + resumeRun: (config: ResumeRunConfig) => void; + cancelRun: () => void; + addToolResult: (options: AddToolResultOptions) => void; + resumeToolCall: (options: ResumeToolCallOptions) => void; + respondToToolApproval: (options: RespondToToolApprovalOptions) => void; + speak: (messageId: string) => void; + stopSpeaking: () => void; + connectVoice: () => void; + disconnectVoice: () => void; + muteVoice: () => void; + unmuteVoice: () => void; + submitFeedback: (feedback: SubmitFeedbackOptions) => void; + getModelContext: () => ModelContext$1; + composer: ThreadComposerRuntimeCore; + getEditComposer: (messageId: string) => EditComposerRuntimeCore | undefined; + beginEdit: (messageId: string) => void; + getQueueItems?: () => readonly QueueItemState[]; + steerQueueItem?: (queueItemId: string) => void; + removeQueueItem?: (queueItemId: string) => void; + speech: SpeechState | undefined; + voice: VoiceSessionState | undefined; + capabilities: Readonly<RuntimeCapabilities>; + isDisabled: boolean; + isSendDisabled: boolean; + isLoading: boolean; + isRunning?: boolean | undefined; + messages: readonly ThreadMessage[]; + state: ReadonlyJSONValue; + suggestions: readonly ThreadSuggestion[]; + extras: unknown; + subscribe: (callback: () => void) => Unsubscribe$1; + getVoiceVolume: () => number; + subscribeVoiceVolume: (callback: () => void) => Unsubscribe$1; + import(repository: ExportedMessageRepository): void; + export(): ExportedMessageRepository; + exportExternalState(): any; + importExternalState(state: any): void; + reset(initialMessages?: readonly ThreadMessageLike[]): void; + unstable_on<E extends ThreadRuntimeEventType>(event: E, callback: ThreadRuntimeEventCallback<E>): Unsubscribe$1; +}>; + +type ThreadRuntimeCoreBinding = SubscribableWithState<ThreadRuntimeCore, ThreadRuntimePath> & { + outerSubscribe(callback: () => void): Unsubscribe$1; +}; + +type ThreadRuntimeEventCallback<E extends ThreadRuntimeEventType> = (payload: ThreadRuntimeEventPayload[E]) => void; + +type ThreadRuntimeEventPayload = { + runStart: Record<string, never>; + runEnd: Record<string, never>; + initialize: Record<string, never>; + modelContextUpdate: Record<string, never>; +}; + +type ThreadRuntimeEventType = keyof ThreadRuntimeEventPayload; + +declare class ThreadRuntimeImpl implements ThreadRuntime { + get path(): ThreadRuntimePath; + get __internal_threadBinding(): Subscribable & { + path: ThreadRuntimePath; + getState: () => Readonly<{ + getMessageById: (messageId: string) => { + parentId: string | null; + message: ThreadMessage; + index: number; + } | undefined; + getBranches: (messageId: string) => readonly string[]; + switchToBranch: (branchId: string) => void; + append: (message: AppendMessage) => void; + deleteMessage: (messageId: string) => void | Promise<void>; + startRun: (config: StartRunConfig) => void; + resumeRun: (config: ResumeRunConfig) => void; + cancelRun: () => void; + addToolResult: (options: AddToolResultOptions) => void; + resumeToolCall: (options: ResumeToolCallOptions) => void; + respondToToolApproval: (options: RespondToToolApprovalOptions) => void; + speak: (messageId: string) => void; + stopSpeaking: () => void; + connectVoice: () => void; + disconnectVoice: () => void; + muteVoice: () => void; + unmuteVoice: () => void; + submitFeedback: (feedback: SubmitFeedbackOptions) => void; + getModelContext: () => ModelContext$1; + composer: Readonly<{ + isEditing: boolean; + canCancel: boolean; + canSend: boolean; + isEmpty: boolean; + attachments: readonly Attachment[]; + attachmentAccept: string; + addAttachment: (fileOrAttachment: File | CreateAttachment) => Promise<void>; + removeAttachment: (attachmentId: string) => Promise<void>; + text: string; + setText: (value: string) => void; + role: MessageRole; + setRole: (role: MessageRole) => void; + runConfig: RunConfig; + setRunConfig: (runConfig: RunConfig) => void; + quote: QuoteInfo | undefined; + setQuote: (quote: QuoteInfo | undefined) => void; + reset: () => Promise<void>; + clearAttachments: () => Promise<void>; + send: (options?: SendOptions) => void; + cancel: () => void; + queue: readonly QueueItemState[]; + steerQueueItem: (queueItemId: string) => void; + removeQueueItem: (queueItemId: string) => void; + dictation: DictationState | undefined; + startDictation: () => void; + stopDictation: () => void; + subscribe: (callback: () => void) => Unsubscribe$1; + unstable_on: <E extends ComposerRuntimeEventType>(event: E, callback: ComposerRuntimeEventCallback<E>) => Unsubscribe$1; + }>; + getEditComposer: (messageId: string) => EditComposerRuntimeCore | undefined; + beginEdit: (messageId: string) => void; + getQueueItems?: () => readonly QueueItemState[]; + steerQueueItem?: (queueItemId: string) => void; + removeQueueItem?: (queueItemId: string) => void; + speech: SpeechState | undefined; + voice: VoiceSessionState | undefined; + capabilities: Readonly<RuntimeCapabilities>; + isDisabled: boolean; + isSendDisabled: boolean; + isLoading: boolean; + isRunning?: boolean | undefined; + messages: readonly ThreadMessage[]; + state: ReadonlyJSONValue; + suggestions: readonly ThreadSuggestion[]; + extras: unknown; + subscribe: (callback: () => void) => Unsubscribe$1; + getVoiceVolume: () => number; + subscribeVoiceVolume: (callback: () => void) => Unsubscribe$1; + import(repository: ExportedMessageRepository): void; + export(): ExportedMessageRepository; + exportExternalState(): any; + importExternalState(state: any): void; + reset(initialMessages?: readonly ThreadMessageLike[]): void; + unstable_on<E extends ThreadRuntimeEventType>(event: E, callback: ThreadRuntimeEventCallback<E>): Unsubscribe$1; + }>; + } & { + outerSubscribe(callback: () => void): Unsubscribe$1; + } & { + getStateState(): ThreadState; + }; + private readonly _threadBinding; + constructor(threadBinding: ThreadRuntimeCoreBinding, threadListItemBinding: ThreadListItemRuntimeBinding); + protected __internal_bindMethods(): void; + readonly composer: ThreadComposerRuntimeImpl; + getState(): ThreadState; + append(message: CreateAppendMessage): void; + deleteMessage(messageId: string): void | Promise<void>; + subscribe(callback: () => void): Unsubscribe$1; + getModelContext(): ModelContext$1; + startRun(config: CreateStartRunConfig): void; + resumeRun(config: CreateResumeRunConfig): void; + exportExternalState(): any; + importExternalState(state: any): void; + cancelRun(): void; + stopSpeaking(): void; + connectVoice(): void; + disconnectVoice(): void; + getVoiceVolume(): number; + subscribeVoiceVolume(callback: () => void): Unsubscribe$1; + muteVoice(): void; + unmuteVoice(): void; + export(): ExportedMessageRepository; + import(data: ExportedMessageRepository): void; + reset(initialMessages?: readonly ThreadMessageLike[]): void; + getMessageByIndex(idx: number): MessageRuntimeImpl; + getMessageById(messageId: string): MessageRuntimeImpl; + private _getMessageRuntime; + private _eventSubscriptionSubjects; + unstable_on<E extends ThreadRuntimeEventType>(event: E, callback: ThreadRuntimeEventCallback<E>): Unsubscribe$1; +} + +type ThreadRuntimePath = { + readonly ref: string; + readonly threadSelector: { + readonly type: "main"; + } | { + readonly type: "threadId"; + readonly threadId: string; + }; +}; + +type ThreadState = { + readonly threadId: string; + readonly metadata: ThreadListItemState$1; + readonly isDisabled: boolean; + readonly isLoading: boolean; + readonly isRunning: boolean; + readonly capabilities: RuntimeCapabilities; + readonly messages: readonly ThreadMessage[]; + readonly state: ReadonlyJSONValue; + readonly suggestions: readonly ThreadSuggestion[]; + readonly extras: unknown; + readonly speech: SpeechState | undefined; + readonly voice: VoiceSessionState | undefined; +}; + +type ThreadState$1 = { + readonly isEmpty: boolean; + readonly isDisabled: boolean; + readonly isLoading: boolean; + readonly isRunning: boolean; + readonly capabilities: RuntimeCapabilities; + readonly messages: readonly MessageState[]; + readonly state: ReadonlyJSONValue; + readonly suggestions: readonly ThreadSuggestion[]; + readonly extras: unknown; + readonly speech: SpeechState | undefined; + readonly voice: VoiceSessionState | undefined; + readonly composer: ComposerState; +}; + +type ThreadStep = { + readonly messageId?: string; + readonly usage?: { + readonly inputTokens: number; + readonly outputTokens: number; + } | undefined; +}; + +type ThreadSuggestion = { + prompt: string; +}; + +type ThreadSystemMessage = MessageCommonProps & { + readonly role: "system"; + readonly content: readonly [ + TextMessagePart + ]; + readonly metadata: { + readonly unstable_state?: undefined; + readonly unstable_annotations?: undefined; + readonly unstable_data?: undefined; + readonly steps?: undefined; + readonly submittedFeedback?: undefined; + readonly timing?: undefined; + readonly custom: Record<string, unknown>; + }; +}; + +type ThreadUserMessage = MessageCommonProps & { + readonly role: "user"; + readonly content: readonly ThreadUserMessagePart[]; + readonly attachments: readonly CompleteAttachment[]; + readonly metadata: { + readonly unstable_state?: undefined; + readonly unstable_annotations?: undefined; + readonly unstable_data?: undefined; + readonly steps?: undefined; + readonly submittedFeedback?: undefined; + readonly timing?: undefined; + readonly custom: Record<string, unknown>; + }; +}; + +type ThreadUserMessagePart = TextMessagePart | ImageMessagePart | FileMessagePart | DataMessagePart | Unstable_AudioMessagePart; + +type ThreadsClientSchema = { + methods: ThreadsMethods; +}; + +type ThreadsMethods = { + getState(): ThreadsState; + switchToThread(threadId: string, options?: { + unarchive?: boolean; + }): void; + switchToNewThread(): void; + item(threadIdOrOptions: "main" | { + id: string; + } | { + index: number; + archived?: boolean; + }): ThreadListItemMethods; + thread(selector: "main"): ThreadMethods; + getLoadThreadsPromise(): Promise<void>; + reload(): Promise<void>; + loadMore(): Promise<void>; + __internal_getAssistantRuntime?(): AssistantRuntime; +}; + +type ThreadsState = { + readonly mainThreadId: string; + readonly newThreadId: string | null; + readonly isLoading: boolean; + readonly isLoadingMore: boolean; + readonly hasMore: boolean; + readonly threadIds: readonly string[]; + readonly archivedThreadIds: readonly string[]; + readonly threadItems: readonly ThreadListItemState[]; + readonly main: ThreadState$1; +}; + +type TitleGenerationAdapter = { + generateTitle(messages: readonly ThreadMessage[]): Promise<string>; +}; + +type Tool<TArgs extends Record<string, unknown> = Record<string, unknown>, TResult = unknown> = FrontendTool<TArgs, TResult> | BackendTool<TArgs, TResult> | HumanTool<TArgs, TResult> | ProviderTool<TArgs, TResult> | McpTool | ToolWithoutType<TArgs, TResult>; + +type ToolApprovalOption = { + readonly id: string; + readonly kind: ToolApprovalOptionKind | (string & {}); + readonly label?: string; + readonly description?: string; + readonly grants?: readonly string[]; + readonly confirm?: boolean | { + title?: string; + description?: string; + }; +}; + +type ToolApprovalOptionKind = "allow-always" | "allow-once" | "reject-always" | "reject-once"; + +type ToolApprovalResponse = { + readonly approved: boolean; + readonly reason?: string; +} | { + readonly optionId: string; + readonly reason?: string; +} | { + readonly approved: boolean; + readonly optionId: string; + readonly reason?: string; +}; + +type ToolArgsStatus<TArgs extends Record<string, unknown> = Record<string, unknown>> = { + status: "complete" | "incomplete" | "requires-action" | "running"; + propStatus: Partial<Record<keyof TArgs, PropFieldStatus>>; +}; + +type ToolBase<TArgs extends Record<string, unknown> = Record<string, unknown>, TResult = unknown> = { + streamCall?: ToolStreamCallFunction<TArgs, TResult>; + display?: ToolDisplay; +}; + +interface ToolCallArgsReader<TArgs extends Record<string, unknown>> { + get<PathT extends TypePath<TArgs>>(...fieldPath: PathT): Promise<TypeAtPath<TArgs, PathT>>; + streamValues<PathT extends TypePath<TArgs>>(...fieldPath: PathT): AsyncIterableStream<DeepPartial<TypeAtPath<TArgs, PathT>>>; + streamText<PathT extends TypePath<TArgs>>(...fieldPath: PathT): TypeAtPath<TArgs, PathT> extends string & (infer U) ? AsyncIterableStream<U> : never; + forEach<PathT extends TypePath<TArgs>>(...fieldPath: PathT): NonNullable<TypeAtPath<TArgs, PathT>> extends Array<infer U> ? AsyncIterableStream<U> : never; +} + +type ToolCallCompleteText<TArgs extends Record<string, unknown>, TResult> = ReactNode | ((options: { + args: TArgs; + result: TResult | undefined; +}) => ReactNode); + +type ToolCallMessagePart<TArgs = ReadonlyJSONObject, TResult = unknown> = { + readonly type: "tool-call"; + readonly toolCallId: string; + readonly toolName: string; + readonly args: TArgs; + readonly result?: TResult | undefined; + readonly isError?: boolean | undefined; + readonly argsText: string; + readonly artifact?: unknown; + readonly timing?: ToolCallTiming; + readonly mcp?: ToolCallMessagePartMcpMetadata; + readonly providerMetadata?: PartProviderMetadata; + readonly modelContent?: readonly ToolModelContentPart[] | undefined; + readonly interrupt?: { + type: "human"; + payload: unknown; + }; + readonly approval?: { + readonly id: string; + readonly approved?: boolean; + readonly reason?: string; + readonly isAutomatic?: boolean; + readonly options?: readonly ToolApprovalOption[]; + readonly optionId?: string; + readonly resolution?: "cancelled" | "expired"; + }; + readonly parentId?: string; + readonly messages?: readonly ThreadMessage[]; +}; + +type ToolCallMessagePartComponent<TArgs = any, TResult = any> = ComponentType<ToolCallMessagePartProps<TArgs, TResult>>; + +type ToolCallMessagePartMcpMetadata = { + readonly app?: McpAppMetadata; +}; + +type ToolCallMessagePartProps<TArgs = any, TResult = unknown> = MessagePartState & ToolCallMessagePart<TArgs, TResult> & { + addResult: (result: TResult | ToolResponse<TResult>) => void; + resume: (payload: unknown) => void; + respondToApproval: (response: ToolApprovalResponse) => void; +}; + +type ToolCallMessagePartStatus = { + readonly type: "requires-action"; + readonly reason: "interrupt"; +} | MessagePartStatus; + +interface ToolCallReader<TArgs extends Record<string, unknown> = Record<string, unknown>, TResult = unknown> { + args: ToolCallArgsReader<TArgs>; + response: ToolCallResponseReader<TResult>; + result: { + get: () => Promise<TResult>; + }; +} + +interface ToolCallResponseReader<TResult> { + get: () => Promise<ToolResponse<TResult>>; +} + +type ToolCallRunningText<TArgs extends Record<string, unknown>> = ReactNode | ((options: { + args: TArgs; +}) => ReactNode); + +type ToolCallText<TArgs extends Record<string, unknown>, TResult> = { + running: ToolCallRunningText<TArgs>; + complete?: ToolCallCompleteText<TArgs, TResult> | undefined; +} | { + running?: ToolCallRunningText<TArgs> | undefined; + complete: ToolCallCompleteText<TArgs, TResult>; +}; + +type ToolCallTiming = { + readonly startedAt: number; + readonly completedAt?: number; +}; + +type ToolDeclaration<TArgs extends Record<string, unknown> = Record<string, unknown>, TResult = unknown> = FrontendTool<TArgs, TResult> | BackendToolDeclaration<TArgs, TResult> | HumanTool<TArgs, TResult> | ProviderTool<TArgs, TResult> | McpTool | ToolWithoutType<TArgs, TResult>; + +type ToolDefinition<TArgs extends Record<string, unknown> = Record<string, unknown>, TResult = unknown> = WithRender<Tool<TArgs, TResult>, TArgs, TResult>; + +type ToolDisplay = "inline" | "standalone"; + +type ToolExecute<TArgs extends Record<string, unknown>, TResult> = (args: TArgs, context: ToolExecuteContext) => TResult | Promise<TResult>; + +type ToolExecuteContext = Parameters<NonNullable<ToolDeclaration["execute"]>>[1]; + +type ToolExecuteFunction<TArgs, TResult> = (args: TArgs, context: ToolExecutionContext) => TResult | Promise<TResult>; + +type ToolExecutionContext = { + toolCallId: string; + abortSignal: AbortSignal; + human: (payload: unknown) => Promise<unknown>; +}; + +type ToolExecutionStatus = { + type: "executing"; +} | { + type: "interrupt"; + payload: { + type: "human"; + payload: unknown; + }; +}; + +type ToolModelContentPart = { + readonly type: "text"; + readonly text: string; +} | { + readonly type: "file"; + readonly data: string; + readonly mediaType: string; + readonly filename?: string; +}; + +type ToolModelOutputFunction<TArgs, TResult> = (options: { + toolCallId: string; + input: TArgs; + output: TResult; +}) => readonly ToolModelContentPart[] | Promise<readonly ToolModelContentPart[]>; + +type ToolParameters<TArgs extends Record<string, unknown>> = ToolDeclaration<TArgs>["parameters"]; + +type ToolRegistration = { + readonly render: ToolCallMessagePartComponent; + readonly standalone: boolean; +}; + +declare class ToolResponse<TResult> { + get [TOOL_RESPONSE_SYMBOL](): boolean; + readonly artifact?: ReadonlyJSONValue; + readonly result: TResult; + readonly isError: boolean; + readonly modelContent?: readonly ToolModelContentPart[]; + readonly messages?: ReadonlyJSONValue; + constructor(options: ToolResponseLike<TResult>); + static [Symbol.hasInstance](obj: unknown): obj is ToolResponse<ReadonlyJSONValue>; + static toResponse(result: any | ToolResponse<any>): ToolResponse<any>; +} + +type ToolResponseLike<TResult> = { + result: TResult; + artifact?: ReadonlyJSONValue | undefined; + isError?: boolean | undefined; + modelContent?: readonly ToolModelContentPart[] | undefined; + messages?: ReadonlyJSONValue | undefined; +}; + +type ToolStreamCall<TArgs extends Record<string, unknown>, TResult> = (reader: ToolCallReader<TArgs, TResult>, context: ToolExecuteContext) => void; + +type ToolStreamCallFunction<TArgs extends Record<string, unknown> = Record<string, unknown>, TResult = unknown> = (reader: ToolCallReader<TArgs, TResult>, context: ToolExecutionContext) => void; + +type ToolWithoutType<TArgs extends Record<string, unknown> = Record<string, unknown>, TResult = unknown> = (Omit<FrontendTool<TArgs, TResult>, "type"> | Omit<BackendTool<TArgs, TResult>, "type"> | Omit<HumanTool<TArgs, TResult>, "type"> | Omit<ProviderTool<TArgs, TResult>, "type">) & { + type?: undefined; +}; + +type Toolkit = Record<string, ToolDefinition<any, any>>; + +type ToolkitDefinition<TArgsByName extends { + [K in keyof TArgsByName]: Record<string, unknown>; +} = Record<string, any>, TResultByName extends { + [K in keyof TArgsByName]: unknown; +} = { + [K in keyof TArgsByName]: any; +}> = { + [K in keyof TArgsByName]: ToolkitDefinitionEntry<TArgsByName[K], TResultByName[K]>; +}; + +type ToolkitDefinitionEntry<TArgs extends Record<string, unknown> = Record<string, unknown>, TResult = unknown> = ToolkitDefinitionInput<TArgs, TResult> | ToolDefinition<any, any>; + +type ToolkitDefinitionEntryWithParameters<TArgs extends Record<string, unknown> = Record<string, unknown>, TResult = unknown> = ToolkitDefinitionInput<TArgs, TResult> & { + parameters: NonNullable<ToolParameters<TArgs>>; +}; + +type ToolkitDefinitionInput<TArgs extends Record<string, unknown>, TResult> = WithRender<ToolDeclaration<TArgs, TResult> extends (infer T) ? T extends { + streamCall?: unknown; +} ? OverrideToolDeclarationCallbacks<T, TArgs, TResult> : never : never, TArgs, TResult>; + +declare const Tools: Resource<ClientOutput<"tools">, [ + { + toolkit?: Toolkit; + mcpApp?: ResourceElement<McpAppResourceOutput> | undefined; + } +]>; + +type ToolsClientSchema = { + methods: ToolsMethods; +}; + +type ToolsMethods = { + getState(): ToolsState; + setToolUI(toolName: string, render: ToolCallMessagePartComponent, options?: { + standalone?: boolean; + }): Unsubscribe$1; +}; + +type ToolsState = { + toolUIs: Record<string, readonly ToolRegistration[]>; + mcpApp?: McpAppResourceOutput | undefined; + tools: Record<string, ToolCallMessagePartComponent[]>; +}; + +type Transform<TState, TResult> = { + execute: () => Promise<TResult>; + then?: (state: TState, result: TResult) => TState; + optimistic?: (state: TState) => TState; + loading?: (state: TState, task: Promise<TResult>) => TState; +}; + +type TupleIndex<T extends readonly any[]> = Exclude<keyof T, keyof any[]>; + +type TypeAtPath<T, P extends readonly any[]> = P extends [ + infer Head, + ...infer Rest +] ? Head extends keyof T ? TypeAtPath<T[Head], Rest> : never : T; + +type TypePath<T> = [ +] | (0 extends 1 & T ? any[] : T extends object ? T extends readonly any[] ? number extends T["length"] ? { + [K in TupleIndex<T>]: [ + AsNumber<K>, + ...TypePath<T[K]> + ]; +}[TupleIndex<T>] : [ + number, + ...TypePath<T[number]> +] : { + [K in ObjectKey<T>]: [ + K, + ...TypePath<T[K]> + ]; +}[ObjectKey<T>] : [ +]); + +type UnionToIntersection<U> = (U extends unknown ? (x: U) => void : never) extends ((x: infer I) => void) ? I : never; + +type Unstable_AudioMessagePart = { + readonly type: "audio"; + readonly audio: { + readonly data: string; + readonly format: "mp3" | "wav"; + }; +}; + +type Unstable_AudioMessagePartComponent = ComponentType<Unstable_AudioMessagePartProps>; + +type Unstable_AudioMessagePartProps = MessagePartState & Unstable_AudioMessagePart; + +type Unstable_DirectiveFormatter = { + serialize(item: Unstable_TriggerItem): string; + parse(text: string): readonly Unstable_DirectiveSegment[]; +}; + +type Unstable_DirectiveSegment = { + readonly kind: "text"; + readonly text: string; +} | { + readonly kind: "mention"; + readonly type: string; + readonly label: string; + readonly id: string; +}; + +type Unstable_InferInteractableState<TSchema> = TSchema extends { + "~standard": { + types?: { + output: infer TOutput; + } | undefined; + }; +} ? TOutput : unknown; + +type Unstable_InteractableConfig<TSchema extends Unstable_InteractableStateSchema> = { + description: string; + stateSchema: TSchema; + initialState: Unstable_InferInteractableState<TSchema>; + id?: string | undefined; + updateRender?: ToolCallMessagePartComponent | undefined; +}; + +type Unstable_InteractableDefinition = { + id: string; + name: string; + description: string; + stateSchema: Unstable_InteractableStateSchema; + state: unknown; + initialState: unknown; + scope?: InteractableScope | undefined; +}; + +type Unstable_InteractablePersistedState = Record<string, { + name: string; + state: unknown; +}>; + +type Unstable_InteractablePersistenceAdapter = { + save(state: Unstable_InteractablePersistedState): void | Promise<void>; + load?(): Unstable_InteractablePersistedState | null | undefined | Promise<Unstable_InteractablePersistedState | null | undefined>; +}; + +type Unstable_InteractablePersistenceStatus = { + isPending: boolean; + error: unknown; +}; + +type Unstable_InteractableRegistration = { + id: string; + name: string; + description: string; + stateSchema: Unstable_InteractableStateSchema; + initialState: unknown; + updateRender?: ToolCallMessagePartComponent | undefined; +}; + +type Unstable_InteractableSnapshotEntry = { + id: string; + name: string; + state: unknown; + partial?: boolean | undefined; +}; + +type Unstable_InteractableStateSchema = NonNullable<Extract<Tool, { + parameters: unknown; +}>["parameters"]>; + +type Unstable_InteractableToolConfig<TSchema extends Unstable_InteractableStateSchema> = { + description: string; + stateSchema: TSchema; + render: (props: Unstable_InteractableToolRenderProps<Unstable_InferInteractableState<TSchema>>) => ReactNode; +}; + +type Unstable_InteractableToolRenderProps<TState> = { + state: TState; + setState: (updater: TState | ((prev: TState) => TState)) => void; + version: Unstable_InteractableVersionInfo<TState> | undefined; + id: string; + streaming: boolean; +}; + +type Unstable_InteractableVersion = { + state: unknown; + origin: "create" | "update" | "user-edit"; + toolCallId?: string | undefined; +}; + +type Unstable_InteractableVersionInfo<TState> = { + state: TState; + isLatest: boolean; + restore: () => void; +}; + +type Unstable_InteractablesClientSchema = { + methods: Unstable_InteractablesMethods; +}; + +type Unstable_InteractablesConfig = { + persistence?: Unstable_InteractablePersistenceAdapter | undefined; +}; + +type Unstable_InteractablesMethods = { + getState(): Unstable_InteractablesState; + register(def: Unstable_InteractableRegistration): Unsubscribe$1; + setState(id: string, updater: (prev: unknown) => unknown): void; + exportState(): Unstable_InteractablePersistedState; + importState(saved: Unstable_InteractablePersistedState): void; + setPersistenceAdapter(adapter: Unstable_InteractablePersistenceAdapter | undefined): void; + flush(): Promise<void>; +}; + +type Unstable_InteractablesState = { + definitions: Record<string, Unstable_InteractableDefinition>; + persistence: Record<string, Unstable_InteractablePersistenceStatus>; +}; + +type Unstable_TriggerAdapter = { + categories(): readonly Unstable_TriggerCategory[]; + categoryItems(categoryId: string): readonly Unstable_TriggerItem[]; + search?(query: string): readonly Unstable_TriggerItem[]; +}; + +type Unstable_TriggerCategory = { + readonly id: string; + readonly label: string; +}; + +type Unstable_TriggerItem = { + readonly id: string; + readonly type: string; + readonly label: string; + readonly description?: string | undefined; + readonly metadata?: ReadonlyJSONObject | undefined; +}; + +type Unsubscribe = () => void; + +type Unsubscribe$1 = () => void; + +type UseActionBarCopyOptions = { + copiedDuration?: number | undefined; + copyToClipboard?: ((text: string) => void | Promise<void>) | undefined; +}; + +type UseComposerIfProps = RequireAtLeastOne<ComposerIfFilters>; + +type UseSuggestionTriggerOptions = { + prompt: string; + send?: boolean | undefined; + clearComposer?: boolean | undefined; +}; + +type ValidateClient<K extends keyof ScopeRegistry> = ScopeRegistry[K] extends { + methods: ClientMethods; +} ? "meta" extends keyof ScopeRegistry[K] ? ScopeRegistry[K]["meta"] extends ClientMetaType ? "events" extends keyof ScopeRegistry[K] ? ScopeRegistry[K]["events"] extends ClientEventsType<K> ? ScopeRegistry[K] : ClientError<`ERROR: ${K & string} has invalid events type`> : ScopeRegistry[K] : ClientError<`ERROR: ${K & string} has invalid meta type`> : "events" extends keyof ScopeRegistry[K] ? ScopeRegistry[K]["events"] extends ClientEventsType<K> ? ScopeRegistry[K] : ClientError<`ERROR: ${K & string} has invalid events type`> : ScopeRegistry[K] : ClientError<`ERROR: ${K & string} has invalid methods type`>; + +type VoiceSessionControls = { + disconnect: () => void; + mute: () => void; + unmute: () => void; +}; + +type VoiceSessionHelpers = { + setStatus: (status: RealtimeVoiceAdapter.Status) => void; + end: (reason: "cancelled" | "error" | "finished", error?: unknown) => void; + emitTranscript: (item: RealtimeVoiceAdapter.TranscriptItem) => void; + emitMode: (mode: RealtimeVoiceAdapter.Mode) => void; + emitVolume: (volume: number) => void; + isDisposed: () => boolean; +}; + +type VoiceSessionState = { + readonly status: RealtimeVoiceAdapter.Status; + readonly isMuted: boolean; + readonly mode: RealtimeVoiceAdapter.Mode; +}; + +declare class WebSpeechDictationAdapter implements DictationAdapter { + private _language; + private _continuous; + private _interimResults; + constructor(options?: { + language?: string; + continuous?: boolean; + interimResults?: boolean; + }); + static isSupported(): boolean; + listen(): DictationAdapter.Session; +} + +declare class WebSpeechSynthesisAdapter implements SpeechSynthesisAdapter { + speak(text: string): SpeechSynthesisAdapter.Utterance; +} + +type WildcardPayload = { + [K in keyof ClientEventMap]: { + event: K; + payload: ClientEventMap[K]; + }; +}[Extract<keyof ClientEventMap, string>]; + +type WithRender<T, TArgs extends Record<string, unknown>, TResult> = T extends { + type: "frontend" | "human"; +} ? T & (T extends { + type: "frontend"; +} ? { + render: ToolCallMessagePartComponent<TArgs, TResult>; +} | { + render?: ToolCallMessagePartComponent<TArgs, TResult>; + renderText: ToolCallText<TArgs, TResult>; +} : { + render: ToolCallMessagePartComponent<TArgs, TResult>; +}) : T & { + render?: ToolCallMessagePartComponent<TArgs, TResult> | undefined; + renderText?: ToolCallText<TArgs, TResult> | undefined; +}; + +declare const baseRuntimeAdapterTransformScopes: (scopes: ScopesConfig, parent: AssistantClient) => void; + +declare const bindExternalStoreMessage: <T>(target: object, message: T | T[]) => void; + +declare const convertExternalMessages: <T extends WeakKey>(messages: T[], callback: useExternalMessageConverter.Callback<T>, isRunning: boolean, metadata: useExternalMessageConverter.Metadata) => ThreadMessage[]; + +declare const createLocalStorageAdapter: (options: LocalStorageAdapterOptions) => RemoteThreadListAdapter; + +declare const createMessageConverter: <T extends object>(callback: useExternalMessageConverter.Callback<T>) => { + useThreadMessages: (_param14: { + messages: T[]; + isRunning: boolean; + joinStrategy?: JoinStrategy | undefined; + metadata?: useExternalMessageConverter.Metadata; + }) => ThreadMessage[]; + toThreadMessages: (messages: T[], isRunning?: boolean, metadata?: useExternalMessageConverter.Metadata) => ThreadMessage[]; + toOriginalMessages: (input: ThreadState | ThreadMessage | ThreadMessage["content"][number]) => unknown[]; + toOriginalMessage: (input: ThreadState | ThreadMessage | ThreadMessage["content"][number]) => {}; + useOriginalMessage: () => {}; + useOriginalMessages: () => unknown[]; +}; + +declare const createMessageQueue: (driver: MessageQueueDriver) => MessageQueueController; + +declare function createRequestHeaders(headersValue: Record<string, string> | Headers | (() => Promise<Record<string, string> | Headers>)): Promise<Headers>; + +declare const createRuntimeExtras: <T extends object>(runtimeName: string) => RuntimeExtras<T>; + +declare const createSimpleTitleAdapter: () => TitleGenerationAdapter; + +declare function createThreadMappingId(id: string): THREAD_MAPPING_ID; + +declare function createVoiceSession(options: { + abortSignal?: AbortSignal; +}, setup: (helpers: VoiceSessionHelpers) => Promise<VoiceSessionControls>): RealtimeVoiceAdapter.Session; + +declare const defaultComponents: { + Text: () => null; + Reasoning: () => null; + Source: () => null; + Image: () => null; + File: () => null; + Unstable_Audio: () => null; + ToolGroup: (_param15: PropsWithChildren) => ReactNode; + ReasoningGroup: (_param16: PropsWithChildren) => ReactNode; +}; + +declare function defineMcpToolkit(definition: McpToolkitDefinition): Toolkit; + +declare function defineToolkit<TArgsByName extends { + [K in keyof TArgsByName]: Record<string, unknown>; +}, TResultByName extends { + [K in keyof TArgsByName]: unknown; +} = { + [K in keyof TArgsByName]: any; +}>(_definition: { + [K in keyof TArgsByName]: ToolkitDefinitionEntryWithParameters<TArgsByName[K], TResultByName[K]>; +}): Toolkit & { + [K in keyof TArgsByName]: ToolkitDefinitionEntryWithParameters<TArgsByName[K], TResultByName[K]>; +}; + +declare function defineToolkit<const TDefinition extends ToolkitDefinition>(_definition: TDefinition): Toolkit & TDefinition; + +declare function externalTool(): never; + +declare const fromThreadMessageLike: (like: ThreadMessageLike, fallbackId: string, fallbackStatus: MessageStatus) => ThreadMessage; + +declare const generateErrorMessageId: () => string; + +declare const generateId: (size?: number) => string; + +declare const getAutoStatus: (isLast: boolean, isRunning: boolean, hasInterruptedToolCalls: boolean, hasPendingToolCalls: boolean, error?: ReadonlyJSONValue) => MessageStatus; + +declare const getExternalStoreMessages: <T>(input: { + messages: readonly ThreadMessage[]; +} | ThreadMessage | ThreadMessage["content"][number]) => T[]; + +declare const getFileDataURL: (file: File) => Promise<string>; + +declare const getMessageQuote: (state: MessageQuoteState) => QuoteInfo | undefined; + +declare const getRenderComponent: (runtime: AssistantRuntime) => ComponentType | undefined; + +declare const getThreadData: (state: RemoteThreadState, threadIdOrRemoteId: string) => RemoteThreadData | undefined; + +declare const getThreadMessageText: (message: ThreadMessage | AppendMessage) => string; + +declare const getThreadState: (runtime: ThreadRuntimeCore, threadListItemState: ThreadListItemState$1) => ThreadState; + +declare global { + interface Window { + SpeechRecognition?: SpeechRecognitionConstructor; + webkitSpeechRecognition?: SpeechRecognitionConstructor; + } +} + +declare const groupPartByType: <TKey extends `group-${string}`>(map: Partial<Readonly<Record<GroupPartType, readonly TKey[]>>>) => ((part: PartState, context?: GroupByContext) => readonly TKey[]); + +declare const hasUpcomingMessage: (isRunning: boolean, messages: readonly ThreadMessage[]) => boolean; + +declare const hitl: typeof humanTool; + +declare const hitlTool: typeof humanTool; + +declare function humanTool(): never; + +declare namespace entry_react_exports { + export { AssistantContextConfig, AssistantDataUI, AssistantDataUIProps, AssistantInteractableProps, AssistantProviderBase, AssistantProviderBaseProps, AssistantRuntimeProvider, AssistantTool, AssistantToolProps, AssistantToolUI, AssistantToolUIProps, AsyncStorageLike, ChainOfThoughtByIndicesProvider, ChainOfThoughtPartByIndexProvider, ChainOfThoughtPrimitiveParts, CloudFileAttachmentAdapter, ComposerAttachmentByIndexProvider, ComposerPrimitiveAttachmentByIndex, ComposerPrimitiveAttachments, ComposerPrimitiveIf, ComposerPrimitiveQueue, DataMessagePartComponent, DataMessagePartProps, DataRenderers, DataRenderersClientSchema, DataRenderersMethods, DataRenderersState, EmptyMessagePartComponent, EmptyMessagePartProps, EnrichedPartState, FileMessagePartComponent, FileMessagePartProps, GenerativeUIComponentRegistry, GenerativeUIMessagePartComponent, GenerativeUIMessagePartProps, GenerativeUIRender, GenerativeUIRenderError, GenerativeUIRenderProps, GroupByContext, ImageMessagePartComponent, ImageMessagePartProps, InteractableDefinition, InteractablePersistedState, InteractablePersistenceAdapter, InteractablePersistenceStatus, InteractableRegistration, InteractableStateSchema, Interactables, InteractablesClientSchema, InteractablesMethods, InteractablesState, JoinStrategy, LocalRuntimeOptions, McpAppResourceOutput, McpToolkitDefinition, McpToolkitEntry, McpToolkitToolConfig, MessageAttachmentByIndexProvider, MessageByIndexProvider, MessagePartComponent, MessagePartPrimitiveInProgress, MessagePrimitiveAttachmentByIndex, MessagePrimitiveAttachments, MessagePrimitiveGenerativeUI, MessagePrimitiveGroupedParts, MessagePrimitivePartByIndex, MessagePrimitiveParts, MessagePrimitiveQuote, PartByIndexProvider, PartPrimitiveMessages, PartPrimitiveMessagesImpl, PartState, ProviderToolConfig, QueueItemByIndexProvider, QueueItemByIndexProviderProps, QuoteMessagePartComponent, QuoteMessagePartProps, ReadonlyThreadProvider, ReasoningGroupComponent, ReasoningGroupProps, ReasoningMessagePartComponent, ReasoningMessagePartProps, RemoteThreadListHookInstanceManager, RemoteThreadListThreadListRuntimeCore, RuntimeAdapter, RuntimeAdapterProvider, RuntimeAdapters, SourceMessagePartComponent, SourceMessagePartProps, StreamingTimingAccessors, StreamingTimingOptions, StreamingTimingState, SuggestionByIndexProvider, SuggestionByIndexProviderProps, TextMessagePartComponent, TextMessagePartProps, TextMessagePartProvider, ThreadListItemByIndexProvider, ThreadListItemPrimitiveTitle, ThreadListItemRuntimeProvider, ThreadListPrimitiveItemByIndex, ThreadListPrimitiveItems, ThreadPrimitiveMessageByIndex, ThreadPrimitiveMessages, ThreadPrimitiveMessagesImpl, ThreadPrimitiveSuggestionByIndex, ThreadPrimitiveSuggestions, ThreadPrimitiveSuggestionsImpl, ThreadPrimitiveUnstable_MessageById, TitleGenerationAdapter, ToolArgsStatus, ToolCallMessagePartComponent, ToolCallMessagePartProps, ToolCallText, ToolDefinition, Toolkit, ToolkitDefinition, ToolkitDefinitionEntry, Tools, ToolsClientSchema, ToolsMethods, ToolsState, Unstable_AudioMessagePartComponent, Unstable_AudioMessagePartProps, Unstable_InferInteractableState, Unstable_InteractableConfig, Unstable_InteractableDefinition, Unstable_InteractablePersistedState, Unstable_InteractablePersistenceAdapter, Unstable_InteractablePersistenceStatus, Unstable_InteractableRegistration, Unstable_InteractableStateSchema, Unstable_InteractableToolConfig, Unstable_InteractableToolRenderProps, Unstable_InteractableVersionInfo, Unstable_InteractablesClientSchema, Unstable_InteractablesConfig, Unstable_InteractablesMethods, Unstable_InteractablesState, UseActionBarCopyOptions, UseComposerIfProps, UseSuggestionTriggerOptions, convertExternalMessages, createLocalStorageAdapter, createMessageConverter, createSimpleTitleAdapter, defineMcpToolkit, defineToolkit, externalTool, getMessageQuote, getRenderComponent, groupPartByType, hitl, hitlTool, humanTool, makeAssistantDataUI, makeAssistantTool, makeAssistantToolUI, defaultComponents as messagePartsDefaultComponents, providerTool, splitLocalRuntimeOptions, stubTool, unstable_Interactables, unstable_interactableTool, unstable_useInteractable, unstable_useInteractableState, unstable_useInteractableVersions, unstable_useThreadMessageIds, useActionBarCopy, useActionBarEdit, useActionBarFeedbackNegative, useActionBarFeedbackPositive, useActionBarReload, useActionBarSpeak, useActionBarStopSpeaking, useAssistantCloudThreadHistoryAdapter, useAssistantContext, useAssistantDataUI, useAssistantInstructions, useAssistantInteractable, useAssistantTool, useAssistantToolUI, useAuiToolOverrides, useBranchPickerNext, useBranchPickerPrevious, useCloudThreadListAdapter, useComposerAddAttachment, useComposerCancel, useComposerDictate, useComposerSend, useEditComposerCancel, useEditComposerSend, useExternalMessageConverter, useExternalStoreRuntime, useExternalStoreSharedOptions, useInlineRender, useInteractableState, useLocalRuntime, useMessageBranching, useMessageError, useMessageReload, useRemoteThreadListRuntime, useRuntimeAdapters, useStreamingTiming, useSuggestionTrigger, useThreadIsEmpty, useThreadIsRunning, useThreadListItemArchive, useThreadListItemDelete, useThreadListItemTrigger, useThreadListItemUnarchive, useThreadListLoadMore, useThreadListNew, useThreadMessages, useToolArgsStatus, useVoiceControls, useVoiceState, useVoiceVolume }; +} + +declare namespace entry_root_exports { + export { AddToolResultOptions, AppendMessage, AssistantContextConfig, AssistantFrameHost, AssistantFrameProvider, AssistantInstructionsConfig, AssistantRuntime, AssistantRuntimeCore, AssistantToolProps$1 as AssistantToolProps, Attachment, AttachmentAdapter, AttachmentAddErrorEvent, AttachmentAddErrorReason, AttachmentRuntime, AttachmentRuntimePath, AttachmentState$1 as AttachmentState, AttachmentStatus, ChatModelAdapter, ChatModelRunOptions, ChatModelRunResult, ChatModelRunUpdate, CompleteAttachment, CompleteAttachmentStatus, ComposerRuntime, ComposerRuntimeCore, ComposerRuntimeEventCallback, ComposerRuntimeEventPayload, ComposerRuntimeEventType, ComposerRuntimePath, ComposerState$1 as ComposerState, CompositeAttachmentAdapter, CoreChatModelRunResult, CreateAppendMessage, CreateAttachment, CreateResumeRunConfig, CreateStartRunConfig, DataMessagePart, DictationAdapter, DictationState, EditComposerRuntime, EditComposerRuntimeCore, EditComposerState, ExportedMessageRepository, ExportedMessageRepositoryItem, ExternalStoreAdapter, ExternalStoreBranchChange, ExternalStoreMessageConverter, ExternalStoreSharedOptions, ExternalStoreThreadData, ExternalStoreThreadListAdapter, ExternalThreadBranchAdapter, ExternalThreadQueueAdapter, FRAME_MESSAGE_CHANNEL, FeedbackAdapter, FileMessagePart, FrameMessage, FrameMessageType, GenerativeUIMessagePart, GenerativeUINode, GenerativeUISpec, GenericThreadHistoryAdapter, ImageMessagePart, InMemoryThreadListAdapter, LanguageModelConfig, LanguageModelV1CallSettings, LocalRuntimeOptionsBase, MCP_APP_URI_SCHEME, McpAppMetadata, MessageFormatAdapter, MessageFormatItem, MessageFormatRepository, MessagePartRuntime, MessagePartRuntimePath, MessagePartState, MessagePartStatus, MessageQueueController, MessageQueueDriver, MessageRole, MessageRuntime, MessageRuntimePath, MessageState$1 as MessageState, MessageStatus, MessageStorageEntry, MessageTiming, ModelContext$1 as ModelContext, ModelContextProvider, ModelContextRegistry, ModelContextRegistryInstructionHandle, ModelContextRegistryProviderHandle, ModelContextRegistryToolHandle, PartProviderMetadata, PendingAttachment, PendingAttachmentStatus, QuoteInfo, RealtimeVoiceAdapter, ReasoningMessagePart, RemoteThreadInitializeResponse, RemoteThreadListAdapter, RemoteThreadListOptions, RemoteThreadListPageOptions, RemoteThreadListResponse, RemoteThreadMetadata, RespondToToolApprovalOptions, ResumeRunConfig, ResumeToolCallOptions, RunConfig, RuntimeCapabilities, SendOptions, SerializedModelContext, SerializedTool, SimpleImageAttachmentAdapter, SimpleTextAttachmentAdapter, SourceMessagePart, SourceProviderMetadata, SpeechState, SpeechSynthesisAdapter, StartRunConfig, StreamingTimingAccessors, StreamingTimingOptions, StreamingTimingState, SubmitFeedbackOptions, SubmittedFeedback, SuggestionAdapter, TextMessagePart, ThreadAssistantMessage, ThreadAssistantMessagePart, ThreadComposerRuntime, ThreadComposerRuntimeCore, ThreadComposerState, ThreadHistoryAdapter, ThreadListItemCoreState, ThreadListItemEventCallback, ThreadListItemEventPayload, ThreadListItemEventType, ThreadListItemRuntime, ThreadListItemRuntimePath, ThreadListItemState$1 as ThreadListItemState, ThreadListItemStatus, ThreadListRuntime, ThreadListRuntimeCore, ThreadListState, ThreadMessage, ThreadMessageLike, ThreadRuntime, ThreadRuntimeCore, ThreadRuntimeEventCallback, ThreadRuntimeEventPayload, ThreadRuntimeEventType, ThreadRuntimePath, ThreadState, ThreadStep, ThreadSuggestion, ThreadSystemMessage, ThreadUserMessage, ThreadUserMessagePart, ToolApprovalOption, ToolApprovalOptionKind, ToolApprovalResponse, ToolCallMessagePart, ToolCallMessagePartMcpMetadata, ToolCallMessagePartStatus, ToolCallTiming, ToolExecutionStatus, ToolModelContentPart, Unstable_AudioMessagePart, Unstable_DirectiveFormatter, Unstable_DirectiveSegment, Unstable_InteractableSnapshotEntry, Unstable_InteractableVersion, Unstable_TriggerAdapter, Unstable_TriggerCategory, Unstable_TriggerItem, Unsubscribe$1 as Unsubscribe, VoiceSessionControls, VoiceSessionHelpers, VoiceSessionState, WebSpeechDictationAdapter, WebSpeechSynthesisAdapter, bindExternalStoreMessage, createMessageQueue, createRequestHeaders, createVoiceSession, fromThreadMessageLike, generateId, getExternalStoreMessages, isMcpAppUri, mergeModelContexts, pickExternalStoreSharedOptions, stepStreamingTiming, tool, unstable_defaultDirectiveFormatter, unstable_formatInteractableSnapshot, unstable_getInteractableSnapshots, unstable_getInteractableVersions }; +} + +declare namespace entry_store_exports { + export { AttachmentClientSchema, AttachmentMeta, AttachmentMethods, AttachmentState, ChainOfThoughtClient, ChainOfThoughtClientSchema, ChainOfThoughtMeta, ChainOfThoughtMethods, ChainOfThoughtPart, ChainOfThoughtState, ComposerClientSchema, ComposerEvents, ComposerMeta, ComposerMethods, ComposerSendOptions, ComposerState, MessageClientSchema, MessageMeta, MessageMethods, MessageState, ModelContext, ModelContextClientSchema, ModelContextMethods, ModelContextState, NoOpComposerClient, PartClientSchema, PartMeta, PartMethods, PartState, QueueItemClientSchema, QueueItemMeta, QueueItemMethods, QueueItemState, Suggestion, SuggestionClientSchema, SuggestionConfig, SuggestionMeta, SuggestionMethods, SuggestionState, Suggestions, SuggestionsClientSchema, SuggestionsMethods, SuggestionsState, ThreadClientSchema, ThreadEvents, ThreadListItemClientSchema, ThreadListItemEvents, ThreadListItemMeta, ThreadListItemMethods, ThreadListItemState, ThreadMessageClient, ThreadMessageClientProps, ThreadMeta, ThreadMethods, ThreadState$1 as ThreadState, ThreadsClientSchema, ThreadsMethods, ThreadsState }; +} + +declare namespace entry_internal_exports { + export { AssistantRuntimeImpl, AttachmentRuntimeImpl, BaseAssistantRuntimeCore, BaseComposerRuntimeCore, BaseSubject, BaseSubscribable, BaseThreadRuntimeCore, ComposerRuntimeCoreBinding, ComposerRuntimeImpl, CompositeContextProvider, ConverterCallback, DefaultEditComposerRuntimeCore, DefaultThreadComposerRuntimeCore, EMPTY_THREAD_CORE, EditComposerAttachmentRuntimeImpl, EditComposerRuntimeCoreBinding, EditComposerRuntimeImpl, EventSubscribable, EventSubscriptionSubject, ExportedMessageRepository, ExportedMessageRepositoryItem, ExternalStoreRuntimeCore, ExternalStoreThreadFactory, ExternalStoreThreadListRuntimeCore, ExternalStoreThreadRuntimeCore, LazyMemoizeSubject, LocalRuntimeCore, LocalRuntimeOptionsBase, LocalThreadFactory, LocalThreadListRuntimeCore, LocalThreadRuntimeCore, MessageAttachmentRuntimeImpl, MessagePartRuntimeImpl, MessageRepository, MessageRuntimeImpl, MessageStateBinding, NestedSubscribable, NestedSubscriptionSubject, OptimisticState, ReadonlyThreadRuntimeCore, RemoteThreadData, RemoteThreadInitializeResponse, RemoteThreadListOptions, RemoteThreadState, RuntimeExtras, SKIP_UPDATE, SKIP_UPDATE as SKIP_UPDATE_TYPE, ShallowMemoizeSubject, Subscribable, SubscribableWithState, THREAD_MAPPING_ID, ThreadComposerAttachmentRuntimeImpl, ThreadComposerRuntimeCoreBinding, ThreadComposerRuntimeImpl, ThreadListItemRuntimeBinding, ThreadListItemRuntimeImpl, ThreadListItemStateBinding, ThreadListRuntimeCoreBinding, ThreadListRuntimeImpl, ThreadMessageConverter, ThreadRuntimeCoreBinding, ThreadRuntimeImpl, createRuntimeExtras, createThreadMappingId, fromThreadMessageLike, generateErrorMessageId, generateId, getAutoStatus, getFileDataURL, getThreadData, getThreadMessageText, getThreadState, hasUpcomingMessage, isAutoStatus, isErrorMessageId, resolveToolApprovalResponse, shouldContinue, symbolInnerMessage, updateStatusReducer }; +} + +declare namespace entry_store_internal_exports { + export { AttachmentRuntimeClient, ComposerClient, MessageClient, MessagePartClient, ThreadClient, ThreadListClient, ThreadListItemClient, baseRuntimeAdapterTransformScopes }; +} + +declare const isAutoStatus: (status: MessageStatus) => boolean; + +declare const isErrorMessageId: (id: string) => boolean; + +declare const isMcpAppUri: (uri: string | undefined) => boolean; + +declare const makeAssistantDataUI: <T = any>(dataUI: AssistantDataUIProps<T>) => AssistantDataUI; + +declare const makeAssistantTool: <TArgs extends Record<string, unknown>, TResult>(tool: AssistantToolProps<TArgs, TResult>) => AssistantTool; + +declare const makeAssistantToolUI: <TArgs, TResult>(tool: AssistantToolUIProps<TArgs, TResult>) => AssistantToolUI; + +declare const mergeModelContexts: (configSet: Set<ModelContextProvider>) => ModelContext$1; + +declare const pickExternalStoreSharedOptions: (options: ExternalStoreSharedOptions) => ExternalStoreSharedOptions; + +declare function providerTool(_config: ProviderToolConfig): never; + +declare const resolveToolApprovalResponse: (approval: { + readonly id: string; + readonly options?: readonly ToolApprovalOption[]; +}, response: ToolApprovalResponse) => RespondToToolApprovalOptions; + +declare const shouldContinue: (result: ThreadAssistantMessage, humanToolNames: string[] | undefined) => boolean; + +declare const splitLocalRuntimeOptions: <T extends LocalRuntimeOptions>(options: T) => { + localRuntimeOptions: { + cloud: AssistantCloud | undefined; + initialMessages: readonly ThreadMessageLike[] | undefined; + maxSteps: number | undefined; + adapters: Omit<{ + chatModel: ChatModelAdapter; + history?: ThreadHistoryAdapter | undefined; + attachments?: AttachmentAdapter | undefined; + speech?: SpeechSynthesisAdapter | undefined; + dictation?: DictationAdapter | undefined; + voice?: RealtimeVoiceAdapter | undefined; + feedback?: FeedbackAdapter | undefined; + suggestion?: SuggestionAdapter | undefined; + }, "chatModel"> | undefined; + unstable_humanToolNames: string[] | undefined; + unstable_enableMessageQueue: boolean | undefined; + }; + otherOptions: Omit<T, "adapters" | "cloud" | "initialMessages" | "maxSteps" | "unstable_enableMessageQueue" | "unstable_humanToolNames">; +}; + +declare const stepStreamingTiming: <TMessage>(state: StreamingTimingState | null, messages: readonly TMessage[], isRunning: boolean, accessors: StreamingTimingAccessors<TMessage>, options: StreamingTimingOptions | undefined, now?: () => number) => { + readonly state: StreamingTimingState | null; + readonly timings: Record<string, MessageTiming>; +}; + +declare function stubTool(): never; + +declare const symbolInnerMessage: unique symbol; + +declare function tool<TArgs extends Record<string, unknown>, TResult = any>(tool: Tool<TArgs, TResult>): Tool<TArgs, TResult>; + +declare const unstable_Interactables: Resource<ClientOutput<"unstable_interactables">, [ + (Unstable_InteractablesConfig | undefined)? +]>; + +declare const unstable_defaultDirectiveFormatter: Unstable_DirectiveFormatter; + +declare function unstable_formatInteractableSnapshot(entry: Unstable_InteractableSnapshotEntry): string; + +declare function unstable_getInteractableSnapshots(message: { + metadata?: unknown; +}): Unstable_InteractableSnapshotEntry[] | undefined; + +declare function unstable_getInteractableVersions(messages: readonly SnapshotCarrierMessage[], id: string, name: string): Unstable_InteractableVersion[]; + +declare const unstable_interactableTool: <TSchema extends Unstable_InteractableStateSchema>(config: Unstable_InteractableToolConfig<TSchema>) => ToolDefinition<Record<string, unknown>, { + success: true; +}>; + +declare const unstable_useInteractable: <TSchema extends Unstable_InteractableStateSchema>(name: string, config: Unstable_InteractableConfig<TSchema>) => readonly [ + Unstable_InferInteractableState<TSchema>, + { + id: string; + version: Unstable_InteractableVersionInfo<Unstable_InferInteractableState<TSchema>> | undefined; + setState: (updater: Unstable_InferInteractableState<TSchema> | ((prev: Unstable_InferInteractableState<TSchema>) => Unstable_InferInteractableState<TSchema>)) => void; + isPending: boolean; + error: unknown; + flush: () => Promise<void>; + } +]; + +declare const unstable_useInteractableState: <TState>(id: string) => [ + TState | undefined, + { + setState: (updater: StateUpdater<TState>) => void; + isPending: boolean; + error: unknown; + flush: () => Promise<void>; + } +]; + +declare const unstable_useInteractableVersions: <TState = unknown>(id: string, name: string) => (Omit<Unstable_InteractableVersion, "state"> & { + state: TState; + restore: () => void; +})[]; + +declare const unstable_useThreadMessageIds: () => readonly string[]; + +declare const updateStatusReducer: (state: RemoteThreadState, threadIdOrRemoteId: string, newStatus: "archived" | "deleted" | "regular") => RemoteThreadState; + +declare const useActionBarCopy: (_param17?: UseActionBarCopyOptions) => { + copy: () => void; + disabled: boolean; + isCopied: boolean; +}; + +declare const useActionBarEdit: () => { + edit: () => void; + disabled: boolean; +}; + +declare const useActionBarFeedbackNegative: () => { + submit: () => void; + isSubmitted: boolean; +}; + +declare const useActionBarFeedbackPositive: () => { + submit: () => void; + isSubmitted: boolean; +}; + +declare const useActionBarReload: () => { + reload: () => void; + disabled: boolean; +}; + +declare const useActionBarSpeak: () => { + speak: () => Promise<void>; + disabled: boolean; +}; + +declare const useActionBarStopSpeaking: () => { + stopSpeaking: () => void; + disabled: boolean; +}; + +declare function useAssistantCloudThreadHistoryAdapter(cloudRef: RefObject<AssistantCloud>): ThreadHistoryAdapter; + +declare const useAssistantContext: (config: AssistantContextConfig) => void; + +declare const useAssistantDataUI: (dataUI: AssistantDataUIProps | null) => void; + +declare const useAssistantInstructions: (config: string | AssistantInstructionsConfig) => void; + +declare const useAssistantInteractable: (name: string, config: AssistantInteractableProps) => string; + +declare const useAssistantTool: <TArgs extends Record<string, unknown>, TResult>(tool: AssistantToolProps<TArgs, TResult>) => void; + +declare const useAssistantToolUI: (tool: AssistantToolUIProps<any, any> | null) => void; + +declare function useAuiToolOverrides(overrides: AuiToolOverrides): void; + +declare const useBranchPickerNext: () => { + next: () => void; + disabled: boolean; +}; + +declare const useBranchPickerPrevious: () => { + previous: () => void; + disabled: boolean; +}; + +declare const useCloudThreadListAdapter: (adapter: CloudThreadListAdapterOptions) => RemoteThreadListAdapter; + +declare const useComposerAddAttachment: () => { + addAttachment: (file: File | CreateAttachment) => Promise<void>; + disabled: boolean; +}; + +declare const useComposerCancel: () => { + cancel: () => void; + disabled: boolean; +}; + +declare const useComposerDictate: () => { + startDictation: () => void; + disabled: boolean; +}; + +declare const useComposerSend: () => { + send: (opts?: ComposerSendOptions) => void; + disabled: boolean; +}; + +declare const useEditComposerCancel: () => { + cancel: () => void; +}; + +declare const useEditComposerSend: () => { + send: () => void; + disabled: boolean; +}; + +declare namespace useExternalMessageConverter { + type Message = (ThreadMessageLike & { + readonly convertConfig?: { + readonly joinStrategy?: JoinStrategy; + }; + }) | { + role: "tool"; + toolCallId: string; + toolName?: string | undefined; + result: any; + artifact?: any; + isError?: boolean; + messages?: readonly ThreadMessage[]; + }; + type Metadata = { + readonly toolStatuses?: Record<string, ToolExecutionStatus>; + readonly error?: ReadonlyJSONValue; + readonly messageTiming?: Record<string, MessageTiming>; + }; + type Callback<T> = (message: T, metadata: Metadata) => Message | Message[]; +} + +declare const useExternalMessageConverter: <T extends WeakKey>(_param18: { + callback: useExternalMessageConverter.Callback<T>; + messages: T[]; + isRunning: boolean; + joinStrategy?: JoinStrategy | undefined; + metadata?: useExternalMessageConverter.Metadata | undefined; +}) => ThreadMessage[]; + +declare const useExternalStoreRuntime: <T>(store: ExternalStoreAdapter<T>) => AssistantRuntime; + +declare const useExternalStoreSharedOptions: (options: ExternalStoreSharedOptions) => ExternalStoreSharedOptions; + +declare const useInlineRender: <TArgs, TResult>(toolUI: FC<ToolCallMessagePartProps<TArgs, TResult>>) => FC<ToolCallMessagePartProps<TArgs, TResult>>; + +declare const useInteractableState: <TState>(id: string, fallback: TState) => [ + TState, + { + setState: (updater: StateUpdater$1<TState>) => void; + setSelected: (selected: boolean) => void; + isPending: boolean; + error: unknown; + flush: () => Promise<void>; + } +]; + +declare const useLocalRuntime: (chatModel: ChatModelAdapter, _param19?: LocalRuntimeOptions) => AssistantRuntime; + +declare const useMessageBranching: () => { + branchNumber: number; + branchCount: number; + goToPrev: () => void; + goToNext: () => void; +}; + +declare const useMessageError: () => string | number | boolean | ReadonlyJSONObject | ReadonlyJSONArray | undefined; + +declare const useMessageReload: () => { + reload: () => void; + canReload: boolean; +}; + +declare const useRemoteThreadListRuntime: (options: RemoteThreadListOptions) => AssistantRuntime; + +declare const useRuntimeAdapters: () => RuntimeAdapters | null; + +declare const useStreamingTiming: <TMessage>(messages: readonly TMessage[], isRunning: boolean, accessors: StreamingTimingAccessors<TMessage>, options?: StreamingTimingOptions) => Record<string, MessageTiming>; + +declare const useSuggestionTrigger: (_param20: UseSuggestionTriggerOptions) => { + trigger: () => void; + disabled: boolean; +}; + +declare const useThreadIsEmpty: () => boolean; + +declare const useThreadIsRunning: () => boolean; + +declare const useThreadListItemArchive: () => { + archive: () => void; +}; + +declare const useThreadListItemDelete: () => { + delete: () => void; +}; + +declare const useThreadListItemTrigger: () => { + switchTo: () => void; +}; + +declare const useThreadListItemUnarchive: () => { + unarchive: () => void; +}; + +declare const useThreadListLoadMore: () => { + loadMore: () => void; + disabled: boolean; +}; + +declare const useThreadListNew: () => { + switchToNewThread: () => void; +}; + +declare const useThreadMessages: () => readonly MessageState[]; + +declare const useToolArgsStatus: <TArgs extends Record<string, unknown> = Record<string, unknown>>() => ToolArgsStatus<TArgs>; + +declare const useVoiceControls: () => { + connect: () => void; + disconnect: () => void; + mute: () => void; + unmute: () => void; +}; + +declare const useVoiceState: () => VoiceSessionState | undefined; + +declare const useVoiceVolume: () => number; + +export { entry_internal_exports as entry_internal, entry_react_exports as entry_react, entry_root_exports as entry_root, entry_store_exports as entry_store, entry_store_internal_exports as entry_store_internal }; diff --git a/api-surface/assistant-ui__eve.ts b/api-surface/assistant-ui__eve.ts new file mode 100644 index 0000000..cd737fb --- /dev/null +++ b/api-surface/assistant-ui__eve.ts @@ -0,0 +1,1473 @@ +import { StandardSchemaV1 } from "@standard-schema/spec"; + +import { SendTurnPayload } from "eve/client"; + +import { EveMessage, EveMessageData, UseEveAgentOptions } from "eve/react"; + +type AddToolResultOptions = { + messageId: string; + toolName: string; + toolCallId: string; + result: ReadonlyJSONValue; + isError: boolean; + artifact?: ReadonlyJSONValue | undefined; + modelContent?: readonly ToolModelContentPart[] | undefined; +}; + +type AppendMessage = Omit<ThreadMessage, "id"> & { + parentId: string | null; + sourceId: string | null; + runConfig: RunConfig | undefined; + startRun?: boolean | undefined; + steer?: boolean | undefined; +}; + +type AsNumber<K> = K extends `${infer N extends number}` ? N | K : never; + +type AssistantRuntime = { + readonly threads: ThreadListRuntime; + readonly thread: ThreadRuntime; + registerModelContextProvider(provider: ModelContextProvider): Unsubscribe; +}; + +type AsyncIterableStream<T> = AsyncIterable<T> & ReadableStream<T>; + +type Attachment = PendingAttachment | CompleteAttachment; + +type AttachmentAdapter = { + accept: string; + add(state: { + file: File; + }): Promise<PendingAttachment> | AsyncGenerator<PendingAttachment, void>; + remove(attachment: Attachment): Promise<void>; + send(attachment: PendingAttachment): Promise<CompleteAttachment>; +}; + +type AttachmentAddErrorEvent = { + readonly reason: AttachmentAddErrorReason; + readonly message: string; + readonly attachmentId?: string; + readonly error?: Error; +}; + +type AttachmentAddErrorReason = "adapter-error" | "no-adapter" | "not-accepted"; + +type AttachmentRuntime<TSource extends AttachmentRuntimeSource = AttachmentRuntimeSource> = { + readonly path: AttachmentRuntimePath & { + attachmentSource: TSource; + }; + readonly source: TSource; + getState(): AttachmentState & { + source: TSource; + }; + remove(): Promise<void>; + subscribe(callback: () => void): Unsubscribe; +}; + +type AttachmentRuntimePath = ((MessageRuntimePath & { + readonly attachmentSource: "edit-composer" | "message"; +}) | (ThreadRuntimePath & { + readonly attachmentSource: "thread-composer"; +})) & { + readonly attachmentSelector: { + readonly type: "index"; + readonly index: number; + } | { + readonly type: "index"; + readonly index: number; + } | { + readonly type: "index"; + readonly index: number; + }; +}; + +type AttachmentRuntimeSource = AttachmentState["source"]; + +type AttachmentState = ThreadComposerAttachmentState | EditComposerAttachmentState | MessageAttachmentState; + +type BackendTool<TArgs extends Record<string, unknown> = Record<string, unknown>, TResult = unknown> = ToolBase<TArgs, TResult> & { + type: "backend"; + description?: undefined; + parameters?: undefined; + disabled?: undefined; + execute?: undefined; + toModelOutput?: undefined; + experimental_onSchemaValidationError?: undefined; + providerOptions?: undefined; +}; + +type BaseAttachment = { + id: string; + type: "image" | "document" | "file" | (string & {}); + name: string; + contentType?: string | undefined; + file?: File; + content?: ThreadUserMessagePart[]; +}; + +type BaseComposerState = { + readonly canCancel: boolean; + readonly canSend: boolean; + readonly isEditing: boolean; + readonly isEmpty: boolean; + readonly text: string; + readonly role: MessageRole; + readonly attachments: readonly Attachment[]; + readonly runConfig: RunConfig; + readonly attachmentAccept: string; + readonly dictation: DictationState | undefined; + readonly quote: QuoteInfo | undefined; + readonly queue: readonly QueueItemState[]; +}; + +type BaseThreadMessage = { + readonly status?: ThreadAssistantMessage["status"]; + readonly metadata: { + readonly unstable_state?: ReadonlyJSONValue; + readonly unstable_annotations?: readonly ReadonlyJSONValue[]; + readonly unstable_data?: readonly ReadonlyJSONValue[]; + readonly steps?: readonly ThreadStep[]; + readonly submittedFeedback?: { + readonly type: "negative" | "positive"; + }; + readonly timing?: MessageTiming; + readonly isOptimistic?: boolean; + readonly custom: Record<string, unknown>; + }; + readonly attachments?: ThreadUserMessage["attachments"]; +}; + +type ChatModelRunOptions = { + readonly messages: readonly ThreadMessage[]; + readonly runConfig: RunConfig; + readonly abortSignal: AbortSignal; + readonly context: ModelContext; + readonly unstable_assistantMessageId?: string | undefined; + readonly unstable_threadId?: string | undefined; + readonly unstable_parentId?: string | null | undefined; + unstable_getMessage(): ThreadMessage; +}; + +type ChatModelRunResult = { + readonly content?: readonly ThreadAssistantMessagePart[] | undefined; + readonly status?: MessageStatus | undefined; + readonly metadata?: { + readonly unstable_state?: ReadonlyJSONValue; + readonly unstable_annotations?: readonly ReadonlyJSONValue[] | undefined; + readonly unstable_data?: readonly ReadonlyJSONValue[] | undefined; + readonly steps?: readonly ThreadStep[] | undefined; + readonly timing?: MessageTiming | undefined; + readonly custom?: Record<string, unknown> | undefined; + }; +}; + +type CompleteAttachment = BaseAttachment & { + status: CompleteAttachmentStatus; + content: ThreadUserMessagePart[]; +}; + +type CompleteAttachmentStatus = { + type: "complete"; +}; + +type ComposerRuntime = { + readonly path: ComposerRuntimePath; + readonly type: "edit" | "thread"; + getState(): ComposerState; + addAttachment(fileOrAttachment: File | CreateAttachment): Promise<void>; + setText(text: string): void; + setRole(role: MessageRole): void; + setRunConfig(runConfig: RunConfig): void; + reset(): Promise<void>; + clearAttachments(): Promise<void>; + send(options?: SendOptions): void; + cancel(): void; + steerQueueItem(queueItemId: string): void; + removeQueueItem(queueItemId: string): void; + subscribe(callback: () => void): Unsubscribe; + getAttachmentByIndex(idx: number): AttachmentRuntime; + startDictation(): void; + stopDictation(): void; + setQuote(quote: QuoteInfo | undefined): void; + unstable_on<E extends ComposerRuntimeEventType>(event: E, callback: ComposerRuntimeEventCallback<E>): Unsubscribe; +}; + +type ComposerRuntimeEventCallback<E extends ComposerRuntimeEventType> = (payload: ComposerRuntimeEventPayload[E]) => void; + +type ComposerRuntimeEventPayload = { + send: Record<string, never>; + attachmentAdd: Record<string, never>; + attachmentAddError: AttachmentAddErrorEvent; +}; + +type ComposerRuntimeEventType = keyof ComposerRuntimeEventPayload; + +type ComposerRuntimePath = (ThreadRuntimePath & { + readonly composerSource: "thread"; +}) | (MessageRuntimePath & { + readonly composerSource: "edit"; +}); + +type ComposerState = ThreadComposerState | EditComposerState; + +type ConvertEveMessagesOptions = { + readonly isRunning?: boolean | undefined; + readonly getCreatedAt?: ((message: EveMessage) => Date) | undefined; +}; + +type CreateAppendMessage = string | { + parentId?: string | null | undefined; + sourceId?: string | null | undefined; + role?: AppendMessage["role"] | undefined; + content: AppendMessage["content"]; + attachments?: AppendMessage["attachments"] | undefined; + metadata?: AppendMessage["metadata"] | undefined; + createdAt?: Date | undefined; + runConfig?: AppendMessage["runConfig"] | undefined; + startRun?: boolean | undefined; +}; + +type CreateAttachment = { + id?: string; + type?: "image" | "document" | "file" | (string & {}); + name: string; + contentType?: string; + content: ThreadUserMessagePart[]; +}; + +type CreateResumeRunConfig = CreateStartRunConfig & { + stream?: (options: ChatModelRunOptions) => AsyncGenerator<ChatModelRunResult, void, unknown>; +}; + +type CreateStartRunConfig = { + parentId: string | null; + sourceId?: string | null | undefined; + runConfig?: RunConfig | undefined; +}; + +type DataMessagePart<T = any> = { + readonly type: "data"; + readonly name: string; + readonly data: T; +}; + +type DataPrefixedPart = { + readonly type: `data-${string}`; + readonly data: any; +}; + +type DeepPartial<T> = T extends readonly any[] ? readonly DeepPartial<T[number]>[] : T extends { + [key: string]: any; +} ? { + readonly [K in keyof T]?: DeepPartial<T[K]>; +} : T; + +declare namespace DictationAdapter { + type Status = { + type: "running" | "starting"; + } | { + type: "ended"; + reason: "cancelled" | "error" | "stopped"; + }; + type Result = { + transcript: string; + isFinal?: boolean; + }; + type Session = { + status: Status; + stop: () => Promise<void>; + cancel: () => void; + onSpeechStart: (callback: () => void) => Unsubscribe; + onSpeechEnd: (callback: (result: Result) => void) => Unsubscribe; + onSpeech: (callback: (result: Result) => void) => Unsubscribe; + }; +} + +type DictationAdapter = { + listen: () => DictationAdapter.Session; + disableInputDuringDictation?: boolean; +}; + +type DictationState = { + readonly status: DictationAdapter.Status; + readonly transcript?: string; + readonly inputDisabled?: boolean; +}; + +type EditComposerAttachmentState = Attachment & { + readonly source: "edit-composer"; +}; + +type EditComposerRuntime = Omit<ComposerRuntime, "getAttachmentByIndex" | "getState"> & { + readonly path: ComposerRuntimePath & { + composerSource: "edit"; + }; + readonly type: "edit"; + getState(): EditComposerState; + beginEdit(): void; + getAttachmentByIndex(idx: number): AttachmentRuntime & { + source: "edit-composer"; + }; +}; + +type EditComposerState = BaseComposerState & { + readonly type: "edit"; + readonly parentId: string | null; + readonly sourceId: string | null; +}; + +type ExportedMessageRepository = { + headId?: string | null; + messages: Array<{ + message: ThreadMessage; + parentId: string | null; + runConfig?: RunConfig; + }>; +}; + +declare const ExportedMessageRepository: { + fromArray: (messages: readonly ThreadMessageLike[]) => ExportedMessageRepository; + fromBranchableArray: (items: readonly { + message: ThreadMessageLike; + parentId: string | null; + }[], options?: { + headId?: string | null; + }) => ExportedMessageRepository; +}; + +type ExternalStoreAdapter<T = ThreadMessage> = ExternalStoreAdapterBase<T> & (T extends ThreadMessage ? object : ExternalStoreMessageConverterAdapter<T>); + +type ExternalStoreAdapterBase<T> = { + isDisabled?: boolean | undefined; + isSendDisabled?: boolean | undefined; + isRunning?: boolean | undefined; + isLoading?: boolean | undefined; + messages?: readonly T[]; + messageRepository?: ExportedMessageRepository; + suggestions?: readonly ThreadSuggestion[] | undefined; + state?: ReadonlyJSONValue | undefined; + extras?: unknown; + setMessages?: ((messages: readonly T[]) => void) | undefined; + unstable_onBranchChange?: ((event: ExternalStoreBranchChange) => void) | undefined; + onImport?: ((messages: readonly ThreadMessage[]) => void) | undefined; + onExportExternalState?: (() => any) | undefined; + onLoadExternalState?: ((state: any) => void) | undefined; + onNew: (message: AppendMessage) => Promise<void>; + queue?: ExternalThreadQueueAdapter | undefined; + onEdit?: ((message: AppendMessage) => Promise<void>) | undefined; + onDelete?: ((messageId: string) => Promise<void> | void) | undefined; + onReload?: ((parentId: string | null, config: StartRunConfig) => Promise<void>) | undefined; + onResume?: ((config: ResumeRunConfig) => Promise<void>) | undefined; + onCancel?: (() => Promise<void>) | undefined; + onAddToolResult?: ((options: AddToolResultOptions) => Promise<void> | void) | undefined; + onResumeToolCall?: ((options: { + toolCallId: string; + payload: unknown; + }) => void) | undefined; + onRespondToToolApproval?: ((options: RespondToToolApprovalOptions) => Promise<void> | void) | undefined; + convertMessage?: ExternalStoreMessageConverter<T> | undefined; + adapters?: { + attachments?: AttachmentAdapter | undefined; + speech?: SpeechSynthesisAdapter | undefined; + dictation?: DictationAdapter | undefined; + voice?: RealtimeVoiceAdapter | undefined; + feedback?: FeedbackAdapter | undefined; + threadList?: ExternalStoreThreadListAdapter | undefined; + } | undefined; + unstable_capabilities?: { + copy?: boolean | undefined; + } | undefined; + unstable_enableToolInvocations?: boolean | undefined; + setToolStatuses?: ((statuses: Record<string, ToolExecutionStatus>) => void) | undefined; +}; + +type ExternalStoreBranchChange = { + headId: string | null; + visibleMessageIds: readonly string[]; +}; + +type ExternalStoreMessageConverter<T> = (message: T, idx: number) => ThreadMessageLike; + +type ExternalStoreMessageConverterAdapter<T> = { + convertMessage: ExternalStoreMessageConverter<T>; +}; + +type ExternalStoreSharedOptions = Pick<ExternalStoreAdapter, "isDisabled" | "isSendDisabled" | "suggestions" | "unstable_capabilities">; + +type ExternalStoreThreadData<TState extends "archived" | "regular"> = { + status: TState; + id: string; + remoteId?: string | undefined; + externalId?: string | undefined; + title?: string | undefined; + custom?: Record<string, unknown> | undefined; +}; + +type ExternalStoreThreadListAdapter = { + threadId?: string | undefined; + isLoading?: boolean | undefined; + threads?: readonly ExternalStoreThreadData<"regular">[] | undefined; + archivedThreads?: readonly ExternalStoreThreadData<"archived">[] | undefined; + onSwitchToNewThread?: (() => Promise<void> | void) | undefined; + onSwitchToThread?: ((threadId: string) => Promise<void> | void) | undefined; + onRename?: (threadId: string, newTitle: string) => (Promise<void> | void) | undefined; + onUpdateCustom?: ((threadId: string, custom: Record<string, unknown> | undefined) => Promise<void> | void) | undefined; + onArchive?: ((threadId: string) => Promise<void> | void) | undefined; + onUnarchive?: ((threadId: string) => Promise<void> | void) | undefined; + onDelete?: ((threadId: string) => Promise<void> | void) | undefined; +}; + +type ExternalThreadQueueAdapter = { + items: readonly QueueItemState[]; + enqueue: (message: AppendMessage, options: { + steer: boolean; + }) => void; + steer: (queueItemId: string) => void; + remove: (queueItemId: string) => void; + clear: (reason: "cancel-run" | "edit" | "reload") => void; +}; + +type FeedbackAdapter = { + submit: (feedback: FeedbackAdapterFeedback) => void; +}; + +type FeedbackAdapterFeedback = { + message: ThreadMessage; + type: "negative" | "positive"; +}; + +type FileMessagePart = { + readonly type: "file"; + readonly filename?: string; + readonly data: string; + readonly mimeType: string; + readonly parentId?: string; +}; + +type FrontendTool<TArgs extends Record<string, unknown> = Record<string, unknown>, TResult = unknown> = ToolBase<TArgs, TResult> & { + type: "frontend"; + description?: string | undefined; + parameters: StandardSchemaV1<TArgs> | JSONSchema7; + disabled?: boolean; + execute?: ToolExecuteFunction<TArgs, TResult>; + toModelOutput?: ToolModelOutputFunction<TArgs, TResult>; + experimental_onSchemaValidationError?: OnSchemaValidationErrorFunction<TResult>; + providerOptions?: ProviderOptions; +}; + +type GenerativeUIMessagePart = { + readonly type: "generative-ui"; + readonly spec: GenerativeUISpec; + readonly id?: string; + readonly parentId?: string; +}; + +type GenerativeUINode = string | { + readonly component: string; + readonly props?: Record<string, unknown>; + readonly children?: readonly GenerativeUINode[]; + readonly key?: string; +}; + +type GenerativeUISpec = { + readonly root: GenerativeUINode | readonly GenerativeUINode[]; +}; + +type HumanTool<TArgs extends Record<string, unknown> = Record<string, unknown>, TResult = unknown> = ToolBase<TArgs, TResult> & { + type: "human"; + description?: string | undefined; + parameters: StandardSchemaV1<TArgs> | JSONSchema7; + disabled?: boolean; + display?: "standalone"; + execute?: undefined; + toModelOutput?: undefined; + experimental_onSchemaValidationError?: undefined; + providerOptions?: ProviderOptions; +}; + +type ImageMessagePart = { + readonly type: "image"; + readonly image: string; + readonly filename?: string; +}; + +interface JSONSchema7 { + $id?: string | undefined; + $ref?: string | undefined; + $schema?: JSONSchema7Version | undefined; + $comment?: string | undefined; + $defs?: { + [key: string]: JSONSchema7Definition; + } | undefined; + type?: JSONSchema7TypeName | JSONSchema7TypeName[] | undefined; + enum?: JSONSchema7Type[] | undefined; + const?: JSONSchema7Type | undefined; + multipleOf?: number | undefined; + maximum?: number | undefined; + exclusiveMaximum?: number | undefined; + minimum?: number | undefined; + exclusiveMinimum?: number | undefined; + maxLength?: number | undefined; + minLength?: number | undefined; + pattern?: string | undefined; + items?: JSONSchema7Definition | JSONSchema7Definition[] | undefined; + additionalItems?: JSONSchema7Definition | undefined; + maxItems?: number | undefined; + minItems?: number | undefined; + uniqueItems?: boolean | undefined; + contains?: JSONSchema7Definition | undefined; + maxProperties?: number | undefined; + minProperties?: number | undefined; + required?: string[] | undefined; + properties?: { + [key: string]: JSONSchema7Definition; + } | undefined; + patternProperties?: { + [key: string]: JSONSchema7Definition; + } | undefined; + additionalProperties?: JSONSchema7Definition | undefined; + dependencies?: { + [key: string]: JSONSchema7Definition | string[]; + } | undefined; + propertyNames?: JSONSchema7Definition | undefined; + if?: JSONSchema7Definition | undefined; + then?: JSONSchema7Definition | undefined; + else?: JSONSchema7Definition | undefined; + allOf?: JSONSchema7Definition[] | undefined; + anyOf?: JSONSchema7Definition[] | undefined; + oneOf?: JSONSchema7Definition[] | undefined; + not?: JSONSchema7Definition | undefined; + format?: string | undefined; + contentMediaType?: string | undefined; + contentEncoding?: string | undefined; + definitions?: { + [key: string]: JSONSchema7Definition; + } | undefined; + title?: string | undefined; + description?: string | undefined; + default?: JSONSchema7Type | undefined; + readOnly?: boolean | undefined; + writeOnly?: boolean | undefined; + examples?: JSONSchema7Type | undefined; +} + +interface JSONSchema7Array extends Array<JSONSchema7Type> { +} + +type JSONSchema7Definition = JSONSchema7 | boolean; + +interface JSONSchema7Object { + [key: string]: JSONSchema7Type; +} + +type JSONSchema7Type = string | number | boolean | JSONSchema7Object | JSONSchema7Array | null; + +type JSONSchema7TypeName = "array" | "boolean" | "integer" | "null" | "number" | "object" | "string"; + +type JSONSchema7Version = string; + +type LanguageModelConfig = { + apiKey?: string; + baseUrl?: string; + modelName?: string; + reasoningEffort?: string; +}; + +type LanguageModelV1CallSettings = { + maxTokens?: number; + temperature?: number; + topP?: number; + presencePenalty?: number; + frequencyPenalty?: number; + seed?: number; + headers?: Record<string, string | undefined>; +}; + +type McpAppMetadata = { + readonly resourceUri: string; + readonly mimeType?: string; + readonly visibility?: readonly ("app" | "model")[]; +}; + +type McpServerConfig = { + type: "http" | "sse"; + url: string; + headers?: Record<string, string>; + redirect?: "error" | "follow"; + connectionTimeout?: number | undefined; +} | { + type: "stdio"; + command: string; + args?: readonly string[]; + env?: Record<string, string>; + cwd?: string; + connectionTimeout?: number | undefined; +}; + +type McpTool = ToolBase<Record<string, unknown>, unknown> & { + type: "mcp"; + server: McpServerConfig; + description?: undefined; + parameters?: undefined; + disabled?: boolean; + execute?: undefined; + toModelOutput?: undefined; + experimental_onSchemaValidationError?: undefined; + providerOptions?: undefined; +}; + +type MessageAttachmentState = CompleteAttachment & { + readonly source: "message"; +}; + +type MessageCommonProps = { + readonly id: string; + readonly createdAt: Date; +}; + +type MessagePartRuntime = { + addToolResult(result: any | ToolResponse<any>): void; + resumeToolCall(payload: unknown): void; + respondToToolApproval(response: ToolApprovalResponse): void; + readonly path: MessagePartRuntimePath; + getState(): MessagePartState; + subscribe(callback: () => void): Unsubscribe; +}; + +type MessagePartRuntimePath = MessageRuntimePath & { + readonly messagePartSelector: { + readonly type: "index"; + readonly index: number; + } | { + readonly type: "toolCallId"; + readonly toolCallId: string; + }; +}; + +type MessagePartState = (ThreadUserMessagePart | ThreadAssistantMessagePart) & { + readonly status: MessagePartStatus | ToolCallMessagePartStatus; +}; + +type MessagePartStatus = { + readonly type: "running"; +} | { + readonly type: "complete"; +} | { + readonly type: "incomplete"; + readonly reason: "cancelled" | "content-filter" | "error" | "length" | "other"; + readonly error?: unknown; +}; + +type MessageRole = ThreadMessage["role"]; + +type MessageRuntime = { + readonly path: MessageRuntimePath; + readonly composer: EditComposerRuntime; + getState(): MessageState; + delete(): void | Promise<void>; + reload(config?: ReloadConfig): void; + speak(): void; + stopSpeaking(): void; + submitFeedback(_param0: { + type: "positive" | "negative"; + }): void; + switchToBranch(_param1: { + position?: "previous" | "next" | undefined; + branchId?: string | undefined; + }): void; + unstable_getCopyText(): string; + subscribe(callback: () => void): Unsubscribe; + getMessagePartByIndex(idx: number): MessagePartRuntime; + getMessagePartByToolCallId(toolCallId: string): MessagePartRuntime; + getAttachmentByIndex(idx: number): AttachmentRuntime & { + source: "message"; + }; +}; + +type MessageRuntimePath = ThreadRuntimePath & { + readonly messageSelector: { + readonly type: "messageId"; + readonly messageId: string; + } | { + readonly type: "index"; + readonly index: number; + }; +}; + +type MessageState = ThreadMessage & { + readonly parentId: string | null; + readonly index: number; + readonly isLast: boolean; + readonly branchNumber: number; + readonly branchCount: number; + readonly speech: SpeechState | undefined; +}; + +type MessageStatus = { + readonly type: "running"; +} | { + readonly type: "requires-action"; + readonly reason: "interrupt" | "tool-calls"; +} | { + readonly type: "complete"; + readonly reason: "stop" | "unknown"; +} | { + readonly type: "incomplete"; + readonly reason: "cancelled" | "content-filter" | "error" | "length" | "other" | "tool-calls"; + readonly error?: ReadonlyJSONValue; +}; + +type MessageTiming = { + readonly streamStartTime: number; + readonly firstTokenTime?: number; + readonly totalStreamTime?: number; + readonly tokenCount?: number; + readonly tokensPerSecond?: number; + readonly totalChunks: number; + readonly toolCallCount: number; +}; + +type ModelContext = { + priority?: number | undefined; + system?: string | undefined; + tools?: Record<string, Tool<any, any>> | undefined; + callSettings?: LanguageModelV1CallSettings | undefined; + config?: LanguageModelConfig | undefined; + unstable_composerMetadata?: Record<string, unknown> | undefined; +}; + +type ModelContextProvider = { + getModelContext: () => ModelContext; + subscribe?: (callback: () => void) => Unsubscribe; +}; + +type ObjectKey<T> = keyof T & (string | number); + +type OnSchemaValidationErrorFunction<TResult> = ToolExecuteFunction<unknown, TResult>; + +type PartProviderMetadata = { + readonly [providerName: string]: ReadonlyJSONObject; +}; + +type PendingAttachment = BaseAttachment & { + status: PendingAttachmentStatus; + file: File; +}; + +type PendingAttachmentStatus = { + type: "running"; + reason: "uploading"; + progress: number; +} | { + type: "requires-action"; + reason: "composer-send"; +} | { + type: "incomplete"; + reason: "error" | "upload-paused"; + message?: string; +}; + +type ProviderOptions = Record<string, Record<string, unknown>>; + +type ProviderTool<TArgs extends Record<string, unknown> = Record<string, unknown>, TResult = unknown> = ToolBase<TArgs, TResult> & { + type: "provider"; + providerId: `${string}.${string}`; + parameters?: StandardSchemaV1<TArgs> | JSONSchema7 | undefined; + args: Record<string, unknown>; + supportsDeferredResults?: boolean; + description?: undefined; + disabled?: boolean; + execute?: undefined; + toModelOutput?: undefined; + experimental_onSchemaValidationError?: undefined; + providerOptions?: ProviderOptions; +}; + +type QueueItemState = { + readonly id: string; + readonly prompt: string; +}; + +type QuoteInfo = { + readonly text: string; + readonly messageId: string; +}; + +type ReadonlyJSONArray = readonly ReadonlyJSONValue[]; + +type ReadonlyJSONObject = { + readonly [key: string]: ReadonlyJSONValue; +}; + +type ReadonlyJSONValue = null | string | number | boolean | ReadonlyJSONObject | ReadonlyJSONArray; + +declare namespace RealtimeVoiceAdapter { + type Status = { + type: "running" | "starting"; + } | { + type: "ended"; + reason: "cancelled" | "error" | "finished"; + error?: unknown; + }; + type Mode = "listening" | "speaking"; + type TranscriptItem = { + role: "assistant" | "user"; + text: string; + isFinal?: boolean; + }; + type Session = { + status: Status; + isMuted: boolean; + disconnect: () => void; + mute: () => void; + unmute: () => void; + onStatusChange: (callback: (status: Status) => void) => Unsubscribe; + onTranscript: (callback: (transcript: TranscriptItem) => void) => Unsubscribe; + onModeChange: (callback: (mode: Mode) => void) => Unsubscribe; + onVolumeChange: (callback: (volume: number) => void) => Unsubscribe; + }; +} + +type RealtimeVoiceAdapter = { + connect: (options: { + abortSignal?: AbortSignal; + }) => RealtimeVoiceAdapter.Session; +}; + +type ReasoningMessagePart = { + readonly type: "reasoning"; + readonly text: string; + readonly providerMetadata?: PartProviderMetadata; + readonly parentId?: string; +}; + +type ReloadConfig = { + runConfig?: RunConfig; +}; + +type RespondToToolApprovalOptions = { + approvalId: string; + approved: boolean; + optionId?: string; + reason?: string; +}; + +type ResumeRunConfig = StartRunConfig & { + stream?: (options: ChatModelRunOptions) => AsyncGenerator<ChatModelRunResult, void, unknown>; +}; + +type RunConfig = { + readonly custom?: Record<string, unknown>; +}; + +type RuntimeCapabilities = { + readonly switchToBranch: boolean; + readonly switchBranchDuringRun: boolean; + readonly edit: boolean; + readonly reload: boolean; + readonly delete: boolean; + readonly cancel: boolean; + readonly unstable_copy: boolean; + readonly speech: boolean; + readonly dictation: boolean; + readonly voice: boolean; + readonly attachments: boolean; + readonly feedback: boolean; + readonly queue: boolean; +}; + +type SendOptions = { + startRun?: boolean; + steer?: boolean; +}; + +type SourceMessagePart = { + readonly type: "source"; + readonly sourceType: "url"; + readonly id: string; + readonly url: string; + readonly title?: string; + readonly providerMetadata?: SourceProviderMetadata; + readonly parentId?: string; +} | { + readonly type: "source"; + readonly sourceType: "document"; + readonly id: string; + readonly url?: undefined; + readonly title: string; + readonly mediaType: string; + readonly filename?: string; + readonly providerMetadata?: SourceProviderMetadata; + readonly parentId?: string; +}; + +type SourceProviderMetadata = PartProviderMetadata; + +interface SpeechRecognitionConstructor { + new (): SpeechRecognitionInstance; +} + +interface SpeechRecognitionInstance extends EventTarget { + lang: string; + continuous: boolean; + interimResults: boolean; + start(): void; + stop(): void; + abort(): void; +} + +type SpeechState = { + readonly messageId: string; + readonly status: SpeechSynthesisAdapter.Status; +}; + +declare namespace SpeechSynthesisAdapter { + type Status = { + type: "running" | "starting"; + } | { + type: "ended"; + reason: "cancelled" | "error" | "finished"; + error?: unknown; + }; + type Utterance = { + status: Status; + cancel: () => void; + subscribe: (callback: () => void) => Unsubscribe; + }; +} + +type SpeechSynthesisAdapter = { + speak: (text: string) => SpeechSynthesisAdapter.Utterance; +}; + +type StartRunConfig = { + parentId: string | null; + sourceId: string | null; + runConfig: RunConfig; +}; + +declare const TOOL_RESPONSE_SYMBOL: unique symbol; + +type TextMessagePart = { + readonly type: "text"; + readonly text: string; + readonly providerMetadata?: PartProviderMetadata; + readonly parentId?: string; +}; + +type ThreadAssistantMessage = MessageCommonProps & { + readonly role: "assistant"; + readonly content: readonly ThreadAssistantMessagePart[]; + readonly status: MessageStatus; + readonly metadata: { + readonly unstable_state: ReadonlyJSONValue; + readonly unstable_annotations: readonly ReadonlyJSONValue[]; + readonly unstable_data: readonly ReadonlyJSONValue[]; + readonly steps: readonly ThreadStep[]; + readonly submittedFeedback?: { + readonly type: "negative" | "positive"; + }; + readonly timing?: MessageTiming; + readonly isOptimistic?: boolean; + readonly custom: Record<string, unknown>; + }; +}; + +type ThreadAssistantMessagePart = TextMessagePart | ReasoningMessagePart | ToolCallMessagePart | SourceMessagePart | FileMessagePart | ImageMessagePart | DataMessagePart | GenerativeUIMessagePart; + +type ThreadComposerAttachmentState = Attachment & { + readonly source: "thread-composer"; +}; + +type ThreadComposerRuntime = Omit<ComposerRuntime, "getAttachmentByIndex" | "getState"> & { + readonly path: ComposerRuntimePath & { + composerSource: "thread"; + }; + readonly type: "thread"; + getState(): ThreadComposerState; + getAttachmentByIndex(idx: number): AttachmentRuntime & { + source: "thread-composer"; + }; +}; + +type ThreadComposerState = BaseComposerState & { + readonly type: "thread"; +}; + +type ThreadListItemEventCallback<E extends ThreadListItemEventType> = (payload: ThreadListItemEventPayload[E]) => void; + +type ThreadListItemEventPayload = { + switchedTo: Record<string, never>; + switchedAway: Record<string, never>; +}; + +type ThreadListItemEventType = keyof ThreadListItemEventPayload; + +type ThreadListItemRuntime = { + readonly path: ThreadListItemRuntimePath; + getState(): ThreadListItemState; + initialize(): Promise<{ + remoteId: string; + externalId: string | undefined; + }>; + generateTitle(): Promise<void>; + switchTo(options?: { + unarchive?: boolean; + }): Promise<void>; + rename(newTitle: string): Promise<void>; + updateCustom(custom: Record<string, unknown> | undefined): Promise<void>; + archive(): Promise<void>; + unarchive(): Promise<void>; + delete(): Promise<void>; + detach(): void; + subscribe(callback: () => void): Unsubscribe; + unstable_on<E extends ThreadListItemEventType>(event: E, callback: ThreadListItemEventCallback<E>): Unsubscribe; + __internal_getRuntime(): ThreadListItemRuntime; +}; + +type ThreadListItemRuntimePath = { + readonly ref: string; + readonly threadSelector: { + readonly type: "main"; + } | { + readonly type: "index"; + readonly index: number; + } | { + readonly type: "archiveIndex"; + readonly index: number; + } | { + readonly type: "threadId"; + readonly threadId: string; + }; +}; + +type ThreadListItemState = { + readonly isMain: boolean; + readonly id: string; + readonly remoteId: string | undefined; + readonly externalId: string | undefined; + readonly status: ThreadListItemStatus; + readonly title?: string | undefined; + readonly lastMessageAt?: Date | undefined; + readonly custom?: Record<string, unknown> | undefined; +}; + +type ThreadListItemStatus = "archived" | "deleted" | "new" | "regular"; + +type ThreadListRuntime = { + getState(): ThreadListState; + subscribe(callback: () => void): Unsubscribe; + readonly main: ThreadRuntime; + getById(threadId: string): ThreadRuntime; + readonly mainItem: ThreadListItemRuntime; + getItemById(threadId: string): ThreadListItemRuntime; + getItemByIndex(idx: number): ThreadListItemRuntime; + getArchivedItemByIndex(idx: number): ThreadListItemRuntime; + switchToThread(threadId: string, options?: { + unarchive?: boolean; + }): Promise<void>; + switchToNewThread(): Promise<void>; + getLoadThreadsPromise(): Promise<void>; + reload(): Promise<void>; + loadMore(): Promise<void>; +}; + +type ThreadListState = { + readonly mainThreadId: string; + readonly newThreadId: string | undefined; + readonly threadIds: readonly string[]; + readonly archivedThreadIds: readonly string[]; + readonly isLoading: boolean; + readonly isLoadingMore: boolean; + readonly hasMore: boolean; + readonly threadItems: Readonly<Record<string, Omit<ThreadListItemState, "isMain" | "threadId">>>; +}; + +type ThreadMessage = BaseThreadMessage & (ThreadSystemMessage | ThreadUserMessage | ThreadAssistantMessage); + +type ThreadMessageLike = { + readonly role: "assistant" | "system" | "user"; + readonly content: string | readonly (TextMessagePart | ReasoningMessagePart | SourceMessagePart | ImageMessagePart | FileMessagePart | DataMessagePart | GenerativeUIMessagePart | Unstable_AudioMessagePart | DataPrefixedPart | { + readonly type: "tool-call"; + readonly toolCallId?: string; + readonly toolName: string; + readonly args?: ReadonlyJSONObject; + readonly argsText?: string; + readonly artifact?: any; + readonly result?: any | undefined; + readonly isError?: boolean | undefined; + readonly parentId?: string | undefined; + readonly messages?: readonly ThreadMessage[] | undefined; + readonly interrupt?: { + type: "human"; + payload: unknown; + }; + readonly timing?: ToolCallTiming; + readonly providerMetadata?: PartProviderMetadata; + readonly approval?: { + readonly id: string; + readonly approved?: boolean; + readonly reason?: string; + readonly isAutomatic?: boolean; + readonly options?: readonly ToolApprovalOption[]; + readonly optionId?: string; + readonly resolution?: "cancelled" | "expired"; + }; + })[]; + readonly id?: string | undefined; + readonly createdAt?: Date | undefined; + readonly status?: MessageStatus | undefined; + readonly attachments?: readonly (Omit<CompleteAttachment, "content"> & { + readonly content: readonly (ThreadUserMessagePart | DataPrefixedPart)[]; + })[] | undefined; + readonly metadata?: { + readonly unstable_state?: ReadonlyJSONValue; + readonly unstable_annotations?: readonly ReadonlyJSONValue[] | undefined; + readonly unstable_data?: readonly ReadonlyJSONValue[] | undefined; + readonly steps?: readonly ThreadStep[] | undefined; + readonly timing?: MessageTiming | undefined; + readonly submittedFeedback?: { + readonly type: "negative" | "positive"; + }; + readonly isOptimistic?: boolean | undefined; + readonly custom?: Record<string, unknown> | undefined; + } | undefined; +}; + +type ThreadRuntime = { + readonly path: ThreadRuntimePath; + readonly composer: ThreadComposerRuntime; + getState(): ThreadState; + append(message: CreateAppendMessage): void; + deleteMessage(messageId: string): void | Promise<void>; + startRun(config: CreateStartRunConfig): void; + resumeRun(config: CreateResumeRunConfig): void; + exportExternalState(): any; + importExternalState(state: any): void; + subscribe(callback: () => void): Unsubscribe; + cancelRun(): void; + getModelContext(): ModelContext; + export(): ExportedMessageRepository; + import(repository: ExportedMessageRepository): void; + reset(initialMessages?: readonly ThreadMessageLike[]): void; + getMessageByIndex(idx: number): MessageRuntime; + getMessageById(messageId: string): MessageRuntime; + stopSpeaking(): void; + connectVoice(): void; + disconnectVoice(): void; + getVoiceVolume(): number; + subscribeVoiceVolume(callback: () => void): Unsubscribe; + muteVoice(): void; + unmuteVoice(): void; + unstable_on<E extends ThreadRuntimeEventType>(event: E, callback: ThreadRuntimeEventCallback<E>): Unsubscribe; +}; + +type ThreadRuntimeEventCallback<E extends ThreadRuntimeEventType> = (payload: ThreadRuntimeEventPayload[E]) => void; + +type ThreadRuntimeEventPayload = { + runStart: Record<string, never>; + runEnd: Record<string, never>; + initialize: Record<string, never>; + modelContextUpdate: Record<string, never>; +}; + +type ThreadRuntimeEventType = keyof ThreadRuntimeEventPayload; + +type ThreadRuntimePath = { + readonly ref: string; + readonly threadSelector: { + readonly type: "main"; + } | { + readonly type: "threadId"; + readonly threadId: string; + }; +}; + +type ThreadState = { + readonly threadId: string; + readonly metadata: ThreadListItemState; + readonly isDisabled: boolean; + readonly isLoading: boolean; + readonly isRunning: boolean; + readonly capabilities: RuntimeCapabilities; + readonly messages: readonly ThreadMessage[]; + readonly state: ReadonlyJSONValue; + readonly suggestions: readonly ThreadSuggestion[]; + readonly extras: unknown; + readonly speech: SpeechState | undefined; + readonly voice: VoiceSessionState | undefined; +}; + +type ThreadStep = { + readonly messageId?: string; + readonly usage?: { + readonly inputTokens: number; + readonly outputTokens: number; + } | undefined; +}; + +type ThreadSuggestion = { + prompt: string; +}; + +type ThreadSystemMessage = MessageCommonProps & { + readonly role: "system"; + readonly content: readonly [ + TextMessagePart + ]; + readonly metadata: { + readonly unstable_state?: undefined; + readonly unstable_annotations?: undefined; + readonly unstable_data?: undefined; + readonly steps?: undefined; + readonly submittedFeedback?: undefined; + readonly timing?: undefined; + readonly custom: Record<string, unknown>; + }; +}; + +type ThreadUserMessage = MessageCommonProps & { + readonly role: "user"; + readonly content: readonly ThreadUserMessagePart[]; + readonly attachments: readonly CompleteAttachment[]; + readonly metadata: { + readonly unstable_state?: undefined; + readonly unstable_annotations?: undefined; + readonly unstable_data?: undefined; + readonly steps?: undefined; + readonly submittedFeedback?: undefined; + readonly timing?: undefined; + readonly custom: Record<string, unknown>; + }; +}; + +type ThreadUserMessagePart = TextMessagePart | ImageMessagePart | FileMessagePart | DataMessagePart | Unstable_AudioMessagePart; + +type Tool<TArgs extends Record<string, unknown> = Record<string, unknown>, TResult = unknown> = FrontendTool<TArgs, TResult> | BackendTool<TArgs, TResult> | HumanTool<TArgs, TResult> | ProviderTool<TArgs, TResult> | McpTool | ToolWithoutType<TArgs, TResult>; + +type ToolApprovalOption = { + readonly id: string; + readonly kind: ToolApprovalOptionKind | (string & {}); + readonly label?: string; + readonly description?: string; + readonly grants?: readonly string[]; + readonly confirm?: boolean | { + title?: string; + description?: string; + }; +}; + +type ToolApprovalOptionKind = "allow-always" | "allow-once" | "reject-always" | "reject-once"; + +type ToolApprovalResponse = { + readonly approved: boolean; + readonly reason?: string; +} | { + readonly optionId: string; + readonly reason?: string; +} | { + readonly approved: boolean; + readonly optionId: string; + readonly reason?: string; +}; + +type ToolBase<TArgs extends Record<string, unknown> = Record<string, unknown>, TResult = unknown> = { + streamCall?: ToolStreamCallFunction<TArgs, TResult>; + display?: ToolDisplay; +}; + +interface ToolCallArgsReader<TArgs extends Record<string, unknown>> { + get<PathT extends TypePath<TArgs>>(...fieldPath: PathT): Promise<TypeAtPath<TArgs, PathT>>; + streamValues<PathT extends TypePath<TArgs>>(...fieldPath: PathT): AsyncIterableStream<DeepPartial<TypeAtPath<TArgs, PathT>>>; + streamText<PathT extends TypePath<TArgs>>(...fieldPath: PathT): TypeAtPath<TArgs, PathT> extends string & (infer U) ? AsyncIterableStream<U> : never; + forEach<PathT extends TypePath<TArgs>>(...fieldPath: PathT): NonNullable<TypeAtPath<TArgs, PathT>> extends Array<infer U> ? AsyncIterableStream<U> : never; +} + +type ToolCallMessagePart<TArgs = ReadonlyJSONObject, TResult = unknown> = { + readonly type: "tool-call"; + readonly toolCallId: string; + readonly toolName: string; + readonly args: TArgs; + readonly result?: TResult | undefined; + readonly isError?: boolean | undefined; + readonly argsText: string; + readonly artifact?: unknown; + readonly timing?: ToolCallTiming; + readonly mcp?: ToolCallMessagePartMcpMetadata; + readonly providerMetadata?: PartProviderMetadata; + readonly modelContent?: readonly ToolModelContentPart[] | undefined; + readonly interrupt?: { + type: "human"; + payload: unknown; + }; + readonly approval?: { + readonly id: string; + readonly approved?: boolean; + readonly reason?: string; + readonly isAutomatic?: boolean; + readonly options?: readonly ToolApprovalOption[]; + readonly optionId?: string; + readonly resolution?: "cancelled" | "expired"; + }; + readonly parentId?: string; + readonly messages?: readonly ThreadMessage[]; +}; + +type ToolCallMessagePartMcpMetadata = { + readonly app?: McpAppMetadata; +}; + +type ToolCallMessagePartStatus = { + readonly type: "requires-action"; + readonly reason: "interrupt"; +} | MessagePartStatus; + +interface ToolCallReader<TArgs extends Record<string, unknown> = Record<string, unknown>, TResult = unknown> { + args: ToolCallArgsReader<TArgs>; + response: ToolCallResponseReader<TResult>; + result: { + get: () => Promise<TResult>; + }; +} + +interface ToolCallResponseReader<TResult> { + get: () => Promise<ToolResponse<TResult>>; +} + +type ToolCallTiming = { + readonly startedAt: number; + readonly completedAt?: number; +}; + +type ToolDisplay = "inline" | "standalone"; + +type ToolExecuteFunction<TArgs, TResult> = (args: TArgs, context: ToolExecutionContext) => TResult | Promise<TResult>; + +type ToolExecutionContext = { + toolCallId: string; + abortSignal: AbortSignal; + human: (payload: unknown) => Promise<unknown>; +}; + +type ToolExecutionStatus = { + type: "executing"; +} | { + type: "interrupt"; + payload: { + type: "human"; + payload: unknown; + }; +}; + +type ToolModelContentPart = { + readonly type: "text"; + readonly text: string; +} | { + readonly type: "file"; + readonly data: string; + readonly mediaType: string; + readonly filename?: string; +}; + +type ToolModelOutputFunction<TArgs, TResult> = (options: { + toolCallId: string; + input: TArgs; + output: TResult; +}) => readonly ToolModelContentPart[] | Promise<readonly ToolModelContentPart[]>; + +declare class ToolResponse<TResult> { + get [TOOL_RESPONSE_SYMBOL](): boolean; + readonly artifact?: ReadonlyJSONValue; + readonly result: TResult; + readonly isError: boolean; + readonly modelContent?: readonly ToolModelContentPart[]; + readonly messages?: ReadonlyJSONValue; + constructor(options: ToolResponseLike<TResult>); + static [Symbol.hasInstance](obj: unknown): obj is ToolResponse<ReadonlyJSONValue>; + static toResponse(result: any | ToolResponse<any>): ToolResponse<any>; +} + +type ToolResponseLike<TResult> = { + result: TResult; + artifact?: ReadonlyJSONValue | undefined; + isError?: boolean | undefined; + modelContent?: readonly ToolModelContentPart[] | undefined; + messages?: ReadonlyJSONValue | undefined; +}; + +type ToolStreamCallFunction<TArgs extends Record<string, unknown> = Record<string, unknown>, TResult = unknown> = (reader: ToolCallReader<TArgs, TResult>, context: ToolExecutionContext) => void; + +type ToolWithoutType<TArgs extends Record<string, unknown> = Record<string, unknown>, TResult = unknown> = (Omit<FrontendTool<TArgs, TResult>, "type"> | Omit<BackendTool<TArgs, TResult>, "type"> | Omit<HumanTool<TArgs, TResult>, "type"> | Omit<ProviderTool<TArgs, TResult>, "type">) & { + type?: undefined; +}; + +type TupleIndex<T extends readonly any[]> = Exclude<keyof T, keyof any[]>; + +type TypeAtPath<T, P extends readonly any[]> = P extends [ + infer Head, + ...infer Rest +] ? Head extends keyof T ? TypeAtPath<T[Head], Rest> : never : T; + +type TypePath<T> = [ +] | (0 extends 1 & T ? any[] : T extends object ? T extends readonly any[] ? number extends T["length"] ? { + [K in TupleIndex<T>]: [ + AsNumber<K>, + ...TypePath<T[K]> + ]; +}[TupleIndex<T>] : [ + number, + ...TypePath<T[number]> +] : { + [K in ObjectKey<T>]: [ + K, + ...TypePath<T[K]> + ]; +}[ObjectKey<T>] : [ +]); + +type Unstable_AudioMessagePart = { + readonly type: "audio"; + readonly audio: { + readonly data: string; + readonly format: "mp3" | "wav"; + }; +}; + +type Unsubscribe = () => void; + +type UseEveAgentRuntimeOptions = Omit<UseEveAgentOptions<EveMessageData>, "reducer"> & ExternalStoreSharedOptions & { + readonly adapters?: { + readonly attachments?: AttachmentAdapter | undefined; + readonly speech?: SpeechSynthesisAdapter | undefined; + readonly dictation?: DictationAdapter | undefined; + readonly voice?: RealtimeVoiceAdapter | undefined; + readonly feedback?: FeedbackAdapter | undefined; + } | undefined; +}; + +type VoiceSessionState = { + readonly status: RealtimeVoiceAdapter.Status; + readonly isMuted: boolean; + readonly mode: RealtimeVoiceAdapter.Mode; +}; + +declare const convertEveMessage: (message: EveMessage, index: number, messages: readonly EveMessage[], options?: ConvertEveMessagesOptions) => ThreadMessage; + +declare const convertEveMessages: (data: EveMessageData, options?: ConvertEveMessagesOptions) => ThreadMessage[]; + +declare const getEveMessageContent: (message: AppendMessage) => NonNullable<SendTurnPayload["message"]>; + +declare global { + interface Window { + SpeechRecognition?: SpeechRecognitionConstructor; + webkitSpeechRecognition?: SpeechRecognitionConstructor; + } +} + +declare namespace entry_root_exports { + export { ConvertEveMessagesOptions, UseEveAgentRuntimeOptions, convertEveMessage, convertEveMessages, getEveMessageContent, useEveAgentRuntime }; +} + +declare const useEveAgentRuntime: (options?: UseEveAgentRuntimeOptions) => AssistantRuntime; + +export { entry_root_exports as entry_root }; diff --git a/api-surface/assistant-ui__gorp.ts b/api-surface/assistant-ui__gorp.ts new file mode 100644 index 0000000..1b5bc3a --- /dev/null +++ b/api-surface/assistant-ui__gorp.ts @@ -0,0 +1,144 @@ +type DeepReadonly<T> = T extends ((...args: never[]) => unknown) ? T : T extends object ? { + readonly [K in keyof T]: DeepReadonly<T[K]>; +} : T; + +declare class GorpClient<T extends Record<string, unknown>, C> { + private readonly mutator; + private readonly _send; + private committed; + private optimistic; + private _pending; + private _nextSeq; + private readonly changeListeners; + private changes; + private previousState; + private optimisticDirty; + constructor(config: GorpClient.Config<T, C>); + get state(): DeepReadonly<T>; + get firstPendingSeq(): number; + apply(_param0: GorpMessage): void; + send(command: C): void; + onChange(callback: () => void): () => void; + resync(): void; + isChangedAt(path: string[]): boolean; + getChangedKeys(path: string[]): string[]; + private beginFrame; + private replayMutator; + private rebuildOptimistic; +} + +declare namespace GorpClient { + type Config<T extends Record<string, unknown>, C> = { + initialState: T; + mutator: (state: T, command: C, seq: number) => void; + send: (command: C) => void; + }; +} + +type GorpMessage = { + ops: GorpOperation[]; + ack?: number; +}; + +type GorpOperation = { + type: "set"; + path: string[]; + value: unknown; +} | { + type: "append-text"; + path: string[]; + value: string; +}; + +interface GorpPubsub<C> { + readonly state: unknown; + receive(command: C): void; + subscribe(callback: (env: GorpMessage) => void): () => void; +} + +declare class GorpRelay<T extends Record<string, unknown>, C> { + private readonly gorp; + private readonly _send; + private readonly subscribers; + constructor(config: GorpRelay.Config<T, C>); + get state(): DeepReadonly<T>; + receive(command: C): void; + applyUpstream(msg: GorpMessage): void; + subscribe(callback: (env: GorpMessage) => void): () => void; + serialize(): RelaySerializedState<T>; + restore(serialized: RelaySerializedState<T>): void; +} + +declare namespace GorpRelay { + type Config<T extends Record<string, unknown>, C> = { + initialState: T; + send: (command: C) => void; + }; +} + +declare class GorpServer<T extends Record<string, unknown>, C> { + private readonly gorp; + private readonly mutator; + private readonly flusher; + private readonly _state; + private _nextSeq; + constructor(config: GorpServer.Config<T, C>); + get state(): T; + set state(value: T); + receive(command: C): void; + subscribe(callback: (env: GorpMessage) => void): () => void; +} + +declare namespace GorpServer { + type Config<T extends Record<string, unknown>, C> = { + initialState: T; + mutator: (state: T, command: C, seq: number) => void; + }; +} + +type GorpSession = { + highWater: number; + lastActivity: number; +}; + +declare class GorpSessions<C> { + private readonly inner; + private readonly _sessions; + private readonly clients; + private outgoing; + private lastSeenAck; + private readonly unsubscribe; + constructor(inner: GorpPubsub<C>); + get sessions(): ReadonlyMap<string, GorpSession>; + addClient(sessionId: string, fromSeq: number, send: (msg: GorpMessage) => void): GorpSessions.ClientHandle<C>; + close(): void; + serialize(): GorpSessionsState; + restore(state: GorpSessionsState): void; + private fanOut; + private pruneSessions; +} + +declare namespace GorpSessions { + interface ClientHandle<C> { + receive(command: C): void; + remove(): void; + } +} + +type GorpSessionsState = { + sessions: Record<string, { + highWater: number; + }>; +}; + +type RelaySerializedState<T> = { + state: T; +}; + +declare function appendText(value: string): string; + +declare namespace entry_root_exports { + export { GorpClient, GorpMessage, GorpRelay, GorpServer, GorpSessions, GorpSessionsState, RelaySerializedState, appendText }; +} + +export { entry_root_exports as entry_root }; diff --git a/api-surface/assistant-ui__mcp-docs-server.ts b/api-surface/assistant-ui__mcp-docs-server.ts new file mode 100644 index 0000000..ff8b6cf --- /dev/null +++ b/api-surface/assistant-ui__mcp-docs-server.ts @@ -0,0 +1,11 @@ +import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js"; + +declare namespace entry_root_exports { + export { runServer, server }; +} + +declare function runServer(): Promise<void>; + +declare const server: McpServer; + +export { entry_root_exports as entry_root }; diff --git a/api-surface/assistant-ui__metro.ts b/api-surface/assistant-ui__metro.ts new file mode 100644 index 0000000..18cbc82 --- /dev/null +++ b/api-surface/assistant-ui__metro.ts @@ -0,0 +1,40 @@ +type BabelTransformer = { + transform: (props: { + filename: string; + src: string; + options?: { + customTransformOptions?: { + environment?: string; + } | undefined; + } | undefined; + [key: string]: unknown; + }) => unknown; + getCacheKey?: (() => string) | undefined; + [key: string]: unknown; +}; + +type MetroConfigLike = { + transformer?: { + babelTransformerPath?: string | undefined; + [key: string]: unknown; + } | undefined; + [key: string]: unknown; +}; + +declare const UPSTREAM_TRANSFORMER_ENV = "AUI_METRO_UPSTREAM_TRANSFORMER"; + +declare function getCacheKey(): string; + +declare namespace entry_root_exports { + export { MetroConfigLike, UPSTREAM_TRANSFORMER_ENV, withAui }; +} + +declare function transform(props: Parameters<BabelTransformer["transform"]>[0]): unknown; + +declare namespace entry_transformer_exports { + export { getCacheKey, transform }; +} + +declare function withAui<T extends MetroConfigLike>(config: T): T; + +export { entry_root_exports as entry_root, entry_transformer_exports as entry_transformer }; diff --git a/api-surface/assistant-ui__next.ts b/api-surface/assistant-ui__next.ts new file mode 100644 index 0000000..fbe99ec --- /dev/null +++ b/api-surface/assistant-ui__next.ts @@ -0,0 +1,34 @@ +interface GenerativeLoaderContext { + resourcePath?: string; + resourceQuery?: string; + sourceMap?: boolean; + getOptions?(): { + path?: string; + } | undefined; + async(): (err: unknown, code?: string, map?: object | null) => void; +} + +type NextConfigLike = { + turbopack?: { + rules?: Record<string, unknown>; + } | undefined; + webpack?: ((config: any, context: any) => any) | null | undefined; +}; + +interface WithAuiOptions { + rules?: string[]; +} + +declare function generativeLoader(this: GenerativeLoaderContext, source: string): void; + +declare namespace entry_root_exports { + export { WithAuiOptions, withAui }; +} + +declare namespace entry_loader_exports { + export { generativeLoader as default }; +} + +declare function withAui<T extends NextConfigLike>(nextConfig?: T, options?: WithAuiOptions): T; + +export { entry_loader_exports as entry_loader, entry_root_exports as entry_root }; diff --git a/api-surface/assistant-ui__react-a2a.ts b/api-surface/assistant-ui__react-a2a.ts new file mode 100644 index 0000000..7bb34eb --- /dev/null +++ b/api-surface/assistant-ui__react-a2a.ts @@ -0,0 +1,1874 @@ +import { StandardSchemaV1 } from "@standard-schema/spec"; + +type A2AAgentCapabilities = { + streaming?: boolean | undefined; + pushNotifications?: boolean | undefined; + extensions?: Array<{ + uri: string; + description?: string | undefined; + required?: boolean | undefined; + params?: Record<string, unknown> | undefined; + }> | undefined; + extendedAgentCard?: boolean | undefined; +}; + +type A2AAgentCard = { + name: string; + description: string; + supportedInterfaces: A2AAgentInterface[]; + provider?: { + organization: string; + url: string; + } | undefined; + version: string; + documentationUrl?: string | undefined; + capabilities: A2AAgentCapabilities; + securitySchemes?: Record<string, A2ASecurityScheme> | undefined; + securityRequirements?: A2ASecurityRequirement[] | undefined; + defaultInputModes: string[]; + defaultOutputModes: string[]; + skills: A2AAgentSkill[]; + signatures?: A2AAgentCardSignature[] | undefined; + iconUrl?: string | undefined; +}; + +type A2AAgentCardSignature = { + protected: string; + signature: string; + header?: Record<string, unknown> | undefined; +}; + +type A2AAgentInterface = { + url: string; + protocolBinding: string; + protocolVersion: string; + tenant?: string | undefined; +}; + +type A2AAgentSkill = { + id: string; + name: string; + description: string; + tags: string[]; + examples?: string[] | undefined; + inputModes?: string[] | undefined; + outputModes?: string[] | undefined; + securityRequirements?: A2ASecurityRequirement[] | undefined; +}; + +type A2AApiKeySecurityScheme = { + description?: string | undefined; + location: string; + name: string; +}; + +type A2AArtifact = { + artifactId: string; + name?: string | undefined; + description?: string | undefined; + parts: A2APart[]; + metadata?: Record<string, unknown> | undefined; + extensions?: string[] | undefined; +}; + +type A2AAuthenticationInfo = { + scheme: string; + credentials?: string | undefined; +}; + +type A2AAuthorizationCodeOAuthFlow = { + authorizationUrl: string; + tokenUrl: string; + refreshUrl?: string | undefined; + scopes: Record<string, string>; + pkceRequired?: boolean | undefined; +}; + +declare class A2AClient { + private baseUrl; + private basePath; + private tenant; + private extensionUris; + private fetchOptions; + private headersFn; + constructor(options: A2AClientOptions); + private getBasePath; + private getHeaders; + private throwResponseError; + private fetchJSON; + getAgentCard(signal?: AbortSignal): Promise<A2AAgentCard>; + getExtendedAgentCard(signal?: AbortSignal): Promise<A2AAgentCard>; + sendMessage(message: A2AMessage, configuration?: A2ASendMessageConfiguration, metadata?: Record<string, unknown>, signal?: AbortSignal): Promise<A2ATask | A2AMessage>; + streamMessage(message: A2AMessage, configuration?: A2ASendMessageConfiguration, metadata?: Record<string, unknown>, signal?: AbortSignal): AsyncGenerator<A2AStreamEvent>; + getTask(taskId: string, historyLength?: number, signal?: AbortSignal): Promise<A2ATask>; + listTasks(request?: A2AListTasksRequest, signal?: AbortSignal): Promise<A2AListTasksResponse>; + cancelTask(taskId: string, metadata?: Record<string, unknown>, signal?: AbortSignal): Promise<A2ATask>; + subscribeToTask(taskId: string, signal?: AbortSignal): AsyncGenerator<A2AStreamEvent>; + createTaskPushNotificationConfig(config: A2ATaskPushNotificationConfig, signal?: AbortSignal): Promise<A2ATaskPushNotificationConfig>; + getTaskPushNotificationConfig(taskId: string, configId: string, signal?: AbortSignal): Promise<A2ATaskPushNotificationConfig>; + listTaskPushNotificationConfigs(taskId: string, options?: { + pageSize?: number; + pageToken?: string; + }, signal?: AbortSignal): Promise<A2AListTaskPushNotificationConfigsResponse>; + deleteTaskPushNotificationConfig(taskId: string, configId: string, signal?: AbortSignal): Promise<void>; + private parseSSE; +} + +type A2AClientCredentialsOAuthFlow = { + tokenUrl: string; + refreshUrl?: string | undefined; + scopes: Record<string, string>; +}; + +type A2AClientOptions = { + baseUrl: string; + basePath?: string | undefined; + tenant?: string | undefined; + headers?: Record<string, string> | (() => Record<string, string> | Promise<Record<string, string>>) | undefined; + extensions?: string[] | undefined; + fetchOptions?: Omit<RequestInit, "body" | "headers" | "method" | "signal"> | undefined; +}; + +type A2ADeviceCodeOAuthFlow = { + deviceAuthorizationUrl: string; + tokenUrl: string; + refreshUrl?: string | undefined; + scopes: Record<string, string>; +}; + +declare class A2AError extends Error { + code: number; + status: string; + details: unknown[] | undefined; + constructor(info: A2AErrorInfo); +} + +type A2AErrorInfo = { + code: number; + status: string; + message: string; + details?: unknown[] | undefined; +}; + +type A2AHttpAuthSecurityScheme = { + description?: string | undefined; + scheme: string; + bearerFormat?: string | undefined; +}; + +type A2AImplicitOAuthFlow = { + authorizationUrl?: string | undefined; + refreshUrl?: string | undefined; + scopes?: Record<string, string> | undefined; +}; + +type A2AListTaskPushNotificationConfigsResponse = { + configs: A2ATaskPushNotificationConfig[]; + nextPageToken?: string | undefined; +}; + +type A2AListTasksRequest = { + contextId?: string | undefined; + status?: A2ATaskState | undefined; + pageSize?: number | undefined; + pageToken?: string | undefined; + historyLength?: number | undefined; + statusTimestampAfter?: string | undefined; + includeArtifacts?: boolean | undefined; +}; + +type A2AListTasksResponse = { + tasks: A2ATask[]; + nextPageToken: string; + pageSize: number; + totalSize: number; +}; + +type A2AMessage = { + messageId: string; + contextId?: string | undefined; + taskId?: string | undefined; + role: A2ARole; + parts: A2APart[]; + metadata?: Record<string, unknown> | undefined; + extensions?: string[] | undefined; + referenceTaskIds?: string[] | undefined; +}; + +type A2AMutualTlsSecurityScheme = { + description?: string | undefined; +}; + +type A2AOAuth2SecurityScheme = { + description?: string | undefined; + flows: A2AOAuthFlows; + oauth2MetadataUrl?: string | undefined; +}; + +type A2AOAuthFlows = { + authorizationCode?: A2AAuthorizationCodeOAuthFlow | undefined; + clientCredentials?: A2AClientCredentialsOAuthFlow | undefined; + implicit?: A2AImplicitOAuthFlow | undefined; + password?: A2APasswordOAuthFlow | undefined; + deviceCode?: A2ADeviceCodeOAuthFlow | undefined; +}; + +type A2AOpenIdConnectSecurityScheme = { + description?: string | undefined; + openIdConnectUrl: string; +}; + +type A2APart = { + text?: string | undefined; + raw?: string | undefined; + url?: string | undefined; + data?: unknown; + metadata?: Record<string, unknown> | undefined; + filename?: string | undefined; + mediaType?: string | undefined; +}; + +type A2APasswordOAuthFlow = { + tokenUrl?: string | undefined; + refreshUrl?: string | undefined; + scopes?: Record<string, string> | undefined; +}; + +type A2ARole = "agent" | "unspecified" | "user"; + +type A2ASecurityRequirement = { + schemes: Record<string, { + list: string[]; + }>; +}; + +type A2ASecurityScheme = { + apiKeySecurityScheme?: A2AApiKeySecurityScheme | undefined; + httpAuthSecurityScheme?: A2AHttpAuthSecurityScheme | undefined; + oauth2SecurityScheme?: A2AOAuth2SecurityScheme | undefined; + openIdConnectSecurityScheme?: A2AOpenIdConnectSecurityScheme | undefined; + mtlsSecurityScheme?: A2AMutualTlsSecurityScheme | undefined; +}; + +type A2ASendMessageConfiguration = { + acceptedOutputModes?: string[] | undefined; + taskPushNotificationConfig?: A2ATaskPushNotificationConfig | undefined; + historyLength?: number | undefined; + returnImmediately?: boolean | undefined; +}; + +type A2AStreamEvent = { + type: "task"; + task: A2ATask; +} | { + type: "message"; + message: A2AMessage; +} | { + type: "statusUpdate"; + event: A2ATaskStatusUpdateEvent; +} | { + type: "artifactUpdate"; + event: A2ATaskArtifactUpdateEvent; +}; + +type A2ATask = { + id: string; + contextId?: string | undefined; + status: A2ATaskStatus; + artifacts?: A2AArtifact[] | undefined; + history?: A2AMessage[] | undefined; + metadata?: Record<string, unknown> | undefined; +}; + +type A2ATaskArtifactUpdateEvent = { + taskId: string; + contextId: string; + artifact: A2AArtifact; + append?: boolean | undefined; + lastChunk?: boolean | undefined; + metadata?: Record<string, unknown> | undefined; +}; + +type A2ATaskPushNotificationConfig = { + tenant?: string | undefined; + id?: string | undefined; + taskId?: string | undefined; + url: string; + token?: string | undefined; + authentication?: A2AAuthenticationInfo | undefined; +}; + +type A2ATaskState = "auth_required" | "canceled" | "completed" | "failed" | "input_required" | "rejected" | "submitted" | "unspecified" | "working"; + +type A2ATaskStatus = { + state: A2ATaskState; + message?: A2AMessage | undefined; + timestamp?: string | undefined; +}; + +type A2ATaskStatusUpdateEvent = { + taskId: string; + contextId: string; + status: A2ATaskStatus; + metadata?: Record<string, unknown> | undefined; +}; + +declare const A2A_PROTOCOL_VERSION = "1.0"; + +type AddToolResultOptions = { + messageId: string; + toolName: string; + toolCallId: string; + result: ReadonlyJSONValue; + isError: boolean; + artifact?: ReadonlyJSONValue | undefined; + modelContent?: readonly ToolModelContentPart[] | undefined; +}; + +type AppendMessage = Omit<ThreadMessage, "id"> & { + parentId: string | null; + sourceId: string | null; + runConfig: RunConfig | undefined; + startRun?: boolean | undefined; + steer?: boolean | undefined; +}; + +type AsNumber<K> = K extends `${infer N extends number}` ? N | K : never; + +type AssistantRuntime = { + readonly threads: ThreadListRuntime; + readonly thread: ThreadRuntime; + registerModelContextProvider(provider: ModelContextProvider): Unsubscribe; +}; + +type AsyncIterableStream<T> = AsyncIterable<T> & ReadableStream<T>; + +type Attachment = PendingAttachment | CompleteAttachment; + +type AttachmentAdapter = { + accept: string; + add(state: { + file: File; + }): Promise<PendingAttachment> | AsyncGenerator<PendingAttachment, void>; + remove(attachment: Attachment): Promise<void>; + send(attachment: PendingAttachment): Promise<CompleteAttachment>; +}; + +type AttachmentAddErrorEvent = { + readonly reason: AttachmentAddErrorReason; + readonly message: string; + readonly attachmentId?: string; + readonly error?: Error; +}; + +type AttachmentAddErrorReason = "adapter-error" | "no-adapter" | "not-accepted"; + +type AttachmentRuntime<TSource extends AttachmentRuntimeSource = AttachmentRuntimeSource> = { + readonly path: AttachmentRuntimePath & { + attachmentSource: TSource; + }; + readonly source: TSource; + getState(): AttachmentState & { + source: TSource; + }; + remove(): Promise<void>; + subscribe(callback: () => void): Unsubscribe; +}; + +type AttachmentRuntimePath = ((MessageRuntimePath & { + readonly attachmentSource: "edit-composer" | "message"; +}) | (ThreadRuntimePath & { + readonly attachmentSource: "thread-composer"; +})) & { + readonly attachmentSelector: { + readonly type: "index"; + readonly index: number; + } | { + readonly type: "index"; + readonly index: number; + } | { + readonly type: "index"; + readonly index: number; + }; +}; + +type AttachmentRuntimeSource = AttachmentState["source"]; + +type AttachmentState = ThreadComposerAttachmentState | EditComposerAttachmentState | MessageAttachmentState; + +type BackendTool<TArgs extends Record<string, unknown> = Record<string, unknown>, TResult = unknown> = ToolBase<TArgs, TResult> & { + type: "backend"; + description?: undefined; + parameters?: undefined; + disabled?: undefined; + execute?: undefined; + toModelOutput?: undefined; + experimental_onSchemaValidationError?: undefined; + providerOptions?: undefined; +}; + +type BaseAttachment = { + id: string; + type: "image" | "document" | "file" | (string & {}); + name: string; + contentType?: string | undefined; + file?: File; + content?: ThreadUserMessagePart[]; +}; + +type BaseComposerState = { + readonly canCancel: boolean; + readonly canSend: boolean; + readonly isEditing: boolean; + readonly isEmpty: boolean; + readonly text: string; + readonly role: MessageRole; + readonly attachments: readonly Attachment[]; + readonly runConfig: RunConfig; + readonly attachmentAccept: string; + readonly dictation: DictationState | undefined; + readonly quote: QuoteInfo | undefined; + readonly queue: readonly QueueItemState[]; +}; + +type BaseThreadMessage = { + readonly status?: ThreadAssistantMessage["status"]; + readonly metadata: { + readonly unstable_state?: ReadonlyJSONValue; + readonly unstable_annotations?: readonly ReadonlyJSONValue[]; + readonly unstable_data?: readonly ReadonlyJSONValue[]; + readonly steps?: readonly ThreadStep[]; + readonly submittedFeedback?: { + readonly type: "negative" | "positive"; + }; + readonly timing?: MessageTiming; + readonly isOptimistic?: boolean; + readonly custom: Record<string, unknown>; + }; + readonly attachments?: ThreadUserMessage["attachments"]; +}; + +type ChatModelRunOptions = { + readonly messages: readonly ThreadMessage[]; + readonly runConfig: RunConfig; + readonly abortSignal: AbortSignal; + readonly context: ModelContext; + readonly unstable_assistantMessageId?: string | undefined; + readonly unstable_threadId?: string | undefined; + readonly unstable_parentId?: string | null | undefined; + unstable_getMessage(): ThreadMessage; +}; + +type ChatModelRunResult = { + readonly content?: readonly ThreadAssistantMessagePart[] | undefined; + readonly status?: MessageStatus | undefined; + readonly metadata?: { + readonly unstable_state?: ReadonlyJSONValue; + readonly unstable_annotations?: readonly ReadonlyJSONValue[] | undefined; + readonly unstable_data?: readonly ReadonlyJSONValue[] | undefined; + readonly steps?: readonly ThreadStep[] | undefined; + readonly timing?: MessageTiming | undefined; + readonly custom?: Record<string, unknown> | undefined; + }; +}; + +type CompleteAttachment = BaseAttachment & { + status: CompleteAttachmentStatus; + content: ThreadUserMessagePart[]; +}; + +type CompleteAttachmentStatus = { + type: "complete"; +}; + +type ComposerRuntime = { + readonly path: ComposerRuntimePath; + readonly type: "edit" | "thread"; + getState(): ComposerState; + addAttachment(fileOrAttachment: File | CreateAttachment): Promise<void>; + setText(text: string): void; + setRole(role: MessageRole): void; + setRunConfig(runConfig: RunConfig): void; + reset(): Promise<void>; + clearAttachments(): Promise<void>; + send(options?: SendOptions): void; + cancel(): void; + steerQueueItem(queueItemId: string): void; + removeQueueItem(queueItemId: string): void; + subscribe(callback: () => void): Unsubscribe; + getAttachmentByIndex(idx: number): AttachmentRuntime; + startDictation(): void; + stopDictation(): void; + setQuote(quote: QuoteInfo | undefined): void; + unstable_on<E extends ComposerRuntimeEventType>(event: E, callback: ComposerRuntimeEventCallback<E>): Unsubscribe; +}; + +type ComposerRuntimeEventCallback<E extends ComposerRuntimeEventType> = (payload: ComposerRuntimeEventPayload[E]) => void; + +type ComposerRuntimeEventPayload = { + send: Record<string, never>; + attachmentAdd: Record<string, never>; + attachmentAddError: AttachmentAddErrorEvent; +}; + +type ComposerRuntimeEventType = keyof ComposerRuntimeEventPayload; + +type ComposerRuntimePath = (ThreadRuntimePath & { + readonly composerSource: "thread"; +}) | (MessageRuntimePath & { + readonly composerSource: "edit"; +}); + +type ComposerState = ThreadComposerState | EditComposerState; + +type CreateAppendMessage = string | { + parentId?: string | null | undefined; + sourceId?: string | null | undefined; + role?: AppendMessage["role"] | undefined; + content: AppendMessage["content"]; + attachments?: AppendMessage["attachments"] | undefined; + metadata?: AppendMessage["metadata"] | undefined; + createdAt?: Date | undefined; + runConfig?: AppendMessage["runConfig"] | undefined; + startRun?: boolean | undefined; +}; + +type CreateAttachment = { + id?: string; + type?: "image" | "document" | "file" | (string & {}); + name: string; + contentType?: string; + content: ThreadUserMessagePart[]; +}; + +type CreateResumeRunConfig = CreateStartRunConfig & { + stream?: (options: ChatModelRunOptions) => AsyncGenerator<ChatModelRunResult, void, unknown>; +}; + +type CreateStartRunConfig = { + parentId: string | null; + sourceId?: string | null | undefined; + runConfig?: RunConfig | undefined; +}; + +type DataMessagePart<T = any> = { + readonly type: "data"; + readonly name: string; + readonly data: T; +}; + +type DataPrefixedPart = { + readonly type: `data-${string}`; + readonly data: any; +}; + +type DeepPartial<T> = T extends readonly any[] ? readonly DeepPartial<T[number]>[] : T extends { + [key: string]: any; +} ? { + readonly [K in keyof T]?: DeepPartial<T[K]>; +} : T; + +declare namespace DictationAdapter { + type Status = { + type: "running" | "starting"; + } | { + type: "ended"; + reason: "cancelled" | "error" | "stopped"; + }; + type Result = { + transcript: string; + isFinal?: boolean; + }; + type Session = { + status: Status; + stop: () => Promise<void>; + cancel: () => void; + onSpeechStart: (callback: () => void) => Unsubscribe; + onSpeechEnd: (callback: (result: Result) => void) => Unsubscribe; + onSpeech: (callback: (result: Result) => void) => Unsubscribe; + }; +} + +type DictationAdapter = { + listen: () => DictationAdapter.Session; + disableInputDuringDictation?: boolean; +}; + +type DictationState = { + readonly status: DictationAdapter.Status; + readonly transcript?: string; + readonly inputDisabled?: boolean; +}; + +type EditComposerAttachmentState = Attachment & { + readonly source: "edit-composer"; +}; + +type EditComposerRuntime = Omit<ComposerRuntime, "getAttachmentByIndex" | "getState"> & { + readonly path: ComposerRuntimePath & { + composerSource: "edit"; + }; + readonly type: "edit"; + getState(): EditComposerState; + beginEdit(): void; + getAttachmentByIndex(idx: number): AttachmentRuntime & { + source: "edit-composer"; + }; +}; + +type EditComposerState = BaseComposerState & { + readonly type: "edit"; + readonly parentId: string | null; + readonly sourceId: string | null; +}; + +type ExportedMessageRepository = { + headId?: string | null; + messages: Array<{ + message: ThreadMessage; + parentId: string | null; + runConfig?: RunConfig; + }>; +}; + +declare const ExportedMessageRepository: { + fromArray: (messages: readonly ThreadMessageLike[]) => ExportedMessageRepository; + fromBranchableArray: (items: readonly { + message: ThreadMessageLike; + parentId: string | null; + }[], options?: { + headId?: string | null; + }) => ExportedMessageRepository; +}; + +type ExportedMessageRepositoryItem = { + message: ThreadMessage; + parentId: string | null; + runConfig?: RunConfig; +}; + +type ExternalStoreAdapter<T = ThreadMessage> = ExternalStoreAdapterBase<T> & (T extends ThreadMessage ? object : ExternalStoreMessageConverterAdapter<T>); + +type ExternalStoreAdapterBase<T> = { + isDisabled?: boolean | undefined; + isSendDisabled?: boolean | undefined; + isRunning?: boolean | undefined; + isLoading?: boolean | undefined; + messages?: readonly T[]; + messageRepository?: ExportedMessageRepository; + suggestions?: readonly ThreadSuggestion[] | undefined; + state?: ReadonlyJSONValue | undefined; + extras?: unknown; + setMessages?: ((messages: readonly T[]) => void) | undefined; + unstable_onBranchChange?: ((event: ExternalStoreBranchChange) => void) | undefined; + onImport?: ((messages: readonly ThreadMessage[]) => void) | undefined; + onExportExternalState?: (() => any) | undefined; + onLoadExternalState?: ((state: any) => void) | undefined; + onNew: (message: AppendMessage) => Promise<void>; + queue?: ExternalThreadQueueAdapter | undefined; + onEdit?: ((message: AppendMessage) => Promise<void>) | undefined; + onDelete?: ((messageId: string) => Promise<void> | void) | undefined; + onReload?: ((parentId: string | null, config: StartRunConfig) => Promise<void>) | undefined; + onResume?: ((config: ResumeRunConfig) => Promise<void>) | undefined; + onCancel?: (() => Promise<void>) | undefined; + onAddToolResult?: ((options: AddToolResultOptions) => Promise<void> | void) | undefined; + onResumeToolCall?: ((options: { + toolCallId: string; + payload: unknown; + }) => void) | undefined; + onRespondToToolApproval?: ((options: RespondToToolApprovalOptions) => Promise<void> | void) | undefined; + convertMessage?: ExternalStoreMessageConverter<T> | undefined; + adapters?: { + attachments?: AttachmentAdapter | undefined; + speech?: SpeechSynthesisAdapter | undefined; + dictation?: DictationAdapter | undefined; + voice?: RealtimeVoiceAdapter | undefined; + feedback?: FeedbackAdapter | undefined; + threadList?: ExternalStoreThreadListAdapter | undefined; + } | undefined; + unstable_capabilities?: { + copy?: boolean | undefined; + } | undefined; + unstable_enableToolInvocations?: boolean | undefined; + setToolStatuses?: ((statuses: Record<string, ToolExecutionStatus>) => void) | undefined; +}; + +type ExternalStoreBranchChange = { + headId: string | null; + visibleMessageIds: readonly string[]; +}; + +type ExternalStoreMessageConverter<T> = (message: T, idx: number) => ThreadMessageLike; + +type ExternalStoreMessageConverterAdapter<T> = { + convertMessage: ExternalStoreMessageConverter<T>; +}; + +type ExternalStoreSharedOptions = Pick<ExternalStoreAdapter, "isDisabled" | "isSendDisabled" | "suggestions" | "unstable_capabilities">; + +type ExternalStoreThreadData<TState extends "archived" | "regular"> = { + status: TState; + id: string; + remoteId?: string | undefined; + externalId?: string | undefined; + title?: string | undefined; + custom?: Record<string, unknown> | undefined; +}; + +type ExternalStoreThreadListAdapter = { + threadId?: string | undefined; + isLoading?: boolean | undefined; + threads?: readonly ExternalStoreThreadData<"regular">[] | undefined; + archivedThreads?: readonly ExternalStoreThreadData<"archived">[] | undefined; + onSwitchToNewThread?: (() => Promise<void> | void) | undefined; + onSwitchToThread?: ((threadId: string) => Promise<void> | void) | undefined; + onRename?: (threadId: string, newTitle: string) => (Promise<void> | void) | undefined; + onUpdateCustom?: ((threadId: string, custom: Record<string, unknown> | undefined) => Promise<void> | void) | undefined; + onArchive?: ((threadId: string) => Promise<void> | void) | undefined; + onUnarchive?: ((threadId: string) => Promise<void> | void) | undefined; + onDelete?: ((threadId: string) => Promise<void> | void) | undefined; +}; + +type ExternalThreadQueueAdapter = { + items: readonly QueueItemState[]; + enqueue: (message: AppendMessage, options: { + steer: boolean; + }) => void; + steer: (queueItemId: string) => void; + remove: (queueItemId: string) => void; + clear: (reason: "cancel-run" | "edit" | "reload") => void; +}; + +type FeedbackAdapter = { + submit: (feedback: FeedbackAdapterFeedback) => void; +}; + +type FeedbackAdapterFeedback = { + message: ThreadMessage; + type: "negative" | "positive"; +}; + +type FileMessagePart = { + readonly type: "file"; + readonly filename?: string; + readonly data: string; + readonly mimeType: string; + readonly parentId?: string; +}; + +type FrontendTool<TArgs extends Record<string, unknown> = Record<string, unknown>, TResult = unknown> = ToolBase<TArgs, TResult> & { + type: "frontend"; + description?: string | undefined; + parameters: StandardSchemaV1<TArgs> | JSONSchema7; + disabled?: boolean; + execute?: ToolExecuteFunction<TArgs, TResult>; + toModelOutput?: ToolModelOutputFunction<TArgs, TResult>; + experimental_onSchemaValidationError?: OnSchemaValidationErrorFunction<TResult>; + providerOptions?: ProviderOptions; +}; + +type GenerativeUIMessagePart = { + readonly type: "generative-ui"; + readonly spec: GenerativeUISpec; + readonly id?: string; + readonly parentId?: string; +}; + +type GenerativeUINode = string | { + readonly component: string; + readonly props?: Record<string, unknown>; + readonly children?: readonly GenerativeUINode[]; + readonly key?: string; +}; + +type GenerativeUISpec = { + readonly root: GenerativeUINode | readonly GenerativeUINode[]; +}; + +type GenericThreadHistoryAdapter<TMessage> = { + load(): Promise<MessageFormatRepository<TMessage>>; + append(item: MessageFormatItem<TMessage>): Promise<void>; + update?(item: MessageFormatItem<TMessage>, localMessageId: string): Promise<void>; + delete?(items: MessageFormatItem<TMessage>[]): Promise<void>; + reportTelemetry?(items: MessageFormatItem<TMessage>[], options?: { + durationMs?: number; + stepTimestamps?: { + start_ms: number; + end_ms: number; + }[]; + }): void; +}; + +type HumanTool<TArgs extends Record<string, unknown> = Record<string, unknown>, TResult = unknown> = ToolBase<TArgs, TResult> & { + type: "human"; + description?: string | undefined; + parameters: StandardSchemaV1<TArgs> | JSONSchema7; + disabled?: boolean; + display?: "standalone"; + execute?: undefined; + toModelOutput?: undefined; + experimental_onSchemaValidationError?: undefined; + providerOptions?: ProviderOptions; +}; + +type ImageMessagePart = { + readonly type: "image"; + readonly image: string; + readonly filename?: string; +}; + +interface JSONSchema7 { + $id?: string | undefined; + $ref?: string | undefined; + $schema?: JSONSchema7Version | undefined; + $comment?: string | undefined; + $defs?: { + [key: string]: JSONSchema7Definition; + } | undefined; + type?: JSONSchema7TypeName | JSONSchema7TypeName[] | undefined; + enum?: JSONSchema7Type[] | undefined; + const?: JSONSchema7Type | undefined; + multipleOf?: number | undefined; + maximum?: number | undefined; + exclusiveMaximum?: number | undefined; + minimum?: number | undefined; + exclusiveMinimum?: number | undefined; + maxLength?: number | undefined; + minLength?: number | undefined; + pattern?: string | undefined; + items?: JSONSchema7Definition | JSONSchema7Definition[] | undefined; + additionalItems?: JSONSchema7Definition | undefined; + maxItems?: number | undefined; + minItems?: number | undefined; + uniqueItems?: boolean | undefined; + contains?: JSONSchema7Definition | undefined; + maxProperties?: number | undefined; + minProperties?: number | undefined; + required?: string[] | undefined; + properties?: { + [key: string]: JSONSchema7Definition; + } | undefined; + patternProperties?: { + [key: string]: JSONSchema7Definition; + } | undefined; + additionalProperties?: JSONSchema7Definition | undefined; + dependencies?: { + [key: string]: JSONSchema7Definition | string[]; + } | undefined; + propertyNames?: JSONSchema7Definition | undefined; + if?: JSONSchema7Definition | undefined; + then?: JSONSchema7Definition | undefined; + else?: JSONSchema7Definition | undefined; + allOf?: JSONSchema7Definition[] | undefined; + anyOf?: JSONSchema7Definition[] | undefined; + oneOf?: JSONSchema7Definition[] | undefined; + not?: JSONSchema7Definition | undefined; + format?: string | undefined; + contentMediaType?: string | undefined; + contentEncoding?: string | undefined; + definitions?: { + [key: string]: JSONSchema7Definition; + } | undefined; + title?: string | undefined; + description?: string | undefined; + default?: JSONSchema7Type | undefined; + readOnly?: boolean | undefined; + writeOnly?: boolean | undefined; + examples?: JSONSchema7Type | undefined; +} + +interface JSONSchema7Array extends Array<JSONSchema7Type> { +} + +type JSONSchema7Definition = JSONSchema7 | boolean; + +interface JSONSchema7Object { + [key: string]: JSONSchema7Type; +} + +type JSONSchema7Type = string | number | boolean | JSONSchema7Object | JSONSchema7Array | null; + +type JSONSchema7TypeName = "array" | "boolean" | "integer" | "null" | "number" | "object" | "string"; + +type JSONSchema7Version = string; + +type LanguageModelConfig = { + apiKey?: string; + baseUrl?: string; + modelName?: string; + reasoningEffort?: string; +}; + +type LanguageModelV1CallSettings = { + maxTokens?: number; + temperature?: number; + topP?: number; + presencePenalty?: number; + frequencyPenalty?: number; + seed?: number; + headers?: Record<string, string | undefined>; +}; + +type McpAppMetadata = { + readonly resourceUri: string; + readonly mimeType?: string; + readonly visibility?: readonly ("app" | "model")[]; +}; + +type McpServerConfig = { + type: "http" | "sse"; + url: string; + headers?: Record<string, string>; + redirect?: "error" | "follow"; + connectionTimeout?: number | undefined; +} | { + type: "stdio"; + command: string; + args?: readonly string[]; + env?: Record<string, string>; + cwd?: string; + connectionTimeout?: number | undefined; +}; + +type McpTool = ToolBase<Record<string, unknown>, unknown> & { + type: "mcp"; + server: McpServerConfig; + description?: undefined; + parameters?: undefined; + disabled?: boolean; + execute?: undefined; + toModelOutput?: undefined; + experimental_onSchemaValidationError?: undefined; + providerOptions?: undefined; +}; + +type MessageAttachmentState = CompleteAttachment & { + readonly source: "message"; +}; + +type MessageCommonProps = { + readonly id: string; + readonly createdAt: Date; +}; + +interface MessageFormatAdapter<TMessage, TStorageFormat extends Record<string, unknown>> { + format: string; + encode(item: MessageFormatItem<TMessage>): TStorageFormat; + decode(stored: MessageStorageEntry<TStorageFormat>): MessageFormatItem<TMessage>; + getId(message: TMessage): string; +} + +interface MessageFormatItem<TMessage> { + parentId: string | null; + message: TMessage; +} + +interface MessageFormatRepository<TMessage> { + headId?: string | null; + messages: MessageFormatItem<TMessage>[]; +} + +type MessagePartRuntime = { + addToolResult(result: any | ToolResponse<any>): void; + resumeToolCall(payload: unknown): void; + respondToToolApproval(response: ToolApprovalResponse): void; + readonly path: MessagePartRuntimePath; + getState(): MessagePartState; + subscribe(callback: () => void): Unsubscribe; +}; + +type MessagePartRuntimePath = MessageRuntimePath & { + readonly messagePartSelector: { + readonly type: "index"; + readonly index: number; + } | { + readonly type: "toolCallId"; + readonly toolCallId: string; + }; +}; + +type MessagePartState = (ThreadUserMessagePart | ThreadAssistantMessagePart) & { + readonly status: MessagePartStatus | ToolCallMessagePartStatus; +}; + +type MessagePartStatus = { + readonly type: "running"; +} | { + readonly type: "complete"; +} | { + readonly type: "incomplete"; + readonly reason: "cancelled" | "content-filter" | "error" | "length" | "other"; + readonly error?: unknown; +}; + +type MessageRole = ThreadMessage["role"]; + +type MessageRuntime = { + readonly path: MessageRuntimePath; + readonly composer: EditComposerRuntime; + getState(): MessageState; + delete(): void | Promise<void>; + reload(config?: ReloadConfig): void; + speak(): void; + stopSpeaking(): void; + submitFeedback(_param0: { + type: "positive" | "negative"; + }): void; + switchToBranch(_param1: { + position?: "previous" | "next" | undefined; + branchId?: string | undefined; + }): void; + unstable_getCopyText(): string; + subscribe(callback: () => void): Unsubscribe; + getMessagePartByIndex(idx: number): MessagePartRuntime; + getMessagePartByToolCallId(toolCallId: string): MessagePartRuntime; + getAttachmentByIndex(idx: number): AttachmentRuntime & { + source: "message"; + }; +}; + +type MessageRuntimePath = ThreadRuntimePath & { + readonly messageSelector: { + readonly type: "messageId"; + readonly messageId: string; + } | { + readonly type: "index"; + readonly index: number; + }; +}; + +type MessageState = ThreadMessage & { + readonly parentId: string | null; + readonly index: number; + readonly isLast: boolean; + readonly branchNumber: number; + readonly branchCount: number; + readonly speech: SpeechState | undefined; +}; + +type MessageStatus = { + readonly type: "running"; +} | { + readonly type: "requires-action"; + readonly reason: "interrupt" | "tool-calls"; +} | { + readonly type: "complete"; + readonly reason: "stop" | "unknown"; +} | { + readonly type: "incomplete"; + readonly reason: "cancelled" | "content-filter" | "error" | "length" | "other" | "tool-calls"; + readonly error?: ReadonlyJSONValue; +}; + +interface MessageStorageEntry<TPayload> { + id: string; + parent_id: string | null; + format: string; + content: TPayload; +} + +type MessageTiming = { + readonly streamStartTime: number; + readonly firstTokenTime?: number; + readonly totalStreamTime?: number; + readonly tokenCount?: number; + readonly tokensPerSecond?: number; + readonly totalChunks: number; + readonly toolCallCount: number; +}; + +type ModelContext = { + priority?: number | undefined; + system?: string | undefined; + tools?: Record<string, Tool<any, any>> | undefined; + callSettings?: LanguageModelV1CallSettings | undefined; + config?: LanguageModelConfig | undefined; + unstable_composerMetadata?: Record<string, unknown> | undefined; +}; + +type ModelContextProvider = { + getModelContext: () => ModelContext; + subscribe?: (callback: () => void) => Unsubscribe; +}; + +type ObjectKey<T> = keyof T & (string | number); + +type OnSchemaValidationErrorFunction<TResult> = ToolExecuteFunction<unknown, TResult>; + +type PartProviderMetadata = { + readonly [providerName: string]: ReadonlyJSONObject; +}; + +type PendingAttachment = BaseAttachment & { + status: PendingAttachmentStatus; + file: File; +}; + +type PendingAttachmentStatus = { + type: "running"; + reason: "uploading"; + progress: number; +} | { + type: "requires-action"; + reason: "composer-send"; +} | { + type: "incomplete"; + reason: "error" | "upload-paused"; + message?: string; +}; + +type ProviderOptions = Record<string, Record<string, unknown>>; + +type ProviderTool<TArgs extends Record<string, unknown> = Record<string, unknown>, TResult = unknown> = ToolBase<TArgs, TResult> & { + type: "provider"; + providerId: `${string}.${string}`; + parameters?: StandardSchemaV1<TArgs> | JSONSchema7 | undefined; + args: Record<string, unknown>; + supportsDeferredResults?: boolean; + description?: undefined; + disabled?: boolean; + execute?: undefined; + toModelOutput?: undefined; + experimental_onSchemaValidationError?: undefined; + providerOptions?: ProviderOptions; +}; + +type QueueItemState = { + readonly id: string; + readonly prompt: string; +}; + +type QuoteInfo = { + readonly text: string; + readonly messageId: string; +}; + +type ReadonlyJSONArray = readonly ReadonlyJSONValue[]; + +type ReadonlyJSONObject = { + readonly [key: string]: ReadonlyJSONValue; +}; + +type ReadonlyJSONValue = null | string | number | boolean | ReadonlyJSONObject | ReadonlyJSONArray; + +declare namespace RealtimeVoiceAdapter { + type Status = { + type: "running" | "starting"; + } | { + type: "ended"; + reason: "cancelled" | "error" | "finished"; + error?: unknown; + }; + type Mode = "listening" | "speaking"; + type TranscriptItem = { + role: "assistant" | "user"; + text: string; + isFinal?: boolean; + }; + type Session = { + status: Status; + isMuted: boolean; + disconnect: () => void; + mute: () => void; + unmute: () => void; + onStatusChange: (callback: (status: Status) => void) => Unsubscribe; + onTranscript: (callback: (transcript: TranscriptItem) => void) => Unsubscribe; + onModeChange: (callback: (mode: Mode) => void) => Unsubscribe; + onVolumeChange: (callback: (volume: number) => void) => Unsubscribe; + }; +} + +type RealtimeVoiceAdapter = { + connect: (options: { + abortSignal?: AbortSignal; + }) => RealtimeVoiceAdapter.Session; +}; + +type ReasoningMessagePart = { + readonly type: "reasoning"; + readonly text: string; + readonly providerMetadata?: PartProviderMetadata; + readonly parentId?: string; +}; + +type ReloadConfig = { + runConfig?: RunConfig; +}; + +type RespondToToolApprovalOptions = { + approvalId: string; + approved: boolean; + optionId?: string; + reason?: string; +}; + +type ResumeRunConfig = StartRunConfig & { + stream?: (options: ChatModelRunOptions) => AsyncGenerator<ChatModelRunResult, void, unknown>; +}; + +type RunConfig = { + readonly custom?: Record<string, unknown>; +}; + +type RuntimeCapabilities = { + readonly switchToBranch: boolean; + readonly switchBranchDuringRun: boolean; + readonly edit: boolean; + readonly reload: boolean; + readonly delete: boolean; + readonly cancel: boolean; + readonly unstable_copy: boolean; + readonly speech: boolean; + readonly dictation: boolean; + readonly voice: boolean; + readonly attachments: boolean; + readonly feedback: boolean; + readonly queue: boolean; +}; + +type SendOptions = { + startRun?: boolean; + steer?: boolean; +}; + +type SourceMessagePart = { + readonly type: "source"; + readonly sourceType: "url"; + readonly id: string; + readonly url: string; + readonly title?: string; + readonly providerMetadata?: SourceProviderMetadata; + readonly parentId?: string; +} | { + readonly type: "source"; + readonly sourceType: "document"; + readonly id: string; + readonly url?: undefined; + readonly title: string; + readonly mediaType: string; + readonly filename?: string; + readonly providerMetadata?: SourceProviderMetadata; + readonly parentId?: string; +}; + +type SourceProviderMetadata = PartProviderMetadata; + +interface SpeechRecognitionConstructor { + new (): SpeechRecognitionInstance; +} + +interface SpeechRecognitionInstance extends EventTarget { + lang: string; + continuous: boolean; + interimResults: boolean; + start(): void; + stop(): void; + abort(): void; +} + +type SpeechState = { + readonly messageId: string; + readonly status: SpeechSynthesisAdapter.Status; +}; + +declare namespace SpeechSynthesisAdapter { + type Status = { + type: "running" | "starting"; + } | { + type: "ended"; + reason: "cancelled" | "error" | "finished"; + error?: unknown; + }; + type Utterance = { + status: Status; + cancel: () => void; + subscribe: (callback: () => void) => Unsubscribe; + }; +} + +type SpeechSynthesisAdapter = { + speak: (text: string) => SpeechSynthesisAdapter.Utterance; +}; + +type StartRunConfig = { + parentId: string | null; + sourceId: string | null; + runConfig: RunConfig; +}; + +declare const TOOL_RESPONSE_SYMBOL: unique symbol; + +type TextMessagePart = { + readonly type: "text"; + readonly text: string; + readonly providerMetadata?: PartProviderMetadata; + readonly parentId?: string; +}; + +type ThreadAssistantMessage = MessageCommonProps & { + readonly role: "assistant"; + readonly content: readonly ThreadAssistantMessagePart[]; + readonly status: MessageStatus; + readonly metadata: { + readonly unstable_state: ReadonlyJSONValue; + readonly unstable_annotations: readonly ReadonlyJSONValue[]; + readonly unstable_data: readonly ReadonlyJSONValue[]; + readonly steps: readonly ThreadStep[]; + readonly submittedFeedback?: { + readonly type: "negative" | "positive"; + }; + readonly timing?: MessageTiming; + readonly isOptimistic?: boolean; + readonly custom: Record<string, unknown>; + }; +}; + +type ThreadAssistantMessagePart = TextMessagePart | ReasoningMessagePart | ToolCallMessagePart | SourceMessagePart | FileMessagePart | ImageMessagePart | DataMessagePart | GenerativeUIMessagePart; + +type ThreadComposerAttachmentState = Attachment & { + readonly source: "thread-composer"; +}; + +type ThreadComposerRuntime = Omit<ComposerRuntime, "getAttachmentByIndex" | "getState"> & { + readonly path: ComposerRuntimePath & { + composerSource: "thread"; + }; + readonly type: "thread"; + getState(): ThreadComposerState; + getAttachmentByIndex(idx: number): AttachmentRuntime & { + source: "thread-composer"; + }; +}; + +type ThreadComposerState = BaseComposerState & { + readonly type: "thread"; +}; + +type ThreadHistoryAdapter = { + load(): Promise<ExportedMessageRepository & { + state?: ReadonlyJSONValue; + unstable_resume?: boolean; + }>; + resume?(options: ChatModelRunOptions): AsyncGenerator<ChatModelRunResult, void, unknown>; + append(item: ExportedMessageRepositoryItem): Promise<void>; + delete?(items: ExportedMessageRepositoryItem[]): Promise<void>; + withFormat?<TMessage, TStorageFormat extends Record<string, unknown>>(formatAdapter: MessageFormatAdapter<TMessage, TStorageFormat>): GenericThreadHistoryAdapter<TMessage>; +}; + +type ThreadListItemEventCallback<E extends ThreadListItemEventType> = (payload: ThreadListItemEventPayload[E]) => void; + +type ThreadListItemEventPayload = { + switchedTo: Record<string, never>; + switchedAway: Record<string, never>; +}; + +type ThreadListItemEventType = keyof ThreadListItemEventPayload; + +type ThreadListItemRuntime = { + readonly path: ThreadListItemRuntimePath; + getState(): ThreadListItemState; + initialize(): Promise<{ + remoteId: string; + externalId: string | undefined; + }>; + generateTitle(): Promise<void>; + switchTo(options?: { + unarchive?: boolean; + }): Promise<void>; + rename(newTitle: string): Promise<void>; + updateCustom(custom: Record<string, unknown> | undefined): Promise<void>; + archive(): Promise<void>; + unarchive(): Promise<void>; + delete(): Promise<void>; + detach(): void; + subscribe(callback: () => void): Unsubscribe; + unstable_on<E extends ThreadListItemEventType>(event: E, callback: ThreadListItemEventCallback<E>): Unsubscribe; + __internal_getRuntime(): ThreadListItemRuntime; +}; + +type ThreadListItemRuntimePath = { + readonly ref: string; + readonly threadSelector: { + readonly type: "main"; + } | { + readonly type: "index"; + readonly index: number; + } | { + readonly type: "archiveIndex"; + readonly index: number; + } | { + readonly type: "threadId"; + readonly threadId: string; + }; +}; + +type ThreadListItemState = { + readonly isMain: boolean; + readonly id: string; + readonly remoteId: string | undefined; + readonly externalId: string | undefined; + readonly status: ThreadListItemStatus; + readonly title?: string | undefined; + readonly lastMessageAt?: Date | undefined; + readonly custom?: Record<string, unknown> | undefined; +}; + +type ThreadListItemStatus = "archived" | "deleted" | "new" | "regular"; + +type ThreadListRuntime = { + getState(): ThreadListState; + subscribe(callback: () => void): Unsubscribe; + readonly main: ThreadRuntime; + getById(threadId: string): ThreadRuntime; + readonly mainItem: ThreadListItemRuntime; + getItemById(threadId: string): ThreadListItemRuntime; + getItemByIndex(idx: number): ThreadListItemRuntime; + getArchivedItemByIndex(idx: number): ThreadListItemRuntime; + switchToThread(threadId: string, options?: { + unarchive?: boolean; + }): Promise<void>; + switchToNewThread(): Promise<void>; + getLoadThreadsPromise(): Promise<void>; + reload(): Promise<void>; + loadMore(): Promise<void>; +}; + +type ThreadListState = { + readonly mainThreadId: string; + readonly newThreadId: string | undefined; + readonly threadIds: readonly string[]; + readonly archivedThreadIds: readonly string[]; + readonly isLoading: boolean; + readonly isLoadingMore: boolean; + readonly hasMore: boolean; + readonly threadItems: Readonly<Record<string, Omit<ThreadListItemState, "isMain" | "threadId">>>; +}; + +type ThreadMessage = BaseThreadMessage & (ThreadSystemMessage | ThreadUserMessage | ThreadAssistantMessage); + +type ThreadMessageLike = { + readonly role: "assistant" | "system" | "user"; + readonly content: string | readonly (TextMessagePart | ReasoningMessagePart | SourceMessagePart | ImageMessagePart | FileMessagePart | DataMessagePart | GenerativeUIMessagePart | Unstable_AudioMessagePart | DataPrefixedPart | { + readonly type: "tool-call"; + readonly toolCallId?: string; + readonly toolName: string; + readonly args?: ReadonlyJSONObject; + readonly argsText?: string; + readonly artifact?: any; + readonly result?: any | undefined; + readonly isError?: boolean | undefined; + readonly parentId?: string | undefined; + readonly messages?: readonly ThreadMessage[] | undefined; + readonly interrupt?: { + type: "human"; + payload: unknown; + }; + readonly timing?: ToolCallTiming; + readonly providerMetadata?: PartProviderMetadata; + readonly approval?: { + readonly id: string; + readonly approved?: boolean; + readonly reason?: string; + readonly isAutomatic?: boolean; + readonly options?: readonly ToolApprovalOption[]; + readonly optionId?: string; + readonly resolution?: "cancelled" | "expired"; + }; + })[]; + readonly id?: string | undefined; + readonly createdAt?: Date | undefined; + readonly status?: MessageStatus | undefined; + readonly attachments?: readonly (Omit<CompleteAttachment, "content"> & { + readonly content: readonly (ThreadUserMessagePart | DataPrefixedPart)[]; + })[] | undefined; + readonly metadata?: { + readonly unstable_state?: ReadonlyJSONValue; + readonly unstable_annotations?: readonly ReadonlyJSONValue[] | undefined; + readonly unstable_data?: readonly ReadonlyJSONValue[] | undefined; + readonly steps?: readonly ThreadStep[] | undefined; + readonly timing?: MessageTiming | undefined; + readonly submittedFeedback?: { + readonly type: "negative" | "positive"; + }; + readonly isOptimistic?: boolean | undefined; + readonly custom?: Record<string, unknown> | undefined; + } | undefined; +}; + +type ThreadRuntime = { + readonly path: ThreadRuntimePath; + readonly composer: ThreadComposerRuntime; + getState(): ThreadState; + append(message: CreateAppendMessage): void; + deleteMessage(messageId: string): void | Promise<void>; + startRun(config: CreateStartRunConfig): void; + resumeRun(config: CreateResumeRunConfig): void; + exportExternalState(): any; + importExternalState(state: any): void; + subscribe(callback: () => void): Unsubscribe; + cancelRun(): void; + getModelContext(): ModelContext; + export(): ExportedMessageRepository; + import(repository: ExportedMessageRepository): void; + reset(initialMessages?: readonly ThreadMessageLike[]): void; + getMessageByIndex(idx: number): MessageRuntime; + getMessageById(messageId: string): MessageRuntime; + stopSpeaking(): void; + connectVoice(): void; + disconnectVoice(): void; + getVoiceVolume(): number; + subscribeVoiceVolume(callback: () => void): Unsubscribe; + muteVoice(): void; + unmuteVoice(): void; + unstable_on<E extends ThreadRuntimeEventType>(event: E, callback: ThreadRuntimeEventCallback<E>): Unsubscribe; +}; + +type ThreadRuntimeEventCallback<E extends ThreadRuntimeEventType> = (payload: ThreadRuntimeEventPayload[E]) => void; + +type ThreadRuntimeEventPayload = { + runStart: Record<string, never>; + runEnd: Record<string, never>; + initialize: Record<string, never>; + modelContextUpdate: Record<string, never>; +}; + +type ThreadRuntimeEventType = keyof ThreadRuntimeEventPayload; + +type ThreadRuntimePath = { + readonly ref: string; + readonly threadSelector: { + readonly type: "main"; + } | { + readonly type: "threadId"; + readonly threadId: string; + }; +}; + +type ThreadState = { + readonly threadId: string; + readonly metadata: ThreadListItemState; + readonly isDisabled: boolean; + readonly isLoading: boolean; + readonly isRunning: boolean; + readonly capabilities: RuntimeCapabilities; + readonly messages: readonly ThreadMessage[]; + readonly state: ReadonlyJSONValue; + readonly suggestions: readonly ThreadSuggestion[]; + readonly extras: unknown; + readonly speech: SpeechState | undefined; + readonly voice: VoiceSessionState | undefined; +}; + +type ThreadStep = { + readonly messageId?: string; + readonly usage?: { + readonly inputTokens: number; + readonly outputTokens: number; + } | undefined; +}; + +type ThreadSuggestion = { + prompt: string; +}; + +type ThreadSystemMessage = MessageCommonProps & { + readonly role: "system"; + readonly content: readonly [ + TextMessagePart + ]; + readonly metadata: { + readonly unstable_state?: undefined; + readonly unstable_annotations?: undefined; + readonly unstable_data?: undefined; + readonly steps?: undefined; + readonly submittedFeedback?: undefined; + readonly timing?: undefined; + readonly custom: Record<string, unknown>; + }; +}; + +type ThreadUserMessage = MessageCommonProps & { + readonly role: "user"; + readonly content: readonly ThreadUserMessagePart[]; + readonly attachments: readonly CompleteAttachment[]; + readonly metadata: { + readonly unstable_state?: undefined; + readonly unstable_annotations?: undefined; + readonly unstable_data?: undefined; + readonly steps?: undefined; + readonly submittedFeedback?: undefined; + readonly timing?: undefined; + readonly custom: Record<string, unknown>; + }; +}; + +type ThreadUserMessagePart = TextMessagePart | ImageMessagePart | FileMessagePart | DataMessagePart | Unstable_AudioMessagePart; + +type Tool<TArgs extends Record<string, unknown> = Record<string, unknown>, TResult = unknown> = FrontendTool<TArgs, TResult> | BackendTool<TArgs, TResult> | HumanTool<TArgs, TResult> | ProviderTool<TArgs, TResult> | McpTool | ToolWithoutType<TArgs, TResult>; + +type ToolApprovalOption = { + readonly id: string; + readonly kind: ToolApprovalOptionKind | (string & {}); + readonly label?: string; + readonly description?: string; + readonly grants?: readonly string[]; + readonly confirm?: boolean | { + title?: string; + description?: string; + }; +}; + +type ToolApprovalOptionKind = "allow-always" | "allow-once" | "reject-always" | "reject-once"; + +type ToolApprovalResponse = { + readonly approved: boolean; + readonly reason?: string; +} | { + readonly optionId: string; + readonly reason?: string; +} | { + readonly approved: boolean; + readonly optionId: string; + readonly reason?: string; +}; + +type ToolBase<TArgs extends Record<string, unknown> = Record<string, unknown>, TResult = unknown> = { + streamCall?: ToolStreamCallFunction<TArgs, TResult>; + display?: ToolDisplay; +}; + +interface ToolCallArgsReader<TArgs extends Record<string, unknown>> { + get<PathT extends TypePath<TArgs>>(...fieldPath: PathT): Promise<TypeAtPath<TArgs, PathT>>; + streamValues<PathT extends TypePath<TArgs>>(...fieldPath: PathT): AsyncIterableStream<DeepPartial<TypeAtPath<TArgs, PathT>>>; + streamText<PathT extends TypePath<TArgs>>(...fieldPath: PathT): TypeAtPath<TArgs, PathT> extends string & (infer U) ? AsyncIterableStream<U> : never; + forEach<PathT extends TypePath<TArgs>>(...fieldPath: PathT): NonNullable<TypeAtPath<TArgs, PathT>> extends Array<infer U> ? AsyncIterableStream<U> : never; +} + +type ToolCallMessagePart<TArgs = ReadonlyJSONObject, TResult = unknown> = { + readonly type: "tool-call"; + readonly toolCallId: string; + readonly toolName: string; + readonly args: TArgs; + readonly result?: TResult | undefined; + readonly isError?: boolean | undefined; + readonly argsText: string; + readonly artifact?: unknown; + readonly timing?: ToolCallTiming; + readonly mcp?: ToolCallMessagePartMcpMetadata; + readonly providerMetadata?: PartProviderMetadata; + readonly modelContent?: readonly ToolModelContentPart[] | undefined; + readonly interrupt?: { + type: "human"; + payload: unknown; + }; + readonly approval?: { + readonly id: string; + readonly approved?: boolean; + readonly reason?: string; + readonly isAutomatic?: boolean; + readonly options?: readonly ToolApprovalOption[]; + readonly optionId?: string; + readonly resolution?: "cancelled" | "expired"; + }; + readonly parentId?: string; + readonly messages?: readonly ThreadMessage[]; +}; + +type ToolCallMessagePartMcpMetadata = { + readonly app?: McpAppMetadata; +}; + +type ToolCallMessagePartStatus = { + readonly type: "requires-action"; + readonly reason: "interrupt"; +} | MessagePartStatus; + +interface ToolCallReader<TArgs extends Record<string, unknown> = Record<string, unknown>, TResult = unknown> { + args: ToolCallArgsReader<TArgs>; + response: ToolCallResponseReader<TResult>; + result: { + get: () => Promise<TResult>; + }; +} + +interface ToolCallResponseReader<TResult> { + get: () => Promise<ToolResponse<TResult>>; +} + +type ToolCallTiming = { + readonly startedAt: number; + readonly completedAt?: number; +}; + +type ToolDisplay = "inline" | "standalone"; + +type ToolExecuteFunction<TArgs, TResult> = (args: TArgs, context: ToolExecutionContext) => TResult | Promise<TResult>; + +type ToolExecutionContext = { + toolCallId: string; + abortSignal: AbortSignal; + human: (payload: unknown) => Promise<unknown>; +}; + +type ToolExecutionStatus = { + type: "executing"; +} | { + type: "interrupt"; + payload: { + type: "human"; + payload: unknown; + }; +}; + +type ToolModelContentPart = { + readonly type: "text"; + readonly text: string; +} | { + readonly type: "file"; + readonly data: string; + readonly mediaType: string; + readonly filename?: string; +}; + +type ToolModelOutputFunction<TArgs, TResult> = (options: { + toolCallId: string; + input: TArgs; + output: TResult; +}) => readonly ToolModelContentPart[] | Promise<readonly ToolModelContentPart[]>; + +declare class ToolResponse<TResult> { + get [TOOL_RESPONSE_SYMBOL](): boolean; + readonly artifact?: ReadonlyJSONValue; + readonly result: TResult; + readonly isError: boolean; + readonly modelContent?: readonly ToolModelContentPart[]; + readonly messages?: ReadonlyJSONValue; + constructor(options: ToolResponseLike<TResult>); + static [Symbol.hasInstance](obj: unknown): obj is ToolResponse<ReadonlyJSONValue>; + static toResponse(result: any | ToolResponse<any>): ToolResponse<any>; +} + +type ToolResponseLike<TResult> = { + result: TResult; + artifact?: ReadonlyJSONValue | undefined; + isError?: boolean | undefined; + modelContent?: readonly ToolModelContentPart[] | undefined; + messages?: ReadonlyJSONValue | undefined; +}; + +type ToolStreamCallFunction<TArgs extends Record<string, unknown> = Record<string, unknown>, TResult = unknown> = (reader: ToolCallReader<TArgs, TResult>, context: ToolExecutionContext) => void; + +type ToolWithoutType<TArgs extends Record<string, unknown> = Record<string, unknown>, TResult = unknown> = (Omit<FrontendTool<TArgs, TResult>, "type"> | Omit<BackendTool<TArgs, TResult>, "type"> | Omit<HumanTool<TArgs, TResult>, "type"> | Omit<ProviderTool<TArgs, TResult>, "type">) & { + type?: undefined; +}; + +type TupleIndex<T extends readonly any[]> = Exclude<keyof T, keyof any[]>; + +type TypeAtPath<T, P extends readonly any[]> = P extends [ + infer Head, + ...infer Rest +] ? Head extends keyof T ? TypeAtPath<T[Head], Rest> : never : T; + +type TypePath<T> = [ +] | (0 extends 1 & T ? any[] : T extends object ? T extends readonly any[] ? number extends T["length"] ? { + [K in TupleIndex<T>]: [ + AsNumber<K>, + ...TypePath<T[K]> + ]; +}[TupleIndex<T>] : [ + number, + ...TypePath<T[number]> +] : { + [K in ObjectKey<T>]: [ + K, + ...TypePath<T[K]> + ]; +}[ObjectKey<T>] : [ +]); + +type Unstable_AudioMessagePart = { + readonly type: "audio"; + readonly audio: { + readonly data: string; + readonly format: "mp3" | "wav"; + }; +}; + +type Unsubscribe = () => void; + +type UseA2ARuntimeOptions = ExternalStoreSharedOptions & { + client?: A2AClient; + baseUrl?: string; + basePath?: string; + tenant?: string; + headers?: A2AClientOptions["headers"]; + extensions?: string[]; + fetchOptions?: A2AClientOptions["fetchOptions"]; + contextId?: string; + configuration?: A2ASendMessageConfiguration; + onError?: (error: Error) => void; + onCancel?: () => void; + onArtifactComplete?: (artifact: A2AArtifact) => void; + adapters?: { + attachments?: AttachmentAdapter; + speech?: SpeechSynthesisAdapter; + dictation?: DictationAdapter; + voice?: RealtimeVoiceAdapter; + feedback?: FeedbackAdapter; + history?: ThreadHistoryAdapter; + threadList?: UseA2AThreadListAdapter; + }; +}; + +type UseA2AThreadListAdapter = { + threadId?: string; + onSwitchToNewThread?: () => Promise<void> | void; + onSwitchToThread?: (threadId: string) => Promise<{ + messages: readonly ThreadMessage[]; + }>; +}; + +type VoiceSessionState = { + readonly status: RealtimeVoiceAdapter.Status; + readonly isMuted: boolean; + readonly mode: RealtimeVoiceAdapter.Mode; +}; + +declare function a2aMessageToContent(message: A2AMessage): ThreadAssistantMessage["content"]; + +declare function a2aPartToContent(part: A2APart): ThreadAssistantMessage["content"][number]; + +declare function a2aPartsToContent(parts: A2APart[]): ThreadAssistantMessage["content"]; + +declare function contentPartsToA2AParts(content: ReadonlyArray<{ + type: string; + text?: string; + image?: string; +}>): A2APart[]; + +declare global { + interface Window { + SpeechRecognition?: SpeechRecognitionConstructor; + webkitSpeechRecognition?: SpeechRecognitionConstructor; + } +} + +declare namespace entry_root_exports { + export { A2AAgentCapabilities, A2AAgentCard, A2AAgentCardSignature, A2AAgentInterface, A2AAgentSkill, A2AApiKeySecurityScheme, A2AArtifact, A2AAuthenticationInfo, A2AAuthorizationCodeOAuthFlow, A2AClient, A2AClientCredentialsOAuthFlow, A2AClientOptions, A2ADeviceCodeOAuthFlow, A2AError, A2AErrorInfo, A2AHttpAuthSecurityScheme, A2AImplicitOAuthFlow, A2AListTaskPushNotificationConfigsResponse, A2AListTasksRequest, A2AListTasksResponse, A2AMessage, A2AMutualTlsSecurityScheme, A2AOAuth2SecurityScheme, A2AOAuthFlows, A2AOpenIdConnectSecurityScheme, A2APart, A2APasswordOAuthFlow, A2ARole, A2ASecurityRequirement, A2ASecurityScheme, A2ASendMessageConfiguration, A2AStreamEvent, A2ATask, A2ATaskArtifactUpdateEvent, A2ATaskPushNotificationConfig, A2ATaskState, A2ATaskStatus, A2ATaskStatusUpdateEvent, A2A_PROTOCOL_VERSION, UseA2ARuntimeOptions, UseA2AThreadListAdapter, a2aMessageToContent, a2aPartToContent, a2aPartsToContent, contentPartsToA2AParts, isInterruptedTaskState, isTerminalTaskState, taskStateToMessageStatus, useA2AAgentCard, useA2AArtifacts, useA2ARuntime, useA2ATask }; +} + +declare function isInterruptedTaskState(state: A2ATaskState): boolean; + +declare function isTerminalTaskState(state: A2ATaskState): boolean; + +declare function taskStateToMessageStatus(state: A2ATaskState): MessageStatus; + +declare const useA2AAgentCard: () => A2AAgentCard | undefined; + +declare const useA2AArtifacts: () => readonly A2AArtifact[]; + +declare function useA2ARuntime(options: UseA2ARuntimeOptions): AssistantRuntime; + +declare const useA2ATask: () => A2ATask | undefined; + +export { entry_root_exports as entry_root }; diff --git a/api-surface/assistant-ui__react-ag-ui.ts b/api-surface/assistant-ui__react-ag-ui.ts new file mode 100644 index 0000000..3bcc8f3 --- /dev/null +++ b/api-surface/assistant-ui__react-ag-ui.ts @@ -0,0 +1,1582 @@ +import { AbstractAgent } from "@ag-ui/client"; + +import { StandardSchemaV1 } from "@standard-schema/spec"; + +type AddToolResultOptions = { + messageId: string; + toolName: string; + toolCallId: string; + result: ReadonlyJSONValue; + isError: boolean; + artifact?: ReadonlyJSONValue | undefined; + modelContent?: readonly ToolModelContentPart[] | undefined; +}; + +type AgUiAssistantRuntime = AssistantRuntime & { + unstable_getPendingInterrupts: () => readonly AgUiInterrupt[]; + unstable_submitInterruptResponses: (responses: readonly AgUiResumeEntry[]) => Promise<void>; +}; + +type AgUiInterrupt = { + id: string; + reason: AgUiInterruptReason; + message?: string; + toolCallId?: string; + responseSchema?: Record<string, unknown>; + expiresAt?: string; + metadata?: Record<string, unknown>; +}; + +type AgUiInterruptReason = "tool_call" | "input_required" | "confirmation" | (string & {}); + +type AgUiResumeEntry = { + interruptId: string; + status: "cancelled" | "resolved"; + payload?: unknown; +}; + +type AgUiRunFinishedOutcome = { + type: "success"; +} | { + type: "interrupt"; + interrupts: AgUiInterrupt[]; +}; + +type AppendMessage = Omit<ThreadMessage, "id"> & { + parentId: string | null; + sourceId: string | null; + runConfig: RunConfig | undefined; + startRun?: boolean | undefined; + steer?: boolean | undefined; +}; + +type AsNumber<K> = K extends `${infer N extends number}` ? N | K : never; + +type AssistantRuntime = { + readonly threads: ThreadListRuntime; + readonly thread: ThreadRuntime; + registerModelContextProvider(provider: ModelContextProvider): Unsubscribe; +}; + +type AsyncIterableStream<T> = AsyncIterable<T> & ReadableStream<T>; + +type Attachment = PendingAttachment | CompleteAttachment; + +type AttachmentAdapter = { + accept: string; + add(state: { + file: File; + }): Promise<PendingAttachment> | AsyncGenerator<PendingAttachment, void>; + remove(attachment: Attachment): Promise<void>; + send(attachment: PendingAttachment): Promise<CompleteAttachment>; +}; + +type AttachmentAddErrorEvent = { + readonly reason: AttachmentAddErrorReason; + readonly message: string; + readonly attachmentId?: string; + readonly error?: Error; +}; + +type AttachmentAddErrorReason = "adapter-error" | "no-adapter" | "not-accepted"; + +type AttachmentRuntime<TSource extends AttachmentRuntimeSource = AttachmentRuntimeSource> = { + readonly path: AttachmentRuntimePath & { + attachmentSource: TSource; + }; + readonly source: TSource; + getState(): AttachmentState & { + source: TSource; + }; + remove(): Promise<void>; + subscribe(callback: () => void): Unsubscribe; +}; + +type AttachmentRuntimePath = ((MessageRuntimePath & { + readonly attachmentSource: "edit-composer" | "message"; +}) | (ThreadRuntimePath & { + readonly attachmentSource: "thread-composer"; +})) & { + readonly attachmentSelector: { + readonly type: "index"; + readonly index: number; + } | { + readonly type: "index"; + readonly index: number; + } | { + readonly type: "index"; + readonly index: number; + }; +}; + +type AttachmentRuntimeSource = AttachmentState["source"]; + +type AttachmentState = ThreadComposerAttachmentState | EditComposerAttachmentState | MessageAttachmentState; + +type BackendTool<TArgs extends Record<string, unknown> = Record<string, unknown>, TResult = unknown> = ToolBase<TArgs, TResult> & { + type: "backend"; + description?: undefined; + parameters?: undefined; + disabled?: undefined; + execute?: undefined; + toModelOutput?: undefined; + experimental_onSchemaValidationError?: undefined; + providerOptions?: undefined; +}; + +type BaseAttachment = { + id: string; + type: "image" | "document" | "file" | (string & {}); + name: string; + contentType?: string | undefined; + file?: File; + content?: ThreadUserMessagePart[]; +}; + +type BaseComposerState = { + readonly canCancel: boolean; + readonly canSend: boolean; + readonly isEditing: boolean; + readonly isEmpty: boolean; + readonly text: string; + readonly role: MessageRole; + readonly attachments: readonly Attachment[]; + readonly runConfig: RunConfig; + readonly attachmentAccept: string; + readonly dictation: DictationState | undefined; + readonly quote: QuoteInfo | undefined; + readonly queue: readonly QueueItemState[]; +}; + +type BaseThreadMessage = { + readonly status?: ThreadAssistantMessage["status"]; + readonly metadata: { + readonly unstable_state?: ReadonlyJSONValue; + readonly unstable_annotations?: readonly ReadonlyJSONValue[]; + readonly unstable_data?: readonly ReadonlyJSONValue[]; + readonly steps?: readonly ThreadStep[]; + readonly submittedFeedback?: { + readonly type: "negative" | "positive"; + }; + readonly timing?: MessageTiming; + readonly isOptimistic?: boolean; + readonly custom: Record<string, unknown>; + }; + readonly attachments?: ThreadUserMessage["attachments"]; +}; + +type ChatModelRunOptions = { + readonly messages: readonly ThreadMessage[]; + readonly runConfig: RunConfig; + readonly abortSignal: AbortSignal; + readonly context: ModelContext; + readonly unstable_assistantMessageId?: string | undefined; + readonly unstable_threadId?: string | undefined; + readonly unstable_parentId?: string | null | undefined; + unstable_getMessage(): ThreadMessage; +}; + +type ChatModelRunResult = { + readonly content?: readonly ThreadAssistantMessagePart[] | undefined; + readonly status?: MessageStatus | undefined; + readonly metadata?: { + readonly unstable_state?: ReadonlyJSONValue; + readonly unstable_annotations?: readonly ReadonlyJSONValue[] | undefined; + readonly unstable_data?: readonly ReadonlyJSONValue[] | undefined; + readonly steps?: readonly ThreadStep[] | undefined; + readonly timing?: MessageTiming | undefined; + readonly custom?: Record<string, unknown> | undefined; + }; +}; + +type CompleteAttachment = BaseAttachment & { + status: CompleteAttachmentStatus; + content: ThreadUserMessagePart[]; +}; + +type CompleteAttachmentStatus = { + type: "complete"; +}; + +type ComposerRuntime = { + readonly path: ComposerRuntimePath; + readonly type: "edit" | "thread"; + getState(): ComposerState; + addAttachment(fileOrAttachment: File | CreateAttachment): Promise<void>; + setText(text: string): void; + setRole(role: MessageRole): void; + setRunConfig(runConfig: RunConfig): void; + reset(): Promise<void>; + clearAttachments(): Promise<void>; + send(options?: SendOptions): void; + cancel(): void; + steerQueueItem(queueItemId: string): void; + removeQueueItem(queueItemId: string): void; + subscribe(callback: () => void): Unsubscribe; + getAttachmentByIndex(idx: number): AttachmentRuntime; + startDictation(): void; + stopDictation(): void; + setQuote(quote: QuoteInfo | undefined): void; + unstable_on<E extends ComposerRuntimeEventType>(event: E, callback: ComposerRuntimeEventCallback<E>): Unsubscribe; +}; + +type ComposerRuntimeEventCallback<E extends ComposerRuntimeEventType> = (payload: ComposerRuntimeEventPayload[E]) => void; + +type ComposerRuntimeEventPayload = { + send: Record<string, never>; + attachmentAdd: Record<string, never>; + attachmentAddError: AttachmentAddErrorEvent; +}; + +type ComposerRuntimeEventType = keyof ComposerRuntimeEventPayload; + +type ComposerRuntimePath = (ThreadRuntimePath & { + readonly composerSource: "thread"; +}) | (MessageRuntimePath & { + readonly composerSource: "edit"; +}); + +type ComposerState = ThreadComposerState | EditComposerState; + +type CreateAppendMessage = string | { + parentId?: string | null | undefined; + sourceId?: string | null | undefined; + role?: AppendMessage["role"] | undefined; + content: AppendMessage["content"]; + attachments?: AppendMessage["attachments"] | undefined; + metadata?: AppendMessage["metadata"] | undefined; + createdAt?: Date | undefined; + runConfig?: AppendMessage["runConfig"] | undefined; + startRun?: boolean | undefined; +}; + +type CreateAttachment = { + id?: string; + type?: "image" | "document" | "file" | (string & {}); + name: string; + contentType?: string; + content: ThreadUserMessagePart[]; +}; + +type CreateResumeRunConfig = CreateStartRunConfig & { + stream?: (options: ChatModelRunOptions) => AsyncGenerator<ChatModelRunResult, void, unknown>; +}; + +type CreateStartRunConfig = { + parentId: string | null; + sourceId?: string | null | undefined; + runConfig?: RunConfig | undefined; +}; + +type DataMessagePart<T = any> = { + readonly type: "data"; + readonly name: string; + readonly data: T; +}; + +type DataPrefixedPart = { + readonly type: `data-${string}`; + readonly data: any; +}; + +type DeepPartial<T> = T extends readonly any[] ? readonly DeepPartial<T[number]>[] : T extends { + [key: string]: any; +} ? { + readonly [K in keyof T]?: DeepPartial<T[K]>; +} : T; + +declare namespace DictationAdapter { + type Status = { + type: "running" | "starting"; + } | { + type: "ended"; + reason: "cancelled" | "error" | "stopped"; + }; + type Result = { + transcript: string; + isFinal?: boolean; + }; + type Session = { + status: Status; + stop: () => Promise<void>; + cancel: () => void; + onSpeechStart: (callback: () => void) => Unsubscribe; + onSpeechEnd: (callback: (result: Result) => void) => Unsubscribe; + onSpeech: (callback: (result: Result) => void) => Unsubscribe; + }; +} + +type DictationAdapter = { + listen: () => DictationAdapter.Session; + disableInputDuringDictation?: boolean; +}; + +type DictationState = { + readonly status: DictationAdapter.Status; + readonly transcript?: string; + readonly inputDisabled?: boolean; +}; + +type EditComposerAttachmentState = Attachment & { + readonly source: "edit-composer"; +}; + +type EditComposerRuntime = Omit<ComposerRuntime, "getAttachmentByIndex" | "getState"> & { + readonly path: ComposerRuntimePath & { + composerSource: "edit"; + }; + readonly type: "edit"; + getState(): EditComposerState; + beginEdit(): void; + getAttachmentByIndex(idx: number): AttachmentRuntime & { + source: "edit-composer"; + }; +}; + +type EditComposerState = BaseComposerState & { + readonly type: "edit"; + readonly parentId: string | null; + readonly sourceId: string | null; +}; + +type ExportedMessageRepository = { + headId?: string | null; + messages: Array<{ + message: ThreadMessage; + parentId: string | null; + runConfig?: RunConfig; + }>; +}; + +declare const ExportedMessageRepository: { + fromArray: (messages: readonly ThreadMessageLike[]) => ExportedMessageRepository; + fromBranchableArray: (items: readonly { + message: ThreadMessageLike; + parentId: string | null; + }[], options?: { + headId?: string | null; + }) => ExportedMessageRepository; +}; + +type ExportedMessageRepositoryItem = { + message: ThreadMessage; + parentId: string | null; + runConfig?: RunConfig; +}; + +type ExternalStoreAdapter<T = ThreadMessage> = ExternalStoreAdapterBase<T> & (T extends ThreadMessage ? object : ExternalStoreMessageConverterAdapter<T>); + +type ExternalStoreAdapterBase<T> = { + isDisabled?: boolean | undefined; + isSendDisabled?: boolean | undefined; + isRunning?: boolean | undefined; + isLoading?: boolean | undefined; + messages?: readonly T[]; + messageRepository?: ExportedMessageRepository; + suggestions?: readonly ThreadSuggestion[] | undefined; + state?: ReadonlyJSONValue | undefined; + extras?: unknown; + setMessages?: ((messages: readonly T[]) => void) | undefined; + unstable_onBranchChange?: ((event: ExternalStoreBranchChange) => void) | undefined; + onImport?: ((messages: readonly ThreadMessage[]) => void) | undefined; + onExportExternalState?: (() => any) | undefined; + onLoadExternalState?: ((state: any) => void) | undefined; + onNew: (message: AppendMessage) => Promise<void>; + queue?: ExternalThreadQueueAdapter | undefined; + onEdit?: ((message: AppendMessage) => Promise<void>) | undefined; + onDelete?: ((messageId: string) => Promise<void> | void) | undefined; + onReload?: ((parentId: string | null, config: StartRunConfig) => Promise<void>) | undefined; + onResume?: ((config: ResumeRunConfig) => Promise<void>) | undefined; + onCancel?: (() => Promise<void>) | undefined; + onAddToolResult?: ((options: AddToolResultOptions) => Promise<void> | void) | undefined; + onResumeToolCall?: ((options: { + toolCallId: string; + payload: unknown; + }) => void) | undefined; + onRespondToToolApproval?: ((options: RespondToToolApprovalOptions) => Promise<void> | void) | undefined; + convertMessage?: ExternalStoreMessageConverter<T> | undefined; + adapters?: { + attachments?: AttachmentAdapter | undefined; + speech?: SpeechSynthesisAdapter | undefined; + dictation?: DictationAdapter | undefined; + voice?: RealtimeVoiceAdapter | undefined; + feedback?: FeedbackAdapter | undefined; + threadList?: ExternalStoreThreadListAdapter | undefined; + } | undefined; + unstable_capabilities?: { + copy?: boolean | undefined; + } | undefined; + unstable_enableToolInvocations?: boolean | undefined; + setToolStatuses?: ((statuses: Record<string, ToolExecutionStatus>) => void) | undefined; +}; + +type ExternalStoreBranchChange = { + headId: string | null; + visibleMessageIds: readonly string[]; +}; + +type ExternalStoreMessageConverter<T> = (message: T, idx: number) => ThreadMessageLike; + +type ExternalStoreMessageConverterAdapter<T> = { + convertMessage: ExternalStoreMessageConverter<T>; +}; + +type ExternalStoreSharedOptions = Pick<ExternalStoreAdapter, "isDisabled" | "isSendDisabled" | "suggestions" | "unstable_capabilities">; + +type ExternalStoreThreadData<TState extends "archived" | "regular"> = { + status: TState; + id: string; + remoteId?: string | undefined; + externalId?: string | undefined; + title?: string | undefined; + custom?: Record<string, unknown> | undefined; +}; + +type ExternalStoreThreadListAdapter = { + threadId?: string | undefined; + isLoading?: boolean | undefined; + threads?: readonly ExternalStoreThreadData<"regular">[] | undefined; + archivedThreads?: readonly ExternalStoreThreadData<"archived">[] | undefined; + onSwitchToNewThread?: (() => Promise<void> | void) | undefined; + onSwitchToThread?: ((threadId: string) => Promise<void> | void) | undefined; + onRename?: (threadId: string, newTitle: string) => (Promise<void> | void) | undefined; + onUpdateCustom?: ((threadId: string, custom: Record<string, unknown> | undefined) => Promise<void> | void) | undefined; + onArchive?: ((threadId: string) => Promise<void> | void) | undefined; + onUnarchive?: ((threadId: string) => Promise<void> | void) | undefined; + onDelete?: ((threadId: string) => Promise<void> | void) | undefined; +}; + +type ExternalThreadQueueAdapter = { + items: readonly QueueItemState[]; + enqueue: (message: AppendMessage, options: { + steer: boolean; + }) => void; + steer: (queueItemId: string) => void; + remove: (queueItemId: string) => void; + clear: (reason: "cancel-run" | "edit" | "reload") => void; +}; + +type FeedbackAdapter = { + submit: (feedback: FeedbackAdapterFeedback) => void; +}; + +type FeedbackAdapterFeedback = { + message: ThreadMessage; + type: "negative" | "positive"; +}; + +type FileMessagePart = { + readonly type: "file"; + readonly filename?: string; + readonly data: string; + readonly mimeType: string; + readonly parentId?: string; +}; + +type FromAgUiMessagesOptions = { + showThinking?: boolean; +}; + +type FrontendTool<TArgs extends Record<string, unknown> = Record<string, unknown>, TResult = unknown> = ToolBase<TArgs, TResult> & { + type: "frontend"; + description?: string | undefined; + parameters: StandardSchemaV1<TArgs> | JSONSchema7; + disabled?: boolean; + execute?: ToolExecuteFunction<TArgs, TResult>; + toModelOutput?: ToolModelOutputFunction<TArgs, TResult>; + experimental_onSchemaValidationError?: OnSchemaValidationErrorFunction<TResult>; + providerOptions?: ProviderOptions; +}; + +type GenerativeUIMessagePart = { + readonly type: "generative-ui"; + readonly spec: GenerativeUISpec; + readonly id?: string; + readonly parentId?: string; +}; + +type GenerativeUINode = string | { + readonly component: string; + readonly props?: Record<string, unknown>; + readonly children?: readonly GenerativeUINode[]; + readonly key?: string; +}; + +type GenerativeUISpec = { + readonly root: GenerativeUINode | readonly GenerativeUINode[]; +}; + +type GenericThreadHistoryAdapter<TMessage> = { + load(): Promise<MessageFormatRepository<TMessage>>; + append(item: MessageFormatItem<TMessage>): Promise<void>; + update?(item: MessageFormatItem<TMessage>, localMessageId: string): Promise<void>; + delete?(items: MessageFormatItem<TMessage>[]): Promise<void>; + reportTelemetry?(items: MessageFormatItem<TMessage>[], options?: { + durationMs?: number; + stepTimestamps?: { + start_ms: number; + end_ms: number; + }[]; + }): void; +}; + +type HumanTool<TArgs extends Record<string, unknown> = Record<string, unknown>, TResult = unknown> = ToolBase<TArgs, TResult> & { + type: "human"; + description?: string | undefined; + parameters: StandardSchemaV1<TArgs> | JSONSchema7; + disabled?: boolean; + display?: "standalone"; + execute?: undefined; + toModelOutput?: undefined; + experimental_onSchemaValidationError?: undefined; + providerOptions?: ProviderOptions; +}; + +type ImageMessagePart = { + readonly type: "image"; + readonly image: string; + readonly filename?: string; +}; + +interface JSONSchema7 { + $id?: string | undefined; + $ref?: string | undefined; + $schema?: JSONSchema7Version | undefined; + $comment?: string | undefined; + $defs?: { + [key: string]: JSONSchema7Definition; + } | undefined; + type?: JSONSchema7TypeName | JSONSchema7TypeName[] | undefined; + enum?: JSONSchema7Type[] | undefined; + const?: JSONSchema7Type | undefined; + multipleOf?: number | undefined; + maximum?: number | undefined; + exclusiveMaximum?: number | undefined; + minimum?: number | undefined; + exclusiveMinimum?: number | undefined; + maxLength?: number | undefined; + minLength?: number | undefined; + pattern?: string | undefined; + items?: JSONSchema7Definition | JSONSchema7Definition[] | undefined; + additionalItems?: JSONSchema7Definition | undefined; + maxItems?: number | undefined; + minItems?: number | undefined; + uniqueItems?: boolean | undefined; + contains?: JSONSchema7Definition | undefined; + maxProperties?: number | undefined; + minProperties?: number | undefined; + required?: string[] | undefined; + properties?: { + [key: string]: JSONSchema7Definition; + } | undefined; + patternProperties?: { + [key: string]: JSONSchema7Definition; + } | undefined; + additionalProperties?: JSONSchema7Definition | undefined; + dependencies?: { + [key: string]: JSONSchema7Definition | string[]; + } | undefined; + propertyNames?: JSONSchema7Definition | undefined; + if?: JSONSchema7Definition | undefined; + then?: JSONSchema7Definition | undefined; + else?: JSONSchema7Definition | undefined; + allOf?: JSONSchema7Definition[] | undefined; + anyOf?: JSONSchema7Definition[] | undefined; + oneOf?: JSONSchema7Definition[] | undefined; + not?: JSONSchema7Definition | undefined; + format?: string | undefined; + contentMediaType?: string | undefined; + contentEncoding?: string | undefined; + definitions?: { + [key: string]: JSONSchema7Definition; + } | undefined; + title?: string | undefined; + description?: string | undefined; + default?: JSONSchema7Type | undefined; + readOnly?: boolean | undefined; + writeOnly?: boolean | undefined; + examples?: JSONSchema7Type | undefined; +} + +interface JSONSchema7Array extends Array<JSONSchema7Type> { +} + +type JSONSchema7Definition = JSONSchema7 | boolean; + +interface JSONSchema7Object { + [key: string]: JSONSchema7Type; +} + +type JSONSchema7Type = string | number | boolean | JSONSchema7Object | JSONSchema7Array | null; + +type JSONSchema7TypeName = "array" | "boolean" | "integer" | "null" | "number" | "object" | "string"; + +type JSONSchema7Version = string; + +type LanguageModelConfig = { + apiKey?: string; + baseUrl?: string; + modelName?: string; + reasoningEffort?: string; +}; + +type LanguageModelV1CallSettings = { + maxTokens?: number; + temperature?: number; + topP?: number; + presencePenalty?: number; + frequencyPenalty?: number; + seed?: number; + headers?: Record<string, string | undefined>; +}; + +type Logger = { + debug: (...a: any[]) => void; + error: (...a: any[]) => void; +}; + +type McpAppMetadata = { + readonly resourceUri: string; + readonly mimeType?: string; + readonly visibility?: readonly ("app" | "model")[]; +}; + +type McpServerConfig = { + type: "http" | "sse"; + url: string; + headers?: Record<string, string>; + redirect?: "error" | "follow"; + connectionTimeout?: number | undefined; +} | { + type: "stdio"; + command: string; + args?: readonly string[]; + env?: Record<string, string>; + cwd?: string; + connectionTimeout?: number | undefined; +}; + +type McpTool = ToolBase<Record<string, unknown>, unknown> & { + type: "mcp"; + server: McpServerConfig; + description?: undefined; + parameters?: undefined; + disabled?: boolean; + execute?: undefined; + toModelOutput?: undefined; + experimental_onSchemaValidationError?: undefined; + providerOptions?: undefined; +}; + +type MessageAttachmentState = CompleteAttachment & { + readonly source: "message"; +}; + +type MessageCommonProps = { + readonly id: string; + readonly createdAt: Date; +}; + +interface MessageFormatAdapter<TMessage, TStorageFormat extends Record<string, unknown>> { + format: string; + encode(item: MessageFormatItem<TMessage>): TStorageFormat; + decode(stored: MessageStorageEntry<TStorageFormat>): MessageFormatItem<TMessage>; + getId(message: TMessage): string; +} + +interface MessageFormatItem<TMessage> { + parentId: string | null; + message: TMessage; +} + +interface MessageFormatRepository<TMessage> { + headId?: string | null; + messages: MessageFormatItem<TMessage>[]; +} + +type MessagePartRuntime = { + addToolResult(result: any | ToolResponse<any>): void; + resumeToolCall(payload: unknown): void; + respondToToolApproval(response: ToolApprovalResponse): void; + readonly path: MessagePartRuntimePath; + getState(): MessagePartState; + subscribe(callback: () => void): Unsubscribe; +}; + +type MessagePartRuntimePath = MessageRuntimePath & { + readonly messagePartSelector: { + readonly type: "index"; + readonly index: number; + } | { + readonly type: "toolCallId"; + readonly toolCallId: string; + }; +}; + +type MessagePartState = (ThreadUserMessagePart | ThreadAssistantMessagePart) & { + readonly status: MessagePartStatus | ToolCallMessagePartStatus; +}; + +type MessagePartStatus = { + readonly type: "running"; +} | { + readonly type: "complete"; +} | { + readonly type: "incomplete"; + readonly reason: "cancelled" | "content-filter" | "error" | "length" | "other"; + readonly error?: unknown; +}; + +type MessageRole = ThreadMessage["role"]; + +type MessageRuntime = { + readonly path: MessageRuntimePath; + readonly composer: EditComposerRuntime; + getState(): MessageState; + delete(): void | Promise<void>; + reload(config?: ReloadConfig): void; + speak(): void; + stopSpeaking(): void; + submitFeedback(_param0: { + type: "positive" | "negative"; + }): void; + switchToBranch(_param1: { + position?: "previous" | "next" | undefined; + branchId?: string | undefined; + }): void; + unstable_getCopyText(): string; + subscribe(callback: () => void): Unsubscribe; + getMessagePartByIndex(idx: number): MessagePartRuntime; + getMessagePartByToolCallId(toolCallId: string): MessagePartRuntime; + getAttachmentByIndex(idx: number): AttachmentRuntime & { + source: "message"; + }; +}; + +type MessageRuntimePath = ThreadRuntimePath & { + readonly messageSelector: { + readonly type: "messageId"; + readonly messageId: string; + } | { + readonly type: "index"; + readonly index: number; + }; +}; + +type MessageState = ThreadMessage & { + readonly parentId: string | null; + readonly index: number; + readonly isLast: boolean; + readonly branchNumber: number; + readonly branchCount: number; + readonly speech: SpeechState | undefined; +}; + +type MessageStatus = { + readonly type: "running"; +} | { + readonly type: "requires-action"; + readonly reason: "interrupt" | "tool-calls"; +} | { + readonly type: "complete"; + readonly reason: "stop" | "unknown"; +} | { + readonly type: "incomplete"; + readonly reason: "cancelled" | "content-filter" | "error" | "length" | "other" | "tool-calls"; + readonly error?: ReadonlyJSONValue; +}; + +interface MessageStorageEntry<TPayload> { + id: string; + parent_id: string | null; + format: string; + content: TPayload; +} + +type MessageTiming = { + readonly streamStartTime: number; + readonly firstTokenTime?: number; + readonly totalStreamTime?: number; + readonly tokenCount?: number; + readonly tokensPerSecond?: number; + readonly totalChunks: number; + readonly toolCallCount: number; +}; + +type ModelContext = { + priority?: number | undefined; + system?: string | undefined; + tools?: Record<string, Tool<any, any>> | undefined; + callSettings?: LanguageModelV1CallSettings | undefined; + config?: LanguageModelConfig | undefined; + unstable_composerMetadata?: Record<string, unknown> | undefined; +}; + +type ModelContextProvider = { + getModelContext: () => ModelContext; + subscribe?: (callback: () => void) => Unsubscribe; +}; + +type ObjectKey<T> = keyof T & (string | number); + +type OnSchemaValidationErrorFunction<TResult> = ToolExecuteFunction<unknown, TResult>; + +type PartProviderMetadata = { + readonly [providerName: string]: ReadonlyJSONObject; +}; + +type PendingAttachment = BaseAttachment & { + status: PendingAttachmentStatus; + file: File; +}; + +type PendingAttachmentStatus = { + type: "running"; + reason: "uploading"; + progress: number; +} | { + type: "requires-action"; + reason: "composer-send"; +} | { + type: "incomplete"; + reason: "error" | "upload-paused"; + message?: string; +}; + +type ProviderOptions = Record<string, Record<string, unknown>>; + +type ProviderTool<TArgs extends Record<string, unknown> = Record<string, unknown>, TResult = unknown> = ToolBase<TArgs, TResult> & { + type: "provider"; + providerId: `${string}.${string}`; + parameters?: StandardSchemaV1<TArgs> | JSONSchema7 | undefined; + args: Record<string, unknown>; + supportsDeferredResults?: boolean; + description?: undefined; + disabled?: boolean; + execute?: undefined; + toModelOutput?: undefined; + experimental_onSchemaValidationError?: undefined; + providerOptions?: ProviderOptions; +}; + +type QueueItemState = { + readonly id: string; + readonly prompt: string; +}; + +type QuoteInfo = { + readonly text: string; + readonly messageId: string; +}; + +type ReadonlyJSONArray = readonly ReadonlyJSONValue[]; + +type ReadonlyJSONObject = { + readonly [key: string]: ReadonlyJSONValue; +}; + +type ReadonlyJSONValue = null | string | number | boolean | ReadonlyJSONObject | ReadonlyJSONArray; + +declare namespace RealtimeVoiceAdapter { + type Status = { + type: "running" | "starting"; + } | { + type: "ended"; + reason: "cancelled" | "error" | "finished"; + error?: unknown; + }; + type Mode = "listening" | "speaking"; + type TranscriptItem = { + role: "assistant" | "user"; + text: string; + isFinal?: boolean; + }; + type Session = { + status: Status; + isMuted: boolean; + disconnect: () => void; + mute: () => void; + unmute: () => void; + onStatusChange: (callback: (status: Status) => void) => Unsubscribe; + onTranscript: (callback: (transcript: TranscriptItem) => void) => Unsubscribe; + onModeChange: (callback: (mode: Mode) => void) => Unsubscribe; + onVolumeChange: (callback: (volume: number) => void) => Unsubscribe; + }; +} + +type RealtimeVoiceAdapter = { + connect: (options: { + abortSignal?: AbortSignal; + }) => RealtimeVoiceAdapter.Session; +}; + +type ReasoningMessagePart = { + readonly type: "reasoning"; + readonly text: string; + readonly providerMetadata?: PartProviderMetadata; + readonly parentId?: string; +}; + +type ReloadConfig = { + runConfig?: RunConfig; +}; + +type RespondToToolApprovalOptions = { + approvalId: string; + approved: boolean; + optionId?: string; + reason?: string; +}; + +type ResumeRunConfig = StartRunConfig & { + stream?: (options: ChatModelRunOptions) => AsyncGenerator<ChatModelRunResult, void, unknown>; +}; + +type RunConfig = { + readonly custom?: Record<string, unknown>; +}; + +type RuntimeCapabilities = { + readonly switchToBranch: boolean; + readonly switchBranchDuringRun: boolean; + readonly edit: boolean; + readonly reload: boolean; + readonly delete: boolean; + readonly cancel: boolean; + readonly unstable_copy: boolean; + readonly speech: boolean; + readonly dictation: boolean; + readonly voice: boolean; + readonly attachments: boolean; + readonly feedback: boolean; + readonly queue: boolean; +}; + +type SendOptions = { + startRun?: boolean; + steer?: boolean; +}; + +type SourceMessagePart = { + readonly type: "source"; + readonly sourceType: "url"; + readonly id: string; + readonly url: string; + readonly title?: string; + readonly providerMetadata?: SourceProviderMetadata; + readonly parentId?: string; +} | { + readonly type: "source"; + readonly sourceType: "document"; + readonly id: string; + readonly url?: undefined; + readonly title: string; + readonly mediaType: string; + readonly filename?: string; + readonly providerMetadata?: SourceProviderMetadata; + readonly parentId?: string; +}; + +type SourceProviderMetadata = PartProviderMetadata; + +interface SpeechRecognitionConstructor { + new (): SpeechRecognitionInstance; +} + +interface SpeechRecognitionInstance extends EventTarget { + lang: string; + continuous: boolean; + interimResults: boolean; + start(): void; + stop(): void; + abort(): void; +} + +type SpeechState = { + readonly messageId: string; + readonly status: SpeechSynthesisAdapter.Status; +}; + +declare namespace SpeechSynthesisAdapter { + type Status = { + type: "running" | "starting"; + } | { + type: "ended"; + reason: "cancelled" | "error" | "finished"; + error?: unknown; + }; + type Utterance = { + status: Status; + cancel: () => void; + subscribe: (callback: () => void) => Unsubscribe; + }; +} + +type SpeechSynthesisAdapter = { + speak: (text: string) => SpeechSynthesisAdapter.Utterance; +}; + +type StartRunConfig = { + parentId: string | null; + sourceId: string | null; + runConfig: RunConfig; +}; + +type SwitchToThreadResult = { + messages: readonly ThreadMessage[]; + state?: ReadonlyJSONValue; + unstable_resume?: boolean; +}; + +declare const TOOL_RESPONSE_SYMBOL: unique symbol; + +type TextMessagePart = { + readonly type: "text"; + readonly text: string; + readonly providerMetadata?: PartProviderMetadata; + readonly parentId?: string; +}; + +type ThreadAssistantMessage = MessageCommonProps & { + readonly role: "assistant"; + readonly content: readonly ThreadAssistantMessagePart[]; + readonly status: MessageStatus; + readonly metadata: { + readonly unstable_state: ReadonlyJSONValue; + readonly unstable_annotations: readonly ReadonlyJSONValue[]; + readonly unstable_data: readonly ReadonlyJSONValue[]; + readonly steps: readonly ThreadStep[]; + readonly submittedFeedback?: { + readonly type: "negative" | "positive"; + }; + readonly timing?: MessageTiming; + readonly isOptimistic?: boolean; + readonly custom: Record<string, unknown>; + }; +}; + +type ThreadAssistantMessagePart = TextMessagePart | ReasoningMessagePart | ToolCallMessagePart | SourceMessagePart | FileMessagePart | ImageMessagePart | DataMessagePart | GenerativeUIMessagePart; + +type ThreadComposerAttachmentState = Attachment & { + readonly source: "thread-composer"; +}; + +type ThreadComposerRuntime = Omit<ComposerRuntime, "getAttachmentByIndex" | "getState"> & { + readonly path: ComposerRuntimePath & { + composerSource: "thread"; + }; + readonly type: "thread"; + getState(): ThreadComposerState; + getAttachmentByIndex(idx: number): AttachmentRuntime & { + source: "thread-composer"; + }; +}; + +type ThreadComposerState = BaseComposerState & { + readonly type: "thread"; +}; + +type ThreadHistoryAdapter = { + load(): Promise<ExportedMessageRepository & { + state?: ReadonlyJSONValue; + unstable_resume?: boolean; + }>; + resume?(options: ChatModelRunOptions): AsyncGenerator<ChatModelRunResult, void, unknown>; + append(item: ExportedMessageRepositoryItem): Promise<void>; + delete?(items: ExportedMessageRepositoryItem[]): Promise<void>; + withFormat?<TMessage, TStorageFormat extends Record<string, unknown>>(formatAdapter: MessageFormatAdapter<TMessage, TStorageFormat>): GenericThreadHistoryAdapter<TMessage>; +}; + +type ThreadListItemEventCallback<E extends ThreadListItemEventType> = (payload: ThreadListItemEventPayload[E]) => void; + +type ThreadListItemEventPayload = { + switchedTo: Record<string, never>; + switchedAway: Record<string, never>; +}; + +type ThreadListItemEventType = keyof ThreadListItemEventPayload; + +type ThreadListItemRuntime = { + readonly path: ThreadListItemRuntimePath; + getState(): ThreadListItemState; + initialize(): Promise<{ + remoteId: string; + externalId: string | undefined; + }>; + generateTitle(): Promise<void>; + switchTo(options?: { + unarchive?: boolean; + }): Promise<void>; + rename(newTitle: string): Promise<void>; + updateCustom(custom: Record<string, unknown> | undefined): Promise<void>; + archive(): Promise<void>; + unarchive(): Promise<void>; + delete(): Promise<void>; + detach(): void; + subscribe(callback: () => void): Unsubscribe; + unstable_on<E extends ThreadListItemEventType>(event: E, callback: ThreadListItemEventCallback<E>): Unsubscribe; + __internal_getRuntime(): ThreadListItemRuntime; +}; + +type ThreadListItemRuntimePath = { + readonly ref: string; + readonly threadSelector: { + readonly type: "main"; + } | { + readonly type: "index"; + readonly index: number; + } | { + readonly type: "archiveIndex"; + readonly index: number; + } | { + readonly type: "threadId"; + readonly threadId: string; + }; +}; + +type ThreadListItemState = { + readonly isMain: boolean; + readonly id: string; + readonly remoteId: string | undefined; + readonly externalId: string | undefined; + readonly status: ThreadListItemStatus; + readonly title?: string | undefined; + readonly lastMessageAt?: Date | undefined; + readonly custom?: Record<string, unknown> | undefined; +}; + +type ThreadListItemStatus = "archived" | "deleted" | "new" | "regular"; + +type ThreadListRuntime = { + getState(): ThreadListState; + subscribe(callback: () => void): Unsubscribe; + readonly main: ThreadRuntime; + getById(threadId: string): ThreadRuntime; + readonly mainItem: ThreadListItemRuntime; + getItemById(threadId: string): ThreadListItemRuntime; + getItemByIndex(idx: number): ThreadListItemRuntime; + getArchivedItemByIndex(idx: number): ThreadListItemRuntime; + switchToThread(threadId: string, options?: { + unarchive?: boolean; + }): Promise<void>; + switchToNewThread(): Promise<void>; + getLoadThreadsPromise(): Promise<void>; + reload(): Promise<void>; + loadMore(): Promise<void>; +}; + +type ThreadListState = { + readonly mainThreadId: string; + readonly newThreadId: string | undefined; + readonly threadIds: readonly string[]; + readonly archivedThreadIds: readonly string[]; + readonly isLoading: boolean; + readonly isLoadingMore: boolean; + readonly hasMore: boolean; + readonly threadItems: Readonly<Record<string, Omit<ThreadListItemState, "isMain" | "threadId">>>; +}; + +type ThreadMessage = BaseThreadMessage & (ThreadSystemMessage | ThreadUserMessage | ThreadAssistantMessage); + +type ThreadMessageLike = { + readonly role: "assistant" | "system" | "user"; + readonly content: string | readonly (TextMessagePart | ReasoningMessagePart | SourceMessagePart | ImageMessagePart | FileMessagePart | DataMessagePart | GenerativeUIMessagePart | Unstable_AudioMessagePart | DataPrefixedPart | { + readonly type: "tool-call"; + readonly toolCallId?: string; + readonly toolName: string; + readonly args?: ReadonlyJSONObject; + readonly argsText?: string; + readonly artifact?: any; + readonly result?: any | undefined; + readonly isError?: boolean | undefined; + readonly parentId?: string | undefined; + readonly messages?: readonly ThreadMessage[] | undefined; + readonly interrupt?: { + type: "human"; + payload: unknown; + }; + readonly timing?: ToolCallTiming; + readonly providerMetadata?: PartProviderMetadata; + readonly approval?: { + readonly id: string; + readonly approved?: boolean; + readonly reason?: string; + readonly isAutomatic?: boolean; + readonly options?: readonly ToolApprovalOption[]; + readonly optionId?: string; + readonly resolution?: "cancelled" | "expired"; + }; + })[]; + readonly id?: string | undefined; + readonly createdAt?: Date | undefined; + readonly status?: MessageStatus | undefined; + readonly attachments?: readonly (Omit<CompleteAttachment, "content"> & { + readonly content: readonly (ThreadUserMessagePart | DataPrefixedPart)[]; + })[] | undefined; + readonly metadata?: { + readonly unstable_state?: ReadonlyJSONValue; + readonly unstable_annotations?: readonly ReadonlyJSONValue[] | undefined; + readonly unstable_data?: readonly ReadonlyJSONValue[] | undefined; + readonly steps?: readonly ThreadStep[] | undefined; + readonly timing?: MessageTiming | undefined; + readonly submittedFeedback?: { + readonly type: "negative" | "positive"; + }; + readonly isOptimistic?: boolean | undefined; + readonly custom?: Record<string, unknown> | undefined; + } | undefined; +}; + +type ThreadRuntime = { + readonly path: ThreadRuntimePath; + readonly composer: ThreadComposerRuntime; + getState(): ThreadState; + append(message: CreateAppendMessage): void; + deleteMessage(messageId: string): void | Promise<void>; + startRun(config: CreateStartRunConfig): void; + resumeRun(config: CreateResumeRunConfig): void; + exportExternalState(): any; + importExternalState(state: any): void; + subscribe(callback: () => void): Unsubscribe; + cancelRun(): void; + getModelContext(): ModelContext; + export(): ExportedMessageRepository; + import(repository: ExportedMessageRepository): void; + reset(initialMessages?: readonly ThreadMessageLike[]): void; + getMessageByIndex(idx: number): MessageRuntime; + getMessageById(messageId: string): MessageRuntime; + stopSpeaking(): void; + connectVoice(): void; + disconnectVoice(): void; + getVoiceVolume(): number; + subscribeVoiceVolume(callback: () => void): Unsubscribe; + muteVoice(): void; + unmuteVoice(): void; + unstable_on<E extends ThreadRuntimeEventType>(event: E, callback: ThreadRuntimeEventCallback<E>): Unsubscribe; +}; + +type ThreadRuntimeEventCallback<E extends ThreadRuntimeEventType> = (payload: ThreadRuntimeEventPayload[E]) => void; + +type ThreadRuntimeEventPayload = { + runStart: Record<string, never>; + runEnd: Record<string, never>; + initialize: Record<string, never>; + modelContextUpdate: Record<string, never>; +}; + +type ThreadRuntimeEventType = keyof ThreadRuntimeEventPayload; + +type ThreadRuntimePath = { + readonly ref: string; + readonly threadSelector: { + readonly type: "main"; + } | { + readonly type: "threadId"; + readonly threadId: string; + }; +}; + +type ThreadState = { + readonly threadId: string; + readonly metadata: ThreadListItemState; + readonly isDisabled: boolean; + readonly isLoading: boolean; + readonly isRunning: boolean; + readonly capabilities: RuntimeCapabilities; + readonly messages: readonly ThreadMessage[]; + readonly state: ReadonlyJSONValue; + readonly suggestions: readonly ThreadSuggestion[]; + readonly extras: unknown; + readonly speech: SpeechState | undefined; + readonly voice: VoiceSessionState | undefined; +}; + +type ThreadStep = { + readonly messageId?: string; + readonly usage?: { + readonly inputTokens: number; + readonly outputTokens: number; + } | undefined; +}; + +type ThreadSuggestion = { + prompt: string; +}; + +type ThreadSystemMessage = MessageCommonProps & { + readonly role: "system"; + readonly content: readonly [ + TextMessagePart + ]; + readonly metadata: { + readonly unstable_state?: undefined; + readonly unstable_annotations?: undefined; + readonly unstable_data?: undefined; + readonly steps?: undefined; + readonly submittedFeedback?: undefined; + readonly timing?: undefined; + readonly custom: Record<string, unknown>; + }; +}; + +type ThreadUserMessage = MessageCommonProps & { + readonly role: "user"; + readonly content: readonly ThreadUserMessagePart[]; + readonly attachments: readonly CompleteAttachment[]; + readonly metadata: { + readonly unstable_state?: undefined; + readonly unstable_annotations?: undefined; + readonly unstable_data?: undefined; + readonly steps?: undefined; + readonly submittedFeedback?: undefined; + readonly timing?: undefined; + readonly custom: Record<string, unknown>; + }; +}; + +type ThreadUserMessagePart = TextMessagePart | ImageMessagePart | FileMessagePart | DataMessagePart | Unstable_AudioMessagePart; + +type Tool<TArgs extends Record<string, unknown> = Record<string, unknown>, TResult = unknown> = FrontendTool<TArgs, TResult> | BackendTool<TArgs, TResult> | HumanTool<TArgs, TResult> | ProviderTool<TArgs, TResult> | McpTool | ToolWithoutType<TArgs, TResult>; + +type ToolApprovalOption = { + readonly id: string; + readonly kind: ToolApprovalOptionKind | (string & {}); + readonly label?: string; + readonly description?: string; + readonly grants?: readonly string[]; + readonly confirm?: boolean | { + title?: string; + description?: string; + }; +}; + +type ToolApprovalOptionKind = "allow-always" | "allow-once" | "reject-always" | "reject-once"; + +type ToolApprovalResponse = { + readonly approved: boolean; + readonly reason?: string; +} | { + readonly optionId: string; + readonly reason?: string; +} | { + readonly approved: boolean; + readonly optionId: string; + readonly reason?: string; +}; + +type ToolBase<TArgs extends Record<string, unknown> = Record<string, unknown>, TResult = unknown> = { + streamCall?: ToolStreamCallFunction<TArgs, TResult>; + display?: ToolDisplay; +}; + +interface ToolCallArgsReader<TArgs extends Record<string, unknown>> { + get<PathT extends TypePath<TArgs>>(...fieldPath: PathT): Promise<TypeAtPath<TArgs, PathT>>; + streamValues<PathT extends TypePath<TArgs>>(...fieldPath: PathT): AsyncIterableStream<DeepPartial<TypeAtPath<TArgs, PathT>>>; + streamText<PathT extends TypePath<TArgs>>(...fieldPath: PathT): TypeAtPath<TArgs, PathT> extends string & (infer U) ? AsyncIterableStream<U> : never; + forEach<PathT extends TypePath<TArgs>>(...fieldPath: PathT): NonNullable<TypeAtPath<TArgs, PathT>> extends Array<infer U> ? AsyncIterableStream<U> : never; +} + +type ToolCallMessagePart<TArgs = ReadonlyJSONObject, TResult = unknown> = { + readonly type: "tool-call"; + readonly toolCallId: string; + readonly toolName: string; + readonly args: TArgs; + readonly result?: TResult | undefined; + readonly isError?: boolean | undefined; + readonly argsText: string; + readonly artifact?: unknown; + readonly timing?: ToolCallTiming; + readonly mcp?: ToolCallMessagePartMcpMetadata; + readonly providerMetadata?: PartProviderMetadata; + readonly modelContent?: readonly ToolModelContentPart[] | undefined; + readonly interrupt?: { + type: "human"; + payload: unknown; + }; + readonly approval?: { + readonly id: string; + readonly approved?: boolean; + readonly reason?: string; + readonly isAutomatic?: boolean; + readonly options?: readonly ToolApprovalOption[]; + readonly optionId?: string; + readonly resolution?: "cancelled" | "expired"; + }; + readonly parentId?: string; + readonly messages?: readonly ThreadMessage[]; +}; + +type ToolCallMessagePartMcpMetadata = { + readonly app?: McpAppMetadata; +}; + +type ToolCallMessagePartStatus = { + readonly type: "requires-action"; + readonly reason: "interrupt"; +} | MessagePartStatus; + +interface ToolCallReader<TArgs extends Record<string, unknown> = Record<string, unknown>, TResult = unknown> { + args: ToolCallArgsReader<TArgs>; + response: ToolCallResponseReader<TResult>; + result: { + get: () => Promise<TResult>; + }; +} + +interface ToolCallResponseReader<TResult> { + get: () => Promise<ToolResponse<TResult>>; +} + +type ToolCallTiming = { + readonly startedAt: number; + readonly completedAt?: number; +}; + +type ToolDisplay = "inline" | "standalone"; + +type ToolExecuteFunction<TArgs, TResult> = (args: TArgs, context: ToolExecutionContext) => TResult | Promise<TResult>; + +type ToolExecutionContext = { + toolCallId: string; + abortSignal: AbortSignal; + human: (payload: unknown) => Promise<unknown>; +}; + +type ToolExecutionStatus = { + type: "executing"; +} | { + type: "interrupt"; + payload: { + type: "human"; + payload: unknown; + }; +}; + +type ToolModelContentPart = { + readonly type: "text"; + readonly text: string; +} | { + readonly type: "file"; + readonly data: string; + readonly mediaType: string; + readonly filename?: string; +}; + +type ToolModelOutputFunction<TArgs, TResult> = (options: { + toolCallId: string; + input: TArgs; + output: TResult; +}) => readonly ToolModelContentPart[] | Promise<readonly ToolModelContentPart[]>; + +declare class ToolResponse<TResult> { + get [TOOL_RESPONSE_SYMBOL](): boolean; + readonly artifact?: ReadonlyJSONValue; + readonly result: TResult; + readonly isError: boolean; + readonly modelContent?: readonly ToolModelContentPart[]; + readonly messages?: ReadonlyJSONValue; + constructor(options: ToolResponseLike<TResult>); + static [Symbol.hasInstance](obj: unknown): obj is ToolResponse<ReadonlyJSONValue>; + static toResponse(result: any | ToolResponse<any>): ToolResponse<any>; +} + +type ToolResponseLike<TResult> = { + result: TResult; + artifact?: ReadonlyJSONValue | undefined; + isError?: boolean | undefined; + modelContent?: readonly ToolModelContentPart[] | undefined; + messages?: ReadonlyJSONValue | undefined; +}; + +type ToolStreamCallFunction<TArgs extends Record<string, unknown> = Record<string, unknown>, TResult = unknown> = (reader: ToolCallReader<TArgs, TResult>, context: ToolExecutionContext) => void; + +type ToolWithoutType<TArgs extends Record<string, unknown> = Record<string, unknown>, TResult = unknown> = (Omit<FrontendTool<TArgs, TResult>, "type"> | Omit<BackendTool<TArgs, TResult>, "type"> | Omit<HumanTool<TArgs, TResult>, "type"> | Omit<ProviderTool<TArgs, TResult>, "type">) & { + type?: undefined; +}; + +type TupleIndex<T extends readonly any[]> = Exclude<keyof T, keyof any[]>; + +type TypeAtPath<T, P extends readonly any[]> = P extends [ + infer Head, + ...infer Rest +] ? Head extends keyof T ? TypeAtPath<T[Head], Rest> : never : T; + +type TypePath<T> = [ +] | (0 extends 1 & T ? any[] : T extends object ? T extends readonly any[] ? number extends T["length"] ? { + [K in TupleIndex<T>]: [ + AsNumber<K>, + ...TypePath<T[K]> + ]; +}[TupleIndex<T>] : [ + number, + ...TypePath<T[number]> +] : { + [K in ObjectKey<T>]: [ + K, + ...TypePath<T[K]> + ]; +}[ObjectKey<T>] : [ +]); + +type Unstable_AudioMessagePart = { + readonly type: "audio"; + readonly audio: { + readonly data: string; + readonly format: "mp3" | "wav"; + }; +}; + +type Unsubscribe = () => void; + +type UseAgUiRuntimeAdapters = { + attachments?: AttachmentAdapter; + speech?: SpeechSynthesisAdapter; + dictation?: DictationAdapter; + voice?: RealtimeVoiceAdapter; + feedback?: FeedbackAdapter; + history?: ThreadHistoryAdapter; + threadList?: UseAgUiThreadListAdapter; +}; + +type UseAgUiRuntimeOptions = ExternalStoreSharedOptions & { + agent: AbstractAgent; + logger?: Partial<Logger>; + showThinking?: boolean; + autoCancelPendingToolCalls?: boolean | undefined; + onError?: (e: Error) => void; + onCancel?: () => void; + adapters?: UseAgUiRuntimeAdapters; +}; + +type UseAgUiThreadListAdapter = Omit<ExternalStoreThreadListAdapter, "onSwitchToThread"> & { + onSwitchToThread?: ((threadId: string) => Promise<SwitchToThreadResult> | SwitchToThreadResult) | undefined; +}; + +type VoiceSessionState = { + readonly status: RealtimeVoiceAdapter.Status; + readonly isMuted: boolean; + readonly mode: RealtimeVoiceAdapter.Mode; +}; + +declare function fromAgUiMessages(messages: readonly unknown[], options?: FromAgUiMessagesOptions): ThreadMessageLike[]; + +declare global { + interface Window { + SpeechRecognition?: SpeechRecognitionConstructor; + webkitSpeechRecognition?: SpeechRecognitionConstructor; + } +} + +declare namespace entry_root_exports { + export { AgUiAssistantRuntime, AgUiInterrupt, AgUiInterruptReason, AgUiResumeEntry, AgUiRunFinishedOutcome, FromAgUiMessagesOptions, UseAgUiRuntimeAdapters, UseAgUiRuntimeOptions, UseAgUiThreadListAdapter, fromAgUiMessages, useAgUiInterrupts, useAgUiRuntime, useAgUiSteerAway, useAgUiSubmitInterruptResponses }; +} + +declare const useAgUiInterrupts: () => readonly AgUiInterrupt[]; + +declare function useAgUiRuntime(options: UseAgUiRuntimeOptions): AgUiAssistantRuntime; + +declare const useAgUiSteerAway: () => (message: CreateAppendMessage, responses?: readonly AgUiResumeEntry[]) => Promise<void>; + +declare const useAgUiSubmitInterruptResponses: () => (responses: readonly AgUiResumeEntry[]) => Promise<void>; + +export { entry_root_exports as entry_root }; diff --git a/api-surface/assistant-ui__react-ai-sdk.ts b/api-surface/assistant-ui__react-ai-sdk.ts new file mode 100644 index 0000000..53a4201 --- /dev/null +++ b/api-surface/assistant-ui__react-ai-sdk.ts @@ -0,0 +1,2030 @@ +import { CreateUIMessage, UIMessage as UIMessage$1, useChat } from "@ai-sdk/react"; + +import { StandardSchemaV1 } from "@standard-schema/spec"; + +import { ChatInit, DefaultChatTransport, HttpChatTransportInitOptions, ToolSet, UIMessage } from "ai"; + +import { ComponentType, ReactNode } from "react"; + +type AISDKRuntimeAdapter = ExternalStoreSharedOptions & { + adapters?: (NonNullable<ExternalStoreAdapter["adapters"]> & { + history?: ThreadHistoryAdapter | undefined; + }) | undefined; + toCreateMessage?: CustomToCreateMessageFunction; + cancelPendingToolCallsOnSend?: boolean | undefined; + onResume?: ExternalStoreAdapter["onResume"]; + joinStrategy?: JoinStrategy | undefined; +}; + +declare class AISDKToolkit { + #private; + constructor(options: AISDKToolkitOptions); + tools(options?: AISDKToolkitToolsOptions): Promise<ToolSet>; + close(): Promise<void>; +} + +type AISDKToolkitOptions = { + toolkit: Toolkit; +}; + +type AISDKToolkitToolsOptions = { + frontend?: FrontendTools; +}; + +type AddToolResultOptions = { + messageId: string; + toolName: string; + toolCallId: string; + result: ReadonlyJSONValue; + isError: boolean; + artifact?: ReadonlyJSONValue | undefined; + modelContent?: readonly ToolModelContentPart[] | undefined; +}; + +type AppendMessage = Omit<ThreadMessage, "id"> & { + parentId: string | null; + sourceId: string | null; + runConfig: RunConfig | undefined; + startRun?: boolean | undefined; + steer?: boolean | undefined; +}; + +type AsNumber<K> = K extends `${infer N extends number}` ? N | K : never; + +type AssistantChatResumableOptions = { + storage: ResumableClientStorage; + resumeApi: string | ((streamId: string) => string); + isFinishEvent?: (chunk: Uint8Array, accumulator: string) => boolean; +}; + +declare class AssistantChatTransport<UI_MESSAGE extends UIMessage> extends DefaultChatTransport<UI_MESSAGE> { + private runtime; + private getThreadListItem; + private readonly resumable; + constructor(initOptions?: AssistantChatTransportInitOptions<UI_MESSAGE>); + setRuntime(runtime: AssistantRuntime): void; + getResumableAdapter(): AssistantChatResumableOptions | undefined; + __internal_setGetThreadListItem(getter: () => InitializableThreadListItem | undefined): void; +} + +type AssistantChatTransportInitOptions<UI_MESSAGE extends UIMessage> = HttpChatTransportInitOptions<UI_MESSAGE> & { + resumable?: AssistantChatResumableOptions; +}; + +declare class AssistantCloud { + readonly threads: AssistantCloudThreads; + readonly auth: { + tokens: AssistantCloudAuthTokens; + }; + readonly runs: AssistantCloudRuns; + readonly files: AssistantCloudFiles; + readonly telemetry: AssistantCloudTelemetryConfig; + constructor(config: AssistantCloudConfig); +} + +declare class AssistantCloudAPI { + _auth: AssistantCloudAuthStrategy; + _baseUrl: string; + constructor(config: AssistantCloudConfig); + initializeAuth(): Promise<boolean>; + makeRawRequest(endpoint: string, options?: MakeRequestOptions): Promise<Response>; + makeRequest(endpoint: string, options?: MakeRequestOptions): Promise<any>; +} + +type AssistantCloudAuthStrategy = { + readonly strategy: "anon" | "api-key" | "jwt"; + getAuthHeaders(): Promise<Record<string, string> | false>; + readAuthHeaders(headers: Headers): void; +}; + +declare class AssistantCloudAuthTokens { + private cloud; + constructor(cloud: AssistantCloudAPI); + create(): Promise<AssistantCloudAuthTokensCreateResponse>; +} + +type AssistantCloudAuthTokensCreateResponse = { + token: string; +}; + +type AssistantCloudConfig = ({ + baseUrl: string; + authToken: () => Promise<string | null>; +} | { + baseUrl?: string; + apiKey: string; + userId: string; + workspaceId: string; +} | { + baseUrl: string; + anonymous: true; +}) & { + telemetry?: boolean | AssistantCloudTelemetryConfig; +}; + +declare class AssistantCloudFiles { + private cloud; + constructor(cloud: AssistantCloudAPI); + pdfToImages(body: PdfToImagesRequestBody): Promise<PdfToImagesResponse>; + generatePresignedUploadUrl(body: GeneratePresignedUploadUrlRequestBody): Promise<GeneratePresignedUploadUrlResponse>; +} + +type AssistantCloudMessageCreateResponse = { + message_id: string; +}; + +type AssistantCloudRunReport = { + thread_id: string; + status: "completed" | "error" | "incomplete"; + total_steps?: number; + tool_calls?: ReportToolCall[]; + steps?: { + input_tokens?: number; + output_tokens?: number; + reasoning_tokens?: number; + cached_input_tokens?: number; + tool_calls?: ReportToolCall[]; + start_ms?: number; + end_ms?: number; + }[]; + input_tokens?: number; + output_tokens?: number; + reasoning_tokens?: number; + cached_input_tokens?: number; + model_id?: string; + provider_type?: string; + duration_ms?: number; + output_text?: string; + metadata?: Record<string, unknown>; +}; + +declare class AssistantCloudRuns { + private cloud; + constructor(cloud: AssistantCloudAPI); + __internal_getAssistantOptions(assistantId: string): { + api: string; + headers: () => Promise<{ + Accept: string; + }>; + body: { + assistant_id: string; + response_format: string; + thread_id: string; + }; + }; + stream(body: AssistantCloudRunsStreamBody): Promise<AssistantStream>; + report(body: AssistantCloudRunReport): Promise<{ + run_id: string; + }>; +} + +type AssistantCloudRunsStreamBody = { + thread_id: string; + assistant_id: "system/thread_title"; + messages: readonly unknown[]; +}; + +type AssistantCloudTelemetryConfig = { + enabled?: boolean; + beforeReport?: (report: AssistantCloudRunReport) => AssistantCloudRunReport | null; +}; + +type AssistantCloudThreadMessageCreateBody = { + parent_id: string | null; + format: "aui/v0" | string; + content: ReadonlyJSONObject; +}; + +type AssistantCloudThreadMessageListQuery = { + format?: string; +}; + +type AssistantCloudThreadMessageListResponse = { + messages: CloudMessage[]; +}; + +type AssistantCloudThreadMessageUpdateBody = { + content: ReadonlyJSONObject; +}; + +declare class AssistantCloudThreadMessages { + private cloud; + constructor(cloud: AssistantCloudAPI); + list(threadId: string, query?: AssistantCloudThreadMessageListQuery): Promise<AssistantCloudThreadMessageListResponse>; + create(threadId: string, body: AssistantCloudThreadMessageCreateBody): Promise<AssistantCloudMessageCreateResponse>; + update(threadId: string, messageId: string, body: AssistantCloudThreadMessageUpdateBody): Promise<void>; +} + +declare class AssistantCloudThreads { + private cloud; + readonly messages: AssistantCloudThreadMessages; + constructor(cloud: AssistantCloudAPI); + list(query?: AssistantCloudThreadsListQuery): Promise<AssistantCloudThreadsListResponse>; + get(threadId: string): Promise<CloudThread>; + create(body: AssistantCloudThreadsCreateBody): Promise<AssistantCloudThreadsCreateResponse>; + update(threadId: string, body: AssistantCloudThreadsUpdateBody): Promise<void>; + delete(threadId: string): Promise<void>; +} + +type AssistantCloudThreadsCreateBody = { + title?: string | undefined; + last_message_at: Date; + metadata?: unknown | undefined; + external_id?: string | undefined; +}; + +type AssistantCloudThreadsCreateResponse = { + thread_id: string; +}; + +type AssistantCloudThreadsListQuery = { + is_archived?: boolean; + limit?: number; + after?: string; +}; + +type AssistantCloudThreadsListResponse = { + threads: CloudThread[]; +}; + +type AssistantCloudThreadsUpdateBody = { + title?: string | undefined; + last_message_at?: Date | undefined; + metadata?: unknown | undefined; + is_archived?: boolean | undefined; +}; + +type AssistantRuntime = { + readonly threads: ThreadListRuntime; + readonly thread: ThreadRuntime; + registerModelContextProvider(provider: ModelContextProvider): Unsubscribe; +}; + +type AssistantStream = ReadableStream<AssistantStreamChunk>; + +declare const AssistantStream: { + toResponse(stream: AssistantStream, transformer: AssistantStreamEncoder): Response; + fromResponse(response: Response, transformer: ReadableWritablePair<AssistantStreamChunk, Uint8Array<ArrayBuffer>>): ReadableStream<AssistantStreamChunk>; + toByteStream(stream: AssistantStream, transformer: ReadableWritablePair<Uint8Array<ArrayBuffer>, AssistantStreamChunk>): ReadableStream<Uint8Array<ArrayBuffer>>; + fromByteStream(readable: ReadableStream<Uint8Array<ArrayBuffer>>, transformer: ReadableWritablePair<AssistantStreamChunk, Uint8Array<ArrayBuffer>>): ReadableStream<AssistantStreamChunk>; +}; + +type AssistantStreamChunk = { + readonly path: readonly number[]; +} & ({ + readonly type: "part-start"; + readonly part: PartInit; +} | { + readonly type: "part-finish"; +} | { + readonly type: "tool-call-args-text-finish"; +} | { + readonly type: "text-delta"; + readonly textDelta: string; +} | { + readonly type: "annotations"; + readonly annotations: ReadonlyJSONValue[]; +} | { + readonly type: "data"; + readonly data: ReadonlyJSONValue[]; +} | { + readonly type: "step-start"; + readonly messageId: string; +} | { + readonly type: "step-finish"; + readonly finishReason: "content-filter" | "error" | "length" | "other" | "stop" | "tool-calls" | "unknown"; + readonly usage: { + readonly inputTokens: number; + readonly outputTokens: number; + }; + readonly isContinued: boolean; +} | { + readonly type: "message-finish"; + readonly finishReason: "content-filter" | "error" | "length" | "other" | "stop" | "tool-calls" | "unknown"; + readonly usage: { + readonly inputTokens: number; + readonly outputTokens: number; + }; +} | { + readonly type: "result"; + readonly artifact?: ReadonlyJSONValue; + readonly result: ReadonlyJSONValue; + readonly isError: boolean; + readonly modelContent?: readonly ToolModelContentPart[]; + readonly messages?: ReadonlyJSONValue; +} | { + readonly type: "error"; + readonly error: string; +} | { + readonly type: "update-state"; + readonly operations: ObjectStreamOperation[]; +}); + +type AssistantStreamEncoder = ReadableWritablePair<Uint8Array<ArrayBuffer>, AssistantStreamChunk> & { + headers?: Headers; +}; + +type AsyncIterableStream<T> = AsyncIterable<T> & ReadableStream<T>; + +type Attachment = PendingAttachment | CompleteAttachment; + +type AttachmentAdapter = { + accept: string; + add(state: { + file: File; + }): Promise<PendingAttachment> | AsyncGenerator<PendingAttachment, void>; + remove(attachment: Attachment): Promise<void>; + send(attachment: PendingAttachment): Promise<CompleteAttachment>; +}; + +type AttachmentAddErrorEvent = { + readonly reason: AttachmentAddErrorReason; + readonly message: string; + readonly attachmentId?: string; + readonly error?: Error; +}; + +type AttachmentAddErrorReason = "adapter-error" | "no-adapter" | "not-accepted"; + +type AttachmentRuntime<TSource extends AttachmentRuntimeSource = AttachmentRuntimeSource> = { + readonly path: AttachmentRuntimePath & { + attachmentSource: TSource; + }; + readonly source: TSource; + getState(): AttachmentState & { + source: TSource; + }; + remove(): Promise<void>; + subscribe(callback: () => void): Unsubscribe; +}; + +type AttachmentRuntimePath = ((MessageRuntimePath & { + readonly attachmentSource: "edit-composer" | "message"; +}) | (ThreadRuntimePath & { + readonly attachmentSource: "thread-composer"; +})) & { + readonly attachmentSelector: { + readonly type: "index"; + readonly index: number; + } | { + readonly type: "index"; + readonly index: number; + } | { + readonly type: "index"; + readonly index: number; + }; +}; + +type AttachmentRuntimeSource = AttachmentState["source"]; + +type AttachmentState = ThreadComposerAttachmentState | EditComposerAttachmentState | MessageAttachmentState; + +type BackendTool<TArgs extends Record<string, unknown> = Record<string, unknown>, TResult = unknown> = ToolBase<TArgs, TResult> & { + type: "backend"; + description?: undefined; + parameters?: undefined; + disabled?: undefined; + execute?: undefined; + toModelOutput?: undefined; + experimental_onSchemaValidationError?: undefined; + providerOptions?: undefined; +}; + +type BaseAttachment = { + id: string; + type: "image" | "document" | "file" | (string & {}); + name: string; + contentType?: string | undefined; + file?: File; + content?: ThreadUserMessagePart[]; +}; + +type BaseComposerState = { + readonly canCancel: boolean; + readonly canSend: boolean; + readonly isEditing: boolean; + readonly isEmpty: boolean; + readonly text: string; + readonly role: MessageRole; + readonly attachments: readonly Attachment[]; + readonly runConfig: RunConfig; + readonly attachmentAccept: string; + readonly dictation: DictationState | undefined; + readonly quote: QuoteInfo | undefined; + readonly queue: readonly QueueItemState[]; +}; + +type BaseThreadMessage = { + readonly status?: ThreadAssistantMessage["status"]; + readonly metadata: { + readonly unstable_state?: ReadonlyJSONValue; + readonly unstable_annotations?: readonly ReadonlyJSONValue[]; + readonly unstable_data?: readonly ReadonlyJSONValue[]; + readonly steps?: readonly ThreadStep[]; + readonly submittedFeedback?: { + readonly type: "negative" | "positive"; + }; + readonly timing?: MessageTiming; + readonly isOptimistic?: boolean; + readonly custom: Record<string, unknown>; + }; + readonly attachments?: ThreadUserMessage["attachments"]; +}; + +type ChatModelRunOptions = { + readonly messages: readonly ThreadMessage[]; + readonly runConfig: RunConfig; + readonly abortSignal: AbortSignal; + readonly context: ModelContext; + readonly unstable_assistantMessageId?: string | undefined; + readonly unstable_threadId?: string | undefined; + readonly unstable_parentId?: string | null | undefined; + unstable_getMessage(): ThreadMessage; +}; + +type ChatModelRunResult = { + readonly content?: readonly ThreadAssistantMessagePart[] | undefined; + readonly status?: MessageStatus | undefined; + readonly metadata?: { + readonly unstable_state?: ReadonlyJSONValue; + readonly unstable_annotations?: readonly ReadonlyJSONValue[] | undefined; + readonly unstable_data?: readonly ReadonlyJSONValue[] | undefined; + readonly steps?: readonly ThreadStep[] | undefined; + readonly timing?: MessageTiming | undefined; + readonly custom?: Record<string, unknown> | undefined; + }; +}; + +type CloudMessage = { + id: string; + parent_id: string | null; + height: number; + created_at: Date; + updated_at: Date; + format: "aui/v0" | string; + content: ReadonlyJSONObject; +}; + +type CloudThread = { + title: string; + last_message_at: Date; + metadata: unknown; + external_id: string | null; + id: string; + project_id: string; + created_at: Date; + updated_at: Date; + workspace_id: string; + is_archived: boolean; +}; + +type CompleteAttachment = BaseAttachment & { + status: CompleteAttachmentStatus; + content: ThreadUserMessagePart[]; +}; + +type CompleteAttachmentStatus = { + type: "complete"; +}; + +type ComposerRuntime = { + readonly path: ComposerRuntimePath; + readonly type: "edit" | "thread"; + getState(): ComposerState; + addAttachment(fileOrAttachment: File | CreateAttachment): Promise<void>; + setText(text: string): void; + setRole(role: MessageRole): void; + setRunConfig(runConfig: RunConfig): void; + reset(): Promise<void>; + clearAttachments(): Promise<void>; + send(options?: SendOptions): void; + cancel(): void; + steerQueueItem(queueItemId: string): void; + removeQueueItem(queueItemId: string): void; + subscribe(callback: () => void): Unsubscribe; + getAttachmentByIndex(idx: number): AttachmentRuntime; + startDictation(): void; + stopDictation(): void; + setQuote(quote: QuoteInfo | undefined): void; + unstable_on<E extends ComposerRuntimeEventType>(event: E, callback: ComposerRuntimeEventCallback<E>): Unsubscribe; +}; + +type ComposerRuntimeEventCallback<E extends ComposerRuntimeEventType> = (payload: ComposerRuntimeEventPayload[E]) => void; + +type ComposerRuntimeEventPayload = { + send: Record<string, never>; + attachmentAdd: Record<string, never>; + attachmentAddError: AttachmentAddErrorEvent; +}; + +type ComposerRuntimeEventType = keyof ComposerRuntimeEventPayload; + +type ComposerRuntimePath = (ThreadRuntimePath & { + readonly composerSource: "thread"; +}) | (MessageRuntimePath & { + readonly composerSource: "edit"; +}); + +type ComposerState = ThreadComposerState | EditComposerState; + +type CreateAppendMessage = string | { + parentId?: string | null | undefined; + sourceId?: string | null | undefined; + role?: AppendMessage["role"] | undefined; + content: AppendMessage["content"]; + attachments?: AppendMessage["attachments"] | undefined; + metadata?: AppendMessage["metadata"] | undefined; + createdAt?: Date | undefined; + runConfig?: AppendMessage["runConfig"] | undefined; + startRun?: boolean | undefined; +}; + +type CreateAttachment = { + id?: string; + type?: "image" | "document" | "file" | (string & {}); + name: string; + contentType?: string; + content: ThreadUserMessagePart[]; +}; + +type CreateResumeRunConfig = CreateStartRunConfig & { + stream?: (options: ChatModelRunOptions) => AsyncGenerator<ChatModelRunResult, void, unknown>; +}; + +type CreateStartRunConfig = { + parentId: string | null; + sourceId?: string | null | undefined; + runConfig?: RunConfig | undefined; +}; + +type CustomToCreateMessageFunction = <UI_MESSAGE extends UIMessage$1 = UIMessage$1>(message: AppendMessage) => CreateUIMessage<UI_MESSAGE>; + +type DataMessagePart<T = any> = { + readonly type: "data"; + readonly name: string; + readonly data: T; +}; + +type DataPrefixedPart = { + readonly type: `data-${string}`; + readonly data: any; +}; + +type DeepPartial<T> = T extends readonly any[] ? readonly DeepPartial<T[number]>[] : T extends { + [key: string]: any; +} ? { + readonly [K in keyof T]?: DeepPartial<T[K]>; +} : T; + +declare namespace DictationAdapter { + type Status = { + type: "running" | "starting"; + } | { + type: "ended"; + reason: "cancelled" | "error" | "stopped"; + }; + type Result = { + transcript: string; + isFinal?: boolean; + }; + type Session = { + status: Status; + stop: () => Promise<void>; + cancel: () => void; + onSpeechStart: (callback: () => void) => Unsubscribe; + onSpeechEnd: (callback: (result: Result) => void) => Unsubscribe; + onSpeech: (callback: (result: Result) => void) => Unsubscribe; + }; +} + +type DictationAdapter = { + listen: () => DictationAdapter.Session; + disableInputDuringDictation?: boolean; +}; + +type DictationState = { + readonly status: DictationAdapter.Status; + readonly transcript?: string; + readonly inputDisabled?: boolean; +}; + +type EditComposerAttachmentState = Attachment & { + readonly source: "edit-composer"; +}; + +type EditComposerRuntime = Omit<ComposerRuntime, "getAttachmentByIndex" | "getState"> & { + readonly path: ComposerRuntimePath & { + composerSource: "edit"; + }; + readonly type: "edit"; + getState(): EditComposerState; + beginEdit(): void; + getAttachmentByIndex(idx: number): AttachmentRuntime & { + source: "edit-composer"; + }; +}; + +type EditComposerState = BaseComposerState & { + readonly type: "edit"; + readonly parentId: string | null; + readonly sourceId: string | null; +}; + +type ExportedMessageRepository = { + headId?: string | null; + messages: Array<{ + message: ThreadMessage; + parentId: string | null; + runConfig?: RunConfig; + }>; +}; + +declare const ExportedMessageRepository: { + fromArray: (messages: readonly ThreadMessageLike[]) => ExportedMessageRepository; + fromBranchableArray: (items: readonly { + message: ThreadMessageLike; + parentId: string | null; + }[], options?: { + headId?: string | null; + }) => ExportedMessageRepository; +}; + +type ExportedMessageRepositoryItem = { + message: ThreadMessage; + parentId: string | null; + runConfig?: RunConfig; +}; + +type ExternalStoreAdapter<T = ThreadMessage> = ExternalStoreAdapterBase<T> & (T extends ThreadMessage ? object : ExternalStoreMessageConverterAdapter<T>); + +type ExternalStoreAdapterBase<T> = { + isDisabled?: boolean | undefined; + isSendDisabled?: boolean | undefined; + isRunning?: boolean | undefined; + isLoading?: boolean | undefined; + messages?: readonly T[]; + messageRepository?: ExportedMessageRepository; + suggestions?: readonly ThreadSuggestion[] | undefined; + state?: ReadonlyJSONValue | undefined; + extras?: unknown; + setMessages?: ((messages: readonly T[]) => void) | undefined; + unstable_onBranchChange?: ((event: ExternalStoreBranchChange) => void) | undefined; + onImport?: ((messages: readonly ThreadMessage[]) => void) | undefined; + onExportExternalState?: (() => any) | undefined; + onLoadExternalState?: ((state: any) => void) | undefined; + onNew: (message: AppendMessage) => Promise<void>; + queue?: ExternalThreadQueueAdapter | undefined; + onEdit?: ((message: AppendMessage) => Promise<void>) | undefined; + onDelete?: ((messageId: string) => Promise<void> | void) | undefined; + onReload?: ((parentId: string | null, config: StartRunConfig) => Promise<void>) | undefined; + onResume?: ((config: ResumeRunConfig) => Promise<void>) | undefined; + onCancel?: (() => Promise<void>) | undefined; + onAddToolResult?: ((options: AddToolResultOptions) => Promise<void> | void) | undefined; + onResumeToolCall?: ((options: { + toolCallId: string; + payload: unknown; + }) => void) | undefined; + onRespondToToolApproval?: ((options: RespondToToolApprovalOptions) => Promise<void> | void) | undefined; + convertMessage?: ExternalStoreMessageConverter<T> | undefined; + adapters?: { + attachments?: AttachmentAdapter | undefined; + speech?: SpeechSynthesisAdapter | undefined; + dictation?: DictationAdapter | undefined; + voice?: RealtimeVoiceAdapter | undefined; + feedback?: FeedbackAdapter | undefined; + threadList?: ExternalStoreThreadListAdapter | undefined; + } | undefined; + unstable_capabilities?: { + copy?: boolean | undefined; + } | undefined; + unstable_enableToolInvocations?: boolean | undefined; + setToolStatuses?: ((statuses: Record<string, ToolExecutionStatus>) => void) | undefined; +}; + +type ExternalStoreBranchChange = { + headId: string | null; + visibleMessageIds: readonly string[]; +}; + +type ExternalStoreMessageConverter<T> = (message: T, idx: number) => ThreadMessageLike; + +type ExternalStoreMessageConverterAdapter<T> = { + convertMessage: ExternalStoreMessageConverter<T>; +}; + +type ExternalStoreSharedOptions = Pick<ExternalStoreAdapter, "isDisabled" | "isSendDisabled" | "suggestions" | "unstable_capabilities">; + +type ExternalStoreThreadData<TState extends "archived" | "regular"> = { + status: TState; + id: string; + remoteId?: string | undefined; + externalId?: string | undefined; + title?: string | undefined; + custom?: Record<string, unknown> | undefined; +}; + +type ExternalStoreThreadListAdapter = { + threadId?: string | undefined; + isLoading?: boolean | undefined; + threads?: readonly ExternalStoreThreadData<"regular">[] | undefined; + archivedThreads?: readonly ExternalStoreThreadData<"archived">[] | undefined; + onSwitchToNewThread?: (() => Promise<void> | void) | undefined; + onSwitchToThread?: ((threadId: string) => Promise<void> | void) | undefined; + onRename?: (threadId: string, newTitle: string) => (Promise<void> | void) | undefined; + onUpdateCustom?: ((threadId: string, custom: Record<string, unknown> | undefined) => Promise<void> | void) | undefined; + onArchive?: ((threadId: string) => Promise<void> | void) | undefined; + onUnarchive?: ((threadId: string) => Promise<void> | void) | undefined; + onDelete?: ((threadId: string) => Promise<void> | void) | undefined; +}; + +type ExternalThreadQueueAdapter = { + items: readonly QueueItemState[]; + enqueue: (message: AppendMessage, options: { + steer: boolean; + }) => void; + steer: (queueItemId: string) => void; + remove: (queueItemId: string) => void; + clear: (reason: "cancel-run" | "edit" | "reload") => void; +}; + +type FeedbackAdapter = { + submit: (feedback: FeedbackAdapterFeedback) => void; +}; + +type FeedbackAdapterFeedback = { + message: ThreadMessage; + type: "negative" | "positive"; +}; + +type FileMessagePart = { + readonly type: "file"; + readonly filename?: string; + readonly data: string; + readonly mimeType: string; + readonly parentId?: string; +}; + +type FrontendTool<TArgs extends Record<string, unknown> = Record<string, unknown>, TResult = unknown> = ToolBase<TArgs, TResult> & { + type: "frontend"; + description?: string | undefined; + parameters: StandardSchemaV1<TArgs> | JSONSchema7; + disabled?: boolean; + execute?: ToolExecuteFunction<TArgs, TResult>; + toModelOutput?: ToolModelOutputFunction<TArgs, TResult>; + experimental_onSchemaValidationError?: OnSchemaValidationErrorFunction<TResult>; + providerOptions?: ProviderOptions; +}; + +type FrontendTools = Record<string, ToolJSONSchema>; + +type GeneratePresignedUploadUrlRequestBody = { + filename: string; +}; + +type GeneratePresignedUploadUrlResponse = { + success: boolean; + signedUrl: string; + expiresAt: string; + publicUrl: string; +}; + +interface GenerativeToolsOptions { + toolkit: Toolkit; + frontendTools?: FrontendTools; +} + +type GenerativeUIMessagePart = { + readonly type: "generative-ui"; + readonly spec: GenerativeUISpec; + readonly id?: string; + readonly parentId?: string; +}; + +type GenerativeUINode = string | { + readonly component: string; + readonly props?: Record<string, unknown>; + readonly children?: readonly GenerativeUINode[]; + readonly key?: string; +}; + +type GenerativeUISpec = { + readonly root: GenerativeUINode | readonly GenerativeUINode[]; +}; + +type GenericThreadHistoryAdapter<TMessage> = { + load(): Promise<MessageFormatRepository<TMessage>>; + append(item: MessageFormatItem<TMessage>): Promise<void>; + update?(item: MessageFormatItem<TMessage>, localMessageId: string): Promise<void>; + delete?(items: MessageFormatItem<TMessage>[]): Promise<void>; + reportTelemetry?(items: MessageFormatItem<TMessage>[], options?: { + durationMs?: number; + stepTimestamps?: { + start_ms: number; + end_ms: number; + }[]; + }): void; +}; + +type HumanTool<TArgs extends Record<string, unknown> = Record<string, unknown>, TResult = unknown> = ToolBase<TArgs, TResult> & { + type: "human"; + description?: string | undefined; + parameters: StandardSchemaV1<TArgs> | JSONSchema7; + disabled?: boolean; + display?: "standalone"; + execute?: undefined; + toModelOutput?: undefined; + experimental_onSchemaValidationError?: undefined; + providerOptions?: ProviderOptions; +}; + +type ImageMessagePart = { + readonly type: "image"; + readonly image: string; + readonly filename?: string; +}; + +type InitializableThreadListItem = Pick<ThreadListItemRuntime, "initialize">; + +interface JSONSchema7 { + $id?: string | undefined; + $ref?: string | undefined; + $schema?: JSONSchema7Version | undefined; + $comment?: string | undefined; + $defs?: { + [key: string]: JSONSchema7Definition; + } | undefined; + type?: JSONSchema7TypeName | JSONSchema7TypeName[] | undefined; + enum?: JSONSchema7Type[] | undefined; + const?: JSONSchema7Type | undefined; + multipleOf?: number | undefined; + maximum?: number | undefined; + exclusiveMaximum?: number | undefined; + minimum?: number | undefined; + exclusiveMinimum?: number | undefined; + maxLength?: number | undefined; + minLength?: number | undefined; + pattern?: string | undefined; + items?: JSONSchema7Definition | JSONSchema7Definition[] | undefined; + additionalItems?: JSONSchema7Definition | undefined; + maxItems?: number | undefined; + minItems?: number | undefined; + uniqueItems?: boolean | undefined; + contains?: JSONSchema7Definition | undefined; + maxProperties?: number | undefined; + minProperties?: number | undefined; + required?: string[] | undefined; + properties?: { + [key: string]: JSONSchema7Definition; + } | undefined; + patternProperties?: { + [key: string]: JSONSchema7Definition; + } | undefined; + additionalProperties?: JSONSchema7Definition | undefined; + dependencies?: { + [key: string]: JSONSchema7Definition | string[]; + } | undefined; + propertyNames?: JSONSchema7Definition | undefined; + if?: JSONSchema7Definition | undefined; + then?: JSONSchema7Definition | undefined; + else?: JSONSchema7Definition | undefined; + allOf?: JSONSchema7Definition[] | undefined; + anyOf?: JSONSchema7Definition[] | undefined; + oneOf?: JSONSchema7Definition[] | undefined; + not?: JSONSchema7Definition | undefined; + format?: string | undefined; + contentMediaType?: string | undefined; + contentEncoding?: string | undefined; + definitions?: { + [key: string]: JSONSchema7Definition; + } | undefined; + title?: string | undefined; + description?: string | undefined; + default?: JSONSchema7Type | undefined; + readOnly?: boolean | undefined; + writeOnly?: boolean | undefined; + examples?: JSONSchema7Type | undefined; +} + +interface JSONSchema7Array extends Array<JSONSchema7Type> { +} + +type JSONSchema7Definition = JSONSchema7 | boolean; + +interface JSONSchema7Object { + [key: string]: JSONSchema7Type; +} + +type JSONSchema7Type = string | number | boolean | JSONSchema7Object | JSONSchema7Array | null; + +type JSONSchema7TypeName = "array" | "boolean" | "integer" | "null" | "number" | "object" | "string"; + +type JSONSchema7Version = string; + +type JoinStrategy = "concat-content" | "none"; + +type LanguageModelConfig = { + apiKey?: string; + baseUrl?: string; + modelName?: string; + reasoningEffort?: string; +}; + +type LanguageModelV1CallSettings = { + maxTokens?: number; + temperature?: number; + topP?: number; + presencePenalty?: number; + frequencyPenalty?: number; + seed?: number; + headers?: Record<string, string | undefined>; +}; + +type MakeRequestOptions = { + method?: "POST" | "PUT" | "DELETE" | undefined; + headers?: Record<string, string> | undefined; + query?: Record<string, string | number | boolean> | undefined; + body?: object | undefined; +}; + +type McpAppMetadata = { + readonly resourceUri: string; + readonly mimeType?: string; + readonly visibility?: readonly ("app" | "model")[]; +}; + +type McpServerConfig = { + type: "http" | "sse"; + url: string; + headers?: Record<string, string>; + redirect?: "error" | "follow"; + connectionTimeout?: number | undefined; +} | { + type: "stdio"; + command: string; + args?: readonly string[]; + env?: Record<string, string>; + cwd?: string; + connectionTimeout?: number | undefined; +}; + +type McpTool = ToolBase<Record<string, unknown>, unknown> & { + type: "mcp"; + server: McpServerConfig; + description?: undefined; + parameters?: undefined; + disabled?: boolean; + execute?: undefined; + toModelOutput?: undefined; + experimental_onSchemaValidationError?: undefined; + providerOptions?: undefined; +}; + +type MessageAttachmentState = CompleteAttachment & { + readonly source: "message"; +}; + +type MessageCommonProps = { + readonly id: string; + readonly createdAt: Date; +}; + +interface MessageFormatAdapter<TMessage, TStorageFormat extends Record<string, unknown>> { + format: string; + encode(item: MessageFormatItem<TMessage>): TStorageFormat; + decode(stored: MessageStorageEntry<TStorageFormat>): MessageFormatItem<TMessage>; + getId(message: TMessage): string; +} + +interface MessageFormatItem<TMessage> { + parentId: string | null; + message: TMessage; +} + +interface MessageFormatRepository<TMessage> { + headId?: string | null; + messages: MessageFormatItem<TMessage>[]; +} + +type MessagePartRuntime = { + addToolResult(result: any | ToolResponse<any>): void; + resumeToolCall(payload: unknown): void; + respondToToolApproval(response: ToolApprovalResponse): void; + readonly path: MessagePartRuntimePath; + getState(): MessagePartState; + subscribe(callback: () => void): Unsubscribe; +}; + +type MessagePartRuntimePath = MessageRuntimePath & { + readonly messagePartSelector: { + readonly type: "index"; + readonly index: number; + } | { + readonly type: "toolCallId"; + readonly toolCallId: string; + }; +}; + +type MessagePartState = (ThreadUserMessagePart | ThreadAssistantMessagePart) & { + readonly status: MessagePartStatus | ToolCallMessagePartStatus; +}; + +type MessagePartStatus = { + readonly type: "running"; +} | { + readonly type: "complete"; +} | { + readonly type: "incomplete"; + readonly reason: "cancelled" | "content-filter" | "error" | "length" | "other"; + readonly error?: unknown; +}; + +type MessageRole = ThreadMessage["role"]; + +type MessageRuntime = { + readonly path: MessageRuntimePath; + readonly composer: EditComposerRuntime; + getState(): MessageState; + delete(): void | Promise<void>; + reload(config?: ReloadConfig): void; + speak(): void; + stopSpeaking(): void; + submitFeedback(_param0: { + type: "positive" | "negative"; + }): void; + switchToBranch(_param1: { + position?: "previous" | "next" | undefined; + branchId?: string | undefined; + }): void; + unstable_getCopyText(): string; + subscribe(callback: () => void): Unsubscribe; + getMessagePartByIndex(idx: number): MessagePartRuntime; + getMessagePartByToolCallId(toolCallId: string): MessagePartRuntime; + getAttachmentByIndex(idx: number): AttachmentRuntime & { + source: "message"; + }; +}; + +type MessageRuntimePath = ThreadRuntimePath & { + readonly messageSelector: { + readonly type: "messageId"; + readonly messageId: string; + } | { + readonly type: "index"; + readonly index: number; + }; +}; + +type MessageState = ThreadMessage & { + readonly parentId: string | null; + readonly index: number; + readonly isLast: boolean; + readonly branchNumber: number; + readonly branchCount: number; + readonly speech: SpeechState | undefined; +}; + +type MessageStatus = { + readonly type: "running"; +} | { + readonly type: "requires-action"; + readonly reason: "interrupt" | "tool-calls"; +} | { + readonly type: "complete"; + readonly reason: "stop" | "unknown"; +} | { + readonly type: "incomplete"; + readonly reason: "cancelled" | "content-filter" | "error" | "length" | "other" | "tool-calls"; + readonly error?: ReadonlyJSONValue; +}; + +interface MessageStorageEntry<TPayload> { + id: string; + parent_id: string | null; + format: string; + content: TPayload; +} + +type MessageTiming = { + readonly streamStartTime: number; + readonly firstTokenTime?: number; + readonly totalStreamTime?: number; + readonly tokenCount?: number; + readonly tokensPerSecond?: number; + readonly totalChunks: number; + readonly toolCallCount: number; +}; + +type ModelContext = { + priority?: number | undefined; + system?: string | undefined; + tools?: Record<string, Tool<any, any>> | undefined; + callSettings?: LanguageModelV1CallSettings | undefined; + config?: LanguageModelConfig | undefined; + unstable_composerMetadata?: Record<string, unknown> | undefined; +}; + +type ModelContextProvider = { + getModelContext: () => ModelContext; + subscribe?: (callback: () => void) => Unsubscribe; +}; + +type ObjectKey<T> = keyof T & (string | number); + +type ObjectStreamOperation = { + readonly type: "set"; + readonly path: readonly string[]; + readonly value: ReadonlyJSONValue; +} | { + readonly type: "append-text"; + readonly path: readonly string[]; + readonly value: string; +}; + +type OnSchemaValidationErrorFunction<TResult> = ToolExecuteFunction<unknown, TResult>; + +type PartInit = { + readonly type: "reasoning" | "text"; + readonly parentId?: string; +} | { + readonly type: "tool-call"; + readonly toolCallId: string; + readonly toolName: string; + readonly parentId?: string; +} | { + readonly type: "source"; + readonly sourceType: "url"; + readonly id: string; + readonly url: string; + readonly title?: string; + readonly parentId?: string; +} | { + readonly type: "file"; + readonly data: string; + readonly mimeType: string; + readonly parentId?: string; +} | { + readonly type: "data"; + readonly name: string; + readonly data: ReadonlyJSONValue; + readonly parentId?: string; +}; + +type PartProviderMetadata = { + readonly [providerName: string]: ReadonlyJSONObject; +}; + +type PdfToImagesRequestBody = { + file_blob?: string | undefined; + file_url?: string | undefined; +}; + +type PdfToImagesResponse = { + success: boolean; + urls: string[]; + message: string; +}; + +type PendingAttachment = BaseAttachment & { + status: PendingAttachmentStatus; + file: File; +}; + +type PendingAttachmentStatus = { + type: "running"; + reason: "uploading"; + progress: number; +} | { + type: "requires-action"; + reason: "composer-send"; +} | { + type: "incomplete"; + reason: "error" | "upload-paused"; + message?: string; +}; + +type ProviderOptions = Record<string, Record<string, unknown>>; + +type ProviderTool<TArgs extends Record<string, unknown> = Record<string, unknown>, TResult = unknown> = ToolBase<TArgs, TResult> & { + type: "provider"; + providerId: `${string}.${string}`; + parameters?: StandardSchemaV1<TArgs> | JSONSchema7 | undefined; + args: Record<string, unknown>; + supportsDeferredResults?: boolean; + description?: undefined; + disabled?: boolean; + execute?: undefined; + toModelOutput?: undefined; + experimental_onSchemaValidationError?: undefined; + providerOptions?: ProviderOptions; +}; + +type QueueItemState = { + readonly id: string; + readonly prompt: string; +}; + +type QuoteInfo = { + readonly text: string; + readonly messageId: string; +}; + +declare const RESUMABLE_STREAM_ID_HEADER = "x-resumable-stream-id"; + +type ReadonlyJSONArray = readonly ReadonlyJSONValue[]; + +type ReadonlyJSONObject = { + readonly [key: string]: ReadonlyJSONValue; +}; + +type ReadonlyJSONValue = null | string | number | boolean | ReadonlyJSONObject | ReadonlyJSONArray; + +declare namespace RealtimeVoiceAdapter { + type Status = { + type: "running" | "starting"; + } | { + type: "ended"; + reason: "cancelled" | "error" | "finished"; + error?: unknown; + }; + type Mode = "listening" | "speaking"; + type TranscriptItem = { + role: "assistant" | "user"; + text: string; + isFinal?: boolean; + }; + type Session = { + status: Status; + isMuted: boolean; + disconnect: () => void; + mute: () => void; + unmute: () => void; + onStatusChange: (callback: (status: Status) => void) => Unsubscribe; + onTranscript: (callback: (transcript: TranscriptItem) => void) => Unsubscribe; + onModeChange: (callback: (mode: Mode) => void) => Unsubscribe; + onVolumeChange: (callback: (volume: number) => void) => Unsubscribe; + }; +} + +type RealtimeVoiceAdapter = { + connect: (options: { + abortSignal?: AbortSignal; + }) => RealtimeVoiceAdapter.Session; +}; + +type ReasoningMessagePart = { + readonly type: "reasoning"; + readonly text: string; + readonly providerMetadata?: PartProviderMetadata; + readonly parentId?: string; +}; + +type ReloadConfig = { + runConfig?: RunConfig; +}; + +type ReportToolCall = { + tool_name: string; + tool_call_id: string; + tool_args?: string; + tool_result?: string; + tool_source?: "backend" | "frontend" | "mcp"; + start_ms?: number; + end_ms?: number; + sampling_calls?: SamplingCallData[]; +}; + +type RespondToToolApprovalOptions = { + approvalId: string; + approved: boolean; + optionId?: string; + reason?: string; +}; + +type ResumableClientStorage = { + getStreamId(): string | null; + setStreamId(id: string): void; + clear(): void; +}; + +type ResumeRunConfig = StartRunConfig & { + stream?: (options: ChatModelRunOptions) => AsyncGenerator<ChatModelRunResult, void, unknown>; +}; + +type RunConfig = { + readonly custom?: Record<string, unknown>; +}; + +type RuntimeCapabilities = { + readonly switchToBranch: boolean; + readonly switchBranchDuringRun: boolean; + readonly edit: boolean; + readonly reload: boolean; + readonly delete: boolean; + readonly cancel: boolean; + readonly unstable_copy: boolean; + readonly speech: boolean; + readonly dictation: boolean; + readonly voice: boolean; + readonly attachments: boolean; + readonly feedback: boolean; + readonly queue: boolean; +}; + +type SamplingCallData = { + model_id?: string; + input_tokens?: number; + output_tokens?: number; + reasoning_tokens?: number; + cached_input_tokens?: number; + duration_ms?: number; +}; + +type SendOptions = { + startRun?: boolean; + steer?: boolean; +}; + +type SourceMessagePart = { + readonly type: "source"; + readonly sourceType: "url"; + readonly id: string; + readonly url: string; + readonly title?: string; + readonly providerMetadata?: SourceProviderMetadata; + readonly parentId?: string; +} | { + readonly type: "source"; + readonly sourceType: "document"; + readonly id: string; + readonly url?: undefined; + readonly title: string; + readonly mediaType: string; + readonly filename?: string; + readonly providerMetadata?: SourceProviderMetadata; + readonly parentId?: string; +}; + +type SourceProviderMetadata = PartProviderMetadata; + +interface SpeechRecognitionConstructor { + new (): SpeechRecognitionInstance; +} + +interface SpeechRecognitionInstance extends EventTarget { + lang: string; + continuous: boolean; + interimResults: boolean; + start(): void; + stop(): void; + abort(): void; +} + +type SpeechState = { + readonly messageId: string; + readonly status: SpeechSynthesisAdapter.Status; +}; + +declare namespace SpeechSynthesisAdapter { + type Status = { + type: "running" | "starting"; + } | { + type: "ended"; + reason: "cancelled" | "error" | "finished"; + error?: unknown; + }; + type Utterance = { + status: Status; + cancel: () => void; + subscribe: (callback: () => void) => Unsubscribe; + }; +} + +type SpeechSynthesisAdapter = { + speak: (text: string) => SpeechSynthesisAdapter.Utterance; +}; + +type StartRunConfig = { + parentId: string | null; + sourceId: string | null; + runConfig: RunConfig; +}; + +declare const TOOL_RESPONSE_SYMBOL: unique symbol; + +type TextMessagePart = { + readonly type: "text"; + readonly text: string; + readonly providerMetadata?: PartProviderMetadata; + readonly parentId?: string; +}; + +type ThreadAssistantMessage = MessageCommonProps & { + readonly role: "assistant"; + readonly content: readonly ThreadAssistantMessagePart[]; + readonly status: MessageStatus; + readonly metadata: { + readonly unstable_state: ReadonlyJSONValue; + readonly unstable_annotations: readonly ReadonlyJSONValue[]; + readonly unstable_data: readonly ReadonlyJSONValue[]; + readonly steps: readonly ThreadStep[]; + readonly submittedFeedback?: { + readonly type: "negative" | "positive"; + }; + readonly timing?: MessageTiming; + readonly isOptimistic?: boolean; + readonly custom: Record<string, unknown>; + }; +}; + +type ThreadAssistantMessagePart = TextMessagePart | ReasoningMessagePart | ToolCallMessagePart | SourceMessagePart | FileMessagePart | ImageMessagePart | DataMessagePart | GenerativeUIMessagePart; + +type ThreadComposerAttachmentState = Attachment & { + readonly source: "thread-composer"; +}; + +type ThreadComposerRuntime = Omit<ComposerRuntime, "getAttachmentByIndex" | "getState"> & { + readonly path: ComposerRuntimePath & { + composerSource: "thread"; + }; + readonly type: "thread"; + getState(): ThreadComposerState; + getAttachmentByIndex(idx: number): AttachmentRuntime & { + source: "thread-composer"; + }; +}; + +type ThreadComposerState = BaseComposerState & { + readonly type: "thread"; +}; + +type ThreadHistoryAdapter = { + load(): Promise<ExportedMessageRepository & { + state?: ReadonlyJSONValue; + unstable_resume?: boolean; + }>; + resume?(options: ChatModelRunOptions): AsyncGenerator<ChatModelRunResult, void, unknown>; + append(item: ExportedMessageRepositoryItem): Promise<void>; + delete?(items: ExportedMessageRepositoryItem[]): Promise<void>; + withFormat?<TMessage, TStorageFormat extends Record<string, unknown>>(formatAdapter: MessageFormatAdapter<TMessage, TStorageFormat>): GenericThreadHistoryAdapter<TMessage>; +}; + +type ThreadListItemEventCallback<E extends ThreadListItemEventType> = (payload: ThreadListItemEventPayload[E]) => void; + +type ThreadListItemEventPayload = { + switchedTo: Record<string, never>; + switchedAway: Record<string, never>; +}; + +type ThreadListItemEventType = keyof ThreadListItemEventPayload; + +type ThreadListItemRuntime = { + readonly path: ThreadListItemRuntimePath; + getState(): ThreadListItemState; + initialize(): Promise<{ + remoteId: string; + externalId: string | undefined; + }>; + generateTitle(): Promise<void>; + switchTo(options?: { + unarchive?: boolean; + }): Promise<void>; + rename(newTitle: string): Promise<void>; + updateCustom(custom: Record<string, unknown> | undefined): Promise<void>; + archive(): Promise<void>; + unarchive(): Promise<void>; + delete(): Promise<void>; + detach(): void; + subscribe(callback: () => void): Unsubscribe; + unstable_on<E extends ThreadListItemEventType>(event: E, callback: ThreadListItemEventCallback<E>): Unsubscribe; + __internal_getRuntime(): ThreadListItemRuntime; +}; + +type ThreadListItemRuntimePath = { + readonly ref: string; + readonly threadSelector: { + readonly type: "main"; + } | { + readonly type: "index"; + readonly index: number; + } | { + readonly type: "archiveIndex"; + readonly index: number; + } | { + readonly type: "threadId"; + readonly threadId: string; + }; +}; + +type ThreadListItemState = { + readonly isMain: boolean; + readonly id: string; + readonly remoteId: string | undefined; + readonly externalId: string | undefined; + readonly status: ThreadListItemStatus; + readonly title?: string | undefined; + readonly lastMessageAt?: Date | undefined; + readonly custom?: Record<string, unknown> | undefined; +}; + +type ThreadListItemStatus = "archived" | "deleted" | "new" | "regular"; + +type ThreadListRuntime = { + getState(): ThreadListState; + subscribe(callback: () => void): Unsubscribe; + readonly main: ThreadRuntime; + getById(threadId: string): ThreadRuntime; + readonly mainItem: ThreadListItemRuntime; + getItemById(threadId: string): ThreadListItemRuntime; + getItemByIndex(idx: number): ThreadListItemRuntime; + getArchivedItemByIndex(idx: number): ThreadListItemRuntime; + switchToThread(threadId: string, options?: { + unarchive?: boolean; + }): Promise<void>; + switchToNewThread(): Promise<void>; + getLoadThreadsPromise(): Promise<void>; + reload(): Promise<void>; + loadMore(): Promise<void>; +}; + +type ThreadListState = { + readonly mainThreadId: string; + readonly newThreadId: string | undefined; + readonly threadIds: readonly string[]; + readonly archivedThreadIds: readonly string[]; + readonly isLoading: boolean; + readonly isLoadingMore: boolean; + readonly hasMore: boolean; + readonly threadItems: Readonly<Record<string, Omit<ThreadListItemState, "isMain" | "threadId">>>; +}; + +type ThreadMessage = BaseThreadMessage & (ThreadSystemMessage | ThreadUserMessage | ThreadAssistantMessage); + +type ThreadMessageLike = { + readonly role: "assistant" | "system" | "user"; + readonly content: string | readonly (TextMessagePart | ReasoningMessagePart | SourceMessagePart | ImageMessagePart | FileMessagePart | DataMessagePart | GenerativeUIMessagePart | Unstable_AudioMessagePart | DataPrefixedPart | { + readonly type: "tool-call"; + readonly toolCallId?: string; + readonly toolName: string; + readonly args?: ReadonlyJSONObject; + readonly argsText?: string; + readonly artifact?: any; + readonly result?: any | undefined; + readonly isError?: boolean | undefined; + readonly parentId?: string | undefined; + readonly messages?: readonly ThreadMessage[] | undefined; + readonly interrupt?: { + type: "human"; + payload: unknown; + }; + readonly timing?: ToolCallTiming; + readonly providerMetadata?: PartProviderMetadata; + readonly approval?: { + readonly id: string; + readonly approved?: boolean; + readonly reason?: string; + readonly isAutomatic?: boolean; + readonly options?: readonly ToolApprovalOption[]; + readonly optionId?: string; + readonly resolution?: "cancelled" | "expired"; + }; + })[]; + readonly id?: string | undefined; + readonly createdAt?: Date | undefined; + readonly status?: MessageStatus | undefined; + readonly attachments?: readonly (Omit<CompleteAttachment, "content"> & { + readonly content: readonly (ThreadUserMessagePart | DataPrefixedPart)[]; + })[] | undefined; + readonly metadata?: { + readonly unstable_state?: ReadonlyJSONValue; + readonly unstable_annotations?: readonly ReadonlyJSONValue[] | undefined; + readonly unstable_data?: readonly ReadonlyJSONValue[] | undefined; + readonly steps?: readonly ThreadStep[] | undefined; + readonly timing?: MessageTiming | undefined; + readonly submittedFeedback?: { + readonly type: "negative" | "positive"; + }; + readonly isOptimistic?: boolean | undefined; + readonly custom?: Record<string, unknown> | undefined; + } | undefined; +}; + +type ThreadRuntime = { + readonly path: ThreadRuntimePath; + readonly composer: ThreadComposerRuntime; + getState(): ThreadState; + append(message: CreateAppendMessage): void; + deleteMessage(messageId: string): void | Promise<void>; + startRun(config: CreateStartRunConfig): void; + resumeRun(config: CreateResumeRunConfig): void; + exportExternalState(): any; + importExternalState(state: any): void; + subscribe(callback: () => void): Unsubscribe; + cancelRun(): void; + getModelContext(): ModelContext; + export(): ExportedMessageRepository; + import(repository: ExportedMessageRepository): void; + reset(initialMessages?: readonly ThreadMessageLike[]): void; + getMessageByIndex(idx: number): MessageRuntime; + getMessageById(messageId: string): MessageRuntime; + stopSpeaking(): void; + connectVoice(): void; + disconnectVoice(): void; + getVoiceVolume(): number; + subscribeVoiceVolume(callback: () => void): Unsubscribe; + muteVoice(): void; + unmuteVoice(): void; + unstable_on<E extends ThreadRuntimeEventType>(event: E, callback: ThreadRuntimeEventCallback<E>): Unsubscribe; +}; + +type ThreadRuntimeEventCallback<E extends ThreadRuntimeEventType> = (payload: ThreadRuntimeEventPayload[E]) => void; + +type ThreadRuntimeEventPayload = { + runStart: Record<string, never>; + runEnd: Record<string, never>; + initialize: Record<string, never>; + modelContextUpdate: Record<string, never>; +}; + +type ThreadRuntimeEventType = keyof ThreadRuntimeEventPayload; + +type ThreadRuntimePath = { + readonly ref: string; + readonly threadSelector: { + readonly type: "main"; + } | { + readonly type: "threadId"; + readonly threadId: string; + }; +}; + +type ThreadState = { + readonly threadId: string; + readonly metadata: ThreadListItemState; + readonly isDisabled: boolean; + readonly isLoading: boolean; + readonly isRunning: boolean; + readonly capabilities: RuntimeCapabilities; + readonly messages: readonly ThreadMessage[]; + readonly state: ReadonlyJSONValue; + readonly suggestions: readonly ThreadSuggestion[]; + readonly extras: unknown; + readonly speech: SpeechState | undefined; + readonly voice: VoiceSessionState | undefined; +}; + +type ThreadStep = { + readonly messageId?: string; + readonly usage?: { + readonly inputTokens: number; + readonly outputTokens: number; + } | undefined; +}; + +type ThreadSuggestion = { + prompt: string; +}; + +type ThreadSystemMessage = MessageCommonProps & { + readonly role: "system"; + readonly content: readonly [ + TextMessagePart + ]; + readonly metadata: { + readonly unstable_state?: undefined; + readonly unstable_annotations?: undefined; + readonly unstable_data?: undefined; + readonly steps?: undefined; + readonly submittedFeedback?: undefined; + readonly timing?: undefined; + readonly custom: Record<string, unknown>; + }; +}; + +type ThreadTokenUsage = { + totalTokens?: number; + inputTokens?: number; + outputTokens?: number; + reasoningTokens?: number; + cachedInputTokens?: number; +}; + +type ThreadUserMessage = MessageCommonProps & { + readonly role: "user"; + readonly content: readonly ThreadUserMessagePart[]; + readonly attachments: readonly CompleteAttachment[]; + readonly metadata: { + readonly unstable_state?: undefined; + readonly unstable_annotations?: undefined; + readonly unstable_data?: undefined; + readonly steps?: undefined; + readonly submittedFeedback?: undefined; + readonly timing?: undefined; + readonly custom: Record<string, unknown>; + }; +}; + +type ThreadUserMessagePart = TextMessagePart | ImageMessagePart | FileMessagePart | DataMessagePart | Unstable_AudioMessagePart; + +interface TokenUsageExtractableMessage { + role?: string; + metadata?: unknown; +} + +type Tool<TArgs extends Record<string, unknown> = Record<string, unknown>, TResult = unknown> = FrontendTool<TArgs, TResult> | BackendTool<TArgs, TResult> | HumanTool<TArgs, TResult> | ProviderTool<TArgs, TResult> | McpTool | ToolWithoutType<TArgs, TResult>; + +type ToolApprovalOption = { + readonly id: string; + readonly kind: ToolApprovalOptionKind | (string & {}); + readonly label?: string; + readonly description?: string; + readonly grants?: readonly string[]; + readonly confirm?: boolean | { + title?: string; + description?: string; + }; +}; + +type ToolApprovalOptionKind = "allow-always" | "allow-once" | "reject-always" | "reject-once"; + +type ToolApprovalResponse = { + readonly approved: boolean; + readonly reason?: string; +} | { + readonly optionId: string; + readonly reason?: string; +} | { + readonly approved: boolean; + readonly optionId: string; + readonly reason?: string; +}; + +type ToolBase<TArgs extends Record<string, unknown> = Record<string, unknown>, TResult = unknown> = { + streamCall?: ToolStreamCallFunction<TArgs, TResult>; + display?: ToolDisplay; +}; + +interface ToolCallArgsReader<TArgs extends Record<string, unknown>> { + get<PathT extends TypePath<TArgs>>(...fieldPath: PathT): Promise<TypeAtPath<TArgs, PathT>>; + streamValues<PathT extends TypePath<TArgs>>(...fieldPath: PathT): AsyncIterableStream<DeepPartial<TypeAtPath<TArgs, PathT>>>; + streamText<PathT extends TypePath<TArgs>>(...fieldPath: PathT): TypeAtPath<TArgs, PathT> extends string & (infer U) ? AsyncIterableStream<U> : never; + forEach<PathT extends TypePath<TArgs>>(...fieldPath: PathT): NonNullable<TypeAtPath<TArgs, PathT>> extends Array<infer U> ? AsyncIterableStream<U> : never; +} + +type ToolCallCompleteText<TArgs extends Record<string, unknown>, TResult> = ReactNode | ((options: { + args: TArgs; + result: TResult | undefined; +}) => ReactNode); + +type ToolCallMessagePart<TArgs = ReadonlyJSONObject, TResult = unknown> = { + readonly type: "tool-call"; + readonly toolCallId: string; + readonly toolName: string; + readonly args: TArgs; + readonly result?: TResult | undefined; + readonly isError?: boolean | undefined; + readonly argsText: string; + readonly artifact?: unknown; + readonly timing?: ToolCallTiming; + readonly mcp?: ToolCallMessagePartMcpMetadata; + readonly providerMetadata?: PartProviderMetadata; + readonly modelContent?: readonly ToolModelContentPart[] | undefined; + readonly interrupt?: { + type: "human"; + payload: unknown; + }; + readonly approval?: { + readonly id: string; + readonly approved?: boolean; + readonly reason?: string; + readonly isAutomatic?: boolean; + readonly options?: readonly ToolApprovalOption[]; + readonly optionId?: string; + readonly resolution?: "cancelled" | "expired"; + }; + readonly parentId?: string; + readonly messages?: readonly ThreadMessage[]; +}; + +type ToolCallMessagePartComponent<TArgs = any, TResult = any> = ComponentType<ToolCallMessagePartProps<TArgs, TResult>>; + +type ToolCallMessagePartMcpMetadata = { + readonly app?: McpAppMetadata; +}; + +type ToolCallMessagePartProps<TArgs = any, TResult = unknown> = MessagePartState & ToolCallMessagePart<TArgs, TResult> & { + addResult: (result: TResult | ToolResponse<TResult>) => void; + resume: (payload: unknown) => void; + respondToApproval: (response: ToolApprovalResponse) => void; +}; + +type ToolCallMessagePartStatus = { + readonly type: "requires-action"; + readonly reason: "interrupt"; +} | MessagePartStatus; + +interface ToolCallReader<TArgs extends Record<string, unknown> = Record<string, unknown>, TResult = unknown> { + args: ToolCallArgsReader<TArgs>; + response: ToolCallResponseReader<TResult>; + result: { + get: () => Promise<TResult>; + }; +} + +interface ToolCallResponseReader<TResult> { + get: () => Promise<ToolResponse<TResult>>; +} + +type ToolCallRunningText<TArgs extends Record<string, unknown>> = ReactNode | ((options: { + args: TArgs; +}) => ReactNode); + +type ToolCallText<TArgs extends Record<string, unknown>, TResult> = { + running: ToolCallRunningText<TArgs>; + complete?: ToolCallCompleteText<TArgs, TResult> | undefined; +} | { + running?: ToolCallRunningText<TArgs> | undefined; + complete: ToolCallCompleteText<TArgs, TResult>; +}; + +type ToolCallTiming = { + readonly startedAt: number; + readonly completedAt?: number; +}; + +type ToolDefinition<TArgs extends Record<string, unknown> = Record<string, unknown>, TResult = unknown> = WithRender<Tool<TArgs, TResult>, TArgs, TResult>; + +type ToolDisplay = "inline" | "standalone"; + +type ToolExecuteFunction<TArgs, TResult> = (args: TArgs, context: ToolExecutionContext) => TResult | Promise<TResult>; + +type ToolExecutionContext = { + toolCallId: string; + abortSignal: AbortSignal; + human: (payload: unknown) => Promise<unknown>; +}; + +type ToolExecutionStatus = { + type: "executing"; +} | { + type: "interrupt"; + payload: { + type: "human"; + payload: unknown; + }; +}; + +type ToolJSONSchema = { + description?: string; + parameters: JSONSchema7; + providerOptions?: ProviderOptions; +}; + +type ToolModelContentPart = { + readonly type: "text"; + readonly text: string; +} | { + readonly type: "file"; + readonly data: string; + readonly mediaType: string; + readonly filename?: string; +}; + +type ToolModelOutputFunction<TArgs, TResult> = (options: { + toolCallId: string; + input: TArgs; + output: TResult; +}) => readonly ToolModelContentPart[] | Promise<readonly ToolModelContentPart[]>; + +declare class ToolResponse<TResult> { + get [TOOL_RESPONSE_SYMBOL](): boolean; + readonly artifact?: ReadonlyJSONValue; + readonly result: TResult; + readonly isError: boolean; + readonly modelContent?: readonly ToolModelContentPart[]; + readonly messages?: ReadonlyJSONValue; + constructor(options: ToolResponseLike<TResult>); + static [Symbol.hasInstance](obj: unknown): obj is ToolResponse<ReadonlyJSONValue>; + static toResponse(result: any | ToolResponse<any>): ToolResponse<any>; +} + +type ToolResponseLike<TResult> = { + result: TResult; + artifact?: ReadonlyJSONValue | undefined; + isError?: boolean | undefined; + modelContent?: readonly ToolModelContentPart[] | undefined; + messages?: ReadonlyJSONValue | undefined; +}; + +type ToolStreamCallFunction<TArgs extends Record<string, unknown> = Record<string, unknown>, TResult = unknown> = (reader: ToolCallReader<TArgs, TResult>, context: ToolExecutionContext) => void; + +type ToolWithoutType<TArgs extends Record<string, unknown> = Record<string, unknown>, TResult = unknown> = (Omit<FrontendTool<TArgs, TResult>, "type"> | Omit<BackendTool<TArgs, TResult>, "type"> | Omit<HumanTool<TArgs, TResult>, "type"> | Omit<ProviderTool<TArgs, TResult>, "type">) & { + type?: undefined; +}; + +type Toolkit = Record<string, ToolDefinition<any, any>>; + +type TupleIndex<T extends readonly any[]> = Exclude<keyof T, keyof any[]>; + +type TypeAtPath<T, P extends readonly any[]> = P extends [ + infer Head, + ...infer Rest +] ? Head extends keyof T ? TypeAtPath<T[Head], Rest> : never : T; + +type TypePath<T> = [ +] | (0 extends 1 & T ? any[] : T extends object ? T extends readonly any[] ? number extends T["length"] ? { + [K in TupleIndex<T>]: [ + AsNumber<K>, + ...TypePath<T[K]> + ]; +}[TupleIndex<T>] : [ + number, + ...TypePath<T[number]> +] : { + [K in ObjectKey<T>]: [ + K, + ...TypePath<T[K]> + ]; +}[ObjectKey<T>] : [ +]); + +type Unstable_AudioMessagePart = { + readonly type: "audio"; + readonly audio: { + readonly data: string; + readonly format: "mp3" | "wav"; + }; +}; + +type Unstable_InteractableSnapshotEntry = { + id: string; + name: string; + state: unknown; + partial?: boolean | undefined; +}; + +type Unsubscribe = () => void; + +type UseChatRuntimeOptions<UI_MESSAGE extends UIMessage$1 = UIMessage$1> = ChatInit<UI_MESSAGE> & ExternalStoreSharedOptions & { + cloud?: AssistantCloud | undefined; + adapters?: AISDKRuntimeAdapter["adapters"] | undefined; + toCreateMessage?: CustomToCreateMessageFunction; + onResume?: AISDKRuntimeAdapter["onResume"]; + onResumeError?: ((error: unknown) => void) | undefined; + joinStrategy?: AISDKRuntimeAdapter["joinStrategy"]; + onThreadIdChange?: ((threadId: string | undefined) => void) | undefined; +}; + +type VoiceSessionState = { + readonly status: RealtimeVoiceAdapter.Status; + readonly isMuted: boolean; + readonly mode: RealtimeVoiceAdapter.Mode; +}; + +type WithRender<T, TArgs extends Record<string, unknown>, TResult> = T extends { + type: "frontend" | "human"; +} ? T & (T extends { + type: "frontend"; +} ? { + render: ToolCallMessagePartComponent<TArgs, TResult>; +} | { + render?: ToolCallMessagePartComponent<TArgs, TResult>; + renderText: ToolCallText<TArgs, TResult>; +} : { + render: ToolCallMessagePartComponent<TArgs, TResult>; +}) : T & { + render?: ToolCallMessagePartComponent<TArgs, TResult> | undefined; + renderText?: ToolCallText<TArgs, TResult> | undefined; +}; + +declare function createResumableSessionStorage(options?: { + key?: string; +}): ResumableClientStorage; + +declare const frontendTools: (tools: FrontendTools) => ToolSet; + +declare const generativeTools: (options: GenerativeToolsOptions) => ToolSet; + +declare function getThreadMessageTokenUsage(message: TokenUsageExtractableMessage | undefined): ThreadTokenUsage | undefined; + +declare global { + interface Window { + SpeechRecognition?: SpeechRecognitionConstructor; + webkitSpeechRecognition?: SpeechRecognitionConstructor; + } +} + +declare namespace entry_root_exports { + export { AISDKToolkit, AISDKToolkitOptions, AISDKToolkitToolsOptions, AssistantChatResumableOptions, AssistantChatTransport, FrontendTools, GenerativeToolsOptions, RESUMABLE_STREAM_ID_HEADER, ResumableClientStorage, ThreadTokenUsage, TokenUsageExtractableMessage, UseChatRuntimeOptions, createResumableSessionStorage, frontendTools, generativeTools, getThreadMessageTokenUsage, injectQuoteContext, unstable_injectInteractableContext, useAISDKRuntime, useChatRuntime, useThreadTokenUsage }; +} + +declare function injectQuoteContext(messages: UIMessage[]): UIMessage[]; + +declare function unstable_injectInteractableContext(messages: UIMessage[], format?: (item: Unstable_InteractableSnapshotEntry) => string): UIMessage[]; + +declare const useAISDKRuntime: <UI_MESSAGE extends UIMessage$1 = UIMessage$1>(chatHelpers: ReturnType<typeof useChat<UI_MESSAGE>>, adapter?: AISDKRuntimeAdapter) => AssistantRuntime; + +declare const useChatRuntime: <UI_MESSAGE extends UIMessage$1 = UIMessage$1>(_param2?: UseChatRuntimeOptions<UI_MESSAGE>) => AssistantRuntime; + +declare function useThreadTokenUsage(): ThreadTokenUsage | undefined; + +export { entry_root_exports as entry_root }; diff --git a/api-surface/assistant-ui__react-data-stream.ts b/api-surface/assistant-ui__react-data-stream.ts new file mode 100644 index 0000000..f927a0f --- /dev/null +++ b/api-surface/assistant-ui__react-data-stream.ts @@ -0,0 +1,1800 @@ +import { LanguageModelV2Message } from "@ai-sdk/provider"; + +import { StandardSchemaV1 } from "@standard-schema/spec"; + +type AppendMessage = Omit<ThreadMessage, "id"> & { + parentId: string | null; + sourceId: string | null; + runConfig: RunConfig | undefined; + startRun?: boolean | undefined; + steer?: boolean | undefined; +}; + +type AsNumber<K> = K extends `${infer N extends number}` ? N | K : never; + +declare class AssistantCloud { + readonly threads: AssistantCloudThreads; + readonly auth: { + tokens: AssistantCloudAuthTokens; + }; + readonly runs: AssistantCloudRuns; + readonly files: AssistantCloudFiles; + readonly telemetry: AssistantCloudTelemetryConfig; + constructor(config: AssistantCloudConfig); +} + +declare class AssistantCloudAPI { + _auth: AssistantCloudAuthStrategy; + _baseUrl: string; + constructor(config: AssistantCloudConfig); + initializeAuth(): Promise<boolean>; + makeRawRequest(endpoint: string, options?: MakeRequestOptions): Promise<Response>; + makeRequest(endpoint: string, options?: MakeRequestOptions): Promise<any>; +} + +type AssistantCloudAuthStrategy = { + readonly strategy: "anon" | "api-key" | "jwt"; + getAuthHeaders(): Promise<Record<string, string> | false>; + readAuthHeaders(headers: Headers): void; +}; + +declare class AssistantCloudAuthTokens { + private cloud; + constructor(cloud: AssistantCloudAPI); + create(): Promise<AssistantCloudAuthTokensCreateResponse>; +} + +type AssistantCloudAuthTokensCreateResponse = { + token: string; +}; + +type AssistantCloudConfig = ({ + baseUrl: string; + authToken: () => Promise<string | null>; +} | { + baseUrl?: string; + apiKey: string; + userId: string; + workspaceId: string; +} | { + baseUrl: string; + anonymous: true; +}) & { + telemetry?: boolean | AssistantCloudTelemetryConfig; +}; + +declare class AssistantCloudFiles { + private cloud; + constructor(cloud: AssistantCloudAPI); + pdfToImages(body: PdfToImagesRequestBody): Promise<PdfToImagesResponse>; + generatePresignedUploadUrl(body: GeneratePresignedUploadUrlRequestBody): Promise<GeneratePresignedUploadUrlResponse>; +} + +type AssistantCloudMessageCreateResponse = { + message_id: string; +}; + +type AssistantCloudRunReport = { + thread_id: string; + status: "completed" | "error" | "incomplete"; + total_steps?: number; + tool_calls?: ReportToolCall[]; + steps?: { + input_tokens?: number; + output_tokens?: number; + reasoning_tokens?: number; + cached_input_tokens?: number; + tool_calls?: ReportToolCall[]; + start_ms?: number; + end_ms?: number; + }[]; + input_tokens?: number; + output_tokens?: number; + reasoning_tokens?: number; + cached_input_tokens?: number; + model_id?: string; + provider_type?: string; + duration_ms?: number; + output_text?: string; + metadata?: Record<string, unknown>; +}; + +declare class AssistantCloudRuns { + private cloud; + constructor(cloud: AssistantCloudAPI); + __internal_getAssistantOptions(assistantId: string): { + api: string; + headers: () => Promise<{ + Accept: string; + }>; + body: { + assistant_id: string; + response_format: string; + thread_id: string; + }; + }; + stream(body: AssistantCloudRunsStreamBody): Promise<AssistantStream>; + report(body: AssistantCloudRunReport): Promise<{ + run_id: string; + }>; +} + +type AssistantCloudRunsStreamBody = { + thread_id: string; + assistant_id: "system/thread_title"; + messages: readonly unknown[]; +}; + +type AssistantCloudTelemetryConfig = { + enabled?: boolean; + beforeReport?: (report: AssistantCloudRunReport) => AssistantCloudRunReport | null; +}; + +type AssistantCloudThreadMessageCreateBody = { + parent_id: string | null; + format: "aui/v0" | string; + content: ReadonlyJSONObject; +}; + +type AssistantCloudThreadMessageListQuery = { + format?: string; +}; + +type AssistantCloudThreadMessageListResponse = { + messages: CloudMessage[]; +}; + +type AssistantCloudThreadMessageUpdateBody = { + content: ReadonlyJSONObject; +}; + +declare class AssistantCloudThreadMessages { + private cloud; + constructor(cloud: AssistantCloudAPI); + list(threadId: string, query?: AssistantCloudThreadMessageListQuery): Promise<AssistantCloudThreadMessageListResponse>; + create(threadId: string, body: AssistantCloudThreadMessageCreateBody): Promise<AssistantCloudMessageCreateResponse>; + update(threadId: string, messageId: string, body: AssistantCloudThreadMessageUpdateBody): Promise<void>; +} + +declare class AssistantCloudThreads { + private cloud; + readonly messages: AssistantCloudThreadMessages; + constructor(cloud: AssistantCloudAPI); + list(query?: AssistantCloudThreadsListQuery): Promise<AssistantCloudThreadsListResponse>; + get(threadId: string): Promise<CloudThread>; + create(body: AssistantCloudThreadsCreateBody): Promise<AssistantCloudThreadsCreateResponse>; + update(threadId: string, body: AssistantCloudThreadsUpdateBody): Promise<void>; + delete(threadId: string): Promise<void>; +} + +type AssistantCloudThreadsCreateBody = { + title?: string | undefined; + last_message_at: Date; + metadata?: unknown | undefined; + external_id?: string | undefined; +}; + +type AssistantCloudThreadsCreateResponse = { + thread_id: string; +}; + +type AssistantCloudThreadsListQuery = { + is_archived?: boolean; + limit?: number; + after?: string; +}; + +type AssistantCloudThreadsListResponse = { + threads: CloudThread[]; +}; + +type AssistantCloudThreadsUpdateBody = { + title?: string | undefined; + last_message_at?: Date | undefined; + metadata?: unknown | undefined; + is_archived?: boolean | undefined; +}; + +type AssistantRuntime = { + readonly threads: ThreadListRuntime; + readonly thread: ThreadRuntime; + registerModelContextProvider(provider: ModelContextProvider): Unsubscribe; +}; + +type AssistantStream = ReadableStream<AssistantStreamChunk>; + +declare const AssistantStream: { + toResponse(stream: AssistantStream, transformer: AssistantStreamEncoder): Response; + fromResponse(response: Response, transformer: ReadableWritablePair<AssistantStreamChunk, Uint8Array<ArrayBuffer>>): ReadableStream<AssistantStreamChunk>; + toByteStream(stream: AssistantStream, transformer: ReadableWritablePair<Uint8Array<ArrayBuffer>, AssistantStreamChunk>): ReadableStream<Uint8Array<ArrayBuffer>>; + fromByteStream(readable: ReadableStream<Uint8Array<ArrayBuffer>>, transformer: ReadableWritablePair<AssistantStreamChunk, Uint8Array<ArrayBuffer>>): ReadableStream<AssistantStreamChunk>; +}; + +type AssistantStreamChunk = { + readonly path: readonly number[]; +} & ({ + readonly type: "part-start"; + readonly part: PartInit; +} | { + readonly type: "part-finish"; +} | { + readonly type: "tool-call-args-text-finish"; +} | { + readonly type: "text-delta"; + readonly textDelta: string; +} | { + readonly type: "annotations"; + readonly annotations: ReadonlyJSONValue[]; +} | { + readonly type: "data"; + readonly data: ReadonlyJSONValue[]; +} | { + readonly type: "step-start"; + readonly messageId: string; +} | { + readonly type: "step-finish"; + readonly finishReason: "content-filter" | "error" | "length" | "other" | "stop" | "tool-calls" | "unknown"; + readonly usage: { + readonly inputTokens: number; + readonly outputTokens: number; + }; + readonly isContinued: boolean; +} | { + readonly type: "message-finish"; + readonly finishReason: "content-filter" | "error" | "length" | "other" | "stop" | "tool-calls" | "unknown"; + readonly usage: { + readonly inputTokens: number; + readonly outputTokens: number; + }; +} | { + readonly type: "result"; + readonly artifact?: ReadonlyJSONValue; + readonly result: ReadonlyJSONValue; + readonly isError: boolean; + readonly modelContent?: readonly ToolModelContentPart[]; + readonly messages?: ReadonlyJSONValue; +} | { + readonly type: "error"; + readonly error: string; +} | { + readonly type: "update-state"; + readonly operations: ObjectStreamOperation[]; +}); + +type AssistantStreamEncoder = ReadableWritablePair<Uint8Array<ArrayBuffer>, AssistantStreamChunk> & { + headers?: Headers; +}; + +type AsyncIterableStream<T> = AsyncIterable<T> & ReadableStream<T>; + +type Attachment = PendingAttachment | CompleteAttachment; + +type AttachmentAdapter = { + accept: string; + add(state: { + file: File; + }): Promise<PendingAttachment> | AsyncGenerator<PendingAttachment, void>; + remove(attachment: Attachment): Promise<void>; + send(attachment: PendingAttachment): Promise<CompleteAttachment>; +}; + +type AttachmentAddErrorEvent = { + readonly reason: AttachmentAddErrorReason; + readonly message: string; + readonly attachmentId?: string; + readonly error?: Error; +}; + +type AttachmentAddErrorReason = "adapter-error" | "no-adapter" | "not-accepted"; + +type AttachmentRuntime<TSource extends AttachmentRuntimeSource = AttachmentRuntimeSource> = { + readonly path: AttachmentRuntimePath & { + attachmentSource: TSource; + }; + readonly source: TSource; + getState(): AttachmentState & { + source: TSource; + }; + remove(): Promise<void>; + subscribe(callback: () => void): Unsubscribe; +}; + +type AttachmentRuntimePath = ((MessageRuntimePath & { + readonly attachmentSource: "edit-composer" | "message"; +}) | (ThreadRuntimePath & { + readonly attachmentSource: "thread-composer"; +})) & { + readonly attachmentSelector: { + readonly type: "index"; + readonly index: number; + } | { + readonly type: "index"; + readonly index: number; + } | { + readonly type: "index"; + readonly index: number; + }; +}; + +type AttachmentRuntimeSource = AttachmentState["source"]; + +type AttachmentState = ThreadComposerAttachmentState | EditComposerAttachmentState | MessageAttachmentState; + +type BackendTool<TArgs extends Record<string, unknown> = Record<string, unknown>, TResult = unknown> = ToolBase<TArgs, TResult> & { + type: "backend"; + description?: undefined; + parameters?: undefined; + disabled?: undefined; + execute?: undefined; + toModelOutput?: undefined; + experimental_onSchemaValidationError?: undefined; + providerOptions?: undefined; +}; + +type BaseAttachment = { + id: string; + type: "image" | "document" | "file" | (string & {}); + name: string; + contentType?: string | undefined; + file?: File; + content?: ThreadUserMessagePart[]; +}; + +type BaseComposerState = { + readonly canCancel: boolean; + readonly canSend: boolean; + readonly isEditing: boolean; + readonly isEmpty: boolean; + readonly text: string; + readonly role: MessageRole; + readonly attachments: readonly Attachment[]; + readonly runConfig: RunConfig; + readonly attachmentAccept: string; + readonly dictation: DictationState | undefined; + readonly quote: QuoteInfo | undefined; + readonly queue: readonly QueueItemState[]; +}; + +type BaseThreadMessage = { + readonly status?: ThreadAssistantMessage["status"]; + readonly metadata: { + readonly unstable_state?: ReadonlyJSONValue; + readonly unstable_annotations?: readonly ReadonlyJSONValue[]; + readonly unstable_data?: readonly ReadonlyJSONValue[]; + readonly steps?: readonly ThreadStep[]; + readonly submittedFeedback?: { + readonly type: "negative" | "positive"; + }; + readonly timing?: MessageTiming; + readonly isOptimistic?: boolean; + readonly custom: Record<string, unknown>; + }; + readonly attachments?: ThreadUserMessage["attachments"]; +}; + +type ChatModelAdapter = { + run(options: ChatModelRunOptions): Promise<ChatModelRunResult> | AsyncGenerator<ChatModelRunResult, void>; +}; + +type ChatModelRunOptions = { + readonly messages: readonly ThreadMessage[]; + readonly runConfig: RunConfig; + readonly abortSignal: AbortSignal; + readonly context: ModelContext; + readonly unstable_assistantMessageId?: string | undefined; + readonly unstable_threadId?: string | undefined; + readonly unstable_parentId?: string | null | undefined; + unstable_getMessage(): ThreadMessage; +}; + +type ChatModelRunResult = { + readonly content?: readonly ThreadAssistantMessagePart[] | undefined; + readonly status?: MessageStatus | undefined; + readonly metadata?: { + readonly unstable_state?: ReadonlyJSONValue; + readonly unstable_annotations?: readonly ReadonlyJSONValue[] | undefined; + readonly unstable_data?: readonly ReadonlyJSONValue[] | undefined; + readonly steps?: readonly ThreadStep[] | undefined; + readonly timing?: MessageTiming | undefined; + readonly custom?: Record<string, unknown> | undefined; + }; +}; + +type CloudMessage = { + id: string; + parent_id: string | null; + height: number; + created_at: Date; + updated_at: Date; + format: "aui/v0" | string; + content: ReadonlyJSONObject; +}; + +type CloudThread = { + title: string; + last_message_at: Date; + metadata: unknown; + external_id: string | null; + id: string; + project_id: string; + created_at: Date; + updated_at: Date; + workspace_id: string; + is_archived: boolean; +}; + +type CompleteAttachment = BaseAttachment & { + status: CompleteAttachmentStatus; + content: ThreadUserMessagePart[]; +}; + +type CompleteAttachmentStatus = { + type: "complete"; +}; + +type ComposerRuntime = { + readonly path: ComposerRuntimePath; + readonly type: "edit" | "thread"; + getState(): ComposerState; + addAttachment(fileOrAttachment: File | CreateAttachment): Promise<void>; + setText(text: string): void; + setRole(role: MessageRole): void; + setRunConfig(runConfig: RunConfig): void; + reset(): Promise<void>; + clearAttachments(): Promise<void>; + send(options?: SendOptions): void; + cancel(): void; + steerQueueItem(queueItemId: string): void; + removeQueueItem(queueItemId: string): void; + subscribe(callback: () => void): Unsubscribe; + getAttachmentByIndex(idx: number): AttachmentRuntime; + startDictation(): void; + stopDictation(): void; + setQuote(quote: QuoteInfo | undefined): void; + unstable_on<E extends ComposerRuntimeEventType>(event: E, callback: ComposerRuntimeEventCallback<E>): Unsubscribe; +}; + +type ComposerRuntimeEventCallback<E extends ComposerRuntimeEventType> = (payload: ComposerRuntimeEventPayload[E]) => void; + +type ComposerRuntimeEventPayload = { + send: Record<string, never>; + attachmentAdd: Record<string, never>; + attachmentAddError: AttachmentAddErrorEvent; +}; + +type ComposerRuntimeEventType = keyof ComposerRuntimeEventPayload; + +type ComposerRuntimePath = (ThreadRuntimePath & { + readonly composerSource: "thread"; +}) | (MessageRuntimePath & { + readonly composerSource: "edit"; +}); + +type ComposerState = ThreadComposerState | EditComposerState; + +type CreateAppendMessage = string | { + parentId?: string | null | undefined; + sourceId?: string | null | undefined; + role?: AppendMessage["role"] | undefined; + content: AppendMessage["content"]; + attachments?: AppendMessage["attachments"] | undefined; + metadata?: AppendMessage["metadata"] | undefined; + createdAt?: Date | undefined; + runConfig?: AppendMessage["runConfig"] | undefined; + startRun?: boolean | undefined; +}; + +type CreateAttachment = { + id?: string; + type?: "image" | "document" | "file" | (string & {}); + name: string; + contentType?: string; + content: ThreadUserMessagePart[]; +}; + +type CreateResumeRunConfig = CreateStartRunConfig & { + stream?: (options: ChatModelRunOptions) => AsyncGenerator<ChatModelRunResult, void, unknown>; +}; + +type CreateStartRunConfig = { + parentId: string | null; + sourceId?: string | null | undefined; + runConfig?: RunConfig | undefined; +}; + +type DataMessagePart<T = any> = { + readonly type: "data"; + readonly name: string; + readonly data: T; +}; + +type DataPrefixedPart = { + readonly type: `data-${string}`; + readonly data: any; +}; + +type DataStreamProtocol = "data-stream" | "ui-message-stream"; + +type DeepPartial<T> = T extends readonly any[] ? readonly DeepPartial<T[number]>[] : T extends { + [key: string]: any; +} ? { + readonly [K in keyof T]?: DeepPartial<T[K]>; +} : T; + +declare namespace DictationAdapter { + type Status = { + type: "running" | "starting"; + } | { + type: "ended"; + reason: "cancelled" | "error" | "stopped"; + }; + type Result = { + transcript: string; + isFinal?: boolean; + }; + type Session = { + status: Status; + stop: () => Promise<void>; + cancel: () => void; + onSpeechStart: (callback: () => void) => Unsubscribe; + onSpeechEnd: (callback: (result: Result) => void) => Unsubscribe; + onSpeech: (callback: (result: Result) => void) => Unsubscribe; + }; +} + +type DictationAdapter = { + listen: () => DictationAdapter.Session; + disableInputDuringDictation?: boolean; +}; + +type DictationState = { + readonly status: DictationAdapter.Status; + readonly transcript?: string; + readonly inputDisabled?: boolean; +}; + +type EditComposerAttachmentState = Attachment & { + readonly source: "edit-composer"; +}; + +type EditComposerRuntime = Omit<ComposerRuntime, "getAttachmentByIndex" | "getState"> & { + readonly path: ComposerRuntimePath & { + composerSource: "edit"; + }; + readonly type: "edit"; + getState(): EditComposerState; + beginEdit(): void; + getAttachmentByIndex(idx: number): AttachmentRuntime & { + source: "edit-composer"; + }; +}; + +type EditComposerState = BaseComposerState & { + readonly type: "edit"; + readonly parentId: string | null; + readonly sourceId: string | null; +}; + +type ExportedMessageRepository = { + headId?: string | null; + messages: Array<{ + message: ThreadMessage; + parentId: string | null; + runConfig?: RunConfig; + }>; +}; + +declare const ExportedMessageRepository: { + fromArray: (messages: readonly ThreadMessageLike[]) => ExportedMessageRepository; + fromBranchableArray: (items: readonly { + message: ThreadMessageLike; + parentId: string | null; + }[], options?: { + headId?: string | null; + }) => ExportedMessageRepository; +}; + +type ExportedMessageRepositoryItem = { + message: ThreadMessage; + parentId: string | null; + runConfig?: RunConfig; +}; + +type FeedbackAdapter = { + submit: (feedback: FeedbackAdapterFeedback) => void; +}; + +type FeedbackAdapterFeedback = { + message: ThreadMessage; + type: "negative" | "positive"; +}; + +type FileMessagePart = { + readonly type: "file"; + readonly filename?: string; + readonly data: string; + readonly mimeType: string; + readonly parentId?: string; +}; + +type FrontendTool<TArgs extends Record<string, unknown> = Record<string, unknown>, TResult = unknown> = ToolBase<TArgs, TResult> & { + type: "frontend"; + description?: string | undefined; + parameters: StandardSchemaV1<TArgs> | JSONSchema7; + disabled?: boolean; + execute?: ToolExecuteFunction<TArgs, TResult>; + toModelOutput?: ToolModelOutputFunction<TArgs, TResult>; + experimental_onSchemaValidationError?: OnSchemaValidationErrorFunction<TResult>; + providerOptions?: ProviderOptions; +}; + +type GeneratePresignedUploadUrlRequestBody = { + filename: string; +}; + +type GeneratePresignedUploadUrlResponse = { + success: boolean; + signedUrl: string; + expiresAt: string; + publicUrl: string; +}; + +type GenerativeUIMessagePart = { + readonly type: "generative-ui"; + readonly spec: GenerativeUISpec; + readonly id?: string; + readonly parentId?: string; +}; + +type GenerativeUINode = string | { + readonly component: string; + readonly props?: Record<string, unknown>; + readonly children?: readonly GenerativeUINode[]; + readonly key?: string; +}; + +type GenerativeUISpec = { + readonly root: GenerativeUINode | readonly GenerativeUINode[]; +}; + +type GenericThreadHistoryAdapter<TMessage> = { + load(): Promise<MessageFormatRepository<TMessage>>; + append(item: MessageFormatItem<TMessage>): Promise<void>; + update?(item: MessageFormatItem<TMessage>, localMessageId: string): Promise<void>; + delete?(items: MessageFormatItem<TMessage>[]): Promise<void>; + reportTelemetry?(items: MessageFormatItem<TMessage>[], options?: { + durationMs?: number; + stepTimestamps?: { + start_ms: number; + end_ms: number; + }[]; + }): void; +}; + +type HeadersValue = Record<string, string> | Headers; + +type HumanTool<TArgs extends Record<string, unknown> = Record<string, unknown>, TResult = unknown> = ToolBase<TArgs, TResult> & { + type: "human"; + description?: string | undefined; + parameters: StandardSchemaV1<TArgs> | JSONSchema7; + disabled?: boolean; + display?: "standalone"; + execute?: undefined; + toModelOutput?: undefined; + experimental_onSchemaValidationError?: undefined; + providerOptions?: ProviderOptions; +}; + +type ImageMessagePart = { + readonly type: "image"; + readonly image: string; + readonly filename?: string; +}; + +interface JSONSchema7 { + $id?: string | undefined; + $ref?: string | undefined; + $schema?: JSONSchema7Version | undefined; + $comment?: string | undefined; + $defs?: { + [key: string]: JSONSchema7Definition; + } | undefined; + type?: JSONSchema7TypeName | JSONSchema7TypeName[] | undefined; + enum?: JSONSchema7Type[] | undefined; + const?: JSONSchema7Type | undefined; + multipleOf?: number | undefined; + maximum?: number | undefined; + exclusiveMaximum?: number | undefined; + minimum?: number | undefined; + exclusiveMinimum?: number | undefined; + maxLength?: number | undefined; + minLength?: number | undefined; + pattern?: string | undefined; + items?: JSONSchema7Definition | JSONSchema7Definition[] | undefined; + additionalItems?: JSONSchema7Definition | undefined; + maxItems?: number | undefined; + minItems?: number | undefined; + uniqueItems?: boolean | undefined; + contains?: JSONSchema7Definition | undefined; + maxProperties?: number | undefined; + minProperties?: number | undefined; + required?: string[] | undefined; + properties?: { + [key: string]: JSONSchema7Definition; + } | undefined; + patternProperties?: { + [key: string]: JSONSchema7Definition; + } | undefined; + additionalProperties?: JSONSchema7Definition | undefined; + dependencies?: { + [key: string]: JSONSchema7Definition | string[]; + } | undefined; + propertyNames?: JSONSchema7Definition | undefined; + if?: JSONSchema7Definition | undefined; + then?: JSONSchema7Definition | undefined; + else?: JSONSchema7Definition | undefined; + allOf?: JSONSchema7Definition[] | undefined; + anyOf?: JSONSchema7Definition[] | undefined; + oneOf?: JSONSchema7Definition[] | undefined; + not?: JSONSchema7Definition | undefined; + format?: string | undefined; + contentMediaType?: string | undefined; + contentEncoding?: string | undefined; + definitions?: { + [key: string]: JSONSchema7Definition; + } | undefined; + title?: string | undefined; + description?: string | undefined; + default?: JSONSchema7Type | undefined; + readOnly?: boolean | undefined; + writeOnly?: boolean | undefined; + examples?: JSONSchema7Type | undefined; +} + +interface JSONSchema7Array extends Array<JSONSchema7Type> { +} + +type JSONSchema7Definition = JSONSchema7 | boolean; + +interface JSONSchema7Object { + [key: string]: JSONSchema7Type; +} + +type JSONSchema7Type = string | number | boolean | JSONSchema7Object | JSONSchema7Array | null; + +type JSONSchema7TypeName = "array" | "boolean" | "integer" | "null" | "number" | "object" | "string"; + +type JSONSchema7Version = string; + +type LanguageModelConfig = { + apiKey?: string; + baseUrl?: string; + modelName?: string; + reasoningEffort?: string; +}; + +type LanguageModelV1CallSettings = { + maxTokens?: number; + temperature?: number; + topP?: number; + presencePenalty?: number; + frequencyPenalty?: number; + seed?: number; + headers?: Record<string, string | undefined>; +}; + +type LocalRuntimeOptions = Omit<LocalRuntimeOptionsBase, "adapters"> & { + cloud?: AssistantCloud | undefined; + initialMessages?: readonly ThreadMessageLike[] | undefined; + adapters?: Omit<LocalRuntimeOptionsBase["adapters"], "chatModel"> | undefined; +}; + +type LocalRuntimeOptionsBase = { + maxSteps?: number | undefined; + adapters: { + chatModel: ChatModelAdapter; + history?: ThreadHistoryAdapter | undefined; + attachments?: AttachmentAdapter | undefined; + speech?: SpeechSynthesisAdapter | undefined; + dictation?: DictationAdapter | undefined; + voice?: RealtimeVoiceAdapter | undefined; + feedback?: FeedbackAdapter | undefined; + suggestion?: SuggestionAdapter | undefined; + }; + unstable_humanToolNames?: string[] | undefined; + unstable_enableMessageQueue?: boolean | undefined; +}; + +type MakeRequestOptions = { + method?: "POST" | "PUT" | "DELETE" | undefined; + headers?: Record<string, string> | undefined; + query?: Record<string, string | number | boolean> | undefined; + body?: object | undefined; +}; + +type McpAppMetadata = { + readonly resourceUri: string; + readonly mimeType?: string; + readonly visibility?: readonly ("app" | "model")[]; +}; + +type McpServerConfig = { + type: "http" | "sse"; + url: string; + headers?: Record<string, string>; + redirect?: "error" | "follow"; + connectionTimeout?: number | undefined; +} | { + type: "stdio"; + command: string; + args?: readonly string[]; + env?: Record<string, string>; + cwd?: string; + connectionTimeout?: number | undefined; +}; + +type McpTool = ToolBase<Record<string, unknown>, unknown> & { + type: "mcp"; + server: McpServerConfig; + description?: undefined; + parameters?: undefined; + disabled?: boolean; + execute?: undefined; + toModelOutput?: undefined; + experimental_onSchemaValidationError?: undefined; + providerOptions?: undefined; +}; + +type MessageAttachmentState = CompleteAttachment & { + readonly source: "message"; +}; + +type MessageCommonProps = { + readonly id: string; + readonly createdAt: Date; +}; + +interface MessageFormatAdapter<TMessage, TStorageFormat extends Record<string, unknown>> { + format: string; + encode(item: MessageFormatItem<TMessage>): TStorageFormat; + decode(stored: MessageStorageEntry<TStorageFormat>): MessageFormatItem<TMessage>; + getId(message: TMessage): string; +} + +interface MessageFormatItem<TMessage> { + parentId: string | null; + message: TMessage; +} + +interface MessageFormatRepository<TMessage> { + headId?: string | null; + messages: MessageFormatItem<TMessage>[]; +} + +type MessagePartRuntime = { + addToolResult(result: any | ToolResponse<any>): void; + resumeToolCall(payload: unknown): void; + respondToToolApproval(response: ToolApprovalResponse): void; + readonly path: MessagePartRuntimePath; + getState(): MessagePartState; + subscribe(callback: () => void): Unsubscribe; +}; + +type MessagePartRuntimePath = MessageRuntimePath & { + readonly messagePartSelector: { + readonly type: "index"; + readonly index: number; + } | { + readonly type: "toolCallId"; + readonly toolCallId: string; + }; +}; + +type MessagePartState = (ThreadUserMessagePart | ThreadAssistantMessagePart) & { + readonly status: MessagePartStatus | ToolCallMessagePartStatus; +}; + +type MessagePartStatus = { + readonly type: "running"; +} | { + readonly type: "complete"; +} | { + readonly type: "incomplete"; + readonly reason: "cancelled" | "content-filter" | "error" | "length" | "other"; + readonly error?: unknown; +}; + +type MessageRole = ThreadMessage["role"]; + +type MessageRuntime = { + readonly path: MessageRuntimePath; + readonly composer: EditComposerRuntime; + getState(): MessageState; + delete(): void | Promise<void>; + reload(config?: ReloadConfig): void; + speak(): void; + stopSpeaking(): void; + submitFeedback(_param0: { + type: "positive" | "negative"; + }): void; + switchToBranch(_param1: { + position?: "previous" | "next" | undefined; + branchId?: string | undefined; + }): void; + unstable_getCopyText(): string; + subscribe(callback: () => void): Unsubscribe; + getMessagePartByIndex(idx: number): MessagePartRuntime; + getMessagePartByToolCallId(toolCallId: string): MessagePartRuntime; + getAttachmentByIndex(idx: number): AttachmentRuntime & { + source: "message"; + }; +}; + +type MessageRuntimePath = ThreadRuntimePath & { + readonly messageSelector: { + readonly type: "messageId"; + readonly messageId: string; + } | { + readonly type: "index"; + readonly index: number; + }; +}; + +type MessageState = ThreadMessage & { + readonly parentId: string | null; + readonly index: number; + readonly isLast: boolean; + readonly branchNumber: number; + readonly branchCount: number; + readonly speech: SpeechState | undefined; +}; + +type MessageStatus = { + readonly type: "running"; +} | { + readonly type: "requires-action"; + readonly reason: "interrupt" | "tool-calls"; +} | { + readonly type: "complete"; + readonly reason: "stop" | "unknown"; +} | { + readonly type: "incomplete"; + readonly reason: "cancelled" | "content-filter" | "error" | "length" | "other" | "tool-calls"; + readonly error?: ReadonlyJSONValue; +}; + +interface MessageStorageEntry<TPayload> { + id: string; + parent_id: string | null; + format: string; + content: TPayload; +} + +type MessageTiming = { + readonly streamStartTime: number; + readonly firstTokenTime?: number; + readonly totalStreamTime?: number; + readonly tokenCount?: number; + readonly tokensPerSecond?: number; + readonly totalChunks: number; + readonly toolCallCount: number; +}; + +type ModelContext = { + priority?: number | undefined; + system?: string | undefined; + tools?: Record<string, Tool<any, any>> | undefined; + callSettings?: LanguageModelV1CallSettings | undefined; + config?: LanguageModelConfig | undefined; + unstable_composerMetadata?: Record<string, unknown> | undefined; +}; + +type ModelContextProvider = { + getModelContext: () => ModelContext; + subscribe?: (callback: () => void) => Unsubscribe; +}; + +type ObjectKey<T> = keyof T & (string | number); + +type ObjectStreamOperation = { + readonly type: "set"; + readonly path: readonly string[]; + readonly value: ReadonlyJSONValue; +} | { + readonly type: "append-text"; + readonly path: readonly string[]; + readonly value: string; +}; + +type OnSchemaValidationErrorFunction<TResult> = ToolExecuteFunction<unknown, TResult>; + +type PartInit = { + readonly type: "reasoning" | "text"; + readonly parentId?: string; +} | { + readonly type: "tool-call"; + readonly toolCallId: string; + readonly toolName: string; + readonly parentId?: string; +} | { + readonly type: "source"; + readonly sourceType: "url"; + readonly id: string; + readonly url: string; + readonly title?: string; + readonly parentId?: string; +} | { + readonly type: "file"; + readonly data: string; + readonly mimeType: string; + readonly parentId?: string; +} | { + readonly type: "data"; + readonly name: string; + readonly data: ReadonlyJSONValue; + readonly parentId?: string; +}; + +type PartProviderMetadata = { + readonly [providerName: string]: ReadonlyJSONObject; +}; + +type PdfToImagesRequestBody = { + file_blob?: string | undefined; + file_url?: string | undefined; +}; + +type PdfToImagesResponse = { + success: boolean; + urls: string[]; + message: string; +}; + +type PendingAttachment = BaseAttachment & { + status: PendingAttachmentStatus; + file: File; +}; + +type PendingAttachmentStatus = { + type: "running"; + reason: "uploading"; + progress: number; +} | { + type: "requires-action"; + reason: "composer-send"; +} | { + type: "incomplete"; + reason: "error" | "upload-paused"; + message?: string; +}; + +type ProviderOptions = Record<string, Record<string, unknown>>; + +type ProviderTool<TArgs extends Record<string, unknown> = Record<string, unknown>, TResult = unknown> = ToolBase<TArgs, TResult> & { + type: "provider"; + providerId: `${string}.${string}`; + parameters?: StandardSchemaV1<TArgs> | JSONSchema7 | undefined; + args: Record<string, unknown>; + supportsDeferredResults?: boolean; + description?: undefined; + disabled?: boolean; + execute?: undefined; + toModelOutput?: undefined; + experimental_onSchemaValidationError?: undefined; + providerOptions?: ProviderOptions; +}; + +type QueueItemState = { + readonly id: string; + readonly prompt: string; +}; + +type QuoteInfo = { + readonly text: string; + readonly messageId: string; +}; + +type ReadonlyJSONArray = readonly ReadonlyJSONValue[]; + +type ReadonlyJSONObject = { + readonly [key: string]: ReadonlyJSONValue; +}; + +type ReadonlyJSONValue = null | string | number | boolean | ReadonlyJSONObject | ReadonlyJSONArray; + +declare namespace RealtimeVoiceAdapter { + type Status = { + type: "running" | "starting"; + } | { + type: "ended"; + reason: "cancelled" | "error" | "finished"; + error?: unknown; + }; + type Mode = "listening" | "speaking"; + type TranscriptItem = { + role: "assistant" | "user"; + text: string; + isFinal?: boolean; + }; + type Session = { + status: Status; + isMuted: boolean; + disconnect: () => void; + mute: () => void; + unmute: () => void; + onStatusChange: (callback: (status: Status) => void) => Unsubscribe; + onTranscript: (callback: (transcript: TranscriptItem) => void) => Unsubscribe; + onModeChange: (callback: (mode: Mode) => void) => Unsubscribe; + onVolumeChange: (callback: (volume: number) => void) => Unsubscribe; + }; +} + +type RealtimeVoiceAdapter = { + connect: (options: { + abortSignal?: AbortSignal; + }) => RealtimeVoiceAdapter.Session; +}; + +type ReasoningMessagePart = { + readonly type: "reasoning"; + readonly text: string; + readonly providerMetadata?: PartProviderMetadata; + readonly parentId?: string; +}; + +type ReloadConfig = { + runConfig?: RunConfig; +}; + +type ReportToolCall = { + tool_name: string; + tool_call_id: string; + tool_args?: string; + tool_result?: string; + tool_source?: "backend" | "frontend" | "mcp"; + start_ms?: number; + end_ms?: number; + sampling_calls?: SamplingCallData[]; +}; + +type RunConfig = { + readonly custom?: Record<string, unknown>; +}; + +type RuntimeCapabilities = { + readonly switchToBranch: boolean; + readonly switchBranchDuringRun: boolean; + readonly edit: boolean; + readonly reload: boolean; + readonly delete: boolean; + readonly cancel: boolean; + readonly unstable_copy: boolean; + readonly speech: boolean; + readonly dictation: boolean; + readonly voice: boolean; + readonly attachments: boolean; + readonly feedback: boolean; + readonly queue: boolean; +}; + +type SamplingCallData = { + model_id?: string; + input_tokens?: number; + output_tokens?: number; + reasoning_tokens?: number; + cached_input_tokens?: number; + duration_ms?: number; +}; + +type SendOptions = { + startRun?: boolean; + steer?: boolean; +}; + +type SourceMessagePart = { + readonly type: "source"; + readonly sourceType: "url"; + readonly id: string; + readonly url: string; + readonly title?: string; + readonly providerMetadata?: SourceProviderMetadata; + readonly parentId?: string; +} | { + readonly type: "source"; + readonly sourceType: "document"; + readonly id: string; + readonly url?: undefined; + readonly title: string; + readonly mediaType: string; + readonly filename?: string; + readonly providerMetadata?: SourceProviderMetadata; + readonly parentId?: string; +}; + +type SourceProviderMetadata = PartProviderMetadata; + +interface SpeechRecognitionConstructor { + new (): SpeechRecognitionInstance; +} + +interface SpeechRecognitionInstance extends EventTarget { + lang: string; + continuous: boolean; + interimResults: boolean; + start(): void; + stop(): void; + abort(): void; +} + +type SpeechState = { + readonly messageId: string; + readonly status: SpeechSynthesisAdapter.Status; +}; + +declare namespace SpeechSynthesisAdapter { + type Status = { + type: "running" | "starting"; + } | { + type: "ended"; + reason: "cancelled" | "error" | "finished"; + error?: unknown; + }; + type Utterance = { + status: Status; + cancel: () => void; + subscribe: (callback: () => void) => Unsubscribe; + }; +} + +type SpeechSynthesisAdapter = { + speak: (text: string) => SpeechSynthesisAdapter.Utterance; +}; + +type SuggestionAdapter = { + generate: (options: SuggestionAdapterGenerateOptions) => Promise<readonly ThreadSuggestion[]> | AsyncGenerator<readonly ThreadSuggestion[], void>; +}; + +type SuggestionAdapterGenerateOptions = { + messages: readonly ThreadMessage[]; +}; + +declare const TOOL_RESPONSE_SYMBOL: unique symbol; + +type TextMessagePart = { + readonly type: "text"; + readonly text: string; + readonly providerMetadata?: PartProviderMetadata; + readonly parentId?: string; +}; + +type ThreadAssistantMessage = MessageCommonProps & { + readonly role: "assistant"; + readonly content: readonly ThreadAssistantMessagePart[]; + readonly status: MessageStatus; + readonly metadata: { + readonly unstable_state: ReadonlyJSONValue; + readonly unstable_annotations: readonly ReadonlyJSONValue[]; + readonly unstable_data: readonly ReadonlyJSONValue[]; + readonly steps: readonly ThreadStep[]; + readonly submittedFeedback?: { + readonly type: "negative" | "positive"; + }; + readonly timing?: MessageTiming; + readonly isOptimistic?: boolean; + readonly custom: Record<string, unknown>; + }; +}; + +type ThreadAssistantMessagePart = TextMessagePart | ReasoningMessagePart | ToolCallMessagePart | SourceMessagePart | FileMessagePart | ImageMessagePart | DataMessagePart | GenerativeUIMessagePart; + +type ThreadComposerAttachmentState = Attachment & { + readonly source: "thread-composer"; +}; + +type ThreadComposerRuntime = Omit<ComposerRuntime, "getAttachmentByIndex" | "getState"> & { + readonly path: ComposerRuntimePath & { + composerSource: "thread"; + }; + readonly type: "thread"; + getState(): ThreadComposerState; + getAttachmentByIndex(idx: number): AttachmentRuntime & { + source: "thread-composer"; + }; +}; + +type ThreadComposerState = BaseComposerState & { + readonly type: "thread"; +}; + +type ThreadHistoryAdapter = { + load(): Promise<ExportedMessageRepository & { + state?: ReadonlyJSONValue; + unstable_resume?: boolean; + }>; + resume?(options: ChatModelRunOptions): AsyncGenerator<ChatModelRunResult, void, unknown>; + append(item: ExportedMessageRepositoryItem): Promise<void>; + delete?(items: ExportedMessageRepositoryItem[]): Promise<void>; + withFormat?<TMessage, TStorageFormat extends Record<string, unknown>>(formatAdapter: MessageFormatAdapter<TMessage, TStorageFormat>): GenericThreadHistoryAdapter<TMessage>; +}; + +type ThreadListItemEventCallback<E extends ThreadListItemEventType> = (payload: ThreadListItemEventPayload[E]) => void; + +type ThreadListItemEventPayload = { + switchedTo: Record<string, never>; + switchedAway: Record<string, never>; +}; + +type ThreadListItemEventType = keyof ThreadListItemEventPayload; + +type ThreadListItemRuntime = { + readonly path: ThreadListItemRuntimePath; + getState(): ThreadListItemState; + initialize(): Promise<{ + remoteId: string; + externalId: string | undefined; + }>; + generateTitle(): Promise<void>; + switchTo(options?: { + unarchive?: boolean; + }): Promise<void>; + rename(newTitle: string): Promise<void>; + updateCustom(custom: Record<string, unknown> | undefined): Promise<void>; + archive(): Promise<void>; + unarchive(): Promise<void>; + delete(): Promise<void>; + detach(): void; + subscribe(callback: () => void): Unsubscribe; + unstable_on<E extends ThreadListItemEventType>(event: E, callback: ThreadListItemEventCallback<E>): Unsubscribe; + __internal_getRuntime(): ThreadListItemRuntime; +}; + +type ThreadListItemRuntimePath = { + readonly ref: string; + readonly threadSelector: { + readonly type: "main"; + } | { + readonly type: "index"; + readonly index: number; + } | { + readonly type: "archiveIndex"; + readonly index: number; + } | { + readonly type: "threadId"; + readonly threadId: string; + }; +}; + +type ThreadListItemState = { + readonly isMain: boolean; + readonly id: string; + readonly remoteId: string | undefined; + readonly externalId: string | undefined; + readonly status: ThreadListItemStatus; + readonly title?: string | undefined; + readonly lastMessageAt?: Date | undefined; + readonly custom?: Record<string, unknown> | undefined; +}; + +type ThreadListItemStatus = "archived" | "deleted" | "new" | "regular"; + +type ThreadListRuntime = { + getState(): ThreadListState; + subscribe(callback: () => void): Unsubscribe; + readonly main: ThreadRuntime; + getById(threadId: string): ThreadRuntime; + readonly mainItem: ThreadListItemRuntime; + getItemById(threadId: string): ThreadListItemRuntime; + getItemByIndex(idx: number): ThreadListItemRuntime; + getArchivedItemByIndex(idx: number): ThreadListItemRuntime; + switchToThread(threadId: string, options?: { + unarchive?: boolean; + }): Promise<void>; + switchToNewThread(): Promise<void>; + getLoadThreadsPromise(): Promise<void>; + reload(): Promise<void>; + loadMore(): Promise<void>; +}; + +type ThreadListState = { + readonly mainThreadId: string; + readonly newThreadId: string | undefined; + readonly threadIds: readonly string[]; + readonly archivedThreadIds: readonly string[]; + readonly isLoading: boolean; + readonly isLoadingMore: boolean; + readonly hasMore: boolean; + readonly threadItems: Readonly<Record<string, Omit<ThreadListItemState, "isMain" | "threadId">>>; +}; + +type ThreadMessage = BaseThreadMessage & (ThreadSystemMessage | ThreadUserMessage | ThreadAssistantMessage); + +type ThreadMessageLike = { + readonly role: "assistant" | "system" | "user"; + readonly content: string | readonly (TextMessagePart | ReasoningMessagePart | SourceMessagePart | ImageMessagePart | FileMessagePart | DataMessagePart | GenerativeUIMessagePart | Unstable_AudioMessagePart | DataPrefixedPart | { + readonly type: "tool-call"; + readonly toolCallId?: string; + readonly toolName: string; + readonly args?: ReadonlyJSONObject; + readonly argsText?: string; + readonly artifact?: any; + readonly result?: any | undefined; + readonly isError?: boolean | undefined; + readonly parentId?: string | undefined; + readonly messages?: readonly ThreadMessage[] | undefined; + readonly interrupt?: { + type: "human"; + payload: unknown; + }; + readonly timing?: ToolCallTiming; + readonly providerMetadata?: PartProviderMetadata; + readonly approval?: { + readonly id: string; + readonly approved?: boolean; + readonly reason?: string; + readonly isAutomatic?: boolean; + readonly options?: readonly ToolApprovalOption[]; + readonly optionId?: string; + readonly resolution?: "cancelled" | "expired"; + }; + })[]; + readonly id?: string | undefined; + readonly createdAt?: Date | undefined; + readonly status?: MessageStatus | undefined; + readonly attachments?: readonly (Omit<CompleteAttachment, "content"> & { + readonly content: readonly (ThreadUserMessagePart | DataPrefixedPart)[]; + })[] | undefined; + readonly metadata?: { + readonly unstable_state?: ReadonlyJSONValue; + readonly unstable_annotations?: readonly ReadonlyJSONValue[] | undefined; + readonly unstable_data?: readonly ReadonlyJSONValue[] | undefined; + readonly steps?: readonly ThreadStep[] | undefined; + readonly timing?: MessageTiming | undefined; + readonly submittedFeedback?: { + readonly type: "negative" | "positive"; + }; + readonly isOptimistic?: boolean | undefined; + readonly custom?: Record<string, unknown> | undefined; + } | undefined; +}; + +type ThreadRuntime = { + readonly path: ThreadRuntimePath; + readonly composer: ThreadComposerRuntime; + getState(): ThreadState; + append(message: CreateAppendMessage): void; + deleteMessage(messageId: string): void | Promise<void>; + startRun(config: CreateStartRunConfig): void; + resumeRun(config: CreateResumeRunConfig): void; + exportExternalState(): any; + importExternalState(state: any): void; + subscribe(callback: () => void): Unsubscribe; + cancelRun(): void; + getModelContext(): ModelContext; + export(): ExportedMessageRepository; + import(repository: ExportedMessageRepository): void; + reset(initialMessages?: readonly ThreadMessageLike[]): void; + getMessageByIndex(idx: number): MessageRuntime; + getMessageById(messageId: string): MessageRuntime; + stopSpeaking(): void; + connectVoice(): void; + disconnectVoice(): void; + getVoiceVolume(): number; + subscribeVoiceVolume(callback: () => void): Unsubscribe; + muteVoice(): void; + unmuteVoice(): void; + unstable_on<E extends ThreadRuntimeEventType>(event: E, callback: ThreadRuntimeEventCallback<E>): Unsubscribe; +}; + +type ThreadRuntimeEventCallback<E extends ThreadRuntimeEventType> = (payload: ThreadRuntimeEventPayload[E]) => void; + +type ThreadRuntimeEventPayload = { + runStart: Record<string, never>; + runEnd: Record<string, never>; + initialize: Record<string, never>; + modelContextUpdate: Record<string, never>; +}; + +type ThreadRuntimeEventType = keyof ThreadRuntimeEventPayload; + +type ThreadRuntimePath = { + readonly ref: string; + readonly threadSelector: { + readonly type: "main"; + } | { + readonly type: "threadId"; + readonly threadId: string; + }; +}; + +type ThreadState = { + readonly threadId: string; + readonly metadata: ThreadListItemState; + readonly isDisabled: boolean; + readonly isLoading: boolean; + readonly isRunning: boolean; + readonly capabilities: RuntimeCapabilities; + readonly messages: readonly ThreadMessage[]; + readonly state: ReadonlyJSONValue; + readonly suggestions: readonly ThreadSuggestion[]; + readonly extras: unknown; + readonly speech: SpeechState | undefined; + readonly voice: VoiceSessionState | undefined; +}; + +type ThreadStep = { + readonly messageId?: string; + readonly usage?: { + readonly inputTokens: number; + readonly outputTokens: number; + } | undefined; +}; + +type ThreadSuggestion = { + prompt: string; +}; + +type ThreadSystemMessage = MessageCommonProps & { + readonly role: "system"; + readonly content: readonly [ + TextMessagePart + ]; + readonly metadata: { + readonly unstable_state?: undefined; + readonly unstable_annotations?: undefined; + readonly unstable_data?: undefined; + readonly steps?: undefined; + readonly submittedFeedback?: undefined; + readonly timing?: undefined; + readonly custom: Record<string, unknown>; + }; +}; + +type ThreadUserMessage = MessageCommonProps & { + readonly role: "user"; + readonly content: readonly ThreadUserMessagePart[]; + readonly attachments: readonly CompleteAttachment[]; + readonly metadata: { + readonly unstable_state?: undefined; + readonly unstable_annotations?: undefined; + readonly unstable_data?: undefined; + readonly steps?: undefined; + readonly submittedFeedback?: undefined; + readonly timing?: undefined; + readonly custom: Record<string, unknown>; + }; +}; + +type ThreadUserMessagePart = TextMessagePart | ImageMessagePart | FileMessagePart | DataMessagePart | Unstable_AudioMessagePart; + +type Tool<TArgs extends Record<string, unknown> = Record<string, unknown>, TResult = unknown> = FrontendTool<TArgs, TResult> | BackendTool<TArgs, TResult> | HumanTool<TArgs, TResult> | ProviderTool<TArgs, TResult> | McpTool | ToolWithoutType<TArgs, TResult>; + +type ToolApprovalOption = { + readonly id: string; + readonly kind: ToolApprovalOptionKind | (string & {}); + readonly label?: string; + readonly description?: string; + readonly grants?: readonly string[]; + readonly confirm?: boolean | { + title?: string; + description?: string; + }; +}; + +type ToolApprovalOptionKind = "allow-always" | "allow-once" | "reject-always" | "reject-once"; + +type ToolApprovalResponse = { + readonly approved: boolean; + readonly reason?: string; +} | { + readonly optionId: string; + readonly reason?: string; +} | { + readonly approved: boolean; + readonly optionId: string; + readonly reason?: string; +}; + +type ToolBase<TArgs extends Record<string, unknown> = Record<string, unknown>, TResult = unknown> = { + streamCall?: ToolStreamCallFunction<TArgs, TResult>; + display?: ToolDisplay; +}; + +interface ToolCallArgsReader<TArgs extends Record<string, unknown>> { + get<PathT extends TypePath<TArgs>>(...fieldPath: PathT): Promise<TypeAtPath<TArgs, PathT>>; + streamValues<PathT extends TypePath<TArgs>>(...fieldPath: PathT): AsyncIterableStream<DeepPartial<TypeAtPath<TArgs, PathT>>>; + streamText<PathT extends TypePath<TArgs>>(...fieldPath: PathT): TypeAtPath<TArgs, PathT> extends string & (infer U) ? AsyncIterableStream<U> : never; + forEach<PathT extends TypePath<TArgs>>(...fieldPath: PathT): NonNullable<TypeAtPath<TArgs, PathT>> extends Array<infer U> ? AsyncIterableStream<U> : never; +} + +type ToolCallMessagePart<TArgs = ReadonlyJSONObject, TResult = unknown> = { + readonly type: "tool-call"; + readonly toolCallId: string; + readonly toolName: string; + readonly args: TArgs; + readonly result?: TResult | undefined; + readonly isError?: boolean | undefined; + readonly argsText: string; + readonly artifact?: unknown; + readonly timing?: ToolCallTiming; + readonly mcp?: ToolCallMessagePartMcpMetadata; + readonly providerMetadata?: PartProviderMetadata; + readonly modelContent?: readonly ToolModelContentPart[] | undefined; + readonly interrupt?: { + type: "human"; + payload: unknown; + }; + readonly approval?: { + readonly id: string; + readonly approved?: boolean; + readonly reason?: string; + readonly isAutomatic?: boolean; + readonly options?: readonly ToolApprovalOption[]; + readonly optionId?: string; + readonly resolution?: "cancelled" | "expired"; + }; + readonly parentId?: string; + readonly messages?: readonly ThreadMessage[]; +}; + +type ToolCallMessagePartMcpMetadata = { + readonly app?: McpAppMetadata; +}; + +type ToolCallMessagePartStatus = { + readonly type: "requires-action"; + readonly reason: "interrupt"; +} | MessagePartStatus; + +interface ToolCallReader<TArgs extends Record<string, unknown> = Record<string, unknown>, TResult = unknown> { + args: ToolCallArgsReader<TArgs>; + response: ToolCallResponseReader<TResult>; + result: { + get: () => Promise<TResult>; + }; +} + +interface ToolCallResponseReader<TResult> { + get: () => Promise<ToolResponse<TResult>>; +} + +type ToolCallTiming = { + readonly startedAt: number; + readonly completedAt?: number; +}; + +type ToolDisplay = "inline" | "standalone"; + +type ToolExecuteFunction<TArgs, TResult> = (args: TArgs, context: ToolExecutionContext) => TResult | Promise<TResult>; + +type ToolExecutionContext = { + toolCallId: string; + abortSignal: AbortSignal; + human: (payload: unknown) => Promise<unknown>; +}; + +type ToolModelContentPart = { + readonly type: "text"; + readonly text: string; +} | { + readonly type: "file"; + readonly data: string; + readonly mediaType: string; + readonly filename?: string; +}; + +type ToolModelOutputFunction<TArgs, TResult> = (options: { + toolCallId: string; + input: TArgs; + output: TResult; +}) => readonly ToolModelContentPart[] | Promise<readonly ToolModelContentPart[]>; + +declare class ToolResponse<TResult> { + get [TOOL_RESPONSE_SYMBOL](): boolean; + readonly artifact?: ReadonlyJSONValue; + readonly result: TResult; + readonly isError: boolean; + readonly modelContent?: readonly ToolModelContentPart[]; + readonly messages?: ReadonlyJSONValue; + constructor(options: ToolResponseLike<TResult>); + static [Symbol.hasInstance](obj: unknown): obj is ToolResponse<ReadonlyJSONValue>; + static toResponse(result: any | ToolResponse<any>): ToolResponse<any>; +} + +type ToolResponseLike<TResult> = { + result: TResult; + artifact?: ReadonlyJSONValue | undefined; + isError?: boolean | undefined; + modelContent?: readonly ToolModelContentPart[] | undefined; + messages?: ReadonlyJSONValue | undefined; +}; + +type ToolStreamCallFunction<TArgs extends Record<string, unknown> = Record<string, unknown>, TResult = unknown> = (reader: ToolCallReader<TArgs, TResult>, context: ToolExecutionContext) => void; + +type ToolWithoutType<TArgs extends Record<string, unknown> = Record<string, unknown>, TResult = unknown> = (Omit<FrontendTool<TArgs, TResult>, "type"> | Omit<BackendTool<TArgs, TResult>, "type"> | Omit<HumanTool<TArgs, TResult>, "type"> | Omit<ProviderTool<TArgs, TResult>, "type">) & { + type?: undefined; +}; + +type TupleIndex<T extends readonly any[]> = Exclude<keyof T, keyof any[]>; + +type TypeAtPath<T, P extends readonly any[]> = P extends [ + infer Head, + ...infer Rest +] ? Head extends keyof T ? TypeAtPath<T[Head], Rest> : never : T; + +type TypePath<T> = [ +] | (0 extends 1 & T ? any[] : T extends object ? T extends readonly any[] ? number extends T["length"] ? { + [K in TupleIndex<T>]: [ + AsNumber<K>, + ...TypePath<T[K]> + ]; +}[TupleIndex<T>] : [ + number, + ...TypePath<T[number]> +] : { + [K in ObjectKey<T>]: [ + K, + ...TypePath<T[K]> + ]; +}[ObjectKey<T>] : [ +]); + +type Unstable_AudioMessagePart = { + readonly type: "audio"; + readonly audio: { + readonly data: string; + readonly format: "mp3" | "wav"; + }; +}; + +type Unsubscribe = () => void; + +type UseCloudRuntimeOptions = Omit<UseDataStreamRuntimeOptions, "api"> & { + cloud: AssistantCloud; + assistantId: string; +}; + +type UseDataStreamRuntimeOptions = { + api: string; + protocol?: DataStreamProtocol; + onData?: (data: { + type: string; + name: string; + data: unknown; + transient?: boolean; + }) => void; + onResponse?: (response: Response) => void | Promise<void>; + onFinish?: (message: ThreadMessage) => void; + onError?: (error: Error) => void; + onCancel?: () => void; + credentials?: RequestCredentials; + headers?: HeadersValue | (() => Promise<HeadersValue>); + body?: object | (() => Promise<object | undefined>); + sendExtraMessageFields?: boolean; +} & LocalRuntimeOptions; + +type VoiceSessionState = { + readonly status: RealtimeVoiceAdapter.Status; + readonly isMuted: boolean; + readonly mode: RealtimeVoiceAdapter.Mode; +}; + +declare global { + interface Window { + SpeechRecognition?: SpeechRecognitionConstructor; + webkitSpeechRecognition?: SpeechRecognitionConstructor; + } +} + +declare namespace entry_root_exports { + export { DataStreamProtocol, UseDataStreamRuntimeOptions, toLanguageModelMessages, useCloudRuntime, useDataStreamRuntime }; +} + +declare function toLanguageModelMessages(messages: readonly ThreadMessage[], options?: { + unstable_includeId?: boolean | undefined; +}): LanguageModelV2Message[]; + +declare const useCloudRuntime: (options: UseCloudRuntimeOptions) => AssistantRuntime; + +declare const useDataStreamRuntime: (options: UseDataStreamRuntimeOptions) => AssistantRuntime; + +export { entry_root_exports as entry_root }; diff --git a/api-surface/assistant-ui__react-devtools.ts b/api-surface/assistant-ui__react-devtools.ts new file mode 100644 index 0000000..9957059 --- /dev/null +++ b/api-surface/assistant-ui__react-devtools.ts @@ -0,0 +1,565 @@ +import "@radix-ui/react-primitive"; + +import { StandardSchemaV1 } from "@standard-schema/spec"; + +import "radix-ui"; + +import { CSSProperties, ComponentType, ReactNode } from "react"; + +import "react-textarea-autosize"; + +import "zustand"; + +type AncestorsOf<K extends ClientNames, Seen extends ClientNames = never> = K extends Seen ? never : ParentOf<K> extends never ? never : ParentOf<K> | AncestorsOf<ParentOf<K>, Seen | K>; + +interface ApiInfo { + id: number; + state: Record<string, unknown>; + logs: EventLogEntry[]; + modelContext?: SerializedModelContext | undefined; + scopes?: unknown; + threadSnapshots?: Readonly<Record<string, unknown>>; +} + +type AsNumber<K> = K extends `${infer N extends number}` ? N | K : never; + +type AssistantClient = { + [K in ClientNames]: AssistantClientAccessor<K>; +} & { + subscribe(listener: () => void): Unsubscribe; + on<TEvent extends AssistantEventName>(selector: AssistantEventSelector<TEvent>, callback: AssistantEventCallback<TEvent>): Unsubscribe; +}; + +type AssistantClientAccessor<K extends ClientNames> = (() => ClientSchemas[K]["methods"]) & (ClientMeta<K> | { + source: "root"; + query: Record<string, never>; +} | { + source: null; + query: null; +}) & { + name: K; +}; + +type AssistantEventCallback<TEvent extends AssistantEventName> = (payload: AssistantEventPayload[TEvent]) => void; + +type AssistantEventName = keyof AssistantEventPayload; + +type AssistantEventPayload = ClientEventMap & { + "*": WildcardPayload; +}; + +type AssistantEventScope<TEvent extends AssistantEventName> = "*" | EventSource<TEvent> | (EventSource<TEvent> extends ClientNames ? AncestorsOf<EventSource<TEvent>> : never); + +type AssistantEventSelector<TEvent extends AssistantEventName> = TEvent | { + scope: AssistantEventScope<TEvent>; + event: TEvent; +}; + +type AsyncIterableStream<T> = AsyncIterable<T> & ReadableStream<T>; + +type BackendTool<TArgs extends Record<string, unknown> = Record<string, unknown>, TResult = unknown> = ToolBase<TArgs, TResult> & { + type: "backend"; + description?: undefined; + parameters?: undefined; + disabled?: undefined; + execute?: undefined; + toModelOutput?: undefined; + experimental_onSchemaValidationError?: undefined; + providerOptions?: undefined; +}; + +type ClientError<E extends string> = { + methods: Record<E, () => E>; + meta: { + source: ClientNames; + query: Record<E, E>; + }; + events: Record<`${E}.`, E>; +}; + +type ClientEventMap = UnionToIntersection<{ + [K in ClientNames]: ClientEvents<K>; +}[ClientNames]>; + +type ClientEvents<K extends ClientNames> = "events" extends keyof ClientSchemas[K] ? ClientSchemas[K]["events"] extends ClientEventsType<K> ? ClientSchemas[K]["events"] : never : never; + +type ClientEventsType<K extends ClientNames> = Record<`${K}.${string}`, unknown>; + +type ClientMeta<K extends ClientNames> = "meta" extends keyof ClientSchemas[K] ? Pick<ClientSchemas[K]["meta"] extends ClientMetaType ? ClientSchemas[K]["meta"] : never, "query" | "source"> : never; + +type ClientMetaType = { + source: ClientNames; + query: Record<string, unknown>; +}; + +interface ClientMethods { + [key: string | symbol]: (...args: any[]) => any; +} + +type ClientNames = keyof ClientSchemas extends (infer U) ? U : never; + +type ClientSchemas = keyof ScopeRegistry extends never ? { + "ERROR: No clients were defined": ClientError<"ERROR: No clients were defined">; +} : { + [K in keyof ScopeRegistry]: ValidateClient<K>; +}; + +type DeepPartial<T> = T extends readonly any[] ? readonly DeepPartial<T[number]>[] : T extends { + [key: string]: any; +} ? { + readonly [K in keyof T]?: DeepPartial<T[K]>; +} : T; + +interface DevToolsApiEntry { + api: Partial<AssistantClient>; + logs: EventLog[]; +} + +interface DevToolsClient { + subscribe(listener: () => void): () => void; + getSnapshot(): DevToolsSnapshot; + getServerSnapshot?(): DevToolsSnapshot; + clearEvents(apiId: number): void; + switchToThread?(apiId: number, threadId: string): void | Promise<void>; +} + +interface DevToolsHook { + apis: Map<number, DevToolsApiEntry>; + nextId: number; + listeners: Set<(apiId: number) => void>; +} + +declare const DevToolsModal: (props?: DevToolsModalProps) => import("react").JSX.Element | null; + +interface DevToolsModalProps { + plugins?: DevToolsPanelPlugin[]; + theme?: "dark" | "light" | "system"; + client?: DevToolsClient; +} + +declare const DevToolsPanel: (_param0: DevToolsPanelProps) => import("react").JSX.Element; + +interface DevToolsPanelPlugin { + id: string; + label: string; + order?: number; + isAvailable?: (ctx: DevToolsTabContext) => boolean; + Component: ComponentType<DevToolsTabContext>; +} + +interface DevToolsPanelProps { + plugins?: DevToolsPanelPlugin[] | undefined; + theme?: "dark" | "light"; + onClose?: (() => void) | undefined; + client?: DevToolsClient | undefined; +} + +interface DevToolsSnapshot { + apiIds: number[]; + byId: ReadonlyMap<number, ApiInfo>; +} + +interface DevToolsTabContext { + apiId: number; + data: ApiInfo; + clearEvents: (apiId: number) => void; + theme: "dark" | "light"; + selection: string | null; + setSelection: (nodeId: string | null) => void; + switchToThread?: ((threadId: string) => void | Promise<void>) | undefined; +} + +interface EventLog { + time: Date; + event: string; + data: unknown; +} + +interface EventLogEntry { + readonly time: Date; + readonly event: string; + readonly data: unknown; +} + +type EventSource<T extends AssistantEventName> = T extends `${infer Source}.${string}` ? Source : never; + +type FrontendTool<TArgs extends Record<string, unknown> = Record<string, unknown>, TResult = unknown> = ToolBase<TArgs, TResult> & { + type: "frontend"; + description?: string | undefined; + parameters: StandardSchemaV1<TArgs> | JSONSchema7; + disabled?: boolean; + execute?: ToolExecuteFunction<TArgs, TResult>; + toModelOutput?: ToolModelOutputFunction<TArgs, TResult>; + experimental_onSchemaValidationError?: OnSchemaValidationErrorFunction<TResult>; + providerOptions?: ProviderOptions; +}; + +type HumanTool<TArgs extends Record<string, unknown> = Record<string, unknown>, TResult = unknown> = ToolBase<TArgs, TResult> & { + type: "human"; + description?: string | undefined; + parameters: StandardSchemaV1<TArgs> | JSONSchema7; + disabled?: boolean; + display?: "standalone"; + execute?: undefined; + toModelOutput?: undefined; + experimental_onSchemaValidationError?: undefined; + providerOptions?: ProviderOptions; +}; + +interface JSONSchema7 { + $id?: string | undefined; + $ref?: string | undefined; + $schema?: JSONSchema7Version | undefined; + $comment?: string | undefined; + $defs?: { + [key: string]: JSONSchema7Definition; + } | undefined; + type?: JSONSchema7TypeName | JSONSchema7TypeName[] | undefined; + enum?: JSONSchema7Type[] | undefined; + const?: JSONSchema7Type | undefined; + multipleOf?: number | undefined; + maximum?: number | undefined; + exclusiveMaximum?: number | undefined; + minimum?: number | undefined; + exclusiveMinimum?: number | undefined; + maxLength?: number | undefined; + minLength?: number | undefined; + pattern?: string | undefined; + items?: JSONSchema7Definition | JSONSchema7Definition[] | undefined; + additionalItems?: JSONSchema7Definition | undefined; + maxItems?: number | undefined; + minItems?: number | undefined; + uniqueItems?: boolean | undefined; + contains?: JSONSchema7Definition | undefined; + maxProperties?: number | undefined; + minProperties?: number | undefined; + required?: string[] | undefined; + properties?: { + [key: string]: JSONSchema7Definition; + } | undefined; + patternProperties?: { + [key: string]: JSONSchema7Definition; + } | undefined; + additionalProperties?: JSONSchema7Definition | undefined; + dependencies?: { + [key: string]: JSONSchema7Definition | string[]; + } | undefined; + propertyNames?: JSONSchema7Definition | undefined; + if?: JSONSchema7Definition | undefined; + then?: JSONSchema7Definition | undefined; + else?: JSONSchema7Definition | undefined; + allOf?: JSONSchema7Definition[] | undefined; + anyOf?: JSONSchema7Definition[] | undefined; + oneOf?: JSONSchema7Definition[] | undefined; + not?: JSONSchema7Definition | undefined; + format?: string | undefined; + contentMediaType?: string | undefined; + contentEncoding?: string | undefined; + definitions?: { + [key: string]: JSONSchema7Definition; + } | undefined; + title?: string | undefined; + description?: string | undefined; + default?: JSONSchema7Type | undefined; + readOnly?: boolean | undefined; + writeOnly?: boolean | undefined; + examples?: JSONSchema7Type | undefined; +} + +interface JSONSchema7Array extends Array<JSONSchema7Type> { +} + +type JSONSchema7Definition = JSONSchema7 | boolean; + +interface JSONSchema7Object { + [key: string]: JSONSchema7Type; +} + +type JSONSchema7Type = string | number | boolean | JSONSchema7Object | JSONSchema7Array | null; + +type JSONSchema7TypeName = "array" | "boolean" | "integer" | "null" | "number" | "object" | "string"; + +type JSONSchema7Version = string; + +type LanguageModelConfig = { + apiKey?: string; + baseUrl?: string; + modelName?: string; + reasoningEffort?: string; +}; + +type LanguageModelV1CallSettings = { + maxTokens?: number; + temperature?: number; + topP?: number; + presencePenalty?: number; + frequencyPenalty?: number; + seed?: number; + headers?: Record<string, string | undefined>; +}; + +type McpServerConfig = { + type: "http" | "sse"; + url: string; + headers?: Record<string, string>; + redirect?: "error" | "follow"; + connectionTimeout?: number | undefined; +} | { + type: "stdio"; + command: string; + args?: readonly string[]; + env?: Record<string, string>; + cwd?: string; + connectionTimeout?: number | undefined; +}; + +type McpTool = ToolBase<Record<string, unknown>, unknown> & { + type: "mcp"; + server: McpServerConfig; + description?: undefined; + parameters?: undefined; + disabled?: boolean; + execute?: undefined; + toModelOutput?: undefined; + experimental_onSchemaValidationError?: undefined; + providerOptions?: undefined; +}; + +type ModelContext = { + priority?: number | undefined; + system?: string | undefined; + tools?: Record<string, Tool<any, any>> | undefined; + callSettings?: LanguageModelV1CallSettings | undefined; + config?: LanguageModelConfig | undefined; + unstable_composerMetadata?: Record<string, unknown> | undefined; +}; + +type NormalizedTool = { + name: string; + type?: string; + description?: string; + disabled?: boolean; + display?: string; + providerId?: string; + supportsDeferredResults?: boolean; + backendDefault?: unknown; + providerOptions?: unknown; + providerArgs?: unknown; + server?: unknown; + parameters?: unknown; +}; + +type ObjectKey<T> = keyof T & (string | number); + +type OnSchemaValidationErrorFunction<TResult> = ToolExecuteFunction<unknown, TResult>; + +type ParentOf<K extends ClientNames> = AssistantClientAccessor<K> extends { + source: infer S; +} ? S extends ClientNames ? S : never : never; + +type ProviderOptions = Record<string, Record<string, unknown>>; + +type ProviderTool<TArgs extends Record<string, unknown> = Record<string, unknown>, TResult = unknown> = ToolBase<TArgs, TResult> & { + type: "provider"; + providerId: `${string}.${string}`; + parameters?: StandardSchemaV1<TArgs> | JSONSchema7 | undefined; + args: Record<string, unknown>; + supportsDeferredResults?: boolean; + description?: undefined; + disabled?: boolean; + execute?: undefined; + toModelOutput?: undefined; + experimental_onSchemaValidationError?: undefined; + providerOptions?: ProviderOptions; +}; + +type ReadonlyJSONArray = readonly ReadonlyJSONValue[]; + +type ReadonlyJSONObject = { + readonly [key: string]: ReadonlyJSONValue; +}; + +type ReadonlyJSONValue = null | string | number | boolean | ReadonlyJSONObject | ReadonlyJSONArray; + +interface ScopeRegistry { + [key: string]: { methods: any; meta?: any; events?: any }; +} + +interface SerializedModelContext { + system?: string; + tools?: NormalizedTool[]; + callSettings?: Record<string, unknown>; + config?: Record<string, unknown>; +} + +declare const ShadowRoot: (_param1: ShadowRootProps) => import("react").JSX.Element; + +interface ShadowRootProps { + theme: "dark" | "light"; + className?: string; + style?: CSSProperties; + children: ReactNode; +} + +interface SpeechRecognitionConstructor { + new (): SpeechRecognitionInstance; +} + +interface SpeechRecognitionInstance extends EventTarget { + lang: string; + continuous: boolean; + interimResults: boolean; + start(): void; + stop(): void; + abort(): void; +} + +declare const TOOL_RESPONSE_SYMBOL: unique symbol; + +type Tool<TArgs extends Record<string, unknown> = Record<string, unknown>, TResult = unknown> = FrontendTool<TArgs, TResult> | BackendTool<TArgs, TResult> | HumanTool<TArgs, TResult> | ProviderTool<TArgs, TResult> | McpTool | ToolWithoutType<TArgs, TResult>; + +type ToolBase<TArgs extends Record<string, unknown> = Record<string, unknown>, TResult = unknown> = { + streamCall?: ToolStreamCallFunction<TArgs, TResult>; + display?: ToolDisplay; +}; + +interface ToolCallArgsReader<TArgs extends Record<string, unknown>> { + get<PathT extends TypePath<TArgs>>(...fieldPath: PathT): Promise<TypeAtPath<TArgs, PathT>>; + streamValues<PathT extends TypePath<TArgs>>(...fieldPath: PathT): AsyncIterableStream<DeepPartial<TypeAtPath<TArgs, PathT>>>; + streamText<PathT extends TypePath<TArgs>>(...fieldPath: PathT): TypeAtPath<TArgs, PathT> extends string & (infer U) ? AsyncIterableStream<U> : never; + forEach<PathT extends TypePath<TArgs>>(...fieldPath: PathT): NonNullable<TypeAtPath<TArgs, PathT>> extends Array<infer U> ? AsyncIterableStream<U> : never; +} + +interface ToolCallReader<TArgs extends Record<string, unknown> = Record<string, unknown>, TResult = unknown> { + args: ToolCallArgsReader<TArgs>; + response: ToolCallResponseReader<TResult>; + result: { + get: () => Promise<TResult>; + }; +} + +interface ToolCallResponseReader<TResult> { + get: () => Promise<ToolResponse<TResult>>; +} + +type ToolDisplay = "inline" | "standalone"; + +type ToolExecuteFunction<TArgs, TResult> = (args: TArgs, context: ToolExecutionContext) => TResult | Promise<TResult>; + +type ToolExecutionContext = { + toolCallId: string; + abortSignal: AbortSignal; + human: (payload: unknown) => Promise<unknown>; +}; + +type ToolModelContentPart = { + readonly type: "text"; + readonly text: string; +} | { + readonly type: "file"; + readonly data: string; + readonly mediaType: string; + readonly filename?: string; +}; + +type ToolModelOutputFunction<TArgs, TResult> = (options: { + toolCallId: string; + input: TArgs; + output: TResult; +}) => readonly ToolModelContentPart[] | Promise<readonly ToolModelContentPart[]>; + +declare class ToolResponse<TResult> { + get [TOOL_RESPONSE_SYMBOL](): boolean; + readonly artifact?: ReadonlyJSONValue; + readonly result: TResult; + readonly isError: boolean; + readonly modelContent?: readonly ToolModelContentPart[]; + readonly messages?: ReadonlyJSONValue; + constructor(options: ToolResponseLike<TResult>); + static [Symbol.hasInstance](obj: unknown): obj is ToolResponse<ReadonlyJSONValue>; + static toResponse(result: any | ToolResponse<any>): ToolResponse<any>; +} + +type ToolResponseLike<TResult> = { + result: TResult; + artifact?: ReadonlyJSONValue | undefined; + isError?: boolean | undefined; + modelContent?: readonly ToolModelContentPart[] | undefined; + messages?: ReadonlyJSONValue | undefined; +}; + +type ToolStreamCallFunction<TArgs extends Record<string, unknown> = Record<string, unknown>, TResult = unknown> = (reader: ToolCallReader<TArgs, TResult>, context: ToolExecutionContext) => void; + +type ToolWithoutType<TArgs extends Record<string, unknown> = Record<string, unknown>, TResult = unknown> = (Omit<FrontendTool<TArgs, TResult>, "type"> | Omit<BackendTool<TArgs, TResult>, "type"> | Omit<HumanTool<TArgs, TResult>, "type"> | Omit<ProviderTool<TArgs, TResult>, "type">) & { + type?: undefined; +}; + +type TupleIndex<T extends readonly any[]> = Exclude<keyof T, keyof any[]>; + +type TypeAtPath<T, P extends readonly any[]> = P extends [ + infer Head, + ...infer Rest +] ? Head extends keyof T ? TypeAtPath<T[Head], Rest> : never : T; + +type TypePath<T> = [ +] | (0 extends 1 & T ? any[] : T extends object ? T extends readonly any[] ? number extends T["length"] ? { + [K in TupleIndex<T>]: [ + AsNumber<K>, + ...TypePath<T[K]> + ]; +}[TupleIndex<T>] : [ + number, + ...TypePath<T[number]> +] : { + [K in ObjectKey<T>]: [ + K, + ...TypePath<T[K]> + ]; +}[ObjectKey<T>] : [ +]); + +type UnionToIntersection<U> = (U extends unknown ? (x: U) => void : never) extends ((x: infer I) => void) ? I : never; + +type Unsubscribe = () => void; + +type ValidateClient<K extends keyof ScopeRegistry> = ScopeRegistry[K] extends { + methods: ClientMethods; +} ? "meta" extends keyof ScopeRegistry[K] ? ScopeRegistry[K]["meta"] extends ClientMetaType ? "events" extends keyof ScopeRegistry[K] ? ScopeRegistry[K]["events"] extends ClientEventsType<K> ? ScopeRegistry[K] : ClientError<`ERROR: ${K & string} has invalid events type`> : ScopeRegistry[K] : ClientError<`ERROR: ${K & string} has invalid meta type`> : "events" extends keyof ScopeRegistry[K] ? ScopeRegistry[K]["events"] extends ClientEventsType<K> ? ScopeRegistry[K] : ClientError<`ERROR: ${K & string} has invalid events type`> : ScopeRegistry[K] : ClientError<`ERROR: ${K & string} has invalid methods type`>; + +type WildcardPayload = { + [K in keyof ClientEventMap]: { + event: K; + payload: ClientEventMap[K]; + }; +}[Extract<keyof ClientEventMap, string>]; + +declare const builtinPlugins: DevToolsPanelPlugin[]; + +declare const createDevToolsPlugin: (plugin: DevToolsPanelPlugin) => DevToolsPanelPlugin; + +declare const createInProcessClient: () => DevToolsClient; + +declare global { + interface Window { + SpeechRecognition?: SpeechRecognitionConstructor; + webkitSpeechRecognition?: SpeechRecognitionConstructor; + } +} + +declare global { + interface Window { + __ASSISTANT_UI_DEVTOOLS_HOOK__?: any; + } +} + +declare const inProcessClient: DevToolsClient; + +declare namespace entry_root_exports { + export { ApiInfo, DevToolsClient, DevToolsModal, DevToolsModalProps, DevToolsPanel, DevToolsPanelPlugin, DevToolsPanelProps, DevToolsSnapshot, DevToolsTabContext, NormalizedTool, SerializedModelContext, ShadowRoot, builtinPlugins, createDevToolsPlugin, createInProcessClient, inProcessClient, normalizeToolList, serializeModelContext }; +} + +declare const normalizeToolList: (value: unknown) => NormalizedTool[]; + +declare const serializeModelContext: (context: ModelContext | undefined) => SerializedModelContext | undefined; + +export { entry_root_exports as entry_root }; diff --git a/api-surface/assistant-ui__react-generative-ui.ts b/api-surface/assistant-ui__react-generative-ui.ts new file mode 100644 index 0000000..99943d6 --- /dev/null +++ b/api-surface/assistant-ui__react-generative-ui.ts @@ -0,0 +1,1094 @@ +import "@radix-ui/react-primitive"; + +import { StandardSchemaV1 } from "@standard-schema/spec"; + +import "radix-ui"; + +import { ComponentType, ReactNode } from "react"; + +import "react-textarea-autosize"; + +import { ZodType } from "zod"; + +import "zustand"; + +declare const ALERT_TONES: readonly [ + "info", + "success", + "warning", + "danger" +]; + +declare const ALIGNS: readonly [ + "start", + "center", + "end" +]; + +interface Action { + readonly type: string; + readonly [payload: string]: unknown; +} + +type ActionDispatchContext = { + readonly payload: Action; +}; + +type ActionHandler = (ctx: ActionDispatchContext) => unknown | Promise<unknown>; + +type ActionRegistry = { + dispatch(action: Action): unknown; + has(type: string): boolean; +}; + +type AlertTone = (typeof ALERT_TONES)[number]; + +type Align = (typeof ALIGNS)[number]; + +type AncestorsOf<K extends ClientNames, Seen extends ClientNames = never> = K extends Seen ? never : ParentOf<K> extends never ? never : ParentOf<K> | AncestorsOf<ParentOf<K>, Seen | K>; + +type AsNumber<K> = K extends `${infer N extends number}` ? N | K : never; + +type AssistantClient = { + [K in ClientNames]: AssistantClientAccessor<K>; +} & { + subscribe(listener: () => void): Unsubscribe; + on<TEvent extends AssistantEventName>(selector: AssistantEventSelector<TEvent>, callback: AssistantEventCallback<TEvent>): Unsubscribe; +}; + +type AssistantClientAccessor<K extends ClientNames> = (() => ClientSchemas[K]["methods"]) & (ClientMeta<K> | { + source: "root"; + query: Record<string, never>; +} | { + source: null; + query: null; +}) & { + name: K; +}; + +type AssistantEventCallback<TEvent extends AssistantEventName> = (payload: AssistantEventPayload[TEvent]) => void; + +type AssistantEventName = keyof AssistantEventPayload; + +type AssistantEventPayload = ClientEventMap & { + "*": WildcardPayload; +}; + +type AssistantEventScope<TEvent extends AssistantEventName> = "*" | EventSource<TEvent> | (EventSource<TEvent> extends ClientNames ? AncestorsOf<EventSource<TEvent>> : never); + +type AssistantEventSelector<TEvent extends AssistantEventName> = TEvent | { + scope: AssistantEventScope<TEvent>; + event: TEvent; +}; + +type AsyncIterableStream<T> = AsyncIterable<T> & ReadableStream<T>; + +declare const BUTTON_STYLES: readonly [ + "primary", + "secondary", + "outline", + "ghost", + "danger" +]; + +type BackendDefaultMetadata = { + unstable_backendDefault?: { + parameters?: boolean; + }; +}; + +type BackendTool<TArgs extends Record<string, unknown> = Record<string, unknown>, TResult = unknown> = ToolBase<TArgs, TResult> & { + type: "backend"; + description?: undefined; + parameters?: undefined; + disabled?: undefined; + execute?: undefined; + toModelOutput?: undefined; + experimental_onSchemaValidationError?: undefined; + providerOptions?: undefined; +}; + +type BaseAttachment = { + id: string; + type: "image" | "document" | "file" | (string & {}); + name: string; + contentType?: string | undefined; + file?: File; + content?: ThreadUserMessagePart[]; +}; + +type BaseThreadMessage = { + readonly status?: ThreadAssistantMessage["status"]; + readonly metadata: { + readonly unstable_state?: ReadonlyJSONValue; + readonly unstable_annotations?: readonly ReadonlyJSONValue[]; + readonly unstable_data?: readonly ReadonlyJSONValue[]; + readonly steps?: readonly ThreadStep[]; + readonly submittedFeedback?: { + readonly type: "negative" | "positive"; + }; + readonly timing?: MessageTiming; + readonly isOptimistic?: boolean; + readonly custom: Record<string, unknown>; + }; + readonly attachments?: ThreadUserMessage["attachments"]; +}; + +type ButtonStyle = (typeof BUTTON_STYLES)[number]; + +declare const COLORS: readonly [ + "emphasis", + "secondary", + "alpha-70", + "white", + "white-70", + "white-50" +]; + +type ClientError<E extends string> = { + methods: Record<E, () => E>; + meta: { + source: ClientNames; + query: Record<E, E>; + }; + events: Record<`${E}.`, E>; +}; + +type ClientEventMap = UnionToIntersection<{ + [K in ClientNames]: ClientEvents<K>; +}[ClientNames]>; + +type ClientEvents<K extends ClientNames> = "events" extends keyof ClientSchemas[K] ? ClientSchemas[K]["events"] extends ClientEventsType<K> ? ClientSchemas[K]["events"] : never : never; + +type ClientEventsType<K extends ClientNames> = Record<`${K}.${string}`, unknown>; + +type ClientMeta<K extends ClientNames> = "meta" extends keyof ClientSchemas[K] ? Pick<ClientSchemas[K]["meta"] extends ClientMetaType ? ClientSchemas[K]["meta"] : never, "query" | "source"> : never; + +type ClientMetaType = { + source: ClientNames; + query: Record<string, unknown>; +}; + +interface ClientMethods { + [key: string | symbol]: (...args: any[]) => any; +} + +type ClientNames = keyof ClientSchemas extends (infer U) ? U : never; + +type ClientSchemas = keyof ScopeRegistry extends never ? { + "ERROR: No clients were defined": ClientError<"ERROR: No clients were defined">; +} : { + [K in keyof ScopeRegistry]: ValidateClient<K>; +}; + +type Color = (typeof COLORS)[number]; + +type CompleteAttachment = BaseAttachment & { + status: CompleteAttachmentStatus; + content: ThreadUserMessagePart[]; +}; + +type CompleteAttachmentStatus = { + type: "complete"; +}; + +type DataMessagePart<T = any> = { + readonly type: "data"; + readonly name: string; + readonly data: T; +}; + +type DeepPartial<T> = T extends readonly any[] ? readonly DeepPartial<T[number]>[] : T extends { + [key: string]: any; +} ? { + readonly [K in keyof T]?: DeepPartial<T[K]>; +} : T; + +interface DevToolsApiEntry { + api: Partial<AssistantClient>; + logs: EventLog[]; +} + +interface DevToolsHook { + apis: Map<number, DevToolsApiEntry>; + nextId: number; + listeners: Set<(apiId: number) => void>; +} + +interface EventLog { + time: Date; + event: string; + data: unknown; +} + +type EventSource<T extends AssistantEventName> = T extends `${infer Source}.${string}` ? Source : never; + +type FileMessagePart = { + readonly type: "file"; + readonly filename?: string; + readonly data: string; + readonly mimeType: string; + readonly parentId?: string; +}; + +type FrontendTool<TArgs extends Record<string, unknown> = Record<string, unknown>, TResult = unknown> = ToolBase<TArgs, TResult> & { + type: "frontend"; + description?: string | undefined; + parameters: StandardSchemaV1<TArgs> | JSONSchema7; + disabled?: boolean; + execute?: ToolExecuteFunction<TArgs, TResult>; + toModelOutput?: ToolModelOutputFunction<TArgs, TResult>; + experimental_onSchemaValidationError?: OnSchemaValidationErrorFunction<TResult>; + providerOptions?: ProviderOptions; +}; + +type GenerativeUIAction = Action; + +type GenerativeUIComponent<P = any> = { + description: string; + properties: ZodType<P>; + streamProperties: boolean | undefined; + render: (props: StreamingRenderProps<P>) => ReactNode; +} | { + description: string; + properties: ZodType<P>; + streamProperties?: false | undefined; + render: (props: StaticRenderProps<P>) => ReactNode; +}; + +type GenerativeUIDispatch = (action: Action) => unknown; + +type GenerativeUIElement = NormalizedUIElement; + +type GenerativeUILibrary = Record<string, GenerativeUIComponent>; + +type GenerativeUIMessagePart = { + readonly type: "generative-ui"; + readonly spec: GenerativeUISpec; + readonly id?: string; + readonly parentId?: string; +}; + +type GenerativeUINode = string | { + readonly component: string; + readonly props?: Record<string, unknown>; + readonly children?: readonly GenerativeUINode[]; + readonly key?: string; +}; + +type GenerativeUINode$1 = GenerativeUIElement | string | number | boolean | null | undefined | GenerativeUINode$1[]; + +type GenerativeUIProps = NormalizedUIElement["props"]; + +type GenerativeUIRenderContext = { + status: GenerativeUIStatus; + dispatch?: GenerativeUIDispatch; +}; + +type GenerativeUISpec = { + readonly root: GenerativeUINode | readonly GenerativeUINode[]; +}; + +type GenerativeUIStatus = "done" | "streaming"; + +type HumanTool<TArgs extends Record<string, unknown> = Record<string, unknown>, TResult = unknown> = ToolBase<TArgs, TResult> & { + type: "human"; + description?: string | undefined; + parameters: StandardSchemaV1<TArgs> | JSONSchema7; + disabled?: boolean; + display?: "standalone"; + execute?: undefined; + toModelOutput?: undefined; + experimental_onSchemaValidationError?: undefined; + providerOptions?: ProviderOptions; +}; + +declare const IMAGE_SIZE_TOKENS: readonly [ + "sm", + "md", + "lg" +]; + +type ImageMessagePart = { + readonly type: "image"; + readonly image: string; + readonly filename?: string; +}; + +type ImageSize = (typeof IMAGE_SIZE_TOKENS)[number] | number; + +declare class JSONGenerativeUI { + private readonly parameters; + constructor(options: JSONGenerativeUIOptions); + present(options?: PresentToolOptions): PresentTool; + promptUser(): PromptUserTool; +} + +declare class JSONGenerativeUI$1 { + private readonly library; + private readonly parameters; + private readonly actions; + constructor(options: JSONGenerativeUIOptions); + private readonly render; + present(options?: PresentToolOptions): PresentTool; + promptUser(): PromptUserTool; +} + +type JSONGenerativeUIOptions = { + library: GenerativeUILibrary; + actions?: ActionRegistry; +}; + +interface JSONSchema7 { + $id?: string | undefined; + $ref?: string | undefined; + $schema?: JSONSchema7Version | undefined; + $comment?: string | undefined; + $defs?: { + [key: string]: JSONSchema7Definition; + } | undefined; + type?: JSONSchema7TypeName | JSONSchema7TypeName[] | undefined; + enum?: JSONSchema7Type[] | undefined; + const?: JSONSchema7Type | undefined; + multipleOf?: number | undefined; + maximum?: number | undefined; + exclusiveMaximum?: number | undefined; + minimum?: number | undefined; + exclusiveMinimum?: number | undefined; + maxLength?: number | undefined; + minLength?: number | undefined; + pattern?: string | undefined; + items?: JSONSchema7Definition | JSONSchema7Definition[] | undefined; + additionalItems?: JSONSchema7Definition | undefined; + maxItems?: number | undefined; + minItems?: number | undefined; + uniqueItems?: boolean | undefined; + contains?: JSONSchema7Definition | undefined; + maxProperties?: number | undefined; + minProperties?: number | undefined; + required?: string[] | undefined; + properties?: { + [key: string]: JSONSchema7Definition; + } | undefined; + patternProperties?: { + [key: string]: JSONSchema7Definition; + } | undefined; + additionalProperties?: JSONSchema7Definition | undefined; + dependencies?: { + [key: string]: JSONSchema7Definition | string[]; + } | undefined; + propertyNames?: JSONSchema7Definition | undefined; + if?: JSONSchema7Definition | undefined; + then?: JSONSchema7Definition | undefined; + else?: JSONSchema7Definition | undefined; + allOf?: JSONSchema7Definition[] | undefined; + anyOf?: JSONSchema7Definition[] | undefined; + oneOf?: JSONSchema7Definition[] | undefined; + not?: JSONSchema7Definition | undefined; + format?: string | undefined; + contentMediaType?: string | undefined; + contentEncoding?: string | undefined; + definitions?: { + [key: string]: JSONSchema7Definition; + } | undefined; + title?: string | undefined; + description?: string | undefined; + default?: JSONSchema7Type | undefined; + readOnly?: boolean | undefined; + writeOnly?: boolean | undefined; + examples?: JSONSchema7Type | undefined; +} + +interface JSONSchema7$1 { + $id?: string | undefined; + $ref?: string | undefined; + $schema?: JSONSchema7Version$1 | undefined; + $comment?: string | undefined; + $defs?: { + [key: string]: JSONSchema7Definition$1; + } | undefined; + type?: JSONSchema7TypeName$1 | JSONSchema7TypeName$1[] | undefined; + enum?: JSONSchema7Type$1[] | undefined; + const?: JSONSchema7Type$1 | undefined; + multipleOf?: number | undefined; + maximum?: number | undefined; + exclusiveMaximum?: number | undefined; + minimum?: number | undefined; + exclusiveMinimum?: number | undefined; + maxLength?: number | undefined; + minLength?: number | undefined; + pattern?: string | undefined; + items?: JSONSchema7Definition$1 | JSONSchema7Definition$1[] | undefined; + additionalItems?: JSONSchema7Definition$1 | undefined; + maxItems?: number | undefined; + minItems?: number | undefined; + uniqueItems?: boolean | undefined; + contains?: JSONSchema7Definition$1 | undefined; + maxProperties?: number | undefined; + minProperties?: number | undefined; + required?: string[] | undefined; + properties?: { + [key: string]: JSONSchema7Definition$1; + } | undefined; + patternProperties?: { + [key: string]: JSONSchema7Definition$1; + } | undefined; + additionalProperties?: JSONSchema7Definition$1 | undefined; + dependencies?: { + [key: string]: JSONSchema7Definition$1 | string[]; + } | undefined; + propertyNames?: JSONSchema7Definition$1 | undefined; + if?: JSONSchema7Definition$1 | undefined; + then?: JSONSchema7Definition$1 | undefined; + else?: JSONSchema7Definition$1 | undefined; + allOf?: JSONSchema7Definition$1[] | undefined; + anyOf?: JSONSchema7Definition$1[] | undefined; + oneOf?: JSONSchema7Definition$1[] | undefined; + not?: JSONSchema7Definition$1 | undefined; + format?: string | undefined; + contentMediaType?: string | undefined; + contentEncoding?: string | undefined; + definitions?: { + [key: string]: JSONSchema7Definition$1; + } | undefined; + title?: string | undefined; + description?: string | undefined; + default?: JSONSchema7Type$1 | undefined; + readOnly?: boolean | undefined; + writeOnly?: boolean | undefined; + examples?: JSONSchema7Type$1 | undefined; +} + +interface JSONSchema7Array extends Array<JSONSchema7Type> { +} + +interface JSONSchema7Array$1 extends Array<JSONSchema7Type$1> { +} + +type JSONSchema7Definition = JSONSchema7 | boolean; + +type JSONSchema7Definition$1 = JSONSchema7$1 | boolean; + +interface JSONSchema7Object { + [key: string]: JSONSchema7Type; +} + +interface JSONSchema7Object$1 { + [key: string]: JSONSchema7Type$1; +} + +type JSONSchema7Type = string | number | boolean | JSONSchema7Object | JSONSchema7Array | null; + +type JSONSchema7Type$1 = string | number | boolean | JSONSchema7Object$1 | JSONSchema7Array$1 | null; + +type JSONSchema7TypeName = "array" | "boolean" | "integer" | "null" | "number" | "object" | "string"; + +type JSONSchema7TypeName$1 = "array" | "boolean" | "integer" | "null" | "number" | "object" | "string"; + +type JSONSchema7Version = string; + +type JSONSchema7Version$1 = string; + +declare const JUSTIFIES: readonly [ + "start", + "center", + "end", + "between" +]; + +type Justify = (typeof JUSTIFIES)[number]; + +interface LegacyComponentNode { + readonly component: string; + readonly props?: Record<string, unknown>; + readonly children?: UIChildren; + readonly key?: string; +} + +type McpAppMetadata = { + readonly resourceUri: string; + readonly mimeType?: string; + readonly visibility?: readonly ("app" | "model")[]; +}; + +type McpServerConfig = { + type: "http" | "sse"; + url: string; + headers?: Record<string, string>; + redirect?: "error" | "follow"; + connectionTimeout?: number | undefined; +} | { + type: "stdio"; + command: string; + args?: readonly string[]; + env?: Record<string, string>; + cwd?: string; + connectionTimeout?: number | undefined; +}; + +type McpTool = ToolBase<Record<string, unknown>, unknown> & { + type: "mcp"; + server: McpServerConfig; + description?: undefined; + parameters?: undefined; + disabled?: boolean; + execute?: undefined; + toModelOutput?: undefined; + experimental_onSchemaValidationError?: undefined; + providerOptions?: undefined; +}; + +type MessageCommonProps = { + readonly id: string; + readonly createdAt: Date; +}; + +type MessagePartState = (ThreadUserMessagePart | ThreadAssistantMessagePart) & { + readonly status: MessagePartStatus | ToolCallMessagePartStatus; +}; + +type MessagePartStatus = { + readonly type: "running"; +} | { + readonly type: "complete"; +} | { + readonly type: "incomplete"; + readonly reason: "cancelled" | "content-filter" | "error" | "length" | "other"; + readonly error?: unknown; +}; + +type MessageStatus = { + readonly type: "running"; +} | { + readonly type: "requires-action"; + readonly reason: "interrupt" | "tool-calls"; +} | { + readonly type: "complete"; + readonly reason: "stop" | "unknown"; +} | { + readonly type: "incomplete"; + readonly reason: "cancelled" | "content-filter" | "error" | "length" | "other" | "tool-calls"; + readonly error?: ReadonlyJSONValue; +}; + +type MessageTiming = { + readonly streamStartTime: number; + readonly firstTokenTime?: number; + readonly totalStreamTime?: number; + readonly tokenCount?: number; + readonly tokensPerSecond?: number; + readonly totalChunks: number; + readonly toolCallCount: number; +}; + +interface NormalizedUIElement { + readonly type: string; + readonly props: Readonly<Record<string, unknown>>; + readonly children?: NormalizedUINode | undefined; + readonly key?: string | number | undefined; + readonly action?: Action | undefined; +} + +type NormalizedUINode = string | number | readonly NormalizedUINode[] | NormalizedUIElement | null; + +type ObjectKey<T> = keyof T & (string | number); + +type OnSchemaValidationErrorFunction<TResult> = ToolExecuteFunction<unknown, TResult>; + +type ParentOf<K extends ClientNames> = AssistantClientAccessor<K> extends { + source: infer S; +} ? S extends ClientNames ? S : never : never; + +type PartProviderMetadata = { + readonly [providerName: string]: ReadonlyJSONObject; +}; + +type PresentTool = ToolDefinition<Record<string, unknown>, Record<string, never>> & BackendDefaultMetadata; + +type PresentToolOptions = { + display?: "standalone"; +}; + +type PromptUserTool = ToolDefinition<Record<string, unknown>, unknown> & BackendDefaultMetadata; + +type ProviderOptions = Record<string, Record<string, unknown>>; + +type ProviderTool<TArgs extends Record<string, unknown> = Record<string, unknown>, TResult = unknown> = ToolBase<TArgs, TResult> & { + type: "provider"; + providerId: `${string}.${string}`; + parameters?: StandardSchemaV1<TArgs> | JSONSchema7 | undefined; + args: Record<string, unknown>; + supportsDeferredResults?: boolean; + description?: undefined; + disabled?: boolean; + execute?: undefined; + toModelOutput?: undefined; + experimental_onSchemaValidationError?: undefined; + providerOptions?: ProviderOptions; +}; + +type ReadonlyJSONArray = readonly ReadonlyJSONValue[]; + +type ReadonlyJSONObject = { + readonly [key: string]: ReadonlyJSONValue; +}; + +type ReadonlyJSONValue = null | string | number | boolean | ReadonlyJSONObject | ReadonlyJSONArray; + +type ReasoningMessagePart = { + readonly type: "reasoning"; + readonly text: string; + readonly providerMetadata?: PartProviderMetadata; + readonly parentId?: string; +}; + +interface ScopeRegistry { + [key: string]: { methods: any; meta?: any; events?: any }; +} + +type SourceMessagePart = { + readonly type: "source"; + readonly sourceType: "url"; + readonly id: string; + readonly url: string; + readonly title?: string; + readonly providerMetadata?: SourceProviderMetadata; + readonly parentId?: string; +} | { + readonly type: "source"; + readonly sourceType: "document"; + readonly id: string; + readonly url?: undefined; + readonly title: string; + readonly mediaType: string; + readonly filename?: string; + readonly providerMetadata?: SourceProviderMetadata; + readonly parentId?: string; +}; + +type SourceProviderMetadata = PartProviderMetadata; + +interface SpeechRecognitionConstructor { + new (): SpeechRecognitionInstance; +} + +interface SpeechRecognitionInstance extends EventTarget { + lang: string; + continuous: boolean; + interimResults: boolean; + start(): void; + stop(): void; + abort(): void; +} + +type StaticRenderProps<P> = P & { + children?: ReactNode; + $status: "done"; + $action?: Action; + $dispatch?: GenerativeUIDispatch; +}; + +type StreamingRenderProps<P> = (Partial<P> & { + children?: ReactNode; + $status: "streaming"; + $action?: Action; + $dispatch?: GenerativeUIDispatch; +}) | (P & { + children?: ReactNode; + $status: "done"; + $action?: Action; + $dispatch?: GenerativeUIDispatch; +}); + +declare const TEXT_SIZES: readonly [ + "sm", + "md", + "lg", + "xl", + "2xl", + "3xl" +]; + +declare const TOOL_RESPONSE_SYMBOL: unique symbol; + +declare const TYPE_KEY = "$type"; + +type TextMessagePart = { + readonly type: "text"; + readonly text: string; + readonly providerMetadata?: PartProviderMetadata; + readonly parentId?: string; +}; + +type TextSize = (typeof TEXT_SIZES)[number]; + +type ThreadAssistantMessage = MessageCommonProps & { + readonly role: "assistant"; + readonly content: readonly ThreadAssistantMessagePart[]; + readonly status: MessageStatus; + readonly metadata: { + readonly unstable_state: ReadonlyJSONValue; + readonly unstable_annotations: readonly ReadonlyJSONValue[]; + readonly unstable_data: readonly ReadonlyJSONValue[]; + readonly steps: readonly ThreadStep[]; + readonly submittedFeedback?: { + readonly type: "negative" | "positive"; + }; + readonly timing?: MessageTiming; + readonly isOptimistic?: boolean; + readonly custom: Record<string, unknown>; + }; +}; + +type ThreadAssistantMessagePart = TextMessagePart | ReasoningMessagePart | ToolCallMessagePart | SourceMessagePart | FileMessagePart | ImageMessagePart | DataMessagePart | GenerativeUIMessagePart; + +type ThreadMessage = BaseThreadMessage & (ThreadSystemMessage | ThreadUserMessage | ThreadAssistantMessage); + +type ThreadStep = { + readonly messageId?: string; + readonly usage?: { + readonly inputTokens: number; + readonly outputTokens: number; + } | undefined; +}; + +type ThreadSystemMessage = MessageCommonProps & { + readonly role: "system"; + readonly content: readonly [ + TextMessagePart + ]; + readonly metadata: { + readonly unstable_state?: undefined; + readonly unstable_annotations?: undefined; + readonly unstable_data?: undefined; + readonly steps?: undefined; + readonly submittedFeedback?: undefined; + readonly timing?: undefined; + readonly custom: Record<string, unknown>; + }; +}; + +type ThreadUserMessage = MessageCommonProps & { + readonly role: "user"; + readonly content: readonly ThreadUserMessagePart[]; + readonly attachments: readonly CompleteAttachment[]; + readonly metadata: { + readonly unstable_state?: undefined; + readonly unstable_annotations?: undefined; + readonly unstable_data?: undefined; + readonly steps?: undefined; + readonly submittedFeedback?: undefined; + readonly timing?: undefined; + readonly custom: Record<string, unknown>; + }; +}; + +type ThreadUserMessagePart = TextMessagePart | ImageMessagePart | FileMessagePart | DataMessagePart | Unstable_AudioMessagePart; + +type Tool<TArgs extends Record<string, unknown> = Record<string, unknown>, TResult = unknown> = FrontendTool<TArgs, TResult> | BackendTool<TArgs, TResult> | HumanTool<TArgs, TResult> | ProviderTool<TArgs, TResult> | McpTool | ToolWithoutType<TArgs, TResult>; + +type ToolApprovalOption = { + readonly id: string; + readonly kind: ToolApprovalOptionKind | (string & {}); + readonly label?: string; + readonly description?: string; + readonly grants?: readonly string[]; + readonly confirm?: boolean | { + title?: string; + description?: string; + }; +}; + +type ToolApprovalOptionKind = "allow-always" | "allow-once" | "reject-always" | "reject-once"; + +type ToolApprovalResponse = { + readonly approved: boolean; + readonly reason?: string; +} | { + readonly optionId: string; + readonly reason?: string; +} | { + readonly approved: boolean; + readonly optionId: string; + readonly reason?: string; +}; + +type ToolBase<TArgs extends Record<string, unknown> = Record<string, unknown>, TResult = unknown> = { + streamCall?: ToolStreamCallFunction<TArgs, TResult>; + display?: ToolDisplay; +}; + +interface ToolCallArgsReader<TArgs extends Record<string, unknown>> { + get<PathT extends TypePath<TArgs>>(...fieldPath: PathT): Promise<TypeAtPath<TArgs, PathT>>; + streamValues<PathT extends TypePath<TArgs>>(...fieldPath: PathT): AsyncIterableStream<DeepPartial<TypeAtPath<TArgs, PathT>>>; + streamText<PathT extends TypePath<TArgs>>(...fieldPath: PathT): TypeAtPath<TArgs, PathT> extends string & (infer U) ? AsyncIterableStream<U> : never; + forEach<PathT extends TypePath<TArgs>>(...fieldPath: PathT): NonNullable<TypeAtPath<TArgs, PathT>> extends Array<infer U> ? AsyncIterableStream<U> : never; +} + +type ToolCallCompleteText<TArgs extends Record<string, unknown>, TResult> = ReactNode | ((options: { + args: TArgs; + result: TResult | undefined; +}) => ReactNode); + +type ToolCallMessagePart<TArgs = ReadonlyJSONObject, TResult = unknown> = { + readonly type: "tool-call"; + readonly toolCallId: string; + readonly toolName: string; + readonly args: TArgs; + readonly result?: TResult | undefined; + readonly isError?: boolean | undefined; + readonly argsText: string; + readonly artifact?: unknown; + readonly timing?: ToolCallTiming; + readonly mcp?: ToolCallMessagePartMcpMetadata; + readonly providerMetadata?: PartProviderMetadata; + readonly modelContent?: readonly ToolModelContentPart[] | undefined; + readonly interrupt?: { + type: "human"; + payload: unknown; + }; + readonly approval?: { + readonly id: string; + readonly approved?: boolean; + readonly reason?: string; + readonly isAutomatic?: boolean; + readonly options?: readonly ToolApprovalOption[]; + readonly optionId?: string; + readonly resolution?: "cancelled" | "expired"; + }; + readonly parentId?: string; + readonly messages?: readonly ThreadMessage[]; +}; + +type ToolCallMessagePartComponent<TArgs = any, TResult = any> = ComponentType<ToolCallMessagePartProps<TArgs, TResult>>; + +type ToolCallMessagePartMcpMetadata = { + readonly app?: McpAppMetadata; +}; + +type ToolCallMessagePartProps<TArgs = any, TResult = unknown> = MessagePartState & ToolCallMessagePart<TArgs, TResult> & { + addResult: (result: TResult | ToolResponse<TResult>) => void; + resume: (payload: unknown) => void; + respondToApproval: (response: ToolApprovalResponse) => void; +}; + +type ToolCallMessagePartStatus = { + readonly type: "requires-action"; + readonly reason: "interrupt"; +} | MessagePartStatus; + +interface ToolCallReader<TArgs extends Record<string, unknown> = Record<string, unknown>, TResult = unknown> { + args: ToolCallArgsReader<TArgs>; + response: ToolCallResponseReader<TResult>; + result: { + get: () => Promise<TResult>; + }; +} + +interface ToolCallResponseReader<TResult> { + get: () => Promise<ToolResponse<TResult>>; +} + +type ToolCallRunningText<TArgs extends Record<string, unknown>> = ReactNode | ((options: { + args: TArgs; +}) => ReactNode); + +type ToolCallText<TArgs extends Record<string, unknown>, TResult> = { + running: ToolCallRunningText<TArgs>; + complete?: ToolCallCompleteText<TArgs, TResult> | undefined; +} | { + running?: ToolCallRunningText<TArgs> | undefined; + complete: ToolCallCompleteText<TArgs, TResult>; +}; + +type ToolCallTiming = { + readonly startedAt: number; + readonly completedAt?: number; +}; + +type ToolDefinition<TArgs extends Record<string, unknown> = Record<string, unknown>, TResult = unknown> = WithRender<Tool<TArgs, TResult>, TArgs, TResult>; + +type ToolDisplay = "inline" | "standalone"; + +type ToolExecuteFunction<TArgs, TResult> = (args: TArgs, context: ToolExecutionContext) => TResult | Promise<TResult>; + +type ToolExecutionContext = { + toolCallId: string; + abortSignal: AbortSignal; + human: (payload: unknown) => Promise<unknown>; +}; + +type ToolModelContentPart = { + readonly type: "text"; + readonly text: string; +} | { + readonly type: "file"; + readonly data: string; + readonly mediaType: string; + readonly filename?: string; +}; + +type ToolModelOutputFunction<TArgs, TResult> = (options: { + toolCallId: string; + input: TArgs; + output: TResult; +}) => readonly ToolModelContentPart[] | Promise<readonly ToolModelContentPart[]>; + +declare class ToolResponse<TResult> { + get [TOOL_RESPONSE_SYMBOL](): boolean; + readonly artifact?: ReadonlyJSONValue; + readonly result: TResult; + readonly isError: boolean; + readonly modelContent?: readonly ToolModelContentPart[]; + readonly messages?: ReadonlyJSONValue; + constructor(options: ToolResponseLike<TResult>); + static [Symbol.hasInstance](obj: unknown): obj is ToolResponse<ReadonlyJSONValue>; + static toResponse(result: any | ToolResponse<any>): ToolResponse<any>; +} + +type ToolResponseLike<TResult> = { + result: TResult; + artifact?: ReadonlyJSONValue | undefined; + isError?: boolean | undefined; + modelContent?: readonly ToolModelContentPart[] | undefined; + messages?: ReadonlyJSONValue | undefined; +}; + +type ToolStreamCallFunction<TArgs extends Record<string, unknown> = Record<string, unknown>, TResult = unknown> = (reader: ToolCallReader<TArgs, TResult>, context: ToolExecutionContext) => void; + +type ToolWithoutType<TArgs extends Record<string, unknown> = Record<string, unknown>, TResult = unknown> = (Omit<FrontendTool<TArgs, TResult>, "type"> | Omit<BackendTool<TArgs, TResult>, "type"> | Omit<HumanTool<TArgs, TResult>, "type"> | Omit<ProviderTool<TArgs, TResult>, "type">) & { + type?: undefined; +}; + +type TupleIndex<T extends readonly any[]> = Exclude<keyof T, keyof any[]>; + +type TypeAtPath<T, P extends readonly any[]> = P extends [ + infer Head, + ...infer Rest +] ? Head extends keyof T ? TypeAtPath<T[Head], Rest> : never : T; + +type TypePath<T> = [ +] | (0 extends 1 & T ? any[] : T extends object ? T extends readonly any[] ? number extends T["length"] ? { + [K in TupleIndex<T>]: [ + AsNumber<K>, + ...TypePath<T[K]> + ]; +}[TupleIndex<T>] : [ + number, + ...TypePath<T[number]> +] : { + [K in ObjectKey<T>]: [ + K, + ...TypePath<T[K]> + ]; +}[ObjectKey<T>] : [ +]); + +type UIChildren = UINode | readonly UINode[]; + +interface UIElement { + readonly $type: string; + readonly $key?: string | number; + readonly children?: UIChildren; + readonly $action?: Action; + readonly [prop: string]: unknown; +} + +type UINode = string | number | UIElement | LegacyComponentNode; + +type UISpec = UINode | readonly UINode[]; + +type UnionToIntersection<U> = (U extends unknown ? (x: U) => void : never) extends ((x: infer I) => void) ? I : never; + +type Unstable_AudioMessagePart = { + readonly type: "audio"; + readonly audio: { + readonly data: string; + readonly format: "mp3" | "wav"; + }; +}; + +type Unsubscribe = () => void; + +type ValidateClient<K extends keyof ScopeRegistry> = ScopeRegistry[K] extends { + methods: ClientMethods; +} ? "meta" extends keyof ScopeRegistry[K] ? ScopeRegistry[K]["meta"] extends ClientMetaType ? "events" extends keyof ScopeRegistry[K] ? ScopeRegistry[K]["events"] extends ClientEventsType<K> ? ScopeRegistry[K] : ClientError<`ERROR: ${K & string} has invalid events type`> : ScopeRegistry[K] : ClientError<`ERROR: ${K & string} has invalid meta type`> : "events" extends keyof ScopeRegistry[K] ? ScopeRegistry[K]["events"] extends ClientEventsType<K> ? ScopeRegistry[K] : ClientError<`ERROR: ${K & string} has invalid events type`> : ScopeRegistry[K] : ClientError<`ERROR: ${K & string} has invalid methods type`>; + +declare const WEIGHTS: readonly [ + "normal", + "medium", + "semibold", + "bold" +]; + +type Weight = (typeof WEIGHTS)[number]; + +type WildcardPayload = { + [K in keyof ClientEventMap]: { + event: K; + payload: ClientEventMap[K]; + }; +}[Extract<keyof ClientEventMap, string>]; + +type WithRender<T, TArgs extends Record<string, unknown>, TResult> = T extends { + type: "frontend" | "human"; +} ? T & (T extends { + type: "frontend"; +} ? { + render: ToolCallMessagePartComponent<TArgs, TResult>; +} | { + render?: ToolCallMessagePartComponent<TArgs, TResult>; + renderText: ToolCallText<TArgs, TResult>; +} : { + render: ToolCallMessagePartComponent<TArgs, TResult>; +}) : T & { + render?: ToolCallMessagePartComponent<TArgs, TResult> | undefined; + renderText?: ToolCallText<TArgs, TResult> | undefined; +}; + +declare function buildPresentParameters(library: GenerativeUILibrary): JSONSchema7$1; + +declare function createActionRegistry(handlers: Readonly<Record<string, ActionHandler>>): ActionRegistry; + +declare const defaultGenerativeUILibrary: GenerativeUILibrary; + +declare function defineGenerativeComponents(_library: GenerativeUILibrary): GenerativeUILibrary; + +declare const emptyActionRegistry: ActionRegistry; + +declare function generativeUIToJSX(node: unknown): string; + +declare global { + interface Window { + SpeechRecognition?: SpeechRecognitionConstructor; + webkitSpeechRecognition?: SpeechRecognitionConstructor; + } +} + +declare global { + interface Window { + __ASSISTANT_UI_DEVTOOLS_HOOK__?: any; + } +} + +declare namespace entry_root_default_exports { + export { ALERT_TONES, ALIGNS, Action, ActionDispatchContext, ActionHandler, ActionRegistry, AlertTone, Align, BUTTON_STYLES, ButtonStyle, COLORS, Color, GenerativeUIAction, GenerativeUIComponent, GenerativeUIDispatch, GenerativeUIElement, GenerativeUILibrary, GenerativeUINode$1 as GenerativeUINode, GenerativeUIProps, GenerativeUIRenderContext, GenerativeUIStatus, IMAGE_SIZE_TOKENS, ImageSize, JSONGenerativeUI$1 as JSONGenerativeUI, JSONGenerativeUIOptions, JUSTIFIES, Justify, LegacyComponentNode, NormalizedUIElement, NormalizedUINode, PresentTool, PresentToolOptions, PromptUserTool, TEXT_SIZES, TYPE_KEY, TextSize, UIChildren, UIElement, UINode, UISpec, WEIGHTS, Weight, buildPresentParameters, createActionRegistry, defaultGenerativeUILibrary, defineGenerativeComponents, emptyActionRegistry, generativeUIToJSX, normalizeSpec, normalizeUINode, renderGenerativeUI }; +} + +declare namespace entry_root_react_server_exports { + export { ALERT_TONES, ALIGNS, Action, ActionDispatchContext, ActionHandler, ActionRegistry, AlertTone, Align, BUTTON_STYLES, ButtonStyle, COLORS, Color, GenerativeUIAction, GenerativeUIComponent, GenerativeUIDispatch, GenerativeUIElement, GenerativeUILibrary, GenerativeUINode$1 as GenerativeUINode, GenerativeUIProps, GenerativeUIRenderContext, GenerativeUIStatus, IMAGE_SIZE_TOKENS, ImageSize, JSONGenerativeUI, JSONGenerativeUIOptions, JUSTIFIES, Justify, LegacyComponentNode, NormalizedUIElement, NormalizedUINode, PresentTool, PresentToolOptions, PromptUserTool, TEXT_SIZES, TYPE_KEY, TextSize, UIChildren, UIElement, UINode, UISpec, WEIGHTS, Weight, buildPresentParameters, createActionRegistry, defaultGenerativeUILibrary, defineGenerativeComponents, emptyActionRegistry, generativeUIToJSX, normalizeSpec, normalizeUINode, renderGenerativeUI }; +} + +declare namespace entry_ir_exports { + export { ALERT_TONES, ALIGNS, Action, AlertTone, Align, BUTTON_STYLES, ButtonStyle, COLORS, Color, IMAGE_SIZE_TOKENS, ImageSize, JUSTIFIES, Justify, LegacyComponentNode, NormalizedUIElement, NormalizedUINode, TEXT_SIZES, TextSize, UIChildren, UIElement, UINode, UISpec, WEIGHTS, Weight, normalizeSpec, normalizeUINode }; +} + +declare function normalizeSpec(spec: UISpec): { + readonly root: NormalizedUINode | readonly NormalizedUINode[]; +}; + +declare function normalizeUINode(node: unknown, partialPath?: readonly string[] | undefined, depth?: number): NormalizedUINode; + +declare function renderGenerativeUI(node: unknown, library: GenerativeUILibrary, context?: GenerativeUIRenderContext): ReactNode; + +export { entry_ir_exports as entry_ir, entry_root_default_exports as entry_root_default, entry_root_react_server_exports as entry_root_react_server }; diff --git a/api-surface/assistant-ui__react-google-adk.ts b/api-surface/assistant-ui__react-google-adk.ts new file mode 100644 index 0000000..6f4515a --- /dev/null +++ b/api-surface/assistant-ui__react-google-adk.ts @@ -0,0 +1,2389 @@ +import { StandardSchemaV1 } from "@standard-schema/spec"; + +import { ComponentType, PropsWithChildren } from "react"; + +type AddToolResultOptions = { + messageId: string; + toolName: string; + toolCallId: string; + result: ReadonlyJSONValue; + isError: boolean; + artifact?: ReadonlyJSONValue | undefined; + modelContent?: readonly ToolModelContentPart[] | undefined; +}; + +type AdkArtifactData = { + inlineData?: { + mimeType: string; + data: string; + } | undefined; + text?: string | undefined; +}; + +type AdkAuthCredential = { + authType: AdkAuthCredentialType; + resourceRef?: string | undefined; + apiKey?: string | undefined; + http?: { + scheme: string; + credentials: { + username?: string; + password?: string; + token?: string; + }; + } | undefined; + oauth2?: { + clientId?: string; + clientSecret?: string; + authUri?: string; + state?: string; + redirectUri?: string; + authResponseUri?: string; + authCode?: string; + accessToken?: string; + refreshToken?: string; + expiresAt?: number; + expiresIn?: number; + } | undefined; + serviceAccount?: unknown; +}; + +type AdkAuthCredentialType = "apiKey" | "http" | "oauth2" | "openIdConnect" | "serviceAccount"; + +type AdkAuthRequest = { + toolCallId: string; + authConfig: unknown; +}; + +type AdkEvent = { + id: string; + invocationId?: string | undefined; + author?: string | undefined; + branch?: string | undefined; + partial?: boolean | undefined; + turnComplete?: boolean | undefined; + interrupted?: boolean | undefined; + finishReason?: string | undefined; + timestamp?: number | undefined; + content?: { + role?: string | undefined; + parts?: AdkEventPart[] | undefined; + } | undefined; + actions?: AdkEventActions | undefined; + longRunningToolIds?: string[] | undefined; + errorCode?: string | undefined; + errorMessage?: string | undefined; + groundingMetadata?: unknown; + citationMetadata?: unknown; + usageMetadata?: unknown; + customMetadata?: Record<string, unknown> | undefined; +}; + +declare class AdkEventAccumulator { + private messagesMap; + private currentMessageId; + private partialTextBuffer; + private partialReasoningBuffer; + private accumulatedStateDelta; + private accumulatedArtifactDelta; + private lastAgentInfo; + private lastTransferToAgent; + private pendingLongRunningToolIds; + private toolConfirmations; + private authRequests; + private escalated; + private messageMetadataMap; + constructor(initialMessages?: AdkMessage[]); + processEvent(rawEvent: AdkEvent): AdkMessage[]; + private processPart; + private trackMessageMetadata; + private getContentArray; + private getOrCreateAiMessage; + private appendContent; + private replaceLastTextContent; + private replaceLastReasoningContent; + private finalizeCurrentMessage; + getMessages(): AdkMessage[]; + getStateDelta(): Record<string, unknown>; + getArtifactDelta(): Record<string, number>; + getAgentInfo(): { + name?: string | undefined; + branch?: string | undefined; + }; + getLastTransferToAgent(): string | undefined; + getLongRunningToolIds(): string[]; + getToolConfirmations(): AdkToolConfirmation[]; + getAuthRequests(): AdkAuthRequest[]; + isEscalated(): boolean; + getMessageMetadata(): Map<string, AdkMessageMetadata>; +} + +type AdkEventActions = { + stateDelta?: Record<string, unknown> | undefined; + artifactDelta?: Record<string, number> | undefined; + transferToAgent?: string | undefined; + escalate?: boolean | undefined; + skipSummarization?: boolean | undefined; + requestedAuthConfigs?: Record<string, unknown> | undefined; + requestedToolConfirmations?: Record<string, unknown> | undefined; +}; + +type AdkEventPart = { + text?: string; + thought?: boolean; + functionCall?: { + name: string; + id?: string; + args: Record<string, unknown>; + }; + functionResponse?: { + name: string; + id?: string; + response: unknown; + }; + executableCode?: { + code: string; + language?: string; + }; + codeExecutionResult?: { + output: string; + outcome?: string; + }; + inlineData?: { + mimeType: string; + data: string; + }; + fileData?: { + fileUri: string; + mimeType?: string; + }; +}; + +type AdkEventStreamOptions = { + onError?: (error: unknown) => void; +}; + +declare const AdkEventType: { + readonly THOUGHT: "thought"; + readonly CONTENT: "content"; + readonly TOOL_CALL: "tool_call"; + readonly TOOL_RESULT: "tool_result"; + readonly CALL_CODE: "call_code"; + readonly CODE_RESULT: "code_result"; + readonly ERROR: "error"; + readonly ACTIVITY: "activity"; + readonly TOOL_CONFIRMATION: "tool_confirmation"; + readonly FINISHED: "finished"; +}; + +type AdkMessage = { + id: string; + type: "human"; + content: string | AdkMessageContentPart[]; +} | { + id: string; + type: "ai"; + content: string | AdkMessageContentPart[]; + tool_calls?: AdkToolCall[] | undefined; + author?: string | undefined; + branch?: string | undefined; + status?: MessageStatus | undefined; +} | { + id: string; + type: "tool"; + content: string; + tool_call_id: string; + name: string; + status?: "success" | "error" | undefined; + artifact?: unknown; +}; + +type AdkMessageContentPart = { + type: "text"; + text: string; +} | { + type: "reasoning"; + text: string; +} | { + type: "image"; + mimeType: string; + data: string; +} | { + type: "image_url"; + url: string; +} | { + type: "file"; + mimeType: string; + data: string; + filename?: string | undefined; +} | { + type: "file_url"; + url: string; + mimeType?: string | undefined; +} | { + type: "code"; + code: string; + language: string; +} | { + type: "code_result"; + output: string; + outcome: string; +} | { + type: "activity"; + message: string; +}; + +type AdkMessageMetadata = { + groundingMetadata?: unknown; + citationMetadata?: unknown; + usageMetadata?: unknown; +}; + +type AdkRunConfig = { + streamingMode?: "none" | "sse" | "bidi" | undefined; + pauseOnToolCalls?: boolean | undefined; + maxLlmCalls?: number | undefined; + saveInputBlobsAsArtifacts?: boolean | undefined; + supportCfc?: boolean | undefined; + speechConfig?: unknown; + responseModalities?: string[] | undefined; + outputAudioTranscription?: unknown; + inputAudioTranscription?: unknown; + enableAffectiveDialog?: boolean | undefined; + proactivity?: unknown; + realtimeInputConfig?: unknown; +}; + +type AdkRunner = { + runAsync(options: Record<string, unknown>): AsyncGenerator<any, void, undefined>; +}; + +type AdkSdkEvent = { + id?: string; + invocationId?: string; + author?: string; + branch?: string; + partial?: boolean; + turnComplete?: boolean; + interrupted?: boolean; + finishReason?: string; + timestamp?: number; + content?: { + role?: string; + parts?: Array<Record<string, unknown>>; + }; + actions?: Record<string, unknown>; + longRunningToolIds?: string[]; + errorCode?: string; + errorMessage?: string; + groundingMetadata?: unknown; + citationMetadata?: unknown; + usageMetadata?: unknown; + customMetadata?: Record<string, unknown>; +}; + +type AdkSendMessageConfig = { + runConfig?: unknown; + checkpointId?: string | undefined; + stateDelta?: Record<string, unknown> | undefined; +}; + +type AdkSessionAdapterOptions = { + apiUrl: string; + appName: string; + userId: string; + headers?: Record<string, string> | (() => Record<string, string> | Promise<Record<string, string>>) | undefined; +}; + +type AdkSessionAdapterResult = { + adapter: RemoteThreadListAdapter; + load: (sessionId: string) => Promise<{ + messages: AdkMessage[]; + }>; + artifacts: { + list: (sessionId: string) => Promise<string[]>; + load: (sessionId: string, artifactName: string, version?: number) => Promise<AdkArtifactData>; + listVersions: (sessionId: string, artifactName: string) => Promise<number[]>; + delete: (sessionId: string, artifactName: string) => Promise<void>; + }; +}; + +type AdkStreamCallback = (messages: AdkMessage[], config: AdkSendMessageConfig & { + abortSignal: AbortSignal; + initialize: () => Promise<{ + remoteId: string; + externalId: string | undefined; + }>; +}) => Promise<AsyncGenerator<AdkEvent>> | AsyncGenerator<AdkEvent>; + +type AdkStructuredEvent = { + type: "thought"; + content: string; +} | { + type: "content"; + content: string; +} | { + type: "tool_call"; + call: { + name: string; + id?: string; + args: Record<string, unknown>; + }; +} | { + type: "tool_result"; + result: { + name: string; + id?: string; + response: unknown; + }; +} | { + type: "call_code"; + code: { + code: string; + language?: string; + }; +} | { + type: "code_result"; + result: { + output: string; + outcome?: string; + }; +} | { + type: "error"; + errorCode?: string; + errorMessage?: string; +} | { + type: "activity"; + message: string; +} | { + type: "tool_confirmation"; + confirmations: Record<string, unknown>; +} | { + type: "finished"; +}; + +type AdkToolCall = { + id: string; + name: string; + args: ReadonlyJSONObject; + argsText?: string; +}; + +type AdkToolConfirmation = { + toolCallId: string; + toolName: string; + args: Record<string, unknown>; + hint: string; + confirmed: boolean; + payload?: unknown; +}; + +type AppendMessage = Omit<ThreadMessage, "id"> & { + parentId: string | null; + sourceId: string | null; + runConfig: RunConfig | undefined; + startRun?: boolean | undefined; + steer?: boolean | undefined; +}; + +type AsNumber<K> = K extends `${infer N extends number}` ? N | K : never; + +declare class AssistantCloud { + readonly threads: AssistantCloudThreads; + readonly auth: { + tokens: AssistantCloudAuthTokens; + }; + readonly runs: AssistantCloudRuns; + readonly files: AssistantCloudFiles; + readonly telemetry: AssistantCloudTelemetryConfig; + constructor(config: AssistantCloudConfig); +} + +declare class AssistantCloudAPI { + _auth: AssistantCloudAuthStrategy; + _baseUrl: string; + constructor(config: AssistantCloudConfig); + initializeAuth(): Promise<boolean>; + makeRawRequest(endpoint: string, options?: MakeRequestOptions): Promise<Response>; + makeRequest(endpoint: string, options?: MakeRequestOptions): Promise<any>; +} + +type AssistantCloudAuthStrategy = { + readonly strategy: "anon" | "api-key" | "jwt"; + getAuthHeaders(): Promise<Record<string, string> | false>; + readAuthHeaders(headers: Headers): void; +}; + +declare class AssistantCloudAuthTokens { + private cloud; + constructor(cloud: AssistantCloudAPI); + create(): Promise<AssistantCloudAuthTokensCreateResponse>; +} + +type AssistantCloudAuthTokensCreateResponse = { + token: string; +}; + +type AssistantCloudConfig = ({ + baseUrl: string; + authToken: () => Promise<string | null>; +} | { + baseUrl?: string; + apiKey: string; + userId: string; + workspaceId: string; +} | { + baseUrl: string; + anonymous: true; +}) & { + telemetry?: boolean | AssistantCloudTelemetryConfig; +}; + +declare class AssistantCloudFiles { + private cloud; + constructor(cloud: AssistantCloudAPI); + pdfToImages(body: PdfToImagesRequestBody): Promise<PdfToImagesResponse>; + generatePresignedUploadUrl(body: GeneratePresignedUploadUrlRequestBody): Promise<GeneratePresignedUploadUrlResponse>; +} + +type AssistantCloudMessageCreateResponse = { + message_id: string; +}; + +type AssistantCloudRunReport = { + thread_id: string; + status: "completed" | "error" | "incomplete"; + total_steps?: number; + tool_calls?: ReportToolCall[]; + steps?: { + input_tokens?: number; + output_tokens?: number; + reasoning_tokens?: number; + cached_input_tokens?: number; + tool_calls?: ReportToolCall[]; + start_ms?: number; + end_ms?: number; + }[]; + input_tokens?: number; + output_tokens?: number; + reasoning_tokens?: number; + cached_input_tokens?: number; + model_id?: string; + provider_type?: string; + duration_ms?: number; + output_text?: string; + metadata?: Record<string, unknown>; +}; + +declare class AssistantCloudRuns { + private cloud; + constructor(cloud: AssistantCloudAPI); + __internal_getAssistantOptions(assistantId: string): { + api: string; + headers: () => Promise<{ + Accept: string; + }>; + body: { + assistant_id: string; + response_format: string; + thread_id: string; + }; + }; + stream(body: AssistantCloudRunsStreamBody): Promise<AssistantStream>; + report(body: AssistantCloudRunReport): Promise<{ + run_id: string; + }>; +} + +type AssistantCloudRunsStreamBody = { + thread_id: string; + assistant_id: "system/thread_title"; + messages: readonly unknown[]; +}; + +type AssistantCloudTelemetryConfig = { + enabled?: boolean; + beforeReport?: (report: AssistantCloudRunReport) => AssistantCloudRunReport | null; +}; + +type AssistantCloudThreadMessageCreateBody = { + parent_id: string | null; + format: "aui/v0" | string; + content: ReadonlyJSONObject; +}; + +type AssistantCloudThreadMessageListQuery = { + format?: string; +}; + +type AssistantCloudThreadMessageListResponse = { + messages: CloudMessage[]; +}; + +type AssistantCloudThreadMessageUpdateBody = { + content: ReadonlyJSONObject; +}; + +declare class AssistantCloudThreadMessages { + private cloud; + constructor(cloud: AssistantCloudAPI); + list(threadId: string, query?: AssistantCloudThreadMessageListQuery): Promise<AssistantCloudThreadMessageListResponse>; + create(threadId: string, body: AssistantCloudThreadMessageCreateBody): Promise<AssistantCloudMessageCreateResponse>; + update(threadId: string, messageId: string, body: AssistantCloudThreadMessageUpdateBody): Promise<void>; +} + +declare class AssistantCloudThreads { + private cloud; + readonly messages: AssistantCloudThreadMessages; + constructor(cloud: AssistantCloudAPI); + list(query?: AssistantCloudThreadsListQuery): Promise<AssistantCloudThreadsListResponse>; + get(threadId: string): Promise<CloudThread>; + create(body: AssistantCloudThreadsCreateBody): Promise<AssistantCloudThreadsCreateResponse>; + update(threadId: string, body: AssistantCloudThreadsUpdateBody): Promise<void>; + delete(threadId: string): Promise<void>; +} + +type AssistantCloudThreadsCreateBody = { + title?: string | undefined; + last_message_at: Date; + metadata?: unknown | undefined; + external_id?: string | undefined; +}; + +type AssistantCloudThreadsCreateResponse = { + thread_id: string; +}; + +type AssistantCloudThreadsListQuery = { + is_archived?: boolean; + limit?: number; + after?: string; +}; + +type AssistantCloudThreadsListResponse = { + threads: CloudThread[]; +}; + +type AssistantCloudThreadsUpdateBody = { + title?: string | undefined; + last_message_at?: Date | undefined; + metadata?: unknown | undefined; + is_archived?: boolean | undefined; +}; + +type AssistantRuntime = { + readonly threads: ThreadListRuntime; + readonly thread: ThreadRuntime; + registerModelContextProvider(provider: ModelContextProvider): Unsubscribe; +}; + +type AssistantStream = ReadableStream<AssistantStreamChunk>; + +declare const AssistantStream: { + toResponse(stream: AssistantStream, transformer: AssistantStreamEncoder): Response; + fromResponse(response: Response, transformer: ReadableWritablePair<AssistantStreamChunk, Uint8Array<ArrayBuffer>>): ReadableStream<AssistantStreamChunk>; + toByteStream(stream: AssistantStream, transformer: ReadableWritablePair<Uint8Array<ArrayBuffer>, AssistantStreamChunk>): ReadableStream<Uint8Array<ArrayBuffer>>; + fromByteStream(readable: ReadableStream<Uint8Array<ArrayBuffer>>, transformer: ReadableWritablePair<AssistantStreamChunk, Uint8Array<ArrayBuffer>>): ReadableStream<AssistantStreamChunk>; +}; + +type AssistantStreamChunk = { + readonly path: readonly number[]; +} & ({ + readonly type: "part-start"; + readonly part: PartInit; +} | { + readonly type: "part-finish"; +} | { + readonly type: "tool-call-args-text-finish"; +} | { + readonly type: "text-delta"; + readonly textDelta: string; +} | { + readonly type: "annotations"; + readonly annotations: ReadonlyJSONValue[]; +} | { + readonly type: "data"; + readonly data: ReadonlyJSONValue[]; +} | { + readonly type: "step-start"; + readonly messageId: string; +} | { + readonly type: "step-finish"; + readonly finishReason: "content-filter" | "error" | "length" | "other" | "stop" | "tool-calls" | "unknown"; + readonly usage: { + readonly inputTokens: number; + readonly outputTokens: number; + }; + readonly isContinued: boolean; +} | { + readonly type: "message-finish"; + readonly finishReason: "content-filter" | "error" | "length" | "other" | "stop" | "tool-calls" | "unknown"; + readonly usage: { + readonly inputTokens: number; + readonly outputTokens: number; + }; +} | { + readonly type: "result"; + readonly artifact?: ReadonlyJSONValue; + readonly result: ReadonlyJSONValue; + readonly isError: boolean; + readonly modelContent?: readonly ToolModelContentPart[]; + readonly messages?: ReadonlyJSONValue; +} | { + readonly type: "error"; + readonly error: string; +} | { + readonly type: "update-state"; + readonly operations: ObjectStreamOperation[]; +}); + +type AssistantStreamEncoder = ReadableWritablePair<Uint8Array<ArrayBuffer>, AssistantStreamChunk> & { + headers?: Headers; +}; + +type AsyncIterableStream<T> = AsyncIterable<T> & ReadableStream<T>; + +type Attachment = PendingAttachment | CompleteAttachment; + +type AttachmentAdapter = { + accept: string; + add(state: { + file: File; + }): Promise<PendingAttachment> | AsyncGenerator<PendingAttachment, void>; + remove(attachment: Attachment): Promise<void>; + send(attachment: PendingAttachment): Promise<CompleteAttachment>; +}; + +type AttachmentAddErrorEvent = { + readonly reason: AttachmentAddErrorReason; + readonly message: string; + readonly attachmentId?: string; + readonly error?: Error; +}; + +type AttachmentAddErrorReason = "adapter-error" | "no-adapter" | "not-accepted"; + +type AttachmentRuntime<TSource extends AttachmentRuntimeSource = AttachmentRuntimeSource> = { + readonly path: AttachmentRuntimePath & { + attachmentSource: TSource; + }; + readonly source: TSource; + getState(): AttachmentState & { + source: TSource; + }; + remove(): Promise<void>; + subscribe(callback: () => void): Unsubscribe; +}; + +type AttachmentRuntimePath = ((MessageRuntimePath & { + readonly attachmentSource: "edit-composer" | "message"; +}) | (ThreadRuntimePath & { + readonly attachmentSource: "thread-composer"; +})) & { + readonly attachmentSelector: { + readonly type: "index"; + readonly index: number; + } | { + readonly type: "index"; + readonly index: number; + } | { + readonly type: "index"; + readonly index: number; + }; +}; + +type AttachmentRuntimeSource = AttachmentState["source"]; + +type AttachmentState = ThreadComposerAttachmentState | EditComposerAttachmentState | MessageAttachmentState; + +type BackendTool<TArgs extends Record<string, unknown> = Record<string, unknown>, TResult = unknown> = ToolBase<TArgs, TResult> & { + type: "backend"; + description?: undefined; + parameters?: undefined; + disabled?: undefined; + execute?: undefined; + toModelOutput?: undefined; + experimental_onSchemaValidationError?: undefined; + providerOptions?: undefined; +}; + +type BaseAttachment = { + id: string; + type: "image" | "document" | "file" | (string & {}); + name: string; + contentType?: string | undefined; + file?: File; + content?: ThreadUserMessagePart[]; +}; + +type BaseComposerState = { + readonly canCancel: boolean; + readonly canSend: boolean; + readonly isEditing: boolean; + readonly isEmpty: boolean; + readonly text: string; + readonly role: MessageRole; + readonly attachments: readonly Attachment[]; + readonly runConfig: RunConfig; + readonly attachmentAccept: string; + readonly dictation: DictationState | undefined; + readonly quote: QuoteInfo | undefined; + readonly queue: readonly QueueItemState[]; +}; + +type BaseThreadMessage = { + readonly status?: ThreadAssistantMessage["status"]; + readonly metadata: { + readonly unstable_state?: ReadonlyJSONValue; + readonly unstable_annotations?: readonly ReadonlyJSONValue[]; + readonly unstable_data?: readonly ReadonlyJSONValue[]; + readonly steps?: readonly ThreadStep[]; + readonly submittedFeedback?: { + readonly type: "negative" | "positive"; + }; + readonly timing?: MessageTiming; + readonly isOptimistic?: boolean; + readonly custom: Record<string, unknown>; + }; + readonly attachments?: ThreadUserMessage["attachments"]; +}; + +type ChatModelRunOptions = { + readonly messages: readonly ThreadMessage[]; + readonly runConfig: RunConfig; + readonly abortSignal: AbortSignal; + readonly context: ModelContext; + readonly unstable_assistantMessageId?: string | undefined; + readonly unstable_threadId?: string | undefined; + readonly unstable_parentId?: string | null | undefined; + unstable_getMessage(): ThreadMessage; +}; + +type ChatModelRunResult = { + readonly content?: readonly ThreadAssistantMessagePart[] | undefined; + readonly status?: MessageStatus | undefined; + readonly metadata?: { + readonly unstable_state?: ReadonlyJSONValue; + readonly unstable_annotations?: readonly ReadonlyJSONValue[] | undefined; + readonly unstable_data?: readonly ReadonlyJSONValue[] | undefined; + readonly steps?: readonly ThreadStep[] | undefined; + readonly timing?: MessageTiming | undefined; + readonly custom?: Record<string, unknown> | undefined; + }; +}; + +type CloudMessage = { + id: string; + parent_id: string | null; + height: number; + created_at: Date; + updated_at: Date; + format: "aui/v0" | string; + content: ReadonlyJSONObject; +}; + +type CloudThread = { + title: string; + last_message_at: Date; + metadata: unknown; + external_id: string | null; + id: string; + project_id: string; + created_at: Date; + updated_at: Date; + workspace_id: string; + is_archived: boolean; +}; + +type CompleteAttachment = BaseAttachment & { + status: CompleteAttachmentStatus; + content: ThreadUserMessagePart[]; +}; + +type CompleteAttachmentStatus = { + type: "complete"; +}; + +type ComposerRuntime = { + readonly path: ComposerRuntimePath; + readonly type: "edit" | "thread"; + getState(): ComposerState; + addAttachment(fileOrAttachment: File | CreateAttachment): Promise<void>; + setText(text: string): void; + setRole(role: MessageRole): void; + setRunConfig(runConfig: RunConfig): void; + reset(): Promise<void>; + clearAttachments(): Promise<void>; + send(options?: SendOptions): void; + cancel(): void; + steerQueueItem(queueItemId: string): void; + removeQueueItem(queueItemId: string): void; + subscribe(callback: () => void): Unsubscribe; + getAttachmentByIndex(idx: number): AttachmentRuntime; + startDictation(): void; + stopDictation(): void; + setQuote(quote: QuoteInfo | undefined): void; + unstable_on<E extends ComposerRuntimeEventType>(event: E, callback: ComposerRuntimeEventCallback<E>): Unsubscribe; +}; + +type ComposerRuntimeEventCallback<E extends ComposerRuntimeEventType> = (payload: ComposerRuntimeEventPayload[E]) => void; + +type ComposerRuntimeEventPayload = { + send: Record<string, never>; + attachmentAdd: Record<string, never>; + attachmentAddError: AttachmentAddErrorEvent; +}; + +type ComposerRuntimeEventType = keyof ComposerRuntimeEventPayload; + +type ComposerRuntimePath = (ThreadRuntimePath & { + readonly composerSource: "thread"; +}) | (MessageRuntimePath & { + readonly composerSource: "edit"; +}); + +type ComposerState = ThreadComposerState | EditComposerState; + +type CreateAdkApiRouteOptions = { + runner: AdkRunner; + userId: string | ((req: Request) => string | Promise<string>); + sessionId: string | ((req: Request) => string | Promise<string>); + onError?: AdkEventStreamOptions["onError"]; +}; + +type CreateAdkStreamOptions = { + api: string; + appName?: string | undefined; + userId?: string | undefined; + headers?: Record<string, string> | (() => Record<string, string> | Promise<Record<string, string>>) | undefined; +}; + +type CreateAppendMessage = string | { + parentId?: string | null | undefined; + sourceId?: string | null | undefined; + role?: AppendMessage["role"] | undefined; + content: AppendMessage["content"]; + attachments?: AppendMessage["attachments"] | undefined; + metadata?: AppendMessage["metadata"] | undefined; + createdAt?: Date | undefined; + runConfig?: AppendMessage["runConfig"] | undefined; + startRun?: boolean | undefined; +}; + +type CreateAttachment = { + id?: string; + type?: "image" | "document" | "file" | (string & {}); + name: string; + contentType?: string; + content: ThreadUserMessagePart[]; +}; + +type CreateResumeRunConfig = CreateStartRunConfig & { + stream?: (options: ChatModelRunOptions) => AsyncGenerator<ChatModelRunResult, void, unknown>; +}; + +type CreateStartRunConfig = { + parentId: string | null; + sourceId?: string | null | undefined; + runConfig?: RunConfig | undefined; +}; + +type DataMessagePart<T = any> = { + readonly type: "data"; + readonly name: string; + readonly data: T; +}; + +type DataPrefixedPart = { + readonly type: `data-${string}`; + readonly data: any; +}; + +type DeepPartial<T> = T extends readonly any[] ? readonly DeepPartial<T[number]>[] : T extends { + [key: string]: any; +} ? { + readonly [K in keyof T]?: DeepPartial<T[K]>; +} : T; + +declare namespace DictationAdapter { + type Status = { + type: "running" | "starting"; + } | { + type: "ended"; + reason: "cancelled" | "error" | "stopped"; + }; + type Result = { + transcript: string; + isFinal?: boolean; + }; + type Session = { + status: Status; + stop: () => Promise<void>; + cancel: () => void; + onSpeechStart: (callback: () => void) => Unsubscribe; + onSpeechEnd: (callback: (result: Result) => void) => Unsubscribe; + onSpeech: (callback: (result: Result) => void) => Unsubscribe; + }; +} + +type DictationAdapter = { + listen: () => DictationAdapter.Session; + disableInputDuringDictation?: boolean; +}; + +type DictationState = { + readonly status: DictationAdapter.Status; + readonly transcript?: string; + readonly inputDisabled?: boolean; +}; + +type EditComposerAttachmentState = Attachment & { + readonly source: "edit-composer"; +}; + +type EditComposerRuntime = Omit<ComposerRuntime, "getAttachmentByIndex" | "getState"> & { + readonly path: ComposerRuntimePath & { + composerSource: "edit"; + }; + readonly type: "edit"; + getState(): EditComposerState; + beginEdit(): void; + getAttachmentByIndex(idx: number): AttachmentRuntime & { + source: "edit-composer"; + }; +}; + +type EditComposerState = BaseComposerState & { + readonly type: "edit"; + readonly parentId: string | null; + readonly sourceId: string | null; +}; + +type ExportedMessageRepository = { + headId?: string | null; + messages: Array<{ + message: ThreadMessage; + parentId: string | null; + runConfig?: RunConfig; + }>; +}; + +declare const ExportedMessageRepository: { + fromArray: (messages: readonly ThreadMessageLike[]) => ExportedMessageRepository; + fromBranchableArray: (items: readonly { + message: ThreadMessageLike; + parentId: string | null; + }[], options?: { + headId?: string | null; + }) => ExportedMessageRepository; +}; + +type ExternalStoreAdapter<T = ThreadMessage> = ExternalStoreAdapterBase<T> & (T extends ThreadMessage ? object : ExternalStoreMessageConverterAdapter<T>); + +type ExternalStoreAdapterBase<T> = { + isDisabled?: boolean | undefined; + isSendDisabled?: boolean | undefined; + isRunning?: boolean | undefined; + isLoading?: boolean | undefined; + messages?: readonly T[]; + messageRepository?: ExportedMessageRepository; + suggestions?: readonly ThreadSuggestion[] | undefined; + state?: ReadonlyJSONValue | undefined; + extras?: unknown; + setMessages?: ((messages: readonly T[]) => void) | undefined; + unstable_onBranchChange?: ((event: ExternalStoreBranchChange) => void) | undefined; + onImport?: ((messages: readonly ThreadMessage[]) => void) | undefined; + onExportExternalState?: (() => any) | undefined; + onLoadExternalState?: ((state: any) => void) | undefined; + onNew: (message: AppendMessage) => Promise<void>; + queue?: ExternalThreadQueueAdapter | undefined; + onEdit?: ((message: AppendMessage) => Promise<void>) | undefined; + onDelete?: ((messageId: string) => Promise<void> | void) | undefined; + onReload?: ((parentId: string | null, config: StartRunConfig) => Promise<void>) | undefined; + onResume?: ((config: ResumeRunConfig) => Promise<void>) | undefined; + onCancel?: (() => Promise<void>) | undefined; + onAddToolResult?: ((options: AddToolResultOptions) => Promise<void> | void) | undefined; + onResumeToolCall?: ((options: { + toolCallId: string; + payload: unknown; + }) => void) | undefined; + onRespondToToolApproval?: ((options: RespondToToolApprovalOptions) => Promise<void> | void) | undefined; + convertMessage?: ExternalStoreMessageConverter<T> | undefined; + adapters?: { + attachments?: AttachmentAdapter | undefined; + speech?: SpeechSynthesisAdapter | undefined; + dictation?: DictationAdapter | undefined; + voice?: RealtimeVoiceAdapter | undefined; + feedback?: FeedbackAdapter | undefined; + threadList?: ExternalStoreThreadListAdapter | undefined; + } | undefined; + unstable_capabilities?: { + copy?: boolean | undefined; + } | undefined; + unstable_enableToolInvocations?: boolean | undefined; + setToolStatuses?: ((statuses: Record<string, ToolExecutionStatus>) => void) | undefined; +}; + +type ExternalStoreBranchChange = { + headId: string | null; + visibleMessageIds: readonly string[]; +}; + +type ExternalStoreMessageConverter<T> = (message: T, idx: number) => ThreadMessageLike; + +type ExternalStoreMessageConverterAdapter<T> = { + convertMessage: ExternalStoreMessageConverter<T>; +}; + +type ExternalStoreSharedOptions = Pick<ExternalStoreAdapter, "isDisabled" | "isSendDisabled" | "suggestions" | "unstable_capabilities">; + +type ExternalStoreThreadData<TState extends "archived" | "regular"> = { + status: TState; + id: string; + remoteId?: string | undefined; + externalId?: string | undefined; + title?: string | undefined; + custom?: Record<string, unknown> | undefined; +}; + +type ExternalStoreThreadListAdapter = { + threadId?: string | undefined; + isLoading?: boolean | undefined; + threads?: readonly ExternalStoreThreadData<"regular">[] | undefined; + archivedThreads?: readonly ExternalStoreThreadData<"archived">[] | undefined; + onSwitchToNewThread?: (() => Promise<void> | void) | undefined; + onSwitchToThread?: ((threadId: string) => Promise<void> | void) | undefined; + onRename?: (threadId: string, newTitle: string) => (Promise<void> | void) | undefined; + onUpdateCustom?: ((threadId: string, custom: Record<string, unknown> | undefined) => Promise<void> | void) | undefined; + onArchive?: ((threadId: string) => Promise<void> | void) | undefined; + onUnarchive?: ((threadId: string) => Promise<void> | void) | undefined; + onDelete?: ((threadId: string) => Promise<void> | void) | undefined; +}; + +type ExternalThreadQueueAdapter = { + items: readonly QueueItemState[]; + enqueue: (message: AppendMessage, options: { + steer: boolean; + }) => void; + steer: (queueItemId: string) => void; + remove: (queueItemId: string) => void; + clear: (reason: "cancel-run" | "edit" | "reload") => void; +}; + +type FeedbackAdapter = { + submit: (feedback: FeedbackAdapterFeedback) => void; +}; + +type FeedbackAdapterFeedback = { + message: ThreadMessage; + type: "negative" | "positive"; +}; + +type FileMessagePart = { + readonly type: "file"; + readonly filename?: string; + readonly data: string; + readonly mimeType: string; + readonly parentId?: string; +}; + +type FrontendTool<TArgs extends Record<string, unknown> = Record<string, unknown>, TResult = unknown> = ToolBase<TArgs, TResult> & { + type: "frontend"; + description?: string | undefined; + parameters: StandardSchemaV1<TArgs> | JSONSchema7; + disabled?: boolean; + execute?: ToolExecuteFunction<TArgs, TResult>; + toModelOutput?: ToolModelOutputFunction<TArgs, TResult>; + experimental_onSchemaValidationError?: OnSchemaValidationErrorFunction<TResult>; + providerOptions?: ProviderOptions; +}; + +type GeneratePresignedUploadUrlRequestBody = { + filename: string; +}; + +type GeneratePresignedUploadUrlResponse = { + success: boolean; + signedUrl: string; + expiresAt: string; + publicUrl: string; +}; + +type GenerativeUIMessagePart = { + readonly type: "generative-ui"; + readonly spec: GenerativeUISpec; + readonly id?: string; + readonly parentId?: string; +}; + +type GenerativeUINode = string | { + readonly component: string; + readonly props?: Record<string, unknown>; + readonly children?: readonly GenerativeUINode[]; + readonly key?: string; +}; + +type GenerativeUISpec = { + readonly root: GenerativeUINode | readonly GenerativeUINode[]; +}; + +type HumanTool<TArgs extends Record<string, unknown> = Record<string, unknown>, TResult = unknown> = ToolBase<TArgs, TResult> & { + type: "human"; + description?: string | undefined; + parameters: StandardSchemaV1<TArgs> | JSONSchema7; + disabled?: boolean; + display?: "standalone"; + execute?: undefined; + toModelOutput?: undefined; + experimental_onSchemaValidationError?: undefined; + providerOptions?: ProviderOptions; +}; + +type ImageMessagePart = { + readonly type: "image"; + readonly image: string; + readonly filename?: string; +}; + +interface JSONSchema7 { + $id?: string | undefined; + $ref?: string | undefined; + $schema?: JSONSchema7Version | undefined; + $comment?: string | undefined; + $defs?: { + [key: string]: JSONSchema7Definition; + } | undefined; + type?: JSONSchema7TypeName | JSONSchema7TypeName[] | undefined; + enum?: JSONSchema7Type[] | undefined; + const?: JSONSchema7Type | undefined; + multipleOf?: number | undefined; + maximum?: number | undefined; + exclusiveMaximum?: number | undefined; + minimum?: number | undefined; + exclusiveMinimum?: number | undefined; + maxLength?: number | undefined; + minLength?: number | undefined; + pattern?: string | undefined; + items?: JSONSchema7Definition | JSONSchema7Definition[] | undefined; + additionalItems?: JSONSchema7Definition | undefined; + maxItems?: number | undefined; + minItems?: number | undefined; + uniqueItems?: boolean | undefined; + contains?: JSONSchema7Definition | undefined; + maxProperties?: number | undefined; + minProperties?: number | undefined; + required?: string[] | undefined; + properties?: { + [key: string]: JSONSchema7Definition; + } | undefined; + patternProperties?: { + [key: string]: JSONSchema7Definition; + } | undefined; + additionalProperties?: JSONSchema7Definition | undefined; + dependencies?: { + [key: string]: JSONSchema7Definition | string[]; + } | undefined; + propertyNames?: JSONSchema7Definition | undefined; + if?: JSONSchema7Definition | undefined; + then?: JSONSchema7Definition | undefined; + else?: JSONSchema7Definition | undefined; + allOf?: JSONSchema7Definition[] | undefined; + anyOf?: JSONSchema7Definition[] | undefined; + oneOf?: JSONSchema7Definition[] | undefined; + not?: JSONSchema7Definition | undefined; + format?: string | undefined; + contentMediaType?: string | undefined; + contentEncoding?: string | undefined; + definitions?: { + [key: string]: JSONSchema7Definition; + } | undefined; + title?: string | undefined; + description?: string | undefined; + default?: JSONSchema7Type | undefined; + readOnly?: boolean | undefined; + writeOnly?: boolean | undefined; + examples?: JSONSchema7Type | undefined; +} + +interface JSONSchema7Array extends Array<JSONSchema7Type> { +} + +type JSONSchema7Definition = JSONSchema7 | boolean; + +interface JSONSchema7Object { + [key: string]: JSONSchema7Type; +} + +type JSONSchema7Type = string | number | boolean | JSONSchema7Object | JSONSchema7Array | null; + +type JSONSchema7TypeName = "array" | "boolean" | "integer" | "null" | "number" | "object" | "string"; + +type JSONSchema7Version = string; + +type JoinStrategy = "concat-content" | "none"; + +type LanguageModelConfig = { + apiKey?: string; + baseUrl?: string; + modelName?: string; + reasoningEffort?: string; +}; + +type LanguageModelV1CallSettings = { + maxTokens?: number; + temperature?: number; + topP?: number; + presencePenalty?: number; + frequencyPenalty?: number; + seed?: number; + headers?: Record<string, string | undefined>; +}; + +type MakeRequestOptions = { + method?: "POST" | "PUT" | "DELETE" | undefined; + headers?: Record<string, string> | undefined; + query?: Record<string, string | number | boolean> | undefined; + body?: object | undefined; +}; + +type McpAppMetadata = { + readonly resourceUri: string; + readonly mimeType?: string; + readonly visibility?: readonly ("app" | "model")[]; +}; + +type McpServerConfig = { + type: "http" | "sse"; + url: string; + headers?: Record<string, string>; + redirect?: "error" | "follow"; + connectionTimeout?: number | undefined; +} | { + type: "stdio"; + command: string; + args?: readonly string[]; + env?: Record<string, string>; + cwd?: string; + connectionTimeout?: number | undefined; +}; + +type McpTool = ToolBase<Record<string, unknown>, unknown> & { + type: "mcp"; + server: McpServerConfig; + description?: undefined; + parameters?: undefined; + disabled?: boolean; + execute?: undefined; + toModelOutput?: undefined; + experimental_onSchemaValidationError?: undefined; + providerOptions?: undefined; +}; + +type MessageAttachmentState = CompleteAttachment & { + readonly source: "message"; +}; + +type MessageCommonProps = { + readonly id: string; + readonly createdAt: Date; +}; + +type MessagePartRuntime = { + addToolResult(result: any | ToolResponse<any>): void; + resumeToolCall(payload: unknown): void; + respondToToolApproval(response: ToolApprovalResponse): void; + readonly path: MessagePartRuntimePath; + getState(): MessagePartState; + subscribe(callback: () => void): Unsubscribe; +}; + +type MessagePartRuntimePath = MessageRuntimePath & { + readonly messagePartSelector: { + readonly type: "index"; + readonly index: number; + } | { + readonly type: "toolCallId"; + readonly toolCallId: string; + }; +}; + +type MessagePartState = (ThreadUserMessagePart | ThreadAssistantMessagePart) & { + readonly status: MessagePartStatus | ToolCallMessagePartStatus; +}; + +type MessagePartStatus = { + readonly type: "running"; +} | { + readonly type: "complete"; +} | { + readonly type: "incomplete"; + readonly reason: "cancelled" | "content-filter" | "error" | "length" | "other"; + readonly error?: unknown; +}; + +type MessageRole = ThreadMessage["role"]; + +type MessageRuntime = { + readonly path: MessageRuntimePath; + readonly composer: EditComposerRuntime; + getState(): MessageState; + delete(): void | Promise<void>; + reload(config?: ReloadConfig): void; + speak(): void; + stopSpeaking(): void; + submitFeedback(_param0: { + type: "positive" | "negative"; + }): void; + switchToBranch(_param1: { + position?: "previous" | "next" | undefined; + branchId?: string | undefined; + }): void; + unstable_getCopyText(): string; + subscribe(callback: () => void): Unsubscribe; + getMessagePartByIndex(idx: number): MessagePartRuntime; + getMessagePartByToolCallId(toolCallId: string): MessagePartRuntime; + getAttachmentByIndex(idx: number): AttachmentRuntime & { + source: "message"; + }; +}; + +type MessageRuntimePath = ThreadRuntimePath & { + readonly messageSelector: { + readonly type: "messageId"; + readonly messageId: string; + } | { + readonly type: "index"; + readonly index: number; + }; +}; + +type MessageState = ThreadMessage & { + readonly parentId: string | null; + readonly index: number; + readonly isLast: boolean; + readonly branchNumber: number; + readonly branchCount: number; + readonly speech: SpeechState | undefined; +}; + +type MessageStatus = { + readonly type: "running"; +} | { + readonly type: "requires-action"; + readonly reason: "interrupt" | "tool-calls"; +} | { + readonly type: "complete"; + readonly reason: "stop" | "unknown"; +} | { + readonly type: "incomplete"; + readonly reason: "cancelled" | "content-filter" | "error" | "length" | "other" | "tool-calls"; + readonly error?: ReadonlyJSONValue; +}; + +type MessageTiming = { + readonly streamStartTime: number; + readonly firstTokenTime?: number; + readonly totalStreamTime?: number; + readonly tokenCount?: number; + readonly tokensPerSecond?: number; + readonly totalChunks: number; + readonly toolCallCount: number; +}; + +type ModelContext = { + priority?: number | undefined; + system?: string | undefined; + tools?: Record<string, Tool<any, any>> | undefined; + callSettings?: LanguageModelV1CallSettings | undefined; + config?: LanguageModelConfig | undefined; + unstable_composerMetadata?: Record<string, unknown> | undefined; +}; + +type ModelContextProvider = { + getModelContext: () => ModelContext; + subscribe?: (callback: () => void) => Unsubscribe; +}; + +type ObjectKey<T> = keyof T & (string | number); + +type ObjectStreamOperation = { + readonly type: "set"; + readonly path: readonly string[]; + readonly value: ReadonlyJSONValue; +} | { + readonly type: "append-text"; + readonly path: readonly string[]; + readonly value: string; +}; + +type OnAdkAgentTransferCallback = (toAgent: string) => void | Promise<void>; + +type OnAdkCustomEventCallback = (type: string, data: unknown) => void | Promise<void>; + +type OnAdkErrorCallback = (error: unknown) => void | Promise<void>; + +type OnSchemaValidationErrorFunction<TResult> = ToolExecuteFunction<unknown, TResult>; + +type ParsedAdkRequest = { + type: "message"; + text: string; + parts?: Array<Record<string, unknown>> | undefined; + config: AdkSendMessageConfig; + stateDelta?: Record<string, unknown> | undefined; +} | { + type: "tool-result"; + toolCallId: string; + toolName: string; + result: unknown; + isError: boolean; + config: AdkSendMessageConfig; + stateDelta?: Record<string, unknown> | undefined; +}; + +type PartInit = { + readonly type: "reasoning" | "text"; + readonly parentId?: string; +} | { + readonly type: "tool-call"; + readonly toolCallId: string; + readonly toolName: string; + readonly parentId?: string; +} | { + readonly type: "source"; + readonly sourceType: "url"; + readonly id: string; + readonly url: string; + readonly title?: string; + readonly parentId?: string; +} | { + readonly type: "file"; + readonly data: string; + readonly mimeType: string; + readonly parentId?: string; +} | { + readonly type: "data"; + readonly name: string; + readonly data: ReadonlyJSONValue; + readonly parentId?: string; +}; + +type PartProviderMetadata = { + readonly [providerName: string]: ReadonlyJSONObject; +}; + +type PdfToImagesRequestBody = { + file_blob?: string | undefined; + file_url?: string | undefined; +}; + +type PdfToImagesResponse = { + success: boolean; + urls: string[]; + message: string; +}; + +type PendingAttachment = BaseAttachment & { + status: PendingAttachmentStatus; + file: File; +}; + +type PendingAttachmentStatus = { + type: "running"; + reason: "uploading"; + progress: number; +} | { + type: "requires-action"; + reason: "composer-send"; +} | { + type: "incomplete"; + reason: "error" | "upload-paused"; + message?: string; +}; + +type ProviderOptions = Record<string, Record<string, unknown>>; + +type ProviderTool<TArgs extends Record<string, unknown> = Record<string, unknown>, TResult = unknown> = ToolBase<TArgs, TResult> & { + type: "provider"; + providerId: `${string}.${string}`; + parameters?: StandardSchemaV1<TArgs> | JSONSchema7 | undefined; + args: Record<string, unknown>; + supportsDeferredResults?: boolean; + description?: undefined; + disabled?: boolean; + execute?: undefined; + toModelOutput?: undefined; + experimental_onSchemaValidationError?: undefined; + providerOptions?: ProviderOptions; +}; + +type QueueItemState = { + readonly id: string; + readonly prompt: string; +}; + +type QuoteInfo = { + readonly text: string; + readonly messageId: string; +}; + +type ReadonlyJSONArray = readonly ReadonlyJSONValue[]; + +type ReadonlyJSONObject = { + readonly [key: string]: ReadonlyJSONValue; +}; + +type ReadonlyJSONValue = null | string | number | boolean | ReadonlyJSONObject | ReadonlyJSONArray; + +declare namespace RealtimeVoiceAdapter { + type Status = { + type: "running" | "starting"; + } | { + type: "ended"; + reason: "cancelled" | "error" | "finished"; + error?: unknown; + }; + type Mode = "listening" | "speaking"; + type TranscriptItem = { + role: "assistant" | "user"; + text: string; + isFinal?: boolean; + }; + type Session = { + status: Status; + isMuted: boolean; + disconnect: () => void; + mute: () => void; + unmute: () => void; + onStatusChange: (callback: (status: Status) => void) => Unsubscribe; + onTranscript: (callback: (transcript: TranscriptItem) => void) => Unsubscribe; + onModeChange: (callback: (mode: Mode) => void) => Unsubscribe; + onVolumeChange: (callback: (volume: number) => void) => Unsubscribe; + }; +} + +type RealtimeVoiceAdapter = { + connect: (options: { + abortSignal?: AbortSignal; + }) => RealtimeVoiceAdapter.Session; +}; + +type ReasoningMessagePart = { + readonly type: "reasoning"; + readonly text: string; + readonly providerMetadata?: PartProviderMetadata; + readonly parentId?: string; +}; + +type ReloadConfig = { + runConfig?: RunConfig; +}; + +type RemoteThreadInitializeResponse = { + remoteId: string; + externalId: string | undefined; +}; + +type RemoteThreadListAdapter = { + list(params?: RemoteThreadListPageOptions): Promise<RemoteThreadListResponse>; + rename(remoteId: string, newTitle: string): Promise<void>; + updateCustom?(remoteId: string, custom: Record<string, unknown> | undefined): Promise<void>; + archive(remoteId: string): Promise<void>; + unarchive(remoteId: string): Promise<void>; + delete(remoteId: string): Promise<void>; + initialize(threadId: string): Promise<RemoteThreadInitializeResponse>; + generateTitle(remoteId: string, unstable_messages: readonly ThreadMessage[]): Promise<AssistantStream>; + fetch(threadId: string): Promise<RemoteThreadMetadata>; + unstable_Provider?: ComponentType<PropsWithChildren> | undefined; +}; + +type RemoteThreadListPageOptions = { + after?: string | undefined; +}; + +type RemoteThreadListResponse = { + threads: RemoteThreadMetadata[]; + nextCursor?: string | undefined; +}; + +type RemoteThreadMetadata = { + readonly status: "archived" | "regular"; + readonly remoteId: string; + readonly externalId?: string | undefined; + readonly title?: string | undefined; + readonly lastMessageAt?: Date | undefined; + readonly custom?: Record<string, unknown> | undefined; +}; + +type ReportToolCall = { + tool_name: string; + tool_call_id: string; + tool_args?: string; + tool_result?: string; + tool_source?: "backend" | "frontend" | "mcp"; + start_ms?: number; + end_ms?: number; + sampling_calls?: SamplingCallData[]; +}; + +type RespondToToolApprovalOptions = { + approvalId: string; + approved: boolean; + optionId?: string; + reason?: string; +}; + +type ResumeRunConfig = StartRunConfig & { + stream?: (options: ChatModelRunOptions) => AsyncGenerator<ChatModelRunResult, void, unknown>; +}; + +type RunConfig = { + readonly custom?: Record<string, unknown>; +}; + +type RuntimeCapabilities = { + readonly switchToBranch: boolean; + readonly switchBranchDuringRun: boolean; + readonly edit: boolean; + readonly reload: boolean; + readonly delete: boolean; + readonly cancel: boolean; + readonly unstable_copy: boolean; + readonly speech: boolean; + readonly dictation: boolean; + readonly voice: boolean; + readonly attachments: boolean; + readonly feedback: boolean; + readonly queue: boolean; +}; + +type SamplingCallData = { + model_id?: string; + input_tokens?: number; + output_tokens?: number; + reasoning_tokens?: number; + cached_input_tokens?: number; + duration_ms?: number; +}; + +type SendOptions = { + startRun?: boolean; + steer?: boolean; +}; + +type SourceMessagePart = { + readonly type: "source"; + readonly sourceType: "url"; + readonly id: string; + readonly url: string; + readonly title?: string; + readonly providerMetadata?: SourceProviderMetadata; + readonly parentId?: string; +} | { + readonly type: "source"; + readonly sourceType: "document"; + readonly id: string; + readonly url?: undefined; + readonly title: string; + readonly mediaType: string; + readonly filename?: string; + readonly providerMetadata?: SourceProviderMetadata; + readonly parentId?: string; +}; + +type SourceProviderMetadata = PartProviderMetadata; + +interface SpeechRecognitionConstructor { + new (): SpeechRecognitionInstance; +} + +interface SpeechRecognitionInstance extends EventTarget { + lang: string; + continuous: boolean; + interimResults: boolean; + start(): void; + stop(): void; + abort(): void; +} + +type SpeechState = { + readonly messageId: string; + readonly status: SpeechSynthesisAdapter.Status; +}; + +declare namespace SpeechSynthesisAdapter { + type Status = { + type: "running" | "starting"; + } | { + type: "ended"; + reason: "cancelled" | "error" | "finished"; + error?: unknown; + }; + type Utterance = { + status: Status; + cancel: () => void; + subscribe: (callback: () => void) => Unsubscribe; + }; +} + +type SpeechSynthesisAdapter = { + speak: (text: string) => SpeechSynthesisAdapter.Utterance; +}; + +type StartRunConfig = { + parentId: string | null; + sourceId: string | null; + runConfig: RunConfig; +}; + +declare const TOOL_RESPONSE_SYMBOL: unique symbol; + +type TextMessagePart = { + readonly type: "text"; + readonly text: string; + readonly providerMetadata?: PartProviderMetadata; + readonly parentId?: string; +}; + +type ThreadAssistantMessage = MessageCommonProps & { + readonly role: "assistant"; + readonly content: readonly ThreadAssistantMessagePart[]; + readonly status: MessageStatus; + readonly metadata: { + readonly unstable_state: ReadonlyJSONValue; + readonly unstable_annotations: readonly ReadonlyJSONValue[]; + readonly unstable_data: readonly ReadonlyJSONValue[]; + readonly steps: readonly ThreadStep[]; + readonly submittedFeedback?: { + readonly type: "negative" | "positive"; + }; + readonly timing?: MessageTiming; + readonly isOptimistic?: boolean; + readonly custom: Record<string, unknown>; + }; +}; + +type ThreadAssistantMessagePart = TextMessagePart | ReasoningMessagePart | ToolCallMessagePart | SourceMessagePart | FileMessagePart | ImageMessagePart | DataMessagePart | GenerativeUIMessagePart; + +type ThreadComposerAttachmentState = Attachment & { + readonly source: "thread-composer"; +}; + +type ThreadComposerRuntime = Omit<ComposerRuntime, "getAttachmentByIndex" | "getState"> & { + readonly path: ComposerRuntimePath & { + composerSource: "thread"; + }; + readonly type: "thread"; + getState(): ThreadComposerState; + getAttachmentByIndex(idx: number): AttachmentRuntime & { + source: "thread-composer"; + }; +}; + +type ThreadComposerState = BaseComposerState & { + readonly type: "thread"; +}; + +type ThreadListItemEventCallback<E extends ThreadListItemEventType> = (payload: ThreadListItemEventPayload[E]) => void; + +type ThreadListItemEventPayload = { + switchedTo: Record<string, never>; + switchedAway: Record<string, never>; +}; + +type ThreadListItemEventType = keyof ThreadListItemEventPayload; + +type ThreadListItemRuntime = { + readonly path: ThreadListItemRuntimePath; + getState(): ThreadListItemState; + initialize(): Promise<{ + remoteId: string; + externalId: string | undefined; + }>; + generateTitle(): Promise<void>; + switchTo(options?: { + unarchive?: boolean; + }): Promise<void>; + rename(newTitle: string): Promise<void>; + updateCustom(custom: Record<string, unknown> | undefined): Promise<void>; + archive(): Promise<void>; + unarchive(): Promise<void>; + delete(): Promise<void>; + detach(): void; + subscribe(callback: () => void): Unsubscribe; + unstable_on<E extends ThreadListItemEventType>(event: E, callback: ThreadListItemEventCallback<E>): Unsubscribe; + __internal_getRuntime(): ThreadListItemRuntime; +}; + +type ThreadListItemRuntimePath = { + readonly ref: string; + readonly threadSelector: { + readonly type: "main"; + } | { + readonly type: "index"; + readonly index: number; + } | { + readonly type: "archiveIndex"; + readonly index: number; + } | { + readonly type: "threadId"; + readonly threadId: string; + }; +}; + +type ThreadListItemState = { + readonly isMain: boolean; + readonly id: string; + readonly remoteId: string | undefined; + readonly externalId: string | undefined; + readonly status: ThreadListItemStatus; + readonly title?: string | undefined; + readonly lastMessageAt?: Date | undefined; + readonly custom?: Record<string, unknown> | undefined; +}; + +type ThreadListItemStatus = "archived" | "deleted" | "new" | "regular"; + +type ThreadListRuntime = { + getState(): ThreadListState; + subscribe(callback: () => void): Unsubscribe; + readonly main: ThreadRuntime; + getById(threadId: string): ThreadRuntime; + readonly mainItem: ThreadListItemRuntime; + getItemById(threadId: string): ThreadListItemRuntime; + getItemByIndex(idx: number): ThreadListItemRuntime; + getArchivedItemByIndex(idx: number): ThreadListItemRuntime; + switchToThread(threadId: string, options?: { + unarchive?: boolean; + }): Promise<void>; + switchToNewThread(): Promise<void>; + getLoadThreadsPromise(): Promise<void>; + reload(): Promise<void>; + loadMore(): Promise<void>; +}; + +type ThreadListState = { + readonly mainThreadId: string; + readonly newThreadId: string | undefined; + readonly threadIds: readonly string[]; + readonly archivedThreadIds: readonly string[]; + readonly isLoading: boolean; + readonly isLoadingMore: boolean; + readonly hasMore: boolean; + readonly threadItems: Readonly<Record<string, Omit<ThreadListItemState, "isMain" | "threadId">>>; +}; + +type ThreadMessage = BaseThreadMessage & (ThreadSystemMessage | ThreadUserMessage | ThreadAssistantMessage); + +type ThreadMessageLike = { + readonly role: "assistant" | "system" | "user"; + readonly content: string | readonly (TextMessagePart | ReasoningMessagePart | SourceMessagePart | ImageMessagePart | FileMessagePart | DataMessagePart | GenerativeUIMessagePart | Unstable_AudioMessagePart | DataPrefixedPart | { + readonly type: "tool-call"; + readonly toolCallId?: string; + readonly toolName: string; + readonly args?: ReadonlyJSONObject; + readonly argsText?: string; + readonly artifact?: any; + readonly result?: any | undefined; + readonly isError?: boolean | undefined; + readonly parentId?: string | undefined; + readonly messages?: readonly ThreadMessage[] | undefined; + readonly interrupt?: { + type: "human"; + payload: unknown; + }; + readonly timing?: ToolCallTiming; + readonly providerMetadata?: PartProviderMetadata; + readonly approval?: { + readonly id: string; + readonly approved?: boolean; + readonly reason?: string; + readonly isAutomatic?: boolean; + readonly options?: readonly ToolApprovalOption[]; + readonly optionId?: string; + readonly resolution?: "cancelled" | "expired"; + }; + })[]; + readonly id?: string | undefined; + readonly createdAt?: Date | undefined; + readonly status?: MessageStatus | undefined; + readonly attachments?: readonly (Omit<CompleteAttachment, "content"> & { + readonly content: readonly (ThreadUserMessagePart | DataPrefixedPart)[]; + })[] | undefined; + readonly metadata?: { + readonly unstable_state?: ReadonlyJSONValue; + readonly unstable_annotations?: readonly ReadonlyJSONValue[] | undefined; + readonly unstable_data?: readonly ReadonlyJSONValue[] | undefined; + readonly steps?: readonly ThreadStep[] | undefined; + readonly timing?: MessageTiming | undefined; + readonly submittedFeedback?: { + readonly type: "negative" | "positive"; + }; + readonly isOptimistic?: boolean | undefined; + readonly custom?: Record<string, unknown> | undefined; + } | undefined; +}; + +type ThreadRuntime = { + readonly path: ThreadRuntimePath; + readonly composer: ThreadComposerRuntime; + getState(): ThreadState; + append(message: CreateAppendMessage): void; + deleteMessage(messageId: string): void | Promise<void>; + startRun(config: CreateStartRunConfig): void; + resumeRun(config: CreateResumeRunConfig): void; + exportExternalState(): any; + importExternalState(state: any): void; + subscribe(callback: () => void): Unsubscribe; + cancelRun(): void; + getModelContext(): ModelContext; + export(): ExportedMessageRepository; + import(repository: ExportedMessageRepository): void; + reset(initialMessages?: readonly ThreadMessageLike[]): void; + getMessageByIndex(idx: number): MessageRuntime; + getMessageById(messageId: string): MessageRuntime; + stopSpeaking(): void; + connectVoice(): void; + disconnectVoice(): void; + getVoiceVolume(): number; + subscribeVoiceVolume(callback: () => void): Unsubscribe; + muteVoice(): void; + unmuteVoice(): void; + unstable_on<E extends ThreadRuntimeEventType>(event: E, callback: ThreadRuntimeEventCallback<E>): Unsubscribe; +}; + +type ThreadRuntimeEventCallback<E extends ThreadRuntimeEventType> = (payload: ThreadRuntimeEventPayload[E]) => void; + +type ThreadRuntimeEventPayload = { + runStart: Record<string, never>; + runEnd: Record<string, never>; + initialize: Record<string, never>; + modelContextUpdate: Record<string, never>; +}; + +type ThreadRuntimeEventType = keyof ThreadRuntimeEventPayload; + +type ThreadRuntimePath = { + readonly ref: string; + readonly threadSelector: { + readonly type: "main"; + } | { + readonly type: "threadId"; + readonly threadId: string; + }; +}; + +type ThreadState = { + readonly threadId: string; + readonly metadata: ThreadListItemState; + readonly isDisabled: boolean; + readonly isLoading: boolean; + readonly isRunning: boolean; + readonly capabilities: RuntimeCapabilities; + readonly messages: readonly ThreadMessage[]; + readonly state: ReadonlyJSONValue; + readonly suggestions: readonly ThreadSuggestion[]; + readonly extras: unknown; + readonly speech: SpeechState | undefined; + readonly voice: VoiceSessionState | undefined; +}; + +type ThreadStep = { + readonly messageId?: string; + readonly usage?: { + readonly inputTokens: number; + readonly outputTokens: number; + } | undefined; +}; + +type ThreadSuggestion = { + prompt: string; +}; + +type ThreadSystemMessage = MessageCommonProps & { + readonly role: "system"; + readonly content: readonly [ + TextMessagePart + ]; + readonly metadata: { + readonly unstable_state?: undefined; + readonly unstable_annotations?: undefined; + readonly unstable_data?: undefined; + readonly steps?: undefined; + readonly submittedFeedback?: undefined; + readonly timing?: undefined; + readonly custom: Record<string, unknown>; + }; +}; + +type ThreadUserMessage = MessageCommonProps & { + readonly role: "user"; + readonly content: readonly ThreadUserMessagePart[]; + readonly attachments: readonly CompleteAttachment[]; + readonly metadata: { + readonly unstable_state?: undefined; + readonly unstable_annotations?: undefined; + readonly unstable_data?: undefined; + readonly steps?: undefined; + readonly submittedFeedback?: undefined; + readonly timing?: undefined; + readonly custom: Record<string, unknown>; + }; +}; + +type ThreadUserMessagePart = TextMessagePart | ImageMessagePart | FileMessagePart | DataMessagePart | Unstable_AudioMessagePart; + +type Tool<TArgs extends Record<string, unknown> = Record<string, unknown>, TResult = unknown> = FrontendTool<TArgs, TResult> | BackendTool<TArgs, TResult> | HumanTool<TArgs, TResult> | ProviderTool<TArgs, TResult> | McpTool | ToolWithoutType<TArgs, TResult>; + +type ToolApprovalOption = { + readonly id: string; + readonly kind: ToolApprovalOptionKind | (string & {}); + readonly label?: string; + readonly description?: string; + readonly grants?: readonly string[]; + readonly confirm?: boolean | { + title?: string; + description?: string; + }; +}; + +type ToolApprovalOptionKind = "allow-always" | "allow-once" | "reject-always" | "reject-once"; + +type ToolApprovalResponse = { + readonly approved: boolean; + readonly reason?: string; +} | { + readonly optionId: string; + readonly reason?: string; +} | { + readonly approved: boolean; + readonly optionId: string; + readonly reason?: string; +}; + +type ToolBase<TArgs extends Record<string, unknown> = Record<string, unknown>, TResult = unknown> = { + streamCall?: ToolStreamCallFunction<TArgs, TResult>; + display?: ToolDisplay; +}; + +interface ToolCallArgsReader<TArgs extends Record<string, unknown>> { + get<PathT extends TypePath<TArgs>>(...fieldPath: PathT): Promise<TypeAtPath<TArgs, PathT>>; + streamValues<PathT extends TypePath<TArgs>>(...fieldPath: PathT): AsyncIterableStream<DeepPartial<TypeAtPath<TArgs, PathT>>>; + streamText<PathT extends TypePath<TArgs>>(...fieldPath: PathT): TypeAtPath<TArgs, PathT> extends string & (infer U) ? AsyncIterableStream<U> : never; + forEach<PathT extends TypePath<TArgs>>(...fieldPath: PathT): NonNullable<TypeAtPath<TArgs, PathT>> extends Array<infer U> ? AsyncIterableStream<U> : never; +} + +type ToolCallMessagePart<TArgs = ReadonlyJSONObject, TResult = unknown> = { + readonly type: "tool-call"; + readonly toolCallId: string; + readonly toolName: string; + readonly args: TArgs; + readonly result?: TResult | undefined; + readonly isError?: boolean | undefined; + readonly argsText: string; + readonly artifact?: unknown; + readonly timing?: ToolCallTiming; + readonly mcp?: ToolCallMessagePartMcpMetadata; + readonly providerMetadata?: PartProviderMetadata; + readonly modelContent?: readonly ToolModelContentPart[] | undefined; + readonly interrupt?: { + type: "human"; + payload: unknown; + }; + readonly approval?: { + readonly id: string; + readonly approved?: boolean; + readonly reason?: string; + readonly isAutomatic?: boolean; + readonly options?: readonly ToolApprovalOption[]; + readonly optionId?: string; + readonly resolution?: "cancelled" | "expired"; + }; + readonly parentId?: string; + readonly messages?: readonly ThreadMessage[]; +}; + +type ToolCallMessagePartMcpMetadata = { + readonly app?: McpAppMetadata; +}; + +type ToolCallMessagePartStatus = { + readonly type: "requires-action"; + readonly reason: "interrupt"; +} | MessagePartStatus; + +interface ToolCallReader<TArgs extends Record<string, unknown> = Record<string, unknown>, TResult = unknown> { + args: ToolCallArgsReader<TArgs>; + response: ToolCallResponseReader<TResult>; + result: { + get: () => Promise<TResult>; + }; +} + +interface ToolCallResponseReader<TResult> { + get: () => Promise<ToolResponse<TResult>>; +} + +type ToolCallTiming = { + readonly startedAt: number; + readonly completedAt?: number; +}; + +type ToolDisplay = "inline" | "standalone"; + +type ToolExecuteFunction<TArgs, TResult> = (args: TArgs, context: ToolExecutionContext) => TResult | Promise<TResult>; + +type ToolExecutionContext = { + toolCallId: string; + abortSignal: AbortSignal; + human: (payload: unknown) => Promise<unknown>; +}; + +type ToolExecutionStatus = { + type: "executing"; +} | { + type: "interrupt"; + payload: { + type: "human"; + payload: unknown; + }; +}; + +type ToolModelContentPart = { + readonly type: "text"; + readonly text: string; +} | { + readonly type: "file"; + readonly data: string; + readonly mediaType: string; + readonly filename?: string; +}; + +type ToolModelOutputFunction<TArgs, TResult> = (options: { + toolCallId: string; + input: TArgs; + output: TResult; +}) => readonly ToolModelContentPart[] | Promise<readonly ToolModelContentPart[]>; + +declare class ToolResponse<TResult> { + get [TOOL_RESPONSE_SYMBOL](): boolean; + readonly artifact?: ReadonlyJSONValue; + readonly result: TResult; + readonly isError: boolean; + readonly modelContent?: readonly ToolModelContentPart[]; + readonly messages?: ReadonlyJSONValue; + constructor(options: ToolResponseLike<TResult>); + static [Symbol.hasInstance](obj: unknown): obj is ToolResponse<ReadonlyJSONValue>; + static toResponse(result: any | ToolResponse<any>): ToolResponse<any>; +} + +type ToolResponseLike<TResult> = { + result: TResult; + artifact?: ReadonlyJSONValue | undefined; + isError?: boolean | undefined; + modelContent?: readonly ToolModelContentPart[] | undefined; + messages?: ReadonlyJSONValue | undefined; +}; + +type ToolStreamCallFunction<TArgs extends Record<string, unknown> = Record<string, unknown>, TResult = unknown> = (reader: ToolCallReader<TArgs, TResult>, context: ToolExecutionContext) => void; + +type ToolWithoutType<TArgs extends Record<string, unknown> = Record<string, unknown>, TResult = unknown> = (Omit<FrontendTool<TArgs, TResult>, "type"> | Omit<BackendTool<TArgs, TResult>, "type"> | Omit<HumanTool<TArgs, TResult>, "type"> | Omit<ProviderTool<TArgs, TResult>, "type">) & { + type?: undefined; +}; + +type TupleIndex<T extends readonly any[]> = Exclude<keyof T, keyof any[]>; + +type TypeAtPath<T, P extends readonly any[]> = P extends [ + infer Head, + ...infer Rest +] ? Head extends keyof T ? TypeAtPath<T[Head], Rest> : never : T; + +type TypePath<T> = [ +] | (0 extends 1 & T ? any[] : T extends object ? T extends readonly any[] ? number extends T["length"] ? { + [K in TupleIndex<T>]: [ + AsNumber<K>, + ...TypePath<T[K]> + ]; +}[TupleIndex<T>] : [ + number, + ...TypePath<T[number]> +] : { + [K in ObjectKey<T>]: [ + K, + ...TypePath<T[K]> + ]; +}[ObjectKey<T>] : [ +]); + +type Unstable_AudioMessagePart = { + readonly type: "audio"; + readonly audio: { + readonly data: string; + readonly format: "mp3" | "wav"; + }; +}; + +type Unsubscribe = () => void; + +type UseAdkMessagesOptions = { + stream: AdkStreamCallback; + eventHandlers?: { + onError?: OnAdkErrorCallback; + onCustomEvent?: OnAdkCustomEventCallback; + onAgentTransfer?: OnAdkAgentTransferCallback; + }; +}; + +type UseAdkRuntimeOptions = ExternalStoreSharedOptions & { + stream: AdkStreamCallback; + onThreadIdChange?: ((threadId: string | undefined) => void) | undefined; + autoCancelPendingToolCalls?: boolean | undefined; + unstable_allowCancellation?: boolean | undefined; + getCheckpointId?: (threadId: string, parentMessages: AdkMessage[]) => Promise<string | null>; + load?: (threadId: string) => Promise<{ + messages: AdkMessage[]; + }>; + create?: () => Promise<{ + externalId: string; + }>; + delete?: (threadId: string) => Promise<void>; + adapters?: { + attachments?: AttachmentAdapter; + speech?: SpeechSynthesisAdapter; + dictation?: DictationAdapter; + voice?: RealtimeVoiceAdapter; + feedback?: FeedbackAdapter; + } | undefined; + eventHandlers?: { + onError?: OnAdkErrorCallback; + onCustomEvent?: OnAdkCustomEventCallback; + onAgentTransfer?: OnAdkAgentTransferCallback; + } | undefined; + cloud?: AssistantCloud | undefined; + sessionAdapter?: RemoteThreadListAdapter | undefined; +}; + +type VoiceSessionState = { + readonly status: RealtimeVoiceAdapter.Status; + readonly isMuted: boolean; + readonly mode: RealtimeVoiceAdapter.Mode; +}; + +declare const adkEventStream: (events: AsyncGenerator<AdkSdkEvent, void, undefined>, options?: AdkEventStreamOptions) => Response; + +declare const convertAdkMessage: useExternalMessageConverter.Callback<AdkMessage>; + +declare function createAdkApiRoute(options: CreateAdkApiRouteOptions): (req: Request) => Promise<Response>; + +declare function createAdkSessionAdapter(options: AdkSessionAdapterOptions): AdkSessionAdapterResult; + +declare function createAdkStream(options: CreateAdkStreamOptions): AdkStreamCallback; + +declare global { + interface Window { + SpeechRecognition?: SpeechRecognitionConstructor; + webkitSpeechRecognition?: SpeechRecognitionConstructor; + } +} + +declare namespace entry_root_exports { + export { AdkArtifactData, AdkAuthCredential, AdkAuthCredentialType, AdkAuthRequest, AdkEvent, AdkEventAccumulator, AdkEventActions, AdkEventPart, AdkEventType, AdkMessage, AdkMessageContentPart, AdkMessageMetadata, AdkRunConfig, AdkSendMessageConfig, AdkSessionAdapterOptions, AdkStreamCallback, AdkStructuredEvent, AdkToolCall, AdkToolConfirmation, CreateAdkStreamOptions, OnAdkAgentTransferCallback, OnAdkCustomEventCallback, OnAdkErrorCallback, UseAdkMessagesOptions, UseAdkRuntimeOptions, convertAdkMessage, createAdkSessionAdapter, createAdkStream, toAdkStructuredEvents, useAdkAgentInfo, useAdkAppState, useAdkArtifacts, useAdkAuthRequests, useAdkConfirmTool, useAdkEscalation, useAdkLongRunningToolIds, useAdkMessageMetadata, useAdkMessages, useAdkRuntime, useAdkSend, useAdkSessionState, useAdkSubmitAuth, useAdkSubmitInput, useAdkTempState, useAdkToolConfirmations, useAdkUserState }; +} + +declare namespace entry_server_exports { + export { AdkEventStreamOptions, CreateAdkApiRouteOptions, adkEventStream, createAdkApiRoute, parseAdkRequest, toAdkContent }; +} + +declare const parseAdkRequest: (request: Request) => Promise<ParsedAdkRequest>; + +declare const toAdkContent: (parsed: ParsedAdkRequest) => { + role: string; + parts: Array<Record<string, unknown>>; +}; + +declare function toAdkStructuredEvents(event: AdkEvent): AdkStructuredEvent[]; + +declare const useAdkAgentInfo: () => { + name?: string | undefined; + branch?: string | undefined; +} | undefined; + +declare const useAdkAppState: () => Record<string, unknown>; + +declare const useAdkArtifacts: () => Record<string, number>; + +declare const useAdkAuthRequests: () => AdkAuthRequest[]; + +declare const useAdkConfirmTool: () => (toolCallId: string, confirmed: boolean, payload?: ReadonlyJSONValue) => Promise<void>; + +declare const useAdkEscalation: () => boolean; + +declare const useAdkLongRunningToolIds: () => string[]; + +declare const useAdkMessageMetadata: () => Map<string, AdkMessageMetadata>; + +declare const useAdkMessages: (_param2: UseAdkMessagesOptions) => { + messages: AdkMessage[]; + stateDelta: Record<string, unknown>; + agentInfo: { + name?: string | undefined; + branch?: string | undefined; + }; + longRunningToolIds: string[]; + artifactDelta: Record<string, number>; + toolConfirmations: AdkToolConfirmation[]; + authRequests: AdkAuthRequest[]; + escalated: boolean; + messageMetadata: Map<string, AdkMessageMetadata>; + sendMessage: (newMessages: AdkMessage[], config: AdkSendMessageConfig) => Promise<void>; + cancel: () => void; + setMessages: (msgs: AdkMessage[]) => void; + replaceMessages: (msgs: AdkMessage[]) => void; +}; + +declare const useAdkRuntime: (_param3: UseAdkRuntimeOptions) => AssistantRuntime; + +declare const useAdkSend: () => (messages: AdkMessage[], config: AdkSendMessageConfig) => Promise<void>; + +declare const useAdkSessionState: () => Record<string, unknown>; + +declare const useAdkSubmitAuth: () => (toolCallId: string, credential: AdkAuthCredential) => Promise<void>; + +declare const useAdkSubmitInput: () => (toolCallId: string, result: ReadonlyJSONValue) => Promise<void>; + +declare const useAdkTempState: () => Record<string, unknown>; + +declare const useAdkToolConfirmations: () => AdkToolConfirmation[]; + +declare const useAdkUserState: () => Record<string, unknown>; + +declare namespace useExternalMessageConverter { + type Message = (ThreadMessageLike & { + readonly convertConfig?: { + readonly joinStrategy?: JoinStrategy; + }; + }) | { + role: "tool"; + toolCallId: string; + toolName?: string | undefined; + result: any; + artifact?: any; + isError?: boolean; + messages?: readonly ThreadMessage[]; + }; + type Metadata = { + readonly toolStatuses?: Record<string, ToolExecutionStatus>; + readonly error?: ReadonlyJSONValue; + readonly messageTiming?: Record<string, MessageTiming>; + }; + type Callback<T> = (message: T, metadata: Metadata) => Message | Message[]; +} + +declare const useExternalMessageConverter: <T extends WeakKey>(_param4: { + callback: useExternalMessageConverter.Callback<T>; + messages: T[]; + isRunning: boolean; + joinStrategy?: JoinStrategy | undefined; + metadata?: useExternalMessageConverter.Metadata | undefined; +}) => ThreadMessage[]; + +export { entry_root_exports as entry_root, entry_server_exports as entry_server }; diff --git a/api-surface/assistant-ui__react-hook-form.ts b/api-surface/assistant-ui__react-hook-form.ts new file mode 100644 index 0000000..6a82065 --- /dev/null +++ b/api-surface/assistant-ui__react-hook-form.ts @@ -0,0 +1,420 @@ +import "@standard-schema/spec"; + +import { ComponentType } from "react"; + +import { FieldValues, UseFormProps, UseFormReturn } from "react-hook-form"; + +import { z } from "zod"; + +type BaseAttachment = { + id: string; + type: "image" | "document" | "file" | (string & {}); + name: string; + contentType?: string | undefined; + file?: File; + content?: ThreadUserMessagePart[]; +}; + +type BaseThreadMessage = { + readonly status?: ThreadAssistantMessage["status"]; + readonly metadata: { + readonly unstable_state?: ReadonlyJSONValue; + readonly unstable_annotations?: readonly ReadonlyJSONValue[]; + readonly unstable_data?: readonly ReadonlyJSONValue[]; + readonly steps?: readonly ThreadStep[]; + readonly submittedFeedback?: { + readonly type: "negative" | "positive"; + }; + readonly timing?: MessageTiming; + readonly isOptimistic?: boolean; + readonly custom: Record<string, unknown>; + }; + readonly attachments?: ThreadUserMessage["attachments"]; +}; + +type CompleteAttachment = BaseAttachment & { + status: CompleteAttachmentStatus; + content: ThreadUserMessagePart[]; +}; + +type CompleteAttachmentStatus = { + type: "complete"; +}; + +type DataMessagePart<T = any> = { + readonly type: "data"; + readonly name: string; + readonly data: T; +}; + +type FileMessagePart = { + readonly type: "file"; + readonly filename?: string; + readonly data: string; + readonly mimeType: string; + readonly parentId?: string; +}; + +type GenerativeUIMessagePart = { + readonly type: "generative-ui"; + readonly spec: GenerativeUISpec; + readonly id?: string; + readonly parentId?: string; +}; + +type GenerativeUINode = string | { + readonly component: string; + readonly props?: Record<string, unknown>; + readonly children?: readonly GenerativeUINode[]; + readonly key?: string; +}; + +type GenerativeUISpec = { + readonly root: GenerativeUINode | readonly GenerativeUINode[]; +}; + +type ImageMessagePart = { + readonly type: "image"; + readonly image: string; + readonly filename?: string; +}; + +type McpAppMetadata = { + readonly resourceUri: string; + readonly mimeType?: string; + readonly visibility?: readonly ("app" | "model")[]; +}; + +type MessageCommonProps = { + readonly id: string; + readonly createdAt: Date; +}; + +type MessagePartState = (ThreadUserMessagePart | ThreadAssistantMessagePart) & { + readonly status: MessagePartStatus | ToolCallMessagePartStatus; +}; + +type MessagePartStatus = { + readonly type: "running"; +} | { + readonly type: "complete"; +} | { + readonly type: "incomplete"; + readonly reason: "cancelled" | "content-filter" | "error" | "length" | "other"; + readonly error?: unknown; +}; + +type MessageStatus = { + readonly type: "running"; +} | { + readonly type: "requires-action"; + readonly reason: "interrupt" | "tool-calls"; +} | { + readonly type: "complete"; + readonly reason: "stop" | "unknown"; +} | { + readonly type: "incomplete"; + readonly reason: "cancelled" | "content-filter" | "error" | "length" | "other" | "tool-calls"; + readonly error?: ReadonlyJSONValue; +}; + +type MessageTiming = { + readonly streamStartTime: number; + readonly firstTokenTime?: number; + readonly totalStreamTime?: number; + readonly tokenCount?: number; + readonly tokensPerSecond?: number; + readonly totalChunks: number; + readonly toolCallCount: number; +}; + +type PartProviderMetadata = { + readonly [providerName: string]: ReadonlyJSONObject; +}; + +type ReadonlyJSONArray = readonly ReadonlyJSONValue[]; + +type ReadonlyJSONObject = { + readonly [key: string]: ReadonlyJSONValue; +}; + +type ReadonlyJSONValue = null | string | number | boolean | ReadonlyJSONObject | ReadonlyJSONArray; + +type ReasoningMessagePart = { + readonly type: "reasoning"; + readonly text: string; + readonly providerMetadata?: PartProviderMetadata; + readonly parentId?: string; +}; + +type SourceMessagePart = { + readonly type: "source"; + readonly sourceType: "url"; + readonly id: string; + readonly url: string; + readonly title?: string; + readonly providerMetadata?: SourceProviderMetadata; + readonly parentId?: string; +} | { + readonly type: "source"; + readonly sourceType: "document"; + readonly id: string; + readonly url?: undefined; + readonly title: string; + readonly mediaType: string; + readonly filename?: string; + readonly providerMetadata?: SourceProviderMetadata; + readonly parentId?: string; +}; + +type SourceProviderMetadata = PartProviderMetadata; + +interface SpeechRecognitionConstructor { + new (): SpeechRecognitionInstance; +} + +interface SpeechRecognitionInstance extends EventTarget { + lang: string; + continuous: boolean; + interimResults: boolean; + start(): void; + stop(): void; + abort(): void; +} + +declare const TOOL_RESPONSE_SYMBOL: unique symbol; + +type TextMessagePart = { + readonly type: "text"; + readonly text: string; + readonly providerMetadata?: PartProviderMetadata; + readonly parentId?: string; +}; + +type ThreadAssistantMessage = MessageCommonProps & { + readonly role: "assistant"; + readonly content: readonly ThreadAssistantMessagePart[]; + readonly status: MessageStatus; + readonly metadata: { + readonly unstable_state: ReadonlyJSONValue; + readonly unstable_annotations: readonly ReadonlyJSONValue[]; + readonly unstable_data: readonly ReadonlyJSONValue[]; + readonly steps: readonly ThreadStep[]; + readonly submittedFeedback?: { + readonly type: "negative" | "positive"; + }; + readonly timing?: MessageTiming; + readonly isOptimistic?: boolean; + readonly custom: Record<string, unknown>; + }; +}; + +type ThreadAssistantMessagePart = TextMessagePart | ReasoningMessagePart | ToolCallMessagePart | SourceMessagePart | FileMessagePart | ImageMessagePart | DataMessagePart | GenerativeUIMessagePart; + +type ThreadMessage = BaseThreadMessage & (ThreadSystemMessage | ThreadUserMessage | ThreadAssistantMessage); + +type ThreadStep = { + readonly messageId?: string; + readonly usage?: { + readonly inputTokens: number; + readonly outputTokens: number; + } | undefined; +}; + +type ThreadSystemMessage = MessageCommonProps & { + readonly role: "system"; + readonly content: readonly [ + TextMessagePart + ]; + readonly metadata: { + readonly unstable_state?: undefined; + readonly unstable_annotations?: undefined; + readonly unstable_data?: undefined; + readonly steps?: undefined; + readonly submittedFeedback?: undefined; + readonly timing?: undefined; + readonly custom: Record<string, unknown>; + }; +}; + +type ThreadUserMessage = MessageCommonProps & { + readonly role: "user"; + readonly content: readonly ThreadUserMessagePart[]; + readonly attachments: readonly CompleteAttachment[]; + readonly metadata: { + readonly unstable_state?: undefined; + readonly unstable_annotations?: undefined; + readonly unstable_data?: undefined; + readonly steps?: undefined; + readonly submittedFeedback?: undefined; + readonly timing?: undefined; + readonly custom: Record<string, unknown>; + }; +}; + +type ThreadUserMessagePart = TextMessagePart | ImageMessagePart | FileMessagePart | DataMessagePart | Unstable_AudioMessagePart; + +type ToolApprovalOption = { + readonly id: string; + readonly kind: ToolApprovalOptionKind | (string & {}); + readonly label?: string; + readonly description?: string; + readonly grants?: readonly string[]; + readonly confirm?: boolean | { + title?: string; + description?: string; + }; +}; + +type ToolApprovalOptionKind = "allow-always" | "allow-once" | "reject-always" | "reject-once"; + +type ToolApprovalResponse = { + readonly approved: boolean; + readonly reason?: string; +} | { + readonly optionId: string; + readonly reason?: string; +} | { + readonly approved: boolean; + readonly optionId: string; + readonly reason?: string; +}; + +type ToolCallMessagePart<TArgs = ReadonlyJSONObject, TResult = unknown> = { + readonly type: "tool-call"; + readonly toolCallId: string; + readonly toolName: string; + readonly args: TArgs; + readonly result?: TResult | undefined; + readonly isError?: boolean | undefined; + readonly argsText: string; + readonly artifact?: unknown; + readonly timing?: ToolCallTiming; + readonly mcp?: ToolCallMessagePartMcpMetadata; + readonly providerMetadata?: PartProviderMetadata; + readonly modelContent?: readonly ToolModelContentPart[] | undefined; + readonly interrupt?: { + type: "human"; + payload: unknown; + }; + readonly approval?: { + readonly id: string; + readonly approved?: boolean; + readonly reason?: string; + readonly isAutomatic?: boolean; + readonly options?: readonly ToolApprovalOption[]; + readonly optionId?: string; + readonly resolution?: "cancelled" | "expired"; + }; + readonly parentId?: string; + readonly messages?: readonly ThreadMessage[]; +}; + +type ToolCallMessagePartComponent<TArgs = any, TResult = any> = ComponentType<ToolCallMessagePartProps<TArgs, TResult>>; + +type ToolCallMessagePartMcpMetadata = { + readonly app?: McpAppMetadata; +}; + +type ToolCallMessagePartProps<TArgs = any, TResult = unknown> = MessagePartState & ToolCallMessagePart<TArgs, TResult> & { + addResult: (result: TResult | ToolResponse<TResult>) => void; + resume: (payload: unknown) => void; + respondToApproval: (response: ToolApprovalResponse) => void; +}; + +type ToolCallMessagePartStatus = { + readonly type: "requires-action"; + readonly reason: "interrupt"; +} | MessagePartStatus; + +type ToolCallTiming = { + readonly startedAt: number; + readonly completedAt?: number; +}; + +type ToolModelContentPart = { + readonly type: "text"; + readonly text: string; +} | { + readonly type: "file"; + readonly data: string; + readonly mediaType: string; + readonly filename?: string; +}; + +declare class ToolResponse<TResult> { + get [TOOL_RESPONSE_SYMBOL](): boolean; + readonly artifact?: ReadonlyJSONValue; + readonly result: TResult; + readonly isError: boolean; + readonly modelContent?: readonly ToolModelContentPart[]; + readonly messages?: ReadonlyJSONValue; + constructor(options: ToolResponseLike<TResult>); + static [Symbol.hasInstance](obj: unknown): obj is ToolResponse<ReadonlyJSONValue>; + static toResponse(result: any | ToolResponse<any>): ToolResponse<any>; +} + +type ToolResponseLike<TResult> = { + result: TResult; + artifact?: ReadonlyJSONValue | undefined; + isError?: boolean | undefined; + modelContent?: readonly ToolModelContentPart[] | undefined; + messages?: ReadonlyJSONValue | undefined; +}; + +type Unstable_AudioMessagePart = { + readonly type: "audio"; + readonly audio: { + readonly data: string; + readonly format: "mp3" | "wav"; + }; +}; + +type UseAssistantFormProps<TFieldValues extends FieldValues, TContext, TTransformedValues> = UseFormProps<TFieldValues, TContext, TTransformedValues> & { + assistant?: { + tools?: { + set_form_field?: { + render?: ToolCallMessagePartComponent<z.infer<(typeof formTools.set_form_field)["parameters"]>, unknown> | undefined; + } | undefined; + submit_form?: { + render?: ToolCallMessagePartComponent<z.infer<(typeof formTools.submit_form)["parameters"]>, unknown> | undefined; + } | undefined; + reset_form?: { + render?: ToolCallMessagePartComponent<z.infer<(typeof formTools.reset_form)["parameters"]>, unknown> | undefined; + } | undefined; + } | undefined; + } | undefined; +}; + +declare const formTools: { + set_form_field: { + description: string; + parameters: z.ZodObject<{ + name: z.ZodString; + value: z.ZodString; + }, z.core.$strip>; + }; + submit_form: { + description: string; + parameters: z.ZodObject<{}, z.core.$strip>; + }; + reset_form: { + description: string; + parameters: z.ZodObject<{}, z.core.$strip>; + }; +}; + +declare global { + interface Window { + SpeechRecognition?: SpeechRecognitionConstructor; + webkitSpeechRecognition?: SpeechRecognitionConstructor; + } +} + +declare namespace entry_root_exports { + export { UseAssistantFormProps, formTools, useAssistantForm }; +} + +declare const useAssistantForm: <TFieldValues extends FieldValues = FieldValues, TContext = any, TTransformedValues = TFieldValues>(props?: UseAssistantFormProps<TFieldValues, TContext, TTransformedValues>) => UseFormReturn<TFieldValues, TContext, TTransformedValues>; + +export { entry_root_exports as entry_root }; diff --git a/api-surface/assistant-ui__react-ink-markdown.ts b/api-surface/assistant-ui__react-ink-markdown.ts new file mode 100644 index 0000000..ac93a36 --- /dev/null +++ b/api-surface/assistant-ui__react-ink-markdown.ts @@ -0,0 +1,45 @@ +import { RenderOptions, Theme, Theme as Theme$1, ThemeName, ThemeName as ThemeName$1 } from "markdansi"; + +declare const MarkdownText: { + (_param0: MarkdownTextProps): import("react").JSX.Element; + displayName: string; +}; + +declare const MarkdownTextPrimitive: { + (_param1: MarkdownTextPrimitiveProps): import("react").JSX.Element; + displayName: string; +}; + +type MarkdownTextPrimitiveProps = Omit<MarkdownTextProps, "text"> & { + preprocess?: ((text: string) => string) | undefined; +}; + +type MarkdownTextProps = { + text: string; + highlighter?: (code: string, lang?: string) => string; + theme?: ThemeName | Theme; + width?: number; + wrap?: boolean; + codeBox?: boolean; + codeGutter?: boolean; + codeWrap?: boolean; + hyperlinks?: boolean; + tableBorder?: "ascii" | "none" | "unicode"; + tablePadding?: number; + tableDense?: boolean; + quotePrefix?: string; + listIndent?: number; +}; + +type UseShikiHighlighterOptions = { + theme?: string; + langs?: string[]; +}; + +declare namespace entry_root_exports { + export { MarkdownText, MarkdownTextPrimitive, MarkdownTextPrimitiveProps, MarkdownTextProps, RenderOptions, Theme$1 as Theme, ThemeName$1 as ThemeName, UseShikiHighlighterOptions, useShikiHighlighter }; +} + +declare function useShikiHighlighter(options?: UseShikiHighlighterOptions): ((code: string, lang?: string) => string) | undefined; + +export { entry_root_exports as entry_root }; diff --git a/api-surface/assistant-ui__react-ink.ts b/api-surface/assistant-ui__react-ink.ts new file mode 100644 index 0000000..bbb106f --- /dev/null +++ b/api-surface/assistant-ui__react-ink.ts @@ -0,0 +1,4682 @@ +import { StandardSchemaV1 } from "@standard-schema/spec"; + +import { Box, Text } from "ink"; + +import Spinner from "ink-spinner"; + +import React, { ComponentProps, ComponentType, FC, PropsWithChildren, ReactElement, ReactNode } from "react"; + +declare const ActionBarCopy: (_param0: ActionBarCopyProps) => import("react").JSX.Element; + +type ActionBarCopyProps = Omit<PressableProps, "children" | "onPress"> & UseActionBarCopyOptions & { + children: ReactNode | ((props: { + isCopied: boolean; + }) => ReactNode); +}; + +declare const ActionBarEdit: (_param1: ActionBarEditProps) => import("react").JSX.Element; + +type ActionBarEditProps = Omit<PressableProps, "onPress"> & { + children: ReactNode; +}; + +declare const ActionBarFeedbackNegative: (_param2: ActionBarFeedbackNegativeProps) => import("react").JSX.Element; + +type ActionBarFeedbackNegativeProps = Omit<PressableProps, "children" | "onPress"> & { + children: ReactNode | ((props: { + isSubmitted: boolean; + }) => ReactNode); +}; + +declare const ActionBarFeedbackPositive: (_param3: ActionBarFeedbackPositiveProps) => import("react").JSX.Element; + +type ActionBarFeedbackPositiveProps = Omit<PressableProps, "children" | "onPress"> & { + children: ReactNode | ((props: { + isSubmitted: boolean; + }) => ReactNode); +}; + +declare const ActionBarReload: (_param4: ActionBarReloadProps) => import("react").JSX.Element; + +type ActionBarReloadProps = Omit<PressableProps, "onPress"> & { + children: ReactNode; +}; + +type AddToolResultOptions = { + messageId: string; + toolName: string; + toolCallId: string; + result: ReadonlyJSONValue; + isError: boolean; + artifact?: ReadonlyJSONValue | undefined; + modelContent?: readonly ToolModelContentPart[] | undefined; +}; + +type AncestorsOf<K extends ClientNames, Seen extends ClientNames = never> = K extends Seen ? never : ParentOf<K> extends never ? never : ParentOf<K> | AncestorsOf<ParentOf<K>, Seen | K>; + +type AppendMessage = Omit<ThreadMessage, "id"> & { + parentId: string | null; + sourceId: string | null; + runConfig: RunConfig | undefined; + startRun?: boolean | undefined; + steer?: boolean | undefined; +}; + +type AsNumber<K> = K extends `${infer N extends number}` ? N | K : never; + +type AssistantClient = { + [K in ClientNames]: AssistantClientAccessor<K>; +} & { + subscribe(listener: () => void): Unsubscribe; + on<TEvent extends AssistantEventName>(selector: AssistantEventSelector<TEvent>, callback: AssistantEventCallback<TEvent>): Unsubscribe; +}; + +type AssistantClientAccessor<K extends ClientNames> = (() => ClientSchemas[K]["methods"]) & (ClientMeta<K> | { + source: "root"; + query: Record<string, never>; +} | { + source: null; + query: null; +}) & { + name: K; +}; + +declare class AssistantCloud { + readonly threads: AssistantCloudThreads; + readonly auth: { + tokens: AssistantCloudAuthTokens; + }; + readonly runs: AssistantCloudRuns; + readonly files: AssistantCloudFiles; + readonly telemetry: AssistantCloudTelemetryConfig; + constructor(config: AssistantCloudConfig); +} + +declare class AssistantCloudAPI { + _auth: AssistantCloudAuthStrategy; + _baseUrl: string; + constructor(config: AssistantCloudConfig); + initializeAuth(): Promise<boolean>; + makeRawRequest(endpoint: string, options?: MakeRequestOptions): Promise<Response>; + makeRequest(endpoint: string, options?: MakeRequestOptions): Promise<any>; +} + +type AssistantCloudAuthStrategy = { + readonly strategy: "anon" | "api-key" | "jwt"; + getAuthHeaders(): Promise<Record<string, string> | false>; + readAuthHeaders(headers: Headers): void; +}; + +declare class AssistantCloudAuthTokens { + private cloud; + constructor(cloud: AssistantCloudAPI); + create(): Promise<AssistantCloudAuthTokensCreateResponse>; +} + +type AssistantCloudAuthTokensCreateResponse = { + token: string; +}; + +type AssistantCloudConfig = ({ + baseUrl: string; + authToken: () => Promise<string | null>; +} | { + baseUrl?: string; + apiKey: string; + userId: string; + workspaceId: string; +} | { + baseUrl: string; + anonymous: true; +}) & { + telemetry?: boolean | AssistantCloudTelemetryConfig; +}; + +declare class AssistantCloudFiles { + private cloud; + constructor(cloud: AssistantCloudAPI); + pdfToImages(body: PdfToImagesRequestBody): Promise<PdfToImagesResponse>; + generatePresignedUploadUrl(body: GeneratePresignedUploadUrlRequestBody): Promise<GeneratePresignedUploadUrlResponse>; +} + +type AssistantCloudMessageCreateResponse = { + message_id: string; +}; + +type AssistantCloudRunReport = { + thread_id: string; + status: "completed" | "error" | "incomplete"; + total_steps?: number; + tool_calls?: ReportToolCall[]; + steps?: { + input_tokens?: number; + output_tokens?: number; + reasoning_tokens?: number; + cached_input_tokens?: number; + tool_calls?: ReportToolCall[]; + start_ms?: number; + end_ms?: number; + }[]; + input_tokens?: number; + output_tokens?: number; + reasoning_tokens?: number; + cached_input_tokens?: number; + model_id?: string; + provider_type?: string; + duration_ms?: number; + output_text?: string; + metadata?: Record<string, unknown>; +}; + +declare class AssistantCloudRuns { + private cloud; + constructor(cloud: AssistantCloudAPI); + __internal_getAssistantOptions(assistantId: string): { + api: string; + headers: () => Promise<{ + Accept: string; + }>; + body: { + assistant_id: string; + response_format: string; + thread_id: string; + }; + }; + stream(body: AssistantCloudRunsStreamBody): Promise<AssistantStream>; + report(body: AssistantCloudRunReport): Promise<{ + run_id: string; + }>; +} + +type AssistantCloudRunsStreamBody = { + thread_id: string; + assistant_id: "system/thread_title"; + messages: readonly unknown[]; +}; + +type AssistantCloudTelemetryConfig = { + enabled?: boolean; + beforeReport?: (report: AssistantCloudRunReport) => AssistantCloudRunReport | null; +}; + +type AssistantCloudThreadMessageCreateBody = { + parent_id: string | null; + format: "aui/v0" | string; + content: ReadonlyJSONObject; +}; + +type AssistantCloudThreadMessageListQuery = { + format?: string; +}; + +type AssistantCloudThreadMessageListResponse = { + messages: CloudMessage[]; +}; + +type AssistantCloudThreadMessageUpdateBody = { + content: ReadonlyJSONObject; +}; + +declare class AssistantCloudThreadMessages { + private cloud; + constructor(cloud: AssistantCloudAPI); + list(threadId: string, query?: AssistantCloudThreadMessageListQuery): Promise<AssistantCloudThreadMessageListResponse>; + create(threadId: string, body: AssistantCloudThreadMessageCreateBody): Promise<AssistantCloudMessageCreateResponse>; + update(threadId: string, messageId: string, body: AssistantCloudThreadMessageUpdateBody): Promise<void>; +} + +declare class AssistantCloudThreads { + private cloud; + readonly messages: AssistantCloudThreadMessages; + constructor(cloud: AssistantCloudAPI); + list(query?: AssistantCloudThreadsListQuery): Promise<AssistantCloudThreadsListResponse>; + get(threadId: string): Promise<CloudThread>; + create(body: AssistantCloudThreadsCreateBody): Promise<AssistantCloudThreadsCreateResponse>; + update(threadId: string, body: AssistantCloudThreadsUpdateBody): Promise<void>; + delete(threadId: string): Promise<void>; +} + +type AssistantCloudThreadsCreateBody = { + title?: string | undefined; + last_message_at: Date; + metadata?: unknown | undefined; + external_id?: string | undefined; +}; + +type AssistantCloudThreadsCreateResponse = { + thread_id: string; +}; + +type AssistantCloudThreadsListQuery = { + is_archived?: boolean; + limit?: number; + after?: string; +}; + +type AssistantCloudThreadsListResponse = { + threads: CloudThread[]; +}; + +type AssistantCloudThreadsUpdateBody = { + title?: string | undefined; + last_message_at?: Date | undefined; + metadata?: unknown | undefined; + is_archived?: boolean | undefined; +}; + +type AssistantContextConfig = { + getContext: () => string; + disabled?: boolean | undefined; +}; + +type AssistantDataUI = FC & { + unstable_data: AssistantDataUIProps; +}; + +type AssistantDataUIProps<T = any> = { + name: string; + render: DataMessagePartComponent<T>; +}; + +type AssistantEventCallback<TEvent extends AssistantEventName> = (payload: AssistantEventPayload[TEvent]) => void; + +type AssistantEventName = keyof AssistantEventPayload; + +type AssistantEventPayload = ClientEventMap & { + "*": WildcardPayload; +}; + +type AssistantEventScope<TEvent extends AssistantEventName> = "*" | EventSource<TEvent> | (EventSource<TEvent> extends ClientNames ? AncestorsOf<EventSource<TEvent>> : never); + +type AssistantEventSelector<TEvent extends AssistantEventName> = TEvent | { + scope: AssistantEventScope<TEvent>; + event: TEvent; +}; + +type AssistantInstructionsConfig = { + disabled?: boolean | undefined; + instruction: string; +}; + +type AssistantInteractableProps = { + description: string; + stateSchema: InteractableStateSchema; + initialState: unknown; + id?: string; + selected?: boolean; +}; + +type AssistantRuntime = { + readonly threads: ThreadListRuntime; + readonly thread: ThreadRuntime; + registerModelContextProvider(provider: ModelContextProvider): Unsubscribe$1; +}; + +type AssistantRuntimeCore = { + readonly threads: ThreadListRuntimeCore; + registerModelContextProvider: (provider: ModelContextProvider) => Unsubscribe$1; + getModelContextProvider: () => ModelContextProvider; + readonly RenderComponent?: ((...args: any[]) => unknown) | undefined; +}; + +declare class AssistantRuntimeImpl implements AssistantRuntime { + private readonly _core; + readonly threads: ThreadListRuntimeImpl; + readonly _thread: ThreadRuntime; + constructor(_core: AssistantRuntimeCore); + protected __internal_bindMethods(): void; + get thread(): ThreadRuntime; + registerModelContextProvider(provider: ModelContextProvider): Unsubscribe$1; +} + +declare const AssistantRuntimeProvider: import("react").MemoExoticComponent<(_param5: { + runtime: AssistantRuntime; + aui?: AssistantClient | null; + children: ReactNode; +}) => import("react").JSX.Element>; + +type AssistantState = { + [K in ClientNames]: ClientSchemas[K]["methods"] extends { + getState: () => infer S; + } ? S : never; +}; + +type AssistantStream = ReadableStream<AssistantStreamChunk>; + +declare const AssistantStream: { + toResponse(stream: AssistantStream, transformer: AssistantStreamEncoder): Response; + fromResponse(response: Response, transformer: ReadableWritablePair<AssistantStreamChunk, Uint8Array<ArrayBuffer>>): ReadableStream<AssistantStreamChunk>; + toByteStream(stream: AssistantStream, transformer: ReadableWritablePair<Uint8Array<ArrayBuffer>, AssistantStreamChunk>): ReadableStream<Uint8Array<ArrayBuffer>>; + fromByteStream(readable: ReadableStream<Uint8Array<ArrayBuffer>>, transformer: ReadableWritablePair<AssistantStreamChunk, Uint8Array<ArrayBuffer>>): ReadableStream<AssistantStreamChunk>; +}; + +type AssistantStreamChunk = { + readonly path: readonly number[]; +} & ({ + readonly type: "part-start"; + readonly part: PartInit; +} | { + readonly type: "part-finish"; +} | { + readonly type: "tool-call-args-text-finish"; +} | { + readonly type: "text-delta"; + readonly textDelta: string; +} | { + readonly type: "annotations"; + readonly annotations: ReadonlyJSONValue[]; +} | { + readonly type: "data"; + readonly data: ReadonlyJSONValue[]; +} | { + readonly type: "step-start"; + readonly messageId: string; +} | { + readonly type: "step-finish"; + readonly finishReason: "content-filter" | "error" | "length" | "other" | "stop" | "tool-calls" | "unknown"; + readonly usage: { + readonly inputTokens: number; + readonly outputTokens: number; + }; + readonly isContinued: boolean; +} | { + readonly type: "message-finish"; + readonly finishReason: "content-filter" | "error" | "length" | "other" | "stop" | "tool-calls" | "unknown"; + readonly usage: { + readonly inputTokens: number; + readonly outputTokens: number; + }; +} | { + readonly type: "result"; + readonly artifact?: ReadonlyJSONValue; + readonly result: ReadonlyJSONValue; + readonly isError: boolean; + readonly modelContent?: readonly ToolModelContentPart[]; + readonly messages?: ReadonlyJSONValue; +} | { + readonly type: "error"; + readonly error: string; +} | { + readonly type: "update-state"; + readonly operations: ObjectStreamOperation[]; +}); + +type AssistantStreamEncoder = ReadableWritablePair<Uint8Array<ArrayBuffer>, AssistantStreamChunk> & { + headers?: Headers; +}; + +type AssistantTool = FC & { + unstable_tool: AssistantToolProps<any, any>; +}; + +type AssistantToolProps<TArgs extends Record<string, unknown>, TResult> = AssistantToolProps$1<TArgs, TResult> & { + render?: ToolCallMessagePartComponent<TArgs, TResult> | undefined; + renderText?: ToolCallText<TArgs, TResult> | undefined; +}; + +type AssistantToolProps$1<TArgs extends Record<string, unknown>, TResult> = Tool<TArgs, TResult> & { + toolName: string; + render?: unknown; +}; + +type AssistantToolUI = FC & { + unstable_tool: AssistantToolUIProps<any, any>; +}; + +type AssistantToolUIProps<TArgs, TResult> = { + toolName: string; + render: ToolCallMessagePartComponent<TArgs, TResult>; + display?: "inline" | "standalone"; +}; + +type AsyncIterableStream<T> = AsyncIterable<T> & ReadableStream<T>; + +type Attachment = PendingAttachment | CompleteAttachment; + +type AttachmentAdapter = { + accept: string; + add(state: { + file: File; + }): Promise<PendingAttachment> | AsyncGenerator<PendingAttachment, void>; + remove(attachment: Attachment): Promise<void>; + send(attachment: PendingAttachment): Promise<CompleteAttachment>; +}; + +type AttachmentAddErrorEvent = { + readonly reason: AttachmentAddErrorReason; + readonly message: string; + readonly attachmentId?: string; + readonly error?: Error; +}; + +type AttachmentAddErrorReason = "adapter-error" | "no-adapter" | "not-accepted"; + +declare const AttachmentName: FC<AttachmentNameProps>; + +type AttachmentNameProps = ComponentProps<typeof Text>; + +declare const AttachmentRemove: (_param6: AttachmentRemoveProps) => import("react").JSX.Element; + +type AttachmentRemoveProps = Omit<PressableProps, "onPress"> & { + children: ReactNode; +}; + +declare const AttachmentRoot: (_param7: AttachmentRootProps) => import("react").JSX.Element; + +type AttachmentRootProps = ComponentProps<typeof Box> & { + children: ReactNode; +}; + +type AttachmentRuntime<TSource extends AttachmentRuntimeSource = AttachmentRuntimeSource> = { + readonly path: AttachmentRuntimePath & { + attachmentSource: TSource; + }; + readonly source: TSource; + getState(): AttachmentState$1 & { + source: TSource; + }; + remove(): Promise<void>; + subscribe(callback: () => void): Unsubscribe$1; +}; + +declare abstract class AttachmentRuntimeImpl<Source extends AttachmentRuntimeSource = AttachmentRuntimeSource> implements AttachmentRuntime { + private _core; + get path(): AttachmentRuntimePath & { + attachmentSource: Source; + }; + abstract get source(): Source; + constructor(_core: AttachmentSnapshotBinding<Source>); + protected __internal_bindMethods(): void; + getState(): AttachmentState$1 & { + source: Source; + }; + abstract remove(): Promise<void>; + subscribe(callback: () => void): Unsubscribe$1; +} + +type AttachmentRuntimePath = ((MessageRuntimePath & { + readonly attachmentSource: "edit-composer" | "message"; +}) | (ThreadRuntimePath & { + readonly attachmentSource: "thread-composer"; +})) & { + readonly attachmentSelector: { + readonly type: "index"; + readonly index: number; + } | { + readonly type: "index"; + readonly index: number; + } | { + readonly type: "index"; + readonly index: number; + }; +}; + +type AttachmentRuntimeSource = AttachmentState$1["source"]; + +type AttachmentSnapshotBinding<Source extends AttachmentRuntimeSource> = SubscribableWithState<AttachmentState$1 & { + source: Source; +}, AttachmentRuntimePath & { + attachmentSource: Source; +}>; + +type AttachmentState = Attachment; + +type AttachmentState$1 = ThreadComposerAttachmentState | EditComposerAttachmentState | MessageAttachmentState; + +declare const AttachmentStatus: FC<AttachmentStatusProps>; + +type AttachmentStatusProps = Omit<ComponentProps<typeof Text>, "children"> & { + showComplete?: boolean | undefined; +}; + +declare const AttachmentThumb: FC<AttachmentThumbProps>; + +type AttachmentThumbProps = ComponentProps<typeof Text>; + +declare namespace AuiIf { + type Props = PropsWithChildren<{ + condition: AuiIf.Condition; + }>; + type Condition = (state: AssistantState) => boolean; +} + +declare const AuiIf: FC<AuiIf.Props>; + +declare const AuiProvider: (_param8: { + value: AssistantClient; + children: React.ReactNode; +}) => React.ReactElement; + +type AuiToolOverride<TArgs extends Record<string, unknown> = Record<string, unknown>, TResult = unknown> = Partial<Tool<TArgs, TResult>>; + +type AuiToolOverrides = Record<string, AuiToolOverride<any, any>>; + +type BackendTool<TArgs extends Record<string, unknown> = Record<string, unknown>, TResult = unknown> = ToolBase<TArgs, TResult> & { + type: "backend"; + description?: undefined; + parameters?: undefined; + disabled?: undefined; + execute?: undefined; + toModelOutput?: undefined; + experimental_onSchemaValidationError?: undefined; + providerOptions?: undefined; +}; + +type BackendToolDeclaration<TArgs extends Record<string, unknown> = Record<string, unknown>, TResult = unknown> = ToolBase<TArgs, TResult> & { + type: "backend"; + description?: string | undefined; + parameters?: StandardSchemaV1<TArgs> | JSONSchema7 | undefined; + disabled?: boolean; + execute?: ToolExecuteFunction<TArgs, TResult>; + toModelOutput?: ToolModelOutputFunction<TArgs, TResult>; + experimental_onSchemaValidationError?: OnSchemaValidationErrorFunction<TResult>; + providerOptions?: ProviderOptions; +}; + +declare abstract class BaseAssistantRuntimeCore implements AssistantRuntimeCore { + protected readonly _contextProvider: CompositeContextProvider; + abstract get threads(): ThreadListRuntimeCore; + registerModelContextProvider(provider: ModelContextProvider): Unsubscribe$1; + getModelContextProvider(): ModelContextProvider; +} + +type BaseAttachment = { + id: string; + type: "image" | "document" | "file" | (string & {}); + name: string; + contentType?: string | undefined; + file?: File; + content?: ThreadUserMessagePart[]; +}; + +declare abstract class BaseComposerRuntimeCore extends BaseSubscribable implements ComposerRuntimeCore { + readonly isEditing = true; + protected abstract getAttachmentAdapter(): AttachmentAdapter | undefined; + protected abstract getDictationAdapter(): DictationAdapter | undefined; + protected enrichWithComposerMetadata<T extends { + metadata?: { + custom?: Record<string, unknown>; + }; + }>(message: T, composerMetadata: Record<string, unknown> | undefined): T; + get attachmentAccept(): string; + private _attachments; + get attachments(): readonly Attachment[]; + protected setAttachments(value: readonly Attachment[]): void; + abstract get canCancel(): boolean; + abstract get canSend(): boolean; + get isEmpty(): boolean; + private _text; + get text(): string; + private _role; + get role(): "assistant" | "system" | "user"; + private _runConfig; + get runConfig(): RunConfig; + private _quote; + get quote(): QuoteInfo | undefined; + setQuote(quote: QuoteInfo | undefined): void; + setText(value: string): void; + setRole(role: MessageRole): void; + setRunConfig(runConfig: RunConfig): void; + private _emptyTextAndAttachments; + private _onClearAttachments; + reset(): Promise<void>; + clearAttachments(): Promise<void>; + send(options?: SendOptions): Promise<void>; + cancel(): void; + get queue(): readonly QueueItemState[]; + steerQueueItem(_queueItemId: string): void; + removeQueueItem(_queueItemId: string): void; + protected abstract handleSend(message: Omit<AppendMessage, "parentId" | "sourceId">, options?: SendOptions): void; + protected abstract handleCancel(): void; + addAttachment(fileOrAttachment: File | CreateAttachment): Promise<void>; + private _safeEmitAttachmentAddError; + removeAttachment(attachmentId: string): Promise<void>; + private _dictation; + private _dictationSession; + private _dictationUnsubscribes; + private _dictationBaseText; + private _currentInterimText; + private _dictationSessionIdCounter; + private _activeDictationSessionId; + private _isCleaningDictation; + get dictation(): DictationState | undefined; + private _isActiveSession; + startDictation(): void; + stopDictation(): void; + private _cleanupDictation; + private _eventSubscribers; + protected _notifyEventSubscribers<E extends ComposerRuntimeEventType>(event: E, payload: ComposerRuntimeEventPayload[E]): void; + unstable_on<E extends ComposerRuntimeEventType>(event: E, callback: ComposerRuntimeEventCallback<E>): () => void; +} + +type BaseComposerState = { + readonly canCancel: boolean; + readonly canSend: boolean; + readonly isEditing: boolean; + readonly isEmpty: boolean; + readonly text: string; + readonly role: MessageRole; + readonly attachments: readonly Attachment[]; + readonly runConfig: RunConfig; + readonly attachmentAccept: string; + readonly dictation: DictationState | undefined; + readonly quote: QuoteInfo | undefined; + readonly queue: readonly QueueItemState[]; +}; + +declare class BaseSubscribable { + private _subscribers; + subscribe(callback: () => void): Unsubscribe$1; + waitForUpdate(): Promise<void>; + protected _notifySubscribers(): void; +} + +type BaseThreadMessage = { + readonly status?: ThreadAssistantMessage["status"]; + readonly metadata: { + readonly unstable_state?: ReadonlyJSONValue; + readonly unstable_annotations?: readonly ReadonlyJSONValue[]; + readonly unstable_data?: readonly ReadonlyJSONValue[]; + readonly steps?: readonly ThreadStep[]; + readonly submittedFeedback?: { + readonly type: "negative" | "positive"; + }; + readonly timing?: MessageTiming; + readonly isOptimistic?: boolean; + readonly custom: Record<string, unknown>; + }; + readonly attachments?: ThreadUserMessage["attachments"]; +}; + +declare const BranchPickerCount: (props: BranchPickerCountProps) => import("react").JSX.Element; + +type BranchPickerCountProps = ComponentProps<typeof Text>; + +declare const BranchPickerNext: (_param9: BranchPickerNextProps) => import("react").JSX.Element; + +type BranchPickerNextProps = Omit<PressableProps, "onPress"> & { + children: ReactNode; +}; + +declare const BranchPickerNumber: (props: BranchPickerNumberProps) => import("react").JSX.Element; + +type BranchPickerNumberProps = ComponentProps<typeof Text>; + +declare const BranchPickerPrevious: (_param10: BranchPickerPreviousProps) => import("react").JSX.Element; + +type BranchPickerPreviousProps = Omit<PressableProps, "onPress"> & { + children: ReactNode; +}; + +declare const ChainOfThoughtAccordionTrigger: (_param11: ChainOfThoughtAccordionTriggerProps) => import("react").JSX.Element; + +type ChainOfThoughtAccordionTriggerProps = Omit<PressableProps, "onPress"> & { + children: ReactNode; +}; + +declare const ChainOfThoughtByIndicesProvider: FC<PropsWithChildren<{ + startIndex: number; + endIndex: number; +}>>; + +declare const ChainOfThoughtClient: Resource<ClientOutput<"chainOfThought">, [ + { + parts: readonly ChainOfThoughtPart[]; + getMessagePart: (selector: { + index: number; + }) => PartMethods; + } +]>; + +type ChainOfThoughtPart = Extract<PartState, { + type: "tool-call"; +} | { + type: "reasoning"; +}>; + +declare const ChainOfThoughtPartByIndexProvider: FC<PropsWithChildren<{ + index: number; +}>>; + +type ChainOfThoughtPartsComponentConfig = { + Reasoning?: ReasoningMessagePartComponent | undefined; + tools?: { + Fallback?: ToolCallMessagePartComponent | undefined; + }; + Layout?: ComponentType<PropsWithChildren> | undefined; +}; + +declare namespace ChainOfThoughtPrimitiveParts { + type Props = { + components?: ChainOfThoughtPartsComponentConfig; + children?: never; + } | { + children: (value: { + part: PartState; + }) => ReactNode; + components?: never; + }; +} + +declare const ChainOfThoughtPrimitiveParts: FC<ChainOfThoughtPrimitiveParts.Props>; + +declare const ChainOfThoughtRoot: (_param12: ChainOfThoughtRootProps) => import("react").JSX.Element; + +type ChainOfThoughtRootProps = ComponentProps<typeof Box> & { + children: ReactNode; +}; + +type ChatModelAdapter = { + run(options: ChatModelRunOptions): Promise<ChatModelRunResult> | AsyncGenerator<ChatModelRunResult, void>; +}; + +type ChatModelRunOptions = { + readonly messages: readonly ThreadMessage[]; + readonly runConfig: RunConfig; + readonly abortSignal: AbortSignal; + readonly context: ModelContext$1; + readonly unstable_assistantMessageId?: string | undefined; + readonly unstable_threadId?: string | undefined; + readonly unstable_parentId?: string | null | undefined; + unstable_getMessage(): ThreadMessage; +}; + +type ChatModelRunResult = { + readonly content?: readonly ThreadAssistantMessagePart[] | undefined; + readonly status?: MessageStatus | undefined; + readonly metadata?: { + readonly unstable_state?: ReadonlyJSONValue; + readonly unstable_annotations?: readonly ReadonlyJSONValue[] | undefined; + readonly unstable_data?: readonly ReadonlyJSONValue[] | undefined; + readonly steps?: readonly ThreadStep[] | undefined; + readonly timing?: MessageTiming | undefined; + readonly custom?: Record<string, unknown> | undefined; + }; +}; + +declare const ChecklistItem: { + (_param13: ChecklistItemProps): import("react").JSX.Element; + displayName: string; +}; + +type ChecklistItemData = { + id: string; + text: string; + status: ChecklistItemStatus; + detail?: string; + children?: ChecklistItemData[]; +}; + +type ChecklistItemProps = ComponentProps<typeof Box> & { + item: ChecklistItemData; + depth?: number | undefined; + maxDepth?: number | undefined; +}; + +type ChecklistItemStatus = "complete" | "error" | "pending" | "running"; + +declare const ChecklistProgress: { + (_param14: ChecklistProgressProps): import("react").JSX.Element; + displayName: string; +}; + +type ChecklistProgressProps = ComponentProps<typeof Box> & { + items: ChecklistItemData[]; +}; + +declare const ChecklistRoot: { + (_param15: ChecklistRootProps): import("react").JSX.Element; + displayName: string; +}; + +type ChecklistRootProps = ComponentProps<typeof Box> & { + children: ReactNode; +}; + +type ClientElement<K extends ClientNames> = ResourceElement<ClientOutput<K>>; + +type ClientError<E extends string> = { + methods: Record<E, () => E>; + meta: { + source: ClientNames; + query: Record<E, E>; + }; + events: Record<`${E}.`, E>; +}; + +type ClientEventMap = UnionToIntersection<{ + [K in ClientNames]: ClientEvents<K>; +}[ClientNames]>; + +type ClientEvents<K extends ClientNames> = "events" extends keyof ClientSchemas[K] ? ClientSchemas[K]["events"] extends ClientEventsType<K> ? ClientSchemas[K]["events"] : never : never; + +type ClientEventsType<K extends ClientNames> = Record<`${K}.${string}`, unknown>; + +type ClientMeta<K extends ClientNames> = "meta" extends keyof ClientSchemas[K] ? Pick<ClientSchemas[K]["meta"] extends ClientMetaType ? ClientSchemas[K]["meta"] : never, "query" | "source"> : never; + +type ClientMetaType = { + source: ClientNames; + query: Record<string, unknown>; +}; + +interface ClientMethods { + [key: string | symbol]: (...args: any[]) => any; +} + +type ClientNames = keyof ClientSchemas extends (infer U) ? U : never; + +type ClientOutput<K extends ClientNames> = ClientSchemas[K]["methods"] & ClientMethods; + +type ClientSchemas = keyof ScopeRegistry extends never ? { + "ERROR: No clients were defined": ClientError<"ERROR: No clients were defined">; +} : { + [K in keyof ScopeRegistry]: ValidateClient<K>; +}; + +type CloudMessage = { + id: string; + parent_id: string | null; + height: number; + created_at: Date; + updated_at: Date; + format: "aui/v0" | string; + content: ReadonlyJSONObject; +}; + +type CloudThread = { + title: string; + last_message_at: Date; + metadata: unknown; + external_id: string | null; + id: string; + project_id: string; + created_at: Date; + updated_at: Date; + workspace_id: string; + is_archived: boolean; +}; + +type CompleteAttachment = BaseAttachment & { + status: CompleteAttachmentStatus; + content: ThreadUserMessagePart[]; +}; + +type CompleteAttachmentStatus = { + type: "complete"; +}; + +declare const ComposerAddAttachment: (_param16: ComposerAddAttachmentProps) => import("react").JSX.Element; + +type ComposerAddAttachmentProps = Omit<PressableProps, "onPress"> & { + children: ReactNode; +}; + +declare const ComposerAttachmentByIndex: import("react").FC<ComposerPrimitiveAttachmentByIndex.Props>; + +declare const ComposerAttachmentByIndexProvider: FC<PropsWithChildren<{ + index: number; +}>>; + +declare abstract class ComposerAttachmentRuntime<Source extends "edit-composer" | "thread-composer"> extends AttachmentRuntimeImpl<Source> { + private _composerApi; + constructor(core: AttachmentSnapshotBinding<Source>, _composerApi: ComposerRuntimeCoreBinding); + remove(): Promise<void>; +} + +declare const ComposerAttachments: import("react").FC<ComposerPrimitiveAttachments.Props>; + +type ComposerAttachmentsComponentConfig = { + Image?: ComponentType | undefined; + Document?: ComponentType | undefined; + File?: ComponentType | undefined; + Attachment?: ComponentType | undefined; +}; + +declare const ComposerCancel: (_param17: ComposerCancelProps) => import("react").JSX.Element; + +type ComposerCancelProps = Omit<PressableProps, "onPress"> & { + children: ReactNode; +}; + +type ComposerIfFilters = { + editing: boolean | undefined; + dictation: boolean | undefined; +}; + +declare const ComposerInput: (_param18: ComposerInputProps) => import("react").JSX.Element; + +type ComposerInputProps = ComponentProps<typeof Box> & { + submitOnEnter?: boolean | undefined; + placeholder?: string | undefined; + autoFocus?: boolean | undefined; + multiLine?: boolean | undefined; + onSubmit?: ((text: string) => void) | undefined; +}; + +declare namespace ComposerPrimitiveAttachmentByIndex { + type Props = { + index: number; + components?: ComposerAttachmentsComponentConfig; + }; +} + +declare const ComposerPrimitiveAttachmentByIndex: FC<ComposerPrimitiveAttachmentByIndex.Props>; + +declare namespace ComposerPrimitiveAttachments { + type Props = { + components: ComposerAttachmentsComponentConfig; + children?: never; + } | { + children: (value: { + attachment: Attachment; + }) => ReactNode; + components?: never; + }; +} + +declare const ComposerPrimitiveAttachments: FC<ComposerPrimitiveAttachments.Props>; + +declare namespace ComposerPrimitiveIf { + type Props = PropsWithChildren<UseComposerIfProps>; +} + +declare const ComposerPrimitiveIf: FC<ComposerPrimitiveIf.Props>; + +declare namespace ComposerPrimitiveQueue { + type Props = { + children: (value: { + queueItem: QueueItemState; + }) => ReactNode; + }; +} + +declare const ComposerPrimitiveQueue: import("react").NamedExoticComponent<{ + children: (value: { + queueItem: QueueItemState; + }) => ReactNode; +}>; + +declare const ComposerQuote: (_param19: ComposerQuoteProps) => import("react").JSX.Element | null; + +declare const ComposerQuoteDismiss: (_param20: ComposerQuoteDismissProps) => import("react").JSX.Element; + +type ComposerQuoteDismissProps = Omit<PressableProps, "onPress"> & { + children: ReactNode; +}; + +type ComposerQuoteProps = ComponentProps<typeof Box> & { + children: ReactNode; +}; + +declare const ComposerQuoteText: (_param21: ComposerQuoteTextProps) => import("react").JSX.Element | null; + +type ComposerQuoteTextProps = ComponentProps<typeof Text> & { + children?: ReactNode; +}; + +declare const ComposerRoot: (_param22: ComposerRootProps) => import("react").JSX.Element; + +type ComposerRootProps = ComponentProps<typeof Box> & { + children: ReactNode; +}; + +type ComposerRuntime = { + readonly path: ComposerRuntimePath; + readonly type: "edit" | "thread"; + getState(): ComposerState$1; + addAttachment(fileOrAttachment: File | CreateAttachment): Promise<void>; + setText(text: string): void; + setRole(role: MessageRole): void; + setRunConfig(runConfig: RunConfig): void; + reset(): Promise<void>; + clearAttachments(): Promise<void>; + send(options?: SendOptions): void; + cancel(): void; + steerQueueItem(queueItemId: string): void; + removeQueueItem(queueItemId: string): void; + subscribe(callback: () => void): Unsubscribe$1; + getAttachmentByIndex(idx: number): AttachmentRuntime; + startDictation(): void; + stopDictation(): void; + setQuote(quote: QuoteInfo | undefined): void; + unstable_on<E extends ComposerRuntimeEventType>(event: E, callback: ComposerRuntimeEventCallback<E>): Unsubscribe$1; +}; + +type ComposerRuntimeCore = Readonly<{ + isEditing: boolean; + canCancel: boolean; + canSend: boolean; + isEmpty: boolean; + attachments: readonly Attachment[]; + attachmentAccept: string; + addAttachment: (fileOrAttachment: File | CreateAttachment) => Promise<void>; + removeAttachment: (attachmentId: string) => Promise<void>; + text: string; + setText: (value: string) => void; + role: MessageRole; + setRole: (role: MessageRole) => void; + runConfig: RunConfig; + setRunConfig: (runConfig: RunConfig) => void; + quote: QuoteInfo | undefined; + setQuote: (quote: QuoteInfo | undefined) => void; + reset: () => Promise<void>; + clearAttachments: () => Promise<void>; + send: (options?: SendOptions) => void; + cancel: () => void; + queue: readonly QueueItemState[]; + steerQueueItem: (queueItemId: string) => void; + removeQueueItem: (queueItemId: string) => void; + dictation: DictationState | undefined; + startDictation: () => void; + stopDictation: () => void; + subscribe: (callback: () => void) => Unsubscribe$1; + unstable_on: <E extends ComposerRuntimeEventType>(event: E, callback: ComposerRuntimeEventCallback<E>) => Unsubscribe$1; +}>; + +type ComposerRuntimeCoreBinding = SubscribableWithState<ComposerRuntimeCore | undefined, ComposerRuntimePath>; + +type ComposerRuntimeEventCallback<E extends ComposerRuntimeEventType> = (payload: ComposerRuntimeEventPayload[E]) => void; + +type ComposerRuntimeEventPayload = { + send: Record<string, never>; + attachmentAdd: Record<string, never>; + attachmentAddError: AttachmentAddErrorEvent; +}; + +type ComposerRuntimeEventType = keyof ComposerRuntimeEventPayload; + +declare abstract class ComposerRuntimeImpl implements ComposerRuntime { + protected _core: ComposerRuntimeCoreBinding; + get path(): ComposerRuntimePath; + abstract get type(): "edit" | "thread"; + constructor(_core: ComposerRuntimeCoreBinding); + protected __internal_bindMethods(): void; + abstract getState(): ComposerState$1; + setText(text: string): void; + setRunConfig(runConfig: RunConfig): void; + addAttachment(fileOrAttachment: File | CreateAttachment): Promise<void>; + reset(): Promise<void>; + clearAttachments(): Promise<void>; + send(options?: SendOptions): void; + cancel(): void; + steerQueueItem(queueItemId: string): void; + removeQueueItem(queueItemId: string): void; + setRole(role: MessageRole): void; + startDictation(): void; + stopDictation(): void; + setQuote(quote: QuoteInfo | undefined): void; + subscribe(callback: () => void): Unsubscribe$1; + private _eventSubscriptionSubjects; + unstable_on<E extends ComposerRuntimeEventType>(event: E, callback: ComposerRuntimeEventCallback<E>): Unsubscribe$1; + abstract getAttachmentByIndex(idx: number): AttachmentRuntime; +} + +type ComposerRuntimePath = (ThreadRuntimePath & { + readonly composerSource: "thread"; +}) | (MessageRuntimePath & { + readonly composerSource: "edit"; +}); + +declare const ComposerSend: (_param23: ComposerSendProps) => import("react").JSX.Element; + +type ComposerSendProps = Omit<PressableProps, "onPress"> & { + children: ReactNode; +}; + +type ComposerState = { + readonly text: string; + readonly role: MessageRole; + readonly attachments: readonly Attachment[]; + readonly runConfig: RunConfig; + readonly isEditing: boolean; + readonly canCancel: boolean; + readonly canSend: boolean; + readonly attachmentAccept: string; + readonly isEmpty: boolean; + readonly type: "edit" | "thread"; + readonly dictation: DictationState | undefined; + readonly quote: QuoteInfo | undefined; + readonly queue: readonly QueueItemState[]; +}; + +type ComposerState$1 = ThreadComposerState | EditComposerState; + +declare class CompositeAttachmentAdapter implements AttachmentAdapter { + private _adapters; + accept: string; + constructor(adapters: AttachmentAdapter[]); + add(state: { + file: File; + }): Promise<PendingAttachment> | AsyncGenerator<PendingAttachment, void, any>; + send(attachment: PendingAttachment): Promise<CompleteAttachment>; + remove(attachment: Attachment): Promise<void>; +} + +declare class CompositeContextProvider implements ModelContextProvider { + private _providers; + getModelContext(): ModelContext$1; + registerModelContextProvider(provider: ModelContextProvider): () => void; + private _subscribers; + notifySubscribers(): void; + subscribe(callback: () => void): () => void; +} + +type CreateAppendMessage = string | { + parentId?: string | null | undefined; + sourceId?: string | null | undefined; + role?: AppendMessage["role"] | undefined; + content: AppendMessage["content"]; + attachments?: AppendMessage["attachments"] | undefined; + metadata?: AppendMessage["metadata"] | undefined; + createdAt?: Date | undefined; + runConfig?: AppendMessage["runConfig"] | undefined; + startRun?: boolean | undefined; +}; + +type CreateAttachment = { + id?: string; + type?: "image" | "document" | "file" | (string & {}); + name: string; + contentType?: string; + content: ThreadUserMessagePart[]; +}; + +type CreateFileStorageAdapterOptions = { + dir: string; + prefix?: string | undefined; + titleGenerator?: TitleGenerationAdapter | undefined; +}; + +type CreateResumeRunConfig = CreateStartRunConfig & { + stream?: (options: ChatModelRunOptions) => AsyncGenerator<ChatModelRunResult, void, unknown>; +}; + +type CreateStartRunConfig = { + parentId: string | null; + sourceId?: string | null | undefined; + runConfig?: RunConfig | undefined; +}; + +type DataMessagePart<T = any> = { + readonly type: "data"; + readonly name: string; + readonly data: T; +}; + +type DataMessagePartComponent<T = any> = ComponentType<DataMessagePartProps<T>>; + +type DataMessagePartProps<T = any> = MessagePartState & DataMessagePart<T>; + +type DataPrefixedPart = { + readonly type: `data-${string}`; + readonly data: any; +}; + +declare const DataRenderers: Resource<ClientOutput<"dataRenderers">, [ +]>; + +type DeepPartial<T> = T extends readonly any[] ? readonly DeepPartial<T[number]>[] : T extends { + [key: string]: any; +} ? { + readonly [K in keyof T]?: DeepPartial<T[K]>; +} : T; + +declare class DefaultThreadComposerRuntimeCore extends BaseComposerRuntimeCore implements ThreadComposerRuntimeCore { + private runtime; + private _canCancel; + get canCancel(): boolean; + get canSend(): boolean; + get queue(): readonly QueueItemState[]; + steerQueueItem(queueItemId: string): void; + removeQueueItem(queueItemId: string): void; + protected getAttachmentAdapter(): AttachmentAdapter | undefined; + protected getDictationAdapter(): DictationAdapter | undefined; + constructor(runtime: Omit<ThreadRuntimeCore, "composer"> & { + adapters?: { + attachments?: AttachmentAdapter | undefined; + dictation?: DictationAdapter | undefined; + } | undefined; + }); + connect(): Unsubscribe$1; + handleSend(message: Omit<AppendMessage, "parentId" | "sourceId">, options?: SendOptions): Promise<void>; + handleCancel(): Promise<void>; +} + +declare const Derived: <K extends ClientNames>(_config: Derived.Props<K>) => ResourceElement<null, [ + _config: Derived.Props<K> +]>; + +declare namespace Derived { + type Props<K extends ClientNames> = { + get: (client: AssistantClient) => ReturnType<AssistantClientAccessor<K>>; + } & ClientMeta<K>; +} + +type DerivedElement<K extends ClientNames> = ResourceElement<null, [ + Derived.Props<K> +]>; + +declare namespace DictationAdapter { + type Status = { + type: "running" | "starting"; + } | { + type: "ended"; + reason: "cancelled" | "error" | "stopped"; + }; + type Result = { + transcript: string; + isFinal?: boolean; + }; + type Session = { + status: Status; + stop: () => Promise<void>; + cancel: () => void; + onSpeechStart: (callback: () => void) => Unsubscribe$1; + onSpeechEnd: (callback: (result: Result) => void) => Unsubscribe$1; + onSpeech: (callback: (result: Result) => void) => Unsubscribe$1; + }; +} + +type DictationAdapter = { + listen: () => DictationAdapter.Session; + disableInputDuringDictation?: boolean; +}; + +type DictationState = { + readonly status: DictationAdapter.Status; + readonly transcript?: string; + readonly inputDisabled?: boolean; +}; + +declare const DiffContent: { + (_param24: DiffContentProps): import("react").JSX.Element | null; + displayName: string; +}; + +type DiffContentProps = ComponentProps<typeof Box> & { + fileIndex?: number | undefined; + showLineNumbers?: boolean | undefined; + contextLines?: number | undefined; + maxLines?: number | undefined; + renderLine?: ((props: { + line: ParsedLine; + index: number; + }) => ReactNode) | undefined; + renderFold?: ((props: { + region: FoldedRegion; + index: number; + }) => ReactNode) | undefined; +}; + +interface DiffFileInput { + content: string; + name?: string | undefined; +} + +declare const DiffHeader: { + (_param25: DiffHeaderProps): import("react").JSX.Element | null; + displayName: string; +}; + +type DiffHeaderProps = ComponentProps<typeof Box> & { + fileIndex?: number; +}; + +declare const DiffLine: { + (_param26: DiffLineProps): import("react").JSX.Element; + displayName: string; +}; + +type DiffLineProps = ComponentProps<typeof Box> & { + line: ParsedLine; + showLineNumbers?: boolean; + lineNumberWidth?: number; +}; + +type DiffLineType = "add" | "del" | "normal"; + +declare const DiffRoot: { + (_param27: DiffRootProps): import("react").JSX.Element; + displayName: string; +}; + +type DiffRootProps = ComponentProps<typeof Box> & { + files?: ParsedFile[] | undefined; + children: ReactNode; +}; + +declare const DiffStats: { + (_param28: DiffStatsProps): import("react").JSX.Element | null; + displayName: string; +}; + +type DiffStatsProps = ComponentProps<typeof Box> & { + fileIndex?: number; +}; + +declare const DiffView: (_param29: DiffViewProps) => import("react").JSX.Element; + +type DiffViewProps = Omit<ComponentProps<typeof Box>, "children"> & { + patch?: string | undefined; + oldFile?: DiffFileInput | undefined; + newFile?: DiffFileInput | undefined; + showLineNumbers?: boolean | undefined; + contextLines?: number | undefined; + maxLines?: number | undefined; +}; + +type DisplayLine = ParsedLine | FoldedRegion; + +declare class EditComposerAttachmentRuntimeImpl extends ComposerAttachmentRuntime<"edit-composer"> { + get source(): "edit-composer"; +} + +type EditComposerAttachmentState = Attachment & { + readonly source: "edit-composer"; +}; + +type EditComposerRuntime = Omit<ComposerRuntime, "getAttachmentByIndex" | "getState"> & { + readonly path: ComposerRuntimePath & { + composerSource: "edit"; + }; + readonly type: "edit"; + getState(): EditComposerState; + beginEdit(): void; + getAttachmentByIndex(idx: number): AttachmentRuntime & { + source: "edit-composer"; + }; +}; + +type EditComposerRuntimeCore = ComposerRuntimeCore & Readonly<{ + parentId: string | null; + sourceId: string | null; +}>; + +type EditComposerRuntimeCoreBinding = SubscribableWithState<EditComposerRuntimeCore | undefined, ComposerRuntimePath & { + composerSource: "edit"; +}>; + +declare class EditComposerRuntimeImpl extends ComposerRuntimeImpl implements EditComposerRuntime { + private _beginEdit; + get path(): ComposerRuntimePath & { + composerSource: "edit"; + }; + get type(): "edit"; + private _getState; + constructor(core: EditComposerRuntimeCoreBinding, _beginEdit: () => void); + __internal_bindMethods(): void; + getState(): EditComposerState; + beginEdit(): void; + getAttachmentByIndex(idx: number): EditComposerAttachmentRuntimeImpl; +} + +type EditComposerState = BaseComposerState & { + readonly type: "edit"; + readonly parentId: string | null; + readonly sourceId: string | null; +}; + +type EmptyMessagePartComponent = ComponentType<EmptyMessagePartProps>; + +type EmptyMessagePartProps = { + status: MessagePartStatus; +}; + +type EnrichedPartState = (Extract<PartState, { + type: "tool-call"; +}> & { + readonly toolUI: ReactNode; + addResult: ToolCallMessagePartProps["addResult"]; + resume: ToolCallMessagePartProps["resume"]; + respondToApproval: ToolCallMessagePartProps["respondToApproval"]; +}) | (Extract<PartState, { + type: "data"; +}> & { + readonly dataRendererUI: ReactNode; +}) | Exclude<PartState, { + type: "tool-call"; +} | { + type: "data"; +}>; + +declare const ErrorMessage: { + (_param30: ErrorMessageProps): import("react").JSX.Element | null; + displayName: string; +}; + +type ErrorMessageProps = ComponentProps<typeof Text> & { + children?: ReactNode; +}; + +declare const ErrorRoot: { + (_param31: ErrorRootProps): import("react").JSX.Element | null; + displayName: string; +}; + +type ErrorRootProps = ComponentProps<typeof Box> & { + children: ReactNode; +}; + +type EventSource<T extends AssistantEventName> = T extends `${infer Source}.${string}` ? Source : never; + +type ExportedMessageRepository = { + headId?: string | null; + messages: Array<{ + message: ThreadMessage; + parentId: string | null; + runConfig?: RunConfig; + }>; +}; + +declare const ExportedMessageRepository: { + fromArray: (messages: readonly ThreadMessageLike[]) => ExportedMessageRepository; + fromBranchableArray: (items: readonly { + message: ThreadMessageLike; + parentId: string | null; + }[], options?: { + headId?: string | null; + }) => ExportedMessageRepository; +}; + +type ExportedMessageRepositoryItem = { + message: ThreadMessage; + parentId: string | null; + runConfig?: RunConfig; +}; + +type FeedbackAdapter = { + submit: (feedback: FeedbackAdapterFeedback) => void; +}; + +type FeedbackAdapterFeedback = { + message: ThreadMessage; + type: "negative" | "positive"; +}; + +type FileMessagePart = { + readonly type: "file"; + readonly filename?: string; + readonly data: string; + readonly mimeType: string; + readonly parentId?: string; +}; + +type FileMessagePartComponent = ComponentType<FileMessagePartProps>; + +type FileMessagePartProps = MessagePartState & FileMessagePart; + +interface FoldedRegion { + type: "fold"; + hiddenCount: number; +} + +type FrontendTool<TArgs extends Record<string, unknown> = Record<string, unknown>, TResult = unknown> = ToolBase<TArgs, TResult> & { + type: "frontend"; + description?: string | undefined; + parameters: StandardSchemaV1<TArgs> | JSONSchema7; + disabled?: boolean; + execute?: ToolExecuteFunction<TArgs, TResult>; + toModelOutput?: ToolModelOutputFunction<TArgs, TResult>; + experimental_onSchemaValidationError?: OnSchemaValidationErrorFunction<TResult>; + providerOptions?: ProviderOptions; +}; + +type GeneratePresignedUploadUrlRequestBody = { + filename: string; +}; + +type GeneratePresignedUploadUrlResponse = { + success: boolean; + signedUrl: string; + expiresAt: string; + publicUrl: string; +}; + +type GenerativeUIComponentRegistry = Record<string, ComponentType<any>>; + +type GenerativeUIMessagePart = { + readonly type: "generative-ui"; + readonly spec: GenerativeUISpec; + readonly id?: string; + readonly parentId?: string; +}; + +type GenerativeUINode = string | { + readonly component: string; + readonly props?: Record<string, unknown>; + readonly children?: readonly GenerativeUINode[]; + readonly key?: string; +}; + +type GenerativeUISpec = { + readonly root: GenerativeUINode | readonly GenerativeUINode[]; +}; + +type GenericThreadHistoryAdapter<TMessage> = { + load(): Promise<MessageFormatRepository<TMessage>>; + append(item: MessageFormatItem<TMessage>): Promise<void>; + update?(item: MessageFormatItem<TMessage>, localMessageId: string): Promise<void>; + delete?(items: MessageFormatItem<TMessage>[]): Promise<void>; + reportTelemetry?(items: MessageFormatItem<TMessage>[], options?: { + durationMs?: number; + stepTimestamps?: { + start_ms: number; + end_ms: number; + }[]; + }): void; +}; + +type HumanTool<TArgs extends Record<string, unknown> = Record<string, unknown>, TResult = unknown> = ToolBase<TArgs, TResult> & { + type: "human"; + description?: string | undefined; + parameters: StandardSchemaV1<TArgs> | JSONSchema7; + disabled?: boolean; + display?: "standalone"; + execute?: undefined; + toModelOutput?: undefined; + experimental_onSchemaValidationError?: undefined; + providerOptions?: ProviderOptions; +}; + +type ImageMessagePart = { + readonly type: "image"; + readonly image: string; + readonly filename?: string; +}; + +type ImageMessagePartComponent = ComponentType<ImageMessagePartProps>; + +type ImageMessagePartProps = MessagePartState & ImageMessagePart; + +declare class InMemoryThreadListAdapter implements RemoteThreadListAdapter { + list(): Promise<RemoteThreadListResponse>; + rename(): Promise<void>; + updateCustom(): Promise<void>; + archive(): Promise<void>; + unarchive(): Promise<void>; + delete(): Promise<void>; + initialize(threadId: string): Promise<RemoteThreadInitializeResponse>; + generateTitle(): Promise<AssistantStream>; + fetch(threadId: string): Promise<RemoteThreadMetadata>; +} + +type IncompleteReason = Extract<MessageStatus, { + type: "incomplete"; +}>["reason"]; + +type InteractableScope = "app" | "thread"; + +type InteractableStateSchema = NonNullable<Extract<Tool, { + parameters: unknown; +}>["parameters"]>; + +declare const Interactables: Resource<ClientOutput<"interactables">, [ +]>; + +interface JSONSchema7 { + $id?: string | undefined; + $ref?: string | undefined; + $schema?: JSONSchema7Version | undefined; + $comment?: string | undefined; + $defs?: { + [key: string]: JSONSchema7Definition; + } | undefined; + type?: JSONSchema7TypeName | JSONSchema7TypeName[] | undefined; + enum?: JSONSchema7Type[] | undefined; + const?: JSONSchema7Type | undefined; + multipleOf?: number | undefined; + maximum?: number | undefined; + exclusiveMaximum?: number | undefined; + minimum?: number | undefined; + exclusiveMinimum?: number | undefined; + maxLength?: number | undefined; + minLength?: number | undefined; + pattern?: string | undefined; + items?: JSONSchema7Definition | JSONSchema7Definition[] | undefined; + additionalItems?: JSONSchema7Definition | undefined; + maxItems?: number | undefined; + minItems?: number | undefined; + uniqueItems?: boolean | undefined; + contains?: JSONSchema7Definition | undefined; + maxProperties?: number | undefined; + minProperties?: number | undefined; + required?: string[] | undefined; + properties?: { + [key: string]: JSONSchema7Definition; + } | undefined; + patternProperties?: { + [key: string]: JSONSchema7Definition; + } | undefined; + additionalProperties?: JSONSchema7Definition | undefined; + dependencies?: { + [key: string]: JSONSchema7Definition | string[]; + } | undefined; + propertyNames?: JSONSchema7Definition | undefined; + if?: JSONSchema7Definition | undefined; + then?: JSONSchema7Definition | undefined; + else?: JSONSchema7Definition | undefined; + allOf?: JSONSchema7Definition[] | undefined; + anyOf?: JSONSchema7Definition[] | undefined; + oneOf?: JSONSchema7Definition[] | undefined; + not?: JSONSchema7Definition | undefined; + format?: string | undefined; + contentMediaType?: string | undefined; + contentEncoding?: string | undefined; + definitions?: { + [key: string]: JSONSchema7Definition; + } | undefined; + title?: string | undefined; + description?: string | undefined; + default?: JSONSchema7Type | undefined; + readOnly?: boolean | undefined; + writeOnly?: boolean | undefined; + examples?: JSONSchema7Type | undefined; +} + +interface JSONSchema7Array extends Array<JSONSchema7Type> { +} + +type JSONSchema7Definition = JSONSchema7 | boolean; + +interface JSONSchema7Object { + [key: string]: JSONSchema7Type; +} + +type JSONSchema7Type = string | number | boolean | JSONSchema7Object | JSONSchema7Array | null; + +type JSONSchema7TypeName = "array" | "boolean" | "integer" | "null" | "number" | "object" | "string"; + +type JSONSchema7Version = string; + +declare const LOADING_FRAMES: { + readonly dots: readonly [ + ". ", + ".. ", + "..." + ]; + readonly pulse: readonly [ + "*--", + "-*-", + "--*" + ]; + readonly bar: readonly [ + "[= ]", + "[== ]", + "[=== ]", + "[ ===]", + "[ ==]", + "[ =]" + ]; + readonly bounce: readonly [ + "[* ]", + "[ * ]", + "[ * ]", + "[ *]", + "[ * ]", + "[ * ]" + ]; +}; + +type LanguageModelConfig = { + apiKey?: string; + baseUrl?: string; + modelName?: string; + reasoningEffort?: string; +}; + +type LanguageModelV1CallSettings = { + maxTokens?: number; + temperature?: number; + topP?: number; + presencePenalty?: number; + frequencyPenalty?: number; + seed?: number; + headers?: Record<string, string | undefined>; +}; + +declare const LiveChecklist: { + (_param32: LiveChecklistProps): import("react").JSX.Element; + displayName: string; +}; + +type LiveChecklistProps = ComponentProps<typeof Box> & { + items?: ChecklistItemData[] | undefined; + formatToolName?: ((toolName: string) => string) | undefined; + title?: string | undefined; + showProgress?: boolean | undefined; + maxDepth?: number | undefined; +}; + +declare const LoadingElapsedTime: { + (_param33: LoadingElapsedTimeProps): import("react").JSX.Element; + displayName: string; +}; + +type LoadingElapsedTimeProps = Omit<ComponentProps<typeof Text>, "children"> & { + format?: (seconds: number) => string; +}; + +declare const LoadingRoot: { + (_param34: LoadingRootProps): import("react").JSX.Element | null; + displayName: string; +}; + +type LoadingRootProps = ComponentProps<typeof Box> & { + children: ReactNode; +}; + +declare const LoadingSpinner: { + (_param35: LoadingSpinnerProps): import("react").JSX.Element; + displayName: string; +}; + +type LoadingSpinnerProps = Omit<ComponentProps<typeof Text>, "children"> & { + variant?: LoadingSpinnerVariant; + type?: ComponentProps<typeof Spinner>["type"]; + intervalMs?: number; +}; + +type LoadingSpinnerVariant = "spinner" | keyof typeof LOADING_FRAMES; + +declare const LoadingText: { + (_param36: LoadingTextProps): import("react").JSX.Element; + displayName: string; +}; + +type LoadingTextProps = ComponentProps<typeof Text> & { + children?: ReactNode; +}; + +type LocalRuntimeOptions = Omit<LocalRuntimeOptionsBase, "adapters"> & { + cloud?: AssistantCloud | undefined; + initialMessages?: readonly ThreadMessageLike[] | undefined; + adapters?: Omit<LocalRuntimeOptionsBase["adapters"], "chatModel"> | undefined; +}; + +type LocalRuntimeOptionsBase = { + maxSteps?: number | undefined; + adapters: { + chatModel: ChatModelAdapter; + history?: ThreadHistoryAdapter | undefined; + attachments?: AttachmentAdapter | undefined; + speech?: SpeechSynthesisAdapter | undefined; + dictation?: DictationAdapter | undefined; + voice?: RealtimeVoiceAdapter | undefined; + feedback?: FeedbackAdapter | undefined; + suggestion?: SuggestionAdapter | undefined; + }; + unstable_humanToolNames?: string[] | undefined; + unstable_enableMessageQueue?: boolean | undefined; +}; + +type MakeRequestOptions = { + method?: "POST" | "PUT" | "DELETE" | undefined; + headers?: Record<string, string> | undefined; + query?: Record<string, string | number | boolean> | undefined; + body?: object | undefined; +}; + +type McpAppMetadata = { + readonly resourceUri: string; + readonly mimeType?: string; + readonly visibility?: readonly ("app" | "model")[]; +}; + +type McpAppResourceOutput = { + readonly render: ToolCallMessagePartComponent; +}; + +type McpServerConfig = { + type: "http" | "sse"; + url: string; + headers?: Record<string, string>; + redirect?: "error" | "follow"; + connectionTimeout?: number | undefined; +} | { + type: "stdio"; + command: string; + args?: readonly string[]; + env?: Record<string, string>; + cwd?: string; + connectionTimeout?: number | undefined; +}; + +type McpTool = ToolBase<Record<string, unknown>, unknown> & { + type: "mcp"; + server: McpServerConfig; + description?: undefined; + parameters?: undefined; + disabled?: boolean; + execute?: undefined; + toModelOutput?: undefined; + experimental_onSchemaValidationError?: undefined; + providerOptions?: undefined; +}; + +type McpToolkitDefinition = Record<string, McpToolkitEntry>; + +type McpToolkitEntry = McpServerConfig | { + server: McpServerConfig; + disabled?: boolean | undefined; + prefix?: string | undefined; + tools?: Record<string, McpToolkitToolConfig> | undefined; +}; + +type McpToolkitToolConfig = { + disabled?: boolean | undefined; +}; + +declare const MessageAttachmentByIndex: import("react").FC<MessagePrimitiveAttachmentByIndex.Props>; + +declare const MessageAttachmentByIndexProvider: FC<PropsWithChildren<{ + index: number; +}>>; + +declare class MessageAttachmentRuntimeImpl extends AttachmentRuntimeImpl<"message"> { + get source(): "message"; + remove(): never; +} + +type MessageAttachmentState = CompleteAttachment & { + readonly source: "message"; +}; + +declare const MessageAttachments: import("react").FC<MessagePrimitiveAttachments.Props>; + +type MessageAttachmentsComponentConfig = { + Image?: ComponentType | undefined; + Document?: ComponentType | undefined; + File?: ComponentType | undefined; + Attachment?: ComponentType | undefined; +}; + +declare const MessageByIndexProvider: FC<PropsWithChildren<{ + index: number; +}>>; + +type MessageCommonProps = { + readonly id: string; + readonly createdAt: Date; +}; + +type MessageComponents = { + Message: ComponentType; + EditComposer?: ComponentType | undefined; + UserEditComposer?: ComponentType | undefined; + AssistantEditComposer?: ComponentType | undefined; + SystemEditComposer?: ComponentType | undefined; + UserMessage?: ComponentType | undefined; + AssistantMessage?: ComponentType | undefined; + SystemMessage?: ComponentType | undefined; +} | { + Message?: ComponentType | undefined; + EditComposer?: ComponentType | undefined; + UserEditComposer?: ComponentType | undefined; + AssistantEditComposer?: ComponentType | undefined; + SystemEditComposer?: ComponentType | undefined; + UserMessage: ComponentType; + AssistantMessage: ComponentType; + SystemMessage?: ComponentType | undefined; +}; + +declare const MessageContent: (_param37: MessageContentProps) => import("react").JSX.Element; + +type MessageContentPart = ThreadUserMessagePart | ThreadAssistantMessagePart; + +type MessageContentProps = { + renderText?: (props: { + part: Extract<MessageContentPart, { + type: "text"; + }>; + index: number; + }) => ReactElement; + renderToolCall?: (props: { + part: Extract<MessageContentPart, { + type: "tool-call"; + }>; + index: number; + }) => ReactElement; + renderImage?: (props: { + part: Extract<MessageContentPart, { + type: "image"; + }>; + index: number; + }) => ReactElement; + renderReasoning?: (props: { + part: Extract<MessageContentPart, { + type: "reasoning"; + }>; + index: number; + }) => ReactElement; + renderSource?: (props: { + part: Extract<MessageContentPart, { + type: "source"; + }>; + index: number; + }) => ReactElement; + renderFile?: (props: { + part: Extract<MessageContentPart, { + type: "file"; + }>; + index: number; + }) => ReactElement; + renderData?: (props: { + part: Extract<MessageContentPart, { + type: "data"; + }>; + index: number; + }) => ReactElement; +}; + +declare const MessageError: FC<PropsWithChildren>; + +interface MessageFormatAdapter<TMessage, TStorageFormat extends Record<string, unknown>> { + format: string; + encode(item: MessageFormatItem<TMessage>): TStorageFormat; + decode(stored: MessageStorageEntry<TStorageFormat>): MessageFormatItem<TMessage>; + getId(message: TMessage): string; +} + +interface MessageFormatItem<TMessage> { + parentId: string | null; + message: TMessage; +} + +interface MessageFormatRepository<TMessage> { + headId?: string | null; + messages: MessageFormatItem<TMessage>[]; +} + +declare const MessageIf: (_param38: MessageIfProps) => import("react").JSX.Element | null; + +type MessageIfProps = { + children: ReactNode; + user?: boolean | undefined; + assistant?: boolean | undefined; + running?: boolean | undefined; + last?: boolean | undefined; +}; + +declare namespace MessagePartPrimitiveData { + type Props = MessagePartPrimitiveDataProps; +} + +declare const MessagePartPrimitiveData: { + (props: MessagePartPrimitiveData.Props): import("react").JSX.Element; + displayName: string; +}; + +type MessagePartPrimitiveDataProps = Omit<ComponentProps<typeof Text>, "children">; + +declare namespace MessagePartPrimitiveFile { + type Props = MessagePartPrimitiveFileProps; +} + +declare const MessagePartPrimitiveFile: { + (props: MessagePartPrimitiveFile.Props): import("react").JSX.Element; + displayName: string; +}; + +type MessagePartPrimitiveFileProps = Omit<ComponentProps<typeof Text>, "children">; + +declare namespace MessagePartPrimitiveImage { + type Props = MessagePartPrimitiveImageProps; +} + +declare const MessagePartPrimitiveImage: { + (props: MessagePartPrimitiveImage.Props): import("react").JSX.Element; + displayName: string; +}; + +type MessagePartPrimitiveImageProps = Omit<ComponentProps<typeof Text>, "children">; + +declare namespace MessagePartPrimitiveInProgress { + type Props = PropsWithChildren; +} + +declare const MessagePartPrimitiveInProgress: FC<MessagePartPrimitiveInProgress.Props>; + +declare namespace MessagePartPrimitiveReasoning { + type Props = MessagePartPrimitiveReasoningProps; +} + +declare const MessagePartPrimitiveReasoning: { + (props: MessagePartPrimitiveReasoning.Props): import("react").JSX.Element; + displayName: string; +}; + +type MessagePartPrimitiveReasoningProps = Omit<ComponentProps<typeof Text>, "children">; + +declare namespace MessagePartPrimitiveSource { + type Props = MessagePartPrimitiveSourceProps; +} + +declare const MessagePartPrimitiveSource: { + (props: MessagePartPrimitiveSource.Props): import("react").JSX.Element; + displayName: string; +}; + +type MessagePartPrimitiveSourceProps = Omit<ComponentProps<typeof Text>, "children">; + +declare namespace MessagePartPrimitiveText { + type Props = MessagePartPrimitiveTextProps; +} + +declare const MessagePartPrimitiveText: { + (props: MessagePartPrimitiveText.Props): import("react").JSX.Element; + displayName: string; +}; + +type MessagePartPrimitiveTextProps = Omit<ComponentProps<typeof Text>, "children">; + +type MessagePartRuntime = { + addToolResult(result: any | ToolResponse<any>): void; + resumeToolCall(payload: unknown): void; + respondToToolApproval(response: ToolApprovalResponse): void; + readonly path: MessagePartRuntimePath; + getState(): MessagePartState; + subscribe(callback: () => void): Unsubscribe$1; +}; + +declare class MessagePartRuntimeImpl implements MessagePartRuntime { + private contentBinding; + private messageApi?; + private threadApi?; + get path(): MessagePartRuntimePath; + constructor(contentBinding: MessagePartSnapshotBinding, messageApi?: MessageStateBinding | undefined, threadApi?: ThreadRuntimeCoreBinding | undefined); + protected __internal_bindMethods(): void; + getState(): MessagePartState; + addToolResult(result: any | ToolResponse<any>): void; + resumeToolCall(payload: unknown): void; + respondToToolApproval(response: ToolApprovalResponse): void; + subscribe(callback: () => void): Unsubscribe$1; +} + +type MessagePartRuntimePath = MessageRuntimePath & { + readonly messagePartSelector: { + readonly type: "index"; + readonly index: number; + } | { + readonly type: "toolCallId"; + readonly toolCallId: string; + }; +}; + +type MessagePartSnapshotBinding = SubscribableWithState<MessagePartState, MessagePartRuntimePath>; + +type MessagePartState = (ThreadUserMessagePart | ThreadAssistantMessagePart) & { + readonly status: MessagePartStatus | ToolCallMessagePartStatus; +}; + +type MessagePartStatus = { + readonly type: "running"; +} | { + readonly type: "complete"; +} | { + readonly type: "incomplete"; + readonly reason: "cancelled" | "content-filter" | "error" | "length" | "other"; + readonly error?: unknown; +}; + +declare namespace MessagePrimitiveAttachmentByIndex { + type Props = { + index: number; + components?: MessageAttachmentsComponentConfig; + }; +} + +declare const MessagePrimitiveAttachmentByIndex: FC<MessagePrimitiveAttachmentByIndex.Props>; + +declare namespace MessagePrimitiveAttachments { + type Props = { + components: MessageAttachmentsComponentConfig; + children?: never; + } | { + children: (value: { + attachment: CompleteAttachment; + }) => ReactNode; + components?: never; + }; +} + +declare const MessagePrimitiveAttachments: FC<MessagePrimitiveAttachments.Props>; + +declare namespace MessagePrimitivePartByIndex { + type Props = { + index: number; + components: MessagePrimitiveParts$1.Props["components"]; + }; +} + +declare const MessagePrimitivePartByIndex: FC<MessagePrimitivePartByIndex.Props>; + +declare namespace MessagePrimitiveParts { + type Props = MessagePrimitiveParts$1.Props; +} + +declare const MessagePrimitiveParts: FC<MessagePrimitiveParts.Props>; + +declare namespace MessagePrimitiveParts$1 { + type DataConfig = { + by_name?: Record<string, DataMessagePartComponent | undefined> | undefined; + Fallback?: DataMessagePartComponent | undefined; + }; + type BaseComponents = { + Empty?: EmptyMessagePartComponent | undefined; + Text?: TextMessagePartComponent | undefined; + Source?: SourceMessagePartComponent | undefined; + Image?: ImageMessagePartComponent | undefined; + File?: FileMessagePartComponent | undefined; + Unstable_Audio?: Unstable_AudioMessagePartComponent | undefined; + data?: DataConfig | undefined; + Quote?: QuoteMessagePartComponent | undefined; + generativeUI?: { + components: GenerativeUIComponentRegistry; + Fallback?: ComponentType<{ + component: string; + props?: unknown; + }> | undefined; + } | undefined; + }; + type ToolsConfig = { + by_name?: Record<string, ToolCallMessagePartComponent | undefined> | undefined; + Fallback?: ComponentType<ToolCallMessagePartProps> | undefined; + } | { + Override: ComponentType<ToolCallMessagePartProps>; + }; + type StandardComponents = BaseComponents & { + Reasoning?: ReasoningMessagePartComponent | undefined; + tools?: ToolsConfig | undefined; + ToolGroup?: ComponentType<PropsWithChildren<{ + startIndex: number; + endIndex: number; + }>>; + ReasoningGroup?: ReasoningGroupComponent; + ChainOfThought?: never; + }; + type ChainOfThoughtComponents = BaseComponents & { + ChainOfThought: ComponentType; + Reasoning?: never; + tools?: never; + ToolGroup?: never; + ReasoningGroup?: never; + }; + export type Props = { + components?: StandardComponents | ChainOfThoughtComponents | undefined; + unstable_showEmptyOnNonTextEnd?: boolean | undefined; + children?: never; + } | { + children: (value: { + part: EnrichedPartState; + }) => ReactNode; + components?: never; + unstable_showEmptyOnNonTextEnd?: never; + }; + export {}; +} + +declare const MessagePrimitiveParts$1: FC<MessagePrimitiveParts$1.Props>; + +declare class MessageRepository { + private messages; + private head; + private root; + private updateLevels; + private performOp; + private _messages; + get headId(): string | null; + get canonicalHeadId(): string | null; + getMessages(headId?: string): readonly ThreadMessage[]; + addOrUpdateMessage(parentId: string | null, message: ThreadMessage): void; + getMessage(messageId: string): { + parentId: string | null; + message: ThreadMessage; + index: number; + }; + deleteMessage(messageId: string, replacementId?: string | null | undefined): void; + getBranches(messageId: string): string[]; + private evictOffBranchOptimisticMessages; + switchToBranch(messageId: string): void; + resetHead(messageId: string | null): void; + clear(): void; + export(): ExportedMessageRepository; + import(_param39: ExportedMessageRepository): void; +} + +type MessageRole = ThreadMessage["role"]; + +declare const MessageRoot: (_param40: MessageRootProps) => import("react").JSX.Element; + +type MessageRootProps = ComponentProps<typeof Box> & { + children: ReactNode; +}; + +type MessageRuntime = { + readonly path: MessageRuntimePath; + readonly composer: EditComposerRuntime; + getState(): MessageState$1; + delete(): void | Promise<void>; + reload(config?: ReloadConfig): void; + speak(): void; + stopSpeaking(): void; + submitFeedback(_param41: { + type: "positive" | "negative"; + }): void; + switchToBranch(_param42: { + position?: "previous" | "next" | undefined; + branchId?: string | undefined; + }): void; + unstable_getCopyText(): string; + subscribe(callback: () => void): Unsubscribe$1; + getMessagePartByIndex(idx: number): MessagePartRuntime; + getMessagePartByToolCallId(toolCallId: string): MessagePartRuntime; + getAttachmentByIndex(idx: number): AttachmentRuntime & { + source: "message"; + }; +}; + +declare class MessageRuntimeImpl implements MessageRuntime { + private _core; + private _threadBinding; + get path(): MessageRuntimePath; + constructor(_core: MessageStateBinding, _threadBinding: ThreadRuntimeCoreBinding); + protected __internal_bindMethods(): void; + readonly composer: EditComposerRuntimeImpl; + private _getEditComposerRuntimeCore; + getState(): ThreadMessage & { + readonly parentId: string | null; + readonly index: number; + readonly isLast: boolean; + readonly branchNumber: number; + readonly branchCount: number; + readonly speech: SpeechState | undefined; + }; + delete(): void | Promise<void>; + reload(reloadConfig?: ReloadConfig): void; + speak(): void; + stopSpeaking(): void; + submitFeedback(_param43: { + type: "positive" | "negative"; + }): void; + switchToBranch(_param44: { + position?: "previous" | "next" | undefined; + branchId?: string | undefined; + }): void; + unstable_getCopyText(): string; + subscribe(callback: () => void): Unsubscribe$1; + getMessagePartByIndex(idx: number): MessagePartRuntimeImpl; + getMessagePartByToolCallId(toolCallId: string): MessagePartRuntimeImpl; + getAttachmentByIndex(idx: number): MessageAttachmentRuntimeImpl; +} + +type MessageRuntimePath = ThreadRuntimePath & { + readonly messageSelector: { + readonly type: "messageId"; + readonly messageId: string; + } | { + readonly type: "index"; + readonly index: number; + }; +}; + +type MessageState = ThreadMessage & { + readonly parentId: string | null; + readonly isLast: boolean; + readonly branchNumber: number; + readonly branchCount: number; + readonly speech: SpeechState | undefined; + readonly composer: ComposerState; + readonly parts: readonly PartState[]; + readonly isCopied: boolean; + readonly isHovering: boolean; + readonly index: number; +}; + +type MessageState$1 = ThreadMessage & { + readonly parentId: string | null; + readonly index: number; + readonly isLast: boolean; + readonly branchNumber: number; + readonly branchCount: number; + readonly speech: SpeechState | undefined; +}; + +type MessageStateBinding = SubscribableWithState<ThreadMessage & { + readonly parentId: string | null; + readonly index: number; + readonly isLast: boolean; + readonly branchNumber: number; + readonly branchCount: number; + readonly speech: SpeechState | undefined; +}, MessageRuntimePath>; + +type MessageStatus = { + readonly type: "running"; +} | { + readonly type: "requires-action"; + readonly reason: "interrupt" | "tool-calls"; +} | { + readonly type: "complete"; + readonly reason: "stop" | "unknown"; +} | { + readonly type: "incomplete"; + readonly reason: "cancelled" | "content-filter" | "error" | "length" | "other" | "tool-calls"; + readonly error?: ReadonlyJSONValue; +}; + +interface MessageStorageEntry<TPayload> { + id: string; + parent_id: string | null; + format: string; + content: TPayload; +} + +type MessageTiming = { + readonly streamStartTime: number; + readonly firstTokenTime?: number; + readonly totalStreamTime?: number; + readonly tokenCount?: number; + readonly tokensPerSecond?: number; + readonly totalChunks: number; + readonly toolCallCount: number; +}; + +type MessagesComponentConfig = { + Message: ComponentType; + EditComposer?: ComponentType | undefined; + UserEditComposer?: ComponentType | undefined; + AssistantEditComposer?: ComponentType | undefined; + SystemEditComposer?: ComponentType | undefined; + UserMessage?: ComponentType | undefined; + AssistantMessage?: ComponentType | undefined; + SystemMessage?: ComponentType | undefined; +} | { + Message?: ComponentType | undefined; + EditComposer?: ComponentType | undefined; + UserEditComposer?: ComponentType | undefined; + AssistantEditComposer?: ComponentType | undefined; + SystemEditComposer?: ComponentType | undefined; + UserMessage: ComponentType; + AssistantMessage: ComponentType; + SystemMessage?: ComponentType | undefined; +}; + +declare const ModelContext: Resource<ClientOutput<"modelContext">, [ +]>; + +type ModelContext$1 = { + priority?: number | undefined; + system?: string | undefined; + tools?: Record<string, Tool<any, any>> | undefined; + callSettings?: LanguageModelV1CallSettings | undefined; + config?: LanguageModelConfig | undefined; + unstable_composerMetadata?: Record<string, unknown> | undefined; +}; + +type ModelContextProvider = { + getModelContext: () => ModelContext$1; + subscribe?: (callback: () => void) => Unsubscribe$1; +}; + +declare class ModelContextRegistry implements ModelContextProvider { + private _tools; + private _instructions; + private _providers; + private _subscribers; + private _providerUnsubscribes; + getModelContext(): ModelContext$1; + subscribe(callback: () => void): Unsubscribe$1; + private notifySubscribers; + addTool<TArgs extends Record<string, unknown>, TResult>(tool: AssistantToolProps$1<TArgs, TResult>): ModelContextRegistryToolHandle<TArgs, TResult>; + addInstruction(config: string | AssistantInstructionsConfig): ModelContextRegistryInstructionHandle; + addProvider(provider: ModelContextProvider): ModelContextRegistryProviderHandle; +} + +interface ModelContextRegistryInstructionHandle { + update(config: string | AssistantInstructionsConfig): void; + remove(): void; +} + +interface ModelContextRegistryProviderHandle { + remove(): void; +} + +interface ModelContextRegistryToolHandle<TArgs extends Record<string, unknown> = any, TResult = any> { + update(tool: AssistantToolProps$1<TArgs, TResult>): void; + remove(): void; +} + +type NotificationConfig = { + enabled?: boolean; + onTaskComplete?: NotificationHandler<"task-complete"> | false; + onTaskIncomplete?: NotificationHandler<"task-incomplete"> | false; + onNeedsInput?: NotificationHandler<"needs-input"> | false; +}; + +type NotificationEvent = { + type: "task-complete"; + threadId: string; + messageId: string; + title: string; +} | { + type: "task-incomplete"; + threadId: string; + messageId: string; + title: string; + reason: IncompleteReason; +} | { + type: "needs-input"; + threadId: string; + messageId: string; + title: string; + reason: "interrupt"; +}; + +type NotificationHandler<T extends NotificationEvent["type"] = NotificationEvent["type"]> = { + bell?: boolean; + osc?: boolean | OSCVariant; + custom?: (event: Extract<NotificationEvent, { + type: T; + }>) => void; +}; + +type OSCVariant = "osc777" | "osc9" | "osc99"; + +type ObjectKey<T> = keyof T & (string | number); + +type ObjectStreamOperation = { + readonly type: "set"; + readonly path: readonly string[]; + readonly value: ReadonlyJSONValue; +} | { + readonly type: "append-text"; + readonly path: readonly string[]; + readonly value: string; +}; + +type OnSchemaValidationErrorFunction<TResult> = ToolExecuteFunction<unknown, TResult>; + +type OverrideOptionalField<T, TKey extends keyof T, TValue> = undefined extends T[TKey] ? Exclude<T[TKey], undefined> extends never ? { + [K in TKey]?: undefined; +} : { + [K in TKey]?: TValue | undefined; +} : { + [K in TKey]: TValue; +}; + +type OverrideToolDeclarationCallbacks<T extends { + streamCall?: unknown; +}, TArgs extends Record<string, unknown>, TResult> = Omit<T, "execute" | "experimental_onSchemaValidationError" | "streamCall" | "toModelOutput" | "type"> & { + type?: never; +} & ("execute" extends keyof T ? OverrideOptionalField<T, "execute", ToolExecute<NoInfer<TArgs>, TResult>> : {}) & ("toModelOutput" extends keyof T ? OverrideOptionalField<T, "toModelOutput", ToolModelOutputFunction<NoInfer<TArgs>, NoInfer<TResult>>> : {}) & ("experimental_onSchemaValidationError" extends keyof T ? OverrideOptionalField<T, "experimental_onSchemaValidationError", (args: unknown, context: ToolExecuteContext) => NoInfer<TResult> | Promise<NoInfer<TResult>>> : {}) & OverrideOptionalField<T, "streamCall", ToolStreamCall<TArgs, unknown>>; + +type ParentOf<K extends ClientNames> = AssistantClientAccessor<K> extends { + source: infer S; +} ? S extends ClientNames ? S : never : never; + +interface ParsedFile { + oldName?: string | undefined; + newName?: string | undefined; + lines: ParsedLine[]; + additions: number; + deletions: number; +} + +interface ParsedLine { + type: DiffLineType; + content: string; + oldLineNumber?: number; + newLineNumber?: number; + hunkIndex?: number; +} + +declare const PartByIndexProvider: FC<PropsWithChildren<{ + index: number; +}>>; + +type PartInit = { + readonly type: "reasoning" | "text"; + readonly parentId?: string; +} | { + readonly type: "tool-call"; + readonly toolCallId: string; + readonly toolName: string; + readonly parentId?: string; +} | { + readonly type: "source"; + readonly sourceType: "url"; + readonly id: string; + readonly url: string; + readonly title?: string; + readonly parentId?: string; +} | { + readonly type: "file"; + readonly data: string; + readonly mimeType: string; + readonly parentId?: string; +} | { + readonly type: "data"; + readonly name: string; + readonly data: ReadonlyJSONValue; + readonly parentId?: string; +}; + +type PartMethods = { + getState(): PartState; + addToolResult(result: unknown | ToolResponse<unknown>): void; + resumeToolCall(payload: unknown): void; + respondToToolApproval(response: ToolApprovalResponse): void; + __internal_getRuntime?(): MessagePartRuntime; +}; + +declare namespace PartPrimitiveMessages { + type Props = { + components: NonNullable<ThreadPrimitiveMessages.Props["components"]>; + children?: never; + } | { + children: (value: { + message: ThreadMessage; + }) => ReactNode; + components?: never; + }; +} + +declare const PartPrimitiveMessages: import("react").NamedExoticComponent<PartPrimitiveMessages.Props>; + +type PartProviderMetadata = { + readonly [providerName: string]: ReadonlyJSONObject; +}; + +type PartState = (ThreadUserMessagePart | ThreadAssistantMessagePart) & { + readonly status: MessagePartStatus | ToolCallMessagePartStatus; +}; + +type PdfToImagesRequestBody = { + file_blob?: string | undefined; + file_url?: string | undefined; +}; + +type PdfToImagesResponse = { + success: boolean; + urls: string[]; + message: string; +}; + +type PendingAttachment = BaseAttachment & { + status: PendingAttachmentStatus; + file: File; +}; + +type PendingAttachmentStatus = { + type: "running"; + reason: "uploading"; + progress: number; +} | { + type: "requires-action"; + reason: "composer-send"; +} | { + type: "incomplete"; + reason: "error" | "upload-paused"; + message?: string; +}; + +type PressableProps = Omit<ComponentProps<typeof Box>, "children"> & { + children: ReactNode | ((state: PressableState) => ReactNode); + onPress?: (() => void) | undefined; + disabled?: boolean | undefined; +}; + +type PressableState = { + isFocused: boolean; + disabled: boolean; +}; + +type PropFieldStatus = "complete" | "streaming"; + +type ProviderOptions = Record<string, Record<string, unknown>>; + +type ProviderTool<TArgs extends Record<string, unknown> = Record<string, unknown>, TResult = unknown> = ToolBase<TArgs, TResult> & { + type: "provider"; + providerId: `${string}.${string}`; + parameters?: StandardSchemaV1<TArgs> | JSONSchema7 | undefined; + args: Record<string, unknown>; + supportsDeferredResults?: boolean; + description?: undefined; + disabled?: boolean; + execute?: undefined; + toModelOutput?: undefined; + experimental_onSchemaValidationError?: undefined; + providerOptions?: ProviderOptions; +}; + +type ProviderToolConfig<TArgs extends Record<string, unknown> = Record<string, unknown>> = Pick<ProviderToolDefinition<TArgs>, "args" | "parameters" | "providerId" | "providerOptions" | "supportsDeferredResults">; + +type ProviderToolDefinition<TArgs extends Record<string, unknown>> = Extract<Tool<TArgs, unknown>, { + type: "provider"; +}>; + +declare const QueueItemRemove: (_param45: QueueItemRemoveProps) => import("react").JSX.Element; + +type QueueItemRemoveProps = Omit<PressableProps, "onPress"> & { + children: ReactNode; +}; + +type QueueItemState = { + readonly id: string; + readonly prompt: string; +}; + +declare const QueueItemSteer: (_param46: QueueItemSteerProps) => import("react").JSX.Element; + +type QueueItemSteerProps = Omit<PressableProps, "onPress"> & { + children: ReactNode; +}; + +declare const QueueItemText: (_param47: QueueItemTextProps) => import("react").JSX.Element; + +type QueueItemTextProps = ComponentProps<typeof Text>; + +type QuoteInfo = { + readonly text: string; + readonly messageId: string; +}; + +type QuoteMessagePartComponent = ComponentType<QuoteMessagePartProps>; + +type QuoteMessagePartProps = QuoteInfo; + +type ReadonlyJSONArray = readonly ReadonlyJSONValue[]; + +type ReadonlyJSONObject = { + readonly [key: string]: ReadonlyJSONValue; +}; + +type ReadonlyJSONValue = null | string | number | boolean | ReadonlyJSONObject | ReadonlyJSONArray; + +declare namespace RealtimeVoiceAdapter { + type Status = { + type: "running" | "starting"; + } | { + type: "ended"; + reason: "cancelled" | "error" | "finished"; + error?: unknown; + }; + type Mode = "listening" | "speaking"; + type TranscriptItem = { + role: "assistant" | "user"; + text: string; + isFinal?: boolean; + }; + type Session = { + status: Status; + isMuted: boolean; + disconnect: () => void; + mute: () => void; + unmute: () => void; + onStatusChange: (callback: (status: Status) => void) => Unsubscribe$1; + onTranscript: (callback: (transcript: TranscriptItem) => void) => Unsubscribe$1; + onModeChange: (callback: (mode: Mode) => void) => Unsubscribe$1; + onVolumeChange: (callback: (volume: number) => void) => Unsubscribe$1; + }; +} + +type RealtimeVoiceAdapter = { + connect: (options: { + abortSignal?: AbortSignal; + }) => RealtimeVoiceAdapter.Session; +}; + +type ReasoningGroupComponent = ComponentType<ReasoningGroupProps>; + +type ReasoningGroupProps = PropsWithChildren<{ + startIndex: number; + endIndex: number; +}>; + +type ReasoningMessagePart = { + readonly type: "reasoning"; + readonly text: string; + readonly providerMetadata?: PartProviderMetadata; + readonly parentId?: string; +}; + +type ReasoningMessagePartComponent = ComponentType<ReasoningMessagePartProps>; + +type ReasoningMessagePartProps = MessagePartState & ReasoningMessagePart; + +type ReloadConfig = { + runConfig?: RunConfig; +}; + +type RemoteThreadInitializeResponse = { + remoteId: string; + externalId: string | undefined; +}; + +type RemoteThreadListAdapter = { + list(params?: RemoteThreadListPageOptions): Promise<RemoteThreadListResponse>; + rename(remoteId: string, newTitle: string): Promise<void>; + updateCustom?(remoteId: string, custom: Record<string, unknown> | undefined): Promise<void>; + archive(remoteId: string): Promise<void>; + unarchive(remoteId: string): Promise<void>; + delete(remoteId: string): Promise<void>; + initialize(threadId: string): Promise<RemoteThreadInitializeResponse>; + generateTitle(remoteId: string, unstable_messages: readonly ThreadMessage[]): Promise<AssistantStream>; + fetch(threadId: string): Promise<RemoteThreadMetadata>; + unstable_Provider?: ComponentType<PropsWithChildren> | undefined; +}; + +type RemoteThreadListOptions = { + runtimeHook: () => AssistantRuntime; + adapter: RemoteThreadListAdapter; + initialThreadId?: string | undefined; + threadId?: string | undefined; + onThreadIdChange?: ((threadId: string | undefined) => void) | undefined; + allowNesting?: boolean | undefined; +}; + +type RemoteThreadListPageOptions = { + after?: string | undefined; +}; + +type RemoteThreadListResponse = { + threads: RemoteThreadMetadata[]; + nextCursor?: string | undefined; +}; + +type RemoteThreadMetadata = { + readonly status: "archived" | "regular"; + readonly remoteId: string; + readonly externalId?: string | undefined; + readonly title?: string | undefined; + readonly lastMessageAt?: Date | undefined; + readonly custom?: Record<string, unknown> | undefined; +}; + +type ReportToolCall = { + tool_name: string; + tool_call_id: string; + tool_args?: string; + tool_result?: string; + tool_source?: "backend" | "frontend" | "mcp"; + start_ms?: number; + end_ms?: number; + sampling_calls?: SamplingCallData[]; +}; + +type RequireAtLeastOne<T, Keys extends keyof T = keyof T> = Pick<T, Exclude<keyof T, Keys>> & { + [K in Keys]-?: Required<Pick<T, K>> & Partial<Pick<T, Exclude<Keys, K>>>; +}[Keys]; + +type Resource<R, A extends readonly unknown[] = any[]> = (...args: A) => ResourceElement<R, A>; + +type ResourceElement<R, A extends readonly unknown[] = any[]> = { + readonly hook: (...args: A) => R; + readonly args: Readonly<A>; + readonly key?: string | number; + readonly deps?: readonly unknown[]; +}; + +type RespondToToolApprovalOptions = { + approvalId: string; + approved: boolean; + optionId?: string; + reason?: string; +}; + +type ResumeRunConfig = StartRunConfig & { + stream?: (options: ChatModelRunOptions) => AsyncGenerator<ChatModelRunResult, void, unknown>; +}; + +type ResumeToolCallOptions = { + toolCallId: string; + payload: unknown; +}; + +type RunConfig = { + readonly custom?: Record<string, unknown>; +}; + +declare namespace RuntimeAdapterProvider { + type Props = { + adapters: RuntimeAdapters; + children: ReactNode; + }; +} + +declare const RuntimeAdapterProvider: FC<RuntimeAdapterProvider.Props>; + +type RuntimeAdapters = { + modelContext?: ModelContextProvider | undefined; + history?: ThreadHistoryAdapter | undefined; + attachments?: AttachmentAdapter | undefined; +}; + +type RuntimeCapabilities = { + readonly switchToBranch: boolean; + readonly switchBranchDuringRun: boolean; + readonly edit: boolean; + readonly reload: boolean; + readonly delete: boolean; + readonly cancel: boolean; + readonly unstable_copy: boolean; + readonly speech: boolean; + readonly dictation: boolean; + readonly voice: boolean; + readonly attachments: boolean; + readonly feedback: boolean; + readonly queue: boolean; +}; + +type SamplingCallData = { + model_id?: string; + input_tokens?: number; + output_tokens?: number; + reasoning_tokens?: number; + cached_input_tokens?: number; + duration_ms?: number; +}; + +interface ScopeRegistry { + [key: string]: { methods: any; meta?: any; events?: any }; +} + +type SendOptions = { + startRun?: boolean; + steer?: boolean; +}; + +declare class SimpleImageAttachmentAdapter implements AttachmentAdapter { + accept: string; + add(state: { + file: File; + }): Promise<PendingAttachment>; + send(attachment: PendingAttachment): Promise<CompleteAttachment>; + remove(): Promise<void>; +} + +declare class SimpleTextAttachmentAdapter implements AttachmentAdapter { + accept: string; + add(state: { + file: File; + }): Promise<PendingAttachment>; + send(attachment: PendingAttachment): Promise<CompleteAttachment>; + remove(): Promise<void>; +} + +type SnapshotCarrierMessage = { + role: string; + metadata?: unknown; + content?: readonly unknown[] | undefined; +}; + +type SourceMessagePart = { + readonly type: "source"; + readonly sourceType: "url"; + readonly id: string; + readonly url: string; + readonly title?: string; + readonly providerMetadata?: SourceProviderMetadata; + readonly parentId?: string; +} | { + readonly type: "source"; + readonly sourceType: "document"; + readonly id: string; + readonly url?: undefined; + readonly title: string; + readonly mediaType: string; + readonly filename?: string; + readonly providerMetadata?: SourceProviderMetadata; + readonly parentId?: string; +}; + +type SourceMessagePartComponent = ComponentType<SourceMessagePartProps>; + +type SourceMessagePartProps = MessagePartState & SourceMessagePart; + +type SourceProviderMetadata = PartProviderMetadata; + +interface SpeechRecognitionConstructor { + new (): SpeechRecognitionInstance; +} + +interface SpeechRecognitionInstance extends EventTarget { + lang: string; + continuous: boolean; + interimResults: boolean; + start(): void; + stop(): void; + abort(): void; +} + +type SpeechState = { + readonly messageId: string; + readonly status: SpeechSynthesisAdapter.Status; +}; + +declare namespace SpeechSynthesisAdapter { + type Status = { + type: "running" | "starting"; + } | { + type: "ended"; + reason: "cancelled" | "error" | "finished"; + error?: unknown; + }; + type Utterance = { + status: Status; + cancel: () => void; + subscribe: (callback: () => void) => Unsubscribe$1; + }; +} + +type SpeechSynthesisAdapter = { + speak: (text: string) => SpeechSynthesisAdapter.Utterance; +}; + +type StartRunConfig = { + parentId: string | null; + sourceId: string | null; + runConfig: RunConfig; +}; + +type StateUpdater<TState> = TState | ((prev: TState) => TState); + +type StateUpdater$1<TState> = TState | ((prev: TState) => TState); + +declare namespace StatusBarPrimitiveLatency { + type Props = StatusBarPrimitiveLatencyProps; +} + +declare const StatusBarPrimitiveLatency: { + (_param48: StatusBarPrimitiveLatency.Props): import("react").JSX.Element | null; + displayName: string; +}; + +type StatusBarPrimitiveLatencyProps = Omit<ComponentProps<typeof Text>, "children"> & { + format?: (tokensPerSecond: number) => string; +}; + +declare namespace StatusBarPrimitiveMessageCount { + type Props = StatusBarPrimitiveMessageCountProps; +} + +declare const StatusBarPrimitiveMessageCount: { + (_param49: StatusBarPrimitiveMessageCount.Props): import("react").JSX.Element | null; + displayName: string; +}; + +type StatusBarPrimitiveMessageCountProps = Omit<ComponentProps<typeof Text>, "children"> & { + format?: (count: number) => string; +}; + +declare namespace StatusBarPrimitiveModelName { + type Props = StatusBarPrimitiveModelNameProps; +} + +declare const StatusBarPrimitiveModelName: { + (_param50: StatusBarPrimitiveModelName.Props): import("react").JSX.Element; + displayName: string; +}; + +type StatusBarPrimitiveModelNameProps = Omit<ComponentProps<typeof Text>, "children"> & { + name?: ReactNode; +}; + +declare namespace StatusBarPrimitiveRoot { + type Props = StatusBarPrimitiveRootProps; +} + +declare const StatusBarPrimitiveRoot: { + (_param51: StatusBarPrimitiveRoot.Props): import("react").JSX.Element; + displayName: string; +}; + +type StatusBarPrimitiveRootProps = ComponentProps<typeof Box> & { + children: ReactNode; +}; + +declare namespace StatusBarPrimitiveStatus { + type Props = StatusBarPrimitiveStatusProps; +} + +declare const StatusBarPrimitiveStatus: { + (_param52: StatusBarPrimitiveStatus.Props): import("react").JSX.Element; + displayName: string; +}; + +type StatusBarPrimitiveStatusProps = Omit<ComponentProps<typeof Text>, "children"> & { + format?: (status: StatusType) => string; +}; + +declare namespace StatusBarPrimitiveTokenCount { + type Props = StatusBarPrimitiveTokenCountProps; +} + +declare const StatusBarPrimitiveTokenCount: { + (_param53: StatusBarPrimitiveTokenCount.Props): import("react").JSX.Element | null; + displayName: string; +}; + +type StatusBarPrimitiveTokenCountProps = Omit<ComponentProps<typeof Text>, "children"> & { + format?: (tokens: number) => string; +}; + +type StatusType = "cancelled" | "error" | "idle" | "running"; + +type SubmitFeedbackOptions = { + messageId: string; + type: "negative" | "positive"; +}; + +type Subscribable = { + subscribe: (callback: () => void) => Unsubscribe$1; +}; + +type SubscribableWithState<TState, TPath> = Subscribable & { + path: TPath; + getState: () => TState; +}; + +type SuggestionAdapter = { + generate: (options: SuggestionAdapterGenerateOptions) => Promise<readonly ThreadSuggestion$1[]> | AsyncGenerator<readonly ThreadSuggestion$1[], void>; +}; + +type SuggestionAdapterGenerateOptions = { + messages: readonly ThreadMessage[]; +}; + +declare const SuggestionByIndexProvider: FC<SuggestionByIndexProviderProps>; + +type SuggestionByIndexProviderProps = PropsWithChildren<{ + index: number; +}>; + +type SuggestionConfig = string | { + title: string; + label: string; + prompt: string; +}; + +declare const SuggestionDescription: (_param54: SuggestionDescriptionProps) => import("react").JSX.Element; + +type SuggestionDescriptionProps = ComponentProps<typeof Text> & { + children?: ReactNode; +}; + +type SuggestionState = { + title: string; + label: string; + prompt: string; +}; + +declare const SuggestionTitle: (_param55: SuggestionTitleProps) => import("react").JSX.Element; + +type SuggestionTitleProps = ComponentProps<typeof Text> & { + children?: ReactNode; +}; + +declare const SuggestionTrigger: (_param56: SuggestionTriggerProps) => import("react").JSX.Element; + +type SuggestionTriggerProps = Omit<PressableProps, "onPress"> & { + children: ReactNode; + send?: boolean | undefined; + clearComposer?: boolean | undefined; +}; + +declare const Suggestions: Resource<ClientOutput<"suggestions">, [ + suggestions?: SuggestionConfig[] | undefined +]>; + +type SuggestionsComponentConfig = { + Suggestion: ComponentType; +}; + +declare const TOOL_RESPONSE_SYMBOL: unique symbol; + +declare const TextInput: (_param57: TextInputProps) => import("react").JSX.Element; + +type TextInputProps = ComponentProps<typeof Box> & { + value: string; + onChange: (text: string) => void; + onSubmit?: ((text: string) => void) | undefined; + submitOnEnter?: boolean | undefined; + placeholder?: string | undefined; + autoFocus?: boolean | undefined; + multiLine?: boolean | undefined; +}; + +type TextMessagePart = { + readonly type: "text"; + readonly text: string; + readonly providerMetadata?: PartProviderMetadata; + readonly parentId?: string; +}; + +type TextMessagePartComponent = ComponentType<TextMessagePartProps>; + +type TextMessagePartProps = MessagePartState & TextMessagePart; + +declare const TextMessagePartProvider: FC<PropsWithChildren<{ + text: string; + isRunning?: boolean; +}>>; + +type ThreadAssistantMessage = MessageCommonProps & { + readonly role: "assistant"; + readonly content: readonly ThreadAssistantMessagePart[]; + readonly status: MessageStatus; + readonly metadata: { + readonly unstable_state: ReadonlyJSONValue; + readonly unstable_annotations: readonly ReadonlyJSONValue[]; + readonly unstable_data: readonly ReadonlyJSONValue[]; + readonly steps: readonly ThreadStep[]; + readonly submittedFeedback?: { + readonly type: "negative" | "positive"; + }; + readonly timing?: MessageTiming; + readonly isOptimistic?: boolean; + readonly custom: Record<string, unknown>; + }; +}; + +type ThreadAssistantMessagePart = TextMessagePart | ReasoningMessagePart | ToolCallMessagePart | SourceMessagePart | FileMessagePart | ImageMessagePart | DataMessagePart | GenerativeUIMessagePart; + +declare class ThreadComposerAttachmentRuntimeImpl extends ComposerAttachmentRuntime<"thread-composer"> { + get source(): "thread-composer"; +} + +type ThreadComposerAttachmentState = Attachment & { + readonly source: "thread-composer"; +}; + +type ThreadComposerRuntime = Omit<ComposerRuntime, "getAttachmentByIndex" | "getState"> & { + readonly path: ComposerRuntimePath & { + composerSource: "thread"; + }; + readonly type: "thread"; + getState(): ThreadComposerState; + getAttachmentByIndex(idx: number): AttachmentRuntime & { + source: "thread-composer"; + }; +}; + +type ThreadComposerRuntimeCore = ComposerRuntimeCore; + +type ThreadComposerRuntimeCoreBinding = SubscribableWithState<ThreadComposerRuntimeCore | undefined, ComposerRuntimePath & { + composerSource: "thread"; +}>; + +declare class ThreadComposerRuntimeImpl extends ComposerRuntimeImpl implements ThreadComposerRuntime { + get path(): ComposerRuntimePath & { + composerSource: "thread"; + }; + get type(): "thread"; + private _getState; + constructor(core: ThreadComposerRuntimeCoreBinding); + getState(): ThreadComposerState; + getAttachmentByIndex(idx: number): ThreadComposerAttachmentRuntimeImpl; +} + +type ThreadComposerState = BaseComposerState & { + readonly type: "thread"; +}; + +declare const ThreadEmpty: (_param58: ThreadEmptyProps) => import("react").JSX.Element | null; + +type ThreadEmptyProps = { + children: ReactNode; +}; + +type ThreadHistoryAdapter = { + load(): Promise<ExportedMessageRepository & { + state?: ReadonlyJSONValue; + unstable_resume?: boolean; + }>; + resume?(options: ChatModelRunOptions): AsyncGenerator<ChatModelRunResult, void, unknown>; + append(item: ExportedMessageRepositoryItem): Promise<void>; + delete?(items: ExportedMessageRepositoryItem[]): Promise<void>; + withFormat?<TMessage, TStorageFormat extends Record<string, unknown>>(formatAdapter: MessageFormatAdapter<TMessage, TStorageFormat>): GenericThreadHistoryAdapter<TMessage>; +}; + +declare const ThreadIf: (_param59: ThreadIfProps) => import("react").JSX.Element | null; + +type ThreadIfProps = { + children: ReactNode; + empty?: boolean | undefined; + running?: boolean | undefined; +}; + +declare const ThreadListItemArchive: (_param60: ThreadListItemArchiveProps) => import("react").JSX.Element; + +type ThreadListItemArchiveProps = Omit<PressableProps, "onPress"> & { + children: ReactNode; +}; + +declare const ThreadListItemByIndexProvider: FC<PropsWithChildren<{ + index: number; + archived: boolean; +}>>; + +type ThreadListItemCoreState = { + readonly id: string; + readonly remoteId: string | undefined; + readonly externalId: string | undefined; + readonly status: ThreadListItemStatus; + readonly title?: string | undefined; + readonly lastMessageAt?: Date | undefined; + readonly custom?: Record<string, unknown> | undefined; + readonly runtime?: ThreadRuntimeCore | undefined; +}; + +declare const ThreadListItemDelete: (_param61: ThreadListItemDeleteProps) => import("react").JSX.Element; + +type ThreadListItemDeleteProps = Omit<PressableProps, "onPress"> & { + children: ReactNode; +}; + +type ThreadListItemEventCallback<E extends ThreadListItemEventType> = (payload: ThreadListItemEventPayload[E]) => void; + +type ThreadListItemEventPayload = { + switchedTo: Record<string, never>; + switchedAway: Record<string, never>; +}; + +type ThreadListItemEventType = keyof ThreadListItemEventPayload; + +declare namespace ThreadListItemPrimitiveTitle { + type Props = { + fallback?: ReactNode; + }; +} + +declare const ThreadListItemPrimitiveTitle: FC<ThreadListItemPrimitiveTitle.Props>; + +declare const ThreadListItemRoot: (_param62: ThreadListItemRootProps) => import("react").JSX.Element; + +type ThreadListItemRootProps = ComponentProps<typeof Box> & { + children: ReactNode; +}; + +type ThreadListItemRuntime = { + readonly path: ThreadListItemRuntimePath; + getState(): ThreadListItemState$1; + initialize(): Promise<{ + remoteId: string; + externalId: string | undefined; + }>; + generateTitle(): Promise<void>; + switchTo(options?: { + unarchive?: boolean; + }): Promise<void>; + rename(newTitle: string): Promise<void>; + updateCustom(custom: Record<string, unknown> | undefined): Promise<void>; + archive(): Promise<void>; + unarchive(): Promise<void>; + delete(): Promise<void>; + detach(): void; + subscribe(callback: () => void): Unsubscribe$1; + unstable_on<E extends ThreadListItemEventType>(event: E, callback: ThreadListItemEventCallback<E>): Unsubscribe$1; + __internal_getRuntime(): ThreadListItemRuntime; +}; + +type ThreadListItemRuntimeBinding = SubscribableWithState<ThreadListItemState$1, ThreadListItemRuntimePath>; + +declare class ThreadListItemRuntimeImpl implements ThreadListItemRuntime { + private _core; + private _threadListBinding; + get path(): ThreadListItemRuntimePath; + constructor(_core: ThreadListItemStateBinding, _threadListBinding: ThreadListRuntimeCoreBinding); + protected __internal_bindMethods(): void; + getState(): ThreadListItemState$1; + switchTo(options?: { + unarchive?: boolean; + }): Promise<void>; + rename(newTitle: string): Promise<void>; + updateCustom(custom: Record<string, unknown> | undefined): Promise<void>; + archive(): Promise<void>; + unarchive(): Promise<void>; + delete(): Promise<void>; + initialize(): Promise<{ + remoteId: string; + externalId: string | undefined; + }>; + generateTitle(): Promise<void>; + unstable_on<E extends ThreadListItemEventType>(event: E, callback: ThreadListItemEventCallback<E>): Unsubscribe$1; + subscribe(callback: () => void): Unsubscribe$1; + detach(): void; + __internal_getRuntime(): ThreadListItemRuntime; +} + +type ThreadListItemRuntimePath = { + readonly ref: string; + readonly threadSelector: { + readonly type: "main"; + } | { + readonly type: "index"; + readonly index: number; + } | { + readonly type: "archiveIndex"; + readonly index: number; + } | { + readonly type: "threadId"; + readonly threadId: string; + }; +}; + +declare const ThreadListItemRuntimeProvider: FC<PropsWithChildren<{ + runtime: ThreadListItemRuntime; +}>>; + +type ThreadListItemState = { + readonly id: string; + readonly remoteId: string | undefined; + readonly externalId: string | undefined; + readonly title?: string | undefined; + readonly lastMessageAt?: Date | undefined; + readonly status: ThreadListItemStatus; + readonly custom?: Record<string, unknown> | undefined; +}; + +type ThreadListItemState$1 = { + readonly isMain: boolean; + readonly id: string; + readonly remoteId: string | undefined; + readonly externalId: string | undefined; + readonly status: ThreadListItemStatus; + readonly title?: string | undefined; + readonly lastMessageAt?: Date | undefined; + readonly custom?: Record<string, unknown> | undefined; +}; + +type ThreadListItemStateBinding = SubscribableWithState<ThreadListItemState$1, ThreadListItemRuntimePath>; + +type ThreadListItemStatus = "archived" | "deleted" | "new" | "regular"; + +declare const ThreadListItemTrigger: (_param63: ThreadListItemTriggerProps) => import("react").JSX.Element; + +type ThreadListItemTriggerProps = Omit<PressableProps, "onPress"> & { + children: ReactNode; +}; + +declare const ThreadListItemUnarchive: (_param64: ThreadListItemUnarchiveProps) => import("react").JSX.Element; + +type ThreadListItemUnarchiveProps = Omit<PressableProps, "onPress"> & { + children: ReactNode; +}; + +declare const ThreadListItems: (_param65: ThreadListItemsProps) => import("react").JSX.Element; + +type ThreadListItemsProps = { + renderItem: (props: { + threadId: string; + index: number; + }) => ReactElement; +}; + +declare const ThreadListNew: (_param66: ThreadListNewProps) => import("react").JSX.Element; + +type ThreadListNewProps = Omit<PressableProps, "onPress"> & { + children: ReactNode; +}; + +declare const ThreadListRoot: (_param67: ThreadListRootProps) => import("react").JSX.Element; + +type ThreadListRootProps = ComponentProps<typeof Box> & { + children: ReactNode; +}; + +type ThreadListRuntime = { + getState(): ThreadListState; + subscribe(callback: () => void): Unsubscribe$1; + readonly main: ThreadRuntime; + getById(threadId: string): ThreadRuntime; + readonly mainItem: ThreadListItemRuntime; + getItemById(threadId: string): ThreadListItemRuntime; + getItemByIndex(idx: number): ThreadListItemRuntime; + getArchivedItemByIndex(idx: number): ThreadListItemRuntime; + switchToThread(threadId: string, options?: { + unarchive?: boolean; + }): Promise<void>; + switchToNewThread(): Promise<void>; + getLoadThreadsPromise(): Promise<void>; + reload(): Promise<void>; + loadMore(): Promise<void>; +}; + +type ThreadListRuntimeCore = { + readonly isLoading: boolean; + readonly isLoadingMore?: boolean; + readonly hasMore?: boolean; + mainThreadId: string; + newThreadId: string | undefined; + threadIds: readonly string[]; + archivedThreadIds: readonly string[]; + readonly threadItems: Readonly<Record<string, ThreadListItemCoreState>>; + getMainThreadRuntimeCore(): ThreadRuntimeCore; + getThreadRuntimeCore(threadId: string): ThreadRuntimeCore; + getItemById(threadId: string): ThreadListItemCoreState | undefined; + switchToThread(threadId: string, options?: { + unarchive?: boolean; + }): Promise<void>; + switchToNewThread(): Promise<void>; + getLoadThreadsPromise(): Promise<void>; + reload?(): Promise<void>; + loadMore?(): Promise<void>; + detach(threadId: string): Promise<void>; + rename(threadId: string, newTitle: string): Promise<void>; + updateCustom?(threadId: string, custom: Record<string, unknown> | undefined): Promise<void>; + archive(threadId: string): Promise<void>; + unarchive(threadId: string): Promise<void>; + delete(threadId: string): Promise<void>; + initialize(threadId: string): Promise<{ + remoteId: string; + externalId: string | undefined; + }>; + generateTitle(threadId: string): Promise<void>; + subscribe(callback: () => void): Unsubscribe$1; +}; + +type ThreadListRuntimeCoreBinding = ThreadListRuntimeCore; + +declare class ThreadListRuntimeImpl implements ThreadListRuntime { + private _core; + private _runtimeFactory; + private _getState; + constructor(_core: ThreadListRuntimeCoreBinding, _runtimeFactory?: new (binding: ThreadRuntimeCoreBinding, threadListItemBinding: ThreadListItemRuntimeBinding) => ThreadRuntime); + protected __internal_bindMethods(): void; + switchToThread(threadId: string, options?: { + unarchive?: boolean; + }): Promise<void>; + switchToNewThread(): Promise<void>; + getLoadThreadsPromise(): Promise<void>; + reload(): Promise<void>; + loadMore(): Promise<void>; + getState(): ThreadListState; + subscribe(callback: () => void): Unsubscribe$1; + private _mainThreadListItemRuntime; + readonly main: ThreadRuntime; + get mainItem(): ThreadListItemRuntimeImpl; + getById(threadId: string): ThreadRuntime; + getItemByIndex(idx: number): ThreadListItemRuntimeImpl; + getArchivedItemByIndex(idx: number): ThreadListItemRuntimeImpl; + getItemById(threadId: string): ThreadListItemRuntimeImpl; +} + +type ThreadListState = { + readonly mainThreadId: string; + readonly newThreadId: string | undefined; + readonly threadIds: readonly string[]; + readonly archivedThreadIds: readonly string[]; + readonly isLoading: boolean; + readonly isLoadingMore: boolean; + readonly hasMore: boolean; + readonly threadItems: Readonly<Record<string, Omit<ThreadListItemState$1, "isMain" | "threadId">>>; +}; + +type ThreadMessage = BaseThreadMessage & (ThreadSystemMessage | ThreadUserMessage | ThreadAssistantMessage); + +type ThreadMessageLike = { + readonly role: "assistant" | "system" | "user"; + readonly content: string | readonly (TextMessagePart | ReasoningMessagePart | SourceMessagePart | ImageMessagePart | FileMessagePart | DataMessagePart | GenerativeUIMessagePart | Unstable_AudioMessagePart | DataPrefixedPart | { + readonly type: "tool-call"; + readonly toolCallId?: string; + readonly toolName: string; + readonly args?: ReadonlyJSONObject; + readonly argsText?: string; + readonly artifact?: any; + readonly result?: any | undefined; + readonly isError?: boolean | undefined; + readonly parentId?: string | undefined; + readonly messages?: readonly ThreadMessage[] | undefined; + readonly interrupt?: { + type: "human"; + payload: unknown; + }; + readonly timing?: ToolCallTiming; + readonly providerMetadata?: PartProviderMetadata; + readonly approval?: { + readonly id: string; + readonly approved?: boolean; + readonly reason?: string; + readonly isAutomatic?: boolean; + readonly options?: readonly ToolApprovalOption[]; + readonly optionId?: string; + readonly resolution?: "cancelled" | "expired"; + }; + })[]; + readonly id?: string | undefined; + readonly createdAt?: Date | undefined; + readonly status?: MessageStatus | undefined; + readonly attachments?: readonly (Omit<CompleteAttachment, "content"> & { + readonly content: readonly (ThreadUserMessagePart | DataPrefixedPart)[]; + })[] | undefined; + readonly metadata?: { + readonly unstable_state?: ReadonlyJSONValue; + readonly unstable_annotations?: readonly ReadonlyJSONValue[] | undefined; + readonly unstable_data?: readonly ReadonlyJSONValue[] | undefined; + readonly steps?: readonly ThreadStep[] | undefined; + readonly timing?: MessageTiming | undefined; + readonly submittedFeedback?: { + readonly type: "negative" | "positive"; + }; + readonly isOptimistic?: boolean | undefined; + readonly custom?: Record<string, unknown> | undefined; + } | undefined; +}; + +declare const ThreadMessages: FC<ThreadMessagesProps>; + +type ThreadMessagesProps = ({ + components: MessageComponents; + children?: never; +} & WindowingProps) | ({ + children: (value: { + message: ThreadMessage; + }) => ReactNode; + components?: never; +} & WindowingProps); + +declare namespace ThreadPrimitiveMessageByIndex { + type Props = { + index: number; + components: MessagesComponentConfig; + }; +} + +declare const ThreadPrimitiveMessageByIndex: FC<ThreadPrimitiveMessageByIndex.Props>; + +declare namespace ThreadPrimitiveMessages { + type Props = { + components: MessagesComponentConfig; + children?: never; + } | { + children: (value: { + message: MessageState; + }) => ReactNode; + components?: never; + }; +} + +declare const ThreadPrimitiveMessages: import("react").NamedExoticComponent<ThreadPrimitiveMessages.Props>; + +declare namespace ThreadPrimitiveSuggestionByIndex { + type Props = { + index: number; + components: SuggestionsComponentConfig; + }; +} + +declare const ThreadPrimitiveSuggestionByIndex: FC<ThreadPrimitiveSuggestionByIndex.Props>; + +declare namespace ThreadPrimitiveSuggestions { + type Props = { + components: SuggestionsComponentConfig; + children?: never; + } | { + children: (value: { + suggestion: SuggestionState; + }) => ReactNode; + components?: never; + }; +} + +declare const ThreadPrimitiveSuggestions: import("react").NamedExoticComponent<ThreadPrimitiveSuggestions.Props>; + +declare namespace ThreadPrimitiveUnstable_MessageById { + type Props = { + messageId: string; + components: MessagesComponentConfig; + }; +} + +declare const ThreadPrimitiveUnstable_MessageById: FC<ThreadPrimitiveUnstable_MessageById.Props>; + +declare const ThreadRoot: (_param68: ThreadRootProps) => import("react").JSX.Element; + +type ThreadRootProps = ComponentProps<typeof Box> & { + children: ReactNode; +}; + +type ThreadRuntime = { + readonly path: ThreadRuntimePath; + readonly composer: ThreadComposerRuntime; + getState(): ThreadState; + append(message: CreateAppendMessage): void; + deleteMessage(messageId: string): void | Promise<void>; + startRun(config: CreateStartRunConfig): void; + resumeRun(config: CreateResumeRunConfig): void; + exportExternalState(): any; + importExternalState(state: any): void; + subscribe(callback: () => void): Unsubscribe$1; + cancelRun(): void; + getModelContext(): ModelContext$1; + export(): ExportedMessageRepository; + import(repository: ExportedMessageRepository): void; + reset(initialMessages?: readonly ThreadMessageLike[]): void; + getMessageByIndex(idx: number): MessageRuntime; + getMessageById(messageId: string): MessageRuntime; + stopSpeaking(): void; + connectVoice(): void; + disconnectVoice(): void; + getVoiceVolume(): number; + subscribeVoiceVolume(callback: () => void): Unsubscribe$1; + muteVoice(): void; + unmuteVoice(): void; + unstable_on<E extends ThreadRuntimeEventType>(event: E, callback: ThreadRuntimeEventCallback<E>): Unsubscribe$1; +}; + +type ThreadRuntimeCore = Readonly<{ + getMessageById: (messageId: string) => { + parentId: string | null; + message: ThreadMessage; + index: number; + } | undefined; + getBranches: (messageId: string) => readonly string[]; + switchToBranch: (branchId: string) => void; + append: (message: AppendMessage) => void; + deleteMessage: (messageId: string) => void | Promise<void>; + startRun: (config: StartRunConfig) => void; + resumeRun: (config: ResumeRunConfig) => void; + cancelRun: () => void; + addToolResult: (options: AddToolResultOptions) => void; + resumeToolCall: (options: ResumeToolCallOptions) => void; + respondToToolApproval: (options: RespondToToolApprovalOptions) => void; + speak: (messageId: string) => void; + stopSpeaking: () => void; + connectVoice: () => void; + disconnectVoice: () => void; + muteVoice: () => void; + unmuteVoice: () => void; + submitFeedback: (feedback: SubmitFeedbackOptions) => void; + getModelContext: () => ModelContext$1; + composer: ThreadComposerRuntimeCore; + getEditComposer: (messageId: string) => EditComposerRuntimeCore | undefined; + beginEdit: (messageId: string) => void; + getQueueItems?: () => readonly QueueItemState[]; + steerQueueItem?: (queueItemId: string) => void; + removeQueueItem?: (queueItemId: string) => void; + speech: SpeechState | undefined; + voice: VoiceSessionState | undefined; + capabilities: Readonly<RuntimeCapabilities>; + isDisabled: boolean; + isSendDisabled: boolean; + isLoading: boolean; + isRunning?: boolean | undefined; + messages: readonly ThreadMessage[]; + state: ReadonlyJSONValue; + suggestions: readonly ThreadSuggestion$1[]; + extras: unknown; + subscribe: (callback: () => void) => Unsubscribe$1; + getVoiceVolume: () => number; + subscribeVoiceVolume: (callback: () => void) => Unsubscribe$1; + import(repository: ExportedMessageRepository): void; + export(): ExportedMessageRepository; + exportExternalState(): any; + importExternalState(state: any): void; + reset(initialMessages?: readonly ThreadMessageLike[]): void; + unstable_on<E extends ThreadRuntimeEventType>(event: E, callback: ThreadRuntimeEventCallback<E>): Unsubscribe$1; +}>; + +type ThreadRuntimeCoreBinding = SubscribableWithState<ThreadRuntimeCore, ThreadRuntimePath> & { + outerSubscribe(callback: () => void): Unsubscribe$1; +}; + +type ThreadRuntimeEventCallback<E extends ThreadRuntimeEventType> = (payload: ThreadRuntimeEventPayload[E]) => void; + +type ThreadRuntimeEventPayload = { + runStart: Record<string, never>; + runEnd: Record<string, never>; + initialize: Record<string, never>; + modelContextUpdate: Record<string, never>; +}; + +type ThreadRuntimeEventType = keyof ThreadRuntimeEventPayload; + +declare class ThreadRuntimeImpl implements ThreadRuntime { + get path(): ThreadRuntimePath; + get __internal_threadBinding(): Subscribable & { + path: ThreadRuntimePath; + getState: () => Readonly<{ + getMessageById: (messageId: string) => { + parentId: string | null; + message: ThreadMessage; + index: number; + } | undefined; + getBranches: (messageId: string) => readonly string[]; + switchToBranch: (branchId: string) => void; + append: (message: AppendMessage) => void; + deleteMessage: (messageId: string) => void | Promise<void>; + startRun: (config: StartRunConfig) => void; + resumeRun: (config: ResumeRunConfig) => void; + cancelRun: () => void; + addToolResult: (options: AddToolResultOptions) => void; + resumeToolCall: (options: ResumeToolCallOptions) => void; + respondToToolApproval: (options: RespondToToolApprovalOptions) => void; + speak: (messageId: string) => void; + stopSpeaking: () => void; + connectVoice: () => void; + disconnectVoice: () => void; + muteVoice: () => void; + unmuteVoice: () => void; + submitFeedback: (feedback: SubmitFeedbackOptions) => void; + getModelContext: () => ModelContext$1; + composer: Readonly<{ + isEditing: boolean; + canCancel: boolean; + canSend: boolean; + isEmpty: boolean; + attachments: readonly Attachment[]; + attachmentAccept: string; + addAttachment: (fileOrAttachment: File | CreateAttachment) => Promise<void>; + removeAttachment: (attachmentId: string) => Promise<void>; + text: string; + setText: (value: string) => void; + role: MessageRole; + setRole: (role: MessageRole) => void; + runConfig: RunConfig; + setRunConfig: (runConfig: RunConfig) => void; + quote: QuoteInfo | undefined; + setQuote: (quote: QuoteInfo | undefined) => void; + reset: () => Promise<void>; + clearAttachments: () => Promise<void>; + send: (options?: SendOptions) => void; + cancel: () => void; + queue: readonly QueueItemState[]; + steerQueueItem: (queueItemId: string) => void; + removeQueueItem: (queueItemId: string) => void; + dictation: DictationState | undefined; + startDictation: () => void; + stopDictation: () => void; + subscribe: (callback: () => void) => Unsubscribe$1; + unstable_on: <E extends ComposerRuntimeEventType>(event: E, callback: ComposerRuntimeEventCallback<E>) => Unsubscribe$1; + }>; + getEditComposer: (messageId: string) => EditComposerRuntimeCore | undefined; + beginEdit: (messageId: string) => void; + getQueueItems?: () => readonly QueueItemState[]; + steerQueueItem?: (queueItemId: string) => void; + removeQueueItem?: (queueItemId: string) => void; + speech: SpeechState | undefined; + voice: VoiceSessionState | undefined; + capabilities: Readonly<RuntimeCapabilities>; + isDisabled: boolean; + isSendDisabled: boolean; + isLoading: boolean; + isRunning?: boolean | undefined; + messages: readonly ThreadMessage[]; + state: ReadonlyJSONValue; + suggestions: readonly ThreadSuggestion$1[]; + extras: unknown; + subscribe: (callback: () => void) => Unsubscribe$1; + getVoiceVolume: () => number; + subscribeVoiceVolume: (callback: () => void) => Unsubscribe$1; + import(repository: ExportedMessageRepository): void; + export(): ExportedMessageRepository; + exportExternalState(): any; + importExternalState(state: any): void; + reset(initialMessages?: readonly ThreadMessageLike[]): void; + unstable_on<E extends ThreadRuntimeEventType>(event: E, callback: ThreadRuntimeEventCallback<E>): Unsubscribe$1; + }>; + } & { + outerSubscribe(callback: () => void): Unsubscribe$1; + } & { + getStateState(): ThreadState; + }; + private readonly _threadBinding; + constructor(threadBinding: ThreadRuntimeCoreBinding, threadListItemBinding: ThreadListItemRuntimeBinding); + protected __internal_bindMethods(): void; + readonly composer: ThreadComposerRuntimeImpl; + getState(): ThreadState; + append(message: CreateAppendMessage): void; + deleteMessage(messageId: string): void | Promise<void>; + subscribe(callback: () => void): Unsubscribe$1; + getModelContext(): ModelContext$1; + startRun(config: CreateStartRunConfig): void; + resumeRun(config: CreateResumeRunConfig): void; + exportExternalState(): any; + importExternalState(state: any): void; + cancelRun(): void; + stopSpeaking(): void; + connectVoice(): void; + disconnectVoice(): void; + getVoiceVolume(): number; + subscribeVoiceVolume(callback: () => void): Unsubscribe$1; + muteVoice(): void; + unmuteVoice(): void; + export(): ExportedMessageRepository; + import(data: ExportedMessageRepository): void; + reset(initialMessages?: readonly ThreadMessageLike[]): void; + getMessageByIndex(idx: number): MessageRuntimeImpl; + getMessageById(messageId: string): MessageRuntimeImpl; + private _getMessageRuntime; + private _eventSubscriptionSubjects; + unstable_on<E extends ThreadRuntimeEventType>(event: E, callback: ThreadRuntimeEventCallback<E>): Unsubscribe$1; +} + +type ThreadRuntimePath = { + readonly ref: string; + readonly threadSelector: { + readonly type: "main"; + } | { + readonly type: "threadId"; + readonly threadId: string; + }; +}; + +type ThreadState = { + readonly threadId: string; + readonly metadata: ThreadListItemState$1; + readonly isDisabled: boolean; + readonly isLoading: boolean; + readonly isRunning: boolean; + readonly capabilities: RuntimeCapabilities; + readonly messages: readonly ThreadMessage[]; + readonly state: ReadonlyJSONValue; + readonly suggestions: readonly ThreadSuggestion$1[]; + readonly extras: unknown; + readonly speech: SpeechState | undefined; + readonly voice: VoiceSessionState | undefined; +}; + +type ThreadState$1 = { + readonly isEmpty: boolean; + readonly isDisabled: boolean; + readonly isLoading: boolean; + readonly isRunning: boolean; + readonly capabilities: RuntimeCapabilities; + readonly messages: readonly MessageState[]; + readonly state: ReadonlyJSONValue; + readonly suggestions: readonly ThreadSuggestion$1[]; + readonly extras: unknown; + readonly speech: SpeechState | undefined; + readonly voice: VoiceSessionState | undefined; + readonly composer: ComposerState; +}; + +type ThreadStep = { + readonly messageId?: string; + readonly usage?: { + readonly inputTokens: number; + readonly outputTokens: number; + } | undefined; +}; + +declare const ThreadSuggestion: (_param69: ThreadSuggestionProps) => import("react").JSX.Element; + +type ThreadSuggestion$1 = { + prompt: string; +}; + +type ThreadSuggestionProps = Omit<PressableProps, "onPress"> & { + children: ReactNode; + prompt: string; + send?: boolean | undefined; + clearComposer?: boolean | undefined; +}; + +type ThreadSystemMessage = MessageCommonProps & { + readonly role: "system"; + readonly content: readonly [ + TextMessagePart + ]; + readonly metadata: { + readonly unstable_state?: undefined; + readonly unstable_annotations?: undefined; + readonly unstable_data?: undefined; + readonly steps?: undefined; + readonly submittedFeedback?: undefined; + readonly timing?: undefined; + readonly custom: Record<string, unknown>; + }; +}; + +type ThreadUserMessage = MessageCommonProps & { + readonly role: "user"; + readonly content: readonly ThreadUserMessagePart[]; + readonly attachments: readonly CompleteAttachment[]; + readonly metadata: { + readonly unstable_state?: undefined; + readonly unstable_annotations?: undefined; + readonly unstable_data?: undefined; + readonly steps?: undefined; + readonly submittedFeedback?: undefined; + readonly timing?: undefined; + readonly custom: Record<string, unknown>; + }; +}; + +type ThreadUserMessagePart = TextMessagePart | ImageMessagePart | FileMessagePart | DataMessagePart | Unstable_AudioMessagePart; + +type ThreadsState = { + readonly mainThreadId: string; + readonly newThreadId: string | null; + readonly isLoading: boolean; + readonly isLoadingMore: boolean; + readonly hasMore: boolean; + readonly threadIds: readonly string[]; + readonly archivedThreadIds: readonly string[]; + readonly threadItems: readonly ThreadListItemState[]; + readonly main: ThreadState$1; +}; + +type TitleGenerationAdapter = { + generateTitle(messages: readonly ThreadMessage[]): Promise<string>; +}; + +type Tool<TArgs extends Record<string, unknown> = Record<string, unknown>, TResult = unknown> = FrontendTool<TArgs, TResult> | BackendTool<TArgs, TResult> | HumanTool<TArgs, TResult> | ProviderTool<TArgs, TResult> | McpTool | ToolWithoutType<TArgs, TResult>; + +type ToolApprovalOption = { + readonly id: string; + readonly kind: ToolApprovalOptionKind | (string & {}); + readonly label?: string; + readonly description?: string; + readonly grants?: readonly string[]; + readonly confirm?: boolean | { + title?: string; + description?: string; + }; +}; + +type ToolApprovalOptionKind = "allow-always" | "allow-once" | "reject-always" | "reject-once"; + +type ToolApprovalResponse = { + readonly approved: boolean; + readonly reason?: string; +} | { + readonly optionId: string; + readonly reason?: string; +} | { + readonly approved: boolean; + readonly optionId: string; + readonly reason?: string; +}; + +type ToolArgsStatus<TArgs extends Record<string, unknown> = Record<string, unknown>> = { + status: "complete" | "incomplete" | "requires-action" | "running"; + propStatus: Partial<Record<keyof TArgs, PropFieldStatus>>; +}; + +type ToolBase<TArgs extends Record<string, unknown> = Record<string, unknown>, TResult = unknown> = { + streamCall?: ToolStreamCallFunction<TArgs, TResult>; + display?: ToolDisplay; +}; + +interface ToolCallArgsReader<TArgs extends Record<string, unknown>> { + get<PathT extends TypePath<TArgs>>(...fieldPath: PathT): Promise<TypeAtPath<TArgs, PathT>>; + streamValues<PathT extends TypePath<TArgs>>(...fieldPath: PathT): AsyncIterableStream<DeepPartial<TypeAtPath<TArgs, PathT>>>; + streamText<PathT extends TypePath<TArgs>>(...fieldPath: PathT): TypeAtPath<TArgs, PathT> extends string & (infer U) ? AsyncIterableStream<U> : never; + forEach<PathT extends TypePath<TArgs>>(...fieldPath: PathT): NonNullable<TypeAtPath<TArgs, PathT>> extends Array<infer U> ? AsyncIterableStream<U> : never; +} + +type ToolCallCompleteText<TArgs extends Record<string, unknown>, TResult> = ReactNode | ((options: { + args: TArgs; + result: TResult | undefined; +}) => ReactNode); + +type ToolCallMessagePart<TArgs = ReadonlyJSONObject, TResult = unknown> = { + readonly type: "tool-call"; + readonly toolCallId: string; + readonly toolName: string; + readonly args: TArgs; + readonly result?: TResult | undefined; + readonly isError?: boolean | undefined; + readonly argsText: string; + readonly artifact?: unknown; + readonly timing?: ToolCallTiming; + readonly mcp?: ToolCallMessagePartMcpMetadata; + readonly providerMetadata?: PartProviderMetadata; + readonly modelContent?: readonly ToolModelContentPart[] | undefined; + readonly interrupt?: { + type: "human"; + payload: unknown; + }; + readonly approval?: { + readonly id: string; + readonly approved?: boolean; + readonly reason?: string; + readonly isAutomatic?: boolean; + readonly options?: readonly ToolApprovalOption[]; + readonly optionId?: string; + readonly resolution?: "cancelled" | "expired"; + }; + readonly parentId?: string; + readonly messages?: readonly ThreadMessage[]; +}; + +type ToolCallMessagePartComponent<TArgs = any, TResult = any> = ComponentType<ToolCallMessagePartProps<TArgs, TResult>>; + +type ToolCallMessagePartMcpMetadata = { + readonly app?: McpAppMetadata; +}; + +type ToolCallMessagePartProps<TArgs = any, TResult = unknown> = MessagePartState & ToolCallMessagePart<TArgs, TResult> & { + addResult: (result: TResult | ToolResponse<TResult>) => void; + resume: (payload: unknown) => void; + respondToApproval: (response: ToolApprovalResponse) => void; +}; + +type ToolCallMessagePartStatus = { + readonly type: "requires-action"; + readonly reason: "interrupt"; +} | MessagePartStatus; + +interface ToolCallReader<TArgs extends Record<string, unknown> = Record<string, unknown>, TResult = unknown> { + args: ToolCallArgsReader<TArgs>; + response: ToolCallResponseReader<TResult>; + result: { + get: () => Promise<TResult>; + }; +} + +interface ToolCallResponseReader<TResult> { + get: () => Promise<ToolResponse<TResult>>; +} + +type ToolCallRunningText<TArgs extends Record<string, unknown>> = ReactNode | ((options: { + args: TArgs; +}) => ReactNode); + +type ToolCallStatus = "complete" | "error" | "requires-action" | "running"; + +type ToolCallText<TArgs extends Record<string, unknown>, TResult> = { + running: ToolCallRunningText<TArgs>; + complete?: ToolCallCompleteText<TArgs, TResult> | undefined; +} | { + running?: ToolCallRunningText<TArgs> | undefined; + complete: ToolCallCompleteText<TArgs, TResult>; +}; + +type ToolCallTiming = { + readonly startedAt: number; + readonly completedAt?: number; +}; + +type ToolDeclaration<TArgs extends Record<string, unknown> = Record<string, unknown>, TResult = unknown> = FrontendTool<TArgs, TResult> | BackendToolDeclaration<TArgs, TResult> | HumanTool<TArgs, TResult> | ProviderTool<TArgs, TResult> | McpTool | ToolWithoutType<TArgs, TResult>; + +type ToolDefinition<TArgs extends Record<string, unknown> = Record<string, unknown>, TResult = unknown> = WithRender<Tool<TArgs, TResult>, TArgs, TResult>; + +type ToolDisplay = "inline" | "standalone"; + +type ToolExecute<TArgs extends Record<string, unknown>, TResult> = (args: TArgs, context: ToolExecuteContext) => TResult | Promise<TResult>; + +type ToolExecuteContext = Parameters<NonNullable<ToolDeclaration["execute"]>>[1]; + +type ToolExecuteFunction<TArgs, TResult> = (args: TArgs, context: ToolExecutionContext) => TResult | Promise<TResult>; + +type ToolExecutionContext = { + toolCallId: string; + abortSignal: AbortSignal; + human: (payload: unknown) => Promise<unknown>; +}; + +declare const ToolFallback: (_param70: ToolFallbackProps) => import("react").JSX.Element; + +type ToolFallbackBaseProps = Omit<ToolCallMessagePartProps, "addResult" | "respondToApproval" | "resume"> & Partial<Pick<ToolCallMessagePartProps, "addResult" | "respondToApproval" | "resume">>; + +type ToolFallbackProps = ToolFallbackBaseProps & { + expanded?: boolean; + maxArgLines?: number; + maxResultLines?: number; + maxResultPreviewLines?: number; + renderHeader?: (props: { + toolName: string; + status: ToolCallStatus; + expanded: boolean; + }) => ReactNode; + renderArgs?: (props: { + args: unknown; + argsText: string; + }) => ReactNode; + renderResult?: (props: { + result: unknown; + isError: boolean; + }) => ReactNode; +}; + +type ToolModelContentPart = { + readonly type: "text"; + readonly text: string; +} | { + readonly type: "file"; + readonly data: string; + readonly mediaType: string; + readonly filename?: string; +}; + +type ToolModelOutputFunction<TArgs, TResult> = (options: { + toolCallId: string; + input: TArgs; + output: TResult; +}) => readonly ToolModelContentPart[] | Promise<readonly ToolModelContentPart[]>; + +type ToolParameters<TArgs extends Record<string, unknown>> = ToolDeclaration<TArgs>["parameters"]; + +declare class ToolResponse<TResult> { + get [TOOL_RESPONSE_SYMBOL](): boolean; + readonly artifact?: ReadonlyJSONValue; + readonly result: TResult; + readonly isError: boolean; + readonly modelContent?: readonly ToolModelContentPart[]; + readonly messages?: ReadonlyJSONValue; + constructor(options: ToolResponseLike<TResult>); + static [Symbol.hasInstance](obj: unknown): obj is ToolResponse<ReadonlyJSONValue>; + static toResponse(result: any | ToolResponse<any>): ToolResponse<any>; +} + +type ToolResponseLike<TResult> = { + result: TResult; + artifact?: ReadonlyJSONValue | undefined; + isError?: boolean | undefined; + modelContent?: readonly ToolModelContentPart[] | undefined; + messages?: ReadonlyJSONValue | undefined; +}; + +type ToolStreamCall<TArgs extends Record<string, unknown>, TResult> = (reader: ToolCallReader<TArgs, TResult>, context: ToolExecuteContext) => void; + +type ToolStreamCallFunction<TArgs extends Record<string, unknown> = Record<string, unknown>, TResult = unknown> = (reader: ToolCallReader<TArgs, TResult>, context: ToolExecutionContext) => void; + +type ToolWithoutType<TArgs extends Record<string, unknown> = Record<string, unknown>, TResult = unknown> = (Omit<FrontendTool<TArgs, TResult>, "type"> | Omit<BackendTool<TArgs, TResult>, "type"> | Omit<HumanTool<TArgs, TResult>, "type"> | Omit<ProviderTool<TArgs, TResult>, "type">) & { + type?: undefined; +}; + +type Toolkit = Record<string, ToolDefinition<any, any>>; + +type ToolkitDefinition<TArgsByName extends { + [K in keyof TArgsByName]: Record<string, unknown>; +} = Record<string, any>, TResultByName extends { + [K in keyof TArgsByName]: unknown; +} = { + [K in keyof TArgsByName]: any; +}> = { + [K in keyof TArgsByName]: ToolkitDefinitionEntry<TArgsByName[K], TResultByName[K]>; +}; + +type ToolkitDefinitionEntry<TArgs extends Record<string, unknown> = Record<string, unknown>, TResult = unknown> = ToolkitDefinitionInput<TArgs, TResult> | ToolDefinition<any, any>; + +type ToolkitDefinitionEntryWithParameters<TArgs extends Record<string, unknown> = Record<string, unknown>, TResult = unknown> = ToolkitDefinitionInput<TArgs, TResult> & { + parameters: NonNullable<ToolParameters<TArgs>>; +}; + +type ToolkitDefinitionInput<TArgs extends Record<string, unknown>, TResult> = WithRender<ToolDeclaration<TArgs, TResult> extends (infer T) ? T extends { + streamCall?: unknown; +} ? OverrideToolDeclarationCallbacks<T, TArgs, TResult> : never : never, TArgs, TResult>; + +declare const Tools: Resource<ClientOutput<"tools">, [ + { + toolkit?: Toolkit; + mcpApp?: ResourceElement<McpAppResourceOutput> | undefined; + } +]>; + +type TupleIndex<T extends readonly any[]> = Exclude<keyof T, keyof any[]>; + +type TypeAtPath<T, P extends readonly any[]> = P extends [ + infer Head, + ...infer Rest +] ? Head extends keyof T ? TypeAtPath<T[Head], Rest> : never : T; + +type TypePath<T> = [ +] | (0 extends 1 & T ? any[] : T extends object ? T extends readonly any[] ? number extends T["length"] ? { + [K in TupleIndex<T>]: [ + AsNumber<K>, + ...TypePath<T[K]> + ]; +}[TupleIndex<T>] : [ + number, + ...TypePath<T[number]> +] : { + [K in ObjectKey<T>]: [ + K, + ...TypePath<T[K]> + ]; +}[ObjectKey<T>] : [ +]); + +type UnionToIntersection<U> = (U extends unknown ? (x: U) => void : never) extends ((x: infer I) => void) ? I : never; + +type Unstable_AudioMessagePart = { + readonly type: "audio"; + readonly audio: { + readonly data: string; + readonly format: "mp3" | "wav"; + }; +}; + +type Unstable_AudioMessagePartComponent = ComponentType<Unstable_AudioMessagePartProps>; + +type Unstable_AudioMessagePartProps = MessagePartState & Unstable_AudioMessagePart; + +type Unstable_InferInteractableState<TSchema> = TSchema extends { + "~standard": { + types?: { + output: infer TOutput; + } | undefined; + }; +} ? TOutput : unknown; + +type Unstable_InteractableConfig<TSchema extends Unstable_InteractableStateSchema> = { + description: string; + stateSchema: TSchema; + initialState: Unstable_InferInteractableState<TSchema>; + id?: string | undefined; + updateRender?: ToolCallMessagePartComponent | undefined; +}; + +type Unstable_InteractableDefinition = { + id: string; + name: string; + description: string; + stateSchema: Unstable_InteractableStateSchema; + state: unknown; + initialState: unknown; + scope?: InteractableScope | undefined; +}; + +type Unstable_InteractablePersistedState = Record<string, { + name: string; + state: unknown; +}>; + +type Unstable_InteractablePersistenceAdapter = { + save(state: Unstable_InteractablePersistedState): void | Promise<void>; + load?(): Unstable_InteractablePersistedState | null | undefined | Promise<Unstable_InteractablePersistedState | null | undefined>; +}; + +type Unstable_InteractablePersistenceStatus = { + isPending: boolean; + error: unknown; +}; + +type Unstable_InteractableRegistration = { + id: string; + name: string; + description: string; + stateSchema: Unstable_InteractableStateSchema; + initialState: unknown; + updateRender?: ToolCallMessagePartComponent | undefined; +}; + +type Unstable_InteractableSnapshotEntry = { + id: string; + name: string; + state: unknown; + partial?: boolean | undefined; +}; + +type Unstable_InteractableStateSchema = NonNullable<Extract<Tool, { + parameters: unknown; +}>["parameters"]>; + +type Unstable_InteractableToolConfig<TSchema extends Unstable_InteractableStateSchema> = { + description: string; + stateSchema: TSchema; + render: (props: Unstable_InteractableToolRenderProps<Unstable_InferInteractableState<TSchema>>) => ReactNode; +}; + +type Unstable_InteractableToolRenderProps<TState> = { + state: TState; + setState: (updater: TState | ((prev: TState) => TState)) => void; + version: Unstable_InteractableVersionInfo<TState> | undefined; + id: string; + streaming: boolean; +}; + +type Unstable_InteractableVersion = { + state: unknown; + origin: "create" | "update" | "user-edit"; + toolCallId?: string | undefined; +}; + +type Unstable_InteractableVersionInfo<TState> = { + state: TState; + isLatest: boolean; + restore: () => void; +}; + +type Unstable_InteractablesClientSchema = { + methods: Unstable_InteractablesMethods; +}; + +type Unstable_InteractablesConfig = { + persistence?: Unstable_InteractablePersistenceAdapter | undefined; +}; + +type Unstable_InteractablesMethods = { + getState(): Unstable_InteractablesState; + register(def: Unstable_InteractableRegistration): Unsubscribe$1; + setState(id: string, updater: (prev: unknown) => unknown): void; + exportState(): Unstable_InteractablePersistedState; + importState(saved: Unstable_InteractablePersistedState): void; + setPersistenceAdapter(adapter: Unstable_InteractablePersistenceAdapter | undefined): void; + flush(): Promise<void>; +}; + +type Unstable_InteractablesState = { + definitions: Record<string, Unstable_InteractableDefinition>; + persistence: Record<string, Unstable_InteractablePersistenceStatus>; +}; + +type Unsubscribe = () => void; + +type Unsubscribe$1 = () => void; + +type UseActionBarCopyOptions = { + copiedDuration?: number | undefined; + copyToClipboard?: ((text: string) => void | Promise<void>) | undefined; +}; + +type UseComposerIfProps = RequireAtLeastOne<ComposerIfFilters>; + +type UseToolCallChecklistOptions = { + formatToolName?: ((toolName: string) => string) | undefined; +}; + +type ValidateClient<K extends keyof ScopeRegistry> = ScopeRegistry[K] extends { + methods: ClientMethods; +} ? "meta" extends keyof ScopeRegistry[K] ? ScopeRegistry[K]["meta"] extends ClientMetaType ? "events" extends keyof ScopeRegistry[K] ? ScopeRegistry[K]["events"] extends ClientEventsType<K> ? ScopeRegistry[K] : ClientError<`ERROR: ${K & string} has invalid events type`> : ScopeRegistry[K] : ClientError<`ERROR: ${K & string} has invalid meta type`> : "events" extends keyof ScopeRegistry[K] ? ScopeRegistry[K]["events"] extends ClientEventsType<K> ? ScopeRegistry[K] : ClientError<`ERROR: ${K & string} has invalid events type`> : ScopeRegistry[K] : ClientError<`ERROR: ${K & string} has invalid methods type`>; + +type VoiceSessionControls = { + disconnect: () => void; + mute: () => void; + unmute: () => void; +}; + +type VoiceSessionHelpers = { + setStatus: (status: RealtimeVoiceAdapter.Status) => void; + end: (reason: "cancelled" | "error" | "finished", error?: unknown) => void; + emitTranscript: (item: RealtimeVoiceAdapter.TranscriptItem) => void; + emitMode: (mode: RealtimeVoiceAdapter.Mode) => void; + emitVolume: (volume: number) => void; + isDisposed: () => boolean; +}; + +type VoiceSessionState = { + readonly status: RealtimeVoiceAdapter.Status; + readonly isMuted: boolean; + readonly mode: RealtimeVoiceAdapter.Mode; +}; + +type WildcardPayload = { + [K in keyof ClientEventMap]: { + event: K; + payload: ClientEventMap[K]; + }; +}[Extract<keyof ClientEventMap, string>]; + +type WindowingProps = { + windowSize?: number | undefined; + windowOverscan?: number | undefined; +}; + +type WithRender<T, TArgs extends Record<string, unknown>, TResult> = T extends { + type: "frontend" | "human"; +} ? T & (T extends { + type: "frontend"; +} ? { + render: ToolCallMessagePartComponent<TArgs, TResult>; +} | { + render?: ToolCallMessagePartComponent<TArgs, TResult>; + renderText: ToolCallText<TArgs, TResult>; +} : { + render: ToolCallMessagePartComponent<TArgs, TResult>; +}) : T & { + render?: ToolCallMessagePartComponent<TArgs, TResult> | undefined; + renderText?: ToolCallText<TArgs, TResult> | undefined; +}; + +declare namespace actionBar_d_exports { + export { ActionBarCopy as Copy, ActionBarCopyProps as CopyProps, ActionBarEdit as Edit, ActionBarEditProps as EditProps, ActionBarFeedbackNegative as FeedbackNegative, ActionBarFeedbackNegativeProps as FeedbackNegativeProps, ActionBarFeedbackPositive as FeedbackPositive, ActionBarFeedbackPositiveProps as FeedbackPositiveProps, ActionBarReload as Reload, ActionBarReloadProps as ReloadProps }; +} + +declare namespace attachment_d_exports { + export { AttachmentName as Name, AttachmentNameProps as NameProps, AttachmentRemove as Remove, AttachmentRemoveProps as RemoveProps, AttachmentRoot as Root, AttachmentRootProps as RootProps, AttachmentStatus as Status, AttachmentStatusProps as StatusProps, AttachmentThumb as Thumb, AttachmentThumbProps as ThumbProps }; +} + +declare namespace branchPicker_d_exports { + export { BranchPickerCount as Count, BranchPickerCountProps as CountProps, BranchPickerNext as Next, BranchPickerNextProps as NextProps, BranchPickerNumber as Number, BranchPickerNumberProps as NumberProps, BranchPickerPrevious as Previous, BranchPickerPreviousProps as PreviousProps }; +} + +declare namespace chainOfThought_d_exports { + export { ChainOfThoughtAccordionTrigger as AccordionTrigger, ChainOfThoughtAccordionTriggerProps as AccordionTriggerProps, ChainOfThoughtPrimitiveParts as Parts, ChainOfThoughtRoot as Root, ChainOfThoughtRootProps as RootProps }; +} + +declare namespace checklist_d_exports { + export { ChecklistItem as Item, ChecklistItemProps as ItemProps, ChecklistProgress as Progress, ChecklistProgressProps as ProgressProps, ChecklistRoot as Root, ChecklistRootProps as RootProps }; +} + +declare namespace composer_d_exports { + export { ComposerAddAttachment as AddAttachment, ComposerAddAttachmentProps as AddAttachmentProps, ComposerAttachmentByIndex as AttachmentByIndex, ComposerAttachments as Attachments, ComposerCancel as Cancel, ComposerCancelProps as CancelProps, ComposerPrimitiveIf as If, ComposerInput as Input, ComposerInputProps as InputProps, ComposerPrimitiveQueue as Queue, ComposerQuote as Quote, ComposerQuoteDismiss as QuoteDismiss, ComposerQuoteDismissProps as QuoteDismissProps, ComposerQuoteProps as QuoteProps, ComposerQuoteText as QuoteText, ComposerQuoteTextProps as QuoteTextProps, ComposerRoot as Root, ComposerRootProps as RootProps, ComposerSend as Send, ComposerSendProps as SendProps }; +} + +declare const createFileStorageAdapter: (options: CreateFileStorageAdapterOptions) => RemoteThreadListAdapter; + +declare const createSimpleTitleAdapter: () => TitleGenerationAdapter; + +declare function createVoiceSession(options: { + abortSignal?: AbortSignal; +}, setup: (helpers: VoiceSessionHelpers) => Promise<VoiceSessionControls>): RealtimeVoiceAdapter.Session; + +declare function defineMcpToolkit(definition: McpToolkitDefinition): Toolkit; + +declare const defineToolkit: typeof defineToolkit$1; + +declare function defineToolkit$1<TArgsByName extends { + [K in keyof TArgsByName]: Record<string, unknown>; +}, TResultByName extends { + [K in keyof TArgsByName]: unknown; +} = { + [K in keyof TArgsByName]: any; +}>(_definition: { + [K in keyof TArgsByName]: ToolkitDefinitionEntryWithParameters<TArgsByName[K], TResultByName[K]>; +}): Toolkit & { + [K in keyof TArgsByName]: ToolkitDefinitionEntryWithParameters<TArgsByName[K], TResultByName[K]>; +}; + +declare function defineToolkit$1<const TDefinition extends ToolkitDefinition>(_definition: TDefinition): Toolkit & TDefinition; + +declare namespace diff_d_exports { + export { DiffContent as Content, DiffContentProps as ContentProps, DiffFileInput, DiffLineType, DisplayLine, FoldedRegion, DiffHeader as Header, DiffHeaderProps as HeaderProps, DiffLine as Line, DiffLineProps as LineProps, ParsedFile, ParsedLine, DiffRoot as Root, DiffRootProps as RootProps, DiffStats as Stats, DiffStatsProps as StatsProps }; +} + +declare const fromThreadMessageLike: (like: ThreadMessageLike, fallbackId: string, fallbackStatus: MessageStatus) => ThreadMessage; + +declare const generateId: (size?: number) => string; + +declare const getAutoStatus: (isLast: boolean, isRunning: boolean, hasInterruptedToolCalls: boolean, hasPendingToolCalls: boolean, error?: ReadonlyJSONValue) => MessageStatus; + +declare global { + interface Window { + SpeechRecognition?: SpeechRecognitionConstructor; + webkitSpeechRecognition?: SpeechRecognitionConstructor; + } +} + +declare const hitl: typeof humanTool; + +declare const hitlTool: typeof humanTool; + +declare function humanTool(): never; + +declare namespace entry_root_exports { + export { actionBar_d_exports as ActionBarPrimitive, AppendMessage, AssistantClient, AssistantContextConfig, AssistantDataUI, AssistantDataUIProps, AssistantEventCallback, AssistantEventName, AssistantEventPayload, AssistantEventScope, AssistantEventSelector, AssistantInteractableProps, AssistantRuntime, AssistantRuntimeProvider, AssistantState, AssistantTool, AssistantToolProps, AssistantToolUI, AssistantToolUIProps, Attachment, AttachmentAdapter, attachment_d_exports as AttachmentPrimitive, AttachmentRuntime, AttachmentState, AuiIf, AuiProvider, branchPicker_d_exports as BranchPickerPrimitive, ChainOfThoughtByIndicesProvider, ChainOfThoughtClient, ChainOfThoughtPartByIndexProvider, chainOfThought_d_exports as ChainOfThoughtPrimitive, ChatModelAdapter, ChatModelRunOptions, ChatModelRunResult, ChecklistItemData, ChecklistItemStatus, checklist_d_exports as ChecklistPrimitive, CompleteAttachment, ComposerAttachmentByIndexProvider, composer_d_exports as ComposerPrimitive, ComposerRuntime, ComposerState, CompositeAttachmentAdapter, CreateAttachment, CreateFileStorageAdapterOptions, DataMessagePart, DataMessagePartComponent, DataMessagePartProps, DataRenderers, diff_d_exports as DiffPrimitive, DiffView, DiffViewProps, EditComposerRuntime, EmptyMessagePartComponent, EmptyMessagePartProps, index_d_exports$2 as ErrorPrimitive, FeedbackAdapter, FileMessagePart, FileMessagePartComponent, FileMessagePartProps, ImageMessagePart, ImageMessagePartComponent, ImageMessagePartProps, InMemoryThreadListAdapter, Interactables, LanguageModelConfig, LanguageModelV1CallSettings, LiveChecklist, LiveChecklistProps, index_d_exports$1 as LoadingPrimitive, LocalRuntimeOptions, McpToolkitDefinition, McpToolkitEntry, McpToolkitToolConfig, MessageAttachmentByIndexProvider, MessageByIndexProvider, messagePart_d_exports as MessagePartPrimitive, message_d_exports as MessagePrimitive, MessageRole, MessageRuntime, MessageState, MessageStatus, ModelContext$1 as ModelContext, ModelContext as ModelContextClient, ModelContextProvider, ModelContextRegistry, ModelContextRegistryInstructionHandle, ModelContextRegistryProviderHandle, ModelContextRegistryToolHandle, NotificationConfig, NotificationEvent, NotificationHandler, OSCVariant, PartByIndexProvider, PendingAttachment, ProviderToolConfig, queueItem_d_exports as QueueItemPrimitive, QueueItemState, RealtimeVoiceAdapter, ReasoningGroupComponent, ReasoningGroupProps, ReasoningMessagePart, ReasoningMessagePartComponent, ReasoningMessagePartProps, RemoteThreadListAdapter, RemoteThreadListOptions, RunConfig, RuntimeAdapterProvider, RuntimeAdapters, RuntimeCapabilities, SimpleImageAttachmentAdapter, SimpleTextAttachmentAdapter, SourceMessagePart, SourceMessagePartComponent, SourceMessagePartProps, statusBar_d_exports as StatusBarPrimitive, SuggestionAdapter, SuggestionByIndexProvider, SuggestionConfig, suggestion_d_exports as SuggestionPrimitive, Suggestions, TextInput, TextInputProps, TextMessagePart, TextMessagePartComponent, TextMessagePartProps, TextMessagePartProvider, ThreadAssistantMessage, ThreadAssistantMessagePart, ThreadComposerRuntime, ThreadHistoryAdapter, ThreadListItemByIndexProvider, threadListItem_d_exports as ThreadListItemPrimitive, ThreadListItemRuntime, ThreadListItemRuntimeProvider, ThreadListItemState, threadList_d_exports as ThreadListPrimitive, ThreadListRuntime, ThreadMessage, ThreadMessageLike, thread_d_exports as ThreadPrimitive, ThreadRuntime, ThreadState$1 as ThreadState, ThreadSystemMessage, ThreadUserMessage, ThreadUserMessagePart, ThreadsState, TitleGenerationAdapter, Tool, ToolArgsStatus, ToolCallMessagePart, ToolCallMessagePartComponent, ToolCallMessagePartProps, toolCall_d_exports as ToolCallPrimitive, ToolCallText, ToolDefinition, ToolModelContentPart, Toolkit, ToolkitDefinition, ToolkitDefinitionEntry, Tools, Unstable_AudioMessagePart, Unstable_AudioMessagePartComponent, Unstable_AudioMessagePartProps, Unstable_InferInteractableState, Unstable_InteractableConfig, Unstable_InteractableDefinition, Unstable_InteractablePersistedState, Unstable_InteractablePersistenceAdapter, Unstable_InteractablePersistenceStatus, Unstable_InteractableRegistration, Unstable_InteractableSnapshotEntry, Unstable_InteractableStateSchema, Unstable_InteractableToolConfig, Unstable_InteractableToolRenderProps, Unstable_InteractableVersion, Unstable_InteractableVersionInfo, Unstable_InteractablesClientSchema, Unstable_InteractablesConfig, Unstable_InteractablesMethods, Unstable_InteractablesState, Unsubscribe$1 as Unsubscribe, UseToolCallChecklistOptions, VoiceSessionControls, VoiceSessionHelpers, createFileStorageAdapter, createSimpleTitleAdapter, createVoiceSession, defineMcpToolkit, defineToolkit, fromThreadMessageLike, generateId, hitl, hitlTool, humanTool, makeAssistantDataUI, makeAssistantTool, makeAssistantToolUI, mergeModelContexts, providerTool, ringBell, sendOSCNotification, stubTool, tool, unstable_Interactables, unstable_formatInteractableSnapshot, unstable_getInteractableSnapshots, unstable_getInteractableVersions, unstable_interactableTool, unstable_useInteractable, unstable_useInteractableState, unstable_useInteractableVersions, unstable_useThreadMessageIds, useAssistantContext, useAssistantDataUI, useAssistantInstructions, useAssistantInteractable, useAssistantTool, useAssistantToolUI, useAui, useAuiEvent, useAuiState, useAuiToolOverrides, useInlineRender, useInteractableState, useLocalRuntime, useNotification, useRemoteThreadListRuntime, useRuntimeAdapters, useToolArgsStatus, useToolCallChecklist, useVoiceControls, useVoiceState, useVoiceVolume }; +} + +declare namespace index_d_exports$1 { + export { LoadingElapsedTime as ElapsedTime, LoadingElapsedTimeProps as ElapsedTimeProps, LoadingRoot as Root, LoadingRootProps as RootProps, LoadingSpinner as Spinner, LoadingSpinnerProps as SpinnerProps, LoadingText as Text, LoadingTextProps as TextProps }; +} + +declare namespace index_d_exports$2 { + export { ErrorMessage as Message, ErrorMessageProps as MessageProps, ErrorRoot as Root, ErrorRootProps as RootProps }; +} + +declare namespace entry_internal_exports { + export { AssistantRuntimeImpl, BaseAssistantRuntimeCore, CompositeContextProvider, DefaultThreadComposerRuntimeCore, MessageRepository, ThreadListItemRuntimeBinding, ThreadListRuntimeCore, ThreadRuntimeCore, ThreadRuntimeCoreBinding, ThreadRuntimeImpl, getAutoStatus }; +} + +declare const makeAssistantDataUI: <T = any>(dataUI: AssistantDataUIProps<T>) => AssistantDataUI; + +declare const makeAssistantTool: <TArgs extends Record<string, unknown>, TResult>(tool: AssistantToolProps<TArgs, TResult>) => AssistantTool; + +declare const makeAssistantToolUI: <TArgs, TResult>(tool: AssistantToolUIProps<TArgs, TResult>) => AssistantToolUI; + +declare const mergeModelContexts: (configSet: Set<ModelContextProvider>) => ModelContext$1; + +declare namespace messagePart_d_exports { + export { MessagePartPrimitiveData as Data, MessagePartPrimitiveFile as File, MessagePartPrimitiveImage as Image, MessagePartPrimitiveInProgress as InProgress, PartPrimitiveMessages as Messages, MessagePartPrimitiveReasoning as Reasoning, MessagePartPrimitiveSource as Source, MessagePartPrimitiveText as Text }; +} + +declare namespace message_d_exports { + export { MessageAttachmentByIndex as AttachmentByIndex, MessageAttachments as Attachments, MessageContent as Content, MessageContentProps as ContentProps, MessageError as Error, MessageIf as If, MessageIfProps as IfProps, MessagePrimitivePartByIndex as PartByIndex, MessagePrimitiveParts as Parts, MessageRoot as Root, MessageRootProps as RootProps }; +} + +declare function providerTool(config: ProviderToolConfig): never; + +declare namespace queueItem_d_exports { + export { QueueItemRemove as Remove, QueueItemRemoveProps as RemoveProps, QueueItemSteer as Steer, QueueItemSteerProps as SteerProps, QueueItemText as Text, QueueItemTextProps as TextProps }; +} + +declare const ringBell: () => void; + +declare const sendOSCNotification: (title: string, body?: string, variant?: OSCVariant) => void; + +declare namespace statusBar_d_exports { + export { StatusBarPrimitiveLatency as Latency, StatusBarPrimitiveMessageCount as MessageCount, StatusBarPrimitiveModelName as ModelName, StatusBarPrimitiveRoot as Root, StatusBarPrimitiveStatus as Status, StatusType, StatusBarPrimitiveTokenCount as TokenCount }; +} + +declare function stubTool(): never; + +declare namespace suggestion_d_exports { + export { SuggestionDescription as Description, SuggestionDescriptionProps as DescriptionProps, SuggestionTitle as Title, SuggestionTitleProps as TitleProps, SuggestionTrigger as Trigger, SuggestionTriggerProps as TriggerProps }; +} + +declare namespace threadListItem_d_exports { + export { ThreadListItemArchive as Archive, ThreadListItemArchiveProps as ArchiveProps, ThreadListItemDelete as Delete, ThreadListItemDeleteProps as DeleteProps, ThreadListItemRoot as Root, ThreadListItemRootProps as RootProps, ThreadListItemPrimitiveTitle as Title, ThreadListItemTrigger as Trigger, ThreadListItemTriggerProps as TriggerProps, ThreadListItemUnarchive as Unarchive, ThreadListItemUnarchiveProps as UnarchiveProps }; +} + +declare namespace threadList_d_exports { + export { ThreadListItems as Items, ThreadListItemsProps as ItemsProps, ThreadListNew as New, ThreadListNewProps as NewProps, ThreadListRoot as Root, ThreadListRootProps as RootProps }; +} + +declare namespace thread_d_exports { + export { ThreadEmpty as Empty, ThreadEmptyProps as EmptyProps, ThreadIf as If, ThreadIfProps as IfProps, ThreadPrimitiveMessageByIndex as MessageByIndex, ThreadMessages as Messages, ThreadMessagesProps as MessagesProps, ThreadRoot as Root, ThreadRootProps as RootProps, ThreadSuggestion as Suggestion, ThreadPrimitiveSuggestionByIndex as SuggestionByIndex, ThreadSuggestionProps as SuggestionProps, ThreadPrimitiveSuggestions as Suggestions, ThreadPrimitiveUnstable_MessageById as Unstable_MessageById }; +} + +declare function tool<TArgs extends Record<string, unknown>, TResult = any>(tool: Tool<TArgs, TResult>): Tool<TArgs, TResult>; + +declare namespace toolCall_d_exports { + export { ToolFallback as Fallback, ToolFallbackProps as FallbackProps, ToolCallStatus }; +} + +declare const unstable_Interactables: Resource<ClientOutput<"unstable_interactables">, [ + (Unstable_InteractablesConfig | undefined)? +]>; + +declare function unstable_formatInteractableSnapshot(entry: Unstable_InteractableSnapshotEntry): string; + +declare function unstable_getInteractableSnapshots(message: { + metadata?: unknown; +}): Unstable_InteractableSnapshotEntry[] | undefined; + +declare function unstable_getInteractableVersions(messages: readonly SnapshotCarrierMessage[], id: string, name: string): Unstable_InteractableVersion[]; + +declare const unstable_interactableTool: <TSchema extends Unstable_InteractableStateSchema>(config: Unstable_InteractableToolConfig<TSchema>) => ToolDefinition<Record<string, unknown>, { + success: true; +}>; + +declare const unstable_useInteractable: <TSchema extends Unstable_InteractableStateSchema>(name: string, config: Unstable_InteractableConfig<TSchema>) => readonly [ + Unstable_InferInteractableState<TSchema>, + { + id: string; + version: Unstable_InteractableVersionInfo<Unstable_InferInteractableState<TSchema>> | undefined; + setState: (updater: Unstable_InferInteractableState<TSchema> | ((prev: Unstable_InferInteractableState<TSchema>) => Unstable_InferInteractableState<TSchema>)) => void; + isPending: boolean; + error: unknown; + flush: () => Promise<void>; + } +]; + +declare const unstable_useInteractableState: <TState>(id: string) => [ + TState | undefined, + { + setState: (updater: StateUpdater<TState>) => void; + isPending: boolean; + error: unknown; + flush: () => Promise<void>; + } +]; + +declare const unstable_useInteractableVersions: <TState = unknown>(id: string, name: string) => (Omit<Unstable_InteractableVersion, "state"> & { + state: TState; + restore: () => void; +})[]; + +declare const unstable_useThreadMessageIds: () => readonly string[]; + +declare const useAssistantContext: (config: AssistantContextConfig) => void; + +declare const useAssistantDataUI: (dataUI: AssistantDataUIProps | null) => void; + +declare const useAssistantInstructions: (config: string | AssistantInstructionsConfig) => void; + +declare const useAssistantInteractable: (name: string, config: AssistantInteractableProps) => string; + +declare const useAssistantTool: <TArgs extends Record<string, unknown>, TResult>(tool: AssistantToolProps<TArgs, TResult>) => void; + +declare const useAssistantToolUI: (tool: AssistantToolUIProps<any, any> | null) => void; + +declare namespace useAui { + type Props = { + [K in ClientNames]?: ClientElement<K> | DerivedElement<K>; + }; +} + +declare function useAui(): AssistantClient; + +declare function useAui(clients: useAui.Props): AssistantClient; + +declare function useAui(clients: useAui.Props, config: { + parent: null | AssistantClient; +}): AssistantClient; + +declare const useAuiEvent: <TEvent extends AssistantEventName>(selector: AssistantEventSelector<TEvent>, callback: AssistantEventCallback<TEvent>) => void; + +declare const useAuiState: <T>(selector: (state: AssistantState) => T) => T; + +declare function useAuiToolOverrides(overrides: AuiToolOverrides): void; + +declare const useInlineRender: <TArgs, TResult>(toolUI: FC<ToolCallMessagePartProps<TArgs, TResult>>) => FC<ToolCallMessagePartProps<TArgs, TResult>>; + +declare const useInteractableState: <TState>(id: string, fallback: TState) => [ + TState, + { + setState: (updater: StateUpdater$1<TState>) => void; + setSelected: (selected: boolean) => void; + isPending: boolean; + error: unknown; + flush: () => Promise<void>; + } +]; + +declare const useLocalRuntime: (chatModel: ChatModelAdapter, _param71?: LocalRuntimeOptions) => AssistantRuntime; + +declare const useNotification: (config?: NotificationConfig) => void; + +declare const useRemoteThreadListRuntime: (options: RemoteThreadListOptions) => AssistantRuntime; + +declare const useRuntimeAdapters: () => RuntimeAdapters | null; + +declare const useToolArgsStatus: <TArgs extends Record<string, unknown> = Record<string, unknown>>() => ToolArgsStatus<TArgs>; + +declare const useToolCallChecklist: (options?: UseToolCallChecklistOptions) => ChecklistItemData[]; + +declare const useVoiceControls: () => { + connect: () => void; + disconnect: () => void; + mute: () => void; + unmute: () => void; +}; + +declare const useVoiceState: () => VoiceSessionState | undefined; + +declare const useVoiceVolume: () => number; + +export { entry_internal_exports as entry_internal, entry_root_exports as entry_root }; diff --git a/api-surface/assistant-ui__react-langchain.ts b/api-surface/assistant-ui__react-langchain.ts new file mode 100644 index 0000000..6e20403 --- /dev/null +++ b/api-surface/assistant-ui__react-langchain.ts @@ -0,0 +1,2028 @@ +import { AssembledToolCall, SubagentDiscoverySnapshot, SubagentDiscoverySnapshot as SubagentDiscoverySnapshot$1, SubgraphDiscoverySnapshot, SubgraphDiscoverySnapshot as SubgraphDiscoverySnapshot$1, UseStreamOptions } from "@langchain/react"; + +import { StandardSchemaV1 } from "@standard-schema/spec"; + +import { ComponentType, PropsWithChildren } from "react"; + +type AddToolResultOptions = { + messageId: string; + toolName: string; + toolCallId: string; + result: ReadonlyJSONValue; + isError: boolean; + artifact?: ReadonlyJSONValue | undefined; + modelContent?: readonly ToolModelContentPart[] | undefined; +}; + +type AppendMessage = Omit<ThreadMessage, "id"> & { + parentId: string | null; + sourceId: string | null; + runConfig: RunConfig | undefined; + startRun?: boolean | undefined; + steer?: boolean | undefined; +}; + +type AsNumber<K> = K extends `${infer N extends number}` ? N | K : never; + +declare class AssistantCloud { + readonly threads: AssistantCloudThreads; + readonly auth: { + tokens: AssistantCloudAuthTokens; + }; + readonly runs: AssistantCloudRuns; + readonly files: AssistantCloudFiles; + readonly telemetry: AssistantCloudTelemetryConfig; + constructor(config: AssistantCloudConfig); +} + +declare class AssistantCloudAPI { + _auth: AssistantCloudAuthStrategy; + _baseUrl: string; + constructor(config: AssistantCloudConfig); + initializeAuth(): Promise<boolean>; + makeRawRequest(endpoint: string, options?: MakeRequestOptions): Promise<Response>; + makeRequest(endpoint: string, options?: MakeRequestOptions): Promise<any>; +} + +type AssistantCloudAuthStrategy = { + readonly strategy: "anon" | "api-key" | "jwt"; + getAuthHeaders(): Promise<Record<string, string> | false>; + readAuthHeaders(headers: Headers): void; +}; + +declare class AssistantCloudAuthTokens { + private cloud; + constructor(cloud: AssistantCloudAPI); + create(): Promise<AssistantCloudAuthTokensCreateResponse>; +} + +type AssistantCloudAuthTokensCreateResponse = { + token: string; +}; + +type AssistantCloudConfig = ({ + baseUrl: string; + authToken: () => Promise<string | null>; +} | { + baseUrl?: string; + apiKey: string; + userId: string; + workspaceId: string; +} | { + baseUrl: string; + anonymous: true; +}) & { + telemetry?: boolean | AssistantCloudTelemetryConfig; +}; + +declare class AssistantCloudFiles { + private cloud; + constructor(cloud: AssistantCloudAPI); + pdfToImages(body: PdfToImagesRequestBody): Promise<PdfToImagesResponse>; + generatePresignedUploadUrl(body: GeneratePresignedUploadUrlRequestBody): Promise<GeneratePresignedUploadUrlResponse>; +} + +type AssistantCloudMessageCreateResponse = { + message_id: string; +}; + +type AssistantCloudRunReport = { + thread_id: string; + status: "completed" | "error" | "incomplete"; + total_steps?: number; + tool_calls?: ReportToolCall[]; + steps?: { + input_tokens?: number; + output_tokens?: number; + reasoning_tokens?: number; + cached_input_tokens?: number; + tool_calls?: ReportToolCall[]; + start_ms?: number; + end_ms?: number; + }[]; + input_tokens?: number; + output_tokens?: number; + reasoning_tokens?: number; + cached_input_tokens?: number; + model_id?: string; + provider_type?: string; + duration_ms?: number; + output_text?: string; + metadata?: Record<string, unknown>; +}; + +declare class AssistantCloudRuns { + private cloud; + constructor(cloud: AssistantCloudAPI); + __internal_getAssistantOptions(assistantId: string): { + api: string; + headers: () => Promise<{ + Accept: string; + }>; + body: { + assistant_id: string; + response_format: string; + thread_id: string; + }; + }; + stream(body: AssistantCloudRunsStreamBody): Promise<AssistantStream>; + report(body: AssistantCloudRunReport): Promise<{ + run_id: string; + }>; +} + +type AssistantCloudRunsStreamBody = { + thread_id: string; + assistant_id: "system/thread_title"; + messages: readonly unknown[]; +}; + +type AssistantCloudTelemetryConfig = { + enabled?: boolean; + beforeReport?: (report: AssistantCloudRunReport) => AssistantCloudRunReport | null; +}; + +type AssistantCloudThreadMessageCreateBody = { + parent_id: string | null; + format: "aui/v0" | string; + content: ReadonlyJSONObject; +}; + +type AssistantCloudThreadMessageListQuery = { + format?: string; +}; + +type AssistantCloudThreadMessageListResponse = { + messages: CloudMessage[]; +}; + +type AssistantCloudThreadMessageUpdateBody = { + content: ReadonlyJSONObject; +}; + +declare class AssistantCloudThreadMessages { + private cloud; + constructor(cloud: AssistantCloudAPI); + list(threadId: string, query?: AssistantCloudThreadMessageListQuery): Promise<AssistantCloudThreadMessageListResponse>; + create(threadId: string, body: AssistantCloudThreadMessageCreateBody): Promise<AssistantCloudMessageCreateResponse>; + update(threadId: string, messageId: string, body: AssistantCloudThreadMessageUpdateBody): Promise<void>; +} + +declare class AssistantCloudThreads { + private cloud; + readonly messages: AssistantCloudThreadMessages; + constructor(cloud: AssistantCloudAPI); + list(query?: AssistantCloudThreadsListQuery): Promise<AssistantCloudThreadsListResponse>; + get(threadId: string): Promise<CloudThread>; + create(body: AssistantCloudThreadsCreateBody): Promise<AssistantCloudThreadsCreateResponse>; + update(threadId: string, body: AssistantCloudThreadsUpdateBody): Promise<void>; + delete(threadId: string): Promise<void>; +} + +type AssistantCloudThreadsCreateBody = { + title?: string | undefined; + last_message_at: Date; + metadata?: unknown | undefined; + external_id?: string | undefined; +}; + +type AssistantCloudThreadsCreateResponse = { + thread_id: string; +}; + +type AssistantCloudThreadsListQuery = { + is_archived?: boolean; + limit?: number; + after?: string; +}; + +type AssistantCloudThreadsListResponse = { + threads: CloudThread[]; +}; + +type AssistantCloudThreadsUpdateBody = { + title?: string | undefined; + last_message_at?: Date | undefined; + metadata?: unknown | undefined; + is_archived?: boolean | undefined; +}; + +type AssistantRuntime = { + readonly threads: ThreadListRuntime; + readonly thread: ThreadRuntime; + registerModelContextProvider(provider: ModelContextProvider): Unsubscribe; +}; + +type AssistantStream = ReadableStream<AssistantStreamChunk>; + +declare const AssistantStream: { + toResponse(stream: AssistantStream, transformer: AssistantStreamEncoder): Response; + fromResponse(response: Response, transformer: ReadableWritablePair<AssistantStreamChunk, Uint8Array<ArrayBuffer>>): ReadableStream<AssistantStreamChunk>; + toByteStream(stream: AssistantStream, transformer: ReadableWritablePair<Uint8Array<ArrayBuffer>, AssistantStreamChunk>): ReadableStream<Uint8Array<ArrayBuffer>>; + fromByteStream(readable: ReadableStream<Uint8Array<ArrayBuffer>>, transformer: ReadableWritablePair<AssistantStreamChunk, Uint8Array<ArrayBuffer>>): ReadableStream<AssistantStreamChunk>; +}; + +type AssistantStreamChunk = { + readonly path: readonly number[]; +} & ({ + readonly type: "part-start"; + readonly part: PartInit; +} | { + readonly type: "part-finish"; +} | { + readonly type: "tool-call-args-text-finish"; +} | { + readonly type: "text-delta"; + readonly textDelta: string; +} | { + readonly type: "annotations"; + readonly annotations: ReadonlyJSONValue[]; +} | { + readonly type: "data"; + readonly data: ReadonlyJSONValue[]; +} | { + readonly type: "step-start"; + readonly messageId: string; +} | { + readonly type: "step-finish"; + readonly finishReason: "content-filter" | "error" | "length" | "other" | "stop" | "tool-calls" | "unknown"; + readonly usage: { + readonly inputTokens: number; + readonly outputTokens: number; + }; + readonly isContinued: boolean; +} | { + readonly type: "message-finish"; + readonly finishReason: "content-filter" | "error" | "length" | "other" | "stop" | "tool-calls" | "unknown"; + readonly usage: { + readonly inputTokens: number; + readonly outputTokens: number; + }; +} | { + readonly type: "result"; + readonly artifact?: ReadonlyJSONValue; + readonly result: ReadonlyJSONValue; + readonly isError: boolean; + readonly modelContent?: readonly ToolModelContentPart[]; + readonly messages?: ReadonlyJSONValue; +} | { + readonly type: "error"; + readonly error: string; +} | { + readonly type: "update-state"; + readonly operations: ObjectStreamOperation[]; +}); + +type AssistantStreamEncoder = ReadableWritablePair<Uint8Array<ArrayBuffer>, AssistantStreamChunk> & { + headers?: Headers; +}; + +type AsyncIterableStream<T> = AsyncIterable<T> & ReadableStream<T>; + +type Attachment = PendingAttachment | CompleteAttachment; + +type AttachmentAdapter = { + accept: string; + add(state: { + file: File; + }): Promise<PendingAttachment> | AsyncGenerator<PendingAttachment, void>; + remove(attachment: Attachment): Promise<void>; + send(attachment: PendingAttachment): Promise<CompleteAttachment>; +}; + +type AttachmentAddErrorEvent = { + readonly reason: AttachmentAddErrorReason; + readonly message: string; + readonly attachmentId?: string; + readonly error?: Error; +}; + +type AttachmentAddErrorReason = "adapter-error" | "no-adapter" | "not-accepted"; + +type AttachmentRuntime<TSource extends AttachmentRuntimeSource = AttachmentRuntimeSource> = { + readonly path: AttachmentRuntimePath & { + attachmentSource: TSource; + }; + readonly source: TSource; + getState(): AttachmentState & { + source: TSource; + }; + remove(): Promise<void>; + subscribe(callback: () => void): Unsubscribe; +}; + +type AttachmentRuntimePath = ((MessageRuntimePath & { + readonly attachmentSource: "edit-composer" | "message"; +}) | (ThreadRuntimePath & { + readonly attachmentSource: "thread-composer"; +})) & { + readonly attachmentSelector: { + readonly type: "index"; + readonly index: number; + } | { + readonly type: "index"; + readonly index: number; + } | { + readonly type: "index"; + readonly index: number; + }; +}; + +type AttachmentRuntimeSource = AttachmentState["source"]; + +type AttachmentState = ThreadComposerAttachmentState | EditComposerAttachmentState | MessageAttachmentState; + +type BackendTool<TArgs extends Record<string, unknown> = Record<string, unknown>, TResult = unknown> = ToolBase<TArgs, TResult> & { + type: "backend"; + description?: undefined; + parameters?: undefined; + disabled?: undefined; + execute?: undefined; + toModelOutput?: undefined; + experimental_onSchemaValidationError?: undefined; + providerOptions?: undefined; +}; + +type BaseAttachment = { + id: string; + type: "image" | "document" | "file" | (string & {}); + name: string; + contentType?: string | undefined; + file?: File; + content?: ThreadUserMessagePart[]; +}; + +type BaseComposerState = { + readonly canCancel: boolean; + readonly canSend: boolean; + readonly isEditing: boolean; + readonly isEmpty: boolean; + readonly text: string; + readonly role: MessageRole; + readonly attachments: readonly Attachment[]; + readonly runConfig: RunConfig; + readonly attachmentAccept: string; + readonly dictation: DictationState | undefined; + readonly quote: QuoteInfo | undefined; + readonly queue: readonly QueueItemState[]; +}; + +type BaseThreadMessage = { + readonly status?: ThreadAssistantMessage["status"]; + readonly metadata: { + readonly unstable_state?: ReadonlyJSONValue; + readonly unstable_annotations?: readonly ReadonlyJSONValue[]; + readonly unstable_data?: readonly ReadonlyJSONValue[]; + readonly steps?: readonly ThreadStep[]; + readonly submittedFeedback?: { + readonly type: "negative" | "positive"; + }; + readonly timing?: MessageTiming; + readonly isOptimistic?: boolean; + readonly custom: Record<string, unknown>; + }; + readonly attachments?: ThreadUserMessage["attachments"]; +}; + +type ChatModelRunOptions = { + readonly messages: readonly ThreadMessage[]; + readonly runConfig: RunConfig; + readonly abortSignal: AbortSignal; + readonly context: ModelContext; + readonly unstable_assistantMessageId?: string | undefined; + readonly unstable_threadId?: string | undefined; + readonly unstable_parentId?: string | null | undefined; + unstable_getMessage(): ThreadMessage; +}; + +type ChatModelRunResult = { + readonly content?: readonly ThreadAssistantMessagePart[] | undefined; + readonly status?: MessageStatus | undefined; + readonly metadata?: { + readonly unstable_state?: ReadonlyJSONValue; + readonly unstable_annotations?: readonly ReadonlyJSONValue[] | undefined; + readonly unstable_data?: readonly ReadonlyJSONValue[] | undefined; + readonly steps?: readonly ThreadStep[] | undefined; + readonly timing?: MessageTiming | undefined; + readonly custom?: Record<string, unknown> | undefined; + }; +}; + +type CloudMessage = { + id: string; + parent_id: string | null; + height: number; + created_at: Date; + updated_at: Date; + format: "aui/v0" | string; + content: ReadonlyJSONObject; +}; + +type CloudThread = { + title: string; + last_message_at: Date; + metadata: unknown; + external_id: string | null; + id: string; + project_id: string; + created_at: Date; + updated_at: Date; + workspace_id: string; + is_archived: boolean; +}; + +type CompleteAttachment = BaseAttachment & { + status: CompleteAttachmentStatus; + content: ThreadUserMessagePart[]; +}; + +type CompleteAttachmentStatus = { + type: "complete"; +}; + +type ComposerRuntime = { + readonly path: ComposerRuntimePath; + readonly type: "edit" | "thread"; + getState(): ComposerState; + addAttachment(fileOrAttachment: File | CreateAttachment): Promise<void>; + setText(text: string): void; + setRole(role: MessageRole): void; + setRunConfig(runConfig: RunConfig): void; + reset(): Promise<void>; + clearAttachments(): Promise<void>; + send(options?: SendOptions): void; + cancel(): void; + steerQueueItem(queueItemId: string): void; + removeQueueItem(queueItemId: string): void; + subscribe(callback: () => void): Unsubscribe; + getAttachmentByIndex(idx: number): AttachmentRuntime; + startDictation(): void; + stopDictation(): void; + setQuote(quote: QuoteInfo | undefined): void; + unstable_on<E extends ComposerRuntimeEventType>(event: E, callback: ComposerRuntimeEventCallback<E>): Unsubscribe; +}; + +type ComposerRuntimeEventCallback<E extends ComposerRuntimeEventType> = (payload: ComposerRuntimeEventPayload[E]) => void; + +type ComposerRuntimeEventPayload = { + send: Record<string, never>; + attachmentAdd: Record<string, never>; + attachmentAddError: AttachmentAddErrorEvent; +}; + +type ComposerRuntimeEventType = keyof ComposerRuntimeEventPayload; + +type ComposerRuntimePath = (ThreadRuntimePath & { + readonly composerSource: "thread"; +}) | (MessageRuntimePath & { + readonly composerSource: "edit"; +}); + +type ComposerState = ThreadComposerState | EditComposerState; + +type CreateAppendMessage = string | { + parentId?: string | null | undefined; + sourceId?: string | null | undefined; + role?: AppendMessage["role"] | undefined; + content: AppendMessage["content"]; + attachments?: AppendMessage["attachments"] | undefined; + metadata?: AppendMessage["metadata"] | undefined; + createdAt?: Date | undefined; + runConfig?: AppendMessage["runConfig"] | undefined; + startRun?: boolean | undefined; +}; + +type CreateAttachment = { + id?: string; + type?: "image" | "document" | "file" | (string & {}); + name: string; + contentType?: string; + content: ThreadUserMessagePart[]; +}; + +type CreateResumeRunConfig = CreateStartRunConfig & { + stream?: (options: ChatModelRunOptions) => AsyncGenerator<ChatModelRunResult, void, unknown>; +}; + +type CreateStartRunConfig = { + parentId: string | null; + sourceId?: string | null | undefined; + runConfig?: RunConfig | undefined; +}; + +type DataMessagePart<T = any> = { + readonly type: "data"; + readonly name: string; + readonly data: T; +}; + +type DataPrefixedPart = { + readonly type: `data-${string}`; + readonly data: any; +}; + +type DeepPartial<T> = T extends readonly any[] ? readonly DeepPartial<T[number]>[] : T extends { + [key: string]: any; +} ? { + readonly [K in keyof T]?: DeepPartial<T[K]>; +} : T; + +declare namespace DictationAdapter { + type Status = { + type: "running" | "starting"; + } | { + type: "ended"; + reason: "cancelled" | "error" | "stopped"; + }; + type Result = { + transcript: string; + isFinal?: boolean; + }; + type Session = { + status: Status; + stop: () => Promise<void>; + cancel: () => void; + onSpeechStart: (callback: () => void) => Unsubscribe; + onSpeechEnd: (callback: (result: Result) => void) => Unsubscribe; + onSpeech: (callback: (result: Result) => void) => Unsubscribe; + }; +} + +type DictationAdapter = { + listen: () => DictationAdapter.Session; + disableInputDuringDictation?: boolean; +}; + +type DictationState = { + readonly status: DictationAdapter.Status; + readonly transcript?: string; + readonly inputDisabled?: boolean; +}; + +type EditComposerAttachmentState = Attachment & { + readonly source: "edit-composer"; +}; + +type EditComposerRuntime = Omit<ComposerRuntime, "getAttachmentByIndex" | "getState"> & { + readonly path: ComposerRuntimePath & { + composerSource: "edit"; + }; + readonly type: "edit"; + getState(): EditComposerState; + beginEdit(): void; + getAttachmentByIndex(idx: number): AttachmentRuntime & { + source: "edit-composer"; + }; +}; + +type EditComposerState = BaseComposerState & { + readonly type: "edit"; + readonly parentId: string | null; + readonly sourceId: string | null; +}; + +type ExportedMessageRepository = { + headId?: string | null; + messages: Array<{ + message: ThreadMessage; + parentId: string | null; + runConfig?: RunConfig; + }>; +}; + +declare const ExportedMessageRepository: { + fromArray: (messages: readonly ThreadMessageLike[]) => ExportedMessageRepository; + fromBranchableArray: (items: readonly { + message: ThreadMessageLike; + parentId: string | null; + }[], options?: { + headId?: string | null; + }) => ExportedMessageRepository; +}; + +type ExternalStoreAdapter<T = ThreadMessage> = ExternalStoreAdapterBase<T> & (T extends ThreadMessage ? object : ExternalStoreMessageConverterAdapter<T>); + +type ExternalStoreAdapterBase<T> = { + isDisabled?: boolean | undefined; + isSendDisabled?: boolean | undefined; + isRunning?: boolean | undefined; + isLoading?: boolean | undefined; + messages?: readonly T[]; + messageRepository?: ExportedMessageRepository; + suggestions?: readonly ThreadSuggestion[] | undefined; + state?: ReadonlyJSONValue | undefined; + extras?: unknown; + setMessages?: ((messages: readonly T[]) => void) | undefined; + unstable_onBranchChange?: ((event: ExternalStoreBranchChange) => void) | undefined; + onImport?: ((messages: readonly ThreadMessage[]) => void) | undefined; + onExportExternalState?: (() => any) | undefined; + onLoadExternalState?: ((state: any) => void) | undefined; + onNew: (message: AppendMessage) => Promise<void>; + queue?: ExternalThreadQueueAdapter | undefined; + onEdit?: ((message: AppendMessage) => Promise<void>) | undefined; + onDelete?: ((messageId: string) => Promise<void> | void) | undefined; + onReload?: ((parentId: string | null, config: StartRunConfig) => Promise<void>) | undefined; + onResume?: ((config: ResumeRunConfig) => Promise<void>) | undefined; + onCancel?: (() => Promise<void>) | undefined; + onAddToolResult?: ((options: AddToolResultOptions) => Promise<void> | void) | undefined; + onResumeToolCall?: ((options: { + toolCallId: string; + payload: unknown; + }) => void) | undefined; + onRespondToToolApproval?: ((options: RespondToToolApprovalOptions) => Promise<void> | void) | undefined; + convertMessage?: ExternalStoreMessageConverter<T> | undefined; + adapters?: { + attachments?: AttachmentAdapter | undefined; + speech?: SpeechSynthesisAdapter | undefined; + dictation?: DictationAdapter | undefined; + voice?: RealtimeVoiceAdapter | undefined; + feedback?: FeedbackAdapter | undefined; + threadList?: ExternalStoreThreadListAdapter | undefined; + } | undefined; + unstable_capabilities?: { + copy?: boolean | undefined; + } | undefined; + unstable_enableToolInvocations?: boolean | undefined; + setToolStatuses?: ((statuses: Record<string, ToolExecutionStatus>) => void) | undefined; +}; + +type ExternalStoreBranchChange = { + headId: string | null; + visibleMessageIds: readonly string[]; +}; + +type ExternalStoreMessageConverter<T> = (message: T, idx: number) => ThreadMessageLike; + +type ExternalStoreMessageConverterAdapter<T> = { + convertMessage: ExternalStoreMessageConverter<T>; +}; + +type ExternalStoreSharedOptions = Pick<ExternalStoreAdapter, "isDisabled" | "isSendDisabled" | "suggestions" | "unstable_capabilities">; + +type ExternalStoreThreadData<TState extends "archived" | "regular"> = { + status: TState; + id: string; + remoteId?: string | undefined; + externalId?: string | undefined; + title?: string | undefined; + custom?: Record<string, unknown> | undefined; +}; + +type ExternalStoreThreadListAdapter = { + threadId?: string | undefined; + isLoading?: boolean | undefined; + threads?: readonly ExternalStoreThreadData<"regular">[] | undefined; + archivedThreads?: readonly ExternalStoreThreadData<"archived">[] | undefined; + onSwitchToNewThread?: (() => Promise<void> | void) | undefined; + onSwitchToThread?: ((threadId: string) => Promise<void> | void) | undefined; + onRename?: (threadId: string, newTitle: string) => (Promise<void> | void) | undefined; + onUpdateCustom?: ((threadId: string, custom: Record<string, unknown> | undefined) => Promise<void> | void) | undefined; + onArchive?: ((threadId: string) => Promise<void> | void) | undefined; + onUnarchive?: ((threadId: string) => Promise<void> | void) | undefined; + onDelete?: ((threadId: string) => Promise<void> | void) | undefined; +}; + +type ExternalThreadQueueAdapter = { + items: readonly QueueItemState[]; + enqueue: (message: AppendMessage, options: { + steer: boolean; + }) => void; + steer: (queueItemId: string) => void; + remove: (queueItemId: string) => void; + clear: (reason: "cancel-run" | "edit" | "reload") => void; +}; + +type FeedbackAdapter = { + submit: (feedback: FeedbackAdapterFeedback) => void; +}; + +type FeedbackAdapterFeedback = { + message: ThreadMessage; + type: "negative" | "positive"; +}; + +type FileMessagePart = { + readonly type: "file"; + readonly filename?: string; + readonly data: string; + readonly mimeType: string; + readonly parentId?: string; +}; + +type FrontendTool<TArgs extends Record<string, unknown> = Record<string, unknown>, TResult = unknown> = ToolBase<TArgs, TResult> & { + type: "frontend"; + description?: string | undefined; + parameters: StandardSchemaV1<TArgs> | JSONSchema7; + disabled?: boolean; + execute?: ToolExecuteFunction<TArgs, TResult>; + toModelOutput?: ToolModelOutputFunction<TArgs, TResult>; + experimental_onSchemaValidationError?: OnSchemaValidationErrorFunction<TResult>; + providerOptions?: ProviderOptions; +}; + +type GeneratePresignedUploadUrlRequestBody = { + filename: string; +}; + +type GeneratePresignedUploadUrlResponse = { + success: boolean; + signedUrl: string; + expiresAt: string; + publicUrl: string; +}; + +type GenerativeUIMessagePart = { + readonly type: "generative-ui"; + readonly spec: GenerativeUISpec; + readonly id?: string; + readonly parentId?: string; +}; + +type GenerativeUINode = string | { + readonly component: string; + readonly props?: Record<string, unknown>; + readonly children?: readonly GenerativeUINode[]; + readonly key?: string; +}; + +type GenerativeUISpec = { + readonly root: GenerativeUINode | readonly GenerativeUINode[]; +}; + +type HumanTool<TArgs extends Record<string, unknown> = Record<string, unknown>, TResult = unknown> = ToolBase<TArgs, TResult> & { + type: "human"; + description?: string | undefined; + parameters: StandardSchemaV1<TArgs> | JSONSchema7; + disabled?: boolean; + display?: "standalone"; + execute?: undefined; + toModelOutput?: undefined; + experimental_onSchemaValidationError?: undefined; + providerOptions?: ProviderOptions; +}; + +type ImageMessagePart = { + readonly type: "image"; + readonly image: string; + readonly filename?: string; +}; + +interface JSONSchema7 { + $id?: string | undefined; + $ref?: string | undefined; + $schema?: JSONSchema7Version | undefined; + $comment?: string | undefined; + $defs?: { + [key: string]: JSONSchema7Definition; + } | undefined; + type?: JSONSchema7TypeName | JSONSchema7TypeName[] | undefined; + enum?: JSONSchema7Type[] | undefined; + const?: JSONSchema7Type | undefined; + multipleOf?: number | undefined; + maximum?: number | undefined; + exclusiveMaximum?: number | undefined; + minimum?: number | undefined; + exclusiveMinimum?: number | undefined; + maxLength?: number | undefined; + minLength?: number | undefined; + pattern?: string | undefined; + items?: JSONSchema7Definition | JSONSchema7Definition[] | undefined; + additionalItems?: JSONSchema7Definition | undefined; + maxItems?: number | undefined; + minItems?: number | undefined; + uniqueItems?: boolean | undefined; + contains?: JSONSchema7Definition | undefined; + maxProperties?: number | undefined; + minProperties?: number | undefined; + required?: string[] | undefined; + properties?: { + [key: string]: JSONSchema7Definition; + } | undefined; + patternProperties?: { + [key: string]: JSONSchema7Definition; + } | undefined; + additionalProperties?: JSONSchema7Definition | undefined; + dependencies?: { + [key: string]: JSONSchema7Definition | string[]; + } | undefined; + propertyNames?: JSONSchema7Definition | undefined; + if?: JSONSchema7Definition | undefined; + then?: JSONSchema7Definition | undefined; + else?: JSONSchema7Definition | undefined; + allOf?: JSONSchema7Definition[] | undefined; + anyOf?: JSONSchema7Definition[] | undefined; + oneOf?: JSONSchema7Definition[] | undefined; + not?: JSONSchema7Definition | undefined; + format?: string | undefined; + contentMediaType?: string | undefined; + contentEncoding?: string | undefined; + definitions?: { + [key: string]: JSONSchema7Definition; + } | undefined; + title?: string | undefined; + description?: string | undefined; + default?: JSONSchema7Type | undefined; + readOnly?: boolean | undefined; + writeOnly?: boolean | undefined; + examples?: JSONSchema7Type | undefined; +} + +interface JSONSchema7Array extends Array<JSONSchema7Type> { +} + +type JSONSchema7Definition = JSONSchema7 | boolean; + +interface JSONSchema7Object { + [key: string]: JSONSchema7Type; +} + +type JSONSchema7Type = string | number | boolean | JSONSchema7Object | JSONSchema7Array | null; + +type JSONSchema7TypeName = "array" | "boolean" | "integer" | "null" | "number" | "object" | "string"; + +type JSONSchema7Version = string; + +type JoinStrategy = "concat-content" | "none"; + +type LangChainBaseMessage = { + _getType: () => string; + content: unknown; + id?: string | undefined; + name?: string | undefined; + additional_kwargs?: Record<string, unknown> | undefined; + tool_calls?: readonly LangChainToolCall[] | undefined; + tool_call_id?: string | undefined; + status?: MessageStatus | "success" | "error" | undefined; + artifact?: unknown; +}; + +type LangChainContentBlock = { + type: "text"; + text: string; +} | { + type: "text_delta"; + text: string; +} | { + type: "image_url"; + image_url: string | { + url?: string; + }; +} | { + type: "thinking"; + thinking: string; +} | { + type: "reasoning"; + summary?: Array<{ + type: "summary_text"; + text?: string; + }>; + reasoning?: string; +} | { + type: "file"; + data: string; + mime_type: string; + source_type?: "base64"; + metadata?: { + filename?: string; + }; +} | { + type: "file"; + url: string; + mime_type?: string; + source_type: "url"; + metadata?: { + filename?: string; + }; +} | { + type: "file"; + id: string; + mime_type?: string; + source_type: "id"; + metadata?: { + filename?: string; + }; +} | { + type: "input_json_delta" | "tool_use"; +}; + +type LangChainMessageConverterMetadata = useExternalMessageConverter.Metadata & { + uiMessagesByParent?: Map<string, UIMessage[]>; + messageTiming?: Record<string, MessageTiming>; +}; + +type LangChainRuntimeExtraOptions = ExternalStoreSharedOptions & { + onThreadIdChange?: ((threadId: string | undefined) => void) | undefined; + cloud?: AssistantCloud | undefined; + adapters?: { + attachments?: AttachmentAdapter | undefined; + speech?: SpeechSynthesisAdapter | undefined; + dictation?: DictationAdapter | undefined; + voice?: RealtimeVoiceAdapter | undefined; + feedback?: FeedbackAdapter | undefined; + } | undefined; + autoCancelPendingToolCalls?: boolean | undefined; + unstable_allowCancellation?: boolean | undefined; + unstable_threadListAdapter?: RemoteThreadListAdapter | undefined; + create?: (() => Promise<{ + externalId: string | undefined; + }>) | undefined; + delete?: ((threadId: string) => Promise<void>) | undefined; + uiStateKey?: string | undefined; +}; + +type LangChainToolCall = { + id: string; + name: string; + args: Record<string, unknown>; +}; + +type LanguageModelConfig = { + apiKey?: string; + baseUrl?: string; + modelName?: string; + reasoningEffort?: string; +}; + +type LanguageModelV1CallSettings = { + maxTokens?: number; + temperature?: number; + topP?: number; + presencePenalty?: number; + frequencyPenalty?: number; + seed?: number; + headers?: Record<string, string | undefined>; +}; + +type MakeRequestOptions = { + method?: "POST" | "PUT" | "DELETE" | undefined; + headers?: Record<string, string> | undefined; + query?: Record<string, string | number | boolean> | undefined; + body?: object | undefined; +}; + +type McpAppMetadata = { + readonly resourceUri: string; + readonly mimeType?: string; + readonly visibility?: readonly ("app" | "model")[]; +}; + +type McpServerConfig = { + type: "http" | "sse"; + url: string; + headers?: Record<string, string>; + redirect?: "error" | "follow"; + connectionTimeout?: number | undefined; +} | { + type: "stdio"; + command: string; + args?: readonly string[]; + env?: Record<string, string>; + cwd?: string; + connectionTimeout?: number | undefined; +}; + +type McpTool = ToolBase<Record<string, unknown>, unknown> & { + type: "mcp"; + server: McpServerConfig; + description?: undefined; + parameters?: undefined; + disabled?: boolean; + execute?: undefined; + toModelOutput?: undefined; + experimental_onSchemaValidationError?: undefined; + providerOptions?: undefined; +}; + +type MessageAttachmentState = CompleteAttachment & { + readonly source: "message"; +}; + +type MessageCommonProps = { + readonly id: string; + readonly createdAt: Date; +}; + +type MessagePartRuntime = { + addToolResult(result: any | ToolResponse<any>): void; + resumeToolCall(payload: unknown): void; + respondToToolApproval(response: ToolApprovalResponse): void; + readonly path: MessagePartRuntimePath; + getState(): MessagePartState; + subscribe(callback: () => void): Unsubscribe; +}; + +type MessagePartRuntimePath = MessageRuntimePath & { + readonly messagePartSelector: { + readonly type: "index"; + readonly index: number; + } | { + readonly type: "toolCallId"; + readonly toolCallId: string; + }; +}; + +type MessagePartState = (ThreadUserMessagePart | ThreadAssistantMessagePart) & { + readonly status: MessagePartStatus | ToolCallMessagePartStatus; +}; + +type MessagePartStatus = { + readonly type: "running"; +} | { + readonly type: "complete"; +} | { + readonly type: "incomplete"; + readonly reason: "cancelled" | "content-filter" | "error" | "length" | "other"; + readonly error?: unknown; +}; + +type MessageRole = ThreadMessage["role"]; + +type MessageRuntime = { + readonly path: MessageRuntimePath; + readonly composer: EditComposerRuntime; + getState(): MessageState; + delete(): void | Promise<void>; + reload(config?: ReloadConfig): void; + speak(): void; + stopSpeaking(): void; + submitFeedback(_param0: { + type: "positive" | "negative"; + }): void; + switchToBranch(_param1: { + position?: "previous" | "next" | undefined; + branchId?: string | undefined; + }): void; + unstable_getCopyText(): string; + subscribe(callback: () => void): Unsubscribe; + getMessagePartByIndex(idx: number): MessagePartRuntime; + getMessagePartByToolCallId(toolCallId: string): MessagePartRuntime; + getAttachmentByIndex(idx: number): AttachmentRuntime & { + source: "message"; + }; +}; + +type MessageRuntimePath = ThreadRuntimePath & { + readonly messageSelector: { + readonly type: "messageId"; + readonly messageId: string; + } | { + readonly type: "index"; + readonly index: number; + }; +}; + +type MessageState = ThreadMessage & { + readonly parentId: string | null; + readonly index: number; + readonly isLast: boolean; + readonly branchNumber: number; + readonly branchCount: number; + readonly speech: SpeechState | undefined; +}; + +type MessageStatus = { + readonly type: "running"; +} | { + readonly type: "requires-action"; + readonly reason: "interrupt" | "tool-calls"; +} | { + readonly type: "complete"; + readonly reason: "stop" | "unknown"; +} | { + readonly type: "incomplete"; + readonly reason: "cancelled" | "content-filter" | "error" | "length" | "other" | "tool-calls"; + readonly error?: ReadonlyJSONValue; +}; + +type MessageTiming = { + readonly streamStartTime: number; + readonly firstTokenTime?: number; + readonly totalStreamTime?: number; + readonly tokenCount?: number; + readonly tokensPerSecond?: number; + readonly totalChunks: number; + readonly toolCallCount: number; +}; + +type ModelContext = { + priority?: number | undefined; + system?: string | undefined; + tools?: Record<string, Tool<any, any>> | undefined; + callSettings?: LanguageModelV1CallSettings | undefined; + config?: LanguageModelConfig | undefined; + unstable_composerMetadata?: Record<string, unknown> | undefined; +}; + +type ModelContextProvider = { + getModelContext: () => ModelContext; + subscribe?: (callback: () => void) => Unsubscribe; +}; + +type ObjectKey<T> = keyof T & (string | number); + +type ObjectStreamOperation = { + readonly type: "set"; + readonly path: readonly string[]; + readonly value: ReadonlyJSONValue; +} | { + readonly type: "append-text"; + readonly path: readonly string[]; + readonly value: string; +}; + +type OnSchemaValidationErrorFunction<TResult> = ToolExecuteFunction<unknown, TResult>; + +type PartInit = { + readonly type: "reasoning" | "text"; + readonly parentId?: string; +} | { + readonly type: "tool-call"; + readonly toolCallId: string; + readonly toolName: string; + readonly parentId?: string; +} | { + readonly type: "source"; + readonly sourceType: "url"; + readonly id: string; + readonly url: string; + readonly title?: string; + readonly parentId?: string; +} | { + readonly type: "file"; + readonly data: string; + readonly mimeType: string; + readonly parentId?: string; +} | { + readonly type: "data"; + readonly name: string; + readonly data: ReadonlyJSONValue; + readonly parentId?: string; +}; + +type PartProviderMetadata = { + readonly [providerName: string]: ReadonlyJSONObject; +}; + +type PdfToImagesRequestBody = { + file_blob?: string | undefined; + file_url?: string | undefined; +}; + +type PdfToImagesResponse = { + success: boolean; + urls: string[]; + message: string; +}; + +type PendingAttachment = BaseAttachment & { + status: PendingAttachmentStatus; + file: File; +}; + +type PendingAttachmentStatus = { + type: "running"; + reason: "uploading"; + progress: number; +} | { + type: "requires-action"; + reason: "composer-send"; +} | { + type: "incomplete"; + reason: "error" | "upload-paused"; + message?: string; +}; + +type ProviderOptions = Record<string, Record<string, unknown>>; + +type ProviderTool<TArgs extends Record<string, unknown> = Record<string, unknown>, TResult = unknown> = ToolBase<TArgs, TResult> & { + type: "provider"; + providerId: `${string}.${string}`; + parameters?: StandardSchemaV1<TArgs> | JSONSchema7 | undefined; + args: Record<string, unknown>; + supportsDeferredResults?: boolean; + description?: undefined; + disabled?: boolean; + execute?: undefined; + toModelOutput?: undefined; + experimental_onSchemaValidationError?: undefined; + providerOptions?: ProviderOptions; +}; + +type QueueItemState = { + readonly id: string; + readonly prompt: string; +}; + +type QuoteInfo = { + readonly text: string; + readonly messageId: string; +}; + +type ReadonlyJSONArray = readonly ReadonlyJSONValue[]; + +type ReadonlyJSONObject = { + readonly [key: string]: ReadonlyJSONValue; +}; + +type ReadonlyJSONValue = null | string | number | boolean | ReadonlyJSONObject | ReadonlyJSONArray; + +declare namespace RealtimeVoiceAdapter { + type Status = { + type: "running" | "starting"; + } | { + type: "ended"; + reason: "cancelled" | "error" | "finished"; + error?: unknown; + }; + type Mode = "listening" | "speaking"; + type TranscriptItem = { + role: "assistant" | "user"; + text: string; + isFinal?: boolean; + }; + type Session = { + status: Status; + isMuted: boolean; + disconnect: () => void; + mute: () => void; + unmute: () => void; + onStatusChange: (callback: (status: Status) => void) => Unsubscribe; + onTranscript: (callback: (transcript: TranscriptItem) => void) => Unsubscribe; + onModeChange: (callback: (mode: Mode) => void) => Unsubscribe; + onVolumeChange: (callback: (volume: number) => void) => Unsubscribe; + }; +} + +type RealtimeVoiceAdapter = { + connect: (options: { + abortSignal?: AbortSignal; + }) => RealtimeVoiceAdapter.Session; +}; + +type ReasoningMessagePart = { + readonly type: "reasoning"; + readonly text: string; + readonly providerMetadata?: PartProviderMetadata; + readonly parentId?: string; +}; + +type ReloadConfig = { + runConfig?: RunConfig; +}; + +type RemoteThreadInitializeResponse = { + remoteId: string; + externalId: string | undefined; +}; + +type RemoteThreadListAdapter = { + list(params?: RemoteThreadListPageOptions): Promise<RemoteThreadListResponse>; + rename(remoteId: string, newTitle: string): Promise<void>; + updateCustom?(remoteId: string, custom: Record<string, unknown> | undefined): Promise<void>; + archive(remoteId: string): Promise<void>; + unarchive(remoteId: string): Promise<void>; + delete(remoteId: string): Promise<void>; + initialize(threadId: string): Promise<RemoteThreadInitializeResponse>; + generateTitle(remoteId: string, unstable_messages: readonly ThreadMessage[]): Promise<AssistantStream>; + fetch(threadId: string): Promise<RemoteThreadMetadata>; + unstable_Provider?: ComponentType<PropsWithChildren> | undefined; +}; + +type RemoteThreadListPageOptions = { + after?: string | undefined; +}; + +type RemoteThreadListResponse = { + threads: RemoteThreadMetadata[]; + nextCursor?: string | undefined; +}; + +type RemoteThreadMetadata = { + readonly status: "archived" | "regular"; + readonly remoteId: string; + readonly externalId?: string | undefined; + readonly title?: string | undefined; + readonly lastMessageAt?: Date | undefined; + readonly custom?: Record<string, unknown> | undefined; +}; + +type RemoveUIMessage = { + type: "remove-ui"; + id: string; +}; + +type ReportToolCall = { + tool_name: string; + tool_call_id: string; + tool_args?: string; + tool_result?: string; + tool_source?: "backend" | "frontend" | "mcp"; + start_ms?: number; + end_ms?: number; + sampling_calls?: SamplingCallData[]; +}; + +type RespondToToolApprovalOptions = { + approvalId: string; + approved: boolean; + optionId?: string; + reason?: string; +}; + +type ResumeRunConfig = StartRunConfig & { + stream?: (options: ChatModelRunOptions) => AsyncGenerator<ChatModelRunResult, void, unknown>; +}; + +type RunConfig = { + readonly custom?: Record<string, unknown>; +}; + +type RuntimeCapabilities = { + readonly switchToBranch: boolean; + readonly switchBranchDuringRun: boolean; + readonly edit: boolean; + readonly reload: boolean; + readonly delete: boolean; + readonly cancel: boolean; + readonly unstable_copy: boolean; + readonly speech: boolean; + readonly dictation: boolean; + readonly voice: boolean; + readonly attachments: boolean; + readonly feedback: boolean; + readonly queue: boolean; +}; + +type SamplingCallData = { + model_id?: string; + input_tokens?: number; + output_tokens?: number; + reasoning_tokens?: number; + cached_input_tokens?: number; + duration_ms?: number; +}; + +type SendOptions = { + startRun?: boolean; + steer?: boolean; +}; + +type SourceMessagePart = { + readonly type: "source"; + readonly sourceType: "url"; + readonly id: string; + readonly url: string; + readonly title?: string; + readonly providerMetadata?: SourceProviderMetadata; + readonly parentId?: string; +} | { + readonly type: "source"; + readonly sourceType: "document"; + readonly id: string; + readonly url?: undefined; + readonly title: string; + readonly mediaType: string; + readonly filename?: string; + readonly providerMetadata?: SourceProviderMetadata; + readonly parentId?: string; +}; + +type SourceProviderMetadata = PartProviderMetadata; + +interface SpeechRecognitionConstructor { + new (): SpeechRecognitionInstance; +} + +interface SpeechRecognitionInstance extends EventTarget { + lang: string; + continuous: boolean; + interimResults: boolean; + start(): void; + stop(): void; + abort(): void; +} + +type SpeechState = { + readonly messageId: string; + readonly status: SpeechSynthesisAdapter.Status; +}; + +declare namespace SpeechSynthesisAdapter { + type Status = { + type: "running" | "starting"; + } | { + type: "ended"; + reason: "cancelled" | "error" | "finished"; + error?: unknown; + }; + type Utterance = { + status: Status; + cancel: () => void; + subscribe: (callback: () => void) => Unsubscribe; + }; +} + +type SpeechSynthesisAdapter = { + speak: (text: string) => SpeechSynthesisAdapter.Utterance; +}; + +type StartRunConfig = { + parentId: string | null; + sourceId: string | null; + runConfig: RunConfig; +}; + +declare const TOOL_RESPONSE_SYMBOL: unique symbol; + +type TextMessagePart = { + readonly type: "text"; + readonly text: string; + readonly providerMetadata?: PartProviderMetadata; + readonly parentId?: string; +}; + +type ThreadAssistantMessage = MessageCommonProps & { + readonly role: "assistant"; + readonly content: readonly ThreadAssistantMessagePart[]; + readonly status: MessageStatus; + readonly metadata: { + readonly unstable_state: ReadonlyJSONValue; + readonly unstable_annotations: readonly ReadonlyJSONValue[]; + readonly unstable_data: readonly ReadonlyJSONValue[]; + readonly steps: readonly ThreadStep[]; + readonly submittedFeedback?: { + readonly type: "negative" | "positive"; + }; + readonly timing?: MessageTiming; + readonly isOptimistic?: boolean; + readonly custom: Record<string, unknown>; + }; +}; + +type ThreadAssistantMessagePart = TextMessagePart | ReasoningMessagePart | ToolCallMessagePart | SourceMessagePart | FileMessagePart | ImageMessagePart | DataMessagePart | GenerativeUIMessagePart; + +type ThreadComposerAttachmentState = Attachment & { + readonly source: "thread-composer"; +}; + +type ThreadComposerRuntime = Omit<ComposerRuntime, "getAttachmentByIndex" | "getState"> & { + readonly path: ComposerRuntimePath & { + composerSource: "thread"; + }; + readonly type: "thread"; + getState(): ThreadComposerState; + getAttachmentByIndex(idx: number): AttachmentRuntime & { + source: "thread-composer"; + }; +}; + +type ThreadComposerState = BaseComposerState & { + readonly type: "thread"; +}; + +type ThreadListItemEventCallback<E extends ThreadListItemEventType> = (payload: ThreadListItemEventPayload[E]) => void; + +type ThreadListItemEventPayload = { + switchedTo: Record<string, never>; + switchedAway: Record<string, never>; +}; + +type ThreadListItemEventType = keyof ThreadListItemEventPayload; + +type ThreadListItemRuntime = { + readonly path: ThreadListItemRuntimePath; + getState(): ThreadListItemState; + initialize(): Promise<{ + remoteId: string; + externalId: string | undefined; + }>; + generateTitle(): Promise<void>; + switchTo(options?: { + unarchive?: boolean; + }): Promise<void>; + rename(newTitle: string): Promise<void>; + updateCustom(custom: Record<string, unknown> | undefined): Promise<void>; + archive(): Promise<void>; + unarchive(): Promise<void>; + delete(): Promise<void>; + detach(): void; + subscribe(callback: () => void): Unsubscribe; + unstable_on<E extends ThreadListItemEventType>(event: E, callback: ThreadListItemEventCallback<E>): Unsubscribe; + __internal_getRuntime(): ThreadListItemRuntime; +}; + +type ThreadListItemRuntimePath = { + readonly ref: string; + readonly threadSelector: { + readonly type: "main"; + } | { + readonly type: "index"; + readonly index: number; + } | { + readonly type: "archiveIndex"; + readonly index: number; + } | { + readonly type: "threadId"; + readonly threadId: string; + }; +}; + +type ThreadListItemState = { + readonly isMain: boolean; + readonly id: string; + readonly remoteId: string | undefined; + readonly externalId: string | undefined; + readonly status: ThreadListItemStatus; + readonly title?: string | undefined; + readonly lastMessageAt?: Date | undefined; + readonly custom?: Record<string, unknown> | undefined; +}; + +type ThreadListItemStatus = "archived" | "deleted" | "new" | "regular"; + +type ThreadListRuntime = { + getState(): ThreadListState; + subscribe(callback: () => void): Unsubscribe; + readonly main: ThreadRuntime; + getById(threadId: string): ThreadRuntime; + readonly mainItem: ThreadListItemRuntime; + getItemById(threadId: string): ThreadListItemRuntime; + getItemByIndex(idx: number): ThreadListItemRuntime; + getArchivedItemByIndex(idx: number): ThreadListItemRuntime; + switchToThread(threadId: string, options?: { + unarchive?: boolean; + }): Promise<void>; + switchToNewThread(): Promise<void>; + getLoadThreadsPromise(): Promise<void>; + reload(): Promise<void>; + loadMore(): Promise<void>; +}; + +type ThreadListState = { + readonly mainThreadId: string; + readonly newThreadId: string | undefined; + readonly threadIds: readonly string[]; + readonly archivedThreadIds: readonly string[]; + readonly isLoading: boolean; + readonly isLoadingMore: boolean; + readonly hasMore: boolean; + readonly threadItems: Readonly<Record<string, Omit<ThreadListItemState, "isMain" | "threadId">>>; +}; + +type ThreadMessage = BaseThreadMessage & (ThreadSystemMessage | ThreadUserMessage | ThreadAssistantMessage); + +type ThreadMessageLike = { + readonly role: "assistant" | "system" | "user"; + readonly content: string | readonly (TextMessagePart | ReasoningMessagePart | SourceMessagePart | ImageMessagePart | FileMessagePart | DataMessagePart | GenerativeUIMessagePart | Unstable_AudioMessagePart | DataPrefixedPart | { + readonly type: "tool-call"; + readonly toolCallId?: string; + readonly toolName: string; + readonly args?: ReadonlyJSONObject; + readonly argsText?: string; + readonly artifact?: any; + readonly result?: any | undefined; + readonly isError?: boolean | undefined; + readonly parentId?: string | undefined; + readonly messages?: readonly ThreadMessage[] | undefined; + readonly interrupt?: { + type: "human"; + payload: unknown; + }; + readonly timing?: ToolCallTiming; + readonly providerMetadata?: PartProviderMetadata; + readonly approval?: { + readonly id: string; + readonly approved?: boolean; + readonly reason?: string; + readonly isAutomatic?: boolean; + readonly options?: readonly ToolApprovalOption[]; + readonly optionId?: string; + readonly resolution?: "cancelled" | "expired"; + }; + })[]; + readonly id?: string | undefined; + readonly createdAt?: Date | undefined; + readonly status?: MessageStatus | undefined; + readonly attachments?: readonly (Omit<CompleteAttachment, "content"> & { + readonly content: readonly (ThreadUserMessagePart | DataPrefixedPart)[]; + })[] | undefined; + readonly metadata?: { + readonly unstable_state?: ReadonlyJSONValue; + readonly unstable_annotations?: readonly ReadonlyJSONValue[] | undefined; + readonly unstable_data?: readonly ReadonlyJSONValue[] | undefined; + readonly steps?: readonly ThreadStep[] | undefined; + readonly timing?: MessageTiming | undefined; + readonly submittedFeedback?: { + readonly type: "negative" | "positive"; + }; + readonly isOptimistic?: boolean | undefined; + readonly custom?: Record<string, unknown> | undefined; + } | undefined; +}; + +type ThreadRuntime = { + readonly path: ThreadRuntimePath; + readonly composer: ThreadComposerRuntime; + getState(): ThreadState; + append(message: CreateAppendMessage): void; + deleteMessage(messageId: string): void | Promise<void>; + startRun(config: CreateStartRunConfig): void; + resumeRun(config: CreateResumeRunConfig): void; + exportExternalState(): any; + importExternalState(state: any): void; + subscribe(callback: () => void): Unsubscribe; + cancelRun(): void; + getModelContext(): ModelContext; + export(): ExportedMessageRepository; + import(repository: ExportedMessageRepository): void; + reset(initialMessages?: readonly ThreadMessageLike[]): void; + getMessageByIndex(idx: number): MessageRuntime; + getMessageById(messageId: string): MessageRuntime; + stopSpeaking(): void; + connectVoice(): void; + disconnectVoice(): void; + getVoiceVolume(): number; + subscribeVoiceVolume(callback: () => void): Unsubscribe; + muteVoice(): void; + unmuteVoice(): void; + unstable_on<E extends ThreadRuntimeEventType>(event: E, callback: ThreadRuntimeEventCallback<E>): Unsubscribe; +}; + +type ThreadRuntimeEventCallback<E extends ThreadRuntimeEventType> = (payload: ThreadRuntimeEventPayload[E]) => void; + +type ThreadRuntimeEventPayload = { + runStart: Record<string, never>; + runEnd: Record<string, never>; + initialize: Record<string, never>; + modelContextUpdate: Record<string, never>; +}; + +type ThreadRuntimeEventType = keyof ThreadRuntimeEventPayload; + +type ThreadRuntimePath = { + readonly ref: string; + readonly threadSelector: { + readonly type: "main"; + } | { + readonly type: "threadId"; + readonly threadId: string; + }; +}; + +type ThreadState = { + readonly threadId: string; + readonly metadata: ThreadListItemState; + readonly isDisabled: boolean; + readonly isLoading: boolean; + readonly isRunning: boolean; + readonly capabilities: RuntimeCapabilities; + readonly messages: readonly ThreadMessage[]; + readonly state: ReadonlyJSONValue; + readonly suggestions: readonly ThreadSuggestion[]; + readonly extras: unknown; + readonly speech: SpeechState | undefined; + readonly voice: VoiceSessionState | undefined; +}; + +type ThreadStep = { + readonly messageId?: string; + readonly usage?: { + readonly inputTokens: number; + readonly outputTokens: number; + } | undefined; +}; + +type ThreadSuggestion = { + prompt: string; +}; + +type ThreadSystemMessage = MessageCommonProps & { + readonly role: "system"; + readonly content: readonly [ + TextMessagePart + ]; + readonly metadata: { + readonly unstable_state?: undefined; + readonly unstable_annotations?: undefined; + readonly unstable_data?: undefined; + readonly steps?: undefined; + readonly submittedFeedback?: undefined; + readonly timing?: undefined; + readonly custom: Record<string, unknown>; + }; +}; + +type ThreadUserMessage = MessageCommonProps & { + readonly role: "user"; + readonly content: readonly ThreadUserMessagePart[]; + readonly attachments: readonly CompleteAttachment[]; + readonly metadata: { + readonly unstable_state?: undefined; + readonly unstable_annotations?: undefined; + readonly unstable_data?: undefined; + readonly steps?: undefined; + readonly submittedFeedback?: undefined; + readonly timing?: undefined; + readonly custom: Record<string, unknown>; + }; +}; + +type ThreadUserMessagePart = TextMessagePart | ImageMessagePart | FileMessagePart | DataMessagePart | Unstable_AudioMessagePart; + +type Tool<TArgs extends Record<string, unknown> = Record<string, unknown>, TResult = unknown> = FrontendTool<TArgs, TResult> | BackendTool<TArgs, TResult> | HumanTool<TArgs, TResult> | ProviderTool<TArgs, TResult> | McpTool | ToolWithoutType<TArgs, TResult>; + +type ToolApprovalOption = { + readonly id: string; + readonly kind: ToolApprovalOptionKind | (string & {}); + readonly label?: string; + readonly description?: string; + readonly grants?: readonly string[]; + readonly confirm?: boolean | { + title?: string; + description?: string; + }; +}; + +type ToolApprovalOptionKind = "allow-always" | "allow-once" | "reject-always" | "reject-once"; + +type ToolApprovalResponse = { + readonly approved: boolean; + readonly reason?: string; +} | { + readonly optionId: string; + readonly reason?: string; +} | { + readonly approved: boolean; + readonly optionId: string; + readonly reason?: string; +}; + +type ToolBase<TArgs extends Record<string, unknown> = Record<string, unknown>, TResult = unknown> = { + streamCall?: ToolStreamCallFunction<TArgs, TResult>; + display?: ToolDisplay; +}; + +interface ToolCallArgsReader<TArgs extends Record<string, unknown>> { + get<PathT extends TypePath<TArgs>>(...fieldPath: PathT): Promise<TypeAtPath<TArgs, PathT>>; + streamValues<PathT extends TypePath<TArgs>>(...fieldPath: PathT): AsyncIterableStream<DeepPartial<TypeAtPath<TArgs, PathT>>>; + streamText<PathT extends TypePath<TArgs>>(...fieldPath: PathT): TypeAtPath<TArgs, PathT> extends string & (infer U) ? AsyncIterableStream<U> : never; + forEach<PathT extends TypePath<TArgs>>(...fieldPath: PathT): NonNullable<TypeAtPath<TArgs, PathT>> extends Array<infer U> ? AsyncIterableStream<U> : never; +} + +type ToolCallMessagePart<TArgs = ReadonlyJSONObject, TResult = unknown> = { + readonly type: "tool-call"; + readonly toolCallId: string; + readonly toolName: string; + readonly args: TArgs; + readonly result?: TResult | undefined; + readonly isError?: boolean | undefined; + readonly argsText: string; + readonly artifact?: unknown; + readonly timing?: ToolCallTiming; + readonly mcp?: ToolCallMessagePartMcpMetadata; + readonly providerMetadata?: PartProviderMetadata; + readonly modelContent?: readonly ToolModelContentPart[] | undefined; + readonly interrupt?: { + type: "human"; + payload: unknown; + }; + readonly approval?: { + readonly id: string; + readonly approved?: boolean; + readonly reason?: string; + readonly isAutomatic?: boolean; + readonly options?: readonly ToolApprovalOption[]; + readonly optionId?: string; + readonly resolution?: "cancelled" | "expired"; + }; + readonly parentId?: string; + readonly messages?: readonly ThreadMessage[]; +}; + +type ToolCallMessagePartMcpMetadata = { + readonly app?: McpAppMetadata; +}; + +type ToolCallMessagePartStatus = { + readonly type: "requires-action"; + readonly reason: "interrupt"; +} | MessagePartStatus; + +interface ToolCallReader<TArgs extends Record<string, unknown> = Record<string, unknown>, TResult = unknown> { + args: ToolCallArgsReader<TArgs>; + response: ToolCallResponseReader<TResult>; + result: { + get: () => Promise<TResult>; + }; +} + +interface ToolCallResponseReader<TResult> { + get: () => Promise<ToolResponse<TResult>>; +} + +type ToolCallTiming = { + readonly startedAt: number; + readonly completedAt?: number; +}; + +type ToolDisplay = "inline" | "standalone"; + +type ToolExecuteFunction<TArgs, TResult> = (args: TArgs, context: ToolExecutionContext) => TResult | Promise<TResult>; + +type ToolExecutionContext = { + toolCallId: string; + abortSignal: AbortSignal; + human: (payload: unknown) => Promise<unknown>; +}; + +type ToolExecutionStatus = { + type: "executing"; +} | { + type: "interrupt"; + payload: { + type: "human"; + payload: unknown; + }; +}; + +type ToolModelContentPart = { + readonly type: "text"; + readonly text: string; +} | { + readonly type: "file"; + readonly data: string; + readonly mediaType: string; + readonly filename?: string; +}; + +type ToolModelOutputFunction<TArgs, TResult> = (options: { + toolCallId: string; + input: TArgs; + output: TResult; +}) => readonly ToolModelContentPart[] | Promise<readonly ToolModelContentPart[]>; + +declare class ToolResponse<TResult> { + get [TOOL_RESPONSE_SYMBOL](): boolean; + readonly artifact?: ReadonlyJSONValue; + readonly result: TResult; + readonly isError: boolean; + readonly modelContent?: readonly ToolModelContentPart[]; + readonly messages?: ReadonlyJSONValue; + constructor(options: ToolResponseLike<TResult>); + static [Symbol.hasInstance](obj: unknown): obj is ToolResponse<ReadonlyJSONValue>; + static toResponse(result: any | ToolResponse<any>): ToolResponse<any>; +} + +type ToolResponseLike<TResult> = { + result: TResult; + artifact?: ReadonlyJSONValue | undefined; + isError?: boolean | undefined; + modelContent?: readonly ToolModelContentPart[] | undefined; + messages?: ReadonlyJSONValue | undefined; +}; + +type ToolStreamCallFunction<TArgs extends Record<string, unknown> = Record<string, unknown>, TResult = unknown> = (reader: ToolCallReader<TArgs, TResult>, context: ToolExecutionContext) => void; + +type ToolWithoutType<TArgs extends Record<string, unknown> = Record<string, unknown>, TResult = unknown> = (Omit<FrontendTool<TArgs, TResult>, "type"> | Omit<BackendTool<TArgs, TResult>, "type"> | Omit<HumanTool<TArgs, TResult>, "type"> | Omit<ProviderTool<TArgs, TResult>, "type">) & { + type?: undefined; +}; + +type TupleIndex<T extends readonly any[]> = Exclude<keyof T, keyof any[]>; + +type TypeAtPath<T, P extends readonly any[]> = P extends [ + infer Head, + ...infer Rest +] ? Head extends keyof T ? TypeAtPath<T[Head], Rest> : never : T; + +type TypePath<T> = [ +] | (0 extends 1 & T ? any[] : T extends object ? T extends readonly any[] ? number extends T["length"] ? { + [K in TupleIndex<T>]: [ + AsNumber<K>, + ...TypePath<T[K]> + ]; +}[TupleIndex<T>] : [ + number, + ...TypePath<T[number]> +] : { + [K in ObjectKey<T>]: [ + K, + ...TypePath<T[K]> + ]; +}[ObjectKey<T>] : [ +]); + +type UIMessage<TName extends string = string, TProps extends Record<string, unknown> = Record<string, unknown>> = { + type: "ui"; + id: string; + name: TName; + props: TProps; + metadata?: { + merge?: boolean; + run_id?: string; + name?: string; + tags?: string[]; + message_id?: string; + id?: string; + [key: string]: unknown; + }; +}; + +type Unstable_AudioMessagePart = { + readonly type: "audio"; + readonly audio: { + readonly data: string; + readonly format: "mp3" | "wav"; + }; +}; + +type Unsubscribe = () => void; + +type UseStreamRuntimeOptions = UseStreamOptions extends (infer O) ? O extends UseStreamOptions ? O & LangChainRuntimeExtraOptions : never : never; + +type VoiceSessionState = { + readonly status: RealtimeVoiceAdapter.Status; + readonly isMuted: boolean; + readonly mode: RealtimeVoiceAdapter.Mode; +}; + +declare const convertLangChainBaseMessage: (message: LangChainBaseMessage, metadata?: LangChainMessageConverterMetadata) => useExternalMessageConverter.Message; + +declare global { + interface Window { + SpeechRecognition?: SpeechRecognitionConstructor; + webkitSpeechRecognition?: SpeechRecognitionConstructor; + } +} + +declare namespace entry_root_exports { + export { LangChainBaseMessage, LangChainContentBlock, LangChainToolCall, RemoveUIMessage, SubagentDiscoverySnapshot$1 as SubagentDiscoverySnapshot, SubgraphDiscoverySnapshot$1 as SubgraphDiscoverySnapshot, UIMessage, UseStreamRuntimeOptions, convertLangChainBaseMessage, useLangChainError, useLangChainInterruptState, useLangChainInterrupts, useLangChainRespond, useLangChainRespondAll, useLangChainSend, useLangChainSendCommand, useLangChainState, useLangChainStream, useLangChainStreamingTiming, useLangChainSubagents, useLangChainSubgraphs, useLangChainSubmit, useLangChainToolCalls, useStreamRuntime }; +} + +declare namespace useExternalMessageConverter { + type Message = (ThreadMessageLike & { + readonly convertConfig?: { + readonly joinStrategy?: JoinStrategy; + }; + }) | { + role: "tool"; + toolCallId: string; + toolName?: string | undefined; + result: any; + artifact?: any; + isError?: boolean; + messages?: readonly ThreadMessage[]; + }; + type Metadata = { + readonly toolStatuses?: Record<string, ToolExecutionStatus>; + readonly error?: ReadonlyJSONValue; + readonly messageTiming?: Record<string, MessageTiming>; + }; + type Callback<T> = (message: T, metadata: Metadata) => Message | Message[]; +} + +declare const useExternalMessageConverter: <T extends WeakKey>(_param2: { + callback: useExternalMessageConverter.Callback<T>; + messages: T[]; + isRunning: boolean; + joinStrategy?: JoinStrategy | undefined; + metadata?: useExternalMessageConverter.Metadata | undefined; +}) => ThreadMessage[]; + +declare const useLangChainError: () => unknown; + +declare const useLangChainInterruptState: () => { + value?: unknown; +} | undefined; + +declare const useLangChainInterrupts: () => readonly { + id?: string; + value?: unknown; +}[]; + +declare const useLangChainRespond: () => (response: unknown, options?: Record<string, unknown>) => Promise<void>; + +declare const useLangChainRespondAll: () => (responsesById: Record<string, unknown>, options?: Record<string, unknown>) => Promise<void>; + +declare const useLangChainSend: () => (messages: readonly LangChainBaseMessage[], options?: Record<string, unknown>) => Promise<void>; + +declare const useLangChainSendCommand: () => (command: Record<string, unknown>) => Promise<void>; + +declare function useLangChainState<T>(key: string): T | undefined; + +declare function useLangChainState<T>(key: string, defaultValue: T): T; + +declare const useLangChainStream: () => import("@langchain/react").AnyStream | undefined; + +declare const useLangChainStreamingTiming: (messages: readonly LangChainBaseMessage[], isRunning: boolean) => Record<string, MessageTiming>; + +declare const useLangChainSubagents: () => ReadonlyMap<string, SubagentDiscoverySnapshot>; + +declare const useLangChainSubgraphs: () => ReadonlyMap<string, SubgraphDiscoverySnapshot>; + +declare const useLangChainSubmit: () => (values: Record<string, unknown> | null | undefined, options?: Record<string, unknown>) => Promise<void>; + +declare const useLangChainToolCalls: () => readonly AssembledToolCall<string, unknown, unknown>[]; + +declare const useStreamRuntime: (rawOptions: UseStreamRuntimeOptions) => AssistantRuntime; + +export { entry_root_exports as entry_root }; diff --git a/api-surface/assistant-ui__react-langgraph.ts b/api-surface/assistant-ui__react-langgraph.ts new file mode 100644 index 0000000..7ca2455 --- /dev/null +++ b/api-surface/assistant-ui__react-langgraph.ts @@ -0,0 +1,2236 @@ +import { Client, StreamMode } from "@langchain/langgraph-sdk"; + +import { StandardSchemaV1 } from "@standard-schema/spec"; + +import { ComponentType, PropsWithChildren } from "react"; + +type AddToolResultOptions = { + messageId: string; + toolName: string; + toolCallId: string; + result: ReadonlyJSONValue; + isError: boolean; + artifact?: ReadonlyJSONValue | undefined; + modelContent?: readonly ToolModelContentPart[] | undefined; +}; + +type AppendMessage = Omit<ThreadMessage, "id"> & { + parentId: string | null; + sourceId: string | null; + runConfig: RunConfig | undefined; + startRun?: boolean | undefined; + steer?: boolean | undefined; +}; + +type AsNumber<K> = K extends `${infer N extends number}` ? N | K : never; + +declare class AssistantCloud { + readonly threads: AssistantCloudThreads; + readonly auth: { + tokens: AssistantCloudAuthTokens; + }; + readonly runs: AssistantCloudRuns; + readonly files: AssistantCloudFiles; + readonly telemetry: AssistantCloudTelemetryConfig; + constructor(config: AssistantCloudConfig); +} + +declare class AssistantCloudAPI { + _auth: AssistantCloudAuthStrategy; + _baseUrl: string; + constructor(config: AssistantCloudConfig); + initializeAuth(): Promise<boolean>; + makeRawRequest(endpoint: string, options?: MakeRequestOptions): Promise<Response>; + makeRequest(endpoint: string, options?: MakeRequestOptions): Promise<any>; +} + +type AssistantCloudAuthStrategy = { + readonly strategy: "anon" | "api-key" | "jwt"; + getAuthHeaders(): Promise<Record<string, string> | false>; + readAuthHeaders(headers: Headers): void; +}; + +declare class AssistantCloudAuthTokens { + private cloud; + constructor(cloud: AssistantCloudAPI); + create(): Promise<AssistantCloudAuthTokensCreateResponse>; +} + +type AssistantCloudAuthTokensCreateResponse = { + token: string; +}; + +type AssistantCloudConfig = ({ + baseUrl: string; + authToken: () => Promise<string | null>; +} | { + baseUrl?: string; + apiKey: string; + userId: string; + workspaceId: string; +} | { + baseUrl: string; + anonymous: true; +}) & { + telemetry?: boolean | AssistantCloudTelemetryConfig; +}; + +declare class AssistantCloudFiles { + private cloud; + constructor(cloud: AssistantCloudAPI); + pdfToImages(body: PdfToImagesRequestBody): Promise<PdfToImagesResponse>; + generatePresignedUploadUrl(body: GeneratePresignedUploadUrlRequestBody): Promise<GeneratePresignedUploadUrlResponse>; +} + +type AssistantCloudMessageCreateResponse = { + message_id: string; +}; + +type AssistantCloudRunReport = { + thread_id: string; + status: "completed" | "error" | "incomplete"; + total_steps?: number; + tool_calls?: ReportToolCall[]; + steps?: { + input_tokens?: number; + output_tokens?: number; + reasoning_tokens?: number; + cached_input_tokens?: number; + tool_calls?: ReportToolCall[]; + start_ms?: number; + end_ms?: number; + }[]; + input_tokens?: number; + output_tokens?: number; + reasoning_tokens?: number; + cached_input_tokens?: number; + model_id?: string; + provider_type?: string; + duration_ms?: number; + output_text?: string; + metadata?: Record<string, unknown>; +}; + +declare class AssistantCloudRuns { + private cloud; + constructor(cloud: AssistantCloudAPI); + __internal_getAssistantOptions(assistantId: string): { + api: string; + headers: () => Promise<{ + Accept: string; + }>; + body: { + assistant_id: string; + response_format: string; + thread_id: string; + }; + }; + stream(body: AssistantCloudRunsStreamBody): Promise<AssistantStream>; + report(body: AssistantCloudRunReport): Promise<{ + run_id: string; + }>; +} + +type AssistantCloudRunsStreamBody = { + thread_id: string; + assistant_id: "system/thread_title"; + messages: readonly unknown[]; +}; + +type AssistantCloudTelemetryConfig = { + enabled?: boolean; + beforeReport?: (report: AssistantCloudRunReport) => AssistantCloudRunReport | null; +}; + +type AssistantCloudThreadMessageCreateBody = { + parent_id: string | null; + format: "aui/v0" | string; + content: ReadonlyJSONObject; +}; + +type AssistantCloudThreadMessageListQuery = { + format?: string; +}; + +type AssistantCloudThreadMessageListResponse = { + messages: CloudMessage[]; +}; + +type AssistantCloudThreadMessageUpdateBody = { + content: ReadonlyJSONObject; +}; + +declare class AssistantCloudThreadMessages { + private cloud; + constructor(cloud: AssistantCloudAPI); + list(threadId: string, query?: AssistantCloudThreadMessageListQuery): Promise<AssistantCloudThreadMessageListResponse>; + create(threadId: string, body: AssistantCloudThreadMessageCreateBody): Promise<AssistantCloudMessageCreateResponse>; + update(threadId: string, messageId: string, body: AssistantCloudThreadMessageUpdateBody): Promise<void>; +} + +declare class AssistantCloudThreads { + private cloud; + readonly messages: AssistantCloudThreadMessages; + constructor(cloud: AssistantCloudAPI); + list(query?: AssistantCloudThreadsListQuery): Promise<AssistantCloudThreadsListResponse>; + get(threadId: string): Promise<CloudThread>; + create(body: AssistantCloudThreadsCreateBody): Promise<AssistantCloudThreadsCreateResponse>; + update(threadId: string, body: AssistantCloudThreadsUpdateBody): Promise<void>; + delete(threadId: string): Promise<void>; +} + +type AssistantCloudThreadsCreateBody = { + title?: string | undefined; + last_message_at: Date; + metadata?: unknown | undefined; + external_id?: string | undefined; +}; + +type AssistantCloudThreadsCreateResponse = { + thread_id: string; +}; + +type AssistantCloudThreadsListQuery = { + is_archived?: boolean; + limit?: number; + after?: string; +}; + +type AssistantCloudThreadsListResponse = { + threads: CloudThread[]; +}; + +type AssistantCloudThreadsUpdateBody = { + title?: string | undefined; + last_message_at?: Date | undefined; + metadata?: unknown | undefined; + is_archived?: boolean | undefined; +}; + +type AssistantMessageContent = string | AssistantMessageContentComplex[]; + +type AssistantMessageContentComplex = MessageContentText | MessageContentImageUrl | MessageContentToolUse | MessageContentFile | MessageContentReasoning | MessageContentThinking | MessageContentComputerCall; + +type AssistantRuntime = { + readonly threads: ThreadListRuntime; + readonly thread: ThreadRuntime; + registerModelContextProvider(provider: ModelContextProvider): Unsubscribe; +}; + +type AssistantStream = ReadableStream<AssistantStreamChunk>; + +declare const AssistantStream: { + toResponse(stream: AssistantStream, transformer: AssistantStreamEncoder): Response; + fromResponse(response: Response, transformer: ReadableWritablePair<AssistantStreamChunk, Uint8Array<ArrayBuffer>>): ReadableStream<AssistantStreamChunk>; + toByteStream(stream: AssistantStream, transformer: ReadableWritablePair<Uint8Array<ArrayBuffer>, AssistantStreamChunk>): ReadableStream<Uint8Array<ArrayBuffer>>; + fromByteStream(readable: ReadableStream<Uint8Array<ArrayBuffer>>, transformer: ReadableWritablePair<AssistantStreamChunk, Uint8Array<ArrayBuffer>>): ReadableStream<AssistantStreamChunk>; +}; + +type AssistantStreamChunk = { + readonly path: readonly number[]; +} & ({ + readonly type: "part-start"; + readonly part: PartInit; +} | { + readonly type: "part-finish"; +} | { + readonly type: "tool-call-args-text-finish"; +} | { + readonly type: "text-delta"; + readonly textDelta: string; +} | { + readonly type: "annotations"; + readonly annotations: ReadonlyJSONValue[]; +} | { + readonly type: "data"; + readonly data: ReadonlyJSONValue[]; +} | { + readonly type: "step-start"; + readonly messageId: string; +} | { + readonly type: "step-finish"; + readonly finishReason: "content-filter" | "error" | "length" | "other" | "stop" | "tool-calls" | "unknown"; + readonly usage: { + readonly inputTokens: number; + readonly outputTokens: number; + }; + readonly isContinued: boolean; +} | { + readonly type: "message-finish"; + readonly finishReason: "content-filter" | "error" | "length" | "other" | "stop" | "tool-calls" | "unknown"; + readonly usage: { + readonly inputTokens: number; + readonly outputTokens: number; + }; +} | { + readonly type: "result"; + readonly artifact?: ReadonlyJSONValue; + readonly result: ReadonlyJSONValue; + readonly isError: boolean; + readonly modelContent?: readonly ToolModelContentPart[]; + readonly messages?: ReadonlyJSONValue; +} | { + readonly type: "error"; + readonly error: string; +} | { + readonly type: "update-state"; + readonly operations: ObjectStreamOperation[]; +}); + +type AssistantStreamEncoder = ReadableWritablePair<Uint8Array<ArrayBuffer>, AssistantStreamChunk> & { + headers?: Headers; +}; + +type AsyncIterableStream<T> = AsyncIterable<T> & ReadableStream<T>; + +type Attachment = PendingAttachment | CompleteAttachment; + +type AttachmentAdapter = { + accept: string; + add(state: { + file: File; + }): Promise<PendingAttachment> | AsyncGenerator<PendingAttachment, void>; + remove(attachment: Attachment): Promise<void>; + send(attachment: PendingAttachment): Promise<CompleteAttachment>; +}; + +type AttachmentAddErrorEvent = { + readonly reason: AttachmentAddErrorReason; + readonly message: string; + readonly attachmentId?: string; + readonly error?: Error; +}; + +type AttachmentAddErrorReason = "adapter-error" | "no-adapter" | "not-accepted"; + +type AttachmentRuntime<TSource extends AttachmentRuntimeSource = AttachmentRuntimeSource> = { + readonly path: AttachmentRuntimePath & { + attachmentSource: TSource; + }; + readonly source: TSource; + getState(): AttachmentState & { + source: TSource; + }; + remove(): Promise<void>; + subscribe(callback: () => void): Unsubscribe; +}; + +type AttachmentRuntimePath = ((MessageRuntimePath & { + readonly attachmentSource: "edit-composer" | "message"; +}) | (ThreadRuntimePath & { + readonly attachmentSource: "thread-composer"; +})) & { + readonly attachmentSelector: { + readonly type: "index"; + readonly index: number; + } | { + readonly type: "index"; + readonly index: number; + } | { + readonly type: "index"; + readonly index: number; + }; +}; + +type AttachmentRuntimeSource = AttachmentState["source"]; + +type AttachmentState = ThreadComposerAttachmentState | EditComposerAttachmentState | MessageAttachmentState; + +type BackendTool<TArgs extends Record<string, unknown> = Record<string, unknown>, TResult = unknown> = ToolBase<TArgs, TResult> & { + type: "backend"; + description?: undefined; + parameters?: undefined; + disabled?: undefined; + execute?: undefined; + toModelOutput?: undefined; + experimental_onSchemaValidationError?: undefined; + providerOptions?: undefined; +}; + +type BaseAttachment = { + id: string; + type: "image" | "document" | "file" | (string & {}); + name: string; + contentType?: string | undefined; + file?: File; + content?: ThreadUserMessagePart[]; +}; + +type BaseComposerState = { + readonly canCancel: boolean; + readonly canSend: boolean; + readonly isEditing: boolean; + readonly isEmpty: boolean; + readonly text: string; + readonly role: MessageRole; + readonly attachments: readonly Attachment[]; + readonly runConfig: RunConfig; + readonly attachmentAccept: string; + readonly dictation: DictationState | undefined; + readonly quote: QuoteInfo | undefined; + readonly queue: readonly QueueItemState[]; +}; + +type BaseThreadMessage = { + readonly status?: ThreadAssistantMessage["status"]; + readonly metadata: { + readonly unstable_state?: ReadonlyJSONValue; + readonly unstable_annotations?: readonly ReadonlyJSONValue[]; + readonly unstable_data?: readonly ReadonlyJSONValue[]; + readonly steps?: readonly ThreadStep[]; + readonly submittedFeedback?: { + readonly type: "negative" | "positive"; + }; + readonly timing?: MessageTiming; + readonly isOptimistic?: boolean; + readonly custom: Record<string, unknown>; + }; + readonly attachments?: ThreadUserMessage["attachments"]; +}; + +type ChatModelRunOptions = { + readonly messages: readonly ThreadMessage[]; + readonly runConfig: RunConfig; + readonly abortSignal: AbortSignal; + readonly context: ModelContext; + readonly unstable_assistantMessageId?: string | undefined; + readonly unstable_threadId?: string | undefined; + readonly unstable_parentId?: string | null | undefined; + unstable_getMessage(): ThreadMessage; +}; + +type ChatModelRunResult = { + readonly content?: readonly ThreadAssistantMessagePart[] | undefined; + readonly status?: MessageStatus | undefined; + readonly metadata?: { + readonly unstable_state?: ReadonlyJSONValue; + readonly unstable_annotations?: readonly ReadonlyJSONValue[] | undefined; + readonly unstable_data?: readonly ReadonlyJSONValue[] | undefined; + readonly steps?: readonly ThreadStep[] | undefined; + readonly timing?: MessageTiming | undefined; + readonly custom?: Record<string, unknown> | undefined; + }; +}; + +type CloudMessage = { + id: string; + parent_id: string | null; + height: number; + created_at: Date; + updated_at: Date; + format: "aui/v0" | string; + content: ReadonlyJSONObject; +}; + +type CloudThread = { + title: string; + last_message_at: Date; + metadata: unknown; + external_id: string | null; + id: string; + project_id: string; + created_at: Date; + updated_at: Date; + workspace_id: string; + is_archived: boolean; +}; + +type CompleteAttachment = BaseAttachment & { + status: CompleteAttachmentStatus; + content: ThreadUserMessagePart[]; +}; + +type CompleteAttachmentStatus = { + type: "complete"; +}; + +type ComposerRuntime = { + readonly path: ComposerRuntimePath; + readonly type: "edit" | "thread"; + getState(): ComposerState; + addAttachment(fileOrAttachment: File | CreateAttachment): Promise<void>; + setText(text: string): void; + setRole(role: MessageRole): void; + setRunConfig(runConfig: RunConfig): void; + reset(): Promise<void>; + clearAttachments(): Promise<void>; + send(options?: SendOptions): void; + cancel(): void; + steerQueueItem(queueItemId: string): void; + removeQueueItem(queueItemId: string): void; + subscribe(callback: () => void): Unsubscribe; + getAttachmentByIndex(idx: number): AttachmentRuntime; + startDictation(): void; + stopDictation(): void; + setQuote(quote: QuoteInfo | undefined): void; + unstable_on<E extends ComposerRuntimeEventType>(event: E, callback: ComposerRuntimeEventCallback<E>): Unsubscribe; +}; + +type ComposerRuntimeEventCallback<E extends ComposerRuntimeEventType> = (payload: ComposerRuntimeEventPayload[E]) => void; + +type ComposerRuntimeEventPayload = { + send: Record<string, never>; + attachmentAdd: Record<string, never>; + attachmentAddError: AttachmentAddErrorEvent; +}; + +type ComposerRuntimeEventType = keyof ComposerRuntimeEventPayload; + +type ComposerRuntimePath = (ThreadRuntimePath & { + readonly composerSource: "thread"; +}) | (MessageRuntimePath & { + readonly composerSource: "edit"; +}); + +type ComposerState = ThreadComposerState | EditComposerState; + +type CreateAppendMessage = string | { + parentId?: string | null | undefined; + sourceId?: string | null | undefined; + role?: AppendMessage["role"] | undefined; + content: AppendMessage["content"]; + attachments?: AppendMessage["attachments"] | undefined; + metadata?: AppendMessage["metadata"] | undefined; + createdAt?: Date | undefined; + runConfig?: AppendMessage["runConfig"] | undefined; + startRun?: boolean | undefined; +}; + +type CreateAttachment = { + id?: string; + type?: "image" | "document" | "file" | (string & {}); + name: string; + contentType?: string; + content: ThreadUserMessagePart[]; +}; + +type CreateLangGraphStreamOptions = { + client: LangGraphStreamClient; + assistantId: string; + streamMode?: StreamMode | StreamMode[]; + onDisconnect?: StreamPayload["onDisconnect"]; +}; + +type CreateResumeRunConfig = CreateStartRunConfig & { + stream?: (options: ChatModelRunOptions) => AsyncGenerator<ChatModelRunResult, void, unknown>; +}; + +type CreateStartRunConfig = { + parentId: string | null; + sourceId?: string | null | undefined; + runConfig?: RunConfig | undefined; +}; + +type CustomEventType = string; + +type DataMessagePart<T = any> = { + readonly type: "data"; + readonly name: string; + readonly data: T; +}; + +type DataMessagePartComponent<T = any> = ComponentType<DataMessagePartProps<T>>; + +type DataMessagePartProps<T = any> = MessagePartState & DataMessagePart<T>; + +type DataPrefixedPart = { + readonly type: `data-${string}`; + readonly data: any; +}; + +type DeepPartial<T> = T extends readonly any[] ? readonly DeepPartial<T[number]>[] : T extends { + [key: string]: any; +} ? { + readonly [K in keyof T]?: DeepPartial<T[K]>; +} : T; + +declare namespace DictationAdapter { + type Status = { + type: "running" | "starting"; + } | { + type: "ended"; + reason: "cancelled" | "error" | "stopped"; + }; + type Result = { + transcript: string; + isFinal?: boolean; + }; + type Session = { + status: Status; + stop: () => Promise<void>; + cancel: () => void; + onSpeechStart: (callback: () => void) => Unsubscribe; + onSpeechEnd: (callback: (result: Result) => void) => Unsubscribe; + onSpeech: (callback: (result: Result) => void) => Unsubscribe; + }; +} + +type DictationAdapter = { + listen: () => DictationAdapter.Session; + disableInputDuringDictation?: boolean; +}; + +type DictationState = { + readonly status: DictationAdapter.Status; + readonly transcript?: string; + readonly inputDisabled?: boolean; +}; + +type EditComposerAttachmentState = Attachment & { + readonly source: "edit-composer"; +}; + +type EditComposerRuntime = Omit<ComposerRuntime, "getAttachmentByIndex" | "getState"> & { + readonly path: ComposerRuntimePath & { + composerSource: "edit"; + }; + readonly type: "edit"; + getState(): EditComposerState; + beginEdit(): void; + getAttachmentByIndex(idx: number): AttachmentRuntime & { + source: "edit-composer"; + }; +}; + +type EditComposerState = BaseComposerState & { + readonly type: "edit"; + readonly parentId: string | null; + readonly sourceId: string | null; +}; + +type EventType = LangGraphKnownEventTypes | CustomEventType; + +type ExportedMessageRepository = { + headId?: string | null; + messages: Array<{ + message: ThreadMessage; + parentId: string | null; + runConfig?: RunConfig; + }>; +}; + +declare const ExportedMessageRepository: { + fromArray: (messages: readonly ThreadMessageLike[]) => ExportedMessageRepository; + fromBranchableArray: (items: readonly { + message: ThreadMessageLike; + parentId: string | null; + }[], options?: { + headId?: string | null; + }) => ExportedMessageRepository; +}; + +type ExternalStoreAdapter<T = ThreadMessage> = ExternalStoreAdapterBase<T> & (T extends ThreadMessage ? object : ExternalStoreMessageConverterAdapter<T>); + +type ExternalStoreAdapterBase<T> = { + isDisabled?: boolean | undefined; + isSendDisabled?: boolean | undefined; + isRunning?: boolean | undefined; + isLoading?: boolean | undefined; + messages?: readonly T[]; + messageRepository?: ExportedMessageRepository; + suggestions?: readonly ThreadSuggestion[] | undefined; + state?: ReadonlyJSONValue | undefined; + extras?: unknown; + setMessages?: ((messages: readonly T[]) => void) | undefined; + unstable_onBranchChange?: ((event: ExternalStoreBranchChange) => void) | undefined; + onImport?: ((messages: readonly ThreadMessage[]) => void) | undefined; + onExportExternalState?: (() => any) | undefined; + onLoadExternalState?: ((state: any) => void) | undefined; + onNew: (message: AppendMessage) => Promise<void>; + queue?: ExternalThreadQueueAdapter | undefined; + onEdit?: ((message: AppendMessage) => Promise<void>) | undefined; + onDelete?: ((messageId: string) => Promise<void> | void) | undefined; + onReload?: ((parentId: string | null, config: StartRunConfig) => Promise<void>) | undefined; + onResume?: ((config: ResumeRunConfig) => Promise<void>) | undefined; + onCancel?: (() => Promise<void>) | undefined; + onAddToolResult?: ((options: AddToolResultOptions) => Promise<void> | void) | undefined; + onResumeToolCall?: ((options: { + toolCallId: string; + payload: unknown; + }) => void) | undefined; + onRespondToToolApproval?: ((options: RespondToToolApprovalOptions) => Promise<void> | void) | undefined; + convertMessage?: ExternalStoreMessageConverter<T> | undefined; + adapters?: { + attachments?: AttachmentAdapter | undefined; + speech?: SpeechSynthesisAdapter | undefined; + dictation?: DictationAdapter | undefined; + voice?: RealtimeVoiceAdapter | undefined; + feedback?: FeedbackAdapter | undefined; + threadList?: ExternalStoreThreadListAdapter | undefined; + } | undefined; + unstable_capabilities?: { + copy?: boolean | undefined; + } | undefined; + unstable_enableToolInvocations?: boolean | undefined; + setToolStatuses?: ((statuses: Record<string, ToolExecutionStatus>) => void) | undefined; +}; + +type ExternalStoreBranchChange = { + headId: string | null; + visibleMessageIds: readonly string[]; +}; + +type ExternalStoreMessageConverter<T> = (message: T, idx: number) => ThreadMessageLike; + +type ExternalStoreMessageConverterAdapter<T> = { + convertMessage: ExternalStoreMessageConverter<T>; +}; + +type ExternalStoreSharedOptions = Pick<ExternalStoreAdapter, "isDisabled" | "isSendDisabled" | "suggestions" | "unstable_capabilities">; + +type ExternalStoreThreadData<TState extends "archived" | "regular"> = { + status: TState; + id: string; + remoteId?: string | undefined; + externalId?: string | undefined; + title?: string | undefined; + custom?: Record<string, unknown> | undefined; +}; + +type ExternalStoreThreadListAdapter = { + threadId?: string | undefined; + isLoading?: boolean | undefined; + threads?: readonly ExternalStoreThreadData<"regular">[] | undefined; + archivedThreads?: readonly ExternalStoreThreadData<"archived">[] | undefined; + onSwitchToNewThread?: (() => Promise<void> | void) | undefined; + onSwitchToThread?: ((threadId: string) => Promise<void> | void) | undefined; + onRename?: (threadId: string, newTitle: string) => (Promise<void> | void) | undefined; + onUpdateCustom?: ((threadId: string, custom: Record<string, unknown> | undefined) => Promise<void> | void) | undefined; + onArchive?: ((threadId: string) => Promise<void> | void) | undefined; + onUnarchive?: ((threadId: string) => Promise<void> | void) | undefined; + onDelete?: ((threadId: string) => Promise<void> | void) | undefined; +}; + +type ExternalThreadQueueAdapter = { + items: readonly QueueItemState[]; + enqueue: (message: AppendMessage, options: { + steer: boolean; + }) => void; + steer: (queueItemId: string) => void; + remove: (queueItemId: string) => void; + clear: (reason: "cancel-run" | "edit" | "reload") => void; +}; + +type FeedbackAdapter = { + submit: (feedback: FeedbackAdapterFeedback) => void; +}; + +type FeedbackAdapterFeedback = { + message: ThreadMessage; + type: "negative" | "positive"; +}; + +type FileMessagePart = { + readonly type: "file"; + readonly filename?: string; + readonly data: string; + readonly mimeType: string; + readonly parentId?: string; +}; + +type FrontendTool<TArgs extends Record<string, unknown> = Record<string, unknown>, TResult = unknown> = ToolBase<TArgs, TResult> & { + type: "frontend"; + description?: string | undefined; + parameters: StandardSchemaV1<TArgs> | JSONSchema7; + disabled?: boolean; + execute?: ToolExecuteFunction<TArgs, TResult>; + toModelOutput?: ToolModelOutputFunction<TArgs, TResult>; + experimental_onSchemaValidationError?: OnSchemaValidationErrorFunction<TResult>; + providerOptions?: ProviderOptions; +}; + +type GeneratePresignedUploadUrlRequestBody = { + filename: string; +}; + +type GeneratePresignedUploadUrlResponse = { + success: boolean; + signedUrl: string; + expiresAt: string; + publicUrl: string; +}; + +type GenerativeUIMessagePart = { + readonly type: "generative-ui"; + readonly spec: GenerativeUISpec; + readonly id?: string; + readonly parentId?: string; +}; + +type GenerativeUINode = string | { + readonly component: string; + readonly props?: Record<string, unknown>; + readonly children?: readonly GenerativeUINode[]; + readonly key?: string; +}; + +type GenerativeUISpec = { + readonly root: GenerativeUINode | readonly GenerativeUINode[]; +}; + +type HumanTool<TArgs extends Record<string, unknown> = Record<string, unknown>, TResult = unknown> = ToolBase<TArgs, TResult> & { + type: "human"; + description?: string | undefined; + parameters: StandardSchemaV1<TArgs> | JSONSchema7; + disabled?: boolean; + display?: "standalone"; + execute?: undefined; + toModelOutput?: undefined; + experimental_onSchemaValidationError?: undefined; + providerOptions?: ProviderOptions; +}; + +type ImageMessagePart = { + readonly type: "image"; + readonly image: string; + readonly filename?: string; +}; + +interface JSONSchema7 { + $id?: string | undefined; + $ref?: string | undefined; + $schema?: JSONSchema7Version | undefined; + $comment?: string | undefined; + $defs?: { + [key: string]: JSONSchema7Definition; + } | undefined; + type?: JSONSchema7TypeName | JSONSchema7TypeName[] | undefined; + enum?: JSONSchema7Type[] | undefined; + const?: JSONSchema7Type | undefined; + multipleOf?: number | undefined; + maximum?: number | undefined; + exclusiveMaximum?: number | undefined; + minimum?: number | undefined; + exclusiveMinimum?: number | undefined; + maxLength?: number | undefined; + minLength?: number | undefined; + pattern?: string | undefined; + items?: JSONSchema7Definition | JSONSchema7Definition[] | undefined; + additionalItems?: JSONSchema7Definition | undefined; + maxItems?: number | undefined; + minItems?: number | undefined; + uniqueItems?: boolean | undefined; + contains?: JSONSchema7Definition | undefined; + maxProperties?: number | undefined; + minProperties?: number | undefined; + required?: string[] | undefined; + properties?: { + [key: string]: JSONSchema7Definition; + } | undefined; + patternProperties?: { + [key: string]: JSONSchema7Definition; + } | undefined; + additionalProperties?: JSONSchema7Definition | undefined; + dependencies?: { + [key: string]: JSONSchema7Definition | string[]; + } | undefined; + propertyNames?: JSONSchema7Definition | undefined; + if?: JSONSchema7Definition | undefined; + then?: JSONSchema7Definition | undefined; + else?: JSONSchema7Definition | undefined; + allOf?: JSONSchema7Definition[] | undefined; + anyOf?: JSONSchema7Definition[] | undefined; + oneOf?: JSONSchema7Definition[] | undefined; + not?: JSONSchema7Definition | undefined; + format?: string | undefined; + contentMediaType?: string | undefined; + contentEncoding?: string | undefined; + definitions?: { + [key: string]: JSONSchema7Definition; + } | undefined; + title?: string | undefined; + description?: string | undefined; + default?: JSONSchema7Type | undefined; + readOnly?: boolean | undefined; + writeOnly?: boolean | undefined; + examples?: JSONSchema7Type | undefined; +} + +interface JSONSchema7Array extends Array<JSONSchema7Type> { +} + +type JSONSchema7Definition = JSONSchema7 | boolean; + +interface JSONSchema7Object { + [key: string]: JSONSchema7Type; +} + +type JSONSchema7Type = string | number | boolean | JSONSchema7Object | JSONSchema7Array | null; + +type JSONSchema7TypeName = "array" | "boolean" | "integer" | "null" | "number" | "object" | "string"; + +type JSONSchema7Version = string; + +type JoinStrategy = "concat-content" | "none"; + +type LangChainEvent = { + event: LangGraphKnownEventTypes.MessagesPartial | LangGraphKnownEventTypes.MessagesComplete; + data: LangChainMessage[]; +}; + +type LangChainMessage = { + id?: string; + type: "system"; + content: string; + additional_kwargs?: Record<string, unknown>; +} | { + id?: string; + type: "human"; + content: UserMessageContent; + additional_kwargs?: Record<string, unknown>; +} | { + id?: string; + type: "tool"; + content: string; + tool_call_id: string; + name: string; + artifact?: any; + status: "error" | "success"; +} | { + id?: string; + type: "ai"; + content: AssistantMessageContent; + tool_call_chunks?: LangChainToolCallChunk[]; + tool_calls?: LangChainToolCall[]; + status?: MessageStatus; + additional_kwargs?: { + reasoning?: MessageContentReasoning; + tool_outputs?: MessageContentComputerCall[]; + metadata?: Record<string, unknown>; + }; +}; + +type LangChainMessageChunk = { + id?: string | undefined; + type: "AIMessageChunk"; + content?: AssistantMessageContent | undefined; + tool_call_chunks?: LangChainToolCallChunk[] | undefined; +}; + +type LangChainToolCall = { + index?: number; + id: string; + name: string; + args: ReadonlyJSONObject; + partial_json?: string; +}; + +type LangChainToolCallChunk = { + index: number; + id: string; + name: string; + args?: string; + args_json?: string; +}; + +type LangGraphCommand = { + resume: string; +}; + +type LangGraphInterruptState = { + value?: any; + resumable?: boolean; + when?: string; + ns?: string[]; +}; + +declare enum LangGraphKnownEventTypes { + Messages = "messages", + MessagesPartial = "messages/partial", + MessagesComplete = "messages/complete", + Metadata = "metadata", + Updates = "updates", + Values = "values", + Info = "info", + Error = "error" +} + +declare class LangGraphMessageAccumulator<TMessage extends { + id?: string; +}> { + private messagesMap; + private metadataMap; + private uiMessages; + private appendMessage; + constructor(_param0?: LangGraphStateAccumulatorConfig<TMessage>); + private ensureMessageId; + addMessages(newMessages: TMessage[]): TMessage[]; + addMessageWithMetadata(message: TMessage, metadata: LangGraphTupleMetadata): TMessage[]; + getMessages(): TMessage[]; + getMetadataMap(): Map<string, LangGraphTupleMetadata>; + replaceMessages(newMessages: TMessage[]): TMessage[]; + reconcileMessages(serverMessages: TMessage[]): TMessage[]; + applyUIUpdate(update: UIMessage | RemoveUIMessage | readonly (UIMessage | RemoveUIMessage)[]): UIMessage[]; + replaceUIMessages(newUIMessages: UIMessage[]): UIMessage[]; + getUIMessages(): UIMessage[]; + clear(): void; +} + +type LangGraphMessagesEvent<TMessage> = { + event: EventType; + data: TMessage[] | any; +}; + +type LangGraphSendMessageConfig = { + command?: LangGraphCommand; + runConfig?: unknown; + checkpointId?: string; +}; + +type LangGraphStateAccumulatorConfig<TMessage> = { + initialMessages?: TMessage[]; + initialUIMessages?: UIMessage[]; + appendMessage?: (prev: TMessage | undefined, curr: TMessage) => TMessage; +}; + +type LangGraphStreamCallback<TMessage> = (messages: TMessage[], config: LangGraphSendMessageConfig & { + abortSignal: AbortSignal; + initialize: () => Promise<{ + remoteId: string; + externalId: string | undefined; + }>; +}) => Promise<AsyncGenerator<LangGraphMessagesEvent<TMessage>>> | AsyncGenerator<LangGraphMessagesEvent<TMessage>>; + +type LangGraphStreamClient = { + runs: Pick<Client["runs"], "stream">; +}; + +type LangGraphTupleMetadata = Record<string, unknown>; + +type LanguageModelConfig = { + apiKey?: string; + baseUrl?: string; + modelName?: string; + reasoningEffort?: string; +}; + +type LanguageModelV1CallSettings = { + maxTokens?: number; + temperature?: number; + topP?: number; + presencePenalty?: number; + frequencyPenalty?: number; + seed?: number; + headers?: Record<string, string | undefined>; +}; + +type MakeRequestOptions = { + method?: "POST" | "PUT" | "DELETE" | undefined; + headers?: Record<string, string> | undefined; + query?: Record<string, string | number | boolean> | undefined; + body?: object | undefined; +}; + +type McpAppMetadata = { + readonly resourceUri: string; + readonly mimeType?: string; + readonly visibility?: readonly ("app" | "model")[]; +}; + +type McpServerConfig = { + type: "http" | "sse"; + url: string; + headers?: Record<string, string>; + redirect?: "error" | "follow"; + connectionTimeout?: number | undefined; +} | { + type: "stdio"; + command: string; + args?: readonly string[]; + env?: Record<string, string>; + cwd?: string; + connectionTimeout?: number | undefined; +}; + +type McpTool = ToolBase<Record<string, unknown>, unknown> & { + type: "mcp"; + server: McpServerConfig; + description?: undefined; + parameters?: undefined; + disabled?: boolean; + execute?: undefined; + toModelOutput?: undefined; + experimental_onSchemaValidationError?: undefined; + providerOptions?: undefined; +}; + +type MessageAttachmentState = CompleteAttachment & { + readonly source: "message"; +}; + +type MessageCommonProps = { + readonly id: string; + readonly createdAt: Date; +}; + +type MessageContentComputerCall = { + type: "computer_call"; + call_id: string; + id: string; + action: unknown; + pending_safety_checks: unknown[]; + index: number; +}; + +type MessageContentFile = { + type: "file"; + data: string; + mime_type: string; + source_type?: "base64"; + metadata?: { + filename?: string; + }; +} | { + type: "file"; + url: string; + mime_type?: string; + source_type: "url"; + metadata?: { + filename?: string; + }; +} | { + type: "file"; + id: string; + mime_type?: string; + source_type: "id"; + metadata?: { + filename?: string; + }; +}; + +type MessageContentImageUrl = { + type: "image_url"; + image_url: string | { + url?: string; + }; +}; + +type MessageContentReasoning = { + type: "reasoning"; + summary?: MessageContentReasoningSummaryText[]; + reasoning?: string; +}; + +type MessageContentReasoningSummaryText = { + type: "summary_text"; + text?: string; +}; + +type MessageContentText = { + type: "text" | "text_delta"; + text: string; +}; + +type MessageContentThinking = { + type: "thinking"; + thinking: string; +}; + +type MessageContentToolUse = { + type: "input_json_delta" | "tool_use"; +}; + +type MessagePartRuntime = { + addToolResult(result: any | ToolResponse<any>): void; + resumeToolCall(payload: unknown): void; + respondToToolApproval(response: ToolApprovalResponse): void; + readonly path: MessagePartRuntimePath; + getState(): MessagePartState; + subscribe(callback: () => void): Unsubscribe; +}; + +type MessagePartRuntimePath = MessageRuntimePath & { + readonly messagePartSelector: { + readonly type: "index"; + readonly index: number; + } | { + readonly type: "toolCallId"; + readonly toolCallId: string; + }; +}; + +type MessagePartState = (ThreadUserMessagePart | ThreadAssistantMessagePart) & { + readonly status: MessagePartStatus | ToolCallMessagePartStatus; +}; + +type MessagePartStatus = { + readonly type: "running"; +} | { + readonly type: "complete"; +} | { + readonly type: "incomplete"; + readonly reason: "cancelled" | "content-filter" | "error" | "length" | "other"; + readonly error?: unknown; +}; + +type MessageRole = ThreadMessage["role"]; + +type MessageRuntime = { + readonly path: MessageRuntimePath; + readonly composer: EditComposerRuntime; + getState(): MessageState; + delete(): void | Promise<void>; + reload(config?: ReloadConfig): void; + speak(): void; + stopSpeaking(): void; + submitFeedback(_param1: { + type: "positive" | "negative"; + }): void; + switchToBranch(_param2: { + position?: "previous" | "next" | undefined; + branchId?: string | undefined; + }): void; + unstable_getCopyText(): string; + subscribe(callback: () => void): Unsubscribe; + getMessagePartByIndex(idx: number): MessagePartRuntime; + getMessagePartByToolCallId(toolCallId: string): MessagePartRuntime; + getAttachmentByIndex(idx: number): AttachmentRuntime & { + source: "message"; + }; +}; + +type MessageRuntimePath = ThreadRuntimePath & { + readonly messageSelector: { + readonly type: "messageId"; + readonly messageId: string; + } | { + readonly type: "index"; + readonly index: number; + }; +}; + +type MessageState = ThreadMessage & { + readonly parentId: string | null; + readonly index: number; + readonly isLast: boolean; + readonly branchNumber: number; + readonly branchCount: number; + readonly speech: SpeechState | undefined; +}; + +type MessageStatus = { + readonly type: "running"; +} | { + readonly type: "requires-action"; + readonly reason: "interrupt" | "tool-calls"; +} | { + readonly type: "complete"; + readonly reason: "stop" | "unknown"; +} | { + readonly type: "incomplete"; + readonly reason: "cancelled" | "content-filter" | "error" | "length" | "other" | "tool-calls"; + readonly error?: ReadonlyJSONValue; +}; + +type MessageTiming = { + readonly streamStartTime: number; + readonly firstTokenTime?: number; + readonly totalStreamTime?: number; + readonly tokenCount?: number; + readonly tokensPerSecond?: number; + readonly totalChunks: number; + readonly toolCallCount: number; +}; + +type ModelContext = { + priority?: number | undefined; + system?: string | undefined; + tools?: Record<string, Tool<any, any>> | undefined; + callSettings?: LanguageModelV1CallSettings | undefined; + config?: LanguageModelConfig | undefined; + unstable_composerMetadata?: Record<string, unknown> | undefined; +}; + +type ModelContextProvider = { + getModelContext: () => ModelContext; + subscribe?: (callback: () => void) => Unsubscribe; +}; + +type ObjectKey<T> = keyof T & (string | number); + +type ObjectStreamOperation = { + readonly type: "set"; + readonly path: readonly string[]; + readonly value: ReadonlyJSONValue; +} | { + readonly type: "append-text"; + readonly path: readonly string[]; + readonly value: string; +}; + +type OnCustomEventCallback = (type: string, data: unknown) => void | Promise<void>; + +type OnErrorEventCallback = (error: unknown) => void | Promise<void>; + +type OnInfoEventCallback = (info: unknown) => void | Promise<void>; + +type OnMessageChunkCallback = (chunk: LangChainMessageChunk, metadata: LangGraphTupleMetadata) => void | Promise<void>; + +type OnMetadataEventCallback = (metadata: unknown) => void | Promise<void>; + +type OnSchemaValidationErrorFunction<TResult> = ToolExecuteFunction<unknown, TResult>; + +type OnSubgraphErrorEventCallback = (namespace: string, error: unknown) => void | Promise<void>; + +type OnSubgraphUpdatesEventCallback = (namespace: string, updates: unknown) => void | Promise<void>; + +type OnSubgraphValuesEventCallback = (namespace: string, values: unknown) => void | Promise<void>; + +type OnUpdatesEventCallback = (updates: unknown) => void | Promise<void>; + +type OnValuesEventCallback = (values: unknown) => void | Promise<void>; + +type PartInit = { + readonly type: "reasoning" | "text"; + readonly parentId?: string; +} | { + readonly type: "tool-call"; + readonly toolCallId: string; + readonly toolName: string; + readonly parentId?: string; +} | { + readonly type: "source"; + readonly sourceType: "url"; + readonly id: string; + readonly url: string; + readonly title?: string; + readonly parentId?: string; +} | { + readonly type: "file"; + readonly data: string; + readonly mimeType: string; + readonly parentId?: string; +} | { + readonly type: "data"; + readonly name: string; + readonly data: ReadonlyJSONValue; + readonly parentId?: string; +}; + +type PartProviderMetadata = { + readonly [providerName: string]: ReadonlyJSONObject; +}; + +type PdfToImagesRequestBody = { + file_blob?: string | undefined; + file_url?: string | undefined; +}; + +type PdfToImagesResponse = { + success: boolean; + urls: string[]; + message: string; +}; + +type PendingAttachment = BaseAttachment & { + status: PendingAttachmentStatus; + file: File; +}; + +type PendingAttachmentStatus = { + type: "running"; + reason: "uploading"; + progress: number; +} | { + type: "requires-action"; + reason: "composer-send"; +} | { + type: "incomplete"; + reason: "error" | "upload-paused"; + message?: string; +}; + +type ProviderOptions = Record<string, Record<string, unknown>>; + +type ProviderTool<TArgs extends Record<string, unknown> = Record<string, unknown>, TResult = unknown> = ToolBase<TArgs, TResult> & { + type: "provider"; + providerId: `${string}.${string}`; + parameters?: StandardSchemaV1<TArgs> | JSONSchema7 | undefined; + args: Record<string, unknown>; + supportsDeferredResults?: boolean; + description?: undefined; + disabled?: boolean; + execute?: undefined; + toModelOutput?: undefined; + experimental_onSchemaValidationError?: undefined; + providerOptions?: ProviderOptions; +}; + +type QueueItemState = { + readonly id: string; + readonly prompt: string; +}; + +type QuoteInfo = { + readonly text: string; + readonly messageId: string; +}; + +type ReadonlyJSONArray = readonly ReadonlyJSONValue[]; + +type ReadonlyJSONObject = { + readonly [key: string]: ReadonlyJSONValue; +}; + +type ReadonlyJSONValue = null | string | number | boolean | ReadonlyJSONObject | ReadonlyJSONArray; + +declare namespace RealtimeVoiceAdapter { + type Status = { + type: "running" | "starting"; + } | { + type: "ended"; + reason: "cancelled" | "error" | "finished"; + error?: unknown; + }; + type Mode = "listening" | "speaking"; + type TranscriptItem = { + role: "assistant" | "user"; + text: string; + isFinal?: boolean; + }; + type Session = { + status: Status; + isMuted: boolean; + disconnect: () => void; + mute: () => void; + unmute: () => void; + onStatusChange: (callback: (status: Status) => void) => Unsubscribe; + onTranscript: (callback: (transcript: TranscriptItem) => void) => Unsubscribe; + onModeChange: (callback: (mode: Mode) => void) => Unsubscribe; + onVolumeChange: (callback: (volume: number) => void) => Unsubscribe; + }; +} + +type RealtimeVoiceAdapter = { + connect: (options: { + abortSignal?: AbortSignal; + }) => RealtimeVoiceAdapter.Session; +}; + +type ReasoningMessagePart = { + readonly type: "reasoning"; + readonly text: string; + readonly providerMetadata?: PartProviderMetadata; + readonly parentId?: string; +}; + +type ReloadConfig = { + runConfig?: RunConfig; +}; + +type RemoteThreadInitializeResponse = { + remoteId: string; + externalId: string | undefined; +}; + +type RemoteThreadListAdapter = { + list(params?: RemoteThreadListPageOptions): Promise<RemoteThreadListResponse>; + rename(remoteId: string, newTitle: string): Promise<void>; + updateCustom?(remoteId: string, custom: Record<string, unknown> | undefined): Promise<void>; + archive(remoteId: string): Promise<void>; + unarchive(remoteId: string): Promise<void>; + delete(remoteId: string): Promise<void>; + initialize(threadId: string): Promise<RemoteThreadInitializeResponse>; + generateTitle(remoteId: string, unstable_messages: readonly ThreadMessage[]): Promise<AssistantStream>; + fetch(threadId: string): Promise<RemoteThreadMetadata>; + unstable_Provider?: ComponentType<PropsWithChildren> | undefined; +}; + +type RemoteThreadListPageOptions = { + after?: string | undefined; +}; + +type RemoteThreadListResponse = { + threads: RemoteThreadMetadata[]; + nextCursor?: string | undefined; +}; + +type RemoteThreadMetadata = { + readonly status: "archived" | "regular"; + readonly remoteId: string; + readonly externalId?: string | undefined; + readonly title?: string | undefined; + readonly lastMessageAt?: Date | undefined; + readonly custom?: Record<string, unknown> | undefined; +}; + +type RemoveUIMessage = { + type: "remove-ui"; + id: string; +}; + +type ReportToolCall = { + tool_name: string; + tool_call_id: string; + tool_args?: string; + tool_result?: string; + tool_source?: "backend" | "frontend" | "mcp"; + start_ms?: number; + end_ms?: number; + sampling_calls?: SamplingCallData[]; +}; + +type RespondToToolApprovalOptions = { + approvalId: string; + approved: boolean; + optionId?: string; + reason?: string; +}; + +type ResumeRunConfig = StartRunConfig & { + stream?: (options: ChatModelRunOptions) => AsyncGenerator<ChatModelRunResult, void, unknown>; +}; + +type RunConfig = { + readonly custom?: Record<string, unknown>; +}; + +type RuntimeCapabilities = { + readonly switchToBranch: boolean; + readonly switchBranchDuringRun: boolean; + readonly edit: boolean; + readonly reload: boolean; + readonly delete: boolean; + readonly cancel: boolean; + readonly unstable_copy: boolean; + readonly speech: boolean; + readonly dictation: boolean; + readonly voice: boolean; + readonly attachments: boolean; + readonly feedback: boolean; + readonly queue: boolean; +}; + +type SamplingCallData = { + model_id?: string; + input_tokens?: number; + output_tokens?: number; + reasoning_tokens?: number; + cached_input_tokens?: number; + duration_ms?: number; +}; + +type SendOptions = { + startRun?: boolean; + steer?: boolean; +}; + +type SourceMessagePart = { + readonly type: "source"; + readonly sourceType: "url"; + readonly id: string; + readonly url: string; + readonly title?: string; + readonly providerMetadata?: SourceProviderMetadata; + readonly parentId?: string; +} | { + readonly type: "source"; + readonly sourceType: "document"; + readonly id: string; + readonly url?: undefined; + readonly title: string; + readonly mediaType: string; + readonly filename?: string; + readonly providerMetadata?: SourceProviderMetadata; + readonly parentId?: string; +}; + +type SourceProviderMetadata = PartProviderMetadata; + +interface SpeechRecognitionConstructor { + new (): SpeechRecognitionInstance; +} + +interface SpeechRecognitionInstance extends EventTarget { + lang: string; + continuous: boolean; + interimResults: boolean; + start(): void; + stop(): void; + abort(): void; +} + +type SpeechState = { + readonly messageId: string; + readonly status: SpeechSynthesisAdapter.Status; +}; + +declare namespace SpeechSynthesisAdapter { + type Status = { + type: "running" | "starting"; + } | { + type: "ended"; + reason: "cancelled" | "error" | "finished"; + error?: unknown; + }; + type Utterance = { + status: Status; + cancel: () => void; + subscribe: (callback: () => void) => Unsubscribe; + }; +} + +type SpeechSynthesisAdapter = { + speak: (text: string) => SpeechSynthesisAdapter.Utterance; +}; + +type StartRunConfig = { + parentId: string | null; + sourceId: string | null; + runConfig: RunConfig; +}; + +type StreamPayload = NonNullable<Parameters<LangGraphStreamClient["runs"]["stream"]>[2]>; + +declare const TOOL_RESPONSE_SYMBOL: unique symbol; + +type TextMessagePart = { + readonly type: "text"; + readonly text: string; + readonly providerMetadata?: PartProviderMetadata; + readonly parentId?: string; +}; + +type ThreadAssistantMessage = MessageCommonProps & { + readonly role: "assistant"; + readonly content: readonly ThreadAssistantMessagePart[]; + readonly status: MessageStatus; + readonly metadata: { + readonly unstable_state: ReadonlyJSONValue; + readonly unstable_annotations: readonly ReadonlyJSONValue[]; + readonly unstable_data: readonly ReadonlyJSONValue[]; + readonly steps: readonly ThreadStep[]; + readonly submittedFeedback?: { + readonly type: "negative" | "positive"; + }; + readonly timing?: MessageTiming; + readonly isOptimistic?: boolean; + readonly custom: Record<string, unknown>; + }; +}; + +type ThreadAssistantMessagePart = TextMessagePart | ReasoningMessagePart | ToolCallMessagePart | SourceMessagePart | FileMessagePart | ImageMessagePart | DataMessagePart | GenerativeUIMessagePart; + +type ThreadComposerAttachmentState = Attachment & { + readonly source: "thread-composer"; +}; + +type ThreadComposerRuntime = Omit<ComposerRuntime, "getAttachmentByIndex" | "getState"> & { + readonly path: ComposerRuntimePath & { + composerSource: "thread"; + }; + readonly type: "thread"; + getState(): ThreadComposerState; + getAttachmentByIndex(idx: number): AttachmentRuntime & { + source: "thread-composer"; + }; +}; + +type ThreadComposerState = BaseComposerState & { + readonly type: "thread"; +}; + +type ThreadListItemEventCallback<E extends ThreadListItemEventType> = (payload: ThreadListItemEventPayload[E]) => void; + +type ThreadListItemEventPayload = { + switchedTo: Record<string, never>; + switchedAway: Record<string, never>; +}; + +type ThreadListItemEventType = keyof ThreadListItemEventPayload; + +type ThreadListItemRuntime = { + readonly path: ThreadListItemRuntimePath; + getState(): ThreadListItemState; + initialize(): Promise<{ + remoteId: string; + externalId: string | undefined; + }>; + generateTitle(): Promise<void>; + switchTo(options?: { + unarchive?: boolean; + }): Promise<void>; + rename(newTitle: string): Promise<void>; + updateCustom(custom: Record<string, unknown> | undefined): Promise<void>; + archive(): Promise<void>; + unarchive(): Promise<void>; + delete(): Promise<void>; + detach(): void; + subscribe(callback: () => void): Unsubscribe; + unstable_on<E extends ThreadListItemEventType>(event: E, callback: ThreadListItemEventCallback<E>): Unsubscribe; + __internal_getRuntime(): ThreadListItemRuntime; +}; + +type ThreadListItemRuntimePath = { + readonly ref: string; + readonly threadSelector: { + readonly type: "main"; + } | { + readonly type: "index"; + readonly index: number; + } | { + readonly type: "archiveIndex"; + readonly index: number; + } | { + readonly type: "threadId"; + readonly threadId: string; + }; +}; + +type ThreadListItemState = { + readonly isMain: boolean; + readonly id: string; + readonly remoteId: string | undefined; + readonly externalId: string | undefined; + readonly status: ThreadListItemStatus; + readonly title?: string | undefined; + readonly lastMessageAt?: Date | undefined; + readonly custom?: Record<string, unknown> | undefined; +}; + +type ThreadListItemStatus = "archived" | "deleted" | "new" | "regular"; + +type ThreadListRuntime = { + getState(): ThreadListState; + subscribe(callback: () => void): Unsubscribe; + readonly main: ThreadRuntime; + getById(threadId: string): ThreadRuntime; + readonly mainItem: ThreadListItemRuntime; + getItemById(threadId: string): ThreadListItemRuntime; + getItemByIndex(idx: number): ThreadListItemRuntime; + getArchivedItemByIndex(idx: number): ThreadListItemRuntime; + switchToThread(threadId: string, options?: { + unarchive?: boolean; + }): Promise<void>; + switchToNewThread(): Promise<void>; + getLoadThreadsPromise(): Promise<void>; + reload(): Promise<void>; + loadMore(): Promise<void>; +}; + +type ThreadListState = { + readonly mainThreadId: string; + readonly newThreadId: string | undefined; + readonly threadIds: readonly string[]; + readonly archivedThreadIds: readonly string[]; + readonly isLoading: boolean; + readonly isLoadingMore: boolean; + readonly hasMore: boolean; + readonly threadItems: Readonly<Record<string, Omit<ThreadListItemState, "isMain" | "threadId">>>; +}; + +type ThreadMessage = BaseThreadMessage & (ThreadSystemMessage | ThreadUserMessage | ThreadAssistantMessage); + +type ThreadMessageLike = { + readonly role: "assistant" | "system" | "user"; + readonly content: string | readonly (TextMessagePart | ReasoningMessagePart | SourceMessagePart | ImageMessagePart | FileMessagePart | DataMessagePart | GenerativeUIMessagePart | Unstable_AudioMessagePart | DataPrefixedPart | { + readonly type: "tool-call"; + readonly toolCallId?: string; + readonly toolName: string; + readonly args?: ReadonlyJSONObject; + readonly argsText?: string; + readonly artifact?: any; + readonly result?: any | undefined; + readonly isError?: boolean | undefined; + readonly parentId?: string | undefined; + readonly messages?: readonly ThreadMessage[] | undefined; + readonly interrupt?: { + type: "human"; + payload: unknown; + }; + readonly timing?: ToolCallTiming; + readonly providerMetadata?: PartProviderMetadata; + readonly approval?: { + readonly id: string; + readonly approved?: boolean; + readonly reason?: string; + readonly isAutomatic?: boolean; + readonly options?: readonly ToolApprovalOption[]; + readonly optionId?: string; + readonly resolution?: "cancelled" | "expired"; + }; + })[]; + readonly id?: string | undefined; + readonly createdAt?: Date | undefined; + readonly status?: MessageStatus | undefined; + readonly attachments?: readonly (Omit<CompleteAttachment, "content"> & { + readonly content: readonly (ThreadUserMessagePart | DataPrefixedPart)[]; + })[] | undefined; + readonly metadata?: { + readonly unstable_state?: ReadonlyJSONValue; + readonly unstable_annotations?: readonly ReadonlyJSONValue[] | undefined; + readonly unstable_data?: readonly ReadonlyJSONValue[] | undefined; + readonly steps?: readonly ThreadStep[] | undefined; + readonly timing?: MessageTiming | undefined; + readonly submittedFeedback?: { + readonly type: "negative" | "positive"; + }; + readonly isOptimistic?: boolean | undefined; + readonly custom?: Record<string, unknown> | undefined; + } | undefined; +}; + +type ThreadRuntime = { + readonly path: ThreadRuntimePath; + readonly composer: ThreadComposerRuntime; + getState(): ThreadState; + append(message: CreateAppendMessage): void; + deleteMessage(messageId: string): void | Promise<void>; + startRun(config: CreateStartRunConfig): void; + resumeRun(config: CreateResumeRunConfig): void; + exportExternalState(): any; + importExternalState(state: any): void; + subscribe(callback: () => void): Unsubscribe; + cancelRun(): void; + getModelContext(): ModelContext; + export(): ExportedMessageRepository; + import(repository: ExportedMessageRepository): void; + reset(initialMessages?: readonly ThreadMessageLike[]): void; + getMessageByIndex(idx: number): MessageRuntime; + getMessageById(messageId: string): MessageRuntime; + stopSpeaking(): void; + connectVoice(): void; + disconnectVoice(): void; + getVoiceVolume(): number; + subscribeVoiceVolume(callback: () => void): Unsubscribe; + muteVoice(): void; + unmuteVoice(): void; + unstable_on<E extends ThreadRuntimeEventType>(event: E, callback: ThreadRuntimeEventCallback<E>): Unsubscribe; +}; + +type ThreadRuntimeEventCallback<E extends ThreadRuntimeEventType> = (payload: ThreadRuntimeEventPayload[E]) => void; + +type ThreadRuntimeEventPayload = { + runStart: Record<string, never>; + runEnd: Record<string, never>; + initialize: Record<string, never>; + modelContextUpdate: Record<string, never>; +}; + +type ThreadRuntimeEventType = keyof ThreadRuntimeEventPayload; + +type ThreadRuntimePath = { + readonly ref: string; + readonly threadSelector: { + readonly type: "main"; + } | { + readonly type: "threadId"; + readonly threadId: string; + }; +}; + +type ThreadState = { + readonly threadId: string; + readonly metadata: ThreadListItemState; + readonly isDisabled: boolean; + readonly isLoading: boolean; + readonly isRunning: boolean; + readonly capabilities: RuntimeCapabilities; + readonly messages: readonly ThreadMessage[]; + readonly state: ReadonlyJSONValue; + readonly suggestions: readonly ThreadSuggestion[]; + readonly extras: unknown; + readonly speech: SpeechState | undefined; + readonly voice: VoiceSessionState | undefined; +}; + +type ThreadStep = { + readonly messageId?: string; + readonly usage?: { + readonly inputTokens: number; + readonly outputTokens: number; + } | undefined; +}; + +type ThreadSuggestion = { + prompt: string; +}; + +type ThreadSystemMessage = MessageCommonProps & { + readonly role: "system"; + readonly content: readonly [ + TextMessagePart + ]; + readonly metadata: { + readonly unstable_state?: undefined; + readonly unstable_annotations?: undefined; + readonly unstable_data?: undefined; + readonly steps?: undefined; + readonly submittedFeedback?: undefined; + readonly timing?: undefined; + readonly custom: Record<string, unknown>; + }; +}; + +type ThreadUserMessage = MessageCommonProps & { + readonly role: "user"; + readonly content: readonly ThreadUserMessagePart[]; + readonly attachments: readonly CompleteAttachment[]; + readonly metadata: { + readonly unstable_state?: undefined; + readonly unstable_annotations?: undefined; + readonly unstable_data?: undefined; + readonly steps?: undefined; + readonly submittedFeedback?: undefined; + readonly timing?: undefined; + readonly custom: Record<string, unknown>; + }; +}; + +type ThreadUserMessagePart = TextMessagePart | ImageMessagePart | FileMessagePart | DataMessagePart | Unstable_AudioMessagePart; + +type Tool<TArgs extends Record<string, unknown> = Record<string, unknown>, TResult = unknown> = FrontendTool<TArgs, TResult> | BackendTool<TArgs, TResult> | HumanTool<TArgs, TResult> | ProviderTool<TArgs, TResult> | McpTool | ToolWithoutType<TArgs, TResult>; + +type ToolApprovalOption = { + readonly id: string; + readonly kind: ToolApprovalOptionKind | (string & {}); + readonly label?: string; + readonly description?: string; + readonly grants?: readonly string[]; + readonly confirm?: boolean | { + title?: string; + description?: string; + }; +}; + +type ToolApprovalOptionKind = "allow-always" | "allow-once" | "reject-always" | "reject-once"; + +type ToolApprovalResponse = { + readonly approved: boolean; + readonly reason?: string; +} | { + readonly optionId: string; + readonly reason?: string; +} | { + readonly approved: boolean; + readonly optionId: string; + readonly reason?: string; +}; + +type ToolBase<TArgs extends Record<string, unknown> = Record<string, unknown>, TResult = unknown> = { + streamCall?: ToolStreamCallFunction<TArgs, TResult>; + display?: ToolDisplay; +}; + +interface ToolCallArgsReader<TArgs extends Record<string, unknown>> { + get<PathT extends TypePath<TArgs>>(...fieldPath: PathT): Promise<TypeAtPath<TArgs, PathT>>; + streamValues<PathT extends TypePath<TArgs>>(...fieldPath: PathT): AsyncIterableStream<DeepPartial<TypeAtPath<TArgs, PathT>>>; + streamText<PathT extends TypePath<TArgs>>(...fieldPath: PathT): TypeAtPath<TArgs, PathT> extends string & (infer U) ? AsyncIterableStream<U> : never; + forEach<PathT extends TypePath<TArgs>>(...fieldPath: PathT): NonNullable<TypeAtPath<TArgs, PathT>> extends Array<infer U> ? AsyncIterableStream<U> : never; +} + +type ToolCallMessagePart<TArgs = ReadonlyJSONObject, TResult = unknown> = { + readonly type: "tool-call"; + readonly toolCallId: string; + readonly toolName: string; + readonly args: TArgs; + readonly result?: TResult | undefined; + readonly isError?: boolean | undefined; + readonly argsText: string; + readonly artifact?: unknown; + readonly timing?: ToolCallTiming; + readonly mcp?: ToolCallMessagePartMcpMetadata; + readonly providerMetadata?: PartProviderMetadata; + readonly modelContent?: readonly ToolModelContentPart[] | undefined; + readonly interrupt?: { + type: "human"; + payload: unknown; + }; + readonly approval?: { + readonly id: string; + readonly approved?: boolean; + readonly reason?: string; + readonly isAutomatic?: boolean; + readonly options?: readonly ToolApprovalOption[]; + readonly optionId?: string; + readonly resolution?: "cancelled" | "expired"; + }; + readonly parentId?: string; + readonly messages?: readonly ThreadMessage[]; +}; + +type ToolCallMessagePartMcpMetadata = { + readonly app?: McpAppMetadata; +}; + +type ToolCallMessagePartStatus = { + readonly type: "requires-action"; + readonly reason: "interrupt"; +} | MessagePartStatus; + +interface ToolCallReader<TArgs extends Record<string, unknown> = Record<string, unknown>, TResult = unknown> { + args: ToolCallArgsReader<TArgs>; + response: ToolCallResponseReader<TResult>; + result: { + get: () => Promise<TResult>; + }; +} + +interface ToolCallResponseReader<TResult> { + get: () => Promise<ToolResponse<TResult>>; +} + +type ToolCallTiming = { + readonly startedAt: number; + readonly completedAt?: number; +}; + +type ToolDisplay = "inline" | "standalone"; + +type ToolExecuteFunction<TArgs, TResult> = (args: TArgs, context: ToolExecutionContext) => TResult | Promise<TResult>; + +type ToolExecutionContext = { + toolCallId: string; + abortSignal: AbortSignal; + human: (payload: unknown) => Promise<unknown>; +}; + +type ToolExecutionStatus = { + type: "executing"; +} | { + type: "interrupt"; + payload: { + type: "human"; + payload: unknown; + }; +}; + +type ToolModelContentPart = { + readonly type: "text"; + readonly text: string; +} | { + readonly type: "file"; + readonly data: string; + readonly mediaType: string; + readonly filename?: string; +}; + +type ToolModelOutputFunction<TArgs, TResult> = (options: { + toolCallId: string; + input: TArgs; + output: TResult; +}) => readonly ToolModelContentPart[] | Promise<readonly ToolModelContentPart[]>; + +declare class ToolResponse<TResult> { + get [TOOL_RESPONSE_SYMBOL](): boolean; + readonly artifact?: ReadonlyJSONValue; + readonly result: TResult; + readonly isError: boolean; + readonly modelContent?: readonly ToolModelContentPart[]; + readonly messages?: ReadonlyJSONValue; + constructor(options: ToolResponseLike<TResult>); + static [Symbol.hasInstance](obj: unknown): obj is ToolResponse<ReadonlyJSONValue>; + static toResponse(result: any | ToolResponse<any>): ToolResponse<any>; +} + +type ToolResponseLike<TResult> = { + result: TResult; + artifact?: ReadonlyJSONValue | undefined; + isError?: boolean | undefined; + modelContent?: readonly ToolModelContentPart[] | undefined; + messages?: ReadonlyJSONValue | undefined; +}; + +type ToolStreamCallFunction<TArgs extends Record<string, unknown> = Record<string, unknown>, TResult = unknown> = (reader: ToolCallReader<TArgs, TResult>, context: ToolExecutionContext) => void; + +type ToolWithoutType<TArgs extends Record<string, unknown> = Record<string, unknown>, TResult = unknown> = (Omit<FrontendTool<TArgs, TResult>, "type"> | Omit<BackendTool<TArgs, TResult>, "type"> | Omit<HumanTool<TArgs, TResult>, "type"> | Omit<ProviderTool<TArgs, TResult>, "type">) & { + type?: undefined; +}; + +type TupleIndex<T extends readonly any[]> = Exclude<keyof T, keyof any[]>; + +type TypeAtPath<T, P extends readonly any[]> = P extends [ + infer Head, + ...infer Rest +] ? Head extends keyof T ? TypeAtPath<T[Head], Rest> : never : T; + +type TypePath<T> = [ +] | (0 extends 1 & T ? any[] : T extends object ? T extends readonly any[] ? number extends T["length"] ? { + [K in TupleIndex<T>]: [ + AsNumber<K>, + ...TypePath<T[K]> + ]; +}[TupleIndex<T>] : [ + number, + ...TypePath<T[number]> +] : { + [K in ObjectKey<T>]: [ + K, + ...TypePath<T[K]> + ]; +}[ObjectKey<T>] : [ +]); + +type UIMessage<TName extends string = string, TProps extends Record<string, unknown> = Record<string, unknown>> = { + type: "ui"; + id: string; + name: TName; + props: TProps; + metadata?: { + merge?: boolean; + run_id?: string; + name?: string; + tags?: string[]; + message_id?: string; + [key: string]: unknown; + }; +}; + +type Unstable_AudioMessagePart = { + readonly type: "audio"; + readonly audio: { + readonly data: string; + readonly format: "mp3" | "wav"; + }; +}; + +type Unsubscribe = () => void; + +type UseLangGraphRuntimeOptions = ExternalStoreSharedOptions & { + onThreadIdChange?: ((threadId: string | undefined) => void) | undefined; + autoCancelPendingToolCalls?: boolean | undefined; + unstable_allowCancellation?: boolean | undefined; + unstable_enableMessageQueue?: boolean | undefined; + stream: LangGraphStreamCallback<LangChainMessage>; + uiStateKey?: string; + getCheckpointId?: (threadId: string, parentMessages: LangChainMessage[]) => Promise<string | null>; + load?: (threadId: string, config?: { + signal: AbortSignal; + }) => Promise<{ + messages: LangChainMessage[]; + interrupts?: LangGraphInterruptState[]; + uiMessages?: UIMessage[]; + }>; + create?: () => Promise<{ + externalId: string; + }>; + delete?: (threadId: string) => Promise<void>; + adapters?: { + attachments?: AttachmentAdapter; + speech?: SpeechSynthesisAdapter; + dictation?: DictationAdapter; + voice?: RealtimeVoiceAdapter; + feedback?: FeedbackAdapter; + } | undefined; + eventHandlers?: { + onMessageChunk?: OnMessageChunkCallback; + onValues?: OnValuesEventCallback; + onUpdates?: OnUpdatesEventCallback; + onSubgraphValues?: OnSubgraphValuesEventCallback; + onSubgraphUpdates?: OnSubgraphUpdatesEventCallback; + onMetadata?: OnMetadataEventCallback; + onInfo?: OnInfoEventCallback; + onError?: OnErrorEventCallback; + onSubgraphError?: OnSubgraphErrorEventCallback; + onCustomEvent?: OnCustomEventCallback; + } | undefined; + uiComponents?: { + fallback?: DataMessagePartComponent; + renderers?: Record<string, DataMessagePartComponent>; + } | undefined; + cloud?: AssistantCloud | undefined; + unstable_threadListAdapter?: RemoteThreadListAdapter | undefined; +}; + +type UserMessageContent = string | UserMessageContentComplex[]; + +type UserMessageContentComplex = MessageContentText | MessageContentImageUrl | MessageContentFile; + +type VoiceSessionState = { + readonly status: RealtimeVoiceAdapter.Status; + readonly isMuted: boolean; + readonly mode: RealtimeVoiceAdapter.Mode; +}; + +declare const appendLangChainChunk: (prev: LangChainMessage | undefined, curr: LangChainMessage | LangChainMessageChunk) => LangChainMessage; + +declare const convertLangChainMessages: useExternalMessageConverter.Callback<LangChainMessage>; + +declare global { + interface Window { + SpeechRecognition?: SpeechRecognitionConstructor; + webkitSpeechRecognition?: SpeechRecognitionConstructor; + } +} + +declare namespace entry_root_exports { + export { CreateLangGraphStreamOptions, LangChainEvent, LangChainMessage, LangChainMessageChunk, LangChainToolCall, LangChainToolCallChunk, LangGraphCommand, LangGraphInterruptState, LangGraphMessageAccumulator, LangGraphMessagesEvent, LangGraphSendMessageConfig, LangGraphStreamCallback, LangGraphStreamClient, LangGraphTupleMetadata, OnCustomEventCallback, OnErrorEventCallback, OnInfoEventCallback, OnMessageChunkCallback, OnMetadataEventCallback, OnSubgraphErrorEventCallback, OnSubgraphUpdatesEventCallback, OnSubgraphValuesEventCallback, OnUpdatesEventCallback, OnValuesEventCallback, RemoveUIMessage, UIMessage, UseLangGraphRuntimeOptions, appendLangChainChunk, convertLangChainMessages, unstable_createLangGraphStream, useLangGraphInterruptState, useLangGraphMessageMetadata, useLangGraphMessages, useLangGraphRuntime, useLangGraphSend, useLangGraphSendCommand, useLangGraphStreamingTiming, useLangGraphUIMessages }; +} + +declare const unstable_createLangGraphStream: (_param3: CreateLangGraphStreamOptions) => LangGraphStreamCallback<LangChainMessage>; + +declare namespace useExternalMessageConverter { + type Message = (ThreadMessageLike & { + readonly convertConfig?: { + readonly joinStrategy?: JoinStrategy; + }; + }) | { + role: "tool"; + toolCallId: string; + toolName?: string | undefined; + result: any; + artifact?: any; + isError?: boolean; + messages?: readonly ThreadMessage[]; + }; + type Metadata = { + readonly toolStatuses?: Record<string, ToolExecutionStatus>; + readonly error?: ReadonlyJSONValue; + readonly messageTiming?: Record<string, MessageTiming>; + }; + type Callback<T> = (message: T, metadata: Metadata) => Message | Message[]; +} + +declare const useExternalMessageConverter: <T extends WeakKey>(_param4: { + callback: useExternalMessageConverter.Callback<T>; + messages: T[]; + isRunning: boolean; + joinStrategy?: JoinStrategy | undefined; + metadata?: useExternalMessageConverter.Metadata | undefined; +}) => ThreadMessage[]; + +declare const useLangGraphInterruptState: () => LangGraphInterruptState | undefined; + +declare const useLangGraphMessageMetadata: () => Map<string, LangGraphTupleMetadata>; + +declare const useLangGraphMessages: <TMessage extends { + id?: string; +}>(_param5: { + stream: LangGraphStreamCallback<TMessage>; + appendMessage?: (prev: TMessage | undefined, curr: TMessage) => TMessage; + uiStateKey?: string; + eventHandlers?: { + onMessageChunk?: OnMessageChunkCallback; + onValues?: OnValuesEventCallback; + onUpdates?: OnUpdatesEventCallback; + onSubgraphValues?: OnSubgraphValuesEventCallback; + onSubgraphUpdates?: OnSubgraphUpdatesEventCallback; + onMetadata?: OnMetadataEventCallback; + onInfo?: OnInfoEventCallback; + onError?: OnErrorEventCallback; + onSubgraphError?: OnSubgraphErrorEventCallback; + onCustomEvent?: OnCustomEventCallback; + }; +}) => { + interrupt: LangGraphInterruptState | undefined; + messages: TMessage[]; + messageMetadata: Map<string, LangGraphTupleMetadata>; + uiMessages: UIMessage[]; + sendMessage: (newMessages: TMessage[], config: LangGraphSendMessageConfig, onComplete?: () => void) => Promise<void>; + cancel: () => void; + setInterrupt: import("react").Dispatch<import("react").SetStateAction<LangGraphInterruptState | undefined>>; + setMessages: (msgs: TMessage[]) => void; + setUIMessages: (next: UIMessage[]) => void; +}; + +declare const useLangGraphRuntime: (_param6: UseLangGraphRuntimeOptions) => AssistantRuntime; + +declare const useLangGraphSend: () => (messages: LangChainMessage[], config: LangGraphSendMessageConfig) => Promise<void>; + +declare const useLangGraphSendCommand: () => (command: LangGraphCommand) => Promise<void>; + +declare const useLangGraphStreamingTiming: (messages: readonly LangChainMessage[], isRunning: boolean) => Record<string, MessageTiming>; + +declare const useLangGraphUIMessages: () => readonly UIMessage<string, Record<string, unknown>>[]; + +export { entry_root_exports as entry_root }; diff --git a/api-surface/assistant-ui__react-lexical.ts b/api-surface/assistant-ui__react-lexical.ts new file mode 100644 index 0000000..5a0d776 --- /dev/null +++ b/api-surface/assistant-ui__react-lexical.ts @@ -0,0 +1,136 @@ +import "@standard-schema/spec"; + +import { DOMConversionMap, DOMExportOutput, DecoratorNode, EditorConfig, LexicalEditor, LexicalNode, NodeKey, SerializedLexicalNode, Spread } from "lexical"; + +import { ComponentPropsWithoutRef, FC, ReactNode } from "react"; + +declare function $createDirectiveNode(item: Unstable_TriggerItem, directiveText?: string | undefined): DirectiveNode; + +declare function $createDirectiveNodeWithFormatter(item: Unstable_TriggerItem, formatter: Unstable_DirectiveFormatter): DirectiveNode; + +declare function $isDirectiveNode(node: LexicalNode | null | undefined): node is DirectiveNode; + +type DirectiveChipProps = { + directiveId: string; + directiveType: string; + label: string; +}; + +declare const DirectiveChipProvider: import("react").Provider<FC<DirectiveChipProps> | null>; + +declare class DirectiveNode extends DecoratorNode<ReactNode> { + __directiveId: string; + __directiveType: string; + __label: string; + __description: string | undefined; + __metadata: Unstable_TriggerItem["metadata"]; + __directiveText: string; + static getType(): string; + static clone(node: DirectiveNode): DirectiveNode; + constructor(item: Unstable_TriggerItem, directiveText: string, key?: NodeKey); + static importJSON(serialized: SerializedDirectiveNode): DirectiveNode; + exportJSON(): SerializedDirectiveNode; + createDOM(_config: EditorConfig): HTMLElement; + updateDOM(): false; + exportDOM(_editor: LexicalEditor): DOMExportOutput; + static importDOM(): DOMConversionMap | null; + getTextContent(): string; + getDirectiveText(): string; + isInline(): boolean; + isIsolated(): boolean; + isKeyboardSelectable(): boolean; + decorate(_editor: LexicalEditor, _config: EditorConfig): ReactNode; + getDirectiveItem(): Unstable_TriggerItem; +} + +declare function DirectivePlugin(_param0?: DirectivePluginProps): null; + +type DirectivePluginProps = { + onDirectiveSelect?: ((item: Unstable_TriggerItem) => void) | undefined; +}; + +declare const LexicalComposerInput: import("react").ForwardRefExoticComponent<Omit<Omit<import("react").DetailedHTMLProps<import("react").HTMLAttributes<HTMLDivElement>, HTMLDivElement>, "ref">, "autoFocus"> & { + submitMode?: "enter" | "ctrlEnter" | "none" | undefined; + cancelOnEscape?: boolean | undefined; + placeholder?: string | undefined; + autoFocus?: boolean | undefined; + directivePluginProps?: DirectivePluginProps | undefined; + directiveChip?: FC<DirectiveChipProps> | undefined; + formatter?: Unstable_DirectiveFormatter | undefined; +} & import("react").RefAttributes<HTMLDivElement>>; + +type LexicalComposerInputProps = Omit<ComponentPropsWithoutRef<"div">, "autoFocus"> & { + submitMode?: "enter" | "ctrlEnter" | "none" | undefined; + cancelOnEscape?: boolean | undefined; + placeholder?: string | undefined; + autoFocus?: boolean | undefined; + directivePluginProps?: DirectivePluginProps | undefined; + directiveChip?: FC<DirectiveChipProps> | undefined; + formatter?: Unstable_DirectiveFormatter | undefined; +}; + +type ReadonlyJSONArray = readonly ReadonlyJSONValue[]; + +type ReadonlyJSONObject = { + readonly [key: string]: ReadonlyJSONValue; +}; + +type ReadonlyJSONValue = null | string | number | boolean | ReadonlyJSONObject | ReadonlyJSONArray; + +type SerializedDirectiveNode = Spread<{ + directiveId: string; + directiveType: string; + label: string; + description?: string | undefined; + metadata?: Unstable_TriggerItem["metadata"]; + directiveText?: string; +}, SerializedLexicalNode>; + +interface SpeechRecognitionConstructor { + new (): SpeechRecognitionInstance; +} + +interface SpeechRecognitionInstance extends EventTarget { + lang: string; + continuous: boolean; + interimResults: boolean; + start(): void; + stop(): void; + abort(): void; +} + +type Unstable_DirectiveFormatter = { + serialize(item: Unstable_TriggerItem): string; + parse(text: string): readonly Unstable_DirectiveSegment[]; +}; + +type Unstable_DirectiveSegment = { + readonly kind: "text"; + readonly text: string; +} | { + readonly kind: "mention"; + readonly type: string; + readonly label: string; + readonly id: string; +}; + +type Unstable_TriggerItem = { + readonly id: string; + readonly type: string; + readonly label: string; + readonly description?: string | undefined; + readonly metadata?: ReadonlyJSONObject | undefined; +}; + +declare global { + interface Window { + SpeechRecognition?: SpeechRecognitionConstructor; + webkitSpeechRecognition?: SpeechRecognitionConstructor; + } +} + +declare namespace entry_root_exports { + export { $createDirectiveNode, $createDirectiveNodeWithFormatter, $isDirectiveNode, DirectiveChipProps, DirectiveChipProvider, DirectiveNode, DirectivePlugin, DirectivePluginProps, LexicalComposerInput, LexicalComposerInputProps }; +} + +export { entry_root_exports as entry_root }; diff --git a/api-surface/assistant-ui__react-markdown.ts b/api-surface/assistant-ui__react-markdown.ts new file mode 100644 index 0000000..ad3a708 --- /dev/null +++ b/api-surface/assistant-ui__react-markdown.ts @@ -0,0 +1,333 @@ +import { Primitive } from "@radix-ui/react-primitive"; + +import "@standard-schema/spec"; + +import "radix-ui"; + +import { ComponentProps, ComponentPropsWithoutRef, ComponentType, ElementType, ForwardRefExoticComponent, RefAttributes } from "react"; + +import { Options } from "react-markdown"; + +import "react-textarea-autosize"; + +import "zustand"; + +type AncestorsOf<K extends ClientNames, Seen extends ClientNames = never> = K extends Seen ? never : ParentOf<K> extends never ? never : ParentOf<K> | AncestorsOf<ParentOf<K>, Seen | K>; + +type AssistantClient = { + [K in ClientNames]: AssistantClientAccessor<K>; +} & { + subscribe(listener: () => void): Unsubscribe; + on<TEvent extends AssistantEventName>(selector: AssistantEventSelector<TEvent>, callback: AssistantEventCallback<TEvent>): Unsubscribe; +}; + +type AssistantClientAccessor<K extends ClientNames> = (() => ClientSchemas[K]["methods"]) & (ClientMeta<K> | { + source: "root"; + query: Record<string, never>; +} | { + source: null; + query: null; +}) & { + name: K; +}; + +type AssistantEventCallback<TEvent extends AssistantEventName> = (payload: AssistantEventPayload[TEvent]) => void; + +type AssistantEventName = keyof AssistantEventPayload; + +type AssistantEventPayload = ClientEventMap & { + "*": WildcardPayload; +}; + +type AssistantEventScope<TEvent extends AssistantEventName> = "*" | EventSource<TEvent> | (EventSource<TEvent> extends ClientNames ? AncestorsOf<EventSource<TEvent>> : never); + +type AssistantEventSelector<TEvent extends AssistantEventName> = TEvent | { + scope: AssistantEventScope<TEvent>; + event: TEvent; +}; + +type ClientError<E extends string> = { + methods: Record<E, () => E>; + meta: { + source: ClientNames; + query: Record<E, E>; + }; + events: Record<`${E}.`, E>; +}; + +type ClientEventMap = UnionToIntersection<{ + [K in ClientNames]: ClientEvents<K>; +}[ClientNames]>; + +type ClientEvents<K extends ClientNames> = "events" extends keyof ClientSchemas[K] ? ClientSchemas[K]["events"] extends ClientEventsType<K> ? ClientSchemas[K]["events"] : never : never; + +type ClientEventsType<K extends ClientNames> = Record<`${K}.${string}`, unknown>; + +type ClientMeta<K extends ClientNames> = "meta" extends keyof ClientSchemas[K] ? Pick<ClientSchemas[K]["meta"] extends ClientMetaType ? ClientSchemas[K]["meta"] : never, "query" | "source"> : never; + +type ClientMetaType = { + source: ClientNames; + query: Record<string, unknown>; +}; + +interface ClientMethods { + [key: string | symbol]: (...args: any[]) => any; +} + +type ClientNames = keyof ClientSchemas extends (infer U) ? U : never; + +type ClientSchemas = keyof ScopeRegistry extends never ? { + "ERROR: No clients were defined": ClientError<"ERROR: No clients were defined">; +} : { + [K in keyof ScopeRegistry]: ValidateClient<K>; +}; + +type CodeComponent = ComponentType<ComponentPropsWithoutRef<"code"> & { + node?: Element | undefined; +}>; + +type CodeHeaderProps = { + node?: Element | undefined; + language: string | undefined; + code: string; +}; + +interface Comment extends Literal { + type: "comment"; + data?: CommentData | undefined; +} + +interface CommentData extends Data { +} + +type Components = { + [Key in Extract<ElementType, string>]?: ComponentType<ComponentProps<Key>>; +} & { + SyntaxHighlighter?: ComponentType<Omit<SyntaxHighlighterProps, "node">> | undefined; + CodeHeader?: ComponentType<Omit<CodeHeaderProps, "node">> | undefined; +}; + +interface Data extends Data$1 { +} + +interface Data$1 { +} + +interface DevToolsApiEntry { + api: Partial<AssistantClient>; + logs: EventLog[]; +} + +interface DevToolsHook { + apis: Map<number, DevToolsApiEntry>; + nextId: number; + listeners: Set<(apiId: number) => void>; +} + +interface Doctype extends Node$1 { + type: "doctype"; + data?: DoctypeData | undefined; +} + +interface DoctypeData extends Data { +} + +interface Element extends Parent { + type: "element"; + tagName: string; + properties: Properties; + children: ElementContent[]; + content?: Root | undefined; + data?: ElementData | undefined; +} + +type ElementContent = ElementContentMap[keyof ElementContentMap]; + +interface ElementContentMap { + comment: Comment; + element: Element; + text: Text; +} + +interface ElementData extends Data { +} + +interface EventLog { + time: Date; + event: string; + data: unknown; +} + +type EventSource<T extends AssistantEventName> = T extends `${infer Source}.${string}` ? Source : never; + +interface Literal extends Node { + value: string; +} + +declare const MarkdownTextPrimitive: ForwardRefExoticComponent<MarkdownTextPrimitiveProps> & RefAttributes<HTMLDivElement>; + +type MarkdownTextPrimitiveProps = Omit<Options, "children" | "components"> & { + className?: string | undefined; + containerProps?: Omit<PrimitiveDivProps, "asChild" | "children"> | undefined; + containerComponent?: ElementType | undefined; + components?: (NonNullable<Options["components"]> & { + SyntaxHighlighter?: ComponentType<SyntaxHighlighterProps> | undefined; + CodeHeader?: ComponentType<CodeHeaderProps> | undefined; + }) | undefined; + componentsByLanguage?: Record<string, { + CodeHeader?: ComponentType<CodeHeaderProps> | undefined; + SyntaxHighlighter?: ComponentType<SyntaxHighlighterProps> | undefined; + }> | undefined; + smooth?: boolean | SmoothOptions | undefined; + defer?: boolean | undefined; + preprocess?: (text: string) => string; +}; + +interface Node extends Node$1 { + data?: Data | undefined; +} + +interface Node$1 { + type: string; + data?: Data$1 | undefined; + position?: Position | undefined; +} + +interface Parent extends Node { + children: RootContent[]; +} + +type ParentOf<K extends ClientNames> = AssistantClientAccessor<K> extends { + source: infer S; +} ? S extends ClientNames ? S : never : never; + +interface Point { + line: number; + column: number; + offset?: number | undefined; +} + +interface Position { + start: Point; + end: Point; +} + +type PreComponent = ComponentType<ComponentPropsWithoutRef<"pre"> & { + node?: Element | undefined; +}>; + +type PrimitiveDivProps = ComponentPropsWithoutRef<typeof Primitive.div>; + +interface Properties { + [PropertyName: string]: boolean | number | string | null | undefined | Array<string | number>; +} + +interface Root extends Parent { + type: "root"; + children: RootContent[]; + data?: RootData | undefined; +} + +type RootContent = RootContentMap[keyof RootContentMap]; + +interface RootContentMap { + comment: Comment; + doctype: Doctype; + element: Element; + text: Text; +} + +interface RootData extends Data { +} + +interface ScopeRegistry { + [key: string]: { methods: any; meta?: any; events?: any }; +} + +type SmoothOptions = { + drainMs?: number | undefined; + maxCharIntervalMs?: number | undefined; + maxCharsPerFrame?: number | undefined; + minCommitMs?: number | undefined; +}; + +interface SpeechRecognitionConstructor { + new (): SpeechRecognitionInstance; +} + +interface SpeechRecognitionInstance extends EventTarget { + lang: string; + continuous: boolean; + interimResults: boolean; + start(): void; + stop(): void; + abort(): void; +} + +type SyntaxHighlighterProps = { + node?: Element | undefined; + components: { + Pre: PreComponent; + Code: CodeComponent; + }; + language: string; + code: string; +}; + +interface Text extends Literal { + type: "text"; + data?: TextData | undefined; +} + +interface TextData extends Data { +} + +type UnionToIntersection<U> = (U extends unknown ? (x: U) => void : never) extends ((x: infer I) => void) ? I : never; + +type Unsubscribe = () => void; + +type ValidateClient<K extends keyof ScopeRegistry> = ScopeRegistry[K] extends { + methods: ClientMethods; +} ? "meta" extends keyof ScopeRegistry[K] ? ScopeRegistry[K]["meta"] extends ClientMetaType ? "events" extends keyof ScopeRegistry[K] ? ScopeRegistry[K]["events"] extends ClientEventsType<K> ? ScopeRegistry[K] : ClientError<`ERROR: ${K & string} has invalid events type`> : ScopeRegistry[K] : ClientError<`ERROR: ${K & string} has invalid meta type`> : "events" extends keyof ScopeRegistry[K] ? ScopeRegistry[K]["events"] extends ClientEventsType<K> ? ScopeRegistry[K] : ClientError<`ERROR: ${K & string} has invalid events type`> : ScopeRegistry[K] : ClientError<`ERROR: ${K & string} has invalid methods type`>; + +type WildcardPayload = { + [K in keyof ClientEventMap]: { + event: K; + payload: ClientEventMap[K]; + }; +}[Extract<keyof ClientEventMap, string>]; + +declare function escapeCurrencyDollars(text: string): string; + +declare global { + interface Window { + SpeechRecognition?: SpeechRecognitionConstructor; + webkitSpeechRecognition?: SpeechRecognitionConstructor; + } +} + +declare global { + interface Window { + __ASSISTANT_UI_DEVTOOLS_HOOK__?: any; + } +} + +declare namespace entry_root_exports { + export { CodeHeaderProps, MarkdownTextPrimitive, MarkdownTextPrimitiveProps, SyntaxHighlighterProps, escapeCurrencyDollars, normalizeMathDelimiters, rewriteCustomMathTags, rewriteLatexBracketDelimiters, memoizeMarkdownComponents as unstable_memoizeMarkdownComponents, useIsMarkdownCodeBlock }; +} + +declare const memoizeMarkdownComponents: (components?: Components) => { + [k: string]: import("react").MemoExoticComponent<(_param0: { + node?: Element; + }) => import("react").JSX.Element> | undefined; +}; + +declare function normalizeMathDelimiters(text: string): string; + +declare function rewriteCustomMathTags(text: string): string; + +declare function rewriteLatexBracketDelimiters(text: string): string; + +declare const useIsMarkdownCodeBlock: () => boolean; + +export { entry_root_exports as entry_root }; diff --git a/api-surface/assistant-ui__react-mcp.ts b/api-surface/assistant-ui__react-mcp.ts new file mode 100644 index 0000000..cb617ac --- /dev/null +++ b/api-surface/assistant-ui__react-mcp.ts @@ -0,0 +1,504 @@ +import { OAuthClientInformationFull, OAuthTokens } from "@modelcontextprotocol/sdk/shared/auth.js"; + +import { Primitive } from "@radix-ui/react-primitive"; + +import { ComponentPropsWithoutRef, ComponentRef, FC, PropsWithChildren, ReactNode } from "react"; + +interface ScopeRegistry { + mcp: { + methods: MCPManagerMethods; + }; + mcpServer: { + methods: MCPServerMethods; + meta: { + source: "mcp"; + query: MCPServerQuery; + }; + }; +} + +type AddFormAuthType = MCPAuthConfig["type"]; + +type ClientError<E extends string> = { + methods: Record<E, () => E>; + meta: { + source: ClientNames; + query: Record<E, E>; + }; + events: Record<`${E}.`, E>; +}; + +type ClientEventsType<K extends ClientNames> = Record<`${K}.${string}`, unknown>; + +type ClientMetaType = { + source: ClientNames; + query: Record<string, unknown>; +}; + +interface ClientMethods { + [key: string | symbol]: (...args: any[]) => any; +} + +type ClientNames = keyof ClientSchemas extends (infer U) ? U : never; + +type ClientOutput<K extends ClientNames> = ClientSchemas[K]["methods"] & ClientMethods; + +type ClientSchemas = keyof ScopeRegistry extends never ? { + "ERROR: No clients were defined": ClientError<"ERROR: No clients were defined">; +} : { + [K in keyof ScopeRegistry]: ValidateClient<K>; +}; + +type MCPAuthConfig = { + type: "none"; +} | { + type: "bearer"; + token?: string | undefined; +} | { + type: "oauth"; + scopes?: string[] | undefined; + authorizationEndpoint?: string | undefined; + tokenEndpoint?: string | undefined; + registrationEndpoint?: string | undefined; + clientId?: string | undefined; + clientSecret?: string | undefined; +}; + +type MCPConnectionState = "authPending" | "authRequired" | "connected" | "connecting" | "disconnected" | "error"; + +type MCPConnector = { + id: string; + name: string; + url: string; + icon?: string | undefined; + auth: MCPAuthConfig; + connectionTimeout?: number | undefined; +}; + +type MCPCustomServerRecord = { + id: string; + name: string; + url: string; + auth: MCPAuthConfig; + connectionTimeout?: number | undefined; + createdAt: number; +}; + +type MCPManagerMethods = { + getState: () => MCPManagerState; + server: (query: MCPServerQuery) => MCPServerMethods; + connector: (query: { + index: number; + }) => MCPServerMethods; + customServer: (query: { + index: number; + }) => MCPServerMethods; + addCustomServer: (input: { + name: string; + url: string; + auth: MCPAuthConfig; + connectionTimeout?: number | undefined; + }) => Promise<string>; + removeServer: (id: string) => Promise<void>; +}; + +type MCPManagerState = { + servers: MCPServerState[]; + connectors: MCPServerState[]; + customServers: MCPServerState[]; + isHydrated: boolean; +}; + +type MCPPersistedAuthState = { + tokens?: OAuthTokens; + clientInformation?: OAuthClientInformationFull; + codeVerifier?: string; + token?: string; +}; + +type MCPServerKind = "connector" | "custom"; + +type MCPServerMethods = { + getState: () => MCPServerState; + connect: () => Promise<void>; + disconnect: () => Promise<void>; + remove: () => Promise<void>; + callTool: (name: string, args: unknown) => Promise<unknown>; + listResources: () => Promise<unknown>; + readResource: (uri: string) => Promise<unknown>; + completeAuth: (callbackUrl: string) => Promise<void>; +}; + +type MCPServerQuery = { + id: string; +} | { + kind: "connector"; + index: number; +} | { + kind: "custom"; + index: number; +}; + +type MCPServerState = { + id: string; + kind: MCPServerKind; + name: string; + url: string; + icon?: string | undefined; + connectionState: MCPConnectionState; + lastError: { + message: string; + } | null; + tools: MCPToolInfo[]; + authorizationUrl: string | null; +}; + +type MCPStorage = { + loadCustomServers: () => Promise<MCPCustomServerRecord[]>; + saveCustomServers: (records: MCPCustomServerRecord[]) => Promise<void>; + loadAuthState: (serverId: string) => Promise<MCPPersistedAuthState | null>; + saveAuthState: (serverId: string, state: MCPPersistedAuthState) => Promise<void>; + clearAuthState: (serverId: string) => Promise<void>; +}; + +type MCPStorageElement = ResourceElement<MCPStorage>; + +type MCPToolInfo = { + name: string; + description?: string | undefined; + inputSchema: unknown; +}; + +declare namespace McpAddFormPrimitiveAuthFields { + type Props = { + children?: FC<{ + authType: AddFormAuthType; + }>; + }; +} + +declare const McpAddFormPrimitiveAuthFields: FC<McpAddFormPrimitiveAuthFields.Props>; + +declare namespace McpAddFormPrimitiveAuthSelect { + type Element = ComponentRef<typeof Primitive.select>; + type Props = Omit<ComponentPropsWithoutRef<typeof Primitive.select>, "onChange" | "value">; +} + +declare const McpAddFormPrimitiveAuthSelect: import("react").ForwardRefExoticComponent<McpAddFormPrimitiveAuthSelect.Props & import("react").RefAttributes<HTMLSelectElement>>; + +declare namespace McpAddFormPrimitiveCancel { + type Element = ComponentRef<typeof Primitive.button>; + type Props = ComponentPropsWithoutRef<typeof Primitive.button>; +} + +declare const McpAddFormPrimitiveCancel: import("react").ForwardRefExoticComponent<Omit<import("react").ClassAttributes<HTMLButtonElement> & import("react").ButtonHTMLAttributes<HTMLButtonElement> & { + asChild?: boolean; +}, "ref"> & import("react").RefAttributes<HTMLButtonElement>>; + +declare namespace McpAddFormPrimitiveError { + type Element = ComponentRef<typeof Primitive.div>; + type Props = ComponentPropsWithoutRef<typeof Primitive.div>; +} + +declare const McpAddFormPrimitiveError: import("react").ForwardRefExoticComponent<Omit<import("react").ClassAttributes<HTMLDivElement> & import("react").HTMLAttributes<HTMLDivElement> & { + asChild?: boolean; +}, "ref"> & import("react").RefAttributes<HTMLDivElement>>; + +declare namespace McpAddFormPrimitiveNameField { + type Element = ComponentRef<typeof Primitive.input>; + type Props = Omit<ComponentPropsWithoutRef<typeof Primitive.input>, "onChange" | "type" | "value">; +} + +declare const McpAddFormPrimitiveNameField: import("react").ForwardRefExoticComponent<McpAddFormPrimitiveNameField.Props & import("react").RefAttributes<HTMLInputElement>>; + +declare namespace McpAddFormPrimitiveRoot { + type Element = ComponentRef<typeof Primitive.form>; + type Props = Omit<ComponentPropsWithoutRef<typeof Primitive.form>, "onSubmit"> & { + onSubmitted?: (id: string) => void; + onCancel?: () => void; + }; +} + +declare const McpAddFormPrimitiveRoot: import("react").ForwardRefExoticComponent<Omit<Omit<import("react").ClassAttributes<HTMLFormElement> & import("react").FormHTMLAttributes<HTMLFormElement> & { + asChild?: boolean; +}, "ref">, "onSubmit"> & { + onSubmitted?: (id: string) => void; + onCancel?: () => void; +} & import("react").RefAttributes<HTMLFormElement>>; + +declare namespace McpAddFormPrimitiveSubmit { + type Element = ComponentRef<typeof Primitive.button>; + type Props = ComponentPropsWithoutRef<typeof Primitive.button>; +} + +declare const McpAddFormPrimitiveSubmit: import("react").ForwardRefExoticComponent<Omit<import("react").ClassAttributes<HTMLButtonElement> & import("react").ButtonHTMLAttributes<HTMLButtonElement> & { + asChild?: boolean; +}, "ref"> & import("react").RefAttributes<HTMLButtonElement>>; + +declare namespace McpAddFormPrimitiveUrlField { + type Element = ComponentRef<typeof Primitive.input>; + type Props = Omit<ComponentPropsWithoutRef<typeof Primitive.input>, "onChange" | "type" | "value">; +} + +declare const McpAddFormPrimitiveUrlField: import("react").ForwardRefExoticComponent<McpAddFormPrimitiveUrlField.Props & import("react").RefAttributes<HTMLInputElement>>; + +declare const McpConnectorByIndexProvider: FC<PropsWithChildren<{ + index: number; +}>>; + +declare const McpCustomServerByIndexProvider: FC<PropsWithChildren<{ + index: number; +}>>; + +declare const McpCustomStorage: Resource<MCPStorage, [ + impl: MCPStorage +]>; + +declare const McpLocalStorage: Resource<MCPStorage, [ + opts?: McpLocalStorageOptions | undefined +]>; + +type McpLocalStorageOptions = { + keyPrefix?: string; + storage?: Storage; +}; + +declare namespace McpManagerPrimitiveAddCustomTrigger { + type Element = ComponentRef<typeof Primitive.button>; + type Props = ComponentPropsWithoutRef<typeof Primitive.button>; +} + +declare const McpManagerPrimitiveAddCustomTrigger: import("react").ForwardRefExoticComponent<Omit<import("react").ClassAttributes<HTMLButtonElement> & import("react").ButtonHTMLAttributes<HTMLButtonElement> & { + asChild?: boolean; +}, "ref"> & import("react").RefAttributes<HTMLButtonElement>>; + +declare namespace McpManagerPrimitiveConnectors { + type Props = { + children: (value: { + server: MCPServerState; + }) => ReactNode; + }; +} + +declare const McpManagerPrimitiveConnectors: FC<McpManagerPrimitiveConnectors.Props>; + +declare namespace McpManagerPrimitiveCustomServers { + type Props = { + children: (value: { + server: MCPServerState; + }) => ReactNode; + }; +} + +declare const McpManagerPrimitiveCustomServers: FC<McpManagerPrimitiveCustomServers.Props>; + +declare namespace McpManagerPrimitiveRoot { + type Element = ComponentRef<typeof Primitive.div>; + type Props = ComponentPropsWithoutRef<typeof Primitive.div>; +} + +declare const McpManagerPrimitiveRoot: import("react").ForwardRefExoticComponent<Omit<import("react").ClassAttributes<HTMLDivElement> & import("react").HTMLAttributes<HTMLDivElement> & { + asChild?: boolean; +}, "ref"> & import("react").RefAttributes<HTMLDivElement>>; + +declare const McpManagerResource: Resource<ClientOutput<"mcp">, [ + props: McpManagerResourceProps +]>; + +type McpManagerResourceProps = { + connectors?: MCPConnector[] | undefined; + storage?: MCPStorageElement | undefined; + oauthRedirectUri?: string | undefined; + autoConnect?: boolean | undefined; + connectionTimeout?: number | undefined; +}; + +declare const McpMemoryStorage: Resource<MCPStorage, [ +]>; + +declare const McpOAuthCallback: FC<UseMcpOAuthCallbackOptions & { + children?: (result: UseMcpOAuthCallbackResult) => ReactNode; +}>; + +declare const McpServerByIdProvider: FC<PropsWithChildren<{ + id: string; +}>>; + +declare namespace McpServerPrimitiveConnectButton { + type Element = ComponentRef<typeof Primitive.button>; + type Props = ComponentPropsWithoutRef<typeof Primitive.button>; +} + +declare const McpServerPrimitiveConnectButton: import("react").ForwardRefExoticComponent<Omit<import("react").ClassAttributes<HTMLButtonElement> & import("react").ButtonHTMLAttributes<HTMLButtonElement> & { + asChild?: boolean; +}, "ref"> & import("react").RefAttributes<HTMLButtonElement>>; + +declare namespace McpServerPrimitiveDisconnectButton { + type Element = ComponentRef<typeof Primitive.button>; + type Props = ComponentPropsWithoutRef<typeof Primitive.button>; +} + +declare const McpServerPrimitiveDisconnectButton: import("react").ForwardRefExoticComponent<Omit<import("react").ClassAttributes<HTMLButtonElement> & import("react").ButtonHTMLAttributes<HTMLButtonElement> & { + asChild?: boolean; +}, "ref"> & import("react").RefAttributes<HTMLButtonElement>>; + +declare namespace McpServerPrimitiveError { + type Element = ComponentRef<typeof Primitive.div>; + type Props = ComponentPropsWithoutRef<typeof Primitive.div>; +} + +declare const McpServerPrimitiveError: import("react").ForwardRefExoticComponent<Omit<import("react").ClassAttributes<HTMLDivElement> & import("react").HTMLAttributes<HTMLDivElement> & { + asChild?: boolean; +}, "ref"> & import("react").RefAttributes<HTMLDivElement>>; + +declare namespace McpServerPrimitiveIcon { + type Element = ComponentRef<typeof Primitive.img>; + type Props = Omit<ComponentPropsWithoutRef<typeof Primitive.img>, "alt" | "src"> & { + src?: string; + alt?: string; + }; +} + +declare const McpServerPrimitiveIcon: import("react").ForwardRefExoticComponent<Omit<Omit<import("react").ClassAttributes<HTMLImageElement> & import("react").ImgHTMLAttributes<HTMLImageElement> & { + asChild?: boolean; +}, "ref">, "alt" | "src"> & { + src?: string; + alt?: string; +} & import("react").RefAttributes<HTMLImageElement>>; + +declare namespace McpServerPrimitiveName { + type Element = ComponentRef<typeof Primitive.span>; + type Props = ComponentPropsWithoutRef<typeof Primitive.span>; +} + +declare const McpServerPrimitiveName: import("react").ForwardRefExoticComponent<Omit<import("react").ClassAttributes<HTMLSpanElement> & import("react").HTMLAttributes<HTMLSpanElement> & { + asChild?: boolean; +}, "ref"> & import("react").RefAttributes<HTMLSpanElement>>; + +declare namespace McpServerPrimitiveOAuthLink { + type Element = ComponentRef<typeof Primitive.a>; + type Props = Omit<ComponentPropsWithoutRef<typeof Primitive.a>, "href"> & { + href?: string; + }; +} + +declare const McpServerPrimitiveOAuthLink: import("react").ForwardRefExoticComponent<Omit<Omit<import("react").ClassAttributes<HTMLAnchorElement> & import("react").AnchorHTMLAttributes<HTMLAnchorElement> & { + asChild?: boolean; +}, "ref">, "href"> & { + href?: string; +} & import("react").RefAttributes<HTMLAnchorElement>>; + +declare namespace McpServerPrimitiveRemoveButton { + type Element = ComponentRef<typeof Primitive.button>; + type Props = ComponentPropsWithoutRef<typeof Primitive.button>; +} + +declare const McpServerPrimitiveRemoveButton: import("react").ForwardRefExoticComponent<Omit<import("react").ClassAttributes<HTMLButtonElement> & import("react").ButtonHTMLAttributes<HTMLButtonElement> & { + asChild?: boolean; +}, "ref"> & import("react").RefAttributes<HTMLButtonElement>>; + +declare namespace McpServerPrimitiveRoot { + type Element = ComponentRef<typeof Primitive.div>; + type Props = ComponentPropsWithoutRef<typeof Primitive.div>; +} + +declare const McpServerPrimitiveRoot: import("react").ForwardRefExoticComponent<Omit<import("react").ClassAttributes<HTMLDivElement> & import("react").HTMLAttributes<HTMLDivElement> & { + asChild?: boolean; +}, "ref"> & import("react").RefAttributes<HTMLDivElement>>; + +declare namespace McpServerPrimitiveStatus { + type Element = ComponentRef<typeof Primitive.span>; + type Props = ComponentPropsWithoutRef<typeof Primitive.span>; +} + +declare const McpServerPrimitiveStatus: import("react").ForwardRefExoticComponent<Omit<import("react").ClassAttributes<HTMLSpanElement> & import("react").HTMLAttributes<HTMLSpanElement> & { + asChild?: boolean; +}, "ref"> & import("react").RefAttributes<HTMLSpanElement>>; + +declare namespace McpServerPrimitiveToolName { + type Element = ComponentRef<typeof Primitive.span>; + type Props = ComponentPropsWithoutRef<typeof Primitive.span>; +} + +declare const McpServerPrimitiveToolName: import("react").ForwardRefExoticComponent<Omit<import("react").ClassAttributes<HTMLSpanElement> & import("react").HTMLAttributes<HTMLSpanElement> & { + asChild?: boolean; +}, "ref"> & import("react").RefAttributes<HTMLSpanElement>>; + +declare namespace McpServerPrimitiveTools { + type Props = { + children: ReactNode | ((tool: MCPToolInfo) => ReactNode); + }; +} + +declare const McpServerPrimitiveTools: FC<McpServerPrimitiveTools.Props>; + +declare const McpServerResource: Resource<ClientOutput<"mcpServer">, [ + props: McpServerResourceProps +]>; + +type McpServerResourceProps = { + id: string; + kind: MCPServerKind; + name: string; + url: string; + icon?: string | undefined; + auth: MCPAuthConfig; + storage: MCPStorage; + redirectUri: string; + autoConnect: boolean; + connectionTimeout?: number | undefined; + onRemove: () => Promise<void>; +}; + +type Resource<R, A extends readonly unknown[] = any[]> = (...args: A) => ResourceElement<R, A>; + +type ResourceElement<R, A extends readonly unknown[] = any[]> = { + readonly hook: (...args: A) => R; + readonly args: Readonly<A>; + readonly key?: string | number; + readonly deps?: readonly unknown[]; +}; + +interface ScopeRegistry { + [key: string]: { methods: any; meta?: any; events?: any }; +} + +type UseMcpOAuthCallbackOptions = { + url?: string; + onComplete?: (serverId: string) => void; + onError?: (err: Error) => void; +}; + +type UseMcpOAuthCallbackResult = { + status: "done" | "error" | "idle" | "running"; + serverId: string | null; + error: Error | null; +}; + +type ValidateClient<K extends keyof ScopeRegistry> = ScopeRegistry[K] extends { + methods: ClientMethods; +} ? "meta" extends keyof ScopeRegistry[K] ? ScopeRegistry[K]["meta"] extends ClientMetaType ? "events" extends keyof ScopeRegistry[K] ? ScopeRegistry[K]["events"] extends ClientEventsType<K> ? ScopeRegistry[K] : ClientError<`ERROR: ${K & string} has invalid events type`> : ScopeRegistry[K] : ClientError<`ERROR: ${K & string} has invalid meta type`> : "events" extends keyof ScopeRegistry[K] ? ScopeRegistry[K]["events"] extends ClientEventsType<K> ? ScopeRegistry[K] : ClientError<`ERROR: ${K & string} has invalid events type`> : ScopeRegistry[K] : ClientError<`ERROR: ${K & string} has invalid methods type`>; + +declare namespace addForm_d_exports { + export { McpAddFormPrimitiveAuthFields as AuthFields, McpAddFormPrimitiveAuthSelect as AuthSelect, McpAddFormPrimitiveCancel as Cancel, McpAddFormPrimitiveError as Error, McpAddFormPrimitiveNameField as NameField, McpAddFormPrimitiveRoot as Root, McpAddFormPrimitiveSubmit as Submit, McpAddFormPrimitiveUrlField as UrlField }; +} + +declare function defineConnector(connector: MCPConnector): MCPConnector; + +declare namespace entry_root_exports { + export { MCPAuthConfig, MCPConnectionState, MCPConnector, MCPCustomServerRecord, MCPManagerMethods, MCPManagerState, MCPPersistedAuthState, MCPServerKind, MCPServerMethods, MCPServerQuery, MCPServerState, MCPStorage, MCPStorageElement, MCPToolInfo, addForm_d_exports as McpAddFormPrimitive, McpConnectorByIndexProvider, McpCustomServerByIndexProvider, McpCustomStorage, McpLocalStorage, McpLocalStorageOptions, manager_d_exports as McpManagerPrimitive, McpManagerResource, McpManagerResourceProps, McpMemoryStorage, McpOAuthCallback, McpServerByIdProvider, server_d_exports as McpServerPrimitive, McpServerResource, McpServerResourceProps, UseMcpOAuthCallbackOptions, UseMcpOAuthCallbackResult, defineConnector, useMcpOAuthCallback }; +} + +declare namespace manager_d_exports { + export { McpManagerPrimitiveAddCustomTrigger as AddCustomTrigger, McpManagerPrimitiveConnectors as Connectors, McpManagerPrimitiveCustomServers as CustomServers, McpManagerPrimitiveRoot as Root }; +} + +declare namespace server_d_exports { + export { McpServerPrimitiveConnectButton as ConnectButton, McpServerPrimitiveDisconnectButton as DisconnectButton, McpServerPrimitiveError as Error, McpServerPrimitiveIcon as Icon, McpServerPrimitiveName as Name, McpServerPrimitiveOAuthLink as OAuthLink, McpServerPrimitiveRemoveButton as RemoveButton, McpServerPrimitiveRoot as Root, McpServerPrimitiveStatus as Status, McpServerPrimitiveToolName as ToolName, McpServerPrimitiveTools as Tools, useMcpServerTool }; +} + +declare function useMcpOAuthCallback(opts?: UseMcpOAuthCallbackOptions): UseMcpOAuthCallbackResult; + +declare const useMcpServerTool: () => MCPToolInfo; + +export { entry_root_exports as entry_root }; diff --git a/api-surface/assistant-ui__react-native.ts b/api-surface/assistant-ui__react-native.ts new file mode 100644 index 0000000..7a0f631 --- /dev/null +++ b/api-surface/assistant-ui__react-native.ts @@ -0,0 +1,4118 @@ +import { StandardSchemaV1 } from "@standard-schema/spec"; + +import React, { ComponentType, FC, PropsWithChildren, ReactElement, ReactNode } from "react"; + +import { FlatList, FlatListProps, PressableProps, TextInputProps, TextProps, ViewProps } from "react-native"; + +declare const ActionBarCopy: (_param0: ActionBarCopyProps) => import("react").JSX.Element; + +type ActionBarCopyProps = Omit<PressableProps, "children" | "onPress"> & UseActionBarCopyOptions & { + children: ReactNode | ((props: { + isCopied: boolean; + }) => ReactNode); +}; + +declare const ActionBarEdit: (_param1: ActionBarEditProps) => import("react").JSX.Element; + +type ActionBarEditProps = Omit<PressableProps, "onPress"> & { + children: ReactNode; +}; + +declare const ActionBarFeedbackNegative: (_param2: ActionBarFeedbackNegativeProps) => import("react").JSX.Element; + +type ActionBarFeedbackNegativeProps = Omit<PressableProps, "children" | "onPress"> & { + children: ReactNode | ((props: { + isSubmitted: boolean; + }) => ReactNode); +}; + +declare const ActionBarFeedbackPositive: (_param3: ActionBarFeedbackPositiveProps) => import("react").JSX.Element; + +type ActionBarFeedbackPositiveProps = Omit<PressableProps, "children" | "onPress"> & { + children: ReactNode | ((props: { + isSubmitted: boolean; + }) => ReactNode); +}; + +declare const ActionBarReload: (_param4: ActionBarReloadProps) => import("react").JSX.Element; + +type ActionBarReloadProps = Omit<PressableProps, "onPress"> & { + children: ReactNode; +}; + +type AddToolResultOptions = { + messageId: string; + toolName: string; + toolCallId: string; + result: ReadonlyJSONValue; + isError: boolean; + artifact?: ReadonlyJSONValue | undefined; + modelContent?: readonly ToolModelContentPart[] | undefined; +}; + +type AncestorsOf<K extends ClientNames, Seen extends ClientNames = never> = K extends Seen ? never : ParentOf<K> extends never ? never : ParentOf<K> | AncestorsOf<ParentOf<K>, Seen | K>; + +type AppendMessage = Omit<ThreadMessage, "id"> & { + parentId: string | null; + sourceId: string | null; + runConfig: RunConfig | undefined; + startRun?: boolean | undefined; + steer?: boolean | undefined; +}; + +type AsNumber<K> = K extends `${infer N extends number}` ? N | K : never; + +type AssistantClient = { + [K in ClientNames]: AssistantClientAccessor<K>; +} & { + subscribe(listener: () => void): Unsubscribe; + on<TEvent extends AssistantEventName>(selector: AssistantEventSelector<TEvent>, callback: AssistantEventCallback<TEvent>): Unsubscribe; +}; + +type AssistantClientAccessor<K extends ClientNames> = (() => ClientSchemas[K]["methods"]) & (ClientMeta<K> | { + source: "root"; + query: Record<string, never>; +} | { + source: null; + query: null; +}) & { + name: K; +}; + +declare class AssistantCloud { + readonly threads: AssistantCloudThreads; + readonly auth: { + tokens: AssistantCloudAuthTokens; + }; + readonly runs: AssistantCloudRuns; + readonly files: AssistantCloudFiles; + readonly telemetry: AssistantCloudTelemetryConfig; + constructor(config: AssistantCloudConfig); +} + +declare class AssistantCloudAPI { + _auth: AssistantCloudAuthStrategy; + _baseUrl: string; + constructor(config: AssistantCloudConfig); + initializeAuth(): Promise<boolean>; + makeRawRequest(endpoint: string, options?: MakeRequestOptions): Promise<Response>; + makeRequest(endpoint: string, options?: MakeRequestOptions): Promise<any>; +} + +type AssistantCloudAuthStrategy = { + readonly strategy: "anon" | "api-key" | "jwt"; + getAuthHeaders(): Promise<Record<string, string> | false>; + readAuthHeaders(headers: Headers): void; +}; + +declare class AssistantCloudAuthTokens { + private cloud; + constructor(cloud: AssistantCloudAPI); + create(): Promise<AssistantCloudAuthTokensCreateResponse>; +} + +type AssistantCloudAuthTokensCreateResponse = { + token: string; +}; + +type AssistantCloudConfig = ({ + baseUrl: string; + authToken: () => Promise<string | null>; +} | { + baseUrl?: string; + apiKey: string; + userId: string; + workspaceId: string; +} | { + baseUrl: string; + anonymous: true; +}) & { + telemetry?: boolean | AssistantCloudTelemetryConfig; +}; + +declare class AssistantCloudFiles { + private cloud; + constructor(cloud: AssistantCloudAPI); + pdfToImages(body: PdfToImagesRequestBody): Promise<PdfToImagesResponse>; + generatePresignedUploadUrl(body: GeneratePresignedUploadUrlRequestBody): Promise<GeneratePresignedUploadUrlResponse>; +} + +type AssistantCloudMessageCreateResponse = { + message_id: string; +}; + +type AssistantCloudRunReport = { + thread_id: string; + status: "completed" | "error" | "incomplete"; + total_steps?: number; + tool_calls?: ReportToolCall[]; + steps?: { + input_tokens?: number; + output_tokens?: number; + reasoning_tokens?: number; + cached_input_tokens?: number; + tool_calls?: ReportToolCall[]; + start_ms?: number; + end_ms?: number; + }[]; + input_tokens?: number; + output_tokens?: number; + reasoning_tokens?: number; + cached_input_tokens?: number; + model_id?: string; + provider_type?: string; + duration_ms?: number; + output_text?: string; + metadata?: Record<string, unknown>; +}; + +declare class AssistantCloudRuns { + private cloud; + constructor(cloud: AssistantCloudAPI); + __internal_getAssistantOptions(assistantId: string): { + api: string; + headers: () => Promise<{ + Accept: string; + }>; + body: { + assistant_id: string; + response_format: string; + thread_id: string; + }; + }; + stream(body: AssistantCloudRunsStreamBody): Promise<AssistantStream>; + report(body: AssistantCloudRunReport): Promise<{ + run_id: string; + }>; +} + +type AssistantCloudRunsStreamBody = { + thread_id: string; + assistant_id: "system/thread_title"; + messages: readonly unknown[]; +}; + +type AssistantCloudTelemetryConfig = { + enabled?: boolean; + beforeReport?: (report: AssistantCloudRunReport) => AssistantCloudRunReport | null; +}; + +type AssistantCloudThreadMessageCreateBody = { + parent_id: string | null; + format: "aui/v0" | string; + content: ReadonlyJSONObject; +}; + +type AssistantCloudThreadMessageListQuery = { + format?: string; +}; + +type AssistantCloudThreadMessageListResponse = { + messages: CloudMessage[]; +}; + +type AssistantCloudThreadMessageUpdateBody = { + content: ReadonlyJSONObject; +}; + +declare class AssistantCloudThreadMessages { + private cloud; + constructor(cloud: AssistantCloudAPI); + list(threadId: string, query?: AssistantCloudThreadMessageListQuery): Promise<AssistantCloudThreadMessageListResponse>; + create(threadId: string, body: AssistantCloudThreadMessageCreateBody): Promise<AssistantCloudMessageCreateResponse>; + update(threadId: string, messageId: string, body: AssistantCloudThreadMessageUpdateBody): Promise<void>; +} + +declare class AssistantCloudThreads { + private cloud; + readonly messages: AssistantCloudThreadMessages; + constructor(cloud: AssistantCloudAPI); + list(query?: AssistantCloudThreadsListQuery): Promise<AssistantCloudThreadsListResponse>; + get(threadId: string): Promise<CloudThread>; + create(body: AssistantCloudThreadsCreateBody): Promise<AssistantCloudThreadsCreateResponse>; + update(threadId: string, body: AssistantCloudThreadsUpdateBody): Promise<void>; + delete(threadId: string): Promise<void>; +} + +type AssistantCloudThreadsCreateBody = { + title?: string | undefined; + last_message_at: Date; + metadata?: unknown | undefined; + external_id?: string | undefined; +}; + +type AssistantCloudThreadsCreateResponse = { + thread_id: string; +}; + +type AssistantCloudThreadsListQuery = { + is_archived?: boolean; + limit?: number; + after?: string; +}; + +type AssistantCloudThreadsListResponse = { + threads: CloudThread[]; +}; + +type AssistantCloudThreadsUpdateBody = { + title?: string | undefined; + last_message_at?: Date | undefined; + metadata?: unknown | undefined; + is_archived?: boolean | undefined; +}; + +type AssistantContextConfig = { + getContext: () => string; + disabled?: boolean | undefined; +}; + +type AssistantDataUI = FC & { + unstable_data: AssistantDataUIProps; +}; + +type AssistantDataUIProps<T = any> = { + name: string; + render: DataMessagePartComponent<T>; +}; + +type AssistantEventCallback<TEvent extends AssistantEventName> = (payload: AssistantEventPayload[TEvent]) => void; + +type AssistantEventName = keyof AssistantEventPayload; + +type AssistantEventPayload = ClientEventMap & { + "*": WildcardPayload; +}; + +type AssistantEventScope<TEvent extends AssistantEventName> = "*" | EventSource<TEvent> | (EventSource<TEvent> extends ClientNames ? AncestorsOf<EventSource<TEvent>> : never); + +type AssistantEventSelector<TEvent extends AssistantEventName> = TEvent | { + scope: AssistantEventScope<TEvent>; + event: TEvent; +}; + +type AssistantInstructionsConfig = { + disabled?: boolean | undefined; + instruction: string; +}; + +type AssistantInteractableProps = { + description: string; + stateSchema: InteractableStateSchema; + initialState: unknown; + id?: string; + selected?: boolean; +}; + +type AssistantRuntime = { + readonly threads: ThreadListRuntime; + readonly thread: ThreadRuntime; + registerModelContextProvider(provider: ModelContextProvider): Unsubscribe$1; +}; + +type AssistantRuntimeCore = { + readonly threads: ThreadListRuntimeCore; + registerModelContextProvider: (provider: ModelContextProvider) => Unsubscribe$1; + getModelContextProvider: () => ModelContextProvider; + readonly RenderComponent?: ((...args: any[]) => unknown) | undefined; +}; + +declare class AssistantRuntimeImpl implements AssistantRuntime { + private readonly _core; + readonly threads: ThreadListRuntimeImpl; + readonly _thread: ThreadRuntime; + constructor(_core: AssistantRuntimeCore); + protected __internal_bindMethods(): void; + get thread(): ThreadRuntime; + registerModelContextProvider(provider: ModelContextProvider): Unsubscribe$1; +} + +declare const AssistantRuntimeProvider: import("react").MemoExoticComponent<(_param5: { + runtime: AssistantRuntime; + aui?: AssistantClient | null; + children: ReactNode; +}) => import("react").JSX.Element>; + +type AssistantState = { + [K in ClientNames]: ClientSchemas[K]["methods"] extends { + getState: () => infer S; + } ? S : never; +}; + +type AssistantStream = ReadableStream<AssistantStreamChunk>; + +declare const AssistantStream: { + toResponse(stream: AssistantStream, transformer: AssistantStreamEncoder): Response; + fromResponse(response: Response, transformer: ReadableWritablePair<AssistantStreamChunk, Uint8Array<ArrayBuffer>>): ReadableStream<AssistantStreamChunk>; + toByteStream(stream: AssistantStream, transformer: ReadableWritablePair<Uint8Array<ArrayBuffer>, AssistantStreamChunk>): ReadableStream<Uint8Array<ArrayBuffer>>; + fromByteStream(readable: ReadableStream<Uint8Array<ArrayBuffer>>, transformer: ReadableWritablePair<AssistantStreamChunk, Uint8Array<ArrayBuffer>>): ReadableStream<AssistantStreamChunk>; +}; + +type AssistantStreamChunk = { + readonly path: readonly number[]; +} & ({ + readonly type: "part-start"; + readonly part: PartInit; +} | { + readonly type: "part-finish"; +} | { + readonly type: "tool-call-args-text-finish"; +} | { + readonly type: "text-delta"; + readonly textDelta: string; +} | { + readonly type: "annotations"; + readonly annotations: ReadonlyJSONValue[]; +} | { + readonly type: "data"; + readonly data: ReadonlyJSONValue[]; +} | { + readonly type: "step-start"; + readonly messageId: string; +} | { + readonly type: "step-finish"; + readonly finishReason: "content-filter" | "error" | "length" | "other" | "stop" | "tool-calls" | "unknown"; + readonly usage: { + readonly inputTokens: number; + readonly outputTokens: number; + }; + readonly isContinued: boolean; +} | { + readonly type: "message-finish"; + readonly finishReason: "content-filter" | "error" | "length" | "other" | "stop" | "tool-calls" | "unknown"; + readonly usage: { + readonly inputTokens: number; + readonly outputTokens: number; + }; +} | { + readonly type: "result"; + readonly artifact?: ReadonlyJSONValue; + readonly result: ReadonlyJSONValue; + readonly isError: boolean; + readonly modelContent?: readonly ToolModelContentPart[]; + readonly messages?: ReadonlyJSONValue; +} | { + readonly type: "error"; + readonly error: string; +} | { + readonly type: "update-state"; + readonly operations: ObjectStreamOperation[]; +}); + +type AssistantStreamEncoder = ReadableWritablePair<Uint8Array<ArrayBuffer>, AssistantStreamChunk> & { + headers?: Headers; +}; + +type AssistantTool = FC & { + unstable_tool: AssistantToolProps<any, any>; +}; + +type AssistantToolProps<TArgs extends Record<string, unknown>, TResult> = AssistantToolProps$1<TArgs, TResult> & { + render?: ToolCallMessagePartComponent<TArgs, TResult> | undefined; + renderText?: ToolCallText<TArgs, TResult> | undefined; +}; + +type AssistantToolProps$1<TArgs extends Record<string, unknown>, TResult> = Tool<TArgs, TResult> & { + toolName: string; + render?: unknown; +}; + +type AssistantToolUI = FC & { + unstable_tool: AssistantToolUIProps<any, any>; +}; + +type AssistantToolUIProps<TArgs, TResult> = { + toolName: string; + render: ToolCallMessagePartComponent<TArgs, TResult>; + display?: "inline" | "standalone"; +}; + +type AsyncIterableStream<T> = AsyncIterable<T> & ReadableStream<T>; + +type Attachment = PendingAttachment | CompleteAttachment; + +type AttachmentAdapter = { + accept: string; + add(state: { + file: File; + }): Promise<PendingAttachment> | AsyncGenerator<PendingAttachment, void>; + remove(attachment: Attachment): Promise<void>; + send(attachment: PendingAttachment): Promise<CompleteAttachment>; +}; + +type AttachmentAddErrorEvent = { + readonly reason: AttachmentAddErrorReason; + readonly message: string; + readonly attachmentId?: string; + readonly error?: Error; +}; + +type AttachmentAddErrorReason = "adapter-error" | "no-adapter" | "not-accepted"; + +declare const AttachmentName: FC<AttachmentNameProps>; + +type AttachmentNameProps = TextProps; + +declare const AttachmentRemove: (_param6: AttachmentRemoveProps) => import("react").JSX.Element; + +type AttachmentRemoveProps = Omit<PressableProps, "onPress"> & { + children: ReactNode; +}; + +declare const AttachmentRoot: (_param7: AttachmentRootProps) => import("react").JSX.Element; + +type AttachmentRootProps = ViewProps & { + children: ReactNode; +}; + +type AttachmentRuntime<TSource extends AttachmentRuntimeSource = AttachmentRuntimeSource> = { + readonly path: AttachmentRuntimePath & { + attachmentSource: TSource; + }; + readonly source: TSource; + getState(): AttachmentState$1 & { + source: TSource; + }; + remove(): Promise<void>; + subscribe(callback: () => void): Unsubscribe$1; +}; + +declare abstract class AttachmentRuntimeImpl<Source extends AttachmentRuntimeSource = AttachmentRuntimeSource> implements AttachmentRuntime { + private _core; + get path(): AttachmentRuntimePath & { + attachmentSource: Source; + }; + abstract get source(): Source; + constructor(_core: AttachmentSnapshotBinding<Source>); + protected __internal_bindMethods(): void; + getState(): AttachmentState$1 & { + source: Source; + }; + abstract remove(): Promise<void>; + subscribe(callback: () => void): Unsubscribe$1; +} + +type AttachmentRuntimePath = ((MessageRuntimePath & { + readonly attachmentSource: "edit-composer" | "message"; +}) | (ThreadRuntimePath & { + readonly attachmentSource: "thread-composer"; +})) & { + readonly attachmentSelector: { + readonly type: "index"; + readonly index: number; + } | { + readonly type: "index"; + readonly index: number; + } | { + readonly type: "index"; + readonly index: number; + }; +}; + +type AttachmentRuntimeSource = AttachmentState$1["source"]; + +type AttachmentSnapshotBinding<Source extends AttachmentRuntimeSource> = SubscribableWithState<AttachmentState$1 & { + source: Source; +}, AttachmentRuntimePath & { + attachmentSource: Source; +}>; + +type AttachmentState = Attachment; + +type AttachmentState$1 = ThreadComposerAttachmentState | EditComposerAttachmentState | MessageAttachmentState; + +declare const AttachmentThumb: FC<AttachmentThumbProps>; + +type AttachmentThumbProps = TextProps; + +declare namespace AuiIf { + type Props = PropsWithChildren<{ + condition: AuiIf.Condition; + }>; + type Condition = (state: AssistantState) => boolean; +} + +declare const AuiIf: FC<AuiIf.Props>; + +declare const AuiProvider: (_param8: { + value: AssistantClient; + children: React.ReactNode; +}) => React.ReactElement; + +type AuiToolOverride<TArgs extends Record<string, unknown> = Record<string, unknown>, TResult = unknown> = Partial<Tool<TArgs, TResult>>; + +type AuiToolOverrides = Record<string, AuiToolOverride<any, any>>; + +type BackendTool<TArgs extends Record<string, unknown> = Record<string, unknown>, TResult = unknown> = ToolBase<TArgs, TResult> & { + type: "backend"; + description?: undefined; + parameters?: undefined; + disabled?: undefined; + execute?: undefined; + toModelOutput?: undefined; + experimental_onSchemaValidationError?: undefined; + providerOptions?: undefined; +}; + +type BackendToolDeclaration<TArgs extends Record<string, unknown> = Record<string, unknown>, TResult = unknown> = ToolBase<TArgs, TResult> & { + type: "backend"; + description?: string | undefined; + parameters?: StandardSchemaV1<TArgs> | JSONSchema7 | undefined; + disabled?: boolean; + execute?: ToolExecuteFunction<TArgs, TResult>; + toModelOutput?: ToolModelOutputFunction<TArgs, TResult>; + experimental_onSchemaValidationError?: OnSchemaValidationErrorFunction<TResult>; + providerOptions?: ProviderOptions; +}; + +declare abstract class BaseAssistantRuntimeCore implements AssistantRuntimeCore { + protected readonly _contextProvider: CompositeContextProvider; + abstract get threads(): ThreadListRuntimeCore; + registerModelContextProvider(provider: ModelContextProvider): Unsubscribe$1; + getModelContextProvider(): ModelContextProvider; +} + +type BaseAttachment = { + id: string; + type: "image" | "document" | "file" | (string & {}); + name: string; + contentType?: string | undefined; + file?: File; + content?: ThreadUserMessagePart[]; +}; + +declare abstract class BaseComposerRuntimeCore extends BaseSubscribable implements ComposerRuntimeCore { + readonly isEditing = true; + protected abstract getAttachmentAdapter(): AttachmentAdapter | undefined; + protected abstract getDictationAdapter(): DictationAdapter | undefined; + protected enrichWithComposerMetadata<T extends { + metadata?: { + custom?: Record<string, unknown>; + }; + }>(message: T, composerMetadata: Record<string, unknown> | undefined): T; + get attachmentAccept(): string; + private _attachments; + get attachments(): readonly Attachment[]; + protected setAttachments(value: readonly Attachment[]): void; + abstract get canCancel(): boolean; + abstract get canSend(): boolean; + get isEmpty(): boolean; + private _text; + get text(): string; + private _role; + get role(): "assistant" | "system" | "user"; + private _runConfig; + get runConfig(): RunConfig; + private _quote; + get quote(): QuoteInfo | undefined; + setQuote(quote: QuoteInfo | undefined): void; + setText(value: string): void; + setRole(role: MessageRole): void; + setRunConfig(runConfig: RunConfig): void; + private _emptyTextAndAttachments; + private _onClearAttachments; + reset(): Promise<void>; + clearAttachments(): Promise<void>; + send(options?: SendOptions): Promise<void>; + cancel(): void; + get queue(): readonly QueueItemState[]; + steerQueueItem(_queueItemId: string): void; + removeQueueItem(_queueItemId: string): void; + protected abstract handleSend(message: Omit<AppendMessage, "parentId" | "sourceId">, options?: SendOptions): void; + protected abstract handleCancel(): void; + addAttachment(fileOrAttachment: File | CreateAttachment): Promise<void>; + private _safeEmitAttachmentAddError; + removeAttachment(attachmentId: string): Promise<void>; + private _dictation; + private _dictationSession; + private _dictationUnsubscribes; + private _dictationBaseText; + private _currentInterimText; + private _dictationSessionIdCounter; + private _activeDictationSessionId; + private _isCleaningDictation; + get dictation(): DictationState | undefined; + private _isActiveSession; + startDictation(): void; + stopDictation(): void; + private _cleanupDictation; + private _eventSubscribers; + protected _notifyEventSubscribers<E extends ComposerRuntimeEventType>(event: E, payload: ComposerRuntimeEventPayload[E]): void; + unstable_on<E extends ComposerRuntimeEventType>(event: E, callback: ComposerRuntimeEventCallback<E>): () => void; +} + +type BaseComposerState = { + readonly canCancel: boolean; + readonly canSend: boolean; + readonly isEditing: boolean; + readonly isEmpty: boolean; + readonly text: string; + readonly role: MessageRole; + readonly attachments: readonly Attachment[]; + readonly runConfig: RunConfig; + readonly attachmentAccept: string; + readonly dictation: DictationState | undefined; + readonly quote: QuoteInfo | undefined; + readonly queue: readonly QueueItemState[]; +}; + +declare class BaseSubscribable { + private _subscribers; + subscribe(callback: () => void): Unsubscribe$1; + waitForUpdate(): Promise<void>; + protected _notifySubscribers(): void; +} + +type BaseThreadMessage = { + readonly status?: ThreadAssistantMessage["status"]; + readonly metadata: { + readonly unstable_state?: ReadonlyJSONValue; + readonly unstable_annotations?: readonly ReadonlyJSONValue[]; + readonly unstable_data?: readonly ReadonlyJSONValue[]; + readonly steps?: readonly ThreadStep[]; + readonly submittedFeedback?: { + readonly type: "negative" | "positive"; + }; + readonly timing?: MessageTiming; + readonly isOptimistic?: boolean; + readonly custom: Record<string, unknown>; + }; + readonly attachments?: ThreadUserMessage["attachments"]; +}; + +declare const BranchPickerCount: (props: BranchPickerCountProps) => import("react").JSX.Element; + +type BranchPickerCountProps = TextProps; + +declare const BranchPickerNext: (_param9: BranchPickerNextProps) => import("react").JSX.Element; + +type BranchPickerNextProps = Omit<PressableProps, "onPress"> & { + children: ReactNode; +}; + +declare const BranchPickerNumber: (props: BranchPickerNumberProps) => import("react").JSX.Element; + +type BranchPickerNumberProps = TextProps; + +declare const BranchPickerPrevious: (_param10: BranchPickerPreviousProps) => import("react").JSX.Element; + +type BranchPickerPreviousProps = Omit<PressableProps, "onPress"> & { + children: ReactNode; +}; + +declare const ChainOfThoughtAccordionTrigger: (_param11: ChainOfThoughtAccordionTriggerProps) => import("react").JSX.Element; + +type ChainOfThoughtAccordionTriggerProps = Omit<PressableProps, "onPress"> & { + children: ReactNode; +}; + +declare const ChainOfThoughtByIndicesProvider: FC<PropsWithChildren<{ + startIndex: number; + endIndex: number; +}>>; + +declare const ChainOfThoughtClient: Resource<ClientOutput<"chainOfThought">, [ + { + parts: readonly ChainOfThoughtPart[]; + getMessagePart: (selector: { + index: number; + }) => PartMethods; + } +]>; + +type ChainOfThoughtPart = Extract<PartState, { + type: "tool-call"; +} | { + type: "reasoning"; +}>; + +declare const ChainOfThoughtPartByIndexProvider: FC<PropsWithChildren<{ + index: number; +}>>; + +type ChainOfThoughtPartsComponentConfig = { + Reasoning?: ReasoningMessagePartComponent | undefined; + tools?: { + Fallback?: ToolCallMessagePartComponent | undefined; + }; + Layout?: ComponentType<PropsWithChildren> | undefined; +}; + +declare namespace ChainOfThoughtPrimitiveParts { + type Props = { + components?: ChainOfThoughtPartsComponentConfig; + children?: never; + } | { + children: (value: { + part: PartState; + }) => ReactNode; + components?: never; + }; +} + +declare const ChainOfThoughtPrimitiveParts: FC<ChainOfThoughtPrimitiveParts.Props>; + +declare const ChainOfThoughtRoot: (_param12: ChainOfThoughtRootProps) => import("react").JSX.Element; + +type ChainOfThoughtRootProps = ViewProps & { + children: ReactNode; +}; + +type ChatModelAdapter = { + run(options: ChatModelRunOptions): Promise<ChatModelRunResult> | AsyncGenerator<ChatModelRunResult, void>; +}; + +type ChatModelRunOptions = { + readonly messages: readonly ThreadMessage[]; + readonly runConfig: RunConfig; + readonly abortSignal: AbortSignal; + readonly context: ModelContext$1; + readonly unstable_assistantMessageId?: string | undefined; + readonly unstable_threadId?: string | undefined; + readonly unstable_parentId?: string | null | undefined; + unstable_getMessage(): ThreadMessage; +}; + +type ChatModelRunResult = { + readonly content?: readonly ThreadAssistantMessagePart[] | undefined; + readonly status?: MessageStatus | undefined; + readonly metadata?: { + readonly unstable_state?: ReadonlyJSONValue; + readonly unstable_annotations?: readonly ReadonlyJSONValue[] | undefined; + readonly unstable_data?: readonly ReadonlyJSONValue[] | undefined; + readonly steps?: readonly ThreadStep[] | undefined; + readonly timing?: MessageTiming | undefined; + readonly custom?: Record<string, unknown> | undefined; + }; +}; + +type ClientElement<K extends ClientNames> = ResourceElement<ClientOutput<K>>; + +type ClientError<E extends string> = { + methods: Record<E, () => E>; + meta: { + source: ClientNames; + query: Record<E, E>; + }; + events: Record<`${E}.`, E>; +}; + +type ClientEventMap = UnionToIntersection<{ + [K in ClientNames]: ClientEvents<K>; +}[ClientNames]>; + +type ClientEvents<K extends ClientNames> = "events" extends keyof ClientSchemas[K] ? ClientSchemas[K]["events"] extends ClientEventsType<K> ? ClientSchemas[K]["events"] : never : never; + +type ClientEventsType<K extends ClientNames> = Record<`${K}.${string}`, unknown>; + +type ClientMeta<K extends ClientNames> = "meta" extends keyof ClientSchemas[K] ? Pick<ClientSchemas[K]["meta"] extends ClientMetaType ? ClientSchemas[K]["meta"] : never, "query" | "source"> : never; + +type ClientMetaType = { + source: ClientNames; + query: Record<string, unknown>; +}; + +interface ClientMethods { + [key: string | symbol]: (...args: any[]) => any; +} + +type ClientNames = keyof ClientSchemas extends (infer U) ? U : never; + +type ClientOutput<K extends ClientNames> = ClientSchemas[K]["methods"] & ClientMethods; + +type ClientSchemas = keyof ScopeRegistry extends never ? { + "ERROR: No clients were defined": ClientError<"ERROR: No clients were defined">; +} : { + [K in keyof ScopeRegistry]: ValidateClient<K>; +}; + +type CloudMessage = { + id: string; + parent_id: string | null; + height: number; + created_at: Date; + updated_at: Date; + format: "aui/v0" | string; + content: ReadonlyJSONObject; +}; + +type CloudThread = { + title: string; + last_message_at: Date; + metadata: unknown; + external_id: string | null; + id: string; + project_id: string; + created_at: Date; + updated_at: Date; + workspace_id: string; + is_archived: boolean; +}; + +type CompleteAttachment = BaseAttachment & { + status: CompleteAttachmentStatus; + content: ThreadUserMessagePart[]; +}; + +type CompleteAttachmentStatus = { + type: "complete"; +}; + +declare const ComposerAddAttachment: (_param13: ComposerAddAttachmentProps) => import("react").JSX.Element; + +type ComposerAddAttachmentProps = Omit<PressableProps, "onPress"> & { + children: ReactNode; +}; + +declare const ComposerAttachmentByIndex: import("react").FC<ComposerPrimitiveAttachmentByIndex.Props>; + +declare const ComposerAttachmentByIndexProvider: FC<PropsWithChildren<{ + index: number; +}>>; + +declare abstract class ComposerAttachmentRuntime<Source extends "edit-composer" | "thread-composer"> extends AttachmentRuntimeImpl<Source> { + private _composerApi; + constructor(core: AttachmentSnapshotBinding<Source>, _composerApi: ComposerRuntimeCoreBinding); + remove(): Promise<void>; +} + +declare const ComposerAttachments: import("react").FC<ComposerPrimitiveAttachments.Props>; + +type ComposerAttachmentsComponentConfig = { + Image?: ComponentType | undefined; + Document?: ComponentType | undefined; + File?: ComponentType | undefined; + Attachment?: ComponentType | undefined; +}; + +declare const ComposerCancel: (_param14: ComposerCancelProps) => import("react").JSX.Element; + +type ComposerCancelProps = Omit<PressableProps, "onPress"> & { + children: ReactNode; +}; + +type ComposerIfFilters = { + editing: boolean | undefined; + dictation: boolean | undefined; +}; + +declare const ComposerInput: (_param15: ComposerInputProps) => import("react").JSX.Element; + +type ComposerInputProps = Omit<TextInputProps, "onChangeText" | "value"> & { + submitMode?: "enter" | "none"; +}; + +declare namespace ComposerPrimitiveAttachmentByIndex { + type Props = { + index: number; + components?: ComposerAttachmentsComponentConfig; + }; +} + +declare const ComposerPrimitiveAttachmentByIndex: FC<ComposerPrimitiveAttachmentByIndex.Props>; + +declare namespace ComposerPrimitiveAttachments { + type Props = { + components: ComposerAttachmentsComponentConfig; + children?: never; + } | { + children: (value: { + attachment: Attachment; + }) => ReactNode; + components?: never; + }; +} + +declare const ComposerPrimitiveAttachments: FC<ComposerPrimitiveAttachments.Props>; + +declare namespace ComposerPrimitiveIf { + type Props = PropsWithChildren<UseComposerIfProps>; +} + +declare const ComposerPrimitiveIf: FC<ComposerPrimitiveIf.Props>; + +declare const ComposerRoot: (_param16: ComposerRootProps) => import("react").JSX.Element; + +type ComposerRootProps = ViewProps & { + children: ReactNode; +}; + +type ComposerRuntime = { + readonly path: ComposerRuntimePath; + readonly type: "edit" | "thread"; + getState(): ComposerState$1; + addAttachment(fileOrAttachment: File | CreateAttachment): Promise<void>; + setText(text: string): void; + setRole(role: MessageRole): void; + setRunConfig(runConfig: RunConfig): void; + reset(): Promise<void>; + clearAttachments(): Promise<void>; + send(options?: SendOptions): void; + cancel(): void; + steerQueueItem(queueItemId: string): void; + removeQueueItem(queueItemId: string): void; + subscribe(callback: () => void): Unsubscribe$1; + getAttachmentByIndex(idx: number): AttachmentRuntime; + startDictation(): void; + stopDictation(): void; + setQuote(quote: QuoteInfo | undefined): void; + unstable_on<E extends ComposerRuntimeEventType>(event: E, callback: ComposerRuntimeEventCallback<E>): Unsubscribe$1; +}; + +type ComposerRuntimeCore = Readonly<{ + isEditing: boolean; + canCancel: boolean; + canSend: boolean; + isEmpty: boolean; + attachments: readonly Attachment[]; + attachmentAccept: string; + addAttachment: (fileOrAttachment: File | CreateAttachment) => Promise<void>; + removeAttachment: (attachmentId: string) => Promise<void>; + text: string; + setText: (value: string) => void; + role: MessageRole; + setRole: (role: MessageRole) => void; + runConfig: RunConfig; + setRunConfig: (runConfig: RunConfig) => void; + quote: QuoteInfo | undefined; + setQuote: (quote: QuoteInfo | undefined) => void; + reset: () => Promise<void>; + clearAttachments: () => Promise<void>; + send: (options?: SendOptions) => void; + cancel: () => void; + queue: readonly QueueItemState[]; + steerQueueItem: (queueItemId: string) => void; + removeQueueItem: (queueItemId: string) => void; + dictation: DictationState | undefined; + startDictation: () => void; + stopDictation: () => void; + subscribe: (callback: () => void) => Unsubscribe$1; + unstable_on: <E extends ComposerRuntimeEventType>(event: E, callback: ComposerRuntimeEventCallback<E>) => Unsubscribe$1; +}>; + +type ComposerRuntimeCoreBinding = SubscribableWithState<ComposerRuntimeCore | undefined, ComposerRuntimePath>; + +type ComposerRuntimeEventCallback<E extends ComposerRuntimeEventType> = (payload: ComposerRuntimeEventPayload[E]) => void; + +type ComposerRuntimeEventPayload = { + send: Record<string, never>; + attachmentAdd: Record<string, never>; + attachmentAddError: AttachmentAddErrorEvent; +}; + +type ComposerRuntimeEventType = keyof ComposerRuntimeEventPayload; + +declare abstract class ComposerRuntimeImpl implements ComposerRuntime { + protected _core: ComposerRuntimeCoreBinding; + get path(): ComposerRuntimePath; + abstract get type(): "edit" | "thread"; + constructor(_core: ComposerRuntimeCoreBinding); + protected __internal_bindMethods(): void; + abstract getState(): ComposerState$1; + setText(text: string): void; + setRunConfig(runConfig: RunConfig): void; + addAttachment(fileOrAttachment: File | CreateAttachment): Promise<void>; + reset(): Promise<void>; + clearAttachments(): Promise<void>; + send(options?: SendOptions): void; + cancel(): void; + steerQueueItem(queueItemId: string): void; + removeQueueItem(queueItemId: string): void; + setRole(role: MessageRole): void; + startDictation(): void; + stopDictation(): void; + setQuote(quote: QuoteInfo | undefined): void; + subscribe(callback: () => void): Unsubscribe$1; + private _eventSubscriptionSubjects; + unstable_on<E extends ComposerRuntimeEventType>(event: E, callback: ComposerRuntimeEventCallback<E>): Unsubscribe$1; + abstract getAttachmentByIndex(idx: number): AttachmentRuntime; +} + +type ComposerRuntimePath = (ThreadRuntimePath & { + readonly composerSource: "thread"; +}) | (MessageRuntimePath & { + readonly composerSource: "edit"; +}); + +declare const ComposerSend: (_param17: ComposerSendProps) => import("react").JSX.Element; + +type ComposerSendProps = Omit<PressableProps, "onPress"> & { + children: ReactNode; +}; + +type ComposerState = { + readonly text: string; + readonly role: MessageRole; + readonly attachments: readonly Attachment[]; + readonly runConfig: RunConfig; + readonly isEditing: boolean; + readonly canCancel: boolean; + readonly canSend: boolean; + readonly attachmentAccept: string; + readonly isEmpty: boolean; + readonly type: "edit" | "thread"; + readonly dictation: DictationState | undefined; + readonly quote: QuoteInfo | undefined; + readonly queue: readonly QueueItemState[]; +}; + +type ComposerState$1 = ThreadComposerState | EditComposerState; + +declare class CompositeAttachmentAdapter implements AttachmentAdapter { + private _adapters; + accept: string; + constructor(adapters: AttachmentAdapter[]); + add(state: { + file: File; + }): Promise<PendingAttachment> | AsyncGenerator<PendingAttachment, void, any>; + send(attachment: PendingAttachment): Promise<CompleteAttachment>; + remove(attachment: Attachment): Promise<void>; +} + +declare class CompositeContextProvider implements ModelContextProvider { + private _providers; + getModelContext(): ModelContext$1; + registerModelContextProvider(provider: ModelContextProvider): () => void; + private _subscribers; + notifySubscribers(): void; + subscribe(callback: () => void): () => void; +} + +type CreateAppendMessage = string | { + parentId?: string | null | undefined; + sourceId?: string | null | undefined; + role?: AppendMessage["role"] | undefined; + content: AppendMessage["content"]; + attachments?: AppendMessage["attachments"] | undefined; + metadata?: AppendMessage["metadata"] | undefined; + createdAt?: Date | undefined; + runConfig?: AppendMessage["runConfig"] | undefined; + startRun?: boolean | undefined; +}; + +type CreateAttachment = { + id?: string; + type?: "image" | "document" | "file" | (string & {}); + name: string; + contentType?: string; + content: ThreadUserMessagePart[]; +}; + +type CreateResumeRunConfig = CreateStartRunConfig & { + stream?: (options: ChatModelRunOptions) => AsyncGenerator<ChatModelRunResult, void, unknown>; +}; + +type CreateStartRunConfig = { + parentId: string | null; + sourceId?: string | null | undefined; + runConfig?: RunConfig | undefined; +}; + +type DataMessagePart<T = any> = { + readonly type: "data"; + readonly name: string; + readonly data: T; +}; + +type DataMessagePartComponent<T = any> = ComponentType<DataMessagePartProps<T>>; + +type DataMessagePartProps<T = any> = MessagePartState & DataMessagePart<T>; + +type DataPrefixedPart = { + readonly type: `data-${string}`; + readonly data: any; +}; + +declare const DataRenderers: Resource<ClientOutput<"dataRenderers">, [ +]>; + +type DeepPartial<T> = T extends readonly any[] ? readonly DeepPartial<T[number]>[] : T extends { + [key: string]: any; +} ? { + readonly [K in keyof T]?: DeepPartial<T[K]>; +} : T; + +declare class DefaultThreadComposerRuntimeCore extends BaseComposerRuntimeCore implements ThreadComposerRuntimeCore { + private runtime; + private _canCancel; + get canCancel(): boolean; + get canSend(): boolean; + get queue(): readonly QueueItemState[]; + steerQueueItem(queueItemId: string): void; + removeQueueItem(queueItemId: string): void; + protected getAttachmentAdapter(): AttachmentAdapter | undefined; + protected getDictationAdapter(): DictationAdapter | undefined; + constructor(runtime: Omit<ThreadRuntimeCore, "composer"> & { + adapters?: { + attachments?: AttachmentAdapter | undefined; + dictation?: DictationAdapter | undefined; + } | undefined; + }); + connect(): Unsubscribe$1; + handleSend(message: Omit<AppendMessage, "parentId" | "sourceId">, options?: SendOptions): Promise<void>; + handleCancel(): Promise<void>; +} + +declare const Derived: <K extends ClientNames>(_config: Derived.Props<K>) => ResourceElement<null, [ + _config: Derived.Props<K> +]>; + +declare namespace Derived { + type Props<K extends ClientNames> = { + get: (client: AssistantClient) => ReturnType<AssistantClientAccessor<K>>; + } & ClientMeta<K>; +} + +type DerivedElement<K extends ClientNames> = ResourceElement<null, [ + Derived.Props<K> +]>; + +declare namespace DictationAdapter { + type Status = { + type: "running" | "starting"; + } | { + type: "ended"; + reason: "cancelled" | "error" | "stopped"; + }; + type Result = { + transcript: string; + isFinal?: boolean; + }; + type Session = { + status: Status; + stop: () => Promise<void>; + cancel: () => void; + onSpeechStart: (callback: () => void) => Unsubscribe$1; + onSpeechEnd: (callback: (result: Result) => void) => Unsubscribe$1; + onSpeech: (callback: (result: Result) => void) => Unsubscribe$1; + }; +} + +type DictationAdapter = { + listen: () => DictationAdapter.Session; + disableInputDuringDictation?: boolean; +}; + +type DictationState = { + readonly status: DictationAdapter.Status; + readonly transcript?: string; + readonly inputDisabled?: boolean; +}; + +declare class EditComposerAttachmentRuntimeImpl extends ComposerAttachmentRuntime<"edit-composer"> { + get source(): "edit-composer"; +} + +type EditComposerAttachmentState = Attachment & { + readonly source: "edit-composer"; +}; + +type EditComposerRuntime = Omit<ComposerRuntime, "getAttachmentByIndex" | "getState"> & { + readonly path: ComposerRuntimePath & { + composerSource: "edit"; + }; + readonly type: "edit"; + getState(): EditComposerState; + beginEdit(): void; + getAttachmentByIndex(idx: number): AttachmentRuntime & { + source: "edit-composer"; + }; +}; + +type EditComposerRuntimeCore = ComposerRuntimeCore & Readonly<{ + parentId: string | null; + sourceId: string | null; +}>; + +type EditComposerRuntimeCoreBinding = SubscribableWithState<EditComposerRuntimeCore | undefined, ComposerRuntimePath & { + composerSource: "edit"; +}>; + +declare class EditComposerRuntimeImpl extends ComposerRuntimeImpl implements EditComposerRuntime { + private _beginEdit; + get path(): ComposerRuntimePath & { + composerSource: "edit"; + }; + get type(): "edit"; + private _getState; + constructor(core: EditComposerRuntimeCoreBinding, _beginEdit: () => void); + __internal_bindMethods(): void; + getState(): EditComposerState; + beginEdit(): void; + getAttachmentByIndex(idx: number): EditComposerAttachmentRuntimeImpl; +} + +type EditComposerState = BaseComposerState & { + readonly type: "edit"; + readonly parentId: string | null; + readonly sourceId: string | null; +}; + +type EmptyMessagePartComponent = ComponentType<EmptyMessagePartProps>; + +type EmptyMessagePartProps = { + status: MessagePartStatus; +}; + +type EnrichedPartState = (Extract<PartState, { + type: "tool-call"; +}> & { + readonly toolUI: ReactNode; + addResult: ToolCallMessagePartProps["addResult"]; + resume: ToolCallMessagePartProps["resume"]; + respondToApproval: ToolCallMessagePartProps["respondToApproval"]; +}) | (Extract<PartState, { + type: "data"; +}> & { + readonly dataRendererUI: ReactNode; +}) | Exclude<PartState, { + type: "tool-call"; +} | { + type: "data"; +}>; + +declare const ErrorMessage: { + (_param18: ErrorMessageProps): import("react").JSX.Element | null; + displayName: string; +}; + +type ErrorMessageProps = TextProps & { + children?: ReactNode; +}; + +declare const ErrorRoot: { + (_param19: ErrorRootProps): import("react").JSX.Element | null; + displayName: string; +}; + +type ErrorRootProps = ViewProps & { + children: ReactNode; +}; + +type EventSource<T extends AssistantEventName> = T extends `${infer Source}.${string}` ? Source : never; + +type ExportedMessageRepository = { + headId?: string | null; + messages: Array<{ + message: ThreadMessage; + parentId: string | null; + runConfig?: RunConfig; + }>; +}; + +declare const ExportedMessageRepository: { + fromArray: (messages: readonly ThreadMessageLike[]) => ExportedMessageRepository; + fromBranchableArray: (items: readonly { + message: ThreadMessageLike; + parentId: string | null; + }[], options?: { + headId?: string | null; + }) => ExportedMessageRepository; +}; + +type ExportedMessageRepositoryItem = { + message: ThreadMessage; + parentId: string | null; + runConfig?: RunConfig; +}; + +type FeedbackAdapter = { + submit: (feedback: FeedbackAdapterFeedback) => void; +}; + +type FeedbackAdapterFeedback = { + message: ThreadMessage; + type: "negative" | "positive"; +}; + +type FileMessagePart = { + readonly type: "file"; + readonly filename?: string; + readonly data: string; + readonly mimeType: string; + readonly parentId?: string; +}; + +type FileMessagePartComponent = ComponentType<FileMessagePartProps>; + +type FileMessagePartProps = MessagePartState & FileMessagePart; + +type FrontendTool<TArgs extends Record<string, unknown> = Record<string, unknown>, TResult = unknown> = ToolBase<TArgs, TResult> & { + type: "frontend"; + description?: string | undefined; + parameters: StandardSchemaV1<TArgs> | JSONSchema7; + disabled?: boolean; + execute?: ToolExecuteFunction<TArgs, TResult>; + toModelOutput?: ToolModelOutputFunction<TArgs, TResult>; + experimental_onSchemaValidationError?: OnSchemaValidationErrorFunction<TResult>; + providerOptions?: ProviderOptions; +}; + +type GeneratePresignedUploadUrlRequestBody = { + filename: string; +}; + +type GeneratePresignedUploadUrlResponse = { + success: boolean; + signedUrl: string; + expiresAt: string; + publicUrl: string; +}; + +type GenerativeUIComponentRegistry = Record<string, ComponentType<any>>; + +type GenerativeUIMessagePart = { + readonly type: "generative-ui"; + readonly spec: GenerativeUISpec; + readonly id?: string; + readonly parentId?: string; +}; + +type GenerativeUINode = string | { + readonly component: string; + readonly props?: Record<string, unknown>; + readonly children?: readonly GenerativeUINode[]; + readonly key?: string; +}; + +type GenerativeUISpec = { + readonly root: GenerativeUINode | readonly GenerativeUINode[]; +}; + +type GenericThreadHistoryAdapter<TMessage> = { + load(): Promise<MessageFormatRepository<TMessage>>; + append(item: MessageFormatItem<TMessage>): Promise<void>; + update?(item: MessageFormatItem<TMessage>, localMessageId: string): Promise<void>; + delete?(items: MessageFormatItem<TMessage>[]): Promise<void>; + reportTelemetry?(items: MessageFormatItem<TMessage>[], options?: { + durationMs?: number; + stepTimestamps?: { + start_ms: number; + end_ms: number; + }[]; + }): void; +}; + +type GroupByContext = { + readonly toolUIs?: ToolsState["toolUIs"]; +}; + +type GroupPartType = PartState["type"] | "standalone-tool-call" | "mcp-app"; + +type HumanTool<TArgs extends Record<string, unknown> = Record<string, unknown>, TResult = unknown> = ToolBase<TArgs, TResult> & { + type: "human"; + description?: string | undefined; + parameters: StandardSchemaV1<TArgs> | JSONSchema7; + disabled?: boolean; + display?: "standalone"; + execute?: undefined; + toModelOutput?: undefined; + experimental_onSchemaValidationError?: undefined; + providerOptions?: ProviderOptions; +}; + +type ImageMessagePart = { + readonly type: "image"; + readonly image: string; + readonly filename?: string; +}; + +type ImageMessagePartComponent = ComponentType<ImageMessagePartProps>; + +type ImageMessagePartProps = MessagePartState & ImageMessagePart; + +declare class InMemoryThreadListAdapter implements RemoteThreadListAdapter { + list(): Promise<RemoteThreadListResponse>; + rename(): Promise<void>; + updateCustom(): Promise<void>; + archive(): Promise<void>; + unarchive(): Promise<void>; + delete(): Promise<void>; + initialize(threadId: string): Promise<RemoteThreadInitializeResponse>; + generateTitle(): Promise<AssistantStream>; + fetch(threadId: string): Promise<RemoteThreadMetadata>; +} + +type InteractableScope = "app" | "thread"; + +type InteractableStateSchema = NonNullable<Extract<Tool, { + parameters: unknown; +}>["parameters"]>; + +declare const Interactables: Resource<ClientOutput<"interactables">, [ +]>; + +interface JSONSchema7 { + $id?: string | undefined; + $ref?: string | undefined; + $schema?: JSONSchema7Version | undefined; + $comment?: string | undefined; + $defs?: { + [key: string]: JSONSchema7Definition; + } | undefined; + type?: JSONSchema7TypeName | JSONSchema7TypeName[] | undefined; + enum?: JSONSchema7Type[] | undefined; + const?: JSONSchema7Type | undefined; + multipleOf?: number | undefined; + maximum?: number | undefined; + exclusiveMaximum?: number | undefined; + minimum?: number | undefined; + exclusiveMinimum?: number | undefined; + maxLength?: number | undefined; + minLength?: number | undefined; + pattern?: string | undefined; + items?: JSONSchema7Definition | JSONSchema7Definition[] | undefined; + additionalItems?: JSONSchema7Definition | undefined; + maxItems?: number | undefined; + minItems?: number | undefined; + uniqueItems?: boolean | undefined; + contains?: JSONSchema7Definition | undefined; + maxProperties?: number | undefined; + minProperties?: number | undefined; + required?: string[] | undefined; + properties?: { + [key: string]: JSONSchema7Definition; + } | undefined; + patternProperties?: { + [key: string]: JSONSchema7Definition; + } | undefined; + additionalProperties?: JSONSchema7Definition | undefined; + dependencies?: { + [key: string]: JSONSchema7Definition | string[]; + } | undefined; + propertyNames?: JSONSchema7Definition | undefined; + if?: JSONSchema7Definition | undefined; + then?: JSONSchema7Definition | undefined; + else?: JSONSchema7Definition | undefined; + allOf?: JSONSchema7Definition[] | undefined; + anyOf?: JSONSchema7Definition[] | undefined; + oneOf?: JSONSchema7Definition[] | undefined; + not?: JSONSchema7Definition | undefined; + format?: string | undefined; + contentMediaType?: string | undefined; + contentEncoding?: string | undefined; + definitions?: { + [key: string]: JSONSchema7Definition; + } | undefined; + title?: string | undefined; + description?: string | undefined; + default?: JSONSchema7Type | undefined; + readOnly?: boolean | undefined; + writeOnly?: boolean | undefined; + examples?: JSONSchema7Type | undefined; +} + +interface JSONSchema7Array extends Array<JSONSchema7Type> { +} + +type JSONSchema7Definition = JSONSchema7 | boolean; + +interface JSONSchema7Object { + [key: string]: JSONSchema7Type; +} + +type JSONSchema7Type = string | number | boolean | JSONSchema7Object | JSONSchema7Array | null; + +type JSONSchema7TypeName = "array" | "boolean" | "integer" | "null" | "number" | "object" | "string"; + +type JSONSchema7Version = string; + +type LanguageModelConfig = { + apiKey?: string; + baseUrl?: string; + modelName?: string; + reasoningEffort?: string; +}; + +type LanguageModelV1CallSettings = { + maxTokens?: number; + temperature?: number; + topP?: number; + presencePenalty?: number; + frequencyPenalty?: number; + seed?: number; + headers?: Record<string, string | undefined>; +}; + +type LocalRuntimeOptions = Omit<LocalRuntimeOptionsBase, "adapters"> & { + cloud?: AssistantCloud | undefined; + initialMessages?: readonly ThreadMessageLike[] | undefined; + adapters?: Omit<LocalRuntimeOptionsBase["adapters"], "chatModel"> | undefined; +}; + +type LocalRuntimeOptionsBase = { + maxSteps?: number | undefined; + adapters: { + chatModel: ChatModelAdapter; + history?: ThreadHistoryAdapter | undefined; + attachments?: AttachmentAdapter | undefined; + speech?: SpeechSynthesisAdapter | undefined; + dictation?: DictationAdapter | undefined; + voice?: RealtimeVoiceAdapter | undefined; + feedback?: FeedbackAdapter | undefined; + suggestion?: SuggestionAdapter | undefined; + }; + unstable_humanToolNames?: string[] | undefined; + unstable_enableMessageQueue?: boolean | undefined; +}; + +type MakeRequestOptions = { + method?: "POST" | "PUT" | "DELETE" | undefined; + headers?: Record<string, string> | undefined; + query?: Record<string, string | number | boolean> | undefined; + body?: object | undefined; +}; + +type McpAppMetadata = { + readonly resourceUri: string; + readonly mimeType?: string; + readonly visibility?: readonly ("app" | "model")[]; +}; + +type McpAppResourceOutput = { + readonly render: ToolCallMessagePartComponent; +}; + +type McpServerConfig = { + type: "http" | "sse"; + url: string; + headers?: Record<string, string>; + redirect?: "error" | "follow"; + connectionTimeout?: number | undefined; +} | { + type: "stdio"; + command: string; + args?: readonly string[]; + env?: Record<string, string>; + cwd?: string; + connectionTimeout?: number | undefined; +}; + +type McpTool = ToolBase<Record<string, unknown>, unknown> & { + type: "mcp"; + server: McpServerConfig; + description?: undefined; + parameters?: undefined; + disabled?: boolean; + execute?: undefined; + toModelOutput?: undefined; + experimental_onSchemaValidationError?: undefined; + providerOptions?: undefined; +}; + +type McpToolkitDefinition = Record<string, McpToolkitEntry>; + +type McpToolkitEntry = McpServerConfig | { + server: McpServerConfig; + disabled?: boolean | undefined; + prefix?: string | undefined; + tools?: Record<string, McpToolkitToolConfig> | undefined; +}; + +type McpToolkitToolConfig = { + disabled?: boolean | undefined; +}; + +declare const MessageAttachmentByIndex: import("react").FC<MessagePrimitiveAttachmentByIndex.Props>; + +declare const MessageAttachmentByIndexProvider: FC<PropsWithChildren<{ + index: number; +}>>; + +declare class MessageAttachmentRuntimeImpl extends AttachmentRuntimeImpl<"message"> { + get source(): "message"; + remove(): never; +} + +type MessageAttachmentState = CompleteAttachment & { + readonly source: "message"; +}; + +declare const MessageAttachments: import("react").FC<MessagePrimitiveAttachments.Props>; + +type MessageAttachmentsComponentConfig = { + Image?: ComponentType | undefined; + Document?: ComponentType | undefined; + File?: ComponentType | undefined; + Attachment?: ComponentType | undefined; +}; + +declare const MessageByIndexProvider: FC<PropsWithChildren<{ + index: number; +}>>; + +type MessageCommonProps = { + readonly id: string; + readonly createdAt: Date; +}; + +type MessageComponents = { + Message: ComponentType; + EditComposer?: ComponentType | undefined; + UserEditComposer?: ComponentType | undefined; + AssistantEditComposer?: ComponentType | undefined; + SystemEditComposer?: ComponentType | undefined; + UserMessage?: ComponentType | undefined; + AssistantMessage?: ComponentType | undefined; + SystemMessage?: ComponentType | undefined; +} | { + Message?: ComponentType | undefined; + EditComposer?: ComponentType | undefined; + UserEditComposer?: ComponentType | undefined; + AssistantEditComposer?: ComponentType | undefined; + SystemEditComposer?: ComponentType | undefined; + UserMessage: ComponentType; + AssistantMessage: ComponentType; + SystemMessage?: ComponentType | undefined; +}; + +declare const MessageContent: (_param20: MessageContentProps) => import("react").JSX.Element; + +type MessageContentPart = ThreadUserMessagePart | ThreadAssistantMessagePart; + +type MessageContentProps = { + renderText?: (props: { + part: Extract<MessageContentPart, { + type: "text"; + }>; + index: number; + }) => ReactElement; + renderToolCall?: (props: { + part: Extract<MessageContentPart, { + type: "tool-call"; + }>; + index: number; + }) => ReactElement; + renderImage?: (props: { + part: Extract<MessageContentPart, { + type: "image"; + }>; + index: number; + }) => ReactElement; + renderReasoning?: (props: { + part: Extract<MessageContentPart, { + type: "reasoning"; + }>; + index: number; + }) => ReactElement; + renderSource?: (props: { + part: Extract<MessageContentPart, { + type: "source"; + }>; + index: number; + }) => ReactElement; + renderFile?: (props: { + part: Extract<MessageContentPart, { + type: "file"; + }>; + index: number; + }) => ReactElement; + renderData?: (props: { + part: Extract<MessageContentPart, { + type: "data"; + }>; + index: number; + }) => ReactElement; +}; + +interface MessageFormatAdapter<TMessage, TStorageFormat extends Record<string, unknown>> { + format: string; + encode(item: MessageFormatItem<TMessage>): TStorageFormat; + decode(stored: MessageStorageEntry<TStorageFormat>): MessageFormatItem<TMessage>; + getId(message: TMessage): string; +} + +interface MessageFormatItem<TMessage> { + parentId: string | null; + message: TMessage; +} + +interface MessageFormatRepository<TMessage> { + headId?: string | null; + messages: MessageFormatItem<TMessage>[]; +} + +declare const MessageIf: (_param21: MessageIfProps) => import("react").JSX.Element | null; + +type MessageIfProps = { + children: ReactNode; + user?: boolean | undefined; + assistant?: boolean | undefined; + running?: boolean | undefined; + last?: boolean | undefined; +}; + +type MessagePartRuntime = { + addToolResult(result: any | ToolResponse<any>): void; + resumeToolCall(payload: unknown): void; + respondToToolApproval(response: ToolApprovalResponse): void; + readonly path: MessagePartRuntimePath; + getState(): MessagePartState; + subscribe(callback: () => void): Unsubscribe$1; +}; + +declare class MessagePartRuntimeImpl implements MessagePartRuntime { + private contentBinding; + private messageApi?; + private threadApi?; + get path(): MessagePartRuntimePath; + constructor(contentBinding: MessagePartSnapshotBinding, messageApi?: MessageStateBinding | undefined, threadApi?: ThreadRuntimeCoreBinding | undefined); + protected __internal_bindMethods(): void; + getState(): MessagePartState; + addToolResult(result: any | ToolResponse<any>): void; + resumeToolCall(payload: unknown): void; + respondToToolApproval(response: ToolApprovalResponse): void; + subscribe(callback: () => void): Unsubscribe$1; +} + +type MessagePartRuntimePath = MessageRuntimePath & { + readonly messagePartSelector: { + readonly type: "index"; + readonly index: number; + } | { + readonly type: "toolCallId"; + readonly toolCallId: string; + }; +}; + +type MessagePartSnapshotBinding = SubscribableWithState<MessagePartState, MessagePartRuntimePath>; + +type MessagePartState = (ThreadUserMessagePart | ThreadAssistantMessagePart) & { + readonly status: MessagePartStatus | ToolCallMessagePartStatus; +}; + +type MessagePartStatus = { + readonly type: "running"; +} | { + readonly type: "complete"; +} | { + readonly type: "incomplete"; + readonly reason: "cancelled" | "content-filter" | "error" | "length" | "other"; + readonly error?: unknown; +}; + +declare namespace MessagePrimitiveAttachmentByIndex { + type Props = { + index: number; + components?: MessageAttachmentsComponentConfig; + }; +} + +declare const MessagePrimitiveAttachmentByIndex: FC<MessagePrimitiveAttachmentByIndex.Props>; + +declare namespace MessagePrimitiveAttachments { + type Props = { + components: MessageAttachmentsComponentConfig; + children?: never; + } | { + children: (value: { + attachment: CompleteAttachment; + }) => ReactNode; + components?: never; + }; +} + +declare const MessagePrimitiveAttachments: FC<MessagePrimitiveAttachments.Props>; + +declare namespace MessagePrimitiveGroupedParts { + type GroupPart<TKey extends `group-${string}` = `group-${string}`> = { + readonly type: TKey; + readonly status: MessagePartStatus | ToolCallMessagePartStatus; + readonly indices: readonly number[]; + }; + type IndicatorPart = { + readonly type: "indicator"; + }; + type IndicatorMode = "always" | "empty" | "never" | "no-text"; + type RenderInfo<TKey extends `group-${string}` = `group-${string}`> = { + readonly part: GroupPart<TKey> | EnrichedPartState | IndicatorPart; + readonly children: ReactNode; + }; + type Props<TKey extends `group-${string}` = `group-${string}`> = { + readonly groupBy: (part: PartState, context: GroupByContext) => readonly TKey[] | null; + readonly indicator?: IndicatorMode; + readonly children: (info: RenderInfo<TKey>) => ReactNode; + }; +} + +declare const MessagePrimitiveGroupedParts: { + <TKey extends `group-${string}`>(_param22: MessagePrimitiveGroupedParts.Props<TKey>): ReactNode; + displayName: string; +}; + +declare namespace MessagePrimitivePartByIndex { + type Props = { + index: number; + components: MessagePrimitiveParts$1.Props["components"]; + }; +} + +declare const MessagePrimitivePartByIndex: FC<MessagePrimitivePartByIndex.Props>; + +declare namespace MessagePrimitiveParts { + type Props = MessagePrimitiveParts$1.Props; +} + +declare const MessagePrimitiveParts: FC<MessagePrimitiveParts.Props>; + +declare namespace MessagePrimitiveParts$1 { + type DataConfig = { + by_name?: Record<string, DataMessagePartComponent | undefined> | undefined; + Fallback?: DataMessagePartComponent | undefined; + }; + type BaseComponents = { + Empty?: EmptyMessagePartComponent | undefined; + Text?: TextMessagePartComponent | undefined; + Source?: SourceMessagePartComponent | undefined; + Image?: ImageMessagePartComponent | undefined; + File?: FileMessagePartComponent | undefined; + Unstable_Audio?: Unstable_AudioMessagePartComponent | undefined; + data?: DataConfig | undefined; + Quote?: QuoteMessagePartComponent | undefined; + generativeUI?: { + components: GenerativeUIComponentRegistry; + Fallback?: ComponentType<{ + component: string; + props?: unknown; + }> | undefined; + } | undefined; + }; + type ToolsConfig = { + by_name?: Record<string, ToolCallMessagePartComponent | undefined> | undefined; + Fallback?: ComponentType<ToolCallMessagePartProps> | undefined; + } | { + Override: ComponentType<ToolCallMessagePartProps>; + }; + type StandardComponents = BaseComponents & { + Reasoning?: ReasoningMessagePartComponent | undefined; + tools?: ToolsConfig | undefined; + ToolGroup?: ComponentType<PropsWithChildren<{ + startIndex: number; + endIndex: number; + }>>; + ReasoningGroup?: ReasoningGroupComponent; + ChainOfThought?: never; + }; + type ChainOfThoughtComponents = BaseComponents & { + ChainOfThought: ComponentType; + Reasoning?: never; + tools?: never; + ToolGroup?: never; + ReasoningGroup?: never; + }; + export type Props = { + components?: StandardComponents | ChainOfThoughtComponents | undefined; + unstable_showEmptyOnNonTextEnd?: boolean | undefined; + children?: never; + } | { + children: (value: { + part: EnrichedPartState; + }) => ReactNode; + components?: never; + unstable_showEmptyOnNonTextEnd?: never; + }; + export {}; +} + +declare const MessagePrimitiveParts$1: FC<MessagePrimitiveParts$1.Props>; + +declare class MessageRepository { + private messages; + private head; + private root; + private updateLevels; + private performOp; + private _messages; + get headId(): string | null; + get canonicalHeadId(): string | null; + getMessages(headId?: string): readonly ThreadMessage[]; + addOrUpdateMessage(parentId: string | null, message: ThreadMessage): void; + getMessage(messageId: string): { + parentId: string | null; + message: ThreadMessage; + index: number; + }; + deleteMessage(messageId: string, replacementId?: string | null | undefined): void; + getBranches(messageId: string): string[]; + private evictOffBranchOptimisticMessages; + switchToBranch(messageId: string): void; + resetHead(messageId: string | null): void; + clear(): void; + export(): ExportedMessageRepository; + import(_param23: ExportedMessageRepository): void; +} + +type MessageRole = ThreadMessage["role"]; + +declare const MessageRoot: (_param24: MessageRootProps) => import("react").JSX.Element; + +type MessageRootProps = ViewProps & { + children: ReactNode; +}; + +type MessageRuntime = { + readonly path: MessageRuntimePath; + readonly composer: EditComposerRuntime; + getState(): MessageState$1; + delete(): void | Promise<void>; + reload(config?: ReloadConfig): void; + speak(): void; + stopSpeaking(): void; + submitFeedback(_param25: { + type: "positive" | "negative"; + }): void; + switchToBranch(_param26: { + position?: "previous" | "next" | undefined; + branchId?: string | undefined; + }): void; + unstable_getCopyText(): string; + subscribe(callback: () => void): Unsubscribe$1; + getMessagePartByIndex(idx: number): MessagePartRuntime; + getMessagePartByToolCallId(toolCallId: string): MessagePartRuntime; + getAttachmentByIndex(idx: number): AttachmentRuntime & { + source: "message"; + }; +}; + +declare class MessageRuntimeImpl implements MessageRuntime { + private _core; + private _threadBinding; + get path(): MessageRuntimePath; + constructor(_core: MessageStateBinding, _threadBinding: ThreadRuntimeCoreBinding); + protected __internal_bindMethods(): void; + readonly composer: EditComposerRuntimeImpl; + private _getEditComposerRuntimeCore; + getState(): ThreadMessage & { + readonly parentId: string | null; + readonly index: number; + readonly isLast: boolean; + readonly branchNumber: number; + readonly branchCount: number; + readonly speech: SpeechState | undefined; + }; + delete(): void | Promise<void>; + reload(reloadConfig?: ReloadConfig): void; + speak(): void; + stopSpeaking(): void; + submitFeedback(_param27: { + type: "positive" | "negative"; + }): void; + switchToBranch(_param28: { + position?: "previous" | "next" | undefined; + branchId?: string | undefined; + }): void; + unstable_getCopyText(): string; + subscribe(callback: () => void): Unsubscribe$1; + getMessagePartByIndex(idx: number): MessagePartRuntimeImpl; + getMessagePartByToolCallId(toolCallId: string): MessagePartRuntimeImpl; + getAttachmentByIndex(idx: number): MessageAttachmentRuntimeImpl; +} + +type MessageRuntimePath = ThreadRuntimePath & { + readonly messageSelector: { + readonly type: "messageId"; + readonly messageId: string; + } | { + readonly type: "index"; + readonly index: number; + }; +}; + +type MessageState = ThreadMessage & { + readonly parentId: string | null; + readonly isLast: boolean; + readonly branchNumber: number; + readonly branchCount: number; + readonly speech: SpeechState | undefined; + readonly composer: ComposerState; + readonly parts: readonly PartState[]; + readonly isCopied: boolean; + readonly isHovering: boolean; + readonly index: number; +}; + +type MessageState$1 = ThreadMessage & { + readonly parentId: string | null; + readonly index: number; + readonly isLast: boolean; + readonly branchNumber: number; + readonly branchCount: number; + readonly speech: SpeechState | undefined; +}; + +type MessageStateBinding = SubscribableWithState<ThreadMessage & { + readonly parentId: string | null; + readonly index: number; + readonly isLast: boolean; + readonly branchNumber: number; + readonly branchCount: number; + readonly speech: SpeechState | undefined; +}, MessageRuntimePath>; + +type MessageStatus = { + readonly type: "running"; +} | { + readonly type: "requires-action"; + readonly reason: "interrupt" | "tool-calls"; +} | { + readonly type: "complete"; + readonly reason: "stop" | "unknown"; +} | { + readonly type: "incomplete"; + readonly reason: "cancelled" | "content-filter" | "error" | "length" | "other" | "tool-calls"; + readonly error?: ReadonlyJSONValue; +}; + +interface MessageStorageEntry<TPayload> { + id: string; + parent_id: string | null; + format: string; + content: TPayload; +} + +type MessageTiming = { + readonly streamStartTime: number; + readonly firstTokenTime?: number; + readonly totalStreamTime?: number; + readonly tokenCount?: number; + readonly tokensPerSecond?: number; + readonly totalChunks: number; + readonly toolCallCount: number; +}; + +type MessagesComponentConfig = { + Message: ComponentType; + EditComposer?: ComponentType | undefined; + UserEditComposer?: ComponentType | undefined; + AssistantEditComposer?: ComponentType | undefined; + SystemEditComposer?: ComponentType | undefined; + UserMessage?: ComponentType | undefined; + AssistantMessage?: ComponentType | undefined; + SystemMessage?: ComponentType | undefined; +} | { + Message?: ComponentType | undefined; + EditComposer?: ComponentType | undefined; + UserEditComposer?: ComponentType | undefined; + AssistantEditComposer?: ComponentType | undefined; + SystemEditComposer?: ComponentType | undefined; + UserMessage: ComponentType; + AssistantMessage: ComponentType; + SystemMessage?: ComponentType | undefined; +}; + +type MessagesContent = { + components: MessageComponents; + children?: never; +} | { + children: (value: { + message: MessageState$1; + }) => ReactNode; + components?: never; +}; + +declare const ModelContext: Resource<ClientOutput<"modelContext">, [ +]>; + +type ModelContext$1 = { + priority?: number | undefined; + system?: string | undefined; + tools?: Record<string, Tool<any, any>> | undefined; + callSettings?: LanguageModelV1CallSettings | undefined; + config?: LanguageModelConfig | undefined; + unstable_composerMetadata?: Record<string, unknown> | undefined; +}; + +type ModelContextProvider = { + getModelContext: () => ModelContext$1; + subscribe?: (callback: () => void) => Unsubscribe$1; +}; + +declare class ModelContextRegistry implements ModelContextProvider { + private _tools; + private _instructions; + private _providers; + private _subscribers; + private _providerUnsubscribes; + getModelContext(): ModelContext$1; + subscribe(callback: () => void): Unsubscribe$1; + private notifySubscribers; + addTool<TArgs extends Record<string, unknown>, TResult>(tool: AssistantToolProps$1<TArgs, TResult>): ModelContextRegistryToolHandle<TArgs, TResult>; + addInstruction(config: string | AssistantInstructionsConfig): ModelContextRegistryInstructionHandle; + addProvider(provider: ModelContextProvider): ModelContextRegistryProviderHandle; +} + +interface ModelContextRegistryInstructionHandle { + update(config: string | AssistantInstructionsConfig): void; + remove(): void; +} + +interface ModelContextRegistryProviderHandle { + remove(): void; +} + +interface ModelContextRegistryToolHandle<TArgs extends Record<string, unknown> = any, TResult = any> { + update(tool: AssistantToolProps$1<TArgs, TResult>): void; + remove(): void; +} + +type ObjectKey<T> = keyof T & (string | number); + +type ObjectStreamOperation = { + readonly type: "set"; + readonly path: readonly string[]; + readonly value: ReadonlyJSONValue; +} | { + readonly type: "append-text"; + readonly path: readonly string[]; + readonly value: string; +}; + +type OnSchemaValidationErrorFunction<TResult> = ToolExecuteFunction<unknown, TResult>; + +type OverrideOptionalField<T, TKey extends keyof T, TValue> = undefined extends T[TKey] ? Exclude<T[TKey], undefined> extends never ? { + [K in TKey]?: undefined; +} : { + [K in TKey]?: TValue | undefined; +} : { + [K in TKey]: TValue; +}; + +type OverrideToolDeclarationCallbacks<T extends { + streamCall?: unknown; +}, TArgs extends Record<string, unknown>, TResult> = Omit<T, "execute" | "experimental_onSchemaValidationError" | "streamCall" | "toModelOutput" | "type"> & { + type?: never; +} & ("execute" extends keyof T ? OverrideOptionalField<T, "execute", ToolExecute<NoInfer<TArgs>, TResult>> : {}) & ("toModelOutput" extends keyof T ? OverrideOptionalField<T, "toModelOutput", ToolModelOutputFunction<NoInfer<TArgs>, NoInfer<TResult>>> : {}) & ("experimental_onSchemaValidationError" extends keyof T ? OverrideOptionalField<T, "experimental_onSchemaValidationError", (args: unknown, context: ToolExecuteContext) => NoInfer<TResult> | Promise<NoInfer<TResult>>> : {}) & OverrideOptionalField<T, "streamCall", ToolStreamCall<TArgs, unknown>>; + +type ParentOf<K extends ClientNames> = AssistantClientAccessor<K> extends { + source: infer S; +} ? S extends ClientNames ? S : never : never; + +declare const PartByIndexProvider: FC<PropsWithChildren<{ + index: number; +}>>; + +type PartInit = { + readonly type: "reasoning" | "text"; + readonly parentId?: string; +} | { + readonly type: "tool-call"; + readonly toolCallId: string; + readonly toolName: string; + readonly parentId?: string; +} | { + readonly type: "source"; + readonly sourceType: "url"; + readonly id: string; + readonly url: string; + readonly title?: string; + readonly parentId?: string; +} | { + readonly type: "file"; + readonly data: string; + readonly mimeType: string; + readonly parentId?: string; +} | { + readonly type: "data"; + readonly name: string; + readonly data: ReadonlyJSONValue; + readonly parentId?: string; +}; + +type PartMethods = { + getState(): PartState; + addToolResult(result: unknown | ToolResponse<unknown>): void; + resumeToolCall(payload: unknown): void; + respondToToolApproval(response: ToolApprovalResponse): void; + __internal_getRuntime?(): MessagePartRuntime; +}; + +type PartProviderMetadata = { + readonly [providerName: string]: ReadonlyJSONObject; +}; + +type PartState = (ThreadUserMessagePart | ThreadAssistantMessagePart) & { + readonly status: MessagePartStatus | ToolCallMessagePartStatus; +}; + +type PdfToImagesRequestBody = { + file_blob?: string | undefined; + file_url?: string | undefined; +}; + +type PdfToImagesResponse = { + success: boolean; + urls: string[]; + message: string; +}; + +type PendingAttachment = BaseAttachment & { + status: PendingAttachmentStatus; + file: File; +}; + +type PendingAttachmentStatus = { + type: "running"; + reason: "uploading"; + progress: number; +} | { + type: "requires-action"; + reason: "composer-send"; +} | { + type: "incomplete"; + reason: "error" | "upload-paused"; + message?: string; +}; + +type PropFieldStatus = "complete" | "streaming"; + +type ProviderOptions = Record<string, Record<string, unknown>>; + +type ProviderTool<TArgs extends Record<string, unknown> = Record<string, unknown>, TResult = unknown> = ToolBase<TArgs, TResult> & { + type: "provider"; + providerId: `${string}.${string}`; + parameters?: StandardSchemaV1<TArgs> | JSONSchema7 | undefined; + args: Record<string, unknown>; + supportsDeferredResults?: boolean; + description?: undefined; + disabled?: boolean; + execute?: undefined; + toModelOutput?: undefined; + experimental_onSchemaValidationError?: undefined; + providerOptions?: ProviderOptions; +}; + +type ProviderToolConfig<TArgs extends Record<string, unknown> = Record<string, unknown>> = Pick<ProviderToolDefinition<TArgs>, "args" | "parameters" | "providerId" | "providerOptions" | "supportsDeferredResults">; + +type ProviderToolDefinition<TArgs extends Record<string, unknown>> = Extract<Tool<TArgs, unknown>, { + type: "provider"; +}>; + +type QueueItemState = { + readonly id: string; + readonly prompt: string; +}; + +type QuoteInfo = { + readonly text: string; + readonly messageId: string; +}; + +type QuoteMessagePartComponent = ComponentType<QuoteMessagePartProps>; + +type QuoteMessagePartProps = QuoteInfo; + +type ReadonlyJSONArray = readonly ReadonlyJSONValue[]; + +type ReadonlyJSONObject = { + readonly [key: string]: ReadonlyJSONValue; +}; + +type ReadonlyJSONValue = null | string | number | boolean | ReadonlyJSONObject | ReadonlyJSONArray; + +declare namespace RealtimeVoiceAdapter { + type Status = { + type: "running" | "starting"; + } | { + type: "ended"; + reason: "cancelled" | "error" | "finished"; + error?: unknown; + }; + type Mode = "listening" | "speaking"; + type TranscriptItem = { + role: "assistant" | "user"; + text: string; + isFinal?: boolean; + }; + type Session = { + status: Status; + isMuted: boolean; + disconnect: () => void; + mute: () => void; + unmute: () => void; + onStatusChange: (callback: (status: Status) => void) => Unsubscribe$1; + onTranscript: (callback: (transcript: TranscriptItem) => void) => Unsubscribe$1; + onModeChange: (callback: (mode: Mode) => void) => Unsubscribe$1; + onVolumeChange: (callback: (volume: number) => void) => Unsubscribe$1; + }; +} + +type RealtimeVoiceAdapter = { + connect: (options: { + abortSignal?: AbortSignal; + }) => RealtimeVoiceAdapter.Session; +}; + +type ReasoningGroupComponent = ComponentType<ReasoningGroupProps>; + +type ReasoningGroupProps = PropsWithChildren<{ + startIndex: number; + endIndex: number; +}>; + +type ReasoningMessagePart = { + readonly type: "reasoning"; + readonly text: string; + readonly providerMetadata?: PartProviderMetadata; + readonly parentId?: string; +}; + +type ReasoningMessagePartComponent = ComponentType<ReasoningMessagePartProps>; + +type ReasoningMessagePartProps = MessagePartState & ReasoningMessagePart; + +type ReloadConfig = { + runConfig?: RunConfig; +}; + +type RemoteThreadInitializeResponse = { + remoteId: string; + externalId: string | undefined; +}; + +type RemoteThreadListAdapter = { + list(params?: RemoteThreadListPageOptions): Promise<RemoteThreadListResponse>; + rename(remoteId: string, newTitle: string): Promise<void>; + updateCustom?(remoteId: string, custom: Record<string, unknown> | undefined): Promise<void>; + archive(remoteId: string): Promise<void>; + unarchive(remoteId: string): Promise<void>; + delete(remoteId: string): Promise<void>; + initialize(threadId: string): Promise<RemoteThreadInitializeResponse>; + generateTitle(remoteId: string, unstable_messages: readonly ThreadMessage[]): Promise<AssistantStream>; + fetch(threadId: string): Promise<RemoteThreadMetadata>; + unstable_Provider?: ComponentType<PropsWithChildren> | undefined; +}; + +type RemoteThreadListOptions = { + runtimeHook: () => AssistantRuntime; + adapter: RemoteThreadListAdapter; + initialThreadId?: string | undefined; + threadId?: string | undefined; + onThreadIdChange?: ((threadId: string | undefined) => void) | undefined; + allowNesting?: boolean | undefined; +}; + +type RemoteThreadListPageOptions = { + after?: string | undefined; +}; + +type RemoteThreadListResponse = { + threads: RemoteThreadMetadata[]; + nextCursor?: string | undefined; +}; + +type RemoteThreadMetadata = { + readonly status: "archived" | "regular"; + readonly remoteId: string; + readonly externalId?: string | undefined; + readonly title?: string | undefined; + readonly lastMessageAt?: Date | undefined; + readonly custom?: Record<string, unknown> | undefined; +}; + +type ReportToolCall = { + tool_name: string; + tool_call_id: string; + tool_args?: string; + tool_result?: string; + tool_source?: "backend" | "frontend" | "mcp"; + start_ms?: number; + end_ms?: number; + sampling_calls?: SamplingCallData[]; +}; + +type RequireAtLeastOne<T, Keys extends keyof T = keyof T> = Pick<T, Exclude<keyof T, Keys>> & { + [K in Keys]-?: Required<Pick<T, K>> & Partial<Pick<T, Exclude<Keys, K>>>; +}[Keys]; + +type Resource<R, A extends readonly unknown[] = any[]> = (...args: A) => ResourceElement<R, A>; + +type ResourceElement<R, A extends readonly unknown[] = any[]> = { + readonly hook: (...args: A) => R; + readonly args: Readonly<A>; + readonly key?: string | number; + readonly deps?: readonly unknown[]; +}; + +type RespondToToolApprovalOptions = { + approvalId: string; + approved: boolean; + optionId?: string; + reason?: string; +}; + +type ResumeRunConfig = StartRunConfig & { + stream?: (options: ChatModelRunOptions) => AsyncGenerator<ChatModelRunResult, void, unknown>; +}; + +type ResumeToolCallOptions = { + toolCallId: string; + payload: unknown; +}; + +type RunConfig = { + readonly custom?: Record<string, unknown>; +}; + +declare namespace RuntimeAdapterProvider { + type Props = { + adapters: RuntimeAdapters; + children: ReactNode; + }; +} + +declare const RuntimeAdapterProvider: FC<RuntimeAdapterProvider.Props>; + +type RuntimeAdapters = { + modelContext?: ModelContextProvider | undefined; + history?: ThreadHistoryAdapter | undefined; + attachments?: AttachmentAdapter | undefined; +}; + +type RuntimeCapabilities = { + readonly switchToBranch: boolean; + readonly switchBranchDuringRun: boolean; + readonly edit: boolean; + readonly reload: boolean; + readonly delete: boolean; + readonly cancel: boolean; + readonly unstable_copy: boolean; + readonly speech: boolean; + readonly dictation: boolean; + readonly voice: boolean; + readonly attachments: boolean; + readonly feedback: boolean; + readonly queue: boolean; +}; + +type SamplingCallData = { + model_id?: string; + input_tokens?: number; + output_tokens?: number; + reasoning_tokens?: number; + cached_input_tokens?: number; + duration_ms?: number; +}; + +interface ScopeRegistry { + [key: string]: { methods: any; meta?: any; events?: any }; +} + +type SendOptions = { + startRun?: boolean; + steer?: boolean; +}; + +declare class SimpleImageAttachmentAdapter implements AttachmentAdapter { + accept: string; + add(state: { + file: File; + }): Promise<PendingAttachment>; + send(attachment: PendingAttachment): Promise<CompleteAttachment>; + remove(): Promise<void>; +} + +declare class SimpleTextAttachmentAdapter implements AttachmentAdapter { + accept: string; + add(state: { + file: File; + }): Promise<PendingAttachment>; + send(attachment: PendingAttachment): Promise<CompleteAttachment>; + remove(): Promise<void>; +} + +type SnapshotCarrierMessage = { + role: string; + metadata?: unknown; + content?: readonly unknown[] | undefined; +}; + +type SourceMessagePart = { + readonly type: "source"; + readonly sourceType: "url"; + readonly id: string; + readonly url: string; + readonly title?: string; + readonly providerMetadata?: SourceProviderMetadata; + readonly parentId?: string; +} | { + readonly type: "source"; + readonly sourceType: "document"; + readonly id: string; + readonly url?: undefined; + readonly title: string; + readonly mediaType: string; + readonly filename?: string; + readonly providerMetadata?: SourceProviderMetadata; + readonly parentId?: string; +}; + +type SourceMessagePartComponent = ComponentType<SourceMessagePartProps>; + +type SourceMessagePartProps = MessagePartState & SourceMessagePart; + +type SourceProviderMetadata = PartProviderMetadata; + +interface SpeechRecognitionConstructor { + new (): SpeechRecognitionInstance; +} + +interface SpeechRecognitionInstance extends EventTarget { + lang: string; + continuous: boolean; + interimResults: boolean; + start(): void; + stop(): void; + abort(): void; +} + +type SpeechState = { + readonly messageId: string; + readonly status: SpeechSynthesisAdapter.Status; +}; + +declare namespace SpeechSynthesisAdapter { + type Status = { + type: "running" | "starting"; + } | { + type: "ended"; + reason: "cancelled" | "error" | "finished"; + error?: unknown; + }; + type Utterance = { + status: Status; + cancel: () => void; + subscribe: (callback: () => void) => Unsubscribe$1; + }; +} + +type SpeechSynthesisAdapter = { + speak: (text: string) => SpeechSynthesisAdapter.Utterance; +}; + +type StartRunConfig = { + parentId: string | null; + sourceId: string | null; + runConfig: RunConfig; +}; + +type StateUpdater<TState> = TState | ((prev: TState) => TState); + +type StateUpdater$1<TState> = TState | ((prev: TState) => TState); + +type SubmitFeedbackOptions = { + messageId: string; + type: "negative" | "positive"; +}; + +type Subscribable = { + subscribe: (callback: () => void) => Unsubscribe$1; +}; + +type SubscribableWithState<TState, TPath> = Subscribable & { + path: TPath; + getState: () => TState; +}; + +type SuggestionAdapter = { + generate: (options: SuggestionAdapterGenerateOptions) => Promise<readonly ThreadSuggestion$1[]> | AsyncGenerator<readonly ThreadSuggestion$1[], void>; +}; + +type SuggestionAdapterGenerateOptions = { + messages: readonly ThreadMessage[]; +}; + +declare const SuggestionByIndexProvider: FC<SuggestionByIndexProviderProps>; + +type SuggestionByIndexProviderProps = PropsWithChildren<{ + index: number; +}>; + +type SuggestionConfig = string | { + title: string; + label: string; + prompt: string; +}; + +declare const SuggestionDescription: (_param29: SuggestionDescriptionProps) => import("react").JSX.Element; + +type SuggestionDescriptionProps = TextProps & { + children?: ReactNode; +}; + +type SuggestionState = { + title: string; + label: string; + prompt: string; +}; + +declare const SuggestionTitle: (_param30: SuggestionTitleProps) => import("react").JSX.Element; + +type SuggestionTitleProps = TextProps & { + children?: ReactNode; +}; + +declare const SuggestionTrigger: (_param31: SuggestionTriggerProps) => import("react").JSX.Element; + +type SuggestionTriggerProps = Omit<PressableProps, "onPress"> & { + children: ReactNode; + send?: boolean | undefined; + clearComposer?: boolean | undefined; +}; + +declare const Suggestions: Resource<ClientOutput<"suggestions">, [ + suggestions?: SuggestionConfig[] | undefined +]>; + +type SuggestionsComponentConfig = { + Suggestion: ComponentType; +}; + +declare const TOOL_RESPONSE_SYMBOL: unique symbol; + +type TextMessagePart = { + readonly type: "text"; + readonly text: string; + readonly providerMetadata?: PartProviderMetadata; + readonly parentId?: string; +}; + +type TextMessagePartComponent = ComponentType<TextMessagePartProps>; + +type TextMessagePartProps = MessagePartState & TextMessagePart; + +declare const TextMessagePartProvider: FC<PropsWithChildren<{ + text: string; + isRunning?: boolean; +}>>; + +type ThreadAssistantMessage = MessageCommonProps & { + readonly role: "assistant"; + readonly content: readonly ThreadAssistantMessagePart[]; + readonly status: MessageStatus; + readonly metadata: { + readonly unstable_state: ReadonlyJSONValue; + readonly unstable_annotations: readonly ReadonlyJSONValue[]; + readonly unstable_data: readonly ReadonlyJSONValue[]; + readonly steps: readonly ThreadStep[]; + readonly submittedFeedback?: { + readonly type: "negative" | "positive"; + }; + readonly timing?: MessageTiming; + readonly isOptimistic?: boolean; + readonly custom: Record<string, unknown>; + }; +}; + +type ThreadAssistantMessagePart = TextMessagePart | ReasoningMessagePart | ToolCallMessagePart | SourceMessagePart | FileMessagePart | ImageMessagePart | DataMessagePart | GenerativeUIMessagePart; + +declare class ThreadComposerAttachmentRuntimeImpl extends ComposerAttachmentRuntime<"thread-composer"> { + get source(): "thread-composer"; +} + +type ThreadComposerAttachmentState = Attachment & { + readonly source: "thread-composer"; +}; + +type ThreadComposerRuntime = Omit<ComposerRuntime, "getAttachmentByIndex" | "getState"> & { + readonly path: ComposerRuntimePath & { + composerSource: "thread"; + }; + readonly type: "thread"; + getState(): ThreadComposerState; + getAttachmentByIndex(idx: number): AttachmentRuntime & { + source: "thread-composer"; + }; +}; + +type ThreadComposerRuntimeCore = ComposerRuntimeCore; + +type ThreadComposerRuntimeCoreBinding = SubscribableWithState<ThreadComposerRuntimeCore | undefined, ComposerRuntimePath & { + composerSource: "thread"; +}>; + +declare class ThreadComposerRuntimeImpl extends ComposerRuntimeImpl implements ThreadComposerRuntime { + get path(): ComposerRuntimePath & { + composerSource: "thread"; + }; + get type(): "thread"; + private _getState; + constructor(core: ThreadComposerRuntimeCoreBinding); + getState(): ThreadComposerState; + getAttachmentByIndex(idx: number): ThreadComposerAttachmentRuntimeImpl; +} + +type ThreadComposerState = BaseComposerState & { + readonly type: "thread"; +}; + +declare const ThreadEmpty: (_param32: ThreadEmptyProps) => import("react").JSX.Element | null; + +type ThreadEmptyProps = { + children: ReactNode; +}; + +type ThreadHistoryAdapter = { + load(): Promise<ExportedMessageRepository & { + state?: ReadonlyJSONValue; + unstable_resume?: boolean; + }>; + resume?(options: ChatModelRunOptions): AsyncGenerator<ChatModelRunResult, void, unknown>; + append(item: ExportedMessageRepositoryItem): Promise<void>; + delete?(items: ExportedMessageRepositoryItem[]): Promise<void>; + withFormat?<TMessage, TStorageFormat extends Record<string, unknown>>(formatAdapter: MessageFormatAdapter<TMessage, TStorageFormat>): GenericThreadHistoryAdapter<TMessage>; +}; + +declare const ThreadIf: (_param33: ThreadIfProps) => import("react").JSX.Element | null; + +type ThreadIfProps = { + children: ReactNode; + empty?: boolean | undefined; + running?: boolean | undefined; +}; + +declare const ThreadListItemArchive: (_param34: ThreadListItemArchiveProps) => import("react").JSX.Element; + +type ThreadListItemArchiveProps = Omit<PressableProps, "onPress"> & { + children: ReactNode; +}; + +declare const ThreadListItemByIndexProvider: FC<PropsWithChildren<{ + index: number; + archived: boolean; +}>>; + +type ThreadListItemCoreState = { + readonly id: string; + readonly remoteId: string | undefined; + readonly externalId: string | undefined; + readonly status: ThreadListItemStatus; + readonly title?: string | undefined; + readonly lastMessageAt?: Date | undefined; + readonly custom?: Record<string, unknown> | undefined; + readonly runtime?: ThreadRuntimeCore | undefined; +}; + +declare const ThreadListItemDelete: (_param35: ThreadListItemDeleteProps) => import("react").JSX.Element; + +type ThreadListItemDeleteProps = Omit<PressableProps, "onPress"> & { + children: ReactNode; +}; + +type ThreadListItemEventCallback<E extends ThreadListItemEventType> = (payload: ThreadListItemEventPayload[E]) => void; + +type ThreadListItemEventPayload = { + switchedTo: Record<string, never>; + switchedAway: Record<string, never>; +}; + +type ThreadListItemEventType = keyof ThreadListItemEventPayload; + +declare namespace ThreadListItemPrimitiveTitle { + type Props = { + fallback?: ReactNode; + }; +} + +declare const ThreadListItemPrimitiveTitle: FC<ThreadListItemPrimitiveTitle.Props>; + +declare const ThreadListItemRoot: (_param36: ThreadListItemRootProps) => import("react").JSX.Element; + +type ThreadListItemRootProps = ViewProps & { + children: ReactNode; +}; + +type ThreadListItemRuntime = { + readonly path: ThreadListItemRuntimePath; + getState(): ThreadListItemState$1; + initialize(): Promise<{ + remoteId: string; + externalId: string | undefined; + }>; + generateTitle(): Promise<void>; + switchTo(options?: { + unarchive?: boolean; + }): Promise<void>; + rename(newTitle: string): Promise<void>; + updateCustom(custom: Record<string, unknown> | undefined): Promise<void>; + archive(): Promise<void>; + unarchive(): Promise<void>; + delete(): Promise<void>; + detach(): void; + subscribe(callback: () => void): Unsubscribe$1; + unstable_on<E extends ThreadListItemEventType>(event: E, callback: ThreadListItemEventCallback<E>): Unsubscribe$1; + __internal_getRuntime(): ThreadListItemRuntime; +}; + +type ThreadListItemRuntimeBinding = SubscribableWithState<ThreadListItemState$1, ThreadListItemRuntimePath>; + +declare class ThreadListItemRuntimeImpl implements ThreadListItemRuntime { + private _core; + private _threadListBinding; + get path(): ThreadListItemRuntimePath; + constructor(_core: ThreadListItemStateBinding, _threadListBinding: ThreadListRuntimeCoreBinding); + protected __internal_bindMethods(): void; + getState(): ThreadListItemState$1; + switchTo(options?: { + unarchive?: boolean; + }): Promise<void>; + rename(newTitle: string): Promise<void>; + updateCustom(custom: Record<string, unknown> | undefined): Promise<void>; + archive(): Promise<void>; + unarchive(): Promise<void>; + delete(): Promise<void>; + initialize(): Promise<{ + remoteId: string; + externalId: string | undefined; + }>; + generateTitle(): Promise<void>; + unstable_on<E extends ThreadListItemEventType>(event: E, callback: ThreadListItemEventCallback<E>): Unsubscribe$1; + subscribe(callback: () => void): Unsubscribe$1; + detach(): void; + __internal_getRuntime(): ThreadListItemRuntime; +} + +type ThreadListItemRuntimePath = { + readonly ref: string; + readonly threadSelector: { + readonly type: "main"; + } | { + readonly type: "index"; + readonly index: number; + } | { + readonly type: "archiveIndex"; + readonly index: number; + } | { + readonly type: "threadId"; + readonly threadId: string; + }; +}; + +declare const ThreadListItemRuntimeProvider: FC<PropsWithChildren<{ + runtime: ThreadListItemRuntime; +}>>; + +type ThreadListItemState = { + readonly id: string; + readonly remoteId: string | undefined; + readonly externalId: string | undefined; + readonly title?: string | undefined; + readonly lastMessageAt?: Date | undefined; + readonly status: ThreadListItemStatus; + readonly custom?: Record<string, unknown> | undefined; +}; + +type ThreadListItemState$1 = { + readonly isMain: boolean; + readonly id: string; + readonly remoteId: string | undefined; + readonly externalId: string | undefined; + readonly status: ThreadListItemStatus; + readonly title?: string | undefined; + readonly lastMessageAt?: Date | undefined; + readonly custom?: Record<string, unknown> | undefined; +}; + +type ThreadListItemStateBinding = SubscribableWithState<ThreadListItemState$1, ThreadListItemRuntimePath>; + +type ThreadListItemStatus = "archived" | "deleted" | "new" | "regular"; + +declare const ThreadListItemTrigger: (_param37: ThreadListItemTriggerProps) => import("react").JSX.Element; + +type ThreadListItemTriggerProps = Omit<PressableProps, "onPress"> & { + children: ReactNode; +}; + +declare const ThreadListItemUnarchive: (_param38: ThreadListItemUnarchiveProps) => import("react").JSX.Element; + +type ThreadListItemUnarchiveProps = Omit<PressableProps, "onPress"> & { + children: ReactNode; +}; + +declare const ThreadListItems: (_param39: ThreadListItemsProps) => import("react").JSX.Element; + +type ThreadListItemsProps = Omit<FlatListProps<string>, "data" | "renderItem"> & { + renderItem: (props: { + threadId: string; + index: number; + }) => ReactElement; +}; + +declare const ThreadListNew: (_param40: ThreadListNewProps) => import("react").JSX.Element; + +type ThreadListNewProps = Omit<PressableProps, "onPress"> & { + children: ReactNode; +}; + +declare const ThreadListRoot: (_param41: ThreadListRootProps) => import("react").JSX.Element; + +type ThreadListRootProps = ViewProps & { + children: ReactNode; +}; + +type ThreadListRuntime = { + getState(): ThreadListState; + subscribe(callback: () => void): Unsubscribe$1; + readonly main: ThreadRuntime; + getById(threadId: string): ThreadRuntime; + readonly mainItem: ThreadListItemRuntime; + getItemById(threadId: string): ThreadListItemRuntime; + getItemByIndex(idx: number): ThreadListItemRuntime; + getArchivedItemByIndex(idx: number): ThreadListItemRuntime; + switchToThread(threadId: string, options?: { + unarchive?: boolean; + }): Promise<void>; + switchToNewThread(): Promise<void>; + getLoadThreadsPromise(): Promise<void>; + reload(): Promise<void>; + loadMore(): Promise<void>; +}; + +type ThreadListRuntimeCore = { + readonly isLoading: boolean; + readonly isLoadingMore?: boolean; + readonly hasMore?: boolean; + mainThreadId: string; + newThreadId: string | undefined; + threadIds: readonly string[]; + archivedThreadIds: readonly string[]; + readonly threadItems: Readonly<Record<string, ThreadListItemCoreState>>; + getMainThreadRuntimeCore(): ThreadRuntimeCore; + getThreadRuntimeCore(threadId: string): ThreadRuntimeCore; + getItemById(threadId: string): ThreadListItemCoreState | undefined; + switchToThread(threadId: string, options?: { + unarchive?: boolean; + }): Promise<void>; + switchToNewThread(): Promise<void>; + getLoadThreadsPromise(): Promise<void>; + reload?(): Promise<void>; + loadMore?(): Promise<void>; + detach(threadId: string): Promise<void>; + rename(threadId: string, newTitle: string): Promise<void>; + updateCustom?(threadId: string, custom: Record<string, unknown> | undefined): Promise<void>; + archive(threadId: string): Promise<void>; + unarchive(threadId: string): Promise<void>; + delete(threadId: string): Promise<void>; + initialize(threadId: string): Promise<{ + remoteId: string; + externalId: string | undefined; + }>; + generateTitle(threadId: string): Promise<void>; + subscribe(callback: () => void): Unsubscribe$1; +}; + +type ThreadListRuntimeCoreBinding = ThreadListRuntimeCore; + +declare class ThreadListRuntimeImpl implements ThreadListRuntime { + private _core; + private _runtimeFactory; + private _getState; + constructor(_core: ThreadListRuntimeCoreBinding, _runtimeFactory?: new (binding: ThreadRuntimeCoreBinding, threadListItemBinding: ThreadListItemRuntimeBinding) => ThreadRuntime); + protected __internal_bindMethods(): void; + switchToThread(threadId: string, options?: { + unarchive?: boolean; + }): Promise<void>; + switchToNewThread(): Promise<void>; + getLoadThreadsPromise(): Promise<void>; + reload(): Promise<void>; + loadMore(): Promise<void>; + getState(): ThreadListState; + subscribe(callback: () => void): Unsubscribe$1; + private _mainThreadListItemRuntime; + readonly main: ThreadRuntime; + get mainItem(): ThreadListItemRuntimeImpl; + getById(threadId: string): ThreadRuntime; + getItemByIndex(idx: number): ThreadListItemRuntimeImpl; + getArchivedItemByIndex(idx: number): ThreadListItemRuntimeImpl; + getItemById(threadId: string): ThreadListItemRuntimeImpl; +} + +type ThreadListState = { + readonly mainThreadId: string; + readonly newThreadId: string | undefined; + readonly threadIds: readonly string[]; + readonly archivedThreadIds: readonly string[]; + readonly isLoading: boolean; + readonly isLoadingMore: boolean; + readonly hasMore: boolean; + readonly threadItems: Readonly<Record<string, Omit<ThreadListItemState$1, "isMain" | "threadId">>>; +}; + +type ThreadMessage = BaseThreadMessage & (ThreadSystemMessage | ThreadUserMessage | ThreadAssistantMessage); + +type ThreadMessageLike = { + readonly role: "assistant" | "system" | "user"; + readonly content: string | readonly (TextMessagePart | ReasoningMessagePart | SourceMessagePart | ImageMessagePart | FileMessagePart | DataMessagePart | GenerativeUIMessagePart | Unstable_AudioMessagePart | DataPrefixedPart | { + readonly type: "tool-call"; + readonly toolCallId?: string; + readonly toolName: string; + readonly args?: ReadonlyJSONObject; + readonly argsText?: string; + readonly artifact?: any; + readonly result?: any | undefined; + readonly isError?: boolean | undefined; + readonly parentId?: string | undefined; + readonly messages?: readonly ThreadMessage[] | undefined; + readonly interrupt?: { + type: "human"; + payload: unknown; + }; + readonly timing?: ToolCallTiming; + readonly providerMetadata?: PartProviderMetadata; + readonly approval?: { + readonly id: string; + readonly approved?: boolean; + readonly reason?: string; + readonly isAutomatic?: boolean; + readonly options?: readonly ToolApprovalOption[]; + readonly optionId?: string; + readonly resolution?: "cancelled" | "expired"; + }; + })[]; + readonly id?: string | undefined; + readonly createdAt?: Date | undefined; + readonly status?: MessageStatus | undefined; + readonly attachments?: readonly (Omit<CompleteAttachment, "content"> & { + readonly content: readonly (ThreadUserMessagePart | DataPrefixedPart)[]; + })[] | undefined; + readonly metadata?: { + readonly unstable_state?: ReadonlyJSONValue; + readonly unstable_annotations?: readonly ReadonlyJSONValue[] | undefined; + readonly unstable_data?: readonly ReadonlyJSONValue[] | undefined; + readonly steps?: readonly ThreadStep[] | undefined; + readonly timing?: MessageTiming | undefined; + readonly submittedFeedback?: { + readonly type: "negative" | "positive"; + }; + readonly isOptimistic?: boolean | undefined; + readonly custom?: Record<string, unknown> | undefined; + } | undefined; +}; + +declare const ThreadMessages: import("react").ForwardRefExoticComponent<ThreadMessagesProps & import("react").RefAttributes<FlatList<ThreadMessage>>>; + +type ThreadMessagesProps = Omit<FlatListProps<ThreadMessage>, "children" | "data" | "renderItem"> & MessagesContent; + +declare namespace ThreadPrimitiveMessageByIndex { + type Props = { + index: number; + components: MessagesComponentConfig; + }; +} + +declare const ThreadPrimitiveMessageByIndex: FC<ThreadPrimitiveMessageByIndex.Props>; + +declare namespace ThreadPrimitiveSuggestionByIndex { + type Props = { + index: number; + components: SuggestionsComponentConfig; + }; +} + +declare const ThreadPrimitiveSuggestionByIndex: FC<ThreadPrimitiveSuggestionByIndex.Props>; + +declare namespace ThreadPrimitiveSuggestions { + type Props = { + components: SuggestionsComponentConfig; + children?: never; + } | { + children: (value: { + suggestion: SuggestionState; + }) => ReactNode; + components?: never; + }; +} + +declare const ThreadPrimitiveSuggestions: import("react").NamedExoticComponent<ThreadPrimitiveSuggestions.Props>; + +declare namespace ThreadPrimitiveUnstable_MessageById { + type Props = { + messageId: string; + components: MessagesComponentConfig; + }; +} + +declare const ThreadPrimitiveUnstable_MessageById: FC<ThreadPrimitiveUnstable_MessageById.Props>; + +declare const ThreadRoot: (_param42: ThreadRootProps) => import("react").JSX.Element; + +type ThreadRootProps = ViewProps & { + children: ReactNode; +}; + +type ThreadRuntime = { + readonly path: ThreadRuntimePath; + readonly composer: ThreadComposerRuntime; + getState(): ThreadState; + append(message: CreateAppendMessage): void; + deleteMessage(messageId: string): void | Promise<void>; + startRun(config: CreateStartRunConfig): void; + resumeRun(config: CreateResumeRunConfig): void; + exportExternalState(): any; + importExternalState(state: any): void; + subscribe(callback: () => void): Unsubscribe$1; + cancelRun(): void; + getModelContext(): ModelContext$1; + export(): ExportedMessageRepository; + import(repository: ExportedMessageRepository): void; + reset(initialMessages?: readonly ThreadMessageLike[]): void; + getMessageByIndex(idx: number): MessageRuntime; + getMessageById(messageId: string): MessageRuntime; + stopSpeaking(): void; + connectVoice(): void; + disconnectVoice(): void; + getVoiceVolume(): number; + subscribeVoiceVolume(callback: () => void): Unsubscribe$1; + muteVoice(): void; + unmuteVoice(): void; + unstable_on<E extends ThreadRuntimeEventType>(event: E, callback: ThreadRuntimeEventCallback<E>): Unsubscribe$1; +}; + +type ThreadRuntimeCore = Readonly<{ + getMessageById: (messageId: string) => { + parentId: string | null; + message: ThreadMessage; + index: number; + } | undefined; + getBranches: (messageId: string) => readonly string[]; + switchToBranch: (branchId: string) => void; + append: (message: AppendMessage) => void; + deleteMessage: (messageId: string) => void | Promise<void>; + startRun: (config: StartRunConfig) => void; + resumeRun: (config: ResumeRunConfig) => void; + cancelRun: () => void; + addToolResult: (options: AddToolResultOptions) => void; + resumeToolCall: (options: ResumeToolCallOptions) => void; + respondToToolApproval: (options: RespondToToolApprovalOptions) => void; + speak: (messageId: string) => void; + stopSpeaking: () => void; + connectVoice: () => void; + disconnectVoice: () => void; + muteVoice: () => void; + unmuteVoice: () => void; + submitFeedback: (feedback: SubmitFeedbackOptions) => void; + getModelContext: () => ModelContext$1; + composer: ThreadComposerRuntimeCore; + getEditComposer: (messageId: string) => EditComposerRuntimeCore | undefined; + beginEdit: (messageId: string) => void; + getQueueItems?: () => readonly QueueItemState[]; + steerQueueItem?: (queueItemId: string) => void; + removeQueueItem?: (queueItemId: string) => void; + speech: SpeechState | undefined; + voice: VoiceSessionState | undefined; + capabilities: Readonly<RuntimeCapabilities>; + isDisabled: boolean; + isSendDisabled: boolean; + isLoading: boolean; + isRunning?: boolean | undefined; + messages: readonly ThreadMessage[]; + state: ReadonlyJSONValue; + suggestions: readonly ThreadSuggestion$1[]; + extras: unknown; + subscribe: (callback: () => void) => Unsubscribe$1; + getVoiceVolume: () => number; + subscribeVoiceVolume: (callback: () => void) => Unsubscribe$1; + import(repository: ExportedMessageRepository): void; + export(): ExportedMessageRepository; + exportExternalState(): any; + importExternalState(state: any): void; + reset(initialMessages?: readonly ThreadMessageLike[]): void; + unstable_on<E extends ThreadRuntimeEventType>(event: E, callback: ThreadRuntimeEventCallback<E>): Unsubscribe$1; +}>; + +type ThreadRuntimeCoreBinding = SubscribableWithState<ThreadRuntimeCore, ThreadRuntimePath> & { + outerSubscribe(callback: () => void): Unsubscribe$1; +}; + +type ThreadRuntimeEventCallback<E extends ThreadRuntimeEventType> = (payload: ThreadRuntimeEventPayload[E]) => void; + +type ThreadRuntimeEventPayload = { + runStart: Record<string, never>; + runEnd: Record<string, never>; + initialize: Record<string, never>; + modelContextUpdate: Record<string, never>; +}; + +type ThreadRuntimeEventType = keyof ThreadRuntimeEventPayload; + +declare class ThreadRuntimeImpl implements ThreadRuntime { + get path(): ThreadRuntimePath; + get __internal_threadBinding(): Subscribable & { + path: ThreadRuntimePath; + getState: () => Readonly<{ + getMessageById: (messageId: string) => { + parentId: string | null; + message: ThreadMessage; + index: number; + } | undefined; + getBranches: (messageId: string) => readonly string[]; + switchToBranch: (branchId: string) => void; + append: (message: AppendMessage) => void; + deleteMessage: (messageId: string) => void | Promise<void>; + startRun: (config: StartRunConfig) => void; + resumeRun: (config: ResumeRunConfig) => void; + cancelRun: () => void; + addToolResult: (options: AddToolResultOptions) => void; + resumeToolCall: (options: ResumeToolCallOptions) => void; + respondToToolApproval: (options: RespondToToolApprovalOptions) => void; + speak: (messageId: string) => void; + stopSpeaking: () => void; + connectVoice: () => void; + disconnectVoice: () => void; + muteVoice: () => void; + unmuteVoice: () => void; + submitFeedback: (feedback: SubmitFeedbackOptions) => void; + getModelContext: () => ModelContext$1; + composer: Readonly<{ + isEditing: boolean; + canCancel: boolean; + canSend: boolean; + isEmpty: boolean; + attachments: readonly Attachment[]; + attachmentAccept: string; + addAttachment: (fileOrAttachment: File | CreateAttachment) => Promise<void>; + removeAttachment: (attachmentId: string) => Promise<void>; + text: string; + setText: (value: string) => void; + role: MessageRole; + setRole: (role: MessageRole) => void; + runConfig: RunConfig; + setRunConfig: (runConfig: RunConfig) => void; + quote: QuoteInfo | undefined; + setQuote: (quote: QuoteInfo | undefined) => void; + reset: () => Promise<void>; + clearAttachments: () => Promise<void>; + send: (options?: SendOptions) => void; + cancel: () => void; + queue: readonly QueueItemState[]; + steerQueueItem: (queueItemId: string) => void; + removeQueueItem: (queueItemId: string) => void; + dictation: DictationState | undefined; + startDictation: () => void; + stopDictation: () => void; + subscribe: (callback: () => void) => Unsubscribe$1; + unstable_on: <E extends ComposerRuntimeEventType>(event: E, callback: ComposerRuntimeEventCallback<E>) => Unsubscribe$1; + }>; + getEditComposer: (messageId: string) => EditComposerRuntimeCore | undefined; + beginEdit: (messageId: string) => void; + getQueueItems?: () => readonly QueueItemState[]; + steerQueueItem?: (queueItemId: string) => void; + removeQueueItem?: (queueItemId: string) => void; + speech: SpeechState | undefined; + voice: VoiceSessionState | undefined; + capabilities: Readonly<RuntimeCapabilities>; + isDisabled: boolean; + isSendDisabled: boolean; + isLoading: boolean; + isRunning?: boolean | undefined; + messages: readonly ThreadMessage[]; + state: ReadonlyJSONValue; + suggestions: readonly ThreadSuggestion$1[]; + extras: unknown; + subscribe: (callback: () => void) => Unsubscribe$1; + getVoiceVolume: () => number; + subscribeVoiceVolume: (callback: () => void) => Unsubscribe$1; + import(repository: ExportedMessageRepository): void; + export(): ExportedMessageRepository; + exportExternalState(): any; + importExternalState(state: any): void; + reset(initialMessages?: readonly ThreadMessageLike[]): void; + unstable_on<E extends ThreadRuntimeEventType>(event: E, callback: ThreadRuntimeEventCallback<E>): Unsubscribe$1; + }>; + } & { + outerSubscribe(callback: () => void): Unsubscribe$1; + } & { + getStateState(): ThreadState; + }; + private readonly _threadBinding; + constructor(threadBinding: ThreadRuntimeCoreBinding, threadListItemBinding: ThreadListItemRuntimeBinding); + protected __internal_bindMethods(): void; + readonly composer: ThreadComposerRuntimeImpl; + getState(): ThreadState; + append(message: CreateAppendMessage): void; + deleteMessage(messageId: string): void | Promise<void>; + subscribe(callback: () => void): Unsubscribe$1; + getModelContext(): ModelContext$1; + startRun(config: CreateStartRunConfig): void; + resumeRun(config: CreateResumeRunConfig): void; + exportExternalState(): any; + importExternalState(state: any): void; + cancelRun(): void; + stopSpeaking(): void; + connectVoice(): void; + disconnectVoice(): void; + getVoiceVolume(): number; + subscribeVoiceVolume(callback: () => void): Unsubscribe$1; + muteVoice(): void; + unmuteVoice(): void; + export(): ExportedMessageRepository; + import(data: ExportedMessageRepository): void; + reset(initialMessages?: readonly ThreadMessageLike[]): void; + getMessageByIndex(idx: number): MessageRuntimeImpl; + getMessageById(messageId: string): MessageRuntimeImpl; + private _getMessageRuntime; + private _eventSubscriptionSubjects; + unstable_on<E extends ThreadRuntimeEventType>(event: E, callback: ThreadRuntimeEventCallback<E>): Unsubscribe$1; +} + +type ThreadRuntimePath = { + readonly ref: string; + readonly threadSelector: { + readonly type: "main"; + } | { + readonly type: "threadId"; + readonly threadId: string; + }; +}; + +type ThreadState = { + readonly threadId: string; + readonly metadata: ThreadListItemState$1; + readonly isDisabled: boolean; + readonly isLoading: boolean; + readonly isRunning: boolean; + readonly capabilities: RuntimeCapabilities; + readonly messages: readonly ThreadMessage[]; + readonly state: ReadonlyJSONValue; + readonly suggestions: readonly ThreadSuggestion$1[]; + readonly extras: unknown; + readonly speech: SpeechState | undefined; + readonly voice: VoiceSessionState | undefined; +}; + +type ThreadState$1 = { + readonly isEmpty: boolean; + readonly isDisabled: boolean; + readonly isLoading: boolean; + readonly isRunning: boolean; + readonly capabilities: RuntimeCapabilities; + readonly messages: readonly MessageState[]; + readonly state: ReadonlyJSONValue; + readonly suggestions: readonly ThreadSuggestion$1[]; + readonly extras: unknown; + readonly speech: SpeechState | undefined; + readonly voice: VoiceSessionState | undefined; + readonly composer: ComposerState; +}; + +type ThreadStep = { + readonly messageId?: string; + readonly usage?: { + readonly inputTokens: number; + readonly outputTokens: number; + } | undefined; +}; + +declare const ThreadSuggestion: (_param43: ThreadSuggestionProps) => import("react").JSX.Element; + +type ThreadSuggestion$1 = { + prompt: string; +}; + +type ThreadSuggestionProps = Omit<PressableProps, "onPress"> & { + children: ReactNode; + prompt: string; + send?: boolean | undefined; + clearComposer?: boolean | undefined; +}; + +type ThreadSystemMessage = MessageCommonProps & { + readonly role: "system"; + readonly content: readonly [ + TextMessagePart + ]; + readonly metadata: { + readonly unstable_state?: undefined; + readonly unstable_annotations?: undefined; + readonly unstable_data?: undefined; + readonly steps?: undefined; + readonly submittedFeedback?: undefined; + readonly timing?: undefined; + readonly custom: Record<string, unknown>; + }; +}; + +type ThreadUserMessage = MessageCommonProps & { + readonly role: "user"; + readonly content: readonly ThreadUserMessagePart[]; + readonly attachments: readonly CompleteAttachment[]; + readonly metadata: { + readonly unstable_state?: undefined; + readonly unstable_annotations?: undefined; + readonly unstable_data?: undefined; + readonly steps?: undefined; + readonly submittedFeedback?: undefined; + readonly timing?: undefined; + readonly custom: Record<string, unknown>; + }; +}; + +type ThreadUserMessagePart = TextMessagePart | ImageMessagePart | FileMessagePart | DataMessagePart | Unstable_AudioMessagePart; + +type ThreadsState = { + readonly mainThreadId: string; + readonly newThreadId: string | null; + readonly isLoading: boolean; + readonly isLoadingMore: boolean; + readonly hasMore: boolean; + readonly threadIds: readonly string[]; + readonly archivedThreadIds: readonly string[]; + readonly threadItems: readonly ThreadListItemState[]; + readonly main: ThreadState$1; +}; + +type Tool<TArgs extends Record<string, unknown> = Record<string, unknown>, TResult = unknown> = FrontendTool<TArgs, TResult> | BackendTool<TArgs, TResult> | HumanTool<TArgs, TResult> | ProviderTool<TArgs, TResult> | McpTool | ToolWithoutType<TArgs, TResult>; + +type ToolApprovalOption = { + readonly id: string; + readonly kind: ToolApprovalOptionKind | (string & {}); + readonly label?: string; + readonly description?: string; + readonly grants?: readonly string[]; + readonly confirm?: boolean | { + title?: string; + description?: string; + }; +}; + +type ToolApprovalOptionKind = "allow-always" | "allow-once" | "reject-always" | "reject-once"; + +type ToolApprovalResponse = { + readonly approved: boolean; + readonly reason?: string; +} | { + readonly optionId: string; + readonly reason?: string; +} | { + readonly approved: boolean; + readonly optionId: string; + readonly reason?: string; +}; + +type ToolArgsStatus<TArgs extends Record<string, unknown> = Record<string, unknown>> = { + status: "complete" | "incomplete" | "requires-action" | "running"; + propStatus: Partial<Record<keyof TArgs, PropFieldStatus>>; +}; + +type ToolBase<TArgs extends Record<string, unknown> = Record<string, unknown>, TResult = unknown> = { + streamCall?: ToolStreamCallFunction<TArgs, TResult>; + display?: ToolDisplay; +}; + +interface ToolCallArgsReader<TArgs extends Record<string, unknown>> { + get<PathT extends TypePath<TArgs>>(...fieldPath: PathT): Promise<TypeAtPath<TArgs, PathT>>; + streamValues<PathT extends TypePath<TArgs>>(...fieldPath: PathT): AsyncIterableStream<DeepPartial<TypeAtPath<TArgs, PathT>>>; + streamText<PathT extends TypePath<TArgs>>(...fieldPath: PathT): TypeAtPath<TArgs, PathT> extends string & (infer U) ? AsyncIterableStream<U> : never; + forEach<PathT extends TypePath<TArgs>>(...fieldPath: PathT): NonNullable<TypeAtPath<TArgs, PathT>> extends Array<infer U> ? AsyncIterableStream<U> : never; +} + +type ToolCallCompleteText<TArgs extends Record<string, unknown>, TResult> = ReactNode | ((options: { + args: TArgs; + result: TResult | undefined; +}) => ReactNode); + +type ToolCallMessagePart<TArgs = ReadonlyJSONObject, TResult = unknown> = { + readonly type: "tool-call"; + readonly toolCallId: string; + readonly toolName: string; + readonly args: TArgs; + readonly result?: TResult | undefined; + readonly isError?: boolean | undefined; + readonly argsText: string; + readonly artifact?: unknown; + readonly timing?: ToolCallTiming; + readonly mcp?: ToolCallMessagePartMcpMetadata; + readonly providerMetadata?: PartProviderMetadata; + readonly modelContent?: readonly ToolModelContentPart[] | undefined; + readonly interrupt?: { + type: "human"; + payload: unknown; + }; + readonly approval?: { + readonly id: string; + readonly approved?: boolean; + readonly reason?: string; + readonly isAutomatic?: boolean; + readonly options?: readonly ToolApprovalOption[]; + readonly optionId?: string; + readonly resolution?: "cancelled" | "expired"; + }; + readonly parentId?: string; + readonly messages?: readonly ThreadMessage[]; +}; + +type ToolCallMessagePartComponent<TArgs = any, TResult = any> = ComponentType<ToolCallMessagePartProps<TArgs, TResult>>; + +type ToolCallMessagePartMcpMetadata = { + readonly app?: McpAppMetadata; +}; + +type ToolCallMessagePartProps<TArgs = any, TResult = unknown> = MessagePartState & ToolCallMessagePart<TArgs, TResult> & { + addResult: (result: TResult | ToolResponse<TResult>) => void; + resume: (payload: unknown) => void; + respondToApproval: (response: ToolApprovalResponse) => void; +}; + +type ToolCallMessagePartStatus = { + readonly type: "requires-action"; + readonly reason: "interrupt"; +} | MessagePartStatus; + +interface ToolCallReader<TArgs extends Record<string, unknown> = Record<string, unknown>, TResult = unknown> { + args: ToolCallArgsReader<TArgs>; + response: ToolCallResponseReader<TResult>; + result: { + get: () => Promise<TResult>; + }; +} + +interface ToolCallResponseReader<TResult> { + get: () => Promise<ToolResponse<TResult>>; +} + +type ToolCallRunningText<TArgs extends Record<string, unknown>> = ReactNode | ((options: { + args: TArgs; +}) => ReactNode); + +type ToolCallText<TArgs extends Record<string, unknown>, TResult> = { + running: ToolCallRunningText<TArgs>; + complete?: ToolCallCompleteText<TArgs, TResult> | undefined; +} | { + running?: ToolCallRunningText<TArgs> | undefined; + complete: ToolCallCompleteText<TArgs, TResult>; +}; + +type ToolCallTiming = { + readonly startedAt: number; + readonly completedAt?: number; +}; + +type ToolDeclaration<TArgs extends Record<string, unknown> = Record<string, unknown>, TResult = unknown> = FrontendTool<TArgs, TResult> | BackendToolDeclaration<TArgs, TResult> | HumanTool<TArgs, TResult> | ProviderTool<TArgs, TResult> | McpTool | ToolWithoutType<TArgs, TResult>; + +type ToolDefinition<TArgs extends Record<string, unknown> = Record<string, unknown>, TResult = unknown> = WithRender<Tool<TArgs, TResult>, TArgs, TResult>; + +type ToolDisplay = "inline" | "standalone"; + +type ToolExecute<TArgs extends Record<string, unknown>, TResult> = (args: TArgs, context: ToolExecuteContext) => TResult | Promise<TResult>; + +type ToolExecuteContext = Parameters<NonNullable<ToolDeclaration["execute"]>>[1]; + +type ToolExecuteFunction<TArgs, TResult> = (args: TArgs, context: ToolExecutionContext) => TResult | Promise<TResult>; + +type ToolExecutionContext = { + toolCallId: string; + abortSignal: AbortSignal; + human: (payload: unknown) => Promise<unknown>; +}; + +type ToolModelContentPart = { + readonly type: "text"; + readonly text: string; +} | { + readonly type: "file"; + readonly data: string; + readonly mediaType: string; + readonly filename?: string; +}; + +type ToolModelOutputFunction<TArgs, TResult> = (options: { + toolCallId: string; + input: TArgs; + output: TResult; +}) => readonly ToolModelContentPart[] | Promise<readonly ToolModelContentPart[]>; + +type ToolParameters<TArgs extends Record<string, unknown>> = ToolDeclaration<TArgs>["parameters"]; + +type ToolRegistration = { + readonly render: ToolCallMessagePartComponent; + readonly standalone: boolean; +}; + +declare class ToolResponse<TResult> { + get [TOOL_RESPONSE_SYMBOL](): boolean; + readonly artifact?: ReadonlyJSONValue; + readonly result: TResult; + readonly isError: boolean; + readonly modelContent?: readonly ToolModelContentPart[]; + readonly messages?: ReadonlyJSONValue; + constructor(options: ToolResponseLike<TResult>); + static [Symbol.hasInstance](obj: unknown): obj is ToolResponse<ReadonlyJSONValue>; + static toResponse(result: any | ToolResponse<any>): ToolResponse<any>; +} + +type ToolResponseLike<TResult> = { + result: TResult; + artifact?: ReadonlyJSONValue | undefined; + isError?: boolean | undefined; + modelContent?: readonly ToolModelContentPart[] | undefined; + messages?: ReadonlyJSONValue | undefined; +}; + +type ToolStreamCall<TArgs extends Record<string, unknown>, TResult> = (reader: ToolCallReader<TArgs, TResult>, context: ToolExecuteContext) => void; + +type ToolStreamCallFunction<TArgs extends Record<string, unknown> = Record<string, unknown>, TResult = unknown> = (reader: ToolCallReader<TArgs, TResult>, context: ToolExecutionContext) => void; + +type ToolWithoutType<TArgs extends Record<string, unknown> = Record<string, unknown>, TResult = unknown> = (Omit<FrontendTool<TArgs, TResult>, "type"> | Omit<BackendTool<TArgs, TResult>, "type"> | Omit<HumanTool<TArgs, TResult>, "type"> | Omit<ProviderTool<TArgs, TResult>, "type">) & { + type?: undefined; +}; + +type Toolkit = Record<string, ToolDefinition<any, any>>; + +type ToolkitDefinition<TArgsByName extends { + [K in keyof TArgsByName]: Record<string, unknown>; +} = Record<string, any>, TResultByName extends { + [K in keyof TArgsByName]: unknown; +} = { + [K in keyof TArgsByName]: any; +}> = { + [K in keyof TArgsByName]: ToolkitDefinitionEntry<TArgsByName[K], TResultByName[K]>; +}; + +type ToolkitDefinitionEntry<TArgs extends Record<string, unknown> = Record<string, unknown>, TResult = unknown> = ToolkitDefinitionInput<TArgs, TResult> | ToolDefinition<any, any>; + +type ToolkitDefinitionEntryWithParameters<TArgs extends Record<string, unknown> = Record<string, unknown>, TResult = unknown> = ToolkitDefinitionInput<TArgs, TResult> & { + parameters: NonNullable<ToolParameters<TArgs>>; +}; + +type ToolkitDefinitionInput<TArgs extends Record<string, unknown>, TResult> = WithRender<ToolDeclaration<TArgs, TResult> extends (infer T) ? T extends { + streamCall?: unknown; +} ? OverrideToolDeclarationCallbacks<T, TArgs, TResult> : never : never, TArgs, TResult>; + +declare const Tools: Resource<ClientOutput<"tools">, [ + { + toolkit?: Toolkit; + mcpApp?: ResourceElement<McpAppResourceOutput> | undefined; + } +]>; + +type ToolsState = { + toolUIs: Record<string, readonly ToolRegistration[]>; + mcpApp?: McpAppResourceOutput | undefined; + tools: Record<string, ToolCallMessagePartComponent[]>; +}; + +type TupleIndex<T extends readonly any[]> = Exclude<keyof T, keyof any[]>; + +type TypeAtPath<T, P extends readonly any[]> = P extends [ + infer Head, + ...infer Rest +] ? Head extends keyof T ? TypeAtPath<T[Head], Rest> : never : T; + +type TypePath<T> = [ +] | (0 extends 1 & T ? any[] : T extends object ? T extends readonly any[] ? number extends T["length"] ? { + [K in TupleIndex<T>]: [ + AsNumber<K>, + ...TypePath<T[K]> + ]; +}[TupleIndex<T>] : [ + number, + ...TypePath<T[number]> +] : { + [K in ObjectKey<T>]: [ + K, + ...TypePath<T[K]> + ]; +}[ObjectKey<T>] : [ +]); + +type UnionToIntersection<U> = (U extends unknown ? (x: U) => void : never) extends ((x: infer I) => void) ? I : never; + +type Unstable_AudioMessagePart = { + readonly type: "audio"; + readonly audio: { + readonly data: string; + readonly format: "mp3" | "wav"; + }; +}; + +type Unstable_AudioMessagePartComponent = ComponentType<Unstable_AudioMessagePartProps>; + +type Unstable_AudioMessagePartProps = MessagePartState & Unstable_AudioMessagePart; + +type Unstable_InferInteractableState<TSchema> = TSchema extends { + "~standard": { + types?: { + output: infer TOutput; + } | undefined; + }; +} ? TOutput : unknown; + +type Unstable_InteractableConfig<TSchema extends Unstable_InteractableStateSchema> = { + description: string; + stateSchema: TSchema; + initialState: Unstable_InferInteractableState<TSchema>; + id?: string | undefined; + updateRender?: ToolCallMessagePartComponent | undefined; +}; + +type Unstable_InteractableDefinition = { + id: string; + name: string; + description: string; + stateSchema: Unstable_InteractableStateSchema; + state: unknown; + initialState: unknown; + scope?: InteractableScope | undefined; +}; + +type Unstable_InteractablePersistedState = Record<string, { + name: string; + state: unknown; +}>; + +type Unstable_InteractablePersistenceAdapter = { + save(state: Unstable_InteractablePersistedState): void | Promise<void>; + load?(): Unstable_InteractablePersistedState | null | undefined | Promise<Unstable_InteractablePersistedState | null | undefined>; +}; + +type Unstable_InteractablePersistenceStatus = { + isPending: boolean; + error: unknown; +}; + +type Unstable_InteractableRegistration = { + id: string; + name: string; + description: string; + stateSchema: Unstable_InteractableStateSchema; + initialState: unknown; + updateRender?: ToolCallMessagePartComponent | undefined; +}; + +type Unstable_InteractableSnapshotEntry = { + id: string; + name: string; + state: unknown; + partial?: boolean | undefined; +}; + +type Unstable_InteractableStateSchema = NonNullable<Extract<Tool, { + parameters: unknown; +}>["parameters"]>; + +type Unstable_InteractableToolConfig<TSchema extends Unstable_InteractableStateSchema> = { + description: string; + stateSchema: TSchema; + render: (props: Unstable_InteractableToolRenderProps<Unstable_InferInteractableState<TSchema>>) => ReactNode; +}; + +type Unstable_InteractableToolRenderProps<TState> = { + state: TState; + setState: (updater: TState | ((prev: TState) => TState)) => void; + version: Unstable_InteractableVersionInfo<TState> | undefined; + id: string; + streaming: boolean; +}; + +type Unstable_InteractableVersion = { + state: unknown; + origin: "create" | "update" | "user-edit"; + toolCallId?: string | undefined; +}; + +type Unstable_InteractableVersionInfo<TState> = { + state: TState; + isLatest: boolean; + restore: () => void; +}; + +type Unstable_InteractablesClientSchema = { + methods: Unstable_InteractablesMethods; +}; + +type Unstable_InteractablesConfig = { + persistence?: Unstable_InteractablePersistenceAdapter | undefined; +}; + +type Unstable_InteractablesMethods = { + getState(): Unstable_InteractablesState; + register(def: Unstable_InteractableRegistration): Unsubscribe$1; + setState(id: string, updater: (prev: unknown) => unknown): void; + exportState(): Unstable_InteractablePersistedState; + importState(saved: Unstable_InteractablePersistedState): void; + setPersistenceAdapter(adapter: Unstable_InteractablePersistenceAdapter | undefined): void; + flush(): Promise<void>; +}; + +type Unstable_InteractablesState = { + definitions: Record<string, Unstable_InteractableDefinition>; + persistence: Record<string, Unstable_InteractablePersistenceStatus>; +}; + +type Unsubscribe = () => void; + +type Unsubscribe$1 = () => void; + +type UseActionBarCopyOptions = { + copiedDuration?: number | undefined; + copyToClipboard?: ((text: string) => void | Promise<void>) | undefined; +}; + +type UseComposerIfProps = RequireAtLeastOne<ComposerIfFilters>; + +type ValidateClient<K extends keyof ScopeRegistry> = ScopeRegistry[K] extends { + methods: ClientMethods; +} ? "meta" extends keyof ScopeRegistry[K] ? ScopeRegistry[K]["meta"] extends ClientMetaType ? "events" extends keyof ScopeRegistry[K] ? ScopeRegistry[K]["events"] extends ClientEventsType<K> ? ScopeRegistry[K] : ClientError<`ERROR: ${K & string} has invalid events type`> : ScopeRegistry[K] : ClientError<`ERROR: ${K & string} has invalid meta type`> : "events" extends keyof ScopeRegistry[K] ? ScopeRegistry[K]["events"] extends ClientEventsType<K> ? ScopeRegistry[K] : ClientError<`ERROR: ${K & string} has invalid events type`> : ScopeRegistry[K] : ClientError<`ERROR: ${K & string} has invalid methods type`>; + +type VoiceSessionControls = { + disconnect: () => void; + mute: () => void; + unmute: () => void; +}; + +type VoiceSessionHelpers = { + setStatus: (status: RealtimeVoiceAdapter.Status) => void; + end: (reason: "cancelled" | "error" | "finished", error?: unknown) => void; + emitTranscript: (item: RealtimeVoiceAdapter.TranscriptItem) => void; + emitMode: (mode: RealtimeVoiceAdapter.Mode) => void; + emitVolume: (volume: number) => void; + isDisposed: () => boolean; +}; + +type VoiceSessionState = { + readonly status: RealtimeVoiceAdapter.Status; + readonly isMuted: boolean; + readonly mode: RealtimeVoiceAdapter.Mode; +}; + +type WildcardPayload = { + [K in keyof ClientEventMap]: { + event: K; + payload: ClientEventMap[K]; + }; +}[Extract<keyof ClientEventMap, string>]; + +type WithRender<T, TArgs extends Record<string, unknown>, TResult> = T extends { + type: "frontend" | "human"; +} ? T & (T extends { + type: "frontend"; +} ? { + render: ToolCallMessagePartComponent<TArgs, TResult>; +} | { + render?: ToolCallMessagePartComponent<TArgs, TResult>; + renderText: ToolCallText<TArgs, TResult>; +} : { + render: ToolCallMessagePartComponent<TArgs, TResult>; +}) : T & { + render?: ToolCallMessagePartComponent<TArgs, TResult> | undefined; + renderText?: ToolCallText<TArgs, TResult> | undefined; +}; + +declare namespace actionBar_d_exports { + export { ActionBarCopy as Copy, ActionBarCopyProps as CopyProps, ActionBarEdit as Edit, ActionBarEditProps as EditProps, ActionBarFeedbackNegative as FeedbackNegative, ActionBarFeedbackNegativeProps as FeedbackNegativeProps, ActionBarFeedbackPositive as FeedbackPositive, ActionBarFeedbackPositiveProps as FeedbackPositiveProps, ActionBarReload as Reload, ActionBarReloadProps as ReloadProps }; +} + +declare namespace attachment_d_exports { + export { AttachmentName as Name, AttachmentNameProps as NameProps, AttachmentRemove as Remove, AttachmentRemoveProps as RemoveProps, AttachmentRoot as Root, AttachmentRootProps as RootProps, AttachmentThumb as Thumb, AttachmentThumbProps as ThumbProps }; +} + +declare namespace branchPicker_d_exports { + export { BranchPickerCount as Count, BranchPickerCountProps as CountProps, BranchPickerNext as Next, BranchPickerNextProps as NextProps, BranchPickerNumber as Number, BranchPickerNumberProps as NumberProps, BranchPickerPrevious as Previous, BranchPickerPreviousProps as PreviousProps }; +} + +declare namespace chainOfThought_d_exports { + export { ChainOfThoughtAccordionTrigger as AccordionTrigger, ChainOfThoughtAccordionTriggerProps as AccordionTriggerProps, ChainOfThoughtPrimitiveParts as Parts, ChainOfThoughtRoot as Root, ChainOfThoughtRootProps as RootProps }; +} + +declare namespace composer_d_exports { + export { ComposerAddAttachment as AddAttachment, ComposerAddAttachmentProps as AddAttachmentProps, ComposerAttachmentByIndex as AttachmentByIndex, ComposerAttachments as Attachments, ComposerCancel as Cancel, ComposerCancelProps as CancelProps, ComposerPrimitiveIf as If, ComposerInput as Input, ComposerInputProps as InputProps, ComposerRoot as Root, ComposerRootProps as RootProps, ComposerSend as Send, ComposerSendProps as SendProps }; +} + +declare function createVoiceSession(options: { + abortSignal?: AbortSignal; +}, setup: (helpers: VoiceSessionHelpers) => Promise<VoiceSessionControls>): RealtimeVoiceAdapter.Session; + +declare function defineMcpToolkit(definition: McpToolkitDefinition): Toolkit; + +declare function defineToolkit<TArgsByName extends { + [K in keyof TArgsByName]: Record<string, unknown>; +}, TResultByName extends { + [K in keyof TArgsByName]: unknown; +} = { + [K in keyof TArgsByName]: any; +}>(_definition: { + [K in keyof TArgsByName]: ToolkitDefinitionEntryWithParameters<TArgsByName[K], TResultByName[K]>; +}): Toolkit & { + [K in keyof TArgsByName]: ToolkitDefinitionEntryWithParameters<TArgsByName[K], TResultByName[K]>; +}; + +declare function defineToolkit<const TDefinition extends ToolkitDefinition>(_definition: TDefinition): Toolkit & TDefinition; + +declare function externalTool(): never; + +declare const fromThreadMessageLike: (like: ThreadMessageLike, fallbackId: string, fallbackStatus: MessageStatus) => ThreadMessage; + +declare const generateId: (size?: number) => string; + +declare const getAutoStatus: (isLast: boolean, isRunning: boolean, hasInterruptedToolCalls: boolean, hasPendingToolCalls: boolean, error?: ReadonlyJSONValue) => MessageStatus; + +declare global { + interface Window { + SpeechRecognition?: SpeechRecognitionConstructor; + webkitSpeechRecognition?: SpeechRecognitionConstructor; + } +} + +declare const groupPartByType: <TKey extends `group-${string}`>(map: Partial<Readonly<Record<GroupPartType, readonly TKey[]>>>) => ((part: PartState, context?: GroupByContext) => readonly TKey[]); + +declare const hitl: typeof humanTool; + +declare const hitlTool: typeof humanTool; + +declare function humanTool(): never; + +declare namespace entry_root_exports { + export { actionBar_d_exports as ActionBarPrimitive, AppendMessage, AssistantClient, AssistantContextConfig, AssistantDataUI, AssistantDataUIProps, AssistantEventCallback, AssistantEventName, AssistantEventPayload, AssistantEventScope, AssistantEventSelector, AssistantInteractableProps, AssistantRuntime, AssistantRuntimeProvider, AssistantState, AssistantTool, AssistantToolProps, AssistantToolUI, AssistantToolUIProps, Attachment, AttachmentAdapter, attachment_d_exports as AttachmentPrimitive, AttachmentRuntime, AttachmentState, AuiIf, AuiProvider, branchPicker_d_exports as BranchPickerPrimitive, ChainOfThoughtByIndicesProvider, ChainOfThoughtClient, ChainOfThoughtPartByIndexProvider, chainOfThought_d_exports as ChainOfThoughtPrimitive, ChatModelAdapter, ChatModelRunOptions, ChatModelRunResult, CompleteAttachment, ComposerAttachmentByIndexProvider, composer_d_exports as ComposerPrimitive, ComposerRuntime, ComposerState, CompositeAttachmentAdapter, CreateAttachment, DataMessagePart, DataMessagePartComponent, DataMessagePartProps, DataRenderers, EditComposerRuntime, EmptyMessagePartComponent, EmptyMessagePartProps, index_d_exports$1 as ErrorPrimitive, ExportedMessageRepository, ExportedMessageRepositoryItem, FeedbackAdapter, FileMessagePart, FileMessagePartComponent, FileMessagePartProps, GroupByContext, ImageMessagePart, ImageMessagePartComponent, ImageMessagePartProps, InMemoryThreadListAdapter, Interactables, LanguageModelConfig, LanguageModelV1CallSettings, LocalRuntimeOptions, McpToolkitDefinition, McpToolkitEntry, McpToolkitToolConfig, MessageAttachmentByIndexProvider, MessageByIndexProvider, message_d_exports as MessagePrimitive, MessageRole, MessageRuntime, MessageState, MessageStatus, ModelContext$1 as ModelContext, ModelContext as ModelContextClient, ModelContextProvider, ModelContextRegistry, ModelContextRegistryInstructionHandle, ModelContextRegistryProviderHandle, ModelContextRegistryToolHandle, PartByIndexProvider, PendingAttachment, ProviderToolConfig, RealtimeVoiceAdapter, ReasoningGroupComponent, ReasoningGroupProps, ReasoningMessagePart, ReasoningMessagePartComponent, ReasoningMessagePartProps, RemoteThreadListAdapter, RemoteThreadListOptions, RespondToToolApprovalOptions, RunConfig, RuntimeAdapterProvider, RuntimeAdapters, RuntimeCapabilities, SimpleImageAttachmentAdapter, SimpleTextAttachmentAdapter, SourceMessagePart, SourceMessagePartComponent, SourceMessagePartProps, SuggestionAdapter, SuggestionByIndexProvider, SuggestionConfig, suggestion_d_exports as SuggestionPrimitive, Suggestions, TextMessagePart, TextMessagePartComponent, TextMessagePartProps, TextMessagePartProvider, ThreadAssistantMessage, ThreadAssistantMessagePart, ThreadComposerRuntime, ThreadHistoryAdapter, ThreadListItemByIndexProvider, threadListItem_d_exports as ThreadListItemPrimitive, ThreadListItemRuntime, ThreadListItemRuntimeProvider, ThreadListItemState, threadList_d_exports as ThreadListPrimitive, ThreadListRuntime, ThreadMessage, ThreadMessageLike, thread_d_exports as ThreadPrimitive, ThreadRuntime, ThreadState$1 as ThreadState, ThreadSystemMessage, ThreadUserMessage, ThreadUserMessagePart, ThreadsState, Tool, ToolApprovalOption, ToolApprovalOptionKind, ToolApprovalResponse, ToolArgsStatus, ToolCallMessagePart, ToolCallMessagePartComponent, ToolCallMessagePartProps, ToolCallText, ToolCallTiming, ToolDefinition, ToolModelContentPart, Toolkit, ToolkitDefinition, ToolkitDefinitionEntry, Tools, Unstable_AudioMessagePart, Unstable_AudioMessagePartComponent, Unstable_AudioMessagePartProps, Unstable_InferInteractableState, Unstable_InteractableConfig, Unstable_InteractableDefinition, Unstable_InteractablePersistedState, Unstable_InteractablePersistenceAdapter, Unstable_InteractablePersistenceStatus, Unstable_InteractableRegistration, Unstable_InteractableSnapshotEntry, Unstable_InteractableStateSchema, Unstable_InteractableToolConfig, Unstable_InteractableToolRenderProps, Unstable_InteractableVersion, Unstable_InteractableVersionInfo, Unstable_InteractablesClientSchema, Unstable_InteractablesConfig, Unstable_InteractablesMethods, Unstable_InteractablesState, Unsubscribe$1 as Unsubscribe, VoiceSessionControls, VoiceSessionHelpers, createVoiceSession, defineMcpToolkit, defineToolkit, externalTool, fromThreadMessageLike, generateId, groupPartByType, hitl, hitlTool, humanTool, makeAssistantDataUI, makeAssistantTool, makeAssistantToolUI, mergeModelContexts, providerTool, stubTool, tool, unstable_Interactables, unstable_formatInteractableSnapshot, unstable_getInteractableSnapshots, unstable_getInteractableVersions, unstable_interactableTool, unstable_useInteractable, unstable_useInteractableState, unstable_useInteractableVersions, unstable_useThreadMessageIds, useAssistantContext, useAssistantDataUI, useAssistantInstructions, useAssistantInteractable, useAssistantTool, useAssistantToolUI, useAui, useAuiEvent, useAuiState, useAuiToolOverrides, useInlineRender, useInteractableState, useLocalRuntime, useRemoteThreadListRuntime, useRuntimeAdapters, useToolArgsStatus, useVoiceControls, useVoiceState, useVoiceVolume }; +} + +declare namespace index_d_exports$1 { + export { ErrorMessage as Message, ErrorMessageProps as MessageProps, ErrorRoot as Root, ErrorRootProps as RootProps }; +} + +declare namespace entry_internal_exports { + export { AssistantRuntimeImpl, BaseAssistantRuntimeCore, CompositeContextProvider, DefaultThreadComposerRuntimeCore, MessageRepository, ThreadListItemRuntimeBinding, ThreadListRuntimeCore, ThreadRuntimeCore, ThreadRuntimeCoreBinding, ThreadRuntimeImpl, getAutoStatus }; +} + +declare const makeAssistantDataUI: <T = any>(dataUI: AssistantDataUIProps<T>) => AssistantDataUI; + +declare const makeAssistantTool: <TArgs extends Record<string, unknown>, TResult>(tool: AssistantToolProps<TArgs, TResult>) => AssistantTool; + +declare const makeAssistantToolUI: <TArgs, TResult>(tool: AssistantToolUIProps<TArgs, TResult>) => AssistantToolUI; + +declare const mergeModelContexts: (configSet: Set<ModelContextProvider>) => ModelContext$1; + +declare namespace message_d_exports { + export { MessageAttachmentByIndex as AttachmentByIndex, MessageAttachments as Attachments, MessageContent as Content, MessageContentProps as ContentProps, MessagePrimitiveGroupedParts as GroupedParts, MessageIf as If, MessageIfProps as IfProps, MessagePrimitivePartByIndex as PartByIndex, MessagePrimitiveParts as Parts, MessageRoot as Root, MessageRootProps as RootProps }; +} + +declare function providerTool(_config: ProviderToolConfig): never; + +declare function stubTool(): never; + +declare namespace suggestion_d_exports { + export { SuggestionDescription as Description, SuggestionDescriptionProps as DescriptionProps, SuggestionTitle as Title, SuggestionTitleProps as TitleProps, SuggestionTrigger as Trigger, SuggestionTriggerProps as TriggerProps }; +} + +declare namespace threadListItem_d_exports { + export { ThreadListItemArchive as Archive, ThreadListItemArchiveProps as ArchiveProps, ThreadListItemDelete as Delete, ThreadListItemDeleteProps as DeleteProps, ThreadListItemRoot as Root, ThreadListItemRootProps as RootProps, ThreadListItemPrimitiveTitle as Title, ThreadListItemTrigger as Trigger, ThreadListItemTriggerProps as TriggerProps, ThreadListItemUnarchive as Unarchive, ThreadListItemUnarchiveProps as UnarchiveProps }; +} + +declare namespace threadList_d_exports { + export { ThreadListItems as Items, ThreadListItemsProps as ItemsProps, ThreadListNew as New, ThreadListNewProps as NewProps, ThreadListRoot as Root, ThreadListRootProps as RootProps }; +} + +declare namespace thread_d_exports { + export { ThreadEmpty as Empty, ThreadEmptyProps as EmptyProps, ThreadIf as If, ThreadIfProps as IfProps, ThreadPrimitiveMessageByIndex as MessageByIndex, ThreadMessages as Messages, ThreadMessagesProps as MessagesProps, ThreadRoot as Root, ThreadRootProps as RootProps, ThreadSuggestion as Suggestion, ThreadPrimitiveSuggestionByIndex as SuggestionByIndex, ThreadSuggestionProps as SuggestionProps, ThreadPrimitiveSuggestions as Suggestions, ThreadPrimitiveUnstable_MessageById as Unstable_MessageById }; +} + +declare function tool<TArgs extends Record<string, unknown>, TResult = any>(tool: Tool<TArgs, TResult>): Tool<TArgs, TResult>; + +declare const unstable_Interactables: Resource<ClientOutput<"unstable_interactables">, [ + (Unstable_InteractablesConfig | undefined)? +]>; + +declare function unstable_formatInteractableSnapshot(entry: Unstable_InteractableSnapshotEntry): string; + +declare function unstable_getInteractableSnapshots(message: { + metadata?: unknown; +}): Unstable_InteractableSnapshotEntry[] | undefined; + +declare function unstable_getInteractableVersions(messages: readonly SnapshotCarrierMessage[], id: string, name: string): Unstable_InteractableVersion[]; + +declare const unstable_interactableTool: <TSchema extends Unstable_InteractableStateSchema>(config: Unstable_InteractableToolConfig<TSchema>) => ToolDefinition<Record<string, unknown>, { + success: true; +}>; + +declare const unstable_useInteractable: <TSchema extends Unstable_InteractableStateSchema>(name: string, config: Unstable_InteractableConfig<TSchema>) => readonly [ + Unstable_InferInteractableState<TSchema>, + { + id: string; + version: Unstable_InteractableVersionInfo<Unstable_InferInteractableState<TSchema>> | undefined; + setState: (updater: Unstable_InferInteractableState<TSchema> | ((prev: Unstable_InferInteractableState<TSchema>) => Unstable_InferInteractableState<TSchema>)) => void; + isPending: boolean; + error: unknown; + flush: () => Promise<void>; + } +]; + +declare const unstable_useInteractableState: <TState>(id: string) => [ + TState | undefined, + { + setState: (updater: StateUpdater<TState>) => void; + isPending: boolean; + error: unknown; + flush: () => Promise<void>; + } +]; + +declare const unstable_useInteractableVersions: <TState = unknown>(id: string, name: string) => (Omit<Unstable_InteractableVersion, "state"> & { + state: TState; + restore: () => void; +})[]; + +declare const unstable_useThreadMessageIds: () => readonly string[]; + +declare const useAssistantContext: (config: AssistantContextConfig) => void; + +declare const useAssistantDataUI: (dataUI: AssistantDataUIProps | null) => void; + +declare const useAssistantInstructions: (config: string | AssistantInstructionsConfig) => void; + +declare const useAssistantInteractable: (name: string, config: AssistantInteractableProps) => string; + +declare const useAssistantTool: <TArgs extends Record<string, unknown>, TResult>(tool: AssistantToolProps<TArgs, TResult>) => void; + +declare const useAssistantToolUI: (tool: AssistantToolUIProps<any, any> | null) => void; + +declare namespace useAui { + type Props = { + [K in ClientNames]?: ClientElement<K> | DerivedElement<K>; + }; +} + +declare function useAui(): AssistantClient; + +declare function useAui(clients: useAui.Props): AssistantClient; + +declare function useAui(clients: useAui.Props, config: { + parent: null | AssistantClient; +}): AssistantClient; + +declare const useAuiEvent: <TEvent extends AssistantEventName>(selector: AssistantEventSelector<TEvent>, callback: AssistantEventCallback<TEvent>) => void; + +declare const useAuiState: <T>(selector: (state: AssistantState) => T) => T; + +declare function useAuiToolOverrides(overrides: AuiToolOverrides): void; + +declare const useInlineRender: <TArgs, TResult>(toolUI: FC<ToolCallMessagePartProps<TArgs, TResult>>) => FC<ToolCallMessagePartProps<TArgs, TResult>>; + +declare const useInteractableState: <TState>(id: string, fallback: TState) => [ + TState, + { + setState: (updater: StateUpdater$1<TState>) => void; + setSelected: (selected: boolean) => void; + isPending: boolean; + error: unknown; + flush: () => Promise<void>; + } +]; + +declare const useLocalRuntime: (chatModel: ChatModelAdapter, _param44?: LocalRuntimeOptions) => AssistantRuntime; + +declare const useRemoteThreadListRuntime: (options: RemoteThreadListOptions) => AssistantRuntime; + +declare const useRuntimeAdapters: () => RuntimeAdapters | null; + +declare const useToolArgsStatus: <TArgs extends Record<string, unknown> = Record<string, unknown>>() => ToolArgsStatus<TArgs>; + +declare const useVoiceControls: () => { + connect: () => void; + disconnect: () => void; + mute: () => void; + unmute: () => void; +}; + +declare const useVoiceState: () => VoiceSessionState | undefined; + +declare const useVoiceVolume: () => number; + +export { entry_internal_exports as entry_internal, entry_root_exports as entry_root }; diff --git a/api-surface/assistant-ui__react-o11y.ts b/api-surface/assistant-ui__react-o11y.ts new file mode 100644 index 0000000..d19d0c8 --- /dev/null +++ b/api-surface/assistant-ui__react-o11y.ts @@ -0,0 +1,238 @@ +import { Primitive } from "@radix-ui/react-primitive"; + +import { ComponentPropsWithoutRef, ComponentRef, ComponentType, FC, PropsWithChildren, ReactNode } from "react"; + +interface ScopeRegistry { + span: { + methods: SpanMethods; + meta: SpanMeta; + }; +} + +type ClientError<E extends string> = { + methods: Record<E, () => E>; + meta: { + source: ClientNames; + query: Record<E, E>; + }; + events: Record<`${E}.`, E>; +}; + +type ClientEventsType<K extends ClientNames> = Record<`${K}.${string}`, unknown>; + +type ClientMetaType = { + source: ClientNames; + query: Record<string, unknown>; +}; + +interface ClientMethods { + [key: string | symbol]: (...args: any[]) => any; +} + +type ClientNames = keyof ClientSchemas extends (infer U) ? U : never; + +type ClientOutput<K extends ClientNames> = ClientSchemas[K]["methods"] & ClientMethods; + +type ClientSchemas = keyof ScopeRegistry extends never ? { + "ERROR: No clients were defined": ClientError<"ERROR: No clients were defined">; +} : { + [K in keyof ScopeRegistry]: ValidateClient<K>; +}; + +type Resource<R, A extends readonly unknown[] = any[]> = (...args: A) => ResourceElement<R, A>; + +type ResourceElement<R, A extends readonly unknown[] = any[]> = { + readonly hook: (...args: A) => R; + readonly args: Readonly<A>; + readonly key?: string | number; + readonly deps?: readonly unknown[]; +}; + +interface ScopeRegistry { + [key: string]: { methods: any; meta?: any; events?: any }; +} + +declare const SpanByIndexProvider: FC<PropsWithChildren<{ + index: number; +}>>; + +type SpanChildrenComponentConfig = { + Span: ComponentType; +}; + +type SpanData = { + id: string; + parentSpanId: string | null; + name: string; + type: string; + status: "completed" | "failed" | "running" | "skipped"; + startedAt: number; + endedAt: number | null; + latencyMs: number | null; +}; + +type SpanItemState = { + id: string; + parentSpanId: string | null; + name: string; + type: string; + status: "completed" | "failed" | "running" | "skipped"; + startedAt: number; + endedAt: number | null; + latencyMs: number | null; + depth: number; + hasChildren: boolean; + isCollapsed: boolean; +}; + +type SpanMeta = { + source: "span"; + query: { + index: number; + } | { + key: string; + }; +}; + +type SpanMethods = { + getState: () => SpanState; + child: (lookup: SpanMeta["query"]) => SpanMethods; + toggleCollapse: () => void; +}; + +declare namespace SpanPrimitiveChildren { + type Props = { + components: SpanChildrenComponentConfig; + children?: never; + } | { + children: (value: { + span: SpanState; + }) => ReactNode; + components?: never; + }; +} + +declare const SpanPrimitiveChildren: FC<SpanPrimitiveChildren.Props>; + +declare namespace SpanPrimitiveCollapseToggle { + type Element = ComponentRef<typeof Primitive.button>; + type Props = ComponentPropsWithoutRef<typeof Primitive.button>; +} + +declare const SpanPrimitiveCollapseToggle: import("react").ForwardRefExoticComponent<Omit<import("react").ClassAttributes<HTMLButtonElement> & import("react").ButtonHTMLAttributes<HTMLButtonElement> & { + asChild?: boolean; +}, "ref"> & import("react").RefAttributes<HTMLButtonElement>>; + +declare namespace SpanPrimitiveIndent { + type Element = ComponentRef<typeof Primitive.div>; + type Props = ComponentPropsWithoutRef<typeof Primitive.div> & { + baseIndent?: number; + indentPerLevel?: number; + }; +} + +declare const SpanPrimitiveIndent: import("react").ForwardRefExoticComponent<Omit<import("react").ClassAttributes<HTMLDivElement> & import("react").HTMLAttributes<HTMLDivElement> & { + asChild?: boolean; +}, "ref"> & { + baseIndent?: number; + indentPerLevel?: number; +} & import("react").RefAttributes<HTMLDivElement>>; + +declare namespace SpanPrimitiveName { + type Element = ComponentRef<typeof Primitive.span>; + type Props = ComponentPropsWithoutRef<typeof Primitive.span>; +} + +declare const SpanPrimitiveName: import("react").ForwardRefExoticComponent<Omit<import("react").ClassAttributes<HTMLSpanElement> & import("react").HTMLAttributes<HTMLSpanElement> & { + asChild?: boolean; +}, "ref"> & import("react").RefAttributes<HTMLSpanElement>>; + +declare namespace SpanPrimitiveRoot { + type Element = ComponentRef<typeof Primitive.div>; + type Props = ComponentPropsWithoutRef<typeof Primitive.div>; +} + +declare const SpanPrimitiveRoot: import("react").ForwardRefExoticComponent<Omit<import("react").ClassAttributes<HTMLDivElement> & import("react").HTMLAttributes<HTMLDivElement> & { + asChild?: boolean; +}, "ref"> & import("react").RefAttributes<HTMLDivElement>>; + +declare namespace SpanPrimitiveStatusIndicator { + type Element = ComponentRef<typeof Primitive.span>; + type Props = ComponentPropsWithoutRef<typeof Primitive.span>; +} + +declare const SpanPrimitiveStatusIndicator: import("react").ForwardRefExoticComponent<Omit<import("react").ClassAttributes<HTMLSpanElement> & import("react").HTMLAttributes<HTMLSpanElement> & { + asChild?: boolean; +}, "ref"> & import("react").RefAttributes<HTMLSpanElement>>; + +declare namespace SpanPrimitiveTimeline { + type Element = ComponentRef<typeof Primitive.div>; + type Props = ComponentPropsWithoutRef<typeof Primitive.div> & { + timeRange?: SpanTimelineRange | undefined; + paddingEnd?: number | undefined; + }; +} + +declare const SpanPrimitiveTimeline: import("react").ForwardRefExoticComponent<Omit<import("react").ClassAttributes<HTMLDivElement> & import("react").HTMLAttributes<HTMLDivElement> & { + asChild?: boolean; +}, "ref"> & { + timeRange?: SpanTimelineRange | undefined; + paddingEnd?: number | undefined; +} & import("react").RefAttributes<HTMLDivElement>>; + +declare namespace SpanPrimitiveTimelineBar { + type Element = ComponentRef<typeof Primitive.div>; + type Props = ComponentPropsWithoutRef<typeof Primitive.div> & { + now?: number | undefined; + timeRange?: SpanTimelineRange | undefined; + }; +} + +declare const SpanPrimitiveTimelineBar: import("react").ForwardRefExoticComponent<Omit<import("react").ClassAttributes<HTMLDivElement> & import("react").HTMLAttributes<HTMLDivElement> & { + asChild?: boolean; +}, "ref"> & { + now?: number | undefined; + timeRange?: SpanTimelineRange | undefined; +} & import("react").RefAttributes<HTMLDivElement>>; + +declare namespace SpanPrimitiveTypeBadge { + type Element = ComponentRef<typeof Primitive.span>; + type Props = ComponentPropsWithoutRef<typeof Primitive.span>; +} + +declare const SpanPrimitiveTypeBadge: import("react").ForwardRefExoticComponent<Omit<import("react").ClassAttributes<HTMLSpanElement> & import("react").HTMLAttributes<HTMLSpanElement> & { + asChild?: boolean; +}, "ref"> & import("react").RefAttributes<HTMLSpanElement>>; + +declare const SpanResource: Resource<ClientOutput<"span">, [ + { + spans: SpanData[]; + } +]>; + +type SpanState = SpanItemState & { + children: SpanItemState[]; + timeRange: { + min: number; + max: number; + }; +}; + +type SpanTimelineRange = { + min: number; + max: number; +}; + +type ValidateClient<K extends keyof ScopeRegistry> = ScopeRegistry[K] extends { + methods: ClientMethods; +} ? "meta" extends keyof ScopeRegistry[K] ? ScopeRegistry[K]["meta"] extends ClientMetaType ? "events" extends keyof ScopeRegistry[K] ? ScopeRegistry[K]["events"] extends ClientEventsType<K> ? ScopeRegistry[K] : ClientError<`ERROR: ${K & string} has invalid events type`> : ScopeRegistry[K] : ClientError<`ERROR: ${K & string} has invalid meta type`> : "events" extends keyof ScopeRegistry[K] ? ScopeRegistry[K]["events"] extends ClientEventsType<K> ? ScopeRegistry[K] : ClientError<`ERROR: ${K & string} has invalid events type`> : ScopeRegistry[K] : ClientError<`ERROR: ${K & string} has invalid methods type`>; + +declare namespace entry_root_exports { + export { SpanByIndexProvider, SpanData, SpanItemState, span_d_exports as SpanPrimitive, SpanResource, SpanState }; +} + +declare namespace span_d_exports { + export { SpanPrimitiveChildren as Children, SpanPrimitiveCollapseToggle as CollapseToggle, SpanPrimitiveIndent as Indent, SpanPrimitiveName as Name, SpanPrimitiveRoot as Root, SpanTimelineRange, SpanPrimitiveStatusIndicator as StatusIndicator, SpanPrimitiveTimeline as Timeline, SpanPrimitiveTimelineBar as TimelineBar, SpanPrimitiveTypeBadge as TypeBadge }; +} + +export { entry_root_exports as entry_root }; diff --git a/api-surface/assistant-ui__react-opencode.ts b/api-surface/assistant-ui__react-opencode.ts new file mode 100644 index 0000000..48ffcc6 --- /dev/null +++ b/api-surface/assistant-ui__react-opencode.ts @@ -0,0 +1,1939 @@ +import { AssistantMessage, Event, FilePart, GlobalSession, Message as Message$1, Message as Message$2, Model, OpencodeClient, OpencodeClient as OpencodeClient$1, OpencodeClientConfig, Part, Part as Part$1, PermissionRequest, PermissionRequest as PermissionRequest$1, Provider, QuestionAnswer, QuestionAnswer as QuestionAnswer$1, QuestionRequest, QuestionRequest as QuestionRequest$1, ReasoningPart, Session, Session as Session$1, SessionStatus, SessionStatus as SessionStatus$1, SnapshotPart, StepFinishPart, StepStartPart, TextPart, ToolPart, ToolState, UserMessage, createOpencodeClient as createOpencodeClient$1 } from "@opencode-ai/sdk/v2/client"; + +import "@radix-ui/react-primitive"; + +import { StandardSchemaV1 } from "@standard-schema/spec"; + +import "radix-ui"; + +import "react-textarea-autosize"; + +import "zustand"; + +type AddToolResultOptions = { + messageId: string; + toolName: string; + toolCallId: string; + result: ReadonlyJSONValue; + isError: boolean; + artifact?: ReadonlyJSONValue | undefined; + modelContent?: readonly ToolModelContentPart[] | undefined; +}; + +type AncestorsOf<K extends ClientNames, Seen extends ClientNames = never> = K extends Seen ? never : ParentOf<K> extends never ? never : ParentOf<K> | AncestorsOf<ParentOf<K>, Seen | K>; + +type AppendMessage = Omit<ThreadMessage, "id"> & { + parentId: string | null; + sourceId: string | null; + runConfig: RunConfig | undefined; + startRun?: boolean | undefined; + steer?: boolean | undefined; +}; + +type AsNumber<K> = K extends `${infer N extends number}` ? N | K : never; + +type AssistantClient = { + [K in ClientNames]: AssistantClientAccessor<K>; +} & { + subscribe(listener: () => void): Unsubscribe$1; + on<TEvent extends AssistantEventName>(selector: AssistantEventSelector<TEvent>, callback: AssistantEventCallback<TEvent>): Unsubscribe$1; +}; + +type AssistantClientAccessor<K extends ClientNames> = (() => ClientSchemas[K]["methods"]) & (ClientMeta<K> | { + source: "root"; + query: Record<string, never>; +} | { + source: null; + query: null; +}) & { + name: K; +}; + +type AssistantEventCallback<TEvent extends AssistantEventName> = (payload: AssistantEventPayload[TEvent]) => void; + +type AssistantEventName = keyof AssistantEventPayload; + +type AssistantEventPayload = ClientEventMap & { + "*": WildcardPayload; +}; + +type AssistantEventScope<TEvent extends AssistantEventName> = "*" | EventSource<TEvent> | (EventSource<TEvent> extends ClientNames ? AncestorsOf<EventSource<TEvent>> : never); + +type AssistantEventSelector<TEvent extends AssistantEventName> = TEvent | { + scope: AssistantEventScope<TEvent>; + event: TEvent; +}; + +type AssistantRuntime = { + readonly threads: ThreadListRuntime; + readonly thread: ThreadRuntime; + registerModelContextProvider(provider: ModelContextProvider): Unsubscribe; +}; + +type AsyncIterableStream<T> = AsyncIterable<T> & ReadableStream<T>; + +type Attachment = PendingAttachment | CompleteAttachment; + +type AttachmentAdapter = { + accept: string; + add(state: { + file: File; + }): Promise<PendingAttachment> | AsyncGenerator<PendingAttachment, void>; + remove(attachment: Attachment): Promise<void>; + send(attachment: PendingAttachment): Promise<CompleteAttachment>; +}; + +type AttachmentAddErrorEvent = { + readonly reason: AttachmentAddErrorReason; + readonly message: string; + readonly attachmentId?: string; + readonly error?: Error; +}; + +type AttachmentAddErrorReason = "adapter-error" | "no-adapter" | "not-accepted"; + +type AttachmentRuntime<TSource extends AttachmentRuntimeSource = AttachmentRuntimeSource> = { + readonly path: AttachmentRuntimePath & { + attachmentSource: TSource; + }; + readonly source: TSource; + getState(): AttachmentState & { + source: TSource; + }; + remove(): Promise<void>; + subscribe(callback: () => void): Unsubscribe; +}; + +type AttachmentRuntimePath = ((MessageRuntimePath & { + readonly attachmentSource: "edit-composer" | "message"; +}) | (ThreadRuntimePath & { + readonly attachmentSource: "thread-composer"; +})) & { + readonly attachmentSelector: { + readonly type: "index"; + readonly index: number; + } | { + readonly type: "index"; + readonly index: number; + } | { + readonly type: "index"; + readonly index: number; + }; +}; + +type AttachmentRuntimeSource = AttachmentState["source"]; + +type AttachmentState = ThreadComposerAttachmentState | EditComposerAttachmentState | MessageAttachmentState; + +type BackendTool<TArgs extends Record<string, unknown> = Record<string, unknown>, TResult = unknown> = ToolBase<TArgs, TResult> & { + type: "backend"; + description?: undefined; + parameters?: undefined; + disabled?: undefined; + execute?: undefined; + toModelOutput?: undefined; + experimental_onSchemaValidationError?: undefined; + providerOptions?: undefined; +}; + +type BaseAttachment = { + id: string; + type: "image" | "document" | "file" | (string & {}); + name: string; + contentType?: string | undefined; + file?: File; + content?: ThreadUserMessagePart[]; +}; + +type BaseComposerState = { + readonly canCancel: boolean; + readonly canSend: boolean; + readonly isEditing: boolean; + readonly isEmpty: boolean; + readonly text: string; + readonly role: MessageRole; + readonly attachments: readonly Attachment[]; + readonly runConfig: RunConfig; + readonly attachmentAccept: string; + readonly dictation: DictationState | undefined; + readonly quote: QuoteInfo | undefined; + readonly queue: readonly QueueItemState[]; +}; + +type BaseThreadMessage = { + readonly status?: ThreadAssistantMessage["status"]; + readonly metadata: { + readonly unstable_state?: ReadonlyJSONValue; + readonly unstable_annotations?: readonly ReadonlyJSONValue[]; + readonly unstable_data?: readonly ReadonlyJSONValue[]; + readonly steps?: readonly ThreadStep[]; + readonly submittedFeedback?: { + readonly type: "negative" | "positive"; + }; + readonly timing?: MessageTiming; + readonly isOptimistic?: boolean; + readonly custom: Record<string, unknown>; + }; + readonly attachments?: ThreadUserMessage["attachments"]; +}; + +type ChatModelRunOptions = { + readonly messages: readonly ThreadMessage[]; + readonly runConfig: RunConfig; + readonly abortSignal: AbortSignal; + readonly context: ModelContext; + readonly unstable_assistantMessageId?: string | undefined; + readonly unstable_threadId?: string | undefined; + readonly unstable_parentId?: string | null | undefined; + unstable_getMessage(): ThreadMessage; +}; + +type ChatModelRunResult = { + readonly content?: readonly ThreadAssistantMessagePart[] | undefined; + readonly status?: MessageStatus | undefined; + readonly metadata?: { + readonly unstable_state?: ReadonlyJSONValue; + readonly unstable_annotations?: readonly ReadonlyJSONValue[] | undefined; + readonly unstable_data?: readonly ReadonlyJSONValue[] | undefined; + readonly steps?: readonly ThreadStep[] | undefined; + readonly timing?: MessageTiming | undefined; + readonly custom?: Record<string, unknown> | undefined; + }; +}; + +type ClientError<E extends string> = { + methods: Record<E, () => E>; + meta: { + source: ClientNames; + query: Record<E, E>; + }; + events: Record<`${E}.`, E>; +}; + +type ClientEventMap = UnionToIntersection<{ + [K in ClientNames]: ClientEvents<K>; +}[ClientNames]>; + +type ClientEvents<K extends ClientNames> = "events" extends keyof ClientSchemas[K] ? ClientSchemas[K]["events"] extends ClientEventsType<K> ? ClientSchemas[K]["events"] : never : never; + +type ClientEventsType<K extends ClientNames> = Record<`${K}.${string}`, unknown>; + +type ClientMeta<K extends ClientNames> = "meta" extends keyof ClientSchemas[K] ? Pick<ClientSchemas[K]["meta"] extends ClientMetaType ? ClientSchemas[K]["meta"] : never, "query" | "source"> : never; + +type ClientMetaType = { + source: ClientNames; + query: Record<string, unknown>; +}; + +interface ClientMethods { + [key: string | symbol]: (...args: any[]) => any; +} + +type ClientNames = keyof ClientSchemas extends (infer U) ? U : never; + +type ClientSchemas = keyof ScopeRegistry extends never ? { + "ERROR: No clients were defined": ClientError<"ERROR: No clients were defined">; +} : { + [K in keyof ScopeRegistry]: ValidateClient<K>; +}; + +type CompleteAttachment = BaseAttachment & { + status: CompleteAttachmentStatus; + content: ThreadUserMessagePart[]; +}; + +type CompleteAttachmentStatus = { + type: "complete"; +}; + +type ComposerRuntime = { + readonly path: ComposerRuntimePath; + readonly type: "edit" | "thread"; + getState(): ComposerState; + addAttachment(fileOrAttachment: File | CreateAttachment): Promise<void>; + setText(text: string): void; + setRole(role: MessageRole): void; + setRunConfig(runConfig: RunConfig): void; + reset(): Promise<void>; + clearAttachments(): Promise<void>; + send(options?: SendOptions): void; + cancel(): void; + steerQueueItem(queueItemId: string): void; + removeQueueItem(queueItemId: string): void; + subscribe(callback: () => void): Unsubscribe; + getAttachmentByIndex(idx: number): AttachmentRuntime; + startDictation(): void; + stopDictation(): void; + setQuote(quote: QuoteInfo | undefined): void; + unstable_on<E extends ComposerRuntimeEventType>(event: E, callback: ComposerRuntimeEventCallback<E>): Unsubscribe; +}; + +type ComposerRuntimeEventCallback<E extends ComposerRuntimeEventType> = (payload: ComposerRuntimeEventPayload[E]) => void; + +type ComposerRuntimeEventPayload = { + send: Record<string, never>; + attachmentAdd: Record<string, never>; + attachmentAddError: AttachmentAddErrorEvent; +}; + +type ComposerRuntimeEventType = keyof ComposerRuntimeEventPayload; + +type ComposerRuntimePath = (ThreadRuntimePath & { + readonly composerSource: "thread"; +}) | (MessageRuntimePath & { + readonly composerSource: "edit"; +}); + +type ComposerState = ThreadComposerState | EditComposerState; + +type CreateAppendMessage = string | { + parentId?: string | null | undefined; + sourceId?: string | null | undefined; + role?: AppendMessage["role"] | undefined; + content: AppendMessage["content"]; + attachments?: AppendMessage["attachments"] | undefined; + metadata?: AppendMessage["metadata"] | undefined; + createdAt?: Date | undefined; + runConfig?: AppendMessage["runConfig"] | undefined; + startRun?: boolean | undefined; +}; + +type CreateAttachment = { + id?: string; + type?: "image" | "document" | "file" | (string & {}); + name: string; + contentType?: string; + content: ThreadUserMessagePart[]; +}; + +type CreateResumeRunConfig = CreateStartRunConfig & { + stream?: (options: ChatModelRunOptions) => AsyncGenerator<ChatModelRunResult, void, unknown>; +}; + +type CreateStartRunConfig = { + parentId: string | null; + sourceId?: string | null | undefined; + runConfig?: RunConfig | undefined; +}; + +type DataMessagePart<T = any> = { + readonly type: "data"; + readonly name: string; + readonly data: T; +}; + +type DataPrefixedPart = { + readonly type: `data-${string}`; + readonly data: any; +}; + +type DeepPartial<T> = T extends readonly any[] ? readonly DeepPartial<T[number]>[] : T extends { + [key: string]: any; +} ? { + readonly [K in keyof T]?: DeepPartial<T[K]>; +} : T; + +interface DevToolsApiEntry { + api: Partial<AssistantClient>; + logs: EventLog[]; +} + +interface DevToolsHook { + apis: Map<number, DevToolsApiEntry>; + nextId: number; + listeners: Set<(apiId: number) => void>; +} + +declare namespace DictationAdapter { + type Status = { + type: "running" | "starting"; + } | { + type: "ended"; + reason: "cancelled" | "error" | "stopped"; + }; + type Result = { + transcript: string; + isFinal?: boolean; + }; + type Session = { + status: Status; + stop: () => Promise<void>; + cancel: () => void; + onSpeechStart: (callback: () => void) => Unsubscribe; + onSpeechEnd: (callback: (result: Result) => void) => Unsubscribe; + onSpeech: (callback: (result: Result) => void) => Unsubscribe; + }; +} + +type DictationAdapter = { + listen: () => DictationAdapter.Session; + disableInputDuringDictation?: boolean; +}; + +type DictationState = { + readonly status: DictationAdapter.Status; + readonly transcript?: string; + readonly inputDisabled?: boolean; +}; + +type EditComposerAttachmentState = Attachment & { + readonly source: "edit-composer"; +}; + +type EditComposerRuntime = Omit<ComposerRuntime, "getAttachmentByIndex" | "getState"> & { + readonly path: ComposerRuntimePath & { + composerSource: "edit"; + }; + readonly type: "edit"; + getState(): EditComposerState; + beginEdit(): void; + getAttachmentByIndex(idx: number): AttachmentRuntime & { + source: "edit-composer"; + }; +}; + +type EditComposerState = BaseComposerState & { + readonly type: "edit"; + readonly parentId: string | null; + readonly sourceId: string | null; +}; + +interface EventLog { + time: Date; + event: string; + data: unknown; +} + +type EventSource<T extends AssistantEventName> = T extends `${infer Source}.${string}` ? Source : never; + +type ExportedMessageRepository = { + headId?: string | null; + messages: Array<{ + message: ThreadMessage; + parentId: string | null; + runConfig?: RunConfig; + }>; +}; + +declare const ExportedMessageRepository: { + fromArray: (messages: readonly ThreadMessageLike[]) => ExportedMessageRepository; + fromBranchableArray: (items: readonly { + message: ThreadMessageLike; + parentId: string | null; + }[], options?: { + headId?: string | null; + }) => ExportedMessageRepository; +}; + +type ExternalStoreAdapter<T = ThreadMessage> = ExternalStoreAdapterBase<T> & (T extends ThreadMessage ? object : ExternalStoreMessageConverterAdapter<T>); + +type ExternalStoreAdapterBase<T> = { + isDisabled?: boolean | undefined; + isSendDisabled?: boolean | undefined; + isRunning?: boolean | undefined; + isLoading?: boolean | undefined; + messages?: readonly T[]; + messageRepository?: ExportedMessageRepository; + suggestions?: readonly ThreadSuggestion[] | undefined; + state?: ReadonlyJSONValue | undefined; + extras?: unknown; + setMessages?: ((messages: readonly T[]) => void) | undefined; + unstable_onBranchChange?: ((event: ExternalStoreBranchChange) => void) | undefined; + onImport?: ((messages: readonly ThreadMessage[]) => void) | undefined; + onExportExternalState?: (() => any) | undefined; + onLoadExternalState?: ((state: any) => void) | undefined; + onNew: (message: AppendMessage) => Promise<void>; + queue?: ExternalThreadQueueAdapter | undefined; + onEdit?: ((message: AppendMessage) => Promise<void>) | undefined; + onDelete?: ((messageId: string) => Promise<void> | void) | undefined; + onReload?: ((parentId: string | null, config: StartRunConfig) => Promise<void>) | undefined; + onResume?: ((config: ResumeRunConfig) => Promise<void>) | undefined; + onCancel?: (() => Promise<void>) | undefined; + onAddToolResult?: ((options: AddToolResultOptions) => Promise<void> | void) | undefined; + onResumeToolCall?: ((options: { + toolCallId: string; + payload: unknown; + }) => void) | undefined; + onRespondToToolApproval?: ((options: RespondToToolApprovalOptions) => Promise<void> | void) | undefined; + convertMessage?: ExternalStoreMessageConverter<T> | undefined; + adapters?: { + attachments?: AttachmentAdapter | undefined; + speech?: SpeechSynthesisAdapter | undefined; + dictation?: DictationAdapter | undefined; + voice?: RealtimeVoiceAdapter | undefined; + feedback?: FeedbackAdapter | undefined; + threadList?: ExternalStoreThreadListAdapter | undefined; + } | undefined; + unstable_capabilities?: { + copy?: boolean | undefined; + } | undefined; + unstable_enableToolInvocations?: boolean | undefined; + setToolStatuses?: ((statuses: Record<string, ToolExecutionStatus>) => void) | undefined; +}; + +type ExternalStoreBranchChange = { + headId: string | null; + visibleMessageIds: readonly string[]; +}; + +type ExternalStoreMessageConverter<T> = (message: T, idx: number) => ThreadMessageLike; + +type ExternalStoreMessageConverterAdapter<T> = { + convertMessage: ExternalStoreMessageConverter<T>; +}; + +type ExternalStoreSharedOptions = Pick<ExternalStoreAdapter, "isDisabled" | "isSendDisabled" | "suggestions" | "unstable_capabilities">; + +type ExternalStoreThreadData<TState extends "archived" | "regular"> = { + status: TState; + id: string; + remoteId?: string | undefined; + externalId?: string | undefined; + title?: string | undefined; + custom?: Record<string, unknown> | undefined; +}; + +type ExternalStoreThreadListAdapter = { + threadId?: string | undefined; + isLoading?: boolean | undefined; + threads?: readonly ExternalStoreThreadData<"regular">[] | undefined; + archivedThreads?: readonly ExternalStoreThreadData<"archived">[] | undefined; + onSwitchToNewThread?: (() => Promise<void> | void) | undefined; + onSwitchToThread?: ((threadId: string) => Promise<void> | void) | undefined; + onRename?: (threadId: string, newTitle: string) => (Promise<void> | void) | undefined; + onUpdateCustom?: ((threadId: string, custom: Record<string, unknown> | undefined) => Promise<void> | void) | undefined; + onArchive?: ((threadId: string) => Promise<void> | void) | undefined; + onUnarchive?: ((threadId: string) => Promise<void> | void) | undefined; + onDelete?: ((threadId: string) => Promise<void> | void) | undefined; +}; + +type ExternalThreadQueueAdapter = { + items: readonly QueueItemState[]; + enqueue: (message: AppendMessage, options: { + steer: boolean; + }) => void; + steer: (queueItemId: string) => void; + remove: (queueItemId: string) => void; + clear: (reason: "cancel-run" | "edit" | "reload") => void; +}; + +type FeedbackAdapter = { + submit: (feedback: FeedbackAdapterFeedback) => void; +}; + +type FeedbackAdapterFeedback = { + message: ThreadMessage; + type: "negative" | "positive"; +}; + +type FileMessagePart = { + readonly type: "file"; + readonly filename?: string; + readonly data: string; + readonly mimeType: string; + readonly parentId?: string; +}; + +type FrontendTool<TArgs extends Record<string, unknown> = Record<string, unknown>, TResult = unknown> = ToolBase<TArgs, TResult> & { + type: "frontend"; + description?: string | undefined; + parameters: StandardSchemaV1<TArgs> | JSONSchema7; + disabled?: boolean; + execute?: ToolExecuteFunction<TArgs, TResult>; + toModelOutput?: ToolModelOutputFunction<TArgs, TResult>; + experimental_onSchemaValidationError?: OnSchemaValidationErrorFunction<TResult>; + providerOptions?: ProviderOptions; +}; + +type GenerativeUIMessagePart = { + readonly type: "generative-ui"; + readonly spec: GenerativeUISpec; + readonly id?: string; + readonly parentId?: string; +}; + +type GenerativeUINode = string | { + readonly component: string; + readonly props?: Record<string, unknown>; + readonly children?: readonly GenerativeUINode[]; + readonly key?: string; +}; + +type GenerativeUISpec = { + readonly root: GenerativeUINode | readonly GenerativeUINode[]; +}; + +type HumanTool<TArgs extends Record<string, unknown> = Record<string, unknown>, TResult = unknown> = ToolBase<TArgs, TResult> & { + type: "human"; + description?: string | undefined; + parameters: StandardSchemaV1<TArgs> | JSONSchema7; + disabled?: boolean; + display?: "standalone"; + execute?: undefined; + toModelOutput?: undefined; + experimental_onSchemaValidationError?: undefined; + providerOptions?: ProviderOptions; +}; + +type ImageMessagePart = { + readonly type: "image"; + readonly image: string; + readonly filename?: string; +}; + +interface JSONSchema7 { + $id?: string | undefined; + $ref?: string | undefined; + $schema?: JSONSchema7Version | undefined; + $comment?: string | undefined; + $defs?: { + [key: string]: JSONSchema7Definition; + } | undefined; + type?: JSONSchema7TypeName | JSONSchema7TypeName[] | undefined; + enum?: JSONSchema7Type[] | undefined; + const?: JSONSchema7Type | undefined; + multipleOf?: number | undefined; + maximum?: number | undefined; + exclusiveMaximum?: number | undefined; + minimum?: number | undefined; + exclusiveMinimum?: number | undefined; + maxLength?: number | undefined; + minLength?: number | undefined; + pattern?: string | undefined; + items?: JSONSchema7Definition | JSONSchema7Definition[] | undefined; + additionalItems?: JSONSchema7Definition | undefined; + maxItems?: number | undefined; + minItems?: number | undefined; + uniqueItems?: boolean | undefined; + contains?: JSONSchema7Definition | undefined; + maxProperties?: number | undefined; + minProperties?: number | undefined; + required?: string[] | undefined; + properties?: { + [key: string]: JSONSchema7Definition; + } | undefined; + patternProperties?: { + [key: string]: JSONSchema7Definition; + } | undefined; + additionalProperties?: JSONSchema7Definition | undefined; + dependencies?: { + [key: string]: JSONSchema7Definition | string[]; + } | undefined; + propertyNames?: JSONSchema7Definition | undefined; + if?: JSONSchema7Definition | undefined; + then?: JSONSchema7Definition | undefined; + else?: JSONSchema7Definition | undefined; + allOf?: JSONSchema7Definition[] | undefined; + anyOf?: JSONSchema7Definition[] | undefined; + oneOf?: JSONSchema7Definition[] | undefined; + not?: JSONSchema7Definition | undefined; + format?: string | undefined; + contentMediaType?: string | undefined; + contentEncoding?: string | undefined; + definitions?: { + [key: string]: JSONSchema7Definition; + } | undefined; + title?: string | undefined; + description?: string | undefined; + default?: JSONSchema7Type | undefined; + readOnly?: boolean | undefined; + writeOnly?: boolean | undefined; + examples?: JSONSchema7Type | undefined; +} + +interface JSONSchema7Array extends Array<JSONSchema7Type> { +} + +type JSONSchema7Definition = JSONSchema7 | boolean; + +interface JSONSchema7Object { + [key: string]: JSONSchema7Type; +} + +type JSONSchema7Type = string | number | boolean | JSONSchema7Object | JSONSchema7Array | null; + +type JSONSchema7TypeName = "array" | "boolean" | "integer" | "null" | "number" | "object" | "string"; + +type JSONSchema7Version = string; + +type LanguageModelConfig = { + apiKey?: string; + baseUrl?: string; + modelName?: string; + reasoningEffort?: string; +}; + +type LanguageModelV1CallSettings = { + maxTokens?: number; + temperature?: number; + topP?: number; + presencePenalty?: number; + frequencyPenalty?: number; + seed?: number; + headers?: Record<string, string | undefined>; +}; + +type Listener = (event: OpenCodeServerEvent) => void; + +type McpAppMetadata = { + readonly resourceUri: string; + readonly mimeType?: string; + readonly visibility?: readonly ("app" | "model")[]; +}; + +type McpServerConfig = { + type: "http" | "sse"; + url: string; + headers?: Record<string, string>; + redirect?: "error" | "follow"; + connectionTimeout?: number | undefined; +} | { + type: "stdio"; + command: string; + args?: readonly string[]; + env?: Record<string, string>; + cwd?: string; + connectionTimeout?: number | undefined; +}; + +type McpTool = ToolBase<Record<string, unknown>, unknown> & { + type: "mcp"; + server: McpServerConfig; + description?: undefined; + parameters?: undefined; + disabled?: boolean; + execute?: undefined; + toModelOutput?: undefined; + experimental_onSchemaValidationError?: undefined; + providerOptions?: undefined; +}; + +type MessageAttachmentState = CompleteAttachment & { + readonly source: "message"; +}; + +type MessageCommonProps = { + readonly id: string; + readonly createdAt: Date; +}; + +type MessagePartRuntime = { + addToolResult(result: any | ToolResponse<any>): void; + resumeToolCall(payload: unknown): void; + respondToToolApproval(response: ToolApprovalResponse): void; + readonly path: MessagePartRuntimePath; + getState(): MessagePartState; + subscribe(callback: () => void): Unsubscribe; +}; + +type MessagePartRuntimePath = MessageRuntimePath & { + readonly messagePartSelector: { + readonly type: "index"; + readonly index: number; + } | { + readonly type: "toolCallId"; + readonly toolCallId: string; + }; +}; + +type MessagePartState = (ThreadUserMessagePart | ThreadAssistantMessagePart) & { + readonly status: MessagePartStatus | ToolCallMessagePartStatus; +}; + +type MessagePartStatus = { + readonly type: "running"; +} | { + readonly type: "complete"; +} | { + readonly type: "incomplete"; + readonly reason: "cancelled" | "content-filter" | "error" | "length" | "other"; + readonly error?: unknown; +}; + +type MessageRole = ThreadMessage["role"]; + +type MessageRuntime = { + readonly path: MessageRuntimePath; + readonly composer: EditComposerRuntime; + getState(): MessageState; + delete(): void | Promise<void>; + reload(config?: ReloadConfig): void; + speak(): void; + stopSpeaking(): void; + submitFeedback(_param0: { + type: "positive" | "negative"; + }): void; + switchToBranch(_param1: { + position?: "previous" | "next" | undefined; + branchId?: string | undefined; + }): void; + unstable_getCopyText(): string; + subscribe(callback: () => void): Unsubscribe; + getMessagePartByIndex(idx: number): MessagePartRuntime; + getMessagePartByToolCallId(toolCallId: string): MessagePartRuntime; + getAttachmentByIndex(idx: number): AttachmentRuntime & { + source: "message"; + }; +}; + +type MessageRuntimePath = ThreadRuntimePath & { + readonly messageSelector: { + readonly type: "messageId"; + readonly messageId: string; + } | { + readonly type: "index"; + readonly index: number; + }; +}; + +type MessageState = ThreadMessage & { + readonly parentId: string | null; + readonly index: number; + readonly isLast: boolean; + readonly branchNumber: number; + readonly branchCount: number; + readonly speech: SpeechState | undefined; +}; + +type MessageStatus = { + readonly type: "running"; +} | { + readonly type: "requires-action"; + readonly reason: "interrupt" | "tool-calls"; +} | { + readonly type: "complete"; + readonly reason: "stop" | "unknown"; +} | { + readonly type: "incomplete"; + readonly reason: "cancelled" | "content-filter" | "error" | "length" | "other" | "tool-calls"; + readonly error?: ReadonlyJSONValue; +}; + +type MessageTiming = { + readonly streamStartTime: number; + readonly firstTokenTime?: number; + readonly totalStreamTime?: number; + readonly tokenCount?: number; + readonly tokensPerSecond?: number; + readonly totalChunks: number; + readonly toolCallCount: number; +}; + +type MessageWithParts = { + info: Message$1; + parts: Part[]; +}; + +type ModelContext = { + priority?: number | undefined; + system?: string | undefined; + tools?: Record<string, Tool<any, any>> | undefined; + callSettings?: LanguageModelV1CallSettings | undefined; + config?: LanguageModelConfig | undefined; + unstable_composerMetadata?: Record<string, unknown> | undefined; +}; + +type ModelContextProvider = { + getModelContext: () => ModelContext; + subscribe?: (callback: () => void) => Unsubscribe; +}; + +type ObjectKey<T> = keyof T & (string | number); + +type OnSchemaValidationErrorFunction<TResult> = ToolExecuteFunction<unknown, TResult>; + +declare class OpenCodeEventSource { + private readonly client; + private readonly listeners; + private readonly reconnectDelayMs; + private readonly maxReconnectDelayMs; + private abortController; + private connectionPromise; + private stopped; + private nextReconnectDelayMs; + private hadConnection; + constructor(client: OpencodeClient); + subscribe(listener: Listener): () => void; + dispose(): void; + private emit; + private connect; + private disconnect; + private run; +} + +type OpenCodeEventSourceProvider = () => Pick<OpenCodeEventSource, "subscribe">; + +type OpenCodeLoadState = { + type: "idle"; +} | { + type: "loading"; +} | { + type: "ready"; +} | { + type: "error"; + error: unknown; +}; + +type OpenCodePartPayload = { + readonly name: string; + readonly data: Record<string, unknown>; +}; + +type OpenCodePermissionRequest = { + id: string; + sessionId: string; + permission: string; + patterns: readonly string[]; + metadata: Record<string, unknown>; + always: readonly string[]; + tool?: PermissionRequest["tool"] | undefined; + toolName?: string | undefined; + toolInput?: unknown; + title?: string | undefined; + askedAt: number; + raw: PermissionRequest; +}; + +type OpenCodePermissionResponse = "always" | "once" | "reject"; + +type OpenCodeProjectedThreadMessage = ThreadMessageLike; + +type OpenCodeQuestionRequest = QuestionRequest & { + askedAt: number; +}; + +type OpenCodeRunState = { + type: "idle"; +} | { + type: "streaming"; +} | { + type: "cancelling"; +} | { + type: "reverting"; +} | { + type: "error"; + error: unknown; +}; + +type OpenCodeRuntime = AssistantRuntime; + +type OpenCodeRuntimeExtras = { + session: Session | null; + state: OpenCodeThreadState; + permissions: OpenCodeThreadState["interactions"]["permissions"]["pending"]; + questions: OpenCodeThreadState["interactions"]["questions"]["pending"]; + fork: (messageId: string) => Promise<string>; + revert: (messageId: string) => Promise<void>; + unrevert: () => Promise<void>; + cancel: () => Promise<void>; + refresh: () => Promise<void>; + replyToPermission: (permissionId: string, response: OpenCodePermissionResponse) => Promise<void>; + replyToQuestion: (questionId: string, answers: readonly QuestionAnswer[]) => Promise<void>; + rejectQuestion: (questionId: string) => Promise<void>; +}; + +type OpenCodeRuntimeOptions = ExternalStoreSharedOptions & { + onThreadIdChange?: ((threadId: string | undefined) => void) | undefined; + client?: OpencodeClient; + baseUrl?: string | undefined; + initialSessionId?: string | undefined; + defaultModel?: { + providerID: string; + modelID: string; + } | undefined; + defaultAgent?: string | undefined; + onError?: (error: unknown) => void; + adapters?: { + attachments?: AttachmentAdapter; + speech?: SpeechSynthesisAdapter; + dictation?: DictationAdapter; + voice?: RealtimeVoiceAdapter; + feedback?: FeedbackAdapter; + } | undefined; +}; + +type OpenCodeServerEvent = { + type: string; + sessionId: string | undefined; + raw: unknown; + properties: Record<string, unknown>; +}; + +type OpenCodeServerMessage = { + id: string; + info: Message$1 | undefined; + parts: readonly Part[]; + shadowParts: readonly ThreadUserMessagePart[] | undefined; +}; + +type OpenCodeStateEvent = { + type: "history.loading"; +} | { + type: "history.loaded"; + session: Session | null; + messages: readonly MessageWithParts[]; +} | { + type: "history.failed"; + error: unknown; +} | { + type: "session.updated"; + session: Session; +} | { + type: "session.status"; + status: SessionStatus; +} | { + type: "session.idle"; + sessionId: string; +} | { + type: "session.compacted"; + sessionId: string; +} | { + type: "run.started"; +} | { + type: "run.cancelling"; +} | { + type: "run.reverting"; +} | { + type: "run.failed"; + error: unknown; +} | { + type: "message.updated"; + info: Message$1; +} | { + type: "message.removed"; + messageId: string; +} | { + type: "part.updated"; + messageId: string; + part: Part; +} | { + type: "part.delta"; + messageId: string; + partId: string; + field: string; + delta: string; +} | { + type: "part.removed"; + messageId: string; + partId: string; +} | { + type: "permission.asked"; + request: OpenCodePermissionRequest; +} | { + type: "permission.replied"; + permissionId: string; + reply: OpenCodePermissionResponse; +} | { + type: "question.asked"; + request: OpenCodeQuestionRequest; +} | { + type: "question.replied"; + questionId: string; + answers: readonly QuestionAnswer[]; +} | { + type: "question.rejected"; + questionId: string; +} | { + type: "unhandled.event"; + event: OpenCodeUnhandledEvent; +} | { + type: "local.message.queued"; + pending: PendingUserMessage; +} | { + type: "local.message.reconciled"; + clientId: string; + messageId: string; +} | { + type: "local.message.failed"; + clientId: string; + error: unknown; +}; + +declare class OpenCodeThreadController implements OpenCodeThreadControllerLike { + private readonly client; + private readonly sessionId; + private state; + private readonly listeners; + private readonly getEventSource; + private unsubscribeFromEvents; + private loadPromise; + private reconnectSyncToken; + private readonly stagedMessages; + constructor(client: OpencodeClient, getEventSource: OpenCodeEventSourceProvider, sessionId: string); + private ensureEventSubscription; + private handleStreamReconnect; + dispose(): void; + getState: () => OpenCodeThreadState; + subscribe: (listener: () => void) => () => void; + load(force?: boolean): Promise<void>; + refresh(): Promise<void>; + private createPendingMessage; + private promptMessage; + sendMessage(message: AppendMessage, options?: OpenCodeUserMessageOptions): Promise<void>; + stageMessage(message: AppendMessage, options?: OpenCodeUserMessageOptions): Promise<void>; + sendStagedMessage(parentId: string, options?: OpenCodeUserMessageOptions): Promise<boolean>; + cancel(): Promise<void>; + revert(messageId: string): Promise<void>; + unrevert(): Promise<void>; + fork(messageId: string): Promise<string>; + replyToPermission(permissionId: string, response: OpenCodePermissionResponse): Promise<void>; + replyToQuestion(questionId: string, answers: readonly QuestionAnswer$1[]): Promise<void>; + rejectQuestion(questionId: string): Promise<void>; + private refreshInBackground; + private handleServerEvent; + private dispatch; +} + +type OpenCodeThreadControllerLike = { + getState(): OpenCodeThreadControllerSnapshot; + subscribe(listener: () => void): () => void; + load(force?: boolean): Promise<void>; + refresh(): Promise<void>; + sendMessage(message: AppendMessage, options?: OpenCodeUserMessageOptions): Promise<void>; + stageMessage(message: AppendMessage, options?: OpenCodeUserMessageOptions): Promise<void>; + sendStagedMessage(parentId: string, options?: OpenCodeUserMessageOptions): Promise<boolean>; + cancel(): Promise<void>; + revert(messageId: string): Promise<void>; + unrevert(): Promise<void>; + fork(messageId: string): Promise<string>; + replyToPermission(permissionId: string, response: OpenCodePermissionResponse): Promise<void>; + replyToQuestion(questionId: string, answers: readonly QuestionAnswer[]): Promise<void>; + rejectQuestion(questionId: string): Promise<void>; +}; + +type OpenCodeThreadControllerSnapshot = OpenCodeThreadState; + +type OpenCodeThreadState = { + sessionId: string; + session: Session | null; + sessionStatus: SessionStatus | null; + loadState: OpenCodeLoadState; + runState: OpenCodeRunState; + messageOrder: readonly string[]; + messagesById: Readonly<Record<string, OpenCodeServerMessage>>; + pendingUserMessages: Readonly<Record<string, PendingUserMessage>>; + interactions: { + permissions: { + pending: Readonly<Record<string, OpenCodePermissionRequest>>; + resolved: Readonly<Record<string, { + request: OpenCodePermissionRequest; + reply: OpenCodePermissionResponse; + respondedAt: number; + }>>; + }; + questions: { + pending: Readonly<Record<string, OpenCodeQuestionRequest>>; + answered: Readonly<Record<string, { + request: OpenCodeQuestionRequest; + answers: readonly QuestionAnswer[]; + respondedAt: number; + }>>; + rejected: Readonly<Record<string, { + request: OpenCodeQuestionRequest; + rejectedAt: number; + }>>; + }; + }; + unhandledEvents: readonly OpenCodeUnhandledEvent[]; + sync: { + lastHistoryLoadAt?: number; + lastEventAt?: number; + lastCompactionAt?: number; + }; +}; + +type OpenCodeThreadStateSelector<T> = (state: OpenCodeThreadState) => T; + +type OpenCodeUnhandledEvent = { + type: string; + sessionId: string | undefined; + properties: Record<string, unknown>; + seenAt: number; +}; + +type OpenCodeUserMessageOptions = { + model?: { + providerID: string; + modelID: string; + } | undefined; + agent?: string | undefined; + noReply?: boolean | undefined; +}; + +type ParentOf<K extends ClientNames> = AssistantClientAccessor<K> extends { + source: infer S; +} ? S extends ClientNames ? S : never : never; + +type PartProviderMetadata = { + readonly [providerName: string]: ReadonlyJSONObject; +}; + +type PendingAttachment = BaseAttachment & { + status: PendingAttachmentStatus; + file: File; +}; + +type PendingAttachmentStatus = { + type: "running"; + reason: "uploading"; + progress: number; +} | { + type: "requires-action"; + reason: "composer-send"; +} | { + type: "incomplete"; + reason: "error" | "upload-paused"; + message?: string; +}; + +type PendingUserMessage = { + clientId: string; + sessionId: string; + createdAt: number; + parentId: string | null; + sourceId: string | null; + runConfig: unknown; + contentText: string; + parts: readonly ThreadUserMessagePart[]; + status: "failed" | "pending"; + error?: unknown; +}; + +type ProviderOptions = Record<string, Record<string, unknown>>; + +type ProviderTool<TArgs extends Record<string, unknown> = Record<string, unknown>, TResult = unknown> = ToolBase<TArgs, TResult> & { + type: "provider"; + providerId: `${string}.${string}`; + parameters?: StandardSchemaV1<TArgs> | JSONSchema7 | undefined; + args: Record<string, unknown>; + supportsDeferredResults?: boolean; + description?: undefined; + disabled?: boolean; + execute?: undefined; + toModelOutput?: undefined; + experimental_onSchemaValidationError?: undefined; + providerOptions?: ProviderOptions; +}; + +type QueueItemState = { + readonly id: string; + readonly prompt: string; +}; + +type QuoteInfo = { + readonly text: string; + readonly messageId: string; +}; + +type ReadonlyJSONArray = readonly ReadonlyJSONValue[]; + +type ReadonlyJSONObject = { + readonly [key: string]: ReadonlyJSONValue; +}; + +type ReadonlyJSONValue = null | string | number | boolean | ReadonlyJSONObject | ReadonlyJSONArray; + +declare namespace RealtimeVoiceAdapter { + type Status = { + type: "running" | "starting"; + } | { + type: "ended"; + reason: "cancelled" | "error" | "finished"; + error?: unknown; + }; + type Mode = "listening" | "speaking"; + type TranscriptItem = { + role: "assistant" | "user"; + text: string; + isFinal?: boolean; + }; + type Session = { + status: Status; + isMuted: boolean; + disconnect: () => void; + mute: () => void; + unmute: () => void; + onStatusChange: (callback: (status: Status) => void) => Unsubscribe; + onTranscript: (callback: (transcript: TranscriptItem) => void) => Unsubscribe; + onModeChange: (callback: (mode: Mode) => void) => Unsubscribe; + onVolumeChange: (callback: (volume: number) => void) => Unsubscribe; + }; +} + +type RealtimeVoiceAdapter = { + connect: (options: { + abortSignal?: AbortSignal; + }) => RealtimeVoiceAdapter.Session; +}; + +type ReasoningMessagePart = { + readonly type: "reasoning"; + readonly text: string; + readonly providerMetadata?: PartProviderMetadata; + readonly parentId?: string; +}; + +type ReloadConfig = { + runConfig?: RunConfig; +}; + +type RespondToToolApprovalOptions = { + approvalId: string; + approved: boolean; + optionId?: string; + reason?: string; +}; + +type ResumeRunConfig = StartRunConfig & { + stream?: (options: ChatModelRunOptions) => AsyncGenerator<ChatModelRunResult, void, unknown>; +}; + +type RunConfig = { + readonly custom?: Record<string, unknown>; +}; + +type RuntimeCapabilities = { + readonly switchToBranch: boolean; + readonly switchBranchDuringRun: boolean; + readonly edit: boolean; + readonly reload: boolean; + readonly delete: boolean; + readonly cancel: boolean; + readonly unstable_copy: boolean; + readonly speech: boolean; + readonly dictation: boolean; + readonly voice: boolean; + readonly attachments: boolean; + readonly feedback: boolean; + readonly queue: boolean; +}; + +declare const STREAM_RECONNECTED_EVENT_TYPE = "stream.reconnected"; + +interface ScopeRegistry { + [key: string]: { methods: any; meta?: any; events?: any }; +} + +type SendOptions = { + startRun?: boolean; + steer?: boolean; +}; + +type SourceMessagePart = { + readonly type: "source"; + readonly sourceType: "url"; + readonly id: string; + readonly url: string; + readonly title?: string; + readonly providerMetadata?: SourceProviderMetadata; + readonly parentId?: string; +} | { + readonly type: "source"; + readonly sourceType: "document"; + readonly id: string; + readonly url?: undefined; + readonly title: string; + readonly mediaType: string; + readonly filename?: string; + readonly providerMetadata?: SourceProviderMetadata; + readonly parentId?: string; +}; + +type SourceProviderMetadata = PartProviderMetadata; + +interface SpeechRecognitionConstructor { + new (): SpeechRecognitionInstance; +} + +interface SpeechRecognitionInstance extends EventTarget { + lang: string; + continuous: boolean; + interimResults: boolean; + start(): void; + stop(): void; + abort(): void; +} + +type SpeechState = { + readonly messageId: string; + readonly status: SpeechSynthesisAdapter.Status; +}; + +declare namespace SpeechSynthesisAdapter { + type Status = { + type: "running" | "starting"; + } | { + type: "ended"; + reason: "cancelled" | "error" | "finished"; + error?: unknown; + }; + type Utterance = { + status: Status; + cancel: () => void; + subscribe: (callback: () => void) => Unsubscribe; + }; +} + +type SpeechSynthesisAdapter = { + speak: (text: string) => SpeechSynthesisAdapter.Utterance; +}; + +type StartRunConfig = { + parentId: string | null; + sourceId: string | null; + runConfig: RunConfig; +}; + +declare const TOOL_RESPONSE_SYMBOL: unique symbol; + +type TextMessagePart = { + readonly type: "text"; + readonly text: string; + readonly providerMetadata?: PartProviderMetadata; + readonly parentId?: string; +}; + +type ThreadAssistantMessage = MessageCommonProps & { + readonly role: "assistant"; + readonly content: readonly ThreadAssistantMessagePart[]; + readonly status: MessageStatus; + readonly metadata: { + readonly unstable_state: ReadonlyJSONValue; + readonly unstable_annotations: readonly ReadonlyJSONValue[]; + readonly unstable_data: readonly ReadonlyJSONValue[]; + readonly steps: readonly ThreadStep[]; + readonly submittedFeedback?: { + readonly type: "negative" | "positive"; + }; + readonly timing?: MessageTiming; + readonly isOptimistic?: boolean; + readonly custom: Record<string, unknown>; + }; +}; + +type ThreadAssistantMessagePart = TextMessagePart | ReasoningMessagePart | ToolCallMessagePart | SourceMessagePart | FileMessagePart | ImageMessagePart | DataMessagePart | GenerativeUIMessagePart; + +type ThreadComposerAttachmentState = Attachment & { + readonly source: "thread-composer"; +}; + +type ThreadComposerRuntime = Omit<ComposerRuntime, "getAttachmentByIndex" | "getState"> & { + readonly path: ComposerRuntimePath & { + composerSource: "thread"; + }; + readonly type: "thread"; + getState(): ThreadComposerState; + getAttachmentByIndex(idx: number): AttachmentRuntime & { + source: "thread-composer"; + }; +}; + +type ThreadComposerState = BaseComposerState & { + readonly type: "thread"; +}; + +type ThreadListItemEventCallback<E extends ThreadListItemEventType> = (payload: ThreadListItemEventPayload[E]) => void; + +type ThreadListItemEventPayload = { + switchedTo: Record<string, never>; + switchedAway: Record<string, never>; +}; + +type ThreadListItemEventType = keyof ThreadListItemEventPayload; + +type ThreadListItemRuntime = { + readonly path: ThreadListItemRuntimePath; + getState(): ThreadListItemState; + initialize(): Promise<{ + remoteId: string; + externalId: string | undefined; + }>; + generateTitle(): Promise<void>; + switchTo(options?: { + unarchive?: boolean; + }): Promise<void>; + rename(newTitle: string): Promise<void>; + updateCustom(custom: Record<string, unknown> | undefined): Promise<void>; + archive(): Promise<void>; + unarchive(): Promise<void>; + delete(): Promise<void>; + detach(): void; + subscribe(callback: () => void): Unsubscribe; + unstable_on<E extends ThreadListItemEventType>(event: E, callback: ThreadListItemEventCallback<E>): Unsubscribe; + __internal_getRuntime(): ThreadListItemRuntime; +}; + +type ThreadListItemRuntimePath = { + readonly ref: string; + readonly threadSelector: { + readonly type: "main"; + } | { + readonly type: "index"; + readonly index: number; + } | { + readonly type: "archiveIndex"; + readonly index: number; + } | { + readonly type: "threadId"; + readonly threadId: string; + }; +}; + +type ThreadListItemState = { + readonly isMain: boolean; + readonly id: string; + readonly remoteId: string | undefined; + readonly externalId: string | undefined; + readonly status: ThreadListItemStatus; + readonly title?: string | undefined; + readonly lastMessageAt?: Date | undefined; + readonly custom?: Record<string, unknown> | undefined; +}; + +type ThreadListItemStatus = "archived" | "deleted" | "new" | "regular"; + +type ThreadListRuntime = { + getState(): ThreadListState; + subscribe(callback: () => void): Unsubscribe; + readonly main: ThreadRuntime; + getById(threadId: string): ThreadRuntime; + readonly mainItem: ThreadListItemRuntime; + getItemById(threadId: string): ThreadListItemRuntime; + getItemByIndex(idx: number): ThreadListItemRuntime; + getArchivedItemByIndex(idx: number): ThreadListItemRuntime; + switchToThread(threadId: string, options?: { + unarchive?: boolean; + }): Promise<void>; + switchToNewThread(): Promise<void>; + getLoadThreadsPromise(): Promise<void>; + reload(): Promise<void>; + loadMore(): Promise<void>; +}; + +type ThreadListState = { + readonly mainThreadId: string; + readonly newThreadId: string | undefined; + readonly threadIds: readonly string[]; + readonly archivedThreadIds: readonly string[]; + readonly isLoading: boolean; + readonly isLoadingMore: boolean; + readonly hasMore: boolean; + readonly threadItems: Readonly<Record<string, Omit<ThreadListItemState, "isMain" | "threadId">>>; +}; + +type ThreadMessage = BaseThreadMessage & (ThreadSystemMessage | ThreadUserMessage | ThreadAssistantMessage); + +type ThreadMessageLike = { + readonly role: "assistant" | "system" | "user"; + readonly content: string | readonly (TextMessagePart | ReasoningMessagePart | SourceMessagePart | ImageMessagePart | FileMessagePart | DataMessagePart | GenerativeUIMessagePart | Unstable_AudioMessagePart | DataPrefixedPart | { + readonly type: "tool-call"; + readonly toolCallId?: string; + readonly toolName: string; + readonly args?: ReadonlyJSONObject; + readonly argsText?: string; + readonly artifact?: any; + readonly result?: any | undefined; + readonly isError?: boolean | undefined; + readonly parentId?: string | undefined; + readonly messages?: readonly ThreadMessage[] | undefined; + readonly interrupt?: { + type: "human"; + payload: unknown; + }; + readonly timing?: ToolCallTiming; + readonly providerMetadata?: PartProviderMetadata; + readonly approval?: { + readonly id: string; + readonly approved?: boolean; + readonly reason?: string; + readonly isAutomatic?: boolean; + readonly options?: readonly ToolApprovalOption[]; + readonly optionId?: string; + readonly resolution?: "cancelled" | "expired"; + }; + })[]; + readonly id?: string | undefined; + readonly createdAt?: Date | undefined; + readonly status?: MessageStatus | undefined; + readonly attachments?: readonly (Omit<CompleteAttachment, "content"> & { + readonly content: readonly (ThreadUserMessagePart | DataPrefixedPart)[]; + })[] | undefined; + readonly metadata?: { + readonly unstable_state?: ReadonlyJSONValue; + readonly unstable_annotations?: readonly ReadonlyJSONValue[] | undefined; + readonly unstable_data?: readonly ReadonlyJSONValue[] | undefined; + readonly steps?: readonly ThreadStep[] | undefined; + readonly timing?: MessageTiming | undefined; + readonly submittedFeedback?: { + readonly type: "negative" | "positive"; + }; + readonly isOptimistic?: boolean | undefined; + readonly custom?: Record<string, unknown> | undefined; + } | undefined; +}; + +type ThreadRuntime = { + readonly path: ThreadRuntimePath; + readonly composer: ThreadComposerRuntime; + getState(): ThreadState; + append(message: CreateAppendMessage): void; + deleteMessage(messageId: string): void | Promise<void>; + startRun(config: CreateStartRunConfig): void; + resumeRun(config: CreateResumeRunConfig): void; + exportExternalState(): any; + importExternalState(state: any): void; + subscribe(callback: () => void): Unsubscribe; + cancelRun(): void; + getModelContext(): ModelContext; + export(): ExportedMessageRepository; + import(repository: ExportedMessageRepository): void; + reset(initialMessages?: readonly ThreadMessageLike[]): void; + getMessageByIndex(idx: number): MessageRuntime; + getMessageById(messageId: string): MessageRuntime; + stopSpeaking(): void; + connectVoice(): void; + disconnectVoice(): void; + getVoiceVolume(): number; + subscribeVoiceVolume(callback: () => void): Unsubscribe; + muteVoice(): void; + unmuteVoice(): void; + unstable_on<E extends ThreadRuntimeEventType>(event: E, callback: ThreadRuntimeEventCallback<E>): Unsubscribe; +}; + +type ThreadRuntimeEventCallback<E extends ThreadRuntimeEventType> = (payload: ThreadRuntimeEventPayload[E]) => void; + +type ThreadRuntimeEventPayload = { + runStart: Record<string, never>; + runEnd: Record<string, never>; + initialize: Record<string, never>; + modelContextUpdate: Record<string, never>; +}; + +type ThreadRuntimeEventType = keyof ThreadRuntimeEventPayload; + +type ThreadRuntimePath = { + readonly ref: string; + readonly threadSelector: { + readonly type: "main"; + } | { + readonly type: "threadId"; + readonly threadId: string; + }; +}; + +type ThreadState = { + readonly threadId: string; + readonly metadata: ThreadListItemState; + readonly isDisabled: boolean; + readonly isLoading: boolean; + readonly isRunning: boolean; + readonly capabilities: RuntimeCapabilities; + readonly messages: readonly ThreadMessage[]; + readonly state: ReadonlyJSONValue; + readonly suggestions: readonly ThreadSuggestion[]; + readonly extras: unknown; + readonly speech: SpeechState | undefined; + readonly voice: VoiceSessionState | undefined; +}; + +type ThreadStep = { + readonly messageId?: string; + readonly usage?: { + readonly inputTokens: number; + readonly outputTokens: number; + } | undefined; +}; + +type ThreadSuggestion = { + prompt: string; +}; + +type ThreadSystemMessage = MessageCommonProps & { + readonly role: "system"; + readonly content: readonly [ + TextMessagePart + ]; + readonly metadata: { + readonly unstable_state?: undefined; + readonly unstable_annotations?: undefined; + readonly unstable_data?: undefined; + readonly steps?: undefined; + readonly submittedFeedback?: undefined; + readonly timing?: undefined; + readonly custom: Record<string, unknown>; + }; +}; + +type ThreadUserMessage = MessageCommonProps & { + readonly role: "user"; + readonly content: readonly ThreadUserMessagePart[]; + readonly attachments: readonly CompleteAttachment[]; + readonly metadata: { + readonly unstable_state?: undefined; + readonly unstable_annotations?: undefined; + readonly unstable_data?: undefined; + readonly steps?: undefined; + readonly submittedFeedback?: undefined; + readonly timing?: undefined; + readonly custom: Record<string, unknown>; + }; +}; + +type ThreadUserMessagePart = TextMessagePart | ImageMessagePart | FileMessagePart | DataMessagePart | Unstable_AudioMessagePart; + +type Tool<TArgs extends Record<string, unknown> = Record<string, unknown>, TResult = unknown> = FrontendTool<TArgs, TResult> | BackendTool<TArgs, TResult> | HumanTool<TArgs, TResult> | ProviderTool<TArgs, TResult> | McpTool | ToolWithoutType<TArgs, TResult>; + +type ToolApprovalOption = { + readonly id: string; + readonly kind: ToolApprovalOptionKind | (string & {}); + readonly label?: string; + readonly description?: string; + readonly grants?: readonly string[]; + readonly confirm?: boolean | { + title?: string; + description?: string; + }; +}; + +type ToolApprovalOptionKind = "allow-always" | "allow-once" | "reject-always" | "reject-once"; + +type ToolApprovalResponse = { + readonly approved: boolean; + readonly reason?: string; +} | { + readonly optionId: string; + readonly reason?: string; +} | { + readonly approved: boolean; + readonly optionId: string; + readonly reason?: string; +}; + +type ToolBase<TArgs extends Record<string, unknown> = Record<string, unknown>, TResult = unknown> = { + streamCall?: ToolStreamCallFunction<TArgs, TResult>; + display?: ToolDisplay; +}; + +interface ToolCallArgsReader<TArgs extends Record<string, unknown>> { + get<PathT extends TypePath<TArgs>>(...fieldPath: PathT): Promise<TypeAtPath<TArgs, PathT>>; + streamValues<PathT extends TypePath<TArgs>>(...fieldPath: PathT): AsyncIterableStream<DeepPartial<TypeAtPath<TArgs, PathT>>>; + streamText<PathT extends TypePath<TArgs>>(...fieldPath: PathT): TypeAtPath<TArgs, PathT> extends string & (infer U) ? AsyncIterableStream<U> : never; + forEach<PathT extends TypePath<TArgs>>(...fieldPath: PathT): NonNullable<TypeAtPath<TArgs, PathT>> extends Array<infer U> ? AsyncIterableStream<U> : never; +} + +type ToolCallMessagePart<TArgs = ReadonlyJSONObject, TResult = unknown> = { + readonly type: "tool-call"; + readonly toolCallId: string; + readonly toolName: string; + readonly args: TArgs; + readonly result?: TResult | undefined; + readonly isError?: boolean | undefined; + readonly argsText: string; + readonly artifact?: unknown; + readonly timing?: ToolCallTiming; + readonly mcp?: ToolCallMessagePartMcpMetadata; + readonly providerMetadata?: PartProviderMetadata; + readonly modelContent?: readonly ToolModelContentPart[] | undefined; + readonly interrupt?: { + type: "human"; + payload: unknown; + }; + readonly approval?: { + readonly id: string; + readonly approved?: boolean; + readonly reason?: string; + readonly isAutomatic?: boolean; + readonly options?: readonly ToolApprovalOption[]; + readonly optionId?: string; + readonly resolution?: "cancelled" | "expired"; + }; + readonly parentId?: string; + readonly messages?: readonly ThreadMessage[]; +}; + +type ToolCallMessagePartMcpMetadata = { + readonly app?: McpAppMetadata; +}; + +type ToolCallMessagePartStatus = { + readonly type: "requires-action"; + readonly reason: "interrupt"; +} | MessagePartStatus; + +interface ToolCallReader<TArgs extends Record<string, unknown> = Record<string, unknown>, TResult = unknown> { + args: ToolCallArgsReader<TArgs>; + response: ToolCallResponseReader<TResult>; + result: { + get: () => Promise<TResult>; + }; +} + +interface ToolCallResponseReader<TResult> { + get: () => Promise<ToolResponse<TResult>>; +} + +type ToolCallTiming = { + readonly startedAt: number; + readonly completedAt?: number; +}; + +type ToolDisplay = "inline" | "standalone"; + +type ToolExecuteFunction<TArgs, TResult> = (args: TArgs, context: ToolExecutionContext) => TResult | Promise<TResult>; + +type ToolExecutionContext = { + toolCallId: string; + abortSignal: AbortSignal; + human: (payload: unknown) => Promise<unknown>; +}; + +type ToolExecutionStatus = { + type: "executing"; +} | { + type: "interrupt"; + payload: { + type: "human"; + payload: unknown; + }; +}; + +type ToolModelContentPart = { + readonly type: "text"; + readonly text: string; +} | { + readonly type: "file"; + readonly data: string; + readonly mediaType: string; + readonly filename?: string; +}; + +type ToolModelOutputFunction<TArgs, TResult> = (options: { + toolCallId: string; + input: TArgs; + output: TResult; +}) => readonly ToolModelContentPart[] | Promise<readonly ToolModelContentPart[]>; + +declare class ToolResponse<TResult> { + get [TOOL_RESPONSE_SYMBOL](): boolean; + readonly artifact?: ReadonlyJSONValue; + readonly result: TResult; + readonly isError: boolean; + readonly modelContent?: readonly ToolModelContentPart[]; + readonly messages?: ReadonlyJSONValue; + constructor(options: ToolResponseLike<TResult>); + static [Symbol.hasInstance](obj: unknown): obj is ToolResponse<ReadonlyJSONValue>; + static toResponse(result: any | ToolResponse<any>): ToolResponse<any>; +} + +type ToolResponseLike<TResult> = { + result: TResult; + artifact?: ReadonlyJSONValue | undefined; + isError?: boolean | undefined; + modelContent?: readonly ToolModelContentPart[] | undefined; + messages?: ReadonlyJSONValue | undefined; +}; + +type ToolStreamCallFunction<TArgs extends Record<string, unknown> = Record<string, unknown>, TResult = unknown> = (reader: ToolCallReader<TArgs, TResult>, context: ToolExecutionContext) => void; + +type ToolWithoutType<TArgs extends Record<string, unknown> = Record<string, unknown>, TResult = unknown> = (Omit<FrontendTool<TArgs, TResult>, "type"> | Omit<BackendTool<TArgs, TResult>, "type"> | Omit<HumanTool<TArgs, TResult>, "type"> | Omit<ProviderTool<TArgs, TResult>, "type">) & { + type?: undefined; +}; + +type TupleIndex<T extends readonly any[]> = Exclude<keyof T, keyof any[]>; + +type TypeAtPath<T, P extends readonly any[]> = P extends [ + infer Head, + ...infer Rest +] ? Head extends keyof T ? TypeAtPath<T[Head], Rest> : never : T; + +type TypePath<T> = [ +] | (0 extends 1 & T ? any[] : T extends object ? T extends readonly any[] ? number extends T["length"] ? { + [K in TupleIndex<T>]: [ + AsNumber<K>, + ...TypePath<T[K]> + ]; +}[TupleIndex<T>] : [ + number, + ...TypePath<T[number]> +] : { + [K in ObjectKey<T>]: [ + K, + ...TypePath<T[K]> + ]; +}[ObjectKey<T>] : [ +]); + +type UnionToIntersection<U> = (U extends unknown ? (x: U) => void : never) extends ((x: infer I) => void) ? I : never; + +type Unstable_AudioMessagePart = { + readonly type: "audio"; + readonly audio: { + readonly data: string; + readonly format: "mp3" | "wav"; + }; +}; + +type Unsubscribe = () => void; + +type Unsubscribe$1 = () => void; + +type ValidateClient<K extends keyof ScopeRegistry> = ScopeRegistry[K] extends { + methods: ClientMethods; +} ? "meta" extends keyof ScopeRegistry[K] ? ScopeRegistry[K]["meta"] extends ClientMetaType ? "events" extends keyof ScopeRegistry[K] ? ScopeRegistry[K]["events"] extends ClientEventsType<K> ? ScopeRegistry[K] : ClientError<`ERROR: ${K & string} has invalid events type`> : ScopeRegistry[K] : ClientError<`ERROR: ${K & string} has invalid meta type`> : "events" extends keyof ScopeRegistry[K] ? ScopeRegistry[K]["events"] extends ClientEventsType<K> ? ScopeRegistry[K] : ClientError<`ERROR: ${K & string} has invalid events type`> : ScopeRegistry[K] : ClientError<`ERROR: ${K & string} has invalid methods type`>; + +type VoiceSessionState = { + readonly status: RealtimeVoiceAdapter.Status; + readonly isMuted: boolean; + readonly mode: RealtimeVoiceAdapter.Mode; +}; + +type WildcardPayload = { + [K in keyof ClientEventMap]: { + event: K; + payload: ClientEventMap[K]; + }; +}[Extract<keyof ClientEventMap, string>]; + +declare const createOpenCodeThreadState: (sessionId: string) => OpenCodeThreadState; + +declare global { + interface Window { + SpeechRecognition?: SpeechRecognitionConstructor; + webkitSpeechRecognition?: SpeechRecognitionConstructor; + } +} + +declare global { + interface Window { + __ASSISTANT_UI_DEVTOOLS_HOOK__?: any; + } +} + +declare namespace entry_root_exports { + export { AssistantMessage, Event, FilePart, GlobalSession, Message$2 as Message, MessageWithParts, Model, OpenCodeEventSource, OpenCodeLoadState, OpenCodePartPayload, OpenCodePermissionRequest, OpenCodePermissionResponse, OpenCodeProjectedThreadMessage, OpenCodeQuestionRequest, OpenCodeRunState, OpenCodeRuntime, OpenCodeRuntimeExtras, OpenCodeRuntimeOptions, OpenCodeServerEvent, OpenCodeServerMessage, OpenCodeStateEvent, OpenCodeThreadController, OpenCodeThreadControllerLike, OpenCodeThreadControllerSnapshot, OpenCodeThreadState, OpenCodeThreadStateSelector, OpenCodeUnhandledEvent, OpenCodeUserMessageOptions, OpencodeClient$1 as OpencodeClient, OpencodeClientConfig, Part$1 as Part, PendingUserMessage, PermissionRequest$1 as PermissionRequest, Provider, QuestionAnswer$1 as QuestionAnswer, QuestionRequest$1 as QuestionRequest, ReasoningPart, STREAM_RECONNECTED_EVENT_TYPE, Session$1 as Session, SessionStatus$1 as SessionStatus, SnapshotPart, StepFinishPart, StepStartPart, TextPart, ToolPart, ToolState, UserMessage, createOpenCodeThreadState, createOpencodeClient$1 as createOpencodeClient, projectOpenCodeThreadMessages, reduceOpenCodeThreadState, useOpenCodePermissions, useOpenCodeQuestions, useOpenCodeRuntime, useOpenCodeRuntimeExtras, useOpenCodeSession, useOpenCodeStreamingTiming, useOpenCodeThreadState }; +} + +declare const projectOpenCodeThreadMessages: (state: OpenCodeThreadState, messageTiming?: Record<string, MessageTiming>) => OpenCodeProjectedThreadMessage[]; + +declare const reduceOpenCodeThreadState: (state: OpenCodeThreadState, event: OpenCodeStateEvent) => OpenCodeThreadState; + +declare const useOpenCodePermissions: () => { + pending: OpenCodePermissionRequest[]; + reply: (permissionId: string, response: OpenCodePermissionResponse) => Promise<void>; +}; + +declare const useOpenCodeQuestions: () => OpenCodeQuestionRequest[]; + +declare const useOpenCodeRuntime: (options?: OpenCodeRuntimeOptions) => AssistantRuntime; + +declare const useOpenCodeRuntimeExtras: () => OpenCodeRuntimeExtras; + +declare const useOpenCodeSession: () => import("@opencode-ai/sdk/v2/types").Session | null; + +declare const useOpenCodeStreamingTiming: (state: OpenCodeThreadState, isRunning: boolean) => Record<string, MessageTiming>; + +declare function useOpenCodeThreadState(): OpenCodeThreadState; + +declare function useOpenCodeThreadState<T>(selector: (state: OpenCodeThreadState) => T): T; + +export { entry_root_exports as entry_root }; diff --git a/api-surface/assistant-ui__react-pi.ts b/api-surface/assistant-ui__react-pi.ts new file mode 100644 index 0000000..f9f19f8 --- /dev/null +++ b/api-surface/assistant-ui__react-pi.ts @@ -0,0 +1,2371 @@ +import { createAgentSession } from "@earendil-works/pi-coding-agent"; + +import "@radix-ui/react-primitive"; + +import { StandardSchemaV1 } from "@standard-schema/spec"; + +import "radix-ui"; + +import "react-textarea-autosize"; + +import "zustand"; + +type AddToolResultOptions = { + messageId: string; + toolName: string; + toolCallId: string; + result: ReadonlyJSONValue; + isError: boolean; + artifact?: ReadonlyJSONValue | undefined; + modelContent?: readonly ToolModelContentPart[] | undefined; +}; + +type AncestorsOf<K extends ClientNames, Seen extends ClientNames = never> = K extends Seen ? never : ParentOf<K> extends never ? never : ParentOf<K> | AncestorsOf<ParentOf<K>, Seen | K>; + +type AppendMessage = Omit<ThreadMessage, "id"> & { + parentId: string | null; + sourceId: string | null; + runConfig: RunConfig | undefined; + startRun?: boolean | undefined; + steer?: boolean | undefined; +}; + +type AsNumber<K> = K extends `${infer N extends number}` ? N | K : never; + +type AssistantClient = { + [K in ClientNames]: AssistantClientAccessor<K>; +} & { + subscribe(listener: () => void): Unsubscribe$1; + on<TEvent extends AssistantEventName>(selector: AssistantEventSelector<TEvent>, callback: AssistantEventCallback<TEvent>): Unsubscribe$1; +}; + +type AssistantClientAccessor<K extends ClientNames> = (() => ClientSchemas[K]["methods"]) & (ClientMeta<K> | { + source: "root"; + query: Record<string, never>; +} | { + source: null; + query: null; +}) & { + name: K; +}; + +type AssistantEventCallback<TEvent extends AssistantEventName> = (payload: AssistantEventPayload[TEvent]) => void; + +type AssistantEventName = keyof AssistantEventPayload; + +type AssistantEventPayload = ClientEventMap & { + "*": WildcardPayload; +}; + +type AssistantEventScope<TEvent extends AssistantEventName> = "*" | EventSource<TEvent> | (EventSource<TEvent> extends ClientNames ? AncestorsOf<EventSource<TEvent>> : never); + +type AssistantEventSelector<TEvent extends AssistantEventName> = TEvent | { + scope: AssistantEventScope<TEvent>; + event: TEvent; +}; + +type AssistantRuntime = { + readonly threads: ThreadListRuntime; + readonly thread: ThreadRuntime; + registerModelContextProvider(provider: ModelContextProvider): Unsubscribe; +}; + +type AsyncIterableStream<T> = AsyncIterable<T> & ReadableStream<T>; + +type Attachment = PendingAttachment | CompleteAttachment; + +type AttachmentAdapter = { + accept: string; + add(state: { + file: File; + }): Promise<PendingAttachment> | AsyncGenerator<PendingAttachment, void>; + remove(attachment: Attachment): Promise<void>; + send(attachment: PendingAttachment): Promise<CompleteAttachment>; +}; + +type AttachmentAddErrorEvent = { + readonly reason: AttachmentAddErrorReason; + readonly message: string; + readonly attachmentId?: string; + readonly error?: Error; +}; + +type AttachmentAddErrorReason = "adapter-error" | "no-adapter" | "not-accepted"; + +type AttachmentRuntime<TSource extends AttachmentRuntimeSource = AttachmentRuntimeSource> = { + readonly path: AttachmentRuntimePath & { + attachmentSource: TSource; + }; + readonly source: TSource; + getState(): AttachmentState & { + source: TSource; + }; + remove(): Promise<void>; + subscribe(callback: () => void): Unsubscribe; +}; + +type AttachmentRuntimePath = ((MessageRuntimePath & { + readonly attachmentSource: "edit-composer" | "message"; +}) | (ThreadRuntimePath & { + readonly attachmentSource: "thread-composer"; +})) & { + readonly attachmentSelector: { + readonly type: "index"; + readonly index: number; + } | { + readonly type: "index"; + readonly index: number; + } | { + readonly type: "index"; + readonly index: number; + }; +}; + +type AttachmentRuntimeSource = AttachmentState["source"]; + +type AttachmentState = ThreadComposerAttachmentState | EditComposerAttachmentState | MessageAttachmentState; + +type BackendTool<TArgs extends Record<string, unknown> = Record<string, unknown>, TResult = unknown> = ToolBase<TArgs, TResult> & { + type: "backend"; + description?: undefined; + parameters?: undefined; + disabled?: undefined; + execute?: undefined; + toModelOutput?: undefined; + experimental_onSchemaValidationError?: undefined; + providerOptions?: undefined; +}; + +type BaseAttachment = { + id: string; + type: "image" | "document" | "file" | (string & {}); + name: string; + contentType?: string | undefined; + file?: File; + content?: ThreadUserMessagePart[]; +}; + +type BaseComposerState = { + readonly canCancel: boolean; + readonly canSend: boolean; + readonly isEditing: boolean; + readonly isEmpty: boolean; + readonly text: string; + readonly role: MessageRole; + readonly attachments: readonly Attachment[]; + readonly runConfig: RunConfig; + readonly attachmentAccept: string; + readonly dictation: DictationState | undefined; + readonly quote: QuoteInfo | undefined; + readonly queue: readonly QueueItemState[]; +}; + +type BaseThreadMessage = { + readonly status?: ThreadAssistantMessage["status"]; + readonly metadata: { + readonly unstable_state?: ReadonlyJSONValue; + readonly unstable_annotations?: readonly ReadonlyJSONValue[]; + readonly unstable_data?: readonly ReadonlyJSONValue[]; + readonly steps?: readonly ThreadStep[]; + readonly submittedFeedback?: { + readonly type: "negative" | "positive"; + }; + readonly timing?: MessageTiming; + readonly isOptimistic?: boolean; + readonly custom: Record<string, unknown>; + }; + readonly attachments?: ThreadUserMessage["attachments"]; +}; + +type ChatModelRunOptions = { + readonly messages: readonly ThreadMessage[]; + readonly runConfig: RunConfig; + readonly abortSignal: AbortSignal; + readonly context: ModelContext; + readonly unstable_assistantMessageId?: string | undefined; + readonly unstable_threadId?: string | undefined; + readonly unstable_parentId?: string | null | undefined; + unstable_getMessage(): ThreadMessage; +}; + +type ChatModelRunResult = { + readonly content?: readonly ThreadAssistantMessagePart[] | undefined; + readonly status?: MessageStatus | undefined; + readonly metadata?: { + readonly unstable_state?: ReadonlyJSONValue; + readonly unstable_annotations?: readonly ReadonlyJSONValue[] | undefined; + readonly unstable_data?: readonly ReadonlyJSONValue[] | undefined; + readonly steps?: readonly ThreadStep[] | undefined; + readonly timing?: MessageTiming | undefined; + readonly custom?: Record<string, unknown> | undefined; + }; +}; + +type ClientError<E extends string> = { + methods: Record<E, () => E>; + meta: { + source: ClientNames; + query: Record<E, E>; + }; + events: Record<`${E}.`, E>; +}; + +type ClientEventMap = UnionToIntersection<{ + [K in ClientNames]: ClientEvents<K>; +}[ClientNames]>; + +type ClientEvents<K extends ClientNames> = "events" extends keyof ClientSchemas[K] ? ClientSchemas[K]["events"] extends ClientEventsType<K> ? ClientSchemas[K]["events"] : never : never; + +type ClientEventsType<K extends ClientNames> = Record<`${K}.${string}`, unknown>; + +type ClientMeta<K extends ClientNames> = "meta" extends keyof ClientSchemas[K] ? Pick<ClientSchemas[K]["meta"] extends ClientMetaType ? ClientSchemas[K]["meta"] : never, "query" | "source"> : never; + +type ClientMetaType = { + source: ClientNames; + query: Record<string, unknown>; +}; + +interface ClientMethods { + [key: string | symbol]: (...args: any[]) => any; +} + +type ClientNames = keyof ClientSchemas extends (infer U) ? U : never; + +type ClientSchemas = keyof ScopeRegistry extends never ? { + "ERROR: No clients were defined": ClientError<"ERROR: No clients were defined">; +} : { + [K in keyof ScopeRegistry]: ValidateClient<K>; +}; + +type CompleteAttachment = BaseAttachment & { + status: CompleteAttachmentStatus; + content: ThreadUserMessagePart[]; +}; + +type CompleteAttachmentStatus = { + type: "complete"; +}; + +type ComposerRuntime = { + readonly path: ComposerRuntimePath; + readonly type: "edit" | "thread"; + getState(): ComposerState; + addAttachment(fileOrAttachment: File | CreateAttachment): Promise<void>; + setText(text: string): void; + setRole(role: MessageRole): void; + setRunConfig(runConfig: RunConfig): void; + reset(): Promise<void>; + clearAttachments(): Promise<void>; + send(options?: SendOptions): void; + cancel(): void; + steerQueueItem(queueItemId: string): void; + removeQueueItem(queueItemId: string): void; + subscribe(callback: () => void): Unsubscribe; + getAttachmentByIndex(idx: number): AttachmentRuntime; + startDictation(): void; + stopDictation(): void; + setQuote(quote: QuoteInfo | undefined): void; + unstable_on<E extends ComposerRuntimeEventType>(event: E, callback: ComposerRuntimeEventCallback<E>): Unsubscribe; +}; + +type ComposerRuntimeEventCallback<E extends ComposerRuntimeEventType> = (payload: ComposerRuntimeEventPayload[E]) => void; + +type ComposerRuntimeEventPayload = { + send: Record<string, never>; + attachmentAdd: Record<string, never>; + attachmentAddError: AttachmentAddErrorEvent; +}; + +type ComposerRuntimeEventType = keyof ComposerRuntimeEventPayload; + +type ComposerRuntimePath = (ThreadRuntimePath & { + readonly composerSource: "thread"; +}) | (MessageRuntimePath & { + readonly composerSource: "edit"; +}); + +type ComposerState = ThreadComposerState | EditComposerState; + +type ContentPart = Exclude<ThreadMessageLike["content"], string>[number]; + +type CreateAppendMessage = string | { + parentId?: string | null | undefined; + sourceId?: string | null | undefined; + role?: AppendMessage["role"] | undefined; + content: AppendMessage["content"]; + attachments?: AppendMessage["attachments"] | undefined; + metadata?: AppendMessage["metadata"] | undefined; + createdAt?: Date | undefined; + runConfig?: AppendMessage["runConfig"] | undefined; + startRun?: boolean | undefined; +}; + +type CreateAttachment = { + id?: string; + type?: "image" | "document" | "file" | (string & {}); + name: string; + contentType?: string; + content: ThreadUserMessagePart[]; +}; + +type CreateResumeRunConfig = CreateStartRunConfig & { + stream?: (options: ChatModelRunOptions) => AsyncGenerator<ChatModelRunResult, void, unknown>; +}; + +type CreateStartRunConfig = { + parentId: string | null; + sourceId?: string | null | undefined; + runConfig?: RunConfig | undefined; +}; + +type DataMessagePart<T = any> = { + readonly type: "data"; + readonly name: string; + readonly data: T; +}; + +type DataPrefixedPart = { + readonly type: `data-${string}`; + readonly data: any; +}; + +type DeepPartial<T> = T extends readonly any[] ? readonly DeepPartial<T[number]>[] : T extends { + [key: string]: any; +} ? { + readonly [K in keyof T]?: DeepPartial<T[K]>; +} : T; + +interface DevToolsApiEntry { + api: Partial<AssistantClient>; + logs: EventLog[]; +} + +interface DevToolsHook { + apis: Map<number, DevToolsApiEntry>; + nextId: number; + listeners: Set<(apiId: number) => void>; +} + +declare namespace DictationAdapter { + type Status = { + type: "running" | "starting"; + } | { + type: "ended"; + reason: "cancelled" | "error" | "stopped"; + }; + type Result = { + transcript: string; + isFinal?: boolean; + }; + type Session = { + status: Status; + stop: () => Promise<void>; + cancel: () => void; + onSpeechStart: (callback: () => void) => Unsubscribe; + onSpeechEnd: (callback: (result: Result) => void) => Unsubscribe; + onSpeech: (callback: (result: Result) => void) => Unsubscribe; + }; +} + +type DictationAdapter = { + listen: () => DictationAdapter.Session; + disableInputDuringDictation?: boolean; +}; + +type DictationState = { + readonly status: DictationAdapter.Status; + readonly transcript?: string; + readonly inputDisabled?: boolean; +}; + +type EditComposerAttachmentState = Attachment & { + readonly source: "edit-composer"; +}; + +type EditComposerRuntime = Omit<ComposerRuntime, "getAttachmentByIndex" | "getState"> & { + readonly path: ComposerRuntimePath & { + composerSource: "edit"; + }; + readonly type: "edit"; + getState(): EditComposerState; + beginEdit(): void; + getAttachmentByIndex(idx: number): AttachmentRuntime & { + source: "edit-composer"; + }; +}; + +type EditComposerState = BaseComposerState & { + readonly type: "edit"; + readonly parentId: string | null; + readonly sourceId: string | null; +}; + +interface EventLog { + time: Date; + event: string; + data: unknown; +} + +type EventSource<T extends AssistantEventName> = T extends `${infer Source}.${string}` ? Source : never; + +type ExportedMessageRepository = { + headId?: string | null; + messages: Array<{ + message: ThreadMessage; + parentId: string | null; + runConfig?: RunConfig; + }>; +}; + +declare const ExportedMessageRepository: { + fromArray: (messages: readonly ThreadMessageLike[]) => ExportedMessageRepository; + fromBranchableArray: (items: readonly { + message: ThreadMessageLike; + parentId: string | null; + }[], options?: { + headId?: string | null; + }) => ExportedMessageRepository; +}; + +type ExternalStoreAdapter<T = ThreadMessage> = ExternalStoreAdapterBase<T> & (T extends ThreadMessage ? object : ExternalStoreMessageConverterAdapter<T>); + +type ExternalStoreAdapterBase<T> = { + isDisabled?: boolean | undefined; + isSendDisabled?: boolean | undefined; + isRunning?: boolean | undefined; + isLoading?: boolean | undefined; + messages?: readonly T[]; + messageRepository?: ExportedMessageRepository; + suggestions?: readonly ThreadSuggestion[] | undefined; + state?: ReadonlyJSONValue | undefined; + extras?: unknown; + setMessages?: ((messages: readonly T[]) => void) | undefined; + unstable_onBranchChange?: ((event: ExternalStoreBranchChange) => void) | undefined; + onImport?: ((messages: readonly ThreadMessage[]) => void) | undefined; + onExportExternalState?: (() => any) | undefined; + onLoadExternalState?: ((state: any) => void) | undefined; + onNew: (message: AppendMessage) => Promise<void>; + queue?: ExternalThreadQueueAdapter | undefined; + onEdit?: ((message: AppendMessage) => Promise<void>) | undefined; + onDelete?: ((messageId: string) => Promise<void> | void) | undefined; + onReload?: ((parentId: string | null, config: StartRunConfig) => Promise<void>) | undefined; + onResume?: ((config: ResumeRunConfig) => Promise<void>) | undefined; + onCancel?: (() => Promise<void>) | undefined; + onAddToolResult?: ((options: AddToolResultOptions) => Promise<void> | void) | undefined; + onResumeToolCall?: ((options: { + toolCallId: string; + payload: unknown; + }) => void) | undefined; + onRespondToToolApproval?: ((options: RespondToToolApprovalOptions) => Promise<void> | void) | undefined; + convertMessage?: ExternalStoreMessageConverter<T> | undefined; + adapters?: { + attachments?: AttachmentAdapter | undefined; + speech?: SpeechSynthesisAdapter | undefined; + dictation?: DictationAdapter | undefined; + voice?: RealtimeVoiceAdapter | undefined; + feedback?: FeedbackAdapter | undefined; + threadList?: ExternalStoreThreadListAdapter | undefined; + } | undefined; + unstable_capabilities?: { + copy?: boolean | undefined; + } | undefined; + unstable_enableToolInvocations?: boolean | undefined; + setToolStatuses?: ((statuses: Record<string, ToolExecutionStatus>) => void) | undefined; +}; + +type ExternalStoreBranchChange = { + headId: string | null; + visibleMessageIds: readonly string[]; +}; + +type ExternalStoreMessageConverter<T> = (message: T, idx: number) => ThreadMessageLike; + +type ExternalStoreMessageConverterAdapter<T> = { + convertMessage: ExternalStoreMessageConverter<T>; +}; + +type ExternalStoreSharedOptions = Pick<ExternalStoreAdapter, "isDisabled" | "isSendDisabled" | "suggestions" | "unstable_capabilities">; + +type ExternalStoreThreadData<TState extends "archived" | "regular"> = { + status: TState; + id: string; + remoteId?: string | undefined; + externalId?: string | undefined; + title?: string | undefined; + custom?: Record<string, unknown> | undefined; +}; + +type ExternalStoreThreadListAdapter = { + threadId?: string | undefined; + isLoading?: boolean | undefined; + threads?: readonly ExternalStoreThreadData<"regular">[] | undefined; + archivedThreads?: readonly ExternalStoreThreadData<"archived">[] | undefined; + onSwitchToNewThread?: (() => Promise<void> | void) | undefined; + onSwitchToThread?: ((threadId: string) => Promise<void> | void) | undefined; + onRename?: (threadId: string, newTitle: string) => (Promise<void> | void) | undefined; + onUpdateCustom?: ((threadId: string, custom: Record<string, unknown> | undefined) => Promise<void> | void) | undefined; + onArchive?: ((threadId: string) => Promise<void> | void) | undefined; + onUnarchive?: ((threadId: string) => Promise<void> | void) | undefined; + onDelete?: ((threadId: string) => Promise<void> | void) | undefined; +}; + +type ExternalThreadQueueAdapter = { + items: readonly QueueItemState[]; + enqueue: (message: AppendMessage, options: { + steer: boolean; + }) => void; + steer: (queueItemId: string) => void; + remove: (queueItemId: string) => void; + clear: (reason: "cancel-run" | "edit" | "reload") => void; +}; + +type FeedbackAdapter = { + submit: (feedback: FeedbackAdapterFeedback) => void; +}; + +type FeedbackAdapterFeedback = { + message: ThreadMessage; + type: "negative" | "positive"; +}; + +type FileMessagePart = { + readonly type: "file"; + readonly filename?: string; + readonly data: string; + readonly mimeType: string; + readonly parentId?: string; +}; + +type FrontendTool<TArgs extends Record<string, unknown> = Record<string, unknown>, TResult = unknown> = ToolBase<TArgs, TResult> & { + type: "frontend"; + description?: string | undefined; + parameters: StandardSchemaV1<TArgs> | JSONSchema7; + disabled?: boolean; + execute?: ToolExecuteFunction<TArgs, TResult>; + toModelOutput?: ToolModelOutputFunction<TArgs, TResult>; + experimental_onSchemaValidationError?: OnSchemaValidationErrorFunction<TResult>; + providerOptions?: ProviderOptions; +}; + +type GenerativeUIMessagePart = { + readonly type: "generative-ui"; + readonly spec: GenerativeUISpec; + readonly id?: string; + readonly parentId?: string; +}; + +type GenerativeUINode = string | { + readonly component: string; + readonly props?: Record<string, unknown>; + readonly children?: readonly GenerativeUINode[]; + readonly key?: string; +}; + +type GenerativeUISpec = { + readonly root: GenerativeUINode | readonly GenerativeUINode[]; +}; + +type HumanTool<TArgs extends Record<string, unknown> = Record<string, unknown>, TResult = unknown> = ToolBase<TArgs, TResult> & { + type: "human"; + description?: string | undefined; + parameters: StandardSchemaV1<TArgs> | JSONSchema7; + disabled?: boolean; + display?: "standalone"; + execute?: undefined; + toModelOutput?: undefined; + experimental_onSchemaValidationError?: undefined; + providerOptions?: ProviderOptions; +}; + +type ImageMessagePart = { + readonly type: "image"; + readonly image: string; + readonly filename?: string; +}; + +interface JSONSchema7 { + $id?: string | undefined; + $ref?: string | undefined; + $schema?: JSONSchema7Version | undefined; + $comment?: string | undefined; + $defs?: { + [key: string]: JSONSchema7Definition; + } | undefined; + type?: JSONSchema7TypeName | JSONSchema7TypeName[] | undefined; + enum?: JSONSchema7Type[] | undefined; + const?: JSONSchema7Type | undefined; + multipleOf?: number | undefined; + maximum?: number | undefined; + exclusiveMaximum?: number | undefined; + minimum?: number | undefined; + exclusiveMinimum?: number | undefined; + maxLength?: number | undefined; + minLength?: number | undefined; + pattern?: string | undefined; + items?: JSONSchema7Definition | JSONSchema7Definition[] | undefined; + additionalItems?: JSONSchema7Definition | undefined; + maxItems?: number | undefined; + minItems?: number | undefined; + uniqueItems?: boolean | undefined; + contains?: JSONSchema7Definition | undefined; + maxProperties?: number | undefined; + minProperties?: number | undefined; + required?: string[] | undefined; + properties?: { + [key: string]: JSONSchema7Definition; + } | undefined; + patternProperties?: { + [key: string]: JSONSchema7Definition; + } | undefined; + additionalProperties?: JSONSchema7Definition | undefined; + dependencies?: { + [key: string]: JSONSchema7Definition | string[]; + } | undefined; + propertyNames?: JSONSchema7Definition | undefined; + if?: JSONSchema7Definition | undefined; + then?: JSONSchema7Definition | undefined; + else?: JSONSchema7Definition | undefined; + allOf?: JSONSchema7Definition[] | undefined; + anyOf?: JSONSchema7Definition[] | undefined; + oneOf?: JSONSchema7Definition[] | undefined; + not?: JSONSchema7Definition | undefined; + format?: string | undefined; + contentMediaType?: string | undefined; + contentEncoding?: string | undefined; + definitions?: { + [key: string]: JSONSchema7Definition; + } | undefined; + title?: string | undefined; + description?: string | undefined; + default?: JSONSchema7Type | undefined; + readOnly?: boolean | undefined; + writeOnly?: boolean | undefined; + examples?: JSONSchema7Type | undefined; +} + +interface JSONSchema7Array extends Array<JSONSchema7Type> { +} + +type JSONSchema7Definition = JSONSchema7 | boolean; + +interface JSONSchema7Object { + [key: string]: JSONSchema7Type; +} + +type JSONSchema7Type = string | number | boolean | JSONSchema7Object | JSONSchema7Array | null; + +type JSONSchema7TypeName = "array" | "boolean" | "integer" | "null" | "number" | "object" | "string"; + +type JSONSchema7Version = string; + +type LanguageModelConfig = { + apiKey?: string; + baseUrl?: string; + modelName?: string; + reasoningEffort?: string; +}; + +type LanguageModelV1CallSettings = { + maxTokens?: number; + temperature?: number; + topP?: number; + presencePenalty?: number; + frequencyPenalty?: number; + seed?: number; + headers?: Record<string, string | undefined>; +}; + +type McpAppMetadata = { + readonly resourceUri: string; + readonly mimeType?: string; + readonly visibility?: readonly ("app" | "model")[]; +}; + +type McpServerConfig = { + type: "http" | "sse"; + url: string; + headers?: Record<string, string>; + redirect?: "error" | "follow"; + connectionTimeout?: number | undefined; +} | { + type: "stdio"; + command: string; + args?: readonly string[]; + env?: Record<string, string>; + cwd?: string; + connectionTimeout?: number | undefined; +}; + +type McpTool = ToolBase<Record<string, unknown>, unknown> & { + type: "mcp"; + server: McpServerConfig; + description?: undefined; + parameters?: undefined; + disabled?: boolean; + execute?: undefined; + toModelOutput?: undefined; + experimental_onSchemaValidationError?: undefined; + providerOptions?: undefined; +}; + +type MessageAttachmentState = CompleteAttachment & { + readonly source: "message"; +}; + +type MessageCommonProps = { + readonly id: string; + readonly createdAt: Date; +}; + +type MessagePartRuntime = { + addToolResult(result: any | ToolResponse<any>): void; + resumeToolCall(payload: unknown): void; + respondToToolApproval(response: ToolApprovalResponse): void; + readonly path: MessagePartRuntimePath; + getState(): MessagePartState; + subscribe(callback: () => void): Unsubscribe; +}; + +type MessagePartRuntimePath = MessageRuntimePath & { + readonly messagePartSelector: { + readonly type: "index"; + readonly index: number; + } | { + readonly type: "toolCallId"; + readonly toolCallId: string; + }; +}; + +type MessagePartState = (ThreadUserMessagePart | ThreadAssistantMessagePart) & { + readonly status: MessagePartStatus | ToolCallMessagePartStatus; +}; + +type MessagePartStatus = { + readonly type: "running"; +} | { + readonly type: "complete"; +} | { + readonly type: "incomplete"; + readonly reason: "cancelled" | "content-filter" | "error" | "length" | "other"; + readonly error?: unknown; +}; + +type MessageRole = ThreadMessage["role"]; + +type MessageRuntime = { + readonly path: MessageRuntimePath; + readonly composer: EditComposerRuntime; + getState(): MessageState; + delete(): void | Promise<void>; + reload(config?: ReloadConfig): void; + speak(): void; + stopSpeaking(): void; + submitFeedback(_param0: { + type: "positive" | "negative"; + }): void; + switchToBranch(_param1: { + position?: "previous" | "next" | undefined; + branchId?: string | undefined; + }): void; + unstable_getCopyText(): string; + subscribe(callback: () => void): Unsubscribe; + getMessagePartByIndex(idx: number): MessagePartRuntime; + getMessagePartByToolCallId(toolCallId: string): MessagePartRuntime; + getAttachmentByIndex(idx: number): AttachmentRuntime & { + source: "message"; + }; +}; + +type MessageRuntimePath = ThreadRuntimePath & { + readonly messageSelector: { + readonly type: "messageId"; + readonly messageId: string; + } | { + readonly type: "index"; + readonly index: number; + }; +}; + +type MessageState = ThreadMessage & { + readonly parentId: string | null; + readonly index: number; + readonly isLast: boolean; + readonly branchNumber: number; + readonly branchCount: number; + readonly speech: SpeechState | undefined; +}; + +type MessageStatus = { + readonly type: "running"; +} | { + readonly type: "requires-action"; + readonly reason: "interrupt" | "tool-calls"; +} | { + readonly type: "complete"; + readonly reason: "stop" | "unknown"; +} | { + readonly type: "incomplete"; + readonly reason: "cancelled" | "content-filter" | "error" | "length" | "other" | "tool-calls"; + readonly error?: ReadonlyJSONValue; +}; + +type MessageTiming = { + readonly streamStartTime: number; + readonly firstTokenTime?: number; + readonly totalStreamTime?: number; + readonly tokenCount?: number; + readonly tokensPerSecond?: number; + readonly totalChunks: number; + readonly toolCallCount: number; +}; + +type ModelContext = { + priority?: number | undefined; + system?: string | undefined; + tools?: Record<string, Tool<any, any>> | undefined; + callSettings?: LanguageModelV1CallSettings | undefined; + config?: LanguageModelConfig | undefined; + unstable_composerMetadata?: Record<string, unknown> | undefined; +}; + +type ModelContextProvider = { + getModelContext: () => ModelContext; + subscribe?: (callback: () => void) => Unsubscribe; +}; + +type ObjectKey<T> = keyof T & (string | number); + +type OnSchemaValidationErrorFunction<TResult> = ToolExecuteFunction<unknown, TResult>; + +type ParentOf<K extends ClientNames> = AssistantClientAccessor<K> extends { + source: infer S; +} ? S extends ClientNames ? S : never : never; + +type PartProviderMetadata = { + readonly [providerName: string]: ReadonlyJSONObject; +}; + +type PendingAttachment = BaseAttachment & { + status: PendingAttachmentStatus; + file: File; +}; + +type PendingAttachmentStatus = { + type: "running"; + reason: "uploading"; + progress: number; +} | { + type: "requires-action"; + reason: "composer-send"; +} | { + type: "incomplete"; + reason: "error" | "upload-paused"; + message?: string; +}; + +type PiAgentMessage = PiKnownAgentMessage | PiUnknownAgentMessage; + +type PiAnyClientEvent = PiClientEvent | (PiUnknownClientEventBody & PiClientEventEnvelope); + +type PiAssistantContent = PiTextContent | PiThinkingContent | PiToolCall; + +interface PiAssistantMessage { + role: "assistant"; + content: PiAssistantContent[]; + api: string; + provider: string; + model: string; + responseModel?: string; + responseId?: string; + usage: PiUsage; + stopReason: PiStopReason; + errorMessage?: string; + timestamp: number; +} + +type PiAssistantMessageDelta = { + type: "start"; + partial: PiAssistantMessage; +} | { + type: "text_start"; + contentIndex: number; + partial: PiAssistantMessage; +} | { + type: "text_delta"; + contentIndex: number; + delta: string; + partial: PiAssistantMessage; +} | { + type: "text_end"; + contentIndex: number; + content: string; + partial: PiAssistantMessage; +} | { + type: "thinking_start"; + contentIndex: number; + partial: PiAssistantMessage; +} | { + type: "thinking_delta"; + contentIndex: number; + delta: string; + partial: PiAssistantMessage; +} | { + type: "thinking_end"; + contentIndex: number; + content: string; + partial: PiAssistantMessage; +} | { + type: "toolcall_start"; + contentIndex: number; + partial: PiAssistantMessage; +} | { + type: "toolcall_delta"; + contentIndex: number; + delta: string; + partial: PiAssistantMessage; +} | { + type: "toolcall_end"; + contentIndex: number; + toolCall: PiToolCall; + partial: PiAssistantMessage; +} | { + type: "done"; + reason: "length" | "stop" | "toolUse"; + message: PiAssistantMessage; +} | { + type: "error"; + reason: "aborted" | "error"; + error: PiAssistantMessage; +}; + +interface PiBashExecutionMessage { + role: "bashExecution"; + command: string; + output: string; + exitCode: number | undefined; + cancelled: boolean; + truncated: boolean; + fullOutputPath?: string; + timestamp: number; + excludeFromContext?: boolean; +} + +interface PiBranchSummaryMessage { + role: "branchSummary"; + summary: string; + fromId: string; + timestamp: number; +} + +interface PiClient { + listThreads(input?: { + workspacePath?: string; + includeArchived?: boolean; + }): Promise<PiThreadMetadata[]>; + createThread(input?: { + workspacePath?: string; + title?: string; + initialMessage?: PiSendMessageInput; + }): Promise<PiThreadSnapshot>; + getThread(threadId: string): Promise<PiThreadSnapshot>; + sendMessage(threadId: string, input: PiSendMessageInput): Promise<void>; + cancelRun(threadId: string): Promise<void>; + clearQueue(threadId: string): Promise<{ + steering: string[]; + followUp: string[]; + }>; + getAvailableModels(input?: { + workspacePath?: string; + }): Promise<PiModelInfo[]>; + setModel(threadId: string, input: { + provider: string; + modelId: string; + }): Promise<void>; + setThinkingLevel(threadId: string, level: PiThinkingLevel): Promise<void>; + renameThread(threadId: string, title: string): Promise<void>; + archiveThread(threadId: string): Promise<void>; + unarchiveThread(threadId: string): Promise<void>; + deleteThread(threadId: string): Promise<void>; + respondToHostUiRequest(threadId: string, response: PiHostUiResponse): Promise<void>; + subscribe(threadId: string, listener: (event: PiClientEvent) => void, options?: { + includeSnapshot?: boolean; + }): () => void; +} + +type PiClientEvent = PiClientEventBody & PiClientEventEnvelope; + +type PiClientEventBody = { + type: "snapshot"; + snapshot: PiThreadSnapshot; +} | { + type: "agent_start"; +} | { + type: "agent_end"; + willRetry?: boolean; +} | { + type: "turn_start"; + turnIndex: number; +} | { + type: "turn_end"; + turnIndex: number; +} | { + type: "message_start"; + message: PiAgentMessage; +} | { + type: "message_update"; + message: PiAgentMessage; + assistantMessageEvent: PiAssistantMessageDelta; +} | { + type: "message_end"; + message: PiAgentMessage; +} | { + type: "tool_execution_start"; + toolCallId: string; + toolName: string; + args: unknown; +} | { + type: "tool_execution_update"; + toolCallId: string; + toolName?: string; + partialResult: unknown; +} | { + type: "tool_execution_end"; + toolCallId: string; + result: unknown; + isError: boolean; +} | { + type: "queue_update"; + steering: readonly string[]; + followUp: readonly string[]; +} | { + type: "compaction_start"; + reason: "manual" | "overflow" | "threshold"; +} | { + type: "compaction_end"; + aborted: boolean; + willRetry: boolean; +} | { + type: "auto_retry_start"; + attempt: number; + delayMs: number; +} | { + type: "auto_retry_end"; + success: boolean; +} | { + type: "session_info_changed"; + name?: string; +} | { + type: "thinking_level_changed"; + level: string; +} | { + type: "context_usage"; + contextUsage: PiContextUsage; +} | { + type: "extension_ui_request"; + request: PiHostUiRequest; +} | { + type: "extension_ui_resolved"; + requestId: string; +} | { + type: "error"; + error: string; +}; + +type PiClientEventEnvelope = { + threadId: string; + seq: number; +}; + +interface PiCompactionSummaryMessage { + role: "compactionSummary"; + summary: string; + tokensBefore: number; + timestamp: number; +} + +interface PiContextUsage { + tokens: number | null; + contextWindow: number; + percent: number | null; +} + +interface PiCustomMessage { + role: "custom"; + customType: string; + content: string | PiUserContent[]; + display: boolean; + details?: unknown; + timestamp: number; +} + +interface PiEventStreamOptions { + url: string; + onEvent: (event: PiAnyClientEvent) => void; + onError?: (error: unknown) => void; + fetchImpl?: typeof fetch; + headers?: Record<string, string>; + reconnectDelay?: () => Promise<void>; +} + +type PiHostUiRequest = { + id: string; + kind: "confirm"; + title: string; + message: string; + toolCallId?: string; + timeoutMs?: number; +} | { + id: string; + kind: "select"; + title: string; + options: readonly string[]; + toolCallId?: string; + timeoutMs?: number; +} | { + id: string; + kind: "input"; + title: string; + placeholder?: string; + toolCallId?: string; + timeoutMs?: number; +} | { + id: string; + kind: "editor"; + title: string; + prefill?: string; + toolCallId?: string; + timeoutMs?: number; +}; + +type PiHostUiRequestKind = "confirm" | "editor" | "input" | "select"; + +type PiHostUiResponse = { + requestId: string; + confirmed: boolean; +} | { + requestId: string; + value: string; +} | { + requestId: string; + dismissed: true; +}; + +interface PiHttpClientOptions { + baseUrl?: string; + fetchImpl?: typeof fetch; + headers?: Record<string, string>; + onStreamError?: (error: unknown) => void; + reconnectDelay?: () => Promise<void>; + streamCloseDelayMs?: number; +} + +interface PiImageContent { + type: "image"; + data: string; + mimeType: string; +} + +type PiInputAttachment = PiImageContent; + +type PiInterruptAnswer = string | { + value?: string | null; + dismissed?: boolean; +} | null | undefined; + +type PiKnownAgentMessage = PiUserMessage | PiAssistantMessage | PiToolResultMessage | PiBashExecutionMessage | PiCustomMessage | PiBranchSummaryMessage | PiCompactionSummaryMessage; + +type PiLoadState = "loaded" | "loading" | "pending"; + +type PiModelInfo = { + provider: string; + modelId: string; + name?: string; + supportsThinking?: boolean; + availableThinkingLevels?: readonly PiThinkingLevel[]; +}; + +type PiNodeClientOptions = PiThreadSupervisorOptions; + +type PiNotificationScheduler = (flush: () => void) => void; + +interface PiProjectionInput { + messages: readonly PiAgentMessage[]; + toolExecutions: PiThreadState["toolExecutions"]; + runStatus: PiThreadState["runStatus"]; + hostUiRequests: readonly PiHostUiRequest[]; +} + +type PiQueueMode = "followUp" | "steer"; + +type PiQueuedMessage = { + id: string; + mode: "followUp" | "steer"; + content: string; +}; + +type PiRunStatus = "failed" | "idle" | "running"; + +interface PiRuntimeExtras { + state: PiThreadState; + metadata: PiThreadMetadata; + status: PiThreadStatus; + readiness: PiRuntimeReadiness | undefined; + contextUsage: PiContextUsage | undefined; + hostUiRequests: readonly PiHostUiRequest[]; + allHostUiRequests: readonly PiHostUiRequest[]; + queue: PiThreadState["queue"]; + compaction: PiThreadState["compaction"]; + retry: PiThreadState["retry"]; + lastError: string | undefined; + cancel: () => Promise<void>; + refresh: () => Promise<void>; + clearQueue: () => Promise<{ + steering: string[]; + followUp: string[]; + }>; + setModel: (input: { + provider: string; + modelId: string; + }) => Promise<void>; + setThinkingLevel: (level: PiThinkingLevel) => Promise<void>; + respondToHostUiRequest: (response: PiHostUiResponse) => Promise<void>; + respondToToolApproval: (id: string, approved: boolean) => Promise<void>; + resumeToolCall: (toolCallId: string, payload: PiInterruptAnswer) => Promise<void>; +} + +type PiRuntimeOptions = ExternalStoreSharedOptions & { + client: PiClient; + workspacePath?: string; + includeArchived?: boolean; + initialThreadId?: string; + threadId?: string; + onThreadIdChange?: ((threadId: string | undefined) => void) | undefined; + onError?: (error: unknown) => void; + adapters?: ExternalStoreAdapter<ThreadMessageLike>["adapters"]; +}; + +type PiRuntimeReadiness = { + state: "ready"; + selection: { + provider: string; + modelId: string; + }; + source: "env" | "pi-default" | "session"; +} | { + state: "missing-model"; + message: string; +} | { + state: "missing-credentials"; + provider?: string; + message: string; +} | { + state: "unavailable-model"; + selection: { + provider: string; + modelId: string; + }; + message: string; +}; + +type PiSendMessageInput = { + content: string; + attachments?: PiInputAttachment[]; + streamingBehavior?: "followUp" | "steer"; +}; + +type PiSendOptions = { + streamingBehavior?: "followUp" | "steer"; +}; + +type PiSessionModel = NonNullable<Parameters<typeof createAgentSession>[0]>["model"]; + +type PiStopReason = "aborted" | "error" | "length" | "stop" | "toolUse"; + +interface PiTextContent { + type: "text"; + text: string; + textSignature?: string; +} + +interface PiThinkingContent { + type: "thinking"; + thinking: string; + thinkingSignature?: string; + redacted?: boolean; +} + +type PiThinkingLevel = "high" | "low" | "medium" | "minimal" | "off" | "xhigh"; + +declare class PiThreadController implements PiThreadControllerLike { + private readonly client; + private readonly threadId; + private readonly options; + private state; + private projectedMessages; + private messageRepository; + private version; + private readonly allListeners; + private readonly metadataListeners; + private readonly messageListeners; + private connectionRetainers; + private readonly optimisticUserMessages; + private unsubscribeFromEvents; + private disconnectTimer; + private loadPromise; + private messageFlushScheduled; + private readonly localSnapshotSeq; + constructor(client: PiClient, threadId: string, options?: { + scheduleNotify?: PiNotificationScheduler; + }); + getState(): PiThreadState; + getProjectedMessages(): readonly ThreadMessageLike[]; + getMessageRepository(): ExportedMessageRepository; + getVersion(): number; + connect(): () => void; + subscribe(listener: () => void): () => void; + subscribeMetadata(listener: () => void): () => void; + subscribeMessages(listener: () => void): () => void; + dispose(): void; + private ensureEventSubscription; + private hasConsumers; + private maybeDisconnectFromEvents; + private clearDisconnectTimer; + load(force?: boolean): Promise<void>; + refresh(): Promise<void>; + private refreshInBackground; + sendMessage(message: AppendMessage, options?: PiSendOptions): Promise<void>; + private sendQueued; + clearQueue(): Promise<{ + steering: string[]; + followUp: string[]; + }>; + cancel(): Promise<void>; + setModel(input: { + provider: string; + modelId: string; + }): Promise<void>; + setThinkingLevel(level: PiThinkingLevel): Promise<void>; + respondToToolApproval(approvalId: string, approved: boolean): Promise<void>; + resumeToolCall(toolCallId: string, payload: unknown): Promise<void>; + respondToHostUiRequest(response: PiHostUiResponse): Promise<void>; + private respond; + private applySnapshot; + private dispatch; + private setState; + private projectedInputMessages; + private reconcileOptimisticUserMessages; + private projectMessages; + private recomputeProjectedMessagesAndNotify; + private scheduleProjectedMessageFlush; + private bumpVersion; + private notifyMetadataListeners; + private notifyMessageListeners; +} + +interface PiThreadControllerLike { + getState(): PiThreadState; + getProjectedMessages(): readonly ThreadMessageLike[]; + getMessageRepository(): ExportedMessageRepository; + getVersion(): number; + connect(): () => void; + subscribe(listener: () => void): () => void; + subscribeMetadata(listener: () => void): () => void; + subscribeMessages(listener: () => void): () => void; + load(force?: boolean): Promise<void>; + refresh(): Promise<void>; + sendMessage(message: AppendMessage, options?: PiSendOptions): Promise<void>; + cancel(): Promise<void>; + clearQueue(): Promise<{ + steering: string[]; + followUp: string[]; + }>; + setModel(input: { + provider: string; + modelId: string; + }): Promise<void>; + setThinkingLevel(level: PiThinkingLevel): Promise<void>; + respondToToolApproval(approvalId: string, approved: boolean): Promise<void>; + resumeToolCall(toolCallId: string, payload: unknown): Promise<void>; + respondToHostUiRequest(response: PiHostUiResponse): Promise<void>; + dispose(): void; +} + +type PiThreadMetadata = { + id: string; + title?: string; + workspacePath?: string; + archived?: boolean; + status: PiThreadStatus; + runningRunId?: string; + queuedMessages?: readonly PiQueuedMessage[]; + config?: { + provider?: string; + modelId?: string; + thinkingLevel?: PiThinkingLevel | string; + }; + contextUsage?: PiContextUsage; + sessionFile?: string; + parentSessionPath?: string; + messageCount?: number; + createdAt?: string; + updatedAt?: string; +}; + +type PiThreadSnapshot = { + metadata: PiThreadMetadata; + messages: PiTranscriptMessage[]; + hostUiRequests?: readonly PiHostUiRequest[]; + readiness?: PiRuntimeReadiness; + lastError?: string; +}; + +interface PiThreadState { + threadId: string; + metadata: PiThreadMetadata; + messages: readonly PiAgentMessage[]; + streamingMessageIndex: number | undefined; + toolExecutions: Readonly<Record<string, PiToolExecutionState>>; + runStatus: PiRunStatus; + queue: { + steering: readonly string[]; + followUp: readonly string[]; + }; + contextUsage: PiContextUsage | undefined; + compaction: { + active: boolean; + reason?: "manual" | "overflow" | "threshold"; + }; + retry: { + active: boolean; + attempt: number; + }; + hostUiRequests: readonly PiHostUiRequest[]; + readiness: PiRuntimeReadiness | undefined; + lastError: string | undefined; + loadState: PiLoadState; + lastSeq: number; +} + +type PiThreadStatus = "failed" | "idle" | "running"; + +declare class PiThreadSupervisor { + private readonly records; + private readonly pendingOpens; + private readonly recordsBySessionFile; + private readonly workspacePath; + private readonly agentDir; + private readonly model; + private readonly modelRegistry; + private readonly archivedSessionFiles; + private readonly catalogCache; + private readonly catalogInfoByThreadId; + constructor(options?: PiThreadSupervisorOptions); + listThreads(input?: { + workspacePath?: string; + includeArchived?: boolean; + }): Promise<PiThreadMetadata[]>; + createThread(input?: { + workspacePath?: string; + title?: string; + initialMessage?: PiSendMessageInput; + }): Promise<PiThreadSnapshot>; + getThread(threadId: string): Promise<PiThreadSnapshot>; + sendMessage(threadId: string, input: PiSendMessageInput): Promise<void>; + cancelRun(threadId: string): Promise<void>; + clearQueue(threadId: string): Promise<{ + steering: string[]; + followUp: string[]; + }>; + getAvailableModels(): Promise<PiModelInfo[]>; + setModel(threadId: string, input: { + provider: string; + modelId: string; + }): Promise<void>; + setThinkingLevel(threadId: string, level: PiThinkingLevel): Promise<void>; + renameThread(threadId: string, title: string): Promise<void>; + archiveThread(threadId: string): Promise<void>; + unarchiveThread(threadId: string): Promise<void>; + deleteThread(threadId: string): Promise<void>; + respondToHostUiRequest(threadId: string, response: PiHostUiResponse): Promise<void>; + subscribe(threadId: string, listener: (event: PiClientEvent) => void, options?: { + includeSnapshot?: boolean; + }): () => void; + dispose(): Promise<void>; + private openSession; + private ensureOpen; + private openCold; + private listSessionInfos; + private invalidateCatalog; + private findSessionInfo; + private rememberSessionInfos; + private send; + private onSessionEvent; + private emitContextUsage; + private emit; + private liveStatusFor; + private runStatus; + private readinessOf; + private queuedMessagesOf; + private metadataOf; + private snapshotOf; + private snapshotFromSessionFile; +} + +interface PiThreadSupervisorOptions { + workspacePath?: string; + agentDir?: string; + model?: PiSessionModel; +} + +interface PiToolCall { + type: "toolCall"; + id: string; + name: string; + arguments: Record<string, unknown>; + thoughtSignature?: string; +} + +interface PiToolExecutionState { + toolCallId: string; + toolName?: string; + args?: unknown; + partialResult?: unknown; + status: "complete" | "error" | "running"; +} + +type PiToolResultContent = PiTextContent | PiImageContent; + +interface PiToolResultMessage { + role: "toolResult"; + toolCallId: string; + toolName: string; + content: PiToolResultContent[]; + details?: unknown; + isError: boolean; + timestamp: number; +} + +type PiTranscriptMessage = PiAgentMessage; + +interface PiUnknownAgentMessage { + role: string; + timestamp?: number; + [key: string]: unknown; +} + +type PiUnknownClientEventBody = { + type: string; + [key: string]: unknown; +}; + +declare class PiUnsupportedHostUiError extends Error { + readonly method: string; + constructor(method: string); +} + +interface PiUsage { + input: number; + output: number; + cacheRead: number; + cacheWrite: number; + totalTokens: number; + cost: { + input: number; + output: number; + cacheRead: number; + cacheWrite: number; + total: number; + }; +} + +type PiUserContent = PiTextContent | PiImageContent; + +interface PiUserMessage { + role: "user"; + content: string | PiUserContent[]; + timestamp: number; +} + +type ProviderOptions = Record<string, Record<string, unknown>>; + +type ProviderTool<TArgs extends Record<string, unknown> = Record<string, unknown>, TResult = unknown> = ToolBase<TArgs, TResult> & { + type: "provider"; + providerId: `${string}.${string}`; + parameters?: StandardSchemaV1<TArgs> | JSONSchema7 | undefined; + args: Record<string, unknown>; + supportsDeferredResults?: boolean; + description?: undefined; + disabled?: boolean; + execute?: undefined; + toModelOutput?: undefined; + experimental_onSchemaValidationError?: undefined; + providerOptions?: ProviderOptions; +}; + +type QueueItemState = { + readonly id: string; + readonly prompt: string; +}; + +type QuoteInfo = { + readonly text: string; + readonly messageId: string; +}; + +type ReadonlyJSONArray = readonly ReadonlyJSONValue[]; + +type ReadonlyJSONObject = { + readonly [key: string]: ReadonlyJSONValue; +}; + +type ReadonlyJSONValue = null | string | number | boolean | ReadonlyJSONObject | ReadonlyJSONArray; + +declare namespace RealtimeVoiceAdapter { + type Status = { + type: "running" | "starting"; + } | { + type: "ended"; + reason: "cancelled" | "error" | "finished"; + error?: unknown; + }; + type Mode = "listening" | "speaking"; + type TranscriptItem = { + role: "assistant" | "user"; + text: string; + isFinal?: boolean; + }; + type Session = { + status: Status; + isMuted: boolean; + disconnect: () => void; + mute: () => void; + unmute: () => void; + onStatusChange: (callback: (status: Status) => void) => Unsubscribe; + onTranscript: (callback: (transcript: TranscriptItem) => void) => Unsubscribe; + onModeChange: (callback: (mode: Mode) => void) => Unsubscribe; + onVolumeChange: (callback: (volume: number) => void) => Unsubscribe; + }; +} + +type RealtimeVoiceAdapter = { + connect: (options: { + abortSignal?: AbortSignal; + }) => RealtimeVoiceAdapter.Session; +}; + +type ReasoningMessagePart = { + readonly type: "reasoning"; + readonly text: string; + readonly providerMetadata?: PartProviderMetadata; + readonly parentId?: string; +}; + +type ReloadConfig = { + runConfig?: RunConfig; +}; + +type RespondToToolApprovalOptions = { + approvalId: string; + approved: boolean; + optionId?: string; + reason?: string; +}; + +type ResumeRunConfig = StartRunConfig & { + stream?: (options: ChatModelRunOptions) => AsyncGenerator<ChatModelRunResult, void, unknown>; +}; + +type RunConfig = { + readonly custom?: Record<string, unknown>; +}; + +type RuntimeCapabilities = { + readonly switchToBranch: boolean; + readonly switchBranchDuringRun: boolean; + readonly edit: boolean; + readonly reload: boolean; + readonly delete: boolean; + readonly cancel: boolean; + readonly unstable_copy: boolean; + readonly speech: boolean; + readonly dictation: boolean; + readonly voice: boolean; + readonly attachments: boolean; + readonly feedback: boolean; + readonly queue: boolean; +}; + +interface ScopeRegistry { + [key: string]: { methods: any; meta?: any; events?: any }; +} + +type SendOptions = { + startRun?: boolean; + steer?: boolean; +}; + +type SharedStream = { + listeners: Set<(event: PiClientEvent) => void>; + close: () => void; + closeTimer: ReturnType<typeof setTimeout> | undefined; +}; + +type SourceMessagePart = { + readonly type: "source"; + readonly sourceType: "url"; + readonly id: string; + readonly url: string; + readonly title?: string; + readonly providerMetadata?: SourceProviderMetadata; + readonly parentId?: string; +} | { + readonly type: "source"; + readonly sourceType: "document"; + readonly id: string; + readonly url?: undefined; + readonly title: string; + readonly mediaType: string; + readonly filename?: string; + readonly providerMetadata?: SourceProviderMetadata; + readonly parentId?: string; +}; + +type SourceProviderMetadata = PartProviderMetadata; + +interface SpeechRecognitionConstructor { + new (): SpeechRecognitionInstance; +} + +interface SpeechRecognitionInstance extends EventTarget { + lang: string; + continuous: boolean; + interimResults: boolean; + start(): void; + stop(): void; + abort(): void; +} + +type SpeechState = { + readonly messageId: string; + readonly status: SpeechSynthesisAdapter.Status; +}; + +declare namespace SpeechSynthesisAdapter { + type Status = { + type: "running" | "starting"; + } | { + type: "ended"; + reason: "cancelled" | "error" | "finished"; + error?: unknown; + }; + type Utterance = { + status: Status; + cancel: () => void; + subscribe: (callback: () => void) => Unsubscribe; + }; +} + +type SpeechSynthesisAdapter = { + speak: (text: string) => SpeechSynthesisAdapter.Utterance; +}; + +interface SplitHostUiRequests { + toolAssociated: Map<string, PiHostUiRequest>; + freeStanding: PiHostUiRequest[]; +} + +interface SseFrame { + event?: string; + data: string; + id?: string; +} + +type StartRunConfig = { + parentId: string | null; + sourceId: string | null; + runConfig: RunConfig; +}; + +declare const TOOL_RESPONSE_SYMBOL: unique symbol; + +type TextMessagePart = { + readonly type: "text"; + readonly text: string; + readonly providerMetadata?: PartProviderMetadata; + readonly parentId?: string; +}; + +type ThreadAssistantMessage = MessageCommonProps & { + readonly role: "assistant"; + readonly content: readonly ThreadAssistantMessagePart[]; + readonly status: MessageStatus; + readonly metadata: { + readonly unstable_state: ReadonlyJSONValue; + readonly unstable_annotations: readonly ReadonlyJSONValue[]; + readonly unstable_data: readonly ReadonlyJSONValue[]; + readonly steps: readonly ThreadStep[]; + readonly submittedFeedback?: { + readonly type: "negative" | "positive"; + }; + readonly timing?: MessageTiming; + readonly isOptimistic?: boolean; + readonly custom: Record<string, unknown>; + }; +}; + +type ThreadAssistantMessagePart = TextMessagePart | ReasoningMessagePart | ToolCallMessagePart | SourceMessagePart | FileMessagePart | ImageMessagePart | DataMessagePart | GenerativeUIMessagePart; + +type ThreadComposerAttachmentState = Attachment & { + readonly source: "thread-composer"; +}; + +type ThreadComposerRuntime = Omit<ComposerRuntime, "getAttachmentByIndex" | "getState"> & { + readonly path: ComposerRuntimePath & { + composerSource: "thread"; + }; + readonly type: "thread"; + getState(): ThreadComposerState; + getAttachmentByIndex(idx: number): AttachmentRuntime & { + source: "thread-composer"; + }; +}; + +type ThreadComposerState = BaseComposerState & { + readonly type: "thread"; +}; + +type ThreadListItemEventCallback<E extends ThreadListItemEventType> = (payload: ThreadListItemEventPayload[E]) => void; + +type ThreadListItemEventPayload = { + switchedTo: Record<string, never>; + switchedAway: Record<string, never>; +}; + +type ThreadListItemEventType = keyof ThreadListItemEventPayload; + +type ThreadListItemRuntime = { + readonly path: ThreadListItemRuntimePath; + getState(): ThreadListItemState; + initialize(): Promise<{ + remoteId: string; + externalId: string | undefined; + }>; + generateTitle(): Promise<void>; + switchTo(options?: { + unarchive?: boolean; + }): Promise<void>; + rename(newTitle: string): Promise<void>; + updateCustom(custom: Record<string, unknown> | undefined): Promise<void>; + archive(): Promise<void>; + unarchive(): Promise<void>; + delete(): Promise<void>; + detach(): void; + subscribe(callback: () => void): Unsubscribe; + unstable_on<E extends ThreadListItemEventType>(event: E, callback: ThreadListItemEventCallback<E>): Unsubscribe; + __internal_getRuntime(): ThreadListItemRuntime; +}; + +type ThreadListItemRuntimePath = { + readonly ref: string; + readonly threadSelector: { + readonly type: "main"; + } | { + readonly type: "index"; + readonly index: number; + } | { + readonly type: "archiveIndex"; + readonly index: number; + } | { + readonly type: "threadId"; + readonly threadId: string; + }; +}; + +type ThreadListItemState = { + readonly isMain: boolean; + readonly id: string; + readonly remoteId: string | undefined; + readonly externalId: string | undefined; + readonly status: ThreadListItemStatus; + readonly title?: string | undefined; + readonly lastMessageAt?: Date | undefined; + readonly custom?: Record<string, unknown> | undefined; +}; + +type ThreadListItemStatus = "archived" | "deleted" | "new" | "regular"; + +type ThreadListRuntime = { + getState(): ThreadListState; + subscribe(callback: () => void): Unsubscribe; + readonly main: ThreadRuntime; + getById(threadId: string): ThreadRuntime; + readonly mainItem: ThreadListItemRuntime; + getItemById(threadId: string): ThreadListItemRuntime; + getItemByIndex(idx: number): ThreadListItemRuntime; + getArchivedItemByIndex(idx: number): ThreadListItemRuntime; + switchToThread(threadId: string, options?: { + unarchive?: boolean; + }): Promise<void>; + switchToNewThread(): Promise<void>; + getLoadThreadsPromise(): Promise<void>; + reload(): Promise<void>; + loadMore(): Promise<void>; +}; + +type ThreadListState = { + readonly mainThreadId: string; + readonly newThreadId: string | undefined; + readonly threadIds: readonly string[]; + readonly archivedThreadIds: readonly string[]; + readonly isLoading: boolean; + readonly isLoadingMore: boolean; + readonly hasMore: boolean; + readonly threadItems: Readonly<Record<string, Omit<ThreadListItemState, "isMain" | "threadId">>>; +}; + +type ThreadMessage = BaseThreadMessage & (ThreadSystemMessage | ThreadUserMessage | ThreadAssistantMessage); + +type ThreadMessageLike = { + readonly role: "assistant" | "system" | "user"; + readonly content: string | readonly (TextMessagePart | ReasoningMessagePart | SourceMessagePart | ImageMessagePart | FileMessagePart | DataMessagePart | GenerativeUIMessagePart | Unstable_AudioMessagePart | DataPrefixedPart | { + readonly type: "tool-call"; + readonly toolCallId?: string; + readonly toolName: string; + readonly args?: ReadonlyJSONObject; + readonly argsText?: string; + readonly artifact?: any; + readonly result?: any | undefined; + readonly isError?: boolean | undefined; + readonly parentId?: string | undefined; + readonly messages?: readonly ThreadMessage[] | undefined; + readonly interrupt?: { + type: "human"; + payload: unknown; + }; + readonly timing?: ToolCallTiming; + readonly providerMetadata?: PartProviderMetadata; + readonly approval?: { + readonly id: string; + readonly approved?: boolean; + readonly reason?: string; + readonly isAutomatic?: boolean; + readonly options?: readonly ToolApprovalOption[]; + readonly optionId?: string; + readonly resolution?: "cancelled" | "expired"; + }; + })[]; + readonly id?: string | undefined; + readonly createdAt?: Date | undefined; + readonly status?: MessageStatus | undefined; + readonly attachments?: readonly (Omit<CompleteAttachment, "content"> & { + readonly content: readonly (ThreadUserMessagePart | DataPrefixedPart)[]; + })[] | undefined; + readonly metadata?: { + readonly unstable_state?: ReadonlyJSONValue; + readonly unstable_annotations?: readonly ReadonlyJSONValue[] | undefined; + readonly unstable_data?: readonly ReadonlyJSONValue[] | undefined; + readonly steps?: readonly ThreadStep[] | undefined; + readonly timing?: MessageTiming | undefined; + readonly submittedFeedback?: { + readonly type: "negative" | "positive"; + }; + readonly isOptimistic?: boolean | undefined; + readonly custom?: Record<string, unknown> | undefined; + } | undefined; +}; + +type ThreadRuntime = { + readonly path: ThreadRuntimePath; + readonly composer: ThreadComposerRuntime; + getState(): ThreadState; + append(message: CreateAppendMessage): void; + deleteMessage(messageId: string): void | Promise<void>; + startRun(config: CreateStartRunConfig): void; + resumeRun(config: CreateResumeRunConfig): void; + exportExternalState(): any; + importExternalState(state: any): void; + subscribe(callback: () => void): Unsubscribe; + cancelRun(): void; + getModelContext(): ModelContext; + export(): ExportedMessageRepository; + import(repository: ExportedMessageRepository): void; + reset(initialMessages?: readonly ThreadMessageLike[]): void; + getMessageByIndex(idx: number): MessageRuntime; + getMessageById(messageId: string): MessageRuntime; + stopSpeaking(): void; + connectVoice(): void; + disconnectVoice(): void; + getVoiceVolume(): number; + subscribeVoiceVolume(callback: () => void): Unsubscribe; + muteVoice(): void; + unmuteVoice(): void; + unstable_on<E extends ThreadRuntimeEventType>(event: E, callback: ThreadRuntimeEventCallback<E>): Unsubscribe; +}; + +type ThreadRuntimeEventCallback<E extends ThreadRuntimeEventType> = (payload: ThreadRuntimeEventPayload[E]) => void; + +type ThreadRuntimeEventPayload = { + runStart: Record<string, never>; + runEnd: Record<string, never>; + initialize: Record<string, never>; + modelContextUpdate: Record<string, never>; +}; + +type ThreadRuntimeEventType = keyof ThreadRuntimeEventPayload; + +type ThreadRuntimePath = { + readonly ref: string; + readonly threadSelector: { + readonly type: "main"; + } | { + readonly type: "threadId"; + readonly threadId: string; + }; +}; + +type ThreadState = { + readonly threadId: string; + readonly metadata: ThreadListItemState; + readonly isDisabled: boolean; + readonly isLoading: boolean; + readonly isRunning: boolean; + readonly capabilities: RuntimeCapabilities; + readonly messages: readonly ThreadMessage[]; + readonly state: ReadonlyJSONValue; + readonly suggestions: readonly ThreadSuggestion[]; + readonly extras: unknown; + readonly speech: SpeechState | undefined; + readonly voice: VoiceSessionState | undefined; +}; + +type ThreadStep = { + readonly messageId?: string; + readonly usage?: { + readonly inputTokens: number; + readonly outputTokens: number; + } | undefined; +}; + +type ThreadSuggestion = { + prompt: string; +}; + +type ThreadSystemMessage = MessageCommonProps & { + readonly role: "system"; + readonly content: readonly [ + TextMessagePart + ]; + readonly metadata: { + readonly unstable_state?: undefined; + readonly unstable_annotations?: undefined; + readonly unstable_data?: undefined; + readonly steps?: undefined; + readonly submittedFeedback?: undefined; + readonly timing?: undefined; + readonly custom: Record<string, unknown>; + }; +}; + +type ThreadUserMessage = MessageCommonProps & { + readonly role: "user"; + readonly content: readonly ThreadUserMessagePart[]; + readonly attachments: readonly CompleteAttachment[]; + readonly metadata: { + readonly unstable_state?: undefined; + readonly unstable_annotations?: undefined; + readonly unstable_data?: undefined; + readonly steps?: undefined; + readonly submittedFeedback?: undefined; + readonly timing?: undefined; + readonly custom: Record<string, unknown>; + }; +}; + +type ThreadUserMessagePart = TextMessagePart | ImageMessagePart | FileMessagePart | DataMessagePart | Unstable_AudioMessagePart; + +type Tool<TArgs extends Record<string, unknown> = Record<string, unknown>, TResult = unknown> = FrontendTool<TArgs, TResult> | BackendTool<TArgs, TResult> | HumanTool<TArgs, TResult> | ProviderTool<TArgs, TResult> | McpTool | ToolWithoutType<TArgs, TResult>; + +type ToolApprovalOption = { + readonly id: string; + readonly kind: ToolApprovalOptionKind | (string & {}); + readonly label?: string; + readonly description?: string; + readonly grants?: readonly string[]; + readonly confirm?: boolean | { + title?: string; + description?: string; + }; +}; + +type ToolApprovalOptionKind = "allow-always" | "allow-once" | "reject-always" | "reject-once"; + +type ToolApprovalResponse = { + readonly approved: boolean; + readonly reason?: string; +} | { + readonly optionId: string; + readonly reason?: string; +} | { + readonly approved: boolean; + readonly optionId: string; + readonly reason?: string; +}; + +type ToolBase<TArgs extends Record<string, unknown> = Record<string, unknown>, TResult = unknown> = { + streamCall?: ToolStreamCallFunction<TArgs, TResult>; + display?: ToolDisplay; +}; + +interface ToolCallArgsReader<TArgs extends Record<string, unknown>> { + get<PathT extends TypePath<TArgs>>(...fieldPath: PathT): Promise<TypeAtPath<TArgs, PathT>>; + streamValues<PathT extends TypePath<TArgs>>(...fieldPath: PathT): AsyncIterableStream<DeepPartial<TypeAtPath<TArgs, PathT>>>; + streamText<PathT extends TypePath<TArgs>>(...fieldPath: PathT): TypeAtPath<TArgs, PathT> extends string & (infer U) ? AsyncIterableStream<U> : never; + forEach<PathT extends TypePath<TArgs>>(...fieldPath: PathT): NonNullable<TypeAtPath<TArgs, PathT>> extends Array<infer U> ? AsyncIterableStream<U> : never; +} + +type ToolCallMessagePart<TArgs = ReadonlyJSONObject, TResult = unknown> = { + readonly type: "tool-call"; + readonly toolCallId: string; + readonly toolName: string; + readonly args: TArgs; + readonly result?: TResult | undefined; + readonly isError?: boolean | undefined; + readonly argsText: string; + readonly artifact?: unknown; + readonly timing?: ToolCallTiming; + readonly mcp?: ToolCallMessagePartMcpMetadata; + readonly providerMetadata?: PartProviderMetadata; + readonly modelContent?: readonly ToolModelContentPart[] | undefined; + readonly interrupt?: { + type: "human"; + payload: unknown; + }; + readonly approval?: { + readonly id: string; + readonly approved?: boolean; + readonly reason?: string; + readonly isAutomatic?: boolean; + readonly options?: readonly ToolApprovalOption[]; + readonly optionId?: string; + readonly resolution?: "cancelled" | "expired"; + }; + readonly parentId?: string; + readonly messages?: readonly ThreadMessage[]; +}; + +type ToolCallMessagePartMcpMetadata = { + readonly app?: McpAppMetadata; +}; + +type ToolCallMessagePartStatus = { + readonly type: "requires-action"; + readonly reason: "interrupt"; +} | MessagePartStatus; + +interface ToolCallReader<TArgs extends Record<string, unknown> = Record<string, unknown>, TResult = unknown> { + args: ToolCallArgsReader<TArgs>; + response: ToolCallResponseReader<TResult>; + result: { + get: () => Promise<TResult>; + }; +} + +interface ToolCallResponseReader<TResult> { + get: () => Promise<ToolResponse<TResult>>; +} + +type ToolCallTiming = { + readonly startedAt: number; + readonly completedAt?: number; +}; + +type ToolDisplay = "inline" | "standalone"; + +type ToolExecuteFunction<TArgs, TResult> = (args: TArgs, context: ToolExecutionContext) => TResult | Promise<TResult>; + +type ToolExecutionContext = { + toolCallId: string; + abortSignal: AbortSignal; + human: (payload: unknown) => Promise<unknown>; +}; + +type ToolExecutionStatus = { + type: "executing"; +} | { + type: "interrupt"; + payload: { + type: "human"; + payload: unknown; + }; +}; + +type ToolModelContentPart = { + readonly type: "text"; + readonly text: string; +} | { + readonly type: "file"; + readonly data: string; + readonly mediaType: string; + readonly filename?: string; +}; + +type ToolModelOutputFunction<TArgs, TResult> = (options: { + toolCallId: string; + input: TArgs; + output: TResult; +}) => readonly ToolModelContentPart[] | Promise<readonly ToolModelContentPart[]>; + +declare class ToolResponse<TResult> { + get [TOOL_RESPONSE_SYMBOL](): boolean; + readonly artifact?: ReadonlyJSONValue; + readonly result: TResult; + readonly isError: boolean; + readonly modelContent?: readonly ToolModelContentPart[]; + readonly messages?: ReadonlyJSONValue; + constructor(options: ToolResponseLike<TResult>); + static [Symbol.hasInstance](obj: unknown): obj is ToolResponse<ReadonlyJSONValue>; + static toResponse(result: any | ToolResponse<any>): ToolResponse<any>; +} + +type ToolResponseLike<TResult> = { + result: TResult; + artifact?: ReadonlyJSONValue | undefined; + isError?: boolean | undefined; + modelContent?: readonly ToolModelContentPart[] | undefined; + messages?: ReadonlyJSONValue | undefined; +}; + +type ToolStreamCallFunction<TArgs extends Record<string, unknown> = Record<string, unknown>, TResult = unknown> = (reader: ToolCallReader<TArgs, TResult>, context: ToolExecutionContext) => void; + +type ToolWithoutType<TArgs extends Record<string, unknown> = Record<string, unknown>, TResult = unknown> = (Omit<FrontendTool<TArgs, TResult>, "type"> | Omit<BackendTool<TArgs, TResult>, "type"> | Omit<HumanTool<TArgs, TResult>, "type"> | Omit<ProviderTool<TArgs, TResult>, "type">) & { + type?: undefined; +}; + +type TupleIndex<T extends readonly any[]> = Exclude<keyof T, keyof any[]>; + +type TypeAtPath<T, P extends readonly any[]> = P extends [ + infer Head, + ...infer Rest +] ? Head extends keyof T ? TypeAtPath<T[Head], Rest> : never : T; + +type TypePath<T> = [ +] | (0 extends 1 & T ? any[] : T extends object ? T extends readonly any[] ? number extends T["length"] ? { + [K in TupleIndex<T>]: [ + AsNumber<K>, + ...TypePath<T[K]> + ]; +}[TupleIndex<T>] : [ + number, + ...TypePath<T[number]> +] : { + [K in ObjectKey<T>]: [ + K, + ...TypePath<T[K]> + ]; +}[ObjectKey<T>] : [ +]); + +type UnionToIntersection<U> = (U extends unknown ? (x: U) => void : never) extends ((x: infer I) => void) ? I : never; + +type Unstable_AudioMessagePart = { + readonly type: "audio"; + readonly audio: { + readonly data: string; + readonly format: "mp3" | "wav"; + }; +}; + +type Unsubscribe = () => void; + +type Unsubscribe$1 = () => void; + +type ValidateClient<K extends keyof ScopeRegistry> = ScopeRegistry[K] extends { + methods: ClientMethods; +} ? "meta" extends keyof ScopeRegistry[K] ? ScopeRegistry[K]["meta"] extends ClientMetaType ? "events" extends keyof ScopeRegistry[K] ? ScopeRegistry[K]["events"] extends ClientEventsType<K> ? ScopeRegistry[K] : ClientError<`ERROR: ${K & string} has invalid events type`> : ScopeRegistry[K] : ClientError<`ERROR: ${K & string} has invalid meta type`> : "events" extends keyof ScopeRegistry[K] ? ScopeRegistry[K]["events"] extends ClientEventsType<K> ? ScopeRegistry[K] : ClientError<`ERROR: ${K & string} has invalid events type`> : ScopeRegistry[K] : ClientError<`ERROR: ${K & string} has invalid methods type`>; + +type VoiceSessionState = { + readonly status: RealtimeVoiceAdapter.Status; + readonly isMuted: boolean; + readonly mode: RealtimeVoiceAdapter.Mode; +}; + +type WildcardPayload = { + [K in keyof ClientEventMap]: { + event: K; + payload: ClientEventMap[K]; + }; +}[Extract<keyof ClientEventMap, string>]; + +declare const createPiHttpClient: (options?: PiHttpClientOptions) => PiClient; + +declare const createPiNodeClient: (options?: PiNodeClientOptions) => PiClient; + +declare const createPiThreadState: (threadId: string) => PiThreadState; + +declare const createSseDecoder: () => { + push(chunk: string): SseFrame[]; +}; + +declare const getPiThreadSupervisor: (options?: PiNodeClientOptions) => PiThreadSupervisor; + +declare global { + var __assistantUiPiHttpStreams: Map<string, SharedStream> | undefined; +} + +declare global { + interface Window { + SpeechRecognition?: SpeechRecognitionConstructor; + webkitSpeechRecognition?: SpeechRecognitionConstructor; + } +} + +declare global { + interface Window { + __ASSISTANT_UI_DEVTOOLS_HOOK__?: any; + } +} + +declare namespace entry_root_exports { + export { PiAgentMessage, PiAnyClientEvent, PiAssistantContent, PiAssistantMessage, PiAssistantMessageDelta, PiBashExecutionMessage, PiBranchSummaryMessage, PiClient, PiClientEvent, PiClientEventBody, PiClientEventEnvelope, PiCompactionSummaryMessage, PiContextUsage, PiCustomMessage, PiEventStreamOptions, PiHostUiRequest, PiHostUiRequestKind, PiHostUiResponse, PiHttpClientOptions, PiImageContent, PiInputAttachment, PiInterruptAnswer, PiKnownAgentMessage, PiLoadState, PiModelInfo, ContentPart as PiProjectedContentPart, PiProjectionInput, PiQueueMode, PiQueuedMessage, PiRunStatus, PiRuntimeExtras, PiRuntimeOptions, PiRuntimeReadiness, PiSendMessageInput, PiSendOptions, PiStopReason, PiTextContent, PiThinkingContent, PiThinkingLevel, PiThreadController, PiThreadControllerLike, PiThreadMetadata, PiThreadSnapshot, PiThreadState, PiThreadStatus, PiToolCall, PiToolExecutionState, PiToolResultContent, PiToolResultMessage, PiTranscriptMessage, PiUnknownAgentMessage, PiUnknownClientEventBody, PiUsage, PiUserContent, PiUserMessage, SplitHostUiRequests, SseFrame, createPiHttpClient, createPiThreadState, createSseDecoder, isPiSteerQueueItemId, openPiEventStream, piQueueItemId, projectPiThreadMessages, projectPiThreadRepository, reducePiThreadState, responseForApproval, responseForInterrupt, responseForRequest, splitHostUiRequests, usePiHostUiRequests, usePiRuntime, usePiRuntimeExtras, usePiSession, usePiThreadState }; +} + +declare const isPiSteerQueueItemId: (id: string) => boolean; + +declare namespace entry_node_exports { + export { PiAgentMessage, PiAnyClientEvent, PiAssistantContent, PiAssistantMessage, PiAssistantMessageDelta, PiBashExecutionMessage, PiBranchSummaryMessage, PiClient, PiClientEvent, PiClientEventBody, PiClientEventEnvelope, PiCompactionSummaryMessage, PiContextUsage, PiCustomMessage, PiHostUiRequest, PiHostUiRequestKind, PiHostUiResponse, PiImageContent, PiInputAttachment, PiKnownAgentMessage, PiModelInfo, PiNodeClientOptions, PiQueuedMessage, PiRuntimeReadiness, PiSendMessageInput, PiStopReason, PiTextContent, PiThinkingContent, PiThinkingLevel, PiThreadMetadata, PiThreadSnapshot, PiThreadStatus, PiThreadSupervisor, PiThreadSupervisorOptions, PiToolCall, PiToolResultContent, PiToolResultMessage, PiTranscriptMessage, PiUnknownAgentMessage, PiUnknownClientEventBody, PiUnsupportedHostUiError, PiUsage, PiUserContent, PiUserMessage, createPiNodeClient, getPiThreadSupervisor }; +} + +declare const openPiEventStream: (options: PiEventStreamOptions) => (() => void); + +declare const piQueueItemId: (mode: PiQueueMode, index: number) => string; + +declare const projectPiThreadMessages: (input: PiProjectionInput) => ThreadMessageLike[]; + +declare const projectPiThreadRepository: (input: PiProjectionInput) => ExportedMessageRepository; + +declare const reducePiThreadState: (state: PiThreadState, event: PiClientEvent) => PiThreadState; + +declare const responseForApproval: (requestId: string, approved: boolean) => PiHostUiResponse; + +declare const responseForInterrupt: (requestId: string, answer: PiInterruptAnswer) => PiHostUiResponse; + +declare const responseForRequest: (request: PiHostUiRequest, answer: boolean | PiInterruptAnswer) => PiHostUiResponse; + +declare const splitHostUiRequests: (requests: readonly PiHostUiRequest[]) => SplitHostUiRequests; + +declare const usePiHostUiRequests: () => { + requests: readonly PiHostUiRequest[]; + respond: (response: PiHostUiResponse) => Promise<void>; +}; + +declare const usePiRuntime: (options: PiRuntimeOptions) => AssistantRuntime; + +declare const usePiRuntimeExtras: () => PiRuntimeExtras; + +declare const usePiSession: () => PiThreadMetadata | null; + +declare function usePiThreadState(): PiThreadState; + +declare function usePiThreadState<T>(selector: (state: PiThreadState) => T): T; + +export { entry_node_exports as entry_node, entry_root_exports as entry_root }; diff --git a/api-surface/assistant-ui__react-streamdown.ts b/api-surface/assistant-ui__react-streamdown.ts new file mode 100644 index 0000000..038019b --- /dev/null +++ b/api-surface/assistant-ui__react-streamdown.ts @@ -0,0 +1,450 @@ +import "@radix-ui/react-primitive"; + +import "@standard-schema/spec"; + +import "radix-ui"; + +import { ComponentPropsWithoutRef, ComponentType, ReactNode } from "react"; + +import "react-textarea-autosize"; + +import { Options as RemarkRehypeOptions } from "remark-rehype"; + +import { RemendOptions } from "remend"; + +import { BundledLanguage, BundledTheme, BundledTheme as BundledTheme$1, CjkPlugin, CodeHighlighterPlugin, DiagramPlugin, HighlightOptions, MathPlugin, MermaidErrorComponentProps, MermaidOptions, StreamdownContext, StreamdownProps, StreamdownProps as StreamdownProps$1, StreamdownProps as StreamdownProps$2, parseMarkdownIntoBlocks } from "streamdown"; + +import "zustand"; + +type AllowedTags = Record<string, string[]>; + +type AncestorsOf<K extends ClientNames, Seen extends ClientNames = never> = K extends Seen ? never : ParentOf<K> extends never ? never : ParentOf<K> | AncestorsOf<ParentOf<K>, Seen | K>; + +type AssistantClient = { + [K in ClientNames]: AssistantClientAccessor<K>; +} & { + subscribe(listener: () => void): Unsubscribe; + on<TEvent extends AssistantEventName>(selector: AssistantEventSelector<TEvent>, callback: AssistantEventCallback<TEvent>): Unsubscribe; +}; + +type AssistantClientAccessor<K extends ClientNames> = (() => ClientSchemas[K]["methods"]) & (ClientMeta<K> | { + source: "root"; + query: Record<string, never>; +} | { + source: null; + query: null; +}) & { + name: K; +}; + +type AssistantEventCallback<TEvent extends AssistantEventName> = (payload: AssistantEventPayload[TEvent]) => void; + +type AssistantEventName = keyof AssistantEventPayload; + +type AssistantEventPayload = ClientEventMap & { + "*": WildcardPayload; +}; + +type AssistantEventScope<TEvent extends AssistantEventName> = "*" | EventSource<TEvent> | (EventSource<TEvent> extends ClientNames ? AncestorsOf<EventSource<TEvent>> : never); + +type AssistantEventSelector<TEvent extends AssistantEventName> = TEvent | { + scope: AssistantEventScope<TEvent>; + event: TEvent; +}; + +type BlockProps = { + content: string; + shouldParseIncompleteMarkdown: boolean; + index: number; + components?: StreamdownProps$2["components"]; + rehypePlugins?: StreamdownProps$2["rehypePlugins"]; + remarkPlugins?: StreamdownProps$2["remarkPlugins"]; + remarkRehypeOptions?: RemarkRehypeOptions; +}; + +type CaretStyle = "block" | "circle"; + +type ClientError<E extends string> = { + methods: Record<E, () => E>; + meta: { + source: ClientNames; + query: Record<E, E>; + }; + events: Record<`${E}.`, E>; +}; + +type ClientEventMap = UnionToIntersection<{ + [K in ClientNames]: ClientEvents<K>; +}[ClientNames]>; + +type ClientEvents<K extends ClientNames> = "events" extends keyof ClientSchemas[K] ? ClientSchemas[K]["events"] extends ClientEventsType<K> ? ClientSchemas[K]["events"] : never : never; + +type ClientEventsType<K extends ClientNames> = Record<`${K}.${string}`, unknown>; + +type ClientMeta<K extends ClientNames> = "meta" extends keyof ClientSchemas[K] ? Pick<ClientSchemas[K]["meta"] extends ClientMetaType ? ClientSchemas[K]["meta"] : never, "query" | "source"> : never; + +type ClientMetaType = { + source: ClientNames; + query: Record<string, unknown>; +}; + +interface ClientMethods { + [key: string | symbol]: (...args: any[]) => any; +} + +type ClientNames = keyof ClientSchemas extends (infer U) ? U : never; + +type ClientSchemas = keyof ScopeRegistry extends never ? { + "ERROR: No clients were defined": ClientError<"ERROR: No clients were defined">; +} : { + [K in keyof ScopeRegistry]: ValidateClient<K>; +}; + +type CodeHeaderProps = { + node?: Element | undefined; + language: string | undefined; + code: string; +}; + +interface Comment extends Literal { + type: "comment"; + data?: CommentData | undefined; +} + +interface CommentData extends Data { +} + +type ComponentsByLanguage = Record<string, { + CodeHeader?: ComponentType<CodeHeaderProps> | undefined; + SyntaxHighlighter?: ComponentType<SyntaxHighlighterProps> | undefined; +}>; + +type ControlsConfig = boolean | { + table?: boolean; + code?: boolean; + mermaid?: boolean | { + download?: boolean; + copy?: boolean; + fullscreen?: boolean; + panZoom?: boolean; + }; +}; + +declare const DEFAULT_SHIKI_THEME: [ + BundledTheme, + BundledTheme +]; + +interface Data extends Data$1 { +} + +interface Data$1 { +} + +interface DevToolsApiEntry { + api: Partial<AssistantClient>; + logs: EventLog[]; +} + +interface DevToolsHook { + apis: Map<number, DevToolsApiEntry>; + nextId: number; + listeners: Set<(apiId: number) => void>; +} + +interface Doctype extends Node$1 { + type: "doctype"; + data?: DoctypeData | undefined; +} + +interface DoctypeData extends Data { +} + +interface Element extends Parent { + type: "element"; + tagName: string; + properties: Properties; + children: ElementContent[]; + content?: Root | undefined; + data?: ElementData | undefined; +} + +type ElementContent = ElementContentMap[keyof ElementContentMap]; + +interface ElementContentMap { + comment: Comment; + element: Element; + text: Text; +} + +interface ElementData extends Data { +} + +interface EventLog { + time: Date; + event: string; + data: unknown; +} + +type EventSource<T extends AssistantEventName> = T extends `${infer Source}.${string}` ? Source : never; + +type LinkSafetyConfig = { + enabled: boolean; + onLinkCheck?: (url: string) => Promise<boolean> | boolean; + renderModal?: (props: LinkSafetyModalProps) => ReactNode; +}; + +type LinkSafetyModalProps = { + url: string; + isOpen: boolean; + onClose: () => void; + onConfirm: () => void; +}; + +interface Literal extends Node { + value: string; +} + +interface Node extends Node$1 { + data?: Data | undefined; +} + +interface Node$1 { + type: string; + data?: Data$1 | undefined; + position?: Position | undefined; +} + +interface Parent extends Node { + children: RootContent[]; +} + +type ParentOf<K extends ClientNames> = AssistantClientAccessor<K> extends { + source: infer S; +} ? S extends ClientNames ? S : never : never; + +type PluginConfig = { + code?: unknown | false | undefined; + math?: unknown | false | undefined; + cjk?: unknown | false | undefined; + mermaid?: unknown | false | undefined; +}; + +interface Point { + line: number; + column: number; + offset?: number | undefined; +} + +interface Position { + start: Point; + end: Point; +} + +type PreOverrideProps = ComponentPropsWithoutRef<"pre"> & { + node?: Element | undefined; +}; + +interface Properties { + [PropertyName: string]: boolean | number | string | null | undefined | Array<string | number>; +} + +type RemendConfig = { + links?: boolean; + images?: boolean; + linkMode?: "protocol" | "text-only"; + bold?: boolean; + italic?: boolean; + boldItalic?: boolean; + inlineCode?: boolean; + strikethrough?: boolean; + katex?: boolean; + setextHeadings?: boolean; + handlers?: RemendHandler[]; +}; + +type RemendHandler = { + name: string; + handle: (text: string) => string; + priority?: number; +}; + +type ResolvedPluginConfig = NonNullable<StreamdownProps$2["plugins"]>; + +interface Root extends Parent { + type: "root"; + children: RootContent[]; + data?: RootData | undefined; +} + +type RootContent = RootContentMap[keyof RootContentMap]; + +interface RootContentMap { + comment: Comment; + doctype: Doctype; + element: Element; + text: Text; +} + +interface RootData extends Data { +} + +interface ScopeRegistry { + [key: string]: { methods: any; meta?: any; events?: any }; +} + +type SecurityConfig = { + allowedLinkPrefixes?: string[]; + allowedImagePrefixes?: string[]; + allowedProtocols?: string[]; + allowDataImages?: boolean; + defaultOrigin?: string; + blockedLinkClass?: string; + blockedImageClass?: string; +}; + +type SmoothOptions = { + drainMs?: number | undefined; + maxCharIntervalMs?: number | undefined; + maxCharsPerFrame?: number | undefined; + minCommitMs?: number | undefined; +}; + +interface SpeechRecognitionConstructor { + new (): SpeechRecognitionInstance; +} + +interface SpeechRecognitionInstance extends EventTarget { + lang: string; + continuous: boolean; + interimResults: boolean; + start(): void; + stop(): void; + abort(): void; +} + +type StreamdownTextComponents = NonNullable<StreamdownProps$2["components"]> & { + SyntaxHighlighter?: ComponentType<SyntaxHighlighterProps> | undefined; + CodeHeader?: ComponentType<CodeHeaderProps> | undefined; +}; + +declare const StreamdownTextPrimitive: import("react").ForwardRefExoticComponent<Omit<StreamdownProps, "BlockComponent" | "caret" | "children" | "components" | "controls" | "linkSafety" | "mermaid" | "parseMarkdownIntoBlocksFn" | "plugins" | "remend"> & { + components?: StreamdownTextComponents | undefined; + componentsByLanguage?: ComponentsByLanguage | undefined; + plugins?: PluginConfig | undefined; + preprocess?: ((text: string) => string) | undefined; + defer?: boolean | undefined; + smooth?: boolean | SmoothOptions | undefined; + containerProps?: Omit<import("react").ComponentPropsWithoutRef<"div">, "children"> | undefined; + containerClassName?: string | undefined; + caret?: CaretStyle | undefined; + controls?: ControlsConfig | undefined; + linkSafety?: LinkSafetyConfig | undefined; + remend?: RemendConfig | undefined; + mermaid?: import("streamdown").MermaidOptions | undefined; + parseIncompleteMarkdown?: boolean | undefined; + allowedTags?: AllowedTags | undefined; + remarkRehypeOptions?: import("remark-rehype").Options | undefined; + security?: SecurityConfig | undefined; + BlockComponent?: StreamdownProps["BlockComponent"] | undefined; + parseMarkdownIntoBlocksFn?: ((markdown: string) => string[]) | undefined; +} & import("react").RefAttributes<HTMLDivElement>>; + +type StreamdownTextPrimitiveProps = Omit<StreamdownProps$2, "BlockComponent" | "caret" | "children" | "components" | "controls" | "linkSafety" | "mermaid" | "parseMarkdownIntoBlocksFn" | "plugins" | "remend"> & { + components?: StreamdownTextComponents | undefined; + componentsByLanguage?: ComponentsByLanguage | undefined; + plugins?: PluginConfig | undefined; + preprocess?: ((text: string) => string) | undefined; + defer?: boolean | undefined; + smooth?: boolean | SmoothOptions | undefined; + containerProps?: Omit<ComponentPropsWithoutRef<"div">, "children"> | undefined; + containerClassName?: string | undefined; + caret?: CaretStyle | undefined; + controls?: ControlsConfig | undefined; + linkSafety?: LinkSafetyConfig | undefined; + remend?: RemendConfig | undefined; + mermaid?: MermaidOptions | undefined; + parseIncompleteMarkdown?: boolean | undefined; + allowedTags?: AllowedTags | undefined; + remarkRehypeOptions?: RemarkRehypeOptions | undefined; + security?: SecurityConfig | undefined; + BlockComponent?: StreamdownProps$2["BlockComponent"] | undefined; + parseMarkdownIntoBlocksFn?: ((markdown: string) => string[]) | undefined; +}; + +type SyntaxHighlighterProps = { + node?: Element | undefined; + components: { + Pre: ComponentType<ComponentPropsWithoutRef<"pre"> & { + node?: Element | undefined; + }>; + Code: ComponentType<ComponentPropsWithoutRef<"code"> & { + node?: Element | undefined; + }>; + }; + language: string; + code: string; +}; + +interface Text extends Literal { + type: "text"; + data?: TextData | undefined; +} + +interface TextData extends Data { +} + +type UnionToIntersection<U> = (U extends unknown ? (x: U) => void : never) extends ((x: infer I) => void) ? I : never; + +type Unsubscribe = () => void; + +type ValidateClient<K extends keyof ScopeRegistry> = ScopeRegistry[K] extends { + methods: ClientMethods; +} ? "meta" extends keyof ScopeRegistry[K] ? ScopeRegistry[K]["meta"] extends ClientMetaType ? "events" extends keyof ScopeRegistry[K] ? ScopeRegistry[K]["events"] extends ClientEventsType<K> ? ScopeRegistry[K] : ClientError<`ERROR: ${K & string} has invalid events type`> : ScopeRegistry[K] : ClientError<`ERROR: ${K & string} has invalid meta type`> : "events" extends keyof ScopeRegistry[K] ? ScopeRegistry[K]["events"] extends ClientEventsType<K> ? ScopeRegistry[K] : ClientError<`ERROR: ${K & string} has invalid events type`> : ScopeRegistry[K] : ClientError<`ERROR: ${K & string} has invalid methods type`>; + +type WildcardPayload = { + [K in keyof ClientEventMap]: { + event: K; + payload: ClientEventMap[K]; + }; +}[Extract<keyof ClientEventMap, string>]; + +declare function escapeCurrencyDollars(text: string): string; + +declare function findRemendWindowStart(text: string): number; + +declare global { + interface Window { + SpeechRecognition?: SpeechRecognitionConstructor; + webkitSpeechRecognition?: SpeechRecognitionConstructor; + } +} + +declare global { + interface Window { + __ASSISTANT_UI_DEVTOOLS_HOOK__?: any; + } +} + +declare namespace entry_root_exports { + export { AllowedTags, BlockProps, BundledLanguage, BundledTheme$1 as BundledTheme, CaretStyle, CjkPlugin, CodeHeaderProps, CodeHighlighterPlugin, ComponentsByLanguage, ControlsConfig, DEFAULT_SHIKI_THEME, DiagramPlugin, HighlightOptions, LinkSafetyConfig, LinkSafetyModalProps, MathPlugin, MermaidErrorComponentProps, MermaidOptions, PluginConfig, RemarkRehypeOptions, RemendConfig, RemendHandler, ResolvedPluginConfig, SecurityConfig, StreamdownContext, StreamdownProps$1 as StreamdownProps, StreamdownTextComponents, StreamdownTextPrimitive, StreamdownTextPrimitiveProps, SyntaxHighlighterProps, escapeCurrencyDollars, findRemendWindowStart, memoCompareNodes, normalizeMathDelimiters, parseMarkdownIntoBlocks, rewriteCustomMathTags, rewriteLatexBracketDelimiters, tailBoundedRemend, useIsStreamdownCodeBlock, useStreamdownPreProps }; +} + +declare function memoCompareNodes<T extends { + children?: ReactNode; + [key: string]: unknown; +}>(prev: Readonly<T>, next: Readonly<T>): boolean; + +declare function normalizeMathDelimiters(text: string): string; + +declare function rewriteCustomMathTags(text: string): string; + +declare function rewriteLatexBracketDelimiters(text: string): string; + +declare function tailBoundedRemend(text: string, options?: RemendOptions): string; + +declare function useIsStreamdownCodeBlock(): boolean; + +declare function useStreamdownPreProps(): PreOverrideProps | null; + +export { entry_root_exports as entry_root }; diff --git a/api-surface/assistant-ui__react-syntax-highlighter.ts b/api-surface/assistant-ui__react-syntax-highlighter.ts new file mode 100644 index 0000000..06ae56f --- /dev/null +++ b/api-surface/assistant-ui__react-syntax-highlighter.ts @@ -0,0 +1,294 @@ +import "@radix-ui/react-primitive"; + +import "@standard-schema/spec"; + +import "radix-ui"; + +import { ComponentPropsWithoutRef, ComponentType } from "react"; + +import "react-markdown"; + +import "react-textarea-autosize"; + +import "zustand"; + +type AncestorsOf<K extends ClientNames, Seen extends ClientNames = never> = K extends Seen ? never : ParentOf<K> extends never ? never : ParentOf<K> | AncestorsOf<ParentOf<K>, Seen | K>; + +type AssistantClient = { + [K in ClientNames]: AssistantClientAccessor<K>; +} & { + subscribe(listener: () => void): Unsubscribe; + on<TEvent extends AssistantEventName>(selector: AssistantEventSelector<TEvent>, callback: AssistantEventCallback<TEvent>): Unsubscribe; +}; + +type AssistantClientAccessor<K extends ClientNames> = (() => ClientSchemas[K]["methods"]) & (ClientMeta<K> | { + source: "root"; + query: Record<string, never>; +} | { + source: null; + query: null; +}) & { + name: K; +}; + +type AssistantEventCallback<TEvent extends AssistantEventName> = (payload: AssistantEventPayload[TEvent]) => void; + +type AssistantEventName = keyof AssistantEventPayload; + +type AssistantEventPayload = ClientEventMap & { + "*": WildcardPayload; +}; + +type AssistantEventScope<TEvent extends AssistantEventName> = "*" | EventSource<TEvent> | (EventSource<TEvent> extends ClientNames ? AncestorsOf<EventSource<TEvent>> : never); + +type AssistantEventSelector<TEvent extends AssistantEventName> = TEvent | { + scope: AssistantEventScope<TEvent>; + event: TEvent; +}; + +type ClientError<E extends string> = { + methods: Record<E, () => E>; + meta: { + source: ClientNames; + query: Record<E, E>; + }; + events: Record<`${E}.`, E>; +}; + +type ClientEventMap = UnionToIntersection<{ + [K in ClientNames]: ClientEvents<K>; +}[ClientNames]>; + +type ClientEvents<K extends ClientNames> = "events" extends keyof ClientSchemas[K] ? ClientSchemas[K]["events"] extends ClientEventsType<K> ? ClientSchemas[K]["events"] : never : never; + +type ClientEventsType<K extends ClientNames> = Record<`${K}.${string}`, unknown>; + +type ClientMeta<K extends ClientNames> = "meta" extends keyof ClientSchemas[K] ? Pick<ClientSchemas[K]["meta"] extends ClientMetaType ? ClientSchemas[K]["meta"] : never, "query" | "source"> : never; + +type ClientMetaType = { + source: ClientNames; + query: Record<string, unknown>; +}; + +interface ClientMethods { + [key: string | symbol]: (...args: any[]) => any; +} + +type ClientNames = keyof ClientSchemas extends (infer U) ? U : never; + +type ClientSchemas = keyof ScopeRegistry extends never ? { + "ERROR: No clients were defined": ClientError<"ERROR: No clients were defined">; +} : { + [K in keyof ScopeRegistry]: ValidateClient<K>; +}; + +type CodeComponent = ComponentType<ComponentPropsWithoutRef<"code"> & { + node?: Element | undefined; +}>; + +interface Comment extends Literal { + type: "comment"; + data?: CommentData | undefined; +} + +interface CommentData extends Data { +} + +interface Data extends Data$1 { +} + +interface Data$1 { +} + +interface DevToolsApiEntry { + api: Partial<AssistantClient>; + logs: EventLog[]; +} + +interface DevToolsHook { + apis: Map<number, DevToolsApiEntry>; + nextId: number; + listeners: Set<(apiId: number) => void>; +} + +interface Doctype extends Node$1 { + type: "doctype"; + data?: DoctypeData | undefined; +} + +interface DoctypeData extends Data { +} + +interface Element extends Parent { + type: "element"; + tagName: string; + properties: Properties; + children: ElementContent[]; + content?: Root | undefined; + data?: ElementData | undefined; +} + +type ElementContent = ElementContentMap[keyof ElementContentMap]; + +interface ElementContentMap { + comment: Comment; + element: Element; + text: Text; +} + +interface ElementData extends Data { +} + +interface EventLog { + time: Date; + event: string; + data: unknown; +} + +type EventSource<T extends AssistantEventName> = T extends `${infer Source}.${string}` ? Source : never; + +interface Literal extends Node { + value: string; +} + +interface Node extends Node$1 { + data?: Data | undefined; +} + +interface Node$1 { + type: string; + data?: Data$1 | undefined; + position?: Position | undefined; +} + +interface Parent extends Node { + children: RootContent[]; +} + +type ParentOf<K extends ClientNames> = AssistantClientAccessor<K> extends { + source: infer S; +} ? S extends ClientNames ? S : never : never; + +interface Point { + line: number; + column: number; + offset?: number | undefined; +} + +interface Position { + start: Point; + end: Point; +} + +type PreComponent = ComponentType<ComponentPropsWithoutRef<"pre"> & { + node?: Element | undefined; +}>; + +interface Properties { + [PropertyName: string]: boolean | number | string | null | undefined | Array<string | number>; +} + +interface Root extends Parent { + type: "root"; + children: RootContent[]; + data?: RootData | undefined; +} + +type RootContent = RootContentMap[keyof RootContentMap]; + +interface RootContentMap { + comment: Comment; + doctype: Doctype; + element: Element; + text: Text; +} + +interface RootData extends Data { +} + +interface ScopeRegistry { + [key: string]: { methods: any; meta?: any; events?: any }; +} + +interface SpeechRecognitionConstructor { + new (): SpeechRecognitionInstance; +} + +interface SpeechRecognitionInstance extends EventTarget { + lang: string; + continuous: boolean; + interimResults: boolean; + start(): void; + stop(): void; + abort(): void; +} + +type SyntaxHighlighterProps = { + node?: Element | undefined; + components: { + Pre: PreComponent; + Code: CodeComponent; + }; + language: string; + code: string; +}; + +interface Text extends Literal { + type: "text"; + data?: TextData | undefined; +} + +interface TextData extends Data { +} + +type UnionToIntersection<U> = (U extends unknown ? (x: U) => void : never) extends ((x: infer I) => void) ? I : never; + +type Unsubscribe = () => void; + +type ValidateClient<K extends keyof ScopeRegistry> = ScopeRegistry[K] extends { + methods: ClientMethods; +} ? "meta" extends keyof ScopeRegistry[K] ? ScopeRegistry[K]["meta"] extends ClientMetaType ? "events" extends keyof ScopeRegistry[K] ? ScopeRegistry[K]["events"] extends ClientEventsType<K> ? ScopeRegistry[K] : ClientError<`ERROR: ${K & string} has invalid events type`> : ScopeRegistry[K] : ClientError<`ERROR: ${K & string} has invalid meta type`> : "events" extends keyof ScopeRegistry[K] ? ScopeRegistry[K]["events"] extends ClientEventsType<K> ? ScopeRegistry[K] : ClientError<`ERROR: ${K & string} has invalid events type`> : ScopeRegistry[K] : ClientError<`ERROR: ${K & string} has invalid methods type`>; + +type WildcardPayload = { + [K in keyof ClientEventMap]: { + event: K; + payload: ClientEventMap[K]; + }; +}[Extract<keyof ClientEventMap, string>]; + +declare namespace entry_full_exports { + export { makePrismAsyncSyntaxHighlighter, makePrismSyntaxHighlighter, makeSyntaxHighlighter }; +} + +declare global { + interface Window { + SpeechRecognition?: SpeechRecognitionConstructor; + webkitSpeechRecognition?: SpeechRecognitionConstructor; + } +} + +declare global { + interface Window { + __ASSISTANT_UI_DEVTOOLS_HOOK__?: any; + } +} + +declare namespace entry_root_exports { + export { makeLightAsyncSyntaxHighlighter, makeLightSyntaxHighlighter, makePrismAsyncLightSyntaxHighlighter, makePrismLightSyntaxHighlighter }; +} + +declare const makeLightAsyncSyntaxHighlighter: (config: Omit<import("react-syntax-highlighter").SyntaxHighlighterProps, "children" | "language">) => import("react").FC<SyntaxHighlighterProps>; + +declare const makeLightSyntaxHighlighter: (config: Omit<import("react-syntax-highlighter").SyntaxHighlighterProps, "children" | "language">) => import("react").FC<SyntaxHighlighterProps>; + +declare const makePrismAsyncLightSyntaxHighlighter: (config: Omit<import("react-syntax-highlighter").SyntaxHighlighterProps, "children" | "language">) => import("react").FC<SyntaxHighlighterProps>; + +declare const makePrismAsyncSyntaxHighlighter: (config: Omit<import("react-syntax-highlighter").SyntaxHighlighterProps, "children" | "language">) => import("react").FC<SyntaxHighlighterProps>; + +declare const makePrismLightSyntaxHighlighter: (config: Omit<import("react-syntax-highlighter").SyntaxHighlighterProps, "children" | "language">) => import("react").FC<SyntaxHighlighterProps>; + +declare const makePrismSyntaxHighlighter: (config: Omit<import("react-syntax-highlighter").SyntaxHighlighterProps, "children" | "language">) => import("react").FC<SyntaxHighlighterProps>; + +declare const makeSyntaxHighlighter: (config: Omit<import("react-syntax-highlighter").SyntaxHighlighterProps, "children" | "language">) => import("react").FC<SyntaxHighlighterProps>; + +export { entry_full_exports as entry_full, entry_root_exports as entry_root }; diff --git a/api-surface/assistant-ui__react.ts b/api-surface/assistant-ui__react.ts new file mode 100644 index 0000000..b21169b --- /dev/null +++ b/api-surface/assistant-ui__react.ts @@ -0,0 +1,7573 @@ +import { Primitive } from "@radix-ui/react-primitive"; + +import { StandardSchemaV1 } from "@standard-schema/spec"; + +import { DropdownMenu, Popover } from "radix-ui"; + +import React, { CSSProperties, ComponentPropsWithoutRef, ComponentRef, ComponentType, ElementRef, ElementType, FC, ForwardRefExoticComponent, KeyboardEventHandler, PropsWithChildren, ReactElement, ReactNode, RefAttributes, RefCallback, RefObject } from "react"; + +import { TextareaAutosizeProps } from "react-textarea-autosize"; + +import { StoreApi } from "zustand"; + +declare namespace ActionBarMorePrimitiveContent { + type Element = ComponentRef<typeof DropdownMenu.Content>; + type Props = WithRenderPropProps<typeof DropdownMenu.Content> & { + portalProps?: ComponentPropsWithoutRef<typeof DropdownMenu.Portal> | undefined; + }; +} + +declare const ActionBarMorePrimitiveContent: import("react").ForwardRefExoticComponent<Omit<DropdownMenu.DropdownMenuContentProps & import("react").RefAttributes<HTMLDivElement>, "ref"> & { + render?: import("react").ReactElement | undefined; +} & { + portalProps?: ComponentPropsWithoutRef<typeof DropdownMenu.Portal> | undefined; +} & import("react").RefAttributes<HTMLDivElement>>; + +declare namespace ActionBarMorePrimitiveItem { + type Element = ComponentRef<typeof DropdownMenu.Item>; + type Props = WithRenderPropProps<typeof DropdownMenu.Item>; +} + +declare const ActionBarMorePrimitiveItem: import("react").ForwardRefExoticComponent<Omit<DropdownMenu.DropdownMenuItemProps & import("react").RefAttributes<HTMLDivElement>, "ref"> & { + render?: import("react").ReactElement | undefined; +} & import("react").RefAttributes<HTMLDivElement>>; + +declare namespace ActionBarMorePrimitiveRoot { + type Props = DropdownMenu.DropdownMenuProps; +} + +declare const ActionBarMorePrimitiveRoot: FC<ActionBarMorePrimitiveRoot.Props>; + +declare namespace ActionBarMorePrimitiveSeparator { + type Element = ComponentRef<typeof DropdownMenu.Separator>; + type Props = WithRenderPropProps<typeof DropdownMenu.Separator>; +} + +declare const ActionBarMorePrimitiveSeparator: import("react").ForwardRefExoticComponent<Omit<DropdownMenu.DropdownMenuSeparatorProps & import("react").RefAttributes<HTMLDivElement>, "ref"> & { + render?: import("react").ReactElement | undefined; +} & import("react").RefAttributes<HTMLDivElement>>; + +declare namespace ActionBarMorePrimitiveTrigger { + type Element = ComponentRef<typeof DropdownMenu.Trigger>; + type Props = WithRenderPropProps<typeof DropdownMenu.Trigger>; +} + +declare const ActionBarMorePrimitiveTrigger: import("react").ForwardRefExoticComponent<Omit<DropdownMenu.DropdownMenuTriggerProps & import("react").RefAttributes<HTMLButtonElement>, "ref"> & { + render?: import("react").ReactElement | undefined; +} & import("react").RefAttributes<HTMLButtonElement>>; + +declare namespace ActionBarPrimitiveCopy { + type Element = HTMLButtonElement; + type Props = ActionButtonProps<typeof useActionBarPrimitiveCopy>; +} + +declare const ActionBarPrimitiveCopy: import("react").ForwardRefExoticComponent<Omit<Omit<import("react").ClassAttributes<HTMLButtonElement> & import("react").ButtonHTMLAttributes<HTMLButtonElement> & { + asChild?: boolean; +}, "ref"> & { + render?: import("react").ReactElement | undefined; +} & import("react").RefAttributes<HTMLButtonElement>, "ref"> & { + copiedDuration?: number | undefined; +} & import("react").RefAttributes<HTMLButtonElement>>; + +declare namespace ActionBarPrimitiveEdit { + type Element = ActionButtonElement; + type Props = ActionButtonProps<typeof useActionBarEdit>; +} + +declare const ActionBarPrimitiveEdit: import("react").ForwardRefExoticComponent<Omit<Omit<import("react").ClassAttributes<HTMLButtonElement> & import("react").ButtonHTMLAttributes<HTMLButtonElement> & { + asChild?: boolean; +}, "ref"> & { + render?: import("react").ReactElement | undefined; +} & import("react").RefAttributes<HTMLButtonElement>, "ref"> & import("react").RefAttributes<HTMLButtonElement>>; + +declare namespace ActionBarPrimitiveExportMarkdown { + type Element = HTMLButtonElement; + type Props = ActionButtonProps<typeof useActionBarExportMarkdown>; +} + +declare const ActionBarPrimitiveExportMarkdown: import("react").ForwardRefExoticComponent<Omit<Omit<import("react").ClassAttributes<HTMLButtonElement> & import("react").ButtonHTMLAttributes<HTMLButtonElement> & { + asChild?: boolean; +}, "ref"> & { + render?: import("react").ReactElement | undefined; +} & import("react").RefAttributes<HTMLButtonElement>, "ref"> & { + filename?: string | undefined; + onExport?: ((content: string) => void | Promise<void>) | undefined; +} & import("react").RefAttributes<HTMLButtonElement>>; + +declare namespace ActionBarPrimitiveFeedbackNegative { + type Element = HTMLButtonElement; + type Props = ActionButtonProps<typeof useActionBarFeedbackNegative>; +} + +declare const ActionBarPrimitiveFeedbackNegative: import("react").ForwardRefExoticComponent<Omit<Omit<import("react").ClassAttributes<HTMLButtonElement> & import("react").ButtonHTMLAttributes<HTMLButtonElement> & { + asChild?: boolean; +}, "ref"> & { + render?: import("react").ReactElement | undefined; +} & import("react").RefAttributes<HTMLButtonElement>, "ref"> & import("react").RefAttributes<HTMLButtonElement>>; + +declare namespace ActionBarPrimitiveFeedbackPositive { + type Element = HTMLButtonElement; + type Props = ActionButtonProps<typeof useActionBarFeedbackPositive>; +} + +declare const ActionBarPrimitiveFeedbackPositive: import("react").ForwardRefExoticComponent<Omit<Omit<import("react").ClassAttributes<HTMLButtonElement> & import("react").ButtonHTMLAttributes<HTMLButtonElement> & { + asChild?: boolean; +}, "ref"> & { + render?: import("react").ReactElement | undefined; +} & import("react").RefAttributes<HTMLButtonElement>, "ref"> & import("react").RefAttributes<HTMLButtonElement>>; + +declare namespace ActionBarPrimitiveReload { + type Element = ActionButtonElement; + type Props = ActionButtonProps<typeof useActionBarReload>; +} + +declare const ActionBarPrimitiveReload: import("react").ForwardRefExoticComponent<Omit<Omit<import("react").ClassAttributes<HTMLButtonElement> & import("react").ButtonHTMLAttributes<HTMLButtonElement> & { + asChild?: boolean; +}, "ref"> & { + render?: import("react").ReactElement | undefined; +} & import("react").RefAttributes<HTMLButtonElement>, "ref"> & import("react").RefAttributes<HTMLButtonElement>>; + +declare namespace ActionBarPrimitiveRoot { + type Element = ComponentRef<typeof Primitive$1.div>; + type Props = PrimitiveDivProps$5 & { + hideWhenRunning?: boolean | undefined; + autohide?: "always" | "not-last" | "never" | undefined; + autohideFloat?: "always" | "single-branch" | "never" | undefined; + }; +} + +declare const ActionBarPrimitiveRoot: import("react").ForwardRefExoticComponent<Omit<Omit<import("react").ClassAttributes<HTMLDivElement> & import("react").HTMLAttributes<HTMLDivElement> & { + asChild?: boolean; +}, "ref"> & { + render?: import("react").ReactElement | undefined; +} & import("react").RefAttributes<HTMLDivElement>, "ref"> & { + hideWhenRunning?: boolean | undefined; + autohide?: "always" | "not-last" | "never" | undefined; + autohideFloat?: "always" | "single-branch" | "never" | undefined; +} & import("react").RefAttributes<HTMLDivElement>>; + +declare namespace ActionBarPrimitiveSpeak { + type Element = ActionButtonElement; + type Props = ActionButtonProps<typeof useActionBarSpeak>; +} + +declare const ActionBarPrimitiveSpeak: import("react").ForwardRefExoticComponent<Omit<Omit<import("react").ClassAttributes<HTMLButtonElement> & import("react").ButtonHTMLAttributes<HTMLButtonElement> & { + asChild?: boolean; +}, "ref"> & { + render?: import("react").ReactElement | undefined; +} & import("react").RefAttributes<HTMLButtonElement>, "ref"> & import("react").RefAttributes<HTMLButtonElement>>; + +declare namespace ActionBarPrimitiveStopSpeaking { + type Element = HTMLButtonElement; + type Props = ActionButtonProps<typeof useActionBarStopSpeaking>; +} + +declare const ActionBarPrimitiveStopSpeaking: import("react").ForwardRefExoticComponent<Omit<Omit<import("react").ClassAttributes<HTMLButtonElement> & import("react").ButtonHTMLAttributes<HTMLButtonElement> & { + asChild?: boolean; +}, "ref"> & { + render?: import("react").ReactElement | undefined; +} & import("react").RefAttributes<HTMLButtonElement>, "ref"> & import("react").RefAttributes<HTMLButtonElement>>; + +type ActionButtonElement = ComponentRef<typeof Primitive$1.button>; + +type ActionButtonProps<THook> = PrimitiveButtonProps & (THook extends ((props: infer TProps) => unknown) ? TProps : never); + +type AddMessageCommand = { + readonly type: "add-message"; + readonly message: UserMessage | AssistantMessage; + readonly parentId: string | null; + readonly sourceId: string | null; +}; + +type AddToolResultCommand = { + readonly type: "add-tool-result"; + readonly toolCallId: string; + readonly toolName: string; + readonly result: ReadonlyJSONValue; + readonly isError: boolean; + readonly artifact?: ReadonlyJSONValue; + readonly modelContent?: readonly ToolModelContentPart[]; +}; + +type AddToolResultOptions = { + messageId: string; + toolName: string; + toolCallId: string; + result: ReadonlyJSONValue; + isError: boolean; + artifact?: ReadonlyJSONValue | undefined; + modelContent?: readonly ToolModelContentPart[] | undefined; +}; + +type AncestorsOf<K extends ClientNames, Seen extends ClientNames = never> = K extends Seen ? never : ParentOf<K> extends never ? never : ParentOf<K> | AncestorsOf<ParentOf<K>, Seen | K>; + +type AppendMessage = Omit<ThreadMessage, "id"> & { + parentId: string | null; + sourceId: string | null; + runConfig: RunConfig | undefined; + startRun?: boolean | undefined; + steer?: boolean | undefined; +}; + +type AsNumber<K> = K extends `${infer N extends number}` ? N | K : never; + +declare namespace Assistant { + interface Commands { + } + interface ExternalState { + } +} + +type AssistantClient = { + [K in ClientNames]: AssistantClientAccessor<K>; +} & { + subscribe(listener: () => void): Unsubscribe$1; + on<TEvent extends AssistantEventName>(selector: AssistantEventSelector<TEvent>, callback: AssistantEventCallback<TEvent>): Unsubscribe$1; +}; + +type AssistantClientAccessor<K extends ClientNames> = (() => ClientSchemas[K]["methods"]) & (ClientMeta<K> | { + source: "root"; + query: Record<string, never>; +} | { + source: null; + query: null; +}) & { + name: K; +}; + +declare class AssistantCloud { + readonly threads: AssistantCloudThreads; + readonly auth: { + tokens: AssistantCloudAuthTokens; + }; + readonly runs: AssistantCloudRuns; + readonly files: AssistantCloudFiles; + readonly telemetry: AssistantCloudTelemetryConfig; + constructor(config: AssistantCloudConfig); +} + +declare class AssistantCloudAPI { + _auth: AssistantCloudAuthStrategy; + _baseUrl: string; + constructor(config: AssistantCloudConfig); + initializeAuth(): Promise<boolean>; + makeRawRequest(endpoint: string, options?: MakeRequestOptions): Promise<Response>; + makeRequest(endpoint: string, options?: MakeRequestOptions): Promise<any>; +} + +type AssistantCloudAuthStrategy = { + readonly strategy: "anon" | "api-key" | "jwt"; + getAuthHeaders(): Promise<Record<string, string> | false>; + readAuthHeaders(headers: Headers): void; +}; + +declare class AssistantCloudAuthTokens { + private cloud; + constructor(cloud: AssistantCloudAPI); + create(): Promise<AssistantCloudAuthTokensCreateResponse>; +} + +type AssistantCloudAuthTokensCreateResponse = { + token: string; +}; + +type AssistantCloudConfig = ({ + baseUrl: string; + authToken: () => Promise<string | null>; +} | { + baseUrl?: string; + apiKey: string; + userId: string; + workspaceId: string; +} | { + baseUrl: string; + anonymous: true; +}) & { + telemetry?: boolean | AssistantCloudTelemetryConfig; +}; + +declare class AssistantCloudFiles { + private cloud; + constructor(cloud: AssistantCloudAPI); + pdfToImages(body: PdfToImagesRequestBody): Promise<PdfToImagesResponse>; + generatePresignedUploadUrl(body: GeneratePresignedUploadUrlRequestBody): Promise<GeneratePresignedUploadUrlResponse>; +} + +type AssistantCloudMessageCreateResponse = { + message_id: string; +}; + +type AssistantCloudRunReport = { + thread_id: string; + status: "completed" | "error" | "incomplete"; + total_steps?: number; + tool_calls?: ReportToolCall[]; + steps?: { + input_tokens?: number; + output_tokens?: number; + reasoning_tokens?: number; + cached_input_tokens?: number; + tool_calls?: ReportToolCall[]; + start_ms?: number; + end_ms?: number; + }[]; + input_tokens?: number; + output_tokens?: number; + reasoning_tokens?: number; + cached_input_tokens?: number; + model_id?: string; + provider_type?: string; + duration_ms?: number; + output_text?: string; + metadata?: Record<string, unknown>; +}; + +declare class AssistantCloudRuns { + private cloud; + constructor(cloud: AssistantCloudAPI); + __internal_getAssistantOptions(assistantId: string): { + api: string; + headers: () => Promise<{ + Accept: string; + }>; + body: { + assistant_id: string; + response_format: string; + thread_id: string; + }; + }; + stream(body: AssistantCloudRunsStreamBody): Promise<AssistantStream>; + report(body: AssistantCloudRunReport): Promise<{ + run_id: string; + }>; +} + +type AssistantCloudRunsStreamBody = { + thread_id: string; + assistant_id: "system/thread_title"; + messages: readonly unknown[]; +}; + +type AssistantCloudTelemetryConfig = { + enabled?: boolean; + beforeReport?: (report: AssistantCloudRunReport) => AssistantCloudRunReport | null; +}; + +type AssistantCloudThreadMessageCreateBody = { + parent_id: string | null; + format: "aui/v0" | string; + content: ReadonlyJSONObject; +}; + +type AssistantCloudThreadMessageListQuery = { + format?: string; +}; + +type AssistantCloudThreadMessageListResponse = { + messages: CloudMessage[]; +}; + +type AssistantCloudThreadMessageUpdateBody = { + content: ReadonlyJSONObject; +}; + +declare class AssistantCloudThreadMessages { + private cloud; + constructor(cloud: AssistantCloudAPI); + list(threadId: string, query?: AssistantCloudThreadMessageListQuery): Promise<AssistantCloudThreadMessageListResponse>; + create(threadId: string, body: AssistantCloudThreadMessageCreateBody): Promise<AssistantCloudMessageCreateResponse>; + update(threadId: string, messageId: string, body: AssistantCloudThreadMessageUpdateBody): Promise<void>; +} + +declare class AssistantCloudThreads { + private cloud; + readonly messages: AssistantCloudThreadMessages; + constructor(cloud: AssistantCloudAPI); + list(query?: AssistantCloudThreadsListQuery): Promise<AssistantCloudThreadsListResponse>; + get(threadId: string): Promise<CloudThread>; + create(body: AssistantCloudThreadsCreateBody): Promise<AssistantCloudThreadsCreateResponse>; + update(threadId: string, body: AssistantCloudThreadsUpdateBody): Promise<void>; + delete(threadId: string): Promise<void>; +} + +type AssistantCloudThreadsCreateBody = { + title?: string | undefined; + last_message_at: Date; + metadata?: unknown | undefined; + external_id?: string | undefined; +}; + +type AssistantCloudThreadsCreateResponse = { + thread_id: string; +}; + +type AssistantCloudThreadsListQuery = { + is_archived?: boolean; + limit?: number; + after?: string; +}; + +type AssistantCloudThreadsListResponse = { + threads: CloudThread[]; +}; + +type AssistantCloudThreadsUpdateBody = { + title?: string | undefined; + last_message_at?: Date | undefined; + metadata?: unknown | undefined; + is_archived?: boolean | undefined; +}; + +type AssistantContextConfig = { + getContext: () => string; + disabled?: boolean | undefined; +}; + +type AssistantDataUI = FC & { + unstable_data: AssistantDataUIProps; +}; + +type AssistantDataUIProps<T = any> = { + name: string; + render: DataMessagePartComponent<T>; +}; + +type AssistantEventCallback<TEvent extends AssistantEventName> = (payload: AssistantEventPayload[TEvent]) => void; + +type AssistantEventName = keyof AssistantEventPayload; + +type AssistantEventPayload = ClientEventMap & { + "*": WildcardPayload; +}; + +type AssistantEventScope<TEvent extends AssistantEventName> = "*" | EventSource<TEvent> | (EventSource<TEvent> extends ClientNames ? AncestorsOf<EventSource<TEvent>> : never); + +type AssistantEventSelector<TEvent extends AssistantEventName> = TEvent | { + scope: AssistantEventScope<TEvent>; + event: TEvent; +}; + +declare class AssistantFrameHost implements ModelContextProvider { + private _context; + private _subscribers; + private _pendingRequests; + private _requestCounter; + private _iframeWindow; + private _targetOrigin; + constructor(iframeWindow: Window, targetOrigin?: string); + private handleMessage; + private updateContext; + private callTool; + private sendRequest; + private requestContext; + private notifySubscribers; + getModelContext(): ModelContext$1; + subscribe(callback: () => void): Unsubscribe; + dispose(): void; +} + +declare class AssistantFrameProvider { + private static _instance; + private _providers; + private _providerUnsubscribes; + private _targetOrigin; + private constructor(); + private static getInstance; + private handleMessage; + private handleToolCall; + private sendMessage; + private getModelContext; + private broadcastUpdate; + static addModelContextProvider(provider: ModelContextProvider, targetOrigin?: string): Unsubscribe; + static dispose(): void; +} + +type AssistantInstructionsConfig = { + disabled?: boolean | undefined; + instruction: string; +}; + +type AssistantInteractableProps = { + description: string; + stateSchema: InteractableStateSchema; + initialState: unknown; + id?: string; + selected?: boolean; +}; + +type AssistantMessage = { + readonly role: "assistant"; + readonly parts: readonly TextPart[]; +}; + +declare namespace AssistantModalPrimitiveAnchor { + type Element = ComponentRef<typeof Popover.Anchor>; + type Props = WithRenderPropProps<typeof Popover.Anchor>; +} + +declare const AssistantModalPrimitiveAnchor: import("react").ForwardRefExoticComponent<Omit<Popover.PopoverAnchorProps & import("react").RefAttributes<HTMLDivElement>, "ref"> & { + render?: import("react").ReactElement | undefined; +} & import("react").RefAttributes<HTMLDivElement>>; + +declare namespace AssistantModalPrimitiveContent { + type Element = ComponentRef<typeof Popover.Content>; + type Props = WithRenderPropProps<typeof Popover.Content> & { + portalProps?: ComponentPropsWithoutRef<typeof Popover.Portal> | undefined; + dissmissOnInteractOutside?: boolean | undefined; + }; +} + +declare const AssistantModalPrimitiveContent: import("react").ForwardRefExoticComponent<Omit<Popover.PopoverContentProps & import("react").RefAttributes<HTMLDivElement>, "ref"> & { + render?: import("react").ReactElement | undefined; +} & { + portalProps?: ComponentPropsWithoutRef<typeof Popover.Portal> | undefined; + dissmissOnInteractOutside?: boolean | undefined; +} & import("react").RefAttributes<HTMLDivElement>>; + +declare namespace AssistantModalPrimitiveRoot { + type Props = Popover.PopoverProps & { + unstable_openOnRunStart?: boolean | undefined; + }; +} + +declare const AssistantModalPrimitiveRoot: FC<AssistantModalPrimitiveRoot.Props>; + +declare namespace AssistantModalPrimitiveTrigger { + type Element = ComponentRef<typeof Popover.Trigger>; + type Props = WithRenderPropProps<typeof Popover.Trigger>; +} + +declare const AssistantModalPrimitiveTrigger: import("react").ForwardRefExoticComponent<Omit<Popover.PopoverTriggerProps & import("react").RefAttributes<HTMLButtonElement>, "ref"> & { + render?: import("react").ReactElement | undefined; +} & import("react").RefAttributes<HTMLButtonElement>>; + +type AssistantRuntime = { + readonly threads: ThreadListRuntime; + readonly thread: ThreadRuntime; + registerModelContextProvider(provider: ModelContextProvider): Unsubscribe; +}; + +type AssistantRuntimeCore = { + readonly threads: ThreadListRuntimeCore; + registerModelContextProvider: (provider: ModelContextProvider) => Unsubscribe; + getModelContextProvider: () => ModelContextProvider; + readonly RenderComponent?: ((...args: any[]) => unknown) | undefined; +}; + +declare class AssistantRuntimeImpl implements AssistantRuntime { + private readonly _core; + readonly threads: ThreadListRuntimeImpl; + readonly _thread: ThreadRuntime; + constructor(_core: AssistantRuntimeCore); + protected __internal_bindMethods(): void; + get thread(): ThreadRuntime; + registerModelContextProvider(provider: ModelContextProvider): Unsubscribe; +} + +declare namespace AssistantRuntimeProvider { + type Props = PropsWithChildren<{ + runtime: AssistantRuntime; + aui?: AssistantClient; + }>; +} + +declare const AssistantRuntimeProvider: import("react").NamedExoticComponent<AssistantRuntimeProvider.Props>; + +type AssistantState = { + [K in ClientNames]: ClientSchemas[K]["methods"] extends { + getState: () => infer S; + } ? S : never; +}; + +type AssistantStream = ReadableStream<AssistantStreamChunk>; + +declare const AssistantStream: { + toResponse(stream: AssistantStream, transformer: AssistantStreamEncoder): Response; + fromResponse(response: Response, transformer: ReadableWritablePair<AssistantStreamChunk, Uint8Array<ArrayBuffer>>): ReadableStream<AssistantStreamChunk>; + toByteStream(stream: AssistantStream, transformer: ReadableWritablePair<Uint8Array<ArrayBuffer>, AssistantStreamChunk>): ReadableStream<Uint8Array<ArrayBuffer>>; + fromByteStream(readable: ReadableStream<Uint8Array<ArrayBuffer>>, transformer: ReadableWritablePair<AssistantStreamChunk, Uint8Array<ArrayBuffer>>): ReadableStream<AssistantStreamChunk>; +}; + +type AssistantStreamChunk = { + readonly path: readonly number[]; +} & ({ + readonly type: "part-start"; + readonly part: PartInit; +} | { + readonly type: "part-finish"; +} | { + readonly type: "tool-call-args-text-finish"; +} | { + readonly type: "text-delta"; + readonly textDelta: string; +} | { + readonly type: "annotations"; + readonly annotations: ReadonlyJSONValue[]; +} | { + readonly type: "data"; + readonly data: ReadonlyJSONValue[]; +} | { + readonly type: "step-start"; + readonly messageId: string; +} | { + readonly type: "step-finish"; + readonly finishReason: "content-filter" | "error" | "length" | "other" | "stop" | "tool-calls" | "unknown"; + readonly usage: { + readonly inputTokens: number; + readonly outputTokens: number; + }; + readonly isContinued: boolean; +} | { + readonly type: "message-finish"; + readonly finishReason: "content-filter" | "error" | "length" | "other" | "stop" | "tool-calls" | "unknown"; + readonly usage: { + readonly inputTokens: number; + readonly outputTokens: number; + }; +} | { + readonly type: "result"; + readonly artifact?: ReadonlyJSONValue; + readonly result: ReadonlyJSONValue; + readonly isError: boolean; + readonly modelContent?: readonly ToolModelContentPart[]; + readonly messages?: ReadonlyJSONValue; +} | { + readonly type: "error"; + readonly error: string; +} | { + readonly type: "update-state"; + readonly operations: ObjectStreamOperation[]; +}); + +type AssistantStreamEncoder = ReadableWritablePair<Uint8Array<ArrayBuffer>, AssistantStreamChunk> & { + headers?: Headers; +}; + +type AssistantTool = FC & { + unstable_tool: AssistantToolProps<any, any>; +}; + +type AssistantToolProps<TArgs extends Record<string, unknown>, TResult> = AssistantToolProps$1<TArgs, TResult> & { + render?: ToolCallMessagePartComponent<TArgs, TResult> | undefined; + renderText?: ToolCallText<TArgs, TResult> | undefined; +}; + +type AssistantToolProps$1<TArgs extends Record<string, unknown>, TResult> = Tool<TArgs, TResult> & { + toolName: string; + render?: unknown; +}; + +type AssistantToolUI = FC & { + unstable_tool: AssistantToolUIProps<any, any>; +}; + +type AssistantToolUIProps<TArgs, TResult> = { + toolName: string; + render: ToolCallMessagePartComponent<TArgs, TResult>; + display?: "inline" | "standalone"; +}; + +type AssistantTransportCommand = AddMessageCommand | AddToolResultCommand | UserCommands; + +type AssistantTransportConnectionMetadata = { + pendingCommands: AssistantTransportCommand[]; + isSending: boolean; + toolStatuses: Record<string, ToolExecutionStatus>; +}; + +type AssistantTransportOptions<T> = { + initialState: T; + api: string; + resumeApi?: string; + protocol?: AssistantTransportProtocol; + converter: AssistantTransportStateConverter<T>; + headers: HeadersValue | (() => Promise<HeadersValue>); + body?: object | (() => Promise<object | undefined>); + prepareSendCommandsRequest?: (body: SendCommandsRequestBody) => Record<string, unknown> | Promise<Record<string, unknown>>; + onResponse?: (response: Response) => void; + onFinish?: () => void; + onError?: (error: Error, params: { + commands: AssistantTransportCommand[]; + updateState: (updater: (state: T) => T) => void; + }) => void | Promise<void>; + onCancel?: (params: { + commands: AssistantTransportCommand[]; + updateState: (updater: (state: T) => T) => void; + error?: Error; + }) => void; + capabilities?: { + edit?: boolean; + }; + adapters?: { + attachments?: AttachmentAdapter | undefined; + history?: ThreadHistoryAdapter | undefined; + }; +}; + +type AssistantTransportProtocol = "assistant-transport" | "data-stream"; + +type AssistantTransportState = { + readonly messages: readonly ThreadMessage[]; + readonly state?: ReadonlyJSONValue; + readonly isRunning: boolean; +}; + +type AssistantTransportStateConverter<T> = (state: T, connectionMetadata: AssistantTransportConnectionMetadata) => AssistantTransportState; + +type AsyncIterableStream<T> = AsyncIterable<T> & ReadableStream<T>; + +type Attachment = PendingAttachment | CompleteAttachment; + +type AttachmentAdapter = { + accept: string; + add(state: { + file: File; + }): Promise<PendingAttachment> | AsyncGenerator<PendingAttachment, void>; + remove(attachment: Attachment): Promise<void>; + send(attachment: PendingAttachment): Promise<CompleteAttachment>; +}; + +type AttachmentAddErrorEvent = { + readonly reason: AttachmentAddErrorReason; + readonly message: string; + readonly attachmentId?: string; + readonly error?: Error; +}; + +type AttachmentAddErrorReason = "adapter-error" | "no-adapter" | "not-accepted"; + +declare namespace AttachmentPrimitiveName { + type Props = Record<string, never>; +} + +declare const AttachmentPrimitiveName: FC<AttachmentPrimitiveName.Props>; + +declare namespace AttachmentPrimitiveRemove { + type Element = ActionButtonElement; + type Props = ActionButtonProps<typeof useAttachmentRemove>; +} + +declare const AttachmentPrimitiveRemove: import("react").ForwardRefExoticComponent<Omit<Omit<import("react").ClassAttributes<HTMLButtonElement> & import("react").ButtonHTMLAttributes<HTMLButtonElement> & { + asChild?: boolean; +}, "ref"> & { + render?: import("react").ReactElement | undefined; +} & import("react").RefAttributes<HTMLButtonElement>, "ref"> & import("react").RefAttributes<HTMLButtonElement>>; + +declare namespace AttachmentPrimitiveRoot { + type Element = ComponentRef<typeof Primitive$1.div>; + type Props = PrimitiveDivProps$4; +} + +declare const AttachmentPrimitiveRoot: import("react").ForwardRefExoticComponent<Omit<Omit<import("react").ClassAttributes<HTMLDivElement> & import("react").HTMLAttributes<HTMLDivElement> & { + asChild?: boolean; +}, "ref"> & { + render?: import("react").ReactElement | undefined; +} & import("react").RefAttributes<HTMLDivElement>, "ref"> & import("react").RefAttributes<HTMLDivElement>>; + +declare namespace AttachmentPrimitiveThumb { + type Element = ComponentRef<typeof Primitive$1.div>; + type Props = PrimitiveDivProps$3; +} + +declare const AttachmentPrimitiveThumb: import("react").ForwardRefExoticComponent<Omit<Omit<import("react").ClassAttributes<HTMLDivElement> & import("react").HTMLAttributes<HTMLDivElement> & { + asChild?: boolean; +}, "ref"> & { + render?: import("react").ReactElement | undefined; +} & import("react").RefAttributes<HTMLDivElement>, "ref"> & import("react").RefAttributes<HTMLDivElement>>; + +type AttachmentRuntime<TSource extends AttachmentRuntimeSource = AttachmentRuntimeSource> = { + readonly path: AttachmentRuntimePath & { + attachmentSource: TSource; + }; + readonly source: TSource; + getState(): AttachmentState & { + source: TSource; + }; + remove(): Promise<void>; + subscribe(callback: () => void): Unsubscribe; +}; + +declare abstract class AttachmentRuntimeImpl<Source extends AttachmentRuntimeSource = AttachmentRuntimeSource> implements AttachmentRuntime { + private _core; + get path(): AttachmentRuntimePath & { + attachmentSource: Source; + }; + abstract get source(): Source; + constructor(_core: AttachmentSnapshotBinding<Source>); + protected __internal_bindMethods(): void; + getState(): AttachmentState & { + source: Source; + }; + abstract remove(): Promise<void>; + subscribe(callback: () => void): Unsubscribe; +} + +type AttachmentRuntimePath = ((MessageRuntimePath & { + readonly attachmentSource: "edit-composer" | "message"; +}) | (ThreadRuntimePath & { + readonly attachmentSource: "thread-composer"; +})) & { + readonly attachmentSelector: { + readonly type: "index"; + readonly index: number; + } | { + readonly type: "index"; + readonly index: number; + } | { + readonly type: "index"; + readonly index: number; + }; +}; + +type AttachmentRuntimeSource = AttachmentState["source"]; + +type AttachmentSnapshotBinding<Source extends AttachmentRuntimeSource> = SubscribableWithState<AttachmentState & { + source: Source; +}, AttachmentRuntimePath & { + attachmentSource: Source; +}>; + +type AttachmentState = ThreadComposerAttachmentState | EditComposerAttachmentState | MessageAttachmentState; + +type AttachmentStatus = PendingAttachmentStatus | CompleteAttachmentStatus; + +declare namespace AuiIf { + type Props = PropsWithChildren<{ + condition: AuiIf.Condition; + }>; + type Condition = (state: AssistantState) => boolean; +} + +declare const AuiIf: FC<AuiIf.Props>; + +declare const AuiProvider: (_param0: { + value: AssistantClient; + children: React.ReactNode; +}) => React.ReactElement; + +type AuiToolOverride<TArgs extends Record<string, unknown> = Record<string, unknown>, TResult = unknown> = Partial<Tool<TArgs, TResult>>; + +type AuiToolOverrides = Record<string, AuiToolOverride<any, any>>; + +type BackendTool<TArgs extends Record<string, unknown> = Record<string, unknown>, TResult = unknown> = ToolBase<TArgs, TResult> & { + type: "backend"; + description?: undefined; + parameters?: undefined; + disabled?: undefined; + execute?: undefined; + toModelOutput?: undefined; + experimental_onSchemaValidationError?: undefined; + providerOptions?: undefined; +}; + +type BackendToolDeclaration<TArgs extends Record<string, unknown> = Record<string, unknown>, TResult = unknown> = ToolBase<TArgs, TResult> & { + type: "backend"; + description?: string | undefined; + parameters?: StandardSchemaV1<TArgs> | JSONSchema7 | undefined; + disabled?: boolean; + execute?: ToolExecuteFunction<TArgs, TResult>; + toModelOutput?: ToolModelOutputFunction<TArgs, TResult>; + experimental_onSchemaValidationError?: OnSchemaValidationErrorFunction<TResult>; + providerOptions?: ProviderOptions; +}; + +declare abstract class BaseAssistantRuntimeCore implements AssistantRuntimeCore { + protected readonly _contextProvider: CompositeContextProvider; + abstract get threads(): ThreadListRuntimeCore; + registerModelContextProvider(provider: ModelContextProvider): Unsubscribe; + getModelContextProvider(): ModelContextProvider; +} + +type BaseAttachment = { + id: string; + type: "image" | "document" | "file" | (string & {}); + name: string; + contentType?: string | undefined; + file?: File; + content?: ThreadUserMessagePart[]; +}; + +declare abstract class BaseComposerRuntimeCore extends BaseSubscribable implements ComposerRuntimeCore { + readonly isEditing = true; + protected abstract getAttachmentAdapter(): AttachmentAdapter | undefined; + protected abstract getDictationAdapter(): DictationAdapter | undefined; + protected enrichWithComposerMetadata<T extends { + metadata?: { + custom?: Record<string, unknown>; + }; + }>(message: T, composerMetadata: Record<string, unknown> | undefined): T; + get attachmentAccept(): string; + private _attachments; + get attachments(): readonly Attachment[]; + protected setAttachments(value: readonly Attachment[]): void; + abstract get canCancel(): boolean; + abstract get canSend(): boolean; + get isEmpty(): boolean; + private _text; + get text(): string; + private _role; + get role(): "assistant" | "system" | "user"; + private _runConfig; + get runConfig(): RunConfig; + private _quote; + get quote(): QuoteInfo | undefined; + setQuote(quote: QuoteInfo | undefined): void; + setText(value: string): void; + setRole(role: MessageRole): void; + setRunConfig(runConfig: RunConfig): void; + private _emptyTextAndAttachments; + private _onClearAttachments; + reset(): Promise<void>; + clearAttachments(): Promise<void>; + send(options?: SendOptions): Promise<void>; + cancel(): void; + get queue(): readonly QueueItemState[]; + steerQueueItem(_queueItemId: string): void; + removeQueueItem(_queueItemId: string): void; + protected abstract handleSend(message: Omit<AppendMessage, "parentId" | "sourceId">, options?: SendOptions): void; + protected abstract handleCancel(): void; + addAttachment(fileOrAttachment: File | CreateAttachment): Promise<void>; + private _safeEmitAttachmentAddError; + removeAttachment(attachmentId: string): Promise<void>; + private _dictation; + private _dictationSession; + private _dictationUnsubscribes; + private _dictationBaseText; + private _currentInterimText; + private _dictationSessionIdCounter; + private _activeDictationSessionId; + private _isCleaningDictation; + get dictation(): DictationState | undefined; + private _isActiveSession; + startDictation(): void; + stopDictation(): void; + private _cleanupDictation; + private _eventSubscribers; + protected _notifyEventSubscribers<E extends ComposerRuntimeEventType>(event: E, payload: ComposerRuntimeEventPayload[E]): void; + unstable_on<E extends ComposerRuntimeEventType>(event: E, callback: ComposerRuntimeEventCallback<E>): () => void; +} + +type BaseComposerState = { + readonly canCancel: boolean; + readonly canSend: boolean; + readonly isEditing: boolean; + readonly isEmpty: boolean; + readonly text: string; + readonly role: MessageRole; + readonly attachments: readonly Attachment[]; + readonly runConfig: RunConfig; + readonly attachmentAccept: string; + readonly dictation: DictationState | undefined; + readonly quote: QuoteInfo | undefined; + readonly queue: readonly QueueItemState[]; +}; + +declare class BaseSubscribable { + private _subscribers; + subscribe(callback: () => void): Unsubscribe; + waitForUpdate(): Promise<void>; + protected _notifySubscribers(): void; +} + +type BaseThreadMessage = { + readonly status?: ThreadAssistantMessage["status"]; + readonly metadata: { + readonly unstable_state?: ReadonlyJSONValue; + readonly unstable_annotations?: readonly ReadonlyJSONValue[]; + readonly unstable_data?: readonly ReadonlyJSONValue[]; + readonly steps?: readonly ThreadStep[]; + readonly submittedFeedback?: { + readonly type: "negative" | "positive"; + }; + readonly timing?: MessageTiming; + readonly isOptimistic?: boolean; + readonly custom: Record<string, unknown>; + }; + readonly attachments?: ThreadUserMessage["attachments"]; +}; + +declare namespace BranchPickerPrimitiveCount { + type Props = Record<string, never>; +} + +declare const BranchPickerPrimitiveCount: FC<BranchPickerPrimitiveCount.Props>; + +declare namespace BranchPickerPrimitiveNext { + type Element = ActionButtonElement; + type Props = ActionButtonProps<typeof useBranchPickerNext>; +} + +declare const BranchPickerPrimitiveNext: import("react").ForwardRefExoticComponent<Omit<Omit<import("react").ClassAttributes<HTMLButtonElement> & import("react").ButtonHTMLAttributes<HTMLButtonElement> & { + asChild?: boolean; +}, "ref"> & { + render?: import("react").ReactElement | undefined; +} & import("react").RefAttributes<HTMLButtonElement>, "ref"> & import("react").RefAttributes<HTMLButtonElement>>; + +declare namespace BranchPickerPrimitiveNumber { + type Props = Record<string, never>; +} + +declare const BranchPickerPrimitiveNumber: FC<BranchPickerPrimitiveNumber.Props>; + +declare namespace BranchPickerPrimitivePrevious { + type Element = ActionButtonElement; + type Props = ActionButtonProps<typeof useBranchPickerPrevious>; +} + +declare const BranchPickerPrimitivePrevious: import("react").ForwardRefExoticComponent<Omit<Omit<import("react").ClassAttributes<HTMLButtonElement> & import("react").ButtonHTMLAttributes<HTMLButtonElement> & { + asChild?: boolean; +}, "ref"> & { + render?: import("react").ReactElement | undefined; +} & import("react").RefAttributes<HTMLButtonElement>, "ref"> & import("react").RefAttributes<HTMLButtonElement>>; + +declare namespace BranchPickerPrimitiveRoot { + type Element = ComponentRef<typeof Primitive$1.div>; + type Props = ComponentPropsWithoutRef<typeof Primitive$1.div> & { + hideWhenSingleBranch?: boolean | undefined; + }; +} + +declare const BranchPickerPrimitiveRoot: import("react").ForwardRefExoticComponent<Omit<Omit<import("react").ClassAttributes<HTMLDivElement> & import("react").HTMLAttributes<HTMLDivElement> & { + asChild?: boolean; +}, "ref"> & { + render?: import("react").ReactElement | undefined; +} & import("react").RefAttributes<HTMLDivElement>, "ref"> & { + hideWhenSingleBranch?: boolean | undefined; +} & import("react").RefAttributes<HTMLDivElement>>; + +declare const ChainOfThoughtByIndicesProvider: FC<PropsWithChildren<{ + startIndex: number; + endIndex: number; +}>>; + +declare const ChainOfThoughtClient: Resource<ClientOutput<"chainOfThought">, [ + { + parts: readonly ChainOfThoughtPart[]; + getMessagePart: (selector: { + index: number; + }) => PartMethods; + } +]>; + +type ChainOfThoughtPart = Extract<PartState, { + type: "tool-call"; +} | { + type: "reasoning"; +}>; + +type ChainOfThoughtPartsComponentConfig = { + Reasoning?: ReasoningMessagePartComponent | undefined; + tools?: { + Fallback?: ToolCallMessagePartComponent | undefined; + }; + Layout?: ComponentType<PropsWithChildren> | undefined; +}; + +declare namespace ChainOfThoughtPrimitiveAccordionTrigger { + type Element = ActionButtonElement; + type Props = ActionButtonProps<typeof useChainOfThoughtAccordionTrigger>; +} + +declare const ChainOfThoughtPrimitiveAccordionTrigger: import("react").ForwardRefExoticComponent<Omit<Omit<import("react").ClassAttributes<HTMLButtonElement> & import("react").ButtonHTMLAttributes<HTMLButtonElement> & { + asChild?: boolean; +}, "ref"> & { + render?: import("react").ReactElement | undefined; +} & import("react").RefAttributes<HTMLButtonElement>, "ref"> & import("react").RefAttributes<HTMLButtonElement>>; + +declare namespace ChainOfThoughtPrimitiveParts { + type Props = { + components?: ChainOfThoughtPartsComponentConfig; + children?: never; + } | { + children: (value: { + part: PartState; + }) => ReactNode; + components?: never; + }; +} + +declare const ChainOfThoughtPrimitiveParts: FC<ChainOfThoughtPrimitiveParts.Props>; + +declare namespace ChainOfThoughtPrimitiveRoot { + type Element = ComponentRef<typeof Primitive$1.div>; + type Props = PrimitiveDivProps$2; +} + +declare const ChainOfThoughtPrimitiveRoot: import("react").ForwardRefExoticComponent<Omit<Omit<import("react").ClassAttributes<HTMLDivElement> & import("react").HTMLAttributes<HTMLDivElement> & { + asChild?: boolean; +}, "ref"> & { + render?: import("react").ReactElement | undefined; +} & import("react").RefAttributes<HTMLDivElement>, "ref"> & import("react").RefAttributes<HTMLDivElement>>; + +type ChatModelAdapter = { + run(options: ChatModelRunOptions): Promise<ChatModelRunResult> | AsyncGenerator<ChatModelRunResult, void>; +}; + +type ChatModelRunOptions = { + readonly messages: readonly ThreadMessage[]; + readonly runConfig: RunConfig; + readonly abortSignal: AbortSignal; + readonly context: ModelContext$1; + readonly unstable_assistantMessageId?: string | undefined; + readonly unstable_threadId?: string | undefined; + readonly unstable_parentId?: string | null | undefined; + unstable_getMessage(): ThreadMessage; +}; + +type ChatModelRunResult = { + readonly content?: readonly ThreadAssistantMessagePart[] | undefined; + readonly status?: MessageStatus | undefined; + readonly metadata?: { + readonly unstable_state?: ReadonlyJSONValue; + readonly unstable_annotations?: readonly ReadonlyJSONValue[] | undefined; + readonly unstable_data?: readonly ReadonlyJSONValue[] | undefined; + readonly steps?: readonly ThreadStep[] | undefined; + readonly timing?: MessageTiming | undefined; + readonly custom?: Record<string, unknown> | undefined; + }; +}; + +type ChatModelRunUpdate = { + readonly content: readonly ThreadAssistantMessagePart[]; + readonly metadata?: Record<string, unknown>; +}; + +type ClientElement<K extends ClientNames> = ResourceElement<ClientOutput<K>>; + +type ClientError<E extends string> = { + methods: Record<E, () => E>; + meta: { + source: ClientNames; + query: Record<E, E>; + }; + events: Record<`${E}.`, E>; +}; + +type ClientEventMap = UnionToIntersection<{ + [K in ClientNames]: ClientEvents<K>; +}[ClientNames]>; + +type ClientEvents<K extends ClientNames> = "events" extends keyof ClientSchemas[K] ? ClientSchemas[K]["events"] extends ClientEventsType<K> ? ClientSchemas[K]["events"] : never : never; + +type ClientEventsType<K extends ClientNames> = Record<`${K}.${string}`, unknown>; + +type ClientMeta<K extends ClientNames> = "meta" extends keyof ClientSchemas[K] ? Pick<ClientSchemas[K]["meta"] extends ClientMetaType ? ClientSchemas[K]["meta"] : never, "query" | "source"> : never; + +type ClientMetaType = { + source: ClientNames; + query: Record<string, unknown>; +}; + +interface ClientMethods { + [key: string | symbol]: (...args: any[]) => any; +} + +type ClientNames = keyof ClientSchemas extends (infer U) ? U : never; + +type ClientOutput<K extends ClientNames> = ClientSchemas[K]["methods"] & ClientMethods; + +type ClientSchemas = keyof ScopeRegistry extends never ? { + "ERROR: No clients were defined": ClientError<"ERROR: No clients were defined">; +} : { + [K in keyof ScopeRegistry]: ValidateClient<K>; +}; + +declare class CloudFileAttachmentAdapter implements AttachmentAdapter { + private cloud; + accept: string; + constructor(cloud: AssistantCloud); + private uploadedUrls; + add(_param1: { + file: File; + }): AsyncGenerator<PendingAttachment, void>; + remove(attachment: Attachment): Promise<void>; + send(attachment: PendingAttachment): Promise<CompleteAttachment>; +} + +type CloudMessage = { + id: string; + parent_id: string | null; + height: number; + created_at: Date; + updated_at: Date; + format: "aui/v0" | string; + content: ReadonlyJSONObject; +}; + +type CloudThread = { + title: string; + last_message_at: Date; + metadata: unknown; + external_id: string | null; + id: string; + project_id: string; + created_at: Date; + updated_at: Date; + workspace_id: string; + is_archived: boolean; +}; + +type CloudThreadListAdapter = { + cloud: AssistantCloud; + runtimeHook: () => AssistantRuntime; + create?(): Promise<ThreadData>; + delete?(threadId: string): Promise<void>; +}; + +type CloudThreadListAdapterOptions = { + cloud?: AssistantCloud | undefined; + create?: (() => Promise<ThreadData$1>) | undefined; + delete?: ((threadId: string) => Promise<void>) | undefined; +}; + +type CompleteAttachment = BaseAttachment & { + status: CompleteAttachmentStatus; + content: ThreadUserMessagePart[]; +}; + +type CompleteAttachmentStatus = { + type: "complete"; +}; + +declare const ComposerAttachmentByIndexProvider: FC<PropsWithChildren<{ + index: number; +}>>; + +declare abstract class ComposerAttachmentRuntime<Source extends "edit-composer" | "thread-composer"> extends AttachmentRuntimeImpl<Source> { + private _composerApi; + constructor(core: AttachmentSnapshotBinding<Source>, _composerApi: ComposerRuntimeCoreBinding); + remove(): Promise<void>; +} + +type ComposerAttachmentsComponentConfig = { + Image?: ComponentType | undefined; + Document?: ComponentType | undefined; + File?: ComponentType | undefined; + Attachment?: ComponentType | undefined; +}; + +type ComposerIfFilters = { + editing: boolean | undefined; + dictation: boolean | undefined; +}; + +type ComposerInputPlugin = { + handleKeyDown(e: { + readonly key: string; + readonly shiftKey: boolean; + readonly ctrlKey?: boolean; + readonly metaKey?: boolean; + readonly nativeEvent?: { + isComposing?: boolean; + }; + preventDefault(): void; + }): boolean; + setCursorPosition(pos: number): void; +}; + +type ComposerInputPluginRegisterOptions = { + priority?: number; +}; + +type ComposerInputPluginRegistry = { + register(plugin: ComposerInputPlugin, opts?: ComposerInputPluginRegisterOptions): () => void; + getPlugins(): readonly ComposerInputPlugin[]; +}; + +declare namespace ComposerPrimitiveAddAttachment { + type Element = ActionButtonElement; + type Props = ActionButtonProps<typeof useComposerAddAttachment>; +} + +declare const ComposerPrimitiveAddAttachment: import("react").ForwardRefExoticComponent<Omit<Omit<import("react").ClassAttributes<HTMLButtonElement> & import("react").ButtonHTMLAttributes<HTMLButtonElement> & { + asChild?: boolean; +}, "ref"> & { + render?: import("react").ReactElement | undefined; +} & import("react").RefAttributes<HTMLButtonElement>, "ref"> & { + multiple?: boolean | undefined; +} & import("react").RefAttributes<HTMLButtonElement>>; + +declare namespace ComposerPrimitiveAttachmentByIndex { + type Props = { + index: number; + components?: ComposerAttachmentsComponentConfig; + }; +} + +declare const ComposerPrimitiveAttachmentByIndex: FC<ComposerPrimitiveAttachmentByIndex.Props>; + +declare namespace ComposerPrimitiveAttachmentDropzone { + type Element = HTMLDivElement; + type Props = React.HTMLAttributes<HTMLDivElement> & { + asChild?: boolean | undefined; + render?: ReactElement | undefined; + disabled?: boolean | undefined; + }; +} + +declare const ComposerPrimitiveAttachmentDropzone: React.ForwardRefExoticComponent<React.HTMLAttributes<HTMLDivElement> & { + asChild?: boolean | undefined; + render?: ReactElement | undefined; + disabled?: boolean | undefined; +} & React.RefAttributes<HTMLDivElement>>; + +declare namespace ComposerPrimitiveAttachments { + type Props = { + components: ComposerAttachmentsComponentConfig; + children?: never; + } | { + children: (value: { + attachment: Attachment; + }) => ReactNode; + components?: never; + }; +} + +declare const ComposerPrimitiveAttachments: FC<ComposerPrimitiveAttachments.Props>; + +declare namespace ComposerPrimitiveCancel { + type Element = ActionButtonElement; + type Props = ActionButtonProps<typeof useComposerCancel>; +} + +declare const ComposerPrimitiveCancel: import("react").ForwardRefExoticComponent<Omit<Omit<import("react").ClassAttributes<HTMLButtonElement> & import("react").ButtonHTMLAttributes<HTMLButtonElement> & { + asChild?: boolean; +}, "ref"> & { + render?: import("react").ReactElement | undefined; +} & import("react").RefAttributes<HTMLButtonElement>, "ref"> & import("react").RefAttributes<HTMLButtonElement>>; + +declare namespace ComposerPrimitiveDictate { + type Element = ActionButtonElement; + type Props = ActionButtonProps<typeof useComposerDictate>; +} + +declare const ComposerPrimitiveDictate: import("react").ForwardRefExoticComponent<Omit<Omit<import("react").ClassAttributes<HTMLButtonElement> & import("react").ButtonHTMLAttributes<HTMLButtonElement> & { + asChild?: boolean; +}, "ref"> & { + render?: import("react").ReactElement | undefined; +} & import("react").RefAttributes<HTMLButtonElement>, "ref"> & import("react").RefAttributes<HTMLButtonElement>>; + +declare namespace ComposerPrimitiveDictationTranscript { + type Element = ComponentRef<typeof Primitive$1.span>; + type Props = ComponentPropsWithoutRef<typeof Primitive$1.span>; +} + +declare const ComposerPrimitiveDictationTranscript: import("react").ForwardRefExoticComponent<Omit<Omit<import("react").ClassAttributes<HTMLSpanElement> & import("react").HTMLAttributes<HTMLSpanElement> & { + asChild?: boolean; +}, "ref"> & { + render?: import("react").ReactElement | undefined; +} & import("react").RefAttributes<HTMLSpanElement>, "ref"> & import("react").RefAttributes<HTMLSpanElement>>; + +declare namespace ComposerPrimitiveIf { + type Props = PropsWithChildren<UseComposerIfProps>; +} + +declare const ComposerPrimitiveIf: FC<ComposerPrimitiveIf.Props>; + +declare namespace ComposerPrimitiveInput { + export type Element = HTMLTextAreaElement; + type BaseProps = { + asChild?: boolean | undefined; + render?: ReactElement | undefined; + cancelOnEscape?: boolean | undefined; + unstable_focusOnRunStart?: boolean | undefined; + unstable_focusOnScrollToBottom?: boolean | undefined; + unstable_focusOnThreadSwitched?: boolean | undefined; + unstable_insertNewlineOnTouchEnter?: boolean | undefined; + addAttachmentOnPaste?: boolean | undefined; + }; + type SubmitModeProps = { + submitMode?: "enter" | "ctrlEnter" | "none" | undefined; + submitOnEnter?: never; + } | { + submitMode?: never; + submitOnEnter?: boolean | undefined; + }; + export type Props = TextareaAutosizeProps & BaseProps & SubmitModeProps; + export {}; +} + +declare const ComposerPrimitiveInput: import("react").ForwardRefExoticComponent<ComposerPrimitiveInput.Props & import("react").RefAttributes<HTMLTextAreaElement>>; + +declare namespace ComposerPrimitiveQueue { + type Props = { + children: (value: { + queueItem: QueueItemState; + }) => ReactNode; + }; +} + +declare const ComposerPrimitiveQueue: import("react").NamedExoticComponent<{ + children: (value: { + queueItem: QueueItemState; + }) => ReactNode; +}>; + +declare namespace ComposerPrimitiveQuote { + type Element = ComponentRef<typeof Primitive$1.div>; + type Props = ComponentPropsWithoutRef<typeof Primitive$1.div>; +} + +declare const ComposerPrimitiveQuote: import("react").ForwardRefExoticComponent<Omit<Omit<import("react").ClassAttributes<HTMLDivElement> & import("react").HTMLAttributes<HTMLDivElement> & { + asChild?: boolean; +}, "ref"> & { + render?: import("react").ReactElement | undefined; +} & import("react").RefAttributes<HTMLDivElement>, "ref"> & import("react").RefAttributes<HTMLDivElement>>; + +declare namespace ComposerPrimitiveQuoteDismiss { + type Element = ComponentRef<typeof Primitive$1.button>; + type Props = ComponentPropsWithoutRef<typeof Primitive$1.button>; +} + +declare const ComposerPrimitiveQuoteDismiss: import("react").ForwardRefExoticComponent<Omit<Omit<import("react").ClassAttributes<HTMLButtonElement> & import("react").ButtonHTMLAttributes<HTMLButtonElement> & { + asChild?: boolean; +}, "ref"> & { + render?: import("react").ReactElement | undefined; +} & import("react").RefAttributes<HTMLButtonElement>, "ref"> & import("react").RefAttributes<HTMLButtonElement>>; + +declare namespace ComposerPrimitiveQuoteText { + type Element = ComponentRef<typeof Primitive$1.span>; + type Props = ComponentPropsWithoutRef<typeof Primitive$1.span>; +} + +declare const ComposerPrimitiveQuoteText: import("react").ForwardRefExoticComponent<Omit<Omit<import("react").ClassAttributes<HTMLSpanElement> & import("react").HTMLAttributes<HTMLSpanElement> & { + asChild?: boolean; +}, "ref"> & { + render?: import("react").ReactElement | undefined; +} & import("react").RefAttributes<HTMLSpanElement>, "ref"> & import("react").RefAttributes<HTMLSpanElement>>; + +declare namespace ComposerPrimitiveRoot { + type Element = ComponentRef<typeof Primitive$1.form>; + type Props = ComponentPropsWithoutRef<typeof Primitive$1.form> & { + compact?: boolean | undefined; + }; +} + +declare const ComposerPrimitiveRoot: import("react").ForwardRefExoticComponent<Omit<Omit<import("react").ClassAttributes<HTMLFormElement> & import("react").FormHTMLAttributes<HTMLFormElement> & { + asChild?: boolean; +}, "ref"> & { + render?: import("react").ReactElement | undefined; +} & import("react").RefAttributes<HTMLFormElement>, "ref"> & { + compact?: boolean | undefined; +} & import("react").RefAttributes<HTMLFormElement>>; + +declare namespace ComposerPrimitiveSend { + type Element = ActionButtonElement; + type Props = ActionButtonProps<typeof useComposerSend>; +} + +declare const ComposerPrimitiveSend: import("react").ForwardRefExoticComponent<Omit<Omit<import("react").ClassAttributes<HTMLButtonElement> & import("react").ButtonHTMLAttributes<HTMLButtonElement> & { + asChild?: boolean; +}, "ref"> & { + render?: import("react").ReactElement | undefined; +} & import("react").RefAttributes<HTMLButtonElement>, "ref"> & import("react").RefAttributes<HTMLButtonElement>>; + +declare namespace ComposerPrimitiveStopDictation { + type Element = ActionButtonElement; + type Props = ActionButtonProps<typeof useComposerStopDictation>; +} + +declare const ComposerPrimitiveStopDictation: import("react").ForwardRefExoticComponent<Omit<Omit<import("react").ClassAttributes<HTMLButtonElement> & import("react").ButtonHTMLAttributes<HTMLButtonElement> & { + asChild?: boolean; +}, "ref"> & { + render?: import("react").ReactElement | undefined; +} & import("react").RefAttributes<HTMLButtonElement>, "ref"> & import("react").RefAttributes<HTMLButtonElement>>; + +declare const ComposerPrimitiveTriggerPopover: import("react").ForwardRefExoticComponent<Omit<Omit<Omit<import("react").ClassAttributes<HTMLDivElement> & import("react").HTMLAttributes<HTMLDivElement> & { + asChild?: boolean; +}, "ref"> & { + render?: import("react").ReactElement | undefined; +} & import("react").RefAttributes<HTMLDivElement>, "ref">, "onSelect"> & { + readonly char: string; + readonly adapter?: Unstable_TriggerAdapter | undefined; + readonly isLoading?: boolean | undefined; +} & import("react").RefAttributes<HTMLDivElement>> & { + Directive: import("react").FC<ComposerPrimitiveTriggerPopoverDirective.Props>; + Action: import("react").FC<ComposerPrimitiveTriggerPopoverAction.Props>; +}; + +declare namespace ComposerPrimitiveTriggerPopoverAction { + type Props = { + readonly formatter?: Unstable_DirectiveFormatter | undefined; + readonly onExecute: (item: Unstable_TriggerItem) => void; + readonly removeOnExecute?: boolean | undefined; + }; +} + +declare const ComposerPrimitiveTriggerPopoverAction: FC<ComposerPrimitiveTriggerPopoverAction.Props>; + +declare namespace ComposerPrimitiveTriggerPopoverBack { + type Element = ComponentRef<typeof Primitive$1.button>; + type Props = ComponentPropsWithoutRef<typeof Primitive$1.button>; +} + +declare const ComposerPrimitiveTriggerPopoverBack: import("react").ForwardRefExoticComponent<Omit<Omit<import("react").ClassAttributes<HTMLButtonElement> & import("react").ButtonHTMLAttributes<HTMLButtonElement> & { + asChild?: boolean; +}, "ref"> & { + render?: import("react").ReactElement | undefined; +} & import("react").RefAttributes<HTMLButtonElement>, "ref"> & import("react").RefAttributes<HTMLButtonElement>>; + +declare namespace ComposerPrimitiveTriggerPopoverCategories { + type Element = ComponentRef<typeof Primitive$1.div>; + type Props = Omit<ComponentPropsWithoutRef<typeof Primitive$1.div>, "children"> & { + children: (categories: readonly Unstable_TriggerCategory[]) => ReactNode; + }; +} + +declare const ComposerPrimitiveTriggerPopoverCategories: import("react").ForwardRefExoticComponent<Omit<Omit<Omit<import("react").ClassAttributes<HTMLDivElement> & import("react").HTMLAttributes<HTMLDivElement> & { + asChild?: boolean; +}, "ref"> & { + render?: import("react").ReactElement | undefined; +} & import("react").RefAttributes<HTMLDivElement>, "ref">, "children"> & { + children: (categories: readonly Unstable_TriggerCategory[]) => ReactNode; +} & import("react").RefAttributes<HTMLDivElement>>; + +declare namespace ComposerPrimitiveTriggerPopoverCategoryItem { + type Element = ComponentRef<typeof Primitive$1.button>; + type Props = ComponentPropsWithoutRef<typeof Primitive$1.button> & { + categoryId: string; + }; +} + +declare const ComposerPrimitiveTriggerPopoverCategoryItem: import("react").ForwardRefExoticComponent<Omit<Omit<import("react").ClassAttributes<HTMLButtonElement> & import("react").ButtonHTMLAttributes<HTMLButtonElement> & { + asChild?: boolean; +}, "ref"> & { + render?: import("react").ReactElement | undefined; +} & import("react").RefAttributes<HTMLButtonElement>, "ref"> & { + categoryId: string; +} & import("react").RefAttributes<HTMLButtonElement>>; + +declare namespace ComposerPrimitiveTriggerPopoverDirective { + type Props = { + readonly formatter?: Unstable_DirectiveFormatter | undefined; + readonly onInserted?: ((item: Unstable_TriggerItem) => void) | undefined; + }; +} + +declare const ComposerPrimitiveTriggerPopoverDirective: FC<ComposerPrimitiveTriggerPopoverDirective.Props>; + +declare namespace ComposerPrimitiveTriggerPopoverItem { + type Element = ComponentRef<typeof Primitive$1.button>; + type Props = ComponentPropsWithoutRef<typeof Primitive$1.button> & { + item: Unstable_TriggerItem; + index?: number | undefined; + }; +} + +declare const ComposerPrimitiveTriggerPopoverItem: import("react").ForwardRefExoticComponent<Omit<Omit<import("react").ClassAttributes<HTMLButtonElement> & import("react").ButtonHTMLAttributes<HTMLButtonElement> & { + asChild?: boolean; +}, "ref"> & { + render?: import("react").ReactElement | undefined; +} & import("react").RefAttributes<HTMLButtonElement>, "ref"> & { + item: Unstable_TriggerItem; + index?: number | undefined; +} & import("react").RefAttributes<HTMLButtonElement>>; + +declare namespace ComposerPrimitiveTriggerPopoverItems { + type Element = ComponentRef<typeof Primitive$1.div>; + type Props = Omit<ComponentPropsWithoutRef<typeof Primitive$1.div>, "children"> & { + children: (items: readonly Unstable_TriggerItem[]) => ReactNode; + }; +} + +declare const ComposerPrimitiveTriggerPopoverItems: import("react").ForwardRefExoticComponent<Omit<Omit<Omit<import("react").ClassAttributes<HTMLDivElement> & import("react").HTMLAttributes<HTMLDivElement> & { + asChild?: boolean; +}, "ref"> & { + render?: import("react").ReactElement | undefined; +} & import("react").RefAttributes<HTMLDivElement>, "ref">, "children"> & { + children: (items: readonly Unstable_TriggerItem[]) => ReactNode; +} & import("react").RefAttributes<HTMLDivElement>>; + +declare namespace ComposerPrimitiveTriggerPopoverRoot { + type Props = { + children: ReactNode; + }; +} + +declare const ComposerPrimitiveTriggerPopoverRoot: FC<ComposerPrimitiveTriggerPopoverRoot.Props>; + +type ComposerRuntime = { + readonly path: ComposerRuntimePath; + readonly type: "edit" | "thread"; + getState(): ComposerState$1; + addAttachment(fileOrAttachment: File | CreateAttachment): Promise<void>; + setText(text: string): void; + setRole(role: MessageRole): void; + setRunConfig(runConfig: RunConfig): void; + reset(): Promise<void>; + clearAttachments(): Promise<void>; + send(options?: SendOptions): void; + cancel(): void; + steerQueueItem(queueItemId: string): void; + removeQueueItem(queueItemId: string): void; + subscribe(callback: () => void): Unsubscribe; + getAttachmentByIndex(idx: number): AttachmentRuntime; + startDictation(): void; + stopDictation(): void; + setQuote(quote: QuoteInfo | undefined): void; + unstable_on<E extends ComposerRuntimeEventType>(event: E, callback: ComposerRuntimeEventCallback<E>): Unsubscribe; +}; + +type ComposerRuntimeCore = Readonly<{ + isEditing: boolean; + canCancel: boolean; + canSend: boolean; + isEmpty: boolean; + attachments: readonly Attachment[]; + attachmentAccept: string; + addAttachment: (fileOrAttachment: File | CreateAttachment) => Promise<void>; + removeAttachment: (attachmentId: string) => Promise<void>; + text: string; + setText: (value: string) => void; + role: MessageRole; + setRole: (role: MessageRole) => void; + runConfig: RunConfig; + setRunConfig: (runConfig: RunConfig) => void; + quote: QuoteInfo | undefined; + setQuote: (quote: QuoteInfo | undefined) => void; + reset: () => Promise<void>; + clearAttachments: () => Promise<void>; + send: (options?: SendOptions) => void; + cancel: () => void; + queue: readonly QueueItemState[]; + steerQueueItem: (queueItemId: string) => void; + removeQueueItem: (queueItemId: string) => void; + dictation: DictationState | undefined; + startDictation: () => void; + stopDictation: () => void; + subscribe: (callback: () => void) => Unsubscribe; + unstable_on: <E extends ComposerRuntimeEventType>(event: E, callback: ComposerRuntimeEventCallback<E>) => Unsubscribe; +}>; + +type ComposerRuntimeCoreBinding = SubscribableWithState<ComposerRuntimeCore | undefined, ComposerRuntimePath>; + +type ComposerRuntimeEventCallback<E extends ComposerRuntimeEventType> = (payload: ComposerRuntimeEventPayload[E]) => void; + +type ComposerRuntimeEventPayload = { + send: Record<string, never>; + attachmentAdd: Record<string, never>; + attachmentAddError: AttachmentAddErrorEvent; +}; + +type ComposerRuntimeEventType = keyof ComposerRuntimeEventPayload; + +declare abstract class ComposerRuntimeImpl implements ComposerRuntime { + protected _core: ComposerRuntimeCoreBinding; + get path(): ComposerRuntimePath; + abstract get type(): "edit" | "thread"; + constructor(_core: ComposerRuntimeCoreBinding); + protected __internal_bindMethods(): void; + abstract getState(): ComposerState$1; + setText(text: string): void; + setRunConfig(runConfig: RunConfig): void; + addAttachment(fileOrAttachment: File | CreateAttachment): Promise<void>; + reset(): Promise<void>; + clearAttachments(): Promise<void>; + send(options?: SendOptions): void; + cancel(): void; + steerQueueItem(queueItemId: string): void; + removeQueueItem(queueItemId: string): void; + setRole(role: MessageRole): void; + startDictation(): void; + stopDictation(): void; + setQuote(quote: QuoteInfo | undefined): void; + subscribe(callback: () => void): Unsubscribe; + private _eventSubscriptionSubjects; + unstable_on<E extends ComposerRuntimeEventType>(event: E, callback: ComposerRuntimeEventCallback<E>): Unsubscribe; + abstract getAttachmentByIndex(idx: number): AttachmentRuntime; +} + +type ComposerRuntimePath = (ThreadRuntimePath & { + readonly composerSource: "thread"; +}) | (MessageRuntimePath & { + readonly composerSource: "edit"; +}); + +type ComposerSendOptions = SendOptions & { + steer?: boolean; +}; + +type ComposerState = { + readonly text: string; + readonly role: MessageRole; + readonly attachments: readonly Attachment[]; + readonly runConfig: RunConfig; + readonly isEditing: boolean; + readonly canCancel: boolean; + readonly canSend: boolean; + readonly attachmentAccept: string; + readonly isEmpty: boolean; + readonly type: "edit" | "thread"; + readonly dictation: DictationState | undefined; + readonly quote: QuoteInfo | undefined; + readonly queue: readonly QueueItemState[]; +}; + +type ComposerState$1 = ThreadComposerState | EditComposerState; + +declare class CompositeAttachmentAdapter implements AttachmentAdapter { + private _adapters; + accept: string; + constructor(adapters: AttachmentAdapter[]); + add(state: { + file: File; + }): Promise<PendingAttachment> | AsyncGenerator<PendingAttachment, void, any>; + send(attachment: PendingAttachment): Promise<CompleteAttachment>; + remove(attachment: Attachment): Promise<void>; +} + +declare class CompositeContextProvider implements ModelContextProvider { + private _providers; + getModelContext(): ModelContext$1; + registerModelContextProvider(provider: ModelContextProvider): () => void; + private _subscribers; + notifySubscribers(): void; + subscribe(callback: () => void): () => void; +} + +type CreateAppendMessage = string | { + parentId?: string | null | undefined; + sourceId?: string | null | undefined; + role?: AppendMessage["role"] | undefined; + content: AppendMessage["content"]; + attachments?: AppendMessage["attachments"] | undefined; + metadata?: AppendMessage["metadata"] | undefined; + createdAt?: Date | undefined; + runConfig?: AppendMessage["runConfig"] | undefined; + startRun?: boolean | undefined; +}; + +type CreateAttachment = { + id?: string; + type?: "image" | "document" | "file" | (string & {}); + name: string; + contentType?: string; + content: ThreadUserMessagePart[]; +}; + +type CreateResumeRunConfig = CreateStartRunConfig & { + stream?: (options: ChatModelRunOptions) => AsyncGenerator<ChatModelRunResult, void, unknown>; +}; + +type CreateStartRunConfig = { + parentId: string | null; + sourceId?: string | null | undefined; + runConfig?: RunConfig | undefined; +}; + +type DataMessagePart<T = any> = { + readonly type: "data"; + readonly name: string; + readonly data: T; +}; + +type DataMessagePartComponent<T = any> = ComponentType<DataMessagePartProps<T>>; + +type DataMessagePartProps<T = any> = MessagePartState & DataMessagePart<T>; + +type DataPrefixedPart = { + readonly type: `data-${string}`; + readonly data: any; +}; + +declare const DataRenderers: Resource<ClientOutput<"dataRenderers">, [ +]>; + +type DeepPartial<T> = T extends readonly any[] ? readonly DeepPartial<T[number]>[] : T extends { + [key: string]: any; +} ? { + readonly [K in keyof T]?: DeepPartial<T[K]>; +} : T; + +declare class DefaultThreadComposerRuntimeCore extends BaseComposerRuntimeCore implements ThreadComposerRuntimeCore { + private runtime; + private _canCancel; + get canCancel(): boolean; + get canSend(): boolean; + get queue(): readonly QueueItemState[]; + steerQueueItem(queueItemId: string): void; + removeQueueItem(queueItemId: string): void; + protected getAttachmentAdapter(): AttachmentAdapter | undefined; + protected getDictationAdapter(): DictationAdapter | undefined; + constructor(runtime: Omit<ThreadRuntimeCore, "composer"> & { + adapters?: { + attachments?: AttachmentAdapter | undefined; + dictation?: DictationAdapter | undefined; + } | undefined; + }); + connect(): Unsubscribe; + handleSend(message: Omit<AppendMessage, "parentId" | "sourceId">, options?: SendOptions): Promise<void>; + handleCancel(): Promise<void>; +} + +declare const Derived: <K extends ClientNames>(_config: Derived.Props<K>) => ResourceElement<null, [ + _config: Derived.Props<K> +]>; + +declare namespace Derived { + type Props<K extends ClientNames> = { + get: (client: AssistantClient) => ReturnType<AssistantClientAccessor<K>>; + } & ClientMeta<K>; +} + +type DerivedElement<K extends ClientNames> = ResourceElement<null, [ + Derived.Props<K> +]>; + +interface DevToolsApiEntry { + api: Partial<AssistantClient>; + logs: EventLog[]; +} + +interface DevToolsHook { + apis: Map<number, DevToolsApiEntry>; + nextId: number; + listeners: Set<(apiId: number) => void>; +} + +declare class DevToolsHooks { + static subscribe(listener: () => void): Unsubscribe; + static clearEventLogs(apiId: number): void; + static getApis(): Map<number, DevToolsApiEntry>; + private static notifyListeners; +} + +declare class DevToolsProviderApi { + private static readonly MAX_EVENT_LOGS_PER_API; + static register(aui: Partial<AssistantClient>): Unsubscribe; + private static notifyListeners; +} + +declare namespace DictationAdapter { + type Status = { + type: "running" | "starting"; + } | { + type: "ended"; + reason: "cancelled" | "error" | "stopped"; + }; + type Result = { + transcript: string; + isFinal?: boolean; + }; + type Session = { + status: Status; + stop: () => Promise<void>; + cancel: () => void; + onSpeechStart: (callback: () => void) => Unsubscribe; + onSpeechEnd: (callback: (result: Result) => void) => Unsubscribe; + onSpeech: (callback: (result: Result) => void) => Unsubscribe; + }; +} + +type DictationAdapter = { + listen: () => DictationAdapter.Session; + disableInputDuringDictation?: boolean; +}; + +type DictationState = { + readonly status: DictationAdapter.Status; + readonly transcript?: string; + readonly inputDisabled?: boolean; +}; + +declare class EditComposerAttachmentRuntimeImpl extends ComposerAttachmentRuntime<"edit-composer"> { + get source(): "edit-composer"; +} + +type EditComposerAttachmentState = Attachment & { + readonly source: "edit-composer"; +}; + +type EditComposerRuntime = Omit<ComposerRuntime, "getAttachmentByIndex" | "getState"> & { + readonly path: ComposerRuntimePath & { + composerSource: "edit"; + }; + readonly type: "edit"; + getState(): EditComposerState; + beginEdit(): void; + getAttachmentByIndex(idx: number): AttachmentRuntime & { + source: "edit-composer"; + }; +}; + +type EditComposerRuntimeCore = ComposerRuntimeCore & Readonly<{ + parentId: string | null; + sourceId: string | null; +}>; + +type EditComposerRuntimeCoreBinding = SubscribableWithState<EditComposerRuntimeCore | undefined, ComposerRuntimePath & { + composerSource: "edit"; +}>; + +declare class EditComposerRuntimeImpl extends ComposerRuntimeImpl implements EditComposerRuntime { + private _beginEdit; + get path(): ComposerRuntimePath & { + composerSource: "edit"; + }; + get type(): "edit"; + private _getState; + constructor(core: EditComposerRuntimeCoreBinding, _beginEdit: () => void); + __internal_bindMethods(): void; + getState(): EditComposerState; + beginEdit(): void; + getAttachmentByIndex(idx: number): EditComposerAttachmentRuntimeImpl; +} + +type EditComposerState = BaseComposerState & { + readonly type: "edit"; + readonly parentId: string | null; + readonly sourceId: string | null; +}; + +type EmptyMessagePartComponent = ComponentType<EmptyMessagePartProps>; + +type EmptyMessagePartProps = { + status: MessagePartStatus; +}; + +type EnrichedPartState = (Extract<PartState, { + type: "tool-call"; +}> & { + readonly toolUI: ReactNode; + addResult: ToolCallMessagePartProps["addResult"]; + resume: ToolCallMessagePartProps["resume"]; + respondToApproval: ToolCallMessagePartProps["respondToApproval"]; +}) | (Extract<PartState, { + type: "data"; +}> & { + readonly dataRendererUI: ReactNode; +}) | Exclude<PartState, { + type: "tool-call"; +} | { + type: "data"; +}>; + +declare namespace ErrorPrimitiveMessage { + type Element = ComponentRef<typeof Primitive$1.span>; + type Props = ComponentPropsWithoutRef<typeof Primitive$1.span>; +} + +declare const ErrorPrimitiveMessage: import("react").ForwardRefExoticComponent<Omit<Omit<import("react").ClassAttributes<HTMLSpanElement> & import("react").HTMLAttributes<HTMLSpanElement> & { + asChild?: boolean; +}, "ref"> & { + render?: import("react").ReactElement | undefined; +} & import("react").RefAttributes<HTMLSpanElement>, "ref"> & import("react").RefAttributes<HTMLSpanElement>>; + +declare namespace ErrorPrimitiveRoot { + type Element = ComponentRef<typeof Primitive$1.div>; + type Props = ComponentPropsWithoutRef<typeof Primitive$1.div>; +} + +declare const ErrorPrimitiveRoot: import("react").ForwardRefExoticComponent<Omit<Omit<import("react").ClassAttributes<HTMLDivElement> & import("react").HTMLAttributes<HTMLDivElement> & { + asChild?: boolean; +}, "ref"> & { + render?: import("react").ReactElement | undefined; +} & import("react").RefAttributes<HTMLDivElement>, "ref"> & import("react").RefAttributes<HTMLDivElement>>; + +interface EventLog { + time: Date; + event: string; + data: unknown; +} + +type EventSource<T extends AssistantEventName> = T extends `${infer Source}.${string}` ? Source : never; + +type ExportedMessageRepository = { + headId?: string | null; + messages: Array<{ + message: ThreadMessage; + parentId: string | null; + runConfig?: RunConfig; + }>; +}; + +declare const ExportedMessageRepository: { + fromArray: (messages: readonly ThreadMessageLike[]) => ExportedMessageRepository; + fromBranchableArray: (items: readonly { + message: ThreadMessageLike; + parentId: string | null; + }[], options?: { + headId?: string | null; + }) => ExportedMessageRepository; +}; + +type ExportedMessageRepositoryItem = { + message: ThreadMessage; + parentId: string | null; + runConfig?: RunConfig; +}; + +type ExternalStoreAdapter<T = ThreadMessage> = ExternalStoreAdapterBase<T> & (T extends ThreadMessage ? object : ExternalStoreMessageConverterAdapter<T>); + +type ExternalStoreAdapterBase<T> = { + isDisabled?: boolean | undefined; + isSendDisabled?: boolean | undefined; + isRunning?: boolean | undefined; + isLoading?: boolean | undefined; + messages?: readonly T[]; + messageRepository?: ExportedMessageRepository; + suggestions?: readonly ThreadSuggestion[] | undefined; + state?: ReadonlyJSONValue | undefined; + extras?: unknown; + setMessages?: ((messages: readonly T[]) => void) | undefined; + unstable_onBranchChange?: ((event: ExternalStoreBranchChange) => void) | undefined; + onImport?: ((messages: readonly ThreadMessage[]) => void) | undefined; + onExportExternalState?: (() => any) | undefined; + onLoadExternalState?: ((state: any) => void) | undefined; + onNew: (message: AppendMessage) => Promise<void>; + queue?: ExternalThreadQueueAdapter | undefined; + onEdit?: ((message: AppendMessage) => Promise<void>) | undefined; + onDelete?: ((messageId: string) => Promise<void> | void) | undefined; + onReload?: ((parentId: string | null, config: StartRunConfig) => Promise<void>) | undefined; + onResume?: ((config: ResumeRunConfig) => Promise<void>) | undefined; + onCancel?: (() => Promise<void>) | undefined; + onAddToolResult?: ((options: AddToolResultOptions) => Promise<void> | void) | undefined; + onResumeToolCall?: ((options: { + toolCallId: string; + payload: unknown; + }) => void) | undefined; + onRespondToToolApproval?: ((options: RespondToToolApprovalOptions) => Promise<void> | void) | undefined; + convertMessage?: ExternalStoreMessageConverter<T> | undefined; + adapters?: { + attachments?: AttachmentAdapter | undefined; + speech?: SpeechSynthesisAdapter | undefined; + dictation?: DictationAdapter | undefined; + voice?: RealtimeVoiceAdapter | undefined; + feedback?: FeedbackAdapter | undefined; + threadList?: ExternalStoreThreadListAdapter | undefined; + } | undefined; + unstable_capabilities?: { + copy?: boolean | undefined; + } | undefined; + unstable_enableToolInvocations?: boolean | undefined; + setToolStatuses?: ((statuses: Record<string, ToolExecutionStatus>) => void) | undefined; +}; + +type ExternalStoreBranchChange = { + headId: string | null; + visibleMessageIds: readonly string[]; +}; + +type ExternalStoreMessageConverter<T> = (message: T, idx: number) => ThreadMessageLike; + +type ExternalStoreMessageConverterAdapter<T> = { + convertMessage: ExternalStoreMessageConverter<T>; +}; + +type ExternalStoreSharedOptions = Pick<ExternalStoreAdapter, "isDisabled" | "isSendDisabled" | "suggestions" | "unstable_capabilities">; + +type ExternalStoreThreadData<TState extends "archived" | "regular"> = { + status: TState; + id: string; + remoteId?: string | undefined; + externalId?: string | undefined; + title?: string | undefined; + custom?: Record<string, unknown> | undefined; +}; + +type ExternalStoreThreadListAdapter = { + threadId?: string | undefined; + isLoading?: boolean | undefined; + threads?: readonly ExternalStoreThreadData<"regular">[] | undefined; + archivedThreads?: readonly ExternalStoreThreadData<"archived">[] | undefined; + onSwitchToNewThread?: (() => Promise<void> | void) | undefined; + onSwitchToThread?: ((threadId: string) => Promise<void> | void) | undefined; + onRename?: (threadId: string, newTitle: string) => (Promise<void> | void) | undefined; + onUpdateCustom?: ((threadId: string, custom: Record<string, unknown> | undefined) => Promise<void> | void) | undefined; + onArchive?: ((threadId: string) => Promise<void> | void) | undefined; + onUnarchive?: ((threadId: string) => Promise<void> | void) | undefined; + onDelete?: ((threadId: string) => Promise<void> | void) | undefined; +}; + +declare const ExternalThread: Resource<ClientOutput<"thread">, [ + ExternalThreadProps +]>; + +type ExternalThreadBranchAdapter = { + getBranches: (messageId: string) => readonly string[]; + switchToBranch: (branchId: string) => void; +}; + +type ExternalThreadMessage = ThreadMessage & { + id: string; +}; + +type ExternalThreadProps = { + messages: readonly ExternalThreadMessage[]; + isRunning?: boolean; + isSendDisabled?: boolean; + onNew?: (message: AppendMessage) => void; + onEdit?: (message: AppendMessage) => void; + onReload?: (parentId: string | null) => void; + onStartRun?: () => void; + onCancel?: () => void; + queue?: ExternalThreadQueueAdapter; + branches?: ExternalThreadBranchAdapter; + onRespondToToolApproval?: (options: RespondToToolApprovalOptions) => void; +}; + +type ExternalThreadQueueAdapter = { + items: readonly QueueItemState[]; + enqueue: (message: AppendMessage, options: { + steer: boolean; + }) => void; + steer: (queueItemId: string) => void; + remove: (queueItemId: string) => void; + clear: (reason: "cancel-run" | "edit" | "reload") => void; +}; + +declare const FRAME_MESSAGE_CHANNEL = "assistant-ui-frame"; + +type FeedbackAdapter = { + submit: (feedback: FeedbackAdapterFeedback) => void; +}; + +type FeedbackAdapterFeedback = { + message: ThreadMessage; + type: "negative" | "positive"; +}; + +type FileMessagePart = { + readonly type: "file"; + readonly filename?: string; + readonly data: string; + readonly mimeType: string; + readonly parentId?: string; +}; + +type FileMessagePartComponent = ComponentType<FileMessagePartProps>; + +type FileMessagePartProps = MessagePartState & FileMessagePart; + +type FrameMessage = { + type: "model-context-request"; +} | { + type: "model-context-update"; + context: SerializedModelContext; +} | { + type: "tool-call"; + id: string; + toolName: string; + args: unknown; +} | { + type: "tool-result"; + id: string; + result?: unknown; + error?: string; +}; + +type FrameMessageType = "model-context-request" | "model-context-update" | "tool-call" | "tool-result"; + +type FrontendTool<TArgs extends Record<string, unknown> = Record<string, unknown>, TResult = unknown> = ToolBase<TArgs, TResult> & { + type: "frontend"; + description?: string | undefined; + parameters: StandardSchemaV1<TArgs> | JSONSchema7; + disabled?: boolean; + execute?: ToolExecuteFunction<TArgs, TResult>; + toModelOutput?: ToolModelOutputFunction<TArgs, TResult>; + experimental_onSchemaValidationError?: OnSchemaValidationErrorFunction<TResult>; + providerOptions?: ProviderOptions; +}; + +type GeneratePresignedUploadUrlRequestBody = { + filename: string; +}; + +type GeneratePresignedUploadUrlResponse = { + success: boolean; + signedUrl: string; + expiresAt: string; + publicUrl: string; +}; + +type GenerativeUIComponentRegistry = Record<string, ComponentType<any>>; + +type GenerativeUIMessagePart = { + readonly type: "generative-ui"; + readonly spec: GenerativeUISpec; + readonly id?: string; + readonly parentId?: string; +}; + +type GenerativeUIMessagePartComponent = ComponentType<GenerativeUIMessagePartProps>; + +type GenerativeUIMessagePartProps = MessagePartState & GenerativeUIMessagePart; + +type GenerativeUINode = string | { + readonly component: string; + readonly props?: Record<string, unknown>; + readonly children?: readonly GenerativeUINode[]; + readonly key?: string; +}; + +declare const GenerativeUIRender: FC<GenerativeUIRenderProps>; + +declare class GenerativeUIRenderError extends Error { + readonly componentName: string; + constructor(componentName: string, message?: string); +} + +type GenerativeUIRenderProps = { + spec: GenerativeUISpec; + components: GenerativeUIComponentRegistry; + Fallback?: ComponentType<{ + component: string; + props?: unknown; + }> | undefined; +}; + +type GenerativeUISpec = { + readonly root: GenerativeUINode | readonly GenerativeUINode[]; +}; + +type GenericThreadHistoryAdapter<TMessage> = { + load(): Promise<MessageFormatRepository<TMessage>>; + append(item: MessageFormatItem<TMessage>): Promise<void>; + update?(item: MessageFormatItem<TMessage>, localMessageId: string): Promise<void>; + delete?(items: MessageFormatItem<TMessage>[]): Promise<void>; + reportTelemetry?(items: MessageFormatItem<TMessage>[], options?: { + durationMs?: number; + stepTimestamps?: { + start_ms: number; + end_ms: number; + }[]; + }): void; +}; + +type GroupByContext = { + readonly toolUIs?: ToolsState["toolUIs"]; +}; + +type GroupPartType = PartState["type"] | "standalone-tool-call" | "mcp-app"; + +type GroupingFunction = (parts: readonly any[]) => MessagePartGroup[]; + +type HeadersValue = Record<string, string> | Headers; + +type HumanTool<TArgs extends Record<string, unknown> = Record<string, unknown>, TResult = unknown> = ToolBase<TArgs, TResult> & { + type: "human"; + description?: string | undefined; + parameters: StandardSchemaV1<TArgs> | JSONSchema7; + disabled?: boolean; + display?: "standalone"; + execute?: undefined; + toModelOutput?: undefined; + experimental_onSchemaValidationError?: undefined; + providerOptions?: ProviderOptions; +}; + +type ImageMessagePart = { + readonly type: "image"; + readonly image: string; + readonly filename?: string; +}; + +type ImageMessagePartComponent = ComponentType<ImageMessagePartProps>; + +type ImageMessagePartProps = MessagePartState & ImageMessagePart; + +type ImagePart = { + readonly type: "image"; + readonly image: string; +}; + +declare const InMemoryThreadList: Resource<ClientOutput<"threads">, [ + props: InMemoryThreadListProps +]>; + +declare class InMemoryThreadListAdapter implements RemoteThreadListAdapter { + list(): Promise<RemoteThreadListResponse>; + rename(): Promise<void>; + updateCustom(): Promise<void>; + archive(): Promise<void>; + unarchive(): Promise<void>; + delete(): Promise<void>; + initialize(threadId: string): Promise<RemoteThreadInitializeResponse>; + generateTitle(): Promise<AssistantStream>; + fetch(threadId: string): Promise<RemoteThreadMetadata>; +} + +type InMemoryThreadListProps = { + thread: (threadId: string) => ResourceElement<ClientOutput<"thread">>; + onSwitchToThread?: (threadId: string) => void; + onSwitchToNewThread?: () => void; +}; + +type InteractableScope = "app" | "thread"; + +type InteractableStateSchema = NonNullable<Extract<Tool, { + parameters: unknown; +}>["parameters"]>; + +declare const Interactables: Resource<ClientOutput<"interactables">, [ +]>; + +interface JSONSchema7 { + $id?: string | undefined; + $ref?: string | undefined; + $schema?: JSONSchema7Version | undefined; + $comment?: string | undefined; + $defs?: { + [key: string]: JSONSchema7Definition; + } | undefined; + type?: JSONSchema7TypeName | JSONSchema7TypeName[] | undefined; + enum?: JSONSchema7Type[] | undefined; + const?: JSONSchema7Type | undefined; + multipleOf?: number | undefined; + maximum?: number | undefined; + exclusiveMaximum?: number | undefined; + minimum?: number | undefined; + exclusiveMinimum?: number | undefined; + maxLength?: number | undefined; + minLength?: number | undefined; + pattern?: string | undefined; + items?: JSONSchema7Definition | JSONSchema7Definition[] | undefined; + additionalItems?: JSONSchema7Definition | undefined; + maxItems?: number | undefined; + minItems?: number | undefined; + uniqueItems?: boolean | undefined; + contains?: JSONSchema7Definition | undefined; + maxProperties?: number | undefined; + minProperties?: number | undefined; + required?: string[] | undefined; + properties?: { + [key: string]: JSONSchema7Definition; + } | undefined; + patternProperties?: { + [key: string]: JSONSchema7Definition; + } | undefined; + additionalProperties?: JSONSchema7Definition | undefined; + dependencies?: { + [key: string]: JSONSchema7Definition | string[]; + } | undefined; + propertyNames?: JSONSchema7Definition | undefined; + if?: JSONSchema7Definition | undefined; + then?: JSONSchema7Definition | undefined; + else?: JSONSchema7Definition | undefined; + allOf?: JSONSchema7Definition[] | undefined; + anyOf?: JSONSchema7Definition[] | undefined; + oneOf?: JSONSchema7Definition[] | undefined; + not?: JSONSchema7Definition | undefined; + format?: string | undefined; + contentMediaType?: string | undefined; + contentEncoding?: string | undefined; + definitions?: { + [key: string]: JSONSchema7Definition; + } | undefined; + title?: string | undefined; + description?: string | undefined; + default?: JSONSchema7Type | undefined; + readOnly?: boolean | undefined; + writeOnly?: boolean | undefined; + examples?: JSONSchema7Type | undefined; +} + +interface JSONSchema7Array extends Array<JSONSchema7Type> { +} + +type JSONSchema7Definition = JSONSchema7 | boolean; + +interface JSONSchema7Object { + [key: string]: JSONSchema7Type; +} + +type JSONSchema7Type = string | number | boolean | JSONSchema7Object | JSONSchema7Array | null; + +type JSONSchema7TypeName = "array" | "boolean" | "integer" | "null" | "number" | "object" | "string"; + +type JSONSchema7Version = string; + +type JoinStrategy = "concat-content" | "none"; + +type LanguageModelConfig = { + apiKey?: string; + baseUrl?: string; + modelName?: string; + reasoningEffort?: string; +}; + +type LanguageModelV1CallSettings = { + maxTokens?: number; + temperature?: number; + topP?: number; + presencePenalty?: number; + frequencyPenalty?: number; + seed?: number; + headers?: Record<string, string | undefined>; +}; + +type LocalRuntimeOptions = Omit<LocalRuntimeOptionsBase, "adapters"> & { + cloud?: AssistantCloud | undefined; + initialMessages?: readonly ThreadMessageLike[] | undefined; + adapters?: Omit<LocalRuntimeOptionsBase["adapters"], "chatModel"> | undefined; +}; + +type LocalRuntimeOptionsBase = { + maxSteps?: number | undefined; + adapters: { + chatModel: ChatModelAdapter; + history?: ThreadHistoryAdapter | undefined; + attachments?: AttachmentAdapter | undefined; + speech?: SpeechSynthesisAdapter | undefined; + dictation?: DictationAdapter | undefined; + voice?: RealtimeVoiceAdapter | undefined; + feedback?: FeedbackAdapter | undefined; + suggestion?: SuggestionAdapter | undefined; + }; + unstable_humanToolNames?: string[] | undefined; + unstable_enableMessageQueue?: boolean | undefined; +}; + +declare const MCP_APP_MIME_TYPE: "text/html;profile=mcp-app"; + +type MakeRequestOptions = { + method?: "POST" | "PUT" | "DELETE" | undefined; + headers?: Record<string, string> | undefined; + query?: Record<string, string | number | boolean> | undefined; + body?: object | undefined; +}; + +type McpAppDisplayMode = "fullscreen" | "inline" | "pip"; + +type McpAppHostContext = { + theme?: "dark" | "light"; + displayMode?: McpAppDisplayMode; + availableDisplayModes?: McpAppDisplayMode[]; + [k: string]: unknown; +}; + +type McpAppHostInfo = { + name: string; + version: string; +}; + +type McpAppMetadata = { + readonly resourceUri: string; + readonly mimeType?: string; + readonly visibility?: readonly ("app" | "model")[]; +}; + +declare const McpAppRenderer: Resource<{ + readonly render: ToolCallMessagePartComponent; +}, [ + options: McpAppRendererOptions +]>; + +type McpAppRendererOptions = { + host: ResourceElement<McpAppsHost>; + sandbox?: McpAppSandboxConfig; + maxHeight?: number; + hostInfo?: McpAppHostInfo; + hostContext?: McpAppHostContext; + fallback?: ReactNode; + loadingFallback?: ReactNode; + errorFallback?: ReactNode | ((error: Error) => ReactNode); +}; + +type McpAppResource = { + uri: string; + mimeType: typeof MCP_APP_MIME_TYPE; + html: string; + meta?: McpAppResourceMeta; +}; + +type McpAppResourceCSP = { + connectDomains?: string[]; + resourceDomains?: string[]; + frameDomains?: string[]; + [k: string]: unknown; +}; + +type McpAppResourceMeta = { + prefersBorder?: boolean; + csp?: McpAppResourceCSP; + permissions?: Record<string, unknown>; + [k: string]: unknown; +}; + +type McpAppResourceOutput = { + readonly render: ToolCallMessagePartComponent; +}; + +type McpAppSandboxConfig = SandboxHostConfig; + +type McpAppToolCallParams = { + name: string; + arguments?: Record<string, unknown>; +}; + +type McpAppsHost = { + loadResource: (params: { + uri: string; + }) => Promise<McpAppResource>; + callTool: (params: McpAppToolCallParams) => Promise<unknown>; + readResource: (params: { + uri: string; + }) => Promise<unknown>; + listResources: (params?: unknown) => Promise<unknown>; +}; + +declare const McpAppsRemoteHost: Resource<McpAppsHost, [ + options: McpAppsRemoteHostOptions +]>; + +type McpAppsRemoteHostOptions = { + url: string; + fetch?: typeof fetch; + headers?: Record<string, string> | (() => Record<string, string> | Promise<Record<string, string>>); +}; + +type McpServerConfig = { + type: "http" | "sse"; + url: string; + headers?: Record<string, string>; + redirect?: "error" | "follow"; + connectionTimeout?: number | undefined; +} | { + type: "stdio"; + command: string; + args?: readonly string[]; + env?: Record<string, string>; + cwd?: string; + connectionTimeout?: number | undefined; +}; + +type McpTool = ToolBase<Record<string, unknown>, unknown> & { + type: "mcp"; + server: McpServerConfig; + description?: undefined; + parameters?: undefined; + disabled?: boolean; + execute?: undefined; + toModelOutput?: undefined; + experimental_onSchemaValidationError?: undefined; + providerOptions?: undefined; +}; + +type McpToolkitDefinition = Record<string, McpToolkitEntry>; + +type McpToolkitEntry = McpServerConfig | { + server: McpServerConfig; + disabled?: boolean | undefined; + prefix?: string | undefined; + tools?: Record<string, McpToolkitToolConfig> | undefined; +}; + +type McpToolkitToolConfig = { + disabled?: boolean | undefined; +}; + +declare const MessageAttachmentByIndexProvider: FC<PropsWithChildren<{ + index: number; +}>>; + +declare class MessageAttachmentRuntimeImpl extends AttachmentRuntimeImpl<"message"> { + get source(): "message"; + remove(): never; +} + +type MessageAttachmentState = CompleteAttachment & { + readonly source: "message"; +}; + +type MessageAttachmentsComponentConfig = { + Image?: ComponentType | undefined; + Document?: ComponentType | undefined; + File?: ComponentType | undefined; + Attachment?: ComponentType | undefined; +}; + +declare const MessageByIndexProvider: FC<PropsWithChildren<{ + index: number; +}>>; + +type MessageCommonProps = { + readonly id: string; + readonly createdAt: Date; +}; + +interface MessageFormatAdapter<TMessage, TStorageFormat extends Record<string, unknown>> { + format: string; + encode(item: MessageFormatItem<TMessage>): TStorageFormat; + decode(stored: MessageStorageEntry<TStorageFormat>): MessageFormatItem<TMessage>; + getId(message: TMessage): string; +} + +interface MessageFormatItem<TMessage> { + parentId: string | null; + message: TMessage; +} + +interface MessageFormatRepository<TMessage> { + headId?: string | null; + messages: MessageFormatItem<TMessage>[]; +} + +type MessageIfFilters = { + user: boolean | undefined; + assistant: boolean | undefined; + system: boolean | undefined; + hasBranches: boolean | undefined; + copied: boolean | undefined; + lastOrHover: boolean | undefined; + last: boolean | undefined; + speaking: boolean | undefined; + hasAttachments: boolean | undefined; + hasContent: boolean | undefined; + submittedFeedback: "positive" | "negative" | null | undefined; +}; + +type MessagePartGroup = { + groupKey: string | undefined; + indices: number[]; +}; + +declare namespace MessagePartPrimitiveImage { + type Element = ComponentRef<typeof Primitive$1.img>; + type Props = ComponentPropsWithoutRef<typeof Primitive$1.img>; +} + +declare const MessagePartPrimitiveImage: import("react").ForwardRefExoticComponent<Omit<Omit<import("react").ClassAttributes<HTMLImageElement> & import("react").ImgHTMLAttributes<HTMLImageElement> & { + asChild?: boolean; +}, "ref"> & { + render?: import("react").ReactElement | undefined; +} & import("react").RefAttributes<HTMLImageElement>, "ref"> & import("react").RefAttributes<HTMLImageElement>>; + +declare namespace MessagePartPrimitiveInProgress { + type Props = PropsWithChildren; +} + +declare const MessagePartPrimitiveInProgress: FC<MessagePartPrimitiveInProgress.Props>; + +declare namespace MessagePartPrimitiveText { + type Element = ComponentRef<typeof Primitive$1.span>; + type Props = Omit<ComponentPropsWithoutRef<typeof Primitive$1.span>, "asChild" | "children"> & { + smooth?: boolean | SmoothOptions; + component?: ElementType; + }; +} + +declare const MessagePartPrimitiveText: import("react").ForwardRefExoticComponent<Omit<Omit<Omit<import("react").ClassAttributes<HTMLSpanElement> & import("react").HTMLAttributes<HTMLSpanElement> & { + asChild?: boolean; +}, "ref"> & { + render?: import("react").ReactElement | undefined; +} & import("react").RefAttributes<HTMLSpanElement>, "ref">, "asChild" | "children"> & { + smooth?: boolean | SmoothOptions; + component?: ElementType; +} & import("react").RefAttributes<HTMLSpanElement>>; + +type MessagePartRuntime = { + addToolResult(result: any | ToolResponse<any>): void; + resumeToolCall(payload: unknown): void; + respondToToolApproval(response: ToolApprovalResponse): void; + readonly path: MessagePartRuntimePath; + getState(): MessagePartState; + subscribe(callback: () => void): Unsubscribe; +}; + +declare class MessagePartRuntimeImpl implements MessagePartRuntime { + private contentBinding; + private messageApi?; + private threadApi?; + get path(): MessagePartRuntimePath; + constructor(contentBinding: MessagePartSnapshotBinding, messageApi?: MessageStateBinding | undefined, threadApi?: ThreadRuntimeCoreBinding | undefined); + protected __internal_bindMethods(): void; + getState(): MessagePartState; + addToolResult(result: any | ToolResponse<any>): void; + resumeToolCall(payload: unknown): void; + respondToToolApproval(response: ToolApprovalResponse): void; + subscribe(callback: () => void): Unsubscribe; +} + +type MessagePartRuntimePath = MessageRuntimePath & { + readonly messagePartSelector: { + readonly type: "index"; + readonly index: number; + } | { + readonly type: "toolCallId"; + readonly toolCallId: string; + }; +}; + +type MessagePartSnapshotBinding = SubscribableWithState<MessagePartState, MessagePartRuntimePath>; + +type MessagePartState = (ThreadUserMessagePart | ThreadAssistantMessagePart) & { + readonly status: MessagePartStatus | ToolCallMessagePartStatus; +}; + +type MessagePartStatus = { + readonly type: "running"; +} | { + readonly type: "complete"; +} | { + readonly type: "incomplete"; + readonly reason: "cancelled" | "content-filter" | "error" | "length" | "other"; + readonly error?: unknown; +}; + +declare namespace MessagePrimitiveAttachmentByIndex { + type Props = { + index: number; + components?: MessageAttachmentsComponentConfig; + }; +} + +declare const MessagePrimitiveAttachmentByIndex: FC<MessagePrimitiveAttachmentByIndex.Props>; + +declare namespace MessagePrimitiveAttachments { + type Props = { + components: MessageAttachmentsComponentConfig; + children?: never; + } | { + children: (value: { + attachment: CompleteAttachment; + }) => ReactNode; + components?: never; + }; +} + +declare const MessagePrimitiveAttachments: FC<MessagePrimitiveAttachments.Props>; + +declare const MessagePrimitiveError: FC<PropsWithChildren>; + +declare namespace MessagePrimitiveGenerativeUI { + type Props = { + components: GenerativeUIComponentRegistry; + spec?: GenerativeUISpec | undefined; + Fallback?: ComponentType<{ + component: string; + props?: unknown; + }> | undefined; + }; +} + +declare const MessagePrimitiveGenerativeUI: FC<MessagePrimitiveGenerativeUI.Props>; + +declare namespace MessagePrimitiveGroupedParts { + type GroupPart<TKey extends `group-${string}` = `group-${string}`> = { + readonly type: TKey; + readonly status: MessagePartStatus | ToolCallMessagePartStatus; + readonly indices: readonly number[]; + }; + type IndicatorPart = { + readonly type: "indicator"; + }; + type IndicatorMode = "always" | "empty" | "never" | "no-text"; + type RenderInfo<TKey extends `group-${string}` = `group-${string}`> = { + readonly part: GroupPart<TKey> | EnrichedPartState | IndicatorPart; + readonly children: ReactNode; + }; + type Props<TKey extends `group-${string}` = `group-${string}`> = { + readonly groupBy: (part: PartState, context: GroupByContext) => readonly TKey[] | null; + readonly indicator?: IndicatorMode; + readonly children: (info: RenderInfo<TKey>) => ReactNode; + }; +} + +declare const MessagePrimitiveGroupedParts: { + <TKey extends `group-${string}`>(_param2: MessagePrimitiveGroupedParts.Props<TKey>): ReactNode; + displayName: string; +}; + +declare namespace MessagePrimitiveIf { + type Props = PropsWithChildren<UseMessageIfProps>; +} + +declare const MessagePrimitiveIf: FC<MessagePrimitiveIf.Props>; + +declare namespace MessagePrimitivePartByIndex { + type Props = { + index: number; + components: MessagePrimitiveParts$1.Props["components"]; + }; +} + +declare const MessagePrimitivePartByIndex: FC<MessagePrimitivePartByIndex.Props>; + +declare namespace MessagePrimitiveParts { + type Props = MessagePrimitiveParts$1.Props; +} + +declare const MessagePrimitiveParts: FC<MessagePrimitiveParts.Props>; + +declare namespace MessagePrimitiveParts$1 { + type DataConfig = { + by_name?: Record<string, DataMessagePartComponent | undefined> | undefined; + Fallback?: DataMessagePartComponent | undefined; + }; + type BaseComponents = { + Empty?: EmptyMessagePartComponent | undefined; + Text?: TextMessagePartComponent | undefined; + Source?: SourceMessagePartComponent | undefined; + Image?: ImageMessagePartComponent | undefined; + File?: FileMessagePartComponent | undefined; + Unstable_Audio?: Unstable_AudioMessagePartComponent | undefined; + data?: DataConfig | undefined; + Quote?: QuoteMessagePartComponent | undefined; + generativeUI?: { + components: GenerativeUIComponentRegistry; + Fallback?: ComponentType<{ + component: string; + props?: unknown; + }> | undefined; + } | undefined; + }; + type ToolsConfig = { + by_name?: Record<string, ToolCallMessagePartComponent | undefined> | undefined; + Fallback?: ComponentType<ToolCallMessagePartProps> | undefined; + } | { + Override: ComponentType<ToolCallMessagePartProps>; + }; + type StandardComponents = BaseComponents & { + Reasoning?: ReasoningMessagePartComponent | undefined; + tools?: ToolsConfig | undefined; + ToolGroup?: ComponentType<PropsWithChildren<{ + startIndex: number; + endIndex: number; + }>>; + ReasoningGroup?: ReasoningGroupComponent; + ChainOfThought?: never; + }; + type ChainOfThoughtComponents = BaseComponents & { + ChainOfThought: ComponentType; + Reasoning?: never; + tools?: never; + ToolGroup?: never; + ReasoningGroup?: never; + }; + export type Props = { + components?: StandardComponents | ChainOfThoughtComponents | undefined; + unstable_showEmptyOnNonTextEnd?: boolean | undefined; + children?: never; + } | { + children: (value: { + part: EnrichedPartState; + }) => ReactNode; + components?: never; + unstable_showEmptyOnNonTextEnd?: never; + }; + export {}; +} + +declare const MessagePrimitiveParts$1: FC<MessagePrimitiveParts$1.Props>; + +declare namespace MessagePrimitiveQuote { + type Props = { + children: (value: QuoteInfo) => ReactNode; + }; +} + +declare const MessagePrimitiveQuote: import("react").NamedExoticComponent<MessagePrimitiveQuote.Props>; + +declare namespace MessagePrimitiveRoot { + type Element = ComponentRef<typeof Primitive$1.div>; + type Props = ComponentPropsWithoutRef<typeof Primitive$1.div>; +} + +declare const MessagePrimitiveRoot: import("react").ForwardRefExoticComponent<Omit<Omit<import("react").ClassAttributes<HTMLDivElement> & import("react").HTMLAttributes<HTMLDivElement> & { + asChild?: boolean; +}, "ref"> & { + render?: import("react").ReactElement | undefined; +} & import("react").RefAttributes<HTMLDivElement>, "ref"> & import("react").RefAttributes<HTMLDivElement>>; + +declare namespace MessagePrimitiveUnstable_PartsGrouped { + type Props = { + groupingFunction: GroupingFunction; + components: { + Empty?: EmptyMessagePartComponent | undefined; + Text?: TextMessagePartComponent | undefined; + Reasoning?: ReasoningMessagePartComponent | undefined; + Source?: SourceMessagePartComponent | undefined; + Image?: ImageMessagePartComponent | undefined; + File?: FileMessagePartComponent | undefined; + Unstable_Audio?: Unstable_AudioMessagePartComponent | undefined; + data?: { + by_name?: Record<string, DataMessagePartComponent | undefined> | undefined; + Fallback?: DataMessagePartComponent | undefined; + } | undefined; + tools?: { + by_name?: Record<string, ToolCallMessagePartComponent | undefined> | undefined; + Fallback?: ComponentType<ToolCallMessagePartProps> | undefined; + } | { + Override: ComponentType<ToolCallMessagePartProps>; + } | undefined; + Group?: ComponentType<PropsWithChildren<{ + groupKey: string | undefined; + indices: number[]; + }>>; + } | undefined; + }; +} + +declare const MessagePrimitiveUnstable_PartsGrouped: FC<MessagePrimitiveUnstable_PartsGrouped.Props>; + +declare const MessagePrimitiveUnstable_PartsGroupedByParentId: FC<Omit<MessagePrimitiveUnstable_PartsGrouped.Props, "groupingFunction">>; + +declare const MessageProvider: FC<PropsWithChildren<ThreadMessageClientProps>>; + +type MessageQueueController = { + readonly adapter: ExternalThreadQueueAdapter; + notifyBusy: () => void; + notifyIdle: () => void; + subscribe: (callback: () => void) => () => void; +}; + +type MessageQueueDriver = { + run: (message: AppendMessage, options: { + steer: boolean; + }) => void; + cancel?: (() => void) | undefined; +}; + +declare class MessageRepository { + private messages; + private head; + private root; + private updateLevels; + private performOp; + private _messages; + get headId(): string | null; + get canonicalHeadId(): string | null; + getMessages(headId?: string): readonly ThreadMessage[]; + addOrUpdateMessage(parentId: string | null, message: ThreadMessage): void; + getMessage(messageId: string): { + parentId: string | null; + message: ThreadMessage; + index: number; + }; + deleteMessage(messageId: string, replacementId?: string | null | undefined): void; + getBranches(messageId: string): string[]; + private evictOffBranchOptimisticMessages; + switchToBranch(messageId: string): void; + resetHead(messageId: string | null): void; + clear(): void; + export(): ExportedMessageRepository; + import(_param3: ExportedMessageRepository): void; +} + +type MessageRole = ThreadMessage["role"]; + +type MessageRuntime = { + readonly path: MessageRuntimePath; + readonly composer: EditComposerRuntime; + getState(): MessageState$1; + delete(): void | Promise<void>; + reload(config?: ReloadConfig): void; + speak(): void; + stopSpeaking(): void; + submitFeedback(_param4: { + type: "positive" | "negative"; + }): void; + switchToBranch(_param5: { + position?: "previous" | "next" | undefined; + branchId?: string | undefined; + }): void; + unstable_getCopyText(): string; + subscribe(callback: () => void): Unsubscribe; + getMessagePartByIndex(idx: number): MessagePartRuntime; + getMessagePartByToolCallId(toolCallId: string): MessagePartRuntime; + getAttachmentByIndex(idx: number): AttachmentRuntime & { + source: "message"; + }; +}; + +declare class MessageRuntimeImpl implements MessageRuntime { + private _core; + private _threadBinding; + get path(): MessageRuntimePath; + constructor(_core: MessageStateBinding, _threadBinding: ThreadRuntimeCoreBinding); + protected __internal_bindMethods(): void; + readonly composer: EditComposerRuntimeImpl; + private _getEditComposerRuntimeCore; + getState(): ThreadMessage & { + readonly parentId: string | null; + readonly index: number; + readonly isLast: boolean; + readonly branchNumber: number; + readonly branchCount: number; + readonly speech: SpeechState | undefined; + }; + delete(): void | Promise<void>; + reload(reloadConfig?: ReloadConfig): void; + speak(): void; + stopSpeaking(): void; + submitFeedback(_param6: { + type: "positive" | "negative"; + }): void; + switchToBranch(_param7: { + position?: "previous" | "next" | undefined; + branchId?: string | undefined; + }): void; + unstable_getCopyText(): string; + subscribe(callback: () => void): Unsubscribe; + getMessagePartByIndex(idx: number): MessagePartRuntimeImpl; + getMessagePartByToolCallId(toolCallId: string): MessagePartRuntimeImpl; + getAttachmentByIndex(idx: number): MessageAttachmentRuntimeImpl; +} + +type MessageRuntimePath = ThreadRuntimePath & { + readonly messageSelector: { + readonly type: "messageId"; + readonly messageId: string; + } | { + readonly type: "index"; + readonly index: number; + }; +}; + +type MessageState = ThreadMessage & { + readonly parentId: string | null; + readonly isLast: boolean; + readonly branchNumber: number; + readonly branchCount: number; + readonly speech: SpeechState | undefined; + readonly composer: ComposerState; + readonly parts: readonly PartState[]; + readonly isCopied: boolean; + readonly isHovering: boolean; + readonly index: number; +}; + +type MessageState$1 = ThreadMessage & { + readonly parentId: string | null; + readonly index: number; + readonly isLast: boolean; + readonly branchNumber: number; + readonly branchCount: number; + readonly speech: SpeechState | undefined; +}; + +type MessageStateBinding = SubscribableWithState<ThreadMessage & { + readonly parentId: string | null; + readonly index: number; + readonly isLast: boolean; + readonly branchNumber: number; + readonly branchCount: number; + readonly speech: SpeechState | undefined; +}, MessageRuntimePath>; + +type MessageStatus = { + readonly type: "running"; +} | { + readonly type: "requires-action"; + readonly reason: "interrupt" | "tool-calls"; +} | { + readonly type: "complete"; + readonly reason: "stop" | "unknown"; +} | { + readonly type: "incomplete"; + readonly reason: "cancelled" | "content-filter" | "error" | "length" | "other" | "tool-calls"; + readonly error?: ReadonlyJSONValue; +}; + +interface MessageStorageEntry<TPayload> { + id: string; + parent_id: string | null; + format: string; + content: TPayload; +} + +type MessageTiming = { + readonly streamStartTime: number; + readonly firstTokenTime?: number; + readonly totalStreamTime?: number; + readonly tokenCount?: number; + readonly tokensPerSecond?: number; + readonly totalChunks: number; + readonly toolCallCount: number; +}; + +type MessagesComponentConfig = { + Message: ComponentType; + EditComposer?: ComponentType | undefined; + UserEditComposer?: ComponentType | undefined; + AssistantEditComposer?: ComponentType | undefined; + SystemEditComposer?: ComponentType | undefined; + UserMessage?: ComponentType | undefined; + AssistantMessage?: ComponentType | undefined; + SystemMessage?: ComponentType | undefined; +} | { + Message?: ComponentType | undefined; + EditComposer?: ComponentType | undefined; + UserEditComposer?: ComponentType | undefined; + AssistantEditComposer?: ComponentType | undefined; + SystemEditComposer?: ComponentType | undefined; + UserMessage: ComponentType; + AssistantMessage: ComponentType; + SystemMessage?: ComponentType | undefined; +}; + +declare const ModelContext: Resource<ClientOutput<"modelContext">, [ +]>; + +type ModelContext$1 = { + priority?: number | undefined; + system?: string | undefined; + tools?: Record<string, Tool<any, any>> | undefined; + callSettings?: LanguageModelV1CallSettings | undefined; + config?: LanguageModelConfig | undefined; + unstable_composerMetadata?: Record<string, unknown> | undefined; +}; + +type ModelContextProvider = { + getModelContext: () => ModelContext$1; + subscribe?: (callback: () => void) => Unsubscribe; +}; + +declare class ModelContextRegistry implements ModelContextProvider { + private _tools; + private _instructions; + private _providers; + private _subscribers; + private _providerUnsubscribes; + getModelContext(): ModelContext$1; + subscribe(callback: () => void): Unsubscribe; + private notifySubscribers; + addTool<TArgs extends Record<string, unknown>, TResult>(tool: AssistantToolProps$1<TArgs, TResult>): ModelContextRegistryToolHandle<TArgs, TResult>; + addInstruction(config: string | AssistantInstructionsConfig): ModelContextRegistryInstructionHandle; + addProvider(provider: ModelContextProvider): ModelContextRegistryProviderHandle; +} + +interface ModelContextRegistryInstructionHandle { + update(config: string | AssistantInstructionsConfig): void; + remove(): void; +} + +interface ModelContextRegistryProviderHandle { + remove(): void; +} + +interface ModelContextRegistryToolHandle<TArgs extends Record<string, unknown> = any, TResult = any> { + update(tool: AssistantToolProps$1<TArgs, TResult>): void; + remove(): void; +} + +type ObjectKey<T> = keyof T & (string | number); + +type ObjectStreamOperation = { + readonly type: "set"; + readonly path: readonly string[]; + readonly value: ReadonlyJSONValue; +} | { + readonly type: "append-text"; + readonly path: readonly string[]; + readonly value: string; +}; + +type OnSchemaValidationErrorFunction<TResult> = ToolExecuteFunction<unknown, TResult>; + +type OverrideOptionalField<T, TKey extends keyof T, TValue> = undefined extends T[TKey] ? Exclude<T[TKey], undefined> extends never ? { + [K in TKey]?: undefined; +} : { + [K in TKey]?: TValue | undefined; +} : { + [K in TKey]: TValue; +}; + +type OverrideToolDeclarationCallbacks<T extends { + streamCall?: unknown; +}, TArgs extends Record<string, unknown>, TResult> = Omit<T, "execute" | "experimental_onSchemaValidationError" | "streamCall" | "toModelOutput" | "type"> & { + type?: never; +} & ("execute" extends keyof T ? OverrideOptionalField<T, "execute", ToolExecute<NoInfer<TArgs>, TResult>> : {}) & ("toModelOutput" extends keyof T ? OverrideOptionalField<T, "toModelOutput", ToolModelOutputFunction<NoInfer<TArgs>, NoInfer<TResult>>> : {}) & ("experimental_onSchemaValidationError" extends keyof T ? OverrideOptionalField<T, "experimental_onSchemaValidationError", (args: unknown, context: ToolExecuteContext) => NoInfer<TResult> | Promise<NoInfer<TResult>>> : {}) & OverrideOptionalField<T, "streamCall", ToolStreamCall<TArgs, unknown>>; + +type ParentOf<K extends ClientNames> = AssistantClientAccessor<K> extends { + source: infer S; +} ? S extends ClientNames ? S : never : never; + +declare const PartByIndexProvider: FC<PropsWithChildren<{ + index: number; +}>>; + +type PartInit = { + readonly type: "reasoning" | "text"; + readonly parentId?: string; +} | { + readonly type: "tool-call"; + readonly toolCallId: string; + readonly toolName: string; + readonly parentId?: string; +} | { + readonly type: "source"; + readonly sourceType: "url"; + readonly id: string; + readonly url: string; + readonly title?: string; + readonly parentId?: string; +} | { + readonly type: "file"; + readonly data: string; + readonly mimeType: string; + readonly parentId?: string; +} | { + readonly type: "data"; + readonly name: string; + readonly data: ReadonlyJSONValue; + readonly parentId?: string; +}; + +type PartMethods = { + getState(): PartState; + addToolResult(result: unknown | ToolResponse<unknown>): void; + resumeToolCall(payload: unknown): void; + respondToToolApproval(response: ToolApprovalResponse): void; + __internal_getRuntime?(): MessagePartRuntime; +}; + +declare namespace PartPrimitiveMessages { + type Props = { + components: NonNullable<ThreadPrimitiveMessages.Props["components"]>; + children?: never; + } | { + children: (value: { + message: ThreadMessage; + }) => ReactNode; + components?: never; + }; +} + +declare const PartPrimitiveMessages: import("react").NamedExoticComponent<PartPrimitiveMessages.Props>; + +type PartProviderMetadata = { + readonly [providerName: string]: ReadonlyJSONObject; +}; + +type PartState = (ThreadUserMessagePart | ThreadAssistantMessagePart) & { + readonly status: MessagePartStatus | ToolCallMessagePartStatus; +}; + +type PdfToImagesRequestBody = { + file_blob?: string | undefined; + file_url?: string | undefined; +}; + +type PdfToImagesResponse = { + success: boolean; + urls: string[]; + message: string; +}; + +type PendingAttachment = BaseAttachment & { + status: PendingAttachmentStatus; + file: File; +}; + +type PendingAttachmentStatus = { + type: "running"; + reason: "uploading"; + progress: number; +} | { + type: "requires-action"; + reason: "composer-send"; +} | { + type: "incomplete"; + reason: "error" | "upload-paused"; + message?: string; +}; + +declare const Primitive$1: { + a: ForwardRefExoticComponent<Omit<import("react").ClassAttributes<HTMLAnchorElement> & import("react").AnchorHTMLAttributes<HTMLAnchorElement> & { + asChild?: boolean; + }, "ref"> & { + render?: ReactElement | undefined; + } & RefAttributes<HTMLAnchorElement>>; + button: ForwardRefExoticComponent<Omit<import("react").ClassAttributes<HTMLButtonElement> & import("react").ButtonHTMLAttributes<HTMLButtonElement> & { + asChild?: boolean; + }, "ref"> & { + render?: ReactElement | undefined; + } & RefAttributes<HTMLButtonElement>>; + div: ForwardRefExoticComponent<Omit<import("react").ClassAttributes<HTMLDivElement> & import("react").HTMLAttributes<HTMLDivElement> & { + asChild?: boolean; + }, "ref"> & { + render?: ReactElement | undefined; + } & RefAttributes<HTMLDivElement>>; + form: ForwardRefExoticComponent<Omit<import("react").ClassAttributes<HTMLFormElement> & import("react").FormHTMLAttributes<HTMLFormElement> & { + asChild?: boolean; + }, "ref"> & { + render?: ReactElement | undefined; + } & RefAttributes<HTMLFormElement>>; + h2: ForwardRefExoticComponent<Omit<import("react").ClassAttributes<HTMLHeadingElement> & import("react").HTMLAttributes<HTMLHeadingElement> & { + asChild?: boolean; + }, "ref"> & { + render?: ReactElement | undefined; + } & RefAttributes<HTMLHeadingElement>>; + h3: ForwardRefExoticComponent<Omit<import("react").ClassAttributes<HTMLHeadingElement> & import("react").HTMLAttributes<HTMLHeadingElement> & { + asChild?: boolean; + }, "ref"> & { + render?: ReactElement | undefined; + } & RefAttributes<HTMLHeadingElement>>; + img: ForwardRefExoticComponent<Omit<import("react").ClassAttributes<HTMLImageElement> & import("react").ImgHTMLAttributes<HTMLImageElement> & { + asChild?: boolean; + }, "ref"> & { + render?: ReactElement | undefined; + } & RefAttributes<HTMLImageElement>>; + input: ForwardRefExoticComponent<Omit<import("react").ClassAttributes<HTMLInputElement> & import("react").InputHTMLAttributes<HTMLInputElement> & { + asChild?: boolean; + }, "ref"> & { + render?: ReactElement | undefined; + } & RefAttributes<HTMLInputElement>>; + label: ForwardRefExoticComponent<Omit<import("react").ClassAttributes<HTMLLabelElement> & import("react").LabelHTMLAttributes<HTMLLabelElement> & { + asChild?: boolean; + }, "ref"> & { + render?: ReactElement | undefined; + } & RefAttributes<HTMLLabelElement>>; + li: ForwardRefExoticComponent<Omit<import("react").ClassAttributes<HTMLLIElement> & import("react").LiHTMLAttributes<HTMLLIElement> & { + asChild?: boolean; + }, "ref"> & { + render?: ReactElement | undefined; + } & RefAttributes<HTMLLIElement>>; + nav: ForwardRefExoticComponent<Omit<import("react").ClassAttributes<HTMLElement> & import("react").HTMLAttributes<HTMLElement> & { + asChild?: boolean; + }, "ref"> & { + render?: ReactElement | undefined; + } & RefAttributes<HTMLElement>>; + ol: ForwardRefExoticComponent<Omit<import("react").ClassAttributes<HTMLOListElement> & import("react").OlHTMLAttributes<HTMLOListElement> & { + asChild?: boolean; + }, "ref"> & { + render?: ReactElement | undefined; + } & RefAttributes<HTMLOListElement>>; + p: ForwardRefExoticComponent<Omit<import("react").ClassAttributes<HTMLParagraphElement> & import("react").HTMLAttributes<HTMLParagraphElement> & { + asChild?: boolean; + }, "ref"> & { + render?: ReactElement | undefined; + } & RefAttributes<HTMLParagraphElement>>; + select: ForwardRefExoticComponent<Omit<import("react").ClassAttributes<HTMLSelectElement> & import("react").SelectHTMLAttributes<HTMLSelectElement> & { + asChild?: boolean; + }, "ref"> & { + render?: ReactElement | undefined; + } & RefAttributes<HTMLSelectElement>>; + span: ForwardRefExoticComponent<Omit<import("react").ClassAttributes<HTMLSpanElement> & import("react").HTMLAttributes<HTMLSpanElement> & { + asChild?: boolean; + }, "ref"> & { + render?: ReactElement | undefined; + } & RefAttributes<HTMLSpanElement>>; + svg: ForwardRefExoticComponent<Omit<import("react").SVGProps<SVGSVGElement> & { + asChild?: boolean; + }, "ref"> & { + render?: ReactElement | undefined; + } & RefAttributes<SVGSVGElement>>; + ul: ForwardRefExoticComponent<Omit<import("react").ClassAttributes<HTMLUListElement> & import("react").HTMLAttributes<HTMLUListElement> & { + asChild?: boolean; + }, "ref"> & { + render?: ReactElement | undefined; + } & RefAttributes<HTMLUListElement>>; +}; + +type PrimitiveButtonProps = ComponentPropsWithoutRef<typeof Primitive$1.button>; + +type PrimitiveDivProps = ComponentPropsWithoutRef<typeof Primitive$1.div>; + +type PrimitiveDivProps$1 = ComponentPropsWithoutRef<typeof Primitive$1.div>; + +type PrimitiveDivProps$2 = ComponentPropsWithoutRef<typeof Primitive$1.div>; + +type PrimitiveDivProps$3 = ComponentPropsWithoutRef<typeof Primitive$1.div>; + +type PrimitiveDivProps$4 = ComponentPropsWithoutRef<typeof Primitive$1.div>; + +type PrimitiveDivProps$5 = ComponentPropsWithoutRef<typeof Primitive$1.div>; + +type PropFieldStatus = "complete" | "streaming"; + +type ProviderOptions = Record<string, Record<string, unknown>>; + +type ProviderTool<TArgs extends Record<string, unknown> = Record<string, unknown>, TResult = unknown> = ToolBase<TArgs, TResult> & { + type: "provider"; + providerId: `${string}.${string}`; + parameters?: StandardSchemaV1<TArgs> | JSONSchema7 | undefined; + args: Record<string, unknown>; + supportsDeferredResults?: boolean; + description?: undefined; + disabled?: boolean; + execute?: undefined; + toModelOutput?: undefined; + experimental_onSchemaValidationError?: undefined; + providerOptions?: ProviderOptions; +}; + +type ProviderToolConfig<TArgs extends Record<string, unknown> = Record<string, unknown>> = Pick<ProviderToolDefinition<TArgs>, "args" | "parameters" | "providerId" | "providerOptions" | "supportsDeferredResults">; + +type ProviderToolDefinition<TArgs extends Record<string, unknown>> = Extract<Tool<TArgs, unknown>, { + type: "provider"; +}>; + +type QueueItemMethods = { + getState(): QueueItemState; + steer(): void; + remove(): void; +}; + +declare namespace QueueItemPrimitiveRemove { + type Element = ActionButtonElement; + type Props = ActionButtonProps<typeof useQueueItemRemove>; +} + +declare const QueueItemPrimitiveRemove: import("react").ForwardRefExoticComponent<Omit<Omit<import("react").ClassAttributes<HTMLButtonElement> & import("react").ButtonHTMLAttributes<HTMLButtonElement> & { + asChild?: boolean; +}, "ref"> & { + render?: import("react").ReactElement | undefined; +} & import("react").RefAttributes<HTMLButtonElement>, "ref"> & import("react").RefAttributes<HTMLButtonElement>>; + +declare namespace QueueItemPrimitiveSteer { + type Element = ActionButtonElement; + type Props = ActionButtonProps<typeof useQueueItemSteer>; +} + +declare const QueueItemPrimitiveSteer: import("react").ForwardRefExoticComponent<Omit<Omit<import("react").ClassAttributes<HTMLButtonElement> & import("react").ButtonHTMLAttributes<HTMLButtonElement> & { + asChild?: boolean; +}, "ref"> & { + render?: import("react").ReactElement | undefined; +} & import("react").RefAttributes<HTMLButtonElement>, "ref"> & import("react").RefAttributes<HTMLButtonElement>>; + +declare namespace QueueItemPrimitiveText { + type Element = ComponentRef<typeof Primitive$1.span>; + type Props = ComponentPropsWithoutRef<typeof Primitive$1.span>; +} + +declare const QueueItemPrimitiveText: import("react").ForwardRefExoticComponent<Omit<Omit<import("react").ClassAttributes<HTMLSpanElement> & import("react").HTMLAttributes<HTMLSpanElement> & { + asChild?: boolean; +}, "ref"> & { + render?: import("react").ReactElement | undefined; +} & import("react").RefAttributes<HTMLSpanElement>, "ref"> & import("react").RefAttributes<HTMLSpanElement>>; + +type QueueItemState = { + readonly id: string; + readonly prompt: string; +}; + +type QueuedCommand = AssistantTransportCommand; + +type QuoteInfo = { + readonly text: string; + readonly messageId: string; +}; + +type QuoteMessagePartComponent = ComponentType<QuoteMessagePartProps>; + +type QuoteMessagePartProps = QuoteInfo; + +type ReadonlyJSONArray = readonly ReadonlyJSONValue[]; + +type ReadonlyJSONObject = { + readonly [key: string]: ReadonlyJSONValue; +}; + +type ReadonlyJSONValue = null | string | number | boolean | ReadonlyJSONObject | ReadonlyJSONArray; + +type ReadonlyStore<T> = Omit<StoreApi<T>, "destroy" | "setState">; + +declare namespace ReadonlyThreadProvider { + type Props = PropsWithChildren<{ + messages: readonly ThreadMessage[]; + }>; +} + +declare const ReadonlyThreadProvider: FC<ReadonlyThreadProvider.Props>; + +declare namespace RealtimeVoiceAdapter { + type Status = { + type: "running" | "starting"; + } | { + type: "ended"; + reason: "cancelled" | "error" | "finished"; + error?: unknown; + }; + type Mode = "listening" | "speaking"; + type TranscriptItem = { + role: "assistant" | "user"; + text: string; + isFinal?: boolean; + }; + type Session = { + status: Status; + isMuted: boolean; + disconnect: () => void; + mute: () => void; + unmute: () => void; + onStatusChange: (callback: (status: Status) => void) => Unsubscribe; + onTranscript: (callback: (transcript: TranscriptItem) => void) => Unsubscribe; + onModeChange: (callback: (mode: Mode) => void) => Unsubscribe; + onVolumeChange: (callback: (volume: number) => void) => Unsubscribe; + }; +} + +type RealtimeVoiceAdapter = { + connect: (options: { + abortSignal?: AbortSignal; + }) => RealtimeVoiceAdapter.Session; +}; + +type ReasoningGroupComponent = ComponentType<ReasoningGroupProps>; + +type ReasoningGroupProps = PropsWithChildren<{ + startIndex: number; + endIndex: number; +}>; + +type ReasoningMessagePart = { + readonly type: "reasoning"; + readonly text: string; + readonly providerMetadata?: PartProviderMetadata; + readonly parentId?: string; +}; + +type ReasoningMessagePartComponent = ComponentType<ReasoningMessagePartProps>; + +type ReasoningMessagePartProps = MessagePartState & ReasoningMessagePart; + +type RegisteredTrigger = { + readonly char: string; + readonly behavior?: TriggerBehavior; + readonly resource: TriggerPopoverResourceOutput; +}; + +type ReloadConfig = { + runConfig?: RunConfig; +}; + +type RemoteThreadInitializeResponse = { + remoteId: string; + externalId: string | undefined; +}; + +type RemoteThreadListAdapter = { + list(params?: RemoteThreadListPageOptions): Promise<RemoteThreadListResponse>; + rename(remoteId: string, newTitle: string): Promise<void>; + updateCustom?(remoteId: string, custom: Record<string, unknown> | undefined): Promise<void>; + archive(remoteId: string): Promise<void>; + unarchive(remoteId: string): Promise<void>; + delete(remoteId: string): Promise<void>; + initialize(threadId: string): Promise<RemoteThreadInitializeResponse>; + generateTitle(remoteId: string, unstable_messages: readonly ThreadMessage[]): Promise<AssistantStream>; + fetch(threadId: string): Promise<RemoteThreadMetadata>; + unstable_Provider?: ComponentType<PropsWithChildren> | undefined; +}; + +type RemoteThreadListOptions = { + runtimeHook: () => AssistantRuntime; + adapter: RemoteThreadListAdapter; + initialThreadId?: string | undefined; + threadId?: string | undefined; + onThreadIdChange?: ((threadId: string | undefined) => void) | undefined; + allowNesting?: boolean | undefined; +}; + +type RemoteThreadListPageOptions = { + after?: string | undefined; +}; + +type RemoteThreadListResponse = { + threads: RemoteThreadMetadata[]; + nextCursor?: string | undefined; +}; + +type RemoteThreadMetadata = { + readonly status: "archived" | "regular"; + readonly remoteId: string; + readonly externalId?: string | undefined; + readonly title?: string | undefined; + readonly lastMessageAt?: Date | undefined; + readonly custom?: Record<string, unknown> | undefined; +}; + +type ReportToolCall = { + tool_name: string; + tool_call_id: string; + tool_args?: string; + tool_result?: string; + tool_source?: "backend" | "frontend" | "mcp"; + start_ms?: number; + end_ms?: number; + sampling_calls?: SamplingCallData[]; +}; + +type RequireAtLeastOne<T, Keys extends keyof T = keyof T> = Pick<T, Exclude<keyof T, Keys>> & { + [K in Keys]-?: Required<Pick<T, K>> & Partial<Pick<T, Exclude<Keys, K>>>; +}[Keys]; + +type RequireAtLeastOne$1<T, Keys extends keyof T = keyof T> = Pick<T, Exclude<keyof T, Keys>> & { + [K in Keys]-?: Required<Pick<T, K>> & Partial<Pick<T, Exclude<Keys, K>>>; +}[Keys]; + +type Resource<R, A extends readonly unknown[] = any[]> = (...args: A) => ResourceElement<R, A>; + +type ResourceElement<R, A extends readonly unknown[] = any[]> = { + readonly hook: (...args: A) => R; + readonly args: Readonly<A>; + readonly key?: string | number; + readonly deps?: readonly unknown[]; +}; + +type RespondToToolApprovalOptions = { + approvalId: string; + approved: boolean; + optionId?: string; + reason?: string; +}; + +type ResumeRunConfig = StartRunConfig & { + stream?: (options: ChatModelRunOptions) => AsyncGenerator<ChatModelRunResult, void, unknown>; +}; + +type ResumeToolCallOptions = { + toolCallId: string; + payload: unknown; +}; + +type RunConfig = { + readonly custom?: Record<string, unknown>; +}; + +declare namespace RuntimeAdapterProvider { + type Props = { + adapters: RuntimeAdapters; + children: ReactNode; + }; +} + +declare const RuntimeAdapterProvider: FC<RuntimeAdapterProvider.Props>; + +type RuntimeAdapters = { + modelContext?: ModelContextProvider | undefined; + history?: ThreadHistoryAdapter | undefined; + attachments?: AttachmentAdapter | undefined; +}; + +type RuntimeCapabilities = { + readonly switchToBranch: boolean; + readonly switchBranchDuringRun: boolean; + readonly edit: boolean; + readonly reload: boolean; + readonly delete: boolean; + readonly cancel: boolean; + readonly unstable_copy: boolean; + readonly speech: boolean; + readonly dictation: boolean; + readonly voice: boolean; + readonly attachments: boolean; + readonly feedback: boolean; + readonly queue: boolean; +}; + +type SamplingCallData = { + model_id?: string; + input_tokens?: number; + output_tokens?: number; + reasoning_tokens?: number; + cached_input_tokens?: number; + duration_ms?: number; +}; + +type SandboxHostConfig = { + sandbox?: SandboxOption[]; + useShadowDom?: boolean; + enableBrowserCaching?: boolean; + salt?: string; + product?: string; + className?: string; + style?: CSSProperties; + unsafeDocumentWrite?: boolean; +}; + +type SandboxOption = "allow-downloads" | "allow-forms" | "allow-modals" | "allow-popups" | "allow-popups-to-escape-sandbox" | "allow-same-origin" | "allow-scripts"; + +interface ScopeRegistry { + [key: string]: { methods: any; meta?: any; events?: any }; +} + +type SelectItemOverride = (item: Unstable_TriggerItem) => boolean; + +declare namespace SelectionToolbarPrimitiveQuote { + type Element = ComponentRef<typeof Primitive$1.button>; + type Props = ComponentPropsWithoutRef<typeof Primitive$1.button>; +} + +declare const SelectionToolbarPrimitiveQuote: import("react").ForwardRefExoticComponent<Omit<Omit<import("react").ClassAttributes<HTMLButtonElement> & import("react").ButtonHTMLAttributes<HTMLButtonElement> & { + asChild?: boolean; +}, "ref"> & { + render?: import("react").ReactElement | undefined; +} & import("react").RefAttributes<HTMLButtonElement>, "ref"> & import("react").RefAttributes<HTMLButtonElement>>; + +declare namespace SelectionToolbarPrimitiveRoot { + type Element = ComponentRef<typeof Primitive$1.div>; + type Props = ComponentPropsWithoutRef<typeof Primitive$1.div>; +} + +declare const SelectionToolbarPrimitiveRoot: import("react").ForwardRefExoticComponent<Omit<Omit<import("react").ClassAttributes<HTMLDivElement> & import("react").HTMLAttributes<HTMLDivElement> & { + asChild?: boolean; +}, "ref"> & { + render?: import("react").ReactElement | undefined; +} & import("react").RefAttributes<HTMLDivElement>, "ref"> & import("react").RefAttributes<HTMLDivElement>>; + +type SendCommandsRequestBody = { + commands: QueuedCommand[]; + state: unknown; + system: string | undefined; + tools: Record<string, unknown> | undefined; + callSettings: LanguageModelV1CallSettings | undefined; + config: LanguageModelConfig | undefined; + threadId: string | null; + parentId?: string | null; + [key: string]: unknown; +}; + +type SendOptions = { + startRun?: boolean; + steer?: boolean; +}; + +type SerializedModelContext = { + system?: string; + tools?: Record<string, SerializedTool>; +}; + +type SerializedTool = { + description?: string; + parameters: any; + disabled?: boolean; + type?: string; +}; + +declare class SimpleImageAttachmentAdapter implements AttachmentAdapter { + accept: string; + add(state: { + file: File; + }): Promise<PendingAttachment>; + send(attachment: PendingAttachment): Promise<CompleteAttachment>; + remove(): Promise<void>; +} + +declare class SimpleTextAttachmentAdapter implements AttachmentAdapter { + accept: string; + add(state: { + file: File; + }): Promise<PendingAttachment>; + send(attachment: PendingAttachment): Promise<CompleteAttachment>; + remove(): Promise<void>; +} + +declare const SingleThreadList: Resource<ClientOutput<"threads">, [ + SingleThreadListProps +]>; + +type SingleThreadListProps = { + thread: ClientElement<"thread">; +}; + +type SizeHandle = { + setHeight: (height: number) => void; + unregister: Unsubscribe; +}; + +type SmoothOptions = { + drainMs?: number | undefined; + maxCharIntervalMs?: number | undefined; + maxCharsPerFrame?: number | undefined; + minCommitMs?: number | undefined; +}; + +type SnapshotCarrierMessage = { + role: string; + metadata?: unknown; + content?: readonly unknown[] | undefined; +}; + +type SourceMessagePart = { + readonly type: "source"; + readonly sourceType: "url"; + readonly id: string; + readonly url: string; + readonly title?: string; + readonly providerMetadata?: SourceProviderMetadata; + readonly parentId?: string; +} | { + readonly type: "source"; + readonly sourceType: "document"; + readonly id: string; + readonly url?: undefined; + readonly title: string; + readonly mediaType: string; + readonly filename?: string; + readonly providerMetadata?: SourceProviderMetadata; + readonly parentId?: string; +}; + +type SourceMessagePartComponent = ComponentType<SourceMessagePartProps>; + +type SourceMessagePartProps = MessagePartState & SourceMessagePart; + +type SourceProviderMetadata = PartProviderMetadata; + +interface SpeechRecognitionConstructor { + new (): SpeechRecognitionInstance; +} + +interface SpeechRecognitionInstance extends EventTarget { + lang: string; + continuous: boolean; + interimResults: boolean; + start(): void; + stop(): void; + abort(): void; +} + +type SpeechState = { + readonly messageId: string; + readonly status: SpeechSynthesisAdapter.Status; +}; + +declare namespace SpeechSynthesisAdapter { + type Status = { + type: "running" | "starting"; + } | { + type: "ended"; + reason: "cancelled" | "error" | "finished"; + error?: unknown; + }; + type Utterance = { + status: Status; + cancel: () => void; + subscribe: (callback: () => void) => Unsubscribe; + }; +} + +type SpeechSynthesisAdapter = { + speak: (text: string) => SpeechSynthesisAdapter.Utterance; +}; + +type StartRunConfig = { + parentId: string | null; + sourceId: string | null; + runConfig: RunConfig; +}; + +type StateUpdater<TState> = TState | ((prev: TState) => TState); + +type StateUpdater$1<TState> = TState | ((prev: TState) => TState); + +type SubmitFeedbackOptions = { + messageId: string; + type: "negative" | "positive"; +}; + +type Subscribable = { + subscribe: (callback: () => void) => Unsubscribe; +}; + +type SubscribableWithState<TState, TPath> = Subscribable & { + path: TPath; + getState: () => TState; +}; + +type SuggestionAdapter = { + generate: (options: SuggestionAdapterGenerateOptions) => Promise<readonly ThreadSuggestion[]> | AsyncGenerator<readonly ThreadSuggestion[], void>; +}; + +type SuggestionAdapterGenerateOptions = { + messages: readonly ThreadMessage[]; +}; + +declare const SuggestionByIndexProvider: FC<SuggestionByIndexProviderProps>; + +type SuggestionByIndexProviderProps = PropsWithChildren<{ + index: number; +}>; + +type SuggestionConfig = string | { + title: string; + label: string; + prompt: string; +}; + +declare namespace SuggestionPrimitiveDescription { + type Element = ElementRef<typeof Primitive$1.span>; + type Props = ComponentPropsWithoutRef<typeof Primitive$1.span>; +} + +declare const SuggestionPrimitiveDescription: import("react").ForwardRefExoticComponent<Omit<Omit<import("react").ClassAttributes<HTMLSpanElement> & import("react").HTMLAttributes<HTMLSpanElement> & { + asChild?: boolean; +}, "ref"> & { + render?: import("react").ReactElement | undefined; +} & import("react").RefAttributes<HTMLSpanElement>, "ref"> & import("react").RefAttributes<HTMLSpanElement>>; + +declare namespace SuggestionPrimitiveTitle { + type Element = ElementRef<typeof Primitive$1.span>; + type Props = ComponentPropsWithoutRef<typeof Primitive$1.span>; +} + +declare const SuggestionPrimitiveTitle: import("react").ForwardRefExoticComponent<Omit<Omit<import("react").ClassAttributes<HTMLSpanElement> & import("react").HTMLAttributes<HTMLSpanElement> & { + asChild?: boolean; +}, "ref"> & { + render?: import("react").ReactElement | undefined; +} & import("react").RefAttributes<HTMLSpanElement>, "ref"> & import("react").RefAttributes<HTMLSpanElement>>; + +declare namespace SuggestionPrimitiveTrigger { + type Element = ActionButtonElement; + type Props = ActionButtonProps<typeof useSuggestionTrigger>; +} + +declare const SuggestionPrimitiveTrigger: import("react").ForwardRefExoticComponent<Omit<Omit<import("react").ClassAttributes<HTMLButtonElement> & import("react").ButtonHTMLAttributes<HTMLButtonElement> & { + asChild?: boolean; +}, "ref"> & { + render?: import("react").ReactElement | undefined; +} & import("react").RefAttributes<HTMLButtonElement>, "ref"> & { + send?: boolean | undefined; + clearComposer?: boolean | undefined; +} & import("react").RefAttributes<HTMLButtonElement>>; + +type SuggestionState = { + title: string; + label: string; + prompt: string; +}; + +declare const Suggestions: Resource<ClientOutput<"suggestions">, [ + suggestions?: SuggestionConfig[] | undefined +]>; + +type SuggestionsComponentConfig = { + Suggestion: ComponentType; +}; + +declare const TOOL_RESPONSE_SYMBOL: unique symbol; + +type TextMessagePart = { + readonly type: "text"; + readonly text: string; + readonly providerMetadata?: PartProviderMetadata; + readonly parentId?: string; +}; + +type TextMessagePartComponent = ComponentType<TextMessagePartProps>; + +type TextMessagePartProps = MessagePartState & TextMessagePart; + +declare const TextMessagePartProvider: FC<PropsWithChildren<{ + text: string; + isRunning?: boolean; +}>>; + +type TextPart = { + readonly type: "text"; + readonly text: string; +}; + +type ThreadAssistantMessage = MessageCommonProps & { + readonly role: "assistant"; + readonly content: readonly ThreadAssistantMessagePart[]; + readonly status: MessageStatus; + readonly metadata: { + readonly unstable_state: ReadonlyJSONValue; + readonly unstable_annotations: readonly ReadonlyJSONValue[]; + readonly unstable_data: readonly ReadonlyJSONValue[]; + readonly steps: readonly ThreadStep[]; + readonly submittedFeedback?: { + readonly type: "negative" | "positive"; + }; + readonly timing?: MessageTiming; + readonly isOptimistic?: boolean; + readonly custom: Record<string, unknown>; + }; +}; + +type ThreadAssistantMessagePart = TextMessagePart | ReasoningMessagePart | ToolCallMessagePart | SourceMessagePart | FileMessagePart | ImageMessagePart | DataMessagePart | GenerativeUIMessagePart; + +declare class ThreadComposerAttachmentRuntimeImpl extends ComposerAttachmentRuntime<"thread-composer"> { + get source(): "thread-composer"; +} + +type ThreadComposerAttachmentState = Attachment & { + readonly source: "thread-composer"; +}; + +type ThreadComposerRuntime = Omit<ComposerRuntime, "getAttachmentByIndex" | "getState"> & { + readonly path: ComposerRuntimePath & { + composerSource: "thread"; + }; + readonly type: "thread"; + getState(): ThreadComposerState; + getAttachmentByIndex(idx: number): AttachmentRuntime & { + source: "thread-composer"; + }; +}; + +type ThreadComposerRuntimeCore = ComposerRuntimeCore; + +type ThreadComposerRuntimeCoreBinding = SubscribableWithState<ThreadComposerRuntimeCore | undefined, ComposerRuntimePath & { + composerSource: "thread"; +}>; + +declare class ThreadComposerRuntimeImpl extends ComposerRuntimeImpl implements ThreadComposerRuntime { + get path(): ComposerRuntimePath & { + composerSource: "thread"; + }; + get type(): "thread"; + private _getState; + constructor(core: ThreadComposerRuntimeCoreBinding); + getState(): ThreadComposerState; + getAttachmentByIndex(idx: number): ThreadComposerAttachmentRuntimeImpl; +} + +type ThreadComposerState = BaseComposerState & { + readonly type: "thread"; +}; + +type ThreadData = { + externalId: string; +}; + +type ThreadData$1 = { + externalId: string | undefined; +}; + +type ThreadHistoryAdapter = { + load(): Promise<ExportedMessageRepository & { + state?: ReadonlyJSONValue; + unstable_resume?: boolean; + }>; + resume?(options: ChatModelRunOptions): AsyncGenerator<ChatModelRunResult, void, unknown>; + append(item: ExportedMessageRepositoryItem): Promise<void>; + delete?(items: ExportedMessageRepositoryItem[]): Promise<void>; + withFormat?<TMessage, TStorageFormat extends Record<string, unknown>>(formatAdapter: MessageFormatAdapter<TMessage, TStorageFormat>): GenericThreadHistoryAdapter<TMessage>; +}; + +type ThreadIfFilters = { + empty: boolean | undefined; + running: boolean | undefined; + disabled: boolean | undefined; +}; + +declare const ThreadListItemByIndexProvider: FC<PropsWithChildren<{ + index: number; + archived: boolean; +}>>; + +type ThreadListItemCoreState = { + readonly id: string; + readonly remoteId: string | undefined; + readonly externalId: string | undefined; + readonly status: ThreadListItemStatus; + readonly title?: string | undefined; + readonly lastMessageAt?: Date | undefined; + readonly custom?: Record<string, unknown> | undefined; + readonly runtime?: ThreadRuntimeCore | undefined; +}; + +type ThreadListItemEventCallback<E extends ThreadListItemEventType> = (payload: ThreadListItemEventPayload[E]) => void; + +type ThreadListItemEventPayload = { + switchedTo: Record<string, never>; + switchedAway: Record<string, never>; +}; + +type ThreadListItemEventType = keyof ThreadListItemEventPayload; + +declare namespace ThreadListItemMorePrimitiveContent { + type Element = ComponentRef<typeof DropdownMenu.Content>; + type Props = WithRenderPropProps<typeof DropdownMenu.Content> & { + portalProps?: ComponentPropsWithoutRef<typeof DropdownMenu.Portal> | undefined; + }; +} + +declare const ThreadListItemMorePrimitiveContent: import("react").ForwardRefExoticComponent<Omit<DropdownMenu.DropdownMenuContentProps & import("react").RefAttributes<HTMLDivElement>, "ref"> & { + render?: import("react").ReactElement | undefined; +} & { + portalProps?: ComponentPropsWithoutRef<typeof DropdownMenu.Portal> | undefined; +} & import("react").RefAttributes<HTMLDivElement>>; + +declare namespace ThreadListItemMorePrimitiveItem { + type Element = ComponentRef<typeof DropdownMenu.Item>; + type Props = WithRenderPropProps<typeof DropdownMenu.Item>; +} + +declare const ThreadListItemMorePrimitiveItem: import("react").ForwardRefExoticComponent<Omit<DropdownMenu.DropdownMenuItemProps & import("react").RefAttributes<HTMLDivElement>, "ref"> & { + render?: import("react").ReactElement | undefined; +} & import("react").RefAttributes<HTMLDivElement>>; + +declare namespace ThreadListItemMorePrimitiveRoot { + type Props = DropdownMenu.DropdownMenuProps & { + sharedFocusGroup?: boolean | undefined; + }; +} + +declare const ThreadListItemMorePrimitiveRoot: FC<ThreadListItemMorePrimitiveRoot.Props>; + +declare namespace ThreadListItemMorePrimitiveSeparator { + type Element = ComponentRef<typeof DropdownMenu.Separator>; + type Props = WithRenderPropProps<typeof DropdownMenu.Separator>; +} + +declare const ThreadListItemMorePrimitiveSeparator: import("react").ForwardRefExoticComponent<Omit<DropdownMenu.DropdownMenuSeparatorProps & import("react").RefAttributes<HTMLDivElement>, "ref"> & { + render?: import("react").ReactElement | undefined; +} & import("react").RefAttributes<HTMLDivElement>>; + +declare namespace ThreadListItemMorePrimitiveTrigger { + type Element = ComponentRef<typeof DropdownMenu.Trigger>; + type Props = WithRenderPropProps<typeof DropdownMenu.Trigger>; +} + +declare const ThreadListItemMorePrimitiveTrigger: import("react").ForwardRefExoticComponent<Omit<DropdownMenu.DropdownMenuTriggerProps & import("react").RefAttributes<HTMLButtonElement>, "ref"> & { + render?: import("react").ReactElement | undefined; +} & import("react").RefAttributes<HTMLButtonElement>>; + +declare namespace ThreadListItemPrimitiveArchive { + type Element = ActionButtonElement; + type Props = ActionButtonProps<typeof useThreadListItemArchive>; +} + +declare const ThreadListItemPrimitiveArchive: import("react").ForwardRefExoticComponent<Omit<Omit<import("react").ClassAttributes<HTMLButtonElement> & import("react").ButtonHTMLAttributes<HTMLButtonElement> & { + asChild?: boolean; +}, "ref"> & { + render?: import("react").ReactElement | undefined; +} & import("react").RefAttributes<HTMLButtonElement>, "ref"> & import("react").RefAttributes<HTMLButtonElement>>; + +declare namespace ThreadListItemPrimitiveDelete { + type Element = ActionButtonElement; + type Props = ActionButtonProps<typeof useThreadListItemDelete>; +} + +declare const ThreadListItemPrimitiveDelete: import("react").ForwardRefExoticComponent<Omit<Omit<import("react").ClassAttributes<HTMLButtonElement> & import("react").ButtonHTMLAttributes<HTMLButtonElement> & { + asChild?: boolean; +}, "ref"> & { + render?: import("react").ReactElement | undefined; +} & import("react").RefAttributes<HTMLButtonElement>, "ref"> & import("react").RefAttributes<HTMLButtonElement>>; + +declare namespace ThreadListItemPrimitiveRoot { + type Element = ComponentRef<typeof Primitive$1.div>; + type Props = PrimitiveDivProps; +} + +declare const ThreadListItemPrimitiveRoot: import("react").ForwardRefExoticComponent<Omit<Omit<import("react").ClassAttributes<HTMLDivElement> & import("react").HTMLAttributes<HTMLDivElement> & { + asChild?: boolean; +}, "ref"> & { + render?: import("react").ReactElement | undefined; +} & import("react").RefAttributes<HTMLDivElement>, "ref"> & import("react").RefAttributes<HTMLDivElement>>; + +declare namespace ThreadListItemPrimitiveTitle { + type Props = { + fallback?: ReactNode; + }; +} + +declare const ThreadListItemPrimitiveTitle: FC<ThreadListItemPrimitiveTitle.Props>; + +declare namespace ThreadListItemPrimitiveTrigger { + type Element = ActionButtonElement; + type Props = ActionButtonProps<typeof useThreadListItemTrigger>; +} + +declare const ThreadListItemPrimitiveTrigger: import("react").ForwardRefExoticComponent<Omit<Omit<import("react").ClassAttributes<HTMLButtonElement> & import("react").ButtonHTMLAttributes<HTMLButtonElement> & { + asChild?: boolean; +}, "ref"> & { + render?: import("react").ReactElement | undefined; +} & import("react").RefAttributes<HTMLButtonElement>, "ref"> & import("react").RefAttributes<HTMLButtonElement>>; + +declare namespace ThreadListItemPrimitiveUnarchive { + type Element = ActionButtonElement; + type Props = ActionButtonProps<typeof useThreadListItemUnarchive>; +} + +declare const ThreadListItemPrimitiveUnarchive: import("react").ForwardRefExoticComponent<Omit<Omit<import("react").ClassAttributes<HTMLButtonElement> & import("react").ButtonHTMLAttributes<HTMLButtonElement> & { + asChild?: boolean; +}, "ref"> & { + render?: import("react").ReactElement | undefined; +} & import("react").RefAttributes<HTMLButtonElement>, "ref"> & import("react").RefAttributes<HTMLButtonElement>>; + +type ThreadListItemRuntime = { + readonly path: ThreadListItemRuntimePath; + getState(): ThreadListItemState$1; + initialize(): Promise<{ + remoteId: string; + externalId: string | undefined; + }>; + generateTitle(): Promise<void>; + switchTo(options?: { + unarchive?: boolean; + }): Promise<void>; + rename(newTitle: string): Promise<void>; + updateCustom(custom: Record<string, unknown> | undefined): Promise<void>; + archive(): Promise<void>; + unarchive(): Promise<void>; + delete(): Promise<void>; + detach(): void; + subscribe(callback: () => void): Unsubscribe; + unstable_on<E extends ThreadListItemEventType>(event: E, callback: ThreadListItemEventCallback<E>): Unsubscribe; + __internal_getRuntime(): ThreadListItemRuntime; +}; + +type ThreadListItemRuntimeBinding = SubscribableWithState<ThreadListItemState$1, ThreadListItemRuntimePath>; + +declare class ThreadListItemRuntimeImpl implements ThreadListItemRuntime { + private _core; + private _threadListBinding; + get path(): ThreadListItemRuntimePath; + constructor(_core: ThreadListItemStateBinding, _threadListBinding: ThreadListRuntimeCoreBinding); + protected __internal_bindMethods(): void; + getState(): ThreadListItemState$1; + switchTo(options?: { + unarchive?: boolean; + }): Promise<void>; + rename(newTitle: string): Promise<void>; + updateCustom(custom: Record<string, unknown> | undefined): Promise<void>; + archive(): Promise<void>; + unarchive(): Promise<void>; + delete(): Promise<void>; + initialize(): Promise<{ + remoteId: string; + externalId: string | undefined; + }>; + generateTitle(): Promise<void>; + unstable_on<E extends ThreadListItemEventType>(event: E, callback: ThreadListItemEventCallback<E>): Unsubscribe; + subscribe(callback: () => void): Unsubscribe; + detach(): void; + __internal_getRuntime(): ThreadListItemRuntime; +} + +type ThreadListItemRuntimePath = { + readonly ref: string; + readonly threadSelector: { + readonly type: "main"; + } | { + readonly type: "index"; + readonly index: number; + } | { + readonly type: "archiveIndex"; + readonly index: number; + } | { + readonly type: "threadId"; + readonly threadId: string; + }; +}; + +declare const ThreadListItemRuntimeProvider: FC<PropsWithChildren<{ + runtime: ThreadListItemRuntime; +}>>; + +type ThreadListItemState = { + readonly id: string; + readonly remoteId: string | undefined; + readonly externalId: string | undefined; + readonly title?: string | undefined; + readonly lastMessageAt?: Date | undefined; + readonly status: ThreadListItemStatus; + readonly custom?: Record<string, unknown> | undefined; +}; + +type ThreadListItemState$1 = { + readonly isMain: boolean; + readonly id: string; + readonly remoteId: string | undefined; + readonly externalId: string | undefined; + readonly status: ThreadListItemStatus; + readonly title?: string | undefined; + readonly lastMessageAt?: Date | undefined; + readonly custom?: Record<string, unknown> | undefined; +}; + +type ThreadListItemStateBinding = SubscribableWithState<ThreadListItemState$1, ThreadListItemRuntimePath>; + +type ThreadListItemStatus = "archived" | "deleted" | "new" | "regular"; + +type ThreadListItemsComponentConfig = { + ThreadListItem: ComponentType; +}; + +declare namespace ThreadListPrimitiveItemByIndex { + type Props = { + index: number; + archived?: boolean | undefined; + components: ThreadListItemsComponentConfig; + }; +} + +declare const ThreadListPrimitiveItemByIndex: FC<ThreadListPrimitiveItemByIndex.Props>; + +declare namespace ThreadListPrimitiveItems { + type Props = { + archived?: boolean | undefined; + } & ({ + components: ThreadListItemsComponentConfig; + children?: never; + } | { + children: (value: { + threadListItem: ThreadListItemState; + }) => ReactNode; + components?: never; + }); +} + +declare const ThreadListPrimitiveItems: FC<ThreadListPrimitiveItems.Props>; + +declare namespace ThreadListPrimitiveLoadMore { + type Element = ActionButtonElement; + type Props = ActionButtonProps<typeof useThreadListLoadMore>; +} + +declare const ThreadListPrimitiveLoadMore: import("react").ForwardRefExoticComponent<Omit<Omit<import("react").ClassAttributes<HTMLButtonElement> & import("react").ButtonHTMLAttributes<HTMLButtonElement> & { + asChild?: boolean; +}, "ref"> & { + render?: import("react").ReactElement | undefined; +} & import("react").RefAttributes<HTMLButtonElement>, "ref"> & import("react").RefAttributes<HTMLButtonElement>>; + +declare namespace ThreadListPrimitiveNew { + type Element = ActionButtonElement; + type Props = ActionButtonProps<() => void>; +} + +declare const ThreadListPrimitiveNew: import("react").ForwardRefExoticComponent<Omit<Omit<import("react").ClassAttributes<HTMLButtonElement> & import("react").ButtonHTMLAttributes<HTMLButtonElement> & { + asChild?: boolean; +}, "ref"> & { + render?: import("react").ReactElement | undefined; +} & import("react").RefAttributes<HTMLButtonElement>, "ref"> & import("react").RefAttributes<HTMLButtonElement>>; + +declare namespace ThreadListPrimitiveRoot { + type Element = ComponentRef<typeof Primitive$1.div>; + type Props = PrimitiveDivProps$1; +} + +declare const ThreadListPrimitiveRoot: import("react").ForwardRefExoticComponent<Omit<Omit<import("react").ClassAttributes<HTMLDivElement> & import("react").HTMLAttributes<HTMLDivElement> & { + asChild?: boolean; +}, "ref"> & { + render?: import("react").ReactElement | undefined; +} & import("react").RefAttributes<HTMLDivElement>, "ref"> & import("react").RefAttributes<HTMLDivElement>>; + +type ThreadListRuntime = { + getState(): ThreadListState; + subscribe(callback: () => void): Unsubscribe; + readonly main: ThreadRuntime; + getById(threadId: string): ThreadRuntime; + readonly mainItem: ThreadListItemRuntime; + getItemById(threadId: string): ThreadListItemRuntime; + getItemByIndex(idx: number): ThreadListItemRuntime; + getArchivedItemByIndex(idx: number): ThreadListItemRuntime; + switchToThread(threadId: string, options?: { + unarchive?: boolean; + }): Promise<void>; + switchToNewThread(): Promise<void>; + getLoadThreadsPromise(): Promise<void>; + reload(): Promise<void>; + loadMore(): Promise<void>; +}; + +type ThreadListRuntimeCore = { + readonly isLoading: boolean; + readonly isLoadingMore?: boolean; + readonly hasMore?: boolean; + mainThreadId: string; + newThreadId: string | undefined; + threadIds: readonly string[]; + archivedThreadIds: readonly string[]; + readonly threadItems: Readonly<Record<string, ThreadListItemCoreState>>; + getMainThreadRuntimeCore(): ThreadRuntimeCore; + getThreadRuntimeCore(threadId: string): ThreadRuntimeCore; + getItemById(threadId: string): ThreadListItemCoreState | undefined; + switchToThread(threadId: string, options?: { + unarchive?: boolean; + }): Promise<void>; + switchToNewThread(): Promise<void>; + getLoadThreadsPromise(): Promise<void>; + reload?(): Promise<void>; + loadMore?(): Promise<void>; + detach(threadId: string): Promise<void>; + rename(threadId: string, newTitle: string): Promise<void>; + updateCustom?(threadId: string, custom: Record<string, unknown> | undefined): Promise<void>; + archive(threadId: string): Promise<void>; + unarchive(threadId: string): Promise<void>; + delete(threadId: string): Promise<void>; + initialize(threadId: string): Promise<{ + remoteId: string; + externalId: string | undefined; + }>; + generateTitle(threadId: string): Promise<void>; + subscribe(callback: () => void): Unsubscribe; +}; + +type ThreadListRuntimeCoreBinding = ThreadListRuntimeCore; + +declare class ThreadListRuntimeImpl implements ThreadListRuntime { + private _core; + private _runtimeFactory; + private _getState; + constructor(_core: ThreadListRuntimeCoreBinding, _runtimeFactory?: new (binding: ThreadRuntimeCoreBinding, threadListItemBinding: ThreadListItemRuntimeBinding) => ThreadRuntime); + protected __internal_bindMethods(): void; + switchToThread(threadId: string, options?: { + unarchive?: boolean; + }): Promise<void>; + switchToNewThread(): Promise<void>; + getLoadThreadsPromise(): Promise<void>; + reload(): Promise<void>; + loadMore(): Promise<void>; + getState(): ThreadListState; + subscribe(callback: () => void): Unsubscribe; + private _mainThreadListItemRuntime; + readonly main: ThreadRuntime; + get mainItem(): ThreadListItemRuntimeImpl; + getById(threadId: string): ThreadRuntime; + getItemByIndex(idx: number): ThreadListItemRuntimeImpl; + getArchivedItemByIndex(idx: number): ThreadListItemRuntimeImpl; + getItemById(threadId: string): ThreadListItemRuntimeImpl; +} + +type ThreadListState = { + readonly mainThreadId: string; + readonly newThreadId: string | undefined; + readonly threadIds: readonly string[]; + readonly archivedThreadIds: readonly string[]; + readonly isLoading: boolean; + readonly isLoadingMore: boolean; + readonly hasMore: boolean; + readonly threadItems: Readonly<Record<string, Omit<ThreadListItemState$1, "isMain" | "threadId">>>; +}; + +type ThreadMessage = BaseThreadMessage & (ThreadSystemMessage | ThreadUserMessage | ThreadAssistantMessage); + +type ThreadMessageClientProps = { + message: ThreadMessage; + index: number; + isLast?: boolean; + branchNumber?: number; + branchCount?: number; +}; + +type ThreadMessageLike = { + readonly role: "assistant" | "system" | "user"; + readonly content: string | readonly (TextMessagePart | ReasoningMessagePart | SourceMessagePart | ImageMessagePart | FileMessagePart | DataMessagePart | GenerativeUIMessagePart | Unstable_AudioMessagePart | DataPrefixedPart | { + readonly type: "tool-call"; + readonly toolCallId?: string; + readonly toolName: string; + readonly args?: ReadonlyJSONObject; + readonly argsText?: string; + readonly artifact?: any; + readonly result?: any | undefined; + readonly isError?: boolean | undefined; + readonly parentId?: string | undefined; + readonly messages?: readonly ThreadMessage[] | undefined; + readonly interrupt?: { + type: "human"; + payload: unknown; + }; + readonly timing?: ToolCallTiming; + readonly providerMetadata?: PartProviderMetadata; + readonly approval?: { + readonly id: string; + readonly approved?: boolean; + readonly reason?: string; + readonly isAutomatic?: boolean; + readonly options?: readonly ToolApprovalOption[]; + readonly optionId?: string; + readonly resolution?: "cancelled" | "expired"; + }; + })[]; + readonly id?: string | undefined; + readonly createdAt?: Date | undefined; + readonly status?: MessageStatus | undefined; + readonly attachments?: readonly (Omit<CompleteAttachment, "content"> & { + readonly content: readonly (ThreadUserMessagePart | DataPrefixedPart)[]; + })[] | undefined; + readonly metadata?: { + readonly unstable_state?: ReadonlyJSONValue; + readonly unstable_annotations?: readonly ReadonlyJSONValue[] | undefined; + readonly unstable_data?: readonly ReadonlyJSONValue[] | undefined; + readonly steps?: readonly ThreadStep[] | undefined; + readonly timing?: MessageTiming | undefined; + readonly submittedFeedback?: { + readonly type: "negative" | "positive"; + }; + readonly isOptimistic?: boolean | undefined; + readonly custom?: Record<string, unknown> | undefined; + } | undefined; +}; + +declare namespace ThreadPrimitiveEmpty { + type Props = PropsWithChildren; +} + +declare const ThreadPrimitiveEmpty: FC<ThreadPrimitiveEmpty.Props>; + +declare namespace ThreadPrimitiveIf { + type Props = PropsWithChildren<UseThreadIfProps>; +} + +declare const ThreadPrimitiveIf: FC<ThreadPrimitiveIf.Props>; + +declare namespace ThreadPrimitiveMessageByIndex { + type Props = { + index: number; + components: MessagesComponentConfig; + }; +} + +declare const ThreadPrimitiveMessageByIndex: FC<ThreadPrimitiveMessageByIndex.Props>; + +declare namespace ThreadPrimitiveMessages { + type Props = { + components: MessagesComponentConfig; + children?: never; + } | { + children: (value: { + message: MessageState; + }) => ReactNode; + components?: never; + }; +} + +declare const ThreadPrimitiveMessages: import("react").NamedExoticComponent<ThreadPrimitiveMessages.Props>; + +declare namespace ThreadPrimitiveRoot { + type Element = ComponentRef<typeof Primitive$1.div>; + type Props = ComponentPropsWithoutRef<typeof Primitive$1.div>; +} + +declare const ThreadPrimitiveRoot: import("react").ForwardRefExoticComponent<Omit<Omit<import("react").ClassAttributes<HTMLDivElement> & import("react").HTMLAttributes<HTMLDivElement> & { + asChild?: boolean; +}, "ref"> & { + render?: import("react").ReactElement | undefined; +} & import("react").RefAttributes<HTMLDivElement>, "ref"> & import("react").RefAttributes<HTMLDivElement>>; + +declare namespace ThreadPrimitiveScrollToBottom { + type Element = ActionButtonElement; + type Props = ActionButtonProps<typeof useThreadScrollToBottom>; +} + +declare const ThreadPrimitiveScrollToBottom: import("react").ForwardRefExoticComponent<Omit<Omit<import("react").ClassAttributes<HTMLButtonElement> & import("react").ButtonHTMLAttributes<HTMLButtonElement> & { + asChild?: boolean; +}, "ref"> & { + render?: import("react").ReactElement | undefined; +} & import("react").RefAttributes<HTMLButtonElement>, "ref"> & useThreadScrollToBottom.Options & import("react").RefAttributes<HTMLButtonElement>>; + +declare namespace ThreadPrimitiveSuggestion { + type Element = ActionButtonElement; + type Props = ActionButtonProps<typeof useThreadSuggestion>; +} + +declare const ThreadPrimitiveSuggestion: import("react").ForwardRefExoticComponent<Omit<Omit<import("react").ClassAttributes<HTMLButtonElement> & import("react").ButtonHTMLAttributes<HTMLButtonElement> & { + asChild?: boolean; +}, "ref"> & { + render?: import("react").ReactElement | undefined; +} & import("react").RefAttributes<HTMLButtonElement>, "ref"> & { + prompt: string; + send?: boolean | undefined; + clearComposer?: boolean | undefined; + autoSend?: boolean | undefined; + method?: "replace"; +} & import("react").RefAttributes<HTMLButtonElement>>; + +declare namespace ThreadPrimitiveSuggestionByIndex { + type Props = { + index: number; + components: SuggestionsComponentConfig; + }; +} + +declare const ThreadPrimitiveSuggestionByIndex: FC<ThreadPrimitiveSuggestionByIndex.Props>; + +declare namespace ThreadPrimitiveSuggestions { + type Props = { + components: SuggestionsComponentConfig; + children?: never; + } | { + children: (value: { + suggestion: SuggestionState; + }) => ReactNode; + components?: never; + }; +} + +declare const ThreadPrimitiveSuggestions: import("react").NamedExoticComponent<ThreadPrimitiveSuggestions.Props>; + +declare namespace ThreadPrimitiveUnstable_MessageById { + type Props = { + messageId: string; + components: MessagesComponentConfig; + }; +} + +declare const ThreadPrimitiveUnstable_MessageById: FC<ThreadPrimitiveUnstable_MessageById.Props>; + +declare namespace ThreadPrimitiveViewport { + type Element = ComponentRef<typeof Primitive$1.div>; + type Props = ComponentPropsWithoutRef<typeof Primitive$1.div> & { + autoScroll?: boolean | undefined; + turnAnchor?: "top" | "bottom" | undefined; + topAnchorMessageClamp?: { + tallerThan?: string; + visibleHeight?: string; + }; + scrollToBottomOnRunStart?: boolean | undefined; + scrollToBottomOnInitialize?: boolean | undefined; + scrollToBottomOnThreadSwitch?: boolean | undefined; + }; +} + +declare const ThreadPrimitiveViewport: import("react").ForwardRefExoticComponent<Omit<Omit<import("react").ClassAttributes<HTMLDivElement> & import("react").HTMLAttributes<HTMLDivElement> & { + asChild?: boolean; +}, "ref"> & { + render?: import("react").ReactElement | undefined; +} & import("react").RefAttributes<HTMLDivElement>, "ref"> & { + autoScroll?: boolean | undefined; + turnAnchor?: "top" | "bottom" | undefined; + topAnchorMessageClamp?: { + tallerThan?: string; + visibleHeight?: string; + }; + scrollToBottomOnRunStart?: boolean | undefined; + scrollToBottomOnInitialize?: boolean | undefined; + scrollToBottomOnThreadSwitch?: boolean | undefined; +} & import("react").RefAttributes<HTMLDivElement>>; + +declare namespace ThreadPrimitiveViewportFooter { + type Element = ComponentRef<typeof Primitive$1.div>; + type Props = ComponentPropsWithoutRef<typeof Primitive$1.div>; +} + +declare const ThreadPrimitiveViewportFooter: import("react").ForwardRefExoticComponent<Omit<Omit<import("react").ClassAttributes<HTMLDivElement> & import("react").HTMLAttributes<HTMLDivElement> & { + asChild?: boolean; +}, "ref"> & { + render?: import("react").ReactElement | undefined; +} & import("react").RefAttributes<HTMLDivElement>, "ref"> & import("react").RefAttributes<HTMLDivElement>>; + +declare const ThreadPrimitiveViewportProvider: FC<ThreadViewportProviderProps>; + +type ThreadRuntime = { + readonly path: ThreadRuntimePath; + readonly composer: ThreadComposerRuntime; + getState(): ThreadState; + append(message: CreateAppendMessage): void; + deleteMessage(messageId: string): void | Promise<void>; + startRun(config: CreateStartRunConfig): void; + resumeRun(config: CreateResumeRunConfig): void; + exportExternalState(): any; + importExternalState(state: any): void; + subscribe(callback: () => void): Unsubscribe; + cancelRun(): void; + getModelContext(): ModelContext$1; + export(): ExportedMessageRepository; + import(repository: ExportedMessageRepository): void; + reset(initialMessages?: readonly ThreadMessageLike[]): void; + getMessageByIndex(idx: number): MessageRuntime; + getMessageById(messageId: string): MessageRuntime; + stopSpeaking(): void; + connectVoice(): void; + disconnectVoice(): void; + getVoiceVolume(): number; + subscribeVoiceVolume(callback: () => void): Unsubscribe; + muteVoice(): void; + unmuteVoice(): void; + unstable_on<E extends ThreadRuntimeEventType>(event: E, callback: ThreadRuntimeEventCallback<E>): Unsubscribe; +}; + +type ThreadRuntimeCore = Readonly<{ + getMessageById: (messageId: string) => { + parentId: string | null; + message: ThreadMessage; + index: number; + } | undefined; + getBranches: (messageId: string) => readonly string[]; + switchToBranch: (branchId: string) => void; + append: (message: AppendMessage) => void; + deleteMessage: (messageId: string) => void | Promise<void>; + startRun: (config: StartRunConfig) => void; + resumeRun: (config: ResumeRunConfig) => void; + cancelRun: () => void; + addToolResult: (options: AddToolResultOptions) => void; + resumeToolCall: (options: ResumeToolCallOptions) => void; + respondToToolApproval: (options: RespondToToolApprovalOptions) => void; + speak: (messageId: string) => void; + stopSpeaking: () => void; + connectVoice: () => void; + disconnectVoice: () => void; + muteVoice: () => void; + unmuteVoice: () => void; + submitFeedback: (feedback: SubmitFeedbackOptions) => void; + getModelContext: () => ModelContext$1; + composer: ThreadComposerRuntimeCore; + getEditComposer: (messageId: string) => EditComposerRuntimeCore | undefined; + beginEdit: (messageId: string) => void; + getQueueItems?: () => readonly QueueItemState[]; + steerQueueItem?: (queueItemId: string) => void; + removeQueueItem?: (queueItemId: string) => void; + speech: SpeechState | undefined; + voice: VoiceSessionState | undefined; + capabilities: Readonly<RuntimeCapabilities>; + isDisabled: boolean; + isSendDisabled: boolean; + isLoading: boolean; + isRunning?: boolean | undefined; + messages: readonly ThreadMessage[]; + state: ReadonlyJSONValue; + suggestions: readonly ThreadSuggestion[]; + extras: unknown; + subscribe: (callback: () => void) => Unsubscribe; + getVoiceVolume: () => number; + subscribeVoiceVolume: (callback: () => void) => Unsubscribe; + import(repository: ExportedMessageRepository): void; + export(): ExportedMessageRepository; + exportExternalState(): any; + importExternalState(state: any): void; + reset(initialMessages?: readonly ThreadMessageLike[]): void; + unstable_on<E extends ThreadRuntimeEventType>(event: E, callback: ThreadRuntimeEventCallback<E>): Unsubscribe; +}>; + +type ThreadRuntimeCoreBinding = SubscribableWithState<ThreadRuntimeCore, ThreadRuntimePath> & { + outerSubscribe(callback: () => void): Unsubscribe; +}; + +type ThreadRuntimeEventCallback<E extends ThreadRuntimeEventType> = (payload: ThreadRuntimeEventPayload[E]) => void; + +type ThreadRuntimeEventPayload = { + runStart: Record<string, never>; + runEnd: Record<string, never>; + initialize: Record<string, never>; + modelContextUpdate: Record<string, never>; +}; + +type ThreadRuntimeEventType = keyof ThreadRuntimeEventPayload; + +declare class ThreadRuntimeImpl implements ThreadRuntime { + get path(): ThreadRuntimePath; + get __internal_threadBinding(): Subscribable & { + path: ThreadRuntimePath; + getState: () => Readonly<{ + getMessageById: (messageId: string) => { + parentId: string | null; + message: ThreadMessage; + index: number; + } | undefined; + getBranches: (messageId: string) => readonly string[]; + switchToBranch: (branchId: string) => void; + append: (message: AppendMessage) => void; + deleteMessage: (messageId: string) => void | Promise<void>; + startRun: (config: StartRunConfig) => void; + resumeRun: (config: ResumeRunConfig) => void; + cancelRun: () => void; + addToolResult: (options: AddToolResultOptions) => void; + resumeToolCall: (options: ResumeToolCallOptions) => void; + respondToToolApproval: (options: RespondToToolApprovalOptions) => void; + speak: (messageId: string) => void; + stopSpeaking: () => void; + connectVoice: () => void; + disconnectVoice: () => void; + muteVoice: () => void; + unmuteVoice: () => void; + submitFeedback: (feedback: SubmitFeedbackOptions) => void; + getModelContext: () => ModelContext$1; + composer: Readonly<{ + isEditing: boolean; + canCancel: boolean; + canSend: boolean; + isEmpty: boolean; + attachments: readonly Attachment[]; + attachmentAccept: string; + addAttachment: (fileOrAttachment: File | CreateAttachment) => Promise<void>; + removeAttachment: (attachmentId: string) => Promise<void>; + text: string; + setText: (value: string) => void; + role: MessageRole; + setRole: (role: MessageRole) => void; + runConfig: RunConfig; + setRunConfig: (runConfig: RunConfig) => void; + quote: QuoteInfo | undefined; + setQuote: (quote: QuoteInfo | undefined) => void; + reset: () => Promise<void>; + clearAttachments: () => Promise<void>; + send: (options?: SendOptions) => void; + cancel: () => void; + queue: readonly QueueItemState[]; + steerQueueItem: (queueItemId: string) => void; + removeQueueItem: (queueItemId: string) => void; + dictation: DictationState | undefined; + startDictation: () => void; + stopDictation: () => void; + subscribe: (callback: () => void) => Unsubscribe; + unstable_on: <E extends ComposerRuntimeEventType>(event: E, callback: ComposerRuntimeEventCallback<E>) => Unsubscribe; + }>; + getEditComposer: (messageId: string) => EditComposerRuntimeCore | undefined; + beginEdit: (messageId: string) => void; + getQueueItems?: () => readonly QueueItemState[]; + steerQueueItem?: (queueItemId: string) => void; + removeQueueItem?: (queueItemId: string) => void; + speech: SpeechState | undefined; + voice: VoiceSessionState | undefined; + capabilities: Readonly<RuntimeCapabilities>; + isDisabled: boolean; + isSendDisabled: boolean; + isLoading: boolean; + isRunning?: boolean | undefined; + messages: readonly ThreadMessage[]; + state: ReadonlyJSONValue; + suggestions: readonly ThreadSuggestion[]; + extras: unknown; + subscribe: (callback: () => void) => Unsubscribe; + getVoiceVolume: () => number; + subscribeVoiceVolume: (callback: () => void) => Unsubscribe; + import(repository: ExportedMessageRepository): void; + export(): ExportedMessageRepository; + exportExternalState(): any; + importExternalState(state: any): void; + reset(initialMessages?: readonly ThreadMessageLike[]): void; + unstable_on<E extends ThreadRuntimeEventType>(event: E, callback: ThreadRuntimeEventCallback<E>): Unsubscribe; + }>; + } & { + outerSubscribe(callback: () => void): Unsubscribe; + } & { + getStateState(): ThreadState; + }; + private readonly _threadBinding; + constructor(threadBinding: ThreadRuntimeCoreBinding, threadListItemBinding: ThreadListItemRuntimeBinding); + protected __internal_bindMethods(): void; + readonly composer: ThreadComposerRuntimeImpl; + getState(): ThreadState; + append(message: CreateAppendMessage): void; + deleteMessage(messageId: string): void | Promise<void>; + subscribe(callback: () => void): Unsubscribe; + getModelContext(): ModelContext$1; + startRun(config: CreateStartRunConfig): void; + resumeRun(config: CreateResumeRunConfig): void; + exportExternalState(): any; + importExternalState(state: any): void; + cancelRun(): void; + stopSpeaking(): void; + connectVoice(): void; + disconnectVoice(): void; + getVoiceVolume(): number; + subscribeVoiceVolume(callback: () => void): Unsubscribe; + muteVoice(): void; + unmuteVoice(): void; + export(): ExportedMessageRepository; + import(data: ExportedMessageRepository): void; + reset(initialMessages?: readonly ThreadMessageLike[]): void; + getMessageByIndex(idx: number): MessageRuntimeImpl; + getMessageById(messageId: string): MessageRuntimeImpl; + private _getMessageRuntime; + private _eventSubscriptionSubjects; + unstable_on<E extends ThreadRuntimeEventType>(event: E, callback: ThreadRuntimeEventCallback<E>): Unsubscribe; +} + +type ThreadRuntimePath = { + readonly ref: string; + readonly threadSelector: { + readonly type: "main"; + } | { + readonly type: "threadId"; + readonly threadId: string; + }; +}; + +type ThreadState = { + readonly threadId: string; + readonly metadata: ThreadListItemState$1; + readonly isDisabled: boolean; + readonly isLoading: boolean; + readonly isRunning: boolean; + readonly capabilities: RuntimeCapabilities; + readonly messages: readonly ThreadMessage[]; + readonly state: ReadonlyJSONValue; + readonly suggestions: readonly ThreadSuggestion[]; + readonly extras: unknown; + readonly speech: SpeechState | undefined; + readonly voice: VoiceSessionState | undefined; +}; + +type ThreadStep = { + readonly messageId?: string; + readonly usage?: { + readonly inputTokens: number; + readonly outputTokens: number; + } | undefined; +}; + +type ThreadSuggestion = { + prompt: string; +}; + +type ThreadSystemMessage = MessageCommonProps & { + readonly role: "system"; + readonly content: readonly [ + TextMessagePart + ]; + readonly metadata: { + readonly unstable_state?: undefined; + readonly unstable_annotations?: undefined; + readonly unstable_data?: undefined; + readonly steps?: undefined; + readonly submittedFeedback?: undefined; + readonly timing?: undefined; + readonly custom: Record<string, unknown>; + }; +}; + +type ThreadUserMessage = MessageCommonProps & { + readonly role: "user"; + readonly content: readonly ThreadUserMessagePart[]; + readonly attachments: readonly CompleteAttachment[]; + readonly metadata: { + readonly unstable_state?: undefined; + readonly unstable_annotations?: undefined; + readonly unstable_data?: undefined; + readonly steps?: undefined; + readonly submittedFeedback?: undefined; + readonly timing?: undefined; + readonly custom: Record<string, unknown>; + }; +}; + +type ThreadUserMessagePart = TextMessagePart | ImageMessagePart | FileMessagePart | DataMessagePart | Unstable_AudioMessagePart; + +type ThreadViewportProviderProps = PropsWithChildren<{ + options?: ThreadViewportStoreOptions; +}>; + +type ThreadViewportState = { + readonly isAtBottom: boolean; + readonly scrollToBottom: (config?: { + behavior?: ScrollBehavior | undefined; + }) => void; + readonly onScrollToBottom: (callback: (_param8: { + behavior: ScrollBehavior; + }) => void) => Unsubscribe; + readonly turnAnchor: "bottom" | "top"; + readonly topAnchorMessageClamp: { + readonly tallerThan: string; + readonly visibleHeight: string; + }; + readonly height: { + readonly viewport: number; + readonly inset: number; + }; + readonly element: { + readonly viewport: HTMLElement | null; + readonly anchor: HTMLElement | null; + readonly target: HTMLElement | null; + }; + readonly targetConfig: { + readonly tallerThan: number; + readonly visibleHeight: number; + } | null; + readonly topAnchorTurn: { + readonly anchorId: string; + readonly targetId: string; + } | null; + readonly registerViewport: () => SizeHandle; + readonly registerContentInset: () => SizeHandle; + readonly registerViewportElement: (element: HTMLElement | null) => Unsubscribe; + readonly registerAnchorElement: (element: HTMLElement | null) => Unsubscribe; + readonly registerAnchorTargetElement: (element: HTMLElement | null, config?: { + readonly tallerThan: number; + readonly visibleHeight: number; + }) => Unsubscribe; + readonly setTopAnchorTurn: (turn: { + readonly anchorId: string; + readonly targetId: string; + } | null) => void; +}; + +type ThreadViewportStoreOptions = { + turnAnchor?: "top" | "bottom" | undefined; + topAnchorMessageClamp?: { + tallerThan?: string | undefined; + visibleHeight?: string | undefined; + } | undefined; +}; + +type Tool<TArgs extends Record<string, unknown> = Record<string, unknown>, TResult = unknown> = FrontendTool<TArgs, TResult> | BackendTool<TArgs, TResult> | HumanTool<TArgs, TResult> | ProviderTool<TArgs, TResult> | McpTool | ToolWithoutType<TArgs, TResult>; + +type ToolApprovalOption = { + readonly id: string; + readonly kind: ToolApprovalOptionKind | (string & {}); + readonly label?: string; + readonly description?: string; + readonly grants?: readonly string[]; + readonly confirm?: boolean | { + title?: string; + description?: string; + }; +}; + +type ToolApprovalOptionKind = "allow-always" | "allow-once" | "reject-always" | "reject-once"; + +type ToolApprovalResponse = { + readonly approved: boolean; + readonly reason?: string; +} | { + readonly optionId: string; + readonly reason?: string; +} | { + readonly approved: boolean; + readonly optionId: string; + readonly reason?: string; +}; + +type ToolArgsStatus<TArgs extends Record<string, unknown> = Record<string, unknown>> = { + status: "complete" | "incomplete" | "requires-action" | "running"; + propStatus: Partial<Record<keyof TArgs, PropFieldStatus>>; +}; + +type ToolBase<TArgs extends Record<string, unknown> = Record<string, unknown>, TResult = unknown> = { + streamCall?: ToolStreamCallFunction<TArgs, TResult>; + display?: ToolDisplay; +}; + +interface ToolCallArgsReader<TArgs extends Record<string, unknown>> { + get<PathT extends TypePath<TArgs>>(...fieldPath: PathT): Promise<TypeAtPath<TArgs, PathT>>; + streamValues<PathT extends TypePath<TArgs>>(...fieldPath: PathT): AsyncIterableStream<DeepPartial<TypeAtPath<TArgs, PathT>>>; + streamText<PathT extends TypePath<TArgs>>(...fieldPath: PathT): TypeAtPath<TArgs, PathT> extends string & (infer U) ? AsyncIterableStream<U> : never; + forEach<PathT extends TypePath<TArgs>>(...fieldPath: PathT): NonNullable<TypeAtPath<TArgs, PathT>> extends Array<infer U> ? AsyncIterableStream<U> : never; +} + +type ToolCallCompleteText<TArgs extends Record<string, unknown>, TResult> = ReactNode | ((options: { + args: TArgs; + result: TResult | undefined; +}) => ReactNode); + +type ToolCallMessagePart<TArgs = ReadonlyJSONObject, TResult = unknown> = { + readonly type: "tool-call"; + readonly toolCallId: string; + readonly toolName: string; + readonly args: TArgs; + readonly result?: TResult | undefined; + readonly isError?: boolean | undefined; + readonly argsText: string; + readonly artifact?: unknown; + readonly timing?: ToolCallTiming; + readonly mcp?: ToolCallMessagePartMcpMetadata; + readonly providerMetadata?: PartProviderMetadata; + readonly modelContent?: readonly ToolModelContentPart[] | undefined; + readonly interrupt?: { + type: "human"; + payload: unknown; + }; + readonly approval?: { + readonly id: string; + readonly approved?: boolean; + readonly reason?: string; + readonly isAutomatic?: boolean; + readonly options?: readonly ToolApprovalOption[]; + readonly optionId?: string; + readonly resolution?: "cancelled" | "expired"; + }; + readonly parentId?: string; + readonly messages?: readonly ThreadMessage[]; +}; + +type ToolCallMessagePartComponent<TArgs = any, TResult = any> = ComponentType<ToolCallMessagePartProps<TArgs, TResult>>; + +type ToolCallMessagePartMcpMetadata = { + readonly app?: McpAppMetadata; +}; + +type ToolCallMessagePartProps<TArgs = any, TResult = unknown> = MessagePartState & ToolCallMessagePart<TArgs, TResult> & { + addResult: (result: TResult | ToolResponse<TResult>) => void; + resume: (payload: unknown) => void; + respondToApproval: (response: ToolApprovalResponse) => void; +}; + +type ToolCallMessagePartStatus = { + readonly type: "requires-action"; + readonly reason: "interrupt"; +} | MessagePartStatus; + +interface ToolCallReader<TArgs extends Record<string, unknown> = Record<string, unknown>, TResult = unknown> { + args: ToolCallArgsReader<TArgs>; + response: ToolCallResponseReader<TResult>; + result: { + get: () => Promise<TResult>; + }; +} + +interface ToolCallResponseReader<TResult> { + get: () => Promise<ToolResponse<TResult>>; +} + +type ToolCallRunningText<TArgs extends Record<string, unknown>> = ReactNode | ((options: { + args: TArgs; +}) => ReactNode); + +type ToolCallText<TArgs extends Record<string, unknown>, TResult> = { + running: ToolCallRunningText<TArgs>; + complete?: ToolCallCompleteText<TArgs, TResult> | undefined; +} | { + running?: ToolCallRunningText<TArgs> | undefined; + complete: ToolCallCompleteText<TArgs, TResult>; +}; + +type ToolCallTiming = { + readonly startedAt: number; + readonly completedAt?: number; +}; + +type ToolDeclaration<TArgs extends Record<string, unknown> = Record<string, unknown>, TResult = unknown> = FrontendTool<TArgs, TResult> | BackendToolDeclaration<TArgs, TResult> | HumanTool<TArgs, TResult> | ProviderTool<TArgs, TResult> | McpTool | ToolWithoutType<TArgs, TResult>; + +type ToolDefinition<TArgs extends Record<string, unknown> = Record<string, unknown>, TResult = unknown> = WithRender<Tool<TArgs, TResult>, TArgs, TResult>; + +type ToolDisplay = "inline" | "standalone"; + +type ToolExecute<TArgs extends Record<string, unknown>, TResult> = (args: TArgs, context: ToolExecuteContext) => TResult | Promise<TResult>; + +type ToolExecuteContext = Parameters<NonNullable<ToolDeclaration["execute"]>>[1]; + +type ToolExecuteFunction<TArgs, TResult> = (args: TArgs, context: ToolExecutionContext) => TResult | Promise<TResult>; + +type ToolExecutionContext = { + toolCallId: string; + abortSignal: AbortSignal; + human: (payload: unknown) => Promise<unknown>; +}; + +type ToolExecutionStatus = { + type: "executing"; +} | { + type: "interrupt"; + payload: { + type: "human"; + payload: unknown; + }; +}; + +type ToolModelContentPart = { + readonly type: "text"; + readonly text: string; +} | { + readonly type: "file"; + readonly data: string; + readonly mediaType: string; + readonly filename?: string; +}; + +type ToolModelOutputFunction<TArgs, TResult> = (options: { + toolCallId: string; + input: TArgs; + output: TResult; +}) => readonly ToolModelContentPart[] | Promise<readonly ToolModelContentPart[]>; + +type ToolParameters<TArgs extends Record<string, unknown>> = ToolDeclaration<TArgs>["parameters"]; + +type ToolPartLike = Pick<ToolCallMessagePart, "mcp">; + +type ToolRegistration = { + readonly render: ToolCallMessagePartComponent; + readonly standalone: boolean; +}; + +declare class ToolResponse<TResult> { + get [TOOL_RESPONSE_SYMBOL](): boolean; + readonly artifact?: ReadonlyJSONValue; + readonly result: TResult; + readonly isError: boolean; + readonly modelContent?: readonly ToolModelContentPart[]; + readonly messages?: ReadonlyJSONValue; + constructor(options: ToolResponseLike<TResult>); + static [Symbol.hasInstance](obj: unknown): obj is ToolResponse<ReadonlyJSONValue>; + static toResponse(result: any | ToolResponse<any>): ToolResponse<any>; +} + +type ToolResponseLike<TResult> = { + result: TResult; + artifact?: ReadonlyJSONValue | undefined; + isError?: boolean | undefined; + modelContent?: readonly ToolModelContentPart[] | undefined; + messages?: ReadonlyJSONValue | undefined; +}; + +type ToolStreamCall<TArgs extends Record<string, unknown>, TResult> = (reader: ToolCallReader<TArgs, TResult>, context: ToolExecuteContext) => void; + +type ToolStreamCallFunction<TArgs extends Record<string, unknown> = Record<string, unknown>, TResult = unknown> = (reader: ToolCallReader<TArgs, TResult>, context: ToolExecutionContext) => void; + +type ToolWithoutType<TArgs extends Record<string, unknown> = Record<string, unknown>, TResult = unknown> = (Omit<FrontendTool<TArgs, TResult>, "type"> | Omit<BackendTool<TArgs, TResult>, "type"> | Omit<HumanTool<TArgs, TResult>, "type"> | Omit<ProviderTool<TArgs, TResult>, "type">) & { + type?: undefined; +}; + +type Toolkit = Record<string, ToolDefinition<any, any>>; + +type ToolkitDefinition<TArgsByName extends { + [K in keyof TArgsByName]: Record<string, unknown>; +} = Record<string, any>, TResultByName extends { + [K in keyof TArgsByName]: unknown; +} = { + [K in keyof TArgsByName]: any; +}> = { + [K in keyof TArgsByName]: ToolkitDefinitionEntry<TArgsByName[K], TResultByName[K]>; +}; + +type ToolkitDefinitionEntry<TArgs extends Record<string, unknown> = Record<string, unknown>, TResult = unknown> = ToolkitDefinitionInput<TArgs, TResult> | ToolDefinition<any, any>; + +type ToolkitDefinitionEntryWithParameters<TArgs extends Record<string, unknown> = Record<string, unknown>, TResult = unknown> = ToolkitDefinitionInput<TArgs, TResult> & { + parameters: NonNullable<ToolParameters<TArgs>>; +}; + +type ToolkitDefinitionInput<TArgs extends Record<string, unknown>, TResult> = WithRender<ToolDeclaration<TArgs, TResult> extends (infer T) ? T extends { + streamCall?: unknown; +} ? OverrideToolDeclarationCallbacks<T, TArgs, TResult> : never : never, TArgs, TResult>; + +declare const Tools: Resource<ClientOutput<"tools">, [ + { + toolkit?: Toolkit; + mcpApp?: ResourceElement<McpAppResourceOutput> | undefined; + } +]>; + +type ToolsState = { + toolUIs: Record<string, readonly ToolRegistration[]>; + mcpApp?: McpAppResourceOutput | undefined; + tools: Record<string, ToolCallMessagePartComponent[]>; +}; + +type TriggerBehavior = { + readonly kind: "directive"; + readonly formatter: Unstable_DirectiveFormatter; + readonly onInserted?: (item: Unstable_TriggerItem) => void; +} | { + readonly kind: "action"; + readonly formatter: Unstable_DirectiveFormatter; + readonly onExecute: (item: Unstable_TriggerItem) => void; + readonly removeOnExecute?: boolean; +}; + +type TriggerPopoverActiveAria = { + popoverId: string; + highlightedItemId: string | undefined; +}; + +type TriggerPopoverAriaProps = { + "aria-controls"?: string; + "aria-expanded"?: true; + "aria-haspopup"?: "listbox"; + "aria-activedescendant"?: string | undefined; +}; + +type TriggerPopoverLifecycleListener = { + added(trigger: RegisteredTrigger): void; + removed(char: string): void; +}; + +type TriggerPopoverResourceOutput = { + readonly open: boolean; + readonly query: string; + readonly activeCategoryId: string | null; + readonly categories: readonly Unstable_TriggerCategory[]; + readonly items: readonly Unstable_TriggerItem[]; + readonly highlightedIndex: number; + readonly isSearchMode: boolean; + readonly isLoading: boolean; + readonly popoverId: string; + readonly highlightedItemId: string | undefined; + selectCategory(categoryId: string): void; + goBack(): void; + selectItem(item: Unstable_TriggerItem): void; + close(): void; + highlightIndex(index: number): void; + handleKeyDown(e: { + readonly key: string; + readonly shiftKey: boolean; + preventDefault(): void; + }): boolean; + setCursorPosition(pos: number): void; + registerSelectItemOverride(fn: SelectItemOverride): () => void; +}; + +type TriggerPopoverRootContextValue = { + register(trigger: RegisteredTrigger): () => void; + getTriggers(): ReadonlyMap<string, RegisteredTrigger>; + subscribe(listener: () => void): () => void; + subscribeLifecycle(listener: TriggerPopoverLifecycleListener): () => void; + getActiveAria(): TriggerPopoverActiveAria | null; + subscribeAria(listener: () => void): () => void; +}; + +type TupleIndex<T extends readonly any[]> = Exclude<keyof T, keyof any[]>; + +type TypeAtPath<T, P extends readonly any[]> = P extends [ + infer Head, + ...infer Rest +] ? Head extends keyof T ? TypeAtPath<T[Head], Rest> : never : T; + +type TypePath<T> = [ +] | (0 extends 1 & T ? any[] : T extends object ? T extends readonly any[] ? number extends T["length"] ? { + [K in TupleIndex<T>]: [ + AsNumber<K>, + ...TypePath<T[K]> + ]; +}[TupleIndex<T>] : [ + number, + ...TypePath<T[number]> +] : { + [K in ObjectKey<T>]: [ + K, + ...TypePath<T[K]> + ]; +}[ObjectKey<T>] : [ +]); + +type UnionToIntersection<U> = (U extends unknown ? (x: U) => void : never) extends ((x: infer I) => void) ? I : never; + +type Unstable_AudioMessagePart = { + readonly type: "audio"; + readonly audio: { + readonly data: string; + readonly format: "mp3" | "wav"; + }; +}; + +type Unstable_AudioMessagePartComponent = ComponentType<Unstable_AudioMessagePartProps>; + +type Unstable_AudioMessagePartProps = MessagePartState & Unstable_AudioMessagePart; + +type Unstable_ComposerInput = { + value: string; + setText(text: string): void; + send(options?: ComposerSendOptions): void; + isDisabled: boolean; + canSend: boolean; +}; + +type Unstable_ComposerInputHistory = { + onKeyDown: KeyboardEventHandler<HTMLTextAreaElement>; +}; + +type Unstable_DirectiveFormatter = { + serialize(item: Unstable_TriggerItem): string; + parse(text: string): readonly Unstable_DirectiveSegment[]; +}; + +type Unstable_DirectiveSegment = { + readonly kind: "text"; + readonly text: string; +} | { + readonly kind: "mention"; + readonly type: string; + readonly label: string; + readonly id: string; +}; + +type Unstable_IconComponent = FC<{ + className?: string; +}>; + +type Unstable_InferInteractableState<TSchema> = TSchema extends { + "~standard": { + types?: { + output: infer TOutput; + } | undefined; + }; +} ? TOutput : unknown; + +type Unstable_InteractableConfig<TSchema extends Unstable_InteractableStateSchema> = { + description: string; + stateSchema: TSchema; + initialState: Unstable_InferInteractableState<TSchema>; + id?: string | undefined; + updateRender?: ToolCallMessagePartComponent | undefined; +}; + +type Unstable_InteractableDefinition = { + id: string; + name: string; + description: string; + stateSchema: Unstable_InteractableStateSchema; + state: unknown; + initialState: unknown; + scope?: InteractableScope | undefined; +}; + +type Unstable_InteractablePersistedState = Record<string, { + name: string; + state: unknown; +}>; + +type Unstable_InteractablePersistenceAdapter = { + save(state: Unstable_InteractablePersistedState): void | Promise<void>; + load?(): Unstable_InteractablePersistedState | null | undefined | Promise<Unstable_InteractablePersistedState | null | undefined>; +}; + +type Unstable_InteractablePersistenceStatus = { + isPending: boolean; + error: unknown; +}; + +type Unstable_InteractableRegistration = { + id: string; + name: string; + description: string; + stateSchema: Unstable_InteractableStateSchema; + initialState: unknown; + updateRender?: ToolCallMessagePartComponent | undefined; +}; + +type Unstable_InteractableSnapshotEntry = { + id: string; + name: string; + state: unknown; + partial?: boolean | undefined; +}; + +type Unstable_InteractableStateSchema = NonNullable<Extract<Tool, { + parameters: unknown; +}>["parameters"]>; + +type Unstable_InteractableToolConfig<TSchema extends Unstable_InteractableStateSchema> = { + description: string; + stateSchema: TSchema; + render: (props: Unstable_InteractableToolRenderProps<Unstable_InferInteractableState<TSchema>>) => ReactNode; +}; + +type Unstable_InteractableToolRenderProps<TState> = { + state: TState; + setState: (updater: TState | ((prev: TState) => TState)) => void; + version: Unstable_InteractableVersionInfo<TState> | undefined; + id: string; + streaming: boolean; +}; + +type Unstable_InteractableVersion = { + state: unknown; + origin: "create" | "update" | "user-edit"; + toolCallId?: string | undefined; +}; + +type Unstable_InteractableVersionInfo<TState> = { + state: TState; + isLatest: boolean; + restore: () => void; +}; + +type Unstable_InteractablesClientSchema = { + methods: Unstable_InteractablesMethods; +}; + +type Unstable_InteractablesConfig = { + persistence?: Unstable_InteractablePersistenceAdapter | undefined; +}; + +type Unstable_InteractablesMethods = { + getState(): Unstable_InteractablesState; + register(def: Unstable_InteractableRegistration): Unsubscribe; + setState(id: string, updater: (prev: unknown) => unknown): void; + exportState(): Unstable_InteractablePersistedState; + importState(saved: Unstable_InteractablePersistedState): void; + setPersistenceAdapter(adapter: Unstable_InteractablePersistenceAdapter | undefined): void; + flush(): Promise<void>; +}; + +type Unstable_InteractablesState = { + definitions: Record<string, Unstable_InteractableDefinition>; + persistence: Record<string, Unstable_InteractablePersistenceStatus>; +}; + +type Unstable_Mention = { + readonly id: string; + readonly type: string; + readonly label: string; + readonly description?: string | undefined; + readonly icon?: string | undefined; + readonly metadata?: ReadonlyJSONObject | undefined; +}; + +type Unstable_MentionCategory = { + readonly id: string; + readonly label: string; + readonly items: readonly Unstable_Mention[]; +}; + +type Unstable_MentionDirective = { + readonly formatter: Unstable_DirectiveFormatter; + readonly onInserted?: ((item: Unstable_TriggerItem) => void) | undefined; +}; + +type Unstable_MessageStallDetection = { + stalled: boolean; + stalledForMs: number; +}; + +type Unstable_MessageStallDetectionOptions = { + thresholdMs?: number | undefined; +}; + +type Unstable_ModelContextToolsOptions = { + readonly category?: { + readonly id: string; + readonly label: string; + }; + readonly formatLabel?: (toolName: string) => string; + readonly icon?: string; +}; + +type Unstable_SlashCommand = { + readonly id: string; + readonly label?: string | undefined; + readonly description?: string | undefined; + readonly icon?: string | undefined; + readonly execute: () => void; +}; + +type Unstable_SlashCommandAction = { + readonly onExecute: (item: Unstable_TriggerItem) => void; + readonly removeOnExecute?: boolean | undefined; +}; + +type Unstable_TriggerAdapter = { + categories(): readonly Unstable_TriggerCategory[]; + categoryItems(categoryId: string): readonly Unstable_TriggerItem[]; + search?(query: string): readonly Unstable_TriggerItem[]; +}; + +type Unstable_TriggerCategory = { + readonly id: string; + readonly label: string; +}; + +type Unstable_TriggerItem = { + readonly id: string; + readonly type: string; + readonly label: string; + readonly description?: string | undefined; + readonly metadata?: ReadonlyJSONObject | undefined; +}; + +type Unstable_TriggerPopoverAriaProps = TriggerPopoverAriaProps; + +type Unstable_UseComposerInputOptions = { + disabled?: boolean | undefined; +}; + +type Unstable_UseLiveCompletionAdapterOptions = { + readonly fetcher: (query: string) => Promise<readonly Unstable_TriggerItem[]>; + readonly debounceMs?: number | undefined; + readonly enabled?: boolean | undefined; +}; + +type Unstable_UseMentionAdapterOptions = { + readonly items?: readonly Unstable_Mention[]; + readonly categories?: readonly Unstable_MentionCategory[]; + readonly includeModelContextTools?: boolean | Unstable_ModelContextToolsOptions; + readonly formatter?: Unstable_DirectiveFormatter; + readonly onInserted?: (item: Unstable_TriggerItem) => void; + readonly iconMap?: Record<string, Unstable_IconComponent>; + readonly fallbackIcon?: Unstable_IconComponent; +}; + +type Unstable_UseSlashCommandAdapterOptions = { + readonly commands: readonly Unstable_SlashCommand[]; + readonly removeOnExecute?: boolean | undefined; + readonly iconMap?: Record<string, Unstable_IconComponent>; + readonly fallbackIcon?: Unstable_IconComponent; +}; + +type Unsubscribe = () => void; + +type Unsubscribe$1 = () => void; + +type UseAssistantFrameHostOptions = { + iframeRef: Readonly<RefObject<HTMLIFrameElement | null | undefined>>; + targetOrigin?: string; + register: (frameHost: AssistantFrameHost) => Unsubscribe; +}; + +type UseComposerIfProps = RequireAtLeastOne$1<ComposerIfFilters>; + +type UseMessageIfProps = RequireAtLeastOne<MessageIfFilters>; + +type UseThreadIfProps = RequireAtLeastOne<ThreadIfFilters>; + +type UserCommands = Assistant.Commands[keyof Assistant.Commands]; + +type UserExternalState = keyof Assistant.ExternalState extends never ? Record<string, unknown> : Assistant.ExternalState[keyof Assistant.ExternalState]; + +type UserMessage = { + readonly role: "user"; + readonly parts: readonly UserMessagePart[]; +}; + +type UserMessagePart = TextPart | ImagePart; + +type ValidateClient<K extends keyof ScopeRegistry> = ScopeRegistry[K] extends { + methods: ClientMethods; +} ? "meta" extends keyof ScopeRegistry[K] ? ScopeRegistry[K]["meta"] extends ClientMetaType ? "events" extends keyof ScopeRegistry[K] ? ScopeRegistry[K]["events"] extends ClientEventsType<K> ? ScopeRegistry[K] : ClientError<`ERROR: ${K & string} has invalid events type`> : ScopeRegistry[K] : ClientError<`ERROR: ${K & string} has invalid meta type`> : "events" extends keyof ScopeRegistry[K] ? ScopeRegistry[K]["events"] extends ClientEventsType<K> ? ScopeRegistry[K] : ClientError<`ERROR: ${K & string} has invalid events type`> : ScopeRegistry[K] : ClientError<`ERROR: ${K & string} has invalid methods type`>; + +type VoiceSessionControls = { + disconnect: () => void; + mute: () => void; + unmute: () => void; +}; + +type VoiceSessionHelpers = { + setStatus: (status: RealtimeVoiceAdapter.Status) => void; + end: (reason: "cancelled" | "error" | "finished", error?: unknown) => void; + emitTranscript: (item: RealtimeVoiceAdapter.TranscriptItem) => void; + emitMode: (mode: RealtimeVoiceAdapter.Mode) => void; + emitVolume: (volume: number) => void; + isDisposed: () => boolean; +}; + +type VoiceSessionState = { + readonly status: RealtimeVoiceAdapter.Status; + readonly isMuted: boolean; + readonly mode: RealtimeVoiceAdapter.Mode; +}; + +declare class WebSpeechDictationAdapter implements DictationAdapter { + private _language; + private _continuous; + private _interimResults; + constructor(options?: { + language?: string; + continuous?: boolean; + interimResults?: boolean; + }); + static isSupported(): boolean; + listen(): DictationAdapter.Session; +} + +declare class WebSpeechSynthesisAdapter implements SpeechSynthesisAdapter { + speak(text: string): SpeechSynthesisAdapter.Utterance; +} + +type WildcardPayload = { + [K in keyof ClientEventMap]: { + event: K; + payload: ClientEventMap[K]; + }; +}[Extract<keyof ClientEventMap, string>]; + +type WithRender<T, TArgs extends Record<string, unknown>, TResult> = T extends { + type: "frontend" | "human"; +} ? T & (T extends { + type: "frontend"; +} ? { + render: ToolCallMessagePartComponent<TArgs, TResult>; +} | { + render?: ToolCallMessagePartComponent<TArgs, TResult>; + renderText: ToolCallText<TArgs, TResult>; +} : { + render: ToolCallMessagePartComponent<TArgs, TResult>; +}) : T & { + render?: ToolCallMessagePartComponent<TArgs, TResult> | undefined; + renderText?: ToolCallText<TArgs, TResult> | undefined; +}; + +type WithRenderPropProps<T extends ElementType> = ComponentPropsWithoutRef<T> & { + render?: ReactElement | undefined; +}; + +declare namespace actionBarMore_d_exports { + export { ActionBarMorePrimitiveContent as Content, ActionBarMorePrimitiveItem as Item, ActionBarMorePrimitiveRoot as Root, ActionBarMorePrimitiveSeparator as Separator, ActionBarMorePrimitiveTrigger as Trigger }; +} + +declare namespace actionBar_d_exports { + export { ActionBarPrimitiveCopy as Copy, ActionBarPrimitiveEdit as Edit, ActionBarPrimitiveExportMarkdown as ExportMarkdown, ActionBarPrimitiveFeedbackNegative as FeedbackNegative, ActionBarPrimitiveFeedbackPositive as FeedbackPositive, ActionBarPrimitiveReload as Reload, ActionBarPrimitiveRoot as Root, ActionBarPrimitiveSpeak as Speak, ActionBarPrimitiveStopSpeaking as StopSpeaking }; +} + +declare namespace assistantModal_d_exports { + export { AssistantModalPrimitiveAnchor as Anchor, AssistantModalPrimitiveContent as Content, AssistantModalPrimitiveRoot as Root, AssistantModalPrimitiveTrigger as Trigger }; +} + +declare namespace attachment_d_exports { + export { AttachmentPrimitiveName as Name, AttachmentPrimitiveRemove as Remove, AttachmentPrimitiveRoot as Root, AttachmentPrimitiveThumb as unstable_Thumb }; +} + +declare const bindExternalStoreMessage: <T>(target: object, message: T | T[]) => void; + +declare namespace branchPicker_d_exports { + export { BranchPickerPrimitiveCount as Count, BranchPickerPrimitiveNext as Next, BranchPickerPrimitiveNumber as Number, BranchPickerPrimitivePrevious as Previous, BranchPickerPrimitiveRoot as Root }; +} + +declare namespace chainOfThought_d_exports { + export { ChainOfThoughtPrimitiveAccordionTrigger as AccordionTrigger, ChainOfThoughtPrimitiveAccordionTrigger as AccordionTriggerPrimitive, ChainOfThoughtPrimitiveParts as Parts, ChainOfThoughtPrimitiveParts as PartsPrimitive, ChainOfThoughtPrimitiveRoot as Root, ChainOfThoughtPrimitiveRoot as RootPrimitive }; +} + +declare namespace composer_d_exports { + export { ComposerPrimitiveAddAttachment as AddAttachment, ComposerPrimitiveAttachmentByIndex as AttachmentByIndex, ComposerPrimitiveAttachmentDropzone as AttachmentDropzone, ComposerPrimitiveAttachments as Attachments, ComposerPrimitiveCancel as Cancel, ComposerPrimitiveDictate as Dictate, ComposerPrimitiveDictationTranscript as DictationTranscript, ComposerPrimitiveIf as If, ComposerPrimitiveInput as Input, ComposerPrimitiveQueue as Queue, ComposerPrimitiveQuote as Quote, ComposerPrimitiveQuoteDismiss as QuoteDismiss, ComposerPrimitiveQuoteText as QuoteText, ComposerPrimitiveRoot as Root, ComposerPrimitiveSend as Send, ComposerPrimitiveStopDictation as StopDictation, RegisteredTrigger as Unstable_RegisteredTrigger, ComposerPrimitiveTriggerPopover as Unstable_TriggerPopover, ComposerPrimitiveTriggerPopoverBack as Unstable_TriggerPopoverBack, ComposerPrimitiveTriggerPopoverCategories as Unstable_TriggerPopoverCategories, ComposerPrimitiveTriggerPopoverCategoryItem as Unstable_TriggerPopoverCategoryItem, ComposerPrimitiveTriggerPopoverItem as Unstable_TriggerPopoverItem, ComposerPrimitiveTriggerPopoverItems as Unstable_TriggerPopoverItems, ComposerPrimitiveTriggerPopoverRoot as Unstable_TriggerPopoverRoot, useTriggerPopoverRootContext as unstable_useTriggerPopoverRootContext, useTriggerPopoverRootContextOptional as unstable_useTriggerPopoverRootContextOptional, useTriggerPopoverScopeContext as unstable_useTriggerPopoverScopeContext, useTriggerPopoverScopeContextOptional as unstable_useTriggerPopoverScopeContextOptional, useTriggerPopoverTriggers as unstable_useTriggerPopoverTriggers, useTriggerPopoverTriggersOptional as unstable_useTriggerPopoverTriggersOptional }; +} + +declare const convertExternalMessages: <T extends WeakKey>(messages: T[], callback: useExternalMessageConverter.Callback<T>, isRunning: boolean, metadata: useExternalMessageConverter.Metadata) => ThreadMessage[]; + +declare const createMessageConverter: <T extends object>(callback: useExternalMessageConverter.Callback<T>) => { + useThreadMessages: (_param9: { + messages: T[]; + isRunning: boolean; + joinStrategy?: JoinStrategy | undefined; + metadata?: useExternalMessageConverter.Metadata; + }) => ThreadMessage[]; + toThreadMessages: (messages: T[], isRunning?: boolean, metadata?: useExternalMessageConverter.Metadata) => ThreadMessage[]; + toOriginalMessages: (input: ThreadState | ThreadMessage | ThreadMessage["content"][number]) => unknown[]; + toOriginalMessage: (input: ThreadState | ThreadMessage | ThreadMessage["content"][number]) => {}; + useOriginalMessage: () => {}; + useOriginalMessages: () => unknown[]; +}; + +declare const createMessageQueue: (driver: MessageQueueDriver) => MessageQueueController; + +declare function createVoiceSession(options: { + abortSignal?: AbortSignal; +}, setup: (helpers: VoiceSessionHelpers) => Promise<VoiceSessionControls>): RealtimeVoiceAdapter.Session; + +declare function defineMcpToolkit(definition: McpToolkitDefinition): Toolkit; + +declare function defineToolkit<TArgsByName extends { + [K in keyof TArgsByName]: Record<string, unknown>; +}, TResultByName extends { + [K in keyof TArgsByName]: unknown; +} = { + [K in keyof TArgsByName]: any; +}>(_definition: { + [K in keyof TArgsByName]: ToolkitDefinitionEntryWithParameters<TArgsByName[K], TResultByName[K]>; +}): Toolkit & { + [K in keyof TArgsByName]: ToolkitDefinitionEntryWithParameters<TArgsByName[K], TResultByName[K]>; +}; + +declare function defineToolkit<const TDefinition extends ToolkitDefinition>(_definition: TDefinition): Toolkit & TDefinition; + +declare namespace error_d_exports { + export { ErrorPrimitiveMessage as Message, ErrorPrimitiveRoot as Root }; +} + +declare function externalTool(): never; + +declare const fromThreadMessageLike: (like: ThreadMessageLike, fallbackId: string, fallbackStatus: MessageStatus) => ThreadMessage; + +declare const generateId: (size?: number) => string; + +declare const getAutoStatus: (isLast: boolean, isRunning: boolean, hasInterruptedToolCalls: boolean, hasPendingToolCalls: boolean, error?: ReadonlyJSONValue) => MessageStatus; + +declare const getExternalStoreMessages: <T>(input: { + messages: readonly ThreadMessage[]; +} | ThreadMessage | ThreadMessage["content"][number]) => T[]; + +declare function getMcpAppFromToolPart(part: ToolPartLike): McpAppMetadata | undefined; + +declare global { + interface Window { + SpeechRecognition?: SpeechRecognitionConstructor; + webkitSpeechRecognition?: SpeechRecognitionConstructor; + } +} + +declare global { + interface Window { + __ASSISTANT_UI_DEVTOOLS_HOOK__?: any; + } +} + +declare const groupPartByType: <TKey extends `group-${string}`>(map: Partial<Readonly<Record<GroupPartType, readonly TKey[]>>>) => ((part: PartState, context?: GroupByContext) => readonly TKey[]); + +declare const hitl: typeof humanTool; + +declare const hitlTool: typeof humanTool; + +declare function humanTool(): never; + +declare namespace entry_root_exports { + export { actionBarMore_d_exports as ActionBarMorePrimitive, actionBar_d_exports as ActionBarPrimitive, AddToolResultOptions, AppendMessage, Assistant, AssistantClient, AssistantCloud, AssistantContextConfig, AssistantDataUI, AssistantDataUIProps, AssistantEventCallback, AssistantEventName, AssistantEventPayload, AssistantEventScope, AssistantEventSelector, AssistantFrameHost, AssistantFrameProvider, AssistantInteractableProps, assistantModal_d_exports as AssistantModalPrimitive, AssistantRuntime, AssistantRuntimeProvider, AssistantState, AssistantTool, AssistantToolProps, AssistantToolUI, AssistantToolUIProps, AssistantTransportCommand, AssistantTransportConnectionMetadata, AssistantTransportProtocol, Attachment, AttachmentAdapter, attachment_d_exports as AttachmentPrimitive, AttachmentRuntime, AttachmentState, AttachmentStatus, AuiIf, AuiProvider, branchPicker_d_exports as BranchPickerPrimitive, ChainOfThoughtByIndicesProvider, ChainOfThoughtClient, chainOfThought_d_exports as ChainOfThoughtPrimitive, ChatModelAdapter, ChatModelRunOptions, ChatModelRunResult, ChatModelRunUpdate, CloudFileAttachmentAdapter, CompleteAttachment, ComposerAttachmentByIndexProvider, composer_d_exports as ComposerPrimitive, ComposerRuntime, ComposerSendOptions, ComposerState$1 as ComposerState, CompositeAttachmentAdapter, CreateAppendMessage, CreateAttachment, CreateResumeRunConfig, CreateStartRunConfig, DataMessagePart, DataMessagePartComponent, DataMessagePartProps, DataRenderers, DevToolsHooks, DevToolsProviderApi, DictationAdapter, DictationState, EditComposerRuntime, EditComposerState, EmptyMessagePartComponent, EmptyMessagePartProps, EnrichedPartState, error_d_exports as ErrorPrimitive, ExportedMessageRepository, ExportedMessageRepositoryItem, ExternalStoreAdapter, ExternalStoreBranchChange, ExternalStoreMessageConverter, ExternalStoreSharedOptions, ExternalStoreThreadData, ExternalStoreThreadListAdapter, ExternalThread, ExternalThreadBranchAdapter, ExternalThreadMessage, ExternalThreadProps, ExternalThreadQueueAdapter, FRAME_MESSAGE_CHANNEL, FeedbackAdapter, FileMessagePart, FileMessagePartComponent, FileMessagePartProps, FrameMessage, FrameMessageType, GenerativeUIComponentRegistry, GenerativeUIMessagePart, GenerativeUIMessagePartComponent, GenerativeUIMessagePartProps, GenerativeUINode, GenerativeUIRender, GenerativeUIRenderError, GenerativeUIRenderProps, GenerativeUISpec, GenericThreadHistoryAdapter, GroupByContext, internal_d_exports as INTERNAL, ImageMessagePart, ImageMessagePartComponent, ImageMessagePartProps, InMemoryThreadList, InMemoryThreadListAdapter, InMemoryThreadListProps, Interactables, LanguageModelConfig, LanguageModelV1CallSettings, LocalRuntimeOptions, LocalRuntimeOptionsBase, McpAppDisplayMode, McpAppHostContext, McpAppHostInfo, McpAppMetadata, McpAppRenderer, McpAppRendererOptions, McpAppResource, McpAppResourceCSP, McpAppResourceMeta, McpAppResourceOutput, McpAppSandboxConfig, McpAppToolCallParams, McpAppsHost, McpAppsRemoteHost, McpAppsRemoteHostOptions, McpToolkitDefinition, McpToolkitEntry, McpToolkitToolConfig, MessageAttachmentByIndexProvider, MessageByIndexProvider, MessageFormatAdapter, MessageFormatItem, MessageFormatRepository, messagePart_d_exports as MessagePartPrimitive, MessagePartRuntime, MessagePartState, MessagePartStatus, message_d_exports as MessagePrimitive, MessageProvider, MessageQueueController, MessageQueueDriver, MessageRuntime, MessageState$1 as MessageState, MessageStatus, MessageStorageEntry, MessageTiming, ModelContext$1 as ModelContext, ModelContext as ModelContextClient, ModelContextProvider, ModelContextRegistry, ModelContextRegistryInstructionHandle, ModelContextRegistryProviderHandle, ModelContextRegistryToolHandle, PartByIndexProvider, PartProviderMetadata, PartState, PendingAttachment, ProviderToolConfig, QueueItemMethods, queueItem_d_exports as QueueItemPrimitive, QueueItemState, QuoteInfo, QuoteMessagePartComponent, QuoteMessagePartProps, ReadonlyThreadProvider, RealtimeVoiceAdapter, ReasoningGroupComponent, ReasoningGroupProps, ReasoningMessagePart, ReasoningMessagePartComponent, ReasoningMessagePartProps, RemoteThreadListAdapter, RespondToToolApprovalOptions, RuntimeAdapterProvider, RuntimeAdapters, selectionToolbar_d_exports as SelectionToolbarPrimitive, SendCommandsRequestBody, SerializedModelContext, SerializedTool, SimpleImageAttachmentAdapter, SimpleTextAttachmentAdapter, SingleThreadList, SmoothOptions, SourceMessagePart, SourceMessagePartComponent, SourceMessagePartProps, SourceProviderMetadata, SpeechSynthesisAdapter, SubmitFeedbackOptions, SuggestionAdapter, SuggestionByIndexProvider, SuggestionConfig, suggestion_d_exports as SuggestionPrimitive, Suggestions, TextMessagePart, TextMessagePartComponent, TextMessagePartProps, TextMessagePartProvider, ThreadAssistantMessage, ThreadAssistantMessagePart, ThreadComposerRuntime, ThreadComposerState, ThreadHistoryAdapter, ThreadListItemByIndexProvider, threadListItemMore_d_exports as ThreadListItemMorePrimitive, threadListItem_d_exports as ThreadListItemPrimitive, ThreadListItemRuntime, ThreadListItemRuntimeProvider, ThreadListItemState$1 as ThreadListItemState, ThreadListItemStatus, threadList_d_exports as ThreadListPrimitive, ThreadListRuntime, ThreadListState, ThreadMessage, ThreadMessageLike, thread_d_exports as ThreadPrimitive, ThreadRuntime, ThreadState, ThreadSuggestion, ThreadSystemMessage, ThreadUserMessage, ThreadUserMessagePart, ThreadViewportState, Tool, ToolApprovalOption, ToolApprovalOptionKind, ToolApprovalResponse, ToolArgsStatus, ToolCallMessagePart, ToolCallMessagePartComponent, ToolCallMessagePartMcpMetadata, ToolCallMessagePartProps, ToolCallMessagePartStatus, ToolCallText, ToolCallTiming, ToolDefinition, ToolExecutionStatus, ToolModelContentPart, Toolkit, ToolkitDefinition, ToolkitDefinitionEntry, Tools, Unstable_AudioMessagePart, Unstable_AudioMessagePartComponent, Unstable_AudioMessagePartProps, Unstable_ComposerInput, Unstable_ComposerInputHistory, Unstable_DirectiveFormatter, Unstable_DirectiveSegment, Unstable_IconComponent, Unstable_InferInteractableState, Unstable_InteractableConfig, Unstable_InteractableDefinition, Unstable_InteractablePersistedState, Unstable_InteractablePersistenceAdapter, Unstable_InteractablePersistenceStatus, Unstable_InteractableRegistration, Unstable_InteractableSnapshotEntry, Unstable_InteractableStateSchema, Unstable_InteractableToolConfig, Unstable_InteractableToolRenderProps, Unstable_InteractableVersion, Unstable_InteractableVersionInfo, Unstable_InteractablesClientSchema, Unstable_InteractablesConfig, Unstable_InteractablesMethods, Unstable_InteractablesState, Unstable_Mention, Unstable_MentionCategory, Unstable_MentionDirective, Unstable_MessageStallDetection, Unstable_MessageStallDetectionOptions, Unstable_ModelContextToolsOptions, RegisteredTrigger as Unstable_RegisteredTrigger, Unstable_SlashCommand, Unstable_SlashCommandAction, TriggerBehavior as Unstable_TriggerBehavior, Unstable_TriggerItem, Unstable_TriggerPopoverAriaProps, Unstable_UseComposerInputOptions, Unstable_UseLiveCompletionAdapterOptions, Unstable_UseMentionAdapterOptions, Unstable_UseSlashCommandAdapterOptions, Unsubscribe, VoiceSessionControls, VoiceSessionHelpers, VoiceSessionState, WebSpeechDictationAdapter, WebSpeechSynthesisAdapter, bindExternalStoreMessage, createMessageQueue, createVoiceSession, defineMcpToolkit, defineToolkit, externalTool, fromThreadMessageLike, generateId, getExternalStoreMessages, getMcpAppFromToolPart, groupPartByType, hitl, hitlTool, humanTool, makeAssistantDataUI, makeAssistantTool, makeAssistantToolUI, makeAssistantVisible, mergeModelContexts, pickExternalStoreSharedOptions, providerTool, stubTool, tool, unstable_Interactables, convertExternalMessages as unstable_convertExternalMessages, createMessageConverter as unstable_createMessageConverter, unstable_defaultDirectiveFormatter, unstable_formatInteractableSnapshot, unstable_getInteractableSnapshots, unstable_getInteractableVersions, unstable_interactableTool, unstable_useComposerInput, unstable_useComposerInputHistory, unstable_useInteractable, unstable_useInteractableState, unstable_useInteractableVersions, unstable_useLiveCompletionAdapter, unstable_useMentionAdapter, unstable_useMessageStallDetection, unstable_useSlashCommandAdapter, unstable_useThreadMessageIds, unstable_useTriggerPopoverAriaProps, useTriggerPopoverRootContext as unstable_useTriggerPopoverRootContext, useTriggerPopoverRootContextOptional as unstable_useTriggerPopoverRootContextOptional, useTriggerPopoverScopeContext as unstable_useTriggerPopoverScopeContext, useTriggerPopoverScopeContextOptional as unstable_useTriggerPopoverScopeContextOptional, useTriggerPopoverTriggers as unstable_useTriggerPopoverTriggers, useTriggerPopoverTriggersOptional as unstable_useTriggerPopoverTriggersOptional, useAssistantContext, useAssistantDataUI, useAssistantFrameHost, useAssistantInstructions, useAssistantInteractable, useAssistantRuntime, useAssistantTool, useAssistantToolUI, useAssistantTransportRuntime, useAssistantTransportSendCommand, useAssistantTransportState, useAttachment, useAttachmentRuntime, useAui, useAuiEvent, useAuiState, useAuiToolOverrides, useCloudThreadListAdapter, useCloudThreadListRuntime, useComposer, useComposerRuntime, useEditComposer, useEditComposerAttachment, useEditComposerAttachmentRuntime, useExternalMessageConverter, useExternalStoreRuntime, useExternalStoreSharedOptions, useInlineRender, useInteractableState, useLocalRuntime, useMessage, useMessageAttachment, useMessageAttachmentRuntime, useMessagePart, useMessagePartData, useMessagePartFile, useMessagePartImage, useMessagePartReasoning, useMessagePartRuntime, useMessagePartSource, useMessagePartText, useMessageQuote, useMessageRuntime, useMessageTiming, useRemoteThreadListRuntime, useRuntimeAdapters, useScrollLock, useSmooth, useThread, useThreadComposer, useThreadComposerAttachment, useThreadComposerAttachmentRuntime, useThreadList, useThreadListItem, useThreadListItemRuntime, useThreadModelContext, useThreadRuntime, useThreadViewport, useThreadViewportAutoScroll, useThreadViewportStore, useToolArgsStatus, useToolCallElapsed, useVoiceControls, useVoiceState, useVoiceVolume }; +} + +declare namespace internal_d_exports { + export { AssistantRuntimeImpl, BaseAssistantRuntimeCore, CompositeContextProvider, DefaultThreadComposerRuntimeCore, MessageRepository, ThreadListItemRuntimeBinding, ThreadListRuntimeCore, ThreadRuntimeCore, ThreadRuntimeCoreBinding, ThreadRuntimeImpl, ToolExecutionStatus, getAutoStatus, splitLocalRuntimeOptions, useComposerInputPluginRegistryOptional, useSmooth, useSmoothStatus, withSmoothContextProvider }; +} + +declare const makeAssistantDataUI: <T = any>(dataUI: AssistantDataUIProps<T>) => AssistantDataUI; + +declare const makeAssistantTool: <TArgs extends Record<string, unknown>, TResult>(tool: AssistantToolProps<TArgs, TResult>) => AssistantTool; + +declare const makeAssistantToolUI: <TArgs, TResult>(tool: AssistantToolUIProps<TArgs, TResult>) => AssistantToolUI; + +declare const makeAssistantVisible: <T extends ComponentType<any>>(Component: T, config?: { + clickable?: boolean | undefined; + editable?: boolean | undefined; +}) => T; + +declare const mergeModelContexts: (configSet: Set<ModelContextProvider>) => ModelContext$1; + +declare namespace messagePart_d_exports { + export { MessagePartPrimitiveImage as Image, MessagePartPrimitiveInProgress as InProgress, PartPrimitiveMessages as Messages, MessagePartPrimitiveText as Text }; +} + +declare namespace message_d_exports { + export { MessagePrimitiveAttachmentByIndex as AttachmentByIndex, MessagePrimitiveAttachments as Attachments, MessagePrimitiveParts as Content, MessagePrimitiveError as Error, MessagePrimitiveGenerativeUI as GenerativeUI, MessagePrimitiveGroupedParts as GroupedParts, MessagePrimitiveIf as If, MessagePrimitivePartByIndex as PartByIndex, MessagePrimitiveParts as Parts, MessagePrimitiveQuote as Quote, MessagePrimitiveRoot as Root, MessagePrimitiveUnstable_PartsGrouped as Unstable_PartsGrouped, MessagePrimitiveUnstable_PartsGroupedByParentId as Unstable_PartsGroupedByParentId }; +} + +declare const pickExternalStoreSharedOptions: (options: ExternalStoreSharedOptions) => ExternalStoreSharedOptions; + +declare function providerTool(_config: ProviderToolConfig): never; + +declare namespace queueItem_d_exports { + export { QueueItemPrimitiveRemove as Remove, QueueItemPrimitiveSteer as Steer, QueueItemPrimitiveText as Text }; +} + +declare namespace selectionToolbar_d_exports { + export { SelectionToolbarPrimitiveQuote as Quote, SelectionToolbarPrimitiveRoot as Root, SelectionToolbarPrimitiveQuote, SelectionToolbarPrimitiveRoot }; +} + +declare const splitLocalRuntimeOptions: <T extends LocalRuntimeOptions>(options: T) => { + localRuntimeOptions: { + cloud: AssistantCloud | undefined; + initialMessages: readonly ThreadMessageLike[] | undefined; + maxSteps: number | undefined; + adapters: Omit<{ + chatModel: ChatModelAdapter; + history?: ThreadHistoryAdapter | undefined; + attachments?: AttachmentAdapter | undefined; + speech?: SpeechSynthesisAdapter | undefined; + dictation?: DictationAdapter | undefined; + voice?: RealtimeVoiceAdapter | undefined; + feedback?: FeedbackAdapter | undefined; + suggestion?: SuggestionAdapter | undefined; + }, "chatModel"> | undefined; + unstable_humanToolNames: string[] | undefined; + unstable_enableMessageQueue: boolean | undefined; + }; + otherOptions: Omit<T, "adapters" | "cloud" | "initialMessages" | "maxSteps" | "unstable_enableMessageQueue" | "unstable_humanToolNames">; +}; + +declare function stubTool(): never; + +declare namespace suggestion_d_exports { + export { SuggestionPrimitiveDescription as Description, SuggestionPrimitiveTitle as Title, SuggestionPrimitiveTrigger as Trigger }; +} + +declare namespace threadListItemMore_d_exports { + export { ThreadListItemMorePrimitiveContent as Content, ThreadListItemMorePrimitiveItem as Item, ThreadListItemMorePrimitiveRoot as Root, ThreadListItemMorePrimitiveSeparator as Separator, ThreadListItemMorePrimitiveTrigger as Trigger }; +} + +declare namespace threadListItem_d_exports { + export { ThreadListItemPrimitiveArchive as Archive, ThreadListItemPrimitiveDelete as Delete, ThreadListItemPrimitiveRoot as Root, ThreadListItemPrimitiveTitle as Title, ThreadListItemPrimitiveTrigger as Trigger, ThreadListItemPrimitiveUnarchive as Unarchive }; +} + +declare namespace threadList_d_exports { + export { ThreadListPrimitiveItemByIndex as ItemByIndex, ThreadListPrimitiveItems as Items, ThreadListPrimitiveLoadMore as LoadMore, ThreadListPrimitiveNew as New, ThreadListPrimitiveRoot as Root }; +} + +declare namespace thread_d_exports { + export { ThreadPrimitiveEmpty as Empty, ThreadPrimitiveIf as If, ThreadPrimitiveMessageByIndex as MessageByIndex, ThreadPrimitiveMessages as Messages, ThreadPrimitiveRoot as Root, ThreadPrimitiveScrollToBottom as ScrollToBottom, ThreadPrimitiveSuggestion as Suggestion, ThreadPrimitiveSuggestionByIndex as SuggestionByIndex, ThreadPrimitiveSuggestions as Suggestions, ThreadPrimitiveUnstable_MessageById as Unstable_MessageById, ThreadPrimitiveViewport as Viewport, ThreadPrimitiveViewportFooter as ViewportFooter, ThreadPrimitiveViewportProvider as ViewportProvider }; +} + +declare function tool<TArgs extends Record<string, unknown>, TResult = any>(tool: Tool<TArgs, TResult>): Tool<TArgs, TResult>; + +declare const unstable_Interactables: Resource<ClientOutput<"unstable_interactables">, [ + (Unstable_InteractablesConfig | undefined)? +]>; + +declare const unstable_defaultDirectiveFormatter: Unstable_DirectiveFormatter; + +declare function unstable_formatInteractableSnapshot(entry: Unstable_InteractableSnapshotEntry): string; + +declare function unstable_getInteractableSnapshots(message: { + metadata?: unknown; +}): Unstable_InteractableSnapshotEntry[] | undefined; + +declare function unstable_getInteractableVersions(messages: readonly SnapshotCarrierMessage[], id: string, name: string): Unstable_InteractableVersion[]; + +declare const unstable_interactableTool: <TSchema extends Unstable_InteractableStateSchema>(config: Unstable_InteractableToolConfig<TSchema>) => ToolDefinition<Record<string, unknown>, { + success: true; +}>; + +declare function unstable_useComposerInput(options?: Unstable_UseComposerInputOptions): Unstable_ComposerInput; + +declare function unstable_useComposerInputHistory(): Unstable_ComposerInputHistory; + +declare const unstable_useInteractable: <TSchema extends Unstable_InteractableStateSchema>(name: string, config: Unstable_InteractableConfig<TSchema>) => readonly [ + Unstable_InferInteractableState<TSchema>, + { + id: string; + version: Unstable_InteractableVersionInfo<Unstable_InferInteractableState<TSchema>> | undefined; + setState: (updater: Unstable_InferInteractableState<TSchema> | ((prev: Unstable_InferInteractableState<TSchema>) => Unstable_InferInteractableState<TSchema>)) => void; + isPending: boolean; + error: unknown; + flush: () => Promise<void>; + } +]; + +declare const unstable_useInteractableState: <TState>(id: string) => [ + TState | undefined, + { + setState: (updater: StateUpdater<TState>) => void; + isPending: boolean; + error: unknown; + flush: () => Promise<void>; + } +]; + +declare const unstable_useInteractableVersions: <TState = unknown>(id: string, name: string) => (Omit<Unstable_InteractableVersion, "state"> & { + state: TState; + restore: () => void; +})[]; + +declare function unstable_useLiveCompletionAdapter(options: Unstable_UseLiveCompletionAdapterOptions): { + adapter: Unstable_TriggerAdapter; + isLoading: boolean; +}; + +declare function unstable_useMentionAdapter(options?: Unstable_UseMentionAdapterOptions): { + adapter: Unstable_TriggerAdapter; + directive: Unstable_MentionDirective; + iconMap?: Record<string, Unstable_IconComponent>; + fallbackIcon?: Unstable_IconComponent; +}; + +declare function unstable_useMessageStallDetection(options?: Unstable_MessageStallDetectionOptions): Unstable_MessageStallDetection; + +declare function unstable_useSlashCommandAdapter(options: Unstable_UseSlashCommandAdapterOptions): { + adapter: Unstable_TriggerAdapter; + action: Unstable_SlashCommandAction; + iconMap?: Record<string, Unstable_IconComponent>; + fallbackIcon?: Unstable_IconComponent; +}; + +declare const unstable_useThreadMessageIds: () => readonly string[]; + +declare function unstable_useTriggerPopoverAriaProps(): Unstable_TriggerPopoverAriaProps; + +declare const useActionBarEdit: () => (() => void) | null; + +declare const useActionBarExportMarkdown: (_param10?: { + filename?: string | undefined; + onExport?: ((content: string) => void | Promise<void>) | undefined; +}) => (() => Promise<void>) | null; + +declare const useActionBarFeedbackNegative: () => () => void; + +declare const useActionBarFeedbackPositive: () => () => void; + +declare const useActionBarPrimitiveCopy: (_param11?: { + copiedDuration?: number | undefined; +}) => (() => void) | null; + +declare const useActionBarReload: () => (() => void) | null; + +declare const useActionBarSpeak: () => (() => Promise<void>) | null; + +declare const useActionBarStopSpeaking: () => (() => void) | null; + +declare const useAssistantContext: (config: AssistantContextConfig) => void; + +declare const useAssistantDataUI: (dataUI: AssistantDataUIProps | null) => void; + +declare const useAssistantFrameHost: (_param12: UseAssistantFrameHostOptions) => void; + +declare const useAssistantInstructions: (config: string | AssistantInstructionsConfig) => void; + +declare const useAssistantInteractable: (name: string, config: AssistantInteractableProps) => string; + +declare function useAssistantRuntime(options?: { + optional?: false | undefined; +}): AssistantRuntime; + +declare function useAssistantRuntime(options?: { + optional?: boolean | undefined; +}): AssistantRuntime | null; + +declare const useAssistantTool: <TArgs extends Record<string, unknown>, TResult>(tool: AssistantToolProps<TArgs, TResult>) => void; + +declare const useAssistantToolUI: (tool: AssistantToolUIProps<any, any> | null) => void; + +declare const useAssistantTransportRuntime: <T>(options: AssistantTransportOptions<T>) => AssistantRuntime; + +declare const useAssistantTransportSendCommand: () => (command: AssistantTransportCommand) => void; + +declare function useAssistantTransportState(): UserExternalState; + +declare function useAssistantTransportState<T>(selector: (state: UserExternalState) => T): T; + +declare const useAttachment: { + (): AttachmentState & { + source: "edit-composer" | "message" | "thread-composer"; + }; + <TSelected>(selector: (state: AttachmentState & { + source: "edit-composer" | "message" | "thread-composer"; + }) => TSelected): TSelected; + <TSelected>(selector: ((state: AttachmentState & { + source: "edit-composer" | "message" | "thread-composer"; + }) => TSelected) | undefined): (AttachmentState & { + source: "edit-composer" | "message" | "thread-composer"; + }) | TSelected; + (options: { + optional?: false | undefined; + }): AttachmentState & { + source: "edit-composer" | "message" | "thread-composer"; + }; + (options: { + optional?: boolean | undefined; + }): (AttachmentState & { + source: "edit-composer" | "message" | "thread-composer"; + }) | null; + <TSelected>(options: { + optional?: false | undefined; + selector: (state: AttachmentState & { + source: "edit-composer" | "message" | "thread-composer"; + }) => TSelected; + }): TSelected; + <TSelected>(options: { + optional?: false | undefined; + selector: ((state: AttachmentState & { + source: "edit-composer" | "message" | "thread-composer"; + }) => TSelected) | undefined; + }): (AttachmentState & { + source: "edit-composer" | "message" | "thread-composer"; + }) | TSelected; + <TSelected>(options: { + optional?: boolean | undefined; + selector: (state: AttachmentState & { + source: "edit-composer" | "message" | "thread-composer"; + }) => TSelected; + }): TSelected | null; + <TSelected>(options: { + optional?: boolean | undefined; + selector: ((state: AttachmentState & { + source: "edit-composer" | "message" | "thread-composer"; + }) => TSelected) | undefined; + }): (AttachmentState & { + source: "edit-composer" | "message" | "thread-composer"; + }) | TSelected | null; +}; + +declare const useAttachmentRemove: () => () => void; + +declare function useAttachmentRuntime(options?: { + optional?: false | undefined; +}): AttachmentRuntime; + +declare function useAttachmentRuntime(options?: { + optional?: boolean | undefined; +}): AttachmentRuntime | null; + +declare namespace useAui { + type Props = { + [K in ClientNames]?: ClientElement<K> | DerivedElement<K>; + }; +} + +declare function useAui(): AssistantClient; + +declare function useAui(clients: useAui.Props): AssistantClient; + +declare function useAui(clients: useAui.Props, config: { + parent: null | AssistantClient; +}): AssistantClient; + +declare const useAuiEvent: <TEvent extends AssistantEventName>(selector: AssistantEventSelector<TEvent>, callback: AssistantEventCallback<TEvent>) => void; + +declare const useAuiState: <T>(selector: (state: AssistantState) => T) => T; + +declare function useAuiToolOverrides(overrides: AuiToolOverrides): void; + +declare const useBranchPickerNext: () => (() => void) | null; + +declare const useBranchPickerPrevious: () => (() => void) | null; + +declare const useChainOfThoughtAccordionTrigger: () => () => void; + +declare const useCloudThreadListAdapter: (adapter: CloudThreadListAdapterOptions) => RemoteThreadListAdapter; + +declare function useCloudThreadListRuntime(_param13: CloudThreadListAdapter): AssistantRuntime; + +declare const useComposer: { + (): ComposerState$1; + <TSelected>(selector: (state: ComposerState$1) => TSelected): TSelected; + <TSelected>(selector: ((state: ComposerState$1) => TSelected) | undefined): ComposerState$1 | TSelected; + (options: { + optional?: false | undefined; + }): ComposerState$1; + (options: { + optional?: boolean | undefined; + }): ComposerState$1 | null; + <TSelected>(options: { + optional?: false | undefined; + selector: (state: ComposerState$1) => TSelected; + }): TSelected; + <TSelected>(options: { + optional?: false | undefined; + selector: ((state: ComposerState$1) => TSelected) | undefined; + }): ComposerState$1 | TSelected; + <TSelected>(options: { + optional?: boolean | undefined; + selector: (state: ComposerState$1) => TSelected; + }): TSelected | null; + <TSelected>(options: { + optional?: boolean | undefined; + selector: ((state: ComposerState$1) => TSelected) | undefined; + }): ComposerState$1 | TSelected | null; +}; + +declare const useComposerAddAttachment: (_param14?: { + multiple?: boolean | undefined; +}) => (() => void) | null; + +declare const useComposerCancel: () => (() => void) | null; + +declare const useComposerDictate: () => (() => void) | null; + +declare const useComposerInputPluginRegistryOptional: () => ComposerInputPluginRegistry | null; + +declare function useComposerRuntime(options?: { + optional?: false | undefined; +}): ComposerRuntime; + +declare function useComposerRuntime(options?: { + optional?: boolean | undefined; +}): ComposerRuntime | null; + +declare const useComposerSend: () => (() => void) | null; + +declare const useComposerStopDictation: () => (() => void) | null; + +declare const useEditComposer: { + (): EditComposerState; + <TSelected>(selector: (state: EditComposerState) => TSelected): TSelected; + <TSelected>(selector: ((state: EditComposerState) => TSelected) | undefined): EditComposerState | TSelected; + (options: { + optional?: false | undefined; + }): EditComposerState; + (options: { + optional?: boolean | undefined; + }): EditComposerState | null; + <TSelected>(options: { + optional?: false | undefined; + selector: (state: EditComposerState) => TSelected; + }): TSelected; + <TSelected>(options: { + optional?: false | undefined; + selector: ((state: EditComposerState) => TSelected) | undefined; + }): EditComposerState | TSelected; + <TSelected>(options: { + optional?: boolean | undefined; + selector: (state: EditComposerState) => TSelected; + }): TSelected | null; + <TSelected>(options: { + optional?: boolean | undefined; + selector: ((state: EditComposerState) => TSelected) | undefined; + }): EditComposerState | TSelected | null; +}; + +declare const useEditComposerAttachment: { + (): ({ + id: string; + type: "image" | "document" | "file" | (string & {}); + name: string; + contentType?: string | undefined; + file?: File; + content?: ThreadUserMessagePart[]; + } & { + status: CompleteAttachmentStatus; + content: ThreadUserMessagePart[]; + } & { + readonly source: "edit-composer"; + } & { + source: "edit-composer"; + }) | ({ + id: string; + type: "image" | "document" | "file" | (string & {}); + name: string; + contentType?: string | undefined; + file?: File; + content?: ThreadUserMessagePart[]; + } & { + status: PendingAttachmentStatus; + file: File; + } & { + readonly source: "edit-composer"; + } & { + source: "edit-composer"; + }); + <TSelected>(selector: (state: ({ + id: string; + type: "image" | "document" | "file" | (string & {}); + name: string; + contentType?: string | undefined; + file?: File; + content?: ThreadUserMessagePart[]; + } & { + status: CompleteAttachmentStatus; + content: ThreadUserMessagePart[]; + } & { + readonly source: "edit-composer"; + } & { + source: "edit-composer"; + }) | ({ + id: string; + type: "image" | "document" | "file" | (string & {}); + name: string; + contentType?: string | undefined; + file?: File; + content?: ThreadUserMessagePart[]; + } & { + status: PendingAttachmentStatus; + file: File; + } & { + readonly source: "edit-composer"; + } & { + source: "edit-composer"; + })) => TSelected): TSelected; + <TSelected>(selector: ((state: ({ + id: string; + type: "image" | "document" | "file" | (string & {}); + name: string; + contentType?: string | undefined; + file?: File; + content?: ThreadUserMessagePart[]; + } & { + status: CompleteAttachmentStatus; + content: ThreadUserMessagePart[]; + } & { + readonly source: "edit-composer"; + } & { + source: "edit-composer"; + }) | ({ + id: string; + type: "image" | "document" | "file" | (string & {}); + name: string; + contentType?: string | undefined; + file?: File; + content?: ThreadUserMessagePart[]; + } & { + status: PendingAttachmentStatus; + file: File; + } & { + readonly source: "edit-composer"; + } & { + source: "edit-composer"; + })) => TSelected) | undefined): ({ + id: string; + type: "image" | "document" | "file" | (string & {}); + name: string; + contentType?: string | undefined; + file?: File; + content?: ThreadUserMessagePart[]; + } & { + status: CompleteAttachmentStatus; + content: ThreadUserMessagePart[]; + } & { + readonly source: "edit-composer"; + } & { + source: "edit-composer"; + }) | ({ + id: string; + type: "image" | "document" | "file" | (string & {}); + name: string; + contentType?: string | undefined; + file?: File; + content?: ThreadUserMessagePart[]; + } & { + status: PendingAttachmentStatus; + file: File; + } & { + readonly source: "edit-composer"; + } & { + source: "edit-composer"; + }) | TSelected; + (options: { + optional?: false | undefined; + }): ({ + id: string; + type: "image" | "document" | "file" | (string & {}); + name: string; + contentType?: string | undefined; + file?: File; + content?: ThreadUserMessagePart[]; + } & { + status: CompleteAttachmentStatus; + content: ThreadUserMessagePart[]; + } & { + readonly source: "edit-composer"; + } & { + source: "edit-composer"; + }) | ({ + id: string; + type: "image" | "document" | "file" | (string & {}); + name: string; + contentType?: string | undefined; + file?: File; + content?: ThreadUserMessagePart[]; + } & { + status: PendingAttachmentStatus; + file: File; + } & { + readonly source: "edit-composer"; + } & { + source: "edit-composer"; + }); + (options: { + optional?: boolean | undefined; + }): ({ + id: string; + type: "image" | "document" | "file" | (string & {}); + name: string; + contentType?: string | undefined; + file?: File; + content?: ThreadUserMessagePart[]; + } & { + status: CompleteAttachmentStatus; + content: ThreadUserMessagePart[]; + } & { + readonly source: "edit-composer"; + } & { + source: "edit-composer"; + }) | ({ + id: string; + type: "image" | "document" | "file" | (string & {}); + name: string; + contentType?: string | undefined; + file?: File; + content?: ThreadUserMessagePart[]; + } & { + status: PendingAttachmentStatus; + file: File; + } & { + readonly source: "edit-composer"; + } & { + source: "edit-composer"; + }) | null; + <TSelected>(options: { + optional?: false | undefined; + selector: (state: ({ + id: string; + type: "image" | "document" | "file" | (string & {}); + name: string; + contentType?: string | undefined; + file?: File; + content?: ThreadUserMessagePart[]; + } & { + status: CompleteAttachmentStatus; + content: ThreadUserMessagePart[]; + } & { + readonly source: "edit-composer"; + } & { + source: "edit-composer"; + }) | ({ + id: string; + type: "image" | "document" | "file" | (string & {}); + name: string; + contentType?: string | undefined; + file?: File; + content?: ThreadUserMessagePart[]; + } & { + status: PendingAttachmentStatus; + file: File; + } & { + readonly source: "edit-composer"; + } & { + source: "edit-composer"; + })) => TSelected; + }): TSelected; + <TSelected>(options: { + optional?: false | undefined; + selector: ((state: ({ + id: string; + type: "image" | "document" | "file" | (string & {}); + name: string; + contentType?: string | undefined; + file?: File; + content?: ThreadUserMessagePart[]; + } & { + status: CompleteAttachmentStatus; + content: ThreadUserMessagePart[]; + } & { + readonly source: "edit-composer"; + } & { + source: "edit-composer"; + }) | ({ + id: string; + type: "image" | "document" | "file" | (string & {}); + name: string; + contentType?: string | undefined; + file?: File; + content?: ThreadUserMessagePart[]; + } & { + status: PendingAttachmentStatus; + file: File; + } & { + readonly source: "edit-composer"; + } & { + source: "edit-composer"; + })) => TSelected) | undefined; + }): ({ + id: string; + type: "image" | "document" | "file" | (string & {}); + name: string; + contentType?: string | undefined; + file?: File; + content?: ThreadUserMessagePart[]; + } & { + status: CompleteAttachmentStatus; + content: ThreadUserMessagePart[]; + } & { + readonly source: "edit-composer"; + } & { + source: "edit-composer"; + }) | ({ + id: string; + type: "image" | "document" | "file" | (string & {}); + name: string; + contentType?: string | undefined; + file?: File; + content?: ThreadUserMessagePart[]; + } & { + status: PendingAttachmentStatus; + file: File; + } & { + readonly source: "edit-composer"; + } & { + source: "edit-composer"; + }) | TSelected; + <TSelected>(options: { + optional?: boolean | undefined; + selector: (state: ({ + id: string; + type: "image" | "document" | "file" | (string & {}); + name: string; + contentType?: string | undefined; + file?: File; + content?: ThreadUserMessagePart[]; + } & { + status: CompleteAttachmentStatus; + content: ThreadUserMessagePart[]; + } & { + readonly source: "edit-composer"; + } & { + source: "edit-composer"; + }) | ({ + id: string; + type: "image" | "document" | "file" | (string & {}); + name: string; + contentType?: string | undefined; + file?: File; + content?: ThreadUserMessagePart[]; + } & { + status: PendingAttachmentStatus; + file: File; + } & { + readonly source: "edit-composer"; + } & { + source: "edit-composer"; + })) => TSelected; + }): TSelected | null; + <TSelected>(options: { + optional?: boolean | undefined; + selector: ((state: ({ + id: string; + type: "image" | "document" | "file" | (string & {}); + name: string; + contentType?: string | undefined; + file?: File; + content?: ThreadUserMessagePart[]; + } & { + status: CompleteAttachmentStatus; + content: ThreadUserMessagePart[]; + } & { + readonly source: "edit-composer"; + } & { + source: "edit-composer"; + }) | ({ + id: string; + type: "image" | "document" | "file" | (string & {}); + name: string; + contentType?: string | undefined; + file?: File; + content?: ThreadUserMessagePart[]; + } & { + status: PendingAttachmentStatus; + file: File; + } & { + readonly source: "edit-composer"; + } & { + source: "edit-composer"; + })) => TSelected) | undefined; + }): ({ + id: string; + type: "image" | "document" | "file" | (string & {}); + name: string; + contentType?: string | undefined; + file?: File; + content?: ThreadUserMessagePart[]; + } & { + status: CompleteAttachmentStatus; + content: ThreadUserMessagePart[]; + } & { + readonly source: "edit-composer"; + } & { + source: "edit-composer"; + }) | ({ + id: string; + type: "image" | "document" | "file" | (string & {}); + name: string; + contentType?: string | undefined; + file?: File; + content?: ThreadUserMessagePart[]; + } & { + status: PendingAttachmentStatus; + file: File; + } & { + readonly source: "edit-composer"; + } & { + source: "edit-composer"; + }) | TSelected | null; +}; + +declare function useEditComposerAttachmentRuntime(options?: { + optional?: false | undefined; +}): AttachmentRuntime<"edit-composer">; + +declare function useEditComposerAttachmentRuntime(options?: { + optional?: boolean | undefined; +}): AttachmentRuntime<"edit-composer"> | null; + +declare namespace useExternalMessageConverter { + type Message = (ThreadMessageLike & { + readonly convertConfig?: { + readonly joinStrategy?: JoinStrategy; + }; + }) | { + role: "tool"; + toolCallId: string; + toolName?: string | undefined; + result: any; + artifact?: any; + isError?: boolean; + messages?: readonly ThreadMessage[]; + }; + type Metadata = { + readonly toolStatuses?: Record<string, ToolExecutionStatus>; + readonly error?: ReadonlyJSONValue; + readonly messageTiming?: Record<string, MessageTiming>; + }; + type Callback<T> = (message: T, metadata: Metadata) => Message | Message[]; +} + +declare const useExternalMessageConverter: <T extends WeakKey>(_param15: { + callback: useExternalMessageConverter.Callback<T>; + messages: T[]; + isRunning: boolean; + joinStrategy?: JoinStrategy | undefined; + metadata?: useExternalMessageConverter.Metadata | undefined; +}) => ThreadMessage[]; + +declare const useExternalStoreRuntime: <T>(store: ExternalStoreAdapter<T>) => AssistantRuntime; + +declare const useExternalStoreSharedOptions: (options: ExternalStoreSharedOptions) => ExternalStoreSharedOptions; + +declare const useInlineRender: <TArgs, TResult>(toolUI: FC<ToolCallMessagePartProps<TArgs, TResult>>) => FC<ToolCallMessagePartProps<TArgs, TResult>>; + +declare const useInteractableState: <TState>(id: string, fallback: TState) => [ + TState, + { + setState: (updater: StateUpdater$1<TState>) => void; + setSelected: (selected: boolean) => void; + isPending: boolean; + error: unknown; + flush: () => Promise<void>; + } +]; + +declare const useLocalRuntime: (chatModel: ChatModelAdapter, _param16?: LocalRuntimeOptions) => AssistantRuntime; + +declare const useMessage: { + (): MessageState$1; + <TSelected>(selector: (state: MessageState$1) => TSelected): TSelected; + <TSelected>(selector: ((state: MessageState$1) => TSelected) | undefined): MessageState$1 | TSelected; + (options: { + optional?: false | undefined; + }): MessageState$1; + (options: { + optional?: boolean | undefined; + }): MessageState$1 | null; + <TSelected>(options: { + optional?: false | undefined; + selector: (state: MessageState$1) => TSelected; + }): TSelected; + <TSelected>(options: { + optional?: false | undefined; + selector: ((state: MessageState$1) => TSelected) | undefined; + }): MessageState$1 | TSelected; + <TSelected>(options: { + optional?: boolean | undefined; + selector: (state: MessageState$1) => TSelected; + }): TSelected | null; + <TSelected>(options: { + optional?: boolean | undefined; + selector: ((state: MessageState$1) => TSelected) | undefined; + }): MessageState$1 | TSelected | null; +}; + +declare const useMessageAttachment: { + (): { + id: string; + type: "image" | "document" | "file" | (string & {}); + name: string; + contentType?: string | undefined; + file?: File; + content?: ThreadUserMessagePart[]; + } & { + status: CompleteAttachmentStatus; + content: ThreadUserMessagePart[]; + } & { + readonly source: "message"; + } & { + source: "message"; + }; + <TSelected>(selector: (state: { + id: string; + type: "image" | "document" | "file" | (string & {}); + name: string; + contentType?: string | undefined; + file?: File; + content?: ThreadUserMessagePart[]; + } & { + status: CompleteAttachmentStatus; + content: ThreadUserMessagePart[]; + } & { + readonly source: "message"; + } & { + source: "message"; + }) => TSelected): TSelected; + <TSelected>(selector: ((state: { + id: string; + type: "image" | "document" | "file" | (string & {}); + name: string; + contentType?: string | undefined; + file?: File; + content?: ThreadUserMessagePart[]; + } & { + status: CompleteAttachmentStatus; + content: ThreadUserMessagePart[]; + } & { + readonly source: "message"; + } & { + source: "message"; + }) => TSelected) | undefined): ({ + id: string; + type: "image" | "document" | "file" | (string & {}); + name: string; + contentType?: string | undefined; + file?: File; + content?: ThreadUserMessagePart[]; + } & { + status: CompleteAttachmentStatus; + content: ThreadUserMessagePart[]; + } & { + readonly source: "message"; + } & { + source: "message"; + }) | TSelected; + (options: { + optional?: false | undefined; + }): { + id: string; + type: "image" | "document" | "file" | (string & {}); + name: string; + contentType?: string | undefined; + file?: File; + content?: ThreadUserMessagePart[]; + } & { + status: CompleteAttachmentStatus; + content: ThreadUserMessagePart[]; + } & { + readonly source: "message"; + } & { + source: "message"; + }; + (options: { + optional?: boolean | undefined; + }): ({ + id: string; + type: "image" | "document" | "file" | (string & {}); + name: string; + contentType?: string | undefined; + file?: File; + content?: ThreadUserMessagePart[]; + } & { + status: CompleteAttachmentStatus; + content: ThreadUserMessagePart[]; + } & { + readonly source: "message"; + } & { + source: "message"; + }) | null; + <TSelected>(options: { + optional?: false | undefined; + selector: (state: { + id: string; + type: "image" | "document" | "file" | (string & {}); + name: string; + contentType?: string | undefined; + file?: File; + content?: ThreadUserMessagePart[]; + } & { + status: CompleteAttachmentStatus; + content: ThreadUserMessagePart[]; + } & { + readonly source: "message"; + } & { + source: "message"; + }) => TSelected; + }): TSelected; + <TSelected>(options: { + optional?: false | undefined; + selector: ((state: { + id: string; + type: "image" | "document" | "file" | (string & {}); + name: string; + contentType?: string | undefined; + file?: File; + content?: ThreadUserMessagePart[]; + } & { + status: CompleteAttachmentStatus; + content: ThreadUserMessagePart[]; + } & { + readonly source: "message"; + } & { + source: "message"; + }) => TSelected) | undefined; + }): ({ + id: string; + type: "image" | "document" | "file" | (string & {}); + name: string; + contentType?: string | undefined; + file?: File; + content?: ThreadUserMessagePart[]; + } & { + status: CompleteAttachmentStatus; + content: ThreadUserMessagePart[]; + } & { + readonly source: "message"; + } & { + source: "message"; + }) | TSelected; + <TSelected>(options: { + optional?: boolean | undefined; + selector: (state: { + id: string; + type: "image" | "document" | "file" | (string & {}); + name: string; + contentType?: string | undefined; + file?: File; + content?: ThreadUserMessagePart[]; + } & { + status: CompleteAttachmentStatus; + content: ThreadUserMessagePart[]; + } & { + readonly source: "message"; + } & { + source: "message"; + }) => TSelected; + }): TSelected | null; + <TSelected>(options: { + optional?: boolean | undefined; + selector: ((state: { + id: string; + type: "image" | "document" | "file" | (string & {}); + name: string; + contentType?: string | undefined; + file?: File; + content?: ThreadUserMessagePart[]; + } & { + status: CompleteAttachmentStatus; + content: ThreadUserMessagePart[]; + } & { + readonly source: "message"; + } & { + source: "message"; + }) => TSelected) | undefined; + }): ({ + id: string; + type: "image" | "document" | "file" | (string & {}); + name: string; + contentType?: string | undefined; + file?: File; + content?: ThreadUserMessagePart[]; + } & { + status: CompleteAttachmentStatus; + content: ThreadUserMessagePart[]; + } & { + readonly source: "message"; + } & { + source: "message"; + }) | TSelected | null; +}; + +declare function useMessageAttachmentRuntime(options?: { + optional?: false | undefined; +}): AttachmentRuntime<"message">; + +declare function useMessageAttachmentRuntime(options?: { + optional?: boolean | undefined; +}): AttachmentRuntime<"message"> | null; + +declare const useMessagePart: { + (): MessagePartState; + <TSelected>(selector: (state: MessagePartState) => TSelected): TSelected; + <TSelected>(selector: ((state: MessagePartState) => TSelected) | undefined): MessagePartState | TSelected; + (options: { + optional?: false | undefined; + }): MessagePartState; + (options: { + optional?: boolean | undefined; + }): MessagePartState | null; + <TSelected>(options: { + optional?: false | undefined; + selector: (state: MessagePartState) => TSelected; + }): TSelected; + <TSelected>(options: { + optional?: false | undefined; + selector: ((state: MessagePartState) => TSelected) | undefined; + }): MessagePartState | TSelected; + <TSelected>(options: { + optional?: boolean | undefined; + selector: (state: MessagePartState) => TSelected; + }): TSelected | null; + <TSelected>(options: { + optional?: boolean | undefined; + selector: ((state: MessagePartState) => TSelected) | undefined; + }): MessagePartState | TSelected | null; +}; + +declare const useMessagePartData: <T = any>(name?: string) => DataMessagePart<T> | null; + +declare const useMessagePartFile: () => FileMessagePart & { + readonly status: MessagePartStatus | ToolCallMessagePartStatus; +}; + +declare const useMessagePartImage: () => ImageMessagePart & { + readonly status: MessagePartStatus | ToolCallMessagePartStatus; +}; + +declare const useMessagePartReasoning: () => ReasoningMessagePart & { + readonly status: MessagePartStatus | ToolCallMessagePartStatus; +}; + +declare function useMessagePartRuntime(options?: { + optional?: false | undefined; +}): MessagePartRuntime; + +declare function useMessagePartRuntime(options?: { + optional?: boolean | undefined; +}): MessagePartRuntime | null; + +declare const useMessagePartSource: () => ({ + readonly type: "source"; + readonly sourceType: "url"; + readonly id: string; + readonly url: string; + readonly title?: string; + readonly providerMetadata?: SourceProviderMetadata; + readonly parentId?: string; +} & { + readonly status: MessagePartStatus | ToolCallMessagePartStatus; +}) | ({ + readonly type: "source"; + readonly sourceType: "document"; + readonly id: string; + readonly url?: undefined; + readonly title: string; + readonly mediaType: string; + readonly filename?: string; + readonly providerMetadata?: SourceProviderMetadata; + readonly parentId?: string; +} & { + readonly status: MessagePartStatus | ToolCallMessagePartStatus; +}); + +declare const useMessagePartText: () => (TextMessagePart & { + readonly status: MessagePartStatus | ToolCallMessagePartStatus; +}) | (ReasoningMessagePart & { + readonly status: MessagePartStatus | ToolCallMessagePartStatus; +}); + +declare const useMessageQuote: () => QuoteInfo | undefined; + +declare function useMessageRuntime(options?: { + optional?: false | undefined; +}): MessageRuntime; + +declare function useMessageRuntime(options?: { + optional?: boolean | undefined; +}): MessageRuntime | null; + +declare const useMessageTiming: () => MessageTiming | undefined; + +declare const useQueueItemRemove: () => () => void; + +declare const useQueueItemSteer: () => () => void; + +declare const useRemoteThreadListRuntime: (options: RemoteThreadListOptions) => AssistantRuntime; + +declare const useRuntimeAdapters: () => RuntimeAdapters | null; + +declare const useScrollLock: <T extends HTMLElement = HTMLElement>(animatedElementRef: RefObject<T | null>, animationDuration: number) => () => void; + +declare const useSmooth: (state: MessagePartState & (TextMessagePart | ReasoningMessagePart), smooth?: boolean | SmoothOptions) => MessagePartState & (TextMessagePart | ReasoningMessagePart); + +declare const useSmoothStatus: { + (): { + readonly type: "running"; + } | { + readonly type: "complete"; + } | { + readonly type: "incomplete"; + readonly reason: "cancelled" | "content-filter" | "error" | "length" | "other"; + readonly error?: unknown; + } | { + readonly type: "requires-action"; + readonly reason: "interrupt"; + }; + <TSelected>(selector: (state: { + readonly type: "running"; + } | { + readonly type: "complete"; + } | { + readonly type: "incomplete"; + readonly reason: "cancelled" | "content-filter" | "error" | "length" | "other"; + readonly error?: unknown; + } | { + readonly type: "requires-action"; + readonly reason: "interrupt"; + }) => TSelected): TSelected; + (options: { + optional: true; + }): { + readonly type: "running"; + } | { + readonly type: "complete"; + } | { + readonly type: "incomplete"; + readonly reason: "cancelled" | "content-filter" | "error" | "length" | "other"; + readonly error?: unknown; + } | { + readonly type: "requires-action"; + readonly reason: "interrupt"; + } | null; + <TSelected>(options: { + optional: true; + selector?: (state: { + readonly type: "running"; + } | { + readonly type: "complete"; + } | { + readonly type: "incomplete"; + readonly reason: "cancelled" | "content-filter" | "error" | "length" | "other"; + readonly error?: unknown; + } | { + readonly type: "requires-action"; + readonly reason: "interrupt"; + }) => TSelected; + }): TSelected | null; +}, useSmoothStatusStore: { + (): ReadonlyStore<{ + readonly type: "running"; + } | { + readonly type: "complete"; + } | { + readonly type: "incomplete"; + readonly reason: "cancelled" | "content-filter" | "error" | "length" | "other"; + readonly error?: unknown; + } | { + readonly type: "requires-action"; + readonly reason: "interrupt"; + }>; + (options: { + optional: true; + }): ReadonlyStore<{ + readonly type: "running"; + } | { + readonly type: "complete"; + } | { + readonly type: "incomplete"; + readonly reason: "cancelled" | "content-filter" | "error" | "length" | "other"; + readonly error?: unknown; + } | { + readonly type: "requires-action"; + readonly reason: "interrupt"; + }> | null; +}; + +declare const useSuggestionTrigger: (_param17: { + send?: boolean | undefined; + clearComposer?: boolean | undefined; +}) => (() => void) | null; + +declare const useThread: { + (): ThreadState; + <TSelected>(selector: (state: ThreadState) => TSelected): TSelected; + <TSelected>(selector: ((state: ThreadState) => TSelected) | undefined): ThreadState | TSelected; + (options: { + optional?: false | undefined; + }): ThreadState; + (options: { + optional?: boolean | undefined; + }): ThreadState | null; + <TSelected>(options: { + optional?: false | undefined; + selector: (state: ThreadState) => TSelected; + }): TSelected; + <TSelected>(options: { + optional?: false | undefined; + selector: ((state: ThreadState) => TSelected) | undefined; + }): ThreadState | TSelected; + <TSelected>(options: { + optional?: boolean | undefined; + selector: (state: ThreadState) => TSelected; + }): TSelected | null; + <TSelected>(options: { + optional?: boolean | undefined; + selector: ((state: ThreadState) => TSelected) | undefined; + }): ThreadState | TSelected | null; +}; + +declare const useThreadComposer: { + (): ThreadComposerState; + <TSelected>(selector: (state: ThreadComposerState) => TSelected): TSelected; + <TSelected>(selector: ((state: ThreadComposerState) => TSelected) | undefined): ThreadComposerState | TSelected; + (options: { + optional?: false | undefined; + }): ThreadComposerState; + (options: { + optional?: boolean | undefined; + }): ThreadComposerState | null; + <TSelected>(options: { + optional?: false | undefined; + selector: (state: ThreadComposerState) => TSelected; + }): TSelected; + <TSelected>(options: { + optional?: false | undefined; + selector: ((state: ThreadComposerState) => TSelected) | undefined; + }): ThreadComposerState | TSelected; + <TSelected>(options: { + optional?: boolean | undefined; + selector: (state: ThreadComposerState) => TSelected; + }): TSelected | null; + <TSelected>(options: { + optional?: boolean | undefined; + selector: ((state: ThreadComposerState) => TSelected) | undefined; + }): ThreadComposerState | TSelected | null; +}; + +declare const useThreadComposerAttachment: { + (): ({ + id: string; + type: "image" | "document" | "file" | (string & {}); + name: string; + contentType?: string | undefined; + file?: File; + content?: ThreadUserMessagePart[]; + } & { + status: CompleteAttachmentStatus; + content: ThreadUserMessagePart[]; + } & { + readonly source: "thread-composer"; + } & { + source: "thread-composer"; + }) | ({ + id: string; + type: "image" | "document" | "file" | (string & {}); + name: string; + contentType?: string | undefined; + file?: File; + content?: ThreadUserMessagePart[]; + } & { + status: PendingAttachmentStatus; + file: File; + } & { + readonly source: "thread-composer"; + } & { + source: "thread-composer"; + }); + <TSelected>(selector: (state: ({ + id: string; + type: "image" | "document" | "file" | (string & {}); + name: string; + contentType?: string | undefined; + file?: File; + content?: ThreadUserMessagePart[]; + } & { + status: CompleteAttachmentStatus; + content: ThreadUserMessagePart[]; + } & { + readonly source: "thread-composer"; + } & { + source: "thread-composer"; + }) | ({ + id: string; + type: "image" | "document" | "file" | (string & {}); + name: string; + contentType?: string | undefined; + file?: File; + content?: ThreadUserMessagePart[]; + } & { + status: PendingAttachmentStatus; + file: File; + } & { + readonly source: "thread-composer"; + } & { + source: "thread-composer"; + })) => TSelected): TSelected; + <TSelected>(selector: ((state: ({ + id: string; + type: "image" | "document" | "file" | (string & {}); + name: string; + contentType?: string | undefined; + file?: File; + content?: ThreadUserMessagePart[]; + } & { + status: CompleteAttachmentStatus; + content: ThreadUserMessagePart[]; + } & { + readonly source: "thread-composer"; + } & { + source: "thread-composer"; + }) | ({ + id: string; + type: "image" | "document" | "file" | (string & {}); + name: string; + contentType?: string | undefined; + file?: File; + content?: ThreadUserMessagePart[]; + } & { + status: PendingAttachmentStatus; + file: File; + } & { + readonly source: "thread-composer"; + } & { + source: "thread-composer"; + })) => TSelected) | undefined): ({ + id: string; + type: "image" | "document" | "file" | (string & {}); + name: string; + contentType?: string | undefined; + file?: File; + content?: ThreadUserMessagePart[]; + } & { + status: CompleteAttachmentStatus; + content: ThreadUserMessagePart[]; + } & { + readonly source: "thread-composer"; + } & { + source: "thread-composer"; + }) | ({ + id: string; + type: "image" | "document" | "file" | (string & {}); + name: string; + contentType?: string | undefined; + file?: File; + content?: ThreadUserMessagePart[]; + } & { + status: PendingAttachmentStatus; + file: File; + } & { + readonly source: "thread-composer"; + } & { + source: "thread-composer"; + }) | TSelected; + (options: { + optional?: false | undefined; + }): ({ + id: string; + type: "image" | "document" | "file" | (string & {}); + name: string; + contentType?: string | undefined; + file?: File; + content?: ThreadUserMessagePart[]; + } & { + status: CompleteAttachmentStatus; + content: ThreadUserMessagePart[]; + } & { + readonly source: "thread-composer"; + } & { + source: "thread-composer"; + }) | ({ + id: string; + type: "image" | "document" | "file" | (string & {}); + name: string; + contentType?: string | undefined; + file?: File; + content?: ThreadUserMessagePart[]; + } & { + status: PendingAttachmentStatus; + file: File; + } & { + readonly source: "thread-composer"; + } & { + source: "thread-composer"; + }); + (options: { + optional?: boolean | undefined; + }): ({ + id: string; + type: "image" | "document" | "file" | (string & {}); + name: string; + contentType?: string | undefined; + file?: File; + content?: ThreadUserMessagePart[]; + } & { + status: CompleteAttachmentStatus; + content: ThreadUserMessagePart[]; + } & { + readonly source: "thread-composer"; + } & { + source: "thread-composer"; + }) | ({ + id: string; + type: "image" | "document" | "file" | (string & {}); + name: string; + contentType?: string | undefined; + file?: File; + content?: ThreadUserMessagePart[]; + } & { + status: PendingAttachmentStatus; + file: File; + } & { + readonly source: "thread-composer"; + } & { + source: "thread-composer"; + }) | null; + <TSelected>(options: { + optional?: false | undefined; + selector: (state: ({ + id: string; + type: "image" | "document" | "file" | (string & {}); + name: string; + contentType?: string | undefined; + file?: File; + content?: ThreadUserMessagePart[]; + } & { + status: CompleteAttachmentStatus; + content: ThreadUserMessagePart[]; + } & { + readonly source: "thread-composer"; + } & { + source: "thread-composer"; + }) | ({ + id: string; + type: "image" | "document" | "file" | (string & {}); + name: string; + contentType?: string | undefined; + file?: File; + content?: ThreadUserMessagePart[]; + } & { + status: PendingAttachmentStatus; + file: File; + } & { + readonly source: "thread-composer"; + } & { + source: "thread-composer"; + })) => TSelected; + }): TSelected; + <TSelected>(options: { + optional?: false | undefined; + selector: ((state: ({ + id: string; + type: "image" | "document" | "file" | (string & {}); + name: string; + contentType?: string | undefined; + file?: File; + content?: ThreadUserMessagePart[]; + } & { + status: CompleteAttachmentStatus; + content: ThreadUserMessagePart[]; + } & { + readonly source: "thread-composer"; + } & { + source: "thread-composer"; + }) | ({ + id: string; + type: "image" | "document" | "file" | (string & {}); + name: string; + contentType?: string | undefined; + file?: File; + content?: ThreadUserMessagePart[]; + } & { + status: PendingAttachmentStatus; + file: File; + } & { + readonly source: "thread-composer"; + } & { + source: "thread-composer"; + })) => TSelected) | undefined; + }): ({ + id: string; + type: "image" | "document" | "file" | (string & {}); + name: string; + contentType?: string | undefined; + file?: File; + content?: ThreadUserMessagePart[]; + } & { + status: CompleteAttachmentStatus; + content: ThreadUserMessagePart[]; + } & { + readonly source: "thread-composer"; + } & { + source: "thread-composer"; + }) | ({ + id: string; + type: "image" | "document" | "file" | (string & {}); + name: string; + contentType?: string | undefined; + file?: File; + content?: ThreadUserMessagePart[]; + } & { + status: PendingAttachmentStatus; + file: File; + } & { + readonly source: "thread-composer"; + } & { + source: "thread-composer"; + }) | TSelected; + <TSelected>(options: { + optional?: boolean | undefined; + selector: (state: ({ + id: string; + type: "image" | "document" | "file" | (string & {}); + name: string; + contentType?: string | undefined; + file?: File; + content?: ThreadUserMessagePart[]; + } & { + status: CompleteAttachmentStatus; + content: ThreadUserMessagePart[]; + } & { + readonly source: "thread-composer"; + } & { + source: "thread-composer"; + }) | ({ + id: string; + type: "image" | "document" | "file" | (string & {}); + name: string; + contentType?: string | undefined; + file?: File; + content?: ThreadUserMessagePart[]; + } & { + status: PendingAttachmentStatus; + file: File; + } & { + readonly source: "thread-composer"; + } & { + source: "thread-composer"; + })) => TSelected; + }): TSelected | null; + <TSelected>(options: { + optional?: boolean | undefined; + selector: ((state: ({ + id: string; + type: "image" | "document" | "file" | (string & {}); + name: string; + contentType?: string | undefined; + file?: File; + content?: ThreadUserMessagePart[]; + } & { + status: CompleteAttachmentStatus; + content: ThreadUserMessagePart[]; + } & { + readonly source: "thread-composer"; + } & { + source: "thread-composer"; + }) | ({ + id: string; + type: "image" | "document" | "file" | (string & {}); + name: string; + contentType?: string | undefined; + file?: File; + content?: ThreadUserMessagePart[]; + } & { + status: PendingAttachmentStatus; + file: File; + } & { + readonly source: "thread-composer"; + } & { + source: "thread-composer"; + })) => TSelected) | undefined; + }): ({ + id: string; + type: "image" | "document" | "file" | (string & {}); + name: string; + contentType?: string | undefined; + file?: File; + content?: ThreadUserMessagePart[]; + } & { + status: CompleteAttachmentStatus; + content: ThreadUserMessagePart[]; + } & { + readonly source: "thread-composer"; + } & { + source: "thread-composer"; + }) | ({ + id: string; + type: "image" | "document" | "file" | (string & {}); + name: string; + contentType?: string | undefined; + file?: File; + content?: ThreadUserMessagePart[]; + } & { + status: PendingAttachmentStatus; + file: File; + } & { + readonly source: "thread-composer"; + } & { + source: "thread-composer"; + }) | TSelected | null; +}; + +declare function useThreadComposerAttachmentRuntime(options?: { + optional?: false | undefined; +}): AttachmentRuntime<"thread-composer">; + +declare function useThreadComposerAttachmentRuntime(options?: { + optional?: boolean | undefined; +}): AttachmentRuntime<"thread-composer"> | null; + +declare const useThreadList: { + (): ThreadListState; + <TSelected>(selector: (state: ThreadListState) => TSelected): TSelected; + <TSelected>(selector: ((state: ThreadListState) => TSelected) | undefined): ThreadListState | TSelected; + (options: { + optional?: false | undefined; + }): ThreadListState; + (options: { + optional?: boolean | undefined; + }): ThreadListState | null; + <TSelected>(options: { + optional?: false | undefined; + selector: (state: ThreadListState) => TSelected; + }): TSelected; + <TSelected>(options: { + optional?: false | undefined; + selector: ((state: ThreadListState) => TSelected) | undefined; + }): ThreadListState | TSelected; + <TSelected>(options: { + optional?: boolean | undefined; + selector: (state: ThreadListState) => TSelected; + }): TSelected | null; + <TSelected>(options: { + optional?: boolean | undefined; + selector: ((state: ThreadListState) => TSelected) | undefined; + }): ThreadListState | TSelected | null; +}; + +declare const useThreadListItem: { + (): ThreadListItemState$1; + <TSelected>(selector: (state: ThreadListItemState$1) => TSelected): TSelected; + <TSelected>(selector: ((state: ThreadListItemState$1) => TSelected) | undefined): ThreadListItemState$1 | TSelected; + (options: { + optional?: false | undefined; + }): ThreadListItemState$1; + (options: { + optional?: boolean | undefined; + }): ThreadListItemState$1 | null; + <TSelected>(options: { + optional?: false | undefined; + selector: (state: ThreadListItemState$1) => TSelected; + }): TSelected; + <TSelected>(options: { + optional?: false | undefined; + selector: ((state: ThreadListItemState$1) => TSelected) | undefined; + }): ThreadListItemState$1 | TSelected; + <TSelected>(options: { + optional?: boolean | undefined; + selector: (state: ThreadListItemState$1) => TSelected; + }): TSelected | null; + <TSelected>(options: { + optional?: boolean | undefined; + selector: ((state: ThreadListItemState$1) => TSelected) | undefined; + }): ThreadListItemState$1 | TSelected | null; +}; + +declare const useThreadListItemArchive: () => () => void; + +declare const useThreadListItemDelete: () => () => void; + +declare function useThreadListItemRuntime(options?: { + optional?: false | undefined; +}): ThreadListItemRuntime; + +declare function useThreadListItemRuntime(options?: { + optional?: boolean | undefined; +}): ThreadListItemRuntime | null; + +declare const useThreadListItemTrigger: () => () => void; + +declare const useThreadListItemUnarchive: () => () => void; + +declare const useThreadListLoadMore: () => (() => void) | null; + +declare function useThreadModelContext(options?: { + optional?: false | undefined; +}): ModelContext$1; + +declare function useThreadModelContext(options?: { + optional?: boolean | undefined; +}): ModelContext$1 | null; + +declare function useThreadRuntime(options?: { + optional?: false | undefined; +}): ThreadRuntime; + +declare function useThreadRuntime(options?: { + optional?: boolean | undefined; +}): ThreadRuntime | null; + +declare namespace useThreadScrollToBottom { + type Options = { + behavior?: ScrollBehavior | undefined; + }; +} + +declare const useThreadScrollToBottom: (_param18?: useThreadScrollToBottom.Options) => (() => void) | null; + +declare const useThreadSuggestion: (_param19: { + prompt: string; + send?: boolean | undefined; + clearComposer?: boolean | undefined; + autoSend?: boolean | undefined; + method?: "replace"; +}) => (() => void) | null; + +declare const useThreadViewport: { + (): ThreadViewportState; + <TSelected>(selector: (state: ThreadViewportState) => TSelected): TSelected; + (options: { + optional: true; + }): ThreadViewportState | null; + <TSelected>(options: { + optional: true; + selector?: (state: ThreadViewportState) => TSelected; + }): TSelected | null; +}, useThreadViewportStore: { + (): ReadonlyStore<ThreadViewportState>; + (options: { + optional: true; + }): ReadonlyStore<ThreadViewportState> | null; +}; + +declare namespace useThreadViewportAutoScroll { + type Options = { + autoScroll?: boolean | undefined; + scrollToBottomOnRunStart?: boolean | undefined; + scrollToBottomOnInitialize?: boolean | undefined; + scrollToBottomOnThreadSwitch?: boolean | undefined; + }; +} + +declare const useThreadViewportAutoScroll: <TElement extends HTMLElement>(_param20: useThreadViewportAutoScroll.Options) => RefCallback<TElement>; + +declare const useToolArgsStatus: <TArgs extends Record<string, unknown> = Record<string, unknown>>() => ToolArgsStatus<TArgs>; + +declare const useToolCallElapsed: () => number | undefined; + +declare const useTriggerPopoverRootContext: () => TriggerPopoverRootContextValue; + +declare const useTriggerPopoverRootContextOptional: () => TriggerPopoverRootContextValue | null; + +declare const useTriggerPopoverScopeContext: () => TriggerPopoverResourceOutput; + +declare const useTriggerPopoverScopeContextOptional: () => TriggerPopoverResourceOutput | null; + +declare const useTriggerPopoverTriggers: () => ReadonlyMap<string, RegisteredTrigger>; + +declare const useTriggerPopoverTriggersOptional: () => ReadonlyMap<string, RegisteredTrigger>; + +declare const useVoiceControls: () => { + connect: () => void; + disconnect: () => void; + mute: () => void; + unmute: () => void; +}; + +declare const useVoiceState: () => VoiceSessionState | undefined; + +declare const useVoiceVolume: () => number; + +declare const withSmoothContextProvider: <C extends ComponentType<any>>(Component: C) => C; + +export { entry_root_exports as entry_root }; diff --git a/api-surface/assistant-ui__store.ts b/api-surface/assistant-ui__store.ts new file mode 100644 index 0000000..9c31b39 --- /dev/null +++ b/api-surface/assistant-ui__store.ts @@ -0,0 +1,254 @@ +import React, { FC, PropsWithChildren, ReactNode } from "react"; + +type AncestorsOf<K extends ClientNames, Seen extends ClientNames = never> = K extends Seen ? never : ParentOf<K> extends never ? never : ParentOf<K> | AncestorsOf<ParentOf<K>, Seen | K>; + +type AssistantClient = { + [K in ClientNames]: AssistantClientAccessor<K>; +} & { + subscribe(listener: () => void): Unsubscribe; + on<TEvent extends AssistantEventName>(selector: AssistantEventSelector<TEvent>, callback: AssistantEventCallback<TEvent>): Unsubscribe; +}; + +type AssistantClientAccessor<K extends ClientNames> = (() => ClientSchemas[K]["methods"]) & (ClientMeta<K> | { + source: "root"; + query: Record<string, never>; +} | { + source: null; + query: null; +}) & { + name: K; +}; + +type AssistantEventCallback<TEvent extends AssistantEventName> = (payload: AssistantEventPayload[TEvent]) => void; + +type AssistantEventName = keyof AssistantEventPayload; + +type AssistantEventPayload = ClientEventMap & { + "*": WildcardPayload; +}; + +type AssistantEventScope<TEvent extends AssistantEventName> = "*" | EventSource<TEvent> | (EventSource<TEvent> extends ClientNames ? AncestorsOf<EventSource<TEvent>> : never); + +type AssistantEventSelector<TEvent extends AssistantEventName> = TEvent | { + scope: AssistantEventScope<TEvent>; + event: TEvent; +}; + +type AssistantState = { + [K in ClientNames]: ClientSchemas[K]["methods"] extends { + getState: () => infer S; + } ? S : never; +}; + +declare namespace AuiIf { + type Props = PropsWithChildren<{ + condition: AuiIf.Condition; + }>; + type Condition = (state: AssistantState) => boolean; +} + +declare const AuiIf: FC<AuiIf.Props>; + +declare const AuiProvider: (_param0: { + value: AssistantClient; + children: React.ReactNode; +}) => React.ReactElement; + +type ClientElement<K extends ClientNames> = ResourceElement<ClientOutput<K>>; + +type ClientError<E extends string> = { + methods: Record<E, () => E>; + meta: { + source: ClientNames; + query: Record<E, E>; + }; + events: Record<`${E}.`, E>; +}; + +type ClientEventMap = UnionToIntersection<{ + [K in ClientNames]: ClientEvents<K>; +}[ClientNames]>; + +type ClientEvents<K extends ClientNames> = "events" extends keyof ClientSchemas[K] ? ClientSchemas[K]["events"] extends ClientEventsType<K> ? ClientSchemas[K]["events"] : never : never; + +type ClientEventsType<K extends ClientNames> = Record<`${K}.${string}`, unknown>; + +type ClientMeta<K extends ClientNames> = "meta" extends keyof ClientSchemas[K] ? Pick<ClientSchemas[K]["meta"] extends ClientMetaType ? ClientSchemas[K]["meta"] : never, "query" | "source"> : never; + +type ClientMetaType = { + source: ClientNames; + query: Record<string, unknown>; +}; + +interface ClientMethods { + [key: string | symbol]: (...args: any[]) => any; +} + +type ClientNames = keyof ClientSchemas extends (infer U) ? U : never; + +type ClientOutput<K extends ClientNames> = ClientSchemas[K]["methods"] & ClientMethods; + +type ClientSchema<TMethods extends ClientMethods = ClientMethods, TMeta extends ClientMetaType = never, TEvents extends Record<string, unknown> = never> = { + methods: TMethods; + meta?: TMeta; + events?: TEvents; +}; + +type ClientSchemas = keyof ScopeRegistry extends never ? { + "ERROR: No clients were defined": ClientError<"ERROR: No clients were defined">; +} : { + [K in keyof ScopeRegistry]: ValidateClient<K>; +}; + +type ContravariantResource<R, A extends readonly unknown[] = any[]> = (...args: A) => ResourceElement<R>; + +declare const Derived: <K extends ClientNames>(_config: Derived.Props<K>) => ResourceElement<null, [ + _config: Derived.Props<K> +]>; + +declare namespace Derived { + type Props<K extends ClientNames> = { + get: (client: AssistantClient) => ReturnType<AssistantClientAccessor<K>>; + } & ClientMeta<K>; +} + +type DerivedElement<K extends ClientNames> = ResourceElement<null, [ + Derived.Props<K> +]>; + +type EventSource<T extends AssistantEventName> = T extends `${infer Source}.${string}` ? Source : never; + +type Hook = (...args: any[]) => any; + +type InferClientState<TMethods> = TMethods extends { + getState: () => infer S; +} ? S : unknown; + +type InferClientState$1<TMethods> = TMethods extends { + getState: () => infer S; +} ? S : unknown; + +type InferClientState$2<TMethods> = TMethods extends { + getState: () => infer S; +} ? S : undefined; + +type ParentOf<K extends ClientNames> = AssistantClientAccessor<K> extends { + source: infer S; +} ? S extends ClientNames ? S : never : never; + +declare function RenderChildrenWithAccessor<T>(_param1: { + getItemState: (aui: AssistantClient) => T; + children: (getItem: () => T) => ReactNode; +}): ReactNode; + +type ResourceElement<R, A extends readonly unknown[] = any[]> = { + readonly hook: (...args: A) => R; + readonly args: Readonly<A>; + readonly key?: string | number; + readonly deps?: readonly unknown[]; +}; + +interface ScopeRegistry { + [key: string]: { methods: any; meta?: any; events?: any }; +} + +type ScopesConfig = { + [K in ClientNames]?: ClientElement<K> | DerivedElement<K>; +}; + +type TransformScopesFn = (scopes: ScopesConfig, parent: AssistantClient) => void; + +type UnionToIntersection<U> = (U extends unknown ? (x: U) => void : never) extends ((x: infer I) => void) ? I : never; + +type Unsubscribe = () => void; + +type ValidateClient<K extends keyof ScopeRegistry> = ScopeRegistry[K] extends { + methods: ClientMethods; +} ? "meta" extends keyof ScopeRegistry[K] ? ScopeRegistry[K]["meta"] extends ClientMetaType ? "events" extends keyof ScopeRegistry[K] ? ScopeRegistry[K]["events"] extends ClientEventsType<K> ? ScopeRegistry[K] : ClientError<`ERROR: ${K & string} has invalid events type`> : ScopeRegistry[K] : ClientError<`ERROR: ${K & string} has invalid meta type`> : "events" extends keyof ScopeRegistry[K] ? ScopeRegistry[K]["events"] extends ClientEventsType<K> ? ScopeRegistry[K] : ClientError<`ERROR: ${K & string} has invalid events type`> : ScopeRegistry[K] : ClientError<`ERROR: ${K & string} has invalid methods type`>; + +type WildcardPayload = { + [K in keyof ClientEventMap]: { + event: K; + payload: ClientEventMap[K]; + }; +}[Extract<keyof ClientEventMap, string>]; + +declare function attachTransformScopes(hook: Hook, transform: TransformScopesFn): void; + +declare function forwardTransformScopes(target: Hook, source: Hook): void; + +declare namespace entry_root_exports { + export { AssistantClient, AssistantClientAccessor, AssistantEventCallback, AssistantEventName, AssistantEventPayload, AssistantEventScope, AssistantEventSelector, AssistantState, AuiIf, AuiProvider, ClientElement, ClientEvents, ClientMeta, ClientMethods, ClientNames, ClientOutput, ClientSchema, Derived, RenderChildrenWithAccessor, ScopeRegistry, ScopesConfig, Unsubscribe, attachTransformScopes, forwardTransformScopes, normalizeEventSelector, useAssistantClientRef, useAssistantEmit, useAui, useAuiEvent, useAuiState, useClientList, useClientLookup, useClientResource }; +} + +declare const normalizeEventSelector: <TEvent extends AssistantEventName>(selector: AssistantEventSelector<TEvent>) => { + scope: AssistantEventScope<TEvent>; + event: TEvent; +}; + +declare const useAssistantClientRef: () => { + parent: AssistantClient; + current: AssistantClient | null; +}; + +declare const useAssistantEmit: () => <TEvent extends Exclude<AssistantEventName, "*">>(event: TEvent, payload: AssistantEventPayload[TEvent]) => void; + +declare namespace useAui { + type Props = { + [K in ClientNames]?: ClientElement<K> | DerivedElement<K>; + }; +} + +declare function useAui(): AssistantClient; + +declare function useAui(clients: useAui.Props): AssistantClient; + +declare function useAui(clients: useAui.Props, config: { + parent: null | AssistantClient; +}): AssistantClient; + +declare const useAuiEvent: <TEvent extends AssistantEventName>(selector: AssistantEventSelector<TEvent>, callback: AssistantEventCallback<TEvent>) => void; + +declare const useAuiState: <T>(selector: (state: AssistantState) => T) => T; + +declare const useClientList: <TData, TMethods extends ClientMethods>(props: useClientList.Props<TData, TMethods>) => { + state: InferClientState<TMethods>[]; + get: (lookup: { + index: number; + } | { + key: string; + }) => TMethods; + add: (initialData: TData) => void; +}; + +declare namespace useClientList { + type ResourceProps<TData> = { + key: string; + getInitialData: () => TData; + remove: () => void; + }; + type Props<TData, TMethods extends ClientMethods> = { + initialValues: TData[]; + getKey: (data: TData) => string; + resource: ContravariantResource<TMethods, [ + ResourceProps<TData> + ]>; + }; +} + +declare function useClientLookup<TMethods extends ClientMethods>(elements: readonly ResourceElement<TMethods>[]): { + state: InferClientState$1<TMethods>[]; + get: (lookup: { + index: number; + } | { + key: string; + }) => TMethods; +}; + +declare const useClientResource: <TMethods extends ClientMethods>(element: ResourceElement<TMethods>) => { + state: InferClientState$2<TMethods>; + methods: TMethods; + key: string | number | undefined; +}; + +export { entry_root_exports as entry_root }; diff --git a/api-surface/assistant-ui__tap.ts b/api-surface/assistant-ui__tap.ts new file mode 100644 index 0000000..808a5bd --- /dev/null +++ b/api-surface/assistant-ui__tap.ts @@ -0,0 +1,55 @@ +import { Context } from "react"; + +type ContravariantResource<R, A extends readonly unknown[] = any[]> = (...args: A) => ResourceElement<R>; + +type ExtractResourceReturnType<T> = T extends ResourceElement<infer R, any> ? R : T extends Resource<infer R, any> ? R : never; + +type Resource<R, A extends readonly unknown[] = any[]> = (...args: A) => ResourceElement<R, A>; + +type ResourceElement<R, A extends readonly unknown[] = any[]> = { + readonly hook: (...args: A) => R; + readonly args: Readonly<A>; + readonly key?: string | number; + readonly deps?: readonly unknown[]; +}; + +declare const createTapRoot: <R>(render: () => R) => useTapRoot.Root<R> & { + unmount: () => void; +}; + +declare const flushTapSync: <T>(callback: () => T) => T; + +declare namespace entry_root_exports { + export { ContravariantResource, Resource, ResourceElement, createTapRoot, flushTapSync, resource, useContextProvider, useResource, useResources, useTapHost, useTapRoot, withKey }; +} + +declare function resource<R, A extends readonly unknown[]>(hook: (...args: A) => R): Resource<R, A>; + +declare const useContextProvider: <T, TResult>(context: Context<T>, value: T, fn: () => TResult) => TResult; + +declare function useResource<E extends ResourceElement<any, any[]>>(element: E): ExtractResourceReturnType<E>; + +declare function useResources<E extends ResourceElement<any, any[]>>(elements: readonly E[]): ExtractResourceReturnType<E>[]; + +declare namespace useTapHost { + interface Result<R> { + value: R; + effects: () => void; + } +} + +declare const useTapHost: <R>(callback: () => R) => useTapHost.Result<R>; + +declare namespace useTapRoot { + type Unsubscribe = () => void; + interface Root<R> { + getValue(): R; + subscribe(listener: () => void): Unsubscribe; + } +} + +declare const useTapRoot: <R>(render: () => R) => useTapRoot.Root<R>; + +declare function withKey<E extends ResourceElement<any, any>>(key: string | number, element: E, deps?: readonly unknown[]): E; + +export { entry_root_exports as entry_root }; diff --git a/api-surface/assistant-ui__vite.ts b/api-surface/assistant-ui__vite.ts new file mode 100644 index 0000000..0cb792b --- /dev/null +++ b/api-surface/assistant-ui__vite.ts @@ -0,0 +1,9 @@ +import { Plugin } from "vite"; + +declare function aui(): Plugin[]; + +declare namespace entry_root_exports { + export { aui }; +} + +export { entry_root_exports as entry_root }; diff --git a/api-surface/assistant-ui__x-generative-compiler.ts b/api-surface/assistant-ui__x-generative-compiler.ts new file mode 100644 index 0000000..c30c20b --- /dev/null +++ b/api-surface/assistant-ui__x-generative-compiler.ts @@ -0,0 +1,31 @@ +interface CompileOptions { + target: Target; + filename?: string; + sourceMaps?: boolean; + injectServerOnly?: boolean; +} + +interface CompileResult { + code: string; + map?: object | null; +} + +declare const DIRECTIVE = "use generative"; + +declare class GenerativeCompileError extends Error { + constructor(message: string, filename?: string); +} + +type Target = "client" | "server"; + +type ToolType = "backend" | "frontend" | "human" | "provider"; + +declare function compileGenerative(code: string, options: CompileOptions): CompileResult; + +declare namespace entry_root_exports { + export { CompileOptions, CompileResult, DIRECTIVE, GenerativeCompileError, Target, ToolType, compileGenerative, isGenerativeModule }; +} + +declare function isGenerativeModule(code: string): boolean; + +export { entry_root_exports as entry_root }; diff --git a/api-surface/create-assistant-ui.ts b/api-surface/create-assistant-ui.ts new file mode 100644 index 0000000..3744b8c --- /dev/null +++ b/api-surface/create-assistant-ui.ts @@ -0,0 +1,73 @@ +type CliSurfaceSnapshot = Record<string, unknown>; + +export const cliSurface: CliSurfaceSnapshot = { + "name": "create-assistant-ui", + "description": "create assistant-ui apps with one command", + "forwardsTo": "assistant-ui create", + "arguments": [ + { + "syntax": "[project-directory]", + "description": "", + "required": false + } + ], + "options": [ + { + "flags": "-t, --template <template>", + "description": "template to use (default, minimal, cloud, cloud-clerk, langchain, mcp, eve)", + "required": true + }, + { + "flags": "-e, --example <example>", + "description": "create from an example (with-ag-ui, with-google-adk, with-ai-sdk-v7, with-eve, with-artifacts, with-assistant-transport, with-chain-of-thought, with-cloud, with-custom-thread-list, with-elevenlabs-conversational, with-elevenlabs-scribe, with-livekit, with-expo, with-interactables, with-external-store, with-ffmpeg, with-langgraph, with-react-hook-form, with-react-ink, with-react-router, with-tanstack, with-resumable-stream)", + "required": true + }, + { + "flags": "-p, --preset <name-or-url>", + "description": "preset name or URL (e.g., chatgpt or https://www.assistant-ui.com/playground/init?preset=chatgpt)", + "required": true + }, + { + "flags": "--use-npm", + "description": "explicitly use npm" + }, + { + "flags": "--use-pnpm", + "description": "explicitly use pnpm" + }, + { + "flags": "--use-yarn", + "description": "explicitly use yarn" + }, + { + "flags": "--use-bun", + "description": "explicitly use bun" + }, + { + "flags": "--native", + "description": "create an Expo / React Native project" + }, + { + "flags": "--ink", + "description": "create a React Ink terminal project" + }, + { + "flags": "--skip-install", + "description": "skip installing packages" + }, + { + "flags": "--skills", + "description": "add assistant-ui agent skills for AI coding assistants" + }, + { + "flags": "--no-skills", + "description": "skip adding assistant-ui agent skills" + }, + { + "flags": "--debug-source-root <path>", + "description": "copy templates/examples from a local assistant-ui repo root", + "required": true, + "hidden": true + } + ] +}; diff --git a/api-surface/heat-graph.ts b/api-surface/heat-graph.ts new file mode 100644 index 0000000..2e56db4 --- /dev/null +++ b/api-surface/heat-graph.ts @@ -0,0 +1,156 @@ +import * as Popper from "@radix-ui/react-popper"; + +import { ComponentPropsWithoutRef, ReactNode } from "react"; + +declare const Cell: import("react").ForwardRefExoticComponent<Omit<import("react").DetailedHTMLProps<import("react").HTMLAttributes<HTMLDivElement>, HTMLDivElement>, "ref"> & { + colorScale?: string[]; +} & import("react").RefAttributes<HTMLDivElement>>; + +type CellData = { + date: Date; + count: number; + level: number; + column: number; + row: number; +}; + +type CellProps = ComponentPropsWithoutRef<"div"> & { + colorScale?: string[]; +}; + +type ClassifyFn = (counts: number[]) => (count: number) => number; + +type ComputeGridOptions = { + data: DataPoint[]; + start?: string | Date | undefined; + end?: string | Date | undefined; + weekStart?: WeekStart | undefined; + classify?: ClassifyFn | undefined; +}; + +declare const DAY_SHORT: readonly [ + "Sun", + "Mon", + "Tue", + "Wed", + "Thu", + "Fri", + "Sat" +]; + +type DataPoint = { + date: string | Date; + count: number; +}; + +type DayLabel = { + dayOfWeek: number; + row: number; +}; + +declare const DayLabels: (_param0: DayLabelsProps) => import("react").JSX.Element[]; + +type DayLabelsProps = { + children: (props: { + label: DayLabel; + }) => ReactNode; +}; + +declare const Grid: import("react").ForwardRefExoticComponent<Omit<Omit<import("react").DetailedHTMLProps<import("react").HTMLAttributes<HTMLDivElement>, HTMLDivElement>, "ref">, "children"> & { + children: (props: { + cell: CellData; + }) => ReactNode; +} & import("react").RefAttributes<HTMLDivElement>>; + +type GridProps = Omit<ComponentPropsWithoutRef<"div">, "children"> & { + children: (props: { + cell: CellData; + }) => ReactNode; +}; + +type HeatGraphState = { + cells: CellData[]; + totalWeeks: number; + monthLabels: MonthLabel[]; + dayLabels: DayLabel[]; + levels: number; + colorScale?: string[] | undefined; +}; + +declare const Legend: (_param1: LegendProps) => import("react").JSX.Element[]; + +type LegendItemData = { + level: number; + color: string | undefined; +}; + +declare const LegendLevel: import("react").ForwardRefExoticComponent<Omit<import("react").DetailedHTMLProps<import("react").HTMLAttributes<HTMLDivElement>, HTMLDivElement>, "ref"> & import("react").RefAttributes<HTMLDivElement>>; + +type LegendLevelProps = ComponentPropsWithoutRef<"div">; + +type LegendProps = { + children: (props: { + item: LegendItemData; + }) => ReactNode; +}; + +declare const MONTH_SHORT: readonly [ + "Jan", + "Feb", + "Mar", + "Apr", + "May", + "Jun", + "Jul", + "Aug", + "Sep", + "Oct", + "Nov", + "Dec" +]; + +type MonthLabel = { + month: number; + column: number; +}; + +declare const MonthLabels: (_param2: MonthLabelsProps) => import("react").JSX.Element[]; + +type MonthLabelsProps = { + children: (props: { + label: MonthLabel; + totalWeeks: number; + }) => ReactNode; +}; + +type PopperContentProps = ComponentPropsWithoutRef<typeof Popper.Content>; + +declare const Root: import("react").ForwardRefExoticComponent<Omit<import("react").DetailedHTMLProps<import("react").HTMLAttributes<HTMLDivElement>, HTMLDivElement>, "ref"> & ComputeGridOptions & { + colorScale?: string[]; +} & import("react").RefAttributes<HTMLDivElement>>; + +type RootProps = ComponentPropsWithoutRef<"div"> & ComputeGridOptions & { + colorScale?: string[]; +}; + +declare const Tooltip: import("react").ForwardRefExoticComponent<Omit<Omit<Popper.PopperContentProps & import("react").RefAttributes<HTMLDivElement>, "ref">, "children"> & { + children: (props: { + cell: CellData; + }) => ReactNode; +} & import("react").RefAttributes<HTMLDivElement>>; + +type TooltipProps = Omit<PopperContentProps, "children"> & { + children: (props: { + cell: CellData; + }) => ReactNode; +}; + +type WeekStart = "monday" | "sunday"; + +declare const autoLevels: (n: number) => ClassifyFn; + +declare namespace entry_root_exports { + export { Cell, CellData, CellProps, ClassifyFn, DAY_SHORT, DataPoint, DayLabel, DayLabels, DayLabelsProps, Grid, GridProps, HeatGraphState, Legend, LegendLevel, LegendLevelProps, LegendProps, MONTH_SHORT, MonthLabel, MonthLabels, MonthLabelsProps, Root, RootProps, Tooltip, TooltipProps, WeekStart, autoLevels }; +} + +export { entry_root_exports as entry_root }; diff --git a/api-surface/package.json b/api-surface/package.json new file mode 100644 index 0000000..18198e0 --- /dev/null +++ b/api-surface/package.json @@ -0,0 +1,48 @@ +{ + "name": "@assistant-ui/api-surface", + "private": true, + "type": "module", + "scripts": { + "generate": "cd .. && node scripts/generate-api-surface.mjs", + "check": "tsc --noEmit" + }, + "dependencies": { + "@ag-ui/client": "^0.0.57", + "@ai-sdk/provider": "^4.0.0", + "@ai-sdk/react": "^4.0.2", + "@earendil-works/pi-coding-agent": "^0.80.3", + "@langchain/langgraph-sdk": "^1.9.25", + "@langchain/react": "^1.0.26", + "@modelcontextprotocol/sdk": "^1.29.0", + "@opencode-ai/sdk": "^1.17.16", + "@radix-ui/react-popper": "^1.3.3", + "@radix-ui/react-primitive": "^2.1.7", + "@standard-schema/spec": "^1.1.0", + "ai": "^7.0.2", + "denque": "^2.1.0", + "eve": "^0.20.0", + "ink": "^7.1.0", + "ink-spinner": "^5.0.0", + "lexical": "^0.46.0", + "markdansi": "^0.3.2", + "radix-ui": "^1.6.2", + "react": "^19.2.7", + "react-hook-form": "^7.81.0", + "react-markdown": "^10.1.0", + "react-native": "^0.86.0", + "react-syntax-highlighter": "^16.1.1", + "react-textarea-autosize": "^8.5.9", + "remark-rehype": "^11.1.2", + "remend": "^1.3.0", + "streamdown": "^2.5.0", + "vite": "^8.1.4", + "zod": "^4.4.3", + "zustand": "^5.0.14" + }, + "devDependencies": { + "@types/node": "^26.1.1", + "@types/react": "^19.2.17", + "@types/react-syntax-highlighter": "^15.5.13", + "typescript": "^7.0.2" + } +} diff --git a/api-surface/safe-content-frame.ts b/api-surface/safe-content-frame.ts new file mode 100644 index 0000000..c06728d --- /dev/null +++ b/api-surface/safe-content-frame.ts @@ -0,0 +1,43 @@ +interface RenderedFrame { + iframe: HTMLIFrameElement; + origin: string; + sendMessage(data: unknown, transfer?: Transferable[]): void; + fullyLoadedPromiseWithTimeout(timeoutMs: number): Promise<void>; + dispose(): void; +} + +declare class SafeContentFrame { + private product; + private options; + constructor(product: string, options?: SafeContentFrameOptions); + renderHtml(html: string, container: HTMLElement, opts?: { + unsafeDocumentWrite?: boolean; + }): Promise<RenderedFrame>; + renderRaw(content: Uint8Array | string, mimeType: string, container: HTMLElement): Promise<RenderedFrame>; + renderPdf(content: Uint8Array, container: HTMLElement): Promise<RenderedFrame>; + private render; + private getSandbox; +} + +interface SafeContentFrameOptions { + useShadowDom?: boolean; + enableBrowserCaching?: boolean; + sandbox?: SandboxOption[]; + salt?: string; +} + +type SandboxOption = "allow-downloads" | "allow-forms" | "allow-modals" | "allow-popups" | "allow-popups-to-escape-sandbox" | "allow-same-origin" | "allow-scripts"; + +declare const enableShadowDom: () => boolean; + +declare namespace entry_root_exports { + export { RenderedFrame, SafeContentFrame, SafeContentFrameOptions, SandboxOption }; +} + +declare namespace entry_shadow_dom_exports { + export { enableShadowDom, unsafeDisableShadowDom }; +} + +declare const unsafeDisableShadowDom: () => boolean; + +export { entry_root_exports as entry_root, entry_shadow_dom_exports as entry_shadow_dom }; diff --git a/api-surface/tsconfig.json b/api-surface/tsconfig.json new file mode 100644 index 0000000..25ded14 --- /dev/null +++ b/api-surface/tsconfig.json @@ -0,0 +1,18 @@ +{ + "compilerOptions": { + "allowJs": false, + "exactOptionalPropertyTypes": false, + "jsx": "react-jsx", + "lib": ["ES2022", "DOM", "DOM.Iterable"], + "module": "Preserve", + "moduleDetection": "force", + "moduleResolution": "Bundler", + "noEmit": true, + "noErrorTruncation": true, + "skipLibCheck": true, + "strict": true, + "target": "ES2022", + "types": ["node", "react"] + }, + "include": ["*.ts"] +} diff --git a/apps/docs/.env.example b/apps/docs/.env.example new file mode 100644 index 0000000..d51e9fe --- /dev/null +++ b/apps/docs/.env.example @@ -0,0 +1,49 @@ +# AI provider used by docs API routes (chat, samples). Read by lib/ai/provider.ts. +# OPENAI_BASE_URL is optional; leave it unset to hit the default OpenAI API, +# or point it at any OpenAI-compatible gateway. +OPENAI_API_KEY=sk-... +OPENAI_BASE_URL= + +# Assistant Cloud project base URL. Required when running the artifacts/docs +# runtime samples that call the cloud (apps/docs/contexts/*RuntimeProvider.tsx). +# Get it from the assistant-cloud dashboard -> your project -> Settings -> Frontend API URL. +NEXT_PUBLIC_ASSISTANT_BASE_URL= + +# Public site URL used for Open Graph metadataBase. Falls back to +# https://www.assistant-ui.com in production and http://localhost:3000 in dev. +NEXT_PUBLIC_APP_URL= + +# Optional. PostHog project key. When unset, analytics are disabled. +NEXT_PUBLIC_POSTHOG_API_KEY= + +# Optional. Personal access token used to raise GitHub API rate limits when +# fetching repo stats, releases, contributors, and stargazers (lib/github.ts). +GITHUB_TOKEN= + +# Optional. AUI-X Prism tracing key (lib/prism-server.ts). Leave unset to +# disable Prism tracing. +AUIX_PRISM_API_KEY= + +# Xulux coding agent — Blaxel cloud sandbox (app/api/xulux). +# Required to provision sandboxes for scaffolding. +# Optional. Set to "1" to enable the AI playground (XuluxApp / Xulux chat routes). +# When unset, /playground defaults to the non-AI UI Builder and all /api/xulux routes return 404. +NEXT_PUBLIC_AUI_AI_PLAYGROUND_ENABLED= +BL_WORKSPACE= +BL_API_KEY= +# Optional sandbox overrides: +BL_SANDBOX_TEMPLATE= +BL_REGION= + +# Xulux agent usage budget (/api/xulux/chat only). +# Enabled by default: $1/chat, $2/day, 8 turns/chat, 10 chats/day. +# Uses Upstash Redis when UPSTASH_REDIS_REST_URL is set; otherwise in-memory (single instance). +# Set to "0" to disable gating entirely. +# XULUX_USAGE_BUDGET_ENABLED=0 +# Optional. Force in-memory store even when Upstash is configured (local/tests). +# XULUX_USAGE_BUDGET_STORE=memory +# Defaults: $1/chat, $2/day, 8 turns/chat, 10 chats/day +# XULUX_CHAT_BUDGET_USD=1 +# XULUX_DAILY_BUDGET_USD=2 +# XULUX_MAX_TURNS_PER_CHAT=8 +# XULUX_MAX_CHATS_PER_DAY=10 diff --git a/apps/docs/.gitignore b/apps/docs/.gitignore new file mode 100644 index 0000000..c82e830 --- /dev/null +++ b/apps/docs/.gitignore @@ -0,0 +1,5 @@ +.vercel +.env*.local +.next +.turbo +dist \ No newline at end of file diff --git a/apps/docs/app/(demos)/demos/[slug]/demo-header.tsx b/apps/docs/app/(demos)/demos/[slug]/demo-header.tsx new file mode 100644 index 0000000..eaf91d3 --- /dev/null +++ b/apps/docs/app/(demos)/demos/[slug]/demo-header.tsx @@ -0,0 +1,62 @@ +"use client"; + +import Image from "next/image"; +import Link from "next/link"; +import { useRouter } from "next/navigation"; +import { Button } from "@/components/ui/radix/button"; +import { Select } from "@/components/assistant-ui/select"; +import { GitHubIcon } from "@/components/icons/github"; +import { ThemeToggle } from "@/components/shared/theme-toggle"; +import { DEMOS, getDemo } from "@/lib/demos"; + +export function DemoHeader({ slug }: { slug: string }) { + const router = useRouter(); + const demo = getDemo(slug); + + return ( + <header className="flex h-12 flex-none items-center justify-between gap-4 border-b px-4"> + <div className="flex min-w-0 items-center"> + <Link href="/" className="flex shrink-0 items-center gap-2"> + <Image + src="/favicon/icon.svg" + alt="assistant-ui logo" + width={18} + height={18} + className="dark:hue-rotate-180 dark:invert" + /> + <span className="hidden font-medium tracking-tight sm:inline"> + assistant-ui + </span> + </Link> + <span className="text-muted-foreground/40 ml-3">/</span> + <Select + value={slug} + onValueChange={(value) => router.push(`/demos/${value}`)} + options={DEMOS.map((d) => ({ value: d.slug, label: d.name }))} + /> + {demo && ( + <p className="text-muted-foreground ml-2 hidden min-w-0 truncate text-xs lg:block"> + {demo.tagline} + </p> + )} + </div> + <div className="flex flex-none items-center gap-1"> + {demo && ( + <a + href={demo.githubLink} + target="_blank" + rel="noopener noreferrer" + className="text-muted-foreground hover:text-foreground flex size-8 items-center justify-center transition-colors" + aria-label="View source on GitHub" + > + <GitHubIcon className="size-4" /> + </a> + )} + <ThemeToggle /> + <Button size="sm" className="ml-1 h-8" asChild> + <Link href="/docs">Get started</Link> + </Button> + </div> + </header> + ); +} diff --git a/apps/docs/app/(demos)/demos/[slug]/page.tsx b/apps/docs/app/(demos)/demos/[slug]/page.tsx new file mode 100644 index 0000000..33473d9 --- /dev/null +++ b/apps/docs/app/(demos)/demos/[slug]/page.tsx @@ -0,0 +1,50 @@ +import type { Metadata } from "next"; +import { notFound } from "next/navigation"; +import { DocsRuntimeProvider } from "@/contexts/DocsRuntimeProvider"; +import { DEMOS, getDemo } from "@/lib/demos"; +import { createOgMetadata } from "@/lib/og"; +import { DemoHeader } from "./demo-header"; + +export function generateStaticParams() { + return DEMOS.map((demo) => ({ slug: demo.slug })); +} + +export async function generateMetadata({ + params, +}: { + params: Promise<{ slug: string }>; +}): Promise<Metadata> { + const { slug } = await params; + const demo = getDemo(slug); + if (!demo) return {}; + + const title = `${demo.name} demo`; + return { + title, + description: demo.description, + ...createOgMetadata(title, demo.description), + }; +} + +export default async function DemoPage({ + params, +}: { + params: Promise<{ slug: string }>; +}) { + const { slug } = await params; + const demo = getDemo(slug); + if (!demo) notFound(); + + const DemoComponent = demo.component; + + return ( + <div className="bg-background flex h-dvh flex-col overflow-hidden"> + <DemoHeader slug={demo.slug} /> + <main className="min-h-0 flex-1"> + <DocsRuntimeProvider> + <DemoComponent /> + </DocsRuntimeProvider> + </main> + </div> + ); +} diff --git a/apps/docs/app/(demos)/heat-graph/heat-graph-demo.tsx b/apps/docs/app/(demos)/heat-graph/heat-graph-demo.tsx new file mode 100644 index 0000000..cbb68a2 --- /dev/null +++ b/apps/docs/app/(demos)/heat-graph/heat-graph-demo.tsx @@ -0,0 +1,686 @@ +"use client"; + +import { useMemo, useState } from "react"; +import * as HeatGraph from "heat-graph"; +import { SyntaxHighlighter } from "@/components/assistant-ui/shiki-highlighter"; +import { cn } from "@/lib/utils"; + +function seededRandom(seed: number) { + let s = seed; + return () => { + s = (s * 16807) % 2147483647; + return s / 2147483647; + }; +} + +const generateContributions = (): HeatGraph.DataPoint[] => { + const end = new Date(); + const data: HeatGraph.DataPoint[] = []; + const rand = seededRandom(42); + + // Momentum: positive = active streak, negative = inactive streak + let momentum = 0; + + for (let i = 0; i < 365; i++) { + const date = new Date(end); + date.setDate(date.getDate() - (364 - i)); + + const dow = date.getDay(); + const isWeekend = dow === 0 || dow === 6; + const r = rand(); + + // Drift momentum toward 0 slowly, with random jolts + momentum = momentum * 0.85 + (rand() - 0.5) * 1.5; + // Occasional vacation: strong negative jolt + if (rand() < 0.01) momentum = -3; + // Occasional burst week: strong positive jolt + if (rand() < 0.015) momentum = 3; + + // Probability of being active today + const activeProb = isWeekend ? 0.25 : 0.7; + const adjusted = activeProb + momentum * 0.12; + + if (r > adjusted) continue; + + // Count: weekdays get more, peaks come from high momentum + const base = isWeekend ? 1 : 3; + const peak = Math.max(0, momentum) * 3; + const noise = rand() * 6; + const count = Math.max(1, Math.round(base + peak + noise)); + + data.push({ date, count }); + } + return data; +}; + +const generateSteps = (): HeatGraph.DataPoint[] => { + const end = new Date(); + const data: HeatGraph.DataPoint[] = []; + const rand = seededRandom(77); + + let momentum = 0; + + for (let i = 0; i < 365; i++) { + const date = new Date(end); + date.setDate(date.getDate() - (364 - i)); + + const dow = date.getDay(); + const isWeekend = dow === 0 || dow === 6; + + // Momentum: positive = active week, negative = couch week + momentum = momentum * 0.9 + (rand() - 0.48) * 1.2; + if (rand() < 0.008) momentum = -2.5; // sick/lazy spell + if (rand() < 0.012) momentum = 2.5; // training kick + + // Weekdays varied (~5-12k), weekends usually lazy but occasional big days + const baseSteps = isWeekend ? 1500 : 7000; + const momentumBoost = momentum * 1500; + const noise = isWeekend ? (rand() - 0.5) * 1000 : (rand() - 0.5) * 4000; + + // Some weekends are extremely active (hikes, trips) — really pop + const spike = isWeekend && rand() < 0.1 ? 10000 + rand() * 5000 : 0; + + const count = Math.max( + 500, + Math.round(baseSteps + momentumBoost + noise + spike), + ); + data.push({ date, count }); + } + return data; +}; + +const generateGym = (): HeatGraph.DataPoint[] => { + const end = new Date(); + const data: HeatGraph.DataPoint[] = []; + const rand = seededRandom(123); + + let momentum = 0; + let weekSessions = 0; + + for (let i = 0; i < 365; i++) { + const date = new Date(end); + date.setDate(date.getDate() - (364 - i)); + + const dow = date.getDay(); + + // Reset weekly counter on Sunday + if (dow === 0) weekSessions = 0; + + // Motivation drift + momentum = momentum * 0.9 + (rand() - 0.45) * 0.8; + if (rand() < 0.01) momentum = -2; // injury/vacation + if (rand() < 0.015) momentum = 1.5; // new year energy + + // Target: 3 sessions per week (weekends preferred) + const sessionsLeft = 3 - weekSessions; + const daysLeftInWeek = 7 - dow; + const urgency = daysLeftInWeek > 0 ? sessionsLeft / daysLeftInWeek : 0; + const overTarget = weekSessions >= 3 ? -0.35 : 0; + const dayBase = dow === 0 || dow === 6 ? 0.45 : 0.25; + + const goProb = dayBase + urgency * 0.3 + momentum * 0.1 + overTarget; + + if (rand() < Math.max(0, Math.min(0.85, goProb))) { + weekSessions++; + data.push({ date, count: 1 }); + } + } + return data; +}; + +const generateMood = (): HeatGraph.DataPoint[] => { + const end = new Date(); + const data: HeatGraph.DataPoint[] = []; + const rand = seededRandom(99); + + // Mood drifts around 2.3 (slightly positive) with mean-reversion + let mood = 2.3; + + for (let i = 0; i < 365; i++) { + const date = new Date(end); + date.setDate(date.getDate() - (364 - i)); + + // Pull back toward slightly-positive baseline + mood = mood + (2.3 - mood) * 0.12 + (rand() - 0.5) * 0.7; + // Rare bad streak + if (rand() < 0.006) mood = 1.1; + // Occasional great streak + if (rand() < 0.02) mood = 2.9; + + mood = Math.max(1, Math.min(3, mood)); + + data.push({ date, count: Math.round(mood) }); + } + return data; +}; + +type ThemeName = + | "Contributions" + | "Steps Walked" + | "Gym Tracker" + | "Mood Tracker"; +const THEME_NAMES: ThemeName[] = [ + "Contributions", + "Steps Walked", + "Gym Tracker", + "Mood Tracker", +]; + +const THEME_SWATCHES: Record<ThemeName, readonly string[]> = { + Contributions: ["#0e4429", "#006d32", "#26a641", "#39d353"], + "Steps Walked": ["#1e3a5f", "#2563eb", "#60a5fa", "#93c5fd"], + "Gym Tracker": ["#292017", "#f59e0b"], + "Mood Tracker": ["#b91c1c", "#3a3a3a", "#16a34a"], +}; + +// Shape styles for tab swatches +const SWATCH_STYLES: Record< + ThemeName, + readonly { className: string; style?: React.CSSProperties }[] +> = { + Contributions: [ + { className: "inline-block size-2.5 rounded-[1px]" }, + { className: "inline-block size-2.5 rounded-[1px]" }, + { className: "inline-block size-2.5 rounded-[1px]" }, + { className: "inline-block size-2.5 rounded-[1px]" }, + ], + "Steps Walked": [ + { className: "inline-block size-2.5 rounded-full" }, + { className: "inline-block size-2.5 rounded-full" }, + { className: "inline-block size-2.5 rounded-full" }, + { className: "inline-block size-2.5 rounded-full" }, + ], + "Gym Tracker": [ + { className: "inline-block size-2.5 rotate-45 scale-[0.78]" }, + { className: "inline-block size-2.5 rotate-45 scale-[0.78]" }, + ], + "Mood Tracker": [ + { + className: "inline-block size-2.5", + style: { clipPath: "polygon(0% 0%, 100% 0%, 50% 100%)" }, + }, + { + className: "inline-block size-2.5", + style: { + clipPath: + "polygon(50% 0%, 100% 25%, 100% 75%, 50% 100%, 0% 75%, 0% 25%)", + }, + }, + { + className: "inline-block size-2.5", + style: { clipPath: "polygon(50% 0%, 100% 100%, 0% 100%)" }, + }, + ], +}; + +const STEPS_GRADIENT = [ + [15, 23, 42], // 0: near-invisible + [25, 45, 100], // low: hint of blue + [45, 100, 220], // mid: solid blue + [100, 170, 250], // high: bright blue + [200, 230, 255], // max: near-white +] as const; + +const STEPS_MAX = 15000; + +function stepsColor(count: number): string { + const t = count / STEPS_MAX; + const clamped = Math.min(1, Math.max(0, t)); + + const pos = clamped * (STEPS_GRADIENT.length - 1); + const i = Math.min(Math.floor(pos), STEPS_GRADIENT.length - 2); + const frac = pos - i; + + const a = STEPS_GRADIENT[i]!; + const b = STEPS_GRADIENT[i + 1]!; + const r = Math.round(a[0] + (b[0] - a[0]) * frac); + const g = Math.round(a[1] + (b[1] - a[1]) * frac); + const bl = Math.round(a[2] + (b[2] - a[2]) * frac); + + return `rgb(${r},${g},${bl})`; +} + +const MOOD_LABELS = ["", "Bad", "Neutral", "Great"]; + +// Per-mood shapes: down triangle (bad), hexagon (neutral), up triangle (great) +const MOOD_SHAPES: Record<number, React.CSSProperties> = { + 0: {}, // no entry + 1: { clipPath: "polygon(0% 0%, 100% 0%, 50% 100%)" }, // down triangle + 2: { + clipPath: "polygon(50% 0%, 100% 25%, 100% 75%, 50% 100%, 0% 75%, 0% 25%)", + }, // hexagon + 3: { clipPath: "polygon(50% 0%, 100% 100%, 0% 100%)" }, // up triangle +}; + +const formatDate = (date: Date) => + date.toLocaleDateString("en-US", { + month: "short", + day: "numeric", + year: "numeric", + }); + +type CellInfo = { + date: Date; + count: number; + level: number; + column: number; + row: number; +}; + +function GraphShell({ + data, + containerClassName, + labelClassName, + weekStart, + colorScale, + classify, + dayLabelFull, + cellClassName, + cellStyle, + legend, + tooltipClassName, + tooltipContent, +}: { + data: HeatGraph.DataPoint[]; + containerClassName: string; + labelClassName: string; + weekStart?: "sunday" | "monday"; + colorScale: string[]; + classify?: (counts: number[]) => (count: number) => number; + dayLabelFull?: boolean; + cellClassName?: string; + cellStyle?: + | React.CSSProperties + | ((cell: CellInfo) => React.CSSProperties | undefined); + legend: React.ReactNode; + tooltipClassName: string; + tooltipContent: (cell: CellInfo) => React.ReactNode; +}) { + return ( + <div + className={cn( + "w-fit rounded-lg border px-4 pt-4 pb-2", + containerClassName, + )} + > + <HeatGraph.Root + data={data} + weekStart={weekStart} + classify={classify} + colorScale={colorScale} + className="flex flex-col" + > + <div + className="relative" + style={{ height: 13, marginLeft: 31, marginBottom: 8 }} + > + <HeatGraph.MonthLabels> + {({ label, totalWeeks }) => ( + <span + className={cn( + "absolute text-[12px] leading-[13px]", + labelClassName, + )} + style={{ left: `${(label.column / totalWeeks) * 100}%` }} + > + {HeatGraph.MONTH_SHORT[label.month]} + </span> + )} + </HeatGraph.MonthLabels> + </div> + + <div className="flex" style={{ gap: 3 }}> + <div className="flex shrink-0 flex-col" style={{ width: 28, gap: 3 }}> + <HeatGraph.DayLabels> + {({ label }) => ( + <span + className={cn( + "relative text-[12px] leading-[10px]", + labelClassName, + )} + style={{ height: 10 }} + > + {label.row % 2 === 1 ? ( + <span className="absolute" style={{ bottom: -3 }}> + {dayLabelFull + ? HeatGraph.DAY_SHORT[label.dayOfWeek] + : HeatGraph.DAY_SHORT[label.dayOfWeek]?.charAt(0)} + </span> + ) : null} + </span> + )} + </HeatGraph.DayLabels> + </div> + + <HeatGraph.Grid + style={{ + gap: 3, + gridTemplateRows: "repeat(7, 10px)", + }} + > + {({ cell }) => ( + <HeatGraph.Cell + className={cn("size-[10px]", cellClassName)} + style={ + typeof cellStyle === "function" ? cellStyle(cell) : cellStyle + } + /> + )} + </HeatGraph.Grid> + </div> + + <div className="flex items-center justify-end" style={{ marginTop: 8 }}> + {legend} + </div> + + <HeatGraph.Tooltip + className={cn( + "pointer-events-none rounded-md px-3 py-1.5 text-xs whitespace-nowrap shadow-lg ring-1", + tooltipClassName, + )} + > + {({ cell }) => tooltipContent(cell)} + </HeatGraph.Tooltip> + </HeatGraph.Root> + </div> + ); +} + +function ContributionsGraph({ data }: { data: HeatGraph.DataPoint[] }) { + return ( + <GraphShell + data={data} + containerClassName="border-[#30363d] bg-[#0d1117]" + labelClassName="text-[#e6edf3]" + weekStart="sunday" + colorScale={["#161b22", "#0e4429", "#006d32", "#26a641", "#39d353"]} + dayLabelFull + cellClassName="rounded-[2px]" + legend={ + <div + className="flex items-center text-[12px] text-[#8b949e]" + style={{ gap: 4 }} + > + <span style={{ marginRight: 2 }}>Less</span> + <HeatGraph.Legend> + {() => ( + <HeatGraph.LegendLevel + style={{ width: 10, height: 10 }} + className="rounded-[2px]" + /> + )} + </HeatGraph.Legend> + <span style={{ marginLeft: 2 }}>More</span> + </div> + } + tooltipClassName="bg-[#1b1f23] text-[#e6edf3] ring-[#30363d]" + tooltipContent={(cell) => ( + <> + {cell.count === 0 ? ( + "No contributions" + ) : ( + <strong> + {cell.count} contribution{cell.count !== 1 ? "s" : ""} + </strong> + )}{" "} + on {formatDate(cell.date)} + </> + )} + /> + ); +} + +function StepsGraph({ data }: { data: HeatGraph.DataPoint[] }) { + return ( + <GraphShell + data={data} + containerClassName="border-blue-900/40 bg-[#0a1628]" + labelClassName="text-blue-300/70" + weekStart="monday" + colorScale={["#1e293b", "#1e3a5f", "#2563eb", "#60a5fa", "#93c5fd"]} + cellClassName="rounded-full" + cellStyle={(cell) => ({ backgroundColor: stepsColor(cell.count) })} + legend={ + <div + className="flex items-center text-[12px] text-blue-300/70" + style={{ gap: 6 }} + > + <span>0</span> + <div + style={{ + width: 80, + height: 10, + borderRadius: 5, + background: + "linear-gradient(to right, rgb(15,23,42), rgb(45,100,220), rgb(100,170,250), rgb(200,230,255))", + }} + /> + <span>15k</span> + </div> + } + tooltipClassName="bg-blue-950 text-blue-50 ring-blue-800/50" + tooltipContent={(cell) => ( + <> + <strong>{cell.count.toLocaleString()} steps</strong> on{" "} + {formatDate(cell.date)} + </> + )} + /> + ); +} + +function GymGraph({ data }: { data: HeatGraph.DataPoint[] }) { + return ( + <GraphShell + data={data} + containerClassName="border-amber-900/40 bg-[#1a1207]" + labelClassName="text-amber-400/70" + weekStart="sunday" + classify={HeatGraph.autoLevels(2)} + colorScale={["#292017", "#f59e0b"]} + cellClassName="rotate-45 scale-[0.72]" + legend={ + <div + className="flex items-center text-[12px] text-amber-400/70" + style={{ gap: 4 }} + > + <div + className="size-2.5 scale-[0.72] rotate-45" + style={{ backgroundColor: "#292017" }} + /> + <span>Rest</span> + <div + className="size-2.5 scale-[0.72] rotate-45" + style={{ backgroundColor: "#f59e0b" }} + /> + <span>Gym</span> + </div> + } + tooltipClassName="bg-amber-950 text-amber-50 ring-amber-800/50" + tooltipContent={(cell) => ( + <> + <strong>{cell.count === 0 ? "Rest day" : "Gym day"}</strong> + {" — "} + {formatDate(cell.date)} + </> + )} + /> + ); +} + +function MoodGraph({ data }: { data: HeatGraph.DataPoint[] }) { + return ( + <GraphShell + data={data} + containerClassName="border-neutral-700/40 bg-[#121212]" + labelClassName="text-neutral-400/70" + weekStart="monday" + classify={() => (count: number) => count} + colorScale={["#121212", "#b91c1c", "#1e1e1e", "#16a34a"]} + cellStyle={(cell) => MOOD_SHAPES[cell.count]} + legend={ + <div + className="flex items-center text-[12px] text-neutral-400/70" + style={{ gap: 6 }} + > + {[ + { level: 1, label: "Bad", color: "#b91c1c" }, + { level: 2, label: "Neutral", color: "#3a3a3a" }, + { level: 3, label: "Great", color: "#16a34a" }, + ].map(({ level, label, color }) => ( + <div key={label} className="flex items-center" style={{ gap: 3 }}> + <div + style={{ + width: 10, + height: 10, + backgroundColor: color, + ...MOOD_SHAPES[level], + }} + /> + <span>{label}</span> + </div> + ))} + </div> + } + tooltipClassName="bg-neutral-900 text-neutral-50 ring-neutral-700/50" + tooltipContent={(cell) => ( + <> + {cell.count === 0 ? ( + "No entry" + ) : ( + <> + Feeling <strong>{MOOD_LABELS[cell.count]}</strong> + </> + )} + {" — "} + {formatDate(cell.date)} + </> + )} + /> + ); +} + +const CODE_CONTRIBUTIONS = `const COLORS = ["#161b22", "#0e4429", "#006d32", "#26a641", "#39d353"]; + +<HeatGraph.Root data={data} weekStart="sunday" colorScale={COLORS}> + <HeatGraph.Grid className="gap-[3px]"> + {() => ( + <HeatGraph.Cell className="aspect-square rounded-[2px]" /> + )} + </HeatGraph.Grid> +</HeatGraph.Root>`; + +const CODE_STEPS = `const COLORS = ["#e0e7ff", "#c6d7f9", "#8fb0f3", "#5888e8", "#2563eb"]; + +<HeatGraph.Root data={data} weekStart="monday" colorScale={COLORS}> + <HeatGraph.Grid className="gap-[4px]"> + {() => ( + <HeatGraph.Cell className="aspect-square rounded-full" /> + )} + </HeatGraph.Grid> +</HeatGraph.Root>`; + +const CODE_GYM = `const COLORS = ["#292017", "#f59e0b"]; + +<HeatGraph.Root + data={data} + classify={HeatGraph.autoLevels(2)} + colorScale={COLORS} +> + <HeatGraph.Grid className="gap-[3px]"> + {() => ( + <HeatGraph.Cell className="aspect-square rotate-45 scale-[0.72]" /> + )} + </HeatGraph.Grid> +</HeatGraph.Root>`; + +const CODE_MOOD = `const COLORS = ["#121212", "#b91c1c", "#1e1e1e", "#16a34a"]; + +<HeatGraph.Root + data={data} + weekStart="monday" + classify={() => (count) => Math.max(0, count - 1)} + colorScale={COLORS} +> + <HeatGraph.Grid className="gap-[3px]"> + {() => ( + <HeatGraph.Cell className="aspect-square rounded-[2px]" /> + )} + </HeatGraph.Grid> +</HeatGraph.Root>`; + +const CODE_SNIPPETS: Record<ThemeName, string> = { + Contributions: CODE_CONTRIBUTIONS, + "Steps Walked": CODE_STEPS, + "Gym Tracker": CODE_GYM, + "Mood Tracker": CODE_MOOD, +}; + +export function HeatGraphDemo() { + const contributions = useMemo(() => generateContributions(), []); + const steps = useMemo(() => generateSteps(), []); + const gym = useMemo(() => generateGym(), []); + const mood = useMemo(() => generateMood(), []); + const [activeTheme, setActiveTheme] = useState<ThemeName>("Contributions"); + + return ( + <div className="flex flex-col gap-4"> + <div className="mx-auto flex w-fit flex-col gap-4"> + {/* Theme tabs */} + <div className="bg-muted/50 flex gap-1 rounded-lg p-1"> + {THEME_NAMES.map((name) => { + const shapes = SWATCH_STYLES[name]; + const colors = THEME_SWATCHES[name]; + return ( + <button + key={name} + type="button" + onClick={() => setActiveTheme(name)} + className={cn( + "flex flex-1 items-center justify-center gap-2 rounded-md px-3 py-1.5 text-xs transition-colors", + activeTheme === name + ? "bg-background text-foreground font-medium shadow-sm" + : "text-muted-foreground hover:text-foreground", + )} + > + <span className="flex gap-0.5"> + {colors.map((c, i) => { + const shape = shapes[i % shapes.length]!; + return ( + <span + key={c} + className={shape.className} + style={{ backgroundColor: c, ...shape.style }} + /> + ); + })} + </span> + {name} + </button> + ); + })} + </div> + + {/* Graph — only render active tab */} + <div className="overflow-x-auto"> + {activeTheme === "Contributions" && ( + <ContributionsGraph data={contributions} /> + )} + {activeTheme === "Steps Walked" && <StepsGraph data={steps} />} + {activeTheme === "Gym Tracker" && <GymGraph data={gym} />} + {activeTheme === "Mood Tracker" && <MoodGraph data={mood} />} + </div> + + {/* Code */} + <div className="border-border/50 overflow-hidden rounded-xl border"> + <SyntaxHighlighter + language="tsx" + code={CODE_SNIPPETS[activeTheme]} + addDefaultStyles={false} + className="[&_pre]:bg-muted/30! [&_pre]:m-0 [&_pre]:rounded-none [&_pre]:p-4" + /> + </div> + </div> + </div> + ); +} diff --git a/apps/docs/app/(demos)/heat-graph/layout.tsx b/apps/docs/app/(demos)/heat-graph/layout.tsx new file mode 100644 index 0000000..6dd727a --- /dev/null +++ b/apps/docs/app/(demos)/heat-graph/layout.tsx @@ -0,0 +1,29 @@ +import type { Metadata } from "next"; +import type { ReactNode } from "react"; +import { SubProjectLayout } from "@/components/shared/sub-project-layout"; +import { createOgMetadata } from "@/lib/og"; + +const title = "Heat Graph"; +const description = + "Headless, composable activity heatmap components for React. Radix-style primitives you fully control."; + +export const metadata: Metadata = { + title, + description, + ...createOgMetadata(title, description), +}; + +export default function HeatGraphLayout({ + children, +}: { + children: ReactNode; +}): React.ReactElement { + return ( + <SubProjectLayout + name="heat-graph" + githubPath="https://github.com/assistant-ui/assistant-ui/tree/main/packages/heat-graph" + > + {children} + </SubProjectLayout> + ); +} diff --git a/apps/docs/app/(demos)/heat-graph/page.tsx b/apps/docs/app/(demos)/heat-graph/page.tsx new file mode 100644 index 0000000..4641f57 --- /dev/null +++ b/apps/docs/app/(demos)/heat-graph/page.tsx @@ -0,0 +1,148 @@ +"use client"; + +import { + ArrowRight, + Blocks, + Globe, + Paintbrush, + Puzzle, + Scaling, +} from "lucide-react"; +import Link from "next/link"; +import { cn } from "@/lib/utils"; +import { CopyCommandButton } from "@/components/home/copy-command-button"; +import { Button, buttonVariants } from "@/components/ui/radix/button"; +import { HeatGraphDemo } from "./heat-graph-demo"; + +const ANALYTICS_PAGE = "heat-graph" as const; + +const INSTALL_COMMAND = "npm install heat-graph"; + +const FEATURES = [ + { + title: "Radix-Style Composable", + description: + "Compound components you fully control. Root, Grid, Cell, Legend, Tooltip — compose only the pieces you need.", + icon: Puzzle, + iconColor: "text-blue-400", + }, + { + title: "Fully Headless", + description: + "Zero styling opinions. Bring your own CSS, Tailwind, or any styling solution. Every element is a plain div you can style.", + icon: Paintbrush, + iconColor: "text-pink-400", + }, + { + title: "Tooltip Built-in", + description: + "Hover tooltips powered by Radix Popper for pixel-perfect positioning. No extra dependencies needed.", + icon: Blocks, + iconColor: "text-purple-400", + }, + { + title: "Custom Bucketing", + description: + "Plug in your own classification function to control how counts map to levels. Defaults to evenly-distributed auto-levels.", + icon: Scaling, + iconColor: "text-green-400", + }, + { + title: "Localizable", + description: + "Month and day labels expose raw numeric values. Format with the included English helpers or use Intl.DateTimeFormat for any locale.", + icon: Globe, + iconColor: "text-orange-400", + }, +] as const; + +export default function HeatGraphPage() { + return ( + <div className="mx-auto w-full max-w-7xl space-y-10 px-4 pt-14 pb-8 md:space-y-20"> + {/* Hero */} + <div className="flex flex-col items-center gap-6 text-center"> + <div className="flex flex-col gap-3"> + <h1 className="mx-auto max-w-2xl text-3xl font-medium tracking-tight md:text-5xl"> + GitHub-style activity heatmaps for React + </h1> + <p className="text-muted-foreground mx-auto max-w-xl text-lg"> + Headless, Radix-style primitives for building activity heatmap + graphs. Composable, unstyled, and fully customizable. + </p> + </div> + + <CopyCommandButton + command={INSTALL_COMMAND} + analyticsContext={{ page: ANALYTICS_PAGE, section: "hero" }} + /> + + <div className="text-muted-foreground flex flex-wrap items-center justify-center gap-x-5 gap-y-3 text-[13px]"> + <Link + href="/docs/utilities/heat-graph" + className="text-foreground/60 hover:text-foreground font-medium transition-colors" + > + Documentation → + </Link> + </div> + </div> + + {/* Live demo */} + <div className="mx-auto max-w-4xl"> + <HeatGraphDemo /> + </div> + + {/* Features */} + <div className="flex flex-col gap-8"> + <div className="flex flex-col items-center gap-2 text-center"> + <h2 className="text-3xl font-medium tracking-tight"> + Why Heat Graph? + </h2> + <p className="text-muted-foreground max-w-xl"> + Everything you need to build activity heatmaps — without fighting + your styling framework or losing control. + </p> + </div> + + <div className="grid gap-4 sm:grid-cols-2 lg:grid-cols-3"> + {FEATURES.map((feature) => { + const Icon = feature.icon; + return ( + <div + key={feature.title} + className="border-border/50 bg-muted/30 hover:border-border/80 flex flex-col gap-2 rounded-xl border p-4 transition-colors" + > + <span className="flex items-center gap-2 font-medium"> + <Icon className={cn("size-4", feature.iconColor)} /> + {feature.title} + </span> + <p className="text-muted-foreground text-sm leading-relaxed"> + {feature.description} + </p> + </div> + ); + })} + </div> + </div> + + {/* CTA */} + <div className="flex flex-col items-center gap-6 py-16 text-center"> + <p className="text-2xl font-medium tracking-tight"> + Start building today + </p> + <div className="flex flex-wrap items-center justify-center gap-3"> + <Button asChild> + <Link href="/docs/utilities/heat-graph"> + Documentation <ArrowRight /> + </Link> + </Button> + <Link + href="https://github.com/assistant-ui/assistant-ui/tree/main/packages/heat-graph" + className={buttonVariants({ variant: "outline" })} + > + View on GitHub + </Link> + </div> + </div> + </div> + ); +} diff --git a/apps/docs/app/(demos)/playground/init/route.ts b/apps/docs/app/(demos)/playground/init/route.ts new file mode 100644 index 0000000..6987587 --- /dev/null +++ b/apps/docs/app/(demos)/playground/init/route.ts @@ -0,0 +1,43 @@ +import { type NextRequest, NextResponse } from "next/server"; +import { getPresetById } from "@/components/builder/presets"; +import { DEFAULT_CONFIG, type BuilderConfig } from "@/components/builder/types"; +import { decodeConfig } from "@/lib/playground-url-state"; +import { generateRegistryJson } from "@/lib/playground-registry"; + +export function GET(request: NextRequest) { + const searchParams = request.nextUrl.searchParams; + const presetId = searchParams.get("preset"); + const configEncoded = searchParams.get("c"); + + let config: BuilderConfig; + + if (presetId) { + const preset = getPresetById(presetId); + if (!preset) { + return NextResponse.json( + { error: `Preset "${presetId}" not found` }, + { status: 404 }, + ); + } + config = preset.config; + } else if (configEncoded) { + try { + config = decodeConfig(configEncoded); + } catch { + return NextResponse.json( + { error: "Invalid configuration encoding" }, + { status: 400 }, + ); + } + } else { + config = DEFAULT_CONFIG; + } + + const registryJson = generateRegistryJson(config); + + return NextResponse.json(registryJson, { + headers: { + "Cache-Control": "public, max-age=3600, s-maxage=3600", + }, + }); +} diff --git a/apps/docs/app/(demos)/playground/layout.tsx b/apps/docs/app/(demos)/playground/layout.tsx new file mode 100644 index 0000000..294d74d --- /dev/null +++ b/apps/docs/app/(demos)/playground/layout.tsx @@ -0,0 +1,31 @@ +import type { Metadata } from "next"; +import { type ReactNode, Suspense } from "react"; +import { SubProjectLayout } from "@/components/shared/sub-project-layout"; +import { createOgMetadata } from "@/lib/og"; + +const title = "Playground"; +const description = + "Experiment with different configurations and settings using the Assistant UI Playground."; + +export const metadata: Metadata = { + title, + description, + ...createOgMetadata(title, description), +}; + +export default function PlaygroundLayout({ + children, +}: { + children: ReactNode; +}): React.ReactElement { + return ( + <SubProjectLayout + name="playground" + githubPath="https://github.com/assistant-ui/assistant-ui/tree/main/apps/docs/app/playground" + fullHeight + hideFooter + > + <Suspense>{children}</Suspense> + </SubProjectLayout> + ); +} diff --git a/apps/docs/app/(demos)/playground/page.tsx b/apps/docs/app/(demos)/playground/page.tsx new file mode 100644 index 0000000..21f13f6 --- /dev/null +++ b/apps/docs/app/(demos)/playground/page.tsx @@ -0,0 +1,461 @@ +"use client"; + +import { + useCallback, + useEffect, + useRef, + useState, + type ReactNode, +} from "react"; +import { createPortal } from "react-dom"; +import dynamic from "next/dynamic"; +import { + CodeIcon, + XIcon, + Monitor, + Tablet, + Smartphone, + Plus, + SlidersHorizontal, + Sparkles, + SquareTerminal, + Loader2, +} from "lucide-react"; +import { ThreadListPrimitive } from "@assistant-ui/react"; +import { BuilderControls } from "@/components/builder/builder-controls"; +import { BuilderPreview } from "@/components/builder/builder-preview"; +import { BuilderCodeOutput } from "@/components/builder/builder-code-output"; +import { ShareButton } from "@/components/builder/share-button"; +import { CreateDialog } from "@/components/builder/create-dialog"; +import { + PlaygroundChatProvider, + PlaygroundChatThread, +} from "@/components/builder/builder-chat-sidebar"; +import { + Sheet, + SheetContent, + SheetHeader, + SheetTitle, + SheetTrigger, +} from "@/components/ui/radix/sheet"; +import { cn } from "@/lib/utils"; +import { + usePlaygroundState, + type ViewportPreset, +} from "@/lib/playground-url-state"; +import { isAiPlaygroundEnabled } from "@/lib/feature-flags"; +import { PlaygroundRuntimeProvider } from "@/contexts/PlaygroundRuntimeProvider"; + +const XuluxApp = isAiPlaygroundEnabled + ? dynamic(() => + import("@/components/xulux/XuluxApp").then((mod) => mod.XuluxApp), + ) + : null; + +const VIEWPORT_PRESETS = { + desktop: { width: "100%" as const, label: "Desktop", icon: Monitor }, + tablet: { width: 768, label: "Tablet", icon: Tablet }, + mobile: { width: 375, label: "Mobile", icon: Smartphone }, +} as const; + +function BuilderPlayground() { + const { + config, + showCode, + viewportPreset, + viewportWidth, + setConfig, + setShowCode, + setViewportPreset, + setViewportWidth, + } = usePlaygroundState(); + + const [controlsOpen, setControlsOpen] = useState(false); + const [showChat, setShowChat] = useState(false); + const [aiRunning, setAiRunning] = useState(false); + const [mobileSheetOpen, setMobileSheetOpen] = useState(false); + const isResizing = useRef(false); + const containerRef = useRef<HTMLDivElement>(null); + const previewContainerRef = useRef<HTMLDivElement>(null); + const cleanupRef = useRef<(() => void) | null>(null); + + useEffect(() => { + return () => { + cleanupRef.current?.(); + }; + }, []); + + const handlePresetChange = useCallback( + (preset: ViewportPreset) => { + setViewportPreset(preset); + }, + [setViewportPreset], + ); + + const handleResizeStart = useCallback( + (e: React.MouseEvent, side: "left" | "right") => { + e.preventDefault(); + isResizing.current = true; + document.body.style.cursor = "ew-resize"; + document.body.style.userSelect = "none"; + + const startX = e.clientX; + const startWidth = + viewportWidth === "100%" + ? (containerRef.current?.offsetWidth ?? 800) + : viewportWidth; + + const handleMouseMove = (e: MouseEvent) => { + if (!isResizing.current) return; + const delta = + side === "right" ? e.clientX - startX : startX - e.clientX; + const newWidth = Math.max(320, startWidth + delta * 2); + setViewportWidth(newWidth); + }; + + const handleMouseUp = () => { + isResizing.current = false; + document.body.style.cursor = ""; + document.body.style.userSelect = ""; + document.removeEventListener("mousemove", handleMouseMove); + document.removeEventListener("mouseup", handleMouseUp); + cleanupRef.current = null; + }; + + cleanupRef.current = () => { + document.body.style.cursor = ""; + document.body.style.userSelect = ""; + document.removeEventListener("mousemove", handleMouseMove); + document.removeEventListener("mouseup", handleMouseUp); + }; + + document.addEventListener("mousemove", handleMouseMove); + document.addEventListener("mouseup", handleMouseUp); + }, + [viewportWidth, setViewportWidth], + ); + + const content = ( + <div className="bg-background flex h-full w-full gap-4 overflow-hidden p-2 md:p-4"> + <div className="hidden w-72 shrink-0 overflow-hidden md:block lg:w-80"> + <BuilderControls config={config} onChange={setConfig} /> + </div> + + <div + ref={containerRef} + className="bg-muted/30 relative flex min-h-0 flex-1 flex-col overflow-hidden rounded-xl border" + > + <div className="bg-background/50 flex shrink-0 items-center justify-between border-b px-2 py-2 md:px-3"> + <div className="hidden items-center gap-1 md:flex"> + {(Object.keys(VIEWPORT_PRESETS) as ViewportPreset[]).map((key) => { + const preset = VIEWPORT_PRESETS[key]; + const Icon = preset.icon; + return ( + <button + type="button" + key={key} + onClick={() => handlePresetChange(key)} + className={cn( + "flex items-center gap-1.5 rounded-md px-2 py-1 text-xs transition-colors", + viewportPreset === key + ? "bg-foreground/10 text-foreground" + : "text-muted-foreground hover:text-foreground", + )} + > + <Icon className="size-3.5" /> + {preset.label} + </button> + ); + })} + <code className="bg-muted text-muted-foreground ml-1.5 rounded-sm px-1.5 py-0.5 font-mono text-[11px] ring-1 ring-black/5 ring-inset dark:ring-white/10"> + {viewportWidth === "100%" ? "100%" : `${viewportWidth}px`} + </code> + </div> + + <Sheet open={controlsOpen} onOpenChange={setControlsOpen}> + <SheetTrigger asChild> + <button + type="button" + className="text-muted-foreground hover:bg-foreground/5 hover:text-foreground flex items-center gap-1.5 rounded-md px-2.5 py-1.5 text-xs font-medium transition-colors md:hidden" + aria-label="Open controls" + > + <SlidersHorizontal className="size-4" /> + <span>Customize</span> + </button> + </SheetTrigger> + <SheetContent + side="bottom" + className="h-[85vh] overflow-hidden rounded-t-2xl" + > + <SheetHeader> + <SheetTitle>Customize</SheetTitle> + </SheetHeader> + <div className="h-[calc(100%-3rem)] overflow-y-auto px-4 pb-8"> + <BuilderControls config={config} onChange={setConfig} /> + </div> + </SheetContent> + </Sheet> + + <div className="flex items-center gap-1"> + <ThreadListPrimitive.New + className="text-muted-foreground hover:text-foreground flex items-center gap-1.5 rounded-md px-2.5 py-1 text-xs font-medium transition-colors" + aria-label="New thread" + > + <Plus className="size-3.5" /> + <span className="hidden sm:inline">New Thread</span> + </ThreadListPrimitive.New> + + <ShareButton /> + + {isAiPlaygroundEnabled && ( + <button + type="button" + onClick={() => setShowChat(!showChat)} + className={cn( + "hidden items-center gap-1.5 rounded-md px-2.5 py-1 text-xs font-medium transition-colors md:flex", + showChat + ? "bg-foreground/10 text-foreground" + : "text-muted-foreground hover:text-foreground", + )} + > + <Sparkles className="size-3.5" /> + <span className="hidden sm:inline">AI</span> + </button> + )} + + {isAiPlaygroundEnabled && ( + <Sheet open={mobileSheetOpen} onOpenChange={setMobileSheetOpen}> + <SheetTrigger asChild> + <button + type="button" + className="text-muted-foreground hover:text-foreground flex items-center gap-1.5 rounded-md px-2.5 py-1 text-xs font-medium transition-colors md:hidden" + aria-label="Open AI chat" + > + <Sparkles className="size-3.5" /> + </button> + </SheetTrigger> + <SheetContent + side="bottom" + className="flex h-[85vh] flex-col overflow-hidden rounded-t-2xl p-0" + > + <SheetHeader className="sr-only"> + <SheetTitle>AI Assistant</SheetTitle> + </SheetHeader> + <PlaygroundChatThread onRunningChange={setAiRunning} /> + </SheetContent> + </Sheet> + )} + + <button + type="button" + onClick={() => setShowCode(!showCode)} + className={cn( + "flex items-center gap-1.5 rounded-md px-2.5 py-1 text-xs font-medium transition-colors", + showCode + ? "bg-foreground/10 text-foreground" + : "text-muted-foreground hover:text-foreground", + )} + > + {showCode ? ( + <> + <XIcon className="size-3.5" /> + <span className="hidden sm:inline">Close</span> + </> + ) : ( + <> + <CodeIcon className="size-3.5" /> + <span className="hidden sm:inline">Code</span> + </> + )} + </button> + + <CreateDialog + config={config} + container={previewContainerRef} + onOpenCodeView={() => setShowCode(true)} + > + <button + type="button" + className="bg-foreground text-background hover:bg-foreground/90 flex items-center gap-1.5 rounded-md px-2.5 py-1 text-xs font-medium transition-colors" + > + <SquareTerminal className="size-3.5" /> + <span className="hidden sm:inline">Create Project</span> + </button> + </CreateDialog> + </div> + </div> + + <div + ref={previewContainerRef} + className="relative min-h-0 flex-1 overflow-hidden" + > + <div className="flex h-full items-stretch justify-center p-2 md:p-4"> + {viewportWidth !== "100%" && ( + <div + onMouseDown={(e) => handleResizeStart(e, "left")} + className="group hidden w-4 shrink-0 cursor-ew-resize items-center justify-center md:flex" + > + <div className="bg-border group-hover:bg-foreground/30 h-12 w-1 rounded-full transition-colors" /> + </div> + )} + + <div + className="bg-background relative h-full overflow-hidden rounded-lg border shadow-sm max-md:!w-full" + style={{ + width: viewportWidth === "100%" ? "100%" : viewportWidth, + maxWidth: "100%", + }} + > + <BuilderPreview config={config} /> + + {isAiPlaygroundEnabled && + aiRunning && + !showCode && + !mobileSheetOpen && ( + <div className="bg-background/90 absolute inset-0 z-4 flex items-center justify-center backdrop-blur-[2px]"> + <div className="bg-background ring-border flex items-center gap-2 rounded-lg px-4 py-2.5 shadow-lg ring-1"> + <Loader2 className="text-muted-foreground size-4 animate-spin" /> + <span className="text-muted-foreground text-sm font-medium"> + Applying changes + </span> + </div> + </div> + )} + + {showCode && ( + <div className="bg-card absolute inset-0 z-5 overflow-hidden"> + <BuilderCodeOutput config={config} /> + </div> + )} + </div> + + {viewportWidth !== "100%" && ( + <div + onMouseDown={(e) => handleResizeStart(e, "right")} + className="group hidden w-4 shrink-0 cursor-ew-resize items-center justify-center md:flex" + > + <div className="bg-border group-hover:bg-foreground/30 h-12 w-1 rounded-full transition-colors" /> + </div> + )} + </div> + </div> + </div> + + {isAiPlaygroundEnabled && showChat && ( + <div className="bg-background hidden h-full shrink-0 flex-col overflow-hidden rounded-xl border md:flex md:w-80"> + <div className="flex items-center justify-between border-b px-3 py-2"> + <span className="text-sm font-medium">AI Assistant</span> + <button + type="button" + onClick={() => setShowChat(false)} + className="text-muted-foreground hover:text-foreground rounded-md p-1 transition-colors" + aria-label="Close chat" + > + <XIcon className="size-4" /> + </button> + </div> + <PlaygroundChatThread onRunningChange={setAiRunning} /> + </div> + )} + </div> + ); + + return ( + <PlaygroundRuntimeProvider> + <PlaygroundChatProvider config={config} setConfig={setConfig}> + {content} + </PlaygroundChatProvider> + </PlaygroundRuntimeProvider> + ); +} + +function HeaderPortal({ children }: { children: ReactNode }) { + const [container, setContainer] = useState<HTMLElement | null>(null); + + useEffect(() => { + setContainer( + document.querySelector<HTMLElement>("[data-sub-project-header-portal]"), + ); + }, []); + + if (!container) return null; + return createPortal(children, container); +} + +export default function PlaygroundPage() { + const [mode, setMode] = useState<"agent" | "builder">( + isAiPlaygroundEnabled ? "agent" : "builder", + ); + const [visitedModes, setVisitedModes] = useState({ + agent: isAiPlaygroundEnabled, + builder: !isAiPlaygroundEnabled, + }); + + const handleModeChange = useCallback((nextMode: "agent" | "builder") => { + setMode(nextMode); + setVisitedModes((current) => ({ + ...current, + [nextMode]: true, + })); + }, []); + + return ( + <> + {isAiPlaygroundEnabled && ( + <HeaderPortal> + <div className="bg-muted/40 grid grid-cols-2 rounded-md border p-0.5 text-xs"> + <button + type="button" + onClick={() => handleModeChange("agent")} + className={cn( + "rounded px-2.5 py-1 font-medium transition-colors", + mode === "agent" + ? "bg-background text-foreground shadow-sm" + : "text-muted-foreground hover:text-foreground", + )} + > + AI Builder + </button> + <button + type="button" + onClick={() => handleModeChange("builder")} + className={cn( + "rounded px-2.5 py-1 font-medium transition-colors", + mode === "builder" + ? "bg-background text-foreground shadow-sm" + : "text-muted-foreground hover:text-foreground", + )} + > + UI Builder + </button> + </div> + </HeaderPortal> + )} + + <div className="flex h-full min-h-0 w-full flex-col overflow-hidden"> + {XuluxApp && visitedModes.agent && ( + <div + className={cn( + "h-full min-h-0 w-full flex-col overflow-hidden", + mode === "agent" ? "flex" : "hidden", + )} + aria-hidden={mode !== "agent"} + > + <XuluxApp /> + </div> + )} + {visitedModes.builder && ( + <div + className={cn( + "h-full min-h-0 w-full flex-col overflow-hidden", + mode === "builder" ? "flex" : "hidden", + )} + aria-hidden={mode !== "builder"} + > + <BuilderPlayground /> + </div> + )} + </div> + </> + ); +} diff --git a/apps/docs/app/(demos)/react-o11y/layout.tsx b/apps/docs/app/(demos)/react-o11y/layout.tsx new file mode 100644 index 0000000..78dea8b --- /dev/null +++ b/apps/docs/app/(demos)/react-o11y/layout.tsx @@ -0,0 +1,29 @@ +import type { Metadata } from "next"; +import type { ReactNode } from "react"; +import { SubProjectLayout } from "@/components/shared/sub-project-layout"; +import { createOgMetadata } from "@/lib/og"; + +const title = "react-o11y"; +const description = + "Headless, composable observability span primitives for React. Render agent traces, sub-agent trees, and run timelines as collapsible waterfalls you fully control."; + +export const metadata: Metadata = { + title, + description, + ...createOgMetadata(title, description), +}; + +export default function ReactO11yLayout({ + children, +}: { + children: ReactNode; +}): React.ReactElement { + return ( + <SubProjectLayout + name="react-o11y" + githubPath="https://github.com/assistant-ui/assistant-ui/tree/main/packages/react-o11y" + > + {children} + </SubProjectLayout> + ); +} diff --git a/apps/docs/app/(demos)/react-o11y/page.tsx b/apps/docs/app/(demos)/react-o11y/page.tsx new file mode 100644 index 0000000..1fd2f73 --- /dev/null +++ b/apps/docs/app/(demos)/react-o11y/page.tsx @@ -0,0 +1,224 @@ +"use client"; + +import { + ArrowRight, + GitBranch, + Paintbrush, + Puzzle, + Radio, + Tags, + Workflow, +} from "lucide-react"; +import Link from "next/link"; +import { cn } from "@/lib/utils"; +import { CopyCommandButton } from "@/components/home/copy-command-button"; +import { Button, buttonVariants } from "@/components/ui/radix/button"; +import { WaterfallSample } from "@/components/docs/samples/o11y/waterfall"; +import { + CollapseSample, + StatusSample, + StreamingSample, +} from "@/components/docs/samples/o11y/capability-samples"; + +const ANALYTICS_PAGE = "react-o11y" as const; + +const INSTALL_COMMAND = "npm install @assistant-ui/react-o11y"; + +const FEATURES = [ + { + title: "Fully Headless", + description: + "Zero styling opinions. Every part is a plain element exposing data attributes; bring your own CSS or Tailwind.", + icon: Paintbrush, + iconColor: "text-pink-400", + }, + { + title: "Radix-Style Composable", + description: + "Compound primitives you fully control. Root, Indent, CollapseToggle, StatusIndicator, TypeBadge, Name, Children.", + icon: Puzzle, + iconColor: "text-blue-400", + }, + { + title: "Tree-Aware", + description: + "Depth, child counts, collapse state, and the global time range are computed for you from a flat span array.", + icon: GitBranch, + iconColor: "text-green-400", + }, + { + title: "Reactive", + description: + "Built on the assistant-ui store. Push new spans and the UI updates live; running spans animate as they stream.", + icon: Radio, + iconColor: "text-orange-400", + }, + { + title: "Style by Status & Type", + description: + "data-span-status and data-span-type drive your colors and badges with pure CSS, no conditional render logic.", + icon: Tags, + iconColor: "text-purple-400", + }, + { + title: "Any Data Source", + description: + "Map spans from OpenTelemetry, Langfuse, LangSmith, or your own backend into SpanData and render in minutes.", + icon: Workflow, + iconColor: "text-cyan-400", + }, +] as const; + +const CAPABILITIES = [ + { + title: "Status at a glance", + description: + "running, completed, failed, and skipped each styled from data-span-status.", + demo: StatusSample, + }, + { + title: "Collapsible subtrees", + description: + "CollapseToggle removes a span's descendants from the visible list, not just hides them.", + demo: CollapseSample, + }, + { + title: "Streams in live", + description: + "Push spans over time and the tree grows; running spans animate until they settle.", + demo: StreamingSample, + }, +] as const; + +export default function ReactO11yPage() { + return ( + <div className="mx-auto w-full max-w-7xl space-y-10 px-4 pt-14 pb-8 md:space-y-20"> + {/* Hero */} + <div className="flex flex-col items-center gap-6 text-center"> + <div className="flex flex-col gap-3"> + <h1 className="mx-auto max-w-2xl text-3xl font-medium tracking-tight md:text-5xl"> + Observability span primitives for React + </h1> + <p className="text-muted-foreground mx-auto max-w-xl text-lg"> + Headless, Radix-style primitives for rendering agent traces, + sub-agent trees, and run timelines as collapsible waterfalls. + Composable, unstyled, and fully reactive. + </p> + </div> + + <CopyCommandButton + command={INSTALL_COMMAND} + analyticsContext={{ page: ANALYTICS_PAGE, section: "hero" }} + /> + + <div className="text-muted-foreground flex flex-wrap items-center justify-center gap-x-5 gap-y-3 text-[13px]"> + <Link + href="/docs/utilities/react-o11y" + className="text-foreground/60 hover:text-foreground font-medium transition-colors" + > + Documentation → + </Link> + <span className="text-border">·</span> + <span>Experimental, API may change</span> + </div> + </div> + + {/* Flagship live demo */} + <div className="mx-auto max-w-4xl"> + <WaterfallSample /> + <p className="text-muted-foreground mt-3 text-center text-xs"> + A live span waterfall. Collapse subtrees, and hold ⌘/Ctrl while + scrolling to zoom the timeline. + </p> + </div> + + {/* Capabilities */} + <div className="flex flex-col gap-8"> + <div className="flex flex-col items-center gap-2 text-center"> + <h2 className="text-3xl font-medium tracking-tight"> + Compose any trace UI + </h2> + <p className="text-muted-foreground max-w-xl"> + The same primitives drive a Gantt-style waterfall or a plain tree. + You decide the layout; the resource handles the data. + </p> + </div> + + <div className="grid gap-4 lg:grid-cols-3"> + {CAPABILITIES.map((capability) => { + const Demo = capability.demo; + return ( + <div + key={capability.title} + className="border-border/50 bg-muted/30 flex flex-col gap-4 rounded-xl border p-4" + > + <div className="flex flex-col gap-1"> + <span className="font-medium">{capability.title}</span> + <p className="text-muted-foreground text-sm leading-relaxed"> + {capability.description} + </p> + </div> + <div className="flex flex-1 items-start justify-center"> + <Demo /> + </div> + </div> + ); + })} + </div> + </div> + + {/* Features */} + <div className="flex flex-col gap-8"> + <div className="flex flex-col items-center gap-2 text-center"> + <h2 className="text-3xl font-medium tracking-tight"> + Why react-o11y? + </h2> + <p className="text-muted-foreground max-w-xl"> + Everything you need to build trace inspectors and run timelines, + without fighting your styling framework or losing control. + </p> + </div> + + <div className="grid gap-4 sm:grid-cols-2 lg:grid-cols-3"> + {FEATURES.map((feature) => { + const Icon = feature.icon; + return ( + <div + key={feature.title} + className="border-border/50 bg-muted/30 hover:border-border/80 flex flex-col gap-2 rounded-xl border p-4 transition-colors" + > + <span className="flex items-center gap-2 font-medium"> + <Icon className={cn("size-4", feature.iconColor)} /> + {feature.title} + </span> + <p className="text-muted-foreground text-sm leading-relaxed"> + {feature.description} + </p> + </div> + ); + })} + </div> + </div> + + {/* CTA */} + <div className="flex flex-col items-center gap-6 py-16 text-center"> + <p className="text-2xl font-medium tracking-tight"> + Start building today + </p> + <div className="flex flex-wrap items-center justify-center gap-3"> + <Button asChild> + <Link href="/docs/utilities/react-o11y"> + Documentation <ArrowRight /> + </Link> + </Button> + <Link + href="https://github.com/assistant-ui/assistant-ui/tree/main/packages/react-o11y" + className={buttonVariants({ variant: "outline" })} + > + View on GitHub + </Link> + </div> + </div> + </div> + ); +} diff --git a/apps/docs/app/(demos)/safe-content-frame/layout.tsx b/apps/docs/app/(demos)/safe-content-frame/layout.tsx new file mode 100644 index 0000000..5db4438 --- /dev/null +++ b/apps/docs/app/(demos)/safe-content-frame/layout.tsx @@ -0,0 +1,29 @@ +import type { Metadata } from "next"; +import type { ReactNode } from "react"; +import { SubProjectLayout } from "@/components/shared/sub-project-layout"; +import { createOgMetadata } from "@/lib/og"; + +const title = "Safe Content Frame"; +const description = + "Render untrusted HTML content securely in sandboxed iframes with unique origins per render."; + +export const metadata: Metadata = { + title, + description, + ...createOgMetadata(title, description), +}; + +export default function SafeContentFrameLayout({ + children, +}: { + children: ReactNode; +}): React.ReactElement { + return ( + <SubProjectLayout + name="safe-content-frame" + githubPath="https://github.com/assistant-ui/assistant-ui/tree/main/packages/safe-content-frame" + > + {children} + </SubProjectLayout> + ); +} diff --git a/apps/docs/app/(demos)/safe-content-frame/opengraph-image.tsx b/apps/docs/app/(demos)/safe-content-frame/opengraph-image.tsx new file mode 100644 index 0000000..045129a --- /dev/null +++ b/apps/docs/app/(demos)/safe-content-frame/opengraph-image.tsx @@ -0,0 +1,76 @@ +import { ImageResponse } from "next/og"; +import { readFile } from "node:fs/promises"; +import { join } from "node:path"; +import { OG_SIZE, OgTemplate } from "@/lib/og-template"; + +export const alt = "Safe Content Frame"; +export const size = OG_SIZE; +export const contentType = "image/png"; + +export default async function Image() { + const [geistSemiBold, geistRegular, geistMedium, geistMono] = + await Promise.all([ + readFile(join(process.cwd(), "assets/Geist-SemiBold.ttf")), + readFile(join(process.cwd(), "assets/Geist-Regular.ttf")), + readFile(join(process.cwd(), "assets/Geist-Medium.ttf")), + readFile(join(process.cwd(), "assets/GeistMono-Regular.ttf")), + ]); + + return new ImageResponse( + <OgTemplate subtleBranding> + <span + style={{ + fontSize: 90, + fontWeight: 600, + color: "#ffffff", + textAlign: "center", + fontFamily: "Geist", + letterSpacing: "-0.02em", + }} + > + Safe Content Frame + </span> + <span + style={{ + fontSize: 38, + fontWeight: 400, + color: "#a3a3a3", + fontFamily: "Geist", + letterSpacing: "-0.01em", + textAlign: "left", + }} + > + Render untrusted HTML securely in sandboxed iframes + </span> + </OgTemplate>, + { + ...size, + fonts: [ + { + name: "Geist", + data: geistSemiBold, + style: "normal", + weight: 600, + }, + { + name: "Geist", + data: geistRegular, + style: "normal", + weight: 400, + }, + { + name: "Geist", + data: geistMedium, + style: "normal", + weight: 500, + }, + { + name: "GeistMono", + data: geistMono, + style: "normal", + weight: 400, + }, + ], + }, + ); +} diff --git a/apps/docs/app/(demos)/safe-content-frame/page.tsx b/apps/docs/app/(demos)/safe-content-frame/page.tsx new file mode 100644 index 0000000..2dd4ff6 --- /dev/null +++ b/apps/docs/app/(demos)/safe-content-frame/page.tsx @@ -0,0 +1,243 @@ +"use client"; + +import { useState, useRef } from "react"; +import { Play, Trash2, Shield, Code, AlertTriangle } from "lucide-react"; +import { Button } from "@/components/ui/button"; +import { SafeContentFrame, type RenderedFrame } from "safe-content-frame"; +import { cn } from "@/lib/utils"; + +const DEFAULT_HTML = `<h1>Hello from Safe Content Frame!</h1> +<p>This content is rendered in a <strong>sandboxed iframe</strong>.</p> +<style> + body { + font-family: system-ui; + padding: 20px; + background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); + color: white; + min-height: 100vh; + margin: 0; + } + h1 { margin-top: 0; } +</style> +<script> + console.log('Script executed in sandbox!'); + document.body.innerHTML += '<p>JavaScript is working!</p>'; +</script>`; + +const XSS_EXAMPLE = `<h1>XSS Attack Demo</h1> +<p>This demonstrates that malicious scripts are sandboxed:</p> +<script> + // These attacks are contained within the sandbox + try { + // Try to access parent window + window.parent.document.body.innerHTML = 'HACKED!'; + } catch (e) { + document.body.innerHTML += '<p style="color: #4ade80;">Cross-origin blocked: ' + e.message + '</p>'; + } + + try { + // Try to access cookies + document.body.innerHTML += '<p style="color: #4ade80;">Cookies: ' + (document.cookie || 'none (sandboxed)') + '</p>'; + } catch (e) { + document.body.innerHTML += '<p style="color: #4ade80;">Cookie access blocked</p>'; + } + + try { + // Try to redirect + // window.top.location = 'https://evil.com'; + document.body.innerHTML += '<p style="color: #4ade80;">Top navigation blocked by sandbox</p>'; + } catch (e) { + document.body.innerHTML += '<p style="color: #4ade80;">Navigation blocked: ' + e.message + '</p>'; + } +</script> +<style> + body { + font-family: system-ui; + padding: 20px; + background: #1a1a2e; + color: #eee; + } + h1 { color: #f472b6; } +</style>`; + +export default function SafeContentFramePage() { + const [html, setHtml] = useState(DEFAULT_HTML); + const [status, setStatus] = useState<{ + type: "idle" | "loading" | "success" | "error"; + message: string; + }>({ type: "idle", message: "Ready to render" }); + const containerRef = useRef<HTMLDivElement>(null); + const frameRef = useRef<RenderedFrame | null>(null); + + const renderContent = async () => { + if (!containerRef.current) return; + + setStatus({ type: "loading", message: "Rendering..." }); + + // Dispose previous frame + if (frameRef.current) { + frameRef.current.dispose(); + frameRef.current = null; + } + + // Clear container + containerRef.current.innerHTML = ""; + + try { + const scf = new SafeContentFrame("assistant-ui-docs", { + sandbox: ["allow-scripts"], + }); + + const frame = await scf.renderHtml(html, containerRef.current); + frameRef.current = frame; + + setStatus({ + type: "success", + message: `Rendered! Origin: ${frame.origin}`, + }); + + // Try to wait for full load + try { + await frame.fullyLoadedPromiseWithTimeout(5000); + setStatus((prev) => ({ + ...prev, + message: `${prev.message} | Content fully loaded`, + })); + } catch { + setStatus((prev) => ({ + ...prev, + message: `${prev.message} | Load timeout`, + })); + } + } catch (error) { + setStatus({ + type: "error", + message: `Error: ${error instanceof Error ? error.message : "Unknown error"}`, + }); + } + }; + + const clearFrame = () => { + if (frameRef.current) { + frameRef.current.dispose(); + frameRef.current = null; + } + if (containerRef.current) { + containerRef.current.innerHTML = ""; + } + setStatus({ type: "idle", message: "Ready to render" }); + }; + + return ( + <div className="container mx-auto max-w-screen-xl space-y-12 px-4 py-12"> + <div className="flex flex-col items-center space-y-6 text-center"> + <div className="flex items-center gap-2 rounded-full border px-4 py-1.5 text-sm"> + <Shield className="size-4" /> + <span>Secure iframe rendering</span> + </div> + + <h1 className="text-5xl font-bold tracking-tight lg:text-6xl"> + Safe Content Frame + </h1> + + <p className="text-muted-foreground max-w-[600px] text-lg text-balance"> + Render untrusted HTML content securely in sandboxed iframes with + unique origins per render. + </p> + </div> + + <div className="grid gap-6 lg:grid-cols-2"> + <div className="space-y-4"> + <div className="flex items-center justify-between"> + <h2 className="flex items-center gap-2 text-lg font-semibold"> + <Code className="size-5" /> + HTML Input + </h2> + <div className="flex gap-2"> + <Button + size="sm" + variant="outline" + onClick={() => setHtml(DEFAULT_HTML)} + > + Default + </Button> + <Button + size="sm" + variant="outline" + onClick={() => setHtml(XSS_EXAMPLE)} + > + <AlertTriangle className="mr-1 size-3" /> + XSS Demo + </Button> + </div> + </div> + + <textarea + value={html} + onChange={(e) => setHtml(e.target.value)} + className="bg-muted/50 focus:ring-primary h-[400px] w-full rounded-lg border p-4 font-mono text-sm focus:ring-2 focus:outline-none" + spellCheck={false} + /> + + <div className="flex gap-2"> + <Button onClick={renderContent} className="flex-1"> + <Play className="mr-2 size-4" /> + Render Content + </Button> + <Button variant="outline" onClick={clearFrame}> + <Trash2 className="mr-2 size-4" /> + Clear + </Button> + </div> + + <div + className={cn("rounded-lg p-3 font-mono text-sm", { + "bg-destructive/10 text-destructive": status.type === "error", + "bg-green-500/10 text-green-600 dark:text-green-400": + status.type === "success", + "bg-yellow-500/10 text-yellow-600 dark:text-yellow-400": + status.type === "loading", + "bg-muted text-muted-foreground": status.type === "idle", + })} + > + {status.message} + </div> + </div> + + <div className="space-y-4"> + <h2 className="flex items-center gap-2 text-lg font-semibold"> + <Shield className="size-5" /> + Sandboxed Output + </h2> + + <div + ref={containerRef} + className="h-[400px] overflow-hidden rounded-lg border bg-white dark:bg-zinc-900" + /> + + <div className="bg-muted/30 rounded-lg border border-dashed p-4 text-sm"> + <h3 className="mb-2 font-semibold">How it works:</h3> + <ul className="text-muted-foreground space-y-1"> + <li> + Each render gets a <strong>unique origin</strong> derived from a + hash + </li> + <li> + Content runs in a <strong>sandboxed iframe</strong> with + allow-scripts + </li> + <li> + <strong>Cross-origin isolation</strong> prevents access to + parent window + </li> + <li> + No cookies, localStorage, or other storage from the parent + domain + </li> + </ul> + </div> + </div> + </div> + </div> + ); +} diff --git a/apps/docs/app/(demos)/tw-glass/(home)/constants.ts b/apps/docs/app/(demos)/tw-glass/(home)/constants.ts new file mode 100644 index 0000000..6de2e54 --- /dev/null +++ b/apps/docs/app/(demos)/tw-glass/(home)/constants.ts @@ -0,0 +1,4 @@ +export const SINE_VIGNETTE = + "radial-gradient(ellipse at center, black 0%, rgba(0,0,0,0.987) 8.5%, rgba(0,0,0,0.951) 16.2%, rgba(0,0,0,0.891) 23.1%, rgba(0,0,0,0.809) 29.6%, rgba(0,0,0,0.707) 35.7%, rgba(0,0,0,0.588) 41.8%, rgba(0,0,0,0.454) 48%, rgba(0,0,0,0.309) 54.5%, rgba(0,0,0,0.191) 61.5%, rgba(0,0,0,0.098) 69.3%, rgba(0,0,0,0.038) 78.1%, transparent 100%)"; + +export const HERO_TEXT = "tw-glass"; diff --git a/apps/docs/app/(demos)/tw-glass/(home)/demo-components.tsx b/apps/docs/app/(demos)/tw-glass/(home)/demo-components.tsx new file mode 100644 index 0000000..9ea43e0 --- /dev/null +++ b/apps/docs/app/(demos)/tw-glass/(home)/demo-components.tsx @@ -0,0 +1,44 @@ +"use client"; + +import { cn } from "@/lib/utils"; +import { unsplash } from "./pattern-picker"; + +export function DemoArea({ + children, + pattern, +}: { + children: React.ReactNode; + pattern: string; +}) { + return ( + <div + className="relative overflow-hidden transition-[background-image] duration-500" + style={{ + backgroundImage: unsplash(pattern), + backgroundAttachment: "fixed", + backgroundSize: "cover", + backgroundPosition: "center", + }} + > + {children} + </div> + ); +} + +export function GlassDemo({ + className, + label, +}: { + className: string; + label?: string; +}) { + return ( + <div className={cn("flex min-h-44 items-center justify-center", className)}> + {label && ( + <code className="text-primary flex items-center justify-center font-mono text-xl text-shadow-sm"> + {label} + </code> + )} + </div> + ); +} diff --git a/apps/docs/app/(demos)/tw-glass/(home)/doc-components.tsx b/apps/docs/app/(demos)/tw-glass/(home)/doc-components.tsx new file mode 100644 index 0000000..cd65889 --- /dev/null +++ b/apps/docs/app/(demos)/tw-glass/(home)/doc-components.tsx @@ -0,0 +1,152 @@ +"use client"; + +import { FileCode } from "lucide-react"; +import { SyntaxHighlighter } from "@/components/assistant-ui/shiki-highlighter"; +import { + transformerMetaHighlight, + transformerMetaWordHighlight, +} from "@shikijs/transformers"; +import { cn } from "@/lib/utils"; + +const HIGHLIGHT_STYLES = ` + .highlighted { + background: rgba(59, 130, 246, 0.15); + display: block; + } + .dark .highlighted { + background: rgba(147, 197, 253, 0.25); + } + .highlighted-word { + background: rgba(59, 130, 246, 0.2); + color: rgb(30, 58, 138); + padding: 0 0.125rem; + border-radius: 0.125rem; + font-style: normal; + font-weight: inherit; + } + .dark .highlighted-word { + background: rgba(147, 197, 253, 0.3); + color: rgb(165, 180, 252); + } + @keyframes glass-text-fade-out { + from { opacity: 1; } + to { opacity: 0; } + } +`; + +export function HighlightStyles() { + return ( + <style jsx global> + {HIGHLIGHT_STYLES} + </style> + ); +} + +interface CodeBlockProps { + language: string; + code: string; + highlight?: string | string[]; + highlightMode?: "line" | "text"; +} + +function buildMetaProps( + code: string, + highlight: string | string[] | undefined, + highlightMode: "line" | "text", +): { meta?: { __raw: string } } { + if (!highlight) return {}; + const highlights = Array.isArray(highlight) ? highlight : [highlight]; + + if (highlightMode === "text") { + return { meta: { __raw: highlights.map((h) => `/${h}/`).join(" ") } }; + } + + const lineNumbers = code + .split("\n") + .flatMap((line, i) => + highlights.some((h) => line.includes(h)) ? [i + 1] : [], + ); + + if (lineNumbers.length === 0) return {}; + return { meta: { __raw: `{${lineNumbers.join(",")}}` } }; +} + +export function CodeBlock({ + language, + code, + highlight, + highlightMode = "line", +}: CodeBlockProps) { + return ( + <SyntaxHighlighter + language={language} + code={code} + {...buildMetaProps(code, highlight, highlightMode)} + addDefaultStyles={false} + className="[--padding-left:1.5rem] [&_code]:block [&_pre]:m-0 [&_pre]:rounded-none [&_pre]:bg-transparent! [&_pre]:px-0 [&_pre]:py-4" + transformers={[ + transformerMetaHighlight(), + transformerMetaWordHighlight(), + ]} + components={{ + Pre: ({ + className, + ...props + }: React.HTMLAttributes<HTMLPreElement>) => ( + <pre className={className} {...props} /> + ), + Code: ({ className, ...props }: React.HTMLAttributes<HTMLElement>) => ( + <code className={className} {...props} /> + ), + }} + /> + ); +} + +export function Box({ + children, + className, +}: { + children: React.ReactNode; + className?: string; +}) { + return ( + <div className={cn("overflow-clip rounded-xl border", className)}> + {children} + </div> + ); +} + +export function BoxTitle({ + title, + description, +}: { + title: string; + description: React.ReactNode; +}) { + return ( + <div className="bg-background/40 space-y-2 p-6"> + <h3 className="font-mono text-lg">{title}</h3> + <p className="text-muted-foreground max-w-[70ch] text-sm text-pretty"> + {description} + </p> + </div> + ); +} + +export function BoxContent({ children }: { children: React.ReactNode }) { + return <div className="p-6">{children}</div>; +} + +export function BoxCodeHeader({ fileName }: { fileName: string }) { + return ( + <div className="flex items-center gap-2 px-6 py-4 font-mono text-sm font-semibold"> + <FileCode className="text-muted-foreground size-4" /> + {fileName} + </div> + ); +} + +export function BoxCode({ children }: { children: React.ReactNode }) { + return <div className="p-2 text-sm">{children}</div>; +} diff --git a/apps/docs/app/(demos)/tw-glass/(home)/glass-text-hero.tsx b/apps/docs/app/(demos)/tw-glass/(home)/glass-text-hero.tsx new file mode 100644 index 0000000..4e97c9c --- /dev/null +++ b/apps/docs/app/(demos)/tw-glass/(home)/glass-text-hero.tsx @@ -0,0 +1,353 @@ +"use client"; + +import { useState, useRef, useEffect } from "react"; +import { useMotionValue, useSpring, useMotionValueEvent } from "motion/react"; +import { useControls, folder, button } from "leva"; +import { HERO_TEXT } from "./constants"; +import { unsplash } from "./pattern-picker"; + +const MOUSE_SPRING = { stiffness: 200, damping: 30, mass: 0.3 }; + +const GLASS_TEXT_DEFAULTS = { + // Blur (height map) + blurStdDev: 1.1, + // Diffuse lighting + diffSurfaceScale: 8, + diffConstant: 1.4, + diffLightX: 0, + diffLightY: -160, + diffLightZ: 30, + // Specular lighting + specSurfaceScale: 9, + specConstant: 3.2, + specExponent: 17, + specLightX: 0, + specLightY: 160, + specLightZ: 50, + // Compositing + diffOpacity: 0.7, + specOpacity: 0.6, + // Background + showBgImage: true, + bgOpacity: 1, +}; + +export function GlassTextHero({ bg }: { bg: string }) { + const filterId = "glass-text-tunable"; + const controlsRef = useRef<Record<string, unknown>>({}); + const diffPointLightRef = useRef<SVGFEPointLightElement>(null); + const specPointLightRef = useRef<SVGFEPointLightElement>(null); + + const mouseX = useMotionValue(0); + const mouseY = useMotionValue(0); + const sx = useSpring(mouseX, MOUSE_SPRING); + const sy = useSpring(mouseY, MOUSE_SPRING); + + useMotionValueEvent(sx, "change", (v) => { + const c = controlsRef.current; + const dx = Number(c.diffLightX ?? 0); + const sx2 = Number(c.specLightX ?? 0); + diffPointLightRef.current?.setAttribute("x", String(v + dx)); + specPointLightRef.current?.setAttribute("x", String(-v + sx2)); + }); + useMotionValueEvent(sy, "change", (v) => { + const c = controlsRef.current; + const dy = Number(c.diffLightY ?? 0); + const sy2 = Number(c.specLightY ?? 0); + diffPointLightRef.current?.setAttribute("y", String(v + dy)); + specPointLightRef.current?.setAttribute("y", String(-v + sy2)); + }); + + useEffect(() => { + const range = 500; + const onMove = (e: MouseEvent) => { + mouseX.set(((e.clientX / window.innerWidth) * 2 - 1) * range); + mouseY.set(((e.clientY / window.innerHeight) * 2 - 1) * range); + }; + window.addEventListener("mousemove", onMove); + return () => window.removeEventListener("mousemove", onMove); + }, [mouseX, mouseY]); + + const controls = useControls({ + "Blur (Height Map)": folder({ + blurStdDev: { + value: GLASS_TEXT_DEFAULTS.blurStdDev, + min: 0, + max: 8, + step: 0.1, + label: "Std Deviation", + }, + }), + "Diffuse Light": folder({ + diffSurfaceScale: { + value: GLASS_TEXT_DEFAULTS.diffSurfaceScale, + min: 0, + max: 30, + step: 0.5, + label: "Surface Scale", + }, + diffConstant: { + value: GLASS_TEXT_DEFAULTS.diffConstant, + min: 0, + max: 3, + step: 0.05, + label: "Intensity", + }, + diffLightX: { + value: GLASS_TEXT_DEFAULTS.diffLightX, + min: -500, + max: 500, + step: 10, + label: "Light X", + }, + diffLightY: { + value: GLASS_TEXT_DEFAULTS.diffLightY, + min: -500, + max: 500, + step: 10, + label: "Light Y", + }, + diffLightZ: { + value: GLASS_TEXT_DEFAULTS.diffLightZ, + min: 0, + max: 800, + step: 10, + label: "Light Z", + }, + diffOpacity: { + value: GLASS_TEXT_DEFAULTS.diffOpacity, + min: 0, + max: 1, + step: 0.05, + label: "Opacity", + }, + }), + "Specular Highlight": folder({ + specSurfaceScale: { + value: GLASS_TEXT_DEFAULTS.specSurfaceScale, + min: 0, + max: 30, + step: 0.5, + label: "Surface Scale", + }, + specConstant: { + value: GLASS_TEXT_DEFAULTS.specConstant, + min: 0, + max: 5, + step: 0.1, + label: "Intensity", + }, + specExponent: { + value: GLASS_TEXT_DEFAULTS.specExponent, + min: 1, + max: 128, + step: 1, + label: "Sharpness", + }, + specLightX: { + value: GLASS_TEXT_DEFAULTS.specLightX, + min: -500, + max: 500, + step: 10, + label: "Light X", + }, + specLightY: { + value: GLASS_TEXT_DEFAULTS.specLightY, + min: -500, + max: 500, + step: 10, + label: "Light Y", + }, + specLightZ: { + value: GLASS_TEXT_DEFAULTS.specLightZ, + min: 0, + max: 800, + step: 10, + label: "Light Z", + }, + specOpacity: { + value: GLASS_TEXT_DEFAULTS.specOpacity, + min: 0, + max: 1, + step: 0.05, + label: "Opacity", + }, + }), + Background: folder( + { + showBgImage: { + value: GLASS_TEXT_DEFAULTS.showBgImage, + label: "Show Image", + }, + bgOpacity: { + value: GLASS_TEXT_DEFAULTS.bgOpacity, + min: 0, + max: 1, + step: 0.05, + label: "Image Opacity", + }, + }, + { collapsed: true }, + ), + "Export for LLM": button(() => { + const c = controlsRef.current; + const lines = Object.entries(GLASS_TEXT_DEFAULTS) + .filter(([, def]) => typeof def === "number") + .map(([key]) => { + const val = c[key]; + return `- ${key}: ${val}`; + }) + .join("\n"); + const text = `## Glass Text Effect — Current Values\n\n${lines}`; + navigator.clipboard.writeText(text); + }), + }); + + controlsRef.current = controls; + + // Crossfade: detect bg change during render (not in effect) per rerender-derived-state-no-effect + const [prevBg, setPrevBg] = useState(bg); + const [fadingBg, setFadingBg] = useState<string | null>(null); + + if (bg !== prevBg) { + setPrevBg(bg); + setFadingBg(prevBg); + } + + // Clear the fading layer after the animation completes + useEffect(() => { + if (fadingBg === null) return; + const timer = setTimeout(() => setFadingBg(null), 600); + return () => clearTimeout(timer); + }, [fadingBg]); + + const textBgStyle = (bgId: string) => ({ + color: "transparent" as const, + backgroundImage: controls.showBgImage + ? `linear-gradient(rgba(255,255,255,${1 - controls.bgOpacity}),rgba(255,255,255,${1 - controls.bgOpacity})),${unsplash(bgId)}` + : undefined, + backgroundColor: controls.showBgImage ? undefined : "#999", + backgroundSize: "cover" as const, + backgroundPosition: "center" as const, + backgroundAttachment: "fixed" as const, + backgroundClip: "text" as const, + WebkitBackgroundClip: "text", + }); + + return ( + <div className="pointer-events-none relative text-5xl font-bold tracking-tight select-none lg:text-7xl"> + {/* SVG lighting filter — outputs only diffuse + specular (no SourceGraphic) */} + <svg + aria-hidden="true" + style={{ position: "absolute", width: 0, height: 0 }} + > + <defs> + <filter + id={filterId} + x="-10%" + y="-10%" + width="120%" + height="120%" + colorInterpolationFilters="sRGB" + > + <feGaussianBlur + in="SourceAlpha" + stdDeviation={controls.blurStdDev} + result="blur" + /> + <feDiffuseLighting + in="blur" + surfaceScale={controls.diffSurfaceScale} + diffuseConstant={controls.diffConstant} + result="diffuse" + > + <fePointLight + ref={diffPointLightRef} + x={0} + y={0} + z={controls.diffLightZ} + /> + </feDiffuseLighting> + <feSpecularLighting + in="blur" + surfaceScale={controls.specSurfaceScale} + specularConstant={controls.specConstant} + specularExponent={controls.specExponent} + result="specular" + > + <fePointLight + ref={specPointLightRef} + x={0} + y={0} + z={controls.specLightZ} + /> + </feSpecularLighting> + <feComposite + in="specular" + in2="SourceAlpha" + operator="in" + result="specClip" + /> + <feComposite + in="diffuse" + in2="SourceAlpha" + operator="in" + result="diffClip" + /> + <feColorMatrix + in="diffClip" + type="matrix" + values={`1 0 0 0 0 0 1 0 0 0 0 0 1 0 0 0 0 0 ${controls.diffOpacity} 0`} + result="diffFade" + /> + <feColorMatrix + in="specClip" + type="matrix" + values={`1 0 0 0 0 0 1 0 0 0 0 0 1 0 0 0 0 0 ${controls.specOpacity} 0`} + result="specFade" + /> + {/* Only lighting — no SourceGraphic in merge */} + <feMerge> + <feMergeNode in="diffFade" /> + <feMergeNode in="specFade" /> + </feMerge> + </filter> + </defs> + </svg> + + {/* Current bg — always visible underneath */} + <h1 + className="pointer-events-auto inline text-9xl" + style={textBgStyle(bg)} + > + {HERO_TEXT} + </h1> + + {/* Previous bg — fades out on top to reveal new image (crossfade) */} + {fadingBg && ( + <span + aria-hidden="true" + className="pointer-events-none absolute inset-0 text-9xl" + style={{ + ...textBgStyle(fadingBg), + animation: "glass-text-fade-out 600ms ease-out forwards", + }} + > + {HERO_TEXT} + </span> + )} + + {/* Lighting overlay: filter uses constant SourceAlpha — no flicker */} + <span + aria-hidden="true" + className="pointer-events-none absolute inset-0 text-9xl" + style={{ + filter: `url(#${filterId})`, + color: "white", + }} + > + {HERO_TEXT} + </span> + </div> + ); +} diff --git a/apps/docs/app/(demos)/tw-glass/(home)/layout.tsx b/apps/docs/app/(demos)/tw-glass/(home)/layout.tsx new file mode 100644 index 0000000..1053ebd --- /dev/null +++ b/apps/docs/app/(demos)/tw-glass/(home)/layout.tsx @@ -0,0 +1,24 @@ +import type { ReactNode } from "react"; +import { SubProjectLayout } from "@/components/shared/sub-project-layout"; +import type { Metadata } from "next"; + +export const metadata: Metadata = { + title: "tw-glass by assistant-ui", + description: + "A Tailwind CSS v4 plugin for glass refraction effects via SVG displacement maps. Pure CSS, no JavaScript required.", +}; + +export default function TwGlassHomeLayout({ + children, +}: { + children: ReactNode; +}): React.ReactElement { + return ( + <SubProjectLayout + name="tw-glass" + githubPath="https://github.com/assistant-ui/assistant-ui/tree/main/packages/tw-glass" + > + {children} + </SubProjectLayout> + ); +} diff --git a/apps/docs/app/(demos)/tw-glass/(home)/page.tsx b/apps/docs/app/(demos)/tw-glass/(home)/page.tsx new file mode 100644 index 0000000..7254f35 --- /dev/null +++ b/apps/docs/app/(demos)/tw-glass/(home)/page.tsx @@ -0,0 +1,348 @@ +"use client"; + +import { useState, useCallback } from "react"; +import { Leva } from "leva"; +import { Copy, Check, Sparkle } from "lucide-react"; +import { Button } from "@/components/ui/button"; +import { SINE_VIGNETTE } from "./constants"; +import { GlassTextHero } from "./glass-text-hero"; +import { PATTERNS, unsplash, PatternPicker } from "./pattern-picker"; +import { DemoArea, GlassDemo } from "./demo-components"; +import { + HighlightStyles, + Box, + BoxTitle, + BoxContent, + BoxCodeHeader, + BoxCode, + CodeBlock, +} from "./doc-components"; + +export default function TwGlassPage() { + const [copied, setCopied] = useState(false); + const [patternIndex, setPatternIndex] = useState(0); + + const copyToClipboard = useCallback(async (text: string) => { + if (typeof navigator === "undefined" || !navigator.clipboard) return; + try { + await navigator.clipboard.writeText(text); + setCopied(true); + setTimeout(() => setCopied(false), 2000); + } catch {} + }, []); + + const bg = PATTERNS[patternIndex]?.id ?? ""; + + return ( + <> + {/* Fixed full-viewport background with selected pattern + sine-eased vignette */} + <div + className="pointer-events-none fixed inset-0 z-0 bg-cover bg-fixed bg-center transition-[background-image] duration-500" + style={{ + backgroundImage: unsplash(bg), + opacity: 0.1, + maskImage: SINE_VIGNETTE, + WebkitMaskImage: SINE_VIGNETTE, + }} + /> + <div className="relative z-10 container mx-auto max-w-7xl space-y-16 px-4 pt-12 pb-28"> + <Leva collapsed={false} titleBar={{ title: "Glass Text Effect" }} /> + <HighlightStyles /> + <PatternPicker active={patternIndex} onChange={setPatternIndex} /> + + {/* Hero */} + <div className="mx-auto flex w-fit flex-col items-center space-y-6 text-center"> + <div className="glass glass-surface glass-bg-3 glass-chromatic-50 glass-blur-0 glass-saturation-200 glass-brightness-150 flex items-center gap-2 rounded-full px-4 py-1.5 text-sm"> + <Sparkle className="size-4 opacity-50" /> + <span className="text-foreground/60">Tailwind CSS v4 Plugin</span> + </div> + + <div className="flex flex-col gap-5"> + <GlassTextHero bg={bg} /> + + <p className="text-muted-foreground text-lg font-light text-balance"> + Realistic glass refraction for Tailwind. Pure CSS, no JavaScript. + </p> + </div> + </div> + + {/* Installation */} + <div id="installation" className="space-y-8"> + <div className="mx-auto max-w-3xl space-y-6"> + <Box className="glass"> + <BoxContent> + <div className="flex items-center justify-between"> + <code className="text-sm">npm install tw-glass</code> + <Button + size="sm" + variant="ghost" + onClick={() => copyToClipboard("npm install tw-glass")} + > + {copied ? ( + <Check className="size-4" /> + ) : ( + <Copy className="size-4" /> + )} + </Button> + </div> + </BoxContent> + </Box> + + <Box className="glass"> + <BoxCodeHeader fileName="styles/globals.css" /> + <BoxCode> + <CodeBlock + language="css" + code={`@import "tailwindcss"; +@import "tw-glass";`} + highlight="tw-glass" + highlightMode="line" + /> + </BoxCode> + </Box> + </div> + </div> + + {/* Base Glass */} + <div className="space-y-8"> + <div className="text-center"> + <h2 className="mb-2 text-3xl font-medium">Glass Refraction</h2> + <p className="text-muted-foreground text-xl"> + Composable utilities for glass-like displacement effects. + </p> + </div> + + <div className="mx-auto max-w-3xl space-y-6"> + <Box> + <BoxTitle + title="glass" + description="Base utility. Applies SVG displacement filter via backdrop-filter. Requires visible content behind the element." + /> + <BoxCode> + <CodeBlock + language="html" + code='<div class="glass rounded-xl p-6">Glass panel</div>' + highlight="glass" + highlightMode="text" + /> + </BoxCode> + <DemoArea pattern={bg}> + <GlassDemo className="glass" /> + </DemoArea> + </Box> + + <Box> + <BoxTitle + title="glass glass-surface" + description="Add frosted surface styling (semi-transparent background + inner glow + shadow)." + /> + <BoxCode> + <CodeBlock + language="html" + code='<div class="glass glass-surface rounded-xl p-6">Frosted panel</div>' + highlight="glass-surface" + highlightMode="text" + /> + </BoxCode> + <DemoArea pattern={bg}> + <GlassDemo className="glass glass-surface" /> + </DemoArea> + </Box> + </div> + </div> + + {/* Strength */} + <div className="space-y-8"> + <div className="text-center"> + <h2 className="mb-2 text-3xl font-medium">Displacement Strength</h2> + <p className="text-muted-foreground text-xl"> + Control how much the background is distorted. + </p> + </div> + + <div className="mx-auto max-w-3xl space-y-6"> + <Box> + <BoxTitle + title="glass-strength-{value}" + description="Displacement intensity. Available: 5, 10, 20 (default), 30, 40, 50. Higher values create more dramatic refraction." + /> + <BoxCode> + <CodeBlock + language="html" + code='<div class="glass glass-strength-40 rounded-xl p-6">Strong glass</div>' + highlight="glass-strength" + highlightMode="text" + /> + </BoxCode> + <DemoArea pattern={bg}> + <div className="grid grid-cols-3"> + <GlassDemo className="glass glass-strength-5" label="5" /> + <GlassDemo className="glass glass-strength-20" label="20" /> + <GlassDemo className="glass glass-strength-50" label="50" /> + </div> + </DemoArea> + </Box> + </div> + </div> + + {/* Chromatic */} + <div className="space-y-8"> + <div className="text-center"> + <h2 className="mb-2 text-3xl font-medium">Chromatic Aberration</h2> + <p className="text-muted-foreground text-xl"> + Simulates light dispersion through a prism using RGB channel + splitting. + </p> + </div> + + <div className="mx-auto max-w-3xl space-y-6"> + <Box> + <BoxTitle + title="glass-chromatic-{value}" + description="Replaces standard displacement with per-channel RGB splitting. Same strength levels: 5, 10, 20, 30, 40, 50." + /> + <BoxCode> + <CodeBlock + language="html" + code='<div class="glass glass-chromatic-20 rounded-xl p-6">Chromatic glass</div>' + highlight="glass-chromatic" + highlightMode="text" + /> + </BoxCode> + <DemoArea pattern={bg}> + <div className="grid grid-cols-3"> + <GlassDemo className="glass glass-chromatic-10" label="10" /> + <GlassDemo className="glass glass-chromatic-20" label="20" /> + <GlassDemo className="glass glass-chromatic-40" label="40" /> + </div> + </DemoArea> + </Box> + </div> + </div> + + {/* Continuous Modifiers */} + <div className="space-y-8"> + <div className="text-center"> + <h2 className="mb-2 text-3xl font-medium">Modifiers</h2> + <p className="text-muted-foreground text-xl"> + Fine-tune blur, saturation, and brightness with any numeric value. + </p> + </div> + + <div className="mx-auto max-w-3xl space-y-6"> + <Box> + <BoxTitle + title="glass-blur-{px}" + description="Post-displacement blur in pixels. Default: 2px." + /> + <BoxCode> + <CodeBlock + language="html" + code='<div class="glass glass-blur-6 rounded-xl p-6">Blurry glass</div>' + highlight="glass-blur" + highlightMode="text" + /> + </BoxCode> + <DemoArea pattern={bg}> + <div className="grid grid-cols-3"> + <GlassDemo className="glass glass-blur-0" label="0px" /> + <GlassDemo className="glass glass-blur-2" label="2px" /> + <GlassDemo className="glass glass-blur-6" label="6px" /> + </div> + </DemoArea> + </Box> + + <Box> + <BoxTitle + title="glass-saturation-{pct}" + description="Color saturation as a percentage. Default: 120 (1.2x). 100 = no change." + /> + <BoxCode> + <CodeBlock + language="html" + code='<div class="glass glass-saturation-200 rounded-xl p-6">Vivid glass</div>' + highlight="glass-saturation" + highlightMode="text" + /> + </BoxCode> + <DemoArea pattern={bg}> + <div className="grid grid-cols-3"> + <GlassDemo className="glass glass-saturation-50" label="50" /> + <GlassDemo + className="glass glass-saturation-120" + label="120" + /> + <GlassDemo + className="glass glass-saturation-200" + label="200" + /> + </div> + </DemoArea> + </Box> + + <Box> + <BoxTitle + title="glass-brightness-{pct}" + description="Brightness as a percentage. Default: 105. 100 = no change." + /> + <BoxCode> + <CodeBlock + language="html" + code='<div class="glass glass-brightness-130 rounded-xl p-6">Bright glass</div>' + highlight="glass-brightness" + highlightMode="text" + /> + </BoxCode> + <DemoArea pattern={bg}> + <div className="grid grid-cols-3"> + <GlassDemo className="glass glass-brightness-80" label="80" /> + <GlassDemo + className="glass glass-brightness-105" + label="105" + /> + <GlassDemo + className="glass glass-brightness-140" + label="140" + /> + </div> + </DemoArea> + </Box> + </div> + </div> + + {/* Composition */} + <div className="space-y-8"> + <div className="text-center"> + <h2 className="mb-2 text-3xl font-medium">Composition</h2> + <p className="text-muted-foreground text-xl"> + Combine any modifiers with the base glass class. + </p> + </div> + + <div className="mx-auto max-w-3xl space-y-6"> + <Box> + <BoxCode> + <CodeBlock + language="html" + code={`<div class="glass glass-strength-30 glass-blur-4 glass-saturation-150 glass-surface rounded-xl p-6"> + Composed glass panel +</div>`} + highlight={[ + "glass-strength", + "glass-blur", + "glass-saturation", + "glass-surface", + ]} + highlightMode="text" + /> + </BoxCode> + <DemoArea pattern={bg}> + <GlassDemo className="glass glass-strength-30 glass-blur-4 glass-saturation-150 glass-surface" /> + </DemoArea> + </Box> + </div> + </div> + </div> + </> + ); +} diff --git a/apps/docs/app/(demos)/tw-glass/(home)/pattern-picker.tsx b/apps/docs/app/(demos)/tw-glass/(home)/pattern-picker.tsx new file mode 100644 index 0000000..b413f6a --- /dev/null +++ b/apps/docs/app/(demos)/tw-glass/(home)/pattern-picker.tsx @@ -0,0 +1,53 @@ +"use client"; + +import { cn } from "@/lib/utils"; + +export const unsplash = (id: string) => + `url(https://images.unsplash.com/${id}?auto=format&fit=crop&w=1920&q=90)`; + +export const unsplashThumb = (id: string) => + `url(https://images.unsplash.com/${id}?auto=format&fit=crop&w=88&h=88&q=60)`; + +export const PATTERNS = [ + { name: "Marble", id: "photo-1761419647919-233829f0f469" }, + { name: "Hands", id: "photo-1541661538396-53ba2d051eed" }, + { name: "Fern", id: "photo-1557672172-298e090bd0f1" }, + { name: "Abstract", id: "photo-1604871000636-074fa5117945" }, + { name: "Gradient", id: "photo-1640280882428-547d0afe0c8d" }, + { name: "Sunset", id: "photo-1517384084767-6bc118943770" }, +]; + +export function PatternPicker({ + active, + onChange, +}: { + active: number; + onChange: (index: number) => void; +}) { + return ( + <div className="fixed inset-x-0 bottom-6 z-50 flex justify-center"> + <div className="glass glass-surface flex gap-2 rounded-2xl p-2"> + {PATTERNS.map((p, i) => ( + <button + key={p.name} + type="button" + onClick={() => onChange(i)} + aria-label={p.name} + title={p.name} + className={cn( + "bg-muted size-11 cursor-pointer overflow-hidden rounded-xl transition-all", + active === i + ? "ring-foreground/40 ring-offset-background ring-2 ring-offset-2" + : "opacity-70 hover:opacity-100", + )} + style={{ + backgroundImage: unsplashThumb(p.id), + backgroundSize: "cover", + backgroundPosition: "center", + }} + /> + ))} + </div> + </div> + ); +} diff --git a/apps/docs/app/(demos)/tw-glass/stress-test-text/layout.tsx b/apps/docs/app/(demos)/tw-glass/stress-test-text/layout.tsx new file mode 100644 index 0000000..4b90eac --- /dev/null +++ b/apps/docs/app/(demos)/tw-glass/stress-test-text/layout.tsx @@ -0,0 +1,32 @@ +import type { Metadata } from "next"; +import type { ReactNode } from "react"; +import { SubProjectLayout } from "@/components/shared/sub-project-layout"; + +export const metadata: Metadata = { + title: "Glass Text Stress Test | tw-glass by assistant-ui", + robots: { + index: false, + follow: true, + }, +}; + +export default function StressTestTextLayout({ + children, +}: { + children: ReactNode; +}): React.ReactElement { + return ( + <SubProjectLayout + name="tw-glass" + githubPath="https://github.com/assistant-ui/assistant-ui/tree/main/packages/tw-glass" + breadcrumbs={[ + { + label: "stress-test-text", + href: "/tw-glass/stress-test-text", + }, + ]} + > + {children} + </SubProjectLayout> + ); +} diff --git a/apps/docs/app/(demos)/tw-glass/stress-test-text/page.tsx b/apps/docs/app/(demos)/tw-glass/stress-test-text/page.tsx new file mode 100644 index 0000000..1d14729 --- /dev/null +++ b/apps/docs/app/(demos)/tw-glass/stress-test-text/page.tsx @@ -0,0 +1,721 @@ +"use client"; + +import { cn } from "@/lib/utils"; +import { useState, useEffect, type ReactNode } from "react"; + +const PHOTO_ID = "photo-1531366936337-7c912a4589a7"; +const unsplash = (id: string) => + `url(https://images.unsplash.com/${id}?auto=format&fit=crop&w=1920&q=80)`; + +const BG_IMAGE = unsplash(PHOTO_ID); + +export default function GlassTextStressTestPage() { + return ( + <div className="bg-background min-h-screen"> + <div className="mx-auto max-w-7xl px-4 py-12"> + <header className="mb-12 space-y-4"> + <h1 className="text-4xl font-bold">Glass Text Stress Test</h1> + <p className="text-muted-foreground max-w-2xl"> + Pressure-test the glass-text utility under heavy load: many + elements, extreme sizes, varied backgrounds, long copy, and all + options simultaneously. + </p> + </header> + + <div className="flex gap-8"> + <aside className="hidden w-56 shrink-0 lg:block"> + <TableOfContents /> + </aside> + + <main className="min-w-0 flex-1 space-y-16"> + <MassGridSection /> + <FontSizeSection /> + <FontWeightSection /> + <BackgroundVariantsSection /> + <LongCopySection /> + <NestedContainersSection /> + <ScrollStressSection /> + <InteractiveSection /> + </main> + </div> + </div> + </div> + ); +} + +function TableOfContents() { + const sections = [ + { id: "mass-grid", label: "Mass Grid" }, + { id: "font-sizes", label: "Font Sizes" }, + { id: "font-weights", label: "Font Weights" }, + { id: "backgrounds", label: "Backgrounds" }, + { id: "long-copy", label: "Long Copy" }, + { id: "nested-containers", label: "Nested Containers" }, + { id: "scroll-stress", label: "Scroll Stress" }, + { id: "interactive", label: "Interactive" }, + ]; + + return ( + <nav className="sticky top-24"> + <h2 className="text-muted-foreground mb-3 text-xs font-semibold tracking-wide uppercase"> + Sections + </h2> + <ul className="space-y-1"> + {sections.map(({ id, label }) => ( + <li key={id}> + <a + href={`#${id}`} + className="text-muted-foreground hover:bg-muted hover:text-foreground block rounded-md px-3 py-1.5 text-sm transition-colors" + > + {label} + </a> + </li> + ))} + </ul> + </nav> + ); +} + +function Section({ + id, + title, + description, + children, +}: { + id: string; + title: string; + description?: string; + children: ReactNode; +}) { + return ( + <section id={id} className="scroll-mt-8 space-y-6"> + <div className="space-y-2"> + <h2 className="text-2xl font-bold">{title}</h2> + {description && <p className="text-muted-foreground">{description}</p>} + </div> + {children} + </section> + ); +} + +function TestCard({ + title, + description, + children, +}: { + title: string; + description?: string; + children: ReactNode; +}) { + return ( + <div className="space-y-3 overflow-x-auto rounded-lg border border-dashed p-4"> + <div> + <h3 className="font-semibold">{title}</h3> + {description && ( + <p className="text-muted-foreground text-sm">{description}</p> + )} + </div> + {children} + </div> + ); +} + +function DemoArea({ + children, + className = "", +}: { + children: ReactNode; + className?: string; +}) { + return ( + <div + className={cn( + "bg-muted relative overflow-hidden rounded-lg p-6", + className, + )} + > + {children} + </div> + ); +} + +function GlassText({ + children, + className = "", + style, +}: { + children: ReactNode; + className?: string; + style?: React.CSSProperties; +}) { + return ( + <span + className={cn("glass-text", className)} + style={{ + backgroundImage: BG_IMAGE, + backgroundAttachment: "fixed", + ...style, + }} + > + {children} + </span> + ); +} + +function useFps() { + const [fps, setFps] = useState(0); + + useEffect(() => { + let frameCount = 0; + let lastTime = performance.now(); + let rafId: number; + + function tick() { + frameCount++; + const now = performance.now(); + if (now - lastTime >= 1000) { + setFps(frameCount); + frameCount = 0; + lastTime = now; + } + rafId = requestAnimationFrame(tick); + } + + rafId = requestAnimationFrame(tick); + return () => cancelAnimationFrame(rafId); + }, []); + + return fps; +} + +function FpsCounter() { + const fps = useFps(); + return ( + <div className="bg-background/80 inline-flex items-center gap-1.5 rounded-full px-3 py-1 font-mono text-sm"> + <span + className={cn("size-2 rounded-full", { + "bg-green-500": fps >= 55, + "bg-yellow-500": fps >= 30, + "bg-red-500": fps < 30, + })} + /> + {fps} FPS + </div> + ); +} + +function MassGridSection() { + const [count, setCount] = useState(24); + const cols = Math.min(Math.ceil(Math.sqrt(count)), 8); + + return ( + <Section + id="mass-grid" + title="Mass Grid" + description="Raw element count with glass-text. Crank the slider to stress the filter chain." + > + <TestCard + title={`${count} glass-text elements`} + description="Each cell is a glass-text heading over a shared photo background." + > + <div className="mb-4 flex items-center gap-4"> + <label + className="flex items-center gap-2 text-sm font-medium" + htmlFor="mass-grid-count" + > + Elements + <span className="text-muted-foreground font-mono">{count}</span> + </label> + <input + id="mass-grid-count" + type="range" + min="4" + max="120" + value={count} + onChange={(e) => setCount(Number(e.target.value))} + className="flex-1" + /> + <FpsCounter /> + </div> + <DemoArea> + <div + className="grid gap-4" + style={{ + gridTemplateColumns: `repeat(${cols}, minmax(0, 1fr))`, + }} + > + {Array.from({ length: count }).map((_, i) => ( + <div key={i} className="text-center"> + <GlassText className="text-3xl font-bold">Glass</GlassText> + </div> + ))} + </div> + </DemoArea> + </TestCard> + </Section> + ); +} + +const FONT_SIZES = [ + { label: "text-sm", cls: "text-sm" }, + { label: "text-base", cls: "text-base" }, + { label: "text-2xl", cls: "text-2xl" }, + { label: "text-4xl", cls: "text-4xl" }, + { label: "text-6xl", cls: "text-6xl" }, + { label: "text-8xl", cls: "text-8xl" }, + { label: "text-9xl", cls: "text-9xl" }, +]; + +function FontSizeSection() { + return ( + <Section + id="font-sizes" + title="Font Sizes" + description="Glass text at every scale, from body copy to hero headlines. Tests filter rendering at extreme glyph sizes." + > + <TestCard + title="text-sm through text-9xl" + description="Background-clip: text at every scale. The photo shows through each glyph." + > + <DemoArea> + <div className="space-y-6"> + {FONT_SIZES.map(({ label, cls }) => ( + <div key={label} className="space-y-1"> + <code className="bg-foreground/10 text-muted-foreground rounded px-1.5 py-0.5 font-mono text-[10px]"> + {label} + </code> + <div> + <GlassText className={cn("font-bold", cls)}> + tw-glass + </GlassText> + </div> + </div> + ))} + </div> + </DemoArea> + </TestCard> + </Section> + ); +} + +const FONT_WEIGHTS = [ + { label: "font-thin", cls: "font-thin" }, + { label: "font-light", cls: "font-light" }, + { label: "font-normal", cls: "font-normal" }, + { label: "font-medium", cls: "font-medium" }, + { label: "font-semibold", cls: "font-semibold" }, + { label: "font-bold", cls: "font-bold" }, + { label: "font-extrabold", cls: "font-extrabold" }, + { label: "font-black", cls: "font-black" }, +]; + +function FontWeightSection() { + return ( + <Section + id="font-weights" + title="Font Weights" + description="Stroke weight affects how much of the photo is visible through each glyph." + > + <TestCard + title="font-thin through font-black" + description="All at text-5xl to make weight differences visible." + > + <DemoArea> + <div className="space-y-4"> + {FONT_WEIGHTS.map(({ label, cls }) => ( + <div key={label} className="flex items-baseline gap-4"> + <code className="bg-foreground/10 text-muted-foreground w-32 shrink-0 rounded px-1.5 py-0.5 text-right font-mono text-[10px]"> + {label} + </code> + <GlassText className={cn("text-5xl", cls)}>tw-glass</GlassText> + </div> + ))} + </div> + </DemoArea> + </TestCard> + </Section> + ); +} + +const BG_VARIANTS: { + label: string; + style: React.CSSProperties; +}[] = [ + { + label: "Photo (fixed)", + style: { + backgroundImage: BG_IMAGE, + backgroundSize: "cover", + backgroundPosition: "center", + backgroundAttachment: "fixed", + }, + }, + { + label: "Linear gradient", + style: { + backgroundImage: "linear-gradient(135deg, #667eea 0%, #764ba2 100%)", + }, + }, + { + label: "Radial gradient", + style: { + backgroundImage: + "radial-gradient(circle at 30% 40%, #f093fb, #f5576c, #4facfe)", + }, + }, + { + label: "Conic gradient", + style: { + backgroundImage: + "conic-gradient(from 45deg, #12c2e9, #c471ed, #f64f59, #12c2e9)", + }, + }, + { + label: "Solid color", + style: { backgroundImage: "none", backgroundColor: "#888" }, + }, + { + label: "Dark solid", + style: { backgroundImage: "none", backgroundColor: "#222" }, + }, +]; + +function BackgroundVariantsSection() { + return ( + <Section + id="backgrounds" + title="Background Variants" + description="Different background sources clipped through the text shape. Photos, gradients, and solids." + > + <TestCard + title="6 background styles" + description="Each heading uses a different background-image/color with background-clip: text." + > + <DemoArea> + <div className="grid gap-6 sm:grid-cols-2"> + {BG_VARIANTS.map(({ label, style }) => ( + <div key={label} className="space-y-1"> + <code className="bg-foreground/10 text-muted-foreground rounded px-1.5 py-0.5 font-mono text-[10px]"> + {label} + </code> + <div> + <GlassText className="text-5xl font-bold" style={style}> + tw-glass + </GlassText> + </div> + </div> + ))} + </div> + </DemoArea> + </TestCard> + </Section> + ); +} + +const LOREM = + "The quick brown fox jumps over the lazy dog. Pack my box with five dozen liquor jugs. How vexingly quick daft zebras jump. Sphinx of black quartz, judge my vow."; + +function LongCopySection() { + return ( + <Section + id="long-copy" + title="Long Copy" + description="Glass text applied to paragraphs and multi-line content. Tests filter performance on large text blocks." + > + <TestCard + title="Paragraph text" + description="Full paragraphs of glass text at different sizes." + > + <DemoArea> + <div className="space-y-6"> + <div className="space-y-1"> + <code className="bg-foreground/10 text-muted-foreground rounded px-1.5 py-0.5 font-mono text-[10px]"> + text-lg + </code> + <p> + <GlassText className="text-lg leading-relaxed font-medium"> + {LOREM} {LOREM} + </GlassText> + </p> + </div> + + <div className="space-y-1"> + <code className="bg-foreground/10 text-muted-foreground rounded px-1.5 py-0.5 font-mono text-[10px]"> + text-2xl + </code> + <p> + <GlassText className="text-2xl leading-relaxed font-semibold"> + {LOREM} + </GlassText> + </p> + </div> + + <div className="space-y-1"> + <code className="bg-foreground/10 text-muted-foreground rounded px-1.5 py-0.5 font-mono text-[10px]"> + text-4xl (multiline) + </code> + <p> + <GlassText className="text-4xl leading-tight font-bold"> + {LOREM} + </GlassText> + </p> + </div> + </div> + </DemoArea> + </TestCard> + </Section> + ); +} + +function NestedContainersSection() { + return ( + <Section + id="nested-containers" + title="Nested Containers" + description="Glass text inside glass panels, and glass panels inside glass text regions. Tests filter stacking." + > + <TestCard + title="glass-text inside glass panels" + description="Glass text headings rendered within glass glass-surface containers." + > + <DemoArea> + <div className="space-y-4"> + <div className="glass glass-surface rounded-xl p-6"> + <GlassText className="text-4xl font-bold"> + Heading inside glass + </GlassText> + <p className="text-muted-foreground mt-2 text-sm"> + Regular text below the glass heading. + </p> + </div> + + <div className="grid grid-cols-2 gap-4"> + <div className="glass glass-surface glass-strength-30 rounded-xl p-6"> + <GlassText className="text-2xl font-bold">Panel A</GlassText> + </div> + <div className="glass glass-surface glass-chromatic-20 rounded-xl p-6"> + <GlassText className="text-2xl font-bold">Panel B</GlassText> + </div> + </div> + + <div className="glass glass-surface rounded-xl p-6"> + <div className="glass glass-surface rounded-lg p-4"> + <GlassText className="text-3xl font-bold"> + Double nested + </GlassText> + </div> + </div> + </div> + </DemoArea> + </TestCard> + </Section> + ); +} + +function ScrollStressSection() { + return ( + <Section + id="scroll-stress" + title="Scroll Stress" + description="40 glass-text headings stacked vertically. Tests filter performance during scroll." + > + <TestCard + title="Scroll through glass text headings" + description="Each heading uses background-clip: text with a fixed background. Tests performance during scroll." + > + <div className="bg-muted relative h-[500px] overflow-y-auto rounded-lg"> + <div className="space-y-4 p-4"> + {Array.from({ length: 40 }).map((_, i) => ( + <div + key={i} + className="border-foreground/10 rounded-xl border border-dashed px-4 py-3" + > + <GlassText className="text-3xl font-bold"> + Glass heading {i + 1} + </GlassText> + </div> + ))} + </div> + </div> + </TestCard> + </Section> + ); +} + +const PHOTO_OPTIONS = [ + { label: "Aurora", id: "photo-1531366936337-7c912a4589a7" }, + { label: "Fern", id: "photo-1557672172-298e090bd0f1" }, + { label: "Wave", id: "photo-1659762073691-e724db40f9d5" }, + { label: "Gradient", id: "" }, +]; + +function InteractiveSection() { + const [count, setCount] = useState(8); + const [fontSize, setFontSize] = useState(64); + const [fontWeight, setFontWeight] = useState(700); + const [photoIdx, setPhotoIdx] = useState(0); + const [text, setText] = useState("tw-glass"); + + const cols = Math.min(count, 4); + + const photo = PHOTO_OPTIONS[photoIdx]; + const bgStyle: React.CSSProperties = photo?.id + ? { + backgroundImage: unsplash(photo.id), + backgroundSize: "cover", + backgroundPosition: "center", + backgroundAttachment: "fixed", + } + : { + backgroundImage: "linear-gradient(135deg, #667eea 0%, #764ba2 100%)", + }; + + return ( + <Section + id="interactive" + title="Interactive" + description="User-controlled parameters. All sliders update in real time." + > + <div className="grid gap-8 lg:grid-cols-2"> + <div className="space-y-5"> + <SliderControl + id="int-count" + label="Elements" + value={count} + min={1} + max={48} + onChange={setCount} + display={`${count}`} + /> + + <SliderControl + id="int-font-size" + label="Font Size" + value={fontSize} + min={12} + max={192} + onChange={setFontSize} + display={`${fontSize}px`} + /> + + <SliderControl + id="int-font-weight" + label="Font Weight" + value={fontWeight} + min={100} + max={900} + step={100} + onChange={setFontWeight} + display={`${fontWeight}`} + /> + + <div className="space-y-2"> + <label className="text-sm font-medium" htmlFor="int-text"> + Text + </label> + <input + id="int-text" + type="text" + value={text} + onChange={(e) => setText(e.target.value)} + className="bg-background w-full rounded-md border px-3 py-2 text-sm" + /> + </div> + + <div className="space-y-2"> + <span className="text-sm font-medium">Background</span> + <div className="flex gap-2"> + {PHOTO_OPTIONS.map((p, i) => ( + <button + key={p.label} + type="button" + onClick={() => setPhotoIdx(i)} + className={cn( + "cursor-pointer rounded-md px-3 py-1.5 text-sm transition-colors", + { + "bg-foreground text-background": photoIdx === i, + "bg-muted text-muted-foreground hover:bg-muted/80": + photoIdx !== i, + }, + )} + > + {p.label} + </button> + ))} + </div> + </div> + + <FpsCounter /> + </div> + + <div> + <DemoArea> + <div + className="grid gap-4" + style={{ + gridTemplateColumns: `repeat(${cols}, minmax(0, 1fr))`, + }} + > + {Array.from({ length: count }).map((_, i) => ( + <div key={i} className="text-center"> + <GlassText + className="inline-block" + style={{ + ...bgStyle, + fontSize: `${fontSize}px`, + fontWeight, + }} + > + {text} + </GlassText> + </div> + ))} + </div> + </DemoArea> + </div> + </div> + </Section> + ); +} + +function SliderControl({ + id, + label, + value, + min, + max, + step = 1, + onChange, + display, +}: { + id: string; + label: string; + value: number; + min: number; + max: number; + step?: number; + onChange: (v: number) => void; + display: string; +}) { + return ( + <div className="space-y-2"> + <label + className="flex items-center justify-between text-sm font-medium" + htmlFor={id} + > + <span>{label}</span> + <span className="text-muted-foreground font-mono">{display}</span> + </label> + <input + id={id} + type="range" + min={min} + max={max} + step={step} + value={value} + onChange={(e) => onChange(Number(e.target.value))} + className="w-full" + /> + </div> + ); +} diff --git a/apps/docs/app/(demos)/tw-glass/stress-test/layout.tsx b/apps/docs/app/(demos)/tw-glass/stress-test/layout.tsx new file mode 100644 index 0000000..eecca70 --- /dev/null +++ b/apps/docs/app/(demos)/tw-glass/stress-test/layout.tsx @@ -0,0 +1,27 @@ +import type { Metadata } from "next"; +import type { ReactNode } from "react"; +import { SubProjectLayout } from "@/components/shared/sub-project-layout"; + +export const metadata: Metadata = { + title: "Stress Test | tw-glass by assistant-ui", + robots: { + index: false, + follow: true, + }, +}; + +export default function StressTestLayout({ + children, +}: { + children: ReactNode; +}): React.ReactElement { + return ( + <SubProjectLayout + name="tw-glass" + githubPath="https://github.com/assistant-ui/assistant-ui/tree/main/packages/tw-glass" + breadcrumbs={[{ label: "stress-test", href: "/tw-glass/stress-test" }]} + > + {children} + </SubProjectLayout> + ); +} diff --git a/apps/docs/app/(demos)/tw-glass/stress-test/page.tsx b/apps/docs/app/(demos)/tw-glass/stress-test/page.tsx new file mode 100644 index 0000000..448b821 --- /dev/null +++ b/apps/docs/app/(demos)/tw-glass/stress-test/page.tsx @@ -0,0 +1,643 @@ +"use client"; + +import { cn } from "@/lib/utils"; +import { useState, useEffect, useCallback, type ReactNode } from "react"; + +const PHOTO_ID = "photo-1531366936337-7c912a4589a7"; +const unsplash = (id: string) => + `url(https://images.unsplash.com/${id}?auto=format&fit=crop&w=1920&q=80)`; + +const BG_STYLE: React.CSSProperties = { + backgroundImage: unsplash(PHOTO_ID), + backgroundAttachment: "fixed", + backgroundSize: "cover", + backgroundPosition: "center", +}; + +export default function GlassStressTestPage() { + return ( + <div className="bg-background min-h-screen"> + <div className="mx-auto max-w-7xl px-4 py-12"> + <header className="mb-12 space-y-4"> + <h1 className="text-4xl font-bold">Glass Stress Test</h1> + <p className="text-muted-foreground max-w-2xl"> + Pressure-test tw-glass rendering under heavy load: many elements, + nested glass, extreme blur, rapid resizing, and all variants + simultaneously. + </p> + </header> + + <div className="flex gap-8"> + <aside className="hidden w-56 shrink-0 lg:block"> + <TableOfContents /> + </aside> + + <main className="min-w-0 flex-1 space-y-16"> + <MassGridSection /> + <AllVariantsSection /> + <NestedGlassSection /> + <ScrollStressSection /> + <ResizeBehaviorSection /> + <BlurExtremesSection /> + <ComposabilitySection /> + <InteractiveSection /> + </main> + </div> + </div> + </div> + ); +} + +function TableOfContents() { + const sections = [ + { id: "mass-grid", label: "Mass Grid" }, + { id: "all-variants", label: "All Variants" }, + { id: "nested-glass", label: "Nested Glass" }, + { id: "scroll-stress", label: "Scroll Stress" }, + { id: "resize-behavior", label: "Resize Behavior" }, + { id: "blur-extremes", label: "Blur Extremes" }, + { id: "composability", label: "Composability" }, + { id: "interactive", label: "Interactive" }, + ]; + + return ( + <nav className="sticky top-24"> + <h2 className="text-muted-foreground mb-3 text-xs font-semibold tracking-wide uppercase"> + Sections + </h2> + <ul className="space-y-1"> + {sections.map(({ id, label }) => ( + <li key={id}> + <a + href={`#${id}`} + className="text-muted-foreground hover:bg-muted hover:text-foreground block rounded-md px-3 py-1.5 text-sm transition-colors" + > + {label} + </a> + </li> + ))} + </ul> + </nav> + ); +} + +function Section({ + id, + title, + description, + children, +}: { + id: string; + title: string; + description?: string; + children: ReactNode; +}) { + return ( + <section id={id} className="scroll-mt-8 space-y-6"> + <div className="space-y-2"> + <h2 className="text-2xl font-bold">{title}</h2> + {description && <p className="text-muted-foreground">{description}</p>} + </div> + {children} + </section> + ); +} + +function TestCard({ + title, + description, + children, +}: { + title: string; + description?: string; + children: ReactNode; +}) { + return ( + <div className="space-y-3 overflow-x-auto rounded-lg border border-dashed p-4"> + <div> + <h3 className="font-semibold">{title}</h3> + {description && ( + <p className="text-muted-foreground text-sm">{description}</p> + )} + </div> + {children} + </div> + ); +} + +function DemoArea({ children }: { children: ReactNode }) { + return ( + <div + className="bg-muted relative overflow-hidden rounded-lg p-6 shadow-[inset_0_1px_4px_rgba(0,0,0,0.08)] dark:shadow-[inset_0_1px_4px_rgba(0,0,0,0.4)]" + style={BG_STYLE} + > + {children} + </div> + ); +} + +function GlassPanel({ + className = "", + label, + compact = false, +}: { + className?: string; + label: string; + compact?: boolean; +}) { + return ( + <div + className={cn("rounded-xl", className, compact ? "px-4 py-6" : "p-6")} + style={{ minHeight: compact ? 80 : 100 }} + > + <code className="bg-foreground/10 text-muted-foreground rounded px-1.5 py-0.5 font-mono text-[10px]"> + {label} + </code> + </div> + ); +} + +function useFps() { + const [fps, setFps] = useState(0); + + useEffect(() => { + let frameCount = 0; + let lastTime = performance.now(); + let rafId: number; + + function tick() { + frameCount++; + const now = performance.now(); + if (now - lastTime >= 1000) { + setFps(frameCount); + frameCount = 0; + lastTime = now; + } + rafId = requestAnimationFrame(tick); + } + + rafId = requestAnimationFrame(tick); + return () => cancelAnimationFrame(rafId); + }, []); + + return fps; +} + +function FpsCounter() { + const fps = useFps(); + return ( + <div className="bg-background/80 inline-flex items-center gap-1.5 rounded-full px-3 py-1 font-mono text-sm"> + <span + className={cn("size-2 rounded-full", { + "bg-green-500": fps >= 55, + "bg-yellow-500": fps >= 30, + "bg-red-500": fps < 30, + })} + /> + {fps} FPS + </div> + ); +} + +function MassGridSection() { + const [count, setCount] = useState(36); + const cols = Math.ceil(Math.sqrt(count)); + + return ( + <Section + id="mass-grid" + title="Mass Grid" + description="Raw element count under glass. Crank the slider to stress the compositor." + > + <TestCard + title={`${count} glass elements`} + description="Each cell is a glass glass-surface div over a shared photo background." + > + <div className="mb-4 flex items-center gap-4"> + <label + className="flex items-center gap-2 text-sm font-medium" + htmlFor="mass-grid-count" + > + Elements + <span className="text-muted-foreground font-mono">{count}</span> + </label> + <input + id="mass-grid-count" + type="range" + min="10" + max="200" + value={count} + onChange={(e) => setCount(Number(e.target.value))} + className="flex-1" + /> + <FpsCounter /> + </div> + <DemoArea> + <div + className="grid gap-2" + style={{ + gridTemplateColumns: `repeat(${cols}, minmax(0, 1fr))`, + }} + > + {Array.from({ length: count }).map((_, i) => ( + <div + key={i} + className="glass glass-surface aspect-square rounded-lg" + /> + ))} + </div> + </DemoArea> + </TestCard> + </Section> + ); +} + +const STRENGTH_VARIANTS = [ + { label: "strength-5", cls: "glass glass-strength-5" }, + { label: "strength-10", cls: "glass glass-strength-10" }, + { label: "strength-20", cls: "glass glass-strength-20" }, + { label: "strength-30", cls: "glass glass-strength-30" }, + { label: "strength-40", cls: "glass glass-strength-40" }, + { label: "strength-50", cls: "glass glass-strength-50" }, +]; + +const CHROMATIC_VARIANTS = [ + { label: "chromatic-5", cls: "glass glass-chromatic-5" }, + { label: "chromatic-10", cls: "glass glass-chromatic-10" }, + { label: "chromatic-20", cls: "glass glass-chromatic-20" }, + { label: "chromatic-30", cls: "glass glass-chromatic-30" }, + { label: "chromatic-40", cls: "glass glass-chromatic-40" }, + { label: "chromatic-50", cls: "glass glass-chromatic-50" }, +]; + +function AllVariantsSection() { + return ( + <Section + id="all-variants" + title="All Variants" + description="Every strength and chromatic level rendered simultaneously." + > + <TestCard + title="6 strength + 6 chromatic = 12 panels" + description="All active at once over the same photo background." + > + <DemoArea> + <div className="space-y-4"> + <div className="grid grid-cols-3 gap-3 md:grid-cols-6"> + {STRENGTH_VARIANTS.map(({ label, cls }) => ( + <GlassPanel key={label} className={cls} label={label} compact /> + ))} + </div> + <div className="grid grid-cols-3 gap-3 md:grid-cols-6"> + {CHROMATIC_VARIANTS.map(({ label, cls }) => ( + <GlassPanel key={label} className={cls} label={label} compact /> + ))} + </div> + </div> + </DemoArea> + </TestCard> + </Section> + ); +} + +function NestedGlassSection() { + const [depth, setDepth] = useState(3); + + return ( + <Section + id="nested-glass" + title="Nested Glass" + description="Glass inside glass. Test compositor stacking and filter composition." + > + <TestCard + title={`${depth} levels deep`} + description="Recursive nesting of glass elements." + > + <div className="mb-4 flex items-center gap-4"> + <label + className="flex items-center gap-2 text-sm font-medium" + htmlFor="nest-depth" + > + Depth + <span className="text-muted-foreground font-mono">{depth}</span> + </label> + <input + id="nest-depth" + type="range" + min="1" + max="5" + value={depth} + onChange={(e) => setDepth(Number(e.target.value))} + className="flex-1" + /> + </div> + <DemoArea> + <NestLevel current={1} max={depth} /> + </DemoArea> + </TestCard> + </Section> + ); +} + +function NestLevel({ current, max }: { current: number; max: number }) { + if (current > max) return null; + return ( + <div className="glass glass-surface rounded-xl p-4"> + <code className="bg-foreground/10 text-muted-foreground mb-2 block w-fit rounded px-1.5 py-0.5 font-mono text-[10px]"> + Level {current} + </code> + {current < max ? ( + <div className="mt-3"> + <NestLevel current={current + 1} max={max} /> + </div> + ) : ( + <p className="text-muted-foreground mt-2 text-sm">Innermost layer</p> + )} + </div> + ); +} + +function ScrollStressSection() { + return ( + <Section + id="scroll-stress" + title="Scroll Stress" + description="50+ glass panels stacked vertically with parallax background. Tests compositor performance during scroll." + > + <TestCard + title="Scroll through glass panels" + description="Each panel uses glass glass-surface over a fixed background." + > + <div + className="relative h-[500px] overflow-y-auto rounded-lg" + style={BG_STYLE} + > + <div className="space-y-3 p-4"> + {Array.from({ length: 60 }).map((_, i) => ( + <div key={i} className="glass glass-surface rounded-xl px-4 py-3"> + <code className="text-muted-foreground font-mono text-[10px]"> + Panel {i + 1} + </code> + </div> + ))} + </div> + </div> + </TestCard> + </Section> + ); +} + +function ResizeBehaviorSection() { + return ( + <Section + id="resize-behavior" + title="Resize Behavior" + description="Drag the resize handle to change element size. Verifies objectBoundingBox filter units scale correctly without JS." + > + <TestCard + title="Resizable glass panel" + description="Use the drag handle at the bottom-right corner." + > + <DemoArea> + <div + className="glass glass-surface glass-strength-30 resize overflow-auto rounded-xl p-6" + style={{ width: 300, height: 200, minWidth: 100, minHeight: 80 }} + > + <code className="bg-foreground/10 text-muted-foreground rounded px-1.5 py-0.5 font-mono text-[10px]"> + Resize me (CSS resize: both) + </code> + <p className="text-muted-foreground mt-3 text-sm"> + The SVG displacement map uses objectBoundingBox units, so it + should scale smoothly as you drag. + </p> + </div> + </DemoArea> + </TestCard> + </Section> + ); +} + +const BLUR_VALUES = [0, 2, 6, 12, 20, 40]; + +function BlurExtremesSection() { + return ( + <Section + id="blur-extremes" + title="Blur Extremes" + description="Glass with extreme blur values. Tests filter chain performance." + > + <TestCard + title="Blur range: 0px to 40px" + description="Higher values push the compositor harder." + > + <DemoArea> + <div className="grid grid-cols-3 gap-4 md:grid-cols-6"> + {BLUR_VALUES.map((blur) => ( + <GlassPanel + key={blur} + className={`glass glass-blur-${blur}`} + label={`blur-${blur}`} + compact + /> + ))} + </div> + </DemoArea> + </TestCard> + </Section> + ); +} + +function ComposabilitySection() { + return ( + <Section + id="composability" + title="Composability" + description="All modifiers stacked on a single element — everything at once." + > + <TestCard + title="Full composition" + description="strength-50 + chromatic + blur-6 + saturation-200 + brightness-130 + surface" + > + <DemoArea> + <GlassPanel + className="glass glass-strength-50 glass-blur-6 glass-saturation-200 glass-brightness-130 glass-surface" + label="glass glass-strength-50 glass-blur-6 glass-saturation-200 glass-brightness-130 glass-surface" + /> + </DemoArea> + <DemoArea> + <GlassPanel + className="glass glass-chromatic-50 glass-blur-6 glass-saturation-200 glass-brightness-130 glass-surface" + label="glass glass-chromatic-50 glass-blur-6 glass-saturation-200 glass-brightness-130 glass-surface" + /> + </DemoArea> + </TestCard> + </Section> + ); +} + +const STRENGTH_STEPS = [5, 10, 20, 30, 40, 50]; + +function InteractiveSection() { + const [count, setCount] = useState(16); + const [strength, setStrength] = useState<number>(20); + const [blur, setBlur] = useState(2); + const [saturation, setSaturation] = useState(120); + const [brightness, setBrightness] = useState(105); + const [chromatic, setChromatic] = useState(false); + + const cols = Math.ceil(Math.sqrt(count)); + + const strengthClass = chromatic + ? `glass-chromatic-${strength}` + : `glass-strength-${strength}`; + const fullClass = `glass ${strengthClass} glass-blur-${blur} glass-saturation-${saturation} glass-brightness-${brightness} glass-surface`; + + // Snap strength to valid discrete values + const snapStrength = useCallback((val: number) => { + return STRENGTH_STEPS.reduce((prev, curr) => + Math.abs(curr - val) < Math.abs(prev - val) ? curr : prev, + ); + }, []); + + return ( + <Section + id="interactive" + title="Interactive" + description="User-controlled parameters. All sliders update in real time." + > + <div className="grid gap-8 lg:grid-cols-2"> + <div className="space-y-5"> + <SliderControl + id="int-count" + label="Elements" + value={count} + min={1} + max={100} + onChange={setCount} + display={`${count}`} + /> + + <SliderControl + id="int-strength" + label={chromatic ? "Chromatic" : "Strength"} + value={strength} + min={5} + max={50} + step={5} + onChange={(v) => setStrength(snapStrength(v))} + display={`${strength}`} + /> + + <SliderControl + id="int-blur" + label="Blur" + value={blur} + min={0} + max={40} + onChange={setBlur} + display={`${blur}px`} + /> + + <SliderControl + id="int-saturation" + label="Saturation" + value={saturation} + min={0} + max={300} + onChange={setSaturation} + display={`${saturation}%`} + /> + + <SliderControl + id="int-brightness" + label="Brightness" + value={brightness} + min={50} + max={200} + onChange={setBrightness} + display={`${brightness}%`} + /> + + <label className="flex items-center gap-2"> + <input + type="checkbox" + checked={chromatic} + onChange={(e) => setChromatic(e.target.checked)} + className="rounded" + /> + <span className="text-sm">Chromatic aberration</span> + </label> + + <div className="bg-muted rounded-lg p-4"> + <p className="text-muted-foreground mb-2 text-xs font-medium"> + Applied Classes: + </p> + <code className="block text-sm break-all">{fullClass}</code> + </div> + + <FpsCounter /> + </div> + + <div> + <DemoArea> + <div + className="grid gap-2" + style={{ + gridTemplateColumns: `repeat(${cols}, minmax(0, 1fr))`, + }} + > + {Array.from({ length: count }).map((_, i) => ( + <div + key={i} + className={cn("aspect-square rounded-lg", fullClass)} + /> + ))} + </div> + </DemoArea> + </div> + </div> + </Section> + ); +} + +function SliderControl({ + id, + label, + value, + min, + max, + step = 1, + onChange, + display, +}: { + id: string; + label: string; + value: number; + min: number; + max: number; + step?: number; + onChange: (v: number) => void; + display: string; +}) { + return ( + <div className="space-y-2"> + <label + className="flex items-center justify-between text-sm font-medium" + htmlFor={id} + > + <span>{label}</span> + <span className="text-muted-foreground font-mono">{display}</span> + </label> + <input + id={id} + type="range" + min={min} + max={max} + step={step} + value={value} + onChange={(e) => onChange(Number(e.target.value))} + className="w-full" + /> + </div> + ); +} diff --git a/apps/docs/app/(demos)/tw-glass/tuner/layout.tsx b/apps/docs/app/(demos)/tw-glass/tuner/layout.tsx new file mode 100644 index 0000000..30f6f48 --- /dev/null +++ b/apps/docs/app/(demos)/tw-glass/tuner/layout.tsx @@ -0,0 +1,27 @@ +import type { Metadata } from "next"; +import type { ReactNode } from "react"; +import { SubProjectLayout } from "@/components/shared/sub-project-layout"; + +export const metadata: Metadata = { + title: "Glass Tuner | tw-glass by assistant-ui", + robots: { + index: false, + follow: true, + }, +}; + +export default function TunerLayout({ + children, +}: { + children: ReactNode; +}): React.ReactElement { + return ( + <SubProjectLayout + name="tw-glass" + githubPath="https://github.com/assistant-ui/assistant-ui/tree/main/packages/tw-glass" + breadcrumbs={[{ label: "tuner", href: "/tw-glass/tuner" }]} + > + {children} + </SubProjectLayout> + ); +} diff --git a/apps/docs/app/(demos)/tw-glass/tuner/page.tsx b/apps/docs/app/(demos)/tw-glass/tuner/page.tsx new file mode 100644 index 0000000..2912e18 --- /dev/null +++ b/apps/docs/app/(demos)/tw-glass/tuner/page.tsx @@ -0,0 +1,800 @@ +"use client"; + +import { useState, useMemo, useCallback, useRef, useEffect } from "react"; +import { + buildDisplacementMapSvg, + encodeSvgUrl, + buildStandardFilter, + buildChromaticFilter, + toDataUri, +} from "../../../../../../packages/tw-glass/scripts/filter-builder.mjs"; +import { PATTERNS, unsplash, PatternPicker } from "../(home)/pattern-picker"; +import { cn } from "@/lib/utils"; + +// ─── Types ───────────────────────────────────────────────────────── + +interface MapParams { + inset: number; + cornerRadius: number; + innerBlur: number; + outerBlur: number; + shape: "rect" | "circle"; +} + +interface FilterParams { + mode: "standard" | "chromatic"; + scale: number; + rRatio: number; + gRatio: number; +} + +type BlendMode = + | "soft-light" + | "screen" + | "overlay" + | "hard-light" + | "color-dodge"; + +interface AppearanceParams { + blur: number; + brightness: number; + saturation: number; + bgOpacity: number; + showSurface: boolean; + highlight: number; + highlightSize: number; + shadow: number; + highlightBlend: BlendMode; + circleScale: number; + sizeVariance: number; + speedMin: number; + speedMax: number; + bubbleCount: number; +} + +// ─── Defaults ────────────────────────────────────────────────────── + +const DEFAULT_MAP: MapParams = { + inset: 6, + cornerRadius: 40, + innerBlur: 0, + outerBlur: 0, + shape: "circle", +}; + +const DEFAULT_FILTER: FilterParams = { + mode: "chromatic", + scale: 0.5, + rRatio: 2.5, + gRatio: 2.0, +}; + +const DEFAULT_APPEARANCE: AppearanceParams = { + blur: 2, + brightness: 1.1, + saturation: 2.0, + bgOpacity: 0, + showSurface: true, + highlight: 0.4, + highlightSize: 42, + shadow: 0.12, + highlightBlend: "overlay", + circleScale: 1.35, + sizeVariance: 1, + speedMin: 15, + speedMax: 19, + bubbleCount: 50, +}; + +// ─── Page ────────────────────────────────────────────────────────── + +export default function GlassTunerPage() { + const [map, setMap] = useState<MapParams>(DEFAULT_MAP); + const [filter, setFilter] = useState<FilterParams>(DEFAULT_FILTER); + const [appearance, setAppearance] = + useState<AppearanceParams>(DEFAULT_APPEARANCE); + const [bgIndex, setBgIndex] = useState(0); + const [copied, setCopied] = useState(false); + + const updateMap = useCallback( + <K extends keyof MapParams>(key: K, value: MapParams[K]) => + setMap((prev) => ({ ...prev, [key]: value })), + [], + ); + const updateFilter = useCallback( + <K extends keyof FilterParams>(key: K, value: FilterParams[K]) => + setFilter((prev) => ({ ...prev, [key]: value })), + [], + ); + const updateAppearance = useCallback( + <K extends keyof AppearanceParams>(key: K, value: AppearanceParams[K]) => + setAppearance((prev) => ({ ...prev, [key]: value })), + [], + ); + + // Rebuild filter SVG whenever map or filter params change + const filterUri = useMemo(() => { + const mapSvg = buildDisplacementMapSvg(map) as string; + const urlEncoded = encodeSvgUrl(mapSvg) as string; + const filterSvg = + filter.mode === "chromatic" + ? (buildChromaticFilter( + urlEncoded, + filter.scale, + filter.rRatio, + filter.gRatio, + ) as string) + : (buildStandardFilter(urlEncoded, filter.scale) as string); + return toDataUri(filterSvg) as string; + }, [map, filter]); + + // Inline style for the preview element + const previewStyle = useMemo<React.CSSProperties>( + () => + ({ + "--tw-glass-filter": filterUri, + "--tw-glass-blur": `${appearance.blur}px`, + "--tw-glass-brightness": appearance.brightness, + "--tw-glass-saturation": appearance.saturation, + "--glass-bg-opacity": appearance.bgOpacity, + }) as React.CSSProperties, + [filterUri, appearance], + ); + + // Compile output CSS + const compiledCss = useMemo(() => { + const lines: string[] = []; + lines.push("@utility my-glass {"); + lines.push(` --tw-glass-filter: ${filterUri};`); + lines.push( + " --tw-backdrop-blur: var(--tw-glass-filter) blur(var(--tw-glass-blur));", + ); + lines.push( + " --tw-backdrop-brightness: brightness(var(--tw-glass-brightness));", + ); + lines.push( + " --tw-backdrop-saturate: saturate(var(--tw-glass-saturation));", + ); + + if (appearance.blur !== DEFAULT_APPEARANCE.blur) { + lines.push(` --tw-glass-blur: ${appearance.blur}px;`); + } + if (appearance.brightness !== DEFAULT_APPEARANCE.brightness) { + lines.push(` --tw-glass-brightness: ${appearance.brightness};`); + } + if (appearance.saturation !== DEFAULT_APPEARANCE.saturation) { + lines.push(` --tw-glass-saturation: ${appearance.saturation};`); + } + if (appearance.bgOpacity !== DEFAULT_APPEARANCE.bgOpacity) { + lines.push(` --glass-bg-opacity: ${appearance.bgOpacity};`); + } + + lines.push("}"); + lines.push(""); + lines.push("@layer components {"); + lines.push(" .my-glass {"); + lines.push( + " -webkit-backdrop-filter: var(--tw-backdrop-blur,) var(--tw-backdrop-brightness,) var(--tw-backdrop-contrast,) var(--tw-backdrop-grayscale,) var(--tw-backdrop-hue-rotate,) var(--tw-backdrop-invert,) var(--tw-backdrop-opacity,) var(--tw-backdrop-saturate,) var(--tw-backdrop-sepia,);", + ); + lines.push( + " backdrop-filter: var(--tw-backdrop-blur,) var(--tw-backdrop-brightness,) var(--tw-backdrop-contrast,) var(--tw-backdrop-grayscale,) var(--tw-backdrop-hue-rotate,) var(--tw-backdrop-invert,) var(--tw-backdrop-opacity,) var(--tw-backdrop-saturate,) var(--tw-backdrop-sepia,);", + ); + lines.push(" }"); + lines.push("}"); + return lines.join("\n"); + }, [filterUri, appearance]); + + const handleCopy = useCallback(() => { + navigator.clipboard.writeText(compiledCss); + setCopied(true); + setTimeout(() => setCopied(false), 2000); + }, [compiledCss]); + + const handleReset = useCallback(() => { + setMap(DEFAULT_MAP); + setFilter(DEFAULT_FILTER); + setAppearance(DEFAULT_APPEARANCE); + }, []); + + const surfaceCls = appearance.showSurface ? "glass-surface" : ""; + + const bgUrl = unsplash(PATTERNS[bgIndex]!.id); + + return ( + <div className="bg-background min-h-screen"> + <div className="mx-auto max-w-7xl px-4 py-12"> + <header className="mb-8 flex items-center justify-between"> + <div className="space-y-2"> + <h1 className="text-3xl font-bold">Glass Tuner</h1> + <p className="text-muted-foreground text-sm"> + Tweak every glass parameter live, then copy the compiled CSS. + </p> + </div> + <button + type="button" + onClick={handleReset} + className="text-muted-foreground hover:bg-muted hover:text-foreground rounded-lg border px-3 py-1.5 text-sm transition-colors" + > + Reset + </button> + </header> + + {/* Controls + Preview */} + <div className="grid gap-8 lg:grid-cols-[320px_1fr]"> + {/* Left: Controls */} + <div className="space-y-6"> + <ControlGroup title="Displacement Map"> + <div className="flex gap-3"> + <ModeButton + label="Rect" + active={map.shape === "rect"} + onClick={() => updateMap("shape", "rect")} + /> + <ModeButton + label="Circle" + active={map.shape === "circle"} + onClick={() => updateMap("shape", "circle")} + /> + </div> + <Slider + label="Inset" + value={map.inset} + min={0} + max={45} + step={1} + display={`${map.inset}`} + onChange={(v) => updateMap("inset", v)} + /> + {map.shape === "rect" && ( + <Slider + label="Corner radius" + value={map.cornerRadius} + min={0} + max={40} + step={1} + display={`${map.cornerRadius}`} + onChange={(v) => updateMap("cornerRadius", v)} + /> + )} + <Slider + label="Inner blur" + value={map.innerBlur} + min={0} + max={30} + step={0.5} + display={`${map.innerBlur}`} + onChange={(v) => updateMap("innerBlur", v)} + /> + <Slider + label="Outer blur" + value={map.outerBlur} + min={0} + max={15} + step={0.1} + display={`${map.outerBlur.toFixed(1)}`} + onChange={(v) => updateMap("outerBlur", v)} + /> + </ControlGroup> + + <ControlGroup title="Filter"> + <div className="flex gap-3"> + <ModeButton + label="Standard" + active={filter.mode === "standard"} + onClick={() => updateFilter("mode", "standard")} + /> + <ModeButton + label="Chromatic" + active={filter.mode === "chromatic"} + onClick={() => updateFilter("mode", "chromatic")} + /> + </div> + <Slider + label="Scale" + value={filter.scale} + min={0.01} + max={0.5} + step={0.01} + display={filter.scale.toFixed(2)} + onChange={(v) => updateFilter("scale", v)} + /> + {filter.mode === "chromatic" && ( + <> + <Slider + label="R ratio" + value={filter.rRatio} + min={1.0} + max={2.5} + step={0.1} + display={filter.rRatio.toFixed(1)} + onChange={(v) => updateFilter("rRatio", v)} + /> + <Slider + label="G ratio" + value={filter.gRatio} + min={1.0} + max={2.0} + step={0.1} + display={filter.gRatio.toFixed(1)} + onChange={(v) => updateFilter("gRatio", v)} + /> + </> + )} + </ControlGroup> + + <ControlGroup title="Appearance"> + <Slider + label="Blur" + value={appearance.blur} + min={0} + max={20} + step={1} + display={`${appearance.blur}px`} + onChange={(v) => updateAppearance("blur", v)} + /> + <Slider + label="Brightness" + value={appearance.brightness} + min={0.5} + max={2.0} + step={0.05} + display={appearance.brightness.toFixed(2)} + onChange={(v) => updateAppearance("brightness", v)} + /> + <Slider + label="Saturation" + value={appearance.saturation} + min={0} + max={3.0} + step={0.1} + display={appearance.saturation.toFixed(1)} + onChange={(v) => updateAppearance("saturation", v)} + /> + <Slider + label="BG opacity" + value={appearance.bgOpacity} + min={0} + max={0.5} + step={0.01} + display={appearance.bgOpacity.toFixed(2)} + onChange={(v) => updateAppearance("bgOpacity", v)} + /> + <Slider + label="Highlight" + value={appearance.highlight} + min={0} + max={0.8} + step={0.05} + display={appearance.highlight.toFixed(2)} + onChange={(v) => updateAppearance("highlight", v)} + /> + <Slider + label="Highlight size" + value={appearance.highlightSize} + min={10} + max={80} + step={1} + display={`${appearance.highlightSize}%`} + onChange={(v) => updateAppearance("highlightSize", v)} + /> + <BlendSelect + value={appearance.highlightBlend} + onChange={(v) => updateAppearance("highlightBlend", v)} + /> + <Slider + label="Shadow" + value={appearance.shadow} + min={0} + max={0.5} + step={0.02} + display={appearance.shadow.toFixed(2)} + onChange={(v) => updateAppearance("shadow", v)} + /> + <Slider + label="Circle scale" + value={appearance.circleScale} + min={0.3} + max={2.5} + step={0.05} + display={`${appearance.circleScale.toFixed(2)}x`} + onChange={(v) => updateAppearance("circleScale", v)} + /> + <Slider + label="Size variance" + value={appearance.sizeVariance} + min={0} + max={1} + step={0.05} + display={appearance.sizeVariance.toFixed(2)} + onChange={(v) => updateAppearance("sizeVariance", v)} + /> + <Slider + label="Speed min" + value={appearance.speedMin} + min={3} + max={40} + step={1} + display={`${appearance.speedMin}s`} + onChange={(v) => updateAppearance("speedMin", v)} + /> + <Slider + label="Speed max" + value={appearance.speedMax} + min={5} + max={60} + step={1} + display={`${appearance.speedMax}s`} + onChange={(v) => updateAppearance("speedMax", v)} + /> + <Slider + label="Bubble count" + value={appearance.bubbleCount} + min={1} + max={50} + step={1} + display={`${appearance.bubbleCount}`} + onChange={(v) => updateAppearance("bubbleCount", v)} + /> + <label className="flex items-center gap-2"> + <input + type="checkbox" + checked={appearance.showSurface} + onChange={(e) => + updateAppearance("showSurface", e.target.checked) + } + className="rounded" + /> + <span className="text-sm">Surface styling</span> + </label> + </ControlGroup> + </div> + + {/* Right: Live Preview */} + <div className="lg:sticky lg:top-16 lg:h-[calc(100vh-5rem)] lg:self-start"> + <SpringChain + bgUrl={bgUrl} + previewStyle={previewStyle} + surfaceCls={surfaceCls} + highlight={appearance.highlight} + highlightSize={appearance.highlightSize} + shadow={appearance.shadow} + highlightBlend={appearance.highlightBlend} + circleScale={appearance.circleScale} + sizeVariance={appearance.sizeVariance} + speedMin={appearance.speedMin} + speedMax={appearance.speedMax} + bubbleCount={appearance.bubbleCount} + /> + </div> + </div> + + {/* Compiled CSS Output */} + <div className="mt-8"> + <div className="bg-muted/50 relative overflow-hidden rounded-xl border"> + <div className="flex items-center justify-between border-b px-4 py-2"> + <span className="text-muted-foreground text-xs font-medium tracking-wide uppercase"> + Compiled CSS + </span> + <button + type="button" + onClick={handleCopy} + className="bg-foreground/10 hover:bg-foreground/20 rounded-md px-3 py-1 text-sm transition-colors" + > + {copied ? "Copied!" : "Copy"} + </button> + </div> + <pre className="overflow-x-auto p-4 font-mono text-sm leading-relaxed"> + <code>{compiledCss}</code> + </pre> + </div> + </div> + </div> + + <PatternPicker active={bgIndex} onChange={setBgIndex} /> + </div> + ); +} + +// ─── Floating Bubbles ───────────────────────────────────────────── + +const MIN_SIZE = 36; +const MAX_SIZE = 120; + +// Stable per-bubble randomness seeded by index +function seededRandom(i: number, salt: number) { + const x = Math.sin(i * 9301 + salt * 4973) * 49297; + return x - Math.floor(x); +} + +interface BubbleDef { + size: number; + xPct: number; // horizontal position as % of container width + duration: number; // animation duration in seconds + delay: number; // initial delay (negative = starts mid-animation) +} + +function computeBubbles( + circleScale: number, + sizeVariance: number, + speedMin: number, + speedMax: number, + count: number, +): BubbleDef[] { + return Array.from({ length: count }, (_, i) => { + const t = seededRandom(i, 0); + const mid = (MIN_SIZE + MAX_SIZE) / 2; + const half = ((MAX_SIZE - MIN_SIZE) / 2) * sizeVariance; + const base = mid + (t - 0.5) * 2 * half; + const size = Math.round(base * circleScale); + const xPct = seededRandom(i, 1) * 100; + const effectiveMax = Math.max(speedMin, speedMax); + const duration = speedMin + seededRandom(i, 2) * (effectiveMax - speedMin); + const delay = -(seededRandom(i, 3) * duration); // stagger start + return { size, xPct, duration, delay }; + }); +} + +// Spherical shading — primary highlight from top-left +function sphereHighlight(highlight: number, size: number) { + return `radial-gradient(circle at 30% 25%, rgba(255,255,255,${highlight}) 0%, rgba(255,255,255,0) ${size}%, rgba(0,0,0,${(highlight * 0.2).toFixed(3)}) 100%)`; +} + +// Secondary rim-light from bottom-right (simulates environment bounce) +function sphereRimLight(highlight: number) { + const rim = highlight * 0.5; + return `radial-gradient(circle at 75% 80%, rgba(255,255,255,${rim.toFixed(3)}) 0%, rgba(255,255,255,0) 45%)`; +} +function sphereShadow(shadow: number) { + return `radial-gradient(ellipse 70% 70% at 75% 80%, rgba(0,0,0,${shadow}) 0%, rgba(0,0,0,${(shadow * 0.33).toFixed(2)}) 35%, transparent 65%)`; +} + +function SpringChain({ + bgUrl, + previewStyle, + surfaceCls, + highlight, + highlightSize, + shadow, + highlightBlend, + circleScale, + sizeVariance, + speedMin, + speedMax, + bubbleCount, +}: { + bgUrl: string; + previewStyle: React.CSSProperties; + surfaceCls: string; + highlight: number; + highlightSize: number; + shadow: number; + highlightBlend: BlendMode; + circleScale: number; + sizeVariance: number; + speedMin: number; + speedMax: number; + bubbleCount: number; +}) { + const bubbles = useMemo( + () => + computeBubbles( + circleScale, + sizeVariance, + speedMin, + speedMax, + bubbleCount, + ), + [circleScale, sizeVariance, speedMin, speedMax, bubbleCount], + ); + + const highlightBg = useMemo( + () => sphereHighlight(highlight, highlightSize), + [highlight, highlightSize], + ); + const shadowBg = useMemo(() => sphereShadow(shadow), [shadow]); + const rimBg = useMemo(() => sphereRimLight(highlight), [highlight]); + + // Crossfade background layers + const prevBgRef = useRef(bgUrl); + const [bgLayers, setBgLayers] = useState<{ url: string; key: number }[]>([ + { url: bgUrl, key: 0 }, + ]); + const keyRef = useRef(0); + + useEffect(() => { + if (bgUrl === prevBgRef.current) return; + prevBgRef.current = bgUrl; + keyRef.current += 1; + const newKey = keyRef.current; + setBgLayers((prev) => [...prev, { url: bgUrl, key: newKey }]); + // Remove old layers after transition completes + const timeout = setTimeout(() => { + setBgLayers((prev) => prev.filter((l) => l.key === newKey)); + }, 800); + return () => clearTimeout(timeout); + }, [bgUrl]); + + return ( + <div + className="relative h-full min-h-[400px] overflow-hidden rounded-2xl" + style={{ containerType: "size" }} + > + {/* Background layers — crossfade */} + {bgLayers.map((layer) => ( + <div + key={layer.key} + className="pointer-events-none absolute inset-0 animate-[fade-in_0.7s_ease-in-out_forwards]" + style={{ + backgroundImage: layer.url, + backgroundSize: "cover", + backgroundPosition: "center", + }} + /> + ))} + + {/* Floating bubbles */} + {bubbles.map((b, i) => ( + <div + key={i} + className={cn( + "glass pointer-events-none absolute rounded-full", + surfaceCls, + )} + style={{ + ...previewStyle, + isolation: "isolate", + width: b.size, + height: b.size, + left: `calc(${b.xPct}% - ${b.size / 2}px)`, + top: "100%", + animation: `float-up ${b.duration}s linear ${b.delay}s infinite`, + }} + > + <div + className="pointer-events-none absolute inset-0 rounded-full" + style={{ background: highlightBg, mixBlendMode: highlightBlend }} + /> + <div + className="pointer-events-none absolute inset-0 rounded-full" + style={{ background: rimBg, mixBlendMode: "screen" }} + /> + <div + className="pointer-events-none absolute inset-0 rounded-full" + style={{ background: shadowBg, mixBlendMode: "multiply" }} + /> + </div> + ))} + + {/* Keyframes — travels from bottom of container to above it */} + <style>{` + @keyframes float-up { + from { transform: translateY(0); } + to { transform: translateY(-250cqh); } + } + @keyframes fade-in { + from { opacity: 0; } + to { opacity: 1; } + } + `}</style> + </div> + ); +} + +// ─── Shared Components ───────────────────────────────────────────── + +function ControlGroup({ + title, + children, +}: { + title: string; + children: React.ReactNode; +}) { + return ( + <div className="space-y-3 rounded-xl border p-4"> + <h3 className="text-muted-foreground text-xs font-semibold tracking-wide uppercase"> + {title} + </h3> + <div className="space-y-3">{children}</div> + </div> + ); +} + +function Slider({ + label, + value, + min, + max, + step, + display, + onChange, +}: { + label: string; + value: number; + min: number; + max: number; + step: number; + display: string; + onChange: (v: number) => void; +}) { + return ( + <div className="space-y-1"> + <div className="flex items-center justify-between text-sm"> + <span>{label}</span> + <span className="text-muted-foreground font-mono">{display}</span> + </div> + <input + type="range" + min={min} + max={max} + step={step} + value={value} + onChange={(e) => onChange(Number(e.target.value))} + className="w-full" + /> + </div> + ); +} + +function ModeButton({ + label, + active, + onClick, +}: { + label: string; + active: boolean; + onClick: () => void; +}) { + return ( + <button + type="button" + onClick={onClick} + className={cn("flex-1 rounded-lg px-3 py-1.5 text-sm transition-colors", { + "bg-foreground text-background": active, + "bg-muted text-muted-foreground hover:text-foreground": !active, + })} + > + {label} + </button> + ); +} + +const BLEND_MODES: BlendMode[] = [ + "soft-light", + "screen", + "overlay", + "hard-light", + "color-dodge", +]; + +function BlendSelect({ + value, + onChange, +}: { + value: BlendMode; + onChange: (v: BlendMode) => void; +}) { + return ( + <div className="space-y-1"> + <div className="flex items-center justify-between text-sm"> + <span>Highlight blend</span> + <span className="text-muted-foreground font-mono">{value}</span> + </div> + <div className="flex flex-wrap gap-1.5"> + {BLEND_MODES.map((mode) => ( + <button + key={mode} + type="button" + onClick={() => onChange(mode)} + className={cn("rounded-md px-2 py-1 text-xs transition-colors", { + "bg-foreground text-background": mode === value, + "bg-muted text-muted-foreground hover:text-foreground": + mode !== value, + })} + > + {mode} + </button> + ))} + </div> + </div> + ); +} diff --git a/apps/docs/app/(demos)/tw-shimmer/(home)/layout.tsx b/apps/docs/app/(demos)/tw-shimmer/(home)/layout.tsx new file mode 100644 index 0000000..2bced3e --- /dev/null +++ b/apps/docs/app/(demos)/tw-shimmer/(home)/layout.tsx @@ -0,0 +1,24 @@ +import type { ReactNode } from "react"; +import { SubProjectLayout } from "@/components/shared/sub-project-layout"; +import type { Metadata } from "next"; + +export const metadata: Metadata = { + title: "tw-shimmer by assistant-ui", + description: + "A zero-dependency Tailwind CSS v4 plugin for beautiful shimmer effects. Fully customizable, performant, and easy to use.", +}; + +export default function TwShimmerHomeLayout({ + children, +}: { + children: ReactNode; +}): React.ReactElement { + return ( + <SubProjectLayout + name="tw-shimmer" + githubPath="https://github.com/assistant-ui/assistant-ui/tree/main/packages/tw-shimmer" + > + {children} + </SubProjectLayout> + ); +} diff --git a/apps/docs/app/(demos)/tw-shimmer/(home)/page.tsx b/apps/docs/app/(demos)/tw-shimmer/(home)/page.tsx new file mode 100644 index 0000000..08786e9 --- /dev/null +++ b/apps/docs/app/(demos)/tw-shimmer/(home)/page.tsx @@ -0,0 +1,665 @@ +"use client"; + +import { useState, useCallback } from "react"; +import { Copy, Check, FileCode, Sparkle } from "lucide-react"; +import { Button } from "@/components/ui/button"; +import { SyntaxHighlighter } from "@/components/assistant-ui/shiki-highlighter"; +import { + transformerMetaHighlight, + transformerMetaWordHighlight, +} from "@shikijs/transformers"; + +const HIGHLIGHT_STYLES = ` + .highlighted { + background: rgba(59, 130, 246, 0.15); + display: block; + } + .dark .highlighted { + background: rgba(147, 197, 253, 0.25); + } + .highlighted-word { + background: rgba(59, 130, 246, 0.2); + color: rgb(30, 58, 138); + padding: 0 0.125rem; + border-radius: 0.125rem; + font-style: normal; + font-weight: inherit; + } + .dark .highlighted-word { + background: rgba(147, 197, 253, 0.3); + color: rgb(165, 180, 252); + } +`; + +function HighlightStyles() { + return ( + <style jsx global> + {HIGHLIGHT_STYLES} + </style> + ); +} + +export default function TwShimmerPage() { + const [copied, setCopied] = useState(false); + + const copyToClipboard = useCallback(async (text: string) => { + if (typeof navigator === "undefined" || !navigator.clipboard) return; + try { + await navigator.clipboard.writeText(text); + setCopied(true); + setTimeout(() => setCopied(false), 2000); + } catch {} + }, []); + + return ( + <div className="container mx-auto max-w-7xl space-y-16 px-4 py-12"> + <HighlightStyles /> + <div className="mx-auto flex w-fit flex-col items-center space-y-6 text-center"> + <div className="bg-border flex cursor-default rounded-full p-px"> + <div className="bg-background flex items-center gap-2 rounded-full px-4 py-1.5 text-sm"> + <Sparkle className="size-4 opacity-50" /> + <span className="text-foreground/60">Tailwind CSS v4 Plugin</span> + </div> + </div> + + <div className="flex flex-col gap-5"> + <div className="pointer-events-none text-5xl font-bold tracking-tight select-none lg:text-7xl"> + <h1 className="shimmer text-foreground/50 inline">tw-shimmer</h1> + </div> + + <p className="text-muted-foreground max-w-[520px] text-lg font-light text-balance"> + Zero-dependency CSS-only shimmer. Simple, beautiful, and + lightweight. + </p> + </div> + </div> + + <div id="installation" className="space-y-8"> + <div className="text-center"> + <h2 className="text-3xl font-medium">Installation</h2> + </div> + + <div className="mx-auto max-w-3xl space-y-6"> + <Box> + <BoxContent> + <div className="flex items-center justify-between"> + <code className="text-sm">npm install tw-shimmer</code> + <Button + size="sm" + variant="ghost" + onClick={() => copyToClipboard("npm install tw-shimmer")} + > + {copied ? ( + <Check className="size-4" /> + ) : ( + <Copy className="size-4" /> + )} + </Button> + </div> + </BoxContent> + </Box> + + <Box> + <BoxCodeHeader fileName="styles/globals.css" /> + <BoxCode> + <CodeBlock + language="css" + code={`@import "tailwindcss"; +@import "tw-shimmer";`} + highlight="tw-shimmer" + highlightMode="line" + /> + </BoxCode> + </Box> + </div> + </div> + + <div className="space-y-8"> + <div className="text-center"> + <h2 className="mb-2 text-3xl font-medium">Text Shimmer</h2> + <p className="text-muted-foreground text-xl"> + Add shimmer effects to text elements. + </p> + </div> + + <div className="mx-auto max-w-3xl space-y-6"> + <div className="border border-dashed border-blue-500/20 bg-blue-500/5 p-4 text-sm"> + <p className="mb-1 font-semibold">💡 Important</p> + <p className="text-muted-foreground"> + You won't see the shimmer if your text color is{" "} + <span className="dark:hidden">black</span> + <span className="hidden dark:inline">white</span>. Set a{" "} + <span className="dark:hidden">lighter</span> + <span className="hidden dark:inline">darker</span> or + semi-transparent text color. + </p> + </div> + + <Box> + <BoxTitle + title="shimmer" + description="Base utility for shimmer effect. Requires text color to be visible." + /> + <BoxCode> + <CodeBlock + language="html" + code='<span class="shimmer text-foreground/60">Shimmer Effect</span>' + highlight="shimmer" + highlightMode="text" + /> + </BoxCode> + <BoxContent> + <span className="shimmer text-foreground/80 dark:text-foreground/60 text-xl font-semibold"> + Shimmer Effect + </span> + </BoxContent> + </Box> + + <Box> + <BoxTitle + title="shimmer-invert" + description={ + <> + Fades text <span className="dark:hidden">lighter</span> + <span className="hidden dark:inline">darker</span> instead. + Allows you to use <span className="dark:hidden">darker</span> + <span className="hidden dark:inline">brighter</span> text + colors and still get a stark shimmer effect. + </> + } + /> + <BoxCode> + <CodeBlock + language="html" + code='<span class="shimmer shimmer-invert text-foreground/60">Shimmer</span>' + highlight="shimmer-invert" + highlightMode="text" + /> + </BoxCode> + <BoxContent> + <span className="shimmer shimmer-invert text-foreground/60 dark:text-foreground/80 text-xl font-semibold"> + Shimmer Effect + </span> + </BoxContent> + </Box> + + <Box> + <BoxTitle + title="Automatic Color" + description="Shimmer automatically picks a color based on your text color." + /> + <BoxCode> + <CodeBlock + language="html" + code='<span class="shimmer text-blue-600">Blue Shimmer</span>' + highlight="text-blue" + highlightMode="text" + /> + </BoxCode> + <BoxContent> + <span className="shimmer text-xl font-semibold text-blue-600"> + Blue Shimmer + </span> + </BoxContent> + </Box> + + <Box> + <BoxTitle + title="shimmer-color-{color}" + description="Override the automatic color with a custom shimmer color." + /> + <BoxCode> + <CodeBlock + language="html" + code='<span class="shimmer shimmer-color-orange-500 text-foreground/40">Orange Shimmer</span>' + highlight="shimmer-color" + highlightMode="text" + /> + </BoxCode> + <BoxContent> + <span className="shimmer shimmer-color-orange-500 text-foreground/40 text-xl font-semibold"> + Orange Shimmer + </span> + </BoxContent> + </Box> + + <Box> + <BoxTitle + title="shimmer-spread-{value}" + description="Width of the shimmer highlight in pixels. Default: 120px." + /> + <BoxCode> + <CodeBlock + language="html" + code='<span class="shimmer shimmer-spread-200 text-foreground/40">Wide Shimmer</span>' + highlight="shimmer-spread" + highlightMode="text" + /> + </BoxCode> + <BoxContent> + <div className="flex flex-col items-start gap-2"> + <span className="shimmer shimmer-spread-60 shimmer-duration-5000 text-foreground/40 text-xl font-semibold"> + Shimmer Effect (60px) + </span> + <span className="shimmer shimmer-duration-5000 text-foreground/40 text-xl font-semibold"> + Shimmer Effect (120px) + </span> + <span className="shimmer shimmer-spread-200 shimmer-duration-5000 text-foreground/40 text-xl font-semibold"> + Shimmer Effect (200px) + </span> + </div> + </BoxContent> + </Box> + + <Box> + <BoxTitle + title="shimmer-angle-{value}" + description="Angle of the shimmer sweep in degrees. Default: 15deg (resulting in a 105deg gradient). Higher values create a more diagonal sweep." + /> + <BoxCode> + <CodeBlock + language="html" + code='<span class="shimmer shimmer-angle-75 text-foreground/40">Diagonal Shimmer</span>' + highlight="shimmer-angle" + highlightMode="text" + /> + </BoxCode> + <BoxContent> + <div className="flex flex-col items-start gap-2"> + <span className="shimmer shimmer-angle-0 shimmer-duration-5000 text-foreground/40 text-xl font-semibold"> + Shimmer Effect (0deg) + </span> + <span className="shimmer shimmer-duration-5000 text-foreground/40 text-xl font-semibold"> + Shimmer Effect (15deg) + </span> + <span className="shimmer shimmer-angle-75 shimmer-duration-5000 text-foreground/40 text-xl font-semibold"> + Shimmer Effect (75deg) + </span> + </div> + </BoxContent> + </Box> + </div> + </div> + + <div className="space-y-8"> + <div className="text-center"> + <h2 className="mb-2 text-3xl font-medium">Animation Timing</h2> + <p className="text-muted-foreground text-xl"> + Control how long the shimmer animation takes. + </p> + </div> + + <div className="mx-auto max-w-3xl space-y-6"> + <div className="rounded-md border border-dashed p-6"> + <p className="text-muted-foreground mb-4 text-center text-sm"> + Total animation cycle + </p> + <div className="flex items-center justify-center gap-2"> + <div className="flex flex-col items-center gap-1"> + <div className="rounded bg-blue-500/20 px-4 py-2 font-mono text-sm"> + shimmer animation + </div> + <span className="text-muted-foreground text-xs"> + (duration or speed) + </span> + </div> + <span className="text-muted-foreground font-mono text-xl">+</span> + <div className="flex flex-col items-center gap-1"> + <div className="rounded bg-orange-500/20 px-4 py-2 font-mono text-sm"> + repeat delay + </div> + <span className="text-muted-foreground text-xs"> + (default: 1000ms) + </span> + </div> + </div> + </div> + + <div className="grid gap-6 md:grid-cols-2"> + <div className="space-y-2"> + <p className="text-muted-foreground text-sm font-medium"> + Option A + </p> + <Box> + <div className="bg-muted/40 space-y-2 p-6"> + <h3 className="font-mono text-lg"> + shimmer-duration-{"{ms}"} + </h3> + <p className="text-muted-foreground max-w-[70ch] text-sm"> + Fixed duration in milliseconds. Larger elements will appear + to move faster since they cover more distance in the same + time. + </p> + </div> + <BoxCode> + <CodeBlock + language="html" + code='<span class="shimmer shimmer-duration-2000">...</span>' + highlight="shimmer-duration" + highlightMode="text" + /> + </BoxCode> + <BoxContent> + <div className="flex flex-col items-start gap-2"> + <span className="shimmer shimmer-duration-2000 text-foreground/40 text-xl font-semibold"> + Short + </span> + <span className="shimmer shimmer-duration-2000 text-foreground/40 text-xl font-semibold"> + Medium Length Text + </span> + <span className="shimmer shimmer-duration-2000 text-foreground/40 text-xl font-semibold"> + This Is A Much Longer Text + </span> + </div> + <p className="text-muted-foreground mt-4 text-xs"> + Same duration, different perceived speeds + </p> + </BoxContent> + </Box> + </div> + + <div className="space-y-2"> + <p className="text-muted-foreground text-sm font-medium"> + Option B + </p> + <Box> + <div className="bg-muted/40 space-y-2 p-6"> + <h3 className="font-mono text-lg"> + shimmer-speed-{"{px/s}"} + </h3> + <p className="text-muted-foreground max-w-[70ch] text-sm"> + Speed in pixels per second. All elements sweep at the same + visual speed regardless of width. Default: 200px/s. + </p> + </div> + <BoxCode> + <CodeBlock + language="html" + code='<span class="shimmer shimmer-speed-200">...</span>' + highlight="shimmer-speed" + highlightMode="text" + /> + </BoxCode> + <BoxContent> + <div className="flex flex-col items-start gap-2"> + <span className="shimmer shimmer-speed-200 shimmer-repeat-delay-1975 text-foreground/40 text-xl font-semibold [--shimmer-track-width:50px]"> + Short + </span> + <span className="shimmer shimmer-speed-200 shimmer-repeat-delay-1300 text-foreground/40 text-xl font-semibold [--shimmer-track-width:185px]"> + Medium Length Text + </span> + <span className="shimmer shimmer-speed-200 text-foreground/40 text-xl font-semibold [--shimmer-track-width:245px]"> + This Is A Much Longer Text + </span> + </div> + <p className="text-muted-foreground mt-4 text-xs"> + Same speed, different durations + </p> + </BoxContent> + </Box> + </div> + </div> + + <div className="grid gap-6 md:grid-cols-2"> + <div /> + <div className="border border-dashed border-amber-500/20 bg-amber-500/5 p-4 text-sm"> + <p className="mb-1 font-semibold"> + Width Required for shimmer-speed + </p> + <p className="text-muted-foreground"> + CSS can't read text width, so you need to measure it with + JavaScript and pass it in. Without it, defaults to 200px. + </p> + <code className="bg-muted mt-2 block rounded px-2 py-1 text-xs"> + {'style={{ "--shimmer-track-width": width }}'} + </code> + </div> + </div> + + <Box> + <BoxTitle + title="shimmer-repeat-delay-{value}" + description="Delay between animation cycles in milliseconds. Default: 1000ms. Use 0 for continuous shimmer without pause." + /> + <BoxCode> + <CodeBlock + language="html" + code='<span class="shimmer shimmer-repeat-delay-0">No Delay</span>' + highlight="shimmer-repeat-delay" + highlightMode="text" + /> + </BoxCode> + <BoxContent> + <div className="flex flex-col items-start gap-2 [--shimmer-track-height:28px]"> + <span className="shimmer shimmer-repeat-delay-0 shimmer-duration-3000 text-foreground/40 text-xl font-semibold"> + Shimmer Effect (0ms) + </span> + <span className="shimmer shimmer-duration-3000 text-foreground/40 text-xl font-semibold"> + Shimmer Effect (1000ms) + </span> + <span className="shimmer shimmer-repeat-delay-2000 shimmer-duration-3000 text-foreground/40 text-xl font-semibold"> + Shimmer Effect (2000ms) + </span> + </div> + </BoxContent> + </Box> + </div> + </div> + + <div className="space-y-8"> + <div className="text-center"> + <h2 className="mb-2 text-3xl font-medium">Background Shimmer</h2> + <p className="text-muted-foreground text-xl"> + Add shimmer to skeleton components and non-text elements. + </p> + </div> + + <div className="mx-auto max-w-3xl space-y-6"> + <Box> + <BoxTitle + title="shimmer shimmer-bg" + description="Background shimmer for skeleton loaders. Apply both shimmer and shimmer-bg classes." + /> + <BoxCode> + <CodeBlock + language="html" + code='<div class="shimmer shimmer-bg h-4 w-64 rounded bg-muted" />' + highlight="shimmer-bg" + highlightMode="text" + /> + </BoxCode> + <BoxContent> + <div className="shimmer-container"> + <div className="shimmer shimmer-bg bg-muted h-4 w-64 rounded" /> + </div> + </BoxContent> + </Box> + + <Box> + <BoxTitle + title="Skeleton Card Example" + description="Wrap skeleton elements in shimmer-container for automatic width detection. Use --shimmer-x and --shimmer-y to coordinate timing across the diagonal sweep." + /> + <BoxCode> + <CodeBlock + language="html" + code={`<div class="shimmer-container flex gap-3"> + <div class="shimmer shimmer-bg [--shimmer-x:0] [--shimmer-y:0] size-12 rounded-full bg-muted" /> + <div class="flex-1 space-y-1"> + <div class="shimmer shimmer-bg [--shimmer-x:60] [--shimmer-y:0] h-4 w-1/4 rounded bg-muted" /> + <div class="shimmer shimmer-bg [--shimmer-x:60] [--shimmer-y:20] h-4 w-full rounded bg-muted" /> + <div class="shimmer shimmer-bg [--shimmer-x:60] [--shimmer-y:40] h-4 w-4/5 rounded bg-muted" /> + </div> +</div>`} + highlight={["--shimmer-x", "--shimmer-y"]} + highlightMode="text" + /> + </BoxCode> + <BoxContent> + <div className="grid gap-6 md:grid-cols-2"> + <div className="space-y-2"> + <p className="text-muted-foreground text-sm font-medium"> + Uncoordinated + </p> + <div className="shimmer-container flex gap-3"> + <div className="shimmer shimmer-bg bg-muted size-12 shrink-0 rounded-full" /> + <div className="flex-1 space-y-1"> + <div className="shimmer shimmer-bg bg-muted h-4 w-1/4 rounded" /> + <div className="shimmer shimmer-bg bg-muted h-4 w-full rounded" /> + <div className="shimmer shimmer-bg bg-muted h-4 w-4/5 rounded" /> + </div> + </div> + </div> + <div className="space-y-2"> + <p className="text-muted-foreground text-sm font-medium"> + Synchronized with x/y coordinates + </p> + <div className="shimmer-container flex gap-3"> + <div className="shimmer shimmer-bg bg-muted size-12 shrink-0 rounded-full [--shimmer-x:0] [--shimmer-y:0]" /> + <div className="flex-1 space-y-1"> + <div className="shimmer shimmer-bg bg-muted h-4 w-1/4 rounded [--shimmer-x:60] [--shimmer-y:0]" /> + <div className="shimmer shimmer-bg bg-muted h-4 w-full rounded [--shimmer-x:60] [--shimmer-y:20]" /> + <div className="shimmer shimmer-bg bg-muted h-4 w-4/5 rounded [--shimmer-x:60] [--shimmer-y:40]" /> + </div> + </div> + </div> + </div> + </BoxContent> + </Box> + + <Box> + <BoxTitle + title="Color Customization" + description="Override the shimmer highlight color with shimmer-color-{color}. Default: text color at 20% alpha." + /> + <BoxCode> + <CodeBlock + language="html" + code={`<div class="shimmer shimmer-bg shimmer-color-blue-300/30 ..." /> +<div class="shimmer shimmer-bg shimmer-color-amber-200/30 ..." />`} + highlight="shimmer-color" + highlightMode="text" + /> + </BoxCode> + <BoxContent> + <div className="shimmer-container space-y-1"> + <div className="shimmer shimmer-bg shimmer-color-blue-300/30 bg-muted h-4 w-48 rounded" /> + <div className="shimmer shimmer-bg shimmer-color-amber-200/30 bg-muted h-4 w-48 rounded" /> + </div> + <p className="text-muted-foreground mt-4 mb-2 text-sm font-medium"> + With custom background color + </p> + <div className="shimmer-container space-y-1"> + <div className="shimmer shimmer-bg shimmer-color-sky-300/20 dark:shimmer-color-sky-900 h-4 w-48 rounded bg-sky-200 dark:bg-sky-800" /> + <div className="shimmer shimmer-bg shimmer-color-violet-300/20 dark:shimmer-color-violet-900 h-4 w-48 rounded bg-violet-200 dark:bg-violet-800" /> + </div> + </BoxContent> + </Box> + </div> + </div> + </div> + ); +} + +interface CodeBlockProps { + language: string; + code: string; + highlight?: string | string[]; + highlightMode?: "line" | "text"; +} + +interface BoxTitleProps { + title: string; + description: React.ReactNode; +} + +interface BoxCodeHeaderProps { + fileName: string; +} + +function CodeBlock({ + language, + code, + highlight, + highlightMode = "line", +}: CodeBlockProps) { + let metaProps = {}; + + if (highlight) { + const highlights = Array.isArray(highlight) ? highlight : [highlight]; + + if (highlightMode === "text") { + const patterns = highlights.map((h) => `/${h}/`).join(" "); + metaProps = { meta: { __raw: patterns } }; + } else if (highlightMode === "line") { + const lines = code.split("\n"); + const lineNumbers = lines + .map((line, index) => + highlights.some((h) => line.includes(h)) ? index + 1 : null, + ) + .filter((n): n is number => n !== null); + + if (lineNumbers.length > 0) { + metaProps = { meta: { __raw: `{${lineNumbers.join(",")}}` } }; + } + } + } + + return ( + <SyntaxHighlighter + language={language} + code={code} + {...metaProps} + addDefaultStyles={false} + className="[--padding-left:1.5rem] [&_code]:block [&_pre]:m-0 [&_pre]:rounded-none [&_pre]:bg-transparent! [&_pre]:px-0 [&_pre]:py-4" + transformers={[ + transformerMetaHighlight(), + transformerMetaWordHighlight(), + ]} + components={{ + Pre: ({ className, ...props }: any) => ( + <pre className={className} {...props} /> + ), + Code: ({ className, ...props }: any) => ( + <code className={className} {...props} /> + ), + }} + /> + ); +} + +function Box({ children }: { children: React.ReactNode }) { + return ( + <div className="rounded-md border border-dashed [&>*:not(:last-child)]:border-b [&>*:not(:last-child)]:border-dashed"> + {children} + </div> + ); +} + +function BoxTitle({ title, description }: BoxTitleProps) { + return ( + <div className="bg-muted/40 space-y-2 p-6"> + <h3 className="font-mono text-lg">{title}</h3> + <p className="text-muted-foreground max-w-[70ch] text-sm"> + {description} + </p> + </div> + ); +} + +function BoxContent({ children }: { children: React.ReactNode }) { + return <div className="p-6">{children}</div>; +} + +function BoxCodeHeader({ fileName }: BoxCodeHeaderProps) { + return ( + <div className="flex items-center gap-2 px-6 py-4 font-mono text-sm font-medium"> + <FileCode className="text-muted-foreground size-4" /> + {fileName} + </div> + ); +} + +function BoxCode({ children }: { children: React.ReactNode }) { + return <div className="p-2 text-sm">{children}</div>; +} diff --git a/apps/docs/app/(demos)/tw-shimmer/opengraph-image.tsx b/apps/docs/app/(demos)/tw-shimmer/opengraph-image.tsx new file mode 100644 index 0000000..937e6f1 --- /dev/null +++ b/apps/docs/app/(demos)/tw-shimmer/opengraph-image.tsx @@ -0,0 +1,66 @@ +import { ImageResponse } from "next/og"; +import { readFile } from "node:fs/promises"; +import { join } from "node:path"; +import { OG_SIZE, OgTemplate } from "@/lib/og-template"; + +export const alt = "tw-shimmer"; +export const size = OG_SIZE; +export const contentType = "image/png"; + +export default async function Image() { + const [geistRegular, geistMedium, geistMono, shimmerTextPng] = + await Promise.all([ + readFile(join(process.cwd(), "assets/Geist-Regular.ttf")), + readFile(join(process.cwd(), "assets/Geist-Medium.ttf")), + readFile(join(process.cwd(), "assets/GeistMono-Regular.ttf")), + readFile(join(process.cwd(), "assets/tw-shimmer-text.png"), "base64"), + ]); + + const shimmerTextSrc = `data:image/png;base64,${shimmerTextPng}`; + + return new ImageResponse( + <OgTemplate subtleBranding> + <img + src={shimmerTextSrc} + alt="tw-shimmer" + height={100} + style={{ objectFit: "contain", marginBottom: 20 }} + /> + <span + style={{ + fontSize: 42, + fontWeight: 400, + color: "#a3a3a3", + fontFamily: "Geist", + letterSpacing: "-0.01em", + textAlign: "center", + }} + > + Zero-dependency CSS-only shimmer + </span> + </OgTemplate>, + { + ...size, + fonts: [ + { + name: "Geist", + data: geistRegular, + style: "normal", + weight: 400, + }, + { + name: "Geist", + data: geistMedium, + style: "normal", + weight: 500, + }, + { + name: "GeistMono", + data: geistMono, + style: "normal", + weight: 400, + }, + ], + }, + ); +} diff --git a/apps/docs/app/(demos)/tw-shimmer/spread-test/layout.tsx b/apps/docs/app/(demos)/tw-shimmer/spread-test/layout.tsx new file mode 100644 index 0000000..a3d18d9 --- /dev/null +++ b/apps/docs/app/(demos)/tw-shimmer/spread-test/layout.tsx @@ -0,0 +1,27 @@ +import type { Metadata } from "next"; +import type { ReactNode } from "react"; +import { SubProjectLayout } from "@/components/shared/sub-project-layout"; + +export const metadata: Metadata = { + title: "Spread Test | tw-shimmer by assistant-ui", + robots: { + index: false, + follow: true, + }, +}; + +export default function SpreadTestLayout({ + children, +}: { + children: ReactNode; +}): React.ReactElement { + return ( + <SubProjectLayout + name="tw-shimmer" + githubPath="https://github.com/assistant-ui/assistant-ui/tree/main/packages/tw-shimmer" + breadcrumbs={[{ label: "spread-test", href: "/tw-shimmer/spread-test" }]} + > + {children} + </SubProjectLayout> + ); +} diff --git a/apps/docs/app/(demos)/tw-shimmer/spread-test/page.tsx b/apps/docs/app/(demos)/tw-shimmer/spread-test/page.tsx new file mode 100644 index 0000000..b62e581 --- /dev/null +++ b/apps/docs/app/(demos)/tw-shimmer/spread-test/page.tsx @@ -0,0 +1,1744 @@ +"use client"; + +/** + * COMPREHENSIVE SHIMMER SANDBOX + * For internal testing, tuning, and experimental purposes. + * + * Tests all variations of: + * - shimmer (text shimmer) + * - shimmer-bg (background shimmer) + * - shimmer-container (container-based auto-sizing) + * - Various utility overrides (angle, color, speed, spread, width) + */ + +import { useState, type ReactNode } from "react"; +import { cn } from "@/lib/utils"; + +export default function ShimmerSandboxPage() { + return ( + <div className="bg-background min-h-screen"> + <div className="mx-auto max-w-7xl px-4 py-12"> + {/* Header - full width */} + <header className="mb-12 space-y-4"> + <h1 className="text-4xl font-bold">Shimmer Sandbox</h1> + <p className="text-muted-foreground max-w-2xl"> + Comprehensive testing playground for shimmer text and shimmer-bg + utilities. Use this to tune values, test edge cases, and experiment + with different configurations. + </p> + </header> + + {/* Two-column layout */} + <div className="flex gap-8"> + {/* Sticky sidebar navigation */} + <aside className="hidden w-56 shrink-0 lg:block"> + <TableOfContents /> + </aside> + + {/* Main content */} + <main className="min-w-0 flex-1 space-y-16"> + <TextShimmerSection /> + <BackgroundShimmerSection /> + <ContainerSizeSection /> + <SkeletonPatternsSection /> + <AngleTestSection /> + <ColorTestSection /> + <SpeedTestSection /> + <SpreadTestSection /> + <NestedContainersSection /> + <EdgeCasesSection /> + <InteractivePlaygroundSection /> + </main> + </div> + </div> + </div> + ); +} + +function TableOfContents() { + const sections = [ + { id: "text-shimmer", label: "Text Shimmer" }, + { id: "background-shimmer", label: "Background Shimmer" }, + { id: "container-sizes", label: "Container Sizes" }, + { id: "skeleton-patterns", label: "Skeleton Patterns" }, + { id: "angles", label: "Angles" }, + { id: "colors", label: "Colors" }, + { id: "speeds", label: "Speeds" }, + { id: "spreads", label: "Spreads" }, + { id: "nested-containers", label: "Nested Containers" }, + { id: "edge-cases", label: "Edge Cases" }, + { id: "interactive", label: "Interactive" }, + ]; + + return ( + <nav className="sticky top-24"> + <h2 className="text-muted-foreground mb-3 text-xs font-semibold tracking-wide uppercase"> + Sections + </h2> + <ul className="space-y-1"> + {sections.map(({ id, label }) => ( + <li key={id}> + <a + href={`#${id}`} + className="text-muted-foreground hover:bg-muted hover:text-foreground block rounded-md px-3 py-1.5 text-sm transition-colors" + > + {label} + </a> + </li> + ))} + </ul> + </nav> + ); +} + +function Section({ + id, + title, + description, + children, +}: { + id: string; + title: string; + description?: string; + children: ReactNode; +}) { + return ( + <section id={id} className="scroll-mt-8 space-y-6"> + <div className="space-y-2"> + <h2 className="text-2xl font-bold">{title}</h2> + {description && <p className="text-muted-foreground">{description}</p>} + </div> + {children} + </section> + ); +} + +function TestCard({ + title, + description, + children, +}: { + title: string; + description?: string; + children: ReactNode; +}) { + return ( + <div className="space-y-3 overflow-x-auto rounded-lg border border-dashed p-4"> + <div> + <h3 className="font-semibold">{title}</h3> + {description && ( + <p className="text-muted-foreground text-sm">{description}</p> + )} + </div> + {children} + </div> + ); +} + +function TextShimmerSection() { + return ( + <Section + id="text-shimmer" + title="Text Shimmer" + description="The shimmer class creates an animated gradient effect on text using background-clip." + > + <div className="grid gap-6 md:grid-cols-2"> + {/* Font sizes */} + <TestCard title="Font Sizes" description="Different text sizes"> + <div className="shimmer-container space-y-2"> + <p className="shimmer text-foreground/40 text-xs"> + Extra small text with shimmer + </p> + <p className="shimmer text-foreground/40 text-sm"> + Small text with shimmer effect + </p> + <p className="shimmer text-foreground/40 text-base"> + Base text with shimmer effect + </p> + <p className="shimmer text-foreground/40 text-lg"> + Large text with shimmer + </p> + <p className="shimmer text-foreground/40 text-xl"> + Extra large shimmer + </p> + <p className="shimmer text-foreground/40 text-2xl">2XL shimmer</p> + <p className="shimmer text-foreground/40 text-4xl font-bold"> + 4XL Bold + </p> + </div> + </TestCard> + + {/* Line lengths */} + <TestCard title="Line Lengths" description="Various text widths"> + <div className="shimmer-container space-y-2"> + <p className="shimmer text-foreground/40">Short</p> + <p className="shimmer text-foreground/40">Medium length text</p> + <p className="shimmer text-foreground/40"> + This is a longer line of text that wraps around + </p> + <p className="shimmer text-foreground/40"> + This is an even longer paragraph that demonstrates how the shimmer + effect works across multiple lines of text content. + </p> + </div> + </TestCard> + + {/* Font weights */} + <TestCard title="Font Weights" description="Different weights"> + <div className="shimmer-container space-y-2"> + <p className="shimmer text-foreground/40 text-lg font-thin"> + Thin weight shimmer + </p> + <p className="shimmer text-foreground/40 text-lg font-light"> + Light weight shimmer + </p> + <p className="shimmer text-foreground/40 text-lg font-normal"> + Normal weight shimmer + </p> + <p className="shimmer text-foreground/40 text-lg font-medium"> + Medium weight shimmer + </p> + <p className="shimmer text-foreground/40 text-lg font-semibold"> + Semibold shimmer + </p> + <p className="shimmer text-foreground/40 text-lg font-bold"> + Bold weight shimmer + </p> + <p className="shimmer text-foreground/40 text-lg font-black"> + Black weight shimmer + </p> + </div> + </TestCard> + + {/* Without container */} + <TestCard + title="Without shimmer-container" + description="Uses default width (200px)" + > + <div className="space-y-2"> + <p className="shimmer text-foreground/40 text-sm"> + No container - default width (200) + </p> + <p className="shimmer text-foreground/40 text-base"> + The shimmer will animate at a fixed 200px track width + </p> + <p className="shimmer text-foreground/40 text-lg"> + Longer text still uses 200px + </p> + </div> + </TestCard> + + {/* Mixed content */} + <TestCard title="Mixed Content" description="Headlines and body text"> + <div className="shimmer-container space-y-4"> + <h4 className="shimmer text-foreground/40 text-2xl font-bold"> + Headline with shimmer + </h4> + <p className="shimmer text-foreground/40"> + Body text that follows the headline. This demonstrates how shimmer + works in a typical content layout with multiple elements. + </p> + <p className="shimmer text-foreground/50 text-sm"> + Caption or metadata text + </p> + </div> + </TestCard> + + {/* Inline shimmer */} + <TestCard + title="Inline Shimmer" + description="Shimmer on inline elements" + > + <div className="shimmer-container"> + <p> + This is regular text with{" "} + <span className="shimmer text-foreground/40">inline shimmer</span>{" "} + mixed in. + </p> + <p className="mt-2"> + Multiple{" "} + <span className="shimmer text-foreground/40">inline</span>{" "} + <span className="shimmer text-foreground/40">shimmer</span>{" "} + <span className="shimmer text-foreground/40">elements</span> in + one line. + </p> + </div> + </TestCard> + </div> + </Section> + ); +} + +function BackgroundShimmerSection() { + return ( + <Section + id="background-shimmer" + title="Background Shimmer" + description="The shimmer-bg class creates an animated highlight stripe overlay using ::after pseudo-element." + > + <div className="grid gap-6 md:grid-cols-2"> + {/* Basic shapes */} + <TestCard title="Basic Shapes" description="Different element shapes"> + <div className="shimmer-container space-y-4"> + <div className="shimmer-bg bg-muted h-4 rounded" /> + <div className="shimmer-bg bg-muted h-8 rounded-md" /> + <div className="shimmer-bg bg-muted h-12 rounded-lg" /> + <div className="shimmer-bg bg-muted h-6 rounded-full" /> + <div className="shimmer-bg bg-muted size-16 rounded-full" /> + </div> + </TestCard> + + {/* Widths */} + <TestCard + title="Partial Widths" + description="Elements with various widths" + > + <div className="shimmer-container space-y-2"> + <div className="shimmer-bg bg-muted h-4 w-full rounded" /> + <div className="shimmer-bg bg-muted h-4 w-4/5 rounded" /> + <div className="shimmer-bg bg-muted h-4 w-3/4 rounded" /> + <div className="shimmer-bg bg-muted h-4 w-2/3 rounded" /> + <div className="shimmer-bg bg-muted h-4 w-1/2 rounded" /> + <div className="shimmer-bg bg-muted h-4 w-1/3 rounded" /> + <div className="shimmer-bg bg-muted h-4 w-1/4 rounded" /> + </div> + </TestCard> + + {/* Heights */} + <TestCard title="Heights" description="Different heights"> + <div className="shimmer-container flex items-end gap-2"> + <div className="shimmer-bg bg-muted h-4 w-12 rounded" /> + <div className="shimmer-bg bg-muted h-8 w-12 rounded" /> + <div className="shimmer-bg bg-muted h-12 w-12 rounded" /> + <div className="shimmer-bg bg-muted h-16 w-12 rounded" /> + <div className="shimmer-bg bg-muted h-24 w-12 rounded" /> + <div className="shimmer-bg bg-muted h-32 w-12 rounded" /> + </div> + </TestCard> + + {/* Without container */} + <TestCard + title="Without shimmer-container" + description="Uses default width (800px)" + > + <div className="space-y-2"> + <div className="shimmer-bg bg-muted h-6 w-full rounded" /> + <div className="shimmer-bg bg-muted h-6 w-3/4 rounded" /> + <div className="shimmer-bg bg-muted h-6 w-1/2 rounded" /> + </div> + </TestCard> + + {/* Aspect ratios */} + <TestCard title="Aspect Ratios" description="Common media ratios"> + <div className="shimmer-container space-y-4"> + <div className="shimmer-bg bg-muted aspect-video rounded-lg" /> + <div className="flex gap-4"> + <div className="shimmer-bg bg-muted aspect-square w-20 rounded" /> + <div className="shimmer-bg bg-muted aspect-4/3 w-24 rounded" /> + <div className="shimmer-bg bg-muted aspect-3/4 w-16 rounded" /> + </div> + </div> + </TestCard> + + {/* Grid of items */} + <TestCard title="Grid Layout" description="Multiple items in grid"> + <div className="shimmer-container grid grid-cols-3 gap-2"> + {Array.from({ length: 9 }).map((_, i) => ( + <div + key={i} + className="shimmer-bg bg-muted aspect-square rounded" + /> + ))} + </div> + </TestCard> + </div> + </Section> + ); +} + +const SHIMMER_COLORS = [ + { + name: "Red", + shimmerColor: "oklch(0.637 0.237 25.331)", + textColor: "text-red-500/50", + }, + { + name: "Orange", + shimmerColor: "oklch(0.705 0.191 47.604)", + textColor: "text-orange-500/50", + }, + { + name: "Yellow", + shimmerColor: "oklch(0.852 0.199 91.936)", + textColor: "text-yellow-500/50", + }, + { + name: "Green", + shimmerColor: "oklch(0.723 0.219 149.579)", + textColor: "text-green-500/50", + }, + { + name: "Blue", + shimmerColor: "oklch(0.623 0.214 259.815)", + textColor: "text-blue-500/50", + }, + { + name: "Purple", + shimmerColor: "oklch(0.627 0.265 303.9)", + textColor: "text-purple-500/50", + }, + { + name: "Pink", + shimmerColor: "oklch(0.656 0.241 354.308)", + textColor: "text-pink-500/50", + }, +]; + +const CONTAINER_SIZES = [ + { width: 50, label: "50px" }, + { width: 80, label: "80px" }, + { width: 120, label: "120px" }, + { width: 200, label: "200px" }, + { width: 300, label: "300px" }, + { width: 400, label: "400px" }, + { width: 600, label: "600px" }, + { width: 800, label: "800px" }, + { width: 1000, label: "1000px" }, +]; + +function ContainerSizeSection() { + return ( + <Section + id="container-sizes" + title="Container Sizes" + description="How shimmer adapts to different container widths via shimmer-container." + > + {/* Side by side comparison */} + <TestCard + title="Side-by-Side Comparison" + description="All container widths at a glance" + > + <div className="flex flex-wrap items-end gap-4"> + {CONTAINER_SIZES.map(({ width, label }) => ( + <div key={width} className="flex flex-col items-center gap-2"> + <span className="text-muted-foreground text-xs">{label}</span> + <div + className="shimmer-container" + style={{ width: `${width}px` }} + > + <div className="shimmer-bg bg-muted h-8 w-full rounded" /> + </div> + </div> + ))} + </div> + </TestCard> + + {/* Detailed breakdown */} + <div className="space-y-4"> + <h3 className="text-lg font-semibold">Detailed Breakdown</h3> + <div className="grid gap-4 overflow-x-auto"> + {CONTAINER_SIZES.map(({ width, label }) => ( + <ContainerSizeRow key={width} width={width} label={label} /> + ))} + </div> + </div> + </Section> + ); +} + +function ContainerSizeRow({ width, label }: { width: number; label: string }) { + const trackMax = Math.min(width, 300); + const minSpread = Math.min(200, trackMax); + const preferred = width * 0.7; + const expectedSpread = Math.max(minSpread, Math.min(preferred, trackMax)); + + const base = width / 320; + const passTimeRaw = 1.1 + (base - 1) * 0.25; + const passTime = Math.min(1.6, Math.max(1.1, passTimeRaw)); + const speed = (width * 2) / passTime; + + return ( + <div className="flex min-w-max items-center gap-4 rounded-lg border p-3"> + <div className="w-20 shrink-0"> + <span className="font-mono text-sm font-semibold">{label}</span> + </div> + <div + className="shimmer-container shrink-0" + style={{ width: `${width}px` }} + > + <div className="shimmer-bg bg-muted h-6 w-full rounded" /> + </div> + <div className="text-muted-foreground hidden shrink-0 text-xs xl:block"> + spread: ~{expectedSpread.toFixed(0)}px | pass: {passTime.toFixed(2)}s | + speed: {speed.toFixed(0)}px/s + </div> + </div> + ); +} + +function SkeletonPatternsSection() { + return ( + <Section + id="skeleton-patterns" + title="Skeleton Patterns" + description="Common UI skeleton patterns using shimmer-bg." + > + <div className="grid gap-6 md:grid-cols-2"> + {/* Card skeleton */} + <TestCard title="Card Skeleton" description="Basic card with content"> + <div className="shimmer-container rounded-lg border p-4"> + <div className="shimmer-bg bg-muted mb-4 aspect-video rounded-lg" /> + <div className="space-y-2"> + <div className="shimmer-bg bg-muted h-5 w-3/4 rounded" /> + <div className="shimmer-bg bg-muted h-4 w-full rounded" /> + <div className="shimmer-bg bg-muted h-4 w-4/5 rounded" /> + </div> + </div> + </TestCard> + + {/* Profile skeleton */} + <TestCard title="Profile Skeleton" description="Avatar with details"> + <div className="shimmer-container flex items-center gap-4"> + <div className="shimmer-bg bg-muted size-16 shrink-0 rounded-full" /> + <div className="flex-1 space-y-2"> + <div className="shimmer-bg bg-muted h-5 w-1/3 rounded" /> + <div className="shimmer-bg bg-muted h-4 w-2/3 rounded" /> + <div className="shimmer-bg bg-muted h-3 w-1/2 rounded" /> + </div> + </div> + </TestCard> + + {/* List skeleton */} + <TestCard title="List Skeleton" description="Multiple list items"> + <div className="shimmer-container divide-y"> + {Array.from({ length: 4 }).map((_, i) => ( + <div key={i} className="flex items-center gap-3 py-3"> + <div className="shimmer-bg bg-muted size-10 shrink-0 rounded-full" /> + <div className="flex-1 space-y-1.5"> + <div className="shimmer-bg bg-muted h-4 w-1/3 rounded" /> + <div className="shimmer-bg bg-muted h-3 w-2/3 rounded" /> + </div> + </div> + ))} + </div> + </TestCard> + + {/* Article skeleton */} + <TestCard title="Article Skeleton" description="Blog post layout"> + <div className="shimmer-container space-y-4"> + <div className="shimmer-bg bg-muted h-8 w-4/5 rounded" /> + <div className="flex gap-2"> + <div className="shimmer-bg bg-muted h-3 w-20 rounded" /> + <div className="shimmer-bg bg-muted h-3 w-24 rounded" /> + </div> + <div className="shimmer-bg bg-muted aspect-video rounded-lg" /> + <div className="space-y-2"> + <div className="shimmer-bg bg-muted h-4 w-full rounded" /> + <div className="shimmer-bg bg-muted h-4 w-full rounded" /> + <div className="shimmer-bg bg-muted h-4 w-3/4 rounded" /> + </div> + </div> + </TestCard> + + {/* Comment skeleton */} + <TestCard title="Comment Skeleton" description="Threaded comments"> + <div className="shimmer-container space-y-4"> + <div className="flex gap-3"> + <div className="shimmer-bg bg-muted size-8 shrink-0 rounded-full" /> + <div className="flex-1 space-y-2"> + <div className="shimmer-bg bg-muted h-3 w-24 rounded" /> + <div className="shimmer-bg bg-muted h-4 w-full rounded" /> + <div className="shimmer-bg bg-muted h-4 w-4/5 rounded" /> + </div> + </div> + <div className="ml-8 flex gap-3"> + <div className="shimmer-bg bg-muted size-8 shrink-0 rounded-full" /> + <div className="flex-1 space-y-2"> + <div className="shimmer-bg bg-muted h-3 w-20 rounded" /> + <div className="shimmer-bg bg-muted h-4 w-full rounded" /> + </div> + </div> + </div> + </TestCard> + + {/* Table skeleton */} + <TestCard title="Table Skeleton" description="Data table rows"> + <div className="shimmer-container space-y-2"> + <div className="flex gap-2 border-b pb-2"> + <div className="shimmer-bg bg-muted h-4 w-1/4 rounded" /> + <div className="shimmer-bg bg-muted h-4 w-1/4 rounded" /> + <div className="shimmer-bg bg-muted h-4 w-1/4 rounded" /> + <div className="shimmer-bg bg-muted h-4 w-1/4 rounded" /> + </div> + {Array.from({ length: 4 }).map((_, i) => ( + <div key={i} className="flex gap-2"> + <div className="shimmer-bg bg-muted h-4 w-1/4 rounded" /> + <div className="shimmer-bg bg-muted h-4 w-1/4 rounded" /> + <div className="shimmer-bg bg-muted h-4 w-1/4 rounded" /> + <div className="shimmer-bg bg-muted h-4 w-1/4 rounded" /> + </div> + ))} + </div> + </TestCard> + + {/* Navigation skeleton */} + <TestCard title="Navigation Skeleton" description="Nav bar items"> + <div className="shimmer-container flex items-center justify-between"> + <div className="shimmer-bg bg-muted h-8 w-32 rounded" /> + <div className="flex gap-4"> + <div className="shimmer-bg bg-muted h-4 w-16 rounded" /> + <div className="shimmer-bg bg-muted h-4 w-16 rounded" /> + <div className="shimmer-bg bg-muted h-4 w-16 rounded" /> + </div> + <div className="shimmer-bg bg-muted size-8 rounded-full" /> + </div> + </TestCard> + + {/* Form skeleton */} + <TestCard title="Form Skeleton" description="Input fields"> + <div className="shimmer-container space-y-4"> + <div className="space-y-1.5"> + <div className="shimmer-bg bg-muted h-3 w-16 rounded" /> + <div className="shimmer-bg bg-muted h-10 w-full rounded-md" /> + </div> + <div className="space-y-1.5"> + <div className="shimmer-bg bg-muted h-3 w-20 rounded" /> + <div className="shimmer-bg bg-muted h-10 w-full rounded-md" /> + </div> + <div className="shimmer-bg bg-muted h-10 w-24 rounded-md" /> + </div> + </TestCard> + + {/* Media grid skeleton */} + <TestCard title="Media Grid" description="Image gallery"> + <div className="shimmer-container grid grid-cols-3 gap-2"> + <div className="shimmer-bg bg-muted col-span-2 row-span-2 aspect-square rounded-lg" /> + <div className="shimmer-bg bg-muted aspect-square rounded-lg" /> + <div className="shimmer-bg bg-muted aspect-square rounded-lg" /> + </div> + </TestCard> + </div> + </Section> + ); +} + +const ANGLES = [0, 15, 30, 45, 60, 75, 90, 105, 120, 135]; + +function AngleTestSection() { + return ( + <Section + id="angles" + title="Angles" + description="Testing shimmer-angle-* utility for different gradient angles." + > + <div className="grid gap-6 md:grid-cols-2"> + {/* Text shimmer angles */} + <TestCard + title="Text Shimmer Angles" + description="Using --shimmer-angle" + > + <div className="shimmer-container space-y-3"> + {ANGLES.map((angle) => ( + <p + key={angle} + className="shimmer text-foreground/40 text-lg font-medium" + style={ + { "--shimmer-angle": `${angle}deg` } as React.CSSProperties + } + > + {angle}deg angle shimmer text + </p> + ))} + </div> + </TestCard> + + {/* Background shimmer angles */} + <TestCard + title="Background Shimmer Angles" + description="Using --shimmer-angle" + > + <div className="shimmer-container space-y-3"> + {ANGLES.map((angle) => ( + <div key={angle} className="flex items-center gap-3"> + <span className="text-muted-foreground w-12 text-sm"> + {angle}deg + </span> + <div + className="shimmer-bg bg-muted h-6 flex-1 rounded" + style={ + { "--shimmer-angle": `${angle}deg` } as React.CSSProperties + } + /> + </div> + ))} + </div> + </TestCard> + + {/* Angled grid with position alignment */} + <TestCard + title="Angled + Position Offset" + description="Using --shimmer-y for vertical alignment" + > + <div className="shimmer-container shimmer-angle-45 space-y-2"> + {Array.from({ length: 6 }).map((_, i) => ( + <div + key={i} + className="shimmer-bg bg-muted h-6 rounded" + style={ + { + "--shimmer-y": i * 24, + } as React.CSSProperties + } + /> + ))} + </div> + </TestCard> + + {/* Horizontal position offset */} + <TestCard + title="Horizontal Position Offset" + description="Using shimmer-x-* for staggered timing" + > + {/* Fixed 96px cells + 8px gap = 104px stride */} + <div className="shimmer-container shimmer-angle-45 grid grid-cols-[repeat(4,96px)] gap-2"> + {Array.from({ length: 8 }).map((_, i) => ( + <div + key={i} + className="shimmer-bg bg-muted h-24 rounded" + style={ + { + "--shimmer-x": (i % 4) * 104, + "--shimmer-y": Math.floor(i / 4) * 104, + } as React.CSSProperties + } + /> + ))} + </div> + </TestCard> + </div> + </Section> + ); +} + +function ColorTestSection() { + return ( + <Section + id="colors" + title="Colors" + description="Testing shimmer-color-* utility for different highlight colors." + > + <div className="grid gap-6 md:grid-cols-2"> + {/* Text shimmer colors */} + <TestCard + title="Text Shimmer Colors" + description="Custom shimmer colors on text" + > + <div className="shimmer-container space-y-2"> + <p className="shimmer text-foreground/40 text-lg">Default color</p> + {SHIMMER_COLORS.map(({ name, shimmerColor, textColor }) => ( + <p + key={name} + className={cn("shimmer text-lg", textColor)} + style={ + { "--shimmer-color": shimmerColor } as React.CSSProperties + } + > + {name} shimmer + </p> + ))} + </div> + </TestCard> + + {/* Background shimmer colors */} + <TestCard + title="Background Shimmer Colors" + description="Custom shimmer colors on backgrounds" + > + <div className="shimmer-container space-y-2"> + <div className="shimmer-bg bg-muted h-6 rounded" /> + {SHIMMER_COLORS.map(({ name, shimmerColor }) => ( + <div + key={name} + className="shimmer-bg bg-muted h-6 rounded" + style={ + { "--shimmer-color": shimmerColor } as React.CSSProperties + } + /> + ))} + </div> + </TestCard> + + {/* On colored backgrounds */} + <TestCard + title="On Colored Backgrounds" + description="Shimmer on non-muted backgrounds" + > + <div className="shimmer-container grid grid-cols-2 gap-2"> + <div className="shimmer-bg h-12 rounded bg-red-200 dark:bg-red-900" /> + <div className="shimmer-bg h-12 rounded bg-blue-200 dark:bg-blue-900" /> + <div className="shimmer-bg h-12 rounded bg-green-200 dark:bg-green-900" /> + <div className="shimmer-bg h-12 rounded bg-yellow-200 dark:bg-yellow-900" /> + <div className="shimmer-bg h-12 rounded bg-purple-200 dark:bg-purple-900" /> + <div className="shimmer-bg h-12 rounded bg-gray-800 dark:bg-gray-200" /> + </div> + </TestCard> + + {/* Inline style colors */} + <TestCard + title="Inline Style Colors" + description="Using CSS custom properties directly" + > + <div className="shimmer-container space-y-2"> + <div + className="shimmer-bg bg-muted h-6 rounded" + style={ + { + "--shimmer-color": "oklch(0.7 0.2 30)", + } as React.CSSProperties + } + /> + <div + className="shimmer-bg bg-muted h-6 rounded" + style={ + { + "--shimmer-color": "oklch(0.8 0.15 150)", + } as React.CSSProperties + } + /> + <div + className="shimmer-bg bg-muted h-6 rounded" + style={ + { + "--shimmer-color": "oklch(0.9 0.1 270)", + } as React.CSSProperties + } + /> + </div> + </TestCard> + </div> + </Section> + ); +} + +const SPEEDS = [50, 100, 150, 200, 300, 500, 800, 1000, 1500, 2000]; + +function SpeedTestSection() { + return ( + <Section + id="speeds" + title="Speeds" + description="Testing shimmer-speed-* utility for different animation speeds (px/s)." + > + <div className="grid gap-6 md:grid-cols-2"> + {/* Text shimmer speeds */} + <TestCard + title="Text Shimmer Speeds" + description="Slower to faster speeds" + > + <div className="shimmer-container space-y-2"> + {SPEEDS.map((speed) => ( + <p + key={speed} + className="shimmer text-foreground/40 text-sm" + style={{ "--shimmer-speed": speed } as React.CSSProperties} + > + Speed {speed}px/s - The quick brown fox + </p> + ))} + </div> + </TestCard> + + {/* Background shimmer speeds */} + <TestCard + title="Background Shimmer Speeds" + description="Slower to faster speeds" + > + <div className="shimmer-container space-y-2"> + {SPEEDS.map((speed) => ( + <div key={speed} className="flex items-center gap-3"> + <span className="text-muted-foreground w-16 text-xs"> + {speed}px/s + </span> + <div + className="shimmer-bg bg-muted h-5 flex-1 rounded" + style={{ "--shimmer-speed": speed } as React.CSSProperties} + /> + </div> + ))} + </div> + </TestCard> + + {/* Speed with width */} + <TestCard + title="Speed + Manual Width" + description="Same speed, different track widths" + > + <div className="space-y-4"> + {[200, 400, 600].map((w) => ( + <div key={w} className="space-y-1"> + <span className="text-muted-foreground text-xs"> + --shimmer-width: 200, --shimmer-speed: 200 + </span> + <div + className="shimmer-bg bg-muted h-6 rounded" + style={ + { + width: `${w}px`, + "--shimmer-width": 200, + "--shimmer-speed": 200, + } as React.CSSProperties + } + /> + </div> + ))} + </div> + </TestCard> + + {/* Auto speed comparison */} + <TestCard + title="Auto Speed (Container-Based)" + description="Speed derived from container width" + > + <div className="space-y-4"> + {[200, 400, 600].map((width) => ( + <div key={width} className="space-y-1"> + <span className="text-muted-foreground text-xs"> + {width}px container - auto speed + </span> + <div + className="shimmer-container" + style={{ width: `${width}px` }} + > + <div className="shimmer-bg bg-muted h-6 w-full rounded" /> + </div> + </div> + ))} + </div> + </TestCard> + </div> + </Section> + ); +} + +const SPREADS = [20, 40, 60, 80, 100, 150, 200, 300, 400]; + +function SpreadTestSection() { + return ( + <Section + id="spreads" + title="Spreads" + description="Testing shimmer-spread-* (text) and shimmer-bg-spread-* (background) utilities." + > + <div className="grid gap-6 md:grid-cols-2"> + {/* Text shimmer spreads */} + <TestCard + title="Text Shimmer Spreads" + description="Using --shimmer-spread (in ch units). Inside shimmer-container with 1x speed multiplier." + > + <div className="shimmer-container space-y-2"> + {[2, 4, 6, 8, 10, 12, 16, 20].map((spread) => ( + <p + key={spread} + className="shimmer text-foreground/40 text-sm" + style={ + { "--shimmer-spread": `${spread}ch` } as React.CSSProperties + } + > + Spread {spread}ch - Shimmer text effect + </p> + ))} + </div> + </TestCard> + + {/* Background shimmer spreads */} + <TestCard + title="Background Shimmer Spreads" + description="Using --shimmer-bg-spread (in px)" + > + <div className="shimmer-container space-y-2"> + {SPREADS.map((spread) => ( + <div key={spread} className="flex items-center gap-3"> + <span className="text-muted-foreground w-12 text-xs"> + {spread}px + </span> + <div + className="shimmer-bg bg-muted h-5 flex-1 rounded" + style={ + { + "--shimmer-bg-spread": `${spread}px`, + } as React.CSSProperties + } + /> + </div> + ))} + </div> + </TestCard> + + {/* Spread vs container width */} + <TestCard + title="Spread vs Container Width" + description="How spread looks at different container sizes" + > + <div className="space-y-4"> + {[150, 300, 600].map((width) => ( + <div key={width} className="space-y-2"> + <span className="text-muted-foreground text-xs"> + {width}px container + </span> + <div + className="shimmer-container space-y-1" + style={{ width: `${width}px` }} + > + <div className="shimmer-bg bg-muted h-5 rounded" /> + <div + className="shimmer-bg bg-muted h-5 rounded" + style={ + { "--shimmer-bg-spread": "100px" } as React.CSSProperties + } + /> + <div + className="shimmer-bg bg-muted h-5 rounded" + style={ + { "--shimmer-bg-spread": "200px" } as React.CSSProperties + } + /> + </div> + </div> + ))} + </div> + </TestCard> + + {/* Auto spread behavior */} + <TestCard + title="Auto Spread Behavior" + description="Default auto spread in shimmer-container" + > + <div className="space-y-4"> + <p className="text-muted-foreground text-xs"> + Auto spread clamps to: min(200px, track) to 70cqw to min(300px, + track) + </p> + {[100, 200, 300, 400, 500].map((width) => ( + <div + key={width} + className="shimmer-container" + style={{ width: `${width}px` }} + > + <div className="flex items-center gap-2"> + <span className="text-muted-foreground w-12 shrink-0 text-xs"> + {width}px + </span> + <div className="shimmer-bg bg-muted h-5 flex-1 rounded" /> + </div> + </div> + ))} + </div> + </TestCard> + </div> + </Section> + ); +} + +function NestedContainersSection() { + return ( + <Section + id="nested-containers" + title="Nested Containers" + description="Testing shimmer behavior with nested shimmer-container elements." + > + <div className="grid gap-6 md:grid-cols-2"> + {/* Simple nesting */} + <TestCard + title="Simple Nesting" + description="Nested containers with different widths" + > + <div className="shimmer-container rounded border p-4"> + <p className="text-muted-foreground mb-2 text-xs"> + Outer container (full width) + </p> + <div className="shimmer-bg bg-muted mb-4 h-6 rounded" /> + <div className="shimmer-container w-3/4 rounded border border-dashed p-4"> + <p className="text-muted-foreground mb-2 text-xs"> + Inner container (75% width) + </p> + <div className="shimmer-bg bg-muted h-6 rounded" /> + </div> + </div> + </TestCard> + + {/* Deep nesting */} + <TestCard title="Deep Nesting" description="Multiple levels deep"> + <div className="shimmer-container rounded border p-3"> + <div className="shimmer-bg bg-muted mb-2 h-4 rounded" /> + <div className="shimmer-container w-4/5 rounded border border-dashed p-3"> + <div className="shimmer-bg bg-muted mb-2 h-4 rounded" /> + <div className="shimmer-container w-4/5 rounded border border-dotted p-3"> + <div className="shimmer-bg bg-muted mb-2 h-4 rounded" /> + <div className="shimmer-container w-4/5 rounded border p-3"> + <div className="shimmer-bg bg-muted h-4 rounded" /> + </div> + </div> + </div> + </div> + </TestCard> + + {/* Mixed content in nesting */} + <TestCard + title="Mixed Content" + description="Text and background shimmer nested" + > + <div className="shimmer-container space-y-4 rounded border p-4"> + <p className="shimmer text-foreground/40 text-lg font-semibold"> + Outer container text shimmer + </p> + <div className="shimmer-bg bg-muted h-8 rounded" /> + <div className="shimmer-container w-2/3 rounded border border-dashed p-4"> + <p className="shimmer text-foreground/40">Inner container text</p> + <div className="shimmer-bg bg-muted mt-2 h-6 rounded" /> + </div> + </div> + </TestCard> + + {/* Grid of nested containers */} + <TestCard + title="Grid of Containers" + description="Multiple containers in a grid" + > + <div className="grid grid-cols-2 gap-4"> + {["A", "B", "C", "D"].map((label) => ( + <div key={label} className="shimmer-container rounded border p-3"> + <p className="text-muted-foreground mb-2 text-xs"> + Container {label} + </p> + <div className="shimmer-bg bg-muted h-8 rounded" /> + </div> + ))} + </div> + </TestCard> + + {/* Container-level overrides */} + <TestCard + title="Container-Level Overrides" + description="Overrides set on shimmer-container propagate to children" + > + <div + className="shimmer-container space-y-3 rounded border p-4" + style={ + { + "--shimmer-speed": 100, + "--shimmer-angle": "75deg", + } as React.CSSProperties + } + > + <p className="text-muted-foreground text-xs"> + Container: speed=100, angle=75deg + </p> + <p className="shimmer text-foreground/40"> + Text inherits container settings + </p> + <div className="shimmer-bg bg-muted h-6 rounded" /> + <div className="flex gap-2"> + <div className="shimmer-bg bg-muted h-10 flex-1 rounded" /> + <div className="shimmer-bg bg-muted size-10 rounded-full" /> + </div> + </div> + </TestCard> + + {/* Element-level overrides within container */} + <TestCard + title="Element-Level Overrides" + description="Individual elements override container settings" + > + <div + className="shimmer-container space-y-3 rounded border p-4" + style={{ "--shimmer-speed": 150 } as React.CSSProperties} + > + <p className="text-muted-foreground text-xs"> + Container: speed=150 (default angle) + </p> + <div className="shimmer-bg bg-muted h-5 rounded" /> + <div + className="shimmer-bg bg-muted h-5 rounded" + style={{ "--shimmer-speed": 400 } as React.CSSProperties} + /> + <div + className="shimmer-bg bg-muted h-5 rounded" + style={{ "--shimmer-angle": "45deg" } as React.CSSProperties} + /> + <div + className="shimmer-bg bg-muted h-5 rounded" + style={ + { + "--shimmer-speed": 600, + "--shimmer-angle": "120deg", + "--shimmer-color": "oklch(0.7 0.15 200)", + } as React.CSSProperties + } + /> + <p className="text-muted-foreground text-[10px]"> + 1: inherit | 2: speed=400 | 3: angle=45 | 4: speed=600, angle=120, + custom color + </p> + </div> + </TestCard> + + {/* Complex nested inheritance */} + <TestCard + title="Complex Nested Inheritance" + description="Multi-level nesting with overrides at each level" + > + <div + className="shimmer-container space-y-2 rounded border p-3" + style={{ "--shimmer-angle": "15deg" } as React.CSSProperties} + > + <p className="text-muted-foreground text-[10px]">L1: angle=15deg</p> + <p className="shimmer text-foreground/40 text-sm"> + L1 text shimmer + </p> + <div className="shimmer-bg bg-muted h-4 rounded" /> + + <div + className="shimmer-container space-y-2 rounded border border-dashed p-3" + style={{ "--shimmer-speed": 200 } as React.CSSProperties} + > + <p className="text-muted-foreground text-[10px]"> + L2: speed=200 (inherits angle=15) + </p> + <p className="shimmer text-foreground/40 text-sm"> + L2 text shimmer + </p> + <div className="shimmer-bg bg-muted h-4 rounded" /> + + <div + className="shimmer-container space-y-2 rounded border border-dotted p-3" + style={ + { + "--shimmer-angle": "90deg", + "--shimmer-color": "oklch(0.65 0.2 150)", + } as React.CSSProperties + } + > + <p className="text-muted-foreground text-[10px]"> + L3: angle=90 (overrides), color=green, inherits speed=200 + </p> + <p className="shimmer text-foreground/40 text-sm"> + L3 text shimmer + </p> + <div className="shimmer-bg bg-muted h-4 rounded" /> + + {/* Element override at deepest level */} + <div + className="shimmer-bg bg-muted h-4 rounded" + style={ + { + "--shimmer-speed": 800, + "--shimmer-color": "oklch(0.7 0.2 30)", + } as React.CSSProperties + } + /> + <p className="text-muted-foreground text-[10px]"> + ^ Element override: speed=800, color=orange + </p> + </div> + </div> + </div> + </TestCard> + + {/* Mixed text and skeleton with varied overrides */} + <TestCard + title="Mixed Content Stress Test" + description="Text + skeletons with varied per-element overrides" + > + <div className="shimmer-container space-y-4 rounded border p-4"> + {/* Card 1: Default settings */} + <div className="flex gap-3"> + <div className="shimmer-bg bg-muted size-12 shrink-0 rounded-full" /> + <div className="flex-1 space-y-1"> + <p className="shimmer text-foreground/40 text-sm font-medium"> + Default speed text + </p> + <div className="shimmer-bg bg-muted h-3 w-full rounded" /> + <div className="shimmer-bg bg-muted h-3 w-3/4 rounded" /> + </div> + </div> + + {/* Card 2: Slow, angled */} + <div + className="flex gap-3" + style={ + { + "--shimmer-speed": 100, + "--shimmer-angle": "30deg", + } as React.CSSProperties + } + > + <div className="shimmer-bg bg-muted size-12 shrink-0 rounded-full" /> + <div className="flex-1 space-y-1"> + <p className="shimmer text-foreground/40 text-sm font-medium"> + Slow + angled (100px/s, 30deg) + </p> + <div className="shimmer-bg bg-muted h-3 w-full rounded" /> + <div className="shimmer-bg bg-muted h-3 w-3/4 rounded" /> + </div> + </div> + + {/* Card 3: Fast, colored */} + <div + className="flex gap-3" + style={ + { + "--shimmer-speed": 600, + "--shimmer-color": "oklch(0.6 0.25 280)", + } as React.CSSProperties + } + > + <div className="shimmer-bg bg-muted size-12 shrink-0 rounded-full" /> + <div className="flex-1 space-y-1"> + <p className="shimmer text-sm font-medium text-purple-500/50"> + Fast + purple (600px/s) + </p> + <div className="shimmer-bg bg-muted h-3 w-full rounded" /> + <div className="shimmer-bg bg-muted h-3 w-3/4 rounded" /> + </div> + </div> + + {/* Card 4: Per-element chaos */} + <div className="flex gap-3"> + <div + className="shimmer-bg bg-muted size-12 shrink-0 rounded-full" + style={ + { + "--shimmer-speed": 50, + "--shimmer-color": "oklch(0.7 0.2 30)", + } as React.CSSProperties + } + /> + <div className="flex-1 space-y-1"> + <p + className="shimmer text-foreground/40 text-sm font-medium" + style={{ "--shimmer-speed": 300 } as React.CSSProperties} + > + Each element different + </p> + <div + className="shimmer-bg bg-muted h-3 w-full rounded" + style={{ "--shimmer-angle": "60deg" } as React.CSSProperties} + /> + <div + className="shimmer-bg bg-muted h-3 w-3/4 rounded" + style={ + { + "--shimmer-speed": 1000, + "--shimmer-color": "oklch(0.65 0.2 150)", + } as React.CSSProperties + } + /> + </div> + </div> + </div> + </TestCard> + </div> + </Section> + ); +} + +function EdgeCasesSection() { + return ( + <Section + id="edge-cases" + title="Edge Cases" + description="Testing unusual or extreme scenarios." + > + <div className="grid gap-6 md:grid-cols-2"> + {/* Very small */} + <TestCard + title="Very Small Elements" + description="Tiny shimmer targets" + > + <div className="shimmer-container flex flex-wrap items-center gap-2"> + <div className="shimmer-bg bg-muted size-2 rounded-full" /> + <div className="shimmer-bg bg-muted size-3 rounded-full" /> + <div className="shimmer-bg bg-muted size-4 rounded-full" /> + <div className="shimmer-bg bg-muted h-2 w-8 rounded" /> + <div className="shimmer-bg bg-muted h-2 w-16 rounded" /> + <span className="shimmer text-foreground/40 text-[8px]"> + Tiny text + </span> + </div> + </TestCard> + + {/* Very large */} + <TestCard title="Very Large Elements" description="Oversized targets"> + <div className="shimmer-container space-y-4"> + <div className="shimmer-bg bg-muted h-32 rounded-lg" /> + <p className="shimmer text-foreground/40 text-6xl font-black"> + HUGE + </p> + </div> + </TestCard> + + {/* Zero width container */} + <TestCard title="Tiny Container" description="Container barely visible"> + <div className="flex gap-4"> + <div className="shimmer-container w-8"> + <div className="shimmer-bg bg-muted h-8 rounded" /> + </div> + <div className="shimmer-container w-16"> + <div className="shimmer-bg bg-muted h-8 rounded" /> + </div> + </div> + </TestCard> + + {/* Multiple shimmers on one element */} + <TestCard + title="Both Shimmer Types" + description="shimmer and shimmer-bg together (not recommended)" + > + <div className="shimmer-container"> + <div className="shimmer-bg shimmer bg-muted text-foreground/40 rounded p-4"> + This has both shimmer classes + </div> + </div> + </TestCard> + + {/* Overflow scenarios */} + <TestCard + title="Overflow Scenarios" + description="Content that overflows container" + > + <div className="shimmer-container w-48 rounded border p-2"> + <div className="shimmer-bg bg-muted h-4 w-96 rounded" /> + <p className="shimmer text-foreground/40 mt-2 whitespace-nowrap"> + This text is way too long to fit + </p> + </div> + </TestCard> + + {/* Rotated elements */} + <TestCard title="Rotated Elements" description="With CSS transforms"> + <div className="shimmer-container flex items-center justify-center py-8"> + <div className="shimmer-bg bg-muted h-16 w-32 rotate-12 rounded" /> + </div> + </TestCard> + + {/* Scaled elements */} + <TestCard title="Scaled Elements" description="With scale transform"> + <div className="shimmer-container flex items-center justify-center py-4"> + <div className="shimmer-bg bg-muted h-8 w-24 scale-150 rounded" /> + </div> + </TestCard> + + {/* Opacity */} + <TestCard title="With Opacity" description="Semi-transparent elements"> + <div className="shimmer-container space-y-2"> + <div className="shimmer-bg bg-muted h-6 rounded opacity-100" /> + <div className="shimmer-bg bg-muted h-6 rounded opacity-75" /> + <div className="shimmer-bg bg-muted h-6 rounded opacity-50" /> + <div className="shimmer-bg bg-muted h-6 rounded opacity-25" /> + </div> + </TestCard> + + {/* Border radius extremes */} + <TestCard + title="Border Radius Extremes" + description="Various border-radius values" + > + <div className="shimmer-container space-y-2"> + <div className="shimmer-bg bg-muted h-8 rounded-none" /> + <div className="shimmer-bg bg-muted h-8 rounded-sm" /> + <div className="shimmer-bg bg-muted h-8 rounded-xl" /> + <div className="shimmer-bg bg-muted h-8 rounded-3xl" /> + <div className="shimmer-bg bg-muted h-8 rounded-full" /> + </div> + </TestCard> + </div> + </Section> + ); +} + +function InteractivePlaygroundSection() { + const [width, setWidth] = useState(400); + const [speed, setSpeed] = useState<number | null>(null); + const [bgSpread, setBgSpread] = useState<number | null>(null); + const [textSpread, setTextSpread] = useState<number | null>(null); + const [angle, setAngle] = useState(90); + const [useContainer, setUseContainer] = useState(true); + + const containerClass = useContainer ? "shimmer-container" : ""; + + // Build inline style object for shimmer overrides + // Note: text shimmer uses --shimmer-spread (in ch), bg shimmer uses --shimmer-bg-spread (in px) + const shimmerStyle: React.CSSProperties = { + ...(speed !== null && { "--shimmer-speed": speed }), + ...(bgSpread !== null && { "--shimmer-bg-spread": `${bgSpread}px` }), + ...(angle !== 90 && { "--shimmer-angle": `${angle}deg` }), + } as React.CSSProperties; + + const textShimmerStyle: React.CSSProperties = { + ...(speed !== null && { "--shimmer-speed": speed }), + ...(textSpread !== null && { "--shimmer-spread": `${textSpread}ch` }), + ...(angle !== 90 && { "--shimmer-angle": `${angle}deg` }), + } as React.CSSProperties; + + // For display purposes + const allStyleEntries = [ + ...Object.entries(shimmerStyle), + ...Object.entries(textShimmerStyle).filter( + ([k]) => k === "--shimmer-spread", + ), + ]; + + return ( + <Section + id="interactive" + title="Interactive Playground" + description="Experiment with different combinations of settings." + > + <div className="grid gap-8 lg:grid-cols-2"> + <div className="space-y-6"> + {/* Width control */} + <div className="space-y-2"> + <label + className="flex items-center justify-between text-sm font-medium" + htmlFor="container-width" + > + <span>Container Width</span> + <span className="text-muted-foreground font-mono">{width}px</span> + </label> + <input + id="container-width" + type="range" + min="50" + max="800" + value={width} + onChange={(e) => setWidth(Number(e.target.value))} + className="w-full" + /> + </div> + + {/* Use container toggle */} + <label className="flex items-center gap-2"> + <input + type="checkbox" + checked={useContainer} + onChange={(e) => setUseContainer(e.target.checked)} + className="rounded" + /> + <span className="text-sm">Use shimmer-container (auto sizing)</span> + </label> + + {/* Speed control */} + <div className="space-y-2"> + <label + className="flex items-center justify-between text-sm font-medium" + htmlFor="speed-override" + > + <span>Speed Override</span> + <span className="text-muted-foreground font-mono"> + {speed !== null ? `${speed}px/s` : "auto"} + </span> + </label> + <div className="flex items-center gap-2"> + <input + id="speed-override" + type="range" + min="50" + max="2000" + value={speed ?? 500} + onChange={(e) => setSpeed(Number(e.target.value))} + disabled={speed === null} + className="flex-1" + /> + <button + type="button" + onClick={() => setSpeed(speed === null ? 500 : null)} + className="bg-muted rounded px-2 py-1 text-xs" + > + {speed === null ? "Enable" : "Reset"} + </button> + </div> + </div> + + {/* Background Spread control */} + <div className="space-y-2"> + <label + className="flex items-center justify-between text-sm font-medium" + htmlFor="bg-spread" + > + <span>BG Spread (--shimmer-bg-spread)</span> + <span className="text-muted-foreground font-mono"> + {bgSpread !== null ? `${bgSpread}px` : "auto"} + </span> + </label> + <div className="flex items-center gap-2"> + <input + id="bg-spread" + type="range" + min="20" + max="400" + value={bgSpread ?? 100} + onChange={(e) => setBgSpread(Number(e.target.value))} + disabled={bgSpread === null} + className="flex-1" + /> + <button + type="button" + onClick={() => setBgSpread(bgSpread === null ? 100 : null)} + className="bg-muted rounded px-2 py-1 text-xs" + > + {bgSpread === null ? "Enable" : "Reset"} + </button> + </div> + </div> + + {/* Text Spread control */} + <div className="space-y-2"> + <label + className="flex items-center justify-between text-sm font-medium" + htmlFor="text-spread" + > + <span>Text Spread (--shimmer-spread)</span> + <span className="text-muted-foreground font-mono"> + {textSpread !== null ? `${textSpread}ch` : "auto (6ch)"} + </span> + </label> + <div className="flex items-center gap-2"> + <input + id="text-spread" + type="range" + min="1" + max="30" + value={textSpread ?? 6} + onChange={(e) => setTextSpread(Number(e.target.value))} + disabled={textSpread === null} + className="flex-1" + /> + <button + type="button" + onClick={() => setTextSpread(textSpread === null ? 6 : null)} + className="bg-muted rounded px-2 py-1 text-xs" + > + {textSpread === null ? "Enable" : "Reset"} + </button> + </div> + </div> + + {/* Angle control */} + <div className="space-y-2"> + <label + className="flex items-center justify-between text-sm font-medium" + htmlFor="angle" + > + <span>Angle</span> + <span className="text-muted-foreground font-mono"> + {angle}deg + </span> + </label> + <input + id="angle" + type="range" + min="0" + max="180" + step="15" + value={angle} + onChange={(e) => setAngle(Number(e.target.value))} + className="w-full" + /> + </div> + + {/* Generated styles display */} + <div className="bg-muted rounded-lg p-4"> + <p className="text-muted-foreground mb-2 text-xs font-medium"> + Applied Styles: + </p> + <code className="block space-y-1 text-sm"> + <div> + class: {containerClass || "(none)"} shimmer / shimmer-bg + </div> + {allStyleEntries.length > 0 ? ( + allStyleEntries.map( + ([key, value]: [string, string | number]) => ( + <div key={key}> + {key}: {String(value)} + </div> + ), + ) + ) : ( + <div className="text-muted-foreground">(no overrides)</div> + )} + </code> + </div> + </div> + + {/* Preview */} + <div className="space-y-4"> + <h3 className="font-semibold">Preview</h3> + <div + className={cn( + "rounded-lg border border-dashed p-4", + containerClass, + )} + style={{ width: `${width}px`, maxWidth: "100%" }} + > + <div className="flex gap-3"> + <div + className="shimmer-bg bg-muted size-12 shrink-0 rounded-full" + style={shimmerStyle} + /> + <div className="flex-1 space-y-2"> + <div + className="shimmer-bg bg-muted h-4 w-1/3 rounded" + style={shimmerStyle} + /> + <div + className="shimmer-bg bg-muted h-4 w-full rounded" + style={shimmerStyle} + /> + <div + className="shimmer-bg bg-muted h-4 w-4/5 rounded" + style={shimmerStyle} + /> + </div> + </div> + <div className="mt-4 space-y-2"> + <div + className="shimmer-bg bg-muted h-32 rounded-lg" + style={shimmerStyle} + /> + <div className="flex gap-2"> + <div + className="shimmer-bg bg-muted h-8 flex-1 rounded" + style={shimmerStyle} + /> + <div + className="shimmer-bg bg-muted h-8 w-24 rounded" + style={shimmerStyle} + /> + </div> + </div> + </div> + + {/* Text shimmer preview */} + <div + className={cn( + "space-y-2 rounded-lg border border-dashed p-4", + containerClass, + )} + style={{ width: `${width}px`, maxWidth: "100%" }} + > + <p + className="shimmer text-foreground/40 text-xl font-bold" + style={textShimmerStyle} + > + Text Shimmer Preview + </p> + <p className="shimmer text-foreground/40" style={textShimmerStyle}> + This is body text with the shimmer effect applied. It demonstrates + how the shimmer looks on actual text content. + </p> + </div> + </div> + </div> + </Section> + ); +} diff --git a/apps/docs/app/(docs)/docs/[[...slug]]/page.tsx b/apps/docs/app/(docs)/docs/[[...slug]]/page.tsx new file mode 100644 index 0000000..5a7ca46 --- /dev/null +++ b/apps/docs/app/(docs)/docs/[[...slug]]/page.tsx @@ -0,0 +1,129 @@ +import type { Metadata } from "next"; +import { DocsPage, DocsBody } from "fumadocs-ui/page"; +import { notFound, redirect } from "next/navigation"; +import { createOgMetadata } from "@/lib/og"; +import { getMDXComponents } from "@/mdx-components"; +import { source } from "@/lib/source"; +import { getPageTreePeers } from "fumadocs-core/page-tree"; +import { getDocsNeighbours } from "@/lib/docs-neighbours"; +import { Card, Cards } from "@/components/docs/fumadocs/card"; +import { TableOfContents } from "@/components/docs/layout/table-of-contents"; +import { DocsFooter } from "@/components/docs/layout/docs-footer"; +import { DocsPager } from "@/components/docs/layout/docs-pager"; +import { ArrowUpRight } from "lucide-react"; +import { Badge } from "@/components/assistant-ui/badge"; + +function DocsCategory({ url }: { url?: string }) { + const effectiveUrl = url ?? ""; + return ( + <Cards> + {getPageTreePeers(source.pageTree, effectiveUrl).map((peer) => ( + <Card key={peer.url} title={peer.name} href={peer.url}> + {peer.description} + </Card> + ))} + </Cards> + ); +} + +export default async function Page(props: { + params: Promise<{ slug?: string[] }>; +}) { + const params = await props.params; + const slug = params.slug ?? []; + const page = source.getPage(slug); + + if (page == null) { + const overviewPage = source.getPage([...slug, "overview"]); + if (overviewPage) redirect(overviewPage.url); + + notFound(); + } + + const mdxComponents = getMDXComponents({ + DocsCategory, + }); + + const path = `apps/docs/content/docs/${page.path}`; + const markdownUrl = `${page.url}.md`; + const githubEditUrl = `https://github.com/assistant-ui/assistant-ui/edit/main/${path}`; + + const neighbours = getDocsNeighbours(source.pageTree, page.url); + const footerPrevious = neighbours.previous; + const footerNext = neighbours.next; + + return ( + <DocsPage + toc={page.data.toc} + full + tableOfContent={{ + enabled: true, + component: ( + <TableOfContents + items={page.data.toc} + githubEditUrl={githubEditUrl} + markdownUrl={markdownUrl} + /> + ), + }} + tableOfContentPopover={{ + enabled: false, + }} + footer={{ + enabled: false, + }} + > + <DocsBody> + <header className="not-prose mb-8"> + <div className="flex items-center justify-between gap-4"> + <h1 className="text-xl font-medium tracking-tight md:text-2xl"> + {page.data.title} + </h1> + <DocsPager + {...(footerPrevious && { previous: { url: footerPrevious.url } })} + {...(footerNext && { next: { url: footerNext.url } })} + markdownUrl={markdownUrl} + /> + </div> + {page.data.description && ( + <p className="text-muted-foreground mt-2 text-sm md:text-base"> + {page.data.description} + </p> + )} + {page.data.links && page.data.links.length > 0 && ( + <div className="mt-3 flex flex-wrap gap-2"> + {page.data.links.map((link) => ( + <Badge key={link.url} asChild variant="muted"> + <a href={link.url} target="_blank" rel="noopener noreferrer"> + {link.label} + <ArrowUpRight /> + </a> + </Badge> + ))} + </div> + )} + </header> + <page.data.body components={mdxComponents} /> + <DocsFooter previous={footerPrevious} next={footerNext} /> + </DocsBody> + </DocsPage> + ); +} + +export function generateStaticParams() { + return source.generateParams(); +} + +export async function generateMetadata( + props: PageProps<"/docs/[[...slug]]">, +): Promise<Metadata> { + const { slug = [] } = await props.params; + const page = source.getPage(slug); + if (!page) return { title: "Not Found" }; + + return { + title: page.data.title, + description: page.data.description, + ...createOgMetadata(page.data.title, page.data.description), + }; +} diff --git a/apps/docs/app/(docs)/docs/layout.tsx b/apps/docs/app/(docs)/docs/layout.tsx new file mode 100644 index 0000000..94a38f9 --- /dev/null +++ b/apps/docs/app/(docs)/docs/layout.tsx @@ -0,0 +1,16 @@ +import type { ReactNode } from "react"; +import { source } from "@/lib/source"; +import { DocsRootLayout } from "@/components/docs/layout/docs-root-layout"; + +export default function Layout({ children }: { children: ReactNode }) { + return ( + <DocsRootLayout + tree={source.pageTree} + section="Docs" + sectionHref="/docs" + showMobileSectionBreadcrumb + > + {children} + </DocsRootLayout> + ); +} diff --git a/apps/docs/app/(docs)/examples/[[...slug]]/page.tsx b/apps/docs/app/(docs)/examples/[[...slug]]/page.tsx new file mode 100644 index 0000000..e08c4f6 --- /dev/null +++ b/apps/docs/app/(docs)/examples/[[...slug]]/page.tsx @@ -0,0 +1,126 @@ +import { examples, type ExamplePage } from "@/lib/source"; +import type { Metadata } from "next"; +import { createOgMetadata } from "@/lib/og"; +import { DocsPage, DocsBody } from "fumadocs-ui/page"; +import { notFound } from "next/navigation"; +import { getMDXComponents } from "@/mdx-components"; +import { DocsRuntimeProvider } from "@/contexts/DocsRuntimeProvider"; +import { ExamplesNavbar } from "@/components/docs/examples-navbar"; +import { DocsFooter } from "@/components/docs/layout/docs-footer"; +import { DocsPager } from "@/components/docs/layout/docs-pager"; +import { findNeighbour } from "fumadocs-core/page-tree"; +import Link from "next/link"; +import { ArrowUpRightIcon } from "lucide-react"; +import { Button } from "@/components/ui/radix/button"; +import { getDemo } from "@/lib/demos"; + +// The AI SDK example renders the Base demo component. +const EXAMPLE_TO_DEMO_SLUG: Record<string, string> = { "ai-sdk": "base" }; + +function getPage(slug: string[] | undefined): ExamplePage { + const page = examples.getPage(slug); + if (page == null) { + notFound(); + } + return page; +} + +export default async function Page(props: { + params: Promise<{ slug?: string[] }>; +}) { + const params = await props.params; + const mdxComponents = getMDXComponents({}); + const page = getPage(params.slug); + const isIndex = !params.slug || params.slug.length === 0; + + const exampleSlug = params.slug?.[0]; + const demo = exampleSlug + ? getDemo(EXAMPLE_TO_DEMO_SLUG[exampleSlug] ?? exampleSlug) + : undefined; + + const markdownUrl = `${page.url}.md`; + + const neighbours = findNeighbour(examples.pageTree, page.url); + const footerPrevious = neighbours.previous + ? { name: neighbours.previous.name, url: neighbours.previous.url } + : undefined; + const footerNext = neighbours.next + ? { name: neighbours.next.name, url: neighbours.next.url } + : undefined; + + return ( + <DocsPage + toc={page.data.toc} + full={true} + tableOfContent={{ + enabled: false, + }} + tableOfContentPopover={{ + enabled: false, + }} + footer={{ + enabled: false, + }} + > + {!isIndex && <ExamplesNavbar />} + <DocsBody> + {!isIndex && ( + <header className="not-prose mb-8"> + <div className="flex items-center justify-between gap-4"> + <h1 className="text-xl font-medium tracking-tight md:text-2xl"> + {page.data.title} + </h1> + <div className="flex items-center gap-2"> + {demo && ( + <Button variant="outline" size="sm" asChild> + <Link href={`/demos/${demo.slug}`}> + Open demo + <ArrowUpRightIcon className="size-3.5" /> + </Link> + </Button> + )} + <DocsPager + {...(footerPrevious && { + previous: { url: footerPrevious.url }, + })} + {...(footerNext && { next: { url: footerNext.url } })} + markdownUrl={markdownUrl} + /> + </div> + </div> + {page.data.description && ( + <p className="text-muted-foreground mt-2 text-sm md:text-base"> + {page.data.description} + </p> + )} + </header> + )} + <DocsRuntimeProvider> + <page.data.body components={mdxComponents} /> + </DocsRuntimeProvider> + {!isIndex && <DocsFooter previous={footerPrevious} next={footerNext} />} + </DocsBody> + </DocsPage> + ); +} + +export async function generateStaticParams() { + const pages = examples.getPages().map((page) => ({ + slug: page.slugs, + })); + + return [{ slug: [] }, ...pages]; +} + +export async function generateMetadata( + props: PageProps<"/examples/[[...slug]]">, +): Promise<Metadata> { + const { slug = [] } = await props.params; + const page = getPage(slug); + + return { + title: page.data.title, + description: page.data.description, + ...createOgMetadata(page.data.title, page.data.description), + }; +} diff --git a/apps/docs/app/(docs)/examples/layout.tsx b/apps/docs/app/(docs)/examples/layout.tsx new file mode 100644 index 0000000..e590297 --- /dev/null +++ b/apps/docs/app/(docs)/examples/layout.tsx @@ -0,0 +1,17 @@ +import type { ReactNode } from "react"; +import { examples } from "@/lib/source"; +import { DocsHeader } from "@/components/docs/layout/docs-header"; +import { DocsSidebarProvider } from "@/components/docs/contexts/sidebar"; +import { ExamplesShell } from "@/components/docs/layout/examples-shell"; +import { AssistantPanelProvider } from "@/components/docs/assistant/context"; + +export default function Layout({ children }: { children: ReactNode }) { + return ( + <AssistantPanelProvider> + <DocsSidebarProvider> + <DocsHeader section="Examples" sectionHref="/examples" /> + <ExamplesShell tree={examples.pageTree}>{children}</ExamplesShell> + </DocsSidebarProvider> + </AssistantPanelProvider> + ); +} diff --git a/apps/docs/app/(home)/blog/[slug]/page.tsx b/apps/docs/app/(home)/blog/[slug]/page.tsx new file mode 100644 index 0000000..6e74008 --- /dev/null +++ b/apps/docs/app/(home)/blog/[slug]/page.tsx @@ -0,0 +1,100 @@ +import type { Metadata } from "next"; +import { use } from "react"; +import { createOgMetadata } from "@/lib/og"; +import { notFound } from "next/navigation"; +import Link from "next/link"; +import { blog, type BlogPage } from "@/lib/source"; +import Image from "next/image"; +import profilePic from "@/components/home/testimonials/profiles/Mc0m3zkD_400x400.jpg"; +import { getMDXComponents } from "@/mdx-components"; +import { ArrowLeft } from "lucide-react"; +import { BlogTOC } from "@/components/blog/blog-toc"; + +interface Param { + slug: string; +} + +function formatDate(date: Date): string { + return date.toLocaleDateString("en-US", { + month: "long", + day: "numeric", + year: "numeric", + }); +} + +export default function Page(props: { + params: Promise<Param>; +}): React.ReactElement { + const params = use(props.params); + const page = blog.getPage([params.slug]) as BlogPage | undefined; + const mdxComponents = getMDXComponents({}); + + if (!page) notFound(); + + return ( + <> + <main className="mx-auto w-full max-w-3xl px-4 py-16 md:py-24"> + <Link + href="/blog" + className="text-muted-foreground hover:text-foreground inline-flex items-center gap-1.5 text-sm transition-colors" + > + <ArrowLeft className="h-3.5 w-3.5" /> + Blog + </Link> + + <header className="mt-8"> + {page.data.date && ( + <time className="text-muted-foreground text-sm"> + {formatDate(page.data.date)} + </time> + )} + <h1 className="mt-2 text-3xl font-medium tracking-tight"> + {page.data.title} + </h1> + {page.data.description && ( + <p className="text-muted-foreground mt-3 text-lg"> + {page.data.description} + </p> + )} + <div className="mt-6 flex items-center gap-2.5"> + <Image + src={profilePic} + alt="Simon Farshid" + width={28} + height={28} + className="rounded-full" + /> + <span className="text-muted-foreground text-sm">Simon Farshid</span> + </div> + </header> + + <article className="prose mt-12 max-w-none"> + <page.data.body components={mdxComponents} /> + </article> + </main> + + <BlogTOC items={page.data.toc} /> + </> + ); +} + +export function generateStaticParams(): Param[] { + return blog.getPages().map((page) => ({ + slug: page.slugs[0]!, + })); +} + +export async function generateMetadata(props: { + params: Promise<Param>; +}): Promise<Metadata> { + const params = await props.params; + const page = blog.getPage([params.slug]) as BlogPage | undefined; + + if (!page) return { title: "Not Found" }; + + return { + title: page.data.title, + description: page.data.description, + ...createOgMetadata(page.data.title, page.data.description), + }; +} diff --git a/apps/docs/app/(home)/blog/llms.md/[slug]/route.ts b/apps/docs/app/(home)/blog/llms.md/[slug]/route.ts new file mode 100644 index 0000000..b8c79b4 --- /dev/null +++ b/apps/docs/app/(home)/blog/llms.md/[slug]/route.ts @@ -0,0 +1,47 @@ +import { type NextRequest, NextResponse } from "next/server"; +import { AGENT_DOCS_DIRECTIVE_MARKDOWN } from "@/lib/agent-docs-directive"; +import { blog, type BlogPage } from "@/lib/source"; +import { notFound } from "next/navigation"; +import { remark } from "remark"; +import remarkGfm from "remark-gfm"; +import remarkMdx from "remark-mdx"; +import { remarkInclude } from "fumadocs-mdx/config"; + +const processor = remark().use(remarkMdx).use(remarkInclude).use(remarkGfm); + +export const revalidate = false; + +export async function GET( + _req: NextRequest, + { params }: { params: Promise<{ slug: string }> }, +) { + const { slug } = await params; + const page = blog.getPage([slug]) as BlogPage | undefined; + if (!page) notFound(); + + const processed = await processor.process({ + path: page.path, + value: await page.data.getText("processed"), + }); + + const text = `# ${page.data.title} +URL: ${page.url} +${page.data.description ? `\n${page.data.description}\n` : ""} +${AGENT_DOCS_DIRECTIVE_MARKDOWN} + +${processed.value}`; + + return new NextResponse(text, { + headers: { + "Content-Type": "text/markdown; charset=utf-8", + "Cache-Control": "no-cache, must-revalidate", + "X-Robots-Tag": "noindex, follow", + }, + }); +} + +export function generateStaticParams() { + return blog.getPages().map((page) => ({ + slug: page.slugs[0]!, + })); +} diff --git a/apps/docs/app/(home)/blog/page.tsx b/apps/docs/app/(home)/blog/page.tsx new file mode 100644 index 0000000..8d2035d --- /dev/null +++ b/apps/docs/app/(home)/blog/page.tsx @@ -0,0 +1,67 @@ +import type { Metadata } from "next"; +import Link from "next/link"; +import { createOgMetadata } from "@/lib/og"; +import { blog, type BlogPage } from "@/lib/source"; + +const title = "Blog"; +const description = "News and updates from assistant-ui"; + +export const metadata: Metadata = { + title, + description, + ...createOgMetadata(title, description), +}; + +function formatDate(date: Date): string { + return date.toLocaleDateString("en-US", { + month: "short", + day: "numeric", + year: "numeric", + }); +} + +export default function BlogIndex(): React.ReactElement { + const posts = [...(blog.getPages() as BlogPage[])].sort( + (a, b) => (b.data.date?.getTime() ?? 0) - (a.data.date?.getTime() ?? 0), + ); + + return ( + <main className="mx-auto w-full max-w-3xl px-4 py-16 md:py-24"> + <header className="mb-12"> + <p className="text-muted-foreground mb-3 text-sm">Blog</p> + <h1 className="text-2xl font-medium tracking-tight"> + News and updates + </h1> + <p className="text-muted-foreground mt-2"> + The latest from assistant-ui. + </p> + </header> + + <div className="space-y-8"> + {posts.map((post) => ( + <Link + key={post.url} + href={post.url} + className="group block sm:flex sm:items-start sm:justify-between sm:gap-4" + > + <div className="min-w-0 sm:flex-1"> + <h2 className="text-foreground/80 group-hover:text-foreground font-medium transition-colors"> + {post.data.title} + </h2> + {post.data.description && ( + <p className="text-muted-foreground mt-1 text-sm"> + {post.data.description} + </p> + )} + </div> + {post.data.date && ( + <time className="text-muted-foreground mt-2 block text-sm sm:mt-0 sm:shrink-0"> + {formatDate(post.data.date)} + </time> + )} + </Link> + ))} + </div> + </main> + ); +} diff --git a/apps/docs/app/(home)/brand/page.tsx b/apps/docs/app/(home)/brand/page.tsx new file mode 100644 index 0000000..f8ec09f --- /dev/null +++ b/apps/docs/app/(home)/brand/page.tsx @@ -0,0 +1,73 @@ +import type { Metadata } from "next"; +import Link from "next/link"; + +export const metadata: Metadata = { + title: "Brand Guidelines — assistant-ui", + description: "Guidelines for using the assistant-ui brand assets.", +}; + +export default function BrandPage() { + return ( + <div className="mx-auto max-w-2xl px-4 py-20"> + <h1 className="text-4xl font-bold tracking-tight">Brand Guidelines</h1> + + <section className="mt-10"> + <h2 className="text-xl font-semibold">Usage</h2> + <p className="text-muted-foreground mt-3 leading-relaxed"> + You are free to use the assistant-ui logo and brand assets to + reference assistant-ui in your projects, blog posts, talks, and social + media. Please follow these guidelines: + </p> + <ul className="text-muted-foreground mt-4 list-disc space-y-2 pl-6 leading-relaxed"> + <li>Use the original, unmodified logo files from the brand kit.</li> + <li> + Do not alter the colors, proportions, or orientation of the logo. + </li> + <li> + Maintain sufficient spacing around the logo — avoid crowding it with + other elements. + </li> + <li> + Do not use the assistant-ui logo to imply endorsement or affiliation + without permission. + </li> + </ul> + </section> + + <section className="mt-10"> + <h2 className="text-xl font-semibold">Attribution</h2> + <p className="text-muted-foreground mt-3 leading-relaxed"> + The assistant-ui logo is based on the{" "} + <Link + href="https://lucide.dev" + target="_blank" + rel="noopener noreferrer" + className="text-foreground underline underline-offset-4" + > + Lucide + </Link>{" "} + icon set, licensed under the{" "} + <Link + href="https://github.com/lucide-icons/lucide/blob/main/LICENSE" + target="_blank" + rel="noopener noreferrer" + className="text-foreground underline underline-offset-4" + > + ISC License + </Link> + . + </p> + </section> + + <section className="mt-10"> + <a + href="/assistant-ui-brand.zip" + download + className="bg-foreground text-background inline-flex items-center rounded-lg px-5 py-2.5 text-sm font-medium transition-opacity hover:opacity-90" + > + Download brand assets + </a> + </section> + </div> + ); +} diff --git a/apps/docs/app/(home)/careers/[slug]/page.tsx b/apps/docs/app/(home)/careers/[slug]/page.tsx new file mode 100644 index 0000000..fb77135 --- /dev/null +++ b/apps/docs/app/(home)/careers/[slug]/page.tsx @@ -0,0 +1,92 @@ +import { use, type ReactElement } from "react"; +import { notFound } from "next/navigation"; +import Link from "next/link"; + +import type { Metadata } from "next"; +import { createOgMetadata } from "@/lib/og"; +import { careers, type CareerPage } from "@/lib/source"; +import { getMDXComponents } from "@/mdx-components"; +import { ApplyForm } from "@/components/careers/apply-form"; +import { ArrowLeft } from "lucide-react"; + +interface Params { + slug: string; +} + +export default function CareerRolePage({ + params, +}: { + params: Promise<Params>; +}): ReactElement { + const { slug } = use(params); + const page = careers.getPage([slug]) as CareerPage | undefined; + + if (!page) { + notFound(); + } + + const role = page; + const mdxComponents = getMDXComponents({}); + + return ( + <main className="mx-auto w-full max-w-3xl px-4 py-16 md:py-24"> + <Link + href="/careers" + className="text-muted-foreground hover:text-foreground inline-flex items-center gap-1.5 text-sm transition-colors" + > + <ArrowLeft className="h-3.5 w-3.5" /> + Careers + </Link> + + <header className="mt-8"> + <p className="text-muted-foreground text-sm"> + {role.data.location} · {role.data.type} + {role.data.salary && ` · ${role.data.salary}`} + </p> + <h1 className="mt-2 text-3xl font-medium tracking-tight"> + {role.data.title} + </h1> + {role.data.summary && ( + <p className="text-muted-foreground mt-3 text-lg"> + {role.data.summary} + </p> + )} + </header> + + <article className="prose mt-12 max-w-none"> + <role.data.body components={mdxComponents} /> + </article> + + <section className="mt-16"> + <h2 className="text-xl font-medium">Apply for this role</h2> + <p className="text-muted-foreground mt-2 text-sm"> + Send a quick introduction and a few links. We read every submission. + </p> + <div className="mt-6"> + <ApplyForm roleTitle={role.data.title} /> + </div> + </section> + </main> + ); +} + +export function generateStaticParams(): Params[] { + return careers.getPages().map((page) => ({ + slug: page.slugs[0]!, + })); +} + +export async function generateMetadata(props: { + params: Promise<Params>; +}): Promise<Metadata> { + const params = await props.params; + const page = careers.getPage([params.slug]) as CareerPage | undefined; + + if (!page) return { title: "Not Found" }; + + return { + title: page.data.title, + description: page.data.summary, + ...createOgMetadata(page.data.title, page.data.summary), + }; +} diff --git a/apps/docs/app/(home)/careers/page.tsx b/apps/docs/app/(home)/careers/page.tsx new file mode 100644 index 0000000..873025b --- /dev/null +++ b/apps/docs/app/(home)/careers/page.tsx @@ -0,0 +1,89 @@ +import Link from "next/link"; +import type { ReactElement } from "react"; +import type { Metadata } from "next"; +import { createOgMetadata } from "@/lib/og"; +import { careers, type CareerPage } from "@/lib/source"; + +const title = "Careers"; +const description = + "Help build the future of agentic UI. Explore open roles at assistant-ui."; + +export const metadata: Metadata = { + title, + description, + ...createOgMetadata(title, description), +}; + +const roleOrder = (value: unknown, fallback: number) => { + if (typeof value === "number") return value; + const parsed = Number(value); + return Number.isFinite(parsed) ? parsed : fallback; +}; + +export default function CareersPage(): ReactElement { + const roles = [...(careers.getPages() as CareerPage[])].sort((a, b) => { + const orderA = roleOrder(a.data.order, Number.MAX_SAFE_INTEGER); + const orderB = roleOrder(b.data.order, Number.MAX_SAFE_INTEGER); + if (orderA === orderB) { + return a.data.title.localeCompare(b.data.title); + } + return orderA - orderB; + }); + + return ( + <main className="mx-auto w-full max-w-3xl px-4 py-16 md:py-24"> + <header className="mb-12"> + <p className="text-muted-foreground mb-3 text-sm">Careers</p> + <h1 className="text-2xl font-medium tracking-tight"> + Build the future of agentic UI + </h1> + <p className="text-muted-foreground mt-2 max-w-xl"> + We're a small, product-obsessed team crafting the tools that + power the next generation of AI-native products. + </p> + </header> + + <section> + <div className="mb-6 flex items-center justify-between"> + <h2 className="text-sm font-medium">Open roles</h2> + <p className="text-muted-foreground text-sm"> + {roles.length} {roles.length === 1 ? "position" : "positions"} + </p> + </div> + + <div className="space-y-6"> + {roles.map((role) => ( + <Link key={role.url} href={role.url} className="group block"> + <h3 className="text-foreground/80 group-hover:text-foreground font-medium transition-colors"> + {role.data.title} + </h3> + <p className="text-muted-foreground mt-1 flex flex-wrap items-center gap-x-2 gap-y-1 text-sm"> + <span>{role.data.location}</span> + <span className="text-muted-foreground/40">·</span> + <span>{role.data.type}</span> + {role.data.salary && ( + <> + <span className="text-muted-foreground/40">·</span> + <span>{role.data.salary}</span> + </> + )} + </p> + </Link> + ))} + </div> + </section> + + <section className="mt-16"> + <p className="text-muted-foreground"> + Don't see the perfect fit?{" "} + <a + href="mailto:hello@assistant-ui.com" + className="text-foreground hover:text-foreground/70 font-medium transition-colors" + > + Reach out anyway → + </a> + </p> + </section> + </main> + ); +} diff --git a/apps/docs/app/(home)/changelog/changelog-list.tsx b/apps/docs/app/(home)/changelog/changelog-list.tsx new file mode 100644 index 0000000..07800c4 --- /dev/null +++ b/apps/docs/app/(home)/changelog/changelog-list.tsx @@ -0,0 +1,345 @@ +"use client"; + +import { useMemo, useState } from "react"; +import Link from "next/link"; +import ReactMarkdown, { type Components } from "react-markdown"; +import { ChevronRight } from "lucide-react"; +import { Button } from "@/components/ui/button"; +import type { ReleaseGroup, PackageRelease } from "@/lib/releases"; +import { + groupByType, + parseRelease, + TYPE_LABELS, + type ChangeType, + type ParsedBullet, +} from "@/lib/changelog-parse"; +import { cn } from "@/lib/utils"; + +const PAGE_SIZE = 10; + +function formatDate(date: string): string { + return new Date(date).toLocaleDateString("en-US", { + month: "short", + day: "numeric", + year: "numeric", + }); +} + +function extractChangeType( + markdown: string, +): "major" | "minor" | "patch" | null { + const match = markdown.match(/^#{1,6}\s+(Major|Minor|Patch)\s+Changes/im); + if (!match) return null; + return match[1]!.toLowerCase() as "major" | "minor" | "patch"; +} + +const semverBadge = { + major: "bg-red-100 text-red-700 dark:bg-red-900/30 dark:text-red-400", + minor: "bg-blue-100 text-blue-700 dark:bg-blue-900/30 dark:text-blue-400", + patch: "bg-muted text-muted-foreground", +} as const; + +const TYPE_BADGE: Record<ChangeType, string> = { + breaking: "bg-red-500/10 text-red-700 ring-red-500/20 dark:text-red-400", + feat: "bg-emerald-500/10 text-emerald-700 ring-emerald-500/20 dark:text-emerald-400", + fix: "bg-blue-500/10 text-blue-700 ring-blue-500/20 dark:text-blue-400", + perf: "bg-amber-500/10 text-amber-700 ring-amber-500/20 dark:text-amber-400", + refactor: + "bg-violet-500/10 text-violet-700 ring-violet-500/20 dark:text-violet-400", + revert: "bg-rose-500/10 text-rose-700 ring-rose-500/20 dark:text-rose-400", + docs: "bg-sky-500/10 text-sky-700 ring-sky-500/20 dark:text-sky-400", + style: "bg-pink-500/10 text-pink-700 ring-pink-500/20 dark:text-pink-400", + test: "bg-teal-500/10 text-teal-700 ring-teal-500/20 dark:text-teal-400", + build: + "bg-indigo-500/10 text-indigo-700 ring-indigo-500/20 dark:text-indigo-400", + ci: "bg-cyan-500/10 text-cyan-700 ring-cyan-500/20 dark:text-cyan-400", + chore: "bg-muted text-muted-foreground ring-border", + other: "bg-muted text-muted-foreground ring-border", +}; + +const TYPE_SHORT_LABEL: Record<ChangeType, string> = { + breaking: "BREAKING", + feat: "feat", + fix: "fix", + perf: "perf", + refactor: "refactor", + revert: "revert", + docs: "docs", + style: "style", + test: "test", + build: "build", + ci: "ci", + chore: "chore", + other: "misc", +}; + +function TypeBadge({ + type, + scope, +}: { + type: ChangeType; + scope?: string | undefined; +}) { + return ( + <span + className={cn( + "inline-flex shrink-0 items-baseline gap-1 rounded-md px-1.5 py-0.5 font-mono text-[10px] leading-none font-medium tracking-wide uppercase ring-1 ring-inset", + TYPE_BADGE[type], + )} + > + <span>{TYPE_SHORT_LABEL[type]}</span> + {scope ? ( + <span className="font-normal normal-case opacity-70">· {scope}</span> + ) : null} + </span> + ); +} + +const inlineMarkdownComponents: Components = { + p: ({ children }) => <>{children}</>, + code: ({ children }) => ( + <code className="bg-foreground/[0.07] text-foreground ring-foreground/10 rounded px-1 py-0.5 font-mono text-[0.85em] ring-1"> + {children} + </code> + ), + a: ({ children, href }) => ( + <a + href={href} + target="_blank" + rel="noopener noreferrer" + className="text-foreground/90 decoration-foreground/30 hover:decoration-foreground underline underline-offset-2 transition-colors" + > + {children} + </a> + ), +}; + +const bodyMarkdownComponents: Components = { + ...inlineMarkdownComponents, + p: ({ children }) => ( + <p className="mt-2 leading-relaxed first:mt-0">{children}</p> + ), + ul: ({ children }) => ( + <ul className="mt-2 list-disc space-y-1 pl-5 first:mt-0">{children}</ul> + ), + li: ({ children }) => <li>{children}</li>, +}; + +function MetaLine({ item }: { item: ParsedBullet }) { + const parts: React.ReactNode[] = []; + if (item.pr) { + parts.push( + <a + key="pr" + href={item.pr.url} + target="_blank" + rel="noopener noreferrer" + className="hover:text-foreground font-mono transition-colors" + > + #{item.pr.number} + </a>, + ); + } + if (item.hash) { + parts.push( + <a + key="hash" + href={item.hash.url} + target="_blank" + rel="noopener noreferrer" + className="hover:text-foreground font-mono transition-colors" + > + {item.hash.value} + </a>, + ); + } + if (item.author) { + parts.push( + <a + key="author" + href={item.author.url} + target="_blank" + rel="noopener noreferrer" + className="hover:text-foreground transition-colors" + > + @{item.author.handle} + </a>, + ); + } + if (parts.length === 0) return null; + const interleaved: React.ReactNode[] = []; + parts.forEach((node, i) => { + if (i > 0) { + interleaved.push( + <span key={`sep-${i}`} className="opacity-40 select-none"> + · + </span>, + ); + } + interleaved.push(node); + }); + return ( + <div className="text-muted-foreground/80 flex flex-wrap items-center gap-x-1.5 gap-y-1 text-xs"> + {interleaved} + </div> + ); +} + +function BulletItem({ item }: { item: ParsedBullet }) { + const [expanded, setExpanded] = useState(false); + const hasBody = item.body.length > 0; + + return ( + <div className="flex flex-col gap-1.5"> + <div className="flex flex-wrap items-baseline gap-x-2 gap-y-1"> + <TypeBadge type={item.type} scope={item.scope} /> + <span className="text-foreground text-sm leading-relaxed"> + <ReactMarkdown components={inlineMarkdownComponents}> + {item.description || "(no description)"} + </ReactMarkdown> + </span> + </div> + <MetaLine item={item} /> + {hasBody ? ( + <div className="text-muted-foreground text-sm"> + <button + type="button" + onClick={() => setExpanded((e) => !e)} + className="text-muted-foreground/70 hover:text-foreground text-xs transition-colors" + > + {expanded ? "Hide description" : "Show description"} + </button> + {expanded ? ( + <div className="mt-2"> + <ReactMarkdown components={bodyMarkdownComponents}> + {item.body} + </ReactMarkdown> + </div> + ) : null} + </div> + ) : null} + </div> + ); +} + +function TypeGroup({ + type, + items, +}: { + type: ChangeType; + items: ParsedBullet[]; +}) { + return ( + <div className="flex flex-col gap-3"> + <div className="border-border/60 flex items-baseline gap-2 border-b pb-1.5"> + <h4 className="text-foreground/70 text-xs font-medium tracking-wide uppercase"> + {TYPE_LABELS[type]} + </h4> + <span className="text-muted-foreground text-xs tabular-nums"> + {items.length} + </span> + </div> + <div className="flex flex-col gap-4"> + {items.map((item, i) => ( + <BulletItem key={i} item={item} /> + ))} + </div> + </div> + ); +} + +function ReleaseEntry({ release }: { release: PackageRelease }) { + const semver = extractChangeType(release.body); + const parsed = useMemo(() => parseRelease(release.body), [release.body]); + const groups = useMemo(() => groupByType(parsed.bullets), [parsed.bullets]); + + return ( + <details className="group/release" open> + <summary className="flex cursor-pointer list-none items-center gap-2 py-1 [&::-webkit-details-marker]:hidden"> + <ChevronRight className="text-muted-foreground size-3.5 shrink-0 transition-transform group-open/release:rotate-90" /> + <span className="text-foreground/80 group-hover/release:text-foreground font-mono text-sm font-medium transition-colors"> + {release.pkg}@{release.version} + </span> + {semver && ( + <span + className={`shrink-0 rounded-full px-1.5 py-0.5 text-[10px] leading-none font-medium ${semverBadge[semver]}`} + > + {semver} + </span> + )} + <Link + href={release.url} + target="_blank" + rel="noopener noreferrer" + onClick={(e) => e.stopPropagation()} + className="text-muted-foreground hover:text-foreground ml-auto shrink-0 text-xs transition-colors" + > + GitHub → + </Link> + </summary> + <div className="mt-3 ml-[1.375rem] flex flex-col gap-6 pb-2"> + {parsed.preamble ? ( + <div className="text-muted-foreground text-sm"> + <ReactMarkdown components={bodyMarkdownComponents}> + {parsed.preamble} + </ReactMarkdown> + </div> + ) : null} + {groups.map(({ type, items }) => ( + <TypeGroup key={type} type={type} items={items} /> + ))} + {groups.length === 0 && !parsed.preamble ? ( + <p className="text-muted-foreground text-sm">No notes.</p> + ) : null} + </div> + </details> + ); +} + +function DateSection({ group }: { group: ReleaseGroup }) { + return ( + <section> + <h2 className="text-lg font-medium tracking-tight"> + {formatDate(group.date)} + </h2> + <p className="text-muted-foreground mt-1 text-sm"> + {group.releases.length}{" "} + {group.releases.length === 1 ? "package" : "packages"} + </p> + + <div className="mt-4 space-y-1"> + {group.releases.map((r) => ( + <ReleaseEntry key={`${r.pkg}@${r.version}`} release={r} /> + ))} + </div> + </section> + ); +} + +export function ChangelogList({ groups }: { groups: ReleaseGroup[] }) { + const [count, setCount] = useState(PAGE_SIZE); + + const visible = groups.slice(0, count); + const remaining = groups.length - count; + + return ( + <> + <div className="space-y-10"> + {visible.map((group) => ( + <DateSection key={group.date} group={group} /> + ))} + </div> + + {remaining > 0 && ( + <div className="mt-12 flex justify-center"> + <Button + variant="outline" + onClick={() => setCount((c) => c + PAGE_SIZE)} + > + Load more ({remaining}) + </Button> + </div> + )} + </> + ); +} diff --git a/apps/docs/app/(home)/changelog/package-filter.tsx b/apps/docs/app/(home)/changelog/package-filter.tsx new file mode 100644 index 0000000..ce1ee19 --- /dev/null +++ b/apps/docs/app/(home)/changelog/package-filter.tsx @@ -0,0 +1,90 @@ +"use client"; + +import { useRouter } from "next/navigation"; +import { + SelectContent, + SelectGroup, + SelectItem, + SelectLabel, + SelectRoot, + SelectSeparator, + SelectTrigger, + SelectValue, +} from "@/components/assistant-ui/select"; + +const ALL = "__all__"; + +type PackageGroup = { + label: string; + packages: string[]; +}; + +function groupPackages(packages: string[]): PackageGroup[] { + const core: string[] = []; + const integrations: string[] = []; + const other: string[] = []; + + for (const pkg of packages) { + if ( + pkg === "@assistant-ui/react" || + pkg === "@assistant-ui/core" || + pkg === "@assistant-ui/store" || + pkg === "@assistant-ui/tap" || + pkg === "@assistant-ui/styles" || + pkg === "@assistant-ui/ui" || + pkg === "assistant-stream" || + pkg === "assistant-ui" + ) { + core.push(pkg); + } else if (pkg.startsWith("@assistant-ui/react-")) { + integrations.push(pkg); + } else { + other.push(pkg); + } + } + + const groups: PackageGroup[] = []; + if (core.length > 0) groups.push({ label: "Core", packages: core }); + if (integrations.length > 0) + groups.push({ label: "Integrations", packages: integrations }); + if (other.length > 0) groups.push({ label: "Other", packages: other }); + return groups; +} + +export function PackageFilter({ + packages, + value, +}: { + packages: string[]; + value: string | undefined; +}) { + const router = useRouter(); + const groups = groupPackages(packages); + + return ( + <SelectRoot + value={value ?? ALL} + onValueChange={(v) => { + router.push(v === ALL ? "/changelog" : `?pkg=${encodeURIComponent(v)}`); + }} + > + <SelectTrigger size="sm" className="w-56 font-mono text-xs"> + <SelectValue /> + </SelectTrigger> + <SelectContent> + <SelectItem value={ALL}>All packages</SelectItem> + {groups.map((group, i) => ( + <SelectGroup key={group.label}> + {i > 0 && <SelectSeparator />} + <SelectLabel>{group.label}</SelectLabel> + {group.packages.map((pkg) => ( + <SelectItem key={pkg} value={pkg} className="font-mono text-xs"> + {pkg} + </SelectItem> + ))} + </SelectGroup> + ))} + </SelectContent> + </SelectRoot> + ); +} diff --git a/apps/docs/app/(home)/changelog/page.tsx b/apps/docs/app/(home)/changelog/page.tsx new file mode 100644 index 0000000..c08d764 --- /dev/null +++ b/apps/docs/app/(home)/changelog/page.tsx @@ -0,0 +1,65 @@ +import type { Metadata } from "next"; +import { createOgMetadata } from "@/lib/og"; +import { fetchReleases } from "@/lib/releases"; +import { PackageFilter } from "./package-filter"; +import { ChangelogList } from "./changelog-list"; + +const title = "Changelog"; +const description = "Release notes for all assistant-ui packages."; + +export const metadata: Metadata = { + title, + description, + ...createOgMetadata(title, description), +}; + +export default async function ChangelogPage({ + searchParams, +}: { + searchParams: Promise<{ pkg?: string }>; +}) { + const { pkg } = await searchParams; + const allGroups = await fetchReleases(); + + const allPackages = Array.from( + new Set(allGroups.flatMap((g) => g.releases.map((r) => r.pkg))), + ).sort(); + + const groups = pkg + ? allGroups + .map((group) => ({ + ...group, + releases: group.releases.filter((r) => r.pkg === pkg), + })) + .filter((group) => group.releases.length > 0) + : allGroups; + + return ( + <main className="mx-auto w-full max-w-5xl px-4 py-16 md:py-24"> + <header className="mb-12"> + <p className="text-muted-foreground mb-3 text-sm">Changelog</p> + <h1 className="text-2xl font-medium tracking-tight">Release history</h1> + <p className="text-muted-foreground mt-2"> + All releases published from the assistant-ui monorepo, grouped by + date. + </p> + </header> + + {allPackages.length > 0 && ( + <div className="mb-8"> + <PackageFilter packages={allPackages} value={pkg} /> + </div> + )} + + {groups.length > 0 ? ( + <ChangelogList groups={groups} /> + ) : ( + <p className="text-muted-foreground"> + {allGroups.length === 0 + ? "Unable to load releases. Please try again later." + : "No releases found for this package."} + </p> + )} + </main> + ); +} diff --git a/apps/docs/app/(home)/layout.tsx b/apps/docs/app/(home)/layout.tsx new file mode 100644 index 0000000..f23eabb --- /dev/null +++ b/apps/docs/app/(home)/layout.tsx @@ -0,0 +1,18 @@ +import type { ReactNode } from "react"; +import { Header } from "@/components/shared/header"; +import { Footer } from "@/components/shared/footer"; +import { HomeAssistant } from "@/components/home/home-assistant"; + +export default function Layout({ + children, +}: { + children: ReactNode; +}): React.ReactElement { + return ( + <HomeAssistant> + <Header /> + {children} + <Footer /> + </HomeAssistant> + ); +} diff --git a/apps/docs/app/(home)/llms-full.txt/route.ts b/apps/docs/app/(home)/llms-full.txt/route.ts new file mode 100644 index 0000000..c509c82 --- /dev/null +++ b/apps/docs/app/(home)/llms-full.txt/route.ts @@ -0,0 +1,19 @@ +import { examples, source } from "@/lib/source"; +import { getLLMText } from "@/lib/get-llm-text"; + +export const revalidate = false; + +export async function GET() { + const scan = [...source.getPages(), ...examples.getPages()].map((page) => + getLLMText(page), + ); + const scanned = await Promise.all(scan); + + return new Response(scanned.join("\n\n"), { + headers: { + "Cache-Control": "no-cache, must-revalidate", + "Content-Type": "text/plain; charset=utf-8", + "X-Robots-Tag": "noindex, follow", + }, + }); +} diff --git a/apps/docs/app/(home)/llms.mdx/[[...slug]]/route.ts b/apps/docs/app/(home)/llms.mdx/[[...slug]]/route.ts new file mode 100644 index 0000000..933ee33 --- /dev/null +++ b/apps/docs/app/(home)/llms.mdx/[[...slug]]/route.ts @@ -0,0 +1,28 @@ +import { type NextRequest, NextResponse } from "next/server"; +import { getLLMText } from "@/lib/get-llm-text"; +import { source } from "@/lib/source"; +import { notFound } from "next/navigation"; + +// The flavor query parameter varies the response, so this route renders per +// request instead of being prerendered. +export const dynamic = "force-dynamic"; + +export async function GET( + req: NextRequest, + { params }: { params: Promise<{ slug?: string[] }> }, +) { + const { slug } = await params; + const page = source.getPage(slug); + if (!page) notFound(); + + const flavor = + req.nextUrl.searchParams.get("view") === "radix-ui" ? "radix" : "base"; + + return new NextResponse(await getLLMText(page, { flavor }), { + headers: { + "Cache-Control": "no-cache, must-revalidate", + "Content-Type": "text/markdown; charset=utf-8", + "X-Robots-Tag": "noindex, follow", + }, + }); +} diff --git a/apps/docs/app/(home)/llms.mdx/examples/[[...slug]]/route.ts b/apps/docs/app/(home)/llms.mdx/examples/[[...slug]]/route.ts new file mode 100644 index 0000000..1178ef9 --- /dev/null +++ b/apps/docs/app/(home)/llms.mdx/examples/[[...slug]]/route.ts @@ -0,0 +1,29 @@ +import { type NextRequest, NextResponse } from "next/server"; +import { getLLMText } from "@/lib/get-llm-text"; +import { examples } from "@/lib/source"; +import { notFound } from "next/navigation"; + +export const revalidate = false; + +export async function GET( + _req: NextRequest, + { params }: { params: Promise<{ slug?: string[] }> }, +) { + const { slug } = await params; + const page = examples.getPage(slug); + if (!page) notFound(); + + return new NextResponse(await getLLMText(page), { + headers: { + "Cache-Control": "no-cache, must-revalidate", + "Content-Type": "text/markdown; charset=utf-8", + "X-Robots-Tag": "noindex, follow", + }, + }); +} + +export function generateStaticParams() { + return examples.getPages().map((page) => ({ + slug: page.slugs, + })); +} diff --git a/apps/docs/app/(home)/llms.txt/route.ts b/apps/docs/app/(home)/llms.txt/route.ts new file mode 100644 index 0000000..db1677f --- /dev/null +++ b/apps/docs/app/(home)/llms.txt/route.ts @@ -0,0 +1,13 @@ +import { examples, source } from "@/lib/source"; +import { buildLLMSIndex } from "@/lib/llms-index"; + +export const revalidate = false; + +export async function GET() { + return new Response(buildLLMSIndex(source.getPages(), examples.getPages()), { + headers: { + "Cache-Control": "no-cache, must-revalidate", + "Content-Type": "text/plain; charset=utf-8", + }, + }); +} diff --git a/apps/docs/app/(home)/packages/page.tsx b/apps/docs/app/(home)/packages/page.tsx new file mode 100644 index 0000000..805d7d8 --- /dev/null +++ b/apps/docs/app/(home)/packages/page.tsx @@ -0,0 +1,316 @@ +import type { Metadata } from "next"; +import Link from "next/link"; +import { + ArrowLeft, + ArrowUpRight, + Download, + Layers, + Package, +} from "lucide-react"; +import { + PACKAGES, + PACKAGE_CATEGORIES, + fetchNpmDownloads, + type PackageCategory, + type PackageInfo, +} from "@/lib/traction"; +import { formatCompact, formatNumber } from "@/lib/format"; +import { Sparkline } from "@/components/traction/sparkline"; +import { TopPackagesBar } from "@/components/traction/top-packages-bar"; +import { createOgMetadata } from "@/lib/og"; + +const title = "Packages"; +const description = + "Every assistant-ui package on npm, grouped by surface area and ranked by weekly downloads."; + +export const metadata: Metadata = { + title, + description, + ...createOgMetadata(title, description), +}; + +const HERO_STAT_ICONS = { + Package, + Layers, + Download, +}; + +export default async function PackagesPage({ + searchParams, +}: { + searchParams: Promise<{ refresh?: string }>; +}) { + const forceFresh = (await searchParams).refresh === "true"; + const npm = await fetchNpmDownloads(forceFresh ? 0 : undefined); + + const topPackages = PACKAGES.filter((pkg) => !pkg.deprecated) + .map((pkg) => ({ + name: pkg.name, + weekly: npm.perPackage[pkg.name]?.weekly ?? 0, + })) + .filter((row) => row.weekly > 0) + .sort((a, b) => b.weekly - a.weekly) + .slice(0, 12); + + const grouped = groupByCategory(PACKAGES); + const visibleCategories = ( + Object.keys(PACKAGE_CATEGORIES) as PackageCategory[] + ).filter((c) => (grouped[c]?.length ?? 0) > 0); + + const activeCount = PACKAGES.filter((pkg) => !pkg.deprecated).length; + const surfaceCount = visibleCategories.filter( + (c) => c !== "deprecated", + ).length; + + const totalWeekly = Object.values(npm.perPackage).reduce( + (sum, p) => sum + (p?.weekly ?? 0), + 0, + ); + + const heroStats = [ + { + icon: HERO_STAT_ICONS.Package, + value: activeCount.toString(), + label: "Packages", + caption: "across the ecosystem", + }, + { + icon: HERO_STAT_ICONS.Layers, + value: surfaceCount.toString(), + label: "Surfaces", + caption: "categories shipped", + }, + { + icon: HERO_STAT_ICONS.Download, + value: totalWeekly > 0 ? formatCompact(totalWeekly) : "—", + label: "Weekly downloads", + caption: "combined across npm", + }, + ]; + + return ( + <main className="mx-auto w-full max-w-7xl px-4 pt-14 pb-16 md:pb-24"> + <header className="mb-12 max-w-3xl"> + <Link + href="/traction" + className="text-muted-foreground hover:text-foreground mb-3 inline-flex items-center gap-1.5 text-sm transition-colors" + > + <ArrowLeft className="size-3.5" /> + Back to traction + </Link> + <h1 className="text-3xl font-medium tracking-tight md:text-4xl"> + Every distribution, in one place. + </h1> + <p className="text-muted-foreground mt-3 md:text-lg"> + {activeCount} packages on npm, grouped by surface area. Pick the one + that fits your stack. + </p> + </header> + + <section className="mb-12"> + <div className="border-border bg-border grid grid-cols-1 gap-px overflow-hidden rounded-xl border md:grid-cols-3"> + {heroStats.map((stat) => { + const Icon = stat.icon; + return ( + <div + key={stat.label} + className="bg-background flex flex-col gap-3 p-6" + > + <Icon className="text-muted-foreground size-4" /> + <div className="text-3xl font-medium tracking-tight tabular-nums md:text-4xl"> + {stat.value} + </div> + <div className="flex flex-col gap-0.5"> + <span className="text-sm">{stat.label}</span> + <span className="text-muted-foreground text-xs"> + {stat.caption} + </span> + </div> + </div> + ); + })} + </div> + </section> + + {topPackages.length > 0 ? ( + <section className="border-border mb-12 rounded-xl border p-4 md:p-6"> + <div className="mb-4 flex items-end justify-between gap-3"> + <div className="flex flex-col gap-0.5"> + <h2 className="text-sm font-medium"> + Top packages by weekly downloads + </h2> + <p className="text-muted-foreground text-xs"> + Last 7 days, npm registry. + </p> + </div> + </div> + <TopPackagesBar rows={topPackages} /> + </section> + ) : null} + + <nav + aria-label="Package categories" + className="border-border bg-background/85 sticky top-12 z-10 -mx-4 mb-12 border-y px-4 py-3 backdrop-blur" + > + <ul className="flex flex-wrap gap-1.5"> + {visibleCategories.map((category) => { + const meta = PACKAGE_CATEGORIES[category]; + const count = grouped[category]?.length ?? 0; + return ( + <li key={category}> + <a + href={`#${category}`} + className="bg-muted/40 text-muted-foreground hover:border-border hover:bg-muted hover:text-foreground flex items-center gap-1.5 rounded-md border border-transparent px-2.5 py-1 text-xs transition-colors" + > + {meta.label} + <span className="text-muted-foreground/70 tabular-nums"> + {count} + </span> + </a> + </li> + ); + })} + </ul> + </nav> + + <section className="flex flex-col gap-12"> + {visibleCategories.map((category) => { + const items = grouped[category]!; + const meta = PACKAGE_CATEGORIES[category]; + return ( + <div + key={category} + id={category} + className="flex scroll-mt-28 flex-col gap-4" + > + <div className="border-border flex items-end justify-between gap-4 border-b pb-3"> + <div className="flex flex-col gap-0.5"> + <h3 className="text-sm font-medium">{meta.label}</h3> + <p className="text-muted-foreground text-xs"> + {meta.description} + </p> + </div> + <span className="text-muted-foreground text-xs tabular-nums"> + {items.length} + </span> + </div> + <div className="grid gap-3 md:grid-cols-2"> + {items.map((pkg) => { + const stats = npm.perPackage[pkg.name]; + return ( + <PackageRow + key={pkg.name} + pkg={pkg} + weekly={stats?.weekly ?? 0} + series={stats?.series ?? []} + monthly={stats?.monthly ?? 0} + prevMonthly={stats?.prevMonthly ?? 0} + /> + ); + })} + </div> + </div> + ); + })} + </section> + </main> + ); +} + +type MoMBadge = { + label: string; + tone: "up" | "down" | "flat"; +}; + +function computeMoM(monthly: number, prevMonthly: number): MoMBadge | null { + if (prevMonthly < 100 || monthly === 0) return null; + const change = ((monthly - prevMonthly) / prevMonthly) * 100; + if (!Number.isFinite(change)) return null; + const capped = Math.max(-99, Math.min(999, change)); + const sign = capped > 0 ? "+" : ""; + const rounded = Math.round(capped); + let tone: MoMBadge["tone"] = "flat"; + if (rounded >= 5) tone = "up"; + else if (rounded <= -5) tone = "down"; + return { label: `${sign}${rounded}%`, tone }; +} + +const MOM_TONE: Record<MoMBadge["tone"], string> = { + up: "text-emerald-600 dark:text-emerald-400", + down: "text-rose-600 dark:text-rose-400", + flat: "text-muted-foreground", +}; + +function PackageRow({ + pkg, + weekly, + series, + monthly, + prevMonthly, +}: { + pkg: PackageInfo; + weekly: number; + series: number[]; + monthly: number; + prevMonthly: number; +}) { + const npmHref = `https://www.npmjs.com/package/${pkg.name}`; + const mom = computeMoM(monthly, prevMonthly); + return ( + <a + href={npmHref} + target="_blank" + rel="noopener noreferrer" + className="group border-border hover:border-foreground/30 hover:bg-muted/40 flex flex-col gap-1.5 rounded-lg border p-4 transition-colors" + > + <div className="flex items-center justify-between gap-3"> + <div className="flex min-w-0 items-center gap-2"> + <span className="truncate font-mono text-sm font-medium"> + {pkg.name} + </span> + {pkg.deprecated ? ( + <span className="text-muted-foreground border-border flex-shrink-0 rounded border px-1.5 py-0.5 text-[10px] font-medium tracking-wide uppercase"> + Deprecated + </span> + ) : null} + </div> + <ArrowUpRight className="text-muted-foreground size-3.5 flex-shrink-0 transition-transform group-hover:translate-x-0.5 group-hover:-translate-y-0.5" /> + </div> + <p className="text-muted-foreground text-sm leading-relaxed"> + {pkg.description} + </p> + {weekly > 0 && !pkg.deprecated ? ( + <div className="mt-1 flex items-center justify-between gap-3"> + <div className="flex items-center gap-2 text-xs tabular-nums"> + <span className="text-muted-foreground"> + {formatNumber(weekly)} / week + </span> + {mom ? ( + <span + className={`font-medium ${MOM_TONE[mom.tone]}`} + title="Past 30 days vs prior 30 days" + > + {mom.label} + </span> + ) : null} + </div> + {series.length > 1 ? ( + <Sparkline values={series} className="text-foreground/50" /> + ) : null} + </div> + ) : null} + </a> + ); +} + +function groupByCategory( + packages: PackageInfo[], +): Record<PackageCategory, PackageInfo[]> { + const result = {} as Record<PackageCategory, PackageInfo[]>; + for (const pkg of packages) { + const list = result[pkg.category] ?? []; + list.push(pkg); + result[pkg.category] = list; + } + return result; +} diff --git a/apps/docs/app/(home)/page.tsx b/apps/docs/app/(home)/page.tsx new file mode 100644 index 0000000..d9e9592 --- /dev/null +++ b/apps/docs/app/(home)/page.tsx @@ -0,0 +1,56 @@ +"use client"; + +import { analytics } from "@/lib/analytics"; +import { Button, buttonVariants } from "@/components/ui/radix/button"; +import Link from "next/link"; +import { TESTIMONIALS } from "@/components/home/testimonials/data"; +import { TestimonialContainer } from "@/components/home/testimonials/testimonials"; +import { ArrowRight } from "lucide-react"; +import { FeatureHighlights } from "@/components/home/feature-highlights"; +import { TrustedBy } from "@/components/home/trusted-by"; +import { Hero } from "@/components/home/hero"; +import { ExampleShowcase } from "@/components/home/example-showcase"; + +export default function HomePage() { + return ( + <main className="relative z-2 mx-auto w-full max-w-7xl flex-col space-y-10 px-4 pt-20 pb-8 md:space-y-20 md:pt-28"> + <Hero /> + + <ExampleShowcase /> + + <FeatureHighlights /> + + <TrustedBy /> + + <TestimonialContainer + testimonials={TESTIMONIALS} + className="sm:columns-2 lg:columns-3 xl:columns-4" + /> + + <section className="flex flex-col items-center gap-6 py-16 text-center"> + <p className="max-w-[24ch] text-3xl font-semibold tracking-tight text-balance"> + The UX of ChatGPT in your own app. + </p> + <div className="flex items-center gap-6 pb-16"> + <Button asChild> + <Link + href="/docs" + onClick={() => analytics.cta.clicked("get_started", "footer")} + > + Get Started <ArrowRight /> + </Link> + </Button> + <Link + href="https://cal.com/simon-farshid/assistant-ui" + onClick={() => analytics.cta.clicked("contact_sales", "footer")} + className={buttonVariants({ + variant: "outline", + })} + > + Contact Sales + </Link> + </div> + </section> + </main> + ); +} diff --git a/apps/docs/app/(home)/pricing.md/route.ts b/apps/docs/app/(home)/pricing.md/route.ts new file mode 100644 index 0000000..beac5c4 --- /dev/null +++ b/apps/docs/app/(home)/pricing.md/route.ts @@ -0,0 +1,44 @@ +import { AGENT_DOCS_DIRECTIVE_MARKDOWN } from "@/lib/agent-docs-directive"; +import { pricingPlans } from "../pricing/pricing-data"; + +export const revalidate = false; + +const formatPlan = (plan: (typeof pricingPlans)[number]) => { + const price = plan.period ? `${plan.price}${plan.period}` : plan.price; + + return [ + `## ${plan.name}`, + "", + `Price: ${price}`, + `Description: ${plan.description}`, + "", + "Included:", + ...plan.features.map((feature) => `- ${feature}`), + "", + `CTA: ${plan.cta}`, + `URL: ${plan.href}`, + ].join("\n"); +}; + +export function GET() { + const markdown = [ + "# assistant-ui pricing", + "", + AGENT_DOCS_DIRECTIVE_MARKDOWN, + "", + "assistant-ui is a free, MIT-licensed TypeScript/React library for AI chat. The commercial pricing below is for assistant-cloud, the fully managed backend for AI chat applications.", + "", + "MAU means Monthly Active Users who send at least one message.", + "B2C pricing is available by contacting b2c-pricing@assistant.dev.", + "", + ...pricingPlans.map(formatPlan), + ].join("\n\n"); + + return new Response(`${markdown}\n`, { + headers: { + "Cache-Control": "no-cache, must-revalidate", + "Content-Type": "text/markdown; charset=utf-8", + "X-Robots-Tag": "noindex, follow", + }, + }); +} diff --git a/apps/docs/app/(home)/pricing/page.tsx b/apps/docs/app/(home)/pricing/page.tsx new file mode 100644 index 0000000..059ca6e --- /dev/null +++ b/apps/docs/app/(home)/pricing/page.tsx @@ -0,0 +1,93 @@ +import type { Metadata } from "next"; +import { createOgMetadata } from "@/lib/og"; +import { Check } from "lucide-react"; +import { GitHubIcon } from "@/components/icons/github"; +import { PricingPlanCard } from "./pricing-plan-card"; +import { pricingPlans } from "./pricing-data"; + +const title = "Pricing"; +const description = "Fully managed backend for AI chat applications"; + +export const metadata: Metadata = { + title, + description, + ...createOgMetadata(title, description), +}; + +export default function PricingPage() { + return ( + <main className="mx-auto w-full max-w-5xl px-4 py-16 md:py-24"> + <header className="mb-16"> + <p className="text-muted-foreground mb-3 text-sm">Pricing</p> + <h1 className="text-2xl font-medium tracking-tight"> + Simple, transparent pricing + </h1> + <p className="text-muted-foreground mt-2"> + Choose the plan that fits your needs. + </p> + </header> + + <section className="mb-20"> + <div className="mb-8"> + <h2 className="text-xl font-medium tracking-tight">assistant-ui</h2> + <p className="text-muted-foreground mt-1"> + TypeScript/React library for AI chat + </p> + </div> + + <div className="border-border rounded-lg border p-6"> + <div className="flex items-start gap-3"> + <GitHubIcon className="mt-0.5 h-5 w-5 flex-shrink-0" /> + <div> + <h3 className="text-sm font-medium"> + Forever free & open source + </h3> + <p className="text-muted-foreground mt-1 text-sm">MIT License</p> + </div> + </div> + + <ul className="mt-6 space-y-2.5"> + {[ + "Customizable UI components", + "Bring your own backend", + "Community support", + ].map((feature) => ( + <li + key={feature} + className="text-muted-foreground flex items-start gap-2 text-sm" + > + <Check className="text-foreground/60 mt-0.5 h-4 w-4 flex-shrink-0" /> + <span>{feature}</span> + </li> + ))} + </ul> + </div> + </section> + + <section> + <div className="mb-8"> + <h2 className="text-xl font-medium tracking-tight"> + assistant-cloud + </h2> + <p className="text-muted-foreground mt-1"> + Fully managed backend for AI chat applications + </p> + </div> + + <div className="grid gap-6 md:grid-cols-3"> + {pricingPlans.map((plan) => ( + <PricingPlanCard key={plan.name} plan={plan} /> + ))} + </div> + + <p className="text-muted-foreground mt-4 text-xs"> + * MAU = Monthly Active Users who send at least one message.{" "} + <a href="mailto:b2c-pricing@assistant.dev" className="underline"> + Contact us + </a>{" "} + for B2C pricing. + </p> + </section> + </main> + ); +} diff --git a/apps/docs/app/(home)/pricing/pricing-data.ts b/apps/docs/app/(home)/pricing/pricing-data.ts new file mode 100644 index 0000000..23c031b --- /dev/null +++ b/apps/docs/app/(home)/pricing/pricing-data.ts @@ -0,0 +1,54 @@ +export type PricingPlan = { + name: string; + price: string; + period?: string; + description: string; + features: string[]; + cta: string; + href: string; + highlighted: boolean; +}; + +export const pricingPlans = [ + { + name: "Free", + price: "$0", + description: "Get started with the basics", + features: ["200 MAU included", "Chat history", "Thread management"], + cta: "Start free", + href: "https://cloud.assistant-ui.com/", + highlighted: false, + }, + { + name: "Pro", + price: "$50", + period: "/mo", + description: "For growing applications", + features: [ + "500 MAU included", + "$0.10 per additional MAU", + "Chat history", + "Thread management", + "Early access to new features", + ], + cta: "Get started", + href: "https://cloud.assistant-ui.com/", + highlighted: true, + }, + { + name: "Enterprise", + price: "Custom", + description: "For large-scale deployments", + features: [ + "Custom MAU pricing", + "Your own backend integration", + "Data replication", + "Dedicated support", + "99.99% uptime SLA", + "On-premises deployment", + ], + cta: "Contact sales", + href: "https://cal.com/simon-farshid/assistant-ui", + highlighted: false, + }, +] satisfies PricingPlan[]; diff --git a/apps/docs/app/(home)/pricing/pricing-plan-card.tsx b/apps/docs/app/(home)/pricing/pricing-plan-card.tsx new file mode 100644 index 0000000..f42815b --- /dev/null +++ b/apps/docs/app/(home)/pricing/pricing-plan-card.tsx @@ -0,0 +1,67 @@ +"use client"; + +import Link from "next/link"; +import { Check } from "lucide-react"; +import { cn } from "@/lib/utils"; +import { analytics } from "@/lib/analytics"; +import type { PricingPlan } from "./pricing-data"; + +export function PricingPlanCard({ plan }: { plan: PricingPlan }) { + const handleClick = () => { + // Track pricing plan clicks with plan name for attribution + // Sanitize plan.name to prevent analytics pollution if component is reused with dynamic data + const safePlanName = plan.name.toLowerCase().replace(/[^a-z0-9_]/g, "_"); + if (plan.name === "Enterprise") { + analytics.cta.clicked("contact_sales", "pricing"); + } else { + analytics.cta.clicked("get_started", `pricing_${safePlanName}`); + } + }; + + return ( + <div + className={cn( + "relative flex flex-col rounded-lg border p-6", + plan.highlighted ? "border-foreground/20 bg-muted/30" : "border-border", + )} + > + <div className="mb-6"> + <h3 className="text-sm font-medium">{plan.name}</h3> + <div className="mt-2 flex items-baseline gap-1"> + <span className="text-2xl font-medium tracking-tight"> + {plan.price} + </span> + {plan.period && ( + <span className="text-muted-foreground text-sm">{plan.period}</span> + )} + </div> + <p className="text-muted-foreground mt-1 text-sm">{plan.description}</p> + </div> + + <ul className="mb-6 flex-1 space-y-2.5"> + {plan.features.map((feature) => ( + <li + key={feature} + className="text-muted-foreground flex items-start gap-2 text-sm" + > + <Check className="text-foreground/60 mt-0.5 h-4 w-4 flex-shrink-0" /> + <span>{feature}</span> + </li> + ))} + </ul> + + <Link + href={plan.href} + onClick={handleClick} + className={cn( + "block rounded-md py-2 text-center text-sm font-medium transition-colors", + plan.highlighted + ? "bg-foreground text-background hover:bg-foreground/90" + : "border-border hover:bg-muted border", + )} + > + {plan.cta} + </Link> + </div> + ); +} diff --git a/apps/docs/app/(home)/privacy-policy/page.tsx b/apps/docs/app/(home)/privacy-policy/page.tsx new file mode 100644 index 0000000..823dcf7 --- /dev/null +++ b/apps/docs/app/(home)/privacy-policy/page.tsx @@ -0,0 +1,191 @@ +import type { Metadata } from "next"; +import type { ReactNode } from "react"; +import { createOgMetadata } from "@/lib/og"; + +const title = "Privacy Policy — assistant-ui"; +const description = + "Privacy Policy for AgentbaseAI Inc. and assistant-ui services."; + +export const metadata: Metadata = { + title, + description, + ...createOgMetadata(title, description), +}; + +const sections = [ + { + title: "1. What Information Do We Collect?", + body: [ + "We collect personal information that you voluntarily provide to us when you register on the Services, express an interest in obtaining information about us or our products and Services, participate in activities on the Services, or otherwise contact us.", + "The personal information we collect depends on the context of your interactions with us and the Services, the choices you make, and the products and features you use. It may include names, email addresses, usernames, passwords, contact or authentication data, and billing addresses.", + <> + We do not process sensitive information. If you choose to make + purchases, payment data is handled and stored by Stripe. You may find + Stripe's privacy notice at{" "} + <a + href="https://stripe.com/privacy" + target="_blank" + rel="noopener noreferrer" + className="text-foreground underline underline-offset-4" + > + https://stripe.com/privacy + </a> + . + </>, + 'If you choose to register or log in using a social media account, we will collect certain profile information from the social media provider as described in the section titled "How Do We Handle Your Social Logins?"', + ], + }, + { + title: "2. How Do We Process Your Information?", + body: [ + "We process your information to provide, improve, and administer our Services, communicate with you, protect our Services, prevent fraud, and comply with law. We may also process your information for other purposes with your consent.", + "This includes facilitating account creation and authentication, delivering Services, responding to inquiries, sending administrative information, fulfilling and managing orders, protecting our Services, and saving or protecting an individual's vital interests.", + ], + }, + { + title: + "3. What Legal Bases Do We Rely On To Process Your Personal Information?", + body: [ + "If you are located in the EU or UK, we may rely on consent, performance of a contract, legitimate interests, legal obligations, and vital interests to process your personal information.", + "If you are located in Canada, we may process your information if you have given us express or implied consent, or in limited situations where processing without consent is legally permitted.", + ], + }, + { + title: "4. When And With Whom Do We Share Your Personal Information?", + body: [ + "We may share information in specific situations and with third-party vendors, service providers, contractors, or agents who perform services for us or on our behalf and require access to such information to do that work.", + "The categories of third parties we may share personal information with include cloud computing services, AI platforms, payment processors, and website hosting service providers.", + "We may also share or transfer information in connection with a merger, sale of company assets, financing, acquisition, or similar business transfer. We may share information with affiliates and require those affiliates to honor this Privacy Notice.", + ], + }, + { + title: "5. Do We Offer Artificial Intelligence-Based Products?", + body: [ + "We offer products, features, or tools powered by artificial intelligence, machine learning, or similar technologies. These tools are designed to enhance your experience and provide innovative solutions.", + "We provide AI Products through third-party service providers, including OpenAI, Anthropic, Microsoft Azure AI, and Google Cloud AI. Your input, output, and personal information may be shared with and processed by these AI Service Providers to enable your use of our AI Products.", + "Our AI Products are designed for AI automation and AI applications. To opt out, you can log in to your account settings and update your user account.", + ], + }, + { + title: "6. How Do We Handle Your Social Logins?", + body: [ + "If you choose to register or log in using third-party social media account details, we will receive certain profile information from your social media provider. This information may include your name, email address, friends list, profile picture, and other information you choose to make public on that platform.", + "We use this information only for the purposes described in this Privacy Notice or otherwise made clear to you on the relevant Services. We recommend reviewing your social media provider's privacy notice to understand how they handle your information.", + ], + }, + { + title: "7. How Long Do We Keep Your Information?", + body: [ + "We keep personal information only for as long as necessary for the purposes set out in this Privacy Notice unless a longer retention period is required or permitted by law.", + "When we have no ongoing legitimate business need to process your personal information, we will delete or anonymize it. If deletion or anonymization is not possible, we will securely store the information and isolate it from further processing until deletion is possible.", + ], + }, + { + title: "8. How Do We Keep Your Information Safe?", + body: [ + "We have implemented appropriate and reasonable technical and organizational security measures designed to protect the security of personal information we process.", + "No electronic transmission over the Internet or information storage technology can be guaranteed to be completely secure, so we cannot promise or guarantee that unauthorized third parties will not be able to defeat our security and improperly collect, access, steal, or modify your information.", + ], + }, + { + title: "9. Do We Collect Information From Minors?", + body: [ + "We do not knowingly collect, solicit data from, or market to children under 18 years of age, nor do we knowingly sell such personal information.", + "If we learn that personal information from users less than 18 years of age has been collected, we will deactivate the account and take reasonable measures to promptly delete such data from our records. If you become aware of data we may have collected from children under age 18, contact us at privacy@assistant-ui.com.", + ], + }, + { + title: "10. What Are Your Privacy Rights?", + body: [ + "Depending on where you are located, you may have rights that allow you greater access to and control over your personal information. These may include the right to request access, correction, deletion, restriction of processing, data portability, objection to processing, and freedom from automated decision-making in certain circumstances.", + "If you are located in the EEA or UK and believe we are unlawfully processing your personal information, you may complain to your Member State data protection authority or UK data protection authority. If you are located in Switzerland, you may contact the Federal Data Protection and Information Commissioner.", + "You can withdraw consent at any time when we rely on consent to process your personal information. You can unsubscribe from marketing and promotional communications by clicking the unsubscribe link in those emails or by contacting us.", + ], + }, + { + title: "11. Controls For Do-Not-Track Features", + body: [ + "Most web browsers and some mobile operating systems and applications include a Do-Not-Track feature or setting. At this stage, no uniform technology standard for recognizing and implementing Do-Not-Track signals has been finalized, so we do not currently respond to them.", + ], + }, + { + title: "12. Do United States Residents Have Specific Privacy Rights?", + body: [ + "If you are a resident of California, Colorado, Connecticut, Delaware, Florida, Indiana, Iowa, Kentucky, Montana, New Hampshire, New Jersey, Oregon, Tennessee, Texas, Utah, or Virginia, you may have rights to request access to and receive details about the personal information we maintain about you, correct inaccuracies, obtain a copy of your personal information, delete your personal information, and withdraw consent to our processing of your personal information.", + "You may also have the right to opt out of processing if it is used for targeted advertising, the sale of personal data, or profiling in furtherance of decisions that produce legal or similarly significant effects. We will honor Global Privacy Control opt-out signals where required.", + "We have not sold or shared personal information to third parties for a business or commercial purpose in the preceding twelve months.", + 'California residents may request information under California\'s "Shine The Light" law by submitting a written request using the contact details below.', + ], + }, + { + title: "13. Do We Make Updates To This Notice?", + body: [ + "We may update this Privacy Notice from time to time. The updated version will be indicated by an updated date at the top of this Privacy Notice. If we make material changes, we may notify you by prominently posting a notice or directly sending you a notification.", + ], + }, + { + title: "14. How Can You Contact Us About This Notice?", + body: [ + "If you have questions or comments about this notice, you may email us at privacy@assistant-ui.com or contact us by post at AgentbaseAI Inc., 340 Fremont Street, Apt 2306, San Francisco, CA 94105, United States.", + ], + }, + { + title: "15. How Can You Review, Update, Or Delete Your Data?", + body: [ + "Based on the applicable laws of your country or state of residence, you may have the right to request access to the personal information we collect from you, details about how we have processed it, correction of inaccuracies, or deletion of your personal information. You may also have the right to withdraw consent to our processing of your personal information.", + "To request review, update, or deletion of your personal information, contact privacy@assistant-ui.com.", + ], + }, +] satisfies { + title: string; + body: ReactNode[]; +}[]; + +export default function PrivacyPolicyPage() { + return ( + <main className="mx-auto w-full max-w-3xl px-4 py-16 md:py-24"> + <header className="mb-12"> + <p className="text-muted-foreground mb-3 text-sm">Legal</p> + <h1 className="text-3xl font-medium tracking-tight">Privacy Policy</h1> + <p className="text-muted-foreground mt-3">Last updated July 18, 2024</p> + <p className="text-muted-foreground mt-6 leading-relaxed"> + This Privacy Notice for AgentbaseAI Inc. ("we," + "us," or "our") describes how and why we might + access, collect, store, use, and/or share your personal information + when you use our services, including when you visit assistant-ui.com + or engage with us in other related ways. + </p> + <p className="text-muted-foreground mt-4 leading-relaxed"> + If you have questions or concerns, contact us at{" "} + <a + href="mailto:privacy@assistant-ui.com" + className="text-foreground underline underline-offset-4" + > + privacy@assistant-ui.com + </a> + . + </p> + </header> + + <div className="space-y-10"> + {sections.map((section) => ( + <section key={section.title}> + <h2 className="text-xl font-medium tracking-tight"> + {section.title} + </h2> + <div className="mt-4 space-y-4"> + {section.body.map((paragraph, index) => ( + <p + key={index} + className="text-muted-foreground leading-relaxed" + > + {paragraph} + </p> + ))} + </div> + </section> + ))} + </div> + </main> + ); +} diff --git a/apps/docs/app/(home)/showcase/page.tsx b/apps/docs/app/(home)/showcase/page.tsx new file mode 100644 index 0000000..5ba21be --- /dev/null +++ b/apps/docs/app/(home)/showcase/page.tsx @@ -0,0 +1,208 @@ +import type { Metadata } from "next"; +import Image from "next/image"; +import Link from "next/link"; +import { createOgMetadata } from "@/lib/og"; +import { Megaphone } from "lucide-react"; +import { GitHubIcon } from "@/components/icons/github"; + +const title = "Showcase"; +const description = "Projects built with assistant-ui"; + +export const metadata: Metadata = { + title, + description, + ...createOgMetadata(title, description), +}; + +type ShowcaseItem = { + title: string; + image: string; + tag: string; + secondaryTag?: string; + link: string; + announcementLink?: string; + repositoryLink?: string; + description: string; +}; + +const SHOWCASE_ITEMS: ShowcaseItem[] = [ + { + title: "Chat LangChain", + image: "/screenshot/chat-langchain.png", + tag: "Developer Tools", + secondaryTag: "OSS", + link: "https://chat.langchain.com/", + repositoryLink: "https://github.com/langchain-ai/chat-langchain", + description: "AI-powered guide to the LangChain ecosystem.", + }, + { + title: "Inconvo", + image: "/screenshot/inconvo.png", + tag: "Developer Tools", + link: "https://inconvo.com/", + repositoryLink: "https://github.com/ten-dev/inconvo-assistant-ui-example", + description: "Build AI agents that answer questions from your databases.", + }, + { + title: "Closing.wtf", + image: "/screenshot/closing-wtf.png", + tag: "AI Assistant", + link: "https://closing.wtf/", + announcementLink: + "https://closing.wtf/blog/mortgage-analysis-chat-with-assistantui", + description: "AI mortgage analysis that saves homebuyers thousands.", + }, + { + title: "Helicone", + image: "/screenshot/helicone.png", + tag: "Developer Tools", + secondaryTag: "OSS", + link: "https://www.helicone.ai/", + repositoryLink: "https://github.com/helicone/helicone", + description: "Open-source LLM observability and gateway platform.", + }, + { + title: "Open Canvas", + image: "/screenshot/open-canvas.png", + tag: "AI Assistant", + secondaryTag: "OSS", + link: "https://opencanvas.langchain.com/", + repositoryLink: "https://github.com/langchain-ai/open-canvas", + description: "Open-source collaborative writing interface with AI.", + }, + { + title: "Portal", + image: "/screenshot/portal.png", + tag: "Browser", + link: "https://www.portal.so/", + description: "AI browser that automates tasks, analysis, and research.", + }, + { + title: "LangGraph Stockbroker", + image: "/screenshot/stockbroker.png", + tag: "Developer Tools", + secondaryTag: "OSS", + link: "https://assistant-ui-stockbroker.vercel.app/", + announcementLink: "https://blog.langchain.dev/assistant-ui/", + repositoryLink: "https://github.com/assistant-ui/assistant-ui-stockbroker", + description: "AI assistant for researching public company financials.", + }, + { + title: "CoreViz", + image: "/screenshot/coreviz.png", + tag: "AI Assistant", + link: "https://coreviz.io/", + description: "Search and analyze photos and videos with natural language.", + }, +]; + +export default function ShowcasePage() { + return ( + <main className="mx-auto w-full max-w-7xl px-4 py-16 md:py-24"> + <header className="mb-16 max-w-2xl"> + <p className="text-muted-foreground mb-3 text-sm">Showcase</p> + <h1 className="text-2xl font-medium tracking-tight"> + Built with assistant-ui + </h1> + <p className="text-muted-foreground mt-2"> + Explore projects from the community using assistant-ui to build + production AI chat experiences. + </p> + </header> + + <div className="grid gap-6 sm:grid-cols-2 lg:grid-cols-3"> + {SHOWCASE_ITEMS.map((item) => ( + <ShowcaseCard key={item.title} {...item} /> + ))} + </div> + + <section className="mt-16"> + <p className="text-muted-foreground"> + Building something cool?{" "} + <a + href="mailto:showcase@assistant-ui.com" + className="text-foreground hover:text-foreground/70 font-medium transition-colors" + > + Let us know → + </a> + </p> + </section> + </main> + ); +} + +function ShowcaseCard({ + title, + image, + tag, + secondaryTag, + link, + announcementLink, + repositoryLink, + description, +}: ShowcaseItem) { + return ( + <div className="group border-border bg-card flex flex-col overflow-hidden rounded-lg border"> + <Link + href={link} + target="_blank" + rel="noopener noreferrer" + className="block" + > + <div className="bg-muted relative aspect-[16/10] overflow-hidden"> + <Image src={image} alt={title} fill className="object-cover" /> + </div> + </Link> + + <div className="flex flex-1 flex-col p-4"> + <div className="mb-2 flex items-center gap-2"> + <span className="bg-muted text-muted-foreground rounded-full px-2 py-0.5 text-xs"> + {tag} + </span> + {secondaryTag && ( + <span className="rounded-full bg-green-500/10 px-2 py-0.5 text-xs text-green-600 dark:text-green-400"> + {secondaryTag} + </span> + )} + </div> + + <Link href={link} target="_blank" rel="noopener noreferrer"> + <h3 className="text-foreground/80 group-hover:text-foreground font-medium transition-colors"> + {title} + </h3> + </Link> + + <p className="text-muted-foreground mt-1 flex-1 text-sm"> + {description} + </p> + + {(repositoryLink || announcementLink) && ( + <div className="mt-4 flex items-center gap-3"> + {repositoryLink && ( + <Link + href={repositoryLink} + target="_blank" + rel="noopener noreferrer" + className="text-muted-foreground hover:text-foreground flex items-center gap-1.5 text-xs transition-colors" + > + <GitHubIcon className="h-3.5 w-3.5" /> + <span>Source</span> + </Link> + )} + {announcementLink && ( + <Link + href={announcementLink} + target="_blank" + rel="noopener noreferrer" + className="text-muted-foreground hover:text-foreground flex items-center gap-1.5 text-xs transition-colors" + > + <Megaphone className="h-3.5 w-3.5" /> + <span>Blog</span> + </Link> + )} + </div> + )} + </div> + </div> + ); +} diff --git a/apps/docs/app/(home)/tap-llms.mdx/[[...slug]]/route.ts b/apps/docs/app/(home)/tap-llms.mdx/[[...slug]]/route.ts new file mode 100644 index 0000000..1e4889e --- /dev/null +++ b/apps/docs/app/(home)/tap-llms.mdx/[[...slug]]/route.ts @@ -0,0 +1,30 @@ +import { type NextRequest, NextResponse } from "next/server"; +import { getLLMText } from "@/lib/get-llm-text"; +import { getTapDocsPage, tapDocs } from "@/lib/source"; +import { notFound } from "next/navigation"; + +export const revalidate = false; + +export async function GET( + _req: NextRequest, + { params }: { params: Promise<{ slug?: string[] }> }, +) { + const { slug } = await params; + // The Tap index MDX redirects; markdown should return content directly. + const page = getTapDocsPage(slug); + if (!page) notFound(); + + return new NextResponse(await getLLMText(page), { + headers: { + "Cache-Control": "no-cache, must-revalidate", + "Content-Type": "text/markdown; charset=utf-8", + "X-Robots-Tag": "noindex, follow", + }, + }); +} + +export function generateStaticParams() { + return tapDocs.getPages().map((page) => ({ + slug: page.slugs, + })); +} diff --git a/apps/docs/app/(home)/terms-of-service/page.tsx b/apps/docs/app/(home)/terms-of-service/page.tsx new file mode 100644 index 0000000..c41ff07 --- /dev/null +++ b/apps/docs/app/(home)/terms-of-service/page.tsx @@ -0,0 +1,270 @@ +import type { Metadata } from "next"; +import Link from "next/link"; +import type { ReactNode } from "react"; +import { createOgMetadata } from "@/lib/og"; + +const title = "Terms of Service — assistant-ui"; +const description = + "Terms of Service for AgentbaseAI Inc. and assistant-ui services."; + +export const metadata: Metadata = { + title, + description, + ...createOgMetadata(title, description), +}; + +const sections = [ + { + title: "1. Our Services", + body: [ + "The information provided when using the Services is not intended for distribution to or use by any person or entity in any jurisdiction or country where such distribution or use would be contrary to law or regulation.", + "The Services are not tailored to comply with industry-specific regulations, including HIPAA or FISMA. You may not use the Services in a way that would violate the Gramm-Leach-Bliley Act.", + ], + }, + { + title: "2. Intellectual Property Rights", + body: [ + "We own or license all intellectual property rights in our Services, including source code, databases, functionality, software, website designs, audio, video, text, photographs, graphics, trademarks, service marks, and logos.", + "Subject to your compliance with these Legal Terms, we grant you a non-exclusive, non-transferable, revocable license to access the Services and download or print a copy of any portion of the Content to which you have properly gained access solely for your internal business purpose.", + "No part of the Services and no Content or Marks may be copied, reproduced, aggregated, republished, uploaded, posted, publicly displayed, encoded, translated, transmitted, distributed, sold, licensed, or otherwise exploited for any commercial purpose without our express prior written permission.", + "By directly sending us any question, comment, suggestion, idea, feedback, or other information about the Services, you agree to assign to us all intellectual property rights in that submission.", + ], + }, + { + title: "3. User Representations", + body: [ + "By using the Services, you represent and warrant that your registration information is true, accurate, current, and complete; that you will maintain its accuracy; that you have legal capacity and agree to comply with these Legal Terms; that you are not a minor in your jurisdiction; that you will not access the Services through automated or non-human means; that you will not use the Services for an illegal or unauthorized purpose; and that your use will not violate applicable law or regulation.", + ], + }, + { + title: "4. User Registration", + body: [ + "You may be required to register to use the Services. You agree to keep your password confidential and are responsible for all use of your account and password.", + "We reserve the right to remove, reclaim, or change a username you select if we determine, in our sole discretion, that the username is inappropriate, obscene, or otherwise objectionable.", + ], + }, + { + title: "5. Purchases And Payment", + body: [ + "We accept Visa, Mastercard, American Express, and Discover.", + "You agree to provide current, complete, and accurate purchase and account information for all purchases made via the Services and to promptly update account and payment information so we can complete your transactions and contact you as needed.", + "You agree to pay all charges at the prices then in effect, and you authorize us to charge your chosen payment provider for those amounts. We reserve the right to correct pricing errors and to refuse, limit, or cancel orders in our sole discretion.", + ], + }, + { + title: "6. Subscriptions", + body: [ + "Your subscription will continue and automatically renew unless canceled. You consent to recurring charges until you cancel the applicable order.", + "All purchases are non-refundable. You can cancel your subscription at any time by logging into your account. Cancellation takes effect at the end of the current paid term.", + "We may make changes to subscription fees and will communicate price changes in accordance with applicable law.", + ], + }, + { + title: "7. Prohibited Activities", + body: [ + "You may not access or use the Services for any purpose other than that for which we make the Services available. The Services may not be used in connection with commercial endeavors except those specifically endorsed or approved by us.", + "You agree not to systematically retrieve content, trick or defraud us or other users, interfere with security-related features, disparage or harm us or the Services, harass or harm others, submit false reports, violate laws, frame or link to the Services without authorization, upload harmful material, use automated systems, delete proprietary notices, impersonate others, collect user information, bypass access restrictions, reverse engineer software except as permitted by law, or use the Services to compete with us.", + ], + }, + { + title: "8. User Generated Contributions", + body: [ + "The Services do not offer users the ability to submit or post content. If we provide you with the opportunity to create, submit, post, display, transmit, perform, publish, distribute, or broadcast content or materials, those contributions may be viewable by other users and through third-party websites.", + "When you create or make available Contributions, you represent and warrant that they do not infringe third-party rights; that you have the necessary rights and permissions; that they are not false, misleading, unlawful, harassing, abusive, discriminatory, obscene, violent, or otherwise objectionable; and that they do not violate these Legal Terms or applicable law.", + ], + }, + { + title: "9. Contribution License", + body: [ + "You and the Services agree that we may access, store, process, and use any information and personal data that you provide following the terms of the Privacy Policy and your choices.", + "By submitting suggestions or other feedback regarding the Services, you agree that we can use and share that feedback for any purpose without compensation to you.", + "We do not assert ownership over your Contributions. You retain full ownership of your Contributions and any associated intellectual property rights. You are solely responsible for your Contributions and agree to exonerate us from responsibility and refrain from legal action against us regarding them.", + ], + }, + { + title: "10. Social Media", + body: [ + "As part of the functionality of the Services, you may link your account with third-party accounts by providing login information or allowing us access as permitted by those accounts' terms.", + "By granting access, you understand that we may access, make available, and store content from those third-party accounts so it is available through the Services. Your relationship with third-party service providers is governed solely by your agreements with those providers.", + "You can deactivate the connection between the Services and your third-party account by contacting us or through account settings, if applicable.", + ], + }, + { + title: "11. Third-Party Websites And Content", + body: [ + "The Services may contain links to third-party websites and content. We are not responsible for third-party websites or content, including their accuracy, reliability, privacy practices, or policies.", + "If you access third-party websites or content, you do so at your own risk and should review the applicable terms and policies.", + ], + }, + { + title: "12. Services Management", + body: [ + "We reserve the right, but not the obligation, to monitor the Services for violations, take appropriate legal action, refuse or restrict access to Contributions, remove excessive or burdensome files and content, and otherwise manage the Services to protect our rights and property and facilitate proper operation.", + ], + }, + { + title: "13. Privacy Policy", + body: [ + <> + We care about data privacy and security. Please review our{" "} + <Link + href="/privacy-policy" + className="text-foreground underline underline-offset-4" + > + Privacy Policy + </Link> + . By using the Services, you agree to be bound by our Privacy Policy, + which is incorporated into these Legal Terms. + </>, + "The Services are hosted in the United States. If you access the Services from another region with laws or requirements governing personal data collection, use, or disclosure that differ from applicable laws in the United States, you consent to have your data transferred to and processed in the United States.", + ], + }, + { + title: "14. Term And Termination", + body: [ + "These Legal Terms remain in full force and effect while you use the Services. We reserve the right, in our sole discretion and without notice or liability, to deny access to and use of the Services to any person for any reason or no reason, including breach of these Legal Terms or applicable law.", + "We may terminate your use or participation in the Services or delete your account and any content or information you posted at any time, without warning, in our sole discretion.", + ], + }, + { + title: "15. Modifications And Interruptions", + body: [ + "We reserve the right to change, modify, or remove the contents of the Services at any time or for any reason at our sole discretion without notice, and we have no obligation to update information on our Services.", + "We cannot guarantee the Services will be available at all times. We may experience hardware, software, or other issues or perform maintenance that results in interruptions, delays, or errors.", + ], + }, + { + title: "16. Governing Law", + body: [ + "These Legal Terms and your use of the Services are governed by and construed in accordance with the laws of the State of Delaware, without regard to conflict of law principles.", + ], + }, + { + title: "17. Dispute Resolution", + body: [ + "The parties agree to first attempt to negotiate any dispute informally for at least thirty days before initiating arbitration.", + "If the parties cannot resolve a dispute through informal negotiations, the dispute will be finally and exclusively resolved by binding arbitration under the Commercial Arbitration Rules of the American Arbitration Association and, where appropriate, the AAA Consumer Rules.", + "Any arbitration will be limited to the dispute between the parties individually. There is no right or authority for a dispute to be arbitrated or brought as a class action or representative proceeding.", + "Disputes seeking to enforce or protect intellectual property rights, disputes related to theft, piracy, invasion of privacy, unauthorized use, and claims for injunctive relief are not subject to the informal negotiation and arbitration provisions.", + ], + }, + { + title: "18. Corrections", + body: [ + "There may be information on the Services that contains typographical errors, inaccuracies, or omissions. We reserve the right to correct errors, inaccuracies, or omissions and to change or update information on the Services at any time, without prior notice.", + ], + }, + { + title: "19. Disclaimer", + body: [ + "The Services are provided on an as-is and as-available basis. Your use of the Services is at your sole risk.", + "To the fullest extent permitted by law, we disclaim all warranties, express or implied, in connection with the Services and your use of them, including implied warranties of merchantability, fitness for a particular purpose, and non-infringement.", + ], + }, + { + title: "20. Limitations Of Liability", + body: [ + "In no event will we or our directors, employees, or agents be liable to you or any third party for any direct, indirect, consequential, exemplary, incidental, special, or punitive damages arising from your use of the Services, even if we have been advised of the possibility of such damages.", + "Our liability to you for any cause will at all times be limited to the amount paid, if any, by you to us during the six-month period prior to the cause of action arising, except where prohibited by applicable law.", + ], + }, + { + title: "21. Indemnification", + body: [ + "You agree to defend, indemnify, and hold us harmless, including our subsidiaries, affiliates, officers, agents, partners, and employees, from and against any loss, damage, liability, claim, or demand arising out of your use of the Services, breach of these Legal Terms, breach of your representations and warranties, violation of third-party rights, or harmful acts toward another user.", + ], + }, + { + title: "22. User Data", + body: [ + "We will maintain certain data that you transmit to the Services for managing the performance of the Services, as well as data relating to your use of the Services. You are solely responsible for all data that you transmit or that relates to your activity using the Services.", + ], + }, + { + title: "23. Electronic Communications, Transactions, And Signatures", + body: [ + "Visiting the Services, sending emails, and completing online forms constitute electronic communications. You consent to receive electronic communications and agree that electronic agreements, notices, disclosures, and communications satisfy any legal requirement that such communication be in writing.", + "You agree to the use of electronic signatures, contracts, orders, and records, and to electronic delivery of notices, policies, and records of transactions initiated or completed by us or via the Services.", + ], + }, + { + title: "24. California Users And Residents", + body: [ + "If a complaint with us is not satisfactorily resolved, California users and residents can contact the Complaint Assistance Unit of the Division of Consumer Services of the California Department of Consumer Affairs in writing at 1625 North Market Blvd., Suite N 112, Sacramento, California 95834 or by telephone at (800) 952-5210 or (916) 445-1254.", + ], + }, + { + title: "25. Miscellaneous", + body: [ + "These Legal Terms and any policies or operating rules posted by us constitute the entire agreement between you and us. Our failure to exercise or enforce any right or provision does not operate as a waiver.", + "If any provision or part of a provision is determined to be unlawful, void, or unenforceable, that provision or part is deemed severable and does not affect the validity and enforceability of the remaining provisions.", + ], + }, + { + title: "26. Contact Us", + body: [ + "To resolve a complaint regarding the Services or to receive further information regarding use of the Services, contact AgentbaseAI Inc., 340 Fremont Street, Apt 2306, San Francisco, CA 94105, United States.", + <> + You may also email us at{" "} + <a + href="mailto:contact@assistant-ui.com" + className="text-foreground underline underline-offset-4" + > + contact@assistant-ui.com + </a> + . + </>, + ], + }, +] satisfies { + title: string; + body: ReactNode[]; +}[]; + +export default function TermsOfServicePage() { + return ( + <main className="mx-auto w-full max-w-3xl px-4 py-16 md:py-24"> + <header className="mb-12"> + <p className="text-muted-foreground mb-3 text-sm">Legal</p> + <h1 className="text-3xl font-medium tracking-tight"> + Terms of Service + </h1> + <p className="text-muted-foreground mt-3">Last updated July 18, 2024</p> + <p className="text-muted-foreground mt-6 leading-relaxed"> + We are AgentbaseAI Inc. ("Company," "we," + "us," or "our"), a company registered in Delaware. + We operate the website https://www.assistant-ui.com, as well as any + other related products and services that refer or link to these legal + terms. + </p> + <p className="text-muted-foreground mt-4 leading-relaxed"> + These Legal Terms constitute a legally binding agreement between you + and AgentbaseAI Inc. concerning your access to and use of the + Services. If you do not agree with all of these Legal Terms, you are + prohibited from using the Services and must discontinue use + immediately. + </p> + </header> + + <div className="space-y-10"> + {sections.map((section) => ( + <section key={section.title}> + <h2 className="text-xl font-medium tracking-tight"> + {section.title} + </h2> + <div className="mt-4 space-y-4"> + {section.body.map((paragraph, index) => ( + <p + key={index} + className="text-muted-foreground leading-relaxed" + > + {paragraph} + </p> + ))} + </div> + </section> + ))} + </div> + </main> + ); +} diff --git a/apps/docs/app/(home)/traction/page.tsx b/apps/docs/app/(home)/traction/page.tsx new file mode 100644 index 0000000..5ad4c95 --- /dev/null +++ b/apps/docs/app/(home)/traction/page.tsx @@ -0,0 +1,414 @@ +import type { Metadata } from "next"; +import Link from "next/link"; +import { + ArrowRight, + ArrowUpRight, + Bot, + GitFork, + GitCommit, + Network, + Package, + Star, + Users, + Sparkles, +} from "lucide-react"; +import { Button, buttonVariants } from "@/components/ui/radix/button"; +import { GitHubIcon } from "@/components/icons/github"; +import { createOgMetadata } from "@/lib/og"; +import { + PACKAGES, + TIMELINE_PACKAGES, + daysSince, + fetchBotCoAuthors, + fetchCommitActivity, + fetchContributors, + fetchNpmDownloads, + fetchReleaseActivity, + fetchStarHistory, + fetchTimelineSeries, +} from "@/lib/traction"; +import { getCommitStats, getDependents, getRepo } from "@/lib/github"; +import { FLAGSHIP_PACKAGE } from "@/lib/npm"; +import { formatCompact, formatNumber } from "@/lib/format"; +import { ActivityHeatmap } from "@/components/traction/activity-heatmap"; +import { DownloadsChart } from "@/components/traction/downloads-chart"; +import { StarHistoryChart } from "@/components/traction/star-history-chart"; +import { WeeklyDownloadsStat } from "@/components/traction/weekly-downloads-stat"; + +const title = "Traction"; +const description = + "GitHub momentum, package coverage, and the numbers behind assistant-ui."; + +export const metadata: Metadata = { + title, + description, + ...createOgMetadata(title, description), +}; + +const REPO_URL = "https://github.com/assistant-ui/assistant-ui"; + +type HeroStat = { + label: string; + value: string | null; + caption: string; + icon: typeof Star; +}; + +export default async function TractionPage({ + searchParams, +}: { + searchParams: Promise<{ refresh?: string }>; +}) { + const forceFresh = (await searchParams).refresh === "true"; + const revalidate = forceFresh ? 0 : undefined; + + const repo = await getRepo(revalidate); + + const [ + npm, + starHistory, + downloadsTimeline, + contributors, + botCoAuthors, + dependents, + commitActivity, + releaseActivity, + commitStats, + ] = await Promise.all([ + fetchNpmDownloads(revalidate), + fetchStarHistory(repo?.stars ?? 0, revalidate), + fetchTimelineSeries(TIMELINE_PACKAGES, revalidate), + fetchContributors(revalidate), + fetchBotCoAuthors(revalidate), + getDependents(revalidate), + fetchCommitActivity(revalidate), + fetchReleaseActivity(revalidate), + getCommitStats(revalidate), + ]); + + const flagshipWeekly = npm.perPackage[FLAGSHIP_PACKAGE]?.weekly ?? 0; + const publicPackages = PACKAGES.filter((pkg) => !pkg.deprecated).length; + + const heroStats: HeroStat[] = [ + { + label: "GitHub stars", + value: repo ? formatCompact(repo.stars) : null, + caption: "and counting", + icon: Star, + }, + { + label: "Public packages", + value: publicPackages.toString(), + caption: "shipped on npm", + icon: Package, + }, + { + label: "Weekly downloads", + value: flagshipWeekly > 0 ? formatCompact(flagshipWeekly) : "—", + caption: FLAGSHIP_PACKAGE, + icon: ArrowUpRight, + }, + { + label: "Contributors", + value: contributors ? contributors.length.toString() : null, + caption: "from the community", + icon: Users, + }, + ]; + + const detailStats = [ + { + label: "Forks", + value: repo ? formatNumber(repo.forks) : null, + icon: GitFork, + }, + { + label: "Total commits", + value: + commitStats.total != null ? commitStats.total.toLocaleString() : null, + icon: GitCommit, + }, + { + label: "Days building in the open", + value: commitStats.firstCommitDate + ? daysSince(commitStats.firstCommitDate).toLocaleString() + : null, + icon: Sparkles, + }, + ...(dependents && dependents.repos > 0 + ? [ + { + label: "Public dependents", + value: formatNumber(dependents.repos), + icon: Network, + }, + ] + : []), + ]; + + return ( + <main className="mx-auto w-full max-w-7xl px-4 pt-14 pb-16 md:pb-24"> + <header className="mb-16 max-w-3xl"> + <p className="text-muted-foreground mb-3 text-sm">Traction</p> + <h1 className="text-3xl font-medium tracking-tight md:text-4xl"> + The receipts behind assistant-ui. + </h1> + <p className="text-muted-foreground mt-3 md:text-lg"> + assistant-ui is the open-source UX layer for AI chat. Here are the + numbers, the packages, and the teams shipping with it today. + </p> + <div className="mt-6 flex flex-wrap items-center gap-3"> + <a + href={REPO_URL} + target="_blank" + rel="noopener noreferrer" + className={buttonVariants({ variant: "outline" })} + > + <GitHubIcon className="size-4" /> + Star on GitHub + </a> + <Button asChild> + <Link href="/docs"> + Get started <ArrowRight /> + </Link> + </Button> + </div> + </header> + + <section className="mb-20"> + <div className="border-border bg-border grid grid-cols-2 gap-px overflow-hidden rounded-xl border md:grid-cols-4"> + {heroStats.map((stat) => { + if (stat.label === "Weekly downloads") { + return ( + <WeeklyDownloadsStat + key={stat.label} + flagship={{ + value: flagshipWeekly, + caption: FLAGSHIP_PACKAGE, + }} + total={{ + value: npm.totalWeekly, + caption: "across all packages", + }} + /> + ); + } + const Icon = stat.icon; + return ( + <div + key={stat.label} + className="bg-background flex flex-col gap-3 p-6" + > + <Icon className="text-muted-foreground size-4" /> + <div className="text-3xl font-medium tracking-tight tabular-nums md:text-4xl"> + {stat.value ?? "—"} + </div> + <div className="flex flex-col gap-0.5"> + <span className="text-sm">{stat.label}</span> + <span className="text-muted-foreground text-xs"> + {stat.caption} + </span> + </div> + </div> + ); + })} + </div> + <div className="mt-6 flex justify-center"> + <Link + href="/packages" + className={buttonVariants({ variant: "outline" })} + > + See packages detail + <ArrowRight /> + </Link> + </div> + </section> + + <section className="mb-20 grid gap-12 lg:grid-cols-2"> + <div className="flex flex-col gap-4"> + <div className="flex flex-col gap-1"> + <h2 className="text-xl font-medium tracking-tight"> + Stars over time + </h2> + <p className="text-muted-foreground text-sm"> + Sampled directly from the GitHub stargazers API. + </p> + </div> + <StarHistoryChart data={starHistory} /> + </div> + <div className="flex flex-col gap-4"> + <div className="flex flex-col gap-1"> + <h2 className="text-xl font-medium tracking-tight"> + Ecosystem downloads + </h2> + <p className="text-muted-foreground text-sm"> + Monthly npm downloads for the {TIMELINE_PACKAGES.length} core + packages. + </p> + </div> + <DownloadsChart timeline={downloadsTimeline} /> + </div> + </section> + + <section className="mb-20 flex flex-col gap-4"> + <div className="flex flex-col gap-1"> + <h2 className="text-xl font-medium tracking-tight"> + Shipping cadence + </h2> + <p className="text-muted-foreground text-sm"> + Daily commits over the last year. Cells with a ring mark the days we + shipped a release. + </p> + </div> + <ActivityHeatmap commits={commitActivity} releases={releaseActivity} /> + </section> + + <section className="mb-20"> + <div className="mb-8 flex flex-col gap-1"> + <h2 className="text-xl font-medium tracking-tight"> + Repository momentum + </h2> + <p className="text-muted-foreground text-sm"> + Live from the assistant-ui/assistant-ui GitHub repository. + </p> + </div> + <div className="grid grid-cols-1 gap-4 md:grid-cols-2 lg:grid-cols-4"> + {detailStats.map((stat) => { + const Icon = stat.icon; + return ( + <div + key={stat.label} + className="border-border flex items-start gap-4 rounded-lg border p-5" + > + <Icon className="text-muted-foreground mt-1 size-4" /> + <div className="flex flex-col"> + <span className="text-2xl font-medium tracking-tight tabular-nums"> + {stat.value ?? "—"} + </span> + <span className="text-muted-foreground text-sm"> + {stat.label} + </span> + </div> + </div> + ); + })} + </div> + </section> + + {contributors && contributors.length > 0 ? ( + <section className="mb-20"> + <div className="mb-8 flex flex-col gap-1"> + <h2 className="text-xl font-medium tracking-tight"> + Built by {contributors.length} contributors + </h2> + <p className="text-muted-foreground text-sm"> + Thanks to everyone who has shipped code to assistant-ui. + </p> + </div> + <div className="flex flex-wrap gap-2"> + {contributors.map((c) => ( + <a + key={c.login} + href={c.htmlUrl} + target="_blank" + rel="noopener noreferrer" + title={`${c.login} · ${c.contributions.toLocaleString()} commit${c.contributions === 1 ? "" : "s"}`} + className="block transition-transform hover:scale-110" + > + {/* eslint-disable-next-line @next/next/no-img-element */} + <img + src={c.avatarUrl} + alt={c.login} + width={32} + height={32} + loading="lazy" + className="border-border size-8 rounded-full border" + /> + </a> + ))} + </div> + {botCoAuthors.length > 0 ? ( + <div className="mt-8 flex flex-col gap-3"> + <div className="text-muted-foreground flex items-center gap-1.5 text-sm"> + <Bot className="size-4" /> + <span>With bot</span> + </div> + <div className="flex flex-wrap gap-2"> + {botCoAuthors.map((c) => ( + <a + key={c.login} + href={c.htmlUrl} + target="_blank" + rel="noopener noreferrer" + title={`${c.login} · co-authored ${c.contributions.toLocaleString()} commit${c.contributions === 1 ? "" : "s"}`} + className="block transition-transform hover:scale-110" + > + {/* eslint-disable-next-line @next/next/no-img-element */} + <img + src={c.avatarUrl} + alt={c.login} + width={32} + height={32} + loading="lazy" + className="border-border size-8 rounded-full border" + /> + </a> + ))} + </div> + </div> + ) : null} + </section> + ) : null} + + <section className="border-border mb-20 rounded-xl border p-8 md:p-12"> + <div className="grid gap-8 md:grid-cols-[1fr_auto] md:items-center"> + <div className="flex flex-col gap-2"> + <h2 className="text-xl font-medium tracking-tight"> + Used by teams shipping AI in production. + </h2> + <p className="text-muted-foreground text-sm"> + From early-stage startups to LangChain, Mastra, and Y + Combinator-backed teams. Browse public deployments in the + showcase. + </p> + </div> + <div className="flex flex-wrap gap-3 md:justify-end"> + <Link + href="/showcase" + className={buttonVariants({ variant: "outline" })} + > + See the showcase + </Link> + <Button asChild> + <Link href="/docs"> + Read the docs <ArrowRight /> + </Link> + </Button> + </div> + </div> + </section> + + <section className="flex flex-col items-center gap-6 py-8 text-center"> + <p className="text-2xl font-medium tracking-tight"> + Build on a library teams already trust. + </p> + <div className="flex items-center gap-3"> + <Button asChild> + <Link href="/docs"> + Get started <ArrowRight /> + </Link> + </Button> + <a + href={REPO_URL} + target="_blank" + rel="noopener noreferrer" + className={buttonVariants({ variant: "outline" })} + > + <GitHubIcon className="size-4" /> + {repo ? `${formatCompact(repo.stars)} on GitHub` : "Star on GitHub"} + </a> + </div> + </section> + </main> + ); +} diff --git a/apps/docs/app/(products)/cloud-ai-sdk/layout.tsx b/apps/docs/app/(products)/cloud-ai-sdk/layout.tsx new file mode 100644 index 0000000..e17ac78 --- /dev/null +++ b/apps/docs/app/(products)/cloud-ai-sdk/layout.tsx @@ -0,0 +1,29 @@ +import type { Metadata } from "next"; +import type { ReactNode } from "react"; +import { SubProjectLayout } from "@/components/shared/sub-project-layout"; +import { createOgMetadata } from "@/lib/og"; + +const title = "Cloud AI SDK"; +const description = + "Add cloud persistence to any AI SDK app with a single hook change. Zero config, automatic thread management, and full message persistence."; + +export const metadata: Metadata = { + title, + description, + ...createOgMetadata(title, description), +}; + +export default function CloudAiSdkLayout({ + children, +}: { + children: ReactNode; +}): React.ReactElement { + return ( + <SubProjectLayout + name="cloud-ai-sdk" + githubPath="https://github.com/assistant-ui/assistant-ui/tree/main/packages/cloud-ai-sdk" + > + {children} + </SubProjectLayout> + ); +} diff --git a/apps/docs/app/(products)/cloud-ai-sdk/page.tsx b/apps/docs/app/(products)/cloud-ai-sdk/page.tsx new file mode 100644 index 0000000..91b6e61 --- /dev/null +++ b/apps/docs/app/(products)/cloud-ai-sdk/page.tsx @@ -0,0 +1,263 @@ +import { + ArrowRight, + Cloud, + FileText, + Layers, + RefreshCw, + Settings, + Zap, +} from "lucide-react"; +import Image from "next/image"; +import Link from "next/link"; +import { cn } from "@/lib/utils"; +import { CopyCommandButton } from "@/components/home/copy-command-button"; +import { Button, buttonVariants } from "@/components/ui/radix/button"; +import { DynamicCodeBlock } from "fumadocs-ui/components/dynamic-codeblock"; + +const ANALYTICS_PAGE = "cloud-ai-sdk" as const; + +const INSTALL_COMMAND = + "npm install @assistant-ui/cloud-ai-sdk @ai-sdk/react ai"; + +const FEATURES = [ + { + title: "Zero Config", + description: + "Set one environment variable. No providers, no context wrappers, no runtime objects. It just works.", + icon: Settings, + iconColor: "text-emerald-400", + }, + { + title: "Thread Management", + description: + "Built-in thread list, selection, creation, deletion, archiving, and renaming. Everything you need for a ChatGPT-style sidebar.", + icon: Layers, + iconColor: "text-blue-400", + }, + { + title: "Auto Persistence", + description: + "Messages persist automatically as they stream in. Users pick up where they left off, even after a full page refresh.", + icon: Cloud, + iconColor: "text-purple-400", + }, + { + title: "Auto Titles", + description: + "Every thread gets an AI-generated title after the first response. No extra code required.", + icon: FileText, + iconColor: "text-yellow-400", + }, + { + title: "Works With Any UI", + description: + "useCloudChat returns the same interface as useChat. Keep your existing components — only the import changes.", + icon: Zap, + iconColor: "text-cyan-400", + }, + { + title: "Full AI SDK Compatibility", + description: + "All useChat options pass through. Streaming, tool calls, message metadata — everything works as expected.", + icon: RefreshCw, + iconColor: "text-orange-400", + }, +] as const; + +export default function CloudAiSdkPage() { + return ( + <div className="mx-auto w-full max-w-7xl space-y-10 px-4 pt-14 pb-8 md:space-y-20"> + {/* Hero */} + <div className="flex flex-col items-center gap-6 text-center"> + <div className="flex flex-col gap-3"> + <h1 className="mx-auto max-w-3xl text-3xl font-medium tracking-tight md:text-5xl"> + <span className="font-mono">useChat</span> + <span className="text-muted-foreground/40 mx-3">{"\u2192"}</span> + <span className="font-mono">useCloudChat</span> + </h1> + <p className="text-muted-foreground mx-auto max-w-xl text-lg"> + Cloud persistence and thread management for any Vercel AI SDK app. + One import change. Zero config. + </p> + </div> + + <CopyCommandButton + command={INSTALL_COMMAND} + analyticsContext={{ page: ANALYTICS_PAGE, section: "hero" }} + /> + + <div className="text-muted-foreground flex flex-wrap items-center justify-center gap-x-5 gap-y-3 text-[13px]"> + <Link + href="/docs/cloud/ai-sdk" + className="text-foreground/60 hover:text-foreground font-medium transition-colors" + > + Getting Started {"\u2192"} + </Link> + <span className="bg-muted-foreground/20 hidden size-1 rounded-full sm:block" /> + <Link + href="/docs/cloud/ai-sdk#api-reference" + className="text-foreground/60 hover:text-foreground font-medium transition-colors" + > + API Reference {"\u2192"} + </Link> + </div> + </div> + + {/* Code comparison */} + <div className="mx-auto grid max-w-5xl gap-4 md:grid-cols-2"> + <DynamicCodeBlock + lang="tsx" + code={`import { useChat } from "@ai-sdk/react" + +const { messages, sendMessage } = useChat()`} + options={{ + themes: { light: "catppuccin-latte", dark: "catppuccin-mocha" }, + }} + codeblock={{ + title: "Before", + keepBackground: true, + className: "my-0 bg-neutral-900!", + }} + /> + <DynamicCodeBlock + lang="tsx" + code={`import { useCloudChat } from "@assistant-ui/cloud-ai-sdk" + +const { messages, sendMessage, threads } = useCloudChat()`} + options={{ + themes: { light: "catppuccin-latte", dark: "catppuccin-mocha" }, + }} + codeblock={{ + title: "After", + keepBackground: true, + className: "border! my-0 border-blue-600! bg-neutral-900!", + }} + /> + </div> + + {/* Features */} + <div className="flex flex-col gap-8"> + <div className="flex flex-col items-center gap-2 text-center"> + <h2 className="text-3xl font-medium tracking-tight"> + Everything you need, nothing you don't + </h2> + <p className="text-muted-foreground max-w-xl"> + Two hooks. Four types. Full cloud persistence and thread management + for any AI SDK app. + </p> + </div> + + <div className="grid gap-4 sm:grid-cols-2 lg:grid-cols-3"> + {FEATURES.map((feature) => { + const Icon = feature.icon; + return ( + <div + key={feature.title} + className="border-border/50 bg-muted/30 hover:border-border/80 flex flex-col gap-2 rounded-xl border p-4 transition-colors" + > + <span className="flex items-center gap-2 font-medium"> + <Icon className={cn("size-4", feature.iconColor)} /> + {feature.title} + </span> + <p className="text-muted-foreground text-sm leading-relaxed"> + {feature.description} + </p> + </div> + ); + })} + </div> + </div> + + {/* Dashboard */} + <div className="flex flex-col gap-8"> + <div className="flex flex-col items-center gap-2 text-center"> + <h2 className="text-3xl font-medium tracking-tight"> + Manage everything from the dashboard + </h2> + <p className="text-muted-foreground max-w-xl"> + Analytics, thread browser, run tracking, user insights, auth rules — + all from{" "} + <a + href="https://cloud.assistant-ui.com" + target="_blank" + rel="noopener noreferrer" + className="text-foreground/80 hover:text-foreground underline underline-offset-4 transition-colors" + > + cloud.assistant-ui.com + </a> + </p> + </div> + + <div className="border-border/50 mx-auto w-full max-w-4xl overflow-hidden rounded-xl border shadow-lg"> + <Image + src="/images/cloud-dashboard.png" + alt="Assistant Cloud dashboard showing analytics, threads, and run tracking" + width={1200} + height={675} + className="w-full" + /> + </div> + + <div className="mx-auto grid max-w-3xl gap-x-8 gap-y-3 text-sm sm:grid-cols-2"> + {[ + "Analytics & cost tracking", + "Thread browser with conversation replay", + "Per-user metrics & activity", + "Run waterfall traces", + "Auth rules (Clerk, Auth0, Supabase, Firebase)", + "API key management", + ].map((item) => ( + <div + key={item} + className="text-muted-foreground flex items-center gap-2" + > + <span className="size-1 shrink-0 rounded-full bg-blue-400" /> + {item} + </div> + ))} + </div> + </div> + + {/* Callout */} + <div className="border-border/50 bg-muted/30 mx-auto flex max-w-3xl flex-col items-center gap-4 rounded-xl border p-8 text-center"> + <Cloud className="size-8 text-blue-400" /> + <h3 className="text-xl font-medium tracking-tight"> + Already using AI SDK? + </h3> + <p className="text-muted-foreground max-w-lg"> + If you're using <code className="text-foreground/80">useChat</code>{" "} + from <code className="text-foreground/80">@ai-sdk/react</code> today, + switching is a one-line change. Your components, your route handlers, + your tool definitions — they all stay the same. + </p> + <Button variant="outline" asChild> + <Link href="/docs/cloud/ai-sdk"> + Read the docs <ArrowRight /> + </Link> + </Button> + </div> + + {/* CTA */} + <div className="flex flex-col items-center gap-6 py-16 text-center"> + <p className="text-2xl font-medium tracking-tight"> + Start building today + </p> + <div className="flex flex-wrap items-center justify-center gap-3"> + <Button asChild> + <Link href="/docs/cloud/ai-sdk"> + Get Started <ArrowRight /> + </Link> + </Button> + <a + href="https://github.com/assistant-ui/assistant-ui/tree/main/examples/with-cloud-standalone" + target="_blank" + rel="noopener noreferrer" + className={buttonVariants({ variant: "outline" })} + > + View Example + </a> + </div> + </div> + </div> + ); +} diff --git a/apps/docs/app/(products)/ink/layout.tsx b/apps/docs/app/(products)/ink/layout.tsx new file mode 100644 index 0000000..5a55d3c --- /dev/null +++ b/apps/docs/app/(products)/ink/layout.tsx @@ -0,0 +1,29 @@ +import type { Metadata } from "next"; +import type { ReactNode } from "react"; +import { SubProjectLayout } from "@/components/shared/sub-project-layout"; +import { createOgMetadata } from "@/lib/og"; + +const title = "assistant-ui for the Terminal"; +const description = + "Build AI chat apps for the terminal with React Ink. Powered by the assistant-ui runtime, with full markdown rendering and cross-platform code sharing."; + +export const metadata: Metadata = { + title, + description, + ...createOgMetadata(title, description), +}; + +export default function InkLayout({ + children, +}: { + children: ReactNode; +}): React.ReactElement { + return ( + <SubProjectLayout + name="ink" + githubPath="https://github.com/assistant-ui/assistant-ui/tree/main/packages/react-ink" + > + {children} + </SubProjectLayout> + ); +} diff --git a/apps/docs/app/(products)/ink/page.tsx b/apps/docs/app/(products)/ink/page.tsx new file mode 100644 index 0000000..98edc80 --- /dev/null +++ b/apps/docs/app/(products)/ink/page.tsx @@ -0,0 +1,180 @@ +"use client"; + +import { + ArrowRight, + Code2, + Layers, + RefreshCw, + Terminal, + Wrench, + Zap, +} from "lucide-react"; +import Link from "next/link"; +import { cn } from "@/lib/utils"; +import { CopyCommandButton } from "@/components/home/copy-command-button"; +import { Button, buttonVariants } from "@/components/ui/radix/button"; +import { TerminalDemo } from "./terminal-demo"; + +const ANALYTICS_PAGE = "ink" as const; + +const INSTALL_COMMAND = "npx assistant-ui@latest create --ink my-app"; + +const FEATURES = [ + { + title: "Built for Ink", + description: + "First-class Ink support. Use React's component model to build beautiful terminal UIs with full ANSI color and layout support.", + icon: Terminal, + iconColor: "text-green-400", + }, + { + title: "Markdown in the Terminal", + description: + "Rich markdown rendering with syntax highlighting, tables, and links — all rendered as ANSI output via @assistant-ui/react-ink-markdown.", + icon: Zap, + iconColor: "text-yellow-400", + }, + { + title: "Battle-Tested Runtime", + description: + "Powered by the same engine and runtime system behind assistant-ui.com, refined over two years of production use.", + icon: RefreshCw, + iconColor: "text-blue-400", + }, + { + title: "Tool Call Support", + description: + "Register tools with execute functions and terminal-native UI renderers. Built-in ToolFallback component with expandable output and spinners.", + icon: Wrench, + iconColor: "text-purple-400", + }, + { + title: "Share Your Runtime Code", + description: + "Already using assistant-ui on the web or mobile? Reuse the same runtime, tools, and adapters in your terminal app.", + icon: Code2, + iconColor: "text-cyan-400", + }, + { + title: "Composable Primitives", + description: + "Unstyled, composable primitives — Thread, Composer, Message, and more — designed for terminal UIs from the ground up.", + icon: Layers, + iconColor: "text-orange-400", + }, +] as const; + +export default function InkPage() { + return ( + <div className="mx-auto w-full max-w-7xl space-y-10 px-4 pt-14 pb-8 md:space-y-20"> + {/* Hero */} + <div className="flex flex-col items-center gap-6 text-center"> + <div className="flex flex-col gap-3"> + <h1 className="mx-auto max-w-2xl text-3xl font-medium tracking-tight md:text-5xl"> + Build AI chat apps for the terminal + </h1> + <p className="text-muted-foreground mx-auto max-w-xl text-lg"> + Beautiful, production-ready AI chat for the terminal. Powered by the + same runtime as assistant-ui, with rich markdown rendering and + cross-platform code sharing. + </p> + </div> + + <CopyCommandButton + command={INSTALL_COMMAND} + analyticsContext={{ page: ANALYTICS_PAGE, section: "hero" }} + /> + + <div className="text-muted-foreground flex flex-wrap items-center justify-center gap-x-5 gap-y-3 text-[13px]"> + <Link + href="/docs/ink" + className="text-foreground/60 hover:text-foreground font-medium transition-colors" + > + Getting Started → + </Link> + <span className="bg-muted-foreground/20 hidden size-1 rounded-full sm:block" /> + <Link + href="/docs/ink/migration" + className="text-foreground/60 hover:text-foreground font-medium transition-colors" + > + Migration from Web → + </Link> + </div> + </div> + + {/* Terminal mockup */} + <TerminalDemo /> + + {/* Features */} + <div className="flex flex-col gap-8"> + <div className="flex flex-col items-center gap-2 text-center"> + <h2 className="text-3xl font-medium tracking-tight"> + Why assistant-ui for the Terminal? + </h2> + <p className="text-muted-foreground max-w-xl"> + Everything you need to build a world-class AI chat experience in the + terminal — without starting from scratch. + </p> + </div> + + <div className="grid gap-4 sm:grid-cols-2 lg:grid-cols-3"> + {FEATURES.map((feature) => { + const Icon = feature.icon; + return ( + <div + key={feature.title} + className="border-border/50 bg-muted/30 hover:border-border/80 flex flex-col gap-2 rounded-xl border p-4 transition-colors" + > + <span className="flex items-center gap-2 font-medium"> + <Icon className={cn("size-4", feature.iconColor)} /> + {feature.title} + </span> + <p className="text-muted-foreground text-sm leading-relaxed"> + {feature.description} + </p> + </div> + ); + })} + </div> + </div> + + {/* Code sharing callout */} + <div className="border-border/50 bg-muted/30 mx-auto flex max-w-3xl flex-col items-center gap-4 rounded-xl border p-8 text-center"> + <Code2 className="size-8 text-emerald-400" /> + <h3 className="text-xl font-medium tracking-tight"> + Already using assistant-ui? + </h3> + <p className="text-muted-foreground max-w-lg"> + Your existing runtime setup, tool definitions, and model adapters work + with the React Ink package. Share the same code between your web, + mobile, and terminal apps — only the UI layer changes. + </p> + <Button variant="outline" asChild> + <Link href="/docs/ink/migration"> + See the migration guide <ArrowRight /> + </Link> + </Button> + </div> + + {/* CTA */} + <div className="flex flex-col items-center gap-6 py-16 text-center"> + <p className="text-2xl font-medium tracking-tight"> + Start building today + </p> + <div className="flex flex-wrap items-center justify-center gap-3"> + <Button asChild> + <Link href="/docs/ink"> + Get Started <ArrowRight /> + </Link> + </Button> + <Link + href="/docs/ink/migration" + className={buttonVariants({ variant: "outline" })} + > + Migration Guide + </Link> + </div> + </div> + </div> + ); +} diff --git a/apps/docs/app/(products)/ink/terminal-demo.tsx b/apps/docs/app/(products)/ink/terminal-demo.tsx new file mode 100644 index 0000000..594a0d6 --- /dev/null +++ b/apps/docs/app/(products)/ink/terminal-demo.tsx @@ -0,0 +1,34 @@ +const INK_DEMO_URL = + process.env.NEXT_PUBLIC_INK_DEMO_URL ?? + (process.env.NODE_ENV === "development" + ? "http://localhost:8081" + : "https://assistant-ui-ink.vercel.app"); + +export function TerminalDemo() { + return ( + <section className="py-8"> + <div className="mx-auto w-full max-w-[800px] overflow-hidden rounded-xl border border-white/10 bg-[#1a1a1a]"> + <div className="flex items-center gap-2 border-b border-white/[0.06] px-4 py-3"> + <div className="flex gap-1.5"> + <div className="size-3 rounded-full bg-[#ff5f57]" /> + <div className="size-3 rounded-full bg-[#febc2e]" /> + <div className="size-3 rounded-full bg-[#28c840]" /> + </div> + <div className="flex-1 text-center font-mono text-[13px] text-white/50"> + assistant-ui ink + </div> + </div> + <iframe + src={INK_DEMO_URL} + className="h-[480px] w-full" + title="assistant-ui ink live demo" + allow="clipboard-read; clipboard-write" + /> + </div> + <p className="text-muted-foreground mt-3 text-center font-mono text-xs"> + A real Ink render loop and a real LLM, live. Click or tap the terminal + and type. + </p> + </section> + ); +} diff --git a/apps/docs/app/(products)/native/layout.tsx b/apps/docs/app/(products)/native/layout.tsx new file mode 100644 index 0000000..34b9d43 --- /dev/null +++ b/apps/docs/app/(products)/native/layout.tsx @@ -0,0 +1,29 @@ +import type { Metadata } from "next"; +import type { ReactNode } from "react"; +import { SubProjectLayout } from "@/components/shared/sub-project-layout"; +import { createOgMetadata } from "@/lib/og"; + +const title = "assistant-ui for React Native"; +const description = + "Get the UX of ChatGPT in your own mobile app. Full Expo support, cross-platform code sharing, powered by the assistant-ui runtime."; + +export const metadata: Metadata = { + title, + description, + ...createOgMetadata(title, description), +}; + +export default function NativeLayout({ + children, +}: { + children: ReactNode; +}): React.ReactElement { + return ( + <SubProjectLayout + name="native" + githubPath="https://github.com/assistant-ui/assistant-ui/tree/main/packages/react-native" + > + {children} + </SubProjectLayout> + ); +} diff --git a/apps/docs/app/(products)/native/page.tsx b/apps/docs/app/(products)/native/page.tsx new file mode 100644 index 0000000..4251e75 --- /dev/null +++ b/apps/docs/app/(products)/native/page.tsx @@ -0,0 +1,194 @@ +"use client"; + +import { + ArrowRight, + Code2, + Globe, + Layers, + RefreshCw, + Smartphone, + Zap, +} from "lucide-react"; +import Link from "next/link"; +import { cn } from "@/lib/utils"; +import { CopyCommandButton } from "@/components/home/copy-command-button"; +import { Button, buttonVariants } from "@/components/ui/radix/button"; + +import "./phone-mockup.css"; + +const ANALYTICS_PAGE = "native" as const; + +const INSTALL_COMMAND = "npx assistant-ui@latest create --native my-app"; + +const FEATURES = [ + { + title: "Built for Expo", + description: + "First-class Expo support. Use Expo Router, EAS Build, and the full Expo ecosystem out of the box.", + icon: Zap, + iconColor: "text-yellow-400", + }, + { + title: "iOS, Android & Web", + description: + "Write once, run on every platform. Share UI components and business logic across iOS, Android, and web.", + icon: Smartphone, + iconColor: "text-green-400", + }, + { + title: "Battle-Tested Runtime", + description: + "Powered by the same engine and runtime system behind assistant-ui.com, refined over two years of production use.", + icon: RefreshCw, + iconColor: "text-blue-400", + }, + { + title: "AI SDK & LangGraph", + description: + "Works with Vercel AI SDK, LangGraph, and other popular frameworks. Plug in your existing backend with zero changes.", + icon: Layers, + iconColor: "text-purple-400", + }, + { + title: "Share Your Runtime Code", + description: + "Already using assistant-ui on the web? Reuse the same runtime, tools, and adapters in your React Native app.", + icon: Code2, + iconColor: "text-cyan-400", + }, + { + title: "Cross-Platform Primitives", + description: + "Composable, unstyled primitives — Thread, Composer, Message, and more — designed for native from the ground up.", + icon: Globe, + iconColor: "text-orange-400", + }, +] as const; + +export default function NativePage() { + return ( + <div className="mx-auto w-full max-w-7xl space-y-10 px-4 pt-14 pb-8 md:space-y-20"> + {/* Hero */} + <div className="flex flex-col items-center gap-6 text-center"> + <div className="flex flex-col gap-3"> + <h1 className="mx-auto max-w-2xl text-3xl font-medium tracking-tight md:text-5xl"> + Get the UX of ChatGPT in your own mobile app + </h1> + <p className="text-muted-foreground mx-auto max-w-xl text-lg"> + Beautiful, production-ready AI chat for React Native. Powered by the + same runtime as assistant-ui, with full Expo support and + cross-platform code sharing. + </p> + </div> + + <CopyCommandButton + command={INSTALL_COMMAND} + analyticsContext={{ page: ANALYTICS_PAGE, section: "hero" }} + /> + + <div className="text-muted-foreground flex flex-wrap items-center justify-center gap-x-5 gap-y-3 text-[13px]"> + <Link + href="/docs/react-native" + className="text-foreground/60 hover:text-foreground font-medium transition-colors" + > + Getting Started → + </Link> + <span className="bg-muted-foreground/20 hidden size-1 rounded-full sm:block" /> + <Link + href="/docs/react-native/migration" + className="text-foreground/60 hover:text-foreground font-medium transition-colors" + > + Migration from Web → + </Link> + </div> + </div> + + {/* Phone mockup */} + <div className="phone-mockup-section relative"> + <div className="phone-mockup-glow" /> + <div className="phone-mockup-frame"> + <div className="phone-mockup-border" /> + <div className="phone-mockup-notch" /> + <div className="phone-mockup-screen"> + <iframe + src="https://assistant-ui-expo.vercel.app/" + className="size-full" + title="assistant-ui React Native demo" + /> + </div> + </div> + </div> + + {/* Features */} + <div className="flex flex-col gap-8"> + <div className="flex flex-col items-center gap-2 text-center"> + <h2 className="text-3xl font-medium tracking-tight"> + Why assistant-ui for React Native? + </h2> + <p className="text-muted-foreground max-w-xl"> + Everything you need to build a world-class AI chat experience on + mobile — without starting from scratch. + </p> + </div> + + <div className="grid gap-4 sm:grid-cols-2 lg:grid-cols-3"> + {FEATURES.map((feature) => { + const Icon = feature.icon; + return ( + <div + key={feature.title} + className="border-border/50 bg-muted/30 hover:border-border/80 flex flex-col gap-2 rounded-xl border p-4 transition-colors" + > + <span className="flex items-center gap-2 font-medium"> + <Icon className={cn("size-4", feature.iconColor)} /> + {feature.title} + </span> + <p className="text-muted-foreground text-sm leading-relaxed"> + {feature.description} + </p> + </div> + ); + })} + </div> + </div> + + {/* Code sharing callout */} + <div className="border-border/50 bg-muted/30 mx-auto flex max-w-3xl flex-col items-center gap-4 rounded-xl border p-8 text-center"> + <Code2 className="size-8 text-violet-400" /> + <h3 className="text-xl font-medium tracking-tight"> + Already using assistant-ui? + </h3> + <p className="text-muted-foreground max-w-lg"> + Your existing runtime setup, tool definitions, and model adapters work + with the React Native package. Share the same code between your web + and mobile apps — only the UI layer changes. + </p> + <Button variant="outline" asChild> + <Link href="/docs/react-native/migration"> + See the migration guide <ArrowRight /> + </Link> + </Button> + </div> + + {/* CTA */} + <div className="flex flex-col items-center gap-6 py-16 text-center"> + <p className="text-2xl font-medium tracking-tight"> + Start building today + </p> + <div className="flex flex-wrap items-center justify-center gap-3"> + <Button asChild> + <Link href="/docs/react-native"> + Get Started <ArrowRight /> + </Link> + </Button> + <Link + href="/docs/react-native/migration" + className={buttonVariants({ variant: "outline" })} + > + Migration Guide + </Link> + </div> + </div> + </div> + ); +} diff --git a/apps/docs/app/(products)/native/phone-mockup.css b/apps/docs/app/(products)/native/phone-mockup.css new file mode 100644 index 0000000..2f02acb --- /dev/null +++ b/apps/docs/app/(products)/native/phone-mockup.css @@ -0,0 +1,110 @@ +.phone-mockup-section { + padding: 2rem 0; + perspective: 1200px; +} + +.phone-mockup-glow { + position: absolute; + inset: 0; + margin: 0 auto; + pointer-events: none; + background: radial-gradient( + ellipse 50% 50% at 50% 40%, + rgba(139, 92, 246, 0.08) 0%, + transparent 70% + ); +} + +.phone-mockup-frame { + position: relative; + width: 320px; + margin: 0 auto; + border-radius: 2.5rem; + background: #1a1a1a; + padding: 12px; + box-shadow: + 0 0 0 1px rgba(255, 255, 255, 0.1), + 0 25px 50px -12px rgba(0, 0, 0, 0.5), + 0 0 80px rgba(139, 92, 246, 0.08); + transition: transform 0.4s ease; +} + +.phone-mockup-frame:hover { + transform: translateY(-4px); +} + +.phone-mockup-notch { + position: absolute; + top: 12px; + left: 50%; + transform: translateX(-50%); + width: 120px; + height: 28px; + background: #1a1a1a; + border-radius: 0 0 1rem 1rem; + z-index: 10; +} + +.phone-mockup-screen { + position: relative; + width: 100%; + aspect-ratio: 9 / 19.5; + border-radius: 2rem; + overflow: hidden; + background: #000; +} + +.phone-mockup-border { + position: absolute; + inset: -2px; + border-radius: 2.75rem; + padding: 2px; + background: linear-gradient( + 135deg, + rgba(139, 92, 246, 0.4) 0%, + rgba(59, 130, 246, 0.3) 25%, + rgba(16, 185, 129, 0.3) 50%, + rgba(59, 130, 246, 0.3) 75%, + rgba(139, 92, 246, 0.4) 100% + ); + background-size: 300% 300%; + animation: phone-gradient-shift 8s ease infinite; + mask: + linear-gradient(#fff 0 0) content-box, + linear-gradient(#fff 0 0); + mask-composite: exclude; + -webkit-mask: + linear-gradient(#fff 0 0) content-box, + linear-gradient(#fff 0 0); + -webkit-mask-composite: xor; + opacity: 0.5; + transition: opacity 0.3s ease; +} + +.phone-mockup-frame:hover .phone-mockup-border { + opacity: 1; +} + +@keyframes phone-gradient-shift { + 0% { + background-position: 0% 50%; + } + 50% { + background-position: 100% 50%; + } + 100% { + background-position: 0% 50%; + } +} + +@media (prefers-reduced-motion: reduce) { + .phone-mockup-border { + animation: none; + } + .phone-mockup-frame { + transition: none; + } + .phone-mockup-frame:hover { + transform: none; + } +} diff --git a/apps/docs/app/(products)/tap/docs/[[...slug]]/page.tsx b/apps/docs/app/(products)/tap/docs/[[...slug]]/page.tsx new file mode 100644 index 0000000..f3f9473 --- /dev/null +++ b/apps/docs/app/(products)/tap/docs/[[...slug]]/page.tsx @@ -0,0 +1,114 @@ +import type { Metadata } from "next"; +import { DocsPage, DocsBody } from "fumadocs-ui/page"; +import { notFound } from "next/navigation"; +import { getMDXComponents } from "@/mdx-components"; +import { tapDocs } from "@/lib/source"; +import { findNeighbour, getPageTreePeers } from "fumadocs-core/page-tree"; +import { Card, Cards } from "@/components/docs/fumadocs/card"; +import { TableOfContents } from "@/components/docs/layout/table-of-contents"; +import { DocsPager } from "@/components/docs/layout/docs-pager"; +import { DocsFooter } from "@/components/docs/layout/docs-footer"; +import { createOgMetadata } from "@/lib/og"; + +function DocsCategory({ url }: { url?: string }) { + const effectiveUrl = url ?? ""; + return ( + <Cards> + {getPageTreePeers(tapDocs.pageTree, effectiveUrl).map((peer) => ( + <Card key={peer.url} title={peer.name} href={peer.url}> + {peer.description} + </Card> + ))} + </Cards> + ); +} + +export default async function Page(props: { + params: Promise<{ slug?: string[] }>; +}) { + const params = await props.params; + const page = tapDocs.getPage(params.slug ?? []); + + if (page == null) { + notFound(); + } + + const mdxComponents = getMDXComponents({ + DocsCategory, + }); + + const path = `apps/docs/content/tap-docs/${page.path}`; + const markdownUrl = `${page.url}.md`; + const githubEditUrl = `https://github.com/assistant-ui/assistant-ui/edit/main/${path}`; + + const neighbours = findNeighbour(tapDocs.pageTree, page.url); + const footerPrevious = neighbours.previous + ? { name: neighbours.previous.name, url: neighbours.previous.url } + : undefined; + const footerNext = neighbours.next + ? { name: neighbours.next.name, url: neighbours.next.url } + : undefined; + + return ( + <DocsPage + toc={page.data.toc} + full + tableOfContent={{ + enabled: true, + component: ( + <TableOfContents + items={page.data.toc} + githubEditUrl={githubEditUrl} + markdownUrl={markdownUrl} + /> + ), + }} + tableOfContentPopover={{ + enabled: false, + }} + footer={{ + enabled: false, + }} + > + <DocsBody> + <header className="not-prose mb-8"> + <div className="flex items-center justify-between gap-4"> + <h1 className="text-xl font-medium tracking-tight md:text-2xl"> + {page.data.title} + </h1> + <DocsPager + {...(footerPrevious && { previous: { url: footerPrevious.url } })} + {...(footerNext && { next: { url: footerNext.url } })} + markdownUrl={markdownUrl} + /> + </div> + {page.data.description && ( + <p className="text-muted-foreground mt-2 text-sm md:text-base"> + {page.data.description} + </p> + )} + </header> + <page.data.body components={mdxComponents} /> + <DocsFooter previous={footerPrevious} next={footerNext} /> + </DocsBody> + </DocsPage> + ); +} + +export function generateStaticParams() { + return tapDocs.generateParams(); +} + +export async function generateMetadata( + props: PageProps<"/tap/docs/[[...slug]]">, +): Promise<Metadata> { + const { slug = [] } = await props.params; + const page = tapDocs.getPage(slug); + if (!page) return { title: "Not Found" }; + + return { + title: page.data.title, + description: page.data.description, + ...createOgMetadata(page.data.title, page.data.description), + }; +} diff --git a/apps/docs/app/(products)/tap/docs/layout.tsx b/apps/docs/app/(products)/tap/docs/layout.tsx new file mode 100644 index 0000000..4a8d0e7 --- /dev/null +++ b/apps/docs/app/(products)/tap/docs/layout.tsx @@ -0,0 +1,16 @@ +import type { ReactNode } from "react"; +import { tapDocs } from "@/lib/source"; +import { DocsRootLayout } from "@/components/docs/layout/docs-root-layout"; + +export default function Layout({ children }: { children: ReactNode }) { + return ( + <DocsRootLayout + tree={tapDocs.pageTree} + section="Tap Docs" + sectionHref="/tap/docs" + platformAware={false} + > + {children} + </DocsRootLayout> + ); +} diff --git a/apps/docs/app/(products)/tap/page.tsx b/apps/docs/app/(products)/tap/page.tsx new file mode 100644 index 0000000..60ee306 --- /dev/null +++ b/apps/docs/app/(products)/tap/page.tsx @@ -0,0 +1,15 @@ +import Link from "next/link"; + +export default function TapLandingPage() { + return ( + <div className="flex min-h-[80vh] items-center justify-center"> + <div className="text-left"> + <h1 className="font-bold">tap</h1> + <p className="text-muted-foreground">Hooks for Reactive Resources</p> + <Link href="/tap/docs" className="text-muted-foreground underline"> + Docs > + </Link> + </div> + </div> + ); +} diff --git a/apps/docs/app/api/chat/route.ts b/apps/docs/app/api/chat/route.ts new file mode 100644 index 0000000..4a8eda4 --- /dev/null +++ b/apps/docs/app/api/chat/route.ts @@ -0,0 +1,143 @@ +import { getDistinctId, posthogServer } from "@/lib/posthog-server"; +import { createPrismTracer, prismAISDK } from "@/lib/prism-server"; +import { + injectQuoteContext, + unstable_injectInteractableContext as injectInteractableContext, +} from "@assistant-ui/react-ai-sdk"; +import { checkRateLimit } from "@/lib/rate-limit"; +import { validateGeneralChatInput } from "@/lib/validate-input"; +import { getModel, withTracing } from "@/lib/ai/provider"; +import { AISDKToolkit } from "@assistant-ui/react-ai-sdk"; +import docsToolkit from "@/lib/docs-toolkit"; +import { + convertToModelMessages, + pruneMessages, + stepCountIs, + streamText, +} from "ai"; + +export const maxDuration = 30; + +const aiToolkit = new AISDKToolkit({ toolkit: docsToolkit }); + +const ALLOWED_ORIGINS = [ + "https://assistant-ui-expo.vercel.app", + "https://assistant-ui-ink.vercel.app", + "http://localhost:8081", +]; + +function corsHeaders(req: Request) { + const origin = req.headers.get("origin") ?? ""; + if (!ALLOWED_ORIGINS.includes(origin)) return {}; + return { + "Access-Control-Allow-Origin": origin, + "Access-Control-Allow-Methods": "POST, OPTIONS", + "Access-Control-Allow-Headers": "Content-Type, User-Agent", + }; +} + +export async function OPTIONS(req: Request) { + return new Response(null, { status: 204, headers: corsHeaders(req) }); +} + +export async function POST(req: Request) { + try { + const rateLimitResponse = await checkRateLimit(req); + if (rateLimitResponse) return rateLimitResponse; + + const body = await req.json(); + const { messages, system: rawSystem, tools, config } = body; + + // Basic validation: only accept short system prompts to limit abuse surface + const MAX_SYSTEM_LENGTH = 4000; + const system = + typeof rawSystem === "string" && rawSystem.length <= MAX_SYSTEM_LENGTH + ? rawSystem + : undefined; + + const inputError = validateGeneralChatInput(messages); + if (inputError) { + const cors = corsHeaders(req); + for (const [key, value] of Object.entries(cors)) { + inputError.headers.set(key, value); + } + return inputError; + } + + const baseModel = getModel(config?.modelName); + const distinctId = getDistinctId(req); + const prismTracer = createPrismTracer(); + + const posthogModel = posthogServer + ? withTracing(baseModel, posthogServer, { + posthogDistinctId: distinctId, + posthogPrivacyMode: false, + posthogProperties: { + $ai_span_name: "general_chat", + source: "general_chat", + }, + }) + : baseModel; + + const prism = prismTracer + ? prismAISDK(prismTracer, posthogModel, { + name: "general_chat", + endUserId: distinctId, + }) + : null; + + const prunedMessages = pruneMessages({ + messages: await convertToModelMessages( + injectInteractableContext(injectQuoteContext(messages)), + ), + reasoning: "none", + }); + + const result = streamText({ + model: prism?.model ?? posthogModel, + ...(system ? { system } : {}), + messages: prunedMessages, + maxOutputTokens: 4096, + stopWhen: stepCountIs(10), + tools: await aiToolkit.tools({ frontend: tools }), + onFinish: async () => { + await prism?.end(); + }, + onError: async ({ error }) => { + console.error(error); + await prism?.end({ status: "error" }); + }, + onAbort: async () => { + await prism?.end(); + }, + }); + + const cors = corsHeaders(req); + const response = result.toUIMessageStreamResponse({ + // gets usage and modelId for assistant-cloud telemetry reports + messageMetadata: ({ part }) => { + if (part.type === "finish-step") { + return { + modelId: part.response.modelId, + }; + } + if (part.type === "finish") { + return { + usage: part.totalUsage, + }; + } + return undefined; + }, + }); + + // Append CORS headers + for (const [key, value] of Object.entries(cors)) { + response.headers.set(key, value); + } + + return response; + } catch (e) { + console.error("[api/chat]", e); + return new Response("Request failed", { status: 500 }); + } +} diff --git a/apps/docs/app/api/doc/chat/route.ts b/apps/docs/app/api/doc/chat/route.ts new file mode 100644 index 0000000..4705837 --- /dev/null +++ b/apps/docs/app/api/doc/chat/route.ts @@ -0,0 +1,450 @@ +import { getLLMText } from "@/lib/get-llm-text"; +import { getDistinctId, posthogServer } from "@/lib/posthog-server"; +import { createPrismTracer, prismAISDK } from "@/lib/prism-server"; +import { readFileSync } from "node:fs"; +import path from "node:path"; +import { injectQuoteContext } from "@assistant-ui/react-ai-sdk"; +import { checkRateLimit } from "@/lib/rate-limit"; +import { validateDocChatInput } from "@/lib/validate-input"; +import { source, examples as examplesSource } from "@/lib/source"; +import { getModel, withTracing } from "@/lib/ai/provider"; +import { frontendTools } from "@assistant-ui/react-ai-sdk"; +import { createBashTool } from "bash-tool"; +import { + convertToModelMessages, + pruneMessages, + stepCountIs, + streamText, + tool, + zodSchema, +} from "ai"; +import type * as PageTree from "fumadocs-core/page-tree"; +import type { UIMessage } from "ai"; +import z from "zod"; + +const SOURCE_SNAPSHOT_PATH = path.join( + process.cwd(), + "generated", + "source-snapshot.json", +); + +function loadSourceSnapshot(): Record<string, string> { + try { + return JSON.parse(readFileSync(SOURCE_SNAPSHOT_PATH, "utf-8")) as Record< + string, + string + >; + } catch (error) { + if ( + error && + typeof error === "object" && + "code" in error && + error.code === "ENOENT" + ) { + console.warn( + `Missing source snapshot at ${SOURCE_SNAPSHOT_PATH}; repo tools will be unavailable until generate:docs runs.`, + ); + return {}; + } + + throw error; + } +} + +const SOURCE_SNAPSHOT = loadSourceSnapshot(); + +function normalizeSegment(name: string): string { + return name.toLowerCase().replace(/\s+/g, "-"); +} + +function findFolderByPath( + tree: PageTree.Root, + path: string, +): PageTree.Folder | undefined { + const segments = path.split("/").filter(Boolean); + let currentFolder: PageTree.Folder | undefined; + let children: PageTree.Node[] = tree.children; + + for (const segment of segments) { + const folder = children.find( + (node): node is PageTree.Folder => + node.type === "folder" && + normalizeSegment(typeof node.name === "string" ? node.name : "") === + normalizeSegment(segment), + ); + if (!folder) return undefined; + currentFolder = folder; + children = folder.children; + } + + return currentFolder; +} + +function listChildren(nodes: PageTree.Node[]) { + return nodes.flatMap((node) => { + const description = + "description" in node && typeof node.description === "string" + ? node.description + : undefined; + + switch (node.type) { + case "page": + return { + type: "page", + title: node.name, + url: node.url, + ...(description ? { description } : {}), + }; + case "folder": + return { + type: "folder", + name: node.name, + ...(node.index ? { url: node.index.url } : {}), + ...(description ? { description } : {}), + }; + default: + return []; + } + }); +} + +const DOCS_PATH_ERROR = "Only local docs paths are supported"; + +function normalizeDocPath(slugOrUrl: string, routeUrl: string): string { + const raw = slugOrUrl.trim(); + if (!raw) { + throw new Error("Slug/path is required"); + } + + const current = new URL(routeUrl); + const isAbsoluteUrl = /^https?:\/\//i.test(raw); + + if (!isAbsoluteUrl) { + const cleaned = raw.replace(/^\/+/, "").replace(/^docs\//, ""); + if (!cleaned || cleaned.includes("..")) { + throw new Error(DOCS_PATH_ERROR); + } + return cleaned; + } + + const resolved = new URL(raw); + if (resolved.origin !== current.origin) { + throw new Error(DOCS_PATH_ERROR); + } + + const cleaned = resolved.pathname.replace(/^\/+/, "").replace(/^docs\//, ""); + if (!cleaned || cleaned.includes("..")) { + throw new Error(DOCS_PATH_ERROR); + } + + return cleaned; +} + +export const maxDuration = 300; + +export const DOC_CHAT_PRUNE_OPTIONS = { + toolCalls: "before-last-2-messages", + reasoning: "none", + emptyMessages: "remove", +} as const; + +export async function prepareDocChatMessages(messages: readonly UIMessage[]) { + const modelMessages = await convertToModelMessages( + injectQuoteContext([...messages]), + ); + + return pruneMessages({ + messages: modelMessages, + ...DOC_CHAT_PRUNE_OPTIONS, + }); +} + +function createRepoTools() { + let bashToolkitPromise: Promise< + Awaited<ReturnType<typeof createBashTool>> + > | null = null; + + const getBashToolkit = () => { + if (!bashToolkitPromise) { + bashToolkitPromise = createBashTool({ + files: SOURCE_SNAPSHOT, + destination: "/repo", + maxFiles: 5000, + maxOutputLength: 15000, + }); + } + + return bashToolkitPromise; + }; + + return { + bash: tool({ + description: + "Execute bash commands in the /repo sandbox containing the assistant-ui monorepo.", + inputSchema: zodSchema( + z.object({ + command: z + .string() + .describe("The bash command to execute from the /repo directory."), + }), + ), + execute: async ({ command }, options) => { + const { tools } = await getBashToolkit(); + return tools.bash.execute!({ command }, options); + }, + }), + readFile: tool({ + description: "Read the contents of a source file from the /repo sandbox.", + inputSchema: zodSchema( + z.object({ + path: z + .string() + .describe("The repo-relative file path to read from /repo."), + }), + ), + execute: async ({ path }, options) => { + const { tools } = await getBashToolkit(); + return tools.readFile.execute!({ path }, options); + }, + }), + }; +} + +const SYSTEM_PROMPT = `You are the assistant-ui docs assistant. + +<about_assistant_ui> +assistant-ui is a React library for building AI chat interfaces. It provides: +- Composable UI primitives (Thread, Composer, Message, etc.) +- Runtime adapters for AI backends (Vercel AI SDK, LangGraph, custom stores) +- Pre-built components with full customization support +</about_assistant_ui> + +<personality> +- Friendly, concise, developer-focused +- Answer the actual question - don't list documentation sections +- Use emoji sparingly (👋 for greetings, ✅ for success, etc.) +- Provide code snippets when they help clarify +- Link to relevant docs naturally within answers +</personality> + +<greetings> +When users send a casual greeting (hey, hi, hello): +1. Welcome them to assistant-ui with emoji 👋 +2. Briefly explain what assistant-ui helps them do (build AI chat interfaces in React) +3. Ask what they're working on or offer 2-3 common starting points + +Example tone: +"Hey! 👋 Welcome to assistant-ui! + +I'm here to help you build AI chat interfaces with React. Whether you're just getting started, connecting to an AI backend, or customizing components — I've got you covered. + +What are you working on?" + +Do NOT dump all documentation categories. Keep it conversational. +</greetings> + +<tools> +You have two documentation tools: + +1. **listDocs** - Browse documentation structure + - Use with no path for root categories + - Use with path (e.g., "ui", "runtimes") to see pages in that section + - Returns: list of folders and pages with URLs + +2. **readDoc** - Read a specific documentation page + - Input: slug (e.g., "ui/thread") or URL (e.g., "/docs/ui/thread") + - Returns: full page content + +**Recommended patterns:** +- User asks a question → listDocs to find relevant section → readDoc to get content +- User mentions a specific path → readDoc directly +</tools> + +<source_code_tools> +You also have tools for exploring the actual assistant-ui source code: + +3. **bash** - Execute bash commands in a sandbox containing the full monorepo + - The sandbox is at /repo with the complete source tree + - Use for: grep, find, cat, awk, head, tail, wc, ls, tree, etc. + - Example: \`grep -r "useThread" packages/ --include="*.ts" -l\` + +4. **readFile** - Read a specific source file by path + - More token-efficient than \`cat\` for reading whole files +</source_code_tools> + +<answering> +- Use the documentation tools to find relevant information +- **CRITICAL: ONLY use URLs that are explicitly returned by your tools** +- **NEVER guess or fabricate URLs** - if a tool didn't return a URL, don't link to it +- When linking, copy the exact URL from tool results: [Page Title](/docs/exact-path-from-tool) +- Prefer not linking over linking to a potentially non-existent page +- Admit uncertainty rather than guessing +</answering> + +<formatting> +Use inline code (\`backticks\`) for: +- Components: \`Thread\`, \`Composer\`, \`Message\` +- Hooks: \`useChat\`, \`useThreadRuntime\` +- Props, parameters, types +- Packages: \`@assistant-ui/react\` +- File paths +</formatting> +`; + +export async function POST(req: Request): Promise<Response> { + try { + const rateLimitResponse = await checkRateLimit(req); + if (rateLimitResponse) return rateLimitResponse; + + const body = await req.json(); + const { messages, tools, system: pageContext, config } = body; + + const prunedMessages = await prepareDocChatMessages(messages); + + const inputError = validateDocChatInput(prunedMessages); + if (inputError) return inputError; + + const baseModel = getModel(config?.modelName); + const distinctId = getDistinctId(req); + const prismTracer = createPrismTracer(); + + const posthogModel = posthogServer + ? withTracing(baseModel, posthogServer, { + posthogDistinctId: distinctId, + posthogPrivacyMode: false, + posthogProperties: { + $ai_span_name: "docs_assistant_chat", + source: "docs_assistant", + }, + }) + : baseModel; + + const prism = prismTracer + ? prismAISDK(prismTracer, posthogModel, { + name: "docs_assistant", + endUserId: distinctId, + }) + : null; + + const repoTools = createRepoTools(); + + const result = streamText({ + model: prism?.model ?? posthogModel, + system: [SYSTEM_PROMPT, pageContext].filter(Boolean).join("\n\n"), + messages: prunedMessages, + maxOutputTokens: 8192, + stopWhen: stepCountIs(25), + tools: { + ...frontendTools(tools), + ...repoTools, + listDocs: tool({ + description: + "List documentation pages. Use with no path for root categories, or specify path to browse a section.", + inputSchema: zodSchema( + z.object({ + path: z + .string() + .optional() + .describe( + "Path to browse (e.g., 'ui', 'runtimes'). Empty for root.", + ), + }), + ), + execute: async ({ path }) => { + const pageTree = source.pageTree; + + if (!path) { + // Return root categories + return [ + ...listChildren( + pageTree.children.filter( + (node): node is PageTree.Folder => node.type === "folder", + ), + ), + { + type: "folder", + name: "examples", + description: + "Examples of app types users can build with assistant-ui, showing instructions, recommended patterns, and UI structure.", + }, + ]; + } + + const segments = path.split("/").filter(Boolean); + if (segments[0] === "examples") { + const rest = segments.slice(1).join("/"); + const target = rest + ? findFolderByPath(examplesSource.pageTree, rest) + : examplesSource.pageTree; + if (!target) return { error: "Path not found" }; + return listChildren(target.children); + } + + const targetFolder = findFolderByPath(pageTree, path); + if (!targetFolder) return { error: "Path not found" }; + return listChildren(targetFolder.children); + }, + }), + readDoc: tool({ + description: "Read full content of a documentation page", + inputSchema: zodSchema( + z.object({ + slugOrUrl: z + .string() + .describe("Page slug (e.g., 'ui/thread') or URL"), + }), + ), + execute: async ({ slugOrUrl }) => { + let normalized: string; + try { + normalized = normalizeDocPath(slugOrUrl, req.url); + } catch (error) { + return { + error: + error instanceof Error ? error.message : "Invalid docs path", + }; + } + + const slugs = normalized.split("/").filter(Boolean); + const isExample = slugs[0] === "examples"; + const docSource = isExample ? examplesSource : source; + const docSlugs = isExample ? slugs.slice(1) : slugs; + + const page = docSource.getPage(docSlugs); + if (!page) return { error: `Page not found: ${slugOrUrl}` }; + + const content = await getLLMText(page); + return { title: page.data.title, url: page.url, content }; + }, + }), + }, + onFinish: async () => { + await prism?.end(); + }, + onError: async ({ error }) => { + console.error(error); + await prism?.end({ status: "error" }); + }, + onAbort: async () => { + await prism?.end(); + }, + }); + + return result.toUIMessageStreamResponse({ + originalMessages: messages, + // gets usage and modelId for internal telemetry + messageMetadata: ({ part }) => { + if (part.type === "finish-step") { + return { modelId: part.response.modelId }; + } + if (part.type === "finish") { + return { custom: { usage: part.totalUsage } }; + } + return undefined; + }, + }); + } catch (e) { + console.error("[api/doc/chat]", e); + return new Response("Request failed", { status: 500 }); + } +} diff --git a/apps/docs/app/api/github/repo/route.ts b/apps/docs/app/api/github/repo/route.ts new file mode 100644 index 0000000..536f715 --- /dev/null +++ b/apps/docs/app/api/github/repo/route.ts @@ -0,0 +1,19 @@ +import { getRepo } from "@/lib/github"; + +export const revalidate = 3600; + +export async function GET() { + const repo = await getRepo(); + if (!repo) { + return Response.json( + { error: "Repository data unavailable" }, + { status: 503 }, + ); + } + return Response.json(repo, { + headers: { + "Cache-Control": + "public, max-age=300, s-maxage=3600, stale-while-revalidate=86400", + }, + }); +} diff --git a/apps/docs/app/api/mcp/route.ts b/apps/docs/app/api/mcp/route.ts new file mode 100644 index 0000000..75dbbfe --- /dev/null +++ b/apps/docs/app/api/mcp/route.ts @@ -0,0 +1,490 @@ +import type * as PageTree from "fumadocs-core/page-tree"; +import { NextResponse, type NextRequest } from "next/server"; +import { getLLMText } from "@/lib/get-llm-text"; +import { examples, getTapDocsPage, source, tapDocs } from "@/lib/source"; + +export const revalidate = false; + +type JsonRpcRequest = { + jsonrpc?: string; + id?: string | number | null; + method?: string; + params?: unknown; +}; + +const PROTOCOL_VERSION = "2025-06-18"; + +const toolDefinitions = [ + { + name: "list_pages", + description: + "List assistant-ui documentation pages. Optionally filter by a URL path prefix such as /docs/tools, /examples, or /tap/docs.", + inputSchema: { + type: "object", + properties: { + path: { + type: "string", + description: "Optional docs path or prefix to filter by.", + }, + }, + additionalProperties: false, + }, + }, + { + name: "get_navigation", + description: "Return the assistant-ui docs navigation tree.", + inputSchema: { + type: "object", + properties: {}, + additionalProperties: false, + }, + }, + { + name: "search_docs", + description: + "Search assistant-ui docs, examples, and Tap docs by title, description, or URL.", + inputSchema: { + type: "object", + properties: { + query: { + type: "string", + description: "Search query.", + }, + }, + required: ["query"], + additionalProperties: false, + }, + }, + { + name: "read_page", + description: + "Read one assistant-ui docs, examples, or Tap docs page as markdown. Accepts a slug, path, .md URL, or same-origin URL.", + inputSchema: { + type: "object", + properties: { + path: { + type: "string", + description: + "Page path such as /docs/installation, /docs/installation.md, examples/ai-sdk, tap/docs/store/state, or a same-origin URL.", + }, + }, + required: ["path"], + additionalProperties: false, + }, + }, +]; + +function getStringParam(params: unknown, key: string) { + if (params && typeof params === "object" && key in params) { + const value = (params as Record<string, unknown>)[key]; + if (typeof value === "string") return value; + } + return undefined; +} + +function pageSummary(page: { + url: string; + data: { title: string; description?: string | undefined }; +}) { + return { + title: page.data.title, + url: page.url, + ...(page.data.description ? { description: page.data.description } : {}), + }; +} + +function allPages() { + return [ + ...source.getPages().map((page) => ({ kind: "docs" as const, page })), + ...examples.getPages().map((page) => ({ + kind: "examples" as const, + page, + })), + ...tapDocs.getPages().map((page) => ({ + kind: "tap" as const, + page, + })), + ]; +} + +function hasHttpScheme(value: string) { + const prefix = value.slice(0, "https://".length).toLowerCase(); + return prefix.startsWith("http://") || prefix.startsWith("https://"); +} + +function stripLeadingSlashes(value: string) { + let start = 0; + while (start < value.length && value.charCodeAt(start) === 47) start += 1; + return value.slice(start); +} + +function stripTrailingSlashes(value: string) { + let end = value.length; + while (end > 0 && value.charCodeAt(end - 1) === 47) end -= 1; + return value.slice(0, end); +} + +function stripMarkdownSuffix(value: string) { + const lower = value.toLowerCase(); + if (lower.endsWith("/index.mdx")) return value.slice(0, -"/index.mdx".length); + if (lower.endsWith("/index.md")) return value.slice(0, -"/index.md".length); + if (lower.endsWith(".mdx")) return value.slice(0, -".mdx".length); + if (lower.endsWith(".md")) return value.slice(0, -".md".length); + return value; +} + +function normalizePathname(rawPath: string, requestUrl?: string) { + let value = rawPath.trim(); + + if (hasHttpScheme(value)) { + const parsed = new URL(value); + if (requestUrl) { + const request = new URL(requestUrl); + if (parsed.origin !== request.origin) { + throw new Error("Only same-origin docs URLs are supported"); + } + } + value = parsed.pathname; + } + + return stripMarkdownSuffix(stripTrailingSlashes(stripLeadingSlashes(value))); +} + +function normalizePageUrlPrefix(rawPath: string) { + const pathname = normalizePathname(rawPath); + return pathname ? `/${pathname}` : ""; +} + +function normalizePath(rawPath: string, requestUrl: string) { + const value = normalizePathname(rawPath, requestUrl); + if (!value) return { kind: "docs" as const, slugs: [] }; + + if (value.includes("..")) { + throw new Error("Parent directory segments are not supported"); + } + + if (value === "docs") return { kind: "docs" as const, slugs: [] }; + if (value === "examples") return { kind: "examples" as const, slugs: [] }; + if (value === "tap/docs") return { kind: "tap" as const, slugs: [] }; + if (value.startsWith("docs/")) { + return { + kind: "docs" as const, + slugs: value.slice("docs/".length).split("/").filter(Boolean), + }; + } + if (value.startsWith("examples/")) { + return { + kind: "examples" as const, + slugs: value.slice("examples/".length).split("/").filter(Boolean), + }; + } + if (value.startsWith("tap/docs/")) { + return { + kind: "tap" as const, + slugs: value.slice("tap/docs/".length).split("/").filter(Boolean), + }; + } + return { kind: "docs" as const, slugs: value.split("/").filter(Boolean) }; +} + +function listPages(path: string | undefined) { + const normalizedPrefix = path ? normalizePageUrlPrefix(path) : undefined; + + return allPages() + .map(({ page }) => pageSummary(page)) + .filter( + (page) => + !normalizedPrefix || + page.url === normalizedPrefix || + page.url.startsWith(`${normalizedPrefix}/`), + ); +} + +function serializeNode(node: PageTree.Node): unknown { + if (node.type === "page") { + return { + type: "page", + title: typeof node.name === "string" ? node.name : node.url, + url: node.url, + ...("description" in node && + typeof node.description === "string" && + node.description + ? { description: node.description } + : {}), + }; + } + + if (node.type === "folder") { + return { + type: "folder", + title: typeof node.name === "string" ? node.name : undefined, + ...(node.index ? { url: node.index.url } : {}), + ...("description" in node && + typeof node.description === "string" && + node.description + ? { description: node.description } + : {}), + children: node.children.map(serializeNode), + }; + } + + return { + type: node.type, + }; +} + +function getNavigation() { + return { + docs: source.pageTree.children.map(serializeNode), + examples: examples.pageTree.children.map(serializeNode), + tapDocs: tapDocs.pageTree.children.map(serializeNode), + }; +} + +function searchDocs(query: string) { + const normalized = query.trim().toLowerCase(); + if (!normalized) return []; + + return allPages() + .map(({ page }) => pageSummary(page)) + .filter((page) => + [page.title, page.url, page.description ?? ""].some((value) => + value.toLowerCase().includes(normalized), + ), + ) + .slice(0, 20); +} + +async function readPage(path: string | undefined, requestUrl: string) { + if (!path) throw new Error("path is required"); + + const normalized = normalizePath(path, requestUrl); + const page = + normalized.kind === "examples" + ? examples.getPage(normalized.slugs) + : normalized.kind === "tap" + ? getTapDocsPage(normalized.slugs) + : source.getPage(normalized.slugs); + + if (!page) throw new Error(`Page not found: ${path}`); + + return { + title: page.data.title, + url: page.url, + content: await getLLMText(page), + }; +} + +async function callTool( + name: string | undefined, + params: unknown, + requestUrl: string, +) { + const args = + params && typeof params === "object" && "arguments" in params + ? (params as { arguments?: unknown }).arguments + : undefined; + + switch (name) { + case "list_pages": + return listPages(getStringParam(args, "path")); + case "get_navigation": + return getNavigation(); + case "search_docs": + return searchDocs(getStringParam(args, "query") ?? ""); + case "read_page": + return readPage(getStringParam(args, "path"), requestUrl); + default: + throw new Error(`Unknown tool: ${name ?? "missing"}`); + } +} + +function jsonRpcResult(id: JsonRpcRequest["id"], result: unknown) { + return { jsonrpc: "2.0", id, result }; +} + +function jsonRpcError(id: JsonRpcRequest["id"], code: number, message: string) { + return { jsonrpc: "2.0", id: id ?? null, error: { code, message } }; +} + +async function handleJsonRpcMessage( + message: JsonRpcRequest, + requestUrl: string, +) { + if (message.id === undefined) return null; + + try { + switch (message.method) { + case "initialize": { + return jsonRpcResult(message.id, { + protocolVersion: PROTOCOL_VERSION, + capabilities: { + tools: { listChanged: false }, + resources: { subscribe: false, listChanged: false }, + }, + serverInfo: { + name: "assistant-ui-docs", + version: "1.0.0", + }, + }); + } + case "tools/list": + return jsonRpcResult(message.id, { tools: toolDefinitions }); + case "tools/call": { + const name = getStringParam(message.params, "name"); + let result: unknown; + try { + result = await callTool(name, message.params, requestUrl); + } catch (error) { + return jsonRpcResult(message.id, { + content: [ + { + type: "text", + text: error instanceof Error ? error.message : String(error), + }, + ], + isError: true, + }); + } + return jsonRpcResult(message.id, { + content: [ + { + type: "text", + text: + typeof result === "string" + ? result + : JSON.stringify(result, null, 2), + }, + ], + }); + } + case "resources/list": + return jsonRpcResult(message.id, { + resources: [ + { + uri: "assistant-ui://navigation", + name: "assistant-ui docs navigation", + mimeType: "application/json", + }, + ...allPages().map(({ page }) => ({ + uri: `assistant-ui://${stripLeadingSlashes(page.url)}`, + name: page.data.title, + mimeType: "text/markdown", + })), + ], + }); + case "resources/read": { + const uri = getStringParam(message.params, "uri"); + if (uri === "assistant-ui://navigation") { + return jsonRpcResult(message.id, { + contents: [ + { + uri, + mimeType: "application/json", + text: JSON.stringify(getNavigation(), null, 2), + }, + ], + }); + } + if (!uri?.startsWith("assistant-ui://")) { + return jsonRpcError(message.id, -32602, "Unsupported resource URI"); + } + const path = uri.slice("assistant-ui://".length); + const page = await readPage(path, requestUrl); + return jsonRpcResult(message.id, { + contents: [ + { + uri, + mimeType: "text/markdown", + text: page.content, + }, + ], + }); + } + default: + return jsonRpcError( + message.id, + -32601, + `Method not found: ${message.method ?? "missing"}`, + ); + } + } catch (error) { + return jsonRpcError( + message.id, + -32000, + error instanceof Error ? error.message : String(error), + ); + } +} + +function jsonResponse(body: unknown, init?: ResponseInit) { + return NextResponse.json(body, { + ...init, + headers: { + "Cache-Control": "no-store", + ...init?.headers, + }, + }); +} + +export async function GET() { + return jsonResponse({ + name: "assistant-ui-docs", + protocol: "mcp", + endpoints: ["/mcp", "/.well-known/mcp", "/docs/mcp"], + tools: toolDefinitions.map((tool) => ({ + name: tool.name, + description: tool.description, + })), + }); +} + +export async function POST(request: NextRequest) { + let payload: unknown; + try { + payload = await request.json(); + } catch { + return jsonResponse(jsonRpcError(null, -32700, "Parse error"), { + status: 400, + }); + } + + if (Array.isArray(payload)) { + if (payload.length === 0) { + return jsonResponse(jsonRpcError(null, -32600, "Invalid Request"), { + status: 400, + }); + } + + const results = ( + await Promise.all( + payload.map((message) => + handleJsonRpcMessage(message as JsonRpcRequest, request.url), + ), + ) + ).filter((result) => result !== null); + + return results.length > 0 + ? jsonResponse(results) + : new NextResponse(null, { status: 202 }); + } + + const result = await handleJsonRpcMessage( + payload as JsonRpcRequest, + request.url, + ); + + return result + ? jsonResponse(result) + : new NextResponse(null, { status: 202 }); +} + +export function OPTIONS() { + return new NextResponse(null, { + status: 204, + headers: { + Allow: "GET, POST, OPTIONS", + "Cache-Control": "no-store", + }, + }); +} diff --git a/apps/docs/app/api/npm/downloads/route.ts b/apps/docs/app/api/npm/downloads/route.ts new file mode 100644 index 0000000..e8291f4 --- /dev/null +++ b/apps/docs/app/api/npm/downloads/route.ts @@ -0,0 +1,22 @@ +import { getWeeklyDownloads } from "@/lib/npm"; + +export const revalidate = 21600; + +export async function GET() { + const downloads = await getWeeklyDownloads(); + if (downloads === null) { + return Response.json( + { error: "npm download data unavailable" }, + { status: 503 }, + ); + } + return Response.json( + { downloads }, + { + headers: { + "Cache-Control": + "public, max-age=3600, s-maxage=21600, stale-while-revalidate=86400", + }, + }, + ); +} diff --git a/apps/docs/app/api/og/route.tsx b/apps/docs/app/api/og/route.tsx new file mode 100644 index 0000000..847e258 --- /dev/null +++ b/apps/docs/app/api/og/route.tsx @@ -0,0 +1,286 @@ +import { ImageResponse } from "next/og"; +import type { ImageResponseOptions, NextRequest } from "next/server"; +import { readFile } from "node:fs/promises"; +import { join } from "node:path"; + +export const runtime = "nodejs"; + +const size = { + width: 1200, + height: 630, +}; + +let fontsCache: { + geistSemiBold: Buffer; + geistRegular: Buffer; + geistMedium: Buffer; + geistMono: Buffer; +} | null = null; + +async function loadFonts() { + if (fontsCache) return fontsCache; + + const [geistSemiBold, geistRegular, geistMedium, geistMono] = + await Promise.all([ + readFile(join(process.cwd(), "assets/Geist-SemiBold.ttf")), + readFile(join(process.cwd(), "assets/Geist-Regular.ttf")), + readFile(join(process.cwd(), "assets/Geist-Medium.ttf")), + readFile(join(process.cwd(), "assets/GeistMono-Regular.ttf")), + ]); + + fontsCache = { geistSemiBold, geistRegular, geistMedium, geistMono }; + return fontsCache; +} + +export async function GET(request: NextRequest) { + const searchParams = request.nextUrl.searchParams; + const title = (searchParams.get("title") ?? "Documentation").slice(0, 100); + const description = searchParams.get("description")?.slice(0, 93) ?? null; + const variant = searchParams.get("variant"); + + if (variant && !["home", "page"].includes(variant)) { + return new Response("Invalid variant", { status: 400 }); + } + + let fonts: Awaited<ReturnType<typeof loadFonts>> | null = null; + try { + fonts = await loadFonts(); + } catch (error) { + // Don't fail the OG endpoint if fonts are missing (e.g. serverless file tracing). + // We'll fall back to system fonts so previews still work. + console.error("Failed to load fonts for OG image:", error); + } + + const fontSans = fonts ? "Geist" : "sans-serif"; + const fontMono = fonts ? "GeistMono" : "monospace"; + + const homeContent = ( + <div + style={{ + height: "100%", + width: "100%", + display: "flex", + flexDirection: "column", + alignItems: "center", + justifyContent: "center", + backgroundColor: "#0a0a0a", + padding: "80px", + gap: 40, + }} + > + <div + style={{ + display: "flex", + alignItems: "center", + gap: 32, + }} + > + <svg + aria-hidden="true" + width="120" + height="120" + viewBox="0 0 32 32" + fill="none" + style={{ marginBottom: -10 }} + > + <rect width="32" height="32" rx="6" fill="#000000" /> + <g + transform="translate(4,4)" + fill="black" + stroke="white" + strokeWidth="2.5" + strokeLinecap="round" + strokeLinejoin="round" + > + <path d="M14 9a2 2 0 0 1-2 2H6l-4 4V4c0-1.1.9-2 2-2h8a2 2 0 0 1 2 2z" /> + <path d="M18 9h2a2 2 0 0 1 2 2v11l-4-4h-6a2 2 0 0 1-2-2v-1" /> + </g> + </svg> + <span + style={{ + fontSize: 120, + fontWeight: 600, + color: "#ffffff", + fontFamily: fontSans, + letterSpacing: "-0.02em", + }} + > + assistant-ui + </span> + </div> + <div + style={{ + display: "flex", + flexDirection: "column", + alignItems: "center", + fontSize: 48, + fontWeight: 400, + gap: 10, + color: "#a3a3a3", + textAlign: "center", + fontFamily: fontSans, + letterSpacing: "-0.01em", + }} + > + <span>An open-source React toolkit for</span> + <span>production AI chat experiences</span> + </div> + </div> + ); + + const pageContent = ( + <div + style={{ + height: "100%", + width: "100%", + display: "flex", + flexDirection: "column", + backgroundColor: "#0a0a0a", + padding: "70px 80px 140px 80px", + }} + > + <div + style={{ + display: "flex", + alignItems: "center", + justifyContent: "space-between", + }} + > + <div + style={{ + display: "flex", + alignItems: "center", + gap: 20, + }} + > + <svg + aria-hidden="true" + width="64" + height="64" + viewBox="0 0 32 32" + fill="none" + > + <rect width="32" height="32" rx="6" fill="#000000" /> + <g + transform="translate(4,4)" + fill="black" + stroke="white" + strokeWidth="2.5" + strokeLinecap="round" + strokeLinejoin="round" + > + <path d="M14 9a2 2 0 0 1-2 2H6l-4 4V4c0-1.1.9-2 2-2h8a2 2 0 0 1 2 2z" /> + <path d="M18 9h2a2 2 0 0 1 2 2v11l-4-4h-6a2 2 0 0 1-2-2v-1" /> + </g> + </svg> + <span + style={{ + fontSize: 44, + fontWeight: 500, + color: "#e5e5e5", + fontFamily: fontSans, + letterSpacing: "-0.01em", + }} + > + assistant-ui + </span> + </div> + <span + style={{ + fontSize: 32, + fontWeight: 400, + color: "#a3a3a3", + fontFamily: fontMono, + }} + > + assistant-ui.com + </span> + </div> + + <div + style={{ + display: "flex", + flexDirection: "column", + flex: 1, + justifyContent: "center", + gap: 24, + }} + > + <span + style={{ + fontSize: title.length > 50 ? 56 : title.length > 30 ? 68 : 80, + fontWeight: 600, + color: "#ffffff", + fontFamily: fontSans, + lineHeight: 1.1, + letterSpacing: "-0.02em", + }} + > + {title} + </span> + {description && ( + <span + style={{ + fontSize: 36, + fontWeight: 400, + color: "#a3a3a3", + fontFamily: fontSans, + lineHeight: 1.4, + letterSpacing: "-0.01em", + }} + > + {description.length > 90 + ? `${description.slice(0, 90)}...` + : description} + </span> + )} + </div> + </div> + ); + + try { + const imageOptions: ImageResponseOptions = { + ...size, + headers: { + "Cache-Control": "public, max-age=86400, s-maxage=31536000", + }, + }; + + if (fonts) { + imageOptions.fonts = [ + { + name: "Geist", + data: fonts.geistSemiBold, + style: "normal", + weight: 600, + }, + { + name: "Geist", + data: fonts.geistRegular, + style: "normal", + weight: 400, + }, + { + name: "Geist", + data: fonts.geistMedium, + style: "normal", + weight: 500, + }, + { + name: "GeistMono", + data: fonts.geistMono, + style: "normal", + weight: 400, + }, + ]; + } + + return new ImageResponse( + variant === "home" ? homeContent : pageContent, + imageOptions, + ); + } catch (error) { + console.error("Failed to generate OG image:", error); + return new Response("Failed to generate image", { status: 500 }); + } +} diff --git a/apps/docs/app/api/playground-chat/route.ts b/apps/docs/app/api/playground-chat/route.ts new file mode 100644 index 0000000..0ec3934 --- /dev/null +++ b/apps/docs/app/api/playground-chat/route.ts @@ -0,0 +1,196 @@ +import { checkRateLimit } from "@/lib/rate-limit"; +import { validateGeneralChatInput } from "@/lib/validate-input"; +import { getModel } from "@/lib/ai/provider"; +import { isAiPlaygroundEnabled } from "@/lib/feature-flags"; +import { frontendTools } from "@assistant-ui/react-ai-sdk"; +import { NextResponse } from "next/server"; +import { + convertToModelMessages, + pruneMessages, + stepCountIs, + streamText, +} from "ai"; + +export const maxDuration = 30; + +const SYSTEM_PROMPT = `You are a UI customization assistant for the assistant-ui playground. Users describe how they want their chat UI to look, and you apply changes by calling the update_config tool. + +## BuilderConfig Schema + +The config has two top-level sections: "components" and "styles". + +### components +- attachments (boolean): Enable file attachments +- branchPicker (boolean): Enable message branch navigation +- editMessage (boolean): Allow editing sent messages +- actionBar.copy (boolean): Show copy button on messages +- actionBar.reload (boolean): Show reload/retry button +- actionBar.speak (boolean): Show text-to-speech button +- actionBar.feedback (boolean): Show thumbs up/down feedback +- threadWelcome (boolean): Show welcome screen +- suggestions (boolean): Show suggestion chips +- scrollToBottom (boolean): Show scroll-to-bottom button +- markdown (boolean): Enable markdown rendering +- codeHighlightTheme ("none" | "github" | "vitesse" | "tokyo-night" | "one-dark-pro" | "dracula"): Code syntax highlighting theme +- reasoning (boolean): Show AI reasoning/thinking +- sources (boolean): Show source citations +- followUpSuggestions (boolean): Show follow-up suggestions after responses +- avatar (boolean): Show user/assistant avatars +- typingIndicator ("none" | "dot"): Typing indicator style +- loadingIndicator ("none" | "spinner" | "text"): Loading indicator style +- loadingText (string): Text shown during loading (e.g. "Thinking...") + +### styles +- theme ("light" | "dark" | "system"): Color theme +- colors.accent ({light: string, dark: string}): Primary accent color (hex) +- colors.background ({light: string, dark: string}): Background color +- colors.foreground ({light: string, dark: string}): Text color +- colors.muted ({light: string, dark: string}): Muted background +- colors.mutedForeground ({light: string, dark: string}): Muted text +- colors.border ({light: string, dark: string}): Border color +- colors.userMessage ({light: string, dark: string}): User message bubble color +- colors.assistantMessage ({light: string, dark: string}): Assistant message bubble color +- colors.composer ({light: string, dark: string}): Composer/input area color +- colors.userAvatar ({light: string, dark: string}): User avatar color +- colors.assistantAvatar ({light: string, dark: string}): Assistant avatar color +- colors.suggestion ({light: string, dark: string}): Suggestion chip color +- colors.suggestionBorder ({light: string, dark: string}): Suggestion chip border +- borderRadius ("none" | "sm" | "md" | "lg" | "full"): Corner rounding +- maxWidth (string): Max content width (e.g. "44rem", "56rem", "100%") +- fontFamily (string): Font family (e.g. "system-ui", "Inter, sans-serif", "Georgia, serif", "ui-monospace, monospace") +- fontSize ("13px" | "14px" | "15px" | "16px"): Base font size +- messageSpacing ("compact" | "comfortable" | "spacious"): Space between messages +- userMessagePosition ("right" | "left"): User message alignment +- animations (boolean): Enable animations + +## Available Presets + +When users ask for a specific look, use the matching preset's exact config values: + +- "Default": Clean, modern design with all features enabled +- "ChatGPT": Dark theme inspired by ChatGPT's interface (dark theme, green accent #10a37f, full border radius, feedback enabled, avatars) +- "Claude": Warm, elegant design inspired by Claude's interface (light theme, warm brown accent #c96442, serif font, spacious, reasoning enabled) +- "Perplexity": Search-focused design with prominent answers (dark theme, cyan accent #20b8cd, left-aligned user messages, follow-up suggestions) +- "Minimal": Stripped-down interface with only essential features (light, no avatars, no suggestions, compact spacing, no animations) +- "Gemini": Google's Gemini-inspired clean interface (light, blue accent #1a73e8, full border radius, suggestions + follow-ups) +- "Copilot": GitHub Copilot inspired developer chat (dark, purple accent #8b5cf6, monospace font, compact, one-dark-pro code theme) +- "Slack": Team chat inspired collaborative interface (light, green accent #007a5a, left-aligned, compact, small border radius, no animations) +- "Grok": xAI's Grok-inspired minimal dark interface (dark, black/white accent, full border radius, dracula code theme) + +### customCSS (string, optional) +Arbitrary CSS injected into the preview. Use this for fine-grained layout/styling changes not covered by the schema above. Target these CSS classes: + +Thread & Viewport: +- .aui-thread-root — the thread container +- .aui-thread-viewport — scrollable message area + +Welcome: +- .aui-thread-welcome-root — welcome section wrapper +- .aui-thread-welcome-center — centering container +- .aui-thread-welcome-message — title/subtitle wrapper +- .aui-thread-welcome-message-inner — individual title/subtitle text +- .aui-thread-welcome-suggestions — suggestions grid container +- .aui-thread-welcome-suggestion — individual suggestion button +- .aui-thread-welcome-suggestion-text-1 — suggestion title text +- .aui-thread-welcome-suggestion-text-2 — suggestion label text + +Messages: +- .aui-user-message-root — user message container +- .aui-user-message-content — user message text bubble +- .aui-assistant-message-root — assistant message container +- .aui-assistant-message-content — assistant message text area +- .aui-assistant-message-footer — area below assistant message (action bar, branch picker) + +Composer: +- .aui-composer-root — composer wrapper +- .aui-composer-attachment-dropzone — input area with border +- .aui-composer-input — text input element +- .aui-composer-send — send button +- .aui-composer-cancel — cancel/stop button + +Action bars: +- .aui-user-action-bar-root — user message actions +- .aui-assistant-action-bar-root — assistant message actions + +Other: +- .aui-thread-scroll-to-bottom — scroll to bottom button +- .aui-branch-picker-root — branch navigation +- .aui-md — markdown content wrapper + +CSS variables available: --aui-thread-max-width, --aui-accent-color, --aui-background, --aui-foreground, --aui-muted, --aui-muted-foreground, --aui-border, --aui-user-message-background, --aui-assistant-message-background, --aui-composer-background, --aui-user-avatar-background, --aui-assistant-avatar-background, --aui-suggestion-background, --aui-suggestion-border + +Custom CSS is automatically scoped to the .aui-root container via @scope — only target .aui-* classes, not body, html, or other page elements. + +Example: To center suggestion chips, use: ".aui-thread-welcome-suggestions { justify-items: center; }" + +When using customCSS, APPEND to any existing customCSS (from current config) rather than replacing it, unless the user asks to reset styles. To clear all custom CSS, send customCSS: "". + +## Rules + +1. Only send the fields that need to change via update_config — do NOT send the entire config +2. For color changes, always provide both light and dark variants +3. Keep your text responses brief — focus on making changes, not explaining them +4. When a user asks for a preset by name (e.g. "make it look like ChatGPT"), apply the full preset config +5. You can make multiple update_config calls for complex changes +6. For layout tweaks, positioning, spacing, sizing, or visual changes not covered by the named config fields, use the customCSS field with CSS targeting the .aui-* classes listed above. This is powerful — use it for things like centering elements, changing padding, hiding elements, etc. +7. CRITICAL: If you truly cannot fulfill a request (e.g. changing actual text content like titles or placeholder text), do NOT call update_config. Respond with only a text message explaining what you can't do. Never call the tool with no-op changes. +`; + +export async function POST(req: Request) { + if (!isAiPlaygroundEnabled) { + return NextResponse.json({ error: "Not found." }, { status: 404 }); + } + + try { + const rateLimitResponse = await checkRateLimit(req); + if (rateLimitResponse) return rateLimitResponse; + + const body = await req.json(); + const { messages, tools, builderConfig } = body; + + const inputError = validateGeneralChatInput(messages); + if (inputError) return inputError; + + // Guard against oversized configs (token inflation / DoS) + const configStr = JSON.stringify(builderConfig ?? {}); + if (configStr.length > 10_000) { + return new Response("Config too large", { status: 400 }); + } + + const model = getModel("openai/gpt-5.4-nano"); + + const prunedMessages = pruneMessages({ + messages: await convertToModelMessages(messages), + reasoning: "none", + }); + + const result = streamText({ + model, + system: + SYSTEM_PROMPT + + `\n\n## Current Config State\n\n\`\`\`json\n${JSON.stringify(builderConfig, null, 2)}\n\`\`\``, + messages: prunedMessages, + maxOutputTokens: 4000, + stopWhen: stepCountIs(3), + tools: frontendTools(tools), + onError: async ({ error }) => { + console.error("[api/playground-chat]", error); + }, + }); + + return result.toUIMessageStreamResponse({ + messageMetadata: ({ part }) => { + if (part.type === "finish-step") { + return { modelId: part.response.modelId }; + } + if (part.type === "finish") { + return { usage: part.totalUsage }; + } + return undefined; + }, + }); + } catch (e) { + console.error("[api/playground-chat]", e); + return new Response("Request failed", { status: 500 }); + } +} diff --git a/apps/docs/app/api/search/route.ts b/apps/docs/app/api/search/route.ts new file mode 100644 index 0000000..029c9af --- /dev/null +++ b/apps/docs/app/api/search/route.ts @@ -0,0 +1,12 @@ +import { source } from "@/lib/source"; +import { createSearchAPI } from "fumadocs-core/search/server"; + +export const { GET } = createSearchAPI("advanced", { + indexes: source.getPages().map((page) => ({ + title: page.data.title, + description: page.data.description ?? "", + structuredData: page.data.structuredData, + id: page.url, + url: page.url, + })), +}); diff --git a/apps/docs/app/api/xulux/blaxel-sandbox.ts b/apps/docs/app/api/xulux/blaxel-sandbox.ts new file mode 100644 index 0000000..e2fe58f --- /dev/null +++ b/apps/docs/app/api/xulux/blaxel-sandbox.ts @@ -0,0 +1,276 @@ +/** + * Minimal @blaxel/core wrapper for the xulux coding agent. + * Provisions a cloud sandbox and returns a single exec() function — + * all file reads, writes, and shell ops go through shell commands. + * + * Required env vars: + * BL_WORKSPACE=<your-blaxel-workspace> + * BL_API_KEY=<your-api-key> + * + * Optional: + * BL_SANDBOX_TEMPLATE=<image-name> (defaults to blaxel/node-sandbox) + * BL_REGION=<region> (defaults to us-pdx-1) + */ + +import { createReadStream } from "node:fs"; +import { mkdtemp, rm } from "node:fs/promises"; +import { tmpdir } from "node:os"; +import { join } from "node:path"; + +const MAX_RETRIES = 3; + +const DEFAULT_TAR_EXCLUDES = [ + "node_modules", + "*/node_modules", + ".next", + "*/.next", + "dist", + "*/dist", + "build", + "*/build", + "out", + "*/out", + ".turbo", + "*/.turbo", + ".cache", + "*/.cache", + ".git", + "*/.git", + ".env", + "*/.env", + ".env.local", + "*/.env.local", + ".env.*", + "*/.env.*", + ".env.*.local", + "*/.env.*.local", + ".codingagent", + "*/.codingagent", + ".agent", + "*/.agent", + ".pnpm-store", + "*/.pnpm-store", + ".vercel", + "*/.vercel", +]; + +function shellQuote(value: string): string { + return `'${value.replace(/'/g, `'\\''`)}'`; +} + +function buildTarExcludeArgs(): string { + return DEFAULT_TAR_EXCLUDES.map( + (pattern) => `--exclude=${shellQuote(pattern)}`, + ).join(" "); +} + +function safeExportFilename(sessionId: string): string { + const session = + sessionId + .toLowerCase() + .replace(/[^a-z0-9._-]+/g, "-") + .slice(0, 12) || "session"; + return `xulux-workspace-${session}.tar.gz`; +} + +function toSandboxName(sessionId: string): string { + const safe = sessionId + .toLowerCase() + .replace(/[^a-z0-9-]+/g, "-") + .replace(/^-+|(?<!-)-+$/g, "") + .slice(0, 32); + return `xulux-${safe}`.slice(0, 40).replace(/(?<!-)-+$/g, ""); +} + +async function withRetry<T>(fn: () => Promise<T>): Promise<T> { + let lastError: unknown; + for (let attempt = 0; attempt < MAX_RETRIES; attempt++) { + try { + return await fn(); + } catch (err: any) { + lastError = err; + const msg = String(err?.message ?? err); + const isTransient = + msg.includes("fetch failed") || + msg.includes("ECONNRESET") || + msg.includes("socket hang up"); + if (isTransient && attempt < MAX_RETRIES - 1) { + await new Promise((r) => setTimeout(r, 1000 * (attempt + 1))); + continue; + } + throw err; + } + } + throw lastError; +} + +async function waitUntilReachable(sb: any, maxWaitMs = 120_000): Promise<void> { + const deadline = Date.now() + maxWaitMs; + let lastError: unknown; + while (Date.now() < deadline) { + try { + await sb.process.exec({ + command: "true", + waitForCompletion: true, + workingDir: "/", + }); + return; + } catch (err) { + lastError = err; + await new Promise((r) => setTimeout(r, 1000)); + } + } + const msg = + lastError instanceof Error ? lastError.message : String(lastError); + throw new Error(`Sandbox not reachable after ${maxWaitMs}ms: ${msg}`); +} + +export type SandboxExecResult = { + stdout: string; + stderr: string; + exitCode: number; +}; + +export type SandboxExec = ( + command: string, + cwd?: string, +) => Promise<SandboxExecResult>; + +export type ProvisionedSandbox = { + exec: SandboxExec; + previewUrl: string; +}; + +export type WorkspaceArchive = { + filename: string; + contentType: string; + stream: ReturnType<typeof createReadStream>; + cleanup: () => Promise<void>; +}; + +export async function provisionSandbox( + sessionId: string, +): Promise<ProvisionedSandbox> { + let SandboxInstance: any; + try { + const mod = await import("@blaxel/core"); + SandboxInstance = mod.SandboxInstance; + } catch { + throw new Error( + "Missing @blaxel/core dependency. Set BL_WORKSPACE and BL_API_KEY in your .env file.", + ); + } + + const sandboxName = toSandboxName(sessionId); + + const sb = await SandboxInstance.createIfNotExists({ + name: sandboxName, + image: process.env.BL_SANDBOX_TEMPLATE ?? "blaxel/node-sandbox", + region: process.env.BL_REGION ?? "us-pdx-1", + memory: 4096, + ports: [{ name: "preview", target: 3000, protocol: "HTTP" }], + }); + + await waitUntilReachable(sb); + await withRetry(() => + sb.process.exec({ + command: "mkdir -p /workspace", + waitForCompletion: true, + workingDir: "/", + }), + ); + + const preview = await sb.previews.createIfNotExists({ + metadata: { name: "preview" }, + spec: { port: 3000, public: true }, + }); + const previewUrl = preview.spec?.url as string; + + const exec: SandboxExec = async (command: string, cwd = "/workspace") => { + const result: any = await withRetry(() => + sb.process.exec({ command, waitForCompletion: true, workingDir: cwd }), + ); + return { + stdout: String(result?.stdout ?? ""), + stderr: String(result?.stderr ?? ""), + exitCode: result?.exitCode ?? 0, + }; + }; + + return { exec, previewUrl }; +} + +export async function fetchPreviewUrl(sessionId: string): Promise<string> { + const { SandboxInstance } = await import("@blaxel/core"); + const sb = await SandboxInstance.get(toSandboxName(sessionId)); + const preview = await sb.previews.createIfNotExists({ + metadata: { name: "preview" }, + spec: { port: 3000, public: true }, + }); + return preview.spec?.url as string; +} + +export async function exportWorkspaceArchive( + sessionId: string, +): Promise<WorkspaceArchive> { + const { SandboxInstance } = await import("@blaxel/core"); + const sb = await SandboxInstance.get(toSandboxName(sessionId)); + if (!sb?.process?.exec || !sb?.fs?.download) { + throw new Error("Sandbox export APIs are not available."); + } + + const tempDir = await mkdtemp(join(tmpdir(), "xulux-export-")); + const filename = safeExportFilename(sessionId); + const localArchivePath = join(tempDir, filename); + const remoteArchivePath = `/tmp/xulux-export-${sessionId.replace( + /[^a-zA-Z0-9._-]/g, + "-", + )}-${Date.now()}.tar.gz`; + const excludeArgs = buildTarExcludeArgs(); + const command = [ + "set -eu", + 'gitignore_args=""', + 'if [ -f ".gitignore" ]; then gitignore_args="--exclude-from=.gitignore"; fi', + `tar ${excludeArgs} $gitignore_args -czf ${shellQuote(remoteArchivePath)} -C /workspace .`, + ].join("\n"); + + const cleanup = async () => { + await sb.process + .exec({ + command: `rm -f ${shellQuote(remoteArchivePath)}`, + waitForCompletion: true, + workingDir: "/", + }) + .catch(() => {}); + await rm(tempDir, { recursive: true, force: true }); + }; + + try { + const result: any = await withRetry(() => + sb.process.exec({ + command, + waitForCompletion: true, + workingDir: "/workspace", + timeout: 60_000, + }), + ); + const exitCode = result?.exitCode ?? result?.exit_code ?? 0; + if (exitCode !== 0) { + throw new Error( + `Failed to create workspace archive: ${result?.stderr ?? result?.stdout ?? `exit ${exitCode}`}`, + ); + } + + await withRetry(() => sb.fs.download(remoteArchivePath, localArchivePath)); + + return { + filename, + contentType: "application/gzip", + stream: createReadStream(localArchivePath), + cleanup, + }; + } catch (error) { + await cleanup().catch(() => {}); + throw error; + } +} diff --git a/apps/docs/app/api/xulux/chat/route.ts b/apps/docs/app/api/xulux/chat/route.ts new file mode 100644 index 0000000..7913ab4 --- /dev/null +++ b/apps/docs/app/api/xulux/chat/route.ts @@ -0,0 +1,592 @@ +import { getDistinctId, posthogServer } from "@/lib/posthog-server"; +import { createPrismTracer, prismAISDK } from "@/lib/prism-server"; +import { injectQuoteContext } from "@assistant-ui/react-ai-sdk"; +import { checkRateLimit } from "@/lib/rate-limit"; +import { validateDocChatInput } from "@/lib/validate-input"; +import { getModel, openai, withTracing } from "@/lib/ai/provider"; +import { isAiPlaygroundEnabled } from "@/lib/feature-flags"; +import { NextResponse } from "next/server"; +import { + convertToModelMessages, + pruneMessages, + stepCountIs, + streamText, +} from "ai"; +import type { UIMessage } from "ai"; +import { beginTurn, finishTurn } from "@/lib/xulux/usage-budget"; +import { + createXuluxDiagnosticMessageResponse, + createXuluxTurnOutcome, + getLatestUserMessageId, +} from "@/lib/xulux/turn-outcome"; +import { createXuluxChatTools } from "./tools"; + +type XuluxReasoningEffort = + | "none" + | "minimal" + | "low" + | "medium" + | "high" + | "xhigh"; + +type XuluxRequestConfig = { + modelName?: unknown; + reasoningEffort?: unknown; +}; + +function isReasoningEffort(value: unknown): value is XuluxReasoningEffort { + return ( + value === "none" || + value === "minimal" || + value === "low" || + value === "medium" || + value === "high" || + value === "xhigh" + ); +} + +function resolveXuluxModel(config: unknown) { + const requestConfig = + config && typeof config === "object" && !Array.isArray(config) + ? (config as XuluxRequestConfig) + : undefined; + const modelName = + typeof requestConfig?.modelName === "string" + ? requestConfig.modelName.trim() + : ""; + const reasoningEffort = isReasoningEffort(requestConfig?.reasoningEffort) + ? requestConfig.reasoningEffort + : undefined; + + if (modelName === "gpt-5.4" && reasoningEffort) { + return { + model: openai.responses("gpt-5.4"), + providerOptions: { openai: { reasoningEffort } }, + }; + } + + return { + model: modelName ? getModel(modelName) : getModel("gpt-5.4-mini"), + providerOptions: undefined, + }; +} + +export const maxDuration = 800; + +const PRUNE_OPTIONS = { + toolCalls: "before-last-2-messages", + reasoning: "none", + emptyMessages: "remove", +} as const; + +type SelectedTemplateRequestContext = { + id?: unknown; + title?: unknown; + description?: unknown; + kind?: unknown; + prompt?: unknown; + sourcePath?: unknown; + downloadUrl?: unknown; +}; + +type ActivePreviewRequestContext = { + source: "template_modal" | "agent_tool"; + templateId: string; + versionId?: string | null; + customized: boolean; + config?: JsonObject; +}; + +type JsonValue = + | string + | number + | boolean + | null + | JsonValue[] + | { [key: string]: JsonValue }; +type JsonObject = { [key: string]: JsonValue }; + +const MAX_ACTIVE_PREVIEW_CONFIG_CHARS = 8_000; +const MAX_RAW_MESSAGES_CHARS = 1_000_000; + +async function prepareMessages(messages: readonly UIMessage[]) { + const modelMessages = await convertToModelMessages( + injectQuoteContext([...messages]), + ); + return pruneMessages({ messages: modelMessages, ...PRUNE_OPTIONS }); +} + +function isRecord(value: unknown): value is Record<string, unknown> { + return !!value && typeof value === "object" && !Array.isArray(value); +} + +function normalizeJsonObject(value: unknown): JsonObject | undefined { + if (!isRecord(value)) return undefined; + try { + const json = JSON.stringify(value); + if (json.length > MAX_ACTIVE_PREVIEW_CONFIG_CHARS) return undefined; + return JSON.parse(json) as JsonObject; + } catch { + return undefined; + } +} + +function normalizeActivePreviewContext( + value: unknown, +): ActivePreviewRequestContext | null { + if (!isRecord(value)) return null; + const source = value.source; + const templateId = value.templateId; + const versionId = value.versionId; + const customized = value.customized; + if ( + (source !== "template_modal" && source !== "agent_tool") || + typeof templateId !== "string" || + typeof customized !== "boolean" + ) { + return null; + } + const config = normalizeJsonObject(value.config); + return { + source, + templateId, + ...(typeof versionId === "string" || versionId === null + ? { versionId } + : {}), + customized, + ...(config ? { config } : {}), + }; +} + +function appendHiddenText(message: { content: unknown }, text: string) { + if (typeof message.content === "string") { + message.content = `${message.content}\n\n${text}`; + } else if (Array.isArray(message.content)) { + message.content = [...message.content, { type: "text", text }]; + } +} + +function formatActivePreviewContext(context: ActivePreviewRequestContext) { + const json = JSON.stringify(context).replaceAll("<", "\\u003c"); + return [ + "<xulux_active_preview_context>", + "Treat this as the currently open preview state.", + json, + "</xulux_active_preview_context>", + ].join("\n"); +} + +function formatSelectedTemplateContext( + selectedTemplate: unknown, +): string | null { + if (!selectedTemplate || typeof selectedTemplate !== "object") return null; + const template = selectedTemplate as SelectedTemplateRequestContext; + const id = typeof template.id === "string" ? template.id : null; + const title = typeof template.title === "string" ? template.title : null; + if (!id || !title) return null; + + const lines = [ + "<selected_template_context>", + `The user selected this ${template.kind === "example" ? "example" : "template"} before sending their message.`, + `id: ${id}`, + `title: ${title}`, + ]; + + if (typeof template.description === "string") { + lines.push(`description: ${template.description}`); + } + if (typeof template.prompt === "string") { + lines.push(`catalog_prompt: ${template.prompt}`); + } + if (typeof template.sourcePath === "string") { + lines.push(`sourcePath: ${template.sourcePath}`); + } + if (typeof template.downloadUrl === "string") { + lines.push(`downloadUrl: ${template.downloadUrl}`); + } + + lines.push( + "Use this as hidden context for the user's next build request. Do not mention this tag unless it is directly useful.", + "</selected_template_context>", + ); + return lines.join("\n"); +} + +const SYSTEM_PROMPT = `You are a coding assistant that helps users get started with assistant-ui using our starter templates. + +<about_assistant_ui> +assistant-ui is a React library for building AI chat interfaces. It provides: +- Composable UI primitives (Thread, Composer, Message, etc.) +- Runtime adapters for AI backends (Vercel AI SDK, LangGraph, custom stores) +- Pre-built components with full customization support +</about_assistant_ui> + +<personality> +- Friendly, concise, developer-focused +- Create actionable MVP projects for users based on their requirements, instead of just answering questions. +- Do not end build-intent requests with "I can build this next"; just build it and share the working app URL. +- Use emoji sparingly (👋 for greetings, ✅ for success, etc.) +</personality> + +<greetings> +When users send a casual greeting (hey, hi, hello): +1. Welcome them to assistant-ui with emoji 👋 +2. Briefly explain what assistant-ui helps them do (build AI chat interfaces in React) +3. Ask what they're working on or offer 2-3 common starter projects using an \`ask-question\` block. + +Example tone: +"Hey! 👋 Welcome to assistant-ui! + +I'm here to help you build AI chat interfaces with React. Whether you're just getting started, connecting to an AI backend, or customizing components — I've got you covered. + +What are you working on? +\`\`\` +\`\`\`ask-question +{"question":"Which direction should I take?","options":[{"label":"Build a new app","prompt":"Build a new app using assistant-ui.","preferred":true},{"label":"Read docs first","prompt":"Read the relevant assistant-ui docs first, then suggest the implementation path."}]} +\`\`\` +\`\`\` +" + +Do NOT dump all documentation categories. Keep it conversational. +</greetings> + +<tools> +You have tools to explore docs, read the monorepo source, and open hosted app previews. + +1. **listDocs** - Browse docs structure + - Call with no path FIRST to discover available top-level sections + - Then call again with a subpath from the returned list to drill in + - Returns: list of folders and pages with URLs +2. **readDoc** - Read a specific documentation page + - Input: slug (e.g., "ui/thread") or URL (e.g., "/docs/ui/thread") + - Returns: full page content +3. **inspectSourceMap** / **readSourceMapFile** - Explore the assistant-ui monorepo source code + - Use for: grep, find, cat, ls, tree on repo files + - Example: \`grep -r "useThread" packages/ --include="*.ts" -l\` +4. **getTemplateList** - Get all available hosted app templates and their versions + - Call this first for any app-building request + - Returns: lightweight list of template ids, titles, and version ids +5. **getTemplateDetails** - Get full details for a specific template + - Input: templateId from getTemplateList + - Returns: intent metadata, versions, contract roots, source files, example config +6. **openTemplatePreview** - Open a hosted template preview in the canvas + - Input: templateId, optional versionId, optional config object + - If config is provided, creates a preview session on the template sandbox + - Returns: previewUrl, downloadUrl, title +</tools> + +<recommended_pattern> +Case 1: User wants to build an app: +1. Call **getTemplateList** to see what hosted templates are available. +2. Call **getTemplateDetails** on any templates that look like a match. +3. Based on users request you can take following three paths: + - If the template matches the user's request, call **openTemplatePreview** with the selected templateId and versionId. + - If you feel the users request needs some customization which the template supports, review the <template_customization_guide> and then call the **openTemplatePreview** with the config object. + - If you dont find the right template and even configs dont support the user's request, follow **Case 1B** below. Do NOT call openTemplatePreview. Do NOT pretend you set up a hosted starter. + +4. **Case 1A — openTemplatePreview succeeded:** include a fenced code block with language \`open-in\` at the end of your response (this renders a card with download + coding agent buttons — do NOT separately write a download markdown link): +\`\`\` +\`\`\`open-in +{"title":"<template title>","downloadUrl":"<exact downloadUrl from openTemplatePreview result>","prompt":"<your build/customization instructions for the external coding agent — be specific about which files to edit and what to change>"} +\`\`\` +\`\`\` + - \`downloadUrl\` MUST be copied exactly from the openTemplatePreview tool result. Never use placeholders. + - This renders an interactive card with buttons to open the template in Claude Code, Codex, Cursor, Conductor, or ChatGPT. Don't share preview or download url separately. + +5. **Case 1B — no suitable hosted template:** +- You MUST call **listDocs** and **readDoc** (and **inspectSourceMap** / **readSourceMapFile** when helpful) before answering. Do not skip documentation. +- Tell the user honestly that no hosted template fits their request and you are not opening a preview. +- Do NOT call **openTemplatePreview**. Do NOT claim you "set up a starter" or adapted a template unless the tool actually succeeded. +- Do NOT include \`downloadUrl\` in an open-in block unless openTemplatePreview returned a real https URL. +- Write a concrete build guide grounded in docs you read (CLI, architecture, components, runtime). +- Optionally end with a prompt-only \`open-in\` block (no downloadUrl) so the user can open the guide in their coding agent: +\`\`\` +\`\`\`open-in +{"title":"<short app name>","prompt":"<full step-by-step build guide from the docs you read — no fake download link>"} +\`\`\` +\`\`\` +- Also include the same prompt as a fenced code block with language \`text\` in your response. + +Case 2: User ask questions about assistant-ui: +- Use listDocs → readDoc to find relevant information. +- Use inspectSourceMap / readSourceMapFile to explore source code. +- You can also use open-in code block to share a prompt to help user get started with assistant-ui, try sharing the code block if you think it is relevant. +</recommended_pattern> + +<template_customization_guide> +- A hosted template is a packaged starter made of multiple parts: the visible app UI, the assistant experience, the tool setup, and the mock/demo flows. Understand the whole template before deciding it matches the user’s request. +- Customization is meant for supported adaptation within that template’s shape, not for turning one kind of app into a completely different kind of product. You can change the UI to show case the app like a dashboard and CRM can be handled by one tempalte, but an app to make movies won't work. +- When customizing, review both the visible UI and the assistant behavior together. A good match requires the screen, assistant identity, prompts, tool descriptions, and mock/demo responses to all reflect the same user request. +- Use 'getTemplateDetails' and especially 'exampleConfig' to understand what the template actually represents in practice: what the UI looks like, how the assistant behaves, what the tools do, and what the demo/mock flows are modeling. +- After reading that full template shape, decide whether the user’s request can be represented within it with supported customization. If not, do not force the template. +- When a user message contains <xulux_active_preview_context>, treat it as the current open preview state. Use it to understand follow-up requests, and call template tools if you need schema or template details before customizing or opening a template. +</template_customization_guide> + +<common_pitfalls_to_avoid> +- You some times try to force a user's requirement on to a template, you can create mock pages to kinda look like users requirement , but that is just slop. Instead read docs, source map and share a starter prompt for them to build that app. +- You creating a prompt to guide the user to build that app, you do not read the docs or the sourcemap to be accurate. Instead read the docs and the sourcemap to be accurate and create a prompt for them to build that app. +- You skip the architecture, installation, and CLI docs and manually scaffold with Next/React create commands, writing low-level code. Instead, read the docs and use the assistant-ui CLI and other available utilities to scaffold with prebuilt components. +- You assume wrong CLI flags; use the help command to understand how to use the CLI. +- You confuse assistant-ui components at \`@/components/assistant-ui/*\` to be exported from \`@assistant-ui/react\`. They are shadcn-based components—read the Components doc/subdocs for details on available components and installation (use assistant-ui CLI or shadcn). If customization is needed, customize the generated components. +- You some time guess for fabricate urls, always use the urls from the tool results. +- You sometimes ask plain-text clarifying questions when the user needs to choose between concrete next actions. Instead, render an \`ask-question\` block. +</common_pitfalls_to_avoid> + +<answering> +- Use the documentation tools to find relevant information +- **CRITICAL: ONLY use URLs that are explicitly returned by your tools** +- **NEVER guess or fabricate URLs** - if a tool didn't return a URL, don't link to it +- **NEVER put placeholder URLs in open-in JSON** (e.g. \`<downloadUrl-from-tool-result>\`). Omit \`downloadUrl\` when there is no real download. +- When linking, copy the exact URL from tool results: [Page Title](/docs/exact-path-from-tool) +- Prefer not linking over linking to a potentially non-existent page +- Admit uncertainty rather than guessing +- If you cannot proceed because the user needs to choose between a few concrete next actions, ask the question and include a fenced code block with language \`ask-question\`. This renders clickable auto-send options: +\`\`\` +\`\`\`ask-question +{"question":"Which direction should I take?","options":[{"label":"Customize current preview","prompt":"Customize the current preview for this request.","preferred":true},{"label":"Read docs first","prompt":"Read the relevant assistant-ui docs first, then suggest the implementation path."}]} +\`\`\` +\`\`\` + - Only use \`question\` and \`options\` at the top level. + - Each option MUST have \`label\` and \`prompt\`, and may include \`preferred: true\`. + - Set \`preferred: true\` on exactly one option when there is a recommended path. + - Put the preferred option first in the JSON options array. + - \`label\` should be short button text. + - \`prompt\` should be the full user message to auto-send when clicked. + - Do not use suggestions when you can confidently proceed with tools or a direct answer. +</answering> + +<formatting> +Use inline code (\`backticks\`) for: +- Components: \`Thread\`, \`Composer\`, \`Message\` +- Hooks: \`useChat\`, \`useThreadRuntime\` +- Props, parameters, types +- Packages: \`@assistant-ui/react\` +- File paths +</formatting> +`; + +export async function POST(req: Request): Promise<Response> { + const requestId = crypto.randomUUID(); + if (!isAiPlaygroundEnabled) { + return NextResponse.json({ error: "Not found." }, { status: 404 }); + } + + try { + const rateLimitResponse = await checkRateLimit(req); + if (rateLimitResponse) return rateLimitResponse; + + const body = await req.json().catch(() => null); + if (!isRecord(body)) { + return NextResponse.json( + { error: "Invalid JSON request body." }, + { status: 400 }, + ); + } + + const { + messages, + tools: clientTools, + system: pageContext, + config, + sessionId, + selectedTemplate, + activePreviewContext, + } = body; + + if (!Array.isArray(messages)) { + return new Response("Invalid messages format", { status: 400 }); + } + if (JSON.stringify(messages).length > MAX_RAW_MESSAGES_CHARS) { + return new Response("Input too long", { status: 400 }); + } + + const uiMessages = messages as UIMessage[]; + const prunedMessages = await prepareMessages(uiMessages); + + const normalizedPreviewContext = + normalizeActivePreviewContext(activePreviewContext); + const userMessageId = getLatestUserMessageId(uiMessages); + + if (typeof sessionId !== "string" || sessionId.trim().length === 0) { + return NextResponse.json( + { error: "sessionId required" }, + { status: 400 }, + ); + } + + const isFirstUserTurn = + prunedMessages.filter((m) => m.role === "user").length === 1 && + !prunedMessages.some((m) => m.role === "assistant"); + if (isFirstUserTurn) { + const templateContext = formatSelectedTemplateContext(selectedTemplate); + const firstUser = prunedMessages.find((m) => m.role === "user"); + if (templateContext && firstUser) { + appendHiddenText(firstUser, templateContext); + } + } + if (normalizedPreviewContext) { + const latestUser = [...prunedMessages] + .reverse() + .find((m) => m.role === "user"); + if (latestUser) { + appendHiddenText( + latestUser, + formatActivePreviewContext(normalizedPreviewContext), + ); + } + } + + const inputError = validateDocChatInput(prunedMessages); + if (inputError) return inputError; + + const distinctId = getDistinctId(req); + const budget = await beginTurn(sessionId.trim(), distinctId); + if (budget.denied) { + const payload = await budget.denied + .clone() + .json() + .catch(() => null); + const userVisibleMessage = + typeof payload?.error === "string" + ? payload.error + : "This request could not run because a usage limit was reached."; + const code = typeof payload?.code === "string" ? payload.code : undefined; + return createXuluxDiagnosticMessageResponse({ + messages: uiMessages, + text: userVisibleMessage, + outcome: createXuluxTurnOutcome({ + type: "budget_denied", + requestId, + sessionId: sessionId.trim(), + distinctId, + statusCode: budget.denied.status, + userVisibleMessage, + ...(userMessageId ? { userMessageId } : {}), + ...(code ? { code } : {}), + }), + }); + } + const budgetDate = budget.budgetDate; + + const evalRunId = req.headers.get("x-agent-eval-run-id"); + const localTraceUrl = req.headers.get("x-agent-eval-trace-url"); + const modelConfig = resolveXuluxModel(config); + const baseModel = modelConfig.model; + const prismTracer = createPrismTracer({ evalRunId, localTraceUrl }); + const xuluxTools = createXuluxChatTools({ + clientTools: clientTools as Parameters< + typeof createXuluxChatTools + >[0]["clientTools"], + routeUrl: req.url, + }); + const toolManifest = + process.env.XULUX_EVAL_MODE === "1" + ? Object.entries(xuluxTools).map(([name, tool]) => { + const { description, inputSchema } = tool as { + description?: unknown; + inputSchema?: { jsonSchema?: unknown }; + }; + return { + name, + ...(typeof description === "string" ? { description } : {}), + ...(inputSchema?.jsonSchema + ? { inputSchema: inputSchema.jsonSchema } + : {}), + }; + }) + : undefined; + + const posthogModel = posthogServer + ? withTracing(baseModel, posthogServer, { + posthogDistinctId: distinctId, + posthogPrivacyMode: false, + posthogProperties: { + $ai_span_name: "xulux_chat", + source: "xulux_chat", + }, + }) + : baseModel; + + const prism = prismTracer + ? prismAISDK(prismTracer, posthogModel, { + name: "xulux_chat", + endUserId: distinctId, + metadata: { + evalRunId, + sessionId, + source: "xulux_chat", + ...(toolManifest ? { toolManifest } : undefined), + }, + }) + : null; + + const result = streamText({ + model: prism?.model ?? posthogModel, + ...(modelConfig.providerOptions + ? { providerOptions: modelConfig.providerOptions } + : undefined), + system: [SYSTEM_PROMPT, pageContext].filter(Boolean).join("\n\n"), + messages: prunedMessages, + maxOutputTokens: 8192, + stopWhen: stepCountIs(50), + tools: xuluxTools, + onFinish: async ({ usage, response }) => { + await finishTurn( + sessionId.trim(), + distinctId, + usage, + response.modelId, + budgetDate, + ); + await prism?.end(); + }, + onError: async ({ error }) => { + console.error(error); + await prism?.end({ status: "error" }); + }, + onAbort: async () => { + await prism?.end(); + }, + }); + + return result.toUIMessageStreamResponse({ + originalMessages: uiMessages, + messageMetadata: ({ part }) => { + if (part.type === "finish-step") { + return { modelId: part.response.modelId }; + } + if (part.type === "finish") { + return { + usage: part.totalUsage, + custom: { + usage: part.totalUsage, + xulux: { + outcome: createXuluxTurnOutcome({ + type: "assistant_response_completed", + requestId, + sessionId: sessionId.trim(), + distinctId, + ...(userMessageId ? { userMessageId } : {}), + }), + ...(normalizedPreviewContext + ? { + activePreviewContext: { + value: normalizedPreviewContext, + ...(userMessageId + ? { injectedIntoUserMessageId: userMessageId } + : {}), + }, + } + : {}), + }, + }, + }; + } + return undefined; + }, + }); + } catch (e) { + console.error("[api/xulux/chat]", e); + return new Response("Request failed", { status: 500 }); + } +} diff --git a/apps/docs/app/api/xulux/chat/tools.ts b/apps/docs/app/api/xulux/chat/tools.ts new file mode 100644 index 0000000..8fb88f4 --- /dev/null +++ b/apps/docs/app/api/xulux/chat/tools.ts @@ -0,0 +1,19 @@ +import { frontendTools, type FrontendTools } from "@assistant-ui/react-ai-sdk"; +import { createDocsTools } from "./tools/docs-tools"; +import { createSourceMapTools } from "./tools/source-map-tools"; +import { createTemplateTools } from "./tools/template-tools"; + +export function createXuluxChatTools({ + clientTools, + routeUrl, +}: { + clientTools: FrontendTools; + routeUrl: string; +}) { + return { + ...frontendTools(clientTools), + ...createSourceMapTools(), + ...createDocsTools({ routeUrl }), + ...createTemplateTools(), + }; +} diff --git a/apps/docs/app/api/xulux/chat/tools/docs-tools.ts b/apps/docs/app/api/xulux/chat/tools/docs-tools.ts new file mode 100644 index 0000000..8514142 --- /dev/null +++ b/apps/docs/app/api/xulux/chat/tools/docs-tools.ts @@ -0,0 +1,175 @@ +import { getLLMText } from "@/lib/get-llm-text"; +import { source, examples as examplesSource } from "@/lib/source"; +import { tool, zodSchema } from "ai"; +import type * as PageTree from "fumadocs-core/page-tree"; +import z from "zod"; + +const DOCS_PATH_ERROR = "Only local docs paths are supported"; + +function normalizeSegment(name: string): string { + return name.toLowerCase().replace(/\s+/g, "-"); +} + +function findFolderByPath( + tree: PageTree.Root, + path: string, +): PageTree.Folder | undefined { + const segments = path.split("/").filter(Boolean); + let currentFolder: PageTree.Folder | undefined; + let children: PageTree.Node[] = tree.children; + + for (const segment of segments) { + const folder = children.find( + (node): node is PageTree.Folder => + node.type === "folder" && + normalizeSegment(typeof node.name === "string" ? node.name : "") === + normalizeSegment(segment), + ); + if (!folder) return undefined; + currentFolder = folder; + children = folder.children; + } + + return currentFolder; +} + +function normalizeDocPath(slugOrUrl: string, routeUrl: string): string { + const raw = slugOrUrl.trim(); + if (!raw) { + throw new Error("Slug/path is required"); + } + + const current = new URL(routeUrl); + const isAbsoluteUrl = /^https?:\/\//i.test(raw); + + if (!isAbsoluteUrl) { + const cleaned = raw.replace(/^\/+/, "").replace(/^docs\//, ""); + if (!cleaned || cleaned.includes("..")) { + throw new Error(DOCS_PATH_ERROR); + } + return cleaned; + } + + const resolved = new URL(raw); + if (resolved.origin !== current.origin) { + throw new Error(DOCS_PATH_ERROR); + } + + const cleaned = resolved.pathname.replace(/^\/+/, "").replace(/^docs\//, ""); + if (!cleaned || cleaned.includes("..")) { + throw new Error(DOCS_PATH_ERROR); + } + + return cleaned; +} + +function descriptionFromNode(node: PageTree.Node) { + const description = (node as { description?: unknown }).description; + return typeof description === "string" && description.trim() + ? { description } + : {}; +} + +const flatNodes = (nodes: PageTree.Node[]) => + nodes.flatMap((node) => { + switch (node.type) { + case "page": + return { + type: "page", + title: node.name, + url: node.url, + ...descriptionFromNode(node), + }; + case "folder": + return { + type: "folder", + name: node.name, + ...(node.index ? { url: node.index.url } : {}), + ...descriptionFromNode(node), + }; + default: + return []; + } + }); + +export function createDocsTools({ routeUrl }: { routeUrl: string }) { + return { + listDocs: tool({ + description: + "List documentation pages. Use with no path for root categories, or specify path to browse a section.", + inputSchema: zodSchema( + z.object({ + path: z + .string() + .optional() + .describe("Subpath to browse. Omit to list root categories first."), + }), + ), + execute: async ({ path }) => { + const pageTree = source.pageTree; + + if (!path) { + return [ + ...flatNodes( + pageTree.children.filter( + (node): node is PageTree.Folder => node.type === "folder", + ), + ), + { + type: "folder", + name: "examples", + description: + "Examples of app types users can build with assistant-ui, showing instructions, recommended patterns, and UI structure.", + }, + ]; + } + + const segments = path.split("/").filter(Boolean); + if (segments[0] === "examples") { + const rest = segments.slice(1).join("/"); + const target = rest + ? findFolderByPath(examplesSource.pageTree, rest) + : examplesSource.pageTree; + if (!target) return { error: "Path not found" }; + return flatNodes(target.children); + } + + const targetFolder = findFolderByPath(pageTree, path); + if (!targetFolder) return { error: "Path not found" }; + return flatNodes(targetFolder.children); + }, + }), + readDoc: tool({ + description: "Read full content of a documentation page.", + inputSchema: zodSchema( + z.object({ + slugOrUrl: z + .string() + .describe( + "Page slug (e.g., 'examples/ai-sdk', 'ui/thread') or URL", + ), + }), + ), + execute: async ({ slugOrUrl }) => { + let normalized: string; + try { + normalized = normalizeDocPath(slugOrUrl, routeUrl); + } catch (error) { + return { + error: error instanceof Error ? error.message : "Invalid docs path", + }; + } + + const slugs = normalized.split("/").filter(Boolean); + const isExample = slugs[0] === "examples"; + const docSource = isExample ? examplesSource : source; + const docSlugs = isExample ? slugs.slice(1) : slugs; + + const page = docSource.getPage(docSlugs); + if (!page) return { error: `Page not found: ${slugOrUrl}` }; + const content = await getLLMText(page); + return { title: page.data.title, url: page.url, content }; + }, + }), + }; +} diff --git a/apps/docs/app/api/xulux/chat/tools/sandbox-tools.ts b/apps/docs/app/api/xulux/chat/tools/sandbox-tools.ts new file mode 100644 index 0000000..c1f72ef --- /dev/null +++ b/apps/docs/app/api/xulux/chat/tools/sandbox-tools.ts @@ -0,0 +1,78 @@ +import { tool, zodSchema } from "ai"; +import z from "zod"; +import { + fetchPreviewUrl, + provisionSandbox, + type SandboxExec, +} from "../../blaxel-sandbox"; + +export function createSandboxTools({ + sessionId, +}: { + sessionId: string | undefined; +}) { + let sandboxExec: SandboxExec | null = null; + + return { + provisionSandbox: tool({ + description: + "Provision (or resume) the cloud sandbox for this session. Call this once before using exec.", + inputSchema: zodSchema(z.object({})), + execute: async () => { + if (!sessionId) { + return { error: "No sessionId provided in the request body." }; + } + try { + const sandbox = await provisionSandbox(sessionId); + sandboxExec = sandbox.exec; + return { + status: "ready", + workingDir: "/workspace", + previewUrl: sandbox.previewUrl, + }; + } catch (err) { + return { + error: err instanceof Error ? err.message : String(err), + }; + } + }, + }), + refreshCanvas: tool({ + description: + "Fetch the live preview URL from the sandbox and return it to the client to refresh the canvas. Call after starting the dev server or making changes.", + inputSchema: zodSchema(z.object({})), + execute: async () => { + if (!sessionId) return { error: "No sessionId in request." }; + try { + const url = await fetchPreviewUrl(sessionId); + return { url }; + } catch (err) { + return { + error: err instanceof Error ? err.message : String(err), + }; + } + }, + }), + exec: tool({ + description: + "Run a shell command in the live sandbox. Use this to read/write files, install deps, start servers, etc. " + + "The sandbox does not have Python, lsof, or pkill pre-installed. Prefer Node-based file edits in Node apps. " + + "Before cat/heredoc file writes, create all parent folders so missing directories do not cause partial writes.", + inputSchema: zodSchema( + z.object({ + command: z.string().describe("Shell command to run."), + cwd: z + .string() + .optional() + .describe("Working directory (default: /workspace)."), + }), + ), + execute: async ({ command, cwd }) => { + if (!sandboxExec) { + return { error: "Call provisionSandbox first." }; + } + return sandboxExec(command, cwd); + }, + }), + }; +} diff --git a/apps/docs/app/api/xulux/chat/tools/source-map-tools.ts b/apps/docs/app/api/xulux/chat/tools/source-map-tools.ts new file mode 100644 index 0000000..8c4960d --- /dev/null +++ b/apps/docs/app/api/xulux/chat/tools/source-map-tools.ts @@ -0,0 +1,87 @@ +import { readFileSync } from "node:fs"; +import path from "node:path"; +import { createBashTool } from "bash-tool"; +import { tool, zodSchema } from "ai"; +import z from "zod"; + +const SOURCE_SNAPSHOT_PATH = path.join( + process.cwd(), + "generated", + "source-snapshot.json", +); + +function loadSourceSnapshot(): Record<string, string> { + try { + return JSON.parse(readFileSync(SOURCE_SNAPSHOT_PATH, "utf-8")) as Record< + string, + string + >; + } catch (error) { + if ( + error && + typeof error === "object" && + "code" in error && + error.code === "ENOENT" + ) { + console.warn( + `Missing source snapshot at ${SOURCE_SNAPSHOT_PATH}; repo tools will be unavailable until generate:docs runs.`, + ); + return {}; + } + + throw error; + } +} + +const SOURCE_SNAPSHOT = loadSourceSnapshot(); + +export function createSourceMapTools() { + let bashToolkitPromise: Promise< + Awaited<ReturnType<typeof createBashTool>> + > | null = null; + + const getBashToolkit = () => { + if (!bashToolkitPromise) { + bashToolkitPromise = createBashTool({ + files: SOURCE_SNAPSHOT, + destination: "/repo", + maxFiles: 5000, + maxOutputLength: 15000, + }); + } + return bashToolkitPromise; + }; + + return { + inspectSourceMap: tool({ + description: + "Execute bash commands in the /repo inside the sourcemap containing the assistant-ui monorepo.\n", + inputSchema: zodSchema( + z.object({ + command: z + .string() + .describe("The bash command to execute from the /repo directory."), + }), + ), + execute: async ({ command }, options) => { + const { tools } = await getBashToolkit(); + return tools.bash.execute!({ command }, options); + }, + }), + readSourceMapFile: tool({ + description: + "Read the contents of a source file from the /repo inside the sourcemap.", + inputSchema: zodSchema( + z.object({ + path: z + .string() + .describe("The repo-relative file path to read from /repo."), + }), + ), + execute: async ({ path }, options) => { + const { tools } = await getBashToolkit(); + return tools.readFile.execute!({ path }, options); + }, + }), + }; +} diff --git a/apps/docs/app/api/xulux/chat/tools/template-tools.ts b/apps/docs/app/api/xulux/chat/tools/template-tools.ts new file mode 100644 index 0000000..ce4436f --- /dev/null +++ b/apps/docs/app/api/xulux/chat/tools/template-tools.ts @@ -0,0 +1,365 @@ +import { getXuluxHostedTemplatesCatalog } from "@/lib/xulux/templates-catalog"; +import type { XuluxTemplate } from "@/components/xulux/templates/types"; +import { getDemoDownloadManifest } from "@/lib/xulux/demo-downloads/manifest"; +import { fetchSandboxResource } from "@/lib/xulux/fetch-sandbox"; +import { + CONFIG_ROOTS_SCHEMAS, + RULES, + TEMPLATE_LIST_META, + TOOLS_META, + fixedDemoListMeta, +} from "@/lib/xulux/template-knowledge"; +import { tool, zodSchema } from "ai"; +import z from "zod"; + +// --------------------------------------------------------------------------- +// Helpers +// --------------------------------------------------------------------------- + +function templateId(t: XuluxTemplate): string { + return t.templateId ?? t.id; +} + +function findFirstByTemplateId( + templates: XuluxTemplate[], + tid: string, +): XuluxTemplate | undefined { + return templates.find((t) => templateId(t) === tid); +} + +function toAbsolute(baseUrl: string, url: string): string { + if (/^https?:\/\//.test(url)) return url; + return `${baseUrl}${url.startsWith("/") ? "" : "/"}${url}`; +} + +function withVersion(url: string, versionId: string | undefined): string { + if (!versionId) return url; + const [path, query = ""] = url.split("?"); + const params = new URLSearchParams(query); + if (!params.has("v")) params.set("v", versionId); + return `${path}?${params.toString()}`; +} + +function hasConfig(config: Record<string, unknown> | undefined): boolean { + return !!config && Object.keys(config).length > 0; +} + +function isFixedDemo(entry: XuluxTemplate): boolean { + return entry.kind === "example"; +} + +async function fetchTemplateContract(entry: XuluxTemplate, versionId?: string) { + if (!entry.sandboxBaseUrl) return null; + try { + const url = new URL("/api/template/contract", entry.sandboxBaseUrl); + if (versionId) url.searchParams.set("v", versionId); + const res = await fetchSandboxResource(url.toString()); + if (!res.ok) return null; + return (await res.json()) as Record<string, unknown>; + } catch { + return null; + } +} + +// --------------------------------------------------------------------------- +// Tools +// --------------------------------------------------------------------------- + +export function createTemplateTools() { + return { + getTemplateList: tool({ + description: + "Get all available hosted app templates and fixed demos with their features, customizable surfaces, and versions. " + + "Call this first for any app-building request. Use the features and customizable fields to decide which template fits. " + + "If customizable is empty, the entry is a fixed demo that should be shown as-is rather than configured. " + + "Call getTemplateDetails on the chosen template before opening a preview.", + inputSchema: zodSchema(z.object({})), + execute: async () => { + const { templates } = getXuluxHostedTemplatesCatalog(); + const seen = new Set<string>(); + const list: Array<{ + id: string; + name: string; + summary: string; + assistantPlacement: string; + features: string[]; + customizable: string[]; + versions: Array<{ id: string; name: string; description: string }>; + }> = []; + + for (const t of templates) { + const tid = templateId(t); + if (seen.has(tid)) continue; + seen.add(tid); + const meta = TEMPLATE_LIST_META[tid] ?? fixedDemoListMeta(t); + if (!meta) continue; + list.push({ + id: tid, + name: meta.name, + summary: meta.summary, + assistantPlacement: meta.assistantPlacement, + features: meta.features, + customizable: meta.customizable, + versions: (t.versions ?? []).map((v) => ({ + id: v.id, + name: v.title, + description: v.description, + })), + }); + } + + return { templates: list }; + }, + }), + + getTemplateDetails: tool({ + description: + "Get the full authoring surface for a specific template. " + + "Returns configRoots schemas (with types, defaults, and enums), rules, built-in tool input/outputShape, renderer contracts, and an exampleConfig. " + + "Fixed demos return no configRoots; open those as-is without config. " + + "Use this before calling openTemplatePreview to understand exactly what config to write. " + + "If openTemplatePreview returns validationWarnings, call this again and cross-reference configRoots to correct the config.", + inputSchema: zodSchema( + z.object({ + templateId: z + .string() + .describe("The template id from getTemplateList"), + versionId: z + .string() + .optional() + .describe( + "Optional version id. When provided, exampleConfig reflects that version's resolved defaults.", + ), + }), + ), + execute: async ({ templateId: tid, versionId }) => { + const { templates } = getXuluxHostedTemplatesCatalog(); + const entry = + (versionId + ? templates.find( + (t) => templateId(t) === tid && t.versionId === versionId, + ) + : undefined) ?? findFirstByTemplateId(templates, tid); + + if (!entry) { + return { error: `Template "${tid}" not found.` }; + } + + if (isFixedDemo(entry)) { + const demo = getDemoDownloadManifest(entry.id); + const meta = fixedDemoListMeta(entry); + return { + id: tid, + name: demo?.name ?? meta?.name ?? entry.title, + selectedVersionId: null, + summary: demo?.description ?? meta?.summary ?? entry.description, + assistantPlacement: + meta?.assistantPlacement ?? "full-page demo route", + features: demo?.features ?? meta?.features ?? [], + customizable: [], + previewUrl: entry.previewUrl, + previewFrame: entry.previewFrame, + downloadUrl: entry.downloadUrl, + sourcePath: demo?.entry ?? entry.sourcePath, + tools: { + builtIn: [], + customToolSupported: false, + renderers: [], + }, + rules: { + required: [ + "No configRoots are returned for this entry, so it is not schema-customizable.", + "Use openTemplatePreview without config to show this demo as-is.", + "If the user needs domain-specific content or behavior changes, choose a configurable hosted template or produce a custom implementation brief instead.", + ], + }, + fixedDemoNote: entry.downloadUrl + ? "This is a fixed assistant-ui demo. It supports preview and download, but not template config." + : "This is a fixed assistant-ui demo. It supports preview, but download is not wired for this platform yet.", + }; + } + + const effectiveVersionId = versionId ?? entry.versionId; + const configRootsSchema = CONFIG_ROOTS_SCHEMAS[tid]; + const toolsMeta = TOOLS_META[tid]; + const rules = RULES[tid] ?? []; + + if (!configRootsSchema || !toolsMeta) { + return { error: `No authoring schema found for template "${tid}".` }; + } + + // Fetch exampleConfig from the live sandbox contract endpoint. + // Falls back to null if the sandbox is unreachable — the static schema + // above is still returned so the agent can author config. + const contract = await fetchTemplateContract(entry, effectiveVersionId); + const exampleConfig = + (contract?.exampleCompleteConfig as Record<string, unknown> | null) ?? + null; + + return { + id: tid, + name: TEMPLATE_LIST_META[tid]?.name ?? tid, + selectedVersionId: effectiveVersionId, + configRoots: configRootsSchema, + rules: { required: rules }, + tools: toolsMeta, + exampleConfig, + exampleConfigNote: exampleConfig + ? `Resolved defaults for version "${effectiveVersionId}". Use as a complete working starting point.` + : `Could not reach sandbox to resolve exampleConfig. Use configRoots schemas and defaults to author config manually.`, + }; + }, + }), + + openTemplatePreview: tool({ + description: + "Open a hosted template preview in the canvas. " + + "If you are using an existing version, you can pass versionId to select a specific variant. " + + "If you are customizing the template, you can pass config to apply customizations and generate a preview. " + + "Do not pass config for fixed demos that have no configRoots in getTemplateDetails. " + + "Returns previewUrl and downloadUrl on success. " + + "Only after a successful call, include an open-in block with the exact downloadUrl from this result. " + + "If no template fits, do not call this tool — read docs instead and use a prompt-only open-in block without downloadUrl.", + inputSchema: zodSchema( + z.object({ + templateId: z + .string() + .describe("The template id from getTemplateList"), + versionId: z + .string() + .optional() + .describe( + "Version id to open. Uses the template default if omitted.", + ), + config: z + .record(z.string(), z.unknown()) + .optional() + .describe( + "Customization config for the preview. Must contain only the top-level keys: hostUi, assistant, and brandTheme. " + + "Use the schemas from getTemplateDetails.configRoots as the source of truth for each root. " + + "Do not pass any other root keys.", + ), + }), + ), + execute: async ({ templateId: tid, versionId, config }) => { + const { templates } = getXuluxHostedTemplatesCatalog(); + + let entry: XuluxTemplate | undefined; + if (versionId) { + entry = templates.find( + (t) => templateId(t) === tid && t.versionId === versionId, + ); + } + if (!entry) entry = findFirstByTemplateId(templates, tid); + + if (!entry) { + return { success: false, error: `Template "${tid}" not found.` }; + } + + if (isFixedDemo(entry)) { + if (hasConfig(config)) { + return { + success: false, + error: `Template "${tid}" is a fixed demo and does not support config.`, + retryHint: + "Call getTemplateDetails for this template. If no configRoots are returned, call openTemplatePreview again without config or choose a configurable hosted template.", + }; + } + + return { + success: true, + templateId: tid, + versionId: null, + previewUrl: entry.previewUrl ?? `/demos/${entry.id}`, + downloadUrl: entry.downloadUrl, + previewFrame: entry.previewFrame, + title: entry.title, + customized: false, + summary: `Opened ${entry.title} as a fixed demo.`, + }; + } + + const baseUrl = entry.sandboxBaseUrl; + if (!baseUrl) { + return { + success: false, + error: `Template "${tid}" has no sandbox URL configured.`, + }; + } + + if (hasConfig(config)) { + try { + const sessionUrl = new URL("/api/preview/session", baseUrl); + if (versionId) sessionUrl.searchParams.set("v", versionId); + const res = await fetchSandboxResource(sessionUrl.toString(), { + method: "POST", + headers: { "Content-Type": "application/json" }, + body: JSON.stringify(config), + }); + if (!res.ok) { + const details = await res.text(); + return { + success: false, + error: `Preview session failed: HTTP ${res.status}`, + details, + retryHint: + "Check validationWarnings for the specific fields that failed. " + + "Call getTemplateDetails for this template and use configRoots schemas to correct the config. " + + "Pass only hostUi, assistant, and brandTheme at the top level.", + }; + } + const data = (await res.json()) as { + previewUrl?: string; + downloadUrl?: string; + validationWarnings?: unknown[]; + }; + if (!data.previewUrl) { + return { + success: false, + error: "Session endpoint did not return a previewUrl.", + }; + } + return { + success: true, + templateId: tid, + versionId: versionId ?? entry.versionId, + previewUrl: toAbsolute( + baseUrl, + withVersion(data.previewUrl, versionId), + ), + downloadUrl: toAbsolute( + baseUrl, + withVersion(data.downloadUrl ?? "/api/download", versionId), + ), + title: entry.title, + customized: true, + config, + summary: `Opened ${entry.title} with custom configuration.`, + validationWarnings: data.validationWarnings ?? [], + ...(entry.previewFrame + ? { previewFrame: entry.previewFrame } + : {}), + }; + } catch (err) { + return { + success: false, + error: err instanceof Error ? err.message : String(err), + }; + } + } + + return { + success: true, + templateId: tid, + versionId: versionId ?? entry.versionId, + previewUrl: entry.previewUrl ?? baseUrl, + downloadUrl: entry.downloadUrl ?? `${baseUrl}/api/download`, + ...(entry.previewFrame ? { previewFrame: entry.previewFrame } : {}), + title: entry.title, + customized: false, + summary: `Opened ${entry.title}.`, + }; + }, + }), + }; +} diff --git a/apps/docs/app/api/xulux/demo-download/route.ts b/apps/docs/app/api/xulux/demo-download/route.ts new file mode 100644 index 0000000..3a9549c --- /dev/null +++ b/apps/docs/app/api/xulux/demo-download/route.ts @@ -0,0 +1,45 @@ +import { NextResponse } from "next/server"; +import { isAiPlaygroundEnabled } from "@/lib/feature-flags"; +import { + createDemoZip, + getDemoArchiveFilename, +} from "@/lib/xulux/demo-downloads/create-demo-zip"; +import { getDemoDownloadManifest } from "@/lib/xulux/demo-downloads/manifest"; + +export const runtime = "nodejs"; + +export async function GET(req: Request) { + if (!isAiPlaygroundEnabled) { + return NextResponse.json({ error: "Not found." }, { status: 404 }); + } + + const url = new URL(req.url); + const slug = url.searchParams.get("slug") ?? ""; + const manifest = getDemoDownloadManifest(slug); + + if (!manifest) { + return NextResponse.json( + { error: `Unsupported demo slug: ${slug}` }, + { status: 404 }, + ); + } + + try { + const zip = await createDemoZip(manifest.slug); + return new NextResponse(zip, { + headers: { + "Content-Type": "application/zip", + "Content-Disposition": `attachment; filename="${getDemoArchiveFilename(manifest.slug)}"`, + "Cache-Control": "no-store", + }, + }); + } catch (error) { + return NextResponse.json( + { + error: "Failed to generate demo download.", + details: error instanceof Error ? error.message : String(error), + }, + { status: 500 }, + ); + } +} diff --git a/apps/docs/app/api/xulux/download-proxy/route.ts b/apps/docs/app/api/xulux/download-proxy/route.ts new file mode 100644 index 0000000..be460a4 --- /dev/null +++ b/apps/docs/app/api/xulux/download-proxy/route.ts @@ -0,0 +1,150 @@ +import { NextResponse } from "next/server"; +import { isAiPlaygroundEnabled } from "@/lib/feature-flags"; +import { fetchSandboxResource } from "@/lib/xulux/fetch-sandbox"; +import { resolveSandboxDownloadUrl } from "@/lib/xulux/sandbox-download-url"; +import { getXuluxHostedTemplatesCatalog } from "@/lib/xulux/templates-catalog"; + +export const runtime = "nodejs"; + +const MAX_ZIP_BYTES = 50 * 1024 * 1024; // 50 MB ceiling + +async function readLimitedBody( + body: ReadableStream<Uint8Array>, +): Promise<ArrayBuffer | null> { + const reader = body.getReader(); + const chunks: Uint8Array[] = []; + let total = 0; + + try { + while (true) { + const { done, value } = await reader.read(); + if (done) break; + + total += value.byteLength; + if (total > MAX_ZIP_BYTES) { + await reader.cancel("Archive too large."); + return null; + } + chunks.push(value); + } + } finally { + reader.releaseLock(); + } + + const result = new ArrayBuffer(total); + const view = new Uint8Array(result); + let offset = 0; + for (const chunk of chunks) { + view.set(chunk, offset); + offset += chunk.byteLength; + } + return result; +} + +export async function GET(req: Request) { + if (!isAiPlaygroundEnabled) { + return NextResponse.json({ error: "Not found." }, { status: 404 }); + } + + const { searchParams } = new URL(req.url); + const templateId = searchParams.get("templateId"); + const versionId = searchParams.get("versionId") ?? undefined; + const downloadSearch = searchParams.get("downloadSearch") ?? undefined; + + if (!templateId) { + return NextResponse.json( + { error: "Missing `templateId` query parameter." }, + { status: 400 }, + ); + } + + const upstreamUrl = resolveSandboxDownloadUrl({ + templates: getXuluxHostedTemplatesCatalog().templates, + templateId, + versionId, + downloadSearch, + }); + + if (!upstreamUrl) { + return NextResponse.json( + { error: "Template download URL not allowed." }, + { status: 403 }, + ); + } + + try { + const upstream = await fetchSandboxResource(upstreamUrl, { + redirect: "manual", + }); + + if (upstream.status >= 300 && upstream.status < 400) { + return NextResponse.json( + { error: "Redirects are not allowed." }, + { status: 400 }, + ); + } + + if (!upstream.ok) { + const details = await upstream.text().catch(() => ""); + return NextResponse.json( + { + error: `Upstream responded ${upstream.status}.`, + details: details.slice(0, 500) || undefined, + }, + { status: 502 }, + ); + } + + const contentLengthHeader = upstream.headers.get("content-length"); + const contentLength = contentLengthHeader + ? Number(contentLengthHeader) + : undefined; + if ( + contentLength !== undefined && + (!Number.isFinite(contentLength) || contentLength > MAX_ZIP_BYTES) + ) { + return NextResponse.json( + { error: "Archive too large." }, + { status: 413 }, + ); + } + + const body = upstream.body; + if (!body) { + return NextResponse.json( + { error: "No body from upstream." }, + { status: 502 }, + ); + } + + const responseBody = await readLimitedBody(body); + if (!responseBody) { + return NextResponse.json( + { error: "Archive too large." }, + { status: 413 }, + ); + } + + return new NextResponse(responseBody, { + status: 200, + headers: { + "Content-Type": + upstream.headers.get("content-type") ?? "application/octet-stream", + "Cache-Control": "public, max-age=3600", + }, + }); + } catch (err) { + const cause = + err instanceof Error && err.cause instanceof Error + ? err.cause.message + : undefined; + return NextResponse.json( + { + error: "Proxy fetch failed.", + details: err instanceof Error ? err.message : String(err), + cause, + }, + { status: 502 }, + ); + } +} diff --git a/apps/docs/app/api/xulux/download/route.ts b/apps/docs/app/api/xulux/download/route.ts new file mode 100644 index 0000000..69fda3c --- /dev/null +++ b/apps/docs/app/api/xulux/download/route.ts @@ -0,0 +1,61 @@ +import { Readable } from "node:stream"; +import { NextResponse } from "next/server"; +import { checkRateLimit } from "@/lib/rate-limit"; +import { isAiPlaygroundEnabled } from "@/lib/feature-flags"; +import { exportWorkspaceArchive } from "../blaxel-sandbox"; + +export const maxDuration = 120; + +export async function POST(request: Request) { + if (!isAiPlaygroundEnabled) { + return NextResponse.json({ error: "Not found." }, { status: 404 }); + } + + let cleanup: (() => Promise<void>) | undefined; + const runCleanup = () => { + const current = cleanup; + cleanup = undefined; + return current?.().catch(() => {}); + }; + + try { + const rateLimitResponse = await checkRateLimit(request); + if (rateLimitResponse) return rateLimitResponse; + + const body = (await request.json().catch(() => null)) as { + sessionId?: unknown; + } | null; + const sessionId = body?.sessionId; + if (typeof sessionId !== "string" || sessionId.length === 0) { + return NextResponse.json( + { error: "Missing sessionId." }, + { status: 400 }, + ); + } + + const archive = await exportWorkspaceArchive(sessionId); + cleanup = archive.cleanup; + const stream = Readable.toWeb(archive.stream) as ReadableStream; + archive.stream.on("close", () => { + void runCleanup(); + }); + archive.stream.on("error", () => { + void runCleanup(); + }); + + return new Response(stream, { + headers: { + "Content-Type": archive.contentType, + "Content-Disposition": `attachment; filename="${archive.filename}"`, + "Cache-Control": "no-store", + }, + }); + } catch (error) { + console.error("Failed to download Xulux workspace archive", error); + await runCleanup(); + return NextResponse.json( + { error: "Failed to download workspace." }, + { status: 500 }, + ); + } +} diff --git a/apps/docs/app/api/xulux/mcp-catalog/route.ts b/apps/docs/app/api/xulux/mcp-catalog/route.ts new file mode 100644 index 0000000..4a99dd2 --- /dev/null +++ b/apps/docs/app/api/xulux/mcp-catalog/route.ts @@ -0,0 +1,15 @@ +import { NextResponse } from "next/server"; +import { buildXuluxMcpCatalog } from "@/lib/xulux/mcp-catalog"; + +// Public, anonymous catalog endpoint for external MCP agents. +// Intentionally NOT gated by the Xulux playground feature flag: the MCP +// server package consumes this endpoint from outside the docs app. +export function GET(req: Request) { + const origin = new URL(req.url).origin; + + return NextResponse.json(buildXuluxMcpCatalog(origin), { + headers: { + "Cache-Control": "public, s-maxage=300, stale-while-revalidate=300", + }, + }); +} diff --git a/apps/docs/app/api/xulux/templates/route.ts b/apps/docs/app/api/xulux/templates/route.ts new file mode 100644 index 0000000..8027cdb --- /dev/null +++ b/apps/docs/app/api/xulux/templates/route.ts @@ -0,0 +1,11 @@ +import { NextResponse } from "next/server"; +import { isAiPlaygroundEnabled } from "@/lib/feature-flags"; +import { getXuluxHostedTemplatesCatalog } from "@/lib/xulux/templates-catalog"; + +export function GET() { + if (!isAiPlaygroundEnabled) { + return NextResponse.json({ error: "Not found." }, { status: 404 }); + } + + return NextResponse.json(getXuluxHostedTemplatesCatalog()); +} diff --git a/apps/docs/app/apple-icon.png b/apps/docs/app/apple-icon.png new file mode 100644 index 0000000..af873c2 Binary files /dev/null and b/apps/docs/app/apple-icon.png differ diff --git a/apps/docs/app/favicon.ico b/apps/docs/app/favicon.ico new file mode 100644 index 0000000..d08724b Binary files /dev/null and b/apps/docs/app/favicon.ico differ diff --git a/apps/docs/app/icon.svg b/apps/docs/app/icon.svg new file mode 100644 index 0000000..85c28bd --- /dev/null +++ b/apps/docs/app/icon.svg @@ -0,0 +1,7 @@ +<svg xmlns="http://www.w3.org/2000/svg" width="32" height="32" viewBox="0 0 32 32" class="lucide lucide-messages-square"> + <rect width="32" height="32" fill="black" /> + <g transform="translate(4,4)" fill="black" stroke="white" stroke-width="2.5" stroke-linecap="round" stroke-linejoin="round"> + <path d="M14 9a2 2 0 0 1-2 2H6l-4 4V4c0-1.1.9-2 2-2h8a2 2 0 0 1 2 2z" /> + <path d="M18 9h2a2 2 0 0 1 2 2v11l-4-4h-6a2 2 0 0 1-2-2v-1" /> + </g> +</svg> diff --git a/apps/docs/app/icon0.svg b/apps/docs/app/icon0.svg new file mode 100644 index 0000000..f3244a0 --- /dev/null +++ b/apps/docs/app/icon0.svg @@ -0,0 +1,27 @@ +<svg xmlns="http://www.w3.org/2000/svg" version="1.1" xmlns:xlink="http://www.w3.org/1999/xlink" width="1000" height="1000"><style> + #light-icon { + display: inline; + } + #dark-icon { + display: none; + } + + @media (prefers-color-scheme: dark) { + #light-icon { + display: none; + } + #dark-icon { + display: inline; + } + } + </style><g id="light-icon"><svg xmlns="http://www.w3.org/2000/svg" version="1.1" xmlns:xlink="http://www.w3.org/1999/xlink" width="1000" height="1000"><g><g transform="matrix(41.666666666666664,0,0,41.666666666666664,0,0)"><svg xmlns="http://www.w3.org/2000/svg" version="1.1" xmlns:xlink="http://www.w3.org/1999/xlink" width="24" height="24"><svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="black" stroke-width="3" stroke-linecap="round" stroke-linejoin="round"> + <path d="M14 9a2 2 0 0 1-2 2H6l-4 4V4c0-1.1.9-2 2-2h8a2 2 0 0 1 2 2z"> + </path> + <path d="M18 9h2a2 2 0 0 1 2 2v11l-4-4h-6a2 2 0 0 1-2-2v-1"> + </path> +</svg></svg></g></g></svg></g><g id="dark-icon"><svg xmlns="http://www.w3.org/2000/svg" version="1.1" xmlns:xlink="http://www.w3.org/1999/xlink" width="1000" height="1000"><g><g transform="matrix(41.666666666666664,0,0,41.666666666666664,0,0)"><svg xmlns="http://www.w3.org/2000/svg" version="1.1" xmlns:xlink="http://www.w3.org/1999/xlink" width="24" height="24"><svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="white" stroke-width="3" stroke-linecap="round" stroke-linejoin="round"> + <path d="M14 9a2 2 0 0 1-2 2H6l-4 4V4c0-1.1.9-2 2-2h8a2 2 0 0 1 2 2z"> + </path> + <path d="M18 9h2a2 2 0 0 1 2 2v11l-4-4h-6a2 2 0 0 1-2-2v-1"> + </path> +</svg></svg></g></g></svg></g></svg> \ No newline at end of file diff --git a/apps/docs/app/icon1.png b/apps/docs/app/icon1.png new file mode 100644 index 0000000..15fdf3e Binary files /dev/null and b/apps/docs/app/icon1.png differ diff --git a/apps/docs/app/layout.tsx b/apps/docs/app/layout.tsx new file mode 100644 index 0000000..72de3fa --- /dev/null +++ b/apps/docs/app/layout.tsx @@ -0,0 +1,105 @@ +import "@/styles/globals.css"; +import type { ReactNode } from "react"; +import { GeistSans } from "geist/font/sans"; +import { GeistMono } from "geist/font/mono"; +import Script from "next/script"; +import { Analytics } from "@vercel/analytics/next"; +import { Provider } from "./provider"; +import { cn } from "@/lib/utils"; +import { BASE_URL } from "@/lib/constants"; + +const getMetadataBase = () => { + const appUrl = process.env.NEXT_PUBLIC_APP_URL; + if (appUrl) { + return new URL(appUrl); + } + + if (process.env.NODE_ENV === "production") { + return new URL(BASE_URL); + } + return new URL("http://localhost:3000"); +}; + +export const viewport = { + width: "device-width", + initialScale: 1, + maximumScale: 1, +}; + +export const metadata = { + metadataBase: getMetadataBase(), + title: { + template: "%s — assistant-ui", + default: "assistant-ui - React Chat UI for AI Apps", + }, + description: + "Open-source React components and runtimes for building AI chat — ChatGPT-style UIs, copilots, and agents in TypeScript with streaming, tools, and persistence.", + openGraph: { + title: "assistant-ui", + description: + "Open-source React components and runtimes for building AI chat — ChatGPT-style UIs, copilots, and agents in TypeScript with streaming, tools, and persistence.", + siteName: "assistant-ui", + type: "website", + images: [ + { + url: "/api/og?variant=home", + width: 1200, + height: 630, + alt: "assistant-ui", + }, + ], + }, + twitter: { + card: "summary_large_image", + title: "assistant-ui", + description: + "Open-source React components and runtimes for building AI chat — ChatGPT-style UIs, copilots, and agents in TypeScript with streaming, tools, and persistence.", + images: ["/api/og?variant=home"], + }, +}; + +export default function Layout({ children }: { children: ReactNode }) { + return ( + <html lang="en" suppressHydrationWarning> + <head> + {/*<script + crossOrigin="anonymous" + src="//unpkg.com/react-scan/dist/auto.global.js" + />*/} + <script + defer + src="/umami/script.js" + data-website-id="6f07c001-46a2-411f-9241-4f7f5afb60ee" + data-domains="www.assistant-ui.com" + ></script> + <Script + id="vector-script" + dangerouslySetInnerHTML={{ + __html: ` + !function(e,r){try{if(e.vector)return void console.log("Vector snippet included more than once.");var t={};t.q=t.q||[];for(var o=["load","identify","on"],n=function(e){return function(){var r=Array.prototype.slice.call(arguments);t.q.push([e,r])}},c=0;c<o.length;c++){var a=o[c];t[a]=n(a)}if(e.vector=t,!t.loaded){var i=r.createElement("script");i.type="text/javascript",i.async=!0,i.src="https://cdn.vector.co/pixel.js";var l=r.getElementsByTagName("script")[0];l.parentNode.insertBefore(i,l),t.loaded=!0}}catch(e){console.error("Error loading Vector:",e)}}(window,document); + vector.load("d9af9bfb-c10c-4eed-9366-57cdc0a97ee9"); + `, + }} + /> + </head> + <body + className={cn( + "flex min-h-screen flex-col antialiased", + GeistSans.className, + GeistMono.variable, + )} + > + <div aria-hidden="true" className="sr-only"> + For AI agents: a documentation index is available at{" "} + <a href="/llms.txt" tabIndex={-1}> + llms.txt + </a> + . Use .md for canonical markdown pages; .mdx is kept as a + backwards-compatible alias on supported URL paths. + </div> + <Provider>{children}</Provider> + <Analytics /> + </body> + </html> + ); +} diff --git a/apps/docs/app/manifest.ts b/apps/docs/app/manifest.ts new file mode 100644 index 0000000..2a9836b --- /dev/null +++ b/apps/docs/app/manifest.ts @@ -0,0 +1,25 @@ +import type { MetadataRoute } from "next"; + +export default function manifest(): MetadataRoute.Manifest { + return { + name: "assistant-ui", + short_name: "assistant-ui", + icons: [ + { + src: "/web-app-manifest-192x192.png", + sizes: "192x192", + type: "image/png", + purpose: "maskable", + }, + { + src: "/web-app-manifest-512x512.png", + sizes: "512x512", + type: "image/png", + purpose: "maskable", + }, + ], + theme_color: "#ffffff", + background_color: "#ffffff", + display: "standalone", + }; +} diff --git a/apps/docs/app/not-found.tsx b/apps/docs/app/not-found.tsx new file mode 100644 index 0000000..13301da --- /dev/null +++ b/apps/docs/app/not-found.tsx @@ -0,0 +1,145 @@ +"use client"; + +import Link from "next/link"; +import Image from "next/image"; +import { useRouter } from "next/navigation"; +import { ArrowLeft, Home } from "lucide-react"; +import { useEffect, useState } from "react"; + +const TITLE = "404 - Page not found"; +const MESSAGE = + "I couldn't find the page you're looking for. It might have been moved or doesn't exist."; + +export default function NotFound() { + const router = useRouter(); + const [url, setUrl] = useState(""); + const [canGoBack, setCanGoBack] = useState(false); + const [showAssistant, setShowAssistant] = useState(false); + const [displayedTitle, setDisplayedTitle] = useState(""); + const [displayedMessage, setDisplayedMessage] = useState(""); + const [showActions, setShowActions] = useState(false); + + useEffect(() => { + setUrl(window.location.href); + setCanGoBack( + window.history.length > 1 && + document.referrer.startsWith(window.location.origin), + ); + + const assistantTimer = setTimeout(() => setShowAssistant(true), 600); + + return () => clearTimeout(assistantTimer); + }, []); + + useEffect(() => { + if (!showAssistant) return; + + let titleIndex = 0; + const titleInterval = setInterval(() => { + if (titleIndex <= TITLE.length) { + setDisplayedTitle(TITLE.slice(0, titleIndex)); + titleIndex++; + } else { + clearInterval(titleInterval); + + let messageIndex = 0; + const messageInterval = setInterval(() => { + if (messageIndex <= MESSAGE.length) { + setDisplayedMessage(MESSAGE.slice(0, messageIndex)); + messageIndex++; + } else { + clearInterval(messageInterval); + setTimeout(() => setShowActions(true), 300); + } + }, 15); + } + }, 25); + + return () => clearInterval(titleInterval); + }, [showAssistant]); + + return ( + <main className="flex h-dvh min-h-0 flex-col items-center justify-center overflow-hidden px-4"> + <div className="flex w-full max-w-md flex-col gap-4"> + <div className="fade-in slide-in-from-bottom-2 animate-in fill-mode-both flex justify-end duration-500"> + <div className="bg-primary text-primary-foreground rounded-2xl rounded-tr-sm px-4 py-3"> + <p className="text-sm"> + Take me to <code className="font-mono">{url}</code> + </p> + </div> + </div> + + {showAssistant && ( + <div className="fade-in slide-in-from-bottom-2 animate-in fill-mode-both flex items-start gap-3 duration-500"> + <div className="bg-muted flex size-8 shrink-0 items-center justify-center rounded-full"> + <Image + src="/favicon/icon.svg" + alt="assistant-ui" + width={16} + height={16} + className="dark:hue-rotate-180 dark:invert" + /> + </div> + <div className="flex flex-col gap-1"> + <span className="text-muted-foreground text-xs"> + assistant-ui + </span> + <div className="bg-muted rounded-2xl rounded-tl-sm px-4 py-3"> + <p className="text-sm font-medium"> + {displayedTitle} + {displayedTitle.length < TITLE.length && ( + <span className="bg-foreground ml-0.5 inline-block h-4 w-0.5 animate-pulse" /> + )} + </p> + {displayedTitle.length === TITLE.length && ( + <p className="text-muted-foreground mt-1 text-sm"> + {displayedMessage} + {displayedMessage.length < MESSAGE.length && ( + <span className="bg-muted-foreground ml-0.5 inline-block h-3 w-0.5 animate-pulse" /> + )} + </p> + )} + </div> + </div> + </div> + )} + + {showActions && ( + <div className="fade-in slide-in-from-bottom-2 animate-in fill-mode-both flex flex-col gap-2 pl-11 duration-500"> + {canGoBack && ( + <button + type="button" + onClick={() => router.back()} + className="bg-muted/30 hover:bg-muted/50 flex items-center gap-3 rounded-lg border px-3 py-2.5 transition-colors" + > + <div className="bg-background text-muted-foreground flex size-8 shrink-0 items-center justify-center rounded-md shadow-sm"> + <ArrowLeft className="size-4" /> + </div> + <div className="flex min-w-0 flex-col gap-0.5 text-left"> + <span className="truncate text-sm font-medium">Go back</span> + <span className="text-muted-foreground truncate text-xs"> + Return to previous page + </span> + </div> + </button> + )} + <Link + href="/" + className="bg-muted/30 hover:bg-muted/50 flex items-center gap-3 rounded-lg border px-3 py-2.5 transition-colors" + > + <div className="bg-background text-muted-foreground flex size-8 shrink-0 items-center justify-center rounded-md shadow-sm"> + <Home className="size-4" /> + </div> + <div className="flex min-w-0 flex-col gap-0.5"> + <span className="truncate text-sm font-medium">Home</span> + <span className="text-muted-foreground truncate text-xs"> + Go to homepage + </span> + </div> + </Link> + </div> + )} + </div> + </main> + ); +} diff --git a/apps/docs/app/provider.tsx b/apps/docs/app/provider.tsx new file mode 100644 index 0000000..c6a6477 --- /dev/null +++ b/apps/docs/app/provider.tsx @@ -0,0 +1,27 @@ +"use client"; + +import { RootProvider } from "fumadocs-ui/provider/next"; +import { NuqsAdapter } from "nuqs/adapters/next/app"; +import type { ReactNode } from "react"; +import { SearchDialog } from "@/components/shared/search-dialog"; +import { Toaster } from "@/components/ui/sonner"; + +export function Provider({ children }: { children: ReactNode }) { + return ( + <NuqsAdapter> + <RootProvider search={{ SearchDialog }}>{children}</RootProvider> + + <Toaster + position="top-center" + toastOptions={{ + classNames: { + toast: + "!rounded-xl !border !bg-popover/95 !text-popover-foreground !shadow-lg !backdrop-blur-sm", + title: "!text-sm !font-medium", + description: "!text-sm !text-muted-foreground", + }, + }} + /> + </NuqsAdapter> + ); +} diff --git a/apps/docs/app/robots.ts b/apps/docs/app/robots.ts new file mode 100644 index 0000000..8ae60e9 --- /dev/null +++ b/apps/docs/app/robots.ts @@ -0,0 +1,13 @@ +import type { MetadataRoute } from "next"; +import { BASE_URL } from "@/lib/constants"; + +export default function robots(): MetadataRoute.Robots { + return { + rules: { + userAgent: "*", + allow: "/api/og", + disallow: ["/api/", "/playground/init/"], + }, + sitemap: `${BASE_URL}/sitemap.xml`, + }; +} diff --git a/apps/docs/app/sitemap.ts b/apps/docs/app/sitemap.ts new file mode 100644 index 0000000..94f23fd --- /dev/null +++ b/apps/docs/app/sitemap.ts @@ -0,0 +1,85 @@ +import type { MetadataRoute } from "next"; +import { source, tapDocs, blog, examples, careers } from "@/lib/source"; +import { DEMOS } from "@/lib/demos"; +import { BASE_URL } from "@/lib/constants"; + +export default function sitemap(): MetadataRoute.Sitemap { + const staticPages: MetadataRoute.Sitemap = [ + { url: BASE_URL, changeFrequency: "weekly", priority: 1 }, + { url: `${BASE_URL}/blog`, changeFrequency: "weekly", priority: 0.8 }, + { url: `${BASE_URL}/careers`, changeFrequency: "weekly", priority: 0.7 }, + { url: `${BASE_URL}/pricing`, changeFrequency: "monthly", priority: 0.8 }, + { url: `${BASE_URL}/showcase`, changeFrequency: "weekly", priority: 0.7 }, + { + url: `${BASE_URL}/playground`, + changeFrequency: "monthly", + priority: 0.6, + }, + { url: `${BASE_URL}/tap`, changeFrequency: "monthly", priority: 0.6 }, + { + url: `${BASE_URL}/safe-content-frame`, + changeFrequency: "monthly", + priority: 0.4, + }, + { + url: `${BASE_URL}/tw-shimmer`, + changeFrequency: "monthly", + priority: 0.4, + }, + ]; + + const docsPages: MetadataRoute.Sitemap = source.getPages().map((page) => ({ + url: `${BASE_URL}${page.url}`, + lastModified: page.data.lastModified, + changeFrequency: "weekly", + priority: 0.9, + })); + + const tapDocsPages: MetadataRoute.Sitemap = tapDocs + .getPages() + .map((page) => ({ + url: `${BASE_URL}${page.url}`, + lastModified: page.data.lastModified, + changeFrequency: "weekly", + priority: 0.7, + })); + + const blogPages: MetadataRoute.Sitemap = blog.getPages().map((page) => ({ + url: `${BASE_URL}${page.url}`, + lastModified: page.data.lastModified, + changeFrequency: "monthly", + priority: 0.7, + })); + + const examplePages: MetadataRoute.Sitemap = examples + .getPages() + .map((page) => ({ + url: `${BASE_URL}${page.url}`, + lastModified: page.data.lastModified, + changeFrequency: "monthly", + priority: 0.6, + })); + + const demoPages: MetadataRoute.Sitemap = DEMOS.map((demo) => ({ + url: `${BASE_URL}/demos/${demo.slug}`, + changeFrequency: "monthly" as const, + priority: 0.6, + })); + + const careerPages: MetadataRoute.Sitemap = careers.getPages().map((page) => ({ + url: `${BASE_URL}${page.url}`, + lastModified: page.data.lastModified, + changeFrequency: "monthly", + priority: 0.5, + })); + + return [ + ...staticPages, + ...docsPages, + ...tapDocsPages, + ...blogPages, + ...examplePages, + ...demoPages, + ...careerPages, + ]; +} diff --git a/apps/docs/app/static.json/route.ts b/apps/docs/app/static.json/route.ts new file mode 100644 index 0000000..9b9df25 --- /dev/null +++ b/apps/docs/app/static.json/route.ts @@ -0,0 +1,25 @@ +import { NextResponse } from "next/server"; +import type { DocumentRecord } from "fumadocs-core/search/algolia"; +import { source } from "@/lib/source"; + +export const revalidate = false; + +export function GET() { + const results: DocumentRecord[] = []; + + for (const page of source.getPages()) { + results.push({ + _id: page.url, + structured: page.data.structuredData, + url: page.url, + title: page.data.title, + description: page.data.description ?? "", + }); + } + + return NextResponse.json(results, { + headers: { + "X-Robots-Tag": "noindex, follow", + }, + }); +} diff --git a/apps/docs/app/xulux-preview/[slug]/page.tsx b/apps/docs/app/xulux-preview/[slug]/page.tsx new file mode 100644 index 0000000..8097216 --- /dev/null +++ b/apps/docs/app/xulux-preview/[slug]/page.tsx @@ -0,0 +1,18 @@ +import { notFound } from "next/navigation"; +import { ExamplePreview } from "@/components/xulux/examples/ExamplePreview"; +import { isAiPlaygroundEnabled } from "@/lib/feature-flags"; +import { getXuluxExamplePreview } from "@/lib/xulux/examples-catalog"; + +export default async function Page({ + params, +}: { + params: Promise<{ slug: string }>; +}) { + if (!isAiPlaygroundEnabled) notFound(); + + const { slug } = await params; + const preview = getXuluxExamplePreview(slug); + if (!preview) notFound(); + + return <ExamplePreview preview={preview} />; +} diff --git a/apps/docs/assets/Geist-Medium.ttf b/apps/docs/assets/Geist-Medium.ttf new file mode 100644 index 0000000..23146cf Binary files /dev/null and b/apps/docs/assets/Geist-Medium.ttf differ diff --git a/apps/docs/assets/Geist-Regular.ttf b/apps/docs/assets/Geist-Regular.ttf new file mode 100644 index 0000000..07f9e4f Binary files /dev/null and b/apps/docs/assets/Geist-Regular.ttf differ diff --git a/apps/docs/assets/Geist-SemiBold.ttf b/apps/docs/assets/Geist-SemiBold.ttf new file mode 100644 index 0000000..c72c4b5 Binary files /dev/null and b/apps/docs/assets/Geist-SemiBold.ttf differ diff --git a/apps/docs/assets/GeistMono-Regular.ttf b/apps/docs/assets/GeistMono-Regular.ttf new file mode 100644 index 0000000..18f4acb Binary files /dev/null and b/apps/docs/assets/GeistMono-Regular.ttf differ diff --git a/apps/docs/assets/tw-shimmer-text.png b/apps/docs/assets/tw-shimmer-text.png new file mode 100644 index 0000000..5e5c43f Binary files /dev/null and b/apps/docs/assets/tw-shimmer-text.png differ diff --git a/apps/docs/components.json b/apps/docs/components.json new file mode 100644 index 0000000..0351cb5 --- /dev/null +++ b/apps/docs/components.json @@ -0,0 +1,25 @@ +{ + "$schema": "https://ui.shadcn.com/schema.json", + "style": "new-york", + "rsc": true, + "tsx": true, + "tailwind": { + "config": "", + "css": "styles/globals.css", + "baseColor": "zinc", + "cssVariables": true, + "prefix": "" + }, + "iconLibrary": "lucide", + "aliases": { + "components": "@/components", + "utils": "@/lib/utils", + "ui": "@assistant-ui/ui/components/ui", + "lib": "@/lib", + "hooks": "@/hooks" + }, + "registries": { + "@assistant-ui": "https://r.assistant-ui.com/{name}.json", + "@tool-ui": "https://tool-ui.com/r/{name}.json" + } +} diff --git a/apps/docs/components/assistant-ui/markdown-text.tsx b/apps/docs/components/assistant-ui/markdown-text.tsx new file mode 100644 index 0000000..dc27a4a --- /dev/null +++ b/apps/docs/components/assistant-ui/markdown-text.tsx @@ -0,0 +1,261 @@ +"use client"; + +import "@assistant-ui/react-markdown/styles/dot.css"; + +import { + type CodeHeaderProps, + MarkdownTextPrimitive, + unstable_memoizeMarkdownComponents as memoizeMarkdownComponents, + useIsMarkdownCodeBlock, +} from "@assistant-ui/react-markdown"; +import remarkGfm from "remark-gfm"; +import { type FC, memo, useState } from "react"; +import { CheckIcon, CopyIcon } from "lucide-react"; + +import { SyntaxHighlighter } from "@/components/assistant-ui/shiki-highlighter"; +import { TooltipIconButton } from "@/components/assistant-ui/tooltip-icon-button"; +import { cn } from "@/lib/utils"; + +const MarkdownTextImpl = () => { + return ( + <MarkdownTextPrimitive + remarkPlugins={[remarkGfm]} + className="aui-md" + components={defaultComponents} + defer + /> + ); +}; + +export const MarkdownText = memo(MarkdownTextImpl); + +const CodeHeader: FC<CodeHeaderProps> = ({ language, code }) => { + const { isCopied, copyToClipboard } = useCopyToClipboard(); + const onCopy = () => { + if (!code || isCopied) return; + copyToClipboard(code); + }; + + return ( + <div className="aui-code-header-root border-border/50 bg-muted/50 mt-3 flex items-center justify-between rounded-t-xl border border-b-0 px-3.5 py-1.5 text-xs"> + <span className="aui-code-header-language text-muted-foreground font-medium lowercase"> + {language} + </span> + <TooltipIconButton tooltip="Copy" onClick={onCopy}> + {!isCopied && ( + <CopyIcon className="animate-in zoom-in-75 fade-in duration-150" /> + )} + {isCopied && ( + <CheckIcon className="animate-in zoom-in-50 fade-in duration-200 ease-out" /> + )} + </TooltipIconButton> + </div> + ); +}; + +const useCopyToClipboard = ({ + copiedDuration = 3000, +}: { + copiedDuration?: number; +} = {}) => { + const [isCopied, setIsCopied] = useState<boolean>(false); + + const copyToClipboard = (value: string) => { + if (!value || typeof navigator === "undefined" || !navigator.clipboard) { + return; + } + + navigator.clipboard.writeText(value).then( + () => { + setIsCopied(true); + setTimeout(() => setIsCopied(false), copiedDuration); + }, + () => {}, + ); + }; + + return { isCopied, copyToClipboard }; +}; + +const defaultComponents = memoizeMarkdownComponents({ + SyntaxHighlighter, + h1: ({ className, ...props }) => ( + <h1 + className={cn( + "aui-md-h1 mt-5 mb-2 scroll-m-20 text-xl font-semibold first:mt-0 last:mb-0", + className, + )} + {...props} + /> + ), + h2: ({ className, ...props }) => ( + <h2 + className={cn( + "aui-md-h2 mt-5 mb-2 scroll-m-20 text-lg font-semibold first:mt-0 last:mb-0", + className, + )} + {...props} + /> + ), + h3: ({ className, ...props }) => ( + <h3 + className={cn( + "aui-md-h3 mt-4 mb-1.5 scroll-m-20 text-base font-semibold first:mt-0 last:mb-0", + className, + )} + {...props} + /> + ), + h4: ({ className, ...props }) => ( + <h4 + className={cn( + "aui-md-h4 mt-3.5 mb-1 scroll-m-20 text-base font-medium first:mt-0 last:mb-0", + className, + )} + {...props} + /> + ), + h5: ({ className, ...props }) => ( + <h5 + className={cn( + "aui-md-h5 mt-3 mb-1 text-sm font-semibold first:mt-0 last:mb-0", + className, + )} + {...props} + /> + ), + h6: ({ className, ...props }) => ( + <h6 + className={cn( + "aui-md-h6 mt-3 mb-1 text-sm font-medium first:mt-0 last:mb-0", + className, + )} + {...props} + /> + ), + p: ({ className, ...props }) => ( + <p + className={cn( + "aui-md-p my-3 leading-relaxed first:mt-0 last:mb-0", + className, + )} + {...props} + /> + ), + a: ({ className, ...props }) => ( + <a + className={cn( + "aui-md-a text-primary hover:text-primary/80 underline underline-offset-2", + className, + )} + {...props} + /> + ), + blockquote: ({ className, ...props }) => ( + <blockquote + className={cn( + "aui-md-blockquote border-muted-foreground/30 text-muted-foreground my-3 border-s-2 ps-4", + className, + )} + {...props} + /> + ), + ul: ({ className, ...props }) => ( + <ul + className={cn( + "aui-md-ul marker:text-muted-foreground my-3 ms-5 list-disc [&>li]:mt-1", + className, + )} + {...props} + /> + ), + ol: ({ className, ...props }) => ( + <ol + className={cn( + "aui-md-ol marker:text-muted-foreground my-3 ms-5 list-decimal [&>li]:mt-1", + className, + )} + {...props} + /> + ), + hr: ({ className, ...props }) => ( + <hr + className={cn("aui-md-hr border-muted-foreground/20 my-3", className)} + {...props} + /> + ), + table: ({ className, ...props }) => ( + <table + className={cn( + "aui-md-table my-3 w-full border-separate border-spacing-0 overflow-y-auto", + className, + )} + {...props} + /> + ), + th: ({ className, ...props }) => ( + <th + className={cn( + "aui-md-th bg-muted px-3 py-1.5 text-start font-medium first:rounded-ss-lg last:rounded-se-lg [[align=center]]:text-center [[align=right]]:text-right", + className, + )} + {...props} + /> + ), + td: ({ className, ...props }) => ( + <td + className={cn( + "aui-md-td border-muted-foreground/20 border-s border-b px-3 py-1.5 text-start last:border-e [[align=center]]:text-center [[align=right]]:text-right", + className, + )} + {...props} + /> + ), + tr: ({ className, ...props }) => ( + <tr + className={cn( + "aui-md-tr m-0 border-b p-0 first:border-t [&:last-child>td:first-child]:rounded-es-lg [&:last-child>td:last-child]:rounded-ee-lg", + className, + )} + {...props} + /> + ), + li: ({ className, ...props }) => ( + <li className={cn("aui-md-li leading-relaxed", className)} {...props} /> + ), + strong: ({ className, ...props }) => ( + <strong + className={cn("aui-md-strong font-semibold", className)} + {...props} + /> + ), + sup: ({ className, ...props }) => ( + <sup + className={cn("aui-md-sup [&>a]:text-xs [&>a]:no-underline", className)} + {...props} + /> + ), + pre: ({ className, ...props }) => ( + <pre + className={cn( + "aui-md-pre border-border/50 bg-muted/30 overflow-x-auto rounded-t-none rounded-b-xl border border-t-0 p-3.5 text-[13px] leading-relaxed", + className, + )} + {...props} + /> + ), + code: function Code({ className, ...props }) { + const isCodeBlock = useIsMarkdownCodeBlock(); + return ( + <code + className={cn( + !isCodeBlock && + "aui-md-inline-code bg-muted rounded-md px-1.5 py-0.5 font-mono text-[0.85em]", + className, + )} + {...props} + /> + ); + }, + CodeHeader, +}); diff --git a/apps/docs/components/blog/blog-toc.tsx b/apps/docs/components/blog/blog-toc.tsx new file mode 100644 index 0000000..e3ad660 --- /dev/null +++ b/apps/docs/components/blog/blog-toc.tsx @@ -0,0 +1,117 @@ +"use client"; + +import { useEffect, useRef, useState, type ReactNode } from "react"; +import { cn } from "@/lib/utils"; + +type TOCItem = { + title: ReactNode; + url: string; + depth: number; +}; + +const MAX_LINE_WIDTH = 36; // h2 active width, used as fixed container width + +function getLineWidth(depth: number, isActive: boolean): number { + const base = depth <= 2 ? 24 : depth === 3 ? 16 : 10; + return isActive ? base + 12 : base; +} + +export function BlogTOC({ items }: { items: TOCItem[] }) { + const [activeId, setActiveId] = useState<string | null>(null); + const [isHovered, setIsHovered] = useState(false); + const listRef = useRef<HTMLElement>(null); + const isClickScrolling = useRef(false); + const clickTimer = useRef<ReturnType<typeof setTimeout>>(null); + + useEffect(() => { + if (items.length === 0) return; + + const headingIds = items.map((item) => item.url.slice(1)); + + const observer = new IntersectionObserver( + (entries) => { + if (isClickScrolling.current) return; + for (const entry of entries) { + if (entry.isIntersecting) { + setActiveId(entry.target.id); + break; + } + } + }, + { + rootMargin: "-80px 0px -70% 0px", + threshold: 0, + }, + ); + + for (const id of headingIds) { + const element = document.getElementById(id); + if (element) { + observer.observe(element); + } + } + + return () => observer.disconnect(); + }, [items]); + + const handleClick = (id: string) => { + setActiveId(id); + isClickScrolling.current = true; + if (clickTimer.current) clearTimeout(clickTimer.current); + clickTimer.current = setTimeout(() => { + isClickScrolling.current = false; + }, 800); + }; + + if (items.length === 0) return null; + + return ( + <nav + ref={listRef} + className="fixed top-1/2 right-6 z-40 -translate-y-1/2 max-lg:hidden" + onMouseEnter={() => setIsHovered(true)} + onMouseLeave={() => setIsHovered(false)} + > + <ul className="flex flex-col" style={{ width: MAX_LINE_WIDTH }}> + {items.map((item) => { + const id = item.url.slice(1); + const isActive = activeId === id; + const lineWidth = getLineWidth(item.depth, isActive); + + return ( + <li key={item.url}> + <a + href={item.url} + onClick={() => handleClick(id)} + className="group relative flex h-6 items-center justify-end" + > + <div + className={cn( + "h-px shrink-0 rounded-full transition-all duration-200", + isActive + ? "bg-foreground" + : "bg-foreground/20 group-hover:bg-foreground/40", + )} + style={{ width: lineWidth }} + /> + <span + className={cn( + "absolute right-full mr-3 text-[12px] leading-none whitespace-nowrap transition-all duration-200", + isHovered + ? "translate-x-0 opacity-100" + : "pointer-events-none -translate-x-2 opacity-0", + isActive + ? "text-foreground font-medium" + : "text-muted-foreground group-hover:text-foreground", + )} + > + {item.title} + </span> + </a> + </li> + ); + })} + </ul> + </nav> + ); +} diff --git a/apps/docs/components/builder/builder-chat-sidebar.tsx b/apps/docs/components/builder/builder-chat-sidebar.tsx new file mode 100644 index 0000000..36892f1 --- /dev/null +++ b/apps/docs/components/builder/builder-chat-sidebar.tsx @@ -0,0 +1,267 @@ +"use client"; + +import { + createContext, + useCallback, + useContext, + useEffect, + useMemo, + useRef, + type ReactNode, +} from "react"; +import { + AssistantRuntimeProvider, + ThreadPrimitive, + ComposerPrimitive, + MessagePrimitive, + ErrorPrimitive, + AuiIf, + useAuiState, + Tools, + Suggestions, +} from "@assistant-ui/react"; +import { + useChatRuntime, + AssistantChatTransport, +} from "@assistant-ui/react-ai-sdk"; +import { lastAssistantMessageIsCompleteWithToolCalls } from "ai"; +import { SendHorizontal, SquareIcon } from "lucide-react"; +import { + createPlaygroundChatToolkit, + type PartialBuilderConfig, +} from "@/lib/playground-chat-toolkit"; +import { useAui } from "@assistant-ui/store"; +import type { BuilderConfig } from "./types"; +import { applyDiff } from "@/lib/playground-url-state"; + +const PLAYGROUND_SUGGESTIONS = [ + { + title: "Make it look like", + label: "ChatGPT", + prompt: "Make it look like ChatGPT", + }, + { + title: "Switch to", + label: "dark mode", + prompt: "Switch to dark mode with blue accents", + }, + { + title: "Enable all", + label: "features", + prompt: "Enable all features like attachments, avatars, and feedback", + }, +]; + +// --- Context (shares runtime between mobile sheet + desktop sidebar) --- + +type PlaygroundChatContextValue = { + runtime: ReturnType<typeof useChatRuntime>; + aui: ReturnType<typeof useAui>; +}; + +const PlaygroundChatContext = createContext<PlaygroundChatContextValue | null>( + null, +); + +function usePlaygroundChat() { + const ctx = useContext(PlaygroundChatContext); + if (!ctx) + throw new Error( + "usePlaygroundChat must be used within PlaygroundChatProvider", + ); + return ctx; +} + +// --- Provider (plain context, no AssistantRuntimeProvider) --- + +interface PlaygroundChatProviderProps { + config: BuilderConfig; + setConfig: (config: BuilderConfig) => void; + children: ReactNode; +} + +export function PlaygroundChatProvider({ + config, + setConfig, + children, +}: PlaygroundChatProviderProps) { + const configRef = useRef(config); + configRef.current = config; + + const onConfigUpdate = useCallback( + (update: PartialBuilderConfig) => { + const { customCSS, ...rest } = update; + const merged = applyDiff( + rest as Record<string, unknown>, + configRef.current, + ); + if (customCSS !== undefined) { + merged.customCSS = customCSS + ? [configRef.current.customCSS, customCSS].filter(Boolean).join("\n") + : ""; + } + setConfig(merged); + }, + [setConfig], + ); + + const toolkit = useMemo( + () => createPlaygroundChatToolkit(onConfigUpdate), + [onConfigUpdate], + ); + + const transport = useMemo( + () => + new AssistantChatTransport({ + api: "/api/playground-chat", + prepareSendMessagesRequest: async (options) => ({ + body: { + ...options.body, + messages: options.messages, + builderConfig: configRef.current, + }, + }), + }), + [], + ); + + const runtime = useChatRuntime({ + transport, + sendAutomaticallyWhen: lastAssistantMessageIsCompleteWithToolCalls, + }); + + const aui = useAui( + { + tools: Tools({ toolkit }), + suggestions: Suggestions(PLAYGROUND_SUGGESTIONS), + }, + { parent: null }, + ); + + const value = useMemo(() => ({ runtime, aui }), [runtime, aui]); + + return ( + <PlaygroundChatContext.Provider value={value}> + {children} + </PlaygroundChatContext.Provider> + ); +} + +// --- Thread UI (each mount creates its own AssistantRuntimeProvider scope) --- + +export function PlaygroundChatThread({ + onRunningChange, +}: { + onRunningChange?: (isRunning: boolean) => void; +}) { + const { runtime, aui } = usePlaygroundChat(); + + return ( + <AssistantRuntimeProvider aui={aui} runtime={runtime}> + {onRunningChange && <RunningObserver onRunningChange={onRunningChange} />} + <ThreadPrimitive.Root className="flex flex-1 flex-col overflow-hidden"> + <ThreadPrimitive.Viewport className="flex flex-1 scrollbar-none flex-col gap-3 overflow-y-auto px-3 pt-3"> + <ThreadPrimitive.Empty> + <div className="flex flex-1 flex-col items-center justify-center gap-4 py-8 text-center"> + <div> + <p className="text-sm font-medium"> + Describe how you want your chat to look + </p> + <p className="text-muted-foreground text-xs"> + e.g. "make it look like ChatGPT" or "use dark + mode with rounded corners" + </p> + </div> + <div className="flex w-full max-w-56 flex-col gap-2"> + {PLAYGROUND_SUGGESTIONS.map((s) => ( + <ThreadPrimitive.Suggestion + key={s.prompt} + prompt={s.prompt} + send + className="hover:bg-muted rounded-lg border px-3 py-2 text-left text-xs transition-colors" + > + <span className="font-medium">{s.title}</span>{" "} + <span className="text-muted-foreground">{s.label}</span> + </ThreadPrimitive.Suggestion> + ))} + </div> + </div> + </ThreadPrimitive.Empty> + + <ThreadPrimitive.Messages + components={{ UserMessage, AssistantMessage }} + /> + + <ThreadPrimitive.ViewportFooter className="bg-background sticky bottom-0 mt-auto"> + <Composer /> + </ThreadPrimitive.ViewportFooter> + </ThreadPrimitive.Viewport> + </ThreadPrimitive.Root> + </AssistantRuntimeProvider> + ); +} + +/** Bridges runtime state to the parent; resets on unmount. */ +function RunningObserver({ + onRunningChange, +}: { + onRunningChange: (isRunning: boolean) => void; +}) { + const isRunning = useAuiState((s) => s.thread.isRunning); + useEffect(() => { + onRunningChange(isRunning); + return () => onRunningChange(false); + }, [isRunning, onRunningChange]); + return null; +} + +function Composer() { + return ( + <ComposerPrimitive.Root className="py-2"> + <div className="border-border bg-background focus-within:border-foreground/60 rounded-lg border transition-colors"> + <ComposerPrimitive.Input asChild> + <textarea + placeholder="Describe a change..." + className="placeholder:text-muted-foreground field-sizing-content max-h-32 w-full resize-none bg-transparent px-3 pt-2.5 pb-2 text-sm leading-5 focus:outline-none" + rows={1} + /> + </ComposerPrimitive.Input> + <div className="flex items-center justify-end px-1.5 pb-1.5"> + <AuiIf condition={(s) => !s.thread.isRunning}> + <ComposerPrimitive.Send className="text-muted-foreground hover:text-foreground rounded-md p-1 transition-colors disabled:opacity-30"> + <SendHorizontal className="size-4" /> + </ComposerPrimitive.Send> + </AuiIf> + <AuiIf condition={(s) => s.thread.isRunning}> + <ComposerPrimitive.Cancel className="text-muted-foreground hover:text-foreground rounded-md p-1 transition-colors"> + <SquareIcon className="size-3.5 fill-current" /> + </ComposerPrimitive.Cancel> + </AuiIf> + </div> + </div> + </ComposerPrimitive.Root> + ); +} + +function UserMessage() { + return ( + <MessagePrimitive.Root className="flex justify-end"> + <div className="bg-foreground/5 max-w-[85%] rounded-lg px-3 py-2 text-sm"> + <MessagePrimitive.Content /> + </div> + </MessagePrimitive.Root> + ); +} + +function AssistantMessage() { + return ( + <MessagePrimitive.Root className="px-3 py-2 text-sm"> + <MessagePrimitive.Content /> + <MessagePrimitive.Error> + <ErrorPrimitive.Root className="border-destructive bg-destructive/10 text-destructive dark:bg-destructive/5 mt-2 rounded-md border p-2 text-xs dark:text-red-200"> + <ErrorPrimitive.Message className="line-clamp-2" /> + </ErrorPrimitive.Root> + </MessagePrimitive.Error> + </MessagePrimitive.Root> + ); +} diff --git a/apps/docs/components/builder/builder-code-output.tsx b/apps/docs/components/builder/builder-code-output.tsx new file mode 100644 index 0000000..2c1ef56 --- /dev/null +++ b/apps/docs/components/builder/builder-code-output.tsx @@ -0,0 +1,590 @@ +"use client"; + +import { useState } from "react"; +import ShikiHighlighter from "react-shiki"; +import { CheckIcon, CopyIcon } from "lucide-react"; + +import type { BuilderConfig } from "./types"; +import { + BORDER_RADIUS_CLASS, + FONT_SIZE_CLASS, + MESSAGE_SPACING_CLASS, + isLightColor, +} from "@/lib/builder-utils"; +import { analytics } from "@/lib/analytics"; + +interface BuilderCodeOutputProps { + config: BuilderConfig; +} + +export function BuilderCodeOutput({ config }: BuilderCodeOutputProps) { + const [copied, setCopied] = useState(false); + + const componentCode = generateComponentCode(config); + + const handleCopy = async () => { + analytics.builder.codeCopied(); + await navigator.clipboard.writeText(componentCode); + setCopied(true); + setTimeout(() => setCopied(false), 2000); + }; + + return ( + <div className="flex h-full flex-col overflow-hidden"> + <div className="flex shrink-0 items-center justify-between px-3 py-2"> + <span className="text-sm font-medium">thread.tsx</span> + <button + type="button" + onClick={handleCopy} + className="text-muted-foreground hover:text-foreground flex items-center gap-1.5 rounded-md px-2 py-1 text-xs transition-colors" + > + {copied ? ( + <> + <CheckIcon className="size-3.5" /> + Copied + </> + ) : ( + <> + <CopyIcon className="size-3.5" /> + Copy + </> + )} + </button> + </div> + + <div className="min-h-0 flex-1 overflow-auto text-xs leading-relaxed [&_pre]:m-0! [&_pre]:bg-transparent! [&_pre]:p-0!"> + <ShikiHighlighter + language="tsx" + theme={{ dark: "vitesse-dark", light: "vitesse-light" }} + addDefaultStyles={false} + showLanguage={false} + defaultColor="light-dark()" + > + {componentCode.trim()} + </ShikiHighlighter> + </div> + </div> + ); +} + +function generateComponentCode(config: BuilderConfig): string { + const { components, styles } = config; + + const iconImports = generateIconImports(config); + + const externalImports = [ + iconImports, + `import {`, + ` ActionBarPrimitive,`, + ` AuiIf,`, + components.branchPicker && ` BranchPickerPrimitive,`, + ` ComposerPrimitive,`, + ` ErrorPrimitive,`, + ` MessagePrimitive,`, + ` ThreadPrimitive,`, + `} from "@assistant-ui/react";`, + components.markdown && + components.typingIndicator === "dot" && + `import "@assistant-ui/react-markdown/styles/dot.css";`, + ] + .filter(Boolean) + .join("\n"); + + const internalImports = [ + `import { Button } from "@/components/ui/button";`, + `import { TooltipIconButton } from "@/components/assistant-ui/tooltip-icon-button";`, + components.markdown && + `import { MarkdownText } from "@/components/assistant-ui/markdown-text";`, + components.markdown && + `import { ToolFallback } from "@/components/assistant-ui/tool-fallback";`, + components.reasoning && + `import { Reasoning, ReasoningGroup } from "@/components/assistant-ui/reasoning";`, + components.sources && + `import { Sources } from "@/components/assistant-ui/sources";`, + components.attachments && + `import { + ComposerAddAttachment, + ComposerAttachments, + UserMessageAttachments, +} from "@/components/assistant-ui/attachment";`, + `import { cn } from "@/lib/utils";`, + ] + .filter(Boolean) + .join("\n"); + + const borderRadiusClass = BORDER_RADIUS_CLASS[styles.borderRadius]; + const fontSizeClass = FONT_SIZE_CLASS[styles.fontSize]; + const messageSpacingClass = MESSAGE_SPACING_CLASS[styles.messageSpacing]; + const accentColor = styles.colors.accent.light; + const accentForeground = isLightColor(accentColor) ? "#000000" : "#ffffff"; + + const cssVariables = ` + "--thread-max-width": "${styles.maxWidth}", + "--accent-color": "${accentColor}", + "--accent-foreground": "${accentForeground}",`; + + const fontFamilyStyle = + styles.fontFamily !== "system-ui" + ? `\n fontFamily: "${styles.fontFamily}",` + : ""; + + const threadComponent = ` +export function Thread() { + return ( + <ThreadPrimitive.Root + className="flex h-full flex-col bg-background ${fontSizeClass}" + style={{${cssVariables}${fontFamilyStyle} + }} + > + <ThreadPrimitive.Viewport + turnAnchor="top" + className="relative flex flex-1 flex-col overflow-x-auto overflow-y-scroll scroll-smooth px-4 pt-4" + > + ${ + components.threadWelcome + ? `<AuiIf condition={(s) => s.thread.isEmpty}> + <ThreadWelcome /> + </AuiIf>` + : "" + } + + <ThreadPrimitive.Messages + components={{ + UserMessage,${ + components.editMessage + ? ` + EditComposer,` + : "" + } + AssistantMessage, + }} + /> + + <ThreadPrimitive.ViewportFooter className="sticky bottom-0 mx-auto mt-auto flex w-full max-w-[var(--thread-max-width)] flex-col gap-4 overflow-visible rounded-t-3xl bg-background pb-4"> + ${components.scrollToBottom ? "<ThreadScrollToBottom />" : ""} + <Composer /> + </ThreadPrimitive.ViewportFooter> + </ThreadPrimitive.Viewport> + </ThreadPrimitive.Root> + ); +}`; + + const welcomeComponent = components.threadWelcome + ? ` +function ThreadWelcome() { + return ( + <div className="mx-auto my-auto flex w-full max-w-[var(--thread-max-width)] flex-grow flex-col"> + <div className="flex w-full flex-grow flex-col items-center justify-center"> + <div className="flex size-full flex-col justify-center px-8"> + <div className="text-2xl font-semibold">Hello there!</div> + <div className="text-2xl text-muted-foreground/65"> + How can I help you today? + </div> + </div> + </div> + ${ + components.suggestions + ? `<div className="grid w-full gap-2 pb-4 md:grid-cols-2"> + {/* Add your suggestions here */} + <ThreadPrimitive.Suggestion prompt="What's the weather in San Francisco?" asChild> + <Button variant="ghost" className="h-auto w-full flex-col items-start justify-start gap-1 border ${borderRadiusClass} px-5 py-4 text-left text-sm"> + <span className="font-medium">What's the weather</span> + <span className="text-muted-foreground">in San Francisco?</span> + </Button> + </ThreadPrimitive.Suggestion> + <ThreadPrimitive.Suggestion prompt="Explain React hooks like useState" asChild> + <Button variant="ghost" className="h-auto w-full flex-col items-start justify-start gap-1 border ${borderRadiusClass} px-5 py-4 text-left text-sm"> + <span className="font-medium">Explain React hooks</span> + <span className="text-muted-foreground">like useState</span> + </Button> + </ThreadPrimitive.Suggestion> + </div>` + : "" + } + </div> + ); +}` + : ""; + + const composerComponent = ` +function Composer() { + return ( + <ComposerPrimitive.Root className="relative flex w-full flex-col"> + <ComposerPrimitive.AttachmentDropzone className="flex w-full flex-col ${borderRadiusClass} border border-input bg-background px-1 pt-2 outline-none transition-colors has-[textarea:focus-visible]:border-foreground/60 data-[dragging=true]:border-ring data-[dragging=true]:border-dashed data-[dragging=true]:bg-accent/50"> + ${components.attachments ? "<ComposerAttachments />" : ""} + <ComposerPrimitive.Input + placeholder="Send a message..." + className="mb-1 max-h-32 min-h-14 w-full resize-none bg-transparent px-4 pt-2 pb-3 text-sm outline-none placeholder:text-muted-foreground focus-visible:ring-0" + rows={1} + autoFocus + aria-label="Message input" + /> + <ComposerAction /> + </ComposerPrimitive.AttachmentDropzone> + </ComposerPrimitive.Root> + ); +} + +function ComposerAction() { + return ( + <div className="relative mx-2 mb-2 flex items-center justify-between"> + ${components.attachments ? "<ComposerAddAttachment />" : "<div />"} + + <AuiIf condition={(s) => !s.thread.isRunning}> + <ComposerPrimitive.Send asChild> + <TooltipIconButton + tooltip="Send message" + side="bottom" + variant="default" + size="icon" + className="size-8 rounded-full" + style={{ + backgroundColor: "var(--accent-color)", + color: "var(--accent-foreground)", + }} + aria-label="Send message" + > + <ArrowUpIcon className="size-4" /> + </TooltipIconButton> + </ComposerPrimitive.Send> + </AuiIf> + + <AuiIf condition={(s) => s.thread.isRunning}> + <ComposerPrimitive.Cancel asChild> + <Button + type="button" + variant="default" + size="icon" + className="size-8 rounded-full" + style={{ + backgroundColor: "var(--accent-color)", + color: "var(--accent-foreground)", + }} + aria-label="Stop generating" + > + <SquareIcon className="size-3 fill-current" /> + </Button> + </ComposerPrimitive.Cancel> + </AuiIf> + </div> + ); +}`; + + const scrollToBottomComponent = components.scrollToBottom + ? ` +function ThreadScrollToBottom() { + return ( + <ThreadPrimitive.ScrollToBottom asChild> + <TooltipIconButton + tooltip="Scroll to bottom" + variant="outline" + className="absolute -top-12 z-10 self-center rounded-full p-4 disabled:invisible" + > + <ArrowDownIcon /> + </TooltipIconButton> + </ThreadPrimitive.ScrollToBottom> + ); +}` + : ""; + + const animationClass = styles.animations + ? " fade-in slide-in-from-bottom-1 animate-in duration-150" + : ""; + + const userMessageComponent = ` +function UserMessage() { + return ( + <MessagePrimitive.Root + className="mx-auto grid w-full max-w-[var(--thread-max-width)] auto-rows-auto grid-cols-[minmax(72px,1fr)_auto] content-start gap-y-2 px-2 ${messageSpacingClass}${animationClass}" + data-role="user" + > + ${components.attachments ? "<UserMessageAttachments />" : ""} + + <div className="relative col-start-2 min-w-0"> + <div className="${borderRadiusClass} bg-muted px-4 py-2.5 break-words text-foreground"> + <MessagePrimitive.Parts /> + </div> + ${ + components.editMessage + ? `<div className="absolute top-1/2 left-0 -translate-x-full -translate-y-1/2 pr-2"> + <UserActionBar /> + </div>` + : "" + } + </div> + + ${components.branchPicker ? `<BranchPicker className="col-span-full col-start-1 row-start-3 -mr-1 justify-end" />` : ""} + </MessagePrimitive.Root> + ); +} + +${ + components.editMessage + ? `function UserActionBar() { + return ( + <ActionBarPrimitive.Root + hideWhenRunning + autohide="not-last" + className="flex flex-col items-end" + > + <ActionBarPrimitive.Edit asChild> + <TooltipIconButton tooltip="Edit" className="p-4"> + <PencilIcon /> + </TooltipIconButton> + </ActionBarPrimitive.Edit> + </ActionBarPrimitive.Root> + ); +}` + : "" +}`; + + const assistantMessageRootClass = `relative mx-auto w-full max-w-[var(--thread-max-width)] ${messageSpacingClass}${animationClass}`; + + // Build MessagePrimitive.Parts components object + const partsComponents: string[] = []; + if (components.markdown) { + partsComponents.push(`Text: MarkdownText`); + partsComponents.push(`tools: { Fallback: ToolFallback }`); + } + if (components.reasoning) { + partsComponents.push(`Reasoning`); + partsComponents.push(`ReasoningGroup`); + } + if (components.sources) { + partsComponents.push(`Source: Sources`); + } + + const partsComponentsStr = + partsComponents.length > 0 + ? ` + components={{ + ${partsComponents.join(",\n ")}, + }}` + : ""; + + const assistantMessageComponent = ` +function AssistantMessage() { + return ( + <MessagePrimitive.Root + className="${assistantMessageRootClass}" + data-role="assistant" + > + ${ + components.avatar + ? `<div className="flex size-8 shrink-0 items-center justify-center rounded-full bg-primary/10"> + <BotIcon className="size-4" /> + </div>` + : "" + } + <div className="break-words px-2 leading-relaxed text-foreground"> + <MessagePrimitive.Parts${partsComponentsStr} /> + <MessageError />${ + components.loadingIndicator !== "none" + ? ` + <AuiIf condition={(s) => s.thread.isRunning && s.message.content.length === 0}> + <div className="flex items-center gap-2 text-muted-foreground"> + <LoaderIcon className="size-4 animate-spin" />${ + components.loadingIndicator === "text" + ? ` + <span className="text-sm">${components.loadingText}</span>` + : "" + } + </div> + </AuiIf>` + : "" + } + </div> + + <div className="mt-1 ml-2 flex min-h-6 items-center"> + ${components.branchPicker ? "<BranchPicker />" : ""} + <AssistantActionBar /> + </div> + ${ + components.followUpSuggestions + ? ` + <AuiIf condition={(s) => !s.thread.isRunning}> + <div className="mt-4 flex flex-wrap gap-2"> + <ThreadPrimitive.Suggestion + prompt="Tell me more" + className="rounded-full border bg-background px-3 py-1 text-sm hover:bg-muted" + > + Tell me more + </ThreadPrimitive.Suggestion> + <ThreadPrimitive.Suggestion + prompt="Can you explain differently?" + className="rounded-full border bg-background px-3 py-1 text-sm hover:bg-muted" + > + Explain differently + </ThreadPrimitive.Suggestion> + </div> + </AuiIf>` + : "" + } + </MessagePrimitive.Root> + ); +} + +function MessageError() { + return ( + <MessagePrimitive.Error> + <ErrorPrimitive.Root className="mt-2 rounded-md border border-destructive bg-destructive/10 p-3 text-destructive text-sm dark:bg-destructive/5 dark:text-red-200"> + <ErrorPrimitive.Message className="line-clamp-2" /> + </ErrorPrimitive.Root> + </MessagePrimitive.Error> + ); +}`; + + const feedbackButtons = components.actionBar.feedback + ? ` + <ActionBarPrimitive.FeedbackPositive asChild> + <TooltipIconButton tooltip="Good response"> + <ThumbsUpIcon /> + </TooltipIconButton> + </ActionBarPrimitive.FeedbackPositive> + <ActionBarPrimitive.FeedbackNegative asChild> + <TooltipIconButton tooltip="Bad response"> + <ThumbsDownIcon /> + </TooltipIconButton> + </ActionBarPrimitive.FeedbackNegative>` + : ""; + + const actionBarComponent = ` +function AssistantActionBar() { + return ( + <ActionBarPrimitive.Root + hideWhenRunning + autohide="not-last" + className="-ml-1 flex gap-1 text-muted-foreground" + > + ${ + components.actionBar.copy + ? `<ActionBarPrimitive.Copy asChild> + <TooltipIconButton tooltip="Copy"> + <AuiIf condition={(s) => s.message.isCopied}> + <CheckIcon /> + </AuiIf> + <AuiIf condition={(s) => !s.message.isCopied}> + <CopyIcon /> + </AuiIf> + </TooltipIconButton> + </ActionBarPrimitive.Copy>` + : "" + } + <ActionBarPrimitive.ExportMarkdown asChild> + <TooltipIconButton tooltip="Export as Markdown"> + <DownloadIcon /> + </TooltipIconButton> + </ActionBarPrimitive.ExportMarkdown> + ${ + components.actionBar.reload + ? `<ActionBarPrimitive.Reload asChild> + <TooltipIconButton tooltip="Refresh"> + <RefreshCwIcon /> + </TooltipIconButton> + </ActionBarPrimitive.Reload>` + : "" + } + ${ + components.actionBar.speak + ? `<ActionBarPrimitive.Speak asChild> + <TooltipIconButton tooltip="Read aloud"> + <Volume2Icon /> + </TooltipIconButton> + </ActionBarPrimitive.Speak>` + : "" + }${feedbackButtons} + </ActionBarPrimitive.Root> + ); +}`; + + const branchPickerComponent = components.branchPicker + ? ` +function BranchPicker({ className, ...rest }: { className?: string }) { + return ( + <BranchPickerPrimitive.Root + hideWhenSingleBranch + className={cn("mr-2 -ml-2 inline-flex items-center text-xs text-muted-foreground", className)} + {...rest} + > + <BranchPickerPrimitive.Previous asChild> + <TooltipIconButton tooltip="Previous"> + <ChevronLeftIcon /> + </TooltipIconButton> + </BranchPickerPrimitive.Previous> + <span className="font-medium"> + <BranchPickerPrimitive.Number /> / <BranchPickerPrimitive.Count /> + </span> + <BranchPickerPrimitive.Next asChild> + <TooltipIconButton tooltip="Next"> + <ChevronRightIcon /> + </TooltipIconButton> + </BranchPickerPrimitive.Next> + </BranchPickerPrimitive.Root> + ); +}` + : ""; + + const editComposerComponent = components.editMessage + ? ` +function EditComposer() { + return ( + <MessagePrimitive.Root className="mx-auto flex w-full max-w-[var(--thread-max-width)] flex-col px-2 py-3"> + <ComposerPrimitive.Root className="ml-auto flex w-full max-w-[85%] flex-col ${borderRadiusClass} bg-muted"> + <ComposerPrimitive.Input + className="min-h-14 w-full resize-none bg-transparent p-4 text-foreground text-sm outline-none" + autoFocus + /> + <div className="mx-3 mb-3 flex items-center gap-2 self-end"> + <ComposerPrimitive.Cancel asChild> + <Button variant="ghost" size="sm">Cancel</Button> + </ComposerPrimitive.Cancel> + <ComposerPrimitive.Send asChild> + <Button size="sm">Update</Button> + </ComposerPrimitive.Send> + </div> + </ComposerPrimitive.Root> + </MessagePrimitive.Root> + ); +}` + : ""; + + const allImports = `"use client"; + +${externalImports} + +${internalImports}`; + + return [ + allImports, + threadComponent, + welcomeComponent, + composerComponent, + scrollToBottomComponent, + userMessageComponent, + editComposerComponent, + assistantMessageComponent, + actionBarComponent, + branchPickerComponent, + ] + .filter(Boolean) + .join("\n"); +} + +function generateIconImports(config: BuilderConfig): string { + const { components } = config; + const icons: string[] = ["ArrowUpIcon", "DownloadIcon", "SquareIcon"]; + + if (components.scrollToBottom) icons.push("ArrowDownIcon"); + if (components.editMessage) icons.push("PencilIcon"); + if (components.branchPicker) + icons.push("ChevronLeftIcon", "ChevronRightIcon"); + if (components.actionBar.copy) icons.push("CheckIcon", "CopyIcon"); + if (components.actionBar.reload) icons.push("RefreshCwIcon"); + if (components.actionBar.speak) icons.push("Volume2Icon"); + if (components.actionBar.feedback) + icons.push("ThumbsUpIcon", "ThumbsDownIcon"); + if (components.avatar) icons.push("BotIcon", "UserIcon"); + if (components.loadingIndicator !== "none") icons.push("LoaderIcon"); + + return `import {\n ${[...new Set(icons)].sort().join(",\n ")},\n} from "lucide-react";`; +} diff --git a/apps/docs/components/builder/builder-controls.tsx b/apps/docs/components/builder/builder-controls.tsx new file mode 100644 index 0000000..3a34a88 --- /dev/null +++ b/apps/docs/components/builder/builder-controls.tsx @@ -0,0 +1,821 @@ +"use client"; + +import { CircleAlertIcon, SunIcon, MoonIcon } from "lucide-react"; + +import { Select } from "@/components/assistant-ui/select"; +import { Switch } from "@/components/ui/switch"; +import { + ThemeColorPicker, + OptionalThemeColorPicker, +} from "@/components/shared/color-picker"; +import { + Tooltip, + TooltipContent, + TooltipTrigger, +} from "@/components/ui/radix/tooltip"; +import { + BORDER_RADIUSES, + CODE_HIGHLIGHT_THEMES, + DEFAULT_COLORS, + FONT_FAMILIES, + FONT_SIZES, + LOADING_INDICATORS, + MAX_WIDTHS, + MESSAGE_SPACINGS, + TYPING_INDICATORS, + USER_MESSAGE_POSITIONS, + type BuilderConfig, + type BorderRadius, + type CodeHighlightTheme, + type FontSize, + type LoadingIndicator, + type MessageSpacing, + type ThemeColor, + type TypingIndicator, +} from "./types"; +import { PRESETS } from "./presets"; +import { analytics } from "@/lib/analytics"; + +interface BuilderControlsProps { + config: BuilderConfig; + onChange: (config: BuilderConfig) => void; +} + +export function BuilderControls({ config, onChange }: BuilderControlsProps) { + const updateComponents = (updates: Partial<BuilderConfig["components"]>) => { + onChange({ + ...config, + components: { ...config.components, ...updates }, + }); + }; + + const updateStyles = (updates: Partial<BuilderConfig["styles"]>) => { + onChange({ + ...config, + styles: { ...config.styles, ...updates }, + }); + }; + + const updateColor = <K extends keyof BuilderConfig["styles"]["colors"]>( + key: K, + value: BuilderConfig["styles"]["colors"][K], + ) => { + onChange({ + ...config, + styles: { + ...config.styles, + colors: { ...config.styles.colors, [key]: value }, + }, + }); + }; + + const updateOptionalColor = < + K extends keyof BuilderConfig["styles"]["colors"], + >( + key: K, + value: ThemeColor | undefined, + ) => { + const newColors = { ...config.styles.colors }; + if (value === undefined) { + delete newColors[key]; + } else { + newColors[key] = value; + } + onChange({ + ...config, + styles: { + ...config.styles, + colors: newColors, + }, + }); + }; + + const updateActionBar = ( + updates: Partial<BuilderConfig["components"]["actionBar"]>, + ) => { + onChange({ + ...config, + components: { + ...config.components, + actionBar: { ...config.components.actionBar, ...updates }, + }, + }); + }; + + const { components, styles } = config; + + return ( + <div className="h-full scrollbar-none overflow-y-auto"> + <div className="space-y-5"> + <div className="bg-muted/30 flex items-center justify-between rounded-lg border px-3 py-1"> + <span className="text-sm font-medium">Preset</span> + <PresetSelect config={config} onChange={onChange} /> + </div> + + <Section + title="Messages" + headerRight={<ColorModeHeader />} + withDivider={false} + > + <div className="space-y-1"> + <Row + label="User Position" + control={ + <Select + value={styles.userMessagePosition} + onValueChange={(value) => + updateStyles({ + userMessagePosition: value as "left" | "right", + }) + } + options={USER_MESSAGE_POSITIONS} + /> + } + /> + <Row + label="User Background" + control={ + <ThemeColorPicker + value={ + styles.colors.userMessage ?? DEFAULT_COLORS.userMessage + } + onChange={(value) => updateColor("userMessage", value)} + /> + } + /> + <SwitchColorRow + label="Assistant Background" + enabled={styles.colors.assistantMessage !== undefined} + onEnabledChange={(enabled) => { + if (enabled) { + updateOptionalColor( + "assistantMessage", + DEFAULT_COLORS.background, + ); + } else { + updateOptionalColor("assistantMessage", undefined); + } + }} + color={styles.colors.assistantMessage} + defaultColor={DEFAULT_COLORS.background} + onColorChange={(color) => + updateOptionalColor("assistantMessage", color) + } + /> + <Row + label="Spacing" + control={ + <Select + value={styles.messageSpacing} + onValueChange={(value) => + updateStyles({ messageSpacing: value as MessageSpacing }) + } + options={MESSAGE_SPACINGS} + /> + } + /> + <Row + label="Edit" + control={ + <Switch + checked={components.editMessage} + onCheckedChange={(checked) => + updateComponents({ editMessage: checked }) + } + /> + } + /> + <Row + label="Branch Picker" + control={ + <Switch + checked={components.branchPicker} + onCheckedChange={(checked) => + updateComponents({ branchPicker: checked }) + } + /> + } + /> + </div> + </Section> + + <Section title="Thread"> + <div className="space-y-1"> + <Row + label="Welcome" + control={ + <Switch + checked={components.threadWelcome} + onCheckedChange={(checked) => + updateComponents({ threadWelcome: checked }) + } + /> + } + /> + <Row + label="Scroll to Bottom" + control={ + <Switch + checked={components.scrollToBottom} + onCheckedChange={(checked) => + updateComponents({ scrollToBottom: checked }) + } + /> + } + /> + <Row + label="Max Width" + control={ + <Select + value={styles.maxWidth} + onValueChange={(value) => updateStyles({ maxWidth: value })} + options={MAX_WIDTHS} + /> + } + /> + </div> + </Section> + + <Section title="Composer" headerRight={<ColorModeHeader />}> + <div className="space-y-1"> + <Row + label="Attachments" + control={ + <Switch + checked={components.attachments} + onCheckedChange={(checked) => + updateComponents({ attachments: checked }) + } + /> + } + /> + <SwitchColorRow + label="Background" + enabled={styles.colors.composer !== undefined} + onEnabledChange={(enabled) => { + if (enabled) { + updateOptionalColor("composer", DEFAULT_COLORS.composer); + } else { + updateOptionalColor("composer", undefined); + } + }} + color={styles.colors.composer} + defaultColor={DEFAULT_COLORS.composer} + onColorChange={(color) => updateOptionalColor("composer", color)} + /> + </div> + </Section> + + <Section title="Content"> + <div className="space-y-1"> + <Row + label="Markdown" + control={ + <Switch + checked={components.markdown} + onCheckedChange={(checked) => + updateComponents({ markdown: checked }) + } + /> + } + /> + {components.markdown && ( + <IndentedRow + label="Code Theme" + info={ + <> + Syntax highlighting powered by{" "} + <a + href="https://shiki.style/themes" + target="_blank" + rel="noopener noreferrer" + className="underline" + > + Shiki + </a> + </> + } + control={ + <Select + value={components.codeHighlightTheme} + onValueChange={(value) => + updateComponents({ + codeHighlightTheme: value as CodeHighlightTheme, + }) + } + options={CODE_HIGHLIGHT_THEMES} + /> + } + /> + )} + <Row + label="Reasoning" + control={ + <Switch + checked={components.reasoning} + onCheckedChange={(checked) => + updateComponents({ reasoning: checked }) + } + /> + } + /> + <Row + label="Sources" + control={ + <Switch + checked={components.sources} + onCheckedChange={(checked) => + updateComponents({ sources: checked }) + } + /> + } + /> + <Row + label="Follow-ups" + control={ + <Switch + checked={components.followUpSuggestions} + onCheckedChange={(checked) => + updateComponents({ followUpSuggestions: checked }) + } + /> + } + /> + <Row + label="Suggestions" + control={ + <Switch + checked={components.suggestions} + onCheckedChange={(checked) => + updateComponents({ suggestions: checked }) + } + /> + } + /> + {components.suggestions && ( + <> + <IndentedRow + label="Background" + control={ + <OptionalThemeColorPicker + value={styles.colors.suggestion} + defaultValue={DEFAULT_COLORS.suggestion} + onChange={(value) => + updateOptionalColor("suggestion", value) + } + /> + } + /> + <IndentedRow + label="Border" + control={ + <OptionalThemeColorPicker + value={styles.colors.suggestionBorder} + defaultValue={DEFAULT_COLORS.suggestionBorder} + onChange={(value) => + updateOptionalColor("suggestionBorder", value) + } + /> + } + /> + </> + )} + </div> + </Section> + + <SectionWithToggle + title="Avatar" + headerRight={<ColorModeHeader />} + enabled={components.avatar} + onEnabledChange={(checked) => updateComponents({ avatar: checked })} + > + <div className="space-y-1"> + <Row + label="User" + control={ + <OptionalThemeColorPicker + value={styles.colors.userAvatar} + defaultValue={DEFAULT_COLORS.userAvatar} + onChange={(value) => updateOptionalColor("userAvatar", value)} + /> + } + /> + <Row + label="Assistant" + control={ + <OptionalThemeColorPicker + value={styles.colors.assistantAvatar} + defaultValue={DEFAULT_COLORS.assistantAvatar} + onChange={(value) => + updateOptionalColor("assistantAvatar", value) + } + /> + } + /> + </div> + </SectionWithToggle> + + <Section title="Loading"> + <div className="space-y-1"> + <Row + label="Typing Indicator" + control={ + <Select + value={components.typingIndicator} + onValueChange={(value) => + updateComponents({ + typingIndicator: value as TypingIndicator, + }) + } + options={TYPING_INDICATORS} + /> + } + /> + <Row + label="Loading Style" + control={ + <Select + value={components.loadingIndicator} + onValueChange={(value) => + updateComponents({ + loadingIndicator: value as LoadingIndicator, + }) + } + options={LOADING_INDICATORS} + /> + } + /> + {components.loadingIndicator === "text" && ( + <IndentedRow + label="Text" + control={ + <input + type="text" + value={components.loadingText} + onChange={(e) => + updateComponents({ loadingText: e.target.value }) + } + className="bg-background h-7 w-32 rounded-md border px-2 text-sm" + placeholder="Thinking..." + /> + } + /> + )} + </div> + </Section> + + <Section title="Actions"> + <div className="space-y-1"> + <Row + label="Copy" + control={ + <Switch + checked={components.actionBar.copy} + onCheckedChange={(checked) => + updateActionBar({ copy: checked }) + } + /> + } + /> + <Row + label="Reload" + control={ + <Switch + checked={components.actionBar.reload} + onCheckedChange={(checked) => + updateActionBar({ reload: checked }) + } + /> + } + /> + <Row + label="Speak" + control={ + <Switch + checked={components.actionBar.speak} + onCheckedChange={(checked) => + updateActionBar({ speak: checked }) + } + /> + } + /> + <Row + label="Feedback" + control={ + <Switch + checked={components.actionBar.feedback} + onCheckedChange={(checked) => + updateActionBar({ feedback: checked }) + } + /> + } + /> + </div> + </Section> + + <Section title="Typography"> + <div className="space-y-1"> + <Row + label="Font" + control={ + <Select + value={styles.fontFamily} + onValueChange={(value) => updateStyles({ fontFamily: value })} + options={FONT_FAMILIES} + /> + } + /> + <Row + label="Size" + control={ + <Select + value={styles.fontSize} + onValueChange={(value) => + updateStyles({ fontSize: value as FontSize }) + } + options={FONT_SIZES} + /> + } + /> + </div> + </Section> + + <Section title="Layout"> + <div className="space-y-1"> + <Row + label="Radius" + control={ + <Select + value={styles.borderRadius} + onValueChange={(value) => + updateStyles({ borderRadius: value as BorderRadius }) + } + options={BORDER_RADIUSES} + /> + } + /> + <Row + label="Animations" + control={ + <Switch + checked={styles.animations} + onCheckedChange={(checked) => + updateStyles({ animations: checked }) + } + /> + } + /> + </div> + </Section> + + <Section title="Colors" headerRight={<ColorModeHeader />}> + <div className="space-y-1"> + <Row + label="Accent" + control={ + <ThemeColorPicker + value={styles.colors.accent} + onChange={(value) => updateColor("accent", value)} + /> + } + /> + <Row + label="Background" + control={ + <OptionalThemeColorPicker + value={styles.colors.background} + defaultValue={DEFAULT_COLORS.background} + onChange={(value) => updateOptionalColor("background", value)} + /> + } + /> + <Row + label="Foreground" + control={ + <OptionalThemeColorPicker + value={styles.colors.foreground} + defaultValue={DEFAULT_COLORS.foreground} + onChange={(value) => updateOptionalColor("foreground", value)} + /> + } + /> + <Row + label="Muted" + control={ + <OptionalThemeColorPicker + value={styles.colors.mutedForeground} + defaultValue={DEFAULT_COLORS.mutedForeground} + onChange={(value) => + updateOptionalColor("mutedForeground", value) + } + /> + } + /> + <Row + label="Border" + control={ + <OptionalThemeColorPicker + value={styles.colors.border} + defaultValue={DEFAULT_COLORS.border} + onChange={(value) => updateOptionalColor("border", value)} + /> + } + /> + </div> + </Section> + </div> + </div> + ); +} + +function Section({ + title, + headerRight, + children, + withDivider = true, +}: { + title: string; + headerRight?: React.ReactNode; + children: React.ReactNode; + withDivider?: boolean; +}) { + return ( + <div + className={ + withDivider ? "border-border/50 space-y-2 border-t pt-4" : "space-y-2" + } + > + <div className="flex items-center justify-between"> + <span className="text-muted-foreground text-xs font-medium"> + {title} + </span> + {headerRight} + </div> + {children} + </div> + ); +} + +function SectionWithToggle({ + title, + headerRight, + enabled, + onEnabledChange, + children, +}: { + title: string; + headerRight?: React.ReactNode; + enabled: boolean; + onEnabledChange: (enabled: boolean) => void; + children: React.ReactNode; +}) { + return ( + <div className="border-border/50 space-y-2 border-t pt-4"> + <div className="flex items-center justify-between"> + <span className="text-muted-foreground text-xs font-medium"> + {title} + </span> + <div className="flex items-center gap-2"> + {enabled && headerRight} + <Switch checked={enabled} onCheckedChange={onEnabledChange} /> + </div> + </div> + {enabled && children} + </div> + ); +} + +function ColorModeHeader() { + return ( + <Tooltip> + <TooltipTrigger asChild> + <div className="flex gap-1"> + <div className="flex size-5 items-center justify-center"> + <SunIcon className="text-muted-foreground size-3.5" /> + </div> + <div className="flex size-5 items-center justify-center"> + <MoonIcon className="text-muted-foreground size-3.5" /> + </div> + </div> + </TooltipTrigger> + <TooltipContent side="top">Light & Dark mode colors</TooltipContent> + </Tooltip> + ); +} + +function Row({ + label, + control, + info, +}: { + label: string; + control: React.ReactNode; + info?: React.ReactNode; +}) { + return ( + <div className="flex h-7 items-center justify-between"> + <span className="flex items-center gap-2 text-sm"> + {label} + {info && ( + <Tooltip> + <TooltipTrigger asChild> + <button + type="button" + className="text-muted-foreground hover:text-foreground flex items-center" + > + <CircleAlertIcon className="size-3" /> + </button> + </TooltipTrigger> + <TooltipContent side="top">{info}</TooltipContent> + </Tooltip> + )} + </span> + {control} + </div> + ); +} + +function IndentedRow({ + label, + control, + info, +}: { + label: string; + control: React.ReactNode; + info?: React.ReactNode; +}) { + return ( + <div className="border-border ml-4 border-l pl-3"> + <Row label={label} control={control} info={info} /> + </div> + ); +} + +function SwitchColorRow({ + label, + enabled, + onEnabledChange, + color, + defaultColor, + onColorChange, +}: { + label: string; + enabled: boolean; + onEnabledChange: (enabled: boolean) => void; + color: ThemeColor | undefined; + defaultColor: ThemeColor; + onColorChange: (color: ThemeColor | undefined) => void; +}) { + return ( + <div className="flex h-7 items-center justify-between"> + <span className="text-sm">{label}</span> + <div className="flex items-center gap-2"> + <Switch + checked={enabled} + onCheckedChange={(checked) => { + onEnabledChange(checked); + if (!checked) onColorChange(undefined); + }} + /> + <div className={enabled ? "" : "pointer-events-none opacity-40"}> + <ThemeColorPicker + value={color ?? defaultColor} + onChange={onColorChange} + /> + </div> + </div> + </div> + ); +} + +function PresetSelect({ + config, + onChange, +}: { + config: BuilderConfig; + onChange: (config: BuilderConfig) => void; +}) { + const currentPreset = PRESETS.find( + (preset) => JSON.stringify(preset.config) === JSON.stringify(config), + ); + + const options = PRESETS.map((preset) => ({ + label: preset.name, + value: preset.id, + })); + + return ( + <Select + value={currentPreset?.id ?? ""} + onValueChange={(id) => { + const preset = PRESETS.find((p) => p.id === id); + if (preset) { + analytics.builder.presetSelected(preset.name); + onChange(preset.config); + } + }} + options={options} + placeholder="Custom" + /> + ); +} diff --git a/apps/docs/components/builder/builder-preview.tsx b/apps/docs/components/builder/builder-preview.tsx new file mode 100644 index 0000000..bc9a724 --- /dev/null +++ b/apps/docs/components/builder/builder-preview.tsx @@ -0,0 +1,1035 @@ +"use client"; + +import "@assistant-ui/react-markdown/styles/dot.css"; + +import { + ArrowDownIcon, + ArrowUpIcon, + BotIcon, + CheckIcon, + ChevronLeftIcon, + ChevronRightIcon, + CopyIcon, + LoaderIcon, + PaperclipIcon, + PencilIcon, + RefreshCwIcon, + SquareIcon, + ThumbsDownIcon, + ThumbsUpIcon, + UserIcon, + Volume2Icon, +} from "lucide-react"; + +import { + ActionBarPrimitive, + AuiIf, + BranchPickerPrimitive, + ComposerPrimitive, + MessagePrimitive, + ThreadPrimitive, + useMessagePartText, +} from "@assistant-ui/react"; + +import { + type FC, + createContext, + useContext, + useMemo, + memo, + useState, + useEffect, +} from "react"; +import { useTheme } from "next-themes"; +import { cn } from "@/lib/utils"; +import { Button } from "@/components/ui/button"; +import { TooltipIconButton } from "@/components/assistant-ui/tooltip-icon-button"; +import { + ReasoningRoot, + ReasoningTrigger, + ReasoningContent, + ReasoningText, +} from "@/components/assistant-ui/reasoning"; +import { + Source, + SourceIcon, + SourceTitle, +} from "@/components/assistant-ui/sources"; +import { + type CodeHeaderProps, + MarkdownTextPrimitive, + unstable_memoizeMarkdownComponents as memoizeMarkdownComponents, + useIsMarkdownCodeBlock, + type SyntaxHighlighterProps, +} from "@assistant-ui/react-markdown"; +import remarkGfm from "remark-gfm"; +import ShikiHighlighter from "react-shiki"; + +import { + SHIKI_THEME_MAP, + DEFAULT_COLORS, + type BuilderConfig, + type CodeHighlightTheme, + type ThemeColor, +} from "./types"; +import { + BORDER_RADIUS_CLASS, + MESSAGE_SPACING_CLASS, + isLightColor, +} from "@/lib/builder-utils"; + +interface BuilderPreviewContextValue { + config: BuilderConfig; + isDark: boolean; + accentColor: string; +} + +const BuilderPreviewContext = createContext<BuilderPreviewContextValue | null>( + null, +); + +function useBuilderPreviewContext() { + const context = useContext(BuilderPreviewContext); + if (!context) { + throw new Error( + "useBuilderPreviewContext must be used within BuilderPreviewProvider", + ); + } + return context; +} + +const UserMessageWrapper: FC = () => { + const { config } = useBuilderPreviewContext(); + return <UserMessage config={config} />; +}; + +const AssistantMessageWrapper: FC = () => { + const { config } = useBuilderPreviewContext(); + return <AssistantMessage config={config} />; +}; + +const PlainText: FC = () => { + const { text } = useMessagePartText(); + return <p className="whitespace-pre-wrap">{text}</p>; +}; + +const MarkdownTextWrapper: FC = () => { + const { config } = useBuilderPreviewContext(); + return ( + <ConfigurableMarkdownText + codeHighlightTheme={config.components.codeHighlightTheme} + /> + ); +}; + +interface BuilderPreviewProps { + config: BuilderConfig; +} + +// Hook to detect page theme from document.documentElement.classList +function usePageTheme() { + const { resolvedTheme } = useTheme(); + const [mounted, setMounted] = useState(false); + + useEffect(() => { + setMounted(true); + }, []); + + // Return false during SSR/hydration to avoid mismatch, then update on client + if (!mounted) return false; + return resolvedTheme === "dark"; +} + +export function BuilderPreview({ config }: BuilderPreviewProps) { + const { components, styles } = config; + + // Always follow page theme so user can preview both light and dark variants + const isDark = usePageTheme(); + + // Helper to get color value based on current theme + const getColor = ( + color: ThemeColor | undefined, + fallback: ThemeColor, + ): string => { + const c = color ?? fallback; + return isDark ? c.dark : c.light; + }; + + const { colors } = styles; + const accentColor = getColor(colors.accent, DEFAULT_COLORS.accent); + + // Define CSS variables with theme-aware values + const cssVars = { + "--aui-thread-max-width": styles.maxWidth, + "--aui-accent-color": accentColor, + "--aui-background": getColor(colors.background, DEFAULT_COLORS.background), + "--aui-foreground": getColor(colors.foreground, DEFAULT_COLORS.foreground), + "--aui-muted": getColor(colors.muted, DEFAULT_COLORS.muted), + "--aui-muted-foreground": getColor( + colors.mutedForeground, + DEFAULT_COLORS.mutedForeground, + ), + "--aui-border": getColor(colors.border, DEFAULT_COLORS.border), + "--aui-user-message-background": getColor( + colors.userMessage, + DEFAULT_COLORS.userMessage, + ), + "--aui-assistant-message-background": colors.assistantMessage + ? getColor(colors.assistantMessage, DEFAULT_COLORS.background) + : undefined, + "--aui-composer-background": getColor( + colors.composer, + DEFAULT_COLORS.composer, + ), + "--aui-user-avatar-background": getColor( + colors.userAvatar, + DEFAULT_COLORS.userAvatar, + ), + "--aui-assistant-avatar-background": getColor( + colors.assistantAvatar, + DEFAULT_COLORS.assistantAvatar, + ), + "--aui-suggestion-background": getColor( + colors.suggestion, + DEFAULT_COLORS.suggestion, + ), + "--aui-suggestion-border": getColor( + colors.suggestionBorder, + DEFAULT_COLORS.suggestionBorder, + ), + fontFamily: styles.fontFamily, + } as React.CSSProperties; + + return ( + <BuilderPreviewContext.Provider value={{ config, isDark, accentColor }}> + <div + className={cn("h-full w-full", isDark ? "dark" : "light")} + style={cssVars} + > + {config.customCSS && ( + <style>{`@scope (.aui-root) { ${config.customCSS} }`}</style> + )} + <ThreadPrimitive.Root + className="aui-root aui-thread-root @container flex h-full flex-col" + style={{ + backgroundColor: "var(--aui-background)", + color: "var(--aui-foreground)", + fontSize: styles.fontSize, + }} + > + <ThreadPrimitive.Viewport + turnAnchor="top" + className="aui-thread-viewport relative flex flex-1 flex-col overflow-x-auto overflow-y-scroll scroll-smooth px-4 pt-4" + > + {components.threadWelcome && ( + <AuiIf condition={(s) => s.thread.isEmpty}> + <ThreadWelcome config={config} /> + </AuiIf> + )} + + {!components.threadWelcome && ( + <AuiIf condition={(s) => s.thread.isEmpty}> + <div className="grow" /> + </AuiIf> + )} + + <ThreadPrimitive.Messages> + {({ message }) => { + if (message.composer.isEditing) return <EditComposer />; + if (message.role === "user") return <UserMessageWrapper />; + return <AssistantMessageWrapper />; + }} + </ThreadPrimitive.Messages> + + <ThreadPrimitive.ViewportFooter + className="aui-thread-viewport-footer sticky bottom-0 mx-auto mt-auto flex w-full max-w-(--aui-thread-max-width) flex-col gap-4 overflow-visible rounded-t-3xl pb-4 md:pb-6" + style={{ backgroundColor: "var(--aui-background)" }} + > + {components.scrollToBottom && <ThreadScrollToBottom />} + <Composer config={config} /> + </ThreadPrimitive.ViewportFooter> + </ThreadPrimitive.Viewport> + </ThreadPrimitive.Root> + </div> + </BuilderPreviewContext.Provider> + ); +} + +interface ThreadWelcomeProps { + config: BuilderConfig; +} + +const ThreadWelcome: FC<ThreadWelcomeProps> = ({ config }) => { + const { components, styles } = config; + + return ( + <div className="aui-thread-welcome-root mx-auto my-auto flex w-full max-w-(--aui-thread-max-width) grow flex-col"> + <div className="aui-thread-welcome-center flex w-full grow flex-col items-center justify-center"> + <div className="aui-thread-welcome-message flex size-full flex-col justify-center px-4"> + <h1 + className={cn( + "aui-thread-welcome-message-inner text-2xl font-semibold", + styles.animations && + "fade-in slide-in-from-bottom-1 animate-in duration-200", + )} + > + Hello there! + </h1> + <p + className={cn( + "aui-thread-welcome-message-inner text-xl", + styles.animations && + "fade-in slide-in-from-bottom-1 animate-in delay-75 duration-200", + )} + style={{ color: "var(--aui-muted-foreground)" }} + > + How can I help you today? + </p> + </div> + </div> + {components.suggestions && <ThreadSuggestions config={config} />} + </div> + ); +}; + +const SUGGESTIONS = [ + { + title: "What's the weather", + label: "in San Francisco?", + prompt: "What's the weather in San Francisco?", + }, + { + title: "Explain React hooks", + label: "like useState and useEffect", + prompt: "Explain React hooks like useState and useEffect", + }, +] as const; + +interface ThreadSuggestionsProps { + config: BuilderConfig; +} + +const ThreadSuggestions: FC<ThreadSuggestionsProps> = ({ config }) => { + const { styles } = config; + + return ( + <div className="aui-thread-welcome-suggestions grid w-full gap-2 pb-4 @md:grid-cols-2"> + {SUGGESTIONS.map((suggestion, index) => ( + <div + key={suggestion.prompt} + className={cn( + "aui-thread-welcome-suggestion-display nth-[n+3]:hidden @md:nth-[n+3]:block", + styles.animations && + "fade-in slide-in-from-bottom-2 animate-in fill-mode-both duration-200", + )} + style={ + styles.animations + ? { animationDelay: `${100 + index * 50}ms` } + : undefined + } + > + <ThreadPrimitive.Suggestion prompt={suggestion.prompt} send asChild> + <Button + variant="ghost" + className="aui-thread-welcome-suggestion h-auto w-full flex-wrap items-start justify-start gap-1 rounded-2xl px-4 py-3 text-left text-sm transition-colors @md:flex-col" + style={{ + backgroundColor: "var(--aui-suggestion-background)", + borderWidth: "1px", + borderStyle: "solid", + borderColor: "var(--aui-suggestion-border)", + }} + aria-label={suggestion.prompt} + > + <span className="aui-thread-welcome-suggestion-text-1 font-medium"> + {suggestion.title} + </span> + <span + className="aui-thread-welcome-suggestion-text-2" + style={{ color: "var(--aui-muted-foreground)" }} + > + {suggestion.label} + </span> + </Button> + </ThreadPrimitive.Suggestion> + </div> + ))} + </div> + ); +}; + +interface ComposerProps { + config: BuilderConfig; +} + +const Composer: FC<ComposerProps> = ({ config }) => { + const { styles } = config; + + return ( + <ComposerPrimitive.Root className="aui-composer-root relative flex w-full flex-col"> + <ComposerPrimitive.AttachmentDropzone + className={cn( + "aui-composer-attachment-dropzone flex w-full flex-col px-1 pt-2 transition-colors outline-none", + "data-[dragging=true]:bg-accent/50 data-[dragging=true]:border-dashed", + BORDER_RADIUS_CLASS[styles.borderRadius], + )} + style={{ + backgroundColor: "var(--aui-composer-background)", + borderWidth: "1px", + borderStyle: "solid", + borderColor: "var(--aui-border)", + }} + > + <ComposerPrimitive.Input + placeholder="Send a message..." + className="aui-composer-input mb-1 max-h-32 min-h-14 w-full resize-none bg-transparent px-4 pt-2 pb-3 text-sm outline-none placeholder:text-(--aui-muted-foreground) focus-visible:ring-0" + rows={1} + autoFocus + aria-label="Message input" + /> + <ComposerAction config={config} /> + </ComposerPrimitive.AttachmentDropzone> + </ComposerPrimitive.Root> + ); +}; + +interface ComposerActionProps { + config: BuilderConfig; +} + +const ComposerAction: FC<ComposerActionProps> = ({ config }) => { + const { accentColor } = useBuilderPreviewContext(); + const { components } = config; + + return ( + <div className="aui-composer-action-wrapper relative mx-2 mb-2 flex items-center justify-between"> + {components.attachments ? ( + <ComposerPrimitive.AddAttachment asChild> + <TooltipIconButton + tooltip="Add attachment" + variant="ghost" + style={{ color: "var(--aui-muted-foreground)" }} + > + <PaperclipIcon className="size-5" /> + </TooltipIconButton> + </ComposerPrimitive.AddAttachment> + ) : ( + <div /> + )} + + <AuiIf condition={(s) => !s.thread.isRunning}> + <ComposerPrimitive.Send asChild> + <TooltipIconButton + tooltip="Send message" + side="bottom" + variant="default" + size="icon" + className={cn( + "aui-composer-send size-8 rounded-full", + isLightColor(accentColor) ? "text-black" : "text-white", + )} + style={{ backgroundColor: "var(--aui-accent-color)" }} + aria-label="Send message" + > + <ArrowUpIcon className="aui-composer-send-icon size-4" /> + </TooltipIconButton> + </ComposerPrimitive.Send> + </AuiIf> + + <AuiIf condition={(s) => s.thread.isRunning}> + <ComposerPrimitive.Cancel asChild> + <Button + type="button" + variant="default" + size="icon" + className={cn( + "aui-composer-cancel size-8 rounded-full", + isLightColor(accentColor) ? "text-black" : "text-white", + )} + style={{ backgroundColor: "var(--aui-accent-color)" }} + aria-label="Stop generating" + > + <SquareIcon className="aui-composer-cancel-icon size-3 fill-current" /> + </Button> + </ComposerPrimitive.Cancel> + </AuiIf> + </div> + ); +}; + +const ThreadScrollToBottom: FC = () => { + return ( + <ThreadPrimitive.ScrollToBottom asChild> + <TooltipIconButton + tooltip="Scroll to bottom" + variant="outline" + className="aui-thread-scroll-to-bottom absolute -top-12 z-10 self-center rounded-full p-4 disabled:invisible" + style={{ + backgroundColor: "var(--aui-background)", + borderColor: "var(--aui-border)", + }} + > + <ArrowDownIcon /> + </TooltipIconButton> + </ThreadPrimitive.ScrollToBottom> + ); +}; + +interface UserMessageProps { + config: BuilderConfig; +} + +const UserMessage: FC<UserMessageProps> = ({ config }) => { + const { components, styles } = config; + const isLeftAligned = styles.userMessagePosition === "left"; + const messageSpacingClass = MESSAGE_SPACING_CLASS[styles.messageSpacing]; + + // For left-aligned, use flex layout like before + // For right-aligned (default), use grid layout like thread.tsx + if (isLeftAligned) { + return ( + <MessagePrimitive.Root + className={cn( + "aui-user-message-root mx-auto flex w-full max-w-(--aui-thread-max-width) gap-3 px-2", + messageSpacingClass, + styles.animations && + "fade-in slide-in-from-bottom-1 animate-in duration-150", + )} + data-role="user" + > + {components.avatar && ( + <div + className="flex size-8 shrink-0 items-center justify-center rounded-full" + style={{ backgroundColor: "var(--aui-user-avatar-background)" }} + > + <UserIcon className="size-4" /> + </div> + )} + <div className="relative max-w-[80%] min-w-0"> + <div + className={cn( + "aui-user-message-content peer px-4 py-2.5 wrap-break-word empty:hidden", + BORDER_RADIUS_CLASS[styles.borderRadius], + )} + style={{ backgroundColor: "var(--aui-user-message-background)" }} + > + <MessagePrimitive.Parts /> + </div> + {components.editMessage && ( + <div className="aui-user-action-bar-wrapper absolute top-1/2 right-0 translate-x-full -translate-y-1/2 pl-2 peer-empty:hidden"> + <UserActionBar /> + </div> + )} + </div> + {components.branchPicker && ( + <BranchPicker className="aui-user-branch-picker -mr-1 self-end" /> + )} + </MessagePrimitive.Root> + ); + } + + // Right-aligned (default) - use grid layout like thread.tsx + return ( + <MessagePrimitive.Root + className={cn( + "aui-user-message-root mx-auto grid w-full max-w-(--aui-thread-max-width) auto-rows-auto grid-cols-[minmax(72px,1fr)_auto] content-start gap-y-2 px-2", + messageSpacingClass, + "[&:where(>*)]:col-start-2", + styles.animations && + "fade-in slide-in-from-bottom-1 animate-in duration-150", + )} + data-role="user" + > + {components.avatar && ( + <div className="col-start-2 flex justify-end"> + <div + className="flex size-8 shrink-0 items-center justify-center rounded-full" + style={{ backgroundColor: "var(--aui-user-avatar-background)" }} + > + <UserIcon className="size-4" /> + </div> + </div> + )} + + <div className="aui-user-message-content-wrapper relative col-start-2 min-w-0"> + <div + className={cn( + "aui-user-message-content peer px-4 py-2.5 wrap-break-word empty:hidden", + BORDER_RADIUS_CLASS[styles.borderRadius], + )} + style={{ backgroundColor: "var(--aui-user-message-background)" }} + > + <MessagePrimitive.Parts /> + </div> + {components.editMessage && ( + <div className="aui-user-action-bar-wrapper absolute top-1/2 left-0 -translate-x-full -translate-y-1/2 pr-2 peer-empty:hidden"> + <UserActionBar /> + </div> + )} + </div> + + {components.branchPicker && ( + <BranchPicker className="aui-user-branch-picker col-span-full col-start-1 row-start-3 -mr-1 justify-end" /> + )} + </MessagePrimitive.Root> + ); +}; + +const UserActionBar: FC = () => { + return ( + <ActionBarPrimitive.Root + hideWhenRunning + autohide="not-last" + className="aui-user-action-bar-root flex flex-col items-end" + > + <ActionBarPrimitive.Edit asChild> + <TooltipIconButton tooltip="Edit" className="aui-user-action-edit p-4"> + <PencilIcon /> + </TooltipIconButton> + </ActionBarPrimitive.Edit> + </ActionBarPrimitive.Root> + ); +}; + +interface AssistantMessageProps { + config: BuilderConfig; +} + +const AssistantMessage: FC<AssistantMessageProps> = ({ config }) => { + const { components, styles } = config; + const messageSpacingClass = MESSAGE_SPACING_CLASS[styles.messageSpacing]; + + const TextComponent = components.markdown ? MarkdownTextWrapper : PlainText; + + return ( + <MessagePrimitive.Root + className={cn( + "aui-assistant-message-root relative mx-auto w-full max-w-(--aui-thread-max-width) px-2", + messageSpacingClass, + styles.animations && + "fade-in slide-in-from-bottom-1 animate-in duration-150", + )} + data-role="assistant" + style={ + components.typingIndicator !== "dot" + ? ({ "--aui-content": "none" } as React.CSSProperties) + : undefined + } + > + <div className="flex gap-3"> + {components.avatar && ( + <div + className="flex size-8 shrink-0 items-center justify-center rounded-full" + style={{ + backgroundColor: "var(--aui-assistant-avatar-background)", + }} + > + <BotIcon className="size-4" /> + </div> + )} + <div className="min-w-0 flex-1 space-y-2"> + {components.reasoning && ( + <ReasoningRoot variant="muted" className="mb-0"> + <ReasoningTrigger /> + <ReasoningContent> + <ReasoningText> + <p> + Let me analyze this step by step. First, I'll consider + the key points of your question... + </p> + </ReasoningText> + </ReasoningContent> + </ReasoningRoot> + )} + + <div + className={cn( + "aui-assistant-message-content leading-relaxed wrap-break-word", + styles.colors.assistantMessage && "rounded-2xl px-4 py-3", + )} + style={ + styles.colors.assistantMessage + ? { backgroundColor: "var(--aui-assistant-message-background)" } + : undefined + } + > + <MessagePrimitive.Parts> + {({ part }) => { + if (part.type === "text") return <TextComponent />; + return null; + }} + </MessagePrimitive.Parts> + + {components.loadingIndicator !== "none" && ( + <AuiIf + condition={({ thread, message }) => + thread.isRunning && message.content.length === 0 + } + > + <div + className="flex items-center gap-2" + style={{ color: "var(--aui-muted-foreground)" }} + > + <LoaderIcon className="size-4 animate-spin" /> + {components.loadingIndicator === "text" && ( + <span className="text-sm">{components.loadingText}</span> + )} + </div> + </AuiIf> + )} + </div> + + {components.sources && ( + <div className="mt-2 flex flex-wrap gap-1.5"> + <Source href="https://react.dev"> + <SourceIcon url="https://react.dev" /> + <SourceTitle>React Documentation</SourceTitle> + </Source> + <Source href="https://nextjs.org"> + <SourceIcon url="https://nextjs.org" /> + <SourceTitle>Next.js</SourceTitle> + </Source> + </div> + )} + + <div className="aui-assistant-message-footer flex min-h-6 items-center"> + {components.branchPicker && <BranchPicker />} + <AssistantActionBar config={config} /> + </div> + + {components.followUpSuggestions && ( + <AuiIf condition={(s) => !s.thread.isRunning}> + <FollowUpSuggestions /> + </AuiIf> + )} + </div> + </div> + </MessagePrimitive.Root> + ); +}; + +const FollowUpSuggestions: FC = () => { + return ( + <div className="flex flex-wrap gap-2"> + <ThreadPrimitive.Suggestion + prompt="Tell me more" + className="rounded-full px-3 py-1 text-sm" + style={{ + backgroundColor: "var(--aui-suggestion-background)", + borderWidth: "1px", + borderStyle: "solid", + borderColor: "var(--aui-suggestion-border)", + }} + > + Tell me more + </ThreadPrimitive.Suggestion> + <ThreadPrimitive.Suggestion + prompt="Can you explain differently?" + className="rounded-full px-3 py-1 text-sm" + style={{ + backgroundColor: "var(--aui-suggestion-background)", + borderWidth: "1px", + borderStyle: "solid", + borderColor: "var(--aui-suggestion-border)", + }} + > + Explain differently + </ThreadPrimitive.Suggestion> + </div> + ); +}; + +interface AssistantActionBarProps { + config: BuilderConfig; +} + +const AssistantActionBar: FC<AssistantActionBarProps> = ({ config }) => { + const { components } = config; + const { actionBar } = components; + + if ( + !actionBar.copy && + !actionBar.reload && + !actionBar.speak && + !actionBar.feedback + ) { + return null; + } + + return ( + <ActionBarPrimitive.Root + hideWhenRunning + autohide="not-last" + className="aui-assistant-action-bar-root -ml-1 flex gap-1" + style={{ color: "var(--aui-muted-foreground)" }} + > + {actionBar.copy && ( + <ActionBarPrimitive.Copy asChild> + <TooltipIconButton tooltip="Copy"> + <AuiIf condition={(s) => s.message.isCopied}> + <CheckIcon /> + </AuiIf> + <AuiIf condition={(s) => !s.message.isCopied}> + <CopyIcon /> + </AuiIf> + </TooltipIconButton> + </ActionBarPrimitive.Copy> + )} + {actionBar.reload && ( + <ActionBarPrimitive.Reload asChild> + <TooltipIconButton tooltip="Refresh"> + <RefreshCwIcon /> + </TooltipIconButton> + </ActionBarPrimitive.Reload> + )} + {actionBar.speak && ( + <ActionBarPrimitive.Speak asChild> + <TooltipIconButton tooltip="Read aloud"> + <Volume2Icon /> + </TooltipIconButton> + </ActionBarPrimitive.Speak> + )} + {actionBar.feedback && ( + <> + <TooltipIconButton tooltip="Good response"> + <ThumbsUpIcon /> + </TooltipIconButton> + <TooltipIconButton tooltip="Bad response"> + <ThumbsDownIcon /> + </TooltipIconButton> + </> + )} + </ActionBarPrimitive.Root> + ); +}; + +interface BranchPickerProps { + className?: string; +} + +const BranchPicker: FC<BranchPickerProps> = ({ className }) => { + return ( + <BranchPickerPrimitive.Root + hideWhenSingleBranch + className={cn( + "aui-branch-picker-root mr-2 -ml-2 inline-flex items-center text-xs", + className, + )} + style={{ color: "var(--aui-muted-foreground)" }} + > + <BranchPickerPrimitive.Previous asChild> + <TooltipIconButton tooltip="Previous"> + <ChevronLeftIcon /> + </TooltipIconButton> + </BranchPickerPrimitive.Previous> + <span className="aui-branch-picker-state font-medium"> + <BranchPickerPrimitive.Number /> / <BranchPickerPrimitive.Count /> + </span> + <BranchPickerPrimitive.Next asChild> + <TooltipIconButton tooltip="Next"> + <ChevronRightIcon /> + </TooltipIconButton> + </BranchPickerPrimitive.Next> + </BranchPickerPrimitive.Root> + ); +}; + +const EditComposer: FC = () => { + return ( + <MessagePrimitive.Root className="aui-edit-composer-wrapper mx-auto flex w-full max-w-(--aui-thread-max-width) flex-col px-2 py-3"> + <ComposerPrimitive.Root className="aui-edit-composer-root bg-muted ml-auto flex w-full max-w-[85%] flex-col rounded-2xl"> + <ComposerPrimitive.Input + className="aui-edit-composer-input text-foreground min-h-14 w-full resize-none bg-transparent p-4 text-sm outline-none" + autoFocus + /> + <div className="aui-edit-composer-footer mx-3 mb-3 flex items-center gap-2 self-end"> + <ComposerPrimitive.Cancel asChild> + <Button variant="ghost" size="sm"> + Cancel + </Button> + </ComposerPrimitive.Cancel> + <ComposerPrimitive.Send asChild> + <Button size="sm">Update</Button> + </ComposerPrimitive.Send> + </div> + </ComposerPrimitive.Root> + </MessagePrimitive.Root> + ); +}; + +const MarkdownCodeHeader: FC<CodeHeaderProps> = ({ language }) => ( + <div className="border-border/50 bg-muted/50 mt-2.5 flex items-center justify-between rounded-t-lg border border-b-0 px-3 py-1.5 text-xs"> + <span className="text-muted-foreground font-medium lowercase"> + {language} + </span> + </div> +); + +const MarkdownH1: FC<React.ComponentProps<"h1">> = ({ + className, + ...props +}) => ( + <h1 + className={cn( + "mb-2 scroll-m-20 text-base font-semibold first:mt-0 last:mb-0", + className, + )} + {...props} + /> +); + +const MarkdownH2: FC<React.ComponentProps<"h2">> = ({ + className, + ...props +}) => ( + <h2 + className={cn( + "mt-3 mb-1.5 scroll-m-20 text-sm font-semibold first:mt-0 last:mb-0", + className, + )} + {...props} + /> +); + +const MarkdownH3: FC<React.ComponentProps<"h3">> = ({ + className, + ...props +}) => ( + <h3 + className={cn( + "mt-2.5 mb-1 scroll-m-20 text-sm font-semibold first:mt-0 last:mb-0", + className, + )} + {...props} + /> +); + +const MarkdownP: FC<React.ComponentProps<"p">> = ({ className, ...props }) => ( + <p + className={cn("my-2.5 leading-normal first:mt-0 last:mb-0", className)} + {...props} + /> +); + +const MarkdownUl: FC<React.ComponentProps<"ul">> = ({ + className, + ...props +}) => ( + <ul + className={cn( + "marker:text-muted-foreground my-2 ml-4 list-disc [&>li]:mt-1", + className, + )} + {...props} + /> +); + +const MarkdownOl: FC<React.ComponentProps<"ol">> = ({ + className, + ...props +}) => ( + <ol + className={cn( + "marker:text-muted-foreground my-2 ml-4 list-decimal [&>li]:mt-1", + className, + )} + {...props} + /> +); + +const MarkdownPre: FC<React.ComponentProps<"pre">> = ({ + className, + ...props +}) => ( + <pre + className={cn( + "border-border/50 bg-muted/30 overflow-x-auto rounded-t-none rounded-b-lg border border-t-0 p-3 text-xs leading-relaxed", + className, + )} + {...props} + /> +); + +const MarkdownCode: FC<React.ComponentProps<"code">> = ({ + className, + ...props +}) => { + const isCodeBlock = useIsMarkdownCodeBlock(); + return ( + <code + className={cn( + !isCodeBlock && + "border-border/50 bg-muted/50 rounded-md border px-1.5 py-0.5 font-mono text-[0.85em]", + className, + )} + {...props} + /> + ); +}; + +const MarkdownLi: FC<React.ComponentProps<"li">> = ({ + className, + ...props +}) => <li className={cn("leading-normal", className)} {...props} />; + +const baseMarkdownComponents = { + h1: MarkdownH1, + h2: MarkdownH2, + h3: MarkdownH3, + p: MarkdownP, + ul: MarkdownUl, + ol: MarkdownOl, + li: MarkdownLi, + pre: MarkdownPre, + code: MarkdownCode, + CodeHeader: MarkdownCodeHeader, +}; + +const createSyntaxHighlighter = ( + theme: Exclude<CodeHighlightTheme, "none">, +): FC<SyntaxHighlighterProps> => { + const SyntaxHighlighter: FC<SyntaxHighlighterProps> = ({ + code, + language, + }) => ( + <ShikiHighlighter + language={language ?? "text"} + theme={SHIKI_THEME_MAP[theme]} + addDefaultStyles={false} + showLanguage={false} + as="div" + className="not-fumadocs-codeblock border-border/50 bg-muted/30 overflow-x-auto rounded-t-none rounded-b-lg border border-t-0 p-3 text-xs leading-relaxed [&_.line:last-child:empty]:hidden" + > + {code} + </ShikiHighlighter> + ); + return SyntaxHighlighter; +}; + +interface ConfigurableMarkdownTextProps { + codeHighlightTheme: CodeHighlightTheme; +} + +const ConfigurableMarkdownText: FC<ConfigurableMarkdownTextProps> = memo( + ({ codeHighlightTheme }) => { + const components = useMemo(() => { + if (codeHighlightTheme === "none") { + return memoizeMarkdownComponents(baseMarkdownComponents); + } + + return memoizeMarkdownComponents({ + ...baseMarkdownComponents, + SyntaxHighlighter: createSyntaxHighlighter(codeHighlightTheme), + }); + }, [codeHighlightTheme]); + + return ( + <MarkdownTextPrimitive + remarkPlugins={[remarkGfm]} + className="aui-md" + components={components} + /> + ); + }, +); + +ConfigurableMarkdownText.displayName = "ConfigurableMarkdownText"; diff --git a/apps/docs/components/builder/create-dialog.tsx b/apps/docs/components/builder/create-dialog.tsx new file mode 100644 index 0000000..60c4347 --- /dev/null +++ b/apps/docs/components/builder/create-dialog.tsx @@ -0,0 +1,272 @@ +"use client"; + +import { useState, type ReactElement, isValidElement } from "react"; +import { CheckIcon, CopyIcon } from "lucide-react"; +import { Dialog as DialogPrimitive } from "@base-ui/react/dialog"; +import { XIcon } from "lucide-react"; + +import type { BuilderConfig } from "./types"; +import { configMatchesPreset } from "./presets"; +import { encodeConfig } from "@/lib/playground-url-state"; +import { BASE_URL } from "@/lib/constants"; +import { analytics } from "@/lib/analytics"; + +interface CreateDialogProps { + config: BuilderConfig; + children: React.ReactNode; + container?: React.RefObject<HTMLElement | null>; + onOpenCodeView?: () => void; +} + +export function CreateDialog({ + config, + children, + container, + onOpenCodeView, +}: CreateDialogProps) { + const [open, setOpen] = useState(false); + const commands = generateCliCommands(config); + + const handleOpenChange = (isOpen: boolean) => { + if (isOpen) { + analytics.builder.createDialogOpened(); + } + setOpen(isOpen); + }; + + const handleOpenCodeView = () => { + setOpen(false); + onOpenCodeView?.(); + }; + + return ( + <DialogPrimitive.Root open={open} onOpenChange={handleOpenChange}> + {isValidElement(children) ? ( + <DialogPrimitive.Trigger render={children as ReactElement} /> + ) : ( + <DialogPrimitive.Trigger>{children}</DialogPrimitive.Trigger> + )} + <DialogPrimitive.Portal container={container?.current}> + <DialogPrimitive.Backdrop className="data-closed:fade-out-0 data-open:fade-in-0 data-closed:animate-out data-open:animate-in absolute inset-0 z-50 bg-black/50" /> + <DialogPrimitive.Popup className="data-closed:fade-out-0 data-open:fade-in-0 data-closed:zoom-out-95 data-open:zoom-in-95 bg-background data-closed:animate-out data-open:animate-in absolute top-1/2 left-1/2 z-50 grid max-h-[85vh] w-full max-w-lg -translate-x-1/2 -translate-y-1/2 gap-4 overflow-hidden rounded-lg border p-6 shadow-lg duration-200"> + <DialogPrimitive.Title className="text-lg leading-none font-semibold"> + Create your assistant + </DialogPrimitive.Title> + <div className="space-y-4 overflow-y-auto text-sm"> + <CommandBlock + label={commands.primary.label} + {...(commands.primary.description && { + description: commands.primary.description, + })} + {...(commands.primary.command && { + command: commands.primary.command, + })} + commandType="create" + /> + + <CommandBlock + label={commands.alternative.label} + {...(commands.alternative.command && { + command: commands.alternative.command, + })} + commandType="shadcn" + /> + + <div className="border-t pt-4"> + <p className="text-muted-foreground mb-3">Or set up manually:</p> + <div className="space-y-3"> + {commands.manual.slice(0, 2).map((cmd, index) => ( + <CommandBlock + key={index} + label={`${index + 1}. ${cmd.label}`} + {...(cmd.command && { command: cmd.command })} + {...(cmd.description && { description: cmd.description })} + commandType={index === 0 ? "manual_init" : "manual_add"} + /> + ))} + <div> + <div className="text-foreground mb-1 font-medium"> + 3. Copy code + </div> + <p className="text-muted-foreground"> + Copy the code from the{" "} + <button + type="button" + onClick={handleOpenCodeView} + className="text-foreground hover:text-foreground/80 underline underline-offset-2" + > + Code view + </button>{" "} + into your thread.tsx + </p> + </div> + </div> + </div> + + <div className="border-t pt-4"> + <p className="text-foreground mb-2 font-medium">Configuration</p> + <div className="text-muted-foreground space-y-1"> + {commands.summary.map((item, index) => ( + <div key={index}>{item}</div> + ))} + </div> + </div> + </div> + <DialogPrimitive.Close className="ring-offset-background focus:ring-ring absolute top-4 right-4 rounded-sm opacity-70 transition-opacity hover:opacity-100 focus:ring-2 focus:ring-offset-2 focus:outline-none"> + <XIcon className="size-4" /> + <span className="sr-only">Close</span> + </DialogPrimitive.Close> + </DialogPrimitive.Popup> + </DialogPrimitive.Portal> + </DialogPrimitive.Root> + ); +} + +function CommandBlock({ + label, + description, + command, + commandType, +}: { + label: string; + description?: string; + command?: string; + commandType?: "create" | "shadcn" | "manual_init" | "manual_add"; +}) { + const [copied, setCopied] = useState(false); + + const handleCopy = async () => { + if (!command) return; + if (commandType) { + analytics.builder.commandCopied(commandType); + } + await navigator.clipboard.writeText(command); + setCopied(true); + setTimeout(() => setCopied(false), 2000); + }; + + return ( + <div> + <div className="text-foreground mb-1 font-medium">{label}</div> + {description && ( + <p className="text-muted-foreground mb-1.5">{description}</p> + )} + {command && ( + <div className="group relative"> + <pre className="bg-muted scrollbar-none overflow-x-auto rounded-md p-2 font-mono text-xs"> + {command} + </pre> + <div className="bg-muted pointer-events-none absolute inset-y-0 right-0 flex items-center rounded-r-md px-1"> + <div className="to-muted pointer-events-none absolute inset-y-0 -left-3 w-3 bg-gradient-to-r from-transparent" /> + <button + type="button" + onClick={handleCopy} + className="text-muted-foreground hover:bg-foreground/10 hover:text-foreground pointer-events-auto rounded p-1 opacity-0 transition-opacity group-hover:opacity-100" + title="Copy command" + > + {copied ? ( + <CheckIcon className="size-3.5" /> + ) : ( + <CopyIcon className="size-3.5" /> + )} + </button> + </div> + </div> + )} + </div> + ); +} + +interface CliCommand { + label: string; + description?: string; + command?: string; +} + +interface CliCommands { + primary: CliCommand; + alternative: CliCommand; + manual: CliCommand[]; + summary: string[]; +} + +function generateCliCommands(config: BuilderConfig): CliCommands { + const { components } = config; + + const matchingPreset = configMatchesPreset(config); + const playgroundInitUrl = `${BASE_URL}/playground/init`; + const presetUrl = matchingPreset + ? `${playgroundInitUrl}?preset=${matchingPreset.id}` + : `${playgroundInitUrl}?c=${encodeConfig(config)}`; + + const componentsToAdd: string[] = ["thread"]; + + if (components.markdown) { + componentsToAdd.push("markdown-text", "tool-fallback"); + } + + componentsToAdd.push("tooltip-icon-button"); + + if (components.attachments) { + componentsToAdd.push("attachment"); + } + + if (components.reasoning) { + componentsToAdd.push("reasoning"); + } + + if (components.sources) { + componentsToAdd.push("sources"); + } + + const addCommand = `npx assistant-ui@latest add ${componentsToAdd.join(" ")}`; + + const enabledFeatures: string[] = []; + if (components.markdown) enabledFeatures.push("Markdown"); + if (components.attachments) enabledFeatures.push("Attachments"); + if (components.branchPicker) enabledFeatures.push("Branch Picker"); + if (components.editMessage) enabledFeatures.push("Edit Message"); + if (components.threadWelcome) enabledFeatures.push("Welcome Screen"); + if (components.suggestions) enabledFeatures.push("Suggestions"); + if (components.scrollToBottom) enabledFeatures.push("Scroll to Bottom"); + if (components.reasoning) enabledFeatures.push("Reasoning"); + if (components.sources) enabledFeatures.push("Sources"); + if (components.followUpSuggestions) enabledFeatures.push("Follow-ups"); + if (components.avatar) enabledFeatures.push("Avatar"); + if (components.actionBar.copy) enabledFeatures.push("Copy"); + if (components.actionBar.reload) enabledFeatures.push("Reload"); + if (components.actionBar.speak) enabledFeatures.push("Speak"); + if (components.actionBar.feedback) enabledFeatures.push("Feedback"); + + const summary: string[] = [ + `Style: ${config.styles.borderRadius} radius, ${config.styles.fontFamily}`, + `Enabled: ${enabledFeatures.length > 0 ? enabledFeatures.join(", ") : "None"}`, + ]; + + return { + primary: { + label: "One-command setup", + description: "Install with your current configuration", + command: `npx assistant-ui@latest create my-app --preset "${presetUrl}"`, + }, + alternative: { + label: "Using shadcn", + command: `npx shadcn@latest add "${presetUrl}"`, + }, + manual: [ + { + label: "Initialize", + command: "npx assistant-ui@latest init", + }, + { + label: "Add components", + command: addCommand, + }, + { + label: "Copy code", + description: "Copy the code from the Code view into your thread.tsx", + }, + ], + summary, + }; +} diff --git a/apps/docs/components/builder/presets.ts b/apps/docs/components/builder/presets.ts new file mode 100644 index 0000000..71a32f4 --- /dev/null +++ b/apps/docs/components/builder/presets.ts @@ -0,0 +1,458 @@ +import type { Preset, BuilderConfig, ThemeColor } from "./types"; + +// Helper to create a ThemeColor +const color = (light: string, dark: string): ThemeColor => ({ light, dark }); + +export const PRESETS: Preset[] = [ + { + id: "default", + name: "Default", + description: "Clean, modern design with all features enabled", + config: { + components: { + attachments: true, + branchPicker: true, + editMessage: true, + actionBar: { + copy: true, + reload: true, + speak: false, + feedback: false, + }, + threadWelcome: true, + suggestions: true, + scrollToBottom: true, + markdown: true, + codeHighlightTheme: "vitesse", + reasoning: false, + sources: false, + followUpSuggestions: false, + avatar: false, + typingIndicator: "dot", + loadingIndicator: "text", + loadingText: "Thinking...", + }, + styles: { + theme: "light", + colors: { + accent: color("#0ea5e9", "#0ea5e9"), + }, + borderRadius: "lg", + maxWidth: "44rem", + fontFamily: "system-ui", + fontSize: "14px", + messageSpacing: "comfortable", + userMessagePosition: "right", + animations: true, + }, + }, + }, + { + id: "chatgpt", + name: "ChatGPT", + description: "Dark theme inspired by ChatGPT's interface", + config: { + components: { + attachments: true, + branchPicker: true, + editMessage: true, + actionBar: { + copy: true, + reload: true, + speak: false, + feedback: true, + }, + threadWelcome: true, + suggestions: false, + scrollToBottom: false, + markdown: true, + codeHighlightTheme: "github", + reasoning: false, + sources: false, + followUpSuggestions: false, + avatar: true, + typingIndicator: "dot", + loadingIndicator: "text", + loadingText: "Thinking...", + }, + styles: { + theme: "dark", + colors: { + accent: color("#10a37f", "#ffffff"), + background: color("#ffffff", "#212121"), + foreground: color("#0d0d0d", "#ececec"), + mutedForeground: color("#6e6e6e", "#9b9b9b"), + border: color("#e5e5e5", "#424242"), + userMessage: color("#f7f7f8", "#2f2f2f"), + composer: color("#f7f7f8", "#2f2f2f"), + userAvatar: color("#10a37f", "#19c37d"), + assistantAvatar: color("#000000", "#ab68ff"), + }, + borderRadius: "full", + maxWidth: "48rem", + fontFamily: "system-ui", + fontSize: "14px", + messageSpacing: "comfortable", + userMessagePosition: "right", + animations: true, + }, + }, + }, + { + id: "claude", + name: "Claude", + description: "Warm, elegant design inspired by Claude's interface", + config: { + components: { + attachments: true, + branchPicker: false, + editMessage: true, + actionBar: { + copy: true, + reload: true, + speak: false, + feedback: true, + }, + threadWelcome: false, + suggestions: false, + scrollToBottom: false, + markdown: true, + codeHighlightTheme: "vitesse", + reasoning: true, + sources: false, + followUpSuggestions: false, + avatar: true, + typingIndicator: "dot", + loadingIndicator: "text", + loadingText: "Thinking...", + }, + styles: { + theme: "light", + colors: { + accent: color("#c96442", "#d4a27a"), + background: color("#faf9f5", "#2b2a27"), + foreground: color("#3d3929", "#e8e4d9"), + mutedForeground: color("#8b8579", "#9b9589"), + border: color("#e5e0d5", "#3d3b36"), + userMessage: color("#f5f0e6", "#3d3b36"), + composer: color("#ffffff", "#1f1e1b"), + userAvatar: color("#c96442", "#d4a27a"), + assistantAvatar: color("#c96442", "#d4a27a"), + suggestion: color("#faf8f5", "#3d3b36"), + suggestionBorder: color("#e5e0d5", "#4d4b46"), + }, + borderRadius: "lg", + maxWidth: "48rem", + fontFamily: "Georgia, serif", + fontSize: "14px", + messageSpacing: "spacious", + userMessagePosition: "right", + animations: true, + }, + }, + }, + { + id: "perplexity", + name: "Perplexity", + description: "Search-focused design with prominent answers", + config: { + components: { + attachments: true, + branchPicker: true, + editMessage: false, + actionBar: { + copy: true, + reload: true, + speak: false, + feedback: false, + }, + threadWelcome: true, + suggestions: false, + scrollToBottom: true, + markdown: true, + codeHighlightTheme: "tokyo-night", + reasoning: false, + sources: false, + followUpSuggestions: true, + avatar: false, + typingIndicator: "dot", + loadingIndicator: "text", + loadingText: "Thinking...", + }, + styles: { + theme: "dark", + colors: { + accent: color("#20b8cd", "#22d3ee"), + background: color("#ffffff", "#0f172a"), + foreground: color("#1e293b", "#e2e8f0"), + mutedForeground: color("#64748b", "#94a3b8"), + border: color("#e2e8f0", "#334155"), + userMessage: color("#f1f5f9", "#1e293b"), + composer: color("#f8fafc", "#1e293b"), + }, + borderRadius: "lg", + maxWidth: "42rem", + fontFamily: "system-ui", + fontSize: "14px", + messageSpacing: "comfortable", + userMessagePosition: "left", + animations: true, + }, + }, + }, + { + id: "minimal", + name: "Minimal", + description: "Stripped-down interface with only essential features", + config: { + components: { + attachments: false, + branchPicker: false, + editMessage: false, + actionBar: { + copy: true, + reload: false, + speak: false, + feedback: false, + }, + threadWelcome: false, + suggestions: false, + scrollToBottom: false, + markdown: true, + codeHighlightTheme: "none", + reasoning: false, + sources: false, + followUpSuggestions: false, + avatar: false, + typingIndicator: "none", + loadingIndicator: "text", + loadingText: "Thinking...", + }, + styles: { + theme: "light", + colors: { + accent: color("#171717", "#fafafa"), + }, + borderRadius: "md", + maxWidth: "40rem", + fontFamily: "system-ui", + fontSize: "14px", + messageSpacing: "compact", + userMessagePosition: "right", + animations: false, + }, + }, + }, + { + id: "gemini", + name: "Gemini", + description: "Google's Gemini-inspired clean interface", + config: { + components: { + attachments: true, + branchPicker: false, + editMessage: true, + actionBar: { + copy: true, + reload: true, + speak: true, + feedback: true, + }, + threadWelcome: true, + suggestions: true, + scrollToBottom: true, + markdown: true, + codeHighlightTheme: "vitesse", + reasoning: false, + sources: false, + followUpSuggestions: true, + avatar: false, + typingIndicator: "dot", + loadingIndicator: "text", + loadingText: "Thinking...", + }, + styles: { + theme: "light", + colors: { + accent: color("#1a73e8", "#8ab4f8"), + background: color("#ffffff", "#1f1f1f"), + foreground: color("#1f1f1f", "#e3e3e3"), + mutedForeground: color("#5f6368", "#9aa0a6"), + border: color("#e8eaed", "#3c4043"), + userMessage: color("#e8f0fe", "#303134"), + composer: color("#ffffff", "#303134"), + suggestion: color("#f8f9fa", "#303134"), + suggestionBorder: color("#dadce0", "#5f6368"), + }, + borderRadius: "full", + maxWidth: "44rem", + fontFamily: "system-ui", + fontSize: "14px", + messageSpacing: "comfortable", + userMessagePosition: "right", + animations: true, + }, + }, + }, + { + id: "copilot", + name: "Copilot", + description: "GitHub Copilot inspired developer chat", + config: { + components: { + attachments: true, + branchPicker: true, + editMessage: true, + actionBar: { + copy: true, + reload: true, + speak: false, + feedback: true, + }, + threadWelcome: true, + suggestions: true, + scrollToBottom: true, + markdown: true, + codeHighlightTheme: "one-dark-pro", + reasoning: true, + sources: false, + followUpSuggestions: false, + avatar: true, + typingIndicator: "dot", + loadingIndicator: "text", + loadingText: "Thinking...", + }, + styles: { + theme: "dark", + colors: { + accent: color("#8b5cf6", "#8b5cf6"), + background: color("#ffffff", "#0d1117"), + foreground: color("#1f2328", "#e6edf3"), + mutedForeground: color("#656d76", "#7d8590"), + border: color("#d0d7de", "#30363d"), + userMessage: color("#f6f8fa", "#161b22"), + composer: color("#f6f8fa", "#161b22"), + userAvatar: color("#1a7f37", "#238636"), + assistantAvatar: color("#8b5cf6", "#8b5cf6"), + suggestion: color("#f6f8fa", "#161b22"), + suggestionBorder: color("#d0d7de", "#30363d"), + }, + borderRadius: "lg", + maxWidth: "56rem", + fontFamily: "ui-monospace, monospace", + fontSize: "13px", + messageSpacing: "compact", + userMessagePosition: "right", + animations: true, + }, + }, + }, + { + id: "slack", + name: "Slack", + description: "Team chat inspired collaborative interface", + config: { + components: { + attachments: true, + branchPicker: false, + editMessage: true, + actionBar: { + copy: true, + reload: false, + speak: false, + feedback: true, + }, + threadWelcome: false, + suggestions: false, + scrollToBottom: true, + markdown: true, + codeHighlightTheme: "vitesse", + reasoning: false, + sources: false, + followUpSuggestions: false, + avatar: true, + typingIndicator: "dot", + loadingIndicator: "text", + loadingText: "Thinking...", + }, + styles: { + theme: "light", + colors: { + accent: color("#007a5a", "#2eb67d"), + background: color("#ffffff", "#1a1d21"), + foreground: color("#1d1c1d", "#d1d2d3"), + mutedForeground: color("#616061", "#ababad"), + border: color("#dddddd", "#323539"), + userAvatar: color("#007a5a", "#2eb67d"), + assistantAvatar: color("#4a154b", "#611f69"), + }, + borderRadius: "sm", + maxWidth: "100%", + fontFamily: "system-ui", + fontSize: "14px", + messageSpacing: "compact", + userMessagePosition: "left", + animations: false, + }, + }, + }, + { + id: "grok", + name: "Grok", + description: "xAI's Grok-inspired minimal dark interface", + config: { + components: { + attachments: true, + branchPicker: false, + editMessage: true, + actionBar: { + copy: true, + reload: true, + speak: false, + feedback: true, + }, + threadWelcome: false, + suggestions: false, + scrollToBottom: false, + markdown: true, + codeHighlightTheme: "dracula", + reasoning: false, + sources: false, + followUpSuggestions: false, + avatar: false, + typingIndicator: "dot", + loadingIndicator: "text", + loadingText: "Thinking...", + }, + styles: { + theme: "dark", + colors: { + accent: color("#000000", "#ffffff"), + background: color("#ffffff", "#000000"), + foreground: color("#000000", "#ffffff"), + mutedForeground: color("#52525b", "#71717a"), + border: color("#e4e4e7", "#27272a"), + userMessage: color("#f4f4f5", "#18181b"), + composer: color("#f4f4f5", "#18181b"), + }, + borderRadius: "full", + maxWidth: "48rem", + fontFamily: "system-ui", + fontSize: "14px", + messageSpacing: "comfortable", + userMessagePosition: "right", + animations: true, + }, + }, + }, +]; + +export function getPresetById(id: string): Preset | undefined { + return PRESETS.find((preset) => preset.id === id); +} + +export function configMatchesPreset(config: BuilderConfig): Preset | undefined { + return PRESETS.find( + (preset) => JSON.stringify(preset.config) === JSON.stringify(config), + ); +} diff --git a/apps/docs/components/builder/share-button.tsx b/apps/docs/components/builder/share-button.tsx new file mode 100644 index 0000000..eec7867 --- /dev/null +++ b/apps/docs/components/builder/share-button.tsx @@ -0,0 +1,79 @@ +"use client"; + +import { useState, useCallback } from "react"; +import { Check, Link2 } from "lucide-react"; +import { cn } from "@/lib/utils"; +import { analytics } from "@/lib/analytics"; + +interface ShareButtonProps { + className?: string; +} + +export function ShareButton({ className }: ShareButtonProps) { + const [copied, setCopied] = useState(false); + + const handleShare = useCallback(async () => { + analytics.builder.shareClicked(); + const url = window.location.href; + + // Try Web Share API on mobile + if (navigator.share && /mobile|android/i.test(navigator.userAgent)) { + try { + await navigator.share({ + title: "assistant-ui Playground", + text: "Check out my chat UI configuration", + url, + }); + return; + } catch { + // User cancelled or not supported, fall through to copy + } + } + + // Copy to clipboard + try { + await navigator.clipboard.writeText(url); + setCopied(true); + setTimeout(() => setCopied(false), 2000); + } catch { + // Fallback for older browsers + const textArea = document.createElement("textarea"); + textArea.value = url; + textArea.style.position = "fixed"; + textArea.style.left = "-9999px"; + document.body.appendChild(textArea); + textArea.select(); + document.execCommand("copy"); + document.body.removeChild(textArea); + setCopied(true); + setTimeout(() => setCopied(false), 2000); + } + }, []); + + return ( + <button + type="button" + onClick={handleShare} + className={cn( + "flex items-center gap-1.5 rounded-md px-2.5 py-1 text-xs font-medium transition-colors", + copied + ? "bg-green-500/10 text-green-600 dark:text-green-400" + : "text-muted-foreground hover:text-foreground", + className, + )} + title={copied ? "Link copied to clipboard" : "Copy shareable link"} + > + {copied ? ( + <> + <Check className="size-3.5" /> + <span className="hidden sm:inline">Copied!</span> + </> + ) : ( + <> + <Link2 className="size-3.5" /> + <span className="hidden sm:inline">Share</span> + </> + )} + </button> + ); +} diff --git a/apps/docs/components/builder/types.ts b/apps/docs/components/builder/types.ts new file mode 100644 index 0000000..d94f055 --- /dev/null +++ b/apps/docs/components/builder/types.ts @@ -0,0 +1,216 @@ +export type Theme = "light" | "dark" | "system"; +export type BorderRadius = "none" | "sm" | "md" | "lg" | "full"; +export type FontSize = "13px" | "14px" | "15px" | "16px"; +export type MessageSpacing = "compact" | "comfortable" | "spacious"; +export type TypingIndicator = "none" | "dot"; +export type LoadingIndicator = "none" | "spinner" | "text"; +export type CodeHighlightTheme = + | "none" + | "github" + | "vitesse" + | "tokyo-night" + | "one-dark-pro" + | "dracula"; + +export interface ActionBarConfig { + copy: boolean; + reload: boolean; + speak: boolean; + feedback: boolean; +} + +export interface ComponentsConfig { + attachments: boolean; + branchPicker: boolean; + editMessage: boolean; + actionBar: ActionBarConfig; + threadWelcome: boolean; + suggestions: boolean; + scrollToBottom: boolean; + markdown: boolean; + codeHighlightTheme: CodeHighlightTheme; + reasoning: boolean; + sources: boolean; + followUpSuggestions: boolean; + avatar: boolean; + typingIndicator: TypingIndicator; + loadingIndicator: LoadingIndicator; + loadingText: string; +} + +export type UserMessagePosition = "right" | "left"; + +export interface ThemeColor { + light: string; + dark: string; +} + +export interface StylesConfig { + theme: Theme; + + // Colors (with light/dark variants) + colors: { + accent: ThemeColor; + background?: ThemeColor; + foreground?: ThemeColor; + muted?: ThemeColor; + mutedForeground?: ThemeColor; + border?: ThemeColor; + userMessage?: ThemeColor; + assistantMessage?: ThemeColor; + composer?: ThemeColor; + userAvatar?: ThemeColor; + assistantAvatar?: ThemeColor; + suggestion?: ThemeColor; + suggestionBorder?: ThemeColor; + }; + + // Layout + borderRadius: BorderRadius; + maxWidth: string; + fontFamily: string; + fontSize: FontSize; + messageSpacing: MessageSpacing; + userMessagePosition: UserMessagePosition; + animations: boolean; +} + +export interface BuilderConfig { + components: ComponentsConfig; + styles: StylesConfig; + customCSS?: string; +} + +export interface Preset { + id: string; + name: string; + description: string; + config: BuilderConfig; +} + +// Default theme colors +export const DEFAULT_COLORS = { + accent: { light: "#0ea5e9", dark: "#0ea5e9" }, + background: { light: "#ffffff", dark: "#18181b" }, + foreground: { light: "#09090b", dark: "#fafafa" }, + muted: { light: "#f4f4f5", dark: "#27272a" }, + mutedForeground: { light: "#71717a", dark: "#a1a1aa" }, + border: { light: "#e4e4e7", dark: "#3f3f46" }, + userMessage: { light: "#f4f4f5", dark: "#27272a" }, + composer: { light: "#ffffff", dark: "#27272a" }, + userAvatar: { light: "#e4e4e7", dark: "#3f3f46" }, + assistantAvatar: { light: "#e4e4e7", dark: "#3f3f46" }, + suggestion: { light: "#fafafa", dark: "#27272a" }, + suggestionBorder: { light: "#e4e4e7", dark: "#3f3f46" }, +} as const; + +export const DEFAULT_CONFIG: BuilderConfig = { + components: { + attachments: true, + branchPicker: true, + editMessage: true, + actionBar: { + copy: true, + reload: true, + speak: false, + feedback: false, + }, + threadWelcome: true, + suggestions: true, + scrollToBottom: true, + markdown: true, + codeHighlightTheme: "vitesse", + reasoning: false, + sources: false, + followUpSuggestions: false, + avatar: false, + typingIndicator: "dot", + loadingIndicator: "text", + loadingText: "Thinking...", + }, + styles: { + theme: "light", + colors: { + accent: DEFAULT_COLORS.accent, + }, + borderRadius: "lg", + maxWidth: "44rem", + fontFamily: "system-ui", + fontSize: "14px", + messageSpacing: "comfortable", + userMessagePosition: "right", + animations: true, + }, +}; + +export const FONT_FAMILIES = [ + { label: "System", value: "system-ui" }, + { label: "Inter", value: "Inter, sans-serif" }, + { label: "Geist", value: "GeistSans, sans-serif" }, + { label: "Serif", value: "Georgia, serif" }, + { label: "Mono", value: "ui-monospace, monospace" }, +] as const; + +export const FONT_SIZES = [ + { label: "13px", value: "13px" }, + { label: "14px", value: "14px" }, + { label: "15px", value: "15px" }, + { label: "16px", value: "16px" }, +] as const; + +export const MAX_WIDTHS = [ + { label: "Narrow", value: "32rem" }, + { label: "Default", value: "44rem" }, + { label: "Wide", value: "56rem" }, + { label: "Full", value: "100%" }, +] as const; + +export const MESSAGE_SPACINGS = [ + { label: "Compact", value: "compact" }, + { label: "Comfortable", value: "comfortable" }, + { label: "Spacious", value: "spacious" }, +] as const; + +export const USER_MESSAGE_POSITIONS = [ + { label: "Left", value: "left" }, + { label: "Right", value: "right" }, +] as const; + +export const BORDER_RADIUSES = [ + { label: "None", value: "none" }, + { label: "Small", value: "sm" }, + { label: "Medium", value: "md" }, + { label: "Large", value: "lg" }, + { label: "Full", value: "full" }, +] as const; + +export const TYPING_INDICATORS = [ + { label: "None", value: "none" }, + { label: "Dot (●)", value: "dot" }, +] as const; + +export const LOADING_INDICATORS = [ + { label: "None", value: "none" }, + { label: "Spinner", value: "spinner" }, + { label: "Text", value: "text" }, +] as const; + +export const CODE_HIGHLIGHT_THEMES = [ + { label: "None", value: "none" }, + { label: "GitHub", value: "github" }, + { label: "Vitesse", value: "vitesse" }, + { label: "Tokyo Night", value: "tokyo-night" }, + { label: "One Dark Pro", value: "one-dark-pro" }, + { label: "Dracula", value: "dracula" }, +] as const; + +export const SHIKI_THEME_MAP: Record< + Exclude<CodeHighlightTheme, "none">, + { dark: string; light: string } +> = { + github: { dark: "github-dark", light: "github-light" }, + vitesse: { dark: "vitesse-dark", light: "vitesse-light" }, + "tokyo-night": { dark: "tokyo-night", light: "github-light" }, + "one-dark-pro": { dark: "one-dark-pro", light: "github-light" }, + dracula: { dark: "dracula", light: "github-light" }, +}; diff --git a/apps/docs/components/careers/apply-form.tsx b/apps/docs/components/careers/apply-form.tsx new file mode 100644 index 0000000..b515de7 --- /dev/null +++ b/apps/docs/components/careers/apply-form.tsx @@ -0,0 +1,166 @@ +"use client"; + +import { type FormEvent, useCallback, useState } from "react"; + +interface ApplyFormProps { + roleTitle: string; +} + +export const ApplyForm = ({ roleTitle }: ApplyFormProps) => { + const [fallbackVisible, setFallbackVisible] = useState(false); + const [mailtoHref, setMailtoHref] = useState(""); + const [composedBody, setComposedBody] = useState(""); + const [copyStatus, setCopyStatus] = useState<"idle" | "success" | "error">( + "idle", + ); + + const handleSubmit = useCallback( + (event: FormEvent<HTMLFormElement>) => { + event.preventDefault(); + const form = event.currentTarget; + const formData = new FormData(form); + + const fullName = (formData.get("full_name") as string)?.trim() ?? ""; + const urlsRaw = (formData.get("urls") as string)?.trim() ?? ""; + const notes = (formData.get("notes") as string)?.trim() ?? ""; + + const urlLines = urlsRaw + .split(/\r?\n/) + .map((line) => line.trim()) + .filter(Boolean) + .map((line) => `- ${line}`); + + const bodyLines = [ + "Hello,", + "", + `I am applying for the ${roleTitle} role.`, + "", + "Here are a few interesting links to showcase my profile:", + urlLines.length ? urlLines.join("\n") : "-", + ]; + + if (notes) { + bodyLines.push("", notes); + } + + bodyLines.push("", "Best,", fullName || ""); + + const body = bodyLines.join("\n"); + + const mailto = `mailto:careers@assistant-ui.com?subject=${encodeURIComponent( + `Application: ${roleTitle}`, + )}&body=${encodeURIComponent(body)}`; + + // Attempt to open the user's default mail client. + // If it doesn't open (no handler configured), reveal fallbacks. + setComposedBody(body); + setMailtoHref(mailto); + setFallbackVisible(true); + try { + window.location.href = mailto; + } catch { + // No-op: rely on the fallback UI + } + }, + [roleTitle], + ); + + return ( + <form onSubmit={handleSubmit} className="grid gap-5"> + <label className="grid gap-2 text-sm"> + <span className="text-foreground font-medium">Full Name</span> + <input + type="text" + name="full_name" + required + autoComplete="name" + className="border-border bg-background focus:border-primary rounded-lg border px-3 py-2 text-base shadow-sm ring-0 transition outline-none" + placeholder="Ada Lovelace" + /> + </label> + + <label className="grid gap-2 text-sm"> + <span className="text-foreground font-medium"> + URLs that best describe you + </span> + <textarea + name="urls" + required + rows={3} + className="border-border bg-background focus:border-primary rounded-lg border px-3 py-2 text-base shadow-sm ring-0 transition outline-none" + placeholder="Portfolio, GitHub, LinkedIn, blog – one per line" + ></textarea> + </label> + + <label className="grid gap-2 text-sm"> + <span className="text-foreground font-medium"> + Anything else?{" "} + <span className="text-muted-foreground">(optional)</span> + </span> + <textarea + name="notes" + rows={4} + className="border-border bg-background focus:border-primary rounded-lg border px-3 py-2 text-base shadow-sm ring-0 transition outline-none" + placeholder="Tell us about goals, timelines, or anything you'd like us to know." + ></textarea> + </label> + + <button + type="submit" + className="border-border text-foreground hover:border-primary hover:bg-primary/5 hover:text-primary inline-flex w-fit items-center justify-center rounded-full border px-5 py-2 text-sm font-medium transition" + > + Apply now + </button> + + {fallbackVisible ? ( + <div className="border-border/70 bg-background/50 mt-2 grid gap-2 rounded-lg border border-dashed p-3"> + <p className="text-muted-foreground text-xs"> + If your email client didn't open, use the options below. + </p> + <div className="flex flex-wrap items-center gap-2"> + <a + href={mailtoHref} + className="border-border text-foreground hover:border-primary hover:bg-primary/5 hover:text-primary inline-flex items-center justify-center rounded-full border px-4 py-1.5 text-xs font-medium transition" + > + Open email client + </a> + <button + type="button" + className="border-border text-foreground hover:border-primary hover:bg-primary/5 hover:text-primary inline-flex items-center justify-center rounded-full border px-4 py-1.5 text-xs font-medium transition" + onClick={async () => { + try { + const text = `To: careers@assistant-ui.com +Subject: Application: ${roleTitle} + +${composedBody}`; + await navigator.clipboard.writeText(text); + setCopyStatus("success"); + setTimeout(() => setCopyStatus("idle"), 2000); + } catch { + setCopyStatus("error"); + setTimeout(() => setCopyStatus("idle"), 2000); + } + }} + > + Copy email text + </button> + <span className="text-muted-foreground text-xs"> + {copyStatus === "success" + ? "Copied!" + : copyStatus === "error" + ? "Copy failed" + : ""} + </span> + </div> + <p className="text-muted-foreground text-[11px]"> + Or email{" "} + <span className="text-foreground font-medium"> + careers@assistant-ui.com + </span>{" "} + with the subject "Application: {roleTitle}". + </p> + </div> + ) : null} + </form> + ); +}; diff --git a/apps/docs/components/docs/assistant/assistant-action-bar.tsx b/apps/docs/components/docs/assistant/assistant-action-bar.tsx new file mode 100644 index 0000000..d957183 --- /dev/null +++ b/apps/docs/components/docs/assistant/assistant-action-bar.tsx @@ -0,0 +1,236 @@ +"use client"; + +import { useEffect, useMemo, useRef, useState, type ReactNode } from "react"; +import { ActionBarPrimitive, AuiIf } from "@assistant-ui/react"; +import { useAui, useAuiState } from "@assistant-ui/store"; +import { + ThumbsUpIcon, + ThumbsDownIcon, + CopyIcon, + CheckIcon, +} from "lucide-react"; +import { toast } from "sonner"; +import { cn } from "@/lib/utils"; +import { analytics } from "@/lib/analytics"; +import { getTextLength, getToolCallToolNames } from "@/lib/assistant-metrics"; +import { FeedbackPopover, type FeedbackCategory } from "./feedback-popover"; + +const NON_WHITESPACE_RE = /\S/; + +function hasNonWhitespaceText( + parts: readonly { type: string; text?: string }[], +): boolean { + for (const part of parts) { + if (part.type !== "text" || !part.text) continue; + if (NON_WHITESPACE_RE.test(part.text)) return true; + } + return false; +} + +function getErrorDetails(error: unknown): { + error_name: string; + error_message: string; +} { + if (error instanceof Error) { + return { + error_name: error.name, + error_message: error.message || "Unknown error", + }; + } + + return { + error_name: "UnknownError", + error_message: typeof error === "string" ? error : String(error), + }; +} + +export function AssistantActionBar(): ReactNode { + const [popoverOpen, setPopoverOpen] = useState(false); + const feedbackShownForMessageRef = useRef<string | null>(null); + const feedbackSubmissionStartedRef = useRef<string | null>(null); + + const aui = useAui(); + const messageId = useAuiState((s) => s.message.id); + const parentId = useAuiState((s) => s.message.parentId); + const content = useAuiState((s) => s.message.content); + const threadId = useAuiState((s) => s.threadListItem.id); + const messages = useAuiState((s) => s.thread.messages); + const isRunning = useAuiState((s) => s.message.status?.type === "running"); + const submittedFeedback = useAuiState( + (s) => + s.message.metadata?.submittedFeedback?.type as + | "positive" + | "negative" + | undefined, + ); + + const userMessage = useMemo( + () => messages.find((m) => m.id === parentId), + [messages, parentId], + ); + const userQuestionLength = userMessage + ? getTextLength(userMessage.content) + : 0; + const assistantResponseLength = getTextLength(content); + const toolNames = getToolCallToolNames(content); + const toolCallsCount = toolNames.length; + const assistantHasText = hasNonWhitespaceText(content); + const feedbackBaseProps = useMemo( + () => ({ + threadId, + messageId, + user_question_length: userQuestionLength, + assistant_response_length: assistantResponseLength, + tool_calls_count: toolCallsCount, + ...(toolNames.length > 0 ? { tool_names: toolNames.join(",") } : {}), + }), + [ + assistantResponseLength, + messageId, + threadId, + toolCallsCount, + toolNames, + userQuestionLength, + ], + ); + + useEffect(() => { + if (isRunning || !assistantHasText) return; + if (feedbackShownForMessageRef.current === messageId) return; + + feedbackShownForMessageRef.current = messageId; + analytics.assistant.feedbackShown(feedbackBaseProps); + }, [assistantHasText, feedbackBaseProps, isRunning, messageId]); + + // Don't show feedback buttons while message is still streaming or if no content + if (isRunning || !assistantHasText) { + return null; + } + + const handlePositiveFeedback = () => { + if (submittedFeedback || feedbackSubmissionStartedRef.current === messageId) + return; + feedbackSubmissionStartedRef.current = messageId; + + analytics.assistant.feedbackClicked({ + ...feedbackBaseProps, + type: "positive", + }); + + try { + aui.message().submitFeedback({ type: "positive" }); + } catch (error) { + feedbackSubmissionStartedRef.current = null; + analytics.assistant.feedbackSubmitFailed({ + ...feedbackBaseProps, + type: "positive", + ...getErrorDetails(error), + }); + toast.error("Failed to submit feedback. Please try again."); + return; + } + + analytics.assistant.feedbackSubmitted({ + ...feedbackBaseProps, + type: "positive", + }); + }; + + const handleNegativeFeedback = ( + category: FeedbackCategory, + comment?: string, + ) => { + if (submittedFeedback || feedbackSubmissionStartedRef.current === messageId) + return; + feedbackSubmissionStartedRef.current = messageId; + + const negativeFeedbackProps = { + ...feedbackBaseProps, + type: "negative" as const, + category, + ...(comment ? { comment_length: comment.length } : {}), + }; + + analytics.assistant.feedbackClicked(negativeFeedbackProps); + + try { + aui.message().submitFeedback({ type: "negative" }); + } catch (error) { + feedbackSubmissionStartedRef.current = null; + analytics.assistant.feedbackSubmitFailed({ + ...negativeFeedbackProps, + ...getErrorDetails(error), + }); + toast.error("Failed to submit feedback. Please try again."); + return; + } + + analytics.assistant.feedbackSubmitted({ + ...negativeFeedbackProps, + }); + }; + + return ( + <ActionBarPrimitive.Root className="mt-2 flex items-center gap-1"> + <ActionBarPrimitive.Copy + aria-label="Copy response" + className={cn( + "text-muted-foreground rounded p-1 transition-colors", + "hover:bg-muted hover:text-foreground", + )} + > + <AuiIf condition={(s) => s.message.isCopied}> + <CheckIcon className="size-4" /> + </AuiIf> + <AuiIf condition={(s) => !s.message.isCopied}> + <CopyIcon className="size-4" /> + </AuiIf> + </ActionBarPrimitive.Copy> + <button + type="button" + onClick={handlePositiveFeedback} + disabled={!!submittedFeedback} + aria-label={ + submittedFeedback === "positive" + ? "Positive feedback submitted" + : "Good response" + } + className={cn( + "text-muted-foreground rounded p-1 transition-colors", + "hover:bg-muted hover:text-foreground", + "disabled:cursor-not-allowed disabled:opacity-50", + submittedFeedback === "positive" && + "text-green-600 dark:text-green-400", + )} + > + <ThumbsUpIcon className="size-4" /> + </button> + + <FeedbackPopover + open={popoverOpen} + onOpenChange={setPopoverOpen} + onSubmit={handleNegativeFeedback} + > + <button + type="button" + onClick={() => setPopoverOpen(true)} + disabled={!!submittedFeedback} + aria-label={ + submittedFeedback === "negative" + ? "Negative feedback submitted" + : "Report issue with response" + } + className={cn( + "text-muted-foreground rounded p-1 transition-colors", + "hover:bg-muted hover:text-foreground", + "disabled:cursor-not-allowed disabled:opacity-50", + submittedFeedback === "negative" && + "text-red-600 dark:text-red-400", + )} + > + <ThumbsDownIcon className="size-4" /> + </button> + </FeedbackPopover> + </ActionBarPrimitive.Root> + ); +} diff --git a/apps/docs/components/docs/assistant/composer.tsx b/apps/docs/components/docs/assistant/composer.tsx new file mode 100644 index 0000000..de791bf --- /dev/null +++ b/apps/docs/components/docs/assistant/composer.tsx @@ -0,0 +1,191 @@ +"use client"; + +import { Button } from "@/components/ui/button"; +import { useCurrentPage } from "@/components/docs/contexts/current-page"; +import { ModelSelector } from "@/components/assistant-ui/model-selector"; +import { docsModelOptions } from "@/components/docs/assistant/docs-model-options"; +import { + DEFAULT_MODEL_ID, + resolveModelId, + type KnownModelId, +} from "@/constants/model"; +import { analytics } from "@/lib/analytics"; +import { getComposerMessageMetrics } from "@/lib/assistant-analytics-helpers"; +import { + AuiIf, + ComposerPrimitive, + useAui, + useAuiState, +} from "@assistant-ui/react"; +import { cn } from "@/lib/utils"; +import { ArrowUpIcon, SquareIcon } from "lucide-react"; +import { + useCallback, + useEffect, + useSyncExternalStore, + type ReactNode, +} from "react"; + +type ModelStoreListener = () => void; + +// Shared docs assistant model selection is intentionally global for current docs behavior, +// where a single model picker state should stay in sync across composer surfaces. +// If independent composer instances are introduced later, move this to a scoped React store. +let sharedDocsModelName: KnownModelId | undefined; +const modelStoreListeners = new Set<ModelStoreListener>(); + +const subscribeModelStore = (listener: ModelStoreListener) => { + modelStoreListeners.add(listener); + return () => { + modelStoreListeners.delete(listener); + }; +}; + +const setSharedDocsModelName = (modelName: KnownModelId) => { + if (sharedDocsModelName === modelName) return; + sharedDocsModelName = modelName; + modelStoreListeners.forEach((listener) => listener()); +}; + +const models = docsModelOptions(); + +export function useComposerSubmitHandler(onSubmitProp?: () => void) { + const aui = useAui(); + const threadId = useAuiState((s) => s.threadListItem.id); + const currentPage = useCurrentPage(); + const pathname = currentPage?.pathname; + + return () => { + const metrics = getComposerMessageMetrics(aui.composer().getState()); + if (!metrics) return; + + let modelName: string | undefined; + try { + modelName = aui.thread().getModelContext()?.config?.modelName; + } catch { + // ignore + } + + analytics.assistant.messageSent({ + threadId, + source: "composer", + message_length: metrics.messageLength, + attachments_count: metrics.attachmentsCount, + ...(pathname ? { pathname } : {}), + ...(modelName ? { model_name: modelName } : {}), + }); + + onSubmitProp?.(); + }; +} + +export function useSharedDocsModelSelection(): { + modelValue: string; + onModelChange: (value: string) => void; +} { + const aui = useAui(); + const threadId = useAuiState((s) => s.threadListItem.id); + + useEffect(() => { + if (!threadId) return; + + let nextModelName = DEFAULT_MODEL_ID; + try { + nextModelName = resolveModelId( + aui.thread().getModelContext()?.config?.modelName, + ); + } catch { + // ignore + } + + setSharedDocsModelName(nextModelName); + }, [aui, threadId]); + + const modelValue = useSyncExternalStore( + subscribeModelStore, + () => sharedDocsModelName ?? DEFAULT_MODEL_ID, + () => DEFAULT_MODEL_ID, + ); + + const onModelChange = useCallback((value: string) => { + setSharedDocsModelName(resolveModelId(value)); + }, []); + + return { modelValue, onModelChange }; +} + +export function AssistantComposer({ + onSubmit: onSubmitProp, + className, + placeholder = "Ask a question...", + modelSelector, +}: { + onSubmit?: () => void; + className?: string; + placeholder?: string; + modelSelector?: ReactNode; +} = {}): ReactNode { + const handleSubmit = useComposerSubmitHandler(onSubmitProp); + + return ( + <ComposerPrimitive.Root + onSubmit={handleSubmit} + className={cn("pb-2.5", className)} + > + <div className="border-border bg-background focus-within:border-foreground/60 rounded-xl border transition-colors"> + <ComposerPrimitive.Input asChild> + <textarea + placeholder={placeholder} + className="placeholder:text-muted-foreground field-sizing-content max-h-32 w-full resize-none bg-transparent px-3 pt-2.5 pb-2 text-sm leading-5 focus:outline-none" + rows={1} + /> + </ComposerPrimitive.Input> + <div className="flex items-center justify-between px-1.5 pb-1.5"> + {modelSelector ?? <DefaultDocsModelSelector />} + <AssistantComposerAction /> + </div> + </div> + </ComposerPrimitive.Root> + ); +} + +function DefaultDocsModelSelector(): ReactNode { + const { modelValue, onModelChange } = useSharedDocsModelSelection(); + + return ( + <ModelSelector + models={models} + value={modelValue} + onValueChange={onModelChange} + variant="ghost" + size="sm" + /> + ); +} + +export function AssistantComposerAction(): ReactNode { + return ( + <> + <AuiIf condition={(s) => !s.thread.isRunning}> + <ComposerPrimitive.Send asChild> + <Button size="icon" className="size-7 rounded-lg"> + <ArrowUpIcon className="size-4" /> + </Button> + </ComposerPrimitive.Send> + </AuiIf> + + <AuiIf condition={(s) => s.thread.isRunning}> + <ComposerPrimitive.Cancel asChild> + <Button + type="button" + variant="secondary" + size="icon" + className="size-7 rounded-lg" + > + <SquareIcon className="size-3 fill-current" /> + </Button> + </ComposerPrimitive.Cancel> + </AuiIf> + </> + ); +} diff --git a/apps/docs/components/docs/assistant/context.tsx b/apps/docs/components/docs/assistant/context.tsx new file mode 100644 index 0000000..6ee2eef --- /dev/null +++ b/apps/docs/components/docs/assistant/context.tsx @@ -0,0 +1,111 @@ +"use client"; + +import { + createContext, + useCallback, + useContext, + useEffect, + useState, + useSyncExternalStore, + type ReactNode, +} from "react"; + +const MIN_WIDTH = 320; +const MAX_WIDTH = 600; +const DEFAULT_WIDTH = 400; + +interface AssistantPanelContextValue { + open: boolean; + setOpen: (open: boolean) => void; + toggle: () => void; + width: number; + setWidth: (width: number) => void; + isResizing: boolean; + setIsResizing: (resizing: boolean) => void; + pendingMessage: string | null; + clearPendingMessage: () => void; + askAI: (message: string) => void; +} + +const AssistantPanelContext = createContext<AssistantPanelContextValue | null>( + null, +); + +export function useAssistantPanel() { + const ctx = useContext(AssistantPanelContext); + if (!ctx) { + throw new Error( + "useAssistantPanel must be used within AssistantPanelProvider", + ); + } + return ctx; +} + +type AskAIFn = (message: string) => void; +const askAIListeners = new Set<() => void>(); +let globalAskAI: AskAIFn | null = null; + +function subscribeAskAI(listener: () => void) { + askAIListeners.add(listener); + return () => askAIListeners.delete(listener); +} + +function getAskAISnapshot() { + return globalAskAI; +} + +export function useGlobalAskAI(): AskAIFn | null { + return useSyncExternalStore(subscribeAskAI, getAskAISnapshot, () => null); +} + +export function AssistantPanelProvider({ children }: { children: ReactNode }) { + const [open, setOpen] = useState(false); + const [width, setWidthState] = useState(DEFAULT_WIDTH); + const [isResizing, setIsResizing] = useState(false); + const [pendingMessage, setPendingMessage] = useState<string | null>(null); + + const toggle = useCallback(() => { + setOpen((prev) => !prev); + }, []); + + const setWidth = useCallback((newWidth: number) => { + setWidthState(Math.min(MAX_WIDTH, Math.max(MIN_WIDTH, newWidth))); + }, []); + + const clearPendingMessage = useCallback(() => { + setPendingMessage(null); + }, []); + + const askAI = useCallback((message: string) => { + setPendingMessage(message); + setOpen(true); + }, []); + + useEffect(() => { + globalAskAI = askAI; + for (const l of askAIListeners) l(); + return () => { + globalAskAI = null; + for (const l of askAIListeners) l(); + }; + }, [askAI]); + + return ( + <AssistantPanelContext.Provider + value={{ + open, + setOpen, + toggle, + width, + setWidth, + isResizing, + setIsResizing, + pendingMessage, + clearPendingMessage, + askAI, + }} + > + {children} + </AssistantPanelContext.Provider> + ); +} diff --git a/apps/docs/components/docs/assistant/docs-model-options.tsx b/apps/docs/components/docs/assistant/docs-model-options.tsx new file mode 100644 index 0000000..231783f --- /dev/null +++ b/apps/docs/components/docs/assistant/docs-model-options.tsx @@ -0,0 +1,19 @@ +import Image from "next/image"; +import { MODELS } from "@/constants/model"; + +export function docsModelOptions() { + return MODELS.map((model) => ({ + id: model.value, + name: model.name, + icon: ( + <Image + src={model.icon} + alt={model.name} + width={16} + height={16} + className="size-4" + /> + ), + ...(model.disabled ? { disabled: true as const } : undefined), + })); +} diff --git a/apps/docs/components/docs/assistant/feedback-popover.tsx b/apps/docs/components/docs/assistant/feedback-popover.tsx new file mode 100644 index 0000000..f5939ac --- /dev/null +++ b/apps/docs/components/docs/assistant/feedback-popover.tsx @@ -0,0 +1,113 @@ +"use client"; + +import { + useState, + type ReactNode, + type ReactElement, + isValidElement, +} from "react"; +import { Popover } from "@base-ui/react/popover"; +import { cn } from "@/lib/utils"; + +const CATEGORIES = [ + { value: "wrong_information", label: "Wrong information" }, + { value: "outdated", label: "Outdated" }, + { value: "didnt_answer", label: "Didn't answer my question" }, + { value: "too_vague", label: "Too vague" }, + { value: "other", label: "Other" }, +] as const; + +export type FeedbackCategory = (typeof CATEGORIES)[number]["value"]; + +type FeedbackPopoverProps = { + open: boolean; + onOpenChange: (open: boolean) => void; + onSubmit: (category: FeedbackCategory, comment?: string) => void; + children: ReactNode; +}; + +export function FeedbackPopover({ + open, + onOpenChange, + onSubmit, + children, +}: FeedbackPopoverProps): ReactNode { + const [category, setCategory] = useState<FeedbackCategory | null>(null); + const [comment, setComment] = useState(""); + + const handleSubmit = () => { + if (!category) return; + onSubmit(category, comment || undefined); + setCategory(null); + setComment(""); + onOpenChange(false); + }; + + const handleOpenChange = (isOpen: boolean) => { + if (!isOpen) { + setCategory(null); + setComment(""); + } + onOpenChange(isOpen); + }; + + return ( + <Popover.Root open={open} onOpenChange={handleOpenChange}> + {isValidElement(children) ? ( + <Popover.Trigger render={children as ReactElement} /> + ) : ( + <Popover.Trigger>{children}</Popover.Trigger> + )} + <Popover.Portal> + <Popover.Positioner sideOffset={5} align="start"> + <Popover.Popup className="border-border bg-popover z-50 w-72 rounded-lg border p-4 shadow-md"> + <div className="space-y-3"> + <p className="text-sm font-medium">What went wrong?</p> + <div className="space-y-2"> + {CATEGORIES.map((cat) => ( + <label + key={cat.value} + className="flex cursor-pointer items-center gap-2 text-sm" + > + <input + type="radio" + name="feedback-category" + value={cat.value} + checked={category === cat.value} + onChange={() => setCategory(cat.value)} + className="accent-primary" + /> + {cat.label} + </label> + ))} + </div> + <textarea + placeholder="Additional details (optional)" + value={comment} + onChange={(e) => setComment(e.target.value)} + className={cn( + "border-border bg-background w-full resize-none rounded-md border px-3 py-2 text-sm", + "placeholder:text-muted-foreground focus:ring-ring focus:ring-1 focus:outline-none", + )} + rows={2} + /> + <button + type="button" + onClick={handleSubmit} + disabled={!category} + className={cn( + "bg-primary text-primary-foreground w-full rounded-md px-3 py-1.5 text-sm font-medium", + "disabled:cursor-not-allowed disabled:opacity-50", + "hover:bg-primary/90", + )} + > + Submit + </button> + </div> + <Popover.Arrow className="fill-popover" /> + </Popover.Popup> + </Popover.Positioner> + </Popover.Portal> + </Popover.Root> + ); +} diff --git a/apps/docs/components/docs/assistant/floating-composer.tsx b/apps/docs/components/docs/assistant/floating-composer.tsx new file mode 100644 index 0000000..3303910 --- /dev/null +++ b/apps/docs/components/docs/assistant/floating-composer.tsx @@ -0,0 +1,230 @@ +"use client"; + +import { + AssistantComposerAction, + useComposerSubmitHandler, + useSharedDocsModelSelection, +} from "@/components/docs/assistant/composer"; +import { useAssistantPanel } from "@/components/docs/assistant/context"; +import { ModelSelector } from "@/components/assistant-ui/model-selector"; +import { MODELS } from "@/constants/model"; +import { SparklesIcon, XIcon } from "lucide-react"; +import Image from "next/image"; +import { ComposerPrimitive, useAuiState } from "@assistant-ui/react"; +import { + useCallback, + useEffect, + useRef, + useState, + type RefObject, + type ReactNode, +} from "react"; +import { cn } from "@/lib/utils"; +import { usePersistentBoolean } from "@/hooks/use-persistent-boolean"; + +const models = MODELS.map((m) => ({ + id: m.value, + name: m.name, + icon: ( + <Image + src={m.icon} + alt={m.name} + width={14} + height={14} + className="size-3.5" + /> + ), + ...(m.disabled ? { disabled: true as const } : undefined), +})); + +function useHasScrolled(threshold: number): boolean { + const [hasScrolled, setHasScrolled] = useState(false); + + useEffect(() => { + const handleScroll = () => { + if (window.scrollY > threshold) { + setHasScrolled(true); + } + }; + window.addEventListener("scroll", handleScroll, { passive: true }); + handleScroll(); + return () => window.removeEventListener("scroll", handleScroll); + }, [threshold]); + + return hasScrolled; +} + +function useClickOutside( + ref: RefObject<HTMLElement | null>, + handler: () => void, + enabled: boolean, +): void { + const handlerRef = useRef(handler); + handlerRef.current = handler; + + useEffect(() => { + if (!enabled) return; + + const handleClickOutside = (e: MouseEvent) => { + if (ref.current && !ref.current.contains(e.target as Node)) { + handlerRef.current(); + } + }; + + document.addEventListener("mousedown", handleClickOutside); + return () => document.removeEventListener("mousedown", handleClickOutside); + }, [ref, enabled]); +} + +export function FloatingComposer(): ReactNode { + const { open, setOpen } = useAssistantPanel(); + const isEmpty = useAuiState((s) => s.composer.isEmpty); + const threadIsEmpty = useAuiState((s) => s.thread.isEmpty); + const { modelValue, onModelChange } = useSharedDocsModelSelection(); + const [expanded, setExpanded] = useState(false); + const [modelSelectorOpen, setModelSelectorOpen] = useState(false); + const [dismissed, setDismissed] = usePersistentBoolean( + "floating-composer-dismissed", + ); + const containerRef = useRef<HTMLDivElement>(null); + + const hasScrolled = useHasScrolled(100); + const visible = hasScrolled && !open && !dismissed; + + // Reset expanded state when floating composer becomes hidden + useEffect(() => { + if (!visible) { + setExpanded(false); + } + }, [visible]); + + useEffect(() => { + if (!visible || !expanded) { + setModelSelectorOpen(false); + } + }, [visible, expanded]); + + // Click outside to collapse (only when composer is empty) + useClickOutside( + containerRef, + useCallback(() => { + if (isEmpty) setExpanded(false); + }, [isEmpty]), + expanded && !modelSelectorOpen, + ); + + const handleSubmit = useComposerSubmitHandler(() => setOpen(true)); + + return ( + <div + className={cn( + "fixed bottom-6 left-1/2 z-40 hidden w-full -translate-x-1/2 px-4 transition-all duration-300 ease-out md:block", + expanded ? "max-w-120" : "max-w-88", + visible + ? "translate-y-0 opacity-100" + : open + ? "pointer-events-none translate-y-4 opacity-0" + : "pointer-events-none translate-y-full opacity-0", + )} + > + <div ref={containerRef} className="group relative"> + <button + type="button" + aria-label="Dismiss" + onClick={() => setDismissed(true)} + className="border-border/60 bg-background text-muted-foreground hover:text-foreground absolute -top-2 -right-2 z-10 flex size-5 items-center justify-center rounded-full border opacity-0 transition-opacity group-hover:opacity-100 [@media(hover:none)]:opacity-100" + > + <XIcon className="size-3" /> + </button> + <ComposerPrimitive.Root onSubmit={handleSubmit}> + <div + className={cn( + "relative rounded-xl border shadow-lg backdrop-blur-sm transition-all duration-200 ease-out", + expanded + ? "border-foreground/60 bg-background/90" + : "border-border bg-background/80", + )} + > + <div className="relative"> + <ComposerPrimitive.Input + asChild + unstable_focusOnRunStart={false} + unstable_focusOnScrollToBottom={false} + unstable_focusOnThreadSwitched={false} + > + <textarea + placeholder="Ask a question..." + className={cn( + `tw-shimer placeholder:text-muted-foreground field-sizing-content w-full resize-none bg-transparent text-sm leading-5 transition-[max-height,padding] duration-200 ease-out focus:outline-none`, + expanded + ? "max-h-32 pt-2.5 pr-3 pb-10 pl-3" + : "max-h-9.5 overflow-hidden pt-3 pr-3 pb-1.5 pl-8", + )} + rows={1} + onMouseDown={(e) => { + if (!expanded && !threadIsEmpty) { + e.preventDefault(); + setOpen(true); + } + }} + onFocus={() => { + if (!expanded && !threadIsEmpty) { + setOpen(true); + return; + } + setExpanded(true); + }} + /> + </ComposerPrimitive.Input> + <SparklesIcon + className={cn( + `text-muted-foreground pointer-events-none absolute top-1/2 left-2.5 size-3.5 -translate-y-1/2 transition-opacity duration-200`, + { + "opacity-0": expanded || !isEmpty, + "opacity-100": !expanded && isEmpty, + }, + )} + /> + </div> + <div + className={cn( + `absolute inset-x-0 bottom-0 px-1.5 pb-1.5 transition-[opacity,transform] ease-out`, + { + "pointer-events-auto translate-y-0 opacity-100 duration-200": + expanded, + "pointer-events-none translate-y-1 opacity-0 duration-100": + !expanded, + }, + )} + > + <div className="flex items-center justify-between"> + <ModelSelector + models={models} + value={modelValue} + onValueChange={onModelChange} + open={modelSelectorOpen} + onOpenChange={(nextOpen) => { + setModelSelectorOpen(nextOpen); + if (nextOpen && !expanded) { + setExpanded(true); + } + }} + variant="ghost" + size="sm" + /> + <div + className={cn("transition-opacity ease-out", { + "opacity-100 duration-200": expanded, + "opacity-0 duration-100": !expanded, + })} + > + <AssistantComposerAction /> + </div> + </div> + </div> + </div> + </ComposerPrimitive.Root> + </div> + </div> + ); +} diff --git a/apps/docs/components/docs/assistant/footer.tsx b/apps/docs/components/docs/assistant/footer.tsx new file mode 100644 index 0000000..9e0afa2 --- /dev/null +++ b/apps/docs/components/docs/assistant/footer.tsx @@ -0,0 +1,112 @@ +"use client"; + +import { useAuiState, useAui } from "@assistant-ui/react"; +import { PlusIcon } from "lucide-react"; +import type { ReactNode } from "react"; +import { analytics } from "@/lib/analytics"; +import { useCurrentPage } from "@/components/docs/contexts/current-page"; +import { useThreadTokenUsage } from "@assistant-ui/react-ai-sdk"; +import { ContextDisplay } from "@assistant-ui/ui/components/assistant-ui/context-display"; +import { useSharedDocsModelSelection } from "./composer"; +import { getContextWindow } from "@/constants/model"; + +type AssistantFooterProps = { + onNewThread?: () => void; + contextWindow?: number; + centerContent?: ReactNode; +}; + +export function AssistantFooter(props: AssistantFooterProps = {}): ReactNode { + if (props.contextWindow !== undefined) { + return ( + <AssistantFooterContent {...props} contextWindow={props.contextWindow} /> + ); + } + + return <DefaultAssistantFooter {...props} />; +} + +function DefaultAssistantFooter(props: AssistantFooterProps): ReactNode { + const { modelValue } = useSharedDocsModelSelection(); + return ( + <AssistantFooterContent + {...props} + contextWindow={getContextWindow(modelValue)} + /> + ); +} + +function AssistantFooterContent({ + onNewThread, + contextWindow, + centerContent, +}: AssistantFooterProps & { contextWindow: number }): ReactNode { + const aui = useAui(); + const threadId = useAuiState((s) => s.threadListItem.id); + const messages = useAuiState((s) => s.thread.messages); + const currentPage = useCurrentPage(); + const pathname = currentPage?.pathname; + const lastUsage = useThreadTokenUsage(); + const contextTokens = lastUsage?.totalTokens ?? 0; + const usagePercent = Math.min((contextTokens / contextWindow) * 100, 100); + + return ( + <div + className={ + centerContent + ? "grid grid-cols-[1fr_auto_1fr] items-center gap-2 px-3 py-1.5" + : "flex items-center justify-between px-3 py-1.5" + } + > + <button + type="button" + onClick={() => { + const modelName = aui.thread().getModelContext()?.config?.modelName; + analytics.assistant.newThreadClicked({ + threadId, + previous_message_count: messages.length, + context_total_tokens: contextTokens, + context_usage_percent: usagePercent, + ...(pathname ? { pathname } : {}), + ...(modelName ? { model_name: modelName } : {}), + }); + if (onNewThread) { + onNewThread(); + } else { + aui.threads().switchToNewThread(); + } + }} + className="text-muted-foreground hover:bg-muted hover:text-foreground flex items-center gap-1.5 rounded-md px-2 py-1 text-xs transition-colors" + > + <PlusIcon className="size-3.5" /> + <span>New thread</span> + </button> + + {centerContent ? ( + <> + <div className="min-w-0 px-1">{centerContent}</div> + <div className="flex justify-end"> + <AssistantContextBar + contextWindow={contextWindow} + usage={lastUsage} + /> + </div> + </> + ) : ( + <AssistantContextBar contextWindow={contextWindow} usage={lastUsage} /> + )} + </div> + ); +} + +function AssistantContextBar({ + contextWindow, + usage, +}: { + contextWindow: number; + usage: ReturnType<typeof useThreadTokenUsage>; +}): ReactNode { + return ( + <ContextDisplay.Bar modelContextWindow={contextWindow} usage={usage} /> + ); +} diff --git a/apps/docs/components/docs/assistant/markdown.tsx b/apps/docs/components/docs/assistant/markdown.tsx new file mode 100644 index 0000000..01612d0 --- /dev/null +++ b/apps/docs/components/docs/assistant/markdown.tsx @@ -0,0 +1,257 @@ +"use client"; + +import "@assistant-ui/react-markdown/styles/dot.css"; +import "react-shiki/css"; + +import { + type CodeHeaderProps, + MarkdownTextPrimitive, + type SyntaxHighlighterProps, + unstable_memoizeMarkdownComponents as memoizeMarkdownComponents, + useIsMarkdownCodeBlock, +} from "@assistant-ui/react-markdown"; +import remarkGfm from "remark-gfm"; +import { type CSSProperties, type FC, memo } from "react"; +import { CheckIcon, CopyIcon } from "lucide-react"; +import { cn } from "@/lib/utils"; +import ShikiHighlighter from "react-shiki"; +import Link from "next/link"; +import { useCopyToClipboard } from "@assistant-ui/ui/hooks/use-copy-to-clipboard"; + +const MarkdownTextImpl = () => { + return ( + <MarkdownTextPrimitive + remarkPlugins={[remarkGfm]} + className="aui-md-assistant" + components={markdownComponents} + /> + ); +}; + +export const MarkdownText = memo(MarkdownTextImpl); + +const CodeHeader: FC<CodeHeaderProps> = ({ language, code }) => { + const { isCopied, copyToClipboard } = useCopyToClipboard(); + const onCopy = () => { + if (!code || isCopied) return; + copyToClipboard(code); + }; + + return ( + <div className="border-border/50 bg-muted/50 mt-2.5 flex items-center justify-between rounded-t-lg border border-b-0 px-3 py-1.5 text-xs"> + <span className="text-muted-foreground font-medium lowercase"> + {language} + </span> + <button + type="button" + onClick={onCopy} + className="text-muted-foreground hover:bg-muted hover:text-foreground flex size-6 items-center justify-center rounded-md transition-colors" + > + {isCopied ? ( + <CheckIcon className="size-3.5" /> + ) : ( + <CopyIcon className="size-3.5" /> + )} + </button> + </div> + ); +}; + +const SyntaxHighlighter: FC<SyntaxHighlighterProps> = ({ code, language }) => { + return ( + <ShikiHighlighter + language={language} + theme={{ dark: "github-dark-default", light: "github-light-default" }} + addDefaultStyles={false} + showLanguage={false} + showLineNumbers + defaultColor={false} + className="[&_pre]:border-border/50 [&_pre]:bg-muted/30 [&_pre]:scrollbar-none [&_pre]:overflow-x-auto [&_pre]:rounded-t-none [&_pre]:rounded-b-lg [&_pre]:border [&_pre]:border-t-0 [&_pre]:py-3 [&_pre]:pr-3 [&_pre]:pl-1 [&_pre]:text-xs [&_pre]:leading-relaxed" + style={ + { + "--line-numbers-foreground": "var(--color-muted-foreground)", + "--line-numbers-width": "2ch", + "--line-numbers-padding-left": "0", + "--line-numbers-padding-right": "1ch", + } as CSSProperties + } + > + {code.trim()} + </ShikiHighlighter> + ); +}; + +const markdownComponents = memoizeMarkdownComponents({ + SyntaxHighlighter: SyntaxHighlighter, + h1: ({ className, ...props }) => ( + <h1 + className={cn( + "mb-2 text-base font-semibold first:mt-0 last:mb-0", + className, + )} + {...props} + /> + ), + h2: ({ className, ...props }) => ( + <h2 + className={cn( + "mt-3 mb-1.5 text-sm font-semibold first:mt-0 last:mb-0", + className, + )} + {...props} + /> + ), + h3: ({ className, ...props }) => ( + <h3 + className={cn( + "mt-2.5 mb-1 text-sm font-semibold first:mt-0 last:mb-0", + className, + )} + {...props} + /> + ), + h4: ({ className, ...props }) => ( + <h4 + className={cn( + "mt-2 mb-1 text-sm font-medium first:mt-0 last:mb-0", + className, + )} + {...props} + /> + ), + h5: ({ className, ...props }) => ( + <h5 + className={cn( + "mt-2 mb-1 text-sm font-medium first:mt-0 last:mb-0", + className, + )} + {...props} + /> + ), + h6: ({ className, ...props }) => ( + <h6 + className={cn( + "mt-2 mb-1 text-sm font-medium first:mt-0 last:mb-0", + className, + )} + {...props} + /> + ), + p: ({ className, ...props }) => ( + <p + className={cn("my-2.5 leading-normal first:mt-0 last:mb-0", className)} + {...props} + /> + ), + a: ({ className, href, children, title, target, rel }) => { + const linkClass = cn( + "text-primary hover:text-primary/80 underline underline-offset-2", + className, + ); + + if (href?.startsWith("/")) { + return ( + <Link href={href} className={linkClass} title={title} target={target}> + {children} + </Link> + ); + } + return ( + <a + href={href} + className={linkClass} + title={title} + target={target} + rel={rel} + > + {children} + </a> + ); + }, + blockquote: ({ className, ...props }) => ( + <blockquote + className={cn( + "border-muted-foreground/30 text-muted-foreground my-2.5 border-l-2 pl-3 italic", + className, + )} + {...props} + /> + ), + ul: ({ className, ...props }) => ( + <ul + className={cn( + "marker:text-muted-foreground my-2 ml-4 list-disc [&>li]:mt-1", + className, + )} + {...props} + /> + ), + ol: ({ className, ...props }) => ( + <ol + className={cn( + "marker:text-muted-foreground my-2 ml-4 list-decimal [&>li]:mt-1", + className, + )} + {...props} + /> + ), + li: ({ className, ...props }) => ( + <li className={cn("leading-normal", className)} {...props} /> + ), + hr: ({ className, ...props }) => ( + <hr + className={cn("border-muted-foreground/20 my-2", className)} + {...props} + /> + ), + table: ({ className, ...props }) => ( + <div className="my-2 overflow-x-auto"> + <table + className={cn("w-full border-collapse text-xs", className)} + {...props} + /> + </div> + ), + th: ({ className, ...props }) => ( + <th + className={cn( + "border-muted-foreground/20 bg-muted border px-2 py-1 text-left font-medium", + className, + )} + {...props} + /> + ), + td: ({ className, ...props }) => ( + <td + className={cn( + "border-muted-foreground/20 border px-2 py-1 text-left", + className, + )} + {...props} + /> + ), + tr: (props) => <tr {...props} />, + pre: ({ className, ...props }) => ( + <pre + className={cn( + "border-border/50 bg-muted/30 overflow-x-auto rounded-t-none rounded-b-lg border border-t-0 p-3 text-xs leading-relaxed", + className, + )} + {...props} + /> + ), + code: function Code({ className, ...props }) { + const isCodeBlock = useIsMarkdownCodeBlock(); + return ( + <code + className={cn( + !isCodeBlock && + "border-border/50 bg-muted/50 rounded-md border px-1.5 py-0.5 font-mono text-[0.85em]", + className, + )} + {...props} + /> + ); + }, + CodeHeader, +}); diff --git a/apps/docs/components/docs/assistant/messages.tsx b/apps/docs/components/docs/assistant/messages.tsx new file mode 100644 index 0000000..7dd2dab --- /dev/null +++ b/apps/docs/components/docs/assistant/messages.tsx @@ -0,0 +1,202 @@ +"use client"; + +import { AssistantActionBar } from "./assistant-action-bar"; +import { MarkdownText } from "./markdown"; +import { + AuiIf, + ErrorPrimitive, + MessagePrimitive, + type ToolCallMessagePartProps, +} from "@assistant-ui/react"; +import { + BookOpenIcon, + CheckIcon, + FileCodeIcon, + FileTextIcon, + FolderTreeIcon, + LoaderIcon, + type LucideIcon, + TerminalIcon, +} from "lucide-react"; +import { + type ComponentType, + type ReactNode, + useState, + useEffect, + useRef, +} from "react"; +import { cn } from "@/lib/utils"; +import { Reasoning } from "@/components/assistant-ui/reasoning"; +import { DotMatrix } from "@/components/assistant-ui/dot-matrix"; + +export function UserMessage(): ReactNode { + return ( + <MessagePrimitive.Root className="flex justify-end py-2" data-role="user"> + <div className="bg-muted max-w-[85%] rounded-2xl px-3 py-2 text-sm empty:hidden"> + <MessagePrimitive.Parts /> + </div> + </MessagePrimitive.Root> + ); +} + +export function AssistantMessage({ + ToolCallComponent = ToolCall, +}: { + ToolCallComponent?: ComponentType<ToolCallMessagePartProps>; +} = {}): ReactNode { + return ( + <MessagePrimitive.Root className="py-2" data-role="assistant"> + <div className="text-sm"> + <MessagePrimitive.Parts> + {({ part }) => { + if (part.type === "text") return <MarkdownText />; + if (part.type === "reasoning") return <Reasoning {...part} />; + if (part.type === "tool-call") + return part.toolUI ?? <ToolCallComponent {...part} />; + return null; + }} + </MessagePrimitive.Parts> + + <AuiIf + condition={(s) => + s.thread.isRunning && s.message.content.length === 0 + } + > + <div className="text-muted-foreground flex items-center gap-2 py-1"> + <DotMatrix state="connecting" aria-hidden /> + <span className="text-sm">Connecting</span> + </div> + </AuiIf> + <MessageError /> + </div> + <AssistantActionBar /> + </MessagePrimitive.Root> + ); +} + +function getToolDisplay( + toolName: string, + args: Record<string, unknown>, + isRunning: boolean, +): { icon: LucideIcon; label: string; detail: string } { + switch (toolName) { + case "listDocs": { + const path = (args as { path?: string })?.path; + return { + icon: FolderTreeIcon, + label: isRunning ? "Listing" : "Listed", + detail: path ? `/${path}` : "documentation structure", + }; + } + case "readDoc": { + const slug = (args as { slugOrUrl?: string })?.slugOrUrl ?? ""; + const normalizedSlug = slug.replace(/^\/docs\/?/, ""); + return { + icon: FileTextIcon, + label: isRunning ? "Reading" : "Read", + detail: `/docs/${normalizedSlug}`, + }; + } + case "bash": { + const command = (args as { command?: string })?.command ?? ""; + const preview = + command.length > 60 ? `${command.slice(0, 57)}...` : command; + return { + icon: TerminalIcon, + label: isRunning ? "Running" : "Ran", + detail: preview, + }; + } + case "readFile": { + const filePath = (args as { path?: string })?.path ?? ""; + const shortPath = filePath.split("/").slice(-2).join("/"); + return { + icon: FileCodeIcon, + label: isRunning ? "Reading" : "Read", + detail: shortPath, + }; + } + default: + return { + icon: BookOpenIcon, + label: isRunning ? "Running" : "Completed", + detail: toolName, + }; + } +} + +function ToolStatusIcon({ + status, + FallbackIcon, +}: { + status: { type: string } | undefined; + FallbackIcon: LucideIcon; +}): ReactNode { + switch (status?.type) { + case "running": + return <LoaderIcon className="size-3 animate-spin" />; + case "complete": + return <CheckIcon className="size-3 text-emerald-500" />; + default: + return <FallbackIcon className="size-3" />; + } +} + +function useToolDuration(isRunning: boolean): number | null { + const startTimeRef = useRef<number | null>(null); + const [duration, setDuration] = useState<number | null>(null); + + useEffect(() => { + if (isRunning && startTimeRef.current === null) { + startTimeRef.current = Date.now(); + } else if (!isRunning && startTimeRef.current !== null) { + setDuration(Date.now() - startTimeRef.current); + } + }, [isRunning]); + + return duration; +} + +function formatDuration(ms: number): string { + if (ms < 1000) return `${ms}ms`; + return `${(ms / 1000).toFixed(1)}s`; +} + +function ToolCall({ + toolName, + args, + status, +}: ToolCallMessagePartProps): ReactNode { + const isRunning = status?.type === "running"; + const { icon, label, detail } = getToolDisplay(toolName, args, isRunning); + const duration = useToolDuration(isRunning); + + return ( + <div + className={cn( + "border-border/60 bg-muted/30 text-muted-foreground my-1.5 flex items-center gap-2 rounded-lg border px-2.5 py-1.5 text-xs", + isRunning && "animate-pulse", + )} + > + <ToolStatusIcon status={status} FallbackIcon={icon} /> + <span className="flex-1 truncate"> + {label} {detail} + </span> + {duration !== null && ( + <span className="text-muted-foreground/60"> + {formatDuration(duration)} + </span> + )} + </div> + ); +} + +function MessageError(): ReactNode { + return ( + <MessagePrimitive.Error> + <ErrorPrimitive.Root className="border-destructive bg-destructive/10 text-destructive dark:bg-destructive/5 mt-2 rounded-md border p-2 text-xs dark:text-red-200"> + <ErrorPrimitive.Message className="line-clamp-2" /> + </ErrorPrimitive.Root> + </MessagePrimitive.Error> + ); +} diff --git a/apps/docs/components/docs/assistant/panel.tsx b/apps/docs/components/docs/assistant/panel.tsx new file mode 100644 index 0000000..6a436f0 --- /dev/null +++ b/apps/docs/components/docs/assistant/panel.tsx @@ -0,0 +1,177 @@ +"use client"; + +import { AssistantThread } from "@/components/docs/assistant/thread"; +import { Button } from "@/components/ui/button"; +import { useAssistantPanel } from "@/components/docs/assistant/context"; +import { COLLAPSED_WIDTH } from "@/components/docs/layout/docs-layout"; +import { cn } from "@/lib/utils"; +import { analytics } from "@/lib/analytics"; +import { PanelRightCloseIcon } from "lucide-react"; +import { useCallback, useEffect, useRef } from "react"; + +function canScrollVertically(element: HTMLElement, deltaY: number): boolean { + if (element.scrollHeight <= element.clientHeight) return false; + if (deltaY < 0) return element.scrollTop > 0; + return element.scrollTop + element.clientHeight < element.scrollHeight - 1; +} + +function hasScrollableAncestor( + target: EventTarget | null, + boundary: HTMLElement, + deltaY: number, +): boolean { + if (!(target instanceof Node)) return false; + + let element = target instanceof HTMLElement ? target : target.parentElement; + + while (element && boundary.contains(element)) { + const overflowY = window.getComputedStyle(element).overflowY; + if ( + (overflowY === "auto" || + overflowY === "scroll" || + overflowY === "overlay") && + canScrollVertically(element, deltaY) + ) { + return true; + } + + if (element === boundary) break; + element = element.parentElement; + } + + return false; +} + +function ResizeHandle() { + const { width, setWidth, setIsResizing, isResizing } = useAssistantPanel(); + const startXRef = useRef(0); + const startWidthRef = useRef(0); + + const handleMouseDown = useCallback( + (e: React.MouseEvent) => { + e.preventDefault(); + setIsResizing(true); + startXRef.current = e.clientX; + startWidthRef.current = width; + + const handleMouseMove = (e: MouseEvent) => { + const delta = startXRef.current - e.clientX; + setWidth(startWidthRef.current + delta); + }; + + const handleMouseUp = () => { + setIsResizing(false); + document.removeEventListener("mousemove", handleMouseMove); + document.removeEventListener("mouseup", handleMouseUp); + }; + + document.addEventListener("mousemove", handleMouseMove); + document.addEventListener("mouseup", handleMouseUp); + }, + [width, setWidth, setIsResizing], + ); + + return ( + <div + onMouseDown={handleMouseDown} + className={cn( + "absolute top-0 bottom-0 -left-0.5 w-1 cursor-col-resize", + "after:absolute after:top-0 after:bottom-0 after:left-1/2 after:w-px after:-translate-x-1/2 after:transition-colors", + isResizing + ? "after:bg-primary/40" + : "hover:after:bg-primary/20 after:bg-transparent", + )} + /> + ); +} + +export function AssistantPanelToggle(): React.ReactNode { + const { open, toggle } = useAssistantPanel(); + + const handleClick = () => { + analytics.assistant.panelToggled({ open: !open, source: "toggle" }); + toggle(); + }; + + return ( + <Button + variant="ghost" + size="icon" + onClick={handleClick} + className={cn( + "bg-background absolute top-1/2 left-0 z-10 size-6 -translate-x-1/2 -translate-y-1/2 rounded-full border shadow-sm transition-opacity duration-300", + open ? "opacity-100" : "pointer-events-none opacity-0", + )} + aria-label="Close AI Chat" + > + <PanelRightCloseIcon className="size-3" /> + </Button> + ); +} + +export function AssistantPanelContent(): React.ReactNode { + const { open, toggle } = useAssistantPanel(); + const contentRef = useRef<HTMLDivElement>(null); + + useEffect(() => { + const content = contentRef.current; + if (!open || !content) return; + + const handleWheel = (event: WheelEvent) => { + if (event.deltaY === 0 || event.ctrlKey) return; + if (!hasScrollableAncestor(event.target, content, event.deltaY)) { + event.preventDefault(); + } + }; + + content.addEventListener("wheel", handleWheel, { + capture: true, + passive: false, + }); + return () => { + content.removeEventListener("wheel", handleWheel, { + capture: true, + }); + }; + }, [open]); + + const handleTriggerClick = () => { + analytics.assistant.panelToggled({ open: !open, source: "trigger" }); + toggle(); + }; + + return ( + <div + ref={contentRef} + className={cn( + "bg-background before:bg-border relative h-full w-(--panel-content-width) before:absolute before:inset-y-0 before:left-0 before:w-px before:transition-opacity before:duration-300", + open ? "before:opacity-100" : "before:opacity-0", + )} + > + <button + type="button" + onClick={handleTriggerClick} + className={cn( + "absolute inset-y-0 left-0 z-20 cursor-pointer", + "before:bg-border before:absolute before:inset-y-0 before:left-0 before:w-px before:opacity-0 before:transition-opacity hover:before:opacity-100 focus-visible:outline-none focus-visible:before:opacity-100", + open ? "pointer-events-none opacity-0" : "opacity-100", + )} + style={{ width: COLLAPSED_WIDTH }} + title="Open AI Chat" + aria-label="Open AI Chat" + /> + + <div + className={cn( + "flex h-full flex-col transition-opacity duration-300", + open ? "opacity-100" : "pointer-events-none opacity-0", + )} + > + <ResizeHandle /> + <div className="min-h-0 flex-1"> + <AssistantThread /> + </div> + </div> + </div> + ); +} diff --git a/apps/docs/components/docs/assistant/thread.tsx b/apps/docs/components/docs/assistant/thread.tsx new file mode 100644 index 0000000..fe87810 --- /dev/null +++ b/apps/docs/components/docs/assistant/thread.tsx @@ -0,0 +1,233 @@ +"use client"; + +import { + AuiIf, + ThreadPrimitive, + useAui, + useAuiState, +} from "@assistant-ui/react"; +import { + type ComponentType, + type ReactNode, + useEffect, + useMemo, + useRef, +} from "react"; +import { AssistantMessage, UserMessage } from "./messages"; +import { AssistantComposer, useSharedDocsModelSelection } from "./composer"; +import { useAssistantPanel } from "@/components/docs/assistant/context"; +import { ContextDisplay } from "@assistant-ui/ui/components/assistant-ui/context-display"; +import { analytics } from "@/lib/analytics"; +import { useCurrentPage } from "@/components/docs/contexts/current-page"; +import { + getThreadMessageTokenUsage, + type ThreadTokenUsage, +} from "@assistant-ui/react-ai-sdk"; +import { getContextWindow } from "@/constants/model"; +import { Button } from "@/components/ui/button"; +import { + PaletteIcon, + PlugIcon, + PlusIcon, + RocketIcon, + BookOpenIcon, + XIcon, +} from "lucide-react"; +import { + Tooltip, + TooltipContent, + TooltipTrigger, +} from "@/components/ui/radix/tooltip"; + +function PendingMessageHandler() { + const { pendingMessage, clearPendingMessage } = useAssistantPanel(); + const aui = useAui(); + const isRunning = useAuiState((s) => s.thread.isRunning); + const threadId = useAuiState((s) => s.threadListItem.id); + const currentPage = useCurrentPage(); + const pathname = currentPage?.pathname; + const processedRef = useRef<string | null>(null); + + useEffect(() => { + if (!pendingMessage || processedRef.current === pendingMessage) return; + if (isRunning) return; + + processedRef.current = pendingMessage; + clearPendingMessage(); + analytics.assistant.messageSent({ + threadId, + source: "ask_ai", + message_length: pendingMessage.length, + attachments_count: 0, + ...(pathname ? { pathname } : {}), + ...(() => { + try { + const modelName = aui.thread().getModelContext()?.config?.modelName; + return modelName ? { model_name: modelName } : {}; + } catch { + return {}; + } + })(), + }); + aui.thread().append(pendingMessage); + }, [pendingMessage, clearPendingMessage, aui, isRunning, threadId, pathname]); + + return null; +} + +type AssistantThreadProps = { + welcome?: ReactNode; + composer?: ReactNode; + footer?: ReactNode; + UserMessageComponent?: ComponentType; + AssistantMessageComponent?: ComponentType; +}; + +export function AssistantThread({ + welcome = <AssistantWelcome />, + composer = <AssistantComposer />, + footer, + UserMessageComponent = UserMessage, + AssistantMessageComponent = AssistantMessage, +}: AssistantThreadProps = {}): ReactNode { + return ( + <ThreadPrimitive.Root className="bg-background flex h-full flex-col"> + <PendingMessageHandler /> + <PanelHeader /> + <ThreadPrimitive.Viewport className="flex flex-1 scrollbar-none flex-col overflow-y-auto overscroll-contain px-3 pt-3"> + <AuiIf condition={(s) => s.thread.isEmpty}>{welcome}</AuiIf> + + <div className="px-1.5" data-slot="thread-messages"> + <ThreadPrimitive.Messages> + {({ message }) => { + if (message.role === "user") return <UserMessageComponent />; + if (message.role === "assistant") + return <AssistantMessageComponent />; + return null; + }} + </ThreadPrimitive.Messages> + </div> + + <ThreadPrimitive.ViewportFooter className="bg-background sticky bottom-0 mt-auto flex flex-col overflow-visible rounded-t-xl"> + {composer} + </ThreadPrimitive.ViewportFooter> + </ThreadPrimitive.Viewport> + {footer} + </ThreadPrimitive.Root> + ); +} + +function PanelHeader(): React.ReactNode { + const { setOpen } = useAssistantPanel(); + const aui = useAui(); + const threadId = useAuiState((s) => s.threadListItem.id); + const messages = useAuiState((s) => s.thread.messages); + const currentPage = useCurrentPage(); + const pathname = currentPage?.pathname; + const contextUsage = useMemo<ThreadTokenUsage | undefined>(() => { + // Each request's usage already counts the full prompt for that turn, so + // context-window fill is the largest request seen, not the sum across + // turns. The max only rises as the thread grows, which keeps the + // indicator monotonic when server-side pruning shrinks a later prompt. + let peak: ThreadTokenUsage | undefined; + let peakTotal = -1; + for (const message of messages) { + const usage = getThreadMessageTokenUsage(message); + if (!usage) continue; + const total = + usage.totalTokens ?? + (usage.inputTokens ?? 0) + (usage.outputTokens ?? 0); + if (total >= peakTotal) { + peak = { ...usage, totalTokens: total }; + peakTotal = total; + } + } + return peak; + }, [messages]); + const contextTokens = contextUsage?.totalTokens ?? 0; + const { modelValue } = useSharedDocsModelSelection(); + const contextWindow = getContextWindow(modelValue); + const usagePercent = Math.min((contextTokens / contextWindow) * 100, 100); + + return ( + <div className="flex h-12 shrink-0 items-center justify-between border-b px-3"> + <span className="text-sm font-semibold">Ask AI</span> + <div className="flex items-center gap-0.5"> + <ContextDisplay.Ring + modelContextWindow={contextWindow} + usage={contextUsage} + side="bottom" + /> + <Tooltip> + <TooltipTrigger asChild> + <Button + variant="ghost" + size="icon-sm" + onClick={() => { + const modelName = aui.thread().getModelContext() + ?.config?.modelName; + analytics.assistant.newThreadClicked({ + threadId, + previous_message_count: messages.length, + context_total_tokens: contextTokens, + context_usage_percent: usagePercent, + ...(pathname ? { pathname } : {}), + ...(modelName ? { model_name: modelName } : {}), + }); + aui.threads().switchToNewThread(); + }} + aria-label="New chat" + > + <PlusIcon className="size-4" /> + </Button> + </TooltipTrigger> + <TooltipContent side="bottom">New chat</TooltipContent> + </Tooltip> + <Tooltip> + <TooltipTrigger asChild> + <Button + variant="ghost" + size="icon-sm" + onClick={() => { + analytics.assistant.panelToggled({ + open: false, + source: "header", + }); + setOpen(false); + }} + aria-label="Close chat" + > + <XIcon className="size-4" /> + </Button> + </TooltipTrigger> + <TooltipContent side="bottom">Close chat</TooltipContent> + </Tooltip> + </div> + </div> + ); +} + +const SUGGESTIONS = [ + { prompt: "What is assistant-ui?", Icon: BookOpenIcon }, + { prompt: "How do I get started?", Icon: RocketIcon }, + { prompt: "How do I customize the styling?", Icon: PaletteIcon }, + { prompt: "How do I connect my own backend?", Icon: PlugIcon }, +]; + +function AssistantWelcome(): React.ReactNode { + return ( + <div className="flex flex-1 flex-col justify-end gap-0.5 pb-3"> + {SUGGESTIONS.map(({ prompt, Icon }) => ( + <ThreadPrimitive.Suggestion + key={prompt} + prompt={prompt} + send + className="text-muted-foreground hover:bg-muted hover:text-foreground flex w-full items-center gap-2 rounded-lg px-2 py-1.5 text-left text-sm transition-colors" + > + <Icon className="text-muted-foreground size-4 shrink-0" /> + {prompt} + </ThreadPrimitive.Suggestion> + ))} + </div> + ); +} diff --git a/apps/docs/components/docs/contexts/current-page.tsx b/apps/docs/components/docs/contexts/current-page.tsx new file mode 100644 index 0000000..0e57b16 --- /dev/null +++ b/apps/docs/components/docs/contexts/current-page.tsx @@ -0,0 +1,24 @@ +"use client"; + +import { createContext, useContext, type ReactNode } from "react"; +import { usePathname } from "next/navigation"; + +interface CurrentPageContextValue { + pathname: string; +} + +const CurrentPageContext = createContext<CurrentPageContextValue | null>(null); + +export function useCurrentPage() { + return useContext(CurrentPageContext); +} + +export function CurrentPageProvider({ children }: { children: ReactNode }) { + const pathname = usePathname(); + + return ( + <CurrentPageContext.Provider value={{ pathname }}> + {children} + </CurrentPageContext.Provider> + ); +} diff --git a/apps/docs/components/docs/contexts/flavor.server.tsx b/apps/docs/components/docs/contexts/flavor.server.tsx new file mode 100644 index 0000000..94d7277 --- /dev/null +++ b/apps/docs/components/docs/contexts/flavor.server.tsx @@ -0,0 +1,25 @@ +import type { ReactNode } from "react"; +import { + Flavored as FlavoredClient, + FlavorSwitcher as FlavorSwitcherClient, +} from "@/components/docs/contexts/flavor"; +import type { LLMRenderContext } from "@/lib/get-llm-text"; + +type FlavoredProps = { radix: ReactNode; base: ReactNode }; + +export function Flavored(props: FlavoredProps) { + return <FlavoredClient {...props} />; +} + +( + Flavored as typeof Flavored & { + llm: (props: FlavoredProps, ctx?: LLMRenderContext) => ReactNode; + } +).llm = ({ radix, base }, ctx) => (ctx?.flavor === "radix" ? radix : base); + +export function FlavorSwitcher(props: { className?: string }) { + return <FlavorSwitcherClient {...props} />; +} + +(FlavorSwitcher as typeof FlavorSwitcher & { llm: () => ReactNode }).llm = () => + null; diff --git a/apps/docs/components/docs/contexts/flavor.tsx b/apps/docs/components/docs/contexts/flavor.tsx new file mode 100644 index 0000000..44b6603 --- /dev/null +++ b/apps/docs/components/docs/contexts/flavor.tsx @@ -0,0 +1,113 @@ +"use client"; + +import { useSyncExternalStore, type ReactNode } from "react"; +import { cn } from "@/lib/utils"; + +export type UiFlavor = "base" | "radix"; + +const STORAGE_KEY = "aui-docs-flavor"; +const PARAM = "view"; + +let flavor: UiFlavor = "base"; +let hydrated = false; +const listeners = new Set<() => void>(); + +function readStoredFlavor(): string | null { + try { + return window.localStorage.getItem(STORAGE_KEY); + } catch { + return null; + } +} + +function readCurrent(): UiFlavor { + if (typeof window === "undefined") return "base"; + const param = new URLSearchParams(window.location.search).get(PARAM); + if (param === "radix-ui") return "radix"; + if (param === "base-ui") return "base"; + return readStoredFlavor() === "radix" ? "radix" : "base"; +} + +function emit() { + for (const listener of listeners) listener(); +} + +function subscribe(listener: () => void) { + if (!hydrated) { + hydrated = true; + flavor = readCurrent(); + window.addEventListener("popstate", () => { + flavor = readCurrent(); + emit(); + }); + } + listeners.add(listener); + return () => listeners.delete(listener); +} + +export function setFlavor(next: UiFlavor) { + flavor = next; + try { + window.localStorage.setItem(STORAGE_KEY, next); + } catch { + // Storage can be blocked (private mode, quota); the in-memory value and + // the url parameter still carry the selection. + } + const url = new URL(window.location.href); + if (next === "radix") { + url.searchParams.set(PARAM, "radix-ui"); + } else { + url.searchParams.delete(PARAM); + } + window.history.replaceState(window.history.state, "", url); + emit(); +} + +export function useFlavor(): UiFlavor { + return useSyncExternalStore( + subscribe, + () => flavor, + () => "base" as const, + ); +} + +export function Flavored({ + radix, + base, +}: { + radix: ReactNode; + base: ReactNode; +}) { + return useFlavor() === "base" ? base : radix; +} + +const FLAVOR_TABS = [ + ["base", "Base UI"], + ["radix", "Radix UI"], +] as const; + +export function FlavorSwitcher({ className }: { className?: string }) { + const current = useFlavor(); + + return ( + <div + className={cn( + "not-prose border-border/60 mt-6 flex items-center gap-5 border-b", + className, + )} + > + {FLAVOR_TABS.map(([value, label]) => ( + <button + key={value} + type="button" + onClick={() => setFlavor(value)} + aria-pressed={current === value} + data-active={current === value} + className="text-muted-foreground hover:text-foreground data-[active=true]:text-foreground after:bg-foreground relative -mb-px pb-2 text-sm font-medium transition-colors after:absolute after:inset-x-0 after:bottom-0 after:h-0.5 after:opacity-0 after:transition-opacity data-[active=true]:after:opacity-100" + > + {label} + </button> + ))} + </div> + ); +} diff --git a/apps/docs/components/docs/contexts/sidebar.tsx b/apps/docs/components/docs/contexts/sidebar.tsx new file mode 100644 index 0000000..077216d --- /dev/null +++ b/apps/docs/components/docs/contexts/sidebar.tsx @@ -0,0 +1,70 @@ +"use client"; + +import { + createContext, + useCallback, + useContext, + useState, + type ReactNode, +} from "react"; +import { cn } from "@/lib/utils"; + +interface DocsSidebarContextValue { + open: boolean; + setOpen: (open: boolean) => void; + toggle: () => void; +} + +const DocsSidebarContext = createContext<DocsSidebarContextValue | null>(null); + +export function useDocsSidebar() { + const ctx = useContext(DocsSidebarContext); + if (!ctx) { + throw new Error("useDocsSidebar must be used within DocsSidebarProvider"); + } + return ctx; +} + +export function DocsSidebarProvider({ children }: { children: ReactNode }) { + const [open, setOpen] = useState(false); + const toggle = useCallback(() => setOpen((prev) => !prev), []); + + return ( + <DocsSidebarContext.Provider value={{ open, setOpen, toggle }}> + {children} + </DocsSidebarContext.Provider> + ); +} + +export const DOCS_SIDEBAR_WIDTH = 260; + +/** + * Renders the sidebar children once and adapts position via responsive CSS: + * - Mobile: full-screen overlay (top-12 bottom-0 inset-x-0), opacity-toggled by `open`. + * - Desktop (md+): fixed left rail of `--sidebar-width`, always visible. + * + * Single element so `children` (and any state inside, e.g. openSectionId in + * SidebarContent) is mounted once — no desync between desktop / mobile views. + */ +export function DocsSidebar({ children }: { children: ReactNode }) { + const { open } = useDocsSidebar(); + + return ( + <aside + className={cn( + // mobile: fullscreen overlay, opacity-toggled + "bg-background fixed inset-x-0 top-12 bottom-0 z-40 transition-opacity duration-200", + open ? "opacity-100" : "pointer-events-none opacity-0", + // desktop: collapse to left rail, always visible + "md:border-border/60 md:pointer-events-auto md:right-auto md:z-30 md:w-(--sidebar-width) md:border-r md:opacity-100", + )} + style={ + { + "--sidebar-width": `${DOCS_SIDEBAR_WIDTH}px`, + } as React.CSSProperties + } + > + {children} + </aside> + ); +} diff --git a/apps/docs/components/docs/demo-iframe.tsx b/apps/docs/components/docs/demo-iframe.tsx new file mode 100644 index 0000000..2cc07e1 --- /dev/null +++ b/apps/docs/components/docs/demo-iframe.tsx @@ -0,0 +1,18 @@ +"use client"; + +import { useTheme } from "next-themes"; +import { useEffect, useState, type ComponentProps } from "react"; + +export function DemoIframe({ src, ...props }: ComponentProps<"iframe">) { + const { resolvedTheme } = useTheme(); + const [mounted, setMounted] = useState(false); + + useEffect(() => setMounted(true), []); + + const themedSrc = + mounted && src + ? `${src}${src.includes("?") ? "&" : "?"}theme=${resolvedTheme ?? "light"}` + : undefined; + + return <iframe src={themedSrc} {...props} />; +} diff --git a/apps/docs/components/docs/example-card.tsx b/apps/docs/components/docs/example-card.tsx new file mode 100644 index 0000000..0244cbb --- /dev/null +++ b/apps/docs/components/docs/example-card.tsx @@ -0,0 +1,43 @@ +import Image from "next/image"; +import Link from "next/link"; +import { ArrowUpRight } from "lucide-react"; +import type { ExampleItem } from "@/lib/examples"; + +export function ExampleCard({ + title, + image, + description, + link, + external = false, +}: ExampleItem) { + return ( + <Link + href={link} + className="not-prose group bg-card hover:border-foreground/20 block overflow-hidden rounded-lg border transition-colors" + {...(external && { target: "_blank", rel: "noopener noreferrer" })} + > + <div className="overflow-hidden"> + <Image + src={image} + alt={title} + width={600} + height={400} + className="aspect-video w-full object-cover transition-transform duration-300 group-hover:scale-[1.02]" + /> + </div> + <div className="flex flex-col gap-1 p-4"> + <div className="flex items-center justify-between"> + <h3 className="font-medium">{title}</h3> + {external && ( + <ArrowUpRight className="text-muted-foreground size-4" /> + )} + </div> + {description && ( + <p className="text-muted-foreground text-sm leading-relaxed"> + {description} + </p> + )} + </div> + </Link> + ); +} diff --git a/apps/docs/components/docs/examples-navbar.tsx b/apps/docs/components/docs/examples-navbar.tsx new file mode 100644 index 0000000..5444c82 --- /dev/null +++ b/apps/docs/components/docs/examples-navbar.tsx @@ -0,0 +1,24 @@ +"use client"; + +import Link from "next/link"; +import { usePathname } from "next/navigation"; +import { ArrowLeft } from "lucide-react"; + +export function ExamplesNavbar() { + const pathname = usePathname(); + const isChildPage = pathname !== "/examples"; + + if (!isChildPage) return null; + + return ( + <nav> + <Link + href="/examples" + className="text-muted-foreground hover:text-foreground inline-flex items-center gap-1.5 text-sm transition-colors" + > + <ArrowLeft className="size-4" /> + Back to Examples + </Link> + </nav> + ); +} diff --git a/apps/docs/components/docs/fumadocs/callout.tsx b/apps/docs/components/docs/fumadocs/callout.tsx new file mode 100644 index 0000000..08fc803 --- /dev/null +++ b/apps/docs/components/docs/fumadocs/callout.tsx @@ -0,0 +1,110 @@ +import { + CircleCheck, + CircleX, + Info, + Lightbulb, + TriangleAlert, +} from "lucide-react"; +import type { ComponentProps, ReactNode } from "react"; +import { cn } from "@/lib/utils"; + +type CalloutType = + | "info" + | "warn" + | "warning" + | "error" + | "success" + | "idea" + | "tip" + | "note"; + +const typeConfig = { + info: { + icon: Info, + className: "bg-blue-500/10 text-blue-600 dark:text-blue-400", + iconClassName: "text-blue-500", + }, + warning: { + icon: TriangleAlert, + className: "bg-amber-500/10 text-amber-700 dark:text-amber-400", + iconClassName: "text-amber-500", + }, + error: { + icon: CircleX, + className: "bg-red-500/10 text-red-600 dark:text-red-400", + iconClassName: "text-red-500", + }, + success: { + icon: CircleCheck, + className: "bg-green-500/10 text-green-600 dark:text-green-400", + iconClassName: "text-green-500", + }, + idea: { + icon: Lightbulb, + className: "bg-purple-500/10 text-purple-600 dark:text-purple-400", + iconClassName: "text-purple-500", + }, +}; + +function resolveType(type: CalloutType): keyof typeof typeConfig { + if (type === "warn") return "warning"; + if (type === "tip" || type === "note") return "info"; + return type as keyof typeof typeConfig; +} + +interface CalloutProps extends Omit<ComponentProps<"div">, "title"> { + type?: CalloutType; + title?: ReactNode; + icon?: ReactNode; + children?: ReactNode; +} + +// Keep the authored fuma type in the `[!type]` marker — don't remap to GitHub's +// smaller admonition set. +export const CalloutLLM = ({ + type = "info", + title, + children, +}: CalloutProps) => ( + <blockquote> + <p>{`[!${type}]`}</p> + {title ? ( + <p> + <strong>{title}</strong> + </p> + ) : null} + {children} + </blockquote> +); + +export function Callout({ + type: inputType = "info", + title, + icon, + children, + className, + ...props +}: CalloutProps) { + const type = resolveType(inputType); + const config = typeConfig[type]; + const Icon = config.icon; + + return ( + <div + className={cn( + "not-prose my-4 flex items-start gap-2 rounded-lg px-3 py-2 text-sm", + config.className, + className, + )} + {...props} + > + <div className="mt-0.5 shrink-0"> + {icon ?? <Icon className={cn("size-4", config.iconClassName)} />} + </div> + <div className="min-w-0 flex-1 [&_a]:underline [&_a]:underline-offset-2 [&_blockquote]:border-l-2 [&_blockquote]:border-current/30 [&_blockquote]:pl-3 [&_blockquote]:italic [&_code]:rounded [&_code]:bg-black/10 [&_code]:px-1.5 [&_code]:py-0.5 [&_code]:text-[0.8125rem] dark:[&_code]:bg-white/10 [&_kbd]:rounded [&_kbd]:border [&_kbd]:border-current/20 [&_kbd]:bg-black/5 [&_kbd]:px-1.5 [&_kbd]:py-0.5 [&_kbd]:font-mono [&_kbd]:text-[0.8125rem] dark:[&_kbd]:bg-white/5 [&_li]:py-0.5 [&_ol]:my-2 [&_ol]:list-decimal [&_ol]:pl-4 [&_p+p]:mt-2 [&_strong]:font-semibold [&_ul]:my-2 [&_ul]:list-disc [&_ul]:pl-4"> + {title && <p className="mb-1 font-medium">{title}</p>} + <div>{children}</div> + </div> + </div> + ); +} diff --git a/apps/docs/components/docs/fumadocs/card.tsx b/apps/docs/components/docs/fumadocs/card.tsx new file mode 100644 index 0000000..33a3b8b --- /dev/null +++ b/apps/docs/components/docs/fumadocs/card.tsx @@ -0,0 +1,85 @@ +import Link from "next/link"; +import { ArrowRight, ArrowUpRight } from "lucide-react"; +import type { ReactNode } from "react"; + +type CardProps = { + title: ReactNode; + description?: string; + href?: string; + children?: ReactNode; + icon?: ReactNode; + external?: boolean; +}; + +const base = + "flex flex-col gap-1.5 rounded-xl border border-fd-border bg-fd-card p-4"; + +export function Card({ + title, + description, + href, + children, + icon, + external, +}: CardProps) { + const content = ( + <> + <span className="flex items-center gap-2 text-sm font-medium"> + {icon} + <span className="flex-1">{title}</span> + {href ? ( + external ? ( + <ArrowUpRight className="text-fd-muted-foreground size-4 shrink-0 transition-transform group-hover:translate-x-0.5 group-hover:-translate-y-0.5" /> + ) : ( + <ArrowRight className="text-fd-muted-foreground size-4 shrink-0 transition-transform group-hover:translate-x-0.5" /> + ) + ) : null} + </span> + {(description || children) && ( + <span className="text-fd-muted-foreground text-sm"> + {description ?? children} + </span> + )} + </> + ); + + if (href) { + const className = `group ${base} transition-colors hover:border-fd-foreground/15 hover:bg-fd-muted/50`; + if (external) { + return ( + <a + href={href} + className={className} + target="_blank" + rel="noopener noreferrer" + > + {content} + </a> + ); + } + return ( + <Link href={href} className={className}> + {content} + </Link> + ); + } + + return <div className={base}>{content}</div>; +} + +export function Cards({ children }: { children: ReactNode }) { + return <div className="not-prose grid gap-3 sm:grid-cols-2">{children}</div>; +} + +export const CardsLLM = ({ children }: { children: ReactNode }) => ( + <ul>{children}</ul> +); +export const CardLLM = ({ title, description, href, children }: CardProps) => { + const blurb = description ?? children; + return ( + <li> + {href ? <a href={href}>{title}</a> : title} + {blurb ? <> — {blurb}</> : null} + </li> + ); +}; diff --git a/apps/docs/components/docs/fumadocs/code-collapsible.tsx b/apps/docs/components/docs/fumadocs/code-collapsible.tsx new file mode 100644 index 0000000..a18ccea --- /dev/null +++ b/apps/docs/components/docs/fumadocs/code-collapsible.tsx @@ -0,0 +1,48 @@ +"use client"; + +import * as React from "react"; +import { cn } from "@/lib/utils"; +import { + Collapsible, + CollapsibleContent, + CollapsibleTrigger, +} from "@/components/ui/radix/collapsible"; +import { ChevronDownIcon } from "lucide-react"; + +export function CodeCollapsible({ + code: _code, + children, + className, +}: { + code: string; + children: React.ReactNode; + className?: string; +}) { + const [isOpen, setIsOpen] = React.useState(false); + + return ( + <Collapsible + open={isOpen} + onOpenChange={setIsOpen} + className={cn("group/collapsible relative my-4", className)} + > + <CollapsibleContent + forceMount + className={cn( + "relative overflow-hidden [&_figure]:my-0", + !isOpen && "max-h-[200px]", + )} + > + {children} + </CollapsibleContent> + {!isOpen && ( + <CollapsibleTrigger className="from-fd-background via-fd-background/90 text-muted-foreground absolute inset-x-0 bottom-0 flex h-24 items-end justify-center rounded-b-lg bg-linear-to-t to-transparent pb-2 text-sm"> + <span className="flex items-center gap-1"> + <ChevronDownIcon className="size-4" /> + Show more + </span> + </CollapsibleTrigger> + )} + </Collapsible> + ); +} diff --git a/apps/docs/components/docs/fumadocs/install/component-source.tsx b/apps/docs/components/docs/fumadocs/install/component-source.tsx new file mode 100644 index 0000000..d61a8e7 --- /dev/null +++ b/apps/docs/components/docs/fumadocs/install/component-source.tsx @@ -0,0 +1,346 @@ +import fs from "node:fs/promises"; +import path from "node:path"; +import { DynamicCodeBlock } from "fumadocs-ui/components/dynamic-codeblock"; +import { CodeCollapsible } from "@/components/docs/fumadocs/code-collapsible"; + +type RegistryFile = { + content: string; + type: string; + path: string; + target?: string; +}; + +type RegistryItem = { + name: string; + type: string; + files: RegistryFile[]; + dependencies?: string[]; + registryDependencies?: string[]; +}; + +export type ResolvedFile = { + name: string; + path: string; + content: string; +}; + +export type ResolvedGroup = { + files: ResolvedFile[]; + dependencies: string[]; +}; + +export type ResolvedComponents = { + main: ResolvedGroup; + auiDeps: ResolvedGroup; + shadcn: ResolvedGroup; +}; + +export type RegistryFlavor = "radix" | "base"; + +const RADIX_IMPORT = /(?:from|import)\s*\(?\s*["'](?:radix-ui["']|@radix-ui\/)/; + +async function readFile(filePath: string): Promise<string | null> { + try { + return await fs.readFile(filePath, "utf-8"); + } catch { + return null; + } +} + +async function readLocalRegistry( + name: string, + flavor: RegistryFlavor, +): Promise<RegistryItem | null> { + const localPath = path.join( + process.cwd(), + "../registry/dist", + flavor === "base" ? "base" : ".", + `${name}.json`, + ); + + try { + const content = await fs.readFile(localPath, "utf-8"); + return JSON.parse(content) as RegistryItem; + } catch { + return null; + } +} + +async function readLocalShadcnComponent( + name: string, + flavor: RegistryFlavor, +): Promise<string | null> { + const uiPath = path.join( + process.cwd(), + "../../packages/ui/src/components/ui/radix", + `${name}.tsx`, + ); + + if (flavor === "base") { + const vendoredPath = path.join( + process.cwd(), + "../../packages/ui/src/components/ui/base", + `${name}.tsx`, + ); + const vendoredContent = await readFile(vendoredPath); + if (vendoredContent !== null) { + return vendoredContent; + } + + const fallbackContent = await readFile(uiPath); + return fallbackContent !== null && !RADIX_IMPORT.test(fallbackContent) + ? fallbackContent.replaceAll("@/components/ui/radix/", "@/components/ui/") + : null; + } + + const radixContent = await readFile(uiPath); + return radixContent !== null + ? radixContent.replaceAll("@/components/ui/radix/", "@/components/ui/") + : null; +} + +function parseRegistryDependency(dep: string): { + source: "assistant-ui" | "shadcn"; + name: string; +} { + if (dep.startsWith("https://r.assistant-ui.com/")) { + return { + source: "assistant-ui", + name: dep + .replace("https://r.assistant-ui.com/", "") + .replace(/^base\//, "") + .replace(".json", ""), + }; + } + return { source: "shadcn", name: dep }; +} + +const PRIMITIVE_BACKED_SHADCN = new Set([ + "button", + "tooltip", + "collapsible", + "dialog", + "popover", + "dropdown-menu", + "avatar", + "select", + "separator", + "tabs", + "toggle", + "toggle-group", + "checkbox", + "label", + "progress", + "slider", + "switch", + "scroll-area", + "context-menu", + "alert-dialog", + "hover-card", + "menubar", + "navigation-menu", + "radio-group", + "accordion", + "aspect-ratio", + "sidebar", +]); + +const NEUTRAL_SHADCN_DEPENDENCIES: Record<string, string[]> = { + form: ["react-hook-form", "@hookform/resolvers", "zod"], + resizable: ["react-resizable-panels"], + sonner: ["sonner"], + drawer: ["vaul"], + carousel: ["embla-carousel-react"], + "input-otp": ["input-otp"], +}; + +function shadcnDependencies( + name: string, + flavor: RegistryFlavor, +): string[] | undefined { + if (PRIMITIVE_BACKED_SHADCN.has(name)) { + return flavor === "base" ? ["@base-ui/react"] : ["radix-ui"]; + } + return NEUTRAL_SHADCN_DEPENDENCIES[name]; +} + +export async function resolveAllComponents( + components: string[], + flavor: RegistryFlavor = "base", +): Promise<ResolvedComponents> { + const visited = new Set<string>(); + const mainNpmDeps = new Set<string>(); + const auiNpmDeps = new Set<string>(); + const shadcnNpmDeps = new Set<string>(); + + const result: ResolvedComponents = { + main: { files: [], dependencies: [] }, + auiDeps: { files: [], dependencies: [] }, + shadcn: { files: [], dependencies: [] }, + }; + + async function resolveAssistantUI( + name: string, + isMain: boolean, + ): Promise<void> { + const key = `assistant-ui:${name}`; + if (visited.has(key)) return; + visited.add(key); + + const item = await readLocalRegistry(name, flavor); + if (!item) return; + + if (item.dependencies) { + for (const dep of item.dependencies) { + if (isMain) { + mainNpmDeps.add(dep); + } else { + auiNpmDeps.add(dep); + } + } + } + + if (item.files) { + for (const file of item.files) { + const filePath = file.target ?? file.path; + if (filePath === "lib/utils.ts") continue; + + const targetGroup = isMain ? result.main : result.auiDeps; + targetGroup.files.push({ + name, + path: filePath, + content: file.content, + }); + } + } + + if (item.registryDependencies) { + for (const dep of item.registryDependencies) { + const parsed = parseRegistryDependency(dep); + if (parsed.source === "assistant-ui") { + await resolveAssistantUI(parsed.name, false); + } else { + await resolveShadcn(parsed.name); + } + } + } + } + + async function resolveShadcn(name: string): Promise<void> { + const key = `shadcn:${name}`; + if (visited.has(key)) return; + visited.add(key); + + const content = await readLocalShadcnComponent(name, flavor); + if (!content) return; + + const deps = shadcnDependencies(name, flavor); + if (deps) { + for (const dep of deps) { + shadcnNpmDeps.add(dep); + } + } + + result.shadcn.files.push({ + name, + path: `components/ui/${name}.tsx`, + content, + }); + } + + for (const component of components) { + await resolveAssistantUI(component, true); + } + + const ignoredDeps = new Set(["clsx", "tailwind-merge", "lucide-react"]); + + result.main.dependencies = Array.from(mainNpmDeps) + .filter((dep) => !ignoredDeps.has(dep)) + .sort(); + result.auiDeps.dependencies = Array.from(auiNpmDeps) + .filter((dep) => !ignoredDeps.has(dep)) + .sort(); + result.shadcn.dependencies = Array.from(shadcnNpmDeps) + .filter((dep) => !ignoredDeps.has(dep)) + .sort(); + + return result; +} + +export async function ComponentSource({ + name, + title, + collapsible = true, + flavor = "base", +}: { + name: string; + title?: string; + collapsible?: boolean; + flavor?: RegistryFlavor; +}) { + const item = await readLocalRegistry(name, flavor); + + if (!item?.files?.[0]?.content) { + return ( + <div className="border-destructive bg-destructive/10 text-destructive rounded-md border p-4 text-sm"> + Component "{name}" not found in registry + </div> + ); + } + + let code = item.files[0].content; + const filePath = item.files[0].target ?? item.files[0].path; + + code = code.replaceAll("export default", "export"); + + const lang = (filePath.split(".").pop() ?? "tsx") as "tsx" | "ts" | "js"; + const displayTitle = title ?? filePath; + + const content = ( + <DynamicCodeBlock + lang={lang} + code={code} + codeblock={{ + title: displayTitle, + className: "[&_pre]:max-h-[450px]", + }} + /> + ); + + if (!collapsible) { + return content; + } + + return <CodeCollapsible code={code}>{content}</CodeCollapsible>; +} + +export function ComponentSourceFromFile({ + file, + collapsible = true, +}: { + file: ResolvedFile; + collapsible?: boolean; +}) { + let code = file.content; + + code = code.replaceAll("export default", "export"); + + const lang = (file.path.split(".").pop() ?? "tsx") as "tsx" | "ts" | "js"; + + const content = ( + <DynamicCodeBlock + lang={lang} + code={code} + codeblock={{ + title: file.path, + className: "[&_pre]:max-h-[450px]", + }} + /> + ); + + if (!collapsible) { + return content; + } + + return <CodeCollapsible code={code}>{content}</CodeCollapsible>; +} diff --git a/apps/docs/components/docs/fumadocs/install/install-command.tsx b/apps/docs/components/docs/fumadocs/install/install-command.tsx new file mode 100644 index 0000000..211e0bd --- /dev/null +++ b/apps/docs/components/docs/fumadocs/install/install-command.tsx @@ -0,0 +1,227 @@ +import fs from "node:fs"; +import path from "node:path"; +import { Tab, Tabs } from "@/components/docs/fumadocs/tabs"; +import { Flavored } from "@/components/docs/contexts/flavor.server"; +import type { LLMRenderContext } from "@/lib/get-llm-text"; +import { + resolveAllComponents, + ComponentSourceFromFile, + type RegistryFlavor, + type ResolvedComponents, + type ResolvedFile, + type ResolvedGroup, +} from "@/components/docs/fumadocs/install/component-source"; +import { SetupInstructions } from "@/components/docs/fumadocs/install/setup-instructions"; +import { + ExpoInstallTabs, + PackageManagerTabs, + ShadcnInstallTabs, +} from "@/components/docs/fumadocs/install/package-manager-tabs"; + +type InstallCommandProps = + | { + /** Shadcn registry components to install (will be prefixed with @assistant-ui/) */ + shadcn: string[]; + /** Show manual setup instructions for React, Tailwind, shadcn/ui */ + manualSetupInstructions?: boolean; + } + | { + /** NPM packages to install */ + npm: string[]; + } + | { + /** Expo packages to install with `expo install` */ + expo: string[]; + }; + +function FileGroup({ title, group }: { title: string; group: ResolvedGroup }) { + if (group.files.length === 0) return null; + + return ( + <div className="mt-6"> + <h4 className="text-muted-foreground mb-3 text-sm font-medium"> + {title} + </h4> + {group.dependencies.length > 0 && ( + <div className="mb-4"> + <PackageManagerTabs packages={group.dependencies} /> + </div> + )} + {group.files.map((file, index) => ( + <ComponentSourceFromFile key={`${file.path}-${index}`} file={file} /> + ))} + </div> + ); +} + +export async function InstallCommand(props: InstallCommandProps) { + if ("npm" in props) { + return <PackageManagerTabs packages={props.npm} />; + } + + if ("expo" in props) { + return <ExpoInstallTabs packages={props.expo} />; + } + + const components = props.shadcn; + const radixUrls = components.map( + (c) => `https://r.assistant-ui.com/${c}.json`, + ); + const baseUrls = components.map( + (c) => `https://r.assistant-ui.com/base/${c}.json`, + ); + + const [radixResolved, baseResolved] = await Promise.all([ + resolveAllComponents(components, "radix"), + resolveAllComponents(components, "base"), + ]); + + const manualFor = (resolved: ResolvedComponents) => ( + <> + {props.manualSetupInstructions && <SetupInstructions />} + <FileGroup title="Main Component" group={resolved.main} /> + <FileGroup title="assistant-ui dependencies" group={resolved.auiDeps} /> + <FileGroup title="shadcn/ui dependencies" group={resolved.shadcn} /> + </> + ); + + const namespaced = components.map((c) => `@assistant-ui/${c}`); + + return ( + <Tabs items={["CLI", "Manual"]}> + <Tab> + <ShadcnInstallTabs urls={namespaced} /> + <p className="text-muted-foreground mt-3 text-sm"> + The <code>@assistant-ui</code> namespace resolves the Radix or Base UI + flavor from your project's style through the{" "} + <a href="/docs/base-ui" className="underline underline-offset-2"> + style-aware registry + </a>{" "} + entry in <code>components.json</code>. Without that entry, add by + direct URL instead: + </p> + <Flavored + radix={<ShadcnInstallTabs urls={radixUrls} />} + base={<ShadcnInstallTabs urls={baseUrls} />} + /> + </Tab> + <Tab> + <Flavored + radix={manualFor(radixResolved)} + base={manualFor(baseResolved)} + /> + </Tab> + </Tabs> + ); +} + +// Every resolvable file is an assistant-ui component, sourced from packages/ui. +const REPO = "assistant-ui/assistant-ui"; +const UI_SRC = "packages/ui/src"; +const GITHUB_BLOB = `https://github.com/${REPO}/blob/main/${UI_SRC}`; +const GITHUB_RAW = `https://raw.githubusercontent.com/${REPO}/main/${UI_SRC}`; + +const CommandBlock = ({ command }: { command: string }) => ( + <pre> + <code className="language-bash">{command}</code> + </pre> +); + +function githubSourcePath(filePath: string, flavor: RegistryFlavor): string { + if (flavor !== "base") return filePath; + const basePath = filePath.replace(/\.tsx$/, ".base.tsx"); + return fs.existsSync( + path.join(process.cwd(), "../../packages/ui/src", basePath), + ) + ? basePath + : filePath; +} + +type LinkedFile = ResolvedFile & { sourcePath: string }; + +function buildDownloadCommand(files: LinkedFile[]): string { + const args = files + .map((file) => ` -o ${file.path} ${GITHUB_RAW}/${file.sourcePath}`) + .join(" \\\n"); + return `curl -sSL --create-dirs \\\n${args}`; +} + +// Instead of dumping each component's full source (the visual Manual tab), give +// the CLI command plus a manual path: npm deps, shadcn components, and the +// GitHub-linked aui files with one curl to fetch them all. +export const InstallCommandLLM = async ( + props: InstallCommandProps, + ctx?: LLMRenderContext, +) => { + if ("npm" in props) { + return <CommandBlock command={`npm install ${props.npm.join(" ")}`} />; + } + if ("expo" in props) { + return ( + <CommandBlock command={`npx expo install ${props.expo.join(" ")}`} /> + ); + } + + const flavor = ctx?.flavor ?? "base"; + const namespaced = props.shadcn.map((c) => `@assistant-ui/${c}`); + const urls = props.shadcn.map((c) => + flavor === "base" + ? `https://r.assistant-ui.com/base/${c}.json` + : `https://r.assistant-ui.com/${c}.json`, + ); + const resolved = await resolveAllComponents(props.shadcn, flavor); + const files: LinkedFile[] = [ + ...resolved.main.files, + ...resolved.auiDeps.files, + ].map((file) => ({ + ...file, + sourcePath: githubSourcePath(file.path, flavor), + })); + // npm packages the copied files import. shadcn deps (e.g. radix-ui) are + // omitted here — they install with the shadcn components below. + const npmDeps = [ + ...new Set([ + ...resolved.main.dependencies, + ...resolved.auiDeps.dependencies, + ]), + ]; + // shadcn/ui components can't be GitHub-linked (not under packages/ui/src), so + // the manual path adds them via the shadcn CLI instead. + const shadcnComponents = resolved.shadcn.files.map((file) => file.name); + + return ( + <> + <p> + With the style-aware registry configured in components.json + ("@assistant-ui": + "https://r.assistant-ui.com/styles/{style}/{name}.json"), + the flavor resolves from the project style automatically: + </p> + <CommandBlock command={`npx shadcn@latest add ${namespaced.join(" ")}`} /> + <p>Or add by direct URL without registry configuration:</p> + <CommandBlock command={`npx shadcn@latest add ${urls.join(" ")}`} /> + {files.length > 0 && ( + <> + <p>Or install manually:</p> + {npmDeps.length > 0 && ( + <CommandBlock command={`npm install ${npmDeps.join(" ")}`} /> + )} + {shadcnComponents.length > 0 && ( + <CommandBlock + command={`npx shadcn@latest add ${shadcnComponents.join(" ")}`} + /> + )} + <p>Then copy these source files from GitHub:</p> + <ul> + {files.map((file) => ( + <li key={file.path}> + <a href={`${GITHUB_BLOB}/${file.sourcePath}`}>{file.path}</a> + </li> + ))} + </ul> + <CommandBlock command={buildDownloadCommand(files)} /> + </> + )} + </> + ); +}; diff --git a/apps/docs/components/docs/fumadocs/install/package-manager-tabs.tsx b/apps/docs/components/docs/fumadocs/install/package-manager-tabs.tsx new file mode 100644 index 0000000..f4fe2a6 --- /dev/null +++ b/apps/docs/components/docs/fumadocs/install/package-manager-tabs.tsx @@ -0,0 +1,161 @@ +"use client"; + +import { useState } from "react"; +import { DynamicCodeBlock } from "fumadocs-ui/components/dynamic-codeblock"; +import { cn } from "@/lib/utils"; +import { useAnimatedTabs } from "@/hooks/use-animated-tabs"; +import { analytics } from "@/lib/analytics"; + +const PACKAGE_MANAGERS = ["npm", "pnpm", "yarn", "bun", "xpm"] as const; +type PackageManager = (typeof PACKAGE_MANAGERS)[number]; + +function getInstallCommand(pm: PackageManager, packages: string[]): string { + const pkgList = packages.join(" "); + switch (pm) { + case "npm": + return `npm install ${pkgList}`; + case "yarn": + return `yarn add ${pkgList}`; + case "pnpm": + return `pnpm add ${pkgList}`; + case "bun": + return `bun add ${pkgList}`; + case "xpm": + return `xpm add ${pkgList}`; + } +} + +function getShadcnCommand(pm: PackageManager, urls: string[]): string { + const urlList = urls.join(" "); + switch (pm) { + case "npm": + case "yarn": + return `npx shadcn@latest add ${urlList}`; + case "pnpm": + return `pnpm dlx shadcn@latest add ${urlList}`; + case "bun": + return `bunx --bun shadcn@latest add ${urlList}`; + case "xpm": + return `xpx shadcn@latest add ${urlList}`; + } +} + +function getExpoInstallCommand(pm: PackageManager, packages: string[]): string { + const pkgList = packages.join(" "); + switch (pm) { + case "npm": + return `npx expo install ${pkgList}`; + case "yarn": + return `npx expo install --yarn ${pkgList}`; + case "pnpm": + return `npx expo install --pnpm ${pkgList}`; + case "bun": + return `npx expo install --bun ${pkgList}`; + case "xpm": + return `xpx expo install ${pkgList}`; + } +} + +function CommandTabs({ + getCommand, + packageManagers = PACKAGE_MANAGERS, +}: { + getCommand: (pm: PackageManager) => string; + packageManagers?: readonly PackageManager[]; +}) { + const [pm, setPm] = useState<PackageManager>(packageManagers[0] ?? "npm"); + const activeIndex = packageManagers.indexOf(pm); + + const { + containerRef, + tabRefs, + hoveredIndex, + setHoveredIndex, + activeStyle, + hoverStyle, + } = useAnimatedTabs({ activeIndex }); + + return ( + <div className="not-prose border-border bg-background my-4 overflow-hidden rounded-xl border"> + <div + ref={containerRef} + className="relative flex items-center gap-1 px-3 py-2" + > + {hoveredIndex !== null && hoverStyle.width > 0 && ( + <div + className="pointer-events-none absolute h-6.5 rounded-md bg-[oklch(0.88_0_0)] transition-all duration-200 ease-out dark:bg-[oklch(0.25_0_0)]" + style={{ + left: `${hoverStyle.left}px`, + width: `${hoverStyle.width}px`, + }} + /> + )} + + {activeStyle.width > 0 && ( + <div + className="pointer-events-none absolute h-6.5 rounded-md bg-[oklch(0.92_0_0)] transition-all duration-200 ease-out dark:bg-[oklch(0.22_0_0)]" + style={{ + left: `${activeStyle.left}px`, + width: `${activeStyle.width}px`, + }} + /> + )} + + {packageManagers.map((manager, index) => ( + <button + key={manager} + ref={(el) => { + tabRefs.current[index] = el; + }} + type="button" + onClick={() => { + if (pm !== manager) { + analytics.install.packageManagerSelected(manager); + } + setPm(manager); + }} + onMouseEnter={() => setHoveredIndex(index)} + onMouseLeave={() => setHoveredIndex(null)} + className={cn( + "relative z-10 rounded-md px-2.5 py-1 text-xs font-medium transition-colors duration-200", + pm === manager + ? "text-foreground" + : "text-muted-foreground hover:text-foreground", + )} + > + {manager} + </button> + ))} + </div> + <div className="bg-muted overflow-hidden rounded-t-lg [&_figure]:my-0! [&_figure]:rounded-none! [&_figure]:border-none! [&_figure]:bg-transparent!"> + <DynamicCodeBlock lang="bash" code={getCommand(pm)} /> + </div> + </div> + ); +} + +export function PackageManagerTabs({ + packages, +}: { + packages: string[]; +}): React.ReactElement { + return <CommandTabs getCommand={(pm) => getInstallCommand(pm, packages)} />; +} + +export function ExpoInstallTabs({ + packages, +}: { + packages: string[]; +}): React.ReactElement { + return ( + <CommandTabs getCommand={(pm) => getExpoInstallCommand(pm, packages)} /> + ); +} + +export function ShadcnInstallTabs({ + urls, +}: { + urls: string[]; +}): React.ReactElement { + return <CommandTabs getCommand={(pm) => getShadcnCommand(pm, urls)} />; +} diff --git a/apps/docs/components/docs/fumadocs/install/setup-instructions.tsx b/apps/docs/components/docs/fumadocs/install/setup-instructions.tsx new file mode 100644 index 0000000..96d26e4 --- /dev/null +++ b/apps/docs/components/docs/fumadocs/install/setup-instructions.tsx @@ -0,0 +1,62 @@ +import { ArrowUpRight } from "lucide-react"; + +function ExternalLink({ + href, + children, +}: { + href: string; + children: React.ReactNode; +}) { + return ( + <a + href={href} + target="_blank" + rel="noopener noreferrer" + className="text-fd-primary hover:text-fd-primary/80 inline-flex items-center gap-0.5 underline underline-offset-2" + > + {children} + <ArrowUpRight className="size-3" /> + </a> + ); +} + +export function SetupInstructions() { + return ( + <div className="mt-6"> + <h4 className="text-muted-foreground mb-3 text-sm font-medium"> + Prerequisites + </h4> + + <ol className="list-decimal space-y-3 pl-5 text-sm"> + <li> + <span className="font-medium">Setup React</span> + <p className="text-muted-foreground mt-1"> + We recommend{" "} + <ExternalLink href="https://nextjs.org/docs/getting-started"> + Next.js + </ExternalLink>{" "} + or <ExternalLink href="https://vite.dev/guide/">Vite</ExternalLink>. + </p> + </li> + + <li> + <span className="font-medium">Setup shadcn/ui</span> + <p className="text-muted-foreground mt-1"> + Follow the{" "} + <ExternalLink href="https://ui.shadcn.com/docs/installation/manual"> + manual installation guide + </ExternalLink>{" "} + to configure: + </p> + <ul className="text-muted-foreground mt-1.5 list-disc space-y-0.5 pl-5"> + <li>Tailwind CSS</li> + <li>TypeScript path aliases</li> + <li>tw-animate-css</li> + <li>The cn helper</li> + <li>components.json (for CLI usage)</li> + </ul> + </li> + </ol> + </div> + ); +} diff --git a/apps/docs/components/docs/fumadocs/steps.tsx b/apps/docs/components/docs/fumadocs/steps.tsx new file mode 100644 index 0000000..60076d7 --- /dev/null +++ b/apps/docs/components/docs/fumadocs/steps.tsx @@ -0,0 +1,32 @@ +import type { ReactNode } from "react"; + +export function Steps({ children }: { children: ReactNode }) { + return ( + <div className="steps-container prose-no-margin flex min-w-0 flex-col"> + {children} + </div> + ); +} + +export function Step({ children }: { children: ReactNode }) { + return ( + <div className="group relative flex gap-4"> + <div className="flex flex-col items-center"> + <div className="bg-fd-muted text-fd-muted-foreground flex size-7 shrink-0 items-center justify-center rounded-full text-sm font-medium"> + <span className="step-number" /> + </div> + <div className="bg-fd-border w-px flex-1 group-last:hidden" /> + </div> + <div className="min-w-0 flex-1 pt-0.5 pb-6 group-last:pb-0 [&>h3]:!mt-0 [&>h3]:!mb-3 [&>h3]:!text-base [&>h3]:!font-medium"> + {children} + </div> + </div> + ); +} + +export const StepsLLM = ({ children }: { children: ReactNode }) => ( + <ol>{children}</ol> +); +export const StepLLM = ({ children }: { children: ReactNode }) => ( + <li>{children}</li> +); diff --git a/apps/docs/components/docs/fumadocs/tabs.llm.tsx b/apps/docs/components/docs/fumadocs/tabs.llm.tsx new file mode 100644 index 0000000..1fca9a8 --- /dev/null +++ b/apps/docs/components/docs/fumadocs/tabs.llm.tsx @@ -0,0 +1,31 @@ +import { Children, Fragment, isValidElement, type ReactNode } from "react"; +import type { TabProps, TabsProps } from "./tabs"; + +// Tab bodies concatenate in text and read as sequential steps ("install all 11 +// SDKs") unless labeled. Frame as "Choose one:" and label each body by its +// `value` prop, falling back to the parent `items` entry by position. +export function TabsLLM({ items, children }: TabsProps): ReactNode { + const tabs = Children.toArray(children).filter(isValidElement); + return ( + <> + <p>Choose one:</p> + {tabs.map((tab, index) => { + const label = (tab.props as { value?: string }).value ?? items?.[index]; + return ( + <Fragment key={index}> + {label ? ( + <p> + <strong>{label}</strong> + </p> + ) : null} + {tab} + </Fragment> + ); + })} + </> + ); +} + +export function TabLLM({ children }: TabProps): ReactNode { + return <>{children}</>; +} diff --git a/apps/docs/components/docs/fumadocs/tabs.tsx b/apps/docs/components/docs/fumadocs/tabs.tsx new file mode 100644 index 0000000..c2cabf5 --- /dev/null +++ b/apps/docs/components/docs/fumadocs/tabs.tsx @@ -0,0 +1,239 @@ +"use client"; + +import { + createContext, + useCallback, + useContext, + useEffect, + useId, + useMemo, + useState, + type ReactNode, +} from "react"; +import { cn } from "@/lib/utils"; +import { useAnimatedTabs } from "@/hooks/use-animated-tabs"; + +type CollectionKey = string | symbol; + +export interface TabsProps extends React.ComponentProps<"div"> { + /** + * Simple mode: pass an array of tab labels + */ + items?: string[]; + + /** + * Default selected tab index (for simple mode) + * @defaultValue 0 + */ + defaultIndex?: number; + + /** + * Group ID for syncing tabs across the page + */ + groupId?: string; + + /** + * Additional label in tabs list + */ + label?: ReactNode; + + /** + * Controlled escaped value of the active tab. Pair with `onValueChange`. + */ + value?: string; + + /** + * Called with the escaped value when the active tab changes (controlled mode). + */ + onValueChange?: (value: string) => void; +} + +const TabsContext = createContext<{ + items: string[] | undefined; + collection: CollectionKey[]; + value: string | undefined; + setValue: (value: string | undefined) => void; +} | null>(null); + +function useTabContext() { + const ctx = useContext(TabsContext); + if (!ctx) throw new Error("You must wrap your component in <Tabs>"); + return ctx; +} + +export function escapeValue(v: string): string { + return v.toLowerCase().replace(/\s/g, "-"); +} + +export function Tabs({ + className, + items, + label, + defaultIndex = 0, + groupId, + value: controlledValue, + onValueChange, + children, + ...props +}: TabsProps): React.ReactElement { + const defaultItem = items?.[defaultIndex]; + const defaultValue = defaultItem ? escapeValue(defaultItem) : undefined; + const [internalValue, setInternalValue] = useState(defaultValue); + const value = controlledValue ?? internalValue; + const setValue = useCallback( + (v: string | undefined) => { + if (onValueChange) { + if (v !== undefined) onValueChange(v); + } else { + setInternalValue(v); + } + }, + [onValueChange], + ); + const collection = useMemo<CollectionKey[]>(() => [], []); + + const activeIndex = items + ? items.findIndex((item) => escapeValue(item) === value) + : -1; + + const { + containerRef, + tabRefs, + hoveredIndex, + setHoveredIndex, + activeStyle, + hoverStyle, + } = useAnimatedTabs({ activeIndex }); + + return ( + <div + className={cn("my-4 flex min-w-0 flex-col", className)} + data-tabs="" + {...props} + > + {items && ( + <div + ref={containerRef} + className="relative flex scrollbar-none items-center gap-1 overflow-x-auto" + > + {label && ( + <span className="my-auto me-auto text-sm font-medium">{label}</span> + )} + + {hoveredIndex !== null && hoverStyle.width > 0 && ( + <div + className="bg-fd-accent pointer-events-none absolute top-0 h-7.5 rounded-md transition-all duration-200 ease-out" + style={{ + left: `${hoverStyle.left}px`, + width: `${hoverStyle.width}px`, + }} + /> + )} + + {activeStyle.width > 0 && ( + <div + className="bg-fd-accent pointer-events-none absolute top-0 h-7.5 rounded-md transition-all duration-200 ease-out" + style={{ + left: `${activeStyle.left}px`, + width: `${activeStyle.width}px`, + }} + /> + )} + + {items.map((item, index) => ( + <button + key={item} + ref={(el) => { + tabRefs.current[index] = el; + }} + type="button" + role="tab" + aria-selected={escapeValue(item) === value} + data-state={escapeValue(item) === value ? "active" : "inactive"} + className={cn( + "relative z-10 flex h-7.5 cursor-pointer items-center justify-center rounded-md px-3 text-sm whitespace-nowrap transition-colors", + "text-fd-muted-foreground hover:text-fd-foreground", + "data-[state=active]:text-fd-foreground data-[state=active]:font-medium", + )} + onClick={() => setValue(escapeValue(item))} + onMouseEnter={() => setHoveredIndex(index)} + onMouseLeave={() => setHoveredIndex(null)} + > + {item} + </button> + ))} + </div> + )} + + <TabsContext.Provider + value={useMemo( + () => ({ items, collection, value, setValue }), + [items, collection, value, setValue], + )} + > + {children} + </TabsContext.Provider> + </div> + ); +} + +export interface TabProps extends React.ComponentProps<"div"> { + /** + * Value of tab, auto-detected from index if not specified + */ + value?: string; +} + +export function Tab({ + value, + className, + children, + ...props +}: TabProps): React.ReactElement { + const { items, value: activeValue } = useTabContext(); + const collectionIndex = useCollectionIndex(); + const resolved = value ?? items?.at(collectionIndex); + + if (!resolved) { + throw new Error( + "Failed to resolve tab `value`, please pass a `value` prop to the Tab component.", + ); + } + + const isActive = escapeValue(resolved) === activeValue; + + return ( + <div + role="tabpanel" + data-state={isActive ? "active" : "inactive"} + hidden={!isActive} + className={cn( + "prose-no-margin mt-4 min-w-0 text-sm", + "[&_a]:text-fd-primary hover:[&_a]:text-fd-primary/80 [&_a]:font-medium [&_a]:underline [&_a]:underline-offset-2", + "data-[state=inactive]:hidden", + className, + )} + {...props} + > + {children} + </div> + ); +} + +/** + * Return the index of children using React context collection pattern + */ +function useCollectionIndex(): number { + const key = useId(); + const { collection } = useTabContext(); + + useEffect(() => { + return () => { + const idx = collection.indexOf(key); + if (idx !== -1) collection.splice(idx, 1); + }; + }, [key, collection]); + + if (!collection.includes(key)) collection.push(key); + return collection.indexOf(key); +} diff --git a/apps/docs/components/docs/layout/docs-footer.tsx b/apps/docs/components/docs/layout/docs-footer.tsx new file mode 100644 index 0000000..18db93a --- /dev/null +++ b/apps/docs/components/docs/layout/docs-footer.tsx @@ -0,0 +1,58 @@ +"use client"; + +import Link from "next/link"; +import { ChevronLeft, ChevronRight } from "lucide-react"; +import type { ReactNode } from "react"; + +type FooterItem = { + name: ReactNode; + url: string; + section?: ReactNode; +}; + +type DocsFooterProps = { + previous?: FooterItem | undefined; + next?: FooterItem | undefined; +}; + +export function DocsFooter({ previous, next }: DocsFooterProps) { + if (!previous && !next) return null; + + return ( + <nav className="not-prose mt-16 flex items-center justify-between gap-4 text-sm"> + {previous ? ( + <Link + href={previous.url} + className="group text-muted-foreground hover:text-foreground inline-flex min-w-0 items-center gap-1.5 transition-colors" + > + <ChevronLeft className="size-4 shrink-0 transition-transform group-hover:-translate-x-0.5" /> + <span className="min-w-0 truncate"> + {previous.section ? ( + <span className="opacity-60">{previous.section} / </span> + ) : null} + {previous.name} + </span> + </Link> + ) : ( + <span /> + )} + + {next ? ( + <Link + href={next.url} + className="group text-muted-foreground hover:text-foreground inline-flex min-w-0 items-center gap-1.5 transition-colors" + > + <span className="min-w-0 truncate"> + {next.section ? ( + <span className="opacity-60">{next.section} / </span> + ) : null} + {next.name} + </span> + <ChevronRight className="size-4 shrink-0 transition-transform group-hover:translate-x-0.5" /> + </Link> + ) : ( + <span /> + )} + </nav> + ); +} diff --git a/apps/docs/components/docs/layout/docs-header.tsx b/apps/docs/components/docs/layout/docs-header.tsx new file mode 100644 index 0000000..5339ae3 --- /dev/null +++ b/apps/docs/components/docs/layout/docs-header.tsx @@ -0,0 +1,348 @@ +"use client"; + +import { useMemo, useState } from "react"; +import Link from "next/link"; +import { usePathname } from "next/navigation"; +import type * as PageTree from "fumadocs-core/page-tree"; +import { ArrowUpRight, LayoutGrid, Menu, Search, X } from "lucide-react"; +import { useSearchContext } from "fumadocs-ui/contexts/search"; +import { NAV_ITEMS, type NavItem } from "@/lib/constants"; +import { CloudButton } from "@/components/shared/cloud-button"; +import { MoreDropdown } from "@/components/shared/more-dropdown"; +import { NavItems } from "@/components/shared/nav-items"; +import { useDocsSidebar } from "@/components/docs/contexts/sidebar"; +import { useAssistantPanel } from "@/components/docs/assistant/context"; +import { getPanelWidth } from "@/components/docs/layout/docs-layout"; +import { ThemeToggle } from "@/components/shared/theme-toggle"; +import { HeaderBrandLink } from "@/components/shared/header-brand-link"; +import { analytics } from "@/lib/analytics"; +import { cn } from "@/lib/utils"; +import { usePlatform } from "@/components/docs/platform/context"; +import { + buildPlatformSections, + findPathToNode, +} from "@/components/docs/platform/tree"; + +interface DocsHeaderProps { + section: string; + sectionHref: string; + mobileSectionTree?: PageTree.Root | undefined; +} + +function AskAIButton() { + const { toggle } = useAssistantPanel(); + + return ( + <button + type="button" + onClick={toggle} + className="border-border/50 bg-muted/50 text-muted-foreground hover:bg-muted hover:text-foreground flex h-8 shrink-0 items-center rounded-lg border px-3 text-sm transition-colors" + > + Ask AI + </button> + ); +} + +function HeaderSearch() { + const { setOpenSearch, hotKey } = useSearchContext(); + + return ( + <button + type="button" + onClick={() => { + analytics.search.opened("header"); + setOpenSearch(true); + }} + className="border-border/50 bg-muted/50 text-muted-foreground hover:bg-muted hover:text-foreground flex h-8 w-full max-w-96 cursor-pointer items-center gap-2 rounded-lg border px-3 text-sm transition-colors" + > + <Search className="size-3.5 shrink-0" /> + <span className="flex-1 text-left">Search...</span> + <div className="flex gap-0.5"> + {hotKey.map((k, i) => ( + <kbd + key={i} + className="bg-background text-muted-foreground rounded px-1.5 py-0.5 text-[10px] font-medium" + > + {k.display} + </kbd> + ))} + </div> + </button> + ); +} + +const CONDENSED_HIDDEN = new Set(["Showcase", "Pricing"]); + +function MobileSectionBreadcrumb({ + tree, + section, +}: { + tree: PageTree.Root; + section: string; +}) { + const pathname = usePathname(); + const { platform } = usePlatform(); + + const activeSection = useMemo(() => { + const folders = tree.children.filter( + (node): node is PageTree.Folder => node.type === "folder", + ); + + const sections = buildPlatformSections(folders, platform); + const active = sections.find((folder) => findPathToNode(folder, pathname)); + if (!active || typeof active.name !== "string" || active.name === section) { + return null; + } + + return { + label: active.name, + href: active.index?.url, + }; + }, [tree, pathname, platform, section]); + + if (!activeSection) return null; + + return ( + <span className="flex min-w-0 items-center md:hidden"> + <span className="text-muted-foreground/40 mx-3 shrink-0">/</span> + {activeSection.href ? ( + <Link + href={activeSection.href} + className="text-foreground hover:text-foreground/80 min-w-0 truncate text-sm font-medium transition-colors" + > + {activeSection.label} + </Link> + ) : ( + <span className="text-foreground min-w-0 truncate text-sm font-medium"> + {activeSection.label} + </span> + )} + </span> + ); +} + +export function DocsHeader({ + section, + sectionHref, + mobileSectionTree, +}: DocsHeaderProps) { + const { setOpenSearch } = useSearchContext(); + const { + open: sidebarOpen, + setOpen: setSidebarOpen, + toggle: toggleSidebar, + } = useDocsSidebar(); + const [navMenuOpen, setNavMenuOpen] = useState(false); + const { open, width, isResizing } = useAssistantPanel(); + + const sectionFilter = (item: (typeof NAV_ITEMS)[number]) => + item.type !== "link" || item.href !== sectionHref; + const filteredItems = NAV_ITEMS.filter(sectionFilter); + const condensedItems = filteredItems.filter( + (item) => !CONDENSED_HIDDEN.has(item.label), + ); + const moreItems = filteredItems.filter( + (item): item is Extract<NavItem, { type: "link" }> => + item.type === "link" && CONDENSED_HIDDEN.has(item.label), + ); + + const handleNavMenuToggle = () => { + if (!navMenuOpen) setSidebarOpen(false); + setNavMenuOpen((prev) => !prev); + }; + + const handleSidebarToggle = () => { + if (!sidebarOpen) setNavMenuOpen(false); + toggleSidebar(); + }; + + return ( + <header + className={cn( + "sticky top-0 z-50 md:mr-(--chat-panel-width)", + !isResizing && + "transition-[margin] duration-200 ease-[cubic-bezier(0.32,0.72,0,1)]", + )} + style={ + { + "--chat-panel-width": getPanelWidth(open, width), + } as React.CSSProperties + } + > + <div className="from-background pointer-events-none absolute inset-x-0 top-0 h-14 bg-linear-to-b to-transparent mask-[linear-gradient(to_bottom,black_75%,transparent)] backdrop-blur-xl" /> + <div className="relative flex h-12 w-full items-center px-4"> + <div className="flex min-w-0 flex-1 items-center"> + <HeaderBrandLink labelClassName="hidden sm:inline" /> + <span className="text-muted-foreground/40 mx-3">/</span> + <Link + href={sectionHref} + className="text-foreground hover:text-foreground/80 text-sm font-medium transition-colors" + > + {section} + </Link> + {mobileSectionTree && ( + <MobileSectionBreadcrumb + tree={mobileSectionTree} + section={section} + /> + )} + </div> + + {/* Mobile controls */} + <div className="ml-auto flex shrink-0 items-center gap-1 md:hidden"> + <AskAIButton /> + <button + type="button" + onClick={() => { + analytics.search.opened("header"); + setOpenSearch(true); + }} + className="text-muted-foreground hover:text-foreground flex size-8 cursor-pointer items-center justify-center transition-colors" + aria-label="Search" + > + <Search className="size-4" /> + </button> + <ThemeToggle /> + <button + type="button" + onClick={handleNavMenuToggle} + className="text-muted-foreground hover:text-foreground flex size-8 items-center justify-center transition-colors" + aria-label="Site navigation" + > + {navMenuOpen ? ( + <X className="size-5" /> + ) : ( + <LayoutGrid className="size-4.5" /> + )} + </button> + <button + type="button" + onClick={handleSidebarToggle} + className="text-muted-foreground hover:text-foreground flex size-8 items-center justify-center transition-colors" + aria-label="Toggle sidebar" + > + {sidebarOpen ? ( + <X className="size-5" /> + ) : ( + <Menu className="size-5" /> + )} + </button> + </div> + + {/* Condensed nav: md to lg */} + <div className="ml-auto hidden items-center gap-2 md:flex lg:hidden"> + <AskAIButton /> + <button + type="button" + onClick={() => { + analytics.search.opened("header"); + setOpenSearch(true); + }} + className="text-muted-foreground hover:text-foreground flex size-8 cursor-pointer items-center justify-center transition-colors" + aria-label="Search" + > + <Search className="size-4" /> + </button> + <nav className="flex shrink-0 items-center"> + <NavItems items={condensedItems} megaAlign="end" /> + {moreItems.length > 0 && <MoreDropdown items={moreItems} />} + </nav> + <CloudButton variant="docs" /> + <ThemeToggle /> + </div> + + {/* Full nav: lg+ */} + <div className="ml-auto hidden items-center gap-2 lg:flex"> + <AskAIButton /> + <HeaderSearch /> + <nav className="flex shrink-0 items-center"> + <NavItems items={filteredItems} megaAlign="end" /> + </nav> + <CloudButton variant="docs" /> + <ThemeToggle /> + </div> + </div> + + {/* Mobile nav menu */} + <div + className={cn( + "bg-background fixed inset-x-0 top-12 bottom-0 z-40 transition-opacity duration-200 md:hidden", + navMenuOpen ? "opacity-100" : "pointer-events-none opacity-0", + )} + > + <nav className="flex h-full flex-col gap-1 overflow-y-auto px-4 pt-4"> + {filteredItems.map((item) => { + if (item.type === "link") { + return item.href.startsWith("http") ? ( + <a + key={item.href} + href={item.href} + target="_blank" + rel="noopener noreferrer" + onClick={() => setNavMenuOpen(false)} + className="text-foreground py-3 text-lg transition-colors" + > + {item.label} + </a> + ) : ( + <Link + key={item.href} + href={item.href} + onClick={() => setNavMenuOpen(false)} + className="text-foreground py-3 text-lg transition-colors" + > + {item.label} + </Link> + ); + } + + const groups = item.groups; + + return ( + <div key={item.label} className="flex flex-col"> + {groups.map((group) => ( + <div key={group.label} className="flex flex-col"> + <span className="text-muted-foreground py-3 text-sm"> + {group.label} + </span> + {group.items.map((link) => + link.external ? ( + <a + key={link.href} + href={link.href} + target="_blank" + rel="noopener noreferrer" + onClick={() => setNavMenuOpen(false)} + className="text-foreground flex items-center gap-1.5 py-2 pl-4 text-lg transition-colors" + > + {link.label} + <ArrowUpRight className="size-3.5 opacity-40" /> + </a> + ) : ( + <Link + key={link.href} + href={link.href} + onClick={() => setNavMenuOpen(false)} + className="text-foreground py-2 pl-4 text-lg transition-colors" + > + {link.label} + </Link> + ), + )} + </div> + ))} + </div> + ); + })} + <div className="mt-auto border-t py-6"> + <CloudButton + variant="mobile" + className="w-auto" + onClick={() => setNavMenuOpen(false)} + /> + </div> + </nav> + </div> + </header> + ); +} diff --git a/apps/docs/components/docs/layout/docs-layout.tsx b/apps/docs/components/docs/layout/docs-layout.tsx new file mode 100644 index 0000000..efef62d --- /dev/null +++ b/apps/docs/components/docs/layout/docs-layout.tsx @@ -0,0 +1,82 @@ +"use client"; + +import { useEffect, type ReactNode } from "react"; +import { AssistantPanelContent } from "@/components/docs/assistant/panel"; +import { useAssistantPanel } from "@/components/docs/assistant/context"; +import { DOCS_SIDEBAR_WIDTH } from "@/components/docs/contexts/sidebar"; +import { analytics } from "@/lib/analytics"; +import { cn } from "@/lib/utils"; + +export const COLLAPSED_WIDTH = "12px"; + +export function getPanelWidth(open: boolean, width: number): string { + return open ? `${width}px` : COLLAPSED_WIDTH; +} + +export function DocsContent({ children }: { children: ReactNode }): ReactNode { + const { open, width, isResizing } = useAssistantPanel(); + + return ( + <div + className={cn( + "@container md:mr-(--chat-panel-width) md:ml-(--sidebar-width)", + !isResizing && + "transition-[margin] duration-200 ease-[cubic-bezier(0.32,0.72,0,1)]", + )} + style={ + { + "--chat-panel-width": getPanelWidth(open, width), + "--sidebar-width": `${DOCS_SIDEBAR_WIDTH}px`, + } as React.CSSProperties + } + > + {children} + </div> + ); +} + +export function DocsAssistantPanel(): ReactNode { + const { open, width, isResizing, toggle } = useAssistantPanel(); + + useEffect(() => { + const handleKeyDown = (e: KeyboardEvent) => { + if (e.repeat || !(e.metaKey || e.ctrlKey) || e.key.toLowerCase() !== "i") + return; + const target = e.target; + if ( + target instanceof HTMLElement && + (target.tagName === "INPUT" || + target.tagName === "TEXTAREA" || + target.isContentEditable) + ) { + return; + } + e.preventDefault(); + e.stopPropagation(); + analytics.assistant.panelToggled({ open: !open, source: "shortcut" }); + toggle(); + }; + document.addEventListener("keydown", handleKeyDown, true); + return () => document.removeEventListener("keydown", handleKeyDown, true); + }, [toggle, open]); + + return ( + <div + className={cn( + "fixed top-0 right-0 bottom-0 z-50 hidden w-(--panel-width) md:block", + !isResizing && + "transition-[width] duration-200 ease-[cubic-bezier(0.32,0.72,0,1)]", + )} + style={ + { + "--panel-width": getPanelWidth(open, width), + "--panel-content-width": `${width}px`, + } as React.CSSProperties + } + > + <div className="h-full overflow-hidden"> + <AssistantPanelContent /> + </div> + </div> + ); +} diff --git a/apps/docs/components/docs/layout/docs-pager.tsx b/apps/docs/components/docs/layout/docs-pager.tsx new file mode 100644 index 0000000..ffad0b2 --- /dev/null +++ b/apps/docs/components/docs/layout/docs-pager.tsx @@ -0,0 +1,85 @@ +"use client"; + +import Link from "next/link"; +import { + ChevronLeft, + ChevronRight, + MoreHorizontal, + Copy, + FileText, +} from "lucide-react"; +import { + DropdownMenu, + DropdownMenuContent, + DropdownMenuItem, + DropdownMenuTrigger, +} from "@/components/ui/dropdown-menu"; +import { BASE_URL } from "@/lib/constants"; +import { useMarkdownCopy } from "@/hooks/use-markdown-copy"; + +type PagerItem = { + url: string; +}; + +type DocsPagerProps = { + previous?: PagerItem; + next?: PagerItem; + markdownUrl?: string; +}; + +export function DocsPager({ previous, next, markdownUrl }: DocsPagerProps) { + const { copy, prefetch, isLoading } = useMarkdownCopy(markdownUrl); + + const buttonClass = + "flex size-7 items-center justify-center rounded-md bg-muted/50 text-muted-foreground transition-colors hover:bg-muted hover:text-foreground sm:size-8"; + const disabledClass = + "flex size-7 items-center justify-center rounded-md bg-muted/30 text-muted-foreground/40 cursor-not-allowed sm:size-8"; + + return ( + <div className="flex items-center gap-1"> + {previous ? ( + <Link href={previous.url} className={buttonClass}> + <ChevronLeft className="size-4" /> + </Link> + ) : ( + <div className={disabledClass}> + <ChevronLeft className="size-4" /> + </div> + )} + {next ? ( + <Link href={next.url} className={buttonClass}> + <ChevronRight className="size-4" /> + </Link> + ) : ( + <div className={disabledClass}> + <ChevronRight className="size-4" /> + </div> + )} + {markdownUrl && ( + <DropdownMenu onOpenChange={(open) => open && prefetch()}> + <DropdownMenuTrigger className={buttonClass}> + <MoreHorizontal className="size-4" /> + </DropdownMenuTrigger> + <DropdownMenuContent> + <DropdownMenuItem onClick={copy} disabled={isLoading}> + <Copy className="size-4" /> + {isLoading ? "Loading..." : "Copy page"} + </DropdownMenuItem> + <DropdownMenuItem + render={ + <a + href={`${BASE_URL}${markdownUrl}`} + target="_blank" + rel="noreferrer noopener" + /> + } + > + <FileText className="size-4" /> + View as Markdown + </DropdownMenuItem> + </DropdownMenuContent> + </DropdownMenu> + )} + </div> + ); +} diff --git a/apps/docs/components/docs/layout/docs-root-layout.tsx b/apps/docs/components/docs/layout/docs-root-layout.tsx new file mode 100644 index 0000000..4f86c35 --- /dev/null +++ b/apps/docs/components/docs/layout/docs-root-layout.tsx @@ -0,0 +1,76 @@ +import { DocsLayout } from "fumadocs-ui/layouts/docs"; +import type * as PageTree from "fumadocs-core/page-tree"; +import type { ReactNode } from "react"; +import { sharedDocsOptions } from "@/lib/layout.shared"; +import { DocsHeader } from "@/components/docs/layout/docs-header"; +import { + DocsSidebarProvider, + DocsSidebar, +} from "@/components/docs/contexts/sidebar"; +import { SidebarContent } from "@/components/docs/layout/sidebar-content"; +import { AssistantPanelProvider } from "@/components/docs/assistant/context"; +import { + DocsContent, + DocsAssistantPanel, +} from "@/components/docs/layout/docs-layout"; +import { DocsRuntimeProvider } from "@/contexts/DocsRuntimeProvider"; +import { DocsAssistantRuntimeProvider } from "@/contexts/AssistantRuntimeProvider"; +import { CurrentPageProvider } from "@/components/docs/contexts/current-page"; +import { PlatformProvider } from "@/components/docs/platform/context"; +import { FloatingComposer } from "@/components/docs/assistant/floating-composer"; + +type DocsRootLayoutProps = { + tree: PageTree.Root; + section: string; + sectionHref: string; + showMobileSectionBreadcrumb?: boolean; + /** Set false for sections that don't share the main docs' React / RN / Ink platform tree. */ + platformAware?: boolean; + children: ReactNode; +}; + +export function DocsRootLayout({ + tree, + section, + sectionHref, + showMobileSectionBreadcrumb = false, + platformAware = true, + children, +}: DocsRootLayoutProps) { + return ( + <CurrentPageProvider> + <AssistantPanelProvider> + <DocsRuntimeProvider> + <PlatformProvider> + <DocsSidebarProvider> + <DocsHeader + section={section} + sectionHref={sectionHref} + mobileSectionTree={ + showMobileSectionBreadcrumb ? tree : undefined + } + /> + <DocsContent> + <DocsLayout + {...sharedDocsOptions} + tree={tree} + nav={{ enabled: false }} + sidebar={{ enabled: false }} + > + {children} + </DocsLayout> + </DocsContent> + <DocsSidebar> + <SidebarContent tree={tree} platformAware={platformAware} /> + </DocsSidebar> + </DocsSidebarProvider> + </PlatformProvider> + </DocsRuntimeProvider> + <DocsAssistantRuntimeProvider> + <DocsAssistantPanel /> + <FloatingComposer /> + </DocsAssistantRuntimeProvider> + </AssistantPanelProvider> + </CurrentPageProvider> + ); +} diff --git a/apps/docs/components/docs/layout/examples-shell.tsx b/apps/docs/components/docs/layout/examples-shell.tsx new file mode 100644 index 0000000..500b7cf --- /dev/null +++ b/apps/docs/components/docs/layout/examples-shell.tsx @@ -0,0 +1,23 @@ +import type { ReactNode } from "react"; +import type * as PageTree from "fumadocs-core/page-tree"; +import { DocsLayout } from "fumadocs-ui/layouts/docs"; +import { sharedDocsOptions } from "@/lib/layout.shared"; + +export function ExamplesShell({ + tree, + children, +}: { + tree: PageTree.Root; + children: ReactNode; +}) { + return ( + <DocsLayout + {...sharedDocsOptions} + tree={tree} + nav={{ enabled: false }} + sidebar={{ enabled: false }} + > + {children} + </DocsLayout> + ); +} diff --git a/apps/docs/components/docs/layout/sidebar-content.tsx b/apps/docs/components/docs/layout/sidebar-content.tsx new file mode 100644 index 0000000..3901edf --- /dev/null +++ b/apps/docs/components/docs/layout/sidebar-content.tsx @@ -0,0 +1,323 @@ +"use client"; + +import { useEffect, useMemo, useRef, useState } from "react"; +import Link from "next/link"; +import { usePathname } from "next/navigation"; +import type * as PageTree from "fumadocs-core/page-tree"; +import { ChevronDown } from "lucide-react"; +import { AnimatePresence, motion } from "motion/react"; +import { cn } from "@/lib/utils"; +import { useDocsSidebar } from "@/components/docs/contexts/sidebar"; +import { + isPlatform, + usePlatform, + type Platform, +} from "@/components/docs/platform/context"; +import { GitHubIcon } from "@/components/icons/github"; +import { DiscordIcon } from "@/components/icons/discord"; +import { analytics } from "@/lib/analytics"; +import { PlatformSwitcher } from "@/components/docs/platform/switcher"; +import { + buildPlatformSections, + findPathToNode, + isNodeVisible, + nodePlatforms, +} from "@/components/docs/platform/tree"; + +function SectionItem({ + item, + onNavigate, + platform, + depth = 0, +}: { + item: PageTree.Node; + onNavigate: () => void; + platform: Platform; + depth?: number; +}) { + const pathname = usePathname(); + + if (!isNodeVisible(item, platform)) return null; + + if (item.type === "separator") { + return ( + <p className="text-muted-foreground/60 mt-4 mb-1 px-2 text-[10px] font-medium tracking-wider uppercase first:mt-1"> + {item.name} + </p> + ); + } + + if (item.type === "folder") { + const isActive = item.index && pathname === item.index.url; + const containsActive = findPathToNode(item, pathname) !== null; + const hasChildren = item.children.length > 0; + + return ( + <div> + {item.index ? ( + <Link + href={item.index.url} + onClick={() => { + analytics.docs.navigationClicked( + String(item.name), + item.index!.url, + depth, + ); + onNavigate(); + }} + data-active={isActive ? "true" : "false"} + className={cn( + "flex items-center gap-2 rounded-md px-2 py-1.5 text-[13px] transition-colors duration-150", + isActive + ? "bg-accent/20 text-foreground dark:bg-accent/50 font-medium" + : "text-muted-foreground hover:bg-accent/30 hover:text-foreground/90 dark:hover:bg-accent/40", + )} + > + {item.icon} + <span className="truncate">{item.name}</span> + </Link> + ) : ( + <p className="text-muted-foreground/70 mt-3 mb-1 flex items-center gap-2 px-2 text-[11px] font-medium tracking-wider uppercase first:mt-1"> + {item.icon} + {item.name} + </p> + )} + {hasChildren && (containsActive || !item.index) && ( + <div className="border-border/50 ml-2 flex flex-col gap-0.5 border-l pl-2"> + {item.children.map((child) => ( + <SectionItem + key={child.$id} + item={child} + onNavigate={onNavigate} + platform={platform} + depth={depth + 1} + /> + ))} + </div> + )} + </div> + ); + } + + const isActive = pathname === item.url; + return ( + <Link + href={item.url} + onClick={() => { + analytics.docs.navigationClicked(String(item.name), item.url, depth); + onNavigate(); + }} + data-active={isActive ? "true" : "false"} + className={cn( + "flex items-center gap-2 rounded-md px-2 py-1.5 text-[13px] transition-colors duration-150", + isActive + ? "bg-accent/20 text-foreground dark:bg-accent/50 font-medium" + : "text-muted-foreground hover:bg-accent/30 hover:text-foreground/90 dark:hover:bg-accent/40", + )} + > + {item.icon} + <span className="truncate">{item.name}</span> + </Link> + ); +} + +function SidebarSection({ + folder, + isOpen, + onToggle, + onNavigate, + platform, +}: { + folder: PageTree.Folder; + isOpen: boolean; + onToggle: () => void; + onNavigate: () => void; + platform: Platform; +}) { + const pathname = usePathname(); + const isActive = folder.index && pathname === folder.index.url; + + return ( + <div> + <button + type="button" + onClick={onToggle} + aria-expanded={isOpen} + className={cn( + "hover:bg-accent/30 dark:hover:bg-accent/40 flex w-full items-center gap-2 rounded-md px-2 py-2 text-left text-[13px] transition-colors duration-150", + isActive || isOpen + ? "text-foreground font-medium" + : "text-muted-foreground/90", + )} + > + <span className="text-muted-foreground flex size-4 shrink-0 items-center justify-center"> + {folder.icon} + </span> + <span className="flex-1 truncate">{folder.name}</span> + <ChevronDown + className={cn( + "text-muted-foreground/60 size-3.5 shrink-0 transition-transform duration-200", + !isOpen && "-rotate-90", + )} + /> + </button> + <AnimatePresence initial={false}> + {isOpen && ( + <motion.div + initial={{ height: 0, opacity: 0 }} + animate={{ height: "auto", opacity: 1 }} + exit={{ height: 0, opacity: 0 }} + transition={{ duration: 0.22, ease: [0.16, 1, 0.3, 1] }} + className="overflow-hidden" + > + <div className="border-border/50 mt-0.5 mb-1 ml-3 flex flex-col gap-0.5 border-l pl-2"> + {folder.children.map((child) => ( + <SectionItem + key={child.$id} + item={child} + onNavigate={onNavigate} + platform={platform} + /> + ))} + </div> + </motion.div> + )} + </AnimatePresence> + </div> + ); +} + +export function SidebarContent({ + tree, + platformAware = true, +}: { + tree?: PageTree.Root; + platformAware?: boolean; +}) { + const { setOpen: setSidebarOpen } = useDocsSidebar(); + const pathname = usePathname(); + const { platform, setPlatform } = usePlatform(); + const navRef = useRef<HTMLElement>(null); + + const platformRef = useRef(platform); + platformRef.current = platform; + + const allFolders = useMemo( + () => + (tree?.children ?? []).filter( + (n): n is PageTree.Folder => n.type === "folder", + ), + [tree], + ); + + const sections = useMemo( + () => + platformAware ? buildPlatformSections(allFolders, platform) : allFolders, + [allFolders, platform, platformAware], + ); + + const activePath = useMemo(() => { + for (const folder of allFolders) { + const path = findPathToNode(folder, pathname); + if (path) return path; + } + return null; + }, [allFolders, pathname]); + + const activeNodePlatforms = useMemo(() => { + if (!activePath) return undefined; + + for (let i = activePath.length - 1; i >= 0; i--) { + const platforms = nodePlatforms(activePath[i]!); + if (platforms !== undefined && platforms.length > 0) return platforms; + } + + return undefined; + }, [activePath]); + + useEffect(() => { + if (!activeNodePlatforms || activeNodePlatforms.length === 0) return; + if (activeNodePlatforms.includes(platformRef.current)) return; + + const next = activeNodePlatforms.find(isPlatform); + if (next) setPlatform(next); + }, [activeNodePlatforms, setPlatform]); + + const activeSectionId = useMemo(() => { + const activeIds = new Set(activePath?.map((node) => node.$id)); + const match = sections.find((section) => activeIds.has(section.$id)); + return match?.$id ?? sections[0]?.$id ?? null; + }, [sections, activePath]); + + const [openSectionId, setOpenSectionId] = useState<string | null>( + activeSectionId, + ); + + useEffect(() => { + if (activeSectionId) setOpenSectionId(activeSectionId); + }, [activeSectionId, pathname]); + + useEffect(() => { + if (openSectionId !== activeSectionId) return; + const timer = setTimeout(() => { + const nav = navRef.current; + if (!nav) return; + const active = nav.querySelector<HTMLElement>("[data-active='true']"); + if (!active) return; + const navRect = nav.getBoundingClientRect(); + const elRect = active.getBoundingClientRect(); + if (elRect.top < navRect.top || elRect.bottom > navRect.bottom) { + active.scrollIntoView({ block: "center", behavior: "smooth" }); + } + }, 260); + return () => clearTimeout(timer); + }, [pathname, openSectionId, activeSectionId]); + + const onNavigate = () => setSidebarOpen(false); + + return ( + <div className="flex h-full flex-col"> + <nav + ref={navRef} + className="sidebar-tree-content flex min-h-0 flex-1 flex-col gap-0.5 overflow-y-auto px-3 pt-4 pb-4" + > + {platformAware && <PlatformSwitcher tree={tree} />} + {sections.map((section) => ( + <SidebarSection + key={section.$id} + folder={section} + isOpen={openSectionId === section.$id} + onToggle={() => { + const id = section.$id ?? null; + const willOpen = openSectionId !== id; + analytics.docs.folderToggled(String(section.name), willOpen, 0); + setOpenSectionId(willOpen ? id : null); + }} + onNavigate={onNavigate} + platform={platform} + /> + ))} + </nav> + <div className="border-border/50 flex shrink-0 items-center gap-1 border-t px-3 py-2"> + <a + href="https://github.com/assistant-ui/assistant-ui" + target="_blank" + rel="noopener noreferrer" + className="text-muted-foreground hover:bg-accent/30 hover:text-foreground dark:hover:bg-accent/40 flex size-8 items-center justify-center rounded-md transition-colors" + aria-label="GitHub" + > + <GitHubIcon className="size-4" /> + </a> + <a + href="https://discord.gg/S9dwgCNEFs" + target="_blank" + rel="noopener noreferrer" + className="text-muted-foreground hover:bg-accent/30 hover:text-foreground dark:hover:bg-accent/40 flex size-8 items-center justify-center rounded-md transition-colors" + aria-label="Discord" + > + <DiscordIcon className="size-4" /> + </a> + </div> + </div> + ); +} diff --git a/apps/docs/components/docs/layout/sidebar-search.tsx b/apps/docs/components/docs/layout/sidebar-search.tsx new file mode 100644 index 0000000..5acdd6e --- /dev/null +++ b/apps/docs/components/docs/layout/sidebar-search.tsx @@ -0,0 +1,30 @@ +"use client"; + +import { Search } from "lucide-react"; +import { useSearchContext } from "fumadocs-ui/contexts/search"; + +export function SidebarSearch() { + const { setOpenSearch, hotKey } = useSearchContext(); + + return ( + <button + type="button" + data-sidebar-control + onClick={() => setOpenSearch(true)} + className="bg-muted text-muted-foreground hover:bg-accent hover:text-foreground flex h-9 w-full items-center gap-2 rounded-lg px-3 text-sm transition-colors" + > + <Search className="size-4 shrink-0" /> + <span className="flex-1 text-left">Search...</span> + <div className="flex gap-0.5"> + {hotKey.map((k, i) => ( + <kbd + key={i} + className="bg-background text-muted-foreground rounded px-1.5 py-0.5 text-[10px] font-medium" + > + {k.display} + </kbd> + ))} + </div> + </button> + ); +} diff --git a/apps/docs/components/docs/layout/table-of-contents.tsx b/apps/docs/components/docs/layout/table-of-contents.tsx new file mode 100644 index 0000000..9729fe4 --- /dev/null +++ b/apps/docs/components/docs/layout/table-of-contents.tsx @@ -0,0 +1,210 @@ +"use client"; + +import { useEffect, useRef, useState, type ReactNode } from "react"; +import { cn } from "@/lib/utils"; +import { Copy, EditIcon, FileText, SparklesIcon } from "lucide-react"; +import { TOCHiringBanner } from "@/components/docs/layout/toc-hiring-banner"; +import { BASE_URL } from "@/lib/constants"; +import { useMarkdownCopy } from "@/hooks/use-markdown-copy"; +import { useAssistantPanel } from "@/components/docs/assistant/context"; +import { useCurrentPage } from "@/components/docs/contexts/current-page"; +import { analytics } from "@/lib/analytics"; + +type TOCItem = { + title: ReactNode; + url: string; + depth: number; +}; + +type TableOfContentsProps = { + items: TOCItem[]; + githubEditUrl?: string; + markdownUrl?: string; +}; + +function TOCActions({ + markdownUrl, + githubEditUrl, +}: { + markdownUrl: string | undefined; + githubEditUrl: string | undefined; +}) { + const { copy, prefetch, isLoading } = useMarkdownCopy(markdownUrl); + const { askAI } = useAssistantPanel(); + const currentPage = useCurrentPage(); + + // Prefetch on mount since TOC is always visible on desktop + useEffect(() => { + prefetch(); + }, [prefetch]); + + const linkClass = + "inline-flex items-center gap-1.5 text-muted-foreground text-xs transition-colors hover:text-foreground disabled:opacity-50"; + + const handleAskAI = () => { + const page = currentPage?.pathname ?? "this page"; + askAI(`Explain ${page}`); + }; + + const handleCopy = () => { + analytics.toc.actionClicked("copy"); + copy(); + }; + + const handleMarkdownClick = () => { + analytics.toc.actionClicked("markdown"); + }; + + const handleGitHubClick = () => { + analytics.toc.actionClicked("github"); + }; + + const handleAskAIClick = () => { + analytics.toc.actionClicked("ask_ai"); + handleAskAI(); + }; + + return ( + <div className="flex flex-col gap-3"> + {markdownUrl && ( + <> + <button + type="button" + onClick={handleCopy} + disabled={isLoading} + className={linkClass} + > + <Copy className="size-3" /> + {isLoading ? "Loading..." : "Copy page"} + </button> + <a + href={`${BASE_URL}${markdownUrl}`} + target="_blank" + rel="noreferrer noopener" + className={linkClass} + onClick={handleMarkdownClick} + > + <FileText className="size-3" /> + View as Markdown + </a> + </> + )} + {githubEditUrl && ( + <a + href={githubEditUrl} + target="_blank" + rel="noreferrer noopener" + className={linkClass} + onClick={handleGitHubClick} + > + <EditIcon className="size-3" /> + Edit on GitHub + </a> + )} + <button type="button" onClick={handleAskAIClick} className={linkClass}> + <SparklesIcon className="size-3" /> + Ask AI + </button> + </div> + ); +} + +export function TableOfContents({ + items, + githubEditUrl, + markdownUrl, +}: TableOfContentsProps) { + const [activeId, setActiveId] = useState<string | null>(null); + const listRef = useRef<HTMLUListElement>(null); + + useEffect(() => { + if (items.length === 0) return; + + const headingIds = items.map((item) => item.url.slice(1)); + + const observer = new IntersectionObserver( + (entries) => { + for (const entry of entries) { + if (entry.isIntersecting) { + setActiveId(entry.target.id); + break; + } + } + }, + { + rootMargin: "-80px 0px -70% 0px", + threshold: 0, + }, + ); + + for (const id of headingIds) { + const element = document.getElementById(id); + if (element) { + observer.observe(element); + } + } + + return () => observer.disconnect(); + }, [items]); + + useEffect(() => { + if (!activeId || !listRef.current) return; + + const activeElement = listRef.current.querySelector( + `[data-toc-id="${activeId}"]`, + ); + if (activeElement) { + activeElement.scrollIntoView({ + behavior: "smooth", + block: "nearest", + }); + } + }, [activeId]); + + if (items.length === 0) return null; + + return ( + <div id="nd-toc" className="w-56 [grid-area:toc] max-xl:hidden"> + <div className="sticky top-14 flex max-h-[calc(100vh-3.5rem)] flex-col pe-4 pb-2"> + <p className="text-muted-foreground/70 mb-3 shrink-0 text-xs"> + On this page + </p> + <ul + ref={listRef} + className="flex min-h-0 flex-1 [scrollbar-width:none] flex-col gap-1 overflow-x-hidden overflow-y-auto [&::-webkit-scrollbar]:hidden" + > + {items.map((item) => { + const id = item.url.slice(1); + const isActive = activeId === id; + const indent = Math.max(0, item.depth - 2) * 12; + const titleText = typeof item.title === "string" ? item.title : id; + + return ( + <li key={item.url} data-toc-id={id}> + <a + href={item.url} + style={{ paddingLeft: indent || undefined }} + className={cn( + "block py-1 text-[13px] leading-snug wrap-break-word transition-colors", + isActive + ? "text-foreground font-medium" + : "text-muted-foreground hover:text-foreground", + )} + onClick={() => + analytics.toc.linkClicked(titleText, item.depth) + } + > + {item.title} + </a> + </li> + ); + })} + </ul> + <div className="mt-6 shrink-0 space-y-4"> + <TOCActions markdownUrl={markdownUrl} githubEditUrl={githubEditUrl} /> + <TOCHiringBanner /> + </div> + </div> + </div> + ); +} diff --git a/apps/docs/components/docs/layout/toc-hiring-banner.tsx b/apps/docs/components/docs/layout/toc-hiring-banner.tsx new file mode 100644 index 0000000..2f622d7 --- /dev/null +++ b/apps/docs/components/docs/layout/toc-hiring-banner.tsx @@ -0,0 +1,40 @@ +"use client"; + +import Link from "next/link"; +import { usePersistentBoolean } from "@/hooks/use-persistent-boolean"; +import { X } from "lucide-react"; + +export const TOCHiringBanner = () => { + const [dismissed, setDismissed] = usePersistentBoolean( + "toc-hiring-banner-dismissed", + ); + + if (dismissed) return null; + + return ( + <div className="group relative"> + <Link + href="/careers" + className="border-border/60 bg-muted/30 hover:border-border hover:bg-muted/50 block rounded-xl border px-3.5 py-3 transition-colors" + > + <p className="shimmer text-foreground/80 text-[11px] font-medium tracking-wide uppercase"> + We are hiring + </p> + <p className="text-muted-foreground mt-1.5 text-xs leading-relaxed"> + Build the future of agentic UI with us → + </p> + </Link> + <button + type="button" + aria-label="Dismiss" + onClick={(e) => { + e.preventDefault(); + setDismissed(true); + }} + className="border-border/60 bg-background text-muted-foreground hover:text-foreground absolute -top-1.5 -right-1.5 flex size-5 items-center justify-center rounded-full border opacity-0 transition-all group-hover:opacity-100" + > + <X className="size-3" /> + </button> + </div> + ); +}; diff --git a/apps/docs/components/docs/mermaid-diagram.tsx b/apps/docs/components/docs/mermaid-diagram.tsx new file mode 100644 index 0000000..f50bac0 --- /dev/null +++ b/apps/docs/components/docs/mermaid-diagram.tsx @@ -0,0 +1,38 @@ +import { renderMermaidSVG } from "beautiful-mermaid"; + +type MermaidDiagramProps = { + children?: string; +}; + +export function MermaidDiagram({ children = "" }: MermaidDiagramProps) { + try { + const svg = renderMermaidSVG(children, { + bg: "var(--background)", + fg: "var(--foreground)", + muted: "var(--muted-foreground)", + border: "var(--border)", + accent: "var(--foreground)", + transparent: true, + }); + + return ( + <figure + className="my-8 flex justify-center [&_svg]:h-auto [&_svg]:max-w-full" + dangerouslySetInnerHTML={{ __html: svg }} + /> + ); + } catch { + return ( + <figure className="my-8"> + <pre className="bg-muted/75 overflow-x-auto rounded-lg p-4 text-sm"> + {children.trim()} + </pre> + <p className="text-muted-foreground border-border border-t px-4 py-1.5 text-xs"> + diagram could not be rendered + </p> + </figure> + ); + } +} + +export default MermaidDiagram; diff --git a/apps/docs/components/docs/parameters-table.context.ts b/apps/docs/components/docs/parameters-table.context.ts new file mode 100644 index 0000000..fd46652 --- /dev/null +++ b/apps/docs/components/docs/parameters-table.context.ts @@ -0,0 +1,337 @@ +import type { ParametersTableProps } from "@/components/docs/parameters-table"; + +const BaseComposerState: ParametersTableProps = { + type: "BaseComposerState", + parameters: [ + { + name: "text", + type: "string", + required: true, + description: "The current text of the composer.", + }, + { + name: "setText", + type: "(text: string) => void", + required: true, + description: "A function to set the text of the composer.", + }, + { + name: "attachments", + type: "readonly Attachment[]", + required: true, + description: "The current attachments of the composer.", + }, + { + name: "addAttachment", + type: "(file: File | CreateAttachment) => Promise<void>", + required: true, + description: + "A function to add an attachment to the composer. Accepts a File (processed through the AttachmentAdapter) or a CreateAttachment descriptor for external-source attachments.", + }, + { + name: "removeAttachment", + type: "(attachmentId: string) => void", + required: true, + description: "A function to remove an attachment from the composer.", + }, + { + name: "reset", + type: "() => void", + required: true, + description: "A function to reset the composer.", + }, + ], +}; + +export const EditComposerState: ParametersTableProps = { + type: "EditComposerState", + parameters: [ + ...BaseComposerState.parameters, + { + name: "canCancel", + type: "boolean", + required: true, + description: "Whether the composer can be canceled.", + }, + { + name: "isEditing", + type: "boolean", + required: true, + description: "Whether the composer is in edit mode.", + }, + { + name: "edit", + type: "() => void", + required: true, + description: "A function to enter edit mode.", + }, + { + name: "send", + type: "() => void", + required: true, + description: "A function to send the message.", + }, + { + name: "cancel", + type: "() => void", + required: true, + description: "A function to exit the edit mode.", + }, + ], +}; + +export const ThreadViewportState: ParametersTableProps = { + type: "ThreadViewportState", + parameters: [ + { + name: "isAtBottom", + type: "boolean", + required: true, + description: "Whether the thread is at the bottom.", + }, + { + name: "scrollToBottom", + type: "(config?: { behavior?: ScrollBehavior }) => void", + required: true, + description: "A function to scroll to the bottom.", + }, + { + name: "onScrollToBottom", + type: "(callback: ({ behavior }: { behavior: ScrollBehavior }) => void) => Unsubscribe", + required: true, + description: "A function to subscribe to scroll to bottom events.", + }, + { + name: "turnAnchor", + type: '"top" | "bottom"', + required: true, + description: + 'Controls scroll anchoring: "top" anchors user messages at top, "bottom" is classic behavior.', + }, + { + name: "topAnchorMessageClamp", + type: "{ tallerThan: string; visibleHeight: string }", + required: true, + description: + "CSS-length clamp configuration for tall user messages when top anchoring is enabled.", + children: [ + { + type: "{ tallerThan: string; visibleHeight: string }", + parameters: [ + { + name: "tallerThan", + type: "string", + required: true, + description: "Clamp messages taller than this CSS length.", + }, + { + name: "visibleHeight", + type: "string", + required: true, + description: + "Visible portion of a clamped message's bottom edge.", + }, + ], + }, + ], + }, + { + name: "height", + type: "{ viewport: number; inset: number }", + required: true, + description: + "Raw height values from registered elements: total viewport height and total content inset height.", + }, + { + name: "element", + type: "{ viewport: HTMLElement | null; anchor: HTMLElement | null; target: HTMLElement | null }", + required: true, + description: + "Current DOM elements used for geometry-based top anchoring.", + }, + { + name: "targetConfig", + type: "{ tallerThan: number; visibleHeight: number } | null", + required: true, + description: + "Numeric clamp configuration for the active top-anchor target message.", + }, + { + name: "registerViewport", + type: "() => SizeHandle", + required: true, + description: "Register a viewport and get a handle to update its height.", + }, + { + name: "registerContentInset", + type: "() => SizeHandle", + required: true, + description: + "Register a content inset (footer, anchor message, etc.) and get a handle to update its height.", + }, + { + name: "registerViewportElement", + type: "(element: HTMLElement | null) => Unsubscribe", + required: true, + description: "Register the scroll viewport element.", + }, + { + name: "registerAnchorElement", + type: "(element: HTMLElement | null) => Unsubscribe", + required: true, + description: "Register the current anchor user message element.", + }, + { + name: "registerAnchorTargetElement", + type: "(element: HTMLElement | null, config?: { tallerThan: number; visibleHeight: number }) => Unsubscribe", + required: true, + description: + "Register the current top-anchor target element with its numeric clamp configuration.", + }, + ], +}; + +export const MessagePartState: ParametersTableProps = { + type: "MessagePartState", + parameters: [ + { + name: "part", + type: "Readonly<MessagePartState>", + required: true, + description: "The current message part.", + }, + { + name: "status", + type: "MessageStatus", + required: true, + description: "The current message part status.", + children: [ + { + type: "MessageStatus", + parameters: [ + { + name: "type", + type: "'running' | 'requires-action' | 'complete' | 'incomplete'", + required: true, + description: "The status.", + }, + { + name: "finish-reason", + type: "'stop' | 'cancelled' | 'length' | 'content-filter' | 'tool-calls' | 'other' | 'unknown'", + required: false, + description: "The finish reason if the status is 'incomplete'.", + }, + { + name: "error", + type: "unknown", + required: false, + description: "The error object if the status is 'error'.", + }, + ], + }, + ], + }, + ], +}; + +export const MessageState: ParametersTableProps = { + type: "MessageState", + parameters: [ + { + name: "message", + type: "Readonly<ThreadMessage>", + required: true, + description: "The current message.", + }, + { + name: "parentId", + type: "string | null", + required: true, + description: "The parent message id.", + }, + { + name: "branches", + type: "readonly string[]", + required: true, + description: "The branches for the message.", + }, + { + name: "isLast", + type: "boolean", + required: true, + description: "Whether the message is the last in the thread.", + }, + ], +}; + +export const MessageUtilsState: ParametersTableProps = { + type: "MessageUtilsState", + parameters: [ + { + name: "isCopied", + type: "boolean", + required: true, + description: "Whether the message is copied.", + }, + { + name: "setIsCopied", + type: "(value: boolean) => void", + required: true, + description: "A function to set the is copied.", + }, + { + name: "isHovering", + type: "boolean", + required: true, + description: "Whether the message is being hovered.", + }, + { + name: "setIsHovering", + type: "(value: boolean) => void", + required: true, + description: "A function to set the is hovering.", + }, + { + name: "isSpeaking", + type: "boolean", + required: true, + description: "Whether the message is currently being spoken.", + }, + { + name: "stopSpeaking", + type: "() => void", + required: true, + description: "A function to stop the message from being spoken.", + }, + { + name: "addUtterance", + type: "(utterance: SpeechSynthesisAdapter.Utterance) => void", + required: true, + description: "A function to add a speech utterance.", + }, + ], +}; + +export const ComposerAttachmentState: ParametersTableProps = { + type: "ComposerAttachmentState", + parameters: [ + { + name: "attachment", + type: "ComposerAttachment", + required: true, + description: "The current composer attachment.", + }, + ], +}; + +export const MessageAttachmentState: ParametersTableProps = { + type: "MessageAttachmentState", + parameters: [ + { + name: "attachment", + type: "MessageAttachment", + required: true, + description: "The current message attachment.", + }, + ], +}; diff --git a/apps/docs/components/docs/parameters-table.tsx b/apps/docs/components/docs/parameters-table.tsx new file mode 100644 index 0000000..caf5fef --- /dev/null +++ b/apps/docs/components/docs/parameters-table.tsx @@ -0,0 +1,290 @@ +import { cn } from "@/lib/utils"; +import Link from "next/link"; +import type { FC, ReactNode } from "react"; +import { StatusBadge } from "./status-badge"; + +const DESCRIPTION_LINK_CLASSNAME = + "font-medium text-foreground underline underline-offset-2"; + +type ParameterDef = { + name: string; + type?: string; + description: string | ReactNode; + required?: boolean; + default?: string; + deprecated?: string; + children?: Array<ParametersTableProps>; +}; + +const COMMON_PARAMS: Record<string, ParameterDef> = { + asChild: { + name: "asChild", + type: "boolean", + default: "false", + description: ( + <> + Change the default rendered element for the one passed as a child, + merging their props and behavior. + <br /> + <br /> + Read the{" "} + <Link + className={DESCRIPTION_LINK_CLASSNAME} + href="/docs/api-reference/primitives/composition" + > + Composition + </Link>{" "} + guide for more details. + </> + ), + }, +}; + +const MARKDOWN_LINK_REGEX = /\[([^\]]+)\]\(([^)\s]+)\)/g; + +function renderLinkLabel(label: string): ReactNode { + if (label.startsWith("`") && label.endsWith("`")) { + return <code>{label.slice(1, -1)}</code>; + } + return label; +} + +export function renderDescription(description: string | ReactNode): ReactNode { + if (typeof description !== "string") return description; + + const parts: ReactNode[] = []; + let lastIndex = 0; + for (const match of description.matchAll(MARKDOWN_LINK_REGEX)) { + const fullMatch = match[0]!; + const label = match[1]!; + const linkHref = match[2]!; + const index = match.index ?? 0; + if (index > lastIndex) { + parts.push(description.slice(lastIndex, index)); + } + + const children = renderLinkLabel(label); + parts.push( + linkHref.startsWith("/") ? ( + <Link + key={`${linkHref}-${index}`} + className={DESCRIPTION_LINK_CLASSNAME} + href={linkHref} + > + {children} + </Link> + ) : ( + <a + key={`${linkHref}-${index}`} + className={DESCRIPTION_LINK_CLASSNAME} + href={linkHref} + > + {children} + </a> + ), + ); + lastIndex = index + fullMatch.length; + } + + if (lastIndex === 0) return description; + if (lastIndex < description.length) parts.push(description.slice(lastIndex)); + return parts; +} + +const Parameter: FC<{ + parameter: ParameterDef; + isNested?: boolean | undefined; +}> = ({ parameter: partialParameter, isNested }) => { + const parameter = { + ...COMMON_PARAMS[partialParameter.name], + ...partialParameter, + }; + + const isOptional = !parameter.required && !parameter.default; + + return ( + <div + className={cn( + "group border-border/50 border-b px-4 py-3 last:border-b-0", + isNested && "bg-muted/30", + )} + > + <dt> + <div className="flex flex-wrap items-baseline gap-x-2 gap-y-1"> + <code className="text-foreground font-mono text-sm font-semibold"> + {parameter.name} + </code> + {parameter.deprecated && <StatusBadge variant="deprecated" />} + {parameter.name.startsWith("unstable_") && ( + <StatusBadge variant="unstable" /> + )} + {parameter.type && ( + <> + {" "} + <code className="text-muted-foreground font-mono text-xs"> + {isOptional && "?"} + {": "} + {parameter.type} + </code> + </> + )} + {parameter.default && ( + <> + {" "} + <span className="text-muted-foreground font-mono text-xs"> + = {parameter.default} + </span> + </> + )} + </div> + </dt> + <dd className="pt-2"> + <p className="text-muted-foreground text-sm leading-relaxed whitespace-pre-line"> + {renderDescription(parameter.description)} + </p> + + {parameter.deprecated && ( + <p className="mt-2 text-xs text-amber-600 dark:text-amber-400"> + Deprecated: {parameter.deprecated} + </p> + )} + + {parameter.children?.map((child, i) => ( + <div key={child.type ?? i} className="mt-3"> + <ParametersBox {...child} isNested /> + </div> + ))} + </dd> + </div> + ); +}; + +const ParametersBox: FC< + ParametersTableProps & { isNested?: boolean | undefined } +> = ({ type, parameters, isNested }) => { + return ( + <div + className={cn( + "border-border/60 overflow-hidden rounded-lg border", + isNested && "border-border/40", + )} + > + {type && !isNested && ( + <div className="border-border/60 bg-muted/50 border-b px-4 py-2"> + <code className="text-muted-foreground font-mono text-xs font-medium"> + {type} + </code> + </div> + )} + <dl> + {parameters.map((parameter) => ( + <Parameter + key={parameter.name} + parameter={parameter} + isNested={isNested} + /> + ))} + </dl> + </div> + ); +}; + +export type ParametersTableProps = { + type?: string | undefined; + parameters: Array<ParameterDef>; +}; + +export const ParametersTable: FC<ParametersTableProps> = ({ + type, + parameters, +}) => { + return ( + <div className="not-prose my-4"> + <ParametersBox type={type} parameters={parameters} /> + </div> + ); +}; + +// Shared bullet-list renderer for both ParametersTable and PrimitivesTypeTable +// in LLM/markdown output. `normalizeType` is the only behavioral difference +// between the two: PrimitivesTypeTable strips a trailing `| undefined`. +export type DefLLM = { + name: string; + type?: string; + description?: string | ReactNode; + required?: boolean; + default?: string; + deprecated?: string; + children?: Array<{ parameters: DefLLM[] }>; +}; + +type DefListLLMProps = { + defs: DefLLM[]; + commonParams?: Record<string, Partial<DefLLM>> | undefined; + normalizeType?: ((type: string) => string) | undefined; +}; + +export const DefListLLM: FC<DefListLLMProps> = ({ + defs, + commonParams, + normalizeType, +}) => { + return ( + <ul> + {defs.map((def) => ( + <DefItemLLM + key={def.name} + def={def} + commonParams={commonParams} + normalizeType={normalizeType} + /> + ))} + </ul> + ); +}; + +const DefItemLLM: FC< + { def: DefLLM } & Pick<DefListLLMProps, "commonParams" | "normalizeType"> +> = ({ def: rawDef, commonParams, normalizeType }) => { + const def = { ...commonParams?.[rawDef.name], ...rawDef }; + const isOptional = !def.required && !def.default; + const type = def.type && normalizeType ? normalizeType(def.type) : def.type; + + return ( + <li> + <code> + {def.name} + {isOptional ? "?" : ""} + </code> + {type ? ( + <> + {": "} + <code>{type}</code> + </> + ) : null} + {def.default ? ( + <> + {" (default "} + <code>{def.default}</code> + {")"} + </> + ) : null} + {def.deprecated ? <> (deprecated: {def.deprecated})</> : null} + {def.description ? <> — {renderDescription(def.description)}</> : null} + {def.children?.map((child, i) => ( + <DefListLLM + key={i} + defs={child.parameters} + commonParams={commonParams} + normalizeType={normalizeType} + /> + ))} + </li> + ); +}; + +export const ParametersTableLLM: FC<ParametersTableProps> = ({ + parameters, +}) => { + return <DefListLLM defs={parameters} commonParams={COMMON_PARAMS} />; +}; diff --git a/apps/docs/components/docs/platform/context.tsx b/apps/docs/components/docs/platform/context.tsx new file mode 100644 index 0000000..ae81a13 --- /dev/null +++ b/apps/docs/components/docs/platform/context.tsx @@ -0,0 +1,248 @@ +"use client"; + +import { + createContext, + useCallback, + useContext, + useEffect, + useSyncExternalStore, + type ReactNode, +} from "react"; +import { usePathname } from "next/navigation"; +import { + DEFAULT_PLATFORM, + PLATFORM_LABELS, + PLATFORMS, + type Platform, +} from "@/lib/constants"; + +export { DEFAULT_PLATFORM, PLATFORM_LABELS, PLATFORMS, type Platform }; + +export const PLATFORM_DOC_BASE_PATHS: Record<Platform, string> = { + react: "/docs", + rn: "/docs/react-native", + ink: "/docs/ink", +}; + +const STORAGE_KEY = "assistant-ui::docs:platform"; +const URL_PARAM = "platform"; + +interface PlatformContextValue { + platform: Platform; + setPlatform: (p: Platform) => void; +} + +const PlatformContext = createContext<PlatformContextValue | null>(null); +const PlatformScopeContext = createContext<Platform | null>(null); + +export function usePlatform() { + const ctx = useContext(PlatformContext); + if (!ctx) { + throw new Error("usePlatform must be used within PlatformProvider"); + } + return ctx; +} + +export function usePlatformOrDefault(): Platform { + const scopedPlatform = useContext(PlatformScopeContext); + const globalPlatform = useContext(PlatformContext)?.platform; + return scopedPlatform ?? globalPlatform ?? DEFAULT_PLATFORM; +} + +export function PlatformScope({ + children, + platform, +}: { + children: ReactNode; + platform: Platform; +}) { + return ( + <PlatformScopeContext.Provider value={platform}> + {children} + </PlatformScopeContext.Provider> + ); +} + +export function isPlatform( + value: string | null | undefined, +): value is Platform { + return value != null && (PLATFORMS as readonly string[]).includes(value); +} + +const isBrowser = () => typeof window !== "undefined"; + +// Avoid useSearchParams so the docs layout stays statically renderable. +function readUrlPlatform(): Platform | null { + if (!isBrowser()) return null; + try { + const value = new URLSearchParams(window.location.search).get(URL_PARAM); + return isPlatform(value) ? value : null; + } catch { + return null; + } +} + +function readStoredPlatform(): Platform | null { + if (!isBrowser()) return null; + try { + const stored = window.localStorage.getItem(STORAGE_KEY); + return isPlatform(stored) ? stored : null; + } catch { + return null; + } +} + +// replaceState keeps platform selection shareable without adding noisy +// back/forward entries for every dropdown change. +function writeUrlPlatform(next: Platform): void { + if (!isBrowser()) return; + try { + const url = new URL(window.location.href); + if (next === DEFAULT_PLATFORM) { + if (!url.searchParams.has(URL_PARAM)) return; + url.searchParams.delete(URL_PARAM); + } else { + if (url.searchParams.get(URL_PARAM) === next) return; + url.searchParams.set(URL_PARAM, next); + } + window.history.replaceState(window.history.state, "", url.toString()); + } catch {} +} + +function platformDocPathSuffix( + pathname: string, + platform: Extract<Platform, "rn" | "ink">, +): string | null { + const basePath = PLATFORM_DOC_BASE_PATHS[platform]; + if (pathname === basePath) return ""; + if (!pathname.startsWith(`${basePath}/`)) return null; + + return pathname.slice(basePath.length); +} + +export function getPlatformSwitchHref( + pathname: string, + nextPlatform: Platform, +): string | null { + const currentPlatformSuffix = + platformDocPathSuffix(pathname, "rn") ?? + platformDocPathSuffix(pathname, "ink"); + + if (currentPlatformSuffix === null) { + if ( + pathname === PLATFORM_DOC_BASE_PATHS.react && + nextPlatform !== "react" + ) { + return PLATFORM_DOC_BASE_PATHS[nextPlatform]; + } + return null; + } + + if (nextPlatform === "react") return PLATFORM_DOC_BASE_PATHS.react; + + return `${PLATFORM_DOC_BASE_PATHS[nextPlatform]}${currentPlatformSuffix}`; +} + +// SSR-safe localStorage backing for useSyncExternalStore, with cross-tab sync +// via the storage event. +class PlatformStore { + private listeners = new Set<() => void>(); + private urlPlatform: Platform | null = readUrlPlatform(); + private storedPlatform: Platform | null = readStoredPlatform(); + + constructor() { + if (isBrowser()) { + window.addEventListener("storage", this.handleStorage); + } + } + + private refreshFromBrowser = () => { + this.urlPlatform = readUrlPlatform(); + this.storedPlatform = readStoredPlatform(); + }; + + private handleStorage = (e: StorageEvent) => { + if (e.storageArea !== window.localStorage) return; + if (e.key !== STORAGE_KEY) return; + this.storedPlatform = readStoredPlatform(); + this.notify(); + }; + + private notify = () => { + this.listeners.forEach((l) => { + l(); + }); + }; + + subscribe = (listener: () => void) => { + this.listeners.add(listener); + return () => { + this.listeners.delete(listener); + }; + }; + + getSnapshot = (): Platform => { + return this.urlPlatform ?? this.storedPlatform ?? DEFAULT_PLATFORM; + }; + + getServerSnapshot = (): Platform => DEFAULT_PLATFORM; + + setValue = (next: Platform) => { + if (!isBrowser()) return; + try { + window.localStorage.setItem(STORAGE_KEY, next); + this.storedPlatform = next; + } catch {} + writeUrlPlatform(next); + this.urlPlatform = next === DEFAULT_PLATFORM ? null : next; + this.notify(); + }; + + syncUrlAndStore = () => { + this.refreshFromBrowser(); + const next = this.getSnapshot(); + if (next === this.storedPlatform) return; + this.setValue(next); + }; +} + +const platformStore = new PlatformStore(); + +export function PlatformProvider({ children }: { children: ReactNode }) { + const pathname = usePathname(); + + const platform = useSyncExternalStore( + platformStore.subscribe, + platformStore.getSnapshot, + platformStore.getServerSnapshot, + ); + + useEffect(() => { + platformStore.syncUrlAndStore(); + }, [pathname]); + + useEffect(() => { + window.addEventListener("popstate", platformStore.syncUrlAndStore); + return () => { + window.removeEventListener("popstate", platformStore.syncUrlAndStore); + }; + }, []); + + const setPlatform = useCallback((next: Platform) => { + platformStore.setValue(next); + }, []); + + return ( + <PlatformContext.Provider value={{ platform, setPlatform }}> + {children} + </PlatformContext.Provider> + ); +} + +export function isVisibleForPlatform( + platforms: readonly string[] | undefined, + active: Platform, +): boolean { + if (!platforms || platforms.length === 0) return true; + return platforms.includes(active); +} diff --git a/apps/docs/components/docs/platform/mdx.llm.tsx b/apps/docs/components/docs/platform/mdx.llm.tsx new file mode 100644 index 0000000..c5db02f --- /dev/null +++ b/apps/docs/components/docs/platform/mdx.llm.tsx @@ -0,0 +1,65 @@ +import { Children, isValidElement, type ReactNode } from "react"; +import { + DEFAULT_PLATFORM, + PLATFORM_LABELS, + PLATFORMS, + type Platform, +} from "@/lib/constants"; +import type { PlatformTabsProps } from "./mdx"; + +// Emit only the React (default) tab — other platforms duplicate content and have +// their own doc trees (/docs/react-native, /docs/ink). Label it so the code isn't +// read as universal. +export function PlatformTabsLLM({ children }: PlatformTabsProps): ReactNode { + const tabs = Children.toArray(children).filter(isValidElement); + const reactTab = + tabs.find( + (child) => + (child.props as { value?: string }).value === + PLATFORM_LABELS[DEFAULT_PLATFORM], + ) ?? tabs[0]; + if (!reactTab) return null; + + return ( + <> + <p> + <strong>{PLATFORM_LABELS[DEFAULT_PLATFORM]}</strong> + </p> + {reactTab} + </> + ); +} + +export function PlatformOnlyLLM({ + children, + except, + platforms, +}: { + children: ReactNode; + except?: readonly Platform[]; + platforms?: readonly Platform[]; +}): ReactNode { + if (except?.includes(DEFAULT_PLATFORM)) return null; + if ( + platforms && + platforms.length > 0 && + !platforms.includes(DEFAULT_PLATFORM) + ) + return null; + + const applicable = PLATFORMS.filter( + (p) => + (!platforms || platforms.length === 0 || platforms.includes(p)) && + !except?.includes(p), + ); + const label = applicable.map((p) => PLATFORM_LABELS[p]).join(", "); + + return ( + <> + <p> + <strong>{`${label} only`}</strong> + </p> + {children} + </> + ); +} diff --git a/apps/docs/components/docs/platform/mdx.tsx b/apps/docs/components/docs/platform/mdx.tsx new file mode 100644 index 0000000..556f1e7 --- /dev/null +++ b/apps/docs/components/docs/platform/mdx.tsx @@ -0,0 +1,118 @@ +"use client"; + +import { + Children, + cloneElement, + isValidElement, + useCallback, + useMemo, + useState, + type ReactNode, +} from "react"; +import { + PlatformScope, + isVisibleForPlatform, + PLATFORM_LABELS, + PLATFORMS, + type Platform, + usePlatformOrDefault, +} from "./context"; +import { + Tabs, + escapeValue, + type TabsProps, +} from "@/components/docs/fumadocs/tabs"; + +const ITEMS = PLATFORMS.map((p) => PLATFORM_LABELS[p]); +const VALUE_TO_PLATFORM: Record<string, Platform> = Object.fromEntries( + PLATFORMS.map((p) => [escapeValue(PLATFORM_LABELS[p]), p]), +); + +export type PlatformTabsProps = Omit< + TabsProps, + "items" | "defaultIndex" | "value" | "onValueChange" +>; + +export function PlatformTabs(props: PlatformTabsProps): React.ReactElement { + const platform = usePlatformOrDefault(); + return ( + <PlatformTabsInner key={platform} defaultPlatform={platform} {...props} /> + ); +} + +// Local tab selection previews this group only, does not update +// global platform. Global overrides this on navigation (via key remount). +function PlatformTabsInner({ + defaultPlatform, + ...props +}: PlatformTabsProps & { defaultPlatform: Platform }): React.ReactElement { + const [localPlatform, setLocalPlatform] = useState(defaultPlatform); + + const handleValueChange = useCallback((value: string) => { + const next = VALUE_TO_PLATFORM[value]; + if (next) setLocalPlatform(next); + }, []); + + return ( + <PlatformScope platform={localPlatform}> + <Tabs + {...props} + items={ITEMS} + value={escapeValue(PLATFORM_LABELS[localPlatform])} + onValueChange={handleValueChange} + /> + </PlatformScope> + ); +} + +export function PlatformOnly({ + children, + except, + platforms, +}: { + children: ReactNode; + except?: readonly Platform[]; + platforms?: readonly Platform[]; +}) { + const platform = usePlatformOrDefault(); + + if (except?.includes(platform)) return null; + if (!isVisibleForPlatform(platforms, platform)) return null; + + return <>{children}</>; +} + +// Negative lookahead avoids matching siblings like `@assistant-ui/react-langgraph`. +const PACKAGE_PATTERN = /@assistant-ui\/react(?![-\w])/g; + +const PLATFORM_PACKAGE: Record<Platform, string> = { + react: "@assistant-ui/react", + rn: "@assistant-ui/react-native", + ink: "@assistant-ui/react-ink", +}; + +function rewrite(node: ReactNode, replacement: string): ReactNode { + if (typeof node === "string") { + return node.replace(PACKAGE_PATTERN, replacement); + } + if (Array.isArray(node)) { + // Children.map auto-keys the siblings; bare Array.map would warn on Shiki spans. + return Children.map(node, (child) => rewrite(child, replacement)); + } + if (isValidElement<{ children?: ReactNode }>(node)) { + const { children } = node.props; + if (children === undefined) return node; + return cloneElement(node, { children: rewrite(children, replacement) }); + } + return node; +} + +export function PlatformAwareCode({ children }: { children: ReactNode }) { + const platform = usePlatformOrDefault(); + const replacement = PLATFORM_PACKAGE[platform]; + const rewritten = useMemo( + () => (platform === "react" ? children : rewrite(children, replacement)), + [children, platform, replacement], + ); + return <>{rewritten}</>; +} diff --git a/apps/docs/components/docs/platform/switcher.tsx b/apps/docs/components/docs/platform/switcher.tsx new file mode 100644 index 0000000..446da36 --- /dev/null +++ b/apps/docs/components/docs/platform/switcher.tsx @@ -0,0 +1,145 @@ +"use client"; + +import { useMemo, useState } from "react"; +import { usePathname, useRouter } from "next/navigation"; +import type * as PageTree from "fumadocs-core/page-tree"; +import { + Popover, + PopoverContent, + PopoverTrigger, +} from "fumadocs-ui/components/ui/popover"; +import { + Check, + ChevronsUpDown, + Monitor, + Smartphone, + Terminal, +} from "lucide-react"; +import { + getPlatformSwitchHref, + PLATFORM_DOC_BASE_PATHS, + PLATFORM_LABELS, + PLATFORMS, + type Platform, + usePlatform, +} from "./context"; +import { cn } from "@/lib/utils"; +import { getVisibleUrlsByPlatform } from "./tree"; + +const PLATFORM_OPTIONS: Record< + Platform, + { + label: string; + description: string; + Icon: typeof Monitor; + } +> = { + react: { + label: PLATFORM_LABELS.react, + description: "For React web apps", + Icon: Monitor, + }, + rn: { + label: PLATFORM_LABELS.rn, + description: "For React Native apps", + Icon: Smartphone, + }, + ink: { + label: PLATFORM_LABELS.ink, + description: "For Ink CLI apps", + Icon: Terminal, + }, +}; + +function getVisiblePlatformSwitchHref( + visibleUrls: ReadonlySet<string>, + pathname: string, + nextPlatform: Platform, +): string { + const equivalentHref = getPlatformSwitchHref(pathname, nextPlatform); + if (equivalentHref && visibleUrls.has(equivalentHref)) { + return equivalentHref; + } + + if (visibleUrls.has(pathname)) { + return pathname; + } + + return PLATFORM_DOC_BASE_PATHS[nextPlatform]; +} + +export function PlatformSwitcher({ + tree, +}: { + tree?: PageTree.Root | undefined; +}) { + const [open, setOpen] = useState(false); + const { platform, setPlatform } = usePlatform(); + const pathname = usePathname(); + const router = useRouter(); + const selected = PLATFORM_OPTIONS[platform]; + const visibleUrlsByPlatform = useMemo( + () => getVisibleUrlsByPlatform(tree), + [tree], + ); + + return ( + <Popover open={open} onOpenChange={setOpen}> + <PopoverTrigger + className={cn( + "bg-fd-secondary/50 text-fd-secondary-foreground hover:bg-fd-accent data-[state=open]:bg-fd-accent data-[state=open]:text-fd-accent-foreground mb-3 flex w-full items-center gap-2 rounded-lg border p-2 text-start transition-colors", + )} + > + <div className="size-9 shrink-0 empty:hidden md:size-5"> + <selected.Icon className="text-muted-foreground size-4 translate-y-0.5" /> + </div> + <div> + <p className="text-sm font-medium">{selected.label}</p> + <p className="text-fd-muted-foreground text-sm empty:hidden md:hidden"> + {selected.description} + </p> + </div> + <ChevronsUpDown className="text-fd-muted-foreground ms-auto size-4 shrink-0" /> + </PopoverTrigger> + <PopoverContent className="fd-scroll-container flex w-(--radix-popover-trigger-width) flex-col gap-1 p-1"> + {PLATFORMS.map((p) => { + const item = PLATFORM_OPTIONS[p]; + const isActive = p === platform; + return ( + <button + key={p} + type="button" + onClick={() => { + const href = getVisiblePlatformSwitchHref( + visibleUrlsByPlatform[p], + pathname, + p, + ); + if (href !== pathname) router.replace(href); + setPlatform(p); + setOpen(false); + }} + className="hover:bg-fd-accent hover:text-fd-accent-foreground flex items-center gap-2 rounded-lg p-1.5 text-start" + > + <div className="size-9 shrink-0 empty:hidden md:mb-auto md:size-5"> + <item.Icon className="text-muted-foreground size-4 translate-y-0.5" /> + </div> + <div> + <p className="text-sm leading-none font-medium">{item.label}</p> + <p className="text-fd-muted-foreground mt-1 text-[0.8125rem] empty:hidden"> + {item.description} + </p> + </div> + <Check + className={cn( + "text-fd-primary ms-auto size-3.5 shrink-0", + !isActive && "invisible", + )} + /> + </button> + ); + })} + </PopoverContent> + </Popover> + ); +} diff --git a/apps/docs/components/docs/platform/tree.ts b/apps/docs/components/docs/platform/tree.ts new file mode 100644 index 0000000..a4155ab --- /dev/null +++ b/apps/docs/components/docs/platform/tree.ts @@ -0,0 +1,223 @@ +import type { ReactNode } from "react"; +import type * as PageTree from "fumadocs-core/page-tree"; +import { isVisibleForPlatform, PLATFORMS, type Platform } from "./context"; + +export interface PlatformTreeConfig { + mainDocsFolder: string; + platformFolders: Partial<Record<Platform, string>>; + sharedPageUrls: ReadonlySet<string>; +} + +export const PLATFORM_TREE_CONFIG: PlatformTreeConfig = { + mainDocsFolder: "Getting Started", + platformFolders: { rn: "React Native", ink: "React Ink" }, + sharedPageUrls: new Set(["/docs/llm", "/docs/architecture"]), +}; + +export function nodePlatforms( + node: PageTree.Node, +): readonly string[] | undefined { + return (node as unknown as { platforms?: readonly string[] }).platforms; +} + +export function isNodeVisible( + node: PageTree.Node, + platform: Platform, +): boolean { + return isVisibleForPlatform(nodePlatforms(node), platform); +} + +function collectVisibleUrls( + node: PageTree.Node, + platform: Platform, + urls: Set<string>, +): void { + if (!isNodeVisible(node, platform)) return; + + if (node.type === "page") { + urls.add(node.url); + return; + } + + if (node.type === "separator") return; + + if (node.index && isNodeVisible(node.index, platform)) { + urls.add(node.index.url); + } + + node.children.forEach((child) => { + collectVisibleUrls(child, platform, urls); + }); +} + +export function getVisibleUrlsByPlatform( + tree: PageTree.Root | undefined, +): Record<Platform, ReadonlySet<string>> { + const folders = (tree?.children ?? []).filter( + (node): node is PageTree.Folder => node.type === "folder", + ); + const result: Record<Platform, Set<string>> = { + react: new Set(), + rn: new Set(), + ink: new Set(), + }; + + PLATFORMS.forEach((platform) => { + buildPlatformSections(folders, platform).forEach((section) => { + collectVisibleUrls(section, platform, result[platform]); + }); + }); + + return result; +} + +export function findPathToNode( + node: PageTree.Node, + pathname: string, +): PageTree.Node[] | null { + if (node.type === "page") return pathname === node.url ? [node] : null; + if (node.type === "separator") return null; + if (node.index && pathname === node.index.url) return [node, node.index]; + + for (const child of node.children) { + const childPath = findPathToNode(child, pathname); + if (childPath) return [node, ...childPath]; + } + + return null; +} + +function withoutPlatformFilter<T extends PageTree.Node>(node: T): T { + const clone = { ...node } as T & { platforms?: readonly string[] }; + delete clone.platforms; + return clone; +} + +function hasVisibleContent(node: PageTree.Node, platform: Platform): boolean { + if (!isNodeVisible(node, platform)) return false; + if (node.type === "page") return true; + if (node.type === "separator") return false; + if (node.index && isNodeVisible(node.index, platform)) return true; + return node.children.some((c) => hasVisibleContent(c, platform)); +} + +export function pruneEmptySeparators( + items: readonly PageTree.Node[], + platform: Platform, +): PageTree.Node[] { + const result: PageTree.Node[] = []; + items.forEach((item, i) => { + if (item.type !== "separator") { + result.push(item); + return; + } + for (const next of items.slice(i + 1)) { + if (next.type === "separator") break; + if (hasVisibleContent(next, platform)) { + result.push(item); + break; + } + } + }); + return result; +} + +function filterChildren( + folder: PageTree.Folder, + platform: Platform, +): PageTree.Folder { + return { + ...folder, + children: pruneEmptySeparators(folder.children, platform), + }; +} + +function separator(name: ReactNode, id: string): PageTree.Separator { + return { type: "separator", name, $id: id }; +} + +function mergePlatformDocs( + docsFolder: PageTree.Folder, + platformFolder: PageTree.Folder, + platform: Platform, + sharedPageUrls: ReadonlySet<string>, +): PageTree.Folder { + const sharedPages = docsFolder.children + .filter((c) => c.type === "page" && sharedPageUrls.has(c.url)) + .map(withoutPlatformFilter); + + return { + ...docsFolder, + children: [ + separator("Getting Started", `platform-${platform}-getting-started`), + ...sharedPages, + separator(platformFolder.name, `platform-${platform}-label`), + ...pruneEmptySeparators(platformFolder.children, platform), + ], + }; +} + +export function buildPlatformSections( + folders: PageTree.Folder[], + platform: Platform, + config: PlatformTreeConfig = PLATFORM_TREE_CONFIG, +): PageTree.Folder[] { + if (folders.length === 0) return []; + + const allPlatformFolderNames = new Set( + Object.values(config.platformFolders).filter( + (v): v is string => v !== null, + ), + ); + + const platformFolderName = config.platformFolders[platform]; + + if (platformFolderName) { + const platformFolder = folders.find((f) => f.name === platformFolderName); + const docsFolder = folders.find((f) => f.name === config.mainDocsFolder); + if (!platformFolder || !docsFolder) { + const missingFolder = !docsFolder + ? config.mainDocsFolder + : platformFolderName; + if (process.env.NODE_ENV !== "production") { + throw new Error(`[platform-tree] Missing folder: ${missingFolder}`); + } + + return folders + .filter( + (f) => + !allPlatformFolderNames.has(String(f.name)) && + isNodeVisible(f, platform), + ) + .map((f) => filterChildren(f, platform)) + .filter((f) => hasVisibleContent(f, platform)); + } + + const merged = mergePlatformDocs( + docsFolder, + platformFolder, + platform, + config.sharedPageUrls, + ); + + const shared = folders + .filter( + (f) => + f.name !== config.mainDocsFolder && + !allPlatformFolderNames.has(String(f.name)) && + isNodeVisible(f, platform), + ) + .map((f) => filterChildren(f, platform)); + + return [merged, ...shared].filter((f) => hasVisibleContent(f, platform)); + } + + return folders + .filter( + (f) => + !allPlatformFolderNames.has(String(f.name)) && + isNodeVisible(f, platform), + ) + .map((f) => filterChildren(f, platform)) + .filter((f) => hasVisibleContent(f, platform)); +} diff --git a/apps/docs/components/docs/preview-code.server.tsx b/apps/docs/components/docs/preview-code.server.tsx new file mode 100644 index 0000000..fcc17bb --- /dev/null +++ b/apps/docs/components/docs/preview-code.server.tsx @@ -0,0 +1,274 @@ +import fs from "node:fs"; +import path from "node:path"; +import type { ReactNode } from "react"; +import { PreviewCodeClient } from "./preview-code"; +import type { LLMRenderContext } from "@/lib/get-llm-text"; + +type PreviewCodeProps = { + /** Path relative to apps/docs, e.g. "components/docs/samples/select" */ + file: string; + /** Function name to extract, e.g. "SelectScrollableSample" */ + name: string; + children: React.ReactNode; + /** Base UI flavored twin of children; enables the flavor switcher. */ + base?: React.ReactNode; + className?: string; +}; + +type StringState = { inString: boolean; stringChar: string }; + +function updateStringState( + state: StringState, + char: string, + prevChar: string, +): boolean { + if (state.inString) { + if (char === state.stringChar && prevChar !== "\\") { + state.inString = false; + } + return true; + } + if (char === '"' || char === "'" || char === "`") { + state.inString = true; + state.stringChar = char; + return true; + } + return false; +} + +function findMatchingParen(source: string, startIndex: number): number { + const state: StringState = { inString: false, stringChar: "" }; + let parenCount = 0; + + for (let i = startIndex; i < source.length; i++) { + const char = source[i]!; + const prevChar = source[i - 1] ?? ""; + if (updateStringState(state, char, prevChar)) continue; + + if (char === "(") parenCount++; + if (char === ")") { + if (parenCount === 0) { + const endIndex = i + 1; + return source[endIndex] === ";" ? endIndex + 1 : endIndex; + } + parenCount--; + } + } + return -1; +} + +function findMatchingBrace(source: string, startIndex: number): number { + const state: StringState = { inString: false, stringChar: "" }; + let braceCount = 0; + let foundFirstBrace = false; + + for (let i = startIndex; i < source.length; i++) { + const char = source[i]!; + const prevChar = source[i - 1] ?? ""; + if (updateStringState(state, char, prevChar)) continue; + + if (char === "{") { + braceCount++; + foundFirstBrace = true; + } + if (char === "}") { + braceCount--; + if (foundFirstBrace && braceCount === 0) { + return i + 1; + } + } + } + return -1; +} + +function extractFunctionCode(source: string, functionName: string): string { + const functionRegex = new RegExp( + `export\\s+function\\s+${functionName}\\s*\\([^)]*\\)\\s*\\{`, + ); + const constRegex = new RegExp( + `export\\s+const\\s+${functionName}\\s*=\\s*(?:function\\s*)?\\([^)]*\\)\\s*(?:=>\\s*)?\\{?`, + ); + + let match = functionRegex.exec(source); + let isArrowWithoutBrace = false; + + if (!match) { + match = constRegex.exec(source); + if (match && !match[0].endsWith("{")) { + isArrowWithoutBrace = true; + } + } + + if (!match) { + return `// Could not find function: ${functionName}`; + } + + const startIndex = match.index; + const searchStart = match.index + match[0].length; + + if (isArrowWithoutBrace) { + const endIndex = findMatchingParen(source, searchStart); + if (endIndex === -1) return `// Could not parse function: ${functionName}`; + return source.slice(startIndex, endIndex).trim(); + } + + const endIndex = findMatchingBrace(source, searchStart - 1); + if (endIndex === -1) return `// Could not parse function: ${functionName}`; + return source.slice(startIndex, endIndex).trim(); +} + +function extractImports(source: string): string[] { + const imports: string[] = []; + const lines = source.split("\n"); + let currentImport = ""; + let inImport = false; + + const isImportComplete = (line: string): boolean => + (line.includes(" from ") && (line.includes('"') || line.includes("'"))) || + line.includes('"') || + line.includes("'"); + + for (const line of lines) { + if (line.trim().startsWith("import ")) { + inImport = true; + currentImport = line; + } else if (inImport) { + currentImport += `\n${line}`; + } + + if (inImport && isImportComplete(line)) { + imports.push(currentImport); + currentImport = ""; + inImport = false; + } + } + return imports; +} + +function filterRelevantImports(imports: string[], code: string): string[] { + return imports.filter((imp) => { + const namedMatch = imp.match(/import\s+\{([^}]+)\}/); + const defaultMatch = imp.match(/import\s+(\w+)\s+from/); + + if (namedMatch?.[1]) { + const names = namedMatch[1] + .split(",") + .map((n) => n.trim().split(" as ")[0]?.trim()) + .filter((name): name is string => Boolean(name)); + return names.some((name) => code.includes(name)); + } + if (defaultMatch?.[1]) { + return code.includes(defaultMatch[1]); + } + return false; + }); +} + +function dedentSampleFrameContent(code: string): string { + const lines = code.split("\n"); + const result: string[] = []; + let inSampleFrame = false; + + for (const line of lines) { + if (line.includes("<SampleFrame")) { + inSampleFrame = true; + continue; + } + if (line.includes("</SampleFrame>")) { + inSampleFrame = false; + continue; + } + if (inSampleFrame && line.startsWith(" ")) { + result.push(line.slice(2)); + } else { + result.push(line); + } + } + return result.join("\n"); +} + +function cleanupCode(code: string): string { + return dedentSampleFrameContent(code).replace(/^export\s+/, ""); +} + +function cleanupImports(imports: string[]): string[] { + return imports + .filter((imp) => !imp.includes("SampleFrame")) + .map((imp) => + imp + .replace(/@\/components\/assistant-ui\//g, "@/components/ui/") + .replace(/(@\/components\/[\w-]+\/[\w.-]+?)\.base(["'])/g, "$1$2"), + ); +} + +function buildPreviewCode(file: string, name: string): string { + const filePath = path.join(process.cwd(), `${file}.tsx`); + try { + const source = fs.readFileSync(filePath, "utf-8"); + const functionCode = extractFunctionCode(source, name); + const cleanedCode = cleanupCode(functionCode); + + const allImports = extractImports(source); + const relevantImports = filterRelevantImports(allImports, cleanedCode); + const cleanedImports = cleanupImports(relevantImports); + + return cleanedImports.length > 0 + ? `${cleanedImports.join("\n")}\n\n${cleanedCode}` + : cleanedCode; + } catch { + return `// Error reading file: ${file}`; + } +} + +export async function PreviewCode({ + file, + name, + children, + base, + className, +}: PreviewCodeProps) { + const code = buildPreviewCode(file, name); + const baseCode = + base !== undefined && + fs.existsSync(path.join(process.cwd(), `${file}.base.tsx`)) + ? buildPreviewCode(`${file}.base`, name) + : undefined; + + return ( + <PreviewCodeClient + code={code} + {...(base !== undefined && { base })} + {...(baseCode !== undefined && { baseCode })} + {...(className && { className })} + > + {children} + </PreviewCodeClient> + ); +} + +// PreviewCode is imported directly in MDX, so the LLM map can't swap it; it +// carries its text variant as a `.llm` static instead. Drop the live preview, +// keep the source. +( + PreviewCode as typeof PreviewCode & { + llm: (props: PreviewCodeProps, ctx?: LLMRenderContext) => ReactNode; + } +).llm = ({ file, name }: PreviewCodeProps, ctx) => { + const sourceFile = + (ctx?.flavor ?? "base") === "base" && + fs.existsSync(path.join(process.cwd(), `${file}.base.tsx`)) + ? `${file}.base` + : file; + + return ( + <> + <p>{`[interactive preview component ${name} omitted]`}</p> + <p>{`Code for ${name} preview:`}</p> + <pre> + <code className="language-tsx"> + {buildPreviewCode(sourceFile, name)} + </code> + </pre> + </> + ); +}; diff --git a/apps/docs/components/docs/preview-code.tsx b/apps/docs/components/docs/preview-code.tsx new file mode 100644 index 0000000..739725f --- /dev/null +++ b/apps/docs/components/docs/preview-code.tsx @@ -0,0 +1,120 @@ +"use client"; + +import { useState } from "react"; +import { CheckIcon, CopyIcon } from "lucide-react"; +import ShikiHighlighter from "react-shiki"; +import { cn } from "@/lib/utils"; +import { useFlavor } from "@/components/docs/contexts/flavor"; + +type Tab = "preview" | "code"; + +type PreviewCodeClientProps = { + code: string; + baseCode?: string; + children: React.ReactNode; + base?: React.ReactNode; + className?: string; +}; + +type TabButtonProps = { + label: string; + value: Tab; + currentTab: Tab; + onSelect: (tab: Tab) => void; +}; + +function TabButton({ label, value, currentTab, onSelect }: TabButtonProps) { + const isActive = currentTab === value; + return ( + <button + type="button" + onClick={() => onSelect(value)} + className={cn( + "rounded-md px-2.5 py-1 text-xs transition-colors", + isActive + ? "bg-muted text-foreground font-medium" + : "text-muted-foreground hover:bg-muted/50 hover:text-foreground", + )} + > + {label} + </button> + ); +} + +export function PreviewCodeClient({ + code, + baseCode, + children, + base, + className, +}: PreviewCodeClientProps) { + const [tab, setTab] = useState<Tab>("preview"); + const [copied, setCopied] = useState(false); + const flavor = useFlavor(); + + const activeCode = + flavor === "base" && baseCode !== undefined ? baseCode : code; + + const handleCopy = async () => { + await navigator.clipboard.writeText(activeCode); + setCopied(true); + setTimeout(() => setCopied(false), 2000); + }; + + return ( + <div className="not-prose my-4"> + <div className="flex justify-end gap-1 pb-2"> + <TabButton + label="Preview" + value="preview" + currentTab={tab} + onSelect={setTab} + /> + <TabButton + label="Code" + value="code" + currentTab={tab} + onSelect={setTab} + /> + </div> + + {tab === "preview" ? ( + <div + className={cn( + "preview-code-preview border-border/50 flex items-center justify-center rounded-xl border p-6", + className, + )} + > + <div className="w-full"> + {flavor === "base" && base !== undefined ? base : children} + </div> + </div> + ) : ( + <div className="preview-code-block relative overflow-hidden rounded-xl"> + <button + type="button" + onClick={handleCopy} + className="text-muted-foreground hover:bg-background hover:text-foreground absolute top-2 right-2 z-10 flex size-7 items-center justify-center rounded-md opacity-50 transition-all hover:opacity-100" + aria-label={copied ? "Copied" : "Copy code"} + > + {copied ? ( + <CheckIcon className="size-3.5" /> + ) : ( + <CopyIcon className="size-3.5" /> + )} + </button> + <div className="max-h-96 scrollbar-none overflow-auto py-3.5 text-[0.8125rem] leading-[1.65]"> + <ShikiHighlighter + language="tsx" + theme={{ dark: "catppuccin-mocha", light: "catppuccin-latte" }} + addDefaultStyles={false} + showLanguage={false} + > + {activeCode.trim()} + </ShikiHighlighter> + </div> + </div> + )} + </div> + ); +} diff --git a/apps/docs/components/docs/primitives-type-table-client.tsx b/apps/docs/components/docs/primitives-type-table-client.tsx new file mode 100644 index 0000000..7801d68 --- /dev/null +++ b/apps/docs/components/docs/primitives-type-table-client.tsx @@ -0,0 +1,187 @@ +"use client"; + +import { Collapsible } from "@base-ui/react/collapsible"; +import { ChevronDown } from "lucide-react"; +import { type ReactNode, useEffect, useState } from "react"; +import { cn } from "@/lib/utils"; + +export type TypeTableRow = { + name: string; + type: ReactNode; + typeFull?: ReactNode | undefined; + description?: ReactNode | undefined; + default?: ReactNode | undefined; + required: boolean; + deprecated: boolean; + children?: { type?: string | undefined; rows: TypeTableRow[] }[] | undefined; +}; + +function PropName({ row }: { row: TypeTableRow }) { + return ( + <code + className={cn( + "text-fd-primary w-1/4 min-w-0 [scrollbar-width:none] overflow-x-auto bg-transparent! mask-[linear-gradient(to_right,black_calc(100%-12px),transparent)] p-0! pe-2 font-mono font-medium whitespace-nowrap [-ms-overflow-style:none] [&::-webkit-scrollbar]:hidden", + row.deprecated && "text-fd-primary/50 line-through", + )} + > + {row.name} + {!row.required && "?"} + </code> + ); +} + +function TypeCell({ + children, + className, +}: { + children: ReactNode; + className?: string; +}) { + return ( + <span + className={cn( + "min-w-0 flex-1 overflow-hidden [&_code]:border-0! [&_code]:bg-transparent! [&_code]:p-0! [&_code]:text-[0.8125rem]! [&_pre]:bg-transparent! [&_pre]:p-0! [&>figure]:my-0!", + "[scrollbar-width:none] overflow-x-auto whitespace-nowrap [-ms-overflow-style:none] [&::-webkit-scrollbar]:hidden", + className, + )} + > + {children} + </span> + ); +} + +function Item({ + row, + parentId, +}: { + row: TypeTableRow; + parentId?: string | undefined; +}) { + const [open, setOpen] = useState(false); + const [mounted, setMounted] = useState(false); + const id = parentId ? `${parentId}-${row.name}` : undefined; + + const hasContent = + row.description || row.default || row.children?.length || row.typeFull; + + useEffect(() => { + setMounted(true); + }, []); + + useEffect(() => { + const expand = () => { + if (id && window.location.hash === `#${id}`) { + setOpen(true); + document.getElementById(id)?.scrollIntoView({ behavior: "smooth" }); + } + }; + + expand(); + window.addEventListener("hashchange", expand); + return () => window.removeEventListener("hashchange", expand); + }, [id]); + + if (!hasContent) { + return ( + <div + id={id} + className="not-prose flex w-full flex-row items-center rounded-xl px-3 py-2" + > + <PropName row={row} /> + <TypeCell>{row.type}</TypeCell> + </div> + ); + } + + return ( + <Collapsible.Root + id={id} + open={open} + onOpenChange={(v) => { + setOpen(v); + if (v && id) window.history.replaceState(null, "", `#${id}`); + }} + className={cn( + "group scroll-m-20 overflow-hidden rounded-xl border transition-all", + open + ? "bg-fd-background shadow-sm not-last:mb-2" + : "border-transparent", + )} + > + <Collapsible.Trigger className="not-prose group/trigger hover:bg-fd-accent relative flex w-full flex-row items-center px-3 py-2 text-start"> + <PropName row={row} /> + <TypeCell className="me-4 mask-[linear-gradient(to_right,black_calc(100%-1rem),transparent)]"> + {row.type} + </TypeCell> + <ChevronDown className="text-fd-muted-foreground absolute inset-e-2 size-4 transition-transform group-data-panel-open/trigger:rotate-180" /> + </Collapsible.Trigger> + + <Collapsible.Panel + className={cn( + "overflow-hidden", + mounted && + "data-closed:animate-fd-collapsible-up data-open:animate-fd-collapsible-down", + )} + > + <div className="fd-scroll-container grid grid-cols-[1fr_3fr] gap-y-4 overflow-auto border-t p-3 text-sm"> + <div className="prose prose-no-margin col-span-full text-sm empty:hidden"> + {row.description} + </div> + {row.typeFull && ( + <> + <span className="not-prose text-fd-muted-foreground pe-2"> + Type + </span> + <TypeCell> + <span className="[&_pre]:inline">{row.typeFull}</span> + </TypeCell> + </> + )} + {row.default && ( + <> + <span className="not-prose text-fd-muted-foreground pe-2"> + Default + </span> + <TypeCell className="my-auto"> + <span className="[&_pre]:inline">{row.default}</span> + </TypeCell> + </> + )} + {row.children?.map((child, i) => ( + <div key={child.type ?? i} className="col-span-full my-1"> + <TypeTableClient id={child.type} rows={child.rows} nested /> + </div> + ))} + </div> + </Collapsible.Panel> + </Collapsible.Root> + ); +} + +export function TypeTableClient({ + id, + rows, + nested, +}: { + id?: string | undefined; + rows: TypeTableRow[]; + nested?: boolean | undefined; +}) { + return ( + <div + id={id} + className={cn( + "bg-fd-card text-fd-card-foreground flex flex-col overflow-hidden rounded-2xl border p-1 text-sm", + nested ? "bg-fd-secondary/50" : "my-6", + )} + > + <div className="not-prose text-fd-muted-foreground flex items-center px-3 py-1 font-medium"> + <p className="w-1/4 shrink-0 pe-2">Prop</p> + <p className="min-w-0 flex-1 pl-4">Type</p> + </div> + {rows.map((row) => ( + <Item key={row.name} row={row} parentId={id} /> + ))} + </div> + ); +} diff --git a/apps/docs/components/docs/primitives-type-table.tsx b/apps/docs/components/docs/primitives-type-table.tsx new file mode 100644 index 0000000..fe74acf --- /dev/null +++ b/apps/docs/components/docs/primitives-type-table.tsx @@ -0,0 +1,167 @@ +import { Fragment, type FC, type ReactNode } from "react"; +import { highlight } from "fumadocs-core/highlight"; +import Link from "next/link"; + +import { + TypeTableClient, + type TypeTableRow, +} from "./primitives-type-table-client"; +import { DefListLLM } from "./parameters-table"; +import { StatusBadge } from "./status-badge"; + +type PropDef = { + name: string; + type?: string; + description?: string | ReactNode; + default?: string; + required?: boolean; + deprecated?: string; + children?: Array<{ type?: string; parameters: PropDef[] }>; +}; + +const COMMON_PARAMS: Record<string, Partial<PropDef>> = { + asChild: { + type: "boolean", + default: "false", + description: ( + <> + Change the default rendered element for the one passed as a child, + merging their props and behavior.{" "} + <Link + className="text-fd-primary font-medium underline underline-offset-2" + href="/docs/api-reference/primitives/composition" + > + Composition guide + </Link> + </> + ), + }, +}; + +async function highlightType(type: string): Promise<ReactNode> { + if (!type) return null; + return highlight(type, { + lang: "typescript", + themes: { light: "github-light", dark: "github-dark" }, + defaultColor: false, + }); +} + +function stripTrailingUndefined(typeRaw: string): string { + return typeRaw.replace(/\s*\|\s*undefined\s*$/, "").trim(); +} + +function getShortType(typeRaw: string): string | undefined { + if (!typeRaw) return undefined; + if (typeRaw.length <= 60 && !typeRaw.includes("{")) return undefined; + + // Replace object literal bodies with "object" + let short = typeRaw.replace(/\{[^{}]*\}/g, "object"); + + // Collapse "object | object | ..." into single "object" + short = short.replace(/\bobject\b(\s*\|\s*\bobject\b)+/g, "object"); + + if (short.length > 60) short = `${short.substring(0, 57)}...`; + if (short === typeRaw) return undefined; + return short; +} + +async function propsToRows(props: PropDef[]): Promise<TypeTableRow[]> { + return Promise.all( + props.map(async (raw) => { + const prop = { ...COMMON_PARAMS[raw.name], ...raw }; + + const descParts: ReactNode[] = [ + prop.deprecated && ( + <Fragment key="deprecated"> + <StatusBadge variant="deprecated" className="mr-1" /> + <span>{prop.deprecated}</span> + </Fragment> + ), + prop.name.startsWith("unstable_") && ( + <StatusBadge key="unstable" variant="unstable" className="mr-1" /> + ), + prop.description && + (typeof prop.description === "string" && + prop.description.includes("\n") ? ( + <span key="description"> + {prop.description.split("\n").map((line, i) => ( + <Fragment key={i}> + {i > 0 && <br />} + {line} + </Fragment> + ))} + </span> + ) : ( + <span key="description">{prop.description}</span> + )), + ].filter(Boolean); + + // Highlight the type (clean version for collapsed row, full for expanded) + const typeRaw = prop.type ?? ""; + const displayType = stripTrailingUndefined(typeRaw); + const shortType = getShortType(displayType); + const highlightedType = displayType + ? await highlightType(shortType ?? displayType) + : null; + + const highlightedTypeFull = + typeRaw && (shortType != null || displayType !== typeRaw) + ? await highlightType(typeRaw) + : undefined; + const highlightedDefault = prop.default + ? await highlightType(prop.default) + : undefined; + + let children: + | { type?: string | undefined; rows: TypeTableRow[] }[] + | undefined; + if (prop.children?.length) { + children = await Promise.all( + prop.children.map(async (child) => ({ + type: child.type, + rows: await propsToRows(child.parameters), + })), + ); + } + + return { + name: prop.name, + type: highlightedType, + typeFull: highlightedTypeFull, + description: descParts.length > 0 ? descParts : undefined, + default: highlightedDefault, + required: prop.required ?? false, + deprecated: !!prop.deprecated, + children, + } satisfies TypeTableRow; + }), + ); +} + +export async function PrimitivesTypeTable({ + type, + parameters, +}: { + type?: string; + parameters: PropDef[]; +}) { + const rows = await propsToRows(parameters); + return <TypeTableClient id={type} rows={rows} />; +} + +// Build from PropDef directly, skipping Shiki highlighting (it emitted stray +// code fences) and the `highlighted*` fields meant for the client table. +// stripTrailingUndefined drops the `| undefined` the prop extractor leaves on. +export const PrimitivesTypeTableLLM: FC<{ + type?: string; + parameters: PropDef[]; +}> = ({ parameters }) => { + return ( + <DefListLLM + defs={parameters} + commonParams={COMMON_PARAMS} + normalizeType={stripTrailingUndefined} + /> + ); +}; diff --git a/apps/docs/components/docs/samples/accordion.base.tsx b/apps/docs/components/docs/samples/accordion.base.tsx new file mode 100644 index 0000000..fea0541 --- /dev/null +++ b/apps/docs/components/docs/samples/accordion.base.tsx @@ -0,0 +1,237 @@ +"use client"; + +import { useState } from "react"; +import { CreditCard, Settings, User, HelpCircle } from "lucide-react"; +import { + Accordion, + AccordionItem, + AccordionTrigger, + AccordionContent, +} from "@/components/assistant-ui/accordion.base"; +import { SampleFrame } from "@/components/docs/samples/sample-frame"; + +export function AccordionSample() { + return ( + <SampleFrame className="flex h-auto items-center justify-center p-6"> + <Accordion className="w-[400px]"> + <AccordionItem value="item-1"> + <AccordionTrigger>Is it accessible?</AccordionTrigger> + <AccordionContent> + Yes. It adheres to the WAI-ARIA design pattern for accordions. + </AccordionContent> + </AccordionItem> + <AccordionItem value="item-2"> + <AccordionTrigger>Is it styled?</AccordionTrigger> + <AccordionContent> + Yes. It comes with default styles that match the other components' + aesthetic. + </AccordionContent> + </AccordionItem> + <AccordionItem value="item-3"> + <AccordionTrigger>Is it animated?</AccordionTrigger> + <AccordionContent> + Yes. It's animated by default, but you can disable it if you prefer. + </AccordionContent> + </AccordionItem> + </Accordion> + </SampleFrame> + ); +} + +export function AccordionVariantsSample() { + return ( + <SampleFrame className="flex h-auto flex-col items-center justify-center gap-8 p-6"> + <div className="flex w-[400px] flex-col gap-2"> + <span className="text-muted-foreground text-xs">Default</span> + <Accordion variant="default"> + <AccordionItem value="a"> + <AccordionTrigger>Section A</AccordionTrigger> + <AccordionContent>Content for section A.</AccordionContent> + </AccordionItem> + <AccordionItem value="b"> + <AccordionTrigger>Section B</AccordionTrigger> + <AccordionContent>Content for section B.</AccordionContent> + </AccordionItem> + </Accordion> + </div> + <div className="flex w-[400px] flex-col gap-2"> + <span className="text-muted-foreground text-xs">Outline</span> + <Accordion variant="outline"> + <AccordionItem value="a"> + <AccordionTrigger>Section A</AccordionTrigger> + <AccordionContent>Content for section A.</AccordionContent> + </AccordionItem> + <AccordionItem value="b"> + <AccordionTrigger>Section B</AccordionTrigger> + <AccordionContent>Content for section B.</AccordionContent> + </AccordionItem> + </Accordion> + </div> + <div className="flex w-[400px] flex-col gap-2"> + <span className="text-muted-foreground text-xs">Ghost</span> + <Accordion variant="ghost"> + <AccordionItem value="a"> + <AccordionTrigger>Section A</AccordionTrigger> + <AccordionContent>Content for section A.</AccordionContent> + </AccordionItem> + <AccordionItem value="b"> + <AccordionTrigger>Section B</AccordionTrigger> + <AccordionContent>Content for section B.</AccordionContent> + </AccordionItem> + </Accordion> + </div> + </SampleFrame> + ); +} + +export function AccordionMultipleSample() { + return ( + <SampleFrame className="flex h-auto items-center justify-center p-6"> + <Accordion multiple className="w-[400px]"> + <AccordionItem value="item-1"> + <AccordionTrigger>First Section</AccordionTrigger> + <AccordionContent> + This section can be open at the same time as others. + </AccordionContent> + </AccordionItem> + <AccordionItem value="item-2"> + <AccordionTrigger>Second Section</AccordionTrigger> + <AccordionContent> + Multiple sections can be expanded simultaneously. + </AccordionContent> + </AccordionItem> + <AccordionItem value="item-3"> + <AccordionTrigger>Third Section</AccordionTrigger> + <AccordionContent>Try opening all three at once!</AccordionContent> + </AccordionItem> + </Accordion> + </SampleFrame> + ); +} + +export function AccordionWithIconsSample() { + return ( + <SampleFrame className="flex h-auto items-center justify-center p-6"> + <Accordion variant="outline" className="w-[400px]"> + <AccordionItem value="account"> + <AccordionTrigger> + <span className="flex items-center gap-2"> + <User className="size-4" /> + Account Settings + </span> + </AccordionTrigger> + <AccordionContent> + Manage your account details, profile picture, and personal + information. + </AccordionContent> + </AccordionItem> + <AccordionItem value="billing"> + <AccordionTrigger> + <span className="flex items-center gap-2"> + <CreditCard className="size-4" /> + Billing + </span> + </AccordionTrigger> + <AccordionContent> + View your billing history, manage payment methods, and update + subscription. + </AccordionContent> + </AccordionItem> + <AccordionItem value="preferences"> + <AccordionTrigger> + <span className="flex items-center gap-2"> + <Settings className="size-4" /> + Preferences + </span> + </AccordionTrigger> + <AccordionContent> + Customize your experience with notification and display settings. + </AccordionContent> + </AccordionItem> + </Accordion> + </SampleFrame> + ); +} + +export function AccordionControlledSample() { + const [value, setValue] = useState<string[]>(["item-1"]); + + return ( + <SampleFrame className="flex h-auto flex-col items-center justify-center gap-4 p-6"> + <Accordion + value={value} + onValueChange={(next) => setValue(next as string[])} + className="w-[400px]" + > + <AccordionItem value="item-1"> + <AccordionTrigger>Overview</AccordionTrigger> + <AccordionContent> + This is the overview section content. + </AccordionContent> + </AccordionItem> + <AccordionItem value="item-2"> + <AccordionTrigger>Details</AccordionTrigger> + <AccordionContent> + This is the details section content. + </AccordionContent> + </AccordionItem> + <AccordionItem value="item-3"> + <AccordionTrigger>Advanced</AccordionTrigger> + <AccordionContent> + This is the advanced section content. + </AccordionContent> + </AccordionItem> + </Accordion> + <p className="text-muted-foreground text-sm"> + Current value: <code className="font-mono">{value ?? "none"}</code> + </p> + </SampleFrame> + ); +} + +export function AccordionFAQSample() { + return ( + <SampleFrame className="flex h-auto items-center justify-center p-6"> + <div className="w-[500px]"> + <div className="mb-4 flex items-center gap-2"> + <HelpCircle className="size-5" /> + <h3 className="text-lg font-semibold">Frequently Asked Questions</h3> + </div> + <Accordion> + <AccordionItem value="faq-1"> + <AccordionTrigger> + What payment methods do you accept? + </AccordionTrigger> + <AccordionContent> + We accept all major credit cards (Visa, MasterCard, American + Express), PayPal, and bank transfers for annual subscriptions. + </AccordionContent> + </AccordionItem> + <AccordionItem value="faq-2"> + <AccordionTrigger> + Can I cancel my subscription anytime? + </AccordionTrigger> + <AccordionContent> + Yes, you can cancel your subscription at any time. Your access + will continue until the end of your current billing period. + </AccordionContent> + </AccordionItem> + <AccordionItem value="faq-3"> + <AccordionTrigger>Do you offer refunds?</AccordionTrigger> + <AccordionContent> + We offer a 30-day money-back guarantee for all new subscriptions. + Contact our support team to request a refund. + </AccordionContent> + </AccordionItem> + <AccordionItem value="faq-4"> + <AccordionTrigger>How do I contact support?</AccordionTrigger> + <AccordionContent> + You can reach our support team via email at support@example.com or + through the live chat feature in the bottom right corner. + </AccordionContent> + </AccordionItem> + </Accordion> + </div> + </SampleFrame> + ); +} diff --git a/apps/docs/components/docs/samples/accordion.tsx b/apps/docs/components/docs/samples/accordion.tsx new file mode 100644 index 0000000..1749636 --- /dev/null +++ b/apps/docs/components/docs/samples/accordion.tsx @@ -0,0 +1,244 @@ +"use client"; + +import { useState } from "react"; +import { CreditCard, Settings, User, HelpCircle } from "lucide-react"; +import { + Accordion, + AccordionItem, + AccordionTrigger, + AccordionContent, +} from "@/components/assistant-ui/accordion"; +import { SampleFrame } from "@/components/docs/samples/sample-frame"; + +export function AccordionSample() { + return ( + <SampleFrame className="flex h-auto items-center justify-center p-6"> + <Accordion type="single" collapsible className="w-[400px]"> + <AccordionItem value="item-1"> + <AccordionTrigger>Is it accessible?</AccordionTrigger> + <AccordionContent> + Yes. It adheres to the WAI-ARIA design pattern for accordions. + </AccordionContent> + </AccordionItem> + <AccordionItem value="item-2"> + <AccordionTrigger>Is it styled?</AccordionTrigger> + <AccordionContent> + Yes. It comes with default styles that match the other components' + aesthetic. + </AccordionContent> + </AccordionItem> + <AccordionItem value="item-3"> + <AccordionTrigger>Is it animated?</AccordionTrigger> + <AccordionContent> + Yes. It's animated by default, but you can disable it if you prefer. + </AccordionContent> + </AccordionItem> + </Accordion> + </SampleFrame> + ); +} + +export function AccordionVariantsSample() { + return ( + <SampleFrame className="flex h-auto flex-col items-center justify-center gap-8 p-6"> + <div className="flex w-[400px] flex-col gap-2"> + <span className="text-muted-foreground text-xs">Default</span> + <Accordion type="single" collapsible variant="default"> + <AccordionItem value="a"> + <AccordionTrigger>Section A</AccordionTrigger> + <AccordionContent>Content for section A.</AccordionContent> + </AccordionItem> + <AccordionItem value="b"> + <AccordionTrigger>Section B</AccordionTrigger> + <AccordionContent>Content for section B.</AccordionContent> + </AccordionItem> + </Accordion> + </div> + <div className="flex w-[400px] flex-col gap-2"> + <span className="text-muted-foreground text-xs">Outline</span> + <Accordion type="single" collapsible variant="outline"> + <AccordionItem value="a"> + <AccordionTrigger>Section A</AccordionTrigger> + <AccordionContent>Content for section A.</AccordionContent> + </AccordionItem> + <AccordionItem value="b"> + <AccordionTrigger>Section B</AccordionTrigger> + <AccordionContent>Content for section B.</AccordionContent> + </AccordionItem> + </Accordion> + </div> + <div className="flex w-[400px] flex-col gap-2"> + <span className="text-muted-foreground text-xs">Ghost</span> + <Accordion type="single" collapsible variant="ghost"> + <AccordionItem value="a"> + <AccordionTrigger>Section A</AccordionTrigger> + <AccordionContent>Content for section A.</AccordionContent> + </AccordionItem> + <AccordionItem value="b"> + <AccordionTrigger>Section B</AccordionTrigger> + <AccordionContent>Content for section B.</AccordionContent> + </AccordionItem> + </Accordion> + </div> + </SampleFrame> + ); +} + +export function AccordionMultipleSample() { + return ( + <SampleFrame className="flex h-auto items-center justify-center p-6"> + <Accordion type="multiple" className="w-[400px]"> + <AccordionItem value="item-1"> + <AccordionTrigger>First Section</AccordionTrigger> + <AccordionContent> + This section can be open at the same time as others. + </AccordionContent> + </AccordionItem> + <AccordionItem value="item-2"> + <AccordionTrigger>Second Section</AccordionTrigger> + <AccordionContent> + Multiple sections can be expanded simultaneously. + </AccordionContent> + </AccordionItem> + <AccordionItem value="item-3"> + <AccordionTrigger>Third Section</AccordionTrigger> + <AccordionContent>Try opening all three at once!</AccordionContent> + </AccordionItem> + </Accordion> + </SampleFrame> + ); +} + +export function AccordionWithIconsSample() { + return ( + <SampleFrame className="flex h-auto items-center justify-center p-6"> + <Accordion + type="single" + collapsible + variant="outline" + className="w-[400px]" + > + <AccordionItem value="account"> + <AccordionTrigger> + <span className="flex items-center gap-2"> + <User className="size-4" /> + Account Settings + </span> + </AccordionTrigger> + <AccordionContent> + Manage your account details, profile picture, and personal + information. + </AccordionContent> + </AccordionItem> + <AccordionItem value="billing"> + <AccordionTrigger> + <span className="flex items-center gap-2"> + <CreditCard className="size-4" /> + Billing + </span> + </AccordionTrigger> + <AccordionContent> + View your billing history, manage payment methods, and update + subscription. + </AccordionContent> + </AccordionItem> + <AccordionItem value="preferences"> + <AccordionTrigger> + <span className="flex items-center gap-2"> + <Settings className="size-4" /> + Preferences + </span> + </AccordionTrigger> + <AccordionContent> + Customize your experience with notification and display settings. + </AccordionContent> + </AccordionItem> + </Accordion> + </SampleFrame> + ); +} + +export function AccordionControlledSample() { + const [value, setValue] = useState("item-1"); + + return ( + <SampleFrame className="flex h-auto flex-col items-center justify-center gap-4 p-6"> + <Accordion + type="single" + collapsible + value={value} + onValueChange={setValue} + className="w-[400px]" + > + <AccordionItem value="item-1"> + <AccordionTrigger>Overview</AccordionTrigger> + <AccordionContent> + This is the overview section content. + </AccordionContent> + </AccordionItem> + <AccordionItem value="item-2"> + <AccordionTrigger>Details</AccordionTrigger> + <AccordionContent> + This is the details section content. + </AccordionContent> + </AccordionItem> + <AccordionItem value="item-3"> + <AccordionTrigger>Advanced</AccordionTrigger> + <AccordionContent> + This is the advanced section content. + </AccordionContent> + </AccordionItem> + </Accordion> + <p className="text-muted-foreground text-sm"> + Current value: <code className="font-mono">{value ?? "none"}</code> + </p> + </SampleFrame> + ); +} + +export function AccordionFAQSample() { + return ( + <SampleFrame className="flex h-auto items-center justify-center p-6"> + <div className="w-[500px]"> + <div className="mb-4 flex items-center gap-2"> + <HelpCircle className="size-5" /> + <h3 className="text-lg font-semibold">Frequently Asked Questions</h3> + </div> + <Accordion type="single" collapsible> + <AccordionItem value="faq-1"> + <AccordionTrigger> + What payment methods do you accept? + </AccordionTrigger> + <AccordionContent> + We accept all major credit cards (Visa, MasterCard, American + Express), PayPal, and bank transfers for annual subscriptions. + </AccordionContent> + </AccordionItem> + <AccordionItem value="faq-2"> + <AccordionTrigger> + Can I cancel my subscription anytime? + </AccordionTrigger> + <AccordionContent> + Yes, you can cancel your subscription at any time. Your access + will continue until the end of your current billing period. + </AccordionContent> + </AccordionItem> + <AccordionItem value="faq-3"> + <AccordionTrigger>Do you offer refunds?</AccordionTrigger> + <AccordionContent> + We offer a 30-day money-back guarantee for all new subscriptions. + Contact our support team to request a refund. + </AccordionContent> + </AccordionItem> + <AccordionItem value="faq-4"> + <AccordionTrigger>How do I contact support?</AccordionTrigger> + <AccordionContent> + You can reach our support team via email at support@example.com or + through the live chat feature in the bottom right corner. + </AccordionContent> + </AccordionItem> + </Accordion> + </div> + </SampleFrame> + ); +} diff --git a/apps/docs/components/docs/samples/action-bar-primitive.tsx b/apps/docs/components/docs/samples/action-bar-primitive.tsx new file mode 100644 index 0000000..546831c --- /dev/null +++ b/apps/docs/components/docs/samples/action-bar-primitive.tsx @@ -0,0 +1,60 @@ +"use client"; + +import { + ActionBarPrimitive, + MessagePrimitive, + ThreadPrimitive, +} from "@assistant-ui/react"; +import { CheckIcon, CopyIcon, RefreshCwIcon } from "lucide-react"; +import { SampleRuntimeProvider } from "./sample-runtime-provider"; + +export function ActionBarPrimitiveSample() { + return ( + <div className="not-prose border-border/50 bg-muted/40 flex items-end rounded-xl border p-4"> + <div className="mx-auto flex w-full max-w-lg flex-col gap-3"> + <SampleRuntimeProvider> + <ThreadPrimitive.Messages + components={{ + UserMessage, + AssistantMessage, + }} + /> + </SampleRuntimeProvider> + </div> + </div> + ); +} + +function UserMessage() { + return ( + <MessagePrimitive.Root className="flex justify-end"> + <div className="bg-primary text-primary-foreground max-w-[80%] rounded-2xl px-4 py-2.5 text-sm"> + <MessagePrimitive.Parts /> + </div> + </MessagePrimitive.Root> + ); +} + +function AssistantMessage() { + return ( + <MessagePrimitive.Root className="group flex flex-col items-start gap-1"> + <div className="bg-muted max-w-[80%] rounded-2xl px-4 py-2.5 text-sm"> + <MessagePrimitive.Parts /> + </div> + <ActionBarPrimitive.Root + hideWhenRunning + autohide="not-last" + autohideFloat="always" + className="flex gap-0.5 data-[floating]:opacity-0 data-[floating]:transition-opacity data-[floating]:group-hover:opacity-100" + > + <ActionBarPrimitive.Copy className="group/copy text-muted-foreground hover:bg-muted hover:text-foreground flex size-8 items-center justify-center rounded-lg transition-colors"> + <CopyIcon className="size-4 group-data-[copied]/copy:hidden" /> + <CheckIcon className="hidden size-4 group-data-[copied]/copy:block" /> + </ActionBarPrimitive.Copy> + <ActionBarPrimitive.Reload className="text-muted-foreground hover:bg-muted hover:text-foreground flex size-8 items-center justify-center rounded-lg transition-colors"> + <RefreshCwIcon className="size-4" /> + </ActionBarPrimitive.Reload> + </ActionBarPrimitive.Root> + </MessagePrimitive.Root> + ); +} diff --git a/apps/docs/components/docs/samples/assistant-modal.base.tsx b/apps/docs/components/docs/samples/assistant-modal.base.tsx new file mode 100644 index 0000000..1852407 --- /dev/null +++ b/apps/docs/components/docs/samples/assistant-modal.base.tsx @@ -0,0 +1,69 @@ +"use client"; + +import { forwardRef, useState } from "react"; +import { BotIcon, ChevronDownIcon } from "lucide-react"; +import { AssistantModalPrimitive } from "@assistant-ui/react"; +import { Thread } from "@/components/assistant-ui/thread"; +import { TooltipIconButton } from "@/components/assistant-ui/tooltip-icon-button.base"; +import { SampleFrame } from "./sample-frame"; + +export function AssistantModalSample() { + const [container, setContainer] = useState<HTMLDivElement | null>(null); + return ( + <SampleFrame className="bg-muted/40 h-125 md:h-160"> + <div ref={setContainer} className="absolute inset-0 contain-[layout]"> + {container && <AssistantModal container={container} />} + </div> + </SampleFrame> + ); +} + +export function AssistantModal({ container }: { container?: HTMLElement }) { + return ( + <AssistantModalPrimitive.Root defaultOpen> + <AssistantModalPrimitive.Anchor className="absolute end-4 bottom-4 size-11"> + <AssistantModalPrimitive.Trigger asChild> + <AssistantModalButton /> + </AssistantModalPrimitive.Trigger> + </AssistantModalPrimitive.Anchor> + <AssistantModalPrimitive.Content + sideOffset={16} + avoidCollisions={false} + portalProps={{ container }} + className="bg-popover text-popover-foreground border-border/60 dark:border-muted-foreground/15 data-[state=open]:animate-in data-[state=open]:fade-in-0 data-[state=open]:zoom-in-95 data-[state=open]:slide-in-from-bottom-2 data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=closed]:zoom-out-95 data-[state=closed]:slide-out-to-bottom-2 [&[data-state=open]_.aui-thread-viewport-footer]:animate-in [&[data-state=open]_.aui-thread-viewport-footer]:fade-in-0 [&[data-state=open]_.aui-thread-viewport-footer]:slide-in-from-bottom-2 [&[data-state=open]_.aui-thread-viewport-footer]:fill-mode-backwards [&>.aui-thread-root_.aui-thread-viewport-footer]:bg-popover z-50 h-100 max-h-(--radix-popover-content-available-height) w-72 max-w-[calc(100vw-2rem)] origin-(--radix-popover-content-transform-origin) overflow-clip rounded-[2.5rem] border p-0 antialiased shadow-xl ease-[cubic-bezier(0.32,0.72,0,1)] outline-none data-[state=closed]:duration-200 data-[state=open]:duration-300 motion-reduce:animate-none md:h-137.5 md:w-105 motion-reduce:[&_.aui-thread-viewport-footer]:animate-none [&_[data-slot=aui\_thread-viewport]]:[scrollbar-gutter:stable_both-edges] [&>.aui-thread-root]:bg-inherit [&[data-state=open]_.aui-thread-viewport-footer]:delay-100 [&[data-state=open]_.aui-thread-viewport-footer]:duration-300 [&[data-state=open]_.aui-thread-viewport-footer]:ease-[cubic-bezier(0.32,0.72,0,1)]" + > + <Thread /> + </AssistantModalPrimitive.Content> + </AssistantModalPrimitive.Root> + ); +} + +type AssistantModalButtonProps = { "data-state"?: "open" | "closed" }; + +const AssistantModalButton = forwardRef< + HTMLButtonElement, + AssistantModalButtonProps +>(function AssistantModalButton({ "data-state": state, ...rest }, ref) { + const tooltip = state === "open" ? "Close Assistant" : "Open Assistant"; + + return ( + <TooltipIconButton + variant="default" + tooltip={tooltip} + side="left" + {...rest} + className="size-full rounded-full shadow-lg transition-transform duration-150 ease-out hover:scale-105 active:scale-96 motion-reduce:transition-none" + ref={ref} + > + <BotIcon + data-state={state} + className="absolute size-6 transition-[scale,opacity,filter] duration-200 ease-[cubic-bezier(0.2,0,0,1)] data-[state=closed]:scale-100 data-[state=closed]:opacity-100 data-[state=closed]:blur-[0px] data-[state=open]:scale-25 data-[state=open]:opacity-0 data-[state=open]:blur-[4px] motion-reduce:transition-none" + /> + <ChevronDownIcon + data-state={state} + className="absolute size-6 transition-[scale,opacity,filter] duration-200 ease-[cubic-bezier(0.2,0,0,1)] data-[state=closed]:scale-25 data-[state=closed]:opacity-0 data-[state=closed]:blur-[4px] data-[state=open]:scale-100 data-[state=open]:opacity-100 data-[state=open]:blur-[0px] motion-reduce:transition-none" + /> + <span className="sr-only">{tooltip}</span> + </TooltipIconButton> + ); +}); diff --git a/apps/docs/components/docs/samples/assistant-modal.tsx b/apps/docs/components/docs/samples/assistant-modal.tsx new file mode 100644 index 0000000..d7fad1c --- /dev/null +++ b/apps/docs/components/docs/samples/assistant-modal.tsx @@ -0,0 +1,69 @@ +"use client"; + +import { forwardRef, useState } from "react"; +import { BotIcon, ChevronDownIcon } from "lucide-react"; +import { AssistantModalPrimitive } from "@assistant-ui/react"; +import { Thread } from "@/components/assistant-ui/thread"; +import { TooltipIconButton } from "@/components/assistant-ui/tooltip-icon-button"; +import { SampleFrame } from "./sample-frame"; + +export function AssistantModalSample() { + const [container, setContainer] = useState<HTMLDivElement | null>(null); + return ( + <SampleFrame className="bg-muted/40 h-125 md:h-160"> + <div ref={setContainer} className="absolute inset-0 contain-[layout]"> + {container && <AssistantModal container={container} />} + </div> + </SampleFrame> + ); +} + +export function AssistantModal({ container }: { container?: HTMLElement }) { + return ( + <AssistantModalPrimitive.Root defaultOpen> + <AssistantModalPrimitive.Anchor className="absolute end-4 bottom-4 size-11"> + <AssistantModalPrimitive.Trigger asChild> + <AssistantModalButton /> + </AssistantModalPrimitive.Trigger> + </AssistantModalPrimitive.Anchor> + <AssistantModalPrimitive.Content + sideOffset={16} + avoidCollisions={false} + portalProps={{ container }} + className="bg-popover text-popover-foreground border-border/60 dark:border-muted-foreground/15 data-[state=open]:animate-in data-[state=open]:fade-in-0 data-[state=open]:zoom-in-95 data-[state=open]:slide-in-from-bottom-2 data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=closed]:zoom-out-95 data-[state=closed]:slide-out-to-bottom-2 [&[data-state=open]_.aui-thread-viewport-footer]:animate-in [&[data-state=open]_.aui-thread-viewport-footer]:fade-in-0 [&[data-state=open]_.aui-thread-viewport-footer]:slide-in-from-bottom-2 [&[data-state=open]_.aui-thread-viewport-footer]:fill-mode-backwards [&>.aui-thread-root_.aui-thread-viewport-footer]:bg-popover z-50 h-100 max-h-(--radix-popover-content-available-height) w-72 max-w-[calc(100vw-2rem)] origin-(--radix-popover-content-transform-origin) overflow-clip rounded-[2.5rem] border p-0 antialiased shadow-xl ease-[cubic-bezier(0.32,0.72,0,1)] outline-none data-[state=closed]:duration-200 data-[state=open]:duration-300 motion-reduce:animate-none md:h-137.5 md:w-105 motion-reduce:[&_.aui-thread-viewport-footer]:animate-none [&_[data-slot=aui\_thread-viewport]]:[scrollbar-gutter:stable_both-edges] [&>.aui-thread-root]:bg-inherit [&[data-state=open]_.aui-thread-viewport-footer]:delay-100 [&[data-state=open]_.aui-thread-viewport-footer]:duration-300 [&[data-state=open]_.aui-thread-viewport-footer]:ease-[cubic-bezier(0.32,0.72,0,1)]" + > + <Thread /> + </AssistantModalPrimitive.Content> + </AssistantModalPrimitive.Root> + ); +} + +type AssistantModalButtonProps = { "data-state"?: "open" | "closed" }; + +const AssistantModalButton = forwardRef< + HTMLButtonElement, + AssistantModalButtonProps +>(function AssistantModalButton({ "data-state": state, ...rest }, ref) { + const tooltip = state === "open" ? "Close Assistant" : "Open Assistant"; + + return ( + <TooltipIconButton + variant="default" + tooltip={tooltip} + side="left" + {...rest} + className="size-full rounded-full shadow-lg transition-transform duration-150 ease-out hover:scale-105 active:scale-96 motion-reduce:transition-none" + ref={ref} + > + <BotIcon + data-state={state} + className="absolute size-6 transition-[scale,opacity,filter] duration-200 ease-[cubic-bezier(0.2,0,0,1)] data-[state=closed]:scale-100 data-[state=closed]:opacity-100 data-[state=closed]:blur-[0px] data-[state=open]:scale-25 data-[state=open]:opacity-0 data-[state=open]:blur-[4px] motion-reduce:transition-none" + /> + <ChevronDownIcon + data-state={state} + className="absolute size-6 transition-[scale,opacity,filter] duration-200 ease-[cubic-bezier(0.2,0,0,1)] data-[state=closed]:scale-25 data-[state=closed]:opacity-0 data-[state=closed]:blur-[4px] data-[state=open]:scale-100 data-[state=open]:opacity-100 data-[state=open]:blur-[0px] motion-reduce:transition-none" + /> + <span className="sr-only">{tooltip}</span> + </TooltipIconButton> + ); +}); diff --git a/apps/docs/components/docs/samples/assistant-sidebar.tsx b/apps/docs/components/docs/samples/assistant-sidebar.tsx new file mode 100644 index 0000000..35c1798 --- /dev/null +++ b/apps/docs/components/docs/samples/assistant-sidebar.tsx @@ -0,0 +1,54 @@ +"use client"; + +import { SampleFrame } from "@/components/docs/samples/sample-frame"; + +function MainContent() { + return ( + <div className="bg-muted/30 flex flex-1 items-center justify-center p-4 max-md:border-b md:border-e"> + <div className="text-muted-foreground text-center"> + <p className="font-medium">Your App Content</p> + <p className="text-sm">Main application area</p> + </div> + </div> + ); +} + +function Sidebar() { + return ( + <div className="bg-background flex w-full flex-col md:w-64"> + <div className="flex-1 overflow-auto p-4"> + <div className="space-y-4"> + <div className="bg-muted/50 rounded-lg p-3"> + <p className="text-sm">How can I help you today?</p> + </div> + <div className="bg-primary text-primary-foreground ms-auto max-w-[80%] rounded-lg p-3"> + <p className="text-sm">Tell me about this feature</p> + </div> + <div className="bg-muted/50 rounded-lg p-3"> + <p className="text-sm"> + The sidebar provides contextual assistance... + </p> + </div> + </div> + </div> + <div className="border-t p-4"> + <div className="bg-background flex items-center rounded-lg border px-3 py-2"> + <span className="text-muted-foreground text-sm"> + Type a message... + </span> + </div> + </div> + </div> + ); +} + +export function AssistantSidebarSample() { + return ( + <SampleFrame className="bg-background h-auto overflow-hidden md:h-150"> + <div className="flex h-full flex-col md:flex-row"> + <MainContent /> + <Sidebar /> + </div> + </SampleFrame> + ); +} diff --git a/apps/docs/components/docs/samples/attachment.base.tsx b/apps/docs/components/docs/samples/attachment.base.tsx new file mode 100644 index 0000000..37a7bab --- /dev/null +++ b/apps/docs/components/docs/samples/attachment.base.tsx @@ -0,0 +1,83 @@ +"use client"; + +import { ArrowUpIcon, FileText, PlusIcon, XIcon } from "lucide-react"; +import { SampleFrame } from "./sample-frame"; +import { TooltipIconButton } from "@/components/assistant-ui/tooltip-icon-button.base"; + +type AttachmentTileStaticProps = { + name: string; + isImage?: boolean; +}; + +function AttachmentTileStatic({ name, isImage }: AttachmentTileStaticProps) { + const attachmentType = isImage ? "Image" : "Document"; + + return ( + <div className="aui-attachment-root relative"> + <div + className="aui-attachment-tile aui-attachment-tile-composer border-foreground/20 bg-muted size-14 cursor-pointer overflow-hidden rounded-[14px] border transition-opacity hover:opacity-75" + role="button" + tabIndex={0} + aria-label={`${attachmentType} attachment: ${name}`} + > + <div className="flex h-full w-full items-center justify-center"> + {isImage ? ( + <div className="h-full w-full bg-linear-to-br from-blue-100 to-blue-200 dark:from-blue-900 dark:to-blue-800" /> + ) : ( + <FileText className="aui-attachment-tile-fallback-icon text-muted-foreground size-8" /> + )} + </div> + </div> + <TooltipIconButton + tooltip="Remove file" + className="aui-attachment-tile-remove text-muted-foreground hover:[&_svg]:text-destructive absolute end-1.5 top-1.5 size-3.5 rounded-full bg-white opacity-100 shadow-sm hover:bg-white! [&_svg]:text-black" + side="top" + > + <XIcon className="aui-attachment-remove-icon size-3 dark:stroke-[2.5px]" /> + </TooltipIconButton> + </div> + ); +} + +export function AttachmentSample() { + return ( + <SampleFrame className="bg-background flex h-auto items-center justify-center p-8"> + <div className="w-full max-w-xl"> + <div className="aui-composer-root border-border bg-muted dark:border-muted-foreground/15 relative flex w-full flex-col rounded-3xl border px-1 pt-2 shadow-[0_9px_9px_0px_rgba(0,0,0,0.01),0_2px_5px_0px_rgba(0,0,0,0.06)]"> + <div className="aui-composer-attachments mb-2 flex w-full flex-row items-center gap-2 overflow-x-auto px-1.5 pt-0.5 pb-1"> + <AttachmentTileStatic name="screenshot.png" isImage /> + <AttachmentTileStatic name="document.pdf" /> + </div> + + <div className="aui-composer-input text-muted-foreground mb-1 min-h-10 w-full px-3.5 pt-1.5 pb-3"> + Send a message... + </div> + + <div className="aui-composer-action-wrapper relative mx-1 mt-2 mb-2 flex items-center justify-between"> + <TooltipIconButton + tooltip="Add Attachment" + side="bottom" + variant="ghost" + size="icon" + className="aui-composer-add-attachment hover:bg-muted-foreground/15 dark:border-muted-foreground/15 dark:hover:bg-muted-foreground/30 size-8.5 rounded-full p-1 text-xs font-semibold" + aria-label="Add Attachment" + > + <PlusIcon className="aui-attachment-add-icon size-5 stroke-[1.5px]" /> + </TooltipIconButton> + + <TooltipIconButton + tooltip="Send message" + side="bottom" + variant="default" + size="icon" + className="aui-composer-send size-8.5 rounded-full p-1" + aria-label="Send message" + > + <ArrowUpIcon className="aui-composer-send-icon size-5" /> + </TooltipIconButton> + </div> + </div> + </div> + </SampleFrame> + ); +} diff --git a/apps/docs/components/docs/samples/attachment.tsx b/apps/docs/components/docs/samples/attachment.tsx new file mode 100644 index 0000000..addc512 --- /dev/null +++ b/apps/docs/components/docs/samples/attachment.tsx @@ -0,0 +1,83 @@ +"use client"; + +import { ArrowUpIcon, FileText, PlusIcon, XIcon } from "lucide-react"; +import { SampleFrame } from "./sample-frame"; +import { TooltipIconButton } from "@/components/assistant-ui/tooltip-icon-button"; + +type AttachmentTileStaticProps = { + name: string; + isImage?: boolean; +}; + +function AttachmentTileStatic({ name, isImage }: AttachmentTileStaticProps) { + const attachmentType = isImage ? "Image" : "Document"; + + return ( + <div className="aui-attachment-root relative"> + <div + className="aui-attachment-tile aui-attachment-tile-composer border-foreground/20 bg-muted size-14 cursor-pointer overflow-hidden rounded-[14px] border transition-opacity hover:opacity-75" + role="button" + tabIndex={0} + aria-label={`${attachmentType} attachment: ${name}`} + > + <div className="flex h-full w-full items-center justify-center"> + {isImage ? ( + <div className="h-full w-full bg-linear-to-br from-blue-100 to-blue-200 dark:from-blue-900 dark:to-blue-800" /> + ) : ( + <FileText className="aui-attachment-tile-fallback-icon text-muted-foreground size-8" /> + )} + </div> + </div> + <TooltipIconButton + tooltip="Remove file" + className="aui-attachment-tile-remove text-muted-foreground hover:[&_svg]:text-destructive absolute end-1.5 top-1.5 size-3.5 rounded-full bg-white opacity-100 shadow-sm hover:bg-white! [&_svg]:text-black" + side="top" + > + <XIcon className="aui-attachment-remove-icon size-3 dark:stroke-[2.5px]" /> + </TooltipIconButton> + </div> + ); +} + +export function AttachmentSample() { + return ( + <SampleFrame className="bg-background flex h-auto items-center justify-center p-8"> + <div className="w-full max-w-xl"> + <div className="aui-composer-root border-border bg-muted dark:border-muted-foreground/15 relative flex w-full flex-col rounded-3xl border px-1 pt-2 shadow-[0_9px_9px_0px_rgba(0,0,0,0.01),0_2px_5px_0px_rgba(0,0,0,0.06)]"> + <div className="aui-composer-attachments mb-2 flex w-full flex-row items-center gap-2 overflow-x-auto px-1.5 pt-0.5 pb-1"> + <AttachmentTileStatic name="screenshot.png" isImage /> + <AttachmentTileStatic name="document.pdf" /> + </div> + + <div className="aui-composer-input text-muted-foreground mb-1 min-h-10 w-full px-3.5 pt-1.5 pb-3"> + Send a message... + </div> + + <div className="aui-composer-action-wrapper relative mx-1 mt-2 mb-2 flex items-center justify-between"> + <TooltipIconButton + tooltip="Add Attachment" + side="bottom" + variant="ghost" + size="icon" + className="aui-composer-add-attachment hover:bg-muted-foreground/15 dark:border-muted-foreground/15 dark:hover:bg-muted-foreground/30 size-8.5 rounded-full p-1 text-xs font-semibold" + aria-label="Add Attachment" + > + <PlusIcon className="aui-attachment-add-icon size-5 stroke-[1.5px]" /> + </TooltipIconButton> + + <TooltipIconButton + tooltip="Send message" + side="bottom" + variant="default" + size="icon" + className="aui-composer-send size-8.5 rounded-full p-1" + aria-label="Send message" + > + <ArrowUpIcon className="aui-composer-send-icon size-5" /> + </TooltipIconButton> + </div> + </div> + </div> + </SampleFrame> + ); +} diff --git a/apps/docs/components/docs/samples/badge.base.tsx b/apps/docs/components/docs/samples/badge.base.tsx new file mode 100644 index 0000000..c4570ca --- /dev/null +++ b/apps/docs/components/docs/samples/badge.base.tsx @@ -0,0 +1,161 @@ +"use client"; + +import { useEffect, useState } from "react"; +import { ArrowUpRight, Check, X, AlertCircle, Loader2 } from "lucide-react"; +import { Badge } from "@/components/assistant-ui/badge.base"; +import { SampleFrame } from "@/components/docs/samples/sample-frame"; +import { cn } from "@/lib/utils"; + +export function BadgeSample() { + return ( + <SampleFrame className="flex h-auto flex-wrap items-center justify-center gap-3 p-6"> + <Badge variant="outline">Outline</Badge> + <Badge variant="secondary">Secondary</Badge> + <Badge variant="muted">Muted</Badge> + <Badge variant="ghost">Ghost</Badge> + <Badge variant="info">Info</Badge> + <Badge variant="warning">Warning</Badge> + <Badge variant="success">Success</Badge> + <Badge variant="destructive">Destructive</Badge> + </SampleFrame> + ); +} + +export function BadgeSizesSample() { + return ( + <SampleFrame className="flex h-auto items-center justify-center gap-4 p-6"> + <div className="flex flex-col items-center gap-2"> + <span className="text-muted-foreground text-xs">Small</span> + <Badge size="sm">Label</Badge> + </div> + <div className="flex flex-col items-center gap-2"> + <span className="text-muted-foreground text-xs">Default</span> + <Badge size="default">Label</Badge> + </div> + <div className="flex flex-col items-center gap-2"> + <span className="text-muted-foreground text-xs">Large</span> + <Badge size="lg">Label</Badge> + </div> + </SampleFrame> + ); +} + +export function BadgeWithIconSample() { + return ( + <SampleFrame className="flex h-auto items-center justify-center gap-3 p-6"> + <Badge variant="secondary"> + <Check /> + Success + </Badge> + <Badge variant="destructive"> + <X /> + Failed + </Badge> + <Badge variant="muted"> + <AlertCircle /> + Pending + </Badge> + </SampleFrame> + ); +} + +export function BadgeAsLinkSample() { + return ( + <SampleFrame className="flex h-auto items-center justify-center gap-3 p-6"> + <Badge + variant="muted" + render={ + <a + href="https://github.com/assistant-ui/assistant-ui" + target="_blank" + rel="noopener noreferrer" + /> + } + > + GitHub + <ArrowUpRight /> + </Badge> + <Badge + variant="outline" + render={ + <a + href="https://www.npmjs.com/package/@assistant-ui/react" + target="_blank" + rel="noopener noreferrer" + /> + } + > + npm + <ArrowUpRight /> + </Badge> + </SampleFrame> + ); +} + +export function BadgeAnimatedSample() { + const [status, setStatus] = useState<"loading" | "success">("loading"); + + useEffect(() => { + const interval = setInterval(() => { + setStatus((prev) => (prev === "loading" ? "success" : "loading")); + }, 2000); + return () => clearInterval(interval); + }, []); + + return ( + <SampleFrame className="flex h-auto items-center justify-center p-6"> + <Badge + variant={status === "loading" ? "muted" : "success"} + className="overflow-hidden" + > + <span className="relative inline-flex h-4 overflow-hidden"> + <span + className={cn( + "invisible overflow-hidden transition-[max-width] duration-500", + status === "loading" ? "max-w-24" : "max-w-0", + )} + > + <span className="flex items-center gap-1 whitespace-nowrap"> + <Loader2 className="shrink-0" /> + Loading + </span> + </span> + <span + className={cn( + "invisible overflow-hidden transition-[max-width] duration-500", + status === "success" ? "max-w-40" : "max-w-0", + )} + > + <span className="flex items-center gap-1 whitespace-nowrap"> + <Check className="shrink-0" /> + Mission Success + </span> + </span> + + <span + className={cn( + "absolute inset-y-0 start-0 flex items-center gap-1 whitespace-nowrap transition-all duration-500", + status === "loading" + ? "translate-y-0 opacity-100" + : "-translate-y-4 opacity-0", + )} + > + <Loader2 className="shrink-0 animate-spin" /> + Loading + </span> + <span + className={cn( + "absolute inset-y-0 start-0 flex items-center gap-1 whitespace-nowrap transition-all duration-500", + status === "success" + ? "translate-y-0 opacity-100" + : "translate-y-4 opacity-0", + )} + > + <Check className="shrink-0" /> + Mission Success + </span> + </span> + </Badge> + </SampleFrame> + ); +} diff --git a/apps/docs/components/docs/samples/badge.tsx b/apps/docs/components/docs/samples/badge.tsx new file mode 100644 index 0000000..0edd952 --- /dev/null +++ b/apps/docs/components/docs/samples/badge.tsx @@ -0,0 +1,155 @@ +"use client"; + +import { useEffect, useState } from "react"; +import { ArrowUpRight, Check, X, AlertCircle, Loader2 } from "lucide-react"; +import { Badge } from "@/components/assistant-ui/badge"; +import { SampleFrame } from "@/components/docs/samples/sample-frame"; +import { cn } from "@/lib/utils"; + +export function BadgeSample() { + return ( + <SampleFrame className="flex h-auto flex-wrap items-center justify-center gap-3 p-6"> + <Badge variant="outline">Outline</Badge> + <Badge variant="secondary">Secondary</Badge> + <Badge variant="muted">Muted</Badge> + <Badge variant="ghost">Ghost</Badge> + <Badge variant="info">Info</Badge> + <Badge variant="warning">Warning</Badge> + <Badge variant="success">Success</Badge> + <Badge variant="destructive">Destructive</Badge> + </SampleFrame> + ); +} + +export function BadgeSizesSample() { + return ( + <SampleFrame className="flex h-auto items-center justify-center gap-4 p-6"> + <div className="flex flex-col items-center gap-2"> + <span className="text-muted-foreground text-xs">Small</span> + <Badge size="sm">Label</Badge> + </div> + <div className="flex flex-col items-center gap-2"> + <span className="text-muted-foreground text-xs">Default</span> + <Badge size="default">Label</Badge> + </div> + <div className="flex flex-col items-center gap-2"> + <span className="text-muted-foreground text-xs">Large</span> + <Badge size="lg">Label</Badge> + </div> + </SampleFrame> + ); +} + +export function BadgeWithIconSample() { + return ( + <SampleFrame className="flex h-auto items-center justify-center gap-3 p-6"> + <Badge variant="secondary"> + <Check /> + Success + </Badge> + <Badge variant="destructive"> + <X /> + Failed + </Badge> + <Badge variant="muted"> + <AlertCircle /> + Pending + </Badge> + </SampleFrame> + ); +} + +export function BadgeAsLinkSample() { + return ( + <SampleFrame className="flex h-auto items-center justify-center gap-3 p-6"> + <Badge asChild variant="muted"> + <a + href="https://github.com/assistant-ui/assistant-ui" + target="_blank" + rel="noopener noreferrer" + > + GitHub + <ArrowUpRight /> + </a> + </Badge> + <Badge asChild variant="outline"> + <a + href="https://www.npmjs.com/package/@assistant-ui/react" + target="_blank" + rel="noopener noreferrer" + > + npm + <ArrowUpRight /> + </a> + </Badge> + </SampleFrame> + ); +} + +export function BadgeAnimatedSample() { + const [status, setStatus] = useState<"loading" | "success">("loading"); + + useEffect(() => { + const interval = setInterval(() => { + setStatus((prev) => (prev === "loading" ? "success" : "loading")); + }, 2000); + return () => clearInterval(interval); + }, []); + + return ( + <SampleFrame className="flex h-auto items-center justify-center p-6"> + <Badge + variant={status === "loading" ? "muted" : "success"} + className="overflow-hidden" + > + <span className="relative inline-flex h-4 overflow-hidden"> + <span + className={cn( + "invisible overflow-hidden transition-[max-width] duration-500", + status === "loading" ? "max-w-24" : "max-w-0", + )} + > + <span className="flex items-center gap-1 whitespace-nowrap"> + <Loader2 className="shrink-0" /> + Loading + </span> + </span> + <span + className={cn( + "invisible overflow-hidden transition-[max-width] duration-500", + status === "success" ? "max-w-40" : "max-w-0", + )} + > + <span className="flex items-center gap-1 whitespace-nowrap"> + <Check className="shrink-0" /> + Mission Success + </span> + </span> + + <span + className={cn( + "absolute inset-y-0 start-0 flex items-center gap-1 whitespace-nowrap transition-all duration-500", + status === "loading" + ? "translate-y-0 opacity-100" + : "-translate-y-4 opacity-0", + )} + > + <Loader2 className="shrink-0 animate-spin" /> + Loading + </span> + <span + className={cn( + "absolute inset-y-0 start-0 flex items-center gap-1 whitespace-nowrap transition-all duration-500", + status === "success" + ? "translate-y-0 opacity-100" + : "translate-y-4 opacity-0", + )} + > + <Check className="shrink-0" /> + Mission Success + </span> + </span> + </Badge> + </SampleFrame> + ); +} diff --git a/apps/docs/components/docs/samples/branch-picker-primitive.tsx b/apps/docs/components/docs/samples/branch-picker-primitive.tsx new file mode 100644 index 0000000..208a408 --- /dev/null +++ b/apps/docs/components/docs/samples/branch-picker-primitive.tsx @@ -0,0 +1,93 @@ +"use client"; + +import { useEffect, useRef } from "react"; +import { + AssistantRuntimeProvider, + BranchPickerPrimitive, + MessagePrimitive, + ThreadPrimitive, + useLocalRuntime, + useMessageRuntime, + type ChatModelAdapter, + type ThreadMessageLike, +} from "@assistant-ui/react"; +import { ChevronLeftIcon, ChevronRightIcon } from "lucide-react"; + +const responses = [ + "assistant-ui is a set of React components for building AI chat interfaces. It provides unstyled primitives that handle state management, streaming, and accessibility — you bring the design.", + "assistant-ui is an open-source library of headless, composable React primitives for AI chat UIs. Style them with any CSS framework — Tailwind, CSS modules, or plain CSS.", +]; + +const adapter: ChatModelAdapter = { + async *run() { + yield { content: [{ type: "text", text: responses[1]! }] }; + }, +}; + +const initialMessages: ThreadMessageLike[] = [ + { role: "user", content: "What is assistant-ui?" }, + { role: "assistant", content: responses[0]! }, +]; + +export function BranchPickerPrimitiveSample() { + const runtime = useLocalRuntime(adapter, { initialMessages }); + + return ( + <div className="not-prose border-border/50 bg-muted/40 flex items-end rounded-xl border p-6"> + <div className="mx-auto flex w-full max-w-lg flex-col gap-3"> + <AssistantRuntimeProvider runtime={runtime}> + <ThreadPrimitive.Messages + components={{ UserMessage, AssistantMessage }} + /> + </AssistantRuntimeProvider> + </div> + </div> + ); +} + +function UserMessage() { + return ( + <MessagePrimitive.Root className="flex justify-end"> + <div className="bg-primary text-primary-foreground max-w-[80%] rounded-2xl px-4 py-2.5 text-sm"> + <MessagePrimitive.Parts /> + </div> + </MessagePrimitive.Root> + ); +} + +function AssistantMessage() { + return ( + <MessagePrimitive.Root className="flex flex-col items-start gap-1"> + <div className="bg-muted max-w-[80%] rounded-2xl px-4 py-2.5 text-sm"> + <MessagePrimitive.Parts /> + </div> + <BranchSeeder /> + <BranchPickerPrimitive.Root className="text-muted-foreground inline-flex items-center gap-0.5 text-xs"> + <BranchPickerPrimitive.Previous className="hover:bg-muted flex size-6 items-center justify-center rounded-md disabled:opacity-30"> + <ChevronLeftIcon className="size-3.5" /> + </BranchPickerPrimitive.Previous> + <span className="tabular-nums"> + <BranchPickerPrimitive.Number /> / <BranchPickerPrimitive.Count /> + </span> + <BranchPickerPrimitive.Next className="hover:bg-muted flex size-6 items-center justify-center rounded-md disabled:opacity-30"> + <ChevronRightIcon className="size-3.5" /> + </BranchPickerPrimitive.Next> + </BranchPickerPrimitive.Root> + </MessagePrimitive.Root> + ); +} + +function BranchSeeder() { + const messageRuntime = useMessageRuntime(); + const seeded = useRef(false); + + useEffect(() => { + if (seeded.current) return; + const state = messageRuntime.getState(); + if (state.branchCount > 1) return; + seeded.current = true; + messageRuntime.reload(); + }, [messageRuntime]); + + return null; +} diff --git a/apps/docs/components/docs/samples/branching.tsx b/apps/docs/components/docs/samples/branching.tsx new file mode 100644 index 0000000..b7502c8 --- /dev/null +++ b/apps/docs/components/docs/samples/branching.tsx @@ -0,0 +1,12 @@ +"use client"; + +import { Thread } from "@/components/assistant-ui/thread"; +import { SampleFrame } from "@/components/docs/samples/sample-frame"; + +export const BranchingSample = () => { + return ( + <SampleFrame className="bg-muted/40 overflow-hidden"> + <Thread /> + </SampleFrame> + ); +}; diff --git a/apps/docs/components/docs/samples/chain-of-thought-primitive.base.tsx b/apps/docs/components/docs/samples/chain-of-thought-primitive.base.tsx new file mode 100644 index 0000000..3c03d9a --- /dev/null +++ b/apps/docs/components/docs/samples/chain-of-thought-primitive.base.tsx @@ -0,0 +1,213 @@ +"use client"; + +import { Collapsible } from "@base-ui/react/collapsible"; +import { + groupPartByType, + MessagePrimitive, + MessagePartPrimitive, + ThreadPrimitive, + type ThreadMessageLike, +} from "@assistant-ui/react"; +import { + BrainIcon, + CheckCircle2Icon, + ChevronDownIcon, + ChevronRightIcon, + WrenchIcon, +} from "lucide-react"; +import { useState, type PropsWithChildren } from "react"; +import { SampleRuntimeProvider } from "./sample-runtime-provider"; + +const sampleMessages: ThreadMessageLike[] = [ + { + role: "assistant", + content: [ + { + type: "reasoning", + text: "The user wants the current weather in Tokyo. I should call the weather tool first.", + }, + { + type: "tool-call", + toolCallId: "tool-1", + toolName: "get_weather", + args: { city: "Tokyo" }, + argsText: '{ "city": "Tokyo" }', + result: { temp: "22C", condition: "Partly cloudy" }, + }, + { + type: "reasoning", + text: "Got the weather result. I can now answer the user directly.", + }, + { + type: "text", + text: "It's currently 22C and partly cloudy in Tokyo.", + }, + ], + status: { type: "complete", reason: "stop" }, + }, +]; + +function ReasoningBlock({ text }: { text: string }) { + return ( + <div className="flex gap-3 px-4 py-2"> + <div className="bg-muted flex size-5 shrink-0 items-center justify-center rounded-full"> + <BrainIcon className="text-muted-foreground size-3" /> + </div> + <p className="text-muted-foreground text-sm leading-relaxed italic"> + {text} + </p> + </div> + ); +} + +function ToolCallBlock({ + toolName, + argsText, + result, +}: { + toolName: string; + argsText: string; + result?: unknown; +}) { + return ( + <div className="flex gap-3 px-4 py-2"> + <div className="flex size-5 shrink-0 items-center justify-center rounded-full bg-emerald-500/15"> + <CheckCircle2Icon className="size-3 text-emerald-600 dark:text-emerald-400" /> + </div> + <Collapsible.Root className="min-w-0 flex-1"> + <Collapsible.Trigger className="group flex w-full items-center gap-1.5 text-start text-sm"> + <WrenchIcon className="text-muted-foreground size-3" /> + <span className="font-medium">{toolName}</span> + <ChevronRightIcon className="text-muted-foreground ms-auto size-3.5 transition-transform duration-150 group-data-panel-open:rotate-90 rtl:rotate-180 rtl:group-data-panel-open:rotate-90" /> + </Collapsible.Trigger> + <Collapsible.Panel> + <div className="bg-muted/40 mt-2 overflow-hidden rounded-md border"> + <div className="px-3 py-2"> + <p className="text-muted-foreground mb-1 text-[10px] font-medium tracking-wider uppercase"> + Args + </p> + <pre className="font-mono text-xs leading-relaxed"> + {argsText} + </pre> + </div> + {result !== undefined && ( + <div className="border-t px-3 py-2"> + <p className="text-muted-foreground mb-1 text-[10px] font-medium tracking-wider uppercase"> + Result + </p> + <pre className="font-mono text-xs leading-relaxed"> + {JSON.stringify(result, null, 2)} + </pre> + </div> + )} + </div> + </Collapsible.Panel> + </Collapsible.Root> + </div> + ); +} + +function AssistantChainOfThought({ children }: PropsWithChildren) { + const [open, setOpen] = useState(true); + + return ( + <div className="border-border/80 bg-background/90 overflow-hidden rounded-xl border shadow-sm"> + <button + type="button" + className="hover:bg-muted/50 flex w-full cursor-pointer items-center gap-2 px-4 py-2.5 text-sm font-medium transition-colors" + onClick={() => setOpen((value) => !value)} + > + {open ? ( + <ChevronDownIcon className="size-4 shrink-0" /> + ) : ( + <ChevronRightIcon className="size-4 shrink-0" /> + )} + Thinking + </button> + {open && <div className="border-t pb-3">{children}</div>} + </div> + ); +} + +function AssistantMessageText() { + return ( + <div className="bg-muted max-w-[80%] rounded-2xl px-4 py-2.5 text-sm"> + <p> + <MessagePartPrimitive.Text /> + </p> + </div> + ); +} + +function UserMessage() { + return ( + <MessagePrimitive.Root className="flex justify-end"> + <div className="bg-primary text-primary-foreground max-w-[80%] rounded-2xl px-4 py-2.5 text-sm"> + <MessagePrimitive.Parts /> + </div> + </MessagePrimitive.Root> + ); +} + +function AssistantMessage() { + return ( + <MessagePrimitive.Root className="flex justify-start gap-3"> + <div className="bg-primary/10 text-primary flex size-8 shrink-0 items-center justify-center rounded-full text-xs font-medium"> + AI + </div> + <div className="min-w-0 flex-1"> + <div className="space-y-2"> + <MessagePrimitive.GroupedParts + groupBy={groupPartByType({ + reasoning: ["group-chainOfThought", "group-reasoning"], + "tool-call": ["group-chainOfThought", "group-tool"], + "standalone-tool-call": [], + })} + > + {({ part, children }) => { + switch (part.type) { + case "group-chainOfThought": + return ( + <AssistantChainOfThought> + {children} + </AssistantChainOfThought> + ); + case "group-reasoning": + case "group-tool": + return <>{children}</>; + case "text": + return <AssistantMessageText />; + case "reasoning": + return <ReasoningBlock text={part.text} />; + case "tool-call": + return ( + <ToolCallBlock + toolName={part.toolName} + argsText={part.argsText} + result={part.result} + /> + ); + default: + return null; + } + }} + </MessagePrimitive.GroupedParts> + </div> + </div> + </MessagePrimitive.Root> + ); +} + +export function ChainOfThoughtPrimitiveSample() { + return ( + <div className="not-prose border-border/50 bg-muted/40 flex items-center rounded-xl border p-8"> + <div className="mx-auto w-full max-w-lg"> + <SampleRuntimeProvider messages={sampleMessages}> + <ThreadPrimitive.Messages + components={{ UserMessage, AssistantMessage }} + /> + </SampleRuntimeProvider> + </div> + </div> + ); +} diff --git a/apps/docs/components/docs/samples/chain-of-thought-primitive.tsx b/apps/docs/components/docs/samples/chain-of-thought-primitive.tsx new file mode 100644 index 0000000..e30ed10 --- /dev/null +++ b/apps/docs/components/docs/samples/chain-of-thought-primitive.tsx @@ -0,0 +1,213 @@ +"use client"; + +import { Collapsible } from "radix-ui"; +import { + groupPartByType, + MessagePrimitive, + MessagePartPrimitive, + ThreadPrimitive, + type ThreadMessageLike, +} from "@assistant-ui/react"; +import { + BrainIcon, + CheckCircle2Icon, + ChevronDownIcon, + ChevronRightIcon, + WrenchIcon, +} from "lucide-react"; +import { useState, type PropsWithChildren } from "react"; +import { SampleRuntimeProvider } from "./sample-runtime-provider"; + +const sampleMessages: ThreadMessageLike[] = [ + { + role: "assistant", + content: [ + { + type: "reasoning", + text: "The user wants the current weather in Tokyo. I should call the weather tool first.", + }, + { + type: "tool-call", + toolCallId: "tool-1", + toolName: "get_weather", + args: { city: "Tokyo" }, + argsText: '{ "city": "Tokyo" }', + result: { temp: "22C", condition: "Partly cloudy" }, + }, + { + type: "reasoning", + text: "Got the weather result. I can now answer the user directly.", + }, + { + type: "text", + text: "It's currently 22C and partly cloudy in Tokyo.", + }, + ], + status: { type: "complete", reason: "stop" }, + }, +]; + +function ReasoningBlock({ text }: { text: string }) { + return ( + <div className="flex gap-3 px-4 py-2"> + <div className="bg-muted flex size-5 shrink-0 items-center justify-center rounded-full"> + <BrainIcon className="text-muted-foreground size-3" /> + </div> + <p className="text-muted-foreground text-sm leading-relaxed italic"> + {text} + </p> + </div> + ); +} + +function ToolCallBlock({ + toolName, + argsText, + result, +}: { + toolName: string; + argsText: string; + result?: unknown; +}) { + return ( + <div className="flex gap-3 px-4 py-2"> + <div className="flex size-5 shrink-0 items-center justify-center rounded-full bg-emerald-500/15"> + <CheckCircle2Icon className="size-3 text-emerald-600 dark:text-emerald-400" /> + </div> + <Collapsible.Root className="min-w-0 flex-1"> + <Collapsible.Trigger className="group flex w-full items-center gap-1.5 text-start text-sm"> + <WrenchIcon className="text-muted-foreground size-3" /> + <span className="font-medium">{toolName}</span> + <ChevronRightIcon className="text-muted-foreground ms-auto size-3.5 transition-transform duration-150 group-data-[state=open]:rotate-90 rtl:group-data-[state=closed]:rotate-180" /> + </Collapsible.Trigger> + <Collapsible.Content> + <div className="bg-muted/40 mt-2 overflow-hidden rounded-md border"> + <div className="px-3 py-2"> + <p className="text-muted-foreground mb-1 text-[10px] font-medium tracking-wider uppercase"> + Args + </p> + <pre className="font-mono text-xs leading-relaxed"> + {argsText} + </pre> + </div> + {result !== undefined && ( + <div className="border-t px-3 py-2"> + <p className="text-muted-foreground mb-1 text-[10px] font-medium tracking-wider uppercase"> + Result + </p> + <pre className="font-mono text-xs leading-relaxed"> + {JSON.stringify(result, null, 2)} + </pre> + </div> + )} + </div> + </Collapsible.Content> + </Collapsible.Root> + </div> + ); +} + +function AssistantChainOfThought({ children }: PropsWithChildren) { + const [open, setOpen] = useState(true); + + return ( + <div className="border-border/80 bg-background/90 overflow-hidden rounded-xl border shadow-sm"> + <button + type="button" + className="hover:bg-muted/50 flex w-full cursor-pointer items-center gap-2 px-4 py-2.5 text-sm font-medium transition-colors" + onClick={() => setOpen((value) => !value)} + > + {open ? ( + <ChevronDownIcon className="size-4 shrink-0" /> + ) : ( + <ChevronRightIcon className="size-4 shrink-0" /> + )} + Thinking + </button> + {open && <div className="border-t pb-3">{children}</div>} + </div> + ); +} + +function AssistantMessageText() { + return ( + <div className="bg-muted max-w-[80%] rounded-2xl px-4 py-2.5 text-sm"> + <p> + <MessagePartPrimitive.Text /> + </p> + </div> + ); +} + +function UserMessage() { + return ( + <MessagePrimitive.Root className="flex justify-end"> + <div className="bg-primary text-primary-foreground max-w-[80%] rounded-2xl px-4 py-2.5 text-sm"> + <MessagePrimitive.Parts /> + </div> + </MessagePrimitive.Root> + ); +} + +function AssistantMessage() { + return ( + <MessagePrimitive.Root className="flex justify-start gap-3"> + <div className="bg-primary/10 text-primary flex size-8 shrink-0 items-center justify-center rounded-full text-xs font-medium"> + AI + </div> + <div className="min-w-0 flex-1"> + <div className="space-y-2"> + <MessagePrimitive.GroupedParts + groupBy={groupPartByType({ + reasoning: ["group-chainOfThought", "group-reasoning"], + "tool-call": ["group-chainOfThought", "group-tool"], + "standalone-tool-call": [], + })} + > + {({ part, children }) => { + switch (part.type) { + case "group-chainOfThought": + return ( + <AssistantChainOfThought> + {children} + </AssistantChainOfThought> + ); + case "group-reasoning": + case "group-tool": + return <>{children}</>; + case "text": + return <AssistantMessageText />; + case "reasoning": + return <ReasoningBlock text={part.text} />; + case "tool-call": + return ( + <ToolCallBlock + toolName={part.toolName} + argsText={part.argsText} + result={part.result} + /> + ); + default: + return null; + } + }} + </MessagePrimitive.GroupedParts> + </div> + </div> + </MessagePrimitive.Root> + ); +} + +export function ChainOfThoughtPrimitiveSample() { + return ( + <div className="not-prose border-border/50 bg-muted/40 flex items-center rounded-xl border p-8"> + <div className="mx-auto w-full max-w-lg"> + <SampleRuntimeProvider messages={sampleMessages}> + <ThreadPrimitive.Messages + components={{ UserMessage, AssistantMessage }} + /> + </SampleRuntimeProvider> + </div> + </div> + ); +} diff --git a/apps/docs/components/docs/samples/composer-primitive.tsx b/apps/docs/components/docs/samples/composer-primitive.tsx new file mode 100644 index 0000000..4372fae --- /dev/null +++ b/apps/docs/components/docs/samples/composer-primitive.tsx @@ -0,0 +1,28 @@ +"use client"; + +import { ComposerPrimitive } from "@assistant-ui/react"; +import { ArrowUpIcon } from "lucide-react"; +import { SampleRuntimeProvider } from "./sample-runtime-provider"; + +export function ComposerPrimitiveSample() { + return ( + <SampleRuntimeProvider messages={[]}> + <div className="not-prose border-border/50 bg-muted/40 flex items-end rounded-xl border p-6"> + <div className="mx-auto w-full max-w-lg"> + <ComposerPrimitive.Root className="border-border bg-muted dark:border-muted-foreground/15 relative flex w-full flex-col rounded-3xl border shadow-[0_9px_9px_0px_rgba(0,0,0,0.01),0_2px_5px_0px_rgba(0,0,0,0.06)]"> + <ComposerPrimitive.Input + placeholder="Ask anything..." + className="field-sizing-content min-h-10 w-full resize-none bg-transparent px-5 pt-4 pb-3 text-sm leading-relaxed focus:outline-none" + rows={1} + /> + <div className="flex items-center justify-end px-3 pb-3"> + <ComposerPrimitive.Send className="bg-primary text-primary-foreground flex size-8 shrink-0 items-center justify-center rounded-full transition-opacity disabled:opacity-30"> + <ArrowUpIcon className="size-4" /> + </ComposerPrimitive.Send> + </div> + </ComposerPrimitive.Root> + </div> + </div> + </SampleRuntimeProvider> + ); +} diff --git a/apps/docs/components/docs/samples/composer-trigger-popover.tsx b/apps/docs/components/docs/samples/composer-trigger-popover.tsx new file mode 100644 index 0000000..16ffe20 --- /dev/null +++ b/apps/docs/components/docs/samples/composer-trigger-popover.tsx @@ -0,0 +1,105 @@ +"use client"; + +import { + AtSignIcon, + ChevronLeftIcon, + ChevronRightIcon, + FileTextIcon, + GlobeIcon, + LanguagesIcon, + SlashIcon, + WrenchIcon, +} from "lucide-react"; +import { SampleFrame } from "@/components/docs/samples/sample-frame"; + +export function ComposerTriggerPopoverSample() { + return ( + <SampleFrame className="flex h-auto flex-wrap items-start justify-center gap-10 p-8"> + <div className="flex flex-col items-center gap-3"> + <span className="text-muted-foreground flex items-center gap-1.5 text-xs"> + <AtSignIcon className="size-3.5" /> + Mention — directive behavior + </span> + <div className="bg-popover text-popover-foreground w-64 overflow-hidden rounded-xl border shadow-lg"> + <div className="flex flex-col py-1"> + <button + type="button" + data-highlighted="" + className="data-[highlighted]:bg-accent flex cursor-pointer items-center justify-between gap-2 px-3 py-2 text-sm transition-colors outline-none" + > + <span className="flex items-center gap-2"> + <WrenchIcon className="text-muted-foreground size-4" /> + Tools + </span> + <ChevronRightIcon className="text-muted-foreground size-4" /> + </button> + <button + type="button" + className="hover:bg-accent flex cursor-pointer items-center justify-between gap-2 px-3 py-2 text-sm transition-colors outline-none" + > + <span className="flex items-center gap-2"> + <WrenchIcon className="text-muted-foreground size-4" /> + Users + </span> + <ChevronRightIcon className="text-muted-foreground size-4" /> + </button> + </div> + </div> + </div> + + <div className="flex flex-col items-center gap-3"> + <span className="text-muted-foreground flex items-center gap-1.5 text-xs"> + <SlashIcon className="size-3.5" /> + Slash — action behavior + </span> + <div className="bg-popover text-popover-foreground w-64 overflow-hidden rounded-xl border shadow-lg"> + <div className="flex flex-col"> + <div className="text-muted-foreground flex items-center gap-1.5 border-b px-3 py-2 text-xs tracking-wide uppercase"> + <ChevronLeftIcon className="size-3.5" /> + Back + </div> + <div className="py-1"> + <button + type="button" + data-highlighted="" + className="data-[highlighted]:bg-accent flex w-full cursor-pointer flex-col items-start gap-0.5 px-3 py-2 text-start transition-colors outline-none" + > + <span className="flex items-center gap-2 text-sm font-medium"> + <FileTextIcon className="text-primary size-3.5" /> + /summarize + </span> + <span className="text-muted-foreground ms-5.5 text-xs leading-tight"> + Summarize the conversation + </span> + </button> + <button + type="button" + className="hover:bg-accent flex w-full cursor-pointer flex-col items-start gap-0.5 px-3 py-2 text-start transition-colors outline-none" + > + <span className="flex items-center gap-2 text-sm font-medium"> + <LanguagesIcon className="text-primary size-3.5" /> + /translate + </span> + <span className="text-muted-foreground ms-5.5 text-xs leading-tight"> + Translate to another language + </span> + </button> + <button + type="button" + className="hover:bg-accent flex w-full cursor-pointer flex-col items-start gap-0.5 px-3 py-2 text-start transition-colors outline-none" + > + <span className="flex items-center gap-2 text-sm font-medium"> + <GlobeIcon className="text-primary size-3.5" /> + /search + </span> + <span className="text-muted-foreground ms-5.5 text-xs leading-tight"> + Search the web + </span> + </button> + </div> + </div> + </div> + </div> + </SampleFrame> + ); +} diff --git a/apps/docs/components/docs/samples/context-display.tsx b/apps/docs/components/docs/samples/context-display.tsx new file mode 100644 index 0000000..9eab8fa --- /dev/null +++ b/apps/docs/components/docs/samples/context-display.tsx @@ -0,0 +1,172 @@ +"use client"; + +import { cn } from "@/lib/utils"; +import { SampleFrame } from "@/components/docs/samples/sample-frame"; + +const RING_SIZE = 24; +const RING_STROKE = 3; +const RING_RADIUS = (RING_SIZE - RING_STROKE) / 2; +const RING_CIRCUMFERENCE = 2 * Math.PI * RING_RADIUS; +const MODEL_CONTEXT_WINDOW = 128_000; + +const USAGE_LEVELS = [ + { label: "Low", percent: 42 }, + { label: "Warning", percent: 72 }, + { label: "Critical", percent: 91 }, +]; + +const getStrokeColor = (percent: number): string => { + if (percent > 85) return "stroke-red-500"; + if (percent >= 65) return "stroke-amber-500"; + return "stroke-emerald-500"; +}; + +const getBarColor = (percent: number): string => { + if (percent > 85) return "bg-red-500"; + if (percent >= 65) return "bg-amber-500"; + return "bg-emerald-500"; +}; + +const formatTokenCount = (tokens: number): string => { + if (tokens >= 1_000_000) return `${(tokens / 1_000_000).toFixed(1)}M`; + if (tokens >= 1_000) return `${(tokens / 1_000).toFixed(1)}k`; + return `${tokens}`; +}; + +export function ContextDisplaySample() { + return ( + <SampleFrame className="flex h-auto flex-wrap items-start justify-center gap-10 p-8"> + {/* Ring variants */} + <div className="flex flex-col items-center gap-3"> + <span className="text-muted-foreground text-xs">Ring</span> + <div className="flex flex-col gap-2"> + {USAGE_LEVELS.map(({ label, percent }) => ( + <div key={label} className="flex items-center gap-2"> + <div className="inline-flex items-center rounded-md p-1 transition-colors"> + <svg + aria-hidden="true" + width={RING_SIZE} + height={RING_SIZE} + viewBox={`0 0 ${RING_SIZE} ${RING_SIZE}`} + className="-rotate-90" + > + <circle + cx={RING_SIZE / 2} + cy={RING_SIZE / 2} + r={RING_RADIUS} + fill="none" + strokeWidth={RING_STROKE} + className="stroke-muted" + /> + <circle + cx={RING_SIZE / 2} + cy={RING_SIZE / 2} + r={RING_RADIUS} + fill="none" + strokeWidth={RING_STROKE} + strokeLinecap="round" + strokeDasharray={RING_CIRCUMFERENCE} + strokeDashoffset={ + RING_CIRCUMFERENCE - (percent / 100) * RING_CIRCUMFERENCE + } + className={getStrokeColor(percent)} + /> + </svg> + </div> + <span className="text-muted-foreground text-xs tabular-nums"> + {label} ({percent}%) + </span> + </div> + ))} + </div> + </div> + + {/* Bar variants */} + <div className="flex flex-col items-center gap-3"> + <span className="text-muted-foreground text-xs">Bar</span> + <div className="flex flex-col gap-2"> + {USAGE_LEVELS.map(({ label, percent }) => { + const totalTokens = Math.round( + (percent / 100) * MODEL_CONTEXT_WINDOW, + ); + return ( + <div + key={label} + className="inline-flex items-center rounded-md px-2 py-1 transition-colors" + > + <div className="flex items-center gap-2"> + <div className="bg-muted h-1.5 w-16 overflow-hidden rounded-full"> + <div + className={cn( + "h-full rounded-full", + getBarColor(percent), + )} + style={{ width: `${percent}%` }} + /> + </div> + <span className="text-muted-foreground text-[10px] tabular-nums"> + {formatTokenCount(totalTokens)} ({percent}%) + </span> + </div> + </div> + ); + })} + </div> + </div> + + {/* Text variants */} + <div className="flex flex-col items-center gap-3"> + <span className="text-muted-foreground text-xs">Text</span> + <div className="flex flex-col gap-2"> + {USAGE_LEVELS.map(({ label, percent }) => { + const totalTokens = Math.round( + (percent / 100) * MODEL_CONTEXT_WINDOW, + ); + return ( + <div + key={label} + className="text-muted-foreground hover:bg-accent hover:text-accent-foreground inline-flex items-center rounded-md px-2 py-1 font-mono text-xs tabular-nums transition-colors" + > + {formatTokenCount(totalTokens)} /{" "} + {formatTokenCount(MODEL_CONTEXT_WINDOW)} + </div> + ); + })} + </div> + </div> + + {/* Tooltip popover example */} + <div className="flex flex-col items-center gap-3"> + <span className="text-muted-foreground text-xs">On hover</span> + <div className="bg-popover text-popover-foreground grid min-w-40 gap-1.5 rounded-lg border px-3 py-2 text-xs shadow-md"> + <div className="flex items-center justify-between gap-4"> + <span className="text-muted-foreground">Usage</span> + <span className="font-mono tabular-nums">72%</span> + </div> + <div className="flex items-center justify-between gap-4"> + <span className="text-muted-foreground">Input</span> + <span className="font-mono tabular-nums">72.3k</span> + </div> + <div className="flex items-center justify-between gap-4"> + <span className="text-muted-foreground">Cached</span> + <span className="font-mono tabular-nums">41.2k</span> + </div> + <div className="flex items-center justify-between gap-4"> + <span className="text-muted-foreground">Output</span> + <span className="font-mono tabular-nums">12.7k</span> + </div> + <div className="flex items-center justify-between gap-4"> + <span className="text-muted-foreground">Reasoning</span> + <span className="font-mono tabular-nums">8.4k</span> + </div> + <div className="mt-0.5 border-t pt-1.5"> + <div className="flex items-center justify-between gap-4"> + <span className="text-muted-foreground">Total</span> + <span className="font-mono tabular-nums">92.2k / 128.0k</span> + </div> + </div> + </div> + </div> + </SampleFrame> + ); +} diff --git a/apps/docs/components/docs/samples/dictation.tsx b/apps/docs/components/docs/samples/dictation.tsx new file mode 100644 index 0000000..babeba6 --- /dev/null +++ b/apps/docs/components/docs/samples/dictation.tsx @@ -0,0 +1,395 @@ +"use client"; + +import { + ComposerAddAttachment, + ComposerAttachments, + UserMessageAttachments, +} from "@/components/assistant-ui/attachment"; +import { + ActionBarPrimitive, + AuiIf, + BranchPickerPrimitive, + ComposerPrimitive, + ErrorPrimitive, + MessagePrimitive, + ThreadPrimitive, +} from "@assistant-ui/react"; +import { SampleFrame } from "./sample-frame"; +import { TooltipIconButton } from "@/components/assistant-ui/tooltip-icon-button"; +import type { FC } from "react"; +import { + ArrowDownIcon, + CopyIcon, + CheckIcon, + PencilIcon, + RefreshCwIcon, + ArrowUpIcon, + ChevronLeftIcon, + ChevronRightIcon, + Square, + MicIcon, +} from "lucide-react"; +import { MarkdownText } from "@/components/assistant-ui/markdown-text"; +import { Button } from "@/components/ui/button"; +import { cn } from "@/lib/utils"; +import { ToolFallback } from "@/components/assistant-ui/tool-fallback"; + +export const DictationSample = () => { + return ( + <SampleFrame className="bg-muted/40 overflow-hidden"> + <Thread /> + </SampleFrame> + ); +}; + +export const Thread: FC = () => { + return ( + <ThreadPrimitive.Root + className="aui-root aui-thread-root bg-background @container flex h-full flex-col" + style={{ + ["--thread-max-width" as string]: "44rem", + }} + > + <ThreadPrimitive.Viewport className="aui-thread-viewport relative flex flex-1 flex-col overflow-x-auto overflow-y-scroll px-4"> + <ThreadWelcome /> + + <ThreadPrimitive.Messages> + {({ message }) => { + if (message.composer.isEditing) return <EditComposer />; + if (message.role === "user") return <UserMessage />; + return <AssistantMessage />; + }} + </ThreadPrimitive.Messages> + <div className="aui-thread-viewport-spacer min-h-8 grow" /> + <Composer /> + </ThreadPrimitive.Viewport> + </ThreadPrimitive.Root> + ); +}; + +const ThreadScrollToBottom: FC = () => { + return ( + <ThreadPrimitive.ScrollToBottom asChild> + <TooltipIconButton + tooltip="Scroll to bottom" + variant="outline" + className="aui-thread-scroll-to-bottom dark:bg-background dark:hover:bg-accent absolute -top-12 z-10 self-center rounded-full p-4 disabled:invisible" + > + <ArrowDownIcon /> + </TooltipIconButton> + </ThreadPrimitive.ScrollToBottom> + ); +}; + +const ThreadWelcome: FC = () => { + return ( + <AuiIf condition={(s) => s.thread.isEmpty}> + <div className="aui-thread-welcome-root mx-auto mb-16 flex w-full max-w-[var(--thread-max-width)] flex-grow flex-col"> + <div className="aui-thread-welcome-center flex w-full flex-grow flex-col items-center justify-center"> + <div className="aui-thread-welcome-message flex size-full flex-col justify-center px-8 md:mt-20"> + <div className="aui-thread-welcome-message-motion-1 text-2xl font-semibold"> + Voice Input Demo + </div> + <div className="aui-thread-welcome-message-motion-2 text-muted-foreground/65 text-2xl"> + Click the mic button to speak + </div> + </div> + </div> + </div> + </AuiIf> + ); +}; + +const ThreadWelcomeSuggestions: FC = () => { + return ( + <div className="aui-thread-welcome-suggestions grid w-full gap-2 @md:grid-cols-2"> + {[ + { + title: "What's the weather", + label: "in San Francisco?", + action: "What's the weather in San Francisco?", + }, + { + title: "Help me write an essay", + label: `about AI chat applications`, + action: `Help me write an essay about AI chat applications`, + }, + ].map((suggestedAction, index) => ( + <div + key={`suggested-action-${suggestedAction.title}-${index}`} + className="aui-thread-welcome-suggestion-display nth-[n+3]:hidden @md:nth-[n+3]:block" + > + <ThreadPrimitive.Suggestion + prompt={suggestedAction.action} + clearComposer + send + asChild + > + <Button + variant="ghost" + className="aui-thread-welcome-suggestion dark:hover:bg-accent/60 h-auto w-full flex-1 flex-wrap items-start justify-start gap-1 rounded-3xl border px-5 py-4 text-start text-sm @md:flex-col" + aria-label={suggestedAction.action} + > + <span className="aui-thread-welcome-suggestion-text-1 font-medium"> + {suggestedAction.title} + </span> + <span className="aui-thread-welcome-suggestion-text-2 text-muted-foreground"> + {suggestedAction.label} + </span> + </Button> + </ThreadPrimitive.Suggestion> + </div> + ))} + </div> + ); +}; + +const Composer: FC = () => { + return ( + <div className="aui-composer-wrapper bg-background sticky bottom-0 mx-auto flex w-full max-w-(--thread-max-width) flex-col gap-4 overflow-visible rounded-t-3xl pb-4 md:pb-6"> + <ThreadScrollToBottom /> + <AuiIf condition={(s) => s.thread.isEmpty}> + <ThreadWelcomeSuggestions /> + </AuiIf> + <ComposerPrimitive.Root className="aui-composer-root border-border bg-muted dark:border-muted-foreground/15 relative flex w-full flex-col rounded-3xl border px-1 pt-2 shadow-[0_9px_9px_0px_rgba(0,0,0,0.01),0_2px_5px_0px_rgba(0,0,0,0.06)]"> + <ComposerAttachments /> + <ComposerPrimitive.Input + placeholder="Send a message..." + className="aui-composer-input placeholder:text-muted-foreground focus:outline-primary mb-1 max-h-32 min-h-16 w-full resize-none bg-transparent px-3.5 pt-1.5 pb-3 text-base outline-none" + rows={1} + autoFocus + aria-label="Message input" + /> + <ComposerAction /> + </ComposerPrimitive.Root> + </div> + ); +}; + +const ComposerAction: FC = () => { + return ( + <div className="aui-composer-action-wrapper relative mx-1 mt-2 mb-2 flex items-center justify-between"> + <div className="flex items-center gap-1"> + <ComposerAddAttachment /> + + {/* Dictation Button - Show when not dictation */} + <ComposerPrimitive.If dictation={false}> + <ComposerPrimitive.Dictate asChild> + <TooltipIconButton + tooltip="Voice input" + side="top" + variant="ghost" + className="aui-composer-dictate size-[34px] rounded-full p-1" + aria-label="Start voice input" + > + <MicIcon className="size-5" /> + </TooltipIconButton> + </ComposerPrimitive.Dictate> + </ComposerPrimitive.If> + + {/* Stop Dictation Button - Show when dictation */} + <ComposerPrimitive.If dictation> + <ComposerPrimitive.StopDictation asChild> + <TooltipIconButton + tooltip="Stop dictation" + side="top" + variant="default" + className="aui-composer-stop-dictation size-[34px] rounded-full p-1" + aria-label="Stop voice input" + > + <Square className="size-4 animate-pulse fill-current" /> + </TooltipIconButton> + </ComposerPrimitive.StopDictation> + </ComposerPrimitive.If> + </div> + + <ThreadPrimitive.If running={false}> + <ComposerPrimitive.Send asChild> + <TooltipIconButton + tooltip="Send message" + side="bottom" + type="button" + variant="default" + size="icon" + className="aui-composer-send size-[34px] rounded-full p-1" + aria-label="Send message" + > + <ArrowUpIcon className="aui-composer-send-icon size-5" /> + </TooltipIconButton> + </ComposerPrimitive.Send> + </ThreadPrimitive.If> + + <ThreadPrimitive.If running> + <ComposerPrimitive.Cancel asChild> + <Button + type="button" + variant="default" + size="icon" + className="aui-composer-cancel border-muted-foreground/60 hover:bg-primary/75 dark:border-muted-foreground/90 size-[34px] rounded-full border" + aria-label="Stop generating" + > + <Square className="aui-composer-cancel-icon size-3.5 fill-white dark:fill-black" /> + </Button> + </ComposerPrimitive.Cancel> + </ThreadPrimitive.If> + </div> + ); +}; + +const MessageError: FC = () => { + return ( + <MessagePrimitive.Error> + <ErrorPrimitive.Root className="aui-message-error-root border-destructive bg-destructive/10 text-destructive dark:bg-destructive/5 mt-2 rounded-md border p-3 text-sm dark:text-red-200"> + <ErrorPrimitive.Message className="aui-message-error-message line-clamp-2" /> + </ErrorPrimitive.Root> + </MessagePrimitive.Error> + ); +}; + +const AssistantMessage: FC = () => { + return ( + <MessagePrimitive.Root + className="aui-assistant-message-root relative mx-auto w-full max-w-[var(--thread-max-width)] py-4 last:mb-24" + data-role="assistant" + > + <div className="aui-assistant-message-content text-foreground mx-2 leading-7 break-words"> + <MessagePrimitive.Parts> + {({ part }) => { + if (part.type === "text") return <MarkdownText />; + if (part.type === "tool-call") + return part.toolUI ?? <ToolFallback {...part} />; + return null; + }} + </MessagePrimitive.Parts> + <MessageError /> + </div> + + <div className="aui-assistant-message-footer ms-2 mt-2 flex min-h-6 items-center"> + <BranchPicker /> + <AssistantActionBar /> + </div> + </MessagePrimitive.Root> + ); +}; + +const AssistantActionBar: FC = () => { + return ( + <ActionBarPrimitive.Root + hideWhenRunning + autohide="not-last" + className="aui-assistant-action-bar-root text-muted-foreground col-start-3 row-start-2 -ms-1 flex gap-1" + > + <ActionBarPrimitive.Copy asChild> + <TooltipIconButton tooltip="Copy"> + <MessagePrimitive.If copied> + <CheckIcon /> + </MessagePrimitive.If> + <MessagePrimitive.If copied={false}> + <CopyIcon /> + </MessagePrimitive.If> + </TooltipIconButton> + </ActionBarPrimitive.Copy> + <ActionBarPrimitive.Reload asChild> + <TooltipIconButton tooltip="Refresh"> + <RefreshCwIcon /> + </TooltipIconButton> + </ActionBarPrimitive.Reload> + </ActionBarPrimitive.Root> + ); +}; + +const UserMessage: FC = () => { + return ( + <MessagePrimitive.Root asChild> + <div + className="aui-user-message-root mx-auto grid w-full max-w-[var(--thread-max-width)] auto-rows-auto grid-cols-[minmax(72px,1fr)_auto] gap-y-2 px-2 py-4 first:mt-3 last:mb-5 [&:where(>*)]:col-start-2" + data-role="user" + > + <UserMessageAttachments /> + + <div className="aui-user-message-content-wrapper relative col-start-2 min-w-0"> + <div className="aui-user-message-content bg-muted text-foreground rounded-3xl px-5 py-2.5 break-words"> + <MessagePrimitive.Parts /> + </div> + <div className="aui-user-action-bar-wrapper absolute start-0 top-1/2 -translate-x-full -translate-y-1/2 pe-2 rtl:translate-x-full"> + <UserActionBar /> + </div> + </div> + + <BranchPicker className="aui-user-branch-picker col-span-full col-start-1 row-start-3 -me-1 justify-end" /> + </div> + </MessagePrimitive.Root> + ); +}; + +const UserActionBar: FC = () => { + return ( + <ActionBarPrimitive.Root + hideWhenRunning + autohide="not-last" + className="aui-user-action-bar-root flex flex-col items-end" + > + <ActionBarPrimitive.Edit asChild> + <TooltipIconButton tooltip="Edit" className="aui-user-action-edit p-4"> + <PencilIcon /> + </TooltipIconButton> + </ActionBarPrimitive.Edit> + </ActionBarPrimitive.Root> + ); +}; + +const EditComposer: FC = () => { + return ( + <div className="aui-edit-composer-wrapper mx-auto flex w-full max-w-[var(--thread-max-width)] flex-col gap-4 px-2 first:mt-4"> + <ComposerPrimitive.Root className="aui-edit-composer-root bg-muted ms-auto flex w-full max-w-7/8 flex-col rounded-xl"> + <ComposerPrimitive.Input + className="aui-edit-composer-input text-foreground flex min-h-[60px] w-full resize-none bg-transparent p-4 outline-none" + autoFocus + /> + + <div className="aui-edit-composer-footer mx-3 mb-3 flex items-center justify-center gap-2 self-end"> + <ComposerPrimitive.Cancel asChild> + <Button variant="ghost" size="sm" aria-label="Cancel edit"> + Cancel + </Button> + </ComposerPrimitive.Cancel> + <ComposerPrimitive.Send asChild> + <Button size="sm" aria-label="Update message"> + Update + </Button> + </ComposerPrimitive.Send> + </div> + </ComposerPrimitive.Root> + </div> + ); +}; + +const BranchPicker: FC<BranchPickerPrimitive.Root.Props> = ({ + className, + ...rest +}) => { + return ( + <BranchPickerPrimitive.Root + hideWhenSingleBranch + className={cn( + "aui-branch-picker-root text-muted-foreground -ms-2 me-2 inline-flex items-center text-xs", + className, + )} + {...rest} + > + <BranchPickerPrimitive.Previous asChild> + <TooltipIconButton tooltip="Previous"> + <ChevronLeftIcon /> + </TooltipIconButton> + </BranchPickerPrimitive.Previous> + <span className="aui-branch-picker-state font-medium"> + <BranchPickerPrimitive.Number /> / <BranchPickerPrimitive.Count /> + </span> + <BranchPickerPrimitive.Next asChild> + <TooltipIconButton tooltip="Next"> + <ChevronRightIcon /> + </TooltipIconButton> + </BranchPickerPrimitive.Next> + </BranchPickerPrimitive.Root> + ); +}; diff --git a/apps/docs/components/docs/samples/diff-viewer.tsx b/apps/docs/components/docs/samples/diff-viewer.tsx new file mode 100644 index 0000000..5016a7e --- /dev/null +++ b/apps/docs/components/docs/samples/diff-viewer.tsx @@ -0,0 +1,144 @@ +"use client"; + +import { useState } from "react"; +import { SampleFrame } from "@/components/docs/samples/sample-frame"; +import { DiffViewer } from "@/components/assistant-ui/diff-viewer"; +import { cn } from "@/lib/utils"; + +const SAMPLE_PATCH = `--- a/example.ts ++++ b/example.ts +@@ -1,5 +1,7 @@ +-function greet(name) { +- console.log("Hello, " + name); ++function greet(name: string): void { ++ console.log(\`Hello, \${name}!\`); + } + +-greet("World"); ++// Call the function ++greet("World"); ++greet("TypeScript");`; + +const SMALL_PATCH = `--- a/example.ts ++++ b/example.ts +@@ -1 +1 @@ +-let x = 1; ++const x = 1;`; + +export function DiffViewerSample() { + return ( + <SampleFrame className="bg-muted/40 h-auto overflow-hidden p-4"> + <DiffViewer patch={SAMPLE_PATCH} /> + </SampleFrame> + ); +} + +export function DiffViewerSplitSample() { + return ( + <SampleFrame className="bg-muted/40 h-auto overflow-hidden p-4"> + <DiffViewer patch={SAMPLE_PATCH} viewMode="split" /> + </SampleFrame> + ); +} + +export function DiffViewerViewModesSample() { + const [viewMode, setViewMode] = useState<"unified" | "split">("unified"); + + return ( + <SampleFrame className="bg-muted/40 h-auto overflow-hidden"> + <div className="flex h-full w-full flex-col items-center justify-center gap-4 p-4"> + <div className="flex gap-2"> + <button + type="button" + onClick={() => setViewMode("unified")} + className={cn("rounded-md px-3 py-1.5 text-sm transition-colors", { + "bg-primary text-primary-foreground": viewMode === "unified", + "bg-muted hover:bg-muted/80": viewMode !== "unified", + })} + > + Unified + </button> + <button + type="button" + onClick={() => setViewMode("split")} + className={cn("rounded-md px-3 py-1.5 text-sm transition-colors", { + "bg-primary text-primary-foreground": viewMode === "split", + "bg-muted hover:bg-muted/80": viewMode !== "split", + })} + > + Split + </button> + </div> + <div className="w-full max-w-3xl"> + <DiffViewer patch={SAMPLE_PATCH} viewMode={viewMode} /> + </div> + </div> + </SampleFrame> + ); +} + +export function DiffViewerVariantsSample() { + return ( + <SampleFrame className="bg-muted/40 h-auto overflow-hidden p-4"> + <div className="grid w-full max-w-3xl gap-4 md:grid-cols-3"> + <div className="flex flex-col gap-2"> + <span className="text-muted-foreground text-xs font-medium"> + default + </span> + <DiffViewer + patch={SMALL_PATCH} + variant="default" + showLineNumbers={false} + /> + </div> + <div className="flex flex-col gap-2"> + <span className="text-muted-foreground text-xs font-medium"> + ghost + </span> + <DiffViewer + patch={SMALL_PATCH} + variant="ghost" + showLineNumbers={false} + /> + </div> + <div className="flex flex-col gap-2"> + <span className="text-muted-foreground text-xs font-medium"> + muted + </span> + <DiffViewer + patch={SMALL_PATCH} + variant="muted" + showLineNumbers={false} + /> + </div> + </div> + </SampleFrame> + ); +} + +export function DiffViewerSizesSample() { + return ( + <SampleFrame className="bg-muted/40 h-auto overflow-hidden p-4"> + <div className="flex w-full max-w-3xl flex-col gap-4"> + <div className="flex flex-col gap-2"> + <span className="text-muted-foreground text-xs font-medium">sm</span> + <DiffViewer patch={SMALL_PATCH} size="sm" showLineNumbers={false} /> + </div> + <div className="flex flex-col gap-2"> + <span className="text-muted-foreground text-xs font-medium"> + default + </span> + <DiffViewer + patch={SMALL_PATCH} + size="default" + showLineNumbers={false} + /> + </div> + <div className="flex flex-col gap-2"> + <span className="text-muted-foreground text-xs font-medium">lg</span> + <DiffViewer patch={SMALL_PATCH} size="lg" showLineNumbers={false} /> + </div> + </div> + </SampleFrame> + ); +} diff --git a/apps/docs/components/docs/samples/directive-text.tsx b/apps/docs/components/docs/samples/directive-text.tsx new file mode 100644 index 0000000..a7061b5 --- /dev/null +++ b/apps/docs/components/docs/samples/directive-text.tsx @@ -0,0 +1,40 @@ +"use client"; + +import { WrenchIcon } from "lucide-react"; +import { unstable_defaultDirectiveFormatter } from "@assistant-ui/react"; +import { createDirectiveText } from "@/components/assistant-ui/directive-text"; +import { SampleFrame } from "@/components/docs/samples/sample-frame"; + +const SampleDirectiveText = createDirectiveText( + unstable_defaultDirectiveFormatter, + { iconMap: { tool: WrenchIcon } }, +); + +const COMPLETE_STATUS = { type: "complete" } as const; + +export function DirectiveTextSample() { + return ( + <SampleFrame className="flex h-auto flex-col items-center justify-center gap-6 p-8"> + <div className="flex w-full max-w-md flex-col items-end gap-2"> + <span className="text-muted-foreground text-xs">User message</span> + <div className="bg-muted text-foreground rounded-2xl px-4 py-2.5 text-sm"> + <SampleDirectiveText + type="text" + text="Use :tool[Get Weather]{name=get_weather} to check today's forecast in Tokyo." + status={COMPLETE_STATUS} + /> + </div> + </div> + <div className="flex w-full max-w-md flex-col items-end gap-2"> + <span className="text-muted-foreground text-xs">Another example</span> + <div className="bg-muted text-foreground rounded-2xl px-4 py-2.5 text-sm"> + <SampleDirectiveText + type="text" + text="Ask :tool[Search] for recent updates on :tool[Calendar]." + status={COMPLETE_STATUS} + /> + </div> + </div> + </SampleFrame> + ); +} diff --git a/apps/docs/components/docs/samples/dot-matrix.tsx b/apps/docs/components/docs/samples/dot-matrix.tsx new file mode 100644 index 0000000..12494ee --- /dev/null +++ b/apps/docs/components/docs/samples/dot-matrix.tsx @@ -0,0 +1,100 @@ +"use client"; + +import { useEffect, useState } from "react"; +import { + DotMatrix, + dotMatrixStates, + type DotMatrixState, +} from "@/components/assistant-ui/dot-matrix"; +import { Button } from "@/components/ui/button"; +import { SampleFrame } from "@/components/docs/samples/sample-frame"; + +export function DotMatrixSample() { + return ( + <SampleFrame className="grid h-auto grid-cols-3 gap-x-4 gap-y-8 p-10 sm:grid-cols-5"> + {dotMatrixStates.map((state) => ( + <div key={state} className="flex flex-col items-center gap-3"> + <DotMatrix state={state} className="size-8" /> + <span className="text-muted-foreground font-mono text-xs"> + {state} + </span> + </div> + ))} + </SampleFrame> + ); +} + +export function DotMatrixLifecycleSample() { + const lifecycle = [ + "idle", + "connecting", + "loading", + "thinking", + "streaming", + "success", + ] as const satisfies readonly DotMatrixState[]; + const [step, setStep] = useState(0); + const state = lifecycle[step % lifecycle.length]!; + + return ( + <SampleFrame className="flex h-auto flex-col items-center justify-center gap-6 p-10"> + <div className="flex items-center gap-3"> + <DotMatrix state={state} className="size-10" /> + <span className="text-muted-foreground font-mono text-sm">{state}</span> + </div> + <div className="flex items-center gap-2"> + <Button variant="outline" onClick={() => setStep((s) => s + 1)}> + Next state + </Button> + <Button + variant="outline" + onClick={() => setStep(0)} + disabled={state === "idle"} + > + Reset + </Button> + </div> + </SampleFrame> + ); +} + +export function DotMatrixInlineSample() { + const [running, setRunning] = useState(true); + + useEffect(() => { + const interval = setInterval(() => setRunning((r) => !r), 3000); + return () => clearInterval(interval); + }, []); + + return ( + <SampleFrame className="flex h-auto flex-col items-center justify-center gap-6 p-10"> + <div className="flex items-center gap-2 text-sm"> + <DotMatrix state={running ? "loading" : "success"} /> + {running ? "Generating response…" : "Done"} + </div> + <div className="bg-foreground text-background flex items-center gap-2 rounded-lg px-4 py-3 text-sm"> + <DotMatrix state={running ? "thinking" : "success"} /> + {running ? "Thinking…" : "Done"} + </div> + <div className="flex items-center gap-2 text-sm"> + <DotMatrix state={running ? "recording" : "stopped"} /> + {running ? "Recording…" : "Stopped"} + </div> + </SampleFrame> + ); +} + +export function DotMatrixSizesSample() { + return ( + <SampleFrame className="flex h-auto items-end justify-center gap-8 p-10"> + {(["size-4", "size-6", "size-10", "size-16"] as const).map((size) => ( + <div key={size} className="flex flex-col items-center gap-3"> + <DotMatrix state="loading" className={size} /> + <span className="text-muted-foreground font-mono text-xs"> + {size} + </span> + </div> + ))} + </SampleFrame> + ); +} diff --git a/apps/docs/components/docs/samples/error-primitive.tsx b/apps/docs/components/docs/samples/error-primitive.tsx new file mode 100644 index 0000000..d61fd72 --- /dev/null +++ b/apps/docs/components/docs/samples/error-primitive.tsx @@ -0,0 +1,26 @@ +"use client"; + +import { SampleFrame } from "./sample-frame"; + +export function ErrorPrimitiveSample() { + return ( + <SampleFrame className="bg-background flex h-auto items-center justify-center p-8"> + <div className="mx-auto w-full max-w-lg space-y-3"> + <div className="flex justify-start gap-3"> + <div className="bg-primary/10 text-primary flex size-8 shrink-0 items-center justify-center rounded-full text-xs font-medium"> + AI + </div> + <div className="bg-muted max-w-[80%] rounded-2xl px-4 py-2.5 text-sm"> + Let me look that up for you... + </div> + </div> + <div + className="bg-destructive/10 text-destructive ms-11 rounded-md px-3 py-2 text-sm" + role="alert" + > + An error occurred. Please try again. + </div> + </div> + </SampleFrame> + ); +} diff --git a/apps/docs/components/docs/samples/file.tsx b/apps/docs/components/docs/samples/file.tsx new file mode 100644 index 0000000..e67cd9f --- /dev/null +++ b/apps/docs/components/docs/samples/file.tsx @@ -0,0 +1,108 @@ +"use client"; + +import { + FileRoot, + FileIconDisplay, + FileName, + FileSize, + FileDownload, +} from "@/components/assistant-ui/file"; +import { SampleFrame } from "@/components/docs/samples/sample-frame"; + +// Sample base64 data (small placeholder) +const SAMPLE_DATA = "SGVsbG8gV29ybGQh"; // "Hello World!" in base64 + +const files = [ + { filename: "report.pdf", mimeType: "application/pdf", size: 2048 }, + { filename: "config.json", mimeType: "application/json", size: 512 }, + { filename: "notes.txt", mimeType: "text/plain", size: 128 }, + { filename: "photo.png", mimeType: "image/png", size: 4096 }, + { filename: "track.mp3", mimeType: "audio/mpeg", size: 8192 }, +]; + +function VariantRow({ + label, + variant, +}: { + label: string; + variant?: "outline" | "ghost" | "muted"; +}) { + return ( + <div className="flex flex-col gap-2"> + <span className="text-muted-foreground text-xs font-medium">{label}</span> + <div className="flex flex-wrap items-center gap-2"> + {files.slice(0, 3).map((file) => ( + <FileRoot key={file.filename} variant={variant}> + <FileIconDisplay mimeType={file.mimeType} /> + <div className="flex min-w-0 flex-1 flex-col gap-0.5"> + <FileName>{file.filename}</FileName> + <FileSize bytes={file.size} className="text-xs" /> + </div> + <FileDownload + data={SAMPLE_DATA} + mimeType={file.mimeType} + filename={file.filename} + /> + </FileRoot> + ))} + </div> + </div> + ); +} + +function SizeRow() { + const file = files[0]!; + return ( + <div className="flex flex-col gap-2"> + <span className="text-muted-foreground text-xs font-medium">Sizes</span> + <div className="flex flex-wrap items-center gap-2"> + {(["sm", "default", "lg"] as const).map((size) => { + const filename = `${size}.pdf`; + return ( + <FileRoot key={size} size={size}> + <FileIconDisplay mimeType={file.mimeType} /> + <div className="flex min-w-0 flex-1 flex-col gap-0.5"> + <FileName>{filename}</FileName> + <FileSize bytes={file.size} className="text-xs" /> + </div> + <FileDownload + data={SAMPLE_DATA} + mimeType={file.mimeType} + filename={filename} + /> + </FileRoot> + ); + })} + </div> + </div> + ); +} + +function MimeTypeRow() { + return ( + <div className="flex flex-col gap-2"> + <span className="text-muted-foreground text-xs font-medium"> + MimeType Icons + </span> + <div className="flex flex-wrap items-center gap-2"> + {files.map((file) => ( + <FileRoot key={file.filename} size="sm"> + <FileIconDisplay mimeType={file.mimeType} /> + <FileName>{file.filename}</FileName> + </FileRoot> + ))} + </div> + </div> + ); +} + +export function FileSample() { + return ( + <SampleFrame className="flex h-auto flex-col gap-6 p-6"> + <VariantRow label="Outline (default)" /> + <VariantRow label="Muted" variant="muted" /> + <SizeRow /> + <MimeTypeRow /> + </SampleFrame> + ); +} diff --git a/apps/docs/components/docs/samples/follow-up-suggestions.tsx b/apps/docs/components/docs/samples/follow-up-suggestions.tsx new file mode 100644 index 0000000..4b85bc8 --- /dev/null +++ b/apps/docs/components/docs/samples/follow-up-suggestions.tsx @@ -0,0 +1,90 @@ +"use client"; + +import type { FC, PropsWithChildren } from "react"; +import { + AssistantRuntimeProvider, + useExternalStoreRuntime, + type ThreadMessageLike, +} from "@assistant-ui/react"; +import { ThreadFollowupSuggestions } from "@/components/assistant-ui/follow-up-suggestions"; +import { SampleFrame } from "@/components/docs/samples/sample-frame"; + +type DemoMessage = { + id: string; + role: "user" | "assistant"; + text: string; +}; + +const userMessage: DemoMessage = { + id: "user-1", + role: "user", + text: "How should I improve onboarding for my AI assistant?", +}; + +const assistantMessage: DemoMessage = { + id: "assistant-1", + role: "assistant", + text: "Start by mapping the first-run path, then add a few suggested prompts that help users discover the highest-value workflows.", +}; + +const messages = [ + userMessage, + assistantMessage, +] satisfies readonly DemoMessage[]; + +const suggestions = [ + { + prompt: "Draft three onboarding prompts", + }, + { + prompt: "Turn this into a checklist", + }, + { + prompt: "Show me a better empty state", + }, +]; + +const convertMessage = (message: DemoMessage): ThreadMessageLike => ({ + id: message.id, + role: message.role, + content: [{ type: "text", text: message.text }], +}); + +const FollowUpSuggestionsRuntimeProvider: FC<PropsWithChildren> = ({ + children, +}) => { + const runtime = useExternalStoreRuntime({ + messages, + suggestions, + convertMessage, + onNew: async () => {}, + }); + + return ( + <AssistantRuntimeProvider runtime={runtime}> + {children} + </AssistantRuntimeProvider> + ); +}; + +export const FollowUpSuggestionsSample = () => { + return ( + <SampleFrame className="bg-muted/40 flex h-auto min-h-72 items-center justify-center p-6"> + <FollowUpSuggestionsRuntimeProvider> + <div className="bg-background w-full max-w-2xl rounded-xl border p-4 shadow-sm"> + <div className="flex justify-end"> + <div className="bg-primary text-primary-foreground max-w-[80%] rounded-2xl px-3.5 py-2 text-sm"> + {userMessage.text} + </div> + </div> + <div className="mt-4 max-w-[85%] text-sm leading-relaxed"> + {assistantMessage.text} + </div> + <div className="mt-4"> + <ThreadFollowupSuggestions /> + </div> + </div> + </FollowUpSuggestionsRuntimeProvider> + </SampleFrame> + ); +}; diff --git a/apps/docs/components/docs/samples/image.tsx b/apps/docs/components/docs/samples/image.tsx new file mode 100644 index 0000000..29c5627 --- /dev/null +++ b/apps/docs/components/docs/samples/image.tsx @@ -0,0 +1,49 @@ +"use client"; + +import { + ImageRoot, + ImagePreview, + ImageFilename, + ImageZoom, +} from "@/components/assistant-ui/image"; +import { SampleFrame } from "@/components/docs/samples/sample-frame"; + +const PLACEHOLDER_IMAGE = + "https://images.unsplash.com/photo-1506905925346-21bda4d32df4?w=800&q=80"; + +function VariantRow({ + label, + variant, +}: { + label: string; + variant?: "outline" | "ghost" | "muted"; +}) { + return ( + <div className="flex flex-col gap-2"> + <span className="text-muted-foreground text-xs font-medium">{label}</span> + <div className="flex flex-wrap items-start gap-4"> + <ImageRoot variant={variant} size="sm"> + <ImageZoom src={PLACEHOLDER_IMAGE} alt="Mountain landscape"> + <ImagePreview src={PLACEHOLDER_IMAGE} alt="Mountain landscape" /> + </ImageZoom> + <ImageFilename>landscape-sm.jpg</ImageFilename> + </ImageRoot> + <ImageRoot variant={variant} size="default"> + <ImageZoom src={PLACEHOLDER_IMAGE} alt="Mountain landscape"> + <ImagePreview src={PLACEHOLDER_IMAGE} alt="Mountain landscape" /> + </ImageZoom> + <ImageFilename>landscape-default.jpg</ImageFilename> + </ImageRoot> + </div> + </div> + ); +} + +export function ImageSample() { + return ( + <SampleFrame className="flex h-auto flex-col gap-6 overflow-x-auto p-6"> + <VariantRow label="Outline (click to zoom)" /> + <VariantRow label="Muted" variant="muted" /> + </SampleFrame> + ); +} diff --git a/apps/docs/components/docs/samples/interactable-state.ts b/apps/docs/components/docs/samples/interactable-state.ts new file mode 100644 index 0000000..c67b0f3 --- /dev/null +++ b/apps/docs/components/docs/samples/interactable-state.ts @@ -0,0 +1,16 @@ +import { z } from "zod"; + +export type Task = { id: string; title: string; done: boolean }; +export type TaskBoardState = { tasks: Task[] }; + +export const taskBoardSchema = z.object({ + tasks: z.array( + z.object({ + id: z.string(), + title: z.string(), + done: z.boolean(), + }), + ), +}); + +export const taskBoardInitialState: TaskBoardState = { tasks: [] }; diff --git a/apps/docs/components/docs/samples/interactable.tsx b/apps/docs/components/docs/samples/interactable.tsx new file mode 100644 index 0000000..fb373e5 --- /dev/null +++ b/apps/docs/components/docs/samples/interactable.tsx @@ -0,0 +1,279 @@ +"use client"; + +import { type FC, useMemo } from "react"; +import { + AssistantRuntimeProvider, + useAui, + useAuiState, + unstable_Interactables, + Suggestions, + unstable_useInteractable, + ThreadPrimitive, + ComposerPrimitive, + MessagePrimitive, + AuiIf, + WebSpeechSynthesisAdapter, + WebSpeechDictationAdapter, + SimpleImageAttachmentAdapter, + AssistantCloud, + type FeedbackAdapter, +} from "@assistant-ui/react"; +import { MarkdownTextPrimitive } from "@assistant-ui/react-markdown"; +import { useChatRuntime } from "@assistant-ui/react-ai-sdk"; +import { lastAssistantMessageIsCompleteWithToolCalls } from "ai"; +import { SampleFrame } from "@/components/docs/samples/sample-frame"; +import remarkGfm from "remark-gfm"; +import { + ArrowUpIcon, + CheckCircle2Icon, + CircleIcon, + ListTodoIcon, + Square, +} from "lucide-react"; +import { cn } from "@/lib/utils"; +import { taskBoardInitialState, taskBoardSchema } from "./interactable-state"; + +const TaskBoard: FC = () => { + const [state, { setState }] = unstable_useInteractable("taskBoard", { + description: + "A task board showing the user's tasks. Use update_taskBoard with tasks.add/update/remove/clear to manage tasks. New tasks need a title and done=false.", + stateSchema: taskBoardSchema, + initialState: taskBoardInitialState, + }); + + const toggleTask = (id: string) => { + setState((prev) => ({ + tasks: prev.tasks.map((t) => (t.id === id ? { ...t, done: !t.done } : t)), + })); + }; + + const doneCount = state.tasks.filter((t) => t.done).length; + + return ( + <div className="bg-muted/30 flex h-full flex-col border-s"> + <div className="flex items-center gap-2 border-b px-4 py-3"> + <ListTodoIcon className="text-muted-foreground size-4" /> + <span className="text-sm font-medium">Task Board</span> + {state.tasks.length > 0 && ( + <span className="text-muted-foreground ms-auto text-xs"> + {doneCount}/{state.tasks.length} + </span> + )} + </div> + + <div className="flex-1 overflow-y-auto p-3"> + {state.tasks.length === 0 ? ( + <div className="text-muted-foreground flex h-full flex-col items-center justify-center text-center text-xs"> + <ListTodoIcon className="mb-2 size-8 opacity-30" /> + <p>No tasks yet.</p> + <p className="mt-1 opacity-70">Ask the assistant to add some!</p> + </div> + ) : ( + <ul className="space-y-1.5"> + {state.tasks.map((task, index) => ( + <li key={task.id ?? `streaming-${index}`}> + <button + type="button" + onClick={() => toggleTask(task.id)} + className={cn( + "hover:bg-muted flex w-full items-center gap-2.5 rounded-lg px-3 py-2 text-start text-sm transition-colors", + task.done && "opacity-50", + )} + > + {task.done ? ( + <CheckCircle2Icon className="text-primary size-4 shrink-0" /> + ) : ( + <CircleIcon className="text-muted-foreground size-4 shrink-0" /> + )} + <span className={cn("flex-1", task.done && "line-through")}> + {task.title} + </span> + </button> + </li> + ))} + </ul> + )} + </div> + </div> + ); +}; + +const MiniThread: FC = () => { + return ( + <ThreadPrimitive.Root className="bg-background flex h-full min-h-0 flex-col"> + <ThreadPrimitive.Viewport className="relative flex min-h-0 flex-1 flex-col overflow-y-auto px-3"> + <AuiIf condition={(s) => s.thread.isEmpty}> + <div className="flex grow flex-col items-center justify-center text-center"> + <p className="text-sm font-medium">Task Assistant</p> + <p className="text-muted-foreground mt-1 text-xs"> + Ask me to add tasks to your board. + </p> + </div> + </AuiIf> + + <ThreadPrimitive.Messages> + {({ message }) => { + if (message.role === "user") return <UserMsg />; + return <AssistantMsg />; + }} + </ThreadPrimitive.Messages> + + <div className="min-h-6 grow" /> + </ThreadPrimitive.Viewport> + <div className="shrink-0 px-4 pt-2 pb-3"> + <MiniSuggestions /> + <MiniComposer /> + </div> + </ThreadPrimitive.Root> + ); +}; + +const UserMsg: FC = () => ( + <MessagePrimitive.Root className="flex justify-end py-2"> + <div className="bg-primary text-primary-foreground max-w-[80%] rounded-2xl px-3.5 py-2 text-sm"> + <MessagePrimitive.Parts /> + </div> + </MessagePrimitive.Root> +); + +const MiniMarkdownText: FC = () => ( + <MarkdownTextPrimitive remarkPlugins={[remarkGfm]} className="aui-md" /> +); + +const AssistantMsg: FC = () => ( + <MessagePrimitive.Root className="py-2"> + <div className="max-w-[85%] text-sm leading-relaxed"> + <MessagePrimitive.Parts components={{ Text: MiniMarkdownText }} /> + </div> + </MessagePrimitive.Root> +); + +const suggestions = [ + { prompt: "Add 3 tasks for a grocery run", label: "Grocery run" }, + { prompt: "Add a task: Review the pull request", label: "Add a task" }, + { prompt: "Clear all tasks from the board", label: "Clear board" }, +]; + +const MiniSuggestions: FC = () => { + const isEmpty = useAuiState((s) => s.thread.isEmpty); + if (!isEmpty) return null; + + return ( + <div className="mb-2 flex flex-wrap gap-1.5"> + {suggestions.map((s) => ( + <ThreadPrimitive.Suggestion + key={s.prompt} + prompt={s.prompt} + send + asChild + > + <button + type="button" + className="bg-background text-muted-foreground hover:bg-muted hover:text-foreground rounded-full border px-2.5 py-1 text-xs transition-colors" + > + {s.label} + </button> + </ThreadPrimitive.Suggestion> + ))} + </div> + ); +}; + +const MiniComposer: FC = () => ( + <ComposerPrimitive.Root className="bg-muted flex items-end gap-2 rounded-2xl border px-3 py-2"> + <ComposerPrimitive.Input + placeholder="Add 3 tasks for a grocery run" + className="placeholder:text-muted-foreground/60 min-h-6 flex-1 resize-none bg-transparent text-sm outline-none" + rows={1} + autoFocus + /> + <AuiIf condition={(s) => !s.thread.isRunning}> + <ComposerPrimitive.Send asChild> + <button + type="button" + className="bg-primary text-primary-foreground flex size-7 shrink-0 items-center justify-center rounded-full" + > + <ArrowUpIcon className="size-4" /> + </button> + </ComposerPrimitive.Send> + </AuiIf> + <AuiIf condition={(s) => s.thread.isRunning}> + <ComposerPrimitive.Cancel asChild> + <button + type="button" + className="bg-primary text-primary-foreground flex size-7 shrink-0 items-center justify-center rounded-full" + > + <Square className="size-3 fill-current" /> + </button> + </ComposerPrimitive.Cancel> + </AuiIf> + </ComposerPrimitive.Root> +); + +const feedbackAdapter: FeedbackAdapter = { submit: () => {} }; + +function InteractableRuntimeProvider({ + children, +}: { + children: React.ReactNode; +}) { + const assistantCloud = useMemo( + () => + new AssistantCloud({ + baseUrl: process.env.NEXT_PUBLIC_ASSISTANT_BASE_URL!, + anonymous: true, + }), + [], + ); + + const adapters = useMemo( + () => ({ + speech: new WebSpeechSynthesisAdapter(), + dictation: new WebSpeechDictationAdapter(), + feedback: feedbackAdapter, + attachments: new SimpleImageAttachmentAdapter(), + }), + [], + ); + + const runtime = useChatRuntime({ + sendAutomaticallyWhen: lastAssistantMessageIsCompleteWithToolCalls, + adapters, + cloud: assistantCloud, + }); + + const aui = useAui({ + unstable_interactables: unstable_Interactables(), + suggestions: Suggestions([ + { + title: "Add 3 tasks", + label: "for a grocery run", + prompt: "Add 3 tasks for a grocery run", + }, + { + title: "Clear all tasks", + label: "from the board", + prompt: "Clear all tasks from the board", + }, + ]), + }); + + return ( + <AssistantRuntimeProvider aui={aui} runtime={runtime}> + {children} + </AssistantRuntimeProvider> + ); +} + +export const InteractableSample = () => { + return ( + <SampleFrame className="bg-muted/40 overflow-hidden"> + <InteractableRuntimeProvider> + <div className="grid h-full min-h-0 grid-cols-[1fr_220px]"> + <MiniThread /> + <TaskBoard /> + </div> + </InteractableRuntimeProvider> + </SampleFrame> + ); +}; diff --git a/apps/docs/components/docs/samples/markdown.tsx b/apps/docs/components/docs/samples/markdown.tsx new file mode 100644 index 0000000..46399aa --- /dev/null +++ b/apps/docs/components/docs/samples/markdown.tsx @@ -0,0 +1,124 @@ +"use client"; + +import ReactMarkdown from "react-markdown"; +import remarkGfm from "remark-gfm"; +import { SampleFrame } from "@/components/docs/samples/sample-frame"; +import { cn } from "@/lib/utils"; + +const sampleMarkdown = `## Markdown Rendering + +This is a paragraph with **bold text**, *italic text*, and [links](#). + +- First item +- Second item +- Third item + +> This is a blockquote with some quoted text. + +| Name | Value | +|-------|-------| +| Alpha | 100 | +| Beta | 200 | +`; + +const components = { + h2: ({ className, ...props }: React.HTMLAttributes<HTMLHeadingElement>) => ( + <h2 + className={cn( + "aui-md-h2 mt-8 mb-4 scroll-m-20 text-2xl font-semibold tracking-tight first:mt-0 last:mb-0", + className, + )} + {...props} + /> + ), + p: ({ className, ...props }: React.HTMLAttributes<HTMLParagraphElement>) => ( + <p + className={cn( + "aui-md-p mt-5 mb-5 leading-7 first:mt-0 last:mb-0", + className, + )} + {...props} + /> + ), + a: ({ + className, + ...props + }: React.AnchorHTMLAttributes<HTMLAnchorElement>) => ( + <a + className={cn( + "aui-md-a text-primary font-medium underline underline-offset-4", + className, + )} + {...props} + /> + ), + ul: ({ className, ...props }: React.HTMLAttributes<HTMLUListElement>) => ( + <ul + className={cn("aui-md-ul my-5 ms-6 list-disc [&>li]:mt-2", className)} + {...props} + /> + ), + blockquote: ({ + className, + ...props + }: React.HTMLAttributes<HTMLQuoteElement>) => ( + <blockquote + className={cn("aui-md-blockquote border-s-2 ps-6 italic", className)} + {...props} + /> + ), + table: ({ className, ...props }: React.HTMLAttributes<HTMLTableElement>) => ( + <table + className={cn( + "aui-md-table my-5 w-full border-separate border-spacing-0 overflow-y-auto", + className, + )} + {...props} + /> + ), + th: ({ + className, + ...props + }: React.ThHTMLAttributes<HTMLTableCellElement>) => ( + <th + className={cn( + "aui-md-th bg-muted px-4 py-2 text-start font-bold first:rounded-ss-lg last:rounded-se-lg", + className, + )} + {...props} + /> + ), + td: ({ + className, + ...props + }: React.TdHTMLAttributes<HTMLTableCellElement>) => ( + <td + className={cn( + "aui-md-td border-s border-b px-4 py-2 text-start last:border-e", + className, + )} + {...props} + /> + ), + tr: ({ className, ...props }: React.HTMLAttributes<HTMLTableRowElement>) => ( + <tr + className={cn( + "aui-md-tr m-0 border-b p-0 first:border-t [&:last-child>td:first-child]:rounded-es-lg [&:last-child>td:last-child]:rounded-ee-lg", + className, + )} + {...props} + /> + ), +}; + +export const MarkdownSample = () => { + return ( + <SampleFrame className="bg-background h-auto p-4"> + <div className="aui-md"> + <ReactMarkdown remarkPlugins={[remarkGfm]} components={components}> + {sampleMarkdown} + </ReactMarkdown> + </div> + </SampleFrame> + ); +}; diff --git a/apps/docs/components/docs/samples/mermaid.tsx b/apps/docs/components/docs/samples/mermaid.tsx new file mode 100644 index 0000000..30c7b6b --- /dev/null +++ b/apps/docs/components/docs/samples/mermaid.tsx @@ -0,0 +1,27 @@ +import { renderMermaidSVG } from "beautiful-mermaid"; +import { SampleFrame } from "@/components/docs/samples/sample-frame"; + +const MERMAID_CODE = `graph TD + A[Start] --> B{Is it working?} + B -->|Yes| C[Great!] + B -->|No| D[Debug] + D --> B`; + +export function MermaidSample() { + const svg = renderMermaidSVG(MERMAID_CODE, { + bg: "var(--background)", + fg: "var(--foreground)", + muted: "var(--muted-foreground)", + border: "var(--border)", + accent: "var(--foreground)", + transparent: true, + }); + return ( + <SampleFrame className="bg-muted/40 h-100 overflow-hidden"> + <div + className="flex h-full items-center justify-center [&_svg]:mx-auto [&_svg]:max-h-full" + dangerouslySetInnerHTML={{ __html: svg }} + /> + </SampleFrame> + ); +} diff --git a/apps/docs/components/docs/samples/message-primitive.tsx b/apps/docs/components/docs/samples/message-primitive.tsx new file mode 100644 index 0000000..8acba4a --- /dev/null +++ b/apps/docs/components/docs/samples/message-primitive.tsx @@ -0,0 +1,64 @@ +"use client"; + +import { + MessagePrimitive, + MessagePartPrimitive, + ThreadPrimitive, +} from "@assistant-ui/react"; +import { SampleRuntimeProvider } from "./sample-runtime-provider"; + +export function MessagePrimitiveSample() { + return ( + <div className="not-prose border-border/50 bg-muted/40 flex items-end rounded-xl border p-4"> + <div className="mx-auto flex w-full max-w-lg flex-col gap-3"> + <SampleRuntimeProvider> + <ThreadPrimitive.Messages + components={{ + UserMessage: CustomUserMessage, + AssistantMessage: CustomAssistantMessage, + }} + /> + </SampleRuntimeProvider> + </div> + </div> + ); +} + +function CustomUserMessage() { + return ( + <MessagePrimitive.Root className="flex justify-end"> + <div className="bg-primary text-primary-foreground max-w-[80%] rounded-2xl px-4 py-2.5 text-sm"> + <MessagePrimitive.Parts components={{ Text: UserText }} /> + </div> + </MessagePrimitive.Root> + ); +} + +function CustomAssistantMessage() { + return ( + <MessagePrimitive.Root className="flex justify-start gap-3"> + <div className="bg-primary/10 text-primary flex size-8 shrink-0 items-center justify-center rounded-full text-xs font-medium"> + AI + </div> + <div className="bg-muted max-w-[80%] space-y-2 rounded-2xl px-4 py-2.5 text-sm"> + <MessagePrimitive.Parts components={{ Text: AssistantText }} /> + </div> + </MessagePrimitive.Root> + ); +} + +function UserText() { + return ( + <p> + <MessagePartPrimitive.Text /> + </p> + ); +} + +function AssistantText() { + return ( + <p className="leading-relaxed"> + <MessagePartPrimitive.Text /> + </p> + ); +} diff --git a/apps/docs/components/docs/samples/message-timing.tsx b/apps/docs/components/docs/samples/message-timing.tsx new file mode 100644 index 0000000..089b0d9 --- /dev/null +++ b/apps/docs/components/docs/samples/message-timing.tsx @@ -0,0 +1,43 @@ +"use client"; + +import { SampleFrame } from "@/components/docs/samples/sample-frame"; + +export function MessageTimingSample() { + return ( + <SampleFrame className="flex h-auto items-start justify-center gap-10 p-8"> + {/* Badge as it appears in the action bar */} + <div className="flex flex-col items-center gap-3"> + <span className="text-muted-foreground text-xs">Badge</span> + <button + type="button" + className="text-muted-foreground hover:bg-accent hover:text-accent-foreground flex items-center rounded-md p-1 font-mono text-xs tabular-nums transition-colors" + > + 1.23s + </button> + </div> + + {/* Tooltip content as it appears on hover */} + <div className="flex flex-col items-center gap-3"> + <span className="text-muted-foreground text-xs">On hover</span> + <div className="bg-popover text-popover-foreground grid min-w-35 gap-1.5 rounded-lg border px-3 py-2 text-xs shadow-md"> + <div className="flex items-center justify-between gap-4"> + <span className="text-muted-foreground">First token</span> + <span className="font-mono tabular-nums">312ms</span> + </div> + <div className="flex items-center justify-between gap-4"> + <span className="text-muted-foreground">Total</span> + <span className="font-mono tabular-nums">1.23s</span> + </div> + <div className="flex items-center justify-between gap-4"> + <span className="text-muted-foreground">Speed</span> + <span className="font-mono tabular-nums">82.5 tok/s</span> + </div> + <div className="flex items-center justify-between gap-4"> + <span className="text-muted-foreground">Chunks</span> + <span className="font-mono tabular-nums">47</span> + </div> + </div> + </div> + </SampleFrame> + ); +} diff --git a/apps/docs/components/docs/samples/model-selector.base.tsx b/apps/docs/components/docs/samples/model-selector.base.tsx new file mode 100644 index 0000000..070be68 --- /dev/null +++ b/apps/docs/components/docs/samples/model-selector.base.tsx @@ -0,0 +1,180 @@ +"use client"; + +import { useState } from "react"; +import { + ModelSelectorRoot, + ModelSelectorTrigger, + ModelSelectorContent, + ModelSelectorSearch, + ModelSelectorList, + ModelSelectorEmpty, + ModelSelectorGroup, + ModelSelectorItem, + ModelSelectorEffort, + type ModelOption, +} from "@/components/assistant-ui/model-selector.base"; +import { DEFAULT_MODEL_ID, getContextWindow } from "@/constants/model"; +import { docsModelOptions } from "@/components/docs/assistant/docs-model-options"; +import { SampleFrame } from "@/components/docs/samples/sample-frame"; +import { cn } from "@/lib/utils"; +import { ToggleGroup } from "@base-ui/react/toggle-group"; +import { Toggle } from "@base-ui/react/toggle"; + +const PROVIDER_NAMES: Record<string, string> = { + openai: "OpenAI", + "google-ai-studio": "Google", + grok: "xAI", + groq: "Groq", +}; + +function providerOf(modelId: string): string { + return modelId.includes("/") ? modelId.split("/")[0]! : "openai"; +} + +const compactNumber = new Intl.NumberFormat("en", { notation: "compact" }); + +const EFFORT_SUPPORTED_MODELS = new Set([ + "gpt-5.4-nano", + "gpt-5.4-mini", + "grok/grok-3-mini", +]); + +const models: ModelOption[] = []; +const modelsByProvider = new Map<string, ModelOption[]>(); +for (const option of docsModelOptions()) { + const provider = PROVIDER_NAMES[providerOf(option.id)] ?? "Other"; + const model: ModelOption = { + ...option, + description: `${compactNumber.format(getContextWindow(option.id))} context window`, + keywords: [provider], + ...(EFFORT_SUPPORTED_MODELS.has(option.id) ? { efforts: true } : undefined), + }; + models.push(model); + const group = modelsByProvider.get(provider); + if (group) { + group.push(model); + } else { + modelsByProvider.set(provider, [model]); + } +} + +function VariantRow({ + label, + variant, +}: { + label: string; + variant?: "outline" | "ghost" | "muted"; +}) { + const [value, setValue] = useState<string>(DEFAULT_MODEL_ID); + const [effort, setEffort] = useState<string>("medium"); + + return ( + <div className="flex flex-col items-center gap-2"> + <span className="text-muted-foreground text-xs font-medium">{label}</span> + <ModelSelectorRoot + models={models} + value={value} + onValueChange={setValue} + effort={effort} + onEffortChange={setEffort} + > + <ModelSelectorTrigger variant={variant} /> + <ModelSelectorContent /> + </ModelSelectorRoot> + </div> + ); +} + +function ComposedRow() { + const [value, setValue] = useState<string>(DEFAULT_MODEL_ID); + const [effort, setEffort] = useState<string>("medium"); + const [providerFilter, setProviderFilter] = useState<ReadonlySet<string>>( + new Set(), + ); + + // An empty filter means no filtering — all providers are shown. + const visibleGroups = [...modelsByProvider].filter( + ([provider]) => providerFilter.size === 0 || providerFilter.has(provider), + ); + + return ( + <div className="flex flex-col items-center gap-2"> + <span className="text-muted-foreground text-xs font-medium"> + With search, provider filters + groups + </span> + <ModelSelectorRoot + models={models} + value={value} + onValueChange={setValue} + effort={effort} + onEffortChange={setEffort} + > + <ModelSelectorTrigger /> + <ModelSelectorContent> + <ModelSelectorSearch /> + <ToggleGroup + multiple + value={[...providerFilter]} + onValueChange={(next) => setProviderFilter(new Set(next))} + aria-label="Filter by provider" + className="flex flex-wrap gap-1 border-b px-3 py-2" + onKeyDown={(e) => { + // Keep cmdk's root handler from claiming Enter (the focused + // chip toggles instead of selecting the highlighted model) and + // Home/End (the roving focus group jumps chips, not the list). + if (e.key === "Enter" || e.key === "Home" || e.key === "End") { + e.stopPropagation(); + } + // Vertical arrows hand focus back to the model list, matching + // the packaged Thinking row. + if (e.key === "ArrowUp" || e.key === "ArrowDown") { + e.currentTarget + .closest("[cmdk-root]") + ?.querySelector<HTMLInputElement>("[cmdk-input]") + ?.focus(); + } + }} + > + {[...modelsByProvider.keys()].map((provider) => ( + <Toggle + key={provider} + value={provider} + className={cn( + "rounded-full border px-2 py-0.5 text-xs transition-colors", + "text-muted-foreground hover:text-foreground", + "data-pressed:bg-accent data-pressed:text-accent-foreground data-pressed:border-transparent", + )} + > + {provider} + </Toggle> + ))} + </ToggleGroup> + <ModelSelectorList> + <ModelSelectorEmpty /> + {visibleGroups.map(([provider, providerModels]) => ( + <ModelSelectorGroup key={provider} heading={provider}> + {providerModels.map((model) => ( + <ModelSelectorItem key={model.id} model={model} /> + ))} + </ModelSelectorGroup> + ))} + </ModelSelectorList> + <ModelSelectorEffort /> + </ModelSelectorContent> + </ModelSelectorRoot> + </div> + ); +} + +export function ModelSelectorSample() { + return ( + <SampleFrame className="flex h-auto flex-col items-center gap-8 p-8"> + <div className="flex flex-wrap items-start justify-center gap-x-10 gap-y-6"> + <VariantRow label="Outline (default)" variant="outline" /> + <VariantRow label="Ghost" variant="ghost" /> + <VariantRow label="Muted" variant="muted" /> + </div> + <ComposedRow /> + </SampleFrame> + ); +} diff --git a/apps/docs/components/docs/samples/model-selector.tsx b/apps/docs/components/docs/samples/model-selector.tsx new file mode 100644 index 0000000..6a1a14f --- /dev/null +++ b/apps/docs/components/docs/samples/model-selector.tsx @@ -0,0 +1,180 @@ +"use client"; + +import { useState } from "react"; +import { + ModelSelectorRoot, + ModelSelectorTrigger, + ModelSelectorContent, + ModelSelectorSearch, + ModelSelectorList, + ModelSelectorEmpty, + ModelSelectorGroup, + ModelSelectorItem, + ModelSelectorEffort, + type ModelOption, +} from "@/components/assistant-ui/model-selector"; +import { DEFAULT_MODEL_ID, getContextWindow } from "@/constants/model"; +import { docsModelOptions } from "@/components/docs/assistant/docs-model-options"; +import { SampleFrame } from "@/components/docs/samples/sample-frame"; +import { cn } from "@/lib/utils"; +import { ToggleGroup as ToggleGroupPrimitive } from "radix-ui"; + +const PROVIDER_NAMES: Record<string, string> = { + openai: "OpenAI", + "google-ai-studio": "Google", + grok: "xAI", + groq: "Groq", +}; + +function providerOf(modelId: string): string { + return modelId.includes("/") ? modelId.split("/")[0]! : "openai"; +} + +const compactNumber = new Intl.NumberFormat("en", { notation: "compact" }); + +const EFFORT_SUPPORTED_MODELS = new Set([ + "gpt-5.4-nano", + "gpt-5.4-mini", + "grok/grok-3-mini", +]); + +const models: ModelOption[] = []; +const modelsByProvider = new Map<string, ModelOption[]>(); +for (const option of docsModelOptions()) { + const provider = PROVIDER_NAMES[providerOf(option.id)] ?? "Other"; + const model: ModelOption = { + ...option, + description: `${compactNumber.format(getContextWindow(option.id))} context window`, + keywords: [provider], + ...(EFFORT_SUPPORTED_MODELS.has(option.id) ? { efforts: true } : undefined), + }; + models.push(model); + const group = modelsByProvider.get(provider); + if (group) { + group.push(model); + } else { + modelsByProvider.set(provider, [model]); + } +} + +function VariantRow({ + label, + variant, +}: { + label: string; + variant?: "outline" | "ghost" | "muted"; +}) { + const [value, setValue] = useState<string>(DEFAULT_MODEL_ID); + const [effort, setEffort] = useState<string>("medium"); + + return ( + <div className="flex flex-col items-center gap-2"> + <span className="text-muted-foreground text-xs font-medium">{label}</span> + <ModelSelectorRoot + models={models} + value={value} + onValueChange={setValue} + effort={effort} + onEffortChange={setEffort} + > + <ModelSelectorTrigger variant={variant} /> + <ModelSelectorContent /> + </ModelSelectorRoot> + </div> + ); +} + +function ComposedRow() { + const [value, setValue] = useState<string>(DEFAULT_MODEL_ID); + const [effort, setEffort] = useState<string>("medium"); + const [providerFilter, setProviderFilter] = useState<ReadonlySet<string>>( + new Set(), + ); + + // An empty filter means no filtering — all providers are shown. + const visibleGroups = [...modelsByProvider].filter( + ([provider]) => providerFilter.size === 0 || providerFilter.has(provider), + ); + + return ( + <div className="flex flex-col items-center gap-2"> + <span className="text-muted-foreground text-xs font-medium"> + With search, provider filters + groups + </span> + <ModelSelectorRoot + models={models} + value={value} + onValueChange={setValue} + effort={effort} + onEffortChange={setEffort} + > + <ModelSelectorTrigger /> + <ModelSelectorContent> + <ModelSelectorSearch /> + <ToggleGroupPrimitive.Root + type="multiple" + orientation="horizontal" + value={[...providerFilter]} + onValueChange={(next) => setProviderFilter(new Set(next))} + aria-label="Filter by provider" + className="flex flex-wrap gap-1 border-b px-3 py-2" + onKeyDown={(e) => { + // Keep cmdk's root handler from claiming Enter (the focused + // chip toggles instead of selecting the highlighted model) and + // Home/End (the roving focus group jumps chips, not the list). + if (e.key === "Enter" || e.key === "Home" || e.key === "End") { + e.stopPropagation(); + } + // Vertical arrows hand focus back to the model list, matching + // the packaged Thinking row. + if (e.key === "ArrowUp" || e.key === "ArrowDown") { + e.currentTarget + .closest("[cmdk-root]") + ?.querySelector<HTMLInputElement>("[cmdk-input]") + ?.focus(); + } + }} + > + {[...modelsByProvider.keys()].map((provider) => ( + <ToggleGroupPrimitive.Item + key={provider} + value={provider} + className={cn( + "rounded-full border px-2 py-0.5 text-xs transition-colors", + "text-muted-foreground hover:text-foreground", + "data-[state=on]:bg-accent data-[state=on]:text-accent-foreground data-[state=on]:border-transparent", + )} + > + {provider} + </ToggleGroupPrimitive.Item> + ))} + </ToggleGroupPrimitive.Root> + <ModelSelectorList> + <ModelSelectorEmpty /> + {visibleGroups.map(([provider, providerModels]) => ( + <ModelSelectorGroup key={provider} heading={provider}> + {providerModels.map((model) => ( + <ModelSelectorItem key={model.id} model={model} /> + ))} + </ModelSelectorGroup> + ))} + </ModelSelectorList> + <ModelSelectorEffort /> + </ModelSelectorContent> + </ModelSelectorRoot> + </div> + ); +} + +export function ModelSelectorSample() { + return ( + <SampleFrame className="flex h-auto flex-col items-center gap-8 p-8"> + <div className="flex flex-wrap items-start justify-center gap-x-10 gap-y-6"> + <VariantRow label="Outline (default)" variant="outline" /> + <VariantRow label="Ghost" variant="ghost" /> + <VariantRow label="Muted" variant="muted" /> + </div> + <ComposedRow /> + </SampleFrame> + ); +} diff --git a/apps/docs/components/docs/samples/number-roll.tsx b/apps/docs/components/docs/samples/number-roll.tsx new file mode 100644 index 0000000..f934ec0 --- /dev/null +++ b/apps/docs/components/docs/samples/number-roll.tsx @@ -0,0 +1,153 @@ +"use client"; + +import { useEffect, useState } from "react"; +import { MinusIcon, PauseIcon, PlayIcon, PlusIcon } from "lucide-react"; +import { NumberRoll } from "@/components/assistant-ui/number-roll"; +import { Button } from "@/components/ui/button"; +import { SampleFrame } from "@/components/docs/samples/sample-frame"; + +export function NumberRollSample() { + const [value, setValue] = useState(12); + + return ( + <SampleFrame className="flex h-auto flex-col items-center justify-center gap-6 p-10"> + <NumberRoll + value={value} + locales="en-US" + className="text-5xl font-semibold" + /> + <div className="flex items-center gap-2"> + <Button + variant="outline" + size="icon" + aria-label="Decrement" + onClick={() => setValue((v) => v - 1)} + > + <MinusIcon /> + </Button> + <Button + variant="outline" + size="icon" + aria-label="Increment" + onClick={() => setValue((v) => v + 1)} + > + <PlusIcon /> + </Button> + <Button + variant="outline" + onClick={() => setValue(Math.floor(Math.random() * 9000) + 100)} + > + Random + </Button> + </div> + </SampleFrame> + ); +} + +export function NumberRollCompactSample() { + const [value, setValue] = useState(700); + + return ( + <SampleFrame className="flex h-auto flex-col items-center justify-center gap-6 p-10"> + <NumberRoll + value={value} + locales="en-US" + format={{ notation: "compact" }} + className="text-5xl font-semibold" + /> + <div className="flex items-center gap-2"> + <Button variant="outline" onClick={() => setValue((v) => v - 400)}> + -400 + </Button> + <Button variant="outline" onClick={() => setValue((v) => v + 400)}> + +400 + </Button> + </div> + <span className="text-muted-foreground font-mono text-xs tabular-nums"> + value = {value} + </span> + </SampleFrame> + ); +} + +export function NumberRollFormatSample() { + const [index, setIndex] = useState(0); + const values = [1234.56, 98765.43, 412.07]; + const value = values[index % values.length]!; + + return ( + <SampleFrame className="flex h-auto flex-col items-center gap-6 p-10"> + <div className="grid grid-cols-[auto_1fr] items-baseline gap-x-8 gap-y-3 text-xl"> + <span className="text-muted-foreground text-xs">Currency</span> + <NumberRoll + value={value} + locales="en-US" + format={{ style: "currency", currency: "USD" }} + /> + <span className="text-muted-foreground text-xs">Percent</span> + <NumberRoll + value={value / 100000} + locales="en-US" + format={{ style: "percent" }} + /> + <span className="text-muted-foreground text-xs">zh-CN compact</span> + <NumberRoll + value={value * 100} + locales="zh-CN" + format={{ notation: "compact" }} + /> + <span className="text-muted-foreground text-xs">Suffix</span> + <NumberRoll value={value} locales="en-US" suffix=" tokens" /> + </div> + <Button variant="outline" onClick={() => setIndex((i) => i + 1)}> + Shuffle + </Button> + </SampleFrame> + ); +} + +export function NumberRollLiveSample() { + const [running, setRunning] = useState(false); + const [tokens, setTokens] = useState(0); + + useEffect(() => { + if (!running) return; + const interval = setInterval(() => { + setTokens((t) => t + Math.floor(Math.random() * 120) + 20); + }, 400); + return () => clearInterval(interval); + }, [running]); + + return ( + <SampleFrame className="flex h-auto flex-col items-center justify-center gap-6 p-10"> + <div className="text-muted-foreground flex items-baseline gap-2 text-sm"> + <NumberRoll + value={tokens} + locales="en-US" + format={{ notation: "compact", maximumFractionDigits: 1 }} + className="text-foreground text-3xl font-semibold" + /> + tokens + </div> + <div className="flex items-center gap-2"> + <Button + variant="outline" + size="icon" + aria-label={running ? "Pause" : "Play"} + onClick={() => setRunning((r) => !r)} + > + {running ? <PauseIcon /> : <PlayIcon />} + </Button> + <Button + variant="outline" + onClick={() => { + setRunning(false); + setTokens(0); + }} + > + Reset + </Button> + </div> + </SampleFrame> + ); +} diff --git a/apps/docs/components/docs/samples/o11y/capability-samples.tsx b/apps/docs/components/docs/samples/o11y/capability-samples.tsx new file mode 100644 index 0000000..681fbd3 --- /dev/null +++ b/apps/docs/components/docs/samples/o11y/capability-samples.tsx @@ -0,0 +1,245 @@ +"use client"; + +import { useEffect, useMemo, useState } from "react"; +import { ChevronRightIcon } from "lucide-react"; +import { useAui, AuiProvider, useAuiState, AuiIf } from "@assistant-ui/store"; +import { + SpanPrimitive, + SpanResource, + type SpanData, +} from "@assistant-ui/react-o11y"; +import { ClientOnly } from "./client-only"; + +function SpanLatency() { + const latencyMs = useAuiState((s) => s.span.latencyMs) as number | null; + return ( + <span className="text-muted-foreground ml-auto pl-3 font-mono text-xs tabular-nums"> + {latencyMs == null ? "" : `${latencyMs} ms`} + </span> + ); +} + +function TreeSpanRow() { + return ( + <SpanPrimitive.Root className="hover:bg-accent/40 flex items-center rounded-md"> + <SpanPrimitive.Indent + baseIndent={8} + indentPerLevel={18} + className="flex w-full items-center gap-2 py-1.5 pr-2" + > + <AuiIf condition={(s) => s.span.hasChildren}> + <SpanPrimitive.CollapseToggle className="text-muted-foreground hover:text-foreground flex size-4 shrink-0 items-center justify-center rounded [&_svg]:transition-transform data-[collapsed=false]:[&_svg]:rotate-90"> + <ChevronRightIcon className="size-3.5" /> + </SpanPrimitive.CollapseToggle> + </AuiIf> + <AuiIf condition={(s) => !s.span.hasChildren}> + <span className="size-4 shrink-0" /> + </AuiIf> + <SpanPrimitive.StatusIndicator className="size-2 shrink-0 rounded-full data-[span-status=completed]:bg-green-500 data-[span-status=failed]:bg-red-500 data-[span-status=running]:animate-pulse data-[span-status=running]:bg-yellow-500 data-[span-status=skipped]:bg-zinc-400" /> + <SpanPrimitive.TypeBadge className="border-border text-muted-foreground shrink-0 rounded border px-1.5 py-0.5 font-mono text-[10px]" /> + <SpanPrimitive.Name className="truncate text-sm" /> + <SpanLatency /> + </SpanPrimitive.Indent> + </SpanPrimitive.Root> + ); +} + +function Tree({ spans }: { spans: SpanData[] }) { + const aui = useAui({ span: SpanResource({ spans }) }); + return ( + <AuiProvider value={aui}> + <div className="border-border bg-background w-full max-w-md rounded-lg border p-2"> + <SpanPrimitive.Children components={{ Span: TreeSpanRow }} /> + </div> + </AuiProvider> + ); +} + +const NOW = Date.now(); + +const STATUS_SPANS: SpanData[] = [ + { + id: "s-completed", + parentSpanId: null, + name: "retrieve-context", + type: "tool", + status: "completed", + startedAt: NOW, + endedAt: NOW + 420, + latencyMs: 420, + }, + { + id: "s-running", + parentSpanId: null, + name: "llm-generate", + type: "action", + status: "running", + startedAt: NOW, + endedAt: null, + latencyMs: null, + }, + { + id: "s-failed", + parentSpanId: null, + name: "store-feedback", + type: "action", + status: "failed", + startedAt: NOW, + endedAt: NOW + 110, + latencyMs: 110, + }, + { + id: "s-skipped", + parentSpanId: null, + name: "cache-write", + type: "flow", + status: "skipped", + startedAt: NOW, + endedAt: NOW, + latencyMs: 0, + }, +]; + +export function StatusSample() { + return ( + <ClientOnly minHeight={150}> + <Tree spans={STATUS_SPANS} /> + </ClientOnly> + ); +} + +const COLLAPSE_SPANS: SpanData[] = [ + { + id: "c-root", + parentSpanId: null, + name: "agent-pipeline", + type: "pipeline", + status: "completed", + startedAt: NOW, + endedAt: NOW + 2400, + latencyMs: 2400, + }, + { + id: "c-retrieve", + parentSpanId: "c-root", + name: "retrieve-context", + type: "tool", + status: "completed", + startedAt: NOW + 20, + endedAt: NOW + 700, + latencyMs: 680, + }, + { + id: "c-vector", + parentSpanId: "c-retrieve", + name: "vector-search", + type: "tool", + status: "completed", + startedAt: NOW + 40, + endedAt: NOW + 460, + latencyMs: 420, + }, + { + id: "c-rerank", + parentSpanId: "c-retrieve", + name: "rerank", + type: "action", + status: "completed", + startedAt: NOW + 470, + endedAt: NOW + 690, + latencyMs: 220, + }, + { + id: "c-generate", + parentSpanId: "c-root", + name: "llm-generate", + type: "action", + status: "completed", + startedAt: NOW + 720, + endedAt: NOW + 2300, + latencyMs: 1580, + }, +]; + +export function CollapseSample() { + return ( + <ClientOnly minHeight={170}> + <Tree spans={COLLAPSE_SPANS} /> + </ClientOnly> + ); +} + +type StreamStep = Omit<SpanData, "status" | "endedAt" | "latencyMs">; + +const STREAM_SCRIPT: StreamStep[] = [ + { + id: "x-run", + parentSpanId: null, + name: "agent-run", + type: "pipeline", + startedAt: 0, + }, + { + id: "x-load", + parentSpanId: "x-run", + name: "load-context", + type: "tool", + startedAt: 0, + }, + { + id: "x-llm", + parentSpanId: "x-run", + name: "llm-call", + type: "action", + startedAt: 0, + }, + { + id: "x-stream", + parentSpanId: "x-llm", + name: "stream-tokens", + type: "flow", + startedAt: 0, + }, + { + id: "x-format", + parentSpanId: "x-run", + name: "format-response", + type: "action", + startedAt: 0, + }, +]; + +function StreamingInner() { + const [count, setCount] = useState(1); + + useEffect(() => { + const id = setInterval(() => { + setCount((c) => (c >= STREAM_SCRIPT.length ? 1 : c + 1)); + }, 1100); + return () => clearInterval(id); + }, []); + + const spans = useMemo<SpanData[]>( + () => + STREAM_SCRIPT.slice(0, count).map((step, i) => { + const running = i === count - 1; + return { + ...step, + status: running ? "running" : "completed", + endedAt: running ? null : step.startedAt + 240, + latencyMs: running ? null : 240, + }; + }), + [count], + ); + + return <Tree spans={spans} />; +} + +export function StreamingSample() { + return ( + <ClientOnly minHeight={170}> + <StreamingInner /> + </ClientOnly> + ); +} diff --git a/apps/docs/components/docs/samples/o11y/client-only.tsx b/apps/docs/components/docs/samples/o11y/client-only.tsx new file mode 100644 index 0000000..daf45aa --- /dev/null +++ b/apps/docs/components/docs/samples/o11y/client-only.tsx @@ -0,0 +1,25 @@ +"use client"; + +import { useEffect, useState, type ReactNode } from "react"; + +export function ClientOnly({ + children, + minHeight, +}: { + children: ReactNode; + minHeight: number; +}) { + const [mounted, setMounted] = useState(false); + useEffect(() => setMounted(true), []); + + if (!mounted) { + return ( + <div + className="border-border bg-muted/20 animate-pulse rounded-lg border" + style={{ minHeight }} + /> + ); + } + + return <>{children}</>; +} diff --git a/apps/docs/components/docs/samples/o11y/mock-spans.ts b/apps/docs/components/docs/samples/o11y/mock-spans.ts new file mode 100644 index 0000000..c81cbcb --- /dev/null +++ b/apps/docs/components/docs/samples/o11y/mock-spans.ts @@ -0,0 +1,156 @@ +import type { SpanData } from "@assistant-ui/react-o11y"; + +const BASE = Date.now() - 5000; + +export const mockSpans: SpanData[] = [ + { + id: "span-1", + parentSpanId: null, + name: "POST /api/chat", + type: "api", + status: "completed", + startedAt: BASE, + endedAt: BASE + 4200, + latencyMs: 4200, + }, + { + id: "span-2", + parentSpanId: "span-1", + name: "authenticate", + type: "action", + status: "completed", + startedAt: BASE + 50, + endedAt: BASE + 200, + latencyMs: 150, + }, + { + id: "span-3", + parentSpanId: "span-1", + name: "agent-pipeline", + type: "pipeline", + status: "completed", + startedAt: BASE + 220, + endedAt: BASE + 4100, + latencyMs: 3880, + }, + { + id: "span-4", + parentSpanId: "span-3", + name: "retrieve-context", + type: "tool", + status: "completed", + startedAt: BASE + 250, + endedAt: BASE + 900, + latencyMs: 650, + }, + { + id: "span-5", + parentSpanId: "span-3", + name: "llm-generate", + type: "action", + status: "completed", + startedAt: BASE + 920, + endedAt: BASE + 2800, + latencyMs: 1880, + }, + { + id: "span-6", + parentSpanId: "span-3", + name: "web-search", + type: "tool", + status: "completed", + startedAt: BASE + 2850, + endedAt: BASE + 3500, + latencyMs: 650, + }, + { + id: "span-7", + parentSpanId: "span-3", + name: "llm-summarize", + type: "action", + status: "completed", + startedAt: BASE + 3520, + endedAt: BASE + 4050, + latencyMs: 530, + }, + { + id: "span-8", + parentSpanId: "span-1", + name: "log-response", + type: "action", + status: "completed", + startedAt: BASE + 4110, + endedAt: BASE + 4180, + latencyMs: 70, + }, + { + id: "span-9", + parentSpanId: null, + name: "POST /api/feedback", + type: "api", + status: "completed", + startedAt: BASE + 4500, + endedAt: BASE + 4700, + latencyMs: 200, + }, + { + id: "span-10", + parentSpanId: "span-9", + name: "validate-input", + type: "action", + status: "completed", + startedAt: BASE + 4520, + endedAt: BASE + 4560, + latencyMs: 40, + }, + { + id: "span-11", + parentSpanId: "span-9", + name: "store-feedback", + type: "action", + status: "failed", + startedAt: BASE + 4570, + endedAt: BASE + 4680, + latencyMs: 110, + }, + { + id: "span-12", + parentSpanId: null, + name: "GET /api/status", + type: "api", + status: "running", + startedAt: BASE + 4800, + endedAt: null, + latencyMs: null, + }, + { + id: "span-13", + parentSpanId: "span-12", + name: "health-check", + type: "flow", + status: "running", + startedAt: BASE + 4830, + endedAt: null, + latencyMs: null, + }, + { + id: "span-14", + parentSpanId: "span-4", + name: "vector-search", + type: "tool", + status: "completed", + startedAt: BASE + 280, + endedAt: BASE + 700, + latencyMs: 420, + }, + { + id: "span-15", + parentSpanId: "span-4", + name: "rerank", + type: "action", + status: "completed", + startedAt: BASE + 710, + endedAt: BASE + 880, + latencyMs: 170, + }, +]; diff --git a/apps/docs/components/docs/samples/o11y/waterfall-bar.tsx b/apps/docs/components/docs/samples/o11y/waterfall-bar.tsx new file mode 100644 index 0000000..c108a62 --- /dev/null +++ b/apps/docs/components/docs/samples/o11y/waterfall-bar.tsx @@ -0,0 +1,43 @@ +"use client"; + +import { type CSSProperties } from "react"; +import { useAuiState } from "@assistant-ui/store"; +import { SpanPrimitive, type SpanItemState } from "@assistant-ui/react-o11y"; +import { + FALLBACK_COLOR, + TYPE_COLORS, + useWaterfallLayout, +} from "./waterfall-timeline"; + +const STATUS_OPACITY: Record<SpanItemState["status"], number> = { + running: 0.7, + completed: 1, + failed: 1, + skipped: 0.5, +}; + +export function WaterfallBar() { + const { barHeight } = useWaterfallLayout(); + const status = useAuiState((s) => s.span.status) as SpanItemState["status"]; + const type = useAuiState((s) => s.span.type); + const fill = TYPE_COLORS[type] ?? FALLBACK_COLOR; + const opacity = STATUS_OPACITY[status]; + + return ( + <SpanPrimitive.TimelineBar + className={status === "running" ? "animate-pulse" : ""} + style={ + { + "--span-timeline-min-width": "4px", + top: 4, + height: barHeight - 8, + borderRadius: 3, + background: fill, + opacity, + boxShadow: + status === "failed" ? "inset 0 0 0 2px hsl(0 84% 60%)" : undefined, + } as CSSProperties & { "--span-timeline-min-width": string } + } + /> + ); +} diff --git a/apps/docs/components/docs/samples/o11y/waterfall-row.tsx b/apps/docs/components/docs/samples/o11y/waterfall-row.tsx new file mode 100644 index 0000000..13d5c07 --- /dev/null +++ b/apps/docs/components/docs/samples/o11y/waterfall-row.tsx @@ -0,0 +1,50 @@ +"use client"; + +import { SpanPrimitive } from "@assistant-ui/react-o11y"; +import { AuiIf } from "@assistant-ui/store"; +import { WaterfallBar } from "./waterfall-bar"; +import { LABEL_WIDTH, useWaterfallLayout } from "./waterfall-timeline"; + +export function WaterfallRow() { + const { barWidth, contentWidth, barHeight } = useWaterfallLayout(); + + return ( + <SpanPrimitive.Root + className="group flex items-center" + style={{ width: contentWidth, height: barHeight }} + > + <SpanPrimitive.Indent + baseIndent={8} + indentPerLevel={12} + className="border-border bg-background group-hover:bg-accent/50 sticky left-0 z-10 flex shrink-0 items-center gap-1 overflow-hidden border-r px-2" + style={{ width: LABEL_WIDTH, height: barHeight }} + > + <AuiIf condition={(s) => s.span.hasChildren}> + <SpanPrimitive.CollapseToggle className="text-muted-foreground hover:text-foreground flex shrink-0 items-center justify-center rounded p-0.5 [&_svg]:transition-transform data-[collapsed=true]:[&_svg]:-rotate-90"> + <svg + aria-hidden="true" + className="size-3.5" + viewBox="0 0 16 16" + fill="currentColor" + > + <path d="M4 6l4 4 4-4H4z" /> + </svg> + </SpanPrimitive.CollapseToggle> + </AuiIf> + <AuiIf condition={(s) => !s.span.hasChildren}> + <span className="w-4.5 shrink-0" /> + </AuiIf> + <SpanPrimitive.StatusIndicator className="size-1.5 shrink-0 rounded-full bg-current" /> + <SpanPrimitive.TypeBadge className="border-border text-muted-foreground shrink-0 rounded border px-1 text-[10px]" /> + <SpanPrimitive.Name className="truncate text-sm" /> + </SpanPrimitive.Indent> + + <div + className="group-hover:bg-accent/30 relative" + style={{ width: barWidth, height: barHeight }} + > + <WaterfallBar /> + </div> + </SpanPrimitive.Root> + ); +} diff --git a/apps/docs/components/docs/samples/o11y/waterfall-timeline.tsx b/apps/docs/components/docs/samples/o11y/waterfall-timeline.tsx new file mode 100644 index 0000000..6df6bf8 --- /dev/null +++ b/apps/docs/components/docs/samples/o11y/waterfall-timeline.tsx @@ -0,0 +1,257 @@ +"use client"; + +import { + createContext, + useContext, + useEffect, + useLayoutEffect, + useMemo, + useRef, + useState, +} from "react"; +import { useAuiState } from "@assistant-ui/store"; +import { SpanPrimitive, type SpanState } from "@assistant-ui/react-o11y"; +import { WaterfallRow } from "./waterfall-row"; + +export const LABEL_WIDTH = 200; +const MAX_LIST_HEIGHT = 400; +const MIN_ZOOM = 1; +const MAX_ZOOM = 20; +const RIGHT_PADDING_RATIO = 0.08; + +export const TYPE_COLORS: Record<string, string> = { + action: "hsl(221 83% 53%)", + api: "hsl(262 83% 58%)", + tool: "hsl(142 71% 45%)", + flow: "hsl(25 95% 53%)", + pipeline: "hsl(340 75% 55%)", +}; + +export const FALLBACK_COLOR = "hsl(220 9% 46%)"; + +export type WaterfallLayoutContextValue = { + barWidth: number; + timeRange: { min: number; max: number }; + barHeight: number; + contentWidth: number; +}; + +export const WaterfallLayoutContext = + createContext<WaterfallLayoutContextValue | null>(null); + +export function useWaterfallLayout(): WaterfallLayoutContextValue { + const ctx = useContext(WaterfallLayoutContext); + if (!ctx) { + throw new Error( + "useWaterfallLayout must be used within WaterfallLayoutContext", + ); + } + return ctx; +} + +function formatTime(ms: number): string { + if (ms < 1000) return `${Math.round(ms)} ms`; + return `${(ms / 1000).toFixed(1)} s`; +} + +function TimeAxisTicks({ + timeRange, + barWidth, +}: { + timeRange: { min: number; max: number }; + barWidth: number; +}) { + const duration = timeRange.max - timeRange.min; + const tickCount = Math.min(5, Math.max(2, Math.floor(barWidth / 100))); + const ticks = Array.from({ length: tickCount + 1 }, (_, i) => { + const t = (i / tickCount) * duration; + const x = (i / tickCount) * barWidth; + return { t, x }; + }); + + return ( + <svg + aria-hidden="true" + width={barWidth} + height={28} + className="overflow-visible" + > + {ticks.map(({ t, x }) => ( + <g key={x}> + <line + x1={x} + y1={20} + x2={x} + y2={28} + stroke="currentColor" + className="text-border" + /> + <text + x={x} + y={14} + textAnchor="middle" + className="fill-muted-foreground text-[10px]" + > + {formatTime(t)} + </text> + </g> + ))} + </svg> + ); +} + +export function WaterfallTimeline() { + const outerRef = useRef<HTMLDivElement>(null); + const scrollRef = useRef<HTMLDivElement>(null); + + const [baseBarWidth, setBaseBarWidth] = useState(400); + const [zoom, setZoom] = useState(1); + const zoomRef = useRef(1); + const baseBarWidthRef = useRef(400); + const scrollAdjustRef = useRef<{ + mouseX: number; + ratio: number; + } | null>(null); + + const barWidth = Math.max(200, Math.round(baseBarWidth * zoom)); + + const hasSpans = useAuiState((s) => s.span.hasChildren); + const timeRange = useAuiState( + (s) => s.span.timeRange, + ) as SpanState["timeRange"]; + + useEffect(() => { + const el = outerRef.current; + if (!el) return; + + const observer = new ResizeObserver((entries) => { + const entry = entries[0]; + if (entry) { + const w = Math.max(200, entry.contentRect.width - LABEL_WIDTH); + baseBarWidthRef.current = w; + setBaseBarWidth(w); + } + }); + + observer.observe(el); + return () => observer.disconnect(); + }, [hasSpans]); + + useEffect(() => { + const el = scrollRef.current; + if (!el) return; + + const handleWheel = (e: WheelEvent) => { + if (!e.metaKey && !e.ctrlKey) return; + e.preventDefault(); + + const rect = el.getBoundingClientRect(); + const mouseXInView = e.clientX - rect.left - LABEL_WIDTH; + const mouseXInContent = el.scrollLeft + mouseXInView; + const currentBarWidth = baseBarWidthRef.current * zoomRef.current; + const ratio = currentBarWidth > 0 ? mouseXInContent / currentBarWidth : 0; + + const newZoom = Math.max( + MIN_ZOOM, + Math.min(MAX_ZOOM, zoomRef.current * 2 ** (-e.deltaY * 0.003)), + ); + + scrollAdjustRef.current = { mouseX: mouseXInView, ratio }; + zoomRef.current = newZoom; + setZoom(newZoom); + }; + + el.addEventListener("wheel", handleWheel, { passive: false }); + return () => el.removeEventListener("wheel", handleWheel); + }, [hasSpans]); + + useLayoutEffect(() => { + const adj = scrollAdjustRef.current; + const el = scrollRef.current; + if (!adj || !el) return; + scrollAdjustRef.current = null; + + const newBarX = adj.ratio * barWidth; + el.scrollLeft = newBarX - adj.mouseX; + }, [barWidth]); + + const renderTimeRange = useMemo(() => { + const duration = timeRange.max - timeRange.min; + return { + min: timeRange.min, + max: timeRange.max + duration * RIGHT_PADDING_RATIO, + }; + }, [timeRange]); + + const contentWidth = LABEL_WIDTH + barWidth; + const scrollMaxHeight = MAX_LIST_HEIGHT + 28; + + const layoutValue = useMemo( + () => ({ + barWidth, + timeRange: renderTimeRange, + barHeight: 32, + contentWidth, + }), + [barWidth, renderTimeRange, contentWidth], + ); + + if (!hasSpans) { + return ( + <div className="border-border text-muted-foreground rounded-lg border py-12 text-center text-sm"> + No spans recorded. + </div> + ); + } + + return ( + <div + ref={outerRef} + className="border-border overflow-hidden rounded-lg border" + > + <div + ref={scrollRef} + className="overflow-auto" + style={{ maxHeight: scrollMaxHeight }} + > + <div + className="border-border bg-background sticky top-0 z-20 flex border-b" + style={{ width: contentWidth }} + > + <div + className="border-border bg-background text-muted-foreground sticky left-0 z-30 shrink-0 border-r px-2 py-1.5 text-xs" + style={{ width: LABEL_WIDTH }} + > + Span + </div> + <div style={{ width: barWidth, height: 28 }}> + <TimeAxisTicks timeRange={renderTimeRange} barWidth={barWidth} /> + </div> + </div> + + <WaterfallLayoutContext.Provider value={layoutValue}> + <SpanPrimitive.Timeline + timeRange={renderTimeRange} + style={{ width: contentWidth }} + > + <SpanPrimitive.Children> + {() => <WaterfallRow />} + </SpanPrimitive.Children> + </SpanPrimitive.Timeline> + </WaterfallLayoutContext.Provider> + </div> + + <div className="border-border text-muted-foreground flex items-center gap-4 border-t px-3 py-2 text-xs"> + {Object.entries(TYPE_COLORS).map(([label, color]) => ( + <div key={label} className="flex items-center gap-1.5"> + <span + className="size-2.5 rounded-sm" + style={{ background: color }} + /> + <span>{label}</span> + </div> + ))} + </div> + </div> + ); +} diff --git a/apps/docs/components/docs/samples/o11y/waterfall.tsx b/apps/docs/components/docs/samples/o11y/waterfall.tsx new file mode 100644 index 0000000..e847032 --- /dev/null +++ b/apps/docs/components/docs/samples/o11y/waterfall.tsx @@ -0,0 +1,25 @@ +"use client"; + +import { useAui, AuiProvider } from "@assistant-ui/store"; +import { SpanResource } from "@assistant-ui/react-o11y"; +import { mockSpans } from "./mock-spans"; +import { WaterfallTimeline } from "./waterfall-timeline"; +import { ClientOnly } from "./client-only"; + +function WaterfallInner() { + const aui = useAui({ span: SpanResource({ spans: mockSpans }) }); + + return ( + <AuiProvider value={aui}> + <WaterfallTimeline /> + </AuiProvider> + ); +} + +export function WaterfallSample() { + return ( + <ClientOnly minHeight={300}> + <WaterfallInner /> + </ClientOnly> + ); +} diff --git a/apps/docs/components/docs/samples/part-grouping.tsx b/apps/docs/components/docs/samples/part-grouping.tsx new file mode 100644 index 0000000..2fa4ca6 --- /dev/null +++ b/apps/docs/components/docs/samples/part-grouping.tsx @@ -0,0 +1,59 @@ +"use client"; + +import { ChevronDownIcon, ChevronUpIcon } from "lucide-react"; +import { useState } from "react"; +import { SampleFrame } from "@/components/docs/samples/sample-frame"; + +const CollapsibleGroup = ({ + groupKey, + itemCount, + children, +}: { + groupKey: string; + itemCount: number; + children: React.ReactNode; +}) => { + const [isCollapsed, setIsCollapsed] = useState(false); + + return ( + <div className="my-2 overflow-hidden rounded-lg border"> + <button + type="button" + onClick={() => setIsCollapsed(!isCollapsed)} + className="hover:bg-muted/50 flex w-full items-center justify-between p-3" + > + <span> + Group {groupKey} ({itemCount} items) + </span> + {isCollapsed ? <ChevronDownIcon /> : <ChevronUpIcon />} + </button> + {!isCollapsed && <div className="border-t p-3">{children}</div>} + </div> + ); +}; + +export const PartGroupingSample = () => { + return ( + <SampleFrame className="bg-background h-auto p-4"> + <div className="space-y-2"> + <p className="text-muted-foreground text-sm"> + Message parts grouped by type: + </p> + <CollapsibleGroup groupKey="research" itemCount={2}> + <div className="space-y-2 text-sm"> + <div className="bg-muted/50 rounded p-2">Search: climate data</div> + <div className="bg-muted/50 rounded p-2"> + Search: renewable energy stats + </div> + </div> + </CollapsibleGroup> + <CollapsibleGroup groupKey="analysis" itemCount={2}> + <div className="space-y-2 text-sm"> + <div className="bg-muted/50 rounded p-2">Analyzing trends...</div> + <div className="bg-muted/50 rounded p-2">Generating summary...</div> + </div> + </CollapsibleGroup> + </div> + </SampleFrame> + ); +}; diff --git a/apps/docs/components/docs/samples/quote-composer.tsx b/apps/docs/components/docs/samples/quote-composer.tsx new file mode 100644 index 0000000..5756925 --- /dev/null +++ b/apps/docs/components/docs/samples/quote-composer.tsx @@ -0,0 +1,53 @@ +"use client"; + +import { ArrowUpIcon, QuoteIcon, XIcon } from "lucide-react"; +import { SampleFrame } from "@/components/docs/samples/sample-frame"; + +export function QuoteComposerSample() { + return ( + <SampleFrame className="relative flex h-auto items-center justify-center p-8 pb-10"> + <div className="bg-background w-full max-w-xl rounded-2xl border shadow-xs"> + {/* Quote preview — matches ComposerQuotePreview styling */} + <div className="bg-muted/60 mx-3 mt-2 flex items-start gap-2 rounded-lg px-3 py-2"> + <QuoteIcon className="text-muted-foreground/70 mt-0.5 size-3.5 shrink-0" /> + <span className="text-muted-foreground line-clamp-2 min-w-0 flex-1 text-sm"> + The runtime system follows a layered architecture with + framework-agnostic core, public API adapters, and React context + hooks + </span> + <button + type="button" + aria-label="Dismiss quote" + className="text-muted-foreground/70 hover:bg-accent hover:text-foreground shrink-0 rounded-sm p-0.5 transition-colors" + > + <XIcon className="size-3.5" /> + </button> + </div> + + {/* Input row */} + <div className="flex items-end gap-2 px-4 py-3"> + <span className="text-foreground flex-1 text-sm leading-relaxed"> + Can you explain how the layers connect? + </span> + <button + type="button" + className="bg-foreground text-background shrink-0 rounded-full p-1.5" + > + <ArrowUpIcon className="size-4" /> + </button> + </div> + </div> + + {/* Footnote linking to component page */} + <p className="text-muted-foreground/60 absolute end-4 bottom-2.5 text-[11px]"> + Built-in{" "} + <a + href="/docs/ui/quote" + className="hover:text-foreground underline underline-offset-2" + > + Quote component + </a> + </p> + </SampleFrame> + ); +} diff --git a/apps/docs/components/docs/samples/quote.tsx b/apps/docs/components/docs/samples/quote.tsx new file mode 100644 index 0000000..0ebf61f --- /dev/null +++ b/apps/docs/components/docs/samples/quote.tsx @@ -0,0 +1,59 @@ +"use client"; + +import { QuoteIcon, XIcon } from "lucide-react"; +import { SampleFrame } from "@/components/docs/samples/sample-frame"; + +export function QuoteSample() { + return ( + <SampleFrame className="flex h-auto flex-wrap items-start justify-center gap-10 p-8"> + {/* Quote Block — as it appears in user messages */} + <div className="flex flex-col items-center gap-3"> + <span className="text-muted-foreground text-xs">User message</span> + <div className="bg-muted rounded-2xl px-4 py-2.5"> + <div className="mb-2 flex items-start gap-1.5"> + <QuoteIcon className="text-muted-foreground/60 mt-0.5 size-3 shrink-0" /> + <p className="text-muted-foreground/80 line-clamp-2 min-w-0 text-sm italic"> + The runtime system follows a layered architecture + </p> + </div> + <p className="text-foreground text-sm"> + Can you explain how the layers connect? + </p> + </div> + </div> + + {/* Selection Toolbar — floating toolbar on text selection */} + <div className="flex flex-col items-center gap-3"> + <span className="text-muted-foreground text-xs">Selection Toolbar</span> + <div className="bg-popover flex items-center gap-1 rounded-lg border px-1 py-1 shadow-md"> + <div className="text-popover-foreground hover:bg-accent flex items-center gap-1.5 rounded-md px-2.5 py-1 text-sm transition-colors"> + <QuoteIcon className="size-3.5" /> + Quote + </div> + </div> + </div> + + {/* Composer Preview — quote preview inside the composer */} + <div className="flex flex-col items-center gap-3"> + <span className="text-muted-foreground text-xs">Composer Preview</span> + <div className="w-64 rounded-xl border"> + <div className="bg-muted/60 mx-3 mt-2 flex items-start gap-2 rounded-lg px-3 py-2"> + <QuoteIcon className="text-muted-foreground/70 mt-0.5 size-3.5 shrink-0" /> + <span className="text-muted-foreground line-clamp-2 min-w-0 flex-1 text-sm"> + The runtime system follows a layered architecture + </span> + <button + type="button" + className="text-muted-foreground/70 hover:bg-accent hover:text-foreground shrink-0 rounded-sm p-0.5 transition-colors" + > + <XIcon className="size-3.5" /> + </button> + </div> + <div className="text-muted-foreground/50 px-3 py-2.5 text-sm"> + Send a message... + </div> + </div> + </div> + </SampleFrame> + ); +} diff --git a/apps/docs/components/docs/samples/reasoning.tsx b/apps/docs/components/docs/samples/reasoning.tsx new file mode 100644 index 0000000..9b0c67f --- /dev/null +++ b/apps/docs/components/docs/samples/reasoning.tsx @@ -0,0 +1,129 @@ +"use client"; + +import { useEffect, useState } from "react"; +import type { VariantProps } from "class-variance-authority"; +import { PlayIcon } from "lucide-react"; +import { SampleFrame } from "@/components/docs/samples/sample-frame"; +import { Button } from "@/components/ui/button"; +import { + ReasoningRoot, + ReasoningTrigger, + ReasoningContent, + ReasoningText, + type reasoningVariants, +} from "@/components/assistant-ui/reasoning"; + +function ReasoningDemo({ variant }: VariantProps<typeof reasoningVariants>) { + return ( + <ReasoningRoot variant={variant} className="mb-0"> + <ReasoningTrigger /> + <ReasoningContent> + <ReasoningText> + <p>Let me think about this step by step...</p> + <p> + First, I need to consider the main factors involved in this problem. + </p> + </ReasoningText> + </ReasoningContent> + </ReasoningRoot> + ); +} + +function VariantRow({ + label, + variant, +}: { + label: string; + variant?: "outline" | "ghost" | "muted"; +}) { + return ( + <div className="flex flex-col gap-2"> + <span className="text-muted-foreground text-xs font-medium">{label}</span> + <ReasoningDemo variant={variant} /> + </div> + ); +} + +export function ReasoningSample() { + return ( + <SampleFrame className="flex h-auto flex-col gap-4 p-4"> + <VariantRow label="Outline (default)" variant="outline" /> + <VariantRow label="Ghost" variant="ghost" /> + <VariantRow label="Muted" variant="muted" /> + </SampleFrame> + ); +} + +function ReasoningGroupDemo() { + const [isStreaming, setIsStreaming] = useState(false); + const [isOpen, setIsOpen] = useState(false); + const [streamedText, setStreamedText] = useState(""); + + const fullText = + "Let me think about this step by step...\n\nFirst, I need to analyze the problem carefully. The key factors to consider are the constraints and requirements.\n\nAfter evaluating all options, the best approach would be to implement a solution that balances performance and maintainability."; + + useEffect(() => { + if (!isStreaming) return; + + setIsOpen(true); + setStreamedText(""); + let index = 0; + const interval = setInterval(() => { + if (index < fullText.length) { + setStreamedText(fullText.slice(0, index + 1)); + index++; + } else { + setIsStreaming(false); + clearInterval(interval); + } + }, 20); + return () => clearInterval(interval); + }, [isStreaming]); + + const handleStart = () => { + setStreamedText(""); + setIsStreaming(true); + }; + + return ( + <div className="flex flex-col gap-3"> + <div className="flex items-center gap-2"> + <Button + size="sm" + variant="outline" + onClick={handleStart} + disabled={isStreaming} + className="gap-1.5" + > + <PlayIcon className="size-3" /> + {isStreaming ? "Streaming..." : "Start Reasoning"} + </Button> + </div> + <ReasoningRoot + variant="muted" + open={isOpen} + onOpenChange={setIsOpen} + className="mb-0" + > + <ReasoningTrigger active={isStreaming} /> + <ReasoningContent> + <ReasoningText className="whitespace-pre-wrap"> + {streamedText || ( + <span className="text-muted-foreground/50 italic"> + Click "Start Reasoning" to see the streaming effect + </span> + )} + </ReasoningText> + </ReasoningContent> + </ReasoningRoot> + </div> + ); +} + +export function ReasoningGroupSample() { + return ( + <SampleFrame className="h-auto p-4"> + <ReasoningGroupDemo /> + </SampleFrame> + ); +} diff --git a/apps/docs/components/docs/samples/sample-frame.tsx b/apps/docs/components/docs/samples/sample-frame.tsx new file mode 100644 index 0000000..84abfc0 --- /dev/null +++ b/apps/docs/components/docs/samples/sample-frame.tsx @@ -0,0 +1,85 @@ +"use client"; + +import { useState } from "react"; +import { CheckIcon, CodeIcon, CopyIcon, XIcon } from "lucide-react"; +import ShikiHighlighter from "react-shiki"; +import { cn } from "@/lib/utils"; + +type SampleFrameProps = { + code?: string; + children: React.ReactNode; + className?: string; +}; + +export function SampleFrame({ code, children, className }: SampleFrameProps) { + const [showCode, setShowCode] = useState(false); + const [copied, setCopied] = useState(false); + + const ToggleIcon = showCode ? XIcon : CodeIcon; + const buttonLabel = showCode ? "Hide Code" : "View Code"; + + const handleCopy = async () => { + if (!code) return; + await navigator.clipboard.writeText(code); + setCopied(true); + setTimeout(() => setCopied(false), 2000); + }; + + return ( + <div className="not-prose my-6"> + {code && ( + <div className="flex justify-end gap-2 px-1 pb-2"> + {showCode && ( + <button + type="button" + onClick={handleCopy} + className="text-muted-foreground hover:bg-muted hover:text-foreground flex items-center gap-1.5 rounded-md px-2 py-1 text-xs transition-colors" + > + {copied ? ( + <> + <CheckIcon className="size-3.5" /> + Copied + </> + ) : ( + <> + <CopyIcon className="size-3.5" /> + Copy + </> + )} + </button> + )} + <button + type="button" + onClick={() => setShowCode(!showCode)} + className="text-muted-foreground hover:bg-muted hover:text-foreground flex items-center gap-1.5 rounded-md px-2 py-1 text-xs transition-colors" + > + <ToggleIcon className="size-3.5" /> + {buttonLabel} + </button> + </div> + )} + + <div + className={cn( + "border-border/50 relative h-150 rounded-xl border", + className, + )} + > + {children} + </div> + + {showCode && code && ( + <div className="mt-3 overflow-hidden rounded-xl bg-zinc-950 text-sm [&_pre]:m-0! [&_pre]:bg-transparent! [&_pre]:p-4!"> + <ShikiHighlighter + language="tsx" + theme={{ dark: "vitesse-dark", light: "vitesse-dark" }} + addDefaultStyles={false} + showLanguage={false} + > + {code.trim()} + </ShikiHighlighter> + </div> + )} + </div> + ); +} diff --git a/apps/docs/components/docs/samples/sample-runtime-provider.tsx b/apps/docs/components/docs/samples/sample-runtime-provider.tsx new file mode 100644 index 0000000..e2f2d68 --- /dev/null +++ b/apps/docs/components/docs/samples/sample-runtime-provider.tsx @@ -0,0 +1,39 @@ +"use client"; + +import type { ReactNode } from "react"; +import { + AssistantRuntimeProvider, + useLocalRuntime, + type ChatModelAdapter, + type ThreadMessageLike, +} from "@assistant-ui/react"; + +const noOpAdapter: ChatModelAdapter = { + async *run() { + yield { content: [{ type: "text", text: "This is a demo." }] }; + }, +}; + +const defaultMessages: ThreadMessageLike[] = [ + { role: "user", content: "What is assistant-ui?" }, + { + role: "assistant", + content: + "assistant-ui is a set of React components for building AI chat interfaces. It provides unstyled primitives that handle state management, streaming, and accessibility — you bring the design.", + }, +]; + +export function SampleRuntimeProvider({ + messages = defaultMessages, + children, +}: { + messages?: ThreadMessageLike[]; + children: ReactNode; +}) { + const runtime = useLocalRuntime(noOpAdapter, { initialMessages: messages }); + return ( + <AssistantRuntimeProvider runtime={runtime}> + {children} + </AssistantRuntimeProvider> + ); +} diff --git a/apps/docs/components/docs/samples/scrollbar.tsx b/apps/docs/components/docs/samples/scrollbar.tsx new file mode 100644 index 0000000..1af77e4 --- /dev/null +++ b/apps/docs/components/docs/samples/scrollbar.tsx @@ -0,0 +1,44 @@ +"use client"; + +import { ScrollArea as ScrollAreaPrimitive } from "radix-ui"; +import { SampleFrame } from "@/components/docs/samples/sample-frame"; +import { cn } from "@/lib/utils"; + +const MESSAGE_COUNT = 20; + +const ScrollBar = ({ + orientation = "vertical", +}: { + orientation?: "vertical" | "horizontal"; +}) => ( + <ScrollAreaPrimitive.ScrollAreaScrollbar + orientation={orientation} + className={cn("flex touch-none p-px transition-colors select-none", { + "h-full w-2.5 border-l border-l-transparent": orientation === "vertical", + "h-2.5 flex-col border-t border-t-transparent": + orientation === "horizontal", + })} + > + <ScrollAreaPrimitive.ScrollAreaThumb className="bg-border relative flex-1 rounded-full" /> + </ScrollAreaPrimitive.ScrollAreaScrollbar> +); + +export function ScrollbarSample() { + return ( + <SampleFrame className="bg-background h-auto overflow-hidden"> + <ScrollAreaPrimitive.Root className="relative h-48 w-full overflow-hidden"> + <ScrollAreaPrimitive.Viewport className="h-full w-full rounded p-4"> + <div className="space-y-4"> + {Array.from({ length: MESSAGE_COUNT }, (_, i) => ( + <p key={i} className="text-sm"> + Message {i + 1}: This is a sample message to demonstrate the + custom scrollbar styling with Radix UI ScrollArea. + </p> + ))} + </div> + </ScrollAreaPrimitive.Viewport> + <ScrollBar /> + </ScrollAreaPrimitive.Root> + </SampleFrame> + ); +} diff --git a/apps/docs/components/docs/samples/select.base.tsx b/apps/docs/components/docs/samples/select.base.tsx new file mode 100644 index 0000000..03edb80 --- /dev/null +++ b/apps/docs/components/docs/samples/select.base.tsx @@ -0,0 +1,312 @@ +"use client"; + +import { useState } from "react"; +import { + Select, + SelectRoot, + SelectTrigger, + SelectContent, + SelectItem, + SelectGroup, + SelectLabel, + SelectValue, + SelectSeparator, +} from "@/components/assistant-ui/select.base"; +import { SampleFrame } from "@/components/docs/samples/sample-frame"; + +const fruits = [ + { value: "apple", label: "Apple" }, + { value: "banana", label: "Banana" }, + { value: "orange", label: "Orange" }, +]; + +const frameworks = [ + { value: "react", label: "React" }, + { value: "vue", label: "Vue" }, + { value: "svelte", label: "Svelte" }, +]; + +const backends = [ + { value: "node", label: "Node.js" }, + { value: "python", label: "Python" }, +]; + +const northAmericaTimezones = [ + { value: "est", label: "Eastern Standard Time (EST)" }, + { value: "cst", label: "Central Standard Time (CST)" }, + { value: "mst", label: "Mountain Standard Time (MST)" }, + { value: "pst", label: "Pacific Standard Time (PST)" }, +]; + +const europeTimezones = [ + { value: "gmt", label: "Greenwich Mean Time (GMT)" }, + { value: "cet", label: "Central European Time (CET)" }, + { value: "eet", label: "Eastern European Time (EET)" }, +]; + +const asiaTimezones = [ + { value: "ist", label: "India Standard Time (IST)" }, + { value: "cst_china", label: "China Standard Time (CST)" }, + { value: "jst", label: "Japan Standard Time (JST)" }, +]; + +export function SelectSample() { + const [value, setValue] = useState("apple"); + + return ( + <SampleFrame className="flex h-auto items-center justify-center p-6"> + <Select + value={value} + onValueChange={setValue} + options={fruits} + placeholder="Select a fruit..." + className="w-fit" + /> + </SampleFrame> + ); +} + +export function SelectDisabledItemsSample() { + const [value, setValue] = useState("free"); + + return ( + <SampleFrame className="flex h-auto items-center justify-center p-6"> + <Select + value={value} + onValueChange={setValue} + options={[ + { value: "free", label: "Free" }, + { value: "pro", label: "Pro" }, + { value: "enterprise", label: "Enterprise", disabled: true }, + ]} + /> + </SampleFrame> + ); +} + +export function SelectPlaceholderSample() { + const [value, setValue] = useState(""); + + return ( + <SampleFrame className="flex h-auto items-center justify-center p-6"> + <Select + value={value} + onValueChange={setValue} + options={fruits} + placeholder="Choose an option..." + /> + </SampleFrame> + ); +} + +export function SelectDisabledSample() { + const [value, setValue] = useState("apple"); + + return ( + <SampleFrame className="flex h-auto items-center justify-center p-6"> + <Select + value={value} + onValueChange={setValue} + options={fruits} + disabled + /> + </SampleFrame> + ); +} + +export function SelectGroupsSample() { + const [value, setValue] = useState("react"); + + return ( + <SampleFrame className="flex h-auto items-center justify-center p-6"> + <SelectRoot + value={value} + onValueChange={(value) => value !== null && setValue(value)} + items={[...frameworks, ...backends]} + > + <SelectTrigger className="w-48"> + <SelectValue placeholder="Select a framework..." /> + </SelectTrigger> + <SelectContent> + <SelectGroup> + <SelectLabel>Frontend</SelectLabel> + {frameworks.map((item) => ( + <SelectItem key={item.value} value={item.value}> + {item.label} + </SelectItem> + ))} + </SelectGroup> + <SelectSeparator /> + <SelectGroup> + <SelectLabel>Backend</SelectLabel> + {backends.map((item) => ( + <SelectItem key={item.value} value={item.value}> + {item.label} + </SelectItem> + ))} + </SelectGroup> + </SelectContent> + </SelectRoot> + </SampleFrame> + ); +} + +export function SelectVariantsSample() { + const [outlineValue, setOutlineValue] = useState("react"); + const [ghostValue, setGhostValue] = useState("vue"); + const [mutedValue, setMutedValue] = useState("svelte"); + + return ( + <SampleFrame className="flex h-auto items-center justify-center gap-4 p-6"> + <div className="flex flex-col gap-2"> + <span className="text-muted-foreground text-xs">Outline (default)</span> + <SelectRoot + value={outlineValue} + onValueChange={(value) => value !== null && setOutlineValue(value)} + items={frameworks} + > + <SelectTrigger variant="outline" className="w-32"> + <SelectValue /> + </SelectTrigger> + <SelectContent> + {frameworks.map((item) => ( + <SelectItem key={item.value} value={item.value}> + {item.label} + </SelectItem> + ))} + </SelectContent> + </SelectRoot> + </div> + <div className="flex flex-col gap-2"> + <span className="text-muted-foreground text-xs">Ghost</span> + <SelectRoot + value={ghostValue} + onValueChange={(value) => value !== null && setGhostValue(value)} + items={frameworks} + > + <SelectTrigger variant="ghost" className="w-32"> + <SelectValue /> + </SelectTrigger> + <SelectContent> + {frameworks.map((item) => ( + <SelectItem key={item.value} value={item.value}> + {item.label} + </SelectItem> + ))} + </SelectContent> + </SelectRoot> + </div> + <div className="flex flex-col gap-2"> + <span className="text-muted-foreground text-xs">Muted</span> + <SelectRoot + value={mutedValue} + onValueChange={(value) => value !== null && setMutedValue(value)} + items={frameworks} + > + <SelectTrigger variant="muted" className="w-32"> + <SelectValue /> + </SelectTrigger> + <SelectContent> + {frameworks.map((item) => ( + <SelectItem key={item.value} value={item.value}> + {item.label} + </SelectItem> + ))} + </SelectContent> + </SelectRoot> + </div> + </SampleFrame> + ); +} + +export function SelectSizesSample() { + const [defaultValue, setDefaultValue] = useState("react"); + const [smValue, setSmValue] = useState("vue"); + + return ( + <SampleFrame className="flex h-auto items-center justify-center gap-4 p-6"> + <div className="flex flex-col gap-2"> + <span className="text-muted-foreground text-xs">Default</span> + <SelectRoot + value={defaultValue} + onValueChange={(value) => value !== null && setDefaultValue(value)} + items={frameworks} + > + <SelectTrigger size="default" className="w-36"> + <SelectValue /> + </SelectTrigger> + <SelectContent> + {frameworks.map((item) => ( + <SelectItem key={item.value} value={item.value}> + {item.label} + </SelectItem> + ))} + </SelectContent> + </SelectRoot> + </div> + <div className="flex flex-col gap-2"> + <span className="text-muted-foreground text-xs">Small</span> + <SelectRoot + value={smValue} + onValueChange={(value) => value !== null && setSmValue(value)} + items={frameworks} + > + <SelectTrigger size="sm" className="w-36"> + <SelectValue /> + </SelectTrigger> + <SelectContent> + {frameworks.map((item) => ( + <SelectItem key={item.value} value={item.value}> + {item.label} + </SelectItem> + ))} + </SelectContent> + </SelectRoot> + </div> + </SampleFrame> + ); +} + +export function SelectScrollableSample() { + const [value, setValue] = useState("est"); + + return ( + <SampleFrame className="flex h-auto items-center justify-center p-6"> + <SelectRoot + value={value} + onValueChange={(value) => value !== null && setValue(value)} + items={[...northAmericaTimezones, ...europeTimezones, ...asiaTimezones]} + > + <SelectTrigger className="w-64"> + <SelectValue placeholder="Select a timezone..." /> + </SelectTrigger> + <SelectContent> + <SelectGroup> + <SelectLabel>North America</SelectLabel> + {northAmericaTimezones.map((item) => ( + <SelectItem key={item.value} value={item.value}> + {item.label} + </SelectItem> + ))} + </SelectGroup> + <SelectGroup> + <SelectLabel>Europe</SelectLabel> + {europeTimezones.map((item) => ( + <SelectItem key={item.value} value={item.value}> + {item.label} + </SelectItem> + ))} + </SelectGroup> + <SelectGroup> + <SelectLabel>Asia</SelectLabel> + {asiaTimezones.map((item) => ( + <SelectItem key={item.value} value={item.value}> + {item.label} + </SelectItem> + ))} + </SelectGroup> + </SelectContent> + </SelectRoot> + </SampleFrame> + ); +} diff --git a/apps/docs/components/docs/samples/select.tsx b/apps/docs/components/docs/samples/select.tsx new file mode 100644 index 0000000..fff14c2 --- /dev/null +++ b/apps/docs/components/docs/samples/select.tsx @@ -0,0 +1,234 @@ +"use client"; + +import { useState } from "react"; +import { + Select, + SelectRoot, + SelectTrigger, + SelectContent, + SelectItem, + SelectGroup, + SelectLabel, + SelectValue, + SelectSeparator, +} from "@/components/assistant-ui/select"; +import { SampleFrame } from "@/components/docs/samples/sample-frame"; + +const fruits = [ + { value: "apple", label: "Apple" }, + { value: "banana", label: "Banana" }, + { value: "orange", label: "Orange" }, +]; + +export function SelectSample() { + const [value, setValue] = useState("apple"); + + return ( + <SampleFrame className="flex h-auto items-center justify-center p-6"> + <Select + value={value} + onValueChange={setValue} + options={fruits} + placeholder="Select a fruit..." + className="w-fit" + /> + </SampleFrame> + ); +} + +export function SelectDisabledItemsSample() { + const [value, setValue] = useState("free"); + + return ( + <SampleFrame className="flex h-auto items-center justify-center p-6"> + <Select + value={value} + onValueChange={setValue} + options={[ + { value: "free", label: "Free" }, + { value: "pro", label: "Pro" }, + { value: "enterprise", label: "Enterprise", disabled: true }, + ]} + /> + </SampleFrame> + ); +} + +export function SelectPlaceholderSample() { + const [value, setValue] = useState(""); + + return ( + <SampleFrame className="flex h-auto items-center justify-center p-6"> + <Select + value={value} + onValueChange={setValue} + options={fruits} + placeholder="Choose an option..." + /> + </SampleFrame> + ); +} + +export function SelectDisabledSample() { + const [value, setValue] = useState("apple"); + + return ( + <SampleFrame className="flex h-auto items-center justify-center p-6"> + <Select + value={value} + onValueChange={setValue} + options={fruits} + disabled + /> + </SampleFrame> + ); +} + +export function SelectGroupsSample() { + const [value, setValue] = useState("react"); + + return ( + <SampleFrame className="flex h-auto items-center justify-center p-6"> + <SelectRoot value={value} onValueChange={setValue}> + <SelectTrigger className="w-48"> + <SelectValue placeholder="Select a framework..." /> + </SelectTrigger> + <SelectContent> + <SelectGroup> + <SelectLabel>Frontend</SelectLabel> + <SelectItem value="react">React</SelectItem> + <SelectItem value="vue">Vue</SelectItem> + <SelectItem value="svelte">Svelte</SelectItem> + </SelectGroup> + <SelectSeparator /> + <SelectGroup> + <SelectLabel>Backend</SelectLabel> + <SelectItem value="node">Node.js</SelectItem> + <SelectItem value="python">Python</SelectItem> + </SelectGroup> + </SelectContent> + </SelectRoot> + </SampleFrame> + ); +} + +export function SelectVariantsSample() { + const [outlineValue, setOutlineValue] = useState("react"); + const [ghostValue, setGhostValue] = useState("vue"); + const [mutedValue, setMutedValue] = useState("svelte"); + + return ( + <SampleFrame className="flex h-auto items-center justify-center gap-4 p-6"> + <div className="flex flex-col gap-2"> + <span className="text-muted-foreground text-xs">Outline (default)</span> + <SelectRoot value={outlineValue} onValueChange={setOutlineValue}> + <SelectTrigger variant="outline" className="w-32"> + <SelectValue /> + </SelectTrigger> + <SelectContent> + <SelectItem value="react">React</SelectItem> + <SelectItem value="vue">Vue</SelectItem> + <SelectItem value="svelte">Svelte</SelectItem> + </SelectContent> + </SelectRoot> + </div> + <div className="flex flex-col gap-2"> + <span className="text-muted-foreground text-xs">Ghost</span> + <SelectRoot value={ghostValue} onValueChange={setGhostValue}> + <SelectTrigger variant="ghost" className="w-32"> + <SelectValue /> + </SelectTrigger> + <SelectContent> + <SelectItem value="react">React</SelectItem> + <SelectItem value="vue">Vue</SelectItem> + <SelectItem value="svelte">Svelte</SelectItem> + </SelectContent> + </SelectRoot> + </div> + <div className="flex flex-col gap-2"> + <span className="text-muted-foreground text-xs">Muted</span> + <SelectRoot value={mutedValue} onValueChange={setMutedValue}> + <SelectTrigger variant="muted" className="w-32"> + <SelectValue /> + </SelectTrigger> + <SelectContent> + <SelectItem value="react">React</SelectItem> + <SelectItem value="vue">Vue</SelectItem> + <SelectItem value="svelte">Svelte</SelectItem> + </SelectContent> + </SelectRoot> + </div> + </SampleFrame> + ); +} + +export function SelectSizesSample() { + const [defaultValue, setDefaultValue] = useState("react"); + const [smValue, setSmValue] = useState("vue"); + + return ( + <SampleFrame className="flex h-auto items-center justify-center gap-4 p-6"> + <div className="flex flex-col gap-2"> + <span className="text-muted-foreground text-xs">Default</span> + <SelectRoot value={defaultValue} onValueChange={setDefaultValue}> + <SelectTrigger size="default" className="w-36"> + <SelectValue /> + </SelectTrigger> + <SelectContent> + <SelectItem value="react">React</SelectItem> + <SelectItem value="vue">Vue</SelectItem> + <SelectItem value="svelte">Svelte</SelectItem> + </SelectContent> + </SelectRoot> + </div> + <div className="flex flex-col gap-2"> + <span className="text-muted-foreground text-xs">Small</span> + <SelectRoot value={smValue} onValueChange={setSmValue}> + <SelectTrigger size="sm" className="w-36"> + <SelectValue /> + </SelectTrigger> + <SelectContent> + <SelectItem value="react">React</SelectItem> + <SelectItem value="vue">Vue</SelectItem> + <SelectItem value="svelte">Svelte</SelectItem> + </SelectContent> + </SelectRoot> + </div> + </SampleFrame> + ); +} + +export function SelectScrollableSample() { + const [value, setValue] = useState("est"); + + return ( + <SampleFrame className="flex h-auto items-center justify-center p-6"> + <SelectRoot value={value} onValueChange={setValue}> + <SelectTrigger className="w-64"> + <SelectValue placeholder="Select a timezone..." /> + </SelectTrigger> + <SelectContent> + <SelectGroup> + <SelectLabel>North America</SelectLabel> + <SelectItem value="est">Eastern Standard Time (EST)</SelectItem> + <SelectItem value="cst">Central Standard Time (CST)</SelectItem> + <SelectItem value="mst">Mountain Standard Time (MST)</SelectItem> + <SelectItem value="pst">Pacific Standard Time (PST)</SelectItem> + </SelectGroup> + <SelectGroup> + <SelectLabel>Europe</SelectLabel> + <SelectItem value="gmt">Greenwich Mean Time (GMT)</SelectItem> + <SelectItem value="cet">Central European Time (CET)</SelectItem> + <SelectItem value="eet">Eastern European Time (EET)</SelectItem> + </SelectGroup> + <SelectGroup> + <SelectLabel>Asia</SelectLabel> + <SelectItem value="ist">India Standard Time (IST)</SelectItem> + <SelectItem value="cst_china">China Standard Time (CST)</SelectItem> + <SelectItem value="jst">Japan Standard Time (JST)</SelectItem> + </SelectGroup> + </SelectContent> + </SelectRoot> + </SampleFrame> + ); +} diff --git a/apps/docs/components/docs/samples/selection-toolbar-primitive.tsx b/apps/docs/components/docs/samples/selection-toolbar-primitive.tsx new file mode 100644 index 0000000..50a79d5 --- /dev/null +++ b/apps/docs/components/docs/samples/selection-toolbar-primitive.tsx @@ -0,0 +1,32 @@ +"use client"; + +import { QuoteIcon } from "lucide-react"; +import { SampleFrame } from "./sample-frame"; + +export function SelectionToolbarPrimitiveSample() { + return ( + <SampleFrame className="bg-background flex h-auto items-center justify-center p-8"> + <div className="w-full max-w-xl"> + <div className="border-border bg-muted/40 relative rounded-xl border px-5 py-4"> + <div className="text-muted-foreground mb-6 text-xs font-medium"> + Assistant + </div> + <div className="relative inline text-sm leading-relaxed"> + React Server Components allow you to{" "} + <span className="relative"> + <span className="bg-popover text-popover-foreground absolute start-1/2 -top-10 z-10 flex -translate-x-1/2 items-center gap-1.5 rounded-lg border px-2.5 py-1 text-sm shadow-md rtl:translate-x-1/2"> + <QuoteIcon className="size-3.5" /> + Quote + </span> + <mark className="bg-primary/20 rounded px-px"> + render on the server and stream to the client + </mark> + </span> + , reducing the amount of JavaScript shipped to the browser while + keeping your UI interactive. + </div> + </div> + </div> + </SampleFrame> + ); +} diff --git a/apps/docs/components/docs/samples/sources.tsx b/apps/docs/components/docs/samples/sources.tsx new file mode 100644 index 0000000..774c2d5 --- /dev/null +++ b/apps/docs/components/docs/samples/sources.tsx @@ -0,0 +1,46 @@ +"use client"; + +import { + Source, + SourceIcon, + SourceTitle, +} from "@/components/assistant-ui/sources"; +import { SampleFrame } from "@/components/docs/samples/sample-frame"; + +const sources = [ + { url: "https://openai.com", title: "OpenAI Research" }, + { url: "https://github.com", title: "github.com" }, + { url: "https://react.dev", title: "React Docs" }, +]; + +function VariantRow({ + label, + variant, +}: { + label: string; + variant?: "outline" | "ghost" | "muted"; +}) { + return ( + <div className="flex flex-col gap-2"> + <span className="text-muted-foreground text-xs font-medium">{label}</span> + <div className="flex flex-wrap items-center gap-2"> + {sources.map((source) => ( + <Source key={source.url} variant={variant} href={source.url}> + <SourceIcon url={source.url} /> + <SourceTitle>{source.title}</SourceTitle> + </Source> + ))} + </div> + </div> + ); +} + +export function SourcesSample() { + return ( + <SampleFrame className="flex h-auto flex-col gap-6 p-6"> + <VariantRow label="Outline (default)" /> + <VariantRow label="Ghost" variant="ghost" /> + <VariantRow label="Muted" variant="muted" /> + </SampleFrame> + ); +} diff --git a/apps/docs/components/docs/samples/speech.tsx b/apps/docs/components/docs/samples/speech.tsx new file mode 100644 index 0000000..c7b96e9 --- /dev/null +++ b/apps/docs/components/docs/samples/speech.tsx @@ -0,0 +1,378 @@ +"use client"; + +import { + ComposerAddAttachment, + ComposerAttachments, + UserMessageAttachments, +} from "@/components/assistant-ui/attachment"; +import { + ActionBarPrimitive, + AuiIf, + BranchPickerPrimitive, + ComposerPrimitive, + ErrorPrimitive, + MessagePrimitive, + ThreadPrimitive, +} from "@assistant-ui/react"; +import { SampleFrame } from "@/components/docs/samples/sample-frame"; +import { TooltipIconButton } from "@/components/assistant-ui/tooltip-icon-button"; +import type { FC } from "react"; +import { + ArrowDownIcon, + CopyIcon, + AudioLinesIcon, + CheckIcon, + PencilIcon, + StopCircleIcon, + RefreshCwIcon, + ArrowUpIcon, + ChevronLeftIcon, + ChevronRightIcon, + Square, +} from "lucide-react"; +import { MarkdownText } from "@/components/assistant-ui/markdown-text"; +import { Button } from "@/components/ui/button"; +import { cn } from "@/lib/utils"; +import { ToolFallback } from "@/components/assistant-ui/tool-fallback"; + +export const SpeechSample = () => { + return ( + <SampleFrame className="bg-muted/40 overflow-hidden"> + <Thread /> + </SampleFrame> + ); +}; + +export const Thread: FC = () => { + return ( + <ThreadPrimitive.Root + className="aui-root aui-thread-root bg-background @container flex h-full flex-col" + style={{ + ["--thread-max-width" as string]: "44rem", + }} + > + <ThreadPrimitive.Viewport className="aui-thread-viewport relative flex flex-1 flex-col overflow-x-auto overflow-y-scroll px-4"> + <ThreadWelcome /> + + <ThreadPrimitive.Messages> + {({ message }) => { + if (message.composer.isEditing) return <EditComposer />; + if (message.role === "user") return <UserMessage />; + return <AssistantMessage />; + }} + </ThreadPrimitive.Messages> + <div className="aui-thread-viewport-spacer min-h-8 grow" /> + <Composer /> + </ThreadPrimitive.Viewport> + </ThreadPrimitive.Root> + ); +}; + +const ThreadScrollToBottom: FC = () => { + return ( + <ThreadPrimitive.ScrollToBottom asChild> + <TooltipIconButton + tooltip="Scroll to bottom" + variant="outline" + className="aui-thread-scroll-to-bottom dark:bg-background dark:hover:bg-accent absolute -top-12 z-10 self-center rounded-full p-4 disabled:invisible" + > + <ArrowDownIcon /> + </TooltipIconButton> + </ThreadPrimitive.ScrollToBottom> + ); +}; + +const ThreadWelcome: FC = () => { + return ( + <AuiIf condition={(s) => s.thread.isEmpty}> + <div className="aui-thread-welcome-root mx-auto mb-16 flex w-full max-w-(--thread-max-width) grow flex-col"> + <div className="aui-thread-welcome-center flex w-full grow flex-col items-center justify-center"> + <div className="aui-thread-welcome-message flex size-full flex-col justify-center px-8 md:mt-20"> + <div className="aui-thread-welcome-message-motion-1 text-2xl font-semibold"> + Hello there! + </div> + <div className="aui-thread-welcome-message-motion-2 text-muted-foreground/65 text-2xl"> + How can I help you today? + </div> + </div> + </div> + </div> + </AuiIf> + ); +}; + +const ThreadWelcomeSuggestions: FC = () => { + return ( + <div className="aui-thread-welcome-suggestions grid w-full gap-2 @md:grid-cols-2"> + {[ + { + title: "What's the weather", + label: "in San Francisco?", + action: "What's the weather in San Francisco?", + }, + { + title: "Help me write an essay", + label: `about AI chat applications`, + action: `Help me write an essay about AI chat applications`, + }, + ].map((suggestedAction, index) => ( + <div + key={`suggested-action-${suggestedAction.title}-${index}`} + className="aui-thread-welcome-suggestion-display nth-[n+3]:hidden @md:nth-[n+3]:block" + > + <ThreadPrimitive.Suggestion + prompt={suggestedAction.action} + clearComposer + send + asChild + > + <Button + variant="ghost" + className="aui-thread-welcome-suggestion dark:hover:bg-accent/60 h-auto w-full flex-1 flex-wrap items-start justify-start gap-1 rounded-3xl border px-5 py-4 text-start text-sm @md:flex-col" + aria-label={suggestedAction.action} + > + <span className="aui-thread-welcome-suggestion-text-1 font-medium"> + {suggestedAction.title} + </span> + <span className="aui-thread-welcome-suggestion-text-2 text-muted-foreground"> + {suggestedAction.label} + </span> + </Button> + </ThreadPrimitive.Suggestion> + </div> + ))} + </div> + ); +}; + +const Composer: FC = () => { + return ( + <div className="aui-composer-wrapper bg-background sticky bottom-0 mx-auto flex w-full max-w-(--thread-max-width) flex-col gap-4 overflow-visible rounded-t-3xl pb-4 md:pb-6"> + <ThreadScrollToBottom /> + <AuiIf condition={(s) => s.thread.isEmpty}> + <ThreadWelcomeSuggestions /> + </AuiIf> + <ComposerPrimitive.Root className="aui-composer-root border-border bg-muted dark:border-muted-foreground/15 relative flex w-full flex-col rounded-3xl border px-1 pt-2 shadow-[0_9px_9px_0px_rgba(0,0,0,0.01),0_2px_5px_0px_rgba(0,0,0,0.06)]"> + <ComposerAttachments /> + <ComposerPrimitive.Input + placeholder="Send a message..." + className="aui-composer-input placeholder:text-muted-foreground focus:outline-primary mb-1 max-h-32 min-h-16 w-full resize-none bg-transparent px-3.5 pt-1.5 pb-3 text-base outline-none" + rows={1} + autoFocus + aria-label="Message input" + /> + <ComposerAction /> + </ComposerPrimitive.Root> + </div> + ); +}; + +const ComposerAction: FC = () => { + return ( + <div className="aui-composer-action-wrapper relative mx-1 mt-2 mb-2 flex items-center justify-between"> + <ComposerAddAttachment /> + + <AuiIf condition={(s) => !s.thread.isRunning}> + <ComposerPrimitive.Send asChild> + <TooltipIconButton + tooltip="Send message" + side="bottom" + type="button" + variant="default" + size="icon" + className="aui-composer-send size-8.5 rounded-full p-1" + aria-label="Send message" + > + <ArrowUpIcon className="aui-composer-send-icon size-5" /> + </TooltipIconButton> + </ComposerPrimitive.Send> + </AuiIf> + + <AuiIf condition={(s) => s.thread.isRunning}> + <ComposerPrimitive.Cancel asChild> + <Button + type="button" + variant="default" + size="icon" + className="aui-composer-cancel border-muted-foreground/60 hover:bg-primary/75 dark:border-muted-foreground/90 size-8.5 rounded-full border" + aria-label="Stop generating" + > + <Square className="aui-composer-cancel-icon size-3.5 fill-white dark:fill-black" /> + </Button> + </ComposerPrimitive.Cancel> + </AuiIf> + </div> + ); +}; + +const MessageError: FC = () => { + return ( + <MessagePrimitive.Error> + <ErrorPrimitive.Root className="aui-message-error-root border-destructive bg-destructive/10 text-destructive dark:bg-destructive/5 mt-2 rounded-md border p-3 text-sm dark:text-red-200"> + <ErrorPrimitive.Message className="aui-message-error-message line-clamp-2" /> + </ErrorPrimitive.Root> + </MessagePrimitive.Error> + ); +}; + +const AssistantMessage: FC = () => { + return ( + <MessagePrimitive.Root + className="aui-assistant-message-root relative mx-auto w-full max-w-(--thread-max-width) py-4 last:mb-24" + data-role="assistant" + > + <div className="aui-assistant-message-content text-foreground mx-2 leading-7 wrap-break-word"> + <MessagePrimitive.Parts> + {({ part }) => { + if (part.type === "text") return <MarkdownText />; + if (part.type === "tool-call") + return part.toolUI ?? <ToolFallback {...part} />; + return null; + }} + </MessagePrimitive.Parts> + <MessageError /> + </div> + + <div className="aui-assistant-message-footer ms-2 mt-2 flex min-h-6 items-center"> + <BranchPicker /> + <AssistantActionBar /> + </div> + </MessagePrimitive.Root> + ); +}; + +const AssistantActionBar: FC = () => { + return ( + <ActionBarPrimitive.Root + hideWhenRunning + autohide="not-last" + className="aui-assistant-action-bar-root text-muted-foreground col-start-3 row-start-2 -ms-1 flex gap-1" + > + <AuiIf condition={(s) => s.message.speech == null}> + <ActionBarPrimitive.Speak asChild> + <TooltipIconButton tooltip="Read aloud"> + <AudioLinesIcon /> + </TooltipIconButton> + </ActionBarPrimitive.Speak> + </AuiIf> + <AuiIf condition={(s) => s.message.speech != null}> + <ActionBarPrimitive.StopSpeaking asChild> + <TooltipIconButton tooltip="Stop"> + <StopCircleIcon /> + </TooltipIconButton> + </ActionBarPrimitive.StopSpeaking> + </AuiIf> + <ActionBarPrimitive.Copy asChild> + <TooltipIconButton tooltip="Copy"> + <AuiIf condition={(s) => s.message.isCopied}> + <CheckIcon /> + </AuiIf> + <AuiIf condition={(s) => !s.message.isCopied}> + <CopyIcon /> + </AuiIf> + </TooltipIconButton> + </ActionBarPrimitive.Copy> + <ActionBarPrimitive.Reload asChild> + <TooltipIconButton tooltip="Refresh"> + <RefreshCwIcon /> + </TooltipIconButton> + </ActionBarPrimitive.Reload> + </ActionBarPrimitive.Root> + ); +}; + +const UserMessage: FC = () => { + return ( + <MessagePrimitive.Root asChild> + <div + className="aui-user-message-root mx-auto grid w-full max-w-(--thread-max-width) auto-rows-auto grid-cols-[minmax(72px,1fr)_auto] gap-y-2 px-2 py-4 first:mt-3 last:mb-5 [&:where(>*)]:col-start-2" + data-role="user" + > + <UserMessageAttachments /> + + <div className="aui-user-message-content-wrapper relative col-start-2 min-w-0"> + <div className="aui-user-message-content bg-muted text-foreground rounded-3xl px-5 py-2.5 break-words"> + <MessagePrimitive.Parts /> + </div> + <div className="aui-user-action-bar-wrapper absolute start-0 top-1/2 -translate-x-full -translate-y-1/2 pe-2 rtl:translate-x-full"> + <UserActionBar /> + </div> + </div> + + <BranchPicker className="aui-user-branch-picker col-span-full col-start-1 row-start-3 -me-1 justify-end" /> + </div> + </MessagePrimitive.Root> + ); +}; + +const UserActionBar: FC = () => { + return ( + <ActionBarPrimitive.Root + hideWhenRunning + autohide="not-last" + className="aui-user-action-bar-root flex flex-col items-end" + > + <ActionBarPrimitive.Edit asChild> + <TooltipIconButton tooltip="Edit" className="aui-user-action-edit p-4"> + <PencilIcon /> + </TooltipIconButton> + </ActionBarPrimitive.Edit> + </ActionBarPrimitive.Root> + ); +}; + +const EditComposer: FC = () => { + return ( + <div className="aui-edit-composer-wrapper mx-auto flex w-full max-w-(--thread-max-width) flex-col gap-4 px-2 first:mt-4"> + <ComposerPrimitive.Root className="aui-edit-composer-root bg-muted ms-auto flex w-full max-w-7/8 flex-col rounded-xl"> + <ComposerPrimitive.Input + className="aui-edit-composer-input text-foreground flex min-h-15 w-full resize-none bg-transparent p-4 outline-none" + autoFocus + /> + + <div className="aui-edit-composer-footer mx-3 mb-3 flex items-center justify-center gap-2 self-end"> + <ComposerPrimitive.Cancel asChild> + <Button variant="ghost" size="sm" aria-label="Cancel edit"> + Cancel + </Button> + </ComposerPrimitive.Cancel> + <ComposerPrimitive.Send asChild> + <Button size="sm" aria-label="Update message"> + Update + </Button> + </ComposerPrimitive.Send> + </div> + </ComposerPrimitive.Root> + </div> + ); +}; + +const BranchPicker: FC<BranchPickerPrimitive.Root.Props> = ({ + className, + ...rest +}) => { + return ( + <BranchPickerPrimitive.Root + hideWhenSingleBranch + className={cn( + "aui-branch-picker-root text-muted-foreground -ms-2 me-2 inline-flex items-center text-xs", + className, + )} + {...rest} + > + <BranchPickerPrimitive.Previous asChild> + <TooltipIconButton tooltip="Previous"> + <ChevronLeftIcon /> + </TooltipIconButton> + </BranchPickerPrimitive.Previous> + <span className="aui-branch-picker-state font-medium"> + <BranchPickerPrimitive.Number /> / <BranchPickerPrimitive.Count /> + </span> + <BranchPickerPrimitive.Next asChild> + <TooltipIconButton tooltip="Next"> + <ChevronRightIcon /> + </TooltipIconButton> + </BranchPickerPrimitive.Next> + </BranchPickerPrimitive.Root> + ); +}; diff --git a/apps/docs/components/docs/samples/streamdown.tsx b/apps/docs/components/docs/samples/streamdown.tsx new file mode 100644 index 0000000..6c43f8d --- /dev/null +++ b/apps/docs/components/docs/samples/streamdown.tsx @@ -0,0 +1,78 @@ +"use client"; + +import { useEffect, useState } from "react"; +import { PlayIcon } from "lucide-react"; +import { Streamdown } from "streamdown"; +import { SampleFrame } from "@/components/docs/samples/sample-frame"; +import { Button } from "@/components/ui/button"; + +const sampleMarkdown = `## Streamdown Rendering + +This is a paragraph with **bold text**, *italic text*, and [links](https://example.com). + +- First item +- Second item +- Third item + +> This is a blockquote with some quoted text. + +| Name | Value | +|-------|-------| +| Alpha | 100 | +| Beta | 200 | + +\`\`\`typescript +function greet(name: string) { + return \`Hello, \${name}!\`; +} +\`\`\` +`; + +export const StreamdownSample = () => { + const [isStreaming, setIsStreaming] = useState(false); + const [streamedText, setStreamedText] = useState(""); + + useEffect(() => { + if (!isStreaming) return; + + setStreamedText(""); + let index = 0; + const interval = setInterval(() => { + if (index < sampleMarkdown.length) { + // Stream by chunks for more realistic effect + const chunkSize = Math.floor(Math.random() * 3) + 1; + setStreamedText(sampleMarkdown.slice(0, index + chunkSize)); + index += chunkSize; + } else { + setIsStreaming(false); + clearInterval(interval); + } + }, 15); + return () => clearInterval(interval); + }, [isStreaming]); + + const handleStart = () => { + setStreamedText(""); + setIsStreaming(true); + }; + + return ( + <SampleFrame className="bg-background h-auto p-4"> + <div className="mb-4 flex items-center gap-2"> + <Button + size="sm" + variant="outline" + onClick={handleStart} + disabled={isStreaming} + className="gap-1.5" + > + <PlayIcon className="size-3" /> + {isStreaming ? "Streaming..." : "Start Streaming"} + </Button> + </div> + <Streamdown mode="streaming" isAnimating={isStreaming}> + {streamedText || sampleMarkdown} + </Streamdown> + </SampleFrame> + ); +}; diff --git a/apps/docs/components/docs/samples/suggestion-primitive.tsx b/apps/docs/components/docs/samples/suggestion-primitive.tsx new file mode 100644 index 0000000..0db1e06 --- /dev/null +++ b/apps/docs/components/docs/samples/suggestion-primitive.tsx @@ -0,0 +1,53 @@ +"use client"; + +import { SampleFrame } from "./sample-frame"; + +type SuggestionItemStaticProps = { + title: string; + description: string; +}; + +function SuggestionItemStatic({ + title, + description, +}: SuggestionItemStaticProps) { + return ( + <button + type="button" + className="border-border bg-background hover:bg-muted flex h-auto w-full flex-col items-start justify-start gap-1 rounded-2xl border px-4 py-3 text-start text-sm transition-colors" + > + <span className="font-medium">{title}</span> + <span className="text-muted-foreground">{description}</span> + </button> + ); +} + +export function SuggestionPrimitiveSample() { + return ( + <SampleFrame className="bg-background flex h-auto items-center justify-center p-8"> + <div className="w-full max-w-xl"> + <div className="mb-4 text-center text-lg font-semibold"> + How can I help you? + </div> + <div className="grid w-full grid-cols-1 gap-2 sm:grid-cols-2"> + <SuggestionItemStatic + title="Write a blog post" + description="About React Server Components" + /> + <SuggestionItemStatic + title="Explain quantum computing" + description="In simple terms" + /> + <SuggestionItemStatic + title="Debug my code" + description="TypeScript type error" + /> + <SuggestionItemStatic + title="Plan a trip" + description="Weekend in Paris" + /> + </div> + </div> + </SampleFrame> + ); +} diff --git a/apps/docs/components/docs/samples/syntax-highlighting.tsx b/apps/docs/components/docs/samples/syntax-highlighting.tsx new file mode 100644 index 0000000..747c115 --- /dev/null +++ b/apps/docs/components/docs/samples/syntax-highlighting.tsx @@ -0,0 +1,32 @@ +"use client"; + +import { CopyIcon } from "lucide-react"; +import { SyntaxHighlighter } from "@/components/assistant-ui/shiki-highlighter"; +import { SampleFrame } from "@/components/docs/samples/sample-frame"; + +const sampleCode = `function greet(name: string) { + return \`Hello, \${name}!\`; +} + +// Usage +const message = greet("World");`; + +export const SyntaxHighlightingSample = () => { + return ( + <SampleFrame className="bg-background h-auto p-4"> + <div className="overflow-hidden rounded-lg"> + <div className="bg-muted-foreground/15 text-foreground dark:bg-muted-foreground/20 flex items-center justify-between gap-4 rounded-t-lg px-4 py-2 text-sm font-semibold"> + <span className="lowercase">typescript</span> + <button type="button" className="hover:bg-muted rounded p-1"> + <CopyIcon className="size-4" /> + </button> + </div> + <SyntaxHighlighter + language="typescript" + code={sampleCode} + addDefaultStyles={false} + /> + </div> + </SampleFrame> + ); +}; diff --git a/apps/docs/components/docs/samples/tabs.base.tsx b/apps/docs/components/docs/samples/tabs.base.tsx new file mode 100644 index 0000000..1991994 --- /dev/null +++ b/apps/docs/components/docs/samples/tabs.base.tsx @@ -0,0 +1,292 @@ +"use client"; + +import { useState } from "react"; +import { FileText, Settings, User, Bell, Lock } from "lucide-react"; +import { + Tabs, + TabsList, + TabsTrigger, + TabsContent, +} from "@/components/assistant-ui/tabs.base"; +import { SampleFrame } from "@/components/docs/samples/sample-frame"; + +export function TabsSample() { + return ( + <SampleFrame className="flex h-auto items-center justify-center p-6"> + <Tabs defaultValue="account" className="w-[400px]"> + <TabsList> + <TabsTrigger value="account">Account</TabsTrigger> + <TabsTrigger value="password">Password</TabsTrigger> + <TabsTrigger value="settings">Settings</TabsTrigger> + </TabsList> + <TabsContent value="account" className="p-4"> + <p className="text-muted-foreground text-sm"> + Manage your account settings and preferences. + </p> + </TabsContent> + <TabsContent value="password" className="p-4"> + <p className="text-muted-foreground text-sm"> + Change your password and security settings. + </p> + </TabsContent> + <TabsContent value="settings" className="p-4"> + <p className="text-muted-foreground text-sm"> + Configure your application settings. + </p> + </TabsContent> + </Tabs> + </SampleFrame> + ); +} + +export function TabsVariantsSample() { + return ( + <SampleFrame className="flex h-auto flex-col items-center justify-center gap-6 p-6"> + <div className="flex flex-col gap-2"> + <span className="text-muted-foreground text-xs">Default</span> + <Tabs defaultValue="a"> + <TabsList variant="default"> + <TabsTrigger value="a">Tab A</TabsTrigger> + <TabsTrigger value="b">Tab B</TabsTrigger> + <TabsTrigger value="c">Tab C</TabsTrigger> + </TabsList> + </Tabs> + </div> + <div className="flex flex-col gap-2"> + <span className="text-muted-foreground text-xs">Line</span> + <Tabs defaultValue="a"> + <TabsList variant="line"> + <TabsTrigger value="a">Tab A</TabsTrigger> + <TabsTrigger value="b">Tab B</TabsTrigger> + <TabsTrigger value="c">Tab C</TabsTrigger> + </TabsList> + </Tabs> + </div> + <div className="flex flex-col gap-2"> + <span className="text-muted-foreground text-xs">Ghost</span> + <Tabs defaultValue="a"> + <TabsList variant="ghost"> + <TabsTrigger value="a">Tab A</TabsTrigger> + <TabsTrigger value="b">Tab B</TabsTrigger> + <TabsTrigger value="c">Tab C</TabsTrigger> + </TabsList> + </Tabs> + </div> + <div className="flex flex-col gap-2"> + <span className="text-muted-foreground text-xs">Pills</span> + <Tabs defaultValue="a"> + <TabsList variant="pills"> + <TabsTrigger value="a">Tab A</TabsTrigger> + <TabsTrigger value="b">Tab B</TabsTrigger> + <TabsTrigger value="c">Tab C</TabsTrigger> + </TabsList> + </Tabs> + </div> + <div className="flex flex-col gap-2"> + <span className="text-muted-foreground text-xs">Outline</span> + <Tabs defaultValue="a"> + <TabsList variant="outline"> + <TabsTrigger value="a">Tab A</TabsTrigger> + <TabsTrigger value="b">Tab B</TabsTrigger> + <TabsTrigger value="c">Tab C</TabsTrigger> + </TabsList> + </Tabs> + </div> + </SampleFrame> + ); +} + +export function TabsSizesSample() { + return ( + <SampleFrame className="flex h-auto flex-col items-center justify-center gap-6 p-6"> + <div className="flex flex-col gap-2"> + <span className="text-muted-foreground text-xs">Small</span> + <Tabs defaultValue="a"> + <TabsList size="sm"> + <TabsTrigger value="a">Tab A</TabsTrigger> + <TabsTrigger value="b">Tab B</TabsTrigger> + </TabsList> + </Tabs> + </div> + <div className="flex flex-col gap-2"> + <span className="text-muted-foreground text-xs">Default</span> + <Tabs defaultValue="a"> + <TabsList size="default"> + <TabsTrigger value="a">Tab A</TabsTrigger> + <TabsTrigger value="b">Tab B</TabsTrigger> + </TabsList> + </Tabs> + </div> + <div className="flex flex-col gap-2"> + <span className="text-muted-foreground text-xs">Large</span> + <Tabs defaultValue="a"> + <TabsList size="lg"> + <TabsTrigger value="a">Tab A</TabsTrigger> + <TabsTrigger value="b">Tab B</TabsTrigger> + </TabsList> + </Tabs> + </div> + </SampleFrame> + ); +} + +export function TabsWithIconsSample() { + return ( + <SampleFrame className="flex h-auto items-center justify-center p-6"> + <Tabs defaultValue="profile" className="w-[400px]"> + <TabsList variant="ghost"> + <TabsTrigger value="profile"> + <User /> + Profile + </TabsTrigger> + <TabsTrigger value="notifications"> + <Bell /> + Notifications + </TabsTrigger> + <TabsTrigger value="security"> + <Lock /> + Security + </TabsTrigger> + </TabsList> + <TabsContent value="profile" className="p-4"> + <p className="text-muted-foreground text-sm"> + Edit your profile information. + </p> + </TabsContent> + <TabsContent value="notifications" className="p-4"> + <p className="text-muted-foreground text-sm"> + Manage your notification preferences. + </p> + </TabsContent> + <TabsContent value="security" className="p-4"> + <p className="text-muted-foreground text-sm"> + Configure security and privacy settings. + </p> + </TabsContent> + </Tabs> + </SampleFrame> + ); +} + +export function TabsControlledSample() { + const [activeTab, setActiveTab] = useState("overview"); + + return ( + <SampleFrame className="flex h-auto flex-col items-center justify-center gap-4 p-6"> + <Tabs + value={activeTab} + onValueChange={setActiveTab} + className="w-[400px]" + > + <TabsList variant="pills"> + <TabsTrigger value="overview">Overview</TabsTrigger> + <TabsTrigger value="analytics">Analytics</TabsTrigger> + <TabsTrigger value="reports">Reports</TabsTrigger> + </TabsList> + <TabsContent value="overview" className="p-4"> + <p className="text-muted-foreground text-sm">Overview content</p> + </TabsContent> + <TabsContent value="analytics" className="p-4"> + <p className="text-muted-foreground text-sm">Analytics content</p> + </TabsContent> + <TabsContent value="reports" className="p-4"> + <p className="text-muted-foreground text-sm">Reports content</p> + </TabsContent> + </Tabs> + <p className="text-muted-foreground text-sm"> + Current tab: <code className="font-mono">{activeTab}</code> + </p> + </SampleFrame> + ); +} + +export function TabsAsLinkSample() { + return ( + <SampleFrame className="flex h-auto items-center justify-center p-6"> + <Tabs defaultValue="docs"> + <TabsList variant="line"> + <TabsTrigger value="docs" render={<a href="#installation" />}> + <FileText /> + Docs + </TabsTrigger> + <TabsTrigger value="api" render={<a href="#api-reference" />}> + <Settings /> + API + </TabsTrigger> + </TabsList> + </Tabs> + </SampleFrame> + ); +} + +export function TabsAnimatedIndicatorSample() { + return ( + <SampleFrame className="flex h-auto flex-col items-center justify-center gap-8 p-6"> + <div className="flex flex-col gap-2"> + <span className="text-muted-foreground text-xs"> + Default - Sliding background + </span> + <Tabs defaultValue="home"> + <TabsList variant="default"> + <TabsTrigger value="home">Home</TabsTrigger> + <TabsTrigger value="about">About</TabsTrigger> + <TabsTrigger value="services">Services</TabsTrigger> + <TabsTrigger value="contact">Contact</TabsTrigger> + </TabsList> + </Tabs> + </div> + <div className="flex flex-col gap-2"> + <span className="text-muted-foreground text-xs"> + Line - Sliding underline + </span> + <Tabs defaultValue="home"> + <TabsList variant="line"> + <TabsTrigger value="home">Home</TabsTrigger> + <TabsTrigger value="about">About</TabsTrigger> + <TabsTrigger value="services">Services</TabsTrigger> + <TabsTrigger value="contact">Contact</TabsTrigger> + </TabsList> + </Tabs> + </div> + <div className="flex flex-col gap-2"> + <span className="text-muted-foreground text-xs"> + Ghost - Sliding background with hover effect + </span> + <Tabs defaultValue="dashboard"> + <TabsList variant="ghost"> + <TabsTrigger value="dashboard">Dashboard</TabsTrigger> + <TabsTrigger value="projects">Projects</TabsTrigger> + <TabsTrigger value="tasks">Tasks</TabsTrigger> + <TabsTrigger value="team">Team</TabsTrigger> + </TabsList> + </Tabs> + </div> + <div className="flex flex-col gap-2"> + <span className="text-muted-foreground text-xs"> + Pills - Sliding pill background + </span> + <Tabs defaultValue="all"> + <TabsList variant="pills"> + <TabsTrigger value="all">All</TabsTrigger> + <TabsTrigger value="active">Active</TabsTrigger> + <TabsTrigger value="completed">Completed</TabsTrigger> + <TabsTrigger value="archived">Archived</TabsTrigger> + </TabsList> + </Tabs> + </div> + <div className="flex flex-col gap-2"> + <span className="text-muted-foreground text-xs"> + Outline - Sliding border + </span> + <Tabs defaultValue="week"> + <TabsList variant="outline"> + <TabsTrigger value="day">Day</TabsTrigger> + <TabsTrigger value="week">Week</TabsTrigger> + <TabsTrigger value="month">Month</TabsTrigger> + <TabsTrigger value="year">Year</TabsTrigger> + </TabsList> + </Tabs> + </div> + </SampleFrame> + ); +} diff --git a/apps/docs/components/docs/samples/tabs.tsx b/apps/docs/components/docs/samples/tabs.tsx new file mode 100644 index 0000000..cb75b7a --- /dev/null +++ b/apps/docs/components/docs/samples/tabs.tsx @@ -0,0 +1,296 @@ +"use client"; + +import { useState } from "react"; +import { FileText, Settings, User, Bell, Lock } from "lucide-react"; +import { + Tabs, + TabsList, + TabsTrigger, + TabsContent, +} from "@/components/assistant-ui/tabs"; +import { SampleFrame } from "@/components/docs/samples/sample-frame"; + +export function TabsSample() { + return ( + <SampleFrame className="flex h-auto items-center justify-center p-6"> + <Tabs defaultValue="account" className="w-[400px]"> + <TabsList> + <TabsTrigger value="account">Account</TabsTrigger> + <TabsTrigger value="password">Password</TabsTrigger> + <TabsTrigger value="settings">Settings</TabsTrigger> + </TabsList> + <TabsContent value="account" className="p-4"> + <p className="text-muted-foreground text-sm"> + Manage your account settings and preferences. + </p> + </TabsContent> + <TabsContent value="password" className="p-4"> + <p className="text-muted-foreground text-sm"> + Change your password and security settings. + </p> + </TabsContent> + <TabsContent value="settings" className="p-4"> + <p className="text-muted-foreground text-sm"> + Configure your application settings. + </p> + </TabsContent> + </Tabs> + </SampleFrame> + ); +} + +export function TabsVariantsSample() { + return ( + <SampleFrame className="flex h-auto flex-col items-center justify-center gap-6 p-6"> + <div className="flex flex-col gap-2"> + <span className="text-muted-foreground text-xs">Default</span> + <Tabs defaultValue="a"> + <TabsList variant="default"> + <TabsTrigger value="a">Tab A</TabsTrigger> + <TabsTrigger value="b">Tab B</TabsTrigger> + <TabsTrigger value="c">Tab C</TabsTrigger> + </TabsList> + </Tabs> + </div> + <div className="flex flex-col gap-2"> + <span className="text-muted-foreground text-xs">Line</span> + <Tabs defaultValue="a"> + <TabsList variant="line"> + <TabsTrigger value="a">Tab A</TabsTrigger> + <TabsTrigger value="b">Tab B</TabsTrigger> + <TabsTrigger value="c">Tab C</TabsTrigger> + </TabsList> + </Tabs> + </div> + <div className="flex flex-col gap-2"> + <span className="text-muted-foreground text-xs">Ghost</span> + <Tabs defaultValue="a"> + <TabsList variant="ghost"> + <TabsTrigger value="a">Tab A</TabsTrigger> + <TabsTrigger value="b">Tab B</TabsTrigger> + <TabsTrigger value="c">Tab C</TabsTrigger> + </TabsList> + </Tabs> + </div> + <div className="flex flex-col gap-2"> + <span className="text-muted-foreground text-xs">Pills</span> + <Tabs defaultValue="a"> + <TabsList variant="pills"> + <TabsTrigger value="a">Tab A</TabsTrigger> + <TabsTrigger value="b">Tab B</TabsTrigger> + <TabsTrigger value="c">Tab C</TabsTrigger> + </TabsList> + </Tabs> + </div> + <div className="flex flex-col gap-2"> + <span className="text-muted-foreground text-xs">Outline</span> + <Tabs defaultValue="a"> + <TabsList variant="outline"> + <TabsTrigger value="a">Tab A</TabsTrigger> + <TabsTrigger value="b">Tab B</TabsTrigger> + <TabsTrigger value="c">Tab C</TabsTrigger> + </TabsList> + </Tabs> + </div> + </SampleFrame> + ); +} + +export function TabsSizesSample() { + return ( + <SampleFrame className="flex h-auto flex-col items-center justify-center gap-6 p-6"> + <div className="flex flex-col gap-2"> + <span className="text-muted-foreground text-xs">Small</span> + <Tabs defaultValue="a"> + <TabsList size="sm"> + <TabsTrigger value="a">Tab A</TabsTrigger> + <TabsTrigger value="b">Tab B</TabsTrigger> + </TabsList> + </Tabs> + </div> + <div className="flex flex-col gap-2"> + <span className="text-muted-foreground text-xs">Default</span> + <Tabs defaultValue="a"> + <TabsList size="default"> + <TabsTrigger value="a">Tab A</TabsTrigger> + <TabsTrigger value="b">Tab B</TabsTrigger> + </TabsList> + </Tabs> + </div> + <div className="flex flex-col gap-2"> + <span className="text-muted-foreground text-xs">Large</span> + <Tabs defaultValue="a"> + <TabsList size="lg"> + <TabsTrigger value="a">Tab A</TabsTrigger> + <TabsTrigger value="b">Tab B</TabsTrigger> + </TabsList> + </Tabs> + </div> + </SampleFrame> + ); +} + +export function TabsWithIconsSample() { + return ( + <SampleFrame className="flex h-auto items-center justify-center p-6"> + <Tabs defaultValue="profile" className="w-[400px]"> + <TabsList variant="ghost"> + <TabsTrigger value="profile"> + <User /> + Profile + </TabsTrigger> + <TabsTrigger value="notifications"> + <Bell /> + Notifications + </TabsTrigger> + <TabsTrigger value="security"> + <Lock /> + Security + </TabsTrigger> + </TabsList> + <TabsContent value="profile" className="p-4"> + <p className="text-muted-foreground text-sm"> + Edit your profile information. + </p> + </TabsContent> + <TabsContent value="notifications" className="p-4"> + <p className="text-muted-foreground text-sm"> + Manage your notification preferences. + </p> + </TabsContent> + <TabsContent value="security" className="p-4"> + <p className="text-muted-foreground text-sm"> + Configure security and privacy settings. + </p> + </TabsContent> + </Tabs> + </SampleFrame> + ); +} + +export function TabsControlledSample() { + const [activeTab, setActiveTab] = useState("overview"); + + return ( + <SampleFrame className="flex h-auto flex-col items-center justify-center gap-4 p-6"> + <Tabs + value={activeTab} + onValueChange={setActiveTab} + className="w-[400px]" + > + <TabsList variant="pills"> + <TabsTrigger value="overview">Overview</TabsTrigger> + <TabsTrigger value="analytics">Analytics</TabsTrigger> + <TabsTrigger value="reports">Reports</TabsTrigger> + </TabsList> + <TabsContent value="overview" className="p-4"> + <p className="text-muted-foreground text-sm">Overview content</p> + </TabsContent> + <TabsContent value="analytics" className="p-4"> + <p className="text-muted-foreground text-sm">Analytics content</p> + </TabsContent> + <TabsContent value="reports" className="p-4"> + <p className="text-muted-foreground text-sm">Reports content</p> + </TabsContent> + </Tabs> + <p className="text-muted-foreground text-sm"> + Current tab: <code className="font-mono">{activeTab}</code> + </p> + </SampleFrame> + ); +} + +export function TabsAsLinkSample() { + return ( + <SampleFrame className="flex h-auto items-center justify-center p-6"> + <Tabs defaultValue="docs"> + <TabsList variant="line"> + <TabsTrigger value="docs" asChild> + <a href="#installation"> + <FileText /> + Docs + </a> + </TabsTrigger> + <TabsTrigger value="api" asChild> + <a href="#api-reference"> + <Settings /> + API + </a> + </TabsTrigger> + </TabsList> + </Tabs> + </SampleFrame> + ); +} + +export function TabsAnimatedIndicatorSample() { + return ( + <SampleFrame className="flex h-auto flex-col items-center justify-center gap-8 p-6"> + <div className="flex flex-col gap-2"> + <span className="text-muted-foreground text-xs"> + Default - Sliding background + </span> + <Tabs defaultValue="home"> + <TabsList variant="default"> + <TabsTrigger value="home">Home</TabsTrigger> + <TabsTrigger value="about">About</TabsTrigger> + <TabsTrigger value="services">Services</TabsTrigger> + <TabsTrigger value="contact">Contact</TabsTrigger> + </TabsList> + </Tabs> + </div> + <div className="flex flex-col gap-2"> + <span className="text-muted-foreground text-xs"> + Line - Sliding underline + </span> + <Tabs defaultValue="home"> + <TabsList variant="line"> + <TabsTrigger value="home">Home</TabsTrigger> + <TabsTrigger value="about">About</TabsTrigger> + <TabsTrigger value="services">Services</TabsTrigger> + <TabsTrigger value="contact">Contact</TabsTrigger> + </TabsList> + </Tabs> + </div> + <div className="flex flex-col gap-2"> + <span className="text-muted-foreground text-xs"> + Ghost - Sliding background with hover effect + </span> + <Tabs defaultValue="dashboard"> + <TabsList variant="ghost"> + <TabsTrigger value="dashboard">Dashboard</TabsTrigger> + <TabsTrigger value="projects">Projects</TabsTrigger> + <TabsTrigger value="tasks">Tasks</TabsTrigger> + <TabsTrigger value="team">Team</TabsTrigger> + </TabsList> + </Tabs> + </div> + <div className="flex flex-col gap-2"> + <span className="text-muted-foreground text-xs"> + Pills - Sliding pill background + </span> + <Tabs defaultValue="all"> + <TabsList variant="pills"> + <TabsTrigger value="all">All</TabsTrigger> + <TabsTrigger value="active">Active</TabsTrigger> + <TabsTrigger value="completed">Completed</TabsTrigger> + <TabsTrigger value="archived">Archived</TabsTrigger> + </TabsList> + </Tabs> + </div> + <div className="flex flex-col gap-2"> + <span className="text-muted-foreground text-xs"> + Outline - Sliding border + </span> + <Tabs defaultValue="week"> + <TabsList variant="outline"> + <TabsTrigger value="day">Day</TabsTrigger> + <TabsTrigger value="week">Week</TabsTrigger> + <TabsTrigger value="month">Month</TabsTrigger> + <TabsTrigger value="year">Year</TabsTrigger> + </TabsList> + </Tabs> + </div> + </SampleFrame> + ); +} diff --git a/apps/docs/components/docs/samples/thread-list-primitive.tsx b/apps/docs/components/docs/samples/thread-list-primitive.tsx new file mode 100644 index 0000000..83b0fc5 --- /dev/null +++ b/apps/docs/components/docs/samples/thread-list-primitive.tsx @@ -0,0 +1,175 @@ +"use client"; + +import { useState, type ReactNode } from "react"; +import { + ArchiveIcon, + MoreHorizontalIcon, + PlusIcon, + TrashIcon, +} from "lucide-react"; +import { + AssistantRuntimeProvider, + ThreadListItemMorePrimitive, + ThreadListItemPrimitive, + ThreadListPrimitive, + useExternalStoreRuntime, + type ExternalStoreThreadData, + type ThreadMessage, +} from "@assistant-ui/react"; +import { SampleFrame } from "./sample-frame"; + +const titles = [ + "Draft the Q3 product roadmap", + "Fix TypeScript generic inference", + "Rewrite the welcome email", + "Optimize the analytics query", + "Compare the pricing tiers", +]; + +const seededThreads: ExternalStoreThreadData<"regular">[] = titles.map( + (title, i) => ({ id: `t-${i}`, status: "regular", title }), +); + +export function ThreadListPrimitiveSample() { + return ( + <SampleFrame className="bg-background h-96"> + <div className="relative flex h-full w-full items-center justify-center p-8"> + <LiveThreadList /> + <ControlLegend /> + </div> + </SampleFrame> + ); +} + +function ControlLegend() { + return ( + <div className="border-border bg-background/80 absolute end-4 bottom-4 flex flex-col gap-1.5 rounded-lg border p-2.5 text-xs shadow-sm backdrop-blur-sm"> + <LegendRow glyphs={["Tab"]} label="change focus" /> + <LegendRow glyphs={["↑", "↓"]} label="cycle list" /> + <LegendRow glyphs={["←", "→"]} label="focus, open/close menu" /> + </div> + ); +} + +function LegendRow({ glyphs, label }: { glyphs: string[]; label: string }) { + return ( + <div className="flex items-center gap-2"> + <span className="flex gap-1"> + {glyphs.map((g) => ( + <Kbd key={g}>{g}</Kbd> + ))} + </span> + <span className="text-muted-foreground">{label}</span> + </div> + ); +} + +function LiveThreadList() { + const [threads, setThreads] = + useState<ExternalStoreThreadData<"regular">[]>(seededThreads); + const [archived, setArchived] = useState< + ExternalStoreThreadData<"archived">[] + >([]); + const [activeId, setActiveId] = useState("new"); + const [newCount, setNewCount] = useState(0); + + const runtime = useExternalStoreRuntime<ThreadMessage>({ + messages: [], + onNew: async () => {}, + adapters: { + threadList: { + threadId: activeId, + threads, + archivedThreads: archived, + onSwitchToThread: (id) => setActiveId(id), + onSwitchToNewThread: () => { + const id = `t-new-${newCount}`; + setNewCount((c) => c + 1); + setThreads((prev) => [{ id, status: "regular" }, ...prev]); + setActiveId(id); + }, + onArchive: (id) => { + const found = threads.find((t) => t.id === id); + const next = threads.filter((t) => t.id !== id); + setThreads(next); + if (found) + setArchived((prev) => [ + { ...found, status: "archived" as const }, + ...prev, + ]); + if (activeId === id) setActiveId(next[0]?.id ?? "new"); + }, + onDelete: (id) => { + const next = threads.filter((t) => t.id !== id); + setThreads(next); + if (activeId === id) setActiveId(next[0]?.id ?? "new"); + }, + }, + }, + }); + + return ( + <AssistantRuntimeProvider runtime={runtime}> + <ThreadListPrimitive.Root className="w-full max-w-xs"> + <ThreadListPrimitive.New className="border-border hover:bg-muted data-active:bg-muted mb-1 flex h-9 w-full items-center gap-2 rounded-lg border px-3 text-sm font-medium transition-colors"> + <PlusIcon className="size-4" /> + New Thread + </ThreadListPrimitive.New> + <div className="flex flex-col gap-1"> + <ThreadListPrimitive.Items> + {() => <ThreadListItem />} + </ThreadListPrimitive.Items> + </div> + </ThreadListPrimitive.Root> + </AssistantRuntimeProvider> + ); +} + +function ThreadListItem() { + return ( + <ThreadListItemPrimitive.Root className="group hover:bg-muted focus-visible:bg-muted data-active:bg-muted has-focus-visible:bg-muted has-data-[state=open]:bg-muted relative flex h-9 items-center rounded-lg transition-colors focus-visible:outline-none"> + <ThreadListItemPrimitive.Trigger className="focus-visible:ring-ring/50 flex h-full min-w-0 flex-1 items-center truncate rounded-lg px-3 text-start text-sm outline-none group-hover:pe-9 group-has-focus-visible:pe-9 group-has-data-[state=open]:pe-9 group-data-active:pe-9 focus-visible:ring-2"> + <ThreadListItemPrimitive.Title fallback="New Chat" /> + </ThreadListItemPrimitive.Trigger> + <ThreadListItemMore /> + </ThreadListItemPrimitive.Root> + ); +} + +function ThreadListItemMore() { + return ( + <ThreadListItemMorePrimitive.Root sharedFocusGroup> + <ThreadListItemMorePrimitive.Trigger className="text-muted-foreground hover:bg-accent focus-visible:ring-ring/50 data-[state=open]:bg-accent absolute end-1.5 top-1/2 flex size-7 -translate-y-1/2 items-center justify-center rounded-md opacity-0 transition-opacity outline-none group-hover:opacity-100 group-has-focus-visible:opacity-100 group-data-active:opacity-100 focus-visible:opacity-100 focus-visible:ring-2 data-[state=open]:opacity-100"> + <MoreHorizontalIcon className="size-4" /> + <span className="sr-only">More options</span> + </ThreadListItemMorePrimitive.Trigger> + <ThreadListItemMorePrimitive.Content + side="right" + align="start" + sideOffset={6} + className="bg-popover text-popover-foreground z-50 min-w-36 rounded-lg border p-1 shadow-md" + > + <ThreadListItemPrimitive.Archive asChild> + <ThreadListItemMorePrimitive.Item className="hover:bg-accent focus:bg-accent flex cursor-pointer items-center gap-2 rounded-md px-2 py-1.5 text-sm outline-none"> + <ArchiveIcon className="size-4" /> + Archive + </ThreadListItemMorePrimitive.Item> + </ThreadListItemPrimitive.Archive> + <ThreadListItemPrimitive.Delete asChild> + <ThreadListItemMorePrimitive.Item className="text-destructive hover:bg-destructive/10 focus:bg-destructive/10 flex cursor-pointer items-center gap-2 rounded-md px-2 py-1.5 text-sm outline-none"> + <TrashIcon className="size-4" /> + Delete + </ThreadListItemMorePrimitive.Item> + </ThreadListItemPrimitive.Delete> + </ThreadListItemMorePrimitive.Content> + </ThreadListItemMorePrimitive.Root> + ); +} + +function Kbd({ children }: { children: ReactNode }) { + return ( + <kbd className="border-border bg-muted inline-flex h-5 min-w-5 items-center justify-center rounded border px-1 font-sans text-[11px]"> + {children} + </kbd> + ); +} diff --git a/apps/docs/components/docs/samples/thread-primitive.tsx b/apps/docs/components/docs/samples/thread-primitive.tsx new file mode 100644 index 0000000..2f45301 --- /dev/null +++ b/apps/docs/components/docs/samples/thread-primitive.tsx @@ -0,0 +1,76 @@ +"use client"; + +import { + ComposerPrimitive, + ThreadPrimitive, + MessagePrimitive, +} from "@assistant-ui/react"; +import { ArrowUpIcon } from "lucide-react"; +import { SampleRuntimeProvider } from "./sample-runtime-provider"; + +export function ThreadPrimitiveSample() { + return ( + <SampleRuntimeProvider messages={[]}> + <div className="not-prose border-border/50 bg-muted/40 flex items-end rounded-xl border p-4"> + <div className="mx-auto w-full max-w-lg"> + <ThreadPrimitive.Root className="flex h-[320px] flex-col"> + <ThreadPrimitive.Viewport className="flex flex-1 flex-col gap-3 overflow-y-auto scroll-smooth p-3"> + <ThreadPrimitive.Empty> + <div className="flex flex-1 flex-col items-center justify-center gap-2 text-center"> + <p className="text-foreground text-sm font-medium"> + Welcome! + </p> + <p className="text-muted-foreground text-xs"> + Ask a question to get started. + </p> + </div> + </ThreadPrimitive.Empty> + + <ThreadPrimitive.Messages + components={{ + UserMessage, + AssistantMessage, + }} + /> + + <ThreadPrimitive.ViewportFooter className="sticky bottom-0 pt-2"> + <ComposerPrimitive.Root className="border-border bg-muted dark:border-muted-foreground/15 relative flex w-full flex-col rounded-3xl border shadow-[0_9px_9px_0px_rgba(0,0,0,0.01),0_2px_5px_0px_rgba(0,0,0,0.06)]"> + <ComposerPrimitive.Input + placeholder="Ask anything..." + className="field-sizing-content min-h-10 w-full resize-none bg-transparent px-5 pt-3.5 pb-2.5 text-sm leading-relaxed focus:outline-none" + rows={1} + /> + <div className="flex items-center justify-end px-2.5 pb-2.5"> + <ComposerPrimitive.Send className="bg-primary text-primary-foreground flex size-8 shrink-0 items-center justify-center rounded-full transition-opacity disabled:opacity-30"> + <ArrowUpIcon className="size-4" /> + </ComposerPrimitive.Send> + </div> + </ComposerPrimitive.Root> + </ThreadPrimitive.ViewportFooter> + </ThreadPrimitive.Viewport> + </ThreadPrimitive.Root> + </div> + </div> + </SampleRuntimeProvider> + ); +} + +function UserMessage() { + return ( + <MessagePrimitive.Root className="flex justify-end"> + <div className="bg-primary text-primary-foreground max-w-[80%] rounded-2xl px-4 py-2.5 text-sm"> + <MessagePrimitive.Parts /> + </div> + </MessagePrimitive.Root> + ); +} + +function AssistantMessage() { + return ( + <MessagePrimitive.Root className="flex justify-start"> + <div className="bg-muted max-w-[80%] rounded-2xl px-4 py-2.5 text-sm"> + <MessagePrimitive.Parts /> + </div> + </MessagePrimitive.Root> + ); +} diff --git a/apps/docs/components/docs/samples/thread.tsx b/apps/docs/components/docs/samples/thread.tsx new file mode 100644 index 0000000..8d529d1 --- /dev/null +++ b/apps/docs/components/docs/samples/thread.tsx @@ -0,0 +1,12 @@ +"use client"; + +import { Thread } from "@/components/assistant-ui/thread"; +import { SampleFrame } from "@/components/docs/samples/sample-frame"; + +export const ThreadSample = () => { + return ( + <SampleFrame className="bg-muted/40 overflow-hidden"> + <Thread /> + </SampleFrame> + ); +}; diff --git a/apps/docs/components/docs/samples/threadlist.tsx b/apps/docs/components/docs/samples/threadlist.tsx new file mode 100644 index 0000000..6ce732b --- /dev/null +++ b/apps/docs/components/docs/samples/threadlist.tsx @@ -0,0 +1,21 @@ +"use client"; + +import { Thread } from "@/components/assistant-ui/thread"; +import { ThreadListSidebar } from "@/components/assistant-ui/threadlist-sidebar"; +import { SidebarInset, SidebarProvider } from "@/components/ui/radix/sidebar"; +import { SampleFrame } from "@/components/docs/samples/sample-frame"; + +export function ThreadListSample() { + return ( + <SampleFrame className="bg-muted/40 h-100 overflow-hidden md:h-150"> + <div className="relative h-full [&_[data-slot='sidebar-container']]:!absolute [&_[data-slot='sidebar-container']]:!h-full [&_[data-slot='sidebar-footer']]:p-0 [&_[data-slot='sidebar-header']]:p-0 [&_[data-slot='sidebar-menu']]:!my-0 [&_[data-slot='sidebar-menu']]:ps-0 [&_[data-slot='sidebar-menu-item']]:list-none [&_[data-slot='sidebar-wrapper']]:!min-h-full [&_a]:!no-underline"> + <SidebarProvider defaultOpen={true} className="h-full !min-h-full"> + <ThreadListSidebar /> + <SidebarInset className="!m-0 h-full"> + <Thread /> + </SidebarInset> + </SidebarProvider> + </div> + </SampleFrame> + ); +} diff --git a/apps/docs/components/docs/samples/tool-fallback.tsx b/apps/docs/components/docs/samples/tool-fallback.tsx new file mode 100644 index 0000000..f393bb9 --- /dev/null +++ b/apps/docs/components/docs/samples/tool-fallback.tsx @@ -0,0 +1,187 @@ +"use client"; + +import { useEffect, useState } from "react"; +import { PlayIcon } from "lucide-react"; +import type { ToolCallMessagePartStatus } from "@assistant-ui/react"; +import { + ToolFallbackRoot, + ToolFallbackTrigger, + ToolFallbackContent, + ToolFallbackArgs, + ToolFallbackResult, + ToolFallbackApproval, +} from "@/components/assistant-ui/tool-fallback"; +import { SampleFrame } from "@/components/docs/samples/sample-frame"; +import { Button } from "@/components/ui/button"; + +export function ToolFallbackSample() { + return ( + <SampleFrame className="flex h-auto items-center p-6"> + <ToolFallbackRoot> + <ToolFallbackTrigger + toolName="get_weather" + status={{ type: "complete" }} + /> + <ToolFallbackContent> + <ToolFallbackArgs + argsText={JSON.stringify({ location: "San Francisco" }, null, 2)} + /> + <ToolFallbackResult + result={{ temperature: 72, condition: "Sunny", humidity: 45 }} + /> + </ToolFallbackContent> + </ToolFallbackRoot> + </SampleFrame> + ); +} + +export function ToolFallbackRunningSample() { + return ( + <SampleFrame className="flex h-auto items-center p-6"> + <ToolFallbackRoot> + <ToolFallbackTrigger + toolName="search_web" + status={{ type: "running" }} + /> + <ToolFallbackContent> + <ToolFallbackArgs + argsText={JSON.stringify({ query: "latest news" }, null, 2)} + /> + </ToolFallbackContent> + </ToolFallbackRoot> + </SampleFrame> + ); +} + +export function ToolFallbackCancelledSample() { + return ( + <SampleFrame className="flex h-auto items-center p-6"> + <ToolFallbackRoot className="border-muted-foreground/30 bg-muted/30"> + <ToolFallbackTrigger + toolName="long_running_task" + status={{ type: "incomplete", reason: "cancelled" }} + /> + <ToolFallbackContent> + <ToolFallbackArgs + argsText={JSON.stringify({ task: "process_data" }, null, 2)} + className="opacity-60" + /> + </ToolFallbackContent> + </ToolFallbackRoot> + </SampleFrame> + ); +} + +export function ToolFallbackRequiresActionSample() { + return ( + <SampleFrame className="flex h-auto items-center p-6"> + <ToolFallbackRoot defaultOpen> + <ToolFallbackTrigger + toolName="delete_file" + status={{ type: "requires-action", reason: "interrupt" }} + /> + <ToolFallbackContent> + <ToolFallbackArgs + argsText={JSON.stringify( + { path: "/tmp/work-in-progress.txt" }, + null, + 2, + )} + /> + <ToolFallbackApproval + addResult={() => {}} + resume={() => {}} + interrupt={{ type: "human", payload: {} }} + /> + </ToolFallbackContent> + </ToolFallbackRoot> + </SampleFrame> + ); +} + +function ToolFallbackStreamingDemo() { + const [status, setStatus] = useState<ToolCallMessagePartStatus>({ + type: "complete", + }); + const [isOpen, setIsOpen] = useState(false); + const [streamedArgs, setStreamedArgs] = useState(""); + const [result, setResult] = useState<object | undefined>(undefined); + + const fullArgs = JSON.stringify({ location: "San Francisco" }, null, 2); + + const isRunning = status.type === "running"; + + useEffect(() => { + if (!isRunning) return; + + setIsOpen(true); + setStreamedArgs(""); + setResult(undefined); + + let index = 0; + const argsInterval = setInterval(() => { + if (index < fullArgs.length) { + setStreamedArgs(fullArgs.slice(0, index + 1)); + index++; + } else { + clearInterval(argsInterval); + // Simulate tool execution delay + setTimeout(() => { + const fullResult = { + temperature: 72, + condition: "Sunny", + humidity: 45, + }; + setResult(fullResult); + setStatus({ type: "complete" }); + }, 500); + } + }, 30); + + return () => clearInterval(argsInterval); + }, [isRunning, fullArgs]); + + const handleStart = () => { + setStreamedArgs(""); + setResult(undefined); + setStatus({ type: "running" }); + }; + + return ( + <div className="flex flex-col gap-3"> + <div className="flex items-center gap-2"> + <Button + size="sm" + variant="outline" + onClick={handleStart} + disabled={isRunning} + className="gap-1.5" + > + <PlayIcon className="size-3" /> + {isRunning ? "Running..." : "Start Tool Call"} + </Button> + </div> + <ToolFallbackRoot open={isOpen} onOpenChange={setIsOpen}> + <ToolFallbackTrigger toolName="get_weather" status={status} /> + <ToolFallbackContent> + {streamedArgs ? ( + <ToolFallbackArgs argsText={streamedArgs} /> + ) : ( + <div className="text-muted-foreground/50 px-4 italic"> + Click "Start Tool Call" to see the streaming effect + </div> + )} + {result && <ToolFallbackResult result={result} />} + </ToolFallbackContent> + </ToolFallbackRoot> + </div> + ); +} + +export function ToolFallbackStreamingSample() { + return ( + <SampleFrame className="h-auto p-4"> + <ToolFallbackStreamingDemo /> + </SampleFrame> + ); +} diff --git a/apps/docs/components/docs/samples/tool-group.tsx b/apps/docs/components/docs/samples/tool-group.tsx new file mode 100644 index 0000000..624d470 --- /dev/null +++ b/apps/docs/components/docs/samples/tool-group.tsx @@ -0,0 +1,374 @@ +"use client"; + +import { useEffect, useState } from "react"; +import { + PlayIcon, + CloudIcon, + SunIcon, + CloudRainIcon, + SearchIcon, + MapPinIcon, +} from "lucide-react"; +import type { ToolCallMessagePartStatus } from "@assistant-ui/react"; +import { + ToolGroupRoot, + ToolGroupTrigger, + ToolGroupContent, +} from "@/components/assistant-ui/tool-group"; +import { + ToolFallbackRoot, + ToolFallbackTrigger, + ToolFallbackContent, + ToolFallbackArgs, + ToolFallbackResult, +} from "@/components/assistant-ui/tool-fallback"; +import { SampleFrame } from "@/components/docs/samples/sample-frame"; +import { Button } from "@/components/ui/button"; +import { cn } from "@/lib/utils"; + +// Custom Weather Tool UI +function WeatherToolUI({ + location, + temperature, + condition, +}: { + location: string; + temperature: number; + condition: "Sunny" | "Cloudy" | "Rainy"; +}) { + const conditionConfig = { + Sunny: { icon: SunIcon, color: "text-yellow-500", bg: "bg-yellow-50" }, + Cloudy: { icon: CloudIcon, color: "text-gray-500", bg: "bg-gray-50" }, + Rainy: { icon: CloudRainIcon, color: "text-blue-500", bg: "bg-blue-50" }, + }; + + const config = conditionConfig[condition]; + const Icon = config.icon; + + return ( + <div className="flex items-center gap-3 rounded-lg border p-3"> + <div className={cn("rounded-full p-2", config.bg)}> + <Icon className={cn("size-5", config.color)} /> + </div> + <div className="flex-1"> + <div className="text-muted-foreground flex items-center gap-1 text-xs"> + <MapPinIcon className="size-3" /> + {location} + </div> + <div className="text-lg font-medium">{temperature}°F</div> + </div> + <div className="text-muted-foreground text-sm">{condition}</div> + </div> + ); +} + +// Custom Search Tool UI +function SearchToolUI({ query, results }: { query: string; results: number }) { + return ( + <div className="flex items-center gap-3 rounded-lg border p-3"> + <div className="rounded-full bg-purple-50 p-2"> + <SearchIcon className="size-5 text-purple-500" /> + </div> + <div className="flex-1"> + <div className="text-muted-foreground text-xs">Search query</div> + <div className="font-medium">"{query}"</div> + </div> + <div className="text-muted-foreground text-sm">{results} results</div> + </div> + ); +} + +function ToolGroupDemo({ + variant = "outline", +}: { + variant?: "outline" | "ghost" | "muted"; +}) { + return ( + <ToolGroupRoot variant={variant}> + <ToolGroupTrigger count={3} /> + <ToolGroupContent> + <WeatherToolUI + location="New York" + temperature={65} + condition="Cloudy" + /> + <WeatherToolUI location="London" temperature={55} condition="Rainy" /> + <SearchToolUI query="best restaurants nearby" results={24} /> + </ToolGroupContent> + </ToolGroupRoot> + ); +} + +function VariantRow({ + label, + variant = "outline", +}: { + label: string; + variant?: "outline" | "ghost" | "muted"; +}) { + return ( + <div className="flex flex-col gap-2"> + <span className="text-muted-foreground text-xs font-medium">{label}</span> + <ToolGroupDemo variant={variant} /> + </div> + ); +} + +export function ToolGroupSample() { + return ( + <SampleFrame className="flex h-auto flex-col gap-4 p-4"> + <VariantRow label="Outline (default)" variant="outline" /> + <VariantRow label="Ghost" variant="ghost" /> + <VariantRow label="Muted" variant="muted" /> + </SampleFrame> + ); +} + +// Streaming Weather Tool UI with loading state +function StreamingWeatherToolUI({ + location, + temperature, + condition, + isLoading, +}: { + location: string | undefined; + temperature: number | undefined; + condition: "Sunny" | "Cloudy" | "Rainy" | undefined; + isLoading: boolean; +}) { + const conditionConfig = { + Sunny: { icon: SunIcon, color: "text-yellow-500", bg: "bg-yellow-50" }, + Cloudy: { icon: CloudIcon, color: "text-gray-500", bg: "bg-gray-50" }, + Rainy: { icon: CloudRainIcon, color: "text-blue-500", bg: "bg-blue-50" }, + }; + + const config = condition ? conditionConfig[condition] : null; + const Icon = config?.icon ?? CloudIcon; + + return ( + <div className="flex items-center gap-3 rounded-lg border p-3"> + <div className={cn("rounded-full p-2", config?.bg ?? "bg-muted")}> + {isLoading ? ( + <div className="bg-muted-foreground/20 size-5 animate-pulse rounded-full" /> + ) : ( + <Icon + className={cn("size-5", config?.color ?? "text-muted-foreground")} + /> + )} + </div> + <div className="flex-1"> + <div className="text-muted-foreground flex items-center gap-1 text-xs"> + <MapPinIcon className="size-3" /> + {location ?? ( + <span className="bg-muted h-3 w-16 animate-pulse rounded" /> + )} + </div> + <div className="text-lg font-medium"> + {temperature !== undefined ? ( + `${temperature}°F` + ) : ( + <span className="bg-muted inline-block h-6 w-12 animate-pulse rounded" /> + )} + </div> + </div> + <div className="text-muted-foreground text-sm"> + {condition ?? + (isLoading && ( + <span className="bg-muted h-4 w-12 animate-pulse rounded" /> + ))} + </div> + </div> + ); +} + +function ToolGroupStreamingDemo() { + const [phase, setPhase] = useState< + "idle" | "tool1" | "tool2" | "tool3" | "done" + >("idle"); + const [isOpen, setIsOpen] = useState(false); + + // Weather tool 1 state + const [weather1, setWeather1] = useState<{ + location?: string; + temperature?: number; + condition?: "Sunny" | "Cloudy" | "Rainy"; + }>({}); + + // Weather tool 2 state (custom UI) + const [weather2, setWeather2] = useState<{ + location?: string; + temperature?: number; + condition?: "Sunny" | "Cloudy" | "Rainy"; + }>({}); + + // Search tool state (fallback UI) + const [searchStatus, setSearchStatus] = useState<ToolCallMessagePartStatus>({ + type: "complete", + }); + const [searchArgs, setSearchArgs] = useState(""); + const [searchResult, setSearchResult] = useState<object | undefined>( + undefined, + ); + + const isActive = phase !== "idle" && phase !== "done"; + const toolCount = + phase === "idle" ? 0 : phase === "tool1" ? 1 : phase === "tool2" ? 2 : 3; + + useEffect(() => { + if (phase === "idle" || phase === "done") return; + + let timeout: ReturnType<typeof setTimeout> | undefined; + + if (phase === "tool1") { + setIsOpen(true); + setWeather1({}); + setWeather2({}); + setSearchArgs(""); + setSearchResult(undefined); + + // Simulate streaming weather data + const steps = [ + () => setWeather1({ location: "Paris" }), + () => setWeather1((w) => ({ ...w, temperature: 68 })), + () => { + setWeather1((w) => ({ ...w, condition: "Sunny" })); + timeout = setTimeout(() => setPhase("tool2"), 300); + }, + ]; + + let step = 0; + const interval = setInterval(() => { + if (step < steps.length) { + steps[step]?.(); + step++; + } else { + clearInterval(interval); + } + }, 400); + return () => { + clearInterval(interval); + if (timeout) clearTimeout(timeout); + }; + } + + if (phase === "tool2") { + const steps = [ + () => setWeather2({ location: "Berlin" }), + () => setWeather2((w) => ({ ...w, temperature: 55 })), + () => { + setWeather2((w) => ({ ...w, condition: "Rainy" })); + timeout = setTimeout(() => setPhase("tool3"), 300); + }, + ]; + + let step = 0; + const interval = setInterval(() => { + if (step < steps.length) { + steps[step]?.(); + step++; + } else { + clearInterval(interval); + } + }, 400); + return () => { + clearInterval(interval); + if (timeout) clearTimeout(timeout); + }; + } + + // phase === "tool3" - Search with ToolFallback + setSearchStatus({ type: "running" }); + const fullArgs = JSON.stringify({ query: "weather comparison" }, null, 2); + + let index = 0; + const interval = setInterval(() => { + if (index < fullArgs.length) { + setSearchArgs(fullArgs.slice(0, index + 1)); + index++; + } else { + clearInterval(interval); + timeout = setTimeout(() => { + setSearchResult({ results: 24, relevance: "high" }); + setSearchStatus({ type: "complete" }); + setPhase("done"); + }, 400); + } + }, 25); + return () => { + clearInterval(interval); + if (timeout) clearTimeout(timeout); + }; + }, [phase]); + + const handleStart = () => { + setPhase("tool1"); + }; + + return ( + <div className="flex flex-col gap-3"> + <div className="flex items-center gap-2"> + <Button + size="sm" + variant="outline" + onClick={handleStart} + disabled={isActive} + className="gap-1.5" + > + <PlayIcon className="size-3" /> + {isActive ? "Running..." : "Start Tool Calls"} + </Button> + </div> + {toolCount > 0 && ( + <ToolGroupRoot open={isOpen} onOpenChange={setIsOpen}> + <ToolGroupTrigger count={toolCount} active={isActive} /> + <ToolGroupContent aria-busy={isActive}> + {/* Custom Weather UI */} + <StreamingWeatherToolUI + location={weather1.location} + temperature={weather1.temperature} + condition={weather1.condition} + isLoading={phase === "tool1" && !weather1.condition} + /> + + {/* Second Custom Weather UI */} + {toolCount >= 2 && ( + <StreamingWeatherToolUI + location={weather2.location} + temperature={weather2.temperature} + condition={weather2.condition} + isLoading={phase === "tool2" && !weather2.condition} + /> + )} + + {/* ToolFallback for search */} + {toolCount >= 3 && ( + <ToolFallbackRoot defaultOpen> + <ToolFallbackTrigger + toolName="search_web" + status={searchStatus} + /> + <ToolFallbackContent> + {searchArgs && <ToolFallbackArgs argsText={searchArgs} />} + {searchResult && <ToolFallbackResult result={searchResult} />} + </ToolFallbackContent> + </ToolFallbackRoot> + )} + </ToolGroupContent> + </ToolGroupRoot> + )} + {toolCount === 0 && ( + <div className="text-muted-foreground rounded-lg border border-dashed p-4 text-center text-sm italic"> + Click "Start Tool Calls" to see mixed custom UI + fallback + </div> + )} + </div> + ); +} + +export function ToolGroupStreamingSample() { + return ( + <SampleFrame className="h-auto p-4"> + <ToolGroupStreamingDemo /> + </SampleFrame> + ); +} diff --git a/apps/docs/components/docs/samples/tool-ui.tsx b/apps/docs/components/docs/samples/tool-ui.tsx new file mode 100644 index 0000000..06997c3 --- /dev/null +++ b/apps/docs/components/docs/samples/tool-ui.tsx @@ -0,0 +1,12 @@ +"use client"; + +import { Thread } from "@/components/assistant-ui/thread"; +import { SampleFrame } from "@/components/docs/samples/sample-frame"; + +export const ToolUISample = () => { + return ( + <SampleFrame className="bg-muted/40 overflow-hidden"> + <Thread /> + </SampleFrame> + ); +}; diff --git a/apps/docs/components/docs/samples/voice.tsx b/apps/docs/components/docs/samples/voice.tsx new file mode 100644 index 0000000..e69d73e --- /dev/null +++ b/apps/docs/components/docs/samples/voice.tsx @@ -0,0 +1,65 @@ +"use client"; + +import { useEffect, useState } from "react"; +import { SampleFrame } from "@/components/docs/samples/sample-frame"; +import { VoiceOrb, type VoiceOrbState } from "@/components/assistant-ui/voice"; + +const CYCLE_STATES: VoiceOrbState[] = [ + "idle", + "connecting", + "listening", + "speaking", + "muted", +]; + +export function VoiceSample() { + const [index, setIndex] = useState(0); + + useEffect(() => { + const interval = setInterval(() => { + setIndex((i) => (i + 1) % CYCLE_STATES.length); + }, 3000); + return () => clearInterval(interval); + }, []); + + const state = CYCLE_STATES[index]!; + + return ( + <SampleFrame className="flex h-auto flex-col items-center gap-6 bg-black/[0.03] py-12 dark:bg-white/[0.03]"> + <VoiceOrb state={state} variant="blue" className="size-32" /> + <span className="text-muted-foreground text-sm font-medium capitalize"> + {state} + </span> + </SampleFrame> + ); +} + +export function VoiceVariantsSample() { + return ( + <SampleFrame className="flex h-auto flex-wrap items-center justify-center gap-12 bg-black/[0.03] py-12 dark:bg-white/[0.03]"> + {(["default", "blue", "violet", "emerald"] as const).map((variant) => ( + <div key={variant} className="flex flex-col items-center gap-4"> + <VoiceOrb state="speaking" variant={variant} className="size-24" /> + <span className="text-muted-foreground text-xs font-medium capitalize"> + {variant} + </span> + </div> + ))} + </SampleFrame> + ); +} + +export function VoiceStatesSample() { + return ( + <SampleFrame className="flex h-auto flex-wrap items-center justify-center gap-10 bg-black/[0.03] py-12 dark:bg-white/[0.03]"> + {CYCLE_STATES.map((state) => ( + <div key={state} className="flex flex-col items-center gap-4"> + <VoiceOrb state={state} variant="blue" className="size-20" /> + <span className="text-muted-foreground text-xs font-medium capitalize"> + {state} + </span> + </div> + ))} + </SampleFrame> + ); +} diff --git a/apps/docs/components/docs/source-link.tsx b/apps/docs/components/docs/source-link.tsx new file mode 100644 index 0000000..f686a35 --- /dev/null +++ b/apps/docs/components/docs/source-link.tsx @@ -0,0 +1,20 @@ +import Link from "next/link"; +import { ExternalLink } from "lucide-react"; + +type SourceLinkProps = { + href: string; +}; + +export function SourceLink({ href }: SourceLinkProps) { + return ( + <Link + href={href} + target="_blank" + rel="noopener noreferrer" + className="text-muted-foreground hover:text-foreground inline-flex items-center gap-2 text-sm transition-colors" + > + <ExternalLink className="size-4" /> + View full source on GitHub + </Link> + ); +} diff --git a/apps/docs/components/docs/status-badge.tsx b/apps/docs/components/docs/status-badge.tsx new file mode 100644 index 0000000..dcad655 --- /dev/null +++ b/apps/docs/components/docs/status-badge.tsx @@ -0,0 +1,65 @@ +"use client"; + +import type { ReactNode } from "react"; +import { + Tooltip, + TooltipContent, + TooltipTrigger, +} from "@/components/ui/radix/tooltip"; +import { cn } from "@/lib/utils"; + +const VARIANTS = { + deprecated: { + label: "deprecated", + className: "bg-amber-500/10 text-amber-600 dark:text-amber-400", + tooltip: undefined, + }, + unstable: { + label: "unstable", + className: "bg-purple-500/10 text-purple-600 dark:text-purple-400", + tooltip: "Experimental: API or behavior may change in the future.", + }, +} as const; + +export type StatusBadgeVariant = keyof typeof VARIANTS; + +type StatusBadgeProps = { + variant: StatusBadgeVariant; + children?: ReactNode; + tooltip?: ReactNode; + className?: string; +}; + +export function StatusBadge({ + variant, + children = VARIANTS[variant].label, + tooltip = VARIANTS[variant].tooltip, + className, +}: StatusBadgeProps) { + const badge = ( + <span + className={cn( + "inline-flex items-center rounded px-1.5 py-0.5 text-xs font-medium", + VARIANTS[variant].className, + className, + )} + > + {children} + </span> + ); + + if (!tooltip) return badge; + + return ( + <Tooltip> + <TooltipTrigger asChild>{badge}</TooltipTrigger> + <TooltipContent + side="top" + sideOffset={6} + className="max-w-56 text-balance" + > + {tooltip} + </TooltipContent> + </Tooltip> + ); +} diff --git a/apps/docs/components/examples/artifacts-view.tsx b/apps/docs/components/examples/artifacts-view.tsx new file mode 100644 index 0000000..dbab063 --- /dev/null +++ b/apps/docs/components/examples/artifacts-view.tsx @@ -0,0 +1,62 @@ +"use client"; + +import { useAuiState } from "@assistant-ui/react"; +import type { ToolCallMessagePart } from "@assistant-ui/react"; +import { Tabs as TabsPrimitive } from "@base-ui/react/tabs"; + +export const ArtifactsView = () => { + const artifact = useAuiState((s) => { + const messages = s.thread.messages || []; + return messages + .flatMap((m) => + m.content.filter( + (c): c is ToolCallMessagePart => + c.type === "tool-call" && c.toolName === "render_html", + ), + ) + .at(-1)?.args.code as string | undefined; + }); + + if (!artifact) return null; + + return ( + <div className="flex grow basis-full justify-stretch p-3 transition-[width]"> + <div className="h-full w-full overflow-hidden rounded-lg border"> + <TabsPrimitive.Root + defaultValue="source" + className="flex h-full flex-col" + > + <TabsPrimitive.List className="grid w-full grid-cols-2 border-b"> + <TabsPrimitive.Tab + value="source" + className="data-active:border-primary border-b-2 border-transparent px-4 py-2 text-sm font-medium transition-colors hover:border-gray-300" + > + Source Code + </TabsPrimitive.Tab> + <TabsPrimitive.Tab + value="preview" + className="data-active:border-primary border-b-2 border-transparent px-4 py-2 text-sm font-medium transition-colors hover:border-gray-300" + > + Preview + </TabsPrimitive.Tab> + </TabsPrimitive.List> + <TabsPrimitive.Panel + value="source" + className="grow overflow-y-scroll px-4 py-2 font-mono text-sm wrap-break-word whitespace-pre-line" + > + {artifact} + </TabsPrimitive.Panel> + <TabsPrimitive.Panel value="preview" className="grow px-4 py-2"> + {artifact && ( + <iframe + title="artifact-preview" + className="h-full w-full" + srcDoc={artifact} + /> + )} + </TabsPrimitive.Panel> + </TabsPrimitive.Root> + </div> + </div> + ); +}; diff --git a/apps/docs/components/examples/artifacts.tsx b/apps/docs/components/examples/artifacts.tsx new file mode 100644 index 0000000..eafea0f --- /dev/null +++ b/apps/docs/components/examples/artifacts.tsx @@ -0,0 +1,15 @@ +"use client"; + +import { Thread } from "@/components/assistant-ui/thread"; +import { ArtifactsView } from "./artifacts-view"; + +export const Artifacts = () => { + return ( + <div className="flex h-full justify-stretch"> + <div className="grow basis-full"> + <Thread /> + </div> + <ArtifactsView /> + </div> + ); +}; diff --git a/apps/docs/components/examples/base.tsx b/apps/docs/components/examples/base.tsx new file mode 100644 index 0000000..a2b314f --- /dev/null +++ b/apps/docs/components/examples/base.tsx @@ -0,0 +1,984 @@ +"use client"; + +import { + ComposerAddAttachment, + ComposerAttachments, + UserMessageAttachments, +} from "@/components/assistant-ui/attachment"; +import { MarkdownText } from "@/components/assistant-ui/markdown-text"; +import { DotMatrix } from "@/components/assistant-ui/dot-matrix"; +import { MessageTiming } from "@/components/assistant-ui/message-timing"; +import { ToolFallback } from "@/components/assistant-ui/tool-fallback"; +import { + ToolGroupContent, + ToolGroupRoot, + ToolGroupTrigger, +} from "@/components/assistant-ui/tool-group"; +import { + ThreadList, + ThreadListItems, + ThreadListNew, + ThreadListRoot, +} from "@/components/assistant-ui/thread-list"; +import { TooltipIconButton } from "@/components/assistant-ui/tooltip-icon-button"; +import { + Reasoning, + ReasoningContent, + ReasoningRoot, + ReasoningText, + ReasoningTrigger, +} from "@/components/assistant-ui/reasoning"; +import { Button } from "@/components/ui/radix/button"; +import { cn } from "@/lib/utils"; +import icon from "@/public/favicon/icon.svg"; +import { + ComposerQuotePreview, + QuoteBlock, + SelectionToolbar, +} from "@/components/assistant-ui/quote"; +import { ComposerTriggerPopover } from "@/components/assistant-ui/composer-trigger-popover"; +import { DirectiveText } from "@/components/assistant-ui/directive-text"; +import { + ActionBarMorePrimitive, + ActionBarPrimitive, + AuiIf, + type AssistantState, + BranchPickerPrimitive, + ComposerPrimitive, + ErrorPrimitive, + groupPartByType, + MessagePrimitive, + ThreadPrimitive, + unstable_useMentionAdapter, + unstable_useSlashCommandAdapter, + useAui, + useAuiState, + type Unstable_SlashCommand, +} from "@assistant-ui/react"; +import { + ArrowDownIcon, + ArrowUpIcon, + ChartColumnIcon, + CheckIcon, + ChevronLeftIcon, + ChevronRightIcon, + CloudSunIcon, + CodeXmlIcon, + CopyIcon, + DownloadIcon, + FileTextIcon, + GlobeIcon, + HelpCircleIcon, + LanguagesIcon, + LightbulbIcon, + MenuIcon, + MicIcon, + MoreHorizontalIcon, + PanelLeftIcon, + PencilIcon, + PencilLineIcon, + RefreshCwIcon, + ShareIcon, + SlashIcon, + SquareIcon, + WrenchIcon, +} from "lucide-react"; +import { + LexicalComposerInput, + type DirectiveChipProps, +} from "@assistant-ui/react-lexical"; +import Image from "next/image"; +import { useState, type FC, type ReactNode } from "react"; +import { Sheet, SheetContent, SheetTrigger } from "@/components/ui/radix/sheet"; +import { + Tooltip, + TooltipContent, + TooltipTrigger, +} from "@/components/ui/radix/tooltip"; +import { ModelSelector } from "@/components/assistant-ui/model-selector"; +import { docsModelOptions } from "@/components/docs/assistant/docs-model-options"; +import { DEFAULT_MODEL_ID } from "@/constants/model"; + +const Logo: FC = () => { + return ( + <div className="flex items-center gap-2 px-2 text-sm font-medium"> + <Image + src={icon} + alt="logo" + className="size-5 dark:hue-rotate-180 dark:invert" + /> + <span className="text-foreground/90">assistant-ui</span> + </div> + ); +}; + +const Sidebar: FC<{ collapsed?: boolean }> = ({ collapsed }) => { + return ( + <aside + className={cn( + "flex h-full flex-col overflow-hidden transition-all duration-200", + collapsed ? "w-12" : "w-65", + )} + > + <div + className={cn( + "mt-2 flex h-12 shrink-0 items-center transition-[padding] duration-200", + collapsed ? "px-3.5" : "px-6", + )} + > + <Image + src={icon} + alt="logo" + className="size-5 shrink-0 dark:hue-rotate-180 dark:invert" + /> + <span + className={cn( + "text-foreground/90 ml-2 text-sm font-medium whitespace-nowrap transition-opacity duration-200", + collapsed && "opacity-0", + )} + > + assistant-ui + </span> + </div> + <ThreadListRoot + className={cn( + "relative flex-1 overflow-y-auto transition-[padding,width] duration-200", + collapsed ? "w-12 px-2 pt-1" : "w-65 p-3", + )} + > + <Tooltip> + <TooltipTrigger asChild> + <ThreadListNew + className={cn( + "overflow-hidden transition-all duration-200", + collapsed + ? "w-8 gap-0 px-2 has-[>svg]:px-2" + : "w-full gap-2 px-2.5 has-[>svg]:px-2.5", + )} + labelClassName={cn( + "overflow-hidden transition-all duration-200", + collapsed ? "max-w-0 opacity-0" : "max-w-24 opacity-100", + )} + /> + </TooltipTrigger> + {collapsed && ( + <TooltipContent side="right">New Thread</TooltipContent> + )} + </Tooltip> + <ThreadListItems + aria-hidden={collapsed} + inert={collapsed} + className={cn( + "transition-[opacity,transform] duration-150", + collapsed + ? "pointer-events-none opacity-0 delay-50" + : "translate-x-0 opacity-100", + )} + /> + </ThreadListRoot> + </aside> + ); +}; + +const MobileSidebar: FC = () => { + return ( + <Sheet> + <SheetTrigger asChild> + <Button + variant="ghost" + size="icon" + className="size-8 shrink-0 md:hidden" + > + <MenuIcon className="size-4" /> + <span className="sr-only">Toggle menu</span> + </Button> + </SheetTrigger> + <SheetContent side="left" className="flex w-70 flex-col p-0"> + <div className="flex h-12 shrink-0 items-center px-4"> + <Logo /> + </div> + <div className="relative flex-1 overflow-y-auto p-3"> + <ThreadList /> + </div> + </SheetContent> + </Sheet> + ); +}; + +const models = docsModelOptions(); + +const ModelPicker: FC = () => { + return ( + <ModelSelector + models={models} + defaultValue={DEFAULT_MODEL_ID} + variant="ghost" + size="sm" + className="h-7 rounded-full" + /> + ); +}; + +const ThreadTitle: FC = () => { + const title = useAuiState( + (s) => + s.threads.threadItems.find((t) => t.id === s.threads.mainThreadId)?.title, + ); + + return ( + <span className="min-w-0 truncate text-sm font-medium"> + {title ?? "New Chat"} + </span> + ); +}; + +const Header: FC<{ + sidebarCollapsed: boolean; + onToggleSidebar: () => void; +}> = ({ sidebarCollapsed, onToggleSidebar }) => { + return ( + <header className="flex h-12 shrink-0 items-center gap-2 px-4"> + <MobileSidebar /> + <TooltipIconButton + variant="ghost" + size="icon" + tooltip={sidebarCollapsed ? "Show sidebar" : "Hide sidebar"} + side="bottom" + onClick={onToggleSidebar} + className="hidden size-8 md:flex" + > + <PanelLeftIcon className="size-4" /> + </TooltipIconButton> + <ThreadTitle /> + <TooltipIconButton + variant="ghost" + size="icon" + tooltip="Share" + side="bottom" + disabled + className="ml-auto size-8" + > + <ShareIcon className="size-4" /> + </TooltipIconButton> + </header> + ); +}; + +// Startup exposes a loading placeholder thread; treat it as a new chat so +// the composer mounts centered. Loads after startup keep the docked layout. +const isNewChatView = (s: AssistantState) => + s.thread.messages.length === 0 && + (!s.thread.isLoading || s.threads.isLoading); + +const Thread: FC = () => { + const isEmpty = useAuiState(isNewChatView); + + return ( + <ThreadPrimitive.Root + className="aui-root aui-thread-root bg-background @container flex h-full flex-col" + style={{ + ["--thread-max-width" as string]: "44rem", + ["--composer-bg" as string]: + "color-mix(in oklab, var(--color-muted) 30%, var(--color-background))", + ["--composer-radius" as string]: "1.5rem", + ["--composer-padding" as string]: "8px", + }} + > + <ThreadPrimitive.Viewport + turnAnchor="top" + data-slot="aui_thread-viewport" + className={cn( + "relative flex flex-1 flex-col overflow-x-auto overflow-y-scroll scroll-smooth px-4 pt-4", + isEmpty && "justify-center", + )} + > + <AuiIf condition={isNewChatView}> + <ThreadWelcome /> + </AuiIf> + + <div + data-slot="aui_message-group" + className="mb-14 flex flex-col gap-y-6 empty:hidden" + > + <ThreadPrimitive.Messages> + {({ message }) => { + if (message.composer.isEditing) return <EditComposer />; + if (message.role === "user") return <UserMessage />; + return <AssistantMessage />; + }} + </ThreadPrimitive.Messages> + </div> + + <ThreadPrimitive.ViewportFooter + className={cn( + "aui-thread-viewport-footer bg-background mx-auto flex w-full max-w-(--thread-max-width) flex-col gap-4 overflow-visible pb-4 md:pb-6", + !isEmpty && "sticky bottom-0 mt-auto rounded-t-(--composer-radius)", + )} + > + <ThreadScrollToBottom /> + <Composer /> + <AuiIf condition={isNewChatView}> + <div className="aui-thread-welcome-suggestions-shell min-h-19"> + <AuiIf condition={(s) => s.composer.isEmpty}> + <ThreadSuggestions /> + </AuiIf> + </div> + </AuiIf> + </ThreadPrimitive.ViewportFooter> + </ThreadPrimitive.Viewport> + + <SelectionToolbar /> + </ThreadPrimitive.Root> + ); +}; + +const ThreadScrollToBottom: FC = () => { + return ( + <ThreadPrimitive.ScrollToBottom asChild> + <TooltipIconButton + tooltip="Scroll to bottom" + variant="outline" + className="aui-thread-scroll-to-bottom dark:border-border dark:bg-background dark:hover:bg-accent absolute -top-12 z-10 self-center rounded-full p-4 disabled:invisible" + > + <ArrowDownIcon /> + </TooltipIconButton> + </ThreadPrimitive.ScrollToBottom> + ); +}; + +const ThreadWelcome: FC = () => { + return ( + <div className="aui-thread-welcome-root mx-auto mb-6 flex w-full max-w-(--thread-max-width) flex-col items-center px-4 text-center"> + <h1 className="aui-thread-welcome-message-inner fade-in slide-in-from-bottom-1 animate-in fill-mode-both text-2xl font-semibold duration-200"> + How can I help you today? + </h1> + </div> + ); +}; + +type SuggestionGroup = { + label: string; + icon: ReactNode; + options: { label: string; prompt: string }[]; +}; + +const SUGGESTION_GROUPS: SuggestionGroup[] = [ + { + label: "Weather", + icon: <CloudSunIcon />, + options: [ + { + label: "in San Francisco", + prompt: "What's the weather in San Francisco?", + }, + { label: "in Singapore", prompt: "What's the weather in Singapore?" }, + { label: "in Tokyo", prompt: "What's the weather in Tokyo?" }, + { label: "in London", prompt: "What's the weather in London?" }, + ], + }, + { + label: "Code", + icon: <CodeXmlIcon />, + options: [ + { + label: "explain React hooks", + prompt: "Explain React hooks like useState and useEffect", + }, + { + label: "write a debounce function", + prompt: "Write a debounce function in TypeScript", + }, + { + label: "review a useEffect cleanup", + prompt: "Show me the right way to clean up a subscription in useEffect", + }, + ], + }, + { + label: "Write", + icon: <PencilLineIcon />, + options: [ + { + label: "a birthday card message", + prompt: + "Help me write a birthday card message for a friend in the notepad", + }, + { + label: "a product announcement", + prompt: "Draft a short product announcement for a new dark mode", + }, + { + label: "release notes", + prompt: + "Write release notes for a bugfix release of a React component library", + }, + { + label: "a PR description", + prompt: + "Write a pull request description for a change that adds keyboard shortcuts", + }, + ], + }, + { + label: "Analyze", + icon: <ChartColumnIcon />, + options: [ + { + label: "React vs Vue vs Svelte", + prompt: "Compare React, Vue, and Svelte in a table", + }, + { + label: "GDP of US, China, Japan", + prompt: + "Compare the GDP of the United States, China, and Japan in a table", + }, + { + label: "pros and cons of SSR", + prompt: "What are the pros and cons of server-side rendering?", + }, + ], + }, + { + label: "Brainstorm", + icon: <LightbulbIcon />, + options: [ + { + label: "side project ideas", + prompt: "Brainstorm five side project ideas for a React developer", + }, + { + label: "names for a dev tool", + prompt: "Brainstorm names for a developer tools startup", + }, + { + label: "talk topics", + prompt: "Brainstorm talk topics for a React meetup", + }, + ], + }, +]; + +const suggestionChipClass = + "aui-thread-welcome-suggestion text-foreground hover:bg-muted border-border/60 h-auto gap-1.5 rounded-full border px-3.5 py-1.5 text-sm font-normal whitespace-nowrap transition-colors [&_svg]:size-4"; + +const ThreadSuggestions: FC = () => { + const aui = useAui(); + const [expandedLabel, setExpandedLabel] = useState<string | null>(null); + const expandedGroup = SUGGESTION_GROUPS.find( + (group) => group.label === expandedLabel, + ); + + const sendPrompt = (prompt: string) => { + if (aui.thread().getState().isRunning) return; + aui.thread().append({ + content: [{ type: "text", text: prompt }], + runConfig: aui.composer().getState().runConfig, + }); + }; + + return ( + <div className="aui-thread-welcome-suggestions flex w-full flex-col gap-2 px-4"> + <div className="w-full scrollbar-none overflow-x-auto"> + <div className="mx-auto flex w-max items-center gap-2"> + {SUGGESTION_GROUPS.map((group) => ( + <Button + key={group.label} + variant="ghost" + className={cn( + suggestionChipClass, + group.label === expandedLabel && "bg-muted", + )} + onClick={() => + setExpandedLabel( + group.label === expandedLabel ? null : group.label, + ) + } + > + {group.icon} + {group.label} + </Button> + ))} + </div> + </div> + {expandedGroup && ( + <div + key={expandedGroup.label} + className="fade-in slide-in-from-top-1 animate-in w-full scrollbar-none overflow-x-auto duration-200" + > + <div className="mx-auto flex w-max items-center gap-2"> + {expandedGroup.options.map((option) => ( + <Button + key={option.label} + variant="ghost" + className={suggestionChipClass} + onClick={() => sendPrompt(option.prompt)} + > + {option.label} + </Button> + ))} + </div> + </div> + )} + </div> + ); +}; + +const slashCommands: readonly Unstable_SlashCommand[] = [ + { + id: "summarize", + description: "Summarize the conversation", + icon: "FileText", + execute: () => console.log("[base example] /summarize invoked"), + }, + { + id: "translate", + description: "Translate text to another language", + icon: "Languages", + execute: () => console.log("[base example] /translate invoked"), + }, + { + id: "search", + description: "Search the web for information", + icon: "Globe", + execute: () => console.log("[base example] /search invoked"), + }, + { + id: "help", + description: "List available commands", + icon: "HelpCircle", + execute: () => console.log("[base example] /help invoked"), + }, +]; + +const slashIconMap: Record<string, FC<{ className?: string }>> = { + FileText: FileTextIcon, + Languages: LanguagesIcon, + Globe: GlobeIcon, + HelpCircle: HelpCircleIcon, +}; + +function DirectiveChip(props: DirectiveChipProps) { + const { directiveId, directiveType, label } = props; + const showWrench = directiveType !== "command"; + return ( + <span + className="aui-directive-chip" + data-directive-type={directiveType} + data-directive-id={directiveId} + > + {showWrench && ( + <span className="aui-directive-chip-icon"> + <WrenchIcon className="size-3" /> + </span> + )} + <span className="aui-directive-chip-label">{label}</span> + </span> + ); +} + +const Composer: FC = () => { + const mention = unstable_useMentionAdapter({ fallbackIcon: WrenchIcon }); + const slash = unstable_useSlashCommandAdapter({ + commands: slashCommands, + iconMap: slashIconMap, + fallbackIcon: SlashIcon, + }); + + return ( + <ComposerPrimitive.Unstable_TriggerPopoverRoot> + <ComposerPrimitive.Root className="aui-composer-root relative flex w-full flex-col"> + <ComposerPrimitive.AttachmentDropzone asChild> + <div + data-slot="aui_composer-shell" + className="border-border/60 data-[dragging=true]:border-ring focus-within:border-border dark:border-muted-foreground/15 dark:focus-within:border-muted-foreground/30 flex w-full flex-col gap-2 rounded-(--composer-radius) border bg-(--composer-bg) p-(--composer-padding) shadow-[0_4px_16px_-8px_rgba(0,0,0,0.08),0_1px_2px_rgba(0,0,0,0.04)] transition-[border-color,box-shadow] focus-within:shadow-[0_6px_24px_-8px_rgba(0,0,0,0.12),0_1px_2px_rgba(0,0,0,0.05)] data-[dragging=true]:border-dashed data-[dragging=true]:bg-[color-mix(in_oklab,var(--color-accent)_50%,var(--color-background))] dark:shadow-none" + > + <ComposerQuotePreview /> + <ComposerAttachments /> + <LexicalComposerInput + directiveChip={DirectiveChip} + placeholder="Send a message... (@ to mention, / for commands)" + className="aui-composer-input [&_.aui-lexical-placeholder]:text-muted-foreground/80 relative max-h-32 min-h-10 w-full resize-none bg-transparent px-2.5 py-1 text-base outline-none [&_.aui-directive-chip]:inline-flex [&_.aui-directive-chip]:items-baseline [&_.aui-directive-chip]:gap-1 [&_.aui-directive-chip]:rounded-md [&_.aui-directive-chip]:bg-blue-100 [&_.aui-directive-chip]:px-1.5 [&_.aui-directive-chip]:py-0.5 [&_.aui-directive-chip]:text-[13px] [&_.aui-directive-chip]:leading-none [&_.aui-directive-chip]:font-medium [&_.aui-directive-chip]:text-blue-700 dark:[&_.aui-directive-chip]:bg-blue-900/50 dark:[&_.aui-directive-chip]:text-blue-300 [&_.aui-directive-chip-icon]:self-center [&_.aui-lexical-input]:min-h-lh [&_.aui-lexical-input]:outline-none [&_.aui-lexical-placeholder]:pointer-events-none [&_.aui-lexical-placeholder]:absolute [&_.aui-lexical-placeholder]:top-0 [&_.aui-lexical-placeholder]:right-0 [&_.aui-lexical-placeholder]:left-0 [&_.aui-lexical-placeholder]:truncate [&_.aui-lexical-placeholder]:px-2.5 [&_.aui-lexical-placeholder]:py-1" + /> + <ComposerAction /> + </div> + </ComposerPrimitive.AttachmentDropzone> + + <ComposerTriggerPopover char="@" {...mention} /> + + <ComposerTriggerPopover + char="/" + {...slash} + emptyItemsLabel="No matching commands" + /> + </ComposerPrimitive.Root> + </ComposerPrimitive.Unstable_TriggerPopoverRoot> + ); +}; + +const ComposerAction: FC = () => { + return ( + <div className="aui-composer-action-wrapper relative flex items-center justify-between"> + <div className="flex items-center gap-1"> + <ComposerAddAttachment /> + <ModelPicker /> + </div> + <div className="flex items-center gap-1.5"> + <AuiIf condition={(s) => s.thread.capabilities.dictation}> + <AuiIf condition={(s) => s.composer.dictation == null}> + <ComposerPrimitive.Dictate asChild> + <TooltipIconButton + tooltip="Voice input" + side="bottom" + type="button" + variant="ghost" + size="icon" + className="aui-composer-dictate size-7 rounded-full" + aria-label="Start voice input" + > + <MicIcon className="aui-composer-dictate-icon size-4" /> + </TooltipIconButton> + </ComposerPrimitive.Dictate> + </AuiIf> + <AuiIf condition={(s) => s.composer.dictation != null}> + <ComposerPrimitive.StopDictation asChild> + <TooltipIconButton + tooltip="Stop dictation" + side="bottom" + type="button" + variant="ghost" + size="icon" + className="aui-composer-stop-dictation text-destructive size-7 rounded-full" + aria-label="Stop voice input" + > + <SquareIcon className="aui-composer-stop-dictation-icon size-3.5 animate-pulse fill-current" /> + </TooltipIconButton> + </ComposerPrimitive.StopDictation> + </AuiIf> + </AuiIf> + <AuiIf condition={(s) => !s.thread.isRunning}> + <ComposerPrimitive.Send asChild> + <TooltipIconButton + tooltip="Send message" + side="bottom" + type="button" + variant="default" + size="icon" + className="aui-composer-send size-7 rounded-full" + aria-label="Send message" + > + <ArrowUpIcon className="aui-composer-send-icon size-4.5" /> + </TooltipIconButton> + </ComposerPrimitive.Send> + </AuiIf> + <AuiIf condition={(s) => s.thread.isRunning}> + <ComposerPrimitive.Cancel asChild> + <Button + type="button" + variant="default" + size="icon" + className="aui-composer-cancel size-7 rounded-full" + aria-label="Stop generating" + > + <SquareIcon className="aui-composer-cancel-icon size-3.5 fill-current" /> + </Button> + </ComposerPrimitive.Cancel> + </AuiIf> + </div> + </div> + ); +}; + +const MessageError: FC = () => { + return ( + <MessagePrimitive.Error> + <ErrorPrimitive.Root className="aui-message-error-root border-destructive bg-destructive/10 text-destructive dark:bg-destructive/5 mt-2 rounded-md border p-3 text-sm dark:text-red-200"> + <ErrorPrimitive.Message className="aui-message-error-message line-clamp-2" /> + </ErrorPrimitive.Root> + </MessagePrimitive.Error> + ); +}; + +const AssistantWorkingIndicator: FC = () => { + const isEmpty = useAuiState((s) => s.message.content.length === 0); + if (isEmpty) { + return ( + <span + data-slot="aui_assistant-message-indicator" + className="text-muted-foreground inline-flex items-center gap-2 align-middle" + > + <DotMatrix state="connecting" aria-hidden /> + <span className="text-sm">Connecting</span> + </span> + ); + } + return ( + <span + data-slot="aui_assistant-message-indicator" + className="animate-pulse font-sans" + aria-label="Assistant is working" + > + {"●"} + </span> + ); +}; + +const AssistantMessage: FC = () => { + // reserves space for action bar and compensates with `-mb` for consistent msg spacing + // keeps hovered action bar from shifting layout (autohide doesn't support absolute positioning well) + // for pt-[n] use -mb-[n + 6] & min-h-[n + 6] to preserve compensation + const ACTION_BAR_PT = "pt-1.5"; + const ACTION_BAR_HEIGHT = `-mb-7.5 min-h-7.5 ${ACTION_BAR_PT}`; + + return ( + <MessagePrimitive.Root + data-slot="aui_assistant-message-root" + data-role="assistant" + className="fade-in slide-in-from-bottom-1 animate-in relative mx-auto w-full max-w-(--thread-max-width) duration-150" + > + <div + data-slot="aui_assistant-message-content" + className="text-foreground px-2 leading-relaxed wrap-break-word" + > + <MessagePrimitive.GroupedParts + groupBy={groupPartByType({ + reasoning: ["group-chainOfThought", "group-reasoning"], + "tool-call": ["group-chainOfThought", "group-tool"], + "standalone-tool-call": [], + })} + > + {({ part, children }) => { + switch (part.type) { + case "group-chainOfThought": + return <div data-slot="aui_chain-of-thought">{children}</div>; + case "group-tool": + return ( + <ToolGroupRoot variant="ghost"> + <ToolGroupTrigger + count={part.indices.length} + active={part.status.type === "running"} + /> + <ToolGroupContent>{children}</ToolGroupContent> + </ToolGroupRoot> + ); + case "group-reasoning": { + const running = part.status.type === "running"; + return ( + <ReasoningRoot defaultOpen={running}> + <ReasoningTrigger active={running} /> + <ReasoningContent aria-busy={running}> + <ReasoningText>{children}</ReasoningText> + </ReasoningContent> + </ReasoningRoot> + ); + } + case "text": + return <MarkdownText />; + case "reasoning": + return <Reasoning {...part} />; + case "tool-call": + return part.toolUI ?? <ToolFallback {...part} />; + case "indicator": + return <AssistantWorkingIndicator />; + case "data": + return part.dataRendererUI; + default: + return null; + } + }} + </MessagePrimitive.GroupedParts> + <MessageError /> + </div> + + <div + data-slot="aui_assistant-message-footer" + className={cn("ml-2 flex items-center", ACTION_BAR_HEIGHT)} + > + <BranchPicker /> + <AssistantActionBar /> + </div> + </MessagePrimitive.Root> + ); +}; + +const AssistantActionBar: FC = () => { + return ( + <ActionBarPrimitive.Root + hideWhenRunning + autohide="not-last" + className="aui-assistant-action-bar-root text-muted-foreground animate-in fade-in col-start-3 row-start-2 -ml-1 flex gap-1 duration-200" + > + <ActionBarPrimitive.Copy asChild> + <TooltipIconButton tooltip="Copy"> + <AuiIf condition={(s) => s.message.isCopied}> + <CheckIcon className="animate-in zoom-in-50 fade-in duration-200 ease-out" /> + </AuiIf> + <AuiIf condition={(s) => !s.message.isCopied}> + <CopyIcon className="animate-in zoom-in-75 fade-in duration-150" /> + </AuiIf> + </TooltipIconButton> + </ActionBarPrimitive.Copy> + <ActionBarPrimitive.Reload asChild> + <TooltipIconButton tooltip="Refresh"> + <RefreshCwIcon /> + </TooltipIconButton> + </ActionBarPrimitive.Reload> + <ActionBarMorePrimitive.Root> + <ActionBarMorePrimitive.Trigger asChild> + <TooltipIconButton + tooltip="More" + className="data-[state=open]:bg-accent" + > + <MoreHorizontalIcon /> + </TooltipIconButton> + </ActionBarMorePrimitive.Trigger> + <ActionBarMorePrimitive.Content + side="bottom" + align="start" + sideOffset={6} + className="aui-action-bar-more-content bg-popover/95 text-popover-foreground data-[state=open]:fade-in-0 data-[state=open]:zoom-in-95 data-[state=open]:animate-in data-[state=closed]:fade-out-0 data-[state=closed]:zoom-out-95 data-[state=closed]:animate-out data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2 z-50 min-w-[8rem] overflow-hidden rounded-xl border p-1.5 shadow-lg backdrop-blur-sm" + > + <ActionBarPrimitive.ExportMarkdown asChild> + <ActionBarMorePrimitive.Item className="aui-action-bar-more-item hover:bg-accent hover:text-accent-foreground focus:bg-accent focus:text-accent-foreground flex cursor-pointer items-center gap-2 rounded-lg px-2.5 py-1.5 text-sm outline-none select-none"> + <DownloadIcon className="size-4" /> + Export as Markdown + </ActionBarMorePrimitive.Item> + </ActionBarPrimitive.ExportMarkdown> + </ActionBarMorePrimitive.Content> + </ActionBarMorePrimitive.Root> + <MessageTiming /> + </ActionBarPrimitive.Root> + ); +}; + +const UserMessage: FC = () => { + return ( + <MessagePrimitive.Root + data-slot="aui_user-message-root" + data-role="user" + className="fade-in slide-in-from-bottom-1 animate-in mx-auto grid w-full max-w-(--thread-max-width) auto-rows-auto grid-cols-[minmax(72px,1fr)_auto] content-start gap-y-2 px-2 duration-150 [&:where(>*)]:col-start-2" + > + <UserMessageAttachments /> + + <div className="aui-user-message-content-wrapper relative col-start-2 min-w-0"> + <div className="aui-user-message-content peer bg-muted text-foreground rounded-xl px-4 py-2 wrap-break-word empty:hidden"> + <MessagePrimitive.Quote> + {(quote) => <QuoteBlock {...quote} />} + </MessagePrimitive.Quote> + <MessagePrimitive.Parts components={{ Text: DirectiveText }} /> + </div> + <div className="aui-user-action-bar-wrapper absolute top-1/2 left-0 -translate-x-full -translate-y-1/2 pr-2 peer-empty:hidden"> + <UserActionBar /> + </div> + </div> + + <BranchPicker + data-slot="aui_user-branch-picker" + className="col-span-full col-start-1 row-start-3 -mr-1 justify-end" + /> + </MessagePrimitive.Root> + ); +}; + +const UserActionBar: FC = () => { + return ( + <ActionBarPrimitive.Root + hideWhenRunning + autohide="not-last" + className="aui-user-action-bar-root flex flex-col items-end" + > + <ActionBarPrimitive.Edit asChild> + <TooltipIconButton tooltip="Edit" className="aui-user-action-edit"> + <PencilIcon /> + </TooltipIconButton> + </ActionBarPrimitive.Edit> + </ActionBarPrimitive.Root> + ); +}; + +const EditComposer: FC = () => { + return ( + <MessagePrimitive.Root + data-slot="aui_edit-composer-wrapper" + className="mx-auto flex w-full max-w-(--thread-max-width) flex-col px-2" + > + <ComposerPrimitive.Unstable_TriggerPopoverRoot> + <ComposerPrimitive.Root className="aui-edit-composer-root border-border/60 dark:border-muted-foreground/15 ml-auto flex w-full max-w-[85%] flex-col rounded-(--composer-radius) border bg-(--composer-bg) shadow-[0_4px_16px_-8px_rgba(0,0,0,0.08),0_1px_2px_rgba(0,0,0,0.04)] dark:shadow-none"> + <LexicalComposerInput + directiveChip={DirectiveChip} + autoFocus + className="aui-edit-composer-input text-foreground min-h-14 w-full resize-none bg-transparent px-4 pt-3 pb-1 text-base outline-none [&_.aui-directive-chip]:inline-flex [&_.aui-directive-chip]:items-baseline [&_.aui-directive-chip]:gap-1 [&_.aui-directive-chip]:rounded-md [&_.aui-directive-chip]:bg-blue-100 [&_.aui-directive-chip]:px-1.5 [&_.aui-directive-chip]:py-0.5 [&_.aui-directive-chip]:text-[13px] [&_.aui-directive-chip]:leading-none [&_.aui-directive-chip]:font-medium [&_.aui-directive-chip]:text-blue-700 dark:[&_.aui-directive-chip]:bg-blue-900/50 dark:[&_.aui-directive-chip]:text-blue-300 [&_.aui-directive-chip-icon]:self-center [&_.aui-lexical-input]:min-h-lh [&_.aui-lexical-input]:outline-none" + /> + <div className="aui-edit-composer-footer mx-2.5 mb-2.5 flex items-center gap-1.5 self-end"> + <ComposerPrimitive.Cancel asChild> + <Button + variant="ghost" + size="sm" + className="h-8 rounded-full px-3.5" + > + Cancel + </Button> + </ComposerPrimitive.Cancel> + <ComposerPrimitive.Send asChild> + <Button size="sm" className="h-8 rounded-full px-3.5"> + Update + </Button> + </ComposerPrimitive.Send> + </div> + </ComposerPrimitive.Root> + </ComposerPrimitive.Unstable_TriggerPopoverRoot> + </MessagePrimitive.Root> + ); +}; + +const BranchPicker: FC<BranchPickerPrimitive.Root.Props> = ({ + className, + ...rest +}) => { + return ( + <BranchPickerPrimitive.Root + hideWhenSingleBranch + className={cn( + "aui-branch-picker-root text-muted-foreground mr-2 -ml-2 inline-flex items-center text-xs", + className, + )} + {...rest} + > + <BranchPickerPrimitive.Previous asChild> + <TooltipIconButton tooltip="Previous"> + <ChevronLeftIcon /> + </TooltipIconButton> + </BranchPickerPrimitive.Previous> + <span className="aui-branch-picker-state font-medium"> + <BranchPickerPrimitive.Number /> / <BranchPickerPrimitive.Count /> + </span> + <BranchPickerPrimitive.Next asChild> + <TooltipIconButton tooltip="Next"> + <ChevronRightIcon /> + </TooltipIconButton> + </BranchPickerPrimitive.Next> + </BranchPickerPrimitive.Root> + ); +}; + +export const Base: FC = () => { + const [sidebarCollapsed, setSidebarCollapsed] = useState(false); + + return ( + <div className="bg-muted/30 flex h-full w-full"> + <div className="hidden md:block"> + <Sidebar collapsed={sidebarCollapsed} /> + </div> + <div className="flex flex-1 flex-col overflow-hidden p-2 md:pl-0"> + <div className="bg-background flex flex-1 flex-col overflow-hidden rounded-lg"> + <Header + sidebarCollapsed={sidebarCollapsed} + onToggleSidebar={() => setSidebarCollapsed(!sidebarCollapsed)} + /> + <main className="flex-1 overflow-hidden"> + <Thread /> + </main> + </div> + </div> + </div> + ); +}; diff --git a/apps/docs/components/examples/chatgpt.tsx b/apps/docs/components/examples/chatgpt.tsx new file mode 100644 index 0000000..90cf5fc --- /dev/null +++ b/apps/docs/components/examples/chatgpt.tsx @@ -0,0 +1,473 @@ +"use client"; + +import { cn } from "@/lib/utils"; +import { + ActionBarPrimitive, + ActionBarMorePrimitive, + AuiIf, + AttachmentPrimitive, + BranchPickerPrimitive, + ComposerPrimitive, + MessagePrimitive, + ThreadPrimitive, + useAui, + useAuiState, +} from "@assistant-ui/react"; +import { useEffect, useState, type FC } from "react"; +import { TooltipIconButton } from "@/components/assistant-ui/tooltip-icon-button"; +import { useShallow } from "zustand/shallow"; +import { + ArrowUpIcon, + AudioLines, + CheckIcon, + ChevronDownIcon, + ChevronLeftIcon, + ChevronRightIcon, + CopyIcon, + Download, + Mic, + MoreHorizontal, + PencilIcon, + PlusIcon, + RefreshCwIcon, + Share, + ThumbsDown, + ThumbsUp, + Volume2, + XIcon, +} from "lucide-react"; +import { MarkdownText } from "@/components/assistant-ui/markdown-text"; +import { ToolFallback } from "@/components/assistant-ui/tool-fallback"; + +export const ChatGPT: FC = () => { + return ( + <ThreadPrimitive.Root className="flex h-full flex-col items-stretch bg-white px-4 text-[#0d0d0d] dark:bg-black dark:text-[#ececec]"> + <AuiIf condition={(s) => s.thread.isEmpty}> + <EmptyState /> + </AuiIf> + + <AuiIf condition={(s) => !s.thread.isEmpty}> + <ThreadPrimitive.Viewport className="flex grow flex-col gap-8 overflow-y-scroll pt-16"> + <ThreadPrimitive.Messages> + {({ message }) => { + if (message.composer.isEditing) return <EditComposer />; + if (message.role === "user") return <UserMessage />; + return <AssistantMessage />; + }} + </ThreadPrimitive.Messages> + + <ThreadPrimitive.ViewportFooter className="sticky bottom-0 mx-auto mt-auto flex w-full max-w-3xl flex-col gap-2 overflow-visible rounded-t-3xl bg-white pb-2 dark:bg-black"> + <ThreadScrollToBottom /> + <Composer placeholder="Ask anything" /> + <p className="text-center text-xs text-[#5d5d5d] dark:text-[#afafaf]"> + ChatGPT can make mistakes. Check important info. + </p> + </ThreadPrimitive.ViewportFooter> + </ThreadPrimitive.Viewport> + </AuiIf> + </ThreadPrimitive.Root> + ); +}; + +const EmptyState: FC = () => { + return ( + <div className="flex grow flex-col items-center justify-center px-4 pb-[16vh]"> + <div className="mx-auto flex w-full max-w-3xl flex-col items-stretch gap-6"> + <h1 className="text-center text-2xl leading-7 font-normal text-[#0d0d0d] dark:text-[#ececec]"> + Where should we begin? + </h1> + <Composer placeholder="Ask anything" /> + </div> + </div> + ); +}; + +const Composer: FC<{ placeholder: string }> = ({ placeholder }) => { + return ( + <ComposerPrimitive.Root className="group/composer flex w-full flex-col rounded-[28px] border border-[#e5e5e5] bg-white px-2 py-2 shadow-[0_2px_6px_-2px_rgba(0,0,0,0.05)] focus-within:border-[#d0d0d0] dark:border-transparent dark:bg-[#212121] dark:shadow-[inset_0_0_1px_0_rgba(255,255,255,0.2)] dark:focus-within:border-transparent"> + <AuiIf condition={(s) => s.composer.attachments.length > 0}> + <div className="flex flex-row flex-wrap gap-2 px-1 pt-1 pb-2"> + <ComposerPrimitive.Attachments + components={{ Attachment: ChatGPTAttachmentUI }} + /> + </div> + </AuiIf> + + <div className="flex items-end gap-1"> + <ComposerPrimitive.AddAttachment asChild> + <TooltipIconButton + type="button" + tooltip="Add photos & files" + side="top" + aria-label="Add attachment" + className="flex size-9 shrink-0 items-center justify-center rounded-full text-[#5d5d5d] transition-colors hover:bg-black/[0.07] hover:text-[#5d5d5d] dark:text-[#cdcdcd] dark:hover:bg-white/15 dark:hover:text-[#cdcdcd]" + > + <PlusIcon size={20} /> + </TooltipIconButton> + </ComposerPrimitive.AddAttachment> + + <ComposerPrimitive.Input + autoFocus + placeholder={placeholder} + rows={1} + className="max-h-52 min-h-9 flex-1 resize-none bg-transparent py-1.5 pr-2 pl-1 text-base text-[#0d0d0d] outline-none placeholder:text-[#8e8e8e] dark:text-[#ececec] dark:placeholder:text-[#8e8e8e]" + /> + + <div className="flex shrink-0 items-center gap-1"> + <ComposerPrimaryAction /> + </div> + </div> + </ComposerPrimitive.Root> + ); +}; + +const ComposerPrimaryAction: FC = () => { + return ( + <div className="flex items-center gap-1"> + <AuiIf condition={(s) => s.thread.isRunning}> + <ComposerPrimitive.Cancel className="flex size-9 items-center justify-center rounded-full bg-[#0d0d0d] text-white dark:bg-white dark:text-black"> + <div className="size-2.5 rounded-[2px] bg-current" /> + </ComposerPrimitive.Cancel> + </AuiIf> + + <AuiIf + condition={(s) => !s.thread.isRunning && s.composer.dictation != null} + > + <ComposerPrimitive.StopDictation + className="flex size-9 items-center justify-center rounded-full bg-[#0d0d0d] text-white dark:bg-white dark:text-black" + aria-label="Stop dictation" + > + <div className="size-2.5 animate-pulse rounded-[2px] bg-current" /> + </ComposerPrimitive.StopDictation> + </AuiIf> + + <AuiIf + condition={(s) => + !s.thread.isRunning && + s.composer.dictation == null && + !s.composer.isEmpty + } + > + <ComposerPrimitive.Send className="flex size-9 items-center justify-center rounded-full bg-[#0d0d0d] text-white transition-opacity disabled:opacity-30 dark:bg-white dark:text-black"> + <ArrowUpIcon className="size-6" /> + </ComposerPrimitive.Send> + </AuiIf> + + <AuiIf + condition={(s) => + !s.thread.isRunning && + s.composer.dictation == null && + s.composer.isEmpty + } + > + <ComposerPrimitive.Dictate asChild> + <TooltipIconButton + tooltip="Dictate" + side="top" + aria-label="Dictate" + className="flex size-9 items-center justify-center rounded-full text-[#5d5d5d] transition-colors hover:bg-black/[0.07] hover:text-[#5d5d5d] dark:text-[#cdcdcd] dark:hover:bg-white/15 dark:hover:text-[#cdcdcd]" + > + <Mic className="size-5" /> + </TooltipIconButton> + </ComposerPrimitive.Dictate> + + <TooltipIconButton + type="button" + tooltip="Use voice mode" + side="top" + aria-hidden="true" + tabIndex={-1} + className="flex size-9 items-center justify-center rounded-full bg-[#0d0d0d] text-white hover:bg-[#0d0d0d] dark:bg-white dark:text-black dark:hover:bg-white" + > + <AudioLines className="size-5" /> + </TooltipIconButton> + </AuiIf> + </div> + ); +}; + +const ThreadScrollToBottom: FC = () => { + return ( + <ThreadPrimitive.ScrollToBottom asChild> + <TooltipIconButton + tooltip="Scroll to bottom" + className="bg-background absolute -top-10 z-10 self-center rounded-full border p-2 shadow-sm disabled:invisible dark:border-white/15 dark:bg-[#2a2a2a]" + > + <ChevronDownIcon className="size-5" /> + </TooltipIconButton> + </ThreadPrimitive.ScrollToBottom> + ); +}; + +const UserMessage: FC = () => { + return ( + <MessagePrimitive.Root className="relative mx-auto flex w-full max-w-3xl flex-col items-end gap-1"> + <div className="flex flex-row flex-wrap justify-end gap-2"> + <MessagePrimitive.Attachments + components={{ Attachment: ChatGPTAttachmentUI }} + /> + </div> + + <div className="max-w-[70%] rounded-[22px] bg-[#0d0d0d] px-4 py-2.5 leading-6 text-white dark:bg-[#ececec] dark:text-[#0d0d0d]"> + <MessagePrimitive.Parts /> + </div> + + <div className="flex items-center gap-0.5"> + <ActionBarPrimitive.Root + hideWhenRunning + autohide="always" + autohideFloat="single-branch" + className="flex items-center" + > + <ActionBarPrimitive.Copy asChild> + <TooltipIconButton + tooltip="Copy" + side="top" + className={assistantActionClassName} + > + <AuiIf condition={(s) => s.message.isCopied}> + <CheckIcon className="size-5" /> + </AuiIf> + <AuiIf condition={(s) => !s.message.isCopied}> + <CopyIcon className="size-5" /> + </AuiIf> + </TooltipIconButton> + </ActionBarPrimitive.Copy> + <ActionBarPrimitive.Edit asChild> + <TooltipIconButton + tooltip="Edit" + side="top" + className={assistantActionClassName} + > + <PencilIcon className="size-5" /> + </TooltipIconButton> + </ActionBarPrimitive.Edit> + </ActionBarPrimitive.Root> + + <BranchPicker /> + </div> + </MessagePrimitive.Root> + ); +}; + +const EditComposer: FC = () => { + return ( + <ComposerPrimitive.Root className="mx-auto flex w-full max-w-3xl flex-col justify-end gap-1 rounded-3xl bg-[#e9e9e9]/50 dark:bg-[#323232]"> + <ComposerPrimitive.Input className="text-foreground flex h-8 w-full resize-none bg-transparent p-5 pb-0 outline-none dark:text-white" /> + + <div className="m-3 mt-2 flex items-center justify-center gap-2 self-end"> + <ComposerPrimitive.Cancel className="bg-background text-foreground hover:bg-muted rounded-full px-3 py-2 text-sm font-semibold dark:bg-zinc-900 dark:text-white dark:hover:bg-zinc-800"> + Cancel + </ComposerPrimitive.Cancel> + <ComposerPrimitive.Send className="bg-primary text-primary-foreground hover:bg-primary/90 rounded-full px-3 py-2 text-sm font-semibold dark:bg-white dark:text-black dark:hover:bg-white/90"> + Send + </ComposerPrimitive.Send> + </div> + </ComposerPrimitive.Root> + ); +}; + +const assistantActionClassName = + "flex size-8 items-center justify-center rounded-lg text-[#5d5d5d] transition-colors hover:bg-black/[0.07] hover:text-[#5d5d5d] dark:text-[#cdcdcd] dark:hover:bg-white/15 dark:hover:text-[#cdcdcd]"; + +const AssistantMessage: FC = () => { + return ( + <MessagePrimitive.Root className="relative mx-auto flex w-full max-w-3xl flex-col"> + <div className="text-[#0d0d0d] dark:text-[#ececec]"> + <MessagePrimitive.Parts> + {({ part }) => { + if (part.type === "text") return <MarkdownText />; + if (part.type === "tool-call") + return part.toolUI ?? <ToolFallback {...part} />; + return null; + }} + </MessagePrimitive.Parts> + </div> + + <div className="-ml-2 flex items-center pt-1"> + <ActionBarPrimitive.Root hideWhenRunning className="flex items-center"> + <ActionBarPrimitive.Copy asChild> + <TooltipIconButton + tooltip="Copy" + side="top" + className={assistantActionClassName} + > + <AuiIf condition={(s) => s.message.isCopied}> + <CheckIcon className="size-5" /> + </AuiIf> + <AuiIf condition={(s) => !s.message.isCopied}> + <CopyIcon className="size-5" /> + </AuiIf> + </TooltipIconButton> + </ActionBarPrimitive.Copy> + <ActionBarPrimitive.FeedbackPositive asChild> + <TooltipIconButton + tooltip="Good response" + side="top" + className={assistantActionClassName} + > + <ThumbsUp className="size-5" /> + </TooltipIconButton> + </ActionBarPrimitive.FeedbackPositive> + <ActionBarPrimitive.FeedbackNegative asChild> + <TooltipIconButton + tooltip="Bad response" + side="top" + className={assistantActionClassName} + > + <ThumbsDown className="size-5" /> + </TooltipIconButton> + </ActionBarPrimitive.FeedbackNegative> + <ActionBarPrimitive.Speak asChild> + <TooltipIconButton + tooltip="Read aloud" + side="top" + className={assistantActionClassName} + > + <Volume2 className="size-5" /> + </TooltipIconButton> + </ActionBarPrimitive.Speak> + <TooltipIconButton + tooltip="Share" + side="top" + className={assistantActionClassName} + > + <Share className="size-5" /> + </TooltipIconButton> + <ActionBarPrimitive.Reload asChild> + <TooltipIconButton + tooltip="Regenerate" + side="top" + className={assistantActionClassName} + > + <RefreshCwIcon className="size-5" /> + </TooltipIconButton> + </ActionBarPrimitive.Reload> + <ActionBarMorePrimitive.Root> + <ActionBarMorePrimitive.Trigger asChild> + <button + type="button" + aria-label="More" + className={cn( + assistantActionClassName, + "data-[state=open]:bg-black/[0.07] dark:data-[state=open]:bg-white/15", + )} + > + <MoreHorizontal className="size-5" /> + </button> + </ActionBarMorePrimitive.Trigger> + <ActionBarMorePrimitive.Content + side="bottom" + align="end" + sideOffset={6} + className="bg-popover/95 text-popover-foreground data-[state=open]:fade-in-0 data-[state=open]:zoom-in-95 data-[state=open]:animate-in data-[state=closed]:fade-out-0 data-[state=closed]:zoom-out-95 data-[state=closed]:animate-out data-[side=bottom]:slide-in-from-top-2 z-50 min-w-40 overflow-hidden rounded-xl border p-1.5 shadow-lg backdrop-blur-sm" + > + <ActionBarPrimitive.ExportMarkdown asChild> + <ActionBarMorePrimitive.Item className="text-muted-foreground focus:bg-accent focus:text-accent-foreground flex cursor-pointer items-center gap-2.5 rounded-lg px-3 py-2 text-sm outline-none select-none"> + <Download className="size-5" /> + Export as Markdown + </ActionBarMorePrimitive.Item> + </ActionBarPrimitive.ExportMarkdown> + </ActionBarMorePrimitive.Content> + </ActionBarMorePrimitive.Root> + </ActionBarPrimitive.Root> + <BranchPicker className="ml-1" /> + </div> + </MessagePrimitive.Root> + ); +}; + +const BranchPicker: FC<{ className?: string }> = ({ className }) => { + return ( + <BranchPickerPrimitive.Root + hideWhenSingleBranch + className={cn( + "text-muted-foreground inline-flex items-center text-sm font-semibold dark:text-[#b4b4b4]", + className, + )} + > + <BranchPickerPrimitive.Previous asChild> + <TooltipIconButton tooltip="Previous" className="text-[#b4b4b4]"> + <ChevronLeftIcon className="size-5" /> + </TooltipIconButton> + </BranchPickerPrimitive.Previous> + <BranchPickerPrimitive.Number />/<BranchPickerPrimitive.Count /> + <BranchPickerPrimitive.Next asChild> + <TooltipIconButton tooltip="Next" className="text-[#b4b4b4]"> + <ChevronRightIcon className="size-5" /> + </TooltipIconButton> + </BranchPickerPrimitive.Next> + </BranchPickerPrimitive.Root> + ); +}; + +const useFileSrc = (file: File | undefined) => { + const [src, setSrc] = useState<string | undefined>(undefined); + + useEffect(() => { + if (!file) { + setSrc(undefined); + return; + } + + const objectUrl = URL.createObjectURL(file); + setSrc(objectUrl); + + return () => { + URL.revokeObjectURL(objectUrl); + }; + }, [file]); + + return src; +}; + +const useAttachmentSrc = () => { + const { file, src } = useAuiState( + useShallow((s): { file?: File; src?: string } => { + if (s.attachment.type !== "image") return {}; + if (s.attachment.file) return { file: s.attachment.file }; + const src = s.attachment.content?.filter((c) => c.type === "image")[0] + ?.image; + if (!src) return {}; + return { src }; + }), + ); + + return useFileSrc(file) ?? src; +}; + +const ChatGPTAttachmentUI: FC = () => { + const aui = useAui(); + const isComposer = aui.attachment.source !== "message"; + const src = useAttachmentSrc(); + + return ( + <AttachmentPrimitive.Root className="group/attachment relative"> + <div className="bg-secondary flex items-center gap-2 overflow-hidden rounded-2xl border dark:bg-white/5"> + <AuiIf condition={(s) => s.attachment.type === "image"}> + {src ? ( + <img + className="size-32 rounded-md object-cover" + alt="Attachment" + src={src} + /> + ) : ( + <div className="flex h-full w-12 items-center justify-center rounded-md"> + <AttachmentPrimitive.unstable_Thumb className="text-xs" /> + </div> + )} + </AuiIf> + <AuiIf condition={(s) => s.attachment.type !== "image"}> + <div className="bg-background flex h-full w-12 items-center justify-center rounded-[9px] text-[#6b6b6b] dark:bg-[#3a3a3a] dark:text-[#9a9a9a]"> + <AttachmentPrimitive.unstable_Thumb className="text-xs" /> + </div> + </AuiIf> + </div> + {isComposer && ( + <AttachmentPrimitive.Remove className="absolute -top-1.5 -right-1.5 flex size-7 items-center justify-center rounded-full border border-[#e5e5e5] bg-white text-[#6b6b6b] transition-all hover:bg-[#f5f5f5] hover:text-[#0d0d0d] dark:border-[#3a3a3a] dark:bg-[#1a1a1a] dark:text-[#9a9a9a] dark:hover:bg-[#252525] dark:hover:text-white"> + <XIcon className="size-5" /> + </AttachmentPrimitive.Remove> + )} + </AttachmentPrimitive.Root> + ); +}; diff --git a/apps/docs/components/examples/claude.tsx b/apps/docs/components/examples/claude.tsx new file mode 100644 index 0000000..4f731e4 --- /dev/null +++ b/apps/docs/components/examples/claude.tsx @@ -0,0 +1,387 @@ +"use client"; + +import { + ActionBarPrimitive, + AuiIf, + AttachmentPrimitive, + ComposerPrimitive, + MessagePrimitive, + ThreadPrimitive, + useAuiState, +} from "@assistant-ui/react"; +import { + ArrowUpIcon, + AudioLines, + Calendar as CalendarIcon, + CheckIcon, + ChevronDownIcon, + ClipboardIcon, + Code as CodeIcon, + FolderOpen, + GraduationCap, + PencilIcon, + PenLine, + PlusIcon, + RefreshCwIcon, + Sparkle, + ThumbsDown, + ThumbsUp, + XIcon, +} from "lucide-react"; +import { useEffect, useState, type FC } from "react"; +import { useShallow } from "zustand/shallow"; +import { MarkdownText } from "@/components/assistant-ui/markdown-text"; +import { + DropdownMenu, + DropdownMenuContent, + DropdownMenuItem, + DropdownMenuSeparator, + DropdownMenuTrigger, +} from "@/components/ui/dropdown-menu"; + +const messageActionButtonClassName = + "flex size-8 items-center justify-center rounded-md text-[#5b5950] transition-colors hover:bg-[#1a1a18]/5 hover:text-[#1a1a18] dark:text-[#a3a098] dark:hover:bg-white/5 dark:hover:text-[#eee]"; + +export const Claude: FC = () => { + return ( + <ThreadPrimitive.Root className="flex h-full flex-col items-stretch bg-[#F0ECE0] font-serif text-[#1a1a18] dark:bg-[#2b2a27] dark:text-[#eee]"> + <AuiIf condition={(s) => s.thread.isEmpty}> + <EmptyState /> + </AuiIf> + + <AuiIf condition={(s) => !s.thread.isEmpty}> + <ThreadPrimitive.Viewport className="flex grow flex-col overflow-y-auto px-4 pt-12"> + <ThreadPrimitive.Messages> + {() => <ChatMessage />} + </ThreadPrimitive.Messages> + + <ThreadPrimitive.ViewportFooter className="sticky bottom-0 mx-auto mt-auto w-full max-w-3xl bg-linear-to-b from-transparent via-[#F0ECE0]/85 to-[#F0ECE0] pt-4 pb-2 dark:via-[#2b2a27]/85 dark:to-[#2b2a27]"> + <Composer /> + <p className="pt-2 text-center text-xs text-[#8a8780] dark:text-[#a3a098]"> + Claude can make mistakes. Please double-check responses. + </p> + </ThreadPrimitive.ViewportFooter> + </ThreadPrimitive.Viewport> + </AuiIf> + </ThreadPrimitive.Root> + ); +}; + +const EmptyState: FC = () => { + return ( + <div className="flex grow flex-col items-center justify-center px-4"> + <div className="mx-auto flex w-full max-w-2xl flex-col items-stretch gap-5"> + <h1 className="flex items-center justify-center gap-3 font-serif text-3xl text-[#1a1a18] sm:text-4xl dark:text-[#eee]"> + <Sparkle className="size-7 fill-[#c96442] text-[#c96442]" /> + <span>How can I help you today?</span> + </h1> + <Composer /> + <ModeTabs /> + </div> + </div> + ); +}; + +const Composer: FC = () => { + return ( + <ComposerPrimitive.Root className="flex w-full flex-col gap-2 rounded-2xl border border-[#E5E0D6] bg-white px-3.5 pt-3 pb-2.5 dark:border-[#3d3a35] dark:bg-[#1f1e1b]"> + <ComposerPrimitive.Input + placeholder="How can I help you today?" + rows={1} + className="block max-h-72 min-h-6 w-full resize-none bg-transparent text-[#1a1a18] outline-none placeholder:text-[#9a9893] dark:text-[#eee] dark:placeholder:text-[#9a9893]" + /> + + <div className="flex w-full items-center gap-2"> + <ComposerPrimitive.AddAttachment + aria-label="Add attachment" + className="flex size-8 shrink-0 items-center justify-center rounded-md text-[#5b5950] transition-colors hover:bg-[#1a1a18]/5 hover:text-[#1a1a18] dark:text-[#a3a098] dark:hover:bg-white/5 dark:hover:text-[#eee]" + > + <PlusIcon width={16} height={16} /> + </ComposerPrimitive.AddAttachment> + + <div className="ml-auto flex items-center gap-1"> + <ClaudeModelPicker /> + <ComposerPrimaryAction /> + </div> + </div> + + <AuiIf condition={(s) => s.composer.attachments.length > 0}> + <div className="-mx-1 -mb-1 flex flex-row gap-2 overflow-x-auto pt-1"> + <ComposerPrimitive.Attachments> + {() => <ClaudeAttachment />} + </ComposerPrimitive.Attachments> + </div> + </AuiIf> + </ComposerPrimitive.Root> + ); +}; + +const ComposerPrimaryAction: FC = () => { + return ( + <> + <AuiIf condition={(s) => s.thread.isRunning}> + <ComposerPrimitive.Cancel className="flex size-8 items-center justify-center rounded-md bg-[#c96442] text-white transition-colors hover:bg-[#b1573a]"> + <div className="size-2.5 rounded-[2px] bg-current" /> + </ComposerPrimitive.Cancel> + </AuiIf> + + <AuiIf + condition={(s) => !s.thread.isRunning && s.composer.dictation != null} + > + <ComposerPrimitive.StopDictation + className="flex size-8 items-center justify-center rounded-md bg-[#c96442] text-white transition-colors hover:bg-[#b1573a]" + aria-label="Stop dictation" + > + <div className="size-2.5 animate-pulse rounded-[2px] bg-current" /> + </ComposerPrimitive.StopDictation> + </AuiIf> + + <AuiIf + condition={(s) => + !s.thread.isRunning && + s.composer.dictation == null && + !s.composer.isEmpty + } + > + <ComposerPrimitive.Send className="flex size-8 items-center justify-center rounded-md bg-[#c96442] text-white transition-colors hover:bg-[#b1573a] disabled:pointer-events-none disabled:opacity-50"> + <ArrowUpIcon width={16} height={16} /> + </ComposerPrimitive.Send> + </AuiIf> + + <AuiIf + condition={(s) => + !s.thread.isRunning && + s.composer.dictation == null && + s.composer.isEmpty + } + > + <ComposerPrimitive.Dictate + className="flex size-8 items-center justify-center rounded-md text-[#5b5950] transition-colors hover:bg-[#1a1a18]/5 hover:text-[#1a1a18] dark:text-[#a3a098] dark:hover:bg-white/5 dark:hover:text-[#eee]" + aria-label="Use voice mode" + > + <AudioLines className="size-4" /> + </ComposerPrimitive.Dictate> + </AuiIf> + </> + ); +}; + +const CLAUDE_MODELS = [ + { + id: "sonnet-4.5", + name: "Sonnet 4.5", + description: "Smart, fast, everyday tasks", + }, + { + id: "opus-4.7", + name: "Opus 4.7", + description: "Anthropic's most capable model", + }, + { + id: "haiku-4.5", + name: "Haiku 4.5", + description: "Fastest, most affordable", + }, +]; + +const ClaudeModelPicker: FC = () => { + const [model, setModel] = useState(CLAUDE_MODELS[0]!.id); + + return ( + <DropdownMenu> + <DropdownMenuTrigger className="flex h-8 items-center gap-1 rounded-md px-2.5 text-sm whitespace-nowrap text-[#1a1a18] transition hover:bg-[#1a1a18]/5 dark:text-[#eee] dark:hover:bg-white/5"> + <span className="font-serif"> + {CLAUDE_MODELS.find((m) => m.id === model)?.name} + </span> + <ChevronDownIcon width={16} height={16} className="opacity-60" /> + </DropdownMenuTrigger> + <DropdownMenuContent align="end" className="min-w-64"> + {CLAUDE_MODELS.map((m) => ( + <DropdownMenuItem + key={m.id} + onClick={() => setModel(m.id)} + className="flex items-start gap-3" + > + <span className="mt-0.5 flex size-4 items-center justify-center text-[#c96442]"> + {m.id === model ? <CheckIcon /> : null} + </span> + <span className="flex flex-1 flex-col"> + <span className="text-foreground font-serif text-sm"> + {m.name} + </span> + <span className="text-muted-foreground text-xs"> + {m.description} + </span> + </span> + </DropdownMenuItem> + ))} + <DropdownMenuSeparator /> + <DropdownMenuItem className="text-muted-foreground text-sm"> + More options + </DropdownMenuItem> + </DropdownMenuContent> + </DropdownMenu> + ); +}; + +const ModeTabs: FC = () => { + const tabs = [ + { label: "Write", Icon: PenLine }, + { label: "Learn", Icon: GraduationCap }, + { label: "Code", Icon: CodeIcon }, + { label: "From Drive", Icon: FolderOpen }, + { label: "From Calendar", Icon: CalendarIcon }, + ]; + + return ( + <div className="flex flex-wrap items-center justify-center gap-2"> + {tabs.map(({ label, Icon }) => ( + <button + key={label} + type="button" + aria-hidden="true" + tabIndex={-1} + className="flex h-8 items-center gap-1.5 rounded-lg border border-[#E5E0D6] bg-transparent px-3 text-sm whitespace-nowrap text-[#3d3a35] transition-colors hover:bg-white/60 dark:border-[#3d3a35] dark:text-[#cdc9be] dark:hover:bg-[#1f1e1b]/60" + > + <Icon className="size-3.5 text-[#8a8780] dark:text-[#a3a098]" /> + <span className="font-serif">{label}</span> + </button> + ))} + </div> + ); +}; + +const ChatMessage: FC = () => { + return ( + <MessagePrimitive.Root className="group/message relative mx-auto flex w-full max-w-3xl flex-col py-2"> + <AuiIf condition={(s) => s.message.role === "user"}> + <div className="flex flex-col items-end gap-1"> + <div className="max-w-[80%] rounded-2xl bg-[#E5E0D6] px-4 py-2.5 wrap-break-word whitespace-pre-wrap text-[#1a1a18] dark:bg-[#393937] dark:text-[#eee]"> + <MessagePrimitive.Parts> + {({ part }) => { + if (part.type === "text") return <MarkdownText />; + return null; + }} + </MessagePrimitive.Parts> + </div> + <ActionBarPrimitive.Root className="-mt-px flex items-center gap-0.5 opacity-0 transition-opacity group-focus-within/message:opacity-100 group-hover/message:opacity-100"> + <ActionBarPrimitive.Edit className={messageActionButtonClassName}> + <PencilIcon width={16} height={16} /> + </ActionBarPrimitive.Edit> + <ActionBarPrimitive.Copy className={messageActionButtonClassName}> + <AuiIf condition={(s) => s.message.isCopied}> + <CheckIcon /> + </AuiIf> + <AuiIf condition={(s) => !s.message.isCopied}> + <ClipboardIcon width={16} height={16} /> + </AuiIf> + </ActionBarPrimitive.Copy> + </ActionBarPrimitive.Root> + </div> + </AuiIf> + + <AuiIf condition={(s) => s.message.role === "assistant"}> + <div className="flex flex-col"> + <div className="prose prose-claude font-serif leading-[1.65rem] wrap-break-word text-[#1a1a18] dark:text-[#eee]"> + <MessagePrimitive.Parts> + {({ part }) => { + if (part.type === "text") return <MarkdownText />; + return null; + }} + </MessagePrimitive.Parts> + </div> + <ActionBarPrimitive.Root className="mt-2 flex items-center gap-0.5 opacity-0 transition-opacity group-focus-within/message:opacity-100 group-hover/message:opacity-100"> + <ActionBarPrimitive.Copy className={messageActionButtonClassName}> + <AuiIf condition={(s) => s.message.isCopied}> + <CheckIcon /> + </AuiIf> + <AuiIf condition={(s) => !s.message.isCopied}> + <ClipboardIcon width={16} height={16} /> + </AuiIf> + </ActionBarPrimitive.Copy> + <ActionBarPrimitive.FeedbackPositive + className={messageActionButtonClassName} + > + <ThumbsUp className="size-4" /> + </ActionBarPrimitive.FeedbackPositive> + <ActionBarPrimitive.FeedbackNegative + className={messageActionButtonClassName} + > + <ThumbsDown className="size-4" /> + </ActionBarPrimitive.FeedbackNegative> + <ActionBarPrimitive.Reload className={messageActionButtonClassName}> + <RefreshCwIcon width={16} height={16} /> + </ActionBarPrimitive.Reload> + </ActionBarPrimitive.Root> + </div> + </AuiIf> + </MessagePrimitive.Root> + ); +}; + +const useFileSrc = (file: File | undefined) => { + const [src, setSrc] = useState<string | undefined>(undefined); + + useEffect(() => { + if (!file) { + setSrc(undefined); + return; + } + + const objectUrl = URL.createObjectURL(file); + setSrc(objectUrl); + + return () => { + URL.revokeObjectURL(objectUrl); + }; + }, [file]); + + return src; +}; + +const useAttachmentSrc = () => { + const { file, src } = useAuiState( + useShallow((s): { file?: File; src?: string } => { + if (s.attachment.type !== "image") return {}; + if (s.attachment.file) return { file: s.attachment.file }; + const src = s.attachment.content?.filter((c) => c.type === "image")[0] + ?.image; + if (!src) return {}; + return { src }; + }), + ); + + return useFileSrc(file) ?? src; +}; + +const ClaudeAttachment: FC = () => { + const isImage = useAuiState((s) => s.attachment.type === "image"); + const src = useAttachmentSrc(); + + return ( + <AttachmentPrimitive.Root className="group/thumbnail relative"> + <div + className="overflow-hidden rounded-lg border border-[#E5E0D6] dark:border-[#3d3a35]" + style={{ width: "80px", height: "80px" }} + > + {isImage && src ? ( + <img + className="h-full w-full object-cover" + alt="Attachment" + src={src} + /> + ) : ( + <div className="flex h-full w-full items-center justify-center bg-white text-[#5b5950] dark:bg-[#2b2a27] dark:text-[#a3a098]"> + <AttachmentPrimitive.unstable_Thumb className="text-xs" /> + </div> + )} + </div> + <AttachmentPrimitive.Remove + className="absolute -top-1.5 -right-1.5 flex size-5 items-center justify-center rounded-full bg-[#1a1a18] text-white opacity-0 transition-opacity group-focus-within/thumbnail:opacity-100 group-hover/thumbnail:opacity-100 hover:bg-[#3d3a35] dark:bg-white dark:text-[#1a1a18] dark:hover:bg-[#cdc9be]" + aria-label="Remove attachment" + > + <XIcon width={12} height={12} /> + </AttachmentPrimitive.Remove> + </AttachmentPrimitive.Root> + ); +}; diff --git a/apps/docs/components/examples/gemini.tsx b/apps/docs/components/examples/gemini.tsx new file mode 100644 index 0000000..3cacc96 --- /dev/null +++ b/apps/docs/components/examples/gemini.tsx @@ -0,0 +1,332 @@ +"use client"; + +import { + ActionBarPrimitive, + AttachmentPrimitive, + AuiIf, + ComposerPrimitive, + MessagePrimitive, + ThreadPrimitive, + useAuiState, +} from "@assistant-ui/react"; +import { + ArrowUpIcon, + CheckIcon, + ChevronDownIcon, + CopyIcon, + EllipsisVertical, + ImageIcon, + Lightbulb, + Mic, + Paperclip, + PencilIcon, + PencilRuler, + PlusIcon, + RefreshCwIcon, + Telescope, + ThumbsDown, + ThumbsUp, + XIcon, +} from "lucide-react"; +import { type FC, useEffect, useState } from "react"; +import { useShallow } from "zustand/shallow"; +import { MarkdownText } from "@/components/assistant-ui/markdown-text"; +import { + DropdownMenu, + DropdownMenuContent, + DropdownMenuItem, + DropdownMenuSeparator, + DropdownMenuTrigger, +} from "@/components/ui/dropdown-menu"; + +export const Gemini: FC = () => { + return ( + <ThreadPrimitive.Root className="flex h-full flex-col overflow-hidden bg-[#fdfcfc] text-[#1f1f1f] dark:bg-[#0c0c0c] dark:text-[#e3e3e3]"> + <AuiIf condition={(s) => s.thread.messages.length === 0}> + <div className="relative flex grow flex-col"> + <div className="flex grow flex-col items-center justify-center px-4"> + <div className="flex w-full max-w-3xl flex-col"> + <h1 className="fade-in slide-in-from-bottom-3 motion-safe:animate-in fill-mode-both relative z-10 mb-6 text-center text-4xl font-normal text-[#1f1f1f] delay-500 duration-400 ease-[cubic-bezier(0.22,1,0.36,1)] dark:text-white"> + How can I help you today? + </h1> + <div className="relative"> + <div + aria-hidden="true" + className="fade-in zoom-in-40 blur-in-[90px] motion-safe:animate-in fill-mode-both pointer-events-none absolute top-1/2 left-1/2 h-[260px] w-[680px] max-w-[92%] -translate-x-1/2 -translate-y-1/2 rounded-[140px] bg-[#a9d1fb]/60 blur-[90px] duration-1000 ease-[cubic-bezier(0.22,1,0.36,1)] dark:bg-[#1b2f9c]/50" + /> + <div className="relative z-10"> + <Composer /> + </div> + </div> + </div> + </div> + </div> + </AuiIf> + + <AuiIf condition={(s) => s.thread.messages.length > 0}> + <ThreadPrimitive.Viewport className="flex grow flex-col overflow-y-scroll pt-12"> + <ThreadPrimitive.Messages components={{ Message: ChatMessage }} /> + <ThreadPrimitive.ViewportFooter className="sticky bottom-0 mt-auto flex w-full flex-col items-center gap-1.5 bg-[#fdfcfc] px-4 pb-3 dark:bg-[#0c0c0c]"> + <Composer /> + <p className="text-center text-xs text-[#5e6063] dark:text-[#9aa0a6]"> + Gemini can make mistakes, so double-check it. + </p> + </ThreadPrimitive.ViewportFooter> + </ThreadPrimitive.Viewport> + </AuiIf> + </ThreadPrimitive.Root> + ); +}; + +const ghostBtnClass = + "flex shrink-0 items-center justify-center rounded-full text-[#444746] transition-colors hover:bg-[#444746]/8 hover:text-[#1f1f1f] dark:text-[#c4c7c5] dark:hover:bg-[#c4c7c5]/10 dark:hover:text-[#e3e3e3]"; + +const Composer: FC = () => { + return ( + <ComposerPrimitive.Root className="mx-auto flex w-full max-w-2xl flex-col rounded-4xl bg-white p-3 shadow-[0_2px_10px_-2px_rgba(0,0,0,0.18)] dark:bg-[#1e1f20] dark:shadow-[0_2px_12px_-2px_rgba(0,0,0,0.6)]"> + <AuiIf condition={(s) => s.composer.attachments.length > 0}> + <div className="flex flex-row gap-2.5 overflow-x-auto px-1 pt-1 pb-2.5"> + <ComposerPrimitive.Attachments + components={{ Attachment: GeminiAttachment }} + /> + </div> + </AuiIf> + + <div className="flex items-end gap-1"> + <GeminiPlusMenu /> + <ComposerPrimitive.Input + rows={1} + placeholder="Ask Gemini" + className="max-h-40 flex-1 resize-none bg-transparent px-2 py-1.5 text-[17px] leading-6 text-[#1f1f1f] outline-none placeholder:text-[#575b5f] dark:text-[#e3e3e3] dark:placeholder:text-[#9aa0a6]" + /> + <GeminiModelPicker /> + <button + type="button" + aria-label="Voice mode" + className={`${ghostBtnClass} size-9`} + > + <Mic width={20} height={20} /> + </button> + <GeminiSendButton /> + </div> + </ComposerPrimitive.Root> + ); +}; + +const GEMINI_TOOLS = [ + { id: "research", label: "Deep Research", Icon: Telescope }, + { id: "canvas", label: "Canvas", Icon: PencilRuler }, + { id: "image", label: "Create image", Icon: ImageIcon }, + { id: "learn", label: "Guided Learning", Icon: Lightbulb }, +]; + +const GeminiPlusMenu: FC = () => { + return ( + <DropdownMenu> + <DropdownMenuTrigger + aria-label="Add files and tools" + className={`${ghostBtnClass} size-9`} + > + <PlusIcon width={20} height={20} /> + </DropdownMenuTrigger> + <DropdownMenuContent align="start" side="bottom" className="min-w-56"> + <DropdownMenuItem render={<ComposerPrimitive.AddAttachment />}> + <span className="flex size-4 items-center justify-center"> + <Paperclip className="size-4" /> + </span> + Add photos & files + </DropdownMenuItem> + <DropdownMenuSeparator /> + {GEMINI_TOOLS.map(({ id, label, Icon }) => ( + <DropdownMenuItem key={id}> + <Icon className="size-4" /> + {label} + </DropdownMenuItem> + ))} + </DropdownMenuContent> + </DropdownMenu> + ); +}; + +const GEMINI_MODELS = [ + { id: "flash", name: "Flash", description: "Fast all-around help" }, + { + id: "thinking", + name: "Thinking", + description: "Reasoning, math & code", + }, +]; + +const GeminiModelPicker: FC = () => { + const [model, setModel] = useState(GEMINI_MODELS[0]!.id); + const current = GEMINI_MODELS.find((m) => m.id === model); + + return ( + <DropdownMenu> + <DropdownMenuTrigger className="h-9 shrink-0 gap-0.5 rounded-full pr-1.5 pl-3 text-sm whitespace-nowrap text-[#444746] transition-colors hover:bg-[#444746]/8 hover:text-[#1f1f1f] dark:text-[#c4c7c5] dark:hover:bg-[#c4c7c5]/10 dark:hover:text-[#e3e3e3]"> + <span>{current?.name}</span> + <ChevronDownIcon width={16} height={16} className="opacity-70" /> + </DropdownMenuTrigger> + <DropdownMenuContent align="end" side="bottom" className="min-w-64"> + {GEMINI_MODELS.map((m) => ( + <DropdownMenuItem + key={m.id} + onClick={() => setModel(m.id)} + className="items-start gap-3" + > + <span className="mt-0.5 flex size-4 items-center justify-center text-[#0b57d0] dark:text-[#a8c7fa]"> + {m.id === model ? <CheckIcon /> : null} + </span> + <span className="flex flex-col"> + <span className="text-foreground text-sm">{m.name}</span> + <span className="text-muted-foreground text-xs"> + {m.description} + </span> + </span> + </DropdownMenuItem> + ))} + </DropdownMenuContent> + </DropdownMenu> + ); +}; + +const sendBtnClass = + "flex size-9 shrink-0 items-center justify-center rounded-full bg-[#1f3b9b] text-white transition-colors hover:bg-[#274aad]"; + +const GeminiSendButton: FC = () => { + return ( + <> + <AuiIf condition={(s) => !s.thread.isRunning && !s.composer.isEmpty}> + <ComposerPrimitive.Send + aria-label="Send message" + className={`${sendBtnClass} disabled:bg-[#e8eaed] disabled:text-[#1f1f1f]/40 dark:disabled:bg-[#2b2c2e] dark:disabled:text-white/30`} + > + <ArrowUpIcon width={20} height={20} /> + </ComposerPrimitive.Send> + </AuiIf> + <AuiIf condition={(s) => s.thread.isRunning}> + <ComposerPrimitive.Cancel + aria-label="Stop generating" + className={sendBtnClass} + > + <span className="size-3 rounded-[3px] bg-current" /> + </ComposerPrimitive.Cancel> + </AuiIf> + </> + ); +}; + +const actionBtnClass = + "flex size-8 items-center justify-center rounded-full text-[#444746] transition-colors hover:bg-[#444746]/8 hover:text-[#1f1f1f] dark:text-[#c4c7c5] dark:hover:bg-[#c4c7c5]/10 dark:hover:text-[#e3e3e3]"; + +const ChatMessage: FC = () => { + return ( + <MessagePrimitive.Root className="group/message mx-auto mb-7 flex w-full max-w-3xl flex-col px-4"> + <AuiIf condition={(s) => s.message.role === "user"}> + <div className="flex items-center justify-end gap-1"> + <ActionBarPrimitive.Root className="flex items-center gap-0.5 opacity-0 transition-opacity group-focus-within/message:opacity-100 group-hover/message:opacity-100"> + <ActionBarPrimitive.Copy className={actionBtnClass}> + <CopyIcon width={16} height={16} /> + </ActionBarPrimitive.Copy> + <ActionBarPrimitive.Edit className={actionBtnClass}> + <PencilIcon width={16} height={16} /> + </ActionBarPrimitive.Edit> + </ActionBarPrimitive.Root> + <div className="max-w-[75%] rounded-3xl bg-[#f2f0f0] px-5 py-3 wrap-break-word text-[#1f1f1f] dark:bg-[#333537] dark:text-[#e3e3e3]"> + <MessagePrimitive.Parts components={{ Text: MarkdownText }} /> + </div> + </div> + </AuiIf> + + <AuiIf condition={(s) => s.message.role === "assistant"}> + <div className="flex flex-col"> + <div className="wrap-break-word text-[#1f1f1f] dark:text-[#e3e3e3]"> + <MessagePrimitive.Parts components={{ Text: MarkdownText }} /> + </div> + <ActionBarPrimitive.Root className="mt-1.5 -ml-2 flex items-center gap-0.5 opacity-0 transition-opacity group-focus-within/message:opacity-100 group-hover/message:opacity-100"> + <ActionBarPrimitive.FeedbackPositive className={actionBtnClass}> + <ThumbsUp width={16} height={16} /> + </ActionBarPrimitive.FeedbackPositive> + <ActionBarPrimitive.FeedbackNegative className={actionBtnClass}> + <ThumbsDown width={16} height={16} /> + </ActionBarPrimitive.FeedbackNegative> + <ActionBarPrimitive.Copy className={actionBtnClass}> + <AuiIf condition={(s) => s.message.isCopied}> + <CheckIcon width={16} height={16} /> + </AuiIf> + <AuiIf condition={(s) => !s.message.isCopied}> + <CopyIcon width={16} height={16} /> + </AuiIf> + </ActionBarPrimitive.Copy> + <ActionBarPrimitive.Reload className={actionBtnClass}> + <RefreshCwIcon width={16} height={16} /> + </ActionBarPrimitive.Reload> + <button type="button" aria-label="More" className={actionBtnClass}> + <EllipsisVertical width={16} height={16} /> + </button> + </ActionBarPrimitive.Root> + </div> + </AuiIf> + </MessagePrimitive.Root> + ); +}; + +const useFileSrc = (file: File | undefined) => { + const [src, setSrc] = useState<string | undefined>(undefined); + + useEffect(() => { + if (!file) { + setSrc(undefined); + return; + } + + const objectUrl = URL.createObjectURL(file); + setSrc(objectUrl); + + return () => { + URL.revokeObjectURL(objectUrl); + }; + }, [file]); + + return src; +}; + +const useAttachmentSrc = () => { + const { file, src } = useAuiState( + useShallow(({ attachment }): { file?: File; src?: string } => { + if (attachment.type !== "image") return {}; + if (attachment.file) return { file: attachment.file }; + const src = attachment.content?.filter((c) => c.type === "image")[0] + ?.image; + if (!src) return {}; + return { src }; + }), + ); + + return useFileSrc(file) ?? src; +}; + +const GeminiAttachment: FC = () => { + const isImage = useAuiState(({ attachment }) => attachment.type === "image"); + const src = useAttachmentSrc(); + + return ( + <AttachmentPrimitive.Root className="group/thumbnail relative"> + <div className="size-[72px] overflow-hidden rounded-xl border border-[#dadce0] bg-[#f1f3f4] dark:border-[#3c4043] dark:bg-[#282a2c]"> + {isImage && src ? ( + <img className="size-full object-cover" alt="Attachment" src={src} /> + ) : ( + <div className="flex size-full items-center justify-center text-[#5e6063] dark:text-[#9aa0a6]"> + <AttachmentPrimitive.unstable_Thumb className="text-xs" /> + </div> + )} + </div> + <AttachmentPrimitive.Remove + className="absolute -top-1.5 -right-1.5 flex size-6 items-center justify-center rounded-full border border-[#dadce0] bg-white text-[#5e6063] opacity-0 transition-all group-focus-within/thumbnail:opacity-100 group-hover/thumbnail:opacity-100 hover:bg-[#f1f3f4] hover:text-[#1f1f1f] dark:border-[#3c4043] dark:bg-[#1e1f20] dark:text-[#9aa0a6] dark:hover:bg-[#2b2c2f] dark:hover:text-[#e3e3e3]" + aria-label="Remove attachment" + > + <XIcon width={14} height={14} /> + </AttachmentPrimitive.Remove> + </AttachmentPrimitive.Root> + ); +}; diff --git a/apps/docs/components/examples/genui.tsx b/apps/docs/components/examples/genui.tsx new file mode 100644 index 0000000..87d796b --- /dev/null +++ b/apps/docs/components/examples/genui.tsx @@ -0,0 +1,9 @@ +export const GenUI = () => { + return ( + <iframe + title="Gen UI Example" + className="h-full w-full border-none" + src="https://assistant-ui-rsc-example.vercel.app/" + /> + ); +}; diff --git a/apps/docs/components/examples/grok.tsx b/apps/docs/components/examples/grok.tsx new file mode 100644 index 0000000..42fb255 --- /dev/null +++ b/apps/docs/components/examples/grok.tsx @@ -0,0 +1,372 @@ +"use client"; + +import { + ActionBarPrimitive, + AuiIf, + AttachmentPrimitive, + ComposerPrimitive, + MessagePrimitive, + ThreadPrimitive, + useAuiState, + useMessageTiming, +} from "@assistant-ui/react"; +import { + ArrowUpIcon, + CheckIcon, + ChevronDownIcon, + CopyIcon, + Mic, + Moon, + Paperclip, + PencilIcon, + RefreshCwIcon, + Square, + ThumbsDown, + ThumbsUp, + XIcon, + Zap, +} from "lucide-react"; +import { useEffect, useState, type FC } from "react"; +import { useShallow } from "zustand/shallow"; +import { MarkdownText } from "@/components/assistant-ui/markdown-text"; +import { GrokIcon } from "@/components/icons/grok"; +import { + DropdownMenu, + DropdownMenuContent, + DropdownMenuItem, + DropdownMenuSeparator, + DropdownMenuTrigger, +} from "@/components/ui/dropdown-menu"; + +export const Grok: FC = () => { + return ( + <ThreadPrimitive.Root className="flex h-full flex-col items-stretch bg-[#fdfdfd] px-4 dark:bg-[#141414]"> + <AuiIf condition={(s) => s.thread.isEmpty}> + <div className="flex h-full flex-col items-center justify-center"> + <GrokIcon className="mb-6 h-10 text-[#0d0d0d] dark:text-white" /> + <Composer /> + </div> + </AuiIf> + + <AuiIf condition={(s) => s.thread.isEmpty === false}> + <ThreadPrimitive.Viewport className="flex grow flex-col overflow-y-scroll pt-16"> + <ThreadPrimitive.Messages> + {() => <ChatMessage />} + </ThreadPrimitive.Messages> + </ThreadPrimitive.Viewport> + <Composer /> + <p className="mx-auto w-full max-w-3xl pb-2 text-center text-xs text-[#9a9a9a]"> + Grok can make mistakes. Verify important information. + </p> + </AuiIf> + </ThreadPrimitive.Root> + ); +}; + +const Composer: FC = () => { + const isEmpty = useAuiState((s) => s.composer.isEmpty); + const isRunning = useAuiState((s) => s.thread.isRunning); + + return ( + <ComposerPrimitive.Root + className="group/composer mx-auto mb-3 w-full max-w-3xl" + data-empty={isEmpty} + data-running={isRunning} + > + <div className="overflow-hidden rounded-4xl bg-[#f8f8f8] shadow-xs ring-1 ring-[#e5e5e5] transition-shadow ring-inset focus-within:ring-[#d0d0d0] dark:bg-[#212121] dark:ring-[#2a2a2a] dark:focus-within:ring-[#3a3a3a]"> + <AuiIf condition={(s) => s.composer.attachments.length > 0}> + <div className="flex flex-row flex-wrap gap-2 px-4 pt-3"> + <ComposerPrimitive.Attachments> + {() => <GrokAttachment />} + </ComposerPrimitive.Attachments> + </div> + </AuiIf> + + <div className="flex items-end gap-1 p-2"> + <ComposerPrimitive.AddAttachment className="mb-0.5 flex h-9 w-9 shrink-0 items-center justify-center rounded-full text-[#0d0d0d] transition-colors hover:bg-[#f0f0f0] dark:text-white dark:hover:bg-[#2a2a2a]"> + <Paperclip width={18} height={18} /> + </ComposerPrimitive.AddAttachment> + + <ComposerPrimitive.Input + placeholder="What do you want to know?" + minRows={1} + className="my-2 h-6 max-h-100 min-w-0 flex-1 resize-none bg-transparent text-base leading-6 text-[#0d0d0d] outline-none placeholder:text-[#9a9a9a] dark:text-white dark:placeholder:text-[#6b6b6b]" + /> + + <GrokModelPicker /> + + <div className="relative mb-0.5 h-9 w-9 shrink-0 rounded-full bg-[#0d0d0d] text-white dark:bg-white dark:text-[#0d0d0d]"> + <button + type="button" + className="absolute inset-0 flex items-center justify-center transition-all duration-300 ease-out group-data-[empty=false]/composer:scale-0 group-data-[empty=false]/composer:opacity-0 group-data-[running=true]/composer:scale-0 group-data-[running=true]/composer:opacity-0" + aria-label="Voice mode" + > + <Mic width={18} height={18} /> + </button> + + <ComposerPrimitive.Send className="absolute inset-0 flex items-center justify-center transition-all duration-300 ease-out group-data-[empty=true]/composer:scale-0 group-data-[empty=true]/composer:opacity-0 group-data-[running=true]/composer:scale-0 group-data-[running=true]/composer:opacity-0"> + <ArrowUpIcon width={18} height={18} /> + </ComposerPrimitive.Send> + + <ComposerPrimitive.Cancel className="absolute inset-0 flex items-center justify-center transition-all duration-300 ease-out group-data-[running=false]/composer:scale-0 group-data-[running=false]/composer:opacity-0"> + <Square width={14} height={14} fill="currentColor" /> + </ComposerPrimitive.Cancel> + </div> + </div> + </div> + </ComposerPrimitive.Root> + ); +}; + +const GROK_MODELS = [ + { + id: "grok-4.1-fast", + name: "Fast", + description: "Default. Quick responses", + Icon: Zap, + }, + { + id: "grok-4.1", + name: "Grok 4.1", + description: "Standard reasoning", + Icon: Moon, + }, + { + id: "grok-4.1-think", + name: "Think", + description: "Multi-step reasoning", + Icon: Moon, + }, +]; + +const GrokModelPicker: FC = () => { + const [model, setModel] = useState(GROK_MODELS[0]!.id); + const current = GROK_MODELS.find((m) => m.id === model) ?? GROK_MODELS[0]!; + const CurrentIcon = current.Icon; + + return ( + <DropdownMenu> + <DropdownMenuTrigger className="mb-0.5 flex h-9 shrink-0 items-center gap-2 rounded-full px-2.5 text-[#0d0d0d] hover:bg-[#f0f0f0] dark:text-white dark:hover:bg-[#2a2a2a]"> + <CurrentIcon width={18} height={18} className="shrink-0" /> + <div className="flex items-center gap-1 overflow-hidden transition-[max-width,opacity] duration-300 group-data-[empty=false]/composer:max-w-0 group-data-[empty=false]/composer:opacity-0 group-data-[empty=true]/composer:max-w-32 group-data-[empty=true]/composer:opacity-100"> + <span className="text-sm font-semibold whitespace-nowrap"> + {current.name} + </span> + <ChevronDownIcon width={16} height={16} className="shrink-0" /> + </div> + </DropdownMenuTrigger> + <DropdownMenuContent align="end" className="min-w-60"> + {GROK_MODELS.map(({ id, name, description, Icon }) => ( + <DropdownMenuItem + key={id} + onClick={() => setModel(id)} + className="flex items-start gap-3" + > + <span className="mt-0.5 flex size-4 items-center justify-center text-[#0d0d0d] dark:text-white"> + {id === model ? <CheckIcon /> : <Icon width={14} height={14} />} + </span> + <span className="flex flex-1 flex-col"> + <span className="text-foreground text-sm">{name}</span> + <span className="text-muted-foreground text-xs"> + {description} + </span> + </span> + </DropdownMenuItem> + ))} + <DropdownMenuSeparator /> + <DropdownMenuItem className="text-muted-foreground text-sm"> + Subscribe to SuperGrok + </DropdownMenuItem> + </DropdownMenuContent> + </DropdownMenu> + ); +}; + +const ChatMessage: FC = () => { + return ( + <MessagePrimitive.Root className="group/message relative mx-auto mb-2 flex w-full max-w-3xl flex-col pb-0.5"> + <AuiIf condition={(s) => s.message.role === "user"}> + <div className="flex flex-col items-end"> + <div className="relative max-w-[90%] rounded-3xl rounded-br-lg border border-[#e5e5e5] bg-[#f0f0f0] px-4 py-3 text-[#0d0d0d] dark:border-[#2a2a2a] dark:bg-[#1a1a1a] dark:text-white"> + <div className="prose prose-sm dark:prose-invert prose-p:my-0 wrap-break-word"> + <MessagePrimitive.Parts> + {({ part }) => { + if (part.type === "text") return <MarkdownText />; + return null; + }} + </MessagePrimitive.Parts> + </div> + </div> + <div className="mt-1 flex h-8 items-center justify-end gap-0.5 opacity-0 transition-opacity group-focus-within/message:opacity-100 group-hover/message:opacity-100"> + <ActionBarPrimitive.Root className="flex items-center gap-0.5"> + <ActionBarPrimitive.Edit className="flex h-8 w-8 items-center justify-center rounded-full text-[#6b6b6b] transition-colors hover:bg-[#e5e5e5] hover:text-[#0d0d0d] dark:text-[#9a9a9a] dark:hover:bg-[#2a2a2a] dark:hover:text-white"> + <PencilIcon width={16} height={16} /> + </ActionBarPrimitive.Edit> + <ActionBarPrimitive.Copy className="flex h-8 w-8 items-center justify-center rounded-full text-[#6b6b6b] transition-colors hover:bg-[#e5e5e5] hover:text-[#0d0d0d] dark:text-[#9a9a9a] dark:hover:bg-[#2a2a2a] dark:hover:text-white"> + <CopyIcon width={16} height={16} /> + </ActionBarPrimitive.Copy> + </ActionBarPrimitive.Root> + </div> + </div> + </AuiIf> + + <AuiIf condition={(s) => s.message.role === "assistant"}> + <div className="flex flex-col items-start"> + <div className="w-full max-w-none"> + <div className="prose prose-sm dark:prose-invert prose-li:my-1 prose-ol:my-1 prose-p:my-2 prose-ul:my-1 wrap-break-word text-[#0d0d0d] dark:text-[#e5e5e5]"> + <MessagePrimitive.Parts> + {({ part }) => { + if (part.type === "text") return <MarkdownText />; + return null; + }} + </MessagePrimitive.Parts> + </div> + </div> + <div className="mt-1 flex h-8 w-full items-center justify-start gap-0.5 opacity-0 transition-opacity group-focus-within/message:opacity-100 group-hover/message:opacity-100"> + <ActionBarPrimitive.Root className="-ml-2 flex items-center gap-0.5"> + <ActionBarPrimitive.Reload className="flex h-8 w-8 items-center justify-center rounded-full text-[#6b6b6b] transition-colors hover:bg-[#e5e5e5] hover:text-[#0d0d0d] dark:text-[#9a9a9a] dark:hover:bg-[#2a2a2a] dark:hover:text-white"> + <RefreshCwIcon width={16} height={16} /> + </ActionBarPrimitive.Reload> + <ActionBarPrimitive.Copy className="flex h-8 w-8 items-center justify-center rounded-full text-[#6b6b6b] transition-colors hover:bg-[#e5e5e5] hover:text-[#0d0d0d] dark:text-[#9a9a9a] dark:hover:bg-[#2a2a2a] dark:hover:text-white"> + <CopyIcon width={16} height={16} /> + </ActionBarPrimitive.Copy> + <ActionBarPrimitive.FeedbackPositive className="flex h-8 w-8 items-center justify-center rounded-full text-[#6b6b6b] transition-colors hover:bg-[#e5e5e5] hover:text-[#0d0d0d] dark:text-[#9a9a9a] dark:hover:bg-[#2a2a2a] dark:hover:text-white"> + <ThumbsUp width={16} height={16} /> + </ActionBarPrimitive.FeedbackPositive> + <ActionBarPrimitive.FeedbackNegative className="flex h-8 w-8 items-center justify-center rounded-full text-[#6b6b6b] transition-colors hover:bg-[#e5e5e5] hover:text-[#0d0d0d] dark:text-[#9a9a9a] dark:hover:bg-[#2a2a2a] dark:hover:text-white"> + <ThumbsDown width={16} height={16} /> + </ActionBarPrimitive.FeedbackNegative> + <MessageTimingDisplay /> + </ActionBarPrimitive.Root> + </div> + </div> + </AuiIf> + </MessagePrimitive.Root> + ); +}; + +const formatTime = (ms: number | undefined) => { + if (ms === undefined) return null; + if (ms < 1000) return `${Math.round(ms)}ms`; + return `${(ms / 1000).toFixed(1)}s`; +}; + +const formatMs = (ms: number | undefined) => { + if (ms === undefined) return "\u2014"; + if (ms < 1000) return `${Math.round(ms)}ms`; + return `${(ms / 1000).toFixed(2)}s`; +}; + +const MessageTimingDisplay: FC = () => { + const timing = useMessageTiming(); + if (!timing?.totalStreamTime) return null; + + const totalTimeText = formatTime(timing.totalStreamTime); + if (!totalTimeText) return null; + + return ( + <div className="group/timing relative"> + <button + type="button" + className="ml-1 flex h-auto items-center justify-center rounded-md px-1.5 py-0.5 font-mono text-xs text-[#6b6b6b] tabular-nums transition-colors hover:bg-[#e5e5e5] hover:text-[#0d0d0d] dark:text-[#9a9a9a] dark:hover:bg-[#2a2a2a] dark:hover:text-white" + > + {totalTimeText} + </button> + <div className="pointer-events-none absolute top-1/2 left-full z-10 ml-2 -translate-y-1/2 scale-95 rounded-lg border border-[#e5e5e5] bg-white px-3 py-2 opacity-0 shadow-lg transition-all group-hover/timing:pointer-events-auto group-hover/timing:scale-100 group-hover/timing:opacity-100 before:absolute before:top-0 before:-left-2 before:h-full before:w-2 before:content-[''] dark:border-[#2a2a2a] dark:bg-[#1a1a1a]"> + <div className="grid min-w-[140px] gap-1.5 text-xs"> + {timing.firstTokenTime !== undefined && ( + <div className="flex items-center justify-between gap-4"> + <span className="text-[#6b6b6b] dark:text-[#9a9a9a]"> + First token + </span> + <span className="font-mono text-[#0d0d0d] tabular-nums dark:text-white"> + {formatMs(timing.firstTokenTime)} + </span> + </div> + )} + <div className="flex items-center justify-between gap-4"> + <span className="text-[#6b6b6b] dark:text-[#9a9a9a]">Total</span> + <span className="font-mono text-[#0d0d0d] tabular-nums dark:text-white"> + {formatMs(timing.totalStreamTime)} + </span> + </div> + {timing.tokensPerSecond !== undefined && ( + <div className="flex items-center justify-between gap-4"> + <span className="text-[#6b6b6b] dark:text-[#9a9a9a]">Speed</span> + <span className="font-mono text-[#0d0d0d] tabular-nums dark:text-white"> + {timing.tokensPerSecond.toFixed(1)} tok/s + </span> + </div> + )} + {timing.totalChunks > 0 && ( + <div className="flex items-center justify-between gap-4"> + <span className="text-[#6b6b6b] dark:text-[#9a9a9a]">Chunks</span> + <span className="font-mono text-[#0d0d0d] tabular-nums dark:text-white"> + {timing.totalChunks} + </span> + </div> + )} + </div> + </div> + </div> + ); +}; + +const useAttachmentSrc = () => { + const { file, src } = useAuiState( + useShallow((s): { file?: File; src?: string } => { + if (s.attachment.type !== "image") return {}; + if (s.attachment.file) return { file: s.attachment.file }; + const src = s.attachment.content?.filter((c) => c.type === "image")[0] + ?.image; + if (!src) return {}; + return { src }; + }), + ); + + const [fileSrc, setFileSrc] = useState<string | undefined>(undefined); + + useEffect(() => { + if (!file) { + setFileSrc(undefined); + return; + } + const objectUrl = URL.createObjectURL(file); + setFileSrc(objectUrl); + return () => URL.revokeObjectURL(objectUrl); + }, [file]); + + return fileSrc ?? src; +}; + +const GrokAttachment: FC = () => { + const src = useAttachmentSrc(); + + return ( + <AttachmentPrimitive.Root className="group/attachment relative"> + <div className="flex h-12 items-center gap-2 overflow-hidden rounded-xl border border-[#e5e5e5] bg-[#f0f0f0] p-0.5 transition-colors hover:border-[#d0d0d0] dark:border-[#2a2a2a] dark:bg-[#252525] dark:hover:border-[#3a3a3a]"> + <AuiIf condition={(s) => s.attachment.type === "image"}> + {src ? ( + <img + className="h-full w-12 rounded-[9px] object-cover" + alt="Attachment" + src={src} + /> + ) : ( + <div className="flex h-full w-12 items-center justify-center rounded-[9px] bg-[#e5e5e5] text-[#6b6b6b] dark:bg-[#3a3a3a] dark:text-[#9a9a9a]"> + <AttachmentPrimitive.unstable_Thumb className="text-xs" /> + </div> + )} + </AuiIf> + <AuiIf condition={(s) => s.attachment.type !== "image"}> + <div className="flex h-full w-12 items-center justify-center rounded-[9px] bg-[#e5e5e5] text-[#6b6b6b] dark:bg-[#3a3a3a] dark:text-[#9a9a9a]"> + <AttachmentPrimitive.unstable_Thumb className="text-xs" /> + </div> + </AuiIf> + </div> + <AttachmentPrimitive.Remove className="absolute -top-1.5 -right-1.5 flex h-6 w-6 scale-50 items-center justify-center rounded-full border border-[#e5e5e5] bg-white text-[#6b6b6b] opacity-0 transition-all group-hover/attachment:scale-100 group-hover/attachment:opacity-100 hover:bg-[#f5f5f5] hover:text-[#0d0d0d] dark:border-[#3a3a3a] dark:bg-[#1a1a1a] dark:text-[#9a9a9a] dark:hover:bg-[#252525] dark:hover:text-white"> + <XIcon width={14} height={14} /> + </AttachmentPrimitive.Remove> + </AttachmentPrimitive.Root> + ); +}; diff --git a/apps/docs/components/examples/modal.tsx b/apps/docs/components/examples/modal.tsx new file mode 100644 index 0000000..8cc8801 --- /dev/null +++ b/apps/docs/components/examples/modal.tsx @@ -0,0 +1,13 @@ +import { AssistantModal } from "@/components/assistant-ui/assistant-modal"; + +export const ModalChat = () => { + return ( + <div className="flex h-full w-full items-center justify-center p-4"> + <AssistantModal /> + <p className="bold text-lg"> + The Assistant Modal is available in the bottom right corner of the + screen. + </p> + </div> + ); +}; diff --git a/apps/docs/components/examples/perplexity.tsx b/apps/docs/components/examples/perplexity.tsx new file mode 100644 index 0000000..5f22746 --- /dev/null +++ b/apps/docs/components/examples/perplexity.tsx @@ -0,0 +1,471 @@ +"use client"; + +import { cn } from "@/lib/utils"; +import { MarkdownText } from "@/components/assistant-ui/markdown-text"; +import { + ActionBarPrimitive, + AuiIf, + AttachmentPrimitive, + BranchPickerPrimitive, + ComposerPrimitive, + MessagePrimitive, + ThreadPrimitive, + useAuiState, +} from "@assistant-ui/react"; +import { + ArrowRight, + AudioLines, + CheckIcon, + ChevronDownIcon, + ChevronLeftIcon, + ChevronRightIcon, + CopyIcon, + FileIcon, + PencilIcon, + Plus, + RefreshCwIcon, + Search, + Sparkles, + Square, + Telescope, + XIcon, +} from "lucide-react"; +import { type FC, useEffect, useState } from "react"; +import { useShallow } from "zustand/shallow"; +import { + DropdownMenu, + DropdownMenuContent, + DropdownMenuItem, + DropdownMenuTrigger, +} from "@/components/ui/dropdown-menu"; + +const composerPrimaryActionClassName = + "absolute inset-0 flex items-center justify-center rounded-full transition-all duration-200 ease-out"; + +const composerPrimaryActionColorsClassName = + "bg-[#25211c] text-[#f8f5f0] hover:bg-[#171411] dark:bg-[#f5f2ed] dark:text-[#1b1713] dark:hover:bg-white"; + +const messageActionClassName = + "flex size-8 items-center justify-center rounded-full text-[#7a7268] transition-colors hover:bg-[#f1ece5] hover:text-[#1f1b17] dark:text-[#9d968d] dark:hover:bg-[#2a2724] dark:hover:text-[#f5f2ed]"; + +export const Perplexity: FC = () => { + return ( + <ThreadPrimitive.Root + className="flex h-full flex-col bg-[#f6f2ec] text-[#1f1b17] dark:bg-[#171615] dark:text-[#f5f2ed]" + style={{ + ["--thread-max-width" as string]: "40rem", + }} + > + <AuiIf condition={(s) => s.thread.isEmpty}> + <EmptyState /> + </AuiIf> + + <AuiIf condition={(s) => !s.thread.isEmpty}> + <ThreadPrimitive.Viewport className="flex grow flex-col overflow-y-auto px-4 pt-12"> + <ThreadPrimitive.Messages> + {() => <ChatMessage />} + </ThreadPrimitive.Messages> + + <ThreadPrimitive.ViewportFooter className="sticky bottom-0 mx-auto mt-auto w-full max-w-(--thread-max-width) bg-linear-to-b from-transparent via-[#f6f2ec]/85 to-[#f6f2ec] pt-6 pb-4 dark:via-[#171615]/85 dark:to-[#171615]"> + <Composer placeholder="Ask a follow-up..." /> + </ThreadPrimitive.ViewportFooter> + </ThreadPrimitive.Viewport> + </AuiIf> + </ThreadPrimitive.Root> + ); +}; + +const EmptyState: FC = () => { + return ( + <div className="flex h-full flex-col justify-center px-4"> + <div className="mx-auto w-full max-w-(--thread-max-width)"> + <p className="font-display mb-8 text-center text-5xl leading-none tracking-[-0.06em] text-[#25211c] sm:text-[3.1rem] dark:text-[#f5f2ed]"> + perplexity + </p> + <Composer placeholder="Ask anything..." /> + </div> + </div> + ); +}; + +const Composer: FC<{ placeholder: string }> = ({ placeholder }) => { + return ( + <ComposerPrimitive.Root className="group/composer mx-auto flex w-full max-w-(--thread-max-width) flex-col rounded-3xl border border-[#d7d0c5] bg-[#fcfbf8] shadow-[0_2px_4px_-2px_rgba(32,24,18,0.06),0_8px_24px_-12px_rgba(32,24,18,0.12)] transition-colors focus-within:border-[#b8b0a5] dark:border-[#4a433b] dark:bg-[#23211f] dark:shadow-[0_2px_8px_-4px_rgba(0,0,0,0.4),0_12px_32px_-16px_rgba(0,0,0,0.5)] dark:focus-within:border-[#6a6258]"> + <AuiIf condition={(s) => s.composer.attachments.length > 0}> + <div className="flex flex-wrap gap-2 px-4 pt-4"> + <ComposerPrimitive.Attachments> + {() => <AttachmentPreview removable />} + </ComposerPrimitive.Attachments> + </div> + </AuiIf> + + <ComposerPrimitive.Input + rows={2} + placeholder={placeholder} + className="min-h-20 w-full resize-none bg-transparent px-5 pt-4 pb-0 text-[1.05rem] leading-7 outline-none placeholder:text-[#8a8176] dark:placeholder:text-[#918a82]" + /> + + <div className="flex items-center justify-between gap-2 px-3 pt-0.5 pb-3"> + <div className="flex min-w-0 items-center gap-1.5"> + <ComposerPrimitive.AddAttachment asChild> + <button + type="button" + className="flex size-9 shrink-0 items-center justify-center rounded-full text-[#6f675d] transition-colors hover:bg-[#f2ede6] hover:text-[#1f1b17] dark:text-[#a39c93] dark:hover:bg-[#2b2825] dark:hover:text-[#f5f2ed]" + aria-label="Add attachment" + > + <Plus className="size-4.5" /> + </button> + </ComposerPrimitive.AddAttachment> + <SearchModePicker /> + </div> + + <div className="flex items-center gap-1"> + <ModelPicker /> + <ComposerPrimaryAction /> + </div> + </div> + </ComposerPrimitive.Root> + ); +}; + +const ComposerPrimaryAction: FC = () => { + return ( + <div className="relative size-10 shrink-0"> + <AuiIf condition={(s) => s.thread.isRunning}> + <ComposerPrimitive.Cancel + className={cn( + composerPrimaryActionClassName, + composerPrimaryActionColorsClassName, + )} + > + <Square className="size-3.5 fill-current" /> + </ComposerPrimitive.Cancel> + </AuiIf> + + <AuiIf + condition={(s) => !s.thread.isRunning && s.composer.dictation != null} + > + <ComposerPrimitive.StopDictation + className={cn( + composerPrimaryActionClassName, + composerPrimaryActionColorsClassName, + )} + > + <Square className="size-3.5 animate-pulse fill-current" /> + </ComposerPrimitive.StopDictation> + </AuiIf> + + <AuiIf + condition={(s) => + !s.thread.isRunning && + s.composer.dictation == null && + !s.composer.isEmpty + } + > + <ComposerPrimitive.Send + className={cn( + composerPrimaryActionClassName, + composerPrimaryActionColorsClassName, + )} + > + <ArrowRight className="size-5" /> + </ComposerPrimitive.Send> + </AuiIf> + + <AuiIf + condition={(s) => + !s.thread.isRunning && + s.composer.dictation == null && + s.composer.isEmpty + } + > + <ComposerPrimitive.Dictate + className={cn( + composerPrimaryActionClassName, + composerPrimaryActionColorsClassName, + )} + > + <AudioLines className="size-5" /> + </ComposerPrimitive.Dictate> + </AuiIf> + </div> + ); +}; + +const SEARCH_MODES = [ + { + id: "search", + name: "Search", + description: "Fast answers to everyday questions", + Icon: Search, + }, + { + id: "research", + name: "Research", + description: "In-depth reports on complex topics", + Icon: Telescope, + }, + { + id: "labs", + name: "Labs", + description: "Apps, slides, and dashboards", + Icon: Sparkles, + }, +]; + +const SearchModePicker: FC = () => { + const [mode, setMode] = useState(SEARCH_MODES[0]!.id); + const current = SEARCH_MODES.find((m) => m.id === mode) ?? SEARCH_MODES[0]!; + const CurrentIcon = current.Icon; + + return ( + <DropdownMenu> + <DropdownMenuTrigger className="flex h-8 items-center gap-1.5 rounded-full border border-[#e0d8cb] bg-[#f5f1eb] px-3 text-sm text-[#3a342d] transition-colors hover:bg-[#ede6dd] dark:border-[#3a342f] dark:bg-[#2a2724] dark:text-[#e6dfd5] dark:hover:bg-[#332f2c]"> + <CurrentIcon className="size-3.5" /> + <span>{current.name}</span> + <ChevronDownIcon className="size-3.5 opacity-70" /> + </DropdownMenuTrigger> + <DropdownMenuContent align="start" className="min-w-64"> + {SEARCH_MODES.map(({ id, name, description, Icon }) => ( + <DropdownMenuItem + key={id} + onClick={() => setMode(id)} + className="flex items-start gap-3" + > + <span className="mt-0.5 flex size-4 items-center justify-center text-[#1f1b17] dark:text-[#f5f2ed]"> + {id === mode ? <CheckIcon /> : <Icon className="size-3.5" />} + </span> + <span className="flex flex-1 flex-col"> + <span className="text-foreground text-sm">{name}</span> + <span className="text-muted-foreground text-xs"> + {description} + </span> + </span> + </DropdownMenuItem> + ))} + </DropdownMenuContent> + </DropdownMenu> + ); +}; + +const PERPLEXITY_MODELS = [ + { id: "best", name: "Best", description: "Auto-pick the best model" }, + { id: "sonar", name: "Sonar", description: "Perplexity's fast model" }, + { id: "claude", name: "Claude 4.5 Sonnet", description: "Anthropic" }, + { id: "gpt-5", name: "GPT-5", description: "OpenAI" }, + { id: "gemini", name: "Gemini 3 Pro", description: "Google" }, +]; + +const ModelPicker: FC = () => { + const [model, setModel] = useState(PERPLEXITY_MODELS[0]!.id); + const current = + PERPLEXITY_MODELS.find((m) => m.id === model) ?? PERPLEXITY_MODELS[0]!; + + return ( + <DropdownMenu> + <DropdownMenuTrigger className="hidden h-8 items-center gap-1 rounded-full px-2.5 text-sm text-[#746c62] transition-colors hover:bg-[#f2ede6] hover:text-[#1f1b17] sm:flex dark:text-[#a19a91] dark:hover:bg-[#2b2825] dark:hover:text-[#f5f2ed]"> + <span>{current.name}</span> + <ChevronDownIcon className="size-4 opacity-70" /> + </DropdownMenuTrigger> + <DropdownMenuContent align="end" className="min-w-60"> + {PERPLEXITY_MODELS.map((m) => ( + <DropdownMenuItem + key={m.id} + onClick={() => setModel(m.id)} + className="flex items-start gap-3" + > + <span className="mt-0.5 flex size-4 items-center justify-center text-[#1f1b17] dark:text-[#f5f2ed]"> + {m.id === model ? <CheckIcon /> : null} + </span> + <span className="flex flex-1 flex-col"> + <span className="text-foreground text-sm">{m.name}</span> + <span className="text-muted-foreground text-xs"> + {m.description} + </span> + </span> + </DropdownMenuItem> + ))} + </DropdownMenuContent> + </DropdownMenu> + ); +}; + +const ChatMessage: FC = () => { + return ( + <MessagePrimitive.Root className="group/message mx-auto flex w-full max-w-(--thread-max-width) flex-col gap-2 py-4"> + <AuiIf condition={(s) => s.message.role === "user"}> + <div className="flex flex-col items-end gap-2"> + <div className="flex max-w-full flex-wrap justify-end gap-2"> + <MessagePrimitive.Attachments> + {() => <AttachmentPreview removable={false} />} + </MessagePrimitive.Attachments> + </div> + + <div className="flex items-start gap-2"> + <ActionBarPrimitive.Root className="mt-1 flex items-center gap-0.5 opacity-0 transition-opacity group-focus-within/message:opacity-100 group-hover/message:opacity-100"> + <ActionBarPrimitive.Copy className={messageActionClassName}> + <CopyIcon className="size-4" /> + </ActionBarPrimitive.Copy> + <ActionBarPrimitive.Edit className={messageActionClassName}> + <PencilIcon className="size-4" /> + </ActionBarPrimitive.Edit> + </ActionBarPrimitive.Root> + + <div className="max-w-[85%] rounded-3xl rounded-tr-md border border-[#ddd5c9] bg-[#fcfbf8] px-4 py-3 text-[#2c2721] shadow-[0_1px_0_rgba(31,27,23,0.03)] dark:border-[#38332e] dark:bg-[#23211f] dark:text-[#f1ede7]"> + <div className="prose prose-sm dark:prose-invert prose-p:my-0 wrap-break-word"> + <MessagePrimitive.Parts components={{ Text: MarkdownText }} /> + </div> + </div> + </div> + + <BranchPicker className="mr-3" /> + </div> + </AuiIf> + + <AuiIf condition={(s) => s.message.role === "assistant"}> + <div className="flex items-start gap-3"> + <div className="mt-1 flex size-8 shrink-0 items-center justify-center rounded-full border border-[#ddd5c9] bg-[#fffdfa] text-[#5b534a] dark:border-[#38332e] dark:bg-[#23211f] dark:text-[#d9d2c8]"> + <Search className="size-4" /> + </div> + + <div className="min-w-0 flex-1"> + <div className="prose prose-sm dark:prose-invert prose-li:my-1 prose-p:my-2 prose-ul:my-2 wrap-break-word text-[#2c2721] dark:text-[#ece7df]"> + <MessagePrimitive.Parts components={{ Text: MarkdownText }} /> + </div> + + <div className="mt-2 flex items-center gap-2"> + <BranchPicker /> + <ActionBarPrimitive.Root className="flex items-center gap-0.5 opacity-0 transition-opacity group-focus-within/message:opacity-100 group-hover/message:opacity-100"> + <ActionBarPrimitive.Reload className={messageActionClassName}> + <RefreshCwIcon className="size-4" /> + </ActionBarPrimitive.Reload> + <ActionBarPrimitive.Copy className={messageActionClassName}> + <AuiIf condition={(s) => s.message.isCopied}> + <CheckIcon className="size-4" /> + </AuiIf> + <AuiIf condition={(s) => !s.message.isCopied}> + <CopyIcon className="size-4" /> + </AuiIf> + </ActionBarPrimitive.Copy> + </ActionBarPrimitive.Root> + </div> + </div> + </div> + </AuiIf> + </MessagePrimitive.Root> + ); +}; + +const BranchPicker: FC<BranchPickerPrimitive.Root.Props> = ({ + className, + ...props +}) => { + return ( + <BranchPickerPrimitive.Root + hideWhenSingleBranch + className={cn( + "inline-flex items-center gap-1 text-xs text-[#8a8176] dark:text-[#9f978e]", + className, + )} + {...props} + > + <BranchPickerPrimitive.Previous className={messageActionClassName}> + <ChevronLeftIcon className="size-4" /> + </BranchPickerPrimitive.Previous> + <span className="min-w-9 text-center"> + <BranchPickerPrimitive.Number /> / <BranchPickerPrimitive.Count /> + </span> + <BranchPickerPrimitive.Next className={messageActionClassName}> + <ChevronRightIcon className="size-4" /> + </BranchPickerPrimitive.Next> + </BranchPickerPrimitive.Root> + ); +}; + +const useFileSrc = (file: File | undefined) => { + const [src, setSrc] = useState<string | undefined>(undefined); + + useEffect(() => { + if (!file) { + setSrc(undefined); + return; + } + + const objectUrl = URL.createObjectURL(file); + setSrc(objectUrl); + + return () => { + URL.revokeObjectURL(objectUrl); + }; + }, [file]); + + return src; +}; + +const useAttachmentSrc = () => { + const { file, src } = useAuiState( + useShallow((s): { file?: File; src?: string } => { + if (s.attachment.type !== "image") return {}; + if (s.attachment.file) return { file: s.attachment.file }; + const src = s.attachment.content?.filter((c) => c.type === "image")[0] + ?.image; + if (!src) return {}; + return { src }; + }), + ); + + return useFileSrc(file) ?? src; +}; + +const AttachmentTypeLabel: FC = () => { + const typeLabel = useAuiState((s) => { + switch (s.attachment.type) { + case "image": + return "Image"; + case "document": + return "Document"; + case "file": + return "File"; + default: + return s.attachment.type; + } + }); + + return <span>{typeLabel}</span>; +}; + +const AttachmentPreview: FC<{ removable: boolean }> = ({ removable }) => { + const src = useAttachmentSrc(); + + return ( + <AttachmentPrimitive.Root className="group/attachment relative"> + <div className="flex max-w-65 items-center gap-3 rounded-2xl border border-[#e3dbcf] bg-[#f5f1eb] py-2 pr-3 pl-2 transition-colors hover:bg-[#efe8de] dark:border-[#3a342f] dark:bg-[#2a2724] dark:hover:bg-[#302c29]"> + <div className="flex size-10 shrink-0 items-center justify-center overflow-hidden rounded-xl bg-[#fffdfa] text-[#6f675d] dark:bg-[#201d1b] dark:text-[#a59f96]"> + {src ? ( + <img + src={src} + alt="Attachment" + className="size-full object-cover" + /> + ) : ( + <FileIcon className="size-4" /> + )} + </div> + <div className="min-w-0"> + <p className="truncate text-sm leading-5 text-[#2d2822] dark:text-[#f3efe9]"> + <AttachmentPrimitive.Name /> + </p> + <p className="text-xs text-[#7d7469] dark:text-[#9d968d]"> + <AttachmentTypeLabel /> + </p> + </div> + </div> + + {removable ? ( + <AttachmentPrimitive.Remove className="absolute -top-1 -right-1 flex size-5 items-center justify-center rounded-full bg-[#ede6dd] text-[#5f574d] opacity-0 transition-all group-hover/attachment:opacity-100 hover:bg-[#dfd5c8] dark:bg-[#3a342f] dark:text-[#d4ccc2] dark:hover:bg-[#4a433b]"> + <XIcon className="size-3.5" /> + </AttachmentPrimitive.Remove> + ) : null} + </AttachmentPrimitive.Root> + ); +}; diff --git a/apps/docs/components/home/copy-command-button.tsx b/apps/docs/components/home/copy-command-button.tsx new file mode 100644 index 0000000..e20337d --- /dev/null +++ b/apps/docs/components/home/copy-command-button.tsx @@ -0,0 +1,102 @@ +"use client"; + +import { analytics, type AnalyticsProperties } from "@/lib/analytics"; +import { cn } from "@/lib/utils"; +import { CheckIcon, CopyIcon, SparklesIcon, TerminalIcon } from "lucide-react"; +import { useState } from "react"; +import { Menu } from "@base-ui/react/menu"; +import { + DropdownMenu, + DropdownMenuContent, + DropdownMenuItem, +} from "@/components/ui/dropdown-menu"; +import { SETUP_PROMPT } from "./setup-prompt"; + +export function CopyCommandButton({ + command = "npx assistant-ui init", + analyticsContext, + withPromptOption = false, +}: { + command?: string; + analyticsContext?: AnalyticsProperties; + withPromptOption?: boolean; +}) { + const [copied, setCopied] = useState(false); + + const flash = () => { + setCopied(true); + setTimeout(() => setCopied(false), 2000); + }; + + const copyCommand = () => { + navigator.clipboard.writeText(command); + analytics.cta.npmCommandCopied(command, analyticsContext); + flash(); + }; + + const copyPrompt = () => { + navigator.clipboard.writeText(SETUP_PROMPT); + analytics.cta.promptCopied(analyticsContext); + flash(); + }; + + const wrapperClassName = + "group border-border/60 bg-muted/30 hover:border-border hover:bg-muted/50 inline-flex w-fit items-center gap-1.5 rounded-md border px-3 py-1.5 font-mono text-sm transition-all"; + + const copyIcon = ( + <div className="text-muted-foreground relative flex size-4 items-center justify-center"> + <CheckIcon + className={cn( + "absolute size-3.5 text-green-500 transition-all duration-100", + copied ? "scale-100 opacity-100" : "scale-50 opacity-0", + )} + /> + <CopyIcon + className={cn( + "absolute size-3.5 transition-all duration-100", + copied + ? "scale-50 opacity-0" + : "scale-100 opacity-50 group-hover:opacity-100", + )} + /> + </div> + ); + + if (!withPromptOption) { + return ( + <button type="button" onClick={copyCommand} className={wrapperClassName}> + <span className="text-muted-foreground/70">$</span> + <span>{command}</span> + <div className="ml-1">{copyIcon}</div> + </button> + ); + } + + return ( + <DropdownMenu> + <Menu.Trigger + aria-label="Copy options" + render={ + <button + type="button" + className={cn(wrapperClassName, "cursor-pointer")} + /> + } + > + <span className="text-muted-foreground/70">$</span> + <span>{command}</span> + <div className="ml-1">{copyIcon}</div> + </Menu.Trigger> + <DropdownMenuContent align="start"> + <DropdownMenuItem onClick={copyCommand}> + <TerminalIcon className="size-3.5" /> + Copy CLI command + </DropdownMenuItem> + <DropdownMenuItem onClick={copyPrompt}> + <SparklesIcon className="size-3.5" /> + Copy coding agent prompt + </DropdownMenuItem> + </DropdownMenuContent> + </DropdownMenu> + ); +} diff --git a/apps/docs/components/home/example-showcase.tsx b/apps/docs/components/home/example-showcase.tsx new file mode 100644 index 0000000..f8c5644 --- /dev/null +++ b/apps/docs/components/home/example-showcase.tsx @@ -0,0 +1,295 @@ +"use client"; + +import { ChatGPT } from "@/components/examples/chatgpt"; +import { Claude } from "@/components/examples/claude"; +import { Perplexity } from "@/components/examples/perplexity"; +import { Base } from "@/components/examples/base"; +import { Tab } from "@/components/shared/tab"; +import { DocsRuntimeProvider } from "@/contexts/DocsRuntimeProvider"; +import { Gemini } from "@/components/examples/gemini"; +import { Grok } from "@/components/examples/grok"; +import { analytics } from "@/lib/analytics"; +import { Button } from "@/components/ui/radix/button"; +import { cn } from "@/lib/utils"; +import { ArrowUpRightIcon, Maximize2Icon, XIcon } from "lucide-react"; +import Link from "next/link"; +import React from "react"; +import { flushSync } from "react-dom"; + +const ExampleWrapper = ({ children }: { children: React.ReactNode }) => ( + <div + className="not-prose h-full overflow-hidden rounded-2xl border" + data-slot="example-shell" + > + {children} + </div> +); + +const EXAMPLE_TABS = [ + { + label: "Base", + slug: "base", + value: ( + <ExampleWrapper> + <DocsRuntimeProvider> + <Base /> + </DocsRuntimeProvider> + </ExampleWrapper> + ), + }, + { + label: "ChatGPT", + slug: "chatgpt", + value: ( + <ExampleWrapper> + <DocsRuntimeProvider> + <ChatGPT /> + </DocsRuntimeProvider> + </ExampleWrapper> + ), + }, + { + label: "Claude", + slug: "claude", + value: ( + <ExampleWrapper> + <DocsRuntimeProvider> + <Claude /> + </DocsRuntimeProvider> + </ExampleWrapper> + ), + }, + { + label: "Grok", + slug: "grok", + value: ( + <ExampleWrapper> + <DocsRuntimeProvider> + <Grok /> + </DocsRuntimeProvider> + </ExampleWrapper> + ), + }, + { + label: "Gemini", + slug: "gemini", + value: ( + <ExampleWrapper> + <DocsRuntimeProvider> + <Gemini /> + </DocsRuntimeProvider> + </ExampleWrapper> + ), + }, + { + label: "Perplexity", + slug: "perplexity", + value: ( + <ExampleWrapper> + <DocsRuntimeProvider> + <Perplexity /> + </DocsRuntimeProvider> + </ExampleWrapper> + ), + }, + { + label: "Explore More →", + href: "/examples", + }, +]; + +const prefersReducedMotion = () => + typeof window !== "undefined" && + window.matchMedia("(prefers-reduced-motion: reduce)").matches; + +export function ExampleShowcase() { + const sectionRef = React.useRef<HTMLElement>(null); + const panelRef = React.useRef<HTMLDivElement>(null); + const animationRef = React.useRef<Animation | null>(null); + const [activeIndex, setActiveIndex] = React.useState(0); + const [isFullscreen, setIsFullscreen] = React.useState(false); + + // FLIP: measure the panel before and after toggling between its inline + // `absolute` slot and `fixed inset-0`, then tween width/height plus a + // translate offset. The content keeps its real pixel size throughout (no + // scaling), so nothing stretches the way a View Transition's bitmap morph — + // or a non-uniform transform scale across the aspect-ratio change — would. + const toggleFullscreen = React.useCallback(() => { + const el = panelRef.current; + if (!el || prefersReducedMotion()) { + setIsFullscreen((value) => !value); + return; + } + + const first = el.getBoundingClientRect(); + flushSync(() => setIsFullscreen((value) => !value)); + const last = el.getBoundingClientRect(); + const expanding = last.height > first.height; + + animationRef.current?.cancel(); + animationRef.current = el.animate( + [ + { + transform: `translate(${first.left - last.left}px, ${first.top - last.top}px)`, + width: `${first.width}px`, + height: `${first.height}px`, + }, + { + transform: "translate(0px, 0px)", + width: `${last.width}px`, + height: `${last.height}px`, + }, + ], + { + duration: expanding ? 350 : 250, + easing: "cubic-bezier(0.32, 0.72, 0, 1)", + }, + ); + }, []); + + // Radix portals mount into <body>, so a portaled dropdown's contents fail + // the shell containment check; require the target to sit inside the panel. + const isOutsideShell = React.useCallback( + (target: EventTarget | null) => + target instanceof Element && + panelRef.current?.contains(target) === true && + !target.closest( + '[data-slot="example-shell"], [data-slot="tab-item"], [data-slot="tab-actions"]', + ), + [], + ); + + // Inline, the demo is a static preview: clicking anywhere except the tab bar + // expands it to fullscreen, where it becomes interactive. Fullscreen, the + // same click on the padding around the shell exits. + const handlePanelClick = (e: React.MouseEvent) => { + if (isFullscreen) { + if (e.defaultPrevented) return; + if (isOutsideShell(e.target)) toggleFullscreen(); + return; + } + if ((e.target as HTMLElement).closest('[data-slot="tab-list"]')) return; + toggleFullscreen(); + }; + + React.useEffect(() => { + if (!isFullscreen) return; + + const handleKeyDown = (e: KeyboardEvent) => { + // An open Radix layer (dropdown, popover, dialog) handles Escape in the + // capture phase and preventDefaults it; let that close first, don't unzoom. + if (e.defaultPrevented) return; + if (e.key === "Escape") toggleFullscreen(); + }; + // Page scroll is locked, so a wheel gesture outside the shell can only + // mean "get back to the page". The deltaY check keeps horizontal trackpad + // swipes over the overflow-x tab bar from exiting. + const handleWheel = (e: WheelEvent) => { + if (Math.abs(e.deltaY) <= Math.abs(e.deltaX)) return; + if (isOutsideShell(e.target)) toggleFullscreen(); + }; + document.addEventListener("keydown", handleKeyDown); + document.addEventListener("wheel", handleWheel, { passive: true }); + document.body.style.overflow = "hidden"; + // The homepage <main> creates a z-2 stacking context, which would paint + // the overlay beneath the sticky z-50 site header. + const stackingAncestor = sectionRef.current?.closest("main"); + if (stackingAncestor instanceof HTMLElement) { + stackingAncestor.style.zIndex = "50"; + } + // The panel stays in the page's DOM (no portal), so keep Tab focus inside + // by inerting every element outside its ancestor chain. Radix portals + // opened while zoomed mount into <body> afterwards and stay interactive. + const inertedSiblings: HTMLElement[] = []; + for ( + let node: HTMLElement | null = panelRef.current; + node && node !== document.body; + node = node.parentElement + ) { + for (const sibling of node.parentElement?.children ?? []) { + if ( + sibling !== node && + sibling instanceof HTMLElement && + !sibling.inert + ) { + sibling.inert = true; + inertedSiblings.push(sibling); + } + } + } + return () => { + document.removeEventListener("keydown", handleKeyDown); + document.removeEventListener("wheel", handleWheel); + document.body.style.overflow = ""; + if (stackingAncestor instanceof HTMLElement) { + stackingAncestor.style.zIndex = ""; + } + for (const sibling of inertedSiblings) { + sibling.inert = false; + } + }; + }, [isFullscreen, isOutsideShell, toggleFullscreen]); + + const activeSlug = EXAMPLE_TABS[activeIndex]?.slug; + + return ( + <section ref={sectionRef}> + {/* Placeholder reserves the inline height so the page doesn't jump when + the panel detaches to fullscreen. */} + <div className="relative h-160"> + <div + ref={panelRef} + onClick={handlePanelClick} + className={cn( + "inset-0", + isFullscreen + ? "bg-background fixed z-[100] p-4 md:p-6" + : "absolute cursor-zoom-in [&_[data-slot=tab-content-panel]]:pointer-events-none", + )} + > + <Tab + tabs={EXAMPLE_TABS} + className="h-full" + variant="ghost" + onTabChange={(label, index) => { + setActiveIndex(index); + analytics.example.tabSwitched(label); + }} + actions={ + <> + {activeSlug && ( + <Button + variant="ghost" + size="icon" + className="text-muted-foreground hover:text-foreground size-[30px]" + aria-label="Open demo" + title="Open demo" + asChild + > + <Link href={`/demos/${activeSlug}`}> + <ArrowUpRightIcon className="size-4" /> + </Link> + </Button> + )} + <Button + variant="ghost" + size="icon" + className="text-muted-foreground hover:text-foreground size-[30px]" + aria-label={isFullscreen ? "Exit fullscreen" : "Fullscreen"} + title={isFullscreen ? "Exit fullscreen" : "Fullscreen"} + onClick={toggleFullscreen} + > + {isFullscreen ? ( + <XIcon className="size-4" /> + ) : ( + <Maximize2Icon className="size-4" /> + )} + </Button> + </> + } + /> + </div> + </div> + </section> + ); +} diff --git a/apps/docs/components/home/feature-highlights.tsx b/apps/docs/components/home/feature-highlights.tsx new file mode 100644 index 0000000..71b3192 --- /dev/null +++ b/apps/docs/components/home/feature-highlights.tsx @@ -0,0 +1,58 @@ +import { Cpu, PanelsTopLeft, Terminal, Zap } from "lucide-react"; + +const FEATURES = [ + { + title: "Instant Chat UI", + description: "Drop in ChatGPT-style UX with theming and sensible defaults.", + icon: PanelsTopLeft, + }, + { + title: "State Management", + description: + "Streaming, interruptions, retries, and multi-turn conversations.", + icon: Cpu, + }, + { + title: "High Performance", + description: + "Optimized rendering and minimal bundle size for responsive streaming.", + icon: Zap, + }, + { + title: "Works Everywhere", + description: "Vercel AI SDK, LangChain, or any LLM provider. React-based.", + icon: Terminal, + }, +] as const; + +export function FeatureHighlights() { + return ( + <section className="flex flex-col gap-10"> + <div className="flex flex-col gap-2"> + <h2 className="max-w-[40ch] text-3xl font-semibold tracking-tight text-balance"> + Everything you need to ship AI chat + </h2> + <p className="text-muted-foreground max-w-[48ch] text-pretty"> + Production-ready components and state management. + </p> + </div> + + <dl className="grid gap-8 sm:grid-cols-2 lg:grid-cols-4"> + {FEATURES.map((feature) => { + const Icon = feature.icon; + return ( + <div key={feature.title} className="flex flex-col gap-1.5"> + <div className="bg-muted/30 mb-2.5 flex size-10 items-center justify-center rounded-lg border"> + <Icon className="text-muted-foreground size-5" /> + </div> + <dt className="font-medium">{feature.title}</dt> + <dd className="text-muted-foreground text-sm leading-relaxed text-pretty"> + {feature.description} + </dd> + </div> + ); + })} + </dl> + </section> + ); +} diff --git a/apps/docs/components/home/glowing-effect.tsx b/apps/docs/components/home/glowing-effect.tsx new file mode 100644 index 0000000..b924528 --- /dev/null +++ b/apps/docs/components/home/glowing-effect.tsx @@ -0,0 +1,282 @@ +"use client"; + +import { memo, useCallback, useEffect, useRef } from "react"; +import { cn } from "@/lib/utils"; + +// Simple easing function (ease-out cubic) +const easeOutCubic = (t: number) => 1 - (1 - t) ** 3; + +// Simple animation helper to replace motion's animate +const animateValue = ( + from: number, + to: number, + duration: number, + onUpdate: (value: number) => void, +): { stop: () => void } => { + const startTime = performance.now(); + let animationFrame: number; + + const tick = (currentTime: number) => { + const elapsed = currentTime - startTime; + const progress = Math.min(elapsed / (duration * 1000), 1); + const easedProgress = easeOutCubic(progress); + const currentValue = from + (to - from) * easedProgress; + + onUpdate(currentValue); + + if (progress < 1) { + animationFrame = requestAnimationFrame(tick); + } + }; + + animationFrame = requestAnimationFrame(tick); + + return { + stop: () => { + if (animationFrame) { + cancelAnimationFrame(animationFrame); + } + }, + }; +}; + +interface GlowingEffectProps { + blur?: number; + inactiveZone?: number; + proximity?: number; + spread?: number; + variant?: "default" | "white"; + glow?: boolean; + className?: string; + enabled?: boolean; + movementDuration?: number; + borderWidth?: number; +} +const GlowingEffect = memo( + ({ + blur = 0, + inactiveZone = 0.7, + proximity = 0, + spread = 20, + variant = "default", + glow = false, + className, + movementDuration = 2, + borderWidth = 1, + enabled = false, + }: GlowingEffectProps) => { + const containerRef = useRef<HTMLDivElement>(null); + const lastPosition = useRef({ x: 0, y: 0 }); + const animationFrameRef = useRef<number>(0); + const animationControlRef = useRef<{ stop: () => void } | null>(null); + const cachedRectRef = useRef<DOMRect | null>(null); + const reducedMotionRef = useRef(false); + + const handleMove = useCallback( + (e?: MouseEvent | { x: number; y: number }) => { + if (!containerRef.current || reducedMotionRef.current) return; + + if (animationFrameRef.current) { + cancelAnimationFrame(animationFrameRef.current); + } + + animationFrameRef.current = requestAnimationFrame(() => { + const element = containerRef.current; + if (!element) return; + + const { left, top, width, height } = + cachedRectRef.current || element.getBoundingClientRect(); + const mouseX = + e && typeof e === "object" + ? "clientX" in e && typeof (e as any).clientX === "number" + ? (e as any).clientX + : "x" in e && typeof (e as any).x === "number" + ? (e as any).x + : lastPosition.current.x + : lastPosition.current.x; + const mouseY = + e && typeof e === "object" + ? "clientY" in e && typeof (e as any).clientY === "number" + ? (e as any).clientY + : "y" in e && typeof (e as any).y === "number" + ? (e as any).y + : lastPosition.current.y + : lastPosition.current.y; + + if (e) { + lastPosition.current = { x: mouseX, y: mouseY }; + } + + const centerX = left + width * 0.5; + const centerY = top + height * 0.5; + const distanceFromCenter = Math.hypot( + mouseX - centerX, + mouseY - centerY, + ); + const inactiveRadius = 0.5 * Math.min(width, height) * inactiveZone; + + if (distanceFromCenter < inactiveRadius) { + element.style.setProperty("--active", "0"); + return; + } + + const isActive = + mouseX > left - proximity && + mouseX < left + width + proximity && + mouseY > top - proximity && + mouseY < top + height + proximity; + + element.style.setProperty("--active", isActive ? "1" : "0"); + + if (!isActive) return; + + const currentAngle = + parseFloat(element.style.getPropertyValue("--start")) || 0; + const targetAngle = + (180 * Math.atan2(mouseY - centerY, mouseX - centerX)) / Math.PI + + 90; + + const angleDiff = ((targetAngle - currentAngle + 180) % 360) - 180; + const newAngle = currentAngle + angleDiff; + + animationControlRef.current?.stop(); + animationControlRef.current = animateValue( + currentAngle, + newAngle, + movementDuration, + (value) => { + element.style.setProperty("--start", String(value)); + }, + ); + }); + }, + [inactiveZone, proximity, movementDuration], + ); + + // Setup ResizeObserver and prefers-reduced-motion + useEffect(() => { + if (!enabled || !containerRef.current) return; + + // Check for reduced motion preference + const mediaQuery = window.matchMedia("(prefers-reduced-motion: reduce)"); + reducedMotionRef.current = mediaQuery.matches; + + const handleReducedMotionChange = (e: MediaQueryListEvent) => { + reducedMotionRef.current = e.matches; + }; + + mediaQuery.addEventListener("change", handleReducedMotionChange); + + // Setup ResizeObserver to cache bounding rect + const resizeObserver = new ResizeObserver(() => { + if (containerRef.current) { + cachedRectRef.current = containerRef.current.getBoundingClientRect(); + } + }); + + resizeObserver.observe(containerRef.current); + + return () => { + mediaQuery.removeEventListener("change", handleReducedMotionChange); + resizeObserver.disconnect(); + }; + }, [enabled]); + + useEffect(() => { + if (!enabled) return; + + const handleScroll = () => handleMove(); + const handlePointerMove = (e: PointerEvent) => handleMove(e); + + window.addEventListener("scroll", handleScroll, { passive: true }); + document.body.addEventListener("pointermove", handlePointerMove, { + passive: true, + }); + + return () => { + if (animationFrameRef.current) { + cancelAnimationFrame(animationFrameRef.current); + } + animationControlRef.current?.stop(); + window.removeEventListener("scroll", handleScroll); + document.body.removeEventListener("pointermove", handlePointerMove); + }; + }, [handleMove, enabled]); + + return ( + <> + <div + className={cn( + "pointer-events-none absolute -inset-px hidden rounded-[inherit] border opacity-0 transition-opacity", + glow && "opacity-100", + variant === "white" && "border-white", + !enabled && "block!", + )} + /> + <div + ref={containerRef} + style={ + { + "--blur": `${blur}px`, + "--spread": spread, + "--start": "0", + "--active": "0", + "--glowingeffect-border-width": `${borderWidth}px`, + "--repeating-conic-gradient-times": "5", + "--gradient": + variant === "white" + ? `repeating-conic-gradient( + from 236.84deg at 50% 50%, + var(--black), + var(--black) calc(25% / var(--repeating-conic-gradient-times)) + )` + : `radial-gradient(circle, #dd7bbb 10%, #dd7bbb00 20%), + radial-gradient(circle at 40% 40%, #d79f1e 5%, #d79f1e00 15%), + radial-gradient(circle at 60% 60%, #5a922c 10%, #5a922c00 20%), + radial-gradient(circle at 40% 60%, #4c7894 10%, #4c789400 20%), + repeating-conic-gradient( + from 236.84deg at 50% 50%, + #dd7bbb 0%, + #d79f1e calc(25% / var(--repeating-conic-gradient-times)), + #5a922c calc(50% / var(--repeating-conic-gradient-times)), + #4c7894 calc(75% / var(--repeating-conic-gradient-times)), + #dd7bbb calc(100% / var(--repeating-conic-gradient-times)) + )`, + } as React.CSSProperties + } + className={cn( + "pointer-events-none absolute inset-0 rounded-[inherit] opacity-100 transition-opacity", + glow && "opacity-100", + blur > 0 && "blur-(--blur)", + className, + !enabled && "hidden!", + )} + > + <div + className={cn( + "glow", + "rounded-[inherit]", + "after:absolute", + "after:inset-[calc(-1*var(--glowingeffect-border-width))]", + "after:rounded-[inherit]", + 'after:content-[""]', + "after:[border:var(--glowingeffect-border-width)_solid_transparent]", + "after:bg-fixed", + "after:[background:var(--gradient)]", + "after:opacity-(--active)", + "after:transition-opacity", + "after:duration-300", + "after:[mask-clip:padding-box,border-box]", + "after:mask-intersect", + "after:mask-[linear-gradient(#0000,#0000),conic-gradient(from_calc((var(--start)-var(--spread))*1deg),#00000000_0deg,#fff,#00000000_calc(var(--spread)*2deg))]", + )} + /> + </div> + </> + ); + }, +); + +GlowingEffect.displayName = "GlowingEffect"; + +export { GlowingEffect }; diff --git a/apps/docs/components/home/hero.tsx b/apps/docs/components/home/hero.tsx new file mode 100644 index 0000000..63fa919 --- /dev/null +++ b/apps/docs/components/home/hero.tsx @@ -0,0 +1,62 @@ +"use client"; + +import { CopyCommandButton } from "@/components/home/copy-command-button"; +import { NpmDownloads } from "@/components/home/npm-downloads"; +import Image from "next/image"; + +export function Hero() { + return ( + <section className="flex flex-col pb-4 md:pb-8"> + <div className="flex flex-col gap-4"> + <div className="flex flex-col gap-2 pb-1"> + <h1 className="max-w-[30ch] text-4xl font-semibold tracking-tight text-balance lg:text-5xl"> + A frontend for your AI agents + </h1> + <p className="text-muted-foreground max-w-[48ch] text-lg text-pretty"> + React primitives for building AI chat interfaces. + </p> + </div> + + <div className="flex flex-wrap items-center"> + <CopyCommandButton withPromptOption /> + </div> + + <div className="text-muted-foreground flex flex-wrap items-center gap-x-3 gap-y-3 text-[13px]"> + <a + href="https://github.com/assistant-ui/assistant-ui/blob/main/LICENSE" + target="_blank" + rel="noopener noreferrer" + className="hover:text-foreground transition-colors" + > + MIT License + </a> + <span className="bg-muted-foreground/20 hidden size-1 rounded-full sm:block" /> + <a + href="https://www.npmjs.com/package/@assistant-ui/react" + target="_blank" + rel="noopener noreferrer" + className="hover:text-foreground transition-colors" + > + <NpmDownloads /> + </a> + <span className="bg-muted-foreground/20 hidden size-1 rounded-full sm:block" /> + <a + href="https://www.ycombinator.com/companies/assistant-ui" + target="_blank" + rel="noopener noreferrer" + className="hover:text-foreground inline-flex w-full items-center gap-1.5 transition-colors sm:w-auto" + > + Backed by + <Image + src="/icons/yc_logo.png" + alt="Y Combinator" + height={18} + width={18} + /> + Combinator + </a> + </div> + </div> + </section> + ); +} diff --git a/apps/docs/components/home/home-assistant.tsx b/apps/docs/components/home/home-assistant.tsx new file mode 100644 index 0000000..189e8cf --- /dev/null +++ b/apps/docs/components/home/home-assistant.tsx @@ -0,0 +1,62 @@ +"use client"; + +import type { ReactNode } from "react"; +import { usePathname } from "next/navigation"; +import { cn } from "@/lib/utils"; +import { CurrentPageProvider } from "@/components/docs/contexts/current-page"; +import { + AssistantPanelProvider, + useAssistantPanel, +} from "@/components/docs/assistant/context"; +import { DocsAssistantRuntimeProvider } from "@/contexts/AssistantRuntimeProvider"; +import { + DocsAssistantPanel, + getPanelWidth, +} from "@/components/docs/layout/docs-layout"; + +function HomeShift({ + isHome, + children, +}: { + isHome: boolean; + children: ReactNode; +}) { + const { open, width, isResizing } = useAssistantPanel(); + + return ( + <div + className={cn( + isHome && "flex min-h-screen flex-col md:mr-(--chat-panel-width)", + isHome && + !isResizing && + "transition-[margin] duration-200 ease-[cubic-bezier(0.32,0.72,0,1)]", + )} + style={ + isHome + ? ({ + "--chat-panel-width": getPanelWidth(open, width), + } as React.CSSProperties) + : undefined + } + > + {children} + </div> + ); +} + +export function HomeAssistant({ children }: { children: ReactNode }) { + const isHome = usePathname() === "/"; + + return ( + <CurrentPageProvider> + <AssistantPanelProvider> + <HomeShift isHome={isHome}>{children}</HomeShift> + {isHome && ( + <DocsAssistantRuntimeProvider> + <DocsAssistantPanel /> + </DocsAssistantRuntimeProvider> + )} + </AssistantPanelProvider> + </CurrentPageProvider> + ); +} diff --git a/apps/docs/components/home/npm-downloads.tsx b/apps/docs/components/home/npm-downloads.tsx new file mode 100644 index 0000000..5b68c78 --- /dev/null +++ b/apps/docs/components/home/npm-downloads.tsx @@ -0,0 +1,28 @@ +"use client"; + +import { useEffect, useState } from "react"; +import { formatCompact } from "@/lib/format"; + +export function NpmDownloads() { + const [downloads, setDownloads] = useState<number | null>(null); + + useEffect(() => { + fetch("/api/npm/downloads") + .then((res) => (res.ok ? res.json() : null)) + .then((data) => { + if (data && typeof data.downloads === "number") { + setDownloads(data.downloads); + } + }) + .catch(console.error); + }, []); + + return ( + <span> + <span className="tabular-nums"> + {downloads !== null ? formatCompact(downloads) : "—"} + </span>{" "} + weekly downloads + </span> + ); +} diff --git a/apps/docs/components/home/setup-prompt.ts b/apps/docs/components/home/setup-prompt.ts new file mode 100644 index 0000000..6df86b7 --- /dev/null +++ b/apps/docs/components/home/setup-prompt.ts @@ -0,0 +1,77 @@ +// The prompt copied by the "Copy prompt" button in the homepage hero. +// Paste into any AI coding agent (Claude Code, Cursor, Windsurf, Codex, etc.) +// to scaffold and wire up assistant-ui inside the user's project. + +export const SETUP_PROMPT = `Step 1: Read the docs + +Read https://assistant-ui.com/llms-full.txt — the full reference in one +file. Append ".md" to any docs URL for raw markdown +(e.g. /docs/installation.md). + +Step 2: Ask the user + +Ask (a) fresh project vs (b) integrate into an existing project, and which +runtime: default (AI SDK + OpenAI), LangGraph, Assistant Cloud, MCP, or a +custom backend (Assistant Transport / AG-UI / A2A / Google ADK). + +Step 3: Create or integrate + +You are in a non-interactive agent shell — do NOT omit the flags below. + +If (a) fresh project: +Run: npx assistant-ui@latest create <app-name> -t <template> + +Templates: default (unsure), minimal, cloud, cloud-clerk, langgraph, mcp. +For custom backends without a template, swap -t for -e: +AG-UI → \`-e with-ag-ui\`, Google ADK → \`-e with-google-adk\`, +Assistant Transport → \`-e with-assistant-transport\`. +A2A: use \`-t minimal\`, then install @assistant-ui/react-a2a manually. +<app-name> and -t/-e are required. + +If (b) existing project: +Detect the framework first. init ONLY supports Next.js App Router (it +scaffolds app/assistant.tsx + app/api/chat/route.ts). + +- Vite / React Router v7 / TanStack Start / Expo → init does NOT work. + Prefer starting fresh with \`create <name> -e with-react-router\` / + \`with-tanstack\` / \`with-expo\` and migrating code in. In-place only + if you must: set components.json registries to + \`"@assistant-ui": "https://r.assistant-ui.com/styles/{style}/{name}.json"\` + (base- styles get Base UI components; others get Radix; plain + \`https://r.assistant-ui.com/{name}.json\` is the Radix-only fallback), + then \`npx shadcn@latest add @assistant-ui/thread\`, install the provider + SDK, and wire your own server for the chat endpoint (Vite/Expo do not + bundle one). See /docs/installation.md Manual Setup. +- Next.js App Router → confirm package manager, Tailwind (v3 or v4), and + env var location, then run: + + npx assistant-ui@latest init --yes + +--yes is required. init scaffolds: +- components/assistant-ui/thread.tsx (Thread UI) +- app/assistant.tsx (exports <Assistant />, already wraps Thread in + AssistantRuntimeProvider with runtime + transport) +- app/api/chat/route.ts (OpenAI backend) + +Dependencies are installed automatically. If components.json already +exists, init aborts — use \`add <name>\` instead. + +After create OR init: +- Put the provider key in .env.local (OPENAI_API_KEY / ANTHROPIC_API_KEY …). +- Non-OpenAI: swap the model in app/api/chat/route.ts and install that + provider's @ai-sdk/* package. +- Render <Assistant /> (from \`@/app/assistant\`) in the root page. + Do NOT rebuild the provider — assistant.tsx already does it. +- More components: \`npx assistant-ui@latest add <name>\` + (thread-list, assistant-modal, attachment, …). + +Step 4: Verify + +Start the dev server and send a test message. Success = the reply streams +token-by-token in the Thread. If it fails, diagnose: +- 401 / missing key → .env.local not loaded; restart the dev server +- 500 on /api/chat → route file provider mismatch +- Styles missing → Tailwind / globals.css not wired + +Stuck on anything else? Report the exact error to the user — do not loop. +`; diff --git a/apps/docs/components/home/testimonials/data.ts b/apps/docs/components/home/testimonials/data.ts new file mode 100644 index 0000000..fe32628 --- /dev/null +++ b/apps/docs/components/home/testimonials/data.ts @@ -0,0 +1,154 @@ +import profileRawWorks from "./profiles/iVBtd9hs_400x400.jpg"; +import profileManuuonly from "./profiles/g2f-gI5N_400x400.jpg"; +import profileEliasdevs from "./profiles/vnGL1NHF_400x400.jpg"; +import profileLangChain from "./profiles/langchain_400x400.jpg"; +import profileHarrisonChase from "./profiles/harrison_chase_400x400.jpg"; +import profileHowardGil from "./profiles/howard_gil_400x400.jpg"; +import profileAdamSilverman from "./profiles/adam_silverman_400x400.jpg"; +import profileAdrian from "./profiles/adian_400x400.jpg"; +import profileVirat from "./profiles/virat_400x400.jpg"; +import profileDaniel from "./profiles/daniel_400x400.jpg"; +import profileAlex from "./profiles/alex_400x400.jpg"; +import profileKeithSchacht from "./profiles/keith_schacht_400x400.jpg"; +import profileNeon from "./profiles/neon_400x400.jpg"; +import profileVoltagent from "./profiles/voltagent_400x400.jpg"; +import profileYagudaev from "./profiles/yagudaev_400x400.jpg"; +import profileYC from "./profiles/ycombinator_400x400.png"; +import type { StaticImageData } from "next/image"; + +export type Testimonial = { + platform: "X"; + avatar: StaticImageData; + username: string; + message: string; + url: string; +}; + +export const TESTIMONIALS: Testimonial[] = [ + { + platform: "X", + avatar: profileLangChain, + username: "@LangChainAI", + message: + "Build stateful conversational AI agents with LangGraph and assistant-ui.", + url: "https://x.com/LangChainAI/status/1833896540542558217", + }, + { + platform: "X", + avatar: profileDaniel, + username: "@js_craft_hq", + message: + "A huge shoutout to @simonfarshid for the cool stuff he makes at assistant-ui", + url: "https://x.com/js_craft_hq/status/1833911916487458887", + }, + { + platform: "X", + avatar: profileAdamSilverman, + username: "@adamsilverman", + message: "assistant-ui powered by LangGraph! It is awesome.", + url: "https://x.com/adamsilverman/status/1834671925076914687", + }, + { + platform: "X", + avatar: profileHarrisonChase, + username: "@hwchase17", + message: + "Pleasure to work with Simon… bring streaming, gen UI, and human-in-the-loop with LangGraph Cloud + assistant-ui.", + url: "https://x.com/hwchase17/status/1833897209747964191", + }, + { + platform: "X", + avatar: profileNeon, + username: "@neondatabase", + message: + "Conversations and streaming AI output are powered by @assistantui. It renders the chat interface and stores threads in Assistant UI Cloud so sessions persist across refreshes and context builds over time.", + url: "https://x.com/neondatabase/status/1983609810798178829", + }, + { + platform: "X", + avatar: profileYC, + username: "@ycombinator", + message: + "@assistantui is an open-source TypeScript/React library for AI chat. It brings a ChatGPT-like UI directly into your app.", + url: "https://x.com/ycombinator/status/1891502867971801368", + }, + { + platform: "X", + avatar: profileVirat, + username: "@virattt", + message: + "My favorite financial assistant is assistant-ui… fully open source… powerful starter tools… integrates with LangSmith and LangGraph.", + url: "https://x.com/virattt/status/1841957600201736586", + }, + { + platform: "X", + avatar: profileAdrian, + username: "@hungrytrtl", + message: "assistant-ui… Could save days of UI work.", + url: "https://x.com/hungrytrtl/status/1856389748321071478", + }, + { + platform: "X", + avatar: profileKeithSchacht, + username: "@keithschacht", + message: + "A great set of pre-built react components for building chatbot experiences.", + url: "https://x.com/keithschacht/status/1899656102310010984", + }, + { + platform: "X", + avatar: profileHowardGil, + username: "@HowardBGil", + message: + "Stop building chat interfaces yourself… Just install assistant-ui and you’re done.", + url: "https://x.com/HowardBGil/status/1833947697872863547", + }, + { + platform: "X", + avatar: profileAlex, + username: "@replyingaleks", + message: "assistant-ui goated 🐐🐐", + url: "https://x.com/replyingaleks/status/1833938146586939645", + }, + { + platform: "X", + avatar: profileYagudaev, + username: "@yagudaev", + message: + "React chat ui so you can focus on your agent logic. Can't wait to try it.", + url: "https://x.com/yagudaev/status/2009121137536405924", + }, + { + platform: "X", + avatar: profileVoltagent, + username: "@voltagent_dev", + message: + "We just added Assistant UI support to VoltAgent. Streaming, tools, memory all work out of the box.", + url: "https://x.com/voltagent_dev/status/2000718877508940013", + }, +]; + +export const BLOG_TESTIMONIALS: Testimonial[] = [ + { + platform: "X", + avatar: profileRawWorks, + username: "@raw_works", + message: "i needed this 7 months ago!", + url: "https://x.com/raw_works/status/1797111840188809472", + }, + { + platform: "X", + avatar: profileEliasdevs, + username: "@eliasdevs", + message: "I was able to get it into production in 2 hours", + url: "https://x.com/eliasdevs/status/1800691268194013219", + }, + { + platform: "X", + avatar: profileManuuonly, + username: "@manuuonly", + message: + "was just thinking of researching a tool that could help me do this. awesome, great product!", + url: "https://x.com/manuuonly/status/1797511225523454243", + }, +]; diff --git a/apps/docs/components/home/testimonials/profiles/0nZ-ergb_400x400.jpg b/apps/docs/components/home/testimonials/profiles/0nZ-ergb_400x400.jpg new file mode 100644 index 0000000..f1a1299 Binary files /dev/null and b/apps/docs/components/home/testimonials/profiles/0nZ-ergb_400x400.jpg differ diff --git a/apps/docs/components/home/testimonials/profiles/8rrHE4fe_400x400.jpg b/apps/docs/components/home/testimonials/profiles/8rrHE4fe_400x400.jpg new file mode 100644 index 0000000..d09a7d2 Binary files /dev/null and b/apps/docs/components/home/testimonials/profiles/8rrHE4fe_400x400.jpg differ diff --git a/apps/docs/components/home/testimonials/profiles/F3t4GW4t_400x400.jpg b/apps/docs/components/home/testimonials/profiles/F3t4GW4t_400x400.jpg new file mode 100644 index 0000000..99c1b89 Binary files /dev/null and b/apps/docs/components/home/testimonials/profiles/F3t4GW4t_400x400.jpg differ diff --git a/apps/docs/components/home/testimonials/profiles/Mc0m3zkD_400x400.jpg b/apps/docs/components/home/testimonials/profiles/Mc0m3zkD_400x400.jpg new file mode 100644 index 0000000..7c4420f Binary files /dev/null and b/apps/docs/components/home/testimonials/profiles/Mc0m3zkD_400x400.jpg differ diff --git a/apps/docs/components/home/testimonials/profiles/Y7hXHG3i_400x400.jpg b/apps/docs/components/home/testimonials/profiles/Y7hXHG3i_400x400.jpg new file mode 100644 index 0000000..debf277 Binary files /dev/null and b/apps/docs/components/home/testimonials/profiles/Y7hXHG3i_400x400.jpg differ diff --git a/apps/docs/components/home/testimonials/profiles/_-WcCpRw_400x400.jpg b/apps/docs/components/home/testimonials/profiles/_-WcCpRw_400x400.jpg new file mode 100644 index 0000000..d5b588e Binary files /dev/null and b/apps/docs/components/home/testimonials/profiles/_-WcCpRw_400x400.jpg differ diff --git a/apps/docs/components/home/testimonials/profiles/_5AVrIjJ_400x400.jpg b/apps/docs/components/home/testimonials/profiles/_5AVrIjJ_400x400.jpg new file mode 100644 index 0000000..e4ab756 Binary files /dev/null and b/apps/docs/components/home/testimonials/profiles/_5AVrIjJ_400x400.jpg differ diff --git a/apps/docs/components/home/testimonials/profiles/adam_silverman_400x400.jpg b/apps/docs/components/home/testimonials/profiles/adam_silverman_400x400.jpg new file mode 100644 index 0000000..8d1a8ef Binary files /dev/null and b/apps/docs/components/home/testimonials/profiles/adam_silverman_400x400.jpg differ diff --git a/apps/docs/components/home/testimonials/profiles/adian_400x400.jpg b/apps/docs/components/home/testimonials/profiles/adian_400x400.jpg new file mode 100644 index 0000000..c316431 Binary files /dev/null and b/apps/docs/components/home/testimonials/profiles/adian_400x400.jpg differ diff --git a/apps/docs/components/home/testimonials/profiles/alex_400x400.jpg b/apps/docs/components/home/testimonials/profiles/alex_400x400.jpg new file mode 100644 index 0000000..aaa7d58 Binary files /dev/null and b/apps/docs/components/home/testimonials/profiles/alex_400x400.jpg differ diff --git a/apps/docs/components/home/testimonials/profiles/daniel_400x400.jpg b/apps/docs/components/home/testimonials/profiles/daniel_400x400.jpg new file mode 100644 index 0000000..ad1dc22 Binary files /dev/null and b/apps/docs/components/home/testimonials/profiles/daniel_400x400.jpg differ diff --git a/apps/docs/components/home/testimonials/profiles/g2f-gI5N_400x400.jpg b/apps/docs/components/home/testimonials/profiles/g2f-gI5N_400x400.jpg new file mode 100644 index 0000000..692f807 Binary files /dev/null and b/apps/docs/components/home/testimonials/profiles/g2f-gI5N_400x400.jpg differ diff --git a/apps/docs/components/home/testimonials/profiles/harrison_chase_400x400.jpg b/apps/docs/components/home/testimonials/profiles/harrison_chase_400x400.jpg new file mode 100644 index 0000000..44faaaf Binary files /dev/null and b/apps/docs/components/home/testimonials/profiles/harrison_chase_400x400.jpg differ diff --git a/apps/docs/components/home/testimonials/profiles/howard_gil_400x400.jpg b/apps/docs/components/home/testimonials/profiles/howard_gil_400x400.jpg new file mode 100644 index 0000000..c9bd00e Binary files /dev/null and b/apps/docs/components/home/testimonials/profiles/howard_gil_400x400.jpg differ diff --git a/apps/docs/components/home/testimonials/profiles/iVBtd9hs_400x400.jpg b/apps/docs/components/home/testimonials/profiles/iVBtd9hs_400x400.jpg new file mode 100644 index 0000000..97cb676 Binary files /dev/null and b/apps/docs/components/home/testimonials/profiles/iVBtd9hs_400x400.jpg differ diff --git a/apps/docs/components/home/testimonials/profiles/keith_schacht_400x400.jpg b/apps/docs/components/home/testimonials/profiles/keith_schacht_400x400.jpg new file mode 100644 index 0000000..53e525d Binary files /dev/null and b/apps/docs/components/home/testimonials/profiles/keith_schacht_400x400.jpg differ diff --git a/apps/docs/components/home/testimonials/profiles/langchain_400x400.jpg b/apps/docs/components/home/testimonials/profiles/langchain_400x400.jpg new file mode 100644 index 0000000..7ed2b6f Binary files /dev/null and b/apps/docs/components/home/testimonials/profiles/langchain_400x400.jpg differ diff --git a/apps/docs/components/home/testimonials/profiles/neon_400x400.jpg b/apps/docs/components/home/testimonials/profiles/neon_400x400.jpg new file mode 100644 index 0000000..e2f8093 Binary files /dev/null and b/apps/docs/components/home/testimonials/profiles/neon_400x400.jpg differ diff --git a/apps/docs/components/home/testimonials/profiles/uuRXSsia_400x400.jpg b/apps/docs/components/home/testimonials/profiles/uuRXSsia_400x400.jpg new file mode 100644 index 0000000..9d481ea Binary files /dev/null and b/apps/docs/components/home/testimonials/profiles/uuRXSsia_400x400.jpg differ diff --git a/apps/docs/components/home/testimonials/profiles/virat_400x400.jpg b/apps/docs/components/home/testimonials/profiles/virat_400x400.jpg new file mode 100644 index 0000000..709926e Binary files /dev/null and b/apps/docs/components/home/testimonials/profiles/virat_400x400.jpg differ diff --git a/apps/docs/components/home/testimonials/profiles/vnGL1NHF_400x400.jpg b/apps/docs/components/home/testimonials/profiles/vnGL1NHF_400x400.jpg new file mode 100644 index 0000000..ea6443e Binary files /dev/null and b/apps/docs/components/home/testimonials/profiles/vnGL1NHF_400x400.jpg differ diff --git a/apps/docs/components/home/testimonials/profiles/voltagent_400x400.jpg b/apps/docs/components/home/testimonials/profiles/voltagent_400x400.jpg new file mode 100644 index 0000000..a17e4b3 Binary files /dev/null and b/apps/docs/components/home/testimonials/profiles/voltagent_400x400.jpg differ diff --git a/apps/docs/components/home/testimonials/profiles/yagudaev_400x400.jpg b/apps/docs/components/home/testimonials/profiles/yagudaev_400x400.jpg new file mode 100644 index 0000000..cd19c25 Binary files /dev/null and b/apps/docs/components/home/testimonials/profiles/yagudaev_400x400.jpg differ diff --git a/apps/docs/components/home/testimonials/profiles/ycombinator_400x400.png b/apps/docs/components/home/testimonials/profiles/ycombinator_400x400.png new file mode 100644 index 0000000..3e4feaf Binary files /dev/null and b/apps/docs/components/home/testimonials/profiles/ycombinator_400x400.png differ diff --git a/apps/docs/components/home/testimonials/testimonials.tsx b/apps/docs/components/home/testimonials/testimonials.tsx new file mode 100644 index 0000000..32c6c28 --- /dev/null +++ b/apps/docs/components/home/testimonials/testimonials.tsx @@ -0,0 +1,65 @@ +import type { Testimonial } from "@/components/home/testimonials/data"; +import { cn } from "@/lib/utils"; +import Image from "next/image"; +import type { FC } from "react"; + +export const TestimonialContainer: FC<{ + testimonials: Testimonial[]; + className?: string; +}> = ({ testimonials, className }) => { + return ( + <div + className="mx-auto w-full max-w-7xl overflow-hidden" + style={{ + maskImage: "linear-gradient(to bottom, black 65%, transparent 95%)", + maxHeight: 400, + }} + > + <div + className={cn("h-[440px] columns-1 gap-4", className)} + style={{ columnFill: "auto" }} + > + {testimonials.map((testimonial, idx) => ( + <TestimonialCard key={idx} {...testimonial} /> + ))} + </div> + </div> + ); +}; + +const TestimonialCard: FC<Testimonial> = ({ + username, + avatar, + message, + url, +}) => ( + <a + href={url} + target="_blank" + rel="noopener noreferrer" + className="bg-card hover:bg-muted/50 mb-4 block break-inside-avoid-column space-y-3 rounded-lg border p-4 transition-colors" + > + <div className="flex items-center justify-between"> + <div className="flex items-center gap-2"> + <Image + alt={`@${username}`} + loading="lazy" + width={24} + height={24} + className="size-6 rounded-full" + src={avatar} + /> + <span className="text-muted-foreground text-xs">{username}</span> + </div> + <svg + className="text-muted-foreground/50 size-3" + fill="currentColor" + viewBox="0 0 24 24" + aria-hidden="true" + > + <path d="M18.244 2.25h3.308l-7.227 8.26 8.502 11.24H16.17l-5.214-6.817L4.99 21.75H1.68l7.73-8.835L1.254 2.25H8.08l4.713 6.231zm-1.161 17.52h1.833L7.084 4.126H5.117z" /> + </svg> + </div> + <p className="text-sm leading-relaxed whitespace-pre-line">{message}</p> + </a> +); diff --git a/apps/docs/components/home/trusted-by.tsx b/apps/docs/components/home/trusted-by.tsx new file mode 100644 index 0000000..a93a281 --- /dev/null +++ b/apps/docs/components/home/trusted-by.tsx @@ -0,0 +1,91 @@ +"use client"; + +import { cn } from "@/lib/utils"; +import { useMediaQuery } from "@/hooks/use-media-query"; +import Image from "next/image"; +import Link from "next/link"; + +const LOGOS = [ + { + src: "/icons/cust/langchain.svg", + alt: "Langchain", + height: "h-7", + href: "https://langchain.com?ref=assistant-ui", + }, + { + src: "/icons/cust/athenaintel.png", + alt: "Athena Intelligence", + height: "h-11", + href: "https://athenaintelligence.ai?ref=assistant-ui", + }, + { + src: "/icons/cust/browseruse.svg", + alt: "Browseruse", + height: "h-6", + href: "https://browser-use.com/?ref=assistant-ui", + }, + { + src: "/icons/cust/stack.svg", + alt: "Stack", + height: "h-5", + href: "https://stack-ai.com?ref=assistant-ui", + }, + { + src: "/icons/cust/mastra.svg", + alt: "Mastra", + height: "h-7", + href: "https://mastra.ai?ref=assistant-ui", + }, +] as const; + +function LogoList() { + return ( + <> + {LOGOS.map((logo) => ( + <Link + key={logo.alt} + href={logo.href} + target="_blank" + rel="noopener noreferrer" + > + <Image + src={logo.src} + alt={logo.alt} + width={100} + height={44} + className={cn( + "w-auto shrink-0 opacity-40 invert transition-opacity hover:opacity-100 dark:invert-0", + logo.height, + )} + /> + </Link> + ))} + </> + ); +} + +export function TrustedBy() { + const isMobile = useMediaQuery("(max-width: 1080px)"); + + return ( + <section className="flex flex-col items-center gap-4"> + {isMobile ? ( + <div className="flex w-full gap-(--gap) overflow-hidden [--duration:20s] [--gap:3rem]"> + {Array.from({ length: 4 }).map((_, i) => ( + <div + key={i} + className="animate-marquee flex shrink-0 items-center justify-around gap-(--gap)" + > + <LogoList /> + </div> + ))} + </div> + ) : ( + <div className="flex w-full items-center justify-around"> + <LogoList /> + </div> + )} + <p className="text-muted-foreground text-sm">and teams everywhere</p> + </section> + ); +} diff --git a/apps/docs/components/icons/a2a.tsx b/apps/docs/components/icons/a2a.tsx new file mode 100644 index 0000000..93ea993 --- /dev/null +++ b/apps/docs/components/icons/a2a.tsx @@ -0,0 +1,24 @@ +export function A2AIcon(props: React.SVGProps<SVGSVGElement>) { + return ( + <svg aria-hidden="true" {...props} viewBox="0 0 860 860" fill="none"> + <circle cx={544} cy={307} r={27} fill="currentColor" /> + <circle cx={154} cy={307} r={27} fill="currentColor" /> + <circle cx={706} cy={307} r={27} fill="currentColor" /> + <circle cx={316} cy={307} r={27} fill="currentColor" /> + <path + d="M336.5 191.003H162C97.6588 191.003 45.5 243.162 45.5 307.503C45.5 371.844 97.6442 424.003 161.985 424.003C206.551 424.003 256.288 424.003 296.5 424.003C487.5 424.003 374 191.005 569 191.001C613.886 191 658.966 191 698.025 191C762.366 191.001 814.5 243.16 814.5 307.501C814.5 371.843 762.34 424.003 697.998 424.003H523.5" + stroke="currentColor" + strokeWidth={48} + strokeLinecap="round" + /> + <path + d="M256 510.002C270.359 510.002 282 521.643 282 536.002C282 550.361 270.359 562.002 256 562.002H148C133.641 562.002 122 550.361 122 536.002C122 521.643 133.641 510.002 148 510.002H256ZM712 510.002C726.359 510.002 738 521.643 738 536.002C738 550.361 726.359 562.002 712 562.002H360C345.641 562.002 334 550.361 334 536.002C334 521.643 345.641 510.002 360 510.002H712Z" + fill="currentColor" + /> + <path + d="M444 628.002C458.359 628.002 470 639.643 470 654.002C470 668.361 458.359 680.002 444 680.002H100C85.6406 680.002 74 668.361 74 654.002C74 639.643 85.6406 628.002 100 628.002H444ZM548 628.002C562.359 628.002 574 639.643 574 654.002C574 668.361 562.359 680.002 548 680.002C533.641 680.002 522 668.361 522 654.002C522 639.643 533.641 628.002 548 628.002ZM760 628.002C774.359 628.002 786 639.643 786 654.002C786 668.361 774.359 680.002 760 680.002H652C637.641 680.002 626 668.361 626 654.002C626 639.643 637.641 628.002 652 628.002H760Z" + fill="currentColor" + /> + </svg> + ); +} diff --git a/apps/docs/components/icons/adk.tsx b/apps/docs/components/icons/adk.tsx new file mode 100644 index 0000000..a59cda5 --- /dev/null +++ b/apps/docs/components/icons/adk.tsx @@ -0,0 +1,16 @@ +import Image, { type ImageProps } from "next/image"; + +type AdkIconProps = Omit<ImageProps, "alt" | "src"> & { + alt?: string; +}; + +export function AdkIcon({ alt = "", ...props }: AdkIconProps) { + return ( + <Image + aria-hidden="true" + alt={alt} + {...props} + src="/icons/agent-development-kit.png" + /> + ); +} diff --git a/apps/docs/components/icons/agui.tsx b/apps/docs/components/icons/agui.tsx new file mode 100644 index 0000000..36e3e6b --- /dev/null +++ b/apps/docs/components/icons/agui.tsx @@ -0,0 +1,11 @@ +export function AguiIcon(props: React.SVGProps<SVGSVGElement>) { + return ( + <svg aria-hidden="true" {...props} viewBox="0 0 24 24" fill="currentColor"> + <path + fillRule="evenodd" + clipRule="evenodd" + d="M7.675.281A.609.609 0 018.32.014l10.047 2.222 2.511.555.628.139.157.034.04.01.01.001a.04.04 0 01.002.001l.06.016a.609.609 0 01.408.688l-3.63 19.82a.609.609 0 01-.989.358L1.718 10.605a.609.609 0 01-.123-.794l6.08-9.53zM3.34 10.374l13.118 10.971-5.76-13.394-7.358 2.423zm8.519-2.805l5.874 13.659L20.77 4.635l-8.912 2.934zM3.539 9.026l6.675-2.197-2.123-4.937L3.54 9.026zm7.836-2.58l8.195-2.698-1.466-.324-8.872-1.962 2.143 4.984z" + /> + </svg> + ); +} diff --git a/apps/docs/components/icons/cloudflare.tsx b/apps/docs/components/icons/cloudflare.tsx new file mode 100644 index 0000000..9fa9fe4 --- /dev/null +++ b/apps/docs/components/icons/cloudflare.tsx @@ -0,0 +1,15 @@ +export function CloudflareIcon(props: React.SVGProps<SVGSVGElement>) { + return ( + <svg aria-hidden="true" {...props} viewBox="54 3 50 23" fill="none"> + <path fill="#fff" d="M98.6 14.2 93 12.9l-1-.4-25.7.2v12.4l32.3.1z" /> + <path + fill="#F48120" + d="M88.1 24c.3-1 .2-2-.3-2.6-.5-.6-1.2-1-2.1-1.1l-17.4-.2c-.1 0-.2-.1-.3-.1-.1-.1-.1-.2 0-.3.1-.2.2-.3.4-.3l17.5-.2c2.1-.1 4.3-1.8 5.1-3.8l1-2.6c0-.1.1-.2 0-.3-1.1-5.1-5.7-8.9-11.1-8.9-5 0-9.3 3.2-10.8 7.7-1-.7-2.2-1.1-3.6-1-2.4.2-4.3 2.2-4.6 4.6-.1.6 0 1.2.1 1.8-3.9.1-7.1 3.3-7.1 7.3 0 .4 0 .7.1 1.1 0 .2.2.3.3.3h32.1c.2 0 .4-.1.4-.3l.3-1.1z" + /> + <path + fill="#FAAD3F" + d="M93.6 12.8h-.5c-.1 0-.2.1-.3.2l-.7 2.4c-.3 1-.2 2 .3 2.6.5.6 1.2 1 2.1 1.1l3.7.2c.1 0 .2.1.3.1.1.1.1.2 0 .3-.1.2-.2.3-.4.3l-3.8.2c-2.1.1-4.3 1.8-5.1 3.8l-.2.9c-.1.1 0 .3.2.3h13.2c.2 0 .3-.1.3-.3.2-.8.4-1.7.4-2.6 0-5.2-4.3-9.5-9.5-9.5" + /> + </svg> + ); +} diff --git a/apps/docs/components/icons/discord.tsx b/apps/docs/components/icons/discord.tsx new file mode 100644 index 0000000..f87d8c5 --- /dev/null +++ b/apps/docs/components/icons/discord.tsx @@ -0,0 +1,15 @@ +export function DiscordIcon({ className }: { className?: string }) { + return ( + <svg + aria-hidden="true" + xmlns="http://www.w3.org/2000/svg" + viewBox="0 0 127.14 96.36" + className={className} + > + <path + fill="currentColor" + d="M107.7,8.07A105.15,105.15,0,0,0,81.47,0a72.06,72.06,0,0,0-3.36,6.83A97.68,97.68,0,0,0,49,6.83,72.37,72.37,0,0,0,45.64,0,105.89,105.89,0,0,0,19.39,8.09C2.79,32.65-1.71,56.6.54,80.21h0A105.73,105.73,0,0,0,32.71,96.36,77.7,77.7,0,0,0,39.6,85.25a68.42,68.42,0,0,1-10.85-5.18c.91-.66,1.8-1.34,2.66-2a75.57,75.57,0,0,0,64.32,0c.87.71,1.76,1.39,2.66,2a68.68,68.68,0,0,1-10.87,5.19,77,77,0,0,0,6.89,11.1A105.25,105.25,0,0,0,126.6,80.22h0C129.24,52.84,122.09,29.11,107.7,8.07ZM42.45,65.69C36.18,65.69,31,60,31,53s5-12.74,11.43-12.74S54,46,53.89,53,48.84,65.69,42.45,65.69Zm42.24,0C78.41,65.69,73.25,60,73.25,53s5-12.74,11.44-12.74S96.23,46,96.12,53,91.08,65.69,84.69,65.69Z" + /> + </svg> + ); +} diff --git a/apps/docs/components/icons/gemini.tsx b/apps/docs/components/icons/gemini.tsx new file mode 100644 index 0000000..dfdf1ff --- /dev/null +++ b/apps/docs/components/icons/gemini.tsx @@ -0,0 +1,24 @@ +const SPARKLE_PATH = + "M32.447 0c.68 0 1.273.465 1.439 1.125a38.904 38.904 0 001.999 5.905c2.152 5 5.105 9.376 8.854 13.125 3.751 3.75 8.126 6.703 13.125 8.855a38.98 38.98 0 005.906 1.999c.66.166 1.124.758 1.124 1.438 0 .68-.464 1.273-1.125 1.439a38.902 38.902 0 00-5.905 1.999c-5 2.152-9.375 5.105-13.125 8.854-3.749 3.751-6.702 8.126-8.854 13.125a38.973 38.973 0 00-2 5.906 1.485 1.485 0 01-1.438 1.124c-.68 0-1.272-.464-1.438-1.125a38.913 38.913 0 00-2-5.905c-2.151-5-5.103-9.375-8.854-13.125-3.75-3.749-8.125-6.702-13.125-8.854a38.973 38.973 0 00-5.905-2A1.485 1.485 0 010 32.448c0-.68.465-1.272 1.125-1.438a38.903 38.903 0 005.905-2c5-2.151 9.376-5.104 13.125-8.854 3.75-3.749 6.703-8.125 8.855-13.125a38.972 38.972 0 001.999-5.905A1.485 1.485 0 0132.447 0z"; + +export function GeminiIcon(props: React.SVGProps<SVGSVGElement>) { + return ( + <svg aria-hidden="true" {...props} viewBox="0 0 65 65" fill="none"> + <defs> + <linearGradient + id="gemini-blue" + x1="18.447" + y1="43.42" + x2="52.153" + y2="15.004" + gradientUnits="userSpaceOnUse" + > + <stop stopColor="#346BF1" /> + <stop offset=".45" stopColor="#3186FF" /> + <stop offset="1" stopColor="#4FA0FF" /> + </linearGradient> + </defs> + <path d={SPARKLE_PATH} fill="url(#gemini-blue)" /> + </svg> + ); +} diff --git a/apps/docs/components/icons/github.tsx b/apps/docs/components/icons/github.tsx new file mode 100644 index 0000000..a38fc23 --- /dev/null +++ b/apps/docs/components/icons/github.tsx @@ -0,0 +1,12 @@ +export function GitHubIcon({ className }: { className?: string }) { + return ( + <svg + aria-hidden="true" + viewBox="0 0 24 24" + className={className} + fill="currentColor" + > + <path d="M12 0c-6.626 0-12 5.373-12 12 0 5.302 3.438 9.8 8.207 11.387.599.111.793-.261.793-.577v-2.234c-3.338.726-4.033-1.416-4.033-1.416-.546-1.387-1.333-1.756-1.333-1.756-1.089-.745.083-.729.083-.729 1.205.084 1.839 1.237 1.839 1.237 1.07 1.834 2.807 1.304 3.492.997.107-.775.418-1.305.762-1.604-2.665-.305-5.467-1.334-5.467-5.931 0-1.311.469-2.381 1.236-3.221-.124-.303-.535-1.524.117-3.176 0 0 1.008-.322 3.301 1.23.957-.266 1.983-.399 3.003-.404 1.02.005 2.047.138 3.006.404 2.291-1.552 3.297-1.23 3.297-1.23.653 1.653.242 2.874.118 3.176.77.84 1.235 1.911 1.235 3.221 0 4.609-2.807 5.624-5.479 5.921.43.372.823 1.102.823 2.222v3.293c0 .319.192.694.801.576 4.765-1.589 8.199-6.086 8.199-11.386 0-6.627-5.373-12-12-12z" /> + </svg> + ); +} diff --git a/apps/docs/components/icons/grok.tsx b/apps/docs/components/icons/grok.tsx new file mode 100644 index 0000000..c1a91db --- /dev/null +++ b/apps/docs/components/icons/grok.tsx @@ -0,0 +1,30 @@ +export function GrokIcon(props: React.SVGProps<SVGSVGElement>) { + return ( + <svg aria-hidden="true" {...props} viewBox="0 0 1024 400" fill="none"> + <path + d="M878.447 292.85V108.548H907.572V230.18L969.173 159.399H1004.48L949.069 220.058L1005 292.85H970.204L924.902 230.304L907.572 230.18V292.85H878.447Z" + fill="currentColor" + /> + <path + d="M790.12 295.947C746.819 295.947 723.364 265.23 723.364 225.995C723.364 186.502 746.819 156.043 790.12 156.043C833.679 156.043 856.876 186.502 856.876 225.995C856.876 265.23 833.679 295.947 790.12 295.947ZM753.778 225.995C753.778 256.454 770.274 271.683 790.12 271.683C810.224 271.683 826.462 256.454 826.462 225.995C826.462 195.536 810.224 180.049 790.12 180.049C770.274 180.049 753.778 195.536 753.778 225.995Z" + fill="currentColor" + /> + <path + d="M642.304 292.85V180.565L666.789 159.399H718.854V184.179H671.429V292.85H642.304Z" + fill="currentColor" + /> + <path + d="M530.938 296.258C475.424 296.258 442.325 255.938 442.325 200.957C442.325 145.46 476.606 104.16 532.021 104.16C575.323 104.16 607.025 126.359 614.5 167.659H581.251C576.353 144.169 556.765 131.005 532.021 131.005C492.07 131.005 474.544 165.594 474.544 200.957C474.544 236.32 492.07 270.651 532.021 270.651C570.168 270.651 586.921 243.031 588.21 220.058H530.732V193.331H617.593L617.451 207.305C617.451 259.231 596.295 296.258 530.938 296.258Z" + fill="currentColor" + /> + <path + d="M163.591 251.367L288.916 158.716C295.06 154.174 303.842 155.946 306.769 163.001C322.178 200.209 315.294 244.924 284.638 275.625C253.982 306.326 211.328 313.059 172.34 297.724L129.75 317.472C190.837 359.287 265.016 348.946 311.369 302.492C348.137 265.67 359.524 215.479 348.877 170.217L348.973 170.314C333.533 103.822 352.769 77.2447 392.174 22.898C393.107 21.6094 394.04 20.3208 394.973 19L343.119 70.9306V70.7695L163.559 251.399" + fill="currentColor" + /> + <path + d="M137.728 273.885C93.8835 231.941 101.443 167.028 138.854 129.594C166.518 101.889 211.842 90.5817 251.409 107.205L293.902 87.5535C286.246 82.0126 276.435 76.0528 265.176 71.8648C214.287 50.8929 153.362 61.3305 111.994 102.727C72.2025 142.577 59.6893 203.85 81.1773 256.135C97.229 295.211 70.9158 322.852 44.4097 350.75C35.0167 360.64 25.5916 370.53 18 381L137.696 273.917" + fill="currentColor" + /> + </svg> + ); +} diff --git a/apps/docs/components/icons/helicone.tsx b/apps/docs/components/icons/helicone.tsx new file mode 100644 index 0000000..d730013 --- /dev/null +++ b/apps/docs/components/icons/helicone.tsx @@ -0,0 +1,28 @@ +export function HeliconeIcon(props: React.SVGProps<SVGSVGElement>) { + return ( + <svg aria-hidden="true" {...props} viewBox="600 430 630 630" fill="none"> + <path + fill="#1e1e1e" + fillRule="evenodd" + clipRule="evenodd" + d="M913.5 458.5C917.247 458.392 920.914 458.892 924.5 460C1017.83 501 1111.17 542 1204.5 583C1205.46 584.419 1206.29 585.919 1207 587.5C1207.67 695.167 1207.67 802.833 1207 910.5C1204.54 919.631 1198.7 925.465 1189.5 928C1098.82 966.227 1007.98 1004.06 917 1041.5C822.603 1002.59 728.436 963.089 634.5 923C630.619 920.072 628.119 916.239 627 911.5C626.333 803.5 626.333 695.5 627 587.5C627.667 586.167 628.333 584.833 629 583.5C723.742 541.373 818.575 499.707 913.5 458.5ZM1185.5 693.5C1186.83 764.334 1186.83 835.334 1185.5 906.5C1125.95 931.951 1066.28 956.784 1006.5 981C1066.3 885.223 1125.97 789.389 1185.5 693.5ZM855.5 697.5C872.054 705.021 888.721 712.354 905.5 719.5C906.663 724.802 906.829 730.136 906 735.5C824.159 794.418 742.159 853.084 660 911.5C656.043 910.125 652.209 908.458 648.5 906.5C647.334 887.175 647.167 867.842 648 848.5C717.145 798.023 786.312 747.69 855.5 697.5ZM905.5 761.5C906.832 845.668 906.832 930.001 905.5 1014.5C831.355 983.591 757.355 952.425 683.5 921C757.743 868.057 831.743 814.89 905.5 761.5Z" + /> + <path + fill="#0ca4e8" + d="M914.5 479.5C919.428 480.128 924.095 481.628 928.5 484C1009.65 519.409 1090.65 555.076 1171.5 591C1087.05 627.725 1002.72 664.725 918.5 702C833.045 665.614 747.712 628.614 662.5 591C746.609 553.915 830.609 516.748 914.5 479.5Z" + /> + <path + fill="#0ca4e8" + d="M647.5 606.5C709.626 633.397 771.626 660.563 833.5 688C771.955 733.052 710.288 777.885 648.5 822.5C647.501 750.669 647.167 678.669 647.5 606.5Z" + /> + <path + fill="#c0faf4" + d="M1184.5 606.5C1185.5 606.5 1186.5 606.5 1187.5 606.5C1187.67 622.17 1187.5 637.837 1187 653.5C1115.5 767.833 1044 882.167 972.5 996.5C957.418 1002.64 942.418 1008.97 927.5 1015.5C926.334 990.507 926.167 965.507 927 940.5C994.393 838.103 1061.39 735.437 1128 632.5C1146.84 623.726 1165.67 615.059 1184.5 606.5Z" + /> + <path + fill="#0ca4e8" + d="M928.5 902.5C927.501 841.669 927.168 780.669 927.5 719.5C983.308 694.629 1039.31 670.462 1095.5 647C1039.61 732.049 983.944 817.216 928.5 902.5Z" + /> + </svg> + ); +} diff --git a/apps/docs/components/icons/langchain.tsx b/apps/docs/components/icons/langchain.tsx new file mode 100644 index 0000000..12ae116 --- /dev/null +++ b/apps/docs/components/icons/langchain.tsx @@ -0,0 +1,7 @@ +export function LangChainIcon(props: React.SVGProps<SVGSVGElement>) { + return ( + <svg aria-hidden="true" {...props} viewBox="0 0 24 24" fill="currentColor"> + <path d="M7.531 15.976a7.534 7.534 0 000-10.651L2.206 0A7.537 7.537 0 000 5.326c0 1.996.794 3.913 2.206 5.325l5.325 5.325zM18.674 16.469a7.535 7.535 0 00-10.65 0l5.325 5.325a7.536 7.536 0 0010.651 0l-5.326-5.325zM2.218 21.782a7.536 7.536 0 005.326 2.206v-7.531H.012c0 1.996.795 3.914 2.206 5.325zM20.73 8.595a7.534 7.534 0 00-10.651.001l5.325 5.326 5.326-5.327z" /> + </svg> + ); +} diff --git a/apps/docs/components/icons/langfuse.tsx b/apps/docs/components/icons/langfuse.tsx new file mode 100644 index 0000000..82db22c --- /dev/null +++ b/apps/docs/components/icons/langfuse.tsx @@ -0,0 +1,30 @@ +export function LangfuseIcon(props: React.SVGProps<SVGSVGElement>) { + return ( + <svg aria-hidden="true" {...props} viewBox="0 0 512 512" fill="none"> + <path + d="M254.75 302.25L285.25 326.75C285.25 326.75 308.587 309.418 325.75 306.875C343.75 304.208 362.954 314.244 380.75 326.208C407.629 344.279 430.25 367.208 430.25 367.208L456.75 341.208C456.75 341.208 383.686 262.047 325.75 269.208C287.75 273.905 254.75 302.25 254.75 302.25Z" + fill="#FF5D5F" + /> + <path + d="M80.25 151.286L55.25 178.786C55.25 178.786 124.902 243.786 179.75 243.786C204.75 243.786 239.419 224.201 269.25 198.757C286.25 184.257 305.25 167.786 324.25 167.786C337.021 167.786 353.866 174.551 369.75 192.316C369.75 192.316 380.003 186.168 386.25 181.75C391.74 177.868 399.896 171.25 399.896 171.25C377.047 146.864 343.998 129.038 324.25 130.786C292.25 130.79 269.25 150.711 240.75 173.75C212.25 196.789 200.25 206.286 179.75 206.286C145.25 206.286 80.25 151.286 80.25 151.286Z" + fill="#4E9CFF" + /> + <path + d="M80.25 360.75L55.25 333.25C55.25 333.25 124.902 268.25 179.75 268.25C204.75 268.25 239.419 287.835 269.25 313.279C286.25 327.779 305.25 344.25 324.25 344.25C337.083 344.25 353.799 337.207 369.75 319.25C369.75 319.25 379.339 325.161 385.25 329.25C391.328 333.455 400.25 340.407 400.25 340.407C377.39 364.987 344.1 383.007 324.25 381.25C292.25 381.246 273.25 364.289 244.75 341.25C216.25 318.211 200.25 305.75 179.75 305.75C145.25 305.75 80.25 360.75 80.25 360.75Z" + fill="#4E9CFF" + /> + <path + d="M406.25 213.25C399.745 217.746 389.25 224.25 389.25 224.25C389.25 224.25 395.25 237.25 395.25 254.75C395.25 272.25 389.75 287.25 389.75 287.25C389.75 287.25 399.172 293.135 405.25 297.25C411.564 301.525 421.25 308.75 421.25 308.75C421.25 308.75 432.75 284.75 432.75 254.75C432.75 224.75 421.25 202.25 421.25 202.25C421.25 202.25 412.226 209.12 406.25 213.25Z" + fill="#4E9CFF" + /> + <path + d="M256.25 209.25L285.25 185.25C285.25 185.25 308.587 202.04 325.75 204.583C343.75 207.25 362.954 197.214 380.75 185.25C407.629 167.179 430.25 144.25 430.25 144.25L456.75 170.25C456.75 170.25 383.686 249.411 325.75 242.25C287.75 237.553 256.25 209.25 256.25 209.25Z" + fill="#FF5D5F" + /> + <path + d="M186.255 130.25C223.755 130.25 255.25 162.25 255.25 162.25C255.25 162.25 246.487 169.155 240.75 173.75C234.775 178.536 225.25 186.25 225.25 186.25C225.25 186.25 208.755 168.75 186.255 168.75C177.028 168.75 165.039 174.292 152.255 185.25C142.391 193.705 132.129 204.216 125.255 217.25C119.31 228.52 116.068 241.802 115.755 255.75C115.361 273.269 121.571 291.634 131.755 306.25C138.58 316.046 146.726 323.418 155.255 329.75C166.323 337.968 177.865 343.75 186.255 343.75C195.217 343.75 203.274 340.635 209.255 337.75C218.755 332.25 226.25 325.75 226.25 325.75L255.75 350.25C255.75 350.25 243.75 362.25 227.255 371.25C216.595 376.507 202.895 381.75 186.255 381.75C169.626 381.75 150.315 372.915 132.255 359.25C120.579 350.416 109.135 339.948 100.255 327.25C85.7005 306.438 78.2004 281.118 78.2502 255.75C78.3008 230.065 86.5823 204.625 101.255 183.75C124.255 153.75 158.273 130.25 186.255 130.25Z" + fill="#FF5D5F" + /> + </svg> + ); +} diff --git a/apps/docs/components/icons/langgraph.tsx b/apps/docs/components/icons/langgraph.tsx new file mode 100644 index 0000000..64dc6a9 --- /dev/null +++ b/apps/docs/components/icons/langgraph.tsx @@ -0,0 +1,11 @@ +export function LangGraphIcon(props: React.SVGProps<SVGSVGElement>) { + return ( + <svg aria-hidden="true" {...props} viewBox="0 0 24 24" fill="currentColor"> + <path + fillRule="evenodd" + clipRule="evenodd" + d="M6.099 6H17.9C21.264 6 24 8.692 24 12s-2.736 6-6.099 6H6.1C2.736 18 0 15.308 0 12s2.736-6 6.099-6zm5.419 9.3c.148.154.367.146.561.106l.002.001c.09-.072-.038-.163-.16-.25-.074-.052-.145-.102-.166-.147.068-.08-.133-.265-.289-.408a1.52 1.52 0 01-.15-.148c-.11-.119-.155-.268-.2-.418-.03-.1-.06-.2-.11-.292-.304-.694-.653-1.383-1.143-1.97-.315-.39-.674-.74-1.033-1.09a19.384 19.384 0 01-.683-.688c-.226-.229-.362-.511-.499-.794-.114-.236-.228-.473-.396-.68-.507-.735-2.107-.936-2.342.104 0 .032-.01.052-.039.073-.13.094-.245.2-.342.327-.238.326-.274.877.022 1.17l.001-.019c.01-.147.02-.286.139-.391.228.193.576.262.841.117.32.45.422.995.525 1.54.085.456.17.912.382 1.316l.014.022c.124.203.25.41.41.587.059.089.178.184.297.279.157.125.314.25.329.359v.143c-.001.285-.002.58.184.813.103.205-.15.41-.352.385-.112.015-.233-.014-.354-.042-.165-.04-.329-.078-.462-.003-.038.04-.091.04-.145.042-.064.002-.129.004-.167.07-.008.019-.026.04-.045.063-.042.05-.087.105-.033.146l.015-.01c.082-.062.16-.12.27-.084-.014.08.039.102.092.123l.027.012a.344.344 0 01-.008.056c-.009.045-.017.088.018.127a.598.598 0 00.046-.054c.037-.046.073-.092.139-.11.144.19.289.111.471.013.206-.111.459-.248.81-.055-.135-.006-.255.01-.345.12-.023.024-.042.052-.002.084.207-.132.294-.085.375-.04.06.032.115.063.212.024l.07-.036c.155-.083.314-.166.499-.137-.139.039-.188.125-.242.218-.026.047-.054.095-.094.14-.021.021-.03.046-.007.08.29-.023.4-.095.548-.192.07-.046.15-.099.261-.154.124-.075.248-.027.368.02.13.05.255.098.371-.014.037-.033.083-.034.129-.034.016 0 .033 0 .05-.002-.037-.19-.24-.188-.448-.186-.24.003-.483.006-.475-.289.222-.149.224-.407.226-.651 0-.06 0-.117.005-.173.163.09.336.16.508.229.162.065.323.13.474.21.158.25.404.58.732.558.008-.026.016-.047.026-.073.019.004.039.008.059.014.086.02.178.044.223-.056zm6.429-2.829c.19.186.447.29.716.29.269 0 .526-.104.716-.29a.98.98 0 00.297-.7.98.98 0 00-.297-.7 1.024 1.024 0 00-1.08-.224l-.58-.831-.405.272.583.835a.978.978 0 00.05 1.348zm-1.817-2.69a1.03 1.03 0 001.056-.095.991.991 0 00.363-.507.97.97 0 00-.016-.62.994.994 0 00-.39-.488 1.028 1.028 0 00-1.298.14.987.987 0 00-.263.856.98.98 0 00.187.42c.095.125.218.225.36.294zm0 5.752a1.032 1.032 0 001.056-.095.991.991 0 00.363-.507.97.97 0 00-.016-.62.994.994 0 00-.39-.488 1.027 1.027 0 00-1.298.14.986.986 0 00-.263.856.98.98 0 00.187.42c.095.125.218.225.36.294zm.93-3.516v-.492h-1.55a.977.977 0 00-.217-.404l.584-.847-.425-.276-.583.847a1.023 1.023 0 00-1.047.23.973.973 0 00-.296.696c0 .261.107.512.296.696a1.023 1.023 0 001.047.23l.583.847.42-.276-.579-.847a.977.977 0 00.217-.404h1.55z" + /> + </svg> + ); +} diff --git a/apps/docs/components/icons/mastra.tsx b/apps/docs/components/icons/mastra.tsx new file mode 100644 index 0000000..5ebfa57 --- /dev/null +++ b/apps/docs/components/icons/mastra.tsx @@ -0,0 +1,10 @@ +export function MastraIcon(props: React.SVGProps<SVGSVGElement>) { + return ( + <svg aria-hidden="true" {...props} viewBox="0 0 34 21" fill="none"> + <path + fill="currentColor" + d="M4.5 11.7a4.5 4.5 0 1 1 0 9 4.5 4.5 0 0 1 0-9M10.4 0a4.5 4.5 0 0 1 4.4 5.5c-.3 1.4-.6 3 .2 4.2l1.3 1.8.3.2q.2 0 .3-.2l1.3-1.9c.8-1.1.5-2.7.2-4a4.5 4.5 0 1 1 8.8 0c-.3 1.3-.6 2.8 0 4l1.3 2a4.5 4.5 0 1 1-4.3 3.5c.3-1.3.6-2.8 0-4l-1.2-2h-.2L21.5 11c-.8 1.2-.5 2.8-.2 4.2a4.5 4.5 0 1 1-8.8.2q.5-2-.4-3.8l-.9-1.3q-.9-1.1-2.4-1.6A4.5 4.5 0 0 1 10.4 0" + /> + </svg> + ); +} diff --git a/apps/docs/components/icons/mcp.tsx b/apps/docs/components/icons/mcp.tsx new file mode 100644 index 0000000..65b3b07 --- /dev/null +++ b/apps/docs/components/icons/mcp.tsx @@ -0,0 +1,24 @@ +export function McpIcon(props: React.SVGProps<SVGSVGElement>) { + return ( + <svg aria-hidden="true" {...props} viewBox="0 0 180 180" fill="none"> + <path + d="M18 84.8528L85.8822 16.9706C95.2548 7.59798 110.451 7.59798 119.823 16.9706C129.196 26.3431 129.196 41.5391 119.823 50.9117L68.5581 102.177" + stroke="currentColor" + strokeWidth={12} + strokeLinecap="round" + /> + <path + d="M69.2652 101.47L119.823 50.9117C129.196 41.5391 144.392 41.5391 153.765 50.9117L154.118 51.2652C163.491 60.6378 163.491 75.8338 154.118 85.2063L92.7248 146.6C89.6006 149.724 89.6006 154.789 92.7248 157.913L105.331 170.52" + stroke="currentColor" + strokeWidth={12} + strokeLinecap="round" + /> + <path + d="M102.853 33.9411L52.6482 84.1457C43.2756 93.5183 43.2756 108.714 52.6482 118.087C62.0208 127.459 77.2167 127.459 86.5893 118.087L136.794 67.8822" + stroke="currentColor" + strokeWidth={12} + strokeLinecap="round" + /> + </svg> + ); +} diff --git a/apps/docs/components/icons/opencode.tsx b/apps/docs/components/icons/opencode.tsx new file mode 100644 index 0000000..9d8c9c3 --- /dev/null +++ b/apps/docs/components/icons/opencode.tsx @@ -0,0 +1,7 @@ +export function OpenCodeIcon(props: React.SVGProps<SVGSVGElement>) { + return ( + <svg aria-hidden="true" {...props} viewBox="0 0 24 24" fill="currentColor"> + <path d="M16 6H8v12h8V6zm4 16H4V2h16v20z" /> + </svg> + ); +} diff --git a/apps/docs/components/icons/vercel.tsx b/apps/docs/components/icons/vercel.tsx new file mode 100644 index 0000000..d29b473 --- /dev/null +++ b/apps/docs/components/icons/vercel.tsx @@ -0,0 +1,7 @@ +export function VercelIcon(props: React.SVGProps<SVGSVGElement>) { + return ( + <svg aria-hidden="true" {...props} viewBox="0 0 24 24" fill="currentColor"> + <path d="M12 0l12 20.785H0L12 0z" /> + </svg> + ); +} diff --git a/apps/docs/components/shared/cloud-button.tsx b/apps/docs/components/shared/cloud-button.tsx new file mode 100644 index 0000000..c8ca616 --- /dev/null +++ b/apps/docs/components/shared/cloud-button.tsx @@ -0,0 +1,32 @@ +import { CLOUD_URL } from "@/lib/constants"; +import { cn } from "@/lib/utils"; + +export const cloudButtonVariants = { + marketing: + "border-border hover:bg-muted hidden rounded-md border px-3 py-1.5 text-sm font-medium transition-colors md:inline-flex", + docs: "border-border/50 bg-muted/50 hover:bg-muted flex h-8 items-center rounded-lg border px-3 text-sm font-medium transition-colors", + mobile: + "border-border hover:bg-muted inline-flex w-fit rounded-md border px-3 py-1.5 text-sm font-medium transition-colors", +}; + +export function CloudButton({ + variant, + className, + onClick, +}: { + variant: keyof typeof cloudButtonVariants; + className?: string; + onClick?: () => void; +}) { + return ( + <a + href={CLOUD_URL} + target="_blank" + rel="noopener noreferrer" + onClick={onClick} + className={cn(cloudButtonVariants[variant], className)} + > + Cloud + </a> + ); +} diff --git a/apps/docs/components/shared/color-picker.tsx b/apps/docs/components/shared/color-picker.tsx new file mode 100644 index 0000000..9a70b5f --- /dev/null +++ b/apps/docs/components/shared/color-picker.tsx @@ -0,0 +1,119 @@ +"use client"; + +import { useState, useEffect, useRef } from "react"; + +import type { ThemeColor } from "@/components/builder/types"; +import { cn } from "@/lib/utils"; + +export type { ThemeColor }; + +export function ColorPicker({ + value, + onChange, +}: { + value: string; + onChange: (value: string) => void; +}) { + const [localValue, setLocalValue] = useState(value); + const timeoutRef = useRef<NodeJS.Timeout | null>(null); + + useEffect(() => { + setLocalValue(value); + }, [value]); + + const handleChange = (newValue: string) => { + setLocalValue(newValue); + + if (timeoutRef.current) { + clearTimeout(timeoutRef.current); + } + + timeoutRef.current = setTimeout(() => { + onChange(newValue); + }, 50); + }; + + return ( + <label className="relative cursor-pointer"> + <div + className="size-5 rounded-md shadow-sm ring-1 ring-black/10 ring-inset" + style={{ backgroundColor: localValue }} + /> + <input + type="color" + value={localValue} + onChange={(e) => handleChange(e.target.value)} + className="absolute inset-0 size-full cursor-pointer opacity-0" + /> + </label> + ); +} + +export function ThemeColorPicker({ + value, + onChange, +}: { + value: ThemeColor; + onChange: (value: ThemeColor) => void; +}) { + return ( + <div className="flex gap-1"> + <ColorPicker + value={value.light} + onChange={(light) => onChange({ ...value, light })} + /> + <ColorPicker + value={value.dark} + onChange={(dark) => onChange({ ...value, dark })} + /> + </div> + ); +} + +export function OptionalThemeColorPicker({ + value, + defaultValue, + onChange, +}: { + value: ThemeColor | undefined; + defaultValue: ThemeColor; + onChange: (value: ThemeColor | undefined) => void; +}) { + const displayValue = value ?? defaultValue; + const isCustom = value !== undefined; + + return ( + <div className="flex gap-1"> + <label className="relative cursor-pointer"> + <div + className={cn( + "size-5 rounded-md shadow-sm ring-1 ring-inset", + isCustom ? "ring-black/10" : "opacity-50 ring-black/5", + )} + style={{ backgroundColor: displayValue.light }} + /> + <input + type="color" + value={displayValue.light} + onChange={(e) => onChange({ ...displayValue, light: e.target.value })} + className="absolute inset-0 size-full cursor-pointer opacity-0" + /> + </label> + <label className="relative cursor-pointer"> + <div + className={cn( + "size-5 rounded-md shadow-sm ring-1 ring-inset", + isCustom ? "ring-black/10" : "opacity-50 ring-black/5", + )} + style={{ backgroundColor: displayValue.dark }} + /> + <input + type="color" + value={displayValue.dark} + onChange={(e) => onChange({ ...displayValue, dark: e.target.value })} + className="absolute inset-0 size-full cursor-pointer opacity-0" + /> + </label> + </div> + ); +} diff --git a/apps/docs/components/shared/footer.tsx b/apps/docs/components/shared/footer.tsx new file mode 100644 index 0000000..6c4bd6e --- /dev/null +++ b/apps/docs/components/shared/footer.tsx @@ -0,0 +1,178 @@ +import type { FC, ReactNode } from "react"; +import Image from "next/image"; +import Link from "next/link"; +import { ArrowUpRight } from "lucide-react"; +import { GitHubIcon } from "@/components/icons/github"; +import { DiscordIcon } from "@/components/icons/discord"; +import { ThemeToggle } from "@/components/shared/theme-toggle"; +import { CLOUD_URL, PRODUCTS } from "@/lib/constants"; + +type FooterLinkItem = { + label: string; + href: string; + external?: boolean; +}; + +const FOOTER_LINKS: Record<string, FooterLinkItem[]> = { + Products: [ + { + label: "Cloud", + href: CLOUD_URL, + external: true, + }, + { label: "Playground", href: "/playground" }, + ...PRODUCTS.map((p) => ({ + label: p.label, + href: p.href, + ...(p.external && { external: true }), + })), + ], + Resources: [ + { label: "Documentation", href: "/docs" }, + { label: "Examples", href: "/examples" }, + { label: "Showcase", href: "/showcase" }, + { label: "Traction", href: "/traction" }, + { label: "Packages", href: "/packages" }, + { label: "Blog", href: "/blog" }, + ], + Company: [ + { label: "Careers", href: "/careers" }, + { + label: "Contact Sales", + href: "https://cal.com/simon-farshid/assistant-ui", + external: true, + }, + { label: "Pricing", href: "/pricing" }, + { label: "Brand", href: "/brand" }, + ], + Legal: [ + { + label: "Terms of Service", + href: "/terms-of-service", + }, + { + label: "Privacy Policy", + href: "/privacy-policy", + }, + ], +}; + +export function Footer(): React.ReactElement { + return ( + <footer className="py-10 md:py-16"> + <div className="mx-auto flex w-full max-w-7xl flex-col gap-10 px-4 md:flex-row md:justify-between"> + <div className="grid grid-cols-2 gap-x-12 gap-y-8 sm:grid-cols-4 md:order-2 lg:gap-x-16"> + {Object.entries(FOOTER_LINKS).map(([category, links]) => ( + <div key={category} className="flex flex-col gap-3"> + <p className="text-sm font-medium">{category}</p> + {links.map((link) => ( + <FooterLink + key={link.href} + href={link.href} + {...(link.external && { external: true })} + > + {link.label} + </FooterLink> + ))} + </div> + ))} + </div> + + <div className="flex flex-col gap-4 md:order-1"> + <Link href="/" className="flex items-center gap-2"> + <Image + src="/favicon/icon.svg" + alt="logo" + width={24} + height={24} + className="size-6 dark:hue-rotate-180 dark:invert" + /> + <span className="text-xl font-medium">assistant-ui</span> + </Link> + + <div className="flex items-center gap-3"> + <a + href="https://x.com/assistantui" + target="_blank" + rel="noopener noreferrer" + className="text-muted-foreground hover:text-foreground transition-colors" + aria-label="X (Twitter)" + > + <svg + aria-hidden="true" + className="size-5" + viewBox="0 0 24 24" + fill="currentColor" + > + <path d="M18.244 2.25h3.308l-7.227 8.26 8.502 11.24H16.17l-5.214-6.817L4.99 21.75H1.68l7.73-8.835L1.254 2.25H8.08l4.713 6.231zm-1.161 17.52h1.833L7.084 4.126H5.117z" /> + </svg> + </a> + <a + href="https://github.com/assistant-ui" + target="_blank" + rel="noopener noreferrer" + className="text-muted-foreground hover:text-foreground transition-colors" + aria-label="GitHub" + > + <GitHubIcon className="size-5" /> + </a> + <a + href="https://discord.gg/S9dwgCNEFs" + target="_blank" + rel="noopener noreferrer" + className="text-muted-foreground hover:text-foreground transition-colors" + aria-label="Discord" + > + <DiscordIcon className="size-5" /> + </a> + </div> + + <div className="mt-auto flex flex-col gap-3"> + <div className="-ml-2"> + <ThemeToggle /> + </div> + <a + href="https://agentbase.dev" + target="_blank" + rel="noopener noreferrer" + className="text-muted-foreground hover:text-foreground text-sm transition-colors" + > + © {new Date().getFullYear()} AgentbaseAI Inc. + </a> + </div> + </div> + </div> + </footer> + ); +} + +const FooterLink: FC<{ + href: string; + external?: boolean; + children: ReactNode; +}> = ({ href, external, children }) => { + const isExternal = external ?? href.startsWith("http"); + + if (isExternal) { + return ( + <a + className="text-muted-foreground hover:text-foreground inline-flex items-center gap-1 text-sm transition-colors" + href={href} + target="_blank" + rel="noopener noreferrer" + > + {children} + <ArrowUpRight className="size-3 opacity-40" /> + </a> + ); + } + + return ( + <Link + className="text-muted-foreground hover:text-foreground text-sm transition-colors" + href={href} + > + {children} + </Link> + ); +}; diff --git a/apps/docs/components/shared/header-brand-link.tsx b/apps/docs/components/shared/header-brand-link.tsx new file mode 100644 index 0000000..53eace7 --- /dev/null +++ b/apps/docs/components/shared/header-brand-link.tsx @@ -0,0 +1,112 @@ +"use client"; + +import type { ReactElement } from "react"; +import Image from "next/image"; +import Link from "next/link"; +import { DownloadIcon, SquareDashedIcon, TypeIcon } from "lucide-react"; +import { toast } from "sonner"; +import { + ContextMenu, + ContextMenuContent, + ContextMenuItem, + ContextMenuSeparator, + ContextMenuTrigger, +} from "@/components/ui/context-menu"; +import { cn } from "@/lib/utils"; + +type BrandAssetsMenuProps = { + children: ReactElement; +}; + +type HeaderBrandLinkProps = { + className?: string; + labelClassName?: string; +}; + +export function HeaderBrandLink({ + className, + labelClassName, +}: HeaderBrandLinkProps) { + return ( + <BrandAssetsMenu> + <Link + href="/" + className={cn("flex shrink-0 items-center gap-2", className)} + > + <Image + src="/favicon/icon.svg" + alt="assistant-ui logo" + width={18} + height={18} + className="dark:hue-rotate-180 dark:invert" + /> + <span className={cn("font-medium tracking-tight", labelClassName)}> + assistant-ui + </span> + </Link> + </BrandAssetsMenu> + ); +} + +function BrandAssetsMenu({ children }: BrandAssetsMenuProps) { + return ( + <ContextMenu modal={false}> + <ContextMenuTrigger asChild>{children}</ContextMenuTrigger> + + <ContextMenuContent className="w-56"> + <ContextMenuItem + onSelect={() => void copySvg("Logomark as SVG", "/favicon/icon.svg")} + > + <Image + src="/favicon/icon.svg" + alt="" + width={16} + height={16} + className="size-4 dark:hue-rotate-180 dark:invert" + /> + Copy Logomark as SVG + </ContextMenuItem> + + <ContextMenuItem + onSelect={() => + void copySvg("Logotype as SVG", "/brand/logotype.svg") + } + > + <TypeIcon className="size-4" /> + Copy Logotype as SVG + </ContextMenuItem> + + <ContextMenuSeparator /> + + <ContextMenuItem asChild> + <Link href="/brand"> + <SquareDashedIcon className="size-4" /> + Brand Guidelines + </Link> + </ContextMenuItem> + + <ContextMenuItem asChild> + <a href="/assistant-ui-brand.zip" download> + <DownloadIcon className="size-4" /> + Download Brand Assets + </a> + </ContextMenuItem> + </ContextMenuContent> + </ContextMenu> + ); +} + +// ClipboardItem wraps the pending fetch so the clipboard write starts inside +// the user activation window; Safari rejects a write that awaits the fetch. +const copySvg = async (label: string, path: string) => { + try { + const svg = fetch(path).then(async (response) => { + if (!response.ok) throw new Error("Failed to load SVG"); + return new Blob([await response.text()], { type: "text/plain" }); + }); + await navigator.clipboard.write([new ClipboardItem({ "text/plain": svg })]); + toast.success(`${label} copied`); + } catch { + toast.error(`Failed to copy ${label.toLowerCase()}`); + } +}; diff --git a/apps/docs/components/shared/header.tsx b/apps/docs/components/shared/header.tsx new file mode 100644 index 0000000..cb65f16 --- /dev/null +++ b/apps/docs/components/shared/header.tsx @@ -0,0 +1,291 @@ +"use client"; + +import { useState, useEffect } from "react"; +import Link from "next/link"; +import { Menu, X, ArrowUpRight, ArrowRight, Search } from "lucide-react"; +import { usePersistentBoolean } from "@/hooks/use-persistent-boolean"; +import { usePathname } from "next/navigation"; +import { cn } from "@/lib/utils"; +import { formatCompact } from "@/lib/format"; +import { SearchDialog } from "./search-dialog"; +import { GitHubIcon } from "@/components/icons/github"; +import { DiscordIcon } from "@/components/icons/discord"; +import { NAV_ITEMS } from "@/lib/constants"; +import { + CloudButton, + cloudButtonVariants, +} from "@/components/shared/cloud-button"; +import { useAssistantPanel } from "@/components/docs/assistant/context"; +import { NavItems } from "@/components/shared/nav-items"; +import { HeaderBrandLink } from "@/components/shared/header-brand-link"; + +function SearchButton({ onToggle }: { onToggle: () => void }) { + useEffect(() => { + const down = (e: KeyboardEvent) => { + if (e.key === "k" && (e.metaKey || e.ctrlKey)) { + e.preventDefault(); + e.stopPropagation(); + onToggle(); + } + }; + document.addEventListener("keydown", down, true); + return () => document.removeEventListener("keydown", down, true); + }, [onToggle]); + + return ( + <button + type="button" + onClick={onToggle} + className="text-muted-foreground hover:text-foreground flex size-8 cursor-pointer items-center justify-center transition-colors" + aria-label="Search (⌘K)" + > + <Search className="size-4" /> + </button> + ); +} + +function HiringBanner({ onDismiss }: { onDismiss: () => void }) { + return ( + <div className="relative flex justify-center"> + <div className="border-border/50 bg-background/60 relative flex items-center gap-3 rounded-full border px-4 py-1.5 backdrop-blur-md"> + <Link + href="/careers" + className="group inline-flex items-center gap-1.5 text-xs" + > + <span className="shimmer text-muted-foreground group-hover:text-foreground transition-colors"> + We're hiring. Build the future of agentic UI. + </span> + <ArrowRight className="text-muted-foreground group-hover:text-foreground size-3 transition-all group-hover:translate-x-0.5" /> + </Link> + <button + type="button" + aria-label="Dismiss" + onClick={onDismiss} + className="text-muted-foreground hover:bg-muted hover:text-foreground flex size-5 items-center justify-center rounded-full transition-colors" + > + <X className="size-3" /> + </button> + </div> + </div> + ); +} + +export function Header() { + const [mobileMenuOpen, setMobileMenuOpen] = useState(false); + const [searchOpen, setSearchOpen] = useState(false); + const [mounted, setMounted] = useState(false); + const [stars, setStars] = useState<number | null>(null); + const pathname = usePathname(); + const { toggle } = useAssistantPanel(); + const [dismissed, setDismissed] = usePersistentBoolean( + "homepage-hiring-banner-dismissed", + ); + const [visited, setVisited] = usePersistentBoolean("homepage-visited"); + const [returningVisitor, setReturningVisitor] = useState(false); + + useEffect(() => { + setMounted(true); + }, []); + + useEffect(() => { + if (pathname !== "/") return; + // Show the banner only from the second homepage visit onward. + setReturningVisitor(visited); + if (!visited) setVisited(true); + // oxlint-disable-next-line react/exhaustive-deps + }, [pathname]); + + useEffect(() => { + fetch("/api/github/repo") + .then((res) => (res.ok ? res.json() : null)) + .then((data) => { + if (data && typeof data.stars === "number") { + setStars(data.stars); + } + }) + .catch(console.error); + }, []); + + const isHome = pathname === "/"; + const showBanner = mounted && isHome && returningVisitor && !dismissed; + + return ( + <header className="sticky top-0 z-50 w-full"> + <div className="from-background pointer-events-none absolute inset-x-0 top-0 h-14 bg-linear-to-b to-transparent mask-[linear-gradient(to_bottom,black_75%,transparent)] backdrop-blur-xl" /> + <div className="relative mx-auto flex h-12 w-full max-w-7xl items-center justify-between px-4"> + <div className="flex items-center gap-4"> + <HeaderBrandLink /> + + <nav className="hidden items-center md:flex"> + <NavItems items={NAV_ITEMS} /> + </nav> + </div> + + <div className="flex items-center gap-1"> + {!isHome && ( + <> + <SearchButton onToggle={() => setSearchOpen((prev) => !prev)} /> + <SearchDialog open={searchOpen} onOpenChange={setSearchOpen} /> + </> + )} + + {isHome && ( + <button + type="button" + onClick={toggle} + className={cloudButtonVariants.marketing} + aria-label="Ask AI (⌘I)" + > + Ask AI + </button> + )} + <CloudButton variant="marketing" /> + + <a + href="https://github.com/assistant-ui/assistant-ui" + target="_blank" + rel="noopener noreferrer" + className="text-muted-foreground hover:text-foreground ml-2 hidden items-center gap-2.5 transition-colors sm:flex" + aria-label="GitHub" + > + <GitHubIcon className="size-4" /> + {stars !== null && ( + <span className="text-sm tabular-nums"> + {formatCompact(stars)} + </span> + )} + </a> + + {!isHome && ( + <a + href="https://discord.gg/S9dwgCNEFs" + target="_blank" + rel="noopener noreferrer" + className="text-muted-foreground hover:text-foreground hidden size-8 items-center justify-center transition-colors sm:flex" + aria-label="Discord" + > + <DiscordIcon className="size-4" /> + </a> + )} + + <button + type="button" + onClick={() => setMobileMenuOpen(!mobileMenuOpen)} + className="text-muted-foreground hover:text-foreground flex size-8 items-center justify-center transition-colors md:hidden" + aria-label="Toggle menu" + > + {mobileMenuOpen ? ( + <X className="size-5" /> + ) : ( + <Menu className="size-5" /> + )} + </button> + </div> + </div> + + <div + className={cn( + "bg-background fixed inset-x-0 top-12 bottom-0 z-40 transition-opacity duration-200 md:hidden", + mobileMenuOpen ? "opacity-100" : "pointer-events-none opacity-0", + )} + > + <nav className="flex h-full flex-col gap-1 overflow-y-auto px-4 pt-4"> + {NAV_ITEMS.map((item) => { + if (item.type === "link") { + return item.href.startsWith("http") ? ( + <a + key={item.href} + href={item.href} + target="_blank" + rel="noopener noreferrer" + onClick={() => setMobileMenuOpen(false)} + className="text-foreground py-3 text-lg transition-colors" + > + {item.label} + </a> + ) : ( + <Link + key={item.href} + href={item.href} + onClick={() => setMobileMenuOpen(false)} + className="text-foreground py-3 text-lg transition-colors" + > + {item.label} + </Link> + ); + } + + const groups = item.groups; + + return ( + <div key={item.label} className="flex flex-col"> + {groups.map((group) => ( + <div key={group.label} className="flex flex-col"> + <span className="text-muted-foreground py-3 text-sm"> + {group.label} + </span> + {group.items.map((link) => + link.external ? ( + <a + key={link.href} + href={link.href} + target="_blank" + rel="noopener noreferrer" + onClick={() => setMobileMenuOpen(false)} + className="text-foreground flex items-center gap-1.5 py-2 pl-4 text-lg transition-colors" + > + {link.label} + <ArrowUpRight className="size-3.5 opacity-40" /> + </a> + ) : ( + <Link + key={link.href} + href={link.href} + onClick={() => setMobileMenuOpen(false)} + className="text-foreground py-2 pl-4 text-lg transition-colors" + > + {link.label} + </Link> + ), + )} + </div> + ))} + </div> + ); + })} + + <div className="mt-auto flex flex-col gap-4 border-t py-6"> + <CloudButton + variant="mobile" + onClick={() => setMobileMenuOpen(false)} + /> + <div className="flex gap-4"> + <a + href="https://github.com/assistant-ui/assistant-ui" + target="_blank" + rel="noopener noreferrer" + className="text-muted-foreground hover:text-foreground flex items-center gap-2 transition-colors" + > + <GitHubIcon className="size-5" /> + </a> + <a + href="https://discord.gg/S9dwgCNEFs" + target="_blank" + rel="noopener noreferrer" + className="text-muted-foreground hover:text-foreground flex items-center gap-2 transition-colors" + > + <DiscordIcon className="size-5" /> + </a> + </div> + </div> + </nav> + </div> + + {showBanner && ( + <div className="absolute top-full right-0 left-0"> + <HiringBanner onDismiss={() => setDismissed(true)} /> + </div> + )} + </header> + ); +} diff --git a/apps/docs/components/shared/more-dropdown.tsx b/apps/docs/components/shared/more-dropdown.tsx new file mode 100644 index 0000000..bd440c2 --- /dev/null +++ b/apps/docs/components/shared/more-dropdown.tsx @@ -0,0 +1,55 @@ +import { ArrowUpRight, ChevronDown } from "lucide-react"; +import Link from "next/link"; +import { + HoverCard, + HoverCardContent, + HoverCardTrigger, +} from "@/components/ui/hover-card"; + +interface MoreDropdownItem { + label: string; + href: string; + external?: boolean; +} + +export function MoreDropdown({ items }: { items: MoreDropdownItem[] }) { + return ( + <HoverCard openDelay={100} closeDelay={100}> + <HoverCardTrigger asChild> + <button + type="button" + className="text-muted-foreground hover:text-foreground flex items-center gap-1 px-3 py-1.5 text-sm transition-colors" + > + More + <ChevronDown className="size-3" /> + </button> + </HoverCardTrigger> + <HoverCardContent className="w-40 rounded-xl p-1 shadow-xs" align="end"> + <div className="flex flex-col"> + {items.map((item) => + item.external ? ( + <a + key={item.href} + href={item.href} + target="_blank" + rel="noopener noreferrer" + className="hover:bg-muted flex items-center gap-1.5 rounded-md px-2.5 py-1.5 text-sm transition-colors" + > + {item.label} + <ArrowUpRight className="size-3 opacity-40" /> + </a> + ) : ( + <Link + key={item.href} + href={item.href} + className="hover:bg-muted rounded-md px-2.5 py-1.5 text-sm transition-colors" + > + {item.label} + </Link> + ), + )} + </div> + </HoverCardContent> + </HoverCard> + ); +} diff --git a/apps/docs/components/shared/nav-items.tsx b/apps/docs/components/shared/nav-items.tsx new file mode 100644 index 0000000..f1c851b --- /dev/null +++ b/apps/docs/components/shared/nav-items.tsx @@ -0,0 +1,96 @@ +import Link from "next/link"; +import { ArrowUpRight } from "lucide-react"; +import { + HoverCard, + HoverCardContent, + HoverCardTrigger, +} from "@/components/ui/hover-card"; +import type { DropdownItem, NavItem } from "@/lib/constants"; + +function DropdownLink({ link }: { link: DropdownItem }) { + const className = + "hover:bg-muted flex flex-col rounded-md px-2 py-1.5 transition-colors"; + + if (link.external) { + return ( + <a + href={link.href} + target="_blank" + rel="noopener noreferrer" + className={className} + > + <span className="flex items-center gap-1.5 text-sm"> + {link.label} + <ArrowUpRight className="size-3 opacity-40" /> + </span> + <span className="text-muted-foreground text-xs"> + {link.description} + </span> + </a> + ); + } + + return ( + <Link href={link.href} className={className}> + <span className="text-sm">{link.label}</span> + <span className="text-muted-foreground text-xs">{link.description}</span> + </Link> + ); +} + +export function NavItems({ + items, + megaAlign = "start", +}: { + items: NavItem[]; + megaAlign?: "start" | "end"; +}) { + return items.map((item) => { + if (item.type === "link") { + return ( + <Link + key={item.href} + href={item.href} + className="text-muted-foreground hover:text-foreground px-3 py-1.5 text-sm transition-colors" + > + {item.label} + </Link> + ); + } + + const trigger = ( + <HoverCardTrigger asChild> + <button + type="button" + className="text-muted-foreground hover:text-foreground px-3 py-1.5 text-sm transition-colors" + > + {item.label} + </button> + </HoverCardTrigger> + ); + + return ( + <HoverCard key={item.label} openDelay={100} closeDelay={100}> + {trigger} + <HoverCardContent + align={megaAlign} + alignOffset={megaAlign === "end" ? -130 : -52} + className="w-[44rem] rounded-xl p-3 shadow-xs" + > + <div className="grid grid-cols-3 gap-2"> + {item.groups.map((group) => ( + <div key={group.label} className="flex flex-col gap-1"> + <span className="text-muted-foreground px-2 pb-1 text-xs font-medium"> + {group.label} + </span> + {group.items.map((link) => ( + <DropdownLink key={link.href} link={link} /> + ))} + </div> + ))} + </div> + </HoverCardContent> + </HoverCard> + ); + }); +} diff --git a/apps/docs/components/shared/search-dialog.tsx b/apps/docs/components/shared/search-dialog.tsx new file mode 100644 index 0000000..1f42539 --- /dev/null +++ b/apps/docs/components/shared/search-dialog.tsx @@ -0,0 +1,446 @@ +"use client"; + +import { useEffect, useState, useCallback, useMemo, useRef } from "react"; +import { useRouter } from "next/navigation"; +import { + Search, + CornerDownLeft, + ArrowUp, + ArrowDown, + FileText, + Hash, + Text, + Sparkles, +} from "lucide-react"; +import { useDocsSearch } from "fumadocs-core/search/client"; +import { + Dialog, + DialogContent, + DialogHeader, + DialogTitle, + DialogDescription, +} from "@/components/ui/dialog"; +import { cn } from "@/lib/utils"; +import { analytics } from "@/lib/analytics"; +import { useGlobalAskAI } from "@/components/docs/assistant/context"; + +interface HighlightSegment { + type: "text"; + content: string; + styles?: { + highlight?: boolean; + }; +} + +interface SearchResult { + id: string; + url: string; + content: string; + type: string; + contentWithHighlights?: HighlightSegment[]; +} + +interface SearchDialogProps { + open: boolean; + onOpenChange: (open: boolean) => void; +} + +function HighlightedText({ + segments, + fallback, +}: { + segments: HighlightSegment[] | undefined; + fallback: string; +}) { + if (!segments || segments.length === 0) { + return <>{fallback}</>; + } + + return ( + <> + {segments.map((segment, i) => ( + <span + key={i} + className={cn( + segment.styles?.highlight && + "bg-primary/15 text-primary rounded px-0.5", + )} + > + {segment.content} + </span> + ))} + </> + ); +} + +function formatBreadcrumb(url: string): string { + const path = url.split("#")[0] ?? ""; + const segments = path.split("/").filter(Boolean); + + if (segments.length === 0) return "Home"; + + return segments + .map((segment) => + segment + .split("-") + .map((word) => word.charAt(0).toUpperCase() + word.slice(1)) + .join(" "), + ) + .join(" / "); +} + +function getResultTitle(item: SearchResult): string { + if (item.type === "page") { + const path = item.url.split("#")[0] ?? ""; + const segments = path.split("/").filter(Boolean); + const lastSegment = segments[segments.length - 1] ?? "Home"; + return lastSegment + .split("-") + .map((word) => word.charAt(0).toUpperCase() + word.slice(1)) + .join(" "); + } + return item.content.replace(/<\/?mark>/g, ""); +} + +function parseHighlightedContent(content: string): HighlightSegment[] { + const segments: HighlightSegment[] = []; + const regex = /<mark>([\s\S]*?)<\/mark>/g; + let lastIndex = 0; + let match: RegExpExecArray | null; + + while ((match = regex.exec(content)) !== null) { + if (match.index > lastIndex) { + segments.push({ + type: "text", + content: content.slice(lastIndex, match.index), + }); + } + segments.push({ + type: "text", + content: match[1] ?? "", + styles: { highlight: true }, + }); + lastIndex = regex.lastIndex; + } + + if (lastIndex < content.length) { + segments.push({ type: "text", content: content.slice(lastIndex) }); + } + + return segments; +} + +function getResultSegments(item: SearchResult): HighlightSegment[] | undefined { + if (item.contentWithHighlights && item.contentWithHighlights.length > 0) { + return item.contentWithHighlights; + } + if (item.type === "page") return undefined; + return parseHighlightedContent(item.content); +} + +function ResultIcon({ type }: { type: string }) { + switch (type) { + case "page": + return <FileText className="size-4" />; + case "heading": + return <Hash className="size-4" />; + default: + return <Text className="size-4" />; + } +} + +function getPageUrl(url: string): string { + return url.split("#")[0] ?? ""; +} + +interface GroupedResult { + pageUrl: string; + items: SearchResult[]; +} + +export function SearchDialog({ open, onOpenChange }: SearchDialogProps) { + const router = useRouter(); + const { setSearch, query } = useDocsSearch({ type: "fetch" }); + const [inputValue, setInputValue] = useState(""); + const [selectedIndex, setSelectedIndex] = useState(0); + const listRef = useRef<HTMLDivElement>(null); + const askAIFn = useGlobalAskAI(); + + const latestResults = useMemo((): SearchResult[] => { + if (!query.data || query.data === "empty") return []; + return query.data as SearchResult[]; + }, [query.data]); + + const staleResultsRef = useRef<SearchResult[]>([]); + useEffect(() => { + if (latestResults.length > 0) { + staleResultsRef.current = latestResults; + } + }, [latestResults]); + const results = + latestResults.length > 0 ? latestResults : staleResultsRef.current; + + const groupedResults = useMemo((): GroupedResult[] => { + const groups: GroupedResult[] = []; + let currentGroup: GroupedResult | null = null; + + for (const item of results) { + const pageUrl = getPageUrl(item.url); + + if (!currentGroup || currentGroup.pageUrl !== pageUrl) { + currentGroup = { pageUrl, items: [item] }; + groups.push(currentGroup); + } else { + currentGroup.items.push(item); + } + } + + return groups; + }, [results]); + + const showAskAI = !!askAIFn && inputValue.length > 0; + + const lastTrackedQuery = useRef(""); + const searchTrackingTimeout = useRef<ReturnType<typeof setTimeout> | null>( + null, + ); + const resultsLengthRef = useRef(0); + useEffect(() => { + resultsLengthRef.current = results.length; + }, [results.length]); + + useEffect(() => { + if (open) { + setInputValue(""); + setSelectedIndex(0); + lastTrackedQuery.current = ""; + staleResultsRef.current = []; + } + }, [open]); + + useEffect(() => { + if (inputValue.length === 0) { + setSearch(""); + staleResultsRef.current = []; + return; + } + const timer = setTimeout(() => { + setSearch(inputValue); + }, 150); + return () => clearTimeout(timer); + }, [inputValue, setSearch]); + + useEffect(() => { + void latestResults; + setSelectedIndex(0); + }, [latestResults]); + + useEffect(() => { + if (listRef.current && results.length > 0) { + const selectedElement = listRef.current.querySelector( + `[data-index="${selectedIndex}"]`, + ); + selectedElement?.scrollIntoView({ block: "nearest" }); + } + }, [selectedIndex, results.length]); + + const handleAskAI = useCallback(() => { + if (!askAIFn) return; + analytics.search.askAITriggered(inputValue); + onOpenChange(false); + askAIFn(inputValue); + }, [askAIFn, inputValue, onOpenChange]); + + const handleSelect = useCallback( + (url: string) => { + if (searchTrackingTimeout.current) { + clearTimeout(searchTrackingTimeout.current); + if (inputValue.length >= 2 && inputValue !== lastTrackedQuery.current) { + lastTrackedQuery.current = inputValue; + const resultCount = resultsLengthRef.current; + if (resultCount === 0) analytics.search.noResults(inputValue); + else analytics.search.querySubmitted(inputValue, resultCount); + } + } + + const position = results.findIndex((r) => r.url === url); + analytics.search.resultClicked(inputValue, url, position); + onOpenChange(false); + router.push(url); + }, + [onOpenChange, router, results, inputValue], + ); + + useEffect(() => { + if (searchTrackingTimeout.current) { + clearTimeout(searchTrackingTimeout.current); + } + + if ( + !inputValue || + inputValue.length < 2 || + query.isLoading || + inputValue === lastTrackedQuery.current + ) + return; + + searchTrackingTimeout.current = setTimeout(() => { + lastTrackedQuery.current = inputValue; + const resultCount = resultsLengthRef.current; + if (resultCount === 0) analytics.search.noResults(inputValue); + else analytics.search.querySubmitted(inputValue, resultCount); + }, 500); + + return () => { + if (searchTrackingTimeout.current) { + clearTimeout(searchTrackingTimeout.current); + } + }; + }, [inputValue, query.isLoading]); + + const handleKeyDown = useCallback( + (e: React.KeyboardEvent) => { + if (e.key === "ArrowDown") { + e.preventDefault(); + setSelectedIndex((i) => Math.min(i + 1, results.length - 1)); + } else if (e.key === "ArrowUp") { + e.preventDefault(); + setSelectedIndex((i) => Math.max(i - 1, 0)); + } else if (e.key === "Enter" && results[selectedIndex]) { + e.preventDefault(); + handleSelect(results[selectedIndex].url); + } + }, + [results, selectedIndex, handleSelect], + ); + + return ( + <Dialog open={open} onOpenChange={onOpenChange}> + <DialogContent + className="gap-0 overflow-hidden rounded-3xl border-none p-2 sm:max-w-xl" + showCloseButton={false} + > + <DialogHeader className="sr-only"> + <DialogTitle>Search</DialogTitle> + <DialogDescription>Search documentation</DialogDescription> + </DialogHeader> + <div className="overflow-hidden rounded-2xl border"> + <div className="border-border/50 flex items-center gap-2.5 border-b px-3"> + <Search className="text-muted-foreground size-4" /> + <input + type="text" + placeholder="Search documentation..." + value={inputValue} + onChange={(e) => { + setInputValue(e.target.value); + }} + onKeyDown={handleKeyDown} + className="placeholder:text-muted-foreground/60 h-11 flex-1 bg-transparent text-sm outline-none" + autoFocus + /> + {showAskAI && ( + <button + type="button" + onClick={handleAskAI} + className="hover:bg-accent hidden shrink-0 items-center gap-1.5 rounded-lg px-2 py-1 text-pink-500 transition-colors sm:flex" + > + <Sparkles className="size-3.5" /> + <span className="text-xs font-medium">Ask AI</span> + </button> + )} + </div> + + <div + ref={listRef} + className="h-[min(400px,90vh)] overflow-x-hidden overflow-y-auto overscroll-contain" + > + {inputValue.length === 0 ? ( + <div className="flex h-full flex-col items-center justify-center gap-3 px-4"> + <div className="text-muted-foreground/60 flex items-center gap-6"> + <span className="flex items-center gap-1.5 text-sm"> + <ArrowUp className="size-3" /> + <ArrowDown className="size-3" /> + <span>navigate</span> + </span> + <span className="flex items-center gap-1.5 text-sm"> + <CornerDownLeft className="size-3" /> + <span>select</span> + </span> + </div> + </div> + ) : query.isLoading && results.length === 0 ? ( + <div className="flex h-full items-center justify-center"> + <div className="text-muted-foreground/60 flex items-center gap-2"> + <div className="size-1 animate-pulse rounded-full bg-current" /> + <div className="size-1 animate-pulse rounded-full bg-current [animation-delay:150ms]" /> + <div className="size-1 animate-pulse rounded-full bg-current [animation-delay:300ms]" /> + </div> + </div> + ) : results.length === 0 ? ( + <div className="flex h-full flex-col items-center justify-center gap-1 px-4"> + <p className="text-muted-foreground/60 text-sm"> + No results for “{inputValue}” + </p> + </div> + ) : ( + <div className="p-2"> + {(() => { + let flatIndex = 0; + return groupedResults.map((group) => ( + <div key={group.pageUrl} className="mb-1 last:mb-0"> + {group.items.map((item, itemIndex) => { + const currentIndex = flatIndex++; + const isSelected = currentIndex === selectedIndex; + const title = getResultTitle(item); + const isNested = itemIndex > 0 || item.type !== "page"; + const showBreadcrumb = itemIndex === 0; + + return ( + <button + type="button" + key={item.id} + data-index={currentIndex} + onClick={() => handleSelect(item.url)} + onMouseEnter={() => setSelectedIndex(currentIndex)} + className={cn( + "group flex w-full cursor-pointer items-center gap-3 rounded-lg py-2 pr-3 text-left transition-colors", + isSelected ? "bg-accent" : "hover:bg-accent/50", + isNested ? "pl-9" : "pl-3", + )} + > + <div className="text-muted-foreground shrink-0"> + <ResultIcon type={item.type} /> + </div> + <div className="flex min-w-0 flex-1 flex-col"> + <span className="text-foreground truncate text-sm font-medium"> + <HighlightedText + segments={getResultSegments(item)} + fallback={title} + /> + </span> + {showBreadcrumb && ( + <span className="text-muted-foreground truncate text-xs"> + {formatBreadcrumb(item.url)} + </span> + )} + </div> + <CornerDownLeft + className={cn( + "text-muted-foreground size-3.5 shrink-0 transition-opacity", + isSelected ? "opacity-60" : "opacity-0", + )} + /> + </button> + ); + })} + </div> + )); + })()} + </div> + )} + </div> + </div> + </DialogContent> + </Dialog> + ); +} diff --git a/apps/docs/components/shared/sub-project-layout.tsx b/apps/docs/components/shared/sub-project-layout.tsx new file mode 100644 index 0000000..d2de8ff --- /dev/null +++ b/apps/docs/components/shared/sub-project-layout.tsx @@ -0,0 +1,166 @@ +"use client"; + +import { type ReactNode, useMemo } from "react"; +import Link from "next/link"; +import { usePathname, useRouter } from "next/navigation"; +import { cn } from "@/lib/utils"; +import { GitHubIcon } from "@/components/icons/github"; +import { Select } from "@/components/assistant-ui/select"; +import { SUB_PROJECTS } from "@/lib/constants"; +import { ThemeToggle } from "./theme-toggle"; +import { HeaderBrandLink } from "./header-brand-link"; + +interface BreadcrumbItem { + label: string; + href: string; + shimmer?: boolean; +} + +interface SubProjectLayoutProps { + name: string; + githubPath: string; + breadcrumbs?: BreadcrumbItem[]; + children: ReactNode; + hideFooter?: boolean; + fullHeight?: boolean; +} + +export function SubProjectLayout({ + name, + githubPath, + breadcrumbs: breadcrumbsOverride, + children, + hideFooter = false, + fullHeight = false, +}: SubProjectLayoutProps): React.ReactElement { + const pathname = usePathname(); + const router = useRouter(); + + const breadcrumbs = useMemo(() => { + if (breadcrumbsOverride) { + return breadcrumbsOverride; + } + + const basePath = `/${name}`; + if (!pathname.startsWith(basePath) || pathname === basePath) { + return []; + } + + const subPath = pathname.slice(basePath.length); + const segments = subPath.split("/").filter(Boolean); + + return segments.map((segment, index) => ({ + label: segment, + href: `${basePath}/${segments.slice(0, index + 1).join("/")}`, + shimmer: false, + })); + }, [pathname, name, breadcrumbsOverride]); + + return ( + <div + className={cn( + "flex flex-col", + fullHeight ? "h-svh overflow-hidden" : "min-h-screen", + )} + > + <header + className={cn("z-50 w-full shrink-0", !fullHeight && "sticky top-0")} + > + {!fullHeight && ( + <div className="from-background pointer-events-none absolute inset-x-0 top-0 h-14 bg-linear-to-b to-transparent mask-[linear-gradient(to_bottom,black_75%,transparent)] backdrop-blur-xl" /> + )} + <div + className={cn( + "relative flex h-12 w-full items-center justify-between px-4", + !fullHeight && "mx-auto max-w-7xl", + )} + > + <div className="flex min-w-0 items-center"> + <HeaderBrandLink labelClassName="hidden sm:inline" /> + <span className="text-muted-foreground/40 ml-3">/</span> + <Select + value={name} + onValueChange={(value) => router.push(`/${value}`)} + options={SUB_PROJECTS.toSorted((a, b) => + a.slug.localeCompare(b.slug), + ).map((p) => ({ + value: p.slug, + label: + p.slug === "tw-shimmer" ? ( + <span className="shimmer">{p.label}</span> + ) : ( + p.label + ), + textValue: p.slug, + }))} + /> + <span className="hidden sm:contents"> + {breadcrumbs?.map((item, index) => ( + <span key={item.href} className="contents"> + <span className="text-muted-foreground/40 mr-3 ml-1">/</span> + <Link + href={item.href} + className={cn( + "hover:text-foreground mr-2 text-sm transition-colors", + index === breadcrumbs.length - 1 + ? "text-foreground" + : "text-muted-foreground", + item.shimmer && "shimmer", + )} + > + {item.label} + </Link> + </span> + ))} + </span> + </div> + + <div className="flex items-center gap-2"> + <div + data-sub-project-header-portal + className="flex items-center gap-1" + /> + <a + href={githubPath} + target="_blank" + rel="noopener noreferrer" + className="text-muted-foreground hover:text-foreground flex size-8 items-center justify-center transition-colors" + aria-label="View on GitHub" + > + <GitHubIcon className="size-4" /> + </a> + <ThemeToggle /> + </div> + </div> + </header> + + <main className={cn("flex-1", fullHeight && "min-h-0 overflow-hidden")}> + {children} + </main> + + {!hideFooter && ( + <footer className="relative px-4 py-8"> + <div className="text-muted-foreground mx-auto flex max-w-7xl items-center justify-between text-sm"> + <p> + By{" "} + <Link + href="/" + className="hover:text-foreground transition-colors" + > + assistant-ui + </Link> + </p> + <a + href="https://agentbase.dev" + target="_blank" + rel="noopener noreferrer" + className="text-foreground/30 hover:text-foreground text-xs transition-colors" + > + © {new Date().getFullYear()} AgentbaseAI Inc. + </a> + </div> + </footer> + )} + </div> + ); +} diff --git a/apps/docs/components/shared/tab.tsx b/apps/docs/components/shared/tab.tsx new file mode 100644 index 0000000..19777cb --- /dev/null +++ b/apps/docs/components/shared/tab.tsx @@ -0,0 +1,456 @@ +"use client"; + +import { cn } from "@/lib/utils"; +import { cva, type VariantProps } from "class-variance-authority"; +import Link from "next/link"; +import { usePathname } from "next/navigation"; +import React from "react"; + +interface TabItem { + label: string; + value?: React.ReactNode; // If present, it's a content tab + href?: string; // If present, it's a navigation tab + icon?: React.ReactNode; + isActive?: (pathname: string) => boolean; +} + +const tabSwitcherVariants = cva("flex flex-col", { + variants: { + orientation: { + horizontal: "h-full overflow-hidden", + navigation: "h-auto", + }, + }, + defaultVariants: { + orientation: "horizontal", + }, +}); + +const tabListVariants = cva("flex-none", { + variants: { + orientation: { + horizontal: "", + navigation: "", + }, + }, + defaultVariants: { + orientation: "horizontal", + }, +}); + +const tabIndicatorVariants = cva( + "absolute transition-all duration-300 ease-out", + { + variants: { + variant: { + ghost: "bg-foreground bottom-0 h-[2px]", + default: "bg-primary bottom-0 h-[2px]", + outline: "bg-foreground bottom-0 h-[2px]", + secondary: "bg-secondary-foreground bottom-0 h-[2px]", + link: "bg-primary bottom-0 h-[2px]", + }, + orientation: { + horizontal: "bottom-0 h-[2px]", + navigation: "bottom-0 h-0.5", + }, + }, + defaultVariants: { + variant: "ghost", + orientation: "horizontal", + }, + }, +); + +const tabItemVariants = cva( + "focus-visible:ring-ring/50 relative flex h-[30px] cursor-pointer items-center justify-center gap-2 rounded-md px-3 py-2 text-sm leading-5 whitespace-nowrap transition-all duration-300 outline-none focus-visible:ring-2 focus-visible:ring-inset data-[active=true]:font-medium", + { + variants: { + variant: { + ghost: + "data-[active=true]:bg-foreground/5 data-[active=true]:text-foreground border-transparent bg-transparent", + default: + "border-border/50 bg-background/50 text-muted-foreground hover:border-border hover:bg-background hover:text-foreground data-[active=true]:border-border data-[active=true]:bg-background data-[active=true]:text-foreground border data-[active=true]:shadow-sm", + outline: + "border-border/30 text-muted-foreground hover:border-border hover:bg-accent/50 hover:text-foreground data-[active=true]:border-border data-[active=true]:bg-accent/30 data-[active=true]:text-foreground border bg-transparent", + secondary: + "bg-secondary/30 text-secondary-foreground/80 hover:bg-secondary/50 hover:text-secondary-foreground data-[active=true]:bg-secondary/70 data-[active=true]:text-secondary-foreground border-transparent", + link: "text-muted-foreground hover:text-primary data-[active=true]:text-primary border-transparent bg-transparent", + }, + }, + defaultVariants: { + variant: "ghost", + }, + }, +); + +function Tab({ + tabs, + className, + defaultActiveIndex = 0, + variant = "ghost", + orientation, + onTabChange, + actions, + ...props +}: React.ComponentProps<"div"> & { + tabs: TabItem[]; + defaultActiveIndex?: number; + variant?: "ghost" | "default" | "outline" | "secondary" | "link"; + onTabChange?: (label: string, index: number) => void; + actions?: React.ReactNode; +} & VariantProps<typeof tabSwitcherVariants>) { + const pathname = usePathname(); + const [hoveredIndex, setHoveredIndex] = React.useState<number | null>(null); + const [contentActiveIndex, setContentActiveIndex] = + React.useState(defaultActiveIndex); + const [hoverStyle, setHoverStyle] = React.useState({}); + const [activeStyle, setActiveStyle] = React.useState({ + left: "0px", + width: "0px", + }); + const tabRefs = React.useRef<(HTMLElement | null)[]>([]); + + // Check if we have content tabs (tabs with value) + const hasContentTabs = tabs.some((tab) => tab.value !== undefined); + // Pure navigation mode: ALL tabs are navigation-only (href without value) + const isPureNavigationMode = + tabs.every((tab) => tab.href && !tab.value) && tabs.some((tab) => tab.href); + + const resolvedOrientation = + orientation || (isPureNavigationMode ? "navigation" : "horizontal"); + + // Find active tab index for pure navigation mode + const navigationActiveIndex = isPureNavigationMode + ? tabs.findIndex((tab) => { + if (tab.href) { + return tab.isActive ? tab.isActive(pathname) : pathname === tab.href; + } + return false; + }) + : -1; + + const activeIndex = isPureNavigationMode + ? navigationActiveIndex + : contentActiveIndex; + + React.useEffect(() => { + if (hoveredIndex !== null) { + const hoveredElement = tabRefs.current[hoveredIndex]; + if (hoveredElement) { + const { offsetLeft, offsetWidth } = hoveredElement; + setHoverStyle({ + left: `${offsetLeft}px`, + width: `${offsetWidth}px`, + }); + } + } + }, [hoveredIndex]); + + React.useEffect(() => { + if (activeIndex !== -1) { + const activeElement = tabRefs.current[activeIndex]; + if (activeElement) { + const { offsetLeft, offsetWidth } = activeElement; + setActiveStyle({ + left: `${offsetLeft}px`, + width: `${offsetWidth}px`, + }); + } + } + }, [activeIndex]); + + React.useEffect(() => { + requestAnimationFrame(() => { + const initialActiveIndex = isPureNavigationMode + ? navigationActiveIndex + : contentActiveIndex; + if (initialActiveIndex !== -1) { + const activeElement = tabRefs.current[initialActiveIndex]; + if (activeElement) { + const { offsetLeft, offsetWidth } = activeElement; + setActiveStyle({ + left: `${offsetLeft}px`, + width: `${offsetWidth}px`, + }); + } + } + }); + }, [isPureNavigationMode, navigationActiveIndex, contentActiveIndex]); + + const handleKeyDown = (index: number, e: React.KeyboardEvent) => { + const tab = tabs[index]; + // Only handle key events for content tabs (tabs with value) + if (tab?.value !== undefined && (e.key === "Enter" || e.key === " ")) { + e.preventDefault(); + if (index !== contentActiveIndex) { + onTabChange?.(tab.label, index); + } + setContentActiveIndex(index); + } + }; + + const handleContentTabClick = (index: number) => { + const tab = tabs[index]; + // Only set active index for content tabs (tabs with value) + if (tab?.value !== undefined) { + if (index !== contentActiveIndex) { + onTabChange?.(tab.label, index); + } + setContentActiveIndex(index); + } + }; + + // Show indicator only for ghost, outline, and link variants + const showIndicator = + variant === "ghost" || variant === "outline" || variant === "link"; + + return ( + <div + className={cn( + tabSwitcherVariants({ orientation: resolvedOrientation }), + className, + )} + data-orientation={resolvedOrientation} + data-slot="unified-tab-switcher" + data-variant={variant} + {...props} + > + <TabList orientation={resolvedOrientation}> + <div className="flex items-center gap-2"> + <TabContainer className="min-w-0 flex-1"> + {showIndicator && ( + <TabIndicator + activeStyle={activeStyle} + hoveredIndex={hoveredIndex} + hoverStyle={hoverStyle} + orientation={resolvedOrientation} + variant={variant} + /> + )} + {tabs.map((tab, index) => ( + <TabItem + index={index} + isActive={ + isPureNavigationMode + ? Boolean( + tab.href && + (tab.isActive + ? tab.isActive(pathname) + : pathname === tab.href), + ) + : tab.value !== undefined && index === contentActiveIndex + } + key={tab.label} + onClick={() => handleContentTabClick(index)} + onKeyDown={(e) => handleKeyDown(index, e)} + onMouseEnter={() => setHoveredIndex(index)} + onMouseLeave={() => setHoveredIndex(null)} + tab={tab} + tabRefs={tabRefs} + variant={variant} + /> + ))} + </TabContainer> + {actions && ( + <div + className="flex flex-none items-center gap-1 pb-2" + data-slot="tab-actions" + > + {actions} + </div> + )} + </div> + </TabList> + + {/* Content panel - show when there are content tabs */} + {hasContentTabs && ( + <TabContent + activeIndex={contentActiveIndex} + activeTab={tabs[contentActiveIndex]} + /> + )} + </div> + ); +} + +function TabList({ + orientation, + className, + children, + ...props +}: React.ComponentProps<"div"> & { + orientation: "horizontal" | "navigation"; +}) { + return ( + <div + className={cn(tabListVariants({ orientation }), className)} + data-orientation={orientation} + data-slot="tab-list" + {...props} + > + {children} + </div> + ); +} + +function TabIndicator({ + variant, + orientation, + hoverStyle, + activeStyle, + hoveredIndex, + className, + ...props +}: React.ComponentProps<"div"> & { + variant: "ghost" | "default" | "outline" | "secondary" | "link"; + orientation: "horizontal" | "navigation"; + hoverStyle: React.CSSProperties; + activeStyle: React.CSSProperties; + hoveredIndex: number | null; +}) { + return ( + <> + {variant === "ghost" && + hoveredIndex !== null && + hoverStyle.width && + Number.parseFloat(String(hoverStyle.width)) > 0 && ( + <div + className="bg-foreground/8 absolute flex h-[30px] items-center rounded-md transition-all duration-300 ease-out" + data-slot="tab-hover-indicator" + style={hoverStyle} + {...props} + /> + )} + <div + className={cn( + tabIndicatorVariants({ variant, orientation }), + className, + )} + data-orientation={orientation} + data-slot="tab-active-indicator" + data-variant={variant} + style={activeStyle} + /> + </> + ); +} + +function TabContainer({ className, ...props }: React.ComponentProps<"div">) { + return ( + <div + className={cn( + "relative flex scrollbar-none items-center space-x-[6px] overflow-x-auto pb-2", + className, + )} + data-slot="tab-container" + role="tablist" + {...props} + /> + ); +} + +function TabItem({ + tab, + index, + isActive, + variant, + tabRefs, + onMouseEnter, + onMouseLeave, + onClick, + onKeyDown, + className, +}: { + tab: TabItem; + index: number; + isActive: boolean; + variant: "ghost" | "default" | "outline" | "secondary" | "link"; + tabRefs: React.RefObject<(HTMLElement | null)[]>; + onMouseEnter: () => void; + onMouseLeave: () => void; + onClick: () => void; + onKeyDown: (e: React.KeyboardEvent) => void; + className?: string; +}) { + const tabContent = ( + <> + {tab.icon} + {tab.label} + </> + ); + + const baseClasses = cn(tabItemVariants({ variant }), className); + + // If tab has a path, render as navigation link + if (tab.href) { + return ( + <Link + aria-controls={`panel-${tab.label}`} + aria-selected={isActive} + className={baseClasses} + data-active={isActive} + data-slot="tab-item" + data-type="navigation" + data-variant={variant} + href={tab.href} + onMouseEnter={onMouseEnter} + onMouseLeave={onMouseLeave} + ref={(el) => { + tabRefs.current[index] = el; + }} + role="tab" + tabIndex={0} + > + {tabContent} + </Link> + ); + } + + // Otherwise render as content tab + return ( + <div + aria-controls={`panel-${tab.label}`} + className={baseClasses} + data-active={isActive} + data-slot="tab-item" + data-type="content" + data-variant={variant} + onClick={onClick} + onKeyDown={onKeyDown} + onMouseEnter={onMouseEnter} + onMouseLeave={onMouseLeave} + ref={(el) => { + tabRefs.current[index] = el; + }} + role="tab" + tabIndex={0} + > + {tabContent} + </div> + ); +} + +function TabContent({ + activeTab, + activeIndex, + className, + ...props +}: React.ComponentProps<"div"> & { + activeTab: TabItem | undefined; + activeIndex: number; +}) { + return ( + <div + aria-labelledby={activeTab?.label} + className={cn("relative mt-4 flex-1 overflow-hidden", className)} + data-slot="tab-content-panel" + id={`panel-${activeTab?.label}`} + role="tabpanel" + {...props} + > + {activeTab?.value} + </div> + ); +} + +export { Tab, TabList, TabIndicator, TabContainer, TabItem, TabContent }; diff --git a/apps/docs/components/shared/theme-toggle.tsx b/apps/docs/components/shared/theme-toggle.tsx new file mode 100644 index 0000000..1108388 --- /dev/null +++ b/apps/docs/components/shared/theme-toggle.tsx @@ -0,0 +1,35 @@ +"use client"; + +import { useTheme } from "next-themes"; +import { useEffect, useState } from "react"; +import { Moon, Sun } from "lucide-react"; + +export function ThemeToggle() { + const { resolvedTheme, setTheme } = useTheme(); + const [mounted, setMounted] = useState(false); + + useEffect(() => setMounted(true), []); + + const toggleTheme = () => { + setTheme(resolvedTheme === "dark" ? "light" : "dark"); + }; + + return ( + <button + type="button" + onClick={toggleTheme} + className="text-muted-foreground hover:text-foreground flex size-8 items-center justify-center transition-colors" + aria-label="Toggle theme" + > + {mounted ? ( + resolvedTheme === "dark" ? ( + <Moon className="size-4" /> + ) : ( + <Sun className="size-4" /> + ) + ) : ( + <div className="size-4" /> + )} + </button> + ); +} diff --git a/apps/docs/components/tool-ui/notepad.tsx b/apps/docs/components/tool-ui/notepad.tsx new file mode 100644 index 0000000..8bba355 --- /dev/null +++ b/apps/docs/components/tool-ui/notepad.tsx @@ -0,0 +1,214 @@ +"use client"; + +import { cn } from "@/lib/utils"; +import { TooltipIconButton } from "@/components/assistant-ui/tooltip-icon-button"; +import { type Unstable_InteractableToolRenderProps as InteractableToolRenderProps } from "@assistant-ui/react"; +import { + CheckIcon, + CopyIcon, + RotateCcwIcon, + SquarePenIcon, +} from "lucide-react"; +import { useEffect, useRef, useState, type FC } from "react"; +import { z } from "zod"; + +// Mirrors the `notepad` tool's parameters schema in docs-toolkit.tsx (which +// can't import from this client module on the server). +const notepadStateSchema = z.object({ + title: z + .string() + .describe("A short title, shown as the heading of the notepad card."), + content: z + .string() + .describe( + "The full plain text, shown to the user in the body of the notepad card.", + ), +}); + +export type NotepadArgs = z.infer<typeof notepadStateSchema>; + +const NotepadCard: FC<{ + title: React.ReactNode; + actions?: React.ReactNode; + muted?: boolean; + children: React.ReactNode; +}> = ({ title, actions, muted, children }) => ( + <div + className={cn( + "border-border/60 bg-muted/40 my-3 rounded-2xl border", + muted && "opacity-75", + )} + > + <div className="flex items-center gap-2.5 py-2.5 pr-3 pl-3.5"> + <SquarePenIcon className="text-muted-foreground size-4.5 shrink-0" /> + {title} + <div className="ml-auto flex items-center gap-0.5">{actions}</div> + </div> + <div className="px-4 pt-0.5 pb-4 text-[15px] leading-7">{children}</div> + </div> +); + +const titleClass = + "text-foreground focus:bg-accent min-w-0 max-w-full truncate rounded-md bg-transparent px-1 py-0.5 -mx-1 text-sm font-semibold outline-none"; + +const CopyButton: FC<{ content: string }> = ({ content }) => { + const [copied, setCopied] = useState(false); + const copyTimeout = useRef<ReturnType<typeof setTimeout>>(undefined); + + const copy = () => { + navigator.clipboard?.writeText(content).catch(() => {}); + setCopied(true); + clearTimeout(copyTimeout.current); + copyTimeout.current = setTimeout(() => setCopied(false), 1400); + }; + + return ( + <TooltipIconButton + tooltip="Copy" + className={cn( + "text-muted-foreground hover:text-foreground size-8 rounded-md", + copied && "text-green-600 hover:text-green-600", + )} + onClick={copy} + > + {copied ? ( + <CheckIcon className="size-4" /> + ) : ( + <CopyIcon className="size-4" /> + )} + </TooltipIconButton> + ); +}; + +type NotepadVersion = NonNullable< + InteractableToolRenderProps<NotepadArgs>["version"] +>; + +type UpdateNote = (updater: (prev: NotepadArgs) => NotepadArgs) => void; + +const sameNote = (a: NotepadArgs, b: NotepadArgs) => + a.title === b.title && a.content === b.content; + +const StreamingNotepad: FC<{ state: NotepadArgs }> = ({ state }) => ( + <NotepadCard + title={ + <span className={cn(titleClass, "animate-pulse")}> + {state.title || "Drafting note..."} + </span> + } + > + <div className="wrap-break-word whitespace-pre-wrap">{state.content}</div> + </NotepadCard> +); + +const NotepadEditor: FC<{ + note: NotepadArgs; + setNote: UpdateNote; + onRestore?: () => void; +}> = ({ note, setNote, onRestore }) => { + const bodyRef = useRef<HTMLDivElement>(null); + + useEffect(() => { + const node = bodyRef.current; + if (!node) return; + if (document.activeElement === node) return; + if (node.innerText !== note.content) node.innerText = note.content; + }, [note.content]); + + return ( + <NotepadCard + title={ + <input + className={titleClass} + value={note.title ?? ""} + spellCheck={false} + aria-label="Note title" + onChange={(e) => + setNote((prev) => ({ ...prev, title: e.target.value })) + } + /> + } + actions={ + <> + {onRestore && ( + <> + <span className="relative inline-flex"> + <TooltipIconButton + tooltip="Restore this version" + className="text-muted-foreground hover:text-foreground size-8 rounded-md" + onClick={onRestore} + > + <RotateCcwIcon className="size-4" /> + </TooltipIconButton> + <span + className="ring-muted/40 pointer-events-none absolute top-0.5 right-0.5 size-1.75 rounded-full bg-amber-500 ring-2" + aria-label="Edited" + /> + </span> + <div className="bg-border mx-1 h-4.5 w-px" /> + </> + )} + <CopyButton content={note.content} /> + </> + } + > + <div + ref={bodyRef} + contentEditable + suppressContentEditableWarning + spellCheck={false} + role="textbox" + aria-multiline="true" + aria-label="Note text" + className="caret-foreground wrap-break-word whitespace-pre-wrap outline-none" + onInput={() => + setNote((prev) => ({ + ...prev, + content: bodyRef.current?.innerText ?? prev.content, + })) + } + /> + </NotepadCard> + ); +}; + +const HistoricalNotepad: FC<{ version: NotepadVersion }> = ({ version }) => { + const [note, setNote] = useState(version.state); + const canRestore = !sameNote(note, version.state); + + return ( + <NotepadEditor + note={note} + setNote={(updater) => setNote(updater)} + {...(canRestore ? { onRestore: () => setNote(version.state) } : {})} + /> + ); +}; + +const LiveNotepad: FC<{ + state: NotepadArgs; + setState: InteractableToolRenderProps<NotepadArgs>["setState"]; + version: InteractableToolRenderProps<NotepadArgs>["version"]; +}> = ({ state, setState, version }) => { + const canRestore = version && !sameNote(state, version.state); + + return ( + <NotepadEditor + note={state} + setNote={(updater) => setState(updater)} + {...(canRestore ? { onRestore: version.restore } : {})} + /> + ); +}; + +export const Notepad: FC<InteractableToolRenderProps<NotepadArgs>> = ({ + state, + setState, + version, + streaming, +}) => { + if (streaming) return <StreamingNotepad state={state} />; + if (version && !version.isLatest) + return <HistoricalNotepad version={version} />; + return <LiveNotepad state={state} setState={setState} version={version} />; +}; diff --git a/apps/docs/components/tool-ui/weather-widget/generated/weather-runtime-core.generated.ts b/apps/docs/components/tool-ui/weather-widget/generated/weather-runtime-core.generated.ts new file mode 100644 index 0000000..cc76cb0 --- /dev/null +++ b/apps/docs/components/tool-ui/weather-widget/generated/weather-runtime-core.generated.ts @@ -0,0 +1,67 @@ +// @ts-nocheck +// AUTO-GENERATED by `pnpm weather:compile`. +// Source: lib/weather-authoring/weather-widget/weather-runtime-core.ts +// DO NOT EDIT MANUALLY. + +import{useMemo as ye,useState as ft,useEffect as dt}from"react";var Ze=8,j=new Set;function he(e){return j.has(e)?!0:j.size>=Ze?!1:(j.add(e),!0)}function ae(e){j.delete(e)}function ge(e,t){return t&&(ae(e),null)}import{useCallback as N,useEffect as Ke,useRef as v}from"react";var O=`#version 300 es +in vec4 a_position;out vec2 v_uv;void main(){gl_Position=a_position;v_uv=a_position.xy*0.5+0.5;}`,se=`#version 300 es +precision highp float;in vec2 v_uv;out vec4 fragColor;uniform float u_time;uniform vec2 u_resolution;uniform float u_timeOfDay;uniform float u_moonPhase;uniform float u_starDensity;uniform vec2 u_celestialPos;uniform float u_sunSize;uniform float u_moonSize;uniform float u_sunGlowIntensity;uniform float u_sunGlowSize;uniform float u_sunRayCount;uniform float u_sunRayLength;uniform float u_sunRayIntensity;uniform float u_sunRayShimmer;uniform float u_sunRayShimmerSpeed;uniform float u_moonGlowIntensity;uniform float u_moonGlowSize;uniform float u_skyBrightness;uniform float u_skySaturation;uniform float u_skyContrast;uniform sampler2D u_moonTexture;uniform bool u_hasMoonTexture; +#define PI 3.14159265359 +#define GODRAY_MAX_SAMPLES 32 +#define TAU 6.28318530718 +float hash(vec2 p){return fract(sin(dot(p,vec2(127.1,311.7)))*43758.5453123);}vec2 hash2(vec2 p){p=vec2(dot(p,vec2(127.1,311.7)),dot(p,vec2(269.5,183.3)));return fract(sin(p)*43758.5453);}float noise(vec2 p){vec2 i=floor(p);vec2 f=fract(p);f=f*f*(3.0-2.0*f);float a=hash(i);float b=hash(i+vec2(1.0,0.0));float c=hash(i+vec2(0.0,1.0));float d=hash(i+vec2(1.0,1.0));return mix(mix(a,b,f.x),mix(c,d,f.x),f.y);}float fbm(vec2 p,int octaves){float value=0.0;float amplitude=0.5;float frequency=1.0;for(int i=0;i<6;i++){if(i>=octaves)break;value+=amplitude*noise(p*frequency);frequency*=2.0;amplitude*=0.5;}return value;}float getSunY(float timeOfDay,float baseY){float belowHorizon=-0.25;float riseProgress=smoothstep(0.18,0.32,timeOfDay);float setProgress=smoothstep(0.68,0.82,timeOfDay);float visible=riseProgress*(1.0-setProgress);return mix(belowHorizon,baseY,visible);}float getMoonY(float timeOfDay,float baseY){float belowHorizon=-0.25;float risingEvening=smoothstep(0.74,0.88,timeOfDay);float settingMorning=1.0-smoothstep(0.12,0.26,timeOfDay);float visible=max(risingEvening,settingMorning);return mix(belowHorizon,baseY,visible);}float getHorizonFade(float y){return smoothstep(-0.2,0.0,y);}vec3 getSkyColor(vec2 uv,float timeOfDay){vec3 dayTop=vec3(0.4,0.6,0.9);vec3 dayHorizon=vec3(0.7,0.8,0.95);vec3 sunsetTop=vec3(0.2,0.2,0.4);vec3 sunsetHorizon=vec3(0.9,0.5,0.2);vec3 nightTop=vec3(0.02,0.02,0.05);vec3 nightHorizon=vec3(0.05,0.05,0.1);float dayAmount=smoothstep(0.25,0.4,timeOfDay)*smoothstep(0.75,0.6,timeOfDay);float sunsetAmount=max(smoothstep(0.2,0.3,timeOfDay)*smoothstep(0.4,0.3,timeOfDay),smoothstep(0.6,0.7,timeOfDay)*smoothstep(0.8,0.7,timeOfDay));float nightAmount=max(0.0,1.0-dayAmount-sunsetAmount);float gradientFactor=pow(1.0-uv.y,1.0);vec3 topColor=dayTop*dayAmount+sunsetTop*sunsetAmount+nightTop*nightAmount;vec3 horizonColor=dayHorizon*dayAmount+sunsetHorizon*sunsetAmount+nightHorizon*nightAmount;vec3 avgColor=(topColor+horizonColor)*0.5;topColor=mix(avgColor,topColor,u_skyContrast);horizonColor=mix(avgColor,horizonColor,u_skyContrast);vec3 color=mix(topColor,horizonColor,gradientFactor);color*=u_skyBrightness;float gray=dot(color,vec3(0.299,0.587,0.114));if(u_skySaturation<=1.0){color=mix(vec3(gray),color,u_skySaturation);}else{float boost=u_skySaturation-1.0;color=color+(color-vec3(gray))*boost;}return clamp(color,0.0,1.0);}float drawStars(vec2 uv,float density,float time){float stars=0.0;for(int layer=0;layer<3;layer++){float layerScale=100.0+float(layer)*50.0;vec2 gridUV=uv*layerScale;vec2 gridID=floor(gridUV);vec2 gridFract=fract(gridUV);vec2 starPos=hash2(gridID+float(layer)*100.0);float dist=length(gridFract-starPos);float starPresent=step(1.0-density*0.3,hash(gridID*(float(layer)+1.0)));float starSize=0.02+hash(gridID.yx)*0.03;float twinkle=sin(time*(2.0+hash(gridID)*3.0)+hash(gridID.yx)*TAU)*0.3+0.7;float star=smoothstep(starSize,0.0,dist)*starPresent*twinkle;star*=1.0-float(layer)*0.3;stars+=star;}return stars;}vec3 drawSun(vec2 uv,vec2 sunPos,float size){vec2 aspect=vec2(u_resolution.x/u_resolution.y,1.0);vec2 diff=(uv-sunPos)*aspect;float dist=length(diff);float angle=atan(diff.y,diff.x);float disc=1.0-smoothstep(size*0.9,size,dist);vec3 sunCore=vec3(1.0,1.0,0.95);vec3 sunEdge=vec3(1.0,0.9,0.4);float edgeFactor=clamp(dist/size,0.0,1.0);vec3 sunColor=mix(sunCore,sunEdge,edgeFactor);float limbDarkening=1.0-pow(clamp(dist/size,0.0,1.0),2.0)*0.3;sunColor*=limbDarkening;float glowSize=max(0.1,u_sunGlowSize);float scaledDist=dist/glowSize;float glow1=exp(-scaledDist*8.0)*0.5;float glow2=exp(-scaledDist*3.0)*0.3;float glow3=exp(-scaledDist*1.5)*0.15;vec3 glowColor=vec3(1.0,0.8,0.4);float glowTotal=(glow1+glow2+glow3)*u_sunGlowIntensity;vec3 result=sunColor*disc*2.0;result+=glowColor*glowTotal;float ringCenter=size*1.15;float ringWidth=max(size*0.35,0.001);float ringMask=smoothstep(size*0.85,size*1.05,dist);ringMask*=1.0-smoothstep(size*5.0,size*9.0,dist);float chromaShift=size*(0.012+u_sunRayIntensity*0.06);chromaShift*=smoothstep(size*0.9,size*2.4,dist);float ringR=exp(-pow((dist-chromaShift-ringCenter)/ringWidth,2.0));float ringG=exp(-pow((dist-ringCenter)/ringWidth,2.0));float ringB=exp(-pow((dist+chromaShift-ringCenter)/ringWidth,2.0));float ringT=clamp((dist-size)/(size*2.2),0.0,1.0);vec3 ringSpectral=0.55+0.45*cos(TAU*(ringT+vec3(0.0,0.33,0.67)));ringSpectral=clamp(ringSpectral,0.0,1.0);vec3 ringColor=mix(vec3(1.0),ringSpectral,0.45);float ringIntensity=(ringR+ringG+ringB)/3.0;ringIntensity*=ringMask*u_sunGlowIntensity*0.025;result+=ringColor*ringIntensity;if(u_sunRayCount>0.0&&u_sunRayIntensity>0.0){if(dist<size*3.6){float motion=clamp(u_sunRayShimmer,0.0,5.0);float t=u_time*max(0.0,u_sunRayShimmerSpeed);float rayPhase=angle*u_sunRayCount;float rayIndex=floor(rayPhase/PI+0.5);float raySeed=hash(vec2(rayIndex,19.17));float major=pow(abs(cos(rayPhase)),10.0);float minor=pow(abs(cos(rayPhase*2.0+raySeed*2.3)),22.0)*0.18;float rayShape=max(major,minor);float breathe=1.0+(noise(vec2(t*0.05,raySeed*7.0))-0.5)*(0.08*motion);float shimmer=1.0+(noise(vec2(dist*12.0-t*0.25,raySeed*23.0))-0.5)*(0.12*motion);float micro=1.0+(noise(vec2(t*0.6,rayPhase*0.8))-0.5)*(0.06*motion);float rayNoise=0.72+0.28*noise(vec2(rayPhase*0.35,t*0.12+raySeed*10.0));float rayPattern=rayShape*rayNoise;float rayStart=smoothstep(size*0.75,size*1.25,dist);float rayEnd=smoothstep(size*(3.0*breathe),size*1.5,dist);float rayLengthVar=0.75+raySeed*0.55;float maxRayDist=max(0.001,u_sunRayLength*0.15);float rayFalloff=exp(-dist*dist/(maxRayDist*maxRayDist*rayLengthVar*breathe));float rays=rayPattern*rayFalloff*rayStart*rayEnd*u_sunRayIntensity;rays*=shimmer*micro;float prismMask=smoothstep(size*1.05,size*2.6,dist);float rayChroma=size*(0.01+u_sunRayIntensity*0.05)*prismMask;float distR=max(0.0,dist-rayChroma);float distB=dist+rayChroma;float falloffR=exp(-distR*distR/(maxRayDist*maxRayDist*rayLengthVar*breathe));float falloffG=rayFalloff;float falloffB=exp(-distB*distB/(maxRayDist*maxRayDist*rayLengthVar*breathe));vec3 rayRGB=vec3(falloffR,falloffG,falloffB)*rayPattern*rayStart*rayEnd;float rayAvg=(rayRGB.r+rayRGB.g+rayRGB.b)/3.0;vec3 rayChromaColor=rayRGB/max(rayAvg,1e-4);float rayT=clamp((dist-size)/(size*2.6),0.0,1.0);vec3 raySpectral=0.55+0.45*cos(TAU*(rayT+vec3(0.0,0.33,0.67)));raySpectral=clamp(raySpectral,0.0,1.0);raySpectral=mix(vec3(1.0),raySpectral,0.28);vec3 rayWarm=vec3(1.0,0.92,0.7);float prismMix=clamp(0.08+u_sunRayIntensity*0.6,0.0,0.45)*prismMask;vec3 rayColor=mix(rayWarm,rayChromaColor,prismMix);rayColor=mix(rayColor,raySpectral,prismMix*0.65);result+=rayColor*rays;}}return result;}vec3 getSphereNormal(vec2 discUV){float r2=dot(discUV,discUV);if(r2>1.0)return vec3(0.0);float z=sqrt(1.0-r2);return normalize(vec3(discUV.x,discUV.y,z));}vec2 sphereToEquirectangular(vec3 normal){float longitude=atan(normal.x,normal.z);float u=longitude/TAU+0.5;float latitude=asin(clamp(normal.y,-1.0,1.0));float v=latitude/PI+0.5;return vec2(u,v);}vec3 getMoonSurfaceColor(vec3 normal,vec2 discUV){if(u_hasMoonTexture){vec2 texUV=sphereToEquirectangular(normal);return texture(u_moonTexture,texUV).rgb;}float brightness=0.7+fbm(discUV*5.0,3)*0.3;return vec3(brightness*0.85,brightness*0.83,brightness*0.8);}vec4 drawMoon(vec2 uv,vec2 moonPos,float size,float phase){vec2 aspect=vec2(u_resolution.x/u_resolution.y,1.0);vec2 diff=(uv-moonPos)*aspect;float dist=length(diff);vec2 discUV=diff/size;float discDist=length(discUV);float disc=1.0-smoothstep(0.95,1.0,discDist);float glowSize=max(0.1,u_moonGlowSize);float glowIntensity=u_moonGlowIntensity;if(disc<0.001){float scaledDist=dist/glowSize;float glow1=exp(-scaledDist*6.0)*0.15;float glow2=exp(-scaledDist*2.0)*0.06;vec3 glowColor=vec3(0.8,0.85,0.95);float phaseAngle=phase*TAU;vec3 sunDir=vec3(sin(phaseAngle),0.0,-cos(phaseAngle));float glowPhase=max(0.2,dot(normalize(vec3(discUV,0.5)),sunDir)*0.5+0.5);return vec4(glowColor*(glow1+glow2)*glowPhase*glowIntensity,0.0);}vec3 normal=getSphereNormal(discUV);float phaseAngle=phase*TAU;vec3 sunDir=vec3(sin(phaseAngle),0.0,-cos(phaseAngle));float NdotL=dot(normal,sunDir);float terminator=smoothstep(-0.02,0.08,NdotL);vec3 baseColor=getMoonSurfaceColor(normal,discUV);vec3 ambient=baseColor*0.03;vec3 lit=baseColor*terminator;vec3 moonSurface=ambient+lit;float limbDarkening=1.0-pow(discDist,3.0)*0.15;moonSurface*=limbDarkening;float rimLight=pow(1.0-abs(NdotL),4.0)*terminator*0.1;moonSurface+=vec3(1.0,0.98,0.95)*rimLight;float scaledDist=dist/glowSize;float glow1=exp(-scaledDist*6.0)*0.12;float glow2=exp(-scaledDist*2.0)*0.06;vec3 glowColor=vec3(0.8,0.85,0.95);float litAmount=max(0.1,terminator);vec3 glow=glowColor*(glow1+glow2)*litAmount*glowIntensity;return vec4(moonSurface*disc+glow,disc);}void main(){vec2 uv=v_uv;vec3 color=getSkyColor(uv,u_timeOfDay);float sunY=getSunY(u_timeOfDay,u_celestialPos.y);float moonY=getMoonY(u_timeOfDay,u_celestialPos.y);vec2 sunPos=vec2(u_celestialPos.x,sunY);vec2 moonPos=vec2(u_celestialPos.x,moonY);float moonFade=getHorizonFade(moonY);if(moonFade>0.01){float stars=drawStars(uv,u_starDensity,u_time);color+=vec3(stars)*moonFade;}float sunFade=getHorizonFade(sunY);if(sunFade>0.01){vec3 sun=drawSun(uv,sunPos,u_sunSize);color+=sun*sunFade;}if(moonFade>0.01){vec4 moon=drawMoon(uv,moonPos,u_moonSize,u_moonPhase);float alpha=moon.a*moonFade;color=mix(color,moon.rgb,alpha)+moon.rgb*(1.0-moon.a)*moonFade;}fragColor=vec4(color,0.0);}`,oe=`#version 300 es +precision highp float;in vec2 v_uv;out vec4 fragColor;uniform float u_time;uniform vec2 u_resolution;uniform sampler2D u_sceneTexture;uniform float u_timeOfDay;uniform float u_coverage;uniform float u_density;uniform float u_softness;uniform float u_windSpeed;uniform float u_windAngle;uniform float u_turbulence;uniform float u_lightIntensity;uniform float u_ambientDarkness;uniform int u_numLayers;uniform float u_cloudScale;uniform vec2 u_celestialPos;uniform float u_celestialSize;uniform float u_celestialBrightness;uniform float u_backlightIntensity; +#define PI 3.14159265359 +float hash(vec2 p){return fract(sin(dot(p,vec2(127.1,311.7)))*43758.5453123);}float noise(vec2 p){vec2 i=floor(p);vec2 f=fract(p);f=f*f*(3.0-2.0*f);float a=hash(i);float b=hash(i+vec2(1.0,0.0));float c=hash(i+vec2(0.0,1.0));float d=hash(i+vec2(1.0,1.0));return mix(mix(a,b,f.x),mix(c,d,f.x),f.y);}float fbm(vec2 p,int octaves){float value=0.0;float amplitude=0.5;for(int i=0;i<8;i++){if(i>=octaves)break;value+=amplitude*noise(p);p*=2.0;amplitude*=0.5;}return value;}float cloudLayer(vec2 uv,float time,float layerSeed,float speed,float turbAmount){vec2 wind=vec2(cos(u_windAngle),sin(u_windAngle))*speed*time;float layerScale=(1.8+hash(vec2(layerSeed,0.0))*1.2)*u_cloudScale;float layerRotation=hash(vec2(layerSeed,1.0))*0.5-0.25;vec2 layerOffset=vec2(hash(vec2(layerSeed,2.0))*100.0,hash(vec2(layerSeed,3.0))*100.0);float c=cos(layerRotation);float s=sin(layerRotation);vec2 rotatedUV=vec2(uv.x*c-uv.y*s,uv.x*s+uv.y*c);vec2 p=rotatedUV*layerScale+wind+layerOffset;float turbSeed=layerSeed*50.0;vec2 turbOffset=vec2(fbm(p*0.5+time*0.1+turbSeed,4),fbm(p*0.5+turbSeed+100.0+time*0.1,4))*turbAmount;float n=fbm(p+turbOffset,6);return n;}vec3 cloudLighting(float density,float heightInCloud,float sunAlt,float warmth,float nightFactor,vec2 uv){float daylight=smoothstep(-0.12,0.1,sunAlt);vec3 dayLitColor=vec3(1.0,0.98,0.96);vec3 sunsetLitColor=vec3(1.0,0.7,0.45);vec3 nightLitColor=vec3(0.12,0.14,0.2);vec3 litColor=mix(dayLitColor,sunsetLitColor,warmth);litColor=mix(litColor,nightLitColor,nightFactor);vec3 dayShadowColor=vec3(0.45,0.5,0.6);vec3 sunsetShadowColor=vec3(0.35,0.25,0.3);vec3 nightShadowColor=vec3(0.03,0.04,0.07);vec3 shadowColor=mix(dayShadowColor,sunsetShadowColor,warmth);shadowColor=mix(shadowColor,nightShadowColor,nightFactor);shadowColor*=(1.0-u_ambientDarkness*0.3);float topLight=heightInCloud*max(0.0,sunAlt);float sideLight=(1.0-abs(heightInCloud-0.5)*2.0)*(1.0-sunAlt*0.5);float bottomLight=(1.0-heightInCloud)*warmth*0.5;float ambientLight=mix(0.03,0.2,daylight);float lightAmount=(topLight*0.5+sideLight*0.3+bottomLight)*daylight+ambientLight;lightAmount=clamp(lightAmount*u_lightIntensity,0.0,1.0);vec3 cloudColor=mix(shadowColor,litColor,lightAmount);float rimLight=pow(density,0.5)*(1.0-density)*4.0;vec3 rimColor=mix(vec3(1.0,1.0,0.95),vec3(1.0,0.8,0.5),warmth);rimColor=mix(rimColor,vec3(0.15,0.18,0.25),nightFactor);float rimStrength=mix(0.1,0.3,daylight);cloudColor+=rimColor*rimLight*rimStrength*u_lightIntensity;float hotSpot=pow(max(0.0,lightAmount-0.6)*2.5,2.0)*warmth*daylight;cloudColor+=vec3(1.0,0.5,0.2)*hotSpot*0.4;float aspect=u_resolution.x/u_resolution.y;vec2 celestialUV=u_celestialPos;vec2 diff=(uv-celestialUV)*vec2(aspect,1.0);float distToCelestial=length(diff);float transmission=pow(1.0-density,2.0);float glowRadius=u_celestialSize*6.0;float proximityGlow=exp(-distToCelestial*distToCelestial/(glowRadius*glowRadius));float backlight=proximityGlow*transmission*u_celestialBrightness;float edgeDist=u_celestialSize*3.0;float nearCelestial=smoothstep(edgeDist*2.5,edgeDist*0.3,distToCelestial);float edgeFactor=density*(1.0-density)*4.0;float silverLining=nearCelestial*edgeFactor*u_celestialBrightness;vec3 backlightColor=mix(vec3(1.0,0.9,0.7),vec3(0.7,0.75,0.9),nightFactor);cloudColor+=backlightColor*(backlight*0.5+silverLining*0.8)*u_backlightIntensity;return cloudColor;}void main(){vec2 uv=v_uv;vec4 scene=texture(u_sceneTexture,uv);float sunAlt=u_timeOfDay<0.5?u_timeOfDay*2.0:2.0-u_timeOfDay*2.0;sunAlt=sunAlt*2.0-1.0;float warmth=1.0-smoothstep(0.0,0.4,sunAlt);warmth=warmth*warmth;float nightFactor=1.0-smoothstep(-0.12,0.02,sunAlt);float daylight=smoothstep(-0.12,0.1,sunAlt);vec3 color=scene.rgb;float accumulatedAlpha=0.0;for(int i=u_numLayers-1;i>=0;i--){float layerIdx=float(i);float layerDepth=layerIdx/max(1.0,float(u_numLayers)-1.0);float layerSeed=layerIdx*7.31+13.0;float layerSpeed=u_windSpeed*(0.6+hash(vec2(layerSeed,10.0))*0.8);float layerTurb=u_turbulence*(0.7+hash(vec2(layerSeed,11.0))*0.6);float cloud=cloudLayer(uv,u_time,layerSeed,layerSpeed,layerTurb);float threshold=1.0-u_coverage;cloud=smoothstep(threshold,threshold+u_softness,cloud);float heightInCloud=uv.y*0.6+cloud*0.4;vec3 cloudColor=cloudLighting(cloud,heightInCloud,sunAlt,warmth,nightFactor,uv);vec3 hazeColor=mix(vec3(0.05,0.06,0.1),vec3(0.6,0.7,0.85),daylight);float hazeAmount=layerDepth*layerDepth*0.5;cloudColor=mix(cloudColor,hazeColor,hazeAmount);float contrastReduction=1.0-layerDepth*0.3;cloudColor=mix(vec3(0.5),cloudColor,contrastReduction);float alpha=cloud*u_density*(0.6+(1.0-layerDepth)*0.4);color=mix(color,cloudColor,alpha*(1.0-accumulatedAlpha));accumulatedAlpha=accumulatedAlpha+alpha*(1.0-accumulatedAlpha);}fragColor=vec4(color,accumulatedAlpha);}`,ie=`#version 300 es +precision highp float;in vec2 v_uv;out vec4 fragColor;uniform float u_time;uniform vec2 u_resolution;uniform sampler2D u_sceneTexture;uniform float u_glassIntensity;uniform float u_glassZoom;uniform float u_fallingIntensity;uniform float u_fallingSpeed;uniform float u_fallingAngle;uniform float u_fallingStreakLength;uniform int u_fallingLayers;uniform float u_refractionStrength; +#define S(a, b, t) smoothstep(a, b, t) +vec3 N13(float p){vec3 p3=fract(vec3(p)*vec3(0.1031,0.11369,0.13787));p3+=dot(p3,p3.yzx+19.19);return fract(vec3((p3.x+p3.y)*p3.z,(p3.x+p3.z)*p3.y,(p3.y+p3.z)*p3.x));}float N(float t){return fract(sin(t*12345.564)*7658.76);}float Saw(float b,float t){return S(0.0,b,t)*S(1.0,b,t);}vec2 DropLayer(vec2 uv,float t){vec2 UV=uv;uv.y+=t*0.75;vec2 aspect=vec2(6.0,1.0);vec2 grid=aspect*2.0;vec2 id=floor(uv*grid);float colShift=N(id.x);uv.y+=colShift;id=floor(uv*grid);vec3 n=N13(id.x*35.2+id.y*2376.1);vec2 st=fract(uv*grid)-vec2(0.5,0.0);float x=n.x-0.5;float y=UV.y*20.0;float wiggle=sin(y+sin(y));x+=wiggle*(0.5-abs(x))*(n.z-0.5);x*=0.7;float ti=fract(t+n.z);y=(Saw(0.85,ti)-0.5)*0.9+0.5;vec2 p=vec2(x,y);float d=length((st-p)*aspect.yx);float mainDrop=S(0.4,0.0,d);float r=sqrt(S(1.0,y,st.y));float cd=abs(st.x-x);float trail=S(0.23*r,0.15*r*r,cd);float trailFront=S(-0.02,0.02,st.y-y);trail*=trailFront*r*r;float y2=fract(UV.y*10.0)+(st.y-0.5);float dd=length(st-vec2(x,y2));float droplets=S(0.3,0.0,dd);float m=mainDrop+droplets*r*trailFront;return vec2(m,trail);}float StaticDrops(vec2 uv,float t){uv*=40.0;vec2 id=floor(uv);uv=fract(uv)-0.5;vec3 n=N13(id.x*107.45+id.y*3543.654);vec2 p=(n.xy-0.5)*0.7;float d=length(uv-p);float fade=Saw(0.025,fract(t+n.z));float c=S(0.3,0.0,d)*fract(n.z*10.0)*fade;return c;}vec2 Drops(vec2 uv,float t,float l0,float l1,float l2){float s=StaticDrops(uv,t)*l0;vec2 m1=DropLayer(uv,t)*l1;vec2 m2=DropLayer(uv*1.85,t)*l2;float c=s+m1.x+m2.x;c=S(0.3,1.0,c);return vec2(c,max(m1.y*l0,m2.y*l1));}float hash12(vec2 p){vec3 p3=fract(vec3(p.xyx)*0.1031);p3+=dot(p3,p3.yzx+33.33);return fract((p3.x+p3.y)*p3.z);}vec2 FallingRainLayer(vec2 uv,float t,float speed,float angle,float streakLen,float scale,float density){vec2 offset=vec2(0.0);vec2 p=uv;p.x+=p.y*angle;p*=scale;p.y+=t*speed;vec2 id=floor(p);vec2 gv=fract(p)-0.5;for(int y=-1;y<=1;y++){for(int x=-1;x<=1;x++){vec2 offs=vec2(float(x),float(y));vec2 cellId=id+offs;float n1=hash12(cellId);if(n1>density)continue;vec2 n2=vec2(hash12(cellId*17.23),hash12(cellId*31.17));vec2 dropPos=offs+n2-0.5;vec2 localUV=gv-dropPos;float streakW=0.025+n1*0.02;float streakH=streakLen*(0.4+hash12(cellId*7.13)*0.6);float t_pos=(localUV.y+streakH)/(2.0*streakH);t_pos=clamp(t_pos,0.0,1.0);if(abs(localUV.y)>streakH*1.2)continue;float taper=mix(1.3,0.4,t_pos*t_pos);float width=streakW*taper;float core=S(width,width*0.2,abs(localUV.x));float vertFade=S(0.0,0.1,t_pos)*S(1.0,0.85,t_pos);float streak=core*vertFade;if(streak>0.001){offset.x+=localUV.x*streak*0.5;offset.y+=(n1-0.5)*streak*0.1;}}}return offset;}vec2 FallingRain(vec2 uv,float t){vec2 totalOffset=vec2(0.0);if(u_fallingIntensity<0.01)return totalOffset;float speed=u_fallingSpeed*5.0;float streakLen=u_fallingStreakLength*0.3;for(int i=0;i<6;i++){if(i>=u_fallingLayers)break;float layerIdx=float(i);float depth=layerIdx/float(max(u_fallingLayers-1,1));float layerScale=mix(6.0,30.0,depth);float layerSpeed=speed*mix(2.0,0.5,depth);float layerDensity=u_fallingIntensity*mix(0.8,0.3,depth);float layerStrength=mix(1.0,0.15,depth);float layerStreakLen=streakLen*mix(1.5,0.4,depth);float layerAngle=u_fallingAngle*mix(1.0,0.6,depth);vec2 layerOffset=vec2(sin(layerIdx*73.156)*3.0,cos(layerIdx*37.842)*3.0);vec2 layer=FallingRainLayer(uv+layerOffset,t+layerIdx*0.13,layerSpeed,layerAngle,layerStreakLen,layerScale,layerDensity);totalOffset+=layer*layerStrength;}return totalOffset*0.4;}void main(){vec2 uv=(gl_FragCoord.xy-0.5*u_resolution.xy)/u_resolution.y;vec2 UV=v_uv;uv*=u_glassZoom;float t=u_time*0.2;float rainAmount=u_glassIntensity;float staticDrops=S(-0.5,1.0,rainAmount)*2.0;float layer1=S(0.25,0.75,rainAmount);float layer2=S(0.0,0.5,rainAmount);vec2 c=Drops(uv,t,staticDrops,layer1,layer2);vec2 e=vec2(0.001,0.0);float cx=Drops(uv+e,t,staticDrops,layer1,layer2).x;float cy=Drops(uv+e.yx,t,staticDrops,layer1,layer2).x;vec2 glassNormal=vec2(cx-c.x,cy-c.x);vec2 fallingRainOffset=FallingRain(uv,u_time);vec2 totalRefraction=(glassNormal+fallingRainOffset)*u_refractionStrength;vec2 refractedUV=UV+totalRefraction;refractedUV=clamp(refractedUV,0.0,1.0);vec4 scene=texture(u_sceneTexture,refractedUV);vec3 color=scene.rgb;float rainMagnitude=length(fallingRainOffset);if(rainMagnitude>0.001){float brightness=dot(scene.rgb,vec3(0.299,0.587,0.114));float specular=rainMagnitude*15.0*(0.1+brightness*0.9);color+=vec3(0.8,0.85,0.95)*specular*0.3;}color+=vec3(0.1,0.12,0.15)*c.x*0.5;fragColor=vec4(color,scene.a);}`,re=`#version 300 es +precision highp float;in vec2 v_uv;out vec4 fragColor;uniform float u_time;uniform vec2 u_resolution;uniform sampler2D u_sceneTexture;uniform bool u_enabled;uniform float u_flashIntensity;uniform float u_branchDensity;uniform float u_sceneIllumination;uniform float u_lastFlashTime;uniform float u_strikeSeed; +#define MAX_SEGMENTS 32 +#define MAX_BRANCHES 16 +#define PI 3.14159265359 +float easeOutSine(float t){return sin(t*PI*0.5);}float easeInSine(float t){return 1.0-cos(t*PI*0.5);}float easeInOutSine(float t){return-(cos(PI*t)-1.0)*0.5;}float easeOutQuad(float t){return 1.0-(1.0-t)*(1.0-t);}float easeOutCubic(float t){float inv=1.0-t;return 1.0-inv*inv*inv;}float hash11(float p){p=fract(p*0.1031);p*=p+33.33;p*=p+p;return fract(p);}float hash12(vec2 p){vec3 p3=fract(vec3(p.xyx)*0.1031);p3+=dot(p3,p3.yzx+33.33);return fract((p3.x+p3.y)*p3.z);}vec2 hash22(vec2 p){vec3 p3=fract(vec3(p.xyx)*vec3(0.1031,0.1030,0.0973));p3+=dot(p3,p3.yzx+33.33);return fract((p3.xx+p3.yz)*p3.zy);}float noise(vec2 p){vec2 i=floor(p);vec2 f=fract(p);f=f*f*(3.0-2.0*f);float a=hash12(i);float b=hash12(i+vec2(1.0,0.0));float c=hash12(i+vec2(0.0,1.0));float d=hash12(i+vec2(1.0,1.0));return mix(mix(a,b,f.x),mix(c,d,f.x),f.y);}float distToSegment(vec2 p,vec2 a,vec2 b){vec2 pa=p-a;vec2 ba=b-a;float h=clamp(dot(pa,ba)/dot(ba,ba),0.0,1.0);return length(pa-ba*h);}vec2 displacedPoint(vec2 start,vec2 end,float t,float seed,float displacementAmt){vec2 basePoint=mix(start,end,t);vec2 dir=end-start;vec2 perp=normalize(vec2(-dir.y,dir.x));float envelope=sin(t*PI);float n1=noise(vec2(t*8.0,seed*100.0))*2.0-1.0;float n2=noise(vec2(t*16.0,seed*100.0+50.0))*2.0-1.0;float n3=noise(vec2(t*32.0,seed*100.0+100.0))*2.0-1.0;float displacement=(n1*0.6+n2*0.3+n3*0.1)*envelope*displacementAmt;float targetBias=1.0-t*0.3;displacement*=targetBias;return basePoint+perp*displacement*length(dir);}float mainBoltDistance(vec2 uv,vec2 start,vec2 end,float seed,float displacementAmt){float minDist=999.0;vec2 prevPoint=start;for(int i=1;i<=MAX_SEGMENTS;i++){float t=float(i)/float(MAX_SEGMENTS);vec2 currPoint=displacedPoint(start,end,t,seed,displacementAmt);float d=distToSegment(uv,prevPoint,currPoint);minDist=min(minDist,d);prevPoint=currPoint;}return minDist;}float branchDistance(vec2 uv,vec2 branchStart,vec2 branchDir,float branchLen,float seed,float displacementAmt){vec2 branchEnd=branchStart+branchDir*branchLen;float minDist=999.0;vec2 prevPoint=branchStart;for(int i=1;i<=12;i++){float t=float(i)/12.0;vec2 currPoint=displacedPoint(branchStart,branchEnd,t,seed,displacementAmt*0.7);float d=distToSegment(uv,prevPoint,currPoint);minDist=min(minDist,d);prevPoint=currPoint;}return minDist;}vec2 branchesDistance(vec2 uv,vec2 start,vec2 end,float seed,float displacementAmt,float density){float minDist=999.0;float brightness=0.0;vec2 mainDir=normalize(end-start);float mainLen=length(end-start);for(int i=0;i<MAX_BRANCHES;i++){float idx=float(i);float branchT=0.15+hash11(seed+idx*7.31)*0.7;float branchProb=(1.0-branchT)*density;if(hash11(seed+idx*3.17)>branchProb)continue;vec2 branchStart=displacedPoint(start,end,branchT,seed,displacementAmt);float angleOffset=(hash11(seed+idx*11.13)*2.0-1.0)*0.6;float side=hash11(seed+idx*5.71)>0.5?1.0:-1.0;float angle=atan(mainDir.y,mainDir.x)+side*(0.3+abs(angleOffset)*0.5);vec2 branchDir=vec2(cos(angle),sin(angle));float branchLen=mainLen*(0.15+hash11(seed+idx*13.37)*0.25);float d=branchDistance(uv,branchStart,branchDir,branchLen,seed+idx*100.0,displacementAmt);if(d<minDist){minDist=d;brightness=0.5-branchT*0.2;}if(density>0.3&&hash11(seed+idx*17.19)<density*0.5){float subT=0.3+hash11(seed+idx*19.23)*0.4;vec2 subStart=branchStart+branchDir*branchLen*subT;float subAngle=angle+(hash11(seed+idx*23.29)*2.0-1.0)*0.5;vec2 subDir=vec2(cos(subAngle),sin(subAngle));float subLen=branchLen*0.4;float subD=branchDistance(uv,subStart,subDir,subLen,seed+idx*200.0,displacementAmt*0.5);if(subD<minDist){minDist=subD;brightness=0.25;}}}return vec2(minDist,brightness);}vec3 lightningGlow(float dist,float brightness,float intensity,float thickness){float scaledDist=dist/max(thickness,0.1);float core=smoothstep(0.003,0.0,scaledDist)*brightness;float innerGlow=exp(-scaledDist*150.0)*brightness;float outerGlow=exp(-dist*dist*3000.0)*brightness*thickness;vec3 coreColor=vec3(1.0,1.0,1.0);vec3 innerColor=vec3(0.7,0.8,1.0);vec3 outerColor=vec3(0.5,0.5,0.9);vec3 color=coreColor*core*2.0;color+=innerColor*innerGlow*0.8;color+=outerColor*outerGlow*0.5;return color*intensity;}float flashEnvelope(float timeSinceStrike,float duration){if(timeSinceStrike<0.0||timeSinceStrike>duration)return 0.0;float t=timeSinceStrike/duration;float attackT=clamp(t/0.03,0.0,1.0);float attack=easeOutCubic(attackT);float sustainT=clamp((t-0.05)/0.65,0.0,1.0);float sustain=1.0-easeInOutSine(sustainT);float decay=exp(-t*2.0);decay=mix(decay,easeOutSine(1.0-t),0.3);float endT=clamp((t-0.75)/0.25,0.0,1.0);float endFade=1.0-easeInSine(endT);return attack*max(sustain,decay*0.4)*endFade;}float restrikeEnvelope(float timeSinceStrike,float duration,float seed){float env=flashEnvelope(timeSinceStrike,duration*0.7);if(hash11(seed*7.7)>0.7){float restrike1=flashEnvelope(timeSinceStrike-duration*0.5,duration*0.3);env=max(env,restrike1*0.6);}if(hash11(seed*11.3)>0.85){float restrike2=flashEnvelope(timeSinceStrike-duration*0.75,duration*0.2);env=max(env,restrike2*0.4);}return env;}void main(){vec4 scene=texture(u_sceneTexture,v_uv);if(!u_enabled){fragColor=scene;return;}vec2 uv=v_uv;float aspect=u_resolution.x/u_resolution.y;uv.x*=aspect;float timeSinceStrike=u_time-u_lastFlashTime;float durationSec=0.8;float flash=restrikeEnvelope(timeSinceStrike,durationSec,u_strikeSeed);float afterimageDuration=durationSec*1.5;float afterimageT=clamp(timeSinceStrike/afterimageDuration,0.0,1.0);float afterimage=timeSinceStrike<0.0?0.0:(1.0-easeInSine(afterimageT));vec3 color=scene.rgb;if(flash>0.01||afterimage>0.01){vec2 strikeHash=hash22(vec2(u_strikeSeed*123.456,u_strikeSeed*789.012));vec2 boltStart=vec2((0.3+strikeHash.x*0.4)*aspect,1.05);vec2 boltEnd=vec2(boltStart.x+(strikeHash.x-0.5)*0.4,-0.05);float straightDist=distToSegment(uv,boltStart,boltEnd);float sourceGlow=exp(-length(uv-boltStart)*3.0);color+=vec3(0.4,0.45,0.6)*sourceGlow*afterimage*0.3;float distLimit=0.18+u_branchDensity*0.25+u_flashIntensity*0.05;float feather=0.08;float region=1.0-smoothstep(distLimit-feather,distLimit,straightDist);if(region<=0.0005){fragColor=vec4(color,scene.a);return;}float displacementAmt=0.15;float mainDist=mainBoltDistance(uv,boltStart,boltEnd,u_strikeSeed,displacementAmt);vec2 branchResult=branchesDistance(uv,boltStart,boltEnd,u_strikeSeed,displacementAmt,u_branchDensity);float branchDist=branchResult.x;float branchBrightness=branchResult.y;float mainThickness=mix(0.2,1.0,easeOutSine(sqrt(max(flash,0.0))));vec3 afterglowColor=vec3(0.5,0.45,0.7);vec3 mainCore=lightningGlow(mainDist,easeOutQuad(max(flash,0.0)),u_flashIntensity,mainThickness);float mainAfterglowDist=mainDist*0.6;float mainAfterglowStrength=exp(-mainAfterglowDist*50.0)*afterimage*0.5;vec3 mainAfterglow=afterglowColor*mainAfterglowStrength;float branchThickness=mix(0.15,1.0,easeOutSine(max(flash,0.0)));vec3 branchCore=lightningGlow(branchDist,branchBrightness*easeOutQuad(max(flash,0.0)),u_flashIntensity,branchThickness);float branchAfterglowDist=branchDist*0.7;float branchAfterglowStrength=exp(-branchAfterglowDist*80.0)*branchBrightness*afterimage*0.4;vec3 branchAfterglow=afterglowColor*branchAfterglowStrength;color+=(mainCore+branchCore)*max(flash,0.0)*region;color+=(mainAfterglow+branchAfterglow)*afterimage*region;}fragColor=vec4(color,scene.a);}`,le=`#version 300 es +precision highp float;in vec2 v_uv;out vec4 fragColor;uniform float u_time;uniform vec2 u_resolution;uniform sampler2D u_sceneTexture;uniform float u_intensity;uniform int u_layers;uniform float u_fallSpeed;uniform float u_windSpeed;uniform float u_windAngle;uniform float u_turbulence;uniform float u_drift;uniform float u_flutter;uniform float u_windShear;uniform float u_flakeSize;uniform float u_sizeVariation;uniform float u_opacity;uniform float u_glowAmount;uniform float u_sparkle; +#define PI 3.14159265359 +#define MAX_LAYERS 6 +float hash12(vec2 p){vec3 p3=fract(vec3(p.xyx)*0.1031);p3+=dot(p3,p3.yzx+33.33);return fract((p3.x+p3.y)*p3.z);}vec2 hash22(vec2 p){vec3 p3=fract(vec3(p.xyx)*vec3(0.1031,0.1030,0.0973));p3+=dot(p3,p3.yzx+33.33);return fract((p3.xx+p3.yz)*p3.zy);}float noise(vec2 p){vec2 i=floor(p);vec2 f=fract(p);f=f*f*(3.0-2.0*f);float a=hash12(i);float b=hash12(i+vec2(1.0,0.0));float c=hash12(i+vec2(0.0,1.0));float d=hash12(i+vec2(1.0,1.0));return mix(mix(a,b,f.x),mix(c,d,f.x),f.y);}vec2 rotate2D(vec2 p,float angle){float c=cos(angle);float s=sin(angle);return vec2(p.x*c-p.y*s,p.x*s+p.y*c);}float snowflakeShape(vec2 uv,float size,float seed,float rotation){vec2 rotatedUV=rotate2D(uv,rotation);float dist=length(rotatedUV);float circle=smoothstep(size,size*0.3,dist);float angle=atan(rotatedUV.y,rotatedUV.x);float hexPattern=0.5+0.5*cos(angle*6.0);hexPattern=pow(hexPattern,2.0);float crystalAmount=smoothstep(0.02,0.05,size)*0.3;float shape=mix(circle,circle*(0.7+hexPattern*0.3),crystalAmount);float glow=exp(-dist*dist/(size*size*3.0))*u_glowAmount;return shape+glow*0.4;}vec2 getWind(float layerDepth){vec2 baseWind=vec2(cos(u_windAngle),0.0)*u_windSpeed;float windResponse=mix(0.3,1.0,1.0-layerDepth);float shearResponse=1.0+u_windShear*(1.0-layerDepth)*0.35;return baseWind*windResponse*shearResponse;}float sparkle(vec2 cellId,float time,float seed){float sparklePhase=hash12(cellId+vec2(seed*100.0,0.0))*100.0;float sparkleFreq=2.0+hash12(cellId+vec2(0.0,seed*100.0))*3.0;float sparkleWave=sin(time*sparkleFreq+sparklePhase);float sparkleIntensity=pow(max(0.0,sparkleWave),16.0);float sparkleProbability=hash12(cellId+vec2(floor(time*0.5),0.0));sparkleIntensity*=step(0.85,sparkleProbability);return sparkleIntensity*u_sparkle;}vec3 snowLayer(vec2 uv,float time,float layerIndex,float totalLayers){float depth=layerIndex/max(1.0,totalLayers-1.0);float layerScale=mix(8.0,40.0,depth);float layerSpeed=u_fallSpeed*mix(1.2,0.4,depth);float layerDensity=u_intensity*mix(1.0,0.5,depth);float layerFlakeSize=u_flakeSize*mix(1.5,0.3,depth);float layerOpacity=u_opacity*mix(1.0,0.4,depth);vec2 layerOffset=vec2(sin(layerIndex*73.156)*10.0,cos(layerIndex*37.842)*10.0);vec2 p=(uv+layerOffset)*layerScale;p.y+=time*layerSpeed*2.0;vec2 baseWind=getWind(depth);p.x+=time*baseWind.x*0.3;vec2 id=floor(p);vec2 gv=fract(p)-0.5;float snow=0.0;float sparkleAccum=0.0;for(int y=-1;y<=1;y++){for(int x=-1;x<=1;x++){vec2 offs=vec2(float(x),float(y));vec2 cellId=id+offs;float h1=hash12(cellId);vec2 h2=hash22(cellId);float h3=hash12(cellId+vec2(127.0,311.0));float h4=hash12(cellId+vec2(271.0,183.0));if(h1>layerDensity)continue;float sizeVar=1.0+(h3-0.5)*u_sizeVariation;float size=layerFlakeSize*sizeVar*0.04;vec2 flakePos=h2*0.8-0.4;float flutterPhase=h3*PI*2.0;float flutterAmp=u_flutter*0.15*(1.0-depth);flakePos.x+=sin(time*3.0+flutterPhase)*flutterAmp;flakePos.y+=cos(time*2.5+flutterPhase*1.3)*flutterAmp*0.5;float driftPhase=h4*PI*2.0+layerIndex*1.7;flakePos.x+=sin(time*0.55+driftPhase)*u_drift*0.18;float turbFreq=0.6+u_turbulence*1.4;vec2 turb=vec2(noise(cellId*0.17+time*turbFreq),noise(cellId.yx*0.17+time*turbFreq+17.0))-0.5;flakePos+=turb*(u_turbulence*0.22)*(1.0-depth);vec2 localUV=gv-offs-flakePos;float rotationSpeed=(1.5-sizeVar*0.5)*(0.5+h4*1.0);float rotationPhase=h4*PI*2.0;float rotation=time*rotationSpeed+rotationPhase;float flake=snowflakeShape(localUV,size,h1,rotation);float flakeSparkle=sparkle(cellId,time,h1)*flake;sparkleAccum+=flakeSparkle;snow+=flake*layerOpacity;}}return vec3(snow,sparkleAccum,depth);}void main(){vec4 scene=texture(u_sceneTexture,v_uv);vec2 uv=v_uv;float aspect=u_resolution.x/u_resolution.y;uv.x*=aspect;float snow=0.0;float totalSparkle=0.0;for(int i=u_layers-1;i>=0;i--){vec3 layerResult=snowLayer(uv,u_time,float(i),float(u_layers));snow+=layerResult.x;totalSparkle+=layerResult.y;}snow=clamp(snow,0.0,1.0);totalSparkle=clamp(totalSparkle,0.0,1.0);vec3 snowColor=vec3(0.75,0.78,0.85);vec3 sparkleColor=vec3(0.9,0.92,1.0);vec3 color=scene.rgb+snowColor*snow+sparkleColor*totalSparkle;fragColor=vec4(color,scene.a);}`,ue=`#version 300 es +precision highp float;in vec2 v_uv;out vec4 fragColor;uniform sampler2D u_sceneTexture;uniform float u_time;uniform vec2 u_resolution;uniform float u_timeOfDay;uniform vec2 u_sunPos;uniform float u_sunVisible;uniform float u_lastFlashTime;uniform float u_strikeSeed;uniform float u_lightningSceneIllumination;uniform bool u_postEnabled;uniform float u_haze;uniform float u_hazeHorizon;uniform float u_hazeDesaturation;uniform float u_hazeContrast;uniform float u_bloomIntensity;uniform float u_bloomThreshold;uniform float u_bloomKnee;uniform float u_bloomRadius;uniform float u_bloomTapScale;uniform float u_exposureIntensity;uniform float u_exposureDesaturation;uniform float u_exposureRecovery;uniform float u_godRayIntensity;uniform float u_godRayDecay;uniform float u_godRayDensity;uniform float u_godRayWeight;uniform int u_godRaySamples; +#define PI 3.14159265359 +#define GODRAY_MAX_SAMPLES 32 +float saturate(float x){return clamp(x,0.0,1.0);}float luminance(vec3 c){return dot(c,vec3(0.299,0.587,0.114));}float easeOutSine(float t){return sin(t*PI*0.5);}float easeInSine(float t){return 1.0-cos(t*PI*0.5);}float easeInOutSine(float t){return-(cos(PI*t)-1.0)*0.5;}float easeOutQuad(float t){return 1.0-(1.0-t)*(1.0-t);}float easeOutCubic(float t){float inv=1.0-t;return 1.0-inv*inv*inv;}float hash11(float p){p=fract(p*0.1031);p*=p+33.33;p*=p+p;return fract(p);}float flashEnvelope(float timeSinceStrike,float duration){if(timeSinceStrike<0.0||timeSinceStrike>duration)return 0.0;float t=timeSinceStrike/duration;float attackT=clamp(t/0.03,0.0,1.0);float attack=easeOutCubic(attackT);float sustainT=clamp((t-0.05)/0.65,0.0,1.0);float sustain=1.0-easeInOutSine(sustainT);float decay=exp(-t*2.0);decay=mix(decay,easeOutSine(1.0-t),0.3);float endT=clamp((t-0.75)/0.25,0.0,1.0);float endFade=1.0-easeInSine(endT);return attack*max(sustain,decay*0.4)*endFade;}float restrikeEnvelope(float timeSinceStrike,float duration,float seed){float env=flashEnvelope(timeSinceStrike,duration*0.7);if(hash11(seed*7.7)>0.7){float restrike1=flashEnvelope(timeSinceStrike-duration*0.5,duration*0.3);env=max(env,restrike1*0.6);}if(hash11(seed*11.3)>0.85){float restrike2=flashEnvelope(timeSinceStrike-duration*0.75,duration*0.2);env=max(env,restrike2*0.4);}return env;}float getSunAltitudeFromTimeOfDay(float timeOfDay){float sunAlt=timeOfDay<0.5?timeOfDay*2.0:2.0-timeOfDay*2.0;return sunAlt*2.0-1.0;}vec3 applyHaze(vec3 color,vec2 uv){float haze=saturate(u_haze);if(haze<=0.0001)return color;float horizon=pow(1.0-uv.y,1.8);float hazeWeight=haze*mix(1.0,horizon,saturate(u_hazeHorizon));float sunAlt=getSunAltitudeFromTimeOfDay(u_timeOfDay);float daylight=smoothstep(-0.12,0.1,sunAlt);vec3 hazeDay=vec3(0.60,0.70,0.85);vec3 hazeNight=vec3(0.06,0.07,0.10);vec3 hazeColor=mix(hazeNight,hazeDay,daylight);color=mix(color,hazeColor,hazeWeight*0.55);float contrast=saturate(1.0-hazeWeight*saturate(u_hazeContrast));color=mix(vec3(0.5),color,contrast);float gray=luminance(color);float sat=saturate(1.0-hazeWeight*saturate(u_hazeDesaturation));color=mix(vec3(gray),color,sat);return color;}vec3 bloomTap(vec2 uv){vec3 c=texture(u_sceneTexture,clamp(uv,0.0,1.0)).rgb;float l=luminance(c);float knee=max(0.0001,u_bloomKnee);float m=smoothstep(u_bloomThreshold,u_bloomThreshold+knee,l);return c*m;}vec3 computeBloom(vec2 uv){float intensity=u_bloomIntensity;if(intensity<=0.0001)return vec3(0.0);vec2 texel=1.0/max(u_resolution,vec2(1.0));float radiusPx=max(0.0,u_bloomRadius)*(u_resolution.y*0.02);radiusPx*=max(0.25,u_bloomTapScale);vec2 d=texel*radiusPx;vec3 sum=vec3(0.0);sum+=bloomTap(uv)*0.20;sum+=bloomTap(uv+vec2(d.x,0.0))*0.12;sum+=bloomTap(uv+vec2(-d.x,0.0))*0.12;sum+=bloomTap(uv+vec2(0.0,d.y))*0.12;sum+=bloomTap(uv+vec2(0.0,-d.y))*0.12;sum+=bloomTap(uv+vec2(d.x,d.y))*0.08;sum+=bloomTap(uv+vec2(-d.x,d.y))*0.08;sum+=bloomTap(uv+vec2(d.x,-d.y))*0.08;sum+=bloomTap(uv+vec2(-d.x,-d.y))*0.08;return sum*intensity;}vec3 applyExposureResponse(vec3 color,float flashStrength){if(flashStrength<=0.0001)return color;float t=saturate(flashStrength);float gain=1.0+flashStrength*2.2;vec3 lifted=color*gain;vec3 tonemapped=1.0-exp(-lifted);vec3 outColor=mix(color,tonemapped,t);float gray=luminance(outColor);float desat=saturate(u_exposureDesaturation)*t;outColor=mix(outColor,vec3(gray),desat);outColor=mix(outColor,vec3(1.0),t*0.06);return outColor;}vec3 computeGodRays(vec2 uv){if(u_godRayIntensity<=0.0001)return vec3(0.0);if(u_godRaySamples<=0)return vec3(0.0);if(u_sunVisible<=0.001)return vec3(0.0);float sunAlt=getSunAltitudeFromTimeOfDay(u_timeOfDay);float daylight=smoothstep(-0.12,0.1,sunAlt);float lowSun=1.0-smoothstep(0.25,0.75,max(0.0,sunAlt));vec2 sunUV=clamp(u_sunPos,vec2(-0.25),vec2(1.25));vec2 delta=(uv-sunUV)*(u_godRayDensity/float(u_godRaySamples));vec2 coord=uv;float illuminationDecay=1.0;float accum=0.0;for(int i=0;i<GODRAY_MAX_SAMPLES;i++){if(i>=u_godRaySamples)break;coord-=delta;vec4 s=texture(u_sceneTexture,clamp(coord,0.0,1.0));float transmittance=1.0-saturate(s.a);float sampleLum=luminance(s.rgb);float brightMask=saturate((sampleLum-0.85)/0.15);brightMask*=brightMask;float raySample=transmittance*brightMask;accum+=raySample*illuminationDecay*u_godRayWeight;illuminationDecay*=u_godRayDecay;}vec3 rayColor=mix(vec3(0.7,0.72,0.8),vec3(1.0,0.92,0.75),daylight);float intensity=u_godRayIntensity*saturate(u_sunVisible)*daylight*lowSun;return rayColor*accum*intensity;}void main(){vec4 scene=texture(u_sceneTexture,v_uv);vec3 color=scene.rgb;if(!u_postEnabled){fragColor=vec4(color,1.0);return;}color+=computeGodRays(v_uv);color+=computeBloom(v_uv);float flashStrength=0.0;if(u_exposureIntensity>0.0001){float timeSinceStrike=u_time-u_lastFlashTime;float durationSec=0.8;float f=restrikeEnvelope(timeSinceStrike,durationSec,u_strikeSeed);float afterimageDuration=durationSec*1.5;float afterT=clamp((timeSinceStrike*max(0.05,u_exposureRecovery))/afterimageDuration,0.0,1.0);float afterimage=timeSinceStrike<0.0?0.0:(1.0-easeInSine(afterT));float sceneFlash=f*max(0.0,u_lightningSceneIllumination);color+=vec3(0.3,0.32,0.4)*sceneFlash;flashStrength=f*u_exposureIntensity;flashStrength=max(flashStrength,afterimage*u_exposureIntensity*0.12);color=applyExposureResponse(color,flashStrength);}color=applyHaze(color,v_uv);fragColor=vec4(clamp(color,0.0,1.0),1.0);}`;function pe(e,t,a){if(e.isContextLost())return null;let n=e.createShader(t);if(!n)return null;if(e.shaderSource(n,a),e.compileShader(n),!e.getShaderParameter(n,e.COMPILE_STATUS)){let s=e.getShaderInfoLog(n);if(!e.isContextLost()){let c=t===e.VERTEX_SHADER?"vertex":t===e.FRAGMENT_SHADER?"fragment":String(t);console.error(`Shader compile error (${c}):`,s??"(no info log)")}return e.deleteShader(n),null}return n}function B(e,t,a){if(e.isContextLost())return null;let n=pe(e,e.VERTEX_SHADER,t),s=pe(e,e.FRAGMENT_SHADER,a);if(!n||!s)return n&&e.deleteShader(n),s&&e.deleteShader(s),null;let c=e.createProgram();if(!c)return e.deleteShader(n),e.deleteShader(s),null;if(e.attachShader(c,n),e.attachShader(c,s),e.linkProgram(c),!e.getProgramParameter(c,e.LINK_STATUS)){let u=e.getProgramInfoLog(c);return e.isContextLost()||console.error("Program link error:",u??"(no info log)"),e.deleteProgram(c),e.deleteShader(n),e.deleteShader(s),null}return e.detachShader(c,n),e.detachShader(c,s),e.deleteShader(n),e.deleteShader(s),c}function ce(e,t,a){let n=e.createTexture();if(!n)return null;e.bindTexture(e.TEXTURE_2D,n),e.texImage2D(e.TEXTURE_2D,0,e.RGBA,t,a,0,e.RGBA,e.UNSIGNED_BYTE,null),e.texParameteri(e.TEXTURE_2D,e.TEXTURE_MIN_FILTER,e.LINEAR),e.texParameteri(e.TEXTURE_2D,e.TEXTURE_MAG_FILTER,e.LINEAR),e.texParameteri(e.TEXTURE_2D,e.TEXTURE_WRAP_S,e.CLAMP_TO_EDGE),e.texParameteri(e.TEXTURE_2D,e.TEXTURE_WRAP_T,e.CLAMP_TO_EDGE);let s=e.createFramebuffer();if(!s)return e.deleteTexture(n),null;e.bindFramebuffer(e.FRAMEBUFFER,s),e.framebufferTexture2D(e.FRAMEBUFFER,e.COLOR_ATTACHMENT0,e.TEXTURE_2D,n,0);let c=e.checkFramebufferStatus(e.FRAMEBUFFER);return c!==e.FRAMEBUFFER_COMPLETE?(e.isContextLost()||console.error("Framebuffer incomplete:",c),e.deleteFramebuffer(s),e.deleteTexture(n),e.bindFramebuffer(e.FRAMEBUFFER,null),e.bindTexture(e.TEXTURE_2D,null),null):(e.bindFramebuffer(e.FRAMEBUFFER,null),e.bindTexture(e.TEXTURE_2D,null),{fbo:s,texture:n,width:t,height:a})}function fe(e,t,a,n){t.width===a&&t.height===n||(e.bindTexture(e.TEXTURE_2D,t.texture),e.texImage2D(e.TEXTURE_2D,0,e.RGBA,a,n,0,e.RGBA,e.UNSIGNED_BYTE,null),e.bindTexture(e.TEXTURE_2D,null),t.width=a,t.height=n)}function J(e,t,a){let n=Math.max(0,Math.min(1,(a-e)/(t-e)));return n*n*(3-2*n)}function Se(e){let t=e.timeOfDay,a=e.celestialY,n=-.25,s=J(.18,.32,t),c=J(.68,.82,t),u=s*(1-c),o=n+(a-n)*u,r=J(.74,.88,t),i=1-J(.12,.26,t),h=Math.max(r,i),f=n+(a-n)*h,p=u>h,b=p?o:f,R=p?e.sunSize:e.moonSize,C=p?Math.min(1,e.sunGlowIntensity*.3)*u:Math.min(.5,e.moonGlowIntensity*.15)*h;return{sunY:o,moonY:f,sunVisible:u,moonVisible:h,activeY:b,activeSize:R,activeBrightness:C}}function Z(e,t,a,n,s){e.bindFramebuffer(e.FRAMEBUFFER,a.fbo),e.viewport(0,0,n,s),e.useProgram(t)}function K(e,t,a,n){e.activeTexture(e.TEXTURE0),e.bindTexture(e.TEXTURE_2D,a),e.uniform1i(n(t,"u_sceneTexture"),0)}function ve(e,t,a,n){e.bindFramebuffer(e.FRAMEBUFFER,t.fbo),e.viewport(0,0,a,n),e.clearColor(0,0,0,0),e.clear(e.COLOR_BUFFER_BIT)}function be({gl:e,program:t,target:a,displayWidth:n,displayHeight:s,time:c,params:u,moonTexture:o,moonTextureLoaded:r,getUniformLocation:i}){Z(e,t,a,n,s),e.uniform1f(i(t,"u_time"),c),e.uniform2f(i(t,"u_resolution"),n,s),e.uniform1f(i(t,"u_timeOfDay"),u.timeOfDay),e.uniform1f(i(t,"u_moonPhase"),u.moonPhase),e.uniform1f(i(t,"u_starDensity"),u.starDensity),e.uniform2f(i(t,"u_celestialPos"),u.celestialX,u.celestialY),e.uniform1f(i(t,"u_sunSize"),u.sunSize),e.uniform1f(i(t,"u_moonSize"),u.moonSize),e.uniform1f(i(t,"u_sunGlowIntensity"),u.sunGlowIntensity),e.uniform1f(i(t,"u_sunGlowSize"),u.sunGlowSize),e.uniform1f(i(t,"u_sunRayCount"),u.sunRayCount),e.uniform1f(i(t,"u_sunRayLength"),u.sunRayLength),e.uniform1f(i(t,"u_sunRayIntensity"),u.sunRayIntensity),e.uniform1f(i(t,"u_sunRayShimmer"),u.sunRayShimmer),e.uniform1f(i(t,"u_sunRayShimmerSpeed"),u.sunRayShimmerSpeed),e.uniform1f(i(t,"u_moonGlowIntensity"),u.moonGlowIntensity),e.uniform1f(i(t,"u_moonGlowSize"),u.moonGlowSize),e.uniform1f(i(t,"u_skyBrightness"),u.skyBrightness),e.uniform1f(i(t,"u_skySaturation"),u.skySaturation),e.uniform1f(i(t,"u_skyContrast"),u.skyContrast),e.activeTexture(e.TEXTURE0),e.bindTexture(e.TEXTURE_2D,o),e.uniform1i(i(t,"u_moonTexture"),0),e.uniform1i(i(t,"u_hasMoonTexture"),r?1:0),e.drawArrays(e.TRIANGLES,0,6)}function we({gl:e,program:t,target:a,sceneTexture:n,displayWidth:s,displayHeight:c,time:u,params:o,celestial:r,getUniformLocation:i}){Z(e,t,a,s,c),K(e,t,n,i),e.uniform1f(i(t,"u_time"),u),e.uniform2f(i(t,"u_resolution"),s,c),e.uniform1f(i(t,"u_timeOfDay"),r.timeOfDay),e.uniform1f(i(t,"u_coverage"),o.coverage),e.uniform1f(i(t,"u_density"),o.density),e.uniform1f(i(t,"u_softness"),o.softness),e.uniform1f(i(t,"u_windSpeed"),o.windSpeed),e.uniform1f(i(t,"u_windAngle"),o.windAngle),e.uniform1f(i(t,"u_turbulence"),o.turbulence),e.uniform1f(i(t,"u_lightIntensity"),o.lightIntensity),e.uniform1f(i(t,"u_ambientDarkness"),o.ambientDarkness),e.uniform1i(i(t,"u_numLayers"),o.numLayers),e.uniform1f(i(t,"u_cloudScale"),o.cloudScale);let h=Se(r);e.uniform2f(i(t,"u_celestialPos"),r.celestialX,h.activeY),e.uniform1f(i(t,"u_celestialSize"),h.activeSize),e.uniform1f(i(t,"u_celestialBrightness"),h.activeBrightness),e.uniform1f(i(t,"u_backlightIntensity"),o.backlightIntensity),e.drawArrays(e.TRIANGLES,0,6)}function xe({gl:e,program:t,target:a,sceneTexture:n,displayWidth:s,displayHeight:c,time:u,params:o,interactions:r,getUniformLocation:i}){Z(e,t,a,s,c),K(e,t,n,i),e.uniform1f(i(t,"u_time"),u),e.uniform2f(i(t,"u_resolution"),s,c),e.uniform1f(i(t,"u_glassIntensity"),o.glassIntensity),e.uniform1f(i(t,"u_glassZoom"),o.glassZoom),e.uniform1f(i(t,"u_fallingIntensity"),o.fallingIntensity),e.uniform1f(i(t,"u_fallingSpeed"),o.fallingSpeed),e.uniform1f(i(t,"u_fallingAngle"),o.fallingAngle),e.uniform1f(i(t,"u_fallingStreakLength"),o.fallingStreakLength),e.uniform1i(i(t,"u_fallingLayers"),o.fallingLayers),e.uniform1f(i(t,"u_refractionStrength"),r.rainRefractionStrength),e.drawArrays(e.TRIANGLES,0,6)}function Ie(e,t,a,n,s){if(!e.lightning||!a||!t.enabled)return!1;let u=.8*1.5,o=n-s;return o>=0&&o<=u}function Re({gl:e,program:t,target:a,sceneTexture:n,displayWidth:s,displayHeight:c,time:u,params:o,interactions:r,lastFlashTime:i,strikeSeed:h,getUniformLocation:f}){Z(e,t,a,s,c),K(e,t,n,f),e.uniform1f(f(t,"u_time"),u),e.uniform2f(f(t,"u_resolution"),s,c),e.uniform1i(f(t,"u_enabled"),o.enabled?1:0),e.uniform1f(f(t,"u_flashIntensity"),o.flashIntensity),e.uniform1f(f(t,"u_branchDensity"),o.branchDensity),e.uniform1f(f(t,"u_sceneIllumination"),r.lightningSceneIllumination),e.uniform1f(f(t,"u_lastFlashTime"),i),e.uniform1f(f(t,"u_strikeSeed"),h),e.drawArrays(e.TRIANGLES,0,6)}function ke({gl:e,program:t,target:a,sceneTexture:n,displayWidth:s,displayHeight:c,time:u,params:o,getUniformLocation:r}){Z(e,t,a,s,c),K(e,t,n,r),e.uniform1f(r(t,"u_time"),u),e.uniform2f(r(t,"u_resolution"),s,c),e.uniform1f(r(t,"u_intensity"),o.intensity),e.uniform1i(r(t,"u_layers"),o.layers),e.uniform1f(r(t,"u_fallSpeed"),o.fallSpeed),e.uniform1f(r(t,"u_windSpeed"),o.windSpeed),e.uniform1f(r(t,"u_windAngle"),o.windAngle),e.uniform1f(r(t,"u_turbulence"),o.turbulence),e.uniform1f(r(t,"u_drift"),o.drift),e.uniform1f(r(t,"u_flutter"),o.flutter),e.uniform1f(r(t,"u_windShear"),o.windShear),e.uniform1f(r(t,"u_flakeSize"),o.flakeSize),e.uniform1f(r(t,"u_sizeVariation"),o.sizeVariation),e.uniform1f(r(t,"u_opacity"),o.opacity),e.uniform1f(r(t,"u_glowAmount"),o.glowAmount),e.uniform1f(r(t,"u_sparkle"),o.sparkle),e.drawArrays(e.TRIANGLES,0,6)}function _e({gl:e,program:t,sceneTexture:a,displayWidth:n,displayHeight:s,time:c,celestial:u,interactions:o,post:r,lastFlashTime:i,strikeSeed:h,getUniformLocation:f}){e.bindFramebuffer(e.FRAMEBUFFER,null),e.viewport(0,0,n,s),e.useProgram(t),K(e,t,a,f),e.uniform1f(f(t,"u_time"),c),e.uniform2f(f(t,"u_resolution"),n,s),e.uniform1f(f(t,"u_timeOfDay"),u.timeOfDay);let p=Se(u);e.uniform2f(f(t,"u_sunPos"),u.celestialX,p.sunY),e.uniform1f(f(t,"u_sunVisible"),p.sunVisible),e.uniform1f(f(t,"u_lastFlashTime"),i),e.uniform1f(f(t,"u_strikeSeed"),h),e.uniform1f(f(t,"u_lightningSceneIllumination"),o.lightningSceneIllumination),e.uniform1i(f(t,"u_postEnabled"),r.enabled?1:0),e.uniform1f(f(t,"u_haze"),r.haze),e.uniform1f(f(t,"u_hazeHorizon"),r.hazeHorizon),e.uniform1f(f(t,"u_hazeDesaturation"),r.hazeDesaturation),e.uniform1f(f(t,"u_hazeContrast"),r.hazeContrast),e.uniform1f(f(t,"u_bloomIntensity"),r.bloomIntensity),e.uniform1f(f(t,"u_bloomThreshold"),r.bloomThreshold),e.uniform1f(f(t,"u_bloomKnee"),r.bloomKnee),e.uniform1f(f(t,"u_bloomRadius"),r.bloomRadius),e.uniform1f(f(t,"u_bloomTapScale"),r.bloomTapScale),e.uniform1f(f(t,"u_exposureIntensity"),r.exposureIntensity),e.uniform1f(f(t,"u_exposureDesaturation"),r.exposureDesaturation),e.uniform1f(f(t,"u_exposureRecovery"),r.exposureRecovery),e.uniform1f(f(t,"u_godRayIntensity"),r.godRayIntensity),e.uniform1f(f(t,"u_godRayDecay"),r.godRayDecay),e.uniform1f(f(t,"u_godRayDensity"),r.godRayDensity),e.uniform1f(f(t,"u_godRayWeight"),r.godRayWeight),e.uniform1i(f(t,"u_godRaySamples"),Math.max(0,Math.min(32,Math.floor(r.godRaySamples)))),e.drawArrays(e.TRIANGLES,0,6)}function Ce(e){let t=v(null),a=v(null),n=v(0),s=v(0),c=v(-100),u=v(0),o=v(0),r=v(null),i=v(!1),h=v(null),f=v(new WeakMap),p=v(!1),b=v(!1),R=v(!1),C=v(!1),A=v(null),k=v(e);k.current=e;let w=v({celestial:null,cloud:null,rain:null,lightning:null,snow:null,composite:null}),T=v({a:null,b:null}),Q=N((d,l,y)=>{let g=f.current.get(l);g||(g=new Map,f.current.set(l,g));let m=g.get(y);if(m!==void 0)return m;let E=d.getUniformLocation(l,y);return g.set(y,E),E},[]),U=N(()=>{n.current&&(cancelAnimationFrame(n.current),n.current=0),b.current=!1},[]),$=N(d=>{d&&A.current&&ae(d),A.current=null},[]),G=N(()=>{U();let d=a.current,l=R.current;if(d&&!l){for(let y of Object.values(w.current))y&&d.deleteProgram(y);for(let y of[T.current.a,T.current.b])y&&(d.deleteFramebuffer(y.fbo),d.deleteTexture(y.texture));r.current&&d.deleteTexture(r.current),h.current&&d.deleteBuffer(h.current)}w.current={celestial:null,cloud:null,rain:null,lightning:null,snow:null,composite:null},T.current={a:null,b:null},r.current=null,i.current=!1,h.current=null,a.current=null,f.current=new WeakMap},[U]),P=N(({canvas:d,contextLost:l=!1,markInitFailed:y=!0,warnMessage:g,errorMessage:m})=>(l&&(R.current=!0),y&&(C.current=!0),m&&console.error(m),G(),A.current=ge(d,A.current),!1),[G]),V=N(()=>{if(C.current)return!1;let d=t.current;if(!d||A.current===!1)return!1;if(A.current===null){if(!he(d))return A.current=!1,!1;A.current=!0}G(),R.current=!1;let l=d.getContext("webgl2");if(!l)return P({canvas:d,warnMessage:"[WeatherEffectsCanvas] WebGL2 not supported; rendering without effects."});if(a.current=l,l.isContextLost())return P({canvas:d,contextLost:!0,markInitFailed:!1});if(w.current.celestial=B(l,O,se),w.current.cloud=B(l,O,oe),w.current.rain=B(l,O,ie),w.current.lightning=B(l,O,re),w.current.snow=B(l,O,le),w.current.composite=B(l,O,ue),!w.current.celestial||!w.current.composite)return l.isContextLost()?P({canvas:d,contextLost:!0,markInitFailed:!1}):P({canvas:d,errorMessage:"Failed to create required WebGL programs"});let y=k.current.dpr??window.devicePixelRatio,g=Math.max(1,Math.floor(d.clientWidth*y)),m=Math.max(1,Math.floor(d.clientHeight*y)),E=ce(l,g,m),x=ce(l,g,m);if(!E||!x)return l.isContextLost()?P({canvas:d,contextLost:!0,markInitFailed:!1}):P({canvas:d,errorMessage:"Failed to create WebGL framebuffers"});T.current.a=E,T.current.b=x;let I=l.createTexture();if(I){l.bindTexture(l.TEXTURE_2D,I),l.texImage2D(l.TEXTURE_2D,0,l.RGBA,1,1,0,l.RGBA,l.UNSIGNED_BYTE,new Uint8Array([128,128,128,255])),r.current=I;let _=new Image;_.crossOrigin="anonymous",_.onload=()=>{let S=a.current;!S||r.current!==I||(S.bindTexture(l.TEXTURE_2D,I),S.texImage2D(l.TEXTURE_2D,0,l.RGBA,l.RGBA,l.UNSIGNED_BYTE,_),S.generateMipmap(l.TEXTURE_2D),S.texParameteri(l.TEXTURE_2D,l.TEXTURE_MIN_FILTER,l.LINEAR_MIPMAP_LINEAR),S.texParameteri(l.TEXTURE_2D,l.TEXTURE_MAG_FILTER,l.LINEAR),S.texParameteri(l.TEXTURE_2D,l.TEXTURE_WRAP_S,l.REPEAT),S.texParameteri(l.TEXTURE_2D,l.TEXTURE_WRAP_T,l.CLAMP_TO_EDGE),i.current=!0)},_.src="data:image/jpeg;base64,/9j/4AAQSkZJRgABAQAAZABkAAD/4QB0RXhpZgAATU0AKgAAAAgABAEaAAUAAAABAAAAPgEbAAUAAAABAAAARgEoAAMAAAABAAIAAIdpAAQAAAABAAAATgAAAAAAAABkAAAAAQAAAGQAAAABAAKgAgAEAAAAAQAAAQCgAwAEAAAAAQAAAIAAAAAA/+0AOFBob3Rvc2hvcCAzLjAAOEJJTQQEAAAAAAAAOEJJTQQlAAAAAAAQ1B2M2Y8AsgTpgAmY7PhCfv/iAdhJQ0NfUFJPRklMRQABAQAAAcgAAAAABDAAAG1udHJSR0IgWFlaIAfgAAEAAQAAAAAAAGFjc3AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD21gABAAAAANMtAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACWRlc2MAAADwAAAAJHJYWVoAAAEUAAAAFGdYWVoAAAEoAAAAFGJYWVoAAAE8AAAAFHd0cHQAAAFQAAAAFHJUUkMAAAFkAAAAKGdUUkMAAAFkAAAAKGJUUkMAAAFkAAAAKGNwcnQAAAGMAAAAPG1sdWMAAAAAAAAAAQAAAAxlblVTAAAACAAAABwAcwBSAEcAQlhZWiAAAAAAAABvogAAOPUAAAOQWFlaIAAAAAAAAGKZAAC3hQAAGNpYWVogAAAAAAAAJKAAAA+EAAC2z1hZWiAAAAAAAAD21gABAAAAANMtcGFyYQAAAAAABAAAAAJmZgAA8qcAAA1ZAAAT0AAAClsAAAAAAAAAAG1sdWMAAAAAAAAAAQAAAAxlblVTAAAAIAAAABwARwBvAG8AZwBsAGUAIABJAG4AYwAuACAAMgAwADEANv/AABEIAIABAAMBIgACEQEDEQH/xAAfAAABBQEBAQEBAQAAAAAAAAAAAQIDBAUGBwgJCgv/xAC1EAACAQMDAgQDBQUEBAAAAX0BAgMABBEFEiExQQYTUWEHInEUMoGRoQgjQrHBFVLR8CQzYnKCCQoWFxgZGiUmJygpKjQ1Njc4OTpDREVGR0hJSlNUVVZXWFlaY2RlZmdoaWpzdHV2d3h5eoOEhYaHiImKkpOUlZaXmJmaoqOkpaanqKmqsrO0tba3uLm6wsPExcbHyMnK0tPU1dbX2Nna4eLj5OXm5+jp6vHy8/T19vf4+fr/xAAfAQADAQEBAQEBAQEBAAAAAAAAAQIDBAUGBwgJCgv/xAC1EQACAQIEBAMEBwUEBAABAncAAQIDEQQFITEGEkFRB2FxEyIygQgUQpGhscEJIzNS8BVictEKFiQ04SXxFxgZGiYnKCkqNTY3ODk6Q0RFRkdISUpTVFVWV1hZWmNkZWZnaGlqc3R1dnd4eXqCg4SFhoeIiYqSk5SVlpeYmZqio6Slpqeoqaqys7S1tre4ubrCw8TFxsfIycrS09TV1tfY2dri4+Tl5ufo6ery8/T19vf4+fr/2wBDAAQEBAQEBAgEBAgLCAgICw8LCwsLDxIPDw8PDxIWEhISEhISFhYWFhYWFhYbGxsbGxsfHx8fHyMjIyMjIyMjIyP/2wBDAQUGBgkICQ8ICA8kGRQZJCQkJCQkJCQkJCQkJCQkJCQkJCQkJCQkJCQkJCQkJCQkJCQkJCQkJCQkJCQkJCQkJCT/3QAEABD/2gAMAwEAAhEDEQA/APUAR3P5U8DPQ0n3uQKeAw5FYliiNj3pwh4yzCoiX7Gm7uxagCYhAetRniqxJJ45p4YdDQBIMnvilBb1pgJ6dqdszyOlAFlYpHHy81IInHUiqPzqecinjcxyQRQBa8l88HNJ5cg6ipI4ZWXPSl3SoMNk0AQ+U/emmNuxFPM6Z5U0/wA1W+6KAKxjYdaQKKvqV9BUyrAwGBzQBleTIRkYpRbSHuK0JWROBWe05zgUAOFu3cikMYHSoDO3bn6UwSs3DUATlcU3kHHFChugH51MsUj8KCfwoAj4xx1pOvWrS2kx6rQbSXnAP60AVghPIp2188Gpfs7A98UG3f7wz+NAERVwOWpu2SpAj5wacsj9h0oAi2MOtN2NntVmSaXaRswfXFVt8x42n8qAAr9KafbFO3L0kzmjeoOAM0Af/9D0uO9QBmjQyY564AqIXY5LIc+3IFWodPiEW6MhmIBIGR9QTVCVJN3lj5cHt/jWJRaghNy+6OTb9aq3kD28nlAkN6nimTRz2wUl1cHn5f5GoxLO5II4Y5DHmgZAGkHyknPX2pjSMgJBJzxyf5VbuJDGm4ckjnA4qGOFXBZjtXg4Pf6UAEUyrCGLnce2efyrRtbkOSBIFIHAP+NYrQyBmfB2/wANRIWi5b6daAN99Rtkz5hJPb0z70q6jA4wT+vFc3qE9tFhQfmJxuzjtnoe1ee6pqUUlztjcqV+UlTwfwoA9zOpbEwuBjuDn9Kz7jWAU2lsf7XpXkkPiC4SIRKwIAxyOeKqx3OrXc4Q/MOvBxkfiaAPSJ9ZJAVZATzkAiqj69cQgqULAjP4VnJZafHH9pvFVQBlhnge9Z8+saaUJDLtjHGBj6cHGaANA+JrpmBB2jH3c1cTxdNAyqqvk1yNtr0HnFRFGwK/K5+8D9elaOmanFJKEvnDHOfmG0EHoKLhY6seITcfvHJLemaZ/bUXkbQdpB5JNY91aQxkzQZKljwP5Cqy2SyI27dvz07fnQB0sOrQbsSTAY7j0rZsJLW4kKozEeleYPZbnxnJJwAPX3NdbpdutrEpDEnGAc8Z9qAPTUW3t0Xcxk3c4bjA+g71BLrIh5jUbM9gOnvXI3N/cQ2+6XJRxtGDyCDzii2uo5h8nzRKAeTkn2OadxWO0bxJb2sP2mcqI+hyas2viXQ73DW4yTxzwT+Ga87kWOUk3MYaPsjdP/11q28Vnbxg2sKgj06gfjRcLHpatbT8ggDHI71TuEtFiLK5LDqFx+FZVhcRso80lTnHByatzSxTzboQAeFPr+vamIx5XQAlWzWTcXSxE4IJ9fSte7tn80xuwBHGF5H5isebSXj/AHj9OceuKQxsd85+Zzk/7XQ0pvvXI5xwOBWbdCVwWVcAHjFVIpCWHmH5h17/AMqQzVeeR8spIHbpViG9hEbSsc44xms6TYj+WGzkfT+dO+wB4PNJ6YOOetAH/9H22KHZaiSFxuYYYcDHv6Vz9xAqS7d2Se471oXFnd7P3QyU/hB6g+5qa1s3dTNcKVbGADx071mO5mQWzs4D/dJq/eQ2kMPykMcYB9PpUltbpubPC46+/wCNUnUStszz+dIDGWZoWKj5QDlTVFrqaGUySKAW9eQfWti7ljKruUEg44OPzrPmBlRtqfMvIB6kUDHS6uv2UQyDLHPSsySNjGDnBByMdqSZhJEAwG/kEfy6U+G1d1YSbgMcfWkM5vUvMSMzTHOe9eXX1zFJIRbpsPRlznJ65r0DxTBcRweUgDc8k+h44/xrhBZQ2sf2iIOmM4Y4OT64PY0wuVYpHBEhOAD1qzZz+XcJcZ+63P0rGMjht2Tye/NdFpdhLfLI0S/LGpYge3WmI2bzWY/sjhTu3DZgn1rjJzNjaTleuK0JkSFgsmNvU468VVdfNO7Bwfun1x2oQBaPtwD92t7TtksnznocHFYUMa79r/KM9evauh0y1i2efGwd88j+lFgudxY3YfdDPxsBLZ4OAOtU28Q2d0Gt4ZGXBO3IwMeorm9YuHgtRcOGiMnylSOSK5SJwwyD8q8/hSGeg2uo4Z1bBI5bHOP6VtaTJ9tUtjDDkKDjI9a4Dw9fzLqJgwpjmHOeoFewxraWkYKlWUDsaVh3I9pknXePlA+6eaS5sUt/3bgjnzMDIGfSrEl7al/MhBJwODx2rON7aXsmTJlgCzAHoBQI2YLq3dApQ7vXt+RrQ6qp+UZ/CqFs0cwUKBnsRj9a2AtvAN0pDMWxjofp1xxTEXtLhF1ItsDyT8pJA596276xW2uxCrbnHX0zXnE15MzFogUO7a2e47kcc1q2F7cSLiVsunGWHJA757+lMRruLqKYhjtPas+7mnkcFGwcdK1o7qF0CTHb7nsfaufcP5nJJGePekMf5kiALJkj26USpYzAErgkcsOOlF9f3un6dJcRpuYIQF77Tzx7VxvhvUdUvb2Q6ivyHoG4x9PwoA7FbLfHvhG4KMinRLO58vBDYziiCd4bp1hG4HkAdvatm1VnidpwFLcAg96BH//S9nBkmugkZJXGD6Efp36VNJcO2+DcUCAFR7+mD+VUZrz7NJsixkd/X/8AXSAT6hOyD+6OeMisxmQ15dPJ5eB68Dt6cU6KVtv7vOV5NPmWSM7Y1xzyfXtV6G1jk+WNAu4cEUgMyC3NxIF3AHr83Hv3rTBiOOieh9ar3lrIk/lqCSVxn0PpWcs0Sv5Mm5iQev8ACaYFq+tooxvhZc4wcDFY8MkvmSLjIxycn9a33sxOFO8A8Ak1RvrGK3la3hkBGeWXJBHrSAw79Le5BBRfugN15z3rxnXrG4hlYfNtRsKPRT6ete3ReRG4acZTv71W1Gzt7/DMioG4B68DpzTGfNl3LLJIZpPyq5aX0vEaEgH1q7r9lDpl/JCuJADjHTGe9Yi7GXOMUxHThGubYzDl1Hp1rZ0/TjeWoctlVOMAfrmq2janaQ2TRPGCSD9citLTLlIDhSQDzjjb9KAGyaDJEMTNzjINULWbT9NuvMckSE4X06dutbWtarqLSxmYgj7ob2H0rmdPJ+2Pc6igZSTsIHA9OtAC3t6mtXAiuXVepBY8cfTvVC6t4rSx88ybfMJ+Qr6dMEdf6V6Lb+FrHVZkulUKTnnH5Vz/AIy0naisBhYzg47/AEoQHG2GpiyuBcgbgBjPQfUCu38N+JEuLuSC4ZSjYbDYG78a86is5biVYIh14Vfc/wCNaun2JtbkrcJjZy28cD1/GhgeseMhHDZxXFhu8thk/j1Bwe1eZ2p1O9vVgsc7znp04557YrpbbxXBb/6DeBnt+gYgcZ9fatX7dp/2fzdMZUP3V4wCTwPQ0hnQ2Ms1lp0cgGZMDIHbHBwKz5PFnk6g0F0D5XB4HzZ9MngGp9MmaHC3khdm5yB/X1PpWRrt3plrOC+5gDgjgjB5z9aQHUwX8GpOZLQ5U/d3DnNaFnJIDsJI55z0NYGkCK7aO4twscbAfh/9eutFsrW7zZ4Q8YHXnr6UAbr2iBNzZ5HTtz3qqYWiCn5Suc+vFdPpsLXumB5Tlhzk9SD2/CsSdWyykBlOeO/FMRmSXUflEOo3dm9vp0xVMxwyRqUO0k42gdvUn0q0kUUgLdMc4PpTPLAcY4J6n+lIZuQadGWVkP3uTxW3HY2bRM3mMhRTheoY+/pWNZyAJ5cj8DOw/wC0fX2q59sEsojcBM8Hb0FAj//T9MljFxOWUnr0rUms5reDejbRgYIrl7TWFtRtuCSMjgcnkVpnWXubVg5wcDYKzGXJJS8Cjk+/uPWoftQhbeCRx271jLqZiUgttLnketLPeW38QOOOaQzTn1Tz3z06ZH9cViTM3nBu5PBHvUTgTMrRMNvTJq3bwyCX1dBnPtQBuWtvcSwhpBlAMA1JLalE+ZcAdT61LZ3UkMJGAwXnBrktQ8ZXcF99kuEMYEgCLn74PQ0xE1+iMwQnHGQM9RWDefbrW3l/swh5iAAHJ2gd8e9bktpK7B9nJ/eLg/dyeR+lVvInfcsQGRznpSGebeKtF8y1S4jx5u0NIVyBuxz17ZrzSOFxFhlPXIbsfXmvo66sruK1ZZQG3/eBGQD7Zrh20ZJZRjaka9AeAO5NMDy2FdqhuQAfz9a6J9kFkJ1cbnbbtX+Zq1qd7pAjFpEN5V8nAIHPcGn2OmJAd14BINoZUJ7n+lMRPHLEdOaVPnKHJB5NdhajTLmxV1jxGQOOvP4+teevJJaBrf7pbKNtx/Ou48MwSSWX2iVsLjGBngDvz60hnWWKwiBkDhdoyPX6VpwWcE8Lm4QvwdgxwT3rHhEKbCdzZ6gDFaMU8kRBf7q9Ae9IRwg0s2es/bZYhD1K44A7ZqbW9Nku7DzYCJFwCcHkd+faux8Tqbyy860+VgD5nYfQVyWkrc2kElvPEQXA8vceGx1pjPLxZPJceWy8jr+Fej6XoE0NsLomMq5wqE5I96S7t45WWCKHG/AEmOnrmtL7JeWDxxNjGOcc5PfnpQBNBA6rnaOP/wBXSsa70CSW+WR4w6NjPbn/AOtXQ2SNKf3nBBAB9B61uRfZow0bvufjHHGPrSAy4bL7LAIyPkUbgAf84q7bSSTwGNGwPTPpVxoUvSBbnnONvrzWK8kUKvCQd/QYOKBnbeFdVjgne3u5PlxxUuqX6PcGWIDA44/nXLWtmRbiXcNzEj6Cn/a4prx7BCTLGhdhtONvAzn8aCS698SohwDQlyQyhyAqtzj0rOwPNV5Bt46j26U590UaTngvklRzgCgZu3UEd9D9pQFIw3y9j7UtoclGd84ODn+tY8eog4gQ/wD1h64qW9vfKh8lGXaCNrD7x9d2ffpQB//Uha/dIWBAyT0znp3zV2K+kazMjt90Y5NZv2FEtWEj5IXo3f2zVGFUi0zygDlGBwPQdqxLNwTSuVEfIGCc9f1rsLLy5Ysy4feDkDsa8wtphPdNeKkiHABBPy4HsD1rrNMvpYpQxPHUgdPSgLHUTpDbRGa2B2njp+dUzdTyMXLcHimy3akAAgw/1qE7G3FOMU7isdHY3cSR4Y81mmO1vrz7RdRxOEO4HYdynt1rHD7ejcVet7luYlOCeo9aAsdDPOhi+QbeMcd658S+XICg5zxnmpy0u0Kx6fyqB49i5P3X5/GgDVvtcmuYFjkCr5Y7cZ96yLuyiubYyROA2CHzg57dDUcwitLaSQ/vFIBBI5GOwrLjl+0Q+bKdqt1+XBH9aLgecz+GZZrgIMbT0OOvrWgtrCZhBC/+rIG5sndkcqPpXdWGnwGPEbkqTkZzkc9BV2CxjSbcqxnOexB+vemBwl34J1Dz87iVcgjHcevNei6D4PnsbMiQtIMjJHYe9dXp1qmxVPzZbAB46/yrfaVdrwvhTnBA5HFAjjJ7OCGT7Rt3ArgL0/Ks5bd5yZbgFUToP5V09zECCydF5+bp+VYUiRsgLuCWOOD0/CkMZPOrIscyjaBwo9feqIhBIuDjBBwOuP8ACppEiSTy13Oeh9PwxVmK1ZwBGeB2HX3oA0rPSLe9tTNwAuPTOe2KgvNIELLbSJgkZyey1HJeSafBhMu2eM9qyrnUZbqTzG3MSMdc9P5UAVLi3IudkAypOMD2qSeS2VBJEQsijkHnpwao3GoLbjefkJ4AYZzn0rlroySL50y4ZT8rA+2frzSGdHBqSyqZLZ+Aeo7Gqduly0rNIDM5J24PT/Gudtne5jLgksOuM8//AF66PTLnyJ8qDg4AByeT9aANuGO5ZNzuYyP4SO/XirUd08cTc5Vhg49RS3UxYBs8sOMfrWcy74QAASQRnOKLisawlikO8yhUx8zN2qnBdwXLC3RtwJ+929KbZaZLqdrJCnzooO72zwahisILC2KoQ204XGcj8+tMDPutStbHfFcEb87R1yQfTAqOS4XcVdQQB1ycAnvVG/kW4nG7JIx7ep6entTJ4VmiVUchjywBxx7d6LhY/9VJ3W6Kxr8pUHPPBx9axpV2wF3AGTjg8f5NdZNpuYQMAM/Uf/qpTpURsyFXJB9OKxNDiYUjEu1Mn6Vpxl1GR0HcHrWimkuCPLQ4P4VchsGg+eVDx0+tAxYrxGRQwGe4HetmNYg6yEjkZHqPY1lLZu7iRc1rJbuq7SCR60CJEiR8Lnn36CmSAq2yMKeeo6gd8Vowo6jYq5A7mphaRSOxA4XqcUCKEgkb5iSe2Cc1KAiwHglqufZj0HOcEZPI/GtKKCIoRjB75FAjkY1Z3IPyjvxmszUYDCpbBwfQYye1egLp0BIIY/SpGtI5P3Zj3Y78dKdgucJZRkKsnpjj+lblvHJNLnGB6ntWtPZx42ABQP7oH86kt7ZPLMeMf1+tAF6w2hSjEsg5LY6n/CrbTxIHUKDgcc/zqxZ2yC3KuQc44HpVW/hIYLAQFP5/jimSZ93LE8S9yRyMn8qwDbxM5ES7efWt5opEGTz+HH5VHLCUX5EAU9D9e9IZkIrW7HDYJ6fWrf2iW2BYsAWHBxStavjenT3FAtGkYB+MdD1oGYk4LzBrjcU7kHr9KqXLrDAPs8W45+8Tz+Vdf/ZJfAYg56CobizmiBVMEdsdqQHlGsTTrcp5m4qvzAkYU8cj35pdt9JceaMCJgOMEH1xzXpt1ZW8tuqyxrI6Hdn0/WuXvbS7kkCkM69if4fp3/KgZlCyRmWVcEnOVHA2/j/SrcVvFMcxAgKCBznH9DV+PQpZ4d0n3R8uC2fzB5/GrltpU9rJlJDIuO/IoAq2tvKUZ5I9wX1zxx7d6ikec/Jjan3vX2rr9NmntcsEUqx5Mg3D8qTVdMkZVuRt2HjKj+X/ANegLnM7H8tQjAcHp79qzGSRnKzg8fwgnH/666NbcxKfKRj6Fhz+dUJYLi6ZowG3nkn1P1oA5+eKBZRcSbBIRt+U7jj39DWeqk3ZdxuzyMjP61rS6GYiZpIzlxyDnj1x/wDrq1FZukJSBQnselAz/9b0o2jN8xaoZ7cLgE9eeK1fJc8bc054JD/BWJZjrbnpxUnljO09KvtbyHHy/pSGGYcBaAKWyJRwPyFMx6Zq+YG/uH8qPJJ42H8qAKwQuMck1KEkU8jH1FTCGRfuqfyNKY58520AMXOeTj6UpJXlSc0FJP7lO2SY+7QBNHdNHx14xzQ8xcj+QqHy5M5CU0pJ0xQA7gnBJNAWTPXApojkqQIR97NAFhfMUD5qP3hPbBquQxOQDT18wHjNAEwtmPJOakMLnHPSoC7Be9O+0MBhQ35UAPMORnPIqJkYnnOKDNIeMH8qZh2GDmgBCWH3CRVciVyScmrGxsYCZ/OmhJgfu4oArmEvxnn3pHtXPIq7sk/u0bZP4hmgDIktmAwCTTxEQowea0jHnquKhMGTwpIoAriNjyPzqU71Xb94elPMD54WgRygcjNAEId8bPuimBFJzzmrDRyN/CfwpFidf4TQA9RvXYfzqm9qoyODV8L/ALJpfKJOQMUAf//Z"}let z=new Float32Array([-1,-1,1,-1,-1,1,-1,1,1,-1,1,1]),L=l.createBuffer();if(!L)return l.isContextLost()?P({canvas:d,contextLost:!0,markInitFailed:!1}):P({canvas:d,errorMessage:"Failed to create WebGL buffer"});h.current=L,l.bindBuffer(l.ARRAY_BUFFER,L),l.bufferData(l.ARRAY_BUFFER,z,l.STATIC_DRAW);for(let _ of Object.values(w.current)){if(!_)continue;let S=l.getAttribLocation(_,"a_position");S>=0&&(l.enableVertexAttribArray(S),l.vertexAttribPointer(S,2,l.FLOAT,!1,0,0))}return s.current=performance.now(),!0},[G,P]),F=N(()=>{let d=a.current,l=t.current,y=w.current,g=T.current,m=k.current;if(R.current||!p.current){b.current=!1,n.current=0;return}if(!d||!l||!g.a||!g.b){b.current=!1;return}let E=m.dpr??window.devicePixelRatio,x=Math.max(1,Math.floor(l.clientWidth*E)),I=Math.max(1,Math.floor(l.clientHeight*E));(l.width!==x||l.height!==I)&&(l.width=x,l.height=I,fe(d,g.a,x,I),fe(d,g.b,x,I));let z=(performance.now()-s.current)/1e3,L=(ne,$e)=>Q(d,ne,$e);m.layers.lightning&&m.lightning.enabled&&m.lightning.autoMode&&z>=u.current&&(c.current=z,o.current=Math.random(),u.current=z+m.lightning.autoInterval*(.5+Math.random()));let _=g.a,S=g.b,X=()=>{let ne=_;_=S,S=ne};m.layers.celestial&&y.celestial?(be({gl:d,program:y.celestial,target:S,displayWidth:x,displayHeight:I,time:z,params:m.celestial,moonTexture:r.current,moonTextureLoaded:i.current,getUniformLocation:L}),X()):(ve(d,S,x,I),X()),m.layers.clouds&&y.cloud&&(we({gl:d,program:y.cloud,target:S,sceneTexture:_.texture,displayWidth:x,displayHeight:I,time:z,params:m.cloud,celestial:m.celestial,getUniformLocation:L}),X()),m.layers.rain&&y.rain&&(xe({gl:d,program:y.rain,target:S,sceneTexture:_.texture,displayWidth:x,displayHeight:I,time:z,params:m.rain,interactions:m.interactions,getUniformLocation:L}),X()),Ie(m.layers,m.lightning,y.lightning,z,c.current)&&y.lightning&&(Re({gl:d,program:y.lightning,target:S,sceneTexture:_.texture,displayWidth:x,displayHeight:I,time:z,params:m.lightning,interactions:m.interactions,lastFlashTime:c.current,strikeSeed:o.current,getUniformLocation:L}),X()),m.layers.snow&&y.snow&&(ke({gl:d,program:y.snow,target:S,sceneTexture:_.texture,displayWidth:x,displayHeight:I,time:z,params:m.snow,getUniformLocation:L}),X()),y.composite&&_e({gl:d,program:y.composite,sceneTexture:_.texture,displayWidth:x,displayHeight:I,time:z,celestial:m.celestial,interactions:m.interactions,post:m.post,lastFlashTime:c.current,strikeSeed:o.current,getUniformLocation:L}),p.current&&!R.current?(b.current=!0,n.current=requestAnimationFrame(F)):(b.current=!1,n.current=0)},[Q]);return Ke(()=>{let d=t.current;if(!d)return;let l=m=>{m.preventDefault(),R.current=!0,G()},y=()=>{R.current=!1,C.current=!1,V()&&p.current&&(b.current=!0,F())};d.addEventListener("webglcontextlost",l,{passive:!1}),d.addEventListener("webglcontextrestored",y);let g=typeof IntersectionObserver<"u"?new IntersectionObserver(m=>{let x=!!m[0]?.isIntersecting;if(p.current=x,!x){U(),G(),$(d);return}!b.current&&!R.current&&(a.current&&T.current.a&&T.current.b||V())&&(b.current=!0,F())},{threshold:0}):null;return g?g.observe(d):p.current=!0,!g&&V()&&p.current&&(b.current=!0,F()),()=>{g?.disconnect(),d.removeEventListener("webglcontextlost",l),d.removeEventListener("webglcontextrestored",y),G(),$(d)}},[G,V,$,F,U]),t}var De={celestial:!0,clouds:!0,rain:!1,lightning:!1,snow:!1},Te={timeOfDay:.5,moonPhase:.5,starDensity:.5,celestialX:.74,celestialY:.78,sunSize:.14,moonSize:.17,sunGlowIntensity:3.05,sunGlowSize:.3,sunRayCount:6,sunRayLength:3,sunRayIntensity:.1,sunRayShimmer:1,sunRayShimmerSpeed:1,moonGlowIntensity:3.45,moonGlowSize:.94,skyBrightness:1,skySaturation:1,skyContrast:1},ze={coverage:.5,density:.7,softness:.5,cloudScale:1,windSpeed:.3,windAngle:0,turbulence:.3,lightIntensity:1,ambientDarkness:.2,backlightIntensity:.5,numLayers:3},Pe={glassIntensity:.5,glassZoom:1,fallingIntensity:.6,fallingSpeed:2,fallingAngle:.1,fallingStreakLength:1,fallingLayers:4},Ee={enabled:!1,autoMode:!0,autoInterval:8,flashIntensity:1,branchDensity:.5},Ae={intensity:.5,layers:4,fallSpeed:.6,windSpeed:.3,windAngle:.2,turbulence:.3,drift:.5,flutter:.5,windShear:.5,flakeSize:1,sizeVariation:.5,opacity:.5,glowAmount:.25,sparkle:.25},Ge={rainRefractionStrength:1,lightningSceneIllumination:.6},Le={enabled:!0,haze:0,hazeHorizon:.8,hazeDesaturation:.35,hazeContrast:.6,bloomIntensity:0,bloomThreshold:.82,bloomKnee:.35,bloomRadius:1.2,bloomTapScale:1,exposureIntensity:0,exposureDesaturation:.25,exposureRecovery:1,godRayIntensity:0,godRayDecay:.965,godRayDensity:.9,godRayWeight:.35,godRaySamples:16};function M(e,t){if(!t)return{...e};let a={...e};for(let n of Object.keys(t)){let s=t[n];s!==void 0&&(a[n]=s)}return a}function We(e){return{layers:M(De,e.layers),celestial:M(Te,e.celestial),cloud:M(ze,e.cloud),rain:M(Pe,e.rain),lightning:M(Ee,e.lightning),snow:M(Ae,e.snow),interactions:M(Ge,e.interactions),post:M(Le,e.post),dpr:e.dpr}}import{jsx as qe}from"react/jsx-runtime";function Fe({className:e,dpr:t,layers:a,celestial:n,cloud:s,rain:c,lightning:u,snow:o,interactions:r,post:i}){let h=Ce(We({dpr:t,layers:a,celestial:n,cloud:s,rain:c,lightning:u,snow:o,interactions:r,post:i}));return qe("canvas",{ref:h,className:e,style:{width:"100%",height:"100%"}})}var de={clear:{dawn:{celestial:{celestialX:.69,skyBrightness:1,starDensity:.36,sunGlowIntensity:3.47,sunGlowSize:.49,sunRayCount:4,sunRayShimmer:5,sunRayShimmerSpeed:5},glass:{blur:4.5,brightness:.95}},dusk:{celestial:{celestialX:.69,skyBrightness:1,starDensity:.36,sunGlowIntensity:3.84,sunGlowSize:.43,sunRayCount:4,sunRayIntensity:.24,sunRayLength:1.37},glass:{brightness:.9}},midnight:{celestial:{celestialY:.71,moonGlowIntensity:3.23,moonPhase:.51,skyBrightness:.44,skyContrast:1,skySaturation:2,starDensity:1.72,sunGlowIntensity:1.59}},noon:{celestial:{celestialY:.71,moonPhase:.2421,skyBrightness:.8,skySaturation:1.9,starDensity:.14,sunGlowIntensity:1.72,sunGlowSize:.69,sunRayCount:4,sunRayIntensity:.19,sunRayShimmer:5,sunRayShimmerSpeed:5},glass:{brightness:1}}},cloudy:{dawn:{celestial:{celestialX:.69,celestialY:.74,skyBrightness:1,skyContrast:1.1,skySaturation:.88,starDensity:.36,sunGlowIntensity:2.4,sunGlowSize:.38,sunRayShimmer:5,sunRayShimmerSpeed:5},cloud:{ambientDarkness:1,backlightIntensity:1.07,cloudScale:1.73,coverage:.81,density:1.1,lightIntensity:.67,numLayers:1,softness:.42,windSpeed:.04},glass:{blur:3,brightness:.95},post:{bloomIntensity:.99,bloomKnee:.58,bloomRadius:5.5,bloomTapScale:1.39,bloomThreshold:.78,exposureIntensity:1.14,exposureRecovery:1.95,godRayDecay:.857,godRayDensity:.79,godRayIntensity:1.08,godRaySamples:121,godRayWeight:.42}},dusk:{celestial:{celestialX:.69,celestialY:.74,skyContrast:.99,skySaturation:1.38,starDensity:.11,sunGlowIntensity:3.21,sunGlowSize:.47,sunRayShimmer:5,sunRayShimmerSpeed:5},cloud:{ambientDarkness:.54,backlightIntensity:.78,cloudScale:1.73,coverage:.81,density:.9,lightIntensity:.78,numLayers:1,softness:.42,windSpeed:.04},glass:{brightness:.9},post:{bloomIntensity:1.11,bloomKnee:.58,bloomRadius:5.5,bloomTapScale:1.39,bloomThreshold:.78,exposureIntensity:1.14,exposureRecovery:1.95,godRayDecay:.857,godRayDensity:.79,godRayIntensity:1.08,godRaySamples:121,godRayWeight:.42}},midnight:{celestial:{celestialX:.74,celestialY:.71,moonGlowIntensity:3.55,moonGlowSize:.97,starDensity:1.72,sunRayShimmer:5,sunRayShimmerSpeed:5},cloud:{cloudScale:1.73,coverage:.81,density:1.02,numLayers:1,softness:.42,windSpeed:.04},post:{bloomIntensity:1.11,bloomKnee:.58,bloomRadius:5.5,bloomTapScale:1.39,bloomThreshold:.78,exposureIntensity:1.14,exposureRecovery:1.95,godRayDecay:.857,godRayDensity:.79,godRayIntensity:1.08,godRaySamples:121,godRayWeight:.42}},noon:{celestial:{celestialX:.74,celestialY:.71,moonPhase:.2421,skyBrightness:.84,skyContrast:1.31,skySaturation:1.44,starDensity:.14,sunGlowIntensity:1.35,sunGlowSize:.61,sunRayCount:4,sunRayIntensity:.22,sunRayShimmer:5,sunRayShimmerSpeed:5},cloud:{ambientDarkness:0,backlightIntensity:.45,cloudScale:1.73,coverage:.81,density:1.06,lightIntensity:.44,numLayers:1,softness:.42,windSpeed:.04},glass:{brightness:1},post:{bloomIntensity:.42,bloomKnee:.58,bloomRadius:5.5,bloomTapScale:1.39,bloomThreshold:.76,exposureIntensity:1.14,exposureRecovery:1.95,godRayDecay:.884,godRayDensity:.79,godRayIntensity:1.33,godRaySamples:121,godRayWeight:.42}}},drizzle:{dawn:{celestial:{celestialX:.69,celestialY:.74,moonGlowIntensity:3.55,moonGlowSize:.97,skyBrightness:.96,skyContrast:.84,skySaturation:.94,starDensity:1.72},cloud:{ambientDarkness:.7,backlightIntensity:.82,cloudScale:1.21,coverage:.61,density:.98,lightIntensity:0,numLayers:1,softness:.17,turbulence:.16,windSpeed:.02},glass:{brightness:.95},rain:{fallingAngle:.02,fallingIntensity:.13,fallingSpeed:2.24,fallingStreakLength:.32,glassIntensity:.29,glassZoom:.65}},dusk:{celestial:{celestialX:.69,celestialY:.74,moonGlowIntensity:3.55,moonGlowSize:.97,skyBrightness:.77,skyContrast:.84,skySaturation:.85,starDensity:1.72,sunGlowIntensity:2.73,sunRayLength:1.45},cloud:{ambientDarkness:.7,backlightIntensity:.82,cloudScale:1.21,coverage:.61,density:.98,lightIntensity:0,numLayers:1,softness:.17,turbulence:.16,windSpeed:.02},glass:{brightness:.9},rain:{fallingAngle:.02,fallingIntensity:.13,fallingSpeed:2.24,fallingStreakLength:.32,glassIntensity:.29,glassZoom:.65}},midnight:{celestial:{celestialX:.74,celestialY:.71,moonGlowIntensity:3.55,moonGlowSize:.97,skyBrightness:.77,skyContrast:.84,skySaturation:.85,starDensity:1.72,sunGlowIntensity:2.73,sunRayLength:1.45},cloud:{ambientDarkness:.7,backlightIntensity:.82,cloudScale:1.21,coverage:.61,density:.98,lightIntensity:0,numLayers:1,softness:.17,turbulence:.16,windSpeed:.02},rain:{fallingAngle:.02,fallingIntensity:.13,fallingSpeed:2.24,fallingStreakLength:.32,glassIntensity:.2,glassZoom:.5}},noon:{celestial:{celestialX:.74,celestialY:.71,moonGlowIntensity:3.55,moonGlowSize:.97,skyBrightness:.77,skyContrast:.84,skySaturation:.85,starDensity:1.72,sunGlowIntensity:2.73,sunRayLength:1.45},cloud:{ambientDarkness:.7,backlightIntensity:.82,cloudScale:1.21,coverage:.61,density:.98,lightIntensity:0,numLayers:1,softness:.17,turbulence:.16,windSpeed:.02},glass:{brightness:1},rain:{fallingAngle:.02,fallingIntensity:.13,fallingSpeed:2.24,fallingStreakLength:.32,glassIntensity:.29,glassZoom:.65}}},fog:{dawn:{celestial:{celestialY:.71,skyBrightness:1.05,skyContrast:.41,skySaturation:1.25,starDensity:.11,sunGlowIntensity:2.42,sunGlowSize:.38,sunRayIntensity:.05,sunRayLength:1.59},cloud:{ambientDarkness:1,backlightIntensity:.54,cloudScale:.64,density:1.5,numLayers:6,softness:.77,turbulence:.67,windSpeed:.02},glass:{brightness:.95}},dusk:{celestial:{celestialY:.71,skyBrightness:1.02,skyContrast:.62,skySaturation:.88,starDensity:.11,sunGlowIntensity:2.42,sunGlowSize:.38,sunRayIntensity:.05,sunRayLength:1.59},cloud:{ambientDarkness:0,backlightIntensity:.54,cloudScale:.93,numLayers:6,softness:.5,turbulence:1,windSpeed:.03},glass:{brightness:.9}},midnight:{celestial:{celestialY:.71,moonGlowIntensity:2.74,moonGlowSize:1.43,moonPhase:.5,skyBrightness:1.02,skyContrast:.62,skySaturation:.88,starDensity:.11,sunGlowIntensity:2.42,sunGlowSize:.38,sunRayIntensity:.05,sunRayLength:1.59},cloud:{ambientDarkness:0,backlightIntensity:1.4,cloudScale:.91,density:1.27,lightIntensity:2,numLayers:6,softness:.63,turbulence:.86,windSpeed:.03}},noon:{celestial:{celestialY:.71,skyBrightness:.76,skyContrast:.47,skySaturation:.6,starDensity:.11,sunGlowIntensity:.92,sunGlowSize:.84,sunRayCount:0,sunRayIntensity:0,sunRayLength:0},cloud:{ambientDarkness:1,backlightIntensity:.72,cloudScale:.83,density:1.5,numLayers:6,softness:.84,turbulence:.86,windSpeed:.03},glass:{brightness:1}}},hail:{dawn:{celestial:{celestialX:.69,celestialY:.74,skyBrightness:.42,skySaturation:.74,starDensity:.36,sunGlowIntensity:5,sunGlowSize:.36,sunRayShimmer:5,sunRayShimmerSpeed:5},cloud:{ambientDarkness:.75,backlightIntensity:.64,density:.83,lightIntensity:.55,numLayers:1,turbulence:.55,windAngle:-3.14,windSpeed:.14},glass:{brightness:.95},interactions:{rainRefractionStrength:.96},post:{bloomIntensity:.13,bloomThreshold:.64,godRayIntensity:.56,haze:.11,hazeHorizon:1},rain:{fallingIntensity:.42,fallingLayers:1,fallingSpeed:3,fallingStreakLength:.56,glassIntensity:1,glassZoom:.69},snow:{drift:.63,fallSpeed:8,flakeSize:.77,flutter:.79,glowAmount:.29,intensity:.81,layers:8,opacity:1,sizeVariation:.48,sparkle:.44,turbulence:.91,windAngle:2.23,windShear:.78,windSpeed:.99}},dusk:{celestial:{celestialX:.69,celestialY:.74,skyBrightness:.47,skyContrast:1.39,skySaturation:1.42,starDensity:.11,sunGlowIntensity:3.21,sunGlowSize:.51,sunRayShimmer:5,sunRayShimmerSpeed:5},cloud:{ambientDarkness:.75,backlightIntensity:.64,density:.83,lightIntensity:.79,numLayers:1,turbulence:.55,windAngle:-3.14,windSpeed:.14},glass:{brightness:.9},interactions:{rainRefractionStrength:.96},post:{bloomIntensity:.13,bloomThreshold:.64,godRayIntensity:.56,haze:.15,hazeHorizon:1},rain:{fallingIntensity:.42,fallingLayers:1,fallingSpeed:3,fallingStreakLength:.56,glassIntensity:.72,glassZoom:.69},snow:{drift:.63,fallSpeed:8,flakeSize:.77,flutter:.79,glowAmount:.71,intensity:.47,layers:8,opacity:1,sizeVariation:.77,sparkle:.1,turbulence:.91,windAngle:2.23,windShear:.78,windSpeed:1}},midnight:{celestial:{celestialX:.74,celestialY:.71,moonGlowIntensity:3.55,moonGlowSize:.97,skyBrightness:.42,skySaturation:.74,starDensity:1.72,sunRayShimmer:5,sunRayShimmerSpeed:5},cloud:{ambientDarkness:.75,backlightIntensity:.64,density:.83,lightIntensity:1.21,numLayers:1,turbulence:.55,windAngle:-3.14,windSpeed:.14},interactions:{rainRefractionStrength:.96},post:{bloomIntensity:.13,bloomThreshold:.64,godRayIntensity:.56,haze:.14,hazeHorizon:1},rain:{fallingIntensity:.42,fallingLayers:1,fallingSpeed:3,fallingStreakLength:.56,glassIntensity:.72,glassZoom:.69},snow:{drift:.63,fallSpeed:8,flakeSize:.77,flutter:.79,glowAmount:.71,intensity:.47,layers:8,opacity:1,sizeVariation:.85,sparkle:.1,turbulence:.87,windAngle:2.23,windShear:.78,windSpeed:1.11}},noon:{celestial:{celestialX:.74,celestialY:.71,moonPhase:.2421,skyBrightness:.56,skyContrast:1.03,skySaturation:.62,starDensity:.14,sunGlowIntensity:2.5,sunGlowSize:.54,sunRayCount:3,sunRayShimmer:5,sunRayShimmerSpeed:5},cloud:{ambientDarkness:1,backlightIntensity:.21,coverage:.74,density:.95,lightIntensity:.6,numLayers:1,softness:.36,turbulence:.55,windAngle:-3.14,windSpeed:.14},glass:{brightness:1},interactions:{rainRefractionStrength:.96},post:{bloomIntensity:.13,bloomThreshold:.64,godRayIntensity:.56,haze:.23,hazeHorizon:1},rain:{fallingIntensity:.42,fallingLayers:1,fallingSpeed:3,fallingStreakLength:.56,glassIntensity:.72,glassZoom:.69},snow:{drift:.63,fallSpeed:8,flakeSize:.77,flutter:.79,glowAmount:.71,intensity:.8,layers:8,opacity:1,sizeVariation:.77,sparkle:.1,turbulence:.91,windAngle:2.23,windShear:.78,windSpeed:.89}}},"heavy-rain":{dawn:{celestial:{celestialX:.69,celestialY:.74,moonGlowIntensity:3.55,moonGlowSize:.97,moonPhase:.3776,skyBrightness:.87,skySaturation:.56,starDensity:1.72,sunGlowIntensity:3.75,sunGlowSize:.43},cloud:{ambientDarkness:1,backlightIntensity:1.35,cloudScale:1.52,coverage:.53,density:1.17,lightIntensity:.06,numLayers:1,softness:.25,turbulence:.45,windSpeed:.05},glass:{brightness:.95},interactions:{rainRefractionStrength:1},post:{bloomIntensity:.36,godRayDecay:.838,godRayDensity:.56,godRayIntensity:1.11,godRaySamples:35,godRayWeight:.66,haze:.14,hazeHorizon:1},rain:{fallingAngle:.3,fallingLayers:6,fallingSpeed:3,fallingStreakLength:2,glassIntensity:.98,glassZoom:.58}},dusk:{celestial:{celestialX:.69,celestialY:.74,moonGlowIntensity:3.55,moonGlowSize:.97,moonPhase:.4115,moonSize:.17,skyBrightness:.96,skyContrast:.99,skySaturation:.72,starDensity:1.72,sunGlowIntensity:4.15,sunGlowSize:.35,sunRayCount:6,sunRayIntensity:.1,sunRayLength:3,sunSize:.14},cloud:{ambientDarkness:1,backlightIntensity:1.8,cloudScale:2.02,coverage:.72,density:.94,lightIntensity:0,numLayers:1,softness:.17,turbulence:.51,windAngle:0,windSpeed:.05},glass:{brightness:.9},interactions:{rainRefractionStrength:1},post:{bloomIntensity:.28,godRayWeight:.55,haze:.21,hazeHorizon:1},rain:{fallingAngle:.3,fallingIntensity:1,fallingLayers:6,fallingSpeed:3,fallingStreakLength:2,glassIntensity:.98,glassZoom:.58}},midnight:{celestial:{celestialX:.74,celestialY:.71,moonGlowIntensity:3.55,moonGlowSize:.97,moonPhase:.3776,moonSize:.17,skyBrightness:1.11,skyContrast:1,skySaturation:.78,starDensity:1.72,sunGlowIntensity:3.05,sunGlowSize:.3,sunRayCount:6,sunRayIntensity:.1,sunRayLength:3,sunSize:.14},cloud:{ambientDarkness:1,backlightIntensity:.81,cloudScale:2.16,coverage:.72,density:1.5,lightIntensity:0,numLayers:1,softness:.37,turbulence:.55,windAngle:0,windSpeed:.05},glass:{blur:1,brightness:1.15},interactions:{rainRefractionStrength:1},rain:{fallingAngle:.3,fallingIntensity:1,fallingLayers:6,fallingSpeed:3,fallingStreakLength:2,glassIntensity:.98,glassZoom:.58}},noon:{celestial:{celestialY:.71,moonGlowIntensity:3.55,moonGlowSize:.97,moonPhase:.3776,skyBrightness:.81,skyContrast:.54,skySaturation:.36,starDensity:1.68,sunGlowIntensity:3.7,sunGlowSize:.22,sunRayCount:0,sunRayIntensity:0,sunRayLength:0},cloud:{ambientDarkness:1,backlightIntensity:.59,cloudScale:1.8,coverage:.58,density:.87,lightIntensity:0,numLayers:1,softness:.2,turbulence:.24,windSpeed:.05},glass:{brightness:1},interactions:{rainRefractionStrength:1},post:{godRayIntensity:1.24,godRaySamples:0,godRayWeight:.6},rain:{fallingAngle:.3,fallingSpeed:3,glassIntensity:.98,glassZoom:.58}}},overcast:{dawn:{celestial:{celestialY:.71,moonGlowIntensity:1.91,moonGlowSize:.32,moonPhase:.5,skyBrightness:1.36,skyContrast:.73,skySaturation:1.6,starDensity:.11,sunGlowIntensity:5.1,sunGlowSize:.27,sunRayCount:6,sunRayIntensity:.05,sunRayLength:.6},cloud:{ambientDarkness:0,backlightIntensity:1.41,cloudScale:1.9,density:.3,lightIntensity:.75,numLayers:8,softness:.77,turbulence:.66,windSpeed:.04},glass:{brightness:.95},post:{bloomThreshold:.81,godRayIntensity:.32,godRayWeight:.3,haze:.15,hazeContrast:0,hazeHorizon:1}},dusk:{celestial:{celestialY:.71,moonGlowIntensity:1.91,moonGlowSize:.32,moonPhase:.5,skyBrightness:1.12,skyContrast:.83,skySaturation:2,starDensity:.11,sunGlowIntensity:4.15,sunGlowSize:.28,sunRayCount:6,sunRayIntensity:.05,sunRayLength:.6},cloud:{ambientDarkness:0,backlightIntensity:1.41,cloudScale:1.9,density:.3,lightIntensity:.78,numLayers:8,softness:.77,turbulence:.66,windSpeed:.04},glass:{brightness:.9},post:{bloomThreshold:.81,godRayIntensity:.32,godRayWeight:.3,haze:.15,hazeContrast:0,hazeHorizon:1}},midnight:{celestial:{celestialY:.71,moonGlowIntensity:2.75,moonGlowSize:1.54,moonPhase:.5,skyBrightness:1.58,skyContrast:.84,skySaturation:1.15,starDensity:.11,sunGlowIntensity:2.42,sunGlowSize:.38,sunRayCount:6,sunRayIntensity:.05,sunRayLength:1.59},cloud:{ambientDarkness:0,backlightIntensity:1.41,cloudScale:1.9,coverage:.9,density:1.16,lightIntensity:0,numLayers:8,softness:.87,turbulence:.66,windSpeed:.04},post:{bloomThreshold:.81,godRayIntensity:.02,godRayWeight:.15,haze:.35,hazeContrast:0,hazeHorizon:.96}},noon:{celestial:{celestialY:.71,moonGlowIntensity:1.91,moonGlowSize:.32,moonPhase:.5,skyBrightness:1.16,skyContrast:.53,skySaturation:.88,starDensity:.11,sunGlowIntensity:5.2,sunGlowSize:.38,sunRayCount:6,sunRayIntensity:.05,sunRayLength:1.59},cloud:{ambientDarkness:0,backlightIntensity:.9,cloudScale:1.9,coverage:.95,density:.3,lightIntensity:.08,numLayers:8,softness:.77,turbulence:.66,windSpeed:.04},glass:{brightness:1},post:{bloomThreshold:.81,godRayIntensity:.12,godRayWeight:.2,haze:.15,hazeContrast:0,hazeHorizon:1}}},"partly-cloudy":{dawn:{celestial:{celestialY:.71,moonGlowIntensity:3.55,moonGlowSize:.97,moonPhase:.5,starDensity:1.72,sunGlowIntensity:3.89,sunGlowSize:.38,sunRayCount:4,sunRayIntensity:.17},cloud:{ambientDarkness:.42,coverage:.37,density:1.24,lightIntensity:1.42,softness:.28,windSpeed:.01},glass:{blur:3,brightness:.95}},dusk:{celestial:{celestialY:.71,moonGlowIntensity:3.55,moonGlowSize:.97,moonPhase:.5,starDensity:1.72,sunGlowIntensity:3.89,sunGlowSize:.38,sunRayCount:4,sunRayIntensity:.17},cloud:{ambientDarkness:.42,cloudScale:1.68,coverage:.37,density:1.24,lightIntensity:1.42,softness:.26,windSpeed:.01},glass:{brightness:.9}},midnight:{celestial:{celestialY:.71,moonGlowIntensity:3.55,moonGlowSize:.97,moonPhase:.5,starDensity:1.72},cloud:{ambientDarkness:.42,backlightIntensity:1.23,cloudScale:1.11,coverage:.37,density:1.24,lightIntensity:1.42,numLayers:5,softness:.21,turbulence:.18,windSpeed:.01}},noon:{celestial:{celestialY:.71,moonGlowIntensity:3.55,moonGlowSize:.97,moonPhase:.5,starDensity:1.72},cloud:{ambientDarkness:.42,backlightIntensity:0,cloudScale:1.86,coverage:.48,density:1.71,lightIntensity:.67,numLayers:2,softness:.41,windSpeed:.01},glass:{brightness:1}}},rain:{dawn:{celestial:{celestialX:.69,celestialY:.74,moonGlowIntensity:3.55,moonGlowSize:.97,skyBrightness:.96,skyContrast:.84,skySaturation:.94,starDensity:1.72},cloud:{ambientDarkness:.7,backlightIntensity:.82,cloudScale:1.13,coverage:.72,density:.94,lightIntensity:0,numLayers:1,softness:.17,turbulence:.34,windSpeed:.05},glass:{brightness:.95},interactions:{rainRefractionStrength:.76},rain:{fallingAngle:0,fallingIntensity:.84,fallingLayers:4,fallingSpeed:3,fallingStreakLength:.54,glassIntensity:.38,glassZoom:.5}},dusk:{celestial:{celestialX:.69,celestialY:.74,moonGlowIntensity:3.55,moonGlowSize:.97,moonPhase:.4115,moonSize:.17,skyBrightness:.96,skyContrast:.84,skySaturation:.94,starDensity:1.72,sunGlowIntensity:3.05,sunGlowSize:.3,sunRayCount:6,sunRayIntensity:.1,sunRayLength:3,sunSize:.14},cloud:{ambientDarkness:1,backlightIntensity:.82,cloudScale:1.13,coverage:.72,density:.94,lightIntensity:0,numLayers:1,softness:.17,turbulence:.34,windAngle:0,windSpeed:.05},glass:{brightness:.9},interactions:{rainRefractionStrength:.76},lightning:{enabled:!0},rain:{fallingAngle:0,fallingIntensity:.84,fallingLayers:5,fallingSpeed:3,fallingStreakLength:.54,glassIntensity:.38,glassZoom:.5}},midnight:{celestial:{celestialX:.74,celestialY:.71,moonGlowIntensity:3.55,moonGlowSize:.97,moonPhase:.3776,moonSize:.17,skyBrightness:1.11,skyContrast:1,skySaturation:.78,starDensity:1.72,sunGlowIntensity:3.05,sunGlowSize:.3,sunRayCount:6,sunRayIntensity:.1,sunRayLength:3,sunSize:.14},cloud:{ambientDarkness:1,backlightIntensity:.77,cloudScale:1.13,coverage:.72,density:1,lightIntensity:0,numLayers:1,softness:.23,turbulence:.34,windAngle:0,windSpeed:.05},interactions:{rainRefractionStrength:.76},rain:{fallingAngle:0,fallingIntensity:.84,fallingLayers:5,fallingSpeed:3,fallingStreakLength:.54,glassIntensity:.38,glassZoom:.5}},noon:{celestial:{celestialX:.74,celestialY:.71,moonGlowIntensity:3.55,moonGlowSize:.97,moonPhase:.3776,moonSize:.17,skyBrightness:1.12,skyContrast:.49,skySaturation:.59,starDensity:1.72,sunGlowIntensity:3.05,sunGlowSize:.22,sunRayCount:0,sunRayIntensity:0,sunRayLength:0,sunSize:.14},cloud:{ambientDarkness:1,backlightIntensity:.59,cloudScale:1.13,coverage:.79,density:1.08,lightIntensity:0,numLayers:1,softness:.23,turbulence:.34,windAngle:0,windSpeed:.05},glass:{brightness:1},interactions:{rainRefractionStrength:.76},lightning:{enabled:!0},rain:{fallingAngle:0,fallingIntensity:.84,fallingLayers:5,fallingSpeed:3,fallingStreakLength:.54,glassIntensity:.38,glassZoom:.5}}},sleet:{dawn:{celestial:{celestialX:.69,celestialY:.74,skyBrightness:.82,starDensity:.36,sunGlowIntensity:5},cloud:{ambientDarkness:1,backlightIntensity:1.27,turbulence:.56,windSpeed:.03},glass:{brightness:.95},rain:{fallingAngle:.31,fallingIntensity:.22,fallingSpeed:2.07,fallingStreakLength:2,glassIntensity:.41,glassZoom:.85},snow:{drift:1,fallSpeed:8,flakeSize:.85,flutter:.98,glowAmount:.09,intensity:.34,layers:8,opacity:.78,sizeVariation:1,sparkle:.48,turbulence:.92,windAngle:1.76,windShear:1,windSpeed:1.8}},dusk:{celestial:{celestialX:.69,celestialY:.74,starDensity:.36,sunGlowIntensity:5},cloud:{turbulence:.56,windSpeed:.03},glass:{brightness:.9},lightning:{enabled:!0},rain:{fallingAngle:.31,fallingIntensity:.22,fallingSpeed:2.07,fallingStreakLength:2,glassIntensity:.41,glassZoom:.85},snow:{drift:1,fallSpeed:8,flakeSize:.85,flutter:.98,glowAmount:.09,intensity:.34,layers:8,opacity:.78,sizeVariation:1,sparkle:.48,windAngle:1.76,windShear:1,windSpeed:1.8}},midnight:{celestial:{celestialX:.69,celestialY:.74,starDensity:.36,sunGlowIntensity:5},cloud:{turbulence:.56,windSpeed:.03},lightning:{enabled:!0},rain:{fallingAngle:.31,fallingIntensity:.22,fallingSpeed:2.07,fallingStreakLength:2,glassIntensity:.41,glassZoom:.85},snow:{drift:1,fallSpeed:8,flakeSize:.85,flutter:.98,glowAmount:.09,intensity:.34,layers:8,opacity:.78,sizeVariation:1,sparkle:.48,windAngle:1.76,windShear:1,windSpeed:1.8}},noon:{celestial:{celestialX:.69,celestialY:.74,starDensity:.36,sunGlowIntensity:5},cloud:{turbulence:.56,windSpeed:.03},glass:{brightness:1},lightning:{enabled:!0},rain:{fallingAngle:.31,fallingIntensity:.22,fallingSpeed:2.07,fallingStreakLength:2,glassIntensity:.41,glassZoom:.85},snow:{drift:1,fallSpeed:8,flakeSize:.85,flutter:.98,glowAmount:.09,intensity:.34,layers:8,opacity:.78,sizeVariation:1,sparkle:.48,turbulence:1,windAngle:1.76,windShear:1,windSpeed:1.8}}},snow:{dawn:{celestial:{celestialY:.71,moonGlowIntensity:3.55,moonGlowSize:.97,skyBrightness:.6,skyContrast:0,skySaturation:1.5,starDensity:1.72},cloud:{ambientDarkness:.94,backlightIntensity:1.14,density:1.19,lightIntensity:1.73,turbulence:.3,windSpeed:.04},glass:{brightness:.95,chromaticAberration:2,depth:8,strength:10},post:{godRayIntensity:.4,haze:.03,hazeContrast:.05,hazeDesaturation:0,hazeHorizon:1},snow:{drift:1,fallSpeed:1.5,flakeSize:1.05,flutter:1,glowAmount:.44,intensity:.53,layers:8,opacity:.4,sizeVariation:.63,sparkle:.97,turbulence:.59,windAngle:1.95,windShear:.8,windSpeed:2}},dusk:{celestial:{celestialY:.71,moonGlowIntensity:3.55,moonGlowSize:.97,skyBrightness:1.01,skyContrast:.86,skySaturation:2,starDensity:1.72,sunGlowIntensity:4.9},cloud:{ambientDarkness:.9,backlightIntensity:1.53,cloudScale:1.82,coverage:.62,density:1.19,lightIntensity:.55,softness:.31,turbulence:.3,windSpeed:.04},glass:{blur:2,chromaticAberration:2,depth:8,strength:10},post:{godRayIntensity:.4,haze:.03,hazeContrast:.05,hazeDesaturation:0,hazeHorizon:1},snow:{drift:1,fallSpeed:1.5,flakeSize:1.05,flutter:1,glowAmount:.88,intensity:.96,layers:8,opacity:.34,sizeVariation:.63,sparkle:.97,turbulence:.59,windAngle:1.95,windShear:.8,windSpeed:2}},midnight:{celestial:{celestialY:.71,moonGlowIntensity:3.55,moonGlowSize:.97,moonPhase:.5,skyBrightness:1.07,skyContrast:.59,skySaturation:1.57,starDensity:1.72},cloud:{ambientDarkness:.04,cloudScale:1.15,coverage:.5,density:1.19,lightIntensity:1.29,softness:.14,turbulence:.3,windAngle:-.08,windSpeed:.04},glass:{blur:1,chromaticAberration:2,depth:8,strength:10},post:{godRayIntensity:.4,haze:.03,hazeContrast:.05,hazeDesaturation:0,hazeHorizon:1},snow:{drift:1,fallSpeed:1.5,flakeSize:1.05,flutter:1,glowAmount:.88,intensity:.53,layers:8,opacity:.34,sizeVariation:.63,sparkle:.97,turbulence:.59,windAngle:1.95,windShear:.8,windSpeed:1.44}},noon:{celestial:{celestialY:.71,moonGlowIntensity:3.55,moonGlowSize:.97,skyBrightness:.35,skyContrast:.69,skySaturation:.73,starDensity:1.72,sunGlowIntensity:3.45,sunGlowSize:.29,sunRayCount:0},cloud:{ambientDarkness:1,backlightIntensity:1.01,density:1.19,lightIntensity:.57,turbulence:.3,windSpeed:.04},glass:{blur:1,brightness:1.15,chromaticAberration:2,depth:8,saturation:1.4,strength:10},post:{godRayIntensity:.4,haze:.03,hazeContrast:.05,hazeDesaturation:0,hazeHorizon:1},snow:{drift:1,fallSpeed:1.5,flakeSize:1.28,flutter:1,glowAmount:.47,intensity:.53,layers:8,opacity:.34,sizeVariation:.63,sparkle:1,turbulence:.59,windAngle:1.95,windShear:.8,windSpeed:2}}},thunderstorm:{dawn:{celestial:{celestialX:.69,celestialY:.74,moonGlowIntensity:0,moonGlowSize:0,skyBrightness:.78,skyContrast:1.03,skySaturation:.94,starDensity:1.72,sunGlowIntensity:5.1,sunRayCount:5,sunRayIntensity:.13,sunRayLength:1.7,sunRayShimmer:5,sunRayShimmerSpeed:5},cloud:{backlightIntensity:.55,cloudScale:1.56,coverage:.71,density:.97,lightIntensity:0,numLayers:1,softness:.28,turbulence:.36,windSpeed:.04},glass:{blur:1,brightness:.9,chromaticAberration:2,strength:45},interactions:{lightningSceneIllumination:.83,rainRefractionStrength:2},lightning:{autoInterval:8,branchDensity:.83,enabled:!0,flashIntensity:1.57},post:{bloomIntensity:.39,exposureIntensity:.95,exposureRecovery:4.5,godRayDecay:.894,godRayDensity:.91,godRayIntensity:.26,godRaySamples:39,godRayWeight:.36,haze:.07,hazeContrast:0,hazeHorizon:1},rain:{fallingAngle:.02,fallingLayers:12,fallingSpeed:3,fallingStreakLength:1.46,glassIntensity:1.71,glassZoom:.51}},dusk:{celestial:{celestialX:.69,celestialY:.74,moonGlowIntensity:0,moonGlowSize:0,skyBrightness:.91,skyContrast:1.03,skySaturation:1.19,starDensity:1.72,sunGlowIntensity:5.05,sunRayShimmer:5,sunRayShimmerSpeed:5},cloud:{ambientDarkness:1,backlightIntensity:.55,cloudScale:1.56,coverage:.71,density:.97,lightIntensity:0,numLayers:1,softness:.28,turbulence:.36,windSpeed:.04},glass:{blur:1,brightness:.9,chromaticAberration:2,strength:45},interactions:{lightningSceneIllumination:.83,rainRefractionStrength:2},lightning:{branchDensity:.83,enabled:!0,flashIntensity:1.57},post:{bloomIntensity:.44,exposureIntensity:1.1,exposureRecovery:4.5,haze:.07,hazeContrast:0,hazeHorizon:1},rain:{fallingAngle:.02,fallingLayers:12,fallingSpeed:3,fallingStreakLength:1.46,glassIntensity:1.32,glassZoom:.51}},midnight:{celestial:{celestialY:.71,moonGlowIntensity:4.55,moonGlowSize:1.25,moonPhase:.5,skyBrightness:.78,skyContrast:1.03,skySaturation:.74,starDensity:1.72,sunRayShimmer:5,sunRayShimmerSpeed:5},cloud:{ambientDarkness:.88,backlightIntensity:.55,cloudScale:1.56,coverage:.71,density:.97,lightIntensity:0,numLayers:1,softness:.28,turbulence:.36,windSpeed:.04},glass:{blur:1,brightness:.9,chromaticAberration:2,strength:45},interactions:{lightningSceneIllumination:.83,rainRefractionStrength:2},lightning:{autoInterval:8.5,branchDensity:.89,enabled:!0,flashIntensity:1.57},post:{bloomIntensity:.44,exposureIntensity:1.1,exposureRecovery:4.5,haze:.07,hazeContrast:0,hazeHorizon:1},rain:{fallingAngle:.02,fallingLayers:12,fallingSpeed:3,fallingStreakLength:1.46,glassIntensity:1.32,glassZoom:.51}},noon:{celestial:{celestialY:.71,moonGlowIntensity:0,moonGlowSize:0,skyBrightness:.74,skyContrast:1.07,skySaturation:.55,starDensity:1.72,sunGlowIntensity:2.7,sunGlowSize:.51,sunRayCount:0,sunRayIntensity:0,sunRayLength:0,sunRayShimmer:5,sunRayShimmerSpeed:5},cloud:{ambientDarkness:1,backlightIntensity:.55,cloudScale:1.56,coverage:.71,density:.97,lightIntensity:0,numLayers:1,softness:.28,turbulence:.36,windSpeed:.04},glass:{blur:1,brightness:.9,chromaticAberration:2,strength:45},interactions:{lightningSceneIllumination:.83,rainRefractionStrength:2},lightning:{enabled:!0,flashIntensity:1.57},post:{bloomIntensity:.44,exposureIntensity:1.1,exposureRecovery:4.5,haze:.07,hazeContrast:0,hazeHorizon:1},rain:{fallingAngle:.02,fallingLayers:12,fallingSpeed:3,fallingStreakLength:1.46,glassIntensity:1.32,glassZoom:.51}}},windy:{dawn:{celestial:{celestialX:.69,celestialY:.74,skyBrightness:1,starDensity:.36,sunGlowIntensity:5},cloud:{cloudScale:.85,coverage:.4,windSpeed:.17},glass:{brightness:.95}},dusk:{celestial:{celestialX:.69,celestialY:.74,starDensity:.11,sunGlowIntensity:3.21,sunGlowSize:.51},cloud:{cloudScale:.85,coverage:.4,windSpeed:.17},glass:{brightness:.9}},midnight:{celestial:{celestialX:.74,celestialY:.71,moonGlowIntensity:3.55,moonGlowSize:.97,starDensity:1.72},cloud:{cloudScale:.85,coverage:.4,windSpeed:.17}},noon:{celestial:{celestialX:.74,celestialY:.71,moonPhase:.2421,starDensity:.14,sunGlowIntensity:1.59,sunGlowSize:.55},cloud:{cloudScale:.85,coverage:.4,windSpeed:.17},glass:{brightness:1}}}};function H(e){if(!e)return .5;let t=new Date(e),a=t.getUTCHours(),n=t.getUTCMinutes();return(a+n/60)/24}function Qe(e){if(!e)return .5;let t=new Date(e);t.setUTCHours(0,0,0,0);let a=new Date("2000-01-06T00:00:00Z"),n=(t.getTime()-a.getTime())/(1e3*60*60*24),s=29.530588853;return(n%s+s)%s/s}function je(e){return e<0}var Je={clear:1,"partly-cloudy":.9,cloudy:.8,overcast:.65,fog:.7,drizzle:.7,rain:.6,"heavy-rain":.45,thunderstorm:.3,snow:.8,sleet:.65,hail:.5,windy:.9};function et(e,t){return e<.35?"dark":e>.45?"light":t??"dark"}function Oe(e){let t=e*24;return t<6?-1+t/6:t<12?(t-6)/6:t<18?1-(t-12)/6:-(t-18)/6}function tt(e,t="clear"){let a=Oe(e),n;a<0?n=.05+(1+a)*.1:n=.15+a*.85;let s=Je[t],c=n*s;return Math.max(0,Math.min(1,c))}function nt(e=0){return e<=10?e/10*.3:e<=25?.3+(e-10)/15*.4:.7+Math.min((e-25)/25,.3)}function at(e){switch(e){case"light":return .3;case"moderate":return .6;case"heavy":return 1;default:return 0}}function st(e=10){return e>=10?0:e>=5?(10-e)/5*.3:.3+(5-e)/5*.7}function te(e){return Math.max(0,Math.min(1,e))}function ee(e,t,a){let n=te((a-e)/(t-e));return n*n*(3-2*n)}var D={x:.74,y:.78,sunSize:.14,moonSize:.17,starDensity:2,sunGlowIntensity:3.05,sunGlowSize:.3,sunRayCount:6,sunRayLength:3,sunRayIntensity:.1,moonGlowIntensity:3.45,moonGlowSize:.94},ot={clear:D,"partly-cloudy":D,cloudy:D,overcast:D,fog:D,drizzle:D,rain:D,"heavy-rain":D,thunderstorm:D,snow:D,sleet:D,hail:D,windy:D},it={clear:{cloud:{coverage:.1,speed:.3,darkness:0,turbulence:.2}},"partly-cloudy":{cloud:{coverage:.4,speed:.4,darkness:.1,turbulence:.3}},cloudy:{cloud:{coverage:.7,speed:.4,darkness:.2,turbulence:.3}},overcast:{cloud:{coverage:.95,speed:.3,darkness:.35,turbulence:.25}},fog:{cloud:{coverage:.6,speed:.15,darkness:.15,turbulence:.1}},drizzle:{cloud:{coverage:.75,speed:.35,darkness:.3,turbulence:.3},rain:{intensity:.25,glassDrops:!0,fallingRain:!0,angle:3}},rain:{cloud:{coverage:.85,speed:.5,darkness:.4,turbulence:.4},rain:{intensity:.6,glassDrops:!0,fallingRain:!0,angle:5}},"heavy-rain":{cloud:{coverage:.95,speed:.6,darkness:.55,turbulence:.5},rain:{intensity:1,glassDrops:!0,fallingRain:!0,angle:8}},thunderstorm:{cloud:{coverage:1,speed:.7,darkness:.7,turbulence:.6},rain:{intensity:1,glassDrops:!0,fallingRain:!0,angle:15},lightning:{enabled:!0,autoTrigger:!0,intervalMin:4,intervalMax:12}},snow:{cloud:{coverage:.7,speed:.25,darkness:.2,turbulence:.2},snow:{intensity:.7,windDrift:.3}},sleet:{cloud:{coverage:.8,speed:.4,darkness:.35,turbulence:.35},rain:{intensity:.5,glassDrops:!0,fallingRain:!0,angle:10},snow:{intensity:.3,windDrift:.4}},hail:{cloud:{coverage:.9,speed:.6,darkness:.5,turbulence:.5},rain:{intensity:.7,glassDrops:!0,fallingRain:!0,angle:5},snow:{intensity:.3,windDrift:.4}},windy:{cloud:{coverage:.5,speed:1,darkness:.1,turbulence:.6}}};function Me(e){let{conditionCode:t,windSpeed:a,precipitationLevel:n,visibility:s,timestamp:c,timeOfDay:u}=e,o=it[t],r=u??H(c),i=Oe(r),h=nt(a),f=at(n),p=st(s),b=je(i),R={sunAltitude:i,haze:Math.max(p,(o.cloud?.darkness??0)*.3),starVisibility:b&&!o.cloud?1:b?1-(o.cloud?.coverage??0):0},C={atmosphere:R};if(o.cloud&&(C.cloud={...o.cloud,speed:o.cloud.speed*(1+h*.5),turbulence:o.cloud.turbulence*(1+h*.3)}),o.rain){let E=f>0?f:o.rain.intensity;C.rain={...o.rain,intensity:E,angle:o.rain.angle+h*10}}o.lightning&&(C.lightning={...o.lightning}),o.snow&&(C.snow={...o.snow,windDrift:o.snow.windDrift+h*.3});let A=Qe(c),k=ot[t];C.celestial={timeOfDay:r,moonPhase:A,starDensity:b?k.starDensity:0,celestialX:k.x,celestialY:k.y,sunSize:k.sunSize,moonSize:k.moonSize,sunGlowIntensity:k.sunGlowIntensity,sunGlowSize:k.sunGlowSize,sunRayCount:k.sunRayCount,sunRayLength:k.sunRayLength,sunRayIntensity:k.sunRayIntensity,moonGlowIntensity:k.moonGlowIntensity,moonGlowSize:k.moonGlowSize};let w=te(R.haze),T=C.cloud?.coverage??0,Q=T>.001,$=te(.04+(t==="fog"?.18:t==="thunderstorm"?.12:t==="heavy-rain"?.1:t==="overcast"?.08:t==="cloudy"||t==="partly-cloudy"?.06:.04)+w*.22),G=1.1+w*1.2,P=C.lightning?.enabled?.85:0,V=ee(-.05,.08,i),F=1-ee(.18,.7,Math.max(0,i)),d=ee(.25,.85,T),l=1-ee(.97,1,T),y=.35+w*.65,g=Q?te(V*F*d*l*y*.6):0,m={enabled:!0,haze:w,bloomIntensity:$,bloomRadius:G,exposureIntensity:P,godRayIntensity:g};return C.post=m,C}function rt(e){let t=Me(e),a=e.timeOfDay??t.celestial?.timeOfDay??H(e.timestamp),n=t.cloud!==void 0,s=t.rain!==void 0,c=t.lightning!==void 0,u=t.snow!==void 0,o=t.lightning?.intervalMin??4,r=t.lightning?.intervalMax??12;return{layers:{celestial:!0,clouds:n,rain:s,lightning:c,snow:u},celestial:{timeOfDay:a,moonPhase:t.celestial?.moonPhase??.5,starDensity:t.celestial?.starDensity??.5,celestialX:t.celestial?.celestialX??.5,celestialY:t.celestial?.celestialY??.72,sunSize:t.celestial?.sunSize??.06,moonSize:t.celestial?.moonSize??.05,sunGlowIntensity:t.celestial?.sunGlowIntensity??1,sunGlowSize:t.celestial?.sunGlowSize??.3,sunRayCount:t.celestial?.sunRayCount??12,sunRayLength:t.celestial?.sunRayLength??.5,sunRayIntensity:t.celestial?.sunRayIntensity??.4,sunRayShimmer:1,sunRayShimmerSpeed:1,moonGlowIntensity:t.celestial?.moonGlowIntensity??1,moonGlowSize:t.celestial?.moonGlowSize??.2,skyBrightness:1,skySaturation:1,skyContrast:1},cloud:{cloudScale:1.5,coverage:t.cloud?.coverage??.5,density:.7,softness:.3,windSpeed:t.cloud?.speed??.5,windAngle:0,turbulence:t.cloud?.turbulence??.5,lightIntensity:1,ambientDarkness:t.cloud?.darkness??.3,backlightIntensity:.5,numLayers:3},rain:{glassIntensity:s?(t.rain?.intensity??.5)*.7:0,zoom:1,fallingIntensity:s?t.rain?.intensity??.6:0,fallingSpeed:1,fallingAngle:s?(t.rain?.angle??5)*.02:.1,fallingStreakLength:.8,fallingLayers:3,fallingRefraction:.3},lightning:{autoMode:c?t.lightning?.autoTrigger??!0:!1,autoInterval:c?(o+r)/2:8,glowIntensity:.8,branchDensity:.6,sceneIllumination:.6},snow:{intensity:u?t.snow?.intensity??.7:0,layers:4,fallSpeed:.5,windSpeed:u?t.snow?.windDrift??.3:.3,windAngle:0,turbulence:.3,drift:u?t.snow?.windDrift??.3:.3,flutter:.5,windShear:.2,flakeSize:1,sizeVariation:.5,opacity:.8,glowAmount:.3,sparkle:.2},post:{enabled:!0,haze:0,hazeHorizon:.5,hazeDesaturation:.3,hazeContrast:.2,bloomIntensity:0,bloomThreshold:.8,bloomKnee:.5,bloomRadius:8,bloomTapScale:1,exposureIntensity:0,exposureDesaturation:.3,exposureRecovery:2,godRayIntensity:0,godRayDecay:.96,godRayDensity:.5,godRayWeight:.3,godRaySamples:60}}}function lt(e){return{layers:e.layers,celestial:{timeOfDay:e.celestial.timeOfDay,moonPhase:e.celestial.moonPhase,starDensity:e.celestial.starDensity,celestialX:e.celestial.celestialX,celestialY:e.celestial.celestialY,sunSize:e.celestial.sunSize,moonSize:e.celestial.moonSize,sunGlowIntensity:e.celestial.sunGlowIntensity,sunGlowSize:e.celestial.sunGlowSize,sunRayCount:e.celestial.sunRayCount,sunRayLength:e.celestial.sunRayLength,sunRayIntensity:e.celestial.sunRayIntensity,sunRayShimmer:e.celestial.sunRayShimmer,sunRayShimmerSpeed:e.celestial.sunRayShimmerSpeed,moonGlowIntensity:e.celestial.moonGlowIntensity,moonGlowSize:e.celestial.moonGlowSize,skyBrightness:e.celestial.skyBrightness,skySaturation:e.celestial.skySaturation,skyContrast:e.celestial.skyContrast},cloud:{coverage:e.cloud.coverage,density:e.cloud.density,softness:e.cloud.softness,cloudScale:e.cloud.cloudScale,windSpeed:e.cloud.windSpeed,windAngle:e.cloud.windAngle,turbulence:e.cloud.turbulence,lightIntensity:e.cloud.lightIntensity,ambientDarkness:e.cloud.ambientDarkness,backlightIntensity:e.cloud.backlightIntensity,numLayers:e.cloud.numLayers},rain:{glassIntensity:e.rain.glassIntensity,glassZoom:e.rain.zoom,fallingIntensity:e.rain.fallingIntensity,fallingSpeed:e.rain.fallingSpeed,fallingAngle:e.rain.fallingAngle,fallingStreakLength:e.rain.fallingStreakLength,fallingLayers:e.rain.fallingLayers},lightning:{enabled:e.layers.lightning,autoMode:e.lightning.autoMode,autoInterval:e.lightning.autoInterval,flashIntensity:e.lightning.glowIntensity,branchDensity:e.lightning.branchDensity},snow:{intensity:e.snow.intensity,layers:e.snow.layers,fallSpeed:e.snow.fallSpeed,windSpeed:e.snow.windSpeed,windAngle:e.snow.windAngle,turbulence:e.snow.turbulence,drift:e.snow.drift,flutter:e.snow.flutter,windShear:e.snow.windShear,flakeSize:e.snow.flakeSize,sizeVariation:e.snow.sizeVariation,opacity:e.snow.opacity,glowAmount:e.snow.glowAmount,sparkle:e.snow.sparkle},interactions:{rainRefractionStrength:e.rain.fallingRefraction,lightningSceneIllumination:e.lightning.sceneIllumination},post:e.post}}function Be(e){return lt(rt(e))}var me={dawn:.25,noon:.5,dusk:.75,midnight:0},ut=["dawn","noon","dusk","midnight"];function q(e){let t=(e%1+1)%1,a="noon",n=1/0;for(let s of ut){let c=me[s],u=Math.abs(t-c);u>.5&&(u=1-u),(u<n||u===n&&s=="midnight")&&(n=u,a=s)}return a}function W(e,t){if(!(!e&&!t))return{...e??{},...t??{}}}function Ne(e,t){return{...e,layers:W(e.layers,t.layers),celestial:W(e.celestial,t.celestial),cloud:W(e.cloud,t.cloud),rain:W(e.rain,t.rain),lightning:W(e.lightning,t.lightning),snow:W(e.snow,t.snow),glass:W(e.glass,t.glass),interactions:W(e.interactions,t.interactions),post:W(e.post,t.post)}}function Ye(e){let t=Be(e),a=e.timeOfDay;typeof a=="number"&&t.celestial&&(t.celestial.timeOfDay=a);let n=e.tunedPresets?.[e.conditionCode],s=a??t.celestial?.timeOfDay;if(!n||s===void 0)return t;let c=q(s);return Ne(t,n[c])}function ct(){if(typeof window>"u")return"high";let e=window.devicePixelRatio||1,t=window.innerWidth*window.innerHeight*e*e,a=typeof navigator<"u"?navigator.hardwareConcurrency:void 0,n=typeof navigator<"u"?navigator.deviceMemory:void 0,s=window.innerWidth<768;return typeof n=="number"&&n<=4||typeof a=="number"&&a<=4||t>25e5?s?"low":"medium":s?"medium":"high"}function Ve(e){return e==="low"||e==="medium"||e==="high"?e:ct()}function Xe(e){if(typeof window>"u")return;let t=window.devicePixelRatio||1;return Math.max(1,Math.min(t,e==="low"?1:e==="medium"?1.5:2))}import{jsx as He}from"react/jsx-runtime";var mt=de;function yt({conditionCode:e,windSpeed:t,precipitationLevel:a,visibility:n,timestamp:s,timeOfDay:c,settings:u,className:o}){let[r,i]=ft(!1),h=u?.enabled!==!1,f=u?.reducedMotion??!1,p=ye(()=>Ve(u?.quality??"auto"),[u?.quality]);dt(()=>{i(!0)},[]);let b=ye(()=>Xe(p),[p]),R=ye(()=>!h||f?null:Ye({conditionCode:e,windSpeed:t,precipitationLevel:a,visibility:n,timestamp:s,timeOfDay:c,tunedPresets:mt}),[h,f,e,t,a,n,s,c]);return!r||!h||f||!R?null:He("div",{className:o,style:{position:"absolute",inset:0,overflow:"hidden",pointerEvents:"none",borderRadius:"inherit"},"aria-hidden":"true",children:He(Fe,{className:"absolute inset-0",dpr:b,...R})})}function ht({glassStyles:e,blurAmount:t}){if(!!e.backdropFilter)return e;let n=`blur(${t}px)`;return{backdropFilter:n,WebkitBackdropFilter:n}}import{useEffect as gt,useMemo as pt,useState as St}from"react";var Y={depth:12,radius:12,strength:40,chromaticAberration:8,blur:2,brightness:1.05,saturation:1.2};function vt({width:e,height:t,radius:a,depth:n}){let s=Math.ceil(a/t*15),c=Math.ceil(a/e*15),u=Math.max(0,e-2*n),o=Math.max(0,t-2*n),r=`<svg height="${t}" width="${e}" viewBox="0 0 ${e} ${t}" xmlns="http://www.w3.org/2000/svg"> + <style>.mix { mix-blend-mode: screen; }</style> + <defs> + <linearGradient id="Y" x1="0" x2="0" y1="${s}%" y2="${100-s}%"> + <stop offset="0%" stop-color="#0F0" /> + <stop offset="100%" stop-color="#000" /> + </linearGradient> + <linearGradient id="X" x1="${c}%" x2="${100-c}%" y1="0" y2="0"> + <stop offset="0%" stop-color="#F00" /> + <stop offset="100%" stop-color="#000" /> + </linearGradient> + </defs> + <rect x="0" y="0" height="${t}" width="${e}" fill="#808080" /> + <g filter="blur(2px)"> + <rect x="0" y="0" height="${t}" width="${e}" fill="#000080" /> + <rect x="0" y="0" height="${t}" width="${e}" fill="url(#Y)" class="mix" /> + <rect x="0" y="0" height="${t}" width="${e}" fill="url(#X)" class="mix" /> + <rect x="${n}" y="${n}" height="${o}" width="${u}" fill="#808080" rx="${a}" ry="${a}" filter="blur(${n}px)" /> + </g> + </svg>`;return"data:image/svg+xml;utf8,"+encodeURIComponent(r)}function bt({width:e,height:t,radius:a,depth:n,strength:s,chromaticAberration:c}){let u=vt({width:e,height:t,radius:a,depth:n}),o=`<feImage x="0" y="0" height="${t}" width="${e}" href="${u}" result="displacementMap" />`,r;if(c===0)r=` + ${o} + <feDisplacementMap in="SourceGraphic" in2="displacementMap" scale="${s}" xChannelSelector="R" yChannelSelector="G" /> + `;else{let h=s+c*2,f=s+c;r=` + ${o} + <feDisplacementMap in="SourceGraphic" in2="displacementMap" scale="${h}" xChannelSelector="R" yChannelSelector="G" /> + <feColorMatrix type="matrix" values="1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 0" result="displacedR" /> + <feDisplacementMap in="SourceGraphic" in2="displacementMap" scale="${f}" xChannelSelector="R" yChannelSelector="G" /> + <feColorMatrix type="matrix" values="0 0 0 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 1 0" result="displacedG" /> + <feDisplacementMap in="SourceGraphic" in2="displacementMap" scale="${s}" xChannelSelector="R" yChannelSelector="G" /> + <feColorMatrix type="matrix" values="0 0 0 0 0 0 0 0 0 0 0 0 1 0 0 0 0 0 1 0" result="displacedB" /> + <feBlend in="displacedR" in2="displacedG" mode="screen"/> + <feBlend in2="displacedB" mode="screen"/> + `}let i=`<svg height="${t}" width="${e}" viewBox="0 0 ${e} ${t}" xmlns="http://www.w3.org/2000/svg"> + <defs> + <filter id="displace" color-interpolation-filters="sRGB">${r}</filter> + </defs> + </svg>`;return"data:image/svg+xml;utf8,"+encodeURIComponent(i)+"#displace"}function wt({filterUrl:e,blur:t,brightness:a,saturation:n}){return`blur(${t/2}px) url('${e}') blur(${t}px) brightness(${a}) saturate(${n})`}function xt(){let[e,t]=St(!0);return gt(()=>{let a=CSS.supports("backdrop-filter","blur(1px)")||CSS.supports("-webkit-backdrop-filter","blur(1px)");t(a)},[]),e}function It({width:e,height:t,depth:a=Y.depth,radius:n=Y.radius,strength:s=Y.strength,chromaticAberration:c=Y.chromaticAberration,blur:u=Y.blur,brightness:o=Y.brightness,saturation:r=Y.saturation,enabled:i=!0}){let h=xt();return pt(()=>{if(!i||!h||e<=0||t<=0)return{};let f=bt({width:e,height:t,radius:n,depth:a,strength:s,chromaticAberration:c}),p=wt({filterUrl:f,blur:u,brightness:o,saturation:r});return{backdropFilter:p,WebkitBackdropFilter:p}},[e,t,a,n,s,c,u,o,r,i,h])}function Ue(e){return(e%1+1)%1}function Rt(e){let t=Ue(e),a=q(t);return me[a]}function kt(e){return((Math.floor(e)%12+12)%12+.5)/12}function _t(e){let{time:t,updatedAt:a}=e;return typeof t?.timeBucket=="number"?{timeOfDay:kt(t.timeBucket),source:"timeBucket"}:typeof t?.localTimeOfDay=="number"?{timeOfDay:Ue(t.localTimeOfDay),source:"localTimeOfDay"}:typeof a=="string"?{timeOfDay:H(a),source:"updatedAt"}:{timeOfDay:.5,source:"defaultNoon"}}export{yt as EffectCompositorRuntime,de as TUNED_WEATHER_EFFECTS_CHECKPOINT_OVERRIDES,q as getNearestCheckpoint,tt as getSceneBrightnessFromTimeOfDay,H as getTimeOfDay,et as getWeatherTheme,ht as resolveGlassBackdropFilterStyles,_t as resolveWeatherTime,Rt as snapTimeOfDayToNearestCheckpoint,It as useGlassStyles}; \ No newline at end of file diff --git a/apps/docs/components/tool-ui/weather-widget/runtime.ts b/apps/docs/components/tool-ui/weather-widget/runtime.ts new file mode 100644 index 0000000..56f2654 --- /dev/null +++ b/apps/docs/components/tool-ui/weather-widget/runtime.ts @@ -0,0 +1,18 @@ +/** + * Start here when wiring the Weather Widget in your app. + * + * Import `WeatherWidget` and the runtime types from this file for production use. + * Reach for `index.tsx` only if you're actively working on authoring/debug internals. + */ +export { WeatherWidget } from "./weather-widget-container"; +export type { + WeatherWidgetPayload, + WeatherWidgetRuntimeProps as WeatherWidgetProps, + WeatherWidgetCurrent, + WeatherWidgetTime, + WeatherWidgetLocation, + WeatherConditionCode, + ForecastDay, + TemperatureUnit, + PrecipitationLevel, +} from "./schema-runtime"; diff --git a/apps/docs/components/tool-ui/weather-widget/schema-runtime.ts b/apps/docs/components/tool-ui/weather-widget/schema-runtime.ts new file mode 100644 index 0000000..080d711 --- /dev/null +++ b/apps/docs/components/tool-ui/weather-widget/schema-runtime.ts @@ -0,0 +1,72 @@ +export type WeatherConditionCode = + | "clear" + | "partly-cloudy" + | "cloudy" + | "overcast" + | "fog" + | "drizzle" + | "rain" + | "heavy-rain" + | "thunderstorm" + | "snow" + | "sleet" + | "hail" + | "windy"; + +export type TemperatureUnit = "celsius" | "fahrenheit"; + +export type PrecipitationLevel = "none" | "light" | "moderate" | "heavy"; + +export type TimeBucket = 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11; + +export interface ForecastDay { + label: string; + conditionCode: WeatherConditionCode; + tempMin: number; + tempMax: number; +} + +export interface WeatherWidgetCurrent { + conditionCode: WeatherConditionCode; + temperature: number; + tempMin: number; + tempMax: number; + windSpeed?: number; + precipitationLevel?: PrecipitationLevel; + visibility?: number; +} + +export interface WeatherWidgetTime { + timeBucket?: TimeBucket; + localTimeOfDay?: number; +} + +export interface WeatherWidgetLocation { + name: string; +} + +export interface WeatherWidgetPayload { + version: "3.1"; + id: string; + location: WeatherWidgetLocation; + units: { + temperature: TemperatureUnit; + }; + current: WeatherWidgetCurrent; + forecast: ForecastDay[]; + time: WeatherWidgetTime; + updatedAt?: string; +} + +export type EffectQuality = "low" | "medium" | "high" | "auto"; + +export interface EffectSettings { + enabled?: boolean; + quality?: EffectQuality; + reducedMotion?: boolean; +} + +export interface WeatherWidgetRuntimeProps extends WeatherWidgetPayload { + className?: string; + effects?: EffectSettings; +} diff --git a/apps/docs/components/tool-ui/weather-widget/weather-data-overlay.tsx b/apps/docs/components/tool-ui/weather-widget/weather-data-overlay.tsx new file mode 100644 index 0000000..2c4a51c --- /dev/null +++ b/apps/docs/components/tool-ui/weather-widget/weather-data-overlay.tsx @@ -0,0 +1,590 @@ +"use client"; + +import { useEffect, useRef, useState, useCallback } from "react"; +import { + Sun, + Cloud, + CloudSun, + CloudFog, + CloudDrizzle, + CloudRain, + CloudLightning, + Snowflake, + CloudHail, + Wind, + type LucideIcon, +} from "lucide-react"; +import { cn } from "@/lib/utils"; +import type { + ForecastDay, + TemperatureUnit, + WeatherConditionCode, +} from "./schema-runtime"; +import { + getSceneBrightnessFromTimeOfDay, + getTimeOfDay, + getWeatherTheme, +} from "./generated/weather-runtime-core.generated"; +import { + resolveGlassBackdropFilterStyles, + useGlassStyles, +} from "./generated/weather-runtime-core.generated"; + +type WeatherTheme = "light" | "dark"; + +function getPeakIntensity(timeOfDay: number): number { + const noonDistance = Math.abs(timeOfDay - 0.5); + const midnightDistance = Math.min(timeOfDay, 1 - timeOfDay); + const minDistance = Math.min(noonDistance, midnightDistance); + return Math.max(0, 1 - minDistance * 4); +} + +function sineEasedGradient( + x: number, + y: number, + radius: number, + peakOpacity: number, + steps = 8, +): string { + const stops: string[] = []; + for (let i = 0; i <= steps; i++) { + const t = i / steps; + const eased = Math.sin((t * Math.PI) / 2); + const opacity = peakOpacity * (1 - eased); + const position = t * 100; + stops.push( + `rgba(255,255,255,${opacity.toFixed(4)}) ${position.toFixed(1)}%`, + ); + } + return `radial-gradient(circle ${radius}px at ${x}px ${y}px, ${stops.join(", ")})`; +} + +const conditionIcons: Record<WeatherConditionCode, LucideIcon> = { + clear: Sun, + "partly-cloudy": CloudSun, + cloudy: Cloud, + overcast: Cloud, + fog: CloudFog, + drizzle: CloudDrizzle, + rain: CloudRain, + "heavy-rain": CloudRain, + thunderstorm: CloudLightning, + snow: Snowflake, + sleet: CloudHail, + hail: CloudHail, + windy: Wind, +}; + +export interface GlassEffectParams { + enabled?: boolean; + depth?: number; + strength?: number; + chromaticAberration?: number; + blur?: number; + brightness?: number; + saturation?: number; +} + +export interface WeatherDataOverlayProps { + location: string; + conditionCode: WeatherConditionCode; + temperature: number; + tempHigh: number; + tempLow: number; + forecast?: ForecastDay[]; + unit?: TemperatureUnit; + theme?: WeatherTheme; + /** + * Provide either `timeOfDay` (0-1) or a `timestamp` ISO string. + * If neither is provided, defaults to noon (0.5). + */ + timeOfDay?: number; + timestamp?: string | undefined; + className?: string; + reducedMotion?: boolean; + /** + * Glass refraction effect parameters for the forecast card. + * When enabled, applies SVG displacement filter for realistic glass distortion. + */ + glassParams?: GlassEffectParams | undefined; +} + +interface GlowState { + x: number; + y: number; + intensity: number; +} + +export function observeCardDimensions( + element: HTMLDivElement | null, + onResize: () => void, +): () => void { + if (!element || typeof ResizeObserver !== "function") { + return () => {}; + } + + const observer = new ResizeObserver(onResize); + observer.observe(element); + return () => observer.disconnect(); +} + +export function WeatherDataOverlay({ + location, + conditionCode, + temperature, + tempHigh, + tempLow, + forecast = [], + unit = "fahrenheit", + theme: themeProp, + timeOfDay: timeOfDayProp, + timestamp, + className, + reducedMotion = false, + glassParams, +}: WeatherDataOverlayProps) { + const timeOfDay = + typeof timeOfDayProp === "number" + ? timeOfDayProp + : typeof timestamp === "string" + ? getTimeOfDay(timestamp) + : 0.5; + + const [glowState, setGlowState] = useState<GlowState>({ + x: 0, + y: 0, + intensity: 0, + }); + const [cardDimensions, setCardDimensions] = useState({ width: 0, height: 0 }); + const cardRef = useRef<HTMLDivElement>(null); + const containerRef = useRef<HTMLDivElement>(null); + const pendingGlowStateRef = useRef<GlowState | null>(null); + const pendingGlowFrameRef = useRef<number | null>(null); + + // Glass effect styles applied directly to forecast container. + // Enabled by default - falls back to simple blur if SVG filter unsupported. + const glassEnabled = glassParams?.enabled !== false; + const glassStyles = useGlassStyles({ + width: cardDimensions.width, + height: cardDimensions.height, + depth: glassParams?.depth ?? 3, + radius: 12, + strength: glassParams?.strength ?? 75, + chromaticAberration: glassParams?.chromaticAberration ?? 6, + blur: glassParams?.blur ?? 1.5, + brightness: glassParams?.brightness ?? 0.8, + saturation: glassParams?.saturation ?? 1.3, + enabled: glassEnabled, + }); + + // Track forecast card dimensions for glass effect + const updateCardDimensions = useCallback(() => { + if (cardRef.current) { + const rect = cardRef.current.getBoundingClientRect(); + setCardDimensions({ + width: Math.round(rect.width), + height: Math.round(rect.height), + }); + } + }, []); + const hasForecastStrip = forecast.length > 0; + + useEffect(() => { + if (!hasForecastStrip) { + return; + } + + updateCardDimensions(); + return observeCardDimensions(cardRef.current, updateCardDimensions); + }, [hasForecastStrip, updateCardDimensions]); + + const theme = + themeProp ?? + getWeatherTheme( + getSceneBrightnessFromTimeOfDay(timeOfDay, conditionCode), + undefined, + ); + + const commitGlowState = useCallback((nextState: GlowState) => { + setGlowState((prevState) => { + if ( + prevState.x === nextState.x && + prevState.y === nextState.y && + prevState.intensity === nextState.intensity + ) { + return prevState; + } + + return nextState; + }); + }, []); + + const cancelPendingGlowFrame = useCallback(() => { + pendingGlowStateRef.current = null; + + if ( + pendingGlowFrameRef.current !== null && + typeof window !== "undefined" && + typeof window.cancelAnimationFrame === "function" + ) { + window.cancelAnimationFrame(pendingGlowFrameRef.current); + } + + pendingGlowFrameRef.current = null; + }, []); + + const scheduleGlowState = useCallback( + (nextState: GlowState) => { + pendingGlowStateRef.current = nextState; + + if (pendingGlowFrameRef.current !== null) { + return; + } + + if ( + typeof window === "undefined" || + typeof window.requestAnimationFrame !== "function" + ) { + pendingGlowStateRef.current = null; + commitGlowState(nextState); + return; + } + + pendingGlowFrameRef.current = window.requestAnimationFrame(() => { + pendingGlowFrameRef.current = null; + const pendingState = pendingGlowStateRef.current; + pendingGlowStateRef.current = null; + + if (pendingState) { + commitGlowState(pendingState); + } + }); + }, + [commitGlowState], + ); + + const clearGlowIntensity = useCallback(() => { + cancelPendingGlowFrame(); + setGlowState((prevState) => { + if (prevState.intensity === 0) { + return prevState; + } + + return { ...prevState, intensity: 0 }; + }); + }, [cancelPendingGlowFrame]); + + useEffect(() => { + if (reducedMotion) { + clearGlowIntensity(); + return; + } + + const container = containerRef.current; + if (!container) return; + + const handleMouseMove = (e: MouseEvent) => { + if (!cardRef.current) return; + const cardRect = cardRef.current.getBoundingClientRect(); + + const clampedX = Math.max( + cardRect.left, + Math.min(e.clientX, cardRect.right), + ); + const clampedY = Math.max( + cardRect.top, + Math.min(e.clientY, cardRect.bottom), + ); + + const distanceX = + e.clientX < cardRect.left + ? cardRect.left - e.clientX + : e.clientX > cardRect.right + ? e.clientX - cardRect.right + : 0; + const distanceY = + e.clientY < cardRect.top + ? cardRect.top - e.clientY + : e.clientY > cardRect.bottom + ? e.clientY - cardRect.bottom + : 0; + const distance = Math.sqrt(distanceX * distanceX + distanceY * distanceY); + + const maxDistance = 150; + const intensity = Math.max(0, 1 - distance / maxDistance); + + scheduleGlowState({ + x: clampedX - cardRect.left, + y: clampedY - cardRect.top, + intensity, + }); + }; + + const handleMouseLeave = () => { + clearGlowIntensity(); + }; + + container.addEventListener("mousemove", handleMouseMove); + container.addEventListener("mouseleave", handleMouseLeave); + + return () => { + container.removeEventListener("mousemove", handleMouseMove); + container.removeEventListener("mouseleave", handleMouseLeave); + cancelPendingGlowFrame(); + }; + }, [ + reducedMotion, + clearGlowIntensity, + scheduleGlowState, + cancelPendingGlowFrame, + ]); + + const roundedTemperature = Math.round(temperature); + const unitSymbol = unit === "celsius" ? "C" : "F"; + const spokenUnit = unit === "celsius" ? "Celsius" : "Fahrenheit"; + const peakIntensity = getPeakIntensity(timeOfDay); + + const isDark = theme === "dark"; + const textPrimary = isDark ? "text-white" : "text-black"; + const textPrimarySoft = isDark ? "text-white/90" : "text-black/85"; + const textSecondary = isDark ? "text-white/80" : "text-black/80"; + const textSubtle = isDark ? "text-white/40" : "text-black/40"; + + const baseBgOpacity = isDark ? 0.04 : 0.04; + const bgOpacity = baseBgOpacity * (1 - peakIntensity * 0.7); + const midnightDistance = Math.min(timeOfDay, 1 - timeOfDay); + const baseBlur = isDark ? 2 + midnightDistance * 38 : 24; + const blurAmount = isDark + ? baseBlur + : baseBlur - peakIntensity * (baseBlur - 8); + + // Dawn intensity peaks around timeOfDay 0.2-0.3 (morning transition) + const isDawn = timeOfDay > 0.1 && timeOfDay < 0.4; + const dawnIntensity = isDawn ? 1 - Math.abs(timeOfDay - 0.25) * 4 : 0; + const forecastTextShadow = + dawnIntensity > 0 + ? `0 0.5px 1px rgba(0,0,0,${(dawnIntensity * 0.4).toFixed(2)})` + : undefined; + + const shadowStyle = isDark + ? "0 1px 8px rgba(0,0,0,0.3)" + : "0 1px 8px rgba(255,255,255,0.3)"; + + // Fluid type scales with the widget container size. (Requires container-type:size.) + const locationFontSize = "clamp(13px, 7.5cqmin, 17px)"; + const temperatureFontSize = "clamp(48px, 32cqmin, 72px)"; + const degreeFontSize = "clamp(18px, 12cqmin, 28px)"; + const hiLoFontSize = "clamp(11px, 6.5cqmin, 15px)"; + const forecastFontFamily = + '"SF Pro Text", Inter, "Noto Sans", system-ui, sans-serif'; + + return ( + <div + ref={containerRef} + className={cn( + "pointer-events-auto absolute inset-0 z-10 flex flex-col select-none", + className, + )} + > + {/* Current weather (more inset than forecast strip) */} + <div className="px-6 pt-6"> + <div className="flex flex-col items-start"> + <h2 + className={cn( + "leading-[1.08] font-medium tracking-tight", + textSecondary, + )} + style={{ + fontSize: locationFontSize, + fontFamily: forecastFontFamily, + textShadow: shadowStyle, + }} + > + {location} + </h2> + + <div className="-mt-0.5 flex items-start gap-1"> + <span + className={cn( + "leading-[1.02] font-[250] tracking-[-0.015em] tabular-nums", + textPrimarySoft, + )} + style={{ + fontSize: temperatureFontSize, + fontFamily: forecastFontFamily, + fontFeatureSettings: '"tnum" 1, "case" 1', + textShadow: isDark + ? "0 2px 20px rgba(0,0,0,0.25)" + : "0 2px 20px rgba(255,255,255,0.3)", + }} + aria-hidden="true" + > + {roundedTemperature} + </span> + <span + className={cn("mt-2 font-[250] tabular-nums", textSecondary)} + style={{ + fontSize: degreeFontSize, + fontFamily: forecastFontFamily, + fontFeatureSettings: '"tnum" 1, "case" 1', + }} + aria-hidden="true" + > + °{unitSymbol} + </span> + <span className="sr-only"> + {roundedTemperature} degrees {spokenUnit} + </span> + </div> + + <div + className="mt-0.5 flex items-center gap-3" + style={{ + fontFamily: forecastFontFamily, + fontFeatureSettings: '"tnum" 1, "case" 1', + }} + > + <span + className="font-medium tabular-nums" + style={{ fontSize: hiLoFontSize }} + > + <span className={textSubtle}>H </span> + <span className={textPrimary}>{Math.round(tempHigh)}°</span> + </span> + <span + className="font-medium tabular-nums" + style={{ fontSize: hiLoFontSize }} + > + <span className={textSubtle}>L </span> + <span className={textPrimary}>{Math.round(tempLow)}°</span> + </span> + </div> + </div> + </div> + + {/* Spacer */} + <div className="flex-1" /> + + {/* Forecast strip - hidden at small container sizes (less inset than header) */} + {forecast.length > 0 && ( + <div className="px-3 pb-3"> + {/* Show the strip earlier, but progressively reduce content as height shrinks. */} + <div ref={cardRef} className="weather-forecast-strip relative hidden"> + {/* Edge shine - outside overflow-hidden so it aligns with border */} + <div + className="pointer-events-none absolute inset-0 z-10 rounded-xl transition-opacity duration-300 ease-out" + style={{ + opacity: glowState.intensity, + background: sineEasedGradient( + glowState.x, + glowState.y, + 100, + isDark ? 0.6 : 1, + ), + mask: "linear-gradient(#fff 0 0) content-box, linear-gradient(#fff 0 0)", + maskComposite: "exclude", + WebkitMaskComposite: "xor", + padding: "0.5px", + }} + /> + <div + className="relative overflow-hidden rounded-xl px-3 py-2.5" + style={{ + backgroundColor: `rgba(255, 255, 255, ${bgOpacity})`, + ...resolveGlassBackdropFilterStyles({ + glassStyles, + blurAmount, + }), + }} + > + {/* Inner glow */} + <div + className="pointer-events-none absolute inset-0 mix-blend-color-dodge transition-opacity duration-300 ease-out" + style={{ + opacity: glowState.intensity, + background: sineEasedGradient( + glowState.x, + glowState.y, + 120, + isDark ? 0.06 : 0.15, + ), + }} + /> + <div className="relative flex items-center justify-between"> + {forecast.slice(0, 5).map((day, index) => { + const DayIcon = conditionIcons[day.conditionCode]; + return ( + <div + key={`${day.label}-${index}`} + className="flex flex-1 flex-col items-center gap-0.5" + style={{ + fontFamily: forecastFontFamily, + fontFeatureSettings: '"tnum" 1, "case" 1', + textShadow: forecastTextShadow, + }} + > + <span + className={cn( + "text-[10px] tracking-[0.08em] uppercase", + index === 0 ? "font-semibold" : "font-medium", + textPrimary, + )} + > + {day.label} + </span> + <DayIcon + className={cn( + "my-0.5 size-5", + textPrimary, + index === 0 ? "opacity-100" : "opacity-70", + // At shorter containers (but still showing the strip), + // omit the icon to preserve legibility. + "weather-forecast-icon hidden", + )} + strokeWidth={1.5} + aria-hidden="true" + /> + <div className="flex flex-col items-center gap-0.5"> + <span + className={cn( + "text-[15px] leading-[1.2] tracking-[-0.01em] tabular-nums", + index === 0 ? "font-semibold" : "font-medium", + textPrimary, + )} + > + {Math.round(day.tempMax)}° + </span> + <span + className={cn( + "text-[12px] leading-[1.3] font-normal tabular-nums", + textPrimary, + )} + > + {Math.round(day.tempMin)}° + </span> + </div> + </div> + ); + })} + </div> + </div> + </div> + </div> + )} + + {/* Height-based container queries (requires container-type:size on the weather container). */} + <style>{` + @container weather (min-height: 245px) { + .weather-forecast-strip { + display: block !important; + } + } + @container weather (min-height: 280px) { + .weather-forecast-icon { + display: block !important; + } + } + `}</style> + </div> + ); +} diff --git a/apps/docs/components/tool-ui/weather-widget/weather-widget-container.tsx b/apps/docs/components/tool-ui/weather-widget/weather-widget-container.tsx new file mode 100644 index 0000000..0988181 --- /dev/null +++ b/apps/docs/components/tool-ui/weather-widget/weather-widget-container.tsx @@ -0,0 +1,144 @@ +"use client"; + +import { useEffect, useState } from "react"; + +import { cn } from "@/lib/utils"; +import { + EffectCompositorRuntime, + getNearestCheckpoint, + getSceneBrightnessFromTimeOfDay, + getWeatherTheme, + resolveWeatherTime, + snapTimeOfDayToNearestCheckpoint, + TUNED_WEATHER_EFFECTS_CHECKPOINT_OVERRIDES, +} from "./generated/weather-runtime-core.generated"; +import type { WeatherWidgetRuntimeProps } from "./schema-runtime"; +import { WeatherDataOverlay } from "./weather-data-overlay"; + +type TimeCheckpoint = "dawn" | "noon" | "dusk" | "midnight"; + +export function WeatherWidget({ + version: _version, + id, + location, + units, + current, + forecast, + time, + updatedAt, + className, + effects, +}: WeatherWidgetRuntimeProps) { + const [prefersReducedMotion, setPrefersReducedMotion] = useState(() => { + if (typeof window === "undefined") { + return false; + } + + return ( + window.matchMedia?.("(prefers-reduced-motion: reduce)")?.matches ?? false + ); + }); + + useEffect(() => { + if ( + typeof window === "undefined" || + typeof window.matchMedia !== "function" + ) { + return; + } + + const mediaQueryList = window.matchMedia( + "(prefers-reduced-motion: reduce)", + ); + setPrefersReducedMotion(mediaQueryList.matches); + + const handleMotionPreferenceChange = (event: MediaQueryListEvent) => { + setPrefersReducedMotion(event.matches); + }; + + if (typeof mediaQueryList.addEventListener === "function") { + mediaQueryList.addEventListener("change", handleMotionPreferenceChange); + return () => { + mediaQueryList.removeEventListener( + "change", + handleMotionPreferenceChange, + ); + }; + } + + mediaQueryList.addListener(handleMotionPreferenceChange); + return () => { + mediaQueryList.removeListener(handleMotionPreferenceChange); + }; + }, []); + + const reducedMotion = effects?.reducedMotion ?? prefersReducedMotion; + const effectsEnabled = effects?.enabled !== false && !reducedMotion; + + const resolvedTime = resolveWeatherTime({ + time, + updatedAt, + }); + const timeOfDay = snapTimeOfDayToNearestCheckpoint(resolvedTime.timeOfDay); + const tunedOverrides = + TUNED_WEATHER_EFFECTS_CHECKPOINT_OVERRIDES[current.conditionCode]; + const checkpoint = getNearestCheckpoint(timeOfDay) as TimeCheckpoint; + const checkpointOverrides = tunedOverrides?.[checkpoint]; + const glassParams = + checkpointOverrides && "glass" in checkpointOverrides + ? checkpointOverrides.glass + : undefined; + const brightness = getSceneBrightnessFromTimeOfDay( + timeOfDay, + current.conditionCode, + ); + const weatherTheme = getWeatherTheme(brightness, undefined); + const isWeatherDark = weatherTheme === "dark"; + const backgroundClass = isWeatherDark + ? "bg-gradient-to-b from-zinc-950 via-zinc-900/70 to-zinc-950" + : "bg-gradient-to-b from-sky-50 via-sky-100/70 to-white"; + + return ( + <article + data-slot="weather-widget" + data-tool-ui-id={id} + className={cn("isolate w-full max-w-md", className)} + > + <div + data-slot="card" + className={cn( + "@container/weather [container-type:size] relative aspect-[4/3] overflow-clip rounded-2xl border-0 p-0 shadow-none", + backgroundClass, + )} + > + {effectsEnabled && ( + <EffectCompositorRuntime + className="absolute inset-0" + conditionCode={current.conditionCode} + windSpeed={current.windSpeed} + precipitationLevel={current.precipitationLevel} + visibility={current.visibility} + timestamp={updatedAt} + timeOfDay={timeOfDay} + settings={effects} + /> + )} + + <WeatherDataOverlay + location={location.name} + conditionCode={current.conditionCode} + temperature={current.temperature} + tempHigh={current.tempMax} + tempLow={current.tempMin} + forecast={forecast} + unit={units.temperature} + theme={weatherTheme} + timeOfDay={timeOfDay} + timestamp={updatedAt} + glassParams={glassParams} + reducedMotion={reducedMotion} + /> + </div> + </article> + ); +} diff --git a/apps/docs/components/traction/activity-heatmap.tsx b/apps/docs/components/traction/activity-heatmap.tsx new file mode 100644 index 0000000..b1568f1 --- /dev/null +++ b/apps/docs/components/traction/activity-heatmap.tsx @@ -0,0 +1,173 @@ +"use client"; + +import { useMemo } from "react"; +import * as HeatGraph from "heat-graph"; +import type { ActivityPoint } from "@/lib/traction"; + +const COMMIT_COLORS = [ + "color-mix(in oklab, var(--color-muted-foreground) 14%, transparent)", + "color-mix(in oklab, var(--color-chart-1) 30%, transparent)", + "color-mix(in oklab, var(--color-chart-1) 55%, transparent)", + "color-mix(in oklab, var(--color-chart-1) 80%, transparent)", + "var(--color-chart-1)", +]; + +const RELEASE_RING = "inset 0 0 0 1.5px var(--color-chart-2)"; + +const formatDate = (date: Date) => + date.toLocaleDateString("en-US", { + month: "short", + day: "numeric", + year: "numeric", + }); + +const localDateKey = (d: Date): string => { + const y = d.getFullYear(); + const m = String(d.getMonth() + 1).padStart(2, "0"); + const day = String(d.getDate()).padStart(2, "0"); + return `${y}-${m}-${day}`; +}; + +export function ActivityHeatmap({ + commits, + releases, +}: { + commits: ActivityPoint[]; + releases: ActivityPoint[]; +}) { + const releaseDays = useMemo(() => { + const set = new Set<string>(); + for (const r of releases) { + if (r.count > 0) set.add(r.date); + } + return set; + }, [releases]); + + if (commits.length === 0 && releases.length === 0) { + return ( + <div className="border-border text-muted-foreground flex h-[220px] items-center justify-center rounded-lg border border-dashed text-sm"> + Activity is currently unavailable. + </div> + ); + } + + return ( + <div className="border-border rounded-lg border p-4 md:p-5"> + <HeatGraph.Root + data={commits} + weekStart="sunday" + colorScale={COMMIT_COLORS} + className="flex flex-col" + > + <div + className="relative" + style={{ height: 14, marginLeft: 28, marginBottom: 6 }} + > + <HeatGraph.MonthLabels> + {({ label, totalWeeks }) => ( + <span + className="text-muted-foreground absolute text-[11px] leading-[14px]" + style={{ left: `${(label.column / totalWeeks) * 100}%` }} + > + {HeatGraph.MONTH_SHORT[label.month]} + </span> + )} + </HeatGraph.MonthLabels> + </div> + + <div className="flex" style={{ gap: 3 }}> + <div className="flex shrink-0 flex-col" style={{ width: 25, gap: 3 }}> + <HeatGraph.DayLabels> + {({ label }) => ( + <span + className="text-muted-foreground relative text-[10px]" + style={{ height: 11 }} + > + {label.row % 2 === 1 ? ( + <span className="absolute" style={{ bottom: -1 }}> + {HeatGraph.DAY_SHORT[label.dayOfWeek]?.charAt(0)} + </span> + ) : null} + </span> + )} + </HeatGraph.DayLabels> + </div> + + <HeatGraph.Grid + style={{ + gap: 3, + gridTemplateRows: "repeat(7, 11px)", + flex: 1, + minWidth: 0, + }} + > + {({ cell }) => { + const released = releaseDays.has(localDateKey(cell.date)); + return ( + <HeatGraph.Cell + className="rounded-[2px]" + style={released ? { boxShadow: RELEASE_RING } : undefined} + /> + ); + }} + </HeatGraph.Grid> + </div> + + <div + className="text-muted-foreground flex flex-wrap items-center justify-end text-[11px]" + style={{ marginTop: 10, gap: 12 }} + > + <div className="flex items-center" style={{ gap: 4 }}> + <span>Less</span> + <HeatGraph.Legend> + {() => ( + <HeatGraph.LegendLevel + className="rounded-[2px]" + style={{ width: 10, height: 10 }} + /> + )} + </HeatGraph.Legend> + <span>More</span> + </div> + <div className="flex items-center" style={{ gap: 6 }}> + <span + className="rounded-[2px]" + style={{ + width: 10, + height: 10, + backgroundColor: + "color-mix(in oklab, var(--color-muted-foreground) 14%, transparent)", + boxShadow: RELEASE_RING, + }} + /> + <span>Released</span> + </div> + </div> + + <HeatGraph.Tooltip className="border-border bg-popover text-popover-foreground pointer-events-none rounded-md border px-3 py-1.5 text-xs whitespace-nowrap shadow-md"> + {({ cell }) => { + const released = releaseDays.has(localDateKey(cell.date)); + const headline = + cell.count > 0 + ? `${cell.count} ${cell.count === 1 ? "commit" : "commits"}` + : released + ? "Released" + : "No commits"; + return ( + <> + <strong>{headline}</strong> + {released && cell.count > 0 ? ( + <span style={{ color: "var(--color-chart-2)" }}> + {" · released"} + </span> + ) : null} + {" — "} + {formatDate(cell.date)} + </> + ); + }} + </HeatGraph.Tooltip> + </HeatGraph.Root> + </div> + ); +} diff --git a/apps/docs/components/traction/downloads-chart.tsx b/apps/docs/components/traction/downloads-chart.tsx new file mode 100644 index 0000000..e728956 --- /dev/null +++ b/apps/docs/components/traction/downloads-chart.tsx @@ -0,0 +1,265 @@ +"use client"; + +import { useId, useState } from "react"; +import { + Area, + AreaChart, + CartesianGrid, + Line, + ReferenceLine, + XAxis, + YAxis, +} from "recharts"; +import { + ChartContainer, + type ChartConfig, + ChartLegend, + ChartTooltip, + ChartTooltipContent, +} from "@/components/ui/chart"; +import { cn } from "@/lib/utils"; +import { formatCompact } from "@/lib/format"; +import type { TimelineSeries } from "@/lib/traction"; + +const MONTH_NAMES = [ + "Jan", + "Feb", + "Mar", + "Apr", + "May", + "Jun", + "Jul", + "Aug", + "Sep", + "Oct", + "Nov", + "Dec", +]; + +const formatTick = (yearMonth: string) => { + const parts = yearMonth.split("-"); + const idx = Number(parts[1]) - 1; + return MONTH_NAMES[idx] ?? yearMonth; +}; + +const formatTooltipLabel = (yearMonth: string) => { + const [y, m] = yearMonth.split("-"); + const date = new Date(Number(y), Number(m) - 1, 1); + return date.toLocaleDateString("en-US", { month: "long", year: "numeric" }); +}; + +export function DownloadsChart({ timeline }: { timeline: TimelineSeries }) { + const gradientPrefix = useId(); + const [hidden, setHidden] = useState<Set<string>>(new Set()); + + if (timeline.data.length < 2 || timeline.series.length === 0) { + return ( + <div className="border-border text-muted-foreground flex h-[260px] items-center justify-center rounded-lg border border-dashed text-sm md:h-[360px]"> + Download history is currently unavailable. + </div> + ); + } + + const config: ChartConfig = {}; + for (const s of timeline.series) { + config[s.key] = { + label: s.label, + color: `var(--chart-${s.chartIndex})`, + }; + } + + const toggle = (key: string) => { + setHidden((prev) => { + const next = new Set(prev); + if (next.has(key)) next.delete(key); + else next.add(key); + return next; + }); + }; + + return ( + <ChartContainer + config={config} + className="aspect-auto h-[260px] w-full md:h-[360px]" + > + <AreaChart + data={timeline.data} + margin={{ left: 8, right: 16, top: 8, bottom: 0 }} + > + <defs> + {timeline.series.map((s) => ( + <linearGradient + key={s.key} + id={`${gradientPrefix}-${s.key}`} + x1="0" + y1="0" + x2="0" + y2="1" + > + <stop + offset="0%" + stopColor={`var(--color-${s.key})`} + stopOpacity={0.18} + /> + <stop + offset="100%" + stopColor={`var(--color-${s.key})`} + stopOpacity={0} + /> + </linearGradient> + ))} + </defs> + <CartesianGrid vertical={false} strokeDasharray="3 3" /> + <XAxis + dataKey="date" + tickFormatter={formatTick} + tickLine={false} + axisLine={false} + minTickGap={32} + /> + <YAxis + tickFormatter={(v) => formatCompact(v as number)} + tickLine={false} + axisLine={false} + width={42} + /> + <ChartTooltip + cursor={{ stroke: "var(--border)" }} + content={(tooltipProps) => { + // hide _proj entries when their raw counterpart has a value (avoids dup rows on the bridging month). + const filtered = tooltipProps.payload + ?.filter((entry) => { + const key = String(entry?.dataKey ?? ""); + if (!key.endsWith("_proj")) return true; + const baseKey = key.slice(0, -5); + const rowPayload = entry.payload as + | Record<string, number | undefined> + | undefined; + return rowPayload?.[baseKey] === undefined; + }) + .slice() + .sort((a, b) => (Number(b.value) || 0) - (Number(a.value) || 0)); + return ( + <ChartTooltipContent + {...(tooltipProps as Record<string, unknown>)} + payload={filtered} + labelFormatter={(_, p) => { + const ym = p?.[0]?.payload?.date as string | undefined; + return ym ? formatTooltipLabel(ym) : ""; + }} + formatter={(value, name, item) => { + const rawName = String(name); + const baseKey = rawName.endsWith("_proj") + ? rawName.slice(0, -5) + : rawName; + const series = timeline.series.find((s) => s.key === baseKey); + const color = + (item as { color?: string } | undefined)?.color ?? + (item?.payload as { fill?: string } | undefined)?.fill; + return ( + <> + <div + className="h-2.5 w-2.5 shrink-0 rounded-[2px]" + style={{ backgroundColor: color }} + /> + <div className="flex flex-1 items-center justify-between gap-3 leading-none"> + <span className="text-muted-foreground"> + {series?.label ?? rawName} + </span> + <span className="text-foreground font-mono font-medium tabular-nums"> + {formatCompact(value as number)} + </span> + </div> + </> + ); + }} + indicator="dot" + /> + ); + }} + /> + {timeline.series.map((s) => { + const isHidden = hidden.has(s.key); + return ( + <Area + key={s.key} + dataKey={s.key} + type="monotone" + stroke={isHidden ? "transparent" : `var(--color-${s.key})`} + strokeWidth={1.75} + fill={ + isHidden ? "transparent" : `url(#${gradientPrefix}-${s.key})` + } + isAnimationActive={false} + /> + ); + })} + {timeline.projectedMonth ? ( + <> + <ReferenceLine + x={timeline.projectedMonth} + stroke="var(--muted-foreground)" + strokeDasharray="3 3" + strokeOpacity={0.4} + /> + {timeline.series.map((s) => { + const isHidden = hidden.has(s.key); + return ( + <Line + key={`${s.key}-proj`} + dataKey={`${s.key}_proj`} + stroke={isHidden ? "transparent" : `var(--color-${s.key})`} + strokeWidth={1.5} + strokeDasharray="5 4" + type="monotone" + dot={false} + activeDot={false} + connectNulls={false} + isAnimationActive={false} + legendType="none" + /> + ); + })} + </> + ) : null} + <ChartLegend + content={({ payload }) => ( + <div className="flex flex-wrap items-center justify-center gap-x-4 gap-y-1 pt-3"> + {( + payload as + | Array<{ + dataKey?: string | number; + value?: string; + color?: string; + }> + | undefined + )?.map((item) => { + const key = String(item.dataKey ?? item.value ?? ""); + const series = timeline.series.find((s) => s.key === key); + if (!series) return null; + const isHidden = hidden.has(key); + return ( + <button + key={key} + type="button" + onClick={() => toggle(key)} + className={cn( + "text-muted-foreground hover:text-foreground focus-visible:ring-ring/50 flex cursor-pointer items-center gap-1.5 rounded-sm text-xs transition-opacity outline-none focus-visible:ring-2", + isHidden && "opacity-40", + )} + > + <div + className="h-2 w-2 shrink-0 rounded-[2px]" + style={{ backgroundColor: `var(--color-${key})` }} + /> + <span>{series.label}</span> + </button> + ); + })} + </div> + )} + /> + </AreaChart> + </ChartContainer> + ); +} diff --git a/apps/docs/components/traction/sparkline.tsx b/apps/docs/components/traction/sparkline.tsx new file mode 100644 index 0000000..f85deba --- /dev/null +++ b/apps/docs/components/traction/sparkline.tsx @@ -0,0 +1,56 @@ +import { cn } from "@/lib/utils"; + +type Props = { + values: number[]; + className?: string; + width?: number; + height?: number; +}; + +export function Sparkline({ + values, + className, + width = 64, + height = 18, +}: Props) { + if (values.length < 2) return null; + + const max = Math.max(...values); + const min = Math.min(...values); + const range = Math.max(1, max - min); + + const stepX = width / (values.length - 1); + const points = values.map((v, i) => { + const x = i * stepX; + const y = height - ((v - min) / range) * (height - 2) - 1; + return [x, y] as const; + }); + + const linePath = points + .map(([x, y], i) => `${i === 0 ? "M" : "L"}${x.toFixed(2)} ${y.toFixed(2)}`) + .join(" "); + + const areaPath = `${linePath} L${width.toFixed(2)} ${height} L0 ${height} Z`; + + return ( + <svg + width={width} + height={height} + viewBox={`0 0 ${width} ${height}`} + preserveAspectRatio="none" + className={cn("text-foreground/60", className)} + role="img" + > + <title>Weekly downloads trend + + + + ); +} diff --git a/apps/docs/components/traction/star-history-chart.tsx b/apps/docs/components/traction/star-history-chart.tsx new file mode 100644 index 0000000..ccd6aa3 --- /dev/null +++ b/apps/docs/components/traction/star-history-chart.tsx @@ -0,0 +1,206 @@ +"use client"; + +import { useId, useMemo } from "react"; +import { Area, AreaChart, CartesianGrid, XAxis, YAxis } from "recharts"; +import { + ChartContainer, + type ChartConfig, + ChartTooltip, + ChartTooltipContent, +} from "@/components/ui/chart"; +import { formatCompact } from "@/lib/format"; + +type Point = { date: string; value: number }; +type ChartPoint = { + date: string; + ts: number; + value: number | null; + forecast: number | null; +}; + +const config = { + stars: { + label: "Stars", + color: "var(--chart-1)", + }, + forecast: { + label: "Projected", + color: "var(--chart-1)", + }, +} satisfies ChartConfig; + +const FORECAST_WINDOW_DAYS = 90; +const DAY_MS = 24 * 60 * 60 * 1000; + +const formatTick = (ms: number) => + new Date(ms).toLocaleDateString("en-US", { month: "short", year: "2-digit" }); + +const formatTooltipLabel = (iso: string) => + new Date(iso).toLocaleDateString("en-US", { + month: "long", + day: "numeric", + year: "numeric", + }); + +function buildChartData(data: Point[]): ChartPoint[] { + const base: ChartPoint[] = data.map((p) => ({ + date: p.date, + ts: new Date(p.date).getTime(), + value: p.value, + forecast: null, + })); + if (data.length < 2) return base; + + const last = data[data.length - 1]!; + const lastMs = new Date(last.date).getTime(); + + const cutoff = lastMs - FORECAST_WINDOW_DAYS * DAY_MS; + let anchor = data[0]!; + for (let i = data.length - 1; i >= 0; i--) { + if (new Date(data[i]!.date).getTime() <= cutoff) { + anchor = data[i]!; + break; + } + } + const anchorMs = new Date(anchor.date).getTime(); + if (anchorMs >= lastMs) return base; + + const slope = (last.value - anchor.value) / (lastMs - anchorMs); + + const now = new Date(); + const monthEnd = Date.UTC( + now.getUTCFullYear(), + now.getUTCMonth() + 1, + 0, + 23, + 59, + 59, + ); + if (monthEnd <= lastMs) return base; + + const projected = Math.max( + last.value, + Math.round(last.value + slope * (monthEnd - lastMs)), + ); + + // Stamp the last real point with both keys so the dashed forecast line + // connects continuously to the solid history line. + base[base.length - 1] = { ...base[base.length - 1]!, forecast: last.value }; + base.push({ + date: new Date(monthEnd).toISOString(), + ts: monthEnd, + value: null, + forecast: projected, + }); + return base; +} + +export function StarHistoryChart({ data }: { data: Point[] }) { + const gradientId = useId(); + const chartData = useMemo(() => buildChartData(data), [data]); + + if (data.length < 2) { + return ( +
+ Star history is currently unavailable. +
+ ); + } + + return ( + + + + + + + + + + + formatCompact(v as number)} + tickLine={false} + axisLine={false} + width={36} + // 'auto' overshoots (10.5k → 12k); pad 5% and round up to 500 instead. + domain={[0, (max: number) => Math.ceil((max * 1.05) / 500) * 500]} + /> + { + // Drop the forecast row at the bridge point so today's tooltip + // doesn't show the same value twice. + const filtered = payload?.filter( + (e) => + !( + e.dataKey === "forecast" && + (e.payload as { value?: number | null } | undefined)?.value != + null + ), + ); + if (!active || !filtered || filtered.length === 0) return null; + return ( + { + const iso = p?.[0]?.payload?.date as string | undefined; + return iso ? formatTooltipLabel(iso) : ""; + }} + formatter={(value, name) => [ + `${formatCompact(value as number)} stars${name === "forecast" ? " (projected)" : ""}`, + "", + ]} + hideIndicator + /> + ); + }} + /> + + + + + ); +} diff --git a/apps/docs/components/traction/top-packages-bar.tsx b/apps/docs/components/traction/top-packages-bar.tsx new file mode 100644 index 0000000..2a3f6df --- /dev/null +++ b/apps/docs/components/traction/top-packages-bar.tsx @@ -0,0 +1,91 @@ +"use client"; + +import { Bar, BarChart, Cell, XAxis, YAxis } from "recharts"; +import { + ChartContainer, + type ChartConfig, + ChartTooltip, + ChartTooltipContent, +} from "@/components/ui/chart"; +import { formatCompact, formatNumber } from "@/lib/format"; + +type Row = { name: string; weekly: number }; + +const config = { + weekly: { + label: "Weekly downloads", + color: "var(--chart-3)", + }, +} satisfies ChartConfig; + +const shortenName = (name: string) => + name.replace("@assistant-ui/", "").replace(/^assistant-/, ""); + +export function TopPackagesBar({ rows }: { rows: Row[] }) { + if (rows.length === 0) { + return null; + } + + const data = rows.map((r) => ({ + name: r.name, + short: shortenName(r.name), + weekly: r.weekly, + })); + + const height = Math.max(220, data.length * 32 + 40); + + return ( + + + formatCompact(v as number)} + tickLine={false} + axisLine={false} + fontSize={10} + /> + + { + const full = payload?.[0]?.payload?.name as string | undefined; + return full ?? ""; + }} + formatter={(value) => [ + `${formatNumber(value as number)} / week`, + "", + ]} + hideIndicator + /> + } + /> + + {data.map((row, i) => ( + + ))} + + + + ); +} diff --git a/apps/docs/components/traction/weekly-downloads-stat.tsx b/apps/docs/components/traction/weekly-downloads-stat.tsx new file mode 100644 index 0000000..69e7b1b --- /dev/null +++ b/apps/docs/components/traction/weekly-downloads-stat.tsx @@ -0,0 +1,46 @@ +"use client"; + +import { useState } from "react"; +import { ArrowLeftRight, ArrowUpRight } from "lucide-react"; +import { NumberRoll } from "@/components/assistant-ui/number-roll"; + +type Mode = { value: number; caption: string }; + +export function WeeklyDownloadsStat({ + flagship, + total, +}: { + flagship: Mode; + total: Mode; +}) { + const [showTotal, setShowTotal] = useState(false); + const current = showTotal ? total : flagship; + return ( +
+ +
+ {current.value > 0 ? ( + + ) : ( + "—" + )} +
+
+ Weekly downloads + +
+
+ ); +} diff --git a/apps/docs/components/xulux/XuluxApp.tsx b/apps/docs/components/xulux/XuluxApp.tsx new file mode 100644 index 0000000..41f52a8 --- /dev/null +++ b/apps/docs/components/xulux/XuluxApp.tsx @@ -0,0 +1,225 @@ +"use client"; + +import { useEffect, useMemo, useRef, useState, type ReactNode } from "react"; +import { + AssistantCloud, + AssistantRuntimeProvider, + useRemoteThreadListRuntime, +} from "@assistant-ui/react"; +import { + AssistantChatTransport, + useChatRuntime, +} from "@assistant-ui/react-ai-sdk"; +import { AssistantPanelProvider } from "@/components/docs/assistant/context"; +import { XuluxAnalyticsProvider } from "@/lib/xulux/analytics-context"; +import type { XuluxTemplate } from "./templates/types"; +import { XuluxShell } from "./shell/XuluxShell"; +import { createXuluxLocalThreadListAdapter } from "./runtime/xulux-thread-list-adapter"; +import { XuluxThreadStatusObserver } from "./runtime/XuluxThreadStatusObserver"; +import { + parseXuluxLimitBlock, + XuluxUsageBudgetProvider, + type XuluxLimitBlock, +} from "./chat/XuluxUsageLimitBanner"; +import type { XuluxActivePreviewContext } from "./runtime/types"; + +export type SelectedTemplateContext = Pick< + XuluxTemplate, + | "id" + | "templateId" + | "versionId" + | "title" + | "description" + | "kind" + | "prompt" + | "previewUrl" + | "downloadUrl" + | "sourcePath" + | "docsUrl" +>; + +export function XuluxApp() { + const [sessionId, setSessionId] = useState(() => crypto.randomUUID()); + const [selectedTemplateContext, setSelectedTemplateContext] = + useState(null); + const [activePreviewContext, setActivePreviewContext] = + useState(null); + + const resetSession = () => { + setSelectedTemplateContext(null); + setActivePreviewContext(null); + }; + + return ( + + + + + + ); +} + +function XuluxRuntimeProvider({ + sessionId, + selectedTemplateContext, + activePreviewContext, + children, +}: { + sessionId: string; + selectedTemplateContext: SelectedTemplateContext | null; + activePreviewContext: XuluxActivePreviewContext | null; + children: ReactNode; +}) { + const cloudBaseUrl = + process.env.NEXT_PUBLIC_XULUX_ASSISTANT_BASE_URL ?? + process.env.NEXT_PUBLIC_ASSISTANT_BASE_URL; + + if (!cloudBaseUrl) { + return ; + } + + return ( + + {children} + + ); +} + +function XuluxMissingCloudConfig() { + return ( +
+

+ Xulux playground requires{" "} + NEXT_PUBLIC_ASSISTANT_BASE_URL{" "} + (or{" "} + + NEXT_PUBLIC_XULUX_ASSISTANT_BASE_URL + + ) to be set for Assistant Cloud message history. +

+
+ ); +} + +function XuluxRuntimeProviderInner({ + sessionId, + selectedTemplateContext, + activePreviewContext, + cloudBaseUrl, + children, +}: { + sessionId: string; + selectedTemplateContext: SelectedTemplateContext | null; + activePreviewContext: XuluxActivePreviewContext | null; + cloudBaseUrl: string; + children: ReactNode; +}) { + const sessionIdRef = useRef(sessionId); + sessionIdRef.current = sessionId; + const selectedTemplateContextRef = useRef(selectedTemplateContext); + selectedTemplateContextRef.current = selectedTemplateContext; + const activePreviewContextRef = useRef(activePreviewContext); + activePreviewContextRef.current = activePreviewContext; + const [limitBlock, setLimitBlock] = useState(null); + + useEffect(() => { + setLimitBlock(null); + }, [sessionId]); + + const assistantCloud = useMemo( + () => + new AssistantCloud({ + baseUrl: cloudBaseUrl, + anonymous: true, + telemetry: { + beforeReport: (report) => ({ + ...report, + metadata: { + ...(report.metadata ?? {}), + app: "xulux_playground", + }, + }), + }, + }), + [cloudBaseUrl], + ); + + const adapter = useMemo( + () => + createXuluxLocalThreadListAdapter({ + getCurrentSessionId: () => sessionIdRef.current, + cloud: assistantCloud, + }), + [assistantCloud], + ); + + const transport = useMemo( + () => + new AssistantChatTransport({ + api: "/api/xulux/chat", + body: { + get sessionId() { + return sessionIdRef.current; + }, + get selectedTemplate() { + return selectedTemplateContextRef.current; + }, + get activePreviewContext() { + return activePreviewContextRef.current; + }, + }, + fetch: async (input, init) => { + const res = await fetch(input, init); + if (res.status === 429) { + const payload = await res + .clone() + .json() + .catch(() => null); + const block = parseXuluxLimitBlock(payload); + if (block) setLimitBlock(block); + } + return res; + }, + }), + [], + ); + + const runtime = useRemoteThreadListRuntime({ + adapter, + runtimeHook: function XuluxChatRuntimeHook() { + return useChatRuntime({ + transport, + isSendDisabled: limitBlock != null, + }); + }, + }); + + return ( + setLimitBlock(null)} + > + + + + {children} + + + + ); +} diff --git a/apps/docs/components/xulux/XuluxPoweredBy.tsx b/apps/docs/components/xulux/XuluxPoweredBy.tsx new file mode 100644 index 0000000..468eb7a --- /dev/null +++ b/apps/docs/components/xulux/XuluxPoweredBy.tsx @@ -0,0 +1,22 @@ +import { cn } from "@/lib/utils"; + +export function XuluxPoweredBy({ className }: { className?: string }) { + return ( +

+ Powered by{" "} + + xulux.ai + +

+ ); +} diff --git a/apps/docs/components/xulux/canvas/XuluxCanvas.tsx b/apps/docs/components/xulux/canvas/XuluxCanvas.tsx new file mode 100644 index 0000000..8e667a2 --- /dev/null +++ b/apps/docs/components/xulux/canvas/XuluxCanvas.tsx @@ -0,0 +1,385 @@ +"use client"; + +import { useCallback, useEffect, useRef, useState } from "react"; +import { + Download, + ExternalLink, + Code2, + GlobeIcon, + Loader2, + RefreshCw, +} from "lucide-react"; +import { Button } from "@/components/ui/radix/button"; +import { GitHubIcon } from "@/components/icons/github"; +import { Tabs } from "@/components/assistant-ui/tabs"; +import { + trackXuluxDownload, + useXuluxAnalytics, +} from "@/lib/xulux/analytics-context"; +import { cn } from "@/lib/utils"; +import type { XuluxPreviewFrame as XuluxPreviewFrameConfig } from "../templates/types"; +import { XuluxCanvasTabBar, type CanvasTab } from "./XuluxCanvasTabBar"; +import { XuluxFileBrowser } from "./XuluxFileBrowser"; +import { XuluxPreviewFrame } from "./XuluxPreviewFrame"; +import { useVirtualArchive } from "./useVirtualArchive"; + +function toAbsoluteUrl(url: string | null): string | null { + if (!url) return null; + if (/^https?:\/\//.test(url)) return url; + if (typeof window === "undefined") return url; + return new URL(url, window.location.origin).toString(); +} + +function filenameFromDisposition(header: string | null): string | null { + if (!header) return null; + const match = /filename="?([^";]+)"?/i.exec(header); + return match?.[1] ?? null; +} + +function hostnameFromUrl(url: string): string { + try { + return new URL(url).hostname; + } catch { + return url; + } +} + +const TABS: CanvasTab[] = [ + { id: "preview", label: "Preview", icon: GlobeIcon }, + { id: "code", label: "Code", icon: Code2 }, +]; + +export function XuluxCanvas({ + sessionId, + status, + previewUrl, + source, + error, + downloadUrl, + previewFrame, + templateId, + versionId, + sourceUrl, + title, +}: { + sessionId: string; + status: "empty" | "loading" | "ready" | "error"; + previewUrl: string | null; + source: "template" | "agent_template" | "refresh" | null; + error: string | null; + downloadUrl?: string; + previewFrame?: XuluxPreviewFrameConfig; + templateId?: string; + versionId?: string; + sourceUrl?: string; + title?: string; +}) { + const analyticsCtx = useXuluxAnalytics(); + const [activeTab, setActiveTab] = useState("preview"); + const [isDownloading, setIsDownloading] = useState(false); + const [downloadError, setDownloadError] = useState(null); + const [iframeVersion, setIframeVersion] = useState(0); + const [isPreviewLoading, setIsPreviewLoading] = useState(false); + const previewLoadedForUrlRef = useRef(null); + const canDownloadTemplate = Boolean(downloadUrl) && status === "ready"; + const canDownloadSandbox = source === "refresh" && status === "ready"; + const canOpenSource = + source === "template" && status === "ready" && sourceUrl; + const resolvedPreviewUrl = toAbsoluteUrl(previewUrl); + const canRefresh = !!resolvedPreviewUrl && status === "ready"; + const iframeKey = resolvedPreviewUrl + ? `${resolvedPreviewUrl}-${iframeVersion}` + : "empty"; + + const archiveUrl = canDownloadTemplate ? downloadUrl : undefined; + const archiveState = useVirtualArchive(archiveUrl, templateId, versionId); + + useEffect(() => { + if (!resolvedPreviewUrl) { + previewLoadedForUrlRef.current = null; + setIsPreviewLoading(false); + return; + } + if (previewLoadedForUrlRef.current === iframeKey) return; + setIsPreviewLoading(true); + }, [resolvedPreviewUrl, iframeKey]); + + const handleRefreshPreview = useCallback(() => { + previewLoadedForUrlRef.current = null; + setIsPreviewLoading(true); + setIframeVersion((value) => value + 1); + }, []); + + const handleDownload = useCallback(async () => { + setIsDownloading(true); + setDownloadError(null); + try { + const response = await fetch("/api/xulux/download", { + method: "POST", + headers: { "Content-Type": "application/json" }, + body: JSON.stringify({ sessionId }), + }); + if (!response.ok) { + const payload = (await response.json().catch(() => null)) as { + error?: string; + } | null; + throw new Error(payload?.error ?? "Failed to download workspace."); + } + + const blob = await response.blob(); + const url = URL.createObjectURL(blob); + const link = document.createElement("a"); + link.href = url; + link.download = + filenameFromDisposition(response.headers.get("Content-Disposition")) ?? + `xulux-workspace-${sessionId.slice(0, 12)}.tar.gz`; + document.body.appendChild(link); + link.click(); + link.remove(); + URL.revokeObjectURL(url); + trackXuluxDownload(analyticsCtx, { + surface: "canvas", + download_type: "sandbox", + ...(templateId ? { template_id: templateId } : {}), + }); + } catch (downloadErr) { + setDownloadError( + downloadErr instanceof Error + ? downloadErr.message + : String(downloadErr), + ); + } finally { + setIsDownloading(false); + } + }, [analyticsCtx, sessionId, templateId]); + + const hasPreview = !!resolvedPreviewUrl; + const resolvedDownloadUrl = downloadUrl + ? toAbsoluteUrl(downloadUrl) + : undefined; + + const tabs: CanvasTab[] = TABS.map((tab) => { + if (tab.id === "preview") { + return { + ...tab, + label: hasPreview + ? (title ?? hostnameFromUrl(resolvedPreviewUrl!)) + : status === "error" + ? "Preview unavailable" + : "Preview", + }; + } + if (tab.id === "code") { + return { + ...tab, + disabled: !canDownloadTemplate, + }; + } + return tab; + }); + + const tabActions = ( + <> + {activeTab === "preview" && hasPreview && ( + + )} + {activeTab === "preview" && canRefresh && ( + + )} + {canDownloadTemplate && ( + + )} + {canDownloadSandbox && ( + + )} + {canOpenSource && ( + + )} + + ); + + return ( + + + + {downloadError && ( +
+ {downloadError} +
+ )} + +
+ {/* Keep both panels mounted and avoid display:none — Radix TabsContent sets + the hidden attribute on inactive tabs, which tears down iframe paint and + causes a blank flash when switching back to Preview. */} +
+ {resolvedPreviewUrl ? ( + <> + {isPreviewLoading && activeTab === "preview" && ( +
+
+ + Loading preview... +
+
+ )} + +