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.
Files changed (225) 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/sandbox/rules-evaluation-error.d.ts +25 -0
  159. package/dist/storage/sandbox/rules-evaluation-error.d.ts.map +1 -1
  160. package/dist/storage/sandbox/rules-evaluation-error.js +29 -0
  161. package/dist/storage/sandbox/rules-evaluation-error.js.map +1 -1
  162. package/dist/storage/sandbox/rules-evaluator.d.ts +10 -5
  163. package/dist/storage/sandbox/rules-evaluator.d.ts.map +1 -1
  164. package/dist/storage/sandbox/rules-evaluator.js +105 -33
  165. package/dist/storage/sandbox/rules-evaluator.js.map +1 -1
  166. package/dist/storage/sandbox/rules-methods.d.ts.map +1 -1
  167. package/dist/storage/sandbox/rules-methods.js +17 -7
  168. package/dist/storage/sandbox/rules-methods.js.map +1 -1
  169. package/dist/storage/service.d.ts +23 -2
  170. package/dist/storage/service.d.ts.map +1 -1
  171. package/dist/storage/service.js +36 -26
  172. package/dist/storage/service.js.map +1 -1
  173. package/package.json +41 -1
  174. package/src/ai/blocked.ts +97 -0
  175. package/src/ai/broker/broker.ts +88 -0
  176. package/src/ai/broker/gemini-engine.ts +36 -9
  177. package/src/ai/broker/index.ts +2 -1
  178. package/src/ai/broker/openai-engine.ts +31 -3
  179. package/src/ai/broker/synthesizer.ts +34 -0
  180. package/src/ai/broker/types.ts +25 -0
  181. package/src/ai/internal.ts +5 -0
  182. package/src/ai/response-helpers.ts +6 -25
  183. package/src/analytics/index.ts +53 -0
  184. package/src/app/dispatch.test.ts +3 -1
  185. package/src/app-check/index.ts +41 -0
  186. package/src/database/controls.ts +1 -1
  187. package/src/database/sandbox/rules-eval.ts +2 -2
  188. package/src/database/sandbox/write-plane.ts +2 -2
  189. package/src/database/sandbox-controls.ts +29 -9
  190. package/src/deferred/entry.ts +167 -0
  191. package/src/firestore/lite.ts +91 -0
  192. package/src/firestore/persistence.ts +2 -2
  193. package/src/functions/index.ts +38 -0
  194. package/src/performance/index.ts +31 -0
  195. package/src/remote-config/index.ts +53 -0
  196. package/src/rules/grammar/FirestoreValidator.ts +44 -28
  197. package/src/rules/grammar/document-access-count.ts +75 -0
  198. package/src/rules/linter/ast-utils.ts +2 -36
  199. package/src/rules/linter/hallucinations.ts +123 -25
  200. package/src/rules/linter/linter.ts +32 -10
  201. package/src/rules/rtdb/grammar/simulator.ts +12 -13
  202. package/src/rules/rtdb/simulation/handler.ts +149 -46
  203. package/src/rules/simulator/document-lookups.ts +46 -3
  204. package/src/rules/simulator/eval-error.ts +16 -0
  205. package/src/rules/simulator/evaluation-builtins.ts +35 -8
  206. package/src/rules/simulator/evaluation-context.ts +12 -0
  207. package/src/rules/simulator/evaluator.ts +14 -3
  208. package/src/rules/simulator/handler.ts +68 -4
  209. package/src/rules/simulator/lookup-budget.ts +101 -0
  210. package/src/rules/stdlib-modules.ts +19 -11
  211. package/src/sandbox/admin-firestore/get-firestore.ts +19 -3
  212. package/src/sandbox/index.ts +5 -1
  213. package/src/sandbox/persistence/types.ts +1 -1
  214. package/src/sandbox/remote.ts +2 -2
  215. package/src/sandbox/sandbox-context.ts +69 -9
  216. package/src/sandbox/types/auth-state.ts +1 -1
  217. package/src/sandbox/types/events.ts +2 -1
  218. package/src/sandbox/types/operation.ts +1 -1
  219. package/src/storage/enforce.ts +53 -14
  220. package/src/storage/index.ts +1 -1
  221. package/src/storage/list.ts +1 -1
  222. package/src/storage/sandbox/rules-evaluation-error.ts +32 -0
  223. package/src/storage/sandbox/rules-evaluator.ts +109 -32
  224. package/src/storage/sandbox/rules-methods.ts +21 -7
  225. 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
