@statelyai/agent 1.1.6 → 2.0.0-next.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 (62) hide show
  1. package/.changeset/cyan-carpets-perform.md +5 -0
  2. package/.changeset/fast-donkeys-argue.md +5 -0
  3. package/.changeset/light-hats-drive.md +9 -0
  4. package/.changeset/old-jobs-check.md +5 -0
  5. package/.changeset/pre.json +13 -0
  6. package/.vscode/launch.json +6 -0
  7. package/CHANGELOG.md +22 -0
  8. package/dist/index.d.mts +262 -171
  9. package/dist/index.d.ts +262 -171
  10. package/dist/index.js +383 -274
  11. package/dist/index.mjs +386 -272
  12. package/examples/chatbot-alt.ts +57 -0
  13. package/examples/chatbot.ts +12 -17
  14. package/examples/cot.ts +26 -23
  15. package/examples/customer-service-sim.ts +107 -0
  16. package/examples/email.ts +37 -41
  17. package/examples/example.ts +6 -6
  18. package/examples/executor.ts +66 -0
  19. package/examples/goal.ts +12 -12
  20. package/examples/helpers/helpers.ts +26 -14
  21. package/examples/joke.ts +79 -76
  22. package/examples/jugs.ts +125 -0
  23. package/examples/multi.ts +5 -5
  24. package/examples/newspaper.ts +98 -104
  25. package/examples/number.ts +6 -5
  26. package/examples/raffle.ts +11 -12
  27. package/examples/river-crossing.ts +140 -0
  28. package/examples/sandbox.ts +1 -1
  29. package/examples/simple.ts +5 -3
  30. package/examples/summary.ts +121 -0
  31. package/examples/support.ts +6 -6
  32. package/examples/ticTacToe.ts +86 -45
  33. package/examples/todo.ts +7 -7
  34. package/examples/tutor.ts +15 -15
  35. package/examples/verify.ts +3 -3
  36. package/examples/weather.ts +6 -9
  37. package/examples/wiki.ts +27 -8
  38. package/examples/word.ts +16 -11
  39. package/package.json +16 -11
  40. package/readme.md +1 -1
  41. package/src/agent-experimental.ts +1 -1
  42. package/src/agent.test.ts +243 -214
  43. package/src/agent.ts +286 -95
  44. package/src/decide.test.ts +276 -0
  45. package/src/decide.ts +163 -0
  46. package/src/index.ts +1 -1
  47. package/src/middleware.ts +91 -0
  48. package/src/mockModel.ts +47 -0
  49. package/src/planners/shortestPath.test.ts +94 -0
  50. package/src/planners/shortestPath.ts +177 -0
  51. package/src/planners/simple.ts +105 -0
  52. package/src/strategies/chain-of-note.ts +6 -55
  53. package/src/text.ts +51 -144
  54. package/src/types.ts +187 -212
  55. package/src/utils.ts +48 -4
  56. package/vitest.config.ts +9 -3
  57. package/src/adapters/vercel.ts +0 -7
  58. package/src/decision.test.ts +0 -179
  59. package/src/decision.ts +0 -84
  60. package/src/memory.ts +0 -25
  61. package/src/planners/shortestPathPlanner.ts +0 -22
  62. package/src/planners/simplePlanner.ts +0 -139
@@ -2,13 +2,14 @@ import { createAgent, fromDecision } from '../src';
2
2
  import { assign, createActor, log, setup } from 'xstate';
3
3
  import { z } from 'zod';
4
4
  import { openai } from '@ai-sdk/openai';
5
- import { getFromTerminal } from './helpers/helpers';
5
+ import { fromTerminal } from './helpers/helpers';
6
6
 
