agency-lang 0.0.46 → 0.0.48

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.
@@ -3,6 +3,8 @@ import * as renderSpecialVar from "../templates/backends/graphGenerator/specialV
3
3
  import * as renderTime from "../templates/backends/typescriptGenerator/builtinFunctions/time.js";
4
4
  import * as builtinTools from "../templates/backends/typescriptGenerator/builtinTools.js";
5
5
  import * as renderFunctionDefinition from "../templates/backends/typescriptGenerator/functionDefinition.js";
6
+ import * as renderInternalFunctionCall from "../templates/backends/typescriptGenerator/internalFunctionCall.js";
7
+ import * as renderFunctionCallAssignment from "../templates/backends/typescriptGenerator/functionCallAssignment.js";
6
8
  import * as renderImports from "../templates/backends/typescriptGenerator/imports.js";
7
9
  import * as promptFunction from "../templates/backends/typescriptGenerator/promptFunction.js";
8
10
  import * as renderTool from "../templates/backends/typescriptGenerator/tool.js";
@@ -167,8 +169,12 @@ export class TypeScriptGenerator extends BaseGenerator {
167
169
  }
168
170
  // Direct assignment for other literal types
169
171
  const code = this.processNode(value);
170
- return (`${this.getScopeVar()}.${variableName}${typeAnnotation} = await ${code.trim()};` +
171
- "\n");
172
+ return renderFunctionCallAssignment.default({
173
+ variableName: `${this.getScopeVar()}.${variableName}`,
174
+ typeAnnotation,
175
+ functionCode: code.trim(),
176
+ nodeContext: this.getCurrentScope() === "node",
177
+ });
172
178
  }
