@pyric/cli 0.1.0-alpha.19 → 0.1.0-alpha.21
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 +64 -89
- package/dist/assurance/.generated/conformance-verdicts.d.ts +68 -1
- package/dist/assurance/.generated/conformance-verdicts.d.ts.map +1 -1
- package/dist/assurance/.generated/conformance-verdicts.js +68 -1
- package/dist/assurance/.generated/conformance-verdicts.js.map +1 -1
- package/dist/bridge/client/dispatch.d.ts +25 -19
- package/dist/bridge/client/dispatch.d.ts.map +1 -1
- package/dist/bridge/client/dispatch.js +41 -48
- package/dist/bridge/client/dispatch.js.map +1 -1
- package/dist/bridge/client/tool-family-factories.d.ts +26 -0
- package/dist/bridge/client/tool-family-factories.d.ts.map +1 -0
- package/dist/bridge/client/tool-family-factories.js +10 -0
- package/dist/bridge/client/tool-family-factories.js.map +1 -0
- package/dist/bridge/server/mcp-contract.d.ts +7 -6
- package/dist/bridge/server/mcp-contract.d.ts.map +1 -1
- package/dist/bridge/server/mcp-contract.js +9 -48
- package/dist/bridge/server/mcp-contract.js.map +1 -1
- package/dist/bridge/server/peer.d.ts +11 -1
- package/dist/bridge/server/peer.d.ts.map +1 -1
- package/dist/bridge/server/peer.js +22 -1
- package/dist/bridge/server/peer.js.map +1 -1
- package/dist/bridge/server/tool-family-factories.d.ts +30 -0
- package/dist/bridge/server/tool-family-factories.d.ts.map +1 -0
- package/dist/bridge/server/tool-family-factories.js +22 -0
- package/dist/bridge/server/tool-family-factories.js.map +1 -0
- package/dist/bridge/server/tool-metadata.d.ts +17 -18
- package/dist/bridge/server/tool-metadata.d.ts.map +1 -1
- package/dist/bridge/server/tool-metadata.js +24 -59
- package/dist/bridge/server/tool-metadata.js.map +1 -1
- package/dist/bridge/tool-families.d.ts +48 -0
- package/dist/bridge/tool-families.d.ts.map +1 -0
- package/dist/bridge/tool-families.generated.d.ts +32 -0
- package/dist/bridge/tool-families.generated.d.ts.map +1 -0
- package/dist/bridge/tool-families.generated.js +15 -0
- package/dist/bridge/tool-families.generated.js.map +1 -0
- package/dist/bridge/tool-families.js +51 -0
- package/dist/bridge/tool-families.js.map +1 -0
- package/dist/bridge/tool-family-records/conformance.d.ts +7 -0
- package/dist/bridge/tool-family-records/conformance.d.ts.map +1 -0
- package/dist/bridge/tool-family-records/conformance.js +6 -0
- package/dist/bridge/tool-family-records/conformance.js.map +1 -0
- package/dist/bridge/tool-family-records/firestore-data.d.ts +7 -0
- package/dist/bridge/tool-family-records/firestore-data.d.ts.map +1 -0
- package/dist/bridge/tool-family-records/firestore-data.js +15 -0
- package/dist/bridge/tool-family-records/firestore-data.js.map +1 -0
- package/dist/bridge/tool-family-records/firestore-inspect.d.ts +7 -0
- package/dist/bridge/tool-family-records/firestore-inspect.d.ts.map +1 -0
- package/dist/bridge/tool-family-records/firestore-inspect.js +6 -0
- package/dist/bridge/tool-family-records/firestore-inspect.js.map +1 -0
- package/dist/bridge/tool-family-records/firestore-rules.d.ts +7 -0
- package/dist/bridge/tool-family-records/firestore-rules.d.ts.map +1 -0
- package/dist/bridge/tool-family-records/firestore-rules.js +15 -0
- package/dist/bridge/tool-family-records/firestore-rules.js.map +1 -0
- package/dist/bridge/tool-family-records/firestore-simulator.d.ts +7 -0
- package/dist/bridge/tool-family-records/firestore-simulator.d.ts.map +1 -0
- package/dist/bridge/tool-family-records/firestore-simulator.js +16 -0
- package/dist/bridge/tool-family-records/firestore-simulator.js.map +1 -0
- package/dist/bridge/tool-family-records/rtdb-inspection.d.ts +7 -0
- package/dist/bridge/tool-family-records/rtdb-inspection.d.ts.map +1 -0
- package/dist/bridge/tool-family-records/rtdb-inspection.js +6 -0
- package/dist/bridge/tool-family-records/rtdb-inspection.js.map +1 -0
- package/dist/bundler/externals.d.ts +37 -0
- package/dist/bundler/externals.d.ts.map +1 -0
- package/dist/bundler/externals.js +52 -0
- package/dist/bundler/externals.js.map +1 -0
- package/dist/bundler/index.d.ts +17 -0
- package/dist/bundler/index.d.ts.map +1 -0
- package/dist/bundler/index.js +17 -0
- package/dist/bundler/index.js.map +1 -0
- package/dist/cli/index.d.ts +2 -2
- package/dist/cli/index.d.ts.map +1 -1
- package/dist/cli/index.js +48 -42
- package/dist/cli/index.js.map +1 -1
- package/dist/cli/inlined-sdk-scanner.d.ts +6 -0
- package/dist/cli/inlined-sdk-scanner.d.ts.map +1 -0
- package/dist/cli/inlined-sdk-scanner.js +82 -0
- package/dist/cli/inlined-sdk-scanner.js.map +1 -0
- package/dist/cli/mcp-proxy.js +3 -3
- package/dist/cli/mcp-proxy.js.map +1 -1
- package/dist/cli/parse-args.d.ts +1 -1
- package/dist/cli/parse-args.d.ts.map +1 -1
- package/dist/cli/parse-args.js +51 -15
- package/dist/cli/parse-args.js.map +1 -1
- package/dist/cli/pyric-config.d.ts +24 -0
- package/dist/cli/pyric-config.d.ts.map +1 -0
- package/dist/cli/pyric-config.js +28 -0
- package/dist/cli/pyric-config.js.map +1 -0
- package/dist/cli/sandbox-interlock.d.ts +97 -0
- package/dist/cli/sandbox-interlock.d.ts.map +1 -0
- package/dist/cli/sandbox-interlock.js +112 -0
- package/dist/cli/sandbox-interlock.js.map +1 -0
- package/dist/cli/{dev-runner.d.ts → sandbox-runner.d.ts} +53 -48
- package/dist/cli/sandbox-runner.d.ts.map +1 -0
- package/dist/cli/{dev-runner.js → sandbox-runner.js} +105 -67
- package/dist/cli/sandbox-runner.js.map +1 -0
- package/dist/cli/serve.d.ts +12 -14
- package/dist/cli/serve.d.ts.map +1 -1
- package/dist/cli/serve.js +155 -116
- package/dist/cli/serve.js.map +1 -1
- package/dist/cli/snapshot.js +5 -5
- package/dist/cli/snapshot.js.map +1 -1
- package/dist/cli/unsupported-runtime.d.ts +27 -0
- package/dist/cli/unsupported-runtime.d.ts.map +1 -0
- package/dist/cli/unsupported-runtime.js +63 -0
- package/dist/cli/unsupported-runtime.js.map +1 -0
- package/dist/cli/verify.js +1 -1
- package/dist/cli/verify.js.map +1 -1
- package/dist/conformance/.generated/can-i-use-browser.d.ts +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 +1 -1
- package/dist/conformance/.generated/can-i-use.d.ts.map +1 -1
- package/dist/conformance/.generated/can-i-use.js +1 -1
- package/dist/conformance/.generated/can-i-use.js.map +1 -1
- package/dist/conformance/.generated/conformance-docs.d.ts.map +1 -1
- package/dist/conformance/.generated/conformance-docs.js +1 -1
- package/dist/conformance/.generated/conformance-docs.js.map +1 -1
- package/dist/functions-rtdb/child.d.ts +37 -0
- package/dist/functions-rtdb/child.d.ts.map +1 -1
- package/dist/functions-rtdb/child.js +90 -21
- package/dist/functions-rtdb/child.js.map +1 -1
- package/dist/functions-rtdb/development-runtime.d.ts +3 -0
- package/dist/functions-rtdb/development-runtime.d.ts.map +1 -1
- package/dist/functions-rtdb/development-runtime.js +2 -1
- package/dist/functions-rtdb/development-runtime.js.map +1 -1
- package/dist/functions-rtdb/project.js +8 -8
- package/dist/functions-rtdb/project.js.map +1 -1
- package/dist/google-endpoints.d.ts +67 -0
- package/dist/google-endpoints.d.ts.map +1 -0
- package/dist/google-endpoints.js +119 -0
- package/dist/google-endpoints.js.map +1 -0
- package/dist/next/guard.js +1 -1
- package/dist/next/guard.js.map +1 -1
- package/dist/next/runtime-env.d.ts.map +1 -1
- package/dist/next/runtime-env.js +5 -0
- package/dist/next/runtime-env.js.map +1 -1
- package/dist/next/with-pyric.d.ts +1 -1
- package/dist/next/with-pyric.js +1 -1
- package/dist/register/beacon.d.ts +69 -0
- package/dist/register/beacon.d.ts.map +1 -0
- package/dist/register/beacon.js +199 -0
- package/dist/register/beacon.js.map +1 -0
- package/dist/register/index.d.ts.map +1 -1
- package/dist/register/index.js +65 -6
- package/dist/register/index.js.map +1 -1
- package/dist/register/net-guard.d.ts +110 -0
- package/dist/register/net-guard.d.ts.map +1 -0
- package/dist/register/net-guard.js +462 -0
- package/dist/register/net-guard.js.map +1 -0
- package/dist/remote/index.d.ts +1 -1
- package/dist/remote/index.js +9 -9
- package/dist/remote/index.js.map +1 -1
- package/dist/serve/ai-diagnostics-relay.d.ts +63 -0
- package/dist/serve/ai-diagnostics-relay.d.ts.map +1 -0
- package/dist/serve/ai-diagnostics-relay.js +125 -0
- package/dist/serve/ai-diagnostics-relay.js.map +1 -0
- package/dist/serve/ai-proxy.d.ts +92 -0
- package/dist/serve/ai-proxy.d.ts.map +1 -0
- package/dist/serve/ai-proxy.js +269 -0
- package/dist/serve/ai-proxy.js.map +1 -0
- package/dist/serve/ai-status.d.ts +44 -0
- package/dist/serve/ai-status.d.ts.map +1 -0
- package/dist/serve/ai-status.js +77 -0
- package/dist/serve/ai-status.js.map +1 -0
- package/dist/serve/ai-terminal-blocks.d.ts +97 -0
- package/dist/serve/ai-terminal-blocks.d.ts.map +1 -0
- package/dist/serve/ai-terminal-blocks.js +128 -0
- package/dist/serve/ai-terminal-blocks.js.map +1 -0
- package/dist/serve/ai-terminal-text.d.ts +36 -0
- package/dist/serve/ai-terminal-text.d.ts.map +1 -0
- package/dist/serve/ai-terminal-text.js +48 -0
- package/dist/serve/ai-terminal-text.js.map +1 -0
- package/dist/serve/beacon-route.d.ts +53 -0
- package/dist/serve/beacon-route.d.ts.map +1 -0
- package/dist/serve/beacon-route.js +101 -0
- package/dist/serve/beacon-route.js.map +1 -0
- package/dist/serve/bundler.js +10 -10
- package/dist/serve/bundler.js.map +1 -1
- package/dist/serve/entries/active-auth-registry.d.ts +16 -0
- package/dist/serve/entries/active-auth-registry.d.ts.map +1 -0
- package/dist/serve/entries/active-auth-registry.js +48 -0
- package/dist/serve/entries/active-auth-registry.js.map +1 -0
- package/dist/serve/entries/active-auth.d.ts +18 -0
- package/dist/serve/entries/active-auth.d.ts.map +1 -0
- package/dist/serve/entries/active-auth.js +84 -0
- package/dist/serve/entries/active-auth.js.map +1 -0
- package/dist/serve/entries/ai.d.ts.map +1 -1
- package/dist/serve/entries/ai.js +27 -1
- package/dist/serve/entries/ai.js.map +1 -1
- package/dist/serve/entries/auth-helper-core.d.ts +1 -0
- package/dist/serve/entries/auth-helper-core.d.ts.map +1 -1
- package/dist/serve/entries/auth-helper-core.js +4 -0
- package/dist/serve/entries/auth-helper-core.js.map +1 -1
- package/dist/serve/entries/auth-helper-dom.d.ts.map +1 -1
- package/dist/serve/entries/auth-helper-dom.js +29 -3
- package/dist/serve/entries/auth-helper-dom.js.map +1 -1
- package/dist/serve/entries/auth-helper-runtime.js +1 -1
- package/dist/serve/entries/auth-helper-runtime.js.map +1 -1
- package/dist/serve/entries/auth.d.ts.map +1 -1
- package/dist/serve/entries/auth.js +8 -0
- package/dist/serve/entries/auth.js.map +1 -1
- package/dist/serve/entries/bridge-url.d.ts +1 -1
- package/dist/serve/entries/bridge-url.js +1 -1
- package/dist/serve/entries/firestore.js +3 -3
- package/dist/serve/entries/firestore.js.map +1 -1
- package/dist/serve/entries/init.d.ts.map +1 -1
- package/dist/serve/entries/init.js +70 -4
- package/dist/serve/entries/init.js.map +1 -1
- package/dist/serve/entries/keepalive.js +1 -1
- package/dist/serve/entries/keepalive.js.map +1 -1
- package/dist/serve/entries/runtime.d.ts.map +1 -1
- package/dist/serve/entries/runtime.js +30 -14
- package/dist/serve/entries/runtime.js.map +1 -1
- package/dist/serve/entries/storage.js +1 -1
- package/dist/serve/entries/storage.js.map +1 -1
- package/dist/serve/entries/worker-runtime.js +1 -1
- package/dist/serve/entries/worker-runtime.js.map +1 -1
- package/dist/serve/init-payload.d.ts +4 -0
- package/dist/serve/init-payload.d.ts.map +1 -1
- package/dist/serve/namespace.d.ts +20 -5
- package/dist/serve/namespace.d.ts.map +1 -1
- package/dist/serve/namespace.js +93 -129
- package/dist/serve/namespace.js.map +1 -1
- package/dist/serve/rules.d.ts.map +1 -1
- package/dist/serve/rules.js +44 -21
- package/dist/serve/rules.js.map +1 -1
- package/dist/serve/runtime/chip-config.d.ts.map +1 -1
- package/dist/serve/runtime/chip-config.js +16 -2
- package/dist/serve/runtime/chip-config.js.map +1 -1
- package/dist/serve/runtime/chip-dialog.d.ts +18 -0
- package/dist/serve/runtime/chip-dialog.d.ts.map +1 -0
- package/dist/serve/runtime/chip-dialog.js +315 -0
- package/dist/serve/runtime/chip-dialog.js.map +1 -0
- package/dist/serve/runtime/chip-install.d.ts +2 -0
- package/dist/serve/runtime/chip-install.d.ts.map +1 -1
- package/dist/serve/runtime/chip-install.js +9 -4
- package/dist/serve/runtime/chip-install.js.map +1 -1
- package/dist/serve/runtime/chip-user-search.d.ts +16 -0
- package/dist/serve/runtime/chip-user-search.d.ts.map +1 -0
- package/dist/serve/runtime/chip-user-search.js +354 -0
- package/dist/serve/runtime/chip-user-search.js.map +1 -0
- package/dist/serve/runtime/chip.d.ts +10 -0
- package/dist/serve/runtime/chip.d.ts.map +1 -1
- package/dist/serve/runtime/chip.js +149 -15
- package/dist/serve/runtime/chip.js.map +1 -1
- package/dist/serve/runtime/identity.d.ts +17 -0
- package/dist/serve/runtime/identity.d.ts.map +1 -0
- package/dist/serve/runtime/identity.js +10 -0
- package/dist/serve/runtime/identity.js.map +1 -0
- package/dist/serve/runtime/manifest.d.ts +1 -1
- package/dist/serve/runtime/manifest.d.ts.map +1 -1
- package/dist/serve/runtime/manifest.js +1 -1
- package/dist/serve/runtime/manifest.js.map +1 -1
- package/dist/serve/runtime/status.d.ts.map +1 -1
- package/dist/serve/runtime/status.js +23 -0
- package/dist/serve/runtime/status.js.map +1 -1
- package/dist/serve/sandbox-session.d.ts +8 -0
- package/dist/serve/sandbox-session.d.ts.map +1 -1
- package/dist/serve/sandbox-session.js +12 -4
- package/dist/serve/sandbox-session.js.map +1 -1
- package/dist/serve/server.d.ts +7 -0
- package/dist/serve/server.d.ts.map +1 -1
- package/dist/serve/server.js +15 -1
- package/dist/serve/server.js.map +1 -1
- package/dist/serve/site-ui/404.html +1 -1
- package/dist/serve/site-ui/_astro/{RulesCodeEditor.Bl-XMkhD.js → RulesCodeEditor.DsTAsKIa.js} +1 -1
- package/dist/serve/site-ui/_astro/_slug_.Da5VLhW1.css +1 -0
- package/dist/serve/site-ui/_astro/_studio_.BnfYCKoI.css +1 -0
- package/dist/serve/site-ui/_astro/app.BOr2kwae.js +115 -0
- package/dist/serve/site-ui/_astro/app.DhAcImFD.js +1 -0
- package/dist/serve/site-ui/_astro/app.pMSnN6Qw.css +1 -0
- package/dist/serve/site-ui/_astro/{chess-showcase.Dh5D8kqe.js → chess-showcase.Bn_pP6UJ.js} +2 -2
- package/dist/serve/site-ui/_astro/{example-runtime.CuyAjjiG.js → example-runtime.CXopTGv7.js} +1 -1
- package/dist/serve/site-ui/_astro/index.Tb-31efc.css +1 -0
- package/dist/serve/site-ui/_astro/index.UkCBr-_0.css +1 -0
- package/dist/serve/site-ui/_astro/sandbox-controls.rOuZTOUG.js +510 -0
- package/dist/serve/site-ui/_astro/{seed.CSUkloMi.js → seed.03a08f9t.js} +2 -2
- package/dist/serve/site-ui/_astro/writes.BsPBG3dp.js +653 -0
- package/dist/serve/site-ui/auth/index.html +5 -3
- package/dist/serve/site-ui/david-east.jpg +0 -0
- package/dist/serve/site-ui/docs/_rhythm/index.html +6 -3
- package/dist/serve/site-ui/docs/_rhythm.md +1 -1
- package/dist/serve/site-ui/docs/agent/set-up-your-agent/index.html +18 -15
- package/dist/serve/site-ui/docs/agent/set-up-your-agent.md +16 -16
- package/dist/serve/site-ui/docs/agent/watch-and-review/index.html +6 -5
- package/dist/serve/site-ui/docs/agent/watch-and-review.md +1 -5
- package/dist/serve/site-ui/docs/agent/work-with-an-agent/index.html +8 -5
- package/dist/serve/site-ui/docs/agent/work-with-an-agent.md +4 -4
- package/dist/serve/site-ui/docs/ai-compat/index.html +5 -2
- package/dist/serve/site-ui/docs/api-reference/index.html +13 -2
- package/dist/serve/site-ui/docs/api-reference.md +8 -0
- package/dist/serve/site-ui/docs/app-compat/index.html +5 -2
- package/dist/serve/site-ui/docs/auth-compat/index.html +7 -4
- package/dist/serve/site-ui/docs/auth-compat.md +2 -2
- package/dist/serve/site-ui/docs/build/ai-logic/index.html +10 -2
- package/dist/serve/site-ui/docs/build/ai-logic.md +6 -0
- package/dist/serve/site-ui/docs/build/authentication/index.html +7 -4
- package/dist/serve/site-ui/docs/build/authentication.md +2 -2
- package/dist/serve/site-ui/docs/build/cloud-firestore/index.html +5 -2
- package/dist/serve/site-ui/docs/build/cloud-messaging/index.html +5 -2
- package/dist/serve/site-ui/docs/build/cloud-storage/index.html +6 -3
- package/dist/serve/site-ui/docs/build/cloud-storage.md +1 -1
- package/dist/serve/site-ui/docs/build/realtime-database/index.html +9 -6
- package/dist/serve/site-ui/docs/build/realtime-database.md +4 -4
- package/dist/serve/site-ui/docs/conformance-scores/index.html +10 -3
- package/dist/serve/site-ui/docs/conformance-scores.md +5 -1
- package/dist/serve/site-ui/docs/create-pyric-reference-api/index.html +6 -3
- package/dist/serve/site-ui/docs/create-pyric-reference-api.md +1 -1
- package/dist/serve/site-ui/docs/database-compat/index.html +48 -45
- package/dist/serve/site-ui/docs/database-compat.md +43 -43
- package/dist/serve/site-ui/docs/examples/chess/index.html +5 -2
- package/dist/serve/site-ui/docs/examples/index.html +5 -2
- package/dist/serve/site-ui/docs/firestore-compat/index.html +8 -5
- package/dist/serve/site-ui/docs/firestore-compat.md +3 -3
- package/dist/serve/site-ui/docs/firestore-flutter-compat/index.html +319 -0
- package/dist/serve/site-ui/docs/firestore-flutter-compat.md +344 -0
- package/dist/serve/site-ui/docs/functions-rtdb-compat/index.html +5 -2
- package/dist/serve/site-ui/docs/get-started/backend-bundlers/index.html +105 -0
- package/dist/serve/site-ui/docs/get-started/backend-bundlers.md +165 -0
- package/dist/serve/site-ui/docs/get-started/how-the-swap-works/index.html +9 -6
- package/dist/serve/site-ui/docs/get-started/how-the-swap-works.md +5 -5
- package/dist/serve/site-ui/docs/get-started/nextjs/index.html +9 -6
- package/dist/serve/site-ui/docs/get-started/nextjs.md +4 -4
- package/dist/serve/site-ui/docs/get-started/start-building/index.html +14 -9
- package/dist/serve/site-ui/docs/get-started/start-building.md +16 -10
- package/dist/serve/site-ui/docs/get-started/vite/index.html +6 -3
- package/dist/serve/site-ui/docs/get-started/vite.md +1 -1
- package/dist/serve/site-ui/docs/index.json +3128 -1613
- package/dist/serve/site-ui/docs/messaging-compat/index.html +5 -2
- package/dist/serve/site-ui/docs/observe/resolve-runtime-status/index.html +5 -2
- package/dist/serve/site-ui/docs/observe/see-whats-happening/index.html +6 -4
- package/dist/serve/site-ui/docs/observe/see-whats-happening.md +2 -4
- package/dist/serve/site-ui/docs/observe/shape-your-data/index.html +9 -6
- package/dist/serve/site-ui/docs/observe/shape-your-data.md +4 -4
- package/dist/serve/site-ui/docs/overview/index.html +16 -10
- package/dist/serve/site-ui/docs/overview.md +19 -11
- package/dist/serve/site-ui/docs/pyric-admin-app-reference-api/index.html +5 -2
- package/dist/serve/site-ui/docs/pyric-admin-auth-reference-api/index.html +5 -2
- package/dist/serve/site-ui/docs/pyric-admin-database-reference-api/index.html +5 -2
- package/dist/serve/site-ui/docs/pyric-admin-firestore-reference-api/index.html +8 -5
- package/dist/serve/site-ui/docs/pyric-admin-firestore-reference-api.md +3 -3
- package/dist/serve/site-ui/docs/pyric-admin-messaging-reference-api/index.html +5 -2
- package/dist/serve/site-ui/docs/pyric-admin-reference-api/index.html +501 -0
- package/dist/serve/site-ui/docs/pyric-admin-reference-api.md +587 -0
- package/dist/serve/site-ui/docs/pyric-admin-storage-reference-api/index.html +5 -2
- package/dist/serve/site-ui/docs/pyric-ai-reference-api/index.html +5 -2
- package/dist/serve/site-ui/docs/pyric-ai-scripting-reference-api/index.html +5 -2
- package/dist/serve/site-ui/docs/pyric-analytics-reference-api/index.html +124 -0
- package/dist/serve/site-ui/docs/pyric-analytics-reference-api.md +287 -0
- package/dist/serve/site-ui/docs/pyric-app-check-reference-api/index.html +87 -0
- package/dist/serve/site-ui/docs/pyric-app-check-reference-api.md +199 -0
- package/dist/serve/site-ui/docs/pyric-app-reference-api/index.html +5 -2
- package/dist/serve/site-ui/docs/pyric-auth-reference-api/index.html +10 -3
- package/dist/serve/site-ui/docs/pyric-auth-reference-api.md +2 -1
- package/dist/serve/site-ui/docs/pyric-cli-assurance-browser-reference-api/index.html +5 -2
- package/dist/serve/site-ui/docs/pyric-cli-assurance-reference-api/index.html +5 -2
- package/dist/serve/site-ui/docs/pyric-cli-bridge-client-reference-api/index.html +6 -3
- package/dist/serve/site-ui/docs/pyric-cli-bridge-client-reference-api.md +1 -1
- package/dist/serve/site-ui/docs/pyric-cli-bridge-reference-api/index.html +5 -2
- package/dist/serve/site-ui/docs/pyric-cli-bundler-reference-api/index.html +56 -0
- package/dist/serve/site-ui/docs/pyric-cli-bundler-reference-api.md +72 -0
- package/dist/serve/site-ui/docs/pyric-cli-conformance-browser-reference-api/index.html +6 -2
- package/dist/serve/site-ui/docs/pyric-cli-conformance-browser-reference-api.md +1 -0
- package/dist/serve/site-ui/docs/pyric-cli-conformance-docs-reference-api/index.html +5 -2
- package/dist/serve/site-ui/docs/pyric-cli-conformance-reference-api/index.html +6 -2
- package/dist/serve/site-ui/docs/pyric-cli-conformance-reference-api.md +1 -0
- package/dist/serve/site-ui/docs/pyric-cli-credentials-node-reference-api/index.html +5 -2
- package/dist/serve/site-ui/docs/pyric-cli-discover-reference-api/index.html +5 -2
- package/dist/serve/site-ui/docs/pyric-cli-next-reference-api/index.html +6 -3
- package/dist/serve/site-ui/docs/pyric-cli-next-reference-api.md +1 -1
- package/dist/serve/site-ui/docs/pyric-cli-register-app-bridge-reference-api/index.html +5 -2
- package/dist/serve/site-ui/docs/pyric-cli-register-reference-api/index.html +5 -2
- package/dist/serve/site-ui/docs/pyric-cli-remote-reference-api/index.html +39 -11
- package/dist/serve/site-ui/docs/pyric-cli-remote-reference-api.md +17 -9
- package/dist/serve/site-ui/docs/pyric-cli-serve-worker-reference-api/index.html +137 -94
- package/dist/serve/site-ui/docs/pyric-cli-serve-worker-reference-api.md +61 -1
- package/dist/serve/site-ui/docs/pyric-cli-verify-reference-api/index.html +6 -3
- package/dist/serve/site-ui/docs/pyric-cli-verify-reference-api.md +1 -1
- package/dist/serve/site-ui/docs/pyric-cli-vite-reference-api/index.html +6 -3
- package/dist/serve/site-ui/docs/pyric-cli-vite-reference-api.md +2 -2
- package/dist/serve/site-ui/docs/pyric-database-reference-api/index.html +167 -96
- package/dist/serve/site-ui/docs/pyric-database-reference-api.md +108 -2
- package/dist/serve/site-ui/docs/pyric-firestore-lite-reference-api/index.html +559 -0
- package/dist/serve/site-ui/docs/pyric-firestore-lite-reference-api.md +1240 -0
- package/dist/serve/site-ui/docs/pyric-firestore-reference-api/index.html +44 -12
- package/dist/serve/site-ui/docs/pyric-firestore-reference-api.md +18 -8
- package/dist/serve/site-ui/docs/pyric-functions-reference-api/index.html +67 -0
- package/dist/serve/site-ui/docs/pyric-functions-reference-api.md +149 -0
- package/dist/serve/site-ui/docs/pyric-messaging-reference-api/index.html +10 -4
- package/dist/serve/site-ui/docs/pyric-messaging-reference-api.md +5 -2
- package/dist/serve/site-ui/docs/pyric-messaging-sw-reference-api/index.html +10 -4
- package/dist/serve/site-ui/docs/pyric-messaging-sw-reference-api.md +5 -2
- package/dist/serve/site-ui/docs/pyric-performance-reference-api/index.html +35 -0
- package/dist/serve/site-ui/docs/pyric-performance-reference-api.md +69 -0
- package/dist/serve/site-ui/docs/pyric-remote-config-reference-api/index.html +132 -0
- package/dist/serve/site-ui/docs/pyric-remote-config-reference-api.md +307 -0
- package/dist/serve/site-ui/docs/pyric-rules-reference-api/index.html +5 -2
- package/dist/serve/site-ui/docs/pyric-sandbox-database-reference-api/index.html +56 -18
- package/dist/serve/site-ui/docs/pyric-sandbox-database-reference-api.md +48 -9
- package/dist/serve/site-ui/docs/pyric-sandbox-firestore-reference-api/index.html +6 -3
- package/dist/serve/site-ui/docs/pyric-sandbox-firestore-reference-api.md +2 -2
- package/dist/serve/site-ui/docs/pyric-sandbox-reference-api/index.html +243 -50
- package/dist/serve/site-ui/docs/pyric-sandbox-reference-api.md +148 -31
- package/dist/serve/site-ui/docs/pyric-storage-reference-api/index.html +26 -5
- package/dist/serve/site-ui/docs/pyric-storage-reference-api.md +24 -2
- package/dist/serve/site-ui/docs/pyric-ui-agents-reference-api/index.html +30 -97
- package/dist/serve/site-ui/docs/pyric-ui-agents-reference-api.md +20 -51
- package/dist/serve/site-ui/docs/pyric-ui-auth-hooks-reference-api/index.html +5 -2
- package/dist/serve/site-ui/docs/pyric-ui-auth-reference-api/index.html +6 -3
- package/dist/serve/site-ui/docs/pyric-ui-auth-reference-api.md +1 -1
- package/dist/serve/site-ui/docs/pyric-ui-events-hooks-reference-api/index.html +5 -2
- package/dist/serve/site-ui/docs/pyric-ui-events-reference-api/index.html +5 -2
- package/dist/serve/site-ui/docs/pyric-ui-firestore-hooks-reference-api/index.html +5 -2
- package/dist/serve/site-ui/docs/pyric-ui-firestore-reference-api/index.html +6 -3
- package/dist/serve/site-ui/docs/pyric-ui-firestore-reference-api.md +1 -1
- package/dist/serve/site-ui/docs/pyric-ui-primitives-reference-api/index.html +128 -27
- package/dist/serve/site-ui/docs/pyric-ui-primitives-reference-api.md +58 -5
- package/dist/serve/site-ui/docs/pyric-ui-rtdb-reference-api/index.html +5 -2
- package/dist/serve/site-ui/docs/pyric-ui-rules-hooks-reference-api/index.html +5 -2
- package/dist/serve/site-ui/docs/pyric-ui-rules-reference-api/index.html +5 -2
- package/dist/serve/site-ui/docs/pyric-ui-storage-hooks-reference-api/index.html +5 -2
- package/dist/serve/site-ui/docs/pyric-ui-storage-reference-api/index.html +8 -6
- package/dist/serve/site-ui/docs/pyric-ui-storage-reference-api.md +3 -4
- package/dist/serve/site-ui/docs/pyric-ui-traffic-hooks-reference-api/index.html +5 -2
- package/dist/serve/site-ui/docs/pyric-ui-traffic-reference-api/index.html +5 -2
- package/dist/serve/site-ui/docs/reference/cli/index.html +156 -0
- package/dist/serve/site-ui/docs/reference/cli.md +120 -0
- package/dist/serve/site-ui/docs/rules-compat/index.html +14 -3
- package/dist/serve/site-ui/docs/rules-compat.md +9 -1
- package/dist/serve/site-ui/docs/secure/audit-your-rules/index.html +5 -2
- package/dist/serve/site-ui/docs/secure/firestore-rules-limits/index.html +5 -2
- package/dist/serve/site-ui/docs/secure/read-a-denial/index.html +6 -3
- package/dist/serve/site-ui/docs/secure/read-a-denial.md +1 -1
- package/dist/serve/site-ui/docs/secure/rtdb-rules-in-typescript/index.html +5 -2
- package/dist/serve/site-ui/docs/secure/rules-standard-library/index.html +5 -2
- package/dist/serve/site-ui/docs/secure/secure-it-with-rules/index.html +5 -2
- package/dist/serve/site-ui/docs/secure/simulate-and-lint/index.html +5 -2
- package/dist/serve/site-ui/docs/secure/write-a-rules-test-suite/index.html +5 -2
- package/dist/serve/site-ui/docs/ship/ship-to-production/index.html +7 -4
- package/dist/serve/site-ui/docs/ship/ship-to-production.md +2 -2
- package/dist/serve/site-ui/docs/ship/test-in-node/index.html +9 -6
- package/dist/serve/site-ui/docs/ship/test-in-node.md +4 -4
- package/dist/serve/site-ui/docs/storage-compat/index.html +17 -14
- package/dist/serve/site-ui/docs/storage-compat.md +12 -12
- package/dist/serve/site-ui/docs/trust/how-we-know-it-matches-firebase/index.html +5 -2
- package/dist/serve/site-ui/docs/trust/rules-standard-library/index.html +5 -2
- package/dist/serve/site-ui/docs/trust/versioning-and-compatibility/index.html +5 -2
- package/dist/serve/site-ui/docs/tutorial/index.html +10 -7
- package/dist/serve/site-ui/docs/tutorial.md +4 -4
- package/dist/serve/site-ui/examples/chess/index.html +4 -2
- package/dist/serve/site-ui/firestore/index.html +5 -3
- package/dist/serve/site-ui/hero-charcoal.jpg +0 -0
- package/dist/serve/site-ui/index.html +9 -7
- package/dist/serve/site-ui/llms.txt +13 -3
- package/dist/serve/site-ui/pyric-campfire-full.png +0 -0
- package/dist/serve/site-ui/pyric-campfire.png +0 -0
- package/dist/serve/site-ui/rtdb/index.html +5 -3
- package/dist/serve/site-ui/settings/index.html +5 -3
- package/dist/serve/site-ui/storage/index.html +5 -3
- package/dist/serve/site-ui/studio/index.html +13 -0
- package/dist/serve/site-ui/studio-routes.json +1 -1
- package/dist/serve/site-ui/traffic/index.html +5 -3
- package/dist/serve/studio/disk-workspace.d.ts.map +1 -1
- package/dist/serve/studio/disk-workspace.js +64 -2
- package/dist/serve/studio/disk-workspace.js.map +1 -1
- package/dist/serve/studio/routes.d.ts +9 -0
- package/dist/serve/studio/routes.d.ts.map +1 -1
- package/dist/serve/studio/routes.js +47 -7
- package/dist/serve/studio/routes.js.map +1 -1
- package/dist/serve/vite-ai-config.d.ts.map +1 -1
- package/dist/serve/vite-ai-config.js +38 -8
- package/dist/serve/vite-ai-config.js.map +1 -1
- package/dist/serve/vite-module-swap.d.ts.map +1 -1
- package/dist/serve/vite-module-swap.js +0 -28
- package/dist/serve/vite-module-swap.js.map +1 -1
- package/dist/serve/vite-plugin.d.ts +7 -7
- package/dist/serve/vite-plugin.js +1 -1
- package/dist/serve/vite-plugin.js.map +1 -1
- package/dist/serve/vite-sandbox-generation.d.ts +1 -1
- package/dist/serve/vite-sandbox-generation.d.ts.map +1 -1
- package/dist/serve/vite-sandbox-generation.js +13 -1
- package/dist/serve/vite-sandbox-generation.js.map +1 -1
- package/dist/serve/worker/client/auth.js +1 -2
- package/dist/serve/worker/client/auth.js.map +1 -1
- package/dist/serve/worker/client/core.d.ts +21 -1
- package/dist/serve/worker/client/core.d.ts.map +1 -1
- package/dist/serve/worker/client/core.js +79 -3
- package/dist/serve/worker/client/core.js.map +1 -1
- package/dist/serve/worker/client/disconnect.js +1 -1
- package/dist/serve/worker/client/disconnect.js.map +1 -1
- package/dist/serve/worker/client/presence.d.ts.map +1 -1
- package/dist/serve/worker/client/presence.js +18 -5
- package/dist/serve/worker/client/presence.js.map +1 -1
- package/dist/serve/worker/client.d.ts +1 -1
- package/dist/serve/worker/client.d.ts.map +1 -1
- package/dist/serve/worker/client.js +1 -1
- package/dist/serve/worker/client.js.map +1 -1
- package/dist/serve/worker/context-resolver.d.ts +30 -0
- package/dist/serve/worker/context-resolver.d.ts.map +1 -0
- package/dist/serve/worker/context-resolver.js +44 -0
- package/dist/serve/worker/context-resolver.js.map +1 -0
- package/dist/serve/worker/entry.js +23 -19
- package/dist/serve/worker/entry.js.map +1 -1
- package/dist/serve/worker/host/core.d.ts +1 -0
- package/dist/serve/worker/host/core.d.ts.map +1 -1
- package/dist/serve/worker/host/core.js +12 -6
- package/dist/serve/worker/host/core.js.map +1 -1
- package/dist/serve/worker/host/storage.js +2 -2
- package/dist/serve/worker/host-ai.d.ts.map +1 -1
- package/dist/serve/worker/host-ai.js +15 -0
- package/dist/serve/worker/host-ai.js.map +1 -1
- package/dist/serve/worker/host-auth.js +1 -2
- package/dist/serve/worker/host-auth.js.map +1 -1
- package/dist/serve/worker/index.d.ts +1 -1
- package/dist/serve/worker/index.d.ts.map +1 -1
- package/dist/serve/worker/index.js +2 -2
- package/dist/serve/worker/index.js.map +1 -1
- package/dist/serve/worker/port-lifecycle.d.ts +18 -0
- package/dist/serve/worker/port-lifecycle.d.ts.map +1 -0
- package/dist/serve/worker/port-lifecycle.js +28 -0
- package/dist/serve/worker/port-lifecycle.js.map +1 -0
- package/dist/serve/worker/protocol.d.ts +4 -0
- package/dist/serve/worker/protocol.d.ts.map +1 -1
- package/dist/serve/worker/protocol.js.map +1 -1
- package/dist/serve/worker/serve-init.d.ts.map +1 -1
- package/dist/serve/worker/serve-init.js +15 -3
- package/dist/serve/worker/serve-init.js.map +1 -1
- package/dist/verify/fixture.d.ts +1 -1
- package/package.json +17 -6
- package/src/assurance/.generated/conformance-verdicts.ts +68 -1
- package/src/bridge/client/dispatch.ts +52 -56
- package/src/bridge/client/tool-family-factories.ts +34 -0
- package/src/bridge/server/mcp-contract.ts +15 -59
- package/src/bridge/server/peer.ts +24 -1
- package/src/bridge/server/tool-family-factories.ts +40 -0
- package/src/bridge/server/tool-metadata.ts +29 -61
- package/src/bridge/tool-families.generated.ts +17 -0
- package/src/bridge/tool-families.ts +87 -0
- package/src/bridge/tool-family-records/conformance.ts +6 -0
- package/src/bridge/tool-family-records/firestore-data.ts +15 -0
- package/src/bridge/tool-family-records/firestore-inspect.ts +6 -0
- package/src/bridge/tool-family-records/firestore-rules.ts +15 -0
- package/src/bridge/tool-family-records/firestore-simulator.ts +16 -0
- package/src/bridge/tool-family-records/rtdb-inspection.ts +6 -0
- package/src/bundler/externals.ts +55 -0
- package/src/bundler/index.ts +21 -0
- package/src/cli/cli.test.ts +2 -46
- package/src/cli/index.ts +48 -42
- package/src/cli/inlined-sdk-scanner.ts +77 -0
- package/src/cli/mcp-proxy.ts +4 -4
- package/src/cli/parse-args.ts +61 -15
- package/src/cli/pyric-config.ts +45 -0
- package/src/cli/sandbox-interlock.ts +167 -0
- package/src/cli/{dev-runner.ts → sandbox-runner.ts} +142 -86
- package/src/cli/serve.ts +182 -115
- package/src/cli/snapshot.ts +5 -5
- package/src/cli/unsupported-runtime.ts +68 -0
- package/src/cli/verify.ts +1 -1
- package/src/conformance/.generated/can-i-use-browser.ts +2 -2
- package/src/conformance/.generated/can-i-use.ts +2 -2
- package/src/conformance/.generated/conformance-docs.ts +1 -1
- package/src/functions-rtdb/child.ts +115 -21
- package/src/functions-rtdb/development-runtime.ts +5 -1
- package/src/functions-rtdb/project.ts +8 -8
- package/src/google-endpoints.ts +155 -0
- package/src/next/guard.ts +1 -1
- package/src/next/runtime-env.ts +5 -0
- package/src/next/with-pyric.ts +1 -1
- package/src/register/beacon.ts +232 -0
- package/src/register/index.ts +68 -6
- package/src/register/net-guard.ts +567 -0
- package/src/remote/index.ts +9 -9
- package/src/serve/ai-diagnostics-relay.ts +243 -0
- package/src/serve/ai-proxy.ts +327 -0
- package/src/serve/ai-status.ts +109 -0
- package/src/serve/ai-terminal-blocks.ts +210 -0
- package/src/serve/ai-terminal-text.ts +52 -0
- package/src/serve/beacon-route.ts +141 -0
- package/src/serve/bundler.ts +10 -10
- package/src/serve/entries/active-auth-registry.ts +56 -0
- package/src/serve/entries/active-auth.ts +100 -0
- package/src/serve/entries/ai.ts +27 -1
- package/src/serve/entries/auth-helper-core.ts +5 -0
- package/src/serve/entries/auth-helper-dom.ts +27 -3
- package/src/serve/entries/auth-helper-runtime.ts +1 -1
- package/src/serve/entries/auth.ts +8 -0
- package/src/serve/entries/bridge-url.ts +1 -1
- package/src/serve/entries/firestore.ts +3 -3
- package/src/serve/entries/init.ts +78 -3
- package/src/serve/entries/keepalive.ts +1 -1
- package/src/serve/entries/runtime.ts +29 -14
- package/src/serve/entries/storage.ts +1 -1
- package/src/serve/entries/worker-runtime.ts +1 -1
- package/src/serve/init-payload.ts +4 -0
- package/src/serve/namespace.ts +117 -145
- package/src/serve/rules.ts +47 -22
- package/src/serve/runtime/chip-config.ts +16 -2
- package/src/serve/runtime/chip-dialog.ts +366 -0
- package/src/serve/runtime/chip-install.ts +10 -4
- package/src/serve/runtime/chip-user-search.ts +391 -0
- package/src/serve/runtime/chip.ts +177 -15
- package/src/serve/runtime/identity.ts +26 -0
- package/src/serve/runtime/manifest.ts +1 -1
- package/src/serve/runtime/status.ts +24 -0
- package/src/serve/sandbox-session.ts +19 -4
- package/src/serve/server.ts +18 -1
- package/src/serve/studio/disk-workspace.ts +76 -2
- package/src/serve/studio/routes.ts +63 -7
- package/src/serve/vite-ai-config.ts +37 -7
- package/src/serve/vite-module-swap.ts +0 -28
- package/src/serve/vite-plugin.ts +8 -8
- package/src/serve/vite-sandbox-generation.ts +16 -1
- package/src/serve/worker/client/auth.ts +1 -1
- package/src/serve/worker/client/core.ts +80 -3
- package/src/serve/worker/client/disconnect.ts +1 -1
- package/src/serve/worker/client/presence.ts +25 -5
- package/src/serve/worker/client.ts +1 -1
- package/src/serve/worker/context-resolver.ts +60 -0
- package/src/serve/worker/entry.ts +22 -18
- package/src/serve/worker/host/core.ts +9 -7
- package/src/serve/worker/host/storage.ts +2 -2
- package/src/serve/worker/host-ai.ts +15 -0
- package/src/serve/worker/host-auth.ts +1 -1
- package/src/serve/worker/index.ts +4 -1
- package/src/serve/worker/port-lifecycle.ts +45 -0
- package/src/serve/worker/protocol.ts +2 -1
- package/src/serve/worker/serve-init.ts +14 -3
- package/src/verify/fixture.ts +1 -1
- package/README.md.orig +0 -145
- package/dist/cli/dev-runner.d.ts.map +0 -1
- package/dist/cli/dev-runner.js.map +0 -1
- package/dist/serve/site-ui/_astro/_slug_.CXh9yTP1.css +0 -1
- package/dist/serve/site-ui/_astro/_slug_.CupVK4TM.css +0 -1
- package/dist/serve/site-ui/_astro/_studio_.CfxiQWkk.css +0 -1
- package/dist/serve/site-ui/_astro/app.BsFsMjn0.css +0 -1
- package/dist/serve/site-ui/_astro/app.Ce3MTLQm.js +0 -1
- package/dist/serve/site-ui/_astro/app.taqzyjD0.js +0 -11
- package/dist/serve/site-ui/_astro/sandbox-controls.BP4M5f1M.js +0 -1169
- package/dist/serve/site-ui/_astro/writes.C57paAj2.js +0 -99
- package/dist/serve/site-ui/docs/trust/storage-rules-stdlib-research/index.html +0 -582
- package/dist/serve/site-ui/docs/trust/storage-rules-stdlib-research.md +0 -448
package/src/remote/index.ts
CHANGED
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
* Server-side Node code (ultimately `pyric-admin`'s remote dispatch arm)
|
|
6
6
|
* reaches the ONE sandbox the app + Studio + agent share:
|
|
7
7
|
*
|
|
8
|
-
* Node process
|
|
8
|
+
* Node process -> `pyric sandbox --bridge` -> browser tab
|
|
9
9
|
* (attach/consumer leg) (peer leg)
|
|
10
10
|
* ──MessagePort──> SharedWorker
|
|
11
11
|
*
|
|
@@ -320,7 +320,7 @@ export function createRemoteSandboxCore(
|
|
|
320
320
|
reject(
|
|
321
321
|
remoteError(
|
|
322
322
|
'deadline-exceeded',
|
|
323
|
-
`remote sandbox op timed out after ${opTimeoutMs}ms (op: ${payload.method})
|
|
323
|
+
`remote sandbox op timed out after ${opTimeoutMs}ms (op: ${payload.method}). Is pyric sandbox still running?` +
|
|
324
324
|
// A version-skewed old worker accepts frames it cannot
|
|
325
325
|
// handle and never responds — a timeout is its signature.
|
|
326
326
|
(versionSkewGuidance ? ` ${versionSkewGuidance}` : ''),
|
|
@@ -379,8 +379,8 @@ export function createRemoteSandboxCore(
|
|
|
379
379
|
msg.serveVersion !== cliVersion()
|
|
380
380
|
) {
|
|
381
381
|
versionSkewGuidance =
|
|
382
|
-
`pyric
|
|
383
|
-
`${cliVersion()}
|
|
382
|
+
`pyric sandbox is running version ${msg.serveVersion}, this client is ` +
|
|
383
|
+
`${cliVersion()}. Restart pyric sandbox and reload the browser tab.`;
|
|
384
384
|
process.stderr.write(`pyric: ${versionSkewGuidance}\n`);
|
|
385
385
|
}
|
|
386
386
|
if (msg.peerConnected) readyResolve();
|
|
@@ -405,7 +405,7 @@ export function createRemoteSandboxCore(
|
|
|
405
405
|
// Version skew: a live tab whose SharedWorker predates this op.
|
|
406
406
|
// Other open pages of this origin keep the old worker alive.
|
|
407
407
|
message +=
|
|
408
|
-
'
|
|
408
|
+
'. The running sandbox may predate this feature. Restart pyric sandbox ' +
|
|
409
409
|
'and close other open pages of this origin, then reload.';
|
|
410
410
|
}
|
|
411
411
|
call.reject(remoteError(code, message, msg.error?.denialContext, (msg.error as any)?.envelope));
|
|
@@ -721,7 +721,7 @@ export function createRemoteSandboxHandle(opts: {
|
|
|
721
721
|
enablePersistence: () =>
|
|
722
722
|
throwMember(
|
|
723
723
|
'enablePersistence',
|
|
724
|
-
'the browser worker owns persistence; it is already enabled by `pyric
|
|
724
|
+
'the browser worker owns persistence; it is already enabled by `pyric sandbox`',
|
|
725
725
|
),
|
|
726
726
|
flush: () =>
|
|
727
727
|
throwMember('flush()', 'the browser worker owns persistence and flushes itself'),
|
|
@@ -742,7 +742,7 @@ export function createRemoteSandboxHandle(opts: {
|
|
|
742
742
|
// ─── connect ───────────────────────────────────────────────────────────────
|
|
743
743
|
|
|
744
744
|
/**
|
|
745
|
-
* Discover the running `pyric
|
|
745
|
+
* Discover the running `pyric sandbox --bridge`, attach to its bridge WS as a
|
|
746
746
|
* worker-relay CONSUMER (never a peer — attaching cannot kick the browser
|
|
747
747
|
* tab out of last-connection-wins), and return the typed remote handle.
|
|
748
748
|
*
|
|
@@ -770,7 +770,7 @@ export async function connectRemoteSandbox(
|
|
|
770
770
|
if (!found) {
|
|
771
771
|
throw remoteError(
|
|
772
772
|
'not-found',
|
|
773
|
-
'no running `pyric
|
|
773
|
+
'no running `pyric sandbox --bridge` found (looked for .pyric/serve.json in ' +
|
|
774
774
|
`${cwd} and the default ports) — start your dev server with the bridge enabled and retry.`,
|
|
775
775
|
);
|
|
776
776
|
}
|
|
@@ -953,7 +953,7 @@ export function createLazyRemoteSandbox(
|
|
|
953
953
|
// Before discovery the URL is unknown; op-level errors always carry the
|
|
954
954
|
// real URL (they come from the inner connect), and `serveUrl` is patched
|
|
955
955
|
// to the discovered value as soon as the first connect succeeds.
|
|
956
|
-
serveUrl: options.url?.replace(/\/$/, '') ?? '<pyric
|
|
956
|
+
serveUrl: options.url?.replace(/\/$/, '') ?? '<pyric sandbox url (pending discovery)>',
|
|
957
957
|
close() {
|
|
958
958
|
if (closed) return;
|
|
959
959
|
closed = true;
|
|
@@ -0,0 +1,243 @@
|
|
|
1
|
+
import type { SandboxEvent } from 'pyric/sandbox';
|
|
2
|
+
import { capTerminalText } from './ai-terminal-text.js';
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* AI diagnostics relay: headless dev visibility for the three things the
|
|
6
|
+
* broker does that no app-level signal reports.
|
|
7
|
+
*
|
|
8
|
+
* The broker lands `service_mutation` events (`service: 'ai'`) on the
|
|
9
|
+
* sandbox's unified stream. That stream is browser-side: the worker host fans
|
|
10
|
+
* it out over the port for Studio, and it never reaches the dev server. An
|
|
11
|
+
* agent driving `pyric dev` headlessly therefore saw NOTHING.
|
|
12
|
+
*
|
|
13
|
+
* - `request_rejected`: a bad role, empty `contents`, a missing thought
|
|
14
|
+
* signature, or an engine that answered an error envelope.
|
|
15
|
+
* - `response_blocked`: the SILENT refusal. A safety / recitation /
|
|
16
|
+
* blocklist filter answers HTTP 200 with an empty candidate, so nothing
|
|
17
|
+
* throws anywhere and the app simply renders nothing.
|
|
18
|
+
* - `model_substituted`: not a refusal at all. The answer arrives, from a
|
|
19
|
+
* model the developer never asked for (an openai `modelMap` entry or
|
|
20
|
+
* catch-all `model`, a gemini experimental alias). Same shape of problem:
|
|
21
|
+
* the sandbox quietly did something other than what the code said, and
|
|
22
|
+
* only the terminal can say so.
|
|
23
|
+
*
|
|
24
|
+
* One record per kind in {@link AI_DIAGNOSTIC_RELAYS} pairs the sandbox `op`
|
|
25
|
+
* with the narrower that turns it into a wire payload. The terminal formatter
|
|
26
|
+
* and throttle key for each kind live in `ai-terminal-blocks.ts`, keyed by the
|
|
27
|
+
* same {@link AiDiagnosticKind} union, so the compiler flags a kind that is
|
|
28
|
+
* relayed but never rendered.
|
|
29
|
+
*
|
|
30
|
+
* This is the AI twin of the Firebase Activity Guard reporter next door
|
|
31
|
+
* (`activity-guard.ts`): a browser-safe subscriber shared by the SharedWorker
|
|
32
|
+
* and in-page planes, which fire-and-forget POSTs to the dev server. It rides
|
|
33
|
+
* the DENIAL relay's existing channel (`POST /__pyric/denials`, see the denial
|
|
34
|
+
* relay section of `namespace.ts`) rather than opening a new one: a broker
|
|
35
|
+
* refusal is the same category of thing as a rules denial, it wants the same
|
|
36
|
+
* terminal treatment, and it wants that endpoint's per-(target, reason)
|
|
37
|
+
* throttle, because an agent retry loop re-sends the same request over and
|
|
38
|
+
* over.
|
|
39
|
+
*
|
|
40
|
+
* Best-effort, exactly like the activity reporter and the denial relay: never
|
|
41
|
+
* throws, never awaited into app flow, and a relay failure (dev server gone,
|
|
42
|
+
* no `fetch`, non-pyric host) is swallowed. Diagnostics, not a correctness
|
|
43
|
+
* path.
|
|
44
|
+
*/
|
|
45
|
+
|
|
46
|
+
/** The slice of the sandbox this relay needs. Live events only: replaying
|
|
47
|
+
* `history()` on boot would re-announce refusals the terminal already
|
|
48
|
+
* printed (and the activity feed's reason for taking history does not apply,
|
|
49
|
+
* since nothing here correlates across events). */
|
|
50
|
+
export interface AiDiagnosticsFeed {
|
|
51
|
+
subscribe(listener: (event: SandboxEvent) => void): unknown;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
/** The `kind` discriminator on every payload this relay POSTs. It is also the
|
|
55
|
+
* key of the formatter table in `ai-terminal-blocks.ts`. */
|
|
56
|
+
export type AiDiagnosticKind = 'ai-rejection' | 'ai-blocked' | 'ai-model-substituted';
|
|
57
|
+
|
|
58
|
+
/** The sandbox events this relay reads. */
|
|
59
|
+
type ServiceMutation = Extract<SandboxEvent, { kind: 'service_mutation' }>;
|
|
60
|
+
|
|
61
|
+
/** Shape POSTed for a `request_rejected` event. `namespace.ts` reads it back
|
|
62
|
+
* loosely (`AiRejectionPayload` in `ai-terminal-blocks.ts`): the body is JSON
|
|
63
|
+
* from the served page, and nothing on the far side enforces this type. */
|
|
64
|
+
interface AiRejectionRelayPayload {
|
|
65
|
+
kind: 'ai-rejection';
|
|
66
|
+
/** Model resource the rejected op targeted (the event's `path`). */
|
|
67
|
+
model?: string;
|
|
68
|
+
/** Which engine the broker resolved to (`scripted` | `openai` | `gemini` |
|
|
69
|
+
* `custom`); a rejection reads very differently for each. */
|
|
70
|
+
engine?: string;
|
|
71
|
+
/** Wire error status (e.g. `INVALID_ARGUMENT`) and HTTP-ish code. */
|
|
72
|
+
status?: string;
|
|
73
|
+
code?: number;
|
|
74
|
+
/** The rejection reason: production's own message for shape errors. */
|
|
75
|
+
message: string;
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
/** Shape POSTed for a `response_blocked` event. Carries the wire's own
|
|
79
|
+
* vocabulary, `finishReason` (a candidate was withheld) or `blockReason`
|
|
80
|
+
* (the prompt was refused), rather than a pre-rendered sentence, so the
|
|
81
|
+
* terminal formatter owns the phrasing exactly the way it does for a
|
|
82
|
+
* rejection. */
|
|
83
|
+
interface AiBlockedRelayPayload {
|
|
84
|
+
kind: 'ai-blocked';
|
|
85
|
+
/** Model resource the blocked op targeted (the event's `path`). */
|
|
86
|
+
model?: string;
|
|
87
|
+
/** Which engine the broker resolved to. */
|
|
88
|
+
engine?: string;
|
|
89
|
+
/** Candidate-level block: `SAFETY`, `RECITATION`, `BLOCKLIST`, and so on. */
|
|
90
|
+
finishReason?: string;
|
|
91
|
+
/** Production's own explanatory text for the block, when it sent one. */
|
|
92
|
+
finishMessage?: string;
|
|
93
|
+
/** Prompt-level block: `promptFeedback.blockReason`. */
|
|
94
|
+
blockReason?: string;
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
/** Shape POSTed for a `model_substituted` event. Not a refusal: the answer
|
|
98
|
+
* arrives normally, just from a model the developer never named. */
|
|
99
|
+
interface AiModelSubstitutionRelayPayload {
|
|
100
|
+
kind: 'ai-model-substituted';
|
|
101
|
+
/** The model the request asked for (the event's `path`). */
|
|
102
|
+
requestedModel: string;
|
|
103
|
+
/** The model the engine actually calls (bare, no `models/` prefix). */
|
|
104
|
+
effectiveModel: string;
|
|
105
|
+
/** Which engine substituted (`openai` | `gemini` | ...). */
|
|
106
|
+
engine?: string;
|
|
107
|
+
/** Why it differs, e.g. `engine modelMap`, `experimental alias`. */
|
|
108
|
+
reason?: string;
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
type AiDiagnosticRelayPayload =
|
|
112
|
+
| AiRejectionRelayPayload
|
|
113
|
+
| AiBlockedRelayPayload
|
|
114
|
+
| AiModelSubstitutionRelayPayload;
|
|
115
|
+
|
|
116
|
+
/**
|
|
117
|
+
* A string field off the event's free-form `detail` record, or `undefined`
|
|
118
|
+
* when the emitter did not set it. `detail` is a public contract anyone can
|
|
119
|
+
* post onto, so every field is re-checked, and every value is length-capped
|
|
120
|
+
* here rather than on the far side: a model server that answers with a
|
|
121
|
+
* megabyte in its `finishMessage` must not push a megabyte through the
|
|
122
|
+
* fire-and-forget POST just to have the terminal cut it.
|
|
123
|
+
*/
|
|
124
|
+
function detailText(detail: Record<string, unknown>, field: string): string | undefined {
|
|
125
|
+
const value = detail[field];
|
|
126
|
+
if (typeof value !== 'string') return undefined;
|
|
127
|
+
return capTerminalText(value);
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
/** The event's `path` when it names a model, or `undefined`. */
|
|
131
|
+
function eventModel(event: ServiceMutation): string | undefined {
|
|
132
|
+
if (typeof event.path !== 'string' || event.path === '') return undefined;
|
|
133
|
+
return capTerminalText(event.path);
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
function detailOf(event: ServiceMutation): Record<string, unknown> {
|
|
137
|
+
return (event.detail ?? {}) as Record<string, unknown>;
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
/** Narrow a `request_rejected` event to its relay payload. */
|
|
141
|
+
function toRejectionPayload(event: ServiceMutation): AiRejectionRelayPayload | null {
|
|
142
|
+
const detail = detailOf(event);
|
|
143
|
+
const payload: AiRejectionRelayPayload = {
|
|
144
|
+
kind: 'ai-rejection',
|
|
145
|
+
message: detailText(detail, 'message') ?? 'request rejected',
|
|
146
|
+
};
|
|
147
|
+
payload.model = eventModel(event);
|
|
148
|
+
payload.engine = detailText(detail, 'engine');
|
|
149
|
+
payload.status = detailText(detail, 'status');
|
|
150
|
+
if (typeof detail.code === 'number') payload.code = detail.code;
|
|
151
|
+
return payload;
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
/** Narrow a `response_blocked` event to its relay payload. */
|
|
155
|
+
function toBlockedPayload(event: ServiceMutation): AiBlockedRelayPayload | null {
|
|
156
|
+
const detail = detailOf(event);
|
|
157
|
+
const payload: AiBlockedRelayPayload = { kind: 'ai-blocked' };
|
|
158
|
+
payload.model = eventModel(event);
|
|
159
|
+
payload.engine = detailText(detail, 'engine');
|
|
160
|
+
payload.finishReason = detailText(detail, 'finishReason');
|
|
161
|
+
payload.finishMessage = detailText(detail, 'finishMessage');
|
|
162
|
+
payload.blockReason = detailText(detail, 'blockReason');
|
|
163
|
+
return payload;
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
/**
|
|
167
|
+
* Narrow a `model_substituted` event to its relay payload, and enforce the
|
|
168
|
+
* invariant the terminal line depends on: an effective model equal to the
|
|
169
|
+
* requested one is NOT a substitution and never relays. The broker already
|
|
170
|
+
* compares before emitting; this re-checks because the event stream is a
|
|
171
|
+
* public contract anyone can post onto.
|
|
172
|
+
*/
|
|
173
|
+
function toModelSubstitutionPayload(
|
|
174
|
+
event: ServiceMutation,
|
|
175
|
+
): AiModelSubstitutionRelayPayload | null {
|
|
176
|
+
const detail = detailOf(event);
|
|
177
|
+
const requestedModel = detailText(detail, 'requestedModel') ?? eventModel(event) ?? '';
|
|
178
|
+
const effectiveModel = detailText(detail, 'effectiveModel') ?? '';
|
|
179
|
+
if (requestedModel === '' || effectiveModel === '') return null;
|
|
180
|
+
let requestedBare = requestedModel;
|
|
181
|
+
if (requestedModel.startsWith('models/')) requestedBare = requestedModel.slice('models/'.length);
|
|
182
|
+
if (effectiveModel === requestedBare || effectiveModel === requestedModel) return null;
|
|
183
|
+
const payload: AiModelSubstitutionRelayPayload = {
|
|
184
|
+
kind: 'ai-model-substituted',
|
|
185
|
+
requestedModel,
|
|
186
|
+
effectiveModel,
|
|
187
|
+
};
|
|
188
|
+
payload.engine = detailText(detail, 'engine');
|
|
189
|
+
payload.reason = detailText(detail, 'reason');
|
|
190
|
+
return payload;
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
/** One record per relayed kind: the sandbox `op` that produces it, and the
|
|
194
|
+
* narrower that turns that event into a wire payload. */
|
|
195
|
+
interface AiDiagnosticRelay {
|
|
196
|
+
op: string;
|
|
197
|
+
fromEvent(event: ServiceMutation): AiDiagnosticRelayPayload | null;
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
const AI_DIAGNOSTIC_RELAYS: { [K in AiDiagnosticKind]: AiDiagnosticRelay } = {
|
|
201
|
+
'ai-rejection': { op: 'request_rejected', fromEvent: toRejectionPayload },
|
|
202
|
+
'ai-blocked': { op: 'response_blocked', fromEvent: toBlockedPayload },
|
|
203
|
+
'ai-model-substituted': { op: 'model_substituted', fromEvent: toModelSubstitutionPayload },
|
|
204
|
+
};
|
|
205
|
+
|
|
206
|
+
/** The relay record for a sandbox `op`, or `null` for an op nothing relays. */
|
|
207
|
+
function relayForOp(op: string): AiDiagnosticRelay | null {
|
|
208
|
+
for (const relay of Object.values(AI_DIAGNOSTIC_RELAYS)) {
|
|
209
|
+
if (relay.op === op) return relay;
|
|
210
|
+
}
|
|
211
|
+
return null;
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
/**
|
|
215
|
+
* Subscribe to the sandbox event stream and relay every broker refusal,
|
|
216
|
+
* rejected request or blocked response, plus every silent model substitution,
|
|
217
|
+
* to the dev server for terminal visibility.
|
|
218
|
+
*
|
|
219
|
+
* Browser-safe (shared by the SharedWorker host and the in-page runtime);
|
|
220
|
+
* returns nothing to unsubscribe with, mirroring the guard next door, since
|
|
221
|
+
* the subscription is observational and lives for the plane's lifetime.
|
|
222
|
+
*/
|
|
223
|
+
export function setupAiDiagnosticsRelay(feed: AiDiagnosticsFeed, fetchFn: typeof fetch): void {
|
|
224
|
+
feed.subscribe((event) => {
|
|
225
|
+
if (event.kind !== 'service_mutation') return;
|
|
226
|
+
if (event.service !== 'ai') return;
|
|
227
|
+
const relay = relayForOp(event.op);
|
|
228
|
+
if (relay === null) return;
|
|
229
|
+
const payload = relay.fromEvent(event);
|
|
230
|
+
if (payload === null) return;
|
|
231
|
+
try {
|
|
232
|
+
void fetchFn('/__pyric/denials', {
|
|
233
|
+
method: 'POST',
|
|
234
|
+
headers: { 'content-type': 'application/json' },
|
|
235
|
+
body: JSON.stringify(payload),
|
|
236
|
+
}).catch(() => {
|
|
237
|
+
/* Diagnostics are best-effort and must never affect app behavior. */
|
|
238
|
+
});
|
|
239
|
+
} catch {
|
|
240
|
+
/* An injected or unavailable fetch must never affect app behavior. */
|
|
241
|
+
}
|
|
242
|
+
});
|
|
243
|
+
}
|
|
@@ -0,0 +1,327 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `/__pyric/ai-proxy`: the same-origin passthrough to an OpenAI-compatible
|
|
3
|
+
* upstream, plus the terminal diagnostics that make an upstream failure
|
|
4
|
+
* visible to a developer with no browser console open.
|
|
5
|
+
*
|
|
6
|
+
* The route is always mounted; it only touches the network when a request
|
|
7
|
+
* arrives. Everything about WHERE it forwards is resolved by
|
|
8
|
+
* {@link resolveAiProxyUpstream}, which the startup banner (`ai-status.ts`)
|
|
9
|
+
* shares so the two can never disagree.
|
|
10
|
+
*/
|
|
11
|
+
import type { IncomingMessage, ServerResponse } from 'node:http';
|
|
12
|
+
import type { ServeLogger } from './server.js';
|
|
13
|
+
import { redactUrl } from 'pyric/ai/internal';
|
|
14
|
+
import { sanitizeForTerminal } from './ai-terminal-text.js';
|
|
15
|
+
|
|
16
|
+
/** The dev server's diagnostics throttle, as this module needs it: the
|
|
17
|
+
* denials route owns the instance, and the proxy shares it so a failing
|
|
18
|
+
* upstream cannot out-shout a denied listener. */
|
|
19
|
+
export interface AiDiagnosticThrottle {
|
|
20
|
+
shouldPrint(key: string, now: number): boolean;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
/** Default upstream: local Ollama's OpenAI-compatible endpoint. */
|
|
24
|
+
export const AI_PROXY_DEFAULT_UPSTREAM = 'http://localhost:11434/v1';
|
|
25
|
+
|
|
26
|
+
/** The route the dev server mounts for OpenAI-compatible traffic. */
|
|
27
|
+
export const AI_PROXY_ROUTE = '/__pyric/ai-proxy';
|
|
28
|
+
|
|
29
|
+
/**
|
|
30
|
+
* Request headers that must NOT be forwarded upstream: origin-sensitive
|
|
31
|
+
* browser context (`origin`/`referer`/`cookie`, since the upstream is a
|
|
32
|
+
* different origin and must never see the page's), hop-by-hop headers, and
|
|
33
|
+
* the two the proxy re-derives (`host`, `content-length`). `accept-encoding`
|
|
34
|
+
* is dropped so fetch negotiates its own compression and the streamed body
|
|
35
|
+
* needs no length fixups.
|
|
36
|
+
*/
|
|
37
|
+
const AI_PROXY_STRIPPED_HEADERS = new Set([
|
|
38
|
+
'host',
|
|
39
|
+
'origin',
|
|
40
|
+
'referer',
|
|
41
|
+
'cookie',
|
|
42
|
+
'connection',
|
|
43
|
+
'content-length',
|
|
44
|
+
'accept-encoding',
|
|
45
|
+
'keep-alive',
|
|
46
|
+
'proxy-authorization',
|
|
47
|
+
'te',
|
|
48
|
+
'trailer',
|
|
49
|
+
'transfer-encoding',
|
|
50
|
+
'upgrade',
|
|
51
|
+
]);
|
|
52
|
+
|
|
53
|
+
/** The upstream-failure shapes the proxy surfaces to the terminal. */
|
|
54
|
+
type AiProxyFailure =
|
|
55
|
+
| { kind: 'unreachable'; target: string; latencyMs: number; cause: string }
|
|
56
|
+
| {
|
|
57
|
+
kind: 'status';
|
|
58
|
+
target: string;
|
|
59
|
+
latencyMs: number;
|
|
60
|
+
status: number;
|
|
61
|
+
/** The upstream's raw `Retry-After` header, when it sent one. */
|
|
62
|
+
retryAfter?: string;
|
|
63
|
+
}
|
|
64
|
+
| { kind: 'stream-abort'; target: string; latencyMs: number; cause: string };
|
|
65
|
+
|
|
66
|
+
/** Longest `Retry-After` value echoed to the terminal (delta-seconds and
|
|
67
|
+
* HTTP-dates are both far shorter; anything longer is upstream noise). */
|
|
68
|
+
const AI_PROXY_RETRY_AFTER_MAX = 64;
|
|
69
|
+
|
|
70
|
+
/**
|
|
71
|
+
* Render a `Retry-After` value for the terminal, or `null` when there is
|
|
72
|
+
* nothing worth printing.
|
|
73
|
+
*
|
|
74
|
+
* The header is remote-authored, so it is sanitized and length-capped like
|
|
75
|
+
* every other upstream string that reaches the terminal. Delta-seconds (the
|
|
76
|
+
* common form) get an `s` suffix so the wait reads as a duration; the
|
|
77
|
+
* HTTP-date form is echoed verbatim, since `Wed, 21 Oct 2026 07:28:00 GMTs`
|
|
78
|
+
* would be nonsense.
|
|
79
|
+
*/
|
|
80
|
+
function formatRetryAfter(raw: string): string | null {
|
|
81
|
+
const clean = sanitizeForTerminal(raw).slice(0, AI_PROXY_RETRY_AFTER_MAX).trim();
|
|
82
|
+
if (clean === '') return null;
|
|
83
|
+
if (/^\d+$/.test(clean)) return `${clean}s`;
|
|
84
|
+
return clean;
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
/**
|
|
88
|
+
* Mask credentials, then flatten and cap, in that order. Both the target and
|
|
89
|
+
* a raw fetch error message are remote-influenced: the URL carries whatever
|
|
90
|
+
* the page put in the query string, and an error message can quote it back.
|
|
91
|
+
*/
|
|
92
|
+
function safeUpstreamText(text: string): string {
|
|
93
|
+
return sanitizeForTerminal(redactUrl(text));
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
/**
|
|
97
|
+
* The throttle key for one upstream failure. A failing upstream fails on
|
|
98
|
+
* every request, so the key is the failure itself (kind, status, and the
|
|
99
|
+
* redacted target) rather than anything per-request: one line per window,
|
|
100
|
+
* exactly like a denied listener that re-fires.
|
|
101
|
+
*/
|
|
102
|
+
function aiProxyThrottleKey(failure: AiProxyFailure): string {
|
|
103
|
+
const target = redactUrl(failure.target);
|
|
104
|
+
if (failure.kind === 'status') return `ai-proxy status ${failure.status} ${target}`;
|
|
105
|
+
return `ai-proxy ${failure.kind} ${target}`;
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
/** Print one upstream failure, unless the throttle already printed it. */
|
|
109
|
+
function noteAiProxyFailure(
|
|
110
|
+
failure: AiProxyFailure,
|
|
111
|
+
throttle: AiDiagnosticThrottle,
|
|
112
|
+
logger?: ServeLogger,
|
|
113
|
+
): void {
|
|
114
|
+
if (logger === undefined) return;
|
|
115
|
+
if (!throttle.shouldPrint(aiProxyThrottleKey(failure), Date.now())) return;
|
|
116
|
+
logger.note(formatAiProxyWarning(failure));
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
/**
|
|
120
|
+
* Format an upstream failure into the compact terminal block, the same
|
|
121
|
+
* ` [pyric] ...` idiom the denial relay prints, so ai-proxy trouble reads
|
|
122
|
+
* like every other dev-server diagnostic. Two lines: what went wrong, then
|
|
123
|
+
* the (redacted) upstream URL with the elapsed time. Exported for unit tests.
|
|
124
|
+
*
|
|
125
|
+
* A 429 says so in words (`rate limited`): the bare number reads as one more
|
|
126
|
+
* failed request, when it actually means the upstream is shedding load or the
|
|
127
|
+
* key is out of quota. When the upstream sent a `Retry-After`, a third line
|
|
128
|
+
* prints the wait it asked for, and states that NOTHING here waits on the
|
|
129
|
+
* developer's behalf. The proxy has no retry loop (the status rides straight
|
|
130
|
+
* through to the caller), so the backoff is the app's decision, not pyric's.
|
|
131
|
+
*/
|
|
132
|
+
export function formatAiProxyWarning(failure: AiProxyFailure): string {
|
|
133
|
+
const target = safeUpstreamText(failure.target);
|
|
134
|
+
let headline: string;
|
|
135
|
+
if (failure.kind === 'unreachable') {
|
|
136
|
+
headline = `upstream unreachable: ${safeUpstreamText(failure.cause)}`;
|
|
137
|
+
} else if (failure.kind === 'status') {
|
|
138
|
+
const rateLimited = failure.status === 429;
|
|
139
|
+
headline = `upstream returned ${failure.status}${rateLimited ? ' (rate limited / quota exhausted)' : ''}`;
|
|
140
|
+
} else {
|
|
141
|
+
headline = `upstream stream aborted mid-response: ${safeUpstreamText(failure.cause)}`;
|
|
142
|
+
}
|
|
143
|
+
const lines = [
|
|
144
|
+
` ⚠ [pyric] ai-proxy: ${headline}`,
|
|
145
|
+
` POST ${target} (${failure.latencyMs}ms)`,
|
|
146
|
+
];
|
|
147
|
+
if (failure.kind === 'unreachable') {
|
|
148
|
+
lines.push(
|
|
149
|
+
` set PYRIC_AI_PROXY_UPSTREAM to an OpenAI-compatible base URL (default ${AI_PROXY_DEFAULT_UPSTREAM})`,
|
|
150
|
+
);
|
|
151
|
+
}
|
|
152
|
+
if (failure.kind === 'status' && failure.retryAfter !== undefined) {
|
|
153
|
+
const wait = formatRetryAfter(failure.retryAfter);
|
|
154
|
+
if (wait !== null) {
|
|
155
|
+
lines.push(` Retry-After: ${wait}, no automatic retry; the status went to the caller`);
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
return lines.join('\n');
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
/**
|
|
162
|
+
* Resolve the upstream `/__pyric/ai-proxy` forwards to, and say WHERE that
|
|
163
|
+
* value came from. One resolution shared by the proxy handler (which needs
|
|
164
|
+
* the target) and the startup line (which needs the provenance, so a
|
|
165
|
+
* developer can tell a deliberate `PYRIC_AI_PROXY_UPSTREAM` from the Ollama
|
|
166
|
+
* default nobody chose).
|
|
167
|
+
*/
|
|
168
|
+
export function resolveAiProxyUpstream(
|
|
169
|
+
configured: string | undefined,
|
|
170
|
+
): { target: string; source: 'option' | 'env' | 'default' } {
|
|
171
|
+
const envUpstream = process.env.PYRIC_AI_PROXY_UPSTREAM;
|
|
172
|
+
let raw = AI_PROXY_DEFAULT_UPSTREAM;
|
|
173
|
+
let source: 'option' | 'env' | 'default' = 'default';
|
|
174
|
+
if (configured !== undefined) {
|
|
175
|
+
raw = configured;
|
|
176
|
+
source = 'option';
|
|
177
|
+
} else if (envUpstream !== undefined) {
|
|
178
|
+
raw = envUpstream;
|
|
179
|
+
source = 'env';
|
|
180
|
+
}
|
|
181
|
+
return { target: raw.replace(/\/$/, ''), source };
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
/**
|
|
185
|
+
* Handle `POST /__pyric/ai-proxy/<suffix>`: a same-origin passthrough to the
|
|
186
|
+
* configured OpenAI-compatible upstream, so the browser openai engine
|
|
187
|
+
* (running in the served page or the SharedWorker host) reaches a localhost
|
|
188
|
+
* upstream like Ollama with ZERO CORS setup (no `OLLAMA_ORIGINS`, ever).
|
|
189
|
+
*
|
|
190
|
+
* Behavior:
|
|
191
|
+
* - POST only (the OpenAI chat-completions surface is POST); 405 otherwise.
|
|
192
|
+
* - the path suffix + query ride through verbatim
|
|
193
|
+
* (`/__pyric/ai-proxy/chat/completions` becomes `<upstream>/chat/completions`).
|
|
194
|
+
* - the request body is forwarded verbatim; headers minus the
|
|
195
|
+
* origin-sensitive/hop-by-hop set above (`authorization` DOES forward,
|
|
196
|
+
* since upstreams may require a key).
|
|
197
|
+
* - the response body is STREAMED through chunk-by-chunk. SSE passthrough
|
|
198
|
+
* must never buffer, or `stream: true` completions would arrive all at
|
|
199
|
+
* once at the end.
|
|
200
|
+
* - an unreachable upstream answers 502 with a plain-text explanation.
|
|
201
|
+
* - every upstream failure (unreachable/timeout, non-2xx status, mid-stream
|
|
202
|
+
* abort) ALSO prints a structured warning on the dev server's terminal
|
|
203
|
+
* logger; a stopped Ollama used to fail in total silence. Diagnostics
|
|
204
|
+
* only: nothing here changes what the caller receives. No logger wired
|
|
205
|
+
* means dropped silently (same opt-out contract as the denial relay).
|
|
206
|
+
*/
|
|
207
|
+
export async function handleAiProxy(
|
|
208
|
+
configuredUpstream: string | undefined,
|
|
209
|
+
throttle: AiDiagnosticThrottle,
|
|
210
|
+
req: IncomingMessage,
|
|
211
|
+
res: ServerResponse,
|
|
212
|
+
url: URL,
|
|
213
|
+
logger?: ServeLogger,
|
|
214
|
+
): Promise<void> {
|
|
215
|
+
if (req.method !== 'POST') {
|
|
216
|
+
res.writeHead(405, { allow: 'POST' }).end('method not allowed');
|
|
217
|
+
return;
|
|
218
|
+
}
|
|
219
|
+
const upstreamBase = resolveAiProxyUpstream(configuredUpstream).target;
|
|
220
|
+
const suffix = url.pathname.slice(AI_PROXY_ROUTE.length);
|
|
221
|
+
const target = `${upstreamBase}${suffix}${url.search}`;
|
|
222
|
+
|
|
223
|
+
// Buffer the REQUEST body (small JSON payloads); the RESPONSE streams.
|
|
224
|
+
const raw = await new Promise<Buffer>((resolve, reject) => {
|
|
225
|
+
const chunks: Buffer[] = [];
|
|
226
|
+
req.on('data', (c: Buffer) => chunks.push(c));
|
|
227
|
+
req.on('end', () => resolve(Buffer.concat(chunks)));
|
|
228
|
+
req.on('error', reject);
|
|
229
|
+
});
|
|
230
|
+
// Copy into a plain-ArrayBuffer view; fetch's BodyInit typing rejects
|
|
231
|
+
// Buffer's ArrayBufferLike backing.
|
|
232
|
+
const body = new Uint8Array(raw.byteLength);
|
|
233
|
+
body.set(raw);
|
|
234
|
+
|
|
235
|
+
const headers: Record<string, string> = {};
|
|
236
|
+
for (const [key, value] of Object.entries(req.headers)) {
|
|
237
|
+
if (value === undefined) continue;
|
|
238
|
+
if (AI_PROXY_STRIPPED_HEADERS.has(key.toLowerCase())) continue;
|
|
239
|
+
headers[key] = Array.isArray(value) ? value.join(', ') : value;
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
const startedAt = Date.now();
|
|
243
|
+
let upstream: Response;
|
|
244
|
+
try {
|
|
245
|
+
upstream = await fetch(target, { method: 'POST', headers, body });
|
|
246
|
+
} catch (e) {
|
|
247
|
+
const cause = e instanceof Error ? e.message : String(e);
|
|
248
|
+
noteAiProxyFailure(
|
|
249
|
+
{ kind: 'unreachable', target, latencyMs: Date.now() - startedAt, cause },
|
|
250
|
+
throttle,
|
|
251
|
+
logger,
|
|
252
|
+
);
|
|
253
|
+
// The 502 body quotes the same two strings the terminal block does, so it
|
|
254
|
+
// gets the same masking: the target carries the page's own query string,
|
|
255
|
+
// and a fetch error routinely echoes the URL back.
|
|
256
|
+
res.writeHead(502, { 'content-type': 'text/plain' });
|
|
257
|
+
res.end(
|
|
258
|
+
`pyric dev ai-proxy: upstream ${redactUrl(target)} unreachable: ${redactUrl(cause)}\n` +
|
|
259
|
+
'Set PYRIC_AI_PROXY_UPSTREAM to an OpenAI-compatible base URL ' +
|
|
260
|
+
`(default ${AI_PROXY_DEFAULT_UPSTREAM}).`,
|
|
261
|
+
);
|
|
262
|
+
return;
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
// A refusal from a reachable upstream (bad model, missing key, rate limit)
|
|
266
|
+
// is just as invisible to a headless developer as a dead socket: the
|
|
267
|
+
// status rides through to the caller untouched, and is ALSO announced here.
|
|
268
|
+
// `Retry-After` rides along when the upstream sent one (429s and 503s carry
|
|
269
|
+
// it): it is the only place the requested backoff appears, since nothing in
|
|
270
|
+
// this path retries or sleeps.
|
|
271
|
+
if (!upstream.ok) {
|
|
272
|
+
const retryAfter = upstream.headers.get('retry-after');
|
|
273
|
+
const failure: AiProxyFailure = {
|
|
274
|
+
kind: 'status',
|
|
275
|
+
target,
|
|
276
|
+
latencyMs: Date.now() - startedAt,
|
|
277
|
+
status: upstream.status,
|
|
278
|
+
};
|
|
279
|
+
if (retryAfter !== null) failure.retryAfter = retryAfter;
|
|
280
|
+
noteAiProxyFailure(failure, throttle, logger);
|
|
281
|
+
}
|
|
282
|
+
|
|
283
|
+
const responseHeaders: Record<string, string> = { 'cache-control': 'no-store' };
|
|
284
|
+
const contentType = upstream.headers.get('content-type');
|
|
285
|
+
if (contentType) responseHeaders['content-type'] = contentType;
|
|
286
|
+
res.writeHead(upstream.status, responseHeaders);
|
|
287
|
+
if (!upstream.body) {
|
|
288
|
+
res.end();
|
|
289
|
+
return;
|
|
290
|
+
}
|
|
291
|
+
(res as ServerResponse & { flushHeaders?: () => void }).flushHeaders?.();
|
|
292
|
+
|
|
293
|
+
// Chunk-by-chunk passthrough. A dropped client cancels the upstream read
|
|
294
|
+
// so an abandoned SSE stream doesn't keep the upstream generating.
|
|
295
|
+
const reader = upstream.body.getReader();
|
|
296
|
+
let clientGone = false;
|
|
297
|
+
res.on('close', () => {
|
|
298
|
+
clientGone = true;
|
|
299
|
+
void reader.cancel().catch(() => {});
|
|
300
|
+
});
|
|
301
|
+
try {
|
|
302
|
+
for (;;) {
|
|
303
|
+
const { done, value } = await reader.read();
|
|
304
|
+
if (done) break;
|
|
305
|
+
res.write(value);
|
|
306
|
+
}
|
|
307
|
+
} catch (e) {
|
|
308
|
+
// Nothing to salvage for the caller: the response is already committed
|
|
309
|
+
// with the upstream's status, so a truncated body is all it gets. But a
|
|
310
|
+
// half-delivered completion is exactly the failure that used to vanish,
|
|
311
|
+
// so announce it, unless the CLIENT is the one who walked away (a closed
|
|
312
|
+
// tab cancelling an SSE stream is normal, not a fault worth a warning).
|
|
313
|
+
if (!clientGone) {
|
|
314
|
+
noteAiProxyFailure(
|
|
315
|
+
{
|
|
316
|
+
kind: 'stream-abort',
|
|
317
|
+
target,
|
|
318
|
+
latencyMs: Date.now() - startedAt,
|
|
319
|
+
cause: e instanceof Error ? e.message : String(e),
|
|
320
|
+
},
|
|
321
|
+
throttle,
|
|
322
|
+
logger,
|
|
323
|
+
);
|
|
324
|
+
}
|
|
325
|
+
}
|
|
326
|
+
res.end();
|
|
327
|
+
}
|