@compilr-dev/agents 0.0.1

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 (160) hide show
  1. package/README.md +1277 -0
  2. package/dist/agent.d.ts +1272 -0
  3. package/dist/agent.js +1912 -0
  4. package/dist/anchors/builtin.d.ts +24 -0
  5. package/dist/anchors/builtin.js +61 -0
  6. package/dist/anchors/index.d.ts +6 -0
  7. package/dist/anchors/index.js +5 -0
  8. package/dist/anchors/manager.d.ts +115 -0
  9. package/dist/anchors/manager.js +412 -0
  10. package/dist/anchors/types.d.ts +168 -0
  11. package/dist/anchors/types.js +10 -0
  12. package/dist/context/index.d.ts +12 -0
  13. package/dist/context/index.js +10 -0
  14. package/dist/context/manager.d.ts +224 -0
  15. package/dist/context/manager.js +770 -0
  16. package/dist/context/types.d.ts +377 -0
  17. package/dist/context/types.js +7 -0
  18. package/dist/costs/index.d.ts +8 -0
  19. package/dist/costs/index.js +7 -0
  20. package/dist/costs/tracker.d.ts +121 -0
  21. package/dist/costs/tracker.js +295 -0
  22. package/dist/costs/types.d.ts +157 -0
  23. package/dist/costs/types.js +8 -0
  24. package/dist/errors.d.ts +178 -0
  25. package/dist/errors.js +249 -0
  26. package/dist/guardrails/builtin.d.ts +27 -0
  27. package/dist/guardrails/builtin.js +223 -0
  28. package/dist/guardrails/index.d.ts +6 -0
  29. package/dist/guardrails/index.js +5 -0
  30. package/dist/guardrails/manager.d.ts +117 -0
  31. package/dist/guardrails/manager.js +288 -0
  32. package/dist/guardrails/types.d.ts +159 -0
  33. package/dist/guardrails/types.js +7 -0
  34. package/dist/hooks/index.d.ts +31 -0
  35. package/dist/hooks/index.js +29 -0
  36. package/dist/hooks/manager.d.ts +147 -0
  37. package/dist/hooks/manager.js +600 -0
  38. package/dist/hooks/types.d.ts +368 -0
  39. package/dist/hooks/types.js +12 -0
  40. package/dist/index.d.ts +45 -0
  41. package/dist/index.js +73 -0
  42. package/dist/mcp/client.d.ts +93 -0
  43. package/dist/mcp/client.js +287 -0
  44. package/dist/mcp/errors.d.ts +60 -0
  45. package/dist/mcp/errors.js +78 -0
  46. package/dist/mcp/index.d.ts +43 -0
  47. package/dist/mcp/index.js +45 -0
  48. package/dist/mcp/manager.d.ts +120 -0
  49. package/dist/mcp/manager.js +276 -0
  50. package/dist/mcp/tools.d.ts +54 -0
  51. package/dist/mcp/tools.js +99 -0
  52. package/dist/mcp/types.d.ts +150 -0
  53. package/dist/mcp/types.js +40 -0
  54. package/dist/memory/index.d.ts +8 -0
  55. package/dist/memory/index.js +7 -0
  56. package/dist/memory/loader.d.ts +114 -0
  57. package/dist/memory/loader.js +463 -0
  58. package/dist/memory/types.d.ts +182 -0
  59. package/dist/memory/types.js +8 -0
  60. package/dist/messages/index.d.ts +82 -0
  61. package/dist/messages/index.js +155 -0
  62. package/dist/permissions/index.d.ts +5 -0
  63. package/dist/permissions/index.js +4 -0
  64. package/dist/permissions/manager.d.ts +125 -0
  65. package/dist/permissions/manager.js +379 -0
  66. package/dist/permissions/types.d.ts +162 -0
  67. package/dist/permissions/types.js +7 -0
  68. package/dist/providers/claude.d.ts +90 -0
  69. package/dist/providers/claude.js +348 -0
  70. package/dist/providers/index.d.ts +8 -0
  71. package/dist/providers/index.js +11 -0
  72. package/dist/providers/mock.d.ts +133 -0
  73. package/dist/providers/mock.js +204 -0
  74. package/dist/providers/types.d.ts +168 -0
  75. package/dist/providers/types.js +4 -0
  76. package/dist/rate-limit/index.d.ts +45 -0
  77. package/dist/rate-limit/index.js +47 -0
  78. package/dist/rate-limit/limiter.d.ts +104 -0
  79. package/dist/rate-limit/limiter.js +326 -0
  80. package/dist/rate-limit/provider-wrapper.d.ts +112 -0
  81. package/dist/rate-limit/provider-wrapper.js +201 -0
  82. package/dist/rate-limit/retry.d.ts +108 -0
  83. package/dist/rate-limit/retry.js +287 -0
  84. package/dist/rate-limit/types.d.ts +181 -0
  85. package/dist/rate-limit/types.js +22 -0
  86. package/dist/rehearsal/file-analyzer.d.ts +22 -0
  87. package/dist/rehearsal/file-analyzer.js +351 -0
  88. package/dist/rehearsal/git-analyzer.d.ts +22 -0
  89. package/dist/rehearsal/git-analyzer.js +472 -0
  90. package/dist/rehearsal/index.d.ts +35 -0
  91. package/dist/rehearsal/index.js +36 -0
  92. package/dist/rehearsal/manager.d.ts +100 -0
  93. package/dist/rehearsal/manager.js +290 -0
  94. package/dist/rehearsal/types.d.ts +235 -0
  95. package/dist/rehearsal/types.js +8 -0
  96. package/dist/skills/index.d.ts +160 -0
  97. package/dist/skills/index.js +282 -0
  98. package/dist/state/agent-state.d.ts +41 -0
  99. package/dist/state/agent-state.js +88 -0
  100. package/dist/state/checkpointer.d.ts +110 -0
  101. package/dist/state/checkpointer.js +362 -0
  102. package/dist/state/errors.d.ts +66 -0
  103. package/dist/state/errors.js +88 -0
  104. package/dist/state/index.d.ts +35 -0
  105. package/dist/state/index.js +37 -0
  106. package/dist/state/serializer.d.ts +55 -0
  107. package/dist/state/serializer.js +172 -0
  108. package/dist/state/types.d.ts +312 -0
  109. package/dist/state/types.js +14 -0
  110. package/dist/tools/builtin/bash-output.d.ts +61 -0
  111. package/dist/tools/builtin/bash-output.js +90 -0
  112. package/dist/tools/builtin/bash.d.ts +150 -0
  113. package/dist/tools/builtin/bash.js +354 -0
  114. package/dist/tools/builtin/edit.d.ts +50 -0
  115. package/dist/tools/builtin/edit.js +215 -0
  116. package/dist/tools/builtin/glob.d.ts +62 -0
  117. package/dist/tools/builtin/glob.js +244 -0
  118. package/dist/tools/builtin/grep.d.ts +74 -0
  119. package/dist/tools/builtin/grep.js +363 -0
  120. package/dist/tools/builtin/index.d.ts +44 -0
  121. package/dist/tools/builtin/index.js +69 -0
  122. package/dist/tools/builtin/kill-shell.d.ts +44 -0
  123. package/dist/tools/builtin/kill-shell.js +80 -0
  124. package/dist/tools/builtin/read-file.d.ts +57 -0
  125. package/dist/tools/builtin/read-file.js +184 -0
  126. package/dist/tools/builtin/shell-manager.d.ts +176 -0
  127. package/dist/tools/builtin/shell-manager.js +337 -0
  128. package/dist/tools/builtin/task.d.ts +202 -0
  129. package/dist/tools/builtin/task.js +350 -0
  130. package/dist/tools/builtin/todo.d.ts +207 -0
  131. package/dist/tools/builtin/todo.js +453 -0
  132. package/dist/tools/builtin/utils.d.ts +27 -0
  133. package/dist/tools/builtin/utils.js +70 -0
  134. package/dist/tools/builtin/web-fetch.d.ts +96 -0
  135. package/dist/tools/builtin/web-fetch.js +290 -0
  136. package/dist/tools/builtin/write-file.d.ts +54 -0
  137. package/dist/tools/builtin/write-file.js +147 -0
  138. package/dist/tools/define.d.ts +60 -0
  139. package/dist/tools/define.js +65 -0
  140. package/dist/tools/index.d.ts +10 -0
  141. package/dist/tools/index.js +37 -0
  142. package/dist/tools/registry.d.ts +79 -0
  143. package/dist/tools/registry.js +151 -0
  144. package/dist/tools/types.d.ts +59 -0
  145. package/dist/tools/types.js +4 -0
  146. package/dist/tracing/hooks.d.ts +58 -0
  147. package/dist/tracing/hooks.js +377 -0
  148. package/dist/tracing/index.d.ts +51 -0
  149. package/dist/tracing/index.js +55 -0
  150. package/dist/tracing/logging.d.ts +78 -0
  151. package/dist/tracing/logging.js +310 -0
  152. package/dist/tracing/manager.d.ts +160 -0
  153. package/dist/tracing/manager.js +468 -0
  154. package/dist/tracing/otel.d.ts +102 -0
  155. package/dist/tracing/otel.js +246 -0
  156. package/dist/tracing/types.d.ts +346 -0
  157. package/dist/tracing/types.js +38 -0
  158. package/dist/utils/index.d.ts +23 -0
  159. package/dist/utils/index.js +44 -0
  160. package/package.json +79 -0
