@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,328 @@
1
+ // Package live adapts the running PiG session, seen through the public Go SDK, to the agent
2
+ // interfaces the AHP host drives (pi.Backend and pi.SessionStore). The upstream host embeds Pi
3
+ // and runs many sessions in-process; an extension lives inside ONE session, so exactly that
4
+ // session can be served. Anything else fails with a clear error.
5
+ package live
6
+
7
+ import (
8
+ "context"
9
+ "encoding/json"
10
+ "errors"
11
+ "fmt"
12
+ "sync"
13
+
14
+ sdk "github.com/MichaelKinsy/PiG/extensions/sdk"
15
+ ahptypes "github.com/microsoft/agent-host-protocol/clients/go/ahptypes"
16
+
17
+ "github.com/MichaelKinsy/pigpen/ahp/internal/mapper"
18
+ "github.com/MichaelKinsy/pigpen/ahp/internal/pi"
19
+ )
20
+
21
+ // ErrReplaced is returned once the PiG session this adapter was attached to has been replaced
22
+ // (new session, resume, fork): a retained SDK context belongs to the runtime it was captured in.
23
+ var ErrReplaced = errors.New("the PiG session was replaced; restart the AHP listener to serve the new one")
24
+
25
+ // forwarded are the agent events the AHP event mapper consumes.
26
+ var forwarded = []string{
27
+ sdk.EventAgentStart, sdk.EventTurnStart, sdk.EventTurnEnd,
28
+ sdk.EventMessageStart, sdk.EventMessageUpdate, sdk.EventMessageEnd,
29
+ sdk.EventToolExecutionStart, sdk.EventToolExecutionUpdate, sdk.EventToolExecutionEnd,
30
+ sdk.EventAgentEnd, sdk.EventAgentSettled,
31
+ }
32
+
33
+ // Session is the live PiG session as the AHP host sees it.
34
+ type Session struct {
35
+ mu sync.Mutex
36
+ ctx sdk.Context
37
+ hasCtx bool
38
+ id string
39
+ cwd string
40
+ listeners map[int]func(mapper.Event)
41
+ nextID int
42
+ steering []string // pending steering texts, for the synthesised queue_update
43
+ onModel func()
44
+
45
+ // Log receives diagnostics; nil discards them.
46
+ Log func(format string, args ...any)
47
+ }
48
+
49
+ // New creates an adapter that has not seen a session yet.
50
+ func New() *Session { return &Session{listeners: map[int]func(mapper.Event){}} }
51
+
52
+ func (s *Session) logf(format string, args ...any) {
53
+ if s.Log != nil {
54
+ s.Log(format, args...)
55
+ }
56
+ }
57
+
58
+ // Register wires the adapter's event handlers to the extension. onStart runs on every
59
+ // session_start with the retained context, the session id and its working directory.
60
+ func (s *Session) Register(e *sdk.Extension, onStart func(ctx sdk.Context, sessionID, cwd, reason string), onShutdown func(ctx sdk.Context, reason string)) {
61
+ e.OnSessionStart(func(ctx sdk.Context, data map[string]any) (any, error) {
62
+ id, err := ctx.GetSessionID()
63
+ if err != nil {
64
+ return nil, fmt.Errorf("ahp: cannot read the session id: %w", err)
65
+ }
66
+ s.mu.Lock()
67
+ s.ctx, s.hasCtx, s.id, s.cwd = ctx, true, id, ctx.Cwd()
68
+ s.steering = nil
69
+ s.mu.Unlock()
70
+ reason, _ := data["reason"].(string)
71
+ if onStart != nil {
72
+ onStart(ctx, id, ctx.Cwd(), reason)
73
+ }
74
+ return nil, nil
75
+ })
76
+ e.OnSessionShutdown(func(ctx sdk.Context, data map[string]any) (any, error) {
77
+ s.mu.Lock()
78
+ s.hasCtx = false
79
+ s.mu.Unlock()
80
+ if onShutdown != nil {
81
+ reason, _ := data["reason"].(string)
82
+ onShutdown(ctx, reason)
83
+ }
84
+ return nil, nil
85
+ })
86
+ for _, name := range forwarded {
87
+ e.OnEvent(name, func(_ sdk.Context, data map[string]any) (any, error) {
88
+ s.deliver(name, data)
89
+ return nil, nil
90
+ })
91
+ }
92
+ e.OnEvent(sdk.EventModelSelect, func(sdk.Context, map[string]any) (any, error) {
93
+ s.mu.Lock()
94
+ fn := s.onModel
95
+ s.mu.Unlock()
96
+ if fn != nil {
97
+ fn()
98
+ }
99
+ return nil, nil
100
+ })
101
+ }
102
+
103
+ // OnModelChanged registers fn to run when the active model changes.
104
+ func (s *Session) OnModelChanged(fn func()) { s.mu.Lock(); s.onModel = fn; s.mu.Unlock() }
105
+
106
+ // ID is the id of the live session ("" before session_start).
107
+ func (s *Session) ID() string { s.mu.Lock(); defer s.mu.Unlock(); return s.id }
108
+
109
+ // WorkingDirectory is the live session's working directory.
110
+ func (s *Session) WorkingDirectory() string { s.mu.Lock(); defer s.mu.Unlock(); return s.cwd }
111
+
112
+ // Context returns the retained SDK context, or an error when there is none or its runtime ended.
113
+ func (s *Session) Context() (sdk.Context, error) {
114
+ s.mu.Lock()
115
+ ctx, ok := s.ctx, s.hasCtx
116
+ s.mu.Unlock()
117
+ if !ok {
118
+ return sdk.Context{}, errors.New("no PiG session is running")
119
+ }
120
+ if err := ctx.Err(); err != nil {
121
+ return sdk.Context{}, fmt.Errorf("%w: %v", ErrReplaced, err)
122
+ }
123
+ return ctx, nil
124
+ }
125
+
126
+ func (s *Session) deliver(name string, data map[string]any) {
127
+ event := mapper.Event{}
128
+ for k, v := range data {
129
+ event[k] = v
130
+ }
131
+ event["type"] = name
132
+ // PiG reports the system prompt as a message; it is neither conversation nor input.
133
+ if name == sdk.EventMessageStart || name == sdk.EventMessageEnd {
134
+ if msg, _ := event["message"].(map[string]any); msg != nil && msg["role"] == "system" {
135
+ return
136
+ }
137
+ }
138
+ var queueUpdate mapper.Event
139
+ if name == sdk.EventMessageStart {
140
+ if msg, _ := event["message"].(map[string]any); msg != nil && msg["role"] == "user" {
141
+ queueUpdate = s.consumeSteering(msg)
142
+ }
143
+ }
144
+ s.emit(event)
145
+ if queueUpdate != nil {
146
+ s.emit(queueUpdate)
147
+ }
148
+ }
149
+
150
+ func (s *Session) emit(event mapper.Event) {
151
+ s.mu.Lock()
152
+ listeners := make([]func(mapper.Event), 0, len(s.listeners))
153
+ for i := 0; i < s.nextID; i++ {
154
+ if l, ok := s.listeners[i]; ok {
155
+ listeners = append(listeners, l)
156
+ }
157
+ }
158
+ s.mu.Unlock()
159
+ for _, l := range listeners {
160
+ l(event)
161
+ }
162
+ }
163
+
164
+ // consumeSteering drops a delivered steering message from the pending list and returns the
165
+ // queue_update Pi would have sent (the SDK carries no such event).
166
+ func (s *Session) consumeSteering(message map[string]any) mapper.Event {
167
+ text := mapper.TextFromPiUserContent(message["content"])
168
+ s.mu.Lock()
169
+ defer s.mu.Unlock()
170
+ for i, pending := range s.steering {
171
+ if pending == text {
172
+ s.steering = append(append([]string(nil), s.steering[:i]...), s.steering[i+1:]...)
173
+ return s.queueUpdateLocked()
174
+ }
175
+ }
176
+ return nil
177
+ }
178
+
179
+ func (s *Session) queueUpdateLocked() mapper.Event {
180
+ steering := make([]any, len(s.steering))
181
+ for i, t := range s.steering {
182
+ steering[i] = t
183
+ }
184
+ return mapper.Event{"type": "queue_update", "steering": steering, "followUp": []any{}}
185
+ }
186
+
187
+ // ── pi.Backend ──────────────────────────────────────────────────────────
188
+
189
+ // Subscribe implements pi.Backend.
190
+ func (s *Session) Subscribe(listener func(mapper.Event)) func() {
191
+ s.mu.Lock()
192
+ id := s.nextID
193
+ s.nextID++
194
+ s.listeners[id] = listener
195
+ s.mu.Unlock()
196
+ return func() { s.mu.Lock(); delete(s.listeners, id); s.mu.Unlock() }
197
+ }
198
+
199
+ func content(text string, images []mapper.Image) (any, error) {
200
+ if len(images) == 0 {
201
+ return text, nil
202
+ }
203
+ prepared, err := pi.PrepareImagesForPi(images, true)
204
+ if err != nil {
205
+ return nil, err
206
+ }
207
+ blocks := []map[string]any{{"type": "text", "text": text}}
208
+ for _, img := range prepared {
209
+ blocks = append(blocks, map[string]any{"type": "image", "data": img.Data, "mimeType": img.MimeType})
210
+ }
211
+ return blocks, nil
212
+ }
213
+
214
+ // Prompt implements pi.Backend.
215
+ func (s *Session) Prompt(ctx context.Context, text string, images []mapper.Image) error {
216
+ return s.send(ctx, text, images, "")
217
+ }
218
+
219
+ // Steer implements pi.Backend: the message is delivered to the running turn.
220
+ func (s *Session) Steer(ctx context.Context, text string, images []mapper.Image) error {
221
+ s.mu.Lock()
222
+ s.steering = append(s.steering, text)
223
+ s.mu.Unlock()
224
+ err := s.send(ctx, text, images, "steer")
225
+ if err != nil {
226
+ s.mu.Lock()
227
+ for i, pending := range s.steering {
228
+ if pending == text {
229
+ s.steering = append(s.steering[:i:i], s.steering[i+1:]...)
230
+ break
231
+ }
232
+ }
233
+ s.mu.Unlock()
234
+ }
235
+ return err
236
+ }
237
+
238
+ func (s *Session) send(ctx context.Context, text string, images []mapper.Image, deliverAs string) error {
239
+ if err := ctx.Err(); err != nil {
240
+ return err
241
+ }
242
+ sdkCtx, err := s.Context()
243
+ if err != nil {
244
+ return err
245
+ }
246
+ body, err := content(text, images)
247
+ if err != nil {
248
+ return err
249
+ }
250
+ return sdkCtx.SendUserMessage(body, deliverAs)
251
+ }
252
+
253
+ // Abort implements pi.Backend.
254
+ func (s *Session) Abort(context.Context) error {
255
+ sdkCtx, err := s.Context()
256
+ if err != nil {
257
+ return err
258
+ }
259
+ sdkCtx.Abort()
260
+ return nil
261
+ }
262
+
263
+ // SelectModel implements pi.ModelSelector.
264
+ func (s *Session) SelectModel(_ context.Context, selection ahptypes.ModelSelection) error {
265
+ sdkCtx, err := s.Context()
266
+ if err != nil {
267
+ return err
268
+ }
269
+ if ok, err := sdkCtx.SetModel(selection.Id); err != nil {
270
+ return err
271
+ } else if !ok {
272
+ return fmt.Errorf("the model %s is not available", selection.Id)
273
+ }
274
+ if raw, ok := selection.Config[pi.ThinkingConfigKey]; ok {
275
+ var level string
276
+ if json.Unmarshal(raw, &level) == nil && level != "" {
277
+ sdkCtx.SetThinkingLevel(level)
278
+ }
279
+ }
280
+ return nil
281
+ }
282
+
283
+ // CurrentSelection implements pi.SelectionReporter.
284
+ func (s *Session) CurrentSelection() *ahptypes.ModelSelection {
285
+ sdkCtx, err := s.Context()
286
+ if err != nil {
287
+ return nil
288
+ }
289
+ info, err := sdkCtx.GetModelInfo()
290
+ if err != nil || info == nil {
291
+ return nil
292
+ }
293
+ selection := &ahptypes.ModelSelection{Id: pi.ModelSelectionID(info.Provider, info.ID)}
294
+ if level, err := sdkCtx.GetThinkingLevel(); err == nil && level != "" {
295
+ raw, _ := json.Marshal(level)
296
+ selection.Config = map[string]json.RawMessage{pi.ThinkingConfigKey: raw}
297
+ }
298
+ return selection
299
+ }
300
+
301
+ // Models lists the models the running session can use.
302
+ func (s *Session) Models() []pi.Model {
303
+ sdkCtx, err := s.Context()
304
+ if err != nil {
305
+ return nil
306
+ }
307
+ available, err := sdkCtx.ModelRegistry().GetAvailable()
308
+ if err != nil {
309
+ s.logf("cannot list models: %v", err)
310
+ return nil
311
+ }
312
+ var models []pi.Model
313
+ for _, raw := range available {
314
+ if m, err := pi.ModelFromMap(raw); err == nil {
315
+ models = append(models, m)
316
+ }
317
+ }
318
+ return models
319
+ }
320
+
321
+ // BackendFactory is the pi.BackendFactory of the composition: only the live session has a
322
+ // backend.
323
+ func (s *Session) BackendFactory(session *pi.LiveSession) (pi.Backend, error) {
324
+ if id := s.ID(); id == "" || session.SessionID != id {
325
+ return nil, fmt.Errorf("this host serves the running PiG session only; session %s is not running", session.SessionID)
326
+ }
327
+ return s, nil
328
+ }
@@ -0,0 +1,91 @@
1
+ package live
2
+
3
+ import (
4
+ "reflect"
5
+ "testing"
6
+
7
+ "github.com/MichaelKinsy/pigpen/ahp/internal/mapper"
8
+ "github.com/MichaelKinsy/pigpen/ahp/internal/pi"
9
+ )
10
+
11
+ // Additions: the adapter's event handling, which needs no host (the SDK-facing calls are proved
12
+ // against a real pig in the root package's real-pig tests).
13
+
14
+ func collect(s *Session) (*[]mapper.Event, func()) {
15
+ var got []mapper.Event
16
+ unsub := s.Subscribe(func(e mapper.Event) { got = append(got, e) })
17
+ return &got, unsub
18
+ }
19
+
20
+ func TestDeliverAddsTheTypeAndDropsSystemMessages(t *testing.T) {
21
+ s := New()
22
+ got, _ := collect(s)
23
+ s.deliver("message_start", map[string]any{"message": map[string]any{"role": "system", "content": ""}})
24
+ s.deliver("message_end", map[string]any{"message": map[string]any{"role": "system", "content": ""}})
25
+ s.deliver("agent_start", map[string]any{})
26
+ s.deliver("message_start", map[string]any{"message": map[string]any{"role": "assistant"}})
27
+ types := []any{}
28
+ for _, e := range *got {
29
+ types = append(types, e["type"])
30
+ }
31
+ if !reflect.DeepEqual(types, []any{"agent_start", "message_start"}) {
32
+ t.Fatalf("delivered %v", types)
33
+ }
34
+ }
35
+
36
+ func TestSubscribersAreIndependent(t *testing.T) {
37
+ s := New()
38
+ a, unsubA := collect(s)
39
+ b, _ := collect(s)
40
+ s.deliver("agent_start", map[string]any{})
41
+ unsubA()
42
+ s.deliver("agent_end", map[string]any{})
43
+ if len(*a) != 1 || len(*b) != 2 {
44
+ t.Fatalf("a=%d b=%d", len(*a), len(*b))
45
+ }
46
+ }
47
+
48
+ func TestSteeringIsReportedConsumedWhenItsUserMessageArrives(t *testing.T) {
49
+ s := New()
50
+ got, _ := collect(s)
51
+ s.steering = []string{"first", "second"}
52
+ user := func(text string) map[string]any {
53
+ return map[string]any{"message": map[string]any{"role": "user", "content": []any{map[string]any{"type": "text", "text": text}}}}
54
+ }
55
+ s.deliver("message_start", user("first"))
56
+ // the user message itself, then the synthesised queue_update carrying what is still pending
57
+ if len(*got) != 2 || (*got)[1]["type"] != "queue_update" || !reflect.DeepEqual((*got)[1]["steering"], []any{"second"}) {
58
+ t.Fatalf("events %v", *got)
59
+ }
60
+ s.deliver("message_start", user("not queued"))
61
+ if len(*got) != 3 {
62
+ t.Fatalf("an unrelated user message must not emit a queue_update: %v", *got)
63
+ }
64
+ }
65
+
66
+ func TestOnlyTheRunningSessionHasABackend(t *testing.T) {
67
+ s := New()
68
+ if _, err := s.BackendFactory(&pi.LiveSession{SessionID: "x"}); err == nil {
69
+ t.Fatal("no session is running yet")
70
+ }
71
+ s.id = "live-id"
72
+ if b, err := s.BackendFactory(&pi.LiveSession{SessionID: "live-id"}); err != nil || b == nil {
73
+ t.Fatalf("the running session must get its backend: %v", err)
74
+ }
75
+ if _, err := s.BackendFactory(&pi.LiveSession{SessionID: "other"}); err == nil {
76
+ t.Fatal("another session must not get a backend")
77
+ }
78
+ }
79
+
80
+ func TestCallsWithoutASessionFailClearly(t *testing.T) {
81
+ s := New()
82
+ if _, err := s.Context(); err == nil {
83
+ t.Fatal("no session, no context")
84
+ }
85
+ if err := s.Abort(nil); err == nil {
86
+ t.Fatal("abort without a session must fail")
87
+ }
88
+ if s.CurrentSelection() != nil || s.Models() != nil {
89
+ t.Fatal("nothing to report without a session")
90
+ }
91
+ }
@@ -0,0 +1,113 @@
1
+ package live
2
+
3
+ import (
4
+ "encoding/json"
5
+
6
+ sdk "github.com/MichaelKinsy/PiG/extensions/sdk"
7
+
8
+ "github.com/MichaelKinsy/pigpen/ahp/internal/pi"
9
+ "github.com/MichaelKinsy/pigpen/ahp/internal/pisession"
10
+ )
11
+
12
+ // Store is a pi.SessionStore over the running session: entries come from the host's session
13
+ // mirror, so it is current without reading the file Pi is writing, and a rename goes through the
14
+ // SDK rather than into the file.
15
+ type Store struct{ s *Session }
16
+
17
+ // NewStore returns the live session's store.
18
+ func (s *Session) NewStore() *Store { return &Store{s} }
19
+
20
+ var _ pi.SessionStore = (*Store)(nil)
21
+
22
+ func (st *Store) snapshot() (*pisession.Manager, sdk.Context, error) {
23
+ ctx, err := st.s.Context()
24
+ if err != nil {
25
+ return nil, ctx, err
26
+ }
27
+ raws, err := ctx.GetEntries()
28
+ if err != nil {
29
+ return nil, ctx, err
30
+ }
31
+ entries := make([]pisession.Entry, 0, len(raws))
32
+ for _, raw := range raws {
33
+ var e pisession.Entry
34
+ if json.Unmarshal(raw, &e) == nil && e.Type() != "session" {
35
+ entries = append(entries, e)
36
+ }
37
+ }
38
+ file := ""
39
+ if f, err := ctx.GetSessionFile(); err == nil && f != nil {
40
+ file = *f
41
+ }
42
+ leaf := ""
43
+ if l, err := ctx.GetLeafID(); err == nil && l != nil {
44
+ leaf = *l
45
+ }
46
+ m, err := pisession.FromEntries(st.s.WorkingDirectory(), st.s.ID(), file, entries, leaf)
47
+ if err != nil {
48
+ // A leaf the mirror does not know yet: fall back to the last entry.
49
+ m, err = pisession.FromEntries(st.s.WorkingDirectory(), st.s.ID(), file, entries, "")
50
+ }
51
+ return m, ctx, err
52
+ }
53
+
54
+ // File implements pi.SessionStore.
55
+ func (st *Store) File() string {
56
+ ctx, err := st.s.Context()
57
+ if err != nil {
58
+ return ""
59
+ }
60
+ if f, err := ctx.GetSessionFile(); err == nil && f != nil {
61
+ return *f
62
+ }
63
+ return ""
64
+ }
65
+
66
+ // LeafEntry implements pi.SessionStore.
67
+ func (st *Store) LeafEntry() pisession.Entry {
68
+ m, _, err := st.snapshot()
69
+ if err != nil {
70
+ return nil
71
+ }
72
+ return m.LeafEntry()
73
+ }
74
+
75
+ // SessionName implements pi.SessionStore.
76
+ func (st *Store) SessionName() string {
77
+ ctx, err := st.s.Context()
78
+ if err != nil {
79
+ return ""
80
+ }
81
+ if name, err := ctx.GetSessionName(); err == nil && name != nil {
82
+ return *name
83
+ }
84
+ return ""
85
+ }
86
+
87
+ // AppendSessionInfo implements pi.SessionStore. It renames through the SDK, so the entry is
88
+ // appended by the session's own writer.
89
+ func (st *Store) AppendSessionInfo(name string) (string, error) {
90
+ ctx, err := st.s.Context()
91
+ if err != nil {
92
+ return "", err
93
+ }
94
+ return "", ctx.SetSessionName(name)
95
+ }
96
+
97
+ // Branch implements pi.SessionStore.
98
+ func (st *Store) Branch(fromID string) []pisession.Entry {
99
+ m, _, err := st.snapshot()
100
+ if err != nil {
101
+ return nil
102
+ }
103
+ return m.Branch(fromID)
104
+ }
105
+
106
+ // ContextEntries implements pi.SessionStore.
107
+ func (st *Store) ContextEntries() []pisession.Entry {
108
+ m, _, err := st.snapshot()
109
+ if err != nil {
110
+ return nil
111
+ }
112
+ return m.ContextEntries()
113
+ }