@statelyai/agent 1.0.0-beta.1 → 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/src/text.ts CHANGED
@@ -5,13 +5,12 @@ import type {
5
5
  StreamTextResult,
6
6
  } from 'ai';
7
7
  import {
8
- Agent,
9
8
  AgentGenerateTextOptions,
10
9
  AgentStreamTextOptions,
10
+ AnyAgent,
11
11
  } from './types';
12
12
  import { defaultTextTemplate } from './templates/defaultText';
13
13
  import {
14
- AnyMachineSnapshot,
15
14
  ObservableActorLogic,
16
15
  Observer,
17
16
  PromiseActorLogic,
@@ -29,15 +28,13 @@ import { randomId } from './utils';
29
28
  * @param options
30
29
  * @returns
31
30
  */
32
- async function getMessages(
33
- agent: Agent<any>,
31
+ export async function getMessages(
32
+ agent: AnyAgent,
34
33
  prompt: string,
35
- options: AgentStreamTextOptions
34
+ options: Omit<AgentGenerateTextOptions, 'prompt'>
36
35
  ): Promise<CoreMessage[]> {
37
36
  let messages: CoreMessage[] = [];
38
- if (options.messages === true) {
39
- messages = agent.select((s) => s.messages);
40
- } else if (typeof options.messages === 'function') {
37
+ if (typeof options.messages === 'function') {
41
38
  messages = await options.messages(agent);
42
39
  } else if (options.messages) {
43
40
  messages = options.messages;
@@ -51,7 +48,7 @@ async function getMessages(
51
48
  return messages;
52
49
  }
53
50
 
54
- export async function agentGenerateText<T extends Agent<any>>(
51
+ export async function agentGenerateText<T extends AnyAgent>(
55
52
  agent: T,
56
53
  options: AgentGenerateTextOptions
57
54
  ) {
@@ -100,7 +97,7 @@ export async function agentGenerateText<T extends Agent<any>>(
100
97
  }
101
98
 
102
99
  export async function agentStreamText(
103
- agent: Agent<any>,
100
+ agent: AnyAgent,
104
101
  options: AgentStreamTextOptions
105
102
  ): Promise<StreamTextResult<any>> {
106
103
  const resolvedOptions = {
@@ -158,21 +155,16 @@ export async function agentStreamText(
158
155
  return result;
159
156
  }
160
157
 
161
- export function fromTextStream<T extends Agent<any>>(
158
+ export function fromTextStream<T extends AnyAgent>(
162
159
  agent: T,
163
160
  defaultOptions?: AgentStreamTextOptions
164
161
  ): ObservableActorLogic<
165
162
  { textDelta: string },
166
163
  Omit<AgentStreamTextOptions, 'context'> & {
167
- context?: AgentStreamTextOptions['context'] | boolean;
164
+ context?: AgentStreamTextOptions['context'];
168
165
  }
169
166
  > {
170
- return fromObservable(({ input, self }) => {
171
- const context =
172
- input.context === true
173
- ? (self._parent?.getSnapshot() as AnyMachineSnapshot).context
174
- : input.context;
175
-
167
+ return fromObservable(({ input }) => {
176
168
  const observers = new Set<Observer<{ textDelta: string }>>();
177
169
 
178
170
  // TODO: check if messages was provided instead
@@ -181,7 +173,7 @@ export function fromTextStream<T extends Agent<any>>(
181
173
  const result = await agentStreamText(agent, {
182
174
  ...defaultOptions,
183
175
  ...input,
184
- context,
176
+ context: input.context,
185
177
  });
186
178
 
187
179
  for await (const part of result.fullStream) {
@@ -208,24 +200,20 @@ export function fromTextStream<T extends Agent<any>>(
208
200
  });
209
201
  }
210
202
 
211
- export function fromText<T extends Agent<any>>(
203
+ export function fromText<T extends AnyAgent>(
212
204
  agent: T,
213
205
  defaultOptions?: AgentGenerateTextOptions
214
206
  ): PromiseActorLogic<
215
207
  GenerateTextResult<Record<string, CoreTool<any, any>>>,
216
208
  Omit<AgentGenerateTextOptions, 'context'> & {
217
- context?: AgentGenerateTextOptions['context'] | boolean;
209
+ context?: AgentGenerateTextOptions['context'];
218
210
  }
219
211
  > {
220
- return fromPromise(async ({ input, self }) => {
221
- const context =
222
- input.context === true
223
- ? (self._parent?.getSnapshot() as AnyMachineSnapshot).context
224
- : input.context;
212
+ return fromPromise(async ({ input }) => {
225
213
  return await agentGenerateText(agent, {
226
214
  ...input,
227
215
  ...defaultOptions,
228
- context,
216
+ context: input.context,
229
217
  });
230
218
  });
231
219
  }
package/src/types.ts CHANGED
@@ -22,7 +22,7 @@ import {
22
22
  streamText,
23
23
  StreamTextResult,
24
24
  } from 'ai';
25
- import { ZodEventMapping } from './schemas';
25
+ import { ZodContextMapping, ZodEventMapping } from './schemas';
26
26
  import { TypeOf } from 'zod';
27
27
 
28
28
  export type GenerateTextOptions = Parameters<typeof generateText>[0];
@@ -62,10 +62,11 @@ export type AgentPlan<TEvent extends EventObject> = {
62
62
  goal: string;
63
63
  state: ObservedState;
64
64
  content?: string;
65
- steps?: Array<{
66
- event: TEvent;
67
- state?: ObservedState;
68
- }>;
65
+ /**
66
+ * Executes the plan based on the given `state` and resolves with
67
+ * a potential next `event` to trigger to achieve the `goal`.
68
+ */
69
+ execute: (state: ObservedState) => Promise<TEvent | undefined>;
69
70
  nextEvent: TEvent | undefined;
70
71
  sessionId: string;
71
72
  timestamp: number;
@@ -103,14 +104,14 @@ export type PromptTemplate<TEvents extends EventObject> = (data: {
103
104
  */
104
105
  observations?: AgentObservation<any>[]; // TODO
105
106
  feedback?: AgentFeedback[];
106
- messages?: AgentMessageHistory[];
107
+ messages?: AgentMessage[];
107
108
  plans?: AgentPlan<TEvents>[];
108
109
  }) => string;
109
110
 
110
- export type AgentPlanner<T extends Agent<any>> = (
111
- agent: T['eventTypes'],
112
- options: AgentPlanInput<T['eventTypes']>
113
- ) => Promise<AgentPlan<T['eventTypes']> | undefined>;
111
+ export type AgentPlanner<T extends AnyAgent> = (
112
+ agent: T,
113
+ input: AgentPlanInput<T['types']['events']>
114
+ ) => Promise<AgentPlan<T['types']['events']> | undefined>;
114
115
 
115
116
  export type AgentDecideOptions = {
116
117
  goal: string;
@@ -141,7 +142,7 @@ export interface AgentFeedbackInput {
141
142
  timestamp?: number;
142
143
  }
143
144
 
144
- export type AgentMessageHistory = CoreMessage & {
145
+ export type AgentMessage = CoreMessage & {
145
146
  timestamp: number;
146
147
  id: string;
147
148
  /**
@@ -153,7 +154,7 @@ export type AgentMessageHistory = CoreMessage & {
153
154
  sessionId: string;
154
155
  };
155
156
 
156
- export type AgentMessageHistoryInput = CoreMessage & {
157
+ export type AgentMessageInput = CoreMessage & {
157
158
  timestamp?: number;
158
159
  id?: string;
159
160
  /**
@@ -169,6 +170,7 @@ export interface AgentObservation<TActor extends AnyActorRef> {
169
170
  prevState: SnapshotFrom<TActor> | undefined;
170
171
  event: EventFrom<TActor>;
171
172
  state: SnapshotFrom<TActor>;
173
+ machineHash: string | undefined;
172
174
  sessionId: string;
173
175
  timestamp: number;
174
176
  }
@@ -178,6 +180,7 @@ export interface AgentObservationInput {
178
180
  prevState: ObservedState | undefined;
179
181
  event: AnyEventObject;
180
182
  state: ObservedState;
183
+ machine?: AnyStateMachine;
181
184
  timestamp?: number;
182
185
  }
183
186
 
@@ -203,7 +206,7 @@ export type AgentEmitted<TEvents extends EventObject> =
203
206
  }
204
207
  | {
205
208
  type: 'message';
206
- message: AgentMessageHistory;
209
+ message: AgentMessage;
207
210
  }
208
211
  | {
209
212
  type: 'plan';
@@ -222,7 +225,7 @@ export type AgentLogic<TEvents extends EventObject> = ActorLogic<
222
225
  }
223
226
  | {
224
227
  type: 'agent.message';
225
- message: AgentMessageHistory;
228
+ message: AgentMessage;
226
229
  }
227
230
  | {
228
231
  type: 'agent.plan';
@@ -240,21 +243,30 @@ export type EventsFromZodEventMapping<TEventSchemas extends ZodEventMapping> =
240
243
  } & TypeOf<TEventSchemas[K]>;
241
244
  }>;
242
245
 
243
- export type Agent<TEvents extends EventObject> = ActorRefFrom<
246
+ export type ContextFromZodContextMapping<
247
+ TContextSchema extends ZodContextMapping
248
+ > = {
249
+ [K in keyof TContextSchema & string]: TypeOf<TContextSchema[K]>;
250
+ };
251
+
252
+ export type Agent<TContext, TEvents extends EventObject> = ActorRefFrom<
244
253
  AgentLogic<TEvents>
245
254
  > & {
246
255
  /**
247
- * The general name of the agent. All agents with the same name are related and
256
+ * The name of the agent. All agents with the same name are related and
248
257
  * able to share experiences (observations, feedback) with each other.
249
258
  */
250
- name: string;
259
+ name?: string;
251
260
  /**
252
- * The unique id of the agent. This is used to partition message history.
261
+ * The unique identifier for the agent.
253
262
  */
254
263
  id?: string;
255
264
  description?: string;
256
265
  events: ZodEventMapping;
257
- eventTypes: TEvents;
266
+ types: {
267
+ events: TEvents;
268
+ context: Compute<TContext>;
269
+ };
258
270
  model: LanguageModel;
259
271
  defaultOptions: GenerateTextOptions;
260
272
  memory: AgentLongTermMemory | undefined;
@@ -271,7 +283,7 @@ export type Agent<TEvents extends EventObject> = ActorRefFrom<
271
283
  *
272
284
  * - The `goal` for the agent to achieve
273
285
  * - The observed current `state`
274
- * - The `logic` (e.g. a state machine) that specifies what can happen next
286
+ * - The `machine` (e.g. a state machine) that specifies what can happen next
275
287
  * - Additional `context`
276
288
  */
277
289
  decide: (
@@ -288,32 +300,100 @@ export type Agent<TEvents extends EventObject> = ActorRefFrom<
288
300
  options: AgentStreamTextOptions
289
301
  ) => Promise<StreamTextResult<Record<string, CoreTool<any, any>>>>;
290
302
 
291
- addObservation: (observation: AgentObservationInput) => AgentObservation<any>; // TODO
292
- addMessage: (history: AgentMessageHistoryInput) => AgentMessageHistory;
293
- addFeedback: (feedbackItem: AgentFeedbackInput) => AgentFeedback;
303
+ addObservation: (
304
+ observationInput: AgentObservationInput
305
+ ) => AgentObservation<any>; // TODO
306
+ addMessage: (messageInput: AgentMessageInput) => AgentMessage;
307
+ addFeedback: (feedbackInput: AgentFeedbackInput) => AgentFeedback;
294
308
  addPlan: (plan: AgentPlan<TEvents>) => void;
295
309
  /**
296
310
  * Called whenever the agent (LLM assistant) receives or sends a message.
297
311
  */
298
- onMessage: (callback: (message: AgentMessageHistory) => void) => void;
312
+ onMessage: (callback: (message: AgentMessage) => void) => void;
299
313
  /**
300
314
  * Selects agent data from its context.
315
+ *
316
+ * @deprecated Select from `agent.getSnapshot().context` directly or:
317
+ * - `agent.getMessages()`
318
+ * - `agent.getObservations()`
319
+ * - `agent.getFeedback()`
320
+ * - `agent.getPlans()`
301
321
  */
302
322
  select: <T>(selector: (context: AgentMemoryContext) => T) => T;
303
323
 
304
324
  /**
305
- * Inspects state machine actor transitions and automatically observes
306
- * (prevState, event, state) tuples.
325
+ * Retrieves messages from the agent's short-term (local) memory.
326
+ */
327
+ getMessages: () => AgentMessage[];
328
+
329
+ /**
330
+ * Retrieves observations from the agent's short-term (local) memory.
331
+ */
332
+ getObservations: () => AgentObservation<Agent<TContext, TEvents>>[];
333
+
334
+ /**
335
+ * Retrieves feedback from the agent's short-term (local) memory.
336
+ */
337
+ getFeedback: () => AgentFeedback[];
338
+
339
+ /**
340
+ * Retrieves strategies from the agent's short-term (local) memory.
341
+ */
342
+ getPlans: () => AgentPlan<TEvents>[];
343
+
344
+ /**
345
+ * Interacts with this state machine actor by inspecting state transitions and storing them as observations.
346
+ *
347
+ * Observations contain the `prevState`, `event`, and current `state` of this
348
+ * actor, as well as other properties that are useful when recalled.
349
+ * These observations are stored in the `agent`'s short-term (local) memory
350
+ * and can be retrieved via `agent.getObservations()`.
351
+ *
352
+ * @example
353
+ * ```ts
354
+ * // Only observes the actor's state transitions
355
+ * agent.interact(actor);
356
+ *
357
+ * actor.start();
358
+ * ```
359
+ */
360
+ interact<TActor extends AnyActorRef>(actorRef: TActor): Subscription;
361
+ /**
362
+ * Interacts with this state machine actor by:
363
+ * 1. Inspecting state transitions and storing them as observations
364
+ * 2. Deciding what to do next (which event to send the actor) based on
365
+ * the agent input returned from `getInput(observation)`, if `getInput(…)` is provided as the 2nd argument.
366
+ *
367
+ * Observations contain the `prevState`, `event`, and current `state` of this
368
+ * actor, as well as other properties that are useful when recalled.
369
+ * These observations are stored in the `agent`'s short-term (local) memory
370
+ * and can be retrieved via `agent.getObservations()`.
371
+ *
372
+ * @example
373
+ * ```ts
374
+ * // Observes the actor's state transitions and
375
+ * // makes a decision if on the "summarize" state
376
+ * agent.interact(actor, observed => {
377
+ * if (observed.state.matches('summarize')) {
378
+ * return {
379
+ * context: observed.state.context,
380
+ * goal: 'Summarize the message'
381
+ * }
382
+ * }
383
+ * });
384
+ *
385
+ * actor.start();
386
+ * ```
307
387
  */
308
- interact: <TActor extends AnyActorRef>(
388
+ interact<TActor extends AnyActorRef>(
309
389
  actorRef: TActor,
310
- getInput?: (
390
+ getInput: (
311
391
  observation: AgentObservation<TActor>
312
392
  ) => AgentDecisionInput | undefined
313
- ) => Subscription;
393
+ ): Subscription;
314
394
  };
315
395
 
316
- export type AnyAgent = Agent<any>;
396
+ export type AnyAgent = Agent<any, any>;
317
397
 
318
398
  export type FromAgent<T> = T | ((self: AnyAgent) => T | Promise<T>);
319
399
 
@@ -321,7 +401,7 @@ export interface CommonTextOptions {
321
401
  prompt: FromAgent<string>;
322
402
  model?: LanguageModel;
323
403
  context?: Record<string, any>;
324
- messages?: FromAgent<CoreMessage[]> | true;
404
+ messages?: FromAgent<CoreMessage[]>;
325
405
  template?: PromptTemplate<any>;
326
406
  }
327
407
 
@@ -356,7 +436,7 @@ export type ObservedStateFrom<TActor extends AnyActorRef> = Pick<
356
436
 
357
437
  export type AgentMemoryContext = {
358
438
  observations: AgentObservation<any>[]; // TODO
359
- messages: AgentMessageHistory[];
439
+ messages: AgentMessage[];
360
440
  plans: AgentPlan<any>[];
361
441
  feedback: AgentFeedback[];
362
442
  };
@@ -393,3 +473,5 @@ export interface AIAdapter {
393
473
  generateText: typeof generateText;
394
474
  streamText: typeof streamText;
395
475
  }
476
+
477
+ export type Compute<A extends any> = { [K in keyof A]: A[K] } & unknown;
package/src/utils.ts CHANGED
@@ -1,18 +1,50 @@
1
- import { AnyMachineSnapshot, AnyStateNode } from 'xstate';
1
+ import { AnyMachineSnapshot, AnyStateMachine, AnyStateNode } from 'xstate';
2
+ import hash from 'object-hash';
2
3
  import { TransitionData } from './types';
3
4
 
4
5
  export function getAllTransitions(state: AnyMachineSnapshot): TransitionData[] {
5
6
  const nodes = state._nodes;
6
7
  const transitions = (nodes as AnyStateNode[])
7
8
  .map((node) => [...(node as AnyStateNode).transitions.values()])
8
- .flat(2)
9
- .map((transition) => ({
10
- ...transition,
11
- guard:
12
- typeof transition.guard === 'string'
13
- ? { type: transition.guard }
14
- : (transition.guard as any), // TODO: fix
15
- }));
9
+ .map((nodeTransitions) => {
10
+ return nodeTransitions.map((nodeEventTransitions) => {
11
+ return nodeEventTransitions.map((transition) => {
12
+ return {
13
+ ...transition,
14
+ guard:
15
+ typeof transition.guard === 'string'
16
+ ? { type: transition.guard }
17
+ : (transition.guard as any), // TODO: fix
18
+ };
19
+ });
20
+ });
21
+ })
22
+ .flat(2);
23
+
24
+ return transitions;
25
+ }
26
+
27
+ export function getAllMachineTransitions(
28
+ stateNode: AnyStateNode
29
+ ): TransitionData[] {
30
+ const transitions: TransitionData[] = [...stateNode.transitions.values()]
31
+ .map((nodeTransitions) => {
32
+ return nodeTransitions.map((transition) => {
33
+ return {
34
+ ...transition,
35
+ guard:
36
+ typeof transition.guard === 'string'
37
+ ? { type: transition.guard }
38
+ : (transition.guard as any), // TODO: fix
39
+ };
40
+ });
41
+ })
42
+ .flat(2);
43
+
44
+ for (const s of Object.values(stateNode.states)) {
45
+ const stateTransitions = getAllMachineTransitions(s);
46
+ transitions.push(...stateTransitions);
47
+ }
16
48
 
17
49
  return transitions;
18
50
  }
@@ -26,3 +58,15 @@ export function randomId() {
26
58
  const random = Math.random().toString(36).substring(2, 9);
27
59
  return timestamp + random;
28
60
  }
61
+
62
+ const machineHashes: WeakMap<AnyStateMachine, string> = new WeakMap();
63
+ /**
64
+ * Returns a string hash representing only the transitions in the state machine.
65
+ */
66
+ export function getMachineHash(machine: AnyStateMachine): string {
67
+ if (machineHashes.has(machine)) return machineHashes.get(machine)!;
68
+ const transitions = getAllMachineTransitions(machine.root);
69
+ const machineHash = hash(transitions);
70
+ machineHashes.set(machine, machineHash);
71
+ return machineHash;
72
+ }
@@ -1,10 +0,0 @@
1
- import { PromptTemplate } from '../types';
2
- import { defaultTextTemplate } from './defaultText';
3
-
4
- export const defaultToolCallTemplate: PromptTemplate<any> = (data) => {
5
- return `
6
- ${defaultTextTemplate(data)}
7
-
8
- Only make a single tool call to achieve the above goal.
9
- `.trim();
10
- };