@n1k1t/pipelain 0.4.15 → 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (143) hide show
  1. package/.claude/settings.local.json +7 -0
  2. package/AGENTS.md +20 -0
  3. package/README.md +151 -10
  4. package/assets/report/index.hbs +78 -24
  5. package/assets/report/main.css +122 -3
  6. package/lib/src/index.d.ts +2 -0
  7. package/lib/src/index.d.ts.map +1 -1
  8. package/lib/src/index.js +2 -0
  9. package/lib/src/index.js.map +1 -1
  10. package/lib/src/models/index.d.ts +1 -0
  11. package/lib/src/models/index.d.ts.map +1 -1
  12. package/lib/src/models/index.js +1 -0
  13. package/lib/src/models/index.js.map +1 -1
  14. package/lib/src/models/llm/hook.d.ts +128 -0
  15. package/lib/src/models/llm/hook.d.ts.map +1 -0
  16. package/lib/src/models/llm/hook.js +129 -0
  17. package/lib/src/models/llm/hook.js.map +1 -0
  18. package/lib/src/models/llm/index.d.ts +1 -0
  19. package/lib/src/models/llm/index.d.ts.map +1 -1
  20. package/lib/src/models/llm/index.js +1 -0
  21. package/lib/src/models/llm/index.js.map +1 -1
  22. package/lib/src/models/llm/mcp.d.ts +4 -0
  23. package/lib/src/models/llm/mcp.d.ts.map +1 -1
  24. package/lib/src/models/llm/mcp.js +5 -2
  25. package/lib/src/models/llm/mcp.js.map +1 -1
  26. package/lib/src/models/llm/providers/proxy.d.ts +6 -0
  27. package/lib/src/models/llm/providers/proxy.d.ts.map +1 -1
  28. package/lib/src/models/llm/providers/proxy.js +5 -3
  29. package/lib/src/models/llm/providers/proxy.js.map +1 -1
  30. package/lib/src/models/llm/router.d.ts +6 -0
  31. package/lib/src/models/llm/router.d.ts.map +1 -1
  32. package/lib/src/models/llm/router.js +12 -0
  33. package/lib/src/models/llm/router.js.map +1 -1
  34. package/lib/src/models/llm/tools/attachment.js +2 -2
  35. package/lib/src/models/llm/tools/attachment.js.map +1 -1
  36. package/lib/src/models/llm/tools/edit.js +3 -3
  37. package/lib/src/models/llm/tools/edit.js.map +1 -1
  38. package/lib/src/models/llm/tools/fetch.js +4 -4
  39. package/lib/src/models/llm/tools/fetch.js.map +1 -1
  40. package/lib/src/models/llm/tools/grep.js +2 -2
  41. package/lib/src/models/llm/tools/grep.js.map +1 -1
  42. package/lib/src/models/llm/tools/ls.js +2 -2
  43. package/lib/src/models/llm/tools/ls.js.map +1 -1
  44. package/lib/src/models/llm/tools/mkdir.js +2 -2
  45. package/lib/src/models/llm/tools/mkdir.js.map +1 -1
  46. package/lib/src/models/llm/tools/model.d.ts +4 -4
  47. package/lib/src/models/llm/tools/model.d.ts.map +1 -1
  48. package/lib/src/models/llm/tools/model.js +19 -10
  49. package/lib/src/models/llm/tools/model.js.map +1 -1
  50. package/lib/src/models/llm/tools/npm.js +1 -1
  51. package/lib/src/models/llm/tools/npm.js.map +1 -1
  52. package/lib/src/models/llm/tools/npx.js +1 -1
  53. package/lib/src/models/llm/tools/npx.js.map +1 -1
  54. package/lib/src/models/llm/tools/read.js +3 -3
  55. package/lib/src/models/llm/tools/read.js.map +1 -1
  56. package/lib/src/models/llm/tools/rm.js +4 -4
  57. package/lib/src/models/llm/tools/rm.js.map +1 -1
  58. package/lib/src/models/llm/tools/search.js +2 -2
  59. package/lib/src/models/llm/tools/search.js.map +1 -1
  60. package/lib/src/models/llm/tools/skill.js +2 -2
  61. package/lib/src/models/llm/tools/skill.js.map +1 -1
  62. package/lib/src/models/llm/tools/write.js +3 -3
  63. package/lib/src/models/llm/tools/write.js.map +1 -1
  64. package/lib/src/models/meta.d.ts +12 -0
  65. package/lib/src/models/meta.d.ts.map +1 -0
  66. package/lib/src/models/meta.js +32 -0
  67. package/lib/src/models/meta.js.map +1 -0
  68. package/lib/src/models/pipeline/model.d.ts +2 -0
  69. package/lib/src/models/pipeline/model.d.ts.map +1 -1
  70. package/lib/src/models/pipeline/model.js +8 -4
  71. package/lib/src/models/pipeline/model.js.map +1 -1
  72. package/lib/src/models/pipeline/parameters.d.ts.map +1 -1
  73. package/lib/src/models/pipeline/parameters.js +5 -1
  74. package/lib/src/models/pipeline/parameters.js.map +1 -1
  75. package/lib/src/models/pipeline/report/index.d.ts.map +1 -1
  76. package/lib/src/models/pipeline/report/index.js +28 -10
  77. package/lib/src/models/pipeline/report/index.js.map +1 -1
  78. package/lib/src/models/pipeline/report/types.d.ts +12 -4
  79. package/lib/src/models/pipeline/report/types.d.ts.map +1 -1
  80. package/lib/src/models/pipeline/session.d.ts +12 -23
  81. package/lib/src/models/pipeline/session.d.ts.map +1 -1
  82. package/lib/src/models/pipeline/session.js +15 -10
  83. package/lib/src/models/pipeline/session.js.map +1 -1
  84. package/lib/src/models/pipeline/stdout/compiled/console.js +16 -13
  85. package/lib/src/models/pipeline/stdout/compiled/console.js.map +1 -1
  86. package/lib/src/models/pipeline/stdout/model.d.ts +1 -1
  87. package/lib/src/models/pipeline/stdout/model.d.ts.map +1 -1
  88. package/lib/src/models/pipeline/stdout/model.js +12 -7
  89. package/lib/src/models/pipeline/stdout/model.js.map +1 -1
  90. package/lib/src/models/pipeline/steps/ai/actions/fallback.d.ts +1 -0
  91. package/lib/src/models/pipeline/steps/ai/actions/fallback.d.ts.map +1 -1
  92. package/lib/src/models/pipeline/steps/ai/actions/fallback.js +4 -1
  93. package/lib/src/models/pipeline/steps/ai/actions/fallback.js.map +1 -1
  94. package/lib/src/models/pipeline/steps/ai/actions/model.d.ts +2 -3
  95. package/lib/src/models/pipeline/steps/ai/actions/model.d.ts.map +1 -1
  96. package/lib/src/models/pipeline/steps/ai/actions/model.js +2 -9
  97. package/lib/src/models/pipeline/steps/ai/actions/model.js.map +1 -1
  98. package/lib/src/models/pipeline/steps/ai/actions/reasoning.d.ts +1 -1
  99. package/lib/src/models/pipeline/steps/ai/actions/reasoning.d.ts.map +1 -1
  100. package/lib/src/models/pipeline/steps/ai/actions/reasoning.js +5 -3
  101. package/lib/src/models/pipeline/steps/ai/actions/reasoning.js.map +1 -1
  102. package/lib/src/models/pipeline/steps/ai/actions/tool.d.ts +1 -1
  103. package/lib/src/models/pipeline/steps/ai/actions/tool.d.ts.map +1 -1
  104. package/lib/src/models/pipeline/steps/ai/actions/tool.js +3 -2
  105. package/lib/src/models/pipeline/steps/ai/actions/tool.js.map +1 -1
  106. package/lib/src/models/pipeline/steps/ai/index.d.ts +3 -0
  107. package/lib/src/models/pipeline/steps/ai/index.d.ts.map +1 -1
  108. package/lib/src/models/pipeline/steps/ai/index.js +31 -9
  109. package/lib/src/models/pipeline/steps/ai/index.js.map +1 -1
  110. package/lib/src/models/pipeline/steps/ai/types.d.ts +2 -1
  111. package/lib/src/models/pipeline/steps/ai/types.d.ts.map +1 -1
  112. package/lib/src/models/pipeline/steps/ai/utils.js +1 -1
  113. package/lib/src/models/pipeline/steps/ai/utils.js.map +1 -1
  114. package/lib/src/models/pipeline/steps/loop.d.ts.map +1 -1
  115. package/lib/src/models/pipeline/steps/loop.js +10 -7
  116. package/lib/src/models/pipeline/steps/loop.js.map +1 -1
  117. package/lib/src/models/pipeline/steps/model.d.ts +2 -0
  118. package/lib/src/models/pipeline/steps/model.d.ts.map +1 -1
  119. package/lib/src/models/pipeline/steps/model.js +2 -0
  120. package/lib/src/models/pipeline/steps/model.js.map +1 -1
  121. package/lib/src/models/pipeline/steps/self.d.ts.map +1 -1
  122. package/lib/src/models/pipeline/steps/self.js +6 -5
  123. package/lib/src/models/pipeline/steps/self.js.map +1 -1
  124. package/lib/src/models/pipeline/steps/swarm.d.ts.map +1 -1
  125. package/lib/src/models/pipeline/steps/swarm.js +10 -8
  126. package/lib/src/models/pipeline/steps/swarm.js.map +1 -1
  127. package/lib/src/models/pipeline/types.d.ts +1 -0
  128. package/lib/src/models/pipeline/types.d.ts.map +1 -1
  129. package/lib/src/setup.js +30 -3
  130. package/lib/src/setup.js.map +1 -1
  131. package/lib/src/utils/common.d.ts +8 -0
  132. package/lib/src/utils/common.d.ts.map +1 -1
  133. package/lib/src/utils/common.js +14 -1
  134. package/lib/src/utils/common.js.map +1 -1
  135. package/lib/src/utils/index.d.ts +0 -1
  136. package/lib/src/utils/index.d.ts.map +1 -1
  137. package/lib/src/utils/index.js +0 -1
  138. package/lib/src/utils/index.js.map +1 -1
  139. package/package.json +1 -1
  140. package/lib/src/utils/meta.d.ts +0 -9
  141. package/lib/src/utils/meta.d.ts.map +0 -1
  142. package/lib/src/utils/meta.js +0 -15
  143. package/lib/src/utils/meta.js.map +0 -1
