llmist 15.11.0 → 15.12.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.cjs CHANGED
@@ -4039,6 +4039,955 @@ var init_registry = __esm({
4039
4039
  }
4040
4040
  });
4041
4041
 
4042
+ // src/agent/file-logging.ts
4043
+ function formatLlmRequest(messages) {
4044
+ const lines = [];
4045
+ for (const msg of messages) {
4046
+ lines.push(`=== ${msg.role.toUpperCase()} ===`);
4047
+ lines.push(msg.content ? extractMessageText(msg.content) : "");
4048
+ lines.push("");
4049
+ }
4050
+ return lines.join("\n");
4051
+ }
4052
+ function formatCallNumber(n, padding = 4) {
4053
+ return n.toString().padStart(padding, "0");
4054
+ }
4055
+ async function writeLogFile(dir, filename, content) {
4056
+ await (0, import_promises2.mkdir)(dir, { recursive: true });
4057
+ await (0, import_promises2.writeFile)((0, import_node_path3.join)(dir, filename), content, "utf-8");
4058
+ }
4059
+ function createFileLoggingHooks(options) {
4060
+ const {
4061
+ directory,
4062
+ startingCounter = 1,
4063
+ counterPadding = 4,
4064
+ skipSubagents = true,
4065
+ formatRequest = formatLlmRequest,
4066
+ onFileWritten
4067
+ } = options;
4068
+ let callCounter = startingCounter - 1;
4069
+ return {
4070
+ observers: {
4071
+ /**
4072
+ * Write request file when LLM call is ready (messages are finalized).
4073
+ */
4074
+ onLLMCallReady: async (context) => {
4075
+ if (skipSubagents && context.subagentContext) {
4076
+ return;
4077
+ }
4078
+ callCounter++;
4079
+ const filename = `${formatCallNumber(callCounter, counterPadding)}.request`;
4080
+ const content = formatRequest(context.options.messages);
4081
+ try {
4082
+ await writeLogFile(directory, filename, content);
4083
+ if (onFileWritten) {
4084
+ onFileWritten({
4085
+ filePath: (0, import_node_path3.join)(directory, filename),
4086
+ type: "request",
4087
+ callNumber: callCounter,
4088
+ contentLength: content.length
4089
+ });
4090
+ }
4091
+ } catch (error) {
4092
+ console.warn(`[file-logging] Failed to write ${filename}:`, error);
4093
+ }
4094
+ },
4095
+ /**
4096
+ * Write response file when LLM call completes.
4097
+ */
4098
+ onLLMCallComplete: async (context) => {
4099
+ if (skipSubagents && context.subagentContext) {
4100
+ return;
4101
+ }
4102
+ const filename = `${formatCallNumber(callCounter, counterPadding)}.response`;
4103
+ const content = context.rawResponse;
4104
+ try {
4105
+ await writeLogFile(directory, filename, content);
4106
+ if (onFileWritten) {
4107
+ onFileWritten({
4108
+ filePath: (0, import_node_path3.join)(directory, filename),
4109
+ type: "response",
4110
+ callNumber: callCounter,
4111
+ contentLength: content.length
4112
+ });
4113
+ }
4114
+ } catch (error) {
4115
+ console.warn(`[file-logging] Failed to write ${filename}:`, error);
4116
+ }
4117
+ }
4118
+ }
4119
+ };
4120
+ }
4121
+ function getEnvFileLoggingHooks() {
4122
+ const directory = process.env[ENV_LOG_RAW_DIRECTORY]?.trim();
4123
+ if (!directory) {
4124
+ return void 0;
4125
+ }
4126
+ return createFileLoggingHooks({ directory });
4127
+ }
4128
+ var import_promises2, import_node_path3, ENV_LOG_RAW_DIRECTORY;
4129
+ var init_file_logging = __esm({
4130
+ "src/agent/file-logging.ts"() {
4131
+ "use strict";
4132
+ import_promises2 = require("fs/promises");
4133
+ import_node_path3 = require("path");
4134
+ init_messages();
4135
+ ENV_LOG_RAW_DIRECTORY = "LLMIST_LOG_RAW_DIRECTORY";
4136
+ }
4137
+ });
4138
+
4139
+ // src/agent/hook-presets.ts
4140
+ var HookPresets;
4141
+ var init_hook_presets = __esm({
4142
+ "src/agent/hook-presets.ts"() {
4143
+ "use strict";
4144
+ init_file_logging();
4145
+ HookPresets = class _HookPresets {
4146
+ /**
4147
+ * Logs LLM calls and gadget execution to console with optional verbosity.
4148
+ *
4149
+ * **Output (basic mode):**
4150
+ * - LLM call start/complete events with iteration numbers
4151
+ * - Gadget execution start/complete with gadget names
4152
+ * - Token counts when available
4153
+ *
4154
+ * **Output (verbose mode):**
4155
+ * - All basic mode output
4156
+ * - Full gadget parameters (formatted JSON)
4157
+ * - Full gadget results
4158
+ * - Complete LLM response text
4159
+ *
4160
+ * **Use cases:**
4161
+ * - Basic development debugging and execution flow visibility
4162
+ * - Understanding agent decision-making and tool usage
4163
+ * - Troubleshooting gadget invocations
4164
+ *
4165
+ * **Performance:** Minimal overhead. Console writes are synchronous but fast.
4166
+ *
4167
+ * @param options - Logging options
4168
+ * @param options.verbose - Include full parameters and results. Default: false
4169
+ * @returns Hook configuration that can be passed to .withHooks()
4170
+ *
4171
+ * @example
4172
+ * ```typescript
4173
+ * // Basic logging
4174
+ * await LLMist.createAgent()
4175
+ * .withHooks(HookPresets.logging())
4176
+ * .ask("Calculate 15 * 23");
4177
+ * // Output: [LLM] Starting call (iteration 0)
4178
+ * // [GADGET] Executing Calculator
4179
+ * // [GADGET] Completed Calculator
4180
+ * // [LLM] Completed (tokens: 245)
4181
+ * ```
4182
+ *
4183
+ * @example
4184
+ * ```typescript
4185
+ * // Verbose logging with full details
4186
+ * await LLMist.createAgent()
4187
+ * .withHooks(HookPresets.logging({ verbose: true }))
4188
+ * .ask("Calculate 15 * 23");
4189
+ * // Output includes: parameters, results, and full responses
4190
+ * ```
4191
+ *
4192
+ * @example
4193
+ * ```typescript
4194
+ * // Environment-based verbosity
4195
+ * const isDev = process.env.NODE_ENV === 'development';
4196
+ * .withHooks(HookPresets.logging({ verbose: isDev }))
4197
+ * ```
4198
+ *
4199
+ * @see {@link https://github.com/zbigniewsobiecki/llmist/blob/main/docs/HOOKS.md#hookpresetsloggingoptions | Full documentation}
4200
+ */
4201
+ static logging(options = {}) {
4202
+ return {
4203
+ observers: {
4204
+ onLLMCallStart: async (ctx) => {
4205
+ console.log(`[LLM] Starting call (iteration ${ctx.iteration})`);
4206
+ },
4207
+ onLLMCallComplete: async (ctx) => {
4208
+ const tokens = ctx.usage?.totalTokens ?? "unknown";
4209
+ console.log(`[LLM] Completed (tokens: ${tokens})`);
4210
+ if (options.verbose && ctx.finalMessage) {
4211
+ console.log(`[LLM] Response: ${ctx.finalMessage}`);
4212
+ }
4213
+ },
4214
+ onGadgetExecutionStart: async (ctx) => {
4215
+ console.log(`[GADGET] Executing ${ctx.gadgetName}`);
4216
+ if (options.verbose) {
4217
+ console.log(`[GADGET] Parameters:`, JSON.stringify(ctx.parameters, null, 2));
4218
+ }
4219
+ },
4220
+ onGadgetExecutionComplete: async (ctx) => {
4221
+ console.log(`[GADGET] Completed ${ctx.gadgetName}`);
4222
+ if (options.verbose) {
4223
+ const display = ctx.error ?? ctx.finalResult ?? "(no result)";
4224
+ console.log(`[GADGET] Result: ${display}`);
4225
+ }
4226
+ }
4227
+ }
4228
+ };
4229
+ }
4230
+ /**
4231
+ * Measures and logs execution time for LLM calls and gadgets.
4232
+ *
4233
+ * **Output:**
4234
+ * - Duration in milliseconds with ⏱️ emoji for each operation
4235
+ * - Separate timing for each LLM iteration
4236
+ * - Separate timing for each gadget execution
4237
+ *
4238
+ * **Use cases:**
4239
+ * - Performance profiling and optimization
4240
+ * - Identifying slow operations (LLM calls vs gadget execution)
4241
+ * - Monitoring response times in production
4242
+ * - Capacity planning and SLA tracking
4243
+ *
4244
+ * **Performance:** Negligible overhead. Uses Date.now() for timing measurements.
4245
+ *
4246
+ * @returns Hook configuration that can be passed to .withHooks()
4247
+ *
4248
+ * @example
4249
+ * ```typescript
4250
+ * // Basic timing
4251
+ * await LLMist.createAgent()
4252
+ * .withHooks(HookPresets.timing())
4253
+ * .withGadgets(Weather, Database)
4254
+ * .ask("What's the weather in NYC?");
4255
+ * // Output: ⏱️ LLM call took 1234ms
4256
+ * // ⏱️ Gadget Weather took 567ms
4257
+ * // ⏱️ LLM call took 890ms
4258
+ * ```
4259
+ *
4260
+ * @example
4261
+ * ```typescript
4262
+ * // Combined with logging for full context
4263
+ * .withHooks(HookPresets.merge(
4264
+ * HookPresets.logging(),
4265
+ * HookPresets.timing()
4266
+ * ))
4267
+ * ```
4268
+ *
4269
+ * @example
4270
+ * ```typescript
4271
+ * // Correlate performance with cost
4272
+ * .withHooks(HookPresets.merge(
4273
+ * HookPresets.timing(),
4274
+ * HookPresets.tokenTracking()
4275
+ * ))
4276
+ * ```
4277
+ *
4278
+ * @see {@link https://github.com/zbigniewsobiecki/llmist/blob/main/docs/HOOKS.md#hookpresetstiming | Full documentation}
4279
+ */
4280
+ static timing() {
4281
+ const timings = /* @__PURE__ */ new Map();
4282
+ return {
4283
+ observers: {
4284
+ onLLMCallStart: async (ctx) => {
4285
+ timings.set(`llm-${ctx.iteration}`, Date.now());
4286
+ },
4287
+ onLLMCallComplete: async (ctx) => {
4288
+ const start = timings.get(`llm-${ctx.iteration}`);
4289
+ if (start) {
4290
+ const duration = Date.now() - start;
4291
+ console.log(`\u23F1\uFE0F LLM call took ${duration}ms`);
4292
+ timings.delete(`llm-${ctx.iteration}`);
4293
+ }
4294
+ },
4295
+ onGadgetExecutionStart: async (ctx) => {
4296
+ const key = `gadget-${ctx.gadgetName}-${Date.now()}`;
4297
+ timings.set(key, Date.now());
4298
+ ctx._timingKey = key;
4299
+ },
4300
+ onGadgetExecutionComplete: async (ctx) => {
4301
+ const key = ctx._timingKey;
4302
+ if (key) {
4303
+ const start = timings.get(key);
4304
+ if (start) {
4305
+ const duration = Date.now() - start;
4306
+ console.log(`\u23F1\uFE0F Gadget ${ctx.gadgetName} took ${duration}ms`);
4307
+ timings.delete(key);
4308
+ }
4309
+ }
4310
+ }
4311
+ }
4312
+ };
4313
+ }
4314
+ /**
4315
+ * Tracks cumulative token usage across all LLM calls.
4316
+ *
4317
+ * **Output:**
4318
+ * - Per-call token count with 📊 emoji
4319
+ * - Cumulative total across all calls
4320
+ * - Call count for average calculations
4321
+ *
4322
+ * **Use cases:**
4323
+ * - Cost monitoring and budget tracking
4324
+ * - Optimizing prompts to reduce token usage
4325
+ * - Comparing token efficiency across different approaches
4326
+ * - Real-time cost estimation
4327
+ *
4328
+ * **Performance:** Minimal overhead. Simple counter increments.
4329
+ *
4330
+ * **Note:** Token counts depend on the provider's response. Some providers
4331
+ * may not include usage data, in which case counts won't be logged.
4332
+ *
4333
+ * @returns Hook configuration that can be passed to .withHooks()
4334
+ *
4335
+ * @example
4336
+ * ```typescript
4337
+ * // Basic token tracking
4338
+ * await LLMist.createAgent()
4339
+ * .withHooks(HookPresets.tokenTracking())
4340
+ * .ask("Summarize this document...");
4341
+ * // Output: 📊 Tokens this call: 1,234
4342
+ * // 📊 Total tokens: 1,234 (across 1 calls)
4343
+ * // 📊 Tokens this call: 567
4344
+ * // 📊 Total tokens: 1,801 (across 2 calls)
4345
+ * ```
4346
+ *
4347
+ * @example
4348
+ * ```typescript
4349
+ * // Cost calculation with custom hook
4350
+ * let totalTokens = 0;
4351
+ * .withHooks(HookPresets.merge(
4352
+ * HookPresets.tokenTracking(),
4353
+ * {
4354
+ * observers: {
4355
+ * onLLMCallComplete: async (ctx) => {
4356
+ * totalTokens += ctx.usage?.totalTokens ?? 0;
4357
+ * const cost = (totalTokens / 1_000_000) * 3.0; // $3 per 1M tokens
4358
+ * console.log(`💰 Estimated cost: $${cost.toFixed(4)}`);
4359
+ * },
4360
+ * },
4361
+ * }
4362
+ * ))
4363
+ * ```
4364
+ *
4365
+ * @see {@link https://github.com/zbigniewsobiecki/llmist/blob/main/docs/HOOKS.md#hookpresetstokentracking | Full documentation}
4366
+ */
4367
+ static tokenTracking() {
4368
+ let totalTokens = 0;
4369
+ let totalCalls = 0;
4370
+ return {
4371
+ observers: {
4372
+ onLLMCallComplete: async (ctx) => {
4373
+ totalCalls++;
4374
+ if (ctx.usage?.totalTokens) {
4375
+ totalTokens += ctx.usage.totalTokens;
4376
+ console.log(`\u{1F4CA} Tokens this call: ${ctx.usage.totalTokens}`);
4377
+ console.log(`\u{1F4CA} Total tokens: ${totalTokens} (across ${totalCalls} calls)`);
4378
+ }
4379
+ }
4380
+ }
4381
+ };
4382
+ }
4383
+ /**
4384
+ * Tracks comprehensive progress metrics including iterations, tokens, cost, and timing.
4385
+ *
4386
+ * **This preset showcases llmist's core capabilities by demonstrating:**
4387
+ * - Observer pattern for non-intrusive monitoring
4388
+ * - Integration with ModelRegistry for cost estimation
4389
+ * - Callback-based architecture for flexible UI updates
4390
+ * - Provider-agnostic token and cost tracking
4391
+ *
4392
+ * Unlike `tokenTracking()` which only logs to console, this preset provides
4393
+ * structured data through callbacks, making it perfect for building custom UIs,
4394
+ * dashboards, or progress indicators (like the llmist CLI).
4395
+ *
4396
+ * **Output (when logProgress: true):**
4397
+ * - Iteration number and call count
4398
+ * - Cumulative token usage (input + output)
4399
+ * - Cumulative cost in USD (requires modelRegistry)
4400
+ * - Elapsed time in seconds
4401
+ *
4402
+ * **Use cases:**
4403
+ * - Building CLI progress indicators with live updates
4404
+ * - Creating web dashboards with real-time metrics
4405
+ * - Budget monitoring and cost alerts
4406
+ * - Performance tracking and optimization
4407
+ * - Custom logging to external systems (Datadog, CloudWatch, etc.)
4408
+ *
4409
+ * **Performance:** Minimal overhead. Uses Date.now() for timing and optional
4410
+ * ModelRegistry.estimateCost() which is O(1) lookup. Callback invocation is
4411
+ * synchronous and fast.
4412
+ *
4413
+ * @param options - Progress tracking options
4414
+ * @param options.modelRegistry - ModelRegistry for cost estimation (optional)
4415
+ * @param options.onProgress - Callback invoked after each LLM call (optional)
4416
+ * @param options.logProgress - Log progress to console (default: false)
4417
+ * @returns Hook configuration with progress tracking observers
4418
+ *
4419
+ * @example
4420
+ * ```typescript
4421
+ * // Basic usage with callback (RECOMMENDED - used by llmist CLI)
4422
+ * import { LLMist, HookPresets } from 'llmist';
4423
+ *
4424
+ * const client = LLMist.create();
4425
+ *
4426
+ * await client.agent()
4427
+ * .withHooks(HookPresets.progressTracking({
4428
+ * modelRegistry: client.modelRegistry,
4429
+ * onProgress: (stats) => {
4430
+ * // Update your UI with stats
4431
+ * console.log(`#${stats.currentIteration} | ${stats.totalTokens} tokens | $${stats.totalCost.toFixed(4)}`);
4432
+ * }
4433
+ * }))
4434
+ * .withGadgets(Calculator)
4435
+ * .ask("Calculate 15 * 23");
4436
+ * // Output: #1 | 245 tokens | $0.0012
4437
+ * ```
4438
+ *
4439
+ * @example
4440
+ * ```typescript
4441
+ * // Console logging mode (quick debugging)
4442
+ * await client.agent()
4443
+ * .withHooks(HookPresets.progressTracking({
4444
+ * modelRegistry: client.modelRegistry,
4445
+ * logProgress: true // Simple console output
4446
+ * }))
4447
+ * .ask("Your prompt");
4448
+ * // Output: 📊 Progress: Iteration #1 | 245 tokens | $0.0012 | 1.2s
4449
+ * ```
4450
+ *
4451
+ * @example
4452
+ * ```typescript
4453
+ * // Budget monitoring with alerts
4454
+ * const BUDGET_USD = 0.10;
4455
+ *
4456
+ * await client.agent()
4457
+ * .withHooks(HookPresets.progressTracking({
4458
+ * modelRegistry: client.modelRegistry,
4459
+ * onProgress: (stats) => {
4460
+ * if (stats.totalCost > BUDGET_USD) {
4461
+ * throw new Error(`Budget exceeded: $${stats.totalCost.toFixed(4)}`);
4462
+ * }
4463
+ * }
4464
+ * }))
4465
+ * .ask("Long running task...");
4466
+ * ```
4467
+ *
4468
+ * @example
4469
+ * ```typescript
4470
+ * // Web dashboard integration
4471
+ * let progressBar: HTMLElement;
4472
+ *
4473
+ * await client.agent()
4474
+ * .withHooks(HookPresets.progressTracking({
4475
+ * modelRegistry: client.modelRegistry,
4476
+ * onProgress: (stats) => {
4477
+ * // Update web UI in real-time
4478
+ * progressBar.textContent = `Iteration ${stats.currentIteration}`;
4479
+ * progressBar.dataset.cost = stats.totalCost.toFixed(4);
4480
+ * progressBar.dataset.tokens = stats.totalTokens.toString();
4481
+ * }
4482
+ * }))
4483
+ * .ask("Your prompt");
4484
+ * ```
4485
+ *
4486
+ * @example
4487
+ * ```typescript
4488
+ * // External logging (Datadog, CloudWatch, etc.)
4489
+ * await client.agent()
4490
+ * .withHooks(HookPresets.progressTracking({
4491
+ * modelRegistry: client.modelRegistry,
4492
+ * onProgress: async (stats) => {
4493
+ * await metrics.gauge('llm.iteration', stats.currentIteration);
4494
+ * await metrics.gauge('llm.cost', stats.totalCost);
4495
+ * await metrics.gauge('llm.tokens', stats.totalTokens);
4496
+ * }
4497
+ * }))
4498
+ * .ask("Your prompt");
4499
+ * ```
4500
+ *
4501
+ * @see {@link https://github.com/zbigniewsobiecki/llmist/blob/main/docs/HOOKS.md#hookpresetsprogresstrackingoptions | Full documentation}
4502
+ * @see {@link ProgressTrackingOptions} for detailed options
4503
+ * @see {@link ProgressStats} for the callback data structure
4504
+ */
4505
+ static progressTracking(options) {
4506
+ const { modelRegistry, onProgress, logProgress = false } = options ?? {};
4507
+ let totalCalls = 0;
4508
+ let currentIteration = 0;
4509
+ let totalInputTokens = 0;
4510
+ let totalOutputTokens = 0;
4511
+ let totalCost = 0;
4512
+ let totalGadgetCost = 0;
4513
+ const startTime = Date.now();
4514
+ return {
4515
+ observers: {
4516
+ // Track iteration on each LLM call start
4517
+ onLLMCallStart: async (ctx) => {
4518
+ currentIteration++;
4519
+ },
4520
+ // Accumulate metrics and report progress on each LLM call completion
4521
+ onLLMCallComplete: async (ctx) => {
4522
+ totalCalls++;
4523
+ if (ctx.usage) {
4524
+ totalInputTokens += ctx.usage.inputTokens;
4525
+ totalOutputTokens += ctx.usage.outputTokens;
4526
+ if (modelRegistry) {
4527
+ try {
4528
+ const modelName = ctx.options.model.includes(":") ? ctx.options.model.split(":")[1] : ctx.options.model;
4529
+ const costEstimate = modelRegistry.estimateCost(
4530
+ modelName,
4531
+ ctx.usage.inputTokens,
4532
+ ctx.usage.outputTokens
4533
+ );
4534
+ if (costEstimate) {
4535
+ totalCost += costEstimate.totalCost;
4536
+ }
4537
+ } catch (error) {
4538
+ if (logProgress) {
4539
+ console.warn(`\u26A0\uFE0F Cost estimation failed:`, error);
4540
+ }
4541
+ }
4542
+ }
4543
+ }
4544
+ const stats = {
4545
+ currentIteration,
4546
+ totalCalls,
4547
+ totalInputTokens,
4548
+ totalOutputTokens,
4549
+ totalTokens: totalInputTokens + totalOutputTokens,
4550
+ totalCost: totalCost + totalGadgetCost,
4551
+ elapsedSeconds: Number(((Date.now() - startTime) / 1e3).toFixed(1))
4552
+ };
4553
+ if (onProgress) {
4554
+ onProgress(stats);
4555
+ }
4556
+ if (logProgress) {
4557
+ const formattedTokens = stats.totalTokens >= 1e3 ? `${(stats.totalTokens / 1e3).toFixed(1)}k` : `${stats.totalTokens}`;
4558
+ const formattedCost = stats.totalCost > 0 ? `$${stats.totalCost.toFixed(4)}` : "$0";
4559
+ console.log(
4560
+ `\u{1F4CA} Progress: Iteration #${stats.currentIteration} | ${formattedTokens} tokens | ${formattedCost} | ${stats.elapsedSeconds}s`
4561
+ );
4562
+ }
4563
+ },
4564
+ // Track gadget execution costs
4565
+ onGadgetExecutionComplete: async (ctx) => {
4566
+ if (ctx.cost && ctx.cost > 0) {
4567
+ totalGadgetCost += ctx.cost;
4568
+ }
4569
+ }
4570
+ }
4571
+ };
4572
+ }
4573
+ /**
4574
+ * Logs detailed error information for debugging and troubleshooting.
4575
+ *
4576
+ * **Output:**
4577
+ * - LLM errors with ❌ emoji, including model and recovery status
4578
+ * - Gadget errors with full context (parameters, error message)
4579
+ * - Separate logging for LLM and gadget failures
4580
+ *
4581
+ * **Use cases:**
4582
+ * - Troubleshooting production issues
4583
+ * - Understanding error patterns and frequency
4584
+ * - Debugging error recovery behavior
4585
+ * - Collecting error metrics for monitoring
4586
+ *
4587
+ * **Performance:** Minimal overhead. Only logs when errors occur.
4588
+ *
4589
+ * @returns Hook configuration that can be passed to .withHooks()
4590
+ *
4591
+ * @example
4592
+ * ```typescript
4593
+ * // Basic error logging
4594
+ * await LLMist.createAgent()
4595
+ * .withHooks(HookPresets.errorLogging())
4596
+ * .withGadgets(Database)
4597
+ * .ask("Fetch user data");
4598
+ * // Output (on LLM error): ❌ LLM Error (iteration 1): Rate limit exceeded
4599
+ * // Model: gpt-5-nano
4600
+ * // Recovered: true
4601
+ * // Output (on gadget error): ❌ Gadget Error: Database
4602
+ * // Error: Connection timeout
4603
+ * // Parameters: {...}
4604
+ * ```
4605
+ *
4606
+ * @example
4607
+ * ```typescript
4608
+ * // Combine with monitoring for full context
4609
+ * .withHooks(HookPresets.merge(
4610
+ * HookPresets.monitoring(), // Includes errorLogging
4611
+ * customErrorAnalytics
4612
+ * ))
4613
+ * ```
4614
+ *
4615
+ * @example
4616
+ * ```typescript
4617
+ * // Error analytics collection
4618
+ * const errors: any[] = [];
4619
+ * .withHooks(HookPresets.merge(
4620
+ * HookPresets.errorLogging(),
4621
+ * {
4622
+ * observers: {
4623
+ * onLLMCallError: async (ctx) => {
4624
+ * errors.push({ type: 'llm', error: ctx.error, recovered: ctx.recovered });
4625
+ * },
4626
+ * },
4627
+ * }
4628
+ * ))
4629
+ * ```
4630
+ *
4631
+ * @see {@link https://github.com/zbigniewsobiecki/llmist/blob/main/docs/HOOKS.md#hookpresetserrorlogging | Full documentation}
4632
+ */
4633
+ static errorLogging() {
4634
+ return {
4635
+ observers: {
4636
+ onLLMCallError: async (ctx) => {
4637
+ console.error(`\u274C LLM Error (iteration ${ctx.iteration}):`, ctx.error.message);
4638
+ console.error(` Model: ${ctx.options.model}`);
4639
+ console.error(` Recovered: ${ctx.recovered}`);
4640
+ },
4641
+ onGadgetExecutionComplete: async (ctx) => {
4642
+ if (ctx.error) {
4643
+ console.error(`\u274C Gadget Error: ${ctx.gadgetName}`);
4644
+ console.error(` Error: ${ctx.error}`);
4645
+ console.error(` Parameters:`, JSON.stringify(ctx.parameters, null, 2));
4646
+ }
4647
+ }
4648
+ }
4649
+ };
4650
+ }
4651
+ /**
4652
+ * Tracks context compaction events.
4653
+ *
4654
+ * **Output:**
4655
+ * - Compaction events with 🗜️ emoji
4656
+ * - Strategy name, tokens before/after, and savings
4657
+ * - Cumulative statistics
4658
+ *
4659
+ * **Use cases:**
4660
+ * - Monitoring long-running conversations
4661
+ * - Understanding when and how compaction occurs
4662
+ * - Debugging context management issues
4663
+ *
4664
+ * **Performance:** Minimal overhead. Simple console output.
4665
+ *
4666
+ * @returns Hook configuration that can be passed to .withHooks()
4667
+ *
4668
+ * @example
4669
+ * ```typescript
4670
+ * await LLMist.createAgent()
4671
+ * .withHooks(HookPresets.compactionTracking())
4672
+ * .ask("Your prompt");
4673
+ * ```
4674
+ */
4675
+ static compactionTracking() {
4676
+ return {
4677
+ observers: {
4678
+ onCompaction: async (ctx) => {
4679
+ const saved = ctx.event.tokensBefore - ctx.event.tokensAfter;
4680
+ const percent = (saved / ctx.event.tokensBefore * 100).toFixed(1);
4681
+ console.log(
4682
+ `\u{1F5DC}\uFE0F Compaction (${ctx.event.strategy}): ${ctx.event.tokensBefore} \u2192 ${ctx.event.tokensAfter} tokens (saved ${saved}, ${percent}%)`
4683
+ );
4684
+ console.log(` Messages: ${ctx.event.messagesBefore} \u2192 ${ctx.event.messagesAfter}`);
4685
+ if (ctx.stats.totalCompactions > 1) {
4686
+ console.log(
4687
+ ` Cumulative: ${ctx.stats.totalCompactions} compactions, ${ctx.stats.totalTokensSaved} tokens saved`
4688
+ );
4689
+ }
4690
+ }
4691
+ }
4692
+ };
4693
+ }
4694
+ /**
4695
+ * Logs LLM requests and responses to files for debugging and audit trails.
4696
+ *
4697
+ * Files are named `{counter}.request` and `{counter}.response` where counter
4698
+ * is a zero-padded number that increments with each LLM call.
4699
+ *
4700
+ * **Output:**
4701
+ * - Request files containing formatted LLM message history
4702
+ * - Response files containing raw LLM output
4703
+ *
4704
+ * **Use cases:**
4705
+ * - Debugging complex agent interactions
4706
+ * - Creating audit trails for compliance
4707
+ * - Analyzing LLM behavior patterns
4708
+ * - Replaying conversations for testing
4709
+ *
4710
+ * **Performance:** Minimal overhead - only file I/O, no synchronous blocking.
4711
+ *
4712
+ * **Note:** Can also be enabled via `LLMIST_LOG_RAW_DIRECTORY` environment
4713
+ * variable for zero-code activation.
4714
+ *
4715
+ * @param options - File logging options
4716
+ * @param options.directory - Directory where log files will be written
4717
+ * @param options.startingCounter - Starting counter (default: 1)
4718
+ * @param options.counterPadding - Number of digits for padding (default: 4)
4719
+ * @param options.skipSubagents - Skip subagent calls (default: true)
4720
+ * @param options.formatRequest - Custom request formatter
4721
+ * @param options.onFileWritten - Callback after each file is written
4722
+ * @returns Hook configuration that can be passed to .withHooks()
4723
+ *
4724
+ * @example
4725
+ * ```typescript
4726
+ * // Basic file logging
4727
+ * await LLMist.createAgent()
4728
+ * .withHooks(HookPresets.fileLogging({
4729
+ * directory: './debug-logs'
4730
+ * }))
4731
+ * .ask("Hello");
4732
+ * // Creates: ./debug-logs/0001.request
4733
+ * // ./debug-logs/0001.response
4734
+ * ```
4735
+ *
4736
+ * @example
4737
+ * ```typescript
4738
+ * // With callback for tracking
4739
+ * await LLMist.createAgent()
4740
+ * .withHooks(HookPresets.fileLogging({
4741
+ * directory: './logs',
4742
+ * onFileWritten: (info) => {
4743
+ * console.log(`Wrote ${info.type}: ${info.filePath}`);
4744
+ * }
4745
+ * }))
4746
+ * .ask("Hello");
4747
+ * ```
4748
+ *
4749
+ * @example
4750
+ * ```typescript
4751
+ * // Combined with other presets
4752
+ * .withHooks(HookPresets.merge(
4753
+ * HookPresets.fileLogging({ directory: logDir }),
4754
+ * HookPresets.progressTracking({ onProgress: updateUI }),
4755
+ * HookPresets.errorLogging()
4756
+ * ))
4757
+ * ```
4758
+ *
4759
+ * @see {@link https://github.com/zbigniewsobiecki/llmist/blob/main/docs/HOOKS.md#hookpresetsfileloggingoptions | Full documentation}
4760
+ */
4761
+ static fileLogging(options) {
4762
+ return createFileLoggingHooks(options);
4763
+ }
4764
+ /**
4765
+ * Returns empty hook configuration for clean output without any logging.
4766
+ *
4767
+ * **Output:**
4768
+ * - None. Returns {} (empty object).
4769
+ *
4770
+ * **Use cases:**
4771
+ * - Clean test output without console noise
4772
+ * - Production environments where logging is handled externally
4773
+ * - Baseline for custom hook development
4774
+ * - Temporary disable of all hook output
4775
+ *
4776
+ * **Performance:** Zero overhead. No-op hook configuration.
4777
+ *
4778
+ * @returns Empty hook configuration
4779
+ *
4780
+ * @example
4781
+ * ```typescript
4782
+ * // Clean test output
4783
+ * describe('Agent tests', () => {
4784
+ * it('should calculate correctly', async () => {
4785
+ * const result = await LLMist.createAgent()
4786
+ * .withHooks(HookPresets.silent()) // No console output
4787
+ * .withGadgets(Calculator)
4788
+ * .askAndCollect("What is 15 times 23?");
4789
+ *
4790
+ * expect(result).toContain("345");
4791
+ * });
4792
+ * });
4793
+ * ```
4794
+ *
4795
+ * @example
4796
+ * ```typescript
4797
+ * // Conditional silence based on environment
4798
+ * const isTesting = process.env.NODE_ENV === 'test';
4799
+ * .withHooks(isTesting ? HookPresets.silent() : HookPresets.monitoring())
4800
+ * ```
4801
+ *
4802
+ * @see {@link https://github.com/zbigniewsobiecki/llmist/blob/main/docs/HOOKS.md#hookpresetssilent | Full documentation}
4803
+ */
4804
+ static silent() {
4805
+ return {};
4806
+ }
4807
+ /**
4808
+ * Combines multiple hook configurations into one.
4809
+ *
4810
+ * Merge allows you to compose preset and custom hooks for modular monitoring
4811
+ * configurations. Understanding merge behavior is crucial for proper composition.
4812
+ *
4813
+ * **Merge behavior:**
4814
+ * - **Observers:** Composed - all handlers run sequentially in order
4815
+ * - **Interceptors:** Last one wins - only the last interceptor applies
4816
+ * - **Controllers:** Last one wins - only the last controller applies
4817
+ *
4818
+ * **Why interceptors/controllers don't compose:**
4819
+ * - Interceptors have different signatures per method, making composition impractical
4820
+ * - Controllers return specific actions that can't be meaningfully combined
4821
+ * - Only observers support composition because they're read-only and independent
4822
+ *
4823
+ * **Use cases:**
4824
+ * - Combining multiple presets (logging + timing + tokens)
4825
+ * - Adding custom hooks to presets
4826
+ * - Building modular, reusable monitoring configurations
4827
+ * - Environment-specific hook composition
4828
+ *
4829
+ * **Performance:** Minimal overhead for merging. Runtime performance depends on merged hooks.
4830
+ *
4831
+ * @param hookSets - Variable number of hook configurations to merge
4832
+ * @returns Single merged hook configuration with composed/overridden handlers
4833
+ *
4834
+ * @example
4835
+ * ```typescript
4836
+ * // Combine multiple presets
4837
+ * .withHooks(HookPresets.merge(
4838
+ * HookPresets.logging(),
4839
+ * HookPresets.timing(),
4840
+ * HookPresets.tokenTracking()
4841
+ * ))
4842
+ * // All observers from all three presets will run
4843
+ * ```
4844
+ *
4845
+ * @example
4846
+ * ```typescript
4847
+ * // Add custom observer to preset (both run)
4848
+ * .withHooks(HookPresets.merge(
4849
+ * HookPresets.timing(),
4850
+ * {
4851
+ * observers: {
4852
+ * onLLMCallComplete: async (ctx) => {
4853
+ * await saveMetrics({ tokens: ctx.usage?.totalTokens });
4854
+ * },
4855
+ * },
4856
+ * }
4857
+ * ))
4858
+ * ```
4859
+ *
4860
+ * @example
4861
+ * ```typescript
4862
+ * // Multiple interceptors (last wins!)
4863
+ * .withHooks(HookPresets.merge(
4864
+ * {
4865
+ * interceptors: {
4866
+ * interceptTextChunk: (chunk) => chunk.toUpperCase(), // Ignored
4867
+ * },
4868
+ * },
4869
+ * {
4870
+ * interceptors: {
4871
+ * interceptTextChunk: (chunk) => chunk.toLowerCase(), // This wins
4872
+ * },
4873
+ * }
4874
+ * ))
4875
+ * // Result: text will be lowercase
4876
+ * ```
4877
+ *
4878
+ * @example
4879
+ * ```typescript
4880
+ * // Modular environment-based configuration
4881
+ * const baseHooks = HookPresets.errorLogging();
4882
+ * const devHooks = HookPresets.merge(baseHooks, HookPresets.monitoring({ verbose: true }));
4883
+ * const prodHooks = HookPresets.merge(baseHooks, HookPresets.tokenTracking());
4884
+ *
4885
+ * const hooks = process.env.NODE_ENV === 'production' ? prodHooks : devHooks;
4886
+ * .withHooks(hooks)
4887
+ * ```
4888
+ *
4889
+ * @see {@link https://github.com/zbigniewsobiecki/llmist/blob/main/docs/HOOKS.md#hookpresetsmergehooksets | Full documentation}
4890
+ */
4891
+ static merge(...hookSets) {
4892
+ const merged = {
4893
+ observers: {},
4894
+ interceptors: {},
4895
+ controllers: {}
4896
+ };
4897
+ for (const hooks of hookSets) {
4898
+ if (hooks.observers) {
4899
+ for (const [key, handler] of Object.entries(hooks.observers)) {
4900
+ const typedKey = key;
4901
+ if (merged.observers[typedKey]) {
4902
+ const existing = merged.observers[typedKey];
4903
+ merged.observers[typedKey] = async (ctx) => {
4904
+ await existing(ctx);
4905
+ await handler(ctx);
4906
+ };
4907
+ } else {
4908
+ merged.observers[typedKey] = handler;
4909
+ }
4910
+ }
4911
+ }
4912
+ if (hooks.interceptors) {
4913
+ Object.assign(merged.interceptors, hooks.interceptors);
4914
+ }
4915
+ if (hooks.controllers) {
4916
+ Object.assign(merged.controllers, hooks.controllers);
4917
+ }
4918
+ }
4919
+ return merged;
4920
+ }
4921
+ /**
4922
+ * Composite preset combining logging, timing, tokenTracking, and errorLogging.
4923
+ *
4924
+ * This is the recommended preset for development and initial production deployments,
4925
+ * providing comprehensive observability with a single method call.
4926
+ *
4927
+ * **Includes:**
4928
+ * - All output from `logging()` preset (with optional verbosity)
4929
+ * - All output from `timing()` preset (execution times)
4930
+ * - All output from `tokenTracking()` preset (token usage)
4931
+ * - All output from `errorLogging()` preset (error details)
4932
+ *
4933
+ * **Output format:**
4934
+ * - Event logging: [LLM]/[GADGET] messages
4935
+ * - Timing: ⏱️ emoji with milliseconds
4936
+ * - Tokens: 📊 emoji with per-call and cumulative counts
4937
+ * - Errors: ❌ emoji with full error details
4938
+ *
4939
+ * **Use cases:**
4940
+ * - Full observability during development
4941
+ * - Comprehensive monitoring in production
4942
+ * - One-liner for complete agent visibility
4943
+ * - Troubleshooting and debugging with full context
4944
+ *
4945
+ * **Performance:** Combined overhead of all four presets, but still minimal in practice.
4946
+ *
4947
+ * @param options - Monitoring options
4948
+ * @param options.verbose - Passed to logging() preset for detailed output. Default: false
4949
+ * @returns Merged hook configuration combining all monitoring presets
4950
+ *
4951
+ * @example
4952
+ * ```typescript
4953
+ * // Basic monitoring (recommended for development)
4954
+ * await LLMist.createAgent()
4955
+ * .withHooks(HookPresets.monitoring())
4956
+ * .withGadgets(Calculator, Weather)
4957
+ * .ask("What is 15 times 23, and what's the weather in NYC?");
4958
+ * // Output: All events, timing, tokens, and errors in one place
4959
+ * ```
4960
+ *
4961
+ * @example
4962
+ * ```typescript
4963
+ * // Verbose monitoring with full details
4964
+ * await LLMist.createAgent()
4965
+ * .withHooks(HookPresets.monitoring({ verbose: true }))
4966
+ * .ask("Your prompt");
4967
+ * // Output includes: parameters, results, and complete responses
4968
+ * ```
4969
+ *
4970
+ * @example
4971
+ * ```typescript
4972
+ * // Environment-based monitoring
4973
+ * const isDev = process.env.NODE_ENV === 'development';
4974
+ * .withHooks(HookPresets.monitoring({ verbose: isDev }))
4975
+ * ```
4976
+ *
4977
+ * @see {@link https://github.com/zbigniewsobiecki/llmist/blob/main/docs/HOOKS.md#hookpresetsmonitoringoptions | Full documentation}
4978
+ */
4979
+ static monitoring(options = {}) {
4980
+ return _HookPresets.merge(
4981
+ _HookPresets.logging(options),
4982
+ _HookPresets.timing(),
4983
+ _HookPresets.tokenTracking(),
4984
+ _HookPresets.errorLogging()
4985
+ );
4986
+ }
4987
+ };
4988
+ }
4989
+ });
4990
+
4042
4991
  // src/providers/anthropic-models.ts
