pyric 0.1.0-alpha.20 → 0.1.0-alpha.22

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.
Files changed (230) hide show
  1. package/README.md +64 -89
  2. package/dist/ai/blocked.d.ts +52 -0
  3. package/dist/ai/blocked.d.ts.map +1 -0
  4. package/dist/ai/blocked.js +83 -0
  5. package/dist/ai/blocked.js.map +1 -0
  6. package/dist/ai/broker/broker.d.ts +49 -0
  7. package/dist/ai/broker/broker.d.ts.map +1 -1
  8. package/dist/ai/broker/broker.js +90 -0
  9. package/dist/ai/broker/broker.js.map +1 -1
  10. package/dist/ai/broker/gemini-engine.d.ts +14 -1
  11. package/dist/ai/broker/gemini-engine.d.ts.map +1 -1
  12. package/dist/ai/broker/gemini-engine.js +30 -9
  13. package/dist/ai/broker/gemini-engine.js.map +1 -1
  14. package/dist/ai/broker/index.d.ts +2 -2
  15. package/dist/ai/broker/index.d.ts.map +1 -1
  16. package/dist/ai/broker/index.js +2 -2
  17. package/dist/ai/broker/index.js.map +1 -1
  18. package/dist/ai/broker/openai-engine.d.ts +10 -1
  19. package/dist/ai/broker/openai-engine.d.ts.map +1 -1
  20. package/dist/ai/broker/openai-engine.js +26 -4
  21. package/dist/ai/broker/openai-engine.js.map +1 -1
  22. package/dist/ai/broker/synthesizer.d.ts +14 -0
  23. package/dist/ai/broker/synthesizer.d.ts.map +1 -1
  24. package/dist/ai/broker/synthesizer.js +31 -0
  25. package/dist/ai/broker/synthesizer.js.map +1 -1
  26. package/dist/ai/broker/types.d.ts +24 -0
  27. package/dist/ai/broker/types.d.ts.map +1 -1
  28. package/dist/ai/internal.d.ts +5 -0
  29. package/dist/ai/internal.d.ts.map +1 -1
  30. package/dist/ai/internal.js +5 -0
  31. package/dist/ai/internal.js.map +1 -1
  32. package/dist/ai/response-helpers.d.ts.map +1 -1
  33. package/dist/ai/response-helpers.js +6 -24
  34. package/dist/ai/response-helpers.js.map +1 -1
  35. package/dist/analytics/index.d.ts +40 -0
  36. package/dist/analytics/index.d.ts.map +1 -0
  37. package/dist/analytics/index.js +25 -0
  38. package/dist/analytics/index.js.map +1 -0
  39. package/dist/app-check/index.d.ts +31 -0
  40. package/dist/app-check/index.d.ts.map +1 -0
  41. package/dist/app-check/index.js +20 -0
  42. package/dist/app-check/index.js.map +1 -0
  43. package/dist/database/controls.d.ts +1 -1
  44. package/dist/database/controls.js +1 -1
  45. package/dist/database/sandbox/rules-eval.d.ts +1 -1
  46. package/dist/database/sandbox/rules-eval.d.ts.map +1 -1
  47. package/dist/database/sandbox/rules-eval.js +2 -2
  48. package/dist/database/sandbox/rules-eval.js.map +1 -1
  49. package/dist/database/sandbox/write-plane.js +2 -2
  50. package/dist/database/sandbox/write-plane.js.map +1 -1
  51. package/dist/database/sandbox-controls.d.ts +12 -5
  52. package/dist/database/sandbox-controls.d.ts.map +1 -1
  53. package/dist/database/sandbox-controls.js +20 -9
  54. package/dist/database/sandbox-controls.js.map +1 -1
  55. package/dist/deferred/entry.d.ts +75 -0
  56. package/dist/deferred/entry.d.ts.map +1 -0
  57. package/dist/deferred/entry.js +143 -0
  58. package/dist/deferred/entry.js.map +1 -0
  59. package/dist/firestore/lite.d.ts +72 -0
  60. package/dist/firestore/lite.d.ts.map +1 -0
  61. package/dist/firestore/lite.js +22 -0
  62. package/dist/firestore/lite.js.map +1 -0
  63. package/dist/firestore/persistence.d.ts +2 -2
  64. package/dist/firestore/persistence.js +1 -1
  65. package/dist/functions/index.d.ts +29 -0
  66. package/dist/functions/index.d.ts.map +1 -0
  67. package/dist/functions/index.js +20 -0
  68. package/dist/functions/index.js.map +1 -0
  69. package/dist/performance/index.d.ts +22 -0
  70. package/dist/performance/index.d.ts.map +1 -0
  71. package/dist/performance/index.js +19 -0
  72. package/dist/performance/index.js.map +1 -0
  73. package/dist/remote-config/index.d.ts +40 -0
  74. package/dist/remote-config/index.d.ts.map +1 -0
  75. package/dist/remote-config/index.js +25 -0
  76. package/dist/remote-config/index.js.map +1 -0
  77. package/dist/rules/grammar/FirestoreValidator.d.ts.map +1 -1
  78. package/dist/rules/grammar/FirestoreValidator.js +43 -27
  79. package/dist/rules/grammar/FirestoreValidator.js.map +1 -1
  80. package/dist/rules/grammar/document-access-count.d.ts +24 -0
  81. package/dist/rules/grammar/document-access-count.d.ts.map +1 -0
  82. package/dist/rules/grammar/document-access-count.js +100 -0
  83. package/dist/rules/grammar/document-access-count.js.map +1 -0
  84. package/dist/rules/linter/ast-utils.d.ts +2 -6
  85. package/dist/rules/linter/ast-utils.d.ts.map +1 -1
  86. package/dist/rules/linter/ast-utils.js +2 -63
  87. package/dist/rules/linter/ast-utils.js.map +1 -1
  88. package/dist/rules/linter/hallucinations.d.ts +8 -0
  89. package/dist/rules/linter/hallucinations.d.ts.map +1 -1
  90. package/dist/rules/linter/hallucinations.js +121 -31
  91. package/dist/rules/linter/hallucinations.js.map +1 -1
  92. package/dist/rules/linter/linter.d.ts +10 -1
  93. package/dist/rules/linter/linter.d.ts.map +1 -1
  94. package/dist/rules/linter/linter.js +23 -9
  95. package/dist/rules/linter/linter.js.map +1 -1
  96. package/dist/rules/rtdb/grammar/simulator.d.ts.map +1 -1
  97. package/dist/rules/rtdb/grammar/simulator.js +13 -13
  98. package/dist/rules/rtdb/grammar/simulator.js.map +1 -1
  99. package/dist/rules/rtdb/simulation/handler.d.ts.map +1 -1
  100. package/dist/rules/rtdb/simulation/handler.js +96 -35
  101. package/dist/rules/rtdb/simulation/handler.js.map +1 -1
  102. package/dist/rules/simulator/document-lookups.d.ts +4 -0
  103. package/dist/rules/simulator/document-lookups.d.ts.map +1 -1
  104. package/dist/rules/simulator/document-lookups.js +42 -3
  105. package/dist/rules/simulator/document-lookups.js.map +1 -1
  106. package/dist/rules/simulator/eval-error.d.ts +11 -0
  107. package/dist/rules/simulator/eval-error.d.ts.map +1 -1
  108. package/dist/rules/simulator/eval-error.js +14 -0
  109. package/dist/rules/simulator/eval-error.js.map +1 -1
  110. package/dist/rules/simulator/evaluation-builtins.d.ts.map +1 -1
  111. package/dist/rules/simulator/evaluation-builtins.js +33 -8
  112. package/dist/rules/simulator/evaluation-builtins.js.map +1 -1
  113. package/dist/rules/simulator/evaluation-context.d.ts +12 -0
  114. package/dist/rules/simulator/evaluation-context.d.ts.map +1 -1
  115. package/dist/rules/simulator/evaluator.d.ts +1 -1
  116. package/dist/rules/simulator/evaluator.d.ts.map +1 -1
  117. package/dist/rules/simulator/evaluator.js +16 -4
  118. package/dist/rules/simulator/evaluator.js.map +1 -1
  119. package/dist/rules/simulator/handler.d.ts.map +1 -1
  120. package/dist/rules/simulator/handler.js +52 -5
  121. package/dist/rules/simulator/handler.js.map +1 -1
  122. package/dist/rules/simulator/lookup-budget.d.ts +37 -0
  123. package/dist/rules/simulator/lookup-budget.d.ts.map +1 -0
  124. package/dist/rules/simulator/lookup-budget.js +96 -0
  125. package/dist/rules/simulator/lookup-budget.js.map +1 -0
  126. package/dist/rules/stdlib-modules.d.ts +7 -0
  127. package/dist/rules/stdlib-modules.d.ts.map +1 -1
  128. package/dist/rules/stdlib-modules.js +11 -11
  129. package/dist/rules/stdlib-modules.js.map +1 -1
  130. package/dist/sandbox/admin-firestore/get-firestore.d.ts +6 -0
  131. package/dist/sandbox/admin-firestore/get-firestore.d.ts.map +1 -1
  132. package/dist/sandbox/admin-firestore/get-firestore.js +22 -3
  133. package/dist/sandbox/admin-firestore/get-firestore.js.map +1 -1
  134. package/dist/sandbox/index.d.ts +1 -1
  135. package/dist/sandbox/index.d.ts.map +1 -1
  136. package/dist/sandbox/index.js +1 -1
  137. package/dist/sandbox/index.js.map +1 -1
  138. package/dist/sandbox/persistence/types.d.ts +1 -1
  139. package/dist/sandbox/remote.d.ts +2 -2
  140. package/dist/sandbox/sandbox-context.d.ts +8 -1
  141. package/dist/sandbox/sandbox-context.d.ts.map +1 -1
  142. package/dist/sandbox/sandbox-context.js +59 -8
  143. package/dist/sandbox/sandbox-context.js.map +1 -1
  144. package/dist/sandbox/types/auth-state.d.ts +1 -0
  145. package/dist/sandbox/types/auth-state.d.ts.map +1 -1
  146. package/dist/sandbox/types/events.d.ts +2 -1
  147. package/dist/sandbox/types/events.d.ts.map +1 -1
  148. package/dist/sandbox/types/operation.d.ts +1 -0
  149. package/dist/sandbox/types/operation.d.ts.map +1 -1
  150. package/dist/storage/enforce.d.ts +20 -1
  151. package/dist/storage/enforce.d.ts.map +1 -1
  152. package/dist/storage/enforce.js +47 -14
  153. package/dist/storage/enforce.js.map +1 -1
  154. package/dist/storage/index.d.ts +1 -1
  155. package/dist/storage/index.d.ts.map +1 -1
  156. package/dist/storage/list.js +1 -1
  157. package/dist/storage/list.js.map +1 -1
  158. package/dist/storage/persistence.d.ts +81 -17
  159. package/dist/storage/persistence.d.ts.map +1 -1
  160. package/dist/storage/persistence.js +267 -50
  161. package/dist/storage/persistence.js.map +1 -1
  162. package/dist/storage/sandbox/rules-evaluation-error.d.ts +25 -0
  163. package/dist/storage/sandbox/rules-evaluation-error.d.ts.map +1 -1
  164. package/dist/storage/sandbox/rules-evaluation-error.js +29 -0
  165. package/dist/storage/sandbox/rules-evaluation-error.js.map +1 -1
  166. package/dist/storage/sandbox/rules-evaluator.d.ts +10 -5
  167. package/dist/storage/sandbox/rules-evaluator.d.ts.map +1 -1
  168. package/dist/storage/sandbox/rules-evaluator.js +105 -33
  169. package/dist/storage/sandbox/rules-evaluator.js.map +1 -1
  170. package/dist/storage/sandbox/rules-methods.d.ts.map +1 -1
  171. package/dist/storage/sandbox/rules-methods.js +17 -7
  172. package/dist/storage/sandbox/rules-methods.js.map +1 -1
  173. package/dist/storage/service.d.ts +25 -6
  174. package/dist/storage/service.d.ts.map +1 -1
  175. package/dist/storage/service.js +98 -46
  176. package/dist/storage/service.js.map +1 -1
  177. package/package.json +41 -1
  178. package/src/ai/blocked.ts +97 -0
  179. package/src/ai/broker/broker.ts +88 -0
  180. package/src/ai/broker/gemini-engine.ts +36 -9
  181. package/src/ai/broker/index.ts +2 -1
  182. package/src/ai/broker/openai-engine.ts +31 -3
  183. package/src/ai/broker/synthesizer.ts +34 -0
  184. package/src/ai/broker/types.ts +25 -0
  185. package/src/ai/internal.ts +5 -0
  186. package/src/ai/response-helpers.ts +6 -25
  187. package/src/analytics/index.ts +53 -0
  188. package/src/app/dispatch.test.ts +3 -1
  189. package/src/app-check/index.ts +41 -0
  190. package/src/database/controls.ts +1 -1
  191. package/src/database/sandbox/rules-eval.ts +2 -2
  192. package/src/database/sandbox/write-plane.ts +2 -2
  193. package/src/database/sandbox-controls.ts +29 -9
  194. package/src/deferred/entry.ts +167 -0
  195. package/src/firestore/lite.ts +91 -0
  196. package/src/firestore/persistence.ts +2 -2
  197. package/src/functions/index.ts +38 -0
  198. package/src/performance/index.ts +31 -0
  199. package/src/remote-config/index.ts +53 -0
  200. package/src/rules/grammar/FirestoreValidator.ts +44 -28
  201. package/src/rules/grammar/document-access-count.ts +75 -0
  202. package/src/rules/linter/ast-utils.ts +2 -36
  203. package/src/rules/linter/hallucinations.ts +123 -25
  204. package/src/rules/linter/linter.ts +32 -10
  205. package/src/rules/rtdb/grammar/simulator.ts +12 -13
  206. package/src/rules/rtdb/simulation/handler.ts +149 -46
  207. package/src/rules/simulator/document-lookups.ts +46 -3
  208. package/src/rules/simulator/eval-error.ts +16 -0
  209. package/src/rules/simulator/evaluation-builtins.ts +35 -8
  210. package/src/rules/simulator/evaluation-context.ts +12 -0
  211. package/src/rules/simulator/evaluator.ts +14 -3
  212. package/src/rules/simulator/handler.ts +68 -4
  213. package/src/rules/simulator/lookup-budget.ts +101 -0
  214. package/src/rules/stdlib-modules.ts +19 -11
  215. package/src/sandbox/admin-firestore/get-firestore.ts +19 -3
  216. package/src/sandbox/index.ts +5 -1
  217. package/src/sandbox/persistence/types.ts +1 -1
  218. package/src/sandbox/remote.ts +2 -2
  219. package/src/sandbox/sandbox-context.ts +69 -9
  220. package/src/sandbox/types/auth-state.ts +1 -1
  221. package/src/sandbox/types/events.ts +2 -1
  222. package/src/sandbox/types/operation.ts +1 -1
  223. package/src/storage/enforce.ts +53 -14
  224. package/src/storage/index.ts +1 -1
  225. package/src/storage/list.ts +1 -1
  226. package/src/storage/persistence.ts +291 -63
  227. package/src/storage/sandbox/rules-evaluation-error.ts +32 -0
  228. package/src/storage/sandbox/rules-evaluator.ts +109 -32
  229. package/src/storage/sandbox/rules-methods.ts +21 -7
  230. package/src/storage/service.ts +173 -54