@@ -0,0 +1,348 @@
1
+ /**
2
+ * ClaudeProvider - Anthropic Claude API implementation
3
+ *
4
+ * @example
5
+ * ```typescript
6
+ * const provider = new ClaudeProvider({
7
+ * apiKey: process.env.ANTHROPIC_API_KEY,
8
+ * });
9
+ *
10
+ * const agent = new Agent({ provider });
11
+ * ```
12
+ */
13
+ import Anthropic from '@anthropic-ai/sdk';
14
+ import { ProviderError } from '../errors.js';
15
+ /**
16
+ * Default model for Claude API
17
+ */
18
+ const DEFAULT_MODEL = 'claude-sonnet-4-20250514';
19
+ /**
20
+ * Default max tokens
21
+ */
22
+ const DEFAULT_MAX_TOKENS = 4096;
23
+ /**
24
+ * ClaudeProvider implements LLMProvider for Anthropic's Claude API
25
+ */
26
+ export class ClaudeProvider {
27
+ name = 'claude';
28
+ client;
29
+ defaultModel;
30
+ defaultMaxTokens;
31
+ constructor(config) {
32
+ this.client = new Anthropic({
33
+ apiKey: config.apiKey,
34
+ baseURL: config.baseURL,
35
+ });
36
+ this.defaultModel = config.model ?? DEFAULT_MODEL;
37
+ this.defaultMaxTokens = config.maxTokens ?? DEFAULT_MAX_TOKENS;
38
+ }
39
+ /**
40
+ * Send messages and stream the response
41
+ */
42
+ async *chat(messages, options) {
43
+ const { systemPrompt, anthropicMessages } = this.convertMessages(messages);
44
+ const tools = this.convertTools(options?.tools);
45
+ const thinking = this.convertThinking(options?.thinking);
46
+ try {
47
+ // Build request parameters
48
+ const params = {
49
+ model: options?.model ?? this.defaultModel,
50
+ max_tokens: options?.maxTokens ?? this.defaultMaxTokens,
51
+ system: systemPrompt,
52
+ messages: anthropicMessages,
53
+ tools: tools.length > 0 ? tools : undefined,
54
+ temperature: options?.temperature,
55
+ stop_sequences: options?.stopSequences,
56
+ };
57
+ // Add thinking if enabled (Claude-specific)
58
+ // Note: Extended thinking types not yet in SDK, using Object.assign
59
+ if (thinking) {
60
+ Object.assign(params, { thinking });
61
+ }
62
+ const stream = this.client.messages.stream(params);
63
+ const model = options?.model ?? this.defaultModel;
64
+ let currentToolId = '';
65
+ let currentToolName = '';
66
+ let inThinkingBlock = false;
67
+ for await (const event of stream) {
68
+ const chunks = this.processEvent(event, currentToolId, currentToolName, inThinkingBlock);
69
+ for (const chunk of chunks) {
70
+ // Track current tool for delta events
71
+ if (chunk.type === 'tool_use_start' && chunk.toolUse) {
72
+ currentToolId = chunk.toolUse.id;
73
+ currentToolName = chunk.toolUse.name;
74
+ }
75
+ else if (chunk.type === 'tool_use_end') {
76
+ currentToolId = '';
77
+ currentToolName = '';
78
+ }
79
+ else if (chunk.type === 'thinking_start') {
80
+ inThinkingBlock = true;
81
+ }
82
+ else if (chunk.type === 'thinking_end') {
83
+ inThinkingBlock = false;
84
+ }
85
+ yield chunk;
86
+ }
87
+ }
88
+ // Get final message to extract usage
89
+ const finalMessage = await stream.finalMessage();
90
+ const usage = finalMessage.usage;
91
+ // Access optional cache token fields via type coercion
92
+ // These fields are present in newer SDK versions but not in the type definitions
93
+ const usageWithCache = usage;
94
+ yield {
95
+ type: 'done',
96
+ model,
97
+ usage: {
98
+ inputTokens: usage.input_tokens,
99
+ outputTokens: usage.output_tokens,
100
+ cacheReadTokens: usageWithCache.cache_read_input_tokens,
101
+ cacheCreationTokens: usageWithCache.cache_creation_input_tokens,
102
+ },
103
+ };
104
+ }
105
+ catch (error) {
106
+ throw this.mapError(error);
107
+ }
108
+ }
109
+ /**
110
+ * Count tokens in messages (not yet available in SDK v0.30)
111
+ *
112
+ * Note: Token counting API is available via Anthropic's beta endpoints
113
+ * but not yet exposed in the stable SDK. For now, this provides an
114
+ * approximation based on character count.
115
+ */
116
+ countTokens(messages) {
117
+ // Approximation: ~4 characters per token (rough estimate)
118
+ let charCount = 0;
119
+ for (const msg of messages) {
120
+ if (typeof msg.content === 'string') {
121
+ charCount += msg.content.length;
122
+ }
123
+ else {
124
+ for (const block of msg.content) {
125
+ switch (block.type) {
126
+ case 'text':
127
+ charCount += block.text.length;
128
+ break;
129
+ case 'tool_use':
130
+ charCount += JSON.stringify(block.input).length;
131
+ break;
132
+ case 'tool_result':
133
+ charCount += block.content.length;
134
+ break;
135
+ }
136
+ }
137
+ }
138
+ }
139
+ return Promise.resolve(Math.ceil(charCount / 4));
140
+ }
141
+ /**
142
+ * Convert our Message format to Anthropic's format
143
+ */
144
+ convertMessages(messages) {
145
+ let systemPrompt = '';
146
+ const anthropicMessages = [];
147
+ for (const msg of messages) {
148
+ if (msg.role === 'system') {
149
+ // Anthropic uses a separate system parameter
150
+ systemPrompt = typeof msg.content === 'string' ? msg.content : '';
151
+ }
152
+ else {
153
+ // user or assistant role
154
+ anthropicMessages.push({
155
+ role: msg.role,
156
+ content: this.convertContent(msg.content),
157
+ });
158
+ }
159
+ }
160
+ return { systemPrompt, anthropicMessages };
161
+ }
162
+ /**
163
+ * Convert content to Anthropic's content block format
164
+ */
165
+ convertContent(content) {
166
+ if (typeof content === 'string') {
167
+ return content;
168
+ }
169
+ return content.map((block) => {
170
+ switch (block.type) {
171
+ case 'text':
172
+ return { type: 'text', text: block.text };
173
+ case 'tool_use':
174
+ return {
175
+ type: 'tool_use',
176
+ id: block.id,
177
+ name: block.name,
178
+ input: block.input,
179
+ };
180
+ case 'tool_result':
181
+ return {
182
+ type: 'tool_result',
183
+ tool_use_id: block.toolUseId,
184
+ content: block.content,
185
+ is_error: block.isError,
186
+ };
187
+ case 'thinking':
188
+ // Thinking blocks are passed through as text for now
189
+ // The API expects thinking in a specific format during beta
190
+ return { type: 'text', text: `<thinking>${block.thinking}</thinking>` };
191
+ default: {
192
+ // Exhaustive check - this should never happen
193
+ const _exhaustive = block;
194
+ throw new Error(`Unknown content block type: ${_exhaustive.type}`);
195
+ }
196
+ }
197
+ });
198
+ }
199
+ /**
200
+ * Convert our ToolDefinition to Anthropic's Tool format
201
+ */
202
+ convertTools(tools) {
203
+ if (!tools || tools.length === 0) {
204
+ return [];
205
+ }
206
+ return tools.map((tool) => ({
207
+ name: tool.name,
208
+ description: tool.description,
209
+ input_schema: {
210
+ type: 'object',
211
+ properties: tool.inputSchema.properties,
212
+ required: tool.inputSchema.required,
213
+ },
214
+ }));
215
+ }
216
+ /**
217
+ * Convert thinking config to Anthropic API format
218
+ */
219
+ convertThinking(thinking) {
220
+ if (!thinking || thinking.type === 'disabled') {
221
+ return undefined;
222
+ }
223
+ // Validate budget_tokens minimum (1024)
224
+ if (thinking.budgetTokens < 1024) {
225
+ throw new ProviderError(`Extended thinking budget_tokens must be at least 1024, got ${String(thinking.budgetTokens)}`, 'claude');
226
+ }
227
+ return {
228
+ type: thinking.type,
229
+ budget_tokens: thinking.budgetTokens,
230
+ };
231
+ }
232
+ /**
233
+ * Process a stream event into StreamChunks
234
+ */
235
+ processEvent(event, currentToolId, _currentToolName, inThinkingBlock) {
236
+ const chunks = [];
237
+ switch (event.type) {
238
+ case 'content_block_start': {
239
+ // Extended thinking support: cast content_block to check for 'thinking' type
240
+ const contentBlock = event.content_block;
241
+ if (event.content_block.type === 'tool_use') {
242
+ chunks.push({
243
+ type: 'tool_use_start',
244
+ toolUse: {
245
+ id: event.content_block.id,
246
+ name: event.content_block.name,
247
+ },
248
+ });
249
+ }
250
+ else if (contentBlock.type === 'thinking') {
251
+ // Extended thinking block started
252
+ chunks.push({ type: 'thinking_start' });
253
+ }
254
+ break;
255
+ }
256
+ case 'content_block_delta':
257
+ chunks.push(...this.processDelta(event, inThinkingBlock));
258
+ break;
259
+ case 'content_block_stop':
260
+ // Check if we were in a tool use block
261
+ if (currentToolId) {
262
+ chunks.push({ type: 'tool_use_end' });
263
+ }
264
+ else if (inThinkingBlock) {
265
+ chunks.push({ type: 'thinking_end' });
266
+ }
267
+ break;
268
+ case 'message_stop':
269
+ // Message complete - done chunk will be yielded after the loop
270
+ break;
271
+ }
272
+ return chunks;
273
+ }
274
+ /**
275
+ * Process a content block delta event
276
+ */
277
+ processDelta(event, inThinkingBlock) {
278
+ const chunks = [];
279
+ // Cast delta to our extended type that includes thinking deltas
280
+ const delta = event.delta;
281
+ switch (delta.type) {
282
+ case 'text_delta':
283
+ chunks.push({
284
+ type: 'text',
285
+ text: delta.text,
286
+ });
287
+ break;
288
+ case 'input_json_delta':
289
+ chunks.push({
290
+ type: 'tool_use_delta',
291
+ text: delta.partial_json,
292
+ });
293
+ break;
294
+ case 'thinking_delta':
295
+ // Extended thinking content delta
296
+ chunks.push({
297
+ type: 'thinking_delta',
298
+ text: delta.thinking,
299
+ thinking: { thinking: delta.thinking },
300
+ });
301
+ break;
302
+ case 'signature_delta':
303
+ // Signature for thinking block verification (sent just before content_block_stop)
304
+ if (inThinkingBlock) {
305
+ chunks.push({
306
+ type: 'thinking_delta',
307
+ thinking: { signature: delta.signature },
308
+ });
309
+ }
310
+ break;
311
+ }
312
+ return chunks;
313
+ }
314
+ /**
315
+ * Map Anthropic SDK errors to ProviderError
316
+ */
317
+ mapError(error) {
318
+ if (error instanceof Anthropic.APIError) {
319
+ return new ProviderError(error.message, 'claude', error.status, error);
320
+ }
321
+ if (error instanceof Anthropic.APIConnectionError) {
322
+ return new ProviderError(`Connection error: ${error.message}`, 'claude', undefined, error);
323
+ }
324
+ if (error instanceof Anthropic.RateLimitError) {
325
+ return new ProviderError('Rate limit exceeded', 'claude', 429, error);
326
+ }
327
+ if (error instanceof Anthropic.AuthenticationError) {
328
+ return new ProviderError('Authentication failed: Invalid API key', 'claude', 401, error);
329
+ }
330
+ if (error instanceof Error) {
331
+ return new ProviderError(error.message, 'claude', undefined, error);
332
+ }
333
+ return new ProviderError(String(error), 'claude');
334
+ }
335
+ }
336
+ /**
337
+ * Create a ClaudeProvider with API key from environment
338
+ */
339
+ export function createClaudeProvider(config) {
340
+ const apiKey = config?.apiKey ?? process.env.ANTHROPIC_API_KEY;
341
+ if (!apiKey) {
342
+ throw new ProviderError('Anthropic API key not found. Set ANTHROPIC_API_KEY environment variable or pass apiKey in config.', 'claude');
343
+ }
344
+ return new ClaudeProvider({
345
+ ...config,
346
+ apiKey,
347
+ });
348
+ }
@@ -0,0 +1,8 @@
1
+ /**
2
+ * Provider exports
3
+ */
4
+ export * from './types.js';
5
+ export { MockProvider, createMockProvider } from './mock.js';
6
+ export type { MockProviderConfig, MockResponse, MockToolCall } from './mock.js';
7
+ export { ClaudeProvider, createClaudeProvider } from './claude.js';
8
+ export type { ClaudeProviderConfig } from './claude.js';
@@ -0,0 +1,11 @@
1
+ /**
2
+ * Provider exports
3
+ */
4
+ export * from './types.js';
5
+ // Mock provider for testing
6
+ export { MockProvider, createMockProvider } from './mock.js';
7
+ // Provider implementations
8
+ export { ClaudeProvider, createClaudeProvider } from './claude.js';
9
+ // Future providers
10
+ // export { OpenAIProvider } from './openai.js';
11
+ // export { GeminiProvider } from './gemini.js';
@@ -0,0 +1,133 @@
1
+ /**
2
+ * MockProvider - A test-friendly LLM provider for unit testing
3
+ *
4
+ * This provider allows you to define responses in advance, making it
5
+ * perfect for testing agent behavior without making API calls.
6
+ *
7
+ * @example
8
+ * ```typescript
9
+ * const mock = new MockProvider();
10
+ *
11
+ * // Simple text response
12
+ * mock.addResponse('Hello, world!');
13
+ *
14
+ * // Response with tool call
15
+ * mock.addResponse({
16
+ * text: 'Let me read that file.',
17
+ * toolCalls: [{
18
+ * id: 'call_1',
19
+ * name: 'read_file',
20
+ * input: { path: '/test.txt' },
21
+ * }],
22
+ * });
23
+ *
24
+ * const agent = new Agent({ provider: mock });
25
+ * ```
26
+ */
27
+ import type { LLMProvider, Message, ChatOptions, StreamChunk } from './types.js';
28
+ /**
29
+ * A tool call in a mock response
30
+ */
31
+ export interface MockToolCall {
32
+ id: string;
33
+ name: string;
34
+ input: Record<string, unknown>;
35
+ }
36
+ /**
37
+ * A mock thinking block for extended thinking simulation
38
+ */
39
+ export interface MockThinking {
40
+ /** The thinking content */
41
+ thinking: string;
42
+ /** Optional signature (for verification simulation) */
43
+ signature?: string;
44
+ }
45
+ /**
46
+ * A mock response definition
47
+ */
48
+ export interface MockResponse {
49
+ /** Text content of the response */
50
+ text?: string;
51
+ /** Tool calls to include in the response */
52
+ toolCalls?: MockToolCall[];
53
+ /** Extended thinking content (Claude-specific) */
54
+ thinking?: MockThinking;
55
+ /** If set, throw this error instead of responding */
56
+ error?: Error;
57
+ /** Delay in ms before responding (simulates network latency) */
58
+ delay?: number;
59
+ }
60
+ /**
61
+ * Configuration for MockProvider
62
+ */
63
+ export interface MockProviderConfig {
64
+ /** Default delay for all responses (ms) */
65
+ defaultDelay?: number;
66
+ /** Throw if no responses are queued */
67
+ throwOnEmpty?: boolean;
68
+ }
69
+ /**
70
+ * MockProvider for testing agents without API calls.
71
+ *
72
+ * Responses are consumed in order (FIFO queue).
73
+ */
74
+ export declare class MockProvider implements LLMProvider {
75
+ readonly name = "mock";
76
+ private readonly responses;
77
+ private readonly defaultDelay;
78
+ private readonly throwOnEmpty;
79
+ private callCount;
80
+ private readonly callHistory;
81
+ constructor(config?: MockProviderConfig);
82
+ /**
83
+ * Add a response (text string or structured response with tool calls)
84
+ */
85
+ addResponse(response: string | MockResponse): this;
86
+ /**
87
+ * Add multiple responses at once
88
+ */
89
+ addResponses(responses: Array<string | MockResponse>): this;
90
+ /**
91
+ * Queue an error to be thrown on the next call
92
+ */
93
+ addError(error: Error): this;
94
+ /**
95
+ * Get number of times chat() was called
96
+ */
97
+ getCallCount(): number;
98
+ /**
99
+ * Get the history of all calls made
100
+ */
101
+ getCallHistory(): ReadonlyArray<{
102
+ messages: Message[];
103
+ options?: ChatOptions;
104
+ }>;
105
+ /**
106
+ * Get the last call made
107
+ */
108
+ getLastCall(): {
109
+ messages: Message[];
110
+ options?: ChatOptions;
111
+ } | undefined;
112
+ /**
113
+ * Clear all queued responses and call history
114
+ */
115
+ reset(): this;
116
+ /**
117
+ * Check if there are any queued responses
118
+ */
119
+ hasResponses(): boolean;
120
+ /**
121
+ * Stream chat response
122
+ */
123
+ chat(messages: Message[], options?: ChatOptions): AsyncIterable<StreamChunk>;
124
+ /**
125
+ * Count tokens (mock implementation)
126
+ */
127
+ countTokens(messages: Message[]): Promise<number>;
128
+ private sleep;
129
+ }
130
+ /**
131
+ * Create a MockProvider with initial responses
132
+ */
133
+ export declare function createMockProvider(responses?: Array<string | MockResponse>, config?: MockProviderConfig): MockProvider;
@@ -0,0 +1,204 @@
1
+ /**
2
+ * MockProvider - A test-friendly LLM provider for unit testing
3
+ *
4
+ * This provider allows you to define responses in advance, making it
5
+ * perfect for testing agent behavior without making API calls.
6
+ *
7
+ * @example
8
+ * ```typescript
9
+ * const mock = new MockProvider();
10
+ *
11
+ * // Simple text response
12
+ * mock.addResponse('Hello, world!');
13
+ *
14
+ * // Response with tool call
15
+ * mock.addResponse({
16
+ * text: 'Let me read that file.',
17
+ * toolCalls: [{
18
+ * id: 'call_1',
19
+ * name: 'read_file',
20
+ * input: { path: '/test.txt' },
21
+ * }],
22
+ * });
23
+ *
24
+ * const agent = new Agent({ provider: mock });
25
+ * ```
26
+ */
27
+ import { ProviderError } from '../errors.js';
28
+ /**
29
+ * MockProvider for testing agents without API calls.
30
+ *
31
+ * Responses are consumed in order (FIFO queue).
32
+ */
33
+ export class MockProvider {
34
+ name = 'mock';
35
+ responses = [];
36
+ defaultDelay;
37
+ throwOnEmpty;
38
+ callCount = 0;
39
+ callHistory = [];
40
+ constructor(config = {}) {
41
+ this.defaultDelay = config.defaultDelay ?? 0;
42
+ this.throwOnEmpty = config.throwOnEmpty ?? true;
43
+ }
44
+ /**
45
+ * Add a response (text string or structured response with tool calls)
46
+ */
47
+ addResponse(response) {
48
+ if (typeof response === 'string') {
49
+ this.responses.push({ text: response });
50
+ }
51
+ else {
52
+ this.responses.push(response);
53
+ }
54
+ return this;
55
+ }
56
+ /**
57
+ * Add multiple responses at once
58
+ */
59
+ addResponses(responses) {
60
+ for (const response of responses) {
61
+ this.addResponse(response);
62
+ }
63
+ return this;
64
+ }
65
+ /**
66
+ * Queue an error to be thrown on the next call
67
+ */
68
+ addError(error) {
69
+ this.responses.push({ error });
70
+ return this;
71
+ }
72
+ /**
73
+ * Get number of times chat() was called
74
+ */
75
+ getCallCount() {
76
+ return this.callCount;
77
+ }
78
+ /**
79
+ * Get the history of all calls made
80
+ */
81
+ getCallHistory() {
82
+ return this.callHistory;
83
+ }
84
+ /**
85
+ * Get the last call made
86
+ */
87
+ getLastCall() {
88
+ return this.callHistory[this.callHistory.length - 1];
89
+ }
90
+ /**
91
+ * Clear all queued responses and call history
92
+ */
93
+ reset() {
94
+ this.responses.length = 0;
95
+ this.callHistory.length = 0;
96
+ this.callCount = 0;
97
+ return this;
98
+ }
99
+ /**
100
+ * Check if there are any queued responses
101
+ */
102
+ hasResponses() {
103
+ return this.responses.length > 0;
104
+ }
105
+ /**
106
+ * Stream chat response
107
+ */
108
+ async *chat(messages, options) {
109
+ this.callCount++;
110
+ this.callHistory.push({ messages, options });
111
+ // Get next response from queue
112
+ const response = this.responses.shift();
113
+ if (!response) {
114
+ if (this.throwOnEmpty) {
115
+ throw new ProviderError('MockProvider: No responses queued', 'mock');
116
+ }
117
+ // Return empty response
118
+ yield { type: 'done' };
119
+ return;
120
+ }
121
+ // Handle delay
122
+ const delay = response.delay ?? this.defaultDelay;
123
+ if (delay > 0) {
124
+ await this.sleep(delay);
125
+ }
126
+ // Handle error
127
+ if (response.error) {
128
+ throw response.error;
129
+ }
130
+ // Emit thinking blocks (extended thinking simulation)
131
+ if (response.thinking) {
132
+ yield { type: 'thinking_start' };
133
+ yield {
134
+ type: 'thinking_delta',
135
+ text: response.thinking.thinking,
136
+ thinking: {
137
+ thinking: response.thinking.thinking,
138
+ },
139
+ };
140
+ if (response.thinking.signature) {
141
+ yield {
142
+ type: 'thinking_delta',
143
+ thinking: { signature: response.thinking.signature },
144
+ };
145
+ }
146
+ yield { type: 'thinking_end' };
147
+ }
148
+ // Emit text content
149
+ if (response.text) {
150
+ yield { type: 'text', text: response.text };
151
+ }
152
+ // Emit tool calls
153
+ if (response.toolCalls && response.toolCalls.length > 0) {
154
+ for (const toolCall of response.toolCalls) {
155
+ yield {
156
+ type: 'tool_use_start',
157
+ toolUse: {
158
+ id: toolCall.id,
159
+ name: toolCall.name,
160
+ },
161
+ };
162
+ // Stream the input as JSON
163
+ const inputJson = JSON.stringify(toolCall.input);
164
+ yield {
165
+ type: 'tool_use_delta',
166
+ text: inputJson,
167
+ };
168
+ yield { type: 'tool_use_end' };
169
+ }
170
+ }
171
+ yield { type: 'done' };
172
+ }
173
+ /**
174
+ * Count tokens (mock implementation)
175
+ */
176
+ countTokens(messages) {
177
+ // Simple approximation: ~4 chars per token
178
+ let charCount = 0;
179
+ for (const msg of messages) {
180
+ if (typeof msg.content === 'string') {
181
+ charCount += msg.content.length;
182
+ }
183
+ else {
184
+ for (const block of msg.content) {
185
+ if (block.type === 'text') {
186
+ charCount += block.text.length;
187
+ }
188
+ }
189
+ }
190
+ }
191
+ return Promise.resolve(Math.ceil(charCount / 4));
192
+ }
193
+ sleep(ms) {
194
+ return new Promise((resolve) => setTimeout(resolve, ms));
195
+ }
196
+ }
197
+ /**
198
+ * Create a MockProvider with initial responses
199
+ */
200
+ export function createMockProvider(responses = [], config) {
201
+ const provider = new MockProvider(config);
202
+ provider.addResponses(responses);
203
+ return provider;
204
+ }