@modelprofile.com/flexharness 14.0.0 → 15.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/dist_ts/00_commitinfo_data.js +1 -1
- package/dist_ts/classes.flexharness.core.d.ts +8 -1
- package/dist_ts/classes.flexharness.core.js +31 -12
- package/dist_ts/classes.flexharness.d.ts +4 -4
- package/dist_ts/classes.flexharness.js +7 -7
- package/dist_ts/classes.flexharness.prompts.js +11 -3
- package/dist_ts/classes.flexharness.runs.d.ts +6 -1
- package/dist_ts/classes.flexharness.runs.js +45 -7
- package/dist_ts/classes.flexharness.sessions.d.ts +4 -5
- package/dist_ts/classes.flexharness.sessions.js +45 -12
- package/dist_ts/errors.d.ts +9 -0
- package/dist_ts/errors.js +14 -1
- package/dist_ts/index.d.ts +2 -0
- package/dist_ts/index.js +2 -1
- package/dist_ts/interfaces.d.ts +96 -2
- package/dist_ts/interfaces.flexharness.d.ts +2 -0
- package/dist_ts/interfaces.js +1 -1
- package/dist_ts/plugins.d.ts +3 -3
- package/dist_ts/plugins.js +3 -3
- package/dist_ts/utils.harnessoptions.js +6 -3
- package/dist_ts/utils.json.js +43 -1
- package/dist_ts/utils.sessionmetrics.d.ts +12 -1
- package/dist_ts/utils.sessionmetrics.js +18 -1
- package/dist_ts/utils.transfer.js +14 -1
- package/dist_ts/utils.validation.d.ts +7 -2
- package/dist_ts/utils.validation.js +28 -7
- package/package.json +3 -3
- package/readme.md +24 -4
- package/ts/00_commitinfo_data.ts +1 -1
- package/ts/classes.flexharness.core.ts +36 -9
- package/ts/classes.flexharness.prompts.ts +11 -1
- package/ts/classes.flexharness.runs.ts +48 -4
- package/ts/classes.flexharness.sessions.ts +54 -9
- package/ts/classes.flexharness.ts +15 -5
- package/ts/errors.ts +15 -0
- package/ts/index.ts +6 -0
- package/ts/interfaces.flexharness.ts +2 -0
- package/ts/interfaces.ts +99 -2
- package/ts/plugins.ts +12 -0
- package/ts/readme.md +24 -4
- package/ts/utils.harnessoptions.ts +9 -2
- package/ts/utils.json.ts +41 -0
- package/ts/utils.sessionmetrics.ts +23 -0
- package/ts/utils.transfer.ts +13 -0
- package/ts/utils.validation.ts +31 -5
|
@@ -186,7 +186,7 @@ export class FlexHarnessRuns<TScope> {
|
|
|
186
186
|
if (!run.modelResolution) throw new FlexHarnessValidationError('Generation completed without a resolved model.');
|
|
187
187
|
const closeReversion = await this.core.reversions.prepareRunReversionClose(run, 'completed');
|
|
188
188
|
await this.core.admission.closeRunDelegatedRunAdmission(run);
|
|
189
|
-
|
|
189
|
+
let terminal = this.buildTerminal(run, 'completed', result);
|
|
190
190
|
try {
|
|
191
191
|
// The reversion segment closes in the save that stages the terminal.
|
|
192
192
|
await this.core.mutateProjection(run.state, run.stored, () => {
|
|
@@ -209,6 +209,7 @@ export class FlexHarnessRuns<TScope> {
|
|
|
209
209
|
throw projected;
|
|
210
210
|
}
|
|
211
211
|
run.steering.keptInContext = true;
|
|
212
|
+
terminal = this.withKeptInContext(terminal, true);
|
|
212
213
|
this.core.reversions.refreshAutomaticArchiveHorizon(run.stored);
|
|
213
214
|
run.phase = 'promoting';
|
|
214
215
|
const closePublication = this.core.openPublication(run.state, run.sessionId);
|
|
@@ -351,7 +352,10 @@ export class FlexHarnessRuns<TScope> {
|
|
|
351
352
|
const terminalError = publicStatus === 'cancelled'
|
|
352
353
|
? run.ownerCancellation ?? this.core.trustInternalError(new FlexHarnessAbortError())
|
|
353
354
|
: safeError;
|
|
354
|
-
const terminal = this.
|
|
355
|
+
const terminal = this.withKeptInContext(
|
|
356
|
+
this.buildTerminal(run, publicStatus, undefined, terminalError),
|
|
357
|
+
run.steering.keptInContext === true,
|
|
358
|
+
);
|
|
355
359
|
if (!this.core.isTombstoned(run)) {
|
|
356
360
|
const closePublication = this.core.openPublication(run.state, run.sessionId);
|
|
357
361
|
try {
|
|
@@ -564,6 +568,21 @@ export class FlexHarnessRuns<TScope> {
|
|
|
564
568
|
} catch (error) {
|
|
565
569
|
throw this.core.projectExternalError(run, error, 'toolProvider');
|
|
566
570
|
}
|
|
571
|
+
const contextCompaction = run.stored.session.contextCompaction ?? this.core.agentSessionPolicy.contextCompaction;
|
|
572
|
+
if (run.metricsOperation) {
|
|
573
|
+
run.metricsOperation.contextCompaction = {
|
|
574
|
+
...(model!.contextWindow === undefined ? {} : { contextWindow: model!.contextWindow }),
|
|
575
|
+
point: this.core.agentSessionPolicy.contextCompactor
|
|
576
|
+
? plugins.resolveAgentContextCompactionPoint({
|
|
577
|
+
...(model!.contextWindow === undefined ? {} : { contextWindow: model!.contextWindow }),
|
|
578
|
+
...(model!.contextCompactionDefaultTokens === undefined
|
|
579
|
+
? {}
|
|
580
|
+
: { contextCompactionDefaultTokens: model!.contextCompactionDefaultTokens }),
|
|
581
|
+
...(contextCompaction === undefined ? {} : { setting: contextCompaction }),
|
|
582
|
+
})
|
|
583
|
+
: { off: 'no-compactor' },
|
|
584
|
+
};
|
|
585
|
+
}
|
|
567
586
|
let closed = false;
|
|
568
587
|
let closePromise: Promise<void> | undefined;
|
|
569
588
|
const close = toolHandle?.close
|
|
@@ -596,6 +615,11 @@ export class FlexHarnessRuns<TScope> {
|
|
|
596
615
|
...(model!.cache === undefined ? {} : { cache: model!.cache }),
|
|
597
616
|
...(options.maxSteps ?? model!.maxSteps ? { maxSteps: options.maxSteps ?? model!.maxSteps } : {}),
|
|
598
617
|
...(run.cacheWarm ? { maxOutputTokens: this.core.cacheWarmOptions.maxOutputTokens } : {}),
|
|
618
|
+
...(model!.contextWindow === undefined ? {} : { contextWindow: model!.contextWindow }),
|
|
619
|
+
...(model!.contextCompactionDefaultTokens === undefined
|
|
620
|
+
? {}
|
|
621
|
+
: { contextCompactionDefaultTokens: model!.contextCompactionDefaultTokens }),
|
|
622
|
+
...(contextCompaction === undefined ? {} : { contextCompaction }),
|
|
599
623
|
...(close ? { close } : {}),
|
|
600
624
|
};
|
|
601
625
|
} catch (error) {
|
|
@@ -706,6 +730,7 @@ export class FlexHarnessRuns<TScope> {
|
|
|
706
730
|
public createReservation(
|
|
707
731
|
run: IActiveRun,
|
|
708
732
|
prompt: INormalizedFlexPrompt,
|
|
733
|
+
promptId?: string,
|
|
709
734
|
): { userMessage: IFlexMessage; assistantMessage: IFlexMessage } {
|
|
710
735
|
const timestamp = new Date().toISOString();
|
|
711
736
|
const userMessage: IFlexMessage = {
|
|
@@ -716,6 +741,7 @@ export class FlexHarnessRuns<TScope> {
|
|
|
716
741
|
status: 'streaming',
|
|
717
742
|
createdAt: timestamp,
|
|
718
743
|
parts: this.userMessageParts(prompt),
|
|
744
|
+
...(promptId === undefined ? {} : { promptId }),
|
|
719
745
|
...(run.cacheWarm ? { cacheWarm: true as const } : {}),
|
|
720
746
|
};
|
|
721
747
|
const assistantMessage: IFlexMessage = {
|
|
@@ -1029,6 +1055,14 @@ export class FlexHarnessRuns<TScope> {
|
|
|
1029
1055
|
}, true);
|
|
1030
1056
|
}
|
|
1031
1057
|
|
|
1058
|
+
/**
|
|
1059
|
+
* The terminal with its prompt's `keptInContext`: whether the run's result kept the prompt and
|
|
1060
|
+
* the steers it took in in the model context. `replaceVisibleRun()` writes it onto the steers.
|
|
1061
|
+
*/
|
|
1062
|
+
public withKeptInContext(terminal: IFlexTerminalProjection, keptInContext: boolean): IFlexTerminalProjection {
|
|
1063
|
+
return { ...terminal, userMessage: { ...terminal.userMessage, keptInContext } };
|
|
1064
|
+
}
|
|
1065
|
+
|
|
1032
1066
|
private replaceStagedTerminal(stored: IStoredSessionState, terminal: IFlexTerminalProjection): void {
|
|
1033
1067
|
stored.stagedTerminals = stored.stagedTerminals.filter((entry) => entry.runId !== terminal.runId);
|
|
1034
1068
|
stored.stagedTerminals.push(cloneSerializable(terminal));
|
|
@@ -1046,11 +1080,18 @@ export class FlexHarnessRuns<TScope> {
|
|
|
1046
1080
|
[terminal.assistantMessage.messageId, terminal.assistantMessage],
|
|
1047
1081
|
]);
|
|
1048
1082
|
const placed = new Set<string>();
|
|
1083
|
+
const keptInContext = terminal.userMessage.keptInContext;
|
|
1049
1084
|
stored.messages = stored.messages.map((message) => {
|
|
1050
1085
|
const replacement = message.runId === terminal.runId
|
|
1051
1086
|
? replacements.get(message.messageId)
|
|
1052
1087
|
: undefined;
|
|
1053
|
-
if (!replacement)
|
|
1088
|
+
if (!replacement) {
|
|
1089
|
+
// The steers the run took in share its prompt's outcome.
|
|
1090
|
+
return keptInContext !== undefined && message.runId === terminal.runId && message.role === 'user'
|
|
1091
|
+
&& message.keptInContext !== keptInContext
|
|
1092
|
+
? { ...cloneSerializable(message), keptInContext }
|
|
1093
|
+
: message;
|
|
1094
|
+
}
|
|
1054
1095
|
placed.add(replacement.messageId);
|
|
1055
1096
|
return cloneSerializable(replacement);
|
|
1056
1097
|
});
|
|
@@ -1089,7 +1130,10 @@ export class FlexHarnessRuns<TScope> {
|
|
|
1089
1130
|
this.core.emitPartEvent(run, 'part.completed', part);
|
|
1090
1131
|
}
|
|
1091
1132
|
}
|
|
1092
|
-
|
|
1133
|
+
const steerMessages = terminal.userMessage.keptInContext === undefined
|
|
1134
|
+
? []
|
|
1135
|
+
: run.stored.messages.filter((message) => message.runId === run.runId && message.steerId !== undefined);
|
|
1136
|
+
for (const message of [terminal.userMessage, ...steerMessages, terminal.assistantMessage]) {
|
|
1093
1137
|
this.core.emitEvent(run.scopeId, run.stored.session, {
|
|
1094
1138
|
type: 'message.updated',
|
|
1095
1139
|
runId: run.runId,
|
|
@@ -11,6 +11,7 @@ import {
|
|
|
11
11
|
type IFlexMessage,
|
|
12
12
|
type IFlexMessagePage,
|
|
13
13
|
type IFlexMessagePageOptions,
|
|
14
|
+
type IFlexMessageReadOptions,
|
|
14
15
|
type IFlexSession,
|
|
15
16
|
type IFlexSessionView,
|
|
16
17
|
type IFlexSessionMetrics,
|
|
@@ -29,6 +30,7 @@ import {
|
|
|
29
30
|
requireTransferIdentifier,
|
|
30
31
|
} from './utils.transfer.js';
|
|
31
32
|
import {
|
|
33
|
+
validateContextCompactionSetting,
|
|
32
34
|
validateIdentifier,
|
|
33
35
|
validateSessionGenerationId,
|
|
34
36
|
validateSessionConfigurationRef,
|
|
@@ -51,6 +53,19 @@ import type { FlexHarnessCore } from './classes.flexharness.core.js';
|
|
|
51
53
|
const maxSessionViewPasses = 64;
|
|
52
54
|
|
|
53
55
|
/** Sessions and their messages: listing, creation, updates, views and message pages. */
|
|
56
|
+
/** Validates transcript read options and states whether undone messages are included. */
|
|
57
|
+
function readIncludeUndone(options: IFlexMessageReadOptions, name: string): boolean {
|
|
58
|
+
if (!options || typeof options !== 'object' || Array.isArray(options)) {
|
|
59
|
+
throw new FlexHarnessValidationError(`${name} must be a plain object.`);
|
|
60
|
+
}
|
|
61
|
+
const unsupported = Object.keys(options).find((key) => key !== 'includeUndone');
|
|
62
|
+
if (unsupported) throw new FlexHarnessValidationError(`${name} do not support "${unsupported}".`);
|
|
63
|
+
if (options.includeUndone !== undefined && typeof options.includeUndone !== 'boolean') {
|
|
64
|
+
throw new FlexHarnessValidationError('includeUndone must be a boolean.');
|
|
65
|
+
}
|
|
66
|
+
return options.includeUndone === true;
|
|
67
|
+
}
|
|
68
|
+
|
|
54
69
|
export class FlexHarnessSessions<TScope> {
|
|
55
70
|
constructor(private readonly core: FlexHarnessCore<TScope>) {}
|
|
56
71
|
|
|
@@ -73,7 +88,7 @@ export class FlexHarnessSessions<TScope> {
|
|
|
73
88
|
throw new FlexHarnessValidationError('createSession options must be a plain object.');
|
|
74
89
|
}
|
|
75
90
|
const unsupported = Object.keys(options).find(
|
|
76
|
-
(key) => !['sessionId', 'sessionGenerationId', 'configurationRef', 'title'].includes(key),
|
|
91
|
+
(key) => !['sessionId', 'sessionGenerationId', 'configurationRef', 'title', 'contextCompaction'].includes(key),
|
|
77
92
|
);
|
|
78
93
|
if (unsupported) throw new FlexHarnessValidationError(`createSession does not support "${unsupported}".`);
|
|
79
94
|
const requestedSessionGenerationId = options.sessionGenerationId;
|
|
@@ -81,6 +96,12 @@ export class FlexHarnessSessions<TScope> {
|
|
|
81
96
|
validateSessionGenerationId(requestedSessionGenerationId);
|
|
82
97
|
}
|
|
83
98
|
if (options.configurationRef !== undefined) validateSessionConfigurationRef(options.configurationRef);
|
|
99
|
+
if (options.contextCompaction !== undefined) {
|
|
100
|
+
validateContextCompactionSetting(
|
|
101
|
+
options.contextCompaction,
|
|
102
|
+
this.core.agentSessionPolicy.contextCompactor !== undefined,
|
|
103
|
+
);
|
|
104
|
+
}
|
|
84
105
|
const resolved = await this.core.resolveState(scopeId);
|
|
85
106
|
const sessionId = options.sessionId ?? plugins.crypto.randomUUID();
|
|
86
107
|
validateIdentifier(sessionId, 'sessionId');
|
|
@@ -131,6 +152,9 @@ export class FlexHarnessSessions<TScope> {
|
|
|
131
152
|
sessionGenerationSequence: resolved.state.revision + 1,
|
|
132
153
|
...(options.configurationRef === undefined ? {} : { configurationRef: options.configurationRef }),
|
|
133
154
|
...(options.title ? { title: options.title } : {}),
|
|
155
|
+
...(options.contextCompaction === undefined
|
|
156
|
+
? {}
|
|
157
|
+
: { contextCompaction: cloneSerializable(options.contextCompaction) }),
|
|
134
158
|
createdAt: timestamp,
|
|
135
159
|
updatedAt: timestamp,
|
|
136
160
|
status: 'idle',
|
|
@@ -257,7 +281,7 @@ export class FlexHarnessSessions<TScope> {
|
|
|
257
281
|
sessionId: string,
|
|
258
282
|
options: IFlexUpdateSessionOptions,
|
|
259
283
|
): Promise<IFlexSession> {
|
|
260
|
-
validateUpdateSessionOptions(options);
|
|
284
|
+
validateUpdateSessionOptions(options, this.core.agentSessionPolicy.contextCompactor !== undefined);
|
|
261
285
|
const { state } = await this.core.resolveState(scopeId);
|
|
262
286
|
let result!: IFlexSession;
|
|
263
287
|
await this.core.mutateScope(state, () => {
|
|
@@ -269,6 +293,10 @@ export class FlexHarnessSessions<TScope> {
|
|
|
269
293
|
if (options.title === null) delete stored.session.title;
|
|
270
294
|
else stored.session.title = options.title!;
|
|
271
295
|
}
|
|
296
|
+
if (Object.prototype.hasOwnProperty.call(options, 'contextCompaction')) {
|
|
297
|
+
if (options.contextCompaction === null) delete stored.session.contextCompaction;
|
|
298
|
+
else stored.session.contextCompaction = cloneSerializable(options.contextCompaction!);
|
|
299
|
+
}
|
|
272
300
|
if (options.archived === true) stored.session.archivedAt ??= timestamp;
|
|
273
301
|
else if (options.archived === false) delete stored.session.archivedAt;
|
|
274
302
|
stored.session.updatedAt = timestamp;
|
|
@@ -278,7 +306,12 @@ export class FlexHarnessSessions<TScope> {
|
|
|
278
306
|
return result;
|
|
279
307
|
}
|
|
280
308
|
|
|
281
|
-
public async getSessionView(
|
|
309
|
+
public async getSessionView(
|
|
310
|
+
scopeId: string,
|
|
311
|
+
sessionId: string,
|
|
312
|
+
options: IFlexMessageReadOptions = {},
|
|
313
|
+
): Promise<IFlexSessionView> {
|
|
314
|
+
const includeUndone = readIncludeUndone(options, 'Session view options');
|
|
282
315
|
const { state } = await this.core.resolveState(scopeId);
|
|
283
316
|
let stored = this.core.requireSession(state, sessionId);
|
|
284
317
|
for (let pass = 0; ; pass++) {
|
|
@@ -301,7 +334,7 @@ export class FlexHarnessSessions<TScope> {
|
|
|
301
334
|
) break;
|
|
302
335
|
}
|
|
303
336
|
const run = state.activeRuns.get(sessionId);
|
|
304
|
-
const messages = this.core.
|
|
337
|
+
const messages = this.core.transcriptMessages(stored, includeUndone).map((message) => (
|
|
305
338
|
run && run.stored === stored && message.messageId === run.assistantMessageId
|
|
306
339
|
&& message.status === 'streaming'
|
|
307
340
|
? { ...message, parts: run.callbackParts }
|
|
@@ -318,12 +351,17 @@ export class FlexHarnessSessions<TScope> {
|
|
|
318
351
|
});
|
|
319
352
|
}
|
|
320
353
|
|
|
321
|
-
public async getMessages(
|
|
354
|
+
public async getMessages(
|
|
355
|
+
scopeId: string,
|
|
356
|
+
sessionId: string,
|
|
357
|
+
options: IFlexMessageReadOptions = {},
|
|
358
|
+
): Promise<IFlexMessage[]> {
|
|
359
|
+
const includeUndone = readIncludeUndone(options, 'Message read options');
|
|
322
360
|
const { state } = await this.core.resolveState(scopeId);
|
|
323
361
|
const stored = this.core.requireSession(state, sessionId);
|
|
324
362
|
await stored.projectionQueue;
|
|
325
363
|
this.core.assertStateAcceptingWork(state);
|
|
326
|
-
return publicSnapshot(this.core.
|
|
364
|
+
return publicSnapshot(this.core.transcriptMessages(stored, includeUndone));
|
|
327
365
|
}
|
|
328
366
|
|
|
329
367
|
public async listMessagePage(
|
|
@@ -334,8 +372,13 @@ export class FlexHarnessSessions<TScope> {
|
|
|
334
372
|
if (!options || typeof options !== 'object' || Array.isArray(options)) {
|
|
335
373
|
throw new FlexHarnessValidationError('Message page options must be a plain object.');
|
|
336
374
|
}
|
|
337
|
-
const unsupported = Object.keys(options)
|
|
375
|
+
const unsupported = Object.keys(options)
|
|
376
|
+
.find((key) => key !== 'limit' && key !== 'before' && key !== 'includeUndone');
|
|
338
377
|
if (unsupported) throw new FlexHarnessValidationError(`Message page options do not support "${unsupported}".`);
|
|
378
|
+
if (options.includeUndone !== undefined && typeof options.includeUndone !== 'boolean') {
|
|
379
|
+
throw new FlexHarnessValidationError('includeUndone must be a boolean.');
|
|
380
|
+
}
|
|
381
|
+
const includeUndone = options.includeUndone === true;
|
|
339
382
|
const limit = options.limit ?? maxMessagePageSize;
|
|
340
383
|
if (!Number.isSafeInteger(limit) || limit < 1 || limit > maxMessagePageSize) {
|
|
341
384
|
throw new FlexHarnessValidationError(`Message page limit must be an integer from 1 through ${maxMessagePageSize}.`);
|
|
@@ -353,7 +396,7 @@ export class FlexHarnessSessions<TScope> {
|
|
|
353
396
|
await stored.projectionQueue;
|
|
354
397
|
this.core.assertStateAcceptingWork(state);
|
|
355
398
|
const namespace = this.core.messageCursorNamespace(state.storageKey);
|
|
356
|
-
const visibleMessages = this.core.
|
|
399
|
+
const visibleMessages = this.core.transcriptMessages(stored, includeUndone);
|
|
357
400
|
let end = visibleMessages.length;
|
|
358
401
|
if (options.before !== undefined) {
|
|
359
402
|
const cursor = this.core.parseMessageCursor(options.before);
|
|
@@ -394,14 +437,16 @@ export class FlexHarnessSessions<TScope> {
|
|
|
394
437
|
scopeId: string,
|
|
395
438
|
sessionId: string,
|
|
396
439
|
messageId: string,
|
|
440
|
+
options: IFlexMessageReadOptions = {},
|
|
397
441
|
): Promise<IFlexMessage> {
|
|
442
|
+
const includeUndone = readIncludeUndone(options, 'Message read options');
|
|
398
443
|
validateIdentifier(messageId, 'messageId');
|
|
399
444
|
requireTransferIdentifier(messageId, 'messageId');
|
|
400
445
|
const { state } = await this.core.resolveState(scopeId);
|
|
401
446
|
const stored = this.core.requireSession(state, sessionId);
|
|
402
447
|
await stored.projectionQueue;
|
|
403
448
|
this.core.assertStateAcceptingWork(state);
|
|
404
|
-
return publicSnapshot(createBoundedTransferMessage(this.core.requireMessage(stored, messageId)));
|
|
449
|
+
return publicSnapshot(createBoundedTransferMessage(this.core.requireMessage(stored, messageId, includeUndone)));
|
|
405
450
|
}
|
|
406
451
|
|
|
407
452
|
public async pushRuntimeEvent(
|
|
@@ -15,6 +15,7 @@ import type {
|
|
|
15
15
|
IFlexMessage,
|
|
16
16
|
IFlexMessagePage,
|
|
17
17
|
IFlexMessagePageOptions,
|
|
18
|
+
IFlexMessageReadOptions,
|
|
18
19
|
IFlexPermissionRequest,
|
|
19
20
|
IFlexProjectGoalResult,
|
|
20
21
|
IFlexSteerAdmission,
|
|
@@ -246,12 +247,20 @@ export class FlexHarness<TScope = unknown> {
|
|
|
246
247
|
* with the instance's event sequence they include. It waits until no queued projection or scope
|
|
247
248
|
* write and no change that emits events for the session is in progress.
|
|
248
249
|
*/
|
|
249
|
-
public getSessionView(
|
|
250
|
-
|
|
250
|
+
public getSessionView(
|
|
251
|
+
scopeId: string,
|
|
252
|
+
sessionId: string,
|
|
253
|
+
options?: IFlexMessageReadOptions,
|
|
254
|
+
): Promise<IFlexSessionView> {
|
|
255
|
+
return this.core.sessions.getSessionView(scopeId, sessionId, options);
|
|
251
256
|
}
|
|
252
257
|
|
|
253
|
-
public getMessages(
|
|
254
|
-
|
|
258
|
+
public getMessages(
|
|
259
|
+
scopeId: string,
|
|
260
|
+
sessionId: string,
|
|
261
|
+
options?: IFlexMessageReadOptions,
|
|
262
|
+
): Promise<IFlexMessage[]> {
|
|
263
|
+
return this.core.sessions.getMessages(scopeId, sessionId, options);
|
|
255
264
|
}
|
|
256
265
|
|
|
257
266
|
public listMessagePage(
|
|
@@ -266,8 +275,9 @@ export class FlexHarness<TScope = unknown> {
|
|
|
266
275
|
scopeId: string,
|
|
267
276
|
sessionId: string,
|
|
268
277
|
messageId: string,
|
|
278
|
+
options?: IFlexMessageReadOptions,
|
|
269
279
|
): Promise<IFlexMessage> {
|
|
270
|
-
return this.core.sessions.getMessage(scopeId, sessionId, messageId);
|
|
280
|
+
return this.core.sessions.getMessage(scopeId, sessionId, messageId, options);
|
|
271
281
|
}
|
|
272
282
|
|
|
273
283
|
public listSlashCommands(
|
package/ts/errors.ts
CHANGED
|
@@ -68,6 +68,21 @@ export class FlexHarnessSteerRejectedError extends FlexHarnessError {
|
|
|
68
68
|
}
|
|
69
69
|
}
|
|
70
70
|
|
|
71
|
+
/**
|
|
72
|
+
* A prompt names a `promptId` that an outstanding prompt of the same session, queued or running,
|
|
73
|
+
* already holds. A finished prompt's id is free again: its user message in the transcript shows
|
|
74
|
+
* that it ran.
|
|
75
|
+
*/
|
|
76
|
+
export class FlexHarnessDuplicatePromptError extends FlexHarnessError {
|
|
77
|
+
public readonly promptId: string;
|
|
78
|
+
|
|
79
|
+
constructor(promptId: string) {
|
|
80
|
+
super('FLEX_PROMPT_DUPLICATE', `Prompt "${promptId}" is already outstanding in this session.`);
|
|
81
|
+
this.name = 'FlexHarnessDuplicatePromptError';
|
|
82
|
+
this.promptId = promptId;
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
|
|
71
86
|
/** An operation would change the history or model context of an imported, read-only session. */
|
|
72
87
|
export class FlexHarnessImportedSessionError extends FlexHarnessError {
|
|
73
88
|
public readonly sessionId: string;
|
package/ts/index.ts
CHANGED
|
@@ -3,6 +3,12 @@ export * from './classes.stores.js';
|
|
|
3
3
|
export { FlexToolCatalog } from './classes.toolcatalog.js';
|
|
4
4
|
export * from './errors.js';
|
|
5
5
|
export * from './interfaces.js';
|
|
6
|
+
export {
|
|
7
|
+
AGENT_CONTEXT_COMPACTION_CEILING_PERCENT,
|
|
8
|
+
getAgentContextCompactionCeiling,
|
|
9
|
+
resolveAgentContextCompactionPoint,
|
|
10
|
+
} from './plugins.js';
|
|
11
|
+
export type { IAgentContextCompactionInput, TAgentContextCompactionPoint } from './plugins.js';
|
|
6
12
|
export {
|
|
7
13
|
createFlexToolPermissionRequester,
|
|
8
14
|
type IFlexToolPermissionRequesterOptions,
|
|
@@ -412,6 +412,8 @@ export interface IQueuedPrompt {
|
|
|
412
412
|
debounceMs?: number;
|
|
413
413
|
/** A cache-warm prompt of the session's cache keeper; see `keepCacheWarm()`. */
|
|
414
414
|
cacheWarm?: true;
|
|
415
|
+
/** The prompt's `promptId`; kept after its payload is released. */
|
|
416
|
+
promptId?: string;
|
|
415
417
|
startedAt?: string;
|
|
416
418
|
prompt?: INormalizedFlexPrompt;
|
|
417
419
|
options?: IFlexPromptOptions;
|
package/ts/interfaces.ts
CHANGED
|
@@ -92,8 +92,36 @@ export interface IFlexSessionMetricsState {
|
|
|
92
92
|
reportedLifetimeCacheWriteTokens?: number;
|
|
93
93
|
/** The breakdown counts every call since the session began, without saturation. */
|
|
94
94
|
lifetimeTokenBreakdownComplete?: boolean;
|
|
95
|
+
/** The served context window of the last settled run's model; see `IFlexSessionMetrics`. */
|
|
96
|
+
contextWindow?: number;
|
|
97
|
+
/** The last settled run's compaction point; exclusive with `contextCompactionOff`. */
|
|
98
|
+
contextCompactionTokens?: number;
|
|
99
|
+
/** Why the last settled run had no compaction point; exclusive with `contextCompactionTokens`. */
|
|
100
|
+
contextCompactionOff?: TFlexContextCompactionOff;
|
|
95
101
|
}
|
|
96
102
|
|
|
103
|
+
/**
|
|
104
|
+
* A session's choice of its proactive compaction point, in tokens of the model in use.
|
|
105
|
+
*
|
|
106
|
+
* - `{ tokens }`: a positive integer, clamped at run time to the ceiling of the run's model (90%
|
|
107
|
+
* of its served context window). Without a known served window it applies unclamped.
|
|
108
|
+
* - `{ percent }`: an integer from 1 through 90 of the served window. Without a known served
|
|
109
|
+
* window the run has no proactive compaction.
|
|
110
|
+
* - `false`: no proactive compaction.
|
|
111
|
+
*
|
|
112
|
+
* Omitted, the model's default applies: the catalog's automatic compaction point
|
|
113
|
+
* (`IFlexResolvedModel.contextCompactionDefaultTokens`) when stated, at most the ceiling,
|
|
114
|
+
* otherwise the ceiling itself.
|
|
115
|
+
*/
|
|
116
|
+
export type TFlexContextCompactionSetting = plugins.TAgentContextCompactionSetting;
|
|
117
|
+
|
|
118
|
+
/**
|
|
119
|
+
* Why a run had no proactive compaction point: the setting is `false` (`disabled`), the run's
|
|
120
|
+
* model states no served context window and the setting needs one (`no-context-window`), or the
|
|
121
|
+
* harness has no `contextCompactor` (`no-compactor`).
|
|
122
|
+
*/
|
|
123
|
+
export type TFlexContextCompactionOff = plugins.TAgentContextCompactionOff | 'no-compactor';
|
|
124
|
+
|
|
97
125
|
/** No member estimates current context size or includes child-session / tool-owned model calls. */
|
|
98
126
|
export interface IFlexSessionMetrics {
|
|
99
127
|
/** Sum of durably settled per-call reports; a lower bound when incomplete. */
|
|
@@ -120,6 +148,19 @@ export interface IFlexSessionMetrics {
|
|
|
120
148
|
* the session's calls were counted by input and cache since it began.
|
|
121
149
|
*/
|
|
122
150
|
lifetimeTokenBreakdownComplete: boolean;
|
|
151
|
+
/**
|
|
152
|
+
* The served context window, in tokens, of the model the last settled run resolved; absent when
|
|
153
|
+
* that model states none or before the first run.
|
|
154
|
+
*/
|
|
155
|
+
contextWindow?: number;
|
|
156
|
+
/**
|
|
157
|
+
* The proactive compaction point the last settled run used, in tokens. Exactly one of it and
|
|
158
|
+
* `contextCompactionOff` is present once a run settled; neither before. A changed session
|
|
159
|
+
* setting shows from the next run; `resolveAgentContextCompactionPoint` states it before.
|
|
160
|
+
*/
|
|
161
|
+
contextCompactionTokens?: number;
|
|
162
|
+
/** Why the last settled run had no proactive compaction point. */
|
|
163
|
+
contextCompactionOff?: TFlexContextCompactionOff;
|
|
123
164
|
}
|
|
124
165
|
|
|
125
166
|
export interface IFlexModelIdentity {
|
|
@@ -222,6 +263,21 @@ export interface IFlexMessage {
|
|
|
222
263
|
rejectedPermissionId?: string;
|
|
223
264
|
/** On a user message that steered its running run: the caller's steer identity. */
|
|
224
265
|
steerId?: string;
|
|
266
|
+
/** On the user message that heads a run: the caller's prompt identity, when the prompt named one. */
|
|
267
|
+
promptId?: string;
|
|
268
|
+
/**
|
|
269
|
+
* On a user message, once its run reached its terminal state: whether the run's result kept this
|
|
270
|
+
* message in the model context of later turns, the value the run's `keptInContext` reports. It
|
|
271
|
+
* records that outcome and is never rewritten afterwards: an undo is reported by `undone`, and a
|
|
272
|
+
* later compaction summarizes the message, which was still delivered. Absent on a message whose
|
|
273
|
+
* run has not ended, and on messages written before FlexHarness 15.0.0.
|
|
274
|
+
*/
|
|
275
|
+
keptInContext?: boolean;
|
|
276
|
+
/**
|
|
277
|
+
* Set only by a read with `includeUndone`: the message belongs to an undone turn that a redo can
|
|
278
|
+
* bring back. Never stored.
|
|
279
|
+
*/
|
|
280
|
+
undone?: true;
|
|
225
281
|
/**
|
|
226
282
|
* On both messages of a cache-warm turn: the harness sent the prompt only to keep the model's
|
|
227
283
|
* prompt cache warm. Hosts leave these out of what they show and report as work.
|
|
@@ -268,6 +324,8 @@ export interface IFlexSession {
|
|
|
268
324
|
depth?: number;
|
|
269
325
|
/** Set only on a session the staged session import created; such a session is read-only. */
|
|
270
326
|
origin?: IFlexSessionOrigin;
|
|
327
|
+
/** The session's compaction setting; absent for the harness default. */
|
|
328
|
+
contextCompaction?: TFlexContextCompactionSetting;
|
|
271
329
|
}
|
|
272
330
|
|
|
273
331
|
/** The position of an imported session when one source conversation was split into several sessions. */
|
|
@@ -394,14 +452,25 @@ export interface IFlexCreateSessionOptions {
|
|
|
394
452
|
sessionGenerationId?: string;
|
|
395
453
|
configurationRef?: string;
|
|
396
454
|
title?: string;
|
|
455
|
+
/** The session's compaction setting; omitted for the harness default. */
|
|
456
|
+
contextCompaction?: TFlexContextCompactionSetting;
|
|
397
457
|
}
|
|
398
458
|
|
|
399
459
|
export interface IFlexUpdateSessionOptions {
|
|
400
460
|
title?: string | null;
|
|
401
461
|
archived?: boolean;
|
|
462
|
+
/** The session's compaction setting; `null` returns the session to the harness default. */
|
|
463
|
+
contextCompaction?: TFlexContextCompactionSetting | null;
|
|
402
464
|
}
|
|
403
465
|
|
|
404
466
|
export interface IFlexPromptOptions {
|
|
467
|
+
/**
|
|
468
|
+
* The caller's identity for this prompt, a non-empty string of at most 512 UTF-8 bytes. It is on
|
|
469
|
+
* the prompt's queue entry and on the user message that heads its run, so a caller can tell
|
|
470
|
+
* exactly whether the prompt was taken in. An outstanding prompt of the session holding the same
|
|
471
|
+
* id rejects the admission with `FlexHarnessDuplicatePromptError`.
|
|
472
|
+
*/
|
|
473
|
+
promptId?: string;
|
|
405
474
|
modelHint?: string;
|
|
406
475
|
system?: string;
|
|
407
476
|
/** Optional positive safe integer step cap; no limit unless configured. */
|
|
@@ -528,6 +597,8 @@ export interface IFlexPromptQueueEntry {
|
|
|
528
597
|
status: TFlexPromptQueueStatus;
|
|
529
598
|
queuedAt: string;
|
|
530
599
|
runId?: string;
|
|
600
|
+
/** The prompt's `promptId`, when it named one. */
|
|
601
|
+
promptId?: string;
|
|
531
602
|
scheduleKey?: string;
|
|
532
603
|
/** The entry is a cache-warm prompt; see {@link IFlexMessage.cacheWarm}. */
|
|
533
604
|
cacheWarm?: true;
|
|
@@ -606,7 +677,16 @@ export interface IFlexScheduledPromptAdmission extends IFlexPromptAdmission {
|
|
|
606
677
|
scheduleKey: string;
|
|
607
678
|
}
|
|
608
679
|
|
|
609
|
-
|
|
680
|
+
/** Options of the transcript reads. */
|
|
681
|
+
export interface IFlexMessageReadOptions {
|
|
682
|
+
/**
|
|
683
|
+
* Also return the messages of undone turns, each marked `undone: true`, in their place after the
|
|
684
|
+
* visible ones. Messages a committed branch removed are gone and never returned.
|
|
685
|
+
*/
|
|
686
|
+
includeUndone?: boolean;
|
|
687
|
+
}
|
|
688
|
+
|
|
689
|
+
export interface IFlexMessagePageOptions extends IFlexMessageReadOptions {
|
|
610
690
|
limit?: number;
|
|
611
691
|
before?: string;
|
|
612
692
|
}
|
|
@@ -669,6 +749,19 @@ export interface IFlexResolvedModel {
|
|
|
669
749
|
cache?: TFlexAgentCacheSetting;
|
|
670
750
|
/** Optional positive safe integer step cap; no limit unless configured. */
|
|
671
751
|
maxSteps?: number;
|
|
752
|
+
/**
|
|
753
|
+
* The model's served context window in tokens, from the provider's catalog: the window the
|
|
754
|
+
* provider serves by default, never an extended maximum (for a Codex catalog `context_window`,
|
|
755
|
+
* not `max_context_window`; for Anthropic's Models API `max_input_tokens`). Omitted when the
|
|
756
|
+
* catalog states none: the run then has a compaction point only from a `{ tokens }` setting.
|
|
757
|
+
*/
|
|
758
|
+
contextWindow?: number;
|
|
759
|
+
/**
|
|
760
|
+
* The catalog's own automatic compaction point in tokens, when it states one (a Codex catalog's
|
|
761
|
+
* `auto_compact_token_limit`). It lowers the default point, never the ceiling. Requires
|
|
762
|
+
* `contextWindow`.
|
|
763
|
+
*/
|
|
764
|
+
contextCompactionDefaultTokens?: number;
|
|
672
765
|
}
|
|
673
766
|
|
|
674
767
|
export interface IFlexModelResolver<TScope> {
|
|
@@ -1891,7 +1984,11 @@ export type TFlexAgentContextCompactor<TScope = unknown> = (
|
|
|
1891
1984
|
export interface IFlexAgentSessionPolicy<TScope = unknown> {
|
|
1892
1985
|
contextBuilder?: plugins.TAgentContextBuilder;
|
|
1893
1986
|
contextCompactor?: TFlexAgentContextCompactor<TScope>;
|
|
1894
|
-
|
|
1987
|
+
/**
|
|
1988
|
+
* The harness default compaction setting, for sessions without their own. Omitted, the model's
|
|
1989
|
+
* default applies. Requires `contextCompactor` unless `false`.
|
|
1990
|
+
*/
|
|
1991
|
+
contextCompaction?: TFlexContextCompactionSetting;
|
|
1895
1992
|
eventRetention?: TFlexAgentSessionOptions['eventRetention'];
|
|
1896
1993
|
changeListenerTimeoutMs?: number;
|
|
1897
1994
|
maxPendingSessionChanges?: number;
|
package/ts/plugins.ts
CHANGED
|
@@ -92,6 +92,7 @@ export const importCodeSandboxEngine = async (): Promise<ICodeSandboxEngine> =>
|
|
|
92
92
|
|
|
93
93
|
// @push.rocks scope
|
|
94
94
|
import {
|
|
95
|
+
AGENT_CONTEXT_COMPACTION_CEILING_PERCENT,
|
|
95
96
|
AgentEventStoreConflictError,
|
|
96
97
|
AgentPendingInputQueue,
|
|
97
98
|
applyAgentEventChange,
|
|
@@ -100,13 +101,16 @@ import {
|
|
|
100
101
|
AgentPendingInputRejectedError,
|
|
101
102
|
AgentSession,
|
|
102
103
|
AgentToolCallScheduler,
|
|
104
|
+
assertAgentContextCompactionSetting,
|
|
103
105
|
buildModelMessages,
|
|
106
|
+
getAgentContextCompactionCeiling,
|
|
104
107
|
ToolJobStoreConflictError,
|
|
105
108
|
createAgentEvent,
|
|
106
109
|
filterModelVisibleAgentEvents,
|
|
107
110
|
getAgentGenerationTransactions,
|
|
108
111
|
isGenerationInModelContext,
|
|
109
112
|
modelMessagesToAgentEvents,
|
|
113
|
+
resolveAgentContextCompactionPoint,
|
|
110
114
|
tool,
|
|
111
115
|
validateAgentEventArchive,
|
|
112
116
|
validateAgentEventSnapshot,
|
|
@@ -114,6 +118,7 @@ import {
|
|
|
114
118
|
} from '@modelprofile.com/flexharness-agent';
|
|
115
119
|
|
|
116
120
|
export {
|
|
121
|
+
AGENT_CONTEXT_COMPACTION_CEILING_PERCENT,
|
|
117
122
|
AgentEventStoreConflictError,
|
|
118
123
|
AgentPendingInputQueue,
|
|
119
124
|
applyAgentEventChange,
|
|
@@ -122,13 +127,16 @@ export {
|
|
|
122
127
|
AgentPendingInputRejectedError,
|
|
123
128
|
AgentSession,
|
|
124
129
|
AgentToolCallScheduler,
|
|
130
|
+
assertAgentContextCompactionSetting,
|
|
125
131
|
buildModelMessages,
|
|
132
|
+
getAgentContextCompactionCeiling,
|
|
126
133
|
ToolJobStoreConflictError,
|
|
127
134
|
createAgentEvent,
|
|
128
135
|
filterModelVisibleAgentEvents,
|
|
129
136
|
getAgentGenerationTransactions,
|
|
130
137
|
isGenerationInModelContext,
|
|
131
138
|
modelMessagesToAgentEvents,
|
|
139
|
+
resolveAgentContextCompactionPoint,
|
|
132
140
|
tool,
|
|
133
141
|
validateAgentEventArchive,
|
|
134
142
|
validateAgentEventSnapshot,
|
|
@@ -141,6 +149,7 @@ export type {
|
|
|
141
149
|
TSmartAiCacheBudgetBasis,
|
|
142
150
|
IAgentEventArchive,
|
|
143
151
|
IAgentEventChange,
|
|
152
|
+
IAgentContextCompactionInput,
|
|
144
153
|
IAgentContextCompactionOptions,
|
|
145
154
|
IAgentEventSnapshot,
|
|
146
155
|
IAgentEventStore,
|
|
@@ -165,6 +174,9 @@ export type {
|
|
|
165
174
|
IToolJobStore,
|
|
166
175
|
IToolPermissionRequest,
|
|
167
176
|
TAgentContextBuilder,
|
|
177
|
+
TAgentContextCompactionOff,
|
|
178
|
+
TAgentContextCompactionPoint,
|
|
179
|
+
TAgentContextCompactionSetting,
|
|
168
180
|
TAgentContextCompactor,
|
|
169
181
|
TAgentEvent,
|
|
170
182
|
TAgentGenerationOutcome,
|