@modelprofile.com/flexharness 8.4.0 → 9.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.
@@ -3,7 +3,7 @@
3
3
  */
4
4
  export const commitinfo = {
5
5
  name: '@modelprofile.com/flexharness',
6
- version: '8.4.0',
6
+ version: '9.0.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. */
@@ -5,16 +6,28 @@ export interface IDocumentRequestOptions {
5
6
  /** Passed to the model call unchanged, for example `{ openai: { store: false } }`. */
6
7
  providerOptions?: TSmartAiProviderOptions;
7
8
  /**
8
- * Checked before each document is rendered and before the model call, and passed to the model
9
- * call. SmartPDF cannot stop a document it is rendering, so that render finishes first.
9
+ * Checked before each document is rendered and before the model call, and passed to both the
10
+ * render and the model call: an abort stops a document that is rendering. The request rejects
11
+ * with the signal's reason.
10
12
  */
11
13
  abortSignal?: AbortSignal;
12
14
  /**
13
- * The most rendered pages one request may send to the model. A request with more pages rejects
14
- * with `DocumentPageLimitError` before the model is called; documents after the one that
15
- * exceeded the limit are not rendered.
15
+ * The most rendered pages one request may send to the model, counted over all its documents. A
16
+ * request with more pages rejects with `DocumentPageLimitError` before the model is called. Each
17
+ * document is rendered only when its page count fits the pages the request has left, so the
18
+ * document that exceeds the limit and every document after it are not rendered. The exception is
19
+ * a one-page document that comes after the request's pages are used up: it renders and is then
20
+ * refused.
16
21
  */
17
22
  maxPages?: number;
23
+ /**
24
+ * Called once for every model call of the request, as soon as its usage is known, whatever the
25
+ * request's outcome: `reported` with the provider's usage, or `unreported` with the reason when
26
+ * the call failed, was aborted or its response carried no usage. A retried attempt is a call of
27
+ * its own. Nothing is reported for a request that ends before its model is called. When a request
28
+ * returns, its reported calls sum to its `usage`. Must not throw.
29
+ */
30
+ onUsage?: plugins.TAgentModelCallUsageReporter;
18
31
  }
19
32
  export interface IDocumentOptions extends IDocumentRequestOptions {
20
33
  model: TSmartAiLanguageModel;
@@ -44,17 +57,28 @@ export interface IDocumentAnalysisResult {
44
57
  pageCount: number;
45
58
  }
46
59
  export interface IDocumentPageLimitErrorDetails {
60
+ /** The pages of the documents up to and including the one that exceeded the limit. */
47
61
  renderedPages: number;
48
62
  maxPages: number;
49
63
  documentCount: number;
64
+ /** Zero-based position, in the request's documents, of the document that exceeded the limit. */
65
+ documentIndex: number;
66
+ /** Whether the pages of every document of the request were counted. */
50
67
  allDocumentsRendered: boolean;
51
68
  }
52
- /** A request whose documents render to more pages than its `maxPages`; the model was not called. */
69
+ /** A request whose documents have more pages than its `maxPages`; the model was not called. */
53
70
  export declare class DocumentPageLimitError extends Error {
54
- /** Pages rendered before the request stopped; with several documents, later documents are not counted. */
71
+ /**
72
+ * The pages of the documents up to and including the one that exceeded the limit. That document's
73
+ * pages are counted but not rendered, except a one-page document that came after the request's
74
+ * pages were used up, which rendered and was then refused. Documents after it are neither counted
75
+ * nor rendered.
76
+ */
55
77
  readonly renderedPages: number;
56
78
  readonly maxPages: number;
57
- constructor(details: IDocumentPageLimitErrorDetails);
79
+ /** Zero-based position, in the request's documents, of the document that exceeded the limit. */
80
+ readonly documentIndex: number;
81
+ constructor(details: IDocumentPageLimitErrorDetails, options?: ErrorOptions);
58
82
  }
59
83
  /** The model-based PDF OCR recipe formerly provided by SmartOCR. */
60
84
  export declare function extractTextFromPdf(pdfDocument: Uint8Array, options: IPdfTextExtractionOptions): Promise<string>;
@@ -14,21 +14,29 @@ function ensureSmartpdf() {
14
14
  }
15
15
  return smartpdfStart;
16
16
  }
17
- /** A request whose documents render to more pages than its `maxPages`; the model was not called. */
17
+ /** A request whose documents have more pages than its `maxPages`; the model was not called. */
18
18
  export class DocumentPageLimitError extends Error {
19
- /** Pages rendered before the request stopped; with several documents, later documents are not counted. */
19
+ /**
20
+ * The pages of the documents up to and including the one that exceeded the limit. That document's
21
+ * pages are counted but not rendered, except a one-page document that came after the request's
22
+ * pages were used up, which rendered and was then refused. Documents after it are neither counted
23
+ * nor rendered.
24
+ */
20
25
  renderedPages;
21
26
  maxPages;
22
- constructor(details) {
27
+ /** Zero-based position, in the request's documents, of the document that exceeded the limit. */
28
+ documentIndex;
29
+ constructor(details, options) {
23
30
  const subject = details.documentCount === 1
24
31
  ? `The document has ${details.renderedPages} pages`
25
32
  : details.allDocumentsRendered
26
33
  ? `The documents have ${details.renderedPages} pages`
27
34
  : `The documents have at least ${details.renderedPages} pages`;
28
- super(`${subject}, more than the ${details.maxPages} allowed.`);
35
+ super(`${subject}, more than the ${details.maxPages} allowed.`, options);
29
36
  this.name = 'DocumentPageLimitError';
30
37
  this.renderedPages = details.renderedPages;
31
38
  this.maxPages = details.maxPages;
39
+ this.documentIndex = details.documentIndex;
32
40
  }
33
41
  }
34
42
  const DEFAULT_OCR_SYSTEM_MESSAGE = 'You are a precise OCR engine. Extract text faithfully and do not summarize.';
@@ -48,6 +56,7 @@ export async function extractTextFromPdfWithUsage(pdfDocument, options) {
48
56
  providerOptions: options.providerOptions,
49
57
  abortSignal: options.abortSignal,
50
58
  maxPages: options.maxPages,
59
+ onUsage: options.onUsage,
51
60
  });
52
61
  }
