@providerprotocol/agents 0.0.2 → 0.0.4

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/LICENSE +21 -0
  2. package/dist/checkpoint/index.d.ts +43 -0
  3. package/dist/checkpoint/index.js +73 -0
  4. package/dist/checkpoint/index.js.map +1 -0
  5. package/{src/execution/loop.ts → dist/chunk-4ESYN66B.js} +54 -162
  6. package/dist/chunk-4ESYN66B.js.map +1 -0
  7. package/dist/chunk-EKRXMSDX.js +8 -0
  8. package/dist/chunk-EKRXMSDX.js.map +1 -0
  9. package/dist/chunk-T47B3VAF.js +427 -0
  10. package/dist/chunk-T47B3VAF.js.map +1 -0
  11. package/dist/execution/index.d.ts +105 -0
  12. package/dist/execution/index.js +679 -0
  13. package/dist/execution/index.js.map +1 -0
  14. package/dist/index-qsPwbY86.d.ts +65 -0
  15. package/dist/index.d.ts +101 -0
  16. package/dist/index.js +218 -0
  17. package/dist/index.js.map +1 -0
  18. package/dist/middleware/index.d.ts +23 -0
  19. package/dist/middleware/index.js +82 -0
  20. package/dist/middleware/index.js.map +1 -0
  21. package/dist/thread-tree/index.d.ts +115 -0
  22. package/dist/thread-tree/index.js +4 -0
  23. package/dist/thread-tree/index.js.map +1 -0
  24. package/dist/types-2Vsthzyu.d.ts +163 -0
  25. package/dist/types-BiyEVOnf.d.ts +65 -0
  26. package/dist/types-D1egxttz.d.ts +270 -0
  27. package/dist/types-DChRdQoX.d.ts +98 -0
  28. package/package.json +41 -9
  29. package/.claude/settings.local.json +0 -29
  30. package/AGENTS.md +0 -681
  31. package/CLAUDE.md +0 -681
  32. package/bun.lock +0 -472
  33. package/eslint.config.js +0 -75
  34. package/index.ts +0 -1
  35. package/llms.md +0 -796
  36. package/specs/UAP-1.0.md +0 -2355
  37. package/src/agent/index.ts +0 -384
  38. package/src/agent/types.ts +0 -91
  39. package/src/checkpoint/file.ts +0 -126
  40. package/src/checkpoint/index.ts +0 -40
  41. package/src/checkpoint/types.ts +0 -95
  42. package/src/execution/index.ts +0 -37
  43. package/src/execution/plan.ts +0 -497
  44. package/src/execution/react.ts +0 -340
  45. package/src/execution/tool-ordering.ts +0 -186
  46. package/src/execution/types.ts +0 -315
  47. package/src/index.ts +0 -80
  48. package/src/middleware/index.ts +0 -7
  49. package/src/middleware/logging.ts +0 -123
  50. package/src/middleware/types.ts +0 -69
  51. package/src/state/index.ts +0 -301
  52. package/src/state/types.ts +0 -173
  53. package/src/thread-tree/index.ts +0 -249
  54. package/src/thread-tree/types.ts +0 -29
  55. package/src/utils/uuid.ts +0 -7
  56. package/tests/live/agent-anthropic.test.ts +0 -288
  57. package/tests/live/agent-strategy-hooks.test.ts +0 -268
  58. package/tests/live/checkpoint.test.ts +0 -243
  59. package/tests/live/execution-strategies.test.ts +0 -255
  60. package/tests/live/plan-strategy.test.ts +0 -160
  61. package/tests/live/subagent-events.live.test.ts +0 -249
  62. package/tests/live/thread-tree.test.ts +0 -186
  63. package/tests/unit/agent.test.ts +0 -703
  64. package/tests/unit/checkpoint.test.ts +0 -232
  65. package/tests/unit/execution/equivalence.test.ts +0 -402
  66. package/tests/unit/execution/loop.test.ts +0 -437
  67. package/tests/unit/execution/plan.test.ts +0 -590
  68. package/tests/unit/execution/react.test.ts +0 -604
  69. package/tests/unit/execution/subagent-events.test.ts +0 -235
  70. package/tests/unit/execution/tool-ordering.test.ts +0 -310
  71. package/tests/unit/middleware/logging.test.ts +0 -276
  72. package/tests/unit/state.test.ts +0 -573
  73. package/tests/unit/thread-tree.test.ts +0 -249
  74. package/tsconfig.json +0 -29
