@modelprofile.com/flexharness 8.4.0 → 8.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.
@@ -3,7 +3,7 @@
3
3
  */
4
4
  export const commitinfo = {
5
5
  name: '@modelprofile.com/flexharness',
6
- version: '8.4.0',
6
+ version: '8.5.0',
7
7
  description: 'The FlexHarness toolbox source repository and shared dependency catalog.'
8
8
  };
9
9
  //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiMDBfY29tbWl0aW5mb19kYXRhLmpzIiwic291cmNlUm9vdCI6IiIsInNvdXJjZXMiOlsiLi4vdHMvMDBfY29tbWl0aW5mb19kYXRhLnRzIl0sIm5hbWVzIjpbXSwibWFwcGluZ3MiOiJBQUFBOztHQUVHO0FBQ0gsTUFBTSxDQUFDLE1BQU0sVUFBVSxHQUFHO0lBQ3hCLElBQUksRUFBRSwrQkFBK0I7SUFDckMsT0FBTyxFQUFFLE9BQU87SUFDaEIsV0FBVyxFQUFFLDBFQUEwRTtDQUN4RixDQUFBIn0=
@@ -1,3 +1,4 @@
1
+ import * as plugins from './plugins.js';
1
2
  import type { ModelMessage } from 'ai';
2
3
  import type { TSmartAiLanguageModel, TSmartAiProviderOptions } from '@modelprofile.com/flexharness-models';
3
4
  /** Options every document request accepts in addition to its model and messages. */
@@ -15,6 +16,14 @@ export interface IDocumentRequestOptions {
15
16
  * exceeded the limit are not rendered.
16
17
  */
17
18
  maxPages?: number;
19
+ /**
20
+ * Called once for every model call of the request, as soon as its usage is known, whatever the
21
+ * request's outcome: `reported` with the provider's usage, or `unreported` with the reason when
22
+ * the call failed, was aborted or its response carried no usage. A retried attempt is a call of
23
+ * its own. Nothing is reported for a request that ends before its model is called. When a request
24
+ * returns, its reported calls sum to its `usage`. Must not throw.
25
+ */
26
+ onUsage?: plugins.TAgentModelCallUsageReporter;
18
27
  }
19
28
  export interface IDocumentOptions extends IDocumentRequestOptions {
20
29
  model: TSmartAiLanguageModel;
@@ -48,6 +48,7 @@ export async function extractTextFromPdfWithUsage(pdfDocument, options) {
48
48
  providerOptions: options.providerOptions,
49
49
  abortSignal: options.abortSignal,
50
50
  maxPages: options.maxPages,
51
+ onUsage: options.onUsage,
51
52
  });
52
53
  }
