@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.
- package/changelog.md +18 -0
- package/dist_ts/00_commitinfo_data.js +1 -1
- package/dist_ts/classes.flexharness.js +8 -667
- package/dist_ts/classes.stores.js +2 -198
- package/dist_ts/utils.harnessoptions.d.ts +21 -0
- package/dist_ts/utils.harnessoptions.js +267 -0
- package/dist_ts/utils.slashcommands.d.ts +1 -0
- package/dist_ts/utils.slashcommands.js +5 -1
- package/dist_ts/utils.storecodecs.d.ts +9 -0
- package/dist_ts/utils.storecodecs.js +201 -0
- package/dist_ts/utils.tooloutput.d.ts +2 -0
- package/dist_ts/utils.tooloutput.js +56 -0
- package/dist_ts/utils.transfer.d.ts +24 -0
- package/dist_ts/utils.transfer.js +195 -0
- package/dist_ts/utils.validation.d.ts +13 -0
- package/dist_ts/utils.validation.js +158 -0
- package/package.json +2 -2
- package/readme.hints.md +8 -1
- package/readme.md +1 -1
- package/ts/00_commitinfo_data.ts +1 -1
- package/ts/classes.flexharness.ts +47 -818
- package/ts/classes.stores.ts +10 -243
- package/ts/utils.harnessoptions.ts +372 -0
- package/ts/utils.slashcommands.ts +5 -0
- package/ts/utils.storecodecs.ts +246 -0
- package/ts/utils.tooloutput.ts +72 -0
- package/ts/utils.transfer.ts +210 -0
- package/ts/utils.validation.ts +204 -0
|
@@ -20,8 +20,6 @@ import {
|
|
|
20
20
|
import {
|
|
21
21
|
FLEX_PROJECT_MANAGEMENT_LIMITS,
|
|
22
22
|
FLEX_REVERSION_MAX_AFFECTED_WORKSPACES,
|
|
23
|
-
FLEX_REVERSION_DEFAULT_LIMITS,
|
|
24
|
-
FLEX_REVERSION_MAXIMUM_LIMITS,
|
|
25
23
|
FLEX_REVERSION_REASON_CODE_MAX_BYTES,
|
|
26
24
|
FLEX_REVERSION_REFERENCE_MAX_BYTES,
|
|
27
25
|
FLEX_REVERSION_WORKSPACE_ID_MAX_BYTES,
|
|
@@ -31,7 +29,6 @@ import type {
|
|
|
31
29
|
IFlexAgentContextInvocation,
|
|
32
30
|
IFlexAgentSessionPolicy,
|
|
33
31
|
IFlexAffectedWorkspace,
|
|
34
|
-
IFlexAttachmentMessagePart,
|
|
35
32
|
IFlexBackgroundExecution,
|
|
36
33
|
IFlexCallbackLimits,
|
|
37
34
|
IFlexCreateProjectTaskInput,
|
|
@@ -83,7 +80,6 @@ import type {
|
|
|
83
80
|
IFlexSlashCommandDescriptor,
|
|
84
81
|
IFlexSlashCommandExecutionOptions,
|
|
85
82
|
IFlexSlashCommandHandlerRegistration,
|
|
86
|
-
IFlexSlashCommandTemplateRegistration,
|
|
87
83
|
IFlexSubagentDefinition,
|
|
88
84
|
IFlexTerminalProjection,
|
|
89
85
|
IFlexToolHandle,
|
|
@@ -93,13 +89,10 @@ import type {
|
|
|
93
89
|
IFlexUndoSessionResult,
|
|
94
90
|
IFlexUpdateSessionOptions,
|
|
95
91
|
IFlexUpdateProjectTaskInput,
|
|
96
|
-
IFlexUsage,
|
|
97
92
|
IJsonObject,
|
|
98
|
-
TFlexAgentRunResult,
|
|
99
93
|
TFlexAgentToolCallUpdateEvent,
|
|
100
94
|
TFlexAgentToolCallFinishEvent,
|
|
101
95
|
TFlexAgentToolSet,
|
|
102
|
-
TFlexAttachmentSource,
|
|
103
96
|
TFlexExternalErrorProjector,
|
|
104
97
|
TFlexExternalErrorSource,
|
|
105
98
|
TFlexHarnessEvent,
|
|
@@ -109,10 +102,8 @@ import type {
|
|
|
109
102
|
TFlexProjectTaskPriority,
|
|
110
103
|
TFlexProjectTaskStatus,
|
|
111
104
|
TFlexPrompt,
|
|
112
|
-
TFlexPromptPart,
|
|
113
105
|
TFlexPromptQueueStatus,
|
|
114
106
|
TFlexSlashCommandExecutionResult,
|
|
115
|
-
TFlexSlashCommandRegistration,
|
|
116
107
|
TFlexPendingReversion,
|
|
117
108
|
TFlexProjectionSnapshot,
|
|
118
109
|
TFlexReversionPolicy,
|
|
@@ -139,14 +130,58 @@ import {
|
|
|
139
130
|
normalizeFlexPrompt,
|
|
140
131
|
type INormalizedFlexPrompt,
|
|
141
132
|
} from './utils.prompt.js';
|
|
133
|
+
import {
|
|
134
|
+
maxSubagentNameBytes,
|
|
135
|
+
normalizeAgentSessionPolicy,
|
|
136
|
+
normalizeBuiltInTools,
|
|
137
|
+
normalizeSlashCommands,
|
|
138
|
+
normalizeSubagents,
|
|
139
|
+
requireHarnessStores,
|
|
140
|
+
resolveBoundedPositiveInteger,
|
|
141
|
+
resolveCallbackLimits,
|
|
142
|
+
resolvePromptQueueLimits,
|
|
143
|
+
resolveReversionLimits,
|
|
144
|
+
type INormalizedBuiltInTools,
|
|
145
|
+
type INormalizedProjectManagementTools,
|
|
146
|
+
type TRegisteredSlashCommand,
|
|
147
|
+
} from './utils.harnessoptions.js';
|
|
142
148
|
import {
|
|
143
149
|
FLEX_SLASH_COMMAND_INITIALIZE_TEMPLATE,
|
|
144
|
-
FLEX_SLASH_COMMAND_MAX_INPUT_BYTES,
|
|
145
150
|
expandSlashCommandTemplate,
|
|
146
|
-
|
|
151
|
+
isReservedSlashCommandName,
|
|
147
152
|
parseSlashCommand,
|
|
148
153
|
slashCommandTemplateHints,
|
|
149
154
|
} from './utils.slashcommands.js';
|
|
155
|
+
import {
|
|
156
|
+
createBoundedTransferMessage,
|
|
157
|
+
describeAttachment,
|
|
158
|
+
jsonBytes,
|
|
159
|
+
maxMessagePageCursorBytes,
|
|
160
|
+
maxMessagePageSize,
|
|
161
|
+
maxTransferIdentifierBytes,
|
|
162
|
+
maxTransferMetadataBytes,
|
|
163
|
+
maxTransferPageBytes,
|
|
164
|
+
maxTransferTextBytes,
|
|
165
|
+
normalizeModelIdentity,
|
|
166
|
+
normalizeResolvedModel,
|
|
167
|
+
normalizeRunResult,
|
|
168
|
+
publicSnapshot,
|
|
169
|
+
requireTransferIdentifier,
|
|
170
|
+
truncateUtf8,
|
|
171
|
+
type IRunResultProjection,
|
|
172
|
+
} from './utils.transfer.js';
|
|
173
|
+
import { wrapToolSet } from './utils.tooloutput.js';
|
|
174
|
+
import {
|
|
175
|
+
validateCreateProjectTaskInput,
|
|
176
|
+
validateIdentifier,
|
|
177
|
+
validateProjectTaskId,
|
|
178
|
+
validateProjectedErrorInfo,
|
|
179
|
+
validatePromptOptions,
|
|
180
|
+
validateSlashCommandExecutionOptions,
|
|
181
|
+
validateUpdateProjectTaskInput,
|
|
182
|
+
validateUpdateSessionOptions,
|
|
183
|
+
validateUtf8String,
|
|
184
|
+
} from './utils.validation.js';
|
|
150
185
|
|
|
151
186
|
type TCanonicalOutcome = 'accepted' | 'rejected' | 'interrupted';
|
|
152
187
|
type TRunPhase =
|
|
@@ -348,10 +383,6 @@ interface IScopeAdmissionState {
|
|
|
348
383
|
retiring: boolean;
|
|
349
384
|
}
|
|
350
385
|
|
|
351
|
-
type TRegisteredSlashCommand<TScope> =
|
|
352
|
-
| Readonly<IFlexSlashCommandTemplateRegistration>
|
|
353
|
-
| Readonly<IFlexSlashCommandHandlerRegistration<TScope>>;
|
|
354
|
-
|
|
355
386
|
interface IActiveSlashCommandExecution {
|
|
356
387
|
kind: 'handler' | 'operation' | 'prompt-admission';
|
|
357
388
|
storageKey: string;
|
|
@@ -380,13 +411,6 @@ interface ISlashCommandActivityOwner {
|
|
|
380
411
|
sessionId: string;
|
|
381
412
|
}
|
|
382
413
|
|
|
383
|
-
interface IRunResultProjection {
|
|
384
|
-
text: string;
|
|
385
|
-
steps: number;
|
|
386
|
-
finishReason: string;
|
|
387
|
-
usage: IFlexUsage;
|
|
388
|
-
}
|
|
389
|
-
|
|
390
414
|
interface IResolverOutcome<TValue> {
|
|
391
415
|
source: 'model' | 'tools' | 'abort';
|
|
392
416
|
success: boolean;
|
|
@@ -401,10 +425,6 @@ interface IFlexMessageCursor {
|
|
|
401
425
|
anchorMessageId: string;
|
|
402
426
|
}
|
|
403
427
|
|
|
404
|
-
interface IExecutableToolRecord extends Record<string, unknown> {
|
|
405
|
-
execute?: (input: unknown, options: unknown) => unknown;
|
|
406
|
-
}
|
|
407
|
-
|
|
408
428
|
interface IFlexSubagentTaskInput {
|
|
409
429
|
description: string;
|
|
410
430
|
prompt: string;
|
|
@@ -417,17 +437,6 @@ interface IFlexSubagentAcquisition {
|
|
|
417
437
|
created: boolean;
|
|
418
438
|
}
|
|
419
439
|
|
|
420
|
-
interface INormalizedProjectManagementTools {
|
|
421
|
-
readonly task: boolean;
|
|
422
|
-
readonly goal: boolean;
|
|
423
|
-
readonly scratchpad: boolean;
|
|
424
|
-
}
|
|
425
|
-
|
|
426
|
-
interface INormalizedBuiltInTools {
|
|
427
|
-
readonly renameSession: boolean;
|
|
428
|
-
readonly projectManagement?: INormalizedProjectManagementTools;
|
|
429
|
-
}
|
|
430
|
-
|
|
431
440
|
type TProjectTaskToolInput =
|
|
432
441
|
| { action: 'list' }
|
|
433
442
|
| {
|
|
@@ -462,35 +471,8 @@ type TEventDetails = Record<string, unknown> & {
|
|
|
462
471
|
type: TFlexHarnessEvent['type'];
|
|
463
472
|
};
|
|
464
473
|
|
|
465
|
-
const DEFAULT_CALLBACK_LIMITS: Required<IFlexCallbackLimits> = {
|
|
466
|
-
maxEvents: 10_000,
|
|
467
|
-
maxOutputBytes: 1024 * 1024,
|
|
468
|
-
maxParts: 2_000,
|
|
469
|
-
};
|
|
470
|
-
const DEFAULT_PROMPT_QUEUE_LIMITS: Required<IFlexPromptQueueLimits> = {
|
|
471
|
-
maxOutstandingPromptsPerSession: 16,
|
|
472
|
-
maxOutstandingBytesPerSession: 64 * 1024 * 1024,
|
|
473
|
-
maxPendingAdmissions: 64,
|
|
474
|
-
maxPendingAdmissionBytes: 128 * 1024 * 1024,
|
|
475
|
-
maxTerminalEntriesPerSession: 64,
|
|
476
|
-
};
|
|
477
|
-
const maxMessagePageSize = 50;
|
|
478
|
-
const maxMessagePageCursorBytes = 4096;
|
|
479
|
-
const maxTransferIdentifierBytes = 512;
|
|
480
|
-
const maxTransferMetadataBytes = 2048;
|
|
481
|
-
const maxTransferTextBytes = 96 * 1024;
|
|
482
|
-
const maxTransferMessageBytes = 480 * 1024;
|
|
483
|
-
const maxTransferPageBytes = 512 * 1024;
|
|
484
|
-
const maxProjectedErrorNameBytes = 128;
|
|
485
|
-
const maxProjectedErrorMessageBytes = 2048;
|
|
486
|
-
const maxProjectedErrorCodeBytes = 128;
|
|
487
474
|
const maxScheduleDebounceMs = 24 * 60 * 60 * 1000;
|
|
488
475
|
const maxBackgroundExecutions = 100;
|
|
489
|
-
const maxSubagentDefinitions = 32;
|
|
490
|
-
const maxSubagentNameBytes = 128;
|
|
491
|
-
const maxSubagentDescriptionBytes = 2048;
|
|
492
|
-
const maxSubagentModelHintBytes = 512;
|
|
493
|
-
const maxSubagentSystemBytes = 64 * 1024;
|
|
494
476
|
const maxSubagentTaskDescriptionBytes = 256;
|
|
495
477
|
const maxSubagentPromptBytes = 64 * 1024;
|
|
496
478
|
const maxSubagentTaskIdBytes = 512;
|
|
@@ -502,15 +484,11 @@ const maximumMaxSubagentCallsPerRun = 128;
|
|
|
502
484
|
const maxResourceToolProviders = 128;
|
|
503
485
|
const maxResourceIdBytes = 512;
|
|
504
486
|
const maxResourceToolNameBytes = 512;
|
|
505
|
-
const maxSlashCommandRegistrations = 128;
|
|
506
|
-
const maxSlashCommandDescriptionBytes = 2048;
|
|
507
|
-
const maxSlashCommandTemplateBytes = FLEX_SLASH_COMMAND_MAX_INPUT_BYTES;
|
|
508
487
|
const maxProjectManagementTombstoneConflicts = 8;
|
|
509
488
|
const resourceToolStemLength = 16;
|
|
510
489
|
const repairCancellationMessage = 'The process stopped before this run completed.';
|
|
511
490
|
const scopeRetirementMessage = 'The scope is being retired.';
|
|
512
491
|
const slashCommandSessionUnavailableReason = 'Session must be idle with no queued prompts.';
|
|
513
|
-
const reservedSlashCommandNames = new Set(['compact', 'undo', 'redo', 'init']);
|
|
514
492
|
const externalErrorFallback: IFlexErrorInfo = Object.freeze({
|
|
515
493
|
name: 'FlexHarnessExternalError',
|
|
516
494
|
message: 'The model operation failed.',
|
|
@@ -554,100 +532,6 @@ function requireSessionGeneration(
|
|
|
554
532
|
};
|
|
555
533
|
}
|
|
556
534
|
|
|
557
|
-
function isAsyncIterable(value: unknown): value is AsyncIterable<unknown> {
|
|
558
|
-
return Boolean(
|
|
559
|
-
value
|
|
560
|
-
&& (typeof value === 'object' || typeof value === 'function')
|
|
561
|
-
&& typeof (value as { [Symbol.asyncIterator]?: unknown })[Symbol.asyncIterator] === 'function',
|
|
562
|
-
);
|
|
563
|
-
}
|
|
564
|
-
|
|
565
|
-
async function* normalizeAsyncIterable(
|
|
566
|
-
iterable: AsyncIterable<unknown>,
|
|
567
|
-
limits: Required<NonNullable<IFlexHarnessOptions<unknown>['toolOutputLimits']>>,
|
|
568
|
-
projectError: (error: unknown) => Error,
|
|
569
|
-
): AsyncGenerator<unknown> {
|
|
570
|
-
try {
|
|
571
|
-
for await (const value of iterable) yield normalizeJsonValue(value, limits);
|
|
572
|
-
} catch (error) {
|
|
573
|
-
throw projectError(error);
|
|
574
|
-
}
|
|
575
|
-
}
|
|
576
|
-
|
|
577
|
-
function wrapToolSet(
|
|
578
|
-
tools: TFlexAgentToolSet,
|
|
579
|
-
limits: Required<NonNullable<IFlexHarnessOptions<unknown>['toolOutputLimits']>>,
|
|
580
|
-
projectError: (error: unknown) => Error,
|
|
581
|
-
): TFlexAgentToolSet {
|
|
582
|
-
const wrapped: Record<string, unknown> = {};
|
|
583
|
-
for (const [name, tool] of Object.entries(tools)) {
|
|
584
|
-
const toolRecord = tool as unknown as IExecutableToolRecord;
|
|
585
|
-
if (typeof toolRecord.execute !== 'function') {
|
|
586
|
-
wrapped[name] = tool;
|
|
587
|
-
continue;
|
|
588
|
-
}
|
|
589
|
-
const execute = toolRecord.execute;
|
|
590
|
-
wrapped[name] = {
|
|
591
|
-
...toolRecord,
|
|
592
|
-
execute(input: unknown, options: unknown): unknown {
|
|
593
|
-
let output: unknown;
|
|
594
|
-
try {
|
|
595
|
-
output = execute.call(tool, input, options);
|
|
596
|
-
if (isAsyncIterable(output)) {
|
|
597
|
-
return normalizeAsyncIterable(output, limits, projectError);
|
|
598
|
-
}
|
|
599
|
-
} catch (error) {
|
|
600
|
-
throw projectError(error);
|
|
601
|
-
}
|
|
602
|
-
return Promise.resolve(output).then(
|
|
603
|
-
(value) => {
|
|
604
|
-
try {
|
|
605
|
-
if (isAsyncIterable(value)) {
|
|
606
|
-
return normalizeAsyncIterable(value, limits, projectError);
|
|
607
|
-
}
|
|
608
|
-
return normalizeJsonValue(value, limits);
|
|
609
|
-
} catch (error) {
|
|
610
|
-
throw projectError(error);
|
|
611
|
-
}
|
|
612
|
-
},
|
|
613
|
-
(error: unknown) => {
|
|
614
|
-
throw projectError(error);
|
|
615
|
-
},
|
|
616
|
-
);
|
|
617
|
-
},
|
|
618
|
-
};
|
|
619
|
-
}
|
|
620
|
-
return wrapped as TFlexAgentToolSet;
|
|
621
|
-
}
|
|
622
|
-
|
|
623
|
-
function publicSnapshot<TValue>(value: TValue): TValue {
|
|
624
|
-
return deepFreeze(cloneSerializable(value));
|
|
625
|
-
}
|
|
626
|
-
|
|
627
|
-
function jsonBytes(value: unknown): number {
|
|
628
|
-
return Buffer.byteLength(JSON.stringify(value), 'utf8');
|
|
629
|
-
}
|
|
630
|
-
|
|
631
|
-
function truncateUtf8(value: string, maxBytes: number): string {
|
|
632
|
-
if (Buffer.byteLength(value, 'utf8') <= maxBytes) return value;
|
|
633
|
-
const suffix = ' [truncated]';
|
|
634
|
-
const suffixBytes = Buffer.byteLength(suffix, 'utf8');
|
|
635
|
-
let truncated = Buffer.from(value, 'utf8')
|
|
636
|
-
.subarray(0, Math.max(0, maxBytes - suffixBytes))
|
|
637
|
-
.toString('utf8')
|
|
638
|
-
.replace(/\uFFFD$/u, '');
|
|
639
|
-
while (Buffer.byteLength(`${truncated}${suffix}`, 'utf8') > maxBytes) {
|
|
640
|
-
truncated = truncated.slice(0, -1);
|
|
641
|
-
}
|
|
642
|
-
return `${truncated}${suffix}`;
|
|
643
|
-
}
|
|
644
|
-
|
|
645
|
-
function validateIdentifier(value: string, name: string): void {
|
|
646
|
-
if (typeof value !== 'string' || value.length === 0) {
|
|
647
|
-
throw new FlexHarnessValidationError(`${name} must be a non-empty string.`);
|
|
648
|
-
}
|
|
649
|
-
}
|
|
650
|
-
|
|
651
535
|
function sha256Hex(value: string): string {
|
|
652
536
|
return plugins.crypto.createHash('sha256').update(value, 'utf8').digest('hex');
|
|
653
537
|
}
|
|
@@ -682,346 +566,6 @@ export function createFlexResourceToolName(namespace: string, toolName: string):
|
|
|
682
566
|
return `${namespace}__${stem}__${sha256Hex(toolName).slice(0, 12)}`;
|
|
683
567
|
}
|
|
684
568
|
|
|
685
|
-
function validateUtf8String(
|
|
686
|
-
value: unknown,
|
|
687
|
-
name: string,
|
|
688
|
-
maxBytes: number,
|
|
689
|
-
nonEmpty = false,
|
|
690
|
-
): asserts value is string {
|
|
691
|
-
if (
|
|
692
|
-
typeof value !== 'string'
|
|
693
|
-
|| (nonEmpty && !value.trim())
|
|
694
|
-
|| Buffer.byteLength(value, 'utf8') > maxBytes
|
|
695
|
-
) {
|
|
696
|
-
throw new FlexHarnessValidationError(
|
|
697
|
-
`${name} must be ${nonEmpty ? 'a non-empty ' : 'a '}string of at most ${maxBytes} UTF-8 bytes.`,
|
|
698
|
-
);
|
|
699
|
-
}
|
|
700
|
-
}
|
|
701
|
-
|
|
702
|
-
function normalizeSubagents(
|
|
703
|
-
definitions: IFlexSubagentDefinition[] | undefined,
|
|
704
|
-
): ReadonlyMap<string, Readonly<IFlexSubagentDefinition>> {
|
|
705
|
-
if (definitions === undefined) return new Map();
|
|
706
|
-
if (!Array.isArray(definitions) || definitions.length > maxSubagentDefinitions) {
|
|
707
|
-
throw new FlexHarnessValidationError(
|
|
708
|
-
`subagents must be an array with at most ${maxSubagentDefinitions} definitions.`,
|
|
709
|
-
);
|
|
710
|
-
}
|
|
711
|
-
const normalized = new Map<string, Readonly<IFlexSubagentDefinition>>();
|
|
712
|
-
for (let index = 0; index < definitions.length; index++) {
|
|
713
|
-
const definition = definitions[index];
|
|
714
|
-
if (
|
|
715
|
-
!definition
|
|
716
|
-
|| typeof definition !== 'object'
|
|
717
|
-
|| Array.isArray(definition)
|
|
718
|
-
|| ![Object.prototype, null].includes(Object.getPrototypeOf(definition))
|
|
719
|
-
) {
|
|
720
|
-
throw new FlexHarnessValidationError(`subagents[${index}] must be a plain object.`);
|
|
721
|
-
}
|
|
722
|
-
const unsupported = Object.keys(definition).find(
|
|
723
|
-
(key) => !['name', 'description', 'modelHint', 'system', 'maxSteps'].includes(key),
|
|
724
|
-
);
|
|
725
|
-
if (unsupported) {
|
|
726
|
-
throw new FlexHarnessValidationError(`subagents[${index}] does not support "${unsupported}".`);
|
|
727
|
-
}
|
|
728
|
-
validateUtf8String(definition.name, `subagents[${index}].name`, maxSubagentNameBytes, true);
|
|
729
|
-
validateUtf8String(
|
|
730
|
-
definition.description,
|
|
731
|
-
`subagents[${index}].description`,
|
|
732
|
-
maxSubagentDescriptionBytes,
|
|
733
|
-
);
|
|
734
|
-
if (definition.modelHint !== undefined) {
|
|
735
|
-
validateUtf8String(
|
|
736
|
-
definition.modelHint,
|
|
737
|
-
`subagents[${index}].modelHint`,
|
|
738
|
-
maxSubagentModelHintBytes,
|
|
739
|
-
);
|
|
740
|
-
}
|
|
741
|
-
if (definition.system !== undefined) {
|
|
742
|
-
validateUtf8String(
|
|
743
|
-
definition.system,
|
|
744
|
-
`subagents[${index}].system`,
|
|
745
|
-
maxSubagentSystemBytes,
|
|
746
|
-
);
|
|
747
|
-
}
|
|
748
|
-
if (
|
|
749
|
-
definition.maxSteps !== undefined
|
|
750
|
-
&& (!Number.isSafeInteger(definition.maxSteps) || definition.maxSteps < 1)
|
|
751
|
-
) {
|
|
752
|
-
throw new FlexHarnessValidationError(`subagents[${index}].maxSteps must be a positive integer.`);
|
|
753
|
-
}
|
|
754
|
-
if (normalized.has(definition.name)) {
|
|
755
|
-
throw new FlexHarnessValidationError(`Duplicate subagent definition "${definition.name}".`);
|
|
756
|
-
}
|
|
757
|
-
normalized.set(definition.name, Object.freeze({
|
|
758
|
-
name: definition.name,
|
|
759
|
-
description: definition.description,
|
|
760
|
-
...(definition.modelHint === undefined ? {} : { modelHint: definition.modelHint }),
|
|
761
|
-
...(definition.system === undefined ? {} : { system: definition.system }),
|
|
762
|
-
...(definition.maxSteps === undefined ? {} : { maxSteps: definition.maxSteps }),
|
|
763
|
-
}));
|
|
764
|
-
}
|
|
765
|
-
return Object.freeze(normalized);
|
|
766
|
-
}
|
|
767
|
-
|
|
768
|
-
function normalizeBuiltInTools(
|
|
769
|
-
options: IFlexHarnessOptions<unknown>['builtInTools'],
|
|
770
|
-
): INormalizedBuiltInTools {
|
|
771
|
-
if (options === undefined) return Object.freeze({ renameSession: false });
|
|
772
|
-
if (
|
|
773
|
-
!options
|
|
774
|
-
|| typeof options !== 'object'
|
|
775
|
-
|| Array.isArray(options)
|
|
776
|
-
|| ![Object.prototype, null].includes(Object.getPrototypeOf(options))
|
|
777
|
-
) {
|
|
778
|
-
throw new FlexHarnessValidationError('builtInTools must be a plain object.');
|
|
779
|
-
}
|
|
780
|
-
const unsupported = Object.keys(options)
|
|
781
|
-
.find((key) => !['renameSession', 'projectManagement'].includes(key));
|
|
782
|
-
if (unsupported) throw new FlexHarnessValidationError(`builtInTools does not support "${unsupported}".`);
|
|
783
|
-
if (options.renameSession !== undefined && typeof options.renameSession !== 'boolean') {
|
|
784
|
-
throw new FlexHarnessValidationError('builtInTools.renameSession must be a boolean.');
|
|
785
|
-
}
|
|
786
|
-
const projectManagement = options.projectManagement;
|
|
787
|
-
if (projectManagement === undefined) {
|
|
788
|
-
return Object.freeze({ renameSession: options.renameSession ?? false });
|
|
789
|
-
}
|
|
790
|
-
if (
|
|
791
|
-
!projectManagement
|
|
792
|
-
|| typeof projectManagement !== 'object'
|
|
793
|
-
|| Array.isArray(projectManagement)
|
|
794
|
-
|| ![Object.prototype, null].includes(Object.getPrototypeOf(projectManagement))
|
|
795
|
-
) {
|
|
796
|
-
throw new FlexHarnessValidationError('builtInTools.projectManagement must be a plain object.');
|
|
797
|
-
}
|
|
798
|
-
const unsupportedProjectOption = Object.keys(projectManagement)
|
|
799
|
-
.find((key) => !['task', 'goal', 'scratchpad'].includes(key));
|
|
800
|
-
if (unsupportedProjectOption) {
|
|
801
|
-
throw new FlexHarnessValidationError(
|
|
802
|
-
`builtInTools.projectManagement does not support "${unsupportedProjectOption}".`,
|
|
803
|
-
);
|
|
804
|
-
}
|
|
805
|
-
for (const key of ['task', 'goal', 'scratchpad'] as const) {
|
|
806
|
-
if (projectManagement[key] !== undefined && typeof projectManagement[key] !== 'boolean') {
|
|
807
|
-
throw new FlexHarnessValidationError(`builtInTools.projectManagement.${key} must be a boolean.`);
|
|
808
|
-
}
|
|
809
|
-
}
|
|
810
|
-
return Object.freeze({
|
|
811
|
-
renameSession: options.renameSession ?? false,
|
|
812
|
-
projectManagement: Object.freeze({
|
|
813
|
-
task: projectManagement.task ?? true,
|
|
814
|
-
goal: projectManagement.goal ?? true,
|
|
815
|
-
scratchpad: projectManagement.scratchpad ?? true,
|
|
816
|
-
}),
|
|
817
|
-
});
|
|
818
|
-
}
|
|
819
|
-
|
|
820
|
-
function normalizeSlashCommands<TScope>(
|
|
821
|
-
registrations: readonly TFlexSlashCommandRegistration<TScope>[] | undefined,
|
|
822
|
-
): ReadonlyMap<string, TRegisteredSlashCommand<TScope>> {
|
|
823
|
-
if (registrations === undefined) return new Map();
|
|
824
|
-
if (!Array.isArray(registrations) || registrations.length > maxSlashCommandRegistrations) {
|
|
825
|
-
throw new FlexHarnessValidationError(
|
|
826
|
-
`slashCommands must be an array with at most ${maxSlashCommandRegistrations} registrations.`,
|
|
827
|
-
);
|
|
828
|
-
}
|
|
829
|
-
const normalized = new Map<string, TRegisteredSlashCommand<TScope>>();
|
|
830
|
-
for (let index = 0; index < registrations.length; index++) {
|
|
831
|
-
const registration = registrations[index];
|
|
832
|
-
if (
|
|
833
|
-
!registration
|
|
834
|
-
|| typeof registration !== 'object'
|
|
835
|
-
|| Array.isArray(registration)
|
|
836
|
-
|| ![Object.prototype, null].includes(Object.getPrototypeOf(registration))
|
|
837
|
-
) {
|
|
838
|
-
throw new FlexHarnessValidationError(`slashCommands[${index}] must be a plain object.`);
|
|
839
|
-
}
|
|
840
|
-
const keys = Object.keys(registration);
|
|
841
|
-
const hasTemplate = Object.prototype.hasOwnProperty.call(registration, 'template');
|
|
842
|
-
const hasHandler = Object.prototype.hasOwnProperty.call(registration, 'handler');
|
|
843
|
-
const supported = hasTemplate
|
|
844
|
-
? ['name', 'description', 'template']
|
|
845
|
-
: ['name', 'description', 'handler'];
|
|
846
|
-
const unsupported = keys.find((key) => !supported.includes(key));
|
|
847
|
-
if (unsupported || hasTemplate === hasHandler) {
|
|
848
|
-
throw new FlexHarnessValidationError(
|
|
849
|
-
`slashCommands[${index}] must define exactly one of template or handler.`,
|
|
850
|
-
);
|
|
851
|
-
}
|
|
852
|
-
if (typeof registration.name !== 'string' || !isValidSlashCommandName(registration.name)) {
|
|
853
|
-
throw new FlexHarnessValidationError(`slashCommands[${index}].name is invalid.`);
|
|
854
|
-
}
|
|
855
|
-
if (reservedSlashCommandNames.has(registration.name)) {
|
|
856
|
-
throw new FlexHarnessValidationError(
|
|
857
|
-
`Slash command "${registration.name}" is reserved.`,
|
|
858
|
-
);
|
|
859
|
-
}
|
|
860
|
-
if (normalized.has(registration.name)) {
|
|
861
|
-
throw new FlexHarnessValidationError(`Duplicate slash command "${registration.name}".`);
|
|
862
|
-
}
|
|
863
|
-
if (registration.description !== undefined) {
|
|
864
|
-
validateUtf8String(
|
|
865
|
-
registration.description,
|
|
866
|
-
`slashCommands[${index}].description`,
|
|
867
|
-
maxSlashCommandDescriptionBytes,
|
|
868
|
-
true,
|
|
869
|
-
);
|
|
870
|
-
}
|
|
871
|
-
if (hasTemplate) {
|
|
872
|
-
const template = registration.template;
|
|
873
|
-
validateUtf8String(
|
|
874
|
-
template,
|
|
875
|
-
`slashCommands[${index}].template`,
|
|
876
|
-
maxSlashCommandTemplateBytes,
|
|
877
|
-
true,
|
|
878
|
-
);
|
|
879
|
-
normalized.set(registration.name, Object.freeze({
|
|
880
|
-
name: registration.name,
|
|
881
|
-
...(registration.description === undefined
|
|
882
|
-
? {}
|
|
883
|
-
: { description: registration.description }),
|
|
884
|
-
template,
|
|
885
|
-
}));
|
|
886
|
-
} else {
|
|
887
|
-
const handler = registration.handler;
|
|
888
|
-
if (typeof handler !== 'function') {
|
|
889
|
-
throw new FlexHarnessValidationError(`slashCommands[${index}].handler must be a function.`);
|
|
890
|
-
}
|
|
891
|
-
normalized.set(registration.name, Object.freeze({
|
|
892
|
-
name: registration.name,
|
|
893
|
-
...(registration.description === undefined
|
|
894
|
-
? {}
|
|
895
|
-
: { description: registration.description }),
|
|
896
|
-
handler,
|
|
897
|
-
}));
|
|
898
|
-
}
|
|
899
|
-
}
|
|
900
|
-
return Object.freeze(normalized);
|
|
901
|
-
}
|
|
902
|
-
|
|
903
|
-
function resolveBoundedPositiveInteger(
|
|
904
|
-
value: number | undefined,
|
|
905
|
-
name: string,
|
|
906
|
-
defaultValue: number,
|
|
907
|
-
maximum: number,
|
|
908
|
-
): number {
|
|
909
|
-
const resolved = value ?? defaultValue;
|
|
910
|
-
if (!Number.isSafeInteger(resolved) || resolved < 1 || resolved > maximum) {
|
|
911
|
-
throw new FlexHarnessValidationError(`${name} must be an integer from 1 through ${maximum}.`);
|
|
912
|
-
}
|
|
913
|
-
return resolved;
|
|
914
|
-
}
|
|
915
|
-
|
|
916
|
-
function requireTransferIdentifier(value: string, field: string): void {
|
|
917
|
-
if (Buffer.byteLength(value, 'utf8') > maxTransferIdentifierBytes) {
|
|
918
|
-
throw new FlexHarnessValidationError(`${field} exceeds the transfer limit.`);
|
|
919
|
-
}
|
|
920
|
-
}
|
|
921
|
-
|
|
922
|
-
function validateProjectedErrorInfo(value: unknown): IFlexErrorInfo {
|
|
923
|
-
if (
|
|
924
|
-
!value
|
|
925
|
-
|| typeof value !== 'object'
|
|
926
|
-
|| Array.isArray(value)
|
|
927
|
-
|| ![Object.prototype, null].includes(Object.getPrototypeOf(value))
|
|
928
|
-
) {
|
|
929
|
-
throw new FlexHarnessValidationError('External error projection is invalid.');
|
|
930
|
-
}
|
|
931
|
-
const descriptors = Object.getOwnPropertyDescriptors(value);
|
|
932
|
-
const unsupportedKey = Reflect.ownKeys(descriptors).find(
|
|
933
|
-
(key) => typeof key !== 'string' || !['name', 'message', 'code'].includes(key),
|
|
934
|
-
);
|
|
935
|
-
const name = descriptors.name?.value;
|
|
936
|
-
const message = descriptors.message?.value;
|
|
937
|
-
const code = descriptors.code?.value;
|
|
938
|
-
if (
|
|
939
|
-
unsupportedKey
|
|
940
|
-
|| descriptors.name?.get !== undefined
|
|
941
|
-
|| descriptors.name?.set !== undefined
|
|
942
|
-
|| descriptors.message?.get !== undefined
|
|
943
|
-
|| descriptors.message?.set !== undefined
|
|
944
|
-
|| descriptors.code?.get !== undefined
|
|
945
|
-
|| descriptors.code?.set !== undefined
|
|
946
|
-
|| typeof name !== 'string'
|
|
947
|
-
|| !name
|
|
948
|
-
|| Buffer.byteLength(name, 'utf8') > maxProjectedErrorNameBytes
|
|
949
|
-
|| typeof message !== 'string'
|
|
950
|
-
|| !message
|
|
951
|
-
|| Buffer.byteLength(message, 'utf8') > maxProjectedErrorMessageBytes
|
|
952
|
-
|| (code !== undefined
|
|
953
|
-
&& (typeof code !== 'string'
|
|
954
|
-
|| !code
|
|
955
|
-
|| Buffer.byteLength(code, 'utf8') > maxProjectedErrorCodeBytes))
|
|
956
|
-
) {
|
|
957
|
-
throw new FlexHarnessValidationError('External error projection is invalid.');
|
|
958
|
-
}
|
|
959
|
-
return Object.freeze({ name, message, ...(code === undefined ? {} : { code }) });
|
|
960
|
-
}
|
|
961
|
-
|
|
962
|
-
function normalizeModelIdentity(identity: IFlexModelIdentity): IFlexModelIdentity {
|
|
963
|
-
const normalized: IFlexModelIdentity = {
|
|
964
|
-
provider: identity.provider,
|
|
965
|
-
model: identity.model,
|
|
966
|
-
...(identity.displayName === undefined ? {} : { displayName: identity.displayName }),
|
|
967
|
-
...(identity.variant === undefined ? {} : { variant: identity.variant }),
|
|
968
|
-
};
|
|
969
|
-
validateIdentifier(normalized.provider, 'model identity provider');
|
|
970
|
-
validateIdentifier(normalized.model, 'model identity model');
|
|
971
|
-
if (normalized.displayName !== undefined) {
|
|
972
|
-
validateIdentifier(normalized.displayName, 'model identity displayName');
|
|
973
|
-
}
|
|
974
|
-
if (normalized.variant !== undefined) validateIdentifier(normalized.variant, 'model identity variant');
|
|
975
|
-
if (
|
|
976
|
-
Buffer.byteLength(normalized.provider, 'utf8') > maxTransferIdentifierBytes
|
|
977
|
-
|| Buffer.byteLength(normalized.model, 'utf8') > maxTransferIdentifierBytes
|
|
978
|
-
|| (normalized.displayName !== undefined
|
|
979
|
-
&& Buffer.byteLength(normalized.displayName, 'utf8') > maxTransferMetadataBytes)
|
|
980
|
-
|| (normalized.variant !== undefined && Buffer.byteLength(normalized.variant, 'utf8') > 128)
|
|
981
|
-
) {
|
|
982
|
-
throw new FlexHarnessValidationError('Model identity exceeds its transfer limit.');
|
|
983
|
-
}
|
|
984
|
-
return normalized;
|
|
985
|
-
}
|
|
986
|
-
|
|
987
|
-
function normalizeResolvedModel(resolvedModel: IFlexResolvedModel): IFlexResolvedModel {
|
|
988
|
-
return {
|
|
989
|
-
model: resolvedModel.model,
|
|
990
|
-
identity: normalizeModelIdentity(resolvedModel.identity),
|
|
991
|
-
...(resolvedModel.system === undefined ? {} : { system: resolvedModel.system }),
|
|
992
|
-
...(resolvedModel.providerOptions === undefined
|
|
993
|
-
? {}
|
|
994
|
-
: { providerOptions: resolvedModel.providerOptions }),
|
|
995
|
-
...(resolvedModel.cache === undefined ? {} : { cache: resolvedModel.cache }),
|
|
996
|
-
...(resolvedModel.maxSteps === undefined ? {} : { maxSteps: resolvedModel.maxSteps }),
|
|
997
|
-
};
|
|
998
|
-
}
|
|
999
|
-
|
|
1000
|
-
function normalizeRunResult(result: TFlexAgentRunResult): IRunResultProjection {
|
|
1001
|
-
const normalized: IRunResultProjection = {
|
|
1002
|
-
text: result.text,
|
|
1003
|
-
steps: result.steps,
|
|
1004
|
-
finishReason: result.finishReason,
|
|
1005
|
-
usage: {
|
|
1006
|
-
inputTokens: result.usage.inputTokens,
|
|
1007
|
-
outputTokens: result.usage.outputTokens,
|
|
1008
|
-
totalTokens: result.usage.totalTokens,
|
|
1009
|
-
cacheReadTokens: result.usage.cacheReadTokens,
|
|
1010
|
-
cacheWriteTokens: result.usage.cacheWriteTokens,
|
|
1011
|
-
},
|
|
1012
|
-
};
|
|
1013
|
-
if (
|
|
1014
|
-
typeof normalized.text !== 'string'
|
|
1015
|
-
|| typeof normalized.finishReason !== 'string'
|
|
1016
|
-
|| !Number.isSafeInteger(normalized.steps)
|
|
1017
|
-
|| normalized.steps < 0
|
|
1018
|
-
|| Object.values(normalized.usage).some((value) => !Number.isFinite(value) || value < 0)
|
|
1019
|
-
) {
|
|
1020
|
-
throw new FlexHarnessValidationError('Agent generation result is invalid.');
|
|
1021
|
-
}
|
|
1022
|
-
return normalized;
|
|
1023
|
-
}
|
|
1024
|
-
|
|
1025
569
|
function combineErrors(errors: unknown[]): unknown {
|
|
1026
570
|
if (errors.length === 1) return errors[0];
|
|
1027
571
|
const combined = new FlexHarnessRunError(errors);
|
|
@@ -1051,321 +595,6 @@ function containsStoreCommitUncertainty(error: unknown, seen = new Set<unknown>(
|
|
|
1051
595
|
}
|
|
1052
596
|
}
|
|
1053
597
|
|
|
1054
|
-
function base64Size(data: string): number | undefined {
|
|
1055
|
-
const compact = data.replace(/\s/g, '');
|
|
1056
|
-
if (!/^[A-Za-z0-9+/]*={0,2}$/.test(compact) || compact.length % 4 === 1) return undefined;
|
|
1057
|
-
return Buffer.from(compact, 'base64').byteLength;
|
|
1058
|
-
}
|
|
1059
|
-
|
|
1060
|
-
function describeAttachment(
|
|
1061
|
-
part: Exclude<TFlexPromptPart, { type: 'text' }>,
|
|
1062
|
-
): Omit<IFlexAttachmentMessagePart, 'partId' | 'type' | 'attachmentType'> {
|
|
1063
|
-
let source: TFlexAttachmentSource = 'inline-base64';
|
|
1064
|
-
let sizeBytes: number | undefined;
|
|
1065
|
-
let inferredMediaType: string | undefined;
|
|
1066
|
-
if (/^data:/i.test(part.data)) {
|
|
1067
|
-
source = 'data-url';
|
|
1068
|
-
const commaIndex = part.data.indexOf(',');
|
|
1069
|
-
if (commaIndex >= 0) {
|
|
1070
|
-
const metadata = part.data.slice(5, commaIndex);
|
|
1071
|
-
const payload = part.data.slice(commaIndex + 1);
|
|
1072
|
-
const metadataParts = metadata.split(';');
|
|
1073
|
-
if (metadataParts[0]?.includes('/')) inferredMediaType = metadataParts[0];
|
|
1074
|
-
if (metadataParts.some((entry) => entry.toLowerCase() === 'base64')) {
|
|
1075
|
-
sizeBytes = base64Size(payload);
|
|
1076
|
-
} else {
|
|
1077
|
-
try {
|
|
1078
|
-
sizeBytes = Buffer.byteLength(decodeURIComponent(payload));
|
|
1079
|
-
} catch {
|
|
1080
|
-
// Malformed URL encoding leaves size unknown without exposing the payload.
|
|
1081
|
-
}
|
|
1082
|
-
}
|
|
1083
|
-
}
|
|
1084
|
-
} else {
|
|
1085
|
-
try {
|
|
1086
|
-
const url = new URL(part.data);
|
|
1087
|
-
if (url.protocol === 'http:' || url.protocol === 'https:') source = 'remote-url';
|
|
1088
|
-
else sizeBytes = base64Size(part.data);
|
|
1089
|
-
} catch {
|
|
1090
|
-
sizeBytes = base64Size(part.data);
|
|
1091
|
-
}
|
|
1092
|
-
}
|
|
1093
|
-
return {
|
|
1094
|
-
source,
|
|
1095
|
-
...(sizeBytes === undefined ? {} : { sizeBytes }),
|
|
1096
|
-
...(part.mediaType ?? inferredMediaType
|
|
1097
|
-
? { mediaType: part.mediaType ?? inferredMediaType }
|
|
1098
|
-
: {}),
|
|
1099
|
-
...(part.name ? { name: part.name } : {}),
|
|
1100
|
-
};
|
|
1101
|
-
}
|
|
1102
|
-
|
|
1103
|
-
function createBoundedTransferMessage(message: IFlexMessage): IFlexMessage {
|
|
1104
|
-
const projected = cloneSerializable(message);
|
|
1105
|
-
requireTransferIdentifier(projected.messageId, 'messageId');
|
|
1106
|
-
requireTransferIdentifier(projected.sessionId, 'sessionId');
|
|
1107
|
-
requireTransferIdentifier(projected.runId, 'runId');
|
|
1108
|
-
projected.createdAt = truncateUtf8(projected.createdAt, 128);
|
|
1109
|
-
if (projected.completedAt !== undefined) projected.completedAt = truncateUtf8(projected.completedAt, 128);
|
|
1110
|
-
if (projected.error !== undefined) projected.error = truncateUtf8(projected.error, maxTransferMetadataBytes);
|
|
1111
|
-
if (projected.model) {
|
|
1112
|
-
projected.model.provider = truncateUtf8(projected.model.provider, maxTransferIdentifierBytes);
|
|
1113
|
-
projected.model.model = truncateUtf8(projected.model.model, maxTransferIdentifierBytes);
|
|
1114
|
-
if (projected.model.displayName !== undefined) {
|
|
1115
|
-
projected.model.displayName = truncateUtf8(projected.model.displayName, maxTransferMetadataBytes);
|
|
1116
|
-
}
|
|
1117
|
-
if (projected.model.variant !== undefined) projected.model.variant = truncateUtf8(projected.model.variant, 128);
|
|
1118
|
-
}
|
|
1119
|
-
for (const part of projected.parts) {
|
|
1120
|
-
requireTransferIdentifier(part.partId, 'partId');
|
|
1121
|
-
if (part.type === 'text' || part.type === 'reasoning') {
|
|
1122
|
-
part.text = truncateUtf8(part.text, maxTransferTextBytes);
|
|
1123
|
-
} else if (part.type === 'tool') {
|
|
1124
|
-
part.toolCallId = truncateUtf8(part.toolCallId, maxTransferIdentifierBytes);
|
|
1125
|
-
part.toolName = truncateUtf8(part.toolName, maxTransferIdentifierBytes);
|
|
1126
|
-
if (part.error !== undefined) part.error = truncateUtf8(part.error, maxTransferMetadataBytes);
|
|
1127
|
-
} else {
|
|
1128
|
-
if (part.mediaType !== undefined) part.mediaType = truncateUtf8(part.mediaType, maxTransferIdentifierBytes);
|
|
1129
|
-
if (part.name !== undefined) part.name = truncateUtf8(part.name, maxTransferMetadataBytes);
|
|
1130
|
-
}
|
|
1131
|
-
}
|
|
1132
|
-
if (jsonBytes(projected) > maxTransferMessageBytes) {
|
|
1133
|
-
projected.parts = [{
|
|
1134
|
-
partId: 'transfer-elided',
|
|
1135
|
-
type: 'text',
|
|
1136
|
-
text: '[elided: message exceeds the transfer budget]',
|
|
1137
|
-
}];
|
|
1138
|
-
}
|
|
1139
|
-
if (jsonBytes(projected) > maxTransferMessageBytes) {
|
|
1140
|
-
throw new FlexHarnessValidationError('Stored message metadata exceeds the transfer limit.');
|
|
1141
|
-
}
|
|
1142
|
-
return projected;
|
|
1143
|
-
}
|
|
1144
|
-
|
|
1145
|
-
function validateUpdateSessionOptions(options: IFlexUpdateSessionOptions): void {
|
|
1146
|
-
if (!options || typeof options !== 'object' || Array.isArray(options)) {
|
|
1147
|
-
throw new FlexHarnessValidationError('updateSession options must be a plain object.');
|
|
1148
|
-
}
|
|
1149
|
-
const keys = Object.keys(options);
|
|
1150
|
-
if (keys.length === 0) throw new FlexHarnessValidationError('updateSession requires title or archived.');
|
|
1151
|
-
const unsupportedKey = keys.find((key) => key !== 'title' && key !== 'archived');
|
|
1152
|
-
if (unsupportedKey) throw new FlexHarnessValidationError(`updateSession does not support "${unsupportedKey}".`);
|
|
1153
|
-
if (Object.prototype.hasOwnProperty.call(options, 'title') && options.title !== null) {
|
|
1154
|
-
validateUtf8String(
|
|
1155
|
-
options.title,
|
|
1156
|
-
'title',
|
|
1157
|
-
FLEX_PROJECT_MANAGEMENT_LIMITS.maxTitleBytes,
|
|
1158
|
-
true,
|
|
1159
|
-
);
|
|
1160
|
-
}
|
|
1161
|
-
if (Object.prototype.hasOwnProperty.call(options, 'archived') && typeof options.archived !== 'boolean') {
|
|
1162
|
-
throw new FlexHarnessValidationError('archived must be a boolean.');
|
|
1163
|
-
}
|
|
1164
|
-
}
|
|
1165
|
-
|
|
1166
|
-
function validateProjectTaskStatus(value: unknown): asserts value is TFlexProjectTaskStatus {
|
|
1167
|
-
if (!['pending', 'in_progress', 'completed', 'cancelled'].includes(String(value))) {
|
|
1168
|
-
throw new FlexHarnessValidationError('Project task status is invalid.');
|
|
1169
|
-
}
|
|
1170
|
-
}
|
|
1171
|
-
|
|
1172
|
-
function validateProjectTaskPriority(value: unknown): asserts value is TFlexProjectTaskPriority {
|
|
1173
|
-
if (!['high', 'medium', 'low'].includes(String(value))) {
|
|
1174
|
-
throw new FlexHarnessValidationError('Project task priority is invalid.');
|
|
1175
|
-
}
|
|
1176
|
-
}
|
|
1177
|
-
|
|
1178
|
-
function validateProjectTaskId(value: unknown): asserts value is string {
|
|
1179
|
-
validateUtf8String(
|
|
1180
|
-
value,
|
|
1181
|
-
'project task id',
|
|
1182
|
-
FLEX_PROJECT_MANAGEMENT_LIMITS.maxTaskIdBytes,
|
|
1183
|
-
true,
|
|
1184
|
-
);
|
|
1185
|
-
}
|
|
1186
|
-
|
|
1187
|
-
function validateProjectTaskContent(value: unknown): asserts value is string {
|
|
1188
|
-
validateUtf8String(
|
|
1189
|
-
value,
|
|
1190
|
-
'project task content',
|
|
1191
|
-
FLEX_PROJECT_MANAGEMENT_LIMITS.maxTaskContentBytes,
|
|
1192
|
-
true,
|
|
1193
|
-
);
|
|
1194
|
-
}
|
|
1195
|
-
|
|
1196
|
-
function validateCreateProjectTaskInput(input: IFlexCreateProjectTaskInput): void {
|
|
1197
|
-
if (!input || typeof input !== 'object' || Array.isArray(input)) {
|
|
1198
|
-
throw new FlexHarnessValidationError('Project task create input must be a plain object.');
|
|
1199
|
-
}
|
|
1200
|
-
const unsupported = Object.keys(input)
|
|
1201
|
-
.find((key) => !['id', 'content', 'status', 'priority'].includes(key));
|
|
1202
|
-
if (unsupported) {
|
|
1203
|
-
throw new FlexHarnessValidationError(`Project task create input does not support "${unsupported}".`);
|
|
1204
|
-
}
|
|
1205
|
-
validateProjectTaskId(input.id);
|
|
1206
|
-
validateProjectTaskContent(input.content);
|
|
1207
|
-
if (input.status !== undefined) validateProjectTaskStatus(input.status);
|
|
1208
|
-
if (input.priority !== undefined) validateProjectTaskPriority(input.priority);
|
|
1209
|
-
}
|
|
1210
|
-
|
|
1211
|
-
function validateUpdateProjectTaskInput(input: IFlexUpdateProjectTaskInput): void {
|
|
1212
|
-
if (!input || typeof input !== 'object' || Array.isArray(input)) {
|
|
1213
|
-
throw new FlexHarnessValidationError('Project task update input must be a plain object.');
|
|
1214
|
-
}
|
|
1215
|
-
const keys = Object.keys(input);
|
|
1216
|
-
const unsupported = keys.find((key) => !['id', 'content', 'status', 'priority'].includes(key));
|
|
1217
|
-
if (unsupported) {
|
|
1218
|
-
throw new FlexHarnessValidationError(`Project task update input does not support "${unsupported}".`);
|
|
1219
|
-
}
|
|
1220
|
-
if (!keys.some((key) => key !== 'id')) {
|
|
1221
|
-
throw new FlexHarnessValidationError('Project task update requires a changed field.');
|
|
1222
|
-
}
|
|
1223
|
-
validateProjectTaskId(input.id);
|
|
1224
|
-
if (input.content !== undefined) validateProjectTaskContent(input.content);
|
|
1225
|
-
if (input.status !== undefined) validateProjectTaskStatus(input.status);
|
|
1226
|
-
if (input.priority !== undefined) validateProjectTaskPriority(input.priority);
|
|
1227
|
-
}
|
|
1228
|
-
|
|
1229
|
-
function validatePromptOptions(options: IFlexPromptOptions, scheduled: boolean): void {
|
|
1230
|
-
if (!options || typeof options !== 'object' || Array.isArray(options)) {
|
|
1231
|
-
throw new FlexHarnessValidationError('Prompt options must be a plain object.');
|
|
1232
|
-
}
|
|
1233
|
-
const supported = scheduled
|
|
1234
|
-
? ['modelHint', 'system', 'maxSteps', 'debounceMs']
|
|
1235
|
-
: ['modelHint', 'system', 'maxSteps'];
|
|
1236
|
-
const unsupported = Object.keys(options).find((key) => !supported.includes(key));
|
|
1237
|
-
if (unsupported) throw new FlexHarnessValidationError(`Prompt options do not support "${unsupported}".`);
|
|
1238
|
-
if (options.modelHint !== undefined) validateIdentifier(options.modelHint, 'modelHint');
|
|
1239
|
-
if (options.system !== undefined) validateIdentifier(options.system, 'system');
|
|
1240
|
-
if (options.maxSteps !== undefined && (!Number.isSafeInteger(options.maxSteps) || options.maxSteps < 1)) {
|
|
1241
|
-
throw new FlexHarnessValidationError('maxSteps must be a positive integer.');
|
|
1242
|
-
}
|
|
1243
|
-
}
|
|
1244
|
-
|
|
1245
|
-
function validateSlashCommandExecutionOptions(
|
|
1246
|
-
options: IFlexSlashCommandExecutionOptions,
|
|
1247
|
-
): IFlexPromptOptions {
|
|
1248
|
-
if (!options || typeof options !== 'object' || Array.isArray(options)) {
|
|
1249
|
-
throw new FlexHarnessValidationError('Slash command execution options must be a plain object.');
|
|
1250
|
-
}
|
|
1251
|
-
const unsupported = Object.keys(options)
|
|
1252
|
-
.find((key) => !['modelHint', 'system', 'maxSteps', 'signal'].includes(key));
|
|
1253
|
-
if (unsupported) {
|
|
1254
|
-
throw new FlexHarnessValidationError(
|
|
1255
|
-
`Slash command execution options do not support "${unsupported}".`,
|
|
1256
|
-
);
|
|
1257
|
-
}
|
|
1258
|
-
if (options.signal !== undefined && !(options.signal instanceof AbortSignal)) {
|
|
1259
|
-
throw new FlexHarnessValidationError('Slash command signal must be an AbortSignal.');
|
|
1260
|
-
}
|
|
1261
|
-
const promptOptions: IFlexPromptOptions = {
|
|
1262
|
-
...(options.modelHint === undefined ? {} : { modelHint: options.modelHint }),
|
|
1263
|
-
...(options.system === undefined ? {} : { system: options.system }),
|
|
1264
|
-
...(options.maxSteps === undefined ? {} : { maxSteps: options.maxSteps }),
|
|
1265
|
-
};
|
|
1266
|
-
validatePromptOptions(promptOptions, false);
|
|
1267
|
-
return promptOptions;
|
|
1268
|
-
}
|
|
1269
|
-
|
|
1270
|
-
function resolveCallbackLimits(limits: IFlexCallbackLimits = {}): Required<IFlexCallbackLimits> {
|
|
1271
|
-
const resolved = {
|
|
1272
|
-
maxEvents: limits.maxEvents ?? DEFAULT_CALLBACK_LIMITS.maxEvents,
|
|
1273
|
-
maxOutputBytes: limits.maxOutputBytes ?? DEFAULT_CALLBACK_LIMITS.maxOutputBytes,
|
|
1274
|
-
maxParts: limits.maxParts ?? DEFAULT_CALLBACK_LIMITS.maxParts,
|
|
1275
|
-
};
|
|
1276
|
-
for (const [name, value] of Object.entries(resolved)) {
|
|
1277
|
-
if (!Number.isSafeInteger(value) || value < 1) {
|
|
1278
|
-
throw new FlexHarnessValidationError(`callbackLimits.${name} must be a positive integer.`);
|
|
1279
|
-
}
|
|
1280
|
-
}
|
|
1281
|
-
return resolved;
|
|
1282
|
-
}
|
|
1283
|
-
|
|
1284
|
-
function resolvePromptQueueLimits(
|
|
1285
|
-
limits: IFlexPromptQueueLimits = {},
|
|
1286
|
-
): Required<IFlexPromptQueueLimits> {
|
|
1287
|
-
const resolved = {
|
|
1288
|
-
maxOutstandingPromptsPerSession: limits.maxOutstandingPromptsPerSession
|
|
1289
|
-
?? DEFAULT_PROMPT_QUEUE_LIMITS.maxOutstandingPromptsPerSession,
|
|
1290
|
-
maxOutstandingBytesPerSession: limits.maxOutstandingBytesPerSession
|
|
1291
|
-
?? DEFAULT_PROMPT_QUEUE_LIMITS.maxOutstandingBytesPerSession,
|
|
1292
|
-
maxPendingAdmissions: limits.maxPendingAdmissions
|
|
1293
|
-
?? DEFAULT_PROMPT_QUEUE_LIMITS.maxPendingAdmissions,
|
|
1294
|
-
maxPendingAdmissionBytes: limits.maxPendingAdmissionBytes
|
|
1295
|
-
?? DEFAULT_PROMPT_QUEUE_LIMITS.maxPendingAdmissionBytes,
|
|
1296
|
-
maxTerminalEntriesPerSession: limits.maxTerminalEntriesPerSession
|
|
1297
|
-
?? DEFAULT_PROMPT_QUEUE_LIMITS.maxTerminalEntriesPerSession,
|
|
1298
|
-
};
|
|
1299
|
-
for (const [name, value] of Object.entries(resolved)) {
|
|
1300
|
-
if (!Number.isSafeInteger(value) || value < 1) {
|
|
1301
|
-
throw new FlexHarnessValidationError(`promptQueueLimits.${name} must be a positive integer.`);
|
|
1302
|
-
}
|
|
1303
|
-
}
|
|
1304
|
-
return resolved;
|
|
1305
|
-
}
|
|
1306
|
-
|
|
1307
|
-
function resolveReversionLimits(
|
|
1308
|
-
limits: IFlexReversionLimits = {},
|
|
1309
|
-
): Required<IFlexReversionLimits> {
|
|
1310
|
-
const resolved = {
|
|
1311
|
-
maxCompletedTurns: limits.maxCompletedTurns ?? FLEX_REVERSION_DEFAULT_LIMITS.maxCompletedTurns,
|
|
1312
|
-
maxSegments: limits.maxSegments ?? FLEX_REVERSION_DEFAULT_LIMITS.maxSegments,
|
|
1313
|
-
maxExcludedRunIds: limits.maxExcludedRunIds ?? FLEX_REVERSION_DEFAULT_LIMITS.maxExcludedRunIds,
|
|
1314
|
-
maxPendingReversionReleases: limits.maxPendingReversionReleases
|
|
1315
|
-
?? FLEX_REVERSION_DEFAULT_LIMITS.maxPendingReversionReleases,
|
|
1316
|
-
};
|
|
1317
|
-
for (const [name, value] of Object.entries(resolved)) {
|
|
1318
|
-
const maximum = FLEX_REVERSION_MAXIMUM_LIMITS[name as keyof IFlexReversionLimits];
|
|
1319
|
-
if (!Number.isSafeInteger(value) || value < 1 || value > maximum) {
|
|
1320
|
-
throw new FlexHarnessValidationError(
|
|
1321
|
-
`reversionLimits.${name} must be an integer from 1 through ${maximum}.`,
|
|
1322
|
-
);
|
|
1323
|
-
}
|
|
1324
|
-
}
|
|
1325
|
-
return resolved;
|
|
1326
|
-
}
|
|
1327
|
-
|
|
1328
|
-
function normalizeAgentSessionPolicy<TScope>(
|
|
1329
|
-
policy: IFlexAgentSessionPolicy<TScope> = {},
|
|
1330
|
-
): IFlexAgentSessionPolicy<TScope> {
|
|
1331
|
-
return {
|
|
1332
|
-
...(policy.contextBuilder === undefined ? {} : { contextBuilder: policy.contextBuilder }),
|
|
1333
|
-
...(policy.contextCompactor === undefined ? {} : { contextCompactor: policy.contextCompactor }),
|
|
1334
|
-
...(policy.eventRetention === undefined ? {} : { eventRetention: policy.eventRetention }),
|
|
1335
|
-
...(policy.changeListenerTimeoutMs === undefined
|
|
1336
|
-
? {}
|
|
1337
|
-
: { changeListenerTimeoutMs: policy.changeListenerTimeoutMs }),
|
|
1338
|
-
...(policy.maxPendingSessionChanges === undefined
|
|
1339
|
-
? {}
|
|
1340
|
-
: { maxPendingSessionChanges: policy.maxPendingSessionChanges }),
|
|
1341
|
-
...(policy.generationLeaseCleanupTimeoutMs === undefined
|
|
1342
|
-
? {}
|
|
1343
|
-
: { generationLeaseCleanupTimeoutMs: policy.generationLeaseCleanupTimeoutMs }),
|
|
1344
|
-
...(policy.maxArchivedTransactionTombstones === undefined
|
|
1345
|
-
? {}
|
|
1346
|
-
: { maxArchivedTransactionTombstones: policy.maxArchivedTransactionTombstones }),
|
|
1347
|
-
...(policy.maxContextOverflowRetries === undefined
|
|
1348
|
-
? {}
|
|
1349
|
-
: { maxContextOverflowRetries: policy.maxContextOverflowRetries }),
|
|
1350
|
-
};
|
|
1351
|
-
}
|
|
1352
|
-
|
|
1353
|
-
function requireHarnessStores(stores: IFlexHarnessStores): IFlexHarnessStores {
|
|
1354
|
-
const projectManagement = stores?.projectManagement;
|
|
1355
|
-
if (
|
|
1356
|
-
!projectManagement
|
|
1357
|
-
|| typeof projectManagement.load !== 'function'
|
|
1358
|
-
|| typeof projectManagement.save !== 'function'
|
|
1359
|
-
|| typeof projectManagement.tombstoneSession !== 'function'
|
|
1360
|
-
|| typeof projectManagement.purgeNamespace !== 'function'
|
|
1361
|
-
) {
|
|
1362
|
-
throw new FlexHarnessValidationError(
|
|
1363
|
-
'stores.projectManagement requires load/save/tombstoneSession/purgeNamespace methods.',
|
|
1364
|
-
);
|
|
1365
|
-
}
|
|
1366
|
-
return stores;
|
|
1367
|
-
}
|
|
1368
|
-
|
|
1369
598
|
export class FlexHarness<TScope = unknown> {
|
|
1370
599
|
private readonly scopeResolver: IFlexHarnessOptions<TScope>['scopeResolver'];
|
|
1371
600
|
private readonly modelResolver: IFlexHarnessOptions<TScope>['modelResolver'];
|
|
@@ -2257,7 +1486,7 @@ export class FlexHarness<TScope = unknown> {
|
|
|
2257
1486
|
const parsed = parseSlashCommand(untouchedInput);
|
|
2258
1487
|
if (parsed.type !== 'parsed') return parsed;
|
|
2259
1488
|
const registration = this.slashCommands.get(parsed.name);
|
|
2260
|
-
if (!
|
|
1489
|
+
if (!isReservedSlashCommandName(parsed.name) && !registration) {
|
|
2261
1490
|
return Object.freeze({
|
|
2262
1491
|
type: 'unknown',
|
|
2263
1492
|
input: parsed.input,
|