@modelprofile.com/flexharness 5.0.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
 
@@ -207,7 +208,7 @@ export interface IFlexSlashCommandDescriptor {
207
208
  workspaceReversion: TFlexSlashCommandWorkspaceReversion;
208
209
  }
209
210
 
210
- export interface IFlexSlashCommandHandlerContext<TScope> {
211
+ export interface IFlexSlashCommandHandlerContext<TScope> extends IFlexSessionGeneration {
211
212
  readonly scopeId: string;
212
213
  readonly scope: TScope;
213
214
  readonly storageKey: string;
@@ -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;
@@ -386,7 +388,7 @@ export interface IFlexPermissionRequest extends IFlexPermissionRequestInput {
386
388
 
387
389
  export type TFlexPermissionDecision = 'once' | 'always' | 'reject';
388
390
 
389
- export interface IFlexToolProviderContext<TScope> {
391
+ export interface IFlexToolProviderContext<TScope> extends IFlexSessionGeneration {
390
392
  scopeId: string;
391
393
  scope: TScope;
392
394
  sessionId: string;
@@ -408,7 +410,7 @@ export interface IFlexToolProvider<TScope> {
408
410
  ): Promise<IFlexToolHandle | undefined> | IFlexToolHandle | undefined;
409
411
  }
410
412
 
411
- export interface IFlexResourceToolProviderResolverContext<TScope> {
413
+ export interface IFlexResourceToolProviderResolverContext<TScope> extends IFlexSessionGeneration {
412
414
  scopeId: string;
413
415
  scope: TScope;
414
416
  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,
@@ -793,7 +810,7 @@ export const FLEX_REVERSION_WORKSPACE_ID_MAX_BYTES = 512;
793
810
  export const FLEX_REVERSION_WORKSPACE_LABEL_MAX_BYTES = 2048;
794
811
  export const FLEX_REVERSION_REASON_CODE_MAX_BYTES = 128;
795
812
 
796
- export interface IFlexTurnReversionBaseContext<TScope> {
813
+ export interface IFlexTurnReversionBaseContext<TScope> extends IFlexSessionGeneration {
797
814
  readonly scopeId: string;
798
815
  readonly scope: TScope;
799
816
  readonly storageKey: string;
@@ -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