package/README.md CHANGED
@@ -2,50 +2,39 @@
2
2
  <img src="https://pyric.dev/pyric-logo.svg" alt="Pyric" width="180" />
3
3
  </p>
4
4
 
5
- <h1 align="center">A local first Firebase development framework built for Agents</h1>
5
+ <h1 align="center">A Firebase that runs inside your app</h1>
6
6
 
7
- <p align="center">Keep the same <code>firebase/*</code> code. Pyric runs it against a local backend during development, then gets out of the way when the app ships to Firebase.</p>
7
+ <p align="center">Keep the same <code>firebase/*</code> code. During <code>vite dev</code> those imports resolve to a local backend running in the page. A production build ships the real Firebase SDK, unchanged.</p>
8
8
 
9
9
  <p align="center"><a href="https://pyric.dev">pyric.dev</a></p>
10
10
 
11
11
  <br />
12
12
 
13
- Pyric adds a development-only resolution layer to a Firebase application. Run the Vite development server and supported Firebase imports resolve to a browser-local backend. Run a normal production build and those imports resolve to Firebase again. The application source does not branch between the two.
13
+ Application code stays canonical Firebase:
14
14
 
15
- The mirrored data services do not connect to a production Firebase project. Local writes cannot delete production data or create Firebase usage charges, and local rules changes do not deploy. Pyric owns the development sandbox and verification workflow. Firebase owns production, with `firebase-tools` or the Firebase Console handling deployment.
16
-
17
- ## Start with a coding agent
18
-
19
- Install the Pyric plugin:
20
-
21
- ```bash
22
- npx plugins add davideast/pyric
23
- ```
24
-
25
- Antigravity CLI and OpenCode use the standalone skill installer:
15
+ ```ts
16
+ // src/firebase.ts
17
+ import { initializeApp } from 'firebase/app';
18
+ import { getAuth } from 'firebase/auth';
19
+ import { getFirestore } from 'firebase/firestore';
26
20
 
27
- ```bash
28
- # Antigravity CLI
29
- npx skills add https://github.com/davideast/pyric/tree/main/pyric-plugin/skills/pyric --agent antigravity-cli
21
+ const app = initializeApp({
22
+ apiKey: import.meta.env.VITE_FIREBASE_API_KEY,
23
+ authDomain: import.meta.env.VITE_FIREBASE_AUTH_DOMAIN,
24
+ projectId: import.meta.env.VITE_FIREBASE_PROJECT_ID,
25
+ });
30
26
 
31
- # OpenCode
32
- npx skills add https://github.com/davideast/pyric/tree/main/pyric-plugin/skills/pyric --agent opencode
27
+ export const auth = getAuth(app);
28
+ export const db = getFirestore(app);
33
29
  ```
34
30
 
35
- Then invoke the skill:
31
+ Run the Vite development server and supported Firebase imports resolve to a browser-local backend: Auth, Firestore with Security Rules, persistence in IndexedDB, one SharedWorker backend shared by every tab on the dev origin. Run a normal production build and those imports resolve to Firebase again. The application source does not branch between the two, and the local mirror never connects to a production project.
36
32
 
37
- | Agent | Enter |
38
- |---|---|
39
- | Codex | `$pyric` |
40
- | Claude Code | `/pyric:pyric` |
41
- | Antigravity CLI | `/pyric` |
42
- | OpenCode | `/pyric` |
33
+ Coding agents get the same backend. A [skill](#start-with-a-coding-agent) sets up the sandbox end to end (`npx plugins add davideast/pyric`, then `/pyric`), and an opt-in MCP bridge lets an agent seed, inspect, and change the backend the app is already running against. `can-i-use` answers what conforms to real Firebase before the agent builds on it.
43
34
 
44
- The leading `$` or `/` is part of the command.
45
-
46
- The skill chooses the project launcher, starts one local sandbox bridge, opens the application, and confirms that the browser sandbox is connected.
35
+ <img width="1512" height="947" alt="Pyric Studio showing local data, requests, authentication state, and Security Rules verdicts" src="https://github.com/user-attachments/assets/cb4a219c-c5f0-4fb7-8904-996416c0a79c" />
47
36
 
48
- ## Start from the terminal
37
+ ## Create a new app
49
38
 
50
39
  Create a Vite application with canonical Firebase imports, Firestore rules, and the Pyric development plugin already configured:
51
40
 
@@ -82,70 +71,69 @@ Start the normal development server:
82
71
  npm run dev
83
72
  ```
84
73
 
85
- During `vite dev`, the plugin swaps supported `firebase/*` modules at resolution time. The backend runs in a SharedWorker, so tabs on the same development origin use one local backend. Browser-local persistence uses IndexedDB.
86
-
87
- Pyric Studio is available on the Vite origin at `/__pyric/ui/studio`. It opens the same backend used by the application.
74
+ During `vite dev`, the plugin swaps supported `firebase/*` modules at resolution time. Pyric Studio is available on the Vite origin at `/__pyric/ui/studio`. It opens the same backend used by the application.
88
75
 
89
76
  For a static application or a Node process, `pyric sandbox` provides the same development-only package swap without the Vite plugin. The [CLI reference](packages/site-docs/src/content/reference/cli.md) documents those paths and every command.
90
77
 
91
- ## Keep using official Firebase SDKs
92
-
93
- Application code continues to import Firebase:
94
-
95
- ```ts
96
- // src/firebase.ts
97
- import { initializeApp } from 'firebase/app';
98
- import { getAuth } from 'firebase/auth';
99
- import { getFirestore } from 'firebase/firestore';
78
+ ## Start with a coding agent
100
79
 
101
- const app = initializeApp({
102
- apiKey: import.meta.env.VITE_FIREBASE_API_KEY,
103
- authDomain: import.meta.env.VITE_FIREBASE_AUTH_DOMAIN,
104
- projectId: import.meta.env.VITE_FIREBASE_PROJECT_ID,
105
- });
80
+ Install the Pyric plugin:
106
81
 
107
- export const auth = getAuth(app);
108
- export const db = getFirestore(app);
82
+ ```bash
83
+ npx plugins add davideast/pyric
109
84
  ```
110
85
 
111
- The Firebase configuration is accepted by the local mirror during development. The production build passes that same configuration to Firebase. Normal Auth, Firestore, Realtime Database, Storage, Messaging, and Firebase AI Logic usage still belongs in the [Firebase documentation](https://firebase.google.com/docs).
86
+ Antigravity CLI and OpenCode use the standalone skill installer:
112
87
 
113
- Pyric documents the parts that differ locally, including supported behavior, Security Rules, persistence, inspection, verification, and known gaps. See [use the Vite plugin](packages/cli/docs/how-to/use-the-vite-plugin.md) for the complete plugin contract.
88
+ ```bash
89
+ # Antigravity CLI
90
+ npx skills add https://github.com/davideast/pyric/tree/main/pyric-plugin/skills/pyric --agent antigravity-cli
114
91
 
115
- ## Monitor the local environment in Pyric Studio
92
+ # OpenCode
93
+ npx skills add https://github.com/davideast/pyric/tree/main/pyric-plugin/skills/pyric --agent opencode
94
+ ```
116
95
 
117
- Pyric Studio shows local data, requests, authentication state, Security Rules verdicts, and denied operations. Open `/__pyric/ui/studio` on the development server and reproduce the failure. A denied request includes the path and verdict needed to correct the rule or the application code.
96
+ Then invoke the skill:
118
97
 
119
- <img width="1512" height="947" alt="image" src="https://github.com/user-attachments/assets/cb4a219c-c5f0-4fb7-8904-996416c0a79c" />
98
+ | Agent | Enter |
99
+ |---|---|
100
+ | Codex | `$pyric` |
101
+ | Claude Code | `/pyric:pyric` |
102
+ | Antigravity CLI | `/pyric` |
103
+ | OpenCode | `/pyric` |
120
104
 
121
- The Vite plugin discovers the Firestore rules path from `firebase.json`, or falls back to `firestore.rules`. Saving that file replaces the active local ruleset without a production deploy. A parse failure leaves the last valid ruleset active and reports the error in the development server.
105
+ The leading `$` or `/` is part of the command.
122
106
 
123
- State and test identities stay browser-local by default. Add `persist: true` to write a committable `.pyric/state/state.json`, or use `seed` to start from a known scenario:
107
+ The skill chooses the project launcher, starts one local sandbox bridge, opens the application, and confirms that the browser sandbox is connected.
108
+
109
+ An agent can also work through the MCP bridge directly. It keeps writing the same Firebase code while inspecting and changing the backend the application is already using. Agent access is opt-in:
124
110
 
125
111
  ```ts
126
112
  pyric({
127
- persist: true,
128
- seed: 'seed.json',
113
+ bridge: true
129
114
  });
130
115
  ```
131
116
 
132
- See [persistence and multi-tab behavior](packages/cli/docs/how-to/serve-persistence-and-multi-tab.md) for reset and seed precedence.
117
+ The bridge mounts on the Vite development origin and routes agent operations to the same SharedWorker backend used by the application and Studio. This protects the local workflow, not an independently credentialed process. A coding agent with production Firebase credentials or deployment access can still affect production outside Pyric.
118
+
119
+ ## Monitor the local environment in Pyric Studio
120
+
121
+ Pyric Studio shows local data, requests, authentication state, Security Rules verdicts, and denied operations. Open `/__pyric/ui/studio` on the development server and reproduce the failure. A denied request includes the path and verdict needed to correct the rule or the application code.
133
122
 
134
- ## Connect Coding Agents to the Sandbox using the Pyric MCP server
123
+ The Vite plugin discovers the Firestore rules path from `firebase.json`, or falls back to `firestore.rules`. Saving that file replaces the active local ruleset without a production deploy. A parse failure leaves the last valid ruleset active and reports the error in the development server.
135
124
 
136
- An agent can keep writing the same Firebase code while inspecting and changing the backend that the application is already using. Agent access is opt-in. Enable the MCP bridge in the Vite plugin:
125
+ State and test identities stay browser-local by default. Add `persist: true` to write a committable `.pyric/state/state.json`, or use `seed` to start from a known scenario:
137
126
 
138
127
  ```ts
139
- pyric({
140
- bridge: true
128
+ pyric({
129
+ persist: true,
130
+ seed: 'seed.json',
141
131
  });
142
132
  ```
143
133
 
144
- The bridge mounts on the Vite development origin and routes agent operations to the same SharedWorker backend used by the application and Studio.
145
-
146
- This protects the local workflow, not an independently credentialed process. A coding agent with production Firebase credentials or deployment access can still affect production outside Pyric.
134
+ See [persistence and multi-tab behavior](packages/site-docs/src/content/observe/shape-your-data.md) for reset and seed precedence.
147
135
 
148
- ## Verify the rules against production
136
+ ## Verify the rules against production
149
137
 
150
138
  Development sessions capture Firestore and Realtime Database operations in `.pyric/last-session.json` by default. Replay those requests against candidate rules before deployment:
151
139
 
@@ -153,7 +141,7 @@ Development sessions capture Firestore and Realtime Database operations in `.pyr
153
141
  npx pyric verify
154
142
  ```
155
143
 
156
- The default engine runs locally. The optional Firestore Rules Test API engine sends derived rule cases to Google's hosted evaluator when production-authority verification is needed. It verifies rules and does not deploy them. See [verify against a captured session](packages/cli/docs/how-to/verify-against-a-captured-session.md).
144
+ The default engine runs locally. The optional Firestore Rules Test API engine sends derived rule cases to Google's hosted evaluator when production-authority verification is needed. It verifies rules and does not deploy them. See [verify against a captured session](packages/site-docs/src/content/ship/ship-to-production.md).
157
145
 
158
146
  ## Ship the same application code
159
147
 
@@ -165,37 +153,24 @@ npm run build
165
153
 
166
154
  The built application contains the real Firebase SDK and uses the Firebase configuration already present in the source. Deploy the build, rules, and indexes with `firebase-tools` or the Firebase Console. Pyric has no production deployment path.
167
155
 
156
+ The mirrored data services do not connect to a production Firebase project. Local writes cannot delete production data or create Firebase usage charges, and local rules changes do not deploy. Pyric owns the development sandbox and verification workflow. Firebase owns production, with `firebase-tools` or the Firebase Console handling deployment.
157
+
158
+ Normal Auth, Firestore, Realtime Database, Storage, Messaging, and Firebase AI Logic usage still belongs in the [Firebase documentation](https://firebase.google.com/docs). Pyric documents the parts that differ locally, including supported behavior, Security Rules, persistence, inspection, verification, and known gaps. See [use the Vite plugin](packages/site-docs/src/content/get-started/vite.md) for the complete plugin contract.
159
+
168
160
  ## Check what Pyric mirrors
169
161
 
170
162
  Pyric is an independent implementation of observable Firebase behavior. Conformance evidence records what has been compared with Firebase and keeps five outcomes distinct: conforms, documented divergence, bug, unsupported, and unverified. The evidence is a floor, not a claim that every Firebase behavior has been measured.
171
163
 
172
- Ask about a developer-facing feature without nesting discovery under rules verification:
164
+ Ask about a developer-facing feature:
173
165
 
174
166
  ```bash
175
167
  npx pyric can-i-use getAfter
176
168
  npx pyric can-i-use firestore-rules/request.query --json
177
169
  ```
178
170
 
179
- The result reports availability, Firebase fidelity, and assurance eligibility as separate axes. Exact names succeed; a name shared by multiple surfaces asks you to qualify it, and fuzzy input prints labeled suggestions and exits nonzero instead of presenting a guess as a trust answer.
180
-
181
- Documentation and other Node consumers can bind the same feature name to the
182
- published import that exposes it, without maintaining a second surface map:
183
-
184
- ```ts
185
- import { canIUse, canIUseImport } from '@pyric/cli/conformance';
186
-
187
- const result = canIUse('getDownloadURL', { importPath: 'pyric/storage' });
188
- const evidence = canIUseImport('pyric/storage');
189
- const evidencePage = evidence && `/docs/${evidence.evidenceSlug}/`;
190
- ```
191
-
192
- Each support result carries only census-proven `importPaths`; registry ownership
193
- cannot invent package exports. `canIUseImport()` supplies the separate canonical
194
- import-to-evidence join used by generated API documentation. An unrelated
195
- import path returns no exact match rather than borrowing another surface's trust
196
- claim.
171
+ The result reports availability, Firebase fidelity, and assurance eligibility as separate axes. Exact names succeed; fuzzy input prints labeled suggestions and exits nonzero instead of presenting a guess as a trust answer.
197
172
 
198
- Read the generated [conformance scores](https://pyric.dev/docs/conformance-scores/) and the service matrices in the site's Conformance section. They are built from the same canonical registry used by assurance and `canIUse`, without committing duplicate Markdown. The [versioning and compatibility policy](packages/pyric/docs/explanation/versioning-and-compatibility.md) explains the release boundary.
173
+ Read the generated [conformance scores](https://pyric.dev/docs/conformance-scores/) and the service matrices in the site's Conformance section. They are built from the same canonical registry used by assurance and `canIUse`. Node consumers can query the same registry through [`@pyric/cli/conformance`](packages/site-docs/src/content/reference/cli.md). The [versioning and compatibility policy](packages/site-docs/src/content/trust/versioning-and-compatibility.md) explains the release boundary.
199
174
 
200
175
  ## Stability
201
176
 
@@ -212,4 +187,4 @@ bun run test
212
187
  bun run compat:check
213
188
  ```
214
189
 
215
- See [CONTRIBUTING.md](CONTRIBUTING.md), [CONTEXT.md](CONTEXT.md), and [AGENTS.md](AGENTS.md) before changing the codebase.
190
+ See [CONTEXT.md](CONTEXT.md) and [AGENTS.md](AGENTS.md) before changing the codebase.
@@ -0,0 +1,52 @@
1
+ /**
2
+ * Blocked-response detection: the ONE definition of "this envelope carries
3
+ * no usable content because a filter fired", shared by the two places that
4
+ * need it:
5
+ *
6
+ * - `response-helpers.ts`, which throws upstream's exact
7
+ * `Response error: Candidate was blocked due to <reason>` out of
8
+ * `text()` / `functionCalls()` / `inlineDataParts()` when the caller
9
+ * reaches for content that isn't there;
10
+ * - `broker/broker.ts`, which lands a `response_blocked` event on the
11
+ * sandbox's unified stream at RESPONSE time, so the block is visible
12
+ * even when nobody ever calls `text()` (an app that renders
13
+ * `response.text ?? ''` used to show an empty bubble and nothing else).
14
+ *
15
+ * A block is not a rejection: production answers HTTP 200 with an empty
16
+ * candidate and the reason in `finishReason` (SAFETY, RECITATION,
17
+ * BLOCKLIST, PROHIBITED_CONTENT, SPII, the IMAGE_* variants), or, when the
18
+ * PROMPT itself was refused, with no candidates at all and a
19
+ * `promptFeedback.blockReason`. Nothing throws on the wire path.
20
+ *
21
+ * Leaf module by construction: it imports only `./enums.js` (which imports
22
+ * nothing) and a type. `broker/` must NOT reach into `response-helpers.ts`
23
+ * for this: that file pulls `./errors.js`, which imports `./broker/index.js`
24
+ * as a value, and the round trip would be a real runtime cycle.
25
+ */
26
+ import type { WireResponse } from './broker/types.js';
27
+ /** Does this candidate's finish reason mean the content was withheld? */
28
+ export declare function isBlockingFinishReason(finishReason: string | undefined): boolean;
29
+ /** What a block was, in the wire's own vocabulary. Exactly one of
30
+ * `finishReason` (a candidate was withheld) / `blockReason` (the prompt was
31
+ * refused) is normally set; both may be absent when production sent
32
+ * `promptFeedback` with no reason at all. */
33
+ export interface ResponseBlock {
34
+ /** Candidate-level: the blocking `finishReason` off candidate 0. */
35
+ finishReason?: string;
36
+ /** Candidate-level: production's own explanatory `finishMessage`. */
37
+ finishMessage?: string;
38
+ /** Prompt-level: `promptFeedback.blockReason`. */
39
+ blockReason?: string;
40
+ }
41
+ /**
42
+ * Classify an envelope, mirroring `hasValidCandidates` in
43
+ * `response-helpers.ts` EXACTLY so the event stream and the thrown error can
44
+ * never disagree: candidate 0 with a blocking finish reason is a block; no
45
+ * candidate 0 at all plus any `promptFeedback` is a block; everything else
46
+ * (including a plain `STOP` or `MAX_TOKENS`) is not.
47
+ *
48
+ * Returns `null` when the response is fine, so a caller reads as
49
+ * `const block = describeResponseBlock(res); if (block !== null) …`.
50
+ */
51
+ export declare function describeResponseBlock(response: WireResponse): ResponseBlock | null;
52
+ //# sourceMappingURL=blocked.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"blocked.d.ts","sourceRoot":"","sources":["../../src/ai/blocked.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;GAwBG;AAEH,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,mBAAmB,CAAC;AA4BtD,yEAAyE;AACzE,wBAAgB,sBAAsB,CAAC,YAAY,EAAE,MAAM,GAAG,SAAS,GAAG,OAAO,CAGhF;AAED;;;8CAG8C;AAC9C,MAAM,WAAW,aAAa;IAC5B,oEAAoE;IACpE,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,qEAAqE;IACrE,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB,kDAAkD;IAClD,WAAW,CAAC,EAAE,MAAM,CAAC;CACtB;AAED;;;;;;;;;GASG;AACH,wBAAgB,qBAAqB,CAAC,QAAQ,EAAE,YAAY,GAAG,aAAa,GAAG,IAAI,CAalF"}
@@ -0,0 +1,83 @@
1
+ /**
2
+ * Blocked-response detection: the ONE definition of "this envelope carries
3
+ * no usable content because a filter fired", shared by the two places that
4
+ * need it:
5
+ *
6
+ * - `response-helpers.ts`, which throws upstream's exact
7
+ * `Response error: Candidate was blocked due to <reason>` out of
8
+ * `text()` / `functionCalls()` / `inlineDataParts()` when the caller
9
+ * reaches for content that isn't there;
10
+ * - `broker/broker.ts`, which lands a `response_blocked` event on the
11
+ * sandbox's unified stream at RESPONSE time, so the block is visible
12
+ * even when nobody ever calls `text()` (an app that renders
13
+ * `response.text ?? ''` used to show an empty bubble and nothing else).
14
+ *
15
+ * A block is not a rejection: production answers HTTP 200 with an empty
16
+ * candidate and the reason in `finishReason` (SAFETY, RECITATION,
17
+ * BLOCKLIST, PROHIBITED_CONTENT, SPII, the IMAGE_* variants), or, when the
18
+ * PROMPT itself was refused, with no candidates at all and a
19
+ * `promptFeedback.blockReason`. Nothing throws on the wire path.
20
+ *
21
+ * Leaf module by construction: it imports only `./enums.js` (which imports
22
+ * nothing) and a type. `broker/` must NOT reach into `response-helpers.ts`
23
+ * for this: that file pulls `./errors.js`, which imports `./broker/index.js`
24
+ * as a value, and the round trip would be a real runtime cycle.
25
+ */
26
+ import { FinishReason } from './enums.js';
27
+ /**
28
+ * Finish reasons that mean "no usable content": the installed
29
+ * `@firebase/ai@2.12.0` `badFinishReasons` set, verbatim and in upstream's
30
+ * order. Every one of these makes the response helpers throw instead of
31
+ * returning text, which is exactly the set worth announcing to a developer.
32
+ */
33
+ const BLOCKING_FINISH_REASONS = [
34
+ FinishReason.RECITATION,
35
+ FinishReason.SAFETY,
36
+ FinishReason.BLOCKLIST,
37
+ FinishReason.PROHIBITED_CONTENT,
38
+ FinishReason.SPII,
39
+ FinishReason.MALFORMED_FUNCTION_CALL,
40
+ FinishReason.IMAGE_SAFETY,
41
+ FinishReason.IMAGE_PROHIBITED_CONTENT,
42
+ FinishReason.IMAGE_OTHER,
43
+ FinishReason.NO_IMAGE,
44
+ FinishReason.IMAGE_RECITATION,
45
+ FinishReason.LANGUAGE,
46
+ FinishReason.UNEXPECTED_TOOL_CALL,
47
+ FinishReason.TOO_MANY_TOOL_CALLS,
48
+ FinishReason.MISSING_THOUGHT_SIGNATURE,
49
+ FinishReason.MALFORMED_RESPONSE,
50
+ ];
51
+ /** Does this candidate's finish reason mean the content was withheld? */
52
+ export function isBlockingFinishReason(finishReason) {
53
+ if (!finishReason)
54
+ return false;
55
+ return BLOCKING_FINISH_REASONS.some((reason) => reason === finishReason);
56
+ }
57
+ /**
58
+ * Classify an envelope, mirroring `hasValidCandidates` in
59
+ * `response-helpers.ts` EXACTLY so the event stream and the thrown error can
60
+ * never disagree: candidate 0 with a blocking finish reason is a block; no
61
+ * candidate 0 at all plus any `promptFeedback` is a block; everything else
62
+ * (including a plain `STOP` or `MAX_TOKENS`) is not.
63
+ *
64
+ * Returns `null` when the response is fine, so a caller reads as
65
+ * `const block = describeResponseBlock(res); if (block !== null) …`.
66
+ */
67
+ export function describeResponseBlock(response) {
68
+ const firstCandidate = response.candidates?.[0];
69
+ if (firstCandidate !== undefined) {
70
+ if (!isBlockingFinishReason(firstCandidate.finishReason))
71
+ return null;
72
+ const candidateBlock = {};
73
+ candidateBlock.finishReason = firstCandidate.finishReason;
74
+ candidateBlock.finishMessage = firstCandidate.finishMessage;
75
+ return candidateBlock;
76
+ }
77
+ if (response.promptFeedback === undefined)
78
+ return null;
79
+ const promptBlock = {};
80
+ promptBlock.blockReason = response.promptFeedback.blockReason;
81
+ return promptBlock;
82
+ }
83
+ //# sourceMappingURL=blocked.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"blocked.js","sourceRoot":"","sources":["../../src/ai/blocked.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;GAwBG;AAGH,OAAO,EAAE,YAAY,EAAE,MAAM,YAAY,CAAC;AAE1C;;;;;GAKG;AACH,MAAM,uBAAuB,GAAsB;IACjD,YAAY,CAAC,UAAU;IACvB,YAAY,CAAC,MAAM;IACnB,YAAY,CAAC,SAAS;IACtB,YAAY,CAAC,kBAAkB;IAC/B,YAAY,CAAC,IAAI;IACjB,YAAY,CAAC,uBAAuB;IACpC,YAAY,CAAC,YAAY;IACzB,YAAY,CAAC,wBAAwB;IACrC,YAAY,CAAC,WAAW;IACxB,YAAY,CAAC,QAAQ;IACrB,YAAY,CAAC,gBAAgB;IAC7B,YAAY,CAAC,QAAQ;IACrB,YAAY,CAAC,oBAAoB;IACjC,YAAY,CAAC,mBAAmB;IAChC,YAAY,CAAC,yBAAyB;IACtC,YAAY,CAAC,kBAAkB;CAChC,CAAC;AAEF,yEAAyE;AACzE,MAAM,UAAU,sBAAsB,CAAC,YAAgC;IACrE,IAAI,CAAC,YAAY;QAAE,OAAO,KAAK,CAAC;IAChC,OAAO,uBAAuB,CAAC,IAAI,CAAC,CAAC,MAAM,EAAE,EAAE,CAAC,MAAM,KAAK,YAAY,CAAC,CAAC;AAC3E,CAAC;AAeD;;;;;;;;;GASG;AACH,MAAM,UAAU,qBAAqB,CAAC,QAAsB;IAC1D,MAAM,cAAc,GAAG,QAAQ,CAAC,UAAU,EAAE,CAAC,CAAC,CAAC,CAAC;IAChD,IAAI,cAAc,KAAK,SAAS,EAAE,CAAC;QACjC,IAAI,CAAC,sBAAsB,CAAC,cAAc,CAAC,YAAY,CAAC;YAAE,OAAO,IAAI,CAAC;QACtE,MAAM,cAAc,GAAkB,EAAE,CAAC;QACzC,cAAc,CAAC,YAAY,GAAG,cAAc,CAAC,YAAY,CAAC;QAC1D,cAAc,CAAC,aAAa,GAAG,cAAc,CAAC,aAAa,CAAC;QAC5D,OAAO,cAAc,CAAC;IACxB,CAAC;IACD,IAAI,QAAQ,CAAC,cAAc,KAAK,SAAS;QAAE,OAAO,IAAI,CAAC;IACvD,MAAM,WAAW,GAAkB,EAAE,CAAC;IACtC,WAAW,CAAC,WAAW,GAAG,QAAQ,CAAC,cAAc,CAAC,WAAW,CAAC;IAC9D,OAAO,WAAW,CAAC;AACrB,CAAC"}
@@ -18,6 +18,17 @@
18
18
  * - a model-turn functionCall part with no thoughtSignature
