@pi-in-go/pigpen-ahp 0.1.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 (304) hide show
  1. package/CREDITS.md +27 -0
  2. package/LICENSE +23 -0
  3. package/README.md +148 -0
  4. package/extensions/ahp/access_options_test.go +116 -0
  5. package/extensions/ahp/extension.go +54 -0
  6. package/extensions/ahp/go.mod +12 -0
  7. package/extensions/ahp/go.sum +2 -0
  8. package/extensions/ahp/go.work +8 -0
  9. package/extensions/ahp/internal/channels/chat.go +110 -0
  10. package/extensions/ahp/internal/channels/root.go +32 -0
  11. package/extensions/ahp/internal/channels/session.go +128 -0
  12. package/extensions/ahp/internal/channels/session_test.go +108 -0
  13. package/extensions/ahp/internal/compose/compose.go +153 -0
  14. package/extensions/ahp/internal/compose/compose_test.go +120 -0
  15. package/extensions/ahp/internal/gaps/gaps_test.go +88 -0
  16. package/extensions/ahp/internal/host/handshake_test.go +206 -0
  17. package/extensions/ahp/internal/host/host.go +1305 -0
  18. package/extensions/ahp/internal/host/reconnect_test.go +197 -0
  19. package/extensions/ahp/internal/host/replaywindow_test.go +31 -0
  20. package/extensions/ahp/internal/host/schema_test.go +79 -0
  21. package/extensions/ahp/internal/host/store.go +291 -0
  22. package/extensions/ahp/internal/host/store_update_test.go +85 -0
  23. package/extensions/ahp/internal/host/subscriptions_test.go +160 -0
  24. package/extensions/ahp/internal/host/surface_test.go +95 -0
  25. package/extensions/ahp/internal/host/workarounds.go +458 -0
  26. package/extensions/ahp/internal/host/workarounds_test.go +96 -0
  27. package/extensions/ahp/internal/live/live.go +328 -0
  28. package/extensions/ahp/internal/live/live_test.go +91 -0
  29. package/extensions/ahp/internal/live/store.go +113 -0
  30. package/extensions/ahp/internal/mapper/activity.go +199 -0
  31. package/extensions/ahp/internal/mapper/activity_test.go +188 -0
  32. package/extensions/ahp/internal/mapper/bench_test.go +38 -0
  33. package/extensions/ahp/internal/mapper/event_mapper_test.go +380 -0
  34. package/extensions/ahp/internal/mapper/fixtures_test.go +387 -0
  35. package/extensions/ahp/internal/mapper/helpers_test.go +251 -0
  36. package/extensions/ahp/internal/mapper/js.go +131 -0
  37. package/extensions/ahp/internal/mapper/mapper.go +600 -0
  38. package/extensions/ahp/internal/mapper/message_input_test.go +196 -0
  39. package/extensions/ahp/internal/mapper/messageinput.go +272 -0
  40. package/extensions/ahp/internal/mapper/schema_helpers_test.go +5 -0
  41. package/extensions/ahp/internal/mapper/testdata/LICENSE-pi-ahp +21 -0
  42. package/extensions/ahp/internal/mapper/testdata/fixtures/abort.json +711 -0
  43. package/extensions/ahp/internal/mapper/testdata/fixtures/bash-long-output.json +1312 -0
  44. package/extensions/ahp/internal/mapper/testdata/fixtures/compaction.json +6285 -0
  45. package/extensions/ahp/internal/mapper/testdata/fixtures/parallel-tools.json +2622 -0
  46. package/extensions/ahp/internal/mapper/testdata/fixtures/plain-text.json +1856 -0
  47. package/extensions/ahp/internal/mapper/testdata/fixtures/single-tool.json +1128 -0
  48. package/extensions/ahp/internal/mapper/testdata/fixtures/steering.json +3926 -0
  49. package/extensions/ahp/internal/mapper/testdata/fixtures/tool-bash.json +2372 -0
  50. package/extensions/ahp/internal/mapper/testdata/fixtures/tool-edit.json +3983 -0
  51. package/extensions/ahp/internal/mapper/testdata/fixtures/tool-error.json +2850 -0
  52. package/extensions/ahp/internal/mapper/testdata/fixtures/tool-find.json +1162 -0
  53. package/extensions/ahp/internal/mapper/testdata/fixtures/tool-grep.json +2783 -0
  54. package/extensions/ahp/internal/mapper/testdata/fixtures/tool-loop.json +1693 -0
  55. package/extensions/ahp/internal/mapper/testdata/fixtures/tool-ls.json +2427 -0
  56. package/extensions/ahp/internal/mapper/testdata/fixtures/tool-write.json +1271 -0
  57. package/extensions/ahp/internal/mapper/title.go +34 -0
  58. package/extensions/ahp/internal/mapper/usermsg.go +87 -0
  59. package/extensions/ahp/internal/pi/activeturn_test.go +296 -0
  60. package/extensions/ahp/internal/pi/backend.go +64 -0
  61. package/extensions/ahp/internal/pi/catalogue.go +370 -0
  62. package/extensions/ahp/internal/pi/catalogue_test.go +271 -0
  63. package/extensions/ahp/internal/pi/chatdriver.go +533 -0
  64. package/extensions/ahp/internal/pi/chatdriver_test.go +416 -0
  65. package/extensions/ahp/internal/pi/clientactions.go +212 -0
  66. package/extensions/ahp/internal/pi/clientactions_test.go +273 -0
  67. package/extensions/ahp/internal/pi/completions.go +230 -0
  68. package/extensions/ahp/internal/pi/completions_test.go +272 -0
  69. package/extensions/ahp/internal/pi/config_test.go +269 -0
  70. package/extensions/ahp/internal/pi/deletesession.go +69 -0
  71. package/extensions/ahp/internal/pi/deletesession_test.go +153 -0
  72. package/extensions/ahp/internal/pi/disposal_test.go +362 -0
  73. package/extensions/ahp/internal/pi/fixture_test.go +308 -0
  74. package/extensions/ahp/internal/pi/foreign_test.go +122 -0
  75. package/extensions/ahp/internal/pi/harness_test.go +83 -0
  76. package/extensions/ahp/internal/pi/history.go +269 -0
  77. package/extensions/ahp/internal/pi/hydrated_test.go +171 -0
  78. package/extensions/ahp/internal/pi/hydratedlifecycle_test.go +196 -0
  79. package/extensions/ahp/internal/pi/hydration_test.go +209 -0
  80. package/extensions/ahp/internal/pi/hydrator.go +212 -0
  81. package/extensions/ahp/internal/pi/imageinput.go +224 -0
  82. package/extensions/ahp/internal/pi/imageinput_test.go +57 -0
  83. package/extensions/ahp/internal/pi/lifecycle_test.go +381 -0
  84. package/extensions/ahp/internal/pi/models.go +212 -0
  85. package/extensions/ahp/internal/pi/models_test.go +128 -0
  86. package/extensions/ahp/internal/pi/paging.go +95 -0
  87. package/extensions/ahp/internal/pi/pagingtruncate_test.go +323 -0
  88. package/extensions/ahp/internal/pi/projecttrust.go +222 -0
  89. package/extensions/ahp/internal/pi/registry.go +924 -0
  90. package/extensions/ahp/internal/pi/restart_test.go +185 -0
  91. package/extensions/ahp/internal/pi/services.go +70 -0
  92. package/extensions/ahp/internal/pi/sessionconfig.go +64 -0
  93. package/extensions/ahp/internal/pi/sessionfiles_test.go +74 -0
  94. package/extensions/ahp/internal/pi/sessionstore.go +83 -0
  95. package/extensions/ahp/internal/pi/summary_test.go +426 -0
  96. package/extensions/ahp/internal/pi/workarounds_wire_test.go +208 -0
  97. package/extensions/ahp/internal/pisession/fromentries_test.go +32 -0
  98. package/extensions/ahp/internal/pisession/json.go +31 -0
  99. package/extensions/ahp/internal/pisession/pisession.go +580 -0
  100. package/extensions/ahp/internal/settings/access.go +63 -0
  101. package/extensions/ahp/internal/settings/access_test.go +52 -0
  102. package/extensions/ahp/internal/settings/settings.go +117 -0
  103. package/extensions/ahp/internal/settings/settings_test.go +104 -0
  104. package/extensions/ahp/internal/svc/etag_other.go +15 -0
  105. package/extensions/ahp/internal/svc/etag_unix.go +34 -0
  106. package/extensions/ahp/internal/svc/glob.go +197 -0
  107. package/extensions/ahp/internal/svc/mime.go +37 -0
  108. package/extensions/ahp/internal/svc/paths.go +108 -0
  109. package/extensions/ahp/internal/svc/pty.go +64 -0
  110. package/extensions/ahp/internal/svc/pty_darwin.go +42 -0
  111. package/extensions/ahp/internal/svc/pty_linux.go +36 -0
  112. package/extensions/ahp/internal/svc/pty_other.go +9 -0
  113. package/extensions/ahp/internal/svc/pty_test.go +114 -0
  114. package/extensions/ahp/internal/svc/pty_unix.go +147 -0
  115. package/extensions/ahp/internal/svc/resource.go +495 -0
  116. package/extensions/ahp/internal/svc/resource_test.go +501 -0
  117. package/extensions/ahp/internal/svc/stat_bsd.go +9 -0
  118. package/extensions/ahp/internal/svc/stat_linux.go +9 -0
  119. package/extensions/ahp/internal/svc/terminal.go +454 -0
  120. package/extensions/ahp/internal/svc/terminal_test.go +512 -0
  121. package/extensions/ahp/internal/svc/watch.go +590 -0
  122. package/extensions/ahp/internal/svc/watch_test.go +627 -0
  123. package/extensions/ahp/internal/svc/watchevents_test.go +452 -0
  124. package/extensions/ahp/internal/svc/watchpolicy.go +81 -0
  125. package/extensions/ahp/internal/svc/watchpolicy_test.go +151 -0
  126. package/extensions/ahp/internal/svc/watchracy_test.go +61 -0
  127. package/extensions/ahp/internal/testkit/schema/LICENSE-agent-host-protocol +21 -0
  128. package/extensions/ahp/internal/testkit/schema/actions.schema.json +9208 -0
  129. package/extensions/ahp/internal/testkit/schema/commands.schema.json +10862 -0
  130. package/extensions/ahp/internal/testkit/schema/errors.schema.json +10928 -0
  131. package/extensions/ahp/internal/testkit/schema/notifications.schema.json +6667 -0
  132. package/extensions/ahp/internal/testkit/schema/state.schema.json +6392 -0
  133. package/extensions/ahp/internal/testkit/schema.go +360 -0
  134. package/extensions/ahp/internal/testkit/testkit.go +367 -0
  135. package/extensions/ahp/internal/twin/twin.go +27 -0
  136. package/extensions/ahp/internal/wire/dispatchable.go +106 -0
  137. package/extensions/ahp/internal/wire/dispatchable_list.go +15 -0
  138. package/extensions/ahp/internal/wire/helpers_test.go +5 -0
  139. package/extensions/ahp/internal/wire/wire.go +218 -0
  140. package/extensions/ahp/internal/wire/wire_test.go +123 -0
  141. package/extensions/ahp/internal/ws/helpers_test.go +7 -0
  142. package/extensions/ahp/internal/ws/vscode_test.go +198 -0
  143. package/extensions/ahp/internal/ws/ws.go +674 -0
  144. package/extensions/ahp/internal/ws/ws_test.go +324 -0
  145. package/extensions/ahp/realpig_test.go +589 -0
  146. package/extensions/ahp/runtime.go +311 -0
  147. package/extensions/ahp/runtime_test.go +110 -0
  148. package/extensions/ahp/testdata/upstream-tests.json +3833 -0
  149. package/extensions/ahp/third_party/agent-host-protocol-go/LICENSE +21 -0
  150. package/extensions/ahp/third_party/agent-host-protocol-go/NOTICE-PIGPEN.md +18 -0
  151. package/extensions/ahp/third_party/agent-host-protocol-go/ahp/client.go +1011 -0
  152. package/extensions/ahp/third_party/agent-host-protocol-go/ahp/error.go +111 -0
  153. package/extensions/ahp/third_party/agent-host-protocol-go/ahp/multi_host_state_mirror.go +239 -0
  154. package/extensions/ahp/third_party/agent-host-protocol-go/ahp/reducers.go +1939 -0
  155. package/extensions/ahp/third_party/agent-host-protocol-go/ahp/transport.go +176 -0
  156. package/extensions/ahp/third_party/agent-host-protocol-go/ahptypes/actions.generated.go +2447 -0
  157. package/extensions/ahp/third_party/agent-host-protocol-go/ahptypes/commands.generated.go +1546 -0
  158. package/extensions/ahp/third_party/agent-host-protocol-go/ahptypes/common.go +205 -0
  159. package/extensions/ahp/third_party/agent-host-protocol-go/ahptypes/errors.generated.go +65 -0
  160. package/extensions/ahp/third_party/agent-host-protocol-go/ahptypes/messages.generated.go +138 -0
  161. package/extensions/ahp/third_party/agent-host-protocol-go/ahptypes/notifications.generated.go +264 -0
  162. package/extensions/ahp/third_party/agent-host-protocol-go/ahptypes/state.generated.go +6171 -0
  163. package/extensions/ahp/third_party/agent-host-protocol-go/ahptypes/version.generated.go +31 -0
  164. package/extensions/ahp/third_party/agent-host-protocol-go/go.mod +3 -0
  165. package/extensions/ahp/twins_test.go +91 -0
  166. package/package.json +40 -0
  167. package/proof/PORT.md +121 -0
  168. package/proof/mutations.json +152 -0
  169. package/proof/oracle/LICENSE +21 -0
  170. package/proof/oracle/README.md +120 -0
  171. package/proof/oracle/UPSTREAM.md +7 -0
  172. package/proof/oracle/package.json +68 -0
  173. package/proof/oracle/src/bin/cli.ts +66 -0
  174. package/proof/oracle/src/bin/tunnel.ts +128 -0
  175. package/proof/oracle/src/channels/chat.ts +134 -0
  176. package/proof/oracle/src/channels/root.ts +39 -0
  177. package/proof/oracle/src/channels/session.ts +118 -0
  178. package/proof/oracle/src/channels/terminal.ts +11 -0
  179. package/proof/oracle/src/core/channels.ts +108 -0
  180. package/proof/oracle/src/core/client-workarounds.ts +358 -0
  181. package/proof/oracle/src/core/connection.ts +41 -0
  182. package/proof/oracle/src/core/host.ts +882 -0
  183. package/proof/oracle/src/core/sequencer.ts +75 -0
  184. package/proof/oracle/src/core/state-store.ts +162 -0
  185. package/proof/oracle/src/core/uri.ts +25 -0
  186. package/proof/oracle/src/host/direct-settings.ts +110 -0
  187. package/proof/oracle/src/host/pi-host.ts +220 -0
  188. package/proof/oracle/src/host/serve.ts +71 -0
  189. package/proof/oracle/src/host/terminal-service.ts +346 -0
  190. package/proof/oracle/src/pi/activity.ts +171 -0
  191. package/proof/oracle/src/pi/changeset-service.ts +646 -0
  192. package/proof/oracle/src/pi/changeset-uri.ts +64 -0
  193. package/proof/oracle/src/pi/chat-driver.ts +529 -0
  194. package/proof/oracle/src/pi/completions.ts +160 -0
  195. package/proof/oracle/src/pi/delete-session.ts +53 -0
  196. package/proof/oracle/src/pi/event-mapper.ts +648 -0
  197. package/proof/oracle/src/pi/git-changes.ts +605 -0
  198. package/proof/oracle/src/pi/history.ts +305 -0
  199. package/proof/oracle/src/pi/image-input.ts +56 -0
  200. package/proof/oracle/src/pi/image-mime.ts +8 -0
  201. package/proof/oracle/src/pi/in-process-backend.ts +155 -0
  202. package/proof/oracle/src/pi/message-input.ts +212 -0
  203. package/proof/oracle/src/pi/models.ts +124 -0
  204. package/proof/oracle/src/pi/project-trust.ts +66 -0
  205. package/proof/oracle/src/pi/provider.ts +2 -0
  206. package/proof/oracle/src/pi/resource-paths.ts +80 -0
  207. package/proof/oracle/src/pi/resource-service.ts +356 -0
  208. package/proof/oracle/src/pi/resource-watch-policy.ts +35 -0
  209. package/proof/oracle/src/pi/resource-watch.ts +357 -0
  210. package/proof/oracle/src/pi/session-catalogue.ts +318 -0
  211. package/proof/oracle/src/pi/session-config.ts +84 -0
  212. package/proof/oracle/src/pi/session-history.ts +75 -0
  213. package/proof/oracle/src/pi/session-hydrator.ts +219 -0
  214. package/proof/oracle/src/pi/session-registry.ts +815 -0
  215. package/proof/oracle/src/pi/session-storage.ts +29 -0
  216. package/proof/oracle/src/pi/session-title.ts +18 -0
  217. package/proof/oracle/src/pi/turn-paging.ts +94 -0
  218. package/proof/oracle/src/pi/user-message.ts +66 -0
  219. package/proof/oracle/src/protocol/errors.ts +44 -0
  220. package/proof/oracle/src/protocol/jsonrpc.ts +89 -0
  221. package/proof/oracle/src/protocol/version.ts +39 -0
  222. package/proof/oracle/src/transport/websocket.ts +136 -0
  223. package/proof/oracle/src/tunnel/devtunnel.ts +293 -0
  224. package/proof/oracle/src/tunnel/discovery.ts +37 -0
  225. package/proof/oracle/test/active-turn-reconnect.test.ts +274 -0
  226. package/proof/oracle/test/activity.test.ts +171 -0
  227. package/proof/oracle/test/changeset-lifecycle.test.serial.ts +303 -0
  228. package/proof/oracle/test/changeset-uri.test.ts +37 -0
  229. package/proof/oracle/test/changeset.test.serial.ts +487 -0
  230. package/proof/oracle/test/chat-driver.test.ts +691 -0
  231. package/proof/oracle/test/client-actions.test.ts +409 -0
  232. package/proof/oracle/test/client-workarounds.test.ts +320 -0
  233. package/proof/oracle/test/completions.test.ts +323 -0
  234. package/proof/oracle/test/delete-session.test.ts +142 -0
  235. package/proof/oracle/test/direct-settings.test.ts +81 -0
  236. package/proof/oracle/test/event-mapper.test.ts +619 -0
  237. package/proof/oracle/test/fetch-turns.test.ts +207 -0
  238. package/proof/oracle/test/fixtures/abort.json +711 -0
  239. package/proof/oracle/test/fixtures/bash-long-output.json +1312 -0
  240. package/proof/oracle/test/fixtures/compaction.json +6285 -0
  241. package/proof/oracle/test/fixtures/parallel-tools.json +2622 -0
  242. package/proof/oracle/test/fixtures/plain-text.json +1856 -0
  243. package/proof/oracle/test/fixtures/single-tool.json +1128 -0
  244. package/proof/oracle/test/fixtures/steering.json +3926 -0
  245. package/proof/oracle/test/fixtures/tool-bash.json +2372 -0
  246. package/proof/oracle/test/fixtures/tool-edit.json +3983 -0
  247. package/proof/oracle/test/fixtures/tool-error.json +2850 -0
  248. package/proof/oracle/test/fixtures/tool-find.json +1162 -0
  249. package/proof/oracle/test/fixtures/tool-grep.json +2783 -0
  250. package/proof/oracle/test/fixtures/tool-loop.json +1693 -0
  251. package/proof/oracle/test/fixtures/tool-ls.json +2427 -0
  252. package/proof/oracle/test/fixtures/tool-write.json +1271 -0
  253. package/proof/oracle/test/handshake.test.ts +275 -0
  254. package/proof/oracle/test/harness.ts +153 -0
  255. package/proof/oracle/test/hydrated-session-lifecycle.test.ts +211 -0
  256. package/proof/oracle/test/image-input.test.ts +28 -0
  257. package/proof/oracle/test/image-session.test.ts +69 -0
  258. package/proof/oracle/test/live-turn.test.ts +304 -0
  259. package/proof/oracle/test/mapper-fixtures.test.ts +293 -0
  260. package/proof/oracle/test/message-input.test.ts +181 -0
  261. package/proof/oracle/test/model-discovery.test.ts +170 -0
  262. package/proof/oracle/test/models.test.ts +109 -0
  263. package/proof/oracle/test/pi-host.test.ts +91 -0
  264. package/proof/oracle/test/pi-replay.test.ts +141 -0
  265. package/proof/oracle/test/project-trust.test.ts +125 -0
  266. package/proof/oracle/test/protocol-surface.test.ts +121 -0
  267. package/proof/oracle/test/pty.test.ts +100 -0
  268. package/proof/oracle/test/reconnect.test.ts +389 -0
  269. package/proof/oracle/test/resource-watch-policy.test.ts +78 -0
  270. package/proof/oracle/test/resource-watch.test.serial.ts +536 -0
  271. package/proof/oracle/test/resource.test.ts +499 -0
  272. package/proof/oracle/test/schema.test.ts +88 -0
  273. package/proof/oracle/test/session-catalogue.test.ts +314 -0
  274. package/proof/oracle/test/session-config.test.ts +224 -0
  275. package/proof/oracle/test/session-disposal.test.ts +364 -0
  276. package/proof/oracle/test/session-hydration.test.ts +194 -0
  277. package/proof/oracle/test/session-lifecycle.test.ts +416 -0
  278. package/proof/oracle/test/session-storage.test.serial.ts +32 -0
  279. package/proof/oracle/test/session-summary.test.ts +418 -0
  280. package/proof/oracle/test/subscriptions.test.ts +198 -0
  281. package/proof/oracle/test/support/assertions.ts +32 -0
  282. package/proof/oracle/test/support/async.ts +26 -0
  283. package/proof/oracle/test/support/hydrated-session.ts +205 -0
  284. package/proof/oracle/test/support/images.ts +8 -0
  285. package/proof/oracle/test/support/recorded-fixtures.ts +60 -0
  286. package/proof/oracle/test/support/recorded-scenarios.ts +145 -0
  287. package/proof/oracle/test/support/replay.ts +194 -0
  288. package/proof/oracle/test/support/schema.ts +221 -0
  289. package/proof/oracle/test/support/session-files.ts +12 -0
  290. package/proof/oracle/test/support/session-storage.ts +22 -0
  291. package/proof/oracle/test/support/upstream.ts +25 -0
  292. package/proof/oracle/test/support/watch-events.ts +108 -0
  293. package/proof/oracle/test/terminal-service.test.ts +495 -0
  294. package/proof/oracle/test/truncate.test.ts +230 -0
  295. package/proof/oracle/test/tunnel.test.ts +258 -0
  296. package/proof/oracle/test/upstream-workarounds.test.ts +37 -0
  297. package/proof/oracle/test/uri.test.ts +24 -0
  298. package/proof/oracle/test/watch-events.test.ts +118 -0
  299. package/proof/oracle/tsconfig.json +32 -0
  300. package/proof/proof-piglet/piglet.yaml +18 -0
  301. package/proof/tools/fakellm/go.mod +3 -0
  302. package/proof/tools/fakellm/main.go +109 -0
  303. package/proof/tools/gen-dispatchable.py +30 -0
  304. package/provenance.json +28 -0
