@modelprofile.com/flexharness 5.1.0 → 5.3.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/changelog.md +19 -0
- package/dist_ts/00_commitinfo_data.js +1 -1
- package/dist_ts/classes.flexharness.d.ts +21 -1
- package/dist_ts/classes.flexharness.js +568 -141
- package/dist_ts/interfaces.d.ts +44 -4
- package/dist_ts/interfaces.js +2 -1
- package/dist_ts/utils.validation.d.ts +6 -1
- package/dist_ts/utils.validation.js +74 -2
- package/package.json +1 -1
- package/readme.md +52 -12
- package/ts/00_commitinfo_data.ts +1 -1
- package/ts/classes.flexharness.ts +824 -142
- package/ts/interfaces.ts +53 -3
- package/ts/utils.validation.ts +125 -1
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
|
|
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,
|
|
@@ -927,6 +944,7 @@ export interface IFlexRedoSessionResult {
|
|
|
927
944
|
export type TFlexExternalErrorSource =
|
|
928
945
|
| 'modelResolver'
|
|
929
946
|
| 'toolProvider'
|
|
947
|
+
| 'delegatedRunAdmissionProvider'
|
|
930
948
|
| 'toolExecution'
|
|
931
949
|
| 'toolCallback'
|
|
932
950
|
| 'toolCleanup'
|
|
@@ -947,6 +965,37 @@ export type TFlexExternalErrorProjector = (
|
|
|
947
965
|
context: IFlexExternalErrorContext,
|
|
948
966
|
) => IFlexErrorInfo;
|
|
949
967
|
|
|
968
|
+
export interface IFlexDelegatedRunAdmissionContext<TScope> extends IFlexSessionGeneration {
|
|
969
|
+
readonly scopeId: string;
|
|
970
|
+
readonly scope: TScope;
|
|
971
|
+
readonly storageKey: string;
|
|
972
|
+
readonly sessionId: string;
|
|
973
|
+
readonly sessionGenerationId: string;
|
|
974
|
+
readonly sessionGenerationSequence: number;
|
|
975
|
+
readonly queueId: string;
|
|
976
|
+
readonly runId: string;
|
|
977
|
+
readonly parentSessionId: string;
|
|
978
|
+
readonly parentSessionGenerationId: string;
|
|
979
|
+
readonly parentSessionGenerationSequence: number;
|
|
980
|
+
readonly parentQueueId: string;
|
|
981
|
+
readonly parentRunId: string;
|
|
982
|
+
readonly parentToolCallId: string;
|
|
983
|
+
readonly agent: string;
|
|
984
|
+
readonly depth: number;
|
|
985
|
+
readonly signal: AbortSignal;
|
|
986
|
+
}
|
|
987
|
+
|
|
988
|
+
export interface IFlexDelegatedRunAdmissionLease {
|
|
989
|
+
/** Must be idempotent and safe to retry after a rejected close attempt. */
|
|
990
|
+
close(): Promise<void> | void;
|
|
991
|
+
}
|
|
992
|
+
|
|
993
|
+
export interface IFlexDelegatedRunAdmissionProvider<TScope> {
|
|
994
|
+
acquireDelegatedRunAdmission(
|
|
995
|
+
context: IFlexDelegatedRunAdmissionContext<TScope>,
|
|
996
|
+
): Promise<IFlexDelegatedRunAdmissionLease> | IFlexDelegatedRunAdmissionLease;
|
|
997
|
+
}
|
|
998
|
+
|
|
950
999
|
export interface IFlexExecutionContextProviderContext<TScope> {
|
|
951
1000
|
scopeId: string;
|
|
952
1001
|
scope: TScope;
|
|
@@ -1018,6 +1067,7 @@ export interface IFlexHarnessOptions<TScope> {
|
|
|
1018
1067
|
toolProvider?: IFlexToolProvider<TScope>;
|
|
1019
1068
|
resourceToolProviderResolver?: IFlexResourceToolProviderResolver<TScope>;
|
|
1020
1069
|
executionContextProvider?: IFlexExecutionContextProvider<TScope>;
|
|
1070
|
+
delegatedRunAdmissionProvider?: IFlexDelegatedRunAdmissionProvider<TScope>;
|
|
1021
1071
|
stores?: IFlexHarnessStores;
|
|
1022
1072
|
agentSessionPolicy?: IFlexAgentSessionPolicy<TScope>;
|
|
1023
1073
|
toolOutputLimits?: IFlexJsonLimits;
|
|
@@ -1070,7 +1120,7 @@ export interface IFlexEventArchiveMetadata {
|
|
|
1070
1120
|
eventCount: number;
|
|
1071
1121
|
}
|
|
1072
1122
|
|
|
1073
|
-
export interface IFlexEventBase {
|
|
1123
|
+
export interface IFlexEventBase extends IFlexSessionGeneration {
|
|
1074
1124
|
readonly eventId: string;
|
|
1075
1125
|
readonly sequence: number;
|
|
1076
1126
|
readonly timestamp: string;
|
package/ts/utils.validation.ts
CHANGED
|
@@ -1,9 +1,15 @@
|
|
|
1
1
|
import { FlexHarnessValidationError } from './errors.js';
|
|
2
|
-
import {
|
|
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
|