@modelprofile.com/flexharness 9.10.0 → 10.1.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.
Files changed (54) hide show
  1. package/dist_ts/00_commitinfo_data.js +1 -1
  2. package/dist_ts/classes.flexharness.d.ts +2 -0
  3. package/dist_ts/classes.flexharness.js +101 -7
  4. package/dist_ts/classes.stores.js +11 -119
  5. package/dist_ts/errors.d.ts +7 -2
  6. package/dist_ts/errors.js +1 -1
  7. package/dist_ts/interfaces.d.ts +1 -0
  8. package/dist_ts/utils.harnessoptions.js +2 -1
  9. package/dist_ts/utils.storerules.d.ts +28 -0
  10. package/dist_ts/utils.storerules.js +107 -0
  11. package/dist_ts_chat_harness/classes.transcript.d.ts +88 -0
  12. package/dist_ts_chat_harness/classes.transcript.js +268 -0
  13. package/dist_ts_chat_harness/functions.intents.d.ts +62 -0
  14. package/dist_ts_chat_harness/functions.intents.js +96 -0
  15. package/dist_ts_chat_harness/functions.mapping.d.ts +33 -0
  16. package/dist_ts_chat_harness/functions.mapping.js +220 -0
  17. package/dist_ts_chat_harness/index.d.ts +3 -0
  18. package/dist_ts_chat_harness/index.js +4 -0
  19. package/dist_ts_chat_harness/plugins.d.ts +1 -0
  20. package/dist_ts_chat_harness/plugins.js +2 -0
  21. package/dist_ts_kvm/classes.browserkvm.js +3 -3
  22. package/dist_ts_kvm/plugins.d.ts +1 -1
  23. package/dist_ts_kvm/plugins.js +2 -2
  24. package/dist_ts_stores_nosqldb/classes.nosqldbstores.d.ts +31 -0
  25. package/dist_ts_stores_nosqldb/classes.nosqldbstores.js +312 -0
  26. package/dist_ts_stores_nosqldb/classes.recordstore.d.ts +110 -0
  27. package/dist_ts_stores_nosqldb/classes.recordstore.js +437 -0
  28. package/dist_ts_stores_nosqldb/index.d.ts +2 -0
  29. package/dist_ts_stores_nosqldb/index.js +3 -0
  30. package/dist_ts_stores_nosqldb/plugins.d.ts +7 -0
  31. package/dist_ts_stores_nosqldb/plugins.js +10 -0
  32. package/package.json +27 -7
  33. package/readme.md +14 -6
  34. package/ts/00_commitinfo_data.ts +1 -1
  35. package/ts/classes.flexharness.ts +102 -7
  36. package/ts/classes.stores.ts +28 -172
  37. package/ts/errors.ts +8 -2
  38. package/ts/interfaces.ts +1 -0
  39. package/ts/readme.md +14 -6
  40. package/ts/utils.harnessoptions.ts +1 -0
  41. package/ts/utils.storerules.ts +177 -0
  42. package/ts_chat_harness/classes.transcript.ts +365 -0
  43. package/ts_chat_harness/functions.intents.ts +169 -0
  44. package/ts_chat_harness/functions.mapping.ts +267 -0
  45. package/ts_chat_harness/index.ts +33 -0
  46. package/ts_chat_harness/plugins.ts +18 -0
  47. package/ts_chat_harness/readme.md +114 -0
  48. package/ts_kvm/classes.browserkvm.ts +2 -2
  49. package/ts_kvm/plugins.ts +1 -1
  50. package/ts_stores_nosqldb/classes.nosqldbstores.ts +465 -0
  51. package/ts_stores_nosqldb/classes.recordstore.ts +560 -0
  52. package/ts_stores_nosqldb/index.ts +9 -0
  53. package/ts_stores_nosqldb/plugins.ts +38 -0
  54. package/ts_stores_nosqldb/readme.md +53 -0
