@aragon-agent/core 0.1.3

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 (74) hide show
  1. package/CHANGELOG.md +126 -0
  2. package/LICENSE +21 -0
  3. package/README.md +147 -0
  4. package/dist/engine/agent-loop.d.ts +52 -0
  5. package/dist/engine/agent-loop.js +260 -0
  6. package/dist/engine/agent.d.ts +129 -0
  7. package/dist/engine/agent.js +283 -0
  8. package/dist/engine/message-manager.d.ts +26 -0
  9. package/dist/engine/message-manager.js +39 -0
  10. package/dist/engine/steering.d.ts +27 -0
  11. package/dist/engine/steering.js +51 -0
  12. package/dist/engine/watchdog.d.ts +24 -0
  13. package/dist/engine/watchdog.js +50 -0
  14. package/dist/index.d.ts +46 -0
  15. package/dist/index.js +63 -0
  16. package/dist/llm/model-registry.d.ts +51 -0
  17. package/dist/llm/model-registry.js +277 -0
  18. package/dist/llm/provider.d.ts +72 -0
  19. package/dist/llm/provider.js +77 -0
  20. package/dist/llm/providers/anthropic.d.ts +23 -0
  21. package/dist/llm/providers/anthropic.js +420 -0
  22. package/dist/llm/providers/google.d.ts +22 -0
  23. package/dist/llm/providers/google.js +336 -0
  24. package/dist/llm/providers/index.d.ts +67 -0
  25. package/dist/llm/providers/index.js +127 -0
  26. package/dist/llm/providers/openai.d.ts +22 -0
  27. package/dist/llm/providers/openai.js +353 -0
  28. package/dist/llm/stream-utils.d.ts +33 -0
  29. package/dist/llm/stream-utils.js +132 -0
  30. package/dist/llm/types.d.ts +129 -0
  31. package/dist/llm/types.js +17 -0
  32. package/dist/sandbox/console-capture.d.ts +29 -0
  33. package/dist/sandbox/console-capture.js +56 -0
  34. package/dist/sandbox/http-bridge.d.ts +47 -0
  35. package/dist/sandbox/http-bridge.js +143 -0
  36. package/dist/sandbox/isolated-vm-sandbox.d.ts +50 -0
  37. package/dist/sandbox/isolated-vm-sandbox.js +226 -0
  38. package/dist/sandbox/tool-bridge.d.ts +42 -0
  39. package/dist/sandbox/tool-bridge.js +98 -0
  40. package/dist/sandbox/types.d.ts +84 -0
  41. package/dist/sandbox/types.js +8 -0
  42. package/dist/skills/constants.d.ts +122 -0
  43. package/dist/skills/constants.js +179 -0
  44. package/dist/skills/disclosure.d.ts +146 -0
  45. package/dist/skills/disclosure.js +788 -0
  46. package/dist/skills/frontmatter.d.ts +27 -0
  47. package/dist/skills/frontmatter.js +177 -0
  48. package/dist/skills/index.d.ts +21 -0
  49. package/dist/skills/index.js +18 -0
  50. package/dist/skills/skill-find-tool.d.ts +41 -0
  51. package/dist/skills/skill-find-tool.js +117 -0
  52. package/dist/skills/skill-registry.d.ts +96 -0
  53. package/dist/skills/skill-registry.js +208 -0
  54. package/dist/skills/skill-tool.d.ts +44 -0
  55. package/dist/skills/skill-tool.js +96 -0
  56. package/dist/skills/tool-policy.d.ts +91 -0
  57. package/dist/skills/tool-policy.js +272 -0
  58. package/dist/skills/types.d.ts +271 -0
  59. package/dist/skills/types.js +8 -0
  60. package/dist/skills/validate.d.ts +37 -0
  61. package/dist/skills/validate.js +264 -0
  62. package/dist/tools/executor.d.ts +54 -0
  63. package/dist/tools/executor.js +206 -0
  64. package/dist/tools/helpers.d.ts +69 -0
  65. package/dist/tools/helpers.js +87 -0
  66. package/dist/tools/registry.d.ts +58 -0
  67. package/dist/tools/registry.js +119 -0
  68. package/dist/tools/types.d.ts +140 -0
  69. package/dist/tools/types.js +7 -0
  70. package/dist/tools/validator.d.ts +46 -0
  71. package/dist/tools/validator.js +210 -0
  72. package/dist/types.d.ts +56 -0
  73. package/dist/types.js +7 -0
  74. package/package.json +115 -0
