@statelyai/agent 0.1.0 → 0.1.2

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
@@ -1,7 +1,518 @@
1
- // src/index.ts
2
- function helloWorld() {
3
- return "Hello World!";
1
+ // src/agent.ts
2
+ import {
3
+ createActor,
4
+ fromTransition,
5
+ toObserver as toObserver2
6
+ } from "xstate";
7
+
8
+ // src/planners/simplePlanner.ts
9
+ import { tool } from "ai";
10
+
11
+ // src/utils.ts
12
+ function getAllTransitions(state) {
13
+ 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
+ }));
19
+ return transitions;
20
+ }
21
+ function wrapInXml(tagName, content) {
22
+ return `<${tagName}>${content}</${tagName}>`;
23
+ }
24
+
25
+ // src/templates/defaultText.ts
26
+ var defaultTextTemplate = (data) => {
27
+ const preamble = [
28
+ data.context ? wrapInXml("context", JSON.stringify(data.context)) : void 0
29
+ ].filter(Boolean).join("\n");
30
+ return `
31
+ ${preamble}
32
+
33
+ ${data.goal}
34
+ `.trim();
35
+ };
36
+
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
+ // src/agent.ts
119
+ import { randomUUID as randomUUID2 } from "crypto";
120
+
121
+ // src/text.ts
122
+ import { randomUUID } from "crypto";
123
+ import {
124
+ fromObservable,
125
+ fromPromise,
126
+ toObserver
127
+ } from "xstate";
128
+ async function getMessages(agent, prompt, options) {
129
+ let messages = [];
130
+ if (options.messages === true) {
131
+ messages = agent.select((s) => s.messages);
132
+ } else if (typeof options.messages === "function") {
133
+ messages = await options.messages(agent);
134
+ } else if (options.messages) {
135
+ messages = options.messages;
136
+ }
137
+ messages = messages.concat({
138
+ role: "user",
139
+ content: prompt
140
+ });
141
+ return messages;
142
+ }
143
+ async function agentGenerateText(agent, options) {
144
+ const resolvedOptions = {
145
+ ...agent.defaultOptions,
146
+ ...options
147
+ };
148
+ const template = resolvedOptions.template ?? defaultTextTemplate;
149
+ const id = randomUUID();
150
+ const goal = typeof resolvedOptions.prompt === "string" ? resolvedOptions.prompt : await resolvedOptions.prompt(agent);
151
+ const promptWithContext = template({
152
+ goal,
153
+ context: resolvedOptions.context
154
+ });
155
+ const messages = await getMessages(agent, promptWithContext, resolvedOptions);
156
+ agent.addMessage({
157
+ id,
158
+ role: "user",
159
+ content: promptWithContext,
160
+ timestamp: Date.now()
161
+ });
162
+ const result = await agent.adapter.generateText({
163
+ ...resolvedOptions,
164
+ prompt: void 0,
165
+ messages
166
+ });
167
+ agent.addMessage({
168
+ content: result.text,
169
+ id,
170
+ role: "assistant",
171
+ timestamp: Date.now(),
172
+ responseId: id,
173
+ result
174
+ });
175
+ return result;
176
+ }
177
+ async function agentStreamText(agent, options) {
178
+ const resolvedOptions = {
179
+ ...agent.defaultOptions,
180
+ ...options
181
+ };
182
+ const template = resolvedOptions.template ?? defaultTextTemplate;
183
+ const id = randomUUID();
184
+ const goal = typeof resolvedOptions.prompt === "string" ? resolvedOptions.prompt : await resolvedOptions.prompt(agent);
185
+ const promptWithContext = template({
186
+ goal,
187
+ context: resolvedOptions.context
188
+ });
189
+ const messages = await getMessages(agent, promptWithContext, resolvedOptions);
190
+ agent.addMessage({
191
+ role: "user",
192
+ content: promptWithContext,
193
+ id,
194
+ timestamp: Date.now()
195
+ });
196
+ const result = await agent.adapter.streamText({
197
+ ...resolvedOptions,
198
+ prompt: void 0,
199
+ messages,
200
+ onFinish: async (res) => {
201
+ agent.addMessage({
202
+ role: "assistant",
203
+ result: {
204
+ text: res.text,
205
+ finishReason: res.finishReason,
206
+ logprobs: void 0,
207
+ responseMessages: [],
208
+ toolCalls: [],
209
+ toolResults: [],
210
+ usage: res.usage,
211
+ warnings: res.warnings,
212
+ rawResponse: res.rawResponse
213
+ },
214
+ content: res.text,
215
+ id: randomUUID(),
216
+ timestamp: Date.now(),
217
+ responseId: id
218
+ });
219
+ }
220
+ });
221
+ return result;
222
+ }
223
+ function fromTextStream(agent, defaultOptions) {
224
+ return fromObservable(({ input, self }) => {
225
+ const context = input.context === true ? (self._parent?.getSnapshot()).context : input.context;
226
+ const observers = /* @__PURE__ */ new Set();
227
+ (async () => {
228
+ const result = await agentStreamText(agent, {
229
+ ...defaultOptions,
230
+ ...input,
231
+ context
232
+ });
233
+ for await (const part of result.fullStream) {
234
+ if (part.type === "text-delta") {
235
+ observers.forEach((observer) => {
236
+ observer.next?.(part);
237
+ });
238
+ }
239
+ }
240
+ })();
241
+ return {
242
+ subscribe: (...args) => {
243
+ const observer = toObserver(...args);
244
+ observers.add(observer);
245
+ return {
246
+ unsubscribe: () => {
247
+ observers.delete(observer);
248
+ }
249
+ };
250
+ }
251
+ };
252
+ });
253
+ }
254
+ function fromText(agent, defaultOptions) {
255
+ return fromPromise(async ({ input, self }) => {
256
+ const context = input.context === true ? (self._parent?.getSnapshot()).context : input.context;
257
+ return await agentGenerateText(agent, {
258
+ ...input,
259
+ ...defaultOptions,
260
+ context
261
+ });
262
+ });
263
+ }
264
+
265
+ // src/decision.ts
266
+ import { fromPromise as fromPromise2 } from "xstate";
267
+ async function agentDecide(agent, options) {
268
+ const resolvedOptions = {
269
+ ...agent.defaultOptions,
270
+ ...options
271
+ };
272
+ const {
273
+ planner = simplePlanner,
274
+ goal,
275
+ events = agent.events,
276
+ state,
277
+ machine,
278
+ model = agent.model,
279
+ ...otherPlanInput
280
+ } = resolvedOptions;
281
+ const plan = await planner(agent, {
282
+ model,
283
+ goal,
284
+ events,
285
+ state,
286
+ machine,
287
+ ...otherPlanInput
288
+ });
289
+ if (plan?.nextEvent) {
290
+ agent.addPlan(plan);
291
+ await resolvedOptions.execute?.(plan.nextEvent);
292
+ }
293
+ return plan;
294
+ }
295
+ function fromDecision(agent, defaultInput) {
296
+ return fromPromise2(async ({ input, self }) => {
297
+ const parentRef = self._parent;
298
+ if (!parentRef) {
299
+ return;
300
+ }
301
+ const snapshot = parentRef.getSnapshot();
302
+ const inputObject = typeof input === "string" ? { goal: input } : input;
303
+ const resolvedInput = {
304
+ ...defaultInput,
305
+ ...inputObject
306
+ };
307
+ const contextToInclude = resolvedInput.context === true ? (
308
+ // include entire context
309
+ parentRef.getSnapshot().context
310
+ ) : resolvedInput.context;
311
+ const state = {
312
+ value: snapshot.value,
313
+ context: contextToInclude
314
+ };
315
+ const plan = await agentDecide(agent, {
316
+ machine: parentRef.src,
317
+ state,
318
+ execute: async (event) => {
319
+ parentRef.send(event);
320
+ },
321
+ ...resolvedInput
322
+ });
323
+ return plan;
324
+ });
325
+ }
326
+
327
+ // src/adapters/vercel.ts
328
+ import { generateText, streamText } from "ai";
329
+ var vercelAdapter = {
330
+ generateText,
331
+ streamText
332
+ };
333
+
334
+ // src/agent.ts
335
+ var agentLogic = fromTransition(
336
+ (state, event, { emit }) => {
337
+ switch (event.type) {
338
+ case "agent.feedback": {
339
+ state.feedback.push(event.feedback);
340
+ emit({
341
+ type: "feedback",
342
+ // @ts-ignore TODO: fix types in XState
343
+ feedback: event.feedback
344
+ });
345
+ break;
346
+ }
347
+ case "agent.observe": {
348
+ state.observations.push(event.observation);
349
+ emit({
350
+ type: "observation",
351
+ // @ts-ignore TODO: fix types in XState
352
+ observation: event.observation
353
+ });
354
+ break;
355
+ }
356
+ case "agent.message": {
357
+ state.messages.push(event.message);
358
+ emit({
359
+ type: "message",
360
+ // @ts-ignore TODO: fix types in XState
361
+ message: event.message
362
+ });
363
+ break;
364
+ }
365
+ case "agent.plan": {
366
+ state.plans.push(event.plan);
367
+ emit({
368
+ type: "plan",
369
+ // @ts-ignore TODO: fix types in XState
370
+ plan: event.plan
371
+ });
372
+ break;
373
+ }
374
+ default:
375
+ break;
376
+ }
377
+ return state;
378
+ },
379
+ {
380
+ feedback: [],
381
+ messages: [],
382
+ observations: [],
383
+ plans: []
384
+ }
385
+ );
386
+ function createAgent({
387
+ name,
388
+ description,
389
+ model,
390
+ events,
391
+ planner = simplePlanner,
392
+ stringify = JSON.stringify,
393
+ getMemory,
394
+ logic = agentLogic,
395
+ adapter = vercelAdapter,
396
+ ...generateTextOptions
397
+ }) {
398
+ const messageHistoryListeners = [];
399
+ const agent = createActor(logic);
400
+ agent.events = events;
401
+ agent.model = model;
402
+ agent.name = name;
403
+ agent.description = description;
404
+ agent.adapter = adapter;
405
+ agent.defaultOptions = { ...generateTextOptions, model };
406
+ agent.select = (selector) => {
407
+ return selector(agent.getSnapshot().context);
408
+ };
409
+ agent.memory = getMemory ? getMemory(agent) : void 0;
410
+ agent.onMessage = (callback) => {
411
+ messageHistoryListeners.push(toObserver2(callback));
412
+ };
413
+ agent.decide = (opts) => {
414
+ return agentDecide(agent, opts);
415
+ };
416
+ agent.addMessage = (messageInput) => {
417
+ const message = {
418
+ ...messageInput,
419
+ id: messageInput.id ?? randomUUID2(),
420
+ timestamp: messageInput.timestamp ?? Date.now(),
421
+ sessionId: agent.sessionId
422
+ };
423
+ agent.send({
424
+ type: "agent.message",
425
+ message
426
+ });
427
+ return message;
428
+ };
429
+ agent.generateText = (opts) => agentGenerateText(agent, opts);
430
+ agent.streamText = (opts) => agentStreamText(agent, opts);
431
+ agent.addFeedback = (feedbackInput) => {
432
+ const feedback = {
433
+ ...feedbackInput,
434
+ timestamp: feedbackInput.timestamp ?? Date.now(),
435
+ sessionId: agent.sessionId
436
+ };
437
+ agent.send({
438
+ type: "agent.feedback",
439
+ feedback
440
+ });
441
+ return feedback;
442
+ };
443
+ agent.addObservation = (observationInput) => {
444
+ const observation = {
445
+ ...observationInput,
446
+ id: observationInput.id ?? randomUUID2(),
447
+ sessionId: agent.sessionId,
448
+ timestamp: observationInput.timestamp ?? Date.now()
449
+ };
450
+ agent.send({
451
+ type: "agent.observe",
452
+ observation
453
+ });
454
+ return observation;
455
+ };
456
+ agent.addPlan = (plan) => {
457
+ agent.send({
458
+ type: "agent.plan",
459
+ plan
460
+ });
461
+ };
462
+ agent.interact = (actorRef, getInput) => {
463
+ let prevState = void 0;
464
+ let subscribed = true;
465
+ async function handleObservation(observationInput) {
466
+ const observation = agent.addObservation(observationInput);
467
+ const input = getInput?.(observation);
468
+ if (input) {
469
+ await agentDecide(agent, {
470
+ machine: actorRef.src,
471
+ state: observation.state,
472
+ execute: async (event) => {
473
+ actorRef.send(event);
474
+ },
475
+ ...input
476
+ });
477
+ }
478
+ prevState = observationInput.state;
479
+ }
480
+ actorRef.system.inspect({
481
+ next: async (inspEvent) => {
482
+ if (!subscribed || inspEvent.actorRef !== actorRef || inspEvent.type !== "@xstate.snapshot") {
483
+ return;
484
+ }
485
+ const observationInput = {
486
+ event: inspEvent.event,
487
+ prevState,
488
+ state: inspEvent.snapshot
489
+ };
490
+ await handleObservation(observationInput);
491
+ }
492
+ });
493
+ if (actorRef._processingStatus === 1) {
494
+ handleObservation({
495
+ prevState: void 0,
496
+ event: { type: "" },
497
+ // TODO: unknown events?
498
+ state: actorRef.getSnapshot()
499
+ });
500
+ }
501
+ return {
502
+ unsubscribe: () => {
503
+ subscribed = false;
504
+ }
505
+ // TODO: make this actually unsubscribe
506
+ };
507
+ };
508
+ agent.start();
509
+ return agent;
4
510
  }
