@yrpri/api 9.0.134 → 9.0.136

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.
@@ -644,6 +644,43 @@ export class NewAiModelSetup {
644
644
  await gemini25ProPreview2.save();
645
645
  console.log("Google model already exists: Gemini 2.5 Pro");
646
646
  }
647
+ const gemini25ProFinalPreview = await PsAiModel.findOne({
648
+ where: { name: "Gemini 2.5 Pro Final Preview" },
649
+ });
650
+ const gemini25ProFinalConfig = {
651
+ type: PsAiModelType.TextReasoning,
652
+ modelSize: PsAiModelSize.Large,
653
+ provider: "google",
654
+ prices: {
655
+ costInTokensPerMillion: 1.25,
656
+ costOutTokensPerMillion: 10,
657
+ costInCachedContextTokensPerMillion: 0.875,
658
+ longContextTokenThreshold: 200000,
659
+ longContextCostInTokensPerMillion: 2.5,
660
+ longContextCostInCachedContextTokensPerMillion: 1.75,
661
+ longContextCostOutTokensPerMillion: 15,
662
+ currency: "USD",
663
+ },
664
+ model: "gemini-2.5-pro-preview-06-05",
665
+ active: true,
666
+ maxTokensOut: 100000,
667
+ defaultTemperature: 0.0,
668
+ };
669
+ if (!gemini25ProFinalPreview) {
670
+ await PsAiModel.create({
671
+ name: "Gemini 2.5 Pro Final Preview",
672
+ organization_id: 1,
673
+ user_id: userId,
674
+ configuration: gemini25ProFinalConfig,
675
+ });
676
+ console.log("Created Google model: Gemini 2.5 Pro Final Preview");
677
+ }
678
+ else {
679
+ gemini25ProFinalPreview.set("configuration", gemini25ProFinalConfig);
680
+ gemini25ProFinalPreview.changed("configuration", true);
681
+ await gemini25ProFinalPreview.save();
682
+ console.log("Google model already exists: Gemini 2.5 Pro Final Preview");
683
+ }
647
684
  const gemini25FlashPreview1 = await PsAiModel.findOne({
648
685
  where: { name: "Gemini 2.5 Flash Preview 1" },
649
686
  });
@@ -681,6 +718,39 @@ export class NewAiModelSetup {
681
718
  await gemini25FlashPreview1.save();
682
719
  console.log("Google model already exists: Gemini 2.5 Pro");
683
720
  }
721
+ const gemini25FlashPreview = await PsAiModel.findOne({
722
+ where: { name: "Gemini 2.5 Flash Preview" },
723
+ });
724
+ const gemini25FlashPreviewConfig = {
725
+ type: PsAiModelType.Text,
726
+ modelSize: PsAiModelSize.Medium,
727
+ provider: "google",
728
+ prices: {
729
+ costInTokensPerMillion: 0.15,
730
+ costOutTokensPerMillion: 0.6,
731
+ costInCachedContextTokensPerMillion: 0.09,
732
+ currency: "USD",
733
+ },
734
+ maxTokensOut: 8192,
735
+ defaultTemperature: 0.0,
736
+ model: "gemini-2.5-flash-preview-05-20",
737
+ active: true,
738
+ };
739
+ if (!gemini25FlashPreview) {
740
+ await PsAiModel.create({
741
+ name: "Gemini 2.5 Flash Preview",
742
+ organization_id: 1,
743
+ user_id: userId,
744
+ configuration: gemini25FlashPreviewConfig,
745
+ });
746
+ console.log("Created Google model: Gemini 2.5 Flash Preview");
747
+ }
748
+ else {
749
+ gemini25FlashPreview.set("configuration", gemini25FlashPreviewConfig);
750
+ gemini25FlashPreview.changed("configuration", true);
751
+ await gemini25FlashPreview.save();
752
+ console.log("Google model already exists: Gemini 2.5 Flash Preview");
753
+ }
684
754
  }