53
62
  export async function analyzeDocuments(options) {
@@ -65,40 +74,92 @@ export async function analyzeDocumentsWithUsage(options) {
65
74
  for (const [index, doc] of options.pdfDocuments.entries()) {
66
75
  const pdf = await ensureSmartpdf();
67
76
  abortSignal?.throwIfAborted();
68
- const pages = await pdf.convertPDFToPngBytes(doc);
77
+ const limitDetails = (renderedPages, limit) => ({
78
+ renderedPages,
79
+ maxPages: limit,
80
+ documentCount: options.pdfDocuments.length,
81
+ documentIndex: index,
82
+ allDocumentsRendered: index === options.pdfDocuments.length - 1,
83
+ });
84
+ // SmartPDF limits one document, so each render gets the pages the request has left. It takes
85
+ // no limit of zero: when the earlier documents used up the request's pages, the smallest limit
86
+ // lets the document's page count be known, and a document of one page renders and is refused below.
87
+ const renderMaxPages = maxPages === undefined ? undefined : Math.max(maxPages - imagePages.length, 1);
88
+ let pages;
89
+ try {
90
+ pages = await pdf.convertPDFToPngBytes(doc, { signal: abortSignal, maxPages: renderMaxPages });
91
+ }
92
+ catch (error) {
93
+ if (maxPages !== undefined && error instanceof plugins.smartpdf.SmartPdfPageLimitError) {
94
+ throw new DocumentPageLimitError(limitDetails(imagePages.length + error.pageCount, maxPages), { cause: error });
95
+ }
96
+ throw error;
97
+ }
69
98
  abortSignal?.throwIfAborted();
70
99
  imagePages.push(...pages);
71
100
  if (maxPages !== undefined && imagePages.length > maxPages) {
72
- throw new DocumentPageLimitError({
73
- renderedPages: imagePages.length,
74
- maxPages,
75
- documentCount: options.pdfDocuments.length,
76
- allDocumentsRendered: index === options.pdfDocuments.length - 1,
77
- });
101
+ throw new DocumentPageLimitError(limitDetails(imagePages.length, maxPages));
78
102
  }
79
103
  }
80
104
  // Filter out empty buffers
81
105
  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
- ],
106
+ const onUsage = options.onUsage;
107
+ const modelCalls = onUsage ? new plugins.AgentModelCallUsageRecorder(options.model, onUsage) : null;
108
+ // Each attempt, retries included, is one model call: it starts in the middleware, which also
109
+ // settles an attempt that throws; a call whose response arrives ends in onLanguageModelCallEnd.
110
+ const model = modelCalls
111
+ ? plugins.wrapLanguageModel({
112
+ model: options.model,
113
+ middleware: {
114
+ wrapGenerate: async ({ doGenerate, params }) => {
115
+ modelCalls.start();
116
+ try {
117
+ return await doGenerate();
118
+ }
119
+ catch (error) {
120
+ modelCalls.settle(params.abortSignal?.aborted ? 'aborted' : 'failed');
121
+ throw error;
122
+ }
123
+ },
97
124
  },
98
- ],
99
- providerOptions: options.providerOptions,
100
- abortSignal,
101
- });
125
+ })
126
+ : options.model;
127
+ const messages = [
128
+ ...(options.messageHistory ?? []),
129
+ {
130
+ role: 'user',
131
+ content: [
132
+ { type: 'text', text: options.userMessage },
133
+ ...validPages.map(page => ({
134
+ type: 'file',
135
+ data: page,
136
+ mediaType: 'image/png',
137
+ })),
138
+ ],
139
+ },
140
+ ];
141
+ let result;
142
+ try {
143
+ result = await plugins.generateText({
144
+ model,
145
+ instructions: options.systemMessage,
146
+ messages,
147
+ providerOptions: options.providerOptions,
148
+ abortSignal,
149
+ ...(modelCalls
150
+ ? {
151
+ onLanguageModelCallEnd: ({ modelId, usage: callUsage }) => {
152
+ modelCalls.end(modelId, callUsage);
153
+ },
154
+ }
155
+ : {}),
156
+ });
157
+ modelCalls?.settle('missing');
158
+ }
159
+ catch (error) {
160
+ modelCalls?.settle(abortSignal?.aborted ? 'aborted' : 'failed');
161
+ throw error;
162
+ }
102
163
  const usage = result.totalUsage;
