@illuma-ai/agents 1.3.1 → 1.4.0-alpha.0

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 (356) hide show
  1. package/dist/cjs/graphs/Graph.cjs +3 -18
  2. package/dist/cjs/graphs/Graph.cjs.map +1 -1
  3. package/dist/cjs/llm/openai/index.cjs +3 -0
  4. package/dist/cjs/llm/openai/index.cjs.map +1 -1
  5. package/dist/cjs/main.cjs +58 -0
  6. package/dist/cjs/main.cjs.map +1 -1
  7. package/dist/cjs/providers/a2a/A2ACapabilityProvider.cjs +288 -0
  8. package/dist/cjs/providers/a2a/A2ACapabilityProvider.cjs.map +1 -0
  9. package/dist/cjs/providers/a2a/client.cjs +92 -0
  10. package/dist/cjs/providers/a2a/client.cjs.map +1 -0
  11. package/dist/cjs/providers/a2a/config.cjs +38 -0
  12. package/dist/cjs/providers/a2a/config.cjs.map +1 -0
  13. package/dist/cjs/providers/capabilityNaming.cjs +43 -0
  14. package/dist/cjs/providers/capabilityNaming.cjs.map +1 -0
  15. package/dist/cjs/providers/composite/CompositeCapabilityProvider.cjs +80 -0
  16. package/dist/cjs/providers/composite/CompositeCapabilityProvider.cjs.map +1 -0
  17. package/dist/cjs/providers/mcp/MCPCapabilityProvider.cjs +244 -0
  18. package/dist/cjs/providers/mcp/MCPCapabilityProvider.cjs.map +1 -0
  19. package/dist/cjs/providers/mcp/config.cjs +42 -0
  20. package/dist/cjs/providers/mcp/config.cjs.map +1 -0
  21. package/dist/cjs/providers/mcp/transport.cjs +65 -0
  22. package/dist/cjs/providers/mcp/transport.cjs.map +1 -0
  23. package/dist/cjs/providers/tools-server/ToolsServerCapabilityProvider.cjs +121 -0
  24. package/dist/cjs/providers/tools-server/ToolsServerCapabilityProvider.cjs.map +1 -0
  25. package/dist/cjs/providers/types.cjs +51 -0
  26. package/dist/cjs/providers/types.cjs.map +1 -0
  27. package/dist/cjs/tools/ToolNode.cjs +3 -0
  28. package/dist/cjs/tools/ToolNode.cjs.map +1 -1
  29. package/dist/cjs/tools/proxyTool.cjs +100 -0
  30. package/dist/cjs/tools/proxyTool.cjs.map +1 -0
  31. package/dist/cjs/utils/credentials.cjs +142 -0
  32. package/dist/cjs/utils/credentials.cjs.map +1 -0
  33. package/dist/cjs/utils/httpClient.cjs +74 -0
  34. package/dist/cjs/utils/httpClient.cjs.map +1 -0
  35. package/dist/cjs/utils/toolManifest.cjs +100 -0
  36. package/dist/cjs/utils/toolManifest.cjs.map +1 -0
  37. package/dist/esm/graphs/Graph.mjs +3 -18
  38. package/dist/esm/graphs/Graph.mjs.map +1 -1
  39. package/dist/esm/llm/openai/index.mjs +3 -0
  40. package/dist/esm/llm/openai/index.mjs.map +1 -1
  41. package/dist/esm/main.mjs +14 -0
  42. package/dist/esm/main.mjs.map +1 -1
  43. package/dist/esm/providers/a2a/A2ACapabilityProvider.mjs +281 -0
  44. package/dist/esm/providers/a2a/A2ACapabilityProvider.mjs.map +1 -0
  45. package/dist/esm/providers/a2a/client.mjs +88 -0
  46. package/dist/esm/providers/a2a/client.mjs.map +1 -0
  47. package/dist/esm/providers/a2a/config.mjs +35 -0
  48. package/dist/esm/providers/a2a/config.mjs.map +1 -0
  49. package/dist/esm/providers/capabilityNaming.mjs +39 -0
  50. package/dist/esm/providers/capabilityNaming.mjs.map +1 -0
  51. package/dist/esm/providers/composite/CompositeCapabilityProvider.mjs +78 -0
  52. package/dist/esm/providers/composite/CompositeCapabilityProvider.mjs.map +1 -0
  53. package/dist/esm/providers/mcp/MCPCapabilityProvider.mjs +240 -0
  54. package/dist/esm/providers/mcp/MCPCapabilityProvider.mjs.map +1 -0
  55. package/dist/esm/providers/mcp/config.mjs +39 -0
  56. package/dist/esm/providers/mcp/config.mjs.map +1 -0
  57. package/dist/esm/providers/mcp/transport.mjs +63 -0
  58. package/dist/esm/providers/mcp/transport.mjs.map +1 -0
  59. package/dist/esm/providers/tools-server/ToolsServerCapabilityProvider.mjs +119 -0
  60. package/dist/esm/providers/tools-server/ToolsServerCapabilityProvider.mjs.map +1 -0
  61. package/dist/esm/providers/types.mjs +51 -0
  62. package/dist/esm/providers/types.mjs.map +1 -0
  63. package/dist/esm/tools/ToolNode.mjs +3 -0
  64. package/dist/esm/tools/ToolNode.mjs.map +1 -1
  65. package/dist/esm/tools/proxyTool.mjs +98 -0
  66. package/dist/esm/tools/proxyTool.mjs.map +1 -0
  67. package/dist/esm/utils/credentials.mjs +135 -0
  68. package/dist/esm/utils/credentials.mjs.map +1 -0
  69. package/dist/esm/utils/httpClient.mjs +70 -0
  70. package/dist/esm/utils/httpClient.mjs.map +1 -0
  71. package/dist/esm/utils/toolManifest.mjs +96 -0
  72. package/dist/esm/utils/toolManifest.mjs.map +1 -0
  73. package/dist/types/index.d.ts +2 -0
  74. package/dist/types/providers/a2a/A2ACapabilityProvider.d.ts +89 -0
  75. package/dist/types/providers/a2a/client.d.ts +47 -0
  76. package/dist/types/providers/a2a/config.d.ts +18 -0
  77. package/dist/types/providers/a2a/index.d.ts +6 -0
  78. package/dist/types/providers/a2a/types.d.ts +173 -0
  79. package/dist/types/providers/capabilityNaming.d.ts +25 -0
  80. package/dist/types/providers/composite/CompositeCapabilityProvider.d.ts +22 -0
  81. package/dist/types/providers/composite/index.d.ts +1 -0
  82. package/dist/types/providers/index.d.ts +13 -0
  83. package/dist/types/providers/mcp/MCPCapabilityProvider.d.ts +54 -0
  84. package/dist/types/providers/mcp/config.d.ts +20 -0
  85. package/dist/types/providers/mcp/index.d.ts +5 -0
  86. package/dist/types/providers/mcp/transport.d.ts +18 -0
  87. package/dist/types/providers/mcp/types.d.ts +112 -0
  88. package/dist/types/providers/tools-server/ToolsServerCapabilityProvider.d.ts +45 -0
  89. package/dist/types/providers/tools-server/index.d.ts +1 -0
  90. package/dist/types/providers/types.d.ts +170 -0
  91. package/dist/types/tools/proxyTool.d.ts +55 -0
  92. package/dist/types/types/stream.d.ts +10 -0
  93. package/dist/types/utils/credentials.d.ts +77 -0
  94. package/dist/types/utils/httpClient.d.ts +46 -0
  95. package/dist/types/utils/index.d.ts +3 -0
  96. package/dist/types/utils/toolManifest.d.ts +49 -0
  97. package/package.json +21 -1
  98. package/src/graphs/Graph.ts +0 -23
  99. package/src/index.ts +4 -0
  100. package/src/providers/__tests__/CompositeCapabilityProvider.test.ts +93 -0
  101. package/src/providers/__tests__/ToolsServerCapabilityProvider.integration.spec.ts +79 -0
  102. package/src/providers/__tests__/ToolsServerCapabilityProvider.test.ts +206 -0
  103. package/src/providers/__tests__/types.test.ts +64 -0
  104. package/src/providers/a2a/A2ACapabilityProvider.ts +384 -0
  105. package/src/providers/a2a/__tests__/A2ACapabilityProvider.integration.spec.ts +345 -0
  106. package/src/providers/a2a/__tests__/A2ACapabilityProvider.test.ts +460 -0
  107. package/src/providers/a2a/client.ts +115 -0
  108. package/src/providers/a2a/config.ts +40 -0
  109. package/src/providers/a2a/index.ts +29 -0
  110. package/src/providers/a2a/types.ts +191 -0
  111. package/src/providers/capabilityNaming.ts +42 -0
  112. package/src/providers/composite/CompositeCapabilityProvider.ts +112 -0
  113. package/src/providers/composite/index.ts +1 -0
  114. package/src/providers/index.ts +65 -0
  115. package/src/providers/mcp/MCPCapabilityProvider.ts +345 -0
  116. package/src/providers/mcp/__tests__/MCPCapabilityProvider.integration.spec.ts +386 -0
  117. package/src/providers/mcp/__tests__/MCPCapabilityProvider.test.ts +371 -0
  118. package/src/providers/mcp/config.ts +45 -0
  119. package/src/providers/mcp/index.ts +21 -0
  120. package/src/providers/mcp/transport.ts +76 -0
  121. package/src/providers/mcp/types.ts +139 -0
  122. package/src/providers/tools-server/ToolsServerCapabilityProvider.ts +220 -0
  123. package/src/providers/tools-server/index.ts +1 -0
  124. package/src/providers/types.ts +187 -0
  125. package/src/tools/proxyTool.ts +146 -0
  126. package/src/types/stream.ts +10 -0
  127. package/src/utils/__tests__/credentials.test.ts +130 -0
  128. package/src/utils/__tests__/errors.test.ts +6 -4
  129. package/src/utils/__tests__/httpClient.test.ts +75 -0
  130. package/src/utils/__tests__/toolManifest.test.ts +116 -0
  131. package/src/utils/credentials.ts +157 -0
  132. package/src/utils/httpClient.ts +92 -0
  133. package/src/utils/index.ts +3 -0
  134. package/src/utils/toolManifest.ts +109 -0
  135. package/src/agents/AgentContext.js.map +0 -1
  136. package/src/agents/AgentContext.test.js.map +0 -1
  137. package/src/agents/__tests__/AgentContext.test.js.map +0 -1
  138. package/src/agents/__tests__/resolveStructuredOutputMode.test.js.map +0 -1
  139. package/src/common/enum.js.map +0 -1
  140. package/src/common/index.js.map +0 -1
  141. package/src/events.js.map +0 -1
  142. package/src/graphs/Graph.js.map +0 -1
  143. package/src/graphs/MultiAgentGraph.js.map +0 -1
  144. package/src/graphs/__tests__/structured-output.integration.test.js.map +0 -1
  145. package/src/graphs/__tests__/structured-output.test.js.map +0 -1
  146. package/src/graphs/contextManagement.e2e.test.js.map +0 -1
  147. package/src/graphs/contextManagement.test.js.map +0 -1
  148. package/src/graphs/handoffValidation.test.js.map +0 -1
  149. package/src/graphs/index.js.map +0 -1
  150. package/src/index.js.map +0 -1
  151. package/src/instrumentation.js.map +0 -1
  152. package/src/llm/anthropic/index.js.map +0 -1
  153. package/src/llm/anthropic/types.js.map +0 -1
  154. package/src/llm/anthropic/utils/message_inputs.js.map +0 -1
  155. package/src/llm/anthropic/utils/message_outputs.js.map +0 -1
  156. package/src/llm/anthropic/utils/output_parsers.js.map +0 -1
  157. package/src/llm/anthropic/utils/tools.js.map +0 -1
  158. package/src/llm/bedrock/__tests__/bedrock-caching.test.js.map +0 -1
  159. package/src/llm/bedrock/index.js.map +0 -1
  160. package/src/llm/bedrock/types.js.map +0 -1
  161. package/src/llm/bedrock/utils/index.js.map +0 -1
  162. package/src/llm/bedrock/utils/message_inputs.js.map +0 -1
  163. package/src/llm/bedrock/utils/message_outputs.js.map +0 -1
  164. package/src/llm/fake.js.map +0 -1
  165. package/src/llm/google/index.js.map +0 -1
  166. package/src/llm/google/types.js.map +0 -1
  167. package/src/llm/google/utils/common.js.map +0 -1
  168. package/src/llm/google/utils/tools.js.map +0 -1
  169. package/src/llm/google/utils/zod_to_genai_parameters.js.map +0 -1
  170. package/src/llm/openai/index.js.map +0 -1
  171. package/src/llm/openai/types.js.map +0 -1
  172. package/src/llm/openai/utils/index.js.map +0 -1
  173. package/src/llm/openai/utils/isReasoningModel.test.js.map +0 -1
  174. package/src/llm/openrouter/index.js.map +0 -1
  175. package/src/llm/openrouter/reasoning.test.js.map +0 -1
  176. package/src/llm/providers.js.map +0 -1
  177. package/src/llm/text.js.map +0 -1
  178. package/src/llm/vertexai/index.js.map +0 -1
  179. package/src/messages/__tests__/tools.test.js.map +0 -1
  180. package/src/messages/cache.js.map +0 -1
  181. package/src/messages/cache.test.js.map +0 -1
  182. package/src/messages/content.js.map +0 -1
  183. package/src/messages/content.test.js.map +0 -1
  184. package/src/messages/core.js.map +0 -1
  185. package/src/messages/ensureThinkingBlock.test.js.map +0 -1
  186. package/src/messages/format.js.map +0 -1
  187. package/src/messages/formatAgentMessages.test.js.map +0 -1
  188. package/src/messages/formatAgentMessages.tools.test.js.map +0 -1
  189. package/src/messages/formatMessage.test.js.map +0 -1
  190. package/src/messages/ids.js.map +0 -1
  191. package/src/messages/index.js.map +0 -1
  192. package/src/messages/labelContentByAgent.test.js.map +0 -1
  193. package/src/messages/prune.js.map +0 -1
  194. package/src/messages/reducer.js.map +0 -1
  195. package/src/messages/shiftIndexTokenCountMap.test.js.map +0 -1
  196. package/src/messages/summarize.js.map +0 -1
  197. package/src/messages/summarize.test.js.map +0 -1
  198. package/src/messages/tools.js.map +0 -1
  199. package/src/mockStream.js.map +0 -1
  200. package/src/prompts/collab.js.map +0 -1
  201. package/src/prompts/index.js.map +0 -1
  202. package/src/prompts/taskmanager.js.map +0 -1
  203. package/src/run.js.map +0 -1
  204. package/src/schemas/index.js.map +0 -1
  205. package/src/schemas/schema-preparation.test.js.map +0 -1
  206. package/src/schemas/validate.js.map +0 -1
  207. package/src/schemas/validate.test.js.map +0 -1
  208. package/src/scripts/abort.js.map +0 -1
  209. package/src/scripts/ant_web_search.js.map +0 -1
  210. package/src/scripts/ant_web_search_edge_case.js.map +0 -1
  211. package/src/scripts/ant_web_search_error_edge_case.js.map +0 -1
  212. package/src/scripts/args.js.map +0 -1
  213. package/src/scripts/bedrock-cache-debug.js.map +0 -1
  214. package/src/scripts/bedrock-content-aggregation-test.js.map +0 -1
  215. package/src/scripts/bedrock-merge-test.js.map +0 -1
  216. package/src/scripts/bedrock-parallel-tools-test.js.map +0 -1
  217. package/src/scripts/caching.js.map +0 -1
  218. package/src/scripts/cli.js.map +0 -1
  219. package/src/scripts/cli2.js.map +0 -1
  220. package/src/scripts/cli3.js.map +0 -1
  221. package/src/scripts/cli4.js.map +0 -1
  222. package/src/scripts/cli5.js.map +0 -1
  223. package/src/scripts/code_exec.js.map +0 -1
  224. package/src/scripts/code_exec_files.js.map +0 -1
  225. package/src/scripts/code_exec_multi_session.js.map +0 -1
  226. package/src/scripts/code_exec_ptc.js.map +0 -1
  227. package/src/scripts/code_exec_session.js.map +0 -1
  228. package/src/scripts/code_exec_simple.js.map +0 -1
  229. package/src/scripts/content.js.map +0 -1
  230. package/src/scripts/empty_input.js.map +0 -1
  231. package/src/scripts/handoff-test.js.map +0 -1
  232. package/src/scripts/image.js.map +0 -1
  233. package/src/scripts/memory.js.map +0 -1
  234. package/src/scripts/multi-agent-chain.js.map +0 -1
  235. package/src/scripts/multi-agent-conditional.js.map +0 -1
  236. package/src/scripts/multi-agent-document-review-chain.js.map +0 -1
  237. package/src/scripts/multi-agent-hybrid-flow.js.map +0 -1
  238. package/src/scripts/multi-agent-parallel-start.js.map +0 -1
  239. package/src/scripts/multi-agent-parallel.js.map +0 -1
  240. package/src/scripts/multi-agent-sequence.js.map +0 -1
  241. package/src/scripts/multi-agent-supervisor.js.map +0 -1
  242. package/src/scripts/multi-agent-test.js.map +0 -1
  243. package/src/scripts/parallel-asymmetric-tools-test.js.map +0 -1
  244. package/src/scripts/parallel-full-metadata-test.js.map +0 -1
  245. package/src/scripts/parallel-tools-test.js.map +0 -1
  246. package/src/scripts/programmatic_exec.js.map +0 -1
  247. package/src/scripts/programmatic_exec_agent.js.map +0 -1
  248. package/src/scripts/search.js.map +0 -1
  249. package/src/scripts/sequential-full-metadata-test.js.map +0 -1
  250. package/src/scripts/simple.js.map +0 -1
  251. package/src/scripts/single-agent-metadata-test.js.map +0 -1
  252. package/src/scripts/stream.js.map +0 -1
  253. package/src/scripts/test-custom-prompt-key.js.map +0 -1
  254. package/src/scripts/test-handoff-input.js.map +0 -1
  255. package/src/scripts/test-handoff-preamble.js.map +0 -1
  256. package/src/scripts/test-handoff-steering.js.map +0 -1
  257. package/src/scripts/test-multi-agent-list-handoff.js.map +0 -1
  258. package/src/scripts/test-parallel-agent-labeling.js.map +0 -1
  259. package/src/scripts/test-parallel-handoffs.js.map +0 -1
  260. package/src/scripts/test-thinking-handoff-bedrock.js.map +0 -1
  261. package/src/scripts/test-thinking-handoff.js.map +0 -1
  262. package/src/scripts/test-thinking-to-thinking-handoff-bedrock.js.map +0 -1
  263. package/src/scripts/test-tool-before-handoff-role-order.js.map +0 -1
  264. package/src/scripts/test-tools-before-handoff.js.map +0 -1
  265. package/src/scripts/test_code_api.js.map +0 -1
  266. package/src/scripts/thinking-bedrock.js.map +0 -1
  267. package/src/scripts/thinking-vertexai.js.map +0 -1
  268. package/src/scripts/thinking.js.map +0 -1
  269. package/src/scripts/tool_search.js.map +0 -1
  270. package/src/scripts/tools.js.map +0 -1
  271. package/src/specs/agent-handoffs-bedrock.integration.test.js.map +0 -1
  272. package/src/specs/agent-handoffs.test.js.map +0 -1
  273. package/src/specs/anthropic.simple.test.js.map +0 -1
  274. package/src/specs/azure.simple.test.js.map +0 -1
  275. package/src/specs/cache.simple.test.js.map +0 -1
  276. package/src/specs/custom-event-await.test.js.map +0 -1
  277. package/src/specs/deepseek.simple.test.js.map +0 -1
  278. package/src/specs/emergency-prune.test.js.map +0 -1
  279. package/src/specs/moonshot.simple.test.js.map +0 -1
  280. package/src/specs/observability.integration.test.js.map +0 -1
  281. package/src/specs/openai.simple.test.js.map +0 -1
  282. package/src/specs/openrouter.simple.test.js.map +0 -1
  283. package/src/specs/prune.test.js.map +0 -1
  284. package/src/specs/reasoning.test.js.map +0 -1
  285. package/src/specs/spec.utils.js.map +0 -1
  286. package/src/specs/thinking-handoff.test.js.map +0 -1
  287. package/src/specs/thinking-prune.test.js.map +0 -1
  288. package/src/specs/token-distribution-edge-case.test.js.map +0 -1
  289. package/src/specs/token-memoization.test.js.map +0 -1
  290. package/src/specs/tokens.test.js.map +0 -1
  291. package/src/specs/tool-error.test.js.map +0 -1
  292. package/src/splitStream.js.map +0 -1
  293. package/src/splitStream.test.js.map +0 -1
  294. package/src/stream.js.map +0 -1
  295. package/src/stream.test.js.map +0 -1
  296. package/src/test/mockTools.js.map +0 -1
  297. package/src/tools/BrowserTools.js.map +0 -1
  298. package/src/tools/Calculator.js.map +0 -1
  299. package/src/tools/Calculator.test.js.map +0 -1
  300. package/src/tools/CodeExecutor.js.map +0 -1
  301. package/src/tools/ProgrammaticToolCalling.js.map +0 -1
  302. package/src/tools/StreamingToolCallBuffer.js.map +0 -1
  303. package/src/tools/ToolNode.js.map +0 -1
  304. package/src/tools/ToolSearch.js.map +0 -1
  305. package/src/tools/__tests__/BrowserTools.test.js.map +0 -1
  306. package/src/tools/__tests__/ProgrammaticToolCalling.integration.test.js.map +0 -1
  307. package/src/tools/__tests__/ProgrammaticToolCalling.test.js.map +0 -1
  308. package/src/tools/__tests__/StreamingToolCallBuffer.test.js.map +0 -1
  309. package/src/tools/__tests__/ToolApproval.test.js.map +0 -1
  310. package/src/tools/__tests__/ToolNode.recovery.test.js.map +0 -1
  311. package/src/tools/__tests__/ToolNode.session.test.js.map +0 -1
  312. package/src/tools/__tests__/ToolSearch.integration.test.js.map +0 -1
  313. package/src/tools/__tests__/ToolSearch.test.js.map +0 -1
  314. package/src/tools/__tests__/handlers.test.js.map +0 -1
  315. package/src/tools/__tests__/truncation-recovery.integration.test.js.map +0 -1
  316. package/src/tools/handlers.js.map +0 -1
  317. package/src/tools/schema.js.map +0 -1
  318. package/src/tools/search/anthropic.js.map +0 -1
  319. package/src/tools/search/content.js.map +0 -1
  320. package/src/tools/search/content.test.js.map +0 -1
  321. package/src/tools/search/firecrawl.js.map +0 -1
  322. package/src/tools/search/format.js.map +0 -1
  323. package/src/tools/search/highlights.js.map +0 -1
  324. package/src/tools/search/index.js.map +0 -1
  325. package/src/tools/search/jina-reranker.test.js.map +0 -1
  326. package/src/tools/search/rerankers.js.map +0 -1
  327. package/src/tools/search/schema.js.map +0 -1
  328. package/src/tools/search/search.js.map +0 -1
  329. package/src/tools/search/serper-scraper.js.map +0 -1
  330. package/src/tools/search/test.js.map +0 -1
  331. package/src/tools/search/tool.js.map +0 -1
  332. package/src/tools/search/types.js.map +0 -1
  333. package/src/tools/search/utils.js.map +0 -1
  334. package/src/types/graph.js.map +0 -1
  335. package/src/types/graph.test.js.map +0 -1
  336. package/src/types/index.js.map +0 -1
  337. package/src/types/llm.js.map +0 -1
  338. package/src/types/messages.js.map +0 -1
  339. package/src/types/run.js.map +0 -1
  340. package/src/types/stream.js.map +0 -1
  341. package/src/types/tools.js.map +0 -1
  342. package/src/utils/contextAnalytics.js.map +0 -1
  343. package/src/utils/contextAnalytics.test.js.map +0 -1
  344. package/src/utils/events.js.map +0 -1
  345. package/src/utils/graph.js.map +0 -1
  346. package/src/utils/handlers.js.map +0 -1
  347. package/src/utils/index.js.map +0 -1
  348. package/src/utils/llm.js.map +0 -1
  349. package/src/utils/llmConfig.js.map +0 -1
  350. package/src/utils/logging.js.map +0 -1
  351. package/src/utils/misc.js.map +0 -1
  352. package/src/utils/run.js.map +0 -1
  353. package/src/utils/schema.js.map +0 -1
  354. package/src/utils/title.js.map +0 -1
  355. package/src/utils/tokens.js.map +0 -1
  356. package/src/utils/toonFormat.js.map +0 -1