53
54
  export async function analyzeDocuments(options) {
@@ -79,26 +80,63 @@ export async function analyzeDocumentsWithUsage(options) {
79
80
  }
80
81
  // Filter out empty buffers
81
82
  const validPages = imagePages.filter(page => page && page.length > 0);
82
- const result = await plugins.generateText({
83
- model: options.model,
84
- instructions: options.systemMessage,
85
- messages: [
86
- ...(options.messageHistory ?? []),
87
- {
88
- role: 'user',
89
- content: [
90
- { type: 'text', text: options.userMessage },
91
- ...validPages.map(page => ({
92
- type: 'file',
93
- data: page,
94
- mediaType: 'image/png',
95
- })),
96
- ],
83
+ const onUsage = options.onUsage;
84
+ const modelCalls = onUsage ? new plugins.AgentModelCallUsageRecorder(options.model, onUsage) : null;
85
+ // Each attempt, retries included, is one model call: it starts in the middleware, which also
86
+ // settles an attempt that throws; a call whose response arrives ends in onLanguageModelCallEnd.
87
+ const model = modelCalls
88
+ ? plugins.wrapLanguageModel({
89
+ model: options.model,
90
+ middleware: {
91
+ wrapGenerate: async ({ doGenerate, params }) => {
92
+ modelCalls.start();
93
+ try {
94
+ return await doGenerate();
95
+ }
96
+ catch (error) {
97
+ modelCalls.settle(params.abortSignal?.aborted ? 'aborted' : 'failed');
98
+ throw error;
99
+ }
100
+ },
97
101
  },
98
- ],
99
- providerOptions: options.providerOptions,
100
- abortSignal,
101
- });
102
+ })
103
+ : options.model;
104
+ const messages = [
105
+ ...(options.messageHistory ?? []),
106
+ {
107
+ role: 'user',
108
+ content: [
109
+ { type: 'text', text: options.userMessage },
110
+ ...validPages.map(page => ({
111
+ type: 'file',
112
+ data: page,
113
+ mediaType: 'image/png',
114
+ })),
115
+ ],
116
+ },
117
+ ];
118
+ let result;
119
+ try {
120
+ result = await plugins.generateText({
121
+ model,
122
+ instructions: options.systemMessage,
123
+ messages,
124
+ providerOptions: options.providerOptions,
125
+ abortSignal,
126
+ ...(modelCalls
127
+ ? {
128
+ onLanguageModelCallEnd: ({ modelId, usage: callUsage }) => {
129
+ modelCalls.end(modelId, callUsage);
130
+ },
131
+ }
132
+ : {}),
133
+ });
134
+ modelCalls?.settle('missing');
135
+ }
136
+ catch (error) {
137
+ modelCalls?.settle(abortSignal?.aborted ? 'aborted' : 'failed');
138
+ throw error;
139
+ }
102
140
  const usage = result.totalUsage;
103
141
  const inputTokens = usage.inputTokens ?? 0;
104
142
  const outputTokens = usage.outputTokens ?? 0;
@@ -132,4 +170,4 @@ export async function stopSmartpdf() {
132
170
  }
133
171
  await instance.stop();
134
172
  }
135
- //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiaW5kZXguanMiLCJzb3VyY2VSb290IjoiIiwic291cmNlcyI6WyIuLi90c19kb2N1bWVudC9pbmRleC50cyJdLCJuYW1lcyI6W10sIm1hcHBpbmdzIjoiQUFBQSxPQUFPLEtBQUssT0FBTyxNQUFNLGNBQWMsQ0FBQztBQU14QywrR0FBK0c7QUFDL0csSUFBSSxhQUFhLEdBQThCLElBQUksQ0FBQztBQUVwRCxTQUFTLGNBQWM7SUFDckIsSUFBSSxDQUFDLGFBQWEsRUFBRSxDQUFDO1FBQ25CLE1BQU0sUUFBUSxHQUFHLElBQUksT0FBTyxDQUFDLFFBQVEsQ0FBQyxRQUFRLEVBQUUsQ0FBQztRQUNqRCxNQUFNLEtBQUssR0FBRyxRQUFRLENBQUMsS0FBSyxFQUFFLENBQUMsSUFBSSxDQUFDLEdBQUcsRUFBRSxDQUFDLFFBQVEsQ0FBQyxDQUFDO1FBQ3BELGFBQWEsR0FBRyxLQUFLLENBQUM7UUFDdEIsS0FBSyxDQUFDLEtBQUssQ0FBQyxHQUFHLEVBQUU7WUFDZixJQUFJLGFBQWEsS0FBSyxLQUFLLEVBQUUsQ0FBQztnQkFDNUIsYUFBYSxHQUFHLElBQUksQ0FBQztZQUN2QixDQUFDO1FBQ0gsQ0FBQyxDQUFDLENBQUM7SUFDTCxDQUFDO0lBQ0QsT0FBTyxhQUFhLENBQUM7QUFDdkIsQ0FBQztBQXlERCxvR0FBb0c7QUFDcEcsTUFBTSxPQUFPLHNCQUF1QixTQUFRLEtBQUs7SUFDL0MsMEdBQTBHO0lBQzFGLGFBQWEsQ0FBUztJQUN0QixRQUFRLENBQVM7SUFFakMsWUFBWSxPQUF1QztRQUNqRCxNQUFNLE9BQU8sR0FBRyxPQUFPLENBQUMsYUFBYSxLQUFLLENBQUM7WUFDekMsQ0FBQyxDQUFDLG9CQUFvQixPQUFPLENBQUMsYUFBYSxRQUFRO1lBQ25ELENBQUMsQ0FBQyxPQUFPLENBQUMsb0JBQW9CO2dCQUM1QixDQUFDLENBQUMsc0JBQXNCLE9BQU8sQ0FBQyxhQUFhLFFBQVE7Z0JBQ3JELENBQUMsQ0FBQywrQkFBK0IsT0FBTyxDQUFDLGFBQWEsUUFBUSxDQUFD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
173
+ //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiaW5kZXguanMiLCJzb3VyY2VSb290IjoiIiwic291cmNlcyI6WyIuLi90c19kb2N1bWVudC9pbmRleC50cyJdLCJuYW1lcyI6W10sIm1hcHBpbmdzIjoiQUFBQSxPQUFPLEtBQUssT0FBTyxNQUFNLGNBQWMsQ0FBQztBQU14QywrR0FBK0c7QUFDL0csSUFBSSxhQUFhLEdBQThCLElBQUksQ0FBQztBQUVwRCxTQUFTLGNBQWM7SUFDckIsSUFBSSxDQUFDLGFBQWEsRUFBRSxDQUFDO1FBQ25CLE1BQU0sUUFBUSxHQUFHLElBQUksT0FBTyxDQUFDLFFBQVEsQ0FBQyxRQUFRLEVBQUUsQ0FBQztRQUNqRCxNQUFNLEtBQUssR0FBRyxRQUFRLENBQUMsS0FBSyxFQUFFLENBQUMsSUFBSSxDQUFDLEdBQUcsRUFBRSxDQUFDLFFBQVEsQ0FBQyxDQUFDO1FBQ3BELGFBQWEsR0FBRyxLQUFLLENBQUM7UUFDdEIsS0FBSyxDQUFDLEtBQUssQ0FBQyxHQUFHLEVBQUU7WUFDZixJQUFJLGFBQWEsS0FBSyxLQUFLLEVBQUUsQ0FBQztnQkFDNUIsYUFBYSxHQUFHLElBQUksQ0FBQztZQUN2QixDQUFDO1FBQ0gsQ0FBQyxDQUFDLENBQUM7SUFDTCxDQUFDO0lBQ0QsT0FBTyxhQUFhLENBQUM7QUFDdkIsQ0FBQztBQWlFRCxvR0FBb0c7QUFDcEcsTUFBTSxPQUFPLHNCQUF1QixTQUFRLEtBQUs7SUFDL0MsMEdBQTBHO0lBQzFGLGFBQWEsQ0FBUztJQUN0QixRQUFRLENBQVM7SUFFakMsWUFBWSxPQUF1QztRQUNqRCxNQUFNLE9BQU8sR0FBRyxPQUFPLENBQUMsYUFBYSxLQUFLLENBQUM7WUFDekMsQ0FBQyxDQUFDLG9CQUFvQixPQUFPLENBQUMsYUFBYSxRQUFRO1lBQ25ELENBQUMsQ0FBQyxPQUFPLENBQUMsb0JBQW9CO2dCQUM1QixDQUFDLENBQUMsc0JBQXNCLE9BQU8sQ0FBQyxhQUFhLFFBQVE7Z0JBQ3JELENBQUMsQ0FBQywrQkFBK0IsT0FBTyxDQUFDLGFBQWEsUUFBUSxDQUFD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
@@ -1,3 +1,5 @@
1
- import { generateText } from 'ai';
1
+ import { generateText, wrapLanguageModel } from 'ai';
2
2
  import * as smartpdf from '@push.rocks/smartpdf';
3
- export { generateText, smartpdf };
3
+ import { AgentModelCallUsageRecorder } from '@modelprofile.com/flexharness-agent';
4
+ export { generateText, wrapLanguageModel, smartpdf, AgentModelCallUsageRecorder };
5
+ export type { TAgentModelCallUsageReporter } from '@modelprofile.com/flexharness-agent';
@@ -1,4 +1,5 @@
1
- import { generateText } from 'ai';
1
+ import { generateText, wrapLanguageModel } from 'ai';
2
2
  import * as smartpdf from '@push.rocks/smartpdf';
3
- export { generateText, smartpdf };
4
- //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoicGx1Z2lucy5qcyIsInNvdXJjZVJvb3QiOiIiLCJzb3VyY2VzIjpbIi4uL3RzX2RvY3VtZW50L3BsdWdpbnMudHMiXSwibmFtZXMiOltdLCJtYXBwaW5ncyI6IkFBQUEsT0FBTyxFQUFFLFlBQVksRUFBRSxNQUFNLElBQUksQ0FBQztBQUNsQyxPQUFPLEtBQUssUUFBUSxNQUFNLHNCQUFzQixDQUFDO0FBQ2pELE9BQU8sRUFBRSxZQUFZLEVBQUUsUUFBUSxFQUFFLENBQUMifQ==
3
+ import { AgentModelCallUsageRecorder } from '@modelprofile.com/flexharness-agent';
4
+ export { generateText, wrapLanguageModel, smartpdf, AgentModelCallUsageRecorder };
5
+ //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoicGx1Z2lucy5qcyIsInNvdXJjZVJvb3QiOiIiLCJzb3VyY2VzIjpbIi4uL3RzX2RvY3VtZW50L3BsdWdpbnMudHMiXSwibmFtZXMiOltdLCJtYXBwaW5ncyI6IkFBQUEsT0FBTyxFQUFFLFlBQVksRUFBRSxpQkFBaUIsRUFBRSxNQUFNLElBQUksQ0FBQztBQUNyRCxPQUFPLEtBQUssUUFBUSxNQUFNLHNCQUFzQixDQUFDO0FBQ2pELE9BQU8sRUFBRSwyQkFBMkIsRUFBRSxNQUFNLHFDQUFxQyxDQUFDO0FBQ2xGLE9BQU8sRUFBRSxZQUFZLEVBQUUsaUJBQWlCLEVBQUUsUUFBUSxFQUFFLDJCQUEyQixFQUFFLENBQUMifQ==
package/package.json CHANGED
@@ -2,7 +2,7 @@
2
2
  "author": "Task Venture Capital GmbH",
3
3
  "license": "MIT",
4
4
  "name": "@modelprofile.com/flexharness",
5
- "version": "8.4.0",
5
+ "version": "8.5.0",
6
6
  "type": "module",
7
7
  "description": "Managed agent sessions with tools, MCP, chat, OCR, documents and browser control through focused subpath imports.",
8
8
  "main": "./dist_ts/index.js",
@@ -62,8 +62,8 @@
62
62
  }