7
7
  const agent = createAgent({
8
- name: 'number-guesser',
8
+ id: 'number-guesser',
9
9
  model: openai('gpt-3.5-turbo-1106'),
10
10
  events: {
11
11
  'agent.guess': z.object({
12
+ reasoning: z.string().describe('The reasoning for the guess'),
12
13
  number: z.number().min(1).max(10).describe('The number guessed'),
13
14
  }),
14
15
  },
@@ -24,7 +25,7 @@ const machine = setup({
24
25
  },
25
26
  actors: {
26
27
  agent: fromDecision(agent),
27
- getFromTerminal,
28
+ getFromTerminal: fromTerminal,
28
29
  },
29
30
  }).createMachine({
30
31
  context: {
@@ -39,7 +40,7 @@ const machine = setup({
39
40
  input: 'Enter a number between 1 and 10',
40
41
  onDone: {
41
42
  actions: assign({
42
- answer: (x) => +x.event.output,
43
+ answer: ({ event }) => +event.output,
43
44
  }),
44
45
  target: 'guessing',
45
46
  },
@@ -78,7 +79,7 @@ const machine = setup({
78
79
  event.number,
79
80
  ],
80
81
  }),
81
- log((x) => x.event.number),
82
+ log(({ event }) => `${event.number} (${event.reasoning})`),
82
83
  ],
83
84
  target: 'guessing',
84
85
  reenter: true,
@@ -2,11 +2,11 @@ import { z } from 'zod';
2
2
  import { createAgent, fromDecision } from '../src';
3
3
  import { openai } from '@ai-sdk/openai';
4
4
  import { assign, createActor, log, setup } from 'xstate';
5
- import { getFromTerminal } from './helpers/helpers';
5
+ import { fromTerminal } from './helpers/helpers';
6
6
 
7
7
  const agent = createAgent({
8
- name: 'raffle-chooser',
9
- model: openai('gpt-4-turbo'),
8
+ id: 'raffle-chooser',
9
+ model: openai('gpt-4o-mini'),
10
10
  events: {
11
11
  'agent.collectEntries': z.object({}).describe('Collect more entries'),
12
12
  'agent.draw': z.object({}).describe('Draw a winner'),
@@ -27,11 +27,10 @@ const machine = setup({
27
27
  lastInput: string | null;
28
28
  entries: string[];
29
29
  },
30
- events: {} as typeof agent.types.events | { type: 'draw' },
30
+ events: agent.types.events,
31
31
  },
32
- actors: { agent: fromDecision(agent), getFromTerminal },
32
+ actors: { agent: fromDecision(agent), getFromTerminal: fromTerminal },
33
33
  }).createMachine({
34
- /** @xstate-layout N4IgpgJg5mDOIC5QAoC2BDAxgCwJYDswBKAOjHwBcwAnAqAYggHtCSCA3JgazBLSzyFS5KrXxQEHJpnQVcLANoAGALrKViUAAcmsXHJaaQAD0QAmAOwBmEmYCsADgCMZgJxmALE4ceHSuwA0IACe5gBsZiR29q5KPnaOrmEOAL4pQfw4BMQkEGCiqAR09OgwlCSYTAA2VWCYFACilLRw6kY6egb4RqYIZg52JB52Ya5jSq5+To4eQaEILhYkA0qjVlYeSlsOYWFpGRhZQrn5NIX4xaUiudToAO5tSCAd+vLdT727SiRKHsl2FjCwxcrlmIUQTl2ticHk26ycMPsqXSIEyghyEFud0uZQoJGoYB01AoAHUCIRqI9tLpXoYPohXBYnCQnP4HK4nFYLF5vK45hCPDYmVtdk5uYzuWkUfgmHl4E80dkiO0aV0eogALRhfkIDWDMZjJn9MxhKwjSb7VGHdHCSg0OgqzpvdUIDxmHWc5l2caeKxhVYDFyWxXHPIFIriR2096gXoeVw2VmgiIOBzWJRiqwe2FRdzcpRWE0JPPB61Km73B1PF5q+kIPweEhWMWjCz+BJKJketwkQEWCyuOywv0WaJ2UsCcvY-AUqO12MQls-Ue+Px2KzspweoHLXxsjO-eNmMxSlJAA */
35
34
  context: {
36
35
  lastInput: null,
37
36
  entries: [],
@@ -56,10 +55,10 @@ const machine = setup({
56
55
  determining: {
57
56
  invoke: {
58
57
  src: 'agent',
59
- input: {
60
- context: true,
58
+ input: ({ context }) => ({
59
+ context,
61
60
  goal: 'If the last input explicitly says to end the drawing and/or choose a winner, start the drawing process. Otherwise, get more entries.',
62
- },
61
+ }),
63
62
  },
64
63
  on: {
65
64
  'agent.collectEntries': {
@@ -76,10 +75,10 @@ const machine = setup({
76
75
  entry: log('And the winner is...'),
77
76
  invoke: {
78
77
  src: 'agent',
79
- input: {
80
- context: true,
78
+ input: ({ context }) => ({
79
+ context,
81
80
  goal: 'Choose the technology that sounds most exciting to you from the entries. Be as unbiased as possible in your choice. Explain why you chose the winning entry.',
82
- },
81
+ }),
83
82
  },
84
83
  on: {
85
84
  'agent.reportWinner': {
@@ -0,0 +1,140 @@
1
+ import { createAgent } from '../src';
2
+ import { assign, createActor, setup } from 'xstate';
3
+ import { openai } from '@ai-sdk/openai';
4
+ import { z } from 'zod';
5
+ import { experimental_createShortestPathPlanner } from '../src/planners/shortestPath';
6
+
7
+ const agent = createAgent({
8
+ id: 'river-crossing-solver',
9
+ model: openai('gpt-4'),
10
+ events: {
11
+ takeWolf: z
12
+ .object({
13
+ reasoning: z.string().describe('Reasoning for taking the wolf across'),
14
+ })
15
+ .describe('Take wolf across the river'),
16
+ takeGoat: z
17
+ .object({
18
+ reasoning: z.string().describe('Reasoning for taking the goat across'),
19
+ })
20
+ .describe('Take goat across the river'),
21
+ takeCabbage: z
22
+ .object({
23
+ reasoning: z
24
+ .string()
25
+ .describe('Reasoning for taking the cabbage across'),
26
+ })
27
+ .describe('Take cabbage across the river'),
28
+ returnEmpty: z
29
+ .object({
30
+ reasoning: z.string().describe('Reasoning for returning alone'),
31
+ })
32
+ .describe('Return across river alone'),
33
+ },
34
+ context: {
35
+ leftBank: z
36
+ .array(z.enum(['wolf', 'goat', 'cabbage']))
37
+ .describe('Items on the left bank'),
38
+ rightBank: z
39
+ .array(z.enum(['wolf', 'goat', 'cabbage']))
40
+ .describe('Items on the right bank'),
41
+ farmerPosition: z
42
+ .enum(['left', 'right'])
43
+ .describe('Which bank the farmer is on'),
44
+ },
45
+ });
46
+
47
+ const riverCrossingMachine = setup({
48
+ types: {
49
+ context: agent.types.context,
50
+ events: agent.types.events,
51
+ },
52
+ }).createMachine({
53
+ initial: 'solving',
54
+ context: {
55
+ leftBank: ['wolf', 'goat', 'cabbage'],
56
+ rightBank: [],
57
+ farmerPosition: 'left',
58
+ },
59
+ states: {
60
+ solving: {
61
+ always: {
62
+ guard: ({ context }) => context.rightBank.length === 3,
63
+ target: 'success',
64
+ },
65
+ on: {
66
+ takeWolf: {
67
+ guard: ({ context }) =>
68
+ context.leftBank.includes('wolf') &&
69
+ context.farmerPosition === 'left',
70
+ actions: assign(({ context }) => ({
71
+ leftBank: context.leftBank.filter((item) => item !== 'wolf'),
72
+ rightBank: [...context.rightBank, 'wolf'],
73
+ farmerPosition: 'right',
74
+ })),
75
+ },
76
+ takeGoat: {
77
+ guard: ({ context }) =>
78
+ context.leftBank.includes('goat') &&
79
+ context.farmerPosition === 'left',
80
+ actions: assign(({ context }) => ({
81
+ leftBank: context.leftBank.filter((item) => item !== 'goat'),
82
+ rightBank: [...context.rightBank, 'goat'],
83
+ farmerPosition: 'right',
84
+ })),
85
+ },
86
+ takeCabbage: {
87
+ guard: ({ context }) =>
88
+ context.leftBank.includes('cabbage') &&
89
+ context.farmerPosition === 'left',
90
+ actions: assign(({ context }) => ({
91
+ leftBank: context.leftBank.filter((item) => item !== 'cabbage'),
92
+ rightBank: [...context.rightBank, 'cabbage'],
93
+ farmerPosition: 'right',
94
+ })),
95
+ },
96
+ returnEmpty: {
97
+ actions: assign(({ context }) => ({
98
+ farmerPosition:
99
+ context.farmerPosition === 'left' ? 'right' : 'left',
100
+ })),
101
+ },
102
+ },
103
+ },
104
+ success: {
105
+ type: 'final',
106
+ },
107
+ },
108
+ });
109
+
110
+ let maxTries = 0;
111
+ async function main() {
112
+ const riverActor = createActor(riverCrossingMachine).start();
113
+
114
+ while (riverActor.getSnapshot().value !== 'success') {
115
+ maxTries++;
116
+ if (maxTries > 20) {
117
+ console.log('Max tries reached');
118
+ throw new Error('Max tries reached');
119
+ }
120
+ const decision = await agent.decide({
121
+ machine: riverCrossingMachine,
122
+ goal: 'Get all items safely across the river. Remember: Cannot leave wolf with goat or goat with cabbage unattended.',
123
+ state: riverActor.getSnapshot(),
124
+ planner: experimental_createShortestPathPlanner(),
125
+ });
126
+
127
+ console.log(decision?.nextEvent);
128
+
129
+ if (decision?.nextEvent) {
130
+ riverActor.send(decision.nextEvent);
131
+ console.log(riverActor.getSnapshot().context);
132
+ } else {
133
+ console.log('No decision made');
134
+ }
135
+ }
136
+
137
+ console.log('Successfully crossed the river!');
138
+ }
139
+
140
+ main();
@@ -4,7 +4,7 @@ import { openai } from '@ai-sdk/openai';
4
4
  import { createMachine } from 'xstate';
5
5
 
6
6
  const agent = createAgent({
7
- model: openai('gpt-4o'),
7
+ model: openai('gpt-4o-mini'),
8
8
  events: {
9
9
  doSomething: z.object({}).describe('Do something'),
10
10
  },
@@ -4,8 +4,8 @@ import { setup, createActor } from 'xstate';
4
4
  import { openai } from '@ai-sdk/openai';
5
5
 
6
6
  const agent = createAgent({
7
- name: 'simple',
8
- model: openai('gpt-3.5-turbo-16k-0613'),
7
+ id: 'simple',
8
+ model: openai('gpt-4o-mini'),
9
9
  events: {
10
10
  'agent.thought': z.object({
11
11
  text: z.string().describe('The text of the thought'),
@@ -21,7 +21,9 @@ const machine = setup({
21
21
  thinking: {
22
22
  invoke: {
23
23
  src: 'agent',
24
- input: 'Think about a random topic, and then share that thought.',
24
+ input: {
25
+ goal: 'Think about a random topic, and then share that thought.',
26
+ },
25
27
  },
26
28
  on: {
27
29
  'agent.thought': {
@@ -0,0 +1,121 @@
1
+ import { createAgent, fromDecision } from '../src';
2
+ import { assign, createActor, setup } from 'xstate';
3
+ import { z } from 'zod';
4
+ import { openai } from '@ai-sdk/openai';
5
+ import { fromTerminal } from './helpers/helpers';
6
+
7
+ const agent = createAgent({
8
+ id: 'summarizing-chat',
9
+ model: openai('gpt-4o'),
10
+ events: {
11
+ 'agent.respond': z.object({
12
+ response: z.string().describe('The response from the agent'),
13
+ }),
14
+ 'agent.summarize': z.object({
15
+ summary: z.string().describe('Summary of the conversation history'),
16
+ }),
17
+ },
18
+ context: {
19
+ messages: z.array(
20
+ z.object({
21
+ role: z.enum(['user', 'assistant']),
22
+ content: z.string(),
23
+ })
24
+ ),
25
+ summary: z.string().nullable(),
26
+ },
27
+ });
28
+
29
+ const machine = setup({
30
+ types: {
31
+ context: agent.types.context,
32
+ events: agent.types.events,
33
+ },
34
+ actors: {
35
+ agent: fromDecision(agent),
36
+ fromTerminal,
37
+ },
38
+ }).createMachine({
39
+ initial: 'user',
40
+ context: {
41
+ messages: [],
42
+ summary: null,
43
+ },
44
+ states: {
45
+ user: {
46
+ invoke: {
47
+ src: 'fromTerminal',
48
+ input: 'Enter a message:',
49
+ onDone: {
50
+ actions: assign({
51
+ messages: ({ context, event }) => [
52
+ ...context.messages,
53
+ { role: 'user', content: event.output },
54
+ ],
55
+ }),
56
+ target: 'chatting',
57
+ },
58
+ },
59
+ },
60
+ chatting: {
61
+ always: {
62
+ guard: ({ context }) => context.messages.length > 10,
63
+ target: 'summarizing',
64
+ },
65
+ invoke: {
66
+ src: 'agent',
67
+ input: ({ context }) => ({
68
+ goal: 'Respond to the user message',
69
+ context: {
70
+ messages: context.messages,
71
+ summary: context.summary,
72
+ },
73
+ }),
74
+ },
75
+ on: {
76
+ 'agent.respond': {
77
+ actions: assign({
78
+ messages: ({ context, event }) => [
79
+ ...context.messages,
80
+ { role: 'assistant', content: event.response },
81
+ ],
82
+ }),
83
+ target: 'user',
84
+ },
85
+ },
86
+ },
87
+ summarizing: {
88
+ invoke: {
89
+ src: 'agent',
90
+ input: ({ context }) => ({
91
+ goal: 'Create a concise summary of the conversation history',
92
+ context: {
93
+ messages: context.messages,
94
+ previousSummary: context.summary,
95
+ },
96
+ }),
97
+ },
98
+ on: {
99
+ 'agent.summarize': {
100
+ actions: assign({
101
+ summary: ({ event }) => event.summary,
102
+ messages: ({ context }) => context.messages.slice(-3), // Keep last 3 messages
103
+ }),
104
+ target: 'chatting',
105
+ },
106
+ },
107
+ },
108
+ },
109
+ });
110
+
111
+ const actor = createActor(machine);
112
+ actor.subscribe((state) => {
113
+ console.log('Current state:', state.value);
114
+ console.log(
115
+ 'Messages:',
116
+ state.context.messages.map((msg) => `${msg.role}: ${msg.content}`)
117
+ );
118
+ console.log('Summary:', state.context.summary);
119
+ });
120
+
121
+ actor.start();
@@ -4,8 +4,8 @@ import { z } from 'zod';
4
4
  import { createActor, log, setup } from 'xstate';
5
5
 
6
6
  const agent = createAgent({
7
- name: 'support-agent',
8
- model: openai('gpt-4-1106-preview'),
7
+ id: 'support-agent',
8
+ model: openai('gpt-4o-mini'),
9
9
  events: {
10
10
  'agent.respond': z.object({
11
11
  response: z.string().describe('The response from the agent'),
@@ -101,11 +101,11 @@ const machine = setup({
101
101
  technical: {
102
102
  invoke: {
103
103
  src: 'agent',
104
- input: {
105
- context: true,
104
+ input: ({ context }) => ({
105
+ context,
106
106
  system: `You are an expert at diagnosing technical computer issues. You work for a company called LangCorp that sells computers. Help the user to the best of your ability, but be concise in your responses.`,
107
107
  goal: 'Solve the customer issue.',
108
- },
108
+ }),
109
109
  },
110
110
  on: {
111
111
  'agent.technical.solve': {
@@ -123,7 +123,7 @@ const machine = setup({
123
123
  },
124
124
  on: {
125
125
  'agent.endConversation': {
126
- actions: log((x) => x.event),
126
+ actions: log(({ event }) => event),
127
127
  target: 'end',
128
128
  },
129
129
  },
@@ -3,40 +3,53 @@ import { z } from 'zod';
3
3
  import { createAgent, fromDecision, fromTextStream } from '../src';
4
4
  import { openai } from '@ai-sdk/openai';
5
5
 
6
- const agent = createAgent({
7
- name: 'tic-tac-toe-bot',
8
- model: openai('gpt-4-0125-preview'),
9
- events: {
10
- 'agent.x.play': z.object({
11
- index: z
12
- .number()
13
- .min(0)
14
- .max(8)
15
- .describe('The index of the cell to play on'),
16
- }),
17
- 'agent.o.play': z.object({
18
- index: z
19
- .number()
20
- .min(0)
21
- .max(8)
22
- .describe('The index of the cell to play on'),
23
- }),
24
- reset: z.object({}).describe('Reset the game to the initial state'),
25
- },
26
- context: {
27
- board: z
28
- .array(z.union([z.literal(null), z.literal('x'), z.literal('o')]))
29
- .describe('The 3x3 board represented as a 9-element array.'),
30
- moves: z
6
+ const events = {
7
+ 'agent.x.play': z.object({
8
+ reasoning: z.string().describe('The reasoning for the move'),
9
+ index: z
31
10
  .number()
32
11
  .min(0)
33
- .max(9)
34
- .describe('The number of moves made in the game.'),
35
- player: z
36
- .union([z.literal('x'), z.literal('o')])
37
- .describe('The current player (x or o)'),
38
- gameReport: z.string(),
39
- },
12
+ .max(8)
13
+ .describe('The index of the cell for xto play on'),
14
+ }),
15
+ 'agent.o.play': z.object({
16
+ reasoning: z.string().describe('The reasoning for the move'),
17
+ index: z
18
+ .number()
19
+ .min(0)
20
+ .max(8)
21
+ .describe('The index of the cell for o to play on'),
22
+ }),
23
+ };
24
+
25
+ const context = {
26
+ board: z
27
+ .array(z.union([z.literal(null), z.literal('x'), z.literal('o')]))
28
+ .describe('The 3x3 board represented as a 9-element array.'),
29
+ moves: z
30
+ .number()
31
+ .min(0)
32
+ .max(9)
33
+ .describe('The number of moves made in the game.'),
34
+ player: z
35
+ .union([z.literal('x'), z.literal('o')])
36
+ .describe('The current player (x or o)'),
37
+ gameReport: z.string(),
38
+ lastReason: z.string(),
39
+ };
40
+
41
+ const xAgent = createAgent({
42
+ id: 'tic-tac-toe-learner',
43
+ model: openai('gpt-4o-mini'),
44
+ events,
45
+ context,
46
+ });
47
+
48
+ const oAgent = createAgent({
49
+ id: 'tic-tac-toe-noob',
50
+ model: openai('gpt-4o-mini'),
51
+ events,
52
+ context,
40
53
  });
41
54
 
42
55
  type Player = 'x' | 'o';
@@ -46,7 +59,8 @@ const initialContext = {
46
59
  moves: 0,
47
60
  player: 'x' as Player,
48
61
  gameReport: '',
49
- } satisfies typeof agent.types.context;
62
+ lastReason: '',
63
+ } satisfies typeof xAgent.types.context;
50
64
 
51
65
  function getWinner(board: typeof initialContext.board): Player | null {
52
66
  const lines = [
@@ -69,12 +83,15 @@ function getWinner(board: typeof initialContext.board): Player | null {
69
83
 
70
84
  export const ticTacToeMachine = setup({
71
85
  types: {
72
- context: agent.types.context,
73
- events: agent.types.events,
86
+ context: xAgent.types.context,
87
+ events: {} as
88
+ | typeof xAgent.types.events
89
+ | {
90
+ type: 'reset';
91
+ },
74
92
  },
75
93
  actors: {
76
- agent: fromDecision(agent),
77
- gameReporter: fromTextStream(agent),
94
+ gameReporter: fromTextStream(xAgent),
78
95
  },
79
96
  actions: {
80
97
  updateBoard: assign({
@@ -90,14 +107,14 @@ export const ticTacToeMachine = setup({
90
107
  resetGame: assign(initialContext),
91
108
  printBoard: ({ context }) => {
92
109
  // Print the context.board in a 3 x 3 grid format
93
- let boardString = '';
110
+ let boardString = `${context.lastReason}\n`;
94
111
  for (let i = 0; i < context.board.length; i++) {
95
112
  if ([0, 3, 6].includes(i)) {
96
113
  boardString += context.board[i] ?? ' ';
97
114
  } else {
98
115
  boardString += ' | ' + (context.board[i] ?? ' ');
99
116
  if ([2, 5].includes(i)) {
100
- boardString += '\n--+---+--\n';
117
+ boardString += `\n--+---+--\n`;
101
118
  }
102
119
  }
103
120
  }
@@ -142,7 +159,12 @@ export const ticTacToeMachine = setup({
142
159
  {
143
160
  target: 'o',
144
161
  guard: 'isValidMove',
145
- actions: 'updateBoard',
162
+ actions: [
163
+ assign({
164
+ lastReason: ({ event }) => event.reasoning,
165
+ }),
166
+ 'updateBoard',
167
+ ],
146
168
  },
147
169
  { target: 'x', reenter: true },
148
170
  ],
@@ -155,7 +177,12 @@ export const ticTacToeMachine = setup({
155
177
  {
156
178
  target: 'x',
157
179
  guard: 'isValidMove',
158
- actions: 'updateBoard',
180
+ actions: [
181
+ assign({
182
+ lastReason: ({ event }) => event.reasoning,
183
+ }),
184
+ 'updateBoard',
185
+ ],
159
186
  },
160
187
  { target: 'o', reenter: true },
161
188
  ],
@@ -169,7 +196,7 @@ export const ticTacToeMachine = setup({
169
196
  src: 'gameReporter',
170
197
  input: ({ context }) => ({
171
198
  context: {
172
- events: agent.getObservations().map((o) => o.event),
199
+ events: xAgent.getObservations().map((o) => o.event),
173
200
  board: context.board,
174
201
  },
175
202
  prompt: 'Provide a short game report analyzing the game.',
@@ -207,10 +234,24 @@ export const ticTacToeMachine = setup({
207
234
 
208
235
  const actor = createActor(ticTacToeMachine);
209
236
 
210
- agent.interact(actor, (observed) => {
211
- if (observed.state.matches('playing')) {
237
+ xAgent.interact(actor, (observed) => {
238
+ if (observed.state.matches({ playing: 'x' })) {
239
+ return {
240
+ goal: `You are playing a game of tic tac toe. This is the current game state. The 3x3 board is represented by a 9-element array. The first element is the top-left cell, the second element is the top-middle cell, the third element is the top-right cell, the fourth element is the middle-left cell, and so on. The value of each cell is either null, x, or o. The value of null means that the cell is empty.
241
+
242
+ ${JSON.stringify(observed.state.context, null, 2)}
243
+
244
+ Execute the single best next move to try to win the game. Do not play on an existing cell.`,
245
+ };
246
+ }
247
+
248
+ return;
249
+ });
250
+
251
+ oAgent.interact(actor, (observed) => {
252
+ if (observed.state.matches({ playing: 'o' })) {
212
253
  return {
213
- goal: `You are playing a game of tic tac toe. This is the current game state. The 3x3 board is represented by a 9-element array. The first element is the top-left cell, the second element is the top-middle cell, the third element is the top-right cell, the fourth element is the middle-left cell, and so on. The value of each cell is either null, x, or o. The value of null means that the cell is empty. The value of x means that the cell is occupied by an x. The value of o means that the cell is occupied by an o.
254
+ goal: `You are playing a game of tic tac toe. This is the current game state. The 3x3 board is represented by a 9-element array. The first element is the top-left cell, the second element is the top-middle cell, the third element is the top-right cell, the fourth element is the middle-left cell, and so on. The value of each cell is either null, x, or o. The value of null means that the cell is empty.
214
255
 
215
256
  ${JSON.stringify(observed.state.context, null, 2)}
216
257
 
package/examples/todo.ts CHANGED
@@ -2,11 +2,11 @@ import { assign, setup, assertEvent, createActor, createMachine } from 'xstate';
2
2
  import { z } from 'zod';
3
3
  import { createAgent, fromDecision } from '../src';
4
4
  import { openai } from '@ai-sdk/openai';
5
- import { getFromTerminal } from './helpers/helpers';
5
+ import { fromTerminal } from './helpers/helpers';
6
6
 
7
7
  const agent = createAgent({
8
- name: 'todo',
9
- model: openai('gpt-4o'),
8
+ id: 'todo',
9
+ model: openai('gpt-4o-mini'),
10
10
  events: {
11
11
  addTodo: z.object({
12
12
  title: z.string().min(1).max(100).describe('The title of the todo'),
@@ -40,7 +40,7 @@ const machine = setup({
40
40
  | typeof agent.types.events
41
41
  | { type: 'assist'; command: string },
42
42
  },
43
- actors: { agent: fromDecision(agent), getFromTerminal },
43
+ actors: { agent: fromDecision(agent), getFromTerminal: fromTerminal },
44
44
  }).createMachine({
45
45
  context: {
46
46
  command: null,
@@ -118,10 +118,10 @@ const machine = setup({
118
118
  assisting: {
119
119
  invoke: {
120
120
  src: 'agent',
121
- input: (x) => ({
121
+ input: ({ context }) => ({
122
122
  context: {
123
- command: x.context.command,
124
- todos: x.context.todos,
123
+ command: context.command,
124
+ todos: context.todos,
125
125
  },
126
126
  goal: 'Interpret the command as an action for this todo list; for example, "I need donuts" would add a todo item with the message "Get donuts".',
127
127
  }),