@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.
@@ -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
- isValidSlashCommandName,
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 (!reservedSlashCommandNames.has(parsed.name) && !registration) {
1489
+ if (!isReservedSlashCommandName(parsed.name) && !registration) {
2261
1490
  return Object.freeze({
2262
1491
  type: 'unknown',
2263
1492
  input: parsed.input,