@modelprofile.com/flexharness 4.1.2 → 5.0.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.
- package/changelog.md +9 -0
- package/dist_ts/00_commitinfo_data.js +1 -1
- package/dist_ts/classes.flexharness.d.ts +3 -0
- package/dist_ts/classes.flexharness.js +75 -21
- package/dist_ts/interfaces.d.ts +13 -4
- package/package.json +1 -1
- package/readme.hints.md +2 -1
- package/readme.md +19 -3
- package/ts/00_commitinfo_data.ts +1 -1
- package/ts/classes.flexharness.ts +106 -23
- package/ts/interfaces.ts +16 -4
package/dist_ts/interfaces.d.ts
CHANGED
|
@@ -817,8 +817,7 @@ export interface IFlexMessageChangedEvent extends IFlexEventBase {
|
|
|
817
817
|
readonly messageId: string;
|
|
818
818
|
readonly message: IFlexMessage;
|
|
819
819
|
}
|
|
820
|
-
export interface
|
|
821
|
-
readonly type: 'part.started' | 'part.delta' | 'part.updated' | 'part.completed';
|
|
820
|
+
export interface IFlexPartEventBase extends IFlexEventBase {
|
|
822
821
|
readonly runId: string;
|
|
823
822
|
readonly messageId: string;
|
|
824
823
|
/** Zero-based position in the session's authoritative message sequence. */
|
|
@@ -826,9 +825,19 @@ export interface IFlexPartChangedEvent extends IFlexEventBase {
|
|
|
826
825
|
readonly partId: string;
|
|
827
826
|
/** Zero-based position in the message's authoritative part sequence. */
|
|
828
827
|
readonly partIndex: number;
|
|
828
|
+
}
|
|
829
|
+
export interface IFlexPartSnapshotEvent extends IFlexPartEventBase {
|
|
830
|
+
readonly type: 'part.started' | 'part.updated' | 'part.completed';
|
|
829
831
|
readonly part: TFlexMessagePart;
|
|
830
|
-
readonly delta?: string;
|
|
831
832
|
}
|
|
833
|
+
export interface IFlexPartDeltaEvent extends IFlexPartEventBase {
|
|
834
|
+
readonly type: 'part.delta';
|
|
835
|
+
readonly partType: 'text' | 'reasoning';
|
|
836
|
+
readonly delta: string;
|
|
837
|
+
readonly baseTextUtf8Bytes: number;
|
|
838
|
+
readonly textUtf8Bytes: number;
|
|
839
|
+
}
|
|
840
|
+
export type TFlexPartEvent = IFlexPartSnapshotEvent | IFlexPartDeltaEvent;
|
|
832
841
|
export interface IFlexPermissionRequestedEvent extends IFlexEventBase {
|
|
833
842
|
readonly type: 'permission.requested';
|
|
834
843
|
readonly runId: string;
|
|
@@ -864,6 +873,6 @@ export interface IFlexErrorInfo {
|
|
|
864
873
|
message: string;
|
|
865
874
|
code?: string;
|
|
866
875
|
}
|
|
867
|
-
export type TFlexHarnessEvent = IFlexSessionCreatedEvent | IFlexSessionUpdatedEvent | IFlexSessionDeletedEvent | IFlexRunStartedEvent | IFlexMessageChangedEvent |
|
|
876
|
+
export type TFlexHarnessEvent = IFlexSessionCreatedEvent | IFlexSessionUpdatedEvent | IFlexSessionDeletedEvent | IFlexRunStartedEvent | IFlexMessageChangedEvent | TFlexPartEvent | IFlexPermissionRequestedEvent | IFlexPermissionResolvedEvent | IFlexRunFinishedEvent | IFlexPromptQueueEvent | IFlexSessionHistoryChangedEvent;
|
|
868
877
|
export type TFlexHarnessEventListener = (event: TFlexHarnessEvent) => void;
|
|
869
878
|
export {};
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@modelprofile.com/flexharness",
|
|
3
|
-
"version": "
|
|
3
|
+
"version": "5.0.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",
|
package/readme.hints.md
CHANGED
|
@@ -12,6 +12,7 @@ Implementation findings for flexharness.
|
|
|
12
12
|
## SmartAgent boundary
|
|
13
13
|
|
|
14
14
|
- SmartAgent is the canonical private conversation and execution runtime. FlexHarness creates one transactional `AgentSession` per public session and derives its public model, prompt, provider-option, tool-set, and result aliases from SmartAgent exports.
|
|
15
|
+
- Non-empty SmartAgent text and reasoning callback deltas cross the public event boundary immediately and exactly once. Delta events never carry a cumulative part and are not subject to snapshot text truncation.
|
|
15
16
|
- Public prompts remain JSON-safe. URL strings are converted to `URL` instances only at the private SmartAgent invocation boundary.
|
|
16
17
|
- Prompt attachment payloads exist only in canonical private Agent events. Public messages, prompt results, projection snapshots, and events expose only `attachmentType`, source kind, optional media/name, and decoded size when determinable.
|
|
17
18
|
- Resolver calls start in promise continuations so synchronous throws are observed. The first failure aborts the shared internal signal without awaiting an ignoring sibling; detached tool-provider settlement is observed and late handles are closed.
|
|
@@ -47,7 +48,7 @@ Implementation findings for flexharness.
|
|
|
47
48
|
- 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.
|
|
48
49
|
- Every distinct SmartAgent streamed tool output mutates only the run-local running tool part and immediately emits a cumulative `part.updated` snapshot, including the final yielded value. Terminal success replaces it with the authoritative final output; failure, cancellation, and callback overflow remove transient output before persistence.
|
|
49
50
|
- 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.
|
|
50
|
-
- Model callbacks use bounded synchronous run-local parts.
|
|
51
|
+
- Model callbacks use bounded synchronous run-local parts. Source text/reasoning deltas accumulate there without per-delta persistence, while public `part.delta` events retain each exact source delta. Per-part UTF-8 counters are updated incrementally, including correction when a source boundary splits a surrogate pair; cumulative text is never rescanned per token. Callback event, byte, or part overflow aborts internally and is classified as failure, not owner cancellation.
|
|
51
52
|
|
|
52
53
|
## Prompt queue boundary
|
|
53
54
|
|
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.
|
|
635
|
+
Streaming callbacks use run-local synchronous state rather than one persistence promise per source delta. Text and reasoning accumulate only in the run-local terminal projection while each source delta remains an immediate exact public event. Every distinct async-iterable tool output appears immediately as a bounded cumulative `part.updated` snapshot while the tool remains `running`, including the final yielded value before completion. Only the authoritative `part.completed` output enters the terminal projection, and failed or interrupted tools discard their transient 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
|
|
|
@@ -656,7 +656,15 @@ Transactional tool calls persist an execution intent before the tool side effect
|
|
|
656
656
|
const unsubscribe = harness.subscribe((event) => {
|
|
657
657
|
switch (event.type) {
|
|
658
658
|
case 'part.delta':
|
|
659
|
-
|
|
659
|
+
applyExactDelta(
|
|
660
|
+
event.sessionId,
|
|
661
|
+
event.messageIndex,
|
|
662
|
+
event.partIndex,
|
|
663
|
+
event.partType,
|
|
664
|
+
event.delta,
|
|
665
|
+
event.baseTextUtf8Bytes,
|
|
666
|
+
event.textUtf8Bytes,
|
|
667
|
+
);
|
|
660
668
|
break;
|
|
661
669
|
case 'permission.requested':
|
|
662
670
|
showPermission(event.request);
|
|
@@ -685,7 +693,15 @@ const unsubscribe = harness.subscribe((event) => {
|
|
|
685
693
|
unsubscribe();
|
|
686
694
|
```
|
|
687
695
|
|
|
688
|
-
Events are discriminated,
|
|
696
|
+
Events are discriminated, deeply immutable values with one global sequence within each `FlexHarness` instance. 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.
|
|
697
|
+
|
|
698
|
+
`part.started`, `part.updated`, and `part.completed` are snapshot events with a complete immutable `part`. A newly streamed text part emits `part.started` with empty text before its first delta; reasoning parts also start empty. `part.delta` is a separate delta-only event: it has no cumulative `part`, and carries `partType`, the required exact source `delta`, and `baseTextUtf8Bytes`/`textUtf8Bytes` for the cumulative text before and after that delta. The counters remain correct when a UTF-16 surrogate pair is split across callbacks. Exact deltas are never truncated, including a single delta above the 96 KiB message-transfer text limit. `part.updated` remains 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, exact delta payloads, and public snapshots only; they do not expose prompt payloads, the resolved scope object, storage key, model object, provider options, or raw storage key.
|
|
699
|
+
|
|
700
|
+
Part events narrow through the exported `TFlexPartEvent` union. `IFlexPartEventBase` contains their shared coordinates, `IFlexPartSnapshotEvent` owns snapshot events and their complete `part`, and `IFlexPartDeltaEvent` owns exact delta-only events and their UTF-8 counters.
|
|
701
|
+
|
|
702
|
+
### Migrating Part Events to 5.x
|
|
703
|
+
|
|
704
|
+
Version `5.x` replaces cumulative `part.delta` payloads with the exact delta-only contract above. Consumers must stop reading `event.part` from `part.delta`; use `event.partType`, `event.delta`, `event.baseTextUtf8Bytes`, and `event.textUtf8Bytes`, then hydrate or settle from the complete `part` carried by snapshot events. `IFlexPartChangedEvent` has been removed; use `TFlexPartEvent`, `IFlexPartSnapshotEvent`, or `IFlexPartDeltaEvent` according to the required narrowing. New streamed text and reasoning parts start with empty text, so consumers must apply subsequent deltas in sequence within that harness instance.
|
|
689
705
|
|
|
690
706
|
## Stores
|
|
691
707
|
|
package/ts/00_commitinfo_data.ts
CHANGED
|
@@ -3,6 +3,6 @@
|
|
|
3
3
|
*/
|
|
4
4
|
export const commitinfo = {
|
|
5
5
|
name: '@modelprofile.com/flexharness',
|
|
6
|
-
version: '
|
|
6
|
+
version: '5.0.0',
|
|
7
7
|
description: 'Provider-neutral model-session runtime with durable history, permissions, typed events, and pluggable local or remote tool execution.'
|
|
8
8
|
}
|
|
@@ -42,7 +42,7 @@ import type {
|
|
|
42
42
|
IFlexMessagePage,
|
|
43
43
|
IFlexMessagePageOptions,
|
|
44
44
|
IFlexModelIdentity,
|
|
45
|
-
|
|
45
|
+
IFlexPartSnapshotEvent,
|
|
46
46
|
IFlexPermissionRequest,
|
|
47
47
|
IFlexPermissionRequestInput,
|
|
48
48
|
IFlexPermissionSnapshot,
|
|
@@ -321,6 +321,7 @@ interface IActiveRun {
|
|
|
321
321
|
callbackEventCount: number;
|
|
322
322
|
callbackOutputBytes: number;
|
|
323
323
|
callbackParts: TFlexMessagePart[];
|
|
324
|
+
partTextUtf8Bytes: Map<string, number>;
|
|
324
325
|
callbacksClosed: boolean;
|
|
325
326
|
reasoningPartIds: Map<string, string>;
|
|
326
327
|
toolPartIds: Map<string, string>;
|
|
@@ -3110,6 +3111,7 @@ export class FlexHarness<TScope = unknown> {
|
|
|
3110
3111
|
callbackEventCount: 0,
|
|
3111
3112
|
callbackOutputBytes: 0,
|
|
3112
3113
|
callbackParts: [],
|
|
3114
|
+
partTextUtf8Bytes: new Map(),
|
|
3113
3115
|
callbacksClosed: false,
|
|
3114
3116
|
reasoningPartIds: new Map(),
|
|
3115
3117
|
toolPartIds: new Map(),
|
|
@@ -6062,14 +6064,25 @@ export class FlexHarness<TScope = unknown> {
|
|
|
6062
6064
|
if (!run || !delta) return;
|
|
6063
6065
|
const lastPart = run.callbackParts.at(-1);
|
|
6064
6066
|
const created = lastPart?.type !== 'text';
|
|
6065
|
-
if (!this.reserveCallbackCapacity(run, created ? 2 : 1, Buffer.byteLength(delta), created ? 1 : 0)) return;
|
|
6066
6067
|
const part = lastPart?.type === 'text'
|
|
6067
6068
|
? lastPart
|
|
6068
6069
|
: { partId: plugins.crypto.randomUUID(), type: 'text' as const, text: '' };
|
|
6069
|
-
|
|
6070
|
+
const accounting = this.textAppendAccounting(run, part, delta, created);
|
|
6071
|
+
if (!accounting) return;
|
|
6072
|
+
if (!this.reserveCallbackCapacity(
|
|
6073
|
+
run,
|
|
6074
|
+
created ? 2 : 1,
|
|
6075
|
+
accounting.textUtf8Bytes - accounting.baseTextUtf8Bytes,
|
|
6076
|
+
created ? 1 : 0,
|
|
6077
|
+
)) return;
|
|
6078
|
+
if (created) {
|
|
6079
|
+
run.callbackParts.push(part);
|
|
6080
|
+
run.partTextUtf8Bytes.set(part.partId, 0);
|
|
6081
|
+
this.emitPartEvent(run, 'part.started', part);
|
|
6082
|
+
}
|
|
6070
6083
|
part.text += delta;
|
|
6071
|
-
|
|
6072
|
-
this.
|
|
6084
|
+
run.partTextUtf8Bytes.set(part.partId, accounting.textUtf8Bytes);
|
|
6085
|
+
this.emitPartDeltaEvent(run, part, delta, accounting);
|
|
6073
6086
|
}
|
|
6074
6087
|
|
|
6075
6088
|
private onReasoningStart(state: IStorageState, sessionId: string, id: string): void {
|
|
@@ -6084,6 +6097,7 @@ export class FlexHarness<TScope = unknown> {
|
|
|
6084
6097
|
};
|
|
6085
6098
|
run.reasoningPartIds.set(id, part.partId);
|
|
6086
6099
|
run.callbackParts.push(part);
|
|
6100
|
+
run.partTextUtf8Bytes.set(part.partId, 0);
|
|
6087
6101
|
this.emitPartEvent(run, 'part.started', part);
|
|
6088
6102
|
}
|
|
6089
6103
|
|
|
@@ -6094,9 +6108,17 @@ export class FlexHarness<TScope = unknown> {
|
|
|
6094
6108
|
const partId = run.reasoningPartIds.get(id);
|
|
6095
6109
|
const part = run.callbackParts.find((entry) => entry.partId === partId && entry.type === 'reasoning');
|
|
6096
6110
|
if (!part || part.type !== 'reasoning' || part.status !== 'running') return;
|
|
6097
|
-
|
|
6111
|
+
const accounting = this.textAppendAccounting(run, part, delta);
|
|
6112
|
+
if (!accounting) return;
|
|
6113
|
+
if (!this.reserveCallbackCapacity(
|
|
6114
|
+
run,
|
|
6115
|
+
1,
|
|
6116
|
+
accounting.textUtf8Bytes - accounting.baseTextUtf8Bytes,
|
|
6117
|
+
0,
|
|
6118
|
+
)) return;
|
|
6098
6119
|
part.text += delta;
|
|
6099
|
-
|
|
6120
|
+
run.partTextUtf8Bytes.set(part.partId, accounting.textUtf8Bytes);
|
|
6121
|
+
this.emitPartDeltaEvent(run, part, delta, accounting);
|
|
6100
6122
|
}
|
|
6101
6123
|
|
|
6102
6124
|
private onReasoningEnd(state: IStorageState, sessionId: string, id: string, text: string): void {
|
|
@@ -6107,12 +6129,44 @@ export class FlexHarness<TScope = unknown> {
|
|
|
6107
6129
|
const part = run.callbackParts.find((entry) => entry.partId === partId && entry.type === 'reasoning');
|
|
6108
6130
|
if (!part || part.type !== 'reasoning' || part.status !== 'running') return;
|
|
6109
6131
|
const missingText = part.text ? '' : text;
|
|
6110
|
-
|
|
6111
|
-
if (
|
|
6132
|
+
const missingTextUtf8Bytes = Buffer.byteLength(missingText, 'utf8');
|
|
6133
|
+
if (!this.reserveCallbackCapacity(run, 1, missingTextUtf8Bytes, 0)) return;
|
|
6134
|
+
if (missingText) {
|
|
6135
|
+
part.text = missingText;
|
|
6136
|
+
run.partTextUtf8Bytes.set(part.partId, missingTextUtf8Bytes);
|
|
6137
|
+
}
|
|
6112
6138
|
part.status = 'completed';
|
|
6113
6139
|
this.emitPartEvent(run, 'part.completed', part);
|
|
6114
6140
|
}
|
|
6115
6141
|
|
|
6142
|
+
private textAppendAccounting(
|
|
6143
|
+
run: IActiveRun,
|
|
6144
|
+
part: Extract<TFlexMessagePart, { type: 'text' | 'reasoning' }>,
|
|
6145
|
+
delta: string,
|
|
6146
|
+
created = false,
|
|
6147
|
+
): { baseTextUtf8Bytes: number; textUtf8Bytes: number } | undefined {
|
|
6148
|
+
const baseTextUtf8Bytes = created ? 0 : run.partTextUtf8Bytes.get(part.partId);
|
|
6149
|
+
if (baseTextUtf8Bytes === undefined) {
|
|
6150
|
+
throw new Error('Flex part text byte accounting is unavailable.');
|
|
6151
|
+
}
|
|
6152
|
+
const trailingCodeUnit = part.text.charCodeAt(part.text.length - 1);
|
|
6153
|
+
const leadingCodeUnit = delta.charCodeAt(0);
|
|
6154
|
+
const joinsSurrogatePair = trailingCodeUnit >= 0xD800
|
|
6155
|
+
&& trailingCodeUnit <= 0xDBFF
|
|
6156
|
+
&& leadingCodeUnit >= 0xDC00
|
|
6157
|
+
&& leadingCodeUnit <= 0xDFFF;
|
|
6158
|
+
const textUtf8Bytes = baseTextUtf8Bytes
|
|
6159
|
+
+ Buffer.byteLength(delta, 'utf8')
|
|
6160
|
+
- (joinsSurrogatePair ? 2 : 0);
|
|
6161
|
+
if (!Number.isSafeInteger(textUtf8Bytes)) {
|
|
6162
|
+
this.failCallback(run, this.trustInternalError(new FlexHarnessCallbackOverflowError(
|
|
6163
|
+
'Callback text byte accounting exceeded the safe integer range.',
|
|
6164
|
+
)));
|
|
6165
|
+
return undefined;
|
|
6166
|
+
}
|
|
6167
|
+
return { baseTextUtf8Bytes, textUtf8Bytes };
|
|
6168
|
+
}
|
|
6169
|
+
|
|
6116
6170
|
private onToolStart(
|
|
6117
6171
|
state: IStorageState,
|
|
6118
6172
|
sessionId: string,
|
|
@@ -6211,7 +6265,10 @@ export class FlexHarness<TScope = unknown> {
|
|
|
6211
6265
|
const nextOutputBytes = run.callbackOutputBytes + outputBytes;
|
|
6212
6266
|
const nextParts = run.callbackParts.length + partCount;
|
|
6213
6267
|
if (
|
|
6214
|
-
nextEvents
|
|
6268
|
+
!Number.isSafeInteger(nextEvents)
|
|
6269
|
+
|| !Number.isSafeInteger(nextOutputBytes)
|
|
6270
|
+
|| !Number.isSafeInteger(nextParts)
|
|
6271
|
+
|| nextEvents > this.callbackLimits.maxEvents
|
|
6215
6272
|
|| nextOutputBytes > this.callbackLimits.maxOutputBytes
|
|
6216
6273
|
|| nextParts > this.callbackLimits.maxParts
|
|
6217
6274
|
) {
|
|
@@ -9329,20 +9386,10 @@ export class FlexHarness<TScope = unknown> {
|
|
|
9329
9386
|
|
|
9330
9387
|
private emitPartEvent(
|
|
9331
9388
|
run: IActiveRun,
|
|
9332
|
-
type:
|
|
9389
|
+
type: IFlexPartSnapshotEvent['type'],
|
|
9333
9390
|
part: TFlexMessagePart,
|
|
9334
|
-
delta?: string,
|
|
9335
9391
|
): void {
|
|
9336
|
-
const messageIndex = run.
|
|
9337
|
-
(message) => message.messageId === run.assistantMessageId,
|
|
9338
|
-
);
|
|
9339
|
-
const partIndex = run.callbackParts.findIndex((entry) => entry.partId === part.partId);
|
|
9340
|
-
if (
|
|
9341
|
-
messageIndex < 0
|
|
9342
|
-
|| run.stored.messages[messageIndex]?.messageId !== run.assistantMessageId
|
|
9343
|
-
|| partIndex < 0
|
|
9344
|
-
|| run.callbackParts[partIndex]?.partId !== part.partId
|
|
9345
|
-
) throw new Error('Flex part event source coordinates are unavailable.');
|
|
9392
|
+
const { messageIndex, partIndex } = this.partEventCoordinates(run, part.partId);
|
|
9346
9393
|
this.emitEvent(run.scopeId, run.sessionId, {
|
|
9347
9394
|
type,
|
|
9348
9395
|
runId: run.runId,
|
|
@@ -9351,10 +9398,46 @@ export class FlexHarness<TScope = unknown> {
|
|
|
9351
9398
|
partId: part.partId,
|
|
9352
9399
|
partIndex,
|
|
9353
9400
|
part: publicSnapshot(part),
|
|
9354
|
-
...(delta === undefined ? {} : { delta: truncateUtf8(delta, maxTransferTextBytes) }),
|
|
9355
9401
|
});
|
|
9356
9402
|
}
|
|
9357
9403
|
|
|
9404
|
+
private emitPartDeltaEvent(
|
|
9405
|
+
run: IActiveRun,
|
|
9406
|
+
part: Extract<TFlexMessagePart, { type: 'text' | 'reasoning' }>,
|
|
9407
|
+
delta: string,
|
|
9408
|
+
accounting: { baseTextUtf8Bytes: number; textUtf8Bytes: number },
|
|
9409
|
+
): void {
|
|
9410
|
+
const { messageIndex, partIndex } = this.partEventCoordinates(run, part.partId);
|
|
9411
|
+
this.emitEvent(run.scopeId, run.sessionId, {
|
|
9412
|
+
type: 'part.delta',
|
|
9413
|
+
runId: run.runId,
|
|
9414
|
+
messageId: run.assistantMessageId,
|
|
9415
|
+
messageIndex,
|
|
9416
|
+
partId: part.partId,
|
|
9417
|
+
partIndex,
|
|
9418
|
+
partType: part.type,
|
|
9419
|
+
delta,
|
|
9420
|
+
...accounting,
|
|
9421
|
+
});
|
|
9422
|
+
}
|
|
9423
|
+
|
|
9424
|
+
private partEventCoordinates(
|
|
9425
|
+
run: IActiveRun,
|
|
9426
|
+
partId: string,
|
|
9427
|
+
): { messageIndex: number; partIndex: number } {
|
|
9428
|
+
const messageIndex = run.stored.messages.findIndex(
|
|
9429
|
+
(message) => message.messageId === run.assistantMessageId,
|
|
9430
|
+
);
|
|
9431
|
+
const partIndex = run.callbackParts.findIndex((entry) => entry.partId === partId);
|
|
9432
|
+
if (
|
|
9433
|
+
messageIndex < 0
|
|
9434
|
+
|| run.stored.messages[messageIndex]?.messageId !== run.assistantMessageId
|
|
9435
|
+
|| partIndex < 0
|
|
9436
|
+
|| run.callbackParts[partIndex]?.partId !== partId
|
|
9437
|
+
) throw new Error('Flex part event source coordinates are unavailable.');
|
|
9438
|
+
return { messageIndex, partIndex };
|
|
9439
|
+
}
|
|
9440
|
+
|
|
9358
9441
|
private emitEvent(scopeId: string, sessionId: string, details: TEventDetails): void {
|
|
9359
9442
|
const event = deepFreeze(cloneSerializable({
|
|
9360
9443
|
eventId: plugins.crypto.randomUUID(),
|
package/ts/interfaces.ts
CHANGED
|
@@ -1107,8 +1107,7 @@ export interface IFlexMessageChangedEvent extends IFlexEventBase {
|
|
|
1107
1107
|
readonly message: IFlexMessage;
|
|
1108
1108
|
}
|
|
1109
1109
|
|
|
1110
|
-
export interface
|
|
1111
|
-
readonly type: 'part.started' | 'part.delta' | 'part.updated' | 'part.completed';
|
|
1110
|
+
export interface IFlexPartEventBase extends IFlexEventBase {
|
|
1112
1111
|
readonly runId: string;
|
|
1113
1112
|
readonly messageId: string;
|
|
1114
1113
|
/** Zero-based position in the session's authoritative message sequence. */
|
|
@@ -1116,10 +1115,23 @@ export interface IFlexPartChangedEvent extends IFlexEventBase {
|
|
|
1116
1115
|
readonly partId: string;
|
|
1117
1116
|
/** Zero-based position in the message's authoritative part sequence. */
|
|
1118
1117
|
readonly partIndex: number;
|
|
1118
|
+
}
|
|
1119
|
+
|
|
1120
|
+
export interface IFlexPartSnapshotEvent extends IFlexPartEventBase {
|
|
1121
|
+
readonly type: 'part.started' | 'part.updated' | 'part.completed';
|
|
1119
1122
|
readonly part: TFlexMessagePart;
|
|
1120
|
-
readonly delta?: string;
|
|
1121
1123
|
}
|
|
1122
1124
|
|
|
1125
|
+
export interface IFlexPartDeltaEvent extends IFlexPartEventBase {
|
|
1126
|
+
readonly type: 'part.delta';
|
|
1127
|
+
readonly partType: 'text' | 'reasoning';
|
|
1128
|
+
readonly delta: string;
|
|
1129
|
+
readonly baseTextUtf8Bytes: number;
|
|
1130
|
+
readonly textUtf8Bytes: number;
|
|
1131
|
+
}
|
|
1132
|
+
|
|
1133
|
+
export type TFlexPartEvent = IFlexPartSnapshotEvent | IFlexPartDeltaEvent;
|
|
1134
|
+
|
|
1123
1135
|
export interface IFlexPermissionRequestedEvent extends IFlexEventBase {
|
|
1124
1136
|
readonly type: 'permission.requested';
|
|
1125
1137
|
readonly runId: string;
|
|
@@ -1167,7 +1179,7 @@ export type TFlexHarnessEvent =
|
|
|
1167
1179
|
| IFlexSessionDeletedEvent
|
|
1168
1180
|
| IFlexRunStartedEvent
|
|
1169
1181
|
| IFlexMessageChangedEvent
|
|
1170
|
-
|
|
|
1182
|
+
| TFlexPartEvent
|
|
1171
1183
|
| IFlexPermissionRequestedEvent
|
|
1172
1184
|
| IFlexPermissionResolvedEvent
|
|
1173
1185
|
| IFlexRunFinishedEvent
|