@illuma-ai/agents 1.1.21 → 1.1.23

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 (244) 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 +105 -1
  4. package/dist/cjs/graphs/MultiAgentGraph.cjs.map +1 -1
  5. package/dist/cjs/run.cjs +20 -9
  6. package/dist/cjs/run.cjs.map +1 -1
  7. package/dist/cjs/utils/llm.cjs.map +1 -1
  8. package/dist/esm/graphs/Graph.mjs +12 -1
  9. package/dist/esm/graphs/Graph.mjs.map +1 -1
  10. package/dist/esm/graphs/MultiAgentGraph.mjs +105 -1
  11. package/dist/esm/graphs/MultiAgentGraph.mjs.map +1 -1
  12. package/dist/esm/run.mjs +20 -9
  13. package/dist/esm/run.mjs.map +1 -1
  14. package/dist/esm/utils/llm.mjs.map +1 -1
  15. package/dist/types/graphs/MultiAgentGraph.d.ts +17 -0
  16. package/package.json +1 -1
  17. package/src/graphs/Graph.ts +13 -1
  18. package/src/graphs/MultiAgentGraph.ts +128 -1
  19. package/src/graphs/__tests__/multi-agent-delegate.test.ts +205 -0
  20. package/src/run.ts +20 -11
  21. package/src/scripts/test-bedrock-handoff-autonomous.ts +231 -0
  22. package/src/utils/llm.ts +1 -0
  23. package/src/agents/AgentContext.js +0 -782
  24. package/src/agents/AgentContext.test.js +0 -421
  25. package/src/agents/__tests__/AgentContext.test.js +0 -678
  26. package/src/agents/__tests__/resolveStructuredOutputMode.test.js +0 -117
  27. package/src/common/enum.js +0 -192
  28. package/src/common/index.js +0 -3
  29. package/src/events.js +0 -166
  30. package/src/graphs/Graph.js +0 -1857
  31. package/src/graphs/MultiAgentGraph.js +0 -1092
  32. package/src/graphs/__tests__/structured-output.integration.test.js +0 -624
  33. package/src/graphs/__tests__/structured-output.test.js +0 -144
  34. package/src/graphs/contextManagement.e2e.test.js +0 -718
  35. package/src/graphs/contextManagement.test.js +0 -485
  36. package/src/graphs/handoffValidation.test.js +0 -276
  37. package/src/graphs/index.js +0 -3
  38. package/src/index.js +0 -28
  39. package/src/instrumentation.js +0 -21
  40. package/src/llm/anthropic/index.js +0 -319
  41. package/src/llm/anthropic/types.js +0 -46
  42. package/src/llm/anthropic/utils/message_inputs.js +0 -627
  43. package/src/llm/anthropic/utils/message_outputs.js +0 -290
  44. package/src/llm/anthropic/utils/output_parsers.js +0 -89
  45. package/src/llm/anthropic/utils/tools.js +0 -25
  46. package/src/llm/bedrock/__tests__/bedrock-caching.test.js +0 -392
  47. package/src/llm/bedrock/index.js +0 -303
  48. package/src/llm/bedrock/types.js +0 -2
  49. package/src/llm/bedrock/utils/index.js +0 -6
  50. package/src/llm/bedrock/utils/message_inputs.js +0 -463
  51. package/src/llm/bedrock/utils/message_outputs.js +0 -269
  52. package/src/llm/fake.js +0 -92
  53. package/src/llm/google/index.js +0 -215
  54. package/src/llm/google/types.js +0 -12
  55. package/src/llm/google/utils/common.js +0 -670
  56. package/src/llm/google/utils/tools.js +0 -111
  57. package/src/llm/google/utils/zod_to_genai_parameters.js +0 -47
  58. package/src/llm/openai/index.js +0 -1033
  59. package/src/llm/openai/types.js +0 -2
  60. package/src/llm/openai/utils/index.js +0 -756
  61. package/src/llm/openai/utils/isReasoningModel.test.js +0 -79
  62. package/src/llm/openrouter/index.js +0 -261
  63. package/src/llm/openrouter/reasoning.test.js +0 -181
  64. package/src/llm/providers.js +0 -36
  65. package/src/llm/text.js +0 -65
  66. package/src/llm/vertexai/index.js +0 -402
  67. package/src/messages/__tests__/tools.test.js +0 -392
  68. package/src/messages/cache.js +0 -404
  69. package/src/messages/cache.test.js +0 -1167
  70. package/src/messages/content.js +0 -48
  71. package/src/messages/content.test.js +0 -314
  72. package/src/messages/core.js +0 -359
  73. package/src/messages/ensureThinkingBlock.test.js +0 -997
  74. package/src/messages/format.js +0 -973
  75. package/src/messages/formatAgentMessages.test.js +0 -2278
  76. package/src/messages/formatAgentMessages.tools.test.js +0 -362
  77. package/src/messages/formatMessage.test.js +0 -608
  78. package/src/messages/ids.js +0 -18
  79. package/src/messages/index.js +0 -9
  80. package/src/messages/labelContentByAgent.test.js +0 -725
  81. package/src/messages/prune.js +0 -438
  82. package/src/messages/reducer.js +0 -60
  83. package/src/messages/shiftIndexTokenCountMap.test.js +0 -63
  84. package/src/messages/summarize.js +0 -146
  85. package/src/messages/summarize.test.js +0 -332
  86. package/src/messages/tools.js +0 -90
  87. package/src/mockStream.js +0 -81
  88. package/src/prompts/collab.js +0 -7
  89. package/src/prompts/index.js +0 -3
  90. package/src/prompts/taskmanager.js +0 -58
  91. package/src/run.js +0 -427
  92. package/src/schemas/index.js +0 -3
  93. package/src/schemas/schema-preparation.test.js +0 -370
  94. package/src/schemas/validate.js +0 -314
  95. package/src/schemas/validate.test.js +0 -264
  96. package/src/scripts/abort.js +0 -127
  97. package/src/scripts/ant_web_search.js +0 -130
  98. package/src/scripts/ant_web_search_edge_case.js +0 -133
  99. package/src/scripts/ant_web_search_error_edge_case.js +0 -119
  100. package/src/scripts/args.js +0 -41
  101. package/src/scripts/bedrock-cache-debug.js +0 -186
  102. package/src/scripts/bedrock-content-aggregation-test.js +0 -195
  103. package/src/scripts/bedrock-merge-test.js +0 -80
  104. package/src/scripts/bedrock-parallel-tools-test.js +0 -150
  105. package/src/scripts/caching.js +0 -106
  106. package/src/scripts/cli.js +0 -152
  107. package/src/scripts/cli2.js +0 -119
  108. package/src/scripts/cli3.js +0 -163
  109. package/src/scripts/cli4.js +0 -165
  110. package/src/scripts/cli5.js +0 -165
  111. package/src/scripts/code_exec.js +0 -171
  112. package/src/scripts/code_exec_files.js +0 -180
  113. package/src/scripts/code_exec_multi_session.js +0 -185
  114. package/src/scripts/code_exec_ptc.js +0 -265
  115. package/src/scripts/code_exec_session.js +0 -217
  116. package/src/scripts/code_exec_simple.js +0 -120
  117. package/src/scripts/content.js +0 -111
  118. package/src/scripts/empty_input.js +0 -125
  119. package/src/scripts/handoff-test.js +0 -96
  120. package/src/scripts/image.js +0 -138
  121. package/src/scripts/memory.js +0 -83
  122. package/src/scripts/multi-agent-chain.js +0 -271
  123. package/src/scripts/multi-agent-conditional.js +0 -185
  124. package/src/scripts/multi-agent-document-review-chain.js +0 -171
  125. package/src/scripts/multi-agent-hybrid-flow.js +0 -264
  126. package/src/scripts/multi-agent-parallel-start.js +0 -214
  127. package/src/scripts/multi-agent-parallel.js +0 -346
  128. package/src/scripts/multi-agent-sequence.js +0 -184
  129. package/src/scripts/multi-agent-supervisor.js +0 -324
  130. package/src/scripts/multi-agent-test.js +0 -147
  131. package/src/scripts/parallel-asymmetric-tools-test.js +0 -202
  132. package/src/scripts/parallel-full-metadata-test.js +0 -176
  133. package/src/scripts/parallel-tools-test.js +0 -256
  134. package/src/scripts/programmatic_exec.js +0 -277
  135. package/src/scripts/programmatic_exec_agent.js +0 -168
  136. package/src/scripts/search.js +0 -118
  137. package/src/scripts/sequential-full-metadata-test.js +0 -143
  138. package/src/scripts/simple.js +0 -174
  139. package/src/scripts/single-agent-metadata-test.js +0 -152
  140. package/src/scripts/stream.js +0 -113
  141. package/src/scripts/test-custom-prompt-key.js +0 -132
  142. package/src/scripts/test-handoff-input.js +0 -143
  143. package/src/scripts/test-handoff-preamble.js +0 -227
  144. package/src/scripts/test-handoff-steering.js +0 -353
  145. package/src/scripts/test-multi-agent-list-handoff.js +0 -318
  146. package/src/scripts/test-parallel-agent-labeling.js +0 -253
  147. package/src/scripts/test-parallel-handoffs.js +0 -229
  148. package/src/scripts/test-thinking-handoff-bedrock.js +0 -132
  149. package/src/scripts/test-thinking-handoff.js +0 -132
  150. package/src/scripts/test-thinking-to-thinking-handoff-bedrock.js +0 -140
  151. package/src/scripts/test-tool-before-handoff-role-order.js +0 -223
  152. package/src/scripts/test-tools-before-handoff.js +0 -187
  153. package/src/scripts/test_code_api.js +0 -263
  154. package/src/scripts/thinking-bedrock.js +0 -128
  155. package/src/scripts/thinking-vertexai.js +0 -130
  156. package/src/scripts/thinking.js +0 -134
  157. package/src/scripts/tool_search.js +0 -114
  158. package/src/scripts/tools.js +0 -125
  159. package/src/specs/agent-handoffs-bedrock.integration.test.js +0 -280
  160. package/src/specs/agent-handoffs.test.js +0 -924
  161. package/src/specs/anthropic.simple.test.js +0 -287
  162. package/src/specs/azure.simple.test.js +0 -381
  163. package/src/specs/cache.simple.test.js +0 -282
  164. package/src/specs/custom-event-await.test.js +0 -148
  165. package/src/specs/deepseek.simple.test.js +0 -189
  166. package/src/specs/emergency-prune.test.js +0 -308
  167. package/src/specs/moonshot.simple.test.js +0 -237
  168. package/src/specs/observability.integration.test.js +0 -1337
  169. package/src/specs/openai.simple.test.js +0 -233
  170. package/src/specs/openrouter.simple.test.js +0 -202
  171. package/src/specs/prune.test.js +0 -733
  172. package/src/specs/reasoning.test.js +0 -144
  173. package/src/specs/spec.utils.js +0 -4
  174. package/src/specs/thinking-handoff.test.js +0 -486
  175. package/src/specs/thinking-prune.test.js +0 -600
  176. package/src/specs/token-distribution-edge-case.test.js +0 -246
  177. package/src/specs/token-memoization.test.js +0 -32
  178. package/src/specs/tokens.test.js +0 -49
  179. package/src/specs/tool-error.test.js +0 -139
  180. package/src/splitStream.js +0 -204
  181. package/src/splitStream.test.js +0 -504
  182. package/src/stream.js +0 -650
  183. package/src/stream.test.js +0 -225
  184. package/src/test/mockTools.js +0 -340
  185. package/src/tools/BrowserTools.js +0 -245
  186. package/src/tools/Calculator.js +0 -38
  187. package/src/tools/Calculator.test.js +0 -225
  188. package/src/tools/CodeExecutor.js +0 -233
  189. package/src/tools/ProgrammaticToolCalling.js +0 -602
  190. package/src/tools/StreamingToolCallBuffer.js +0 -179
  191. package/src/tools/ToolNode.js +0 -930
  192. package/src/tools/ToolSearch.js +0 -904
  193. package/src/tools/__tests__/BrowserTools.test.js +0 -306
  194. package/src/tools/__tests__/ProgrammaticToolCalling.integration.test.js +0 -276
  195. package/src/tools/__tests__/ProgrammaticToolCalling.test.js +0 -807
  196. package/src/tools/__tests__/StreamingToolCallBuffer.test.js +0 -175
  197. package/src/tools/__tests__/ToolApproval.test.js +0 -675
  198. package/src/tools/__tests__/ToolNode.recovery.test.js +0 -200
  199. package/src/tools/__tests__/ToolNode.session.test.js +0 -319
  200. package/src/tools/__tests__/ToolSearch.integration.test.js +0 -125
  201. package/src/tools/__tests__/ToolSearch.test.js +0 -812
  202. package/src/tools/__tests__/handlers.test.js +0 -799
  203. package/src/tools/__tests__/truncation-recovery.integration.test.js +0 -362
  204. package/src/tools/handlers.js +0 -306
  205. package/src/tools/schema.js +0 -25
  206. package/src/tools/search/anthropic.js +0 -34
  207. package/src/tools/search/content.js +0 -116
  208. package/src/tools/search/content.test.js +0 -133
  209. package/src/tools/search/firecrawl.js +0 -173
  210. package/src/tools/search/format.js +0 -198
  211. package/src/tools/search/highlights.js +0 -241
  212. package/src/tools/search/index.js +0 -3
  213. package/src/tools/search/jina-reranker.test.js +0 -106
  214. package/src/tools/search/rerankers.js +0 -165
  215. package/src/tools/search/schema.js +0 -102
  216. package/src/tools/search/search.js +0 -561
  217. package/src/tools/search/serper-scraper.js +0 -126
  218. package/src/tools/search/test.js +0 -129
  219. package/src/tools/search/tool.js +0 -453
  220. package/src/tools/search/types.js +0 -2
  221. package/src/tools/search/utils.js +0 -59
  222. package/src/types/graph.js +0 -24
  223. package/src/types/graph.test.js +0 -192
  224. package/src/types/index.js +0 -7
  225. package/src/types/llm.js +0 -2
  226. package/src/types/messages.js +0 -2
  227. package/src/types/run.js +0 -2
  228. package/src/types/stream.js +0 -2
  229. package/src/types/tools.js +0 -2
  230. package/src/utils/contextAnalytics.js +0 -79
  231. package/src/utils/contextAnalytics.test.js +0 -166
  232. package/src/utils/events.js +0 -26
  233. package/src/utils/graph.js +0 -11
  234. package/src/utils/handlers.js +0 -65
  235. package/src/utils/index.js +0 -10
  236. package/src/utils/llm.js +0 -21
  237. package/src/utils/llmConfig.js +0 -205
  238. package/src/utils/logging.js +0 -37
  239. package/src/utils/misc.js +0 -51
  240. package/src/utils/run.js +0 -69
  241. package/src/utils/schema.js +0 -21
  242. package/src/utils/title.js +0 -119
  243. package/src/utils/tokens.js +0 -92
  244. 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