@@ -0,0 +1,420 @@
1
+ /**
2
+ * Anthropic Messages API adapter.
3
+ *
4
+ * Converts the universal LLM types to/from Anthropic's wire format and maps
5
+ * SSE events to the unified StreamEvent protocol.
6
+ *
7
+ * Supports:
8
+ * - Streaming via `POST /v1/messages` with `stream: true`
9
+ * - Extended thinking (thinking_start / thinking_delta)
10
+ * - Tool use (content_block_start type=tool_use → tool_call_*)
11
+ * - Multimodal user messages (text + image)
12
+ * - Model list via `GET /v1/models`
13
+ */
14
+ import { THINKING_BUDGET } from '../types.js';
15
+ import { LLMError, classifyHttpError, wrapFetchError } from '../provider.js';
16
+ import { parseSSEStream } from '../stream-utils.js';
17
+ import { consumeStream } from '../stream-utils.js';
18
+ // ---------------------------------------------------------------------------
19
+ // Constants
20
+ // ---------------------------------------------------------------------------
21
+ const DEFAULT_BASE_URL = 'https://api.anthropic.com';
22
+ const ANTHROPIC_VERSION = '2023-06-01';
23
+ const DEFAULT_MAX_TOKENS = 16384;
24
+ // ---------------------------------------------------------------------------
25
+ // Anthropic provider
26
+ // ---------------------------------------------------------------------------
27
+ export class AnthropicProvider {
28
+ id = 'anthropic';
29
+ displayName = 'Anthropic';
30
+ defaultBaseUrl = DEFAULT_BASE_URL;
31
+ // -----------------------------------------------------------------------
32
+ // stream
33
+ // -----------------------------------------------------------------------
34
+ async *stream(request) {
35
+ const baseUrl = (request.baseUrl || this.defaultBaseUrl).replace(/\/+$/, '');
36
+ const url = `${baseUrl}/v1/messages`;
37
+ const body = buildRequestBody(request);
38
+ let response;
39
+ try {
40
+ response = await fetch(url, {
41
+ method: 'POST',
42
+ headers: {
43
+ 'Content-Type': 'application/json',
44
+ 'x-api-key': request.apiKey,
45
+ 'anthropic-version': ANTHROPIC_VERSION,
46
+ },
47
+ body: JSON.stringify(body),
48
+ signal: request.signal,
49
+ });
50
+ }
51
+ catch (err) {
52
+ yield { type: 'error', error: wrapFetchError(err, this.id) };
53
+ return;
54
+ }
55
+ if (!response.ok) {
56
+ const text = await response.text().catch(() => '');
57
+ yield { type: 'error', error: classifyHttpError(response.status, text, this.id) };
58
+ return;
59
+ }
60
+ if (!response.body) {
61
+ yield { type: 'error', error: new LLMError('Response body is null', this.id, 'network_error', true) };
62
+ return;
63
+ }
64
+ // -- Parse SSE events and map to StreamEvent --
65
+ const usage = { inputTokens: 0, outputTokens: 0 };
66
+ const contentBlocks = [];
67
+ let stopReason;
68
+ // Accumulator state per content block index
69
+ const blockAccumulators = new Map();
70
+ let currentBlockIndex = -1;
71
+ try {
72
+ for await (const sse of parseSSEStream(response.body, request.signal)) {
73
+ // Anthropic uses the `event` field to distinguish event types
74
+ const eventType = sse.event;
75
+ if (!eventType || !sse.data)
76
+ continue;
77
+ let data;
78
+ try {
79
+ data = JSON.parse(sse.data);
80
+ }
81
+ catch {
82
+ continue; // skip malformed JSON
83
+ }
84
+ switch (eventType) {
85
+ case 'message_start': {
86
+ const msg = data.message;
87
+ if (msg?.usage) {
88
+ const u = msg.usage;
89
+ usage.inputTokens = u.input_tokens ?? 0;
90
+ usage.cacheReadTokens = u.cache_read_input_tokens;
91
+ usage.cacheWriteTokens = u.cache_creation_input_tokens;
92
+ }
93
+ break;
94
+ }
95
+ case 'content_block_start': {
96
+ currentBlockIndex = data.index ?? currentBlockIndex + 1;
97
+ const block = data.content_block;
98
+ if (!block)
99
+ break;
100
+ const blockType = block.type;
101
+ if (blockType === 'thinking') {
102
+ blockAccumulators.set(currentBlockIndex, { type: 'thinking', text: '' });
103
+ yield { type: 'thinking_start' };
104
+ }
105
+ else if (blockType === 'tool_use') {
106
+ const toolCallId = block.id || '';
107
+ const toolName = block.name || '';
108
+ blockAccumulators.set(currentBlockIndex, {
109
+ type: 'tool_use',
110
+ toolCallId,
111
+ toolName,
112
+ argsJson: '',
113
+ });
114
+ yield { type: 'tool_call_start', toolCallId, toolName };
115
+ }
116
+ else {
117
+ // text or redacted_thinking — treat as text
118
+ blockAccumulators.set(currentBlockIndex, { type: 'text', text: '' });
119
+ }
120
+ break;
121
+ }
122
+ case 'content_block_delta': {
123
+ const idx = data.index ?? currentBlockIndex;
124
+ const delta = data.delta;
125
+ if (!delta)
126
+ break;
127
+ const deltaType = delta.type;
128
+ const acc = blockAccumulators.get(idx);
129
+ if (deltaType === 'text_delta') {
130
+ const text = delta.text || '';
131
+ if (acc && acc.type === 'text')
132
+ acc.text += text;
133
+ yield { type: 'text_delta', delta: text };
134
+ }
135
+ else if (deltaType === 'thinking_delta') {
136
+ const thinking = delta.thinking || '';
137
+ if (acc && acc.type === 'thinking')
138
+ acc.text += thinking;
139
+ yield { type: 'thinking_delta', delta: thinking };
140
+ }
141
+ else if (deltaType === 'input_json_delta') {
142
+ const partial = delta.partial_json || '';
143
+ if (acc && acc.type === 'tool_use') {
144
+ acc.argsJson += partial;
145
+ yield { type: 'tool_call_delta', toolCallId: acc.toolCallId, argsDelta: partial };
146
+ }
147
+ }
148
+ break;
149
+ }
150
+ case 'content_block_stop': {
151
+ const idx = data.index ?? currentBlockIndex;
152
+ const acc = blockAccumulators.get(idx);
153
+ if (!acc)
154
+ break;
155
+ // Extract signature from content_block_stop for thinking blocks
156
+ const stopBlock = data.content_block;
157
+ if (acc.type === 'text') {
158
+ contentBlocks.push({ type: 'text', text: acc.text });
159
+ }
160
+ else if (acc.type === 'thinking') {
161
+ const signature = stopBlock?.signature ?? acc.signature;
162
+ contentBlocks.push({ type: 'thinking', text: acc.text, signature });
163
+ }
164
+ else if (acc.type === 'tool_use') {
165
+ let args = {};
166
+ try {
167
+ args = acc.argsJson ? JSON.parse(acc.argsJson) : {};
168
+ }
169
+ catch {
170
+ // JSON is malformed — likely truncated due to max_tokens.
171
+ // Mark with __parse_error so the executor returns a clear error
172
+ // to the LLM instead of a confusing "missing required property" message.
173
+ console.warn(`[AnthropicProvider] Malformed tool call JSON for "${acc.toolName}" ` +
174
+ `(stopReason=${stopReason ?? 'unknown'}, ` +
175
+ `argsJson length=${acc.argsJson.length}): ` +
176
+ acc.argsJson.slice(0, 200));
177
+ args = { __parse_error: true, __raw: acc.argsJson };
178
+ }
179
+ contentBlocks.push({
180
+ type: 'tool_call',
181
+ toolCallId: acc.toolCallId,
182
+ toolName: acc.toolName,
183
+ args,
184
+ });
185
+ yield {
186
+ type: 'tool_call_end',
187
+ toolCallId: acc.toolCallId,
188
+ toolName: acc.toolName,
189
+ args,
190
+ };
191
+ }
192
+ blockAccumulators.delete(idx);
193
+ break;
194
+ }
195
+ case 'message_delta': {
196
+ const delta = data.delta;
197
+ if (delta?.stop_reason) {
198
+ stopReason = mapStopReason(delta.stop_reason);
199
+ }
200
+ const u = data.usage;
201
+ if (u) {
202
+ usage.outputTokens = u.output_tokens ?? usage.outputTokens;
203
+ }
204
+ break;
205
+ }
206
+ case 'message_stop': {
207
+ const message = {
208
+ role: 'assistant',
209
+ content: contentBlocks,
210
+ usage,
211
+ stopReason,
212
+ };
213
+ yield { type: 'done', message, usage };
214
+ return;
215
+ }
216
+ case 'error': {
217
+ const errObj = data.error;
218
+ const errMsg = errObj?.message || 'Unknown Anthropic error';
219
+ const errType = errObj?.type || '';
220
+ // Map Anthropic error types to our classification
221
+ const llmErr = /overloaded/i.test(errType)
222
+ ? new LLMError(errMsg, this.id, 'overloaded', true)
223
+ : /rate/i.test(errType)
224
+ ? new LLMError(errMsg, this.id, 'rate_limit', true)
225
+ : /auth/i.test(errType)
226
+ ? new LLMError(errMsg, this.id, 'auth_error', false)
227
+ : new LLMError(errMsg, this.id, 'server_error', true);
228
+ yield { type: 'error', error: llmErr };
229
+ return;
230
+ }
231
+ default:
232
+ // ping, etc. — ignored
233
+ break;
234
+ }
235
+ }
236
+ // Stream ended without message_stop — emit done with what we have
237
+ const message = {
238
+ role: 'assistant',
239
+ content: contentBlocks,
240
+ usage,
241
+ stopReason,
242
+ };
243
+ yield { type: 'done', message, usage };
244
+ }
245
+ catch (err) {
246
+ yield { type: 'error', error: wrapFetchError(err, this.id) };
247
+ }
248
+ }
249
+ // -----------------------------------------------------------------------
250
+ // complete
251
+ // -----------------------------------------------------------------------
252
+ async complete(request) {
253
+ return consumeStream(this.stream(request));
254
+ }
255
+ // -----------------------------------------------------------------------
256
+ // listModels
257
+ // -----------------------------------------------------------------------
258
+ async listModels(apiKey, baseUrl) {
259
+ const base = (baseUrl || this.defaultBaseUrl).replace(/\/+$/, '');
260
+ const url = `${base}/v1/models`;
261
+ try {
262
+ const res = await fetch(url, {
263
+ method: 'GET',
264
+ headers: {
265
+ 'x-api-key': apiKey,
266
+ 'anthropic-version': ANTHROPIC_VERSION,
267
+ },
268
+ signal: AbortSignal.timeout(15_000),
269
+ });
270
+ if (!res.ok)
271
+ return [];
272
+ const data = await res.json();
273
+ return (data.data || []).map((m) => ({
274
+ id: String(m.id || ''),
275
+ name: String(m.display_name || m.id || ''),
276
+ provider: this.id,
277
+ contextWindow: 200_000,
278
+ maxOutputTokens: 8192,
279
+ supportsThinking: /claude-(sonnet-4|opus-4)/i.test(String(m.id)),
280
+ supportsTools: true,
281
+ supportsImages: true,
282
+ cost: { input: 0, output: 0 },
283
+ }));
284
+ }
285
+ catch {
286
+ return [];
287
+ }
288
+ }
289
+ }
290
+ // ---------------------------------------------------------------------------
291
+ // Internal: request body builder
292
+ // ---------------------------------------------------------------------------
293
+ function buildRequestBody(request) {
294
+ const body = {
295
+ model: request.model,
296
+ messages: convertMessages(request.messages),
297
+ stream: true,
298
+ max_tokens: request.maxTokens ?? DEFAULT_MAX_TOKENS,
299
+ };
300
+ if (request.systemPrompt) {
301
+ body.system = request.systemPrompt;
302
+ }
303
+ if (request.tools && request.tools.length > 0) {
304
+ body.tools = request.tools.map(convertToolDefinition);
305
+ }
306
+ if (request.temperature !== undefined) {
307
+ body.temperature = request.temperature;
308
+ }
309
+ if (request.stopSequences && request.stopSequences.length > 0) {
310
+ body.stop_sequences = request.stopSequences;
311
+ }
312
+ // Extended thinking
313
+ const thinkingBudget = request.thinkingBudget
314
+ ?? (request.thinkingLevel ? THINKING_BUDGET[request.thinkingLevel] : 0);
315
+ if (thinkingBudget > 0) {
316
+ body.thinking = {
317
+ type: 'enabled',
318
+ budget_tokens: thinkingBudget,
319
+ };
320
+ // When thinking is enabled, temperature must not be set (Anthropic requirement)
321
+ delete body.temperature;
322
+ }
323
+ return body;
324
+ }
325
+ // ---------------------------------------------------------------------------
326
+ // Internal: message format conversion
327
+ // ---------------------------------------------------------------------------
328
+ function convertMessages(messages) {
329
+ const result = [];
330
+ for (const msg of messages) {
331
+ switch (msg.role) {
332
+ case 'user':
333
+ result.push({
334
+ role: 'user',
335
+ content: typeof msg.content === 'string'
336
+ ? msg.content
337
+ : convertContentParts(msg.content),
338
+ });
339
+ break;
340
+ case 'assistant':
341
+ result.push({
342
+ role: 'assistant',
343
+ content: convertAssistantContent(msg.content),
344
+ });
345
+ break;
346
+ case 'tool_result':
347
+ result.push({
348
+ role: 'user',
349
+ content: [
350
+ {
351
+ type: 'tool_result',
352
+ tool_use_id: msg.toolCallId,
353
+ content: typeof msg.content === 'string'
354
+ ? msg.content
355
+ : convertContentParts(msg.content),
356
+ is_error: msg.isError || false,
357
+ },
358
+ ],
359
+ });
360
+ break;
361
+ }
362
+ }
363
+ return result;
364
+ }
365
+ function convertContentParts(parts) {
366
+ return parts.map((part) => {
367
+ if (part.type === 'text') {
368
+ return { type: 'text', text: part.text };
369
+ }
370
+ // image
371
+ return {
372
+ type: 'image',
373
+ source: {
374
+ type: 'base64',
375
+ media_type: part.mediaType,
376
+ data: part.data,
377
+ },
378
+ };
379
+ });
380
+ }
381
+ function convertAssistantContent(blocks) {
382
+ return blocks.map((block) => {
383
+ switch (block.type) {
384
+ case 'text':
385
+ return { type: 'text', text: block.text };
386
+ case 'thinking':
387
+ return {
388
+ type: 'thinking',
389
+ thinking: block.text,
390
+ ...(block.signature ? { signature: block.signature } : {}),
391
+ };
392
+ case 'tool_call':
393
+ return {
394
+ type: 'tool_use',
395
+ id: block.toolCallId,
396
+ name: block.toolName,
397
+ input: block.args,
398
+ };
399
+ }
400
+ });
401
+ }
402
+ function convertToolDefinition(tool) {
403
+ return {
404
+ name: tool.name,
405
+ description: tool.description,
406
+ input_schema: tool.parameters,
407
+ };
408
+ }
409
+ // ---------------------------------------------------------------------------
410
+ // Internal: helpers
411
+ // ---------------------------------------------------------------------------
412
+ function mapStopReason(reason) {
413
+ switch (reason) {
414
+ case 'end_turn': return 'end_turn';
415
+ case 'tool_use': return 'tool_use';
416
+ case 'max_tokens': return 'max_tokens';
417
+ case 'stop_sequence': return 'stop_sequence';
418
+ default: return 'end_turn';
419
+ }
420
+ }
@@ -0,0 +1,22 @@
1
+ /**
2
+ * Google Gemini (Generative AI) adapter.
3
+ *
4
+ * Uses `POST /v1beta/models/{model}:streamGenerateContent?alt=sse` for
5
+ * streaming and maps the chunked response into unified StreamEvents.
6
+ *
7
+ * Key differences from Anthropic/OpenAI:
8
+ * - Tool calls (functionCall) arrive as complete objects in a single chunk,
9
+ * not incrementally — so we emit tool_call_start + tool_call_end together.
10
+ * - System instructions use a dedicated `system_instruction` field.
11
+ * - The API key is passed as a query parameter, not a header.
12
+ */
13
+ import type { AssistantMessage, ModelInfo, StreamEvent } from '../types.js';
14
+ import type { LLMProvider, LLMRequest } from '../provider.js';
15
+ export declare class GoogleProvider implements LLMProvider {
16
+ readonly id = "google";
17
+ readonly displayName = "Google";
18
+ readonly defaultBaseUrl = "https://generativelanguage.googleapis.com";
19
+ stream(request: LLMRequest): AsyncIterableIterator<StreamEvent>;
20
+ complete(request: LLMRequest): Promise<AssistantMessage>;
21
+ listModels(apiKey: string, baseUrl?: string): Promise<ModelInfo[]>;
22
+ }