@@ -0,0 +1,206 @@
1
+ import axios from 'axios';
2
+ import { ToolsServerCapabilityProvider } from '../tools-server/ToolsServerCapabilityProvider';
3
+ import { CapabilityKind } from '../types';
4
+
5
+ /**
6
+ * Tests use a stub axios instance passed via config — no real HTTP.
7
+ */
8
+ function makeStubClient(response: {
9
+ status: number;
10
+ data: unknown;
11
+ }): ReturnType<typeof axios.create> {
12
+ const stub = {
13
+ get: jest.fn().mockResolvedValue(response),
14
+ post: jest.fn().mockResolvedValue(response),
15
+ defaults: { baseURL: 'http://stub', headers: {} },
16
+ };
17
+ return stub as unknown as ReturnType<typeof axios.create>;
18
+ }
19
+
20
+ const manifestFixture = [
21
+ {
22
+ pluginKey: 'wikipedia',
23
+ name: 'Wikipedia',
24
+ description: 'Search Wikipedia',
25
+ icon: '/assets/icons/wikipedia.svg',
26
+ authConfig: [],
27
+ schema: { type: 'object', properties: { query: { type: 'string' } } },
28
+ tags: ['search', 'knowledge'],
29
+ },
30
+ {
31
+ pluginKey: 'dalle',
32
+ name: 'DALL-E 3',
33
+ description: 'Generate images',
34
+ authConfig: [
35
+ {
36
+ authField: 'OPENAI_API_KEY',
37
+ label: 'OpenAI API Key',
38
+ source: 'user',
39
+ required: true,
40
+ },
41
+ ],
42
+ jsonSchema: { type: 'object', properties: { prompt: { type: 'string' } } },
43
+ tags: ['image'],
44
+ },
45
+ ];
46
+
47
+ describe('ToolsServerCapabilityProvider construction', () => {
48
+ it('throws when baseUrl missing', () => {
49
+ expect(
50
+ () => new ToolsServerCapabilityProvider({ baseUrl: '', apiKey: 'k' })
51
+ ).toThrow(/baseUrl is required/);
52
+ });
53
+ it('throws when apiKey missing', () => {
54
+ expect(
55
+ () =>
56
+ new ToolsServerCapabilityProvider({ baseUrl: 'http://x', apiKey: '' })
57
+ ).toThrow(/apiKey is required/);
58
+ });
59
+ it('composes providerId from baseUrl', () => {
60
+ const p = new ToolsServerCapabilityProvider({
61
+ baseUrl: 'http://localhost:3500',
62
+ apiKey: 'k',
63
+ client: makeStubClient({ status: 200, data: [] }),
64
+ });
65
+ expect(p.providerId).toBe('tools-server:http://localhost:3500');
66
+ });
67
+ });
68
+
69
+ describe('ToolsServerCapabilityProvider.fetchManifest', () => {
70
+ it('normalizes manifest entries into Capability shape', async () => {
71
+ const client = makeStubClient({ status: 200, data: manifestFixture });
72
+ const p = new ToolsServerCapabilityProvider({
73
+ baseUrl: 'http://x',
74
+ apiKey: 'k',
75
+ client,
76
+ });
77
+ const caps = await p.fetchManifest();
78
+ expect(caps).toHaveLength(2);
79
+ expect(caps[0].kind).toBe(CapabilityKind.TOOL);
80
+ expect(caps[0].name).toBe('wikipedia');
81
+ expect(caps[0].metadata.tags).toEqual(['search', 'knowledge']);
82
+ expect(caps[1].authConfig[0].authField).toBe('OPENAI_API_KEY');
83
+ expect(caps[1].authConfig[0].required).toBe(true);
84
+ });
85
+
86
+ it('caches manifest between calls', async () => {
87
+ const client = makeStubClient({ status: 200, data: manifestFixture });
88
+ const p = new ToolsServerCapabilityProvider({
89
+ baseUrl: 'http://x',
90
+ apiKey: 'k',
91
+ client,
92
+ });
93
+ await p.fetchManifest();
94
+ await p.fetchManifest();
95
+ expect((client.get as jest.Mock).mock.calls).toHaveLength(1);
96
+ });
97
+
98
+ it('invalidateCache forces refetch', async () => {
99
+ const client = makeStubClient({ status: 200, data: manifestFixture });
100
+ const p = new ToolsServerCapabilityProvider({
101
+ baseUrl: 'http://x',
102
+ apiKey: 'k',
103
+ client,
104
+ });
105
+ await p.fetchManifest();
106
+ p.invalidateCache();
107
+ await p.fetchManifest();
108
+ expect((client.get as jest.Mock).mock.calls).toHaveLength(2);
109
+ });
110
+
111
+ it('filters by name', async () => {
112
+ const client = makeStubClient({ status: 200, data: manifestFixture });
113
+ const p = new ToolsServerCapabilityProvider({
114
+ baseUrl: 'http://x',
115
+ apiKey: 'k',
116
+ client,
117
+ });
118
+ const caps = await p.fetchManifest({ names: ['wikipedia'] });
119
+ expect(caps).toHaveLength(1);
120
+ expect(caps[0].name).toBe('wikipedia');
121
+ });
122
+
123
+ it('throws HttpError on non-2xx', async () => {
124
+ const client = makeStubClient({ status: 500, data: { error: 'boom' } });
125
+ const p = new ToolsServerCapabilityProvider({
126
+ baseUrl: 'http://x',
127
+ apiKey: 'k',
128
+ client,
129
+ });
130
+ await expect(p.fetchManifest()).rejects.toThrow(/HTTP 500/);
131
+ });
132
+ });
133
+
134
+ describe('ToolsServerCapabilityProvider.createRunnables', () => {
135
+ it('returns one StructuredTool per capability', async () => {
136
+ const client = makeStubClient({ status: 200, data: manifestFixture });
137
+ const p = new ToolsServerCapabilityProvider({
138
+ baseUrl: 'http://x',
139
+ apiKey: 'k',
140
+ client,
141
+ });
142
+ const caps = await p.fetchManifest();
143
+ const tools = await p.createRunnables(caps, {});
144
+ expect(tools).toHaveLength(2);
145
+ expect(tools[0].name).toBe('wikipedia');
146
+ expect(tools[1].name).toBe('dalle');
147
+ });
148
+
149
+ it('proxy tool invocation POSTs to /execute/:name with credentials', async () => {
150
+ const client = {
151
+ get: jest.fn().mockResolvedValue({ status: 200, data: manifestFixture }),
152
+ post: jest.fn().mockResolvedValue({
153
+ status: 200,
154
+ data: {
155
+ success: true,
156
+ result: 'Node.js is a JS runtime',
157
+ timing: { durationMs: 42 },
158
+ },
159
+ }),
160
+ defaults: { baseURL: 'http://stub', headers: {} },
161
+ };
162
+ const p = new ToolsServerCapabilityProvider({
163
+ baseUrl: 'http://x',
164
+ apiKey: 'k',
165
+ client: client as unknown as ReturnType<typeof axios.create>,
166
+ });
167
+ const caps = await p.fetchManifest();
168
+ const [wikipedia] = await p.createRunnables(
169
+ caps.filter((c) => c.name === 'wikipedia'),
170
+ { WIKIPEDIA_API_KEY: 'test' }
171
+ );
172
+ const result = await wikipedia.invoke({ query: 'Node.js' });
173
+ expect(result).toBe('Node.js is a JS runtime');
174
+ expect((client.post as jest.Mock).mock.calls[0][0]).toBe(
175
+ '/execute/wikipedia'
176
+ );
177
+ expect((client.post as jest.Mock).mock.calls[0][1]).toEqual({
178
+ input: { query: 'Node.js' },
179
+ credentials: { WIKIPEDIA_API_KEY: 'test' },
180
+ });
181
+ });
182
+
183
+ it('proxy tool surfaces backend success:false as thrown error', async () => {
184
+ const client = {
185
+ get: jest.fn().mockResolvedValue({ status: 200, data: manifestFixture }),
186
+ post: jest.fn().mockResolvedValue({
187
+ status: 200,
188
+ data: { success: false, error: 'missing API key' },
189
+ }),
190
+ defaults: { baseURL: 'http://stub', headers: {} },
191
+ };
192
+ const p = new ToolsServerCapabilityProvider({
193
+ baseUrl: 'http://x',
194
+ apiKey: 'k',
195
+ client: client as unknown as ReturnType<typeof axios.create>,
196
+ });
197
+ const caps = await p.fetchManifest();
198
+ const [dalle] = await p.createRunnables(
199
+ caps.filter((c) => c.name === 'dalle'),
200
+ {}
201
+ );
202
+ await expect(dalle.invoke({ prompt: 'cat' })).rejects.toThrow(
203
+ /missing API key/
204
+ );
205
+ });
206
+ });
@@ -0,0 +1,64 @@
1
+ import {
2
+ CapabilityKind,
3
+ AuthSource,
4
+ type Capability,
5
+ type CapabilityProvider,
6
+ type CredentialMap,
7
+ } from '../types';
8
+
9
+ describe('CapabilityProvider types', () => {
10
+ it('CapabilityKind enum covers tool/skill/mcp', () => {
11
+ expect(CapabilityKind.TOOL).toBe('tool');
12
+ expect(CapabilityKind.SKILL).toBe('skill');
13
+ expect(CapabilityKind.MCP).toBe('mcp');
14
+ });
15
+
16
+ it('AuthSource enum covers server/user/forwarded', () => {
17
+ expect(AuthSource.SERVER).toBe('server');
18
+ expect(AuthSource.USER).toBe('user');
19
+ expect(AuthSource.FORWARDED).toBe('forwarded');
20
+ });
21
+
22
+ it('Capability shape reserves upstream metadata fields', () => {
23
+ const cap: Capability = {
24
+ kind: CapabilityKind.TOOL,
25
+ name: 'sample',
26
+ description: 'test capability',
27
+ schema: { type: 'object' },
28
+ authConfig: [],
29
+ metadata: {
30
+ icon: '/icon.svg',
31
+ category: 'test',
32
+ tags: ['a', 'b'],
33
+ auth: 'oauth',
34
+ expires_at: 1735689600,
35
+ injectedMessages: false,
36
+ sessionAware: false,
37
+ },
38
+ };
39
+ expect(cap.name).toBe('sample');
40
+ expect(cap.metadata.expires_at).toBe(1735689600);
41
+ });
42
+
43
+ it('CredentialMap is a plain string record', () => {
44
+ const creds: CredentialMap = {
45
+ OPENAI_API_KEY: 'sk-xxx',
46
+ WOLFRAM_APP_ID: 'abc123',
47
+ };
48
+ expect(Object.keys(creds)).toHaveLength(2);
49
+ });
50
+
51
+ it('CapabilityProvider is an implementable contract', () => {
52
+ class FakeProvider implements CapabilityProvider {
53
+ readonly providerId = 'fake';
54
+ async fetchManifest() {
55
+ return [];
56
+ }
57
+ async createRunnables() {
58
+ return [];
59
+ }
60
+ }
61
+ const p: CapabilityProvider = new FakeProvider();
62
+ expect(p.providerId).toBe('fake');
63
+ });
64
+ });
@@ -0,0 +1,384 @@
1
+ /**
2
+ * A2ACapabilityProvider — client-side adapter for consuming A2A-served
3
+ * remote agents as capabilities.
4
+ *
5
+ * Same pattern as `MCPCapabilityProvider`: take a unified spec map,
6
+ * speak the protocol (A2A JSON-RPC over HTTP), expose each skill as a
7
+ * `Capability` / `StructuredTool`.
8
+ *
9
+ * Design:
10
+ * - One `A2AClient` per remote spec, cached after first use.
11
+ * - `fetchManifest()`:
12
+ * for each remote, GET /.well-known/agent.json → emit one
13
+ * Capability per advertised skill (or one per remote when
14
+ * `flattenAsSingleTool` is set).
15
+ * - `createRunnables()`:
16
+ * each Capability becomes a StructuredTool whose invoke sends a
17
+ * JSON-RPC `tasks/send` to the remote and returns the final
18
+ * artifact's text.
19
+ *
20
+ * What the host supplies:
21
+ * - `remotes`: unified spec map (normalized from whatever source).
22
+ * - `getAuthHeaders?`: per-remote header resolver. Called at connect
23
+ * time. OAuth / token refresh stays in the host.
24
+ */
25
+
26
+ import { tool, type StructuredToolInterface } from '@langchain/core/tools';
27
+
28
+ import {
29
+ CapabilityKind,
30
+ type Capability,
31
+ type CapabilityFilter,
32
+ type CapabilityProvider,
33
+ type CredentialMap,
34
+ } from '@/providers/types';
35
+ import {
36
+ CAPABILITY_NAME_SEPARATOR,
37
+ formatCapabilityName,
38
+ parseCapabilityName as parseSharedName,
39
+ } from '@/providers/capabilityNaming';
40
+ import { A2AClient, extractTaskText, generateRpcId } from './client';
41
+ import { consoleLogger, getA2AEnvDefaults } from './config';
42
+ import type {
43
+ A2AAgentCard,
44
+ A2ALogger,
45
+ A2AProviderConfig,
46
+ A2ARemoteSpec,
47
+ A2ASkill,
48
+ } from './types';
49
+
50
+ /**
51
+ * Per-remote cached state. The agent card is fetched lazily and reused.
52
+ */
53
+ interface RemoteConnection {
54
+ remoteName: string;
55
+ spec: A2ARemoteSpec;
56
+ client: A2AClient;
57
+ card: A2AAgentCard;
58
+ }
59
+
60
+ export class A2ACapabilityProvider implements CapabilityProvider {
61
+ readonly providerId: string;
62
+ private readonly config: Required<
63
+ Pick<A2AProviderConfig, 'clientInfo' | 'timeoutMs'>
64
+ > &
65
+ Pick<A2AProviderConfig, 'remotes' | 'getAuthHeaders'>;
66
+ private readonly logger: A2ALogger;
67
+ private readonly connections = new Map<string, RemoteConnection>();
68
+
69
+ constructor(config: A2AProviderConfig) {
70
+ if (!config.remotes || Object.keys(config.remotes).length === 0) {
71
+ throw new Error('A2ACapabilityProvider: at least one remote is required');
72
+ }
73
+ const env = getA2AEnvDefaults();
74
+ this.config = {
75
+ remotes: config.remotes,
76
+ getAuthHeaders: config.getAuthHeaders,
77
+ clientInfo: config.clientInfo ?? {
78
+ name: env.clientName,
79
+ version: env.clientVersion,
80
+ },
81
+ timeoutMs: config.timeoutMs ?? env.timeoutMs,
82
+ };
83
+ this.logger = config.logger ?? consoleLogger;
84
+ const names = Object.keys(config.remotes).sort().join(',');
85
+ this.providerId = `a2a:${names}`;
86
+ }
87
+
88
+ async fetchManifest(filter?: CapabilityFilter): Promise<Capability[]> {
89
+ if (filter?.kind && filter.kind !== CapabilityKind.A2A) return [];
90
+
91
+ const capabilities: Capability[] = [];
92
+ for (const remoteName of Object.keys(this.config.remotes)) {
93
+ try {
94
+ const conn = await this.ensureConnection(remoteName);
95
+ const caps = this.cardToCapabilities(remoteName, conn.card, conn.spec);
96
+ for (const cap of caps) {
97
+ if (this.capabilityMatchesFilter(cap, filter)) capabilities.push(cap);
98
+ }
99
+ } catch (err) {
100
+ // One remote failing must not block others — log and continue.
101
+ this.logger.warn(
102
+ `[a2a] failed to fetch manifest for remote "${remoteName}":`,
103
+ err instanceof Error ? err.message : err
104
+ );
105
+ }
106
+ }
107
+
108
+ this.logger.debug(
109
+ `[a2a] manifest assembled — ${capabilities.length} capabilities across ${this.connections.size}/${Object.keys(this.config.remotes).length} connected remotes`
110
+ );
111
+
112
+ return capabilities;
113
+ }
114
+
115
+ async createRunnables(
116
+ capabilities: Capability[],
117
+ _credentials: CredentialMap
118
+ ): Promise<StructuredToolInterface[]> {
119
+ const runnables: StructuredToolInterface[] = [];
120
+ for (const cap of capabilities) {
121
+ if (cap.kind !== CapabilityKind.A2A) continue;
122
+ const parsed = parseCapabilityName(cap.name);
123
+ if (!parsed) {
124
+ this.logger.warn(
125
+ `[a2a] skipping capability "${cap.name}" — unparseable`
126
+ );
127
+ continue;
128
+ }
129
+ runnables.push(
130
+ this.buildProxyTool(parsed.remoteName, parsed.skillId, cap)
131
+ );
132
+ }
133
+ return runnables;
134
+ }
135
+
136
+ /** Disconnect any cached clients. A2A is stateless over HTTP so this
137
+ * is effectively a cache clear — no sockets to close. */
138
+ async close(): Promise<void> {
139
+ this.connections.clear();
140
+ }
141
+
142
+ // --- internals ---------------------------------------------------------
143
+
144
+ private async ensureConnection(
145
+ remoteName: string
146
+ ): Promise<RemoteConnection> {
147
+ const existing = this.connections.get(remoteName);
148
+ if (existing) return existing;
149
+
150
+ const spec = this.config.remotes[remoteName];
151
+ if (!spec) {
152
+ throw new Error(`[a2a] remote "${remoteName}" not in config`);
153
+ }
154
+
155
+ const authHeaders = await this.resolveAuthHeaders(remoteName);
156
+ const headers = {
157
+ ...(spec.headers ?? {}),
158
+ ...(authHeaders ?? {}),
159
+ 'user-agent': `${this.config.clientInfo.name}/${this.config.clientInfo.version}`,
160
+ };
161
+
162
+ const client = new A2AClient({
163
+ baseUrl: spec.url,
164
+ headers,
165
+ timeoutMs: this.config.timeoutMs,
166
+ cardPath: spec.cardPath,
167
+ rpcPath: spec.rpcPath,
168
+ });
169
+
170
+ this.logger.debug(
171
+ `[a2a] fetching card from "${remoteName}" at ${spec.url}`
172
+ );
173
+ const card = await client.fetchAgentCard();
174
+ this.logger.debug(
175
+ `[a2a] connected to "${remoteName}" — ${card.skills.length ?? 0} skills discovered`
176
+ );
177
+
178
+ const conn: RemoteConnection = { remoteName, spec, client, card };
179
+ this.connections.set(remoteName, conn);
180
+ return conn;
181
+ }
182
+
183
+ private async resolveAuthHeaders(
184
+ remoteName: string
185
+ ): Promise<Record<string, string> | undefined> {
186
+ if (!this.config.getAuthHeaders) return undefined;
187
+ try {
188
+ return await this.config.getAuthHeaders(remoteName);
189
+ } catch (err) {
190
+ this.logger.warn(
191
+ `[a2a] getAuthHeaders("${remoteName}") threw:`,
192
+ err instanceof Error ? err.message : err
193
+ );
194
+ return undefined;
195
+ }
196
+ }
197
+
198
+ private cardToCapabilities(
199
+ remoteName: string,
200
+ card: A2AAgentCard,
201
+ spec: A2ARemoteSpec
202
+ ): Capability[] {
203
+ if (spec.flattenAsSingleTool) {
204
+ // One capability per remote, named after the remote. Useful when
205
+ // the card has no skills listed or you want coarse routing.
206
+ return [
207
+ {
208
+ kind: CapabilityKind.A2A,
209
+ name: formatCapabilityName(remoteName),
210
+ description: card.description ?? card.name,
211
+ schema: MESSAGE_INPUT_SCHEMA,
212
+ authConfig: [],
213
+ metadata: {
214
+ category: 'a2a',
215
+ tags: [remoteName, ...(card.skills.map((s) => s.id) ?? [])],
216
+ },
217
+ },
218
+ ];
219
+ }
220
+
221
+ if (!card.skills || card.skills.length === 0) {
222
+ // No skills advertised — expose a single message endpoint so the
223
+ // agent can still invoke the remote with free-form text.
224
+ return [
225
+ {
226
+ kind: CapabilityKind.A2A,
227
+ name: formatCapabilityName(remoteName),
228
+ description:
229
+ card.description ?? `Delegate to remote agent "${card.name}".`,
230
+ schema: MESSAGE_INPUT_SCHEMA,
231
+ authConfig: [],
232
+ metadata: { category: 'a2a', tags: [remoteName] },
233
+ },
234
+ ];
235
+ }
236
+
237
+ return card.skills.map(
238
+ (skill): Capability => skillToCapability(remoteName, skill)
239
+ );
240
+ }
241
+
242
+ private capabilityMatchesFilter(
243
+ cap: Capability,
244
+ filter?: CapabilityFilter
245
+ ): boolean {
246
+ if (!filter) return true;
247
+ if (filter.kind && cap.kind !== filter.kind) return false;
248
+ if (filter.names && !filter.names.includes(cap.name)) return false;
249
+ if (filter.tags?.length) {
250
+ const capTags = new Set(cap.metadata.tags ?? []);
251
+ if (!filter.tags.some((t) => capTags.has(t))) return false;
252
+ }
253
+ return true;
254
+ }
255
+
256
+ private buildProxyTool(
257
+ remoteName: string,
258
+ skillId: string | undefined,
259
+ cap: Capability
260
+ ): StructuredToolInterface {
261
+ const logger = this.logger;
262
+ const ensureConnection = this.ensureConnection.bind(this);
263
+
264
+ return tool(
265
+ async (input: unknown): Promise<string> => {
266
+ // DEBUG: log tool invocation (remove after POC stabilizes)
267
+ logger.debug(
268
+ `[a2a] invoking ${remoteName}${skillId ? `:${skillId}` : ''}`
269
+ );
270
+
271
+ const message = coerceInputToA2AMessage(input, skillId);
272
+ const conn = await ensureConnection(remoteName);
273
+ const task = await conn.client.sendTask({
274
+ id: generateRpcId(),
275
+ message,
276
+ });
277
+
278
+ try {
279
+ return extractTaskText(task);
280
+ } catch (err) {
281
+ throw new Error(
282
+ `A2A ${remoteName}${skillId ? `:${skillId}` : ''} failed: ${err instanceof Error ? err.message : String(err)}`
283
+ );
284
+ }
285
+ },
286
+ {
287
+ name: cap.name,
288
+ description: cap.description,
289
+ schema: (cap.schema as object) ?? MESSAGE_INPUT_SCHEMA,
290
+ responseFormat: 'content',
291
+ }
292
+ );
293
+ }
294
+ }
295
+
296
+ // ---------------------------------------------------------------------------
297
+ // Helpers exported for testing
298
+ // ---------------------------------------------------------------------------
299
+
300
+ // CAPABILITY_NAME_SEPARATOR + formatCapabilityName live in the shared
301
+ // `@/providers/capabilityNaming` module so every provider uses the same
302
+ // encoding. Re-exported here for backward-compatible imports from the
303
+ // A2A barrel.
304
+ export { CAPABILITY_NAME_SEPARATOR, formatCapabilityName };
305
+
306
+ /**
307
+ * Parse a capability name produced by this provider, returning A2A-specific
308
+ * field names (`remoteName` / `skillId`). Thin adapter over the shared
309
+ * `parseCapabilityName` — kept local so callers see the A2A vocabulary.
310
+ */
311
+ export function parseCapabilityName(
312
+ name: string
313
+ ): { remoteName: string; skillId?: string } | null {
314
+ const parsed = parseSharedName(name);
315
+ if (!parsed) return null;
316
+ return {
317
+ remoteName: parsed.sourceName,
318
+ skillId: parsed.itemName,
319
+ };
320
+ }
321
+
322
+ /** Derive a Capability from a skill entry on the agent card. */
323
+ export function skillToCapability(
324
+ remoteName: string,
325
+ skill: A2ASkill
326
+ ): Capability {
327
+ return {
328
+ kind: CapabilityKind.A2A,
329
+ name: formatCapabilityName(remoteName, skill.id),
330
+ description: skill.description || skill.name || skill.id,
331
+ schema: MESSAGE_INPUT_SCHEMA,
332
+ authConfig: [],
333
+ metadata: {
334
+ category: 'a2a',
335
+ tags: [remoteName, ...(skill.tags ?? [])],
336
+ },
337
+ };
338
+ }
339
+
340
+ /**
341
+ * Default input schema for A2A capabilities. Every invocation is a text
342
+ * message — we don't try to infer a per-skill input shape because the
343
+ * A2A spec's skill entries don't include a parameter schema.
344
+ */
345
+ export const MESSAGE_INPUT_SCHEMA = {
346
+ type: 'object',
347
+ properties: {
348
+ message: {
349
+ type: 'string',
350
+ description:
351
+ 'Instruction or question to send to the remote agent as a user message.',
352
+ },
353
+ },
354
+ required: ['message'],
355
+ } as const;
356
+
357
+ /**
358
+ * Coerce arbitrary LLM-supplied input into a valid A2A message. If the
359
+ * input is a string, wraps it directly. If it's `{ message: "..." }`,
360
+ * uses `message`. Otherwise JSON-stringifies so the agent receives
361
+ * something it can try to interpret.
362
+ *
363
+ * When a skill id is known, it's prepended to the message text so the
364
+ * remote agent can route — some A2A implementations use this, others
365
+ * ignore it, it's best-effort.
366
+ */
367
+ export function coerceInputToA2AMessage(
368
+ input: unknown,
369
+ skillId?: string
370
+ ): { role: 'user'; parts: Array<{ type: 'text'; text: string }> } {
371
+ let text: string;
372
+ if (typeof input === 'string') {
373
+ text = input;
374
+ } else if (input && typeof input === 'object' && 'message' in input) {
375
+ const val = (input as { message: unknown }).message;
376
+ text = typeof val === 'string' ? val : JSON.stringify(val);
377
+ } else {
378
+ text = JSON.stringify(input);
379
+ }
380
+ if (skillId) {
381
+ text = `[skill: ${skillId}]\n${text}`;
382
+ }
383
+ return { role: 'user', parts: [{ type: 'text', text }] };
384
+ }