@modelprofile.com/flexharness-agent 6.0.0 → 7.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.
Files changed (40) hide show
  1. package/dist_ts_agent/classes.sessionengine.d.ts +93 -0
  2. package/dist_ts_agent/classes.sessionengine.js +2140 -0
  3. package/dist_ts_agent/index.d.ts +3 -2
  4. package/dist_ts_agent/index.js +3 -2
  5. package/dist_ts_agent/persistence.file.d.ts +24 -0
  6. package/dist_ts_agent/persistence.file.js +199 -0
  7. package/dist_ts_agent/plugins.d.ts +0 -7
  8. package/dist_ts_agent/plugins.js +1 -9
  9. package/dist_ts_agent/plugins.node.d.ts +7 -0
  10. package/dist_ts_agent/plugins.node.js +9 -0
  11. package/dist_ts_agent/plugins.portable.d.ts +4 -0
  12. package/dist_ts_agent/plugins.portable.js +5 -0
  13. package/dist_ts_agent/runner.d.ts +4 -0
  14. package/dist_ts_agent/runner.js +34 -0
  15. package/dist_ts_agent/runtime.interfaces.d.ts +21 -0
  16. package/dist_ts_agent/runtime.interfaces.js +2 -0
  17. package/dist_ts_agent/runtime.run.d.ts +3 -0
  18. package/dist_ts_agent/runtime.run.js +94 -0
  19. package/dist_ts_agent/smartagent.classes.agent.js +4 -93
  20. package/dist_ts_agent/smartagent.classes.session.d.ts +5 -88
  21. package/dist_ts_agent/smartagent.classes.session.js +19 -2134
  22. package/dist_ts_agent/smartagent.events.js +2 -2
  23. package/dist_ts_agent/smartagent.persistence.d.ts +2 -22
  24. package/dist_ts_agent/smartagent.persistence.js +3 -198
  25. package/package.json +9 -3
  26. package/readme.md +9 -3
  27. package/ts_agent/classes.sessionengine.ts +2591 -0
  28. package/ts_agent/index.ts +2 -2
  29. package/ts_agent/persistence.file.ts +232 -0
  30. package/ts_agent/plugins.node.ts +10 -0
  31. package/ts_agent/plugins.portable.ts +5 -0
  32. package/ts_agent/plugins.ts +0 -10
  33. package/ts_agent/readme.md +9 -3
  34. package/ts_agent/runner.ts +40 -0
  35. package/ts_agent/runtime.interfaces.ts +21 -0
  36. package/ts_agent/runtime.run.ts +107 -0
  37. package/ts_agent/smartagent.classes.agent.ts +4 -101
  38. package/ts_agent/smartagent.classes.session.ts +24 -2582
  39. package/ts_agent/smartagent.events.ts +1 -1
  40. package/ts_agent/smartagent.persistence.ts +2 -224
package/ts_agent/index.ts CHANGED
@@ -20,7 +20,6 @@ export {
20
20
  } from './smartagent.interfaces.js';
