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