agency-lang 0.0.66 → 0.0.67

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.
@@ -1,4 +1,4 @@
1
- export declare const template = "goToNode(\"{{{nodeName:string}}}\",\n {\n messages: __self.messages,\n __metadata: {\n graph: __graph,\n statelogClient,\n callbacks: __callbacks,\n },\n {{#hasData}}\n data: {{{data:string}}}\n {{/hasData}}\n {{^hasData}}\n data: null\n {{/hasData}}\n }\n);";
1
+ export declare const template = "goToNode(\"{{{nodeName:string}}}\",\n {\n messages: __self.messages,\n __metadata: {\n graph: __graph,\n // we need to pass in the state log client here because\n // if we rely on the local state log client\n // each client in each file has a different trace id.\n // So we pass in the client to make sure they all use the same trace id\n\n statelogClient,\n callbacks: __callbacks,\n },\n {{#hasData}}\n data: {{{data:string}}}\n {{/hasData}}\n {{^hasData}}\n data: null\n {{/hasData}}\n }\n);";
2
2
  export type TemplateType = {
3
3
  nodeName: string;
4
4
  hasData: boolean;
@@ -7,6 +7,11 @@ export const template = `goToNode("{{{nodeName:string}}}",
7
7
  messages: __self.messages,
8
8
  __metadata: {
9
9
  graph: __graph,
10
+ // we need to pass in the state log client here because
11
+ // if we rely on the local state log client
12
+ // each client in each file has a different trace id.
13
+ // So we pass in the client to make sure they all use the same trace id
14
+
10
15
  statelogClient,
11
16
  callbacks: __callbacks,
12
17
  },
@@ -1,4 +1,4 @@
1
- export declare const template = "import { fileURLToPath } from \"url\";\nimport process from \"process\";\nimport { z } from \"zod\";\nimport * as readline from \"readline\";\nimport fs from \"fs\";\nimport { StatelogClient, SimpleMachine, goToNode, nanoid } from \"agency-lang\";\nimport * as smoltalk from \"agency-lang\";\n\n/* Code to log to statelog */\nconst statelogHost = \"{{{logHost:string}}}\";\nconst __traceId = nanoid();\nconst statelogConfig = {\n host: statelogHost,\n traceId: __traceId,\n {{#hasApiKey}}\n apiKey: \"{{{logApiKey?:string}}}\",\n {{/hasApiKey}}\n {{^hasApiKey}}\n apiKey: process.env.STATELOG_API_KEY || \"\",\n {{/hasApiKey}}\n projectId: \"{{{logProjectId:string}}}\",\n debugMode: {{{logDebugMode:boolean}}},\n};\nconst __statelogClient = new StatelogClient(statelogConfig);\n\n/* Code for Smoltalk client */\nconst __model = \"{{{clientDefaultModel:string}}}\";\n\nconst __getClientWithConfig = (config = {}) => {\n const defaultConfig = {\n {{#hasOpenAiApiKey}}\n openAiApiKey: \"{{{clientOpenAiApiKey?:string}}}\",\n {{/hasOpenAiApiKey}}\n {{^hasOpenAiApiKey}}\n openAiApiKey: process.env.OPENAI_API_KEY || \"\",\n {{/hasOpenAiApiKey}}\n {{#hasGoogleApiKey}}\n googleApiKey: \"{{{clientGoogleApiKey?:string}}}\",\n {{/hasGoogleApiKey}}\n {{^hasGoogleApiKey}}\n googleApiKey: process.env.GEMINI_API_KEY || \"\",\n {{/hasGoogleApiKey}}\n model: __model,\n logLevel: \"{{{clientLogLevel:string}}}\",\n };\n\n return smoltalk.getClient({ ...defaultConfig, ...config });\n};\n\nlet __client = __getClientWithConfig();\n\n/* Code for SimpleMachine graph */\n\n// enable debug logging\nconst graphConfig = {\n debug: {\n log: true,\n logData: false,\n },\n statelog: statelogConfig,\n};\n\nconst graph = new SimpleMachine(graphConfig);\n\n/******** builtins ********/\n\nconst not = (val) => !val;\nconst eq = (a, b) => a === b;\nconst neq = (a, b) => a !== b;\nconst lt = (a, b) => a < b;\nconst lte = (a, b) => a <= b;\nconst gt = (a, b) => a > b;\nconst gte = (a, b) => a >= b;\nconst and = (a, b) => a && b;\nconst or = (a, b) => a || b;\nconst head = (arr) => arr[0];\nconst tail = (arr) => arr.slice(1);\nconst empty = (arr) => arr.length === 0;\n\nasync function _builtinFetch(url, args = {}) {\n const result = await fetch(url, args);\n try {\n const text = await result.text();\n return text;\n } catch (e) {\n throw new Error(`Failed to get text from ${url}: ${e}`);\n }\n}\n\nasync function _builtinFetchJSON(url, args = {}) {\n const result = await fetch(url, args);\n try {\n const json = await result.json();\n return json;\n } catch (e) {\n throw new Error(`Failed to parse JSON from ${url}: ${e}`);\n }\n}\n\nfunction _builtinInput(prompt) {\n const rl = readline.createInterface({\n input: process.stdin,\n output: process.stdout,\n });\n\n return new Promise((resolve) => {\n rl.question(prompt, (answer) => {\n rl.close();\n resolve(answer);\n });\n });\n}\n\nfunction _builtinRead(filename) {\n const data = fs.readFileSync(filename);\n const contents = data.toString(\"utf8\");\n return contents;\n}\n\n/*\n * @param filePath The absolute or relative path to the image file.\n * @returns The Base64 string, or null if an error occurs.\n */\nfunction _builtinReadImage(filePath) {\n const data = fs.readFileSync(filePath); // Synchronous file reading\n const base64String = data.toString(\"base64\");\n return base64String;\n}\n\nfunction _builtinSleep(seconds) {\n return new Promise((resolve) => {\n setTimeout(resolve, seconds * 1000);\n });\n}\n\nfunction printJSON(obj) {\n console.log(JSON.stringify(obj, null, 2));\n}\n\nexport const __readSkillTool = {\n name: \"readSkill\",\n description: `Skills provide specialized knowledge and instructions for particular scenarios.\nUse this tool when you need enhanced guidance for a specific type of task.\n\nArgs:\n filepath: The name of the skill to read.\n\nReturns:\n The skill content with specialized instructions, or an error message\n if the skill is not found.\n`,\n schema: z.object({\"filepath\": z.string(), })\n};\n\nexport function readSkill({filepath}) {\n return _builtinRead(filepath);\n}\n\n/******** for internal agency use only ********/\n\nfunction __createReturnObject(result) {\n // Note: we're *not* using structuredClone here because structuredClone\n // doesn't call `toJSON`, so it's not cloning our message objects correctly.\n return JSON.parse(JSON.stringify({\n messages: result.messages,\n data: result.data,\n tokens: __stateStack.globals.__tokenStats\n }));\n}\n\n\n\n/******** interrupts ********/\n\nexport function interrupt(data) {\n return {\n type: \"interrupt\",\n data,\n };\n}\n\nexport function isInterrupt(obj) {\n return obj && obj.type === \"interrupt\";\n}\n\nexport async function respondToInterrupt(\n _interrupt,\n _interruptResponse,\n metadata = {},\n) {\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(\n (json) => {\n // create message objects from JSON\n return smoltalk.messageFromJSON(json);\n },\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: {\n ...__stateStack.interruptData.toolCall.arguments,\n ...interruptResponse.newArguments,\n },\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 // 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 __callbacks: metadata.callbacks,\n },\n\n // restore args from the state stack\n data: \"<from-stack>\",\n });\n return __createReturnObject(__result);\n}\n\nexport async function approveInterrupt(\n interrupt,\n metadata = {},\n) {\n return await respondToInterrupt(interrupt, { type: \"approve\" }, metadata);\n}\n\nexport async function modifyInterrupt(\n interrupt,\n newArguments,\n metadata = {},\n) {\n return await respondToInterrupt(\n interrupt,\n { type: \"approve\", newArguments },\n metadata,\n );\n}\n\nexport async function rejectInterrupt(\n interrupt,\n metadata = {},\n) {\n return await respondToInterrupt(interrupt, { type: \"reject\" }, metadata);\n}\n\n/****** StateStack and related functions for serializing/deserializing execution state during interrupts ********/\n\n// See docs for notes on how this works.\nclass StateStack {\n stack = [];\n mode = \"serialize\";\n globals = {};\n other = {};\n interruptData = {};\n\n deserializeStackLength = 0;\n\n constructor(\n stack = [],\n mode = \"serialize\",\n ) {\n this.stack = stack;\n this.mode = mode;\n }\n\n getNewState() {\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 = {\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() {\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) {\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();\n\n__stateStack.globals.__tokenStats = {\n usage: {\n inputTokens: 0,\n outputTokens: 0,\n cachedInputTokens: 0,\n totalTokens: 0,\n },\n cost: {\n inputCost: 0,\n outputCost: 0,\n totalCost: 0,\n currency: \"USD\",\n },\n};\n\nfunction __updateTokenStats(\n usage,\n cost,\n) {\n if (!usage || !cost) return;\n const tokenStats = __stateStack.globals.__tokenStats;\n tokenStats.usage.inputTokens += usage.inputTokens || 0;\n tokenStats.usage.outputTokens += usage.outputTokens || 0;\n tokenStats.usage.cachedInputTokens += usage.cachedInputTokens || 0;\n tokenStats.usage.totalTokens += usage.totalTokens || 0;\n\n tokenStats.cost.inputCost += cost.inputCost || 0;\n tokenStats.cost.outputCost += cost.outputCost || 0;\n tokenStats.cost.totalCost += cost.totalCost || 0;\n}\n\n/**** Streaming callback and lock ****/\nfunction isGenerator(variable) {\n const toString = Object.prototype.toString.call(variable);\n return (\n toString === \"[object Generator]\" || toString === \"[object AsyncGenerator]\"\n );\n}\n\nlet __callbacks = {};\n\nasync function __callHook(name, data) {\n if (__callbacks[name]) {\n await __callbacks[name](data);\n }\n}\n\nlet onStreamLock = false;\n\nfunction __cloneArray(arr) {\n if (arr == undefined) return [];\n return [...arr];\n}\n\nconst handleStreamingResponse = async (__completion) => {\n if (isGenerator(__completion)) {\n if (!__callbacks.onStream) {\n console.log(\n \"No onStream callback provided for streaming response, returning response synchronously\",\n );\n statelogClient.debug(\n \"Got streaming response but no onStream callback provided, returning response synchronously\",\n {\n prompt: __prompt,\n callbacks: Object.keys(__callbacks),\n },\n );\n let syncResult = \"\";\n for await (const chunk of __completion) {\n switch (chunk.type) {\n case \"tool_call\":\n __toolCalls.push(chunk.toolCall);\n break;\n case \"done\":\n syncResult = chunk.result;\n break;\n case \"error\":\n console.error(`Error in LLM response stream: ${chunk.error}`);\n break;\n default:\n break;\n }\n }\n __completion = { success: true, value: syncResult };\n } else {\n // try to acquire lock\n let count = 0;\n // wait 60 seconds to acquire lock\n while (onStreamLock && count < 10 * 60) {\n await _builtinSleep(0.1);\n count++;\n }\n if (onStreamLock) {\n console.log(`Couldn't acquire lock, ${count}`);\n }\n onStreamLock = true;\n\n for await (const chunk of __completion) {\n switch (chunk.type) {\n case \"text\":\n __callbacks.onStream({ type: \"text\", text: chunk.text });\n break;\n case \"tool_call\":\n __toolCalls.push(chunk.toolCall);\n __callbacks.onStream({\n type: \"tool_call\",\n toolCall: chunk.toolCall,\n });\n break;\n case \"done\":\n __callbacks.onStream({ type: \"done\", result: chunk.result });\n __completion = { success: true, value: chunk.result };\n break;\n case \"error\":\n __callbacks.onStream({ type: \"error\", error: chunk.error });\n break;\n }\n }\n\n onStreamLock = false;\n }\n }\n};\n\n\n/**** Message thread handling ****/\n\nclass MessageThread {\n messages = [];\n children = [];\n\n constructor(messages = []) {\n this.messages = messages;\n this.children = [];\n }\n\n addMessage(message) {\n this.messages.push(message);\n }\n\n cloneMessages() {\n return this.messages.map(m => m.toJSON()).map(m => smoltalk.messageFromJSON(m));\n }\n\n getMessages() {\n return this.messages;\n }\n\n setMessages(messages) {\n this.messages = messages;\n }\n\n newChild() {\n const child = new MessageThread();\n this.children.push(child);\n return child;\n }\n\n newSubthreadChild() {\n const child = new MessageThread(this.cloneMessages());\n this.children.push(child);\n return child;\n }\n\n toJSON() {\n return {\n messages: this.messages.map(m => m.toJSON()),\n children: this.children.map((child) => child.toJSON()),\n };\n }\n}";
1
+ export declare const template = "import { fileURLToPath } from \"url\";\nimport process from \"process\";\nimport { z } from \"zod\";\nimport * as readline from \"readline\";\nimport fs from \"fs\";\nimport { StatelogClient, SimpleMachine, goToNode, nanoid } from \"agency-lang\";\nimport * as smoltalk from \"agency-lang\";\n\n/* Code to log to statelog */\nconst statelogHost = \"{{{logHost:string}}}\";\nconst __traceId = nanoid();\nconst statelogConfig = {\n host: statelogHost,\n traceId: __traceId,\n {{#hasApiKey}}\n apiKey: \"{{{logApiKey?:string}}}\",\n {{/hasApiKey}}\n {{^hasApiKey}}\n apiKey: process.env.STATELOG_API_KEY || \"\",\n {{/hasApiKey}}\n projectId: \"{{{logProjectId:string}}}\",\n debugMode: {{{logDebugMode:boolean}}},\n};\nconst __statelogClient = new StatelogClient(statelogConfig);\n\n/* Code for Smoltalk client */\nconst __model = \"{{{clientDefaultModel:string}}}\";\n\nconst __getClientWithConfig = (config = {}) => {\n const defaultConfig = {\n {{#hasOpenAiApiKey}}\n openAiApiKey: \"{{{clientOpenAiApiKey?:string}}}\",\n {{/hasOpenAiApiKey}}\n {{^hasOpenAiApiKey}}\n openAiApiKey: process.env.OPENAI_API_KEY || \"\",\n {{/hasOpenAiApiKey}}\n {{#hasGoogleApiKey}}\n googleApiKey: \"{{{clientGoogleApiKey?:string}}}\",\n {{/hasGoogleApiKey}}\n {{^hasGoogleApiKey}}\n googleApiKey: process.env.GEMINI_API_KEY || \"\",\n {{/hasGoogleApiKey}}\n model: __model,\n logLevel: \"{{{clientLogLevel:string}}}\",\n };\n\n return smoltalk.getClient({ ...defaultConfig, ...config });\n};\n\nlet __client = __getClientWithConfig();\n\n/* Code for SimpleMachine graph */\n\n// enable debug logging\nconst graphConfig = {\n debug: {\n log: true,\n logData: false,\n },\n statelog: statelogConfig,\n};\n\nconst graph = new SimpleMachine(graphConfig);\n\n/******** builtins ********/\n\nconst not = (val) => !val;\nconst eq = (a, b) => a === b;\nconst neq = (a, b) => a !== b;\nconst lt = (a, b) => a < b;\nconst lte = (a, b) => a <= b;\nconst gt = (a, b) => a > b;\nconst gte = (a, b) => a >= b;\nconst and = (a, b) => a && b;\nconst or = (a, b) => a || b;\nconst head = (arr) => arr[0];\nconst tail = (arr) => arr.slice(1);\nconst empty = (arr) => arr.length === 0;\n\nasync function _builtinFetch(url, args = {}) {\n const result = await fetch(url, args);\n try {\n const text = await result.text();\n return text;\n } catch (e) {\n throw new Error(`Failed to get text from ${url}: ${e}`);\n }\n}\n\nasync function _builtinFetchJSON(url, args = {}) {\n const result = await fetch(url, args);\n try {\n const json = await result.json();\n return json;\n } catch (e) {\n throw new Error(`Failed to parse JSON from ${url}: ${e}`);\n }\n}\n\nfunction _builtinInput(prompt) {\n const rl = readline.createInterface({\n input: process.stdin,\n output: process.stdout,\n });\n\n return new Promise((resolve) => {\n rl.question(prompt, (answer) => {\n rl.close();\n resolve(answer);\n });\n });\n}\n\nfunction _builtinRead(filename) {\n const data = fs.readFileSync(filename);\n const contents = data.toString(\"utf8\");\n return contents;\n}\n\n/*\n * @param filePath The absolute or relative path to the image file.\n * @returns The Base64 string, or null if an error occurs.\n */\nfunction _builtinReadImage(filePath) {\n const data = fs.readFileSync(filePath); // Synchronous file reading\n const base64String = data.toString(\"base64\");\n return base64String;\n}\n\nfunction _builtinSleep(seconds) {\n return new Promise((resolve) => {\n setTimeout(resolve, seconds * 1000);\n });\n}\n\nfunction printJSON(obj) {\n console.log(JSON.stringify(obj, null, 2));\n}\n\nexport const __readSkillTool = {\n name: \"readSkill\",\n description: `Skills provide specialized knowledge and instructions for particular scenarios.\nUse this tool when you need enhanced guidance for a specific type of task.\n\nArgs:\n filepath: The name of the skill to read.\n\nReturns:\n The skill content with specialized instructions, or an error message\n if the skill is not found.\n`,\n schema: z.object({\"filepath\": z.string(), })\n};\n\nexport function readSkill({filepath}) {\n return _builtinRead(filepath);\n}\n\n/******** for internal agency use only ********/\n\nfunction __createReturnObject(result) {\n // Note: we're *not* using structuredClone here because structuredClone\n // doesn't call `toJSON`, so it's not cloning our message objects correctly.\n return JSON.parse(JSON.stringify({\n messages: result.messages,\n data: result.data,\n tokens: __stateStack.globals.__tokenStats\n }));\n}\n\n\n\n/******** interrupts ********/\n\nexport function interrupt(data) {\n return {\n type: \"interrupt\",\n data,\n };\n}\n\nexport function isInterrupt(obj) {\n return obj && obj.type === \"interrupt\";\n}\n\nexport async function respondToInterrupt(\n _interrupt,\n _interruptResponse,\n metadata = {},\n) {\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(\n (json) => {\n // create message objects from JSON\n return smoltalk.messageFromJSON(json);\n },\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: {\n ...__stateStack.interruptData.toolCall.arguments,\n ...interruptResponse.newArguments,\n },\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 // 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 // we need to pass in the state log client here because\n // if we rely on the local state log client\n // each client in each file has a different trace id.\n // So we pass in the client to make sure they all use the same trace id\n statelogClient: __statelogClient,\n __stateStack: __stateStack,\n __callbacks: metadata.callbacks,\n },\n\n // restore args from the state stack\n data: \"<from-stack>\",\n });\n return __createReturnObject(__result);\n}\n\nexport async function approveInterrupt(\n interrupt,\n metadata = {},\n) {\n return await respondToInterrupt(interrupt, { type: \"approve\" }, metadata);\n}\n\nexport async function modifyInterrupt(\n interrupt,\n newArguments,\n metadata = {},\n) {\n return await respondToInterrupt(\n interrupt,\n { type: \"approve\", newArguments },\n metadata,\n );\n}\n\nexport async function rejectInterrupt(\n interrupt,\n metadata = {},\n) {\n return await respondToInterrupt(interrupt, { type: \"reject\" }, metadata);\n}\n\n/****** StateStack and related functions for serializing/deserializing execution state during interrupts ********/\n\n// See docs for notes on how this works.\nclass StateStack {\n stack = [];\n mode = \"serialize\";\n globals = {};\n other = {};\n interruptData = {};\n\n deserializeStackLength = 0;\n\n constructor(\n stack = [],\n mode = \"serialize\",\n ) {\n this.stack = stack;\n this.mode = mode;\n }\n\n getNewState() {\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 = {\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() {\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) {\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();\n\n__stateStack.globals.__tokenStats = {\n usage: {\n inputTokens: 0,\n outputTokens: 0,\n cachedInputTokens: 0,\n totalTokens: 0,\n },\n cost: {\n inputCost: 0,\n outputCost: 0,\n totalCost: 0,\n currency: \"USD\",\n },\n};\n\nfunction __updateTokenStats(\n usage,\n cost,\n) {\n if (!usage || !cost) return;\n const tokenStats = __stateStack.globals.__tokenStats;\n tokenStats.usage.inputTokens += usage.inputTokens || 0;\n tokenStats.usage.outputTokens += usage.outputTokens || 0;\n tokenStats.usage.cachedInputTokens += usage.cachedInputTokens || 0;\n tokenStats.usage.totalTokens += usage.totalTokens || 0;\n\n tokenStats.cost.inputCost += cost.inputCost || 0;\n tokenStats.cost.outputCost += cost.outputCost || 0;\n tokenStats.cost.totalCost += cost.totalCost || 0;\n}\n\n/**** Streaming callback and lock ****/\nfunction isGenerator(variable) {\n const toString = Object.prototype.toString.call(variable);\n return (\n toString === \"[object Generator]\" || toString === \"[object AsyncGenerator]\"\n );\n}\n\nlet __callbacks = {};\n\nasync function __callHook(name, data) {\n if (__callbacks[name]) {\n await __callbacks[name](data);\n }\n}\n\nlet onStreamLock = false;\n\nfunction __cloneArray(arr) {\n if (arr == undefined) return [];\n return [...arr];\n}\n\nconst handleStreamingResponse = async (__completion, statelogClient, __prompt, __toolCalls) => {\n if (isGenerator(__completion)) {\n if (!__callbacks.onStream) {\n console.log(\n \"No onStream callback provided for streaming response, returning response synchronously\",\n );\n statelogClient.debug(\n \"Got streaming response but no onStream callback provided, returning response synchronously\",\n {\n prompt: __prompt,\n callbacks: Object.keys(__callbacks),\n },\n );\n let syncResult = \"\";\n for await (const chunk of __completion) {\n switch (chunk.type) {\n case \"tool_call\":\n __toolCalls.push(chunk.toolCall);\n break;\n case \"done\":\n syncResult = chunk.result;\n break;\n case \"error\":\n console.error(`Error in LLM response stream: ${chunk.error}`);\n break;\n default:\n break;\n }\n }\n return { success: true, value: syncResult };\n } else {\n // try to acquire lock\n let count = 0;\n // wait 60 seconds to acquire lock\n while (onStreamLock && count < 10 * 60) {\n await _builtinSleep(0.1);\n count++;\n }\n if (onStreamLock) {\n console.log(`Couldn't acquire lock, ${count}`);\n }\n onStreamLock = true;\n\n for await (const chunk of __completion) {\n switch (chunk.type) {\n case \"text\":\n __callbacks.onStream({ type: \"text\", text: chunk.text });\n break;\n case \"tool_call\":\n __toolCalls.push(chunk.toolCall);\n __callbacks.onStream({\n type: \"tool_call\",\n toolCall: chunk.toolCall,\n });\n break;\n case \"done\":\n __callbacks.onStream({ type: \"done\", result: chunk.result });\n return { success: true, value: chunk.result };\n case \"error\":\n __callbacks.onStream({ type: \"error\", error: chunk.error });\n break;\n }\n }\n\n onStreamLock = false;\n }\n }\n};\n\n\n/**** Message thread handling ****/\n\nclass MessageThread {\n messages = [];\n children = [];\n\n constructor(messages = []) {\n this.messages = messages;\n this.children = [];\n }\n\n addMessage(message) {\n this.messages.push(message);\n }\n\n cloneMessages() {\n return this.messages.map(m => m.toJSON()).map(m => smoltalk.messageFromJSON(m));\n }\n\n getMessages() {\n return this.messages;\n }\n\n setMessages(messages) {\n this.messages = messages;\n }\n\n newChild() {\n const child = new MessageThread();\n this.children.push(child);\n return child;\n }\n\n newSubthreadChild() {\n const child = new MessageThread(this.cloneMessages());\n this.children.push(child);\n return child;\n }\n\n toJSON() {\n return {\n messages: this.messages.map(m => m.toJSON()),\n children: this.children.map((child) => child.toJSON()),\n };\n }\n}";
2
2
  export type TemplateType = {
3
3
  logHost: string;
4
4
  hasApiKey: boolean;
@@ -235,6 +235,10 @@ export async function respondToInterrupt(
235
235
  messages: messages,
236
236
  __metadata: {
237
237
  graph: graph,
238
+ // we need to pass in the state log client here because
239
+ // if we rely on the local state log client
240
+ // each client in each file has a different trace id.
241
+ // So we pass in the client to make sure they all use the same trace id
238
242
  statelogClient: __statelogClient,
239
243
  __stateStack: __stateStack,
240
244
  __callbacks: metadata.callbacks,
@@ -402,7 +406,7 @@ function __cloneArray(arr) {
402
406
  return [...arr];
403
407
  }
404
408
 
405
- const handleStreamingResponse = async (__completion) => {
409
+ const handleStreamingResponse = async (__completion, statelogClient, __prompt, __toolCalls) => {
406
410
  if (isGenerator(__completion)) {
407
411
  if (!__callbacks.onStream) {
408
412
  console.log(
@@ -431,7 +435,7 @@ const handleStreamingResponse = async (__completion) => {
431
435
  break;
432
436
  }
433
437
  }
434
- __completion = { success: true, value: syncResult };
438
+ return { success: true, value: syncResult };
435
439
  } else {
436
440
  // try to acquire lock
437
441
  let count = 0;
@@ -459,8 +463,7 @@ const handleStreamingResponse = async (__completion) => {
459
463
  break;
460
464
  case "done":
461
465
  __callbacks.onStream({ type: "done", result: chunk.result });
462
- __completion = { success: true, value: chunk.result };
463
- break;
466
+ return { success: true, value: chunk.result };
464
467
  case "error":
465
468
  __callbacks.onStream({ type: "error", error: chunk.error });
466
469
  break;
@@ -1,4 +1,4 @@
1
- export declare const template = "\nasync function _{{{variableName:string}}}({{{argsStr:string}}}) {\n const __prompt = {{{promptCode:string}}};\n const startTime = performance.now();\n let __messages = __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 = __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;\n\n if (__toolCalls.length === 0) {\n __messages.push(smoltalk.userMessage(__prompt));\n \n \n await __callHook(\"onLLMCallStart\", { prompt: __prompt, tools: __tools, model: __client.getModel() });\n let __completion = await __client.text({\n messages: __messages,\n tools: __tools,\n responseFormat: __responseFormat,\n stream: {{{isStreaming:boolean}}}\n });\n\n const endTime = performance.now();\n\n await handleStreamingResponse(__completion);\n\n statelogClient.promptCompletion({\n messages: __messages,\n completion: __completion,\n model: __client.getModel(),\n timeTaken: endTime - startTime,\n tools: __tools,\n responseFormat: __responseFormat\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(smoltalk.assistantMessage(responseMessage.output, { toolCalls: __toolCalls }));\n }\n\n __updateTokenStats(responseMessage.usage, responseMessage.cost);\n await __callHook(\"onLLMCallEnd\", { result: responseMessage, usage: responseMessage.usage, cost: responseMessage.cost, timeTaken: endTime - startTime });\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 = 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 await __callHook(\"onLLMCallStart\", { prompt: __prompt, tools: __tools, model: __client.getModel() });\n let __completion = await __client.text({\n messages: __messages,\n tools: __tools,\n responseFormat: __responseFormat,\n stream: {{{isStreaming:boolean}}}\n });\n\n const nextEndTime = performance.now();\n\n await handleStreamingResponse(__completion);\n\n statelogClient.promptCompletion({\n messages: __messages,\n completion: __completion,\n model: __client.getModel(),\n timeTaken: nextEndTime - nextStartTime,\n tools: __tools,\n responseFormat: __responseFormat,\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 __updateTokenStats(responseMessage.usage, responseMessage.cost);\n await __callHook(\"onLLMCallEnd\", { result: responseMessage, usage: responseMessage.usage, cost: responseMessage.cost, timeTaken: nextEndTime - nextStartTime });\n }\n\n // Add final assistant response to history\n // not passing tool calls back this time\n __messages.push(smoltalk.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{{#isAsync}}\n__self.{{{variableName:string}}} = _{{{variableName:string}}}({{{funcCallParams:string}}});\n{{/isAsync}}\n\n{{^isAsync}}\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 { messages: {{{messagesVar:string}}} , data: __self.{{{variableName:string}}} };\n {{/nodeContext}}\n {{^nodeContext}}\n return __self.{{{variableName:string}}};\n {{/nodeContext}}\n}\n{{/isAsync}}";
1
+ export declare const template = "\nasync function _{{{variableName:string}}}({{{argsStr:string}}}) {\n const __prompt = {{{promptCode:string}}};\n const startTime = performance.now();\n let __messages = __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 = __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;\n\n if (__toolCalls.length === 0) {\n __messages.push(smoltalk.userMessage(__prompt));\n \n \n await __callHook(\"onLLMCallStart\", { prompt: __prompt, tools: __tools, model: __client.getModel() });\n let __completion = await __client.text({\n messages: __messages,\n tools: __tools,\n responseFormat: __responseFormat,\n stream: {{{isStreaming:boolean}}}\n });\n\n const endTime = performance.now();\n\n {{#isStreaming}}\n __completion = await handleStreamingResponse(__completion, statelogClient, __prompt, __toolCalls);\n {{/isStreaming}}\n\n statelogClient.promptCompletion({\n messages: __messages,\n completion: __completion,\n model: __client.getModel(),\n timeTaken: endTime - startTime,\n tools: __tools,\n responseFormat: __responseFormat\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(smoltalk.assistantMessage(responseMessage.output, { toolCalls: __toolCalls }));\n }\n\n __updateTokenStats(responseMessage.usage, responseMessage.cost);\n await __callHook(\"onLLMCallEnd\", { result: responseMessage, usage: responseMessage.usage, cost: responseMessage.cost, timeTaken: endTime - startTime });\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 = 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 await __callHook(\"onLLMCallStart\", { prompt: __prompt, tools: __tools, model: __client.getModel() });\n let __completion = await __client.text({\n messages: __messages,\n tools: __tools,\n responseFormat: __responseFormat,\n stream: {{{isStreaming:boolean}}}\n });\n\n const nextEndTime = performance.now();\n\n {{#isStreaming}}\n __completion = await handleStreamingResponse(__completion, statelogClient, __prompt, __toolCalls);\n {{/isStreaming}}\n\n statelogClient.promptCompletion({\n messages: __messages,\n completion: __completion,\n model: __client.getModel(),\n timeTaken: nextEndTime - nextStartTime,\n tools: __tools,\n responseFormat: __responseFormat,\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 __updateTokenStats(responseMessage.usage, responseMessage.cost);\n await __callHook(\"onLLMCallEnd\", { result: responseMessage, usage: responseMessage.usage, cost: responseMessage.cost, timeTaken: nextEndTime - nextStartTime });\n }\n\n // Add final assistant response to history\n // not passing tool calls back this time\n __messages.push(smoltalk.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{{#isAsync}}\n__self.{{{variableName:string}}} = _{{{variableName:string}}}({{{funcCallParams:string}}});\n{{/isAsync}}\n\n{{^isAsync}}\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 { messages: {{{messagesVar:string}}} , data: __self.{{{variableName:string}}} };\n {{/nodeContext}}\n {{^nodeContext}}\n return __self.{{{variableName:string}}};\n {{/nodeContext}}\n}\n{{/isAsync}}";
2
2
  export type TemplateType = {
3
3
  variableName: string;
4
4
  argsStr: string;
@@ -41,7 +41,9 @@ async function _{{{variableName:string}}}({{{argsStr:string}}}) {
41
41
 
42
42
  const endTime = performance.now();
43
43
 
44
- await handleStreamingResponse(__completion);
44
+ {{#isStreaming}}
45
+ __completion = await handleStreamingResponse(__completion, statelogClient, __prompt, __toolCalls);
46
+ {{/isStreaming}}
45
47
 
46
48
  statelogClient.promptCompletion({
47
49
  messages: __messages,
@@ -109,7 +111,9 @@ async function _{{{variableName:string}}}({{{argsStr:string}}}) {
109
111
 
110
112
  const nextEndTime = performance.now();
111
113
 
112
- await handleStreamingResponse(__completion);
114
+ {{#isStreaming}}
115
+ __completion = await handleStreamingResponse(__completion, statelogClient, __prompt, __toolCalls);
116
+ {{/isStreaming}}
113
117
 
114
118
  statelogClient.promptCompletion({
115
119
  messages: __messages,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "agency-lang",
3
- "version": "0.0.66",
3
+ "version": "0.0.67",
4
4
  "description": "The Agency language",
5
5
  "main": "lib/index.js",
6
6
  "scripts": {
@@ -70,4 +70,4 @@
70
70
  "typescript": "^5.9.3",
71
71
  "vitest": "^4.0.16"
72
72
  }
73
- }
73
+ }