@@ -0,0 +1,7 @@
1
+ {
2
+ "permissions": {
3
+ "allow": [
4
+ "Bash(npx jest *)"
5
+ ]
6
+ }
7
+ }
package/AGENTS.md ADDED
@@ -0,0 +1,20 @@
1
+ # AGENTS.md
2
+
3
+ Instructions for AI agents working in this repository (`@n1k1t/pipelain`).
4
+
5
+ ## Mandatory rules
6
+
7
+ - If the `caveman` skill is installed, use it for communication in this session.
8
+
9
+ ## Project documentation
10
+
11
+ Before starting work, review the documentation in `.ai/docs`:
12
+
13
+ - [`.ai/docs/structure.md`](.ai/docs/structure.md) — project structure, purpose of the main directories and files.
14
+ - [`.ai/docs/style.md`](.ai/docs/style.md) — code style: import/export order, object and function formatting.
15
+
16
+ ## Useful commands
17
+
18
+ - `npm test` — run tests (Jest).
19
+ - `npm run build:check` — type-check without building (`tsc --noEmit`).
20
+ - `npm run build` — build the project into `lib/`.
package/README.md CHANGED
@@ -33,14 +33,17 @@ Powerful utility to build and execute type-safe AI pipelines with structured out
33
33
  - [AI Step with LLM Configuration](#ai-step-with-llm-configuration)
34
34
  - [AI Step with Fallback](#ai-step-with-fallback)
35
35
  - [Debugging AI Steps](#debugging-ai-steps)
36
+ - [Execution Reports](#execution-reports)
36
37
  - [Combining self and ai Steps](#combining-self-and-ai-steps)
37
38
  - [Parallel Execution with swarm](#parallel-execution-with-swarm)
38
39
  - [Iterative Execution with loop](#iterative-execution-with-loop)
39
40
  - [Multi-Provider Pipelines](#multi-provider-pipelines)
41
+ - [Custom Model Routing](#custom-model-routing)
40
42
  - [MCP (Model Context Protocol) Integration](#mcp-model-context-protocol-integration)
41
43
  - [Using Skills](#using-skills)
42
44
  - [Extensions](#extensions)
43
45
  - [Custom LLM Tools](#custom-llm-tools)
46
+ - [LLM Tool Hooks](#llm-tool-hooks)
44
47
  - [Custom LLM Skills](#custom-llm-skills)
45
48
  - [Registering Skills from Markdown](#registering-skills-from-markdown)
46
49
  - [License](#license)
@@ -114,9 +117,11 @@ The following environment variables are supported:
114
117
  | --- | --- | --- |
115
118
  | `PIPELAIN_API_KEY` | API key for the LLM provider. | - |
116
119
  | `PIPELAIN_API_URL` | Custom API URL for the LLM provider. | - |
117
- | `PIPELAIN_SKILLS_PATHS` | Directories paths where LLM skills are stored (separated by `;`). | `~/.agents/skills` |
120
+ | `PIPELAIN_SKILLS_PATHS` | Directories paths where LLM skills are stored (separated by `;`). | `./.agents/skills;~/.agents/skills` |
118
121
  | `PIPELAIN_MODEL` | Default LLM model to use. | `gemini-flash-latest` |
119
122
  | `PIPELAIN_PROVIDER` | LLM provider name (e.g., `google`, `openai`). | - |
123
+ | `PIPELAIN_FLAGS_REPORT` | Save an HTML report of the pipeline execution. | `false` |
124
+ | `PIPELAIN_FLAGS_DEBUG` | Enable debug mode for all AI steps. | `false` |
120
125
  | `EXA_API_KEY` | API key for Exa web search tools. | - |
121
126
 
122
127
  ## Pipeline Step Utilities
@@ -136,7 +141,7 @@ Used to create different types of steps:
136
141
  General-purpose utilities:
137
142
  - `content`: `ContentFactory` instance to create structured prompt content (articles, tasks, rules, attachments).
138
143
  - `bash`: Execute shell commands on the local machine.
139
- - `log`: Emit log events for the current pipeline session.
144
+ - `log`: Emit `INFO` log events for the current pipeline session (shown in stdout and HTML reports).
140
145
 
141
146
  ### `context`
142
147
  Shared state and configuration:
@@ -209,13 +214,13 @@ To use a custom logger or override specific event handlers, use `PipelineStdout`
209
214
  import { PipelineStdout } from '@n1k1t/pipelain';
210
215
 
211
216
  const customStdout = PipelineStdout
212
- .build(console) // Pass any logger with .info and .warn methods
213
- .override('log', (event) => {
214
- console.log(`[CUSTOM LOG] ${event.message}`);
217
+ .build(console) // Pass any logger with .info, .warn and .debug methods
218
+ .override('log', ({ level, message }) => {
219
+ console.log(`[${level}]`, ...message);
215
220
  })
216
- .override('step:run', (event) => {
217
- if (event.meta.state === 'SUCCESS') {
218
- console.log(`Step ${event.step.title} finished in ${event.meta.spent}ms`);
221
+ .override('step:run', ({ step }) => {
222
+ if (step.meta.is('DONE')) {
223
+ console.log(`Step ${step.title} finished in ${step.meta.spent}ms`);
219
224
  }
220
225
  });
221
226
 
@@ -225,6 +230,17 @@ const customStdout = PipelineStdout
225
230
  })();
226
231
  ```
227
232
 
233
+ Each pipeline, step and AI action has a `meta` object with its execution state:
234
+
235
+ | Property / Method | Description |
236
+ | --- | --- |
237
+ | `state` | `'INIT' \| 'PENDING' \| 'DONE' \| 'ERROR'` |
238
+ | `spent` | Time spent since creation (ms). |
239
+ | `timestamp` | Creation timestamp. |
240
+ | `is(state \| state[])` | Checks the current state. |
241
+
242
+ The `log` event has a `level` field: `'DEBUG' | 'INFO' | 'WARN'`. `utils.log(...)` emits `INFO`.
243
+
228
244
  ### Structured Prompt Content
229
245
 
230
246
  You can combine multiple content types to create a rich, structured prompt for the AI:
@@ -353,7 +369,28 @@ You can use the `.debug()` method to inspect the prompts sent to the AI. When de
353
369
  )
354
370
  ```
355
371
 
356
- The prompt will be saved to: `.pipelain/${timestamp}-${session-id}/${step-title}.md`.
372
+ The prompt will be saved to: `.pipelain/debug/YYYY-MM-DD--HH-mm-ss--<session-id>/<step-number>.<step-trace>.md`.
373
+
374
+ ### Execution Reports
375
+
376
+ Use `.report()` on the pipeline compiler (or set `PIPELAIN_FLAGS_REPORT=true`) to save an HTML report of the AI steps execution:
377
+
378
+ ```ts
379
+ const pipeline = PipelineCompiler
380
+ .build('Translate')
381
+ .report() // Enables HTML report
382
+ .input(z.string())
383
+ .step('translated', ({ factory, context }) => factory
384
+ .ai('Translating')
385
+ .prompt([`Translate "${context.input}" into Spanish`])
386
+ );
387
+ ```
388
+
389
+ The report is saved into `.pipelain/reports/YYYY-MM-DD--HH-mm-ss--<session-id>.html` and includes:
390
+
391
+ - AI steps with messages, actions (tools, reasoning, fallbacks), output and timing;
392
+ - `INFO` and `WARN` logs emitted via `utils.log`;
393
+ - token usage per provider and model (prompt, cached, completion) and total session time.
357
394
 
358
395
  ### Combining `self` and `ai` Steps
359
396
 
@@ -504,6 +541,29 @@ const pipeline = PipelineCompiler
504
541
  );
505
542
  ```
506
543
 
544
+ ### Custom Model Routing
545
+
546
+ `LlmRouter` picks a provider by model name. Use `register` to route models matched by a [minimatch](https://github.com/isaacs/minimatch) pattern to your own provider:
547
+
548
+ ```ts
549
+ import { LlmRouter, llm } from '@n1k1t/pipelain';
550
+
551
+ const router = LlmRouter
552
+ .build()
553
+ .register('corp-*', (model) => llm.providers.LlmProxyProvider.build(model, {
554
+ name: 'corp', // Shown in usage stats and reports instead of "proxy"
555
+ connection: { key: process.env.CORP_API_KEY!, url: 'https://llm.corp.local/v1' },
556
+ }));
557
+
558
+ pipeline.step('analysis', ({ factory }) => factory
559
+ .ai('Corp Analysis')
560
+ .llm(() => router.provide('corp-gpt-4o'))
561
+ .prompt(['...'])
562
+ );
563
+ ```
564
+
565
+ Registrations are checked in order and the first match wins. Unmatched models fall back to the default routing.
566
+
507
567
  ### MCP (Model Context Protocol) Integration
508
568
 
509
569
  You can integrate MCP servers into your pipeline steps. This allows the AI to use tools provided by external MCP servers. You can also filter which tools are enabled:
@@ -526,6 +586,9 @@ pipeline.step('mcp_research', ({ factory, context }) => factory
526
586
  enabled: ['list-files', 'read-file'],
527
587
  // Or disable specific tools
528
588
  // disabled: ['delete-file'],
589
+
590
+ // Add prefix to tool names (e.g. "gdrive_read-file")
591
+ prefix: 'gdrive_',
529
592
  },
530
593
  }),
531
594
  ],
@@ -534,6 +597,8 @@ pipeline.step('mcp_research', ({ factory, context }) => factory
534
597
  );
535
598
  ```
536
599
 
600
+ Use `tools.prefix` to avoid name conflicts between MCP servers or with built-in tools. The `enabled` and `disabled` patterns match tool names without the prefix. Hooks by name (`LlmHook.build('gdrive_read-file')`) match tool names with the prefix.
601
+
537
602
  ### Using Skills
538
603
 
539
604
  Skills allow you to inject reusable domain-specific instructions or workflows into your AI steps. You can manage them via `factory.skills`:
@@ -562,7 +627,7 @@ You can create custom tools for the AI using `LlmToolCompiler`. This allows the
562
627
 
563
628
  ```ts
564
629
  import z from 'zod/v3';
565
- import { LlmToolCompiler } from '@n1k1t/pipelain';
630
+ import { LlmToolCompiler, LlmToolExecutionError } from '@n1k1t/pipelain';
566
631
 
567
632
  // 1. Define the tool
568
633
  const weatherTool = LlmToolCompiler
@@ -575,6 +640,11 @@ const weatherTool = LlmToolCompiler
575
640
  condition: z.string()
576
641
  }))
577
642
  .execute(() => async ({ city }) => {
643
+ if (!city.trim()) {
644
+ // Errors are reported back to the AI as "Execution failed: <reason>"
645
+ throw LlmToolExecutionError.build('City name is empty');
646
+ }
647
+
578
648
  // Your implementation here
579
649
  return { temperature: 22, condition: 'Sunny' };
580
650
  });
@@ -592,6 +662,77 @@ pipeline.step('weather_report', ({ factory, context }) => factory
592
662
  );
593
663
  ```
594
664
 
665
+ ### LLM Tool Hooks
666
+
667
+ Use `LlmHook` to intercept tool calls inside an `ai` step. With a hook you can validate or modify the input, override the output, skip the original execution or handle errors. Provide hooks to the step with the `.hooks([...])` method.
668
+
669
+ A hook matches a tool by one of these targets:
670
+
671
+ - `LlmHook.build('name')` matches a tool by its name (built-in, custom or MCP tool).
672
+ - `LlmHook.build(compiler)` matches a tool compiled from the same `LlmToolCompiler` instance.
673
+ - `LlmHook.build(tool)` matches the same `Tool` instance.
674
+
675
+ When a hook has a name (`LlmHook.build(compiler, { name: 'weather' })`), the name has priority over the compiler or tool.
676
+
677
+ Each hook has two handlers:
678
+
679
+ - `before({ input, next, session, context, step })` runs instead of the original `execute`. The original `execute` runs only when you call `next(input)`. If you do not call `next`, the returned value becomes the tool output.
680
+ - `after({ output, input, session, context, step })` runs after `before` is settled. `output` is a `PromiseSettledResult`, so you can handle errors. The returned value becomes the final tool output and must match the tool output type. By default, `after` rethrows the error or returns the value.
681
+
682
+ ```ts
683
+ import { LlmHook, LlmToolExecutionError } from '@n1k1t/pipelain';
684
+
685
+ // Restrict paths of the built-in "write" tool
686
+ const guard = LlmHook
687
+ .build<{ input: { path: string; content: string } }>('write')
688
+ .before(({ input, next }) => {
689
+ if (input.path.startsWith('/etc')) {
690
+ throw LlmToolExecutionError.build('Writing to /etc is not allowed');
691
+ }
692
+
693
+ return next(input);
694
+ });
695
+
696
+ // Cache results of the custom tool and convert errors for the AI
697
+ const cache = new Map<string, { temperature: number; condition: string }>();
698
+ const weather = LlmHook
699
+ .build(weatherTool)
700
+ .before(async ({ input, next }) => {
701
+ const cached = cache.get(input.city);
702
+ if (cached) {
703
+ return cached;
704
+ }
705
+
706
+ const output = await next(input);
707
+
708
+ cache.set(input.city, output);
709
+ return output;
710
+ })
711
+ .after(({ output }) => {
712
+ if (output.status === 'rejected') {
713
+ throw LlmToolExecutionError.build(output.reason);
714
+ }
715
+
716
+ return output.value;
717
+ });
718
+
719
+ pipeline.step('hooked', ({ factory, context }) => factory
720
+ .ai('Executing with hooks')
721
+ .llm(({ context }) => context.llm.assign({
722
+ tools: factory.tools
723
+ .files('read-write')
724
+ .custom({ weather: weatherTool })
725
+ .provide(),
726
+ }))
727
+ .hooks([guard, weather])
728
+ .prompt([`Check weather in ${context.input} and save it to weather.md`])
729
+ );
730
+ ```
731
+
732
+ When several hooks match the same tool, they run in the order of the array: the `before` handler of the first hook runs first, and its `after` handler runs last.
733
+
734
+ > Built-in tools with options (for example, `write` in `files('read-write')`) are clones of the original compiler. Match built-in tools by name.
735
+
595
736
  ### Custom LLM Skills
596
737
 
597
738
  You can also provide custom skills programmatically:
@@ -22,13 +22,21 @@
22
22
  PipelAIn Report
23
23
  </h1>
24
24
  <div class="session-info">
25
- <div class="info-item">
26
- <span class="info-label">Session ID:</span>
27
- <span class="info-value text-mono">{{session.id}}</span>
25
+ <div class="info-column">
26
+ <div class="info-item">
27
+ <span class="info-label">Session ID:</span>
28
+ <span class="info-value text-mono">{{session.id}}</span>
29
+ </div>
28
30
  </div>
29
- <div class="info-item">
30
- <span class="info-label">Timestamp:</span>
31
- <span class="info-value">{{formatDate session.timestamp}} {{formatTime session.timestamp}}</span>
31
+ <div class="info-column">
32
+ <div class="info-item">
33
+ <span class="info-label">Timestamp:</span>
34
+ <span class="info-value">{{formatDate session.timestamp}} {{formatTime session.timestamp}}</span>
35
+ </div>
36
+ <div class="info-item">
37
+ <span class="info-label">Spent:</span>
38
+ <span class="info-value">{{formatDuration session.spent}}</span>
39
+ </div>
32
40
  </div>
33
41
  </div>
34
42
  </div>
@@ -37,12 +45,16 @@
37
45
  <div class="metrics-grid">
38
46
  <div class="metric-card">
39
47
  <div class="metric-label">Total Tokens</div>
40
- <div class="metric-value">{{sum usage.llm.total.prompt usage.llm.total.completion}}</div>
48
+ <div class="metric-value">{{sum (sum usage.llm.total.prompt usage.llm.total.cached) usage.llm.total.completion}}</div>
41
49
  </div>
42
50
  <div class="metric-card">
43
51
  <div class="metric-label">Prompt Tokens</div>
44
52
  <div class="metric-value">{{usage.llm.total.prompt}}</div>
45
53
  </div>
54
+ <div class="metric-card">
55
+ <div class="metric-label">Cached Tokens</div>
56
+ <div class="metric-value">{{usage.llm.total.cached}}</div>
57
+ </div>
46
58
  <div class="metric-card">
47
59
  <div class="metric-label">Completion Tokens</div>
48
60
  <div class="metric-value">{{usage.llm.total.completion}}</div>
@@ -52,31 +64,69 @@
52
64
 
53
65
  <!-- Snapshots -->
54
66
  <main class="snapshots-section">
55
- <h2 class="section-title">Steps <span class="text-muted">({{snapshots.length}})</span></h2>
67
+ <h2 class="section-title">Snapshots <span class="text-muted">({{snapshots.length}})</span></h2>
56
68
 
57
69
  <div class="snapshots-list">
58
70
  {{#each snapshots}}
59
- <details class="snapshot-card {{#if error}}state-error{{else}}state-done{{/if}}">
71
+ {{#if (eq type "log")}}
72
+ <!-- Log -->
73
+ <details class="snapshot-card state-log log-{{level}}">
74
+ <summary class="snapshot-summary">
75
+ <div class="summary-left">
76
+ <span class="expand-arrow">▶</span>
77
+ <span class="action-type-badge badge-log-{{level}}">{{level}}</span>
78
+ <span class="log-preview text-mono">{{message}}</span>
79
+ </div>
80
+ </summary>
81
+
82
+ <div class="snapshot-content">
83
+ <pre class="log-message"><code>{{message}}</code></pre>
84
+ </div>
85
+ </details>
86
+ {{else}}
87
+ <details class="snapshot-card {{#if (eq type "step:error")}}state-error{{else}}state-done{{/if}}">
60
88
  <summary class="snapshot-summary">
61
89
  <div class="summary-left">
62
90
  <span class="expand-arrow">▶</span>
91
+ {{#if (eq type "step:error")}}
92
+ <span class="action-type-badge badge-error">ERROR</span>
93
+ {{else}}
94
+ <span class="action-type-badge badge-success">DONE</span>
95
+ {{/if}}
63
96
  <span class="step-title">{{step.title}}</span>
64
97
  </div>
65
98
  <div class="summary-right">
66
99
  <span class="llm-tag text-muted">{{llm.name}}/{{llm.model}}</span>
67
- {{#if error}}
68
- <span class="badge badge-error">ERROR</span>
69
- {{else}}
70
- {{#if usage.totalTokens}}
71
- <span class="usage-tag">{{usage.totalTokens}} tokens</span>
72
- {{/if}}
73
- <span class="badge badge-success">DONE</span>
100
+ {{#if (tokens usage "total")}}
101
+ <span class="usage-tag">{{tokens usage "total"}} tokens</span>
74
102
  {{/if}}
75
- <span class="time-tag text-muted">{{formatTime timestamp}}</span>
103
+ <span class="time-tag text-muted">{{formatDuration meta.spent}}</span>
76
104
  </div>
77
105
  </summary>
78
106
 
79
107
  <div class="snapshot-content">
108
+ {{#if usage}}
109
+ <!-- Step Tokens -->
110
+ <div class="tokens-grid">
111
+ <div class="token-cell">
112
+ <span class="token-label">Total Tokens</span>
113
+ <span class="token-value text-mono">{{tokens usage "total"}}</span>
114
+ </div>
115
+ <div class="token-cell">
116
+ <span class="token-label">Prompt Tokens</span>
117
+ <span class="token-value text-mono">{{tokens usage "prompt"}}</span>
118
+ </div>
119
+ <div class="token-cell">
120
+ <span class="token-label">Cached Tokens</span>
121
+ <span class="token-value text-mono">{{tokens usage "cached"}}</span>
122
+ </div>
123
+ <div class="token-cell">
124
+ <span class="token-label">Completion Tokens</span>
125
+ <span class="token-value text-mono">{{tokens usage "completion"}}</span>
126
+ </div>
127
+ </div>
128
+ {{/if}}
129
+
80
130
  <!-- Step Metrics -->
81
131
  <div class="step-meta">
82
132
  <div class="meta-row">
@@ -84,15 +134,14 @@
84
134
  <span class="meta-value text-mono">{{llm.name}}/{{llm.model}}</span>
85
135
  </div>
86
136
 
87
- {{#if usage}}
88
- <div class="meta-row">
89
- <span class="meta-label">Usage:</span>
90
- <span class="meta-value text-mono">{{usage.inputTokens}} (prompt) + {{usage.outputTokens}} (completion)</span>
91
- </div>
92
- {{/if}}
137
+ <div class="meta-row">
138
+ <span class="meta-label">Started:</span>
139
+ <span class="meta-value text-mono">{{formatTime meta.timestamp}}</span>
140
+ </div>
93
141
  </div>
94
142
 
95
- {{#if error}}
143
+
144
+ {{#if (eq type "step:error")}}
96
145
  <div class="error-box">
97
146
  <div class="error-header">
98
147
  <svg class="error-icon" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
@@ -167,6 +216,9 @@
167
216
  {{#if (eq type "ai:tool")}}
168
217
  <span class="action-type-badge badge-tool">TOOL</span>
169
218
  <span class="action-name text-mono">{{name}}</span>
219
+ {{#if (previewInput input)}}
220
+ <span class="action-preview text-mono">{{previewInput input}}</span>
221
+ {{/if}}
170
222
  {{else if (eq type "ai:fallback")}}
171
223
  <span class="action-type-badge badge-fallback">FALLBACK</span>
172
224
  <span class="action-name text-mono">{{llm.old.model}}</span>
@@ -177,6 +229,7 @@
177
229
  <div class="summary-right">
178
230
  {{#if (eq type "ai:fallback")}}
179
231
  <span class="badge badge-error">{{error.type}}</span>
232
+ <span class="time-tag">{{formatDuration meta.spent}}</span>
180
233
  {{else}}
181
234
  <span class="llm-tag text-muted">{{llm.name}}/{{llm.model}}</span>
182
235
  <span class="badge badge-{{meta.state}}">{{meta.state}}</span>
@@ -321,6 +374,7 @@
321
374
  </div>
322
375
  </div>
323
376
  </details>
377
+ {{/if}}
324
378
  {{/each}}
325
379
  </div>
326
380
  </main>
@@ -72,14 +72,25 @@ body {
72
72
 
73
73
  .session-info {
74
74
  display: flex;
75
- flex-direction: column;
76
- gap: 10px;
75
+ flex-wrap: wrap;
76
+ gap: 10px 20px;
77
77
  background: var(--card-bg);
78
78
  padding: 10px 20px;
79
79
  border-radius: 8px;
80
80
  border: 1px solid var(--border);
81
81
  }
82
82
 
83
+ .info-column {
84
+ display: flex;
85
+ flex-direction: column;
86
+ gap: 10px;
87
+ }
88
+
89
+ .info-column + .info-column {
90
+ padding-left: 20px;
91
+ border-left: 1px solid var(--border);
92
+ }
93
+
83
94
  .info-item {
84
95
  display: flex;
85
96
  gap: 10px;
@@ -200,6 +211,46 @@ details[open] > summary .expand-arrow {
200
211
  border-left: 4px solid var(--color-error);
201
212
  }
202
213
 
214
+ .snapshot-card.state-log {
215
+ border-left: 4px solid var(--color-info);
216
+ }
217
+
218
+ .snapshot-card.state-log.log-WARN {
219
+ border-left-color: var(--color-warning);
220
+ }
221
+
222
+ .snapshot-card.state-log.log-DEBUG {
223
+ border-left-color: var(--border);
224
+ }
225
+
226
+ .snapshot-card.state-log .summary-left {
227
+ min-width: 0;
228
+ flex: 1;
229
+ }
230
+
231
+ .log-preview {
232
+ font-size: 12px;
233
+ color: var(--text-muted);
234
+ white-space: nowrap;
235
+ overflow: hidden;
236
+ text-overflow: ellipsis;
237
+ min-width: 0;
238
+ }
239
+
240
+ .log-message {
241
+ margin: 0;
242
+ white-space: pre-wrap;
243
+ word-break: break-word;
244
+ }
245
+
246
+ .snapshot-summary {
247
+ font-family: ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, monospace;
248
+ }
249
+
250
+ .snapshot-summary .summary-right {
251
+ font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif;
252
+ }
253
+
203
254
  .snapshot-summary .step-title {
204
255
  font-size: 14px;
205
256
  font-weight: 600;
@@ -274,6 +325,21 @@ details[open] > summary .expand-arrow {
274
325
  color: var(--color-reasoning);
275
326
  }
276
327
 
328
+ .badge-log-INFO {
329
+ background: var(--color-info-bg);
330
+ color: var(--color-info);
331
+ }
332
+
333
+ .badge-log-WARN {
334
+ background: var(--color-warning-bg);
335
+ color: var(--color-warning);
336
+ }
337
+
338
+ .badge-log-DEBUG {
339
+ background: var(--border);
340
+ color: var(--text-muted);
341
+ }
342
+
277
343
  .badge-fallback {
278
344
  background: var(--color-warning-bg);
279
345
  color: var(--color-warning);
@@ -316,7 +382,37 @@ details[open] > summary .expand-arrow {
316
382
  padding: 10px 20px;
317
383
  border-radius: 6px;
318
384
  border: 1px solid rgba(255, 255, 255, 0.03);
319
- margin-bottom: 20px;
385
+ }
386
+
387
+ .tokens-grid {
388
+ display: grid;
389
+ grid-template-columns: repeat(auto-fit, minmax(120px, 1fr));
390
+ gap: 10px;
391
+ }
392
+
393
+ .token-cell {
394
+ display: flex;
395
+ flex-direction: column;
396
+ align-items: center;
397
+ text-align: center;
398
+ gap: 4px;
399
+ padding: 8px 12px;
400
+ border-radius: 6px;
401
+ border: 1px solid rgba(255, 255, 255, 0.03);
402
+ background: rgba(255, 255, 255, 0.02);
403
+ }
404
+
405
+ .token-label {
406
+ color: var(--text-muted);
407
+ font-size: 11px;
408
+ text-transform: uppercase;
409
+ letter-spacing: 0.05em;
410
+ }
411
+
412
+ .token-value {
413
+ color: var(--color-info);
414
+ font-size: 16px;
415
+ font-weight: 700;
320
416
  }
321
417
 
322
418
  .text-muted {
@@ -377,6 +473,29 @@ details[open] > summary .expand-arrow {
377
473
  flex-shrink: 0;
378
474
  }
379
475
 
476
+ .action-card.action-type-ai\:tool > summary {
477
+ flex-wrap: nowrap;
478
+ gap: 10px;
479
+ }
480
+
481
+ .action-card.action-type-ai\:tool .summary-left {
482
+ min-width: 0;
483
+ flex: 1;
484
+ }
485
+
486
+ .action-card.action-type-ai\:tool .summary-right {
487
+ flex-shrink: 0;
488
+ }
489
+
490
+ .action-preview {
491
+ font-size: 11px;
492
+ color: var(--text-muted);
493
+ white-space: nowrap;
494
+ overflow: hidden;
495
+ text-overflow: ellipsis;
496
+ min-width: 0;
497
+ }
498
+
380
499
  .action-content {
381
500
  padding: 10px 20px;
382
501
  border-top: 1px solid var(--border);
@@ -12,12 +12,14 @@ export * from './models/pipeline/steps/ai/errors';
12
12
  export * from './models/project';
13
13
  export * from './models/file';
14
14
  export * from './models/bash';
15
+ export * from './models/meta';
15
16
  export * from './models/content/factory';
16
17
  export * from './models/content/types';
17
18
  export * from './models/llm/providers/model';
18
19
  export * from './models/llm/tools/model';
19
20
  export * from './models/llm/factory';
20
21
  export * from './models/llm/router';
22
+ export * from './models/llm/hook';
21
23
  export * from './models/llm/mcp';
22
24
  export * as content from './models/content/kinds';
23
25
  export * as stdout from './models/pipeline/stdout/compiled';
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,SAAS,CAAC;AAEjB,OAAO,KAAK,SAAS,MAAM,wBAAwB,CAAC;AACpD,OAAO,KAAK,KAAK,MAAM,oBAAoB,CAAC;AAE5C,cAAc,2BAA2B,CAAC;AAC1C,cAAc,2BAA2B,CAAC;AAC1C,cAAc,0BAA0B,CAAC;AACzC,cAAc,0BAA0B,CAAC;AACzC,cAAc,0BAA0B,CAAC;AACzC,cAAc,yBAAyB,CAAC;AAExC,cAAc,oCAAoC,CAAC;AACnD,cAAc,mCAAmC,CAAC;AAElD,cAAc,kBAAkB,CAAC;AACjC,cAAc,eAAe,CAAC;AAC9B,cAAc,eAAe,CAAC;AAE9B,cAAc,0BAA0B,CAAC;AACzC,cAAc,wBAAwB,CAAC;AAEvC,cAAc,8BAA8B,CAAC;AAC7C,cAAc,0BAA0B,CAAC;AACzC,cAAc,sBAAsB,CAAC;AACrC,cAAc,qBAAqB,CAAC;AACpC,cAAc,kBAAkB,CAAC;AAEjC,OAAO,KAAK,OAAO,MAAM,wBAAwB,CAAC;AAClD,OAAO,KAAK,MAAM,MAAM,mCAAmC,CAAC;AAE5D,eAAO,MAAM,GAAG;;;CAGf,CAAC"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,SAAS,CAAC;AAEjB,OAAO,KAAK,SAAS,MAAM,wBAAwB,CAAC;AACpD,OAAO,KAAK,KAAK,MAAM,oBAAoB,CAAC;AAE5C,cAAc,2BAA2B,CAAC;AAC1C,cAAc,2BAA2B,CAAC;AAC1C,cAAc,0BAA0B,CAAC;AACzC,cAAc,0BAA0B,CAAC;AACzC,cAAc,0BAA0B,CAAC;AACzC,cAAc,yBAAyB,CAAC;AAExC,cAAc,oCAAoC,CAAC;AACnD,cAAc,mCAAmC,CAAC;AAElD,cAAc,kBAAkB,CAAC;AACjC,cAAc,eAAe,CAAC;AAC9B,cAAc,eAAe,CAAC;AAC9B,cAAc,eAAe,CAAC;AAE9B,cAAc,0BAA0B,CAAC;AACzC,cAAc,wBAAwB,CAAC;AAEvC,cAAc,8BAA8B,CAAC;AAC7C,cAAc,0BAA0B,CAAC;AACzC,cAAc,sBAAsB,CAAC;AACrC,cAAc,qBAAqB,CAAC;AACpC,cAAc,mBAAmB,CAAC;AAClC,cAAc,kBAAkB,CAAC;AAEjC,OAAO,KAAK,OAAO,MAAM,wBAAwB,CAAC;AAClD,OAAO,KAAK,MAAM,MAAM,mCAAmC,CAAC;AAE5D,eAAO,MAAM,GAAG;;;CAGf,CAAC"}
package/lib/src/index.js CHANGED
@@ -51,12 +51,14 @@ __exportStar(require("./models/pipeline/steps/ai/errors"), exports);
51
51
  __exportStar(require("./models/project"), exports);
52
52
  __exportStar(require("./models/file"), exports);
53
53
  __exportStar(require("./models/bash"), exports);
54
+ __exportStar(require("./models/meta"), exports);
54
55
  __exportStar(require("./models/content/factory"), exports);
55
56
  __exportStar(require("./models/content/types"), exports);
56
57
  __exportStar(require("./models/llm/providers/model"), exports);
57
58
  __exportStar(require("./models/llm/tools/model"), exports);
58
59
  __exportStar(require("./models/llm/factory"), exports);
59
60
  __exportStar(require("./models/llm/router"), exports);
61
+ __exportStar(require("./models/llm/hook"), exports);
60
62
  __exportStar(require("./models/llm/mcp"), exports);
61
63
  exports.content = __importStar(require("./models/content/kinds"));
62
64
  exports.stdout = __importStar(require("./models/pipeline/stdout/compiled"));
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/index.ts"],"names":[],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAAA,mBAAiB;AAEjB,kEAAoD;AACpD,0DAA4C;AAE5C,4DAA0C;AAC1C,4DAA0C;AAC1C,2DAAyC;AACzC,2DAAyC;AACzC,2DAAyC;AACzC,0DAAwC;AAExC,qEAAmD;AACnD,oEAAkD;AAElD,mDAAiC;AACjC,gDAA8B;AAC9B,gDAA8B;AAE9B,2DAAyC;AACzC,yDAAuC;AAEvC,+DAA6C;AAC7C,2DAAyC;AACzC,uDAAqC;AACrC,sDAAoC;AACpC,mDAAiC;AAEjC,kEAAkD;AAClD,4EAA4D;AAE/C,QAAA,GAAG,GAAG;IACjB,SAAS;IACT,KAAK;CACN,CAAC"}
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/index.ts"],"names":[],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAAA,mBAAiB;AAEjB,kEAAoD;AACpD,0DAA4C;AAE5C,4DAA0C;AAC1C,4DAA0C;AAC1C,2DAAyC;AACzC,2DAAyC;AACzC,2DAAyC;AACzC,0DAAwC;AAExC,qEAAmD;AACnD,oEAAkD;AAElD,mDAAiC;AACjC,gDAA8B;AAC9B,gDAA8B;AAC9B,gDAA8B;AAE9B,2DAAyC;AACzC,yDAAuC;AAEvC,+DAA6C;AAC7C,2DAAyC;AACzC,uDAAqC;AACrC,sDAAoC;AACpC,oDAAkC;AAClC,mDAAiC;AAEjC,kEAAkD;AAClD,4EAA4D;AAE/C,QAAA,GAAG,GAAG;IACjB,SAAS;IACT,KAAK;CACN,CAAC"}
@@ -3,6 +3,7 @@ export * from './project';
3
3
  export * from './content';
4
4
  export * from './file';
5
5
  export * from './bash';
6
+ export * from './meta';
6
7
  export * from './llm';
7
8
  export * from './vfs';
8
9
  export * from './rg';