173
179
  else if (value.type === "timeBlock") {
174
180
  const timingVarName = variableName;
@@ -314,7 +320,16 @@ export class TypeScriptGenerator extends BaseGenerator {
314
320
  let argsString = "";
315
321
  if (this.isInternalFunction(node.functionName)) {
316
322
  argsString = parts.join(", ");
317
- return `${functionName}([${argsString}])`;
323
+ const metadata = `{
324
+ statelogClient,
325
+ graph: __graph,
326
+ messages: __messages,
327
+ }`;
328
+ return renderInternalFunctionCall.default({
329
+ functionName,
330
+ argsString,
331
+ metadata,
332
+ });
318
333
  }
319
334
  else {
320
335
  // must be a builtin function or imported function
@@ -424,8 +439,6 @@ export class TypeScriptGenerator extends BaseGenerator {
424
439
  const clientConfig = prompt.config ? this.processNode(prompt.config) : "{}";
425
440
  const metadataObj = `{
426
441
  messages: __messages,
427
- interruptResponse: __interruptResponse,
428
- toolCall: __toolCall,
429
442
  }`;
430
443
  this.toolsUsed = []; // reset after use
431
444
  const scopedFunctionArgs = functionArgs.map((arg) => this.generateScopedVariableName(arg));
@@ -440,6 +453,7 @@ export class TypeScriptGenerator extends BaseGenerator {
440
453
  tools,
441
454
  functionCalls,
442
455
  clientConfig,
456
+ nodeContext: this.getCurrentScope() === "node",
443
457
  });
444
458
  }
445
459
  processImportStatement(node) {
@@ -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 \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";
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 = state.__metadata.__stateStack;\n \n // restore global state\n if (state.__metadata?.__stateStack?.global) {\n __global = state.__metadata.__stateStack.global;\n }\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 {{#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;
@@ -11,8 +11,13 @@ graph.node("{{{name}}}", async (state): Promise<any> => {
11
11
  // if \`state.__metadata?.__stateStack\` is set, that means we are resuming execution
12
12
  // at this node after an interrupt. In that case, this is the line that restores the state.
13
13
  if (state.__metadata?.__stateStack) {
14
- __stateStack = structuredClone(state.__metadata.__stateStack);
14
+ __stateStack = state.__metadata.__stateStack;
15
15
 
16
+ // restore global state
17
+ if (state.__metadata?.__stateStack?.global) {
18
+ __global = state.__metadata.__stateStack.global;
19
+ }
20
+
16
21
  // clear the state stack from metadata so it doesn't propagate to other nodes.
17
22
  state.__metadata.__stateStack = undefined;
18
23
  }
@@ -29,17 +34,6 @@ graph.node("{{{name}}}", async (state): Promise<any> => {
29
34
 
30
35
  const __self: Record<string, any> = __stack.locals;
31
36
 
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
35
- const __interruptResponse: InterruptResponseType | undefined = state.__metadata?.interruptResponse;
36
- const __toolCall: Record<string, any>|undefined = __stateStack.other?.toolCall;
37
-
38
- // TODO pretty sure this isn't needed, check and remove
39
- if (state.__metadata?.state?.global) {
40
- __global = state.__metadata.state.global;
41
- }
42
-
43
37
  {{#hasParam}}
44
38
 
45
39
  const __params = {{{paramNames}}};
@@ -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\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();";
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;\n\nexport type InterruptResponseApprove = {\n type: \"approve\";\n newArguments?: Record<string, any>;\n};\nexport type InterruptResponseReject = {\n type: \"reject\";\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.interruptData.messages || []).map((json: any) => {\n // create message objects from JSON\n return messageFromJSON(json);\n });\n __stateStack.interruptData.messages = messages;\n __stateStack.interruptData.interruptResponse = interruptResponse;\n\n if (interruptResponse.type === \"approve\" && interruptResponse.newArguments) {\n __stateStack.interruptData.toolCall = {\n ...__stateStack.interruptData.toolCall,\n arguments: { ...__stateStack.interruptData.toolCall.arguments, ...interruptResponse.newArguments },\n };\n // Error:\n // TypeError: Cannot set property arguments of #<ToolCall> which has only a getter\n // toolCall.arguments = { ...toolCall.arguments, ...interruptResponse.newArguments };\n //\n // const lastMessage = __stateStack.interruptData.messages[__stateStack.interruptData.messages.length - 1];\n // if (lastMessage && lastMessage.role === \"assistant\") {\n // const toolCall = lastMessage.toolCalls?.[lastMessage.toolCalls.length - 1];\n // if (toolCall) {\n // toolCall.arguments = { ...toolCall.arguments, ...interruptResponse.newArguments };\n // }\n // }\n }\n\n\n // start at the last node we visited\n const nodesTraversed = __stateStack.interruptData.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 __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, newArguments?: Record<string, any>) {\n return await respondToInterrupt(interrupt, { type: \"approve\", newArguments });\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 public interruptData: 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 interruptData: this.interruptData,\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.interruptData = json.interruptData || {};\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;
@@ -92,18 +92,15 @@ function printJSON(obj: any) {
92
92
  console.log(JSON.stringify(obj, null, 2));
93
93
  }
94
94
 
95
- export type InterruptResponseType = InterruptResponseApprove | InterruptResponseReject | InterruptResponseModify;
95
+ export type InterruptResponseType = InterruptResponseApprove | InterruptResponseReject;
96
+
96
97
  export type InterruptResponseApprove = {
97
98
  type: "approve";
99
+ newArguments?: Record<string, any>;
98
100
  };
99
101
  export type InterruptResponseReject = {
100
102
  type: "reject";
101
103
  };
102
- export type InterruptResponseModify = {
103
- type: "modify";
104
- newArguments: Record<string, any>;
105
- };
106
-
107
104
 
108
105
  export async function respondToInterrupt(_interrupt: Interrupt, _interruptResponse: InterruptResponseType) {
109
106
  const interrupt = structuredClone(_interrupt);
@@ -112,21 +109,40 @@ export async function respondToInterrupt(_interrupt: Interrupt, _interruptRespon
112
109
  __stateStack = StateStack.fromJSON(interrupt.__state || {});
113
110
  __stateStack.deserializeMode();
114
111
 
115
- const messages = (__stateStack.other.messages || []).map((json: any) => {
112
+ const messages = (__stateStack.interruptData.messages || []).map((json: any) => {
116
113
  // create message objects from JSON
117
114
  return messageFromJSON(json);
118
115
  });
116
+ __stateStack.interruptData.messages = messages;
117
+ __stateStack.interruptData.interruptResponse = interruptResponse;
118
+
119
+ if (interruptResponse.type === "approve" && interruptResponse.newArguments) {
120
+ __stateStack.interruptData.toolCall = {
121
+ ...__stateStack.interruptData.toolCall,
122
+ arguments: { ...__stateStack.interruptData.toolCall.arguments, ...interruptResponse.newArguments },
123
+ };
124
+ // Error:
125
+ // TypeError: Cannot set property arguments of #<ToolCall> which has only a getter
126
+ // toolCall.arguments = { ...toolCall.arguments, ...interruptResponse.newArguments };
127
+ //
128
+ // const lastMessage = __stateStack.interruptData.messages[__stateStack.interruptData.messages.length - 1];
129
+ // if (lastMessage && lastMessage.role === "assistant") {
130
+ // const toolCall = lastMessage.toolCalls?.[lastMessage.toolCalls.length - 1];
131
+ // if (toolCall) {
132
+ // toolCall.arguments = { ...toolCall.arguments, ...interruptResponse.newArguments };
133
+ // }
134
+ // }
135
+ }
136
+
119
137
 
120
138
  // start at the last node we visited
121
- const nodesTraversed = __stateStack.other.nodesTraversed || [];
139
+ const nodesTraversed = __stateStack.interruptData.nodesTraversed || [];
122
140
  const nodeName = nodesTraversed[nodesTraversed.length - 1];
123
141
  const result = await graph.run(nodeName, {
124
142
  messages: messages,
125
143
  __metadata: {
126
144
  graph: graph,
127
145
  statelogClient: __statelogClient,
128
- interruptResponse: interruptResponse,
129
- state: interrupt.__state,
130
146
  __stateStack: __stateStack,
131
147
  },
132
148
 
@@ -136,8 +152,8 @@ export async function respondToInterrupt(_interrupt: Interrupt, _interruptRespon
136
152
  return result.data;
137
153
  }
138
154
 
139
- export async function approveInterrupt(interrupt: Interrupt) {
140
- return await respondToInterrupt(interrupt, { type: "approve" });
155
+ export async function approveInterrupt(interrupt: Interrupt, newArguments?: Record<string, any>) {
156
+ return await respondToInterrupt(interrupt, { type: "approve", newArguments });
141
157
  }
142
158
 
143
159
  export async function rejectInterrupt(interrupt: Interrupt) {
@@ -156,6 +172,7 @@ class StateStack {
156
172
  private mode: "serialize" | "deserialize" = "serialize";
157
173
  public globals: Record<string, any> = {};
158
174
  public other: Record<string, any> = {};
175
+ public interruptData: Record<string, any> = {};
159
176
 
160
177
  private deserializeStackLength = 0;
161
178
 
@@ -200,6 +217,7 @@ class StateStack {
200
217
  stack: this.stack,
201
218
  globals: this.globals,
202
219
  other: this.other,
220
+ interruptData: this.interruptData,
203
221
  mode: this.mode,
204
222
  deserializeStackLength: this.deserializeStackLength,
205
223
  });
@@ -210,6 +228,7 @@ class StateStack {
210
228
  stateStack.stack = json.stack || [];
211
229
  stateStack.globals = json.globals || {};
212
230
  stateStack.other = json.other || {};
231
+ stateStack.interruptData = json.interruptData || {};
213
232
  stateStack.mode = json.mode || "serialize";
214
233
  stateStack.deserializeStackLength = json.deserializeStackLength || 0;
215
234
  return stateStack;
@@ -0,0 +1,9 @@
1
+ export declare const template = "{{{variableName:string}}}{{{typeAnnotation:string}}} = await {{{functionCode:string}}};\n\nif (isInterrupt({{{variableName:string}}})) {\n {{#nodeContext}}\n return { ...state, data: {{{variableName:string}}} };\n {{/nodeContext}}\n {{^nodeContext}}\n return { data: {{{variableName:string}}} };\n {{/nodeContext}}\n}";
2
+ export type TemplateType = {
3
+ variableName: string;
4
+ typeAnnotation: string;
5
+ functionCode: string;
6
+ nodeContext: boolean;
7
+ };
8
+ declare const render: (args: TemplateType) => string;
9
+ export default render;
@@ -0,0 +1,18 @@
1
+ // THIS FILE WAS AUTO-GENERATED
2
+ // Source: lib/templates/backends/typescriptGenerator/functionCallAssignment.mustache
3
+ // Any manual changes will be lost.
4
+ import { apply } from "typestache";
5
+ export const template = `{{{variableName:string}}}{{{typeAnnotation:string}}} = await {{{functionCode:string}}};
6
+
7
+ if (isInterrupt({{{variableName:string}}})) {
8
+ {{#nodeContext}}
9
+ return { ...state, data: {{{variableName:string}}} };
10
+ {{/nodeContext}}
11
+ {{^nodeContext}}
12
+ return { data: {{{variableName:string}}} };
13
+ {{/nodeContext}}
14
+ }`;
15
+ const render = (args) => {
16
+ return apply(template, args);
17
+ };
18
+ export default render;
@@ -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;\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}";
1
+ export declare const template = "\nexport async function {{{functionName:string}}}(args, __metadata={}) : Promise<{{{returnType}}}> {\n const __messages: Message[] = __metadata?.messages || [];\n const __stack = __stateStack.getNewState();\n const __step = __stack.step;\n const __self: Record<string, any> = __stack.locals;\n const __graph = __metadata?.graph || graph;\n const statelogClient = __metadata?.statelogClient || __statelogClient;\n\n // args are always set whether we're restoring from state or not.\n // If we're not restoring from state, args were obviously passed in through the code.\n // If we are restoring from state, the node that called this function had to have passed \n // these arguments into this function call.\n // if we're restoring state, this will override __stack.args (which will be set),\n // but with the same values, so it doesn't matter that those values are being overwritten.\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;
@@ -4,16 +4,19 @@
4
4
  import { apply } from "typestache";
5
5
  export const template = `
6
6
  export async function {{{functionName:string}}}(args, __metadata={}) : Promise<{{{returnType}}}> {
7
- const __messages: Message[] = [];
7
+ const __messages: Message[] = __metadata?.messages || [];
8
8
  const __stack = __stateStack.getNewState();
9
9
  const __step = __stack.step;
10
10
  const __self: Record<string, any> = __stack.locals;
11
+ const __graph = __metadata?.graph || graph;
12
+ const statelogClient = __metadata?.statelogClient || __statelogClient;
11
13
 
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.
14
+ // args are always set whether we're restoring from state or not.
15
+ // If we're not restoring from state, args were obviously passed in through the code.
16
+ // If we are restoring from state, the node that called this function had to have passed
17
+ // these arguments into this function call.
18
+ // if we're restoring state, this will override __stack.args (which will be set),
19
+ // but with the same values, so it doesn't matter that those values are being overwritten.
17
20
  const __params = [{{{argsStr}}}];
18
21
  (args).forEach((item, index) => {
19
22
  __stack.args[__params[index]] = item;
@@ -0,0 +1,8 @@
1
+ export declare const template = "{{{functionName:string}}}([{{{argsString:string}}}], {{{metadata:string}}});";
2
+ export type TemplateType = {
3
+ functionName: string;
4
+ argsString: string;
5
+ metadata: string;
6
+ };
7
+ declare const render: (args: TemplateType) => string;
8
+ export default render;
@@ -0,0 +1,9 @@
1
+ // THIS FILE WAS AUTO-GENERATED
2
+ // Source: lib/templates/backends/typescriptGenerator/internalFunctionCall.mustache
3
+ // Any manual changes will be lost.
4
+ import { apply } from "typestache";
5
+ export const template = `{{{functionName:string}}}([{{{argsString:string}}}], {{{metadata:string}}});`;
6
+ const render = (args) => {
7
+ return apply(template, args);
8
+ };
9
+ export default render;
@@ -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\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}";
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 = __stateStack.interruptData?.toolCall ? [__stateStack.interruptData.toolCall] : [];\n const __interruptResponse:InterruptResponseType|null = __stateStack.interruptData?.interruptResponse || null;\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 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 statelogClient.debug(`Tool call interrupted execution.`, {\n messages: __messages,\n model: __client.getModel(),\n });\n\n __stateStack.interruptData = {\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 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 {{#nodeContext}}\n return { ...state, data: __self.{{{variableName:string}}} };\n {{/nodeContext}}\n {{^nodeContext}}\n return __self.{{{variableName:string}}};\n {{/nodeContext}}\n}";
2
2
  export type TemplateType = {
3
3
  variableName: string;
4
4
  argsStr: string;
@@ -10,6 +10,7 @@ export type TemplateType = {
10
10
  clientConfig: string;
11
11
  functionCalls: string;
12
12
  funcCallParams: string;
13
+ nodeContext: boolean;
13
14
  };
14
15
  declare const render: (args: TemplateType) => string;
15
16
  export default render;
@@ -10,8 +10,8 @@ async function _{{{variableName:string}}}({{{argsStr:string}}}): Promise<{{{type
10
10
 
11
11
  // These are to restore state after interrupt.
12
12
  // TODO I think this could be implemented in a cleaner way.
13
- let __toolCalls = __metadata?.toolCall ? [__metadata.toolCall] : [];
14
- const __interruptResponse:InterruptResponseType|undefined = __metadata?.interruptResponse;
13
+ let __toolCalls = __stateStack.interruptData?.toolCall ? [__stateStack.interruptData.toolCall] : [];
14
+ const __interruptResponse:InterruptResponseType|null = __stateStack.interruptData?.interruptResponse || null;
15
15
  const __tools = {{{tools}}};
16
16
 
17
17
  {{#hasResponseFormat}}
@@ -38,7 +38,7 @@ async function _{{{variableName:string}}}({{{argsStr:string}}}): Promise<{{{type
38
38
  });
39
39
 
40
40
  const endTime = performance.now();
41
- await statelogClient.promptCompletion({
41
+ statelogClient.promptCompletion({
42
42
  messages: __messages,
43
43
  completion: __completion,
44
44
  model: __client.getModel(),
@@ -73,12 +73,12 @@ async function _{{{variableName:string}}}({{{argsStr:string}}}): Promise<{{{type
73
73
  }
74
74
 
75
75
  if (haltExecution) {
76
- await statelogClient.debug(\`Tool call interrupted execution.\`, {
76
+ statelogClient.debug(\`Tool call interrupted execution.\`, {
77
77
  messages: __messages,
78
78
  model: __client.getModel(),
79
79
  });
80
80
 
81
- __stateStack.other = {
81
+ __stateStack.interruptData = {
82
82
  messages: __messages.map((msg) => msg.toJSON()),
83
83
  nodesTraversed: __graph.getNodesTraversed(),
84
84
  toolCall: haltToolCall,
@@ -96,7 +96,7 @@ async function _{{{variableName:string}}}({{{argsStr:string}}}): Promise<{{{type
96
96
 
97
97
  const nextEndTime = performance.now();
98
98
 
99
- await statelogClient.promptCompletion({
99
+ statelogClient.promptCompletion({
100
100
  messages: __messages,
101
101
  completion: __completion,
102
102
  model: __client.getModel(),
@@ -135,7 +135,12 @@ __self.{{{variableName:string}}} = await _{{{variableName:string}}}({{{funcCallP
135
135
 
136
136
  // return early from node if this is an interrupt
137
137
  if (isInterrupt(__self.{{{variableName:string}}})) {
138
+ {{#nodeContext}}
138
139
  return { ...state, data: __self.{{{variableName:string}}} };
140
+ {{/nodeContext}}
141
+ {{^nodeContext}}
142
+ return __self.{{{variableName:string}}};
143
+ {{/nodeContext}}
139
144
  }`;
140
145
  const render = (args) => {
141
146
  return apply(template, args);
@@ -1,4 +1,4 @@
1
- export declare const template = "if (\n toolCall.name === \"{{{name:string}}}\"\n) {\n const args = toolCall.arguments;\n\n const params = [ {{{paramsStr:string}}} ];\n\n toolCallStartTime = performance.now();\n \n let result: any;\n if (__interruptResponse && __interruptResponse.type === \"reject\") {\n __messages.push(toolMessage(\"tool call rejected\", {\n tool_call_id: toolCall.id,\n name: toolCall.name,\n }));\n } else {\n result = await {{{name}}}(params);\n }\n toolCallEndTime = performance.now();\n\nawait statelogClient.toolCall({\n toolName: \"{{{name:string}}}\",\n params,\n output: result,\n model: __client.getModel(),\n timeTaken: toolCallEndTime - toolCallStartTime,\n });\n\n if (isInterrupt(result)) {\n haltInterrupt = result;\n haltToolCall = {\n id: toolCall.id,\n name: toolCall.name,\n arguments: toolCall.arguments,\n }\n haltExecution = true;\n break;\n }\n\n // Add function result to messages\n __messages.push(toolMessage(result, {\n tool_call_id: toolCall.id,\n name: toolCall.name,\n }));\n}";
1
+ export declare const template = "if (\n toolCall.name === \"{{{name:string}}}\"\n) {\n const args = toolCall.arguments;\n\n const params = [ {{{paramsStr:string}}} ];\n\n toolCallStartTime = performance.now();\n \n let result: any;\n if (__interruptResponse && __interruptResponse.type === \"reject\") {\n __messages.push(toolMessage(\"tool call rejected\", {\n tool_call_id: toolCall.id,\n name: toolCall.name,\n }));\n } else {\n result = await {{{name}}}(params);\n }\n toolCallEndTime = performance.now();\n\n statelogClient.toolCall({\n toolName: \"{{{name:string}}}\",\n params,\n output: result,\n model: __client.getModel(),\n timeTaken: toolCallEndTime - toolCallStartTime,\n });\n\n if (isInterrupt(result)) {\n haltInterrupt = result;\n haltToolCall = {\n id: toolCall.id,\n name: toolCall.name,\n arguments: toolCall.arguments,\n }\n haltExecution = true;\n break;\n }\n\n // Add function result to messages\n __messages.push(toolMessage(result, {\n tool_call_id: toolCall.id,\n name: toolCall.name,\n }));\n}";
2
2
  export type TemplateType = {
3
3
  name: string;
4
4
  paramsStr: string;
@@ -22,7 +22,7 @@ export const template = `if (
22
22
  }
23
23
  toolCallEndTime = performance.now();
24
24
 
25
- await statelogClient.toolCall({
25
+ statelogClient.toolCall({
26
26
  toolName: "{{{name:string}}}",
27
27
  params,
28
28
  output: result,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "agency-lang",
3
- "version": "0.0.46",
3
+ "version": "0.0.48",
4
4
  "description": "The Agency language",
5
5
  "main": "lib/index.js",
6
6
  "scripts": {
@@ -41,9 +41,9 @@
41
41
  "homepage": "https://github.com/egonSchiele/agency-lang#readme",
42
42
  "dependencies": {
43
43
  "egonlog": "^0.0.2",
44
- "piemachine": "^0.0.6",
44
+ "piemachine": "^0.0.7",
45
45
  "smoltalk": "^0.0.15",
46
- "statelog-client": "^0.0.34",
46
+ "statelog-client": "^0.0.35",
47
47
  "tarsec": "^0.1.1",
48
48
  "typestache": "^0.4.4",
49
49
  "zod": "^4.3.5"
@@ -56,4 +56,4 @@
56
56
  "typescript": "^5.9.3",
57
57
  "vitest": "^4.0.16"
58
58
  }
59
- }
59
+ }