@pyric/cli 0.1.0-alpha.11 → 0.1.0-alpha.13
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/dist/cli/serve.js +4 -4
- package/dist/cli/serve.js.map +1 -1
- package/dist/conformance/.generated/can-i-use-browser.d.ts.map +1 -1
- package/dist/conformance/.generated/can-i-use-browser.js +1 -1
- package/dist/conformance/.generated/can-i-use-browser.js.map +1 -1
- package/dist/conformance/.generated/can-i-use.d.ts.map +1 -1
- package/dist/conformance/.generated/can-i-use.js +2 -2
- package/dist/conformance/.generated/can-i-use.js.map +1 -1
- package/dist/serve/bundler.d.ts +13 -8
- package/dist/serve/bundler.d.ts.map +1 -1
- package/dist/serve/bundler.js +31 -36
- package/dist/serve/bundler.js.map +1 -1
- package/dist/serve/docs-ui/docs/_rhythm/index.html +1 -1
- package/dist/serve/docs-ui/docs/agent/set-up-your-agent/index.html +1 -1
- package/dist/serve/docs-ui/docs/agent/watch-and-review/index.html +1 -1
- package/dist/serve/docs-ui/docs/agent/work-with-an-agent/index.html +1 -1
- package/dist/serve/docs-ui/docs/ai-compat/index.html +1 -1
- package/dist/serve/docs-ui/docs/api-reference/index.html +2 -2
- package/dist/serve/docs-ui/docs/api-reference.md +1 -1
- package/dist/serve/docs-ui/docs/app-compat/index.html +1 -1
- package/dist/serve/docs-ui/docs/auth-compat/index.html +1 -1
- package/dist/serve/docs-ui/docs/build/ai-logic/index.html +44 -22
- package/dist/serve/docs-ui/docs/build/ai-logic.md +69 -21
- package/dist/serve/docs-ui/docs/build/authentication/index.html +1 -1
- package/dist/serve/docs-ui/docs/build/cloud-firestore/index.html +1 -1
- package/dist/serve/docs-ui/docs/build/cloud-messaging/index.html +1 -1
- package/dist/serve/docs-ui/docs/build/cloud-storage/index.html +1 -1
- package/dist/serve/docs-ui/docs/build/realtime-database/index.html +1 -1
- package/dist/serve/docs-ui/docs/conformance-scores/index.html +1 -1
- package/dist/serve/docs-ui/docs/create-pyric-reference-api/index.html +1 -1
- package/dist/serve/docs-ui/docs/database-compat/index.html +1 -1
- package/dist/serve/docs-ui/docs/firestore-compat/index.html +1 -1
- package/dist/serve/docs-ui/docs/functions-rtdb-compat/index.html +1 -1
- package/dist/serve/docs-ui/docs/get-started/how-the-swap-works/index.html +2 -1
- package/dist/serve/docs-ui/docs/get-started/how-the-swap-works.md +2 -0
- package/dist/serve/docs-ui/docs/get-started/start-building/index.html +3 -2
- package/dist/serve/docs-ui/docs/get-started/start-building.md +3 -1
- package/dist/serve/docs-ui/docs/index.json +349 -29
- package/dist/serve/docs-ui/docs/messaging-compat/index.html +1 -1
- package/dist/serve/docs-ui/docs/observe/resolve-runtime-status/index.html +28 -0
- package/dist/serve/docs-ui/docs/observe/resolve-runtime-status.md +47 -0
- package/dist/serve/docs-ui/docs/observe/see-whats-happening/index.html +1 -1
- package/dist/serve/docs-ui/docs/observe/shape-your-data/index.html +1 -1
- package/dist/serve/docs-ui/docs/overview/index.html +1 -1
- package/dist/serve/docs-ui/docs/pyric-admin-app-reference-api/index.html +1 -1
- package/dist/serve/docs-ui/docs/pyric-admin-auth-reference-api/index.html +1 -1
- package/dist/serve/docs-ui/docs/pyric-admin-database-reference-api/index.html +1 -1
- package/dist/serve/docs-ui/docs/pyric-admin-firestore-reference-api/index.html +1 -1
- package/dist/serve/docs-ui/docs/pyric-admin-messaging-reference-api/index.html +916 -0
- package/dist/serve/docs-ui/docs/pyric-admin-messaging-reference-api.md +706 -0
- package/dist/serve/docs-ui/docs/pyric-admin-storage-reference-api/index.html +1 -1
- package/dist/serve/docs-ui/docs/pyric-ai-reference-api/index.html +1 -1
- package/dist/serve/docs-ui/docs/pyric-ai-scripting-reference-api/index.html +1 -1
- package/dist/serve/docs-ui/docs/pyric-app-reference-api/index.html +1 -1
- package/dist/serve/docs-ui/docs/pyric-auth-reference-api/index.html +1 -1
- package/dist/serve/docs-ui/docs/pyric-cli-assurance-browser-reference-api/index.html +1 -1
- package/dist/serve/docs-ui/docs/pyric-cli-assurance-reference-api/index.html +1 -1
- package/dist/serve/docs-ui/docs/pyric-cli-bridge-client-reference-api/index.html +1 -1
- package/dist/serve/docs-ui/docs/pyric-cli-bridge-reference-api/index.html +1 -1
- package/dist/serve/docs-ui/docs/pyric-cli-conformance-browser-reference-api/index.html +1 -1
- package/dist/serve/docs-ui/docs/pyric-cli-conformance-docs-reference-api/index.html +1 -1
- package/dist/serve/docs-ui/docs/pyric-cli-conformance-reference-api/index.html +1 -1
- package/dist/serve/docs-ui/docs/pyric-cli-credentials-node-reference-api/index.html +1 -1
- package/dist/serve/docs-ui/docs/pyric-cli-discover-reference-api/index.html +1 -1
- package/dist/serve/docs-ui/docs/pyric-cli-register-reference-api/index.html +1 -1
- package/dist/serve/docs-ui/docs/pyric-cli-remote-reference-api/index.html +1 -1
- package/dist/serve/docs-ui/docs/pyric-cli-serve-worker-reference-api/index.html +168 -76
- package/dist/serve/docs-ui/docs/pyric-cli-serve-worker-reference-api.md +156 -73
- package/dist/serve/docs-ui/docs/pyric-cli-verify-reference-api/index.html +1 -1
- package/dist/serve/docs-ui/docs/pyric-cli-vite-reference-api/index.html +11 -8
- package/dist/serve/docs-ui/docs/pyric-cli-vite-reference-api.md +17 -4
- package/dist/serve/docs-ui/docs/pyric-database-reference-api/index.html +1 -1
- package/dist/serve/docs-ui/docs/pyric-firestore-reference-api/index.html +1 -1
- package/dist/serve/docs-ui/docs/pyric-messaging-reference-api/index.html +1 -1
- package/dist/serve/docs-ui/docs/pyric-messaging-sw-reference-api/index.html +1 -1
- package/dist/serve/docs-ui/docs/pyric-rules-reference-api/index.html +1 -1
- package/dist/serve/docs-ui/docs/pyric-sandbox-database-reference-api/index.html +1 -1
- package/dist/serve/docs-ui/docs/pyric-sandbox-firestore-reference-api/index.html +1 -1
- package/dist/serve/docs-ui/docs/pyric-sandbox-reference-api/index.html +37 -14
- package/dist/serve/docs-ui/docs/pyric-sandbox-reference-api.md +28 -8
- package/dist/serve/docs-ui/docs/pyric-storage-reference-api/index.html +1 -1
- package/dist/serve/docs-ui/docs/pyric-ui-agents-reference-api/index.html +1 -1
- package/dist/serve/docs-ui/docs/pyric-ui-auth-hooks-reference-api/index.html +1 -1
- package/dist/serve/docs-ui/docs/pyric-ui-auth-reference-api/index.html +1 -1
- package/dist/serve/docs-ui/docs/pyric-ui-events-hooks-reference-api/index.html +1 -1
- package/dist/serve/docs-ui/docs/pyric-ui-events-reference-api/index.html +1 -1
- package/dist/serve/docs-ui/docs/pyric-ui-firestore-hooks-reference-api/index.html +1 -1
- package/dist/serve/docs-ui/docs/pyric-ui-firestore-reference-api/index.html +1 -1
- package/dist/serve/docs-ui/docs/pyric-ui-primitives-reference-api/index.html +1 -1
- package/dist/serve/docs-ui/docs/pyric-ui-rtdb-reference-api/index.html +1 -1
- package/dist/serve/docs-ui/docs/pyric-ui-rules-hooks-reference-api/index.html +1 -1
- package/dist/serve/docs-ui/docs/pyric-ui-rules-reference-api/index.html +1 -1
- package/dist/serve/docs-ui/docs/pyric-ui-storage-hooks-reference-api/index.html +1 -1
- package/dist/serve/docs-ui/docs/pyric-ui-storage-reference-api/index.html +1 -1
- package/dist/serve/docs-ui/docs/pyric-ui-traffic-hooks-reference-api/index.html +1 -1
- package/dist/serve/docs-ui/docs/pyric-ui-traffic-reference-api/index.html +1 -1
- package/dist/serve/docs-ui/docs/rules-compat/index.html +1 -1
- package/dist/serve/docs-ui/docs/secure/audit-your-rules/index.html +1 -1
- package/dist/serve/docs-ui/docs/secure/firestore-rules-limits/index.html +1 -1
- package/dist/serve/docs-ui/docs/secure/read-a-denial/index.html +1 -1
- package/dist/serve/docs-ui/docs/secure/rtdb-rules-in-typescript/index.html +1 -1
- package/dist/serve/docs-ui/docs/secure/rules-standard-library/index.html +1 -1
- package/dist/serve/docs-ui/docs/secure/secure-it-with-rules/index.html +1 -1
- package/dist/serve/docs-ui/docs/secure/simulate-and-lint/index.html +1 -1
- package/dist/serve/docs-ui/docs/secure/write-a-rules-test-suite/index.html +1 -1
- package/dist/serve/docs-ui/docs/ship/ship-to-production/index.html +1 -1
- package/dist/serve/docs-ui/docs/ship/test-in-node/index.html +1 -1
- package/dist/serve/docs-ui/docs/storage-compat/index.html +1 -1
- package/dist/serve/docs-ui/docs/trust/how-we-know-it-matches-firebase/index.html +1 -1
- package/dist/serve/docs-ui/docs/trust/versioning-and-compatibility/index.html +1 -1
- package/dist/serve/docs-ui/docs/tutorial/index.html +429 -0
- package/dist/serve/docs-ui/docs/tutorial.md +701 -0
- package/dist/serve/docs-ui/llms.txt +3 -1
- package/dist/serve/entries/init.d.ts.map +1 -1
- package/dist/serve/entries/init.js +3 -0
- package/dist/serve/entries/init.js.map +1 -1
- package/dist/serve/entries/runtime.d.ts.map +1 -1
- package/dist/serve/entries/runtime.js +20 -5
- package/dist/serve/entries/runtime.js.map +1 -1
- package/dist/serve/entries/worker-runtime.d.ts +2 -2
- package/dist/serve/entries/worker-runtime.d.ts.map +1 -1
- package/dist/serve/entries/worker-runtime.js +45 -10
- package/dist/serve/entries/worker-runtime.js.map +1 -1
- package/dist/serve/runtime/chip-config.d.ts +19 -0
- package/dist/serve/runtime/chip-config.d.ts.map +1 -0
- package/dist/serve/runtime/chip-config.js +19 -0
- package/dist/serve/runtime/chip-config.js.map +1 -0
- package/dist/serve/runtime/chip-install.d.ts +10 -0
- package/dist/serve/runtime/chip-install.d.ts.map +1 -0
- package/dist/serve/runtime/chip-install.js +15 -0
- package/dist/serve/runtime/chip-install.js.map +1 -0
- package/dist/serve/runtime/chip.d.ts +17 -0
- package/dist/serve/runtime/chip.d.ts.map +1 -0
- package/dist/serve/runtime/chip.js +257 -0
- package/dist/serve/runtime/chip.js.map +1 -0
- package/dist/serve/runtime/manifest.d.ts +22 -0
- package/dist/serve/runtime/manifest.d.ts.map +1 -0
- package/dist/serve/runtime/manifest.js +20 -0
- package/dist/serve/runtime/manifest.js.map +1 -0
- package/dist/serve/runtime/status.d.ts +49 -0
- package/dist/serve/runtime/status.d.ts.map +1 -0
- package/dist/serve/runtime/status.js +164 -0
- package/dist/serve/runtime/status.js.map +1 -0
- package/dist/serve/runtime/worker-connection.d.ts +4 -0
- package/dist/serve/runtime/worker-connection.d.ts.map +1 -0
- package/dist/serve/runtime/worker-connection.js +11 -0
- package/dist/serve/runtime/worker-connection.js.map +1 -0
- package/dist/serve/runtime/worker-generation.d.ts +14 -0
- package/dist/serve/runtime/worker-generation.d.ts.map +1 -0
- package/dist/serve/runtime/worker-generation.js +56 -0
- package/dist/serve/runtime/worker-generation.js.map +1 -0
- package/dist/serve/runtime/worker-replacement.d.ts +18 -0
- package/dist/serve/runtime/worker-replacement.d.ts.map +1 -0
- package/dist/serve/runtime/worker-replacement.js +29 -0
- package/dist/serve/runtime/worker-replacement.js.map +1 -0
- package/dist/serve/standalone-assets.d.ts +2 -2
- package/dist/serve/standalone-assets.d.ts.map +1 -1
- package/dist/serve/standalone-assets.js +1 -1
- package/dist/serve/standalone-assets.js.map +1 -1
- package/dist/serve/studio-ui/assets/{RulesCodeEditor-CxVYcZXw.js → RulesCodeEditor-BDlH-5lp.js} +1 -1
- package/dist/serve/studio-ui/assets/{index-cpSfgQ7H.js → index-Bf9SSE8L.js} +5 -5
- package/dist/serve/studio-ui/assets/{seed-DeuFKi4J.js → seed-D_UjxfOU.js} +1 -1
- package/dist/serve/studio-ui/index.html +1 -1
- package/dist/serve/vite-ai-config.d.ts +25 -0
- package/dist/serve/vite-ai-config.d.ts.map +1 -0
- package/dist/serve/vite-ai-config.js +45 -0
- package/dist/serve/vite-ai-config.js.map +1 -0
- package/dist/serve/vite-plugin.d.ts +17 -36
- package/dist/serve/vite-plugin.d.ts.map +1 -1
- package/dist/serve/vite-plugin.js +38 -57
- package/dist/serve/vite-plugin.js.map +1 -1
- package/dist/serve/vite-rules-source.d.ts +8 -0
- package/dist/serve/vite-rules-source.d.ts.map +1 -0
- package/dist/serve/vite-rules-source.js +23 -0
- package/dist/serve/vite-rules-source.js.map +1 -0
- package/dist/serve/vite-worker-runtime.d.ts +20 -0
- package/dist/serve/vite-worker-runtime.d.ts.map +1 -0
- package/dist/serve/vite-worker-runtime.js +25 -0
- package/dist/serve/vite-worker-runtime.js.map +1 -0
- package/dist/serve/worker/client/connection.d.ts +7 -2
- package/dist/serve/worker/client/connection.d.ts.map +1 -1
- package/dist/serve/worker/client/connection.js +13 -16
- package/dist/serve/worker/client/connection.js.map +1 -1
- package/dist/serve/worker/client/core.d.ts +7 -0
- package/dist/serve/worker/client/core.d.ts.map +1 -1
- package/dist/serve/worker/client/core.js +28 -0
- package/dist/serve/worker/client/core.js.map +1 -1
- package/dist/serve/worker/client/runtime-control.d.ts +8 -0
- package/dist/serve/worker/client/runtime-control.d.ts.map +1 -0
- package/dist/serve/worker/client/runtime-control.js +11 -0
- package/dist/serve/worker/client/runtime-control.js.map +1 -0
- package/dist/serve/worker/entry.js +44 -2
- package/dist/serve/worker/entry.js.map +1 -1
- package/dist/serve/worker/host/core.js +1 -1
- package/dist/serve/worker/host/core.js.map +1 -1
- package/dist/serve/worker/host/firestore-writes.d.ts.map +1 -1
- package/dist/serve/worker/host/firestore-writes.js +3 -2
- package/dist/serve/worker/host/firestore-writes.js.map +1 -1
- package/dist/serve/worker/host/studio.js +1 -1
- package/dist/serve/worker/host/studio.js.map +1 -1
- package/dist/serve/worker/host-context.d.ts +1 -1
- package/dist/serve/worker/host-context.d.ts.map +1 -1
- package/dist/serve/worker/index.d.ts +2 -1
- package/dist/serve/worker/index.d.ts.map +1 -1
- package/dist/serve/worker/index.js +2 -1
- package/dist/serve/worker/index.js.map +1 -1
- package/dist/serve/worker/protocol.d.ts +15 -1
- package/dist/serve/worker/protocol.d.ts.map +1 -1
- package/dist/serve/worker/protocol.js +2 -2
- package/dist/serve/worker/protocol.js.map +1 -1
- package/dist/serve/worker/retirement.d.ts +21 -0
- package/dist/serve/worker/retirement.d.ts.map +1 -0
- package/dist/serve/worker/retirement.js +97 -0
- package/dist/serve/worker/retirement.js.map +1 -0
- package/dist/serve/worker/serve-init.d.ts.map +1 -1
- package/dist/serve/worker/serve-init.js +5 -6
- package/dist/serve/worker/serve-init.js.map +1 -1
- package/dist/vite.d.ts +1 -0
- package/dist/vite.d.ts.map +1 -1
- package/dist/vite.js.map +1 -1
- package/package.json +6 -4
- package/src/assurance/.generated/conformance-verdicts.ts +1090 -0
- package/src/assurance/attachment.ts +211 -0
- package/src/assurance/browser.ts +80 -0
- package/src/assurance/campaign.ts +496 -0
- package/src/assurance/capabilities.ts +446 -0
- package/src/assurance/cases.ts +43 -0
- package/src/assurance/index.ts +75 -0
- package/src/assurance/runner.ts +885 -0
- package/src/assurance/tool-names.ts +15 -0
- package/src/assurance/tools.ts +830 -0
- package/src/assurance/types.ts +352 -0
- package/src/assurance/validation.ts +434 -0
- package/src/bridge/client/bridge.ts +550 -0
- package/src/bridge/client/dispatch.ts +137 -0
- package/src/bridge/client.ts +45 -0
- package/src/bridge/protocol.ts +351 -0
- package/src/bridge/server/audit.ts +53 -0
- package/src/bridge/server/bridge.ts +597 -0
- package/src/bridge/server/headless.ts +160 -0
- package/src/bridge/server/json-schema-to-zod.ts +108 -0
- package/src/bridge/server/local-bridge.ts +66 -0
- package/src/bridge/server/logger.ts +50 -0
- package/src/bridge/server/mcp-contract.ts +93 -0
- package/src/bridge/server/mcp.ts +129 -0
- package/src/bridge/server/peer.ts +232 -0
- package/src/bridge/server/standalone.ts +347 -0
- package/src/bridge/server/tool-metadata.ts +99 -0
- package/src/bridge/server.ts +28 -0
- package/src/cli/can-i-use.ts +50 -0
- package/src/cli/cli.test.ts +493 -0
- package/src/cli/database-rules.ts +329 -0
- package/src/cli/dev-runner.ts +282 -0
- package/src/cli/firebase-json.ts +100 -0
- package/src/cli/firestore-indexes.ts +70 -0
- package/src/cli/index.ts +425 -0
- package/src/cli/init.ts +260 -0
- package/src/cli/mcp-proxy.ts +196 -0
- package/src/cli/parse-args.ts +80 -0
- package/src/cli/rules.ts +259 -0
- package/src/cli/scope.ts +67 -0
- package/src/cli/serve.ts +996 -0
- package/src/cli/service-commands.ts +73 -0
- package/src/cli/snapshot.ts +164 -0
- package/src/cli/storage-rules.ts +172 -0
- package/src/cli/verify.ts +416 -0
- package/src/conformance/.generated/can-i-use-browser.ts +113 -0
- package/src/conformance/.generated/can-i-use.ts +117 -0
- package/src/conformance/.generated/conformance-docs.ts +14 -0
- package/src/conformance/browser.ts +24 -0
- package/src/conformance/can-i-use-tool.ts +51 -0
- package/src/conformance/can-i-use.ts +18 -0
- package/src/conformance/docs.ts +8 -0
- package/src/conformance/index.ts +20 -0
- package/src/conformance/tools.ts +16 -0
- package/src/credentials/core/memoize-ttl.ts +142 -0
- package/src/credentials/core/types.ts +10 -0
- package/src/credentials/node/from-adc.ts +96 -0
- package/src/credentials/node/from-service-account.ts +133 -0
- package/src/credentials/node/index.ts +6 -0
- package/src/discover/concurrency.ts +127 -0
- package/src/discover/crawler-adapter.ts +142 -0
- package/src/discover/crawler.ts +1127 -0
- package/src/discover/credential-free.ts +27 -0
- package/src/discover/findCollectionGroup.ts +131 -0
- package/src/discover/firestore-source.ts +59 -0
- package/src/discover/index.ts +5 -0
- package/src/discover/merge.ts +523 -0
- package/src/discover/session.ts +402 -0
- package/src/discover/tools.ts +201 -0
- package/src/discover/types.ts +187 -0
- package/src/discover/wire.ts +324 -0
- package/src/functions-rtdb/child.ts +382 -0
- package/src/functions-rtdb/delivery.ts +7 -0
- package/src/functions-rtdb/discovery.ts +136 -0
- package/src/functions-rtdb/event.ts +64 -0
- package/src/functions-rtdb/execution.ts +111 -0
- package/src/functions-rtdb/in-memory-delivery.ts +40 -0
- package/src/functions-rtdb/project.ts +74 -0
- package/src/functions-rtdb/projection.ts +104 -0
- package/src/functions-rtdb/reference-pattern.ts +20 -0
- package/src/functions-rtdb/remote-delivery.ts +23 -0
- package/src/pkg-version.ts +49 -0
- package/src/register/esm-exports.ts +63 -0
- package/src/register/hooks.ts +30 -0
- package/src/register/index.ts +150 -0
- package/src/register/mapping.ts +33 -0
- package/src/remote/index.ts +1050 -0
- package/src/rtdb/crawl-snapshot.ts +118 -0
- package/src/rtdb/inspection.ts +137 -0
- package/src/rtdb/load-rules-document.ts +56 -0
- package/src/rtdb/rules-generation-tool.ts +38 -0
- package/src/rtdb/rules-json.ts +29 -0
- package/src/serve/activity-guard.ts +29 -0
- package/src/serve/activity-route.ts +164 -0
- package/src/serve/activity-warning.ts +26 -0
- package/src/serve/bridge-mount.ts +200 -0
- package/src/serve/bundler.ts +519 -0
- package/src/serve/capture-store.ts +65 -0
- package/src/serve/discovery.ts +199 -0
- package/src/serve/entries/ai.ts +292 -0
- package/src/serve/entries/app-backend.ts +4 -0
- package/src/serve/entries/app-client.ts +26 -0
- package/src/serve/entries/app-session-store.ts +47 -0
- package/src/serve/entries/app.ts +11 -0
- package/src/serve/entries/auth-helper-core.ts +181 -0
- package/src/serve/entries/auth-helper-dom.ts +134 -0
- package/src/serve/entries/auth-helper-runtime.ts +20 -0
- package/src/serve/entries/auth.ts +255 -0
- package/src/serve/entries/bridge-url.ts +31 -0
- package/src/serve/entries/database.ts +129 -0
- package/src/serve/entries/firestore.ts +232 -0
- package/src/serve/entries/init.ts +49 -0
- package/src/serve/entries/keepalive.ts +46 -0
- package/src/serve/entries/messaging-sw.ts +75 -0
- package/src/serve/entries/messaging.ts +96 -0
- package/src/serve/entries/runtime.ts +556 -0
- package/src/serve/entries/session-store.ts +97 -0
- package/src/serve/entries/storage.ts +92 -0
- package/src/serve/entries/tab-sync-wiring.ts +274 -0
- package/src/serve/entries/worker-runtime.ts +135 -0
- package/src/serve/init-payload.ts +42 -0
- package/src/serve/namespace.ts +675 -0
- package/src/serve/open-browser.ts +68 -0
- package/src/serve/rules.ts +272 -0
- package/src/serve/runtime/chip-config.ts +30 -0
- package/src/serve/runtime/chip-install.ts +27 -0
- package/src/serve/runtime/chip.ts +282 -0
- package/src/serve/runtime/manifest.ts +37 -0
- package/src/serve/runtime/status.ts +216 -0
- package/src/serve/runtime/worker-connection.ts +14 -0
- package/src/serve/runtime/worker-generation.ts +75 -0
- package/src/serve/runtime/worker-replacement.ts +42 -0
- package/src/serve/sandbox-marker.ts +31 -0
- package/src/serve/server.ts +428 -0
- package/src/serve/standalone-assets.ts +180 -0
- package/src/serve/state-store.ts +166 -0
- package/src/serve/studio/disk-project-store.ts +185 -0
- package/src/serve/studio/disk-workspace.ts +162 -0
- package/src/serve/studio/index.ts +20 -0
- package/src/serve/studio/routes.ts +237 -0
- package/src/serve/studio/store-types.ts +48 -0
- package/src/serve/studio/studio-storage.test.ts +289 -0
- package/src/serve/vite-ai-config.ts +77 -0
- package/src/serve/vite-plugin.ts +1075 -0
- package/src/serve/vite-rules-source.ts +28 -0
- package/src/serve/vite-worker-runtime.ts +51 -0
- package/src/serve/worker/activity-bootstrap.ts +24 -0
- package/src/serve/worker/client/admin-firestore.ts +38 -0
- package/src/serve/worker/client/ai.ts +124 -0
- package/src/serve/worker/client/auth.ts +489 -0
- package/src/serve/worker/client/connection.ts +227 -0
- package/src/serve/worker/client/core.ts +384 -0
- package/src/serve/worker/client/disconnect.ts +45 -0
- package/src/serve/worker/client/firestore-reads.ts +181 -0
- package/src/serve/worker/client/firestore-refs.ts +226 -0
- package/src/serve/worker/client/firestore-writes.ts +232 -0
- package/src/serve/worker/client/handles.ts +91 -0
- package/src/serve/worker/client/messaging.ts +119 -0
- package/src/serve/worker/client/presence.ts +174 -0
- package/src/serve/worker/client/rtdb.ts +311 -0
- package/src/serve/worker/client/rules.ts +63 -0
- package/src/serve/worker/client/runtime-control.ts +22 -0
- package/src/serve/worker/client/service-worker-connection.ts +59 -0
- package/src/serve/worker/client/snapshots.ts +86 -0
- package/src/serve/worker/client/storage.ts +195 -0
- package/src/serve/worker/client/studio.ts +88 -0
- package/src/serve/worker/client.ts +57 -0
- package/src/serve/worker/durable-persistence.ts +137 -0
- package/src/serve/worker/entry.ts +223 -0
- package/src/serve/worker/host/admin-firestore.ts +88 -0
- package/src/serve/worker/host/connection.ts +179 -0
- package/src/serve/worker/host/core.ts +387 -0
- package/src/serve/worker/host/dispatch.ts +316 -0
- package/src/serve/worker/host/firestore-reads.ts +117 -0
- package/src/serve/worker/host/firestore-writes.ts +456 -0
- package/src/serve/worker/host/presence.ts +312 -0
- package/src/serve/worker/host/rtdb.ts +136 -0
- package/src/serve/worker/host/rules.ts +128 -0
- package/src/serve/worker/host/storage.ts +312 -0
- package/src/serve/worker/host/studio.ts +75 -0
- package/src/serve/worker/host/subscriptions.ts +238 -0
- package/src/serve/worker/host-ai.ts +165 -0
- package/src/serve/worker/host-auth.ts +469 -0
- package/src/serve/worker/host-context.ts +273 -0
- package/src/serve/worker/host-events.ts +94 -0
- package/src/serve/worker/host-messaging.ts +240 -0
- package/src/serve/worker/host.ts +47 -0
- package/src/serve/worker/index.ts +196 -0
- package/src/serve/worker/presence-timing.ts +15 -0
- package/src/serve/worker/protocol.ts +1124 -0
- package/src/serve/worker/retirement.ts +125 -0
- package/src/serve/worker/serve-init.ts +605 -0
- package/src/serve/worker/service-worker-channel.ts +33 -0
- package/src/serve/worker/service-worker-relay.ts +86 -0
- package/src/serve/writer-lock.ts +53 -0
- package/src/verify/cases.ts +233 -0
- package/src/verify/fixture.ts +258 -0
- package/src/verify/index.ts +519 -0
- package/src/verify/tools.ts +108 -0
- package/src/version/compat-target.ts +17 -0
- package/src/vite.ts +18 -0
- package/dist/serve/docs-ui/docs/pyric-firestore-values-reference-api/index.html +0 -31
- package/dist/serve/docs-ui/docs/pyric-firestore-values-reference-api.md +0 -29
|
@@ -0,0 +1,199 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Discovery of a RUNNING `pyric dev --bridge` — shared by the stdio MCP
|
|
3
|
+
* proxy (`cli/mcp-proxy.ts`) and the Node remote-sandbox client
|
|
4
|
+
* (`remote/index.ts`). Extracted here so both speak the SAME pointer +
|
|
5
|
+
* identity-pinning rules instead of drifting copies.
|
|
6
|
+
*
|
|
7
|
+
* Strategy: the `.pyric/serve.json` pointer serve writes in the project cwd
|
|
8
|
+
* (exact + project-correct) first, then a health probe across the scan
|
|
9
|
+
* window as a fallback. Degrades LEGIBLY: if no serve is found, or the
|
|
10
|
+
* pointed server's identity can't be matched, callers get `null` (plus a
|
|
11
|
+
* `log` diagnostic) — never a silent wrong-server hit.
|
|
12
|
+
*
|
|
13
|
+
* IDENTITY — the discovery pointer records the bridge's `instanceId`; a
|
|
14
|
+
* server is accepted only if its `/__pyric/health` reports the SAME id. Two
|
|
15
|
+
* sandboxes can collide on one port across loopback families (IPv4 `*:P` +
|
|
16
|
+
* `[::1]:P`); without this, a client locks onto whichever family answers
|
|
17
|
+
* first while the browser is on the other — split-brain.
|
|
18
|
+
*/
|
|
19
|
+
import { existsSync, readFileSync } from 'node:fs';
|
|
20
|
+
import { join } from 'node:path';
|
|
21
|
+
|
|
22
|
+
/** Ports probed when the pointer is absent — serve's default scan window PLUS
|
|
23
|
+
* the vite-plugin default (5174), which the plain 3473+ window would miss (so a
|
|
24
|
+
* vite-only project was never found by scan). The standalone `pyric bridge`
|
|
25
|
+
* serves `/health` (not `/__pyric/health`) and writes no pointer, so it is
|
|
26
|
+
* registered directly via `claude mcp add`, not discovered here. */
|
|
27
|
+
export const SCAN_PORTS = [3473, 3474, 3475, 3476, 3477, 5174];
|
|
28
|
+
export const POINTER = join('.pyric', 'serve.json');
|
|
29
|
+
|
|
30
|
+
export interface HealthLite {
|
|
31
|
+
mode?: string;
|
|
32
|
+
instanceId?: string;
|
|
33
|
+
/** Whether a browser tab is currently connected as the sandbox peer. */
|
|
34
|
+
sandboxConnected?: boolean;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
export interface Discovered {
|
|
38
|
+
mcpUrl: string;
|
|
39
|
+
/**
|
|
40
|
+
* The CANONICAL display URL — what a human/browser should OPEN. Comes from
|
|
41
|
+
* the serve-written pointer when present (`http://localhost:<port>` for the
|
|
42
|
+
* default host, the explicit `--host` otherwise); falls back to
|
|
43
|
+
* `http://localhost:<port>`. NEVER a literal loopback address: to a browser
|
|
44
|
+
* `localhost` and `127.0.0.1` are DIFFERENT ORIGINS (different
|
|
45
|
+
* SharedWorkers, so different sandboxes), and serve's banner/auto-open use
|
|
46
|
+
* `localhost` — guidance built from this field must land on the SAME origin.
|
|
47
|
+
* Node-side connectivity uses `base`/`mcpUrl` instead.
|
|
48
|
+
*/
|
|
49
|
+
url: string;
|
|
50
|
+
/** `http://<family>:<port>` the server actually answered on. */
|
|
51
|
+
base: string;
|
|
52
|
+
/** Identity pinned at discovery. Null only when talking to an older server
|
|
53
|
+
* that predates the instanceId field (matching is then skipped). */
|
|
54
|
+
instanceId: string | null;
|
|
55
|
+
source: string;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
/**
|
|
59
|
+
* Probe BOTH loopback families on a port and return the base that answers.
|
|
60
|
+
*
|
|
61
|
+
* Hostname-based URLs are a trap here: serve writes `http://localhost:...`
|
|
62
|
+
* for humans (browsers dual-stack fine), but `localhost` resolution differs
|
|
63
|
+
* by runtime — node/undici prefers IPv6 `::1`, and a serve under one runtime
|
|
64
|
+
* may bind `127.0.0.1`-only while under another binds `::1`-only. So a
|
|
65
|
+
* discovery client never trusts the hostname: it takes the PORT and tries
|
|
66
|
+
* explicit `127.0.0.1` and `[::1]`, using whichever the server is actually on.
|
|
67
|
+
*/
|
|
68
|
+
export function basesForPort(port: number): string[] {
|
|
69
|
+
return [`http://127.0.0.1:${port}`, `http://[::1]:${port}`];
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
export async function probeHealth(base: string): Promise<HealthLite | null> {
|
|
73
|
+
try {
|
|
74
|
+
const res = await fetch(`${base}/__pyric/health`, { signal: AbortSignal.timeout(1000) });
|
|
75
|
+
if (res.status !== 200) return null;
|
|
76
|
+
const body = (await res.json()) as HealthLite;
|
|
77
|
+
return body.mode === 'sandbox' ? body : null;
|
|
78
|
+
} catch {
|
|
79
|
+
return null;
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
/**
|
|
84
|
+
* First loopback base on `port` whose health reports a sandbox bridge. With an
|
|
85
|
+
* `expectedInstanceId`, returns ONLY a family whose health identity matches —
|
|
86
|
+
* so when two sandboxes collide on one port across families, the client locks
|
|
87
|
+
* onto the one the pointer names, not merely the first to answer. (An older
|
|
88
|
+
* server with no `instanceId` field can't be identity-checked; matching is
|
|
89
|
+
* skipped in that case so the pointer still resolves.)
|
|
90
|
+
*/
|
|
91
|
+
export async function healthyBase(
|
|
92
|
+
port: number,
|
|
93
|
+
expectedInstanceId?: string | null,
|
|
94
|
+
): Promise<{ base: string; instanceId: string | null } | null> {
|
|
95
|
+
for (const base of basesForPort(port)) {
|
|
96
|
+
const health = await probeHealth(base);
|
|
97
|
+
if (!health) continue;
|
|
98
|
+
const id = health.instanceId ?? null;
|
|
99
|
+
// Healthy but the WRONG server (the cross-family squatter): skip, try next.
|
|
100
|
+
if (expectedInstanceId && id !== expectedInstanceId) continue;
|
|
101
|
+
return { base, instanceId: id };
|
|
102
|
+
}
|
|
103
|
+
return null;
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
/** Best-effort port extraction from a pointer url/mcpUrl. */
|
|
107
|
+
function portOf(u: string | undefined): number | null {
|
|
108
|
+
const m = u?.match(/:(\d{2,5})(?:\/|$)/);
|
|
109
|
+
return m ? Number(m[1]) : null;
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
/**
|
|
113
|
+
* Canonical display URL for a serve on `port`. Prefers the pointer's own
|
|
114
|
+
* `url` (serve writes `http://<requested host>:<port>` — `localhost` for the
|
|
115
|
+
* default, the explicit `--host` otherwise) so guidance shares the origin the
|
|
116
|
+
* banner/auto-open used; falls back to `http://localhost:<port>` (browsers
|
|
117
|
+
* resolve `localhost` dual-stack, so it reaches either loopback family).
|
|
118
|
+
*/
|
|
119
|
+
export function canonicalServeUrl(port: number, pointerUrl?: string): string {
|
|
120
|
+
if (pointerUrl) {
|
|
121
|
+
try {
|
|
122
|
+
const u = new URL(pointerUrl);
|
|
123
|
+
if (u.protocol === 'http:' || u.protocol === 'https:') {
|
|
124
|
+
return `${u.protocol}//${u.host}`;
|
|
125
|
+
}
|
|
126
|
+
} catch {
|
|
127
|
+
/* malformed pointer url — fall through to the localhost default */
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
return `http://localhost:${port}`;
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
/** Find the running serve: pointer first (in `cwd`), then a port scan. The
|
|
134
|
+
* pointer gives the PORT and (when present) the identity; the family is
|
|
135
|
+
* resolved by probing, so the returned base always uses the address the
|
|
136
|
+
* server is actually reachable on. */
|
|
137
|
+
export async function discoverServe(
|
|
138
|
+
cwd: string,
|
|
139
|
+
log: (m: string) => void = () => {},
|
|
140
|
+
// Injectable so discovery can be tested hermetically — the default scan probes
|
|
141
|
+
// real localhost ports, which a test environment can't guarantee are free.
|
|
142
|
+
scanPorts: number[] = SCAN_PORTS,
|
|
143
|
+
): Promise<Discovered | null> {
|
|
144
|
+
const pointerPath = join(cwd, POINTER);
|
|
145
|
+
if (existsSync(pointerPath)) {
|
|
146
|
+
try {
|
|
147
|
+
const p = JSON.parse(readFileSync(pointerPath, 'utf8')) as {
|
|
148
|
+
url?: string;
|
|
149
|
+
mcpUrl?: string;
|
|
150
|
+
port?: number;
|
|
151
|
+
instanceId?: string;
|
|
152
|
+
};
|
|
153
|
+
const port = p.port ?? portOf(p.mcpUrl) ?? portOf(p.url);
|
|
154
|
+
if (port) {
|
|
155
|
+
const expectedId = typeof p.instanceId === 'string' && p.instanceId ? p.instanceId : null;
|
|
156
|
+
const hit = await healthyBase(port, expectedId);
|
|
157
|
+
if (hit) {
|
|
158
|
+
return {
|
|
159
|
+
mcpUrl: `${hit.base}/__pyric/mcp`,
|
|
160
|
+
url: canonicalServeUrl(port, p.url),
|
|
161
|
+
base: hit.base,
|
|
162
|
+
instanceId: hit.instanceId,
|
|
163
|
+
source: `pointer ${POINTER}`,
|
|
164
|
+
};
|
|
165
|
+
}
|
|
166
|
+
// The pointer named a specific identity we could NOT find on its port:
|
|
167
|
+
// a different sandbox may be squatting it (cross-family collision) or
|
|
168
|
+
// the server stopped. Do NOT scan into a possibly-wrong server — that
|
|
169
|
+
// split-brain is exactly what this identity check prevents. Fail legibly.
|
|
170
|
+
if (expectedId) {
|
|
171
|
+
log(
|
|
172
|
+
`pointer ${POINTER} names a server (instanceId ${expectedId.slice(0, 8)}…) ` +
|
|
173
|
+
`that isn't answering on port ${port} — another sandbox may be squatting the ` +
|
|
174
|
+
`port on the other loopback family, or the server stopped. Not falling back to ` +
|
|
175
|
+
`a blind port scan (it could hit the wrong sandbox). Restart your dev server, ` +
|
|
176
|
+
`and open the exact URL it prints (http://localhost:<port> by default) — every ` +
|
|
177
|
+
`page must share that ONE origin, or the browser splits into separate sandboxes.`,
|
|
178
|
+
);
|
|
179
|
+
return null;
|
|
180
|
+
}
|
|
181
|
+
}
|
|
182
|
+
} catch {
|
|
183
|
+
/* stale/corrupt pointer — fall through to scan */
|
|
184
|
+
}
|
|
185
|
+
}
|
|
186
|
+
for (const port of scanPorts) {
|
|
187
|
+
const hit = await healthyBase(port);
|
|
188
|
+
if (hit) {
|
|
189
|
+
return {
|
|
190
|
+
mcpUrl: `${hit.base}/__pyric/mcp`,
|
|
191
|
+
url: canonicalServeUrl(port),
|
|
192
|
+
base: hit.base,
|
|
193
|
+
instanceId: hit.instanceId,
|
|
194
|
+
source: `port scan (:${port})`,
|
|
195
|
+
};
|
|
196
|
+
}
|
|
197
|
+
}
|
|
198
|
+
return null;
|
|
199
|
+
}
|
|
@@ -0,0 +1,292 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The bundle the import map serves for `firebase/ai`.
|
|
3
|
+
*
|
|
4
|
+
* DUAL-PATH (cdd-deltas #98): the FULL `pyric/ai` mirror surface
|
|
5
|
+
* (GenerativeModel / ChatSession / Schema / enums) always runs in-page —
|
|
6
|
+
* but ANSWERING follows the sandbox. When a SharedWorker is available, the
|
|
7
|
+
* broker + engines live in the WORKER HOST, in-process with the ONE shared
|
|
8
|
+
* sandbox (#98.1): `getAI` plugs a port-forwarding AnswerEngine into the
|
|
9
|
+
* in-page mirror, so every `generateContent` / `streamGenerateContent` /
|
|
10
|
+
* `countTokens` rides the worker protocol's `ai.*` ops (#98.3) and the
|
|
11
|
+
* worker broker's `service: 'ai'` events land on the shared event stream
|
|
12
|
+
* Studio consumes. Otherwise the mirror runs whole against the in-page
|
|
13
|
+
* sandbox — the unchanged fallback. Branch picked ONCE at load (`useWorker`).
|
|
14
|
+
*
|
|
15
|
+
* OPENAI ENGINE IN THE BROWSER (#98.2): an `engine: { kind: 'openai' }`
|
|
16
|
+
* config with no `baseUrl` targets serve's same-origin `/__pyric/ai-proxy`
|
|
17
|
+
* route (which forwards to the configured upstream, default
|
|
18
|
+
* `http://localhost:11434/v1`) — so a localhost Ollama needs no CORS setup
|
|
19
|
+
* on EITHER path. A custom AnswerEngine OBJECT can't cross the port and is
|
|
20
|
+
* rejected in worker mode rather than creating a second page-local backend.
|
|
21
|
+
* Direct/in-process `pyric/ai` sandboxes still accept custom engines.
|
|
22
|
+
*
|
|
23
|
+
* Engine config is per-sandbox (#98.4) and first-call-wins at the worker
|
|
24
|
+
* host. Each app has its own in-page port wrapper so deleting one app cannot
|
|
25
|
+
* disconnect a sibling; all wrappers still forward to that one worker broker.
|
|
26
|
+
* Browser-bundled by `../bundler.ts`; never imported by node-side.
|
|
27
|
+
*/
|
|
28
|
+
import * as ipAi from 'pyric/ai';
|
|
29
|
+
import { aiErrorFromEnvelope } from 'pyric/ai/internal';
|
|
30
|
+
import { getAI as pyricGetAI } from 'pyric/ai';
|
|
31
|
+
import { createTransportAI } from 'pyric/ai/internal';
|
|
32
|
+
import { FirebaseError, getApp, type FirebaseApp } from 'pyric/app';
|
|
33
|
+
import { isSandboxAppDeleted } from 'pyric/app/internal';
|
|
34
|
+
import type { AiEngineConfigWire } from '../worker/protocol.js';
|
|
35
|
+
import { aiGenerateContent, aiCountTokens, aiStreamGenerateContent } from '../worker/client.js';
|
|
36
|
+
import { useWorker } from './worker-runtime.js';
|
|
37
|
+
import { workerClientForApp } from './app-client.js';
|
|
38
|
+
|
|
39
|
+
// ── Mirror surface — path-independent (the mirror always runs in-page) ────
|
|
40
|
+
export {
|
|
41
|
+
AIError,
|
|
42
|
+
AIErrorCode,
|
|
43
|
+
Backend,
|
|
44
|
+
BackendType,
|
|
45
|
+
GoogleAIBackend,
|
|
46
|
+
VertexAIBackend,
|
|
47
|
+
AIModel,
|
|
48
|
+
ChatSession,
|
|
49
|
+
ChatSessionBase,
|
|
50
|
+
GenerativeModel,
|
|
51
|
+
AnyOfSchema,
|
|
52
|
+
ArraySchema,
|
|
53
|
+
BooleanSchema,
|
|
54
|
+
IntegerSchema,
|
|
55
|
+
NumberSchema,
|
|
56
|
+
ObjectSchema,
|
|
57
|
+
Schema,
|
|
58
|
+
StringSchema,
|
|
59
|
+
BlockReason,
|
|
60
|
+
FinishReason,
|
|
61
|
+
FunctionCallingMode,
|
|
62
|
+
HarmBlockMethod,
|
|
63
|
+
HarmBlockThreshold,
|
|
64
|
+
HarmCategory,
|
|
65
|
+
HarmProbability,
|
|
66
|
+
HarmSeverity,
|
|
67
|
+
ImageConfigAspectRatio,
|
|
68
|
+
ImageConfigImageSize,
|
|
69
|
+
Language,
|
|
70
|
+
Modality,
|
|
71
|
+
Outcome,
|
|
72
|
+
POSSIBLE_ROLES,
|
|
73
|
+
ResponseModality,
|
|
74
|
+
SchemaType,
|
|
75
|
+
ThinkingLevel,
|
|
76
|
+
URLRetrievalStatus,
|
|
77
|
+
getGenerativeModel,
|
|
78
|
+
} from 'pyric/ai';
|
|
79
|
+
|
|
80
|
+
type EngineOption = NonNullable<ipAi.AIOptions['engine']>;
|
|
81
|
+
type AnswerTransport = Parameters<typeof createTransportAI>[2];
|
|
82
|
+
|
|
83
|
+
/** The serve proxy route the browser openai engine defaults to (#98.2). */
|
|
84
|
+
const AI_PROXY_PATH = '/__pyric/ai-proxy';
|
|
85
|
+
|
|
86
|
+
/** A custom AnswerEngine object (vs a plain, worker-cloneable EngineConfig). */
|
|
87
|
+
function isAnswerEngine(engine: EngineOption): boolean {
|
|
88
|
+
return typeof (engine as { generateContent?: unknown }).generateContent === 'function';
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
/**
|
|
92
|
+
* Derive the JSON-safe wire config that crosses the port from the app's
|
|
93
|
+
* `AIOptions.engine`. Predicate script matchers are functions — structured
|
|
94
|
+
* clone rejects them LOUDLY at send time (never silently dropped).
|
|
95
|
+
*/
|
|
96
|
+
function toEngineWire(engine: EngineOption | undefined): AiEngineConfigWire | undefined {
|
|
97
|
+
if (!engine || isAnswerEngine(engine)) return undefined;
|
|
98
|
+
const config = engine as Exclude<EngineOption, { generateContent: unknown }> & { kind: string };
|
|
99
|
+
if (config.kind === 'openai') {
|
|
100
|
+
const openai = config as Extract<EngineOption, { kind: 'openai' }>;
|
|
101
|
+
return {
|
|
102
|
+
kind: 'openai',
|
|
103
|
+
baseUrl: openai.baseUrl ?? AI_PROXY_PATH,
|
|
104
|
+
...(openai.model !== undefined ? { model: openai.model } : {}),
|
|
105
|
+
...(openai.modelMap !== undefined ? { modelMap: openai.modelMap } : {}),
|
|
106
|
+
};
|
|
107
|
+
}
|
|
108
|
+
const scripted = config as Extract<EngineOption, { kind: 'scripted' }>;
|
|
109
|
+
return {
|
|
110
|
+
kind: 'scripted',
|
|
111
|
+
...(scripted.script !== undefined
|
|
112
|
+
? { script: scripted.script as unknown as Array<Record<string, unknown>> }
|
|
113
|
+
: {}),
|
|
114
|
+
};
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
/**
|
|
118
|
+
* The plugin-level engine (`@pyric/cli/vite`'s `ai.engine`) for the IN-PAGE
|
|
119
|
+
* path, injected as a synchronous global BEFORE any app code runs (the plugin's
|
|
120
|
+
* transformIndexHtml — a classic inline script). The served `getAI` is
|
|
121
|
+
* synchronous and can't await init.json, so the worker path's init.json channel
|
|
122
|
+
* (→ ctx.aiEngine) has this page-side twin. Undefined outside the plugin.
|
|
123
|
+
*/
|
|
124
|
+
function pluginEngineWire(): AiEngineConfigWire | undefined {
|
|
125
|
+
return (globalThis as { __PYRIC_AI_ENGINE__?: AiEngineConfigWire }).__PYRIC_AI_ENGINE__;
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
/**
|
|
129
|
+
* Resolve a plugin wire engine to the mirror's `EngineOption` — an openai
|
|
130
|
+
* config with no `baseUrl` targets the serve proxy (mirrors resolveEngineConfig
|
|
131
|
+
* host-side and toEngineWire's inverse).
|
|
132
|
+
*/
|
|
133
|
+
function wireToEngineOption(wire: AiEngineConfigWire): EngineOption {
|
|
134
|
+
if (wire.kind === 'openai') {
|
|
135
|
+
return {
|
|
136
|
+
kind: 'openai',
|
|
137
|
+
baseUrl: wire.baseUrl ?? AI_PROXY_PATH,
|
|
138
|
+
...(wire.model !== undefined ? { model: wire.model } : {}),
|
|
139
|
+
...(wire.modelMap !== undefined ? { modelMap: wire.modelMap } : {}),
|
|
140
|
+
} as EngineOption;
|
|
141
|
+
}
|
|
142
|
+
return {
|
|
143
|
+
kind: 'scripted',
|
|
144
|
+
...(wire.script !== undefined
|
|
145
|
+
? { script: wire.script as unknown as Extract<EngineOption, { kind: 'scripted' }>['script'] }
|
|
146
|
+
: {}),
|
|
147
|
+
} as EngineOption;
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
/** In-page path: default an openai config's absent baseUrl to the proxy. */
|
|
151
|
+
function withProxyDefault(options?: ipAi.AIOptions): ipAi.AIOptions | undefined {
|
|
152
|
+
const engine = options?.engine;
|
|
153
|
+
if (!engine || isAnswerEngine(engine)) return options;
|
|
154
|
+
const config = engine as { kind?: string; baseUrl?: string };
|
|
155
|
+
if (config.kind === 'openai' && config.baseUrl === undefined) {
|
|
156
|
+
return { ...options, engine: { ...config, baseUrl: AI_PROXY_PATH } as EngineOption };
|
|
157
|
+
}
|
|
158
|
+
return options;
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
function aiErrorFromWire(err: unknown, modelResource: string, op: string): unknown {
|
|
162
|
+
const envelope = (err as { aiEnvelope?: Parameters<typeof aiErrorFromEnvelope>[0] } | null)
|
|
163
|
+
?.aiEnvelope;
|
|
164
|
+
return envelope ? aiErrorFromEnvelope(envelope, modelResource, op) : err;
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
// ── The port-forwarding AnswerEngine (worker path) ─────────────────────────
|
|
168
|
+
|
|
169
|
+
function portEngine(db: ReturnType<typeof workerClientForApp>, engineWire: AiEngineConfigWire | undefined): AnswerTransport {
|
|
170
|
+
const params = (model: string, request: Record<string, unknown>) => ({
|
|
171
|
+
model,
|
|
172
|
+
request,
|
|
173
|
+
...(engineWire !== undefined ? { engine: engineWire } : {}),
|
|
174
|
+
});
|
|
175
|
+
const engine = {
|
|
176
|
+
async generateContent(req: Record<string, unknown>, model: string): Promise<Record<string, unknown>> {
|
|
177
|
+
try {
|
|
178
|
+
return await aiGenerateContent(db, params(model, req));
|
|
179
|
+
} catch (err) {
|
|
180
|
+
throw aiErrorFromWire(err, model, 'generateContent');
|
|
181
|
+
}
|
|
182
|
+
},
|
|
183
|
+
streamGenerateContent(req: Record<string, unknown>, model: string): AsyncIterable<Record<string, unknown>> {
|
|
184
|
+
const inner = aiStreamGenerateContent(db, params(model, req));
|
|
185
|
+
return (async function* mapped(): AsyncGenerator<Record<string, unknown>> {
|
|
186
|
+
try {
|
|
187
|
+
for await (const chunk of inner) yield chunk;
|
|
188
|
+
} catch (err) {
|
|
189
|
+
throw aiErrorFromWire(err, model, 'streamGenerateContent');
|
|
190
|
+
}
|
|
191
|
+
})();
|
|
192
|
+
},
|
|
193
|
+
async countTokens(req: Record<string, unknown>, model: string): Promise<Record<string, unknown>> {
|
|
194
|
+
try {
|
|
195
|
+
return await aiCountTokens(db, params(model, req));
|
|
196
|
+
} catch (err) {
|
|
197
|
+
throw aiErrorFromWire(err, model, 'countTokens');
|
|
198
|
+
}
|
|
199
|
+
},
|
|
200
|
+
};
|
|
201
|
+
return engine as unknown as AnswerTransport;
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
/**
|
|
205
|
+
* Firebase constructs an AI handle for a deleted app, but that observation
|
|
206
|
+
* does not authorize resurrecting the app against an in-page sandbox. Keep
|
|
207
|
+
* the handle constructible while every model operation stays tombstoned.
|
|
208
|
+
*/
|
|
209
|
+
function deletedAppEngine(): AnswerTransport {
|
|
210
|
+
return {
|
|
211
|
+
async generateContent(): Promise<never> {
|
|
212
|
+
throw deletedAppError();
|
|
213
|
+
},
|
|
214
|
+
streamGenerateContent(): AsyncIterable<never> {
|
|
215
|
+
return (async function* tombstoned(): AsyncGenerator<never> {
|
|
216
|
+
throw deletedAppError();
|
|
217
|
+
})();
|
|
218
|
+
},
|
|
219
|
+
async countTokens(): Promise<never> {
|
|
220
|
+
throw deletedAppError();
|
|
221
|
+
},
|
|
222
|
+
} as AnswerTransport;
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
function deletedAppError(): FirebaseError {
|
|
226
|
+
return new FirebaseError(
|
|
227
|
+
'app/app-deleted',
|
|
228
|
+
'Firebase App has already been deleted.',
|
|
229
|
+
);
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
// ── getAI — worker-forwarded answering or whole-mirror in-page ─────────────
|
|
233
|
+
function mirrorGetAI(
|
|
234
|
+
app: unknown,
|
|
235
|
+
options?: ipAi.AIOptions,
|
|
236
|
+
): ipAi.AI {
|
|
237
|
+
const resolved = (app ?? getApp()) as FirebaseApp;
|
|
238
|
+
const ai = pyricGetAI(resolved, options);
|
|
239
|
+
const target = (ai as unknown as {
|
|
240
|
+
[ipAi.TARGET_SYMBOL]?: { assertAlive?: () => void };
|
|
241
|
+
})[ipAi.TARGET_SYMBOL];
|
|
242
|
+
if (target) {
|
|
243
|
+
// Served entries are separately bundled, so the in-page AI mirror cannot
|
|
244
|
+
// rely on sharing pyric/app's private adapter singleton. Stamp the target
|
|
245
|
+
// with the served registry's per-app deletion guard instead. The target is
|
|
246
|
+
// per app handle even though its broker is shared by the sandbox.
|
|
247
|
+
target.assertAlive = () => {
|
|
248
|
+
if (isSandboxAppDeleted(resolved)) throw deletedAppError();
|
|
249
|
+
};
|
|
250
|
+
}
|
|
251
|
+
return ai;
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
export const getAI = (
|
|
255
|
+
useWorker
|
|
256
|
+
? (app?: unknown, options?: ipAi.AIOptions) => {
|
|
257
|
+
const resolved = (app ?? getApp()) as FirebaseApp;
|
|
258
|
+
const engine = options?.engine;
|
|
259
|
+
const assertAlive = () => {
|
|
260
|
+
if (isSandboxAppDeleted(resolved)) throw deletedAppError();
|
|
261
|
+
};
|
|
262
|
+
if (isSandboxAppDeleted(resolved)) {
|
|
263
|
+
return createTransportAI(
|
|
264
|
+
resolved,
|
|
265
|
+
options,
|
|
266
|
+
deletedAppEngine(),
|
|
267
|
+
assertAlive,
|
|
268
|
+
);
|
|
269
|
+
}
|
|
270
|
+
if (engine && isAnswerEngine(engine)) {
|
|
271
|
+
throw new ipAi.AIError(
|
|
272
|
+
ipAi.AIErrorCode.UNSUPPORTED,
|
|
273
|
+
'Custom AnswerEngine objects cannot run in SharedWorker mode. ' +
|
|
274
|
+
"Use a structured { kind: 'scripted' } or { kind: 'openai' } engine config.",
|
|
275
|
+
);
|
|
276
|
+
}
|
|
277
|
+
return createTransportAI(
|
|
278
|
+
resolved,
|
|
279
|
+
options,
|
|
280
|
+
portEngine(workerClientForApp(resolved), toEngineWire(engine)),
|
|
281
|
+
assertAlive,
|
|
282
|
+
);
|
|
283
|
+
}
|
|
284
|
+
: (app?: unknown, options?: ipAi.AIOptions) => {
|
|
285
|
+
// Plugin-level engine wins over an app-code `getAI` engine (mirrors the
|
|
286
|
+
// worker host's ctx.aiEngine precedence). Absent ⇒ app options as-is.
|
|
287
|
+
const pluginWire = pluginEngineWire();
|
|
288
|
+
return pluginWire
|
|
289
|
+
? mirrorGetAI(app, { ...options, engine: wireToEngineOption(pluginWire) })
|
|
290
|
+
: mirrorGetAI(app, withProxyDefault(options));
|
|
291
|
+
}
|
|
292
|
+
) as typeof pyricGetAI;
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
/** Per-app SharedWorker ports: distinct Firebase service containers, one worker backend. */
|
|
2
|
+
import type { FirebaseApp } from 'pyric/app';
|
|
3
|
+
import { registerAppCleanup } from 'pyric/app/internal';
|
|
4
|
+
import type { ClientDb } from '../worker/client.js';
|
|
5
|
+
import type { InboundMessage } from '../worker/protocol.js';
|
|
6
|
+
import { disconnectClient } from '../worker/client/disconnect.js';
|
|
7
|
+
import { openWorkerDb } from './worker-runtime.js';
|
|
8
|
+
|
|
9
|
+
const clients = new WeakMap<FirebaseApp, ClientDb>();
|
|
10
|
+
|
|
11
|
+
export function workerClientForApp(app: FirebaseApp): ClientDb {
|
|
12
|
+
const existing = clients.get(app);
|
|
13
|
+
if (existing) return existing;
|
|
14
|
+
let client: ClientDb | undefined;
|
|
15
|
+
registerAppCleanup(app, async () => {
|
|
16
|
+
clients.delete(app);
|
|
17
|
+
if (client) await disconnectClient(client);
|
|
18
|
+
});
|
|
19
|
+
client = openWorkerDb(app.name);
|
|
20
|
+
client.port.postMessage({
|
|
21
|
+
t: 'appConfig',
|
|
22
|
+
options: app.options as Record<string, unknown>,
|
|
23
|
+
} satisfies InboundMessage);
|
|
24
|
+
clients.set(app, client);
|
|
25
|
+
return client;
|
|
26
|
+
}
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
/** App-owned browser auth-session stores for the served Firebase adapters. */
|
|
2
|
+
import type { FirebaseApp } from 'pyric/app';
|
|
3
|
+
import { registerAppCleanup } from 'pyric/app/internal';
|
|
4
|
+
import { SessionStore, type SessionStores } from './session-store.js';
|
|
5
|
+
|
|
6
|
+
export interface AppSessionStores {
|
|
7
|
+
forApp(app: FirebaseApp): SessionStore;
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* Build an app-identity-keyed store registry. The durable key inside each
|
|
12
|
+
* SessionStore remains app-name-keyed, so deleting/recreating an app can
|
|
13
|
+
* restore the same session while its in-memory mode/callback state is fresh.
|
|
14
|
+
*/
|
|
15
|
+
export function createAppSessionStores(stores: SessionStores): AppSessionStores {
|
|
16
|
+
const byApp = new WeakMap<FirebaseApp, SessionStore>();
|
|
17
|
+
return {
|
|
18
|
+
forApp(app) {
|
|
19
|
+
const existing = byApp.get(app);
|
|
20
|
+
if (existing) return existing;
|
|
21
|
+
const store = new SessionStore(stores, app.name);
|
|
22
|
+
byApp.set(app, store);
|
|
23
|
+
registerAppCleanup(app, () => { byApp.delete(app); });
|
|
24
|
+
return store;
|
|
25
|
+
},
|
|
26
|
+
};
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
function memoryStorage(): SessionStores['local'] {
|
|
30
|
+
const values = new Map<string, string>();
|
|
31
|
+
return {
|
|
32
|
+
getItem: (key) => values.get(key) ?? null,
|
|
33
|
+
setItem: (key, value) => { values.set(key, value); },
|
|
34
|
+
removeItem: (key) => { values.delete(key); },
|
|
35
|
+
};
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
const browserStores: SessionStores =
|
|
39
|
+
typeof localStorage !== 'undefined' && typeof sessionStorage !== 'undefined'
|
|
40
|
+
? { local: localStorage, session: sessionStorage }
|
|
41
|
+
: { local: memoryStorage(), session: memoryStorage() };
|
|
42
|
+
|
|
43
|
+
const servedStores = createAppSessionStores(browserStores);
|
|
44
|
+
|
|
45
|
+
export function sessionStoreForApp(app: FirebaseApp): SessionStore {
|
|
46
|
+
return servedStores.forApp(app);
|
|
47
|
+
}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
/** Served `firebase/app`: the exact Pyric client registry over the page runtime. */
|
|
2
|
+
import { bindAppRegistrySandbox } from 'pyric/app/internal';
|
|
3
|
+
import { sandbox } from './app-backend.js';
|
|
4
|
+
|
|
5
|
+
// Keep this entry free of dynamic imports: ServiceWorkerGlobalScope supports
|
|
6
|
+
// static module imports but rejects dynamic import(). The attached sandbox is
|
|
7
|
+
// only the app handle's realm-local placeholder; served service calls route to
|
|
8
|
+
// the SharedWorker (directly in Window, via BroadcastChannel in a real SW).
|
|
9
|
+
bindAppRegistrySandbox(sandbox);
|
|
10
|
+
|
|
11
|
+
export * from 'pyric/app';
|