backend-manager 5.0.164 → 5.0.166

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/CHANGELOG.md CHANGED
@@ -14,6 +14,20 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/).
14
14
  - `Fixed` for any bug fixes.
15
15
  - `Security` in case of vulnerabilities.
16
16
 
17
+ # [5.0.166] - 2026-03-20
18
+ ### Added
19
+ - `reasoning: true` feature flag to GPT-5.x and o-series models in MODEL_TABLE
20
+ - New GPT-5.4-mini and GPT-5.4-nano model entries with pricing
21
+
22
+ ### Changed
23
+ - Reasoning parameter is now conditionally included in API requests only when the model supports it
24
+ - `resolveReasoning()` validates model support and warns when reasoning is requested for unsupported models
25
+
26
+ # [5.0.165] - 2026-03-20
27
+ ### Changed
28
+ - Serve command now reads hosting port from `firebase.json` emulator config before falling back to default 5000
29
+ - Notification test fixtures migrated from flat `createdAt`/`updatedAt` to nested `metadata.created`/`metadata.updated` objects matching standard BEM metadata format
30
+
17
31
  # [5.0.164] - 2026-03-18
18
32
  ### Added
19
33
  - Default field backfill in campaign seed setup — missing fields are restored from seed defaults without overwriting user edits
package/TODO-2.md CHANGED
@@ -13,6 +13,31 @@ payments/upgrade
13
13
  TEST NEWSLETTER
14
14
  POST /admin/cron { id: 'daily/marketing-newsletter-generate' }
15
15
 
16
+
17
+ SIGNUP HANDLER
18
+ Here are the instructions for BEM:
19
+
20
+ Update updateReferral() in sign-up.js to resolve all legacy affiliate code formats:
21
+
22
+ The affiliateCode value coming from the client could be in 3 formats:
23
+
24
+ Format Example How to resolve
25
+ Affiliate code (7-14 alphanumeric) rmUKlC4z1 Query users where affiliate.code == value (current behavior)
26
+ UID (28 chars) 6sNjQFxTsObA73D8lkF01gcWdP92 Direct doc lookup: users/{value}
27
+ Base64 email (e.g. cG9ldH...Lm_2) cG9ldHJ5aW5hY3Rpb24yMDE4QGdtYWlsLmNvbQ_2 Strip _\d+ suffix, base64-decode, query users where auth.email == decoded
28
+ Any other format → ignore (resolve with no referrer).
29
+
30
+ The resolution logic should go at the top of updateReferral(), before the current where('affiliate.code', '==', affiliateCode) query. Try each format in order:
31
+
32
+ If affiliateCode matches /^[0-9a-zA-Z_-]{7,14}$/ → query by affiliate.code (current path)
33
+ Else if affiliateCode.length === 28 → direct doc get users/{affiliateCode}, use that as the referrer
34
+ Else try base64 decode: strip trailing _\d+, decode, if result contains @ → query by auth.email
35
+ Else → log and return (unrecognized format)
36
+ Once the referrer doc is found (by any method), the rest stays the same: push to affiliate.referrals.
37
+
38
+ * if the usage was used and the user is actuall authenticated (uid, not just an admin or unauthed user),
39
+ * set the user's context (ip, location, etc)
40
+
16
41
  Payment attribution
17
42
  * can you ensure the the user's attribution (utm etc) is assocaited with the purchase
18
43
  * mostly i am referring to the payent events sent to GA4, tiktok, meta
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "backend-manager",
3
- "version": "5.0.164",
3
+ "version": "5.0.166",
4
4
  "description": "Quick tools for developing Firebase functions",
5
5
  "main": "src/manager/index.js",