4043
4992
  var ANTHROPIC_MODELS;
4044
4993
  var init_anthropic_models = __esm({
@@ -9229,6 +10178,8 @@ var init_builder = __esm({
9229
10178
  init_agent();
9230
10179
  init_agent_internal_key();
9231
10180
  init_event_handlers();
10181
+ init_file_logging();
10182
+ init_hook_presets();
9232
10183
  AgentBuilder = class {
9233
10184
  client;
9234
10185
  model;
@@ -10022,9 +10973,16 @@ ${endPrefix}`
10022
10973
  * Note: Subagent event visibility is now handled entirely by the ExecutionTree.
10023
10974
  * When a subagent uses withParentContext(ctx), it shares the parent's tree,
10024
10975
  * and all events are automatically visible to tree subscribers (like the TUI).
10976
+ *
10977
+ * Environment-based file logging (via LLMIST_LOG_RAW_DIRECTORY) is automatically
10978
+ * injected if the env var is set. User-provided hooks take precedence.
10025
10979
  */
10026
10980
  composeHooks() {
10027
- const hooks = this.hooks;
10981
+ let hooks = this.hooks;
10982
+ const envFileLogging = getEnvFileLoggingHooks();
10983
+ if (envFileLogging) {
10984
+ hooks = hooks ? HookPresets.merge(envFileLogging, hooks) : envFileLogging;
10985
+ }
10028
10986
  if (!this.trailingMessage) {
10029
10987
  return hooks;
10030
10988
  }
@@ -13969,9 +14927,11 @@ __export(index_exports, {
13969
14927
  filterRootEvents: () => filterRootEvents,
13970
14928
  format: () => format,
13971
14929
  formatBytes: () => formatBytes,
14930
+ formatCallNumber: () => formatCallNumber,
13972
14931
  formatDate: () => formatDate,
13973
14932
  formatDuration: () => formatDuration,
13974
14933
  formatLLMError: () => formatLLMError,
14934
+ formatLlmRequest: () => formatLlmRequest,
13975
14935
  gadgetError: () => gadgetError,
13976
14936
  gadgetSuccess: () => gadgetSuccess,
13977
14937
  getErrorMessage: () => getErrorMessage,
@@ -14043,781 +15003,8 @@ var import_zod3 = require("zod");
14043
15003
  init_agent();
14044
15004
  init_builder();
14045
15005
  init_event_handlers();
14046
-
14047
- // src/agent/hook-presets.ts
14048
- var HookPresets = class _HookPresets {
14049
- /**
14050
- * Logs LLM calls and gadget execution to console with optional verbosity.
14051
- *
14052
- * **Output (basic mode):**
14053
- * - LLM call start/complete events with iteration numbers
14054
- * - Gadget execution start/complete with gadget names
14055
- * - Token counts when available
14056
- *
14057
- * **Output (verbose mode):**
14058
- * - All basic mode output
14059
- * - Full gadget parameters (formatted JSON)
14060
- * - Full gadget results
14061
- * - Complete LLM response text
14062
- *
14063
- * **Use cases:**
14064
- * - Basic development debugging and execution flow visibility
14065
- * - Understanding agent decision-making and tool usage
14066
- * - Troubleshooting gadget invocations
14067
- *
14068
- * **Performance:** Minimal overhead. Console writes are synchronous but fast.
14069
- *
14070
- * @param options - Logging options
14071
- * @param options.verbose - Include full parameters and results. Default: false
14072
- * @returns Hook configuration that can be passed to .withHooks()
14073
- *
14074
- * @example
14075
- * ```typescript
14076
- * // Basic logging
14077
- * await LLMist.createAgent()
14078
- * .withHooks(HookPresets.logging())
14079
- * .ask("Calculate 15 * 23");
14080
- * // Output: [LLM] Starting call (iteration 0)
14081
- * // [GADGET] Executing Calculator
14082
- * // [GADGET] Completed Calculator
14083
- * // [LLM] Completed (tokens: 245)
14084
- * ```
14085
- *
14086
- * @example
14087
- * ```typescript
14088
- * // Verbose logging with full details
14089
- * await LLMist.createAgent()
14090
- * .withHooks(HookPresets.logging({ verbose: true }))
14091
- * .ask("Calculate 15 * 23");
14092
- * // Output includes: parameters, results, and full responses
14093
- * ```
14094
- *
14095
- * @example
14096
- * ```typescript
14097
- * // Environment-based verbosity
14098
- * const isDev = process.env.NODE_ENV === 'development';
14099
- * .withHooks(HookPresets.logging({ verbose: isDev }))
14100
- * ```
14101
- *
14102
- * @see {@link https://github.com/zbigniewsobiecki/llmist/blob/main/docs/HOOKS.md#hookpresetsloggingoptions | Full documentation}
14103
- */
14104
- static logging(options = {}) {
14105
- return {
14106
- observers: {
14107
- onLLMCallStart: async (ctx) => {
14108
- console.log(`[LLM] Starting call (iteration ${ctx.iteration})`);
14109
- },
14110
- onLLMCallComplete: async (ctx) => {
14111
- const tokens = ctx.usage?.totalTokens ?? "unknown";
14112
- console.log(`[LLM] Completed (tokens: ${tokens})`);
14113
- if (options.verbose && ctx.finalMessage) {
14114
- console.log(`[LLM] Response: ${ctx.finalMessage}`);
14115
- }
14116
- },
14117
- onGadgetExecutionStart: async (ctx) => {
14118
- console.log(`[GADGET] Executing ${ctx.gadgetName}`);
14119
- if (options.verbose) {
14120
- console.log(`[GADGET] Parameters:`, JSON.stringify(ctx.parameters, null, 2));
14121
- }
14122
- },
14123
- onGadgetExecutionComplete: async (ctx) => {
14124
- console.log(`[GADGET] Completed ${ctx.gadgetName}`);
14125
- if (options.verbose) {
14126
- const display = ctx.error ?? ctx.finalResult ?? "(no result)";
14127
- console.log(`[GADGET] Result: ${display}`);
14128
- }
14129
- }
14130
- }
14131
- };
14132
- }
14133
- /**
14134
- * Measures and logs execution time for LLM calls and gadgets.
14135
- *
14136
- * **Output:**
14137
- * - Duration in milliseconds with ⏱️ emoji for each operation
14138
- * - Separate timing for each LLM iteration
14139
- * - Separate timing for each gadget execution
14140
- *
14141
- * **Use cases:**
14142
- * - Performance profiling and optimization
14143
- * - Identifying slow operations (LLM calls vs gadget execution)
14144
- * - Monitoring response times in production
14145
- * - Capacity planning and SLA tracking
14146
- *
14147
- * **Performance:** Negligible overhead. Uses Date.now() for timing measurements.
14148
- *
14149
- * @returns Hook configuration that can be passed to .withHooks()
14150
- *
14151
- * @example
14152
- * ```typescript
14153
- * // Basic timing
14154
- * await LLMist.createAgent()
14155
- * .withHooks(HookPresets.timing())
14156
- * .withGadgets(Weather, Database)
14157
- * .ask("What's the weather in NYC?");
14158
- * // Output: ⏱️ LLM call took 1234ms
14159
- * // ⏱️ Gadget Weather took 567ms
14160
- * // ⏱️ LLM call took 890ms
14161
- * ```
14162
- *
14163
- * @example
14164
- * ```typescript
14165
- * // Combined with logging for full context
14166
- * .withHooks(HookPresets.merge(
14167
- * HookPresets.logging(),
14168
- * HookPresets.timing()
14169
- * ))
14170
- * ```
14171
- *
14172
- * @example
14173
- * ```typescript
14174
- * // Correlate performance with cost
14175
- * .withHooks(HookPresets.merge(
14176
- * HookPresets.timing(),
14177
- * HookPresets.tokenTracking()
14178
- * ))
14179
- * ```
14180
- *
14181
- * @see {@link https://github.com/zbigniewsobiecki/llmist/blob/main/docs/HOOKS.md#hookpresetstiming | Full documentation}
14182
- */
14183
- static timing() {
14184
- const timings = /* @__PURE__ */ new Map();
14185
- return {
14186
- observers: {
14187
- onLLMCallStart: async (ctx) => {
14188
- timings.set(`llm-${ctx.iteration}`, Date.now());
14189
- },
14190
- onLLMCallComplete: async (ctx) => {
14191
- const start = timings.get(`llm-${ctx.iteration}`);
14192
- if (start) {
14193
- const duration = Date.now() - start;
14194
- console.log(`\u23F1\uFE0F LLM call took ${duration}ms`);
14195
- timings.delete(`llm-${ctx.iteration}`);
14196
- }
14197
- },
14198
- onGadgetExecutionStart: async (ctx) => {
14199
- const key = `gadget-${ctx.gadgetName}-${Date.now()}`;
14200
- timings.set(key, Date.now());
14201
- ctx._timingKey = key;
14202
- },
14203
- onGadgetExecutionComplete: async (ctx) => {
14204
- const key = ctx._timingKey;
14205
- if (key) {
14206
- const start = timings.get(key);
14207
- if (start) {
14208
- const duration = Date.now() - start;
14209
- console.log(`\u23F1\uFE0F Gadget ${ctx.gadgetName} took ${duration}ms`);
14210
- timings.delete(key);
14211
- }
14212
- }
14213
- }
14214
- }
14215
- };
14216
- }
14217
- /**
14218
- * Tracks cumulative token usage across all LLM calls.
14219
- *
14220
- * **Output:**
14221
- * - Per-call token count with 📊 emoji
14222
- * - Cumulative total across all calls
14223
- * - Call count for average calculations
14224
- *
14225
- * **Use cases:**
14226
- * - Cost monitoring and budget tracking
14227
- * - Optimizing prompts to reduce token usage
14228
- * - Comparing token efficiency across different approaches
14229
- * - Real-time cost estimation
14230
- *
14231
- * **Performance:** Minimal overhead. Simple counter increments.
14232
- *
14233
- * **Note:** Token counts depend on the provider's response. Some providers
14234
- * may not include usage data, in which case counts won't be logged.
14235
- *
14236
- * @returns Hook configuration that can be passed to .withHooks()
14237
- *
14238
- * @example
14239
- * ```typescript
14240
- * // Basic token tracking
14241
- * await LLMist.createAgent()
14242
- * .withHooks(HookPresets.tokenTracking())
14243
- * .ask("Summarize this document...");
14244
- * // Output: 📊 Tokens this call: 1,234
14245
- * // 📊 Total tokens: 1,234 (across 1 calls)
14246
- * // 📊 Tokens this call: 567
14247
- * // 📊 Total tokens: 1,801 (across 2 calls)
14248
- * ```
14249
- *
14250
- * @example
14251
- * ```typescript
14252
- * // Cost calculation with custom hook
14253
- * let totalTokens = 0;
14254
- * .withHooks(HookPresets.merge(
14255
- * HookPresets.tokenTracking(),
14256
- * {
14257
- * observers: {
14258
- * onLLMCallComplete: async (ctx) => {
14259
- * totalTokens += ctx.usage?.totalTokens ?? 0;
14260
- * const cost = (totalTokens / 1_000_000) * 3.0; // $3 per 1M tokens
14261
- * console.log(`💰 Estimated cost: $${cost.toFixed(4)}`);
14262
- * },
14263
- * },
14264
- * }
14265
- * ))
14266
- * ```
14267
- *
14268
- * @see {@link https://github.com/zbigniewsobiecki/llmist/blob/main/docs/HOOKS.md#hookpresetstokentracking | Full documentation}
14269
- */
14270
- static tokenTracking() {
14271
- let totalTokens = 0;
14272
- let totalCalls = 0;
14273
- return {
14274
- observers: {
14275
- onLLMCallComplete: async (ctx) => {
14276
- totalCalls++;
14277
- if (ctx.usage?.totalTokens) {
14278
- totalTokens += ctx.usage.totalTokens;
14279
- console.log(`\u{1F4CA} Tokens this call: ${ctx.usage.totalTokens}`);
14280
- console.log(`\u{1F4CA} Total tokens: ${totalTokens} (across ${totalCalls} calls)`);
14281
- }
14282
- }
14283
- }
14284
- };
14285
- }
14286
- /**
14287
- * Tracks comprehensive progress metrics including iterations, tokens, cost, and timing.
14288
- *
14289
- * **This preset showcases llmist's core capabilities by demonstrating:**
14290
- * - Observer pattern for non-intrusive monitoring
14291
- * - Integration with ModelRegistry for cost estimation
14292
- * - Callback-based architecture for flexible UI updates
14293
- * - Provider-agnostic token and cost tracking
14294
- *
14295
- * Unlike `tokenTracking()` which only logs to console, this preset provides
14296
- * structured data through callbacks, making it perfect for building custom UIs,
14297
- * dashboards, or progress indicators (like the llmist CLI).
14298
- *
14299
- * **Output (when logProgress: true):**
14300
- * - Iteration number and call count
14301
- * - Cumulative token usage (input + output)
14302
- * - Cumulative cost in USD (requires modelRegistry)
14303
- * - Elapsed time in seconds
14304
- *
14305
- * **Use cases:**
14306
- * - Building CLI progress indicators with live updates
14307
- * - Creating web dashboards with real-time metrics
14308
- * - Budget monitoring and cost alerts
14309
- * - Performance tracking and optimization
14310
- * - Custom logging to external systems (Datadog, CloudWatch, etc.)
14311
- *
14312
- * **Performance:** Minimal overhead. Uses Date.now() for timing and optional
14313
- * ModelRegistry.estimateCost() which is O(1) lookup. Callback invocation is
14314
- * synchronous and fast.
14315
- *
14316
- * @param options - Progress tracking options
14317
- * @param options.modelRegistry - ModelRegistry for cost estimation (optional)
14318
- * @param options.onProgress - Callback invoked after each LLM call (optional)
14319
- * @param options.logProgress - Log progress to console (default: false)
14320
- * @returns Hook configuration with progress tracking observers
14321
- *
14322
- * @example
14323
- * ```typescript
14324
- * // Basic usage with callback (RECOMMENDED - used by llmist CLI)
14325
- * import { LLMist, HookPresets } from 'llmist';
14326
- *
14327
- * const client = LLMist.create();
14328
- *
14329
- * await client.agent()
14330
- * .withHooks(HookPresets.progressTracking({
14331
- * modelRegistry: client.modelRegistry,
14332
- * onProgress: (stats) => {
14333
- * // Update your UI with stats
14334
- * console.log(`#${stats.currentIteration} | ${stats.totalTokens} tokens | $${stats.totalCost.toFixed(4)}`);
14335
- * }
14336
- * }))
14337
- * .withGadgets(Calculator)
14338
- * .ask("Calculate 15 * 23");
14339
- * // Output: #1 | 245 tokens | $0.0012
14340
- * ```
14341
- *
14342
- * @example
14343
- * ```typescript
14344
- * // Console logging mode (quick debugging)
14345
- * await client.agent()
14346
- * .withHooks(HookPresets.progressTracking({
14347
- * modelRegistry: client.modelRegistry,
14348
- * logProgress: true // Simple console output
14349
- * }))
14350
- * .ask("Your prompt");
14351
- * // Output: 📊 Progress: Iteration #1 | 245 tokens | $0.0012 | 1.2s
14352
- * ```
14353
- *
14354
- * @example
14355
- * ```typescript
14356
- * // Budget monitoring with alerts
14357
- * const BUDGET_USD = 0.10;
14358
- *
14359
- * await client.agent()
14360
- * .withHooks(HookPresets.progressTracking({
14361
- * modelRegistry: client.modelRegistry,
14362
- * onProgress: (stats) => {
14363
- * if (stats.totalCost > BUDGET_USD) {
14364
- * throw new Error(`Budget exceeded: $${stats.totalCost.toFixed(4)}`);
14365
- * }
14366
- * }
14367
- * }))
14368
- * .ask("Long running task...");
14369
- * ```
14370
- *
14371
- * @example
14372
- * ```typescript
14373
- * // Web dashboard integration
14374
- * let progressBar: HTMLElement;
14375
- *
14376
- * await client.agent()
14377
- * .withHooks(HookPresets.progressTracking({
14378
- * modelRegistry: client.modelRegistry,
14379
- * onProgress: (stats) => {
14380
- * // Update web UI in real-time
14381
- * progressBar.textContent = `Iteration ${stats.currentIteration}`;
14382
- * progressBar.dataset.cost = stats.totalCost.toFixed(4);
14383
- * progressBar.dataset.tokens = stats.totalTokens.toString();
14384
- * }
14385
- * }))
14386
- * .ask("Your prompt");
14387
- * ```
14388
- *
14389
- * @example
14390
- * ```typescript
14391
- * // External logging (Datadog, CloudWatch, etc.)
14392
- * await client.agent()
14393
- * .withHooks(HookPresets.progressTracking({
14394
- * modelRegistry: client.modelRegistry,
14395
- * onProgress: async (stats) => {
14396
- * await metrics.gauge('llm.iteration', stats.currentIteration);
14397
- * await metrics.gauge('llm.cost', stats.totalCost);
14398
- * await metrics.gauge('llm.tokens', stats.totalTokens);
14399
- * }
14400
- * }))
14401
- * .ask("Your prompt");
14402
- * ```
14403
- *
14404
- * @see {@link https://github.com/zbigniewsobiecki/llmist/blob/main/docs/HOOKS.md#hookpresetsprogresstrackingoptions | Full documentation}
14405
- * @see {@link ProgressTrackingOptions} for detailed options
14406
- * @see {@link ProgressStats} for the callback data structure
14407
- */
14408
- static progressTracking(options) {
14409
- const { modelRegistry, onProgress, logProgress = false } = options ?? {};
14410
- let totalCalls = 0;
14411
- let currentIteration = 0;
14412
- let totalInputTokens = 0;
14413
- let totalOutputTokens = 0;
14414
- let totalCost = 0;
14415
- let totalGadgetCost = 0;
14416
- const startTime = Date.now();
14417
- return {
14418
- observers: {
14419
- // Track iteration on each LLM call start
14420
- onLLMCallStart: async (ctx) => {
14421
- currentIteration++;
14422
- },
14423
- // Accumulate metrics and report progress on each LLM call completion
14424
- onLLMCallComplete: async (ctx) => {
14425
- totalCalls++;
14426
- if (ctx.usage) {
14427
- totalInputTokens += ctx.usage.inputTokens;
14428
- totalOutputTokens += ctx.usage.outputTokens;
14429
- if (modelRegistry) {
14430
- try {
14431
- const modelName = ctx.options.model.includes(":") ? ctx.options.model.split(":")[1] : ctx.options.model;
14432
- const costEstimate = modelRegistry.estimateCost(
14433
- modelName,
14434
- ctx.usage.inputTokens,
14435
- ctx.usage.outputTokens
14436
- );
14437
- if (costEstimate) {
14438
- totalCost += costEstimate.totalCost;
14439
- }
14440
- } catch (error) {
14441
- if (logProgress) {
14442
- console.warn(`\u26A0\uFE0F Cost estimation failed:`, error);
14443
- }
14444
- }
14445
- }
14446
- }
14447
- const stats = {
14448
- currentIteration,
14449
- totalCalls,
14450
- totalInputTokens,
14451
- totalOutputTokens,
14452
- totalTokens: totalInputTokens + totalOutputTokens,
14453
- totalCost: totalCost + totalGadgetCost,
14454
- elapsedSeconds: Number(((Date.now() - startTime) / 1e3).toFixed(1))
14455
- };
14456
- if (onProgress) {
14457
- onProgress(stats);
14458
- }
14459
- if (logProgress) {
14460
- const formattedTokens = stats.totalTokens >= 1e3 ? `${(stats.totalTokens / 1e3).toFixed(1)}k` : `${stats.totalTokens}`;
14461
- const formattedCost = stats.totalCost > 0 ? `$${stats.totalCost.toFixed(4)}` : "$0";
14462
- console.log(
14463
- `\u{1F4CA} Progress: Iteration #${stats.currentIteration} | ${formattedTokens} tokens | ${formattedCost} | ${stats.elapsedSeconds}s`
14464
- );
14465
- }
14466
- },
14467
- // Track gadget execution costs
14468
- onGadgetExecutionComplete: async (ctx) => {
14469
- if (ctx.cost && ctx.cost > 0) {
14470
- totalGadgetCost += ctx.cost;
14471
- }
14472
- }
14473
- }
14474
- };
14475
- }
14476
- /**
14477
- * Logs detailed error information for debugging and troubleshooting.
14478
- *
14479
- * **Output:**
14480
- * - LLM errors with ❌ emoji, including model and recovery status
14481
- * - Gadget errors with full context (parameters, error message)
14482
- * - Separate logging for LLM and gadget failures
14483
- *
14484
- * **Use cases:**
14485
- * - Troubleshooting production issues
14486
- * - Understanding error patterns and frequency
14487
- * - Debugging error recovery behavior
14488
- * - Collecting error metrics for monitoring
14489
- *
14490
- * **Performance:** Minimal overhead. Only logs when errors occur.
14491
- *
14492
- * @returns Hook configuration that can be passed to .withHooks()
14493
- *
14494
- * @example
14495
- * ```typescript
14496
- * // Basic error logging
14497
- * await LLMist.createAgent()
14498
- * .withHooks(HookPresets.errorLogging())
14499
- * .withGadgets(Database)
14500
- * .ask("Fetch user data");
14501
- * // Output (on LLM error): ❌ LLM Error (iteration 1): Rate limit exceeded
14502
- * // Model: gpt-5-nano
14503
- * // Recovered: true
14504
- * // Output (on gadget error): ❌ Gadget Error: Database
14505
- * // Error: Connection timeout
14506
- * // Parameters: {...}
14507
- * ```
14508
- *
14509
- * @example
14510
- * ```typescript
14511
- * // Combine with monitoring for full context
14512
- * .withHooks(HookPresets.merge(
14513
- * HookPresets.monitoring(), // Includes errorLogging
14514
- * customErrorAnalytics
14515
- * ))
14516
- * ```
14517
- *
14518
- * @example
14519
- * ```typescript
14520
- * // Error analytics collection
14521
- * const errors: any[] = [];
14522
- * .withHooks(HookPresets.merge(
14523
- * HookPresets.errorLogging(),
14524
- * {
14525
- * observers: {
14526
- * onLLMCallError: async (ctx) => {
14527
- * errors.push({ type: 'llm', error: ctx.error, recovered: ctx.recovered });
14528
- * },
14529
- * },
14530
- * }
14531
- * ))
14532
- * ```
14533
- *
14534
- * @see {@link https://github.com/zbigniewsobiecki/llmist/blob/main/docs/HOOKS.md#hookpresetserrorlogging | Full documentation}
14535
- */
14536
- static errorLogging() {
14537
- return {
14538
- observers: {
14539
- onLLMCallError: async (ctx) => {
14540
- console.error(`\u274C LLM Error (iteration ${ctx.iteration}):`, ctx.error.message);
14541
- console.error(` Model: ${ctx.options.model}`);
14542
- console.error(` Recovered: ${ctx.recovered}`);
14543
- },
14544
- onGadgetExecutionComplete: async (ctx) => {
14545
- if (ctx.error) {
14546
- console.error(`\u274C Gadget Error: ${ctx.gadgetName}`);
14547
- console.error(` Error: ${ctx.error}`);
14548
- console.error(` Parameters:`, JSON.stringify(ctx.parameters, null, 2));
14549
- }
14550
- }
14551
- }
14552
- };
14553
- }
14554
- /**
14555
- * Tracks context compaction events.
14556
- *
14557
- * **Output:**
14558
- * - Compaction events with 🗜️ emoji
14559
- * - Strategy name, tokens before/after, and savings
14560
- * - Cumulative statistics
14561
- *
14562
- * **Use cases:**
14563
- * - Monitoring long-running conversations
14564
- * - Understanding when and how compaction occurs
14565
- * - Debugging context management issues
14566
- *
14567
- * **Performance:** Minimal overhead. Simple console output.
14568
- *
14569
- * @returns Hook configuration that can be passed to .withHooks()
14570
- *
14571
- * @example
14572
- * ```typescript
14573
- * await LLMist.createAgent()
14574
- * .withHooks(HookPresets.compactionTracking())
14575
- * .ask("Your prompt");
14576
- * ```
14577
- */
14578
- static compactionTracking() {
14579
- return {
14580
- observers: {
14581
- onCompaction: async (ctx) => {
14582
- const saved = ctx.event.tokensBefore - ctx.event.tokensAfter;
14583
- const percent = (saved / ctx.event.tokensBefore * 100).toFixed(1);
14584
- console.log(
14585
- `\u{1F5DC}\uFE0F Compaction (${ctx.event.strategy}): ${ctx.event.tokensBefore} \u2192 ${ctx.event.tokensAfter} tokens (saved ${saved}, ${percent}%)`
14586
- );
14587
- console.log(` Messages: ${ctx.event.messagesBefore} \u2192 ${ctx.event.messagesAfter}`);
14588
- if (ctx.stats.totalCompactions > 1) {
14589
- console.log(
14590
- ` Cumulative: ${ctx.stats.totalCompactions} compactions, ${ctx.stats.totalTokensSaved} tokens saved`
14591
- );
14592
- }
14593
- }
14594
- }
14595
- };
14596
- }
14597
- /**
14598
- * Returns empty hook configuration for clean output without any logging.
14599
- *
14600
- * **Output:**
14601
- * - None. Returns {} (empty object).
14602
- *
14603
- * **Use cases:**
14604
- * - Clean test output without console noise
14605
- * - Production environments where logging is handled externally
14606
- * - Baseline for custom hook development
14607
- * - Temporary disable of all hook output
14608
- *
14609
- * **Performance:** Zero overhead. No-op hook configuration.
14610
- *
14611
- * @returns Empty hook configuration
14612
- *
14613
- * @example
14614
- * ```typescript
14615
- * // Clean test output
14616
- * describe('Agent tests', () => {
14617
- * it('should calculate correctly', async () => {
14618
- * const result = await LLMist.createAgent()
14619
- * .withHooks(HookPresets.silent()) // No console output
14620
- * .withGadgets(Calculator)
14621
- * .askAndCollect("What is 15 times 23?");
14622
- *
14623
- * expect(result).toContain("345");
14624
- * });
14625
- * });
14626
- * ```
14627
- *
14628
- * @example
14629
- * ```typescript
14630
- * // Conditional silence based on environment
14631
- * const isTesting = process.env.NODE_ENV === 'test';
14632
- * .withHooks(isTesting ? HookPresets.silent() : HookPresets.monitoring())
14633
- * ```
14634
- *
14635
- * @see {@link https://github.com/zbigniewsobiecki/llmist/blob/main/docs/HOOKS.md#hookpresetssilent | Full documentation}
14636
- */
14637
- static silent() {
14638
- return {};
14639
- }
14640
- /**
14641
- * Combines multiple hook configurations into one.
14642
- *
14643
- * Merge allows you to compose preset and custom hooks for modular monitoring
14644
- * configurations. Understanding merge behavior is crucial for proper composition.
14645
- *
14646
- * **Merge behavior:**
14647
- * - **Observers:** Composed - all handlers run sequentially in order
14648
- * - **Interceptors:** Last one wins - only the last interceptor applies
14649
- * - **Controllers:** Last one wins - only the last controller applies
14650
- *
14651
- * **Why interceptors/controllers don't compose:**
14652
- * - Interceptors have different signatures per method, making composition impractical
14653
- * - Controllers return specific actions that can't be meaningfully combined
14654
- * - Only observers support composition because they're read-only and independent
14655
- *
14656
- * **Use cases:**
14657
- * - Combining multiple presets (logging + timing + tokens)
14658
- * - Adding custom hooks to presets
14659
- * - Building modular, reusable monitoring configurations
14660
- * - Environment-specific hook composition
14661
- *
14662
- * **Performance:** Minimal overhead for merging. Runtime performance depends on merged hooks.
14663
- *
14664
- * @param hookSets - Variable number of hook configurations to merge
14665
- * @returns Single merged hook configuration with composed/overridden handlers
14666
- *
14667
- * @example
14668
- * ```typescript
14669
- * // Combine multiple presets
14670
- * .withHooks(HookPresets.merge(
14671
- * HookPresets.logging(),
14672
- * HookPresets.timing(),
14673
- * HookPresets.tokenTracking()
14674
- * ))
14675
- * // All observers from all three presets will run
14676
- * ```
14677
- *
14678
- * @example
14679
- * ```typescript
14680
- * // Add custom observer to preset (both run)
14681
- * .withHooks(HookPresets.merge(
14682
- * HookPresets.timing(),
14683
- * {
14684
- * observers: {
14685
- * onLLMCallComplete: async (ctx) => {
14686
- * await saveMetrics({ tokens: ctx.usage?.totalTokens });
14687
- * },
14688
- * },
14689
- * }
14690
- * ))
14691
- * ```
14692
- *
14693
- * @example
14694
- * ```typescript
14695
- * // Multiple interceptors (last wins!)
14696
- * .withHooks(HookPresets.merge(
14697
- * {
14698
- * interceptors: {
14699
- * interceptTextChunk: (chunk) => chunk.toUpperCase(), // Ignored
14700
- * },
14701
- * },
14702
- * {
14703
- * interceptors: {
14704
- * interceptTextChunk: (chunk) => chunk.toLowerCase(), // This wins
14705
- * },
14706
- * }
14707
- * ))
14708
- * // Result: text will be lowercase
14709
- * ```
14710
- *
14711
- * @example
14712
- * ```typescript
14713
- * // Modular environment-based configuration
14714
- * const baseHooks = HookPresets.errorLogging();
14715
- * const devHooks = HookPresets.merge(baseHooks, HookPresets.monitoring({ verbose: true }));
14716
- * const prodHooks = HookPresets.merge(baseHooks, HookPresets.tokenTracking());
14717
- *
14718
- * const hooks = process.env.NODE_ENV === 'production' ? prodHooks : devHooks;
14719
- * .withHooks(hooks)
14720
- * ```
14721
- *
14722
- * @see {@link https://github.com/zbigniewsobiecki/llmist/blob/main/docs/HOOKS.md#hookpresetsmergehooksets | Full documentation}
14723
- */
14724
- static merge(...hookSets) {
14725
- const merged = {
14726
- observers: {},
14727
- interceptors: {},
14728
- controllers: {}
14729
- };
14730
- for (const hooks of hookSets) {
14731
- if (hooks.observers) {
14732
- for (const [key, handler] of Object.entries(hooks.observers)) {
14733
- const typedKey = key;
14734
- if (merged.observers[typedKey]) {
14735
- const existing = merged.observers[typedKey];
14736
- merged.observers[typedKey] = async (ctx) => {
14737
- await existing(ctx);
14738
- await handler(ctx);
14739
- };
14740
- } else {
14741
- merged.observers[typedKey] = handler;
14742
- }
14743
- }
14744
- }
14745
- if (hooks.interceptors) {
14746
- Object.assign(merged.interceptors, hooks.interceptors);
14747
- }
14748
- if (hooks.controllers) {
14749
- Object.assign(merged.controllers, hooks.controllers);
14750
- }
14751
- }
14752
- return merged;
14753
- }
14754
- /**
14755
- * Composite preset combining logging, timing, tokenTracking, and errorLogging.
14756
- *
14757
- * This is the recommended preset for development and initial production deployments,
14758
- * providing comprehensive observability with a single method call.
14759
- *
14760
- * **Includes:**
14761
- * - All output from `logging()` preset (with optional verbosity)
14762
- * - All output from `timing()` preset (execution times)
14763
- * - All output from `tokenTracking()` preset (token usage)
14764
- * - All output from `errorLogging()` preset (error details)
14765
- *
14766
- * **Output format:**
14767
- * - Event logging: [LLM]/[GADGET] messages
14768
- * - Timing: ⏱️ emoji with milliseconds
14769
- * - Tokens: 📊 emoji with per-call and cumulative counts
14770
- * - Errors: ❌ emoji with full error details
14771
- *
14772
- * **Use cases:**
14773
- * - Full observability during development
14774
- * - Comprehensive monitoring in production
14775
- * - One-liner for complete agent visibility
14776
- * - Troubleshooting and debugging with full context
14777
- *
14778
- * **Performance:** Combined overhead of all four presets, but still minimal in practice.
14779
- *
14780
- * @param options - Monitoring options
14781
- * @param options.verbose - Passed to logging() preset for detailed output. Default: false
14782
- * @returns Merged hook configuration combining all monitoring presets
14783
- *
14784
- * @example
14785
- * ```typescript
14786
- * // Basic monitoring (recommended for development)
14787
- * await LLMist.createAgent()
14788
- * .withHooks(HookPresets.monitoring())
14789
- * .withGadgets(Calculator, Weather)
14790
- * .ask("What is 15 times 23, and what's the weather in NYC?");
14791
- * // Output: All events, timing, tokens, and errors in one place
14792
- * ```
14793
- *
14794
- * @example
14795
- * ```typescript
14796
- * // Verbose monitoring with full details
14797
- * await LLMist.createAgent()
14798
- * .withHooks(HookPresets.monitoring({ verbose: true }))
14799
- * .ask("Your prompt");
14800
- * // Output includes: parameters, results, and complete responses
14801
- * ```
14802
- *
14803
- * @example
14804
- * ```typescript
14805
- * // Environment-based monitoring
14806
- * const isDev = process.env.NODE_ENV === 'development';
14807
- * .withHooks(HookPresets.monitoring({ verbose: isDev }))
14808
- * ```
14809
- *
14810
- * @see {@link https://github.com/zbigniewsobiecki/llmist/blob/main/docs/HOOKS.md#hookpresetsmonitoringoptions | Full documentation}
14811
- */
14812
- static monitoring(options = {}) {
14813
- return _HookPresets.merge(
14814
- _HookPresets.logging(options),
14815
- _HookPresets.timing(),
14816
- _HookPresets.tokenTracking(),
14817
- _HookPresets.errorLogging()
14818
- );
14819
- }
14820
- };
15006
+ init_file_logging();
15007
+ init_hook_presets();
14821
15008
 
14822
15009
  // src/agent/compaction/index.ts
14823
15010
  init_config();
@@ -14830,6 +15017,7 @@ init_gadget_output_store();
14830
15017
 
14831
15018
  // src/agent/hints.ts
14832
15019
  init_prompt_config();
15020
+ init_hook_presets();
14833
15021
  function iterationProgressHint(options) {
14834
15022
  const { timing: timing2 = "always", showUrgency = true, template } = options ?? {};
14835
15023
  return {
@@ -15619,9 +15807,11 @@ function getHostExports2(ctx) {
15619
15807
  filterRootEvents,
15620
15808
  format,
15621
15809
  formatBytes,
15810
+ formatCallNumber,
15622
15811
  formatDate,
15623
15812
  formatDuration,
15624
15813
  formatLLMError,
15814
+ formatLlmRequest,
15625
15815
  gadgetError,
15626
15816
  gadgetSuccess,
15627
15817
  getErrorMessage,