@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,34 @@
|
|
|
1
|
+
package mapper
|
|
2
|
+
|
|
3
|
+
// Shared display-title rules for live, catalogued and hydrated sessions (src/pi/session-title.ts).
|
|
4
|
+
|
|
5
|
+
const (
|
|
6
|
+
// NewSessionTitle is the title of a session nobody has named or spoken in yet.
|
|
7
|
+
NewSessionTitle = "New Session"
|
|
8
|
+
untitledSessionTitle = "Untitled session"
|
|
9
|
+
titleFallbackLength = 60
|
|
10
|
+
)
|
|
11
|
+
|
|
12
|
+
// FallbackSessionTitle is what pi displays when no explicit session name exists: the first user
|
|
13
|
+
// message, collapsed. ok is false when there is nothing to show.
|
|
14
|
+
func FallbackSessionTitle(text string) (string, bool) {
|
|
15
|
+
collapsed := collapseSpace(text)
|
|
16
|
+
if collapsed == "" {
|
|
17
|
+
return "", false
|
|
18
|
+
}
|
|
19
|
+
if utf16Len(collapsed) > titleFallbackLength {
|
|
20
|
+
return sliceUTF16(collapsed, 0, titleFallbackLength-1) + "…", true
|
|
21
|
+
}
|
|
22
|
+
return collapsed, true
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
// SessionDisplayTitle is the explicit name, else the first user message, else "Untitled session".
|
|
26
|
+
func SessionDisplayTitle(name, firstUserMessage string) string {
|
|
27
|
+
if n := trimJS(name); n != "" {
|
|
28
|
+
return n
|
|
29
|
+
}
|
|
30
|
+
if t, ok := FallbackSessionTitle(firstUserMessage); ok {
|
|
31
|
+
return t
|
|
32
|
+
}
|
|
33
|
+
return untitledSessionTitle
|
|
34
|
+
}
|
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
package mapper
|
|
2
|
+
|
|
3
|
+
import (
|
|
4
|
+
"strconv"
|
|
5
|
+
"strings"
|
|
6
|
+
|
|
7
|
+
"github.com/microsoft/agent-host-protocol/clients/go/ahptypes"
|
|
8
|
+
)
|
|
9
|
+
|
|
10
|
+
// NormalizeImageMimeType normalizes MIME values at the AHP/pi image boundary ("" when the value
|
|
11
|
+
// is not an image type).
|
|
12
|
+
func NormalizeImageMimeType(mimeType string) string {
|
|
13
|
+
base, _, _ := strings.Cut(mimeType, ";")
|
|
14
|
+
base = strings.ToLower(trimJS(base))
|
|
15
|
+
if !strings.HasPrefix(base, "image/") {
|
|
16
|
+
return ""
|
|
17
|
+
}
|
|
18
|
+
if base == "image/jpg" {
|
|
19
|
+
return "image/jpeg"
|
|
20
|
+
}
|
|
21
|
+
return base
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
func blocksOf(content any) []map[string]any {
|
|
25
|
+
arr, _ := content.([]any)
|
|
26
|
+
var out []map[string]any
|
|
27
|
+
for _, b := range arr {
|
|
28
|
+
if m, ok := b.(map[string]any); ok {
|
|
29
|
+
out = append(out, m)
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
return out
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
// TextFromPiUserContent is the text of a pi user message (a string or content blocks).
|
|
36
|
+
func TextFromPiUserContent(content any) string {
|
|
37
|
+
if s, ok := content.(string); ok {
|
|
38
|
+
return s
|
|
39
|
+
}
|
|
40
|
+
var sb strings.Builder
|
|
41
|
+
for _, b := range blocksOf(content) {
|
|
42
|
+
if b["type"] == "text" {
|
|
43
|
+
if s, ok := b["text"].(string); ok {
|
|
44
|
+
sb.WriteString(s)
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
return sb.String()
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
func imageAttachments(content any) []ahptypes.MessageAttachment {
|
|
52
|
+
var out []ahptypes.MessageAttachment
|
|
53
|
+
for _, b := range blocksOf(content) {
|
|
54
|
+
data, okData := b["data"].(string)
|
|
55
|
+
mime, okMime := b["mimeType"].(string)
|
|
56
|
+
if b["type"] != "image" || !okData || !okMime {
|
|
57
|
+
continue
|
|
58
|
+
}
|
|
59
|
+
contentType := NormalizeImageMimeType(mime)
|
|
60
|
+
if contentType == "" {
|
|
61
|
+
continue
|
|
62
|
+
}
|
|
63
|
+
kind := "image"
|
|
64
|
+
out = append(out, ahptypes.MessageAttachment{Value: &ahptypes.MessageEmbeddedResourceAttachment{
|
|
65
|
+
Type: ahptypes.MessageAttachmentKindEmbeddedResource,
|
|
66
|
+
Label: "Image " + strconv.Itoa(len(out)+1),
|
|
67
|
+
DisplayKind: &kind,
|
|
68
|
+
Data: data,
|
|
69
|
+
ContentType: contentType,
|
|
70
|
+
}})
|
|
71
|
+
}
|
|
72
|
+
return out
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
// UserMessageFromPiContent reconstructs the protocol-visible part of a user message stored by
|
|
76
|
+
// pi. pi persists image bytes but not client-side labels, so replay uses stable ordinal labels
|
|
77
|
+
// while preserving the actual image and MIME type.
|
|
78
|
+
func UserMessageFromPiContent(content any) ahptypes.Message {
|
|
79
|
+
msg := ahptypes.Message{
|
|
80
|
+
Text: TextFromPiUserContent(content),
|
|
81
|
+
Origin: ahptypes.MessageOrigin{Kind: ahptypes.MessageKindUser},
|
|
82
|
+
}
|
|
83
|
+
if atts := imageAttachments(content); len(atts) > 0 {
|
|
84
|
+
msg.Attachments = atts
|
|
85
|
+
}
|
|
86
|
+
return msg
|
|
87
|
+
}
|
|
@@ -0,0 +1,296 @@
|
|
|
1
|
+
package pi_test
|
|
2
|
+
|
|
3
|
+
import (
|
|
4
|
+
"context"
|
|
5
|
+
"encoding/json"
|
|
6
|
+
"reflect"
|
|
7
|
+
"sync"
|
|
8
|
+
"testing"
|
|
9
|
+
|
|
10
|
+
"github.com/microsoft/agent-host-protocol/clients/go/ahptypes"
|
|
11
|
+
|
|
12
|
+
"github.com/MichaelKinsy/pigpen/ahp/internal/mapper"
|
|
13
|
+
"github.com/MichaelKinsy/pigpen/ahp/internal/pi"
|
|
14
|
+
"github.com/MichaelKinsy/pigpen/ahp/internal/testkit"
|
|
15
|
+
"github.com/MichaelKinsy/pigpen/ahp/internal/twin"
|
|
16
|
+
"github.com/MichaelKinsy/pigpen/ahp/internal/wire"
|
|
17
|
+
)
|
|
18
|
+
|
|
19
|
+
// Twins of upstream test/active-turn-reconnect.test.ts: a client that drops in the middle of a
|
|
20
|
+
// turn resumes it by replay, or by snapshot when the replay buffer has moved on.
|
|
21
|
+
|
|
22
|
+
const (
|
|
23
|
+
reconnectClientID = "active-turn-reconnect-client"
|
|
24
|
+
activeTurnID = "active-turn"
|
|
25
|
+
)
|
|
26
|
+
|
|
27
|
+
// controlledBackend records prompts and lets the test stream a response by hand.
|
|
28
|
+
type controlledBackend struct {
|
|
29
|
+
mu sync.Mutex
|
|
30
|
+
prompts []string
|
|
31
|
+
steers []string
|
|
32
|
+
aborts int
|
|
33
|
+
listeners []func(mapper.Event)
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
func (b *controlledBackend) Subscribe(l func(mapper.Event)) func() {
|
|
37
|
+
b.mu.Lock()
|
|
38
|
+
b.listeners = append(b.listeners, l)
|
|
39
|
+
b.mu.Unlock()
|
|
40
|
+
return func() {}
|
|
41
|
+
}
|
|
42
|
+
func (b *controlledBackend) Prompt(_ context.Context, text string, _ []mapper.Image) error {
|
|
43
|
+
b.mu.Lock()
|
|
44
|
+
b.prompts = append(b.prompts, text)
|
|
45
|
+
b.mu.Unlock()
|
|
46
|
+
return nil
|
|
47
|
+
}
|
|
48
|
+
func (b *controlledBackend) Steer(_ context.Context, text string, _ []mapper.Image) error {
|
|
49
|
+
b.mu.Lock()
|
|
50
|
+
b.steers = append(b.steers, text)
|
|
51
|
+
b.mu.Unlock()
|
|
52
|
+
return nil
|
|
53
|
+
}
|
|
54
|
+
func (b *controlledBackend) Abort(context.Context) error {
|
|
55
|
+
b.mu.Lock()
|
|
56
|
+
b.aborts++
|
|
57
|
+
b.mu.Unlock()
|
|
58
|
+
return nil
|
|
59
|
+
}
|
|
60
|
+
func (b *controlledBackend) emit(t *testing.T, literal string) {
|
|
61
|
+
b.mu.Lock()
|
|
62
|
+
ls := append([]func(mapper.Event){}, b.listeners...)
|
|
63
|
+
b.mu.Unlock()
|
|
64
|
+
for _, l := range ls {
|
|
65
|
+
l(event(t, literal))
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
func (b *controlledBackend) startResponse(t *testing.T, text string) {
|
|
69
|
+
b.emit(t, `{"type":"agent_start"}`)
|
|
70
|
+
b.emit(t, `{"type":"message_start","message":{"role":"assistant"}}`)
|
|
71
|
+
b.emit(t, `{"type":"message_update","message":{"role":"assistant"},"assistantMessageEvent":{"type":"text_delta","contentIndex":0,"delta":"`+text+`"}}`)
|
|
72
|
+
}
|
|
73
|
+
func (b *controlledBackend) finishResponse(t *testing.T) {
|
|
74
|
+
b.emit(t, `{"type":"message_end","message":{"role":"assistant"}}`)
|
|
75
|
+
b.emit(t, `{"type":"agent_end","messages":[],"willRetry":false}`)
|
|
76
|
+
b.emit(t, `{"type":"agent_settled"}`)
|
|
77
|
+
}
|
|
78
|
+
func (b *controlledBackend) count(f func(*controlledBackend) int) int {
|
|
79
|
+
b.mu.Lock()
|
|
80
|
+
defer b.mu.Unlock()
|
|
81
|
+
return f(b)
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
type activeTurn struct {
|
|
85
|
+
t *testing.T
|
|
86
|
+
backend *controlledBackend
|
|
87
|
+
h *harness
|
|
88
|
+
client *testkit.Client
|
|
89
|
+
session, chat string
|
|
90
|
+
baseline int64
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
func startActiveTurn(t *testing.T, replayBuffer int) *activeTurn {
|
|
94
|
+
t.Helper()
|
|
95
|
+
backend := &controlledBackend{}
|
|
96
|
+
h := startHarness(t, harnessOptions{replayBuffer: replayBuffer, createBackend: func(*pi.LiveSession) (pi.Backend, error) { return backend, nil }})
|
|
97
|
+
client := testkit.Connect(t, h.host)
|
|
98
|
+
t.Cleanup(client.Close)
|
|
99
|
+
client.Initialize(reconnectClientID, nil)
|
|
100
|
+
id := newID()
|
|
101
|
+
f := &activeTurn{t: t, backend: backend, h: h, client: client, session: wire.SessionURI(id), chat: wire.ChatURI(id)}
|
|
102
|
+
client.Must("createSession", obj{"channel": f.session})
|
|
103
|
+
client.Subscribe(f.session)
|
|
104
|
+
client.Subscribe(f.chat)
|
|
105
|
+
client.Dispatch(f.chat, obj{"type": "chat/turnStarted", "turnId": activeTurnID, "startedAt": "2025-01-01T00:00:00.000Z", "message": userMessage("keep working")})
|
|
106
|
+
testkit.Eventually(t, "the initial prompt to reach the backend", func() bool { return backend.count(func(b *controlledBackend) int { return len(b.prompts) }) == 1 })
|
|
107
|
+
if a := h.host.Store().Chat(f.chat).ActiveTurn; a == nil || a.Id != activeTurnID {
|
|
108
|
+
t.Fatalf("activeTurn = %+v", a)
|
|
109
|
+
}
|
|
110
|
+
f.baseline = h.host.ServerSeq()
|
|
111
|
+
return f
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
func (f *activeTurn) reconnect(c *testkit.Client, into any) {
|
|
115
|
+
f.t.Helper()
|
|
116
|
+
c.Decode(c.Must("reconnect", obj{"channel": wire.RootChannel, "clientId": reconnectClientID, "lastSeenServerSeq": f.baseline, "subscriptions": []string{f.session, f.chat}}), into)
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
func responseText(state *ahptypes.ChatState) string {
|
|
120
|
+
turn := (*ahptypes.Turn)(nil)
|
|
121
|
+
var parts []ahptypes.ResponsePart
|
|
122
|
+
if state.ActiveTurn != nil {
|
|
123
|
+
parts = state.ActiveTurn.ResponseParts
|
|
124
|
+
} else if n := len(state.Turns); n > 0 {
|
|
125
|
+
turn = &state.Turns[n-1]
|
|
126
|
+
parts = turn.ResponseParts
|
|
127
|
+
}
|
|
128
|
+
for _, p := range parts {
|
|
129
|
+
if md, ok := p.Value.(*ahptypes.MarkdownResponsePart); ok {
|
|
130
|
+
return md.Content
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
return ""
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
func (f *activeTurn) prompts() []string {
|
|
137
|
+
f.backend.mu.Lock()
|
|
138
|
+
defer f.backend.mu.Unlock()
|
|
139
|
+
return append([]string(nil), f.backend.prompts...)
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
func TestActiveTurnReconnect(t *testing.T) {
|
|
143
|
+
twin.Run(t, "active-turn-reconnect", "replays offline output and accepts steering and cancellation after reconnect", func(t *testing.T) {
|
|
144
|
+
f := startActiveTurn(t, 64)
|
|
145
|
+
observer := testkit.Connect(t, f.h.host)
|
|
146
|
+
t.Cleanup(observer.Close)
|
|
147
|
+
observer.Initialize(nextClientID(), nil)
|
|
148
|
+
observer.Subscribe(f.chat)
|
|
149
|
+
|
|
150
|
+
f.client.Close()
|
|
151
|
+
testkit.Eventually(t, "only the observer to remain subscribed", func() bool { return f.h.host.SubscriberCount(f.chat) == 1 })
|
|
152
|
+
f.backend.startResponse(t, "offline partial")
|
|
153
|
+
n, ok := observer.Await(func(n testkit.Notification) bool {
|
|
154
|
+
e, isAction := n.Envelope()
|
|
155
|
+
if !isAction {
|
|
156
|
+
return false
|
|
157
|
+
}
|
|
158
|
+
_, part := e.Action.Value.(*ahptypes.ChatResponsePartAction)
|
|
159
|
+
return part
|
|
160
|
+
}, testkit.Timeout)
|
|
161
|
+
if !ok {
|
|
162
|
+
t.Fatal("the observer saw no response part")
|
|
163
|
+
}
|
|
164
|
+
observed, _ := n.Envelope()
|
|
165
|
+
|
|
166
|
+
resumed := testkit.Connect(t, f.h.host)
|
|
167
|
+
t.Cleanup(resumed.Close)
|
|
168
|
+
var result struct {
|
|
169
|
+
Type string
|
|
170
|
+
Actions []ahptypes.ActionEnvelope
|
|
171
|
+
}
|
|
172
|
+
f.reconnect(resumed, &result)
|
|
173
|
+
if result.Type != "replay" {
|
|
174
|
+
t.Fatalf("type = %q", result.Type)
|
|
175
|
+
}
|
|
176
|
+
found := false
|
|
177
|
+
for _, e := range result.Actions {
|
|
178
|
+
found = found || e.ServerSeq == observed.ServerSeq
|
|
179
|
+
}
|
|
180
|
+
if !found {
|
|
181
|
+
t.Fatal("the replay lacks the action the observer saw")
|
|
182
|
+
}
|
|
183
|
+
if got := responseText(f.h.host.Store().Chat(f.chat)); got != "offline partial" {
|
|
184
|
+
t.Fatalf("response = %q", got)
|
|
185
|
+
}
|
|
186
|
+
if got := f.prompts(); !reflect.DeepEqual(got, []string{"keep working"}) {
|
|
187
|
+
t.Fatalf("prompts = %v", got)
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
resumed.Dispatch(f.chat, obj{"type": "chat/pendingMessageSet", "kind": "steering", "id": "after-reconnect", "message": userMessage("focus here")})
|
|
191
|
+
testkit.Eventually(t, "reconnected steering to reach the backend", func() bool { return f.backend.count(func(b *controlledBackend) int { return len(b.steers) }) == 1 })
|
|
192
|
+
resumed.Dispatch(f.chat, obj{"type": "chat/turnCancelled", "turnId": activeTurnID, "duration": 0})
|
|
193
|
+
testkit.Eventually(t, "reconnected cancellation to reach the backend", func() bool { return f.backend.count(func(b *controlledBackend) int { return b.aborts }) == 1 })
|
|
194
|
+
state := f.h.host.Store().Chat(f.chat)
|
|
195
|
+
if state.ActiveTurn != nil || state.Turns[len(state.Turns)-1].State != ahptypes.TurnStateCancelled {
|
|
196
|
+
t.Fatalf("activeTurn %+v, turns %+v", state.ActiveTurn, turnShapes(state.Turns))
|
|
197
|
+
}
|
|
198
|
+
})
|
|
199
|
+
|
|
200
|
+
twin.Run(t, "active-turn-reconnect", "replays a turn that completed while its client was offline", func(t *testing.T) {
|
|
201
|
+
f := startActiveTurn(t, 64)
|
|
202
|
+
f.client.Close()
|
|
203
|
+
testkit.Eventually(t, "the original chat subscription to disconnect", func() bool { return f.h.host.SubscriberCount(f.chat) == 0 })
|
|
204
|
+
f.backend.startResponse(t, "finished offline")
|
|
205
|
+
f.backend.finishResponse(t)
|
|
206
|
+
|
|
207
|
+
resumed := testkit.Connect(t, f.h.host)
|
|
208
|
+
t.Cleanup(resumed.Close)
|
|
209
|
+
var result struct {
|
|
210
|
+
Type string
|
|
211
|
+
Actions []ahptypes.ActionEnvelope
|
|
212
|
+
}
|
|
213
|
+
f.reconnect(resumed, &result)
|
|
214
|
+
if result.Type != "replay" {
|
|
215
|
+
t.Fatalf("type = %q", result.Type)
|
|
216
|
+
}
|
|
217
|
+
completed := false
|
|
218
|
+
for _, e := range result.Actions {
|
|
219
|
+
_, ok := e.Action.Value.(*ahptypes.ChatTurnCompleteAction)
|
|
220
|
+
completed = completed || ok
|
|
221
|
+
}
|
|
222
|
+
if !completed {
|
|
223
|
+
t.Fatal("the replay lacks chat/turnComplete")
|
|
224
|
+
}
|
|
225
|
+
state := f.h.host.Store().Chat(f.chat)
|
|
226
|
+
if state.Turns[len(state.Turns)-1].State != ahptypes.TurnStateComplete || responseText(state) != "finished offline" {
|
|
227
|
+
t.Fatalf("turns %+v response %q", turnShapes(state.Turns), responseText(state))
|
|
228
|
+
}
|
|
229
|
+
if got := f.prompts(); !reflect.DeepEqual(got, []string{"keep working"}) {
|
|
230
|
+
t.Fatalf("prompts = %v", got)
|
|
231
|
+
}
|
|
232
|
+
})
|
|
233
|
+
|
|
234
|
+
twin.Run(t, "active-turn-reconnect", "falls back to an active-turn snapshot and continues streaming", func(t *testing.T) {
|
|
235
|
+
f := startActiveTurn(t, 4)
|
|
236
|
+
f.client.Close()
|
|
237
|
+
testkit.Eventually(t, "the original chat subscription to disconnect", func() bool { return f.h.host.SubscriberCount(f.chat) == 0 })
|
|
238
|
+
f.backend.startResponse(t, "snapshot partial")
|
|
239
|
+
for i := int64(0); i < 6; i++ {
|
|
240
|
+
f.h.host.DispatchServerAction(wire.RootChannel, activeSessions(i))
|
|
241
|
+
}
|
|
242
|
+
resumed := testkit.Connect(t, f.h.host)
|
|
243
|
+
t.Cleanup(resumed.Close)
|
|
244
|
+
// The snapshot list also carries the session: decode chat state only for its own entry.
|
|
245
|
+
raw := resumed.Must("reconnect", obj{"channel": wire.RootChannel, "clientId": reconnectClientID, "lastSeenServerSeq": f.baseline, "subscriptions": []string{f.session, f.chat}})
|
|
246
|
+
var generic struct {
|
|
247
|
+
Type string
|
|
248
|
+
Snapshots []struct {
|
|
249
|
+
Resource string
|
|
250
|
+
State map[string]any
|
|
251
|
+
}
|
|
252
|
+
}
|
|
253
|
+
resumed.Decode(raw, &generic)
|
|
254
|
+
if generic.Type != "snapshot" {
|
|
255
|
+
t.Fatalf("type = %q", generic.Type)
|
|
256
|
+
}
|
|
257
|
+
var chatState *ahptypes.ChatState
|
|
258
|
+
for _, s := range generic.Snapshots {
|
|
259
|
+
if s.Resource == f.chat {
|
|
260
|
+
var st ahptypes.ChatState
|
|
261
|
+
resumedJSON := testkit.JSON(t, s.State)
|
|
262
|
+
if err := jsonUnmarshal(resumedJSON, &st); err != nil {
|
|
263
|
+
t.Fatal(err)
|
|
264
|
+
}
|
|
265
|
+
chatState = &st
|
|
266
|
+
}
|
|
267
|
+
}
|
|
268
|
+
if chatState == nil || responseText(chatState) != "snapshot partial" {
|
|
269
|
+
t.Fatalf("chat snapshot = %+v", chatState)
|
|
270
|
+
}
|
|
271
|
+
f.backend.finishResponse(t)
|
|
272
|
+
if _, ok := resumed.Await(func(n testkit.Notification) bool {
|
|
273
|
+
e, isAction := n.Envelope()
|
|
274
|
+
if !isAction {
|
|
275
|
+
return false
|
|
276
|
+
}
|
|
277
|
+
_, done := e.Action.Value.(*ahptypes.ChatTurnCompleteAction)
|
|
278
|
+
return done
|
|
279
|
+
}, testkit.Timeout); !ok {
|
|
280
|
+
t.Fatal("the resumed client never saw the turn complete")
|
|
281
|
+
}
|
|
282
|
+
state := f.h.host.Store().Chat(f.chat)
|
|
283
|
+
if state.Turns[len(state.Turns)-1].State != ahptypes.TurnStateComplete {
|
|
284
|
+
t.Fatalf("turns %+v", turnShapes(state.Turns))
|
|
285
|
+
}
|
|
286
|
+
if got := f.prompts(); !reflect.DeepEqual(got, []string{"keep working"}) {
|
|
287
|
+
t.Fatalf("prompts = %v", got)
|
|
288
|
+
}
|
|
289
|
+
})
|
|
290
|
+
}
|
|
291
|
+
|
|
292
|
+
func jsonUnmarshal(s string, v any) error { return json.Unmarshal([]byte(s), v) }
|
|
293
|
+
|
|
294
|
+
func activeSessions(n int64) ahptypes.StateAction {
|
|
295
|
+
return ahptypes.StateAction{Value: &ahptypes.RootActiveSessionsChangedAction{Type: ahptypes.ActionTypeRootActiveSessionsChanged, ActiveSessions: n}}
|
|
296
|
+
}
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
// Package pi is the agent-facing half of the host: it drives a Pi agent through a [Backend], owns
|
|
2
|
+
// the live-session registry, and serves session history. It ports pi-ahp's src/pi. The
|
|
3
|
+
// protocol-shaped half (state and summaries) lives in package channels and knows nothing about
|
|
4
|
+
// the agent; the dependency runs one way.
|
|
5
|
+
package pi
|
|
6
|
+
|
|
7
|
+
import (
|
|
8
|
+
"context"
|
|
9
|
+
"encoding/json"
|
|
10
|
+
|
|
11
|
+
"github.com/microsoft/agent-host-protocol/clients/go/ahptypes"
|
|
12
|
+
|
|
13
|
+
"github.com/MichaelKinsy/pigpen/ahp/internal/mapper"
|
|
14
|
+
)
|
|
15
|
+
|
|
16
|
+
// Backend is the agent a chat drives. Subscribe delivers Pi's agent events (decoded JSON).
|
|
17
|
+
//
|
|
18
|
+
// Prompt and Steer take a context: cancelling it abandons the call (the agent itself is stopped
|
|
19
|
+
// with Abort). The optional capabilities are separate interfaces: [ModelSelector],
|
|
20
|
+
// [SelectionReporter], [Truncater], [Disposer].
|
|
21
|
+
type Backend interface {
|
|
22
|
+
Subscribe(listener func(mapper.Event)) (unsubscribe func())
|
|
23
|
+
Prompt(ctx context.Context, text string, images []mapper.Image) error
|
|
24
|
+
Steer(ctx context.Context, text string, images []mapper.Image) error
|
|
25
|
+
Abort(ctx context.Context) error
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
// Disposer releases a backend's resources. A failure is logged; it never blocks removal.
|
|
29
|
+
type Disposer interface{ Dispose() error }
|
|
30
|
+
|
|
31
|
+
// ModelSelector switches the model a turn runs on.
|
|
32
|
+
type ModelSelector interface {
|
|
33
|
+
SelectModel(ctx context.Context, selection ahptypes.ModelSelection) error
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
// SelectionReporter reports the model and reasoning effort in effect.
|
|
37
|
+
type SelectionReporter interface {
|
|
38
|
+
CurrentSelection() *ahptypes.ModelSelection
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
// Truncater moves the agent back to a session entry; false means it was refused.
|
|
42
|
+
type Truncater interface {
|
|
43
|
+
Truncate(ctx context.Context, entryID string) (bool, error)
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
// messageInput converts a typed AHP message to Pi's prompt shape.
|
|
47
|
+
func messageInput(message ahptypes.Message) (mapper.MessageInput, error) {
|
|
48
|
+
raw, err := json.Marshal(message)
|
|
49
|
+
if err != nil {
|
|
50
|
+
return mapper.MessageInput{}, err
|
|
51
|
+
}
|
|
52
|
+
var generic any
|
|
53
|
+
if err := json.Unmarshal(raw, &generic); err != nil {
|
|
54
|
+
return mapper.MessageInput{}, err
|
|
55
|
+
}
|
|
56
|
+
return mapper.MessageInputForPi(generic)
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
func rejectionReason(message ahptypes.Message) string {
|
|
60
|
+
raw, _ := json.Marshal(message)
|
|
61
|
+
var generic any
|
|
62
|
+
_ = json.Unmarshal(raw, &generic)
|
|
63
|
+
return mapper.MessageRejectionReason(generic)
|
|
64
|
+
}
|