@modelprofile.com/flexharness 12.3.0 → 12.5.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 +3 -0
- package/dist_ts/classes.flexharness.core.js +22 -2
- package/dist_ts/classes.flexharness.prompts.js +4 -4
- package/dist_ts/classes.flexharness.runs.js +2 -2
- package/dist_ts/classes.flexharness.slashcommands.js +2 -2
- package/dist_ts/classes.flexharness.subagents.js +2 -2
- package/dist_ts/interfaces.d.ts +32 -1
- 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.d.ts +3 -1
- package/dist_ts/utils.harnessoptions.js +14 -1
- package/dist_ts/utils.json.js +2 -2
- package/dist_ts/utils.prompt.d.ts +7 -1
- package/dist_ts/utils.prompt.js +38 -6
- package/dist_ts/utils.transfer.d.ts +1 -1
- package/dist_ts/utils.transfer.js +8 -3
- package/dist_ts_chat_harness/functions.intents.d.ts +6 -0
- package/dist_ts_chat_harness/functions.intents.js +5 -2
- package/dist_ts_chat_harness_server/classes.chathub.js +2 -2
- package/dist_ts_chat_harness_server/classes.turngate.d.ts +5 -2
- package/dist_ts_chat_harness_server/classes.turngate.js +31 -8
- package/dist_ts_chat_harness_server/index.d.ts +1 -1
- package/dist_ts_chat_harness_server/interfaces.d.ts +20 -0
- package/dist_ts_chat_harness_server/plugins.d.ts +1 -1
- package/dist_ts_compaction/plugins.d.ts +3 -3
- package/dist_ts_compaction/plugins.js +3 -3
- package/dist_ts_compaction/smartagent.compaction.d.ts +6 -0
- package/dist_ts_compaction/smartagent.compaction.js +4 -1
- package/package.json +3 -3
- package/readme.md +27 -5
- package/ts/00_commitinfo_data.ts +1 -1
- package/ts/classes.flexharness.core.ts +21 -0
- package/ts/classes.flexharness.prompts.ts +3 -3
- package/ts/classes.flexharness.runs.ts +1 -1
- package/ts/classes.flexharness.slashcommands.ts +1 -1
- package/ts/classes.flexharness.subagents.ts +1 -1
- package/ts/interfaces.ts +34 -1
- package/ts/plugins.ts +3 -0
- package/ts/readme.md +27 -5
- package/ts/utils.harnessoptions.ts +15 -0
- package/ts/utils.json.ts +1 -1
- package/ts/utils.prompt.ts +48 -5
- package/ts/utils.transfer.ts +6 -1
- package/ts_chat_harness/functions.intents.ts +10 -1
- package/ts_chat_harness/readme.md +1 -1
- package/ts_chat_harness_server/classes.chathub.ts +5 -1
- package/ts_chat_harness_server/classes.turngate.ts +45 -7
- package/ts_chat_harness_server/index.ts +1 -0
- package/ts_chat_harness_server/interfaces.ts +21 -0
- package/ts_chat_harness_server/plugins.ts +1 -0
- package/ts_chat_harness_server/readme.md +2 -0
- package/ts_compaction/plugins.ts +11 -3
- package/ts_compaction/smartagent.compaction.ts +9 -0
|
@@ -1,7 +1,12 @@
|
|
|
1
1
|
import * as plugins from './plugins.js';
|
|
2
2
|
import { FlexHarnessChatRequestError } from './errors.js';
|
|
3
3
|
import { STOPPED_MESSAGE, isSameTab } from './functions.validation.js';
|
|
4
|
-
import type {
|
|
4
|
+
import type {
|
|
5
|
+
IFlexHarnessChatCaller,
|
|
6
|
+
IFlexHarnessChatHubOptions,
|
|
7
|
+
IFlexHarnessChatLimits,
|
|
8
|
+
IFlexHarnessChatTurn,
|
|
9
|
+
} from './interfaces.js';
|
|
5
10
|
|
|
6
11
|
const STILL_FINISHING_MESSAGE =
|
|
7
12
|
'The assistant is still finishing its previous reply. Send your message again in a moment.';
|
|
@@ -53,6 +58,7 @@ const settlesBefore = async (promiseArg: Promise<unknown>, deadlineArg: number):
|
|
|
53
58
|
export class FlexHarnessChatTurnGate<TContext> {
|
|
54
59
|
private readonly harness: TFlexHarnessChatGateHarness;
|
|
55
60
|
private readonly limits: IFlexHarnessChatLimits;
|
|
61
|
+
private readonly attachmentPart: IFlexHarnessChatHubOptions<TContext>['attachmentPart'];
|
|
56
62
|
private readonly bindings = new Map<string, ITurnBinding<TContext>>();
|
|
57
63
|
/** serializes the admission of messages per conversation */
|
|
58
64
|
private readonly locks = new Map<string, Promise<void>>();
|
|
@@ -60,9 +66,14 @@ export class FlexHarnessChatTurnGate<TContext> {
|
|
|
60
66
|
private readonly admissions = new Set<Promise<void>>();
|
|
61
67
|
private stopped = false;
|
|
62
68
|
|
|
63
|
-
constructor(
|
|
69
|
+
constructor(
|
|
70
|
+
harnessArg: TFlexHarnessChatGateHarness,
|
|
71
|
+
limitsArg: IFlexHarnessChatLimits,
|
|
72
|
+
attachmentPartArg?: IFlexHarnessChatHubOptions<TContext>['attachmentPart'],
|
|
73
|
+
) {
|
|
64
74
|
this.harness = harnessArg;
|
|
65
75
|
this.limits = limitsArg;
|
|
76
|
+
this.attachmentPart = attachmentPartArg;
|
|
66
77
|
}
|
|
67
78
|
|
|
68
79
|
public turnOf(scopeIdArg: string, sessionIdArg: string): IFlexHarnessChatTurn<TContext> | undefined {
|
|
@@ -160,10 +171,12 @@ export class FlexHarnessChatTurnGate<TContext> {
|
|
|
160
171
|
if (!(await settlesBefore(promiseArg, deadlineArg))) throw new FlexHarnessChatRequestError(STILL_FINISHING_MESSAGE);
|
|
161
172
|
};
|
|
162
173
|
this.assertOpen();
|
|
174
|
+
const attachmentParts = await this.attachmentPartsOf(callerArg, target, sendArg, contextArg);
|
|
175
|
+
this.assertOpen();
|
|
163
176
|
const binding = this.bindings.get(bindingKey(target.scopeId, target.sessionId));
|
|
164
177
|
if (binding?.queueId) {
|
|
165
178
|
for (let attempt = 0; attempt < 2; attempt++) {
|
|
166
|
-
const intent = intentOf(target, { ...sendArg, delivery: 'steer' }, binding.queueId);
|
|
179
|
+
const intent = intentOf(target, { ...sendArg, delivery: 'steer' }, { activeQueueId: binding.queueId, attachmentParts });
|
|
167
180
|
if (intent.type !== 'steerPrompt') break;
|
|
168
181
|
this.assertOpen();
|
|
169
182
|
try {
|
|
@@ -187,7 +200,28 @@ export class FlexHarnessChatTurnGate<TContext> {
|
|
|
187
200
|
await waitFor(binding.settled);
|
|
188
201
|
}
|
|
189
202
|
this.assertOpen();
|
|
190
|
-
return this.startTurn(callerArg, target, sendArg, contextArg);
|
|
203
|
+
return this.startTurn(callerArg, target, sendArg, contextArg, attachmentParts);
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
/** The prompt parts the host makes of the message's attachments, when it makes them. */
|
|
207
|
+
private async attachmentPartsOf(
|
|
208
|
+
callerArg: IFlexHarnessChatCaller,
|
|
209
|
+
target: ITarget,
|
|
210
|
+
sendArg: plugins.IHarnessComposerSendDetail,
|
|
211
|
+
contextArg: TContext,
|
|
212
|
+
): Promise<plugins.TFlexPromptPart[] | undefined> {
|
|
213
|
+
if (!this.attachmentPart || sendArg.attachments.length === 0) return undefined;
|
|
214
|
+
const parts: plugins.TFlexPromptPart[] = [];
|
|
215
|
+
for (const attachment of sendArg.attachments) {
|
|
216
|
+
parts.push(await this.attachmentPart({
|
|
217
|
+
tab: { tabId: callerArg.tabId, scopeId: callerArg.scopeId },
|
|
218
|
+
scopeId: target.scopeId,
|
|
219
|
+
sessionId: target.sessionId,
|
|
220
|
+
context: contextArg,
|
|
221
|
+
attachment,
|
|
222
|
+
}));
|
|
223
|
+
}
|
|
224
|
+
return parts;
|
|
191
225
|
}
|
|
192
226
|
|
|
193
227
|
private async startTurn(
|
|
@@ -195,10 +229,11 @@ export class FlexHarnessChatTurnGate<TContext> {
|
|
|
195
229
|
target: ITarget,
|
|
196
230
|
sendArg: plugins.IHarnessComposerSendDetail,
|
|
197
231
|
contextArg: TContext,
|
|
232
|
+
attachmentPartsArg: plugins.TFlexPromptPart[] | undefined,
|
|
198
233
|
): Promise<plugins.TFlexHarnessChatResponse<'flexHarnessChatSend'>> {
|
|
199
234
|
const { scopeId, sessionId } = target;
|
|
200
235
|
const key = bindingKey(scopeId, sessionId);
|
|
201
|
-
const intent = intentOf(target, { ...sendArg, delivery: 'turn' });
|
|
236
|
+
const intent = intentOf(target, { ...sendArg, delivery: 'turn' }, { attachmentParts: attachmentPartsArg });
|
|
202
237
|
if (intent.type !== 'enqueuePrompt') throw new Error('A new turn must be queued.');
|
|
203
238
|
let markSettled!: () => void;
|
|
204
239
|
let markRunning!: () => void;
|
|
@@ -244,10 +279,13 @@ export class FlexHarnessChatTurnGate<TContext> {
|
|
|
244
279
|
const intentOf = (
|
|
245
280
|
targetArg: { scopeId: string; sessionId: string },
|
|
246
281
|
sendArg: plugins.IHarnessComposerSendDetail,
|
|
247
|
-
|
|
282
|
+
optionsArg: { activeQueueId?: string; attachmentParts?: plugins.TFlexPromptPart[] },
|
|
248
283
|
) => {
|
|
249
284
|
try {
|
|
250
|
-
return plugins.flexHarnessSendIntent(targetArg, sendArg,
|
|
285
|
+
return plugins.flexHarnessSendIntent(targetArg, sendArg, {
|
|
286
|
+
...(optionsArg.activeQueueId ? { activeQueueId: optionsArg.activeQueueId } : {}),
|
|
287
|
+
...(optionsArg.attachmentParts ? { attachmentParts: optionsArg.attachmentParts } : {}),
|
|
288
|
+
});
|
|
251
289
|
} catch (error) {
|
|
252
290
|
throw new FlexHarnessChatRequestError((error as Error)?.message || 'The message could not be read.');
|
|
253
291
|
}
|
|
@@ -114,6 +114,18 @@ export type TFlexHarnessChatHubHarness = Pick<
|
|
|
114
114
|
| 'redoSession'
|
|
115
115
|
>;
|
|
116
116
|
|
|
117
|
+
/** An attachment of a message, as the hub asks the host to turn it into the prompt part the turn sends. */
|
|
118
|
+
export interface IFlexHarnessChatAttachmentRequest<TContext> {
|
|
119
|
+
/** the tab that sent the message */
|
|
120
|
+
tab: IFlexHarnessChatCaller;
|
|
121
|
+
/** the conversation the message goes to: its harness scope and session */
|
|
122
|
+
scopeId: string;
|
|
123
|
+
sessionId: string;
|
|
124
|
+
/** the context the message was sent with, as `readContext` returned it */
|
|
125
|
+
context: TContext;
|
|
126
|
+
attachment: plugins.IHarnessAttachment;
|
|
127
|
+
}
|
|
128
|
+
|
|
117
129
|
export interface IFlexHarnessChatHubOptions<TContext> {
|
|
118
130
|
/**
|
|
119
131
|
* The harness the conversations run on. The host keeps configuring it: its model resolver and
|
|
@@ -127,5 +139,14 @@ export interface IFlexHarnessChatHubOptions<TContext> {
|
|
|
127
139
|
titleOf?(send: plugins.IHarnessComposerSendDetail): string;
|
|
128
140
|
/** Where the tabs' conversations are. Defaults to the root sessions of the tab's scope. */
|
|
129
141
|
directory?: IFlexHarnessChatDirectory;
|
|
142
|
+
/**
|
|
143
|
+
* Turns each attachment of a message into the prompt part the turn sends, before the message is
|
|
144
|
+
* admitted; throw `FlexHarnessChatRequestError` to refuse the message. A host whose tabs upload
|
|
145
|
+
* their files to it first gets the attachments without content, under the ids it gave the
|
|
146
|
+
* uploads, and returns references its harness's `attachmentResolver` reads. The same message
|
|
147
|
+
* may come again after a refusal. Without it, each attachment is sent with the content the
|
|
148
|
+
* composer read, and one without content is refused.
|
|
149
|
+
*/
|
|
150
|
+
attachmentPart?(request: IFlexHarnessChatAttachmentRequest<TContext>): Promise<plugins.TFlexPromptPart>;
|
|
130
151
|
limits?: Partial<IFlexHarnessChatLimits>;
|
|
131
152
|
}
|
|
@@ -53,6 +53,8 @@ The host keeps its harness, models, system prompt, tools and scopes. It authenti
|
|
|
53
53
|
|
|
54
54
|
Without a directory, a tab's conversations are the root sessions of its scope, and the harness's session events update the tabs' lists. A host that runs each conversation in a scope of its own, or keeps records of its own, passes `directory`, an `IFlexHarnessChatDirectory`: it lists the conversations a tab reaches, finds one by its session id (one the tab does not reach is null, and reads as not found), creates one for a message without `sessionId`, renames and deletes. The caller's `scopeId` is then the tab's reach; each conversation names the harness scope its session runs in, and the hub subscribes, runs turns and calls `turnOf` in that scope. A conversation created for a message that then never starts, for instance because its session does not exist, is deleted again through the directory. With a directory, the harness's session events no longer reach the lists: the host calls `hub.publishSessions(reach, { upserts, deletedIds })` for every change, and the tabs of that reach get it as a list push.
|
|
55
55
|
|
|
56
|
+
A message's attachments are sent with the content the composer read, within `maxAttachments` and `maxAttachmentChars`. A host whose tabs upload their files to it first, for files larger than a message may carry, passes `attachmentPart(request)`: before the message is admitted, the hub asks it for the prompt part of each attachment, in order, with the tab, the conversation's `scopeId` and `sessionId` and the message's context. The attachments then arrive as the composer describes them, under the ids the host gave the uploads; the host checks that the tab may send each one into that conversation and returns a reference its harness's `attachmentResolver` reads, with `sizeBytes`, so the transcript shows the file by name and size. Throw `FlexHarnessChatRequestError` to refuse the message; nothing of it is sent, and a conversation created for it is deleted again. The same message may come again after a refusal, and a steer's attachments go through the hook as a new turn's do.
|
|
57
|
+
|
|
56
58
|
Refusals are `FlexHarnessChatRequestError`s with a message for the person in the tab; FlexHarness refusals keep their message. Every other error stays an internal error.
|
|
57
59
|
|
|
58
60
|
## Turns and tools in the tab
|
package/ts_compaction/plugins.ts
CHANGED
|
@@ -5,8 +5,16 @@ export { asSchema, streamText, wrapLanguageModel };
|
|
|
5
5
|
export type { ModelMessage, ToolSet } from 'ai';
|
|
6
6
|
export type { TSmartAiLanguageModel } from '@modelprofile.com/flexharness-models';
|
|
7
7
|
|
|
8
|
-
import {
|
|
8
|
+
import {
|
|
9
|
+
AgentModelCallUsageRecorder,
|
|
10
|
+
createAttachmentDownload,
|
|
11
|
+
endModelStreamAtAbort,
|
|
12
|
+
} from '@modelprofile.com/flexharness-agent';
|
|
9
13
|
|
|
10
|
-
export { AgentModelCallUsageRecorder, endModelStreamAtAbort };
|
|
14
|
+
export { AgentModelCallUsageRecorder, createAttachmentDownload, endModelStreamAtAbort };
|
|
11
15
|
|
|
12
|
-
export type {
|
|
16
|
+
export type {
|
|
17
|
+
IAgentAttachmentResolver,
|
|
18
|
+
ProviderOptions,
|
|
19
|
+
TAgentModelCallUsageReporter,
|
|
20
|
+
} from '@modelprofile.com/flexharness-agent';
|
|
@@ -55,6 +55,12 @@ export interface ICompactMessagesOptions {
|
|
|
55
55
|
* cost more than the history saves. A context compactor passes its options' `tools` through.
|
|
56
56
|
*/
|
|
57
57
|
tools?: plugins.ToolSet;
|
|
58
|
+
/**
|
|
59
|
+
* Reads the attachments the history names by a URL of one of its schemes, so the summary call
|
|
60
|
+
* sends them as the turns do. A context compactor passes its options' `attachmentResolver`
|
|
61
|
+
* through.
|
|
62
|
+
*/
|
|
63
|
+
attachmentResolver?: plugins.IAgentAttachmentResolver;
|
|
58
64
|
}
|
|
59
65
|
|
|
60
66
|
/**
|
|
@@ -134,6 +140,8 @@ export async function compactMessages(
|
|
|
134
140
|
},
|
|
135
141
|
})
|
|
136
142
|
: model;
|
|
143
|
+
// The summary's attempts read each attachment once.
|
|
144
|
+
const download = plugins.createAttachmentDownload(options.attachmentResolver, options.abortSignal);
|
|
137
145
|
// The summary is asked for in the shape of a turn, the tools offered as the turns offer them. A
|
|
138
146
|
// model that calls a tool instead of answering is asked again with tool use turned off, which
|
|
139
147
|
// keeps the tool definitions and so still the cached tools and instructions.
|
|
@@ -148,6 +156,7 @@ export async function compactMessages(
|
|
|
148
156
|
...(tools ? { tools, ...(toolChoice ? { toolChoice } : {}) } : {}),
|
|
149
157
|
...(options.providerOptions ? { providerOptions: options.providerOptions } : {}),
|
|
150
158
|
abortSignal: options.abortSignal,
|
|
159
|
+
...(download ? { experimental_download: download } : {}),
|
|
151
160
|
onError: ({ error }) => {
|
|
152
161
|
streamError ??= error;
|
|
153
162
|
},
|