agency-lang 0.0.44 → 0.0.46

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/README.md CHANGED
@@ -25,10 +25,13 @@ agency infile.agency
25
25
  Note that the generated files use several other libraries that you will need to install as well:
26
26
 
27
27
  ```bash
28
- pnpm i nanoid openai egonSchiele/simplemachine statelog-client zod
28
+ pnpm i nanoid openai piemachine statelog-client zod
29
29
  ```
30
30
 
31
31
  ## troubleshooting
32
32
  ### Weird undefined error
33
33
 
34
- A couple of times, I have tried to import a parser, and even though it exists, when I import it, the value that is `undefined`. This is due to a circular dependency issue. If I move that parser to its own file and then import it, it works.
34
+ A couple of times, I have tried to import a parser, and even though it exists, when I import it, the value that is `undefined`. This is due to a circular dependency issue. If I move that parser to its own file and then import it, it works.
35
+
36
+ ## License
37
+ [FSL](https://fsl.software).
@@ -1,17 +1,17 @@
1
1
  import { SpecialVar } from "../types/specialVar.js";
2
2
  import { AgencyComment, AgencyNode, AgencyProgram, Assignment, Literal, NewLine, PromptLiteral, TypeAlias, TypeHint, TypeHintMap, VariableType } from "../types.js";
3
+ import { AwaitStatement } from "../types/await.js";
4
+ import { TimeBlock } from "../types/timeBlock.js";
3
5
  import { AccessExpression, DotFunctionCall, DotProperty, IndexAccess } from "../types/access.js";
4
6
  import { AgencyArray, AgencyObject } from "../types/dataStructures.js";
5
7
  import { FunctionCall, FunctionDefinition } from "../types/function.js";
6
8
  import { GraphNodeDefinition } from "../types/graphNode.js";
9
+ import { IfElse } from "../types/ifElse.js";
7
10
  import { ImportNodeStatement, ImportStatement, ImportToolStatement } from "../types/importStatement.js";
8
11
  import { MatchBlock } from "../types/matchBlock.js";
9
12
  import { ReturnStatement } from "../types/returnStatement.js";
10
13
  import { UsesTool } from "../types/tools.js";
11
14
  import { WhileLoop } from "../types/whileLoop.js";
12
- import { IfElse } from "../types/ifElse.js";
13
- import { TimeBlock } from "../types/timeBlock.js";
14
- import { AwaitStatement } from "../types/await.js";
15
15
  type Scope = "global" | "function" | "node";
16
16
  export declare class BaseGenerator {
17
17
  protected typeHints: TypeHintMap;
@@ -71,6 +71,10 @@ export declare class BaseGenerator {
71
71
  protected postprocess(): string;
72
72
  protected startScope(scope: Scope): void;
73
73
  protected endScope(): void;
74
+ protected getCurrentScope(): Scope;
74
75
  protected getScopeVar(): string;
76
+ protected generateScopedVariableName(variableName: string): string;
77
+ protected isImportedTool(functionName: string): boolean;
78
+ protected isInternalFunction(functionName: string): boolean;
75
79
  }
76
80
  export {};
@@ -261,6 +261,12 @@ export class BaseGenerator {
261
261
  endScope() {
262
262
  this.currentScope.pop();
263
263
  }
264
+ getCurrentScope() {
265
+ return this.currentScope[this.currentScope.length - 1];
266
+ }
267
+ /* This function is used during assignment, so we can figure out whether this variable
268
+ should be assigned on local or global scope. There's a related function `generateScopedVariableName`,
269
+ which is used when retrieving the value of a variable. */
264
270
  getScopeVar() {
265
271
  const currentScope = this.currentScope[this.currentScope.length - 1];
266
272
  switch (currentScope) {
@@ -271,4 +277,29 @@ export class BaseGenerator {
271
277
  return "__stack.locals";
272
278
  }
273
279
  }
280
+ generateScopedVariableName(variableName) {
281
+ if (this.functionParameters.includes(variableName)) {
282
+ return `__stack.args.${variableName}`;
283
+ }
284
+ if (this.functionScopedVariables.includes(variableName)) {
285
+ return `__stack.locals.${variableName}`;
286
+ }
287
+ else if (this.globalScopedVariables.includes(variableName)) {
288
+ return `__stateStack.globals.${variableName}`;
289
+ }
290
+ return variableName;
291
+ }
292
+ isImportedTool(functionName) {
293
+ return this.importedTools
294
+ .map((node) => node.importedTools)
295
+ .flat()
296
+ .includes(functionName);
297
+ }
298
+ /* Internal function means the user defined this function in an Agency file,
299
+ as opposed to an external function, which was defined in an external TypeScript file
300
+ and imported into Agency. */
301
+ isInternalFunction(functionName) {
302
+ return (!!this.functionDefinitions[functionName] ||
303
+ this.isImportedTool(functionName));
304
+ }
274
305
  }
@@ -3,8 +3,8 @@ import * as renderConditionalEdge from "../templates/backends/graphGenerator/con
3
3
  import * as goToNode from "../templates/backends/graphGenerator/goToNode.js";
4
4
  import * as renderGraphNode from "../templates/backends/graphGenerator/graphNode.js";
5
5
  import * as renderImports from "../templates/backends/graphGenerator/imports.js";
6
- import * as renderStartNode from "../templates/backends/graphGenerator/startNode.js";
7
6
  import * as renderRunNodeFunction from "../templates/backends/graphGenerator/runNodeFunction.js";
7
+ import * as renderStartNode from "../templates/backends/graphGenerator/startNode.js";
8
8
  import { TypeScriptGenerator } from "./typescriptGenerator.js";
9
9
  import { mapFunctionName } from "./typescriptGenerator/builtins.js";
10
10
  import { variableTypeToString } from "./typescriptGenerator/typeToString.js";
@@ -43,7 +43,6 @@ export declare class TypeScriptGenerator extends BaseGenerator {
43
43
  * Generates TypeScript expression for a function call (without semicolon)
44
44
  */
45
45
  protected generateFunctionCallExpression(node: FunctionCall): string;
46
- protected generateScopedVariableName(variableName: string): string;
47
46
  protected generateLiteral(literal: Literal): string;
48
47
  protected generateImports(): string;
49
48
  buildPromptString(segments: PromptSegment[], typeHints: TypeHintMap): string;
@@ -1,3 +1,4 @@
1
+ import { TYPES_THAT_DONT_TRIGGER_NEW_PART } from "../config.js";
1
2
  import * as renderSpecialVar from "../templates/backends/graphGenerator/specialVar.js";
2
3
  import * as renderTime from "../templates/backends/typescriptGenerator/builtinFunctions/time.js";
3
4
  import * as builtinTools from "../templates/backends/typescriptGenerator/builtinTools.js";
@@ -11,7 +12,6 @@ import { BaseGenerator } from "./baseGenerator.js";
11
12
  import { generateBuiltinHelpers, mapFunctionName, } from "./typescriptGenerator/builtins.js";
12
13
  import { variableTypeToString } from "./typescriptGenerator/typeToString.js";
13
14
  import { DEFAULT_SCHEMA, mapTypeToZodSchema, } from "./typescriptGenerator/typeToZodSchema.js";
14
- import { TYPES_THAT_DONT_TRIGGER_NEW_PART } from "../config.js";
15
15
  export class TypeScriptGenerator extends BaseGenerator {
16
16
  constructor() {
17
17
  super();
@@ -61,8 +61,14 @@ export class TypeScriptGenerator extends BaseGenerator {
61
61
  const returnCode = this.processNode(node.value);
62
62
  if (node.value.type === "functionCall" &&
63
63
  node.value.functionName === "interrupt") {
64
+ /* In this case we're not popping off the stack, because we need to save the state,
65
+ because we will be restoring it (since this is an interrupt). However we do need to
66
+ advance the step so that the next time we come here, we start at the part after this
67
+ interrupt. */
64
68
  return `__stack.step++;\nreturn ${returnCode}\n`;
65
69
  }
70
+ /* Pop the state off the stack, we won't be coming back.
71
+ Doesn't matter if we update the step or not, since we won't be coming back here. */
66
72
  return `__stateStack.pop();\nreturn ${returnCode}\n`;
67
73
  }
68
74
  processAccessExpression(node) {
@@ -115,8 +121,13 @@ export class TypeScriptGenerator extends BaseGenerator {
115
121
  }
116
122
  processAssignment(node) {
117
123
  const { variableName, typeHint, value } = node;
118
- // Track this variable as in scope
119
- this.functionScopedVariables.push(variableName);
124
+ const _currentScope = this.getCurrentScope();
125
+ if (_currentScope === "global") {
126
+ this.globalScopedVariables.push(variableName);
127
+ }
128
+ else {
129
+ this.functionScopedVariables.push(variableName);
130
+ }
120
131
  const typeAnnotation = typeHint
121
132
  ? `: ${variableTypeToString(typeHint, this.typeAliases)}`
122
133
  : "";
@@ -130,7 +141,11 @@ export class TypeScriptGenerator extends BaseGenerator {
130
141
  throw new Error(`llm function call must have at least one argument for the prompt.`);
131
142
  }
132
143
  const promptArg = args[0];
133
- if (promptArg.type !== "string") {
144
+ const promptArgIsPrompt = promptArg.type === "prompt" ||
145
+ promptArg.type === "string" ||
146
+ promptArg.type === "multiLineString" ||
147
+ promptArg.type === "variableName"; // if variable name, assume its a string
148
+ if (!promptArgIsPrompt) {
134
149
  throw new Error(`First argument to llm function must be a prompt literal.`);
135
150
  }
136
151
  const promptConfig = args[1];
@@ -139,7 +154,14 @@ export class TypeScriptGenerator extends BaseGenerator {
139
154
  }
140
155
  return this.processPromptLiteral(variableName, typeHint, {
141
156
  type: "prompt",
142
- segments: promptArg.segments,
157
+ segments: promptArg.type === "variableName"
158
+ ? [
159
+ {
160
+ type: "interpolation",
161
+ variableName: promptArg.value,
162
+ },
163
+ ]
164
+ : promptArg.segments,
143
165
  config: promptConfig,
144
166
  });
145
167
  }
@@ -290,11 +312,7 @@ export class TypeScriptGenerator extends BaseGenerator {
290
312
  }
291
313
  });
292
314
  let argsString = "";
293
- const isImportedTool = this.importedTools
294
- .map((node) => node.importedTools)
295
- .flat()
296
- .includes(node.functionName);
297
- if (this.functionDefinitions[node.functionName] || isImportedTool) {
315
+ if (this.isInternalFunction(node.functionName)) {
298
316
  argsString = parts.join(", ");
299
317
  return `${functionName}([${argsString}])`;
300
318
  }
@@ -304,18 +322,6 @@ export class TypeScriptGenerator extends BaseGenerator {
304
322
  return `${functionName}(${argsString})`;
305
323
  }
306
324
  }
307
- generateScopedVariableName(variableName) {
308
- if (this.functionParameters.includes(variableName)) {
309
- return `__stack.args.${variableName}`;
310
- }
311
- if (this.functionScopedVariables.includes(variableName)) {
312
- return `__stack.locals.${variableName}`;
313
- }
314
- else if (this.globalScopedVariables.includes(variableName)) {
315
- return `__stateStack.globals.${variableName}`;
316
- }
317
- return variableName;
318
- }
319
325
  generateLiteral(literal) {
320
326
  switch (literal.type) {
321
327
  case "number":
@@ -502,6 +508,16 @@ export class TypeScriptGenerator extends BaseGenerator {
502
508
  const code = this.processNode(node.expression);
503
509
  return `await ${code}`;
504
510
  }
511
+ /* This generates the body of a node or function separated into multiple parts.
512
+ You can think of a part as roughly corresponding to a single statement
513
+ (although some statements don't need their own parts, such as a newlines or type definitions).
514
+
515
+ This is done so that we can keep track of what statement we're currently executing,
516
+ so that we can serialize that as part of the state if we return from an interrupt,
517
+ so that when we deserialize the state, we can pick up where we were and avoid having to
518
+ re-execute all the statements that we already executed.
519
+
520
+ Basically, this is part of the reason why agency can pick up exactly where you left off. */
505
521
  processBodyAsParts(body) {
506
522
  const parts = [[]];
507
523
  for (const stmt of body) {
@@ -1,4 +1,4 @@
1
- export declare const template = "\ngraph.node(\"{{{name}}}\", async (state): Promise<any> => {\n const __messages: Message[] = state.messages || [];\n const __graph = state.__metadata?.graph || graph;\n const statelogClient = state.__metadata?.statelogClient || __statelogClient;\n if (state.__metadata?.__stateStack) {\n __stateStack = state.__metadata.__stateStack;\n }\n const __stack = __stateStack.getNewState();\n const __step = __stack.step;\n\n const __self: Record<string, any> = __stack.locals;\n\n const __interruptResponse: InterruptResponseType | undefined = state.__metadata?.interruptResponse;\n const __toolCall: Record<string, any>|undefined = __stateStack.other?.toolCall;\n\n if (state.__metadata?.state?.global) {\n __global = state.__metadata.state.global;\n }\n\n {{#hasParam}}\n \n const __params = {{{paramNames}}};\n if (state.data !== \"<from-stack>\") {\n (state.data).forEach((item, index) => {\n __stack.args[__params[index]] = item;\n });\n }\n {{/hasParam}}\n {{{body}}}\n \n // this is just here to have a default return value from a node if the user doesn't specify one\n return { ...state, data: undefined };\n});\n";
1
+ export declare const template = "\ngraph.node(\"{{{name}}}\", async (state): Promise<any> => {\n const __messages: Message[] = state.messages || [];\n const __graph = state.__metadata?.graph || graph;\n const statelogClient = state.__metadata?.statelogClient || __statelogClient;\n \n // if `state.__metadata?.__stateStack` is set, that means we are resuming execution\n // at this node after an interrupt. In that case, this is the line that restores the state.\n if (state.__metadata?.__stateStack) {\n __stateStack = structuredClone(state.__metadata.__stateStack);\n \n // clear the state stack from metadata so it doesn't propagate to other nodes.\n state.__metadata.__stateStack = undefined;\n }\n\n // either creates a new stack for this node,\n // or restores the stack if we're resuming after an interrupt,\n // depending on the mode of the state stack (serialize or deserialize).\n const __stack = __stateStack.getNewState();\n \n // We're going to modify __stack.step to keep track of what line we're on,\n // but first we save this value. This will help us figure out if we should execute\n // from the start of this node or from a specific line.\n const __step = __stack.step;\n\n const __self: Record<string, any> = __stack.locals;\n\n // If we're resuming after an interrupt, these will be set.\n // There should be a cleaner way to handle this,\n // instead of littering this scope with these variables\n const __interruptResponse: InterruptResponseType | undefined = state.__metadata?.interruptResponse;\n const __toolCall: Record<string, any>|undefined = __stateStack.other?.toolCall;\n\n // TODO pretty sure this isn't needed, check and remove\n if (state.__metadata?.state?.global) {\n __global = state.__metadata.state.global;\n }\n\n {{#hasParam}}\n \n const __params = {{{paramNames}}};\n \n // Any arguments that were passed into this node,\n // save them onto the stack, unless we are restoring the stack after an interrupt,\n // in which case leave as is\n if (state.data !== \"<from-stack>\") {\n (state.data).forEach((item, index) => {\n __stack.args[__params[index]] = item;\n });\n }\n {{/hasParam}}\n {{{body}}}\n \n // this is just here to have a default return value from a node if the user doesn't specify one\n return { ...state, data: undefined };\n});\n";
2
2
  export type TemplateType = {
3
3
  name: string | boolean | number;
4
4
  hasParam: boolean;
@@ -7,17 +7,35 @@ graph.node("{{{name}}}", async (state): Promise<any> => {
7
7
  const __messages: Message[] = state.messages || [];
8
8
  const __graph = state.__metadata?.graph || graph;
9
9
  const statelogClient = state.__metadata?.statelogClient || __statelogClient;
10
+
11
+ // if \`state.__metadata?.__stateStack\` is set, that means we are resuming execution
12
+ // at this node after an interrupt. In that case, this is the line that restores the state.
10
13
  if (state.__metadata?.__stateStack) {
11
- __stateStack = state.__metadata.__stateStack;
14
+ __stateStack = structuredClone(state.__metadata.__stateStack);
15
+
16
+ // clear the state stack from metadata so it doesn't propagate to other nodes.
17
+ state.__metadata.__stateStack = undefined;
12
18
  }
19
+
20
+ // either creates a new stack for this node,
21
+ // or restores the stack if we're resuming after an interrupt,
22
+ // depending on the mode of the state stack (serialize or deserialize).
13
23
  const __stack = __stateStack.getNewState();
24
+
25
+ // We're going to modify __stack.step to keep track of what line we're on,
26
+ // but first we save this value. This will help us figure out if we should execute
27
+ // from the start of this node or from a specific line.
14
28
  const __step = __stack.step;
15
29
 
16
30
  const __self: Record<string, any> = __stack.locals;
17
31
 
32
+ // If we're resuming after an interrupt, these will be set.
33
+ // There should be a cleaner way to handle this,
34
+ // instead of littering this scope with these variables
18
35
  const __interruptResponse: InterruptResponseType | undefined = state.__metadata?.interruptResponse;
19
36
  const __toolCall: Record<string, any>|undefined = __stateStack.other?.toolCall;
20
37
 
38
+ // TODO pretty sure this isn't needed, check and remove
21
39
  if (state.__metadata?.state?.global) {
22
40
  __global = state.__metadata.state.global;
23
41
  }
@@ -25,6 +43,10 @@ graph.node("{{{name}}}", async (state): Promise<any> => {
25
43
  {{#hasParam}}
26
44
 
27
45
  const __params = {{{paramNames}}};
46
+
47
+ // Any arguments that were passed into this node,
48
+ // save them onto the stack, unless we are restoring the stack after an interrupt,
49
+ // in which case leave as is
28
50
  if (state.data !== "<from-stack>") {
29
51
  (state.data).forEach((item, index) => {
30
52
  __stack.args[__params[index]] = item;
@@ -1,4 +1,4 @@
1
- export declare const template = "import { z } from \"zod\";\nimport * as readline from \"readline\";\nimport fs from \"fs\";\nimport { PieMachine, goToNode } from \"piemachine\";\nimport { StatelogClient } from \"statelog-client\";\nimport { nanoid } from \"nanoid\";\nimport { assistantMessage, getClient, userMessage, toolMessage, messageFromJSON } from \"smoltalk\";\nimport type { Message } from \"smoltalk\";\n\nconst statelogHost = \"https://statelog.adit.io\";\nconst traceId = nanoid();\nconst statelogConfig = {\n host: statelogHost,\n traceId: traceId,\n apiKey: process.env.STATELOG_API_KEY || \"\",\n projectId: \"agency-lang\",\n debugMode: false,\n };\nconst __statelogClient = new StatelogClient(statelogConfig);\nconst __model: ModelName = \"gpt-4o-mini\";\n\nconst getClientWithConfig = (config = {}) => {\n const defaultConfig = {\n openAiApiKey: process.env.OPENAI_API_KEY || \"\",\n googleApiKey: process.env.GEMINI_API_KEY || \"\",\n model: __model,\n logLevel: \"warn\",\n };\n\n return getClient({ ...defaultConfig, ...config });\n};\n\nlet __client = getClientWithConfig();\n\ntype State = {\n messages: string[];\n data: any;\n}\n\n// enable debug logging\nconst graphConfig = {\n debug: {\n log: true,\n logData: false,\n },\n statelog: statelogConfig,\n};\n\nconst graph = new PieMachine<State>(graphConfig);\n\n// builtins\n\nconst not = (val: any): boolean => !val;\nconst eq = (a: any, b: any): boolean => a === b;\nconst neq = (a: any, b: any): boolean => a !== b;\nconst lt = (a: any, b: any): boolean => a < b;\nconst lte = (a: any, b: any): boolean => a <= b;\nconst gt = (a: any, b: any): boolean => a > b;\nconst gte = (a: any, b: any): boolean => a >= b;\nconst and = (a: any, b: any): boolean => a && b;\nconst or = (a: any, b: any): boolean => a || b;\nconst head = <T>(arr: T[]): T | undefined => arr[0];\nconst tail = <T>(arr: T[]): T[] => arr.slice(1);\nconst empty = <T>(arr: T[]): boolean => arr.length === 0;\n\n// interrupts\n\nexport type Interrupt<T> = {\n type: \"interrupt\";\n data: T;\n __state?: PackagedState;\n};\n\nexport function interrupt<T>(data: T): Interrupt<T> {\n return {\n type: \"interrupt\",\n data,\n };\n}\n\nexport function isInterrupt<T>(obj: any): obj is Interrupt<T> {\n return obj && obj.type === \"interrupt\";\n}\n\nfunction printJSON(obj: any) {\n console.log(JSON.stringify(obj, null, 2));\n}\n\nexport type InterruptResponseType = InterruptResponseApprove | InterruptResponseReject | InterruptResponseModify;\nexport type InterruptResponseApprove = {\n type: \"approve\";\n};\nexport type InterruptResponseReject = {\n type: \"reject\";\n};\nexport type InterruptResponseModify = {\n type: \"modify\";\n newArguments: Record<string, any>;\n};\n\n\nexport async function respondToInterrupt(_interrupt: Interrupt, _interruptResponse: InterruptResponseType) {\n const interrupt = structuredClone(_interrupt);\n const interruptResponse = structuredClone(_interruptResponse);\n __stateStack = StateStack.fromJSON(interrupt.__state || {});\n __stateStack.setMode(\"deserialize\");\n const messages = (__stateStack.other.messages || []).map((json: any) => {\n return messageFromJSON(json);\n });\n\n const nodesTraversed = __stateStack.other.nodesTraversed || [];\n const nodeName = nodesTraversed[nodesTraversed.length - 1];\n const result = await graph.run(nodeName, {\n messages: messages,\n __metadata: {\n graph: graph,\n statelogClient: __statelogClient,\n interruptResponse: interruptResponse,\n state: interrupt.__state,\n __stateStack: __stateStack,\n },\n data: \"<from-stack>\"\n });\n //console.log(`Result of graph.run(\"${nodeName}\"):`, JSON.stringify(result, null, 2));\n return result.data;\n}\n\n\nclass PackagedState {\n public messages?: Message[];\n public nodesTraversed?: string[];\n public toolCall?: Record<string, any>;\n public step?: number;\n public self?: Record<string, any>;\n public global?: Record<string, any>;\n public args?: any;\n constructor(_state: Record<string, any>, args?: any) {\n const state = structuredClone(_state);\n this.messages = state.messages;\n this.nodesTraversed = state.graph?.getNodesTraversed();\n this.toolCall = state.toolCall;\n this.step = state.part;\n this.self = state.self;\n this.global = state.global;\n this.args = state.args;\n }\n\n toJSON() {\n return {\n messages: this.messages,\n nodesTraversed: this.nodesTraversed,\n toolCall: this.toolCall,\n step: this.step,\n self: this.self,\n global: this.global,\n args: this.args,\n };\n }\n\n nextStep() {\n this.step ||= 0;\n this.step += 1;\n }\n}\n\n\nclass StateStack {\n public stack: StateItem[] = [];\n public mode: \"serialize\" | \"deserialize\" = \"serialize\";\n public globals: Record<string, any> = {};\n public other: Record<string, any> = {};\n\n constructor(stack: StateItem[] = [], mode: \"serialize\" | \"deserialize\" = \"serialize\") {\n this.stack = stack;\n this.mode = mode;\n }\n\n getNewState(): StateItem | null {\n if (this.stack.length === 0 && this.mode !== \"serialize\") {\n console.log(\"Forcing mode to serialize, nothing left to deserialize\");\n this.mode = \"serialize\";\n }\n if (this.mode === \"serialize\") {\n const newState: StateItem = {\n args: {},\n locals: {},\n step: 0,\n };\n this.stack.push(newState);\n return newState;\n } else if (this.mode === \"deserialize\") {\n const item = this.stack.shift();\n this.stack.push(item);\n return item;\n }\n return null;\n }\n\n setMode(mode: \"serialize\" | \"deserialize\") {\n this.mode = mode;\n }\n\n pop(): StateItem | undefined {\n return this.stack.pop();\n }\n\n toJSON() {\n return structuredClone({\n stack: this.stack,\n globals: this.globals,\n other: this.other,\n mode: this.mode,\n });\n }\n\n static fromJSON(json: any): StateStack {\n const stateStack = new StateStack([], \"serialize\");\n stateStack.stack = json.stack || [];\n stateStack.globals = json.globals || {};\n stateStack.other = json.other || {};\n stateStack.mode = json.mode || \"serialize\";\n return stateStack;\n }\n}\n\nlet __stateStack = new StateStack();";
1
+ export declare const template = "import { z } from \"zod\";\nimport * as readline from \"readline\";\nimport fs from \"fs\";\nimport { PieMachine, goToNode } from \"piemachine\";\nimport { StatelogClient } from \"statelog-client\";\nimport { nanoid } from \"nanoid\";\nimport { assistantMessage, getClient, userMessage, toolMessage, messageFromJSON } from \"smoltalk\";\nimport type { Message } from \"smoltalk\";\n\nconst statelogHost = \"https://statelog.adit.io\";\nconst traceId = nanoid();\nconst statelogConfig = {\n host: statelogHost,\n traceId: traceId,\n apiKey: process.env.STATELOG_API_KEY || \"\",\n projectId: \"agency-lang\",\n debugMode: false,\n };\nconst __statelogClient = new StatelogClient(statelogConfig);\nconst __model: ModelName = \"gpt-4o-mini\";\n\nconst getClientWithConfig = (config = {}) => {\n const defaultConfig = {\n openAiApiKey: process.env.OPENAI_API_KEY || \"\",\n googleApiKey: process.env.GEMINI_API_KEY || \"\",\n model: __model,\n logLevel: \"warn\",\n };\n\n return getClient({ ...defaultConfig, ...config });\n};\n\nlet __client = getClientWithConfig();\n\ntype State = {\n messages: string[];\n data: any;\n}\n\n// enable debug logging\nconst graphConfig = {\n debug: {\n log: true,\n logData: false,\n },\n statelog: statelogConfig,\n};\n\nconst graph = new PieMachine<State>(graphConfig);\n\n// builtins\n\nconst not = (val: any): boolean => !val;\nconst eq = (a: any, b: any): boolean => a === b;\nconst neq = (a: any, b: any): boolean => a !== b;\nconst lt = (a: any, b: any): boolean => a < b;\nconst lte = (a: any, b: any): boolean => a <= b;\nconst gt = (a: any, b: any): boolean => a > b;\nconst gte = (a: any, b: any): boolean => a >= b;\nconst and = (a: any, b: any): boolean => a && b;\nconst or = (a: any, b: any): boolean => a || b;\nconst head = <T>(arr: T[]): T | undefined => arr[0];\nconst tail = <T>(arr: T[]): T[] => arr.slice(1);\nconst empty = <T>(arr: T[]): boolean => arr.length === 0;\n\n// interrupts\n\nexport type Interrupt<T> = {\n type: \"interrupt\";\n data: T;\n\n // JSONified StateStack, i.e. serialized execution state\n __state?: Record<string, any>;\n};\n\nexport function interrupt<T>(data: T): Interrupt<T> {\n return {\n type: \"interrupt\",\n data,\n };\n}\n\nexport function isInterrupt<T>(obj: any): obj is Interrupt<T> {\n return obj && obj.type === \"interrupt\";\n}\n\nfunction printJSON(obj: any) {\n console.log(JSON.stringify(obj, null, 2));\n}\n\nexport type InterruptResponseType = InterruptResponseApprove | InterruptResponseReject | InterruptResponseModify;\nexport type InterruptResponseApprove = {\n type: \"approve\";\n};\nexport type InterruptResponseReject = {\n type: \"reject\";\n};\nexport type InterruptResponseModify = {\n type: \"modify\";\n newArguments: Record<string, any>;\n};\n\n\nexport async function respondToInterrupt(_interrupt: Interrupt, _interruptResponse: InterruptResponseType) {\n const interrupt = structuredClone(_interrupt);\n const interruptResponse = structuredClone(_interruptResponse);\n\n __stateStack = StateStack.fromJSON(interrupt.__state || {});\n __stateStack.deserializeMode();\n \n const messages = (__stateStack.other.messages || []).map((json: any) => {\n // create message objects from JSON\n return messageFromJSON(json);\n });\n\n // start at the last node we visited\n const nodesTraversed = __stateStack.other.nodesTraversed || [];\n const nodeName = nodesTraversed[nodesTraversed.length - 1];\n const result = await graph.run(nodeName, {\n messages: messages,\n __metadata: {\n graph: graph,\n statelogClient: __statelogClient,\n interruptResponse: interruptResponse,\n state: interrupt.__state,\n __stateStack: __stateStack,\n },\n\n // restore args from the state stack\n data: \"<from-stack>\"\n });\n return result.data;\n}\n\nexport async function approveInterrupt(interrupt: Interrupt) {\n return await respondToInterrupt(interrupt, { type: \"approve\" });\n}\n\nexport async function rejectInterrupt(interrupt: Interrupt) {\n return await respondToInterrupt(interrupt, { type: \"reject\" });\n}\n\ntype StackFrame = {\n args: Record<string, any>;\n locals: Record<string, any>;\n step: number;\n};\n\n// See docs for notes on how this works.\nclass StateStack {\n public stack: StackFrame[] = [];\n private mode: \"serialize\" | \"deserialize\" = \"serialize\";\n public globals: Record<string, any> = {};\n public other: Record<string, any> = {};\n\n private deserializeStackLength = 0;\n\n constructor(stack: StackFrame[] = [], mode: \"serialize\" | \"deserialize\" = \"serialize\") {\n this.stack = stack;\n this.mode = mode;\n }\n\n getNewState(): StackFrame | null {\n if (this.mode === \"deserialize\" && this.deserializeStackLength <= 0) {\n console.log(\"Forcing mode to serialize, nothing left to deserialize\");\n this.mode = \"serialize\";\n }\n if (this.mode === \"serialize\") {\n const newState: StackFrame = {\n args: {},\n locals: {},\n step: 0,\n };\n this.stack.push(newState);\n return newState;\n } else if (this.mode === \"deserialize\") {\n this.deserializeStackLength -= 1;\n const item = this.stack.shift();\n this.stack.push(item);\n return item;\n }\n return null;\n }\n\n deserializeMode() {\n this.mode = \"deserialize\";\n this.deserializeStackLength = this.stack.length;\n }\n\n pop(): StackFrame | undefined {\n return this.stack.pop();\n }\n\n toJSON() {\n return structuredClone({\n stack: this.stack,\n globals: this.globals,\n other: this.other,\n mode: this.mode,\n deserializeStackLength: this.deserializeStackLength,\n });\n }\n\n static fromJSON(json: any): StateStack {\n const stateStack = new StateStack([], \"serialize\");\n stateStack.stack = json.stack || [];\n stateStack.globals = json.globals || {};\n stateStack.other = json.other || {};\n stateStack.mode = json.mode || \"serialize\";\n stateStack.deserializeStackLength = json.deserializeStackLength || 0;\n return stateStack;\n }\n}\n\nlet __stateStack = new StateStack();";
2
2
  export type TemplateType = {};
3
3
  declare const render: (args: TemplateType) => string;
4
4
  export default render;
@@ -72,7 +72,9 @@ const empty = <T>(arr: T[]): boolean => arr.length === 0;
72
72
  export type Interrupt<T> = {
73
73
  type: "interrupt";
74
74
  data: T;
75
- __state?: PackagedState;
75
+
76
+ // JSONified StateStack, i.e. serialized execution state
77
+ __state?: Record<string, any>;
76
78
  };
77
79
 
78
80
  export function interrupt<T>(data: T): Interrupt<T> {
@@ -106,12 +108,16 @@ export type InterruptResponseModify = {
106
108
  export async function respondToInterrupt(_interrupt: Interrupt, _interruptResponse: InterruptResponseType) {
107
109
  const interrupt = structuredClone(_interrupt);
108
110
  const interruptResponse = structuredClone(_interruptResponse);
111
+
109
112
  __stateStack = StateStack.fromJSON(interrupt.__state || {});
110
- __stateStack.setMode("deserialize");
113
+ __stateStack.deserializeMode();
114
+
111
115
  const messages = (__stateStack.other.messages || []).map((json: any) => {
116
+ // create message objects from JSON
112
117
  return messageFromJSON(json);
113
118
  });
114
119
 
120
+ // start at the last node we visited
115
121
  const nodesTraversed = __stateStack.other.nodesTraversed || [];
116
122
  const nodeName = nodesTraversed[nodesTraversed.length - 1];
117
123
  const result = await graph.run(nodeName, {
@@ -123,69 +129,48 @@ export async function respondToInterrupt(_interrupt: Interrupt, _interruptRespon
123
129
  state: interrupt.__state,
124
130
  __stateStack: __stateStack,
125
131
  },
132
+
133
+ // restore args from the state stack
126
134
  data: "<from-stack>"
127
135
  });
128
- //console.log(\`Result of graph.run("\${nodeName}"):\`, JSON.stringify(result, null, 2));
129
136
  return result.data;
130
137
  }
131
138
 
139
+ export async function approveInterrupt(interrupt: Interrupt) {
140
+ return await respondToInterrupt(interrupt, { type: "approve" });
141
+ }
132
142
 
133
- class PackagedState {
134
- public messages?: Message[];
135
- public nodesTraversed?: string[];
136
- public toolCall?: Record<string, any>;
137
- public step?: number;
138
- public self?: Record<string, any>;
139
- public global?: Record<string, any>;
140
- public args?: any;
141
- constructor(_state: Record<string, any>, args?: any) {
142
- const state = structuredClone(_state);
143
- this.messages = state.messages;
144
- this.nodesTraversed = state.graph?.getNodesTraversed();
145
- this.toolCall = state.toolCall;
146
- this.step = state.part;
147
- this.self = state.self;
148
- this.global = state.global;
149
- this.args = state.args;
150
- }
151
-
152
- toJSON() {
153
- return {
154
- messages: this.messages,
155
- nodesTraversed: this.nodesTraversed,
156
- toolCall: this.toolCall,
157
- step: this.step,
158
- self: this.self,
159
- global: this.global,
160
- args: this.args,
161
- };
162
- }
163
-
164
- nextStep() {
165
- this.step ||= 0;
166
- this.step += 1;
167
- }
143
+ export async function rejectInterrupt(interrupt: Interrupt) {
144
+ return await respondToInterrupt(interrupt, { type: "reject" });
168
145
  }
169
146
 
147
+ type StackFrame = {
148
+ args: Record<string, any>;
149
+ locals: Record<string, any>;
150
+ step: number;
151
+ };
170
152
 
153
+ // See docs for notes on how this works.
171
154
  class StateStack {
172
- public stack: StateItem[] = [];
173
- public mode: "serialize" | "deserialize" = "serialize";
155
+ public stack: StackFrame[] = [];
156
+ private mode: "serialize" | "deserialize" = "serialize";
174
157
  public globals: Record<string, any> = {};
175
158
  public other: Record<string, any> = {};
176
159
 
177
- constructor(stack: StateItem[] = [], mode: "serialize" | "deserialize" = "serialize") {
160
+ private deserializeStackLength = 0;
161
+
162
+ constructor(stack: StackFrame[] = [], mode: "serialize" | "deserialize" = "serialize") {
178
163
  this.stack = stack;
179
164
  this.mode = mode;
180
165
  }
181
166
 
182
- getNewState(): StateItem | null {
183
- if (this.stack.length === 0 && this.mode !== "serialize") {
167
+ getNewState(): StackFrame | null {
168
+ if (this.mode === "deserialize" && this.deserializeStackLength <= 0) {
184
169
  console.log("Forcing mode to serialize, nothing left to deserialize");
185
170
  this.mode = "serialize";
186
171
  }
187
172
  if (this.mode === "serialize") {
188
- const newState: StateItem = {
173
+ const newState: StackFrame = {
189
174
  args: {},
190
175
  locals: {},
191
176
  step: 0,
@@ -193,6 +178,7 @@ class StateStack {
193
178
  this.stack.push(newState);
194
179
  return newState;
195
180
  } else if (this.mode === "deserialize") {
181
+ this.deserializeStackLength -= 1;
196
182
  const item = this.stack.shift();
197
183
  this.stack.push(item);
198
184
  return item;
@@ -200,11 +186,12 @@ class StateStack {
200
186
  return null;
201
187
  }
202
188
 
203
- setMode(mode: "serialize" | "deserialize") {
204
- this.mode = mode;
189
+ deserializeMode() {
190
+ this.mode = "deserialize";
191
+ this.deserializeStackLength = this.stack.length;
205
192
  }
206
193
 
207
- pop(): StateItem | undefined {
194
+ pop(): StackFrame | undefined {
208
195
  return this.stack.pop();
209
196
  }
210
197
 
@@ -214,6 +201,7 @@ class StateStack {
214
201
  globals: this.globals,
215
202
  other: this.other,
216
203
  mode: this.mode,
204
+ deserializeStackLength: this.deserializeStackLength,
217
205
  });
218
206
  }
219
207
 
@@ -223,6 +211,7 @@ class StateStack {
223
211
  stateStack.globals = json.globals || {};
224
212
  stateStack.other = json.other || {};
225
213
  stateStack.mode = json.mode || "serialize";
214
+ stateStack.deserializeStackLength = json.deserializeStackLength || 0;
226
215
  return stateStack;
227
216
  }
228
217
  }
@@ -1,4 +1,4 @@
1
- export declare const template = "\nexport async function {{{functionName:string}}}(args, __metadata={}) : Promise<{{{returnType}}}> {\n const __messages: Message[] = [];\n const __stack = __stateStack.getNewState();\n const __step = __stack.step; // > 0 ? __stack.step + 1 : 0;\n const __self: Record<string, any> = __stack.locals;\n\n const __params = [{{{argsStr}}}];\n (args).forEach((item, index) => {\n __stack.args[__params[index]] = item;\n });\n\n\n {{{functionBody}}}\n}";
1
+ export declare const template = "\nexport async function {{{functionName:string}}}(args, __metadata={}) : Promise<{{{returnType}}}> {\n const __messages: Message[] = [];\n const __stack = __stateStack.getNewState();\n const __step = __stack.step;\n const __self: Record<string, any> = __stack.locals;\n\n // TODO: Note that we don't need to use the same kind of restoration\n // from state for arguments as we do for nodes,\n // because the args are serialized in the tool call.\n // But what about situations where it was a function call, not a tool call?\n // In that case, we would want to deserialize the argument.\n const __params = [{{{argsStr}}}];\n (args).forEach((item, index) => {\n __stack.args[__params[index]] = item;\n });\n\n\n {{{functionBody}}}\n}";
2
2
  export type TemplateType = {
3
3
  functionName: string;
4
4
  returnType: string | boolean | number;
@@ -6,13 +6,18 @@ export const template = `
6
6
  export async function {{{functionName:string}}}(args, __metadata={}) : Promise<{{{returnType}}}> {
7
7
  const __messages: Message[] = [];
8
8
  const __stack = __stateStack.getNewState();
9
- const __step = __stack.step; // > 0 ? __stack.step + 1 : 0;
9
+ const __step = __stack.step;
10
10
  const __self: Record<string, any> = __stack.locals;
11
11
 
12
+ // TODO: Note that we don't need to use the same kind of restoration
13
+ // from state for arguments as we do for nodes,
14
+ // because the args are serialized in the tool call.
15
+ // But what about situations where it was a function call, not a tool call?
16
+ // In that case, we would want to deserialize the argument.
12
17
  const __params = [{{{argsStr}}}];
13
- (args).forEach((item, index) => {
14
- __stack.args[__params[index]] = item;
15
- });
18
+ (args).forEach((item, index) => {
19
+ __stack.args[__params[index]] = item;
20
+ });
16
21
 
17
22
 
18
23
  {{{functionBody}}}
@@ -1,4 +1,4 @@
1
- export declare const template = "\nasync function _{{{variableName:string}}}({{{argsStr:string}}}): Promise<{{{typeString:string}}}> {\n const __prompt = {{{promptCode:string}}};\n const startTime = performance.now();\n const __messages: Message[] = __metadata?.messages || [];\n let __toolCalls = __metadata?.toolCall ? [__metadata.toolCall] : [];\n const __interruptResponse:InterruptResponseType|undefined = __metadata?.interruptResponse;\n const __tools = {{{tools}}};\n\n {{#hasResponseFormat}}\n // Need to make sure this is always an object\n const __responseFormat = z.object({\n response: {{{zodSchema:string}}}\n });\n {{/hasResponseFormat}}\n {{^hasResponseFormat}}\n const __responseFormat = undefined;\n {{/hasResponseFormat}}\n \n const __client = getClientWithConfig({{{clientConfig:string}}});\n let responseMessage:any;\n\n if (__toolCalls.length === 0) {\n __messages.push(userMessage(__prompt));\n \n \n let __completion = await __client.text({\n messages: __messages,\n tools: __tools,\n responseFormat: __responseFormat,\n });\n \n const endTime = performance.now();\n await statelogClient.promptCompletion({\n messages: __messages,\n completion: __completion,\n model: __client.getModel(),\n timeTaken: endTime - startTime,\n });\n \n if (!__completion.success) {\n throw new Error(\n `Error getting response from ${__model}: ${__completion.error}`\n );\n }\n \n responseMessage = __completion.value;\n __toolCalls = responseMessage.toolCalls || [];\n\n if (__toolCalls.length > 0) {\n // Add assistant's response with tool calls to message history\n __messages.push(assistantMessage(responseMessage.output, { toolCalls: __toolCalls }));\n }\n }\n\n // Handle function calls\n if (__toolCalls.length > 0) {\n let toolCallStartTime, toolCallEndTime;\n let haltExecution = false;\n let haltToolCall = {}\n let haltInterrupt:any = null;\n\n // Process each tool call\n for (const toolCall of __toolCalls) {\n {{{functionCalls:string}}}\n }\n\n if (haltExecution) {\n await statelogClient.debug(`Tool call interrupted execution.`, {\n messages: __messages,\n model: __client.getModel(),\n });\n\n __stateStack.other = {\n messages: __messages.map((msg) => msg.toJSON()),\n nodesTraversed: __graph.getNodesTraversed(),\n toolCall: haltToolCall,\n };\n haltInterrupt.__state = __stateStack.toJSON();\n return haltInterrupt;\n }\n \n const nextStartTime = performance.now();\n let __completion = await __client.text({\n messages: __messages,\n tools: __tools,\n responseFormat: __responseFormat,\n });\n\n const nextEndTime = performance.now();\n\n await statelogClient.promptCompletion({\n messages: __messages,\n completion: __completion,\n model: __client.getModel(),\n timeTaken: nextEndTime - nextStartTime,\n });\n\n if (!__completion.success) {\n throw new Error(\n `Error getting response from ${__model}: ${__completion.error}`\n );\n }\n responseMessage = __completion.value;\n }\n\n // Add final assistant response to history\n // not passing tool calls back this time\n __messages.push(assistantMessage(responseMessage.output));\n {{#hasResponseFormat}}\n try {\n const result = JSON.parse(responseMessage.output || \"\");\n return result.response;\n } catch (e) {\n return responseMessage.output;\n // console.error(\"Error parsing response for variable '{{{variableName:string}}}':\", e);\n // console.error(\"Full completion response:\", JSON.stringify(__completion, null, 2));\n // throw e;\n }\n {{/hasResponseFormat}}\n\n {{^hasResponseFormat}}\n return responseMessage.output;\n {{/hasResponseFormat}}\n}\n\n__self.{{{variableName:string}}} = await _{{{variableName:string}}}({{{funcCallParams:string}}});\n\nif (isInterrupt(__self.{{{variableName:string}}})) {\n return { ...state, data: __self.{{{variableName:string}}} };\n}";
1
+ export declare const template = "\nasync function _{{{variableName:string}}}({{{argsStr:string}}}): Promise<{{{typeString:string}}}> {\n const __prompt = {{{promptCode:string}}};\n const startTime = performance.now();\n const __messages: Message[] = __metadata?.messages || [];\n\n // These are to restore state after interrupt.\n // TODO I think this could be implemented in a cleaner way.\n let __toolCalls = __metadata?.toolCall ? [__metadata.toolCall] : [];\n const __interruptResponse:InterruptResponseType|undefined = __metadata?.interruptResponse;\n const __tools = {{{tools}}};\n\n {{#hasResponseFormat}}\n // Need to make sure this is always an object\n const __responseFormat = z.object({\n response: {{{zodSchema:string}}}\n });\n {{/hasResponseFormat}}\n {{^hasResponseFormat}}\n const __responseFormat = undefined;\n {{/hasResponseFormat}}\n \n const __client = getClientWithConfig({{{clientConfig:string}}});\n let responseMessage:any;\n\n if (__toolCalls.length === 0) {\n __messages.push(userMessage(__prompt));\n \n \n let __completion = await __client.text({\n messages: __messages,\n tools: __tools,\n responseFormat: __responseFormat,\n });\n \n const endTime = performance.now();\n await statelogClient.promptCompletion({\n messages: __messages,\n completion: __completion,\n model: __client.getModel(),\n timeTaken: endTime - startTime,\n });\n \n if (!__completion.success) {\n throw new Error(\n `Error getting response from ${__model}: ${__completion.error}`\n );\n }\n \n responseMessage = __completion.value;\n __toolCalls = responseMessage.toolCalls || [];\n\n if (__toolCalls.length > 0) {\n // Add assistant's response with tool calls to message history\n __messages.push(assistantMessage(responseMessage.output, { toolCalls: __toolCalls }));\n }\n }\n\n // Handle function calls\n if (__toolCalls.length > 0) {\n let toolCallStartTime, toolCallEndTime;\n let haltExecution = false;\n let haltToolCall = {}\n let haltInterrupt:any = null;\n\n // Process each tool call\n for (const toolCall of __toolCalls) {\n {{{functionCalls:string}}}\n }\n\n if (haltExecution) {\n await statelogClient.debug(`Tool call interrupted execution.`, {\n messages: __messages,\n model: __client.getModel(),\n });\n\n __stateStack.other = {\n messages: __messages.map((msg) => msg.toJSON()),\n nodesTraversed: __graph.getNodesTraversed(),\n toolCall: haltToolCall,\n };\n haltInterrupt.__state = __stateStack.toJSON();\n return haltInterrupt;\n }\n \n const nextStartTime = performance.now();\n let __completion = await __client.text({\n messages: __messages,\n tools: __tools,\n responseFormat: __responseFormat,\n });\n\n const nextEndTime = performance.now();\n\n await statelogClient.promptCompletion({\n messages: __messages,\n completion: __completion,\n model: __client.getModel(),\n timeTaken: nextEndTime - nextStartTime,\n });\n\n if (!__completion.success) {\n throw new Error(\n `Error getting response from ${__model}: ${__completion.error}`\n );\n }\n responseMessage = __completion.value;\n }\n\n // Add final assistant response to history\n // not passing tool calls back this time\n __messages.push(assistantMessage(responseMessage.output));\n {{#hasResponseFormat}}\n try {\n const result = JSON.parse(responseMessage.output || \"\");\n return result.response;\n } catch (e) {\n return responseMessage.output;\n // console.error(\"Error parsing response for variable '{{{variableName:string}}}':\", e);\n // console.error(\"Full completion response:\", JSON.stringify(__completion, null, 2));\n // throw e;\n }\n {{/hasResponseFormat}}\n\n {{^hasResponseFormat}}\n return responseMessage.output;\n {{/hasResponseFormat}}\n}\n\n__self.{{{variableName:string}}} = await _{{{variableName:string}}}({{{funcCallParams:string}}});\n\n// return early from node if this is an interrupt\nif (isInterrupt(__self.{{{variableName:string}}})) {\n return { ...state, data: __self.{{{variableName:string}}} };\n}";
2
2
  export type TemplateType = {
3
3
  variableName: string;
4
4
  argsStr: string;
@@ -7,6 +7,9 @@ async function _{{{variableName:string}}}({{{argsStr:string}}}): Promise<{{{type
7
7
  const __prompt = {{{promptCode:string}}};
8
8
  const startTime = performance.now();
9
9
  const __messages: Message[] = __metadata?.messages || [];
10
+
11
+ // These are to restore state after interrupt.
12
+ // TODO I think this could be implemented in a cleaner way.
10
13
  let __toolCalls = __metadata?.toolCall ? [__metadata.toolCall] : [];
11
14
  const __interruptResponse:InterruptResponseType|undefined = __metadata?.interruptResponse;
12
15
  const __tools = {{{tools}}};
@@ -130,6 +133,7 @@ async function _{{{variableName:string}}}({{{argsStr:string}}}): Promise<{{{type
130
133
 
131
134
  __self.{{{variableName:string}}} = await _{{{variableName:string}}}({{{funcCallParams:string}}});
132
135
 
136
+ // return early from node if this is an interrupt
133
137
  if (isInterrupt(__self.{{{variableName:string}}})) {
134
138
  return { ...state, data: __self.{{{variableName:string}}} };
135
139
  }`;
package/dist/lib/utils.js CHANGED
@@ -19,32 +19,3 @@ export function zip(arr1, arr2) {
19
19
  export function uniq(arr) {
20
20
  return Array.from(new Set(arr));
21
21
  }
22
- class PackagedState {
23
- messages;
24
- nodesTraversed;
25
- toolCall;
26
- step;
27
- selfState;
28
- globalState;
29
- args;
30
- constructor(state, args) {
31
- this.messages = state.messages;
32
- this.nodesTraversed = state.graph?.getNodesTraversed();
33
- this.toolCall = state.toolCall;
34
- this.step = state.part;
35
- this.selfState = JSON.parse(JSON.stringify(state.self));
36
- this.globalState = JSON.parse(JSON.stringify(state.global));
37
- this.args = args;
38
- }
39
- toJSON() {
40
- return {
41
- messages: this.messages,
42
- nodesTraversed: this.nodesTraversed,
43
- toolCall: this.toolCall,
44
- step: this.step,
45
- selfState: this.selfState,
46
- globalState: this.globalState,
47
- args: this.args,
48
- };
49
- }
50
- }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "agency-lang",
3
- "version": "0.0.44",
3
+ "version": "0.0.46",
4
4
  "description": "The Agency language",
5
5
  "main": "lib/index.js",
6
6
  "scripts": {