- constructor(backend: StorageBackend, rules?: StorageRules | null);
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 dev` passes
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;gBAD1B,OAAO,EAAE,cAAc,EACvB,KAAK,GAAE,YAAY,GAAG,IAAW;CAE7C;AAED,6CAA6C;AAC7C,MAAM,WAAW,cAAc;IAC7B;;;;;OAKG;IACH,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB;;;;OAIG;IACH,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB;;;;;;;;OAQG;IACH,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB;;;;OAIG;IACH,KAAK,CAAC,EAAE,MAAM,CAAC;CAChB;AAyHD;;;;;GAKG;AACH,wBAAgB,iBAAiB,CAC/B,MAAM,EAAE,OAAO,GAAG,cAAc,EAChC,OAAO,GAAE,cAAmB,GAC3B,eAAe,CAqCjB;AAOD;;;;;;;;;;;;;;GAcG;AACH,wBAAgB,sBAAsB,CACpC,MAAM,EAAE,OAAO,GAAG,cAAc,EAChC,OAAO,GAAE,cAAmB,GAC3B,eAAe,CAoCjB;AAED;;8DAE8D;AAC9D,wBAAgB,0BAA0B,CACxC,MAAM,EAAE,aAAa,EACrB,QAAQ,CAAC,EAAE,eAAe,GACzB,eAAe,CAUjB;AAED;;;qEAGqE;AACrE,wBAAgB,2BAA2B,CACzC,OAAO,EAAE,eAAe,EACxB,UAAU,EAAE,eAAe,GAAG,SAAS,GACtC,eAAe,CAYjB;AAED;;;GAGG;AACH,wBAAgB,QAAQ,CAAC,OAAO,EAAE,eAAe,GAAG,MAAM,CAQzD;AAED;;GAEG;AACH,wBAAgB,iBAAiB,CAAC,OAAO,EAAE,eAAe,GAAG,OAAO,CAAC,cAAc,CAAC,CAEnF;AAED,oFAAoF;AACpF,wBAAgB,yBAAyB,CACvC,OAAO,EAAE,eAAe,GACvB,sBAAsB,GAAG,IAAI,CAE/B"}
1
+ {"version":3,"file":"service.d.ts","sourceRoot":"","sources":["../../src/storage/service.ts"],"names":[],"mappings":"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"}
@@ -45,14 +45,15 @@ export function storageAuth(target) {
45
45
  export class StorageService {
46
46
  backend;
47
47
  rules;
48
- constructor(backend, rules = null) {
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
- // ─── Caches ─────────────────────────────────────────────────────────
54
- /** One `StorageService` per `Sandbox`. */
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
- * The rules SOURCE each sandbox's service was opened with (`null` when
69
- * opened without rules). Late-config detection: rules are honored only on
70
- * the FIRST storage call per `Sandbox`, so silently discarding a LATER,
71
- * DIFFERENT `rules` option would be a silent rules wipe — {@link
72
- * ensureService} throws instead. Re-supplying the IDENTICAL source stays
73
- * fine (idempotent multi-handle construction, e.g. per-user contexts).
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
- const SERVICE_RULES_SOURCE = new WeakMap();
76
- const SERVICE_RULES_RESOLUTION = new WeakMap();
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 = SERVICES.get(sandbox);
96
+ const existing = OPEN_SERVICES.get(sandbox);
86
97
  if (existing) {
87
- const openedWith = SERVICE_RULES_SOURCE.get(sandbox) ?? null;
88
- if (options.rules !== undefined && options.rules !== openedWith) {
89
- throw new Error(`pyric/storage: ${caller} received a rules source, but this sandbox's storage ` +
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 servicePromise = openStorageBackend(options.dbName ?? storageDbName(options.projectId)).then((backend) => new StorageService(backend, rules));
120
- SERVICES.set(sandbox, servicePromise);
121
- SERVICE_RULES_SOURCE.set(sandbox, options.rules ?? null);
122
- SERVICE_RULES_RESOLUTION.set(sandbox, resolution);
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 SERVICE_RULES_RESOLUTION.get(targetOf(storage).sandbox) ?? null;
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":"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"}
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.20",
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
+ }
@@ -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
- const DEFAULT_BASE_URL = 'https://generativelanguage.googleapis.com';
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 ?? DEFAULT_BASE_URL).replace(/\/$/, '');
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
- private normalizeModel(model: string): string {
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
- const resolved = isExperimentalFlashLite ? 'gemini-flash-lite-latest' : stripped;
125
- return `models/${resolved}`;
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
- const message = err instanceof Error ? err.message : String(err);
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(502, `Failed to connect to Gemini API at ${url}: ${message}`, 'UNAVAILABLE'),
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
  );
@@ -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 {