@illuma-ai/agents 1.1.20 → 1.1.22

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.
Files changed (246) hide show
  1. package/dist/cjs/graphs/Graph.cjs +12 -1
  2. package/dist/cjs/graphs/Graph.cjs.map +1 -1
  3. package/dist/cjs/graphs/MultiAgentGraph.cjs +85 -1
  4. package/dist/cjs/graphs/MultiAgentGraph.cjs.map +1 -1
  5. package/dist/cjs/llm/bedrock/index.cjs +14 -0
  6. package/dist/cjs/llm/bedrock/index.cjs.map +1 -1
  7. package/dist/cjs/run.cjs +20 -9
  8. package/dist/cjs/run.cjs.map +1 -1
  9. package/dist/esm/graphs/Graph.mjs +12 -1
  10. package/dist/esm/graphs/Graph.mjs.map +1 -1
  11. package/dist/esm/graphs/MultiAgentGraph.mjs +85 -1
  12. package/dist/esm/graphs/MultiAgentGraph.mjs.map +1 -1
  13. package/dist/esm/llm/bedrock/index.mjs +14 -0
  14. package/dist/esm/llm/bedrock/index.mjs.map +1 -1
  15. package/dist/esm/run.mjs +20 -9
  16. package/dist/esm/run.mjs.map +1 -1
  17. package/dist/types/graphs/MultiAgentGraph.d.ts +17 -0
  18. package/package.json +1 -1
  19. package/src/graphs/Graph.ts +12 -1
  20. package/src/graphs/MultiAgentGraph.ts +105 -1
  21. package/src/graphs/__tests__/multi-agent-delegate.test.ts +191 -0
  22. package/src/llm/bedrock/index.ts +17 -0
  23. package/src/run.ts +20 -11
  24. package/src/scripts/test-bedrock-handoff-autonomous.ts +231 -0
  25. package/src/agents/AgentContext.js +0 -782
  26. package/src/agents/AgentContext.test.js +0 -421
  27. package/src/agents/__tests__/AgentContext.test.js +0 -678
  28. package/src/agents/__tests__/resolveStructuredOutputMode.test.js +0 -117
  29. package/src/common/enum.js +0 -192
  30. package/src/common/index.js +0 -3
  31. package/src/events.js +0 -166
  32. package/src/graphs/Graph.js +0 -1857
  33. package/src/graphs/MultiAgentGraph.js +0 -1092
  34. package/src/graphs/__tests__/structured-output.integration.test.js +0 -624
  35. package/src/graphs/__tests__/structured-output.test.js +0 -144
  36. package/src/graphs/contextManagement.e2e.test.js +0 -718
  37. package/src/graphs/contextManagement.test.js +0 -485
  38. package/src/graphs/handoffValidation.test.js +0 -276
  39. package/src/graphs/index.js +0 -3
  40. package/src/index.js +0 -28
  41. package/src/instrumentation.js +0 -21
  42. package/src/llm/anthropic/index.js +0 -319
  43. package/src/llm/anthropic/types.js +0 -46
  44. package/src/llm/anthropic/utils/message_inputs.js +0 -627
  45. package/src/llm/anthropic/utils/message_outputs.js +0 -290
  46. package/src/llm/anthropic/utils/output_parsers.js +0 -89
  47. package/src/llm/anthropic/utils/tools.js +0 -25
  48. package/src/llm/bedrock/__tests__/bedrock-caching.test.js +0 -392
  49. package/src/llm/bedrock/index.js +0 -303
  50. package/src/llm/bedrock/types.js +0 -2
  51. package/src/llm/bedrock/utils/index.js +0 -6
  52. package/src/llm/bedrock/utils/message_inputs.js +0 -463
  53. package/src/llm/bedrock/utils/message_outputs.js +0 -269
  54. package/src/llm/fake.js +0 -92
  55. package/src/llm/google/index.js +0 -215
  56. package/src/llm/google/types.js +0 -12
  57. package/src/llm/google/utils/common.js +0 -670
  58. package/src/llm/google/utils/tools.js +0 -111
  59. package/src/llm/google/utils/zod_to_genai_parameters.js +0 -47
  60. package/src/llm/openai/index.js +0 -1033
  61. package/src/llm/openai/types.js +0 -2
  62. package/src/llm/openai/utils/index.js +0 -756
  63. package/src/llm/openai/utils/isReasoningModel.test.js +0 -79
  64. package/src/llm/openrouter/index.js +0 -261
  65. package/src/llm/openrouter/reasoning.test.js +0 -181
  66. package/src/llm/providers.js +0 -36
  67. package/src/llm/text.js +0 -65
  68. package/src/llm/vertexai/index.js +0 -402
  69. package/src/messages/__tests__/tools.test.js +0 -392
  70. package/src/messages/cache.js +0 -404
  71. package/src/messages/cache.test.js +0 -1167
  72. package/src/messages/content.js +0 -48
  73. package/src/messages/content.test.js +0 -314
  74. package/src/messages/core.js +0 -359
  75. package/src/messages/ensureThinkingBlock.test.js +0 -997
  76. package/src/messages/format.js +0 -973
  77. package/src/messages/formatAgentMessages.test.js +0 -2278
  78. package/src/messages/formatAgentMessages.tools.test.js +0 -362
  79. package/src/messages/formatMessage.test.js +0 -608
  80. package/src/messages/ids.js +0 -18
  81. package/src/messages/index.js +0 -9
  82. package/src/messages/labelContentByAgent.test.js +0 -725
  83. package/src/messages/prune.js +0 -438
  84. package/src/messages/reducer.js +0 -60
  85. package/src/messages/shiftIndexTokenCountMap.test.js +0 -63
  86. package/src/messages/summarize.js +0 -146
  87. package/src/messages/summarize.test.js +0 -332
  88. package/src/messages/tools.js +0 -90
  89. package/src/mockStream.js +0 -81
  90. package/src/prompts/collab.js +0 -7
  91. package/src/prompts/index.js +0 -3
  92. package/src/prompts/taskmanager.js +0 -58
  93. package/src/run.js +0 -427
  94. package/src/schemas/index.js +0 -3
  95. package/src/schemas/schema-preparation.test.js +0 -370
  96. package/src/schemas/validate.js +0 -314
  97. package/src/schemas/validate.test.js +0 -264
  98. package/src/scripts/abort.js +0 -127
  99. package/src/scripts/ant_web_search.js +0 -130
  100. package/src/scripts/ant_web_search_edge_case.js +0 -133
  101. package/src/scripts/ant_web_search_error_edge_case.js +0 -119
  102. package/src/scripts/args.js +0 -41
  103. package/src/scripts/bedrock-cache-debug.js +0 -186
  104. package/src/scripts/bedrock-content-aggregation-test.js +0 -195
  105. package/src/scripts/bedrock-merge-test.js +0 -80
  106. package/src/scripts/bedrock-parallel-tools-test.js +0 -150
  107. package/src/scripts/caching.js +0 -106
  108. package/src/scripts/cli.js +0 -152
  109. package/src/scripts/cli2.js +0 -119
  110. package/src/scripts/cli3.js +0 -163
  111. package/src/scripts/cli4.js +0 -165
  112. package/src/scripts/cli5.js +0 -165
  113. package/src/scripts/code_exec.js +0 -171
  114. package/src/scripts/code_exec_files.js +0 -180
  115. package/src/scripts/code_exec_multi_session.js +0 -185
  116. package/src/scripts/code_exec_ptc.js +0 -265
  117. package/src/scripts/code_exec_session.js +0 -217
  118. package/src/scripts/code_exec_simple.js +0 -120
  119. package/src/scripts/content.js +0 -111
  120. package/src/scripts/empty_input.js +0 -125
  121. package/src/scripts/handoff-test.js +0 -96
  122. package/src/scripts/image.js +0 -138
  123. package/src/scripts/memory.js +0 -83
  124. package/src/scripts/multi-agent-chain.js +0 -271
  125. package/src/scripts/multi-agent-conditional.js +0 -185
  126. package/src/scripts/multi-agent-document-review-chain.js +0 -171
  127. package/src/scripts/multi-agent-hybrid-flow.js +0 -264
  128. package/src/scripts/multi-agent-parallel-start.js +0 -214
  129. package/src/scripts/multi-agent-parallel.js +0 -346
  130. package/src/scripts/multi-agent-sequence.js +0 -184
  131. package/src/scripts/multi-agent-supervisor.js +0 -324
  132. package/src/scripts/multi-agent-test.js +0 -147
  133. package/src/scripts/parallel-asymmetric-tools-test.js +0 -202
  134. package/src/scripts/parallel-full-metadata-test.js +0 -176
  135. package/src/scripts/parallel-tools-test.js +0 -256
  136. package/src/scripts/programmatic_exec.js +0 -277
  137. package/src/scripts/programmatic_exec_agent.js +0 -168
  138. package/src/scripts/search.js +0 -118
  139. package/src/scripts/sequential-full-metadata-test.js +0 -143
  140. package/src/scripts/simple.js +0 -174
  141. package/src/scripts/single-agent-metadata-test.js +0 -152
  142. package/src/scripts/stream.js +0 -113
  143. package/src/scripts/test-custom-prompt-key.js +0 -132
  144. package/src/scripts/test-handoff-input.js +0 -143
  145. package/src/scripts/test-handoff-preamble.js +0 -227
  146. package/src/scripts/test-handoff-steering.js +0 -353
  147. package/src/scripts/test-multi-agent-list-handoff.js +0 -318
  148. package/src/scripts/test-parallel-agent-labeling.js +0 -253
  149. package/src/scripts/test-parallel-handoffs.js +0 -229
  150. package/src/scripts/test-thinking-handoff-bedrock.js +0 -132
  151. package/src/scripts/test-thinking-handoff.js +0 -132
  152. package/src/scripts/test-thinking-to-thinking-handoff-bedrock.js +0 -140
  153. package/src/scripts/test-tool-before-handoff-role-order.js +0 -223
  154. package/src/scripts/test-tools-before-handoff.js +0 -187
  155. package/src/scripts/test_code_api.js +0 -263
  156. package/src/scripts/thinking-bedrock.js +0 -128
  157. package/src/scripts/thinking-vertexai.js +0 -130
  158. package/src/scripts/thinking.js +0 -134
  159. package/src/scripts/tool_search.js +0 -114
  160. package/src/scripts/tools.js +0 -125
  161. package/src/specs/agent-handoffs-bedrock.integration.test.js +0 -280
  162. package/src/specs/agent-handoffs.test.js +0 -924
  163. package/src/specs/anthropic.simple.test.js +0 -287
  164. package/src/specs/azure.simple.test.js +0 -381
  165. package/src/specs/cache.simple.test.js +0 -282
  166. package/src/specs/custom-event-await.test.js +0 -148
  167. package/src/specs/deepseek.simple.test.js +0 -189
  168. package/src/specs/emergency-prune.test.js +0 -308
  169. package/src/specs/moonshot.simple.test.js +0 -237
  170. package/src/specs/observability.integration.test.js +0 -1337
  171. package/src/specs/openai.simple.test.js +0 -233
  172. package/src/specs/openrouter.simple.test.js +0 -202
  173. package/src/specs/prune.test.js +0 -733
  174. package/src/specs/reasoning.test.js +0 -144
  175. package/src/specs/spec.utils.js +0 -4
  176. package/src/specs/thinking-handoff.test.js +0 -486
  177. package/src/specs/thinking-prune.test.js +0 -600
  178. package/src/specs/token-distribution-edge-case.test.js +0 -246
  179. package/src/specs/token-memoization.test.js +0 -32
  180. package/src/specs/tokens.test.js +0 -49
  181. package/src/specs/tool-error.test.js +0 -139
  182. package/src/splitStream.js +0 -204
  183. package/src/splitStream.test.js +0 -504
  184. package/src/stream.js +0 -650
  185. package/src/stream.test.js +0 -225
  186. package/src/test/mockTools.js +0 -340
  187. package/src/tools/BrowserTools.js +0 -245
  188. package/src/tools/Calculator.js +0 -38
  189. package/src/tools/Calculator.test.js +0 -225
  190. package/src/tools/CodeExecutor.js +0 -233
  191. package/src/tools/ProgrammaticToolCalling.js +0 -602
  192. package/src/tools/StreamingToolCallBuffer.js +0 -179
  193. package/src/tools/ToolNode.js +0 -930
  194. package/src/tools/ToolSearch.js +0 -904
  195. package/src/tools/__tests__/BrowserTools.test.js +0 -306
  196. package/src/tools/__tests__/ProgrammaticToolCalling.integration.test.js +0 -276
  197. package/src/tools/__tests__/ProgrammaticToolCalling.test.js +0 -807
  198. package/src/tools/__tests__/StreamingToolCallBuffer.test.js +0 -175
  199. package/src/tools/__tests__/ToolApproval.test.js +0 -675
  200. package/src/tools/__tests__/ToolNode.recovery.test.js +0 -200
  201. package/src/tools/__tests__/ToolNode.session.test.js +0 -319
  202. package/src/tools/__tests__/ToolSearch.integration.test.js +0 -125
  203. package/src/tools/__tests__/ToolSearch.test.js +0 -812
  204. package/src/tools/__tests__/handlers.test.js +0 -799
  205. package/src/tools/__tests__/truncation-recovery.integration.test.js +0 -362
  206. package/src/tools/handlers.js +0 -306
  207. package/src/tools/schema.js +0 -25
  208. package/src/tools/search/anthropic.js +0 -34
  209. package/src/tools/search/content.js +0 -116
  210. package/src/tools/search/content.test.js +0 -133
  211. package/src/tools/search/firecrawl.js +0 -173
  212. package/src/tools/search/format.js +0 -198
  213. package/src/tools/search/highlights.js +0 -241
  214. package/src/tools/search/index.js +0 -3
  215. package/src/tools/search/jina-reranker.test.js +0 -106
  216. package/src/tools/search/rerankers.js +0 -165
  217. package/src/tools/search/schema.js +0 -102
  218. package/src/tools/search/search.js +0 -561
  219. package/src/tools/search/serper-scraper.js +0 -126
  220. package/src/tools/search/test.js +0 -129
  221. package/src/tools/search/tool.js +0 -453
  222. package/src/tools/search/types.js +0 -2
  223. package/src/tools/search/utils.js +0 -59
  224. package/src/types/graph.js +0 -24
  225. package/src/types/graph.test.js +0 -192
  226. package/src/types/index.js +0 -7
  227. package/src/types/llm.js +0 -2
  228. package/src/types/messages.js +0 -2
  229. package/src/types/run.js +0 -2
  230. package/src/types/stream.js +0 -2
  231. package/src/types/tools.js +0 -2
  232. package/src/utils/contextAnalytics.js +0 -79
  233. package/src/utils/contextAnalytics.test.js +0 -166
  234. package/src/utils/events.js +0 -26
  235. package/src/utils/graph.js +0 -11
  236. package/src/utils/handlers.js +0 -65
  237. package/src/utils/index.js +0 -10
  238. package/src/utils/llm.js +0 -21
  239. package/src/utils/llmConfig.js +0 -205
  240. package/src/utils/logging.js +0 -37
  241. package/src/utils/misc.js +0 -51
  242. package/src/utils/run.js +0 -69
  243. package/src/utils/schema.js +0 -21
  244. package/src/utils/title.js +0 -119
  245. package/src/utils/tokens.js +0 -92
  246. package/src/utils/toonFormat.js +0 -379