63
63
  },
64
64
  "dependencies": {
65
- "@modelprofile.com/flexharness-agent": "8.4.0",
66
- "@modelprofile.com/flexharness-models": "8.4.0",
65
+ "@modelprofile.com/flexharness-agent": "8.5.0",
66
+ "@modelprofile.com/flexharness-models": "8.5.0",
67
67
  "@push.rocks/smartrequest": "^5.0.1",
68
68
  "ai": "^7.0.79",
69
69
  "zod": "^4.4.1"
@@ -3,6 +3,6 @@
3
3
  */
4
4
  export const commitinfo = {
5
5
  name: '@modelprofile.com/flexharness',
6
- version: '8.4.0',
6
+ version: '8.5.0',
7
7
  description: 'The FlexHarness toolbox source repository and shared dependency catalog.'
8
8
  }
@@ -36,6 +36,14 @@ export interface IDocumentRequestOptions {
36
36
  * exceeded the limit are not rendered.
37
37
  */
38
38
  maxPages?: number;
39
+ /**
40
+ * Called once for every model call of the request, as soon as its usage is known, whatever the
41
+ * request's outcome: `reported` with the provider's usage, or `unreported` with the reason when
42
+ * the call failed, was aborted or its response carried no usage. A retried attempt is a call of
43
+ * its own. Nothing is reported for a request that ends before its model is called. When a request
44
+ * returns, its reported calls sum to its `usage`. Must not throw.
45
+ */
46
+ onUsage?: plugins.TAgentModelCallUsageReporter;
39
47
  }
40
48
 
41
49
  export interface IDocumentOptions extends IDocumentRequestOptions {
@@ -120,6 +128,7 @@ export async function extractTextFromPdfWithUsage(
120
128
  providerOptions: options.providerOptions,
121
129
  abortSignal: options.abortSignal,
122
130
  maxPages: options.maxPages,
131
+ onUsage: options.onUsage,
123
132
  });
124
133
  }
