@modelprofile.com/flexharness 4.0.2 → 4.1.1

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