@statelyai/agent 1.0.0-beta.0 → 1.0.0

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/dist/index.mjs CHANGED
@@ -9,18 +9,54 @@ import {
9
9
  import { tool } from "ai";
10
10
 
11
11
  // src/utils.ts
12
+ import hash from "object-hash";
12
13
  function getAllTransitions(state) {
13
14
  const nodes = state._nodes;
14
- const transitions = nodes.map((node) => [...node.transitions.values()]).flat(2).map((transition) => ({
15
- ...transition,
16
- guard: typeof transition.guard === "string" ? { type: transition.guard } : transition.guard
17
- // TODO: fix
18
- }));
15
+ const transitions = nodes.map((node) => [...node.transitions.values()]).map((nodeTransitions) => {
16
+ return nodeTransitions.map((nodeEventTransitions) => {
17
+ return nodeEventTransitions.map((transition) => {
18
+ return {
19
+ ...transition,
20
+ guard: typeof transition.guard === "string" ? { type: transition.guard } : transition.guard
21
+ // TODO: fix
22
+ };
23
+ });
24
+ });
25
+ }).flat(2);
26
+ return transitions;
27
+ }
28
+ function getAllMachineTransitions(stateNode) {
29
+ const transitions = [...stateNode.transitions.values()].map((nodeTransitions) => {
30
+ return nodeTransitions.map((transition) => {
31
+ return {
32
+ ...transition,
33
+ guard: typeof transition.guard === "string" ? { type: transition.guard } : transition.guard
34
+ // TODO: fix
35
+ };
36
+ });
37
+ }).flat(2);
38
+ for (const s of Object.values(stateNode.states)) {
39
+ const stateTransitions = getAllMachineTransitions(s);
40
+ transitions.push(...stateTransitions);
41
+ }
19
42
  return transitions;
20
43
  }
21
44
  function wrapInXml(tagName, content) {
22
45
  return `<${tagName}>${content}</${tagName}>`;
23
46
  }
47
+ function randomId() {
48
+ const timestamp = Date.now().toString(36);
49
+ const random = Math.random().toString(36).substring(2, 9);
50
+ return timestamp + random;
51
+ }
52
+ var machineHashes = /* @__PURE__ */ new WeakMap();
53
+ function getMachineHash(machine) {
54
+ if (machineHashes.has(machine)) return machineHashes.get(machine);
55
+ const transitions = getAllMachineTransitions(machine.root);
56
+ const machineHash = hash(transitions);
57
+ machineHashes.set(machine, machineHash);
58
+ return machineHash;
59
+ }
24
60
 
25
61
  // src/templates/defaultText.ts
26
62
  var defaultTextTemplate = (data) => {
@@ -34,99 +70,15 @@ ${data.goal}
34
70
  `.trim();
35
71
  };
36
72
 
37
- // src/planners/simplePlanner.ts
38
- function getTransitions(state, machine) {
39
- if (!machine) {
40
- return [];
41
- }
42
- const resolvedState = machine.resolveState(state);
43
- return getAllTransitions(resolvedState);
44
- }
45
- var simplePlannerPromptTemplate = (data) => {
46
- return `
47
- ${defaultTextTemplate(data)}
48
-
49
- Only make a single tool call to achieve the above goal.
50
- `.trim();
51
- };
52
- async function simplePlanner(agent, input) {
53
- const transitions = input.machine ? getTransitions(input.state, input.machine) : Object.entries(input.events).map(([eventType, { description }]) => ({
54
- eventType,
55
- description
56
- }));
57
- const filter = (eventType) => Object.keys(input.events).includes(eventType);
58
- const functionNameMapping = {};
59
- const toolTransitions = transitions.filter((t) => {
60
- return filter(t.eventType);
61
- }).map((t) => {
62
- const name = t.eventType.replace(/\./g, "_");
63
- functionNameMapping[name] = t.eventType;
64
- return {
65
- type: "function",
66
- eventType: t.eventType,
67
- description: t.description,
68
- name
69
- };
70
- });
71
- const toolMap = {};
72
- for (const toolTransitionData of toolTransitions) {
73
- const toolZodType = input.events?.[toolTransitionData.eventType];
74
- if (!toolZodType) {
75
- continue;
76
- }
77
- toolMap[toolTransitionData.name] = tool({
78
- description: toolZodType?.description ?? toolTransitionData.description,
79
- parameters: toolZodType,
80
- execute: async (params) => {
81
- const event = {
82
- type: toolTransitionData.eventType,
83
- ...params
84
- };
85
- return event;
86
- }
87
- });
88
- }
89
- const prompt = simplePlannerPromptTemplate({
90
- context: input.state.context,
91
- goal: input.goal
92
- });
93
- const result = await agent.generateText({
94
- prompt,
95
- tools: toolMap,
96
- toolChoice: "required",
97
- ...input
98
- });
99
- const singleResult = result.toolResults[0];
100
- if (!singleResult) {
101
- console.warn("No tool call results returned");
102
- return void 0;
103
- }
104
- return {
105
- goal: input.goal,
106
- state: input.state,
107
- steps: [
108
- {
109
- event: singleResult.result
110
- }
111
- ],
112
- nextEvent: singleResult.result,
113
- sessionId: agent.sessionId,
114
- timestamp: Date.now()
115
- };
116
- }
117
-
118
73
  // src/text.ts
119
74
  import {
120
75
  fromObservable,
121
76
  fromPromise,
122
77
  toObserver
123
78
  } from "xstate";
124
- import { nanoid } from "nanoid";
125
79
  async function getMessages(agent, prompt, options) {
126
80
  let messages = [];
127
- if (options.messages === true) {
128
- messages = agent.select((s) => s.messages);
129
- } else if (typeof options.messages === "function") {
81
+ if (typeof options.messages === "function") {
130
82
  messages = await options.messages(agent);
131
83
  } else if (options.messages) {
132
84
  messages = options.messages;
@@ -143,7 +95,7 @@ async function agentGenerateText(agent, options) {
143
95
  ...options
144
96
  };
145
97
  const template = resolvedOptions.template ?? defaultTextTemplate;
146
- const id = nanoid();
98
+ const id = randomId();
147
99
  const goal = typeof resolvedOptions.prompt === "string" ? resolvedOptions.prompt : await resolvedOptions.prompt(agent);
148
100
  const promptWithContext = template({
149
101
  goal,
@@ -177,7 +129,7 @@ async function agentStreamText(agent, options) {
177
129
  ...options
178
130
  };
179
131
  const template = resolvedOptions.template ?? defaultTextTemplate;
180
- const id = nanoid();
132
+ const id = randomId();
181
133
  const goal = typeof resolvedOptions.prompt === "string" ? resolvedOptions.prompt : await resolvedOptions.prompt(agent);
182
134
  const promptWithContext = template({
183
135
  goal,
@@ -209,7 +161,7 @@ async function agentStreamText(agent, options) {
209
161
  rawResponse: res.rawResponse
210
162
  },
211
163
  content: res.text,
212
- id: nanoid(),
164
+ id: randomId(),
213
165
  timestamp: Date.now(),
214
166
  responseId: id
215
167
  });
@@ -218,14 +170,13 @@ async function agentStreamText(agent, options) {
218
170
  return result;
219
171
  }
220
172
  function fromTextStream(agent, defaultOptions) {
221
- return fromObservable(({ input, self }) => {
222
- const context = input.context === true ? (self._parent?.getSnapshot()).context : input.context;
173
+ return fromObservable(({ input }) => {
223
174
  const observers = /* @__PURE__ */ new Set();
224
175
  (async () => {
225
176
  const result = await agentStreamText(agent, {
226
177
  ...defaultOptions,
227
178
  ...input,
228
- context
179
+ context: input.context
229
180
  });
230
181
  for await (const part of result.fullStream) {
231
182
  if (part.type === "text-delta") {
@@ -249,16 +200,103 @@ function fromTextStream(agent, defaultOptions) {
249
200
  });
250
201
  }
251
202
  function fromText(agent, defaultOptions) {
252
- return fromPromise(async ({ input, self }) => {
253
- const context = input.context === true ? (self._parent?.getSnapshot()).context : input.context;
203
+ return fromPromise(async ({ input }) => {
254
204
  return await agentGenerateText(agent, {
255
205
  ...input,
256
206
  ...defaultOptions,
257
- context
207
+ context: input.context
258
208
  });
259
209
  });
260
210
  }
261
211
 
212
+ // src/planners/simplePlanner.ts
213
+ function getTransitions(state, machine) {
214
+ if (!machine) {
215
+ return [];
216
+ }
217
+ const resolvedState = machine.resolveState(state);
218
+ return getAllTransitions(resolvedState);
219
+ }
220
+ var simplePlannerPromptTemplate = (data) => {
221
+ return `
222
+ ${defaultTextTemplate(data)}
223
+
224
+ Make at most one tool call to achieve the above goal. If the goal cannot be achieved with any tool calls, do not make any tool call.
225
+ `.trim();
226
+ };
227
+ async function simplePlanner(agent, input) {
228
+ const transitions = input.machine ? getTransitions(input.state, input.machine) : Object.entries(input.events).map(([eventType, { description }]) => ({
229
+ eventType,
230
+ description
231
+ }));
232
+ const filter = (eventType) => Object.keys(input.events).includes(eventType);
233
+ const functionNameMapping = {};
234
+ const toolTransitions = transitions.filter((t) => {
235
+ return filter(t.eventType);
236
+ }).map((t) => {
237
+ const name = t.eventType.replace(/\./g, "_");
238
+ functionNameMapping[name] = t.eventType;
239
+ return {
240
+ type: "function",
241
+ eventType: t.eventType,
242
+ description: t.description,
243
+ name
244
+ };
245
+ });
246
+ const toolMap = {};
247
+ for (const toolTransitionData of toolTransitions) {
248
+ const toolZodType = input.events?.[toolTransitionData.eventType];
249
+ if (!toolZodType) {
250
+ continue;
251
+ }
252
+ toolMap[toolTransitionData.name] = tool({
253
+ description: toolZodType?.description ?? toolTransitionData.description,
254
+ parameters: toolZodType,
255
+ execute: async (params) => {
256
+ const event = {
257
+ type: toolTransitionData.eventType,
258
+ ...params
259
+ };
260
+ return event;
261
+ }
262
+ });
263
+ }
264
+ if (!Object.keys(toolMap).length) {
265
+ return void 0;
266
+ }
267
+ const prompt = simplePlannerPromptTemplate({
268
+ context: input.state.context,
269
+ goal: input.goal
270
+ });
271
+ const messages = await getMessages(agent, prompt, input);
272
+ const result = await agent.generateText({
273
+ toolChoice: "required",
274
+ ...input,
275
+ prompt,
276
+ messages,
277
+ tools: toolMap
278
+ });
279
+ const singleResult = result.toolResults[0];
280
+ if (!singleResult) {
281
+ console.log(toolMap);
282
+ console.warn("No tool call results returned");
283
+ return void 0;
284
+ }
285
+ return {
286
+ goal: input.goal,
287
+ state: input.state,
288
+ execute: async (state) => {
289
+ if (JSON.stringify(state) === JSON.stringify(input.state)) {
290
+ return singleResult.result;
291
+ }
292
+ return void 0;
293
+ },
294
+ nextEvent: singleResult.result,
295
+ sessionId: agent.sessionId,
296
+ timestamp: Date.now()
297
+ };
298
+ }
299
+
262
300
  // src/decision.ts
263
301
  import { fromPromise as fromPromise2 } from "xstate";
264
302
  async function agentDecide(agent, options) {
@@ -329,7 +367,6 @@ var vercelAdapter = {
329
367
  };
330
368
 
331
369
  // src/agent.ts
332
- import { nanoid as nanoid2 } from "nanoid";
333
370
  var agentLogic = fromTransition(
334
371
  (state, event, { emit }) => {
335
372
  switch (event.type) {
@@ -386,6 +423,7 @@ function createAgent({
386
423
  description,
387
424
  model,
388
425
  events,
426
+ context,
389
427
  planner = simplePlanner,
390
428
  stringify = JSON.stringify,
391
429
  getMemory,
@@ -414,7 +452,7 @@ function createAgent({
414
452
  agent.addMessage = (messageInput) => {
415
453
  const message = {
416
454
  ...messageInput,
417
- id: messageInput.id ?? nanoid2(),
455
+ id: messageInput.id ?? randomId(),
418
456
  timestamp: messageInput.timestamp ?? Date.now(),
419
457
  sessionId: agent.sessionId
420
458
  };
@@ -424,6 +462,7 @@ function createAgent({
424
462
  });
425
463
  return message;
426
464
  };
465
+ agent.getMessages = () => agent.getSnapshot().context.messages;
427
466
  agent.generateText = (opts) => agentGenerateText(agent, opts);
428
467
  agent.streamText = (opts) => agentStreamText(agent, opts);
429
468
  agent.addFeedback = (feedbackInput) => {
@@ -438,12 +477,17 @@ function createAgent({
438
477
  });
439
478
  return feedback;
440
479
  };
480
+ agent.getFeedback = () => agent.getSnapshot().context.feedback;
441
481
  agent.addObservation = (observationInput) => {
482
+ const { prevState, event, state } = observationInput;
442
483
  const observation = {
443
- ...observationInput,
444
- id: observationInput.id ?? nanoid2(),
484
+ prevState,
485
+ event,
486
+ state,
487
+ id: observationInput.id ?? randomId(),
445
488
  sessionId: agent.sessionId,
446
- timestamp: observationInput.timestamp ?? Date.now()
489
+ timestamp: observationInput.timestamp ?? Date.now(),
490
+ machineHash: observationInput.machine ? getMachineHash(observationInput.machine) : void 0
447
491
  };
448
492
  agent.send({
449
493
  type: "agent.observe",
@@ -451,12 +495,14 @@ function createAgent({
451
495
  });
452
496
  return observation;
453
497
  };
498
+ agent.getObservations = () => agent.getSnapshot().context.observations;
454
499
  agent.addPlan = (plan) => {
455
500
  agent.send({
456
501
  type: "agent.plan",
457
502
  plan
458
503
  });
459
504
  };
505
+ agent.getPlans = () => agent.getSnapshot().context.plans;
460
506
  agent.interact = (actorRef, getInput) => {
461
507
  let prevState = void 0;
462
508
  let subscribed = true;
@@ -483,7 +529,8 @@ function createAgent({
483
529
  const observationInput = {
484
530
  event: inspEvent.event,
485
531
  prevState,
486
- state: inspEvent.snapshot
532
+ state: inspEvent.snapshot,
533
+ machine: actorRef.src
487
534
  };
488
535
  await handleObservation(observationInput);
489
536
  }
@@ -493,7 +540,8 @@ function createAgent({
493
540
  prevState: void 0,
494
541
  event: { type: "" },
495
542
  // TODO: unknown events?
496
- state: actorRef.getSnapshot()
543
+ state: actorRef.getSnapshot(),
544
+ machine: actorRef.src
497
545
  });
498
546
  }
499
547
  return {
@@ -503,12 +551,11 @@ function createAgent({
503
551
  // TODO: make this actually unsubscribe
504
552
  };
505
553
  };
554
+ agent.types = {};
506
555
  agent.start();
507
556
  return agent;
508
557
  }
509
558
  export {
510
- agentDecide,
511
- agentGenerateText,
512
559
  createAgent,
513
560
  fromDecision,
514
561
  fromText,
@@ -13,20 +13,18 @@ const agent = createAgent({
13
13
  }),
14
14
  'agent.endConversation': z.object({}).describe('Stop the conversation'),
15
15
  },
16
+ context: {
17
+ userMessage: z.string(),
18
+ },
16
19
  });
17
20
 
18
21
  const machine = setup({
19
- types: {
20
- context: {} as {
21
- conversation: string[];
22
- },
23
- events: agent.eventTypes,
24
- },
22
+ types: agent.types,
25
23
  actors: { agent: fromDecision(agent), getFromTerminal },
26
24
  }).createMachine({
27
25
  initial: 'listening',
28
26
  context: {
29
- conversation: [],
27
+ userMessage: '',
30
28
  },
31
29
  states: {
32
30
  listening: {
@@ -35,8 +33,7 @@ const machine = setup({
35
33
  input: 'User:',
36
34
  onDone: {
37
35
  actions: assign({
38
- conversation: (x) =>
39
- x.context.conversation.concat('User: ' + x.event.output),
36
+ userMessage: (x) => x.event.output,
40
37
  }),
41
38
  target: 'responding',
42
39
  },
@@ -47,20 +44,15 @@ const machine = setup({
47
44
  src: 'agent',
48
45
  input: (x) => ({
49
46
  context: {
50
- conversation: x.context.conversation,
47
+ userMessage: 'User says: ' + x.context.userMessage,
51
48
  },
49
+ messages: agent.getMessages(),
52
50
  goal: 'Respond to the user, unless they want to end the conversation.',
53
51
  }),
54
52
  },
55
53
  on: {
56
54
  'agent.respond': {
57
- actions: [
58
- assign({
59
- conversation: (x) =>
60
- x.context.conversation.concat('Assistant: ' + x.event.response),
61
- }),
62
- log((x) => `Agent: ${x.event.response}`),
63
- ],
55
+ actions: [log((x) => `Agent: ${x.event.response}`)],
64
56
  target: 'listening',
65
57
  },
66
58
  'agent.endConversation': 'finished',
package/examples/cot.ts CHANGED
@@ -17,16 +17,14 @@ const agent = createAgent({
17
17
  answer: z.string().describe('The answer to the question'),
18
18
  }),
19
19
  },
20
+ context: {
21
+ question: z.string().nullable(),
22
+ thought: z.string().nullable(),
23
+ },
20
24
  });
21
25
 
22
26
  const machine = setup({
23
- types: {
24
- context: {} as {
25
- question: string | null;
26
- thought: string | null;
27
- },
28
- events: agent.eventTypes,
29
- },
27
+ types: agent.types,
30
28
  actors: { agent: fromDecision(agent), getFromTerminal },
31
29
  }).createMachine({
32
30
  initial: 'asking',
package/examples/email.ts CHANGED
@@ -19,7 +19,7 @@ const agent = createAgent({
19
19
 
20
20
  const machine = setup({
21
21
  types: {
22
- events: agent.eventTypes,
22
+ events: agent.types.events,
23
23
  input: {} as {
24
24
  email: string;
25
25
  instructions: string;
@@ -50,7 +50,7 @@ const machine = setup({
50
50
  instructions: x.context.instructions,
51
51
  clarifications: x.context.clarifications,
52
52
  },
53
- messages: agent.select((ctx) => ctx.messages),
53
+ messages: agent.getMessages(),
54
54
  goal: 'Respond to the email given the instructions and the provided clarifications. If not enough information is provided, ask for clarification. Otherwise, if you are absolutely sure that there is no ambiguous or missing information, create and submit a response email.',
55
55
  }),
56
56
  },
@@ -18,7 +18,7 @@ const agent = createAgent({
18
18
 
19
19
  const machine = setup({
20
20
  types: {
21
- events: agent.eventTypes,
21
+ events: agent.types.events,
22
22
  },
23
23
  actors: { agent: fromDecision(agent), summarizer: fromText(agent) },
24
24
  }).createMachine({
@@ -32,11 +32,11 @@ const machine = setup({
32
32
  invoke: [
33
33
  {
34
34
  src: 'summarizer',
35
- input: {
36
- context: true,
35
+ input: (x) => ({
36
+ context: x.context,
37
37
  prompt:
38
38
  'Summarize the patient visit in a single sentence. The summary should be in English.',
39
- },
39
+ }),
40
40
  onDone: {
41
41
  actions: assign({
42
42
  englishSummary: ({ event }) => event.output.text,
@@ -45,11 +45,11 @@ const machine = setup({
45
45
  },
46
46
  {
47
47
  src: 'summarizer',
48
- input: {
49
- context: true,
48
+ input: (x) => ({
49
+ context: x.context,
50
50
  prompt:
51
51
  'Summarize the patient visit in a single sentence. The summary should be in Spanish.',
52
- },
52
+ }),
53
53
  onDone: {
54
54
  actions: assign({
55
55
  spanishSummary: ({ event }) => event.output.text,
package/examples/goal.ts CHANGED
@@ -25,7 +25,7 @@ const machine = setup({
25
25
  question: string | null;
26
26
  goal: string | null;
27
27
  },
28
- events: agent.eventTypes,
28
+ events: agent.types.events,
29
29
  },
30
30
  actors: { decider, getFromTerminal },
31
31
  }).createMachine({
package/examples/joke.ts CHANGED
@@ -67,19 +67,17 @@ const agent = createAgent({
67
67
  .describe('Explains why the joke was irrelevant'),
68
68
  'agent.markAsRelevant': z.object({}).describe('The joke was relevant'),
69
69
  },
70
+ context: {
71
+ topic: z.string().describe('The topic for the joke'),
72
+ jokes: z.array(z.string()).describe('The jokes told so far'),
73
+ desire: z.string().nullable().describe('The user desire'),
74
+ lastRating: z.number().nullable().describe('The last joke rating'),
75
+ loader: z.string().nullable().describe('The loader text'),
76
+ },
70
77
  });
71
78
 
72
79
  const jokeMachine = setup({
73
- types: {
74
- context: {} as {
75
- topic: string;
76
- jokes: string[];
77
- desire: string | null;
78
- lastRating: number | null;
79
- loader: string | null;
80
- },
81
- events: agent.eventTypes,
82
- },
80
+ types: agent.types,
83
81
  actors: {
84
82
  agent: fromDecision(agent),
85
83
  loader,
@@ -20,7 +20,7 @@ const machine = setup({
20
20
  previousGuesses: number[];
21
21
  answer: number | null;
22
22
  },
23
- events: agent.eventTypes,
23
+ events: agent.types.events,
24
24
  },
25
25
  actors: {
26
26
  agent: fromDecision(agent),
@@ -27,7 +27,7 @@ const machine = setup({
27
27
  lastInput: string | null;
28
28
  entries: string[];
29
29
  },
30
- events: {} as typeof agent.eventTypes | { type: 'draw' },
30
+ events: {} as typeof agent.types.events | { type: 'draw' },
31
31
  },
32
32
  actors: { agent: fromDecision(agent), getFromTerminal },
33
33
  }).createMachine({
@@ -0,0 +1,28 @@
1
+ import { z } from 'zod';
2
+ import { createAgent } from '../src';
3
+ import { openai } from '@ai-sdk/openai';
4
+ import { createMachine } from 'xstate';
5
+
6
+ const agent = createAgent({
7
+ model: openai('gpt-4o'),
8
+ events: {
9
+ doSomething: z.object({}).describe('Do something'),
10
+ },
11
+ });
12
+
13
+ async function main() {
14
+ const machine = createMachine({
15
+ on: {
16
+ doSomething: {},
17
+ },
18
+ });
19
+ const result = await agent.decide({
20
+ goal: 'Do not do anything',
21
+ state: { value: {}, context: {} },
22
+ machine,
23
+ });
24
+
25
+ console.log(result);
26
+ }
27
+
28
+ main();
@@ -35,7 +35,7 @@ const agent = createAgent({
35
35
 
36
36
  const machine = setup({
37
37
  types: {
38
- events: agent.eventTypes,
38
+ events: agent.types.events,
39
39
  input: {} as string,
40
40
  context: {} as {
41
41
  customerIssue: string;