backend-manager 5.11.6 → 5.12.0
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/docs/ai-library.md +3 -1
- package/docs/usage-rate-limiting.md +2 -0
- package/package.json +1 -1
- package/src/cli/commands/serve.js +49 -2
- package/src/manager/helpers/usage.js +9 -2
- package/src/manager/libraries/ai/providers/test.js +15 -1
- package/src/manager/libraries/email/data/disposable-domains.json +96 -0
- package/src/test/fixtures/firebase-project/.temp/test-mode.json +1 -1
- package/src/test/fixtures/firebase-project/database-debug.log +8 -14
- package/src/test/fixtures/firebase-project/firestore-debug.log +55 -1344
- package/src/test/fixtures/firebase-project/pubsub-debug.log +3 -5
- package/test/helpers/ai-test-provider.js +24 -0
- package/test/helpers/usage-limits.js +85 -0
package/docs/ai-library.md
CHANGED
|
@@ -96,7 +96,9 @@ URL citations live in the returned `output` (message content) as `annotations` o
|
|
|
96
96
|
|
|
97
97
|
`provider: 'test'` is the AI analog of the `test` payment processor: a first-class provider that suites drive with directives in the LAST user message, so consumer routes exercise their full loop (Firestore writes, usage, locks, tool execution) against the real emulator with zero paid API calls. It **refuses to run outside development/testing**.
|
|
98
98
|
|
|
99
|
-
Directives form a sequence consumed across loop turns (call N executes directive N-1, indexed by assistant turns after the last user turn). Directive values must not contain `]]` internally (a trailing JSON `]` is fine)
|
|
99
|
+
Directives form a sequence consumed across loop turns (call N executes directive N-1, indexed by assistant turns after the last user turn). Directive values must not contain `]]` internally (a trailing JSON `]` is fine).
|
|
100
|
+
|
|
101
|
+
The directive source is resolved in order: the last `messages[]` user turn → `message.content` → **`message.settings` values** (flattened raw). That last fallback makes path-based routes (`message: { path, settings }` — the idiomatic BEM prompt-template style) scriptable as-is: embed the directive in whatever request field gets interpolated into the template (e.g. a chat's `message`, a quiz's `topic`).
|
|
100
102
|
|
|
101
103
|
| Directive | Behavior |
|
|
102
104
|
|---|---|
|
|
@@ -27,6 +27,8 @@ For anonymous-to-owner billing, call `setUser()` BEFORE `validate()` — validat
|
|
|
27
27
|
|
|
28
28
|
Limits are always specified as **monthly** values in product config (e.g., `limits.requests = 100` means 100/month).
|
|
29
29
|
|
|
30
|
+
**Negative limits are unlimited.** `limits.requests = -1` means the metric is never rate-limited for that product — `validate()` always resolves and no daily caps apply. A limit of `0` (or a metric missing from `limits`) always rejects.
|
|
31
|
+
|
|
30
32
|
By default, limits are enforced with **daily caps** to prevent users from burning their entire monthly quota in a single day. Two checks are applied:
|
|
31
33
|
|
|
32
34
|
1. **Flat daily cap**: `ceil(monthlyLimit / daysInMonth)` — max uses per day
|
package/package.json
CHANGED
|
@@ -207,14 +207,61 @@ class ServeCommand extends BaseCommand {
|
|
|
207
207
|
const certFile = certFiles.find((f) => !f.includes('-key.pem'));
|
|
208
208
|
|
|
209
209
|
if (keyFile && certFile) {
|
|
210
|
-
|
|
211
|
-
|
|
210
|
+
const problem = this._checkCertProblem(certFile);
|
|
211
|
+
|
|
212
|
+
if (!problem) {
|
|
213
|
+
this.log(chalk.gray(' Using existing mkcert certificates from .temp/certs/'));
|
|
214
|
+
return { key: keyFile, cert: certFile };
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
// Stale certs (expired, or issued by a DIFFERENT machine's mkcert CA — e.g.
|
|
218
|
+
// .temp copied over from another Mac) make browsers reject the proxy outright,
|
|
219
|
+
// so the only thing that responds is the internal plain-HTTP firebase port.
|
|
220
|
+
// Wipe and regenerate against THIS machine's trusted CA.
|
|
221
|
+
this.log(chalk.yellow(` Existing certificates are not usable (${problem}) — regenerating...`));
|
|
222
|
+
jetpack.remove(certsDir);
|
|
223
|
+
jetpack.dir(certsDir);
|
|
212
224
|
}
|
|
213
225
|
|
|
214
226
|
// Try to generate with mkcert
|
|
215
227
|
return this._generateMkcertCerts(certsDir);
|
|
216
228
|
}
|
|
217
229
|
|
|
230
|
+
// Returns a reason string when the existing cert must be regenerated, or null
|
|
231
|
+
// when it's usable: unexpired AND signed by this machine's trusted mkcert root CA.
|
|
232
|
+
_checkCertProblem(certFile) {
|
|
233
|
+
const { X509Certificate } = require('crypto');
|
|
234
|
+
const { execSync } = require('child_process');
|
|
235
|
+
|
|
236
|
+
let cert;
|
|
237
|
+
try {
|
|
238
|
+
cert = new X509Certificate(fs.readFileSync(certFile));
|
|
239
|
+
} catch (e) {
|
|
240
|
+
return 'unreadable certificate';
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
if (new Date(cert.validTo) <= new Date()) {
|
|
244
|
+
return `expired ${cert.validTo}`;
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
// Verify the signature chains to the CURRENT mkcert root CA. If mkcert (or its
|
|
248
|
+
// root) isn't available we can't verify — keep the existing certs rather than
|
|
249
|
+
// breaking the no-mkcert fallback path.
|
|
250
|
+
try {
|
|
251
|
+
const caRoot = execSync('mkcert -CAROOT', { encoding: 'utf8' }).trim();
|
|
252
|
+
const ca = new X509Certificate(fs.readFileSync(path.join(caRoot, 'rootCA.pem')));
|
|
253
|
+
|
|
254
|
+
if (!cert.verify(ca.publicKey)) {
|
|
255
|
+
const issuerCN = cert.issuer.split('\n').find((line) => line.startsWith('CN=')) || cert.issuer;
|
|
256
|
+
return `issued by a different CA (${issuerCN})`;
|
|
257
|
+
}
|
|
258
|
+
} catch (e) {
|
|
259
|
+
return null;
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
return null;
|
|
263
|
+
}
|
|
264
|
+
|
|
218
265
|
async _generateMkcertCerts(certsDir) {
|
|
219
266
|
try {
|
|
220
267
|
await powertools.execute('which mkcert', { log: false });
|
|
@@ -170,6 +170,13 @@ Usage.prototype.validate = function (name, options) {
|
|
|
170
170
|
return _reject();
|
|
171
171
|
}
|
|
172
172
|
|
|
173
|
+
// Negative limits are unlimited (product config convention: -1)
|
|
174
|
+
if (allowed < 0) {
|
|
175
|
+
self.log(`Usage.validate(): Unlimited limit (${allowed}) for ${name}`);
|
|
176
|
+
|
|
177
|
+
return resolve(true);
|
|
178
|
+
}
|
|
179
|
+
|
|
173
180
|
// Check if they have a white list key
|
|
174
181
|
const hasWhitelistKey = self.options.whitelistKeys.some((key) => key && key === self?.user?.api?.privateKey);
|
|
175
182
|
if (hasWhitelistKey) {
|
|
@@ -367,9 +374,9 @@ Usage.prototype.getDailyAllowance = function (name) {
|
|
|
367
374
|
return null;
|
|
368
375
|
}
|
|
369
376
|
|
|
370
|
-
// Get the monthly limit
|
|
377
|
+
// Get the monthly limit (negative limits are unlimited — no daily cap)
|
|
371
378
|
const monthlyLimit = self.getLimit(name);
|
|
372
|
-
if (!monthlyLimit) {
|
|
379
|
+
if (!monthlyLimit || monthlyLimit < 0) {
|
|
373
380
|
return null;
|
|
374
381
|
}
|
|
375
382
|
|
|
@@ -55,7 +55,12 @@ TestProvider.prototype.request = async function (options) {
|
|
|
55
55
|
|
|
56
56
|
const messages = Array.isArray(options.messages) ? options.messages : [];
|
|
57
57
|
const lastUserMessage = [...messages].reverse().find((m) => m.role === 'user' && typeof m.content === 'string');
|
|
58
|
-
|
|
58
|
+
// Path-based messages ({ path, settings }) are rendered inside the real
|
|
59
|
+
// providers, so directives arrive via the settings values — read them too.
|
|
60
|
+
const scriptSource = lastUserMessage?.content
|
|
61
|
+
|| stringifyLoose(options.message?.content)
|
|
62
|
+
|| stringifyLoose(options.message?.settings)
|
|
63
|
+
|| '';
|
|
59
64
|
|
|
60
65
|
const { steps, cleanText } = parseScript(scriptSource);
|
|
61
66
|
|
|
@@ -230,6 +235,15 @@ function stringifyLoose(content) {
|
|
|
230
235
|
return content.map((c) => c?.text || '').join('\n');
|
|
231
236
|
}
|
|
232
237
|
|
|
238
|
+
// Plain objects (e.g. prompt-template settings) flatten to their raw values
|
|
239
|
+
// so directives embedded in them stay parseable (JSON.stringify would escape
|
|
240
|
+
// the quotes inside a directive's JSON payload)
|
|
241
|
+
if (content && typeof content === 'object') {
|
|
242
|
+
return Object.values(content)
|
|
243
|
+
.map((value) => (typeof value === 'string' ? value : `${value}`))
|
|
244
|
+
.join('\n');
|
|
245
|
+
}
|
|
246
|
+
|
|
233
247
|
return content ? String(content) : '';
|
|
234
248
|
}
|
|
235
249
|
|