@modelprofile.com/flexharness 5.1.0 → 5.2.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/ts/interfaces.ts CHANGED
@@ -155,6 +155,7 @@ export interface IFlexSession {
155
155
 
156
156
  export interface IFlexCreateSessionOptions {
157
157
  sessionId?: string;
158
+ sessionGenerationId?: string;
158
159
  title?: string;
159
160
  }
160
161
 
@@ -342,7 +343,7 @@ export interface IFlexScopeResolver<TScope> {
342
343
  resolveScope(scopeId: string): Promise<IFlexResolvedScope<TScope>> | IFlexResolvedScope<TScope>;
343
344
  }
344
345
 
345
- export interface IFlexModelResolverContext<TScope> {
346
+ export interface IFlexModelResolverContext<TScope> extends IFlexSessionGeneration {
346
347
  scopeId: string;
347
348
  scope: TScope;
348
349
  sessionId: string;
@@ -376,7 +377,8 @@ export interface IFlexPermissionRequestInput {
376
377
  metadata?: TJsonValue;
377
378
  }
378
379
 
379
- export interface IFlexPermissionRequest extends IFlexPermissionRequestInput {
380
+ export interface IFlexPermissionRequest
381
+ extends IFlexPermissionRequestInput, IFlexSessionGeneration {
380
382
  permissionId: string;
381
383
  scopeId: string;
382
384
  sessionId: string;
@@ -573,12 +575,27 @@ export interface IFlexPermissionSnapshot {
573
575
 
574
576
  export const FLEX_PROJECT_MANAGEMENT_SCHEMA_VERSION = 1 as const;
575
577
  export const FLEX_SESSION_GENERATION_ID_MAX_BYTES = 128;
578
+ export const FLEX_SESSION_GENERATION_COHORT_MAX_ENTRIES = 2048;
576
579
 
577
580
  export interface IFlexSessionGeneration {
578
581
  sessionGenerationId: string;
579
582
  sessionGenerationSequence: number;
580
583
  }
581
584
 
585
+ export interface IFlexSessionGenerationCohortEntry extends IFlexSessionGeneration {
586
+ sessionId: string;
587
+ }
588
+
589
+ export interface IFlexDeleteSessionGenerationCohortInput {
590
+ root: IFlexSessionGenerationCohortEntry;
591
+ /** Exact authorized entries ordered by strictly ascending sessionId. */
592
+ authorizedCohort: readonly IFlexSessionGenerationCohortEntry[];
593
+ }
594
+
595
+ export interface IFlexDeleteSessionGenerationCohortResult {
596
+ readonly matched: boolean;
597
+ }
598
+
582
599
  export const FLEX_PROJECT_MANAGEMENT_LIMITS = Object.freeze({
583
600
  maxGoalBytes: 8 * 1024,
584
601
  maxScratchpadBytes: 128 * 1024,
@@ -1070,7 +1087,7 @@ export interface IFlexEventArchiveMetadata {
1070
1087
  eventCount: number;
1071
1088
  }
1072
1089
 
1073
- export interface IFlexEventBase {
1090
+ export interface IFlexEventBase extends IFlexSessionGeneration {
1074
1091
  readonly eventId: string;
1075
1092
  readonly sequence: number;
1076
1093
  readonly timestamp: string;
@@ -1,9 +1,15 @@
1
1
  import { FlexHarnessValidationError } from './errors.js';
2
- import { FLEX_PROJECT_MANAGEMENT_LIMITS } from './interfaces.js';
2
+ import {
3
+ FLEX_PROJECT_MANAGEMENT_LIMITS,
4
+ FLEX_SESSION_GENERATION_COHORT_MAX_ENTRIES,
5
+ FLEX_SESSION_GENERATION_ID_MAX_BYTES,
6
+ } from './interfaces.js';
3
7
  import type {
4
8
  IFlexCreateProjectTaskInput,
9
+ IFlexDeleteSessionGenerationCohortInput,
5
10
  IFlexErrorInfo,
6
11
  IFlexPromptOptions,
12
+ IFlexSessionGenerationCohortEntry,
7
13
  IFlexSlashCommandExecutionOptions,
8
14
  IFlexUpdateProjectTaskInput,
9
15
  IFlexUpdateSessionOptions,
@@ -14,6 +20,7 @@ import type {
14
20
  const maxProjectedErrorNameBytes = 128;
15
21
  const maxProjectedErrorMessageBytes = 2048;
16
22
  const maxProjectedErrorCodeBytes = 128;
23
+ const controlCharacterPattern = /[\u0000-\u001f\u007f]/u;
17
24
 
18
25
  export function validateIdentifier(value: string, name: string): void {
19
26
  if (typeof value !== 'string' || value.length === 0) {
@@ -38,6 +45,123 @@ export function validateUtf8String(
38
45
  }
39
46
  }
40
47
 
48
+ export function validateSessionGenerationId(
49
+ value: unknown,
50
+ name = 'sessionGenerationId',
51
+ ): asserts value is string {
52
+ if (
53
+ typeof value !== 'string'
54
+ || !value.trim()
55
+ || Buffer.byteLength(value, 'utf8') > FLEX_SESSION_GENERATION_ID_MAX_BYTES
56
+ || controlCharacterPattern.test(value)
57
+ ) {
58
+ throw new FlexHarnessValidationError(
59
+ `${name} must be a non-empty string without control characters of at most ${FLEX_SESSION_GENERATION_ID_MAX_BYTES} UTF-8 bytes.`,
60
+ );
61
+ }
62
+ }
63
+
64
+ function validateSessionGenerationSequence(value: unknown, name: string): asserts value is number {
65
+ if (!Number.isSafeInteger(value) || Number(value) < 1) {
66
+ throw new FlexHarnessValidationError(`${name} must be a positive integer.`);
67
+ }
68
+ }
69
+
70
+ function validateSessionGenerationCohortEntry(
71
+ value: unknown,
72
+ name: string,
73
+ ): Readonly<IFlexSessionGenerationCohortEntry> {
74
+ if (
75
+ !value
76
+ || typeof value !== 'object'
77
+ || Array.isArray(value)
78
+ || ![Object.prototype, null].includes(Object.getPrototypeOf(value))
79
+ ) {
80
+ throw new FlexHarnessValidationError(`${name} must be a plain object.`);
81
+ }
82
+ const entry = value as Record<string, unknown>;
83
+ const keys = Reflect.ownKeys(entry);
84
+ if (
85
+ keys.length !== 3
86
+ || keys.some((key) => typeof key !== 'string'
87
+ || !['sessionId', 'sessionGenerationId', 'sessionGenerationSequence'].includes(key))
88
+ ) {
89
+ throw new FlexHarnessValidationError(`${name} must use the exact session generation shape.`);
90
+ }
91
+ validateIdentifier(entry.sessionId as string, `${name}.sessionId`);
92
+ validateSessionGenerationId(entry.sessionGenerationId, `${name}.sessionGenerationId`);
93
+ validateSessionGenerationSequence(
94
+ entry.sessionGenerationSequence,
95
+ `${name}.sessionGenerationSequence`,
96
+ );
97
+ return Object.freeze({
98
+ sessionId: entry.sessionId as string,
99
+ sessionGenerationId: entry.sessionGenerationId,
100
+ sessionGenerationSequence: entry.sessionGenerationSequence,
101
+ });
102
+ }
103
+
104
+ export function validateDeleteSessionGenerationCohortInput(
105
+ value: IFlexDeleteSessionGenerationCohortInput,
106
+ ): {
107
+ root: Readonly<IFlexSessionGenerationCohortEntry>;
108
+ authorizedBySessionId: ReadonlyMap<string, Readonly<IFlexSessionGenerationCohortEntry>>;
109
+ } {
110
+ if (
111
+ !value
112
+ || typeof value !== 'object'
113
+ || Array.isArray(value)
114
+ || ![Object.prototype, null].includes(Object.getPrototypeOf(value))
115
+ ) {
116
+ throw new FlexHarnessValidationError(
117
+ 'deleteSessionGenerationCohort input must be a plain object.',
118
+ );
119
+ }
120
+ const keys = Reflect.ownKeys(value);
121
+ if (
122
+ keys.length !== 2
123
+ || keys.some((key) => typeof key !== 'string'
124
+ || !['root', 'authorizedCohort'].includes(key))
125
+ ) {
126
+ throw new FlexHarnessValidationError(
127
+ 'deleteSessionGenerationCohort input must use the exact root and authorizedCohort shape.',
128
+ );
129
+ }
130
+ const root = validateSessionGenerationCohortEntry(value.root, 'root');
131
+ if (!Array.isArray(value.authorizedCohort)) {
132
+ throw new FlexHarnessValidationError('authorizedCohort must be an array.');
133
+ }
134
+ if (value.authorizedCohort.length > FLEX_SESSION_GENERATION_COHORT_MAX_ENTRIES) {
135
+ throw new FlexHarnessValidationError(
136
+ `authorizedCohort exceeds ${FLEX_SESSION_GENERATION_COHORT_MAX_ENTRIES} entries.`,
137
+ );
138
+ }
139
+ const authorizedBySessionId = new Map<
140
+ string,
141
+ Readonly<IFlexSessionGenerationCohortEntry>
142
+ >();
143
+ let previousSessionId: string | undefined;
144
+ for (const [index, rawEntry] of value.authorizedCohort.entries()) {
145
+ const entry = validateSessionGenerationCohortEntry(
146
+ rawEntry,
147
+ `authorizedCohort[${index}]`,
148
+ );
149
+ if (authorizedBySessionId.has(entry.sessionId)) {
150
+ throw new FlexHarnessValidationError(
151
+ `authorizedCohort contains duplicate sessionId "${entry.sessionId}".`,
152
+ );
153
+ }
154
+ if (previousSessionId !== undefined && previousSessionId >= entry.sessionId) {
155
+ throw new FlexHarnessValidationError(
156
+ 'authorizedCohort must be ordered by strictly ascending sessionId.',
157
+ );
158
+ }
159
+ authorizedBySessionId.set(entry.sessionId, entry);
160
+ previousSessionId = entry.sessionId;
161
+ }
162
+ return { root, authorizedBySessionId };
163
+ }
164
+
41
165
  export function validateProjectedErrorInfo(value: unknown): IFlexErrorInfo {
42
166
  if (
43
167
  !value