@compilr-dev/agents 0.5.8 → 0.5.9

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/README.md CHANGED
@@ -14,6 +14,9 @@
14
14
 
15
15
  [![npm version](https://img.shields.io/npm/v/@compilr-dev/agents.svg)](https://www.npmjs.com/package/@compilr-dev/agents)
16
16
  [![License: FSL-1.1-MIT](https://img.shields.io/badge/License-FSL--1.1--MIT-blue.svg)](https://fsl.software/)
17
+ [![API Docs](https://img.shields.io/badge/API_Docs-GitHub_Pages-blue)](https://compilr-dev.github.io/agents/)
18
+
19
+ **[API Reference](https://compilr-dev.github.io/agents/)** | **[llms.txt](https://compilr.dev/llms.txt)** (for AI agents)
17
20
 
18
21
  > [!WARNING]
19
22
  > This package is in beta. APIs may change between minor versions.
package/dist/agent.d.ts CHANGED
@@ -1134,13 +1134,33 @@ export declare class Agent {
1134
1134
  hasPins(): boolean;
1135
1135
  /**
1136
1136
  * Get the current model ID for this agent.
1137
+ *
1138
+ * @returns The model ID string (e.g., 'claude-sonnet-4-20250514')
1137
1139
  */
1138
1140
  getModel(): string;
1139
1141
  /**
1140
- * Change the model for subsequent LLM calls. Same provider only.
1141
- * Takes effect on the next chat() call, not mid-stream.
1142
+ * Change the model for subsequent LLM calls (same provider only).
1143
+ *
1144
+ * Takes effect on the next `run()` or `stream()` call — never interrupts
1145
+ * a running turn. Conversation history is preserved (it's provider-agnostic).
1146
+ * Emits a `model_changed` event.
1147
+ *
1148
+ * Use this to switch between models within the same provider, e.g.,
1149
+ * Claude Sonnet → Claude Opus for a harder task, then back.
1142
1150
  *
1143
1151
  * @param modelId - The new model ID (e.g., 'claude-opus-4-20250514')
1152
+ * @throws If modelId is empty or not a string
1153
+ *
1154
+ * @example
1155
+ * ```typescript
1156
+ * console.log(agent.getModel()); // 'claude-sonnet-4-20250514'
1157
+ * agent.setModel('claude-opus-4-20250514');
1158
+ * // Next run() uses Opus
1159
+ * const result = await agent.run('Solve this complex problem');
1160
+ * agent.setModel('claude-sonnet-4-20250514'); // Switch back
1161
+ * ```
1162
+ *
1163
+ * @since 0.5.8
1144
1164
  */
1145
1165
  setModel(modelId: string): void;
1146
1166
  /** @deprecated Use addPin() instead */
@@ -1647,11 +1667,38 @@ export declare class Agent {
1647
1667
  */
1648
1668
  private generateContextSummary;
1649
1669
  /**
1650
- * Register a tool for the agent to use
1670
+ * Register a tool that the agent can call during conversations.
1671
+ *
1672
+ * Tools are functions the LLM can invoke to perform actions like reading
1673
+ * files, running commands, or querying APIs. The LLM sees the tool's name,
1674
+ * description, and parameter schema, then decides when to call it.
1675
+ *
1676
+ * @param tool - Tool definition created with `defineTool()`
1677
+ * @returns The agent instance (for chaining)
1678
+ *
1679
+ * @example
1680
+ * ```typescript
1681
+ * agent.registerTool(defineTool({
1682
+ * name: 'get_weather',
1683
+ * description: 'Get current weather for a city',
1684
+ * parameters: {
1685
+ * type: 'object',
1686
+ * properties: { city: { type: 'string' } },
1687
+ * required: ['city'],
1688
+ * },
1689
+ * execute: async ({ city }) => {
1690
+ * const data = await fetchWeather(city);
1691
+ * return { content: JSON.stringify(data) };
1692
+ * },
1693
+ * }));
1694
+ * ```
1651
1695
  */
1652
1696
  registerTool(tool: Tool): this;
1653
1697
  /**
1654
- * Register multiple tools at once
1698
+ * Register multiple tools at once.
1699
+ *
1700
+ * @param tools - Array of tool definitions
1701
+ * @returns The agent instance (for chaining)
1655
1702
  */
1656
1703
  registerTools(tools: Tool[]): this;
1657
1704
  /**
@@ -1663,14 +1710,62 @@ export declare class Agent {
1663
1710
  */
1664
1711
  isToolSilent(name: string): boolean;
1665
1712
  /**
1666
- * Run the agent with a user message
1713
+ * Run the agent with a user message and return the result.
1714
+ *
1715
+ * This is the main entry point for agent interaction. The agent will:
1716
+ * 1. Add the user message to conversation history
1717
+ * 2. Send the conversation to the LLM
1718
+ * 3. Execute any tool calls the LLM requests
1719
+ * 4. Repeat steps 2-3 until the LLM responds with text (no tool calls)
1720
+ * 5. Return the final text response and metadata
1721
+ *
1722
+ * Events are emitted throughout the process via the `onEvent` callback
1723
+ * configured at construction time.
1724
+ *
1725
+ * @param userMessage - The user's message (string or content blocks for images)
1726
+ * @param options - Optional run configuration (max iterations, abort signal, etc.)
1727
+ * @returns The agent's response, tool call history, and context stats
1728
+ *
1729
+ * @example
1730
+ * ```typescript
1731
+ * const result = await agent.run('What files are in this directory?');
1732
+ * console.log(result.response);
1733
+ * console.log(`Used ${result.toolCalls.length} tool calls`);
1734
+ * ```
1735
+ *
1736
+ * @example
1737
+ * ```typescript
1738
+ * // With abort signal
1739
+ * const controller = new AbortController();
1740
+ * const result = await agent.run('Refactor this file', {
1741
+ * signal: controller.signal,
1742
+ * });
1743
+ * ```
1667
1744
  */
1668
1745
  run(userMessage: string | ContentBlock[], options?: RunOptions): Promise<AgentRunResult>;
1669
1746
  /**
1670
- * Stream the agent's response with full tool use support
1747
+ * Stream the agent's response as events.
1748
+ *
1749
+ * Yields `AgentEvent` objects in real time as the agent thinks, calls tools,
1750
+ * and generates text. Use this for building interactive UIs that show
1751
+ * progress as it happens.
1671
1752
  *
1672
- * Yields AgentEvent objects as the agent executes, allowing
1673
- * real-time monitoring of the agentic loop.
1753
+ * @param userMessage - The user's message
1754
+ * @param options - Optional run configuration
1755
+ * @returns An async iterable of agent events
1756
+ *
1757
+ * @example
1758
+ * ```typescript
1759
+ * for await (const event of agent.stream('Explain this code')) {
1760
+ * if (event.type === 'llm_chunk') {
1761
+ * process.stdout.write(event.chunk.text ?? '');
1762
+ * } else if (event.type === 'tool_start') {
1763
+ * console.log(`\nCalling tool: ${event.name}`);
1764
+ * } else if (event.type === 'done') {
1765
+ * console.log('\n\nDone!');
1766
+ * }
1767
+ * }
1768
+ * ```
1674
1769
  */
1675
1770
  stream(userMessage: string, options?: RunOptions): AsyncIterable<AgentEvent>;
1676
1771
  /**
package/dist/agent.js CHANGED
@@ -526,15 +526,35 @@ export class Agent {
526
526
  // --- Model management -------------------------------------------------------
527
527
  /**
528
528
  * Get the current model ID for this agent.
529
+ *
530
+ * @returns The model ID string (e.g., 'claude-sonnet-4-20250514')
529
531
  */
530
532
  getModel() {
531
533
  return this.provider.getModel();
532
534
  }
533
535
  /**
534
- * Change the model for subsequent LLM calls. Same provider only.
535
- * Takes effect on the next chat() call, not mid-stream.
536
+ * Change the model for subsequent LLM calls (same provider only).
537
+ *
538
+ * Takes effect on the next `run()` or `stream()` call — never interrupts
539
+ * a running turn. Conversation history is preserved (it's provider-agnostic).
540
+ * Emits a `model_changed` event.
541
+ *
542
+ * Use this to switch between models within the same provider, e.g.,
543
+ * Claude Sonnet → Claude Opus for a harder task, then back.
536
544
  *
537
545
  * @param modelId - The new model ID (e.g., 'claude-opus-4-20250514')
546
+ * @throws If modelId is empty or not a string
547
+ *
548
+ * @example
549
+ * ```typescript
550
+ * console.log(agent.getModel()); // 'claude-sonnet-4-20250514'
551
+ * agent.setModel('claude-opus-4-20250514');
552
+ * // Next run() uses Opus
553
+ * const result = await agent.run('Solve this complex problem');
554
+ * agent.setModel('claude-sonnet-4-20250514'); // Switch back
555
+ * ```
556
+ *
557
+ * @since 0.5.8
538
558
  */
539
559
  setModel(modelId) {
540
560
  if (!modelId || typeof modelId !== 'string') {
@@ -1518,14 +1538,41 @@ export class Agent {
1518
1538
  return summaryParts.join('\n');
1519
1539
  }
1520
1540
  /**
1521
- * Register a tool for the agent to use
1541
+ * Register a tool that the agent can call during conversations.
1542
+ *
1543
+ * Tools are functions the LLM can invoke to perform actions like reading
1544
+ * files, running commands, or querying APIs. The LLM sees the tool's name,
1545
+ * description, and parameter schema, then decides when to call it.
1546
+ *
1547
+ * @param tool - Tool definition created with `defineTool()`
1548
+ * @returns The agent instance (for chaining)
1549
+ *
1550
+ * @example
1551
+ * ```typescript
1552
+ * agent.registerTool(defineTool({
1553
+ * name: 'get_weather',
1554
+ * description: 'Get current weather for a city',
1555
+ * parameters: {
1556
+ * type: 'object',
1557
+ * properties: { city: { type: 'string' } },
1558
+ * required: ['city'],
1559
+ * },
1560
+ * execute: async ({ city }) => {
1561
+ * const data = await fetchWeather(city);
1562
+ * return { content: JSON.stringify(data) };
1563
+ * },
1564
+ * }));
1565
+ * ```
1522
1566
  */
1523
1567
  registerTool(tool) {
1524
1568
  this.toolRegistry.register(tool);
1525
1569
  return this;
1526
1570
  }
1527
1571
  /**
1528
- * Register multiple tools at once
1572
+ * Register multiple tools at once.
1573
+ *
1574
+ * @param tools - Array of tool definitions
1575
+ * @returns The agent instance (for chaining)
1529
1576
  */
1530
1577
  registerTools(tools) {
1531
1578
  for (const tool of tools) {
@@ -1547,7 +1594,37 @@ export class Agent {
1547
1594
  return tool?.silent === true;
1548
1595
  }
1549
1596
  /**
1550
- * Run the agent with a user message
1597
+ * Run the agent with a user message and return the result.
1598
+ *
1599
+ * This is the main entry point for agent interaction. The agent will:
1600
+ * 1. Add the user message to conversation history
1601
+ * 2. Send the conversation to the LLM
1602
+ * 3. Execute any tool calls the LLM requests
1603
+ * 4. Repeat steps 2-3 until the LLM responds with text (no tool calls)
1604
+ * 5. Return the final text response and metadata
1605
+ *
1606
+ * Events are emitted throughout the process via the `onEvent` callback
1607
+ * configured at construction time.
1608
+ *
1609
+ * @param userMessage - The user's message (string or content blocks for images)
1610
+ * @param options - Optional run configuration (max iterations, abort signal, etc.)
1611
+ * @returns The agent's response, tool call history, and context stats
1612
+ *
1613
+ * @example
1614
+ * ```typescript
1615
+ * const result = await agent.run('What files are in this directory?');
1616
+ * console.log(result.response);
1617
+ * console.log(`Used ${result.toolCalls.length} tool calls`);
1618
+ * ```
1619
+ *
1620
+ * @example
1621
+ * ```typescript
1622
+ * // With abort signal
1623
+ * const controller = new AbortController();
1624
+ * const result = await agent.run('Refactor this file', {
1625
+ * signal: controller.signal,
1626
+ * });
1627
+ * ```
1551
1628
  */
1552
1629
  async run(userMessage, options) {
1553
1630
  let maxIterations = options?.maxIterations ?? this.maxIterations;
@@ -1835,9 +1912,19 @@ export class Agent {
1835
1912
  durationMs: Date.now() - llmStartTime,
1836
1913
  });
1837
1914
  }
1915
+ // Accumulate every iteration's text into finalResponse, separated by
1916
+ // blank lines. The previous code only assigned finalResponse in the
1917
+ // terminal iteration (no tool uses), which silently dropped any
1918
+ // text the agent produced BEFORE intermediate tool calls. Pattern:
1919
+ // iter 1: "Running test 1..." → tool t1 ← text was lost
1920
+ // iter 2: "Now test 2..." → tool t2 ← text was lost
1921
+ // iter 3: "All done." → no tool ← only this survived
1922
+ // Now every text segment is preserved in finalResponse.
1923
+ if (text) {
1924
+ finalResponse = finalResponse ? finalResponse + '\n\n' + text : text;
1925
+ }
1838
1926
  // If no tool uses, we're done
1839
1927
  if (toolUses.length === 0) {
1840
- finalResponse = text;
1841
1928
  // Add final assistant response to history (only if non-empty)
1842
1929
  // Empty responses can occur after silent tools like 'suggest'
1843
1930
  if (text) {
@@ -2425,10 +2512,28 @@ export class Agent {
2425
2512
  return result;
2426
2513
  }
2427
2514
  /**
2428
- * Stream the agent's response with full tool use support
2515
+ * Stream the agent's response as events.
2516
+ *
2517
+ * Yields `AgentEvent` objects in real time as the agent thinks, calls tools,
2518
+ * and generates text. Use this for building interactive UIs that show
2519
+ * progress as it happens.
2429
2520
  *
2430
- * Yields AgentEvent objects as the agent executes, allowing
2431
- * real-time monitoring of the agentic loop.
2521
+ * @param userMessage - The user's message
2522
+ * @param options - Optional run configuration
2523
+ * @returns An async iterable of agent events
2524
+ *
2525
+ * @example
2526
+ * ```typescript
2527
+ * for await (const event of agent.stream('Explain this code')) {
2528
+ * if (event.type === 'llm_chunk') {
2529
+ * process.stdout.write(event.chunk.text ?? '');
2530
+ * } else if (event.type === 'tool_start') {
2531
+ * console.log(`\nCalling tool: ${event.name}`);
2532
+ * } else if (event.type === 'done') {
2533
+ * console.log('\n\nDone!');
2534
+ * }
2535
+ * }
2536
+ * ```
2432
2537
  */
2433
2538
  async *stream(userMessage, options) {
2434
2539
  // Use a simple queue-based approach
@@ -52,7 +52,29 @@ export interface ContextManagerOptions {
52
52
  fileTracker?: FileAccessTracker;
53
53
  }
54
54
  /**
55
- * ContextManager tracks and manages context window usage
55
+ * Manages the agent's context window — tracks token usage, triggers
56
+ * compaction when the conversation grows too large, and ensures the
57
+ * agent never exceeds the model's context limit.
58
+ *
59
+ * The context window is divided into budgets:
60
+ * - **System prompt** (~15%) — always present, never compacted
61
+ * - **Anchors/pins** (~10%) — critical info that survives compaction
62
+ * - **Conversation history** (~75%) — compacted when budget exceeded
63
+ *
64
+ * When conversation history exceeds its budget, the manager triggers
65
+ * smart windowing (3-zone compaction): recent messages preserved,
66
+ * middle messages summarized, old messages dropped.
67
+ *
68
+ * @example
69
+ * ```typescript
70
+ * const agent = new Agent({
71
+ * provider,
72
+ * contextManager: new ContextManager({
73
+ * maxTokens: 100000,
74
+ * budgets: { system: 0.15, anchors: 0.10, conversation: 0.75 },
75
+ * }),
76
+ * });
77
+ * ```
56
78
  */
57
79
  export declare class ContextManager {
58
80
  private readonly provider;
@@ -62,7 +62,29 @@ export const DEFAULT_CONTEXT_CONFIG = {
62
62
  },
63
63
  };
64
64
  /**
65
- * ContextManager tracks and manages context window usage
65
+ * Manages the agent's context window — tracks token usage, triggers
66
+ * compaction when the conversation grows too large, and ensures the
67
+ * agent never exceeds the model's context limit.
68
+ *
69
+ * The context window is divided into budgets:
70
+ * - **System prompt** (~15%) — always present, never compacted
71
+ * - **Anchors/pins** (~10%) — critical info that survives compaction
72
+ * - **Conversation history** (~75%) — compacted when budget exceeded
73
+ *
74
+ * When conversation history exceeds its budget, the manager triggers
75
+ * smart windowing (3-zone compaction): recent messages preserved,
76
+ * middle messages summarized, old messages dropped.
77
+ *
78
+ * @example
79
+ * ```typescript
80
+ * const agent = new Agent({
81
+ * provider,
82
+ * contextManager: new ContextManager({
83
+ * maxTokens: 100000,
84
+ * budgets: { system: 0.15, anchors: 0.10, conversation: 0.75 },
85
+ * }),
86
+ * });
87
+ * ```
66
88
  */
67
89
  export class ContextManager {
68
90
  provider;
@@ -67,7 +67,32 @@ export interface ClaudeProviderConfig {
67
67
  enableExtendedContext?: boolean;
68
68
  }
69
69
  /**
70
- * ClaudeProvider implements LLMProvider for Anthropic's Claude API
70
+ * LLM provider for Anthropic's Claude models (Opus, Sonnet, Haiku).
71
+ *
72
+ * Supports streaming, tool use, prompt caching, extended context (1M tokens),
73
+ * and token-efficient tool schemas.
74
+ *
75
+ * @example
76
+ * ```typescript
77
+ * const provider = new ClaudeProvider({
78
+ * apiKey: process.env.ANTHROPIC_API_KEY,
79
+ * model: 'claude-sonnet-4-20250514',
80
+ * });
81
+ *
82
+ * const agent = new Agent({
83
+ * provider,
84
+ * systemPrompt: 'You are a helpful assistant.',
85
+ * });
86
+ * ```
87
+ *
88
+ * @example
89
+ * ```typescript
90
+ * // Using the factory function
91
+ * const provider = createClaudeProvider({
92
+ * apiKey: 'sk-ant-...',
93
+ * enableExtendedContext: true, // 1M token context
94
+ * });
95
+ * ```
71
96
  */
72
97
  export declare class ClaudeProvider implements LLMProvider {
73
98
  readonly name = "claude";
@@ -22,7 +22,32 @@ const DEFAULT_MODEL = 'claude-sonnet-4-6';
22
22
  */
23
23
  const DEFAULT_MAX_TOKENS = 4096;
24
24
  /**
25
- * ClaudeProvider implements LLMProvider for Anthropic's Claude API
25
+ * LLM provider for Anthropic's Claude models (Opus, Sonnet, Haiku).
26
+ *
27
+ * Supports streaming, tool use, prompt caching, extended context (1M tokens),
28
+ * and token-efficient tool schemas.
29
+ *
30
+ * @example
31
+ * ```typescript
32
+ * const provider = new ClaudeProvider({
33
+ * apiKey: process.env.ANTHROPIC_API_KEY,
34
+ * model: 'claude-sonnet-4-20250514',
35
+ * });
36
+ *
37
+ * const agent = new Agent({
38
+ * provider,
39
+ * systemPrompt: 'You are a helpful assistant.',
40
+ * });
41
+ * ```
42
+ *
43
+ * @example
44
+ * ```typescript
45
+ * // Using the factory function
46
+ * const provider = createClaudeProvider({
47
+ * apiKey: 'sk-ant-...',
48
+ * enableExtendedContext: true, // 1M token context
49
+ * });
50
+ * ```
26
51
  */
27
52
  export class ClaudeProvider {
28
53
  name = 'claude';
@@ -226,13 +226,26 @@ export interface ToolResult {
226
226
  /**
227
227
  * LLM Provider interface - all providers must implement this
228
228
  */
229
+ /**
230
+ * Interface for LLM providers. Implement this to add support for a new AI model.
231
+ *
232
+ * Built-in providers: `ClaudeProvider`, `OpenAIProvider`, `GeminiProvider`,
233
+ * `OllamaProvider`, `TogetherProvider`, `GroqProvider`, `FireworksProvider`,
234
+ * `PerplexityProvider`, `OpenRouterProvider`.
235
+ *
236
+ * For OpenAI-compatible APIs, extend `OpenAICompatibleProvider` instead of
237
+ * implementing this interface directly.
238
+ */
229
239
  export interface LLMProvider {
230
240
  /**
231
- * Provider name (e.g., 'claude', 'openai', 'gemini')
241
+ * Provider identifier (e.g., 'claude', 'openai', 'gemini')
232
242
  */
233
243
  readonly name: string;
234
244
  /**
235
- * Send messages and get a streaming response
245
+ * Send messages to the LLM and stream the response.
246
+ *
247
+ * Yields `StreamChunk` objects containing text fragments, tool calls,
248
+ * usage stats, and other provider-specific data.
236
249
  */
237
250
  chat(messages: Message[], options?: ChatOptions): AsyncIterable<StreamChunk>;
238
251
  /**
@@ -73,7 +73,36 @@ export interface ToolExecutionContext {
73
73
  */
74
74
  export type ToolHandler<T = object> = (input: T, context?: ToolExecutionContext) => Promise<ToolExecutionResult>;
75
75
  /**
76
- * Tool implementation - combines definition with handler
76
+ * A tool that the agent can call during conversations.
77
+ *
78
+ * Tools are the primary way agents interact with the outside world — reading
79
+ * files, running commands, querying APIs, etc. Each tool has a definition
80
+ * (name, description, parameters) that the LLM sees, and an execute function
81
+ * that runs when the LLM decides to call it.
82
+ *
83
+ * Use `defineTool()` to create tools with type-safe parameters.
84
+ *
85
+ * @typeParam T - The type of the tool's input parameters
86
+ *
87
+ * @example
88
+ * ```typescript
89
+ * const readFileTool: Tool<{ path: string }> = {
90
+ * definition: {
91
+ * name: 'read_file',
92
+ * description: 'Read the contents of a file',
93
+ * parameters: {
94
+ * type: 'object',
95
+ * properties: { path: { type: 'string', description: 'File path' } },
96
+ * required: ['path'],
97
+ * },
98
+ * },
99
+ * execute: async ({ path }) => {
100
+ * const content = await fs.readFile(path, 'utf-8');
101
+ * return { content };
102
+ * },
103
+ * readonly: true, // Safe for parallel execution
104
+ * };
105
+ * ```
77
106
  */
78
107
  export interface Tool<T = object> {
79
108
  definition: ToolDefinition;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@compilr-dev/agents",
3
- "version": "0.5.8",
3
+ "version": "0.5.9",
4
4
  "description": "Lightweight multi-LLM agent library for building CLI AI assistants",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",
@@ -28,7 +28,11 @@
28
28
  "test:watch": "vitest",
29
29
  "test:coverage": "vitest run --coverage",
30
30
  "prepublishOnly": "npm run clean && npm run lint && npm run test && npm run build",
31
- "typecheck": "tsc --noEmit"
31
+ "typecheck": "tsc --noEmit",
32
+ "docs": "typedoc && node scripts/inject-frontmatter.mjs && typedoc --options typedoc.llms.json && mv docs/llms/README.md docs/llms.txt",
33
+ "docs:api": "typedoc",
34
+ "docs:llms": "typedoc --options typedoc.llms.json",
35
+ "docs:watch": "typedoc --watch"
32
36
  },
33
37
  "repository": {
34
38
  "type": "git",
@@ -76,6 +80,8 @@
76
80
  "dotenv": "^17.2.3",
77
81
  "eslint": "^9.39.1",
78
82
  "prettier": "^3.7.1",
83
+ "typedoc": "^0.28.19",
84
+ "typedoc-plugin-markdown": "^4.11.0",
79
85
  "typescript": "^5.3.0",
80
86
  "typescript-eslint": "^8.48.0",
81
87
  "vitest": "^4.0.18"