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