103
164
  const inputTokens = usage.inputTokens ?? 0;
104
165
  const outputTokens = usage.outputTokens ?? 0;
@@ -132,4 +193,4 @@ export async function stopSmartpdf() {
132
193
  }
133
194
  await instance.stop();
134
195
  }
135
- //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiaW5kZXguanMiLCJzb3VyY2VSb290IjoiIiwic291cmNlcyI6WyIuLi90c19kb2N1bWVudC9pbmRleC50cyJdLCJuYW1lcyI6W10sIm1hcHBpbmdzIjoiQUFBQSxPQUFPLEtBQUssT0FBTyxNQUFNLGNBQWMsQ0FBQztBQU14QywrR0FBK0c7QUFDL0csSUFBSSxhQUFhLEdBQThCLElBQUksQ0FBQztBQUVwRCxTQUFTLGNBQWM7SUFDckIsSUFBSSxDQUFDLGFBQWEsRUFBRSxDQUFDO1FBQ25CLE1BQU0sUUFBUSxHQUFHLElBQUksT0FBTyxDQUFDLFFBQVEsQ0FBQyxRQUFRLEVBQUUsQ0FBQztRQUNqRCxNQUFNLEtBQUssR0FBRyxRQUFRLENBQUMsS0FBSyxFQUFFLENBQUMsSUFBSSxDQUFDLEdBQUcsRUFBRSxDQUFDLFFBQVEsQ0FBQyxDQUFDO1FBQ3BELGFBQWEsR0FBRyxLQUFLLENBQUM7UUFDdEIsS0FBSyxDQUFDLEtBQUssQ0FBQyxHQUFHLEVBQUU7WUFDZixJQUFJLGFBQWEsS0FBSyxLQUFLLEVBQUUsQ0FBQztnQkFDNUIsYUFBYSxHQUFHLElBQUksQ0FBQztZQUN2QixDQUFDO1FBQ0gsQ0FBQyxDQUFDLENBQUM7SUFDTCxDQUFDO0lBQ0QsT0FBTyxhQUFhLENBQUM7QUFDdkIsQ0FBQztBQXlERCxvR0FBb0c7QUFDcEcsTUFBTSxPQUFPLHNCQUF1QixTQUFRLEtBQUs7SUFDL0MsMEdBQTBHO0lBQzFGLGFBQWEsQ0FBUztJQUN0QixRQUFRLENBQVM7SUFFakMsWUFBWSxPQUF1QztRQUNqRCxNQUFNLE9BQU8sR0FBRyxPQUFPLENBQUMsYUFBYSxLQUFLLENBQUM7WUFDekMsQ0FBQyxDQUFDLG9CQUFvQixPQUFPLENBQUMsYUFBYSxRQUFRO1lBQ25ELENBQUMsQ0FBQyxPQUFPLENBQUMsb0JBQW9CO2dCQUM1QixDQUFDLENBQUMsc0JBQXNCLE9BQU8sQ0FBQyxhQUFhLFFBQVE7Z0JBQ3JELENBQUMsQ0FBQywrQkFBK0IsT0FBTyxDQUFDLGFBQWEsUUFBUSxDQUFD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
196
+ //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiaW5kZXguanMiLCJzb3VyY2VSb290IjoiIiwic291cmNlcyI6WyIuLi90c19kb2N1bWVudC9pbmRleC50cyJdLCJuYW1lcyI6W10sIm1hcHBpbmdzIjoiQUFBQSxPQUFPLEtBQUssT0FBTyxNQUFNLGNBQWMsQ0FBQztBQU14QywrR0FBK0c7QUFDL0csSUFBSSxhQUFhLEdBQThCLElBQUksQ0FBQztBQUVwRCxTQUFTLGNBQWM7SUFDckIsSUFBSSxDQUFDLGFBQWEsRUFBRSxDQUFDO1FBQ25CLE1BQU0sUUFBUSxHQUFHLElBQUksT0FBTyxDQUFDLFFBQVEsQ0FBQyxRQUFRLEVBQUUsQ0FBQztRQUNqRCxNQUFNLEtBQUssR0FBRyxRQUFRLENBQUMsS0FBSyxFQUFFLENBQUMsSUFBSSxDQUFDLEdBQUcsRUFBRSxDQUFDLFFBQVEsQ0FBQyxDQUFDO1FBQ3BELGFBQWEsR0FBRyxLQUFLLENBQUM7UUFDdEIsS0FBSyxDQUFDLEtBQUssQ0FBQyxHQUFHLEVBQUU7WUFDZixJQUFJLGFBQWEsS0FBSyxLQUFLLEVBQUUsQ0FBQztnQkFDNUIsYUFBYSxHQUFHLElBQUksQ0FBQztZQUN2QixDQUFDO1FBQ0gsQ0FBQyxDQUFDLENBQUM7SUFDTCxDQUFDO0lBQ0QsT0FBTyxhQUFhLENBQUM7QUFDdkIsQ0FBQztBQXlFRCwrRkFBK0Y7QUFDL0YsTUFBTSxPQUFPLHNCQUF1QixTQUFRLEtBQUs7SUFDL0M7Ozs7O09BS0c7SUFDYSxhQUFhLENBQVM7SUFDdEIsUUFBUSxDQUFTO0lBQ2pDLGdHQUFnRztJQUNoRixhQUFhLENBQVM7SUFFdEMsWUFBWSxPQUF1QyxFQUFFLE9BQXNCO1FBQ3pFLE1BQU0sT0FBTyxHQUFHLE9BQU8sQ0FBQyxhQUFhLEtBQUssQ0FBQztZQUN6QyxDQUFDLENBQUMsb0JBQW9CLE9BQU8sQ0FBQyxhQUFhLFFBQVE7WUFDbkQsQ0FBQyxDQUFDLE9BQU8sQ0FBQyxvQkFBb0I7Z0JBQzVCLENBQUMsQ0FBQyxzQkFBc0IsT0FBTyxDQUFDLGFBQWEsUUFBUTtnQkFDckQs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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": "9.0.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,15 +62,15 @@
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": "9.0.0",
66
+ "@modelprofile.com/flexharness-models": "9.0.0",
67
67
  "@push.rocks/smartrequest": "^5.0.1",