@@ -0,0 +1,465 @@
1
+ import * as plugins from './plugins.js';
2
+ import { FlexHarnessStoreConflictError, FlexHarnessValidationError } from '../ts/errors.js';
3
+ import type {
4
+ IFlexAgentEventStoreProvider,
5
+ IFlexHarnessStores,
6
+ IFlexPermissionSnapshot,
7
+ IFlexPermissionStore,
8
+ IFlexProjectManagementSnapshot,
9
+ IFlexProjectManagementStore,
10
+ IFlexProjectManagementTombstone,
11
+ IFlexProjectManagementWriteContext,
12
+ IFlexProjectionSnapshotV3,
13
+ IFlexProjectionStore,
14
+ IFlexScopeSnapshot,
15
+ IFlexScopeStore,
16
+ IFlexToolJobStoreProvider,
17
+ TFlexProjectionSnapshot,
18
+ TFlexProjectManagementRecord,
19
+ } from '../ts/interfaces.js';
20
+ import {
21
+ assertFlexPermissionSnapshot,
22
+ assertFlexProjectionSnapshot,
23
+ assertFlexScopeSnapshot,
24
+ } from '../ts/utils.json.js';
25
+ import {
26
+ assertFlexProjectManagementRecord,
27
+ assertFlexProjectManagementSnapshot,
28
+ assertFlexProjectManagementTombstone,
29
+ } from '../ts/utils.projectmanagement.js';
30
+ import {
31
+ createAgentEventSnapshot,
32
+ createToolJobSnapshot,
33
+ normalizeJsonObjectUndefined,
34
+ validateAgentEventArchive,
35
+ validateAgentEventSnapshot,
36
+ validateToolJobSnapshot,
37
+ } from '../ts/utils.storecodecs.js';
38
+ import {
39
+ assertProjectionSession,
40
+ assertProjectManagementSaveAdmitted,
41
+ assertSameArchiveEvents,
42
+ isProjectManagementTombstoneAdmitted,
43
+ providerKey,
44
+ validateExpectedRevision,
45
+ validateIdentifier,
46
+ validateProjectManagementWriteContext,
47
+ validateSnapshotSave,
48
+ } from '../ts/utils.storerules.js';
49
+ import { FlexRecordStore, type IFlexRecordWrite } from './classes.recordstore.js';
50
+
51
+ function revisionWrite<TSnapshot extends { revision: number }>(
52
+ current: { revision: number } | undefined,
53
+ snapshot: TSnapshot,
54
+ expectedRevision: number,
55
+ conflict: (actualRevision: number) => Error,
56
+ ): IFlexRecordWrite {
57
+ const actualRevision = current?.revision ?? 0;
58
+ if (actualRevision !== expectedRevision) throw conflict(actualRevision);
59
+ return { revision: snapshot.revision, value: snapshot };
60
+ }
61
+
62
+ function validateScope(value: unknown): IFlexScopeSnapshot {
63
+ assertFlexScopeSnapshot(value);
64
+ return value;
65
+ }
66
+
67
+ function validatePermission(value: unknown): IFlexPermissionSnapshot {
68
+ assertFlexPermissionSnapshot(value);
69
+ return value;
70
+ }
71
+
72
+ function validateProjectManagement(value: unknown): TFlexProjectManagementRecord {
73
+ assertFlexProjectManagementRecord(value);
74
+ return value;
75
+ }
76
+
77
+ class NoSqlScopeStore implements IFlexScopeStore {
78
+ constructor(private readonly records: FlexRecordStore) {}
79
+
80
+ public load(storageKey: string): Promise<IFlexScopeSnapshot | undefined> {
81
+ validateIdentifier(storageKey, 'storageKey');
82
+ return this.records.read({ domain: 'scope', storageKey }, validateScope);
83
+ }
84
+
85
+ public async save(
86
+ storageKey: string,
87
+ snapshot: IFlexScopeSnapshot,
88
+ expectedRevision: number,
89
+ ): Promise<void> {
90
+ validateIdentifier(storageKey, 'storageKey');
91
+ const validated = validateSnapshotSave(snapshot, expectedRevision, assertFlexScopeSnapshot);
92
+ const conflict = (actualRevision: number) =>
93
+ new FlexHarnessStoreConflictError(storageKey, expectedRevision, actualRevision);
94
+ await this.records.mutate({ domain: 'scope', storageKey }, {
95
+ validate: validateScope,
96
+ decide: (current) => revisionWrite(current, validated, expectedRevision, conflict),
97
+ contention: conflict,
98
+ });
99
+ }
100
+ }
101
+
102
+ class NoSqlProjectionStore implements IFlexProjectionStore {
103
+ constructor(private readonly records: FlexRecordStore) {}
104
+
105
+ private validator(sessionId: string): (value: unknown) => TFlexProjectionSnapshot {
106
+ return (value) => {
107
+ assertFlexProjectionSnapshot(value);
108
+ assertProjectionSession(value, sessionId);
109
+ return value;
110
+ };
111
+ }
112
+
113
+ public load(storageKey: string, sessionId: string): Promise<TFlexProjectionSnapshot | undefined> {
114
+ providerKey(storageKey, sessionId);
115
+ return this.records.read(
116
+ { domain: 'projection', storageKey, sessionId },
117
+ this.validator(sessionId),
118
+ );
119
+ }
120
+
121
+ public async save(
122
+ storageKey: string,
123
+ sessionId: string,
124
+ snapshot: IFlexProjectionSnapshotV3,
125
+ expectedRevision: number,
126
+ ): Promise<void> {
127
+ const key = providerKey(storageKey, sessionId);
128
+ const validated = validateSnapshotSave(snapshot, expectedRevision, assertFlexProjectionSnapshot);
129
+ assertProjectionSession(validated, sessionId);
130
+ const conflict = (actualRevision: number) =>
131
+ new FlexHarnessStoreConflictError(key, expectedRevision, actualRevision);
132
+ await this.records.mutate({ domain: 'projection', storageKey, sessionId }, {
133
+ validate: this.validator(sessionId),
134
+ decide: (current) => revisionWrite(current, validated, expectedRevision, conflict),
135
+ contention: conflict,
136
+ });
137
+ }
138
+
139
+ public deleteSession(storageKey: string, sessionId: string): Promise<void> {
140
+ providerKey(storageKey, sessionId);
141
+ return this.records.deleteOwner(storageKey, sessionId, ['projection']);
142
+ }
143
+ }
144
+
145
+ class NoSqlPermissionStore implements IFlexPermissionStore {
146
+ constructor(private readonly records: FlexRecordStore) {}
147
+
148
+ public load(storageKey: string, sessionId: string): Promise<IFlexPermissionSnapshot | undefined> {
149
+ providerKey(storageKey, sessionId);
150
+ return this.records.read({ domain: 'permission', storageKey, sessionId }, validatePermission);
151
+ }
152
+
153
+ public async save(
154
+ storageKey: string,
155
+ sessionId: string,
156
+ snapshot: IFlexPermissionSnapshot,
157
+ expectedRevision: number,
158
+ ): Promise<void> {
159
+ const key = providerKey(storageKey, sessionId);
160
+ const validated = validateSnapshotSave(snapshot, expectedRevision, assertFlexPermissionSnapshot);
161
+ const conflict = (actualRevision: number) =>
162
+ new FlexHarnessStoreConflictError(key, expectedRevision, actualRevision);
163
+ await this.records.mutate({ domain: 'permission', storageKey, sessionId }, {
164
+ validate: validatePermission,
165
+ decide: (current) => revisionWrite(current, validated, expectedRevision, conflict),
166
+ contention: conflict,
167
+ });
168
+ }
169
+
170
+ public deleteSession(storageKey: string, sessionId: string): Promise<void> {
171
+ providerKey(storageKey, sessionId);
172
+ return this.records.deleteOwner(storageKey, sessionId, ['permission']);
173
+ }
174
+ }
175
+
176
+ class NoSqlProjectManagementStore implements IFlexProjectManagementStore {
177
+ constructor(private readonly records: FlexRecordStore) {}
178
+
179
+ public load(
180
+ storageKey: string,
181
+ sessionId: string,
182
+ ): Promise<TFlexProjectManagementRecord | undefined> {
183
+ providerKey(storageKey, sessionId);
184
+ return this.records.read(
185
+ { domain: 'projectManagement', storageKey, sessionId },
186
+ validateProjectManagement,
187
+ );
188
+ }
189
+
190
+ public async save(
191
+ storageKey: string,
192
+ sessionId: string,
193
+ snapshot: IFlexProjectManagementSnapshot,
194
+ expectedRevision: number,
195
+ writeContext: IFlexProjectManagementWriteContext,
196
+ ): Promise<void> {
197
+ const key = providerKey(storageKey, sessionId);
198
+ validateProjectManagementWriteContext(writeContext);
199
+ const validated = validateSnapshotSave(
200
+ snapshot,
201
+ expectedRevision,
202
+ assertFlexProjectManagementSnapshot,
203
+ );
204
+ await this.records.mutate({ domain: 'projectManagement', storageKey, sessionId }, {
205
+ validate: validateProjectManagement,
206
+ decide: (current) => {
207
+ assertProjectManagementSaveAdmitted(key, current, validated, expectedRevision);
208
+ return { revision: validated.revision, value: validated };
209
+ },
210
+ contention: (actualRevision) =>
211
+ new FlexHarnessStoreConflictError(key, expectedRevision, actualRevision),
212
+ });
213
+ }
214
+
215
+ public async tombstoneSession(
216
+ storageKey: string,
217
+ sessionId: string,
218
+ tombstone: IFlexProjectManagementTombstone,
219
+ expectedRevision: number,
220
+ ): Promise<void> {
221
+ const key = providerKey(storageKey, sessionId);
222
+ const validated = validateSnapshotSave(
223
+ tombstone,
224
+ expectedRevision,
225
+ assertFlexProjectManagementTombstone,
226
+ );
227
+ await this.records.mutate({ domain: 'projectManagement', storageKey, sessionId }, {
228
+ validate: validateProjectManagement,
229
+ decide: (current) => (
230
+ isProjectManagementTombstoneAdmitted(key, current, validated, expectedRevision)
231
+ ? { revision: validated.revision, value: validated }
232
+ : undefined
233
+ ),
234
+ contention: (actualRevision) =>
235
+ new FlexHarnessStoreConflictError(key, expectedRevision, actualRevision),
236
+ });
237
+ }
238
+
239
+ public purgeNamespace(storageKey: string): Promise<void> {
240
+ validateIdentifier(storageKey, 'storageKey');
241
+ return this.records.purgeNamespace(storageKey, 'projectManagement');
242
+ }
243
+ }
244
+
245
+ class NoSqlAgentEventStore implements plugins.IAgentEventStoreV2 {
246
+ public readonly eventSchemaVersion = 2 as const;
247
+
248
+ constructor(
249
+ private readonly records: FlexRecordStore,
250
+ private readonly storageKey: string,
251
+ private readonly sessionId: string,
252
+ ) {}
253
+
254
+ private assertSession(sessionId: string): void {
255
+ if (sessionId !== this.sessionId) {
256
+ throw new FlexHarnessValidationError(
257
+ `Agent event store is bound to session "${this.sessionId}", not "${sessionId}".`,
258
+ );
259
+ }
260
+ }
261
+
262
+ private readonly validateSnapshot = (value: unknown): plugins.IAgentEventSnapshotV2 =>
263
+ validateAgentEventSnapshot(value, this.sessionId);
264
+
265
+ public load(sessionId: string): Promise<plugins.IAgentEventSnapshotV2 | undefined> {
266
+ this.assertSession(sessionId);
267
+ return this.records.read(
268
+ { domain: 'agentEvents', storageKey: this.storageKey, sessionId },
269
+ this.validateSnapshot,
270
+ );
271
+ }
272
+
273
+ public async save(
274
+ sessionId: string,
275
+ events: readonly plugins.TAgentEvent[],
276
+ expectedRevision: number,
277
+ ): Promise<number> {
278
+ this.assertSession(sessionId);
279
+ validateExpectedRevision(expectedRevision);
280
+ const snapshot = createAgentEventSnapshot(sessionId, events, expectedRevision + 1);
281
+ validateAgentEventSnapshot(snapshot, sessionId);
282
+ const conflict = (actualRevision: number) =>
283
+ new plugins.AgentEventStoreConflictError(sessionId, expectedRevision, actualRevision);
284
+ await this.records.mutate({ domain: 'agentEvents', storageKey: this.storageKey, sessionId }, {
285
+ validate: this.validateSnapshot,
286
+ decide: (current) => revisionWrite(current, snapshot, expectedRevision, conflict),
287
+ contention: conflict,
288
+ });
289
+ return snapshot.revision;
290
+ }
291
+
292
+ public async archive(archive: plugins.IAgentEventArchiveV2): Promise<void> {
293
+ this.assertSession(archive.sessionId);
294
+ const normalized = normalizeJsonObjectUndefined(archive, '$archive');
295
+ const validated = validateAgentEventArchive(normalized, this.sessionId, archive.archiveId);
296
+ const address = {
297
+ domain: 'agentEventArchive' as const,
298
+ storageKey: this.storageKey,
299
+ sessionId: this.sessionId,
300
+ archiveId: archive.archiveId,
301
+ };
302
+ await this.records.mutate(address, {
303
+ validate: (value) => validateAgentEventArchive(value, this.sessionId, archive.archiveId),
304
+ decide: (current) => {
305
+ if (!current) return { revision: 0, value: validated };
306
+ assertSameArchiveEvents(current, validated);
307
+ return undefined;
308
+ },
309
+ contention: () =>
310
+ new Error(`Archive "${archive.archiveId}" is contended by concurrent writers.`),
311
+ });
312
+ }
313
+
314
+ public loadArchive(
315
+ sessionId: string,
316
+ archiveId: string,
317
+ ): Promise<plugins.IAgentEventArchiveV2 | undefined> {
318
+ this.assertSession(sessionId);
319
+ validateIdentifier(archiveId, 'archiveId');
320
+ return this.records.read(
321
+ { domain: 'agentEventArchive', storageKey: this.storageKey, sessionId, archiveId },
322
+ (value) => validateAgentEventArchive(value, this.sessionId, archiveId),
323
+ );
324
+ }
325
+ }
326
+
327
+ class NoSqlAgentEventStoreProvider implements IFlexAgentEventStoreProvider {
328
+ private readonly stores = new Map<string, NoSqlAgentEventStore>();
329
+
330
+ constructor(private readonly records: FlexRecordStore) {}
331
+
332
+ public getStore(storageKey: string, sessionId: string): NoSqlAgentEventStore {
333
+ const key = providerKey(storageKey, sessionId);
334
+ let store = this.stores.get(key);
335
+ if (!store) {
336
+ store = new NoSqlAgentEventStore(this.records, storageKey, sessionId);
337
+ this.stores.set(key, store);
338
+ }
339
+ return store;
340
+ }
341
+
342
+ public releaseSession(storageKey: string, sessionId: string): void {
343
+ this.stores.delete(providerKey(storageKey, sessionId));
344
+ }
345
+
346
+ public async deleteSession(storageKey: string, sessionId: string): Promise<void> {
347
+ const key = providerKey(storageKey, sessionId);
348
+ await this.records.deleteOwner(storageKey, sessionId, ['agentEvents', 'agentEventArchive']);
349
+ this.stores.delete(key);
350
+ }
351
+ }
352
+
353
+ class NoSqlToolJobStore implements plugins.IToolJobStore {
354
+ constructor(
355
+ private readonly records: FlexRecordStore,
356
+ private readonly storageKey: string,
357
+ private readonly sessionId: string,
358
+ ) {}
359
+
360
+ public async load(abortSignal?: AbortSignal): Promise<plugins.IToolJobSnapshot | undefined> {
361
+ abortSignal?.throwIfAborted();
362
+ const snapshot = await this.records.read(
363
+ { domain: 'jobs', storageKey: this.storageKey, sessionId: this.sessionId },
364
+ validateToolJobSnapshot,
365
+ );
366
+ abortSignal?.throwIfAborted();
367
+ return snapshot;
368
+ }
369
+
370
+ public async save(
371
+ jobs: readonly plugins.IToolJobState[],
372
+ expectedRevision: number,
373
+ abortSignal?: AbortSignal,
374
+ ): Promise<number> {
375
+ abortSignal?.throwIfAborted();
376
+ validateExpectedRevision(expectedRevision);
377
+ const snapshot = createToolJobSnapshot(jobs, expectedRevision + 1);
378
+ const conflict = (actualRevision: number) =>
379
+ new plugins.ToolJobStoreConflictError(expectedRevision, actualRevision);
380
+ await this.records.mutate(
381
+ { domain: 'jobs', storageKey: this.storageKey, sessionId: this.sessionId },
382
+ {
383
+ validate: validateToolJobSnapshot,
384
+ decide: (current) => {
385
+ abortSignal?.throwIfAborted();
386
+ return revisionWrite(current, snapshot, expectedRevision, conflict);
387
+ },
388
+ contention: conflict,
389
+ },
390
+ );
391
+ return snapshot.revision;
392
+ }
393
+ }
394
+
395
+ class NoSqlToolJobStoreProvider implements IFlexToolJobStoreProvider {
396
+ private readonly stores = new Map<string, NoSqlToolJobStore>();
397
+
398
+ constructor(private readonly records: FlexRecordStore) {}
399
+
400
+ public getStore(storageKey: string, sessionId: string): NoSqlToolJobStore {
401
+ const key = providerKey(storageKey, sessionId);
402
+ let store = this.stores.get(key);
403
+ if (!store) {
404
+ store = new NoSqlToolJobStore(this.records, storageKey, sessionId);
405
+ this.stores.set(key, store);
406
+ }
407
+ return store;
408
+ }
409
+
410
+ public releaseSession(storageKey: string, sessionId: string): void {
411
+ this.stores.delete(providerKey(storageKey, sessionId));
412
+ }
413
+
414
+ public async deleteSession(storageKey: string, sessionId: string): Promise<void> {
415
+ const key = providerKey(storageKey, sessionId);
416
+ await this.records.deleteOwner(storageKey, sessionId, ['jobs']);
417
+ this.stores.delete(key);
418
+ }
419
+ }
420
+
421
+ export interface INoSqlFlexHarnessStoresOptions {
422
+ /**
423
+ * An initialized database the application owns, on NoSQLDB or a MongoDB deployment with
424
+ * transactions. The stores never close it.
425
+ */
426
+ db: plugins.SmartdataDb;
427
+ /** Client deadline of every database call and transaction, 1 to 120000 ms. Defaults to 30000. */
428
+ operationTimeoutMs?: number;
429
+ }
430
+
431
+ /**
432
+ * Every FlexHarness persistence domain on `@lossless.org/client/nosqldb`, with compare-and-swap
433
+ * that holds across processes. Records live in `flexharness_records`; payloads longer than one
434
+ * chunk live in `flexharness_chunks`.
435
+ */
436
+ export class NoSqlFlexHarnessStores implements IFlexHarnessStores {
437
+ public readonly scopes: IFlexScopeStore;
438
+ public readonly projections: IFlexProjectionStore;
439
+ public readonly permissions: IFlexPermissionStore;
440
+ public readonly projectManagement: IFlexProjectManagementStore;
441
+ public readonly agentEvents: IFlexAgentEventStoreProvider;
442
+ public readonly jobs: IFlexToolJobStoreProvider;
443
+ private readonly records: FlexRecordStore;
444
+
445
+ constructor(options: INoSqlFlexHarnessStoresOptions) {
446
+ if (!(options?.db instanceof plugins.SmartdataDb)) {
447
+ throw new FlexHarnessValidationError('NoSqlFlexHarnessStores requires a SmartdataDb.');
448
+ }
449
+ this.records = new FlexRecordStore(options);
450
+ this.scopes = new NoSqlScopeStore(this.records);
451
+ this.projections = new NoSqlProjectionStore(this.records);
452
+ this.permissions = new NoSqlPermissionStore(this.records);
453
+ this.projectManagement = new NoSqlProjectManagementStore(this.records);
454
+ this.agentEvents = new NoSqlAgentEventStoreProvider(this.records);
455
+ this.jobs = new NoSqlToolJobStoreProvider(this.records);
456
+ }
457
+
458
+ /**
459
+ * Creates or verifies the two collections and their indexes. Every store operation awaits the
460
+ * same preparation, so calling it at startup only moves that work, and its failure, forward.
461
+ */
462
+ public prepare(): Promise<void> {
463
+ return this.records.prepare();
464
+ }
465
+ }