@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.
- package/README.md +1277 -0
- package/dist/agent.d.ts +1272 -0
- package/dist/agent.js +1912 -0
- package/dist/anchors/builtin.d.ts +24 -0
- package/dist/anchors/builtin.js +61 -0
- package/dist/anchors/index.d.ts +6 -0
- package/dist/anchors/index.js +5 -0
- package/dist/anchors/manager.d.ts +115 -0
- package/dist/anchors/manager.js +412 -0
- package/dist/anchors/types.d.ts +168 -0
- package/dist/anchors/types.js +10 -0
- package/dist/context/index.d.ts +12 -0
- package/dist/context/index.js +10 -0
- package/dist/context/manager.d.ts +224 -0
- package/dist/context/manager.js +770 -0
- package/dist/context/types.d.ts +377 -0
- package/dist/context/types.js +7 -0
- package/dist/costs/index.d.ts +8 -0
- package/dist/costs/index.js +7 -0
- package/dist/costs/tracker.d.ts +121 -0
- package/dist/costs/tracker.js +295 -0
- package/dist/costs/types.d.ts +157 -0
- package/dist/costs/types.js +8 -0
- package/dist/errors.d.ts +178 -0
- package/dist/errors.js +249 -0
- package/dist/guardrails/builtin.d.ts +27 -0
- package/dist/guardrails/builtin.js +223 -0
- package/dist/guardrails/index.d.ts +6 -0
- package/dist/guardrails/index.js +5 -0
- package/dist/guardrails/manager.d.ts +117 -0
- package/dist/guardrails/manager.js +288 -0
- package/dist/guardrails/types.d.ts +159 -0
- package/dist/guardrails/types.js +7 -0
- package/dist/hooks/index.d.ts +31 -0
- package/dist/hooks/index.js +29 -0
- package/dist/hooks/manager.d.ts +147 -0
- package/dist/hooks/manager.js +600 -0
- package/dist/hooks/types.d.ts +368 -0
- package/dist/hooks/types.js +12 -0
- package/dist/index.d.ts +45 -0
- package/dist/index.js +73 -0
- package/dist/mcp/client.d.ts +93 -0
- package/dist/mcp/client.js +287 -0
- package/dist/mcp/errors.d.ts +60 -0
- package/dist/mcp/errors.js +78 -0
- package/dist/mcp/index.d.ts +43 -0
- package/dist/mcp/index.js +45 -0
- package/dist/mcp/manager.d.ts +120 -0
- package/dist/mcp/manager.js +276 -0
- package/dist/mcp/tools.d.ts +54 -0
- package/dist/mcp/tools.js +99 -0
- package/dist/mcp/types.d.ts +150 -0
- package/dist/mcp/types.js +40 -0
- package/dist/memory/index.d.ts +8 -0
- package/dist/memory/index.js +7 -0
- package/dist/memory/loader.d.ts +114 -0
- package/dist/memory/loader.js +463 -0
- package/dist/memory/types.d.ts +182 -0
- package/dist/memory/types.js +8 -0
- package/dist/messages/index.d.ts +82 -0
- package/dist/messages/index.js +155 -0
- package/dist/permissions/index.d.ts +5 -0
- package/dist/permissions/index.js +4 -0
- package/dist/permissions/manager.d.ts +125 -0
- package/dist/permissions/manager.js +379 -0
- package/dist/permissions/types.d.ts +162 -0
- package/dist/permissions/types.js +7 -0
- package/dist/providers/claude.d.ts +90 -0
- package/dist/providers/claude.js +348 -0
- package/dist/providers/index.d.ts +8 -0
- package/dist/providers/index.js +11 -0
- package/dist/providers/mock.d.ts +133 -0
- package/dist/providers/mock.js +204 -0
- package/dist/providers/types.d.ts +168 -0
- package/dist/providers/types.js +4 -0
- package/dist/rate-limit/index.d.ts +45 -0
- package/dist/rate-limit/index.js +47 -0
- package/dist/rate-limit/limiter.d.ts +104 -0
- package/dist/rate-limit/limiter.js +326 -0
- package/dist/rate-limit/provider-wrapper.d.ts +112 -0
- package/dist/rate-limit/provider-wrapper.js +201 -0
- package/dist/rate-limit/retry.d.ts +108 -0
- package/dist/rate-limit/retry.js +287 -0
- package/dist/rate-limit/types.d.ts +181 -0
- package/dist/rate-limit/types.js +22 -0
- package/dist/rehearsal/file-analyzer.d.ts +22 -0
- package/dist/rehearsal/file-analyzer.js +351 -0
- package/dist/rehearsal/git-analyzer.d.ts +22 -0
- package/dist/rehearsal/git-analyzer.js +472 -0
- package/dist/rehearsal/index.d.ts +35 -0
- package/dist/rehearsal/index.js +36 -0
- package/dist/rehearsal/manager.d.ts +100 -0
- package/dist/rehearsal/manager.js +290 -0
- package/dist/rehearsal/types.d.ts +235 -0
- package/dist/rehearsal/types.js +8 -0
- package/dist/skills/index.d.ts +160 -0
- package/dist/skills/index.js +282 -0
- package/dist/state/agent-state.d.ts +41 -0
- package/dist/state/agent-state.js +88 -0
- package/dist/state/checkpointer.d.ts +110 -0
- package/dist/state/checkpointer.js +362 -0
- package/dist/state/errors.d.ts +66 -0
- package/dist/state/errors.js +88 -0
- package/dist/state/index.d.ts +35 -0
- package/dist/state/index.js +37 -0
- package/dist/state/serializer.d.ts +55 -0
- package/dist/state/serializer.js +172 -0
- package/dist/state/types.d.ts +312 -0
- package/dist/state/types.js +14 -0
- package/dist/tools/builtin/bash-output.d.ts +61 -0
- package/dist/tools/builtin/bash-output.js +90 -0
- package/dist/tools/builtin/bash.d.ts +150 -0
- package/dist/tools/builtin/bash.js +354 -0
- package/dist/tools/builtin/edit.d.ts +50 -0
- package/dist/tools/builtin/edit.js +215 -0
- package/dist/tools/builtin/glob.d.ts +62 -0
- package/dist/tools/builtin/glob.js +244 -0
- package/dist/tools/builtin/grep.d.ts +74 -0
- package/dist/tools/builtin/grep.js +363 -0
- package/dist/tools/builtin/index.d.ts +44 -0
- package/dist/tools/builtin/index.js +69 -0
- package/dist/tools/builtin/kill-shell.d.ts +44 -0
- package/dist/tools/builtin/kill-shell.js +80 -0
- package/dist/tools/builtin/read-file.d.ts +57 -0
- package/dist/tools/builtin/read-file.js +184 -0
- package/dist/tools/builtin/shell-manager.d.ts +176 -0
- package/dist/tools/builtin/shell-manager.js +337 -0
- package/dist/tools/builtin/task.d.ts +202 -0
- package/dist/tools/builtin/task.js +350 -0
- package/dist/tools/builtin/todo.d.ts +207 -0
- package/dist/tools/builtin/todo.js +453 -0
- package/dist/tools/builtin/utils.d.ts +27 -0
- package/dist/tools/builtin/utils.js +70 -0
- package/dist/tools/builtin/web-fetch.d.ts +96 -0
- package/dist/tools/builtin/web-fetch.js +290 -0
- package/dist/tools/builtin/write-file.d.ts +54 -0
- package/dist/tools/builtin/write-file.js +147 -0
- package/dist/tools/define.d.ts +60 -0
- package/dist/tools/define.js +65 -0
- package/dist/tools/index.d.ts +10 -0
- package/dist/tools/index.js +37 -0
- package/dist/tools/registry.d.ts +79 -0
- package/dist/tools/registry.js +151 -0
- package/dist/tools/types.d.ts +59 -0
- package/dist/tools/types.js +4 -0
- package/dist/tracing/hooks.d.ts +58 -0
- package/dist/tracing/hooks.js +377 -0
- package/dist/tracing/index.d.ts +51 -0
- package/dist/tracing/index.js +55 -0
- package/dist/tracing/logging.d.ts +78 -0
- package/dist/tracing/logging.js +310 -0
- package/dist/tracing/manager.d.ts +160 -0
- package/dist/tracing/manager.js +468 -0
- package/dist/tracing/otel.d.ts +102 -0
- package/dist/tracing/otel.js +246 -0
- package/dist/tracing/types.d.ts +346 -0
- package/dist/tracing/types.js +38 -0
- package/dist/utils/index.d.ts +23 -0
- package/dist/utils/index.js +44 -0
- 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
|
+
}
|