68
68
  "ai": "^7.0.79",
69
69
  "zod": "^4.4.1"
70
70
  },
71
71
  "peerDependencies": {
72
72
  "@modelcontextprotocol/sdk": "^1.29.0",
73
- "@push.rocks/smartpdf": "^5.0.1",
73
+ "@push.rocks/smartpdf": "^6.0.0",
74
74
  "puppeteer": "^24.42.0",
75
75
  "ink": "^6.8.0",
76
76
  "ink-text-input": "^6.0.0",
@@ -3,6 +3,6 @@
3
3
  */
4
4
  export const commitinfo = {
5
5
  name: '@modelprofile.com/flexharness',
6
- version: '8.4.0',
6
+ version: '9.0.0',
7
7
  description: 'The FlexHarness toolbox source repository and shared dependency catalog.'
8
8
  }
@@ -26,16 +26,28 @@ export interface IDocumentRequestOptions {
26
26
  /** Passed to the model call unchanged, for example `{ openai: { store: false } }`. */
27
27
  providerOptions?: TSmartAiProviderOptions;
28
28
  /**
29
- * Checked before each document is rendered and before the model call, and passed to the model
30
- * call. SmartPDF cannot stop a document it is rendering, so that render finishes first.
29
+ * Checked before each document is rendered and before the model call, and passed to both the
30
+ * render and the model call: an abort stops a document that is rendering. The request rejects
31
+ * with the signal's reason.
31
32
  */
32
33
  abortSignal?: AbortSignal;
33
34
  /**
34
- * The most rendered pages one request may send to the model. A request with more pages rejects
35
- * with `DocumentPageLimitError` before the model is called; documents after the one that
36
- * exceeded the limit are not rendered.
35
+ * The most rendered pages one request may send to the model, counted over all its documents. A
36
+ * request with more pages rejects with `DocumentPageLimitError` before the model is called. Each
37
+ * document is rendered only when its page count fits the pages the request has left, so the
38
+ * document that exceeds the limit and every document after it are not rendered. The exception is
39
+ * a one-page document that comes after the request's pages are used up: it renders and is then
40
+ * refused.
37
41
  */
38
42
  maxPages?: number;
43
+ /**
44
+ * Called once for every model call of the request, as soon as its usage is known, whatever the
45
+ * request's outcome: `reported` with the provider's usage, or `unreported` with the reason when
46
+ * the call failed, was aborted or its response carried no usage. A retried attempt is a call of
47
+ * its own. Nothing is reported for a request that ends before its model is called. When a request
48
+ * returns, its reported calls sum to its `usage`. Must not throw.
49
+ */
50
+ onUsage?: plugins.TAgentModelCallUsageReporter;
39
51
  }
40
52
 
41
53
  export interface IDocumentOptions extends IDocumentRequestOptions {
@@ -70,28 +82,40 @@ export interface IDocumentAnalysisResult {
70
82
  }
71
83
 
72
84
  export interface IDocumentPageLimitErrorDetails {
85
+ /** The pages of the documents up to and including the one that exceeded the limit. */
73
86
  renderedPages: number;
74
87
  maxPages: number;
75
88
  documentCount: number;
89
+ /** Zero-based position, in the request's documents, of the document that exceeded the limit. */
90
+ documentIndex: number;
91
+ /** Whether the pages of every document of the request were counted. */
76
92
  allDocumentsRendered: boolean;
77
93
  }
78
94
 
79
- /** A request whose documents render to more pages than its `maxPages`; the model was not called. */
95
+ /** A request whose documents have more pages than its `maxPages`; the model was not called. */
80
96
  export class DocumentPageLimitError extends Error {
81
- /** Pages rendered before the request stopped; with several documents, later documents are not counted. */
97
+ /**
98
+ * The pages of the documents up to and including the one that exceeded the limit. That document's
99
+ * pages are counted but not rendered, except a one-page document that came after the request's
100
+ * pages were used up, which rendered and was then refused. Documents after it are neither counted
101
+ * nor rendered.
102
+ */
82
103
  public readonly renderedPages: number;
83
104
  public readonly maxPages: number;
105
+ /** Zero-based position, in the request's documents, of the document that exceeded the limit. */
106
+ public readonly documentIndex: number;
84
107
 
85
- constructor(details: IDocumentPageLimitErrorDetails) {
108
+ constructor(details: IDocumentPageLimitErrorDetails, options?: ErrorOptions) {
86
109
  const subject = details.documentCount === 1
87
110
  ? `The document has ${details.renderedPages} pages`
88
111
  : details.allDocumentsRendered
89
112
  ? `The documents have ${details.renderedPages} pages`
90
113
  : `The documents have at least ${details.renderedPages} pages`;
91
- super(`${subject}, more than the ${details.maxPages} allowed.`);
114
+ super(`${subject}, more than the ${details.maxPages} allowed.`, options);
92
115
  this.name = 'DocumentPageLimitError';
93
116
  this.renderedPages = details.renderedPages;
94
117
  this.maxPages = details.maxPages;
118
+ this.documentIndex = details.documentIndex;
95
119
  }
96
120
  }
97
121
 
@@ -120,6 +144,7 @@ export async function extractTextFromPdfWithUsage(
120
144
  providerOptions: options.providerOptions,
121
145
  abortSignal: options.abortSignal,
122
146
  maxPages: options.maxPages,
147
+ onUsage: options.onUsage,
123
148
  });
124
149
  }
125
150
 
@@ -140,42 +165,91 @@ export async function analyzeDocumentsWithUsage(options: IDocumentOptions): Prom
140
165
  for (const [index, doc] of options.pdfDocuments.entries()) {
141
166
  const pdf = await ensureSmartpdf();
142
167
  abortSignal?.throwIfAborted();
143
- const pages = await pdf.convertPDFToPngBytes(doc);
168
+ const limitDetails = (renderedPages: number, limit: number): IDocumentPageLimitErrorDetails => ({
169
+ renderedPages,
170
+ maxPages: limit,
171
+ documentCount: options.pdfDocuments.length,
172
+ documentIndex: index,
173
+ allDocumentsRendered: index === options.pdfDocuments.length - 1,
174
+ });
175
+ // SmartPDF limits one document, so each render gets the pages the request has left. It takes
176
+ // no limit of zero: when the earlier documents used up the request's pages, the smallest limit
177
+ // lets the document's page count be known, and a document of one page renders and is refused below.
178
+ const renderMaxPages = maxPages === undefined ? undefined : Math.max(maxPages - imagePages.length, 1);
179
+ let pages: Uint8Array[];
180
+ try {
181
+ pages = await pdf.convertPDFToPngBytes(doc, { signal: abortSignal, maxPages: renderMaxPages });
182
+ } catch (error) {
183
+ if (maxPages !== undefined && error instanceof plugins.smartpdf.SmartPdfPageLimitError) {
184
+ throw new DocumentPageLimitError(limitDetails(imagePages.length + error.pageCount, maxPages), { cause: error });
185
+ }
186
+ throw error;
187
+ }
144
188
  abortSignal?.throwIfAborted();
145
189
  imagePages.push(...pages);
146
190
  if (maxPages !== undefined && imagePages.length > maxPages) {
147
- throw new DocumentPageLimitError({
148
- renderedPages: imagePages.length,
149
- maxPages,
150
- documentCount: options.pdfDocuments.length,
151
- allDocumentsRendered: index === options.pdfDocuments.length - 1,
152
- });
191
+ throw new DocumentPageLimitError(limitDetails(imagePages.length, maxPages));
153
192
  }
154
193
  }
155
194
 
156
195
  // Filter out empty buffers
157
196
  const validPages = imagePages.filter(page => page && page.length > 0);
158
197
 
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
- });
198
+ const onUsage = options.onUsage;
199
+ const modelCalls = onUsage ? new plugins.AgentModelCallUsageRecorder(options.model, onUsage) : null;
200
+ // Each attempt, retries included, is one model call: it starts in the middleware, which also
201
+ // settles an attempt that throws; a call whose response arrives ends in onLanguageModelCallEnd.
202
+ const model = modelCalls
203
+ ? plugins.wrapLanguageModel({
204
+ model: options.model,
205
+ middleware: {
206
+ wrapGenerate: async ({ doGenerate, params }) => {
207
+ modelCalls.start();
208
+ try {
209
+ return await doGenerate();
210
+ } catch (error) {
211
+ modelCalls.settle(params.abortSignal?.aborted ? 'aborted' : 'failed');
212
+ throw error;
213
+ }
214
+ },
215
+ },
216
+ })
217
+ : options.model;
218
+ const messages: ModelMessage[] = [
219
+ ...(options.messageHistory ?? []),
220
+ {
221
+ role: 'user',
222
+ content: [
223
+ { type: 'text', text: options.userMessage },
224
+ ...validPages.map(page => ({
225
+ type: 'file' as const,
226
+ data: page,
227
+ mediaType: 'image/png' as const,
228
+ })),
229
+ ],
230
+ },
231
+ ];
232
+ let result: Awaited<ReturnType<typeof plugins.generateText>>;
233
+ try {
234
+ result = await plugins.generateText({
235
+ model,
236
+ instructions: options.systemMessage,
237
+ messages,
238
+ providerOptions: options.providerOptions,
239
+ abortSignal,
240
+ ...(modelCalls
241
+ ? {
242
+ onLanguageModelCallEnd: ({ modelId, usage: callUsage }) => {
243
+ modelCalls.end(modelId, callUsage);
244
+ },
245
+ }
246
+ : {}),
247
+ });
248
+ modelCalls?.settle('missing');
249
+ } catch (error) {
250
+ modelCalls?.settle(abortSignal?.aborted ? 'aborted' : 'failed');
251
+ throw error;
252
+ }
179
253
  const usage = result.totalUsage;
180
254
  const inputTokens = usage.inputTokens ?? 0;
181
255
  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
- - `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
- - `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.
29
+ - `abortSignal` is checked before each document is rendered and before the model call, and is passed to both the render and the model call, so an abort also stops a document that is rendering. The request rejects with the signal's reason.
30
+ - `maxPages` is the most rendered pages one request may send to the model, counted over all its documents. A request with more pages rejects with `DocumentPageLimitError` before the model is called. Each document is rendered only when its page count fits the pages the request has left, so the document that exceeds the limit and every document after it are not rendered; SmartPDF counts that document's pages without rendering them. The exception is a one-page document that comes after the request's pages are used up: SmartPDF takes no limit of zero pages, so that document renders and is then refused. The error carries `renderedPages` (the pages of the documents up to and including the one that exceeded the limit), `maxPages` and `documentIndex` (the zero-based position of that document), and SmartPDF's `SmartPdfPageLimitError` as its `cause` when SmartPDF refused the document.
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).