@compilr-dev/agents 0.5.7 → 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
@@ -151,6 +151,10 @@ export type AgentEvent = {
151
151
  type: 'iteration_limit_extended';
152
152
  newMaxIterations: number;
153
153
  addedIterations: number;
154
+ } | {
155
+ type: 'model_changed';
156
+ previousModel: string;
157
+ newModel: string;
154
158
  } | {
155
159
  type: 'llm_retry';
156
160
  attempt: number;
@@ -1128,6 +1132,37 @@ export declare class Agent {
1128
1132
  * Check if pins are enabled
1129
1133
  */
1130
1134
  hasPins(): boolean;
1135
+ /**
1136
+ * Get the current model ID for this agent.
1137
+ *
1138
+ * @returns The model ID string (e.g., 'claude-sonnet-4-20250514')
1139
+ */
1140
+ getModel(): string;
1141
+ /**
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.
1150
+ *
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
1164
+ */
1165
+ setModel(modelId: string): void;
1131
1166
  /** @deprecated Use addPin() instead */
1132
1167
  addAnchor(input: AnchorInput): Anchor | undefined;
1133
1168
  /** @deprecated Use getPin() instead */
@@ -1632,11 +1667,38 @@ export declare class Agent {
1632
1667
  */
1633
1668
  private generateContextSummary;
1634
1669
  /**
1635
- * 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
+ * ```
1636
1695
  */
1637
1696
  registerTool(tool: Tool): this;
1638
1697
  /**
1639
- * 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)
1640
1702
  */
1641
1703
  registerTools(tools: Tool[]): this;
1642
1704
  /**
@@ -1648,14 +1710,62 @@ export declare class Agent {
1648
1710
  */
1649
1711
  isToolSilent(name: string): boolean;
1650
1712
  /**
1651
- * 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
+ * ```
1652
1744
  */
1653
1745
  run(userMessage: string | ContentBlock[], options?: RunOptions): Promise<AgentRunResult>;
1654
1746
  /**
1655
- * 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.
1752
+ *
1753
+ * @param userMessage - The user's message
1754
+ * @param options - Optional run configuration
1755
+ * @returns An async iterable of agent events
1656
1756
  *
1657
- * Yields AgentEvent objects as the agent executes, allowing
1658
- * real-time monitoring of the agentic loop.
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
+ * ```
1659
1769
  */
1660
1770
  stream(userMessage: string, options?: RunOptions): AsyncIterable<AgentEvent>;
1661
1771
  /**
package/dist/agent.js CHANGED
@@ -523,6 +523,47 @@ export class Agent {
523
523
  hasPins() {
524
524
  return this.pinManager !== undefined;
525
525
  }
526
+ // --- Model management -------------------------------------------------------
527
+ /**
528
+ * Get the current model ID for this agent.
529
+ *
530
+ * @returns The model ID string (e.g., 'claude-sonnet-4-20250514')
531
+ */
532
+ getModel() {
533
+ return this.provider.getModel();
534
+ }
535
+ /**
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.
544
+ *
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
558
+ */
559
+ setModel(modelId) {
560
+ if (!modelId || typeof modelId !== 'string') {
561
+ throw new Error('modelId must be a non-empty string');
562
+ }
563
+ const previousModel = this.provider.getModel();
564
+ this.provider.setModel(modelId);
565
+ this.onEvent?.({ type: 'model_changed', previousModel, newModel: modelId });
566
+ }
526
567
  // --- Deprecated aliases (use pin methods instead) --------------------------
527
568
  /** @deprecated Use addPin() instead */
528
569
  addAnchor(input) {
@@ -1497,14 +1538,41 @@ export class Agent {
1497
1538
  return summaryParts.join('\n');
1498
1539
  }
1499
1540
  /**
1500
- * 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
+ * ```
1501
1566
  */
1502
1567
  registerTool(tool) {
1503
1568
  this.toolRegistry.register(tool);
1504
1569
  return this;
1505
1570
  }
1506
1571
  /**
1507
- * 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)
1508
1576
  */
1509
1577
  registerTools(tools) {
1510
1578
  for (const tool of tools) {
@@ -1526,7 +1594,37 @@ export class Agent {
1526
1594
  return tool?.silent === true;
1527
1595
  }
1528
1596
  /**
1529
- * 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
+ * ```
1530
1628
  */
1531
1629
  async run(userMessage, options) {
1532
1630
  let maxIterations = options?.maxIterations ?? this.maxIterations;
@@ -1814,9 +1912,19 @@ export class Agent {
1814
1912
  durationMs: Date.now() - llmStartTime,
1815
1913
  });
1816
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
+ }
1817
1926
  // If no tool uses, we're done
1818
1927
  if (toolUses.length === 0) {
1819
- finalResponse = text;
1820
1928
  // Add final assistant response to history (only if non-empty)
1821
1929
  // Empty responses can occur after silent tools like 'suggest'
1822
1930
  if (text) {
@@ -2404,10 +2512,28 @@ export class Agent {
2404
2512
  return result;
2405
2513
  }
2406
2514
  /**
2407
- * 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.
2520
+ *
2521
+ * @param userMessage - The user's message
2522
+ * @param options - Optional run configuration
2523
+ * @returns An async iterable of agent events
2408
2524
  *
2409
- * Yields AgentEvent objects as the agent executes, allowing
2410
- * real-time monitoring of the agentic loop.
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
+ * ```
2411
2537
  */
2412
2538
  async *stream(userMessage, options) {
2413
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,18 +67,45 @@ 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";
74
99
  private readonly client;
75
- private readonly defaultModel;
100
+ private defaultModel;
76
101
  private readonly defaultMaxTokens;
77
102
  private readonly enablePromptCaching;
78
103
  private readonly enableTokenEfficientTools;
79
104
  private readonly enableExtendedContext;
80
105
  private readonly estimateTokensFn;
81
106
  constructor(config: ClaudeProviderConfig);
107
+ getModel(): string;
108
+ setModel(modelId: string): void;
82
109
  /**
83
110
  * Send messages and stream the response
84
111
  */
@@ -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';
@@ -46,6 +71,12 @@ export class ClaudeProvider {
46
71
  this.estimateTokensFn =
47
72
  config.estimateTokens ?? ((s) => Math.ceil(s.length / 4));
48
73
  }
74
+ getModel() {
75
+ return this.defaultModel;
76
+ }
77
+ setModel(modelId) {
78
+ this.defaultModel = modelId;
79
+ }
49
80
  /**
50
81
  * Send messages and stream the response
51
82
  */
@@ -50,10 +50,12 @@ export interface GeminiNativeProviderConfig {
50
50
  export declare class GeminiNativeProvider implements LLMProvider {
51
51
  readonly name = "gemini";
52
52
  private readonly client;
53
- private readonly defaultModel;
53
+ private defaultModel;
54
54
  private readonly defaultMaxTokens;
55
55
  private readonly estimateTokensFn;
56
56
  constructor(config: GeminiNativeProviderConfig);
57
+ getModel(): string;
58
+ setModel(modelId: string): void;
57
59
  /**
58
60
  * Send messages and stream the response
59
61
  */
@@ -45,6 +45,12 @@ export class GeminiNativeProvider {
45
45
  this.estimateTokensFn =
46
46
  config.estimateTokens ?? ((s) => Math.ceil(s.length / 4));
47
47
  }
48
+ getModel() {
49
+ return this.defaultModel;
50
+ }
51
+ setModel(modelId) {
52
+ this.defaultModel = modelId;
53
+ }
48
54
  /**
49
55
  * Send messages and stream the response
50
56
  */
@@ -78,7 +78,10 @@ export declare class MockProvider implements LLMProvider {
78
78
  private readonly throwOnEmpty;
79
79
  private callCount;
80
80
  private readonly callHistory;
81
+ private mockModel;
81
82
  constructor(config?: MockProviderConfig);
83
+ getModel(): string;
84
+ setModel(modelId: string): void;
82
85
  /**
83
86
  * Add a response (text string or structured response with tool calls)
84
87
  */
@@ -38,10 +38,17 @@ export class MockProvider {
38
38
  throwOnEmpty;
39
39
  callCount = 0;
40
40
  callHistory = [];
41
+ mockModel = 'mock-model';
41
42
  constructor(config = {}) {
42
43
  this.defaultDelay = config.defaultDelay ?? 0;
43
44
  this.throwOnEmpty = config.throwOnEmpty ?? true;
44
45
  }
46
+ getModel() {
47
+ return this.mockModel;
48
+ }
49
+ setModel(modelId) {
50
+ this.mockModel = modelId;
51
+ }
45
52
  /**
46
53
  * Add a response (text string or structured response with tool calls)
47
54
  */
@@ -121,11 +121,13 @@ export declare abstract class OpenAICompatibleProvider implements LLMProvider {
121
121
  */
122
122
  abstract readonly name: string;
123
123
  protected readonly baseUrl: string;
124
- protected readonly defaultModel: string;
124
+ protected defaultModel: string;
125
125
  protected readonly defaultMaxTokens: number;
126
126
  protected readonly timeout: number;
127
127
  protected readonly estimateTokensFn: (text: string) => number;
128
128
  constructor(config: OpenAICompatibleConfig);
129
+ getModel(): string;
130
+ setModel(modelId: string): void;
129
131
  /**
130
132
  * Get authentication headers for API requests
131
133
  * @returns Headers object with auth credentials
@@ -46,6 +46,12 @@ export class OpenAICompatibleProvider {
46
46
  this.estimateTokensFn =
47
47
  config.estimateTokens ?? ((s) => Math.ceil(s.length / 4));
48
48
  }
49
+ getModel() {
50
+ return this.defaultModel;
51
+ }
52
+ setModel(modelId) {
53
+ this.defaultModel = modelId;
54
+ }
49
55
  /**
50
56
  * Extract cache statistics from response headers.
51
57
  * Override in subclasses for providers that return cache stats in headers (e.g., Fireworks).
@@ -226,17 +226,41 @@ 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
  /**
239
252
  * Count tokens in messages (optional, provider-specific)
240
253
  */
241
254
  countTokens?(messages: Message[]): Promise<number>;
255
+ /**
256
+ * Get the current default model ID.
257
+ */
258
+ getModel(): string;
259
+ /**
260
+ * Change the default model for subsequent calls. Same provider only.
261
+ * Takes effect on the next chat() call, not mid-stream.
262
+ *
263
+ * @param modelId - The new model ID (e.g., 'claude-opus-4-20250514')
264
+ */
265
+ setModel(modelId: string): void;
242
266
  }
@@ -32,6 +32,8 @@ export declare class RateLimitedProvider implements LLMProvider {
32
32
  private readonly rateLimiter;
33
33
  private readonly retryConfig;
34
34
  constructor(provider: LLMProvider, config?: RateLimitRetryConfig);
35
+ getModel(): string;
36
+ setModel(modelId: string): void;
35
37
  /**
36
38
  * Get the rate limiter instance for statistics
37
39
  */
@@ -38,6 +38,12 @@ export class RateLimitedProvider {
38
38
  this.rateLimiter = config.rateLimit ? createRateLimiter(config.rateLimit) : createRateLimiter();
39
39
  this.retryConfig = config.retry ?? {};
40
40
  }
41
+ getModel() {
42
+ return this.provider.getModel();
43
+ }
44
+ setModel(modelId) {
45
+ this.provider.setModel(modelId);
46
+ }
41
47
  /**
42
48
  * Get the rate limiter instance for statistics
43
49
  */
@@ -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.7",
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"