19
19
  * → `ai-error-fncall-missing-thought-signature`
20
20
  *
21
+ * It also announces the refusals production does NOT throw for: a safety /
22
+ * recitation / blocklist block arrives as a 200 with an empty candidate, so
23
+ * the broker lands a `response_blocked` event ({@link emitBlockIfBlocked})
24
+ * alongside the normal op event. Without it a filtered answer is
25
+ * indistinguishable from a model that simply had nothing to say.
26
+ *
27
+ * The same goes for a model SUBSTITUTION ({@link emitModelSubstitution}): an
28
+ * engine that redirects the requested model (an openai `modelMap` entry or
29
+ * catch-all `model`, a gemini experimental alias) answers normally, so
30
+ * nothing anywhere says the developer tested a model they never named.
31
+ *
21
32
  * Event emission is best-effort behind one small choke-point ({@link emit}):
22
33
  * a throw from the emit path (or any event consumer downstream) must never
23
34
  * poison the AI operation the caller just completed — handler errors are the
@@ -43,6 +54,44 @@ export declare class AiBroker {
43
54
  private describeEngine;
44
55
  /** Additive detail fields every emitted event carries so Studio can show the engine. */
45
56
  private engineDetail;
57
+ /**
58
+ * Announce a SAFETY / RECITATION / BLOCKLIST / … block on the event stream.
59
+ *
60
+ * A block is the broker's SILENT refusal: unlike `request_rejected` nothing
61
+ * throws (production answers 200 with an empty candidate), so an app that
62
+ * renders whatever `text()` gave it just shows nothing, and the developer
63
+ * gets no signal anywhere. Emitting at RESPONSE time (not from the response
64
+ * helpers, which only fire if someone reaches for content) means the event
65
+ * lands whether or not the caller ever asks for the text.
66
+ *
67
+ * Detection is `blocked.ts`'s, shared verbatim with the response helpers'
68
+ * throw path, so the stream and the thrown error can never disagree.
69
+ *
70
+ * Returns whether the envelope WAS blocked, so the stream wrapper can
71
+ * announce at most once across its chunks.
72
+ */
73
+ private emitBlockIfBlocked;
74
+ /**
75
+ * Announce that the engine will answer as a DIFFERENT model than the one
76
+ * requested.
77
+ *
78
+ * A substitution is the quietest failure the broker has: nothing throws,
79
+ * content comes back, and the developer concludes they tested model X when
80
+ * model Y answered (an openai `modelMap` entry or catch-all `model`, a
81
+ * gemini experimental alias). Only engines that actually redirect implement
82
+ * {@link AnswerEngine.resolveEffectiveModel}; the rest are silent by
83
+ * construction.
84
+ *
85
+ * Fired at REQUEST time, before delegating: the swap is a property of the
86
+ * call about to go out, so it lands even when that call then fails.
87
+ * Comparison is bare-vs-bare, since `models/x` to `x` is the wire's prefix
88
+ * convention rather than a substitution, and an exact match emits nothing.
89
+ *
90
+ * `countTokens` is deliberately not announced: the openai engine never
91
+ * sends a model there at all (the count is synthesized locally), so an
92
+ * announcement would name an upstream call that never happens.
93
+ */
94
+ private emitModelSubstitution;
46
95
  private emitRejectionIfBrokerError;
47
96
  generateContent(req: GenerateContentRequest, model: string): Promise<WireResponse>;
48
97
  /**
@@ -1 +1 @@
1
- {"version":3,"file":"broker.d.ts","sourceRoot":"","sources":["../../../src/ai/broker/broker.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;GAyBG;AAEH,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,eAAe,CAAC;AAO7C,OAAO,KAAK,EACV,YAAY,EACZ,kBAAkB,EAClB,mBAAmB,EACnB,YAAY,EACZ,sBAAsB,EACtB,WAAW,EACX,SAAS,EACT,YAAY,EACb,MAAM,YAAY,CAAC;AAuBpB,MAAM,WAAW,eAAe;IAC9B,8EAA8E;IAC9E,MAAM,CAAC,EAAE,YAAY,GAAG,YAAY,CAAC;IACrC,oEAAoE;IACpE,OAAO,CAAC,EAAE,OAAO,CAAC;CACnB;AASD,qBAAa,QAAQ;IACnB,QAAQ,CAAC,MAAM,EAAE,YAAY,CAAC;IAC9B,OAAO,CAAC,QAAQ,CAAC,OAAO,CAAsB;IAC9C,OAAO,CAAC,QAAQ,CAAC,UAAU,CAAa;IACxC,OAAO,CAAC,QAAQ,CAAC,WAAW,CAAqB;IACjD,OAAO,CAAC,QAAQ,CAAC,aAAa,CAAqB;gBAEvC,OAAO,GAAE,eAAoB;IAsBzC,0EAA0E;IAC1E,OAAO,CAAC,cAAc;IAgBtB,wFAAwF;IACxF,OAAO,CAAC,YAAY;IAgBpB,OAAO,CAAC,0BAA0B;IAS5B,eAAe,CAAC,GAAG,EAAE,sBAAsB,EAAE,KAAK,EAAE,MAAM,GAAG,OAAO,CAAC,YAAY,CAAC;IAiBxF;;;;;OAKG;IACH,qBAAqB,CAAC,GAAG,EAAE,sBAAsB,EAAE,KAAK,EAAE,MAAM,GAAG,aAAa,CAAC,SAAS,CAAC;IAwBrF,WAAW,CAAC,GAAG,EAAE,kBAAkB,EAAE,KAAK,EAAE,MAAM,GAAG,OAAO,CAAC,mBAAmB,CAAC;IAcvF,OAAO,CAAC,QAAQ;IAgBhB,OAAO,CAAC,gBAAgB;IAaxB,8EAA8E;IAC9E,OAAO,CAAC,MAAM;IAWd;;;;OAIG;IACH,OAAO,CAAC,IAAI;CAkBb;AAED;;;;GAIG;AACH,wBAAgB,uBAAuB,CAAC,OAAO,EAAE,OAAO,GAAG,WAAW,CAYrE"}
1
+ {"version":3,"file":"broker.d.ts","sourceRoot":"","sources":["../../../src/ai/broker/broker.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAoCG;AAEH,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,eAAe,CAAC;AAQ7C,OAAO,KAAK,EACV,YAAY,EACZ,kBAAkB,EAClB,mBAAmB,EACnB,YAAY,EACZ,sBAAsB,EAEtB,WAAW,EACX,SAAS,EACT,YAAY,EACb,MAAM,YAAY,CAAC;AAuBpB,MAAM,WAAW,eAAe;IAC9B,8EAA8E;IAC9E,MAAM,CAAC,EAAE,YAAY,GAAG,YAAY,CAAC;IACrC,oEAAoE;IACpE,OAAO,CAAC,EAAE,OAAO,CAAC;CACnB;AASD,qBAAa,QAAQ;IACnB,QAAQ,CAAC,MAAM,EAAE,YAAY,CAAC;IAC9B,OAAO,CAAC,QAAQ,CAAC,OAAO,CAAsB;IAC9C,OAAO,CAAC,QAAQ,CAAC,UAAU,CAAa;IACxC,OAAO,CAAC,QAAQ,CAAC,WAAW,CAAqB;IACjD,OAAO,CAAC,QAAQ,CAAC,aAAa,CAAqB;gBAEvC,OAAO,GAAE,eAAoB;IAsBzC,0EAA0E;IAC1E,OAAO,CAAC,cAAc;IAgBtB,wFAAwF;IACxF,OAAO,CAAC,YAAY;IAgBpB;;;;;;;;;;;;;;;OAeG;IACH,OAAO,CAAC,kBAAkB;IAO1B;;;;;;;;;;;;;;;;;;;OAmBG;IACH,OAAO,CAAC,qBAAqB;IAmB7B,OAAO,CAAC,0BAA0B;IAS5B,eAAe,CAAC,GAAG,EAAE,sBAAsB,EAAE,KAAK,EAAE,MAAM,GAAG,OAAO,CAAC,YAAY,CAAC;IAmBxF;;;;;OAKG;IACH,qBAAqB,CAAC,GAAG,EAAE,sBAAsB,EAAE,KAAK,EAAE,MAAM,GAAG,aAAa,CAAC,SAAS,CAAC;IAmCrF,WAAW,CAAC,GAAG,EAAE,kBAAkB,EAAE,KAAK,EAAE,MAAM,GAAG,OAAO,CAAC,mBAAmB,CAAC;IAcvF,OAAO,CAAC,QAAQ;IAgBhB,OAAO,CAAC,gBAAgB;IAaxB,8EAA8E;IAC9E,OAAO,CAAC,MAAM;IAWd;;;;OAIG;IACH,OAAO,CAAC,IAAI;CAkBb;AAED;;;;GAIG;AACH,wBAAgB,uBAAuB,CAAC,OAAO,EAAE,OAAO,GAAG,WAAW,CAYrE"}
@@ -18,6 +18,17 @@
18
18
  * - a model-turn functionCall part with no thoughtSignature
19
19
  * → `ai-error-fncall-missing-thought-signature`
20
20
  *
21
+ * It also announces the refusals production does NOT throw for: a safety /
22
+ * recitation / blocklist block arrives as a 200 with an empty candidate, so
23
+ * the broker lands a `response_blocked` event ({@link emitBlockIfBlocked})
24
+ * alongside the normal op event. Without it a filtered answer is
25
+ * indistinguishable from a model that simply had nothing to say.
26
+ *
27
+ * The same goes for a model SUBSTITUTION ({@link emitModelSubstitution}): an
28
+ * engine that redirects the requested model (an openai `modelMap` entry or
29
+ * catch-all `model`, a gemini experimental alias) answers normally, so
30
+ * nothing anywhere says the developer tested a model they never named.
31
+ *
21
32
  * Event emission is best-effort behind one small choke-point ({@link emit}):
22
33
  * a throw from the emit path (or any event consumer downstream) must never
23
34
  * poison the AI operation the caller just completed — handler errors are the
@@ -25,6 +36,7 @@
25
36
  * isolates listener throws; this guard covers the emit path itself).
26
37
  */
27
38
  import { emitSandboxEvent, makeServiceMutationEvent } from 'pyric/sandbox/internal';
39
+ import { describeResponseBlock } from '../blocked.js';
28
40
  import { AiBrokerError, Synthesizer, badRole, emptyContents, missingThoughtSignature } from './synthesizer.js';
29
41
  import { ScriptedEngine } from './scripted-engine.js';
30
42
  import { OpenAiEngine } from './openai-engine.js';
@@ -114,6 +126,70 @@ export class AiBroker {
114
126
  }
115
127
  return detail;
116
128
  }
