acp-sdk 0.8.0 → 0.10.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 (317) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +19 -0
  3. data/README.md +18 -4
  4. data/lib/acp/agent_connection/client.rb +31 -1
  5. data/lib/acp/agent_connection/optional_method/disable_provider.rb +23 -0
  6. data/lib/acp/agent_connection/optional_method/fork_session.rb +23 -0
  7. data/lib/acp/agent_connection/optional_method/list_providers.rb +23 -0
  8. data/lib/acp/agent_connection/optional_method/set_provider.rb +23 -0
  9. data/lib/acp/agent_connection.rb +73 -3
  10. data/lib/acp/client_connection.rb +108 -9
  11. data/lib/acp/request_error.rb +10 -0
  12. data/lib/acp/sdk.rb +3 -1
  13. data/lib/acp/transport/cancellation.rb +42 -0
  14. data/lib/acp/transport/stdio.rb +72 -1
  15. data/lib/acp/types/unstable/accept_nes_notification.rb +41 -0
  16. data/lib/acp/types/unstable/agent_capabilities.rb +122 -0
  17. data/lib/acp/types/unstable/client_capabilities.rb +127 -0
  18. data/lib/acp/types/unstable/client_nes_capabilities.rb +62 -0
  19. data/lib/acp/types/unstable/client_session_capabilities.rb +57 -0
  20. data/lib/acp/types/unstable/close_nes_request.rb +35 -0
  21. data/lib/acp/types/unstable/close_nes_response.rb +27 -0
  22. data/lib/acp/types/unstable/compaction_status.rb +11 -0
  23. data/lib/acp/types/unstable/compaction_summary_chunk.rb +41 -0
  24. data/lib/acp/types/unstable/compaction_update.rb +62 -0
  25. data/lib/acp/types/unstable/did_change_document_notification.rb +65 -0
  26. data/lib/acp/types/unstable/did_close_document_notification.rb +41 -0
  27. data/lib/acp/types/unstable/did_focus_document_notification.rb +74 -0
  28. data/lib/acp/types/unstable/did_open_document_notification.rb +66 -0
  29. data/lib/acp/types/unstable/did_save_document_notification.rb +41 -0
  30. data/lib/acp/types/unstable/disable_provider_request.rb +35 -0
  31. data/lib/acp/types/unstable/disable_provider_response.rb +27 -0
  32. data/lib/acp/types/unstable/fork_session_request.rb +69 -0
  33. data/lib/acp/types/unstable/fork_session_response.rb +55 -0
  34. data/lib/acp/types/unstable/idle_state_update.rb +46 -0
  35. data/lib/acp/types/unstable/initialize_response.rb +72 -0
  36. data/lib/acp/types/unstable/list_providers_request.rb +27 -0
  37. data/lib/acp/types/unstable/list_providers_response.rb +35 -0
  38. data/lib/acp/types/unstable/llm_protocol.rb +12 -0
  39. data/lib/acp/types/unstable/mcp_capabilities.rb +47 -0
  40. data/lib/acp/types/unstable/mcp_error.rb +41 -0
  41. data/lib/acp/types/unstable/mcp_server/acp.rb +41 -0
  42. data/lib/acp/types/unstable/mcp_server/http.rb +50 -0
  43. data/lib/acp/types/unstable/mcp_server/sse.rb +50 -0
  44. data/lib/acp/types/unstable/mcp_server.rb +25 -0
  45. data/lib/acp/types/unstable/mcp_server_acp.rb +41 -0
  46. data/lib/acp/types/unstable/message_mcp_notification.rb +59 -0
  47. data/lib/acp/types/unstable/message_mcp_request.rb +59 -0
  48. data/lib/acp/types/unstable/message_mcp_response/error.rb +35 -0
  49. data/lib/acp/types/unstable/message_mcp_response/result.rb +32 -0
  50. data/lib/acp/types/unstable/message_mcp_response.rb +23 -0
  51. data/lib/acp/types/unstable/nes_capabilities.rb +51 -0
  52. data/lib/acp/types/unstable/nes_context_capabilities.rb +111 -0
  53. data/lib/acp/types/unstable/nes_diagnostic.rb +57 -0
  54. data/lib/acp/types/unstable/nes_diagnostic_severity.rb +11 -0
  55. data/lib/acp/types/unstable/nes_diagnostics_capabilities.rb +27 -0
  56. data/lib/acp/types/unstable/nes_document_did_change_capabilities.rb +35 -0
  57. data/lib/acp/types/unstable/nes_document_did_close_capabilities.rb +27 -0
  58. data/lib/acp/types/unstable/nes_document_did_focus_capabilities.rb +27 -0
  59. data/lib/acp/types/unstable/nes_document_did_open_capabilities.rb +27 -0
  60. data/lib/acp/types/unstable/nes_document_did_save_capabilities.rb +27 -0
  61. data/lib/acp/types/unstable/nes_document_event_capabilities.rb +91 -0
  62. data/lib/acp/types/unstable/nes_edit_history_capabilities.rb +35 -0
  63. data/lib/acp/types/unstable/nes_edit_history_entry.rb +41 -0
  64. data/lib/acp/types/unstable/nes_edit_suggestion.rb +67 -0
  65. data/lib/acp/types/unstable/nes_event_capabilities.rb +40 -0
  66. data/lib/acp/types/unstable/nes_excerpt.rb +47 -0
  67. data/lib/acp/types/unstable/nes_jump_capabilities.rb +27 -0
  68. data/lib/acp/types/unstable/nes_jump_suggestion.rb +51 -0
  69. data/lib/acp/types/unstable/nes_open_file.rb +64 -0
  70. data/lib/acp/types/unstable/nes_open_files_capabilities.rb +27 -0
  71. data/lib/acp/types/unstable/nes_recent_file.rb +47 -0
  72. data/lib/acp/types/unstable/nes_recent_files_capabilities.rb +35 -0
  73. data/lib/acp/types/unstable/nes_reject_reason.rb +11 -0
  74. data/lib/acp/types/unstable/nes_related_snippet.rb +40 -0
  75. data/lib/acp/types/unstable/nes_related_snippets_capabilities.rb +27 -0
  76. data/lib/acp/types/unstable/nes_rename_capabilities.rb +27 -0
  77. data/lib/acp/types/unstable/nes_rename_suggestion.rb +57 -0
  78. data/lib/acp/types/unstable/nes_repository.rb +47 -0
  79. data/lib/acp/types/unstable/nes_search_and_replace_capabilities.rb +27 -0
  80. data/lib/acp/types/unstable/nes_search_and_replace_suggestion.rb +66 -0
  81. data/lib/acp/types/unstable/nes_suggest_context.rb +95 -0
  82. data/lib/acp/types/unstable/nes_suggestion/edit.rb +68 -0
  83. data/lib/acp/types/unstable/nes_suggestion/jump.rb +51 -0
  84. data/lib/acp/types/unstable/nes_suggestion/rename.rb +64 -0
  85. data/lib/acp/types/unstable/nes_suggestion/searchandreplace.rb +67 -0
  86. data/lib/acp/types/unstable/nes_suggestion.rb +25 -0
  87. data/lib/acp/types/unstable/nes_text_edit.rb +41 -0
  88. data/lib/acp/types/unstable/nes_trigger_kind.rb +10 -0
  89. data/lib/acp/types/unstable/nes_user_action.rb +63 -0
  90. data/lib/acp/types/unstable/nes_user_actions_capabilities.rb +35 -0
  91. data/lib/acp/types/unstable/notice.rb +47 -0
  92. data/lib/acp/types/unstable/notice_severity.rb +10 -0
  93. data/lib/acp/types/unstable/plan_capabilities.rb +27 -0
  94. data/lib/acp/types/unstable/plan_file.rb +41 -0
  95. data/lib/acp/types/unstable/plan_items.rb +44 -0
  96. data/lib/acp/types/unstable/plan_markdown.rb +41 -0
  97. data/lib/acp/types/unstable/plan_removed.rb +32 -0
  98. data/lib/acp/types/unstable/plan_update.rb +39 -0
  99. data/lib/acp/types/unstable/plan_update_content/file.rb +41 -0
  100. data/lib/acp/types/unstable/plan_update_content/items.rb +44 -0
  101. data/lib/acp/types/unstable/plan_update_content/markdown.rb +41 -0
  102. data/lib/acp/types/unstable/plan_update_content.rb +23 -0
  103. data/lib/acp/types/unstable/position.rb +41 -0
  104. data/lib/acp/types/unstable/position_encoding_kind.rb +10 -0
  105. data/lib/acp/types/unstable/prompt_response.rb +45 -0
  106. data/lib/acp/types/unstable/provider_current_config.rb +41 -0
  107. data/lib/acp/types/unstable/provider_info.rb +64 -0
  108. data/lib/acp/types/unstable/providers_capabilities.rb +27 -0
  109. data/lib/acp/types/unstable/range.rb +41 -0
  110. data/lib/acp/types/unstable/reject_nes_notification.rb +52 -0
  111. data/lib/acp/types/unstable/requires_action_state_update.rb +27 -0
  112. data/lib/acp/types/unstable/running_state_update.rb +27 -0
  113. data/lib/acp/types/unstable/session_cancel_capabilities.rb +27 -0
  114. data/lib/acp/types/unstable/session_capabilities.rb +93 -0
  115. data/lib/acp/types/unstable/session_fork_capabilities.rb +27 -0
  116. data/lib/acp/types/unstable/session_message.rb +62 -0
  117. data/lib/acp/types/unstable/session_message_chunk.rb +59 -0
  118. data/lib/acp/types/unstable/session_update/agent_message_chunk.rb +46 -0
  119. data/lib/acp/types/unstable/session_update/agent_thought_chunk.rb +46 -0
  120. data/lib/acp/types/unstable/session_update/available_commands_update.rb +42 -0
  121. data/lib/acp/types/unstable/session_update/compaction_summary_chunk.rb +46 -0
  122. data/lib/acp/types/unstable/session_update/compaction_update.rb +63 -0
  123. data/lib/acp/types/unstable/session_update/config_option_update.rb +42 -0
  124. data/lib/acp/types/unstable/session_update/current_mode_update.rb +35 -0
  125. data/lib/acp/types/unstable/session_update/notice.rb +53 -0
  126. data/lib/acp/types/unstable/session_update/plan.rb +35 -0
  127. data/lib/acp/types/unstable/session_update/plan_removed.rb +32 -0
  128. data/lib/acp/types/unstable/session_update/plan_update.rb +39 -0
  129. data/lib/acp/types/unstable/session_update/session_info_update.rb +49 -0
  130. data/lib/acp/types/unstable/session_update/session_message.rb +63 -0
  131. data/lib/acp/types/unstable/session_update/session_message_chunk.rb +60 -0
  132. data/lib/acp/types/unstable/session_update/subagent_update.rb +72 -0
  133. data/lib/acp/types/unstable/session_update/tool_call.rb +122 -0
  134. data/lib/acp/types/unstable/session_update/tool_call_update.rb +122 -0
  135. data/lib/acp/types/unstable/session_update/usage_update.rb +47 -0
  136. data/lib/acp/types/unstable/session_update/user_message_chunk.rb +46 -0
  137. data/lib/acp/types/unstable/session_update.rb +55 -0
  138. data/lib/acp/types/unstable/set_provider_request.rb +62 -0
  139. data/lib/acp/types/unstable/set_provider_response.rb +27 -0
  140. data/lib/acp/types/unstable/start_nes_request.rb +64 -0
  141. data/lib/acp/types/unstable/start_nes_response.rb +35 -0
  142. data/lib/acp/types/unstable/state_update/idle.rb +46 -0
  143. data/lib/acp/types/unstable/state_update/requires_action.rb +27 -0
  144. data/lib/acp/types/unstable/state_update/running.rb +27 -0
  145. data/lib/acp/types/unstable/state_update/unknown.rb +27 -0
  146. data/lib/acp/types/unstable/state_update.rb +25 -0
  147. data/lib/acp/types/unstable/subagent_capabilities.rb +27 -0
  148. data/lib/acp/types/unstable/subagent_session_capabilities.rb +40 -0
  149. data/lib/acp/types/unstable/subagent_update.rb +71 -0
  150. data/lib/acp/types/unstable/suggest_nes_request.rb +93 -0
  151. data/lib/acp/types/unstable/suggest_nes_response.rb +35 -0
  152. data/lib/acp/types/unstable/text_document_content_change_event.rb +41 -0
  153. data/lib/acp/types/unstable/text_document_sync_kind.rb +9 -0
  154. data/lib/acp/types/unstable/unknown_state_update.rb +27 -0
  155. data/lib/acp/types/unstable/usage.rb +91 -0
  156. data/lib/acp/types/unstable/workspace_folder.rb +41 -0
  157. data/lib/acp/types/unstable.rb +155 -0
  158. data/lib/acp/version.rb +1 -1
  159. data/sig/generated/acp/agent_connection/client.rbs +20 -1
  160. data/sig/generated/acp/agent_connection/optional_method/disable_provider.rbs +13 -0
  161. data/sig/generated/acp/agent_connection/optional_method/fork_session.rbs +13 -0
  162. data/sig/generated/acp/agent_connection/optional_method/list_providers.rbs +13 -0
  163. data/sig/generated/acp/agent_connection/optional_method/set_provider.rbs +13 -0
  164. data/sig/generated/acp/agent_connection.rbs +29 -0
  165. data/sig/generated/acp/client_connection.rbs +57 -1
  166. data/sig/generated/acp/request_error.rbs +8 -0
  167. data/sig/generated/acp/transport/cancellation.rbs +32 -0
  168. data/sig/generated/acp/transport/stdio.rbs +42 -0
  169. data/sig/generated/acp/types/unstable/accept_nes_notification.rbs +25 -0
  170. data/sig/generated/acp/types/unstable/agent_capabilities.rbs +49 -0
  171. data/sig/generated/acp/types/unstable/client_capabilities.rbs +53 -0
  172. data/sig/generated/acp/types/unstable/client_nes_capabilities.rbs +29 -0
  173. data/sig/generated/acp/types/unstable/client_session_capabilities.rbs +29 -0
  174. data/sig/generated/acp/types/unstable/close_nes_request.rbs +21 -0
  175. data/sig/generated/acp/types/unstable/close_nes_response.rbs +17 -0
  176. data/sig/generated/acp/types/unstable/compaction_status.rbs +11 -0
  177. data/sig/generated/acp/types/unstable/compaction_summary_chunk.rbs +25 -0
  178. data/sig/generated/acp/types/unstable/compaction_update.rbs +33 -0
  179. data/sig/generated/acp/types/unstable/did_change_document_notification.rbs +33 -0
  180. data/sig/generated/acp/types/unstable/did_close_document_notification.rbs +25 -0
  181. data/sig/generated/acp/types/unstable/did_focus_document_notification.rbs +37 -0
  182. data/sig/generated/acp/types/unstable/did_open_document_notification.rbs +37 -0
  183. data/sig/generated/acp/types/unstable/did_save_document_notification.rbs +25 -0
  184. data/sig/generated/acp/types/unstable/disable_provider_request.rbs +21 -0
  185. data/sig/generated/acp/types/unstable/disable_provider_response.rbs +17 -0
  186. data/sig/generated/acp/types/unstable/fork_session_request.rbs +33 -0
  187. data/sig/generated/acp/types/unstable/fork_session_response.rbs +29 -0
  188. data/sig/generated/acp/types/unstable/idle_state_update.rbs +25 -0
  189. data/sig/generated/acp/types/unstable/initialize_response.rbs +33 -0
  190. data/sig/generated/acp/types/unstable/list_providers_request.rbs +17 -0
  191. data/sig/generated/acp/types/unstable/list_providers_response.rbs +21 -0
  192. data/sig/generated/acp/types/unstable/llm_protocol.rbs +13 -0
  193. data/sig/generated/acp/types/unstable/mcp_capabilities.rbs +29 -0
  194. data/sig/generated/acp/types/unstable/mcp_error.rbs +25 -0
  195. data/sig/generated/acp/types/unstable/mcp_server/acp.rbs +25 -0
  196. data/sig/generated/acp/types/unstable/mcp_server/http.rbs +29 -0
  197. data/sig/generated/acp/types/unstable/mcp_server/sse.rbs +29 -0
  198. data/sig/generated/acp/types/unstable/mcp_server.rbs +9 -0
  199. data/sig/generated/acp/types/unstable/mcp_server_acp.rbs +25 -0
  200. data/sig/generated/acp/types/unstable/message_mcp_notification.rbs +33 -0
  201. data/sig/generated/acp/types/unstable/message_mcp_request.rbs +33 -0
  202. data/sig/generated/acp/types/unstable/message_mcp_response/error.rbs +21 -0
  203. data/sig/generated/acp/types/unstable/message_mcp_response/result.rbs +21 -0
  204. data/sig/generated/acp/types/unstable/message_mcp_response.rbs +9 -0
  205. data/sig/generated/acp/types/unstable/nes_capabilities.rbs +25 -0
  206. data/sig/generated/acp/types/unstable/nes_context_capabilities.rbs +41 -0
  207. data/sig/generated/acp/types/unstable/nes_diagnostic.rbs +33 -0
  208. data/sig/generated/acp/types/unstable/nes_diagnostic_severity.rbs +11 -0
  209. data/sig/generated/acp/types/unstable/nes_diagnostics_capabilities.rbs +17 -0
  210. data/sig/generated/acp/types/unstable/nes_document_did_change_capabilities.rbs +21 -0
  211. data/sig/generated/acp/types/unstable/nes_document_did_close_capabilities.rbs +17 -0
  212. data/sig/generated/acp/types/unstable/nes_document_did_focus_capabilities.rbs +17 -0
  213. data/sig/generated/acp/types/unstable/nes_document_did_open_capabilities.rbs +17 -0
  214. data/sig/generated/acp/types/unstable/nes_document_did_save_capabilities.rbs +17 -0
  215. data/sig/generated/acp/types/unstable/nes_document_event_capabilities.rbs +37 -0
  216. data/sig/generated/acp/types/unstable/nes_edit_history_capabilities.rbs +21 -0
  217. data/sig/generated/acp/types/unstable/nes_edit_history_entry.rbs +25 -0
  218. data/sig/generated/acp/types/unstable/nes_edit_suggestion.rbs +33 -0
  219. data/sig/generated/acp/types/unstable/nes_event_capabilities.rbs +21 -0
  220. data/sig/generated/acp/types/unstable/nes_excerpt.rbs +29 -0
  221. data/sig/generated/acp/types/unstable/nes_jump_capabilities.rbs +17 -0
  222. data/sig/generated/acp/types/unstable/nes_jump_suggestion.rbs +29 -0
  223. data/sig/generated/acp/types/unstable/nes_open_file.rbs +33 -0
  224. data/sig/generated/acp/types/unstable/nes_open_files_capabilities.rbs +17 -0
  225. data/sig/generated/acp/types/unstable/nes_recent_file.rbs +29 -0
  226. data/sig/generated/acp/types/unstable/nes_recent_files_capabilities.rbs +21 -0
  227. data/sig/generated/acp/types/unstable/nes_reject_reason.rbs +11 -0
  228. data/sig/generated/acp/types/unstable/nes_related_snippet.rbs +25 -0
  229. data/sig/generated/acp/types/unstable/nes_related_snippets_capabilities.rbs +17 -0
  230. data/sig/generated/acp/types/unstable/nes_rename_capabilities.rbs +17 -0
  231. data/sig/generated/acp/types/unstable/nes_rename_suggestion.rbs +33 -0
  232. data/sig/generated/acp/types/unstable/nes_repository.rbs +29 -0
  233. data/sig/generated/acp/types/unstable/nes_search_and_replace_capabilities.rbs +17 -0
  234. data/sig/generated/acp/types/unstable/nes_search_and_replace_suggestion.rbs +37 -0
  235. data/sig/generated/acp/types/unstable/nes_suggest_context.rbs +41 -0
  236. data/sig/generated/acp/types/unstable/nes_suggestion/edit.rbs +33 -0
  237. data/sig/generated/acp/types/unstable/nes_suggestion/jump.rbs +29 -0
  238. data/sig/generated/acp/types/unstable/nes_suggestion/rename.rbs +33 -0
  239. data/sig/generated/acp/types/unstable/nes_suggestion/searchandreplace.rbs +37 -0
  240. data/sig/generated/acp/types/unstable/nes_suggestion.rbs +9 -0
  241. data/sig/generated/acp/types/unstable/nes_text_edit.rbs +25 -0
  242. data/sig/generated/acp/types/unstable/nes_trigger_kind.rbs +9 -0
  243. data/sig/generated/acp/types/unstable/nes_user_action.rbs +33 -0
  244. data/sig/generated/acp/types/unstable/nes_user_actions_capabilities.rbs +21 -0
  245. data/sig/generated/acp/types/unstable/notice.rbs +29 -0
  246. data/sig/generated/acp/types/unstable/notice_severity.rbs +9 -0
  247. data/sig/generated/acp/types/unstable/plan_capabilities.rbs +17 -0
  248. data/sig/generated/acp/types/unstable/plan_file.rbs +25 -0
  249. data/sig/generated/acp/types/unstable/plan_items.rbs +25 -0
  250. data/sig/generated/acp/types/unstable/plan_markdown.rbs +25 -0
  251. data/sig/generated/acp/types/unstable/plan_removed.rbs +21 -0
  252. data/sig/generated/acp/types/unstable/plan_update.rbs +21 -0
  253. data/sig/generated/acp/types/unstable/plan_update_content/file.rbs +25 -0
  254. data/sig/generated/acp/types/unstable/plan_update_content/items.rbs +25 -0
  255. data/sig/generated/acp/types/unstable/plan_update_content/markdown.rbs +25 -0
  256. data/sig/generated/acp/types/unstable/plan_update_content.rbs +9 -0
  257. data/sig/generated/acp/types/unstable/position.rbs +25 -0
  258. data/sig/generated/acp/types/unstable/position_encoding_kind.rbs +9 -0
  259. data/sig/generated/acp/types/unstable/prompt_response.rbs +25 -0
  260. data/sig/generated/acp/types/unstable/provider_current_config.rbs +25 -0
  261. data/sig/generated/acp/types/unstable/provider_info.rbs +33 -0
  262. data/sig/generated/acp/types/unstable/providers_capabilities.rbs +17 -0
  263. data/sig/generated/acp/types/unstable/range.rbs +25 -0
  264. data/sig/generated/acp/types/unstable/reject_nes_notification.rbs +29 -0
  265. data/sig/generated/acp/types/unstable/requires_action_state_update.rbs +17 -0
  266. data/sig/generated/acp/types/unstable/running_state_update.rbs +17 -0
  267. data/sig/generated/acp/types/unstable/session_cancel_capabilities.rbs +17 -0
  268. data/sig/generated/acp/types/unstable/session_capabilities.rbs +41 -0
  269. data/sig/generated/acp/types/unstable/session_fork_capabilities.rbs +17 -0
  270. data/sig/generated/acp/types/unstable/session_message.rbs +33 -0
  271. data/sig/generated/acp/types/unstable/session_message_chunk.rbs +33 -0
  272. data/sig/generated/acp/types/unstable/session_update/agent_message_chunk.rbs +25 -0
  273. data/sig/generated/acp/types/unstable/session_update/agent_thought_chunk.rbs +25 -0
  274. data/sig/generated/acp/types/unstable/session_update/available_commands_update.rbs +21 -0
  275. data/sig/generated/acp/types/unstable/session_update/compaction_summary_chunk.rbs +25 -0
  276. data/sig/generated/acp/types/unstable/session_update/compaction_update.rbs +33 -0
  277. data/sig/generated/acp/types/unstable/session_update/config_option_update.rbs +21 -0
  278. data/sig/generated/acp/types/unstable/session_update/current_mode_update.rbs +21 -0
  279. data/sig/generated/acp/types/unstable/session_update/notice.rbs +29 -0
  280. data/sig/generated/acp/types/unstable/session_update/plan.rbs +21 -0
  281. data/sig/generated/acp/types/unstable/session_update/plan_removed.rbs +21 -0
  282. data/sig/generated/acp/types/unstable/session_update/plan_update.rbs +21 -0
  283. data/sig/generated/acp/types/unstable/session_update/session_info_update.rbs +25 -0
  284. data/sig/generated/acp/types/unstable/session_update/session_message.rbs +33 -0
  285. data/sig/generated/acp/types/unstable/session_update/session_message_chunk.rbs +33 -0
  286. data/sig/generated/acp/types/unstable/session_update/subagent_update.rbs +37 -0
  287. data/sig/generated/acp/types/unstable/session_update/tool_call.rbs +53 -0
  288. data/sig/generated/acp/types/unstable/session_update/tool_call_update.rbs +53 -0
  289. data/sig/generated/acp/types/unstable/session_update/usage_update.rbs +29 -0
  290. data/sig/generated/acp/types/unstable/session_update/user_message_chunk.rbs +25 -0
  291. data/sig/generated/acp/types/unstable/session_update.rbs +9 -0
  292. data/sig/generated/acp/types/unstable/set_provider_request.rbs +33 -0
  293. data/sig/generated/acp/types/unstable/set_provider_response.rbs +17 -0
  294. data/sig/generated/acp/types/unstable/start_nes_request.rbs +29 -0
  295. data/sig/generated/acp/types/unstable/start_nes_response.rbs +21 -0
  296. data/sig/generated/acp/types/unstable/state_update/idle.rbs +25 -0
  297. data/sig/generated/acp/types/unstable/state_update/requires_action.rbs +17 -0
  298. data/sig/generated/acp/types/unstable/state_update/running.rbs +17 -0
  299. data/sig/generated/acp/types/unstable/state_update/unknown.rbs +17 -0
  300. data/sig/generated/acp/types/unstable/state_update.rbs +9 -0
  301. data/sig/generated/acp/types/unstable/subagent_capabilities.rbs +17 -0
  302. data/sig/generated/acp/types/unstable/subagent_session_capabilities.rbs +21 -0
  303. data/sig/generated/acp/types/unstable/subagent_update.rbs +37 -0
  304. data/sig/generated/acp/types/unstable/suggest_nes_request.rbs +45 -0
  305. data/sig/generated/acp/types/unstable/suggest_nes_response.rbs +21 -0
  306. data/sig/generated/acp/types/unstable/text_document_content_change_event.rbs +25 -0
  307. data/sig/generated/acp/types/unstable/text_document_sync_kind.rbs +7 -0
  308. data/sig/generated/acp/types/unstable/unknown_state_update.rbs +17 -0
  309. data/sig/generated/acp/types/unstable/usage.rbs +41 -0
  310. data/sig/generated/acp/types/unstable/workspace_folder.rbs +25 -0
  311. data/sig/generated/acp/types/unstable.rbs +4 -0
  312. data/sig/manual/acp/agent_connection/optional_method/disable_provider.rbs +6 -0
  313. data/sig/manual/acp/agent_connection/optional_method/fork_session.rbs +6 -0
  314. data/sig/manual/acp/agent_connection/optional_method/list_providers.rbs +6 -0
  315. data/sig/manual/acp/agent_connection/optional_method/set_provider.rbs +6 -0
  316. data/sig/manual/acp/agent_connection.rbs +30 -0
  317. metadata +302 -2
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: e0b8b181c4cbd41a411868aecdf9f34d0356b490526c446a962287a5a3ae01cc
4
- data.tar.gz: 1376e6d42bc4369de51a645ed423a563e7aa9b35c4c49b060c77a983cb2c603c
3
+ metadata.gz: e0d983006184ce11ac51e9361062aa6878c01d348645f41fe226423db5d72e8b
4
+ data.tar.gz: 0d50ff69cb0c6cc743d75dcd3fd943a9ddeaa4469a8f1e8d6e54be811e3c3b39
5
5
  SHA512:
6
- metadata.gz: 6c5d6271be17f17e203c178664ad6de2234c4b1a7b1857663c1ebb20bdbe1fa3c282db70ac6fe3c3e6b9647dfaadbe761bd018972943393a0cb3dec3d10bb2a3
7
- data.tar.gz: 9ca3cae203c38496096dd020403054daaa4cb6797183c95b04a75c045f4a8aff89754a07af0fd74eb4c2a45f48dbc6d7c64352faf572000f118130d473283f0b
6
+ metadata.gz: 4296fbbf1fa985cb3913f2019528c06658fd8fcac285a238abf79073d2d5eb13d081e69da57f05476569967189f732c804f6dc851e291709c0b4052572b17d9f
7
+ data.tar.gz: 8f0a5bedfa32615f867111297c4fc593f2b567fd77a55e76dda4eabe57bd8494e225de93b470f1ac294f4b99a57797fda4d7f69704693fb8cd322e65fe650f59
data/CHANGELOG.md CHANGED
@@ -1,5 +1,24 @@
1
1
  # Changelog
2
2
 
3
+ ## [0.10.0](https://github.com/bottrall/acp-sdk/compare/v0.9.0...v0.10.0) (2026-10-07)
4
+
5
+
6
+ ### Features
7
+
8
+ * MCP-over-ACP mcp/message (unstable) ([#89](https://github.com/bottrall/acp-sdk/issues/89)) ([3d6d30c](https://github.com/bottrall/acp-sdk/commit/3d6d30c6e31e88dd0fe78e08a5bc4308948cacdd))
9
+ * providers/list, providers/set and providers/disable (unstable) ([#88](https://github.com/bottrall/acp-sdk/issues/88)) ([eac16f7](https://github.com/bottrall/acp-sdk/commit/eac16f78809ea5a677835c6fe064063dcb64c162)), closes [#50](https://github.com/bottrall/acp-sdk/issues/50)
10
+ * request cancellation on the agent and client connections ([#85](https://github.com/bottrall/acp-sdk/issues/85)) ([58ae0ab](https://github.com/bottrall/acp-sdk/commit/58ae0abe6387e2887482ec14e361cfac91cd54ea))
11
+ * session/fork (unstable) ([#86](https://github.com/bottrall/acp-sdk/issues/86)) ([9868cb6](https://github.com/bottrall/acp-sdk/commit/9868cb666a8111c414bf3b041167d6aa022e1e95)), closes [#49](https://github.com/bottrall/acp-sdk/issues/49)
12
+ * support all currently supported Ruby versions ([#90](https://github.com/bottrall/acp-sdk/issues/90)) ([343d07b](https://github.com/bottrall/acp-sdk/commit/343d07b9e6d98b1c24201c5eef8b250858c0626b)), closes [#73](https://github.com/bottrall/acp-sdk/issues/73)
13
+
14
+ ## [0.9.0](https://github.com/bottrall/acp-sdk/compare/v0.8.0...v0.9.0) (2026-10-07)
15
+
16
+
17
+ ### Features
18
+
19
+ * $/cancel_request in ACP::Transport::Stdio ([#82](https://github.com/bottrall/acp-sdk/issues/82)) ([8679883](https://github.com/bottrall/acp-sdk/commit/86798837ce9e35949c7cc3770e52f3191d81bc6f))
20
+ * opt-in types for the unstable schema ([#83](https://github.com/bottrall/acp-sdk/issues/83)) ([a49a5cc](https://github.com/bottrall/acp-sdk/commit/a49a5ccba84e599fc8fae95fe2336a4eb3169835))
21
+
3
22
  ## [0.8.0](https://github.com/bottrall/acp-sdk/compare/v0.7.0...v0.8.0) (2026-10-06)
4
23
 
5
24
 
data/README.md CHANGED
@@ -11,7 +11,7 @@ Every ACP request, response and notification is a generated Ruby class under `AC
11
11
 
12
12
  ## Installation
13
13
 
14
- Requires Ruby 4.0 or later.
14
+ Requires Ruby 3.4 or later (CI tests 3.4 and 4.0).
15
15
 
16
16
  Add the gem to your Gemfile:
17
17
 
@@ -48,11 +48,12 @@ connection.start.join
48
48
 
49
49
  The agent's contract:
50
50
 
51
- - It defines `new_session`, `prompt` and `cancel`. `load_session`, `list_sessions`, `resume_session`, `close_session` and `delete_session` are required only when `capabilities` advertises `load_session` or `session_capabilities.list`, `.resume`, `.close` or `.delete`, and `authenticate` only when `auth_methods` is non-empty, and `logout` only when `capabilities` advertises `auth.logout`; `start` raises `ArgumentError` when one is advertised but missing, and a method that is not advertised is answered with `-32601`. `change_session_mode` and `change_session_config_option` are optional, since modes and config options are offered per session rather than in `initialize`; `session/set_mode` and `session/set_config_option` are answered with `-32601` when the agent does not define them. `session_created`, if defined, runs right after the `session/new` reply is sent.
51
+ - It defines `new_session`, `prompt` and `cancel`. `load_session`, `list_sessions`, `resume_session`, `close_session` and `delete_session` are required only when `capabilities` advertises `load_session` or `session_capabilities.list`, `.resume`, `.close` or `.delete`, and `authenticate` only when `auth_methods` is non-empty, and `logout` only when `capabilities` advertises `auth.logout`; `start` raises `ArgumentError` when one is advertised but missing, and a method that is not advertised is answered with `-32601`. `change_session_mode` and `change_session_config_option` are optional, since modes and config options are offered per session rather than in `initialize`; `session/set_mode` and `session/set_config_option` are answered with `-32601` when the agent does not define them. `session_created`, if defined, runs right after the `session/new` reply is sent. `fork_session` is the same shape but unstable: `session/fork` routes only when the caller has opted into the unstable types with `require 'acp/types/unstable'` and `capabilities` advertises `session_capabilities.fork`, and is answered with `-32601` otherwise. `list_providers`, `set_provider` and `disable_provider` are the same shape but unstable too: `providers/list`, `providers/set` and `providers/disable` route only when the caller has opted into the unstable types with `require 'acp/types/unstable'` and `capabilities` advertises `providers`, and are answered with `-32601` otherwise.
52
52
  - Each request method returns its response type or an `ACP::RequestError`, which is sent as the error reply. Params that do not match the schema are answered with `-32602` before the agent sees them, with `data` naming the failing path, e.g. `{ errors: ['sessionId: expected String, got Integer'] }`.
53
53
  - A handler that raises is answered with `-32603` and the message `Internal error`. Nothing from the exception reaches the peer — its class and message are logged to the transport's logger instead, since messages can interpolate paths, queries or credentials. Return an `ACP::RequestError` from the handler when the peer should see a real error message.
54
- - `cancel` runs on the transport's reader thread, so it must return quickly: set a flag and let the prompt notice it.
54
+ - `cancel` runs on the transport's reader thread, so it must return quickly: set a flag and let the prompt notice it, and call `client.cancel_requests` to cancel the connection's outstanding requests to the client — a blocked `request_permission` or `wait_for_terminal_exit`, say. That sends `$/cancel_request` for each one, and the blocked call returns once the client answers it, with its response or the `-32800` error.
55
55
  - After a cancel, the agent must itself end the turn with `stopReason: cancelled`. `ACP::AgentConnection` does not enforce it.
56
+ - `client.cancelled?` is true in a handler when the client cancelled the request it is serving with `$/cancel_request`. The handler can end early by returning `ACP::RequestError.request_cancelled`, which is answered as `-32800`, or return a normal response, which is sent as-is — the transport sends exactly one response either way.
56
57
  - `client.capabilities` is `nil` until the client sends `initialize`.
57
58
  - `client.read_text_file` and `client.write_text_file` take an `ACP::Types::ReadTextFileRequest` or `ACP::Types::WriteTextFileRequest` and return the response type or an `ACP::RequestError`. Unless `client.capabilities` advertises `fs.read_text_file` or `fs.write_text_file`, they return `-32601` without sending anything to the client.
58
59
  - `client.create_terminal`, `client.terminal_output`, `client.wait_for_terminal_exit`, `client.kill_terminal` and `client.release_terminal` take the matching `ACP::Types` request (`CreateTerminalRequest`, `TerminalOutputRequest`, `WaitForTerminalExitRequest`, `KillTerminalRequest`, `ReleaseTerminalRequest`) and return its response type or an `ACP::RequestError`. Unless `client.capabilities` advertises `terminal`, they return `-32601` without sending anything to the client. The agent must release every terminal it creates.
@@ -64,7 +65,7 @@ Extensions are `_`-prefixed methods outside the spec. `AgentConnection` takes `e
64
65
 
65
66
  ## Driving an agent
66
67
 
67
- `ACP::ClientConnection` is the other side of the same transport. Each method takes the request's generated `ACP::Types` object and returns its response type or the agent's `ACP::RequestError`. `connect` sends `initialize`, since Ruby reserves that name for the constructor, and `authenticate` and `logout` send their namesake methods. `session_set_mode` sends `session/set_mode`, and `session_set_config_option` sends `session/set_config_option` and returns the session's complete option list, which replaces any state held from an earlier reply. A handler that raises is answered with `-32603` and the message `Internal error`, and the exception is only logged to the transport's logger.
68
+ `ACP::ClientConnection` is the other side of the same transport. Each method takes the request's generated `ACP::Types` object and returns its response type or the agent's `ACP::RequestError`. `connect` sends `initialize`, since Ruby reserves that name for the constructor, and `authenticate` and `logout` send their namesake methods. `session_set_mode` sends `session/set_mode`, and `session_set_config_option` sends `session/set_config_option` and returns the session's complete option list, which replaces any state held from an earlier reply. `session_fork` sends `session/fork`; it is unstable, so the request and response types exist only under `ACP::Types::Unstable` and calling it before `require 'acp/types/unstable'` raises `NameError`. `providers_list`, `providers_set` and `providers_disable` send `providers/list`, `providers/set` and `providers/disable` on the same unstable terms. A handler that raises is answered with `-32603` and the message `Internal error`, and the exception is only logged to the transport's logger.
68
69
 
69
70
  `ACP::AgentProcess.spawn` starts a command with args, env and cwd, wires its stdio to a new `ACP::ClientConnection` and starts it. The block receives the connection and the process handle (`pid`, plus `stderr` when captured), and the child is terminated — after its stdin is closed, with TERM and then KILL if it will not exit — when the block exits, so a raised block cannot leak the process. Without a block, `spawn` returns the process and calling `terminate` is yours. `stderr` is `:inherit` by default, so the child writes to your stderr; pass `:capture` to collect it on the process instead.
70
71
 
@@ -105,12 +106,25 @@ connection = ACP::ClientConnection.new(
105
106
 
106
107
  - `session_prompt` and `session_load` yield the session's updates on the calling thread as they arrive and return once the agent replies. Updates outside those calls, such as the available commands after `session/new`, go to `updates`, which runs on the reader thread and must return quickly. `session_resume` does not take a block: the agent must not replay history on resume, so unlike `session_load` there is nothing to stream. `session_close` and `session_delete` end the session, discarding it on the agent, and return once the agent replies.
107
108
  - `permission` answers `session/request_permission` with an `ACP::Types::RequestPermissionResponse` or an `ACP::RequestError`. It runs on its own thread, so it may block while the user decides, and `session_cancel` can be sent meanwhile.
109
+ - The agent may cancel any outstanding request with `$/cancel_request`: a handler that can end early checks `connection.cancelled?` — close over the connection when constructing it — and returns `ACP::RequestError.request_cancelled`, which is answered as `-32800`. The permission handler still sees the cancel on its own thread. Whatever the handler returns, the transport sends exactly one response.
108
110
  - `read_text_file` and `write_text_file` answer the agent's `fs/read_text_file` and `fs/write_text_file` with an `ACP::Types::ReadTextFileResponse`, an `ACP::Types::WriteTextFileResponse` or an `ACP::RequestError`. They are routed by the `fs` capabilities `connect` sends: a method the client does not advertise is answered with `-32601` without reaching the handler, and `connect` raises `ArgumentError` — before sending `initialize` — when the request advertises a capability that has no handler.
109
111
  - `create_terminal`, `terminal_output`, `wait_for_terminal_exit`, `kill_terminal` and `release_terminal` answer the agent's `terminal/create`, `terminal/output`, `terminal/wait_for_exit`, `terminal/kill` and `terminal/release` with the matching `ACP::Types` response or an `ACP::RequestError`. They are routed by the `terminal` capability `connect` sends, the same way as `fs`: a method the client does not advertise is answered with `-32601` without reaching the handler, and `connect` raises `ArgumentError` — before sending `initialize` — when the request advertises `terminal` with any of the five missing. The transport serves each request on its own thread, so `wait_for_terminal_exit` may block without holding up the others.
110
112
  - `elicitation` answers the agent's `elicitation/create` with an `ACP::Types::CreateElicitationResponse` or an `ACP::RequestError`, serving both form and url mode, and `complete_elicitation` receives the agent's `elicitation/complete` notification. They are routed by the `elicitation` capabilities `connect` sends: a request whose mode the client does not advertise is answered with `-32602` without reaching the handler, and `connect` raises `ArgumentError` — before sending `initialize` — when the request advertises `elicitation.form` or `elicitation.url` with no `elicitation` handler. The client tracks the elicitation ids of the url-mode requests it serves, so a completion for an unknown or already-completed id is ignored. `complete_elicitation` runs on the reader thread and must return quickly; it may be omitted, in which case completions are only tracked.
111
113
 
112
114
  Extensions work the same way in both directions: `extension_requests` and `extension_notifications` are hashes keyed by the raw `_`-prefixed wire name, `ext_request` sends one to the agent and returns its result as-is, and `ext_notify` sends an extension notification. A handler name without the `_` prefix raises `ArgumentError`, and an extension request nothing answers gets `-32601`.
113
115
 
116
+ ## Unstable types
117
+
118
+ Some protocol features are not part of the spec yet, so their schema lives in a separate unstable file: draft session updates (`plan_update`, `plan_removed`, `notice`, `compaction_update`, `compaction_summary_chunk`, `subagent_update`, `session_message`, `session_message_chunk`), `PromptResponse#usage`, the NE (next edit) and provider methods, and the matching capabilities. They are generated under `ACP::Types::Unstable` and load only when you opt in:
119
+
120
+ ```ruby
121
+ require 'acp/types/unstable'
122
+ ```
123
+
124
+ Until then, referencing `ACP::Types::Unstable` is a `NameError`, and the stable `ACP::Types` namespace is byte-identical either way: types the unstable schema shares with the stable one resolve to the same generated classes, so opting in adds names without changing or duplicating any stable one.
125
+
126
+ These types are **unstable**: they are not part of the ACP spec, agents and clients must not rely on the peer supporting them, and any of them may change or disappear between releases without a major version bump. When a stable schema absorbs one of them, the unstable twin goes away and the stable name stays.
127
+
114
128
  ## Contributing
115
129
 
116
130
  Bug reports and pull requests are welcome on [GitHub](https://github.com/bottrall/acp-sdk). See [CONTRIBUTING.md](CONTRIBUTING.md) for setup and workflow. Everyone interacting in the project is expected to follow the [code of conduct](CODE_OF_CONDUCT.md).
@@ -2,14 +2,20 @@
2
2
 
3
3
  class ACP::AgentConnection::Client
4
4
  # @rbs @peer: ACP::AgentConnection::_Peer
5
+ # @rbs @mcp_advertised: bool
5
6
 
6
7
  # @dynamic capabilities, capabilities=
7
8
  attr_accessor :capabilities #: ACP::Types::ClientCapabilities?
8
9
 
9
10
  # @rbs peer: ACP::AgentConnection::_Peer
11
+ # @rbs mcp_advertised: bool
10
12
  # @rbs return: void
11
- def initialize(peer:)
13
+ def initialize(peer:, mcp_advertised: false)
12
14
  @peer = peer
15
+ # The agent's own initialize advertisement of mcpCapabilities.acp, which
16
+ # licenses this side's mcp/message sends; the client's advertisement is
17
+ # the separate capabilities accessor.
18
+ @mcp_advertised = mcp_advertised
13
19
  @capabilities = nil
14
20
  end
15
21
 
@@ -36,6 +42,18 @@ class ACP::AgentConnection::Client
36
42
  call('session/request_permission', ACP::Types::RequestPermissionResponse, request)
37
43
  end
38
44
 
45
+ # @rbs return: void
46
+ def cancel_requests
47
+ @peer.cancel_requests
48
+ end
49
+
50
+ # Always false off a handler thread.
51
+ #
52
+ # @rbs return: bool
53
+ def cancelled?
54
+ !!@peer.cancellation&.cancelled?
55
+ end
56
+
39
57
  # @rbs request: ACP::Types::ReadTextFileRequest
40
58
  # @rbs return: ACP::Types::ReadTextFileResponse | ACP::RequestError
41
59
  def read_text_file(request)
@@ -105,6 +123,18 @@ class ACP::AgentConnection::Client
105
123
  @peer.notify('elicitation/complete', notification.to_h)
106
124
  end
107
125
 
126
+ # Sends one inner MCP operation to the client's declared ACP MCP server. The
127
+ # reply's result may be null, and a reply matching neither carrier branch
128
+ # comes back as the raw hash.
129
+ #
130
+ # @rbs request: ACP::Types::Unstable::MessageMcpRequest
131
+ # @rbs return: (ACP::Types::Unstable::MessageMcpResponse::t | ACP::RequestError)
132
+ def mcp_message(request)
133
+ return ACP::RequestError.unadvertised_agent('mcpCapabilities.acp') unless @mcp_advertised
134
+
135
+ call('mcp/message', ACP::Types::Unstable::MessageMcpResponse, request)
136
+ end
137
+
108
138
  # Sends an extension request, keyed by its raw `_`-prefixed wire name, and
109
139
  # returns the client's reply as-is: extension methods have no schema to
110
140
  # parse the result into.
@@ -0,0 +1,23 @@
1
+ # frozen_string_literal: true
2
+
3
+ module ACP::AgentConnection::OptionalMethod::DisableProvider
4
+ extend self
5
+
6
+ # @rbs return: String
7
+ def rpc_method
8
+ 'providers/disable'
9
+ end
10
+
11
+ # @rbs return: Symbol
12
+ def agent_method
13
+ :disable_provider
14
+ end
15
+
16
+ # @rbs initialize_response: ACP::Types::InitializeResponse
17
+ # @rbs return: bool
18
+ def advertised?(initialize_response)
19
+ capabilities = initialize_response.agent_capabilities #: ACP::Types::AgentCapabilities | ACP::Types::Unstable::AgentCapabilities?
20
+ providers = capabilities.is_a?(ACP::Types::Unstable::AgentCapabilities) ? capabilities.providers : nil #: ACP::Types::Unstable::ProvidersCapabilities?
21
+ providers ? true : false
22
+ end
23
+ end
@@ -0,0 +1,23 @@
1
+ # frozen_string_literal: true
2
+
3
+ module ACP::AgentConnection::OptionalMethod::ForkSession
4
+ extend self
5
+
6
+ # @rbs return: String
7
+ def rpc_method
8
+ 'session/fork'
9
+ end
10
+
11
+ # @rbs return: Symbol
12
+ def agent_method
13
+ :fork_session
14
+ end
15
+
16
+ # @rbs initialize_response: ACP::Types::InitializeResponse
17
+ # @rbs return: bool
18
+ def advertised?(initialize_response)
19
+ session = initialize_response.agent_capabilities&.session_capabilities #: ACP::Types::SessionCapabilities | ACP::Types::Unstable::SessionCapabilities?
20
+ fork = session.is_a?(ACP::Types::Unstable::SessionCapabilities) ? session.fork : nil #: ACP::Types::Unstable::SessionForkCapabilities?
21
+ fork ? true : false
22
+ end
23
+ end
@@ -0,0 +1,23 @@
1
+ # frozen_string_literal: true
2
+
3
+ module ACP::AgentConnection::OptionalMethod::ListProviders
4
+ extend self
5
+
6
+ # @rbs return: String
7
+ def rpc_method
8
+ 'providers/list'
9
+ end
10
+
11
+ # @rbs return: Symbol
12
+ def agent_method
13
+ :list_providers
14
+ end
15
+
16
+ # @rbs initialize_response: ACP::Types::InitializeResponse
17
+ # @rbs return: bool
18
+ def advertised?(initialize_response)
19
+ capabilities = initialize_response.agent_capabilities #: ACP::Types::AgentCapabilities | ACP::Types::Unstable::AgentCapabilities?
20
+ providers = capabilities.is_a?(ACP::Types::Unstable::AgentCapabilities) ? capabilities.providers : nil #: ACP::Types::Unstable::ProvidersCapabilities?
21
+ providers ? true : false
22
+ end
23
+ end
@@ -0,0 +1,23 @@
1
+ # frozen_string_literal: true
2
+
3
+ module ACP::AgentConnection::OptionalMethod::SetProvider
4
+ extend self
5
+
6
+ # @rbs return: String
7
+ def rpc_method
8
+ 'providers/set'
9
+ end
10
+
11
+ # @rbs return: Symbol
12
+ def agent_method
13
+ :set_provider
14
+ end
15
+
16
+ # @rbs initialize_response: ACP::Types::InitializeResponse
17
+ # @rbs return: bool
18
+ def advertised?(initialize_response)
19
+ capabilities = initialize_response.agent_capabilities #: ACP::Types::AgentCapabilities | ACP::Types::Unstable::AgentCapabilities?
20
+ providers = capabilities.is_a?(ACP::Types::Unstable::AgentCapabilities) ? capabilities.providers : nil #: ACP::Types::Unstable::ProvidersCapabilities?
21
+ providers ? true : false
22
+ end
23
+ end
@@ -61,15 +61,21 @@ class ACP::AgentConnection
61
61
 
62
62
  # @rbs return: Thread
63
63
  def start
64
- client = ACP::AgentConnection::Client.new(peer: @transport)
64
+ client = ACP::AgentConnection::Client.new(peer: @transport, mcp_advertised: mcp_acp_advertised?)
65
65
  agent = @factory.call(client)
66
66
  # Routes are fixed before a client connects, so the check sees the
67
67
  # unfiltered auth methods: a terminal-capable client still needs
68
68
  # authenticate routed.
69
- advertised, unadvertised = OPTIONAL.partition { |method| method.advertised?(initialize_response(@auth_methods)) }
69
+ advertised, unadvertised = optional_methods.partition do |method|
70
+ method.advertised?(initialize_response(@auth_methods))
71
+ end
70
72
  missing = advertised.map(&:agent_method).reject { |name| agent.respond_to?(name) }
71
73
  raise ArgumentError, "initialize advertises methods the agent lacks: #{missing.join(', ')}" unless missing.empty?
72
74
 
75
+ if mcp_acp_advertised? && !agent.respond_to?(:mcp_message_notification)
76
+ raise ArgumentError, 'initialize advertises mcpCapabilities.acp but the agent lacks mcp_message_notification'
77
+ end
78
+
73
79
  unrouted = unadvertised.map(&:rpc_method) + PER_SESSION.reject { |_, name| agent.respond_to?(name) }.keys
74
80
  @transport.start(
75
81
  requests: requests(agent, client).except(*unrouted).merge(@extension_requests),
@@ -101,13 +107,49 @@ class ACP::AgentConnection
101
107
  'session/set_config_option' => route(ACP::Types::SetSessionConfigOptionRequest) do |request|
102
108
  full.change_session_config_option(request)
103
109
  end
110
+ }.merge(unstable_requests(full))
111
+ end
112
+
113
+ # The unstable request types are referenced while the hash is built, so
114
+ # without the guard start would raise NameError even for an agent that never
115
+ # advertises fork or providers.
116
+ #
117
+ # @rbs full: ACP::AgentConnection::_FullAgent
118
+ # @rbs return: Hash[String, ^(untyped) -> (ACP::AgentConnection::_Response | ACP::RequestError | ACP::Transport::Reply)]
119
+ def unstable_requests(full)
120
+ return {} unless defined?(ACP::Types::Unstable)
121
+
122
+ {
123
+ 'session/fork' => route(ACP::Types::Unstable::ForkSessionRequest) { |request| full.fork_session(request) },
124
+ 'providers/list' => route(ACP::Types::Unstable::ListProvidersRequest) { |request| full.list_providers(request) },
125
+ 'providers/set' => route(ACP::Types::Unstable::SetProviderRequest) { |request| full.set_provider(request) },
126
+ 'providers/disable' => route(ACP::Types::Unstable::DisableProviderRequest) { |request| full.disable_provider(request) }
104
127
  }
105
128
  end
106
129
 
130
+ # ForkSession's and the provider methods' advertised? reference the unstable
131
+ # types, so they can only be probed when the caller opted in with
132
+ # `require 'acp/types/unstable'`.
133
+ #
134
+ # @rbs return: Array[ACP::AgentConnection::_OptionalMethod]
135
+ def optional_methods
136
+ return OPTIONAL unless defined?(ACP::Types::Unstable)
137
+
138
+ OPTIONAL + [
139
+ ACP::AgentConnection::OptionalMethod::ForkSession,
140
+ ACP::AgentConnection::OptionalMethod::ListProviders,
141
+ ACP::AgentConnection::OptionalMethod::SetProvider,
142
+ ACP::AgentConnection::OptionalMethod::DisableProvider
143
+ ]
144
+ end
145
+
107
146
  # @rbs agent: ACP::AgentConnection::_Agent
108
147
  # @rbs return: Hash[String, ^(untyped) -> void]
109
148
  def notifications(agent)
110
- { 'session/cancel' => ->(params) { cancel(agent, params) } }
149
+ routes = { 'session/cancel' => ->(params) { cancel(agent, params) } }
150
+ return routes unless mcp_acp_advertised?
151
+
152
+ routes.merge('mcp/message' => ->(params) { mcp_message_notification(agent, params) })
111
153
  end
112
154
 
113
155
  # A notification has no reply to carry a parse failure.
@@ -123,6 +165,34 @@ class ACP::AgentConnection
123
165
  agent.cancel(notification)
124
166
  end
125
167
 
168
+ # The unstable gates reference the unstable types, so they can only be
169
+ # probed when the caller opted in with `require 'acp/types/unstable'`; the
170
+ # stable McpCapabilities has no acp field at all.
171
+ #
172
+ # @rbs return: bool
173
+ def mcp_acp_advertised?
174
+ return false unless defined?(ACP::Types::Unstable)
175
+
176
+ mcp = @capabilities.mcp_capabilities #: ACP::Types::McpCapabilities | ACP::Types::Unstable::McpCapabilities?
177
+ return false unless mcp.is_a?(ACP::Types::Unstable::McpCapabilities)
178
+
179
+ mcp.acp ? true : false
180
+ end
181
+
182
+ # A notification has no reply to carry a parse failure.
183
+ #
184
+ # @rbs agent: ACP::AgentConnection::_Agent
185
+ # @rbs params: untyped
186
+ # @rbs return: void
187
+ def mcp_message_notification(agent, params)
188
+ notification = ACP::Types::Unstable::MessageMcpNotification.from_h(params)
189
+ rescue ACP::Types::ParseError, KeyError, TypeError, NoMethodError => e
190
+ @logger.warn("dropped malformed mcp/message: #{e.class}: #{e.message}")
191
+ else
192
+ hook = agent #: ACP::AgentConnection::_Agent & ACP::AgentConnection::_McpMessageNotification
193
+ hook.mcp_message_notification(notification)
194
+ end
195
+
126
196
  # Generated from_h raises on a missing key or a value of the wrong shape.
127
197
  #
128
198
  # @rbs type: ACP::AgentConnection::_Parser
@@ -15,6 +15,9 @@ class ACP::ClientConnection
15
15
  # (ACP::Types::CreateElicitationResponse | ACP::RequestError))?
16
16
  # @rbs @complete_elicitation: (^(ACP::Types::CompleteElicitationNotification) -> void)?
17
17
  # @rbs @updates: ACP::ClientConnection::_UpdateHandler
18
+ # @rbs @mcp_message: (^(ACP::Types::Unstable::MessageMcpRequest) ->
19
+ # (ACP::Types::Unstable::MessageMcpResponse::t | ACP::RequestError))?
20
+ # @rbs @mcp_advertised: bool?
18
21
  # @rbs @fs_capabilities: ACP::Types::FileSystemCapabilities?
19
22
  # @rbs @terminal: bool?
20
23
  # @rbs @elicitation_capabilities: ACP::Types::ElicitationCapabilities?
@@ -48,6 +51,8 @@ class ACP::ClientConnection
48
51
  # (ACP::Types::CreateElicitationResponse | ACP::RequestError))?
49
52
  # @rbs complete_elicitation: (^(ACP::Types::CompleteElicitationNotification) -> void)?
50
53
  # @rbs updates: ACP::ClientConnection::_UpdateHandler
54
+ # @rbs mcp_message: (^(ACP::Types::Unstable::MessageMcpRequest) ->
55
+ # (ACP::Types::Unstable::MessageMcpResponse::t | ACP::RequestError))?
51
56
  # @rbs extension_requests: Hash[String, ^(untyped) -> untyped]
52
57
  # @rbs extension_notifications: Hash[String, ^(untyped) -> void]
53
58
  # @rbs logger: ACP::Transport::_Logger
@@ -65,6 +70,7 @@ class ACP::ClientConnection
65
70
  elicitation: nil,
66
71
  complete_elicitation: nil,
67
72
  updates: IGNORE,
73
+ mcp_message: nil,
68
74
  extension_requests: {},
69
75
  extension_notifications: {},
70
76
  logger: ACP::Transport::StderrLogger.new
@@ -81,6 +87,8 @@ class ACP::ClientConnection
81
87
  @elicitation = elicitation
82
88
  @complete_elicitation = complete_elicitation
83
89
  @updates = updates
90
+ @mcp_message = mcp_message
91
+ @mcp_advertised = nil
84
92
  @fs_capabilities = nil
85
93
  @terminal = nil
86
94
  @elicitation_capabilities = nil
@@ -108,7 +116,7 @@ class ACP::ClientConnection
108
116
  'terminal/kill' => method(:kill_terminal),
109
117
  'terminal/release' => method(:release_terminal),
110
118
  'elicitation/create' => method(:create_elicitation)
111
- }.merge(@extension_requests),
119
+ }.merge(mcp_requests).merge(@extension_requests),
112
120
  notifications: { 'session/update' => method(:dispatch),
113
121
  'elicitation/complete' => method(:complete_elicitation) }.merge(@extension_notifications)
114
122
  )
@@ -141,16 +149,17 @@ class ACP::ClientConnection
141
149
  @fs_capabilities = capabilities
142
150
  @terminal = terminal
143
151
  @elicitation_capabilities = elicitation
144
- parse(ACP::Types::InitializeResponse, @transport.request('initialize', request.to_h)).then do |response|
145
- next response if response.is_a?(ACP::RequestError)
152
+ result = @transport.request('initialize', request.to_h)
153
+ response = parse(ACP::Types::InitializeResponse, result)
154
+ return response if response.is_a?(ACP::RequestError)
146
155
 
147
- next response if response.protocol_version == PROTOCOL_VERSION
156
+ @mcp_advertised = agent_mcp_advertised?(result)
157
+ return response if response.protocol_version == PROTOCOL_VERSION
148
158
 
149
- ACP::RequestError.unsupported_protocol_version(
150
- requested: request.protocol_version,
151
- returned: response.protocol_version
152
- )
153
- end
159
+ ACP::RequestError.unsupported_protocol_version(
160
+ requested: request.protocol_version,
161
+ returned: response.protocol_version
162
+ )
154
163
  end
155
164
 
156
165
  # @rbs request: ACP::Types::NewSessionRequest
@@ -200,6 +209,30 @@ class ACP::ClientConnection
200
209
  parse(ACP::Types::DeleteSessionResponse, @transport.request('session/delete', request.to_h))
201
210
  end
202
211
 
212
+ # @rbs request: ACP::Types::Unstable::ForkSessionRequest
213
+ # @rbs return: (ACP::Types::Unstable::ForkSessionResponse | ACP::RequestError)
214
+ def session_fork(request)
215
+ parse(ACP::Types::Unstable::ForkSessionResponse, @transport.request('session/fork', request.to_h))
216
+ end
217
+
218
+ # @rbs request: ACP::Types::Unstable::ListProvidersRequest
219
+ # @rbs return: (ACP::Types::Unstable::ListProvidersResponse | ACP::RequestError)
220
+ def providers_list(request)
221
+ parse(ACP::Types::Unstable::ListProvidersResponse, @transport.request('providers/list', request.to_h))
222
+ end
223
+
224
+ # @rbs request: ACP::Types::Unstable::SetProviderRequest
225
+ # @rbs return: (ACP::Types::Unstable::SetProviderResponse | ACP::RequestError)
226
+ def providers_set(request)
227
+ parse(ACP::Types::Unstable::SetProviderResponse, @transport.request('providers/set', request.to_h))
228
+ end
229
+
230
+ # @rbs request: ACP::Types::Unstable::DisableProviderRequest
231
+ # @rbs return: (ACP::Types::Unstable::DisableProviderResponse | ACP::RequestError)
232
+ def providers_disable(request)
233
+ parse(ACP::Types::Unstable::DisableProviderResponse, @transport.request('providers/disable', request.to_h))
234
+ end
235
+
203
236
  # The spec requires a pending session/request_permission to be answered with
204
237
  # the cancelled outcome once the turn is cancelled, so every queue registered
205
238
  # for the session gets one before this returns. The notification goes first
@@ -252,6 +285,13 @@ class ACP::ClientConnection
252
285
  @transport.request(method, params)
253
286
  end
254
287
 
288
+ # Always false off a handler thread.
289
+ #
290
+ # @rbs return: bool
291
+ def cancelled?
292
+ !!@transport.cancellation&.cancelled?
293
+ end
294
+
255
295
  # @rbs method: String
256
296
  # @rbs params: untyped
257
297
  # @rbs return: void
@@ -260,8 +300,44 @@ class ACP::ClientConnection
260
300
  @transport.notify(method, params)
261
301
  end
262
302
 
303
+ # Unlike the mcp/message serve route, this send is not gated: a notification
304
+ # has no reply to carry a refusal, and the agent drops what it did not
305
+ # advertise.
306
+ #
307
+ # @rbs notification: ACP::Types::Unstable::MessageMcpNotification
308
+ # @rbs return: void
309
+ def mcp_message_notification(notification)
310
+ @transport.notify('mcp/message', notification.to_h)
311
+ end
312
+
263
313
  private
264
314
 
315
+ # The route is only registered when a handler serves it, but the agent's
316
+ # advertisement is only known after connect, so an unadvertised request
317
+ # still reaches the route and is refused there.
318
+ #
319
+ # @rbs return: Hash[String, ACP::Transport::Stdio::_Handler]
320
+ def mcp_requests
321
+ return {} unless @mcp_message
322
+
323
+ { 'mcp/message' => method(:mcp_message) }
324
+ end
325
+
326
+ # The stable McpCapabilities has no acp field, so the gate re-reads the raw
327
+ # initialize reply with the unstable types when they are opted into.
328
+ #
329
+ # @rbs result: (Hash[String, untyped] | ACP::RequestError)
330
+ # @rbs return: bool
331
+ def agent_mcp_advertised?(result)
332
+ return false unless defined?(ACP::Types::Unstable)
333
+ return false unless result.is_a?(Hash)
334
+
335
+ mcp = ACP::Types::Unstable::McpCapabilities.from_h(result['agentCapabilities']['mcpCapabilities'] || {})
336
+ mcp.acp ? true : false
337
+ rescue ACP::Types::ParseError, KeyError, TypeError, NoMethodError
338
+ false
339
+ end
340
+
265
341
  # @rbs type: ACP::AgentConnection::_Parser
266
342
  # @rbs result: (Hash[String, untyped] | ACP::RequestError)
267
343
  # @rbs return: untyped
@@ -340,7 +416,11 @@ class ACP::ClientConnection
340
416
  else
341
417
  replies = Thread::Queue.new
342
418
  register_permission(request.session_id, replies)
419
+ # The handler runs on this connection's own thread, so the serving
420
+ # identity must be carried over for cancelled? to see the cancel there.
421
+ serving = @transport.cancellation
343
422
  Thread.new do
423
+ ACP::Transport::Cancellation.current = serving
344
424
  # A raised StandardError is not a returned ACP::RequestError, so the two
345
425
  # need distinct shapes on the queue.
346
426
  replies << begin
@@ -490,6 +570,25 @@ class ACP::ClientConnection
490
570
  handler.call(request)
491
571
  end
492
572
 
573
+ # The route is registered at start, before connect records the agent's
574
+ # advertisement, so an unadvertised request answers -32601 until then.
575
+ #
576
+ # @rbs params: untyped
577
+ # @rbs return: (ACP::Types::Unstable::MessageMcpResponse::t | ACP::RequestError)
578
+ def mcp_message(params)
579
+ return ACP::RequestError.unadvertised_agent('mcpCapabilities.acp') unless @mcp_advertised
580
+
581
+ request = ACP::Types::Unstable::MessageMcpRequest.from_h(params)
582
+ rescue ACP::Types::ParseError => e
583
+ ACP::RequestError.invalid_params([e.message])
584
+ rescue KeyError, TypeError, NoMethodError
585
+ ACP::RequestError.invalid_params
586
+ else
587
+ # Safe: the route is only registered when a handler serves it.
588
+ handler = @mcp_message #: ^(ACP::Types::Unstable::MessageMcpRequest) -> (ACP::Types::Unstable::MessageMcpResponse::t | ACP::RequestError)
589
+ handler.call(request)
590
+ end
591
+
493
592
  # The routes are registered at start, before connect records the advertised
494
593
  # capabilities, so a request for an unadvertised mode answers -32602.
495
594
  #
@@ -82,6 +82,16 @@ class ACP::RequestError
82
82
  new(code: INVALID_PARAMS, message: "Client does not advertise #{mode}")
83
83
  end
84
84
 
85
+ # unadvertised names a capability of the client's, this one a capability of
86
+ # the agent's: the same method-not-found code, with a message naming which
87
+ # side's advertisement is missing.
88
+ #
89
+ # @rbs capability: String
90
+ # @rbs return: ACP::RequestError
91
+ def self.unadvertised_agent(capability)
92
+ new(code: METHOD_NOT_FOUND, message: "Agent does not advertise #{capability}")
93
+ end
94
+
85
95
  # @rbs return: ACP::RequestError
86
96
  def self.internal_error
87
97
  new(code: INTERNAL_ERROR, message: 'Internal error')
data/lib/acp/sdk.rb CHANGED
@@ -9,5 +9,7 @@ root = __dir__ #: String
9
9
  loader = Zeitwerk::Loader.new
10
10
  loader.tag = 'acp-sdk'
11
11
  loader.push_dir(root, namespace: ACP)
12
- loader.ignore(__FILE__, "#{root}/version.rb")
12
+ # The unstable types load only through `require 'acp/types/unstable'`, so
13
+ # Zeitwerk must not map them.
14
+ loader.ignore(__FILE__, "#{root}/version.rb", "#{root}/types/unstable.rb", "#{root}/types/unstable")
13
15
  loader.setup
@@ -0,0 +1,42 @@
1
+ # frozen_string_literal: true
2
+
3
+ # Whether the peer cancelled one request. The transport creates one per served
4
+ # request, marks it when the peer's $/cancel_request arrives, and hands it to
5
+ # the serving thread, whose handler can observe it and end the request early.
6
+ class ACP::Transport::Cancellation
7
+ KEY = :acp_serving_cancellation #: Symbol
8
+
9
+ # Connections read this to expose the serving state to their handlers and
10
+ # write it to carry serving identity onto a thread they spawn; nil off a
11
+ # serve thread.
12
+ #
13
+ # @rbs return: ACP::Transport::Cancellation?
14
+ def self.current
15
+ Thread.current[KEY] #: ACP::Transport::Cancellation?
16
+ end
17
+
18
+ # @rbs cancellation: ACP::Transport::Cancellation?
19
+ # @rbs return: void
20
+ def self.current=(cancellation)
21
+ Thread.current[KEY] = cancellation
22
+ end
23
+
24
+ # @rbs @lock: Thread::Mutex
25
+ # @rbs @cancelled: bool
26
+
27
+ # @rbs return: void
28
+ def initialize
29
+ @lock = Mutex.new
30
+ @cancelled = false
31
+ end
32
+
33
+ # @rbs return: void
34
+ def cancel
35
+ @lock.synchronize { @cancelled = true }
36
+ end
37
+
38
+ # @rbs return: bool
39
+ def cancelled?
40
+ @lock.synchronize { @cancelled }
41
+ end
42
+ end