@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.
- package/CREDITS.md +27 -0
- package/LICENSE +23 -0
- package/README.md +148 -0
- package/extensions/ahp/access_options_test.go +116 -0
- package/extensions/ahp/extension.go +54 -0
- package/extensions/ahp/go.mod +12 -0
- package/extensions/ahp/go.sum +2 -0
- package/extensions/ahp/go.work +8 -0
- package/extensions/ahp/internal/channels/chat.go +110 -0
- package/extensions/ahp/internal/channels/root.go +32 -0
- package/extensions/ahp/internal/channels/session.go +128 -0
- package/extensions/ahp/internal/channels/session_test.go +108 -0
- package/extensions/ahp/internal/compose/compose.go +153 -0
- package/extensions/ahp/internal/compose/compose_test.go +120 -0
- package/extensions/ahp/internal/gaps/gaps_test.go +88 -0
- package/extensions/ahp/internal/host/handshake_test.go +206 -0
- package/extensions/ahp/internal/host/host.go +1305 -0
- package/extensions/ahp/internal/host/reconnect_test.go +197 -0
- package/extensions/ahp/internal/host/replaywindow_test.go +31 -0
- package/extensions/ahp/internal/host/schema_test.go +79 -0
- package/extensions/ahp/internal/host/store.go +291 -0
- package/extensions/ahp/internal/host/store_update_test.go +85 -0
- package/extensions/ahp/internal/host/subscriptions_test.go +160 -0
- package/extensions/ahp/internal/host/surface_test.go +95 -0
- package/extensions/ahp/internal/host/workarounds.go +458 -0
- package/extensions/ahp/internal/host/workarounds_test.go +96 -0
- package/extensions/ahp/internal/live/live.go +328 -0
- package/extensions/ahp/internal/live/live_test.go +91 -0
- package/extensions/ahp/internal/live/store.go +113 -0
- package/extensions/ahp/internal/mapper/activity.go +199 -0
- package/extensions/ahp/internal/mapper/activity_test.go +188 -0
- package/extensions/ahp/internal/mapper/bench_test.go +38 -0
- package/extensions/ahp/internal/mapper/event_mapper_test.go +380 -0
- package/extensions/ahp/internal/mapper/fixtures_test.go +387 -0
- package/extensions/ahp/internal/mapper/helpers_test.go +251 -0
- package/extensions/ahp/internal/mapper/js.go +131 -0
- package/extensions/ahp/internal/mapper/mapper.go +600 -0
- package/extensions/ahp/internal/mapper/message_input_test.go +196 -0
- package/extensions/ahp/internal/mapper/messageinput.go +272 -0
- package/extensions/ahp/internal/mapper/schema_helpers_test.go +5 -0
- package/extensions/ahp/internal/mapper/testdata/LICENSE-pi-ahp +21 -0
- package/extensions/ahp/internal/mapper/testdata/fixtures/abort.json +711 -0
- package/extensions/ahp/internal/mapper/testdata/fixtures/bash-long-output.json +1312 -0
- package/extensions/ahp/internal/mapper/testdata/fixtures/compaction.json +6285 -0
- package/extensions/ahp/internal/mapper/testdata/fixtures/parallel-tools.json +2622 -0
- package/extensions/ahp/internal/mapper/testdata/fixtures/plain-text.json +1856 -0
- package/extensions/ahp/internal/mapper/testdata/fixtures/single-tool.json +1128 -0
- package/extensions/ahp/internal/mapper/testdata/fixtures/steering.json +3926 -0
- package/extensions/ahp/internal/mapper/testdata/fixtures/tool-bash.json +2372 -0
- package/extensions/ahp/internal/mapper/testdata/fixtures/tool-edit.json +3983 -0
- package/extensions/ahp/internal/mapper/testdata/fixtures/tool-error.json +2850 -0
- package/extensions/ahp/internal/mapper/testdata/fixtures/tool-find.json +1162 -0
- package/extensions/ahp/internal/mapper/testdata/fixtures/tool-grep.json +2783 -0
- package/extensions/ahp/internal/mapper/testdata/fixtures/tool-loop.json +1693 -0
- package/extensions/ahp/internal/mapper/testdata/fixtures/tool-ls.json +2427 -0
- package/extensions/ahp/internal/mapper/testdata/fixtures/tool-write.json +1271 -0
- package/extensions/ahp/internal/mapper/title.go +34 -0
- package/extensions/ahp/internal/mapper/usermsg.go +87 -0
- package/extensions/ahp/internal/pi/activeturn_test.go +296 -0
- package/extensions/ahp/internal/pi/backend.go +64 -0
- package/extensions/ahp/internal/pi/catalogue.go +370 -0
- package/extensions/ahp/internal/pi/catalogue_test.go +271 -0
- package/extensions/ahp/internal/pi/chatdriver.go +533 -0
- package/extensions/ahp/internal/pi/chatdriver_test.go +416 -0
- package/extensions/ahp/internal/pi/clientactions.go +212 -0
- package/extensions/ahp/internal/pi/clientactions_test.go +273 -0
- package/extensions/ahp/internal/pi/completions.go +230 -0
- package/extensions/ahp/internal/pi/completions_test.go +272 -0
- package/extensions/ahp/internal/pi/config_test.go +269 -0
- package/extensions/ahp/internal/pi/deletesession.go +69 -0
- package/extensions/ahp/internal/pi/deletesession_test.go +153 -0
- package/extensions/ahp/internal/pi/disposal_test.go +362 -0
- package/extensions/ahp/internal/pi/fixture_test.go +308 -0
- package/extensions/ahp/internal/pi/foreign_test.go +122 -0
- package/extensions/ahp/internal/pi/harness_test.go +83 -0
- package/extensions/ahp/internal/pi/history.go +269 -0
- package/extensions/ahp/internal/pi/hydrated_test.go +171 -0
- package/extensions/ahp/internal/pi/hydratedlifecycle_test.go +196 -0
- package/extensions/ahp/internal/pi/hydration_test.go +209 -0
- package/extensions/ahp/internal/pi/hydrator.go +212 -0
- package/extensions/ahp/internal/pi/imageinput.go +224 -0
- package/extensions/ahp/internal/pi/imageinput_test.go +57 -0
- package/extensions/ahp/internal/pi/lifecycle_test.go +381 -0
- package/extensions/ahp/internal/pi/models.go +212 -0
- package/extensions/ahp/internal/pi/models_test.go +128 -0
- package/extensions/ahp/internal/pi/paging.go +95 -0
- package/extensions/ahp/internal/pi/pagingtruncate_test.go +323 -0
- package/extensions/ahp/internal/pi/projecttrust.go +222 -0
- package/extensions/ahp/internal/pi/registry.go +924 -0
- package/extensions/ahp/internal/pi/restart_test.go +185 -0
- package/extensions/ahp/internal/pi/services.go +70 -0
- package/extensions/ahp/internal/pi/sessionconfig.go +64 -0
- package/extensions/ahp/internal/pi/sessionfiles_test.go +74 -0
- package/extensions/ahp/internal/pi/sessionstore.go +83 -0
- package/extensions/ahp/internal/pi/summary_test.go +426 -0
- package/extensions/ahp/internal/pi/workarounds_wire_test.go +208 -0
- package/extensions/ahp/internal/pisession/fromentries_test.go +32 -0
- package/extensions/ahp/internal/pisession/json.go +31 -0
- package/extensions/ahp/internal/pisession/pisession.go +580 -0
- package/extensions/ahp/internal/settings/access.go +63 -0
- package/extensions/ahp/internal/settings/access_test.go +52 -0
- package/extensions/ahp/internal/settings/settings.go +117 -0
- package/extensions/ahp/internal/settings/settings_test.go +104 -0
- package/extensions/ahp/internal/svc/etag_other.go +15 -0
- package/extensions/ahp/internal/svc/etag_unix.go +34 -0
- package/extensions/ahp/internal/svc/glob.go +197 -0
- package/extensions/ahp/internal/svc/mime.go +37 -0
- package/extensions/ahp/internal/svc/paths.go +108 -0
- package/extensions/ahp/internal/svc/pty.go +64 -0
- package/extensions/ahp/internal/svc/pty_darwin.go +42 -0
- package/extensions/ahp/internal/svc/pty_linux.go +36 -0
- package/extensions/ahp/internal/svc/pty_other.go +9 -0
- package/extensions/ahp/internal/svc/pty_test.go +114 -0
- package/extensions/ahp/internal/svc/pty_unix.go +147 -0
- package/extensions/ahp/internal/svc/resource.go +495 -0
- package/extensions/ahp/internal/svc/resource_test.go +501 -0
- package/extensions/ahp/internal/svc/stat_bsd.go +9 -0
- package/extensions/ahp/internal/svc/stat_linux.go +9 -0
- package/extensions/ahp/internal/svc/terminal.go +454 -0
- package/extensions/ahp/internal/svc/terminal_test.go +512 -0
- package/extensions/ahp/internal/svc/watch.go +590 -0
- package/extensions/ahp/internal/svc/watch_test.go +627 -0
- package/extensions/ahp/internal/svc/watchevents_test.go +452 -0
- package/extensions/ahp/internal/svc/watchpolicy.go +81 -0
- package/extensions/ahp/internal/svc/watchpolicy_test.go +151 -0
- package/extensions/ahp/internal/svc/watchracy_test.go +61 -0
- package/extensions/ahp/internal/testkit/schema/LICENSE-agent-host-protocol +21 -0
- package/extensions/ahp/internal/testkit/schema/actions.schema.json +9208 -0
- package/extensions/ahp/internal/testkit/schema/commands.schema.json +10862 -0
- package/extensions/ahp/internal/testkit/schema/errors.schema.json +10928 -0
- package/extensions/ahp/internal/testkit/schema/notifications.schema.json +6667 -0
- package/extensions/ahp/internal/testkit/schema/state.schema.json +6392 -0
- package/extensions/ahp/internal/testkit/schema.go +360 -0
- package/extensions/ahp/internal/testkit/testkit.go +367 -0
- package/extensions/ahp/internal/twin/twin.go +27 -0
- package/extensions/ahp/internal/wire/dispatchable.go +106 -0
- package/extensions/ahp/internal/wire/dispatchable_list.go +15 -0
- package/extensions/ahp/internal/wire/helpers_test.go +5 -0
- package/extensions/ahp/internal/wire/wire.go +218 -0
- package/extensions/ahp/internal/wire/wire_test.go +123 -0
- package/extensions/ahp/internal/ws/helpers_test.go +7 -0
- package/extensions/ahp/internal/ws/vscode_test.go +198 -0
- package/extensions/ahp/internal/ws/ws.go +674 -0
- package/extensions/ahp/internal/ws/ws_test.go +324 -0
- package/extensions/ahp/realpig_test.go +589 -0
- package/extensions/ahp/runtime.go +311 -0
- package/extensions/ahp/runtime_test.go +110 -0
- package/extensions/ahp/testdata/upstream-tests.json +3833 -0
- package/extensions/ahp/third_party/agent-host-protocol-go/LICENSE +21 -0
- package/extensions/ahp/third_party/agent-host-protocol-go/NOTICE-PIGPEN.md +18 -0
- package/extensions/ahp/third_party/agent-host-protocol-go/ahp/client.go +1011 -0
- package/extensions/ahp/third_party/agent-host-protocol-go/ahp/error.go +111 -0
- package/extensions/ahp/third_party/agent-host-protocol-go/ahp/multi_host_state_mirror.go +239 -0
- package/extensions/ahp/third_party/agent-host-protocol-go/ahp/reducers.go +1939 -0
- package/extensions/ahp/third_party/agent-host-protocol-go/ahp/transport.go +176 -0
- package/extensions/ahp/third_party/agent-host-protocol-go/ahptypes/actions.generated.go +2447 -0
- package/extensions/ahp/third_party/agent-host-protocol-go/ahptypes/commands.generated.go +1546 -0
- package/extensions/ahp/third_party/agent-host-protocol-go/ahptypes/common.go +205 -0
- package/extensions/ahp/third_party/agent-host-protocol-go/ahptypes/errors.generated.go +65 -0
- package/extensions/ahp/third_party/agent-host-protocol-go/ahptypes/messages.generated.go +138 -0
- package/extensions/ahp/third_party/agent-host-protocol-go/ahptypes/notifications.generated.go +264 -0
- package/extensions/ahp/third_party/agent-host-protocol-go/ahptypes/state.generated.go +6171 -0
- package/extensions/ahp/third_party/agent-host-protocol-go/ahptypes/version.generated.go +31 -0
- package/extensions/ahp/third_party/agent-host-protocol-go/go.mod +3 -0
- package/extensions/ahp/twins_test.go +91 -0
- package/package.json +40 -0
- package/proof/PORT.md +121 -0
- package/proof/mutations.json +152 -0
- package/proof/oracle/LICENSE +21 -0
- package/proof/oracle/README.md +120 -0
- package/proof/oracle/UPSTREAM.md +7 -0
- package/proof/oracle/package.json +68 -0
- package/proof/oracle/src/bin/cli.ts +66 -0
- package/proof/oracle/src/bin/tunnel.ts +128 -0
- package/proof/oracle/src/channels/chat.ts +134 -0
- package/proof/oracle/src/channels/root.ts +39 -0
- package/proof/oracle/src/channels/session.ts +118 -0
- package/proof/oracle/src/channels/terminal.ts +11 -0
- package/proof/oracle/src/core/channels.ts +108 -0
- package/proof/oracle/src/core/client-workarounds.ts +358 -0
- package/proof/oracle/src/core/connection.ts +41 -0
- package/proof/oracle/src/core/host.ts +882 -0
- package/proof/oracle/src/core/sequencer.ts +75 -0
- package/proof/oracle/src/core/state-store.ts +162 -0
- package/proof/oracle/src/core/uri.ts +25 -0
- package/proof/oracle/src/host/direct-settings.ts +110 -0
- package/proof/oracle/src/host/pi-host.ts +220 -0
- package/proof/oracle/src/host/serve.ts +71 -0
- package/proof/oracle/src/host/terminal-service.ts +346 -0
- package/proof/oracle/src/pi/activity.ts +171 -0
- package/proof/oracle/src/pi/changeset-service.ts +646 -0
- package/proof/oracle/src/pi/changeset-uri.ts +64 -0
- package/proof/oracle/src/pi/chat-driver.ts +529 -0
- package/proof/oracle/src/pi/completions.ts +160 -0
- package/proof/oracle/src/pi/delete-session.ts +53 -0
- package/proof/oracle/src/pi/event-mapper.ts +648 -0
- package/proof/oracle/src/pi/git-changes.ts +605 -0
- package/proof/oracle/src/pi/history.ts +305 -0
- package/proof/oracle/src/pi/image-input.ts +56 -0
- package/proof/oracle/src/pi/image-mime.ts +8 -0
- package/proof/oracle/src/pi/in-process-backend.ts +155 -0
- package/proof/oracle/src/pi/message-input.ts +212 -0
- package/proof/oracle/src/pi/models.ts +124 -0
- package/proof/oracle/src/pi/project-trust.ts +66 -0
- package/proof/oracle/src/pi/provider.ts +2 -0
- package/proof/oracle/src/pi/resource-paths.ts +80 -0
- package/proof/oracle/src/pi/resource-service.ts +356 -0
- package/proof/oracle/src/pi/resource-watch-policy.ts +35 -0
- package/proof/oracle/src/pi/resource-watch.ts +357 -0
- package/proof/oracle/src/pi/session-catalogue.ts +318 -0
- package/proof/oracle/src/pi/session-config.ts +84 -0
- package/proof/oracle/src/pi/session-history.ts +75 -0
- package/proof/oracle/src/pi/session-hydrator.ts +219 -0
- package/proof/oracle/src/pi/session-registry.ts +815 -0
- package/proof/oracle/src/pi/session-storage.ts +29 -0
- package/proof/oracle/src/pi/session-title.ts +18 -0
- package/proof/oracle/src/pi/turn-paging.ts +94 -0
- package/proof/oracle/src/pi/user-message.ts +66 -0
- package/proof/oracle/src/protocol/errors.ts +44 -0
- package/proof/oracle/src/protocol/jsonrpc.ts +89 -0
- package/proof/oracle/src/protocol/version.ts +39 -0
- package/proof/oracle/src/transport/websocket.ts +136 -0
- package/proof/oracle/src/tunnel/devtunnel.ts +293 -0
- package/proof/oracle/src/tunnel/discovery.ts +37 -0
- package/proof/oracle/test/active-turn-reconnect.test.ts +274 -0
- package/proof/oracle/test/activity.test.ts +171 -0
- package/proof/oracle/test/changeset-lifecycle.test.serial.ts +303 -0
- package/proof/oracle/test/changeset-uri.test.ts +37 -0
- package/proof/oracle/test/changeset.test.serial.ts +487 -0
- package/proof/oracle/test/chat-driver.test.ts +691 -0
- package/proof/oracle/test/client-actions.test.ts +409 -0
- package/proof/oracle/test/client-workarounds.test.ts +320 -0
- package/proof/oracle/test/completions.test.ts +323 -0
- package/proof/oracle/test/delete-session.test.ts +142 -0
- package/proof/oracle/test/direct-settings.test.ts +81 -0
- package/proof/oracle/test/event-mapper.test.ts +619 -0
- package/proof/oracle/test/fetch-turns.test.ts +207 -0
- package/proof/oracle/test/fixtures/abort.json +711 -0
- package/proof/oracle/test/fixtures/bash-long-output.json +1312 -0
- package/proof/oracle/test/fixtures/compaction.json +6285 -0
- package/proof/oracle/test/fixtures/parallel-tools.json +2622 -0
- package/proof/oracle/test/fixtures/plain-text.json +1856 -0
- package/proof/oracle/test/fixtures/single-tool.json +1128 -0
- package/proof/oracle/test/fixtures/steering.json +3926 -0
- package/proof/oracle/test/fixtures/tool-bash.json +2372 -0
- package/proof/oracle/test/fixtures/tool-edit.json +3983 -0
- package/proof/oracle/test/fixtures/tool-error.json +2850 -0
- package/proof/oracle/test/fixtures/tool-find.json +1162 -0
- package/proof/oracle/test/fixtures/tool-grep.json +2783 -0
- package/proof/oracle/test/fixtures/tool-loop.json +1693 -0
- package/proof/oracle/test/fixtures/tool-ls.json +2427 -0
- package/proof/oracle/test/fixtures/tool-write.json +1271 -0
- package/proof/oracle/test/handshake.test.ts +275 -0
- package/proof/oracle/test/harness.ts +153 -0
- package/proof/oracle/test/hydrated-session-lifecycle.test.ts +211 -0
- package/proof/oracle/test/image-input.test.ts +28 -0
- package/proof/oracle/test/image-session.test.ts +69 -0
- package/proof/oracle/test/live-turn.test.ts +304 -0
- package/proof/oracle/test/mapper-fixtures.test.ts +293 -0
- package/proof/oracle/test/message-input.test.ts +181 -0
- package/proof/oracle/test/model-discovery.test.ts +170 -0
- package/proof/oracle/test/models.test.ts +109 -0
- package/proof/oracle/test/pi-host.test.ts +91 -0
- package/proof/oracle/test/pi-replay.test.ts +141 -0
- package/proof/oracle/test/project-trust.test.ts +125 -0
- package/proof/oracle/test/protocol-surface.test.ts +121 -0
- package/proof/oracle/test/pty.test.ts +100 -0
- package/proof/oracle/test/reconnect.test.ts +389 -0
- package/proof/oracle/test/resource-watch-policy.test.ts +78 -0
- package/proof/oracle/test/resource-watch.test.serial.ts +536 -0
- package/proof/oracle/test/resource.test.ts +499 -0
- package/proof/oracle/test/schema.test.ts +88 -0
- package/proof/oracle/test/session-catalogue.test.ts +314 -0
- package/proof/oracle/test/session-config.test.ts +224 -0
- package/proof/oracle/test/session-disposal.test.ts +364 -0
- package/proof/oracle/test/session-hydration.test.ts +194 -0
- package/proof/oracle/test/session-lifecycle.test.ts +416 -0
- package/proof/oracle/test/session-storage.test.serial.ts +32 -0
- package/proof/oracle/test/session-summary.test.ts +418 -0
- package/proof/oracle/test/subscriptions.test.ts +198 -0
- package/proof/oracle/test/support/assertions.ts +32 -0
- package/proof/oracle/test/support/async.ts +26 -0
- package/proof/oracle/test/support/hydrated-session.ts +205 -0
- package/proof/oracle/test/support/images.ts +8 -0
- package/proof/oracle/test/support/recorded-fixtures.ts +60 -0
- package/proof/oracle/test/support/recorded-scenarios.ts +145 -0
- package/proof/oracle/test/support/replay.ts +194 -0
- package/proof/oracle/test/support/schema.ts +221 -0
- package/proof/oracle/test/support/session-files.ts +12 -0
- package/proof/oracle/test/support/session-storage.ts +22 -0
- package/proof/oracle/test/support/upstream.ts +25 -0
- package/proof/oracle/test/support/watch-events.ts +108 -0
- package/proof/oracle/test/terminal-service.test.ts +495 -0
- package/proof/oracle/test/truncate.test.ts +230 -0
- package/proof/oracle/test/tunnel.test.ts +258 -0
- package/proof/oracle/test/upstream-workarounds.test.ts +37 -0
- package/proof/oracle/test/uri.test.ts +24 -0
- package/proof/oracle/test/watch-events.test.ts +118 -0
- package/proof/oracle/tsconfig.json +32 -0
- package/proof/proof-piglet/piglet.yaml +18 -0
- package/proof/tools/fakellm/go.mod +3 -0
- package/proof/tools/fakellm/main.go +109 -0
- package/proof/tools/gen-dispatchable.py +30 -0
- package/provenance.json +28 -0
|
@@ -0,0 +1,458 @@
|
|
|
1
|
+
package host
|
|
2
|
+
|
|
3
|
+
import (
|
|
4
|
+
"encoding/base64"
|
|
5
|
+
"encoding/json"
|
|
6
|
+
"regexp"
|
|
7
|
+
"strings"
|
|
8
|
+
|
|
9
|
+
"github.com/MichaelKinsy/pigpen/ahp/internal/wire"
|
|
10
|
+
)
|
|
11
|
+
|
|
12
|
+
// Repairs to the traffic of clients whose reading of the protocol differs from this host's. Each
|
|
13
|
+
// entry says what the client does and what would let it go. Port of src/core/client-workarounds.ts.
|
|
14
|
+
// Messages are handled as generic decoded JSON (map[string]any, numbers as json.Number or float64).
|
|
15
|
+
|
|
16
|
+
// rewritableFields carry session or chat URIs in state, actions and catalogue notifications.
|
|
17
|
+
var rewritableFields = map[string]bool{"channel": true, "resource": true, "defaultChat": true, "session": true, "chat": true}
|
|
18
|
+
|
|
19
|
+
// derivedChatURI is VS Code's derived chat URI: ahp-chat://<anything>/<base64url(sessionUri)>.
|
|
20
|
+
var derivedChatURI = regexp.MustCompile(`^ahp-chat://[^/]+/([^/?#]+)$`)
|
|
21
|
+
|
|
22
|
+
var vscodeClientNames = map[string]bool{"vscode-editor-window": true, "vscode-agents-window": true}
|
|
23
|
+
|
|
24
|
+
const vscodeMaterializedSessionDisposalRefusal = "Materialized session disposal is temporarily disabled for VS Code because of a VS Code provisional-session lifecycle bug; this session was kept."
|
|
25
|
+
|
|
26
|
+
// materializingActions are those after which a session is no longer an abandoned empty draft.
|
|
27
|
+
var materializingActions = map[string]bool{"chat/turnStarted": true, "chat/pendingMessageSet": true, "session/titleChanged": true}
|
|
28
|
+
|
|
29
|
+
// providerSessionMethods are the methods where a direct pi:/... target can only mean a session.
|
|
30
|
+
var providerSessionMethods = map[string]bool{
|
|
31
|
+
"createSession": true, "disposeSession": true, "subscribe": true, "unsubscribe": true, "dispatchAction": true, "completions": true,
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
type uriDialect int
|
|
35
|
+
|
|
36
|
+
const (
|
|
37
|
+
dialectCanonical uriDialect = iota
|
|
38
|
+
dialectProvider
|
|
39
|
+
dialectVSCode
|
|
40
|
+
)
|
|
41
|
+
|
|
42
|
+
// ClientWorkarounds translates one connection's traffic. VS Code computes provider-scoped session
|
|
43
|
+
// URIs and derived default-chat URIs instead of using the canonical resources published by the host.
|
|
44
|
+
// It also targets completions at the session URI rather than the chat URI. Publishing those shapes
|
|
45
|
+
// globally would impose one client's dialect on every client, so translation stays per connection
|
|
46
|
+
// and core services see canonical URIs.
|
|
47
|
+
type ClientWorkarounds struct {
|
|
48
|
+
provider string
|
|
49
|
+
isVSCode bool
|
|
50
|
+
usesProv bool
|
|
51
|
+
guard *vscodeDisposalGuard
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
// NewClientWorkarounds returns a translator for the given provider scheme ("pi").
|
|
55
|
+
func NewClientWorkarounds(providerScheme string) *ClientWorkarounds {
|
|
56
|
+
return &ClientWorkarounds{provider: providerScheme, guard: newVSCodeDisposalGuard(providerScheme)}
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
// Identify reads the implementation name given at initialize without erasing observations from wire traffic.
|
|
60
|
+
func (w *ClientWorkarounds) Identify(clientName string) {
|
|
61
|
+
if vscodeClientNames[clientName] {
|
|
62
|
+
w.isVSCode = true
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
func (w *ClientWorkarounds) providerSessionID(uri string) (string, bool) {
|
|
67
|
+
return wire.SessionIDFromURI(uri, w.provider)
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
func (w *ClientWorkarounds) isProviderSession(uri string) bool {
|
|
71
|
+
return strings.HasPrefix(strings.ToLower(uri), strings.ToLower(w.provider)+":/") && func() bool { _, ok := w.providerSessionID(uri); return ok }()
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
func (w *ClientWorkarounds) canonicalSessionURI(uri string) (string, bool) {
|
|
75
|
+
id, ok := w.providerSessionID(uri)
|
|
76
|
+
if !ok {
|
|
77
|
+
return "", false
|
|
78
|
+
}
|
|
79
|
+
return wire.SessionURI(id), true
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
func (w *ClientWorkarounds) owningSessionURI(uri string) (string, bool) {
|
|
83
|
+
if s, ok := w.canonicalSessionURI(uri); ok {
|
|
84
|
+
return s, true
|
|
85
|
+
}
|
|
86
|
+
if id, ok := wire.ChatIDFromURI(uri); ok {
|
|
87
|
+
return wire.SessionURI(id), true
|
|
88
|
+
}
|
|
89
|
+
return "", false
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
func params(msg map[string]any) map[string]any {
|
|
93
|
+
p, _ := msg["params"].(map[string]any)
|
|
94
|
+
return p
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
func str(v any) (string, bool) { s, ok := v.(string); return s, ok }
|
|
98
|
+
|
|
99
|
+
func actionTypeOf(v any) string {
|
|
100
|
+
m, ok := v.(map[string]any)
|
|
101
|
+
if !ok {
|
|
102
|
+
return ""
|
|
103
|
+
}
|
|
104
|
+
s, _ := m["type"].(string)
|
|
105
|
+
return s
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
// ApplyToIncoming rewrites a parsed request or notification in place. A *wire.Error result means
|
|
109
|
+
// the request must be refused with that error.
|
|
110
|
+
func (w *ClientWorkarounds) ApplyToIncoming(msg map[string]any) error {
|
|
111
|
+
p := params(msg)
|
|
112
|
+
if p == nil {
|
|
113
|
+
return nil
|
|
114
|
+
}
|
|
115
|
+
method, _ := msg["method"].(string)
|
|
116
|
+
_, hasID := msg["id"]
|
|
117
|
+
w.observeTraffic(method, p)
|
|
118
|
+
// Remove when VS Code includes the AHP 0.9 root-channel discriminant in reconnect.
|
|
119
|
+
if hasID && w.isVSCode && method == "reconnect" {
|
|
120
|
+
if _, present := p["channel"]; !present {
|
|
121
|
+
p["channel"] = wire.RootChannel
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
dialect := w.dialect()
|
|
126
|
+
rewrite := func(uri string) string { return w.inbound(uri, dialect) }
|
|
127
|
+
if channel, ok := str(p["channel"]); ok {
|
|
128
|
+
channel = rewrite(channel)
|
|
129
|
+
if method == "completions" && dialect != dialectCanonical {
|
|
130
|
+
// Both VS Code and the iOS client currently target completions at the provider-style session URI.
|
|
131
|
+
if id, ok := w.providerSessionID(channel); ok {
|
|
132
|
+
channel = wire.ChatURI(id)
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
// VS Code addresses its session rename to the selected chat. AHP defines session/titleChanged
|
|
136
|
+
// only on the owning session.
|
|
137
|
+
if method == "dispatchAction" && w.isVSCode && actionTypeOf(p["action"]) == "session/titleChanged" {
|
|
138
|
+
if id, ok := wire.ChatIDFromURI(channel); ok {
|
|
139
|
+
channel = wire.SessionURI(id)
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
p["channel"] = channel
|
|
143
|
+
}
|
|
144
|
+
// Handshake requests name their subscribed channels in arrays rather than the routing channel.
|
|
145
|
+
for _, field := range []string{"initialSubscriptions", "subscriptions"} {
|
|
146
|
+
if list, ok := p[field].([]any); ok {
|
|
147
|
+
out := make([]any, len(list))
|
|
148
|
+
for i, item := range list {
|
|
149
|
+
if s, ok := str(item); ok {
|
|
150
|
+
out[i] = rewrite(s)
|
|
151
|
+
} else {
|
|
152
|
+
out[i] = item
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
p[field] = out
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
if w.isVSCode {
|
|
159
|
+
return w.guard.applyToIncoming(msg, p)
|
|
160
|
+
}
|
|
161
|
+
return nil
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
// ApplyToOutgoing returns the outgoing message, rewritten when this client needs it.
|
|
165
|
+
func (w *ClientWorkarounds) ApplyToOutgoing(msg map[string]any) map[string]any {
|
|
166
|
+
if w.isVSCode {
|
|
167
|
+
w.guard.applyToOutgoing(msg)
|
|
168
|
+
}
|
|
169
|
+
dialect := w.dialect()
|
|
170
|
+
if dialect == dialectCanonical {
|
|
171
|
+
return msg
|
|
172
|
+
}
|
|
173
|
+
return rewriteFields(msg, func(uri string) string { return w.outbound(uri, dialect) }).(map[string]any)
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
// Active reports whether this connection needs any translation, so the host can skip decoding
|
|
177
|
+
// messages for the common canonical client.
|
|
178
|
+
func (w *ClientWorkarounds) Active() bool { return w.isVSCode || w.usesProv }
|
|
179
|
+
|
|
180
|
+
func (w *ClientWorkarounds) observeTraffic(method string, p map[string]any) {
|
|
181
|
+
var listed []string
|
|
182
|
+
for _, field := range []string{"initialSubscriptions", "subscriptions"} {
|
|
183
|
+
if list, ok := p[field].([]any); ok {
|
|
184
|
+
for _, item := range list {
|
|
185
|
+
if s, ok := str(item); ok {
|
|
186
|
+
listed = append(listed, s)
|
|
187
|
+
}
|
|
188
|
+
}
|
|
189
|
+
}
|
|
190
|
+
}
|
|
191
|
+
observed := listed
|
|
192
|
+
if direct, ok := str(p["channel"]); ok {
|
|
193
|
+
observed = append([]string{direct}, listed...)
|
|
194
|
+
}
|
|
195
|
+
w.observeVSCode(p, observed)
|
|
196
|
+
if w.usesProv {
|
|
197
|
+
return
|
|
198
|
+
}
|
|
199
|
+
check := listed
|
|
200
|
+
if providerSessionMethods[method] {
|
|
201
|
+
check = observed
|
|
202
|
+
}
|
|
203
|
+
for _, uri := range check {
|
|
204
|
+
if w.isProviderSession(uri) {
|
|
205
|
+
w.usesProv = true
|
|
206
|
+
return
|
|
207
|
+
}
|
|
208
|
+
}
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
func (w *ClientWorkarounds) observeVSCode(p map[string]any, observed []string) {
|
|
212
|
+
if meta, ok := p["_meta"].(map[string]any); ok {
|
|
213
|
+
_, a := meta["vscode.telemetryLevel"]
|
|
214
|
+
_, b := meta["vscode.clientConnectionKind"]
|
|
215
|
+
if a || b {
|
|
216
|
+
w.isVSCode = true
|
|
217
|
+
return
|
|
218
|
+
}
|
|
219
|
+
}
|
|
220
|
+
for _, uri := range observed {
|
|
221
|
+
if _, ok := w.sessionFromDerivedChat(uri); ok {
|
|
222
|
+
w.isVSCode = true
|
|
223
|
+
return
|
|
224
|
+
}
|
|
225
|
+
}
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
func (w *ClientWorkarounds) dialect() uriDialect {
|
|
229
|
+
if w.isVSCode {
|
|
230
|
+
return dialectVSCode
|
|
231
|
+
}
|
|
232
|
+
if w.usesProv {
|
|
233
|
+
return dialectProvider
|
|
234
|
+
}
|
|
235
|
+
return dialectCanonical
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
// sessionFromDerivedChat translates a URI this client computed into the one this host minted: a
|
|
239
|
+
// derived chat URI is unwrapped regardless of who sent it, since it names no channel this host could
|
|
240
|
+
// otherwise serve.
|
|
241
|
+
func (w *ClientWorkarounds) sessionFromDerivedChat(uri string) (string, bool) {
|
|
242
|
+
m := derivedChatURI.FindStringSubmatch(uri)
|
|
243
|
+
if m == nil || m[1] == "" {
|
|
244
|
+
return "", false
|
|
245
|
+
}
|
|
246
|
+
raw, err := base64.RawURLEncoding.DecodeString(strings.TrimRight(m[1], "="))
|
|
247
|
+
if err != nil {
|
|
248
|
+
return "", false
|
|
249
|
+
}
|
|
250
|
+
session := string(raw)
|
|
251
|
+
if !w.isProviderSession(session) {
|
|
252
|
+
return "", false
|
|
253
|
+
}
|
|
254
|
+
return session, true
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
func (w *ClientWorkarounds) inbound(uri string, dialect uriDialect) string {
|
|
258
|
+
if session, ok := w.sessionFromDerivedChat(uri); ok {
|
|
259
|
+
if id, ok := w.providerSessionID(session); ok {
|
|
260
|
+
return wire.ChatURI(id)
|
|
261
|
+
}
|
|
262
|
+
}
|
|
263
|
+
if dialect == dialectCanonical || !w.isProviderSession(uri) {
|
|
264
|
+
return uri
|
|
265
|
+
}
|
|
266
|
+
if id, ok := w.providerSessionID(uri); ok {
|
|
267
|
+
return wire.SessionURI(id)
|
|
268
|
+
}
|
|
269
|
+
return uri
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
// outbound translates a URI this host minted into the dialect this client expects.
|
|
273
|
+
func (w *ClientWorkarounds) outbound(uri string, dialect uriDialect) string {
|
|
274
|
+
if id, ok := wire.ChatIDFromURI(uri); ok {
|
|
275
|
+
if dialect == dialectVSCode {
|
|
276
|
+
return "ahp-chat://default/" + base64.RawURLEncoding.EncodeToString([]byte(w.provider+":/"+id))
|
|
277
|
+
}
|
|
278
|
+
return uri
|
|
279
|
+
}
|
|
280
|
+
if id, ok := w.providerSessionID(uri); ok {
|
|
281
|
+
return w.provider + ":/" + id
|
|
282
|
+
}
|
|
283
|
+
return uri
|
|
284
|
+
}
|
|
285
|
+
|
|
286
|
+
// rewriteFields applies rewrite to every rewritable field, however deeply nested.
|
|
287
|
+
func rewriteFields(v any, rewrite func(string) string) any {
|
|
288
|
+
switch x := v.(type) {
|
|
289
|
+
case []any:
|
|
290
|
+
out := make([]any, len(x))
|
|
291
|
+
for i, item := range x {
|
|
292
|
+
out[i] = rewriteFields(item, rewrite)
|
|
293
|
+
}
|
|
294
|
+
return out
|
|
295
|
+
case map[string]any:
|
|
296
|
+
out := make(map[string]any, len(x))
|
|
297
|
+
for k, item := range x {
|
|
298
|
+
if s, ok := item.(string); ok && rewritableFields[k] {
|
|
299
|
+
out[k] = rewrite(s)
|
|
300
|
+
} else {
|
|
301
|
+
out[k] = rewriteFields(item, rewrite)
|
|
302
|
+
}
|
|
303
|
+
}
|
|
304
|
+
return out
|
|
305
|
+
}
|
|
306
|
+
return v
|
|
307
|
+
}
|
|
308
|
+
|
|
309
|
+
// ── VS Code provisional-session disposal guard ──────────────────────────
|
|
310
|
+
|
|
311
|
+
type trackedState string
|
|
312
|
+
|
|
313
|
+
const (
|
|
314
|
+
stateCreating trackedState = "creating"
|
|
315
|
+
stateEmpty trackedState = "empty"
|
|
316
|
+
stateMaterialized trackedState = "materialized"
|
|
317
|
+
stateDisposing trackedState = "disposing"
|
|
318
|
+
)
|
|
319
|
+
|
|
320
|
+
type pendingLifecycle struct {
|
|
321
|
+
kind string // "create" | "dispose"
|
|
322
|
+
session string
|
|
323
|
+
}
|
|
324
|
+
|
|
325
|
+
// vscodeDisposalGuard allows VS Code to clean up only sessions known to be unused drafts on this
|
|
326
|
+
// connection. Unknown sessions are protected: reconnect does not carry enough history to prove that
|
|
327
|
+
// they are empty. Remove it when VS Code graduates materialized remote sessions before tearing down
|
|
328
|
+
// its provisional-session service.
|
|
329
|
+
type vscodeDisposalGuard struct {
|
|
330
|
+
provider string
|
|
331
|
+
sessions map[string]trackedState
|
|
332
|
+
pending map[string]pendingLifecycle
|
|
333
|
+
}
|
|
334
|
+
|
|
335
|
+
func newVSCodeDisposalGuard(provider string) *vscodeDisposalGuard {
|
|
336
|
+
return &vscodeDisposalGuard{provider: provider, sessions: map[string]trackedState{}, pending: map[string]pendingLifecycle{}}
|
|
337
|
+
}
|
|
338
|
+
|
|
339
|
+
func idKey(v any) string {
|
|
340
|
+
switch n := v.(type) {
|
|
341
|
+
case json.Number:
|
|
342
|
+
return n.String()
|
|
343
|
+
case float64:
|
|
344
|
+
b, _ := json.Marshal(n)
|
|
345
|
+
return string(b)
|
|
346
|
+
case string:
|
|
347
|
+
return "s:" + n
|
|
348
|
+
}
|
|
349
|
+
return ""
|
|
350
|
+
}
|
|
351
|
+
|
|
352
|
+
func (g *vscodeDisposalGuard) canonical(uri string) (string, bool) {
|
|
353
|
+
id, ok := wire.SessionIDFromURI(uri, g.provider)
|
|
354
|
+
if !ok {
|
|
355
|
+
return "", false
|
|
356
|
+
}
|
|
357
|
+
return wire.SessionURI(id), true
|
|
358
|
+
}
|
|
359
|
+
|
|
360
|
+
func (g *vscodeDisposalGuard) owning(uri string) (string, bool) {
|
|
361
|
+
if s, ok := g.canonical(uri); ok {
|
|
362
|
+
return s, true
|
|
363
|
+
}
|
|
364
|
+
if id, ok := wire.ChatIDFromURI(uri); ok {
|
|
365
|
+
return wire.SessionURI(id), true
|
|
366
|
+
}
|
|
367
|
+
return "", false
|
|
368
|
+
}
|
|
369
|
+
|
|
370
|
+
func (g *vscodeDisposalGuard) markMaterialized(channel string) {
|
|
371
|
+
if session, ok := g.owning(channel); ok {
|
|
372
|
+
if _, tracked := g.sessions[session]; tracked {
|
|
373
|
+
g.sessions[session] = stateMaterialized
|
|
374
|
+
}
|
|
375
|
+
}
|
|
376
|
+
}
|
|
377
|
+
|
|
378
|
+
func (g *vscodeDisposalGuard) applyToIncoming(msg map[string]any, p map[string]any) error {
|
|
379
|
+
channel, _ := str(p["channel"])
|
|
380
|
+
if t := actionTypeOf(p["action"]); channel != "" && t != "" && materializingActions[t] {
|
|
381
|
+
g.markMaterialized(channel)
|
|
382
|
+
}
|
|
383
|
+
id, isRequest := msg["id"]
|
|
384
|
+
if !isRequest || channel == "" {
|
|
385
|
+
return nil
|
|
386
|
+
}
|
|
387
|
+
session, ok := g.canonical(channel)
|
|
388
|
+
if !ok {
|
|
389
|
+
return nil
|
|
390
|
+
}
|
|
391
|
+
method, _ := msg["method"].(string)
|
|
392
|
+
switch method {
|
|
393
|
+
case "createSession":
|
|
394
|
+
if _, imported := p["importConversation"]; imported {
|
|
395
|
+
g.sessions[session] = stateMaterialized
|
|
396
|
+
} else {
|
|
397
|
+
g.sessions[session] = stateCreating
|
|
398
|
+
}
|
|
399
|
+
g.pending[idKey(id)] = pendingLifecycle{"create", session}
|
|
400
|
+
case "disposeSession":
|
|
401
|
+
if g.sessions[session] != stateEmpty {
|
|
402
|
+
return &wire.Error{Code: wire.CodeInvalidRequest, Message: vscodeMaterializedSessionDisposalRefusal}
|
|
403
|
+
}
|
|
404
|
+
g.sessions[session] = stateDisposing
|
|
405
|
+
g.pending[idKey(id)] = pendingLifecycle{"dispose", session}
|
|
406
|
+
}
|
|
407
|
+
return nil
|
|
408
|
+
}
|
|
409
|
+
|
|
410
|
+
func (g *vscodeDisposalGuard) applyToOutgoing(msg map[string]any) {
|
|
411
|
+
method, isNotification := msg["method"].(string)
|
|
412
|
+
if !isNotification {
|
|
413
|
+
g.applyResponse(msg)
|
|
414
|
+
return
|
|
415
|
+
}
|
|
416
|
+
p := params(msg)
|
|
417
|
+
if p == nil {
|
|
418
|
+
return
|
|
419
|
+
}
|
|
420
|
+
if t := actionTypeOf(p["action"]); method == "action" && p["rejectionReason"] == nil && t != "" && materializingActions[t] {
|
|
421
|
+
if channel, ok := str(p["channel"]); ok {
|
|
422
|
+
g.markMaterialized(channel)
|
|
423
|
+
}
|
|
424
|
+
}
|
|
425
|
+
if method == "root/sessionRemoved" {
|
|
426
|
+
if s, ok := str(p["session"]); ok {
|
|
427
|
+
if session, ok := g.canonical(s); ok {
|
|
428
|
+
delete(g.sessions, session)
|
|
429
|
+
}
|
|
430
|
+
}
|
|
431
|
+
}
|
|
432
|
+
}
|
|
433
|
+
|
|
434
|
+
func (g *vscodeDisposalGuard) applyResponse(msg map[string]any) {
|
|
435
|
+
key := idKey(msg["id"])
|
|
436
|
+
pending, ok := g.pending[key]
|
|
437
|
+
if !ok {
|
|
438
|
+
return
|
|
439
|
+
}
|
|
440
|
+
delete(g.pending, key)
|
|
441
|
+
state := g.sessions[pending.session]
|
|
442
|
+
_, success := msg["result"]
|
|
443
|
+
if pending.kind == "create" {
|
|
444
|
+
if success {
|
|
445
|
+
if state == stateCreating {
|
|
446
|
+
g.sessions[pending.session] = stateEmpty
|
|
447
|
+
}
|
|
448
|
+
} else {
|
|
449
|
+
delete(g.sessions, pending.session)
|
|
450
|
+
}
|
|
451
|
+
return
|
|
452
|
+
}
|
|
453
|
+
if success {
|
|
454
|
+
delete(g.sessions, pending.session)
|
|
455
|
+
} else if state == stateDisposing {
|
|
456
|
+
g.sessions[pending.session] = stateEmpty
|
|
457
|
+
}
|
|
458
|
+
}
|
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
package host_test
|
|
2
|
+
|
|
3
|
+
import (
|
|
4
|
+
"encoding/base64"
|
|
5
|
+
"reflect"
|
|
6
|
+
"testing"
|
|
7
|
+
|
|
8
|
+
"github.com/MichaelKinsy/pigpen/ahp/internal/host"
|
|
9
|
+
"github.com/MichaelKinsy/pigpen/ahp/internal/testkit"
|
|
10
|
+
"github.com/MichaelKinsy/pigpen/ahp/internal/twin"
|
|
11
|
+
"github.com/MichaelKinsy/pigpen/ahp/internal/wire"
|
|
12
|
+
)
|
|
13
|
+
|
|
14
|
+
func derivedChat(providerSession string) string {
|
|
15
|
+
return "ahp-chat://default/" + base64.RawURLEncoding.EncodeToString([]byte(providerSession))
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
// Twins of upstream test/client-workarounds.test.ts, describe "session URI classification"
|
|
19
|
+
// (the cases that need no session registry; the wire-level ones live with the registry tests).
|
|
20
|
+
func TestSessionURIClassification(t *testing.T) {
|
|
21
|
+
twin.Run(t, "client-workarounds", "recognises only canonical or explicitly allowed session schemes", func(t *testing.T) {
|
|
22
|
+
id := "9991C40A-74CC-4991-85CC-F37CD2BFF065"
|
|
23
|
+
check := func(uri string, aliases []string, want string, ok bool) {
|
|
24
|
+
t.Helper()
|
|
25
|
+
got, gotOK := wire.SessionIDFromURI(uri, aliases...)
|
|
26
|
+
if got != want || gotOK != ok {
|
|
27
|
+
t.Errorf("SessionIDFromURI(%q, %v) = %q, %v; want %q, %v", uri, aliases, got, gotOK, want, ok)
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
check("pi:/"+id, []string{"pi"}, id, true)
|
|
31
|
+
check("ahp-session:/abc", nil, "abc", true)
|
|
32
|
+
check("copilot:/test-session", nil, "", false)
|
|
33
|
+
check("copilot:/test-session", []string{"copilot"}, "test-session", true)
|
|
34
|
+
check("pi://test-session", []string{"pi"}, "", false)
|
|
35
|
+
})
|
|
36
|
+
|
|
37
|
+
twin.Run(t, "client-workarounds", "does not infer sessions from other channel schemes", func(t *testing.T) {
|
|
38
|
+
for _, uri := range []string{"ahp-chat:/c1", "ahp-terminal:/t1", "agenthost-terminal:/t1", "file:///etc/passwd"} {
|
|
39
|
+
if id, ok := wire.SessionIDFromURI(uri, "pi"); ok {
|
|
40
|
+
t.Errorf("%s classified as session %q", uri, id)
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
})
|
|
44
|
+
|
|
45
|
+
twin.Run(t, "client-workarounds", "retargets VS Code's chat-addressed rename to the owning session", func(t *testing.T) {
|
|
46
|
+
id := "rename-me"
|
|
47
|
+
w := host.NewClientWorkarounds("pi")
|
|
48
|
+
w.Identify("vscode-editor-window")
|
|
49
|
+
msg := map[string]any{"jsonrpc": "2.0", "method": "dispatchAction", "params": map[string]any{
|
|
50
|
+
"channel": derivedChat("pi:/" + id), "clientSeq": float64(1),
|
|
51
|
+
"action": map[string]any{"type": "session/titleChanged", "title": "Renamed"},
|
|
52
|
+
}}
|
|
53
|
+
if err := w.ApplyToIncoming(msg); err != nil {
|
|
54
|
+
t.Fatal(err)
|
|
55
|
+
}
|
|
56
|
+
if got := msg["params"].(map[string]any)["channel"]; got != wire.SessionURI(id) {
|
|
57
|
+
t.Fatalf("channel = %v, want %s", got, wire.SessionURI(id))
|
|
58
|
+
}
|
|
59
|
+
})
|
|
60
|
+
|
|
61
|
+
twin.Run(t, "client-workarounds", "rewrites URI-bearing action and catalogue fields for VS Code", func(t *testing.T) {
|
|
62
|
+
id := "nested-fields"
|
|
63
|
+
session, chat := wire.SessionURI(id), wire.ChatURI(id)
|
|
64
|
+
providerSession := "pi:/" + id
|
|
65
|
+
derived := derivedChat(providerSession)
|
|
66
|
+
w := host.NewClientWorkarounds("pi")
|
|
67
|
+
w.Identify("vscode-editor-window")
|
|
68
|
+
|
|
69
|
+
removed := w.ApplyToOutgoing(map[string]any{"jsonrpc": "2.0", "method": "root/sessionRemoved", "params": map[string]any{"channel": wire.RootChannel, "session": session}})
|
|
70
|
+
wantRemoved := map[string]any{"jsonrpc": "2.0", "method": "root/sessionRemoved", "params": map[string]any{"channel": wire.RootChannel, "session": providerSession}}
|
|
71
|
+
if !reflect.DeepEqual(removed, wantRemoved) {
|
|
72
|
+
t.Fatalf("removed = %#v", removed)
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
updated := w.ApplyToOutgoing(map[string]any{"jsonrpc": "2.0", "method": "action", "params": map[string]any{
|
|
76
|
+
"channel": session, "serverSeq": float64(1),
|
|
77
|
+
"action": map[string]any{"type": "session/chatUpdated", "chat": chat, "changes": map[string]any{"resource": chat}},
|
|
78
|
+
}})
|
|
79
|
+
wantUpdated := map[string]any{"jsonrpc": "2.0", "method": "action", "params": map[string]any{
|
|
80
|
+
"channel": providerSession, "serverSeq": float64(1),
|
|
81
|
+
"action": map[string]any{"type": "session/chatUpdated", "chat": derived, "changes": map[string]any{"resource": derived}},
|
|
82
|
+
}}
|
|
83
|
+
if !reflect.DeepEqual(updated, wantUpdated) {
|
|
84
|
+
t.Fatalf("updated = %#v", testkit.Normalize(t, updated))
|
|
85
|
+
}
|
|
86
|
+
})
|
|
87
|
+
|
|
88
|
+
twin.Run(t, "client-workarounds", "leaves VS Code's client-chosen terminal URI outside session translation", func(t *testing.T) {
|
|
89
|
+
w := host.NewClientWorkarounds("pi")
|
|
90
|
+
w.Identify("vscode-editor-window")
|
|
91
|
+
msg := map[string]any{"jsonrpc": "2.0", "method": "action", "params": map[string]any{"channel": "agenthost-terminal:/terminal-1"}}
|
|
92
|
+
if got := w.ApplyToOutgoing(msg); !reflect.DeepEqual(got, msg) {
|
|
93
|
+
t.Fatalf("got %#v", got)
|
|
94
|
+
}
|
|
95
|
+
})
|
|
96
|
+
}
|