5
511
  export {
6
- helloWorld
512
+ agentDecide,
513
+ agentGenerateText,
514
+ createAgent,
515
+ fromDecision,
516
+ fromText,
517
+ fromTextStream
7
518
  };
package/examples/todo.ts CHANGED
@@ -9,7 +9,8 @@ const agent = createAgent({
9
9
  model: openai('gpt-4o'),
10
10
  events: {
11
11
  addTodo: z.object({
12
- message: z.string().min(1).max(100).describe('The message of the todo'),
12
+ title: z.string().min(1).max(100).describe('The title of the todo'),
13
+ content: z.string().min(1).max(100).describe('The content of the todo'),
13
14
  }),
14
15
  deleteTodo: z.object({
15
16
  index: z.number().describe('The index of the todo to delete'),
@@ -24,7 +25,8 @@ const agent = createAgent({
24
25
  });
25
26
 
26
27
  interface Todo {
27
- message: string;
28
+ title: string;
29
+ content: string;
28
30
  done: boolean;
29
31
  }
30
32
 
@@ -48,7 +50,8 @@ const machine = setup({
48
50
  todos: ({ context, event }) => [
49
51
  ...context.todos,
50
52
  {
51
- message: event.message,
53
+ title: event.title,
54
+ content: event.content,
52
55
  done: false,
53
56
  },
54
57
  ],
package/package.json CHANGED
@@ -1,10 +1,20 @@
1
1
  {
2
2
  "name": "@statelyai/agent",
3
- "version": "0.1.0",
3
+ "version": "0.1.2",
4
4
  "description": "",
5
5
  "main": "dist/index.js",
6
6
  "module": "dist/index.mjs",
7
7
  "types": "dist/index.d.ts",
8
+ "scripts": {
9
+ "build": "tsup src/index.ts --format cjs,esm --dts",
10
+ "lint": "tsc --noEmit",
11
+ "test": "vitest",
12
+ "example": "ts-node examples/helpers/runner.ts",
13
+ "prepublishOnly": "tsup src/index.ts --format cjs,esm --dts",
14
+ "changeset": "changeset",
15
+ "release": "changeset publish",
16
+ "version": "changeset version"
17
+ },
8
18
  "keywords": [],
9
19
  "author": "",
10
20
  "license": "MIT",
@@ -33,14 +43,5 @@
33
43
  "ai": "^3.1.32",
34
44
  "xstate": "^5.13.2"
35
45
  },
36
- "packageManager": "pnpm@8.11.0",
37
- "scripts": {
38
- "build": "tsup src/index.ts --format cjs,esm --dts",
39
- "lint": "tsc --noEmit",
40
- "test": "vitest",
41
- "example": "ts-node examples/helpers/runner.ts",
42
- "changeset": "changeset",
43
- "release": "changeset publish",
44
- "version": "changeset version"
45
- }
46
- }
46
+ "packageManager": "pnpm@8.11.0"
47
+ }
@@ -9,7 +9,6 @@ import {
9
9
  } from '../types';
10
10
  import { getAllTransitions } from '../utils';
11
11
  import { AnyStateMachine } from 'xstate';
12
- import { z } from 'zod';
13
12
  import { defaultTextTemplate } from '../templates/defaultText';
14
13
 
15
14
  function getTransitions(
@@ -75,9 +74,13 @@ export async function simplePlanner<T extends Agent<any>>(
75
74
  for (const toolTransitionData of toolTransitions) {
76
75
  const toolZodType = input.events?.[toolTransitionData.eventType];
77
76
 
77
+ if (!toolZodType) {
78
+ continue;
79
+ }
80
+
78
81
  toolMap[toolTransitionData.name] = tool({
79
82
  description: toolZodType?.description ?? toolTransitionData.description,
80
- parameters: toolZodType ?? z.object({}),
83
+ parameters: toolZodType,
81
84
  execute: async (params) => {
82
85
  const event = {
83
86
  type: toolTransitionData.eventType,
package/src/schemas.ts CHANGED
@@ -1,4 +1,4 @@
1
- import { SomeZodObject } from 'zod';
1
+ import type { SomeZodObject } from 'zod';
2
2
  import { AnyEventObject } from 'xstate';
3
3
  import { ObservedState } from './types';
4
4