129
+ /**
130
+ * Announce a SAFETY / RECITATION / BLOCKLIST / … block on the event stream.
131
+ *
132
+ * A block is the broker's SILENT refusal: unlike `request_rejected` nothing
133
+ * throws (production answers 200 with an empty candidate), so an app that
134
+ * renders whatever `text()` gave it just shows nothing, and the developer
135
+ * gets no signal anywhere. Emitting at RESPONSE time (not from the response
136
+ * helpers, which only fire if someone reaches for content) means the event
137
+ * lands whether or not the caller ever asks for the text.
138
+ *
139
+ * Detection is `blocked.ts`'s, shared verbatim with the response helpers'
140
+ * throw path, so the stream and the thrown error can never disagree.
141
+ *
142
+ * Returns whether the envelope WAS blocked, so the stream wrapper can
143
+ * announce at most once across its chunks.
144
+ */
145
+ emitBlockIfBlocked(model, response) {
146
+ const block = describeResponseBlock(response);
147
+ if (block === null)
148
+ return false;
149
+ this.emit('response_blocked', model, { ...block });
150
+ return true;
151
+ }
152
+ /**
153
+ * Announce that the engine will answer as a DIFFERENT model than the one
154
+ * requested.
155
+ *
156
+ * A substitution is the quietest failure the broker has: nothing throws,
157
+ * content comes back, and the developer concludes they tested model X when
158
+ * model Y answered (an openai `modelMap` entry or catch-all `model`, a
159
+ * gemini experimental alias). Only engines that actually redirect implement
160
+ * {@link AnswerEngine.resolveEffectiveModel}; the rest are silent by
161
+ * construction.
162
+ *
163
+ * Fired at REQUEST time, before delegating: the swap is a property of the
164
+ * call about to go out, so it lands even when that call then fails.
165
+ * Comparison is bare-vs-bare, since `models/x` to `x` is the wire's prefix
166
+ * convention rather than a substitution, and an exact match emits nothing.
167
+ *
168
+ * `countTokens` is deliberately not announced: the openai engine never
169
+ * sends a model there at all (the count is synthesized locally), so an
170
+ * announcement would name an upstream call that never happens.
171
+ */
172
+ emitModelSubstitution(model) {
173
+ const resolve = this.engine.resolveEffectiveModel;
174
+ if (typeof resolve !== 'function')
175
+ return;
176
+ let effective;
177
+ try {
178
+ effective = resolve.call(this.engine, model);
179
+ }
180
+ catch {
181
+ // A misbehaving engine's self-report must never fail the op.
182
+ return;
183
+ }
184
+ const requestedBare = model.startsWith('models/') ? model.slice('models/'.length) : model;
185
+ if (effective.model === requestedBare)
186
+ return;
187
+ this.emit('model_substituted', model, {
188
+ requestedModel: model,
189
+ effectiveModel: effective.model,
190
+ reason: effective.reason,
191
+ });
192
+ }
117
193
  emitRejectionIfBrokerError(model, err) {
118
194
  const code = err instanceof AiBrokerError ? err.envelope.error.code : 500;
119
195
  const status = err instanceof AiBrokerError ? err.envelope.error.status : 'INTERNAL';
@@ -124,6 +200,7 @@ export class AiBroker {
124
200
  }
125
201
  async generateContent(req, model) {
126
202
  this.validate(req, model);
203
+ this.emitModelSubstitution(model);
127
204
  try {
128
205
  const response = await this.engine.generateContent(req, model);
129
206
  this.emit('generate_content', model, {
@@ -132,6 +209,7 @@ export class AiBroker {
132
209
  totalTokenCount: response.usageMetadata?.totalTokenCount,
133
210
  responseId: response.responseId,
134
211
  });
212
+ this.emitBlockIfBlocked(model, response);
135
213
  return response;
136
214
  }
137
215
  catch (err) {
@@ -147,17 +225,29 @@ export class AiBroker {
147
225
  */
148
226
  streamGenerateContent(req, model) {
149
227
  this.validate(req, model);
228
+ this.emitModelSubstitution(model);
150
229
  const inner = this.engine.streamGenerateContent(req, model);
151
230
  const emit = (chunkCount) => this.emit('stream_generate_content', model, {
152
231
  contentCount: req.contents.length,
153
232
  chunkCount,
154
233
  });
155
234
  const emitRejection = (err) => this.emitRejectionIfBrokerError(model, err);
235
+ // A block surfaces on whichever chunk carries the finish reason (the last
236
+ // one, per the captured framing), so it is announced DURING iteration:
237
+ // an abandoned stream still reports why it produced nothing. At most one
238
+ // per stream: the aggregate has a single blocking reason, not one per chunk.
239
+ let announcedBlock = false;
240
+ const emitBlock = (chunk) => {
241
+ if (announcedBlock)
242
+ return;
243
+ announcedBlock = this.emitBlockIfBlocked(model, chunk);
244
+ };
156
245
  return (async function* wrapped() {
157
246
  let chunkCount = 0;
158
247
  try {
159
248
  for await (const chunk of inner) {
160
249
  chunkCount++;
250
+ emitBlock(chunk);
161
251
  yield chunk;
162
252
  }
163
253
  emit(chunkCount);
@@ -1 +1 @@
1
- {"version":3,"file":"broker.js","sourceRoot":"","sources":["../../../src/ai/broker/broker.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"broker.js","sourceRoot":"","sources":["../../../src/ai/broker/broker.ts"],"names":[],"mappings":"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"}