@@ -1,602 +0,0 @@
1
- // src/tools/ProgrammaticToolCalling.ts
2
- import { config } from 'dotenv';
3
- import fetch from 'node-fetch';
4
- import { HttpsProxyAgent } from 'https-proxy-agent';
5
- import { getEnvironmentVariable } from '@langchain/core/utils/env';
6
- import { tool } from '@langchain/core/tools';
7
- import { imageExtRegex, getCodeBaseURL } from './CodeExecutor';
8
- import { EnvVar, Constants } from '@/common';
9
- config();
10
- // ============================================================================
11
- // Constants
12
- // ============================================================================
13
- const imageMessage = 'Image is already displayed to the user';
14
- const otherMessage = 'File is already downloaded by the user';
15
- const accessMessage = 'Note: Files from previous executions are automatically available and can be modified.';
16
- const emptyOutputMessage = 'stdout: Empty. Ensure you\'re writing output explicitly.\n';
17
- /** Default max round-trips to prevent infinite loops */
18
- const DEFAULT_MAX_ROUND_TRIPS = 20;
19
- /** Default execution timeout in milliseconds */
20
- const DEFAULT_TIMEOUT = 60000;
21
- // ============================================================================
22
- // Description Components (Single Source of Truth)
23
- // ============================================================================
24
- const STATELESS_WARNING = `CRITICAL - STATELESS EXECUTION:
25
- Each call is a fresh Python interpreter. Variables, imports, and data do NOT persist between calls.
26
- You MUST complete your entire workflow in ONE code block: query → process → output.
27
- DO NOT split work across multiple calls expecting to reuse variables.`;
28
- const CORE_RULES = `Rules:
29
- - EVERYTHING in one call—no state persists between executions
30
- - Just write code with await—auto-wrapped in async context
31
- - DO NOT define async def main() or call asyncio.run()
32
- - Tools are pre-defined—DO NOT write function definitions
33
- - Only print() output returns to the model`;
34
- const ADDITIONAL_RULES = `- Generated files are automatically available in /mnt/data/ for subsequent executions
35
- - Tool names normalized: hyphens→underscores, keywords get \`_tool\` suffix`;
36
- const EXAMPLES = `Example (Complete workflow in one call):
37
- # Query data
38
- data = await query_database(sql="SELECT * FROM users")
39
- # Process it
40
- df = pd.DataFrame(data)
41
- summary = df.groupby('region').sum()
42
- # Output results
43
- await write_to_sheet(spreadsheet_id=sid, data=summary.to_dict())
44
- print(f"Wrote {len(summary)} rows")
45
-
46
- Example (Parallel calls):
47
- sf, ny = await asyncio.gather(get_weather(city="SF"), get_weather(city="NY"))
48
- print(f"SF: {sf}, NY: {ny}")`;
49
- // ============================================================================
50
- // Schema
51
- // ============================================================================
52
- const CODE_PARAM_DESCRIPTION = `Python code that calls tools programmatically. Tools are available as async functions.
53
-
54
- ${STATELESS_WARNING}
55
-
56
- Your code is auto-wrapped in async context. Just write logic with await—no boilerplate needed.
57
-
58
- ${EXAMPLES}
59
-
60
- ${CORE_RULES}`;
61
- export const ProgrammaticToolCallingSchema = {
62
- type: 'object',
63
- properties: {
64
- code: {
65
- type: 'string',
66
- minLength: 1,
67
- description: CODE_PARAM_DESCRIPTION,
68
- },
69
- timeout: {
70
- type: 'integer',
71
- minimum: 1000,
72
- maximum: 300000,
73
- default: DEFAULT_TIMEOUT,
74
- description: 'Maximum execution time in milliseconds. Default: 60 seconds. Max: 5 minutes.',
75
- },
76
- },
77
- required: ['code'],
78
- };
79
- export const ProgrammaticToolCallingName = Constants.PROGRAMMATIC_TOOL_CALLING;
80
- export const ProgrammaticToolCallingDescription = `
81
- Run tools via Python code. Auto-wrapped in async context—just use \`await\` directly.
82
-
83
- ${STATELESS_WARNING}
84
-
85
- ${CORE_RULES}
86
- ${ADDITIONAL_RULES}
87
-
88
- When to use: loops, conditionals, parallel (\`asyncio.gather\`), multi-step pipelines.
89
-
90
- ${EXAMPLES}
91
- `.trim();
92
- export const ProgrammaticToolCallingDefinition = {
93
- name: ProgrammaticToolCallingName,
94
- description: ProgrammaticToolCallingDescription,
95
- schema: ProgrammaticToolCallingSchema,
96
- };
97
- // ============================================================================
98
- // Helper Functions
99
- // ============================================================================
100
- /** Python reserved keywords that get `_tool` suffix in Code API */
101
- const PYTHON_KEYWORDS = new Set([
102
- 'False',
103
- 'None',
104
- 'True',
105
- 'and',
106
- 'as',
107
- 'assert',
108
- 'async',
109
- 'await',
110
- 'break',
111
- 'class',
112
- 'continue',
113
- 'def',
114
- 'del',
115
- 'elif',
116
- 'else',
117
- 'except',
118
- 'finally',
119
- 'for',
120
- 'from',
121
- 'global',
122
- 'if',
123
- 'import',
124
- 'in',
125
- 'is',
126
- 'lambda',
127
- 'nonlocal',
128
- 'not',
129
- 'or',
130
- 'pass',
131
- 'raise',
132
- 'return',
133
- 'try',
134
- 'while',
135
- 'with',
136
- 'yield',
137
- ]);
138
- /**
139
- * Normalizes a tool name to Python identifier format.
140
- * Must match the Code API's `normalizePythonFunctionName` exactly:
141
- * 1. Replace hyphens and spaces with underscores
142
- * 2. Remove any other invalid characters
143
- * 3. Prefix with underscore if starts with number
144
- * 4. Append `_tool` if it's a Python keyword
145
- * @param name - The tool name to normalize
146
- * @returns Normalized Python-safe identifier
147
- */
148
- export function normalizeToPythonIdentifier(name) {
149
- let normalized = name.replace(/[-\s]/g, '_');
150
- normalized = normalized.replace(/[^a-zA-Z0-9_]/g, '');
151
- if (/^[0-9]/.test(normalized)) {
152
- normalized = '_' + normalized;
153
- }
154
- if (PYTHON_KEYWORDS.has(normalized)) {
155
- normalized = normalized + '_tool';
156
- }
157
- return normalized;
158
- }
159
- /**
160
- * Extracts tool names that are actually called in the Python code.
161
- * Handles hyphen/underscore conversion since Python identifiers use underscores.
162
- * @param code - The Python code to analyze
163
- * @param toolNameMap - Map from normalized Python name to original tool name
164
- * @returns Set of original tool names found in the code
165
- */
166
- export function extractUsedToolNames(code, toolNameMap) {
167
- const usedTools = new Set();
168
- for (const [pythonName, originalName] of toolNameMap) {
169
- const escapedName = pythonName.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
170
- const pattern = new RegExp(`\\b${escapedName}\\s*\\(`, 'g');
171
- if (pattern.test(code)) {
172
- usedTools.add(originalName);
173
- }
174
- }
175
- return usedTools;
176
- }
177
- /**
178
- * Filters tool definitions to only include tools actually used in the code.
179
- * Handles the hyphen-to-underscore conversion for Python compatibility.
180
- * @param toolDefs - All available tool definitions
181
- * @param code - The Python code to analyze
182
- * @param debug - Enable debug logging
183
- * @returns Filtered array of tool definitions
184
- */
185
- export function filterToolsByUsage(toolDefs, code, debug = false) {
186
- const toolNameMap = new Map();
187
- for (const tool of toolDefs) {
188
- const pythonName = normalizeToPythonIdentifier(tool.name);
189
- toolNameMap.set(pythonName, tool.name);
190
- }
191
- const usedToolNames = extractUsedToolNames(code, toolNameMap);
192
- if (debug) {
193
- // eslint-disable-next-line no-console
194
- console.log(`[PTC Debug] Tool filtering: found ${usedToolNames.size}/${toolDefs.length} tools in code`);
195
- if (usedToolNames.size > 0) {
196
- // eslint-disable-next-line no-console
197
- console.log(`[PTC Debug] Matched tools: ${Array.from(usedToolNames).join(', ')}`);
198
- }
199
- }
200
- if (usedToolNames.size === 0) {
201
- if (debug) {
202
- // eslint-disable-next-line no-console
203
- console.log('[PTC Debug] No tools detected in code - sending all tools as fallback');
204
- }
205
- return toolDefs;
206
- }
207
- return toolDefs.filter((tool) => usedToolNames.has(tool.name));
208
- }
209
- /**
210
- * Fetches files from a previous session to make them available for the current execution.
211
- * Files are returned as CodeEnvFile references to be included in the request.
212
- * @param baseUrl - The base URL for the Code API
213
- * @param apiKey - The API key for authentication
214
- * @param sessionId - The session ID to fetch files from
215
- * @param proxy - Optional HTTP proxy URL
216
- * @returns Array of CodeEnvFile references, or empty array if fetch fails
217
- */
218
- export async function fetchSessionFiles(baseUrl, apiKey, sessionId, proxy) {
219
- try {
220
- const filesEndpoint = `${baseUrl}/files/${sessionId}?detail=full`;
221
- const fetchOptions = {
222
- method: 'GET',
223
- headers: {
224
- 'User-Agent': 'Illuma/1.0',
225
- 'X-API-Key': apiKey,
226
- },
227
- };
228
- if (proxy != null && proxy !== '') {
229
- fetchOptions.agent = new HttpsProxyAgent(proxy);
230
- }
231
- const response = await fetch(filesEndpoint, fetchOptions);
232
- if (!response.ok) {
233
- throw new Error(`Failed to fetch files for session: ${response.status}`);
234
- }
235
- const files = await response.json();
236
- if (!Array.isArray(files) || files.length === 0) {
237
- return [];
238
- }
239
- return files.map((file) => {
240
- // Extract the ID from the file name (part after session ID prefix and before extension)
241
- const nameParts = file.name.split('/');
242
- const id = nameParts.length > 1 ? nameParts[1].split('.')[0] : '';
243
- return {
244
- session_id: sessionId,
245
- id,
246
- name: file.metadata['original-filename'],
247
- };
248
- });
249
- }
250
- catch (error) {
251
- // eslint-disable-next-line no-console
252
- console.warn(`Failed to fetch files for session: ${sessionId}, ${error.message}`);
253
- return [];
254
- }
255
- }
256
- /**
257
- * Makes an HTTP request to the Code API.
258
- * @param endpoint - The API endpoint URL
259
- * @param apiKey - The API key for authentication
260
- * @param body - The request body
261
- * @param proxy - Optional HTTP proxy URL
262
- * @returns The parsed API response
263
- */
264
- export async function makeRequest(endpoint, apiKey, body, proxy) {
265
- const fetchOptions = {
266
- method: 'POST',
267
- headers: {
268
- 'Content-Type': 'application/json',
269
- 'User-Agent': 'Illuma/1.0',
270
- 'X-API-Key': apiKey,
271
- },
272
- body: JSON.stringify(body),
273
- };
274
- if (proxy != null && proxy !== '') {
275
- fetchOptions.agent = new HttpsProxyAgent(proxy);
276
- }
277
- const response = await fetch(endpoint, fetchOptions);
278
- if (!response.ok) {
279
- const errorText = await response.text();
280
- throw new Error(`HTTP error! status: ${response.status}, body: ${errorText}`);
281
- }
282
- return (await response.json());
283
- }
284
- /**
285
- * Unwraps tool responses that may be formatted as tuples or content blocks.
286
- * MCP tools return [content, artifacts], we need to extract the raw data.
287
- * @param result - The raw result from tool.invoke()
288
- * @param isMCPTool - Whether this is an MCP tool (has mcp property)
289
- * @returns Unwrapped raw data (string, object, or parsed JSON)
290
- */
291
- export function unwrapToolResponse(result, isMCPTool) {
292
- // Only unwrap if this is an MCP tool and result is a tuple
293
- if (!isMCPTool) {
294
- return result;
295
- }
296
- /**
297
- * Checks if a value is a content block object (has type and text).
298
- */
299
- const isContentBlock = (value) => {
300
- if (typeof value !== 'object' || value === null || Array.isArray(value)) {
301
- return false;
302
- }
303
- const obj = value;
304
- return typeof obj.type === 'string';
305
- };
306
- /**
307
- * Checks if an array is an array of content blocks.
308
- */
309
- const isContentBlockArray = (arr) => {
310
- return arr.length > 0 && arr.every(isContentBlock);
311
- };
312
- /**
313
- * Extracts text from a single content block object.
314
- * Returns the text if it's a text block, otherwise returns null.
315
- */
316
- const extractTextFromBlock = (block) => {
317
- if (typeof block !== 'object' || block === null)
318
- return null;
319
- const b = block;
320
- if (b.type === 'text' && typeof b.text === 'string') {
321
- return b.text;
322
- }
323
- return null;
324
- };
325
- /**
326
- * Extracts text from content blocks (array or single object).
327
- * Returns combined text or null if no text blocks found.
328
- */
329
- const extractTextFromContent = (content) => {
330
- // Single content block object: { type: 'text', text: '...' }
331
- if (typeof content === 'object' &&
332
- content !== null &&
333
- !Array.isArray(content)) {
334
- const text = extractTextFromBlock(content);
335
- if (text !== null)
336
- return text;
337
- }
338
- // Array of content blocks: [{ type: 'text', text: '...' }, ...]
339
- if (Array.isArray(content) && content.length > 0) {
340
- const texts = content
341
- .map(extractTextFromBlock)
342
- .filter((t) => t !== null);
343
- if (texts.length > 0) {
344
- return texts.join('\n');
345
- }
346
- }
347
- return null;
348
- };
349
- /**
350
- * Tries to parse a string as JSON if it looks like JSON.
351
- */
352
- const maybeParseJSON = (str) => {
353
- const trimmed = str.trim();
354
- if (trimmed.startsWith('{') || trimmed.startsWith('[')) {
355
- try {
356
- return JSON.parse(trimmed);
357
- }
358
- catch {
359
- return str;
360
- }
361
- }
362
- return str;
363
- };
364
- // Handle array of content blocks at top level FIRST
365
- // (before checking for tuple, since both are arrays)
366
- if (Array.isArray(result) && isContentBlockArray(result)) {
367
- const extractedText = extractTextFromContent(result);
368
- if (extractedText !== null) {
369
- return maybeParseJSON(extractedText);
370
- }
371
- }
372
- // Check if result is a tuple/array with [content, artifacts]
373
- if (Array.isArray(result) && result.length >= 1) {
374
- const [content] = result;
375
- // If first element is a string, return it (possibly parsed as JSON)
376
- if (typeof content === 'string') {
377
- return maybeParseJSON(content);
378
- }
379
- // Try to extract text from content blocks
380
- const extractedText = extractTextFromContent(content);
381
- if (extractedText !== null) {
382
- return maybeParseJSON(extractedText);
383
- }
384
- // If first element is an object (but not a text block), return it
385
- if (typeof content === 'object' && content !== null) {
386
- return content;
387
- }
388
- }
389
- // Handle single content block object at top level (not in tuple)
390
- const extractedText = extractTextFromContent(result);
391
- if (extractedText !== null) {
392
- return maybeParseJSON(extractedText);
393
- }
394
- // Not a formatted response, return as-is
395
- return result;
396
- }
397
- /**
398
- * Executes tools in parallel when requested by the API.
399
- * Uses Promise.all for parallel execution, catching individual errors.
400
- * Unwraps formatted responses (e.g., MCP tool tuples) to raw data.
401
- * @param toolCalls - Array of tool calls from the API
402
- * @param toolMap - Map of tool names to executable tools
403
- * @returns Array of tool results
404
- */
405
- export async function executeTools(toolCalls, toolMap) {
406
- const executions = toolCalls.map(async (call) => {
407
- const tool = toolMap.get(call.name);
408
- if (!tool) {
409
- return {
410
- call_id: call.id,
411
- result: null,
412
- is_error: true,
413
- error_message: `Tool '${call.name}' not found. Available tools: ${Array.from(toolMap.keys()).join(', ')}`,
414
- };
415
- }
416
- try {
417
- const result = await tool.invoke(call.input, {
418
- metadata: { [Constants.PROGRAMMATIC_TOOL_CALLING]: true },
419
- });
420
- const isMCPTool = tool.mcp === true;
421
- const unwrappedResult = unwrapToolResponse(result, isMCPTool);
422
- return {
423
- call_id: call.id,
424
- result: unwrappedResult,
425
- is_error: false,
426
- };
427
- }
428
- catch (error) {
429
- return {
430
- call_id: call.id,
431
- result: null,
432
- is_error: true,
433
- error_message: error.message || 'Tool execution failed',
434
- };
435
- }
436
- });
437
- return await Promise.all(executions);
438
- }
439
- /**
440
- * Formats the completed response for the agent.
441
- * @param response - The completed API response
442
- * @returns Tuple of [formatted string, artifact]
443
- */
444
- export function formatCompletedResponse(response) {
445
- let formatted = '';
446
- if (response.stdout != null && response.stdout !== '') {
447
- formatted += `stdout:\n${response.stdout}\n`;
448
- }
449
- else {
450
- formatted += emptyOutputMessage;
451
- }
452
- if (response.stderr != null && response.stderr !== '') {
453
- formatted += `stderr:\n${response.stderr}\n`;
454
- }
455
- if (response.files && response.files.length > 0) {
456
- formatted += 'Generated files:\n';
457
- const fileCount = response.files.length;
458
- for (let i = 0; i < fileCount; i++) {
459
- const file = response.files[i];
460
- const isImage = imageExtRegex.test(file.name);
461
- formatted += `- /mnt/data/${file.name} | ${isImage ? imageMessage : otherMessage}`;
462
- if (i < fileCount - 1) {
463
- formatted += fileCount <= 3 ? ', ' : ',\n';
464
- }
465
- }
466
- formatted += `\n\n${accessMessage}`;
467
- }
468
- return [
469
- formatted.trim(),
470
- {
471
- session_id: response.session_id,
472
- files: response.files,
473
- },
474
- ];
475
- }
476
- // ============================================================================
477
- // Tool Factory
478
- // ============================================================================
479
- /**
480
- * Creates a Programmatic Tool Calling tool for complex multi-tool workflows.
481
- *
482
- * This tool enables AI agents to write Python code that orchestrates multiple
483
- * tool calls programmatically, reducing LLM round-trips and token usage.
484
- *
485
- * The tool map must be provided at runtime via config.configurable.toolMap.
486
- *
487
- * @param params - Configuration parameters (apiKey, baseUrl, maxRoundTrips, proxy)
488
- * @returns A LangChain DynamicStructuredTool for programmatic tool calling
489
- *
490
- * @example
491
- * const ptcTool = createProgrammaticToolCallingTool({
492
- * apiKey: process.env.CODE_API_KEY,
493
- * maxRoundTrips: 20
494
- * });
495
- *
496
- * const [output, artifact] = await ptcTool.invoke(
497
- * { code, tools },
498
- * { configurable: { toolMap } }
499
- * );
500
- */
501
- export function createProgrammaticToolCallingTool(initParams = {}) {
502
- const apiKey = initParams[EnvVar.CODE_API_KEY] ??
503
- initParams.apiKey ??
504
- getEnvironmentVariable(EnvVar.CODE_API_KEY) ??
505
- '';
506
- if (!apiKey) {
507
- throw new Error('No API key provided for programmatic tool calling. ' +
508
- 'Set CODE_API_KEY environment variable or pass apiKey in initParams.');
509
- }
510
- const baseUrl = initParams.baseUrl ?? getCodeBaseURL();
511
- const maxRoundTrips = initParams.maxRoundTrips ?? DEFAULT_MAX_ROUND_TRIPS;
512
- const proxy = initParams.proxy ?? process.env.PROXY;
513
- const debug = initParams.debug ?? process.env.PTC_DEBUG === 'true';
514
- const EXEC_ENDPOINT = `${baseUrl}/exec/programmatic`;
515
- return tool(async (rawParams, config) => {
516
- const params = rawParams;
517
- const { code, timeout = DEFAULT_TIMEOUT } = params;
518
- // Extra params injected by ToolNode (follows web_search pattern)
519
- const { toolMap, toolDefs, session_id, _injected_files } = (config.toolCall ?? {});
520
- if (toolMap == null || toolMap.size === 0) {
521
- throw new Error('No toolMap provided. ' +
522
- 'ToolNode should inject this from AgentContext when invoked through the graph.');
523
- }
524
- if (toolDefs == null || toolDefs.length === 0) {
525
- throw new Error('No tool definitions provided. ' +
526
- 'Either pass tools in the input or ensure ToolNode injects toolDefs.');
527
- }
528
- let roundTrip = 0;
529
- try {
530
- // ====================================================================
531
- // Phase 1: Filter tools and make initial request
532
- // ====================================================================
533
- const effectiveTools = filterToolsByUsage(toolDefs, code, debug);
534
- if (debug) {
535
- // eslint-disable-next-line no-console
536
- console.log(`[PTC Debug] Sending ${effectiveTools.length} tools to API ` +
537
- `(filtered from ${toolDefs.length})`);
538
- }
539
- /**
540
- * File injection priority:
541
- * 1. Use _injected_files from ToolNode (avoids /files endpoint race condition)
542
- * 2. Fall back to fetching from /files endpoint if session_id provided but no injected files
543
- */
544
- let files;
545
- if (_injected_files && _injected_files.length > 0) {
546
- files = _injected_files;
547
- }
548
- else if (session_id != null && session_id.length > 0) {
549
- files = await fetchSessionFiles(baseUrl, apiKey, session_id, proxy);
550
- }
551
- let response = await makeRequest(EXEC_ENDPOINT, apiKey, {
552
- code,
553
- tools: effectiveTools,
554
- session_id,
555
- timeout,
556
- ...(files && files.length > 0 ? { files } : {}),
557
- }, proxy);
558
- // ====================================================================
559
- // Phase 2: Handle response loop
560
- // ====================================================================
561
- while (response.status === 'tool_call_required') {
562
- roundTrip++;
563
- if (roundTrip > maxRoundTrips) {
564
- throw new Error(`Exceeded maximum round trips (${maxRoundTrips}). ` +
565
- 'This may indicate an infinite loop, excessive tool calls, ' +
566
- 'or a logic error in your code.');
567
- }
568
- if (debug) {
569
- // eslint-disable-next-line no-console
570
- console.log(`[PTC Debug] Round trip ${roundTrip}: ${response.tool_calls?.length ?? 0} tool(s) to execute`);
571
- }
572
- const toolResults = await executeTools(response.tool_calls ?? [], toolMap);
573
- response = await makeRequest(EXEC_ENDPOINT, apiKey, {
574
- continuation_token: response.continuation_token,
575
- tool_results: toolResults,
576
- }, proxy);
577
- }
578
- // ====================================================================
579
- // Phase 3: Handle final state
580
- // ====================================================================
581
- if (response.status === 'completed') {
582
- return formatCompletedResponse(response);
583
- }
584
- if (response.status === 'error') {
585
- throw new Error(`Execution error: ${response.error}` +
586
- (response.stderr != null && response.stderr !== ''
587
- ? `\n\nStderr:\n${response.stderr}`
588
- : ''));
589
- }
590
- throw new Error(`Unexpected response status: ${response.status}`);
591
- }
592
- catch (error) {
593
- throw new Error(`Programmatic execution failed: ${error.message}`);
594
- }
595
- }, {
596
- name: Constants.PROGRAMMATIC_TOOL_CALLING,
597
- description: ProgrammaticToolCallingDescription,
598
- schema: ProgrammaticToolCallingSchema,
599
- responseFormat: Constants.CONTENT_AND_ARTIFACT,
600
- });
601
- }
602
- //# sourceMappingURL=ProgrammaticToolCalling.js.map