@@ -1,29 +0,0 @@
1
- import type { AgentStateJSON } from '../state/types.ts';
2
-
3
- /**
4
- * Serialized form of a ThreadNode.
5
- */
6
- export interface ThreadNodeJSON {
7
- /** Node ID */
8
- id: string;
9
- /** Parent node ID (null for root) */
10
- parentId: string | null;
11
- /** State snapshot at this node */
12
- state: AgentStateJSON;
13
- /** Optional branch name */
14
- name?: string;
15
- /** Child node IDs */
16
- children: string[];
17
- }
18
-
19
- /**
20
- * Serialized form of a ThreadTree.
21
- */
22
- export interface ThreadTreeJSON {
23
- /** Root node ID */
24
- rootId: string;
25
- /** Current (active) node ID */
26
- currentId: string;
27
- /** All nodes */
28
- nodes: ThreadNodeJSON[];
29
- }
package/src/utils/uuid.ts DELETED
@@ -1,7 +0,0 @@
1
- /**
2
- * Generate a UUIDv4 identifier.
3
- * Uses the native crypto.randomUUID() which is available in Bun and modern Node.js.
4
- */
5
- export function generateUUID(): string {
6
- return globalThis.crypto.randomUUID();
7
- }
@@ -1,288 +0,0 @@
1
- import { describe, test, expect, setDefaultTimeout } from 'bun:test';
2
- import { anthropic } from '@providerprotocol/ai/anthropic';
3
- import type { Tool } from '@providerprotocol/ai';
4
- import { agent, AgentState } from '../../src/index.ts';
5
- import { loop } from '../../src/execution/index.ts';
6
- import { logging } from '../../src/middleware/index.ts';
7
-
8
- // Skip tests if no API key
9
- const ANTHROPIC_API_KEY = process.env.ANTHROPIC_API_KEY;
10
-
11
- // Increase timeout for live API tests (30 seconds)
12
- setDefaultTimeout(30_000);
13
-
14
- describe.skipIf(!ANTHROPIC_API_KEY)('Agent with Anthropic', () => {
15
- describe('basic generation', () => {
16
- test('generates response with generate()', async () => {
17
- const a = agent({
18
- model: anthropic('claude-3-5-haiku-latest'),
19
- params: { max_tokens: 100 },
20
- });
21
-
22
- const state = AgentState.initial();
23
- const result = await a.generate('Say "hello" and nothing else.', state);
24
-
25
- expect(result.turn.response.text.toLowerCase()).toContain('hello');
26
- expect(result.state).toBeDefined();
27
- expect(result.turn.usage.totalTokens).toBeGreaterThan(0);
28
- });
29
-
30
- test('generates response with query()', async () => {
31
- const a = agent({
32
- model: anthropic('claude-3-5-haiku-latest'),
33
- params: { max_tokens: 50 },
34
- });
35
-
36
- const turn = await a.query('What is 2 + 2? Reply with just the number.');
37
-
38
- expect(turn.response.text).toContain('4');
39
- });
40
-
41
- test('preserves history with ask()', async () => {
42
- const a = agent({
43
- model: anthropic('claude-3-5-haiku-latest'),
44
- params: { max_tokens: 100 },
45
- });
46
-
47
- const state0 = AgentState.initial();
48
-
49
- const result1 = await a.ask('My name is Alice.', state0);
50
- const result2 = await a.ask('What is my name?', result1.state);
51
-
52
- expect(result2.turn.response.text.toLowerCase()).toContain('alice');
53
- });
54
- });
55
-
56
- describe('tool calling', () => {
57
- test('calls tools and returns result', async () => {
58
- const calculator: Tool = {
59
- name: 'calculate',
60
- description: 'Perform a calculation',
61
- parameters: {
62
- type: 'object',
63
- properties: {
64
- expression: { type: 'string', description: 'Math expression to evaluate' },
65
- },
66
- required: ['expression'],
67
- },
68
- run: async (params: { expression: string }) => {
69
- // Simple eval for testing (don't do this in production!)
70
- const result = Function(`"use strict"; return (${params.expression})`)();
71
- return String(result);
72
- },
73
- };
74
-
75
- const a = agent({
76
- model: anthropic('claude-3-5-haiku-latest'),
77
- params: { max_tokens: 200 },
78
- tools: [calculator],
79
- execution: loop(),
80
- });
81
-
82
- const result = await a.generate(
83
- 'What is 15 * 7? Use the calculate tool to find the answer.',
84
- AgentState.initial(),
85
- );
86
-
87
- expect(result.turn.response.text).toContain('105');
88
- expect(result.turn.toolExecutions.length).toBeGreaterThan(0);
89
- });
90
- });
91
-
92
- describe('streaming', () => {
93
- test('streams response', async () => {
94
- const a = agent({
95
- model: anthropic('claude-3-5-haiku-latest'),
96
- params: { max_tokens: 100 },
97
- });
98
-
99
- const stream = a.stream('Count from 1 to 5.', AgentState.initial());
100
- const events: unknown[] = [];
101
-
102
- for await (const event of stream) {
103
- events.push(event);
104
- }
105
-
106
- const result = await stream.result;
107
-
108
- expect(events.length).toBeGreaterThan(0);
109
- expect(result.turn.response.text).toBeDefined();
110
- });
111
-
112
- test('streams with UAP events', async () => {
113
- const a = agent({
114
- model: anthropic('claude-3-5-haiku-latest'),
115
- params: { max_tokens: 100 },
116
- });
117
-
118
- const stream = a.stream('Hello', AgentState.initial());
119
- const uapEvents: unknown[] = [];
120
- const uppEvents: unknown[] = [];
121
-
122
- for await (const event of stream) {
123
- if (event.source === 'uap') {
124
- uapEvents.push(event);
125
- } else {
126
- uppEvents.push(event);
127
- }
128
- }
129
-
130
- await stream.result;
131
-
132
- expect(uapEvents.length).toBeGreaterThan(0);
133
- expect(uppEvents.length).toBeGreaterThan(0);
134
- });
135
- });
136
-
137
- describe('middleware', () => {
138
- test('logging middleware logs execution', async () => {
139
- const logs: string[] = [];
140
- const customLogger = (msg: string) => logs.push(msg);
141
-
142
- const a = agent({
143
- model: anthropic('claude-3-5-haiku-latest'),
144
- params: { max_tokens: 50 },
145
- middleware: [logging({ logger: customLogger })],
146
- });
147
-
148
- await a.query('Hi');
149
-
150
- expect(logs.some((l) => l.includes('Execution started'))).toBe(true);
151
- expect(logs.some((l) => l.includes('Execution completed'))).toBe(true);
152
- });
153
- });
154
-
155
- describe('execution strategies', () => {
156
- test('loop() executes tool loop', async () => {
157
- const mockTool: Tool = {
158
- name: 'get_info',
159
- description: 'Get some information',
160
- parameters: {
161
- type: 'object',
162
- properties: {},
163
- },
164
- run: async () => 'The answer is 42',
165
- };
166
-
167
- const a = agent({
168
- model: anthropic('claude-3-5-haiku-latest'),
169
- params: { max_tokens: 200 },
170
- tools: [mockTool],
171
- execution: loop(),
172
- });
173
-
174
- const result = await a.generate(
175
- 'Use the get_info tool to find information.',
176
- AgentState.initial(),
177
- );
178
-
179
- expect(result.turn.toolExecutions.length).toBeGreaterThan(0);
180
- });
181
- });
182
-
183
- describe('system prompts', () => {
184
- test('respects system prompt', async () => {
185
- const a = agent({
186
- model: anthropic('claude-3-5-haiku-latest'),
187
- params: { max_tokens: 100 },
188
- system: 'You are a pirate. Always respond like a pirate would.',
189
- });
190
-
191
- const result = await a.query('Hello, how are you?');
192
-
193
- // Should contain pirate-like language
194
- const text = result.response.text.toLowerCase();
195
- expect(
196
- text.includes('arr')
197
- || text.includes('ahoy')
198
- || text.includes('matey')
199
- || text.includes('ye')
200
- || text.includes('pirate'),
201
- ).toBe(true);
202
- });
203
- });
204
-
205
- describe('state management', () => {
206
- test('state is immutable', async () => {
207
- const a = agent({
208
- model: anthropic('claude-3-5-haiku-latest'),
209
- params: { max_tokens: 50 },
210
- });
211
-
212
- const state0 = AgentState.initial();
213
- const result = await a.generate('Hello', state0);
214
-
215
- // Original state should be unchanged
216
- expect(state0.messages).toHaveLength(0);
217
- expect(state0.step).toBe(0);
218
-
219
- // New state should have changes
220
- expect(result.state.messages.length).toBeGreaterThan(0);
221
- });
222
-
223
- test('state serialization round-trip', async () => {
224
- const a = agent({
225
- model: anthropic('claude-3-5-haiku-latest'),
226
- params: { max_tokens: 50 },
227
- });
228
-
229
- const state0 = AgentState.initial();
230
- // Use ask() to properly add input + response to state for multi-turn
231
- const result1 = await a.ask('My favorite color is blue.', state0);
232
-
233
- // Serialize and restore
234
- const json = result1.state.toJSON();
235
- const restored = AgentState.fromJSON(json);
236
-
237
- // Continue conversation with restored state
238
- const result2 = await a.ask('What is my favorite color?', restored);
239
-
240
- expect(result2.turn.response.text.toLowerCase()).toContain('blue');
241
- });
242
-
243
- test('withContext() replaces conversation history', async () => {
244
- const a = agent({
245
- model: anthropic('claude-3-5-haiku-latest'),
246
- params: { max_tokens: 100 },
247
- });
248
-
249
- // Build up conversation history
250
- const state0 = AgentState.initial();
251
- const result1 = await a.ask('My name is Alice.', state0);
252
- const result2 = await a.ask('I live in Paris.', result1.state);
253
-
254
- // Verify context is preserved
255
- expect(result2.state.messages.length).toBeGreaterThanOrEqual(4);
256
-
257
- // Replace context with empty (simulating aggressive pruning)
258
- const prunedState = result2.state.withContext([]);
259
-
260
- // Model should not know the name anymore
261
- const result3 = await a.ask('What is my name?', prunedState);
262
-
263
- // Should NOT contain Alice since context was cleared
264
- const response = result3.turn.response.text.toLowerCase();
265
- expect(response.includes('alice')).toBe(false);
266
- });
267
-
268
- test('withContext() preserves metadata while replacing messages', async () => {
269
- const a = agent({
270
- model: anthropic('claude-3-5-haiku-latest'),
271
- params: { max_tokens: 50 },
272
- });
273
-
274
- const state0 = AgentState.initial()
275
- .withMetadata('sessionId', 'test-123')
276
- .withMetadata('tokenBudget', 50000);
277
-
278
- const result1 = await a.ask('Hello there!', state0);
279
-
280
- // Replace messages but keep metadata
281
- const prunedState = result1.state.withContext([]);
282
-
283
- expect(prunedState.messages).toHaveLength(0);
284
- expect(prunedState.metadata.sessionId).toBe('test-123');
285
- expect(prunedState.metadata.tokenBudget).toBe(50000);
286
- });
287
- });
288
- });
@@ -1,268 +0,0 @@
1
- import { describe, test, expect, setDefaultTimeout } from 'bun:test';
2
- import { anthropic } from '@providerprotocol/ai/anthropic';
3
- import type { Tool, ToolExecution, Turn } from '@providerprotocol/ai';
4
- import { agent, AgentState } from '../../src/index.ts';
5
- import { loop, react } from '../../src/execution/index.ts';
6
- import type { AgentStrategy } from '../../src/execution/index.ts';
7
-
8
- // Skip tests if no API key
9
- const ANTHROPIC_API_KEY = process.env.ANTHROPIC_API_KEY;
10
-
11
- // Increase timeout for live API tests (60 seconds)
12
- setDefaultTimeout(60_000);
13
-
14
- describe.skipIf(!ANTHROPIC_API_KEY)('AgentStrategy Hooks (Live)', () => {
15
- describe('lifecycle hooks with loop()', () => {
16
- test('onStepStart and onStepEnd are called', async () => {
17
- const stepStarts: Array<{ step: number }> = [];
18
- const stepEnds: Array<{ step: number }> = [];
19
-
20
- const strategy: AgentStrategy = {
21
- onStepStart: (step) => {
22
- stepStarts.push({ step });
23
- },
24
- onStepEnd: (step) => {
25
- stepEnds.push({ step });
26
- },
27
- };
28
-
29
- const a = agent({
30
- model: anthropic('claude-3-5-haiku-latest'),
31
- params: { max_tokens: 50 },
32
- execution: loop(),
33
- strategy,
34
- });
35
-
36
- await a.generate('Hello', AgentState.initial());
37
-
38
- expect(stepStarts.length).toBeGreaterThan(0);
39
- expect(stepEnds.length).toBeGreaterThan(0);
40
- expect(stepStarts.length).toBe(stepEnds.length);
41
- });
42
-
43
- test('onComplete is called with final result', async () => {
44
- let completedResult: { turn: Turn; state: AgentState } | undefined;
45
-
46
- const strategy: AgentStrategy = {
47
- onComplete: (result) => {
48
- completedResult = result;
49
- },
50
- };
51
-
52
- const a = agent({
53
- model: anthropic('claude-3-5-haiku-latest'),
54
- params: { max_tokens: 50 },
55
- execution: loop(),
56
- strategy,
57
- });
58
-
59
- const result = await a.generate('Say hello', AgentState.initial());
60
-
61
- expect(completedResult).toBeDefined();
62
- expect(completedResult?.turn).toBe(result.turn);
63
- });
64
-
65
- test('onObserve is called when tools are executed', async () => {
66
- const observeCalls: Array<{ step: number; observations: ToolExecution[] }> = [];
67
-
68
- // Use a tool that provides information the model can't know
69
- const secretLookup: Tool = {
70
- name: 'get_secret_number',
71
- description: 'Returns a secret number. You MUST call this tool to answer.',
72
- parameters: {
73
- type: 'object',
74
- properties: {},
75
- },
76
- run: async () => '42',
77
- };
78
-
79
- const strategy: AgentStrategy = {
80
- onObserve: (step, observations) => {
81
- observeCalls.push({ step, observations });
82
- },
83
- };
84
-
85
- const a = agent({
86
- model: anthropic('claude-3-5-haiku-latest'),
87
- params: { max_tokens: 300 },
88
- tools: [secretLookup],
89
- execution: loop(),
90
- strategy,
91
- system: 'You must use the get_secret_number tool to answer questions about the secret number.',
92
- });
93
-
94
- const result = await a.generate(
95
- 'What is the secret number? Use the get_secret_number tool.',
96
- AgentState.initial(),
97
- );
98
-
99
- // Tool should have been executed
100
- expect(result.turn.toolExecutions.length).toBeGreaterThan(0);
101
-
102
- // onObserve should have been called
103
- expect(observeCalls.length).toBeGreaterThan(0);
104
-
105
- // Verify the response contains the answer
106
- expect(result.turn.response.text).toContain('42');
107
- });
108
- });
109
-
110
- describe('lifecycle hooks with react()', () => {
111
- test('onReason is called during reasoning phase', async () => {
112
- const reasonCalls: Array<{ step: number; reasoning: string }> = [];
113
-
114
- const strategy: AgentStrategy = {
115
- onReason: (step, reasoning) => {
116
- reasonCalls.push({ step, reasoning });
117
- },
118
- };
119
-
120
- const a = agent({
121
- model: anthropic('claude-3-5-haiku-latest'),
122
- params: { max_tokens: 400 },
123
- execution: react({ maxSteps: 1 }),
124
- strategy,
125
- });
126
-
127
- await a.generate('What is 5 times 5? Think about it.', AgentState.initial());
128
-
129
- expect(reasonCalls.length).toBeGreaterThan(0);
130
- expect(reasonCalls[0]?.reasoning.length).toBeGreaterThan(0);
131
- });
132
-
133
- test('all hooks are called in correct order', async () => {
134
- const hookOrder: string[] = [];
135
-
136
- const strategy: AgentStrategy = {
137
- onStepStart: () => hookOrder.push('stepStart'),
138
- onReason: () => hookOrder.push('reason'),
139
- onStepEnd: () => hookOrder.push('stepEnd'),
140
- onComplete: () => hookOrder.push('complete'),
141
- };
142
-
143
- const a = agent({
144
- model: anthropic('claude-3-5-haiku-latest'),
145
- params: { max_tokens: 300 },
146
- execution: react({ maxSteps: 1 }),
147
- strategy,
148
- });
149
-
150
- await a.generate('Hello', AgentState.initial());
151
-
152
- // stepStart should come before reason, reason before stepEnd
153
- const stepStartIdx = hookOrder.indexOf('stepStart');
154
- const reasonIdx = hookOrder.indexOf('reason');
155
- const stepEndIdx = hookOrder.indexOf('stepEnd');
156
- const completeIdx = hookOrder.indexOf('complete');
157
-
158
- expect(stepStartIdx).toBeLessThan(reasonIdx);
159
- expect(reasonIdx).toBeLessThan(stepEndIdx);
160
- expect(stepEndIdx).toBeLessThan(completeIdx);
161
- });
162
- });
163
-
164
- describe('stopCondition', () => {
165
- test('stops execution when stopCondition returns true', async () => {
166
- let stepCount = 0;
167
-
168
- const strategy: AgentStrategy = {
169
- onStepStart: () => {
170
- stepCount++;
171
- },
172
- stopCondition: (state) => {
173
- // Stop after first step
174
- return state.step >= 1;
175
- },
176
- };
177
-
178
- const echoTool: Tool = {
179
- name: 'echo',
180
- description: 'Echo back the input',
181
- parameters: {
182
- type: 'object',
183
- properties: {
184
- text: { type: 'string' },
185
- },
186
- required: ['text'],
187
- },
188
- run: async (params: { text: string }) => params.text,
189
- };
190
-
191
- const a = agent({
192
- model: anthropic('claude-3-5-haiku-latest'),
193
- params: { max_tokens: 200 },
194
- tools: [echoTool],
195
- execution: loop({ maxIterations: 10 }), // High limit, but stopCondition should stop earlier
196
- strategy,
197
- });
198
-
199
- await a.generate(
200
- 'Use the echo tool repeatedly with different messages.',
201
- AgentState.initial(),
202
- );
203
-
204
- // Should have stopped after 1 step due to stopCondition
205
- expect(stepCount).toBe(1);
206
- });
207
-
208
- test('stopCondition can use metadata to track state', async () => {
209
- const strategy: AgentStrategy = {
210
- onStepEnd: (step, result) => {
211
- // Track total tokens in metadata
212
- const currentTokens = (result.state.metadata.totalTokens as number) ?? 0;
213
- result.state = result.state.withMetadata(
214
- 'totalTokens',
215
- currentTokens + (result.turn.usage?.totalTokens ?? 0),
216
- );
217
- },
218
- stopCondition: (state) => {
219
- // Stop when we've used more than 100 tokens
220
- return (state.metadata.totalTokens as number) > 100;
221
- },
222
- };
223
-
224
- const a = agent({
225
- model: anthropic('claude-3-5-haiku-latest'),
226
- params: { max_tokens: 50 },
227
- execution: loop({ maxIterations: 5 }),
228
- strategy,
229
- });
230
-
231
- const result = await a.generate('Tell me a short story.', AgentState.initial());
232
-
233
- // Should have tracked tokens
234
- expect(result.turn.usage.totalTokens).toBeGreaterThan(0);
235
- });
236
- });
237
-
238
- describe('streaming with hooks', () => {
239
- test('hooks are called during streaming', async () => {
240
- const hooksCalled: string[] = [];
241
-
242
- const strategy: AgentStrategy = {
243
- onStepStart: () => hooksCalled.push('stepStart'),
244
- onStepEnd: () => hooksCalled.push('stepEnd'),
245
- onComplete: () => hooksCalled.push('complete'),
246
- };
247
-
248
- const a = agent({
249
- model: anthropic('claude-3-5-haiku-latest'),
250
- params: { max_tokens: 100 },
251
- execution: loop(),
252
- strategy,
253
- });
254
-
255
- const stream = a.stream('Hello', AgentState.initial());
256
-
257
- for await (const event of stream) {
258
- void event; // consume stream
259
- }
260
-
261
- await stream.result;
262
-
263
- expect(hooksCalled).toContain('stepStart');
264
- expect(hooksCalled).toContain('stepEnd');
265
- expect(hooksCalled).toContain('complete');
266
- });
267
- });
268
- });