125
134
 
@@ -156,26 +165,61 @@ export async function analyzeDocumentsWithUsage(options: IDocumentOptions): Prom
156
165
  // Filter out empty buffers
157
166
  const validPages = imagePages.filter(page => page && page.length > 0);
158
167
 
159
- const result = await plugins.generateText({
160
- model: options.model,
161
- instructions: options.systemMessage,
162
- messages: [
163
- ...(options.messageHistory ?? []),
164
- {
165
- role: 'user',
166
- content: [
167
- { type: 'text', text: options.userMessage },
168
- ...validPages.map(page => ({
169
- type: 'file' as const,
170
- data: page,
171
- mediaType: 'image/png' as const,
172
- })),
173
- ],
174
- },
175
- ],
176
- providerOptions: options.providerOptions,
177
- abortSignal,
178
- });
168
+ const onUsage = options.onUsage;
169
+ const modelCalls = onUsage ? new plugins.AgentModelCallUsageRecorder(options.model, onUsage) : null;
170
+ // Each attempt, retries included, is one model call: it starts in the middleware, which also
171
+ // settles an attempt that throws; a call whose response arrives ends in onLanguageModelCallEnd.
172
+ const model = modelCalls
173
+ ? plugins.wrapLanguageModel({
174
+ model: options.model,
175
+ middleware: {
176
+ wrapGenerate: async ({ doGenerate, params }) => {
177
+ modelCalls.start();
178
+ try {
179
+ return await doGenerate();
180
+ } catch (error) {
181
+ modelCalls.settle(params.abortSignal?.aborted ? 'aborted' : 'failed');
182
+ throw error;
183
+ }
184
+ },
185
+ },
186
+ })
187
+ : options.model;
188
+ const messages: ModelMessage[] = [
189
+ ...(options.messageHistory ?? []),
190
+ {
191
+ role: 'user',
192
+ content: [
193
+ { type: 'text', text: options.userMessage },
194
+ ...validPages.map(page => ({
195
+ type: 'file' as const,
196
+ data: page,
197
+ mediaType: 'image/png' as const,
198
+ })),
199
+ ],
200
+ },
201
+ ];
202
+ let result: Awaited<ReturnType<typeof plugins.generateText>>;
203
+ try {
204
+ result = await plugins.generateText({
205
+ model,
206
+ instructions: options.systemMessage,
207
+ messages,
208
+ providerOptions: options.providerOptions,
209
+ abortSignal,
210
+ ...(modelCalls
211
+ ? {
212
+ onLanguageModelCallEnd: ({ modelId, usage: callUsage }) => {
213
+ modelCalls.end(modelId, callUsage);
214
+ },
215
+ }
216
+ : {}),
217
+ });
218
+ modelCalls?.settle('missing');
219
+ } catch (error) {
220
+ modelCalls?.settle(abortSignal?.aborted ? 'aborted' : 'failed');
221
+ throw error;
222
+ }
179
223
  const usage = result.totalUsage;
