@pyric/cli 0.1.0-alpha.30 → 0.1.0-alpha.30.2
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/assurance/.generated/conformance-verdicts.d.ts +8 -0
- package/dist/assurance/.generated/conformance-verdicts.d.ts.map +1 -1
- package/dist/assurance/.generated/conformance-verdicts.js +8 -0
- package/dist/assurance/.generated/conformance-verdicts.js.map +1 -1
- package/dist/bridge/server/peer.d.ts +10 -0
- package/dist/bridge/server/peer.d.ts.map +1 -1
- package/dist/bridge/server/peer.js +46 -1
- package/dist/bridge/server/peer.js.map +1 -1
- package/dist/cli/serve.d.ts +2 -0
- package/dist/cli/serve.d.ts.map +1 -1
- package/dist/cli/serve.js +13 -2
- package/dist/cli/serve.js.map +1 -1
- package/dist/conformance/.generated/can-i-use-browser.js +2 -2
- 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 +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/register/app-bridge-admin-app-check.d.ts +3 -0
- package/dist/register/app-bridge-admin-app-check.d.ts.map +1 -0
- package/dist/register/app-bridge-admin-app-check.js +43 -0
- package/dist/register/app-bridge-admin-app-check.js.map +1 -0
- package/dist/register/app-bridge-app-check.d.ts +4 -0
- package/dist/register/app-bridge-app-check.d.ts.map +1 -1
- package/dist/register/app-bridge-app-check.js +4 -0
- package/dist/register/app-bridge-app-check.js.map +1 -1
- package/dist/register/app-bridge-shadow.d.ts +4 -2
- package/dist/register/app-bridge-shadow.d.ts.map +1 -1
- package/dist/register/app-bridge-shadow.js +29 -5
- package/dist/register/app-bridge-shadow.js.map +1 -1
- package/dist/register/index.d.ts.map +1 -1
- package/dist/register/index.js +21 -0
- package/dist/register/index.js.map +1 -1
- package/dist/serve/ai-diagnostics-relay.d.ts +4 -1
- package/dist/serve/ai-diagnostics-relay.d.ts.map +1 -1
- package/dist/serve/ai-diagnostics-relay.js +3 -0
- package/dist/serve/ai-diagnostics-relay.js.map +1 -1
- package/dist/serve/ai-proxy.d.ts +21 -1
- package/dist/serve/ai-proxy.d.ts.map +1 -1
- package/dist/serve/ai-proxy.js +39 -14
- package/dist/serve/ai-proxy.js.map +1 -1
- package/dist/serve/ai-terminal-blocks.d.ts +6 -4
- package/dist/serve/ai-terminal-blocks.d.ts.map +1 -1
- package/dist/serve/ai-terminal-blocks.js +7 -4
- package/dist/serve/ai-terminal-blocks.js.map +1 -1
- package/dist/serve/ai-terminal-text.d.ts +8 -0
- package/dist/serve/ai-terminal-text.d.ts.map +1 -1
- package/dist/serve/ai-terminal-text.js +10 -0
- package/dist/serve/ai-terminal-text.js.map +1 -1
- package/dist/serve/bundler.d.ts +40 -0
- package/dist/serve/bundler.d.ts.map +1 -1
- package/dist/serve/bundler.js +133 -2
- package/dist/serve/bundler.js.map +1 -1
- package/dist/serve/entries/ai.d.ts.map +1 -1
- package/dist/serve/entries/ai.js +28 -26
- package/dist/serve/entries/ai.js.map +1 -1
- package/dist/serve/entries/app-ai-passthrough.d.ts +4 -4
- package/dist/serve/entries/app-ai-passthrough.d.ts.map +1 -1
- package/dist/serve/entries/app-ai-passthrough.js +8 -52
- package/dist/serve/entries/app-ai-passthrough.js.map +1 -1
- package/dist/serve/firebase-module-swap.d.ts +22 -0
- package/dist/serve/firebase-module-swap.d.ts.map +1 -1
- package/dist/serve/firebase-module-swap.js +51 -3
- package/dist/serve/firebase-module-swap.js.map +1 -1
- package/dist/serve/gemini-error-redaction.d.ts +6 -0
- package/dist/serve/gemini-error-redaction.d.ts.map +1 -0
- package/dist/serve/gemini-error-redaction.js +61 -0
- package/dist/serve/gemini-error-redaction.js.map +1 -0
- package/dist/serve/gemini-proxy.d.ts +40 -0
- package/dist/serve/gemini-proxy.d.ts.map +1 -0
- package/dist/serve/gemini-proxy.js +105 -0
- package/dist/serve/gemini-proxy.js.map +1 -0
- package/dist/serve/hosted/method-protocol.d.ts +4 -4
- package/dist/serve/html-injection.d.ts +2 -0
- package/dist/serve/html-injection.d.ts.map +1 -1
- package/dist/serve/html-injection.js +4 -2
- package/dist/serve/html-injection.js.map +1 -1
- package/dist/serve/namespace.d.ts +9 -0
- package/dist/serve/namespace.d.ts.map +1 -1
- package/dist/serve/namespace.js +71 -15
- package/dist/serve/namespace.js.map +1 -1
- package/dist/serve/sandbox-session.d.ts +3 -0
- package/dist/serve/sandbox-session.d.ts.map +1 -1
- package/dist/serve/sandbox-session.js +1 -0
- package/dist/serve/sandbox-session.js.map +1 -1
- package/dist/serve/site-ui/docs/ai-compat/index.html +29 -10
- package/dist/serve/site-ui/docs/ai-compat.md +30 -9
- package/dist/serve/site-ui/docs/app-check-compat/index.html +42 -18
- package/dist/serve/site-ui/docs/app-check-compat.md +42 -18
- package/dist/serve/site-ui/docs/auth-compat/index.html +4 -4
- package/dist/serve/site-ui/docs/auth-compat.md +4 -4
- package/dist/serve/site-ui/docs/build/ai-logic/index.html +21 -5
- package/dist/serve/site-ui/docs/build/ai-logic.md +23 -2
- package/dist/serve/site-ui/docs/database-compat/index.html +4 -4
- package/dist/serve/site-ui/docs/database-compat.md +4 -4
- package/dist/serve/site-ui/docs/firestore-compat/index.html +4 -4
- package/dist/serve/site-ui/docs/firestore-compat.md +4 -4
- package/dist/serve/site-ui/docs/index.json +10 -0
- package/dist/serve/site-ui/docs/messaging-compat/index.html +4 -4
- package/dist/serve/site-ui/docs/messaging-compat.md +4 -4
- package/dist/serve/site-ui/docs/pyric-admin-app-reference-api/index.html +4 -0
- package/dist/serve/site-ui/docs/pyric-admin-app-reference-api.md +5 -0
- package/dist/serve/site-ui/docs/pyric-cli-register-app-bridge-app-check-reference-api/index.html +2 -0
- package/dist/serve/site-ui/docs/pyric-cli-register-app-bridge-app-check-reference-api.md +3 -0
- package/dist/serve/site-ui/docs/storage-compat/index.html +5 -5
- package/dist/serve/site-ui/docs/storage-compat.md +5 -5
- package/dist/serve/site-ui/docs/tutorial/index.html +11 -1
- package/dist/serve/site-ui/docs/tutorial.md +12 -1
- package/dist/serve/site-ui/examples/chess/index.html +1 -1
- package/dist/serve/vite-sandbox-generation.d.ts.map +1 -1
- package/dist/serve/vite-sandbox-generation.js +2 -0
- package/dist/serve/vite-sandbox-generation.js.map +1 -1
- package/dist/serve/worker/ai-proxy-paths.d.ts +32 -0
- package/dist/serve/worker/ai-proxy-paths.d.ts.map +1 -0
- package/dist/serve/worker/ai-proxy-paths.js +20 -0
- package/dist/serve/worker/ai-proxy-paths.js.map +1 -0
- package/dist/serve/worker/client/ai.d.ts +3 -1
- package/dist/serve/worker/client/ai.d.ts.map +1 -1
- package/dist/serve/worker/client/ai.js +3 -0
- package/dist/serve/worker/client/ai.js.map +1 -1
- package/dist/serve/worker/entry.js +2 -0
- package/dist/serve/worker/entry.js.map +1 -1
- package/dist/serve/worker/host-ai.d.ts +4 -5
- package/dist/serve/worker/host-ai.d.ts.map +1 -1
- package/dist/serve/worker/host-ai.js +19 -23
- package/dist/serve/worker/host-ai.js.map +1 -1
- package/dist/serve/worker/host-context.d.ts +13 -4
- package/dist/serve/worker/host-context.d.ts.map +1 -1
- package/dist/serve/worker/host-context.js.map +1 -1
- package/dist/serve/worker/inbound-validation/operation-arguments.d.ts.map +1 -1
- package/dist/serve/worker/inbound-validation/operation-arguments.js +11 -0
- package/dist/serve/worker/inbound-validation/operation-arguments.js.map +1 -1
- package/dist/serve/worker/protocol.d.ts +13 -0
- package/dist/serve/worker/protocol.d.ts.map +1 -1
- package/dist/serve/worker/protocol.js.map +1 -1
- package/package.json +4 -4
- package/src/assurance/.generated/conformance-verdicts.ts +8 -0
- package/src/bridge/server/peer.ts +49 -1
- package/src/cli/serve.ts +14 -2
- package/src/conformance/.generated/can-i-use-browser.ts +2 -2
- package/src/conformance/.generated/can-i-use.ts +1 -1
- package/src/conformance/.generated/conformance-docs.ts +1 -1
- package/src/register/app-bridge-admin-app-check.ts +46 -0
- package/src/register/app-bridge-app-check.ts +4 -0
- package/src/register/app-bridge-shadow.ts +35 -5
- package/src/register/index.ts +23 -0
- package/src/serve/ai-diagnostics-relay.ts +6 -1
- package/src/serve/ai-proxy.ts +43 -14
- package/src/serve/ai-terminal-blocks.ts +8 -4
- package/src/serve/ai-terminal-text.ts +11 -0
- package/src/serve/bundler.ts +163 -2
- package/src/serve/entries/ai.ts +28 -27
- package/src/serve/entries/app-ai-passthrough.ts +8 -58
- package/src/serve/firebase-module-swap.ts +52 -3
- package/src/serve/gemini-error-redaction.ts +60 -0
- package/src/serve/gemini-proxy.ts +140 -0
- package/src/serve/html-injection.ts +4 -2
- package/src/serve/namespace.ts +74 -15
- package/src/serve/sandbox-session.ts +4 -0
- package/src/serve/vite-sandbox-generation.ts +2 -0
- package/src/serve/worker/ai-proxy-paths.ts +35 -0
- package/src/serve/worker/client/ai.ts +6 -1
- package/src/serve/worker/entry.ts +2 -0
- package/src/serve/worker/host-ai.ts +23 -23
- package/src/serve/worker/host-context.ts +13 -3
- package/src/serve/worker/inbound-validation/operation-arguments.ts +11 -0
- package/src/serve/worker/protocol.ts +14 -2
|
@@ -374,12 +374,12 @@ html{scrollbar-gutter:stable}
|
|
|
374
374
|
<div class="compat-list">
|
|
375
375
|
<details class="compat-row" data-status="diverged">
|
|
376
376
|
<summary class="compat-line"><span class="compat-dot" data-status="diverged" role="img" aria-label="Diverged (documented)" title="Diverged (documented)"></span><span class="compat-main"><code class="compat-api">getAI(app) in a registered server process</code><span class="compat-sub"><span class="compat-behavior">Under a remote register activation, <code>getAI(app)</code> in a server process fails with <code>failed-precondition</code> and a message that names the client services that do reach the Node host: <code>firebase/ai</code> has no path to the host, and no in-process sandbox stands in for it. Production AI pass-through (<code>PYRIC_AI=production</code>) loads the real <code>firebase/ai</code> and is unaffected.</span></span></span></summary>
|
|
377
|
-
<div class="compat-evidence"><div class="compat-probe">App scenario <code>test/apps/client-sdk-under-register</code> calls it under <code>node --import @pyric/cli/register</code> against a hosted Vite dev server and asserts the refusal; on main (942cb175) it returned a handle on a private in-process sandbox. <a href="https://github.com/davideast/pyric/blob/main/packages/cli/test/register/mapping.test.ts#
|
|
377
|
+
<div class="compat-evidence"><div class="compat-probe">App scenario <code>test/apps/client-sdk-under-register</code> calls it under <code>node --import @pyric/cli/register</code> against a hosted Vite dev server and asserts the refusal; on main (942cb175) it returned a handle on a private in-process sandbox. <a href="https://github.com/davideast/pyric/blob/main/packages/cli/test/register/mapping.test.ts#L80" target="_blank" rel="noopener noreferrer"><code>mapping.test.ts</code></a> pins that the subpath keeps its mirror under a remote activation, and <a href="https://github.com/davideast/pyric/blob/main/packages/cli/test/register/node-origin.test.ts#L2" target="_blank" rel="noopener noreferrer"><code>node-origin.test.ts</code></a> that a mirror resolved from such an app refuses. (Structured evidence: <a href="https://github.com/davideast/pyric/blob/main/test/apps/client-sdk-under-register/scenario.ts#L1" target="_blank" rel="noopener noreferrer"><code>scenario.ts</code></a>)</div>
|
|
378
378
|
<div class="compat-note">refused in a server process; production runs it in Node</div></div>
|
|
379
379
|
</details>
|
|
380
380
|
<details class="compat-row" data-status="diverged">
|
|
381
381
|
<summary class="compat-line"><span class="compat-dot" data-status="diverged" role="img" aria-label="Diverged (documented)" title="Diverged (documented)"></span><span class="compat-main"><code class="compat-api">getAI(app) in a server-rendered module under vite dev</code><span class="compat-sub"><span class="compat-behavior">Under <code>vite dev</code>, <code>getAI(app)</code> in a server-rendered module fails with <code>failed-precondition</code> in the default sandbox AI mode, naming the client services that reach the host: <code>firebase/ai</code> has no path to the dev server's host from server code. In production AI mode the real <code>firebase/ai</code> loads and the network guard permits Firebase AI Logic. A SharedWorker dev server refuses it the same way.</span></span></span></summary>
|
|
382
|
-
<div class="compat-evidence"><div class="compat-probe">App scenario <code>test/apps/vite-ssr-client-sdk</code> calls it in SSR on a hosted dev server and asserts the refusal; a second dev server in production AI mode lets the real SDK reach only <code>firebasevertexai.googleapis.com</code>, which a stand-in beneath the guard refuses. On main (ba2c0bf0a) it was the real SDK. <a href="https://github.com/davideast/pyric/blob/main/packages/cli/test/serve/vite-module-swap.test.ts#
|
|
382
|
+
<div class="compat-evidence"><div class="compat-probe">App scenario <code>test/apps/vite-ssr-client-sdk</code> calls it in SSR on a hosted dev server and asserts the refusal; a second dev server in production AI mode lets the real SDK reach only <code>firebasevertexai.googleapis.com</code>, which a stand-in beneath the guard refuses. On main (ba2c0bf0a) it was the real SDK. <a href="https://github.com/davideast/pyric/blob/main/packages/cli/test/serve/vite-module-swap.test.ts#L4" target="_blank" rel="noopener noreferrer"><code>vite-module-swap.test.ts</code></a> pins the routing. (Structured evidence: <a href="https://github.com/davideast/pyric/blob/main/test/apps/vite-ssr-client-sdk/scenario.ts#L1" target="_blank" rel="noopener noreferrer"><code>scenario.ts</code></a>)</div>
|
|
383
383
|
<div class="compat-note">refused in server-rendered modules under vite dev; production runs it in the server's runtime</div></div>
|
|
384
384
|
</details>
|
|
385
385
|
</div>
|
|
@@ -387,23 +387,42 @@ html{scrollbar-gutter:stable}
|
|
|
387
387
|
<div class="compat-list">
|
|
388
388
|
<details class="compat-row" data-status="ok">
|
|
389
389
|
<summary class="compat-line"><span class="compat-dot" data-status="ok" role="img" aria-label="Conforming" title="Conforming"></span><span class="compat-main"><code class="compat-api">initializeAppCheck(app) then getAI(app) in production AI mode</code><span class="compat-sub"><span class="compat-behavior">In production AI mode the app's <code>firebase/app-check</code> is the Firebase SDK attached to the same app <code>firebase/ai</code> uses, so <code>generateContent</code> carries <code>X-Firebase-AppCheck</code> equal to the token <code>getToken</code> returns, and AI Logic answers 200.</span></span></span></summary>
|
|
390
|
-
<div class="compat-evidence"><div class="compat-probe">capture <a href="https://github.com/davideast/pyric/blob/main/packages/conformance/observations/app-check/app-check-client-ai-header.json" target="_blank" rel="noopener noreferrer"><code>app-check-client-ai-header</code></a> (<code>appCheckInitialized</code>), replayed by the AI pass-through suite through the served App Check bridge. On main before the bridge, <code>firebase/app-check</code> was swapped to the deferred <code>pyric/app-check</code> stub in production AI mode, and the real SDK on the Pyric app failed <code>appCheck/use-before-activation</code> because activation and lookup used different app objects. (Structured evidence: <a href="https://github.com/davideast/pyric/blob/main/packages/cli/test/serve/ai-app-check-passthrough.test.ts#L131" target="_blank" rel="noopener noreferrer"><code>ai-app-check-passthrough.test.ts</code></a>, <a href="https://github.com/davideast/pyric/blob/main/test/apps/ai-production-app-check/scenario.ts#
|
|
390
|
+
<div class="compat-evidence"><div class="compat-probe">capture <a href="https://github.com/davideast/pyric/blob/main/packages/conformance/observations/app-check/app-check-client-ai-header.json" target="_blank" rel="noopener noreferrer"><code>app-check-client-ai-header</code></a> (<code>appCheckInitialized</code>), replayed by the AI pass-through suite through the served App Check bridge. On main before the bridge, <code>firebase/app-check</code> was swapped to the deferred <code>pyric/app-check</code> stub in production AI mode, and the real SDK on the Pyric app failed <code>appCheck/use-before-activation</code> because activation and lookup used different app objects. (Structured evidence: <a href="https://github.com/davideast/pyric/blob/main/packages/cli/test/serve/ai-app-check-passthrough.test.ts#L131" target="_blank" rel="noopener noreferrer"><code>ai-app-check-passthrough.test.ts</code></a>, <a href="https://github.com/davideast/pyric/blob/main/test/apps/ai-production-app-check/scenario.ts#L122" target="_blank" rel="noopener noreferrer"><code>scenario.ts</code></a>, <a href="https://github.com/davideast/pyric/blob/main/test/apps/ai-production-import-map/scenario.ts#L112" target="_blank" rel="noopener noreferrer"><code>scenario.ts</code></a>)</div></div>
|
|
391
391
|
</details>
|
|
392
392
|
<details class="compat-row" data-status="ok">
|
|
393
393
|
<summary class="compat-line"><span class="compat-dot" data-status="ok" role="img" aria-label="Conforming" title="Conforming"></span><span class="compat-main"><code class="compat-api">getAI(app, { useLimitedUseAppCheckTokens: true }) in production AI mode</code><span class="compat-sub"><span class="compat-behavior">With <code>useLimitedUseAppCheckTokens</code>, each request carries a fresh limited-use token rather than the cached one, and AI Logic answers 200.</span></span></span></summary>
|
|
394
|
-
<div class="compat-evidence"><div class="compat-probe">capture <a href="https://github.com/davideast/pyric/blob/main/packages/conformance/observations/app-check/app-check-client-ai-header.json" target="_blank" rel="noopener noreferrer"><code>app-check-client-ai-header</code></a> (<code>limitedUseModel</code>). (Structured evidence: <a href="https://github.com/davideast/pyric/blob/main/packages/cli/test/serve/ai-app-check-passthrough.test.ts#L132" target="_blank" rel="noopener noreferrer"><code>ai-app-check-passthrough.test.ts</code></a>, <a href="https://github.com/davideast/pyric/blob/main/test/apps/ai-production-app-check/scenario.ts#
|
|
394
|
+
<div class="compat-evidence"><div class="compat-probe">capture <a href="https://github.com/davideast/pyric/blob/main/packages/conformance/observations/app-check/app-check-client-ai-header.json" target="_blank" rel="noopener noreferrer"><code>app-check-client-ai-header</code></a> (<code>limitedUseModel</code>). (Structured evidence: <a href="https://github.com/davideast/pyric/blob/main/packages/cli/test/serve/ai-app-check-passthrough.test.ts#L132" target="_blank" rel="noopener noreferrer"><code>ai-app-check-passthrough.test.ts</code></a>, <a href="https://github.com/davideast/pyric/blob/main/test/apps/ai-production-app-check/scenario.ts#L122" target="_blank" rel="noopener noreferrer"><code>scenario.ts</code></a>, <a href="https://github.com/davideast/pyric/blob/main/test/apps/ai-production-import-map/scenario.ts#L112" target="_blank" rel="noopener noreferrer"><code>scenario.ts</code></a>)</div></div>
|
|
395
395
|
</details>
|
|
396
396
|
<details class="compat-row" data-status="ok">
|
|
397
397
|
<summary class="compat-line"><span class="compat-dot" data-status="ok" role="img" aria-label="Conforming" title="Conforming"></span><span class="compat-main"><code class="compat-api">generateContent without App Check in production AI mode</code><span class="compat-sub"><span class="compat-behavior">On an app that never initialized App Check, <code>firebase/ai</code> sends no <code>X-Firebase-AppCheck</code> header, and AI Logic rejects the request: HTTP 403, <code>PERMISSION_DENIED</code>, message <code>Firebase AI Logic has been deactivated in this project. To resume using Firebase AI Logic, you must enforce Firebase App Check.</code> with a docs link. A malformed header, the SDK placeholder token, and a tampered token get the same envelope; a valid token answers 200. Seen with App Check enforcement for AI Logic reading UNENFORCED.</span></span></span></summary>
|
|
398
|
-
<div class="compat-evidence"><div class="compat-probe">captures <a href="https://github.com/davideast/pyric/blob/main/packages/conformance/observations/app-check/app-check-ai-gate-wire.json" target="_blank" rel="noopener noreferrer"><code>app-check-ai-gate-wire</code></a> (all five cases on the wire) and <a href="https://github.com/davideast/pyric/blob/main/packages/conformance/observations/app-check/app-check-client-ai-header.json" target="_blank" rel="noopener noreferrer"><code>app-check-client-ai-header</code></a> (<code>noAppCheck</code>). (Structured evidence: <a href="https://github.com/davideast/pyric/blob/main/packages/cli/test/serve/ai-app-check-passthrough.test.ts#L109" target="_blank" rel="noopener noreferrer"><code>ai-app-check-passthrough.test.ts</code></a>, <a href="https://github.com/davideast/pyric/blob/main/test/apps/ai-production-app-check/scenario.ts#L4" target="_blank" rel="noopener noreferrer"><code>scenario.ts</code></a>)</div></div>
|
|
398
|
+
<div class="compat-evidence"><div class="compat-probe">captures <a href="https://github.com/davideast/pyric/blob/main/packages/conformance/observations/app-check/app-check-ai-gate-wire.json" target="_blank" rel="noopener noreferrer"><code>app-check-ai-gate-wire</code></a> (all five cases on the wire) and <a href="https://github.com/davideast/pyric/blob/main/packages/conformance/observations/app-check/app-check-client-ai-header.json" target="_blank" rel="noopener noreferrer"><code>app-check-client-ai-header</code></a> (<code>noAppCheck</code>). (Structured evidence: <a href="https://github.com/davideast/pyric/blob/main/packages/cli/test/serve/ai-app-check-passthrough.test.ts#L109" target="_blank" rel="noopener noreferrer"><code>ai-app-check-passthrough.test.ts</code></a>, <a href="https://github.com/davideast/pyric/blob/main/test/apps/ai-production-app-check/scenario.ts#L4" target="_blank" rel="noopener noreferrer"><code>scenario.ts</code></a>, <a href="https://github.com/davideast/pyric/blob/main/test/apps/ai-production-import-map/scenario.ts#L9" target="_blank" rel="noopener noreferrer"><code>scenario.ts</code></a>)</div></div>
|
|
399
399
|
</details>
|
|
400
400
|
<details class="compat-row" data-status="ok">
|
|
401
401
|
<summary class="compat-line"><span class="compat-dot" data-status="ok" role="img" aria-label="Conforming" title="Conforming"></span><span class="compat-main"><code class="compat-api">initializeAppCheck(app) after getAI(app)</code><span class="compat-sub"><span class="compat-behavior">Calling <code>initializeAppCheck</code> after <code>getAI(app)</code> built a model does not throw and <code>getToken</code> works, but that model sends no <code>X-Firebase-AppCheck</code> header and AI Logic answers 403. App Check must be initialized before <code>getAI</code>.</span></span></span></summary>
|
|
402
|
-
<div class="compat-evidence"><div class="compat-probe">capture <a href="https://github.com/davideast/pyric/blob/main/packages/conformance/observations/app-check/app-check-client-get-token-before-initialize.json" target="_blank" rel="noopener noreferrer"><code>app-check-client-get-token-before-initialize</code></a> (<code>initializeAfterAi</code>, <code>getTokenAfterLateInitialize</code>, <code>aiRequestAfterLateInitialize</code>, <code>aiGenerationAfterLateInitialize</code>). (Structured evidence: <a href="https://github.com/davideast/pyric/blob/main/packages/cli/test/serve/ai-app-check-passthrough.test.ts#L170" target="_blank" rel="noopener noreferrer"><code>ai-app-check-passthrough.test.ts</code></a>, <a href="https://github.com/davideast/pyric/blob/main/test/apps/ai-production-app-check/scenario.ts#
|
|
402
|
+
<div class="compat-evidence"><div class="compat-probe">capture <a href="https://github.com/davideast/pyric/blob/main/packages/conformance/observations/app-check/app-check-client-get-token-before-initialize.json" target="_blank" rel="noopener noreferrer"><code>app-check-client-get-token-before-initialize</code></a> (<code>initializeAfterAi</code>, <code>getTokenAfterLateInitialize</code>, <code>aiRequestAfterLateInitialize</code>, <code>aiGenerationAfterLateInitialize</code>). (Structured evidence: <a href="https://github.com/davideast/pyric/blob/main/packages/cli/test/serve/ai-app-check-passthrough.test.ts#L170" target="_blank" rel="noopener noreferrer"><code>ai-app-check-passthrough.test.ts</code></a>, <a href="https://github.com/davideast/pyric/blob/main/test/apps/ai-production-app-check/scenario.ts#L122" target="_blank" rel="noopener noreferrer"><code>scenario.ts</code></a>, <a href="https://github.com/davideast/pyric/blob/main/test/apps/ai-production-import-map/scenario.ts#L112" target="_blank" rel="noopener noreferrer"><code>scenario.ts</code></a>)</div></div>
|
|
403
403
|
</details>
|
|
404
404
|
<details class="compat-row" data-status="ok">
|
|
405
405
|
<summary class="compat-line"><span class="compat-dot" data-status="ok" role="img" aria-label="Conforming" title="Conforming"></span><span class="compat-main"><code class="compat-api">generateContent when App Check attestation fails</code><span class="compat-sub"><span class="compat-behavior">When the token exchange is refused, <code>firebase/ai</code> sends the SDK placeholder token (base64 of <code>{"error":"UNKNOWN_ERROR"}</code>) and AI Logic answers 403; with <code>useLimitedUseAppCheckTokens</code> it throws <code>AI/error</code> naming <code>appCheck/fetch-status-error</code> before sending any request.</span></span></span></summary>
|
|
406
|
-
<div class="compat-evidence"><div class="compat-probe">capture <a href="https://github.com/davideast/pyric/blob/main/packages/conformance/observations/app-check/app-check-client-invalid-debug-token.json" target="_blank" rel="noopener noreferrer"><code>app-check-client-invalid-debug-token</code></a> (<code>aiRequest</code>, <code>aiGeneration</code>, <code>aiLimitedUseRequest</code>, <code>aiLimitedUseGeneration</code>). (Structured evidence: <a href="https://github.com/davideast/pyric/blob/main/packages/cli/test/serve/ai-app-check-passthrough.test.ts#L189" target="_blank" rel="noopener noreferrer"><code>ai-app-check-passthrough.test.ts</code></a>, <a href="https://github.com/davideast/pyric/blob/main/test/apps/ai-production-app-check/scenario.ts#L3" target="_blank" rel="noopener noreferrer"><code>scenario.ts</code></a>)</div></div>
|
|
406
|
+
<div class="compat-evidence"><div class="compat-probe">capture <a href="https://github.com/davideast/pyric/blob/main/packages/conformance/observations/app-check/app-check-client-invalid-debug-token.json" target="_blank" rel="noopener noreferrer"><code>app-check-client-invalid-debug-token</code></a> (<code>aiRequest</code>, <code>aiGeneration</code>, <code>aiLimitedUseRequest</code>, <code>aiLimitedUseGeneration</code>). (Structured evidence: <a href="https://github.com/davideast/pyric/blob/main/packages/cli/test/serve/ai-app-check-passthrough.test.ts#L189" target="_blank" rel="noopener noreferrer"><code>ai-app-check-passthrough.test.ts</code></a>, <a href="https://github.com/davideast/pyric/blob/main/test/apps/ai-production-app-check/scenario.ts#L3" target="_blank" rel="noopener noreferrer"><code>scenario.ts</code></a>, <a href="https://github.com/davideast/pyric/blob/main/test/apps/ai-production-import-map/scenario.ts#L4" target="_blank" rel="noopener noreferrer"><code>scenario.ts</code></a>)</div></div>
|
|
407
|
+
</details>
|
|
408
|
+
</div>
|
|
409
|
+
<h2 id="app-check-in-sandbox-ai-mode">App Check in sandbox AI mode</h2>
|
|
410
|
+
<div class="compat-list">
|
|
411
|
+
<details class="compat-row" data-status="ok">
|
|
412
|
+
<summary class="compat-line"><span class="compat-dot" data-status="ok" role="img" aria-label="Conforming" title="Conforming"></span><span class="compat-main"><code class="compat-api">generateContent from getAI(app) in sandbox AI mode</code><span class="compat-sub"><span class="compat-behavior">An app that never initialized App Check gets the <code>AIError</code> the SDK throws for AI Logic's 403: code <code>fetch-error</code>, <code>customErrorData.status</code> 403, message <code>[403 Forbidden] Firebase AI Logic has been deactivated in this project. ...</code> with the docs link. The broker refuses a missing, malformed, placeholder, tampered or expired token with production's envelope before any engine runs, and answers a sandbox token, including one minted under the debug provider. Pyric's guidance goes to the console once per app and to the dev server terminal, beside the error.</span></span></span></summary>
|
|
413
|
+
<div class="compat-evidence"><div class="compat-probe">captures <a href="https://github.com/davideast/pyric/blob/main/packages/conformance/observations/app-check/app-check-ai-gate-wire.json" target="_blank" rel="noopener noreferrer"><code>app-check-ai-gate-wire</code></a> (<code>noHeader</code>, <code>malformedHeader</code>, <code>sdkPlaceholderToken</code>, <code>tamperedSignature</code>, <code>validAdminMintedToken</code>) and <a href="https://github.com/davideast/pyric/blob/main/packages/conformance/observations/app-check/app-check-client-ai-header.json" target="_blank" rel="noopener noreferrer"><code>app-check-client-ai-header</code></a> (<code>noAppCheck</code>), replayed by the gate suite in process and over the worker port; the app scenario drives the in-page, SharedWorker, Node host and registered Node process planes. On main before #1291 the sandbox answered tokenless requests. (Structured evidence: <a href="https://github.com/davideast/pyric/blob/main/packages/pyric/test/ai/app-check-gate.test.ts#L8" target="_blank" rel="noopener noreferrer"><code>app-check-gate.test.ts</code></a>, <a href="https://github.com/davideast/pyric/blob/main/packages/cli/test/serve/worker/ai-ops.test.ts#L10" target="_blank" rel="noopener noreferrer"><code>ai-ops.test.ts</code></a>, <a href="https://github.com/davideast/pyric/blob/main/test/apps/sandbox-app-check/scenario.ts#L2" target="_blank" rel="noopener noreferrer"><code>scenario.ts</code></a>)</div></div>
|
|
414
|
+
</details>
|
|
415
|
+
<details class="compat-row" data-status="ok">
|
|
416
|
+
<summary class="compat-line"><span class="compat-dot" data-status="ok" role="img" aria-label="Conforming" title="Conforming"></span><span class="compat-main"><code class="compat-api">initializeAppCheck(app) after getAI(app) in sandbox AI mode</code><span class="compat-sub"><span class="compat-behavior"><code>getAI(app)</code> reads App Check when it builds the handle. <code>initializeAppCheck</code> after it does not throw and <code>getToken</code> works, but that handle's requests carry no token and are refused with the 403.</span></span></span></summary>
|
|
417
|
+
<div class="compat-evidence"><div class="compat-probe">capture <a href="https://github.com/davideast/pyric/blob/main/packages/conformance/observations/app-check/app-check-client-get-token-before-initialize.json" target="_blank" rel="noopener noreferrer"><code>app-check-client-get-token-before-initialize</code></a> (<code>initializeAfterAi</code>, <code>getTokenAfterLateInitialize</code>, <code>aiRequestAfterLateInitialize</code>, <code>aiGenerationAfterLateInitialize</code>). (Structured evidence: <a href="https://github.com/davideast/pyric/blob/main/packages/pyric/test/ai/app-check-gate.test.ts#L11" target="_blank" rel="noopener noreferrer"><code>app-check-gate.test.ts</code></a>, <a href="https://github.com/davideast/pyric/blob/main/packages/cli/test/serve/worker/ai-ops.test.ts#L5" target="_blank" rel="noopener noreferrer"><code>ai-ops.test.ts</code></a>, <a href="https://github.com/davideast/pyric/blob/main/test/apps/sandbox-app-check/scenario.ts#L2" target="_blank" rel="noopener noreferrer"><code>scenario.ts</code></a>)</div></div>
|
|
418
|
+
</details>
|
|
419
|
+
<details class="compat-row" data-status="ok">
|
|
420
|
+
<summary class="compat-line"><span class="compat-dot" data-status="ok" role="img" aria-label="Conforming" title="Conforming"></span><span class="compat-main"><code class="compat-api">getAI(app, { useLimitedUseAppCheckTokens: true }) in sandbox AI mode</code><span class="compat-sub"><span class="compat-behavior">Each request carries a fresh limited-use token rather than the cached one, and the sandbox answers.</span></span></span></summary>
|
|
421
|
+
<div class="compat-evidence"><div class="compat-probe">capture <a href="https://github.com/davideast/pyric/blob/main/packages/conformance/observations/app-check/app-check-client-ai-header.json" target="_blank" rel="noopener noreferrer"><code>app-check-client-ai-header</code></a> (<code>limitedUseModel</code>). (Structured evidence: <a href="https://github.com/davideast/pyric/blob/main/packages/pyric/test/ai/app-check-gate.test.ts#L9" target="_blank" rel="noopener noreferrer"><code>app-check-gate.test.ts</code></a>, <a href="https://github.com/davideast/pyric/blob/main/packages/cli/test/serve/worker/ai-ops.test.ts#L5" target="_blank" rel="noopener noreferrer"><code>ai-ops.test.ts</code></a>, <a href="https://github.com/davideast/pyric/blob/main/test/apps/sandbox-app-check/scenario.ts#L11" target="_blank" rel="noopener noreferrer"><code>scenario.ts</code></a>)</div></div>
|
|
422
|
+
</details>
|
|
423
|
+
<details class="compat-row" data-status="ok">
|
|
424
|
+
<summary class="compat-line"><span class="compat-dot" data-status="ok" role="img" aria-label="Conforming" title="Conforming"></span><span class="compat-main"><code class="compat-api">generateContentStream and countTokens without App Check in sandbox AI mode</code><span class="compat-sub"><span class="compat-behavior">Streaming and <code>countTokens</code> are refused with the same 403 <code>AIError</code> as <code>generateContent</code>. In process <code>generateContentStream</code> rejects before any chunk; on the SharedWorker and Node host planes the error reaches the caller through the stream and <code>response</code>, as other request errors do there.</span></span></span></summary>
|
|
425
|
+
<div class="compat-evidence"><div class="compat-probe">AI Logic applies App Check to every request (https://firebase.google.com/docs/ai-logic/app-check); the captures cover only <code>generateContent</code>. Unit-backed by the gate suite and the worker protocol suite. (Structured evidence: <a href="https://github.com/davideast/pyric/blob/main/packages/pyric/test/ai/app-check-gate.test.ts#L193" target="_blank" rel="noopener noreferrer"><code>app-check-gate.test.ts</code></a>, <a href="https://github.com/davideast/pyric/blob/main/packages/cli/test/serve/worker/ai-ops.test.ts#L12" target="_blank" rel="noopener noreferrer"><code>ai-ops.test.ts</code></a>, <a href="https://github.com/davideast/pyric/blob/main/test/apps/sandbox-app-check/scenario.ts#L40" target="_blank" rel="noopener noreferrer"><code>scenario.ts</code></a>)</div></div>
|
|
407
426
|
</details>
|
|
408
427
|
</div>
|
|
409
428
|
<h2 id="current-gaps">Current gaps</h2>
|
|
@@ -423,9 +442,9 @@ html{scrollbar-gutter:stable}
|
|
|
423
442
|
<details class="compat-row" data-status="diverged"><summary class="compat-line"><span class="compat-dot" data-status="diverged" role="img" aria-label="Diverged (documented)" title="Diverged (documented)"></span><span class="compat-main"><code class="compat-api">Schema.enumString()</code><span class="compat-sub"><span class="compat-behavior"><code>Schema.enumString</code> serializes the enum values with type <code>string</code> and format <code>enum</code></span></span></span></summary>
|
|
424
443
|
<div class="compat-evidence"><div class="compat-note"><a href="https://github.com/davideast/pyric/blob/main/packages/pyric/test/ai/helpers-schema.test.ts#L153" target="_blank" rel="noopener noreferrer"><code>unit:helpers-schema.test.ts</code></a> test <code>ai#schema-string-enum</code> (upstream toJSON request shape; GoogleAI accepts only enum and date-time formats)</div></div></details>
|
|
425
444
|
<details class="compat-row" data-status="diverged"><summary class="compat-line"><span class="compat-dot" data-status="diverged" role="img" aria-label="Diverged (documented)" title="Diverged (documented)"></span><span class="compat-main"><code class="compat-api">getAI(app) in a registered server process</code><span class="compat-sub"><span class="compat-behavior">Under a remote register activation, <code>getAI(app)</code> in a server process fails with <code>failed-precondition</code> and a message that names the client services that do reach the Node host: <code>firebase/ai</code> has no path to the host, and no in-process sandbox stands in for it. Production AI pass-through (<code>PYRIC_AI=production</code>) loads the real <code>firebase/ai</code> and is unaffected.</span></span></span></summary>
|
|
426
|
-
<div class="compat-evidence"><div class="compat-note">App scenario <code>test/apps/client-sdk-under-register</code> calls it under <code>node --import @pyric/cli/register</code> against a hosted Vite dev server and asserts the refusal; on main (942cb175) it returned a handle on a private in-process sandbox. <a href="https://github.com/davideast/pyric/blob/main/packages/cli/test/register/mapping.test.ts#
|
|
445
|
+
<div class="compat-evidence"><div class="compat-note">App scenario <code>test/apps/client-sdk-under-register</code> calls it under <code>node --import @pyric/cli/register</code> against a hosted Vite dev server and asserts the refusal; on main (942cb175) it returned a handle on a private in-process sandbox. <a href="https://github.com/davideast/pyric/blob/main/packages/cli/test/register/mapping.test.ts#L80" target="_blank" rel="noopener noreferrer"><code>mapping.test.ts</code></a> pins that the subpath keeps its mirror under a remote activation, and <a href="https://github.com/davideast/pyric/blob/main/packages/cli/test/register/node-origin.test.ts#L2" target="_blank" rel="noopener noreferrer"><code>node-origin.test.ts</code></a> that a mirror resolved from such an app refuses. (Structured evidence: <a href="https://github.com/davideast/pyric/blob/main/test/apps/client-sdk-under-register/scenario.ts#L1" target="_blank" rel="noopener noreferrer"><code>scenario.ts</code></a>)</div></div></details>
|
|
427
446
|
<details class="compat-row" data-status="diverged"><summary class="compat-line"><span class="compat-dot" data-status="diverged" role="img" aria-label="Diverged (documented)" title="Diverged (documented)"></span><span class="compat-main"><code class="compat-api">getAI(app) in a server-rendered module under vite dev</code><span class="compat-sub"><span class="compat-behavior">Under <code>vite dev</code>, <code>getAI(app)</code> in a server-rendered module fails with <code>failed-precondition</code> in the default sandbox AI mode, naming the client services that reach the host: <code>firebase/ai</code> has no path to the dev server's host from server code. In production AI mode the real <code>firebase/ai</code> loads and the network guard permits Firebase AI Logic. A SharedWorker dev server refuses it the same way.</span></span></span></summary>
|
|
428
|
-
<div class="compat-evidence"><div class="compat-note">App scenario <code>test/apps/vite-ssr-client-sdk</code> calls it in SSR on a hosted dev server and asserts the refusal; a second dev server in production AI mode lets the real SDK reach only <code>firebasevertexai.googleapis.com</code>, which a stand-in beneath the guard refuses. On main (ba2c0bf0a) it was the real SDK. <a href="https://github.com/davideast/pyric/blob/main/packages/cli/test/serve/vite-module-swap.test.ts#
|
|
447
|
+
<div class="compat-evidence"><div class="compat-note">App scenario <code>test/apps/vite-ssr-client-sdk</code> calls it in SSR on a hosted dev server and asserts the refusal; a second dev server in production AI mode lets the real SDK reach only <code>firebasevertexai.googleapis.com</code>, which a stand-in beneath the guard refuses. On main (ba2c0bf0a) it was the real SDK. <a href="https://github.com/davideast/pyric/blob/main/packages/cli/test/serve/vite-module-swap.test.ts#L4" target="_blank" rel="noopener noreferrer"><code>vite-module-swap.test.ts</code></a> pins the routing. (Structured evidence: <a href="https://github.com/davideast/pyric/blob/main/test/apps/vite-ssr-client-sdk/scenario.ts#L1" target="_blank" rel="noopener noreferrer"><code>scenario.ts</code></a>)</div></div></details>
|
|
429
448
|
</div>
|
|
430
449
|
<h2 id="reviewed-public-runtime-gaps">Reviewed public-runtime gaps</h2>
|
|
431
450
|
<div class="compat-list">
|
|
@@ -454,4 +473,4 @@ html{scrollbar-gutter:stable}
|
|
|
454
473
|
<div class="compat-evidence"><div class="compat-note"><code>startAudioConversation</code></div>
|
|
455
474
|
<div class="compat-note">disposition:ai.live-api</div></div>
|
|
456
475
|
</details>
|
|
457
|
-
</div> </article> </main> <aside class="doc-toc" aria-label="On this page" data-astro-cid-mw7aashj> <h2 class="doc-toc-heading" data-astro-cid-mw7aashj>On this page</h2> <ul data-astro-cid-mw7aashj> <li data-depth="2" data-astro-cid-mw7aashj> <a href="#status-legend" data-astro-cid-mw7aashj>Status legend</a> </li><li data-depth="2" data-astro-cid-mw7aashj> <a href="#getaitarget-and-dispatch" data-astro-cid-mw7aashj>getAI(target) and dispatch</a> </li><li data-depth="2" data-astro-cid-mw7aashj> <a href="#generativemodelgeneratecontent-envelope" data-astro-cid-mw7aashj>GenerativeModel.generateContent envelope</a> </li><li data-depth="2" data-astro-cid-mw7aashj> <a href="#generatecontentstream-framing-and-aggregation" data-astro-cid-mw7aashj>generateContentStream framing and aggregation</a> </li><li data-depth="2" data-astro-cid-mw7aashj> <a href="#chatsession-history-and-streaming-turns" data-astro-cid-mw7aashj>ChatSession history and streaming turns</a> </li><li data-depth="2" data-astro-cid-mw7aashj> <a href="#function-calling" data-astro-cid-mw7aashj>Function calling</a> </li><li data-depth="2" data-astro-cid-mw7aashj> <a href="#counttokens" data-astro-cid-mw7aashj>countTokens</a> </li><li data-depth="2" data-astro-cid-mw7aashj> <a href="#error-envelopes" data-astro-cid-mw7aashj>Error envelopes</a> </li><li data-depth="2" data-astro-cid-mw7aashj> <a href="#response-helpers-enhancedgeneratecontentresponse" data-astro-cid-mw7aashj>Response helpers (EnhancedGenerateContentResponse)</a> </li><li data-depth="2" data-astro-cid-mw7aashj> <a href="#schema-builders" data-astro-cid-mw7aashj>Schema builders</a> </li><li data-depth="2" data-astro-cid-mw7aashj> <a href="#sandbox-answer-engine-scripted" data-astro-cid-mw7aashj>Sandbox answer engine: scripted</a> </li><li data-depth="2" data-astro-cid-mw7aashj> <a href="#sandbox-answer-engine-openai-translation" data-astro-cid-mw7aashj>Sandbox answer engine: openai translation</a> </li><li data-depth="2" data-astro-cid-mw7aashj> <a href="#client-sdks-in-a-registered-server-process" data-astro-cid-mw7aashj>Client SDKs in a registered server process</a> </li><li data-depth="2" data-astro-cid-mw7aashj> <a href="#app-check-on-the-ai-path" data-astro-cid-mw7aashj>App Check on the AI path</a> </li><li data-depth="2" data-astro-cid-mw7aashj> <a href="#current-gaps" data-astro-cid-mw7aashj>Current gaps</a> </li><li data-depth="3" data-astro-cid-mw7aashj> <a href="#documented-divergences" data-astro-cid-mw7aashj>Documented divergences</a> </li><li data-depth="2" data-astro-cid-mw7aashj> <a href="#reviewed-public-runtime-gaps" data-astro-cid-mw7aashj>Reviewed public-runtime gaps</a> </li> </ul> </aside> </div> <script type="module">const a=document.querySelector("[data-api-symbol-filter]"),d=[...document.querySelectorAll("[data-api-symbol-group]")],n=document.querySelector("[data-api-symbol-empty]");a?.addEventListener("input",()=>{const t=a.value.trim().toLowerCase();let o=0;for(const i of d){let e=0;i.querySelectorAll("[data-api-symbol]").forEach(l=>{const r=!t||(l.dataset.apiSymbol??"").includes(t);l.hidden=!r,r&&e++}),i.hidden=e===0,o+=e}n&&(n.hidden=o>0)});</script> <script type="module">const s=new Map;document.querySelectorAll('.doc-toc a[href^="#"]').forEach(t=>s.set(decodeURIComponent(t.hash.slice(1)),t));if(s.size>0){const t=new Set,n=()=>{let o=null;for(const e of s.keys())if(t.has(e)){o=e;break}s.forEach((e,c)=>e.classList.toggle("active",c===o))},r=new IntersectionObserver(o=>{for(const e of o)e.isIntersecting?t.add(e.target.id):t.delete(e.target.id);n()},{rootMargin:"-72px 0px -60% 0px"});s.forEach((o,e)=>{const c=document.getElementById(e);c&&r.observe(c)})}</script> <script type="module">document.querySelector('.studio__nav [aria-current="page"]')?.scrollIntoView({block:"nearest",inline:"center"});</script> </body></html>
|
|
476
|
+
</div> </article> </main> <aside class="doc-toc" aria-label="On this page" data-astro-cid-mw7aashj> <h2 class="doc-toc-heading" data-astro-cid-mw7aashj>On this page</h2> <ul data-astro-cid-mw7aashj> <li data-depth="2" data-astro-cid-mw7aashj> <a href="#status-legend" data-astro-cid-mw7aashj>Status legend</a> </li><li data-depth="2" data-astro-cid-mw7aashj> <a href="#getaitarget-and-dispatch" data-astro-cid-mw7aashj>getAI(target) and dispatch</a> </li><li data-depth="2" data-astro-cid-mw7aashj> <a href="#generativemodelgeneratecontent-envelope" data-astro-cid-mw7aashj>GenerativeModel.generateContent envelope</a> </li><li data-depth="2" data-astro-cid-mw7aashj> <a href="#generatecontentstream-framing-and-aggregation" data-astro-cid-mw7aashj>generateContentStream framing and aggregation</a> </li><li data-depth="2" data-astro-cid-mw7aashj> <a href="#chatsession-history-and-streaming-turns" data-astro-cid-mw7aashj>ChatSession history and streaming turns</a> </li><li data-depth="2" data-astro-cid-mw7aashj> <a href="#function-calling" data-astro-cid-mw7aashj>Function calling</a> </li><li data-depth="2" data-astro-cid-mw7aashj> <a href="#counttokens" data-astro-cid-mw7aashj>countTokens</a> </li><li data-depth="2" data-astro-cid-mw7aashj> <a href="#error-envelopes" data-astro-cid-mw7aashj>Error envelopes</a> </li><li data-depth="2" data-astro-cid-mw7aashj> <a href="#response-helpers-enhancedgeneratecontentresponse" data-astro-cid-mw7aashj>Response helpers (EnhancedGenerateContentResponse)</a> </li><li data-depth="2" data-astro-cid-mw7aashj> <a href="#schema-builders" data-astro-cid-mw7aashj>Schema builders</a> </li><li data-depth="2" data-astro-cid-mw7aashj> <a href="#sandbox-answer-engine-scripted" data-astro-cid-mw7aashj>Sandbox answer engine: scripted</a> </li><li data-depth="2" data-astro-cid-mw7aashj> <a href="#sandbox-answer-engine-openai-translation" data-astro-cid-mw7aashj>Sandbox answer engine: openai translation</a> </li><li data-depth="2" data-astro-cid-mw7aashj> <a href="#client-sdks-in-a-registered-server-process" data-astro-cid-mw7aashj>Client SDKs in a registered server process</a> </li><li data-depth="2" data-astro-cid-mw7aashj> <a href="#app-check-on-the-ai-path" data-astro-cid-mw7aashj>App Check on the AI path</a> </li><li data-depth="2" data-astro-cid-mw7aashj> <a href="#app-check-in-sandbox-ai-mode" data-astro-cid-mw7aashj>App Check in sandbox AI mode</a> </li><li data-depth="2" data-astro-cid-mw7aashj> <a href="#current-gaps" data-astro-cid-mw7aashj>Current gaps</a> </li><li data-depth="3" data-astro-cid-mw7aashj> <a href="#documented-divergences" data-astro-cid-mw7aashj>Documented divergences</a> </li><li data-depth="2" data-astro-cid-mw7aashj> <a href="#reviewed-public-runtime-gaps" data-astro-cid-mw7aashj>Reviewed public-runtime gaps</a> </li> </ul> </aside> </div> <script type="module">const a=document.querySelector("[data-api-symbol-filter]"),d=[...document.querySelectorAll("[data-api-symbol-group]")],n=document.querySelector("[data-api-symbol-empty]");a?.addEventListener("input",()=>{const t=a.value.trim().toLowerCase();let o=0;for(const i of d){let e=0;i.querySelectorAll("[data-api-symbol]").forEach(l=>{const r=!t||(l.dataset.apiSymbol??"").includes(t);l.hidden=!r,r&&e++}),i.hidden=e===0,o+=e}n&&(n.hidden=o>0)});</script> <script type="module">const s=new Map;document.querySelectorAll('.doc-toc a[href^="#"]').forEach(t=>s.set(decodeURIComponent(t.hash.slice(1)),t));if(s.size>0){const t=new Set,n=()=>{let o=null;for(const e of s.keys())if(t.has(e)){o=e;break}s.forEach((e,c)=>e.classList.toggle("active",c===o))},r=new IntersectionObserver(o=>{for(const e of o)e.isIntersecting?t.add(e.target.id):t.delete(e.target.id);n()},{rootMargin:"-72px 0px -60% 0px"});s.forEach((o,e)=>{const c=document.getElementById(e);c&&r.observe(c)})}</script> <script type="module">document.querySelector('.studio__nav [aria-current="page"]')?.scrollIntoView({block:"nearest",inline:"center"});</script> </body></html>
|
|
@@ -399,12 +399,12 @@
|
|
|
399
399
|
<div class="compat-list">
|
|
400
400
|
<details class="compat-row" data-status="diverged">
|
|
401
401
|
<summary class="compat-line"><span class="compat-dot" data-status="diverged" role="img" aria-label="Diverged (documented)" title="Diverged (documented)"></span><span class="compat-main"><code class="compat-api">getAI(app) in a registered server process</code><span class="compat-sub"><span class="compat-behavior">Under a remote register activation, <code>getAI(app)</code> in a server process fails with <code>failed-precondition</code> and a message that names the client services that do reach the Node host: <code>firebase/ai</code> has no path to the host, and no in-process sandbox stands in for it. Production AI pass-through (<code>PYRIC_AI=production</code>) loads the real <code>firebase/ai</code> and is unaffected.</span></span></span></summary>
|
|
402
|
-
<div class="compat-evidence"><div class="compat-probe">App scenario <code>test/apps/client-sdk-under-register</code> calls it under <code>node --import @pyric/cli/register</code> against a hosted Vite dev server and asserts the refusal; on main (942cb175) it returned a handle on a private in-process sandbox. <a href="https://github.com/davideast/pyric/blob/main/packages/cli/test/register/mapping.test.ts#
|
|
402
|
+
<div class="compat-evidence"><div class="compat-probe">App scenario <code>test/apps/client-sdk-under-register</code> calls it under <code>node --import @pyric/cli/register</code> against a hosted Vite dev server and asserts the refusal; on main (942cb175) it returned a handle on a private in-process sandbox. <a href="https://github.com/davideast/pyric/blob/main/packages/cli/test/register/mapping.test.ts#L80" target="_blank" rel="noopener noreferrer"><code>mapping.test.ts</code></a> pins that the subpath keeps its mirror under a remote activation, and <a href="https://github.com/davideast/pyric/blob/main/packages/cli/test/register/node-origin.test.ts#L2" target="_blank" rel="noopener noreferrer"><code>node-origin.test.ts</code></a> that a mirror resolved from such an app refuses. (Structured evidence: <a href="https://github.com/davideast/pyric/blob/main/test/apps/client-sdk-under-register/scenario.ts#L1" target="_blank" rel="noopener noreferrer"><code>scenario.ts</code></a>)</div>
|
|
403
403
|
<div class="compat-note">refused in a server process; production runs it in Node</div></div>
|
|
404
404
|
</details>
|
|
405
405
|
<details class="compat-row" data-status="diverged">
|
|
406
406
|
<summary class="compat-line"><span class="compat-dot" data-status="diverged" role="img" aria-label="Diverged (documented)" title="Diverged (documented)"></span><span class="compat-main"><code class="compat-api">getAI(app) in a server-rendered module under vite dev</code><span class="compat-sub"><span class="compat-behavior">Under <code>vite dev</code>, <code>getAI(app)</code> in a server-rendered module fails with <code>failed-precondition</code> in the default sandbox AI mode, naming the client services that reach the host: <code>firebase/ai</code> has no path to the dev server's host from server code. In production AI mode the real <code>firebase/ai</code> loads and the network guard permits Firebase AI Logic. A SharedWorker dev server refuses it the same way.</span></span></span></summary>
|
|
407
|
-
<div class="compat-evidence"><div class="compat-probe">App scenario <code>test/apps/vite-ssr-client-sdk</code> calls it in SSR on a hosted dev server and asserts the refusal; a second dev server in production AI mode lets the real SDK reach only <code>firebasevertexai.googleapis.com</code>, which a stand-in beneath the guard refuses. On main (ba2c0bf0a) it was the real SDK. <a href="https://github.com/davideast/pyric/blob/main/packages/cli/test/serve/vite-module-swap.test.ts#
|
|
407
|
+
<div class="compat-evidence"><div class="compat-probe">App scenario <code>test/apps/vite-ssr-client-sdk</code> calls it in SSR on a hosted dev server and asserts the refusal; a second dev server in production AI mode lets the real SDK reach only <code>firebasevertexai.googleapis.com</code>, which a stand-in beneath the guard refuses. On main (ba2c0bf0a) it was the real SDK. <a href="https://github.com/davideast/pyric/blob/main/packages/cli/test/serve/vite-module-swap.test.ts#L4" target="_blank" rel="noopener noreferrer"><code>vite-module-swap.test.ts</code></a> pins the routing. (Structured evidence: <a href="https://github.com/davideast/pyric/blob/main/test/apps/vite-ssr-client-sdk/scenario.ts#L1" target="_blank" rel="noopener noreferrer"><code>scenario.ts</code></a>)</div>
|
|
408
408
|
<div class="compat-note">refused in server-rendered modules under vite dev; production runs it in the server's runtime</div></div>
|
|
409
409
|
</details>
|
|
410
410
|
</div>
|
|
@@ -414,23 +414,44 @@
|
|
|
414
414
|
<div class="compat-list">
|
|
415
415
|
<details class="compat-row" data-status="ok">
|
|
416
416
|
<summary class="compat-line"><span class="compat-dot" data-status="ok" role="img" aria-label="Conforming" title="Conforming"></span><span class="compat-main"><code class="compat-api">initializeAppCheck(app) then getAI(app) in production AI mode</code><span class="compat-sub"><span class="compat-behavior">In production AI mode the app's <code>firebase/app-check</code> is the Firebase SDK attached to the same app <code>firebase/ai</code> uses, so <code>generateContent</code> carries <code>X-Firebase-AppCheck</code> equal to the token <code>getToken</code> returns, and AI Logic answers 200.</span></span></span></summary>
|
|
417
|
-
<div class="compat-evidence"><div class="compat-probe">capture <a href="https://github.com/davideast/pyric/blob/main/packages/conformance/observations/app-check/app-check-client-ai-header.json" target="_blank" rel="noopener noreferrer"><code>app-check-client-ai-header</code></a> (<code>appCheckInitialized</code>), replayed by the AI pass-through suite through the served App Check bridge. On main before the bridge, <code>firebase/app-check</code> was swapped to the deferred <code>pyric/app-check</code> stub in production AI mode, and the real SDK on the Pyric app failed <code>appCheck/use-before-activation</code> because activation and lookup used different app objects. (Structured evidence: <a href="https://github.com/davideast/pyric/blob/main/packages/cli/test/serve/ai-app-check-passthrough.test.ts#L131" target="_blank" rel="noopener noreferrer"><code>ai-app-check-passthrough.test.ts</code></a>, <a href="https://github.com/davideast/pyric/blob/main/test/apps/ai-production-app-check/scenario.ts#
|
|
417
|
+
<div class="compat-evidence"><div class="compat-probe">capture <a href="https://github.com/davideast/pyric/blob/main/packages/conformance/observations/app-check/app-check-client-ai-header.json" target="_blank" rel="noopener noreferrer"><code>app-check-client-ai-header</code></a> (<code>appCheckInitialized</code>), replayed by the AI pass-through suite through the served App Check bridge. On main before the bridge, <code>firebase/app-check</code> was swapped to the deferred <code>pyric/app-check</code> stub in production AI mode, and the real SDK on the Pyric app failed <code>appCheck/use-before-activation</code> because activation and lookup used different app objects. (Structured evidence: <a href="https://github.com/davideast/pyric/blob/main/packages/cli/test/serve/ai-app-check-passthrough.test.ts#L131" target="_blank" rel="noopener noreferrer"><code>ai-app-check-passthrough.test.ts</code></a>, <a href="https://github.com/davideast/pyric/blob/main/test/apps/ai-production-app-check/scenario.ts#L122" target="_blank" rel="noopener noreferrer"><code>scenario.ts</code></a>, <a href="https://github.com/davideast/pyric/blob/main/test/apps/ai-production-import-map/scenario.ts#L112" target="_blank" rel="noopener noreferrer"><code>scenario.ts</code></a>)</div></div>
|
|
418
418
|
</details>
|
|
419
419
|
<details class="compat-row" data-status="ok">
|
|
420
420
|
<summary class="compat-line"><span class="compat-dot" data-status="ok" role="img" aria-label="Conforming" title="Conforming"></span><span class="compat-main"><code class="compat-api">getAI(app, { useLimitedUseAppCheckTokens: true }) in production AI mode</code><span class="compat-sub"><span class="compat-behavior">With <code>useLimitedUseAppCheckTokens</code>, each request carries a fresh limited-use token rather than the cached one, and AI Logic answers 200.</span></span></span></summary>
|
|
421
|
-
<div class="compat-evidence"><div class="compat-probe">capture <a href="https://github.com/davideast/pyric/blob/main/packages/conformance/observations/app-check/app-check-client-ai-header.json" target="_blank" rel="noopener noreferrer"><code>app-check-client-ai-header</code></a> (<code>limitedUseModel</code>). (Structured evidence: <a href="https://github.com/davideast/pyric/blob/main/packages/cli/test/serve/ai-app-check-passthrough.test.ts#L132" target="_blank" rel="noopener noreferrer"><code>ai-app-check-passthrough.test.ts</code></a>, <a href="https://github.com/davideast/pyric/blob/main/test/apps/ai-production-app-check/scenario.ts#
|
|
421
|
+
<div class="compat-evidence"><div class="compat-probe">capture <a href="https://github.com/davideast/pyric/blob/main/packages/conformance/observations/app-check/app-check-client-ai-header.json" target="_blank" rel="noopener noreferrer"><code>app-check-client-ai-header</code></a> (<code>limitedUseModel</code>). (Structured evidence: <a href="https://github.com/davideast/pyric/blob/main/packages/cli/test/serve/ai-app-check-passthrough.test.ts#L132" target="_blank" rel="noopener noreferrer"><code>ai-app-check-passthrough.test.ts</code></a>, <a href="https://github.com/davideast/pyric/blob/main/test/apps/ai-production-app-check/scenario.ts#L122" target="_blank" rel="noopener noreferrer"><code>scenario.ts</code></a>, <a href="https://github.com/davideast/pyric/blob/main/test/apps/ai-production-import-map/scenario.ts#L112" target="_blank" rel="noopener noreferrer"><code>scenario.ts</code></a>)</div></div>
|
|
422
422
|
</details>
|
|
423
423
|
<details class="compat-row" data-status="ok">
|
|
424
424
|
<summary class="compat-line"><span class="compat-dot" data-status="ok" role="img" aria-label="Conforming" title="Conforming"></span><span class="compat-main"><code class="compat-api">generateContent without App Check in production AI mode</code><span class="compat-sub"><span class="compat-behavior">On an app that never initialized App Check, <code>firebase/ai</code> sends no <code>X-Firebase-AppCheck</code> header, and AI Logic rejects the request: HTTP 403, <code>PERMISSION_DENIED</code>, message <code>Firebase AI Logic has been deactivated in this project. To resume using Firebase AI Logic, you must enforce Firebase App Check.</code> with a docs link. A malformed header, the SDK placeholder token, and a tampered token get the same envelope; a valid token answers 200. Seen with App Check enforcement for AI Logic reading UNENFORCED.</span></span></span></summary>
|
|
425
|
-
<div class="compat-evidence"><div class="compat-probe">captures <a href="https://github.com/davideast/pyric/blob/main/packages/conformance/observations/app-check/app-check-ai-gate-wire.json" target="_blank" rel="noopener noreferrer"><code>app-check-ai-gate-wire</code></a> (all five cases on the wire) and <a href="https://github.com/davideast/pyric/blob/main/packages/conformance/observations/app-check/app-check-client-ai-header.json" target="_blank" rel="noopener noreferrer"><code>app-check-client-ai-header</code></a> (<code>noAppCheck</code>). (Structured evidence: <a href="https://github.com/davideast/pyric/blob/main/packages/cli/test/serve/ai-app-check-passthrough.test.ts#L109" target="_blank" rel="noopener noreferrer"><code>ai-app-check-passthrough.test.ts</code></a>, <a href="https://github.com/davideast/pyric/blob/main/test/apps/ai-production-app-check/scenario.ts#L4" target="_blank" rel="noopener noreferrer"><code>scenario.ts</code></a>)</div></div>
|
|
425
|
+
<div class="compat-evidence"><div class="compat-probe">captures <a href="https://github.com/davideast/pyric/blob/main/packages/conformance/observations/app-check/app-check-ai-gate-wire.json" target="_blank" rel="noopener noreferrer"><code>app-check-ai-gate-wire</code></a> (all five cases on the wire) and <a href="https://github.com/davideast/pyric/blob/main/packages/conformance/observations/app-check/app-check-client-ai-header.json" target="_blank" rel="noopener noreferrer"><code>app-check-client-ai-header</code></a> (<code>noAppCheck</code>). (Structured evidence: <a href="https://github.com/davideast/pyric/blob/main/packages/cli/test/serve/ai-app-check-passthrough.test.ts#L109" target="_blank" rel="noopener noreferrer"><code>ai-app-check-passthrough.test.ts</code></a>, <a href="https://github.com/davideast/pyric/blob/main/test/apps/ai-production-app-check/scenario.ts#L4" target="_blank" rel="noopener noreferrer"><code>scenario.ts</code></a>, <a href="https://github.com/davideast/pyric/blob/main/test/apps/ai-production-import-map/scenario.ts#L9" target="_blank" rel="noopener noreferrer"><code>scenario.ts</code></a>)</div></div>
|
|
426
426
|
</details>
|
|
427
427
|
<details class="compat-row" data-status="ok">
|
|
428
428
|
<summary class="compat-line"><span class="compat-dot" data-status="ok" role="img" aria-label="Conforming" title="Conforming"></span><span class="compat-main"><code class="compat-api">initializeAppCheck(app) after getAI(app)</code><span class="compat-sub"><span class="compat-behavior">Calling <code>initializeAppCheck</code> after <code>getAI(app)</code> built a model does not throw and <code>getToken</code> works, but that model sends no <code>X-Firebase-AppCheck</code> header and AI Logic answers 403. App Check must be initialized before <code>getAI</code>.</span></span></span></summary>
|
|
429
|
-
<div class="compat-evidence"><div class="compat-probe">capture <a href="https://github.com/davideast/pyric/blob/main/packages/conformance/observations/app-check/app-check-client-get-token-before-initialize.json" target="_blank" rel="noopener noreferrer"><code>app-check-client-get-token-before-initialize</code></a> (<code>initializeAfterAi</code>, <code>getTokenAfterLateInitialize</code>, <code>aiRequestAfterLateInitialize</code>, <code>aiGenerationAfterLateInitialize</code>). (Structured evidence: <a href="https://github.com/davideast/pyric/blob/main/packages/cli/test/serve/ai-app-check-passthrough.test.ts#L170" target="_blank" rel="noopener noreferrer"><code>ai-app-check-passthrough.test.ts</code></a>, <a href="https://github.com/davideast/pyric/blob/main/test/apps/ai-production-app-check/scenario.ts#
|
|
429
|
+
<div class="compat-evidence"><div class="compat-probe">capture <a href="https://github.com/davideast/pyric/blob/main/packages/conformance/observations/app-check/app-check-client-get-token-before-initialize.json" target="_blank" rel="noopener noreferrer"><code>app-check-client-get-token-before-initialize</code></a> (<code>initializeAfterAi</code>, <code>getTokenAfterLateInitialize</code>, <code>aiRequestAfterLateInitialize</code>, <code>aiGenerationAfterLateInitialize</code>). (Structured evidence: <a href="https://github.com/davideast/pyric/blob/main/packages/cli/test/serve/ai-app-check-passthrough.test.ts#L170" target="_blank" rel="noopener noreferrer"><code>ai-app-check-passthrough.test.ts</code></a>, <a href="https://github.com/davideast/pyric/blob/main/test/apps/ai-production-app-check/scenario.ts#L122" target="_blank" rel="noopener noreferrer"><code>scenario.ts</code></a>, <a href="https://github.com/davideast/pyric/blob/main/test/apps/ai-production-import-map/scenario.ts#L112" target="_blank" rel="noopener noreferrer"><code>scenario.ts</code></a>)</div></div>
|
|
430
430
|
</details>
|
|
431
431
|
<details class="compat-row" data-status="ok">
|
|
432
432
|
<summary class="compat-line"><span class="compat-dot" data-status="ok" role="img" aria-label="Conforming" title="Conforming"></span><span class="compat-main"><code class="compat-api">generateContent when App Check attestation fails</code><span class="compat-sub"><span class="compat-behavior">When the token exchange is refused, <code>firebase/ai</code> sends the SDK placeholder token (base64 of <code>{"error":"UNKNOWN_ERROR"}</code>) and AI Logic answers 403; with <code>useLimitedUseAppCheckTokens</code> it throws <code>AI/error</code> naming <code>appCheck/fetch-status-error</code> before sending any request.</span></span></span></summary>
|
|
433
|
-
<div class="compat-evidence"><div class="compat-probe">capture <a href="https://github.com/davideast/pyric/blob/main/packages/conformance/observations/app-check/app-check-client-invalid-debug-token.json" target="_blank" rel="noopener noreferrer"><code>app-check-client-invalid-debug-token</code></a> (<code>aiRequest</code>, <code>aiGeneration</code>, <code>aiLimitedUseRequest</code>, <code>aiLimitedUseGeneration</code>). (Structured evidence: <a href="https://github.com/davideast/pyric/blob/main/packages/cli/test/serve/ai-app-check-passthrough.test.ts#L189" target="_blank" rel="noopener noreferrer"><code>ai-app-check-passthrough.test.ts</code></a>, <a href="https://github.com/davideast/pyric/blob/main/test/apps/ai-production-app-check/scenario.ts#L3" target="_blank" rel="noopener noreferrer"><code>scenario.ts</code></a>)</div></div>
|
|
433
|
+
<div class="compat-evidence"><div class="compat-probe">capture <a href="https://github.com/davideast/pyric/blob/main/packages/conformance/observations/app-check/app-check-client-invalid-debug-token.json" target="_blank" rel="noopener noreferrer"><code>app-check-client-invalid-debug-token</code></a> (<code>aiRequest</code>, <code>aiGeneration</code>, <code>aiLimitedUseRequest</code>, <code>aiLimitedUseGeneration</code>). (Structured evidence: <a href="https://github.com/davideast/pyric/blob/main/packages/cli/test/serve/ai-app-check-passthrough.test.ts#L189" target="_blank" rel="noopener noreferrer"><code>ai-app-check-passthrough.test.ts</code></a>, <a href="https://github.com/davideast/pyric/blob/main/test/apps/ai-production-app-check/scenario.ts#L3" target="_blank" rel="noopener noreferrer"><code>scenario.ts</code></a>, <a href="https://github.com/davideast/pyric/blob/main/test/apps/ai-production-import-map/scenario.ts#L4" target="_blank" rel="noopener noreferrer"><code>scenario.ts</code></a>)</div></div>
|
|
434
|
+
</details>
|
|
435
|
+
</div>
|
|
436
|
+
|
|
437
|
+
## App Check in sandbox AI mode
|
|
438
|
+
|
|
439
|
+
<div class="compat-list">
|
|
440
|
+
<details class="compat-row" data-status="ok">
|
|
441
|
+
<summary class="compat-line"><span class="compat-dot" data-status="ok" role="img" aria-label="Conforming" title="Conforming"></span><span class="compat-main"><code class="compat-api">generateContent from getAI(app) in sandbox AI mode</code><span class="compat-sub"><span class="compat-behavior">An app that never initialized App Check gets the <code>AIError</code> the SDK throws for AI Logic's 403: code <code>fetch-error</code>, <code>customErrorData.status</code> 403, message <code>[403 Forbidden] Firebase AI Logic has been deactivated in this project. ...</code> with the docs link. The broker refuses a missing, malformed, placeholder, tampered or expired token with production's envelope before any engine runs, and answers a sandbox token, including one minted under the debug provider. Pyric's guidance goes to the console once per app and to the dev server terminal, beside the error.</span></span></span></summary>
|
|
442
|
+
<div class="compat-evidence"><div class="compat-probe">captures <a href="https://github.com/davideast/pyric/blob/main/packages/conformance/observations/app-check/app-check-ai-gate-wire.json" target="_blank" rel="noopener noreferrer"><code>app-check-ai-gate-wire</code></a> (<code>noHeader</code>, <code>malformedHeader</code>, <code>sdkPlaceholderToken</code>, <code>tamperedSignature</code>, <code>validAdminMintedToken</code>) and <a href="https://github.com/davideast/pyric/blob/main/packages/conformance/observations/app-check/app-check-client-ai-header.json" target="_blank" rel="noopener noreferrer"><code>app-check-client-ai-header</code></a> (<code>noAppCheck</code>), replayed by the gate suite in process and over the worker port; the app scenario drives the in-page, SharedWorker, Node host and registered Node process planes. On main before #1291 the sandbox answered tokenless requests. (Structured evidence: <a href="https://github.com/davideast/pyric/blob/main/packages/pyric/test/ai/app-check-gate.test.ts#L8" target="_blank" rel="noopener noreferrer"><code>app-check-gate.test.ts</code></a>, <a href="https://github.com/davideast/pyric/blob/main/packages/cli/test/serve/worker/ai-ops.test.ts#L10" target="_blank" rel="noopener noreferrer"><code>ai-ops.test.ts</code></a>, <a href="https://github.com/davideast/pyric/blob/main/test/apps/sandbox-app-check/scenario.ts#L2" target="_blank" rel="noopener noreferrer"><code>scenario.ts</code></a>)</div></div>
|
|
443
|
+
</details>
|
|
444
|
+
<details class="compat-row" data-status="ok">
|
|
445
|
+
<summary class="compat-line"><span class="compat-dot" data-status="ok" role="img" aria-label="Conforming" title="Conforming"></span><span class="compat-main"><code class="compat-api">initializeAppCheck(app) after getAI(app) in sandbox AI mode</code><span class="compat-sub"><span class="compat-behavior"><code>getAI(app)</code> reads App Check when it builds the handle. <code>initializeAppCheck</code> after it does not throw and <code>getToken</code> works, but that handle's requests carry no token and are refused with the 403.</span></span></span></summary>
|
|
446
|
+
<div class="compat-evidence"><div class="compat-probe">capture <a href="https://github.com/davideast/pyric/blob/main/packages/conformance/observations/app-check/app-check-client-get-token-before-initialize.json" target="_blank" rel="noopener noreferrer"><code>app-check-client-get-token-before-initialize</code></a> (<code>initializeAfterAi</code>, <code>getTokenAfterLateInitialize</code>, <code>aiRequestAfterLateInitialize</code>, <code>aiGenerationAfterLateInitialize</code>). (Structured evidence: <a href="https://github.com/davideast/pyric/blob/main/packages/pyric/test/ai/app-check-gate.test.ts#L11" target="_blank" rel="noopener noreferrer"><code>app-check-gate.test.ts</code></a>, <a href="https://github.com/davideast/pyric/blob/main/packages/cli/test/serve/worker/ai-ops.test.ts#L5" target="_blank" rel="noopener noreferrer"><code>ai-ops.test.ts</code></a>, <a href="https://github.com/davideast/pyric/blob/main/test/apps/sandbox-app-check/scenario.ts#L2" target="_blank" rel="noopener noreferrer"><code>scenario.ts</code></a>)</div></div>
|
|
447
|
+
</details>
|
|
448
|
+
<details class="compat-row" data-status="ok">
|
|
449
|
+
<summary class="compat-line"><span class="compat-dot" data-status="ok" role="img" aria-label="Conforming" title="Conforming"></span><span class="compat-main"><code class="compat-api">getAI(app, { useLimitedUseAppCheckTokens: true }) in sandbox AI mode</code><span class="compat-sub"><span class="compat-behavior">Each request carries a fresh limited-use token rather than the cached one, and the sandbox answers.</span></span></span></summary>
|
|
450
|
+
<div class="compat-evidence"><div class="compat-probe">capture <a href="https://github.com/davideast/pyric/blob/main/packages/conformance/observations/app-check/app-check-client-ai-header.json" target="_blank" rel="noopener noreferrer"><code>app-check-client-ai-header</code></a> (<code>limitedUseModel</code>). (Structured evidence: <a href="https://github.com/davideast/pyric/blob/main/packages/pyric/test/ai/app-check-gate.test.ts#L9" target="_blank" rel="noopener noreferrer"><code>app-check-gate.test.ts</code></a>, <a href="https://github.com/davideast/pyric/blob/main/packages/cli/test/serve/worker/ai-ops.test.ts#L5" target="_blank" rel="noopener noreferrer"><code>ai-ops.test.ts</code></a>, <a href="https://github.com/davideast/pyric/blob/main/test/apps/sandbox-app-check/scenario.ts#L11" target="_blank" rel="noopener noreferrer"><code>scenario.ts</code></a>)</div></div>
|
|
451
|
+
</details>
|
|
452
|
+
<details class="compat-row" data-status="ok">
|
|
453
|
+
<summary class="compat-line"><span class="compat-dot" data-status="ok" role="img" aria-label="Conforming" title="Conforming"></span><span class="compat-main"><code class="compat-api">generateContentStream and countTokens without App Check in sandbox AI mode</code><span class="compat-sub"><span class="compat-behavior">Streaming and <code>countTokens</code> are refused with the same 403 <code>AIError</code> as <code>generateContent</code>. In process <code>generateContentStream</code> rejects before any chunk; on the SharedWorker and Node host planes the error reaches the caller through the stream and <code>response</code>, as other request errors do there.</span></span></span></summary>
|
|
454
|
+
<div class="compat-evidence"><div class="compat-probe">AI Logic applies App Check to every request (https://firebase.google.com/docs/ai-logic/app-check); the captures cover only <code>generateContent</code>. Unit-backed by the gate suite and the worker protocol suite. (Structured evidence: <a href="https://github.com/davideast/pyric/blob/main/packages/pyric/test/ai/app-check-gate.test.ts#L193" target="_blank" rel="noopener noreferrer"><code>app-check-gate.test.ts</code></a>, <a href="https://github.com/davideast/pyric/blob/main/packages/cli/test/serve/worker/ai-ops.test.ts#L12" target="_blank" rel="noopener noreferrer"><code>ai-ops.test.ts</code></a>, <a href="https://github.com/davideast/pyric/blob/main/test/apps/sandbox-app-check/scenario.ts#L40" target="_blank" rel="noopener noreferrer"><code>scenario.ts</code></a>)</div></div>
|
|
434
455
|
</details>
|
|
435
456
|
</div>
|
|
436
457
|
|
|
@@ -454,9 +475,9 @@ Known differences between Pyric and production Firebase. Each remains tracked as
|
|
|
454
475
|
<details class="compat-row" data-status="diverged"><summary class="compat-line"><span class="compat-dot" data-status="diverged" role="img" aria-label="Diverged (documented)" title="Diverged (documented)"></span><span class="compat-main"><code class="compat-api">Schema.enumString()</code><span class="compat-sub"><span class="compat-behavior"><code>Schema.enumString</code> serializes the enum values with type <code>string</code> and format <code>enum</code></span></span></span></summary>
|
|
455
476
|
<div class="compat-evidence"><div class="compat-note"><a href="https://github.com/davideast/pyric/blob/main/packages/pyric/test/ai/helpers-schema.test.ts#L153" target="_blank" rel="noopener noreferrer"><code>unit:helpers-schema.test.ts</code></a> test <code>ai#schema-string-enum</code> (upstream toJSON request shape; GoogleAI accepts only enum and date-time formats)</div></div></details>
|
|
456
477
|
<details class="compat-row" data-status="diverged"><summary class="compat-line"><span class="compat-dot" data-status="diverged" role="img" aria-label="Diverged (documented)" title="Diverged (documented)"></span><span class="compat-main"><code class="compat-api">getAI(app) in a registered server process</code><span class="compat-sub"><span class="compat-behavior">Under a remote register activation, <code>getAI(app)</code> in a server process fails with <code>failed-precondition</code> and a message that names the client services that do reach the Node host: <code>firebase/ai</code> has no path to the host, and no in-process sandbox stands in for it. Production AI pass-through (<code>PYRIC_AI=production</code>) loads the real <code>firebase/ai</code> and is unaffected.</span></span></span></summary>
|
|
457
|
-
<div class="compat-evidence"><div class="compat-note">App scenario <code>test/apps/client-sdk-under-register</code> calls it under <code>node --import @pyric/cli/register</code> against a hosted Vite dev server and asserts the refusal; on main (942cb175) it returned a handle on a private in-process sandbox. <a href="https://github.com/davideast/pyric/blob/main/packages/cli/test/register/mapping.test.ts#
|
|
478
|
+
<div class="compat-evidence"><div class="compat-note">App scenario <code>test/apps/client-sdk-under-register</code> calls it under <code>node --import @pyric/cli/register</code> against a hosted Vite dev server and asserts the refusal; on main (942cb175) it returned a handle on a private in-process sandbox. <a href="https://github.com/davideast/pyric/blob/main/packages/cli/test/register/mapping.test.ts#L80" target="_blank" rel="noopener noreferrer"><code>mapping.test.ts</code></a> pins that the subpath keeps its mirror under a remote activation, and <a href="https://github.com/davideast/pyric/blob/main/packages/cli/test/register/node-origin.test.ts#L2" target="_blank" rel="noopener noreferrer"><code>node-origin.test.ts</code></a> that a mirror resolved from such an app refuses. (Structured evidence: <a href="https://github.com/davideast/pyric/blob/main/test/apps/client-sdk-under-register/scenario.ts#L1" target="_blank" rel="noopener noreferrer"><code>scenario.ts</code></a>)</div></div></details>
|
|
458
479
|
<details class="compat-row" data-status="diverged"><summary class="compat-line"><span class="compat-dot" data-status="diverged" role="img" aria-label="Diverged (documented)" title="Diverged (documented)"></span><span class="compat-main"><code class="compat-api">getAI(app) in a server-rendered module under vite dev</code><span class="compat-sub"><span class="compat-behavior">Under <code>vite dev</code>, <code>getAI(app)</code> in a server-rendered module fails with <code>failed-precondition</code> in the default sandbox AI mode, naming the client services that reach the host: <code>firebase/ai</code> has no path to the dev server's host from server code. In production AI mode the real <code>firebase/ai</code> loads and the network guard permits Firebase AI Logic. A SharedWorker dev server refuses it the same way.</span></span></span></summary>
|
|
459
|
-
<div class="compat-evidence"><div class="compat-note">App scenario <code>test/apps/vite-ssr-client-sdk</code> calls it in SSR on a hosted dev server and asserts the refusal; a second dev server in production AI mode lets the real SDK reach only <code>firebasevertexai.googleapis.com</code>, which a stand-in beneath the guard refuses. On main (ba2c0bf0a) it was the real SDK. <a href="https://github.com/davideast/pyric/blob/main/packages/cli/test/serve/vite-module-swap.test.ts#
|
|
480
|
+
<div class="compat-evidence"><div class="compat-note">App scenario <code>test/apps/vite-ssr-client-sdk</code> calls it in SSR on a hosted dev server and asserts the refusal; a second dev server in production AI mode lets the real SDK reach only <code>firebasevertexai.googleapis.com</code>, which a stand-in beneath the guard refuses. On main (ba2c0bf0a) it was the real SDK. <a href="https://github.com/davideast/pyric/blob/main/packages/cli/test/serve/vite-module-swap.test.ts#L4" target="_blank" rel="noopener noreferrer"><code>vite-module-swap.test.ts</code></a> pins the routing. (Structured evidence: <a href="https://github.com/davideast/pyric/blob/main/test/apps/vite-ssr-client-sdk/scenario.ts#L1" target="_blank" rel="noopener noreferrer"><code>scenario.ts</code></a>)</div></div></details>
|
|
460
481
|
</div>
|
|
461
482
|
|
|
462
483
|
## Reviewed public-runtime gaps
|