pyric 0.1.0-alpha.20 → 0.1.0-alpha.21
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +64 -89
- package/dist/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/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 +23 -2
- package/dist/storage/service.d.ts.map +1 -1
- package/dist/storage/service.js +36 -26
- 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/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 +92 -27
|
@@ -48,6 +48,19 @@ export interface FirebaseStorage {
|
|
|
48
48
|
export type AppFirebaseStorage = FirebaseStorage & {
|
|
49
49
|
readonly app: FirebaseApp;
|
|
50
50
|
};
|
|
51
|
+
/**
|
|
52
|
+
* Cross-service IAM posture for `firestore.get()/exists()` in Storage rules.
|
|
53
|
+
*
|
|
54
|
+
* Production Storage rules can read Firestore ONLY when the project's
|
|
55
|
+
* Storage service agent holds `roles/firebaserules.firestoreServiceAgent`.
|
|
56
|
+
* `'granted'` (the default, the common configured-project state) serves
|
|
57
|
+
* lookups from the same-sandbox Firestore store; `'denied'` makes every
|
|
58
|
+
* EXECUTED lookup fail exactly like production without the role (error →
|
|
59
|
+
* rule denies), while short-circuited lookups are never executed and stay
|
|
60
|
+
* unaffected. Captured boundary: conformance observation
|
|
61
|
+
* `stdlib-realstorage-p3-lookup-budget` (registry row storage-rules#134).
|
|
62
|
+
*/
|
|
63
|
+
export type CrossServiceIam = 'granted' | 'denied';
|
|
51
64
|
/**
|
|
52
65
|
* Internal sandbox service — owns the IDB connection + parsed rules.
|
|
53
66
|
* Only constructed inside the sandbox `getStorageSandbox` path.
|
|
@@ -55,7 +68,8 @@ export type AppFirebaseStorage = FirebaseStorage & {
|
|
|
55
68
|
export declare class StorageService {
|
|
56
69
|
readonly backend: StorageBackend;
|
|
57
70
|
readonly rules: StorageRules | null;
|
|
58
|
-
|
|
71
|
+
readonly crossServiceIam: CrossServiceIam;
|
|
72
|
+
constructor(backend: StorageBackend, rules?: StorageRules | null, crossServiceIam?: CrossServiceIam);
|
|
59
73
|
}
|
|
60
74
|
/** Options for {@link getStorageSandbox}. */
|
|
61
75
|
export interface StorageOptions {
|
|
@@ -78,7 +92,7 @@ export interface StorageOptions {
|
|
|
78
92
|
* origin-scoped, so without this every project served on the same
|
|
79
93
|
* localhost port shared one storage database (issue #359). Ignored when
|
|
80
94
|
* an explicit `dbName` is given; only honored on the FIRST call per
|
|
81
|
-
* `Sandbox`. Hosts pass their project identity here (`pyric
|
|
95
|
+
* `Sandbox`. Hosts pass their project identity here (`pyric sandbox` passes
|
|
82
96
|
* the served project's key; app handles pass `options.projectId`).
|
|
83
97
|
*/
|
|
84
98
|
projectId?: string;
|
|
@@ -88,6 +102,13 @@ export interface StorageOptions {
|
|
|
88
102
|
* `Sandbox`.
|
|
89
103
|
*/
|
|
90
104
|
rules?: string;
|
|
105
|
+
/**
|
|
106
|
+
* Model the production Storage→Firestore IAM dependency (see
|
|
107
|
+
* {@link CrossServiceIam}). Defaults to `'granted'`. Like `rules`,
|
|
108
|
+
* only honored on the FIRST storage call per `Sandbox`; a LATER,
|
|
109
|
+
* DIFFERING value throws instead of silently flipping the mode.
|
|
110
|
+
*/
|
|
111
|
+
crossServiceIam?: CrossServiceIam;
|
|
91
112
|
}
|
|
92
113
|
/**
|
|
93
114
|
* Construct (or return cached) a sandbox-backed `FirebaseStorage`
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"service.d.ts","sourceRoot":"","sources":["../../src/storage/service.ts"],"names":[],"mappings":"AAqBA,OAAO,KAAK,EAAE,SAAS,EAAE,eAAe,EAAE,OAAO,EAAE,cAAc,EAAE,MAAM,eAAe,CAAC;AACzF,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,iBAAiB,CAAC;AAMnD,OAAO,EAAqC,KAAK,cAAc,EAAE,MAAM,kBAAkB,CAAC;AAC1F,OAAO,EAAqB,KAAK,YAAY,EAAE,MAAM,oBAAoB,CAAC;AAE1E,OAAO,EAEL,KAAK,sBAAsB,EAC5B,MAAM,uBAAuB,CAAC;AAS/B;;;GAGG;AACH,eAAO,MAAM,aAAa,EAAE,OAAO,MAAuC,CAAC;AAE3E;;;GAGG;AACH,MAAM,WAAW,aAAa;IAC5B,QAAQ,CAAC,IAAI,EAAE,SAAS,CAAC;IACzB,QAAQ,CAAC,OAAO,EAAE,OAAO,CAAC;IAC1B,QAAQ,CAAC,OAAO,EAAE,cAAc,CAAC;IACjC,iFAAiF;IACjF,QAAQ,CAAC,WAAW,CAAC,EAAE,MAAM,SAAS,CAAC;IACvC,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;IACxB,QAAQ,CAAC,cAAc,EAAE,OAAO,CAAC,cAAc,CAAC,CAAC;IACjD;;;;;;;;;OASG;IACH,QAAQ,CAAC,KAAK,CAAC,EAAE,OAAO,CAAC;CAC1B;AAED,MAAM,MAAM,MAAM,GAAG,aAAa,CAAC;AAEnC,wBAAgB,WAAW,CAAC,MAAM,EAAE,aAAa,GAAG,SAAS,CAE5D;AAED;;;;;GAKG;AACH,MAAM,WAAW,eAAe;IAC9B,QAAQ,CAAC,CAAC,aAAa,CAAC,EAAE,MAAM,CAAC;IACjC,QAAQ,CAAC,GAAG,CAAC,EAAE,WAAW,CAAC;CAC5B;AAED,gEAAgE;AAChE,MAAM,MAAM,kBAAkB,GAAG,eAAe,GAAG;IAAE,QAAQ,CAAC,GAAG,EAAE,WAAW,CAAA;CAAE,CAAC;AAEjF;;;GAGG;AACH,qBAAa,cAAc;IAEvB,QAAQ,CAAC,OAAO,EAAE,cAAc;IAChC,QAAQ,CAAC,KAAK,EAAE,YAAY,GAAG,IAAI;
|
|
1
|
+
{"version":3,"file":"service.d.ts","sourceRoot":"","sources":["../../src/storage/service.ts"],"names":[],"mappings":"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"}
|
package/dist/storage/service.js
CHANGED
|
@@ -45,14 +45,15 @@ export function storageAuth(target) {
|
|
|
45
45
|
export class StorageService {
|
|
46
46
|
backend;
|
|
47
47
|
rules;
|
|
48
|
-
|
|
48
|
+
crossServiceIam;
|
|
49
|
+
constructor(backend, rules = null, crossServiceIam = 'granted') {
|
|
49
50
|
this.backend = backend;
|
|
50
51
|
this.rules = rules;
|
|
52
|
+
this.crossServiceIam = crossServiceIam;
|
|
51
53
|
}
|
|
52
54
|
}
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
const SERVICES = new WeakMap();
|
|
55
|
+
/** One open storage service, with its opening configuration, per `Sandbox`. */
|
|
56
|
+
const OPEN_SERVICES = new WeakMap();
|
|
56
57
|
/** One sandbox `FirebaseStorage` handle per `SandboxContext`. */
|
|
57
58
|
const SANDBOX_HANDLES = new WeakMap();
|
|
58
59
|
/**
|
|
@@ -65,15 +66,25 @@ const SANDBOX_HANDLES = new WeakMap();
|
|
|
65
66
|
*/
|
|
66
67
|
const BARE_SANDBOX_HANDLES = new WeakMap();
|
|
67
68
|
/**
|
|
68
|
-
*
|
|
69
|
-
*
|
|
70
|
-
*
|
|
71
|
-
*
|
|
72
|
-
*
|
|
73
|
-
*
|
|
69
|
+
* Reject a late option that differs from the one the service is already
|
|
70
|
+
* open with. Both `rules` and `crossServiceIam` are honored only on the
|
|
71
|
+
* FIRST storage call per `Sandbox`. Silently discarding a later, differing
|
|
72
|
+
* value would wipe the rules, or reopen the false-ALLOW surface the IAM
|
|
73
|
+
* mode models, without a word. Re-supplying the IDENTICAL value stays fine
|
|
74
|
+
* (idempotent multi-handle construction, for example per-user contexts),
|
|
75
|
+
* and so does omitting it.
|
|
74
76
|
*/
|
|
75
|
-
|
|
76
|
-
|
|
77
|
+
function rejectDifferingLateConfig(option, descriptor, supplied, openedWith, caller) {
|
|
78
|
+
if (supplied === undefined || supplied === openedWith)
|
|
79
|
+
return;
|
|
80
|
+
const openedDescription = openedWith === null
|
|
81
|
+
? `without ${option}`
|
|
82
|
+
: `with a different ${descriptor}`;
|
|
83
|
+
throw new Error(`pyric/storage: ${caller} received a ${descriptor}, but this sandbox's storage service ` +
|
|
84
|
+
`is already open ${openedDescription}. ${option} is honored only on the FIRST storage ` +
|
|
85
|
+
'call per Sandbox, so this value would be silently discarded. Configure it on the first ' +
|
|
86
|
+
'storage call for this sandbox.');
|
|
87
|
+
}
|
|
77
88
|
/**
|
|
78
89
|
* Get (or open) the ONE per-sandbox `StorageService`. Loud on the
|
|
79
90
|
* silent-rules-wipe hazard: when the service is already open and the
|
|
@@ -82,16 +93,11 @@ const SERVICE_RULES_RESOLUTION = new WeakMap();
|
|
|
82
93
|
* on the first storage call for the sandbox instead.
|
|
83
94
|
*/
|
|
84
95
|
function ensureService(sandbox, options, caller) {
|
|
85
|
-
const existing =
|
|
96
|
+
const existing = OPEN_SERVICES.get(sandbox);
|
|
86
97
|
if (existing) {
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
`service is already open ${openedWith === null ? 'without rules' : 'with a different rules source'} ` +
|
|
91
|
-
'— rules are honored only on the FIRST storage call per Sandbox, so these rules ' +
|
|
92
|
-
'would be silently discarded. Configure rules on the first storage call for this sandbox.');
|
|
93
|
-
}
|
|
94
|
-
return existing;
|
|
98
|
+
rejectDifferingLateConfig('rules', 'rules source', options.rules, existing.rulesSource, caller);
|
|
99
|
+
rejectDifferingLateConfig('crossServiceIam', 'crossServiceIam mode', options.crossServiceIam, existing.crossServiceIam, caller);
|
|
100
|
+
return existing.service;
|
|
95
101
|
}
|
|
96
102
|
let rules = null;
|
|
97
103
|
let resolution = null;
|
|
@@ -116,10 +122,14 @@ function ensureService(sandbox, options, caller) {
|
|
|
116
122
|
}
|
|
117
123
|
// Explicit dbName wins; otherwise scope the default by project identity so
|
|
118
124
|
// two projects on one origin never share a storage database (issue #359).
|
|
119
|
-
const
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
125
|
+
const crossServiceIam = options.crossServiceIam ?? 'granted';
|
|
126
|
+
const servicePromise = openStorageBackend(options.dbName ?? storageDbName(options.projectId)).then((backend) => new StorageService(backend, rules, crossServiceIam));
|
|
127
|
+
OPEN_SERVICES.set(sandbox, {
|
|
128
|
+
service: servicePromise,
|
|
129
|
+
rulesSource: options.rules ?? null,
|
|
130
|
+
rulesResolution: resolution,
|
|
131
|
+
crossServiceIam,
|
|
132
|
+
});
|
|
123
133
|
// Join the sandbox's persistable-service REGISTRY so `sandbox.resetAll()`
|
|
124
134
|
// reaches storage (issue #359: Studio's reset cleared Firestore + auth but
|
|
125
135
|
// never storage — storage was invisible to the sandbox). Storage does NOT
|
|
@@ -287,6 +297,6 @@ export function getStorageService(storage) {
|
|
|
287
297
|
}
|
|
288
298
|
/** Internal assurance seam for the exact rules source retained by Storage setup. */
|
|
289
299
|
export function getStorageRulesResolution(storage) {
|
|
290
|
-
return
|
|
300
|
+
return OPEN_SERVICES.get(targetOf(storage).sandbox)?.rulesResolution ?? null;
|
|
291
301
|
}
|
|
292
302
|
//# sourceMappingURL=service.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"service.js","sourceRoot":"","sources":["../../src/storage/service.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;GAmBG;AACH,OAAO,EAAE,kBAAkB,EAAE,MAAM,eAAe,CAAC;AAGnD,OAAO,EACL,oBAAoB,EACpB,6BAA6B,EAC7B,uBAAuB,GACxB,MAAM,wBAAwB,CAAC;AAChC,OAAO,EAAE,kBAAkB,EAAE,aAAa,EAAuB,MAAM,kBAAkB,CAAC;AAC1F,OAAO,EAAE,iBAAiB,EAAqB,MAAM,oBAAoB,CAAC;AAC1E,OAAO,EAAE,qBAAqB,EAAE,MAAM,sCAAsC,CAAC;AAC7E,OAAO,EACL,4BAA4B,GAE7B,MAAM,uBAAuB,CAAC;AAE/B;;;;GAIG;AACH,MAAM,cAAc,GAAG,eAAe,CAAC;AAEvC;;;GAGG;AACH,MAAM,CAAC,MAAM,aAAa,GAAkB,MAAM,CAAC,sBAAsB,CAAC,CAAC;AA6B3E,MAAM,UAAU,WAAW,CAAC,MAAqB;IAC/C,OAAO,MAAM,CAAC,WAAW,EAAE,EAAE,IAAI,MAAM,CAAC,OAAO,CAAC,IAAI,CAAC;AACvD,CAAC;
|
|
1
|
+
{"version":3,"file":"service.js","sourceRoot":"","sources":["../../src/storage/service.ts"],"names":[],"mappings":"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"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "pyric",
|
|
3
|
-
"version": "0.1.0-alpha.
|
|
3
|
+
"version": "0.1.0-alpha.21",
|
|
4
4
|
"license": "Apache-2.0",
|
|
5
5
|
"homepage": "https://pyric.dev",
|
|
6
6
|
"repository": {
|
|
@@ -13,6 +13,22 @@
|
|
|
13
13
|
},
|
|
14
14
|
"description": "Firebase for agents \u2014 modular Web SDK adapters (firestore, auth, database, storage) with swappable sandbox/prod backends, plus rules tooling and in-process sandbox. Mirrors firebase.",
|
|
15
15
|
"type": "module",
|
|
16
|
+
"sideEffects": [
|
|
17
|
+
"./dist/app/register.js",
|
|
18
|
+
"./dist/app/registry.js",
|
|
19
|
+
"./dist/app/runtime.js",
|
|
20
|
+
"./dist/firestore/sandbox/write-runtime.js",
|
|
21
|
+
"./dist/rules/grammar/FirestoreParser.js",
|
|
22
|
+
"./dist/sandbox/admin-firestore/listeners.js",
|
|
23
|
+
"./dist/sandbox/internal/firebase-error.js",
|
|
24
|
+
"./src/app/register.ts",
|
|
25
|
+
"./src/app/registry.ts",
|
|
26
|
+
"./src/app/runtime.ts",
|
|
27
|
+
"./src/firestore/sandbox/write-runtime.ts",
|
|
28
|
+
"./src/rules/grammar/FirestoreParser.ts",
|
|
29
|
+
"./src/sandbox/admin-firestore/listeners.ts",
|
|
30
|
+
"./src/sandbox/internal/firebase-error.ts"
|
|
31
|
+
],
|
|
16
32
|
"exports": {
|
|
17
33
|
"./app": {
|
|
18
34
|
"types": "./dist/app/index.d.ts",
|
|
@@ -38,6 +54,10 @@
|
|
|
38
54
|
"types": "./dist/firestore/internal/value-codec.d.ts",
|
|
39
55
|
"import": "./dist/firestore/internal/value-codec.js"
|
|
40
56
|
},
|
|
57
|
+
"./firestore/lite": {
|
|
58
|
+
"types": "./dist/firestore/lite.d.ts",
|
|
59
|
+
"import": "./dist/firestore/lite.js"
|
|
60
|
+
},
|
|
41
61
|
"./auth": {
|
|
42
62
|
"types": "./dist/auth/index.d.ts",
|
|
43
63
|
"import": "./dist/auth/index.js"
|
|
@@ -125,6 +145,26 @@
|
|
|
125
145
|
"./sandbox/admin-firestore": {
|
|
126
146
|
"types": "./dist/sandbox/admin-firestore/index.d.ts",
|
|
127
147
|
"import": "./dist/sandbox/admin-firestore/index.js"
|
|
148
|
+
},
|
|
149
|
+
"./app-check": {
|
|
150
|
+
"types": "./dist/app-check/index.d.ts",
|
|
151
|
+
"import": "./dist/app-check/index.js"
|
|
152
|
+
},
|
|
153
|
+
"./functions": {
|
|
154
|
+
"types": "./dist/functions/index.d.ts",
|
|
155
|
+
"import": "./dist/functions/index.js"
|
|
156
|
+
},
|
|
157
|
+
"./analytics": {
|
|
158
|
+
"types": "./dist/analytics/index.d.ts",
|
|
159
|
+
"import": "./dist/analytics/index.js"
|
|
160
|
+
},
|
|
161
|
+
"./performance": {
|
|
162
|
+
"types": "./dist/performance/index.d.ts",
|
|
163
|
+
"import": "./dist/performance/index.js"
|
|
164
|
+
},
|
|
165
|
+
"./remote-config": {
|
|
166
|
+
"types": "./dist/remote-config/index.d.ts",
|
|
167
|
+
"import": "./dist/remote-config/index.js"
|
|
128
168
|
}
|
|
129
169
|
},
|
|
130
170
|
"files": [
|
|
@@ -0,0 +1,97 @@
|
|
|
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
|
+
|
|
27
|
+
import type { WireResponse } from './broker/types.js';
|
|
28
|
+
import { FinishReason } from './enums.js';
|
|
29
|
+
|
|
30
|
+
/**
|
|
31
|
+
* Finish reasons that mean "no usable content": the installed
|
|
32
|
+
* `@firebase/ai@2.12.0` `badFinishReasons` set, verbatim and in upstream's
|
|
33
|
+
* order. Every one of these makes the response helpers throw instead of
|
|
34
|
+
* returning text, which is exactly the set worth announcing to a developer.
|
|
35
|
+
*/
|
|
36
|
+
const BLOCKING_FINISH_REASONS: readonly string[] = [
|
|
37
|
+
FinishReason.RECITATION,
|
|
38
|
+
FinishReason.SAFETY,
|
|
39
|
+
FinishReason.BLOCKLIST,
|
|
40
|
+
FinishReason.PROHIBITED_CONTENT,
|
|
41
|
+
FinishReason.SPII,
|
|
42
|
+
FinishReason.MALFORMED_FUNCTION_CALL,
|
|
43
|
+
FinishReason.IMAGE_SAFETY,
|
|
44
|
+
FinishReason.IMAGE_PROHIBITED_CONTENT,
|
|
45
|
+
FinishReason.IMAGE_OTHER,
|
|
46
|
+
FinishReason.NO_IMAGE,
|
|
47
|
+
FinishReason.IMAGE_RECITATION,
|
|
48
|
+
FinishReason.LANGUAGE,
|
|
49
|
+
FinishReason.UNEXPECTED_TOOL_CALL,
|
|
50
|
+
FinishReason.TOO_MANY_TOOL_CALLS,
|
|
51
|
+
FinishReason.MISSING_THOUGHT_SIGNATURE,
|
|
52
|
+
FinishReason.MALFORMED_RESPONSE,
|
|
53
|
+
];
|
|
54
|
+
|
|
55
|
+
/** Does this candidate's finish reason mean the content was withheld? */
|
|
56
|
+
export function isBlockingFinishReason(finishReason: string | undefined): boolean {
|
|
57
|
+
if (!finishReason) return false;
|
|
58
|
+
return BLOCKING_FINISH_REASONS.some((reason) => reason === finishReason);
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
/** What a block was, in the wire's own vocabulary. Exactly one of
|
|
62
|
+
* `finishReason` (a candidate was withheld) / `blockReason` (the prompt was
|
|
63
|
+
* refused) is normally set; both may be absent when production sent
|
|
64
|
+
* `promptFeedback` with no reason at all. */
|
|
65
|
+
export interface ResponseBlock {
|
|
66
|
+
/** Candidate-level: the blocking `finishReason` off candidate 0. */
|
|
67
|
+
finishReason?: string;
|
|
68
|
+
/** Candidate-level: production's own explanatory `finishMessage`. */
|
|
69
|
+
finishMessage?: string;
|
|
70
|
+
/** Prompt-level: `promptFeedback.blockReason`. */
|
|
71
|
+
blockReason?: string;
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
/**
|
|
75
|
+
* Classify an envelope, mirroring `hasValidCandidates` in
|
|
76
|
+
* `response-helpers.ts` EXACTLY so the event stream and the thrown error can
|
|
77
|
+
* never disagree: candidate 0 with a blocking finish reason is a block; no
|
|
78
|
+
* candidate 0 at all plus any `promptFeedback` is a block; everything else
|
|
79
|
+
* (including a plain `STOP` or `MAX_TOKENS`) is not.
|
|
80
|
+
*
|
|
81
|
+
* Returns `null` when the response is fine, so a caller reads as
|
|
82
|
+
* `const block = describeResponseBlock(res); if (block !== null) …`.
|
|
83
|
+
*/
|
|
84
|
+
export function describeResponseBlock(response: WireResponse): ResponseBlock | null {
|
|
85
|
+
const firstCandidate = response.candidates?.[0];
|
|
86
|
+
if (firstCandidate !== undefined) {
|
|
87
|
+
if (!isBlockingFinishReason(firstCandidate.finishReason)) return null;
|
|
88
|
+
const candidateBlock: ResponseBlock = {};
|
|
89
|
+
candidateBlock.finishReason = firstCandidate.finishReason;
|
|
90
|
+
candidateBlock.finishMessage = firstCandidate.finishMessage;
|
|
91
|
+
return candidateBlock;
|
|
92
|
+
}
|
|
93
|
+
if (response.promptFeedback === undefined) return null;
|
|
94
|
+
const promptBlock: ResponseBlock = {};
|
|
95
|
+
promptBlock.blockReason = response.promptFeedback.blockReason;
|
|
96
|
+
return promptBlock;
|
|
97
|
+
}
|
package/src/ai/broker/broker.ts
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
|
|
@@ -28,6 +39,7 @@
|
|
|
28
39
|
import type { Sandbox } from 'pyric/sandbox';
|
|
29
40
|
import { emitSandboxEvent, makeServiceMutationEvent } from 'pyric/sandbox/internal';
|
|
30
41
|
|
|
42
|
+
import { describeResponseBlock } from '../blocked.js';
|
|
31
43
|
import { AiBrokerError, Synthesizer, badRole, emptyContents, missingThoughtSignature } from './synthesizer.js';
|
|
32
44
|
import { ScriptedEngine } from './scripted-engine.js';
|
|
33
45
|
import { OpenAiEngine } from './openai-engine.js';
|
|
@@ -38,6 +50,7 @@ import type {
|
|
|
38
50
|
CountTokensResponse,
|
|
39
51
|
EngineConfig,
|
|
40
52
|
GenerateContentRequest,
|
|
53
|
+
ModelResolution,
|
|
41
54
|
RawEnvelope,
|
|
42
55
|
WireChunk,
|
|
43
56
|
WireResponse,
|
|
@@ -141,6 +154,68 @@ export class AiBroker {
|
|
|
141
154
|
return detail;
|
|
142
155
|
}
|
|
143
156
|
|
|
157
|
+
/**
|
|
158
|
+
* Announce a SAFETY / RECITATION / BLOCKLIST / … block on the event stream.
|
|
159
|
+
*
|
|
160
|
+
* A block is the broker's SILENT refusal: unlike `request_rejected` nothing
|
|
161
|
+
* throws (production answers 200 with an empty candidate), so an app that
|
|
162
|
+
* renders whatever `text()` gave it just shows nothing, and the developer
|
|
163
|
+
* gets no signal anywhere. Emitting at RESPONSE time (not from the response
|
|
164
|
+
* helpers, which only fire if someone reaches for content) means the event
|
|
165
|
+
* lands whether or not the caller ever asks for the text.
|
|
166
|
+
*
|
|
167
|
+
* Detection is `blocked.ts`'s, shared verbatim with the response helpers'
|
|
168
|
+
* throw path, so the stream and the thrown error can never disagree.
|
|
169
|
+
*
|
|
170
|
+
* Returns whether the envelope WAS blocked, so the stream wrapper can
|
|
171
|
+
* announce at most once across its chunks.
|
|
172
|
+
*/
|
|
173
|
+
private emitBlockIfBlocked(model: string, response: WireResponse): boolean {
|
|
174
|
+
const block = describeResponseBlock(response);
|
|
175
|
+
if (block === null) return false;
|
|
176
|
+
this.emit('response_blocked', model, { ...block });
|
|
177
|
+
return true;
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
/**
|
|
181
|
+
* Announce that the engine will answer as a DIFFERENT model than the one
|
|
182
|
+
* requested.
|
|
183
|
+
*
|
|
184
|
+
* A substitution is the quietest failure the broker has: nothing throws,
|
|
185
|
+
* content comes back, and the developer concludes they tested model X when
|
|
186
|
+
* model Y answered (an openai `modelMap` entry or catch-all `model`, a
|
|
187
|
+
* gemini experimental alias). Only engines that actually redirect implement
|
|
188
|
+
* {@link AnswerEngine.resolveEffectiveModel}; the rest are silent by
|
|
189
|
+
* construction.
|
|
190
|
+
*
|
|
191
|
+
* Fired at REQUEST time, before delegating: the swap is a property of the
|
|
192
|
+
* call about to go out, so it lands even when that call then fails.
|
|
193
|
+
* Comparison is bare-vs-bare, since `models/x` to `x` is the wire's prefix
|
|
194
|
+
* convention rather than a substitution, and an exact match emits nothing.
|
|
195
|
+
*
|
|
196
|
+
* `countTokens` is deliberately not announced: the openai engine never
|
|
197
|
+
* sends a model there at all (the count is synthesized locally), so an
|
|
198
|
+
* announcement would name an upstream call that never happens.
|
|
199
|
+
*/
|
|
200
|
+
private emitModelSubstitution(model: string): void {
|
|
201
|
+
const resolve = this.engine.resolveEffectiveModel;
|
|
202
|
+
if (typeof resolve !== 'function') return;
|
|
203
|
+
let effective: ModelResolution;
|
|
204
|
+
try {
|
|
205
|
+
effective = resolve.call(this.engine, model);
|
|
206
|
+
} catch {
|
|
207
|
+
// A misbehaving engine's self-report must never fail the op.
|
|
208
|
+
return;
|
|
209
|
+
}
|
|
210
|
+
const requestedBare = model.startsWith('models/') ? model.slice('models/'.length) : model;
|
|
211
|
+
if (effective.model === requestedBare) return;
|
|
212
|
+
this.emit('model_substituted', model, {
|
|
213
|
+
requestedModel: model,
|
|
214
|
+
effectiveModel: effective.model,
|
|
215
|
+
reason: effective.reason,
|
|
216
|
+
});
|
|
217
|
+
}
|
|
218
|
+
|
|
144
219
|
private emitRejectionIfBrokerError(model: string, err: unknown): void {
|
|
145
220
|
const code = err instanceof AiBrokerError ? err.envelope.error.code : 500;
|
|
146
221
|
const status = err instanceof AiBrokerError ? err.envelope.error.status : 'INTERNAL';
|
|
@@ -152,6 +227,7 @@ export class AiBroker {
|
|
|
152
227
|
|
|
153
228
|
async generateContent(req: GenerateContentRequest, model: string): Promise<WireResponse> {
|
|
154
229
|
this.validate(req, model);
|
|
230
|
+
this.emitModelSubstitution(model);
|
|
155
231
|
try {
|
|
156
232
|
const response = await this.engine.generateContent(req, model);
|
|
157
233
|
this.emit('generate_content', model, {
|
|
@@ -160,6 +236,7 @@ export class AiBroker {
|
|
|
160
236
|
totalTokenCount: response.usageMetadata?.totalTokenCount,
|
|
161
237
|
responseId: response.responseId,
|
|
162
238
|
});
|
|
239
|
+
this.emitBlockIfBlocked(model, response);
|
|
163
240
|
return response;
|
|
164
241
|
} catch (err) {
|
|
165
242
|
this.emitRejectionIfBrokerError(model, err);
|
|
@@ -175,6 +252,7 @@ export class AiBroker {
|
|
|
175
252
|
*/
|
|
176
253
|
streamGenerateContent(req: GenerateContentRequest, model: string): AsyncIterable<WireChunk> {
|
|
177
254
|
this.validate(req, model);
|
|
255
|
+
this.emitModelSubstitution(model);
|
|
178
256
|
const inner = this.engine.streamGenerateContent(req, model);
|
|
179
257
|
const emit = (chunkCount: number) =>
|
|
180
258
|
this.emit('stream_generate_content', model, {
|
|
@@ -182,11 +260,21 @@ export class AiBroker {
|
|
|
182
260
|
chunkCount,
|
|
183
261
|
});
|
|
184
262
|
const emitRejection = (err: unknown) => this.emitRejectionIfBrokerError(model, err);
|
|
263
|
+
// A block surfaces on whichever chunk carries the finish reason (the last
|
|
264
|
+
// one, per the captured framing), so it is announced DURING iteration:
|
|
265
|
+
// an abandoned stream still reports why it produced nothing. At most one
|
|
266
|
+
// per stream: the aggregate has a single blocking reason, not one per chunk.
|
|
267
|
+
let announcedBlock = false;
|
|
268
|
+
const emitBlock = (chunk: WireChunk) => {
|
|
269
|
+
if (announcedBlock) return;
|
|
270
|
+
announcedBlock = this.emitBlockIfBlocked(model, chunk);
|
|
271
|
+
};
|
|
185
272
|
return (async function* wrapped(): AsyncGenerator<WireChunk> {
|
|
186
273
|
let chunkCount = 0;
|
|
187
274
|
try {
|
|
188
275
|
for await (const chunk of inner) {
|
|
189
276
|
chunkCount++;
|
|
277
|
+
emitBlock(chunk);
|
|
190
278
|
yield chunk;
|
|
191
279
|
}
|
|
192
280
|
emit(chunkCount);
|
|
@@ -10,17 +10,21 @@
|
|
|
10
10
|
* without exposing secrets to the browser.
|
|
11
11
|
*/
|
|
12
12
|
|
|
13
|
-
import { AiBrokerError, errorEnvelope } from './synthesizer.js';
|
|
13
|
+
import { AiBrokerError, errorEnvelope, redactUrl } from './synthesizer.js';
|
|
14
14
|
import type {
|
|
15
15
|
AnswerEngine,
|
|
16
16
|
CountTokensRequest,
|
|
17
17
|
CountTokensResponse,
|
|
18
18
|
GenerateContentRequest,
|
|
19
|
+
ModelResolution,
|
|
19
20
|
WireChunk,
|
|
20
21
|
WireResponse,
|
|
21
22
|
} from './types.js';
|
|
22
23
|
|
|
23
|
-
|
|
24
|
+
/** Where the gemini engine talks when no `baseUrl` overrides it. Published
|
|
25
|
+
* through `pyric/ai/internal` so the CLI's startup banner can name the same
|
|
26
|
+
* endpoint instead of keeping its own copy. */
|
|
27
|
+
export const GEMINI_DEFAULT_BASE_URL = 'https://generativelanguage.googleapis.com';
|
|
24
28
|
|
|
25
29
|
/**
|
|
26
30
|
* BROWSER-CLEAN: acquires `node:child_process` lazily via `process.getBuiltinModule`
|
|
@@ -58,7 +62,7 @@ export class GeminiEngine implements AnswerEngine {
|
|
|
58
62
|
private readonly explicitKey?: string;
|
|
59
63
|
|
|
60
64
|
constructor(options?: GeminiEngineOptions) {
|
|
61
|
-
this.baseUrl = (options?.baseUrl ??
|
|
65
|
+
this.baseUrl = (options?.baseUrl ?? GEMINI_DEFAULT_BASE_URL).replace(/\/$/, '');
|
|
62
66
|
this.fetchImpl = options?.fetch ?? ((input, init) => fetch(input, init));
|
|
63
67
|
this.explicitKey = options?.apiKey;
|
|
64
68
|
}
|
|
@@ -114,15 +118,29 @@ export class GeminiEngine implements AnswerEngine {
|
|
|
114
118
|
);
|
|
115
119
|
}
|
|
116
120
|
|
|
117
|
-
|
|
121
|
+
/**
|
|
122
|
+
* Which model this engine ACTUALLY calls upstream, and why it differs.
|
|
123
|
+
* Experimental and superseded flash aliases are redirected onto the served
|
|
124
|
+
* `gemini-flash-lite-latest`; everything else passes through. Reported so
|
|
125
|
+
* the broker can announce the redirect rather than let a developer believe
|
|
126
|
+
* `gemini-2.5-flash` answered when `gemini-flash-lite-latest` did
|
|
127
|
+
* ({@link AnswerEngine.resolveEffectiveModel}).
|
|
128
|
+
*/
|
|
129
|
+
resolveEffectiveModel(model: string): ModelResolution {
|
|
118
130
|
const stripped = model.replace(/^models\//, '');
|
|
119
131
|
const isExperimentalFlashLite =
|
|
120
132
|
stripped === 'gemini-3.5-flash-lite' ||
|
|
121
133
|
stripped === 'gemini-2.5-flash' ||
|
|
122
134
|
stripped === 'gemini-2.5-flash-lite' ||
|
|
123
135
|
stripped === 'gemini-1.5-flash';
|
|
124
|
-
|
|
125
|
-
|
|
136
|
+
if (isExperimentalFlashLite) {
|
|
137
|
+
return { model: 'gemini-flash-lite-latest', reason: 'experimental alias' };
|
|
138
|
+
}
|
|
139
|
+
return { model: stripped, reason: 'passthrough' };
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
private normalizeModel(model: string): string {
|
|
143
|
+
return `models/${this.resolveEffectiveModel(model).model}`;
|
|
126
144
|
}
|
|
127
145
|
|
|
128
146
|
/**
|
|
@@ -169,9 +187,18 @@ export class GeminiEngine implements AnswerEngine {
|
|
|
169
187
|
body: JSON.stringify(body),
|
|
170
188
|
});
|
|
171
189
|
} catch (err) {
|
|
172
|
-
|
|
190
|
+
// Redact both: `url` always carries the key when auth is a static key
|
|
191
|
+
// (not bearer/ADC); `message` is defensively redacted too in case the
|
|
192
|
+
// fetch implementation's own error text echoes the request URL.
|
|
193
|
+
const rawMessage = err instanceof Error ? err.message : String(err);
|
|
194
|
+
const safeUrl = redactUrl(url);
|
|
195
|
+
const safeMessage = redactUrl(rawMessage);
|
|
173
196
|
throw new AiBrokerError(
|
|
174
|
-
errorEnvelope(
|
|
197
|
+
errorEnvelope(
|
|
198
|
+
502,
|
|
199
|
+
`Failed to connect to Gemini API at ${safeUrl}: ${safeMessage}`,
|
|
200
|
+
'UNAVAILABLE',
|
|
201
|
+
),
|
|
175
202
|
);
|
|
176
203
|
}
|
|
177
204
|
|
|
@@ -181,7 +208,7 @@ export class GeminiEngine implements AnswerEngine {
|
|
|
181
208
|
throw new AiBrokerError(
|
|
182
209
|
errorEnvelope(
|
|
183
210
|
response.status,
|
|
184
|
-
`Gemini API returned status ${response.status}: ${errorText}`,
|
|
211
|
+
`Gemini API returned status ${response.status}: ${redactUrl(errorText)}`,
|
|
185
212
|
'INTERNAL',
|
|
186
213
|
),
|
|
187
214
|
);
|
package/src/ai/broker/index.ts
CHANGED
|
@@ -7,7 +7,7 @@
|
|
|
7
7
|
|
|
8
8
|
export { AiBroker, loadObservationEnvelope, type AiBrokerOptions } from './broker.js';
|
|
9
9
|
export { ScriptedEngine, lastUserText, promptTextOf } from './scripted-engine.js';
|
|
10
|
-
export { GeminiEngine, type GeminiEngineOptions } from './gemini-engine.js';
|
|
10
|
+
export { GeminiEngine, GEMINI_DEFAULT_BASE_URL, type GeminiEngineOptions } from './gemini-engine.js';
|
|
11
11
|
export {
|
|
12
12
|
OpenAiEngine,
|
|
13
13
|
ToolCallBuffer,
|
|
@@ -37,6 +37,7 @@ export {
|
|
|
37
37
|
estimateTokens,
|
|
38
38
|
mintThoughtSignature,
|
|
39
39
|
mintFunctionCallId,
|
|
40
|
+
redactUrl,
|
|
40
41
|
type SynthesizeOptions,
|
|
41
42
|
} from './synthesizer.js';
|
|
42
43
|
export type {
|