llmist 15.11.0 → 15.13.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
@@ -229,7 +229,8 @@ var init_execution_tree = __esm({
229
229
  response: llmNode.response,
230
230
  usage: llmNode.usage,
231
231
  finishReason: llmNode.finishReason,
232
- cost: llmNode.cost
232
+ cost: llmNode.cost,
233
+ thinkingContent: params.thinkingContent
233
234
  });
234
235
  }
235
236
  /**
@@ -4039,6 +4040,958 @@ var init_registry = __esm({
4039
4040
  }
4040
4041
  });
4041
4042
 
4043
+ // src/agent/file-logging.ts
4044
+ function formatLlmRequest(messages) {
4045
+ const lines = [];
4046
+ for (const msg of messages) {
4047
+ lines.push(`=== ${msg.role.toUpperCase()} ===`);
4048
+ lines.push(msg.content ? extractMessageText(msg.content) : "");
4049
+ lines.push("");
4050
+ }
4051
+ return lines.join("\n");
4052
+ }
4053
+ function formatCallNumber(n, padding = 4) {
4054
+ return n.toString().padStart(padding, "0");
4055
+ }
4056
+ async function writeLogFile(dir, filename, content) {
4057
+ await (0, import_promises2.mkdir)(dir, { recursive: true });
4058
+ await (0, import_promises2.writeFile)((0, import_node_path3.join)(dir, filename), content, "utf-8");
4059
+ }
4060
+ function createFileLoggingHooks(options) {
4061
+ const {
4062
+ directory,
4063
+ startingCounter = 1,
4064
+ counterPadding = 4,
4065
+ skipSubagents = true,
4066
+ formatRequest = formatLlmRequest,
4067
+ onFileWritten
4068
+ } = options;
4069
+ let callCounter = startingCounter - 1;
4070
+ return {
4071
+ observers: {
4072
+ /**
4073
+ * Write request file when LLM call is ready (messages are finalized).
4074
+ */
4075
+ onLLMCallReady: async (context) => {
4076
+ if (skipSubagents && context.subagentContext) {
4077
+ return;
4078
+ }
4079
+ callCounter++;
4080
+ const filename = `${formatCallNumber(callCounter, counterPadding)}.request`;
4081
+ const content = formatRequest(context.options.messages);
4082
+ try {
4083
+ await writeLogFile(directory, filename, content);
4084
+ if (onFileWritten) {
4085
+ onFileWritten({
4086
+ filePath: (0, import_node_path3.join)(directory, filename),
4087
+ type: "request",
4088
+ callNumber: callCounter,
4089
+ contentLength: content.length
4090
+ });
4091
+ }
4092
+ } catch (error) {
4093
+ console.warn(`[file-logging] Failed to write ${filename}:`, error);
4094
+ }
4095
+ },
4096
+ /**
4097
+ * Write response file when LLM call completes.
4098
+ */
4099
+ onLLMCallComplete: async (context) => {
4100
+ if (skipSubagents && context.subagentContext) {
4101
+ return;
4102
+ }
4103
+ const filename = `${formatCallNumber(callCounter, counterPadding)}.response`;
4104
+ const content = context.rawResponse;
4105
+ try {
4106
+ await writeLogFile(directory, filename, content);
4107
+ if (onFileWritten) {
4108
+ onFileWritten({
4109
+ filePath: (0, import_node_path3.join)(directory, filename),
4110
+ type: "response",
4111
+ callNumber: callCounter,
4112
+ contentLength: content.length
4113
+ });
4114
+ }
4115
+ } catch (error) {
4116
+ console.warn(`[file-logging] Failed to write ${filename}:`, error);
4117
+ }
4118
+ }
4119
+ }
4120
+ };
4121
+ }
4122
+ function getEnvFileLoggingHooks() {
4123
+ const directory = process.env[ENV_LOG_RAW_DIRECTORY]?.trim();
4124
+ if (!directory) {
4125
+ return void 0;
4126
+ }
4127
+ return createFileLoggingHooks({ directory });
4128
+ }
4129
+ var import_promises2, import_node_path3, ENV_LOG_RAW_DIRECTORY;
4130
+ var init_file_logging = __esm({
4131
+ "src/agent/file-logging.ts"() {
4132
+ "use strict";
4133
+ import_promises2 = require("fs/promises");
4134
+ import_node_path3 = require("path");
4135
+ init_messages();
4136
+ ENV_LOG_RAW_DIRECTORY = "LLMIST_LOG_RAW_DIRECTORY";
4137
+ }
4138
+ });
4139
+
4140
+ // src/agent/hook-presets.ts
4141
+ var HookPresets;
4142
+ var init_hook_presets = __esm({
4143
+ "src/agent/hook-presets.ts"() {
4144
+ "use strict";
4145
+ init_file_logging();
4146
+ HookPresets = class _HookPresets {
4147
+ /**
4148
+ * Logs LLM calls and gadget execution to console with optional verbosity.
4149
+ *
4150
+ * **Output (basic mode):**
4151
+ * - LLM call start/complete events with iteration numbers
4152
+ * - Gadget execution start/complete with gadget names
4153
+ * - Token counts when available
4154
+ *
4155
+ * **Output (verbose mode):**
4156
+ * - All basic mode output
4157
+ * - Full gadget parameters (formatted JSON)
4158
+ * - Full gadget results
4159
+ * - Complete LLM response text
4160
+ *
4161
+ * **Use cases:**
4162
+ * - Basic development debugging and execution flow visibility
4163
+ * - Understanding agent decision-making and tool usage
4164
+ * - Troubleshooting gadget invocations
4165
+ *
4166
+ * **Performance:** Minimal overhead. Console writes are synchronous but fast.
4167
+ *
4168
+ * @param options - Logging options
4169
+ * @param options.verbose - Include full parameters and results. Default: false
4170
+ * @returns Hook configuration that can be passed to .withHooks()
4171
+ *
4172
+ * @example
4173
+ * ```typescript
4174
+ * // Basic logging
4175
+ * await LLMist.createAgent()
4176
+ * .withHooks(HookPresets.logging())
4177
+ * .ask("Calculate 15 * 23");
4178
+ * // Output: [LLM] Starting call (iteration 0)
4179
+ * // [GADGET] Executing Calculator
4180
+ * // [GADGET] Completed Calculator
4181
+ * // [LLM] Completed (tokens: 245)
4182
+ * ```
4183
+ *
4184
+ * @example
4185
+ * ```typescript
4186
+ * // Verbose logging with full details
4187
+ * await LLMist.createAgent()
4188
+ * .withHooks(HookPresets.logging({ verbose: true }))
4189
+ * .ask("Calculate 15 * 23");
4190
+ * // Output includes: parameters, results, and full responses
4191
+ * ```
4192
+ *
4193
+ * @example
4194
+ * ```typescript
4195
+ * // Environment-based verbosity
4196
+ * const isDev = process.env.NODE_ENV === 'development';
4197
+ * .withHooks(HookPresets.logging({ verbose: isDev }))
4198
+ * ```
4199
+ *
4200
+ * @see {@link https://github.com/zbigniewsobiecki/llmist/blob/main/docs/HOOKS.md#hookpresetsloggingoptions | Full documentation}
4201
+ */
4202
+ static logging(options = {}) {
4203
+ return {
4204
+ observers: {
4205
+ onLLMCallStart: async (ctx) => {
4206
+ console.log(`[LLM] Starting call (iteration ${ctx.iteration})`);
4207
+ },
4208
+ onLLMCallComplete: async (ctx) => {
4209
+ const tokens = ctx.usage?.totalTokens ?? "unknown";
4210
+ console.log(`[LLM] Completed (tokens: ${tokens})`);
4211
+ if (options.verbose && ctx.finalMessage) {
4212
+ console.log(`[LLM] Response: ${ctx.finalMessage}`);
4213
+ }
4214
+ },
4215
+ onGadgetExecutionStart: async (ctx) => {
4216
+ console.log(`[GADGET] Executing ${ctx.gadgetName}`);
4217
+ if (options.verbose) {
4218
+ console.log(`[GADGET] Parameters:`, JSON.stringify(ctx.parameters, null, 2));
4219
+ }
4220
+ },
4221
+ onGadgetExecutionComplete: async (ctx) => {
4222
+ console.log(`[GADGET] Completed ${ctx.gadgetName}`);
4223
+ if (options.verbose) {
4224
+ const display = ctx.error ?? ctx.finalResult ?? "(no result)";
4225
+ console.log(`[GADGET] Result: ${display}`);
4226
+ }
4227
+ }
4228
+ }
4229
+ };
4230
+ }
4231
+ /**
4232
+ * Measures and logs execution time for LLM calls and gadgets.
4233
+ *
4234
+ * **Output:**
4235
+ * - Duration in milliseconds with ⏱️ emoji for each operation
4236
+ * - Separate timing for each LLM iteration
4237
+ * - Separate timing for each gadget execution
4238
+ *
4239
+ * **Use cases:**
4240
+ * - Performance profiling and optimization
4241
+ * - Identifying slow operations (LLM calls vs gadget execution)
4242
+ * - Monitoring response times in production
4243
+ * - Capacity planning and SLA tracking
4244
+ *
4245
+ * **Performance:** Negligible overhead. Uses Date.now() for timing measurements.
4246
+ *
4247
+ * @returns Hook configuration that can be passed to .withHooks()
4248
+ *
4249
+ * @example
4250
+ * ```typescript
4251
+ * // Basic timing
4252
+ * await LLMist.createAgent()
4253
+ * .withHooks(HookPresets.timing())
4254
+ * .withGadgets(Weather, Database)
4255
+ * .ask("What's the weather in NYC?");
4256
+ * // Output: ⏱️ LLM call took 1234ms
4257
+ * // ⏱️ Gadget Weather took 567ms
4258
+ * // ⏱️ LLM call took 890ms
4259
+ * ```
4260
+ *
4261
+ * @example
4262
+ * ```typescript
4263
+ * // Combined with logging for full context
4264
+ * .withHooks(HookPresets.merge(
4265
+ * HookPresets.logging(),
4266
+ * HookPresets.timing()
4267
+ * ))
4268
+ * ```
4269
+ *
4270
+ * @example
4271
+ * ```typescript
4272
+ * // Correlate performance with cost
4273
+ * .withHooks(HookPresets.merge(
4274
+ * HookPresets.timing(),
4275
+ * HookPresets.tokenTracking()
4276
+ * ))
4277
+ * ```
4278
+ *
4279
+ * @see {@link https://github.com/zbigniewsobiecki/llmist/blob/main/docs/HOOKS.md#hookpresetstiming | Full documentation}
4280
+ */
4281
+ static timing() {
4282
+ const timings = /* @__PURE__ */ new Map();
4283
+ return {
4284
+ observers: {
4285
+ onLLMCallStart: async (ctx) => {
4286
+ timings.set(`llm-${ctx.iteration}`, Date.now());
4287
+ },
4288
+ onLLMCallComplete: async (ctx) => {
4289
+ const start = timings.get(`llm-${ctx.iteration}`);
4290
+ if (start) {
4291
+ const duration = Date.now() - start;
4292
+ console.log(`\u23F1\uFE0F LLM call took ${duration}ms`);
4293
+ timings.delete(`llm-${ctx.iteration}`);
4294
+ }
4295
+ },
4296
+ onGadgetExecutionStart: async (ctx) => {
4297
+ const key = `gadget-${ctx.gadgetName}-${Date.now()}`;
4298
+ timings.set(key, Date.now());
4299
+ ctx._timingKey = key;
4300
+ },
4301
+ onGadgetExecutionComplete: async (ctx) => {
4302
+ const key = ctx._timingKey;
4303
+ if (key) {
4304
+ const start = timings.get(key);
4305
+ if (start) {
4306
+ const duration = Date.now() - start;
4307
+ console.log(`\u23F1\uFE0F Gadget ${ctx.gadgetName} took ${duration}ms`);
4308
+ timings.delete(key);
4309
+ }
4310
+ }
4311
+ }
4312
+ }
4313
+ };
4314
+ }
4315
+ /**
4316
+ * Tracks cumulative token usage across all LLM calls.
4317
+ *
4318
+ * **Output:**
4319
+ * - Per-call token count with 📊 emoji
4320
+ * - Cumulative total across all calls
4321
+ * - Call count for average calculations
4322
+ *
4323
+ * **Use cases:**
4324
+ * - Cost monitoring and budget tracking
4325
+ * - Optimizing prompts to reduce token usage
4326
+ * - Comparing token efficiency across different approaches
4327
+ * - Real-time cost estimation
4328
+ *
4329
+ * **Performance:** Minimal overhead. Simple counter increments.
4330
+ *
4331
+ * **Note:** Token counts depend on the provider's response. Some providers
4332
+ * may not include usage data, in which case counts won't be logged.
4333
+ *
4334
+ * @returns Hook configuration that can be passed to .withHooks()
4335
+ *
4336
+ * @example
4337
+ * ```typescript
4338
+ * // Basic token tracking
4339
+ * await LLMist.createAgent()
4340
+ * .withHooks(HookPresets.tokenTracking())
4341
+ * .ask("Summarize this document...");
4342
+ * // Output: 📊 Tokens this call: 1,234
4343
+ * // 📊 Total tokens: 1,234 (across 1 calls)
4344
+ * // 📊 Tokens this call: 567
4345
+ * // 📊 Total tokens: 1,801 (across 2 calls)
4346
+ * ```
4347
+ *
4348
+ * @example
4349
+ * ```typescript
4350
+ * // Cost calculation with custom hook
4351
+ * let totalTokens = 0;
4352
+ * .withHooks(HookPresets.merge(
4353
+ * HookPresets.tokenTracking(),
4354
+ * {
4355
+ * observers: {
4356
+ * onLLMCallComplete: async (ctx) => {
4357
+ * totalTokens += ctx.usage?.totalTokens ?? 0;
4358
+ * const cost = (totalTokens / 1_000_000) * 3.0; // $3 per 1M tokens
4359
+ * console.log(`💰 Estimated cost: $${cost.toFixed(4)}`);
4360
+ * },
4361
+ * },
4362
+ * }
4363
+ * ))
4364
+ * ```
4365
+ *
4366
+ * @see {@link https://github.com/zbigniewsobiecki/llmist/blob/main/docs/HOOKS.md#hookpresetstokentracking | Full documentation}
4367
+ */
4368
+ static tokenTracking() {
4369
+ let totalTokens = 0;
4370
+ let totalCalls = 0;
4371
+ return {
4372
+ observers: {
4373
+ onLLMCallComplete: async (ctx) => {
4374
+ totalCalls++;
4375
+ if (ctx.usage?.totalTokens) {
4376
+ totalTokens += ctx.usage.totalTokens;
4377
+ console.log(`\u{1F4CA} Tokens this call: ${ctx.usage.totalTokens}`);
4378
+ console.log(`\u{1F4CA} Total tokens: ${totalTokens} (across ${totalCalls} calls)`);
4379
+ }
4380
+ }
4381
+ }
4382
+ };
4383
+ }
4384
+ /**
4385
+ * Tracks comprehensive progress metrics including iterations, tokens, cost, and timing.
4386
+ *
4387
+ * **This preset showcases llmist's core capabilities by demonstrating:**
4388
+ * - Observer pattern for non-intrusive monitoring
4389
+ * - Integration with ModelRegistry for cost estimation
4390
+ * - Callback-based architecture for flexible UI updates
4391
+ * - Provider-agnostic token and cost tracking
4392
+ *
4393
+ * Unlike `tokenTracking()` which only logs to console, this preset provides
4394
+ * structured data through callbacks, making it perfect for building custom UIs,
4395
+ * dashboards, or progress indicators (like the llmist CLI).
4396
+ *
4397
+ * **Output (when logProgress: true):**
4398
+ * - Iteration number and call count
4399
+ * - Cumulative token usage (input + output)
4400
+ * - Cumulative cost in USD (requires modelRegistry)
4401
+ * - Elapsed time in seconds
4402
+ *
4403
+ * **Use cases:**
4404
+ * - Building CLI progress indicators with live updates
4405
+ * - Creating web dashboards with real-time metrics
4406
+ * - Budget monitoring and cost alerts
4407
+ * - Performance tracking and optimization
4408
+ * - Custom logging to external systems (Datadog, CloudWatch, etc.)
4409
+ *
4410
+ * **Performance:** Minimal overhead. Uses Date.now() for timing and optional
4411
+ * ModelRegistry.estimateCost() which is O(1) lookup. Callback invocation is
4412
+ * synchronous and fast.
4413
+ *
4414
+ * @param options - Progress tracking options
4415
+ * @param options.modelRegistry - ModelRegistry for cost estimation (optional)
4416
+ * @param options.onProgress - Callback invoked after each LLM call (optional)
4417
+ * @param options.logProgress - Log progress to console (default: false)
4418
+ * @returns Hook configuration with progress tracking observers
4419
+ *
4420
+ * @example
4421
+ * ```typescript
4422
+ * // Basic usage with callback (RECOMMENDED - used by llmist CLI)
4423
+ * import { LLMist, HookPresets } from 'llmist';
4424
+ *
4425
+ * const client = LLMist.create();
4426
+ *
4427
+ * await client.agent()
4428
+ * .withHooks(HookPresets.progressTracking({
4429
+ * modelRegistry: client.modelRegistry,
4430
+ * onProgress: (stats) => {
4431
+ * // Update your UI with stats
4432
+ * console.log(`#${stats.currentIteration} | ${stats.totalTokens} tokens | $${stats.totalCost.toFixed(4)}`);
4433
+ * }
4434
+ * }))
4435
+ * .withGadgets(Calculator)
4436
+ * .ask("Calculate 15 * 23");
4437
+ * // Output: #1 | 245 tokens | $0.0012
4438
+ * ```
4439
+ *
4440
+ * @example
4441
+ * ```typescript
4442
+ * // Console logging mode (quick debugging)
4443
+ * await client.agent()
4444
+ * .withHooks(HookPresets.progressTracking({
4445
+ * modelRegistry: client.modelRegistry,
4446
+ * logProgress: true // Simple console output
4447
+ * }))
4448
+ * .ask("Your prompt");
4449
+ * // Output: 📊 Progress: Iteration #1 | 245 tokens | $0.0012 | 1.2s
4450
+ * ```
4451
+ *
4452
+ * @example
4453
+ * ```typescript
4454
+ * // Budget monitoring with alerts
4455
+ * const BUDGET_USD = 0.10;
4456
+ *
4457
+ * await client.agent()
4458
+ * .withHooks(HookPresets.progressTracking({
4459
+ * modelRegistry: client.modelRegistry,
4460
+ * onProgress: (stats) => {
4461
+ * if (stats.totalCost > BUDGET_USD) {
4462
+ * throw new Error(`Budget exceeded: $${stats.totalCost.toFixed(4)}`);
4463
+ * }
4464
+ * }
4465
+ * }))
4466
+ * .ask("Long running task...");
4467
+ * ```
4468
+ *
4469
+ * @example
4470
+ * ```typescript
4471
+ * // Web dashboard integration
4472
+ * let progressBar: HTMLElement;
4473
+ *
4474
+ * await client.agent()
4475
+ * .withHooks(HookPresets.progressTracking({
4476
+ * modelRegistry: client.modelRegistry,
4477
+ * onProgress: (stats) => {
4478
+ * // Update web UI in real-time
4479
+ * progressBar.textContent = `Iteration ${stats.currentIteration}`;
4480
+ * progressBar.dataset.cost = stats.totalCost.toFixed(4);
4481
+ * progressBar.dataset.tokens = stats.totalTokens.toString();
4482
+ * }
4483
+ * }))
4484
+ * .ask("Your prompt");
4485
+ * ```
4486
+ *
4487
+ * @example
4488
+ * ```typescript
4489
+ * // External logging (Datadog, CloudWatch, etc.)
4490
+ * await client.agent()
4491
+ * .withHooks(HookPresets.progressTracking({
4492
+ * modelRegistry: client.modelRegistry,
4493
+ * onProgress: async (stats) => {
4494
+ * await metrics.gauge('llm.iteration', stats.currentIteration);
4495
+ * await metrics.gauge('llm.cost', stats.totalCost);
4496
+ * await metrics.gauge('llm.tokens', stats.totalTokens);
4497
+ * }
4498
+ * }))
4499
+ * .ask("Your prompt");
4500
+ * ```
4501
+ *
4502
+ * @see {@link https://github.com/zbigniewsobiecki/llmist/blob/main/docs/HOOKS.md#hookpresetsprogresstrackingoptions | Full documentation}
4503
+ * @see {@link ProgressTrackingOptions} for detailed options
4504
+ * @see {@link ProgressStats} for the callback data structure
4505
+ */
4506
+ static progressTracking(options) {
4507
+ const { modelRegistry, onProgress, logProgress = false } = options ?? {};
4508
+ let totalCalls = 0;
4509
+ let currentIteration = 0;
4510
+ let totalInputTokens = 0;
4511
+ let totalOutputTokens = 0;
4512
+ let totalCost = 0;
4513
+ let totalGadgetCost = 0;
4514
+ const startTime = Date.now();
4515
+ return {
4516
+ observers: {
4517
+ // Track iteration on each LLM call start
4518
+ onLLMCallStart: async (ctx) => {
4519
+ currentIteration++;
4520
+ },
4521
+ // Accumulate metrics and report progress on each LLM call completion
4522
+ onLLMCallComplete: async (ctx) => {
4523
+ totalCalls++;
4524
+ if (ctx.usage) {
4525
+ totalInputTokens += ctx.usage.inputTokens;
4526
+ totalOutputTokens += ctx.usage.outputTokens;
4527
+ if (modelRegistry) {
4528
+ try {
4529
+ const modelName = ctx.options.model.includes(":") ? ctx.options.model.split(":")[1] : ctx.options.model;
4530
+ const costEstimate = modelRegistry.estimateCost(
4531
+ modelName,
4532
+ ctx.usage.inputTokens,
4533
+ ctx.usage.outputTokens,
4534
+ ctx.usage.cachedInputTokens ?? 0,
4535
+ ctx.usage.cacheCreationInputTokens ?? 0,
4536
+ ctx.usage.reasoningTokens ?? 0
4537
+ );
4538
+ if (costEstimate) {
4539
+ totalCost += costEstimate.totalCost;
4540
+ }
4541
+ } catch (error) {
4542
+ if (logProgress) {
4543
+ console.warn(`\u26A0\uFE0F Cost estimation failed:`, error);
4544
+ }
4545
+ }
4546
+ }
4547
+ }
4548
+ const stats = {
4549
+ currentIteration,
4550
+ totalCalls,
4551
+ totalInputTokens,
4552
+ totalOutputTokens,
4553
+ totalTokens: totalInputTokens + totalOutputTokens,
4554
+ totalCost: totalCost + totalGadgetCost,
4555
+ elapsedSeconds: Number(((Date.now() - startTime) / 1e3).toFixed(1))
4556
+ };
4557
+ if (onProgress) {
4558
+ onProgress(stats);
4559
+ }
4560
+ if (logProgress) {
4561
+ const formattedTokens = stats.totalTokens >= 1e3 ? `${(stats.totalTokens / 1e3).toFixed(1)}k` : `${stats.totalTokens}`;
4562
+ const formattedCost = stats.totalCost > 0 ? `$${stats.totalCost.toFixed(4)}` : "$0";
4563
+ console.log(
4564
+ `\u{1F4CA} Progress: Iteration #${stats.currentIteration} | ${formattedTokens} tokens | ${formattedCost} | ${stats.elapsedSeconds}s`
4565
+ );
4566
+ }
4567
+ },
4568
+ // Track gadget execution costs
4569
+ onGadgetExecutionComplete: async (ctx) => {
4570
+ if (ctx.cost && ctx.cost > 0) {
4571
+ totalGadgetCost += ctx.cost;
4572
+ }
4573
+ }
4574
+ }
4575
+ };
4576
+ }
4577
+ /**
4578
+ * Logs detailed error information for debugging and troubleshooting.
4579
+ *
4580
+ * **Output:**
4581
+ * - LLM errors with ❌ emoji, including model and recovery status
4582
+ * - Gadget errors with full context (parameters, error message)
4583
+ * - Separate logging for LLM and gadget failures
4584
+ *
4585
+ * **Use cases:**
4586
+ * - Troubleshooting production issues
4587
+ * - Understanding error patterns and frequency
4588
+ * - Debugging error recovery behavior
4589
+ * - Collecting error metrics for monitoring
4590
+ *
4591
+ * **Performance:** Minimal overhead. Only logs when errors occur.
4592
+ *
4593
+ * @returns Hook configuration that can be passed to .withHooks()
4594
+ *
4595
+ * @example
4596
+ * ```typescript
4597
+ * // Basic error logging
4598
+ * await LLMist.createAgent()
4599
+ * .withHooks(HookPresets.errorLogging())
4600
+ * .withGadgets(Database)
4601
+ * .ask("Fetch user data");
4602
+ * // Output (on LLM error): ❌ LLM Error (iteration 1): Rate limit exceeded
4603
+ * // Model: gpt-5-nano
4604
+ * // Recovered: true
4605
+ * // Output (on gadget error): ❌ Gadget Error: Database
4606
+ * // Error: Connection timeout
4607
+ * // Parameters: {...}
4608
+ * ```
4609
+ *
4610
+ * @example
4611
+ * ```typescript
4612
+ * // Combine with monitoring for full context
4613
+ * .withHooks(HookPresets.merge(
4614
+ * HookPresets.monitoring(), // Includes errorLogging
4615
+ * customErrorAnalytics
4616
+ * ))
4617
+ * ```
4618
+ *
4619
+ * @example
4620
+ * ```typescript
4621
+ * // Error analytics collection
4622
+ * const errors: any[] = [];
4623
+ * .withHooks(HookPresets.merge(
4624
+ * HookPresets.errorLogging(),
4625
+ * {
4626
+ * observers: {
4627
+ * onLLMCallError: async (ctx) => {
4628
+ * errors.push({ type: 'llm', error: ctx.error, recovered: ctx.recovered });
4629
+ * },
4630
+ * },
4631
+ * }
4632
+ * ))
4633
+ * ```
4634
+ *
4635
+ * @see {@link https://github.com/zbigniewsobiecki/llmist/blob/main/docs/HOOKS.md#hookpresetserrorlogging | Full documentation}
4636
+ */
4637
+ static errorLogging() {
4638
+ return {
4639
+ observers: {
4640
+ onLLMCallError: async (ctx) => {
4641
+ console.error(`\u274C LLM Error (iteration ${ctx.iteration}):`, ctx.error.message);
4642
+ console.error(` Model: ${ctx.options.model}`);
4643
+ console.error(` Recovered: ${ctx.recovered}`);
4644
+ },
4645
+ onGadgetExecutionComplete: async (ctx) => {
4646
+ if (ctx.error) {
4647
+ console.error(`\u274C Gadget Error: ${ctx.gadgetName}`);
4648
+ console.error(` Error: ${ctx.error}`);
4649
+ console.error(` Parameters:`, JSON.stringify(ctx.parameters, null, 2));
4650
+ }
4651
+ }
4652
+ }
4653
+ };
4654
+ }
4655
+ /**
4656
+ * Tracks context compaction events.
4657
+ *
4658
+ * **Output:**
4659
+ * - Compaction events with 🗜️ emoji
4660
+ * - Strategy name, tokens before/after, and savings
4661
+ * - Cumulative statistics
4662
+ *
4663
+ * **Use cases:**
4664
+ * - Monitoring long-running conversations
4665
+ * - Understanding when and how compaction occurs
4666
+ * - Debugging context management issues
4667
+ *
4668
+ * **Performance:** Minimal overhead. Simple console output.
4669
+ *
4670
+ * @returns Hook configuration that can be passed to .withHooks()
4671
+ *
4672
+ * @example
4673
+ * ```typescript
4674
+ * await LLMist.createAgent()
4675
+ * .withHooks(HookPresets.compactionTracking())
4676
+ * .ask("Your prompt");
4677
+ * ```
4678
+ */
4679
+ static compactionTracking() {
4680
+ return {
4681
+ observers: {
4682
+ onCompaction: async (ctx) => {
4683
+ const saved = ctx.event.tokensBefore - ctx.event.tokensAfter;
4684
+ const percent = (saved / ctx.event.tokensBefore * 100).toFixed(1);
4685
+ console.log(
4686
+ `\u{1F5DC}\uFE0F Compaction (${ctx.event.strategy}): ${ctx.event.tokensBefore} \u2192 ${ctx.event.tokensAfter} tokens (saved ${saved}, ${percent}%)`
4687
+ );
4688
+ console.log(` Messages: ${ctx.event.messagesBefore} \u2192 ${ctx.event.messagesAfter}`);
4689
+ if (ctx.stats.totalCompactions > 1) {
4690
+ console.log(
4691
+ ` Cumulative: ${ctx.stats.totalCompactions} compactions, ${ctx.stats.totalTokensSaved} tokens saved`
4692
+ );
4693
+ }
4694
+ }
4695
+ }
4696
+ };
4697
+ }
4698
+ /**
4699
+ * Logs LLM requests and responses to files for debugging and audit trails.
4700
+ *
4701
+ * Files are named `{counter}.request` and `{counter}.response` where counter
4702
+ * is a zero-padded number that increments with each LLM call.
4703
+ *
4704
+ * **Output:**
4705
+ * - Request files containing formatted LLM message history
4706
+ * - Response files containing raw LLM output
4707
+ *
4708
+ * **Use cases:**
4709
+ * - Debugging complex agent interactions
4710
+ * - Creating audit trails for compliance
4711
+ * - Analyzing LLM behavior patterns
4712
+ * - Replaying conversations for testing
4713
+ *
4714
+ * **Performance:** Minimal overhead - only file I/O, no synchronous blocking.
4715
+ *
4716
+ * **Note:** Can also be enabled via `LLMIST_LOG_RAW_DIRECTORY` environment
4717
+ * variable for zero-code activation.
4718
+ *
4719
+ * @param options - File logging options
4720
+ * @param options.directory - Directory where log files will be written
4721
+ * @param options.startingCounter - Starting counter (default: 1)
4722
+ * @param options.counterPadding - Number of digits for padding (default: 4)
4723
+ * @param options.skipSubagents - Skip subagent calls (default: true)
4724
+ * @param options.formatRequest - Custom request formatter
4725
+ * @param options.onFileWritten - Callback after each file is written
4726
+ * @returns Hook configuration that can be passed to .withHooks()
4727
+ *
4728
+ * @example
4729
+ * ```typescript
4730
+ * // Basic file logging
4731
+ * await LLMist.createAgent()
4732
+ * .withHooks(HookPresets.fileLogging({
4733
+ * directory: './debug-logs'
4734
+ * }))
4735
+ * .ask("Hello");
4736
+ * // Creates: ./debug-logs/0001.request
4737
+ * // ./debug-logs/0001.response
4738
+ * ```
4739
+ *
4740
+ * @example
4741
+ * ```typescript
4742
+ * // With callback for tracking
4743
+ * await LLMist.createAgent()
4744
+ * .withHooks(HookPresets.fileLogging({
4745
+ * directory: './logs',
4746
+ * onFileWritten: (info) => {
4747
+ * console.log(`Wrote ${info.type}: ${info.filePath}`);
4748
+ * }
4749
+ * }))
4750
+ * .ask("Hello");
4751
+ * ```
4752
+ *
4753
+ * @example
4754
+ * ```typescript
4755
+ * // Combined with other presets
4756
+ * .withHooks(HookPresets.merge(
4757
+ * HookPresets.fileLogging({ directory: logDir }),
4758
+ * HookPresets.progressTracking({ onProgress: updateUI }),
4759
+ * HookPresets.errorLogging()
4760
+ * ))
4761
+ * ```
4762
+ *
4763
+ * @see {@link https://github.com/zbigniewsobiecki/llmist/blob/main/docs/HOOKS.md#hookpresetsfileloggingoptions | Full documentation}
4764
+ */
4765
+ static fileLogging(options) {
4766
+ return createFileLoggingHooks(options);
4767
+ }
4768
+ /**
4769
+ * Returns empty hook configuration for clean output without any logging.
4770
+ *
4771
+ * **Output:**
4772
+ * - None. Returns {} (empty object).
4773
+ *
4774
+ * **Use cases:**
4775
+ * - Clean test output without console noise
4776
+ * - Production environments where logging is handled externally
4777
+ * - Baseline for custom hook development
4778
+ * - Temporary disable of all hook output
4779
+ *
4780
+ * **Performance:** Zero overhead. No-op hook configuration.
4781
+ *
4782
+ * @returns Empty hook configuration
4783
+ *
4784
+ * @example
4785
+ * ```typescript
4786
+ * // Clean test output
4787
+ * describe('Agent tests', () => {
4788
+ * it('should calculate correctly', async () => {
4789
+ * const result = await LLMist.createAgent()
4790
+ * .withHooks(HookPresets.silent()) // No console output
4791
+ * .withGadgets(Calculator)
4792
+ * .askAndCollect("What is 15 times 23?");
4793
+ *
4794
+ * expect(result).toContain("345");
4795
+ * });
4796
+ * });
4797
+ * ```
4798
+ *
4799
+ * @example
4800
+ * ```typescript
4801
+ * // Conditional silence based on environment
4802
+ * const isTesting = process.env.NODE_ENV === 'test';
4803
+ * .withHooks(isTesting ? HookPresets.silent() : HookPresets.monitoring())
4804
+ * ```
4805
+ *
4806
+ * @see {@link https://github.com/zbigniewsobiecki/llmist/blob/main/docs/HOOKS.md#hookpresetssilent | Full documentation}
4807
+ */
4808
+ static silent() {
4809
+ return {};
4810
+ }
4811
+ /**
4812
+ * Combines multiple hook configurations into one.
4813
+ *
4814
+ * Merge allows you to compose preset and custom hooks for modular monitoring
4815
+ * configurations. Understanding merge behavior is crucial for proper composition.
4816
+ *
4817
+ * **Merge behavior:**
4818
+ * - **Observers:** Composed - all handlers run sequentially in order
4819
+ * - **Interceptors:** Last one wins - only the last interceptor applies
4820
+ * - **Controllers:** Last one wins - only the last controller applies
4821
+ *
4822
+ * **Why interceptors/controllers don't compose:**
4823
+ * - Interceptors have different signatures per method, making composition impractical
4824
+ * - Controllers return specific actions that can't be meaningfully combined
4825
+ * - Only observers support composition because they're read-only and independent
4826
+ *
4827
+ * **Use cases:**
4828
+ * - Combining multiple presets (logging + timing + tokens)
4829
+ * - Adding custom hooks to presets
4830
+ * - Building modular, reusable monitoring configurations
4831
+ * - Environment-specific hook composition
4832
+ *
4833
+ * **Performance:** Minimal overhead for merging. Runtime performance depends on merged hooks.
4834
+ *
4835
+ * @param hookSets - Variable number of hook configurations to merge
4836
+ * @returns Single merged hook configuration with composed/overridden handlers
4837
+ *
4838
+ * @example
4839
+ * ```typescript
4840
+ * // Combine multiple presets
4841
+ * .withHooks(HookPresets.merge(
4842
+ * HookPresets.logging(),
4843
+ * HookPresets.timing(),
4844
+ * HookPresets.tokenTracking()
4845
+ * ))
4846
+ * // All observers from all three presets will run
4847
+ * ```
4848
+ *
4849
+ * @example
4850
+ * ```typescript
4851
+ * // Add custom observer to preset (both run)
4852
+ * .withHooks(HookPresets.merge(
4853
+ * HookPresets.timing(),
4854
+ * {
4855
+ * observers: {
4856
+ * onLLMCallComplete: async (ctx) => {
4857
+ * await saveMetrics({ tokens: ctx.usage?.totalTokens });
4858
+ * },
4859
+ * },
4860
+ * }
4861
+ * ))
4862
+ * ```
4863
+ *
4864
+ * @example
4865
+ * ```typescript
4866
+ * // Multiple interceptors (last wins!)
4867
+ * .withHooks(HookPresets.merge(
4868
+ * {
4869
+ * interceptors: {
4870
+ * interceptTextChunk: (chunk) => chunk.toUpperCase(), // Ignored
4871
+ * },
4872
+ * },
4873
+ * {
4874
+ * interceptors: {
4875
+ * interceptTextChunk: (chunk) => chunk.toLowerCase(), // This wins
4876
+ * },
4877
+ * }
4878
+ * ))
4879
+ * // Result: text will be lowercase
4880
+ * ```
4881
+ *
4882
+ * @example
4883
+ * ```typescript
4884
+ * // Modular environment-based configuration
4885
+ * const baseHooks = HookPresets.errorLogging();
4886
+ * const devHooks = HookPresets.merge(baseHooks, HookPresets.monitoring({ verbose: true }));
4887
+ * const prodHooks = HookPresets.merge(baseHooks, HookPresets.tokenTracking());
4888
+ *
4889
+ * const hooks = process.env.NODE_ENV === 'production' ? prodHooks : devHooks;
4890
+ * .withHooks(hooks)
4891
+ * ```
4892
+ *
4893
+ * @see {@link https://github.com/zbigniewsobiecki/llmist/blob/main/docs/HOOKS.md#hookpresetsmergehooksets | Full documentation}
4894
+ */
4895
+ static merge(...hookSets) {
4896
+ const merged = {
4897
+ observers: {},
4898
+ interceptors: {},
4899
+ controllers: {}
4900
+ };
4901
+ for (const hooks of hookSets) {
4902
+ if (hooks.observers) {
4903
+ for (const [key, handler] of Object.entries(hooks.observers)) {
4904
+ const typedKey = key;
4905
+ if (merged.observers[typedKey]) {
4906
+ const existing = merged.observers[typedKey];
4907
+ merged.observers[typedKey] = async (ctx) => {
4908
+ await existing(ctx);
4909
+ await handler(ctx);
4910
+ };
4911
+ } else {
4912
+ merged.observers[typedKey] = handler;
4913
+ }
4914
+ }
4915
+ }
4916
+ if (hooks.interceptors) {
4917
+ Object.assign(merged.interceptors, hooks.interceptors);
4918
+ }
4919
+ if (hooks.controllers) {
4920
+ Object.assign(merged.controllers, hooks.controllers);
4921
+ }
4922
+ }
4923
+ return merged;
4924
+ }
4925
+ /**
4926
+ * Composite preset combining logging, timing, tokenTracking, and errorLogging.
4927
+ *
4928
+ * This is the recommended preset for development and initial production deployments,
4929
+ * providing comprehensive observability with a single method call.
4930
+ *
4931
+ * **Includes:**
4932
+ * - All output from `logging()` preset (with optional verbosity)
4933
+ * - All output from `timing()` preset (execution times)
4934
+ * - All output from `tokenTracking()` preset (token usage)
4935
+ * - All output from `errorLogging()` preset (error details)
4936
+ *
4937
+ * **Output format:**
4938
+ * - Event logging: [LLM]/[GADGET] messages
4939
+ * - Timing: ⏱️ emoji with milliseconds
4940
+ * - Tokens: 📊 emoji with per-call and cumulative counts
4941
+ * - Errors: ❌ emoji with full error details
4942
+ *
4943
+ * **Use cases:**
4944
+ * - Full observability during development
4945
+ * - Comprehensive monitoring in production
4946
+ * - One-liner for complete agent visibility
4947
+ * - Troubleshooting and debugging with full context
4948
+ *
4949
+ * **Performance:** Combined overhead of all four presets, but still minimal in practice.
4950
+ *
4951
+ * @param options - Monitoring options
4952
+ * @param options.verbose - Passed to logging() preset for detailed output. Default: false
4953
+ * @returns Merged hook configuration combining all monitoring presets
4954
+ *
4955
+ * @example
4956
+ * ```typescript
4957
+ * // Basic monitoring (recommended for development)
4958
+ * await LLMist.createAgent()
4959
+ * .withHooks(HookPresets.monitoring())
4960
+ * .withGadgets(Calculator, Weather)
4961
+ * .ask("What is 15 times 23, and what's the weather in NYC?");
4962
+ * // Output: All events, timing, tokens, and errors in one place
4963
+ * ```
4964
+ *
4965
+ * @example
4966
+ * ```typescript
4967
+ * // Verbose monitoring with full details
4968
+ * await LLMist.createAgent()
4969
+ * .withHooks(HookPresets.monitoring({ verbose: true }))
4970
+ * .ask("Your prompt");
4971
+ * // Output includes: parameters, results, and complete responses
4972
+ * ```
4973
+ *
4974
+ * @example
4975
+ * ```typescript
4976
+ * // Environment-based monitoring
4977
+ * const isDev = process.env.NODE_ENV === 'development';
4978
+ * .withHooks(HookPresets.monitoring({ verbose: isDev }))
4979
+ * ```
4980
+ *
4981
+ * @see {@link https://github.com/zbigniewsobiecki/llmist/blob/main/docs/HOOKS.md#hookpresetsmonitoringoptions | Full documentation}
4982
+ */
4983
+ static monitoring(options = {}) {
4984
+ return _HookPresets.merge(
4985
+ _HookPresets.logging(options),
4986
+ _HookPresets.timing(),
4987
+ _HookPresets.tokenTracking(),
4988
+ _HookPresets.errorLogging()
4989
+ );
4990
+ }
4991
+ };
4992
+ }
4993
+ });
4994
+
4042
4995
  // src/providers/anthropic-models.ts