21
21
  export {
22
22
  AgentEventStoreConflictError,
23
- FileAgentEventStore,
24
23
  InMemoryAgentEventStore,
25
24
  isAgentEventStoreV2,
26
25
  validateAgentEventArchiveV2,
@@ -67,7 +66,6 @@ export type {
67
66
  IAgentEventSnapshotV2,
68
67
  IAgentEventStore,
69
68
  IAgentEventStoreV2,
70
- IFileAgentEventStoreOptions,
71
69
  TAgentEventArchive,
72
70
  TAgentEventSnapshot,
73
71
  TAgentEventStore,
@@ -117,3 +115,5 @@ export * from './tool.persistence.js';
117
115
  export * from './tool.adapter.js';
118
116
  export { tool, jsonSchema, stepCountIs } from 'ai';
119
117
  export { z } from 'zod';
118
+
119
+ export { FileAgentEventStore, type IFileAgentEventStoreOptions } from './persistence.file.js';
@@ -0,0 +1,232 @@
1
+ import * as plugins from './plugins.node.js';
2
+ import type { TAgentEvent } from './smartagent.events.js';
3
+ import {
4
+ AgentEventStoreConflictError, clone, createSnapshot,
5
+ validateAgentEventArchiveV2, validateAgentEventSnapshotV2,
6
+ type IAgentEventArchiveV2, type IAgentEventStoreV2,
7
+ type TAgentEventArchive, type TAgentEventSnapshot,
8
+ } from './smartagent.persistence.js';
9
+
10
+ const SNAPSHOT_SCHEMA_VERSION = 2 as const;
11
+ const ARCHIVE_SCHEMA_VERSION = 2 as const;
12
+
13
+ export interface IFileAgentEventStoreOptions {
14
+ rootDir: string;
15
+ /** Maximum time spent waiting for another writer. Default: 10 seconds. */
16
+ lockTimeoutMs?: number;
17
+ }
18
+
19
+ const assertSessionId = (sessionId: string): void => {
20
+ if (!sessionId.trim()) throw new Error('Agent event store sessionId must not be empty.');
21
+ };
22
+
23
+ const decodeSnapshot = (value: unknown, sessionId: string): TAgentEventSnapshot => {
24
+ const snapshot = value as Partial<TAgentEventSnapshot>;
25
+ if (snapshot?.schemaVersion === SNAPSHOT_SCHEMA_VERSION) {
26
+ try {
27
+ return validateAgentEventSnapshotV2(value, sessionId);
28
+ } catch {
29
+ throw new Error(`Invalid agent event snapshot for session "${sessionId}".`);
30
+ }
31
+ }
32
+ if (
33
+ !snapshot
34
+ || snapshot.schemaVersion !== 1
35
+ || snapshot.sessionId !== sessionId
36
+ || !Number.isSafeInteger(snapshot.revision)
37
+ || (snapshot.revision ?? -1) < 1
38
+ || !Array.isArray(snapshot.events)
39
+ ) {
40
+ throw new Error(`Invalid agent event snapshot for session "${sessionId}".`);
41
+ }
42
+ return snapshot as TAgentEventSnapshot;
43
+ };
44
+
45
+ const decodeArchive = (
46
+ value: unknown,
47
+ sessionId: string,
48
+ archiveId: string,
49
+ ): TAgentEventArchive => {
50
+ const archive = value as Partial<TAgentEventArchive>;
51
+ if (archive?.schemaVersion === ARCHIVE_SCHEMA_VERSION) {
52
+ try {
53
+ return validateAgentEventArchiveV2(value, sessionId, archiveId);
54
+ } catch {
55
+ throw new Error(`Invalid agent event archive "${archiveId}" for session "${sessionId}".`);
56
+ }
57
+ }
58
+ if (
59
+ !archive
60
+ || archive.schemaVersion !== 1
61
+ || archive.sessionId !== sessionId
62
+ || archive.archiveId !== archiveId
63
+ || !Array.isArray(archive.events)
64
+ ) {
65
+ throw new Error(`Invalid agent event archive "${archiveId}" for session "${sessionId}".`);
66
+ }
67
+ return archive as TAgentEventArchive;
68
+ };
69
+
70
+ const sleep = async (milliseconds: number): Promise<void> => {
71
+ await new Promise((resolve) => setTimeout(resolve, milliseconds));
72
+ };
73
+
74
+ export class FileAgentEventStore implements IAgentEventStoreV2 {
75
+ public readonly eventSchemaVersion = 2 as const;
76
+ private readonly rootDir: string;
77
+ private readonly lockTimeoutMs: number;
78
+
79
+ constructor(options: IFileAgentEventStoreOptions) {
80
+ if (!options.rootDir.trim()) throw new Error('FileAgentEventStore rootDir must not be empty.');
81
+ this.rootDir = plugins.path.resolve(options.rootDir);
82
+ this.lockTimeoutMs = options.lockTimeoutMs ?? 10_000;
83
+ if (!Number.isSafeInteger(this.lockTimeoutMs) || this.lockTimeoutMs <= 0) {
84
+ throw new Error('lockTimeoutMs must be a positive integer.');
85
+ }
86
+ }
87
+
88
+ private sessionKey(sessionId: string): string {
89
+ assertSessionId(sessionId);
90
+ return plugins.crypto.createHash('sha256').update(sessionId).digest('hex');
91
+ }
92
+
93
+ private snapshotPath(sessionId: string): string {
94
+ return plugins.path.join(this.rootDir, 'sessions', `${this.sessionKey(sessionId)}.bin`);
95
+ }
96
+
97
+ private archivePath(sessionId: string, archiveId: string): string {
98
+ if (!archiveId.trim()) throw new Error('Agent event archiveId must not be empty.');
99
+ const archiveKey = plugins.crypto.createHash('sha256').update(archiveId).digest('hex');
100
+ return plugins.path.join(this.rootDir, 'archives', this.sessionKey(sessionId), `${archiveKey}.bin`);
101
+ }
102
+
103
+ private async readValue(filePath: string): Promise<unknown | undefined> {
104
+ try {
105
+ return plugins.v8.deserialize(await plugins.fs.readFile(filePath));
106
+ } catch (error) {
107
+ if ((error as NodeJS.ErrnoException).code === 'ENOENT') return undefined;
108
+ throw error;
109
+ }
110
+ }
111
+
112
+ private async syncDirectory(directoryPath: string): Promise<void> {
113
+ const handle = await plugins.fs.open(directoryPath, 'r');
114
+ try {
115
+ await handle.sync();
116
+ } finally {
117
+ await handle.close();
118
+ }
119
+ }
120
+
121
+ private async writeAtomic(filePath: string, value: unknown): Promise<void> {
122
+ const directoryPath = plugins.path.dirname(filePath);
123
+ await plugins.fs.mkdir(directoryPath, { recursive: true });
124
+ const temporaryPath = `${filePath}.${process.pid}.${plugins.crypto.randomUUID()}.tmp`;
125
+ let handle: Awaited<ReturnType<typeof plugins.fs.open>> | undefined;
126
+ try {
127
+ handle = await plugins.fs.open(temporaryPath, 'wx');
128
+ await handle.writeFile(plugins.v8.serialize(value));
129
+ await handle.sync();
130
+ await handle.close();
131
+ handle = undefined;
132
+ await plugins.fs.rename(temporaryPath, filePath);
133
+ await this.syncDirectory(directoryPath);
134
+ } finally {
135
+ if (handle) await handle.close().catch(() => undefined);
136
+ await plugins.fs.rm(temporaryPath, { force: true });
137
+ }
138
+ }
139
+
140
+ private async withSessionLock<TValue>(
141
+ sessionId: string,
142
+ operation: () => Promise<TValue>,
143
+ ): Promise<TValue> {
144
+ const lockPath = plugins.path.join(this.rootDir, 'locks', `${this.sessionKey(sessionId)}.lock`);
145
+ const ownerPath = plugins.path.join(lockPath, 'owner');
146
+ const ownerToken = plugins.crypto.randomUUID();
147
+ const ownerRecord = JSON.stringify({ token: ownerToken });
148
+ await plugins.fs.mkdir(plugins.path.dirname(lockPath), { recursive: true });
149
+ const startedAt = Date.now();
150
+ while (true) {
151
+ try {
152
+ await plugins.fs.mkdir(lockPath);
153
+ try {
154
+ await plugins.fs.writeFile(ownerPath, ownerRecord, 'utf8');
155
+ } catch (error) {
156
+ await plugins.fs.rm(lockPath, { recursive: true, force: true });
157
+ throw error;
158
+ }
159
+ break;
160
+ } catch (error) {
161
+ if ((error as NodeJS.ErrnoException).code !== 'EEXIST') throw error;
162
+ if (Date.now() - startedAt >= this.lockTimeoutMs) {
163
+ throw new Error(
164
+ `Timed out waiting for the event-store lock for session "${sessionId}" at "${lockPath}".`,
165
+ );
166
+ }
167
+ await sleep(20);
168
+ }
169
+ }
170
+ try {
171
+ return await operation();
172
+ } finally {
173
+ let stillOwner = false;
174
+ try {
175
+ const owner = JSON.parse(await plugins.fs.readFile(ownerPath, 'utf8')) as { token?: unknown };
176
+ stillOwner = owner.token === ownerToken;
177
+ } catch {}
178
+ if (stillOwner) await plugins.fs.rm(lockPath, { recursive: true, force: true });
179
+ }
180
+ }
181
+
182
+ public async load(sessionId: string): Promise<TAgentEventSnapshot | undefined> {
183
+ const value = await this.readValue(this.snapshotPath(sessionId));
184
+ return value === undefined ? undefined : decodeSnapshot(value, sessionId);
185
+ }
186
+
187
+ public async save(
188
+ sessionId: string,
189
+ events: readonly TAgentEvent[],
190
+ expectedRevision: number,
191
+ ): Promise<number> {
192
+ return this.withSessionLock(sessionId, async () => {
193
+ const filePath = this.snapshotPath(sessionId);
194
+ const currentValue = await this.readValue(filePath);
195
+ const actualRevision = currentValue === undefined
196
+ ? 0
197
+ : decodeSnapshot(currentValue, sessionId).revision;
198
+ if (actualRevision !== expectedRevision) {
199
+ throw new AgentEventStoreConflictError(sessionId, expectedRevision, actualRevision);
200
+ }
201
+ const revision = expectedRevision + 1;
202
+ await this.writeAtomic(filePath, createSnapshot(sessionId, revision, events));
203
+ return revision;
204
+ });
205
+ }
206
+
207
+ public async archive(archive: IAgentEventArchiveV2): Promise<void> {
208
+ const nextArchive = clone(archive);
209
+ await this.withSessionLock(archive.sessionId, async () => {
210
+ const filePath = this.archivePath(archive.sessionId, archive.archiveId);
211
+ const currentValue = await this.readValue(filePath);
212
+ if (currentValue !== undefined) {
213
+ const current = decodeArchive(currentValue, archive.sessionId, archive.archiveId);
214
+ const currentIds = current.events.map((event) => event.id).join('\n');
215
+ const nextIds = nextArchive.events.map((event) => event.id).join('\n');
216
+ if (currentIds !== nextIds) {
217
+ throw new Error(`Archive "${archive.archiveId}" already exists with different events.`);
218
+ }
219
+ return;
220
+ }
221
+ await this.writeAtomic(filePath, nextArchive);
222
+ });
223
+ }
224
+
225
+ public async loadArchive(
226
+ sessionId: string,
227
+ archiveId: string,
228
+ ): Promise<TAgentEventArchive | undefined> {
229
+ const value = await this.readValue(this.archivePath(sessionId, archiveId));
230
+ return value === undefined ? undefined : decodeArchive(value, sessionId, archiveId);
231
+ }
232
+ }
@@ -0,0 +1,10 @@
1
+ // node native
2
+ import { AsyncLocalStorage } from 'node:async_hooks';
3
+ import * as crypto from 'node:crypto';
4
+ import * as fs from 'node:fs/promises';
5
+ import * as path from 'node:path';
6
+ import { isDeepStrictEqual } from 'node:util';
7
+ import * as v8 from 'node:v8';
8
+
9
+ export { AsyncLocalStorage, crypto, fs, isDeepStrictEqual, path, v8 };
10
+
@@ -0,0 +1,5 @@
1
+ import { sha256 } from '@noble/hashes/sha2.js';
2
+ import { bytesToHex, utf8ToBytes } from '@noble/hashes/utils.js';
3
+ import { strictCircularDeepEqual } from 'fast-equals';
4
+
5
+ export { sha256, bytesToHex, utf8ToBytes, strictCircularDeepEqual };
@@ -1,13 +1,3 @@
1
- // node native
2
- import { AsyncLocalStorage } from 'node:async_hooks';
3
- import * as crypto from 'node:crypto';
4
- import * as fs from 'node:fs/promises';
5
- import * as path from 'node:path';
6
- import { isDeepStrictEqual } from 'node:util';
7
- import * as v8 from 'node:v8';
8
-
9
- export { AsyncLocalStorage, crypto, fs, isDeepStrictEqual, path, v8 };
10
-
11
1
  // ai-sdk core
12
2
  import { streamText, generateText, stepCountIs, wrapLanguageModel } from 'ai';
13
3
 
@@ -1,7 +1,11 @@
1
1
  Standalone event-driven agent runtime with generation transactions, context and tool execution contracts.
2
2
 
3
+ ## Issue Reporting and Security
4
+
5
+ For reporting bugs, issues, or security vulnerabilities, please visit [community.foss.global/](https://community.foss.global/). This is the central community hub for all issue reporting. Developers who sign and comply with our contribution agreement and go through identification can also get a [code.foss.global/](https://code.foss.global/) account to submit Pull Requests directly.
6
+
3
7
  Part of the [FlexHarness toolbox](https://code.foss.global/modelprofile.com/flexharness).
4
- Install the components your project uses. Provider adapters are registered explicitly;
8
+ The toolbox has four package names: `flexharness`, `flexharness-agent`, `flexharness-models`, and `flexharness-providers`. Install the packages your project uses. Provider adapters are registered explicitly;
5
9
  the agent and harness do not install or register vendor SDKs.
6
10
 
7
11
  ```sh
@@ -11,9 +15,11 @@ pnpm add @modelprofile.com/flexharness-agent
11
15
  See the [toolbox usage and migration guide](https://code.foss.global/modelprofile.com/flexharness#readme)
12
16
  for composition examples and the SmartAI/SmartAgent migration map.
13
17
 
14
- ## Issue Reporting and Security
18
+ ## Portable runner
19
+
20
+ `@modelprofile.com/flexharness-agent/runner` exports `runAgent` and its option/result types for Node and secure browser contexts. It uses the same generation engine, retries and tool-execution ledger, owns its session internally, and closes it before returning. It does not expose session lifecycle or filesystem storage. Modern Web Crypto, streams and structured cloning are required.
15
21
 
16
- Report issues and security concerns through [community.foss.global](https://community.foss.global).
22
+ Use the root entrypoint for the full Node `AgentSession` API, including externally controlled cancellation and close. Its callback ownership uses Node AsyncLocalStorage. Browser code must import `/runner` directly; the root entrypoint remains Node-only. Provider credentials and transports are supplied by the caller.
17
23
 
18
24
  ## License and Legal Information
19
25
 
@@ -0,0 +1,40 @@
1
+ import * as plugins from './plugins.portable.js';
2
+ import { AgentSessionEngine } from './classes.sessionengine.js';
3
+ import { runAgentWithSession } from './runtime.run.js';
4
+ import type { IAgentRuntimePlatform } from './runtime.interfaces.js';
5
+ import type { IAgentSessionOptions, IAgentRunOptions, IAgentRunResult } from './smartagent.interfaces.js';
6
+
7
+ const portablePlatform: IAgentRuntimePlatform = {
8
+ createClaimToken: () => btoa(String.fromCharCode(...globalThis.crypto.getRandomValues(new Uint8Array(32))))
9
+ .replace(/\+/g, '-').replace(/\//g, '_').replace(/=+$/, ''),
10
+ hashClaimToken: (token) => plugins.bytesToHex(plugins.sha256(plugins.utf8ToBytes(token))),
11
+ equalClaimDigests: (left, right) => {
12
+ if (left.length !== right.length) return false;
13
+ let difference = 0;
14
+ for (let index = 0; index < left.length; index++) {
15
+ difference |= left.charCodeAt(index) ^ right.charCodeAt(index);
16
+ }
17
+ return difference === 0;
18
+ },
19
+ isDeepEqual: plugins.strictCircularDeepEqual,
20
+ reportListenerError: (error) => console.warn('FLEXHARNESS_SESSION_LISTENER_ERROR', error),
21
+ };
22
+
23
+ // This class and its factory are deliberately not exported. Callbacks receive
24
+ // data only; no caller can close this session from inside its active callback.
25
+ class RunnerSession extends AgentSessionEngine {
26
+ constructor(options: IAgentSessionOptions) {
27
+ super(options, portablePlatform, { kind: 'encapsulated' });
28
+ }
29
+
30
+ public static create(options: IAgentSessionOptions): Promise<RunnerSession> {
31
+ return this.createWithFactory(options, () => new RunnerSession(options));
32
+ }
33
+ }
34
+
35
+ /** Run in Node or a secure browser context, with internally owned cleanup. */
36
+ export function runAgent(options: IAgentRunOptions): Promise<IAgentRunResult> {
37
+ return runAgentWithSession(options, (sessionOptions) => RunnerSession.create(sessionOptions));
38
+ }
39
+
40
+ export type { IAgentRunOptions, IAgentRunResult } from './smartagent.interfaces.js';
@@ -0,0 +1,21 @@
1
+ /** Internal platform boundary; only the documented agent entrypoints are exported. */
2
+ export interface IAgentCallbackState {
3
+ active: boolean;
4
+ }
5
+
6
+ export interface IAgentCallbackScope {
7
+ getStore(): IAgentCallbackState | undefined;
8
+ run<TValue>(state: IAgentCallbackState, callback: () => TValue): TValue;
9
+ }
10
+
11
+ export type TAgentSessionOwnership =
12
+ | { kind: 'exposed'; callbackScope: IAgentCallbackScope }
13
+ | { kind: 'encapsulated' };
14
+
15
+ export interface IAgentRuntimePlatform {
16
+ createClaimToken(): string;
17
+ hashClaimToken(token: string): string;
18
+ equalClaimDigests(left: string, right: string): boolean;
19
+ isDeepEqual(left: unknown, right: unknown): boolean;
20
+ reportListenerError(error: unknown): void;
21
+ }
@@ -0,0 +1,107 @@
1
+ import type { IAgentSession, IAgentSessionOptions } from './smartagent.interfaces.js';
2
+ import type {
3
+ IAgentRunOptions,
4
+ IAgentRunResult,
5
+ IAgentToolCallRecord,
6
+ } from './smartagent.interfaces.js';
7
+
8
+ const mergeToolCalls = (
9
+ target: IAgentToolCallRecord[],
10
+ source: readonly IAgentToolCallRecord[],
11
+ ): void => {
12
+ const indexes = new Map(target.map((record, index) => [record.toolCallId, index]));
13
+ for (const record of source) {
14
+ const existingIndex = indexes.get(record.toolCallId);
15
+ if (existingIndex === undefined) {
16
+ target.push({ ...record });
17
+ indexes.set(record.toolCallId, target.length - 1);
18
+ } else {
19
+ target[existingIndex] = { ...target[existingIndex], ...record };
20
+ }
21
+ }
22
+ };
23
+
24
+ export async function runAgentWithSession(
25
+ options: IAgentRunOptions,
26
+ createSession: (options: IAgentSessionOptions) => Promise<IAgentSession>,
27
+ ): Promise<IAgentRunResult> {
28
+ const session = await createSession({
29
+ model: options.model,
30
+ system: options.system,
31
+ tools: options.tools,
32
+ providerOptions: options.providerOptions,
33
+ sessionId: options.sessionId,
34
+ eventStore: options.eventStore,
35
+ cache: options.cache,
36
+ maxSteps: options.maxSteps,
37
+ messages: options.messages,
38
+ events: options.events,
39
+ executionContext: options.executionContext,
40
+ contextBuilder: options.contextBuilder,
41
+ contextCompactor: options.contextCompactor,
42
+ eventRetention: options.eventRetention,
43
+ changeListenerTimeoutMs: options.changeListenerTimeoutMs,
44
+ maxPendingSessionChanges: options.maxPendingSessionChanges,
45
+ generationLeaseCleanupTimeoutMs: options.generationLeaseCleanupTimeoutMs,
46
+ maxArchivedTransactionTombstones: options.maxArchivedTransactionTombstones,
47
+ onToken: options.onToken,
48
+ onReasoningStart: options.onReasoningStart,
49
+ onReasoningDelta: options.onReasoningDelta,
50
+ onReasoningEnd: options.onReasoningEnd,
51
+ onToolCallStart: options.onToolCallStart,
52
+ onToolCallUpdate: options.onToolCallUpdate,
53
+ onToolCallFinish: options.onToolCallFinish,
54
+ onToolCall: options.onToolCall,
55
+ onToolResult: options.onToolResult,
56
+ onContextOverflow: options.onContextOverflow,
57
+ maxContextOverflowRetries: options.maxContextOverflowRetries,
58
+ });
59
+ let totalSteps = 0;
60
+ let totalInput = 0;
61
+ let totalOutput = 0;
62
+ let totalCacheRead = 0;
63
+ let totalCacheWrite = 0;
64
+ let validationRetries = 0;
65
+ const toolCalls: IAgentToolCallRecord[] = [];
66
+
67
+ try {
68
+ await session.pushUserMessage(options.prompt);
69
+ while (true) {
70
+ const generationResult = await session.generate({
71
+ maxSteps: options.maxSteps,
72
+ abort: options.abort,
73
+ });
74
+ totalSteps += generationResult.steps;
75
+ totalInput += generationResult.usage.inputTokens;
76
+ totalOutput += generationResult.usage.outputTokens;
77
+ totalCacheRead += generationResult.usage.cacheReadTokens;
78
+ totalCacheWrite += generationResult.usage.cacheWriteTokens;
79
+ mergeToolCalls(toolCalls, generationResult.toolCalls);
80
+
81
+ const runResult: IAgentRunResult = {
82
+ text: generationResult.text,
83
+ messages: session.getModelMessages(),
84
+ steps: totalSteps,
85
+ finishReason: generationResult.finishReason,
86
+ usage: {
87
+ inputTokens: totalInput,
88
+ outputTokens: totalOutput,
89
+ totalTokens: totalInput + totalOutput,
90
+ cacheReadTokens: totalCacheRead,
91
+ cacheWriteTokens: totalCacheWrite,
92
+ },
93
+ toolCalls,
94
+ };
95
+
96
+ const validationPrompt = await options.validateCompletion?.(runResult);
97
+ if (typeof validationPrompt !== 'string') return runResult;
98
+ if (validationRetries >= (options.maxValidationRetries ?? 0)) {
99
+ throw new Error(`Agent completion validation failed: ${validationPrompt}`);
100
+ }
101
+ validationRetries++;
102
+ await session.pushUserMessage(validationPrompt);
103
+ }
104
+ } finally {
105
+ await session.close();
106
+ }
107
+ }
@@ -1,104 +1,7 @@
1
1
  import { AgentSession } from './smartagent.classes.session.js';
2
- import type {
3
- IAgentRunOptions,
4
- IAgentRunResult,
5
- IAgentToolCallRecord,
6
- } from './smartagent.interfaces.js';
2
+ import { runAgentWithSession } from './runtime.run.js';
3
+ import type { IAgentRunOptions, IAgentRunResult } from './smartagent.interfaces.js';
7
4
 
8
- const mergeToolCalls = (
9
- target: IAgentToolCallRecord[],
10
- source: readonly IAgentToolCallRecord[],
11
- ): void => {
12
- const indexes = new Map(target.map((record, index) => [record.toolCallId, index]));
13
- for (const record of source) {
14
- const existingIndex = indexes.get(record.toolCallId);
15
- if (existingIndex === undefined) {
16
- target.push({ ...record });
17
- indexes.set(record.toolCallId, target.length - 1);
18
- } else {
19
- target[existingIndex] = { ...target[existingIndex], ...record };
20
- }
21
- }
22
- };
23
-
24
- export async function runAgent(options: IAgentRunOptions): Promise<IAgentRunResult> {
25
- const session = await AgentSession.create({
26
- model: options.model,
27
- system: options.system,
28
- tools: options.tools,
29
- providerOptions: options.providerOptions,
30
- sessionId: options.sessionId,
31
- eventStore: options.eventStore,
32
- cache: options.cache,
33
- maxSteps: options.maxSteps,
34
- messages: options.messages,
35
- events: options.events,
36
- executionContext: options.executionContext,
37
- contextBuilder: options.contextBuilder,
38
- contextCompactor: options.contextCompactor,
39
- eventRetention: options.eventRetention,
40
- changeListenerTimeoutMs: options.changeListenerTimeoutMs,
41
- maxPendingSessionChanges: options.maxPendingSessionChanges,
42
- generationLeaseCleanupTimeoutMs: options.generationLeaseCleanupTimeoutMs,
43
- maxArchivedTransactionTombstones: options.maxArchivedTransactionTombstones,
44
- onToken: options.onToken,
45
- onReasoningStart: options.onReasoningStart,
46
- onReasoningDelta: options.onReasoningDelta,
47
- onReasoningEnd: options.onReasoningEnd,
48
- onToolCallStart: options.onToolCallStart,
49
- onToolCallUpdate: options.onToolCallUpdate,
50
- onToolCallFinish: options.onToolCallFinish,
51
- onToolCall: options.onToolCall,
52
- onToolResult: options.onToolResult,
53
- onContextOverflow: options.onContextOverflow,
54
- maxContextOverflowRetries: options.maxContextOverflowRetries,
55
- });
56
- let totalSteps = 0;
57
- let totalInput = 0;
58
- let totalOutput = 0;
59
- let totalCacheRead = 0;
60
- let totalCacheWrite = 0;
61
- let validationRetries = 0;
62
- const toolCalls: IAgentToolCallRecord[] = [];
63
-
64
- try {
65
- await session.pushUserMessage(options.prompt);
66
- while (true) {
67
- const generationResult = await session.generate({
68
- maxSteps: options.maxSteps,
69
- abort: options.abort,
70
- });
71
- totalSteps += generationResult.steps;
72
- totalInput += generationResult.usage.inputTokens;
73
- totalOutput += generationResult.usage.outputTokens;
74
- totalCacheRead += generationResult.usage.cacheReadTokens;
75
- totalCacheWrite += generationResult.usage.cacheWriteTokens;
76
- mergeToolCalls(toolCalls, generationResult.toolCalls);
77
-
78
- const runResult: IAgentRunResult = {
79
- text: generationResult.text,
80
- messages: session.getModelMessages(),
81
- steps: totalSteps,
82
- finishReason: generationResult.finishReason,
83
- usage: {
84
- inputTokens: totalInput,
85
- outputTokens: totalOutput,
86
- totalTokens: totalInput + totalOutput,
87
- cacheReadTokens: totalCacheRead,
88
- cacheWriteTokens: totalCacheWrite,
89
- },
90
- toolCalls,
91
- };
92
-
93
- const validationPrompt = await options.validateCompletion?.(runResult);
94
- if (typeof validationPrompt !== 'string') return runResult;
95
- if (validationRetries >= (options.maxValidationRetries ?? 0)) {
96
- throw new Error(`Agent completion validation failed: ${validationPrompt}`);
97
- }
98
- validationRetries++;
99
- await session.pushUserMessage(validationPrompt);
100
- }
101
- } finally {
102
- await session.close();
103
- }
5
+ export function runAgent(options: IAgentRunOptions): Promise<IAgentRunResult> {
6
+ return runAgentWithSession(options, (sessionOptions) => AgentSession.create(sessionOptions));
104
7
  }