@agtlantis/core 0.4.1

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/index.js ADDED
@@ -0,0 +1,3082 @@
1
+ import { hasToolCall, tool, generateText, streamText } from 'ai';
2
+ import { z } from 'zod';
3
+ import Handlebars from 'handlebars';
4
+ import * as fs from 'fs/promises';
5
+ import { readFile, stat } from 'fs/promises';
6
+ import * as path3 from 'path';
7
+ import path3__default from 'path';
8
+ import * as yaml from 'yaml';
9
+ import { createHash } from 'crypto';
10
+ import { createGoogleGenerativeAI } from '@ai-sdk/google';
11
+ import { GoogleGenAI } from '@google/genai';
12
+ import merge from 'lodash/merge';
13
+ import { createOpenAI } from '@ai-sdk/openai';
14
+
15
+ // src/errors/utils.ts
16
+ function wrapAsError(error, ErrorClass, options) {
17
+ const cause = error instanceof Error ? error : new Error(String(error));
18
+ return new ErrorClass(cause.message, { ...options, cause });
19
+ }
20
+
21
+ // src/errors/types.ts
22
+ var ExecutionErrorCode = /* @__PURE__ */ ((ExecutionErrorCode2) => {
23
+ ExecutionErrorCode2["EXECUTION_ERROR"] = "EXECUTION_ERROR";
24
+ ExecutionErrorCode2["STREAM_ERROR"] = "STREAM_ERROR";
25
+ ExecutionErrorCode2["RESULT_EXTRACTION_ERROR"] = "RESULT_EXTRACTION_ERROR";
26
+ ExecutionErrorCode2["CANCELLED"] = "CANCELLED";
27
+ ExecutionErrorCode2["VALIDATION_ERROR"] = "VALIDATION_ERROR";
28
+ return ExecutionErrorCode2;
29
+ })(ExecutionErrorCode || {});
30
+ var ConfigurationErrorCode = /* @__PURE__ */ ((ConfigurationErrorCode2) => {
31
+ ConfigurationErrorCode2["CONFIG_ERROR"] = "CONFIG_ERROR";
32
+ ConfigurationErrorCode2["MISSING_API_KEY"] = "MISSING_API_KEY";
33
+ ConfigurationErrorCode2["INVALID_CONFIG"] = "INVALID_CONFIG";
34
+ ConfigurationErrorCode2["MISSING_REQUIRED"] = "MISSING_REQUIRED";
35
+ return ConfigurationErrorCode2;
36
+ })(ConfigurationErrorCode || {});
37
+ var FileErrorCode = /* @__PURE__ */ ((FileErrorCode2) => {
38
+ FileErrorCode2["FILE_ERROR"] = "FILE_ERROR";
39
+ FileErrorCode2["UPLOAD_ERROR"] = "UPLOAD_ERROR";
40
+ FileErrorCode2["DELETE_ERROR"] = "DELETE_ERROR";
41
+ FileErrorCode2["NOT_FOUND"] = "NOT_FOUND";
42
+ FileErrorCode2["TOO_LARGE"] = "TOO_LARGE";
43
+ FileErrorCode2["UNSUPPORTED_TYPE"] = "UNSUPPORTED_TYPE";
44
+ return FileErrorCode2;
45
+ })(FileErrorCode || {});
46
+ var AgtlantisError = class extends Error {
47
+ code;
48
+ cause;
49
+ context;
50
+ constructor(message, options) {
51
+ super(message);
52
+ this.name = "AgtlantisError";
53
+ this.code = options.code;
54
+ this.cause = options.cause;
55
+ this.context = options.context;
56
+ const ErrorWithCapture = Error;
57
+ ErrorWithCapture.captureStackTrace?.(this, this.constructor);
58
+ }
59
+ get isRetryable() {
60
+ return false;
61
+ }
62
+ toJSON() {
63
+ return {
64
+ name: this.name,
65
+ message: this.message,
66
+ code: this.code,
67
+ isRetryable: this.isRetryable,
68
+ context: this.context,
69
+ cause: this.cause?.message,
70
+ stack: this.stack
71
+ };
72
+ }
73
+ };
74
+ var ExecutionError = class _ExecutionError extends AgtlantisError {
75
+ constructor(message, options = {}) {
76
+ super(message, {
77
+ code: options.code ?? "EXECUTION_ERROR" /* EXECUTION_ERROR */,
78
+ cause: options.cause,
79
+ context: options.context
80
+ });
81
+ this.name = "ExecutionError";
82
+ }
83
+ static from(error, code = "EXECUTION_ERROR" /* EXECUTION_ERROR */, context) {
84
+ if (error instanceof _ExecutionError) {
85
+ return error;
86
+ }
87
+ return wrapAsError(error, _ExecutionError, { code, context });
88
+ }
89
+ };
90
+ var ConfigurationError = class _ConfigurationError extends AgtlantisError {
91
+ constructor(message, options = {}) {
92
+ super(message, {
93
+ code: options.code ?? "CONFIG_ERROR" /* CONFIG_ERROR */,
94
+ cause: options.cause,
95
+ context: options.context
96
+ });
97
+ this.name = "ConfigurationError";
98
+ }
99
+ static from(error, code = "CONFIG_ERROR" /* CONFIG_ERROR */, context) {
100
+ if (error instanceof _ConfigurationError) {
101
+ return error;
102
+ }
103
+ return wrapAsError(error, _ConfigurationError, { code, context });
104
+ }
105
+ };
106
+ var FileError = class _FileError extends AgtlantisError {
107
+ constructor(message, options = {}) {
108
+ super(message, {
109
+ code: options.code ?? "FILE_ERROR" /* FILE_ERROR */,
110
+ cause: options.cause,
111
+ context: options.context
112
+ });
113
+ this.name = "FileError";
114
+ }
115
+ static from(error, code = "FILE_ERROR" /* FILE_ERROR */, context) {
116
+ if (error instanceof _FileError) {
117
+ return error;
118
+ }
119
+ return wrapAsError(error, _FileError, { code, context });
120
+ }
121
+ };
122
+
123
+ // src/execution/constants.ts
124
+ var ERRORS = {
125
+ ALREADY_CONSUMED: "Execution already consumed",
126
+ METADATA_NOT_AVAILABLE: "Metadata not available yet. Consume the execution first.",
127
+ NO_RESULT: "No result available",
128
+ UNKNOWN_ERROR: "Execution failed with unknown error"
129
+ };
130
+
131
+ // src/execution/utils.ts
132
+ function getDuration(startTime) {
133
+ return Date.now() - startTime;
134
+ }
135
+ function combineSignals(...signals) {
136
+ const controller = new AbortController();
137
+ for (const signal of signals) {
138
+ if (signal.aborted) {
139
+ controller.abort(signal.reason);
140
+ return controller.signal;
141
+ }
142
+ signal.addEventListener("abort", () => controller.abort(signal.reason), {
143
+ once: true
144
+ });
145
+ }
146
+ return controller.signal;
147
+ }
148
+ var Deferred = class {
149
+ promise;
150
+ resolve;
151
+ reject;
152
+ constructor() {
153
+ let res;
154
+ let rej;
155
+ this.promise = new Promise((resolve2, reject) => {
156
+ res = resolve2;
157
+ rej = reject;
158
+ });
159
+ this.resolve = res;
160
+ this.reject = rej;
161
+ }
162
+ };
163
+
164
+ // src/execution/shared.ts
165
+ function isAbortError(error, signal) {
166
+ if (error instanceof Error && error.name === "AbortError") {
167
+ return true;
168
+ }
169
+ return signal.aborted;
170
+ }
171
+ function normalizeError(error) {
172
+ return error instanceof Error ? error : new Error(String(error));
173
+ }
174
+ function determineResultStatus(cancelRequested, aborted, hasError) {
175
+ if (cancelRequested || aborted) {
176
+ return "canceled";
177
+ }
178
+ if (hasError) {
179
+ return "failed";
180
+ }
181
+ return "succeeded";
182
+ }
183
+ function createHookRunner(runHooks) {
184
+ let ran = false;
185
+ return {
186
+ ensureRun: async () => {
187
+ if (!ran) {
188
+ ran = true;
189
+ await runHooks();
190
+ }
191
+ },
192
+ hasRun: () => ran
193
+ };
194
+ }
195
+
196
+ // src/execution/streaming-host.ts
197
+ var StreamingExecutionHost = class {
198
+ constructor(createSession, generator, userSignal) {
199
+ this.createSession = createSession;
200
+ this.generator = generator;
201
+ this.effectiveSignal = userSignal ? combineSignals(userSignal, this.abortController.signal) : this.abortController.signal;
202
+ this.consumerPromise = this.startConsuming();
203
+ }
204
+ abortController = new AbortController();
205
+ effectiveSignal;
206
+ consumerPromise;
207
+ eventBuffer = [];
208
+ subscribers = /* @__PURE__ */ new Set();
209
+ completed = false;
210
+ cleaned = false;
211
+ hookRunner = null;
212
+ cancelRequested = false;
213
+ extractedOutcome = null;
214
+ extractedSummary = null;
215
+ hasDataField(event) {
216
+ return "data" in event && event.data !== void 0;
217
+ }
218
+ hasSummaryField(event) {
219
+ return "summary" in event && event.summary !== void 0;
220
+ }
221
+ hasErrorField(event) {
222
+ return "error" in event && event.error instanceof Error;
223
+ }
224
+ extractResultAndMetadata(event) {
225
+ const isCompleteOrError = event.type === "complete" || event.type === "error";
226
+ if (!isCompleteOrError) {
227
+ return;
228
+ }
229
+ if (this.hasErrorField(event)) {
230
+ this.extractedOutcome = { type: "error", error: event.error };
231
+ } else if (this.hasDataField(event)) {
232
+ this.extractedOutcome = { type: "result", value: event.data };
233
+ }
234
+ if (this.hasSummaryField(event)) {
235
+ this.extractedSummary = event.summary;
236
+ }
237
+ }
238
+ notifySubscribers(event) {
239
+ this.subscribers.forEach((fn) => fn(event));
240
+ }
241
+ async startConsuming() {
242
+ const session = this.createSession(this.effectiveSignal);
243
+ this.hookRunner = createHookRunner(() => session.runOnDoneHooks());
244
+ const gen = this.generator(session);
245
+ try {
246
+ let next = await gen.next();
247
+ while (!next.done) {
248
+ this.eventBuffer.push(next.value);
249
+ this.notifySubscribers(next.value);
250
+ const isTerminal = next.value.type === "complete" || next.value.type === "error";
251
+ if (isTerminal) {
252
+ this.extractResultAndMetadata(next.value);
253
+ this.abortController.abort();
254
+ break;
255
+ }
256
+ if (this.abortController.signal.aborted) {
257
+ break;
258
+ }
259
+ next = await gen.next();
260
+ }
261
+ if (next.done && next.value !== void 0) {
262
+ const finalEvent = await Promise.resolve(next.value);
263
+ this.eventBuffer.push(finalEvent);
264
+ this.notifySubscribers(finalEvent);
265
+ this.extractResultAndMetadata(finalEvent);
266
+ const isTerminal = finalEvent.type === "complete" || finalEvent.type === "error";
267
+ if (isTerminal) {
268
+ this.abortController.abort();
269
+ }
270
+ }
271
+ return this.buildResult(session);
272
+ } catch (error) {
273
+ const errorObj = normalizeError(error);
274
+ if (isAbortError(error, this.abortController.signal)) {
275
+ return {
276
+ success: false,
277
+ aborted: true,
278
+ error: errorObj,
279
+ summary: await session.getSummary()
280
+ };
281
+ }
282
+ const errorEvent = await session.fail(errorObj);
283
+ this.eventBuffer.push(errorEvent);
284
+ this.notifySubscribers(errorEvent);
285
+ this.extractResultAndMetadata(errorEvent);
286
+ this.abortController.abort();
287
+ return this.buildResult(session);
288
+ } finally {
289
+ this.completed = true;
290
+ await this.hookRunner?.ensureRun();
291
+ await gen.return(void 0);
292
+ }
293
+ }
294
+ async buildResult(session) {
295
+ const summary = this.extractedSummary ?? await session.getSummary();
296
+ if (this.extractedOutcome?.type === "error") {
297
+ return {
298
+ success: false,
299
+ aborted: false,
300
+ error: this.extractedOutcome.error,
301
+ summary
302
+ };
303
+ }
304
+ if (this.extractedOutcome?.type === "result") {
305
+ return {
306
+ success: true,
307
+ result: this.extractedOutcome.value,
308
+ summary
309
+ };
310
+ }
311
+ return {
312
+ success: false,
313
+ aborted: true,
314
+ error: new Error(ERRORS.NO_RESULT),
315
+ summary
316
+ };
317
+ }
318
+ /**
319
+ * Get the event stream.
320
+ * Returns buffered events first, then real-time events.
321
+ * Can be called multiple times - replays buffer each time.
322
+ */
323
+ async *stream() {
324
+ let index = 0;
325
+ while (index < this.eventBuffer.length) {
326
+ yield this.eventBuffer[index++];
327
+ }
328
+ if (this.completed) {
329
+ return;
330
+ }
331
+ const queue = [];
332
+ let pending = new Deferred();
333
+ const subscriber = (event) => {
334
+ queue.push(event);
335
+ pending.resolve();
336
+ };
337
+ this.subscribers.add(subscriber);
338
+ try {
339
+ while (!this.completed || queue.length > 0) {
340
+ if (queue.length > 0) {
341
+ yield queue.shift();
342
+ } else if (!this.completed) {
343
+ await pending.promise;
344
+ pending = new Deferred();
345
+ }
346
+ }
347
+ } finally {
348
+ this.subscribers.delete(subscriber);
349
+ }
350
+ }
351
+ cancel() {
352
+ this.cancelRequested = true;
353
+ this.abortController.abort();
354
+ }
355
+ async cleanup() {
356
+ if (this.cleaned) {
357
+ return;
358
+ }
359
+ this.cleaned = true;
360
+ if (!this.completed) {
361
+ this.cancel();
362
+ await this.consumerPromise.catch(() => {
363
+ });
364
+ }
365
+ this.subscribers.clear();
366
+ await this.hookRunner?.ensureRun();
367
+ }
368
+ async [Symbol.asyncDispose]() {
369
+ await this.cleanup();
370
+ }
371
+ /**
372
+ * Get the execution result with status, summary, and all events.
373
+ * Never throws - returns a discriminated union with status.
374
+ */
375
+ async result() {
376
+ const internal = await this.consumerPromise;
377
+ const events = Object.freeze([...this.eventBuffer]);
378
+ if (internal.success) {
379
+ return {
380
+ status: "succeeded",
381
+ value: internal.result,
382
+ summary: internal.summary,
383
+ events
384
+ };
385
+ }
386
+ if (this.cancelRequested || internal.aborted) {
387
+ return {
388
+ status: "canceled",
389
+ summary: internal.summary,
390
+ events
391
+ };
392
+ }
393
+ return {
394
+ status: "failed",
395
+ error: internal.error,
396
+ summary: internal.summary,
397
+ events
398
+ };
399
+ }
400
+ };
401
+
402
+ // src/execution/simple-host.ts
403
+ var SimpleExecutionHost = class {
404
+ abortController = new AbortController();
405
+ effectiveSignal;
406
+ consumerPromise;
407
+ cachedSession;
408
+ startTime = Date.now();
409
+ cancelRequested = false;
410
+ constructor(createSession, fn, userSignal) {
411
+ this.effectiveSignal = userSignal ? combineSignals(userSignal, this.abortController.signal) : this.abortController.signal;
412
+ this.consumerPromise = this.execute(createSession, fn);
413
+ }
414
+ async execute(createSession, fn) {
415
+ const session = createSession(this.effectiveSignal);
416
+ this.cachedSession = session;
417
+ const hookRunner = createHookRunner(() => session.runOnDoneHooks());
418
+ session.notifyExecutionStart();
419
+ try {
420
+ const result = await fn(session);
421
+ await session.notifyExecutionDone(result, this.startTime);
422
+ return {
423
+ success: true,
424
+ result,
425
+ summary: await session.getSummary()
426
+ };
427
+ } catch (error) {
428
+ const errorObj = normalizeError(error);
429
+ const isCancellation = isAbortError(error, this.abortController.signal);
430
+ if (!isCancellation) {
431
+ await session.notifyExecutionError(errorObj, this.startTime);
432
+ }
433
+ return {
434
+ success: false,
435
+ error: errorObj,
436
+ aborted: isCancellation,
437
+ summary: await session.getSummary()
438
+ };
439
+ } finally {
440
+ await hookRunner.ensureRun();
441
+ }
442
+ }
443
+ /**
444
+ * Request cancellation of the execution.
445
+ * Aborts the current LLM call if in progress.
446
+ * No-op if execution already completed.
447
+ */
448
+ cancel() {
449
+ this.cancelRequested = true;
450
+ this.abortController.abort();
451
+ }
452
+ /**
453
+ * Get the execution result with status and summary.
454
+ * Never throws - returns a discriminated union with status.
455
+ */
456
+ async result() {
457
+ const internal = await this.consumerPromise;
458
+ if (internal.success) {
459
+ return {
460
+ status: "succeeded",
461
+ value: internal.result,
462
+ summary: internal.summary
463
+ };
464
+ }
465
+ if (this.cancelRequested || internal.aborted) {
466
+ return {
467
+ status: "canceled",
468
+ summary: internal.summary
469
+ };
470
+ }
471
+ return {
472
+ status: "failed",
473
+ error: internal.error,
474
+ summary: internal.summary
475
+ };
476
+ }
477
+ /**
478
+ * Cleanup resources.
479
+ * For SimpleExecution, hooks are already run during execution,
480
+ * so this is intentionally a no-op.
481
+ */
482
+ async cleanup() {
483
+ }
484
+ async [Symbol.asyncDispose]() {
485
+ await this.cleanup();
486
+ }
487
+ };
488
+
489
+ // src/execution/mapping.ts
490
+ function mapExecution(execution, fn) {
491
+ if ("stream" in execution) {
492
+ return mapStreamingExecution(execution, fn);
493
+ }
494
+ return mapSimpleExecution(execution, fn);
495
+ }
496
+ function mapExecutionResult(execution, fn) {
497
+ if ("stream" in execution) {
498
+ return mapStreamingExecutionResult(execution, fn);
499
+ }
500
+ return mapSimpleExecution(execution, fn);
501
+ }
502
+ function mapStreamingExecution(execution, fn) {
503
+ return {
504
+ stream() {
505
+ const original = execution.stream();
506
+ return {
507
+ [Symbol.asyncIterator]() {
508
+ const iter = original[Symbol.asyncIterator]();
509
+ return {
510
+ async next() {
511
+ const { value, done } = await iter.next();
512
+ if (done) return { value: void 0, done: true };
513
+ const event = value;
514
+ if (event.type === "error") {
515
+ return { value: event, done: false };
516
+ }
517
+ const { metrics, ...pureEvent } = event;
518
+ try {
519
+ const mapped = await fn(pureEvent);
520
+ return {
521
+ value: { ...mapped, metrics },
522
+ done: false
523
+ };
524
+ } catch (err) {
525
+ const errorEvent = {
526
+ type: "error",
527
+ error: normalizeError(err),
528
+ metrics
529
+ };
530
+ return { value: errorEvent, done: false };
531
+ }
532
+ }
533
+ };
534
+ }
535
+ };
536
+ },
537
+ async result() {
538
+ const original = await execution.result();
539
+ if (original.status !== "succeeded") {
540
+ return original;
541
+ }
542
+ try {
543
+ const mappedEvents = [];
544
+ for (const event of original.events) {
545
+ if (event.type === "error") {
546
+ mappedEvents.push(event);
547
+ continue;
548
+ }
549
+ const { metrics, ...pureEvent } = event;
550
+ const mapped = await fn(pureEvent);
551
+ mappedEvents.push({ ...mapped, metrics });
552
+ }
553
+ const completionEvent = mappedEvents.find((e) => e.type === "complete");
554
+ return {
555
+ status: "succeeded",
556
+ value: completionEvent.data,
557
+ events: mappedEvents,
558
+ summary: original.summary
559
+ };
560
+ } catch (err) {
561
+ return {
562
+ status: "failed",
563
+ error: normalizeError(err),
564
+ events: original.events,
565
+ summary: original.summary
566
+ };
567
+ }
568
+ },
569
+ cancel: () => execution.cancel(),
570
+ cleanup: () => execution.cleanup(),
571
+ [Symbol.asyncDispose]: () => execution[Symbol.asyncDispose]()
572
+ };
573
+ }
574
+ function mapStreamingExecutionResult(execution, fn) {
575
+ return {
576
+ stream() {
577
+ const original = execution.stream();
578
+ return {
579
+ [Symbol.asyncIterator]() {
580
+ const iter = original[Symbol.asyncIterator]();
581
+ return {
582
+ async next() {
583
+ const { value, done } = await iter.next();
584
+ if (done) return { value: void 0, done: true };
585
+ const event = value;
586
+ if (event.type === "complete") {
587
+ const { metrics, ...rest } = event;
588
+ try {
589
+ const mapped = await fn(rest.data);
590
+ return {
591
+ value: { type: "complete", data: mapped, summary: rest.summary, metrics },
592
+ done: false
593
+ };
594
+ } catch (err) {
595
+ const errorEvent = {
596
+ type: "error",
597
+ error: normalizeError(err),
598
+ metrics
599
+ };
600
+ return { value: errorEvent, done: false };
601
+ }
602
+ }
603
+ return { value: event, done: false };
604
+ }
605
+ };
606
+ }
607
+ };
608
+ },
609
+ async result() {
610
+ const original = await execution.result();
611
+ if (original.status !== "succeeded") {
612
+ return original;
613
+ }
614
+ try {
615
+ const mapped = await fn(original.value);
616
+ const mappedEvents = original.events.map((event) => {
617
+ if (event.type === "complete") {
618
+ return { ...event, data: mapped };
619
+ }
620
+ return event;
621
+ });
622
+ return {
623
+ status: "succeeded",
624
+ value: mapped,
625
+ events: mappedEvents,
626
+ summary: original.summary
627
+ };
628
+ } catch (err) {
629
+ return {
630
+ status: "failed",
631
+ error: normalizeError(err),
632
+ events: original.events,
633
+ summary: original.summary
634
+ };
635
+ }
636
+ },
637
+ cancel: () => execution.cancel(),
638
+ cleanup: () => execution.cleanup(),
639
+ [Symbol.asyncDispose]: () => execution[Symbol.asyncDispose]()
640
+ };
641
+ }
642
+ function mapSimpleExecution(execution, fn) {
643
+ return {
644
+ async result() {
645
+ const original = await execution.result();
646
+ if (original.status !== "succeeded") {
647
+ return original;
648
+ }
649
+ try {
650
+ const mapped = await fn(original.value);
651
+ return {
652
+ status: "succeeded",
653
+ value: mapped,
654
+ summary: original.summary
655
+ };
656
+ } catch (err) {
657
+ return {
658
+ status: "failed",
659
+ error: normalizeError(err),
660
+ summary: original.summary
661
+ };
662
+ }
663
+ },
664
+ cancel: () => execution.cancel(),
665
+ cleanup: () => execution.cleanup(),
666
+ [Symbol.asyncDispose]: () => execution[Symbol.asyncDispose]()
667
+ };
668
+ }
669
+
670
+ // src/observability/logger.ts
671
+ var noopLogger = {};
672
+ function createLogger(handlers) {
673
+ return handlers;
674
+ }
675
+ var TOOL_DESCRIPTIONS = {
676
+ reportProgress: "[OPTIONAL] Report progress during task execution. Use this to show intermediate work. You may call this multiple times, then you MUST call tools::submitResult.",
677
+ submitResult: "[REQUIRED] Submit the final result. You MUST call this exactly once to complete the task. Without this call, the task FAILS."
678
+ };
679
+ var TOOL_CALLING_PROTOCOL = `## CRITICAL INSTRUCTION - READ CAREFULLY
680
+
681
+ You have 2 tools available:
682
+ 1. reportProgress - [OPTIONAL] Show intermediate work (multiple times)
683
+ 2. submitResult - [REQUIRED] Submit your final answer
684
+
685
+ \u26A0\uFE0F IMPORTANT RULES:
686
+ - You may call reportProgress 0-3 times to show progress
687
+ - You MUST call submitResult exactly once to complete the task
688
+ - After calling reportProgress, you MUST call submitResult in your NEXT response
689
+ - If you call reportProgress more than 3 times without submitResult, the task FAILS
690
+ - The task is NOT complete until submitResult is called
691
+
692
+ CORRECT SEQUENCE:
693
+ 1. [Optional] reportProgress
694
+ 2. [Required] submitResult (exactly once)
695
+
696
+ \u274C WRONG: reportProgress \u2192 reportProgress \u2192 reportProgress \u2192 reportProgress (no submitResult = FAIL)
697
+ \u2705 CORRECT: reportProgress \u2192 submitResult (SUCCESS)`;
698
+ function defineProgressivePattern(config) {
699
+ return new ProgressivePattern(config.progressSchema, config.resultSchema);
700
+ }
701
+ var ProgressivePattern = class {
702
+ constructor(progressSchema, resultSchema) {
703
+ this.progressSchema = progressSchema;
704
+ this.resultSchema = resultSchema;
705
+ }
706
+ lastParseError = null;
707
+ /**
708
+ * Runs the pattern within an existing session. Use this for composing
709
+ * multiple patterns or when you need fine-grained control over the session.
710
+ *
711
+ * @param session - The streaming session to run within
712
+ * @param options - Stream options including:
713
+ * - `stopWhen` - Additional stop conditions (combined with default `hasToolCall('submitResult')`)
714
+ * - `protocol` - Custom protocol instructions (replaces default `TOOL_CALLING_PROTOCOL`)
715
+ * - All other `streamText` options except `tools`, `toolChoice`, `stopWhen`
716
+ *
717
+ * @example Basic usage
718
+ * ```typescript
719
+ * provider.streamingExecution(async function*(session) {
720
+ * yield* pattern.runInSession(session, {
721
+ * system: 'You are an analyzer.',
722
+ * messages: [{ role: 'user', content: 'Analyze...' }],
723
+ * });
724
+ * });
725
+ * ```
726
+ *
727
+ * @example With custom stopWhen
728
+ * ```typescript
729
+ * yield* pattern.runInSession(session, {
730
+ * prompt: 'Analyze...',
731
+ * stopWhen: stepCountIs(5), // Combined: submitResult OR 5 steps
732
+ * });
733
+ * ```
734
+ */
735
+ async *runInSession(session, options) {
736
+ const {
737
+ tools: userTools,
738
+ system,
739
+ stopWhen: userStopWhen,
740
+ protocol,
741
+ ...restOptions
742
+ } = options;
743
+ const internalTools = this.createTools();
744
+ const allTools = { ...userTools, ...internalTools };
745
+ const systemString = typeof system === "string" ? system : void 0;
746
+ const fullSystem = this.renderSystemPrompt(systemString, protocol);
747
+ const defaultStopCondition = hasToolCall("submitResult");
748
+ const stopConditions = this.combineStopConditions(defaultStopCondition, userStopWhen);
749
+ const stream = session.streamText({
750
+ ...restOptions,
751
+ system: fullSystem,
752
+ tools: allTools,
753
+ toolChoice: "required",
754
+ stopWhen: stopConditions
755
+ });
756
+ let result = null;
757
+ for await (const part of stream.fullStream) {
758
+ if (part.type === "tool-call") {
759
+ if (part.toolName === "reportProgress") {
760
+ const input = "input" in part ? part.input : void 0;
761
+ const progressData = this.parseProgressInput(input);
762
+ if (progressData !== null) {
763
+ yield session.emit({
764
+ type: "progress",
765
+ data: progressData
766
+ });
767
+ }
768
+ } else if (part.toolName === "submitResult") {
769
+ const input = "input" in part ? part.input : void 0;
770
+ result = this.parseResultInput(input);
771
+ }
772
+ }
773
+ }
774
+ if (result === null) {
775
+ const baseMsg = "ProgressivePattern: No result received.";
776
+ const detail = this.lastParseError ? ` Last parse error: ${this.lastParseError.message}` : " The LLM did not call submitResult tool.";
777
+ throw new Error(baseMsg + detail);
778
+ }
779
+ const completeEvent = await session.done(result);
780
+ yield completeEvent;
781
+ return completeEvent;
782
+ }
783
+ /**
784
+ * Standalone execution that creates a new session internally.
785
+ * Use this for simple, single-pattern executions.
786
+ *
787
+ * @param provider - The AI provider to use
788
+ * @param options - Stream options including:
789
+ * - `stopWhen` - Additional stop conditions (combined with default `hasToolCall('submitResult')`)
790
+ * - `protocol` - Custom protocol instructions (replaces default `TOOL_CALLING_PROTOCOL`)
791
+ *
792
+ * @example Basic usage
793
+ * ```typescript
794
+ * for await (const event of pattern.run(provider, {
795
+ * system: 'You are an analyzer.',
796
+ * prompt: 'Analyze this document...',
797
+ * })) {
798
+ * if (event.type === 'progress') {
799
+ * console.log('Progress:', event.data);
800
+ * } else if (event.type === 'complete') {
801
+ * console.log('Result:', event.data);
802
+ * }
803
+ * }
804
+ * ```
805
+ *
806
+ * @example With step limit
807
+ * ```typescript
808
+ * import { stepCountIs } from 'ai';
809
+ *
810
+ * for await (const event of pattern.run(provider, {
811
+ * prompt: 'Analyze...',
812
+ * stopWhen: stepCountIs(10),
813
+ * })) { ... }
814
+ * ```
815
+ */
816
+ run(provider, options) {
817
+ const self = this;
818
+ const execution = provider.streamingExecution(async function* (session) {
819
+ return yield* self.runInSession(session, options);
820
+ });
821
+ return execution.stream();
822
+ }
823
+ createTools() {
824
+ return {
825
+ reportProgress: tool({
826
+ description: TOOL_DESCRIPTIONS.reportProgress,
827
+ inputSchema: z.object({
828
+ data: this.progressSchema
829
+ }),
830
+ execute: async () => ({
831
+ status: "progress_recorded",
832
+ instruction: "After all progress reports, you MUST call tools::submitResult to complete the task."
833
+ })
834
+ }),
835
+ submitResult: tool({
836
+ description: TOOL_DESCRIPTIONS.submitResult,
837
+ inputSchema: z.object({
838
+ data: this.resultSchema
839
+ }),
840
+ execute: async () => ({
841
+ status: "result_submitted",
842
+ message: "Task completed successfully."
843
+ })
844
+ })
845
+ };
846
+ }
847
+ parseJsonWrapper(input, schema) {
848
+ try {
849
+ if (!input || typeof input !== "object") return null;
850
+ const wrapper = input;
851
+ if (wrapper.data === void 0) return null;
852
+ const parsed = typeof wrapper.data === "string" ? JSON.parse(wrapper.data) : wrapper.data;
853
+ return schema.parse(parsed);
854
+ } catch (error) {
855
+ this.lastParseError = error instanceof Error ? error : new Error(String(error));
856
+ return null;
857
+ }
858
+ }
859
+ parseProgressInput(input) {
860
+ return this.parseJsonWrapper(input, this.progressSchema);
861
+ }
862
+ parseResultInput(input) {
863
+ return this.parseJsonWrapper(input, this.resultSchema);
864
+ }
865
+ combineStopConditions(defaultCondition, userConditions) {
866
+ if (!userConditions) {
867
+ return [defaultCondition];
868
+ }
869
+ const userArray = Array.isArray(userConditions) ? userConditions : [userConditions];
870
+ return [defaultCondition, ...userArray];
871
+ }
872
+ renderSystemPrompt(userSystem, protocol) {
873
+ const protocolText = protocol ?? TOOL_CALLING_PROTOCOL;
874
+ return userSystem ? `${userSystem}
875
+
876
+ ${protocolText}` : protocolText;
877
+ }
878
+ };
879
+
880
+ // src/pricing/validator.ts
881
+ function validatePriceValue(value, fieldName, context) {
882
+ if (!Number.isFinite(value)) {
883
+ throw new Error(`${context}: ${fieldName} must be a finite number`);
884
+ }
885
+ if (value < 0) {
886
+ throw new Error(`${context}: ${fieldName} cannot be negative`);
887
+ }
888
+ }
889
+ function validateModelPricing(pricing, context) {
890
+ validatePriceValue(
891
+ pricing.inputPricePerMillion,
892
+ "inputPricePerMillion",
893
+ context
894
+ );
895
+ validatePriceValue(
896
+ pricing.outputPricePerMillion,
897
+ "outputPricePerMillion",
898
+ context
899
+ );
900
+ if (pricing.cachedInputPricePerMillion !== void 0) {
901
+ validatePriceValue(
902
+ pricing.cachedInputPricePerMillion,
903
+ "cachedInputPricePerMillion",
904
+ context
905
+ );
906
+ }
907
+ }
908
+ function validateProviderPricing(pricing, providerContext) {
909
+ for (const [model, modelPricing] of Object.entries(pricing)) {
910
+ const context = providerContext ? `${providerContext}/${model}` : model;
911
+ validateModelPricing(modelPricing, context);
912
+ }
913
+ }
914
+ function validatePricingConfig(config) {
915
+ if (config.providers) {
916
+ for (const [providerKey, pricing] of Object.entries(config.providers)) {
917
+ if (pricing) {
918
+ validateProviderPricing(pricing, providerKey);
919
+ }
920
+ }
921
+ }
922
+ if (config.fallback) {
923
+ validateModelPricing(config.fallback, "fallback");
924
+ }
925
+ }
926
+
927
+ // src/pricing/defaults.ts
928
+ var OPENAI_PRICING = {
929
+ "gpt-4o": { inputPricePerMillion: 2.5, outputPricePerMillion: 10 },
930
+ "gpt-4o-mini": { inputPricePerMillion: 0.15, outputPricePerMillion: 0.6 },
931
+ "gpt-4-turbo": { inputPricePerMillion: 10, outputPricePerMillion: 30 },
932
+ "gpt-4-turbo-preview": {
933
+ inputPricePerMillion: 10,
934
+ outputPricePerMillion: 30
935
+ },
936
+ "gpt-4": { inputPricePerMillion: 30, outputPricePerMillion: 60 },
937
+ "gpt-4-32k": { inputPricePerMillion: 60, outputPricePerMillion: 120 },
938
+ "gpt-3.5-turbo": { inputPricePerMillion: 0.5, outputPricePerMillion: 1.5 },
939
+ "gpt-3.5-turbo-16k": {
940
+ inputPricePerMillion: 3,
941
+ outputPricePerMillion: 4
942
+ },
943
+ "o1": { inputPricePerMillion: 15, outputPricePerMillion: 60 },
944
+ "o1-mini": { inputPricePerMillion: 3, outputPricePerMillion: 12 },
945
+ "o1-preview": { inputPricePerMillion: 15, outputPricePerMillion: 60 },
946
+ "o3": { inputPricePerMillion: 20, outputPricePerMillion: 80 },
947
+ "o3-mini": { inputPricePerMillion: 4, outputPricePerMillion: 16 }
948
+ };
949
+ var GOOGLE_PRICING = {
950
+ "gemini-2.5-flash": {
951
+ inputPricePerMillion: 0.15,
952
+ outputPricePerMillion: 0.6,
953
+ cachedInputPricePerMillion: 0.0375
954
+ },
955
+ "gemini-2.5-flash-lite": {
956
+ inputPricePerMillion: 0.075,
957
+ outputPricePerMillion: 0.3,
958
+ cachedInputPricePerMillion: 0.01875
959
+ },
960
+ "gemini-2.5-pro": {
961
+ inputPricePerMillion: 1.25,
962
+ outputPricePerMillion: 10,
963
+ cachedInputPricePerMillion: 0.3125
964
+ },
965
+ "gemini-2.0-flash": {
966
+ inputPricePerMillion: 0.1,
967
+ outputPricePerMillion: 0.4,
968
+ cachedInputPricePerMillion: 0.025
969
+ },
970
+ "gemini-2.0-flash-lite": {
971
+ inputPricePerMillion: 0.075,
972
+ outputPricePerMillion: 0.3,
973
+ cachedInputPricePerMillion: 0.01875
974
+ },
975
+ "gemini-1.5-pro": {
976
+ inputPricePerMillion: 1.25,
977
+ outputPricePerMillion: 5,
978
+ cachedInputPricePerMillion: 0.3125
979
+ },
980
+ "gemini-1.5-flash": {
981
+ inputPricePerMillion: 0.075,
982
+ outputPricePerMillion: 0.3,
983
+ cachedInputPricePerMillion: 0.01875
984
+ },
985
+ "gemini-1.5-flash-8b": {
986
+ inputPricePerMillion: 0.0375,
987
+ outputPricePerMillion: 0.15,
988
+ cachedInputPricePerMillion: 0.01
989
+ },
990
+ "gemini-pro": { inputPricePerMillion: 0.5, outputPricePerMillion: 1.5 }
991
+ };
992
+ var ANTHROPIC_PRICING = {
993
+ "claude-opus-4-5-20250514": {
994
+ inputPricePerMillion: 15,
995
+ outputPricePerMillion: 75,
996
+ cachedInputPricePerMillion: 1.875
997
+ },
998
+ "claude-sonnet-4-20250514": {
999
+ inputPricePerMillion: 3,
1000
+ outputPricePerMillion: 15,
1001
+ cachedInputPricePerMillion: 0.375
1002
+ },
1003
+ "claude-3-5-sonnet-20241022": {
1004
+ inputPricePerMillion: 3,
1005
+ outputPricePerMillion: 15,
1006
+ cachedInputPricePerMillion: 0.375
1007
+ },
1008
+ "claude-3-5-haiku-20241022": {
1009
+ inputPricePerMillion: 0.8,
1010
+ outputPricePerMillion: 4,
1011
+ cachedInputPricePerMillion: 0.1
1012
+ },
1013
+ "claude-3-opus-20240229": {
1014
+ inputPricePerMillion: 15,
1015
+ outputPricePerMillion: 75,
1016
+ cachedInputPricePerMillion: 1.875
1017
+ },
1018
+ "claude-3-sonnet-20240229": {
1019
+ inputPricePerMillion: 3,
1020
+ outputPricePerMillion: 15,
1021
+ cachedInputPricePerMillion: 0.375
1022
+ },
1023
+ "claude-3-haiku-20240307": {
1024
+ inputPricePerMillion: 0.25,
1025
+ outputPricePerMillion: 1.25,
1026
+ cachedInputPricePerMillion: 0.03
1027
+ }
1028
+ };
1029
+ var DEFAULT_PRICING_CONFIG = {
1030
+ providers: {
1031
+ openai: OPENAI_PRICING,
1032
+ google: GOOGLE_PRICING,
1033
+ anthropic: ANTHROPIC_PRICING
1034
+ },
1035
+ fallback: {
1036
+ inputPricePerMillion: 1,
1037
+ outputPricePerMillion: 5
1038
+ }
1039
+ };
1040
+ var DEFAULT_FALLBACK_PRICING = {
1041
+ inputPricePerMillion: 1,
1042
+ outputPricePerMillion: 5
1043
+ };
1044
+
1045
+ // src/pricing/config.ts
1046
+ var globalConfig;
1047
+ function configurePricing(config) {
1048
+ if (config) {
1049
+ validatePricingConfig(config);
1050
+ }
1051
+ globalConfig = config;
1052
+ }
1053
+ function getPricingConfig() {
1054
+ return globalConfig;
1055
+ }
1056
+ function resetPricingConfig() {
1057
+ globalConfig = void 0;
1058
+ }
1059
+ function getEffectivePricing(model, provider) {
1060
+ if (globalConfig?.providers?.[provider]?.[model]) {
1061
+ return {
1062
+ pricing: globalConfig.providers[provider][model],
1063
+ source: "global"
1064
+ };
1065
+ }
1066
+ const defaultPricing = DEFAULT_PRICING_CONFIG.providers[provider];
1067
+ if (defaultPricing?.[model]) {
1068
+ return {
1069
+ pricing: defaultPricing[model],
1070
+ source: "default"
1071
+ };
1072
+ }
1073
+ return {
1074
+ pricing: globalConfig?.fallback ?? DEFAULT_PRICING_CONFIG.fallback ?? DEFAULT_FALLBACK_PRICING,
1075
+ source: "fallback"
1076
+ };
1077
+ }
1078
+
1079
+ // src/pricing/calculator.ts
1080
+ var TOKENS_PER_MILLION = 1e6;
1081
+ function getModelPricing(model, provider, providerPricing) {
1082
+ if (providerPricing?.[model]) {
1083
+ return providerPricing[model];
1084
+ }
1085
+ const globalConfig2 = getPricingConfig();
1086
+ if (globalConfig2?.providers?.[provider]?.[model]) {
1087
+ return globalConfig2.providers[provider][model];
1088
+ }
1089
+ const defaultProviderPricing = DEFAULT_PRICING_CONFIG.providers[provider];
1090
+ if (defaultProviderPricing?.[model]) {
1091
+ return defaultProviderPricing[model];
1092
+ }
1093
+ return globalConfig2?.fallback ?? DEFAULT_PRICING_CONFIG.fallback ?? DEFAULT_FALLBACK_PRICING;
1094
+ }
1095
+ function validateCostParams(params) {
1096
+ const { inputTokens, outputTokens, cachedInputTokens = 0 } = params;
1097
+ if (inputTokens < 0 || outputTokens < 0 || cachedInputTokens < 0) {
1098
+ throw new Error("Token counts must be non-negative");
1099
+ }
1100
+ if (cachedInputTokens > inputTokens) {
1101
+ throw new Error("cachedInputTokens cannot exceed inputTokens");
1102
+ }
1103
+ if (!Number.isFinite(inputTokens) || !Number.isFinite(outputTokens) || !Number.isFinite(cachedInputTokens)) {
1104
+ throw new Error("Token counts must be finite numbers");
1105
+ }
1106
+ }
1107
+ function calculateCost(params, providerPricing) {
1108
+ validateCostParams(params);
1109
+ const {
1110
+ inputTokens,
1111
+ outputTokens,
1112
+ cachedInputTokens = 0,
1113
+ model,
1114
+ provider
1115
+ } = params;
1116
+ const pricing = getModelPricing(model, provider, providerPricing);
1117
+ const nonCachedInputTokens = inputTokens - cachedInputTokens;
1118
+ const inputCost = nonCachedInputTokens / TOKENS_PER_MILLION * pricing.inputPricePerMillion;
1119
+ const outputCost = outputTokens / TOKENS_PER_MILLION * pricing.outputPricePerMillion;
1120
+ const cachedInputPricePerMillion = pricing.cachedInputPricePerMillion ?? pricing.inputPricePerMillion;
1121
+ const cachedInputCost = cachedInputTokens / TOKENS_PER_MILLION * cachedInputPricePerMillion;
1122
+ return {
1123
+ total: inputCost + outputCost + cachedInputCost,
1124
+ inputCost,
1125
+ outputCost,
1126
+ cachedInputCost
1127
+ };
1128
+ }
1129
+ function calculateCostFromUsage(usage, model, provider, providerPricing) {
1130
+ return calculateCost(
1131
+ {
1132
+ inputTokens: usage.inputTokens ?? 0,
1133
+ outputTokens: usage.outputTokens ?? 0,
1134
+ cachedInputTokens: usage.inputTokenDetails?.cacheReadTokens ?? 0,
1135
+ model,
1136
+ provider
1137
+ },
1138
+ providerPricing
1139
+ );
1140
+ }
1141
+ function calculateTotalCost(calls, providerPricing) {
1142
+ const costByModel = {};
1143
+ let totalCost = 0;
1144
+ for (const call of calls) {
1145
+ const cost = calculateCostFromUsage(
1146
+ call.usage,
1147
+ call.model,
1148
+ call.provider,
1149
+ providerPricing
1150
+ );
1151
+ totalCost += cost.total;
1152
+ const key = `${call.provider}/${call.model}`;
1153
+ costByModel[key] = (costByModel[key] ?? 0) + cost.total;
1154
+ }
1155
+ return { totalCost, costByModel };
1156
+ }
1157
+
1158
+ // src/prompt/errors.ts
1159
+ var PromptErrorCode = /* @__PURE__ */ ((PromptErrorCode2) => {
1160
+ PromptErrorCode2["PROMPT_ERROR"] = "PROMPT_ERROR";
1161
+ PromptErrorCode2["NOT_FOUND"] = "PROMPT_NOT_FOUND";
1162
+ PromptErrorCode2["INVALID_FORMAT"] = "PROMPT_INVALID_FORMAT";
1163
+ PromptErrorCode2["TEMPLATE_ERROR"] = "PROMPT_TEMPLATE_ERROR";
1164
+ PromptErrorCode2["IO_ERROR"] = "PROMPT_IO_ERROR";
1165
+ return PromptErrorCode2;
1166
+ })(PromptErrorCode || {});
1167
+ var PromptError = class _PromptError extends AgtlantisError {
1168
+ constructor(message, options = {}) {
1169
+ super(message, {
1170
+ code: options.code ?? "PROMPT_ERROR" /* PROMPT_ERROR */,
1171
+ cause: options.cause,
1172
+ context: options.context
1173
+ });
1174
+ this.name = "PromptError";
1175
+ }
1176
+ /**
1177
+ * Creates a PromptError from an unknown error.
1178
+ */
1179
+ static from(error, code = "PROMPT_ERROR" /* PROMPT_ERROR */, context) {
1180
+ if (error instanceof _PromptError) {
1181
+ return error;
1182
+ }
1183
+ const cause = error instanceof Error ? error : new Error(String(error));
1184
+ return new _PromptError(cause.message, { code, cause, context });
1185
+ }
1186
+ };
1187
+ var PromptNotFoundError = class extends PromptError {
1188
+ promptId;
1189
+ version;
1190
+ constructor(promptId, version, options = {}) {
1191
+ const message = version ? `Prompt '${promptId}' version '${version}' not found` : `Prompt '${promptId}' not found`;
1192
+ super(message, {
1193
+ code: "PROMPT_NOT_FOUND" /* NOT_FOUND */,
1194
+ cause: options.cause,
1195
+ context: { promptId, version, ...options.context }
1196
+ });
1197
+ this.name = "PromptNotFoundError";
1198
+ this.promptId = promptId;
1199
+ this.version = version;
1200
+ }
1201
+ };
1202
+ var PromptInvalidFormatError = class extends PromptError {
1203
+ promptId;
1204
+ details;
1205
+ constructor(promptId, details, options = {}) {
1206
+ super(`Invalid format for prompt '${promptId}': ${details}`, {
1207
+ code: "PROMPT_INVALID_FORMAT" /* INVALID_FORMAT */,
1208
+ cause: options.cause,
1209
+ context: { promptId, details, ...options.context }
1210
+ });
1211
+ this.name = "PromptInvalidFormatError";
1212
+ this.promptId = promptId;
1213
+ this.details = details;
1214
+ }
1215
+ };
1216
+ var PromptTemplateError = class extends PromptError {
1217
+ promptId;
1218
+ details;
1219
+ constructor(promptId, details, options = {}) {
1220
+ super(`Template compilation failed for prompt '${promptId}': ${details}`, {
1221
+ code: "PROMPT_TEMPLATE_ERROR" /* TEMPLATE_ERROR */,
1222
+ cause: options.cause,
1223
+ context: { promptId, details, ...options.context }
1224
+ });
1225
+ this.name = "PromptTemplateError";
1226
+ this.promptId = promptId;
1227
+ this.details = details;
1228
+ }
1229
+ };
1230
+ var PromptIOError = class extends PromptError {
1231
+ operation;
1232
+ path;
1233
+ constructor(operation, path5, options = {}) {
1234
+ const opText = operation === "list" ? "list prompts in" : `${operation} prompt file`;
1235
+ super(`Failed to ${opText}: ${path5}`, {
1236
+ code: "PROMPT_IO_ERROR" /* IO_ERROR */,
1237
+ cause: options.cause,
1238
+ context: { operation, path: path5, ...options.context }
1239
+ });
1240
+ this.name = "PromptIOError";
1241
+ this.operation = operation;
1242
+ this.path = path5;
1243
+ }
1244
+ };
1245
+
1246
+ // src/prompt/template.ts
1247
+ var handlebars = Handlebars.create();
1248
+ handlebars.registerHelper("add", (a, b) => {
1249
+ const numA = Number(a);
1250
+ const numB = Number(b);
1251
+ if (Number.isNaN(numA) || Number.isNaN(numB)) {
1252
+ return NaN;
1253
+ }
1254
+ return numA + numB;
1255
+ });
1256
+ function wrapTemplateError(promptId, error) {
1257
+ const message = error instanceof Error ? error.message : String(error);
1258
+ const cause = error instanceof Error ? error : void 0;
1259
+ return new PromptTemplateError(promptId, message, { cause });
1260
+ }
1261
+ function compileTemplate(template, promptId) {
1262
+ try {
1263
+ const compiled = handlebars.compile(template, {
1264
+ strict: true,
1265
+ noEscape: true
1266
+ });
1267
+ return (input) => {
1268
+ try {
1269
+ return compiled(input);
1270
+ } catch (error) {
1271
+ throw wrapTemplateError(promptId, error);
1272
+ }
1273
+ };
1274
+ } catch (error) {
1275
+ throw wrapTemplateError(promptId, error);
1276
+ }
1277
+ }
1278
+
1279
+ // src/prompt/prompt-template.ts
1280
+ var PromptTemplate = class _PromptTemplate {
1281
+ constructor(id, version, system, userTemplate) {
1282
+ this.id = id;
1283
+ this.version = version;
1284
+ this.system = system;
1285
+ this.userTemplate = userTemplate;
1286
+ }
1287
+ /**
1288
+ * Creates a PromptTemplate instance from raw data.
1289
+ *
1290
+ * @param data - Raw prompt template data
1291
+ * @returns PromptTemplate instance
1292
+ */
1293
+ static from(data) {
1294
+ return new _PromptTemplate(data.id, data.version, data.system, data.userTemplate);
1295
+ }
1296
+ /**
1297
+ * Compiles templates and returns a PromptRenderer.
1298
+ *
1299
+ * @typeParam TSystemInput - Type of input for system prompt template
1300
+ * @typeParam TUserInput - Type of input for user prompt template (defaults to TSystemInput)
1301
+ * @returns Compiled prompt renderer with renderSystemPrompt and renderUserPrompt functions
1302
+ * @throws {PromptTemplateError} If template compilation fails
1303
+ *
1304
+ * @example
1305
+ * ```typescript
1306
+ * // Different input types for system and user prompts
1307
+ * const renderer = template.compile<SessionCtx, TurnCtx>();
1308
+ *
1309
+ * // Same input type for both
1310
+ * const simpleRenderer = template.compile<CommonCtx>();
1311
+ * ```
1312
+ */
1313
+ compile() {
1314
+ return {
1315
+ id: this.id,
1316
+ version: this.version,
1317
+ renderSystemPrompt: compileTemplate(this.system, this.id),
1318
+ renderUserPrompt: compileTemplate(this.userTemplate, this.id)
1319
+ };
1320
+ }
1321
+ /**
1322
+ * Returns raw data representation.
1323
+ * Useful for serialization or passing to repository.write().
1324
+ */
1325
+ toData() {
1326
+ return {
1327
+ id: this.id,
1328
+ version: this.version,
1329
+ system: this.system,
1330
+ userTemplate: this.userTemplate
1331
+ };
1332
+ }
1333
+ };
1334
+ function toErrorCause(error) {
1335
+ return error instanceof Error ? error : void 0;
1336
+ }
1337
+ function toErrorMessage(error) {
1338
+ return error instanceof Error ? error.message : String(error);
1339
+ }
1340
+ var defaultFileSystem = {
1341
+ readFile: (filePath) => fs.readFile(filePath, "utf-8"),
1342
+ writeFile: (filePath, content) => fs.writeFile(filePath, content, "utf-8"),
1343
+ readdir: (dirPath) => fs.readdir(dirPath)
1344
+ };
1345
+ function parseVersion(version) {
1346
+ const match = version.match(/^(\d+)\.(\d+)\.(\d+)$/);
1347
+ if (!match) return null;
1348
+ return [parseInt(match[1], 10), parseInt(match[2], 10), parseInt(match[3], 10)];
1349
+ }
1350
+ function compareVersions(a, b) {
1351
+ const parsedA = parseVersion(a);
1352
+ const parsedB = parseVersion(b);
1353
+ if (!parsedA && !parsedB) return 0;
1354
+ if (!parsedA) return 1;
1355
+ if (!parsedB) return -1;
1356
+ for (let i = 0; i < 3; i++) {
1357
+ if (parsedA[i] !== parsedB[i]) {
1358
+ return parsedA[i] - parsedB[i];
1359
+ }
1360
+ }
1361
+ return 0;
1362
+ }
1363
+ var FILE_EXTENSION = ".yaml";
1364
+ function getFileName(id, version) {
1365
+ return `${id}-${version}${FILE_EXTENSION}`;
1366
+ }
1367
+ function parseFileName(fileName) {
1368
+ if (!fileName.endsWith(FILE_EXTENSION)) return null;
1369
+ const baseName = fileName.slice(0, -FILE_EXTENSION.length);
1370
+ const lastDash = baseName.lastIndexOf("-");
1371
+ if (lastDash === -1) return null;
1372
+ const id = baseName.slice(0, lastDash);
1373
+ const version = baseName.slice(lastDash + 1);
1374
+ if (!id || !parseVersion(version)) return null;
1375
+ return { id, version };
1376
+ }
1377
+ function parsePromptYaml(content, promptId) {
1378
+ let parsed;
1379
+ try {
1380
+ parsed = yaml.parse(content);
1381
+ } catch (error) {
1382
+ throw new PromptInvalidFormatError(
1383
+ promptId,
1384
+ `Invalid YAML: ${toErrorMessage(error)}`,
1385
+ { cause: toErrorCause(error) }
1386
+ );
1387
+ }
1388
+ if (!parsed || typeof parsed !== "object") {
1389
+ throw new PromptInvalidFormatError(promptId, "Expected YAML object");
1390
+ }
1391
+ const obj = parsed;
1392
+ const requiredFields = ["id", "version", "system", "userTemplate"];
1393
+ for (const field of requiredFields) {
1394
+ if (typeof obj[field] !== "string") {
1395
+ throw new PromptInvalidFormatError(
1396
+ promptId,
1397
+ `Missing or invalid required field: ${field} (expected string)`
1398
+ );
1399
+ }
1400
+ }
1401
+ return {
1402
+ id: obj.id,
1403
+ version: obj.version,
1404
+ system: obj.system,
1405
+ userTemplate: obj.userTemplate
1406
+ };
1407
+ }
1408
+ function serializePromptYaml(content) {
1409
+ return yaml.stringify(content);
1410
+ }
1411
+ var FilePromptRepository = class {
1412
+ directory;
1413
+ fileSystem;
1414
+ cacheEnabled;
1415
+ contentCache = /* @__PURE__ */ new Map();
1416
+ constructor(options) {
1417
+ this.directory = options.directory;
1418
+ this.fileSystem = options.fs ?? defaultFileSystem;
1419
+ this.cacheEnabled = options.cache ?? true;
1420
+ }
1421
+ /**
1422
+ * Generates file name from prompt id and version.
1423
+ * Override this along with `parseFileName` to change file naming convention.
1424
+ */
1425
+ getFileName(id, version) {
1426
+ return getFileName(id, version);
1427
+ }
1428
+ /**
1429
+ * Parses file name into id and version.
1430
+ * Override this along with `getFileName` to change file naming convention.
1431
+ */
1432
+ parseFileName(fileName) {
1433
+ return parseFileName(fileName);
1434
+ }
1435
+ /**
1436
+ * Parses raw file content into PromptTemplateData.
1437
+ * Override this to support different file formats (e.g., JSON, TOML).
1438
+ */
1439
+ parseContent(content, promptId) {
1440
+ return parsePromptYaml(content, promptId);
1441
+ }
1442
+ /**
1443
+ * Serializes PromptTemplateData to file content string.
1444
+ * Override this to support different file formats (e.g., JSON, TOML).
1445
+ */
1446
+ serializeContent(content) {
1447
+ return serializePromptYaml(content);
1448
+ }
1449
+ getCacheKey(id, version) {
1450
+ return `${id}:${version}`;
1451
+ }
1452
+ async read(id, version) {
1453
+ if (version) {
1454
+ const cacheKey = this.getCacheKey(id, version);
1455
+ if (this.cacheEnabled) {
1456
+ const cached = this.contentCache.get(cacheKey);
1457
+ if (cached) {
1458
+ return cached;
1459
+ }
1460
+ }
1461
+ const fileName = this.getFileName(id, version);
1462
+ const filePath = path3.join(this.directory, fileName);
1463
+ let content;
1464
+ try {
1465
+ content = await this.fileSystem.readFile(filePath);
1466
+ } catch (error) {
1467
+ if (error instanceof Error && "code" in error && error.code === "ENOENT") {
1468
+ throw new PromptNotFoundError(id, version);
1469
+ }
1470
+ throw new PromptIOError("read", filePath, { cause: toErrorCause(error) });
1471
+ }
1472
+ const promptContent = this.parseContent(content, id);
1473
+ if (promptContent.id !== id || promptContent.version !== version) {
1474
+ throw new PromptInvalidFormatError(
1475
+ id,
1476
+ `File content mismatch: expected id='${id}' version='${version}', got id='${promptContent.id}' version='${promptContent.version}'`
1477
+ );
1478
+ }
1479
+ if (this.cacheEnabled) {
1480
+ this.contentCache.set(cacheKey, promptContent);
1481
+ }
1482
+ return promptContent;
1483
+ }
1484
+ let files;
1485
+ try {
1486
+ files = await this.fileSystem.readdir(this.directory);
1487
+ } catch (error) {
1488
+ throw new PromptIOError("list", this.directory, { cause: toErrorCause(error) });
1489
+ }
1490
+ const versions = [];
1491
+ for (const file of files) {
1492
+ const parsed = this.parseFileName(file);
1493
+ if (parsed && parsed.id === id) {
1494
+ versions.push(parsed.version);
1495
+ }
1496
+ }
1497
+ if (versions.length === 0) {
1498
+ throw new PromptNotFoundError(id);
1499
+ }
1500
+ versions.sort((a, b) => compareVersions(b, a));
1501
+ const latestVersion = versions[0];
1502
+ return this.read(id, latestVersion);
1503
+ }
1504
+ async write(content) {
1505
+ if (this.cacheEnabled) {
1506
+ this.contentCache.delete(this.getCacheKey(content.id, content.version));
1507
+ }
1508
+ const fileName = this.getFileName(content.id, content.version);
1509
+ const filePath = path3.join(this.directory, fileName);
1510
+ if (!parseVersion(content.version)) {
1511
+ throw new PromptInvalidFormatError(
1512
+ content.id,
1513
+ `Invalid version format: '${content.version}' (expected semver like '1.0.0')`
1514
+ );
1515
+ }
1516
+ compileTemplate(content.system, content.id);
1517
+ compileTemplate(content.userTemplate, content.id);
1518
+ const yamlContent = this.serializeContent(content);
1519
+ try {
1520
+ await this.fileSystem.writeFile(filePath, yamlContent);
1521
+ } catch (error) {
1522
+ throw new PromptIOError("write", filePath, { cause: toErrorCause(error) });
1523
+ }
1524
+ }
1525
+ };
1526
+ function createFilePromptRepository(options) {
1527
+ return new FilePromptRepository(options);
1528
+ }
1529
+
1530
+ // src/provider/types.ts
1531
+ var FILE_SOURCE_TYPES = /* @__PURE__ */ new Set(["path", "data", "base64", "url"]);
1532
+ function isFileSource(v) {
1533
+ return typeof v === "object" && v !== null && FILE_SOURCE_TYPES.has(v.source);
1534
+ }
1535
+ function isFileSourcePath(v) {
1536
+ return v.source === "path";
1537
+ }
1538
+ function isFileSourceData(v) {
1539
+ return v.source === "data";
1540
+ }
1541
+ function isFileSourceBase64(v) {
1542
+ return v.source === "base64";
1543
+ }
1544
+ function isFileSourceUrl(v) {
1545
+ return v.source === "url";
1546
+ }
1547
+ async function computeFileSourceHash(source) {
1548
+ if (source.hash) {
1549
+ return source.hash;
1550
+ }
1551
+ const hash = createHash("sha256");
1552
+ switch (source.source) {
1553
+ case "path": {
1554
+ const fullPath = path3__default.isAbsolute(source.path) ? source.path : path3__default.resolve(process.cwd(), source.path);
1555
+ const content = await readFile(fullPath);
1556
+ hash.update(content);
1557
+ break;
1558
+ }
1559
+ case "data": {
1560
+ hash.update(source.data);
1561
+ break;
1562
+ }
1563
+ case "base64": {
1564
+ const buffer = Buffer.from(source.data, "base64");
1565
+ hash.update(buffer);
1566
+ break;
1567
+ }
1568
+ case "url": {
1569
+ hash.update(source.url);
1570
+ break;
1571
+ }
1572
+ }
1573
+ return hash.digest("hex");
1574
+ }
1575
+
1576
+ // src/provider/file-cache.ts
1577
+ var InMemoryFileCache = class {
1578
+ cache = /* @__PURE__ */ new Map();
1579
+ defaultTTL;
1580
+ constructor(options) {
1581
+ this.defaultTTL = options?.defaultTTL;
1582
+ }
1583
+ get(hash) {
1584
+ const entry = this.cache.get(hash);
1585
+ if (!entry) return null;
1586
+ if (entry.expiresAt !== void 0 && Date.now() > entry.expiresAt) {
1587
+ this.cache.delete(hash);
1588
+ return null;
1589
+ }
1590
+ return entry.file;
1591
+ }
1592
+ set(hash, file, ttl) {
1593
+ const effectiveTTL = ttl ?? this.defaultTTL;
1594
+ const expiresAt = effectiveTTL !== void 0 ? Date.now() + effectiveTTL : void 0;
1595
+ this.cache.set(hash, { file, expiresAt });
1596
+ }
1597
+ delete(hash) {
1598
+ this.cache.delete(hash);
1599
+ }
1600
+ clear() {
1601
+ this.cache.clear();
1602
+ }
1603
+ };
1604
+
1605
+ // src/provider/base-provider.ts
1606
+ var BaseProvider = class {
1607
+ streamingExecution(generator, options) {
1608
+ return new StreamingExecutionHost(
1609
+ (signal) => this.createStreamingSession(signal),
1610
+ generator,
1611
+ options?.signal
1612
+ );
1613
+ }
1614
+ /**
1615
+ * Execute a non-streaming function with cancellation support.
1616
+ * Returns immediately - execution starts in the background.
1617
+ */
1618
+ simpleExecution(fn, options) {
1619
+ return new SimpleExecutionHost(
1620
+ (signal) => this.createSimpleSession(signal),
1621
+ fn,
1622
+ options?.signal
1623
+ );
1624
+ }
1625
+ };
1626
+
1627
+ // src/provider/noop-file-manager.ts
1628
+ var NoOpFileManager = class {
1629
+ upload(_files) {
1630
+ throw new FileError("File upload not supported by this provider", {
1631
+ code: "UNSUPPORTED_TYPE" /* UNSUPPORTED_TYPE */,
1632
+ context: {
1633
+ provider: "noop",
1634
+ suggestion: "Use a provider with file support (e.g., Google) or pass files inline"
1635
+ }
1636
+ });
1637
+ }
1638
+ delete(_fileId) {
1639
+ throw new FileError("File delete not supported by this provider", {
1640
+ code: "UNSUPPORTED_TYPE" /* UNSUPPORTED_TYPE */,
1641
+ context: {
1642
+ provider: "noop"
1643
+ }
1644
+ });
1645
+ }
1646
+ clear() {
1647
+ return Promise.resolve();
1648
+ }
1649
+ getUploadedFiles() {
1650
+ return [];
1651
+ }
1652
+ };
1653
+ var EXTENSION_TO_MIME = {
1654
+ ".pdf": "application/pdf",
1655
+ ".html": "text/html",
1656
+ ".htm": "text/html",
1657
+ ".txt": "text/plain",
1658
+ ".json": "application/json",
1659
+ ".xml": "application/xml",
1660
+ ".csv": "text/csv",
1661
+ ".png": "image/png",
1662
+ ".jpg": "image/jpeg",
1663
+ ".jpeg": "image/jpeg",
1664
+ ".gif": "image/gif",
1665
+ ".webp": "image/webp",
1666
+ ".svg": "image/svg+xml",
1667
+ ".bmp": "image/bmp",
1668
+ ".zip": "application/zip"
1669
+ };
1670
+ function inferMediaType(filePath) {
1671
+ const ext = path3.extname(filePath).toLowerCase();
1672
+ return EXTENSION_TO_MIME[ext];
1673
+ }
1674
+ function scanForFileSources(input, currentPath = []) {
1675
+ if (isFileSource(input)) {
1676
+ return [{ part: input, path: currentPath }];
1677
+ }
1678
+ if (input === null || typeof input !== "object") {
1679
+ return [];
1680
+ }
1681
+ if (Buffer.isBuffer(input) || input instanceof Uint8Array || input instanceof URL) {
1682
+ return [];
1683
+ }
1684
+ const results = [];
1685
+ if (Array.isArray(input)) {
1686
+ for (let i = 0; i < input.length; i++) {
1687
+ results.push(...scanForFileSources(input[i], [...currentPath, i]));
1688
+ }
1689
+ } else {
1690
+ for (const key of Object.keys(input)) {
1691
+ results.push(
1692
+ ...scanForFileSources(input[key], [
1693
+ ...currentPath,
1694
+ key
1695
+ ])
1696
+ );
1697
+ }
1698
+ }
1699
+ return results;
1700
+ }
1701
+ var DEFAULT_MAX_SIZE = 50 * 1024 * 1024;
1702
+ async function resolveFileSource(part, options = {}) {
1703
+ const { basePath = process.cwd(), maxSize = DEFAULT_MAX_SIZE } = options;
1704
+ if (!isFileSourcePath(part)) {
1705
+ return part;
1706
+ }
1707
+ const fullPath = path3.isAbsolute(part.path) ? part.path : path3.resolve(basePath, part.path);
1708
+ const stats = await stat(fullPath).catch((err) => {
1709
+ throw new FileError(`File not found: ${part.path}`, {
1710
+ code: "NOT_FOUND" /* NOT_FOUND */,
1711
+ context: { path: part.path, fullPath },
1712
+ cause: err
1713
+ });
1714
+ });
1715
+ if (stats.size > maxSize) {
1716
+ throw new FileError(`File too large: ${stats.size} bytes > ${maxSize} bytes`, {
1717
+ code: "TOO_LARGE" /* TOO_LARGE */,
1718
+ context: { path: part.path, size: stats.size, maxSize }
1719
+ });
1720
+ }
1721
+ const buffer = await readFile(fullPath);
1722
+ const mediaType = part.mediaType ?? inferMediaType(part.path) ?? "application/octet-stream";
1723
+ const filename = part.filename ?? path3.basename(part.path);
1724
+ const result = {
1725
+ source: "data",
1726
+ data: buffer,
1727
+ mediaType,
1728
+ filename
1729
+ };
1730
+ return result;
1731
+ }
1732
+ function deepClone(value) {
1733
+ if (value === null || typeof value !== "object") return value;
1734
+ if (Buffer.isBuffer(value)) return Buffer.from(value);
1735
+ if (value instanceof URL) return new URL(value.href);
1736
+ if (value instanceof Uint8Array) return new Uint8Array(value);
1737
+ if (Array.isArray(value)) return value.map(deepClone);
1738
+ const result = {};
1739
+ for (const key of Object.keys(value)) {
1740
+ result[key] = deepClone(value[key]);
1741
+ }
1742
+ return result;
1743
+ }
1744
+ function setAtPath(obj, targetPath, value) {
1745
+ if (targetPath.length === 0) {
1746
+ return value;
1747
+ }
1748
+ let current = obj;
1749
+ for (let i = 0; i < targetPath.length - 1; i++) {
1750
+ current = current[targetPath[i]];
1751
+ }
1752
+ current[targetPath[targetPath.length - 1]] = value;
1753
+ return obj;
1754
+ }
1755
+ async function resolveFileSourcesInInput(input, options = {}) {
1756
+ const found = scanForFileSources(input);
1757
+ if (found.length === 0) {
1758
+ return input;
1759
+ }
1760
+ const resolved = await Promise.all(found.map(({ part }) => resolveFileSource(part, options)));
1761
+ if (found.length === 1 && found[0].path.length === 0) {
1762
+ return resolved[0];
1763
+ }
1764
+ const result = deepClone(input);
1765
+ for (let i = 0; i < found.length; i++) {
1766
+ setAtPath(result, found[i].path, resolved[i]);
1767
+ }
1768
+ return result;
1769
+ }
1770
+ function getFileSourceDisplayInfo(part) {
1771
+ switch (part.source) {
1772
+ case "path":
1773
+ return {
1774
+ source: "path",
1775
+ description: part.path,
1776
+ mediaType: part.mediaType ?? inferMediaType(part.path) ?? "unknown",
1777
+ filename: part.filename ?? path3.basename(part.path)
1778
+ };
1779
+ case "url":
1780
+ return {
1781
+ source: "url",
1782
+ description: part.url,
1783
+ mediaType: part.mediaType ?? "unknown",
1784
+ filename: part.filename
1785
+ };
1786
+ case "base64": {
1787
+ const sizeKB = (part.data.length * 3 / 4 / 1024).toFixed(1);
1788
+ return {
1789
+ source: "base64",
1790
+ description: `[base64 data, ~${sizeKB}KB]`,
1791
+ mediaType: part.mediaType,
1792
+ filename: part.filename
1793
+ };
1794
+ }
1795
+ case "data": {
1796
+ const size = Buffer.isBuffer(part.data) ? part.data.length : part.data.length;
1797
+ const sizeKB = (size / 1024).toFixed(1);
1798
+ return {
1799
+ source: "data",
1800
+ description: `[Buffer, ${sizeKB}KB]`,
1801
+ mediaType: part.mediaType,
1802
+ filename: part.filename
1803
+ };
1804
+ }
1805
+ }
1806
+ }
1807
+ function getFileSourcesDisplayInfo(input) {
1808
+ const found = scanForFileSources(input);
1809
+ return found.map(({ part }) => getFileSourceDisplayInfo(part));
1810
+ }
1811
+ function assertValidUploadResponse(response, context) {
1812
+ if (!response.name || !response.uri || !response.mimeType) {
1813
+ throw new FileError("Invalid upload response from Google API: missing required fields", {
1814
+ code: "UPLOAD_ERROR" /* UPLOAD_ERROR */,
1815
+ context: {
1816
+ ...context,
1817
+ hasName: !!response.name,
1818
+ hasUri: !!response.uri,
1819
+ hasMediaType: !!response.mimeType
1820
+ }
1821
+ });
1822
+ }
1823
+ }
1824
+ function isImageMediaType(mediaType) {
1825
+ return mediaType.startsWith("image/");
1826
+ }
1827
+ function createPart(uri, mediaType) {
1828
+ const url = new URL(uri);
1829
+ if (isImageMediaType(mediaType)) {
1830
+ return { type: "image", image: url, mediaType };
1831
+ }
1832
+ return { type: "file", data: url, mediaType };
1833
+ }
1834
+ var GoogleFileManager = class {
1835
+ uploadedFiles = [];
1836
+ client;
1837
+ cache;
1838
+ constructor(apiKey, options) {
1839
+ this.client = new GoogleGenAI({ apiKey });
1840
+ this.cache = options?.cache ?? null;
1841
+ }
1842
+ async uploadOne(fileSource, index, hash) {
1843
+ if (this.cache) {
1844
+ const cached = this.cache.get(hash);
1845
+ if (cached) return cached;
1846
+ }
1847
+ if (fileSource.source === "url") {
1848
+ const mediaType = fileSource.mediaType ?? "application/octet-stream";
1849
+ const result = {
1850
+ id: null,
1851
+ part: createPart(fileSource.url, mediaType)
1852
+ };
1853
+ if (this.cache) {
1854
+ this.cache.set(hash, result);
1855
+ }
1856
+ return result;
1857
+ }
1858
+ if (fileSource.source === "path") {
1859
+ const fullPath = path3__default.isAbsolute(fileSource.path) ? fileSource.path : path3__default.resolve(process.cwd(), fileSource.path);
1860
+ try {
1861
+ const uploaded = await this.client.files.upload({
1862
+ file: fullPath,
1863
+ config: {
1864
+ mimeType: fileSource.mediaType,
1865
+ displayName: fileSource.filename ?? path3__default.basename(fileSource.path)
1866
+ }
1867
+ });
1868
+ assertValidUploadResponse(uploaded, {
1869
+ source: "path",
1870
+ path: fileSource.path
1871
+ });
1872
+ const result = {
1873
+ id: uploaded.name,
1874
+ part: createPart(uploaded.uri, uploaded.mimeType)
1875
+ };
1876
+ if (this.cache) {
1877
+ this.cache.set(hash, result);
1878
+ }
1879
+ return result;
1880
+ } catch (error) {
1881
+ if (error instanceof FileError) throw error;
1882
+ throw FileError.from(error, "UPLOAD_ERROR" /* UPLOAD_ERROR */, {
1883
+ source: "path",
1884
+ path: fileSource.path,
1885
+ mediaType: fileSource.mediaType
1886
+ });
1887
+ }
1888
+ }
1889
+ const buffer = fileSource.source === "base64" ? Buffer.from(fileSource.data, "base64") : fileSource.data;
1890
+ const arrayBuffer = buffer.buffer.slice(
1891
+ buffer.byteOffset,
1892
+ buffer.byteOffset + buffer.byteLength
1893
+ );
1894
+ const blob = new Blob([arrayBuffer], { type: fileSource.mediaType });
1895
+ try {
1896
+ const uploaded = await this.client.files.upload({
1897
+ file: blob,
1898
+ config: {
1899
+ mimeType: fileSource.mediaType,
1900
+ displayName: fileSource.filename ?? `upload-${Date.now()}-${index}`
1901
+ }
1902
+ });
1903
+ assertValidUploadResponse(uploaded, {
1904
+ source: fileSource.source,
1905
+ mediaType: fileSource.mediaType
1906
+ });
1907
+ const result = {
1908
+ id: uploaded.name,
1909
+ part: createPart(uploaded.uri, uploaded.mimeType)
1910
+ };
1911
+ if (this.cache) {
1912
+ this.cache.set(hash, result);
1913
+ }
1914
+ return result;
1915
+ } catch (error) {
1916
+ if (error instanceof FileError) throw error;
1917
+ throw FileError.from(error, "UPLOAD_ERROR" /* UPLOAD_ERROR */, {
1918
+ source: fileSource.source,
1919
+ mediaType: fileSource.mediaType,
1920
+ filename: fileSource.filename
1921
+ });
1922
+ }
1923
+ }
1924
+ async upload(files) {
1925
+ const hashes = await Promise.all(files.map((file) => computeFileSourceHash(file)));
1926
+ const results = await Promise.allSettled(
1927
+ files.map((file, i) => this.uploadOne(file, i, hashes[i]))
1928
+ );
1929
+ const successful = [];
1930
+ const failed = [];
1931
+ for (let i = 0; i < results.length; i++) {
1932
+ const result = results[i];
1933
+ if (result.status === "fulfilled") {
1934
+ successful.push({ file: result.value, hash: hashes[i] });
1935
+ } else {
1936
+ failed.push(result);
1937
+ }
1938
+ }
1939
+ if (failed.length > 0) {
1940
+ await Promise.all(
1941
+ successful.filter(({ file }) => file.id !== null).map(async ({ file, hash }) => {
1942
+ await this.client.files.delete({ name: file.id }).catch(() => {
1943
+ });
1944
+ if (this.cache) {
1945
+ this.cache.delete(hash);
1946
+ }
1947
+ })
1948
+ );
1949
+ const firstError = failed[0].reason;
1950
+ throw new FileError(
1951
+ `Failed to upload ${failed.length} file(s): ${firstError instanceof Error ? firstError.message : String(firstError)}`,
1952
+ {
1953
+ code: "UPLOAD_ERROR" /* UPLOAD_ERROR */,
1954
+ cause: firstError instanceof Error ? firstError : void 0,
1955
+ context: {
1956
+ totalFiles: files.length,
1957
+ failedCount: failed.length,
1958
+ successCount: successful.length
1959
+ }
1960
+ }
1961
+ );
1962
+ }
1963
+ this.uploadedFiles.push(...successful.filter(({ file }) => file.id !== null).map(({ file }) => file));
1964
+ return successful.map(({ file }) => file);
1965
+ }
1966
+ async delete(fileId) {
1967
+ try {
1968
+ await this.client.files.delete({ name: fileId });
1969
+ this.uploadedFiles = this.uploadedFiles.filter((f) => f.id !== fileId);
1970
+ } catch (error) {
1971
+ throw FileError.from(error, "DELETE_ERROR" /* DELETE_ERROR */, {
1972
+ fileId
1973
+ });
1974
+ }
1975
+ }
1976
+ async clear() {
1977
+ await Promise.all(
1978
+ this.uploadedFiles.filter((f) => f.id !== null).map((f) => this.delete(f.id).catch(() => {
1979
+ }))
1980
+ );
1981
+ this.uploadedFiles = [];
1982
+ }
1983
+ getUploadedFiles() {
1984
+ return [...this.uploadedFiles];
1985
+ }
1986
+ };
1987
+
1988
+ // src/session/usage-extractors.ts
1989
+ function mergeUsages(usages) {
1990
+ if (usages.length === 0) {
1991
+ return createZeroUsage();
1992
+ }
1993
+ let inputTokens = 0;
1994
+ let outputTokens = 0;
1995
+ let totalTokens = 0;
1996
+ let noCacheTokens = 0;
1997
+ let cacheReadTokens = 0;
1998
+ let cacheWriteTokens = 0;
1999
+ let textTokens = 0;
2000
+ let reasoningTokens = 0;
2001
+ for (const usage of usages) {
2002
+ inputTokens += usage.inputTokens ?? 0;
2003
+ outputTokens += usage.outputTokens ?? 0;
2004
+ totalTokens += usage.totalTokens ?? 0;
2005
+ noCacheTokens += usage.inputTokenDetails?.noCacheTokens ?? 0;
2006
+ cacheReadTokens += usage.inputTokenDetails?.cacheReadTokens ?? 0;
2007
+ cacheWriteTokens += usage.inputTokenDetails?.cacheWriteTokens ?? 0;
2008
+ textTokens += usage.outputTokenDetails?.textTokens ?? 0;
2009
+ reasoningTokens += usage.outputTokenDetails?.reasoningTokens ?? 0;
2010
+ }
2011
+ return {
2012
+ inputTokens,
2013
+ outputTokens,
2014
+ totalTokens,
2015
+ inputTokenDetails: {
2016
+ noCacheTokens,
2017
+ cacheReadTokens,
2018
+ cacheWriteTokens
2019
+ },
2020
+ outputTokenDetails: {
2021
+ textTokens,
2022
+ reasoningTokens
2023
+ }
2024
+ };
2025
+ }
2026
+ function createZeroUsage() {
2027
+ return {
2028
+ inputTokens: 0,
2029
+ outputTokens: 0,
2030
+ totalTokens: 0,
2031
+ inputTokenDetails: {
2032
+ noCacheTokens: 0,
2033
+ cacheReadTokens: 0,
2034
+ cacheWriteTokens: 0
2035
+ },
2036
+ outputTokenDetails: {
2037
+ textTokens: 0,
2038
+ reasoningTokens: 0
2039
+ }
2040
+ };
2041
+ }
2042
+ function detectProviderType(modelId) {
2043
+ const lowerModel = modelId.toLowerCase();
2044
+ if (lowerModel.startsWith("gpt-") || lowerModel === "o1" || lowerModel.startsWith("o1-") || lowerModel === "o3" || lowerModel.startsWith("o3-")) {
2045
+ return "openai";
2046
+ }
2047
+ if (lowerModel.startsWith("gemini")) {
2048
+ return "google";
2049
+ }
2050
+ if (lowerModel.startsWith("claude")) {
2051
+ return "anthropic";
2052
+ }
2053
+ return void 0;
2054
+ }
2055
+
2056
+ // src/session/types.ts
2057
+ var SessionSummary = class _SessionSummary {
2058
+ totalLLMUsage;
2059
+ llmCallCount;
2060
+ llmCalls;
2061
+ toolCalls;
2062
+ customRecords;
2063
+ llmCost;
2064
+ additionalCosts;
2065
+ metadata;
2066
+ /** Cost breakdown by model. Key format: `${provider}/${model}` */
2067
+ costByModel;
2068
+ startTime;
2069
+ constructor(data, startTime) {
2070
+ this.startTime = startTime;
2071
+ this.totalLLMUsage = data.totalLLMUsage;
2072
+ this.llmCallCount = data.llmCalls.length;
2073
+ this.llmCalls = Object.freeze([...data.llmCalls]);
2074
+ this.toolCalls = Object.freeze([...data.toolCalls]);
2075
+ this.customRecords = Object.freeze([...data.customRecords]);
2076
+ this.llmCost = data.llmCost;
2077
+ this.additionalCosts = Object.freeze([...data.additionalCosts]);
2078
+ this.metadata = Object.freeze({ ...data.metadata });
2079
+ this.costByModel = Object.freeze({ ...data.costByModel });
2080
+ }
2081
+ /**
2082
+ * Total duration from session start to now (computed dynamically).
2083
+ */
2084
+ get totalDuration() {
2085
+ return Date.now() - this.startTime;
2086
+ }
2087
+ /**
2088
+ * Creates an empty SessionSummary.
2089
+ */
2090
+ static empty(startTime) {
2091
+ return new _SessionSummary(
2092
+ {
2093
+ totalLLMUsage: createZeroUsage(),
2094
+ llmCalls: [],
2095
+ toolCalls: [],
2096
+ customRecords: [],
2097
+ llmCost: 0,
2098
+ additionalCosts: [],
2099
+ metadata: {},
2100
+ costByModel: {}
2101
+ },
2102
+ startTime
2103
+ );
2104
+ }
2105
+ /**
2106
+ * Creates a SessionSummary with custom data for testing purposes.
2107
+ * @internal For testing only - do not use in production code.
2108
+ */
2109
+ static forTest(data) {
2110
+ const startTime = data.startTime ?? Date.now() - 1e3;
2111
+ return new _SessionSummary(
2112
+ {
2113
+ totalLLMUsage: data.totalLLMUsage ?? createZeroUsage(),
2114
+ llmCalls: data.llmCalls ?? [],
2115
+ toolCalls: data.toolCalls ?? [],
2116
+ customRecords: data.customRecords ?? [],
2117
+ llmCost: data.llmCost ?? 0,
2118
+ additionalCosts: data.additionalCosts ?? [],
2119
+ metadata: data.metadata ?? {},
2120
+ costByModel: data.costByModel ?? {}
2121
+ },
2122
+ startTime
2123
+ );
2124
+ }
2125
+ /**
2126
+ * Total cost of all additional (non-LLM) operations.
2127
+ */
2128
+ get totalAdditionalCost() {
2129
+ return this.additionalCosts.reduce((sum, c) => sum + c.cost, 0);
2130
+ }
2131
+ /**
2132
+ * Total cost including LLM and additional costs.
2133
+ */
2134
+ get totalCost() {
2135
+ return this.llmCost + this.totalAdditionalCost;
2136
+ }
2137
+ /**
2138
+ * Returns a new SessionSummary with an LLM call added.
2139
+ */
2140
+ withLLMCall(call, newLlmCost, newCostByModel, newTotalUsage) {
2141
+ return new _SessionSummary(
2142
+ {
2143
+ totalLLMUsage: newTotalUsage,
2144
+ llmCalls: [...this.llmCalls, call],
2145
+ toolCalls: [...this.toolCalls],
2146
+ customRecords: [...this.customRecords],
2147
+ llmCost: newLlmCost,
2148
+ additionalCosts: [...this.additionalCosts],
2149
+ metadata: { ...this.metadata },
2150
+ costByModel: newCostByModel
2151
+ },
2152
+ this.startTime
2153
+ );
2154
+ }
2155
+ /**
2156
+ * Returns a new SessionSummary with an additional cost recorded.
2157
+ */
2158
+ withAdditionalCost(cost) {
2159
+ return new _SessionSummary(
2160
+ {
2161
+ totalLLMUsage: this.totalLLMUsage,
2162
+ llmCalls: [...this.llmCalls],
2163
+ toolCalls: [...this.toolCalls],
2164
+ customRecords: [...this.customRecords],
2165
+ llmCost: this.llmCost,
2166
+ additionalCosts: [...this.additionalCosts, cost],
2167
+ metadata: { ...this.metadata },
2168
+ costByModel: { ...this.costByModel }
2169
+ },
2170
+ this.startTime
2171
+ );
2172
+ }
2173
+ /**
2174
+ * Returns a new SessionSummary with metadata updated.
2175
+ */
2176
+ withMetadata(key, value) {
2177
+ return new _SessionSummary(
2178
+ {
2179
+ totalLLMUsage: this.totalLLMUsage,
2180
+ llmCalls: [...this.llmCalls],
2181
+ toolCalls: [...this.toolCalls],
2182
+ customRecords: [...this.customRecords],
2183
+ llmCost: this.llmCost,
2184
+ additionalCosts: [...this.additionalCosts],
2185
+ metadata: { ...this.metadata, [key]: value },
2186
+ costByModel: { ...this.costByModel }
2187
+ },
2188
+ this.startTime
2189
+ );
2190
+ }
2191
+ /**
2192
+ * Returns a new SessionSummary with a tool call added.
2193
+ */
2194
+ withToolCall(call) {
2195
+ return new _SessionSummary(
2196
+ {
2197
+ totalLLMUsage: this.totalLLMUsage,
2198
+ llmCalls: [...this.llmCalls],
2199
+ toolCalls: [...this.toolCalls, call],
2200
+ customRecords: [...this.customRecords],
2201
+ llmCost: this.llmCost,
2202
+ additionalCosts: [...this.additionalCosts],
2203
+ metadata: { ...this.metadata },
2204
+ costByModel: { ...this.costByModel }
2205
+ },
2206
+ this.startTime
2207
+ );
2208
+ }
2209
+ /**
2210
+ * Returns a new SessionSummary with a custom record added.
2211
+ */
2212
+ withCustomRecord(record) {
2213
+ return new _SessionSummary(
2214
+ {
2215
+ totalLLMUsage: this.totalLLMUsage,
2216
+ llmCalls: [...this.llmCalls],
2217
+ toolCalls: [...this.toolCalls],
2218
+ customRecords: [...this.customRecords, record],
2219
+ llmCost: this.llmCost,
2220
+ additionalCosts: [...this.additionalCosts],
2221
+ metadata: { ...this.metadata },
2222
+ costByModel: { ...this.costByModel }
2223
+ },
2224
+ this.startTime
2225
+ );
2226
+ }
2227
+ /**
2228
+ * Serializes to plain JSON object for database storage.
2229
+ */
2230
+ toJSON() {
2231
+ return {
2232
+ totalDuration: this.totalDuration,
2233
+ totalLLMUsage: this.totalLLMUsage,
2234
+ llmCallCount: this.llmCallCount,
2235
+ llmCalls: [...this.llmCalls],
2236
+ toolCalls: [...this.toolCalls],
2237
+ customRecords: [...this.customRecords],
2238
+ llmCost: this.llmCost,
2239
+ additionalCosts: [...this.additionalCosts],
2240
+ metadata: { ...this.metadata },
2241
+ costByModel: { ...this.costByModel },
2242
+ totalCost: this.totalCost,
2243
+ totalAdditionalCost: this.totalAdditionalCost
2244
+ };
2245
+ }
2246
+ };
2247
+
2248
+ // src/session/simple-session.ts
2249
+ var SimpleSession = class {
2250
+ defaultLanguageModel;
2251
+ modelFactory;
2252
+ providerType;
2253
+ providerPricing;
2254
+ defaultProviderOptions;
2255
+ defaultTools;
2256
+ _fileManager;
2257
+ logger;
2258
+ sessionStartTime;
2259
+ signal;
2260
+ summary;
2261
+ pendingUsagePromises = [];
2262
+ onDoneFns = [];
2263
+ constructor(options) {
2264
+ this.defaultLanguageModel = options.defaultLanguageModel ?? null;
2265
+ this.modelFactory = options.modelFactory ?? null;
2266
+ this.providerType = options.providerType;
2267
+ this.providerPricing = options.providerPricing;
2268
+ this.defaultProviderOptions = options.defaultProviderOptions;
2269
+ this.defaultTools = options.defaultTools;
2270
+ this._fileManager = options.fileManager;
2271
+ this.logger = options.logger ?? noopLogger;
2272
+ this.sessionStartTime = options.startTime ?? Date.now();
2273
+ this.signal = options.signal;
2274
+ this.summary = SessionSummary.empty(this.sessionStartTime);
2275
+ }
2276
+ getModel(requestedModelId) {
2277
+ if (requestedModelId) {
2278
+ if (!this.modelFactory) {
2279
+ throw new Error(
2280
+ `Model '${requestedModelId}' requested but no modelFactory provided. Either use the default model or configure the provider with modelFactory.`
2281
+ );
2282
+ }
2283
+ return this.modelFactory(requestedModelId);
2284
+ }
2285
+ if (!this.defaultLanguageModel) {
2286
+ throw new Error(
2287
+ "No model specified and no default model set. Either specify a model in the call or configure the provider with withDefaultModel()."
2288
+ );
2289
+ }
2290
+ return this.defaultLanguageModel;
2291
+ }
2292
+ extractModelId(model) {
2293
+ const modelWithId = model;
2294
+ if (!modelWithId.modelId) {
2295
+ console.warn(
2296
+ '[SimpleSession] Model does not have modelId property, using "unknown". This may affect cost tracking accuracy.'
2297
+ );
2298
+ }
2299
+ return modelWithId.modelId ?? "unknown";
2300
+ }
2301
+ async generateText(params) {
2302
+ const callStartTime = Date.now();
2303
+ const { model: requestedModel, providerOptions, tools, ...restParams } = params;
2304
+ const languageModel = this.getModel(requestedModel);
2305
+ const modelId = this.extractModelId(languageModel);
2306
+ const mergedProviderOptions = this.defaultProviderOptions || providerOptions ? merge({}, this.defaultProviderOptions ?? {}, providerOptions ?? {}) : void 0;
2307
+ const mergedTools = this.defaultTools || tools ? { ...this.defaultTools, ...tools } : void 0;
2308
+ this.logger.onLLMCallStart?.({
2309
+ type: "llm_call_start",
2310
+ callType: "generateText",
2311
+ modelId,
2312
+ timestamp: callStartTime,
2313
+ request: { params: restParams }
2314
+ });
2315
+ try {
2316
+ const result = await generateText({
2317
+ ...restParams,
2318
+ tools: mergedTools,
2319
+ providerOptions: mergedProviderOptions,
2320
+ model: languageModel,
2321
+ abortSignal: this.signal
2322
+ });
2323
+ const callEndTime = Date.now();
2324
+ const call = {
2325
+ startTime: callStartTime,
2326
+ endTime: callEndTime,
2327
+ duration: callEndTime - callStartTime,
2328
+ usage: result.usage ?? createZeroUsage(),
2329
+ type: "generateText",
2330
+ model: modelId,
2331
+ provider: this.providerType
2332
+ };
2333
+ this.updateSummaryWithLLMCall(call);
2334
+ this.logger.onLLMCallEnd?.({
2335
+ type: "llm_call_end",
2336
+ callType: "generateText",
2337
+ modelId,
2338
+ timestamp: callEndTime,
2339
+ response: {
2340
+ duration: callEndTime - callStartTime,
2341
+ usage: result.usage,
2342
+ raw: result
2343
+ }
2344
+ });
2345
+ return result;
2346
+ } catch (error) {
2347
+ const callEndTime = Date.now();
2348
+ this.logger.onLLMCallEnd?.({
2349
+ type: "llm_call_end",
2350
+ callType: "generateText",
2351
+ modelId,
2352
+ timestamp: callEndTime,
2353
+ response: {
2354
+ duration: callEndTime - callStartTime,
2355
+ raw: null,
2356
+ error: error instanceof Error ? error : new Error(String(error))
2357
+ }
2358
+ });
2359
+ throw error;
2360
+ }
2361
+ }
2362
+ streamText(params) {
2363
+ const callStartTime = Date.now();
2364
+ const { model: requestedModel, providerOptions, tools, ...restParams } = params;
2365
+ const languageModel = this.getModel(requestedModel);
2366
+ const modelId = this.extractModelId(languageModel);
2367
+ const mergedProviderOptions = this.defaultProviderOptions || providerOptions ? merge({}, this.defaultProviderOptions ?? {}, providerOptions ?? {}) : void 0;
2368
+ const mergedTools = this.defaultTools || tools ? { ...this.defaultTools, ...tools } : void 0;
2369
+ this.logger.onLLMCallStart?.({
2370
+ type: "llm_call_start",
2371
+ callType: "streamText",
2372
+ modelId,
2373
+ timestamp: callStartTime,
2374
+ request: { params: restParams }
2375
+ });
2376
+ const result = streamText({
2377
+ ...restParams,
2378
+ tools: mergedTools,
2379
+ providerOptions: mergedProviderOptions,
2380
+ model: languageModel,
2381
+ abortSignal: this.signal
2382
+ });
2383
+ const usagePromise = Promise.resolve(result.usage).then((usage) => {
2384
+ const callEndTime = Date.now();
2385
+ const call = {
2386
+ startTime: callStartTime,
2387
+ endTime: callEndTime,
2388
+ duration: callEndTime - callStartTime,
2389
+ usage: usage ?? createZeroUsage(),
2390
+ type: "streamText",
2391
+ model: modelId,
2392
+ provider: this.providerType
2393
+ };
2394
+ this.updateSummaryWithLLMCall(call);
2395
+ this.logger.onLLMCallEnd?.({
2396
+ type: "llm_call_end",
2397
+ callType: "streamText",
2398
+ modelId,
2399
+ timestamp: callEndTime,
2400
+ response: {
2401
+ duration: callEndTime - callStartTime,
2402
+ usage,
2403
+ raw: result
2404
+ }
2405
+ });
2406
+ return usage;
2407
+ });
2408
+ this.pendingUsagePromises.push(usagePromise);
2409
+ return result;
2410
+ }
2411
+ get fileManager() {
2412
+ return this._fileManager;
2413
+ }
2414
+ record(data) {
2415
+ this.summary = this.summary.withCustomRecord(data);
2416
+ }
2417
+ recordToolCall(toolCallSummary) {
2418
+ this.summary = this.summary.withToolCall(toolCallSummary);
2419
+ }
2420
+ recordLLMCall(record) {
2421
+ const call = {
2422
+ ...record,
2423
+ type: record.type ?? "manual"
2424
+ };
2425
+ this.updateSummaryWithLLMCall(call);
2426
+ }
2427
+ recordAdditionalCost(cost) {
2428
+ this.summary = this.summary.withAdditionalCost({
2429
+ ...cost,
2430
+ timestamp: Date.now()
2431
+ });
2432
+ }
2433
+ setMetadata(keyOrData, value) {
2434
+ if (typeof keyOrData === "string") {
2435
+ this.summary = this.summary.withMetadata(keyOrData, value);
2436
+ } else {
2437
+ for (const [k, v] of Object.entries(keyOrData)) {
2438
+ this.summary = this.summary.withMetadata(k, v);
2439
+ }
2440
+ }
2441
+ }
2442
+ updateSummaryWithLLMCall(call) {
2443
+ const newCalls = [...this.summary.llmCalls, call];
2444
+ const { totalCost: llmCost, costByModel } = calculateTotalCost(
2445
+ newCalls.map((c) => ({ usage: c.usage, model: c.model, provider: c.provider })),
2446
+ this.providerPricing
2447
+ );
2448
+ const newTotalUsage = mergeUsages(newCalls.map((c) => c.usage));
2449
+ this.summary = this.summary.withLLMCall(call, llmCost, costByModel, newTotalUsage);
2450
+ }
2451
+ onDone(fn) {
2452
+ this.onDoneFns.push(fn);
2453
+ }
2454
+ async runOnDoneHooks() {
2455
+ const reversedHooks = [...this.onDoneFns].reverse();
2456
+ for (const fn of reversedHooks) {
2457
+ try {
2458
+ await fn();
2459
+ } catch (error) {
2460
+ console.error("[SimpleSession] onDone hook error:", error);
2461
+ }
2462
+ }
2463
+ }
2464
+ async getSummary() {
2465
+ await Promise.all(this.pendingUsagePromises);
2466
+ return this.summary;
2467
+ }
2468
+ /**
2469
+ * Notifies Logger of execution start.
2470
+ * @internal Called by SimpleExecutionHost - not intended for direct use.
2471
+ */
2472
+ notifyExecutionStart() {
2473
+ this._logger.onExecutionStart?.({
2474
+ type: "execution_start",
2475
+ timestamp: Date.now()
2476
+ });
2477
+ }
2478
+ /**
2479
+ * Notifies Logger of execution completion with result data and summary.
2480
+ * @param data - The execution result data
2481
+ * @param startTime - Execution start timestamp for duration calculation
2482
+ * @internal Called by SimpleExecutionHost - not intended for direct use.
2483
+ */
2484
+ async notifyExecutionDone(data, startTime) {
2485
+ const summary = await this.getSummary();
2486
+ this._logger.onExecutionDone?.({
2487
+ type: "execution_done",
2488
+ timestamp: Date.now(),
2489
+ duration: Date.now() - startTime,
2490
+ data,
2491
+ summary
2492
+ });
2493
+ }
2494
+ /**
2495
+ * Notifies Logger of execution error with error details and summary (if available).
2496
+ * Gracefully handles getSummary() failures - summary will be undefined if it fails.
2497
+ * @param error - The error that occurred
2498
+ * @param startTime - Execution start timestamp for duration calculation
2499
+ * @internal Called by SimpleExecutionHost - not intended for direct use.
2500
+ */
2501
+ async notifyExecutionError(error, startTime) {
2502
+ let summary;
2503
+ try {
2504
+ summary = await this.getSummary();
2505
+ } catch {
2506
+ }
2507
+ this._logger.onExecutionError?.({
2508
+ type: "execution_error",
2509
+ timestamp: Date.now(),
2510
+ duration: Date.now() - startTime,
2511
+ error,
2512
+ summary
2513
+ });
2514
+ }
2515
+ get _logger() {
2516
+ return this.logger;
2517
+ }
2518
+ get _startTime() {
2519
+ return this.sessionStartTime;
2520
+ }
2521
+ get _modelId() {
2522
+ if (!this.defaultLanguageModel) {
2523
+ return "unknown";
2524
+ }
2525
+ return this.extractModelId(this.defaultLanguageModel);
2526
+ }
2527
+ };
2528
+
2529
+ // src/session/streaming-session.ts
2530
+ var StreamingSession = class extends SimpleSession {
2531
+ lastEventTime;
2532
+ _terminated = false;
2533
+ constructor(options) {
2534
+ super({
2535
+ defaultLanguageModel: options.defaultLanguageModel,
2536
+ modelFactory: options.modelFactory,
2537
+ providerType: options.providerType,
2538
+ providerPricing: options.providerPricing,
2539
+ fileManager: options.fileManager,
2540
+ logger: options.logger,
2541
+ startTime: options.startTime,
2542
+ signal: options.signal,
2543
+ defaultProviderOptions: options.defaultProviderOptions,
2544
+ defaultTools: options.defaultTools
2545
+ });
2546
+ this.lastEventTime = this._startTime;
2547
+ this._logger.onExecutionStart?.({
2548
+ type: "execution_start",
2549
+ timestamp: Date.now()
2550
+ });
2551
+ }
2552
+ /**
2553
+ * Emits a streaming event with automatically attached metrics.
2554
+ *
2555
+ * Reserved types ('complete', 'error') throw at runtime - use session.done()
2556
+ * or session.fail() instead.
2557
+ *
2558
+ * @param event - The event to emit (metrics will be added automatically)
2559
+ * @returns The complete event with metrics attached
2560
+ * @throws Error when attempting to emit reserved types ('complete', 'error')
2561
+ */
2562
+ emit(event) {
2563
+ if (this._terminated) {
2564
+ throw new Error("Session already terminated. Cannot call emit() after a terminal operation.");
2565
+ }
2566
+ const eventType = event.type;
2567
+ if (eventType === "complete" || eventType === "error") {
2568
+ throw new Error(
2569
+ `Cannot emit reserved type "${eventType}". Use session.done() for completion or session.fail() for errors.`
2570
+ );
2571
+ }
2572
+ return this.emitInternal(event);
2573
+ }
2574
+ /**
2575
+ * Internal emit method - bypasses reserved type check.
2576
+ * Used by done() and fail() to emit terminal events.
2577
+ */
2578
+ emitInternal(event) {
2579
+ const metrics = this.createMetrics();
2580
+ const fullEvent = { ...event, metrics };
2581
+ this._logger.onExecutionEmit?.({
2582
+ type: "execution_emit",
2583
+ event: fullEvent
2584
+ });
2585
+ return fullEvent;
2586
+ }
2587
+ /**
2588
+ * Signals successful completion of the streaming execution.
2589
+ * Emits a 'complete' event with the result data and session summary.
2590
+ * Also triggers Logger.onExecutionDone for observability.
2591
+ * @param data - The final result data
2592
+ * @returns The complete event with data and summary
2593
+ */
2594
+ async done(data) {
2595
+ if (this._terminated) {
2596
+ throw new Error("Session already terminated. Cannot call done() after a terminal operation.");
2597
+ }
2598
+ this._terminated = true;
2599
+ const summary = await this.getSummary();
2600
+ this._logger.onExecutionDone?.({
2601
+ type: "execution_done",
2602
+ timestamp: Date.now(),
2603
+ duration: summary.totalDuration,
2604
+ data,
2605
+ summary
2606
+ });
2607
+ return this.emitInternal({
2608
+ type: "complete",
2609
+ data,
2610
+ summary
2611
+ });
2612
+ }
2613
+ /**
2614
+ * Signals that the streaming execution failed with an error.
2615
+ * Emits an 'error' event and triggers Logger.onExecutionError for observability.
2616
+ * Gracefully handles getSummary() failures - summary will be undefined if it fails.
2617
+ * @param error - The error that caused the failure
2618
+ * @param data - Optional partial result data (if any was produced before failure)
2619
+ * @returns The error event
2620
+ */
2621
+ async fail(error, data) {
2622
+ if (this._terminated) {
2623
+ throw new Error("Session already terminated. Cannot call fail() after a terminal operation.");
2624
+ }
2625
+ this._terminated = true;
2626
+ let summary;
2627
+ try {
2628
+ summary = await this.getSummary();
2629
+ } catch {
2630
+ }
2631
+ this._logger.onExecutionError?.({
2632
+ type: "execution_error",
2633
+ timestamp: Date.now(),
2634
+ duration: summary?.totalDuration ?? Date.now() - this._startTime,
2635
+ error,
2636
+ data,
2637
+ summary
2638
+ });
2639
+ const errorEvent = {
2640
+ type: "error",
2641
+ error
2642
+ };
2643
+ if (summary) {
2644
+ errorEvent.summary = summary;
2645
+ }
2646
+ if (data !== void 0) {
2647
+ errorEvent.data = data;
2648
+ }
2649
+ return this.emitInternal(errorEvent);
2650
+ }
2651
+ createMetrics() {
2652
+ const now = Date.now();
2653
+ const metrics = {
2654
+ timestamp: now,
2655
+ elapsedMs: now - this._startTime,
2656
+ deltaMs: now - this.lastEventTime
2657
+ };
2658
+ this.lastEventTime = now;
2659
+ return metrics;
2660
+ }
2661
+ };
2662
+ function createStreamingSession(options) {
2663
+ const session = new StreamingSession({
2664
+ defaultLanguageModel: options.defaultLanguageModel,
2665
+ providerType: options.providerType,
2666
+ fileManager: options.fileManager,
2667
+ logger: options.logger ?? noopLogger,
2668
+ startTime: options.startTime,
2669
+ signal: options.signal
2670
+ });
2671
+ return session;
2672
+ }
2673
+
2674
+ // src/provider/google/factory.ts
2675
+ var GoogleProvider = class _GoogleProvider extends BaseProvider {
2676
+ constructor(apiKey, defaultModelId, logger, safetySettings, pricingConfig, defaultOptions, searchEnabled = false, urlContextEnabled = false, fileCache) {
2677
+ super();
2678
+ this.apiKey = apiKey;
2679
+ this.defaultModelId = defaultModelId;
2680
+ this.logger = logger;
2681
+ this.safetySettings = safetySettings;
2682
+ this.pricingConfig = pricingConfig;
2683
+ this.defaultOptions = defaultOptions;
2684
+ this.searchEnabled = searchEnabled;
2685
+ this.urlContextEnabled = urlContextEnabled;
2686
+ this.fileCache = fileCache;
2687
+ this.google = createGoogleGenerativeAI({ apiKey });
2688
+ }
2689
+ google;
2690
+ withDefaultModel(modelId) {
2691
+ return new _GoogleProvider(
2692
+ this.apiKey,
2693
+ modelId,
2694
+ this.logger,
2695
+ this.safetySettings,
2696
+ this.pricingConfig,
2697
+ this.defaultOptions,
2698
+ this.searchEnabled,
2699
+ this.urlContextEnabled,
2700
+ this.fileCache
2701
+ );
2702
+ }
2703
+ withLogger(newLogger) {
2704
+ return new _GoogleProvider(
2705
+ this.apiKey,
2706
+ this.defaultModelId,
2707
+ newLogger,
2708
+ this.safetySettings,
2709
+ this.pricingConfig,
2710
+ this.defaultOptions,
2711
+ this.searchEnabled,
2712
+ this.urlContextEnabled,
2713
+ this.fileCache
2714
+ );
2715
+ }
2716
+ withPricing(pricing) {
2717
+ validateProviderPricing(pricing, "google");
2718
+ return new _GoogleProvider(
2719
+ this.apiKey,
2720
+ this.defaultModelId,
2721
+ this.logger,
2722
+ this.safetySettings,
2723
+ pricing,
2724
+ this.defaultOptions,
2725
+ this.searchEnabled,
2726
+ this.urlContextEnabled,
2727
+ this.fileCache
2728
+ );
2729
+ }
2730
+ /**
2731
+ * Set default provider-specific options for all LLM calls.
2732
+ * These options will be deep-merged with per-call providerOptions.
2733
+ *
2734
+ * @example
2735
+ * ```typescript
2736
+ * createGoogleProvider({ apiKey: 'xxx' })
2737
+ * .withDefaultModel('gemini-2.0-flash-thinking-exp')
2738
+ * .withDefaultOptions({
2739
+ * thinkingConfig: { includeThoughts: true, thinkingLevel: 'low' }
2740
+ * })
2741
+ * ```
2742
+ */
2743
+ withDefaultOptions(options) {
2744
+ return new _GoogleProvider(
2745
+ this.apiKey,
2746
+ this.defaultModelId,
2747
+ this.logger,
2748
+ this.safetySettings,
2749
+ this.pricingConfig,
2750
+ options,
2751
+ this.searchEnabled,
2752
+ this.urlContextEnabled,
2753
+ this.fileCache
2754
+ );
2755
+ }
2756
+ /**
2757
+ * Enable Google Search grounding for all LLM calls.
2758
+ * Allows the model to access real-time web information.
2759
+ *
2760
+ * @example
2761
+ * ```typescript
2762
+ * createGoogleProvider({ apiKey: 'xxx' })
2763
+ * .withDefaultModel('gemini-2.5-flash')
2764
+ * .withSearchEnabled()
2765
+ * ```
2766
+ */
2767
+ withSearchEnabled() {
2768
+ return new _GoogleProvider(
2769
+ this.apiKey,
2770
+ this.defaultModelId,
2771
+ this.logger,
2772
+ this.safetySettings,
2773
+ this.pricingConfig,
2774
+ this.defaultOptions,
2775
+ true,
2776
+ this.urlContextEnabled,
2777
+ this.fileCache
2778
+ );
2779
+ }
2780
+ /**
2781
+ * Enable URL Context grounding for all LLM calls.
2782
+ * Allows the model to retrieve and use content from URLs in the prompt.
2783
+ *
2784
+ * @example
2785
+ * ```typescript
2786
+ * createGoogleProvider({ apiKey: 'xxx' })
2787
+ * .withDefaultModel('gemini-2.5-flash')
2788
+ * .withUrlContextEnabled()
2789
+ * ```
2790
+ */
2791
+ withUrlContextEnabled() {
2792
+ return new _GoogleProvider(
2793
+ this.apiKey,
2794
+ this.defaultModelId,
2795
+ this.logger,
2796
+ this.safetySettings,
2797
+ this.pricingConfig,
2798
+ this.defaultOptions,
2799
+ this.searchEnabled,
2800
+ true,
2801
+ this.fileCache
2802
+ );
2803
+ }
2804
+ /**
2805
+ * Set a file cache for reusing uploaded files across sessions.
2806
+ * If no cache is provided, creates a new InMemoryFileCache.
2807
+ *
2808
+ * @example
2809
+ * ```typescript
2810
+ * // Use default InMemoryFileCache
2811
+ * createGoogleProvider({ apiKey: 'xxx' })
2812
+ * .withFileCache()
2813
+ *
2814
+ * // Use custom cache with TTL
2815
+ * const cache = new InMemoryFileCache({ defaultTTL: 3600000 });
2816
+ * createGoogleProvider({ apiKey: 'xxx' })
2817
+ * .withFileCache(cache)
2818
+ * ```
2819
+ */
2820
+ withFileCache(cache) {
2821
+ return new _GoogleProvider(
2822
+ this.apiKey,
2823
+ this.defaultModelId,
2824
+ this.logger,
2825
+ this.safetySettings,
2826
+ this.pricingConfig,
2827
+ this.defaultOptions,
2828
+ this.searchEnabled,
2829
+ this.urlContextEnabled,
2830
+ cache ?? new InMemoryFileCache()
2831
+ );
2832
+ }
2833
+ getSessionConfig() {
2834
+ const defaultTools = {
2835
+ ...this.searchEnabled && { google_search: this.google.tools.googleSearch({}) },
2836
+ ...this.urlContextEnabled && { url_context: this.google.tools.urlContext({}) }
2837
+ };
2838
+ const hasDefaultTools = this.searchEnabled || this.urlContextEnabled;
2839
+ return {
2840
+ defaultLanguageModel: this.defaultModelId ? this.createModel(this.defaultModelId) : null,
2841
+ modelFactory: (modelId) => this.createModel(modelId),
2842
+ providerType: "google",
2843
+ providerPricing: this.pricingConfig,
2844
+ fileManager: new GoogleFileManager(this.apiKey, { cache: this.fileCache }),
2845
+ logger: this.logger,
2846
+ defaultProviderOptions: this.defaultOptions ? { google: this.defaultOptions } : void 0,
2847
+ defaultTools: hasDefaultTools ? defaultTools : void 0
2848
+ };
2849
+ }
2850
+ createStreamingSession(signal) {
2851
+ return new StreamingSession({ ...this.getSessionConfig(), signal });
2852
+ }
2853
+ createSimpleSession(signal) {
2854
+ return new SimpleSession({ ...this.getSessionConfig(), signal });
2855
+ }
2856
+ /**
2857
+ * Type assertion needed because @ai-sdk/google's type signature doesn't
2858
+ * include the second parameter, but it's supported at runtime.
2859
+ */
2860
+ createModel(modelId) {
2861
+ if (this.safetySettings) {
2862
+ return this.google(modelId, { safetySettings: this.safetySettings });
2863
+ }
2864
+ return this.google(modelId);
2865
+ }
2866
+ };
2867
+ function createGoogleProvider(config) {
2868
+ return new GoogleProvider(
2869
+ config.apiKey,
2870
+ null,
2871
+ // No default model - must be set with withDefaultModel()
2872
+ noopLogger,
2873
+ config.safetySettings
2874
+ );
2875
+ }
2876
+ var OpenAIProvider = class _OpenAIProvider extends BaseProvider {
2877
+ constructor(apiKey, defaultModelId, logger, baseURL, organization, pricingConfig, defaultOptions, fileCache) {
2878
+ super();
2879
+ this.apiKey = apiKey;
2880
+ this.defaultModelId = defaultModelId;
2881
+ this.logger = logger;
2882
+ this.baseURL = baseURL;
2883
+ this.organization = organization;
2884
+ this.pricingConfig = pricingConfig;
2885
+ this.defaultOptions = defaultOptions;
2886
+ this.fileCache = fileCache;
2887
+ this.openai = createOpenAI({
2888
+ apiKey,
2889
+ baseURL,
2890
+ organization
2891
+ });
2892
+ }
2893
+ openai;
2894
+ withDefaultModel(modelId) {
2895
+ return new _OpenAIProvider(
2896
+ this.apiKey,
2897
+ modelId,
2898
+ this.logger,
2899
+ this.baseURL,
2900
+ this.organization,
2901
+ this.pricingConfig,
2902
+ this.defaultOptions,
2903
+ this.fileCache
2904
+ );
2905
+ }
2906
+ withLogger(newLogger) {
2907
+ return new _OpenAIProvider(
2908
+ this.apiKey,
2909
+ this.defaultModelId,
2910
+ newLogger,
2911
+ this.baseURL,
2912
+ this.organization,
2913
+ this.pricingConfig,
2914
+ this.defaultOptions,
2915
+ this.fileCache
2916
+ );
2917
+ }
2918
+ withPricing(pricing) {
2919
+ validateProviderPricing(pricing, "openai");
2920
+ return new _OpenAIProvider(
2921
+ this.apiKey,
2922
+ this.defaultModelId,
2923
+ this.logger,
2924
+ this.baseURL,
2925
+ this.organization,
2926
+ pricing,
2927
+ this.defaultOptions,
2928
+ this.fileCache
2929
+ );
2930
+ }
2931
+ /**
2932
+ * Set default provider-specific options for all LLM calls.
2933
+ * These options will be deep-merged with per-call providerOptions.
2934
+ *
2935
+ * @example
2936
+ * ```typescript
2937
+ * createOpenAIProvider({ apiKey: 'xxx' })
2938
+ * .withDefaultModel('gpt-4o')
2939
+ * .withDefaultOptions({
2940
+ * reasoningEffort: 'high',
2941
+ * parallelToolCalls: true,
2942
+ * })
2943
+ * ```
2944
+ */
2945
+ withDefaultOptions(options) {
2946
+ return new _OpenAIProvider(
2947
+ this.apiKey,
2948
+ this.defaultModelId,
2949
+ this.logger,
2950
+ this.baseURL,
2951
+ this.organization,
2952
+ this.pricingConfig,
2953
+ options,
2954
+ this.fileCache
2955
+ );
2956
+ }
2957
+ /**
2958
+ * Set a file cache for API consistency with GoogleProvider.
2959
+ * Note: OpenAI does not support file caching, so this is a no-op.
2960
+ *
2961
+ * @example
2962
+ * ```typescript
2963
+ * createOpenAIProvider({ apiKey: 'xxx' })
2964
+ * .withFileCache()
2965
+ * ```
2966
+ */
2967
+ withFileCache(cache) {
2968
+ return new _OpenAIProvider(
2969
+ this.apiKey,
2970
+ this.defaultModelId,
2971
+ this.logger,
2972
+ this.baseURL,
2973
+ this.organization,
2974
+ this.pricingConfig,
2975
+ this.defaultOptions,
2976
+ cache
2977
+ );
2978
+ }
2979
+ getSessionConfig() {
2980
+ return {
2981
+ defaultLanguageModel: this.defaultModelId ? this.openai(this.defaultModelId) : null,
2982
+ modelFactory: (modelId) => this.openai(modelId),
2983
+ providerType: "openai",
2984
+ providerPricing: this.pricingConfig,
2985
+ fileManager: new NoOpFileManager(),
2986
+ logger: this.logger,
2987
+ defaultProviderOptions: this.defaultOptions ? { openai: this.defaultOptions } : void 0
2988
+ };
2989
+ }
2990
+ createStreamingSession(signal) {
2991
+ return new StreamingSession({ ...this.getSessionConfig(), signal });
2992
+ }
2993
+ createSimpleSession(signal) {
2994
+ return new SimpleSession({ ...this.getSessionConfig(), signal });
2995
+ }
2996
+ };
2997
+ function createOpenAIProvider(config) {
2998
+ return new OpenAIProvider(
2999
+ config.apiKey,
3000
+ null,
3001
+ // No default model - must be set with withDefaultModel()
3002
+ noopLogger,
3003
+ config.baseURL,
3004
+ config.organization
3005
+ );
3006
+ }
3007
+
3008
+ // src/validation/validation-history.ts
3009
+ var ValidationHistory = class {
3010
+ attempts = [];
3011
+ get nextAttempt() {
3012
+ return this.attempts.length + 1;
3013
+ }
3014
+ get last() {
3015
+ return this.attempts.at(-1);
3016
+ }
3017
+ get all() {
3018
+ return this.attempts;
3019
+ }
3020
+ get failureReasons() {
3021
+ return this.attempts.filter((a) => !a.valid && a.reason).map((a) => a.reason);
3022
+ }
3023
+ get isRetry() {
3024
+ return this.attempts.length > 0;
3025
+ }
3026
+ /** Internal use only - not exposed via ReadonlyValidationHistory. */
3027
+ add(result, validation) {
3028
+ this.attempts.push({
3029
+ result,
3030
+ ...validation,
3031
+ attempt: this.attempts.length + 1
3032
+ });
3033
+ }
3034
+ };
3035
+
3036
+ // src/validation/errors.ts
3037
+ var ValidationErrorCode = /* @__PURE__ */ ((ValidationErrorCode2) => {
3038
+ ValidationErrorCode2["VALIDATION_EXHAUSTED"] = "VALIDATION_EXHAUSTED";
3039
+ return ValidationErrorCode2;
3040
+ })(ValidationErrorCode || {});
3041
+ var ValidationExhaustedError = class extends AgtlantisError {
3042
+ constructor(message, history) {
3043
+ super(message, {
3044
+ code: "VALIDATION_EXHAUSTED" /* VALIDATION_EXHAUSTED */,
3045
+ context: {
3046
+ attempts: history.all.length,
3047
+ failureReasons: history.failureReasons
3048
+ }
3049
+ });
3050
+ this.history = history;
3051
+ this.name = "ValidationExhaustedError";
3052
+ }
3053
+ };
3054
+
3055
+ // src/validation/with-validation.ts
3056
+ async function withValidation(execute, options) {
3057
+ const { validate, maxAttempts: rawMax, signal, retryDelay, onAttempt } = options;
3058
+ const maxAttempts = Math.max(1, rawMax ?? 3);
3059
+ const history = new ValidationHistory();
3060
+ while (history.nextAttempt <= maxAttempts) {
3061
+ signal?.throwIfAborted();
3062
+ const result = await execute(history);
3063
+ const validation = await validate(result, history);
3064
+ history.add(result, validation);
3065
+ onAttempt?.(history.last);
3066
+ if (validation.valid) {
3067
+ return result;
3068
+ }
3069
+ const hasMoreAttempts = history.nextAttempt <= maxAttempts;
3070
+ if (retryDelay && hasMoreAttempts) {
3071
+ await new Promise((resolve2) => setTimeout(resolve2, retryDelay));
3072
+ }
3073
+ }
3074
+ throw new ValidationExhaustedError(
3075
+ `Validation failed after ${history.all.length} attempts`,
3076
+ history
3077
+ );
3078
+ }
3079
+
3080
+ export { ANTHROPIC_PRICING, AgtlantisError, BaseProvider, ConfigurationError, ConfigurationErrorCode, DEFAULT_FALLBACK_PRICING, DEFAULT_MAX_SIZE, DEFAULT_PRICING_CONFIG, ERRORS, EXTENSION_TO_MIME, ExecutionError, ExecutionErrorCode, FileError, FileErrorCode, FilePromptRepository, GOOGLE_PRICING, GoogleFileManager, GoogleProvider, InMemoryFileCache, NoOpFileManager, OPENAI_PRICING, ProgressivePattern, PromptError, PromptErrorCode, PromptIOError, PromptInvalidFormatError, PromptNotFoundError, PromptTemplate, PromptTemplateError, SessionSummary, SimpleExecutionHost, SimpleSession, StreamingExecutionHost, StreamingSession, TOOL_CALLING_PROTOCOL, ValidationErrorCode, ValidationExhaustedError, ValidationHistory, calculateCost, calculateCostFromUsage, calculateTotalCost, combineSignals, compileTemplate, computeFileSourceHash, configurePricing, createFilePromptRepository, createGoogleProvider, createHookRunner, createLogger, createOpenAIProvider, createStreamingSession, createZeroUsage, defineProgressivePattern, detectProviderType, determineResultStatus, getDuration, getEffectivePricing, getFileSourceDisplayInfo, getFileSourcesDisplayInfo, getModelPricing, getPricingConfig, inferMediaType, isAbortError, isFileSource, isFileSourceBase64, isFileSourceData, isFileSourcePath, isFileSourceUrl, mapExecution, mapExecutionResult, mergeUsages, noopLogger, normalizeError, resetPricingConfig, resolveFileSource, resolveFileSourcesInInput, scanForFileSources, validateModelPricing, validatePricingConfig, validateProviderPricing, withValidation };
3081
+ //# sourceMappingURL=index.js.map
3082
+ //# sourceMappingURL=index.js.map