@modelprofile.com/flexharness 4.0.2 → 4.1.0

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.
@@ -12,6 +12,7 @@ export type TFlexAgentModelMessage = ReturnType<plugins.IAgentSession['getModelM
12
12
  export type TFlexAgentToolSet = NonNullable<TFlexAgentSessionOptions['tools']>;
13
13
  export type TFlexAgentProviderOptions = TFlexAgentSessionOptions['providerOptions'];
14
14
  export type TFlexAgentCacheSetting = TFlexAgentSessionOptions['cache'];
15
+ export type TFlexAgentToolCallUpdateEvent = Parameters<NonNullable<TFlexAgentSessionOptions['onToolCallUpdate']>>[0];
15
16
  export type TFlexAgentToolCallFinishEvent = Parameters<NonNullable<TFlexAgentSessionOptions['onToolCallFinish']>>[0];
16
17
  export interface IFlexTextPromptPart {
17
18
  type: 'text';
@@ -26,4 +26,4 @@ export const FLEX_REVERSION_MAX_AFFECTED_WORKSPACES = 64;
26
26
  export const FLEX_REVERSION_WORKSPACE_ID_MAX_BYTES = 512;
27
27
  export const FLEX_REVERSION_WORKSPACE_LABEL_MAX_BYTES = 2048;
28
28
  export const FLEX_REVERSION_REASON_CODE_MAX_BYTES = 128;
29
- //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiaW50ZXJmYWNlcy5qcyIsInNvdXJjZVJvb3QiOiIiLCJzb3VyY2VzIjpbIi4uL3RzL2ludGVyZmFjZXMudHMiXSwibmFtZXMiOltdLCJtYXBwaW5ncyI6IkFBMGpCQSxNQUFNLENBQUMsTUFBTSxzQ0FBc0MsR0FBRyxDQUFVLENBQUM7QUFDakUsTUFBTSxDQUFDLE1BQU0sb0NBQW9DLEdBQUcsR0FBRyxDQUFDO0FBT3hELE1BQU0sQ0FBQyxNQUFNLDhCQUE4QixHQUFHLE1BQU0sQ0FBQyxNQUFNLENBQUM7SUFDMUQsWUFBWSxFQUFFLENBQUMsR0FBRyxJQUFJO0lBQ3RCLGtCQUFrQixFQUFFLEdBQUcsR0FBRyxJQUFJO0lBQzlCLG1CQUFtQixFQUFFLENBQUMsR0FBRyxJQUFJO0lBQzdCLGNBQWMsRUFBRSxHQUFHO0lBQ25CLFFBQVEsRUFBRSxHQUFHO0lBQ2IsYUFBYSxFQUFFLElBQUk7SUFDbkIsZ0JBQWdCLEVBQUUsSUFBSSxHQUFHLElBQUk7Q0FDOUIsQ0FBQyxDQUFDO0FBMExILE1BQU0sQ0FBQyxNQUFNLDZCQUE2QixHQUFHLE1BQU0sQ0FBQyxNQUFNLENBQUM7SUFDekQsaUJBQWlCLEVBQUUsR0FBRztJQUN0QixXQUFXLEVBQUUsR0FBRztJQUNoQixpQkFBaUIsRUFBRSxJQUFJO0lBQ3ZCLDJCQUEyQixFQUFFLElBQUk7Q0FDbEMsQ0FBQyxDQUFDO0FBRUgsTUFBTSxDQUFDLE1BQU0sNkJBQTZCLEdBQUcsTUFBTSxDQUFDLE1BQU0sQ0FBQztJQUN6RCxpQkFBaUIsRUFBRSxJQUFJO0lBQ3ZCLFdBQVcsRUFBRSxJQUFJO0lBQ2pCLGlCQUFpQixFQUFFLE1BQU07SUFDekIsMkJBQTJCLEVBQUUsTUFBTTtDQUNwQyxDQUFDLENBQUM7QUFFSCxNQUFNLENBQUMsTUFBTSxrQ0FBa0MsR0FBRyxHQUFHLEdBQUcsSUFBSSxDQUFDO0FBQzdELE1BQU0sQ0FBQyxNQUFNLHNDQUFzQyxHQUFHLEVBQUUsQ0FBQztBQUN6RCxNQUFNLENBQUMsTUFBTSxxQ0FBcUMsR0FBRyxHQUFHLENBQUM7QUFDekQsTUFBTSxDQUFDLE1BQU0sd0NBQXdDLEdBQUcsSUFBSSxDQUFDO0FBQzdELE1BQU0sQ0FBQyxNQUFNLG9DQUFvQyxHQUFHLEdBQUcsQ0FBQyJ9
29
+ //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiaW50ZXJmYWNlcy5qcyIsInNvdXJjZVJvb3QiOiIiLCJzb3VyY2VzIjpbIi4uL3RzL2ludGVyZmFjZXMudHMiXSwibmFtZXMiOltdLCJtYXBwaW5ncyI6IkFBNmpCQSxNQUFNLENBQUMsTUFBTSxzQ0FBc0MsR0FBRyxDQUFVLENBQUM7QUFDakUsTUFBTSxDQUFDLE1BQU0sb0NBQW9DLEdBQUcsR0FBRyxDQUFDO0FBT3hELE1BQU0sQ0FBQyxNQUFNLDhCQUE4QixHQUFHLE1BQU0sQ0FBQyxNQUFNLENBQUM7SUFDMUQsWUFBWSxFQUFFLENBQUMsR0FBRyxJQUFJO0lBQ3RCLGtCQUFrQixFQUFFLEdBQUcsR0FBRyxJQUFJO0lBQzlCLG1CQUFtQixFQUFFLENBQUMsR0FBRyxJQUFJO0lBQzdCLGNBQWMsRUFBRSxHQUFHO0lBQ25CLFFBQVEsRUFBRSxHQUFHO0lBQ2IsYUFBYSxFQUFFLElBQUk7SUFDbkIsZ0JBQWdCLEVBQUUsSUFBSSxHQUFHLElBQUk7Q0FDOUIsQ0FBQyxDQUFDO0FBMExILE1BQU0sQ0FBQyxNQUFNLDZCQUE2QixHQUFHLE1BQU0sQ0FBQyxNQUFNLENBQUM7SUFDekQsaUJBQWlCLEVBQUUsR0FBRztJQUN0QixXQUFXLEVBQUUsR0FBRztJQUNoQixpQkFBaUIsRUFBRSxJQUFJO0lBQ3ZCLDJCQUEyQixFQUFFLElBQUk7Q0FDbEMsQ0FBQyxDQUFDO0FBRUgsTUFBTSxDQUFDLE1BQU0sNkJBQTZCLEdBQUcsTUFBTSxDQUFDLE1BQU0sQ0FBQztJQUN6RCxpQkFBaUIsRUFBRSxJQUFJO0lBQ3ZCLFdBQVcsRUFBRSxJQUFJO0lBQ2pCLGlCQUFpQixFQUFFLE1BQU07SUFDekIsMkJBQTJCLEVBQUUsTUFBTTtDQUNwQyxDQUFDLENBQUM7QUFFSCxNQUFNLENBQUMsTUFBTSxrQ0FBa0MsR0FBRyxHQUFHLEdBQUcsSUFBSSxDQUFDO0FBQzdELE1BQU0sQ0FBQyxNQUFNLHNDQUFzQyxHQUFHLEVBQUUsQ0FBQztBQUN6RCxNQUFNLENBQUMsTUFBTSxxQ0FBcUMsR0FBRyxHQUFHLENBQUM7QUFDekQsTUFBTSxDQUFDLE1BQU0sd0NBQXdDLEdBQUcsSUFBSSxDQUFDO0FBQzdELE1BQU0sQ0FBQyxNQUFNLG9DQUFvQyxHQUFHLEdBQUcsQ0FBQyJ9
@@ -5,4 +5,4 @@ import * as path from 'node:path';
5
5
  export { AsyncLocalStorage, crypto, fs, path };
6
6
  import { AgentEventStoreConflictError, AgentSession, buildModelMessages, ToolJobStoreConflictError, createAgentEvent, filterModelVisibleAgentEvents, getAgentGenerationTransactions, modelMessagesToAgentEvents, tool, validateAgentEventArchiveV2, validateAgentEventSnapshotV2, z } from '@push.rocks/smartagent';
7
7
  export { AgentEventStoreConflictError, AgentSession, buildModelMessages, ToolJobStoreConflictError, createAgentEvent, filterModelVisibleAgentEvents, getAgentGenerationTransactions, modelMessagesToAgentEvents, tool, validateAgentEventArchiveV2, validateAgentEventSnapshotV2, z, };
8
- export type { IAgentEventArchiveV2, IAgentContextCompactionOptions, IAgentEventSnapshotV2, IAgentEventStoreV2, IAgentGenerateResult, IAgentGenerationHandle, IAgentGenerationLease, IAgentRunResult, IAgentSession, IAgentSessionOptions, IAgentRuntimeEventPayload, IGenerationBegunEvent, IGenerationExecutionCompletedEvent, IGenerationExecutionStartedEvent, IGenerationOutcomeEvent, IToolExecutionContext, IToolExecutionIntentEvent, IToolJobSnapshot, IToolJobState, IToolJobStore, TAgentContextBuilder, TAgentContextCompactor, TAgentEvent, TAgentGenerationOutcome, TAgentToolCallFinishEvent, TAgentToolExecutionReconciliationOptions, } from '@push.rocks/smartagent';
8
+ export type { IAgentEventArchiveV2, IAgentContextCompactionOptions, IAgentEventSnapshotV2, IAgentEventStoreV2, IAgentGenerateResult, IAgentGenerationHandle, IAgentGenerationLease, IAgentRunResult, IAgentSession, IAgentSessionOptions, IAgentToolCallUpdateEvent, IAgentRuntimeEventPayload, IGenerationBegunEvent, IGenerationExecutionCompletedEvent, IGenerationExecutionStartedEvent, IGenerationOutcomeEvent, IToolExecutionContext, IToolExecutionIntentEvent, IToolJobSnapshot, IToolJobState, IToolJobStore, TAgentContextBuilder, TAgentContextCompactor, TAgentEvent, TAgentGenerationOutcome, TAgentToolCallFinishEvent, TAgentToolExecutionReconciliationOptions, } from '@push.rocks/smartagent';
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@modelprofile.com/flexharness",
3
- "version": "4.0.2",
3
+ "version": "4.1.0",
4
4
  "private": false,
5
5
  "description": "Provider-neutral model-session runtime with durable history, permissions, typed events, and pluggable local or remote tool execution.",
6
6
  "main": "dist_ts/index.js",
@@ -22,7 +22,7 @@
22
22
  "node": ">=24"
23
23
  },
24
24
  "dependencies": {
25
- "@push.rocks/smartagent": "^4.6.0",
25
+ "@push.rocks/smartagent": "^5.0.0",
26
26
  "@types/json-schema": "7.0.15"
27
27
  },
28
28
  "devDependencies": {
package/readme.hints.md CHANGED
@@ -38,6 +38,7 @@ Implementation findings for flexharness.
38
38
  ## Tool output boundary
39
39
 
40
40
  - Every tool `execute` result and every async-iterable yield is converted to bounded JSON before SmartAgent observes it. Thrown errors and iterator failures are not converted or swallowed.
41
+ - SmartAgent preliminary tool outputs mutate only the run-local running tool part and emit cumulative `part.updated` snapshots. Terminal success replaces them with the authoritative final output; failure, cancellation, and callback overflow remove them before persistence.
41
42
  - JSON byte limits are propagated through traversal. Large strings never enter normalized output, and array/object traversal stops once the remaining allowance is reserved for deterministic truncation metadata.
42
43
  - Model callbacks use bounded synchronous run-local parts. Adjacent text/reasoning deltas coalesce; no per-delta snapshot is written. Callback event, byte, or part overflow aborts internally and is classified as failure, not owner cancellation.
43
44
 
package/readme.md CHANGED
@@ -632,7 +632,7 @@ FlexHarness wraps every provided tool `execute` method before SmartAgent receive
632
632
 
633
633
  `toolOutputLimits` in the complete setup above bounds traversal depth and encoded bytes. The normalizer enforces its byte allowance incrementally: oversized strings are replaced before entering output, and arrays/objects stop reading entries once only truncation metadata fits.
634
634
 
635
- Streaming callbacks use run-local synchronous state rather than one persistence promise per delta. Adjacent text and reasoning deltas coalesce. `callbackLimits` bounds callback events, accumulated output bytes, and part count; overflow aborts internally with `FlexHarnessCallbackOverflowError` and the turn is recorded as failed. Reservation and terminal finalization are the normal persistence checkpoints, with permission state changes as explicit additional checkpoints.
635
+ Streaming callbacks use run-local synchronous state rather than one persistence promise per delta. Adjacent text and reasoning deltas coalesce. Preliminary async-iterable tool outputs appear as bounded cumulative `part.updated` snapshots while the tool remains `running`; only the authoritative `part.completed` output enters the terminal projection, and failed or interrupted tools discard their preliminary output. `callbackLimits` bounds callback events, accumulated output bytes, and part count; overflow aborts internally with `FlexHarnessCallbackOverflowError` and the turn is recorded as failed. Reservation and terminal finalization are the normal persistence checkpoints, with permission state changes as explicit additional checkpoints.
636
636
 
637
637
  Model resolver, tool provider, AgentSession, tool execution, tool callback, tool cleanup, and run-persistence failures cross an untrusted error boundary. By default they become a fixed immutable `FlexHarnessExternalError` before completion rejection, persistence, events, or detached-cleanup reporting. Raw external messages and aggregate members are not retained. A failed `onToolCallFinish` callback stores and accounts for only the bounded projected message; it does not otherwise reject completion, although exceeding the configured callback limits still fails the run. Scope resolution and the initial store load happen before a run exists and remain outside this boundary.
638
638
 
@@ -685,7 +685,7 @@ const unsubscribe = harness.subscribe((event) => {
685
685
  unsubscribe();
686
686
  ```
687
687
 
688
- Events are discriminated, sequenced, deeply immutable snapshots. Listener exceptions are isolated from runs and other listeners. Every accepted queue entry emits `prompt.queued` and exactly one `prompt.finished`. Durable promotion additionally emits `prompt.started`, and actual model preparation emits `prompt.running`; cancellation or failure can omit either intermediate event. Existing durable run/message terminal events precede `prompt.finished`. Every callback-backed streamed text part emits exactly one `part.completed` event before the corresponding `run.finished` event. Every `part.started`, `part.delta`, `part.updated`, and `part.completed` event carries zero-based `messageIndex` and `partIndex` coordinates from the session's authoritative message and part sequences. `part.updated` is cumulative metadata replacement rather than a text delta. `session.history.changed` carries `direction: 'undo' | 'redo' | 'branch'`; `runId` is present for undo and redo. Events contain public IDs and snapshots only; they do not expose prompt payloads, the resolved scope object, storage key, model object, provider options, or raw storage key.
688
+ Events are discriminated, sequenced, deeply immutable snapshots. Listener exceptions are isolated from runs and other listeners. Every accepted queue entry emits `prompt.queued` and exactly one `prompt.finished`. Durable promotion additionally emits `prompt.started`, and actual model preparation emits `prompt.running`; cancellation or failure can omit either intermediate event. Existing durable run/message terminal events precede `prompt.finished`. Every callback-backed streamed text part emits exactly one `part.completed` event before the corresponding `run.finished` event. Every `part.started`, `part.delta`, `part.updated`, and `part.completed` event carries zero-based `messageIndex` and `partIndex` coordinates from the session's authoritative message and part sequences. `part.updated` is a cumulative replacement snapshot for running tool output or metadata, not a text delta. `session.history.changed` carries `direction: 'undo' | 'redo' | 'branch'`; `runId` is present for undo and redo. Events contain public IDs and snapshots only; they do not expose prompt payloads, the resolved scope object, storage key, model object, provider options, or raw storage key.
689
689
 
690
690
  ## Stores
691
691
 
@@ -3,6 +3,6 @@
3
3
  */
4
4
  export const commitinfo = {
5
5
  name: '@modelprofile.com/flexharness',
6
- version: '4.0.2',
6
+ version: '4.1.0',
7
7
  description: 'Provider-neutral model-session runtime with durable history, permissions, typed events, and pluggable local or remote tool execution.'
8
8
  }
@@ -96,6 +96,7 @@ import type {
96
96
  IFlexUsage,
97
97
  IJsonObject,
98
98
  TFlexAgentRunResult,
99
+ TFlexAgentToolCallUpdateEvent,
99
100
  TFlexAgentToolCallFinishEvent,
100
101
  TFlexAgentToolSet,
101
102
  TFlexAttachmentSource,
@@ -556,7 +557,7 @@ function requireSessionGeneration(
556
557
  function isAsyncIterable(value: unknown): value is AsyncIterable<unknown> {
557
558
  return Boolean(
558
559
  value
559
- && typeof value === 'object'
560
+ && (typeof value === 'object' || typeof value === 'function')
560
561
  && typeof (value as { [Symbol.asyncIterator]?: unknown })[Symbol.asyncIterator] === 'function',
561
562
  );
562
563
  }
@@ -601,6 +602,9 @@ function wrapToolSet(
601
602
  return Promise.resolve(output).then(
602
603
  (value) => {
603
604
  try {
605
+ if (isAsyncIterable(value)) {
606
+ return normalizeAsyncIterable(value, limits, projectError);
607
+ }
604
608
  return normalizeJsonValue(value, limits);
605
609
  } catch (error) {
606
610
  throw projectError(error);
@@ -5667,6 +5671,7 @@ export class FlexHarness<TScope = unknown> {
5667
5671
  part.status = status === 'completed' ? 'completed' : 'cancelled';
5668
5672
  } else if (part.type === 'tool' && part.status === 'running') {
5669
5673
  part.status = status === 'cancelled' ? 'cancelled' : 'failed';
5674
+ delete part.output;
5670
5675
  part.error = status === 'cancelled'
5671
5676
  ? 'Tool execution was cancelled.'
5672
5677
  : 'Tool execution ended without a terminal event.';
@@ -6928,6 +6933,7 @@ export class FlexHarness<TScope = unknown> {
6928
6933
  part.output = output!;
6929
6934
  } else {
6930
6935
  part.status = 'failed';
6936
+ delete part.output;
6931
6937
  part.error = projectedError!.message;
6932
6938
  }
6933
6939
  this.emitPartEvent(run, 'part.completed', part);
@@ -6936,6 +6942,31 @@ export class FlexHarness<TScope = unknown> {
6936
6942
  }
6937
6943
  }
6938
6944
 
6945
+ private onToolUpdate(
6946
+ state: IStorageState,
6947
+ sessionId: string,
6948
+ event: TFlexAgentToolCallUpdateEvent,
6949
+ ): void {
6950
+ let run = this.callbackRun(state, sessionId);
6951
+ if (!run) return;
6952
+ if (!run.toolPartIds.has(event.toolCallId)) {
6953
+ this.onToolStart(state, sessionId, event);
6954
+ run = this.callbackRun(state, sessionId);
6955
+ if (!run) return;
6956
+ }
6957
+ const partId = run.toolPartIds.get(event.toolCallId);
6958
+ const part = run.callbackParts.find((entry) => entry.partId === partId && entry.type === 'tool');
6959
+ if (!part || part.type !== 'tool' || part.status !== 'running') return;
6960
+ try {
6961
+ const output = normalizeJsonValue(event.output, this.toolOutputLimits);
6962
+ if (!this.reserveCallbackCapacity(run, 1, jsonBytes(output), 0)) return;
6963
+ part.output = output;
6964
+ this.emitPartEvent(run, 'part.updated', part);
6965
+ } catch (error) {
6966
+ this.failCallback(run, this.projectExternalError(run, error, 'toolCallback'));
6967
+ }
6968
+ }
6969
+
6939
6970
  private callbackRun(state: IStorageState, sessionId: string): IActiveRun | undefined {
6940
6971
  const run = state.activeRuns.get(sessionId);
6941
6972
  return !run || run.callbacksClosed || run.callbackError ? undefined : run;
@@ -8100,6 +8131,7 @@ export class FlexHarness<TScope = unknown> {
8100
8131
  onReasoningDelta: (id, delta) => this.onReasoningDelta(state, sessionId, id, delta),
8101
8132
  onReasoningEnd: (id, text) => this.onReasoningEnd(state, sessionId, id, text),
8102
8133
  onToolCallStart: (event) => this.onToolStart(state, sessionId, event),
8134
+ onToolCallUpdate: (event) => this.onToolUpdate(state, sessionId, event),
8103
8135
  onToolCallFinish: (event) => this.onToolFinish(state, sessionId, event),
8104
8136
  };
8105
8137
  stored.agentSession = await this.withCompactorContext(
package/ts/interfaces.ts CHANGED
@@ -16,6 +16,9 @@ export type TFlexAgentModelMessage = ReturnType<plugins.IAgentSession['getModelM
16
16
  export type TFlexAgentToolSet = NonNullable<TFlexAgentSessionOptions['tools']>;
17
17
  export type TFlexAgentProviderOptions = TFlexAgentSessionOptions['providerOptions'];
18
18
  export type TFlexAgentCacheSetting = TFlexAgentSessionOptions['cache'];
19
+ export type TFlexAgentToolCallUpdateEvent = Parameters<
20
+ NonNullable<TFlexAgentSessionOptions['onToolCallUpdate']>
21
+ >[0];
19
22
  export type TFlexAgentToolCallFinishEvent = Parameters<
20
23
  NonNullable<TFlexAgentSessionOptions['onToolCallFinish']>
21
24
  >[0];
package/ts/plugins.ts CHANGED
@@ -47,6 +47,7 @@ export type {
47
47
  IAgentRunResult,
48
48
  IAgentSession,
49
49
  IAgentSessionOptions,
50
+ IAgentToolCallUpdateEvent,
50
51
  IAgentRuntimeEventPayload,
51
52
  IGenerationBegunEvent,
52
53
  IGenerationExecutionCompletedEvent,