@modelprofile.com/flexharness 10.15.0 → 10.16.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.codesandbox.js +6 -2
- package/dist_ts/classes.flexharness.d.ts +33 -0
- package/dist_ts/classes.flexharness.js +328 -45
- package/dist_ts/classes.stores.d.ts +3 -1
- package/dist_ts/classes.stores.js +59 -1
- package/dist_ts/classes.toolcatalog.d.ts +9 -1
- package/dist_ts/classes.toolcatalog.js +82 -11
- package/dist_ts/interfaces.d.ts +59 -0
- package/dist_ts/interfaces.js +9 -1
- package/dist_ts/plugins.d.ts +3 -3
- package/dist_ts/plugins.js +3 -3
- package/dist_ts/utils.json.d.ts +5 -0
- package/dist_ts/utils.json.js +35 -1
- package/dist_ts/utils.results.d.ts +21 -0
- package/dist_ts/utils.results.js +158 -0
- package/dist_ts/utils.tooloutput.d.ts +5 -2
- package/dist_ts/utils.tooloutput.js +10 -3
- package/dist_ts_document/index.js +21 -8
- package/dist_ts_document/plugins.d.ts +2 -2
- package/dist_ts_document/plugins.js +3 -3
- package/dist_ts_media/index.js +14 -3
- package/dist_ts_media/plugins.d.ts +2 -2
- package/dist_ts_media/plugins.js +3 -3
- package/dist_ts_stores_nosqldb/classes.nosqldbstores.d.ts +2 -1
- package/dist_ts_stores_nosqldb/classes.nosqldbstores.js +37 -4
- package/dist_ts_stores_nosqldb/classes.recordstore.d.ts +4 -3
- package/dist_ts_stores_nosqldb/classes.recordstore.js +2 -2
- package/package.json +3 -3
- package/ts/00_commitinfo_data.ts +1 -1
- package/ts/classes.codesandbox.ts +5 -1
- package/ts/classes.flexharness.ts +382 -44
- package/ts/classes.stores.ts +66 -0
- package/ts/classes.toolcatalog.ts +87 -7
- package/ts/interfaces.ts +60 -0
- package/ts/plugins.ts +3 -0
- package/ts/utils.json.ts +30 -0
- package/ts/utils.results.ts +164 -0
- package/ts/utils.tooloutput.ts +9 -2
- package/ts_document/index.ts +18 -7
- package/ts_document/plugins.ts +2 -2
- package/ts_document/readme.md +2 -0
- package/ts_media/index.ts +11 -2
- package/ts_media/plugins.ts +2 -2
- package/ts_stores_nosqldb/classes.nosqldbstores.ts +39 -2
- package/ts_stores_nosqldb/classes.recordstore.ts +6 -4
package/ts_document/index.ts
CHANGED
|
@@ -489,10 +489,10 @@ export async function analyzeDocumentsWithUsage(options: IDocumentOptions): Prom
|
|
|
489
489
|
? plugins.wrapLanguageModel({
|
|
490
490
|
model: options.model,
|
|
491
491
|
middleware: {
|
|
492
|
-
|
|
492
|
+
wrapStream: async ({ doStream, params }) => {
|
|
493
493
|
modelCalls.start();
|
|
494
494
|
try {
|
|
495
|
-
return await
|
|
495
|
+
return await doStream();
|
|
496
496
|
} catch (error) {
|
|
497
497
|
modelCalls.settle(params.abortSignal?.aborted ? 'aborted' : 'failed');
|
|
498
498
|
throw error;
|
|
@@ -515,14 +515,20 @@ export async function analyzeDocumentsWithUsage(options: IDocumentOptions): Prom
|
|
|
515
515
|
],
|
|
516
516
|
},
|
|
517
517
|
];
|
|
518
|
-
|
|
518
|
+
// The call is streamed: some providers, such as the ChatGPT Codex backend, refuse a call that is not.
|
|
519
|
+
let streamError: unknown;
|
|
520
|
+
let text = '';
|
|
521
|
+
let usage: Awaited<ReturnType<typeof plugins.streamText>['totalUsage']>;
|
|
519
522
|
try {
|
|
520
|
-
result =
|
|
523
|
+
const result = plugins.streamText({
|
|
521
524
|
model,
|
|
522
525
|
instructions: options.systemMessage,
|
|
523
526
|
messages,
|
|
524
527
|
providerOptions: options.providerOptions,
|
|
525
528
|
abortSignal,
|
|
529
|
+
onError: ({ error }) => {
|
|
530
|
+
streamError ??= error;
|
|
531
|
+
},
|
|
526
532
|
...(modelCalls
|
|
527
533
|
? {
|
|
528
534
|
onLanguageModelCallEnd: ({ modelId, usage: callUsage }) => {
|
|
@@ -531,16 +537,21 @@ export async function analyzeDocumentsWithUsage(options: IDocumentOptions): Prom
|
|
|
531
537
|
}
|
|
532
538
|
: {}),
|
|
533
539
|
});
|
|
540
|
+
for await (const delta of result.textStream) text += delta;
|
|
541
|
+
// A stream that fails delivers its error to onError and ends, and an aborted stream just ends;
|
|
542
|
+
// either way the text is incomplete.
|
|
543
|
+
if (streamError !== undefined) throw streamError;
|
|
544
|
+
abortSignal?.throwIfAborted();
|
|
545
|
+
usage = await result.totalUsage;
|
|
534
546
|
modelCalls?.settle('missing');
|
|
535
547
|
} catch (error) {
|
|
536
548
|
modelCalls?.settle(abortSignal?.aborted ? 'aborted' : 'failed');
|
|
537
|
-
throw error;
|
|
549
|
+
throw streamError ?? error;
|
|
538
550
|
}
|
|
539
|
-
const usage = result.totalUsage;
|
|
540
551
|
const inputTokens = usage.inputTokens ?? 0;
|
|
541
552
|
const outputTokens = usage.outputTokens ?? 0;
|
|
542
553
|
return {
|
|
543
|
-
text
|
|
554
|
+
text,
|
|
544
555
|
usage: {
|
|
545
556
|
inputTokens,
|
|
546
557
|
outputTokens,
|
package/ts_document/plugins.ts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { streamText, wrapLanguageModel } from 'ai';
|
|
2
2
|
import * as smartpdf from '@push.rocks/smartpdf';
|
|
3
3
|
import { AgentModelCallUsageRecorder } from '@modelprofile.com/flexharness-agent';
|
|
4
|
-
export {
|
|
4
|
+
export { streamText, wrapLanguageModel, smartpdf, AgentModelCallUsageRecorder };
|
|
5
5
|
export type { ModelMessage } from 'ai';
|
|
6
6
|
export type { TSmartAiLanguageModel, TSmartAiProviderOptions } from '@modelprofile.com/flexharness-models';
|
|
7
7
|
export type { TAgentModelCallUsageReporter } from '@modelprofile.com/flexharness-agent';
|
package/ts_document/readme.md
CHANGED
|
@@ -67,6 +67,8 @@ Both functions accept these request options:
|
|
|
67
67
|
- `maxImageEdge` is the longest edge, in pixels, of an image document as it is sent; see above.
|
|
68
68
|
- `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).
|
|
69
69
|
|
|
70
|
+
A request makes one streamed model call, retries aside, so it also reads documents on providers that answer streamed calls only, such as the Codex backend of a ChatGPT account.
|
|
71
|
+
|
|
70
72
|
`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.
|
|
71
73
|
|
|
72
74
|
```typescript
|
package/ts_media/index.ts
CHANGED
|
@@ -8,7 +8,9 @@ export interface IVisionOptions {
|
|
|
8
8
|
}
|
|
9
9
|
|
|
10
10
|
export async function analyzeImage(options: IVisionOptions): Promise<string> {
|
|
11
|
-
|
|
11
|
+
// The call is streamed: some providers, such as the ChatGPT Codex backend, refuse a call that is not.
|
|
12
|
+
let streamError: unknown;
|
|
13
|
+
const result = plugins.streamText({
|
|
12
14
|
model: options.model,
|
|
13
15
|
messages: [
|
|
14
16
|
{
|
|
@@ -23,6 +25,13 @@ export async function analyzeImage(options: IVisionOptions): Promise<string> {
|
|
|
23
25
|
],
|
|
24
26
|
},
|
|
25
27
|
],
|
|
28
|
+
onError: ({ error }) => {
|
|
29
|
+
streamError ??= error;
|
|
30
|
+
},
|
|
26
31
|
});
|
|
27
|
-
|
|
32
|
+
let text = '';
|
|
33
|
+
for await (const delta of result.textStream) text += delta;
|
|
34
|
+
// A stream that fails delivers its error to onError and ends; its text is incomplete.
|
|
35
|
+
if (streamError !== undefined) throw streamError;
|
|
36
|
+
return text;
|
|
28
37
|
}
|
package/ts_media/plugins.ts
CHANGED
|
@@ -1,3 +1,3 @@
|
|
|
1
|
-
import {
|
|
2
|
-
export {
|
|
1
|
+
import { streamText } from 'ai';
|
|
2
|
+
export { streamText };
|
|
3
3
|
export type { TSmartAiLanguageModel } from '@modelprofile.com/flexharness-models';
|
|
@@ -11,16 +11,19 @@ import type {
|
|
|
11
11
|
IFlexProjectManagementWriteContext,
|
|
12
12
|
IFlexProjectionSnapshotV3,
|
|
13
13
|
IFlexProjectionStore,
|
|
14
|
+
IFlexResultStore,
|
|
14
15
|
IFlexScopeSnapshot,
|
|
15
16
|
IFlexScopeStore,
|
|
16
17
|
IFlexToolJobStoreProvider,
|
|
17
18
|
TFlexProjectionSnapshot,
|
|
18
19
|
TFlexProjectManagementRecord,
|
|
20
|
+
TJsonValue,
|
|
19
21
|
} from '../ts/interfaces.js';
|
|
20
22
|
import {
|
|
21
23
|
assertFlexPermissionSnapshot,
|
|
22
24
|
assertFlexProjectionSnapshot,
|
|
23
25
|
assertFlexScopeSnapshot,
|
|
26
|
+
assertJsonSerializable,
|
|
24
27
|
} from '../ts/utils.json.js';
|
|
25
28
|
import {
|
|
26
29
|
assertFlexProjectManagementRecord,
|
|
@@ -297,7 +300,7 @@ class NoSqlAgentEventStore implements plugins.IAgentEventStoreV2 {
|
|
|
297
300
|
domain: 'agentEventArchive' as const,
|
|
298
301
|
storageKey: this.storageKey,
|
|
299
302
|
sessionId: this.sessionId,
|
|
300
|
-
|
|
303
|
+
entryId: archive.archiveId,
|
|
301
304
|
};
|
|
302
305
|
await this.records.mutate(address, {
|
|
303
306
|
validate: (value) => validateAgentEventArchive(value, this.sessionId, archive.archiveId),
|
|
@@ -318,7 +321,7 @@ class NoSqlAgentEventStore implements plugins.IAgentEventStoreV2 {
|
|
|
318
321
|
this.assertSession(sessionId);
|
|
319
322
|
validateIdentifier(archiveId, 'archiveId');
|
|
320
323
|
return this.records.read(
|
|
321
|
-
{ domain: 'agentEventArchive', storageKey: this.storageKey, sessionId, archiveId },
|
|
324
|
+
{ domain: 'agentEventArchive', storageKey: this.storageKey, sessionId, entryId: archiveId },
|
|
322
325
|
(value) => validateAgentEventArchive(value, this.sessionId, archiveId),
|
|
323
326
|
);
|
|
324
327
|
}
|
|
@@ -428,6 +431,38 @@ export interface INoSqlFlexHarnessStoresOptions {
|
|
|
428
431
|
operationTimeoutMs?: number;
|
|
429
432
|
}
|
|
430
433
|
|
|
434
|
+
class NoSqlResultStore implements IFlexResultStore {
|
|
435
|
+
constructor(private readonly records: FlexRecordStore) {}
|
|
436
|
+
|
|
437
|
+
public async save(storageKey: string, sessionId: string, handle: string, value: TJsonValue): Promise<void> {
|
|
438
|
+
validateIdentifier(handle, 'handle');
|
|
439
|
+
assertJsonSerializable(value);
|
|
440
|
+
const conflict = () => new FlexHarnessStoreConflictError(`${providerKey(storageKey, sessionId)}:${handle}`, 0, 1);
|
|
441
|
+
await this.records.mutate({ domain: 'result', storageKey, sessionId, entryId: handle }, {
|
|
442
|
+
validate: validateResult,
|
|
443
|
+
decide: (current) => {
|
|
444
|
+
if (current !== undefined) throw conflict();
|
|
445
|
+
return { revision: 1, value };
|
|
446
|
+
},
|
|
447
|
+
contention: conflict,
|
|
448
|
+
});
|
|
449
|
+
}
|
|
450
|
+
|
|
451
|
+
public load(storageKey: string, sessionId: string, handle: string): Promise<TJsonValue | undefined> {
|
|
452
|
+
validateIdentifier(handle, 'handle');
|
|
453
|
+
return this.records.read({ domain: 'result', storageKey, sessionId, entryId: handle }, validateResult);
|
|
454
|
+
}
|
|
455
|
+
|
|
456
|
+
public deleteSession(storageKey: string, sessionId: string): Promise<void> {
|
|
457
|
+
return this.records.deleteOwner(storageKey, sessionId, ['result']);
|
|
458
|
+
}
|
|
459
|
+
}
|
|
460
|
+
|
|
461
|
+
function validateResult(value: unknown): TJsonValue {
|
|
462
|
+
assertJsonSerializable(value);
|
|
463
|
+
return value as TJsonValue;
|
|
464
|
+
}
|
|
465
|
+
|
|
431
466
|
/**
|
|
432
467
|
* Every FlexHarness persistence domain on `@lossless.org/client/nosqldb`, with compare-and-swap
|
|
433
468
|
* that holds across processes. Records live in `flexharness_records`; payloads longer than one
|
|
@@ -440,6 +475,7 @@ export class NoSqlFlexHarnessStores implements IFlexHarnessStores {
|
|
|
440
475
|
public readonly projectManagement: IFlexProjectManagementStore;
|
|
441
476
|
public readonly agentEvents: IFlexAgentEventStoreProvider;
|
|
442
477
|
public readonly jobs: IFlexToolJobStoreProvider;
|
|
478
|
+
public readonly results: IFlexResultStore;
|
|
443
479
|
private readonly records: FlexRecordStore;
|
|
444
480
|
|
|
445
481
|
constructor(options: INoSqlFlexHarnessStoresOptions) {
|
|
@@ -453,6 +489,7 @@ export class NoSqlFlexHarnessStores implements IFlexHarnessStores {
|
|
|
453
489
|
this.projectManagement = new NoSqlProjectManagementStore(this.records);
|
|
454
490
|
this.agentEvents = new NoSqlAgentEventStoreProvider(this.records);
|
|
455
491
|
this.jobs = new NoSqlToolJobStoreProvider(this.records);
|
|
492
|
+
this.results = new NoSqlResultStore(this.records);
|
|
456
493
|
}
|
|
457
494
|
|
|
458
495
|
/**
|
|
@@ -12,14 +12,16 @@ export type TFlexRecordDomain =
|
|
|
12
12
|
| 'projectManagement'
|
|
13
13
|
| 'agentEvents'
|
|
14
14
|
| 'agentEventArchive'
|
|
15
|
-
| 'jobs'
|
|
15
|
+
| 'jobs'
|
|
16
|
+
| 'result';
|
|
16
17
|
|
|
17
|
-
/** Names one record: a domain snapshot of a storage key
|
|
18
|
+
/** Names one record: a domain snapshot of a storage key or a session, or one entry of a session. */
|
|
18
19
|
export interface IFlexRecordAddress {
|
|
19
20
|
domain: TFlexRecordDomain;
|
|
20
21
|
storageKey: string;
|
|
21
22
|
sessionId?: string;
|
|
22
|
-
|
|
23
|
+
/** The archive or result the record holds. */
|
|
24
|
+
entryId?: string;
|
|
23
25
|
}
|
|
24
26
|
|
|
25
27
|
/** The next stored value a mutation decided on. */
|
|
@@ -256,7 +258,7 @@ export class FlexRecordStore {
|
|
|
256
258
|
address.domain,
|
|
257
259
|
address.storageKey,
|
|
258
260
|
address.sessionId ?? null,
|
|
259
|
-
address.
|
|
261
|
+
address.entryId ?? null,
|
|
260
262
|
]));
|
|
261
263
|
return {
|
|
262
264
|
recordId,
|