685
755
  /**
686
756
  * Master seeding function which calls the provider-specific functions
@@ -790,7 +860,9 @@ export class NewAiModelSetup {
790
860
  { name: "Gemini 2.0 Flash", envKey: "GEMINI_API_KEY" },
791
861
  { name: "Gemini 2.5 Pro Preview 1", envKey: "GEMINI_API_KEY" },
792
862
  { name: "Gemini 2.5 Pro Preview 2", envKey: "GEMINI_API_KEY" },
863
+ { name: "Gemini 2.5 Pro Final Preview", envKey: "GEMINI_API_KEY" },
793
864
  { name: "Gemini 2.5 Flash Preview 1", envKey: "GEMINI_API_KEY" },
865
+ { name: "Gemini 2.5 Flash Preview", envKey: "GEMINI_API_KEY" },
794
866
  { name: "o1 24", envKey: "OPENAI_API_KEY" },
795
867
  { name: "o3 mini", envKey: "OPENAI_API_KEY" },
796
868
  { name: "o4 mini", envKey: "OPENAI_API_KEY" },
package/app.js CHANGED
@@ -198,10 +198,10 @@ export class YourPrioritiesApi {
198
198
  this.addDirnameToRequest();
199
199
  this.forceHttps();
200
200
  this.initializeMiddlewares();
201
- this.setupNewWebAppVersionHandling();
202
201
  this.handleShortenedRedirects();
203
202
  this.initializeRateLimiting();
204
203
  this.setupDomainAndCommunity();
204
+ this.setupNewWebAppVersionHandling();
205
205
  this.setupSitemapRoute();
206
206
  this.initializePassportStrategies();
207
207
  this.addInviteAsAnonMiddleWare();
@@ -216,10 +216,10 @@ export class YourPrioritiesApi {
216
216
  this.addDirnameToRequest();
217
217
  this.forceHttps();
218
218
  this.initializeMiddlewares();
219
- this.setupNewWebAppVersionHandling();
220
219
  this.handleShortenedRedirects();
221
220
  this.initializeRateLimiting();
222
221
  this.setupDomainAndCommunity();
222
+ this.setupNewWebAppVersionHandling();
223
223
  this.setupSitemapRoute();
224
224
  this.initializePassportStrategies();
225
225
  this.addInviteAsAnonMiddleWare();
@@ -2210,6 +2210,103 @@ router.get('/:id/status_update/:bulkStatusUpdateId', function (req, res, next) {
2210
2210
  });
2211
2211
  }
2212
2212
  });
2213
+ // Audkenni REST Authentication
2214
+ router.post('/auth/audkenni-rest/start', async function (req, res) {
2215
+ try {
2216
+ const { phone, authenticator } = req.body;
2217
+ if (!phone || !authenticator) {
2218
+ res.status(400).send({ error: 'missing_parameters' });
2219
+ return;
2220
+ }
2221
+ const { default: AudkenniRestService } = await import('../services/auth/audkenniRestService.js');
2222
+ const service = new AudkenniRestService();
2223
+ const startData = await service.start();
2224
+ const callbacks = (startData.callbacks || []).map((cb) => {
2225
+ if (cb.type === 'NameCallback') {
2226
+ cb.input[0].value = phone;
2227
+ }
2228
+ else if (cb.type === 'ChoiceCallback') {
2229
+ const index = cb.output[1].value.indexOf(authenticator);
2230
+ cb.input[0].value = index >= 0 ? index : 0;
2231
+ }
2232
+ return cb;
2233
+ });
2234
+ await service.continue(startData.authId, callbacks);
2235
+ res.send({ pollId: startData.authId });
2236
+ }
2237
+ catch (error) {
2238
+ log.error('Error starting Audkenni REST login', { error });
2239
+ res.status(500).send({ error: 'audkenni_start_failed' });
2240
+ }
2241
+ });
2242
+ router.post('/auth/audkenni-rest/continue', async function (req, res) {
2243
+ try {
2244
+ const { authId, callbacks } = req.body;
2245
+ const { default: AudkenniRestService } = await import('../services/auth/audkenniRestService.js');
2246
+ const service = new AudkenniRestService();
2247
+ const data = await service.continue(authId, callbacks);
2248
+ res.send({ authId: data.authId, callbacks: data.callbacks, tokenId: data.tokenId });
2249
+ }
2250
+ catch (error) {
2251
+ log.error('Error continuing Audkenni REST login', { error });
2252
+ res.status(500).send({ error: 'audkenni_continue_failed' });
2253
+ }
2254
+ });
2255
+ router.get('/auth/audkenni-rest/poll/:id', async function (req, res) {
2256
+ try {
2257
+ const authId = req.params.id;
2258
+ const { default: AudkenniRestService } = await import('../services/auth/audkenniRestService.js');
2259
+ const service = new AudkenniRestService();
2260
+ const result = await service.poll(authId);
2261
+ if (!result.tokenId) {
2262
+ res.send({ pending: true });
2263
+ return;
2264
+ }
2265
+ const profile = result.profile || { nationalRegisterId: result.nationalId, name: result.name, provider: 'oidc' };
2266
+ models.User.serializeOidcUser(profile, req, function (error, user) {
2267
+ if (error || !user) {
2268
+ log.error('Error serializing Audkenni user', { error });
2269
+ res.status(500).send({ error: 'audkenni_login_failed' });
2270
+ }
2271
+ else {
2272
+ req.logIn(user, async function (err) {
2273
+ if (err) {
2274
+ log.error('Error logging in Audkenni user', { err });
2275
+ res.status(500).send({ error: 'audkenni_login_failed' });
2276
+ }
2277
+ else {
2278
+ await new Promise(resolve => setTimeout(resolve, 50));
2279
+ getUserWithAll(user.id, true, function (getErr, fullUser) {
2280
+ if (getErr || !fullUser) {
2281
+ res.status(500).send({ error: 'audkenni_user_fetch_failed' });
2282
+ }
2283
+ else {
2284
+ if (fullUser.email) {
2285
+ delete fullUser.email;
2286
+ }
2287
+ else {
2288
+ fullUser.missingEmail = true;
2289
+ }
2290
+ if (fullUser.private_profile_data && fullUser.private_profile_data.registration_answers) {
2291
+ fullUser.dataValues.hasRegistrationAnswers = true;
2292
+ }
2293
+ else {
2294
+ fullUser.dataValues.hasRegistrationAnswers = false;
2295
+ }
2296
+ delete fullUser.private_profile_data;
2297
+ res.send(fullUser);
2298
+ }
2299
+ });
2300
+ }
2301
+ });
2302
+ }
2303
+ });
2304
+ }
2305
+ catch (error) {
2306
+ log.error('Error polling Audkenni REST login', { error });
2307
+ res.status(500).send({ error: 'audkenni_poll_failed' });
2308
+ }
2309
+ });
2213
2310
  // Facebook Authentication
2214
2311
  router.get('/auth/facebook', function (req, res) {
2215
2312
  req.sso.authenticate('facebook-strategy-' + req.ypDomain.id, {}, req, res, function (error, user) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@yrpri/api",
3
- "version": "9.0.134",
3
+ "version": "9.0.136",
4
4
  "license": "MIT",
5
5
  "author": "Robert Bjarnason & Citizens Foundation",
6
6
  "repository": {
@@ -0,0 +1,82 @@
1
+ import { initializeModels, PsAgent, PsAiModel, } from "@policysynth/agents/dbModels/index.js";
2
+ import { PsAiModelSize, PsAiModelType } from "@policysynth/agents/aiModelTypes.js";
3
+ import models from "../../models/index.cjs";
4
+ (async () => {
5
+ const [groupIdArg, sizeArg, typeArg, modelNameArg] = process.argv.slice(2);
6
+ if (!groupIdArg || !sizeArg || !typeArg || !modelNameArg) {
7
+ console.error("Usage: ts-node changeModelForWorkflowGroupTemplate.ts <groupId> <modelSize (small, medium, large)> <modeltype: text, textReasoning> <model_name>");
8
+ process.exit(1);
9
+ }
10
+ const groupId = Number(groupIdArg);
11
+ if (isNaN(groupId)) {
12
+ console.error("groupId must be a number");
13
+ process.exit(1);
14
+ }
15
+ const sizeMap = {
16
+ small: PsAiModelSize.Small,
17
+ medium: PsAiModelSize.Medium,
18
+ large: PsAiModelSize.Large,
19
+ };
20
+ const typeMap = {
21
+ text: PsAiModelType.Text,
22
+ textreasoning: PsAiModelType.TextReasoning,
23
+ };
24
+ const size = sizeMap[sizeArg.toLowerCase()];
25
+ const modelType = typeMap[typeArg.toLowerCase()];
26
+ if (!size) {
27
+ console.error("modelSize must be one of small, medium or large");
28
+ process.exit(1);
29
+ }
30
+ if (!modelType) {
31
+ console.error("modeltype must be text or textReasoning");
32
+ process.exit(1);
33
+ }
34
+ try {
35
+ await initializeModels();
36
+ const group = await models.Group.findByPk(groupId);
37
+ if (!group) {
38
+ throw new Error(`Group ${groupId} not found`);
39
+ }
40
+ const topLevelAgentId = group.configuration?.agents?.topLevelAgentId;
41
+ if (!topLevelAgentId) {
42
+ throw new Error("Top level agent id not found in group configuration");
43
+ }
44
+ const subAgents = await PsAgent.findAll({
45
+ where: { group_id: groupId, parent_agent_id: topLevelAgentId },
46
+ });
47
+ if (subAgents.length === 0) {
48
+ throw new Error("No sub-agents found for workflow group");
49
+ }
50
+ const newModel = await PsAiModel.findOne({
51
+ where: { configuration: { model: modelNameArg } },
52
+ });
53
+ if (!newModel) {
54
+ throw new Error(`AI model with configuration.model ${modelNameArg} not found`);
55
+ }
56
+ const privateConfig = (group.private_access_configuration ?? []);
57
+ for (const agent of subAgents) {
58
+ const currentModels = await agent.getAiModels();
59
+ for (const current of currentModels) {
60
+ const cfg = current.configuration || {};
61
+ if (cfg.modelSize === size && cfg.type === modelType) {
62
+ await agent.removeAiModel(current);
63
+ const entry = privateConfig.find((p) => p.aiModelId === current.id);
64
+ if (entry)
65
+ entry.aiModelId = newModel.id;
66
+ }
67
+ }
68
+ await agent.addAiModel(newModel);
69
+ console.log(`Updated agent ${agent.id} to use model ${newModel.name}`);
70
+ }
71
+ group.set("private_access_configuration", privateConfig);
72
+ group.changed("private_access_configuration", true);
73
+ await group.save();
74
+ console.log(`Group ${groupId} updated successfully`);
75
+ }
76
+ catch (error) {
77
+ console.error(error);
78
+ }
79
+ finally {
80
+ await models.sequelize.close();
81
+ }
82
+ })();
@@ -7,6 +7,7 @@ models.Domain.findOne({ where: { id: domainId } }).then(async (domain) => {
7
7
  console.log("Domain " + domain.domain_name);
8
8
  if (useNewVersionStatus === "true" || useNewVersionStatus === "false") {
9
9
  domain.set("configuration.useNewVersion", useNewVersionStatus === "true" ? true : false);
10
+ domain.changed("configuration", true);
10
11
  await domain.save();
11
12
  console.log("Set useNewVersionStatus to " + useNewVersionStatus);
12
13
  }
@@ -23,7 +23,7 @@ import models from '../../models/index.cjs';
23
23
  email,
24
24
  name,
25
25
  status: 'active',
26
- ssn: ssn ? Number(ssn) : undefined,
26
+ ssn: ssn || undefined,
27
27
  encrypted_password: hashed,
28
28
  });
29
29
  await domain.addDomainUsers(user);
@@ -18,7 +18,7 @@ import models from '../../models/index.cjs';
18
18
  const fullUserName = String(row.getCell(2).text).trim();
19
19
  const userEmail = String(row.getCell(3).text).trim().toLowerCase();
20
20
  const userSsn = String(row.getCell(4).text).trim();
21
- const userSsnNumber = userSsn ? Number(userSsn) : undefined;
21
+ const userSsnNumber = userSsn || undefined;
22
22
  if (!domainName || !fullUserName || !userEmail) {
23
23
  throw new Error(`Missing data in row ${i}`);
24
24
  }
@@ -7,7 +7,7 @@ import models from '../../models/index.cjs';
7
7
  console.log('Usage: node updatePasswordFromSsn.js <ssn> <newPassword>');
8
8
  process.exit(1);
9
9
  }
10
- const ssn = Number(ssnArg);
10
+ const ssn = ssnArg;
11
11
  const user = await models.User.findOne({ where: { ssn } });
12
12
  if (!user) {
13
13
  console.error(`User with ssn ${ssn} not found`);
@@ -11,7 +11,7 @@ import models from '../../models/index.cjs';
11
11
  console.error(`User ${email} not found`);
12
12
  process.exit(1);
13
13
  }
14
- const ssn = Number(ssnArg);
14
+ const ssn = ssnArg;
15
15
  user.ssn = ssn;
16
16
  await user.save();
17
17
  console.log(`Updated SSN for ${email}`);
@@ -0,0 +1,14 @@
1
+ export interface AudkenniAuthResponse {
2
+ authId?: string;
3
+ callbacks?: any[];
4
+ tokenId?: string;
5
+ [key: string]: any;
6
+ }
7
+ export default class AudkenniRestService {
8
+ private endpoint;
9
+ private client;
10
+ start(): Promise<AudkenniAuthResponse>;
11
+ authenticate(phone: string, authenticator: 'sim' | 'app'): Promise<AudkenniAuthResponse>;
12
+ continue(authId: string, callbacks: any[]): Promise<AudkenniAuthResponse>;
13
+ poll(authId: string, callbacks?: any[], interval?: number, maxAttempts?: number): Promise<AudkenniAuthResponse>;
14
+ }
@@ -0,0 +1,52 @@
1
+ import axios from "axios";
2
+ export default class AudkenniRestService {
3
+ constructor() {
4
+ this.endpoint = "https://idp.audkenni.is/sso/json/realms/root/realms/audkenni/authenticate";
5
+ this.client = axios.create({
6
+ headers: {
7
+ Accept: "application/json",
8
+ "Content-Type": "application/json",
9
+ },
10
+ validateStatus: () => true,
11
+ });
12
+ }
13
+ async start() {
14
+ const { data } = await this.client.post(this.endpoint, {});
15
+ return data;
16
+ }
17
+ async authenticate(phone, authenticator) {
18
+ const startData = await this.start();
19
+ if (!startData.authId || !startData.callbacks) {
20
+ throw new Error('Invalid start response');
21
+ }
22
+ const callbacks = (startData.callbacks || []).map((cb) => {
23
+ if (cb.type === 'NameCallback') {
24
+ cb.input[0].value = phone;
25
+ }
26
+ else if (cb.type === 'ChoiceCallback') {
27
+ const choices = cb.output?.find((o) => o.name === 'choices')?.value || [];
28
+ const idx = choices.findIndex((c) => c.toLowerCase().includes(authenticator));
29
+ cb.input[0].value = idx >= 0 ? idx : 0;
30
+ }
31
+ return cb;
32
+ });
33
+ const cont = await this.continue(startData.authId, callbacks);
34
+ return this.poll(cont.authId || startData.authId, cont.callbacks);
35
+ }
36
+ async continue(authId, callbacks) {
37
+ const payload = { authId, callbacks };
38
+ const { data } = await this.client.post(this.endpoint, payload);
39
+ return data;
40
+ }
41
+ async poll(authId, callbacks, interval = 2000, maxAttempts = 30) {
42
+ for (let i = 0; i < maxAttempts; i++) {
43
+ const payload = callbacks ? { authId, callbacks } : { authId };
44
+ const { data } = await this.client.post(this.endpoint, payload);
45
+ if (data.tokenId) {
46
+ return data;
47
+ }
48
+ await new Promise((r) => setTimeout(r, interval));
49
+ }
50
+ throw new Error("Timeout waiting for tokenId");
51
+ }
52
+ }