@modelprofile.com/flexharness 4.1.0 → 4.1.2

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.
@@ -0,0 +1,204 @@
1
+ import { FlexHarnessValidationError } from './errors.js';
2
+ import { FLEX_PROJECT_MANAGEMENT_LIMITS } from './interfaces.js';
3
+ import type {
4
+ IFlexCreateProjectTaskInput,
5
+ IFlexErrorInfo,
6
+ IFlexPromptOptions,
7
+ IFlexSlashCommandExecutionOptions,
8
+ IFlexUpdateProjectTaskInput,
9
+ IFlexUpdateSessionOptions,
10
+ TFlexProjectTaskPriority,
11
+ TFlexProjectTaskStatus,
12
+ } from './interfaces.js';
13
+
14
+ const maxProjectedErrorNameBytes = 128;
15
+ const maxProjectedErrorMessageBytes = 2048;
16
+ const maxProjectedErrorCodeBytes = 128;
17
+
18
+ export function validateIdentifier(value: string, name: string): void {
19
+ if (typeof value !== 'string' || value.length === 0) {
20
+ throw new FlexHarnessValidationError(`${name} must be a non-empty string.`);
21
+ }
22
+ }
23
+
24
+ export function validateUtf8String(
25
+ value: unknown,
26
+ name: string,
27
+ maxBytes: number,
28
+ nonEmpty = false,
29
+ ): asserts value is string {
30
+ if (
31
+ typeof value !== 'string'
32
+ || (nonEmpty && !value.trim())
33
+ || Buffer.byteLength(value, 'utf8') > maxBytes
34
+ ) {
35
+ throw new FlexHarnessValidationError(
36
+ `${name} must be ${nonEmpty ? 'a non-empty ' : 'a '}string of at most ${maxBytes} UTF-8 bytes.`,
37
+ );
38
+ }
39
+ }
40
+
41
+ export function validateProjectedErrorInfo(value: unknown): IFlexErrorInfo {
42
+ if (
43
+ !value
44
+ || typeof value !== 'object'
45
+ || Array.isArray(value)
46
+ || ![Object.prototype, null].includes(Object.getPrototypeOf(value))
47
+ ) {
48
+ throw new FlexHarnessValidationError('External error projection is invalid.');
49
+ }
50
+ const descriptors = Object.getOwnPropertyDescriptors(value);
51
+ const unsupportedKey = Reflect.ownKeys(descriptors).find(
52
+ (key) => typeof key !== 'string' || !['name', 'message', 'code'].includes(key),
53
+ );
54
+ const name = descriptors.name?.value;
55
+ const message = descriptors.message?.value;
56
+ const code = descriptors.code?.value;
57
+ if (
58
+ unsupportedKey
59
+ || descriptors.name?.get !== undefined
60
+ || descriptors.name?.set !== undefined
61
+ || descriptors.message?.get !== undefined
62
+ || descriptors.message?.set !== undefined
63
+ || descriptors.code?.get !== undefined
64
+ || descriptors.code?.set !== undefined
65
+ || typeof name !== 'string'
66
+ || !name
67
+ || Buffer.byteLength(name, 'utf8') > maxProjectedErrorNameBytes
68
+ || typeof message !== 'string'
69
+ || !message
70
+ || Buffer.byteLength(message, 'utf8') > maxProjectedErrorMessageBytes
71
+ || (code !== undefined
72
+ && (typeof code !== 'string'
73
+ || !code
74
+ || Buffer.byteLength(code, 'utf8') > maxProjectedErrorCodeBytes))
75
+ ) {
76
+ throw new FlexHarnessValidationError('External error projection is invalid.');
77
+ }
78
+ return Object.freeze({ name, message, ...(code === undefined ? {} : { code }) });
79
+ }
80
+
81
+ export function validateUpdateSessionOptions(options: IFlexUpdateSessionOptions): void {
82
+ if (!options || typeof options !== 'object' || Array.isArray(options)) {
83
+ throw new FlexHarnessValidationError('updateSession options must be a plain object.');
84
+ }
85
+ const keys = Object.keys(options);
86
+ if (keys.length === 0) throw new FlexHarnessValidationError('updateSession requires title or archived.');
87
+ const unsupportedKey = keys.find((key) => key !== 'title' && key !== 'archived');
88
+ if (unsupportedKey) throw new FlexHarnessValidationError(`updateSession does not support "${unsupportedKey}".`);
89
+ if (Object.prototype.hasOwnProperty.call(options, 'title') && options.title !== null) {
90
+ validateUtf8String(
91
+ options.title,
92
+ 'title',
93
+ FLEX_PROJECT_MANAGEMENT_LIMITS.maxTitleBytes,
94
+ true,
95
+ );
96
+ }
97
+ if (Object.prototype.hasOwnProperty.call(options, 'archived') && typeof options.archived !== 'boolean') {
98
+ throw new FlexHarnessValidationError('archived must be a boolean.');
99
+ }
100
+ }
101
+
102
+ export function validateProjectTaskStatus(value: unknown): asserts value is TFlexProjectTaskStatus {
103
+ if (!['pending', 'in_progress', 'completed', 'cancelled'].includes(String(value))) {
104
+ throw new FlexHarnessValidationError('Project task status is invalid.');
105
+ }
106
+ }
107
+
108
+ export function validateProjectTaskPriority(value: unknown): asserts value is TFlexProjectTaskPriority {
109
+ if (!['high', 'medium', 'low'].includes(String(value))) {
110
+ throw new FlexHarnessValidationError('Project task priority is invalid.');
111
+ }
112
+ }
113
+
114
+ export function validateProjectTaskId(value: unknown): asserts value is string {
115
+ validateUtf8String(
116
+ value,
117
+ 'project task id',
118
+ FLEX_PROJECT_MANAGEMENT_LIMITS.maxTaskIdBytes,
119
+ true,
120
+ );
121
+ }
122
+
123
+ export function validateProjectTaskContent(value: unknown): asserts value is string {
124
+ validateUtf8String(
125
+ value,
126
+ 'project task content',
127
+ FLEX_PROJECT_MANAGEMENT_LIMITS.maxTaskContentBytes,
128
+ true,
129
+ );
130
+ }
131
+
132
+ export function validateCreateProjectTaskInput(input: IFlexCreateProjectTaskInput): void {
133
+ if (!input || typeof input !== 'object' || Array.isArray(input)) {
134
+ throw new FlexHarnessValidationError('Project task create input must be a plain object.');
135
+ }
136
+ const unsupported = Object.keys(input)
137
+ .find((key) => !['id', 'content', 'status', 'priority'].includes(key));
138
+ if (unsupported) {
139
+ throw new FlexHarnessValidationError(`Project task create input does not support "${unsupported}".`);
140
+ }
141
+ validateProjectTaskId(input.id);
142
+ validateProjectTaskContent(input.content);
143
+ if (input.status !== undefined) validateProjectTaskStatus(input.status);
144
+ if (input.priority !== undefined) validateProjectTaskPriority(input.priority);
145
+ }
146
+
147
+ export function validateUpdateProjectTaskInput(input: IFlexUpdateProjectTaskInput): void {
148
+ if (!input || typeof input !== 'object' || Array.isArray(input)) {
149
+ throw new FlexHarnessValidationError('Project task update input must be a plain object.');
150
+ }
151
+ const keys = Object.keys(input);
152
+ const unsupported = keys.find((key) => !['id', 'content', 'status', 'priority'].includes(key));
153
+ if (unsupported) {
154
+ throw new FlexHarnessValidationError(`Project task update input does not support "${unsupported}".`);
155
+ }
156
+ if (!keys.some((key) => key !== 'id')) {
157
+ throw new FlexHarnessValidationError('Project task update requires a changed field.');
158
+ }
159
+ validateProjectTaskId(input.id);
160
+ if (input.content !== undefined) validateProjectTaskContent(input.content);
161
+ if (input.status !== undefined) validateProjectTaskStatus(input.status);
162
+ if (input.priority !== undefined) validateProjectTaskPriority(input.priority);
163
+ }
164
+
165
+ export function validatePromptOptions(options: IFlexPromptOptions, scheduled: boolean): void {
166
+ if (!options || typeof options !== 'object' || Array.isArray(options)) {
167
+ throw new FlexHarnessValidationError('Prompt options must be a plain object.');
168
+ }
169
+ const supported = scheduled
170
+ ? ['modelHint', 'system', 'maxSteps', 'debounceMs']
171
+ : ['modelHint', 'system', 'maxSteps'];
172
+ const unsupported = Object.keys(options).find((key) => !supported.includes(key));
173
+ if (unsupported) throw new FlexHarnessValidationError(`Prompt options do not support "${unsupported}".`);
174
+ if (options.modelHint !== undefined) validateIdentifier(options.modelHint, 'modelHint');
175
+ if (options.system !== undefined) validateIdentifier(options.system, 'system');
176
+ if (options.maxSteps !== undefined && (!Number.isSafeInteger(options.maxSteps) || options.maxSteps < 1)) {
177
+ throw new FlexHarnessValidationError('maxSteps must be a positive integer.');
178
+ }
179
+ }
180
+
181
+ export function validateSlashCommandExecutionOptions(
182
+ options: IFlexSlashCommandExecutionOptions,
183
+ ): IFlexPromptOptions {
184
+ if (!options || typeof options !== 'object' || Array.isArray(options)) {
185
+ throw new FlexHarnessValidationError('Slash command execution options must be a plain object.');
186
+ }
187
+ const unsupported = Object.keys(options)
188
+ .find((key) => !['modelHint', 'system', 'maxSteps', 'signal'].includes(key));
189
+ if (unsupported) {
190
+ throw new FlexHarnessValidationError(
191
+ `Slash command execution options do not support "${unsupported}".`,
192
+ );
193
+ }
194
+ if (options.signal !== undefined && !(options.signal instanceof AbortSignal)) {
195
+ throw new FlexHarnessValidationError('Slash command signal must be an AbortSignal.');
196
+ }
197
+ const promptOptions: IFlexPromptOptions = {
198
+ ...(options.modelHint === undefined ? {} : { modelHint: options.modelHint }),
199
+ ...(options.system === undefined ? {} : { system: options.system }),
200
+ ...(options.maxSteps === undefined ? {} : { maxSteps: options.maxSteps }),
201
+ };
202
+ validatePromptOptions(promptOptions, false);
203
+ return promptOptions;
204
+ }