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.
- package/README.md +64 -89
- package/dist/ai/blocked.d.ts +52 -0
- package/dist/ai/blocked.d.ts.map +1 -0
- package/dist/ai/blocked.js +83 -0
- package/dist/ai/blocked.js.map +1 -0
- package/dist/ai/broker/broker.d.ts +49 -0
- package/dist/ai/broker/broker.d.ts.map +1 -1
- package/dist/ai/broker/broker.js +90 -0
- package/dist/ai/broker/broker.js.map +1 -1
- package/dist/ai/broker/gemini-engine.d.ts +14 -1
- package/dist/ai/broker/gemini-engine.d.ts.map +1 -1
- package/dist/ai/broker/gemini-engine.js +30 -9
- package/dist/ai/broker/gemini-engine.js.map +1 -1
- package/dist/ai/broker/index.d.ts +2 -2
- package/dist/ai/broker/index.d.ts.map +1 -1
- package/dist/ai/broker/index.js +2 -2
- package/dist/ai/broker/index.js.map +1 -1
- package/dist/ai/broker/openai-engine.d.ts +10 -1
- package/dist/ai/broker/openai-engine.d.ts.map +1 -1
- package/dist/ai/broker/openai-engine.js +26 -4
- package/dist/ai/broker/openai-engine.js.map +1 -1
- package/dist/ai/broker/synthesizer.d.ts +14 -0
- package/dist/ai/broker/synthesizer.d.ts.map +1 -1
- package/dist/ai/broker/synthesizer.js +31 -0
- package/dist/ai/broker/synthesizer.js.map +1 -1
- package/dist/ai/broker/types.d.ts +24 -0
- package/dist/ai/broker/types.d.ts.map +1 -1
- package/dist/ai/internal.d.ts +5 -0
- package/dist/ai/internal.d.ts.map +1 -1
- package/dist/ai/internal.js +5 -0
- package/dist/ai/internal.js.map +1 -1
- package/dist/ai/response-helpers.d.ts.map +1 -1
- package/dist/ai/response-helpers.js +6 -24
- package/dist/ai/response-helpers.js.map +1 -1
- package/dist/analytics/index.d.ts +40 -0
- package/dist/analytics/index.d.ts.map +1 -0
- package/dist/analytics/index.js +25 -0
- package/dist/analytics/index.js.map +1 -0
- package/dist/app-check/index.d.ts +31 -0
- package/dist/app-check/index.d.ts.map +1 -0
- package/dist/app-check/index.js +20 -0
- package/dist/app-check/index.js.map +1 -0
- package/dist/database/controls.d.ts +1 -1
- package/dist/database/controls.js +1 -1
- package/dist/database/sandbox/rules-eval.d.ts +1 -1
- package/dist/database/sandbox/rules-eval.d.ts.map +1 -1
- package/dist/database/sandbox/rules-eval.js +2 -2
- package/dist/database/sandbox/rules-eval.js.map +1 -1
- package/dist/database/sandbox/write-plane.js +2 -2
- package/dist/database/sandbox/write-plane.js.map +1 -1
- package/dist/database/sandbox-controls.d.ts +12 -5
- package/dist/database/sandbox-controls.d.ts.map +1 -1
- package/dist/database/sandbox-controls.js +20 -9
- package/dist/database/sandbox-controls.js.map +1 -1
- package/dist/deferred/entry.d.ts +75 -0
- package/dist/deferred/entry.d.ts.map +1 -0
- package/dist/deferred/entry.js +143 -0
- package/dist/deferred/entry.js.map +1 -0
- package/dist/firestore/lite.d.ts +72 -0
- package/dist/firestore/lite.d.ts.map +1 -0
- package/dist/firestore/lite.js +22 -0
- package/dist/firestore/lite.js.map +1 -0
- package/dist/firestore/persistence.d.ts +2 -2
- package/dist/firestore/persistence.js +1 -1
- package/dist/functions/index.d.ts +29 -0
- package/dist/functions/index.d.ts.map +1 -0
- package/dist/functions/index.js +20 -0
- package/dist/functions/index.js.map +1 -0
- package/dist/performance/index.d.ts +22 -0
- package/dist/performance/index.d.ts.map +1 -0
- package/dist/performance/index.js +19 -0
- package/dist/performance/index.js.map +1 -0
- package/dist/remote-config/index.d.ts +40 -0
- package/dist/remote-config/index.d.ts.map +1 -0
- package/dist/remote-config/index.js +25 -0
- package/dist/remote-config/index.js.map +1 -0
- package/dist/rules/grammar/FirestoreValidator.d.ts.map +1 -1
- package/dist/rules/grammar/FirestoreValidator.js +43 -27
- package/dist/rules/grammar/FirestoreValidator.js.map +1 -1
- package/dist/rules/grammar/document-access-count.d.ts +24 -0
- package/dist/rules/grammar/document-access-count.d.ts.map +1 -0
- package/dist/rules/grammar/document-access-count.js +100 -0
- package/dist/rules/grammar/document-access-count.js.map +1 -0
- package/dist/rules/linter/ast-utils.d.ts +2 -6
- package/dist/rules/linter/ast-utils.d.ts.map +1 -1
- package/dist/rules/linter/ast-utils.js +2 -63
- package/dist/rules/linter/ast-utils.js.map +1 -1
- package/dist/rules/linter/hallucinations.d.ts +8 -0
- package/dist/rules/linter/hallucinations.d.ts.map +1 -1
- package/dist/rules/linter/hallucinations.js +121 -31
- package/dist/rules/linter/hallucinations.js.map +1 -1
- package/dist/rules/linter/linter.d.ts +10 -1
- package/dist/rules/linter/linter.d.ts.map +1 -1
- package/dist/rules/linter/linter.js +23 -9
- package/dist/rules/linter/linter.js.map +1 -1
- package/dist/rules/rtdb/grammar/simulator.d.ts.map +1 -1
- package/dist/rules/rtdb/grammar/simulator.js +13 -13
- package/dist/rules/rtdb/grammar/simulator.js.map +1 -1
- package/dist/rules/rtdb/simulation/handler.d.ts.map +1 -1
- package/dist/rules/rtdb/simulation/handler.js +96 -35
- package/dist/rules/rtdb/simulation/handler.js.map +1 -1
- package/dist/rules/simulator/document-lookups.d.ts +4 -0
- package/dist/rules/simulator/document-lookups.d.ts.map +1 -1
- package/dist/rules/simulator/document-lookups.js +42 -3
- package/dist/rules/simulator/document-lookups.js.map +1 -1
- package/dist/rules/simulator/eval-error.d.ts +11 -0
- package/dist/rules/simulator/eval-error.d.ts.map +1 -1
- package/dist/rules/simulator/eval-error.js +14 -0
- package/dist/rules/simulator/eval-error.js.map +1 -1
- package/dist/rules/simulator/evaluation-builtins.d.ts.map +1 -1
- package/dist/rules/simulator/evaluation-builtins.js +33 -8
- package/dist/rules/simulator/evaluation-builtins.js.map +1 -1
- package/dist/rules/simulator/evaluation-context.d.ts +12 -0
- package/dist/rules/simulator/evaluation-context.d.ts.map +1 -1
- package/dist/rules/simulator/evaluator.d.ts +1 -1
- package/dist/rules/simulator/evaluator.d.ts.map +1 -1
- package/dist/rules/simulator/evaluator.js +16 -4
- package/dist/rules/simulator/evaluator.js.map +1 -1
- package/dist/rules/simulator/handler.d.ts.map +1 -1
- package/dist/rules/simulator/handler.js +52 -5
- package/dist/rules/simulator/handler.js.map +1 -1
- package/dist/rules/simulator/lookup-budget.d.ts +37 -0
- package/dist/rules/simulator/lookup-budget.d.ts.map +1 -0
- package/dist/rules/simulator/lookup-budget.js +96 -0
- package/dist/rules/simulator/lookup-budget.js.map +1 -0
- package/dist/rules/stdlib-modules.d.ts +7 -0
- package/dist/rules/stdlib-modules.d.ts.map +1 -1
- package/dist/rules/stdlib-modules.js +11 -11
- package/dist/rules/stdlib-modules.js.map +1 -1
- package/dist/sandbox/admin-firestore/get-firestore.d.ts +6 -0
- package/dist/sandbox/admin-firestore/get-firestore.d.ts.map +1 -1
- package/dist/sandbox/admin-firestore/get-firestore.js +22 -3
- package/dist/sandbox/admin-firestore/get-firestore.js.map +1 -1
- package/dist/sandbox/index.d.ts +1 -1
- package/dist/sandbox/index.d.ts.map +1 -1
- package/dist/sandbox/index.js +1 -1
- package/dist/sandbox/index.js.map +1 -1
- package/dist/sandbox/persistence/types.d.ts +1 -1
- package/dist/sandbox/remote.d.ts +2 -2
- package/dist/sandbox/sandbox-context.d.ts +8 -1
- package/dist/sandbox/sandbox-context.d.ts.map +1 -1
- package/dist/sandbox/sandbox-context.js +59 -8
- package/dist/sandbox/sandbox-context.js.map +1 -1
- package/dist/sandbox/types/auth-state.d.ts +1 -0
- package/dist/sandbox/types/auth-state.d.ts.map +1 -1
- package/dist/sandbox/types/events.d.ts +2 -1
- package/dist/sandbox/types/events.d.ts.map +1 -1
- package/dist/sandbox/types/operation.d.ts +1 -0
- package/dist/sandbox/types/operation.d.ts.map +1 -1
- package/dist/storage/enforce.d.ts +20 -1
- package/dist/storage/enforce.d.ts.map +1 -1
- package/dist/storage/enforce.js +47 -14
- package/dist/storage/enforce.js.map +1 -1
- package/dist/storage/index.d.ts +1 -1
- package/dist/storage/index.d.ts.map +1 -1
- package/dist/storage/list.js +1 -1
- package/dist/storage/list.js.map +1 -1
- package/dist/storage/persistence.d.ts +81 -17
- package/dist/storage/persistence.d.ts.map +1 -1
- package/dist/storage/persistence.js +267 -50
- package/dist/storage/persistence.js.map +1 -1
- package/dist/storage/sandbox/rules-evaluation-error.d.ts +25 -0
- package/dist/storage/sandbox/rules-evaluation-error.d.ts.map +1 -1
- package/dist/storage/sandbox/rules-evaluation-error.js +29 -0
- package/dist/storage/sandbox/rules-evaluation-error.js.map +1 -1
- package/dist/storage/sandbox/rules-evaluator.d.ts +10 -5
- package/dist/storage/sandbox/rules-evaluator.d.ts.map +1 -1
- package/dist/storage/sandbox/rules-evaluator.js +105 -33
- package/dist/storage/sandbox/rules-evaluator.js.map +1 -1
- package/dist/storage/sandbox/rules-methods.d.ts.map +1 -1
- package/dist/storage/sandbox/rules-methods.js +17 -7
- package/dist/storage/sandbox/rules-methods.js.map +1 -1
- package/dist/storage/service.d.ts +25 -6
- package/dist/storage/service.d.ts.map +1 -1
- package/dist/storage/service.js +98 -46
- package/dist/storage/service.js.map +1 -1
- package/package.json +41 -1
- package/src/ai/blocked.ts +97 -0
- package/src/ai/broker/broker.ts +88 -0
- package/src/ai/broker/gemini-engine.ts +36 -9
- package/src/ai/broker/index.ts +2 -1
- package/src/ai/broker/openai-engine.ts +31 -3
- package/src/ai/broker/synthesizer.ts +34 -0
- package/src/ai/broker/types.ts +25 -0
- package/src/ai/internal.ts +5 -0
- package/src/ai/response-helpers.ts +6 -25
- package/src/analytics/index.ts +53 -0
- package/src/app/dispatch.test.ts +3 -1
- package/src/app-check/index.ts +41 -0
- package/src/database/controls.ts +1 -1
- package/src/database/sandbox/rules-eval.ts +2 -2
- package/src/database/sandbox/write-plane.ts +2 -2
- package/src/database/sandbox-controls.ts +29 -9
- package/src/deferred/entry.ts +167 -0
- package/src/firestore/lite.ts +91 -0
- package/src/firestore/persistence.ts +2 -2
- package/src/functions/index.ts +38 -0
- package/src/performance/index.ts +31 -0
- package/src/remote-config/index.ts +53 -0
- package/src/rules/grammar/FirestoreValidator.ts +44 -28
- package/src/rules/grammar/document-access-count.ts +75 -0
- package/src/rules/linter/ast-utils.ts +2 -36
- package/src/rules/linter/hallucinations.ts +123 -25
- package/src/rules/linter/linter.ts +32 -10
- package/src/rules/rtdb/grammar/simulator.ts +12 -13
- package/src/rules/rtdb/simulation/handler.ts +149 -46
- package/src/rules/simulator/document-lookups.ts +46 -3
- package/src/rules/simulator/eval-error.ts +16 -0
- package/src/rules/simulator/evaluation-builtins.ts +35 -8
- package/src/rules/simulator/evaluation-context.ts +12 -0
- package/src/rules/simulator/evaluator.ts +14 -3
- package/src/rules/simulator/handler.ts +68 -4
- package/src/rules/simulator/lookup-budget.ts +101 -0
- package/src/rules/stdlib-modules.ts +19 -11
- package/src/sandbox/admin-firestore/get-firestore.ts +19 -3
- package/src/sandbox/index.ts +5 -1
- package/src/sandbox/persistence/types.ts +1 -1
- package/src/sandbox/remote.ts +2 -2
- package/src/sandbox/sandbox-context.ts +69 -9
- package/src/sandbox/types/auth-state.ts +1 -1
- package/src/sandbox/types/events.ts +2 -1
- package/src/sandbox/types/operation.ts +1 -1
- package/src/storage/enforce.ts +53 -14
- package/src/storage/index.ts +1 -1
- package/src/storage/list.ts +1 -1
- package/src/storage/persistence.ts +291 -63
- package/src/storage/sandbox/rules-evaluation-error.ts +32 -0
- package/src/storage/sandbox/rules-evaluator.ts +109 -32
- package/src/storage/sandbox/rules-methods.ts +21 -7
- 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
|
|
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.
|
|
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
|
-
|
|
13
|
+
Application code stays canonical Firebase:
|
|
14
14
|
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
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
|
-
|
|
28
|
-
|
|
29
|
-
|
|
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
|
-
|
|
32
|
-
|
|
27
|
+
export const auth = getAuth(app);
|
|
28
|
+
export const db = getFirestore(app);
|
|
33
29
|
```
|
|
34
30
|
|
|
35
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
##
|
|
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.
|
|
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
|
-
##
|
|
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
|
-
|
|
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
|
-
|
|
108
|
-
|
|
82
|
+
```bash
|
|
83
|
+
npx plugins add davideast/pyric
|
|
109
84
|
```
|
|
110
85
|
|
|
111
|
-
|
|
86
|
+
Antigravity CLI and OpenCode use the standalone skill installer:
|
|
112
87
|
|
|
113
|
-
|
|
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
|
-
|
|
92
|
+
# OpenCode
|
|
93
|
+
npx skills add https://github.com/davideast/pyric/tree/main/pyric-plugin/skills/pyric --agent opencode
|
|
94
|
+
```
|
|
116
95
|
|
|
117
|
-
|
|
96
|
+
Then invoke the skill:
|
|
118
97
|
|
|
119
|
-
|
|
98
|
+
| Agent | Enter |
|
|
99
|
+
|---|---|
|
|
100
|
+
| Codex | `$pyric` |
|
|
101
|
+
| Claude Code | `/pyric:pyric` |
|
|
102
|
+
| Antigravity CLI | `/pyric` |
|
|
103
|
+
| OpenCode | `/pyric` |
|
|
120
104
|
|
|
121
|
-
The
|
|
105
|
+
The leading `$` or `/` is part of the command.
|
|
122
106
|
|
|
123
|
-
|
|
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
|
-
|
|
128
|
-
seed: 'seed.json',
|
|
113
|
+
bridge: true
|
|
129
114
|
});
|
|
130
115
|
```
|
|
131
116
|
|
|
132
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
128
|
+
pyric({
|
|
129
|
+
persist: true,
|
|
130
|
+
seed: 'seed.json',
|
|
141
131
|
});
|
|
142
132
|
```
|
|
143
133
|
|
|
144
|
-
|
|
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/
|
|
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
|
|
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;
|
|
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
|
|
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 [
|
|
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
|
|
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"}
|
package/dist/ai/broker/broker.js
CHANGED
|
@@ -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":"AAAA
|
|
1
|
+
{"version":3,"file":"broker.js","sourceRoot":"","sources":["../../../src/ai/broker/broker.ts"],"names":[],"mappings":"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"}
|