@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,358 @@
1
+ /**
2
+ * Repairs to the traffic of clients whose reading of the protocol differs from
3
+ * this host's. Each entry says what the client does and what would let it go.
4
+ */
5
+
6
+ import { ActionType, JsonRpcErrorCodes, type URI } from "@microsoft/agent-host-protocol";
7
+ import { ProtocolError } from "../protocol/errors.ts";
8
+ import type { JsonRpcMessage, JsonRpcNotification, JsonRpcRequest, JsonRpcResponse } from "../protocol/jsonrpc.ts";
9
+ import { chatIdFromUri, chatUri, ROOT_CHANNEL, sessionIdFromUri, sessionUri } from "./channels.ts";
10
+
11
+ /**
12
+ * Field names that carry session or chat URIs in state, actions, and catalogue
13
+ * notifications. The rewrite itself ignores every other URI scheme.
14
+ */
15
+ const REWRITABLE_FIELDS = new Set(["channel", "resource", "defaultChat", "session", "chat"]);
16
+
17
+ /** VS Code's derived chat URI: `ahp-chat://<anything>/<base64url(sessionUri)>`. */
18
+ const DERIVED_CHAT_URI = /^ahp-chat:\/\/[^/]+\/([^/?#]+)$/;
19
+
20
+ /** The client names VS Code identifies itself with at `initialize`. */
21
+ const VSCODE_CLIENT_NAMES = new Set(["vscode-editor-window", "vscode-agents-window"]);
22
+
23
+ const VSCODE_MATERIALIZED_SESSION_DISPOSAL_REFUSAL =
24
+ "Materialized session disposal is temporarily disabled for VS Code because of a VS Code provisional-session lifecycle bug; this session was kept.";
25
+
26
+ /** Actions after which a session is no longer an abandoned empty draft. */
27
+ const MATERIALIZING_ACTIONS = new Set<string>([
28
+ ActionType.ChatTurnStarted,
29
+ ActionType.ChatPendingMessageSet,
30
+ ActionType.SessionTitleChanged,
31
+ ]);
32
+
33
+ /**
34
+ * The provider scheme affected by this compatibility layer. Keep it local so
35
+ * client-specific routing does not leak into canonical host APIs.
36
+ */
37
+ const PROVIDER_SESSION_SCHEME = "pi";
38
+ type SessionUriDialect = "canonical" | "provider" | "vscode";
39
+
40
+ /** Methods where a direct `pi:/...` target can only mean a session. */
41
+ const PROVIDER_SESSION_METHODS = new Set([
42
+ "createSession",
43
+ "disposeSession",
44
+ "subscribe",
45
+ "unsubscribe",
46
+ "dispatchAction",
47
+ "completions",
48
+ ]);
49
+
50
+ function providerSessionId(uri: URI): string | undefined {
51
+ return sessionIdFromUri(uri, [PROVIDER_SESSION_SCHEME]);
52
+ }
53
+
54
+ function isProviderSession(uri: URI): boolean {
55
+ return uri.toLowerCase().startsWith(`${PROVIDER_SESSION_SCHEME}:/`) && providerSessionId(uri) !== undefined;
56
+ }
57
+
58
+ function canonicalSessionUri(uri: URI): URI | undefined {
59
+ const sessionId = providerSessionId(uri);
60
+ return sessionId ? sessionUri(sessionId) : undefined;
61
+ }
62
+
63
+ function owningSessionUri(uri: URI): URI | undefined {
64
+ const session = canonicalSessionUri(uri);
65
+ if (session) return session;
66
+ const chatId = chatIdFromUri(uri);
67
+ return chatId ? sessionUri(chatId) : undefined;
68
+ }
69
+
70
+ interface MessageParams {
71
+ _meta?: unknown;
72
+ action?: unknown;
73
+ channel?: unknown;
74
+ importConversation?: unknown;
75
+ initialSubscriptions?: unknown;
76
+ rejectionReason?: unknown;
77
+ session?: unknown;
78
+ subscriptions?: unknown;
79
+ }
80
+
81
+ type TrackedSessionState = "creating" | "empty" | "materialized" | "disposing";
82
+
83
+ interface PendingLifecycleRequest {
84
+ readonly kind: "create" | "dispose";
85
+ readonly session: URI;
86
+ }
87
+
88
+ function typeOfAction(value: unknown): string | undefined {
89
+ return typeof value === "object" && value !== null && "type" in value && typeof value.type === "string"
90
+ ? value.type
91
+ : undefined;
92
+ }
93
+
94
+ function hasVscodeClientMeta(value: unknown): boolean {
95
+ return (
96
+ typeof value === "object" &&
97
+ value !== null &&
98
+ ("vscode.telemetryLevel" in value || "vscode.clientConnectionKind" in value)
99
+ );
100
+ }
101
+
102
+ /**
103
+ * Allows VS Code to clean up only sessions known to be unused drafts on this
104
+ * connection. Unknown sessions are protected: reconnect does not carry enough
105
+ * history to prove that they are empty.
106
+ *
107
+ * Remove this tracker when VS Code graduates materialized remote sessions
108
+ * before tearing down its provisional-session service.
109
+ */
110
+ class VscodeSessionDisposalGuard {
111
+ readonly #sessions = new Map<URI, TrackedSessionState>();
112
+ readonly #pendingRequests = new Map<number, PendingLifecycleRequest>();
113
+
114
+ applyToIncoming(message: JsonRpcRequest | JsonRpcNotification, params: MessageParams): void {
115
+ const channel = typeof params.channel === "string" ? params.channel : undefined;
116
+ const actionType = typeOfAction(params.action);
117
+ if (channel && actionType && MATERIALIZING_ACTIONS.has(actionType)) {
118
+ this.#markMaterialized(channel);
119
+ }
120
+ if (!("id" in message) || !channel) {
121
+ return;
122
+ }
123
+ const session = canonicalSessionUri(channel);
124
+ if (!session) {
125
+ return;
126
+ }
127
+ if (message.method === "createSession") {
128
+ this.#sessions.set(session, params.importConversation === undefined ? "creating" : "materialized");
129
+ this.#pendingRequests.set(message.id, { kind: "create", session });
130
+ return;
131
+ }
132
+ if (message.method !== "disposeSession") {
133
+ return;
134
+ }
135
+ if (this.#sessions.get(session) !== "empty") {
136
+ throw new ProtocolError(JsonRpcErrorCodes.InvalidRequest, VSCODE_MATERIALIZED_SESSION_DISPOSAL_REFUSAL);
137
+ }
138
+ this.#sessions.set(session, "disposing");
139
+ this.#pendingRequests.set(message.id, { kind: "dispose", session });
140
+ }
141
+
142
+ applyToOutgoing(message: JsonRpcMessage): void {
143
+ if (!("method" in message)) {
144
+ this.#applyResponse(message);
145
+ return;
146
+ }
147
+ const params = message.params as MessageParams | undefined;
148
+ if (!params) {
149
+ return;
150
+ }
151
+ const actionType = typeOfAction(params.action);
152
+ if (
153
+ message.method === "action" &&
154
+ params.rejectionReason === undefined &&
155
+ actionType !== undefined &&
156
+ MATERIALIZING_ACTIONS.has(actionType) &&
157
+ typeof params.channel === "string"
158
+ ) {
159
+ this.#markMaterialized(params.channel);
160
+ }
161
+ if (message.method === "root/sessionRemoved" && typeof params.session === "string") {
162
+ const session = canonicalSessionUri(params.session);
163
+ if (session) this.#sessions.delete(session);
164
+ }
165
+ }
166
+
167
+ #applyResponse(message: JsonRpcResponse): void {
168
+ const pending = this.#pendingRequests.get(message.id);
169
+ if (!pending) {
170
+ return;
171
+ }
172
+ this.#pendingRequests.delete(message.id);
173
+ const state = this.#sessions.get(pending.session);
174
+ if (pending.kind === "create") {
175
+ if ("result" in message) {
176
+ if (state === "creating") this.#sessions.set(pending.session, "empty");
177
+ } else {
178
+ this.#sessions.delete(pending.session);
179
+ }
180
+ return;
181
+ }
182
+ if ("result" in message) {
183
+ this.#sessions.delete(pending.session);
184
+ } else if (state === "disposing") {
185
+ this.#sessions.set(pending.session, "empty");
186
+ }
187
+ }
188
+
189
+ #markMaterialized(channel: URI): void {
190
+ const session = owningSessionUri(channel);
191
+ if (session && this.#sessions.has(session)) {
192
+ this.#sessions.set(session, "materialized");
193
+ }
194
+ }
195
+ }
196
+
197
+ /**
198
+ * VS Code computes provider-scoped session URIs and derived default-chat URIs
199
+ * instead of using the canonical resources published by the host. It also
200
+ * targets `completions` at the session URI rather than the chat URI. Publishing
201
+ * those shapes globally would impose one client's dialect on every client, so
202
+ * translation remains per connection and core services see canonical URIs.
203
+ *
204
+ * `initialize.clientInfo`, VS Code's namespaced request metadata, and derived
205
+ * chat URIs identify VS Code and gate its workarounds. Independently, a raw
206
+ * `pi:/...` target selects the provider-session URI dialect used by the iOS
207
+ * client. Both observations remain per connection; core services stay canonical.
208
+ *
209
+ * Remove each compatibility branch when its clients emit canonical AHP traffic.
210
+ */
211
+ export class ClientWorkarounds {
212
+ #isVscode = false;
213
+ #usesProviderSessionUris = false;
214
+ readonly #vscodeSessionDisposal = new VscodeSessionDisposalGuard();
215
+
216
+ /** Reads implementation identity without erasing observations from wire traffic. */
217
+ identify(clientInfo: { name?: string } | undefined): void {
218
+ if (VSCODE_CLIENT_NAMES.has(clientInfo?.name ?? "")) this.#isVscode = true;
219
+ }
220
+
221
+ /** Rewrites this connection's parsed request or notification in place. */
222
+ applyToIncoming(message: JsonRpcRequest | JsonRpcNotification): void {
223
+ const params = message.params as MessageParams | undefined;
224
+ if (!params) return;
225
+ this.#observeTraffic(message.method, params);
226
+ // Remove when VS Code includes the AHP 0.9 root-channel discriminant in reconnect.
227
+ if ("id" in message && this.#isVscode && message.method === "reconnect" && params.channel === undefined) {
228
+ params.channel = ROOT_CHANNEL;
229
+ }
230
+
231
+ const dialect = this.#uriDialect();
232
+ const rewrite = (uri: URI) => inbound(uri, dialect);
233
+ if (typeof params.channel === "string") {
234
+ let channel = rewrite(params.channel);
235
+ if (message.method === "completions" && dialect !== "canonical") {
236
+ // Both VS Code and the iOS client currently target completions at the
237
+ // provider-style session URI.
238
+ const sessionId = providerSessionId(channel);
239
+ channel = sessionId ? chatUri(sessionId) : channel;
240
+ }
241
+ // VS Code addresses its session rename to the selected chat. AHP defines
242
+ // `session/titleChanged` only on the owning session.
243
+ if (
244
+ message.method === "dispatchAction" &&
245
+ this.#isVscode &&
246
+ typeOfAction(params.action) === ActionType.SessionTitleChanged
247
+ ) {
248
+ const chatId = chatIdFromUri(channel);
249
+ channel = chatId ? sessionUri(chatId) : channel;
250
+ }
251
+ params.channel = channel;
252
+ }
253
+ // Handshake requests name their subscribed channels in arrays rather than
254
+ // the top-level routing `channel`.
255
+ for (const field of ["initialSubscriptions", "subscriptions"] as const) {
256
+ const subscriptions = params[field];
257
+ if (Array.isArray(subscriptions)) {
258
+ params[field] = subscriptions.map((uri) => (typeof uri === "string" ? rewrite(uri) : uri));
259
+ }
260
+ }
261
+ if (this.#isVscode) {
262
+ this.#vscodeSessionDisposal.applyToIncoming(message, params);
263
+ }
264
+ }
265
+
266
+ /** Returns the outgoing message, rewritten if this client needs it. */
267
+ applyToOutgoing(message: JsonRpcMessage): JsonRpcMessage {
268
+ if (this.#isVscode) {
269
+ this.#vscodeSessionDisposal.applyToOutgoing(message);
270
+ }
271
+ const dialect = this.#uriDialect();
272
+ return dialect === "canonical"
273
+ ? message
274
+ : (rewriteFields(message, (uri) => outbound(uri, dialect)) as JsonRpcMessage);
275
+ }
276
+
277
+ #observeTraffic(method: string, params: MessageParams): void {
278
+ const direct = typeof params.channel === "string" ? params.channel : undefined;
279
+ const listed = [
280
+ ...(Array.isArray(params.initialSubscriptions) ? params.initialSubscriptions : []),
281
+ ...(Array.isArray(params.subscriptions) ? params.subscriptions : []),
282
+ ];
283
+ const observed = direct ? [direct, ...listed] : listed;
284
+ this.#observeVscode(params, observed);
285
+ if (
286
+ !this.#usesProviderSessionUris &&
287
+ (PROVIDER_SESSION_METHODS.has(method) ? observed : listed).some(
288
+ (uri) => typeof uri === "string" && isProviderSession(uri),
289
+ )
290
+ ) {
291
+ this.#usesProviderSessionUris = true;
292
+ }
293
+ }
294
+
295
+ #observeVscode(params: MessageParams, observed: readonly unknown[]): void {
296
+ if (
297
+ hasVscodeClientMeta(params._meta) ||
298
+ observed.some((uri) => typeof uri === "string" && sessionFromDerivedChat(uri) !== undefined)
299
+ ) {
300
+ this.#isVscode = true;
301
+ }
302
+ }
303
+
304
+ #uriDialect(): SessionUriDialect {
305
+ if (this.#isVscode) return "vscode";
306
+ return this.#usesProviderSessionUris ? "provider" : "canonical";
307
+ }
308
+ }
309
+
310
+ /**
311
+ * Translates a URI this client computed into the one this host minted.
312
+ *
313
+ * A derived chat URI is unwrapped regardless of who sent it — it names no channel this
314
+ * host could otherwise serve. A provider-aliased session URI is only rewritten
315
+ * for a client identified as using that alias; unknown schemes are left alone.
316
+ */
317
+ function sessionFromDerivedChat(uri: URI): URI | undefined {
318
+ const [, encoded = ""] = DERIVED_CHAT_URI.exec(uri) ?? [];
319
+ const session = encoded ? Buffer.from(encoded, "base64url").toString("utf8") : "";
320
+ return isProviderSession(session) ? session : undefined;
321
+ }
322
+
323
+ function inbound(uri: URI, dialect: SessionUriDialect): URI {
324
+ const derivedSession = sessionFromDerivedChat(uri);
325
+ const derivedSessionId = derivedSession ? providerSessionId(derivedSession) : undefined;
326
+ if (derivedSessionId) return chatUri(derivedSessionId);
327
+ if (dialect === "canonical" || !isProviderSession(uri)) return uri;
328
+ const sessionId = providerSessionId(uri);
329
+ return sessionId ? sessionUri(sessionId) : uri;
330
+ }
331
+
332
+ /** Translates a URI this host minted into the dialect this client expects. */
333
+ function outbound(uri: URI, dialect: Exclude<SessionUriDialect, "canonical">): URI {
334
+ const chatId = chatIdFromUri(uri);
335
+ if (chatId) {
336
+ return dialect === "vscode"
337
+ ? `ahp-chat://default/${Buffer.from(`${PROVIDER_SESSION_SCHEME}:/${chatId}`).toString("base64url")}`
338
+ : uri;
339
+ }
340
+ const sessionId = providerSessionId(uri);
341
+ return sessionId ? `${PROVIDER_SESSION_SCHEME}:/${sessionId}` : uri;
342
+ }
343
+
344
+ /** Applies `rewrite` to every rewritable field, however deeply nested. */
345
+ function rewriteFields(value: unknown, rewrite: (uri: URI) => URI): unknown {
346
+ if (Array.isArray(value)) {
347
+ return value.map((item) => rewriteFields(item, rewrite));
348
+ }
349
+ if (typeof value !== "object" || value === null) {
350
+ return value;
351
+ }
352
+ const out: Record<string, unknown> = {};
353
+ for (const [key, item] of Object.entries(value)) {
354
+ out[key] =
355
+ REWRITABLE_FIELDS.has(key) && typeof item === "string" ? rewrite(item as URI) : rewriteFields(item, rewrite);
356
+ }
357
+ return out;
358
+ }
@@ -0,0 +1,41 @@
1
+ /** One transport connection and its per-socket subscriptions. */
2
+
3
+ import type { URI } from "@microsoft/agent-host-protocol";
4
+ import type { JsonRpcMessage } from "../protocol/jsonrpc.ts";
5
+ import { ClientWorkarounds } from "./client-workarounds.ts";
6
+
7
+ /** A bidirectional, ordered, reliable message stream carrying one JSON-RPC message per frame. */
8
+ export interface Transport {
9
+ send(message: JsonRpcMessage): void;
10
+ close(): void;
11
+ onMessage(handler: (message: unknown) => void): void;
12
+ onClose(handler: () => void): void;
13
+ }
14
+
15
+ export class ClientConnection {
16
+ /** Assigned at `initialize` / `reconnect`; empty until the handshake completes. */
17
+ clientId = "";
18
+ readonly subscriptions = new Set<URI>();
19
+ readonly transport: Transport;
20
+ readonly workarounds = new ClientWorkarounds();
21
+
22
+ constructor(transport: Transport) {
23
+ this.transport = transport;
24
+ }
25
+
26
+ send(message: JsonRpcMessage): void {
27
+ this.transport.send(this.workarounds.applyToOutgoing(message));
28
+ }
29
+
30
+ subscribe(channel: URI): void {
31
+ this.subscriptions.add(channel);
32
+ }
33
+
34
+ unsubscribe(channel: URI): void {
35
+ this.subscriptions.delete(channel);
36
+ }
37
+
38
+ isSubscribed(channel: URI): boolean {
39
+ return this.subscriptions.has(channel);
40
+ }
41
+ }