180
224
  const inputTokens = usage.inputTokens ?? 0;
181
225
  const outputTokens = usage.outputTokens ?? 0;
@@ -1,3 +1,5 @@
1
- import { generateText } from 'ai';
1
+ import { generateText, wrapLanguageModel } from 'ai';
2
2
  import * as smartpdf from '@push.rocks/smartpdf';
3
- export { generateText, smartpdf };
3
+ import { AgentModelCallUsageRecorder } from '@modelprofile.com/flexharness-agent';
4
+ export { generateText, wrapLanguageModel, smartpdf, AgentModelCallUsageRecorder };
5
+ export type { TAgentModelCallUsageReporter } from '@modelprofile.com/flexharness-agent';
@@ -23,11 +23,12 @@ Supply a model from your selected FlexHarness provider. `extractTextFromPdf` rep
23
23
 
24
24
  `analyzeDocuments({ model, userMessage, pdfDocuments, systemMessage?, messageHistory? })` supports general analysis across multiple PDFs. This component owns a shared SmartPDF instance; call `stopSmartpdf()` when all document work has finished. Image-only OCR is available independently in `flexharness/ocr`.
25
25
 
26
- Both functions accept three request options:
26
+ Both functions accept four request options:
27
27
 
28
28
  - `providerOptions` is passed to the model call unchanged, for example `{ openai: { store: false } }` so the provider does not keep the request as application state.