4043
4996
  var ANTHROPIC_MODELS;
4044
4997
  var init_anthropic_models = __esm({
@@ -4077,10 +5030,10 @@ var init_anthropic_models = __esm({
4077
5030
  contextWindow: 2e5,
4078
5031
  maxOutputTokens: 64e3,
4079
5032
  pricing: {
4080
- input: 0.8,
4081
- output: 4,
4082
- cachedInput: 0.08,
4083
- cacheWriteInput: 1
5033
+ input: 1,
5034
+ output: 5,
5035
+ cachedInput: 0.1,
5036
+ cacheWriteInput: 1.25
4084
5037
  },
4085
5038
  knowledgeCutoff: "2025-02",
4086
5039
  features: {
@@ -4276,10 +5229,10 @@ var init_anthropic_models = __esm({
4276
5229
  contextWindow: 2e5,
4277
5230
  maxOutputTokens: 64e3,
4278
5231
  pricing: {
4279
- input: 0.8,
4280
- output: 4,
4281
- cachedInput: 0.08,
4282
- cacheWriteInput: 1
5232
+ input: 1,
5233
+ output: 5,
5234
+ cachedInput: 0.1,
5235
+ cacheWriteInput: 1.25
4283
5236
  },
4284
5237
  knowledgeCutoff: "2025-02",
4285
5238
  features: {
@@ -4422,10 +5375,15 @@ var init_utils = __esm({
4422
5375
  });
4423
5376
 
4424
5377
  // src/providers/anthropic.ts
5378
+ function resolveAnthropicThinking(reasoning) {
5379
+ if (!reasoning?.enabled) return void 0;
5380
+ const budget = reasoning.budgetTokens ? Math.max(1024, reasoning.budgetTokens) : ANTHROPIC_EFFORT_BUDGET[reasoning.effort ?? "medium"];
5381
+ return { type: "enabled", budget_tokens: budget };
5382
+ }
4425
5383
  function createAnthropicProviderFromEnv() {
4426
5384
  return createProviderFromEnv("ANTHROPIC_API_KEY", import_sdk.default, AnthropicMessagesProvider);
4427
5385
  }
4428
- var import_sdk, AnthropicMessagesProvider;
5386
+ var import_sdk, ANTHROPIC_EFFORT_BUDGET, AnthropicMessagesProvider;
4429
5387
  var init_anthropic = __esm({
4430
5388
  "src/providers/anthropic.ts"() {
4431
5389
  "use strict";
@@ -4435,6 +5393,14 @@ var init_anthropic = __esm({
4435
5393
  init_base_provider();
4436
5394
  init_constants2();
4437
5395
  init_utils();
5396
+ ANTHROPIC_EFFORT_BUDGET = {
5397
+ none: 1024,
5398
+ // Minimum allowed by Anthropic
5399
+ low: 2048,
5400
+ medium: 8192,
5401
+ high: 16384,
5402
+ maximum: 32768
5403
+ };
4438
5404
  AnthropicMessagesProvider = class extends BaseProviderAdapter {
4439
5405
  providerId = "anthropic";
4440
5406
  supports(descriptor) {
@@ -4488,15 +5454,18 @@ var init_anthropic = __esm({
4488
5454
  )
4489
5455
  }));
4490
5456
  const defaultMaxTokens = spec?.maxOutputTokens ?? ANTHROPIC_DEFAULT_MAX_OUTPUT_TOKENS;
5457
+ const thinking = resolveAnthropicThinking(options.reasoning);
5458
+ const temperature = thinking ? void 0 : options.temperature;
4491
5459
  const payload = {
4492
5460
  model: descriptor.name,
4493
5461
  system,
4494
5462
  messages: conversation,
4495
5463
  max_tokens: options.maxTokens ?? defaultMaxTokens,
4496
- temperature: options.temperature,
5464
+ temperature,
4497
5465
  top_p: options.topP,
4498
5466
  stop_sequences: options.stopSequences,
4499
5467
  stream: true,
5468
+ ...thinking ? { thinking } : {},
4500
5469
  ...options.extra
4501
5470
  };
4502
5471
  return payload;
@@ -4576,8 +5545,39 @@ var init_anthropic = __esm({
4576
5545
  };
4577
5546
  continue;
4578
5547
  }
4579
- if (event.type === "content_block_delta" && event.delta.type === "text_delta") {
4580
- yield { text: event.delta.text ?? "", rawEvent: event };
5548
+ if (event.type === "content_block_start") {
5549
+ const block = event.content_block;
5550
+ if (block.type === "thinking") {
5551
+ yield { text: "", thinking: { content: "", type: "thinking" }, rawEvent: event };
5552
+ continue;
5553
+ }
5554
+ if (block.type === "redacted_thinking") {
5555
+ yield { text: "", thinking: { content: "", type: "redacted" }, rawEvent: event };
5556
+ continue;
5557
+ }
5558
+ }
5559
+ if (event.type === "content_block_delta") {
5560
+ const delta = event.delta;
5561
+ if (delta.type === "thinking_delta" && delta.thinking) {
5562
+ yield {
5563
+ text: "",
5564
+ thinking: { content: delta.thinking, type: "thinking" },
5565
+ rawEvent: event
5566
+ };
5567
+ continue;
5568
+ }
5569
+ if (delta.type === "signature_delta" && delta.signature) {
5570
+ yield {
5571
+ text: "",
5572
+ thinking: { content: "", type: "thinking", signature: delta.signature },
5573
+ rawEvent: event
5574
+ };
5575
+ continue;
5576
+ }
5577
+ if (delta.type === "text_delta") {
5578
+ yield { text: delta.text ?? "", rawEvent: event };
5579
+ continue;
5580
+ }
4581
5581
  continue;
4582
5582
  }
4583
5583
  if (event.type === "message_delta") {
@@ -4886,10 +5886,10 @@ var init_gemini_models = __esm({
4886
5886
  contextWindow: 1048576,
4887
5887
  maxOutputTokens: 65536,
4888
5888
  pricing: {
4889
- input: 0.4,
4890
- // $0.40 for text/image/video
5889
+ input: 0.5,
5890
+ // $0.50 for text/image/video
4891
5891
  output: 3,
4892
- cachedInput: 0.04
5892
+ cachedInput: 0.05
4893
5893
  },
4894
5894
  knowledgeCutoff: "2025-01",
4895
5895
  features: {
@@ -5183,6 +6183,23 @@ var init_gemini_speech_models = __esm({
5183
6183
  });
5184
6184
 
5185
6185
  // src/providers/gemini.ts
6186
+ function resolveGeminiThinkingConfig(reasoning, modelName) {
6187
+ if (!reasoning?.enabled) return void 0;
6188
+ const isGemini3 = modelName.includes("gemini-3");
6189
+ if (isGemini3) {
6190
+ return {
6191
+ thinkingConfig: {
6192
+ thinkingLevel: GEMINI3_THINKING_LEVEL[reasoning.effort ?? "medium"]
6193
+ }
6194
+ };
6195
+ }
6196
+ const budget = reasoning.budgetTokens ?? GEMINI25_THINKING_BUDGET[reasoning.effort ?? "medium"];
6197
+ return {
6198
+ thinkingConfig: {
6199
+ thinkingBudget: budget
6200
+ }
6201
+ };
6202
+ }
5186
6203
  function wrapPcmInWav(pcmData, sampleRate, bitsPerSample, numChannels) {
5187
6204
  const byteRate = sampleRate * numChannels * bitsPerSample / 8;
5188
6205
  const blockAlign = numChannels * bitsPerSample / 8;
@@ -5211,7 +6228,7 @@ function wrapPcmInWav(pcmData, sampleRate, bitsPerSample, numChannels) {
5211
6228
  function createGeminiProviderFromEnv() {
5212
6229
  return createProviderFromEnv("GEMINI_API_KEY", import_genai.GoogleGenAI, GeminiGenerativeProvider);
5213
6230
  }
5214
- var import_genai, GEMINI_ROLE_MAP, GeminiGenerativeProvider;
6231
+ var import_genai, GEMINI3_THINKING_LEVEL, GEMINI25_THINKING_BUDGET, GEMINI_ROLE_MAP, GeminiGenerativeProvider;
5215
6232
  var init_gemini = __esm({
5216
6233
  "src/providers/gemini.ts"() {
5217
6234
  "use strict";
@@ -5223,6 +6240,20 @@ var init_gemini = __esm({
5223
6240
  init_gemini_models();
5224
6241
  init_gemini_speech_models();
5225
6242
  init_utils();
6243
+ GEMINI3_THINKING_LEVEL = {
6244
+ none: "minimal",
6245
+ low: "low",
6246
+ medium: "medium",
6247
+ high: "high",
6248
+ maximum: "high"
6249
+ };
6250
+ GEMINI25_THINKING_BUDGET = {
6251
+ none: 0,
6252
+ low: 2048,
6253
+ medium: 8192,
6254
+ high: 16384,
6255
+ maximum: 24576
6256
+ };
5226
6257
  GEMINI_ROLE_MAP = {
5227
6258
  system: "user",
5228
6259
  user: "user",
@@ -5372,6 +6403,7 @@ var init_gemini = __esm({
5372
6403
  buildApiRequest(options, descriptor, _spec, messages) {
5373
6404
  const contents = this.convertMessagesToContents(messages);
5374
6405
  const generationConfig = this.buildGenerationConfig(options);
6406
+ const thinkingConfig = resolveGeminiThinkingConfig(options.reasoning, descriptor.name);
5375
6407
  const config = {
5376
6408
  // Note: systemInstruction removed - it doesn't work with countTokens()
5377
6409
  // System messages are now included in contents as user+model exchanges
@@ -5382,6 +6414,7 @@ var init_gemini = __esm({
5382
6414
  mode: import_genai.FunctionCallingConfigMode.NONE
5383
6415
  }
5384
6416
  },
6417
+ ...thinkingConfig ?? {},
5385
6418
  ...options.extra
5386
6419
  };
5387
6420
  return {
@@ -5519,7 +6552,18 @@ var init_gemini = __esm({
5519
6552
  async *normalizeProviderStream(iterable) {
5520
6553
  const stream2 = iterable;
5521
6554
  for await (const chunk of stream2) {
5522
- const text3 = this.extractMessageText(chunk);
6555
+ const { text: text3, thinkingText, thinkingSignature } = this.extractTextAndThinking(chunk);
6556
+ if (thinkingText) {
6557
+ yield {
6558
+ text: "",
6559
+ thinking: {
6560
+ content: thinkingText,
6561
+ type: "thinking",
6562
+ signature: thinkingSignature
6563
+ },
6564
+ rawEvent: chunk
6565
+ };
6566
+ }
5523
6567
  if (text3) {
5524
6568
  yield { text: text3, rawEvent: chunk };
5525
6569
  }
@@ -5530,11 +6574,30 @@ var init_gemini = __esm({
5530
6574
  }
5531
6575
  }
5532
6576
  }
5533
- extractMessageText(chunk) {
6577
+ /**
6578
+ * Extract both regular text and thinking text from a chunk.
6579
+ * Gemini marks thinking parts with `thought: true`.
6580
+ */
6581
+ extractTextAndThinking(chunk) {
5534
6582
  if (!chunk?.candidates) {
5535
- return "";
6583
+ return { text: "", thinkingText: "" };
6584
+ }
6585
+ let text3 = "";
6586
+ let thinkingText = "";
6587
+ let thinkingSignature;
6588
+ for (const candidate of chunk.candidates) {
6589
+ for (const part of candidate.content?.parts ?? []) {
6590
+ if (part.thought) {
6591
+ thinkingText += part.text ?? "";
6592
+ if (part.thoughtSignature) {
6593
+ thinkingSignature = part.thoughtSignature;
6594
+ }
6595
+ } else {
6596
+ text3 += part.text ?? "";
6597
+ }
6598
+ }
5536
6599
  }
5537
- return chunk.candidates.flatMap((candidate) => candidate.content?.parts ?? []).map((part) => part.text ?? "").join("");
6600
+ return { text: text3, thinkingText, thinkingSignature };
5538
6601
  }
5539
6602
  extractFinishReason(chunk) {
5540
6603
  const candidate = chunk?.candidates?.find((item) => item.finishReason);
@@ -5550,7 +6613,9 @@ var init_gemini = __esm({
5550
6613
  outputTokens: usageMetadata.candidatesTokenCount ?? 0,
5551
6614
  totalTokens: usageMetadata.totalTokenCount ?? 0,
5552
6615
  // Gemini returns cached token count in cachedContentTokenCount
5553
- cachedInputTokens: usageMetadata.cachedContentTokenCount ?? 0
6616
+ cachedInputTokens: usageMetadata.cachedContentTokenCount ?? 0,
6617
+ // Gemini returns thinking tokens in thoughtsTokenCount
6618
+ reasoningTokens: usageMetadata.thoughtsTokenCount
5554
6619
  };
5555
6620
  }
5556
6621
  /**
@@ -6571,11 +7636,13 @@ var init_openai_compatible_provider = __esm({
6571
7636
  yield { text: text3, rawEvent: chunk };
6572
7637
  }
6573
7638
  const finishReason = chunk.choices.find((choice) => choice.finish_reason)?.finish_reason;
7639
+ const usageDetails = chunk.usage;
6574
7640
  const usage = chunk.usage ? {
6575
7641
  inputTokens: chunk.usage.prompt_tokens,
6576
7642
  outputTokens: chunk.usage.completion_tokens,
6577
7643
  totalTokens: chunk.usage.total_tokens,
6578
- cachedInputTokens: 0
7644
+ cachedInputTokens: 0,
7645
+ reasoningTokens: usageDetails?.completion_tokens_details?.reasoning_tokens
6579
7646
  } : void 0;
6580
7647
  if (finishReason || usage) {
6581
7648
  yield { text: "", finishReason, usage, rawEvent: chunk };
@@ -6651,6 +7718,21 @@ var init_huggingface = __esm({
6651
7718
  getModelSpecs() {
6652
7719
  return HUGGINGFACE_MODELS;
6653
7720
  }
7721
+ /**
7722
+ * Override buildApiRequest to inject DeepSeek-specific thinking parameters.
7723
+ * DeepSeek models use `extra_body: { thinking: { type: "enabled" } }` for reasoning.
7724
+ */
7725
+ buildApiRequest(options, descriptor, spec, messages) {
7726
+ const request = super.buildApiRequest(options, descriptor, spec, messages);
7727
+ if (options.reasoning?.enabled && descriptor.name.toLowerCase().includes("deepseek")) {
7728
+ const requestObj = request;
7729
+ requestObj.extra_body = {
7730
+ ...requestObj.extra_body,
7731
+ thinking: { type: "enabled" }
7732
+ };
7733
+ }
7734
+ return request;
7735
+ }
6654
7736
  /**
6655
7737
  * Enhance error messages with HuggingFace-specific guidance.
6656
7738
  */
@@ -7536,7 +8618,7 @@ function sanitizeExtra(extra, allowTemperature) {
7536
8618
  function createOpenAIProviderFromEnv() {
7537
8619
  return createProviderFromEnv("OPENAI_API_KEY", import_openai3.default, OpenAIChatProvider);
7538
8620
  }
7539
- var import_openai3, import_tiktoken, ROLE_MAP2, OpenAIChatProvider;
8621
+ var import_openai3, import_tiktoken, ROLE_MAP2, OPENAI_EFFORT_MAP, OpenAIChatProvider;
7540
8622
  var init_openai = __esm({
7541
8623
  "src/providers/openai.ts"() {
7542
8624
  "use strict";
@@ -7554,6 +8636,13 @@ var init_openai = __esm({
7554
8636
  user: "user",
7555
8637
  assistant: "assistant"
7556
8638
  };
8639
+ OPENAI_EFFORT_MAP = {
8640
+ none: "none",
8641
+ low: "low",
8642
+ medium: "medium",
8643
+ high: "high",
8644
+ maximum: "xhigh"
8645
+ };
7557
8646
  OpenAIChatProvider = class extends BaseProviderAdapter {
7558
8647
  providerId = "openai";
7559
8648
  supports(descriptor) {
@@ -7644,10 +8733,15 @@ var init_openai = __esm({
7644
8733
  };
7645
8734
  }
7646
8735
  buildApiRequest(options, descriptor, spec, messages) {
7647
- const { maxTokens, temperature, topP, stopSequences, extra } = options;
8736
+ const { maxTokens, temperature, topP, stopSequences, extra, reasoning } = options;
7648
8737
  const supportsTemperature = spec?.metadata?.supportsTemperature !== false;
7649
8738
  const shouldIncludeTemperature = typeof temperature === "number" && supportsTemperature;
7650
8739
  const sanitizedExtra = sanitizeExtra(extra, shouldIncludeTemperature);
8740
+ const reasoningParam = reasoning?.enabled !== void 0 ? {
8741
+ reasoning: {
8742
+ effort: OPENAI_EFFORT_MAP[reasoning.effort ?? "medium"]
8743
+ }
8744
+ } : {};
7651
8745
  return {
7652
8746
  model: descriptor.name,
7653
8747
  messages: messages.map((message) => this.convertToOpenAIMessage(message)),
@@ -7658,6 +8752,7 @@ var init_openai = __esm({
7658
8752
  stop: stopSequences,
7659
8753
  stream: true,
7660
8754
  stream_options: { include_usage: true },
8755
+ ...reasoningParam,
7661
8756
  ...sanitizedExtra ?? {},
7662
8757
  ...shouldIncludeTemperature ? { temperature } : {}
7663
8758
  };
@@ -7746,11 +8841,13 @@ var init_openai = __esm({
7746
8841
  yield { text: text3, rawEvent: chunk };
7747
8842
  }
7748
8843
  const finishReason = chunk.choices.find((choice) => choice.finish_reason)?.finish_reason;
8844
+ const usageDetails = chunk.usage;
7749
8845
  const usage = chunk.usage ? {
7750
8846
  inputTokens: chunk.usage.prompt_tokens,
7751
8847
  outputTokens: chunk.usage.completion_tokens,
7752
8848
  totalTokens: chunk.usage.total_tokens,
7753
- cachedInputTokens: chunk.usage.prompt_tokens_details?.cached_tokens ?? 0
8849
+ cachedInputTokens: usageDetails?.prompt_tokens_details?.cached_tokens ?? 0,
8850
+ reasoningTokens: usageDetails?.completion_tokens_details?.reasoning_tokens
7754
8851
  } : void 0;
7755
8852
  if (finishReason || usage) {
7756
8853
  yield { text: "", finishReason, usage, rawEvent: chunk };
@@ -8285,7 +9382,7 @@ function createOpenRouterProviderFromEnv() {
8285
9382
  });
8286
9383
  return new OpenRouterProvider(client, config);
8287
9384
  }
8288
- var import_openai4, OpenRouterProvider;
9385
+ var import_openai4, OPENROUTER_EFFORT_MAP, OpenRouterProvider;
8289
9386
  var init_openrouter = __esm({
8290
9387
  "src/providers/openrouter.ts"() {
8291
9388
  "use strict";
@@ -8293,6 +9390,13 @@ var init_openrouter = __esm({
8293
9390
  init_openai_compatible_provider();
8294
9391
  init_openrouter_models();
8295
9392
  init_utils();
9393
+ OPENROUTER_EFFORT_MAP = {
9394
+ none: "none",
9395
+ low: "low",
9396
+ medium: "medium",
9397
+ high: "high",
9398
+ maximum: "xhigh"
9399
+ };
8296
9400
  OpenRouterProvider = class extends OpenAICompatibleProvider {
8297
9401
  providerId = "openrouter";
8298
9402
  providerAlias = "or";
@@ -8302,6 +9406,20 @@ var init_openrouter = __esm({
8302
9406
  getModelSpecs() {
8303
9407
  return OPENROUTER_MODELS;
8304
9408
  }
9409
+ /**
9410
+ * Override buildApiRequest to inject reasoning parameters.
9411
+ * OpenRouter normalizes reasoning into the standard OpenAI format.
9412
+ */
9413
+ buildApiRequest(options, descriptor, spec, messages) {
9414
+ const request = super.buildApiRequest(options, descriptor, spec, messages);
9415
+ if (options.reasoning?.enabled !== void 0) {
9416
+ const requestObj = request;
9417
+ requestObj.reasoning = {
9418
+ effort: OPENROUTER_EFFORT_MAP[options.reasoning.effort ?? "medium"]
9419
+ };
9420
+ }
9421
+ return request;
9422
+ }
8305
9423
  /**
8306
9424
  * Get custom headers for OpenRouter analytics.
8307
9425
  */
@@ -8539,9 +9657,10 @@ var init_model_registry = __esm({
8539
9657
  * @param outputTokens - Number of output tokens
8540
9658
  * @param cachedInputTokens - Number of cached input tokens (subset of inputTokens)
8541
9659
  * @param cacheCreationInputTokens - Number of cache creation tokens (subset of inputTokens, Anthropic only)
9660
+ * @param reasoningTokens - Number of reasoning/thinking tokens (subset of outputTokens)
8542
9661
  * @returns CostEstimate if model found, undefined otherwise
8543
9662
  */
8544
- estimateCost(modelId, inputTokens, outputTokens, cachedInputTokens = 0, cacheCreationInputTokens = 0) {
9663
+ estimateCost(modelId, inputTokens, outputTokens, cachedInputTokens = 0, cacheCreationInputTokens = 0, reasoningTokens = 0) {
8545
9664
  const spec = this.getModelSpec(modelId);
8546
9665
  if (!spec) return void 0;
8547
9666
  const cachedRate = spec.pricing.cachedInput ?? spec.pricing.input;
@@ -8551,13 +9670,18 @@ var init_model_registry = __esm({
8551
9670
  const cachedInputCost = cachedInputTokens / 1e6 * cachedRate;
8552
9671
  const cacheCreationCost = cacheCreationInputTokens / 1e6 * cacheWriteRate;
8553
9672
  const inputCost = uncachedInputCost + cachedInputCost + cacheCreationCost;
8554
- const outputCost = outputTokens / 1e6 * spec.pricing.output;
9673
+ const reasoningRate = spec.pricing.reasoningOutput ?? spec.pricing.output;
9674
+ const nonReasoningOutputTokens = outputTokens - reasoningTokens;
9675
+ const reasoningCost = reasoningTokens / 1e6 * reasoningRate;
9676
+ const nonReasoningOutputCost = nonReasoningOutputTokens / 1e6 * spec.pricing.output;
9677
+ const outputCost = nonReasoningOutputCost + reasoningCost;
8555
9678
  const totalCost = inputCost + outputCost;
8556
9679
  return {
8557
9680
  inputCost,
8558
9681
  cachedInputCost,
8559
9682
  cacheCreationCost,
8560
9683
  outputCost,
9684
+ reasoningCost,
8561
9685
  totalCost,
8562
9686
  currency: "USD"
8563
9687
  };
@@ -9229,6 +10353,8 @@ var init_builder = __esm({
9229
10353
  init_agent();
9230
10354
  init_agent_internal_key();
9231
10355
  init_event_handlers();
10356
+ init_file_logging();
10357
+ init_hook_presets();
9232
10358
  AgentBuilder = class {
9233
10359
  client;
9234
10360
  model;
@@ -9270,6 +10396,7 @@ var init_builder = __esm({
9270
10396
  // Shared retry config from parent for consistent backoff behavior
9271
10397
  // When a gadget calls withParentContext(ctx), this config is shared
9272
10398
  sharedRetryConfig;
10399
+ reasoningConfig;
9273
10400
  constructor(client) {
9274
10401
  this.client = client;
9275
10402
  }
@@ -9855,6 +10982,60 @@ var init_builder = __esm({
9855
10982
  this.signal = signal;
9856
10983
  return this;
9857
10984
  }
10985
+ /**
10986
+ * Enable reasoning/thinking mode for reasoning-capable models.
10987
+ *
10988
+ * Can be called with:
10989
+ * - No args: enables reasoning at "medium" effort
10990
+ * - A string effort level: `withReasoning("high")`
10991
+ * - A full config object: `withReasoning({ enabled: true, budgetTokens: 10000 })`
10992
+ *
10993
+ * @param config - Optional effort level or full reasoning config
10994
+ * @returns This builder for chaining
10995
+ *
10996
+ * @example
10997
+ * ```typescript
10998
+ * // Simple — medium effort
10999
+ * LLMist.createAgent()
11000
+ * .withModel("o3")
11001
+ * .withReasoning()
11002
+ * .ask("Solve this logic puzzle...");
11003
+ *
11004
+ * // Explicit effort level
11005
+ * LLMist.createAgent()
11006
+ * .withModel("anthropic:claude-4-opus")
11007
+ * .withReasoning("high")
11008
+ * .ask("Analyze this complex problem");
11009
+ *
11010
+ * // Full config with explicit token budget
11011
+ * LLMist.createAgent()
11012
+ * .withModel("anthropic:claude-4-opus")
11013
+ * .withReasoning({ enabled: true, budgetTokens: 16000 })
11014
+ * .ask("Step through this proof");
11015
+ * ```
11016
+ */
11017
+ withReasoning(config) {
11018
+ if (typeof config === "string") {
11019
+ this.reasoningConfig = { enabled: true, effort: config };
11020
+ } else if (config === void 0) {
11021
+ this.reasoningConfig = { enabled: true, effort: "medium" };
11022
+ } else {
11023
+ this.reasoningConfig = config;
11024
+ }
11025
+ return this;
11026
+ }
11027
+ /**
11028
+ * Explicitly disable reasoning for this agent, even if the model supports it.
11029
+ *
11030
+ * By default, reasoning is auto-enabled at "medium" effort for models with
11031
+ * `features.reasoning: true`. Use this to opt out.
11032
+ *
11033
+ * @returns This builder for chaining
11034
+ */
11035
+ withoutReasoning() {
11036
+ this.reasoningConfig = { enabled: false };
11037
+ return this;
11038
+ }
9858
11039
  /**
9859
11040
  * Set subagent configuration overrides.
9860
11041
  *
@@ -10022,9 +11203,16 @@ ${endPrefix}`
10022
11203
  * Note: Subagent event visibility is now handled entirely by the ExecutionTree.
10023
11204
  * When a subagent uses withParentContext(ctx), it shares the parent's tree,
10024
11205
  * and all events are automatically visible to tree subscribers (like the TUI).
11206
+ *
11207
+ * Environment-based file logging (via LLMIST_LOG_RAW_DIRECTORY) is automatically
11208
+ * injected if the env var is set. User-provided hooks take precedence.
10025
11209
  */
10026
11210
  composeHooks() {
10027
- const hooks = this.hooks;
11211
+ let hooks = this.hooks;
11212
+ const envFileLogging = getEnvFileLoggingHooks();
11213
+ if (envFileLogging) {
11214
+ hooks = hooks ? HookPresets.merge(envFileLogging, hooks) : envFileLogging;
11215
+ }
10028
11216
  if (!this.trailingMessage) {
10029
11217
  return hooks;
10030
11218
  }
@@ -10133,6 +11321,7 @@ ${endPrefix}`
10133
11321
  retryConfig: this.retryConfig,
10134
11322
  rateLimitConfig: this.rateLimitConfig,
10135
11323
  signal: this.signal,
11324
+ reasoning: this.reasoningConfig,
10136
11325
  subagentConfig: this.subagentConfig,
10137
11326
  // Tree context for shared tree model (subagents share parent's tree)
10138
11327
  parentTree: this.parentContext?.tree,
@@ -10320,6 +11509,7 @@ ${endPrefix}`
10320
11509
  retryConfig: this.retryConfig,
10321
11510
  rateLimitConfig: this.rateLimitConfig,
10322
11511
  signal: this.signal,
11512
+ reasoning: this.reasoningConfig,
10323
11513
  subagentConfig: this.subagentConfig,
10324
11514
  // Tree context for shared tree model (subagents share parent's tree)
10325
11515
  parentTree: this.parentContext?.tree,
@@ -10774,6 +11964,7 @@ var init_cost_reporting_client = __esm({
10774
11964
  let outputTokens = 0;
10775
11965
  let cachedInputTokens = 0;
10776
11966
  let cacheCreationInputTokens = 0;
11967
+ let reasoningTokens = 0;
10777
11968
  const messages = [
10778
11969
  ...options?.systemPrompt ? [{ role: "system", content: options.systemPrompt }] : [],
10779
11970
  { role: "user", content: prompt }
@@ -10790,6 +11981,7 @@ var init_cost_reporting_client = __esm({
10790
11981
  outputTokens = chunk.usage.outputTokens;
10791
11982
  cachedInputTokens = chunk.usage.cachedInputTokens ?? 0;
10792
11983
  cacheCreationInputTokens = chunk.usage.cacheCreationInputTokens ?? 0;
11984
+ reasoningTokens = chunk.usage.reasoningTokens ?? 0;
10793
11985
  }
10794
11986
  }
10795
11987
  this.reportCostFromUsage(
@@ -10797,7 +11989,8 @@ var init_cost_reporting_client = __esm({
10797
11989
  inputTokens,
10798
11990
  outputTokens,
10799
11991
  cachedInputTokens,
10800
- cacheCreationInputTokens
11992
+ cacheCreationInputTokens,
11993
+ reasoningTokens
10801
11994
  );
10802
11995
  return result;
10803
11996
  }
@@ -10816,6 +12009,7 @@ var init_cost_reporting_client = __esm({
10816
12009
  let outputTokens = 0;
10817
12010
  let cachedInputTokens = 0;
10818
12011
  let cacheCreationInputTokens = 0;
12012
+ let reasoningTokens = 0;
10819
12013
  const messages = [
10820
12014
  ...options?.systemPrompt ? [{ role: "system", content: options.systemPrompt }] : [],
10821
12015
  { role: "user", content: prompt }
@@ -10835,6 +12029,7 @@ var init_cost_reporting_client = __esm({
10835
12029
  outputTokens = chunk.usage.outputTokens;
10836
12030
  cachedInputTokens = chunk.usage.cachedInputTokens ?? 0;
10837
12031
  cacheCreationInputTokens = chunk.usage.cacheCreationInputTokens ?? 0;
12032
+ reasoningTokens = chunk.usage.reasoningTokens ?? 0;
10838
12033
  }
10839
12034
  }
10840
12035
  } finally {
@@ -10843,7 +12038,8 @@ var init_cost_reporting_client = __esm({
10843
12038
  inputTokens,
10844
12039
  outputTokens,
10845
12040
  cachedInputTokens,
10846
- cacheCreationInputTokens
12041
+ cacheCreationInputTokens,
12042
+ reasoningTokens
10847
12043
  );
10848
12044
  }
10849
12045
  }
@@ -10870,6 +12066,7 @@ var init_cost_reporting_client = __esm({
10870
12066
  let outputTokens = 0;
10871
12067
  let cachedInputTokens = 0;
10872
12068
  let cacheCreationInputTokens = 0;
12069
+ let reasoningTokens = 0;
10873
12070
  try {
10874
12071
  for await (const chunk of innerStream) {
10875
12072
  if (chunk.usage) {
@@ -10877,6 +12074,7 @@ var init_cost_reporting_client = __esm({
10877
12074
  outputTokens = chunk.usage.outputTokens;
10878
12075
  cachedInputTokens = chunk.usage.cachedInputTokens ?? 0;
10879
12076
  cacheCreationInputTokens = chunk.usage.cacheCreationInputTokens ?? 0;
12077
+ reasoningTokens = chunk.usage.reasoningTokens ?? 0;
10880
12078
  }
10881
12079
  yield chunk;
10882
12080
  }
@@ -10887,7 +12085,8 @@ var init_cost_reporting_client = __esm({
10887
12085
  inputTokens,
10888
12086
  outputTokens,
10889
12087
  cachedInputTokens,
10890
- cacheCreationInputTokens
12088
+ cacheCreationInputTokens,
12089
+ reasoningTokens
10891
12090
  );
10892
12091
  }
10893
12092
  }
@@ -10897,14 +12096,15 @@ var init_cost_reporting_client = __esm({
10897
12096
  /**
10898
12097
  * Calculates and reports cost from token usage.
10899
12098
  */
10900
- reportCostFromUsage(model, inputTokens, outputTokens, cachedInputTokens = 0, cacheCreationInputTokens = 0) {
12099
+ reportCostFromUsage(model, inputTokens, outputTokens, cachedInputTokens = 0, cacheCreationInputTokens = 0, reasoningTokens = 0) {
10901
12100
  if (inputTokens === 0 && outputTokens === 0) return;
10902
12101
  const estimate = this.client.modelRegistry.estimateCost(
10903
12102
  model,
10904
12103
  inputTokens,
10905
12104
  outputTokens,
10906
12105
  cachedInputTokens,
10907
- cacheCreationInputTokens
12106
+ cacheCreationInputTokens,
12107
+ reasoningTokens
10908
12108
  );
10909
12109
  if (estimate && estimate.totalCost > 0) {
10910
12110
  this.reportCost(estimate.totalCost);
@@ -11996,9 +13196,18 @@ var init_stream_processor = __esm({
11996
13196
  let usage;
11997
13197
  let didExecuteGadgets = false;
11998
13198
  let shouldBreakLoop = false;
13199
+ let thinkingContent = "";
11999
13200
  for await (const chunk of stream2) {
12000
13201
  if (chunk.finishReason) finishReason = chunk.finishReason;
12001
13202
  if (chunk.usage) usage = chunk.usage;
13203
+ if (chunk.thinking?.content) {
13204
+ thinkingContent += chunk.thinking.content;
13205
+ yield {
13206
+ type: "thinking",
13207
+ content: chunk.thinking.content,
13208
+ thinkingType: chunk.thinking.type
13209
+ };
13210
+ }
12002
13211
  let processedChunk = "";
12003
13212
  if (chunk.text) {
12004
13213
  processedChunk = chunk.text;
@@ -12112,7 +13321,8 @@ var init_stream_processor = __esm({
12112
13321
  finishReason,
12113
13322
  usage,
12114
13323
  rawResponse: this.responseText,
12115
- finalMessage
13324
+ finalMessage,
13325
+ thinkingContent: thinkingContent || void 0
12116
13326
  };
12117
13327
  yield completionEvent;
12118
13328
  }
@@ -12914,6 +14124,7 @@ var init_agent = __esm({
12914
14124
  mediaStore;
12915
14125
  // Cancellation
12916
14126
  signal;
14127
+ reasoning;
12917
14128
  // Retry configuration
12918
14129
  retryConfig;
12919
14130
  // Rate limit tracker for proactive throttling
@@ -13005,6 +14216,7 @@ var init_agent = __esm({
13005
14216
  );
13006
14217
  }
13007
14218
  this.signal = options.signal;
14219
+ this.reasoning = options.reasoning;
13008
14220
  this.retryConfig = options.sharedRetryConfig ?? resolveRetryConfig(options.retryConfig);
13009
14221
  if (options.sharedRateLimitTracker) {
13010
14222
  this.rateLimitTracker = options.sharedRateLimitTracker;
@@ -13407,6 +14619,7 @@ var init_agent = __esm({
13407
14619
  usage: result.usage,
13408
14620
  rawResponse: result.rawResponse,
13409
14621
  finalMessage: result.finalMessage,
14622
+ thinkingContent: result.thinkingContent,
13410
14623
  logger: this.logger,
13411
14624
  subagentContext
13412
14625
  };
@@ -13707,17 +14920,34 @@ var init_agent = __esm({
13707
14920
  });
13708
14921
  return { type: "compaction", event: compactionEvent };
13709
14922
  }
14923
+ /**
14924
+ * Resolve reasoning configuration with auto-enable logic.
14925
+ *
14926
+ * Priority: explicit config > auto-enable for reasoning models > undefined
14927
+ * When a model has `features.reasoning: true` and no explicit config is set,
14928
+ * reasoning is automatically enabled at "medium" effort.
14929
+ */
14930
+ resolveReasoningConfig(spec) {
14931
+ if (this.reasoning !== void 0) return this.reasoning;
14932
+ if (spec?.features?.reasoning) {
14933
+ return { enabled: true, effort: "medium" };
14934
+ }
14935
+ return void 0;
14936
+ }
13710
14937
  /**
13711
14938
  * Prepare LLM call options, create tree node, and process beforeLLMCall controller.
13712
14939
  * @returns options, node ID, and optional skipWithSynthetic response if controller wants to skip
13713
14940
  */
13714
14941
  async prepareLLMCall(iteration) {
14942
+ const spec = this.client.modelRegistry?.getModelSpec?.(this.model);
14943
+ const reasoning = this.resolveReasoningConfig(spec);
13715
14944
  let llmOptions = {
13716
14945
  model: this.model,
13717
14946
  messages: this.conversation.getMessages(),
13718
14947
  temperature: this.temperature,
13719
14948
  maxTokens: this.defaultMaxTokens,
13720
- signal: this.signal
14949
+ signal: this.signal,
14950
+ reasoning
13721
14951
  };
13722
14952
  const llmNode = this.tree.addLLMCall({
13723
14953
  iteration,
@@ -13787,13 +15017,15 @@ var init_agent = __esm({
13787
15017
  inputTokens,
13788
15018
  outputTokens,
13789
15019
  result.usage?.cachedInputTokens ?? 0,
13790
- result.usage?.cacheCreationInputTokens ?? 0
15020
+ result.usage?.cacheCreationInputTokens ?? 0,
15021
+ result.usage?.reasoningTokens ?? 0
13791
15022
  )?.totalCost;
13792
15023
  this.tree.completeLLMCall(nodeId, {
13793
15024
  response: result.rawResponse,
13794
15025
  usage: result.usage,
13795
15026
  finishReason: result.finishReason,
13796
- cost: llmCost
15027
+ cost: llmCost,
15028
+ thinkingContent: result.thinkingContent
13797
15029
  });
13798
15030
  }
13799
15031
  /**
@@ -13969,9 +15201,11 @@ __export(index_exports, {
13969
15201
  filterRootEvents: () => filterRootEvents,
13970
15202
  format: () => format,
13971
15203
  formatBytes: () => formatBytes,
15204
+ formatCallNumber: () => formatCallNumber,
13972
15205
  formatDate: () => formatDate,
13973
15206
  formatDuration: () => formatDuration,
13974
15207
  formatLLMError: () => formatLLMError,
15208
+ formatLlmRequest: () => formatLlmRequest,
13975
15209
  gadgetError: () => gadgetError,
13976
15210
  gadgetSuccess: () => gadgetSuccess,
13977
15211
  getErrorMessage: () => getErrorMessage,
@@ -14043,781 +15277,8 @@ var import_zod3 = require("zod");
14043
15277
  init_agent();
14044
15278
  init_builder();
14045
15279
  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
- };
15280
+ init_file_logging();
15281
+ init_hook_presets();
14821
15282
 
14822
15283
  // src/agent/compaction/index.ts
14823
15284
  init_config();
@@ -14830,6 +15291,7 @@ init_gadget_output_store();
14830
15291
 
14831
15292
  // src/agent/hints.ts
14832
15293
  init_prompt_config();
15294
+ init_hook_presets();
14833
15295
  function iterationProgressHint(options) {
14834
15296
  const { timing: timing2 = "always", showUrgency = true, template } = options ?? {};
14835
15297
  return {
@@ -15619,9 +16081,11 @@ function getHostExports2(ctx) {
15619
16081
  filterRootEvents,
15620
16082
  format,
15621
16083
  formatBytes,
16084
+ formatCallNumber,
15622
16085
  formatDate,
15623
16086
  formatDuration,
15624
16087
  formatLLMError,
16088
+ formatLlmRequest,
15625
16089
  gadgetError,
15626
16090
  gadgetSuccess,
15627
16091
  getErrorMessage,