@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,246 @@
1
+ import * as plugins from './plugins.js';
2
+ import { FlexHarnessStoreFormatError } from './errors.js';
3
+ import { assertJsonSerializable } from './utils.json.js';
4
+
5
+ export function normalizeJsonObjectUndefined<TValue>(value: TValue, path = '$'): TValue {
6
+ const seen = new WeakSet<object>();
7
+ const visit = (current: unknown, currentPath: string): unknown => {
8
+ if (
9
+ current === null
10
+ || typeof current === 'string'
11
+ || typeof current === 'boolean'
12
+ ) return current;
13
+ if (typeof current === 'number') {
14
+ if (!Number.isFinite(current)) {
15
+ throw new FlexHarnessStoreFormatError(`${currentPath} contains a non-finite number.`);
16
+ }
17
+ return current;
18
+ }
19
+ if (typeof current !== 'object') {
20
+ throw new FlexHarnessStoreFormatError(
21
+ `${currentPath} contains ${typeof current}, which is not JSON-safe.`,
22
+ );
23
+ }
24
+ if (seen.has(current)) {
25
+ throw new FlexHarnessStoreFormatError(`${currentPath} contains a circular reference.`);
26
+ }
27
+ const prototype = Object.getPrototypeOf(current);
28
+ if (!Array.isArray(current) && prototype !== Object.prototype && prototype !== null) {
29
+ throw new FlexHarnessStoreFormatError(`${currentPath} contains a non-plain object.`);
30
+ }
31
+ if (Object.getOwnPropertySymbols(current).length > 0) {
32
+ throw new FlexHarnessStoreFormatError(`${currentPath} contains a symbol-keyed property.`);
33
+ }
34
+ seen.add(current);
35
+ if (Array.isArray(current)) {
36
+ const result = current.map((entry, index) => {
37
+ if (!(index in current) || entry === undefined) {
38
+ throw new FlexHarnessStoreFormatError(`${currentPath}[${index}] is not JSON-safe.`);
39
+ }
40
+ return visit(entry, `${currentPath}[${index}]`);
41
+ });
42
+ seen.delete(current);
43
+ return result;
44
+ }
45
+ const result: Record<string, unknown> = {};
46
+ for (const key of Object.getOwnPropertyNames(current)) {
47
+ const descriptor = Object.getOwnPropertyDescriptor(current, key)!;
48
+ if (!descriptor.enumerable || !('value' in descriptor)) {
49
+ throw new FlexHarnessStoreFormatError(`${currentPath}.${key} is not a plain JSON property.`);
50
+ }
51
+ if (descriptor.value !== undefined) {
52
+ result[key] = visit(descriptor.value, `${currentPath}.${key}`);
53
+ }
54
+ }
55
+ seen.delete(current);
56
+ return result;
57
+ };
58
+ const normalized = visit(value, path);
59
+ assertJsonSerializable(normalized, path);
60
+ return normalized as TValue;
61
+ }
62
+
63
+ function assertNonEmptyString(value: unknown, path: string): asserts value is string {
64
+ if (typeof value !== 'string' || !value.trim()) {
65
+ throw new FlexHarnessStoreFormatError(`${path} must be a non-empty string.`);
66
+ }
67
+ }
68
+
69
+ function assertNonNegativeInteger(value: unknown, path: string): asserts value is number {
70
+ if (!Number.isSafeInteger(value) || Number(value) < 0) {
71
+ throw new FlexHarnessStoreFormatError(`${path} must be a non-negative integer.`);
72
+ }
73
+ }
74
+
75
+ export function assertExactKeys(
76
+ value: Record<string, unknown>,
77
+ keys: readonly string[],
78
+ path: string,
79
+ ): void {
80
+ const invalidKey = Object.keys(value).find((key) => !keys.includes(key));
81
+ if (invalidKey) {
82
+ throw new FlexHarnessStoreFormatError(`${path}.${invalidKey} is not supported.`);
83
+ }
84
+ }
85
+
86
+ export function assertRecord(value: unknown, path: string): asserts value is Record<string, unknown> {
87
+ if (
88
+ !value
89
+ || typeof value !== 'object'
90
+ || Array.isArray(value)
91
+ || (Object.getPrototypeOf(value) !== Object.prototype && Object.getPrototypeOf(value) !== null)
92
+ ) {
93
+ throw new FlexHarnessStoreFormatError(`${path} must be a plain object.`);
94
+ }
95
+ }
96
+
97
+ export function createAgentEventSnapshot(
98
+ sessionId: string,
99
+ events: readonly plugins.TAgentEvent[],
100
+ revision: number,
101
+ ): plugins.IAgentEventSnapshotV2 {
102
+ return {
103
+ schemaVersion: 2,
104
+ sessionId,
105
+ revision,
106
+ updatedAt: events.reduce((latest, event) => Math.max(latest, event.timestamp), 0),
107
+ events: normalizeJsonObjectUndefined([...events], '$events'),
108
+ };
109
+ }
110
+
111
+ export function validateAgentEventSnapshot(
112
+ value: unknown,
113
+ sessionId: string,
114
+ ): plugins.IAgentEventSnapshotV2 {
115
+ assertJsonSerializable(value, '$snapshot');
116
+ try {
117
+ return plugins.validateAgentEventSnapshotV2(value, sessionId);
118
+ } catch (error) {
119
+ throw new FlexHarnessStoreFormatError(
120
+ `$snapshot is not a canonical schema-2 Agent event snapshot: ${error instanceof Error ? error.message : String(error)}`,
121
+ { cause: error },
122
+ );
123
+ }
124
+ }
125
+
126
+ export function validateAgentEventArchive(
127
+ value: unknown,
128
+ sessionId: string,
129
+ archiveId: string,
130
+ ): plugins.IAgentEventArchiveV2 {
131
+ assertJsonSerializable(value, '$archive');
132
+ try {
133
+ return plugins.validateAgentEventArchiveV2(value, sessionId, archiveId);
134
+ } catch (error) {
135
+ throw new FlexHarnessStoreFormatError(
136
+ `$archive is not a canonical schema-2 Agent event archive: ${error instanceof Error ? error.message : String(error)}`,
137
+ { cause: error },
138
+ );
139
+ }
140
+ }
141
+
142
+ export function validateToolJobSnapshot(value: unknown): plugins.IToolJobSnapshot {
143
+ assertJsonSerializable(value, '$snapshot');
144
+ assertRecord(value, '$snapshot');
145
+ assertExactKeys(value, ['schemaVersion', 'revision', 'updatedAt', 'jobs'], '$snapshot');
146
+ if (value.schemaVersion !== 1) {
147
+ throw new FlexHarnessStoreFormatError('Tool job snapshot schemaVersion must be 1.');
148
+ }
149
+ assertNonNegativeInteger(value.revision, '$snapshot.revision');
150
+ assertNonNegativeInteger(value.updatedAt, '$snapshot.updatedAt');
151
+ if (!Array.isArray(value.jobs)) {
152
+ throw new FlexHarnessStoreFormatError('$snapshot.jobs must be an array.');
153
+ }
154
+ const executionIds = new Set<string>();
155
+ for (let index = 0; index < value.jobs.length; index++) {
156
+ const path = `$snapshot.jobs[${index}]`;
157
+ assertRecord(value.jobs[index], path);
158
+ const job = value.jobs[index];
159
+ assertExactKeys(
160
+ job,
161
+ [
162
+ 'executionId',
163
+ 'type',
164
+ 'state',
165
+ 'request',
166
+ 'exitCode',
167
+ 'stdout',
168
+ 'stderr',
169
+ 'signal',
170
+ 'startedAt',
171
+ 'updatedAt',
172
+ 'finishedAt',
173
+ ],
174
+ path,
175
+ );
176
+ assertNonEmptyString(job.executionId, `${path}.executionId`);
177
+ if (executionIds.has(job.executionId)) {
178
+ throw new FlexHarnessStoreFormatError(`$snapshot.jobs contains duplicate job "${job.executionId}".`);
179
+ }
180
+ executionIds.add(job.executionId);
181
+ assertNonEmptyString(job.type, `${path}.type`);
182
+ if (!['running', 'finished', 'failed', 'aborted'].includes(String(job.state))) {
183
+ throw new FlexHarnessStoreFormatError(`${path}.state is invalid.`);
184
+ }
185
+ if (job.request !== undefined) {
186
+ const requestPath = `${path}.request`;
187
+ assertRecord(job.request, requestPath);
188
+ assertExactKeys(
189
+ job.request,
190
+ ['type', 'command', 'cwd', 'timeoutMs', 'metadata'],
191
+ requestPath,
192
+ );
193
+ assertNonEmptyString(job.request.type, `${requestPath}.type`);
194
+ if (job.request.command !== undefined) {
195
+ const commandPath = `${requestPath}.command`;
196
+ assertRecord(job.request.command, commandPath);
197
+ assertExactKeys(job.request.command, ['executable', 'args'], commandPath);
198
+ assertNonEmptyString(job.request.command.executable, `${commandPath}.executable`);
199
+ if (!Array.isArray(job.request.command.args)) {
200
+ throw new FlexHarnessStoreFormatError(`${commandPath}.args must be an array.`);
201
+ }
202
+ for (let argumentIndex = 0; argumentIndex < job.request.command.args.length; argumentIndex++) {
203
+ if (typeof job.request.command.args[argumentIndex] !== 'string') {
204
+ throw new FlexHarnessStoreFormatError(
205
+ `${commandPath}.args[${argumentIndex}] must be a string.`,
206
+ );
207
+ }
208
+ }
209
+ }
210
+ if (job.request.cwd !== undefined && typeof job.request.cwd !== 'string') {
211
+ throw new FlexHarnessStoreFormatError(`${requestPath}.cwd must be a string.`);
212
+ }
213
+ if (job.request.timeoutMs !== undefined) {
214
+ assertNonNegativeInteger(job.request.timeoutMs, `${requestPath}.timeoutMs`);
215
+ }
216
+ if (job.request.metadata !== undefined) {
217
+ assertRecord(job.request.metadata, `${requestPath}.metadata`);
218
+ }
219
+ }
220
+ if (job.exitCode !== undefined && job.exitCode !== null && !Number.isSafeInteger(job.exitCode)) {
221
+ throw new FlexHarnessStoreFormatError(`${path}.exitCode must be an integer or null.`);
222
+ }
223
+ for (const key of ['stdout', 'stderr', 'signal'] as const) {
224
+ if (job[key] !== undefined && typeof job[key] !== 'string') {
225
+ throw new FlexHarnessStoreFormatError(`${path}.${key} must be a string.`);
226
+ }
227
+ }
228
+ for (const key of ['startedAt', 'updatedAt', 'finishedAt'] as const) {
229
+ if (job[key] !== undefined) assertNonNegativeInteger(job[key], `${path}.${key}`);
230
+ }
231
+ }
232
+ return value as unknown as plugins.IToolJobSnapshot;
233
+ }
234
+
235
+ export function createToolJobSnapshot(
236
+ jobs: readonly plugins.IToolJobState[],
237
+ revision: number,
238
+ ): plugins.IToolJobSnapshot {
239
+ const snapshot: plugins.IToolJobSnapshot = {
240
+ schemaVersion: 1,
241
+ revision,
242
+ updatedAt: Date.now(),
243
+ jobs: normalizeJsonObjectUndefined([...jobs], '$jobs'),
244
+ };
245
+ return validateToolJobSnapshot(snapshot);
246
+ }
@@ -0,0 +1,72 @@
1
+ import type { IFlexHarnessOptions, TFlexAgentToolSet } from './interfaces.js';
2
+ import { normalizeJsonValue } from './utils.json.js';
3
+
4
+ interface IExecutableToolRecord extends Record<string, unknown> {
5
+ execute?: (input: unknown, options: unknown) => unknown;
6
+ }
7
+
8
+ function isAsyncIterable(value: unknown): value is AsyncIterable<unknown> {
9
+ return Boolean(
10
+ value
11
+ && (typeof value === 'object' || typeof value === 'function')
12
+ && typeof (value as { [Symbol.asyncIterator]?: unknown })[Symbol.asyncIterator] === 'function',
13
+ );
14
+ }
15
+
16
+ async function* normalizeAsyncIterable(
17
+ iterable: AsyncIterable<unknown>,
18
+ limits: Required<NonNullable<IFlexHarnessOptions<unknown>['toolOutputLimits']>>,
19
+ projectError: (error: unknown) => Error,
20
+ ): AsyncGenerator<unknown> {
21
+ try {
22
+ for await (const value of iterable) yield normalizeJsonValue(value, limits);
23
+ } catch (error) {
24
+ throw projectError(error);
25
+ }
26
+ }
27
+
28
+ export function wrapToolSet(
29
+ tools: TFlexAgentToolSet,
30
+ limits: Required<NonNullable<IFlexHarnessOptions<unknown>['toolOutputLimits']>>,
31
+ projectError: (error: unknown) => Error,
32
+ ): TFlexAgentToolSet {
33
+ const wrapped: Record<string, unknown> = {};
34
+ for (const [name, tool] of Object.entries(tools)) {
35
+ const toolRecord = tool as unknown as IExecutableToolRecord;
36
+ if (typeof toolRecord.execute !== 'function') {
37
+ wrapped[name] = tool;
38
+ continue;
39
+ }
40
+ const execute = toolRecord.execute;
41
+ wrapped[name] = {
42
+ ...toolRecord,
43
+ execute(input: unknown, options: unknown): unknown {
44
+ let output: unknown;
45
+ try {
46
+ output = execute.call(tool, input, options);
47
+ if (isAsyncIterable(output)) {
48
+ return normalizeAsyncIterable(output, limits, projectError);
49
+ }
50
+ } catch (error) {
51
+ throw projectError(error);
52
+ }
53
+ return Promise.resolve(output).then(
54
+ (value) => {
55
+ try {
56
+ if (isAsyncIterable(value)) {
57
+ return normalizeAsyncIterable(value, limits, projectError);
58
+ }
59
+ return normalizeJsonValue(value, limits);
60
+ } catch (error) {
61
+ throw projectError(error);
62
+ }
63
+ },
64
+ (error: unknown) => {
65
+ throw projectError(error);
66
+ },
67
+ );
68
+ },
69
+ };
70
+ }
71
+ return wrapped as TFlexAgentToolSet;
72
+ }
@@ -0,0 +1,210 @@
1
+ import { FlexHarnessValidationError } from './errors.js';
2
+ import type {
3
+ IFlexAttachmentMessagePart,
4
+ IFlexMessage,
5
+ IFlexModelIdentity,
6
+ IFlexResolvedModel,
7
+ IFlexUsage,
8
+ TFlexAgentRunResult,
9
+ TFlexAttachmentSource,
10
+ TFlexPromptPart,
11
+ } from './interfaces.js';
12
+ import { cloneSerializable, deepFreeze } from './utils.json.js';
13
+ import { validateIdentifier } from './utils.validation.js';
14
+
15
+ export interface IRunResultProjection {
16
+ text: string;
17
+ steps: number;
18
+ finishReason: string;
19
+ usage: IFlexUsage;
20
+ }
21
+
22
+ export const maxMessagePageSize = 50;
23
+ export const maxMessagePageCursorBytes = 4096;
24
+ export const maxTransferIdentifierBytes = 512;
25
+ export const maxTransferMetadataBytes = 2048;
26
+ export const maxTransferTextBytes = 96 * 1024;
27
+ const maxTransferMessageBytes = 480 * 1024;
28
+ export const maxTransferPageBytes = 512 * 1024;
29
+
30
+ export function publicSnapshot<TValue>(value: TValue): TValue {
31
+ return deepFreeze(cloneSerializable(value));
32
+ }
33
+
34
+ export function jsonBytes(value: unknown): number {
35
+ return Buffer.byteLength(JSON.stringify(value), 'utf8');
36
+ }
37
+
38
+ export function truncateUtf8(value: string, maxBytes: number): string {
39
+ if (Buffer.byteLength(value, 'utf8') <= maxBytes) return value;
40
+ const suffix = ' [truncated]';
41
+ const suffixBytes = Buffer.byteLength(suffix, 'utf8');
42
+ let truncated = Buffer.from(value, 'utf8')
43
+ .subarray(0, Math.max(0, maxBytes - suffixBytes))
44
+ .toString('utf8')
45
+ .replace(/\uFFFD$/u, '');
46
+ while (Buffer.byteLength(`${truncated}${suffix}`, 'utf8') > maxBytes) {
47
+ truncated = truncated.slice(0, -1);
48
+ }
49
+ return `${truncated}${suffix}`;
50
+ }
51
+
52
+ export function requireTransferIdentifier(value: string, field: string): void {
53
+ if (Buffer.byteLength(value, 'utf8') > maxTransferIdentifierBytes) {
54
+ throw new FlexHarnessValidationError(`${field} exceeds the transfer limit.`);
55
+ }
56
+ }
57
+
58
+ export function normalizeModelIdentity(identity: IFlexModelIdentity): IFlexModelIdentity {
59
+ const normalized: IFlexModelIdentity = {
60
+ provider: identity.provider,
61
+ model: identity.model,
62
+ ...(identity.displayName === undefined ? {} : { displayName: identity.displayName }),
63
+ ...(identity.variant === undefined ? {} : { variant: identity.variant }),
64
+ };
65
+ validateIdentifier(normalized.provider, 'model identity provider');
66
+ validateIdentifier(normalized.model, 'model identity model');
67
+ if (normalized.displayName !== undefined) {
68
+ validateIdentifier(normalized.displayName, 'model identity displayName');
69
+ }
70
+ if (normalized.variant !== undefined) validateIdentifier(normalized.variant, 'model identity variant');
71
+ if (
72
+ Buffer.byteLength(normalized.provider, 'utf8') > maxTransferIdentifierBytes
73
+ || Buffer.byteLength(normalized.model, 'utf8') > maxTransferIdentifierBytes
74
+ || (normalized.displayName !== undefined
75
+ && Buffer.byteLength(normalized.displayName, 'utf8') > maxTransferMetadataBytes)
76
+ || (normalized.variant !== undefined && Buffer.byteLength(normalized.variant, 'utf8') > 128)
77
+ ) {
78
+ throw new FlexHarnessValidationError('Model identity exceeds its transfer limit.');
79
+ }
80
+ return normalized;
81
+ }
82
+
83
+ export function normalizeResolvedModel(resolvedModel: IFlexResolvedModel): IFlexResolvedModel {
84
+ return {
85
+ model: resolvedModel.model,
86
+ identity: normalizeModelIdentity(resolvedModel.identity),
87
+ ...(resolvedModel.system === undefined ? {} : { system: resolvedModel.system }),
88
+ ...(resolvedModel.providerOptions === undefined
89
+ ? {}
90
+ : { providerOptions: resolvedModel.providerOptions }),
91
+ ...(resolvedModel.cache === undefined ? {} : { cache: resolvedModel.cache }),
92
+ ...(resolvedModel.maxSteps === undefined ? {} : { maxSteps: resolvedModel.maxSteps }),
93
+ };
94
+ }
95
+
96
+ export function normalizeRunResult(result: TFlexAgentRunResult): IRunResultProjection {
97
+ const normalized: IRunResultProjection = {
98
+ text: result.text,
99
+ steps: result.steps,
100
+ finishReason: result.finishReason,
101
+ usage: {
102
+ inputTokens: result.usage.inputTokens,
103
+ outputTokens: result.usage.outputTokens,
104
+ totalTokens: result.usage.totalTokens,
105
+ cacheReadTokens: result.usage.cacheReadTokens,
106
+ cacheWriteTokens: result.usage.cacheWriteTokens,
107
+ },
108
+ };
109
+ if (
110
+ typeof normalized.text !== 'string'
111
+ || typeof normalized.finishReason !== 'string'
112
+ || !Number.isSafeInteger(normalized.steps)
113
+ || normalized.steps < 0
114
+ || Object.values(normalized.usage).some((value) => !Number.isFinite(value) || value < 0)
115
+ ) {
116
+ throw new FlexHarnessValidationError('Agent generation result is invalid.');
117
+ }
118
+ return normalized;
119
+ }
120
+
121
+ function base64Size(data: string): number | undefined {
122
+ const compact = data.replace(/\s/g, '');
123
+ if (!/^[A-Za-z0-9+/]*={0,2}$/.test(compact) || compact.length % 4 === 1) return undefined;
124
+ return Buffer.from(compact, 'base64').byteLength;
125
+ }
126
+
127
+ export function describeAttachment(
128
+ part: Exclude<TFlexPromptPart, { type: 'text' }>,
129
+ ): Omit<IFlexAttachmentMessagePart, 'partId' | 'type' | 'attachmentType'> {
130
+ let source: TFlexAttachmentSource = 'inline-base64';
131
+ let sizeBytes: number | undefined;
132
+ let inferredMediaType: string | undefined;
133
+ if (/^data:/i.test(part.data)) {
134
+ source = 'data-url';
135
+ const commaIndex = part.data.indexOf(',');
136
+ if (commaIndex >= 0) {
137
+ const metadata = part.data.slice(5, commaIndex);
138
+ const payload = part.data.slice(commaIndex + 1);
139
+ const metadataParts = metadata.split(';');
140
+ if (metadataParts[0]?.includes('/')) inferredMediaType = metadataParts[0];
141
+ if (metadataParts.some((entry) => entry.toLowerCase() === 'base64')) {
142
+ sizeBytes = base64Size(payload);
143
+ } else {
144
+ try {
145
+ sizeBytes = Buffer.byteLength(decodeURIComponent(payload));
146
+ } catch {
147
+ // Malformed URL encoding leaves size unknown without exposing the payload.
148
+ }
149
+ }
150
+ }
151
+ } else {
152
+ try {
153
+ const url = new URL(part.data);
154
+ if (url.protocol === 'http:' || url.protocol === 'https:') source = 'remote-url';
155
+ else sizeBytes = base64Size(part.data);
156
+ } catch {
157
+ sizeBytes = base64Size(part.data);
158
+ }
159
+ }
160
+ return {
161
+ source,
162
+ ...(sizeBytes === undefined ? {} : { sizeBytes }),
163
+ ...(part.mediaType ?? inferredMediaType
164
+ ? { mediaType: part.mediaType ?? inferredMediaType }
165
+ : {}),
166
+ ...(part.name ? { name: part.name } : {}),
167
+ };
168
+ }
169
+
170
+ export function createBoundedTransferMessage(message: IFlexMessage): IFlexMessage {
171
+ const projected = cloneSerializable(message);
172
+ requireTransferIdentifier(projected.messageId, 'messageId');
173
+ requireTransferIdentifier(projected.sessionId, 'sessionId');
174
+ requireTransferIdentifier(projected.runId, 'runId');
175
+ projected.createdAt = truncateUtf8(projected.createdAt, 128);
176
+ if (projected.completedAt !== undefined) projected.completedAt = truncateUtf8(projected.completedAt, 128);
177
+ if (projected.error !== undefined) projected.error = truncateUtf8(projected.error, maxTransferMetadataBytes);
178
+ if (projected.model) {
179
+ projected.model.provider = truncateUtf8(projected.model.provider, maxTransferIdentifierBytes);
180
+ projected.model.model = truncateUtf8(projected.model.model, maxTransferIdentifierBytes);
181
+ if (projected.model.displayName !== undefined) {
182
+ projected.model.displayName = truncateUtf8(projected.model.displayName, maxTransferMetadataBytes);
183
+ }
184
+ if (projected.model.variant !== undefined) projected.model.variant = truncateUtf8(projected.model.variant, 128);
185
+ }
186
+ for (const part of projected.parts) {
187
+ requireTransferIdentifier(part.partId, 'partId');
188
+ if (part.type === 'text' || part.type === 'reasoning') {
189
+ part.text = truncateUtf8(part.text, maxTransferTextBytes);
190
+ } else if (part.type === 'tool') {
191
+ part.toolCallId = truncateUtf8(part.toolCallId, maxTransferIdentifierBytes);
192
+ part.toolName = truncateUtf8(part.toolName, maxTransferIdentifierBytes);
193
+ if (part.error !== undefined) part.error = truncateUtf8(part.error, maxTransferMetadataBytes);
194
+ } else {
195
+ if (part.mediaType !== undefined) part.mediaType = truncateUtf8(part.mediaType, maxTransferIdentifierBytes);
196
+ if (part.name !== undefined) part.name = truncateUtf8(part.name, maxTransferMetadataBytes);
197
+ }
198
+ }
199
+ if (jsonBytes(projected) > maxTransferMessageBytes) {
200
+ projected.parts = [{
201
+ partId: 'transfer-elided',
202
+ type: 'text',
203
+ text: '[elided: message exceeds the transfer budget]',
204
+ }];
205
+ }
206
+ if (jsonBytes(projected) > maxTransferMessageBytes) {
207
+ throw new FlexHarnessValidationError('Stored message metadata exceeds the transfer limit.');
208
+ }
209
+ return projected;
210
+ }