@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,199 @@
|
|
|
1
|
+
package mapper
|
|
2
|
+
|
|
3
|
+
import (
|
|
4
|
+
"bytes"
|
|
5
|
+
"encoding/json"
|
|
6
|
+
"strings"
|
|
7
|
+
)
|
|
8
|
+
|
|
9
|
+
// Human-readable descriptions of what the agent is doing right now (port of src/pi/activity.ts).
|
|
10
|
+
// They are display strings a client renders verbatim next to its spinner.
|
|
11
|
+
|
|
12
|
+
const (
|
|
13
|
+
// ThinkingActivity and RespondingActivity are the two model-side activities.
|
|
14
|
+
ThinkingActivity = "Thinking"
|
|
15
|
+
RespondingActivity = "Responding"
|
|
16
|
+
|
|
17
|
+
maxLength = 60
|
|
18
|
+
)
|
|
19
|
+
|
|
20
|
+
// shorten trims the middle rather than the end: the informative parts of a path or a shell
|
|
21
|
+
// command sit at the two ends.
|
|
22
|
+
func shorten(value string, limit ...int) string {
|
|
23
|
+
lim := maxLength
|
|
24
|
+
if len(limit) > 0 {
|
|
25
|
+
lim = limit[0]
|
|
26
|
+
}
|
|
27
|
+
collapsed := collapseSpace(value)
|
|
28
|
+
n := utf16Len(collapsed)
|
|
29
|
+
if n <= lim {
|
|
30
|
+
return collapsed
|
|
31
|
+
}
|
|
32
|
+
head := (lim - 1 + 1) / 2 // Math.ceil((limit-1)/2)
|
|
33
|
+
tail := (lim - 1) / 2 // Math.floor((limit-1)/2)
|
|
34
|
+
return sliceUTF16(collapsed, 0, head) + "…" + sliceUTF16(collapsed, n-tail, n)
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
// displayPath renders a path relative to the working directory when it sits underneath it.
|
|
38
|
+
func displayPath(value any, workingDirectory string) (string, bool) {
|
|
39
|
+
s, ok := value.(string)
|
|
40
|
+
if !ok || s == "" {
|
|
41
|
+
return "", false
|
|
42
|
+
}
|
|
43
|
+
if workingDirectory != "" && strings.HasPrefix(s, workingDirectory+"/") {
|
|
44
|
+
s = s[len(workingDirectory)+1:]
|
|
45
|
+
}
|
|
46
|
+
return shorten(s), true
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
func argsMap(args any) map[string]any {
|
|
50
|
+
m, _ := args.(map[string]any)
|
|
51
|
+
return m
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
// DescribeToolCall describes a tool invocation. pi's built-in tools are named individually;
|
|
55
|
+
// anything else falls back to its own name.
|
|
56
|
+
func DescribeToolCall(toolName string, args any, workingDirectory string) string {
|
|
57
|
+
input := argsMap(args)
|
|
58
|
+
path, hasPath := displayPath(input["path"], workingDirectory)
|
|
59
|
+
switch toolName {
|
|
60
|
+
case "read":
|
|
61
|
+
if hasPath {
|
|
62
|
+
return "Reading " + path
|
|
63
|
+
}
|
|
64
|
+
return "Reading a file"
|
|
65
|
+
case "write":
|
|
66
|
+
if hasPath {
|
|
67
|
+
return "Writing " + path
|
|
68
|
+
}
|
|
69
|
+
return "Writing a file"
|
|
70
|
+
case "edit":
|
|
71
|
+
if hasPath {
|
|
72
|
+
return "Editing " + path
|
|
73
|
+
}
|
|
74
|
+
return "Editing a file"
|
|
75
|
+
case "bash":
|
|
76
|
+
if c, ok := input["command"].(string); ok {
|
|
77
|
+
if command := shorten(c); command != "" {
|
|
78
|
+
return "Running " + command
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
return "Running a command"
|
|
82
|
+
case "ls":
|
|
83
|
+
if hasPath {
|
|
84
|
+
return "Listing " + path
|
|
85
|
+
}
|
|
86
|
+
return "Listing files"
|
|
87
|
+
case "find":
|
|
88
|
+
if p, ok := input["pattern"].(string); ok {
|
|
89
|
+
if pattern := shorten(p, 30); pattern != "" {
|
|
90
|
+
return "Finding " + pattern
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
return "Finding files"
|
|
94
|
+
case "grep":
|
|
95
|
+
if p, ok := input["pattern"].(string); ok {
|
|
96
|
+
if pattern := shorten(p, 30); pattern != "" {
|
|
97
|
+
return "Searching for " + pattern
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
return "Searching"
|
|
101
|
+
default:
|
|
102
|
+
return "Running " + shorten(toolName, 40)
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
func stringArg(v any) (string, bool) {
|
|
107
|
+
s, ok := v.(string)
|
|
108
|
+
return s, ok && s != ""
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
// fullArguments is JSON.stringify(args, null, 2). Go marshals object keys in sorted order where
|
|
112
|
+
// JavaScript keeps insertion order; Pi's events reach an extension already decoded, so the
|
|
113
|
+
// original order is not available (documented in PORT.md).
|
|
114
|
+
func fullArguments(args any) (string, bool) {
|
|
115
|
+
if args == nil {
|
|
116
|
+
return "", false
|
|
117
|
+
}
|
|
118
|
+
out, err := StringifyJSON(args, true)
|
|
119
|
+
if err != nil {
|
|
120
|
+
return "", false
|
|
121
|
+
}
|
|
122
|
+
return out, true
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
// StringifyJSON is JSON.stringify(v) (or JSON.stringify(v, null, 2) when indent is set): unlike
|
|
126
|
+
// encoding/json.Marshal it leaves <, > and & unescaped.
|
|
127
|
+
func StringifyJSON(v any, indent bool) (string, error) {
|
|
128
|
+
var buf bytes.Buffer
|
|
129
|
+
enc := json.NewEncoder(&buf)
|
|
130
|
+
enc.SetEscapeHTML(false)
|
|
131
|
+
if indent {
|
|
132
|
+
enc.SetIndent("", " ")
|
|
133
|
+
}
|
|
134
|
+
if err := enc.Encode(v); err != nil {
|
|
135
|
+
return "", err
|
|
136
|
+
}
|
|
137
|
+
return strings.TrimSuffix(buf.String(), "\n"), nil
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
// ToolInputFor is the one argument that says what a tool call is about. Anything without an
|
|
141
|
+
// obvious subject keeps the full arguments. ok is false when there is nothing to show.
|
|
142
|
+
func ToolInputFor(toolName string, args any) (string, bool) {
|
|
143
|
+
input := argsMap(args)
|
|
144
|
+
switch toolName {
|
|
145
|
+
case "bash":
|
|
146
|
+
if s, ok := stringArg(input["command"]); ok {
|
|
147
|
+
return s, true
|
|
148
|
+
}
|
|
149
|
+
case "grep", "find":
|
|
150
|
+
if s, ok := searchInput(input); ok {
|
|
151
|
+
return s, true
|
|
152
|
+
}
|
|
153
|
+
case "read", "write", "edit", "ls":
|
|
154
|
+
if s, ok := stringArg(input["path"]); ok {
|
|
155
|
+
return s, true
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
return fullArguments(args)
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
// searchInput renders a search as its pattern plus whatever narrows it. Models restate the
|
|
162
|
+
// defaults (`path: "."`, a glob matching everything, `ignoreCase: false`), so only arguments that
|
|
163
|
+
// actually narrow the search are shown, in the order `rg` writes them.
|
|
164
|
+
func searchInput(input map[string]any) (string, bool) {
|
|
165
|
+
pattern, ok := stringArg(input["pattern"])
|
|
166
|
+
if !ok {
|
|
167
|
+
return "", false
|
|
168
|
+
}
|
|
169
|
+
parts := []string{pattern}
|
|
170
|
+
if glob, ok := stringArg(input["glob"]); ok && glob != "**/*" {
|
|
171
|
+
parts = append(parts, "--glob "+glob)
|
|
172
|
+
}
|
|
173
|
+
if ic, _ := input["ignoreCase"].(bool); ic {
|
|
174
|
+
parts = append(parts, "--ignore-case")
|
|
175
|
+
}
|
|
176
|
+
if path, ok := stringArg(input["path"]); ok && path != "." {
|
|
177
|
+
parts = append(parts, "in "+path)
|
|
178
|
+
}
|
|
179
|
+
return strings.Join(parts, " "), true
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
var pastTenseVerbs = map[string]string{
|
|
183
|
+
"Reading": "Read", "Writing": "Wrote", "Editing": "Edited", "Running": "Ran",
|
|
184
|
+
"Listing": "Listed", "Finding": "Found", "Searching": "Searched",
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
// DescribeFinishedToolCall is the same description phrased for a call that has finished: only
|
|
188
|
+
// the leading verb changes, which keeps the subject identical.
|
|
189
|
+
func DescribeFinishedToolCall(toolName string, args any, workingDirectory string) string {
|
|
190
|
+
description := DescribeToolCall(toolName, args, workingDirectory)
|
|
191
|
+
verb, rest, _ := strings.Cut(description, " ")
|
|
192
|
+
if past, ok := pastTenseVerbs[verb]; ok {
|
|
193
|
+
if rest == "" && !strings.Contains(description, " ") {
|
|
194
|
+
return past
|
|
195
|
+
}
|
|
196
|
+
return past + " " + rest
|
|
197
|
+
}
|
|
198
|
+
return description
|
|
199
|
+
}
|
|
@@ -0,0 +1,188 @@
|
|
|
1
|
+
package mapper_test
|
|
2
|
+
|
|
3
|
+
import (
|
|
4
|
+
"regexp"
|
|
5
|
+
"strings"
|
|
6
|
+
"testing"
|
|
7
|
+
"unicode/utf16"
|
|
8
|
+
|
|
9
|
+
"github.com/microsoft/agent-host-protocol/clients/go/ahp"
|
|
10
|
+
"github.com/microsoft/agent-host-protocol/clients/go/ahptypes"
|
|
11
|
+
|
|
12
|
+
"github.com/MichaelKinsy/pigpen/ahp/internal/channels"
|
|
13
|
+
"github.com/MichaelKinsy/pigpen/ahp/internal/mapper"
|
|
14
|
+
"github.com/MichaelKinsy/pigpen/ahp/internal/twin"
|
|
15
|
+
)
|
|
16
|
+
|
|
17
|
+
func utf16Len(s string) int { return len(utf16.Encode([]rune(s))) }
|
|
18
|
+
|
|
19
|
+
// Twins of upstream test/activity.test.ts, describe "activity descriptions".
|
|
20
|
+
func TestActivityDescriptions(t *testing.T) {
|
|
21
|
+
describe := func(name string, args any, cwd string) string { return mapper.DescribeToolCall(name, args, cwd) }
|
|
22
|
+
|
|
23
|
+
twin.Run(t, "activity", "names the file a read touches", func(t *testing.T) {
|
|
24
|
+
if got := describe("read", obj{"path": "/ws/src/main.ts"}, "/ws"); got != "Reading src/main.ts" {
|
|
25
|
+
t.Fatal(got)
|
|
26
|
+
}
|
|
27
|
+
})
|
|
28
|
+
|
|
29
|
+
twin.Run(t, "activity", "renders paths relative to the workspace", func(t *testing.T) {
|
|
30
|
+
// An absolute path is mostly noise the user already knows.
|
|
31
|
+
if got := describe("edit", obj{"path": "/ws/a/b.ts"}, "/ws"); got != "Editing a/b.ts" {
|
|
32
|
+
t.Fatal(got)
|
|
33
|
+
}
|
|
34
|
+
if got := describe("edit", obj{"path": "/elsewhere/b.ts"}, "/ws"); got != "Editing /elsewhere/b.ts" {
|
|
35
|
+
t.Fatal(got)
|
|
36
|
+
}
|
|
37
|
+
})
|
|
38
|
+
|
|
39
|
+
twin.Run(t, "activity", "shows the command a bash call runs", func(t *testing.T) {
|
|
40
|
+
if got := describe("bash", obj{"command": "npm test"}, ""); got != "Running npm test" {
|
|
41
|
+
t.Fatal(got)
|
|
42
|
+
}
|
|
43
|
+
})
|
|
44
|
+
|
|
45
|
+
twin.Run(t, "activity", "shortens the middle, keeping both informative ends", func(t *testing.T) {
|
|
46
|
+
long := describe("bash", obj{"command": "git log " + strings.Repeat("x", 200) + " --oneline"}, "")
|
|
47
|
+
if utf16Len(long) >= 80 {
|
|
48
|
+
t.Fatalf("not shortened: %d units", utf16Len(long))
|
|
49
|
+
}
|
|
50
|
+
if !regexp.MustCompile(`^Running git log`).MatchString(long) || !strings.HasSuffix(long, "--oneline") {
|
|
51
|
+
t.Fatal(long)
|
|
52
|
+
}
|
|
53
|
+
})
|
|
54
|
+
|
|
55
|
+
twin.Run(t, "activity", "falls back to the tool's own name", func(t *testing.T) {
|
|
56
|
+
// Extension and custom tools still say something useful.
|
|
57
|
+
if got := describe("my_custom_tool", obj{}, ""); got != "Running my_custom_tool" {
|
|
58
|
+
t.Fatal(got)
|
|
59
|
+
}
|
|
60
|
+
})
|
|
61
|
+
|
|
62
|
+
twin.Run(t, "activity", "copes with missing arguments", func(t *testing.T) {
|
|
63
|
+
if got := describe("read", nil, ""); got != "Reading a file" {
|
|
64
|
+
t.Fatal(got)
|
|
65
|
+
}
|
|
66
|
+
if got := describe("bash", obj{}, ""); got != "Running a command" {
|
|
67
|
+
t.Fatal(got)
|
|
68
|
+
}
|
|
69
|
+
})
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
func runActivity(t testing.TB, events []obj) replayed {
|
|
73
|
+
t.Helper()
|
|
74
|
+
m := mapper.NewTurnMapper("t", 0, mapper.Options{WorkingDirectory: "/ws"})
|
|
75
|
+
actions := []ahptypes.StateAction{mapper.UserTurnStarted("t", "go", "1970-01-01T00:00:00.000Z")}
|
|
76
|
+
for _, e := range events {
|
|
77
|
+
actions = append(actions, m.Handle(e)...)
|
|
78
|
+
}
|
|
79
|
+
state := channels.InitialChatState("ahp-chat:/a", "chat", nil)
|
|
80
|
+
for _, a := range actions {
|
|
81
|
+
ahp.ApplyActionToChat(state, a)
|
|
82
|
+
}
|
|
83
|
+
return replayed{actions: actions, state: toObj(t, state)}
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
// activities lists each chat/activityChanged action's activity, "<cleared>" when it has none.
|
|
87
|
+
func activities(actions []ahptypes.StateAction) []string {
|
|
88
|
+
out := []string{}
|
|
89
|
+
for _, a := range ofType(actions, "chat/activityChanged") {
|
|
90
|
+
if s, ok := a["activity"].(string); ok {
|
|
91
|
+
out = append(out, s)
|
|
92
|
+
} else {
|
|
93
|
+
out = append(out, "<cleared>")
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
return out
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
func toolStartEvent(id, name string, args any) obj {
|
|
100
|
+
return obj{"type": "tool_execution_start", "toolCallId": id, "toolName": name, "args": args}
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
// Twins of upstream test/activity.test.ts, describe "activity over a turn".
|
|
104
|
+
func TestActivityOverATurn(t *testing.T) {
|
|
105
|
+
msgStart := obj{"type": "message_start", "message": obj{"role": "assistant"}}
|
|
106
|
+
|
|
107
|
+
twin.Run(t, "activity", "tracks thinking, tools, and responding in order", func(t *testing.T) {
|
|
108
|
+
r := runActivity(t, []obj{
|
|
109
|
+
pi.agentStart(), msgStart,
|
|
110
|
+
toolStartEvent("tc1", "read", obj{"path": "/ws/a.ts"}),
|
|
111
|
+
{"type": "tool_execution_end", "toolCallId": "tc1", "toolName": "read", "result": obj{}, "isError": false},
|
|
112
|
+
pi.text(0, "done"), pi.settled(),
|
|
113
|
+
})
|
|
114
|
+
equal(t, activities(r.actions), []string{mapper.ThinkingActivity, "Reading a.ts", mapper.ThinkingActivity, mapper.RespondingActivity, "<cleared>"}, "activities")
|
|
115
|
+
})
|
|
116
|
+
|
|
117
|
+
twin.Run(t, "activity", "clears the description when the turn ends", func(t *testing.T) {
|
|
118
|
+
// A stale one would leave the client showing "Reading ..." against an idle chat.
|
|
119
|
+
r := runActivity(t, []obj{pi.agentStart(), toolStartEvent("tc1", "bash", obj{"command": "ls"}), pi.settled()})
|
|
120
|
+
if r.state["activity"] != nil {
|
|
121
|
+
t.Fatalf("activity = %v", r.state["activity"])
|
|
122
|
+
}
|
|
123
|
+
})
|
|
124
|
+
|
|
125
|
+
twin.Run(t, "activity", "clears it on a cancelled turn too", func(t *testing.T) {
|
|
126
|
+
m := mapper.NewTurnMapper("t", 0)
|
|
127
|
+
m.Handle(pi.agentStart())
|
|
128
|
+
equal(t, activities(m.Finish(mapper.OutcomeCancelled, "")), []string{"<cleared>"}, "activities")
|
|
129
|
+
})
|
|
130
|
+
|
|
131
|
+
twin.Run(t, "activity", "does not re-send an unchanged description", func(t *testing.T) {
|
|
132
|
+
// Every delta of one response is still "Responding"; re-sending would burn a serverSeq per token.
|
|
133
|
+
events := []obj{pi.agentStart(), msgStart}
|
|
134
|
+
for _, d := range []string{"a", "b", "c", "d"} {
|
|
135
|
+
events = append(events, pi.text(0, d))
|
|
136
|
+
}
|
|
137
|
+
events = append(events, pi.settled())
|
|
138
|
+
r := runActivity(t, events)
|
|
139
|
+
equal(t, activities(r.actions), []string{mapper.ThinkingActivity, mapper.RespondingActivity, "<cleared>"}, "activities")
|
|
140
|
+
})
|
|
141
|
+
|
|
142
|
+
twin.Run(t, "activity", "emits schema-conforming actions", func(t *testing.T) {
|
|
143
|
+
r := runActivity(t, []obj{pi.agentStart(), toolStartEvent("tc1", "grep", obj{"pattern": "TODO"}), pi.settled()})
|
|
144
|
+
for _, a := range r.actions {
|
|
145
|
+
if actionType(a) == "chat/activityChanged" {
|
|
146
|
+
assertActionsValid(t, []ahptypes.StateAction{a}, "activity")
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
})
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
// Twins of upstream test/activity.test.ts, describe "toolInputFor".
|
|
153
|
+
func TestToolInputFor(t *testing.T) {
|
|
154
|
+
input := func(name string, args any) string {
|
|
155
|
+
s, ok := mapper.ToolInputFor(name, args)
|
|
156
|
+
if !ok {
|
|
157
|
+
t.Fatalf("no input for %s", name)
|
|
158
|
+
}
|
|
159
|
+
return s
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
twin.Run(t, "activity", "shows the one argument a call is about", func(t *testing.T) {
|
|
163
|
+
if got := input("bash", obj{"command": "ls -la", "timeout": 5}); got != "ls -la" {
|
|
164
|
+
t.Fatal(got)
|
|
165
|
+
}
|
|
166
|
+
if got := input("read", obj{"path": "a.ts", "offset": 1, "limit": 50}); got != "a.ts" {
|
|
167
|
+
t.Fatal(got)
|
|
168
|
+
}
|
|
169
|
+
})
|
|
170
|
+
|
|
171
|
+
twin.Run(t, "activity", "keeps a search's scope, and drops the defaults models restate", func(t *testing.T) {
|
|
172
|
+
// Every one of these is what a capture actually contained: the model fills in the defaults.
|
|
173
|
+
if got := input("grep", obj{"pattern": "BEACON", "path": ".", "glob": "**/*", "ignoreCase": false, "limit": 100}); got != "BEACON" {
|
|
174
|
+
t.Fatal(got)
|
|
175
|
+
}
|
|
176
|
+
if got := input("grep", obj{"pattern": "TODO", "path": "src", "glob": "*.ts", "ignoreCase": true}); got != "TODO --glob *.ts --ignore-case in src" {
|
|
177
|
+
t.Fatal(got)
|
|
178
|
+
}
|
|
179
|
+
})
|
|
180
|
+
|
|
181
|
+
twin.Run(t, "activity", "falls back to the arguments for a tool it does not know", func(t *testing.T) {
|
|
182
|
+
// An extension's tool has no argument this code can single out; its arguments are still the
|
|
183
|
+
// most informative thing available for it.
|
|
184
|
+
if got := input("some-extension-tool", obj{"a": 1}); got != "{\n \"a\": 1\n}" {
|
|
185
|
+
t.Fatalf("%q", got)
|
|
186
|
+
}
|
|
187
|
+
})
|
|
188
|
+
}
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
package mapper_test
|
|
2
|
+
|
|
3
|
+
import (
|
|
4
|
+
"testing"
|
|
5
|
+
|
|
6
|
+
"github.com/MichaelKinsy/pigpen/ahp/internal/mapper"
|
|
7
|
+
)
|
|
8
|
+
|
|
9
|
+
// One streamed text delta through the turn mapper: this runs for every token batch of a turn while a remote
|
|
10
|
+
// client is attached (the listener is off, and nothing here runs, unless it is configured).
|
|
11
|
+
//
|
|
12
|
+
// go test -run xxx -bench . -benchmem
|
|
13
|
+
func BenchmarkMapTextDelta(b *testing.B) {
|
|
14
|
+
m := mapper.NewTurnMapper("turn-1", 0)
|
|
15
|
+
m.Handle(pi.agentStart())
|
|
16
|
+
m.Handle(pi.assistantStart())
|
|
17
|
+
delta := pi.text(0, "some streamed text from the model ")
|
|
18
|
+
b.ReportAllocs()
|
|
19
|
+
b.ResetTimer()
|
|
20
|
+
for i := 0; i < b.N; i++ {
|
|
21
|
+
m.Handle(delta)
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
func BenchmarkMapToolExecution(b *testing.B) {
|
|
26
|
+
m := mapper.NewTurnMapper("turn-1", 0)
|
|
27
|
+
m.Handle(pi.agentStart())
|
|
28
|
+
m.Handle(pi.assistantStart())
|
|
29
|
+
b.ReportAllocs()
|
|
30
|
+
b.ResetTimer()
|
|
31
|
+
for i := 0; i < b.N; i++ {
|
|
32
|
+
m.Handle(pi.toolStart(1, "call-1", "bash"))
|
|
33
|
+
m.Handle(pi.toolDelta(1, `{"command":"go test ./..."}`))
|
|
34
|
+
m.Handle(pi.toolEnd(1, "call-1", "bash", map[string]any{"command": "go test ./..."}))
|
|
35
|
+
m.Handle(pi.execUpdate("call-1", "bash", "ok \tpkg\t0.1s\n"))
|
|
36
|
+
m.Handle(pi.execEnd("call-1", "bash", "ok \tpkg\t0.1s\n", false))
|
|
37
|
+
}
|
|
38
|
+
}
|