6
6
  "bin": {
@@ -8,8 +8,9 @@ const WatchCommand = require('./watch');
8
8
  class ServeCommand extends BaseCommand {
9
9
  async execute() {
10
10
  const self = this.main;
11
- const port = self.argv.port || self.argv?._?.[1] || '5000';
12
11
  const projectDir = self.firebaseProjectPath;
12
+ const firebaseConfig = JSON.parse(fs.readFileSync(path.join(projectDir, 'firebase.json'), 'utf8'));
13
+ const port = self.argv.port || self.argv?._?.[1] || firebaseConfig?.emulators?.hosting?.port || '5000';
13
14
 
14
15
  // Check for port conflicts before starting server
15
16
  const canProceed = await this.checkAndKillBlockingProcesses({ serving: parseInt(port, 10) });
@@ -22,6 +22,7 @@ const MODEL_TABLE = {
22
22
  features: {
23
23
  json: true,
24
24
  temperature: false,
25
+ reasoning: true,
25
26
  },
26
27
  },
27
28
  'gpt-5.2': {
@@ -31,6 +32,7 @@ const MODEL_TABLE = {
31
32
  features: {
32
33
  json: true,
33
34
  temperature: false,
35
+ reasoning: true,
34
36
  },
35
37
  },
36
38
  'gpt-5.1': {
@@ -40,6 +42,7 @@ const MODEL_TABLE = {
40
42
  features: {
41
43
  json: true,
42
44
  temperature: false,
45
+ reasoning: true,
43
46
  },
44
47
  },
45
48
  'gpt-5': {
@@ -49,6 +52,7 @@ const MODEL_TABLE = {
49
52
  features: {
50
53
  json: true,
51
54
  temperature: false,
55
+ reasoning: true,
52
56
  },
53
57
  },
54
58
  'gpt-5-mini': {
@@ -58,6 +62,7 @@ const MODEL_TABLE = {
58
62
  features: {
59
63
  json: true,
60
64
  temperature: false,
65
+ reasoning: true,
61
66
  },
62
67
  },
63
68
  'gpt-5-nano': {
@@ -67,6 +72,29 @@ const MODEL_TABLE = {
67
72
  features: {
68
73
  json: true,
69
74
  temperature: false,
75
+ reasoning: true,
76
+ },
77
+ },
78
+ // Mar 20, 2026
79
+ // GPT-5.4 mini/nano family
80
+ 'gpt-5.4-mini': {
81
+ input: 0.75,
82
+ output: 4.50,
83
+ provider: 'openai',
84
+ features: {
85
+ json: true,
86
+ temperature: false,
87
+ reasoning: true,
88
+ },
89
+ },
90
+ 'gpt-5.4-nano': {
91
+ input: 0.20,
92
+ output: 1.25,
93
+ provider: 'openai',
94
+ features: {
95
+ json: true,
96
+ temperature: false,
97
+ reasoning: true,
70
98
  },
71
99
  },
72
100
  // GPT-4.5
@@ -127,6 +155,7 @@ const MODEL_TABLE = {
127
155
  provider: 'openai',
128
156
  features: {
129
157
  json: true,
158
+ reasoning: true,
130
159
  },
131
160
  },
132
161
  'o3-pro': {
@@ -135,6 +164,7 @@ const MODEL_TABLE = {
135
164
  provider: 'openai',
136
165
  features: {
137
166
  json: true,
167
+ reasoning: true,
138
168
  },
139
169
  },
140
170
  'o3': {
@@ -143,6 +173,7 @@ const MODEL_TABLE = {
143
173
  provider: 'openai',
144
174
  features: {
145
175
  json: true,
176
+ reasoning: true,
146
177
  },
147
178
  },
148
179
  'o3-mini': {
@@ -151,6 +182,7 @@ const MODEL_TABLE = {
151
182
  provider: 'openai',
152
183
  features: {
153
184
  json: true,
185
+ reasoning: true,
154
186
  },
155
187
  },
156
188
  'o1-pro': {
@@ -159,6 +191,7 @@ const MODEL_TABLE = {
159
191
  provider: 'openai',
160
192
  features: {
161
193
  json: true,
194
+ reasoning: true,
162
195
  },
163
196
  },
164
197
  'o1': {
@@ -167,6 +200,7 @@ const MODEL_TABLE = {
167
200
  provider: 'openai',
168
201
  features: {
169
202
  json: true,
203
+ reasoning: true,
170
204
  },
171
205
  },
172
206
  'o1-preview': {
@@ -175,6 +209,7 @@ const MODEL_TABLE = {
175
209
  provider: 'openai',
176
210
  features: {
177
211
  json: true,
212
+ reasoning: true,
178
213
  },
179
214
  },
180
215
  'o1-mini': {
@@ -183,6 +218,7 @@ const MODEL_TABLE = {
183
218
  provider: 'openai',
184
219
  features: {
185
220
  json: true,
221
+ reasoning: true,
186
222
  },
187
223
  },
188
224
  'gpt-4-turbo': {
@@ -794,13 +830,18 @@ function makeRequest(mode, options, self, prompt, message, user, _log) {
794
830
  user: user,
795
831
  max_output_tokens: options.maxTokens,
796
832
  text: resolveFormatting(options),
797
- reasoning: resolveReasoning(options),
798
833
  }
799
834
 
800
835
  // Only include temperature if the model supports it
801
836
  if (modelConfig.features?.temperature !== false) {
802
837
  request.body.temperature = options.temperature;
803
838
  }
839
+
840
+ // Only include reasoning if the model supports it
841
+ const reasoning = resolveReasoning(options);
842
+ if (reasoning) {
843
+ request.body.reasoning = reasoning;
844
+ }
804
845
  }
805
846
 
806
847
  // Request
@@ -861,16 +902,22 @@ function resolveFormatting(options) {
861
902
  }
862
903
 
863
904
  function resolveReasoning(options) {
864
- // If reasoning is set, return reasoning format
865
- if (options.reasoning) {
866
- return {
867
- effort: options.reasoning.effort || 'medium',
868
- // summary: options.reasoning.summary || 'concise',
869
- };
905
+ // If reasoning is not requested, return undefined
906
+ if (!options.reasoning) {
907
+ return undefined;
870
908
  }
871
909
 
872
- // Other, return undefined
873
- return undefined;
910
+ // Check if the model supports reasoning
911
+ const modelConfig = getModelConfig(options.model);
912
+ if (!modelConfig.features?.reasoning) {
913
+ console.warn(`Reasoning not supported for model: ${options.model}, ignoring reasoning option`);
914
+ return undefined;
915
+ }
916
+
917
+ return {
918
+ effort: options.reasoning.effort || 'medium',
919
+ // summary: options.reasoning.summary || 'concise',
920
+ };
874
921
  }
875
922
 
876
923
  module.exports = OpenAI;
@@ -31,7 +31,7 @@ module.exports = {
31
31
  db.doc(`notifications/${token}`).set({
32
32
  token: token,
33
33
  owner: 'anonymous',
34
- createdAt: new Date(),
34
+ metadata: { created: { timestamp: new Date().toISOString(), timestampUNIX: Math.floor(Date.now() / 1000) } },
35
35
  })
36
36
  );
37
37
  },
@@ -52,7 +52,7 @@ module.exports = {
52
52
  db.doc(`notifications/${token}`).set({
53
53
  token: token,
54
54
  owner: uid,
55
- createdAt: new Date(),
55
+ metadata: { created: { timestamp: new Date().toISOString(), timestampUNIX: Math.floor(Date.now() / 1000) } },
56
56
  })
57
57
  );
58
58
  },
@@ -74,7 +74,7 @@ module.exports = {
74
74
  adminDb.doc(`notifications/${token}`).set({
75
75
  token: token,
76
76
  owner: uid,
77
- createdAt: new Date(),
77
+ metadata: { created: { timestamp: new Date().toISOString(), timestampUNIX: Math.floor(Date.now() / 1000) } },
78
78
  })
79
79
  );
80
80
 
@@ -101,7 +101,7 @@ module.exports = {
101
101
  adminDb.doc(`notifications/${token}`).set({
102
102
  token: token,
103
103
  owner: otherUid,
104
- createdAt: new Date(),
104
+ metadata: { created: { timestamp: new Date().toISOString(), timestampUNIX: Math.floor(Date.now() / 1000) } },
105
105
  })
106
106
  );
107
107
 
@@ -144,14 +144,14 @@ module.exports = {
144
144
  adminDb.doc(`notifications/${token}`).set({
145
145
  token: token,
146
146
  owner: otherUid,
147
- createdAt: new Date(),
147
+ metadata: { created: { timestamp: new Date().toISOString(), timestampUNIX: Math.floor(Date.now() / 1000) } },
148
148
  })
149
149
  );
150
150
 
151
151
  // Should succeed - user can update if token matches
152
152
  await rules.expectSuccess(
153
153
  db.doc(`notifications/${token}`).update({
154
- updatedAt: new Date(),
154
+ 'metadata.updated': { timestamp: new Date().toISOString(), timestampUNIX: Math.floor(Date.now() / 1000) },
155
155
  })
156
156
  );
157
157
  },
@@ -173,7 +173,7 @@ module.exports = {
173
173
  adminDb.doc(`notifications/${token}`).set({
174
174
  token: token,
175
175
  owner: uid,
176
- createdAt: new Date(),
176
+ metadata: { created: { timestamp: new Date().toISOString(), timestampUNIX: Math.floor(Date.now() / 1000) } },
177
177
  })
178
178
  );
179
179
 
@@ -203,7 +203,7 @@ module.exports = {
203
203
  adminDb.doc(`notifications/${realToken}`).set({
204
204
  token: realToken,
205
205
  owner: otherUid,
206
- createdAt: new Date(),
206
+ metadata: { created: { timestamp: new Date().toISOString(), timestampUNIX: Math.floor(Date.now() / 1000) } },
207
207
  })
208
208
  );
209
209
 
@@ -237,7 +237,7 @@ module.exports = {
237
237
  adminDb.doc(`notifications/${token}`).set({
238
238
  token: token,
239
239
  owner: uid,
240
- createdAt: new Date(),
240
+ metadata: { created: { timestamp: new Date().toISOString(), timestampUNIX: Math.floor(Date.now() / 1000) } },
241
241
  })
242
242
  );
243
243
 
@@ -269,7 +269,7 @@ module.exports = {
269
269
  adminDb.doc(`notifications/${token}`).set({
270
270
  token: token,
271
271
  owner: accounts.basic.uid,
272
- createdAt: new Date(),
272
+ metadata: { created: { timestamp: new Date().toISOString(), timestampUNIX: Math.floor(Date.now() / 1000) } },
273
273
  })
274
274
  );
275
275
  },
@@ -290,7 +290,7 @@ module.exports = {
290
290
  adminDb.doc(`notifications/${token}`).set({
291
291
  token: token,
292
292
  owner: basicUid,
293
- createdAt: new Date(),
293
+ metadata: { created: { timestamp: new Date().toISOString(), timestampUNIX: Math.floor(Date.now() / 1000) } },
294
294
  })
295
295
  );
296
296
 
@@ -315,14 +315,14 @@ module.exports = {
315
315
  adminDb.doc(`notifications/${token}`).set({
316
316
  token: token,
317
317
  owner: accounts.basic.uid,
318
- createdAt: new Date(),
318
+ metadata: { created: { timestamp: new Date().toISOString(), timestampUNIX: Math.floor(Date.now() / 1000) } },
319
319
  })
320
320
  );
321
321
 
322
322
  // Should succeed - admin can update any doc
323
323
  await rules.expectSuccess(
324
324
  adminDb.doc(`notifications/${token}`).update({
325
- updatedAt: new Date(),
325
+ 'metadata.updated': { timestamp: new Date().toISOString(), timestampUNIX: Math.floor(Date.now() / 1000) },
326
326
  adminModified: true,
327
327
  })
328
328
  );
@@ -343,7 +343,7 @@ module.exports = {
343
343
  adminDb.doc(`notifications/${token}`).set({
344
344
  token: token,
345
345
  owner: 'someone',
346
- createdAt: new Date(),
346
+ metadata: { created: { timestamp: new Date().toISOString(), timestampUNIX: Math.floor(Date.now() / 1000) } },
347
347
  })
348
348
  );
349
349
 
@@ -370,7 +370,7 @@ module.exports = {
370
370
  adminDb.doc(`notifications/${token}`).set({
371
371
  token: token,
372
372
  owner: uid,
373
- createdAt: new Date(),
373
+ metadata: { created: { timestamp: new Date().toISOString(), timestampUNIX: Math.floor(Date.now() / 1000) } },
374
374
  })
375
375
  );
376
376
 
@@ -396,7 +396,7 @@ module.exports = {
396
396
  adminDb.doc(`notifications/${token}`).set({
397
397
  token: token,
398
398
  owner: 'someone',
399
- createdAt: new Date(),
399
+ metadata: { created: { timestamp: new Date().toISOString(), timestampUNIX: Math.floor(Date.now() / 1000) } },
400
400
  })
401
401
  );
402
402