29
29
  - `abortSignal` is checked before each document is rendered and before the model call, and is passed to the model call. SmartPDF cannot stop a document it is already rendering, so that render finishes first.
30
30
  - `maxPages` is the most rendered pages one request may send to the model. A request with more pages rejects with `DocumentPageLimitError` (`renderedPages`, `maxPages`) before the model is called, and documents after the one that exceeded the limit are not rendered.
31
+ - `onUsage` receives the usage of each model call of the request, also of a request that fails or is aborted; see [Usage of a request that fails or is aborted](#usage-of-a-request-that-fails-or-is-aborted).
31
32
 
32
33
  `extractTextFromPdfWithUsage` and `analyzeDocumentsWithUsage` take the same arguments and return `{ text, usage, pageCount }`. `usage` has the shape of an agent run's usage (`inputTokens`, `outputTokens`, `totalTokens`, `cacheReadTokens`, `cacheWriteTokens`), summed over the request's model steps, with usage the provider leaves out counted as zero; `pageCount` is the number of rendered pages sent to the model.
33
34
 
@@ -49,6 +50,28 @@ try {
49
50
  }
50
51
  ```
51
52
 
53
+ ### Usage of a request that fails or is aborted
54
+
55
+ A request that throws returns no `usage`. `onUsage` reports each model call of a request once, as soon as its usage is known, whatever the outcome, with the events of `AgentModelCallUsageRecorder` (the type is `TFlexModelCallUsage` from `@modelprofile.com/flexharness`):
56
+
57
+ ```typescript
58
+ import type { TFlexModelCallUsage } from '@modelprofile.com/flexharness';
59
+
60
+ const calls: TFlexModelCallUsage[] = [];
61
+ try {
62
+ await extractTextFromPdfWithUsage(pdfBytes, { model, abortSignal, onUsage: (call) => calls.push(call) });
63
+ } finally {
64
+ const reported = calls.filter((call) => call.status === 'reported');
65
+ const complete = reported.length === calls.length; // an unreported call used an unknown number of tokens
66
+ }
67
+ ```
68
+
69
+ - `status: 'reported'`: the provider reported the call's `usage`; `requestedModelId` is the model's id and `responseModelId` the id the response named.
70
+ - `status: 'unreported'`: the call `failed`, was `aborted`, or its response carried no usage (`missing`). The provider may still have consumed tokens for it.
71
+ - Every attempt is a call of its own: a call retried after a failure reports the failed attempt and the retry.
72
+ - A request that ends before its model is called (an abort during rendering, `DocumentPageLimitError`) reports nothing.
73
+ - When a request returns, its reported calls sum to its `usage`; count one or the other. `onUsage` must not throw.
74
+
52
75
  ## Issue Reporting and Security
53
76
 
54
77
  Report issues and security concerns through [community.foss.global](https://community.foss.global).