@@ -0,0 +1,367 @@
1
+ // Package testkit is the test harness shared by the twin tests: an in-memory JSON-RPC
2
+ // client for a host.Host, the counterpart of upstream test/harness.ts (which drives a
3
+ // live host with the Microsoft AHP client). It is test support only and is not imported by
4
+ // the extension.
5
+ package testkit
6
+
7
+ import (
8
+ "bytes"
9
+ "encoding/json"
10
+ "errors"
11
+ "fmt"
12
+ "sync"
13
+ "testing"
14
+ "time"
15
+
16
+ "github.com/microsoft/agent-host-protocol/clients/go/ahptypes"
17
+
18
+ "github.com/MichaelKinsy/pigpen/ahp/internal/host"
19
+ "github.com/MichaelKinsy/pigpen/ahp/internal/wire"
20
+ )
21
+
22
+ // Timeout is how long a request or an awaited message may take before the test fails.
23
+ const Timeout = 2 * time.Second
24
+
25
+ // RPCError is a JSON-RPC error response.
26
+ type RPCError struct {
27
+ Code int `json:"code"`
28
+ Message string `json:"message"`
29
+ Data json.RawMessage `json:"data,omitempty"`
30
+ }
31
+
32
+ func (e *RPCError) Error() string { return fmt.Sprintf("rpc error %d: %s", e.Code, e.Message) }
33
+
34
+ // Notification is a server notification received by a client.
35
+ type Notification struct {
36
+ Method string
37
+ Params json.RawMessage
38
+ }
39
+
40
+ // Envelope decodes an "action" notification.
41
+ func (n Notification) Envelope() (ahptypes.ActionEnvelope, bool) {
42
+ var env ahptypes.ActionEnvelope
43
+ if n.Method != "action" || json.Unmarshal(n.Params, &env) != nil {
44
+ return env, false
45
+ }
46
+ return env, true
47
+ }
48
+
49
+ type memTransport struct{ c *Client }
50
+
51
+ func (m *memTransport) Send(frame []byte) error {
52
+ m.c.deliver(append([]byte(nil), frame...))
53
+ return nil
54
+ }
55
+
56
+ func (m *memTransport) Close() error {
57
+ m.c.mu.Lock()
58
+ m.c.serverClosed = true
59
+ m.c.cond.Broadcast()
60
+ m.c.mu.Unlock()
61
+ return nil
62
+ }
63
+
64
+ // Client is an in-memory AHP client attached to a host.
65
+ type Client struct {
66
+ t *testing.T
67
+ conn *host.Conn
68
+
69
+ mu sync.Mutex
70
+ cond *sync.Cond
71
+ nextID uint64
72
+ clientSeq int64
73
+ pending map[uint64]chan response
74
+ notes []Notification
75
+ cursor int
76
+ serverClosed bool
77
+ }
78
+
79
+ type response struct {
80
+ result json.RawMessage
81
+ err *RPCError
82
+ }
83
+
84
+ // Connect attaches a new client to the host.
85
+ func Connect(t *testing.T, h *host.Host) *Client {
86
+ t.Helper()
87
+ c := &Client{t: t, pending: map[uint64]chan response{}}
88
+ c.cond = sync.NewCond(&c.mu)
89
+ c.conn = h.Accept(&memTransport{c: c})
90
+ return c
91
+ }
92
+
93
+ func (c *Client) deliver(frame []byte) {
94
+ var msg struct {
95
+ ID *uint64 `json:"id"`
96
+ Method string `json:"method"`
97
+ Params json.RawMessage `json:"params"`
98
+ Result json.RawMessage `json:"result"`
99
+ Error *RPCError `json:"error"`
100
+ }
101
+ if err := json.Unmarshal(frame, &msg); err != nil {
102
+ c.t.Errorf("host sent an unparseable frame: %v: %s", err, frame)
103
+ return
104
+ }
105
+ c.mu.Lock()
106
+ defer c.mu.Unlock()
107
+ if msg.Method == "" && msg.ID != nil {
108
+ if ch, ok := c.pending[*msg.ID]; ok {
109
+ delete(c.pending, *msg.ID)
110
+ ch <- response{result: msg.Result, err: msg.Error}
111
+ }
112
+ return
113
+ }
114
+ c.notes = append(c.notes, Notification{Method: msg.Method, Params: msg.Params})
115
+ c.cond.Broadcast()
116
+ }
117
+
118
+ // Request sends a request and waits for its response.
119
+ func (c *Client) Request(method string, params any) (json.RawMessage, *RPCError) {
120
+ c.t.Helper()
121
+ ch := make(chan response, 1)
122
+ c.mu.Lock()
123
+ c.nextID++
124
+ id := c.nextID
125
+ c.pending[id] = ch
126
+ c.mu.Unlock()
127
+ frame, err := json.Marshal(map[string]any{"jsonrpc": "2.0", "id": id, "method": method, "params": params})
128
+ if err != nil {
129
+ c.t.Fatalf("marshal %s: %v", method, err)
130
+ }
131
+ c.conn.Receive(frame)
132
+ select {
133
+ case r := <-ch:
134
+ return r.result, r.err
135
+ case <-time.After(Timeout):
136
+ c.t.Fatalf("%s: no response within %v", method, Timeout)
137
+ return nil, nil
138
+ }
139
+ }
140
+
141
+ // Must returns a successful result or fails the test.
142
+ func (c *Client) Must(method string, params any) json.RawMessage {
143
+ c.t.Helper()
144
+ result, err := c.Request(method, params)
145
+ if err != nil {
146
+ c.t.Fatalf("%s: %v", method, err)
147
+ }
148
+ return result
149
+ }
150
+
151
+ // ExpectError requires the request to fail with the given code and returns the error.
152
+ func (c *Client) ExpectError(method string, params any, code int) *RPCError {
153
+ c.t.Helper()
154
+ result, err := c.Request(method, params)
155
+ if err == nil {
156
+ c.t.Fatalf("%s: expected error %d, got result %s", method, code, result)
157
+ }
158
+ if err.Code != code {
159
+ c.t.Fatalf("%s: expected error %d, got %d: %s", method, code, err.Code, err.Message)
160
+ }
161
+ return err
162
+ }
163
+
164
+ // Notify sends a notification.
165
+ func (c *Client) Notify(method string, params any) {
166
+ c.t.Helper()
167
+ frame, err := json.Marshal(map[string]any{"jsonrpc": "2.0", "method": method, "params": params})
168
+ if err != nil {
169
+ c.t.Fatalf("marshal %s: %v", method, err)
170
+ }
171
+ c.conn.Receive(frame)
172
+ }
173
+
174
+ // Raw sends a frame exactly as given.
175
+ func (c *Client) Raw(frame []byte) { c.conn.Receive(frame) }
176
+
177
+ // Initialize performs the handshake with the supported versions and returns the result.
178
+ func (c *Client) Initialize(clientID string, extra map[string]any) ahptypes.InitializeResult {
179
+ c.t.Helper()
180
+ params := map[string]any{
181
+ "channel": wire.RootChannel, "clientId": clientID,
182
+ "protocolVersions": SupportedVersions(),
183
+ }
184
+ for k, v := range extra {
185
+ params[k] = v
186
+ }
187
+ var result ahptypes.InitializeResult
188
+ c.decode(c.Must("initialize", params), &result)
189
+ return result
190
+ }
191
+
192
+ // SupportedVersions is SUPPORTED_PROTOCOL_VERSIONS of the pinned spec.
193
+ func SupportedVersions() []string {
194
+ return []string{"0.9.0", "0.8.0", "0.7.0", "0.6.0", "0.5.2", "0.5.1"}
195
+ }
196
+
197
+ func (c *Client) decode(raw json.RawMessage, into any) {
198
+ c.t.Helper()
199
+ if err := json.Unmarshal(raw, into); err != nil {
200
+ c.t.Fatalf("decode %s into %T: %v", raw, into, err)
201
+ }
202
+ }
203
+
204
+ // Decode decodes a result.
205
+ func (c *Client) Decode(raw json.RawMessage, into any) { c.t.Helper(); c.decode(raw, into) }
206
+
207
+ // Subscribe subscribes to a channel and returns the result.
208
+ func (c *Client) Subscribe(channel string) ahptypes.SubscribeResult {
209
+ c.t.Helper()
210
+ var result ahptypes.SubscribeResult
211
+ c.decode(c.Must("subscribe", map[string]any{"channel": channel}), &result)
212
+ return result
213
+ }
214
+
215
+ // Unsubscribe releases a channel.
216
+ func (c *Client) Unsubscribe(channel string) {
217
+ c.t.Helper()
218
+ c.Notify("unsubscribe", map[string]any{"channel": channel})
219
+ }
220
+
221
+ // Ping round-trips a ping; with in-order delivery it is a barrier for earlier notifications.
222
+ func (c *Client) Ping() { c.t.Helper(); c.Must("ping", map[string]any{"channel": wire.RootChannel}) }
223
+
224
+ // Dispatch sends a dispatchAction notification and returns its clientSeq. The action is any
225
+ // JSON-marshalable value (a typed action or a map).
226
+ func (c *Client) Dispatch(channel string, action any) int64 {
227
+ c.t.Helper()
228
+ c.mu.Lock()
229
+ c.clientSeq++
230
+ seq := c.clientSeq
231
+ c.mu.Unlock()
232
+ c.Notify("dispatchAction", map[string]any{"channel": channel, "clientSeq": seq, "action": action})
233
+ return seq
234
+ }
235
+
236
+ // Notifications returns a copy of every notification received so far.
237
+ func (c *Client) Notifications() []Notification {
238
+ c.mu.Lock()
239
+ defer c.mu.Unlock()
240
+ return append([]Notification(nil), c.notes...)
241
+ }
242
+
243
+ // Await waits for the next unconsumed notification matching pred; ok is false on timeout.
244
+ func (c *Client) Await(pred func(Notification) bool, timeout time.Duration) (Notification, bool) {
245
+ deadline := time.Now().Add(timeout)
246
+ stop := time.AfterFunc(timeout, func() { c.mu.Lock(); c.cond.Broadcast(); c.mu.Unlock() })
247
+ defer stop.Stop()
248
+ c.mu.Lock()
249
+ defer c.mu.Unlock()
250
+ for {
251
+ for c.cursor < len(c.notes) {
252
+ n := c.notes[c.cursor]
253
+ c.cursor++
254
+ if pred(n) {
255
+ return n, true
256
+ }
257
+ }
258
+ if !time.Now().Before(deadline) {
259
+ return Notification{}, false
260
+ }
261
+ c.cond.Wait()
262
+ }
263
+ }
264
+
265
+ // NextEnvelope waits for the next action envelope on channel (any channel when empty) that
266
+ // matches clientSeq (any when 0).
267
+ func (c *Client) NextEnvelope(channel string, clientSeq int64) ahptypes.ActionEnvelope {
268
+ c.t.Helper()
269
+ n, ok := c.Await(func(n Notification) bool {
270
+ env, isAction := n.Envelope()
271
+ if !isAction {
272
+ return false
273
+ }
274
+ return (channel == "" || env.Channel == channel) && (clientSeq == 0 || (env.Origin != nil && env.Origin.ClientSeq == clientSeq))
275
+ }, Timeout)
276
+ if !ok {
277
+ c.t.Fatalf("timed out waiting for an action envelope on %q (clientSeq %d)", channel, clientSeq)
278
+ }
279
+ env, _ := n.Envelope()
280
+ return env
281
+ }
282
+
283
+ // Silent reports whether no matching notification arrives within d.
284
+ func (c *Client) Silent(pred func(Notification) bool, d time.Duration) bool {
285
+ _, ok := c.Await(pred, d)
286
+ return !ok
287
+ }
288
+
289
+ // ServerClosed reports whether the host closed this client's transport.
290
+ func (c *Client) ServerClosed() bool {
291
+ c.mu.Lock()
292
+ defer c.mu.Unlock()
293
+ return c.serverClosed
294
+ }
295
+
296
+ // Close simulates the transport going away.
297
+ func (c *Client) Close() { c.conn.Closed() }
298
+
299
+ // JSON marshals v, failing the test on error.
300
+ func JSON(t *testing.T, v any) string {
301
+ t.Helper()
302
+ b, err := json.Marshal(v)
303
+ if err != nil {
304
+ t.Fatal(err)
305
+ }
306
+ return string(b)
307
+ }
308
+
309
+ // Normalize decodes JSON into generic values, with numbers as float64, for deep comparison.
310
+ func Normalize(t *testing.T, v any) any {
311
+ t.Helper()
312
+ var b []byte
313
+ switch x := v.(type) {
314
+ case []byte:
315
+ b = x
316
+ case json.RawMessage:
317
+ b = x
318
+ case string:
319
+ b = []byte(x)
320
+ default:
321
+ var err error
322
+ if b, err = json.Marshal(v); err != nil {
323
+ t.Fatal(err)
324
+ }
325
+ }
326
+ var out any
327
+ dec := json.NewDecoder(bytes.NewReader(b))
328
+ if err := dec.Decode(&out); err != nil {
329
+ t.Fatalf("normalize %s: %v", b, err)
330
+ }
331
+ return out
332
+ }
333
+
334
+ // TestAgent is upstream's TEST_AGENT.
335
+ func TestAgent() ahptypes.AgentInfo {
336
+ return ahptypes.AgentInfo{
337
+ Provider: "pi", DisplayName: "pi", Description: "pi coding agent",
338
+ Models: []ahptypes.SessionModelInfo{{Id: "test-model", Provider: "pi", Name: "Test Model"}},
339
+ }
340
+ }
341
+
342
+ // NewHost returns a host with the root channel installed and TestAgent as its only agent.
343
+ func NewHost(opts host.Options) *host.Host {
344
+ if opts.ServerInfo == nil {
345
+ opts.ServerInfo = &ahptypes.Implementation{Name: "pi-ahp", Version: strPtr("0.0.1-test")}
346
+ }
347
+ h := host.New(opts)
348
+ h.InstallRootChannel([]ahptypes.AgentInfo{TestAgent()})
349
+ return h
350
+ }
351
+
352
+ func strPtr(s string) *string { return &s }
353
+
354
+ // Eventually polls cond until it holds or the timeout passes.
355
+ func Eventually(t *testing.T, what string, cond func() bool) {
356
+ t.Helper()
357
+ deadline := time.Now().Add(Timeout)
358
+ for !cond() {
359
+ if time.Now().After(deadline) {
360
+ t.Fatalf("timed out waiting for %s", what)
361
+ }
362
+ time.Sleep(2 * time.Millisecond)
363
+ }
364
+ }
365
+
366
+ // ErrTimeout is returned by helpers that give up.
367
+ var ErrTimeout = errors.New("timed out")
@@ -0,0 +1,27 @@
1
+ // Package twin records which upstream (pi-ahp) test cases have a Go twin.
2
+ //
3
+ // Every upstream case has a twin named after it: [Run] runs one, [Skip] declares a case
4
+ // that cannot be ported and says why. Both take the upstream file (without the
5
+ // .test.ts suffix) and the exact case title as string literals, which the
6
+ // completeness test (../../twins_test.go) reads from the source and compares with
7
+ // testdata/upstream-tests.json. A skipped twin is the visible record of a gap.
8
+ package twin
9
+
10
+ import "testing"
11
+
12
+ // Run runs fn as the subtest for the upstream case (file, title).
13
+ func Run(t *testing.T, file, title string, fn func(t *testing.T)) {
14
+ t.Helper()
15
+ t.Run(title, func(t *testing.T) {
16
+ t.Helper()
17
+ fn(t)
18
+ })
19
+ }
20
+
21
+ // Skip declares the upstream case (file, title) as a skipped twin with a named reason.
22
+ func Skip(t *testing.T, file, title, reason string) {
23
+ t.Helper()
24
+ t.Run(title, func(t *testing.T) {
25
+ t.Skip(reason)
26
+ })
27
+ }
@@ -0,0 +1,106 @@
1
+ // Code generated by proof/tools/gen-dispatchable.py from microsoft/agent-host-protocol
2
+ // types/action-origin.generated.ts (IS_CLIENT_DISPATCHABLE) at the pinned commit; DO NOT EDIT.
3
+
4
+ package wire
5
+
6
+ var clientDispatchable = map[string]bool{
7
+ "annotations/entryRemoved": true,
8
+ "annotations/entrySet": true,
9
+ "annotations/removed": true,
10
+ "annotations/set": true,
11
+ "annotations/updated": true,
12
+ "automation/createRequested": true,
13
+ "automation/removed": true,
14
+ "automation/set": false,
15
+ "automation/updateRequested": true,
16
+ "automationRun/cancelRequested": true,
17
+ "automationRun/lifecycleChanged": false,
18
+ "automationRun/primarySessionChanged": false,
19
+ "automationRun/sessionRemoved": false,
20
+ "automationRun/sessionSet": false,
21
+ "changeset/cleared": false,
22
+ "changeset/contentChanged": false,
23
+ "changeset/fileRemoved": false,
24
+ "changeset/fileSet": false,
25
+ "changeset/filesReviewChanged": true,
26
+ "changeset/operationStatusChanged": false,
27
+ "changeset/operationsChanged": false,
28
+ "changeset/statusChanged": false,
29
+ "chat/activityChanged": false,
30
+ "chat/changesetsChanged": false,
31
+ "chat/delta": false,
32
+ "chat/draftChanged": true,
33
+ "chat/error": false,
34
+ "chat/inputAnswerChanged": true,
35
+ "chat/inputCompleted": true,
36
+ "chat/inputRequested": false,
37
+ "chat/isArchivedChanged": true,
38
+ "chat/pendingMessageRemoved": true,
39
+ "chat/pendingMessageSet": true,
40
+ "chat/queuedMessagesReordered": true,
41
+ "chat/reasoning": false,
42
+ "chat/responsePart": false,
43
+ "chat/toolCallAuthRequired": false,
44
+ "chat/toolCallAuthResolved": false,
45
+ "chat/toolCallComplete": true,
46
+ "chat/toolCallConfirmed": true,
47
+ "chat/toolCallContentChanged": true,
48
+ "chat/toolCallDelta": false,
49
+ "chat/toolCallReady": false,
50
+ "chat/toolCallResultConfirmed": true,
51
+ "chat/toolCallStart": false,
52
+ "chat/truncated": true,
53
+ "chat/turnCancelled": true,
54
+ "chat/turnComplete": false,
55
+ "chat/turnResume": true,
56
+ "chat/turnStarted": true,
57
+ "chat/turnsLoaded": false,
58
+ "chat/usage": false,
59
+ "chat/workingDirectoryRemoved": true,
60
+ "chat/workingDirectorySet": true,
61
+ "resourceWatch/changed": false,
62
+ "root/activeSessionsChanged": false,
63
+ "root/agentsChanged": false,
64
+ "root/configChanged": true,
65
+ "root/terminalsChanged": false,
66
+ "session/activeClientRemoved": true,
67
+ "session/activeClientSet": true,
68
+ "session/activityChanged": false,
69
+ "session/changesetsChanged": false,
70
+ "session/chatAdded": false,
71
+ "session/chatRemoved": false,
72
+ "session/chatUpdated": false,
73
+ "session/configChanged": true,
74
+ "session/creationFailed": false,
75
+ "session/customizationRemoved": false,
76
+ "session/customizationToggled": true,
77
+ "session/customizationUpdated": false,
78
+ "session/customizationsChanged": false,
79
+ "session/defaultChatChanged": false,
80
+ "session/inputNeededRemoved": false,
81
+ "session/inputNeededSet": false,
82
+ "session/isArchivedChanged": true,
83
+ "session/isReadChanged": true,
84
+ "session/mcpServerBackgroundRequested": true,
85
+ "session/mcpServerStartRequested": true,
86
+ "session/mcpServerStateChanged": false,
87
+ "session/mcpServerStopRequested": true,
88
+ "session/metaChanged": false,
89
+ "session/ready": false,
90
+ "session/serverToolsChanged": false,
91
+ "session/titleChanged": true,
92
+ "session/workingDirectoryRemoved": true,
93
+ "session/workingDirectoryReplaced": true,
94
+ "session/workingDirectorySet": true,
95
+ "terminal/claimed": true,
96
+ "terminal/cleared": true,
97
+ "terminal/commandDetectionAvailable": false,
98
+ "terminal/commandExecuted": false,
99
+ "terminal/commandFinished": false,
100
+ "terminal/cwdChanged": false,
101
+ "terminal/data": false,
102
+ "terminal/exited": false,
103
+ "terminal/input": true,
104
+ "terminal/resized": true,
105
+ "terminal/titleChanged": true,
106
+ }
@@ -0,0 +1,15 @@
1
+ package wire
2
+
3
+ import "sort"
4
+
5
+ // ClientDispatchableTypes lists every action type a client may dispatch, sorted.
6
+ func ClientDispatchableTypes() []string {
7
+ var out []string
8
+ for t, ok := range clientDispatchable {
9
+ if ok {
10
+ out = append(out, t)
11
+ }
12
+ }
13
+ sort.Strings(out)
14
+ return out
15
+ }
@@ -0,0 +1,5 @@
1
+ package wire_test
2
+
3
+ import "errors"
4
+
5
+ func asError[T any](err error, target *T) bool { return errors.As(err, target) }