skybridge 0.0.0-dev.e97960f → 0.0.0-dev.e99ee17
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +126 -124
- package/dist/cli/build-helpers.d.ts +11 -0
- package/dist/cli/build-helpers.js +107 -0
- package/dist/cli/build-helpers.js.map +1 -0
- package/dist/cli/build-helpers.test.js +132 -0
- package/dist/cli/build-helpers.test.js.map +1 -0
- package/dist/cli/build-steps.d.ts +2 -0
- package/dist/cli/build-steps.js +73 -0
- package/dist/cli/build-steps.js.map +1 -0
- package/dist/cli/build-steps.test.js +52 -0
- package/dist/cli/build-steps.test.js.map +1 -0
- package/dist/cli/detect-port.d.ts +2 -2
- package/dist/cli/detect-port.js +8 -20
- package/dist/cli/detect-port.js.map +1 -1
- package/dist/cli/header.d.ts +1 -1
- package/dist/cli/resolve-skybridge-config.d.ts +5 -0
- package/dist/cli/resolve-skybridge-config.js +18 -0
- package/dist/cli/resolve-skybridge-config.js.map +1 -0
- package/dist/cli/run-plain.d.ts +18 -0
- package/dist/cli/run-plain.js +90 -0
- package/dist/cli/run-plain.js.map +1 -0
- package/dist/cli/telemetry.js +2 -0
- package/dist/cli/telemetry.js.map +1 -1
- package/dist/cli/use-execute-steps.d.ts +1 -1
- package/dist/cli/use-nodemon.d.ts +25 -1
- package/dist/cli/use-nodemon.js +96 -63
- package/dist/cli/use-nodemon.js.map +1 -1
- package/dist/cli/use-typescript-check.d.ts +8 -2
- package/dist/cli/use-typescript-check.js +71 -68
- package/dist/cli/use-typescript-check.js.map +1 -1
- package/dist/commands/build.d.ts +0 -3
- package/dist/commands/build.js +2 -73
- package/dist/commands/build.js.map +1 -1
- package/dist/commands/create.d.ts +9 -0
- package/dist/commands/create.js +30 -0
- package/dist/commands/create.js.map +1 -0
- package/dist/commands/dev.d.ts +1 -0
- package/dist/commands/dev.js +51 -3
- package/dist/commands/dev.js.map +1 -1
- package/dist/commands/start.js +7 -1
- package/dist/commands/start.js.map +1 -1
- package/dist/context-warnings.d.ts +5 -0
- package/dist/context-warnings.js +31 -0
- package/dist/context-warnings.js.map +1 -0
- package/dist/context-warnings.test.js +28 -0
- package/dist/context-warnings.test.js.map +1 -0
- package/dist/server/app.d.ts +194 -0
- package/dist/server/app.js +266 -0
- package/dist/server/app.js.map +1 -0
- package/dist/server/app.test.js +97 -0
- package/dist/server/app.test.js.map +1 -0
- package/dist/server/asset-base-url-transform-plugin.d.ts +1 -0
- package/dist/server/asset-base-url-transform-plugin.js +16 -1
- package/dist/server/asset-base-url-transform-plugin.js.map +1 -1
- package/dist/server/asset-base-url-transform-plugin.test.js +51 -1
- package/dist/server/asset-base-url-transform-plugin.test.js.map +1 -1
- package/dist/server/auth/discovery.d.ts +33 -0
- package/dist/server/auth/discovery.js +56 -0
- package/dist/server/auth/discovery.js.map +1 -0
- package/dist/server/auth/discovery.test.d.ts +1 -0
- package/dist/server/auth/discovery.test.js +93 -0
- package/dist/server/auth/discovery.test.js.map +1 -0
- package/dist/server/auth/index.d.ts +48 -0
- package/dist/server/auth/index.js +2 -0
- package/dist/server/auth/index.js.map +1 -0
- package/dist/server/auth/providers/auth0.d.ts +40 -0
- package/dist/server/auth/providers/auth0.js +36 -0
- package/dist/server/auth/providers/auth0.js.map +1 -0
- package/dist/server/auth/providers/auth0.test.d.ts +1 -0
- package/dist/server/auth/providers/auth0.test.js +50 -0
- package/dist/server/auth/providers/auth0.test.js.map +1 -0
- package/dist/server/auth/providers/authplane.d.ts +49 -0
- package/dist/server/auth/providers/authplane.js +57 -0
- package/dist/server/auth/providers/authplane.js.map +1 -0
- package/dist/server/auth/providers/authplane.test.d.ts +1 -0
- package/dist/server/auth/providers/authplane.test.js +143 -0
- package/dist/server/auth/providers/authplane.test.js.map +1 -0
- package/dist/server/auth/providers/clerk.d.ts +51 -0
- package/dist/server/auth/providers/clerk.js +19 -0
- package/dist/server/auth/providers/clerk.js.map +1 -0
- package/dist/server/auth/providers/clerk.test.d.ts +1 -0
- package/dist/server/auth/providers/clerk.test.js +30 -0
- package/dist/server/auth/providers/clerk.test.js.map +1 -0
- package/dist/server/auth/providers/custom.d.ts +42 -0
- package/dist/server/auth/providers/custom.js +59 -0
- package/dist/server/auth/providers/custom.js.map +1 -0
- package/dist/server/auth/providers/custom.test.d.ts +1 -0
- package/dist/server/auth/providers/custom.test.js +161 -0
- package/dist/server/auth/providers/custom.test.js.map +1 -0
- package/dist/server/auth/providers/descope.d.ts +44 -0
- package/dist/server/auth/providers/descope.js +44 -0
- package/dist/server/auth/providers/descope.js.map +1 -0
- package/dist/server/auth/providers/descope.test.d.ts +1 -0
- package/dist/server/auth/providers/descope.test.js +62 -0
- package/dist/server/auth/providers/descope.test.js.map +1 -0
- package/dist/server/auth/providers/shared.d.ts +2 -0
- package/dist/server/auth/providers/shared.js +6 -0
- package/dist/server/auth/providers/shared.js.map +1 -0
- package/dist/server/auth/providers/shared.test.d.ts +1 -0
- package/dist/server/auth/providers/shared.test.js +10 -0
- package/dist/server/auth/providers/shared.test.js.map +1 -0
- package/dist/server/auth/providers/stytch.d.ts +25 -0
- package/dist/server/auth/providers/stytch.js +16 -0
- package/dist/server/auth/providers/stytch.js.map +1 -0
- package/dist/server/auth/providers/verify-spy.d.ts +12 -0
- package/dist/server/auth/providers/verify-spy.js +21 -0
- package/dist/server/auth/providers/verify-spy.js.map +1 -0
- package/dist/server/auth/providers/workos.d.ts +34 -0
- package/dist/server/auth/providers/workos.js +16 -0
- package/dist/server/auth/providers/workos.js.map +1 -0
- package/dist/server/auth/security-schemes.d.ts +23 -0
- package/dist/server/auth/security-schemes.js +64 -0
- package/dist/server/auth/security-schemes.js.map +1 -0
- package/dist/server/auth/security-schemes.test.d.ts +1 -0
- package/dist/server/auth/security-schemes.test.js +99 -0
- package/dist/server/auth/security-schemes.test.js.map +1 -0
- package/dist/server/auth/setup.d.ts +6 -0
- package/dist/server/auth/setup.js +91 -0
- package/dist/server/auth/setup.js.map +1 -0
- package/dist/server/auth/setup.test.d.ts +1 -0
- package/dist/server/auth/setup.test.js +514 -0
- package/dist/server/auth/setup.test.js.map +1 -0
- package/dist/server/auth/verify.d.ts +48 -0
- package/dist/server/auth/verify.js +62 -0
- package/dist/server/auth/verify.js.map +1 -0
- package/dist/server/auth/verify.test.d.ts +1 -0
- package/dist/server/auth/verify.test.js +165 -0
- package/dist/server/auth/verify.test.js.map +1 -0
- package/dist/server/auth-extra.test-d.js +130 -0
- package/dist/server/auth-extra.test-d.js.map +1 -0
- package/dist/server/auth.d.ts +57 -0
- package/dist/server/auth.js +27 -0
- package/dist/server/auth.js.map +1 -0
- package/dist/server/build-manifest.test.d.ts +1 -0
- package/dist/server/build-manifest.test.js +28 -0
- package/dist/server/build-manifest.test.js.map +1 -0
- package/dist/server/content-helpers.d.ts +41 -1
- package/dist/server/content-helpers.js +33 -0
- package/dist/server/content-helpers.js.map +1 -1
- package/dist/server/events.d.ts +113 -0
- package/dist/server/events.js +318 -0
- package/dist/server/events.js.map +1 -0
- package/dist/server/events.test.d.ts +1 -0
- package/dist/server/events.test.js +172 -0
- package/dist/server/events.test.js.map +1 -0
- package/dist/server/express.d.ts +23 -3
- package/dist/server/express.js +43 -32
- package/dist/server/express.js.map +1 -1
- package/dist/server/express.test.js +269 -90
- package/dist/server/express.test.js.map +1 -1
- package/dist/server/file-ref.d.ts +21 -1
- package/dist/server/file-ref.js +20 -1
- package/dist/server/file-ref.js.map +1 -1
- package/dist/server/form-input.d.ts +120 -0
- package/dist/server/form-input.js +89 -0
- package/dist/server/form-input.js.map +1 -0
- package/dist/server/host.d.ts +2 -0
- package/dist/server/host.js +11 -0
- package/dist/server/host.js.map +1 -0
- package/dist/server/host.test.d.ts +1 -0
- package/dist/server/host.test.js +13 -0
- package/dist/server/host.test.js.map +1 -0
- package/dist/server/index.d.ts +19 -2
- package/dist/server/index.js +16 -1
- package/dist/server/index.js.map +1 -1
- package/dist/server/mcp-errors.test.d.ts +1 -0
- package/dist/server/mcp-errors.test.js +41 -0
- package/dist/server/mcp-errors.test.js.map +1 -0
- package/dist/server/middleware.d.ts +51 -13
- package/dist/server/middleware.js +33 -0
- package/dist/server/middleware.js.map +1 -1
- package/dist/server/middleware.test-d.js +1 -1
- package/dist/server/middleware.test-d.js.map +1 -1
- package/dist/server/middleware.test.js +242 -134
- package/dist/server/middleware.test.js.map +1 -1
- package/dist/server/register-tool.test.d.ts +1 -0
- package/dist/server/register-tool.test.js +230 -0
- package/dist/server/register-tool.test.js.map +1 -0
- package/dist/server/requestOrigin.d.ts +7 -0
- package/dist/server/requestOrigin.js +25 -0
- package/dist/server/requestOrigin.js.map +1 -0
- package/dist/server/server.d.ts +532 -67
- package/dist/server/server.js +646 -315
- package/dist/server/server.js.map +1 -1
- package/dist/server/skills-integration.test.d.ts +1 -0
- package/dist/server/skills-integration.test.js +86 -0
- package/dist/server/skills-integration.test.js.map +1 -0
- package/dist/server/skills.d.ts +27 -0
- package/dist/server/skills.js +188 -0
- package/dist/server/skills.js.map +1 -0
- package/dist/server/skills.test.d.ts +1 -0
- package/dist/server/skills.test.js +243 -0
- package/dist/server/skills.test.js.map +1 -0
- package/dist/server/templateHelper.d.ts +0 -3
- package/dist/server/templateHelper.js.map +1 -1
- package/dist/server/templates.generated.js +4 -8
- package/dist/server/templates.generated.js.map +1 -1
- package/dist/server/tool-extra.test.d.ts +1 -0
- package/dist/server/tool-extra.test.js +167 -0
- package/dist/server/tool-extra.test.js.map +1 -0
- package/dist/server/view-name.test-d.d.ts +1 -0
- package/dist/server/view-name.test-d.js +8 -0
- package/dist/server/view-name.test-d.js.map +1 -0
- package/dist/server/view-resource-resolution.test.d.ts +6 -0
- package/dist/server/view-resource-resolution.test.js +176 -0
- package/dist/server/view-resource-resolution.test.js.map +1 -0
- package/dist/server/viewsDevServer.js +4 -1
- package/dist/server/viewsDevServer.js.map +1 -1
- package/dist/test/utils.d.ts +9 -11
- package/dist/test/utils.js +220 -201
- package/dist/test/utils.js.map +1 -1
- package/dist/test/view.test.js +123 -106
- package/dist/test/view.test.js.map +1 -1
- package/dist/views/index.d.ts +10 -0
- package/dist/views/index.js +3 -0
- package/dist/views/index.js.map +1 -0
- package/dist/{web/plugin → views}/scan-views.js +1 -1
- package/dist/views/scan-views.js.map +1 -0
- package/dist/views/scan-views.test.d.ts +1 -0
- package/dist/views/scan-views.test.js.map +1 -0
- package/dist/views/validate-view.d.ts +1 -0
- package/dist/{web/plugin → views}/validate-view.js +2 -2
- package/dist/views/validate-view.js.map +1 -0
- package/dist/views/validate-view.test.d.ts +1 -0
- package/dist/views/validate-view.test.js.map +1 -0
- package/dist/web/bridges/adaptor.d.ts +62 -0
- package/dist/web/bridges/adaptor.js +471 -0
- package/dist/web/bridges/adaptor.js.map +1 -0
- package/dist/web/bridges/adaptor.test.d.ts +1 -0
- package/dist/web/bridges/adaptor.test.js +264 -0
- package/dist/web/bridges/adaptor.test.js.map +1 -0
- package/dist/web/bridges/apps-sdk/bridge.d.ts +6 -1
- package/dist/web/bridges/apps-sdk/bridge.js +15 -3
- package/dist/web/bridges/apps-sdk/bridge.js.map +1 -1
- package/dist/web/bridges/apps-sdk/index.d.ts +0 -1
- package/dist/web/bridges/apps-sdk/index.js +0 -1
- package/dist/web/bridges/apps-sdk/index.js.map +1 -1
- package/dist/web/bridges/apps-sdk/use-apps-sdk-context.d.ts +11 -0
- package/dist/web/bridges/apps-sdk/use-apps-sdk-context.js +11 -0
- package/dist/web/bridges/apps-sdk/use-apps-sdk-context.js.map +1 -1
- package/dist/web/bridges/get-adaptor.d.ts +7 -0
- package/dist/web/bridges/get-adaptor.js +9 -7
- package/dist/web/bridges/get-adaptor.js.map +1 -1
- package/dist/web/bridges/get-adaptor.test.d.ts +1 -0
- package/dist/web/bridges/get-adaptor.test.js +32 -0
- package/dist/web/bridges/get-adaptor.test.js.map +1 -0
- package/dist/web/bridges/mcp-app/bridge.d.ts +15 -2
- package/dist/web/bridges/mcp-app/bridge.js +80 -7
- package/dist/web/bridges/mcp-app/bridge.js.map +1 -1
- package/dist/web/bridges/mcp-app/bridge.test.d.ts +1 -0
- package/dist/web/bridges/mcp-app/bridge.test.js +17 -0
- package/dist/web/bridges/mcp-app/bridge.test.js.map +1 -0
- package/dist/web/bridges/mcp-app/index.d.ts +0 -1
- package/dist/web/bridges/mcp-app/index.js +0 -1
- package/dist/web/bridges/mcp-app/index.js.map +1 -1
- package/dist/web/bridges/mcp-app/types.d.ts +19 -2
- package/dist/web/bridges/mcp-app/types.js.map +1 -1
- package/dist/web/bridges/mcp-app/use-mcp-app-context.d.ts +13 -1
- package/dist/web/bridges/mcp-app/use-mcp-app-context.js +12 -0
- package/dist/web/bridges/mcp-app/use-mcp-app-context.js.map +1 -1
- package/dist/web/bridges/mcp-app/use-mcp-app-context.test.js +1 -1
- package/dist/web/bridges/mcp-app/use-mcp-app-context.test.js.map +1 -1
- package/dist/web/bridges/mcp-app/view-tools.test.d.ts +1 -0
- package/dist/web/bridges/mcp-app/view-tools.test.js +143 -0
- package/dist/web/bridges/mcp-app/view-tools.test.js.map +1 -0
- package/dist/web/bridges/types.d.ts +160 -5
- package/dist/web/bridges/types.js +14 -1
- package/dist/web/bridges/types.js.map +1 -1
- package/dist/web/bridges/types.test.d.ts +1 -0
- package/dist/web/bridges/types.test.js +19 -0
- package/dist/web/bridges/types.test.js.map +1 -0
- package/dist/web/bridges/use-host-context.d.ts +5 -0
- package/dist/web/bridges/use-host-context.js +5 -0
- package/dist/web/bridges/use-host-context.js.map +1 -1
- package/dist/web/components/modal-provider.d.ts +1 -1
- package/dist/web/components/modal-provider.js +4 -3
- package/dist/web/components/modal-provider.js.map +1 -1
- package/dist/web/create-store.d.ts +26 -0
- package/dist/web/create-store.js +26 -0
- package/dist/web/create-store.js.map +1 -1
- package/dist/web/create-store.test.js +10 -7
- package/dist/web/create-store.test.js.map +1 -1
- package/dist/web/data-llm.d.ts +34 -1
- package/dist/web/data-llm.js +31 -2
- package/dist/web/data-llm.js.map +1 -1
- package/dist/web/data-llm.test.js +26 -7
- package/dist/web/data-llm.test.js.map +1 -1
- package/dist/web/generate-helpers.d.ts +25 -19
- package/dist/web/generate-helpers.js +23 -17
- package/dist/web/generate-helpers.js.map +1 -1
- package/dist/web/generate-helpers.test-d.js +7 -5
- package/dist/web/generate-helpers.test-d.js.map +1 -1
- package/dist/web/generate-helpers.test.js +2 -2
- package/dist/web/generate-helpers.test.js.map +1 -1
- package/dist/web/hooks/index.d.ts +8 -1
- package/dist/web/hooks/index.js +8 -1
- package/dist/web/hooks/index.js.map +1 -1
- package/dist/web/hooks/openai-extensions.test.d.ts +1 -0
- package/dist/web/hooks/openai-extensions.test.js +107 -0
- package/dist/web/hooks/openai-extensions.test.js.map +1 -0
- package/dist/web/hooks/test/utils.d.ts +7 -2
- package/dist/web/hooks/test/utils.js +14 -3
- package/dist/web/hooks/test/utils.js.map +1 -1
- package/dist/web/hooks/use-call-tool.d.ts +47 -2
- package/dist/web/hooks/use-call-tool.js +43 -10
- package/dist/web/hooks/use-call-tool.js.map +1 -1
- package/dist/web/hooks/use-call-tool.test.js +75 -25
- package/dist/web/hooks/use-call-tool.test.js.map +1 -1
- package/dist/web/hooks/use-deep-link.d.ts +22 -0
- package/dist/web/hooks/use-deep-link.js +34 -0
- package/dist/web/hooks/use-deep-link.js.map +1 -0
- package/dist/web/hooks/use-display-mode.d.ts +20 -0
- package/dist/web/hooks/use-display-mode.js +20 -0
- package/dist/web/hooks/use-display-mode.js.map +1 -1
- package/dist/web/hooks/use-display-mode.test.js +57 -21
- package/dist/web/hooks/use-display-mode.test.js.map +1 -1
- package/dist/web/hooks/use-download.d.ts +3 -0
- package/dist/web/hooks/use-download.js +7 -0
- package/dist/web/hooks/use-download.js.map +1 -0
- package/dist/web/hooks/use-download.test.d.ts +1 -0
- package/dist/web/hooks/use-download.test.js +103 -0
- package/dist/web/hooks/use-download.test.js.map +1 -0
- package/dist/web/hooks/use-file-resource.d.ts +37 -0
- package/dist/web/hooks/use-file-resource.js +98 -0
- package/dist/web/hooks/use-file-resource.js.map +1 -0
- package/dist/web/hooks/use-file-resource.test.d.ts +1 -0
- package/dist/web/hooks/use-file-resource.test.js +65 -0
- package/dist/web/hooks/use-file-resource.test.js.map +1 -0
- package/dist/web/hooks/use-files.d.ts +32 -0
- package/dist/web/hooks/use-files.js +32 -0
- package/dist/web/hooks/use-files.js.map +1 -1
- package/dist/web/hooks/use-files.test.js +11 -3
- package/dist/web/hooks/use-files.test.js.map +1 -1
- package/dist/web/hooks/use-host.d.ts +44 -0
- package/dist/web/hooks/use-host.js +45 -0
- package/dist/web/hooks/use-host.js.map +1 -0
- package/dist/web/hooks/use-host.test.d.ts +1 -0
- package/dist/web/hooks/use-host.test.js +59 -0
- package/dist/web/hooks/use-host.test.js.map +1 -0
- package/dist/web/hooks/use-model-context.d.ts +44 -0
- package/dist/web/hooks/use-model-context.js +63 -0
- package/dist/web/hooks/use-model-context.js.map +1 -0
- package/dist/web/hooks/use-open-external.d.ts +17 -0
- package/dist/web/hooks/use-open-external.js +16 -0
- package/dist/web/hooks/use-open-external.js.map +1 -1
- package/dist/web/hooks/use-open-external.test.js +15 -5
- package/dist/web/hooks/use-open-external.test.js.map +1 -1
- package/dist/web/hooks/use-open-file.d.ts +20 -0
- package/dist/web/hooks/use-open-file.js +25 -0
- package/dist/web/hooks/use-open-file.js.map +1 -0
- package/dist/web/hooks/use-register-view-tool.d.ts +37 -0
- package/dist/web/hooks/use-register-view-tool.js +50 -0
- package/dist/web/hooks/use-register-view-tool.js.map +1 -0
- package/dist/web/hooks/use-request-close.d.ts +14 -0
- package/dist/web/hooks/use-request-close.js +13 -0
- package/dist/web/hooks/use-request-close.js.map +1 -1
- package/dist/web/hooks/use-request-close.test.js +35 -40
- package/dist/web/hooks/use-request-close.test.js.map +1 -1
- package/dist/web/hooks/use-request-modal.d.ts +16 -1
- package/dist/web/hooks/use-request-modal.js +16 -1
- package/dist/web/hooks/use-request-modal.js.map +1 -1
- package/dist/web/hooks/use-request-modal.test.js +11 -1
- package/dist/web/hooks/use-request-modal.test.js.map +1 -1
- package/dist/web/hooks/use-request-size.d.ts +17 -0
- package/dist/web/hooks/use-request-size.js +16 -0
- package/dist/web/hooks/use-request-size.js.map +1 -1
- package/dist/web/hooks/use-request-size.test.js +35 -53
- package/dist/web/hooks/use-request-size.test.js.map +1 -1
- package/dist/web/hooks/use-send-follow-up-message.d.ts +25 -2
- package/dist/web/hooks/use-send-follow-up-message.js +23 -0
- package/dist/web/hooks/use-send-follow-up-message.js.map +1 -1
- package/dist/web/hooks/use-set-open-in-app-url.d.ts +17 -0
- package/dist/web/hooks/use-set-open-in-app-url.js +17 -0
- package/dist/web/hooks/use-set-open-in-app-url.js.map +1 -1
- package/dist/web/hooks/use-set-open-in-app-url.test.js +26 -12
- package/dist/web/hooks/use-set-open-in-app-url.test.js.map +1 -1
- package/dist/web/hooks/use-tool-info.d.ts +46 -14
- package/dist/web/hooks/use-tool-info.js +29 -7
- package/dist/web/hooks/use-tool-info.js.map +1 -1
- package/dist/web/hooks/use-tool-info.test-d.js +11 -34
- package/dist/web/hooks/use-tool-info.test-d.js.map +1 -1
- package/dist/web/hooks/use-tool-info.test.js +42 -37
- package/dist/web/hooks/use-tool-info.test.js.map +1 -1
- package/dist/web/hooks/use-user.d.ts +5 -2
- package/dist/web/hooks/use-user.js +6 -3
- package/dist/web/hooks/use-user.js.map +1 -1
- package/dist/web/hooks/use-user.test.js +85 -37
- package/dist/web/hooks/use-user.test.js.map +1 -1
- package/dist/web/hooks/use-view-state.d.ts +21 -0
- package/dist/web/hooks/use-view-state.js +19 -9
- package/dist/web/hooks/use-view-state.js.map +1 -1
- package/dist/web/hooks/use-view-state.test.js +39 -4
- package/dist/web/hooks/use-view-state.test.js.map +1 -1
- package/dist/web/hooks/use-viewport.d.ts +20 -0
- package/dist/web/hooks/use-viewport.js +21 -0
- package/dist/web/hooks/use-viewport.js.map +1 -0
- package/dist/web/hooks/use-viewport.test.d.ts +1 -0
- package/dist/web/hooks/use-viewport.test.js +109 -0
- package/dist/web/hooks/use-viewport.test.js.map +1 -0
- package/dist/web/mount-view.d.ts +20 -0
- package/dist/web/mount-view.js +22 -6
- package/dist/web/mount-view.js.map +1 -1
- package/dist/web/types.d.ts +4 -0
- package/dist/web/types.js.map +1 -1
- package/package.json +35 -23
- package/dist/web/bridges/apps-sdk/adaptor.d.ts +0 -26
- package/dist/web/bridges/apps-sdk/adaptor.js +0 -102
- package/dist/web/bridges/apps-sdk/adaptor.js.map +0 -1
- package/dist/web/bridges/mcp-app/adaptor.d.ts +0 -50
- package/dist/web/bridges/mcp-app/adaptor.js +0 -271
- package/dist/web/bridges/mcp-app/adaptor.js.map +0 -1
- package/dist/web/hooks/use-layout.d.ts +0 -22
- package/dist/web/hooks/use-layout.js +0 -23
- package/dist/web/hooks/use-layout.js.map +0 -1
- package/dist/web/hooks/use-layout.test.js +0 -96
- package/dist/web/hooks/use-layout.test.js.map +0 -1
- package/dist/web/plugin/data-llm.test.js +0 -81
- package/dist/web/plugin/data-llm.test.js.map +0 -1
- package/dist/web/plugin/plugin.d.ts +0 -5
- package/dist/web/plugin/plugin.js +0 -163
- package/dist/web/plugin/plugin.js.map +0 -1
- package/dist/web/plugin/scan-views.js.map +0 -1
- package/dist/web/plugin/scan-views.test.js.map +0 -1
- package/dist/web/plugin/transform-data-llm.d.ts +0 -12
- package/dist/web/plugin/transform-data-llm.js +0 -96
- package/dist/web/plugin/transform-data-llm.js.map +0 -1
- package/dist/web/plugin/transform-data-llm.test.js +0 -81
- package/dist/web/plugin/transform-data-llm.test.js.map +0 -1
- package/dist/web/plugin/validate-view.d.ts +0 -1
- package/dist/web/plugin/validate-view.js.map +0 -1
- package/dist/web/plugin/validate-view.test.js.map +0 -1
- /package/dist/{web/hooks/use-layout.test.d.ts → cli/build-helpers.test.d.ts} +0 -0
- /package/dist/{web/plugin/data-llm.test.d.ts → cli/build-steps.test.d.ts} +0 -0
- /package/dist/{web/plugin/scan-views.test.d.ts → context-warnings.test.d.ts} +0 -0
- /package/dist/{web/plugin/transform-data-llm.test.d.ts → server/app.test.d.ts} +0 -0
- /package/dist/{web/plugin/validate-view.test.d.ts → server/auth-extra.test-d.d.ts} +0 -0
- /package/dist/{web/plugin → views}/scan-views.d.ts +0 -0
- /package/dist/{web/plugin → views}/scan-views.test.js +0 -0
- /package/dist/{web/plugin → views}/validate-view.test.js +0 -0
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
import { renderHook, waitFor } from "@testing-library/react";
|
|
2
|
+
import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
|
|
3
|
+
import { HostAdaptor } from "../bridges/adaptor.js";
|
|
4
|
+
import { McpAppBridge } from "../bridges/mcp-app/bridge.js";
|
|
5
|
+
import { getMcpAppHostPostMessageMock, MockResizeObserver, } from "./test/utils.js";
|
|
6
|
+
import { useDownload } from "./use-download.js";
|
|
7
|
+
const params = {
|
|
8
|
+
contents: [
|
|
9
|
+
{
|
|
10
|
+
type: "resource",
|
|
11
|
+
resource: {
|
|
12
|
+
uri: "file:///export.json",
|
|
13
|
+
mimeType: "application/json",
|
|
14
|
+
text: '{"hello":"world"}',
|
|
15
|
+
},
|
|
16
|
+
},
|
|
17
|
+
],
|
|
18
|
+
};
|
|
19
|
+
describe("useDownload", () => {
|
|
20
|
+
describe("apps-sdk host", () => {
|
|
21
|
+
beforeEach(() => {
|
|
22
|
+
vi.stubGlobal("openai", {});
|
|
23
|
+
vi.stubGlobal("skybridge", {});
|
|
24
|
+
vi.stubGlobal("ResizeObserver", MockResizeObserver);
|
|
25
|
+
vi.stubGlobal("parent", { postMessage: getMcpAppHostPostMessageMock() });
|
|
26
|
+
});
|
|
27
|
+
afterEach(() => {
|
|
28
|
+
vi.unstubAllGlobals();
|
|
29
|
+
vi.resetAllMocks();
|
|
30
|
+
HostAdaptor.resetInstance();
|
|
31
|
+
McpAppBridge.resetInstance();
|
|
32
|
+
});
|
|
33
|
+
it("returns { isError: true } and logs an error", async () => {
|
|
34
|
+
const errorSpy = vi.spyOn(console, "error").mockImplementation(() => { });
|
|
35
|
+
const { result } = renderHook(() => useDownload());
|
|
36
|
+
const res = await result.current(params);
|
|
37
|
+
expect(res).toEqual({ isError: true });
|
|
38
|
+
expect(errorSpy).toHaveBeenCalledWith(expect.stringContaining("does not support ui/download-file"));
|
|
39
|
+
});
|
|
40
|
+
});
|
|
41
|
+
describe("mcp-app host without downloadFile capability", () => {
|
|
42
|
+
beforeEach(() => {
|
|
43
|
+
vi.stubGlobal("openai", undefined);
|
|
44
|
+
vi.stubGlobal("skybridge", {});
|
|
45
|
+
vi.stubGlobal("ResizeObserver", MockResizeObserver);
|
|
46
|
+
vi.stubGlobal("parent", { postMessage: getMcpAppHostPostMessageMock() });
|
|
47
|
+
});
|
|
48
|
+
afterEach(async () => {
|
|
49
|
+
vi.unstubAllGlobals();
|
|
50
|
+
vi.resetAllMocks();
|
|
51
|
+
HostAdaptor.resetInstance();
|
|
52
|
+
McpAppBridge.resetInstance();
|
|
53
|
+
});
|
|
54
|
+
it("returns { isError: true } and logs an error", async () => {
|
|
55
|
+
const errorSpy = vi.spyOn(console, "error").mockImplementation(() => { });
|
|
56
|
+
const { result } = renderHook(() => useDownload());
|
|
57
|
+
const res = await result.current(params);
|
|
58
|
+
expect(res).toEqual({ isError: true });
|
|
59
|
+
expect(errorSpy).toHaveBeenCalledWith(expect.stringContaining("does not support ui/download-file"));
|
|
60
|
+
});
|
|
61
|
+
});
|
|
62
|
+
describe("mcp-app host with downloadFile capability", () => {
|
|
63
|
+
let postMessageMock;
|
|
64
|
+
beforeEach(() => {
|
|
65
|
+
vi.stubGlobal("openai", undefined);
|
|
66
|
+
vi.stubGlobal("skybridge", {});
|
|
67
|
+
vi.stubGlobal("ResizeObserver", MockResizeObserver);
|
|
68
|
+
postMessageMock = getMcpAppHostPostMessageMock({}, { hostCapabilities: { downloadFile: {} } });
|
|
69
|
+
vi.stubGlobal("parent", { postMessage: postMessageMock });
|
|
70
|
+
});
|
|
71
|
+
afterEach(async () => {
|
|
72
|
+
vi.unstubAllGlobals();
|
|
73
|
+
vi.resetAllMocks();
|
|
74
|
+
HostAdaptor.resetInstance();
|
|
75
|
+
McpAppBridge.resetInstance();
|
|
76
|
+
});
|
|
77
|
+
it("sends ui/download-file with the provided contents", async () => {
|
|
78
|
+
const { result } = renderHook(() => useDownload());
|
|
79
|
+
const res = await result.current(params);
|
|
80
|
+
expect(res).toEqual({});
|
|
81
|
+
await waitFor(() => {
|
|
82
|
+
expect(postMessageMock).toHaveBeenCalledWith(expect.objectContaining({
|
|
83
|
+
jsonrpc: "2.0",
|
|
84
|
+
method: "ui/download-file",
|
|
85
|
+
params,
|
|
86
|
+
}), "*");
|
|
87
|
+
});
|
|
88
|
+
});
|
|
89
|
+
it("returns { isError: true } when the host denies the request", async () => {
|
|
90
|
+
HostAdaptor.resetInstance();
|
|
91
|
+
McpAppBridge.resetInstance();
|
|
92
|
+
postMessageMock = getMcpAppHostPostMessageMock({}, {
|
|
93
|
+
hostCapabilities: { downloadFile: {} },
|
|
94
|
+
downloadFileResult: { isError: true },
|
|
95
|
+
});
|
|
96
|
+
vi.stubGlobal("parent", { postMessage: postMessageMock });
|
|
97
|
+
const { result } = renderHook(() => useDownload());
|
|
98
|
+
const res = await result.current(params);
|
|
99
|
+
expect(res).toEqual({ isError: true });
|
|
100
|
+
});
|
|
101
|
+
});
|
|
102
|
+
});
|
|
103
|
+
//# sourceMappingURL=use-download.test.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"use-download.test.js","sourceRoot":"","sources":["../../../src/web/hooks/use-download.test.ts"],"names":[],"mappings":"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","sourcesContent":["import { renderHook, waitFor } from \"@testing-library/react\";\nimport { afterEach, beforeEach, describe, expect, it, vi } from \"vitest\";\nimport { HostAdaptor } from \"../bridges/adaptor.js\";\nimport { McpAppBridge } from \"../bridges/mcp-app/bridge.js\";\nimport type { DownloadParams } from \"../bridges/types.js\";\nimport {\n getMcpAppHostPostMessageMock,\n MockResizeObserver,\n} from \"./test/utils.js\";\nimport { useDownload } from \"./use-download.js\";\n\nconst params: DownloadParams = {\n contents: [\n {\n type: \"resource\",\n resource: {\n uri: \"file:///export.json\",\n mimeType: \"application/json\",\n text: '{\"hello\":\"world\"}',\n },\n },\n ],\n};\n\ndescribe(\"useDownload\", () => {\n describe(\"apps-sdk host\", () => {\n beforeEach(() => {\n vi.stubGlobal(\"openai\", {});\n vi.stubGlobal(\"skybridge\", {});\n vi.stubGlobal(\"ResizeObserver\", MockResizeObserver);\n vi.stubGlobal(\"parent\", { postMessage: getMcpAppHostPostMessageMock() });\n });\n\n afterEach(() => {\n vi.unstubAllGlobals();\n vi.resetAllMocks();\n HostAdaptor.resetInstance();\n McpAppBridge.resetInstance();\n });\n\n it(\"returns { isError: true } and logs an error\", async () => {\n const errorSpy = vi.spyOn(console, \"error\").mockImplementation(() => {});\n const { result } = renderHook(() => useDownload());\n\n const res = await result.current(params);\n\n expect(res).toEqual({ isError: true });\n expect(errorSpy).toHaveBeenCalledWith(\n expect.stringContaining(\"does not support ui/download-file\"),\n );\n });\n });\n\n describe(\"mcp-app host without downloadFile capability\", () => {\n beforeEach(() => {\n vi.stubGlobal(\"openai\", undefined);\n vi.stubGlobal(\"skybridge\", {});\n vi.stubGlobal(\"ResizeObserver\", MockResizeObserver);\n vi.stubGlobal(\"parent\", { postMessage: getMcpAppHostPostMessageMock() });\n });\n\n afterEach(async () => {\n vi.unstubAllGlobals();\n vi.resetAllMocks();\n HostAdaptor.resetInstance();\n McpAppBridge.resetInstance();\n });\n\n it(\"returns { isError: true } and logs an error\", async () => {\n const errorSpy = vi.spyOn(console, \"error\").mockImplementation(() => {});\n const { result } = renderHook(() => useDownload());\n\n const res = await result.current(params);\n\n expect(res).toEqual({ isError: true });\n expect(errorSpy).toHaveBeenCalledWith(\n expect.stringContaining(\"does not support ui/download-file\"),\n );\n });\n });\n\n describe(\"mcp-app host with downloadFile capability\", () => {\n let postMessageMock: ReturnType<typeof getMcpAppHostPostMessageMock>;\n\n beforeEach(() => {\n vi.stubGlobal(\"openai\", undefined);\n vi.stubGlobal(\"skybridge\", {});\n vi.stubGlobal(\"ResizeObserver\", MockResizeObserver);\n postMessageMock = getMcpAppHostPostMessageMock(\n {},\n { hostCapabilities: { downloadFile: {} } },\n );\n vi.stubGlobal(\"parent\", { postMessage: postMessageMock });\n });\n\n afterEach(async () => {\n vi.unstubAllGlobals();\n vi.resetAllMocks();\n HostAdaptor.resetInstance();\n McpAppBridge.resetInstance();\n });\n\n it(\"sends ui/download-file with the provided contents\", async () => {\n const { result } = renderHook(() => useDownload());\n\n const res = await result.current(params);\n\n expect(res).toEqual({});\n await waitFor(() => {\n expect(postMessageMock).toHaveBeenCalledWith(\n expect.objectContaining({\n jsonrpc: \"2.0\",\n method: \"ui/download-file\",\n params,\n }),\n \"*\",\n );\n });\n });\n\n it(\"returns { isError: true } when the host denies the request\", async () => {\n HostAdaptor.resetInstance();\n McpAppBridge.resetInstance();\n postMessageMock = getMcpAppHostPostMessageMock(\n {},\n {\n hostCapabilities: { downloadFile: {} },\n downloadFileResult: { isError: true },\n },\n );\n vi.stubGlobal(\"parent\", { postMessage: postMessageMock });\n\n const { result } = renderHook(() => useDownload());\n\n const res = await result.current(params);\n\n expect(res).toEqual({ isError: true });\n });\n });\n});\n"]}
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
import { type FileResource, type FileResourceContent, type FileResourceWriteResult, type ResourceRepresentation } from "../bridges/index.js";
|
|
2
|
+
/** State and actions returned by {@link useFileResource}. */
|
|
3
|
+
export type FileResourceState = {
|
|
4
|
+
data: FileResource | undefined;
|
|
5
|
+
isLoading: boolean;
|
|
6
|
+
error: unknown;
|
|
7
|
+
write: (content: FileResourceContent) => Promise<FileResourceWriteResult>;
|
|
8
|
+
};
|
|
9
|
+
/**
|
|
10
|
+
* Read a resource by URI, re-read it whenever the host reports a change, and
|
|
11
|
+
* write it back. Built for the file a ChatGPT file entrypoint opened, whose
|
|
12
|
+
* `resourceUri` arrives in the tool input as `file.resourceUri`.
|
|
13
|
+
*
|
|
14
|
+
* Reading uses the standard `resources/read`. `representation` asks ChatGPT
|
|
15
|
+
* for text or a base64 blob. `write` uses the OpenAI MCP extensions
|
|
16
|
+
* (`openai/resources/write`) and sends the current `etag` as `ifMatch`, so it
|
|
17
|
+
* resolves with `conflict` instead of overwriting a newer version, and then
|
|
18
|
+
* re-reads the latest one. When ChatGPT returned no `etag`, the write has no
|
|
19
|
+
* version check. It rejects with `NotSupportedError` when the host doesn't
|
|
20
|
+
* advertise `openai/resource`, or the resource isn't loaded or writable.
|
|
21
|
+
*
|
|
22
|
+
* Pass `undefined` or an empty string as `uri` to do nothing, for example outside a file
|
|
23
|
+
* entrypoint.
|
|
24
|
+
*
|
|
25
|
+
* @example
|
|
26
|
+
* ```tsx
|
|
27
|
+
* const { input } = useToolInfo<"open-part">();
|
|
28
|
+
* const { data, write } = useFileResource(input.file?.resourceUri, {
|
|
29
|
+
* representation: "text",
|
|
30
|
+
* });
|
|
31
|
+
* ```
|
|
32
|
+
*
|
|
33
|
+
* @see https://docs.skybridge.tech/api-reference/use-file-resource
|
|
34
|
+
*/
|
|
35
|
+
export declare function useFileResource(uri: string | undefined, options?: {
|
|
36
|
+
representation?: ResourceRepresentation;
|
|
37
|
+
}): FileResourceState;
|
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
import { useCallback, useEffect, useRef, useState } from "react";
|
|
2
|
+
import { getAdaptor, NotSupportedError, } from "../bridges/index.js";
|
|
3
|
+
/**
|
|
4
|
+
* Read a resource by URI, re-read it whenever the host reports a change, and
|
|
5
|
+
* write it back. Built for the file a ChatGPT file entrypoint opened, whose
|
|
6
|
+
* `resourceUri` arrives in the tool input as `file.resourceUri`.
|
|
7
|
+
*
|
|
8
|
+
* Reading uses the standard `resources/read`. `representation` asks ChatGPT
|
|
9
|
+
* for text or a base64 blob. `write` uses the OpenAI MCP extensions
|
|
10
|
+
* (`openai/resources/write`) and sends the current `etag` as `ifMatch`, so it
|
|
11
|
+
* resolves with `conflict` instead of overwriting a newer version, and then
|
|
12
|
+
* re-reads the latest one. When ChatGPT returned no `etag`, the write has no
|
|
13
|
+
* version check. It rejects with `NotSupportedError` when the host doesn't
|
|
14
|
+
* advertise `openai/resource`, or the resource isn't loaded or writable.
|
|
15
|
+
*
|
|
16
|
+
* Pass `undefined` or an empty string as `uri` to do nothing, for example outside a file
|
|
17
|
+
* entrypoint.
|
|
18
|
+
*
|
|
19
|
+
* @example
|
|
20
|
+
* ```tsx
|
|
21
|
+
* const { input } = useToolInfo<"open-part">();
|
|
22
|
+
* const { data, write } = useFileResource(input.file?.resourceUri, {
|
|
23
|
+
* representation: "text",
|
|
24
|
+
* });
|
|
25
|
+
* ```
|
|
26
|
+
*
|
|
27
|
+
* @see https://docs.skybridge.tech/api-reference/use-file-resource
|
|
28
|
+
*/
|
|
29
|
+
export function useFileResource(uri, options) {
|
|
30
|
+
const representation = options?.representation;
|
|
31
|
+
const [state, setState] = useState();
|
|
32
|
+
const sequence = useRef(0);
|
|
33
|
+
const current = uri && state?.uri === uri ? state : undefined;
|
|
34
|
+
const currentRef = useRef(current);
|
|
35
|
+
currentRef.current = current;
|
|
36
|
+
const read = useCallback(() => {
|
|
37
|
+
if (!uri) {
|
|
38
|
+
return;
|
|
39
|
+
}
|
|
40
|
+
const id = ++sequence.current;
|
|
41
|
+
getAdaptor()
|
|
42
|
+
.readResource(uri, representation)
|
|
43
|
+
.then((resource) => ({ resource, error: undefined }), (error) => ({ error }))
|
|
44
|
+
.then((next) => {
|
|
45
|
+
if (id === sequence.current) {
|
|
46
|
+
setState((previous) => ({
|
|
47
|
+
...(previous?.uri === uri && previous),
|
|
48
|
+
uri,
|
|
49
|
+
...next,
|
|
50
|
+
}));
|
|
51
|
+
}
|
|
52
|
+
});
|
|
53
|
+
}, [uri, representation]);
|
|
54
|
+
useEffect(() => {
|
|
55
|
+
if (!uri) {
|
|
56
|
+
return;
|
|
57
|
+
}
|
|
58
|
+
read();
|
|
59
|
+
const stopWatching = getAdaptor().watchResource(uri, read);
|
|
60
|
+
return () => {
|
|
61
|
+
sequence.current++;
|
|
62
|
+
stopWatching();
|
|
63
|
+
};
|
|
64
|
+
}, [uri, read]);
|
|
65
|
+
const write = useCallback(async (content) => {
|
|
66
|
+
const resource = currentRef.current?.resource;
|
|
67
|
+
if (!uri || currentRef.current?.uri !== uri || !resource?.writable) {
|
|
68
|
+
throw new NotSupportedError("writeResource", "the resource is not loaded or not writable");
|
|
69
|
+
}
|
|
70
|
+
const result = await getAdaptor().writeResource(uri, content, resource.etag);
|
|
71
|
+
if (currentRef.current?.uri !== uri) {
|
|
72
|
+
return result;
|
|
73
|
+
}
|
|
74
|
+
if (result.outcome === "saved") {
|
|
75
|
+
sequence.current++;
|
|
76
|
+
setState({
|
|
77
|
+
uri,
|
|
78
|
+
resource: {
|
|
79
|
+
...content,
|
|
80
|
+
mimeType: resource.mimeType,
|
|
81
|
+
writable: true,
|
|
82
|
+
etag: result.etag,
|
|
83
|
+
},
|
|
84
|
+
});
|
|
85
|
+
}
|
|
86
|
+
else if (result.outcome === "conflict") {
|
|
87
|
+
read();
|
|
88
|
+
}
|
|
89
|
+
return result;
|
|
90
|
+
}, [uri, read]);
|
|
91
|
+
return {
|
|
92
|
+
data: current?.resource,
|
|
93
|
+
isLoading: Boolean(uri) && current === undefined,
|
|
94
|
+
error: current?.error,
|
|
95
|
+
write,
|
|
96
|
+
};
|
|
97
|
+
}
|
|
98
|
+
//# sourceMappingURL=use-file-resource.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"use-file-resource.js","sourceRoot":"","sources":["../../../src/web/hooks/use-file-resource.ts"],"names":[],"mappings":"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","sourcesContent":["import { useCallback, useEffect, useRef, useState } from \"react\";\nimport {\n type FileResource,\n type FileResourceContent,\n type FileResourceWriteResult,\n getAdaptor,\n NotSupportedError,\n type ResourceRepresentation,\n} from \"../bridges/index.js\";\n\n/** State and actions returned by {@link useFileResource}. */\nexport type FileResourceState = {\n data: FileResource | undefined;\n isLoading: boolean;\n error: unknown;\n write: (content: FileResourceContent) => Promise<FileResourceWriteResult>;\n};\n\n/**\n * Read a resource by URI, re-read it whenever the host reports a change, and\n * write it back. Built for the file a ChatGPT file entrypoint opened, whose\n * `resourceUri` arrives in the tool input as `file.resourceUri`.\n *\n * Reading uses the standard `resources/read`. `representation` asks ChatGPT\n * for text or a base64 blob. `write` uses the OpenAI MCP extensions\n * (`openai/resources/write`) and sends the current `etag` as `ifMatch`, so it\n * resolves with `conflict` instead of overwriting a newer version, and then\n * re-reads the latest one. When ChatGPT returned no `etag`, the write has no\n * version check. It rejects with `NotSupportedError` when the host doesn't\n * advertise `openai/resource`, or the resource isn't loaded or writable.\n *\n * Pass `undefined` or an empty string as `uri` to do nothing, for example outside a file\n * entrypoint.\n *\n * @example\n * ```tsx\n * const { input } = useToolInfo<\"open-part\">();\n * const { data, write } = useFileResource(input.file?.resourceUri, {\n * representation: \"text\",\n * });\n * ```\n *\n * @see https://docs.skybridge.tech/api-reference/use-file-resource\n */\nexport function useFileResource(\n uri: string | undefined,\n options?: { representation?: ResourceRepresentation },\n): FileResourceState {\n const representation = options?.representation;\n const [state, setState] = useState<{\n uri: string;\n resource?: FileResource;\n error?: unknown;\n }>();\n const sequence = useRef(0);\n const current = uri && state?.uri === uri ? state : undefined;\n const currentRef = useRef(current);\n currentRef.current = current;\n\n const read = useCallback(() => {\n if (!uri) {\n return;\n }\n const id = ++sequence.current;\n getAdaptor()\n .readResource(uri, representation)\n .then(\n (resource) => ({ resource, error: undefined }),\n (error: unknown) => ({ error }),\n )\n .then((next) => {\n if (id === sequence.current) {\n setState((previous) => ({\n ...(previous?.uri === uri && previous),\n uri,\n ...next,\n }));\n }\n });\n }, [uri, representation]);\n\n useEffect(() => {\n if (!uri) {\n return;\n }\n read();\n const stopWatching = getAdaptor().watchResource(uri, read);\n return () => {\n sequence.current++;\n stopWatching();\n };\n }, [uri, read]);\n\n const write = useCallback(\n async (content: FileResourceContent) => {\n const resource = currentRef.current?.resource;\n if (!uri || currentRef.current?.uri !== uri || !resource?.writable) {\n throw new NotSupportedError(\n \"writeResource\",\n \"the resource is not loaded or not writable\",\n );\n }\n const result = await getAdaptor().writeResource(\n uri,\n content,\n resource.etag,\n );\n if (currentRef.current?.uri !== uri) {\n return result;\n }\n if (result.outcome === \"saved\") {\n sequence.current++;\n setState({\n uri,\n resource: {\n ...content,\n mimeType: resource.mimeType,\n writable: true,\n etag: result.etag,\n },\n });\n } else if (result.outcome === \"conflict\") {\n read();\n }\n return result;\n },\n [uri, read],\n );\n\n return {\n data: current?.resource,\n isLoading: Boolean(uri) && current === undefined,\n error: current?.error,\n write,\n };\n}\n"]}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
import { act, renderHook, waitFor } from "@testing-library/react";
|
|
2
|
+
import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
|
|
3
|
+
import { HostAdaptor } from "../bridges/adaptor.js";
|
|
4
|
+
import { getAdaptor } from "../bridges/get-adaptor.js";
|
|
5
|
+
import { McpAppBridge } from "../bridges/mcp-app/bridge.js";
|
|
6
|
+
import { useFileResource } from "./use-file-resource.js";
|
|
7
|
+
describe("useFileResource", () => {
|
|
8
|
+
beforeEach(() => {
|
|
9
|
+
vi.stubGlobal("parent", { postMessage: vi.fn() });
|
|
10
|
+
vi.stubGlobal("skybridge", {});
|
|
11
|
+
vi.stubGlobal("openai", undefined);
|
|
12
|
+
});
|
|
13
|
+
afterEach(() => {
|
|
14
|
+
vi.unstubAllGlobals();
|
|
15
|
+
HostAdaptor.resetInstance();
|
|
16
|
+
McpAppBridge.resetInstance();
|
|
17
|
+
});
|
|
18
|
+
it("reads the resource, re-reads it on update and writes with the current etag", async () => {
|
|
19
|
+
let version = 1;
|
|
20
|
+
let onUpdated = () => { };
|
|
21
|
+
const request = vi.fn(async ({ method }) => method === "openai/resources/write"
|
|
22
|
+
? { outcome: "saved", etag: "v3" }
|
|
23
|
+
: {});
|
|
24
|
+
const app = {
|
|
25
|
+
getHostCapabilities: () => ({ experimental: { "openai/resource": {} } }),
|
|
26
|
+
readServerResource: vi.fn(async ({ uri }) => ({
|
|
27
|
+
contents: [
|
|
28
|
+
{
|
|
29
|
+
uri,
|
|
30
|
+
text: `version ${version}`,
|
|
31
|
+
_meta: {
|
|
32
|
+
"openai/resource": { writable: true, etag: `v${version}` },
|
|
33
|
+
},
|
|
34
|
+
},
|
|
35
|
+
],
|
|
36
|
+
})),
|
|
37
|
+
setNotificationHandler: (_method, handler) => {
|
|
38
|
+
onUpdated = handler;
|
|
39
|
+
},
|
|
40
|
+
request,
|
|
41
|
+
};
|
|
42
|
+
getAdaptor().mcp.getApp = () => Promise.resolve(app);
|
|
43
|
+
const { result } = renderHook(() => useFileResource("host-resource://part", { representation: "text" }));
|
|
44
|
+
await waitFor(() => expect(result.current.data).toMatchObject({
|
|
45
|
+
text: "version 1",
|
|
46
|
+
etag: "v1",
|
|
47
|
+
}));
|
|
48
|
+
await waitFor(() => expect(request).toHaveBeenCalledWith({
|
|
49
|
+
method: "resources/subscribe",
|
|
50
|
+
params: { uri: "host-resource://part" },
|
|
51
|
+
}, expect.anything()));
|
|
52
|
+
version = 2;
|
|
53
|
+
act(() => onUpdated({ params: { uri: "host-resource://part" } }));
|
|
54
|
+
await waitFor(() => expect(result.current.data).toMatchObject({ etag: "v2" }));
|
|
55
|
+
await act(async () => {
|
|
56
|
+
await result.current.write({ text: "edited" });
|
|
57
|
+
});
|
|
58
|
+
expect(request).toHaveBeenCalledWith({
|
|
59
|
+
method: "openai/resources/write",
|
|
60
|
+
params: { uri: "host-resource://part", text: "edited", ifMatch: "v2" },
|
|
61
|
+
}, expect.anything());
|
|
62
|
+
expect(result.current.data).toMatchObject({ text: "edited", etag: "v3" });
|
|
63
|
+
});
|
|
64
|
+
});
|
|
65
|
+
//# sourceMappingURL=use-file-resource.test.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"use-file-resource.test.js","sourceRoot":"","sources":["../../../src/web/hooks/use-file-resource.test.ts"],"names":[],"mappings":"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","sourcesContent":["import { act, renderHook, waitFor } from \"@testing-library/react\";\nimport { afterEach, beforeEach, describe, expect, it, vi } from \"vitest\";\nimport { HostAdaptor } from \"../bridges/adaptor.js\";\nimport { getAdaptor } from \"../bridges/get-adaptor.js\";\nimport { McpAppBridge } from \"../bridges/mcp-app/bridge.js\";\nimport { useFileResource } from \"./use-file-resource.js\";\n\ndescribe(\"useFileResource\", () => {\n beforeEach(() => {\n vi.stubGlobal(\"parent\", { postMessage: vi.fn() });\n vi.stubGlobal(\"skybridge\", {});\n vi.stubGlobal(\"openai\", undefined);\n });\n\n afterEach(() => {\n vi.unstubAllGlobals();\n HostAdaptor.resetInstance();\n McpAppBridge.resetInstance();\n });\n\n it(\"reads the resource, re-reads it on update and writes with the current etag\", async () => {\n let version = 1;\n let onUpdated: (notification: { params: { uri: string } }) => void =\n () => {};\n const request = vi.fn(async ({ method }: { method: string }) =>\n method === \"openai/resources/write\"\n ? { outcome: \"saved\", etag: \"v3\" }\n : {},\n );\n const app = {\n getHostCapabilities: () => ({ experimental: { \"openai/resource\": {} } }),\n readServerResource: vi.fn(async ({ uri }: { uri: string }) => ({\n contents: [\n {\n uri,\n text: `version ${version}`,\n _meta: {\n \"openai/resource\": { writable: true, etag: `v${version}` },\n },\n },\n ],\n })),\n setNotificationHandler: (_method: string, handler: typeof onUpdated) => {\n onUpdated = handler;\n },\n request,\n };\n (\n getAdaptor() as unknown as { mcp: { getApp: () => Promise<object> } }\n ).mcp.getApp = () => Promise.resolve(app);\n\n const { result } = renderHook(() =>\n useFileResource(\"host-resource://part\", { representation: \"text\" }),\n );\n await waitFor(() =>\n expect(result.current.data).toMatchObject({\n text: \"version 1\",\n etag: \"v1\",\n }),\n );\n await waitFor(() =>\n expect(request).toHaveBeenCalledWith(\n {\n method: \"resources/subscribe\",\n params: { uri: \"host-resource://part\" },\n },\n expect.anything(),\n ),\n );\n\n version = 2;\n act(() => onUpdated({ params: { uri: \"host-resource://part\" } }));\n await waitFor(() =>\n expect(result.current.data).toMatchObject({ etag: \"v2\" }),\n );\n\n await act(async () => {\n await result.current.write({ text: \"edited\" });\n });\n expect(request).toHaveBeenCalledWith(\n {\n method: \"openai/resources/write\",\n params: { uri: \"host-resource://part\", text: \"edited\", ifMatch: \"v2\" },\n },\n expect.anything(),\n );\n expect(result.current.data).toMatchObject({ text: \"edited\", etag: \"v3\" });\n });\n});\n"]}
|
|
@@ -1,3 +1,35 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* File operations bound to the current host: `upload` a `File`, resolve a
|
|
3
|
+
* `downloadUrl` for an uploaded file, and `selectFiles` to open the host's
|
|
4
|
+
* native file picker.
|
|
5
|
+
*
|
|
6
|
+
* Currently Apps-SDK-only — calling any of these from MCP Apps throws.
|
|
7
|
+
* `selectFiles` additionally requires a ChatGPT host version that exposes the
|
|
8
|
+
* picker; it throws if the capability is unavailable.
|
|
9
|
+
*
|
|
10
|
+
* `upload` returns `FileMetadata` (`fileId`, optional `fileName`, `mimeType`).
|
|
11
|
+
* To pass an uploaded file to a tool whose input uses {@link FileRef}, first
|
|
12
|
+
* call `getDownloadUrl` and then build the ref yourself — field names differ
|
|
13
|
+
* (camelCase on the client, snake_case in the schema) and `download_url` is
|
|
14
|
+
* required.
|
|
15
|
+
*
|
|
16
|
+
* @example
|
|
17
|
+
* ```tsx
|
|
18
|
+
* const { upload, getDownloadUrl } = useFiles();
|
|
19
|
+
* const meta = await upload(file);
|
|
20
|
+
* const { downloadUrl } = await getDownloadUrl(meta);
|
|
21
|
+
* callTool({
|
|
22
|
+
* document: {
|
|
23
|
+
* file_id: meta.fileId,
|
|
24
|
+
* download_url: downloadUrl,
|
|
25
|
+
* file_name: meta.fileName,
|
|
26
|
+
* mime_type: meta.mimeType,
|
|
27
|
+
* },
|
|
28
|
+
* });
|
|
29
|
+
* ```
|
|
30
|
+
*
|
|
31
|
+
* @see https://docs.skybridge.tech/api-reference/use-files
|
|
32
|
+
*/
|
|
1
33
|
export declare function useFiles(): {
|
|
2
34
|
upload: (file: File, options?: import("../index.js").UploadFileOptions) => Promise<import("../index.js").FileMetadata>;
|
|
3
35
|
getDownloadUrl: (file: import("../index.js").FileMetadata) => Promise<{
|
|
@@ -1,4 +1,36 @@
|
|
|
1
1
|
import { getAdaptor } from "../bridges/index.js";
|
|
2
|
+
/**
|
|
3
|
+
* File operations bound to the current host: `upload` a `File`, resolve a
|
|
4
|
+
* `downloadUrl` for an uploaded file, and `selectFiles` to open the host's
|
|
5
|
+
* native file picker.
|
|
6
|
+
*
|
|
7
|
+
* Currently Apps-SDK-only — calling any of these from MCP Apps throws.
|
|
8
|
+
* `selectFiles` additionally requires a ChatGPT host version that exposes the
|
|
9
|
+
* picker; it throws if the capability is unavailable.
|
|
10
|
+
*
|
|
11
|
+
* `upload` returns `FileMetadata` (`fileId`, optional `fileName`, `mimeType`).
|
|
12
|
+
* To pass an uploaded file to a tool whose input uses {@link FileRef}, first
|
|
13
|
+
* call `getDownloadUrl` and then build the ref yourself — field names differ
|
|
14
|
+
* (camelCase on the client, snake_case in the schema) and `download_url` is
|
|
15
|
+
* required.
|
|
16
|
+
*
|
|
17
|
+
* @example
|
|
18
|
+
* ```tsx
|
|
19
|
+
* const { upload, getDownloadUrl } = useFiles();
|
|
20
|
+
* const meta = await upload(file);
|
|
21
|
+
* const { downloadUrl } = await getDownloadUrl(meta);
|
|
22
|
+
* callTool({
|
|
23
|
+
* document: {
|
|
24
|
+
* file_id: meta.fileId,
|
|
25
|
+
* download_url: downloadUrl,
|
|
26
|
+
* file_name: meta.fileName,
|
|
27
|
+
* mime_type: meta.mimeType,
|
|
28
|
+
* },
|
|
29
|
+
* });
|
|
30
|
+
* ```
|
|
31
|
+
*
|
|
32
|
+
* @see https://docs.skybridge.tech/api-reference/use-files
|
|
33
|
+
*/
|
|
2
34
|
export function useFiles() {
|
|
3
35
|
const adaptor = getAdaptor();
|
|
4
36
|
return {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"use-files.js","sourceRoot":"","sources":["../../../src/web/hooks/use-files.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,EAAE,MAAM,qBAAqB,CAAC;AAEjD,MAAM,UAAU,QAAQ;IACtB,MAAM,OAAO,GAAG,UAAU,EAAE,CAAC;IAC7B,OAAO;QACL,MAAM,EAAE,OAAO,CAAC,UAAU;QAC1B,cAAc,EAAE,OAAO,CAAC,kBAAkB;QAC1C,WAAW,EAAE,OAAO,CAAC,WAAW;KACjC,CAAC;AACJ,CAAC","sourcesContent":["import { getAdaptor } from \"../bridges/index.js\";\n\nexport function useFiles() {\n const adaptor = getAdaptor();\n return {\n upload: adaptor.uploadFile,\n getDownloadUrl: adaptor.getFileDownloadUrl,\n selectFiles: adaptor.selectFiles,\n };\n}\n"]}
|
|
1
|
+
{"version":3,"file":"use-files.js","sourceRoot":"","sources":["../../../src/web/hooks/use-files.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,EAAE,MAAM,qBAAqB,CAAC;AAEjD;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA+BG;AACH,MAAM,UAAU,QAAQ;IACtB,MAAM,OAAO,GAAG,UAAU,EAAE,CAAC;IAC7B,OAAO;QACL,MAAM,EAAE,OAAO,CAAC,UAAU;QAC1B,cAAc,EAAE,OAAO,CAAC,kBAAkB;QAC1C,WAAW,EAAE,OAAO,CAAC,WAAW;KACjC,CAAC;AACJ,CAAC","sourcesContent":["import { getAdaptor } from \"../bridges/index.js\";\n\n/**\n * File operations bound to the current host: `upload` a `File`, resolve a\n * `downloadUrl` for an uploaded file, and `selectFiles` to open the host's\n * native file picker.\n *\n * Currently Apps-SDK-only — calling any of these from MCP Apps throws.\n * `selectFiles` additionally requires a ChatGPT host version that exposes the\n * picker; it throws if the capability is unavailable.\n *\n * `upload` returns `FileMetadata` (`fileId`, optional `fileName`, `mimeType`).\n * To pass an uploaded file to a tool whose input uses {@link FileRef}, first\n * call `getDownloadUrl` and then build the ref yourself — field names differ\n * (camelCase on the client, snake_case in the schema) and `download_url` is\n * required.\n *\n * @example\n * ```tsx\n * const { upload, getDownloadUrl } = useFiles();\n * const meta = await upload(file);\n * const { downloadUrl } = await getDownloadUrl(meta);\n * callTool({\n * document: {\n * file_id: meta.fileId,\n * download_url: downloadUrl,\n * file_name: meta.fileName,\n * mime_type: meta.mimeType,\n * },\n * });\n * ```\n *\n * @see https://docs.skybridge.tech/api-reference/use-files\n */\nexport function useFiles() {\n const adaptor = getAdaptor();\n return {\n upload: adaptor.uploadFile,\n getDownloadUrl: adaptor.getFileDownloadUrl,\n selectFiles: adaptor.selectFiles,\n };\n}\n"]}
|
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
import { renderHook } from "@testing-library/react";
|
|
2
2
|
import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
|
|
3
|
-
import {
|
|
3
|
+
import { HostAdaptor } from "../bridges/adaptor.js";
|
|
4
|
+
import { AppsSdkBridge } from "../bridges/apps-sdk/bridge.js";
|
|
5
|
+
import { McpAppBridge } from "../bridges/mcp-app/bridge.js";
|
|
4
6
|
import { useFiles } from "./use-files.js";
|
|
5
7
|
describe("useFiles", () => {
|
|
6
8
|
const OpenaiMock = {
|
|
@@ -12,13 +14,19 @@ describe("useFiles", () => {
|
|
|
12
14
|
setWidgetState: vi.fn(),
|
|
13
15
|
};
|
|
14
16
|
beforeEach(() => {
|
|
15
|
-
|
|
17
|
+
HostAdaptor.resetInstance();
|
|
18
|
+
McpAppBridge.resetInstance();
|
|
19
|
+
AppsSdkBridge.resetInstance();
|
|
20
|
+
vi.stubGlobal("skybridge", {});
|
|
16
21
|
vi.stubGlobal("openai", OpenaiMock);
|
|
22
|
+
vi.stubGlobal("parent", { postMessage: vi.fn() });
|
|
17
23
|
});
|
|
18
24
|
afterEach(() => {
|
|
19
25
|
vi.unstubAllGlobals();
|
|
20
26
|
vi.clearAllMocks();
|
|
21
|
-
|
|
27
|
+
HostAdaptor.resetInstance();
|
|
28
|
+
McpAppBridge.resetInstance();
|
|
29
|
+
AppsSdkBridge.resetInstance();
|
|
22
30
|
});
|
|
23
31
|
const dummyFile = new File([], "test.txt");
|
|
24
32
|
it("should upload a file to ChatGPT", () => {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"use-files.test.js","sourceRoot":"","sources":["../../../src/web/hooks/use-files.test.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,EAAE,MAAM,wBAAwB,CAAC;AACpD,OAAO,EAAE,SAAS,EAAE,UAAU,EAAE,QAAQ,EAAE,MAAM,EAAE,EAAE,EAAE,EAAE,EAAE,MAAM,QAAQ,CAAC;AACzE,OAAO,EAAE,
|
|
1
|
+
{"version":3,"file":"use-files.test.js","sourceRoot":"","sources":["../../../src/web/hooks/use-files.test.ts"],"names":[],"mappings":"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","sourcesContent":["import { renderHook } from \"@testing-library/react\";\nimport { afterEach, beforeEach, describe, expect, it, vi } from \"vitest\";\nimport { HostAdaptor } from \"../bridges/adaptor.js\";\nimport { AppsSdkBridge } from \"../bridges/apps-sdk/bridge.js\";\nimport { McpAppBridge } from \"../bridges/mcp-app/bridge.js\";\nimport { useFiles } from \"./use-files.js\";\n\ndescribe(\"useFiles\", () => {\n const OpenaiMock: Record<string, unknown> = {\n uploadFile: vi.fn().mockResolvedValue({\n fileId: `sediment://file_abc123`,\n }),\n getFileDownloadUrl: vi.fn(),\n widgetState: null,\n setWidgetState: vi.fn(),\n };\n\n beforeEach(() => {\n HostAdaptor.resetInstance();\n McpAppBridge.resetInstance();\n AppsSdkBridge.resetInstance();\n vi.stubGlobal(\"skybridge\", {});\n vi.stubGlobal(\"openai\", OpenaiMock);\n vi.stubGlobal(\"parent\", { postMessage: vi.fn() });\n });\n\n afterEach(() => {\n vi.unstubAllGlobals();\n vi.clearAllMocks();\n HostAdaptor.resetInstance();\n McpAppBridge.resetInstance();\n AppsSdkBridge.resetInstance();\n });\n\n const dummyFile = new File([], \"test.txt\");\n\n it(\"should upload a file to ChatGPT\", () => {\n const { result } = renderHook(() => useFiles());\n\n result.current.upload(dummyFile);\n expect(OpenaiMock.uploadFile).toHaveBeenCalledWith(dummyFile, undefined);\n });\n\n it(\"should upload a file with library option\", () => {\n const { result } = renderHook(() => useFiles());\n\n result.current.upload(dummyFile, { library: true });\n expect(OpenaiMock.uploadFile).toHaveBeenCalledWith(dummyFile, {\n library: true,\n });\n });\n\n it(\"should select files from ChatGPT\", async () => {\n const selectedFiles = [\n { fileId: \"file_1\", fileName: \"doc.pdf\", mimeType: \"application/pdf\" },\n ];\n OpenaiMock.selectFiles = vi.fn().mockResolvedValue(selectedFiles);\n\n const { result } = renderHook(() => useFiles());\n\n const files = await result.current.selectFiles();\n expect(OpenaiMock.selectFiles).toHaveBeenCalled();\n expect(files).toEqual(selectedFiles);\n\n delete OpenaiMock.selectFiles;\n });\n\n it(\"should download a file from ChatGPT\", () => {\n const fileId = \"123\";\n const { result } = renderHook(() => useFiles());\n\n result.current.getDownloadUrl({ fileId });\n expect(OpenaiMock.getFileDownloadUrl).toHaveBeenCalledWith({ fileId });\n });\n});\n"]}
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
import type { McpUiHostCapabilities } from "@modelcontextprotocol/ext-apps";
|
|
2
|
+
/**
|
|
3
|
+
* Known host applications, as normalized slugs. Unrecognized hosts surface
|
|
4
|
+
* their raw `hostInfo.name` string instead.
|
|
5
|
+
*/
|
|
6
|
+
export type Host = "chatgpt" | "claude" | "cursor" | "goose" | "mistral-vibe" | "alpic";
|
|
7
|
+
/**
|
|
8
|
+
* OpenAI MCP extensions the host advertises. Each flag is `true` only when the
|
|
9
|
+
* host sends a truthy `hostCapabilities.experimental["openai/<name>"]`, so every flag is
|
|
10
|
+
* `false` before the handshake and on hosts other than ChatGPT.
|
|
11
|
+
*/
|
|
12
|
+
export type OpenAIHostCapabilities = {
|
|
13
|
+
/** Read, subscribe to and write the file opened through a file entrypoint. */
|
|
14
|
+
resource: boolean;
|
|
15
|
+
/** Titles, thumbnails and background content in `ui/update-model-context`. */
|
|
16
|
+
modelContext: boolean;
|
|
17
|
+
/** `ui/message` targets and titled items. */
|
|
18
|
+
message: boolean;
|
|
19
|
+
/** Open a local file with `openai/files/open`. */
|
|
20
|
+
files: boolean;
|
|
21
|
+
};
|
|
22
|
+
export type HostInfo = {
|
|
23
|
+
name: Host | (string & {}) | undefined;
|
|
24
|
+
version: string | undefined;
|
|
25
|
+
/** The host's MCP Apps capabilities, `undefined` until the handshake. */
|
|
26
|
+
capabilities: McpUiHostCapabilities | undefined;
|
|
27
|
+
/** OpenAI MCP extensions the host supports. */
|
|
28
|
+
openaiCapabilities: OpenAIHostCapabilities;
|
|
29
|
+
};
|
|
30
|
+
/**
|
|
31
|
+
* Identity and capabilities of the host application rendering the view, from
|
|
32
|
+
* the MCP Apps `ui/initialize` handshake. `name` is normalized to a
|
|
33
|
+
* {@link Host} slug when recognized, otherwise the raw string; `name`,
|
|
34
|
+
* `version` and `capabilities` are `undefined` until the handshake resolves
|
|
35
|
+
* (the view renders first and re-renders once it lands).
|
|
36
|
+
*
|
|
37
|
+
* @example
|
|
38
|
+
* ```tsx
|
|
39
|
+
* const { name, openaiCapabilities } = useHost();
|
|
40
|
+
* if (name === "claude") return <ClaudeLayout />;
|
|
41
|
+
* if (openaiCapabilities.files) return <OpenFileButton />;
|
|
42
|
+
* ```
|
|
43
|
+
*/
|
|
44
|
+
export declare function useHost(): HostInfo;
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
import { useMemo } from "react";
|
|
2
|
+
import { useMcpAppContext } from "../bridges/index.js";
|
|
3
|
+
const HOST_BY_REPORTED_NAME = {
|
|
4
|
+
chatgpt: "chatgpt",
|
|
5
|
+
Claude: "claude",
|
|
6
|
+
Cursor: "cursor",
|
|
7
|
+
"MCP-UI Host": "goose",
|
|
8
|
+
"Le Chat": "mistral-vibe",
|
|
9
|
+
"alpic-playground": "alpic",
|
|
10
|
+
};
|
|
11
|
+
/**
|
|
12
|
+
* Identity and capabilities of the host application rendering the view, from
|
|
13
|
+
* the MCP Apps `ui/initialize` handshake. `name` is normalized to a
|
|
14
|
+
* {@link Host} slug when recognized, otherwise the raw string; `name`,
|
|
15
|
+
* `version` and `capabilities` are `undefined` until the handshake resolves
|
|
16
|
+
* (the view renders first and re-renders once it lands).
|
|
17
|
+
*
|
|
18
|
+
* @example
|
|
19
|
+
* ```tsx
|
|
20
|
+
* const { name, openaiCapabilities } = useHost();
|
|
21
|
+
* if (name === "claude") return <ClaudeLayout />;
|
|
22
|
+
* if (openaiCapabilities.files) return <OpenFileButton />;
|
|
23
|
+
* ```
|
|
24
|
+
*/
|
|
25
|
+
export function useHost() {
|
|
26
|
+
const hostInfo = useMcpAppContext("hostInfo");
|
|
27
|
+
const capabilities = useMcpAppContext("hostCapabilities") ?? undefined;
|
|
28
|
+
const name = hostInfo?.name;
|
|
29
|
+
const openaiCapabilities = useMemo(() => {
|
|
30
|
+
const supports = (extension) => Boolean(capabilities?.experimental?.[`openai/${extension}`]);
|
|
31
|
+
return {
|
|
32
|
+
resource: supports("resource"),
|
|
33
|
+
modelContext: supports("modelContext"),
|
|
34
|
+
message: supports("message"),
|
|
35
|
+
files: supports("files"),
|
|
36
|
+
};
|
|
37
|
+
}, [capabilities]);
|
|
38
|
+
return {
|
|
39
|
+
name: name !== undefined ? (HOST_BY_REPORTED_NAME[name] ?? name) : undefined,
|
|
40
|
+
version: hostInfo?.version,
|
|
41
|
+
capabilities,
|
|
42
|
+
openaiCapabilities,
|
|
43
|
+
};
|
|
44
|
+
}
|
|
45
|
+
//# sourceMappingURL=use-host.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"use-host.js","sourceRoot":"","sources":["../../../src/web/hooks/use-host.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,OAAO,EAAE,MAAM,OAAO,CAAC;AAChC,OAAO,EAAE,gBAAgB,EAAE,MAAM,qBAAqB,CAAC;AAcvD,MAAM,qBAAqB,GAAyB;IAClD,OAAO,EAAE,SAAS;IAClB,MAAM,EAAE,QAAQ;IAChB,MAAM,EAAE,QAAQ;IAChB,aAAa,EAAE,OAAO;IACtB,SAAS,EAAE,cAAc;IACzB,kBAAkB,EAAE,OAAO;CAC5B,CAAC;AA2BF;;;;;;;;;;;;;GAaG;AACH,MAAM,UAAU,OAAO;IACrB,MAAM,QAAQ,GAAG,gBAAgB,CAAC,UAAU,CAAC,CAAC;IAC9C,MAAM,YAAY,GAAG,gBAAgB,CAAC,kBAAkB,CAAC,IAAI,SAAS,CAAC;IACvE,MAAM,IAAI,GAAG,QAAQ,EAAE,IAAI,CAAC;IAC5B,MAAM,kBAAkB,GAAG,OAAO,CAAC,GAAG,EAAE;QACtC,MAAM,QAAQ,GAAG,CAAC,SAAiB,EAAE,EAAE,CACrC,OAAO,CAAC,YAAY,EAAE,YAAY,EAAE,CAAC,UAAU,SAAS,EAAE,CAAC,CAAC,CAAC;QAC/D,OAAO;YACL,QAAQ,EAAE,QAAQ,CAAC,UAAU,CAAC;YAC9B,YAAY,EAAE,QAAQ,CAAC,cAAc,CAAC;YACtC,OAAO,EAAE,QAAQ,CAAC,SAAS,CAAC;YAC5B,KAAK,EAAE,QAAQ,CAAC,OAAO,CAAC;SACzB,CAAC;IACJ,CAAC,EAAE,CAAC,YAAY,CAAC,CAAC,CAAC;IAEnB,OAAO;QACL,IAAI,EACF,IAAI,KAAK,SAAS,CAAC,CAAC,CAAC,CAAC,qBAAqB,CAAC,IAAI,CAAC,IAAI,IAAI,CAAC,CAAC,CAAC,CAAC,SAAS;QACxE,OAAO,EAAE,QAAQ,EAAE,OAAO;QAC1B,YAAY;QACZ,kBAAkB;KACnB,CAAC;AACJ,CAAC","sourcesContent":["import type { McpUiHostCapabilities } from \"@modelcontextprotocol/ext-apps\";\nimport { useMemo } from \"react\";\nimport { useMcpAppContext } from \"../bridges/index.js\";\n\n/**\n * Known host applications, as normalized slugs. Unrecognized hosts surface\n * their raw `hostInfo.name` string instead.\n */\nexport type Host =\n | \"chatgpt\"\n | \"claude\"\n | \"cursor\"\n | \"goose\"\n | \"mistral-vibe\"\n | \"alpic\";\n\nconst HOST_BY_REPORTED_NAME: Record<string, Host> = {\n chatgpt: \"chatgpt\",\n Claude: \"claude\",\n Cursor: \"cursor\",\n \"MCP-UI Host\": \"goose\",\n \"Le Chat\": \"mistral-vibe\",\n \"alpic-playground\": \"alpic\",\n};\n\n/**\n * OpenAI MCP extensions the host advertises. Each flag is `true` only when the\n * host sends a truthy `hostCapabilities.experimental[\"openai/<name>\"]`, so every flag is\n * `false` before the handshake and on hosts other than ChatGPT.\n */\nexport type OpenAIHostCapabilities = {\n /** Read, subscribe to and write the file opened through a file entrypoint. */\n resource: boolean;\n /** Titles, thumbnails and background content in `ui/update-model-context`. */\n modelContext: boolean;\n /** `ui/message` targets and titled items. */\n message: boolean;\n /** Open a local file with `openai/files/open`. */\n files: boolean;\n};\n\nexport type HostInfo = {\n name: Host | (string & {}) | undefined;\n version: string | undefined;\n /** The host's MCP Apps capabilities, `undefined` until the handshake. */\n capabilities: McpUiHostCapabilities | undefined;\n /** OpenAI MCP extensions the host supports. */\n openaiCapabilities: OpenAIHostCapabilities;\n};\n\n/**\n * Identity and capabilities of the host application rendering the view, from\n * the MCP Apps `ui/initialize` handshake. `name` is normalized to a\n * {@link Host} slug when recognized, otherwise the raw string; `name`,\n * `version` and `capabilities` are `undefined` until the handshake resolves\n * (the view renders first and re-renders once it lands).\n *\n * @example\n * ```tsx\n * const { name, openaiCapabilities } = useHost();\n * if (name === \"claude\") return <ClaudeLayout />;\n * if (openaiCapabilities.files) return <OpenFileButton />;\n * ```\n */\nexport function useHost(): HostInfo {\n const hostInfo = useMcpAppContext(\"hostInfo\");\n const capabilities = useMcpAppContext(\"hostCapabilities\") ?? undefined;\n const name = hostInfo?.name;\n const openaiCapabilities = useMemo(() => {\n const supports = (extension: string) =>\n Boolean(capabilities?.experimental?.[`openai/${extension}`]);\n return {\n resource: supports(\"resource\"),\n modelContext: supports(\"modelContext\"),\n message: supports(\"message\"),\n files: supports(\"files\"),\n };\n }, [capabilities]);\n\n return {\n name:\n name !== undefined ? (HOST_BY_REPORTED_NAME[name] ?? name) : undefined,\n version: hostInfo?.version,\n capabilities,\n openaiCapabilities,\n };\n}\n"]}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|