@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,1939 @@
|
|
|
1
|
+
package ahp
|
|
2
|
+
|
|
3
|
+
import (
|
|
4
|
+
"encoding/json"
|
|
5
|
+
"time"
|
|
6
|
+
|
|
7
|
+
"github.com/microsoft/agent-host-protocol/clients/go/ahptypes"
|
|
8
|
+
)
|
|
9
|
+
|
|
10
|
+
// ─── ReduceOutcome ─────────────────────────────────────────────────────
|
|
11
|
+
|
|
12
|
+
// ReduceOutcome reports what happened when a reducer was asked to
|
|
13
|
+
// apply an action.
|
|
14
|
+
type ReduceOutcome int
|
|
15
|
+
|
|
16
|
+
const (
|
|
17
|
+
// ReduceOutcomeApplied indicates the action was applied and the
|
|
18
|
+
// state was mutated.
|
|
19
|
+
ReduceOutcomeApplied ReduceOutcome = iota
|
|
20
|
+
// ReduceOutcomeNoOp indicates the action was recognized but had no
|
|
21
|
+
// effect against this state (e.g. an unknown turn id).
|
|
22
|
+
ReduceOutcomeNoOp
|
|
23
|
+
// ReduceOutcomeOutOfScope indicates the action targets a different
|
|
24
|
+
// scope (e.g. a session action passed to the root reducer).
|
|
25
|
+
ReduceOutcomeOutOfScope
|
|
26
|
+
)
|
|
27
|
+
|
|
28
|
+
func addMillisecondsToTimestamp(timestamp string, duration int64) string {
|
|
29
|
+
start, err := time.Parse(time.RFC3339Nano, timestamp)
|
|
30
|
+
if err != nil {
|
|
31
|
+
panic("invalid ISO 8601 timestamp: " + timestamp)
|
|
32
|
+
}
|
|
33
|
+
delta := time.Duration(duration) * time.Millisecond
|
|
34
|
+
if int64(delta/time.Millisecond) != duration {
|
|
35
|
+
panic("timestamp duration overflows time.Duration")
|
|
36
|
+
}
|
|
37
|
+
return start.Add(delta).UTC().Format("2006-01-02T15:04:05.000Z")
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
// ─── Status helpers ────────────────────────────────────────────────────
|
|
41
|
+
|
|
42
|
+
// statusActivityMask covers the mutually-exclusive activity bits
|
|
43
|
+
// (bits 0–4) of [ahptypes.SessionStatus].
|
|
44
|
+
const statusActivityMask ahptypes.SessionStatus = (1 << 5) - 1
|
|
45
|
+
|
|
46
|
+
func withStatusFlag(status, flag ahptypes.SessionStatus, set bool) ahptypes.SessionStatus {
|
|
47
|
+
if set {
|
|
48
|
+
return status | flag
|
|
49
|
+
}
|
|
50
|
+
return status &^ flag
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
// awaitsUser reports whether an entry blocks on the *user*.
|
|
54
|
+
//
|
|
55
|
+
// SessionInputRequestKindToolClientExecution is work delegated to a client, not
|
|
56
|
+
// a prompt: the call has already cleared its confirmation gate and is simply
|
|
57
|
+
// running somewhere else. Counting it would report a session as awaiting the
|
|
58
|
+
// user for the entire duration of every client tool call.
|
|
59
|
+
func awaitsUser(request ahptypes.SessionInputRequest) bool {
|
|
60
|
+
_, isClientExecution := request.Value.(*ahptypes.SessionToolClientExecutionRequest)
|
|
61
|
+
return !isClientExecution
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
// withInputNeededStatus reflects the session-level input queue into the activity
|
|
65
|
+
// bits of status. A queue holding any user-blocking entry promotes the activity
|
|
66
|
+
// to InputNeeded; draining those entries clears the input-needed-specific bit.
|
|
67
|
+
// Because InputNeeded implies InProgress, an unblocked turn falls back to
|
|
68
|
+
// InProgress while an already-idle session stays idle. Orthogonal flags
|
|
69
|
+
// (IsRead / IsArchived) are preserved.
|
|
70
|
+
func withInputNeededStatus(status ahptypes.SessionStatus, inputNeeded []ahptypes.SessionInputRequest) ahptypes.SessionStatus {
|
|
71
|
+
for _, request := range inputNeeded {
|
|
72
|
+
if awaitsUser(request) {
|
|
73
|
+
return (status &^ statusActivityMask) | ahptypes.SessionStatusInputNeeded
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
return status &^ (ahptypes.SessionStatusInputNeeded &^ ahptypes.SessionStatusInProgress)
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
// ─── Tool-call helpers ─────────────────────────────────────────────────
|
|
80
|
+
|
|
81
|
+
// toolCallCommon carries the fields shared by every concrete
|
|
82
|
+
// [ahptypes.ToolCallState] variant.
|
|
83
|
+
type toolCallCommon struct {
|
|
84
|
+
id string
|
|
85
|
+
name string
|
|
86
|
+
displayName string
|
|
87
|
+
intention *string
|
|
88
|
+
toolInput *ahptypes.ToolInput
|
|
89
|
+
contributor *ahptypes.ToolCallContributor
|
|
90
|
+
meta ahptypes.JSONObject
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
func toolCallMeta(tc ahptypes.ToolCallState) toolCallCommon {
|
|
94
|
+
switch v := tc.Value.(type) {
|
|
95
|
+
case *ahptypes.ToolCallStreamingState:
|
|
96
|
+
return toolCallCommon{v.ToolCallId, v.ToolName, v.DisplayName, v.Intention, nil, v.Contributor, v.Meta}
|
|
97
|
+
case *ahptypes.ToolCallPendingConfirmationState:
|
|
98
|
+
return toolCallCommon{v.ToolCallId, v.ToolName, v.DisplayName, v.Intention, v.ToolInput, v.Contributor, v.Meta}
|
|
99
|
+
case *ahptypes.ToolCallRunningState:
|
|
100
|
+
return toolCallCommon{v.ToolCallId, v.ToolName, v.DisplayName, v.Intention, v.ToolInput, v.Contributor, v.Meta}
|
|
101
|
+
case *ahptypes.ToolCallAuthRequiredState:
|
|
102
|
+
return toolCallCommon{v.ToolCallId, v.ToolName, v.DisplayName, v.Intention, v.ToolInput, v.Contributor, v.Meta}
|
|
103
|
+
case *ahptypes.ToolCallPendingResultConfirmationState:
|
|
104
|
+
return toolCallCommon{v.ToolCallId, v.ToolName, v.DisplayName, v.Intention, v.ToolInput, v.Contributor, v.Meta}
|
|
105
|
+
case *ahptypes.ToolCallCompletedState:
|
|
106
|
+
return toolCallCommon{v.ToolCallId, v.ToolName, v.DisplayName, v.Intention, v.ToolInput, v.Contributor, v.Meta}
|
|
107
|
+
case *ahptypes.ToolCallCancelledState:
|
|
108
|
+
return toolCallCommon{v.ToolCallId, v.ToolName, v.DisplayName, v.Intention, v.ToolInput, v.Contributor, v.Meta}
|
|
109
|
+
}
|
|
110
|
+
return toolCallCommon{}
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
// toolCallInvocationMessage pulls the invocation message from a non-terminal
|
|
114
|
+
// tool-call variant.
|
|
115
|
+
func toolCallInvocationMessage(tc ahptypes.ToolCallState) ahptypes.StringOrMarkdown {
|
|
116
|
+
switch v := tc.Value.(type) {
|
|
117
|
+
case *ahptypes.ToolCallStreamingState:
|
|
118
|
+
var im ahptypes.StringOrMarkdown
|
|
119
|
+
if v.InvocationMessage != nil {
|
|
120
|
+
im = *v.InvocationMessage
|
|
121
|
+
}
|
|
122
|
+
return im
|
|
123
|
+
case *ahptypes.ToolCallPendingConfirmationState:
|
|
124
|
+
return v.InvocationMessage
|
|
125
|
+
case *ahptypes.ToolCallRunningState:
|
|
126
|
+
return v.InvocationMessage
|
|
127
|
+
case *ahptypes.ToolCallAuthRequiredState:
|
|
128
|
+
return v.InvocationMessage
|
|
129
|
+
case *ahptypes.ToolCallPendingResultConfirmationState:
|
|
130
|
+
return v.InvocationMessage
|
|
131
|
+
}
|
|
132
|
+
return ahptypes.StringOrMarkdown{}
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
func toolCallID(tc ahptypes.ToolCallState) string {
|
|
136
|
+
return toolCallMeta(tc).id
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
// hasBlockingToolCall reports whether the active turn has any tool call blocked
|
|
140
|
+
// on confirmation or MCP authentication.
|
|
141
|
+
func hasBlockingToolCall(state *ahptypes.ChatState) bool {
|
|
142
|
+
if state.ActiveTurn == nil {
|
|
143
|
+
return false
|
|
144
|
+
}
|
|
145
|
+
for _, part := range state.ActiveTurn.ResponseParts {
|
|
146
|
+
tc, ok := part.Value.(*ahptypes.ToolCallResponsePart)
|
|
147
|
+
if !ok {
|
|
148
|
+
continue
|
|
149
|
+
}
|
|
150
|
+
switch tc.ToolCall.Value.(type) {
|
|
151
|
+
case *ahptypes.ToolCallPendingConfirmationState,
|
|
152
|
+
*ahptypes.ToolCallAuthRequiredState,
|
|
153
|
+
*ahptypes.ToolCallPendingResultConfirmationState:
|
|
154
|
+
return true
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
return false
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
func hasOpenInputRequest(state *ahptypes.ChatState) bool {
|
|
161
|
+
if state.ActiveTurn == nil {
|
|
162
|
+
return false
|
|
163
|
+
}
|
|
164
|
+
for _, part := range state.ActiveTurn.ResponseParts {
|
|
165
|
+
input, ok := part.Value.(*ahptypes.InputRequestResponsePart)
|
|
166
|
+
if ok && input.Response == nil {
|
|
167
|
+
return true
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
return false
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
func hasResumableError(turn *ahptypes.Turn) bool {
|
|
174
|
+
if len(turn.ResponseParts) == 0 {
|
|
175
|
+
return false
|
|
176
|
+
}
|
|
177
|
+
part, ok := turn.ResponseParts[len(turn.ResponseParts)-1].Value.(*ahptypes.ErrorResponsePart)
|
|
178
|
+
return ok && part.Resumable != nil && *part.Resumable
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
func summaryStatus(state *ahptypes.ChatState, terminal *ahptypes.SessionStatus) ahptypes.SessionStatus {
|
|
182
|
+
var activity ahptypes.SessionStatus
|
|
183
|
+
switch {
|
|
184
|
+
case terminal != nil:
|
|
185
|
+
activity = *terminal
|
|
186
|
+
case hasOpenInputRequest(state) || hasBlockingToolCall(state):
|
|
187
|
+
activity = ahptypes.SessionStatusInputNeeded
|
|
188
|
+
case state.ActiveTurn != nil:
|
|
189
|
+
activity = ahptypes.SessionStatusInProgress
|
|
190
|
+
default:
|
|
191
|
+
activity = ahptypes.SessionStatusIdle
|
|
192
|
+
}
|
|
193
|
+
return (state.Status &^ statusActivityMask) | activity
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
func refreshSummaryStatus(state *ahptypes.ChatState) {
|
|
197
|
+
state.Status = summaryStatus(state, nil)
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
// ─── Active-turn helpers ───────────────────────────────────────────────
|
|
201
|
+
|
|
202
|
+
func endTurn(state *ahptypes.ChatState, turnID string, duration int64, turnState ahptypes.TurnState, terminalStatus *ahptypes.SessionStatus, errorPart *ahptypes.ErrorResponsePart) ReduceOutcome {
|
|
203
|
+
if state.ActiveTurn == nil || state.ActiveTurn.Id != turnID {
|
|
204
|
+
return ReduceOutcomeNoOp
|
|
205
|
+
}
|
|
206
|
+
active := state.ActiveTurn
|
|
207
|
+
state.ActiveTurn = nil
|
|
208
|
+
|
|
209
|
+
parts := make([]ahptypes.ResponsePart, 0, len(active.ResponseParts))
|
|
210
|
+
for _, part := range active.ResponseParts {
|
|
211
|
+
tc, ok := part.Value.(*ahptypes.ToolCallResponsePart)
|
|
212
|
+
if !ok {
|
|
213
|
+
parts = append(parts, part)
|
|
214
|
+
continue
|
|
215
|
+
}
|
|
216
|
+
switch tc.ToolCall.Value.(type) {
|
|
217
|
+
case *ahptypes.ToolCallCompletedState, *ahptypes.ToolCallCancelledState:
|
|
218
|
+
parts = append(parts, part)
|
|
219
|
+
continue
|
|
220
|
+
}
|
|
221
|
+
common := toolCallMeta(tc.ToolCall)
|
|
222
|
+
invocation := toolCallInvocationMessage(tc.ToolCall)
|
|
223
|
+
cancelled := &ahptypes.ToolCallCancelledState{
|
|
224
|
+
Status: ahptypes.ToolCallStatusCancelled,
|
|
225
|
+
ToolCallId: common.id,
|
|
226
|
+
ToolName: common.name,
|
|
227
|
+
DisplayName: common.displayName,
|
|
228
|
+
Intention: common.intention,
|
|
229
|
+
ToolInput: common.toolInput,
|
|
230
|
+
Contributor: common.contributor,
|
|
231
|
+
Meta: common.meta,
|
|
232
|
+
InvocationMessage: invocation,
|
|
233
|
+
Reason: ahptypes.ToolCallCancellationReasonSkipped,
|
|
234
|
+
}
|
|
235
|
+
parts = append(parts, ahptypes.ResponsePart{Value: &ahptypes.ToolCallResponsePart{
|
|
236
|
+
Kind: ahptypes.ResponsePartKindToolCall,
|
|
237
|
+
ToolCall: ahptypes.ToolCallState{Value: cancelled},
|
|
238
|
+
}})
|
|
239
|
+
}
|
|
240
|
+
if errorPart != nil {
|
|
241
|
+
parts = append(parts, ahptypes.ResponsePart{Value: errorPart})
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
// Defensive clamp: duration is producer-supplied and opaque to this
|
|
245
|
+
// reducer, but a negative value would be nonsensical to display.
|
|
246
|
+
if duration < 0 {
|
|
247
|
+
duration = 0
|
|
248
|
+
}
|
|
249
|
+
startedAt := active.StartedAt
|
|
250
|
+
turn := ahptypes.Turn{
|
|
251
|
+
Id: active.Id,
|
|
252
|
+
StartedAt: &startedAt,
|
|
253
|
+
Duration: &duration,
|
|
254
|
+
Message: active.Message,
|
|
255
|
+
ResponseParts: parts,
|
|
256
|
+
Usage: active.Usage,
|
|
257
|
+
State: turnState,
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
state.Turns = append(state.Turns, turn)
|
|
261
|
+
state.ModifiedAt = addMillisecondsToTimestamp(active.StartedAt, duration)
|
|
262
|
+
state.Status = summaryStatus(state, terminalStatus)
|
|
263
|
+
return ReduceOutcomeApplied
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
func upsertInputRequest(state *ahptypes.ChatState, req ahptypes.ChatInputRequest) ReduceOutcome {
|
|
267
|
+
if state.ActiveTurn == nil {
|
|
268
|
+
return ReduceOutcomeNoOp
|
|
269
|
+
}
|
|
270
|
+
parts := state.ActiveTurn.ResponseParts
|
|
271
|
+
for i := range parts {
|
|
272
|
+
input, ok := parts[i].Value.(*ahptypes.InputRequestResponsePart)
|
|
273
|
+
if ok && input.Response == nil && input.Request.Id == req.Id {
|
|
274
|
+
if req.Answers == nil {
|
|
275
|
+
req.Answers = input.Request.Answers
|
|
276
|
+
}
|
|
277
|
+
parts[i].Value = &ahptypes.InputRequestResponsePart{
|
|
278
|
+
Kind: ahptypes.ResponsePartKindInputRequest,
|
|
279
|
+
Request: req,
|
|
280
|
+
}
|
|
281
|
+
state.ActiveTurn.ResponseParts = parts
|
|
282
|
+
state.Status = summaryStatus(state, nil)
|
|
283
|
+
state.Status = withStatusFlag(state.Status, ahptypes.SessionStatusIsRead, false)
|
|
284
|
+
return ReduceOutcomeApplied
|
|
285
|
+
}
|
|
286
|
+
}
|
|
287
|
+
state.ActiveTurn.ResponseParts = append(parts, ahptypes.ResponsePart{
|
|
288
|
+
Value: &ahptypes.InputRequestResponsePart{
|
|
289
|
+
Kind: ahptypes.ResponsePartKindInputRequest,
|
|
290
|
+
Request: req,
|
|
291
|
+
},
|
|
292
|
+
})
|
|
293
|
+
state.Status = summaryStatus(state, nil)
|
|
294
|
+
state.Status = withStatusFlag(state.Status, ahptypes.SessionStatusIsRead, false)
|
|
295
|
+
return ReduceOutcomeApplied
|
|
296
|
+
}
|
|
297
|
+
|
|
298
|
+
// ─── Customization helpers ─────────────────────────────────────────────
|
|
299
|
+
|
|
300
|
+
func customizationID(c ahptypes.Customization) (string, bool) {
|
|
301
|
+
switch v := c.Value.(type) {
|
|
302
|
+
case *ahptypes.PluginCustomization:
|
|
303
|
+
return v.Id, true
|
|
304
|
+
case *ahptypes.DirectoryCustomization:
|
|
305
|
+
return v.Id, true
|
|
306
|
+
case *ahptypes.McpServerCustomization:
|
|
307
|
+
return v.Id, true
|
|
308
|
+
}
|
|
309
|
+
return "", false
|
|
310
|
+
}
|
|
311
|
+
|
|
312
|
+
func sessionInputRequestID(r ahptypes.SessionInputRequest) (string, bool) {
|
|
313
|
+
switch v := r.Value.(type) {
|
|
314
|
+
case *ahptypes.SessionChatInputRequest:
|
|
315
|
+
return v.Id, true
|
|
316
|
+
case *ahptypes.SessionToolConfirmationRequest:
|
|
317
|
+
return v.Id, true
|
|
318
|
+
case *ahptypes.SessionToolClientExecutionRequest:
|
|
319
|
+
return v.Id, true
|
|
320
|
+
case *ahptypes.SessionToolAuthenticationRequest:
|
|
321
|
+
return v.Id, true
|
|
322
|
+
}
|
|
323
|
+
return "", false
|
|
324
|
+
}
|
|
325
|
+
|
|
326
|
+
func childCustomizationID(c ahptypes.ChildCustomization) (string, bool) {
|
|
327
|
+
switch v := c.Value.(type) {
|
|
328
|
+
case *ahptypes.AgentCustomization:
|
|
329
|
+
return v.Id, true
|
|
330
|
+
case *ahptypes.SkillCustomization:
|
|
331
|
+
return v.Id, true
|
|
332
|
+
case *ahptypes.PromptCustomization:
|
|
333
|
+
return v.Id, true
|
|
334
|
+
case *ahptypes.RuleCustomization:
|
|
335
|
+
return v.Id, true
|
|
336
|
+
case *ahptypes.HookCustomization:
|
|
337
|
+
return v.Id, true
|
|
338
|
+
case *ahptypes.McpServerCustomization:
|
|
339
|
+
return v.Id, true
|
|
340
|
+
}
|
|
341
|
+
return "", false
|
|
342
|
+
}
|
|
343
|
+
|
|
344
|
+
func containerChildren(c *ahptypes.Customization) *[]ahptypes.ChildCustomization {
|
|
345
|
+
switch v := c.Value.(type) {
|
|
346
|
+
case *ahptypes.PluginCustomization:
|
|
347
|
+
return &v.Children
|
|
348
|
+
case *ahptypes.DirectoryCustomization:
|
|
349
|
+
return &v.Children
|
|
350
|
+
}
|
|
351
|
+
return nil
|
|
352
|
+
}
|
|
353
|
+
|
|
354
|
+
func effectiveEnablement(enablement []ahptypes.CustomizationEnablement) bool {
|
|
355
|
+
if len(enablement) == 0 {
|
|
356
|
+
return true
|
|
357
|
+
}
|
|
358
|
+
switch decision := enablement[0].Value.(type) {
|
|
359
|
+
case *ahptypes.CustomizationEnablementGlobal:
|
|
360
|
+
return decision.Enabled
|
|
361
|
+
case *ahptypes.CustomizationEnablementWorkspace:
|
|
362
|
+
return decision.Enabled
|
|
363
|
+
case *ahptypes.CustomizationEnablementSession:
|
|
364
|
+
return decision.Enabled
|
|
365
|
+
default:
|
|
366
|
+
return true
|
|
367
|
+
}
|
|
368
|
+
}
|
|
369
|
+
|
|
370
|
+
func applyContainerEnablement(c *ahptypes.Customization, enablement []ahptypes.CustomizationEnablement) {
|
|
371
|
+
enabled := effectiveEnablement(enablement)
|
|
372
|
+
provenance := append([]ahptypes.CustomizationEnablement(nil), enablement...)
|
|
373
|
+
switch v := c.Value.(type) {
|
|
374
|
+
case *ahptypes.PluginCustomization:
|
|
375
|
+
v.Enablement = provenance
|
|
376
|
+
case *ahptypes.DirectoryCustomization:
|
|
377
|
+
v.Enabled = enabled
|
|
378
|
+
case *ahptypes.McpServerCustomization:
|
|
379
|
+
v.Enablement = provenance
|
|
380
|
+
}
|
|
381
|
+
}
|
|
382
|
+
|
|
383
|
+
func applyChildEnablement(c *ahptypes.ChildCustomization, enablement []ahptypes.CustomizationEnablement) {
|
|
384
|
+
enabled := effectiveEnablement(enablement)
|
|
385
|
+
provenance := append([]ahptypes.CustomizationEnablement(nil), enablement...)
|
|
386
|
+
switch v := c.Value.(type) {
|
|
387
|
+
case *ahptypes.AgentCustomization:
|
|
388
|
+
v.Enabled = &enabled
|
|
389
|
+
case *ahptypes.SkillCustomization:
|
|
390
|
+
v.Enabled = &enabled
|
|
391
|
+
case *ahptypes.PromptCustomization:
|
|
392
|
+
v.Enabled = &enabled
|
|
393
|
+
case *ahptypes.RuleCustomization:
|
|
394
|
+
v.Enabled = &enabled
|
|
395
|
+
case *ahptypes.HookCustomization:
|
|
396
|
+
v.Enabled = &enabled
|
|
397
|
+
case *ahptypes.McpServerCustomization:
|
|
398
|
+
v.Enablement = provenance
|
|
399
|
+
}
|
|
400
|
+
}
|
|
401
|
+
|
|
402
|
+
func applyToggle(list []ahptypes.Customization, id string, enablement []ahptypes.CustomizationEnablement) bool {
|
|
403
|
+
for i := range list {
|
|
404
|
+
got, ok := customizationID(list[i])
|
|
405
|
+
if ok && got == id {
|
|
406
|
+
applyContainerEnablement(&list[i], enablement)
|
|
407
|
+
return true
|
|
408
|
+
}
|
|
409
|
+
}
|
|
410
|
+
for i := range list {
|
|
411
|
+
children := containerChildren(&list[i])
|
|
412
|
+
if children == nil {
|
|
413
|
+
continue
|
|
414
|
+
}
|
|
415
|
+
for j := range *children {
|
|
416
|
+
got, ok := childCustomizationID((*children)[j])
|
|
417
|
+
if ok && got == id {
|
|
418
|
+
applyChildEnablement(&(*children)[j], enablement)
|
|
419
|
+
return true
|
|
420
|
+
}
|
|
421
|
+
}
|
|
422
|
+
}
|
|
423
|
+
return false
|
|
424
|
+
}
|
|
425
|
+
|
|
426
|
+
// ─── Active-turn mutation helpers ──────────────────────────────────────
|
|
427
|
+
|
|
428
|
+
func updateToolCall(state *ahptypes.ChatState, turnID, targetToolCallID string, updater func(ahptypes.ToolCallState) ahptypes.ToolCallState) ReduceOutcome {
|
|
429
|
+
if state.ActiveTurn == nil || state.ActiveTurn.Id != turnID {
|
|
430
|
+
return ReduceOutcomeNoOp
|
|
431
|
+
}
|
|
432
|
+
for i := range state.ActiveTurn.ResponseParts {
|
|
433
|
+
tc, ok := state.ActiveTurn.ResponseParts[i].Value.(*ahptypes.ToolCallResponsePart)
|
|
434
|
+
if !ok {
|
|
435
|
+
continue
|
|
436
|
+
}
|
|
437
|
+
if toolCallID(tc.ToolCall) == targetToolCallID {
|
|
438
|
+
tc.ToolCall = updater(tc.ToolCall)
|
|
439
|
+
return ReduceOutcomeApplied
|
|
440
|
+
}
|
|
441
|
+
}
|
|
442
|
+
return ReduceOutcomeNoOp
|
|
443
|
+
}
|
|
444
|
+
|
|
445
|
+
func updateResponsePart(state *ahptypes.ChatState, turnID, partID string, updater func(*ahptypes.ResponsePart)) ReduceOutcome {
|
|
446
|
+
if state.ActiveTurn == nil || state.ActiveTurn.Id != turnID {
|
|
447
|
+
return ReduceOutcomeNoOp
|
|
448
|
+
}
|
|
449
|
+
|
|
450
|
+
for i := range state.ActiveTurn.ResponseParts {
|
|
451
|
+
part := &state.ActiveTurn.ResponseParts[i]
|
|
452
|
+
var id string
|
|
453
|
+
switch v := part.Value.(type) {
|
|
454
|
+
case *ahptypes.ToolCallResponsePart:
|
|
455
|
+
id = toolCallID(v.ToolCall)
|
|
456
|
+
case *ahptypes.MarkdownResponsePart:
|
|
457
|
+
id = v.Id
|
|
458
|
+
case *ahptypes.ReasoningResponsePart:
|
|
459
|
+
id = v.Id
|
|
460
|
+
}
|
|
461
|
+
if id != "" && id == partID {
|
|
462
|
+
updater(part)
|
|
463
|
+
return ReduceOutcomeApplied
|
|
464
|
+
}
|
|
465
|
+
}
|
|
466
|
+
return ReduceOutcomeNoOp
|
|
467
|
+
}
|
|
468
|
+
|
|
469
|
+
// ─── Root Reducer ──────────────────────────────────────────────────────
|
|
470
|
+
|
|
471
|
+
// ApplyActionToRoot applies action to the [ahptypes.RootState] in
|
|
472
|
+
// place. Returns [ReduceOutcomeOutOfScope] for actions that target a
|
|
473
|
+
// different state tree.
|
|
474
|
+
func ApplyActionToRoot(state *ahptypes.RootState, action ahptypes.StateAction) ReduceOutcome {
|
|
475
|
+
switch a := action.Value.(type) {
|
|
476
|
+
case *ahptypes.RootAgentsChangedAction:
|
|
477
|
+
state.Agents = append([]ahptypes.AgentInfo(nil), a.Agents...)
|
|
478
|
+
return ReduceOutcomeApplied
|
|
479
|
+
case *ahptypes.RootActiveSessionsChangedAction:
|
|
480
|
+
v := a.ActiveSessions
|
|
481
|
+
state.ActiveSessions = &v
|
|
482
|
+
return ReduceOutcomeApplied
|
|
483
|
+
case *ahptypes.RootTerminalsChangedAction:
|
|
484
|
+
state.Terminals = append([]ahptypes.TerminalInfo(nil), a.Terminals...)
|
|
485
|
+
return ReduceOutcomeApplied
|
|
486
|
+
case *ahptypes.RootConfigChangedAction:
|
|
487
|
+
if state.Config == nil {
|
|
488
|
+
return ReduceOutcomeNoOp
|
|
489
|
+
}
|
|
490
|
+
replace := a.Replace != nil && *a.Replace
|
|
491
|
+
if replace {
|
|
492
|
+
state.Config.Values = make(map[string]json.RawMessage, len(a.Config))
|
|
493
|
+
} else if state.Config.Values == nil {
|
|
494
|
+
state.Config.Values = make(map[string]json.RawMessage, len(a.Config))
|
|
495
|
+
}
|
|
496
|
+
for k, v := range a.Config {
|
|
497
|
+
state.Config.Values[k] = v
|
|
498
|
+
}
|
|
499
|
+
return ReduceOutcomeApplied
|
|
500
|
+
}
|
|
501
|
+
return ReduceOutcomeOutOfScope
|
|
502
|
+
}
|
|
503
|
+
|
|
504
|
+
// ─── Chat Reducer ──────────────────────────────────────────────────────
|
|
505
|
+
|
|
506
|
+
// ApplyActionToChat applies action to the [ahptypes.ChatState] in
|
|
507
|
+
// place. Returns [ReduceOutcomeOutOfScope] for actions that target a
|
|
508
|
+
// different state tree.
|
|
509
|
+
func ApplyActionToChat(state *ahptypes.ChatState, action ahptypes.StateAction) ReduceOutcome {
|
|
510
|
+
switch a := action.Value.(type) {
|
|
511
|
+
case *ahptypes.ChatTurnStartedAction:
|
|
512
|
+
return applyTurnStarted(state, a)
|
|
513
|
+
case *ahptypes.ChatDeltaAction:
|
|
514
|
+
return updateResponsePart(state, a.TurnId, a.PartId, func(p *ahptypes.ResponsePart) {
|
|
515
|
+
if m, ok := p.Value.(*ahptypes.MarkdownResponsePart); ok {
|
|
516
|
+
m.Content += a.Content
|
|
517
|
+
}
|
|
518
|
+
})
|
|
519
|
+
case *ahptypes.ChatResponsePartAction:
|
|
520
|
+
if state.ActiveTurn == nil || state.ActiveTurn.Id != a.TurnId {
|
|
521
|
+
return ReduceOutcomeNoOp
|
|
522
|
+
}
|
|
523
|
+
if _, ok := a.Part.Value.(*ahptypes.ErrorResponsePart); ok {
|
|
524
|
+
return ReduceOutcomeNoOp
|
|
525
|
+
}
|
|
526
|
+
state.ActiveTurn.ResponseParts = append(state.ActiveTurn.ResponseParts, a.Part)
|
|
527
|
+
return ReduceOutcomeApplied
|
|
528
|
+
case *ahptypes.ChatTurnCompleteAction:
|
|
529
|
+
return endTurn(state, a.TurnId, a.Duration, ahptypes.TurnStateComplete, nil, nil)
|
|
530
|
+
case *ahptypes.ChatTurnCancelledAction:
|
|
531
|
+
return endTurn(state, a.TurnId, a.Duration, ahptypes.TurnStateCancelled, nil, nil)
|
|
532
|
+
case *ahptypes.ChatErrorAction:
|
|
533
|
+
errStatus := ahptypes.SessionStatusError
|
|
534
|
+
return endTurn(state, a.TurnId, a.Duration, ahptypes.TurnStateError, &errStatus, &a.Part)
|
|
535
|
+
case *ahptypes.ChatTurnResumeAction:
|
|
536
|
+
if state.ActiveTurn != nil || len(state.Turns) == 0 {
|
|
537
|
+
return ReduceOutcomeNoOp
|
|
538
|
+
}
|
|
539
|
+
turnIndex := len(state.Turns) - 1
|
|
540
|
+
turn := state.Turns[turnIndex]
|
|
541
|
+
if turn.Id != a.TurnId || turn.State != ahptypes.TurnStateError || !hasResumableError(&turn) {
|
|
542
|
+
return ReduceOutcomeNoOp
|
|
543
|
+
}
|
|
544
|
+
|
|
545
|
+
startedAt := state.ModifiedAt
|
|
546
|
+
if turn.StartedAt != nil {
|
|
547
|
+
startedAt = *turn.StartedAt
|
|
548
|
+
}
|
|
549
|
+
state.Turns = state.Turns[:turnIndex]
|
|
550
|
+
state.ActiveTurn = &ahptypes.ActiveTurn{
|
|
551
|
+
Id: turn.Id,
|
|
552
|
+
StartedAt: startedAt,
|
|
553
|
+
Message: turn.Message,
|
|
554
|
+
ResponseParts: turn.ResponseParts,
|
|
555
|
+
Usage: turn.Usage,
|
|
556
|
+
}
|
|
557
|
+
state.Status = withStatusFlag(summaryStatus(state, nil), ahptypes.SessionStatusIsRead, false)
|
|
558
|
+
return ReduceOutcomeApplied
|
|
559
|
+
case *ahptypes.ChatActivityChangedAction:
|
|
560
|
+
state.Activity = a.Activity
|
|
561
|
+
return ReduceOutcomeApplied
|
|
562
|
+
case *ahptypes.ChatChangesetsChangedAction:
|
|
563
|
+
if a.Changesets == nil {
|
|
564
|
+
state.Changesets = nil
|
|
565
|
+
} else {
|
|
566
|
+
state.Changesets = append([]ahptypes.Changeset(nil), a.Changesets...)
|
|
567
|
+
}
|
|
568
|
+
return ReduceOutcomeApplied
|
|
569
|
+
case *ahptypes.ChatWorkingDirectorySetAction:
|
|
570
|
+
for _, d := range state.WorkingDirectories {
|
|
571
|
+
if d == a.Directory {
|
|
572
|
+
return ReduceOutcomeNoOp
|
|
573
|
+
}
|
|
574
|
+
}
|
|
575
|
+
state.WorkingDirectories = append(state.WorkingDirectories, a.Directory)
|
|
576
|
+
return ReduceOutcomeApplied
|
|
577
|
+
case *ahptypes.ChatWorkingDirectoryRemovedAction:
|
|
578
|
+
for i := range state.WorkingDirectories {
|
|
579
|
+
if state.WorkingDirectories[i] == a.Directory {
|
|
580
|
+
state.WorkingDirectories = append(state.WorkingDirectories[:i], state.WorkingDirectories[i+1:]...)
|
|
581
|
+
return ReduceOutcomeApplied
|
|
582
|
+
}
|
|
583
|
+
}
|
|
584
|
+
return ReduceOutcomeNoOp
|
|
585
|
+
case *ahptypes.ChatToolCallStartAction:
|
|
586
|
+
if state.ActiveTurn == nil || state.ActiveTurn.Id != a.TurnId {
|
|
587
|
+
return ReduceOutcomeNoOp
|
|
588
|
+
}
|
|
589
|
+
state.ActiveTurn.ResponseParts = append(state.ActiveTurn.ResponseParts, ahptypes.ResponsePart{Value: &ahptypes.ToolCallResponsePart{
|
|
590
|
+
Kind: ahptypes.ResponsePartKindToolCall,
|
|
591
|
+
ToolCall: ahptypes.ToolCallState{Value: &ahptypes.ToolCallStreamingState{
|
|
592
|
+
Status: ahptypes.ToolCallStatusStreaming,
|
|
593
|
+
ToolCallId: a.ToolCallId,
|
|
594
|
+
ToolName: a.ToolName,
|
|
595
|
+
DisplayName: a.DisplayName,
|
|
596
|
+
Intention: a.Intention,
|
|
597
|
+
Contributor: a.Contributor,
|
|
598
|
+
Meta: a.Meta,
|
|
599
|
+
}},
|
|
600
|
+
}})
|
|
601
|
+
return ReduceOutcomeApplied
|
|
602
|
+
case *ahptypes.ChatToolCallDeltaAction:
|
|
603
|
+
return applyToolCallDelta(state, a)
|
|
604
|
+
case *ahptypes.ChatToolCallReadyAction:
|
|
605
|
+
res := applyToolCallReady(state, a)
|
|
606
|
+
if res == ReduceOutcomeApplied {
|
|
607
|
+
refreshSummaryStatus(state)
|
|
608
|
+
}
|
|
609
|
+
return res
|
|
610
|
+
case *ahptypes.ChatToolCallConfirmedAction:
|
|
611
|
+
res := applyToolCallConfirmed(state, a)
|
|
612
|
+
if res == ReduceOutcomeApplied {
|
|
613
|
+
refreshSummaryStatus(state)
|
|
614
|
+
}
|
|
615
|
+
return res
|
|
616
|
+
case *ahptypes.ChatToolCallCompleteAction:
|
|
617
|
+
res := applyToolCallComplete(state, a)
|
|
618
|
+
if res == ReduceOutcomeApplied {
|
|
619
|
+
refreshSummaryStatus(state)
|
|
620
|
+
}
|
|
621
|
+
return res
|
|
622
|
+
case *ahptypes.ChatToolCallResultConfirmedAction:
|
|
623
|
+
res := applyToolCallResultConfirmed(state, a)
|
|
624
|
+
if res == ReduceOutcomeApplied {
|
|
625
|
+
refreshSummaryStatus(state)
|
|
626
|
+
}
|
|
627
|
+
return res
|
|
628
|
+
case *ahptypes.ChatToolCallContentChangedAction:
|
|
629
|
+
return updateToolCall(state, a.TurnId, a.ToolCallId, func(tc ahptypes.ToolCallState) ahptypes.ToolCallState {
|
|
630
|
+
if r, ok := tc.Value.(*ahptypes.ToolCallRunningState); ok {
|
|
631
|
+
if a.Meta != nil {
|
|
632
|
+
r.Meta = a.Meta
|
|
633
|
+
}
|
|
634
|
+
r.Content = append([]ahptypes.ToolResultContent(nil), a.Content...)
|
|
635
|
+
}
|
|
636
|
+
return tc
|
|
637
|
+
})
|
|
638
|
+
case *ahptypes.ChatToolCallAuthRequiredAction:
|
|
639
|
+
res := applyToolCallAuthRequired(state, a)
|
|
640
|
+
if res == ReduceOutcomeApplied {
|
|
641
|
+
refreshSummaryStatus(state)
|
|
642
|
+
}
|
|
643
|
+
return res
|
|
644
|
+
case *ahptypes.ChatToolCallAuthResolvedAction:
|
|
645
|
+
res := applyToolCallAuthResolved(state, a)
|
|
646
|
+
if res == ReduceOutcomeApplied {
|
|
647
|
+
refreshSummaryStatus(state)
|
|
648
|
+
}
|
|
649
|
+
return res
|
|
650
|
+
case *ahptypes.ChatUsageAction:
|
|
651
|
+
if state.ActiveTurn == nil || state.ActiveTurn.Id != a.TurnId {
|
|
652
|
+
return ReduceOutcomeNoOp
|
|
653
|
+
}
|
|
654
|
+
usage := a.Usage
|
|
655
|
+
state.ActiveTurn.Usage = &usage
|
|
656
|
+
return ReduceOutcomeApplied
|
|
657
|
+
case *ahptypes.ChatReasoningAction:
|
|
658
|
+
return updateResponsePart(state, a.TurnId, a.PartId, func(p *ahptypes.ResponsePart) {
|
|
659
|
+
if r, ok := p.Value.(*ahptypes.ReasoningResponsePart); ok {
|
|
660
|
+
r.Content += a.Content
|
|
661
|
+
}
|
|
662
|
+
})
|
|
663
|
+
case *ahptypes.ChatTruncatedAction:
|
|
664
|
+
return applyTruncated(state, a.TurnId)
|
|
665
|
+
case *ahptypes.ChatTurnsLoadedAction:
|
|
666
|
+
existingIds := make(map[string]struct{}, len(state.Turns))
|
|
667
|
+
for _, turn := range state.Turns {
|
|
668
|
+
existingIds[turn.Id] = struct{}{}
|
|
669
|
+
}
|
|
670
|
+
olderTurns := make([]ahptypes.Turn, 0, len(a.Turns)+len(state.Turns))
|
|
671
|
+
for _, turn := range a.Turns {
|
|
672
|
+
if _, ok := existingIds[turn.Id]; !ok {
|
|
673
|
+
olderTurns = append(olderTurns, turn)
|
|
674
|
+
}
|
|
675
|
+
}
|
|
676
|
+
state.Turns = append(olderTurns, state.Turns...)
|
|
677
|
+
state.TurnsNextCursor = a.TurnsNextCursor
|
|
678
|
+
return ReduceOutcomeApplied
|
|
679
|
+
case *ahptypes.ChatInputRequestedAction:
|
|
680
|
+
return upsertInputRequest(state, a.Request)
|
|
681
|
+
case *ahptypes.ChatInputAnswerChangedAction:
|
|
682
|
+
return applyInputAnswerChanged(state, a)
|
|
683
|
+
case *ahptypes.ChatInputCompletedAction:
|
|
684
|
+
if state.ActiveTurn == nil {
|
|
685
|
+
return ReduceOutcomeNoOp
|
|
686
|
+
}
|
|
687
|
+
for i := range state.ActiveTurn.ResponseParts {
|
|
688
|
+
input, ok := state.ActiveTurn.ResponseParts[i].Value.(*ahptypes.InputRequestResponsePart)
|
|
689
|
+
if !ok || input.Response != nil || input.Request.Id != a.RequestId {
|
|
690
|
+
continue
|
|
691
|
+
}
|
|
692
|
+
finalAnswers := map[string]ahptypes.ChatInputAnswer{}
|
|
693
|
+
for k, v := range input.Request.Answers {
|
|
694
|
+
finalAnswers[k] = v
|
|
695
|
+
}
|
|
696
|
+
for k, v := range a.Answers {
|
|
697
|
+
finalAnswers[k] = v
|
|
698
|
+
}
|
|
699
|
+
request := input.Request
|
|
700
|
+
if len(finalAnswers) == 0 {
|
|
701
|
+
request.Answers = nil
|
|
702
|
+
} else {
|
|
703
|
+
request.Answers = finalAnswers
|
|
704
|
+
}
|
|
705
|
+
response := a.Response
|
|
706
|
+
state.ActiveTurn.ResponseParts[i].Value = &ahptypes.InputRequestResponsePart{
|
|
707
|
+
Kind: ahptypes.ResponsePartKindInputRequest,
|
|
708
|
+
Request: request,
|
|
709
|
+
Response: &response,
|
|
710
|
+
}
|
|
711
|
+
refreshSummaryStatus(state)
|
|
712
|
+
return ReduceOutcomeApplied
|
|
713
|
+
}
|
|
714
|
+
return ReduceOutcomeNoOp
|
|
715
|
+
case *ahptypes.ChatPendingMessageSetAction:
|
|
716
|
+
entry := ahptypes.PendingMessage{Id: a.Id, Message: a.Message}
|
|
717
|
+
switch a.Kind {
|
|
718
|
+
case ahptypes.PendingMessageKindSteering:
|
|
719
|
+
state.SteeringMessage = &entry
|
|
720
|
+
case ahptypes.PendingMessageKindQueued:
|
|
721
|
+
list := state.QueuedMessages
|
|
722
|
+
idx := -1
|
|
723
|
+
for i := range list {
|
|
724
|
+
if list[i].Id == entry.Id {
|
|
725
|
+
idx = i
|
|
726
|
+
break
|
|
727
|
+
}
|
|
728
|
+
}
|
|
729
|
+
if idx >= 0 {
|
|
730
|
+
list[idx] = entry
|
|
731
|
+
} else {
|
|
732
|
+
list = append(list, entry)
|
|
733
|
+
}
|
|
734
|
+
state.QueuedMessages = list
|
|
735
|
+
}
|
|
736
|
+
return ReduceOutcomeApplied
|
|
737
|
+
case *ahptypes.ChatPendingMessageRemovedAction:
|
|
738
|
+
switch a.Kind {
|
|
739
|
+
case ahptypes.PendingMessageKindSteering:
|
|
740
|
+
if state.SteeringMessage != nil && state.SteeringMessage.Id == a.Id {
|
|
741
|
+
state.SteeringMessage = nil
|
|
742
|
+
return ReduceOutcomeApplied
|
|
743
|
+
}
|
|
744
|
+
return ReduceOutcomeNoOp
|
|
745
|
+
case ahptypes.PendingMessageKindQueued:
|
|
746
|
+
list := state.QueuedMessages
|
|
747
|
+
if list == nil {
|
|
748
|
+
return ReduceOutcomeNoOp
|
|
749
|
+
}
|
|
750
|
+
next := list[:0]
|
|
751
|
+
removed := false
|
|
752
|
+
for _, m := range list {
|
|
753
|
+
if m.Id == a.Id {
|
|
754
|
+
removed = true
|
|
755
|
+
continue
|
|
756
|
+
}
|
|
757
|
+
next = append(next, m)
|
|
758
|
+
}
|
|
759
|
+
if !removed {
|
|
760
|
+
return ReduceOutcomeNoOp
|
|
761
|
+
}
|
|
762
|
+
if len(next) == 0 {
|
|
763
|
+
state.QueuedMessages = nil
|
|
764
|
+
} else {
|
|
765
|
+
state.QueuedMessages = next
|
|
766
|
+
}
|
|
767
|
+
return ReduceOutcomeApplied
|
|
768
|
+
}
|
|
769
|
+
return ReduceOutcomeNoOp
|
|
770
|
+
case *ahptypes.ChatQueuedMessagesReorderedAction:
|
|
771
|
+
if state.QueuedMessages == nil {
|
|
772
|
+
return ReduceOutcomeNoOp
|
|
773
|
+
}
|
|
774
|
+
byID := make(map[string]ahptypes.PendingMessage, len(state.QueuedMessages))
|
|
775
|
+
for _, m := range state.QueuedMessages {
|
|
776
|
+
byID[m.Id] = m
|
|
777
|
+
}
|
|
778
|
+
reordered := make([]ahptypes.PendingMessage, 0, len(byID))
|
|
779
|
+
seen := make(map[string]struct{}, len(byID))
|
|
780
|
+
for _, id := range a.Order {
|
|
781
|
+
if msg, ok := byID[id]; ok {
|
|
782
|
+
if _, dup := seen[id]; !dup {
|
|
783
|
+
seen[id] = struct{}{}
|
|
784
|
+
reordered = append(reordered, msg)
|
|
785
|
+
}
|
|
786
|
+
}
|
|
787
|
+
}
|
|
788
|
+
// Append messages absent from `order`, preserving their original
|
|
789
|
+
// relative order in state.QueuedMessages (mirrors the canonical
|
|
790
|
+
// TypeScript reducer in types/channels-session/reducer.ts).
|
|
791
|
+
for _, m := range state.QueuedMessages {
|
|
792
|
+
if _, in := seen[m.Id]; !in {
|
|
793
|
+
reordered = append(reordered, m)
|
|
794
|
+
}
|
|
795
|
+
}
|
|
796
|
+
state.QueuedMessages = reordered
|
|
797
|
+
return ReduceOutcomeApplied
|
|
798
|
+
case *ahptypes.ChatDraftChangedAction:
|
|
799
|
+
state.Draft = a.Draft
|
|
800
|
+
return ReduceOutcomeApplied
|
|
801
|
+
case *ahptypes.ChatIsArchivedChangedAction:
|
|
802
|
+
state.Status = withStatusFlag(state.Status, ahptypes.SessionStatusIsArchived, a.IsArchived)
|
|
803
|
+
return ReduceOutcomeApplied
|
|
804
|
+
}
|
|
805
|
+
return ReduceOutcomeOutOfScope
|
|
806
|
+
}
|
|
807
|
+
|
|
808
|
+
func mergeChatSummaryPartial(summary *ahptypes.ChatSummary, changes ahptypes.PartialChatSummary) {
|
|
809
|
+
if changes.Title != nil {
|
|
810
|
+
summary.Title = *changes.Title
|
|
811
|
+
}
|
|
812
|
+
if changes.Status != nil {
|
|
813
|
+
summary.Status = *changes.Status
|
|
814
|
+
}
|
|
815
|
+
if changes.Activity != nil {
|
|
816
|
+
summary.Activity = changes.Activity
|
|
817
|
+
}
|
|
818
|
+
if changes.ModifiedAt != nil {
|
|
819
|
+
summary.ModifiedAt = *changes.ModifiedAt
|
|
820
|
+
}
|
|
821
|
+
if changes.Origin != nil {
|
|
822
|
+
summary.Origin = changes.Origin
|
|
823
|
+
}
|
|
824
|
+
if changes.WorkingDirectories != nil {
|
|
825
|
+
summary.WorkingDirectories = changes.WorkingDirectories
|
|
826
|
+
}
|
|
827
|
+
}
|
|
828
|
+
|
|
829
|
+
// ─── Session Reducer ───────────────────────────────────────────────────
|
|
830
|
+
|
|
831
|
+
// ApplyActionToSession applies action to the [ahptypes.SessionState]
|
|
832
|
+
// in place. Returns [ReduceOutcomeOutOfScope] for actions that target
|
|
833
|
+
// a different state tree.
|
|
834
|
+
func ApplyActionToSession(state *ahptypes.SessionState, action ahptypes.StateAction) ReduceOutcome {
|
|
835
|
+
switch a := action.Value.(type) {
|
|
836
|
+
case *ahptypes.SessionReadyAction:
|
|
837
|
+
state.Lifecycle = ahptypes.SessionLifecycleReady
|
|
838
|
+
return ReduceOutcomeApplied
|
|
839
|
+
case *ahptypes.SessionCreationFailedAction:
|
|
840
|
+
state.Lifecycle = ahptypes.SessionLifecycleFailed
|
|
841
|
+
errCopy := a.Error
|
|
842
|
+
state.CreationError = &errCopy
|
|
843
|
+
return ReduceOutcomeApplied
|
|
844
|
+
case *ahptypes.SessionChatAddedAction:
|
|
845
|
+
for i := range state.Chats {
|
|
846
|
+
if state.Chats[i].Resource == a.Summary.Resource {
|
|
847
|
+
state.Chats[i] = a.Summary
|
|
848
|
+
return ReduceOutcomeApplied
|
|
849
|
+
}
|
|
850
|
+
}
|
|
851
|
+
state.Chats = append(state.Chats, a.Summary)
|
|
852
|
+
return ReduceOutcomeApplied
|
|
853
|
+
case *ahptypes.SessionChatRemovedAction:
|
|
854
|
+
for i := range state.Chats {
|
|
855
|
+
if state.Chats[i].Resource == a.Chat {
|
|
856
|
+
state.Chats = append(state.Chats[:i], state.Chats[i+1:]...)
|
|
857
|
+
if state.DefaultChat != nil && *state.DefaultChat == a.Chat {
|
|
858
|
+
state.DefaultChat = nil
|
|
859
|
+
}
|
|
860
|
+
return ReduceOutcomeApplied
|
|
861
|
+
}
|
|
862
|
+
}
|
|
863
|
+
return ReduceOutcomeNoOp
|
|
864
|
+
case *ahptypes.SessionChatUpdatedAction:
|
|
865
|
+
for i := range state.Chats {
|
|
866
|
+
if state.Chats[i].Resource == a.Chat {
|
|
867
|
+
mergeChatSummaryPartial(&state.Chats[i], a.Changes)
|
|
868
|
+
return ReduceOutcomeApplied
|
|
869
|
+
}
|
|
870
|
+
}
|
|
871
|
+
return ReduceOutcomeNoOp
|
|
872
|
+
case *ahptypes.SessionDefaultChatChangedAction:
|
|
873
|
+
state.DefaultChat = a.DefaultChat
|
|
874
|
+
return ReduceOutcomeApplied
|
|
875
|
+
case *ahptypes.SessionTitleChangedAction:
|
|
876
|
+
state.Title = a.Title
|
|
877
|
+
return ReduceOutcomeApplied
|
|
878
|
+
case *ahptypes.SessionIsReadChangedAction:
|
|
879
|
+
state.Status = withStatusFlag(state.Status, ahptypes.SessionStatusIsRead, a.IsRead)
|
|
880
|
+
return ReduceOutcomeApplied
|
|
881
|
+
case *ahptypes.SessionIsArchivedChangedAction:
|
|
882
|
+
state.Status = withStatusFlag(state.Status, ahptypes.SessionStatusIsArchived, a.IsArchived)
|
|
883
|
+
return ReduceOutcomeApplied
|
|
884
|
+
case *ahptypes.SessionActivityChangedAction:
|
|
885
|
+
state.Activity = a.Activity
|
|
886
|
+
return ReduceOutcomeApplied
|
|
887
|
+
case *ahptypes.SessionChangesetsChangedAction:
|
|
888
|
+
if a.Changesets == nil {
|
|
889
|
+
state.Changesets = nil
|
|
890
|
+
} else {
|
|
891
|
+
state.Changesets = append([]ahptypes.Changeset(nil), a.Changesets...)
|
|
892
|
+
}
|
|
893
|
+
return ReduceOutcomeApplied
|
|
894
|
+
case *ahptypes.SessionConfigChangedAction:
|
|
895
|
+
if state.Config == nil {
|
|
896
|
+
return ReduceOutcomeNoOp
|
|
897
|
+
}
|
|
898
|
+
replace := a.Replace != nil && *a.Replace
|
|
899
|
+
if replace {
|
|
900
|
+
state.Config.Values = make(map[string]json.RawMessage, len(a.Config))
|
|
901
|
+
} else if state.Config.Values == nil {
|
|
902
|
+
state.Config.Values = make(map[string]json.RawMessage, len(a.Config))
|
|
903
|
+
}
|
|
904
|
+
for k, v := range a.Config {
|
|
905
|
+
state.Config.Values[k] = v
|
|
906
|
+
}
|
|
907
|
+
return ReduceOutcomeApplied
|
|
908
|
+
case *ahptypes.SessionMetaChangedAction:
|
|
909
|
+
state.Meta = a.Meta
|
|
910
|
+
return ReduceOutcomeApplied
|
|
911
|
+
case *ahptypes.SessionServerToolsChangedAction:
|
|
912
|
+
state.ServerTools = append([]ahptypes.ToolDefinition(nil), a.Tools...)
|
|
913
|
+
return ReduceOutcomeApplied
|
|
914
|
+
case *ahptypes.SessionActiveClientSetAction:
|
|
915
|
+
for i := range state.ActiveClients {
|
|
916
|
+
if state.ActiveClients[i].ClientId == a.ActiveClient.ClientId {
|
|
917
|
+
state.ActiveClients[i] = a.ActiveClient
|
|
918
|
+
return ReduceOutcomeApplied
|
|
919
|
+
}
|
|
920
|
+
}
|
|
921
|
+
state.ActiveClients = append(state.ActiveClients, a.ActiveClient)
|
|
922
|
+
return ReduceOutcomeApplied
|
|
923
|
+
case *ahptypes.SessionActiveClientRemovedAction:
|
|
924
|
+
for i := range state.ActiveClients {
|
|
925
|
+
if state.ActiveClients[i].ClientId == a.ClientId {
|
|
926
|
+
state.ActiveClients = append(state.ActiveClients[:i], state.ActiveClients[i+1:]...)
|
|
927
|
+
return ReduceOutcomeApplied
|
|
928
|
+
}
|
|
929
|
+
}
|
|
930
|
+
return ReduceOutcomeNoOp
|
|
931
|
+
case *ahptypes.SessionWorkingDirectorySetAction:
|
|
932
|
+
for _, d := range state.WorkingDirectories {
|
|
933
|
+
if d == a.Directory {
|
|
934
|
+
return ReduceOutcomeNoOp
|
|
935
|
+
}
|
|
936
|
+
}
|
|
937
|
+
state.WorkingDirectories = append(state.WorkingDirectories, a.Directory)
|
|
938
|
+
return ReduceOutcomeApplied
|
|
939
|
+
case *ahptypes.SessionWorkingDirectoryRemovedAction:
|
|
940
|
+
for i := range state.WorkingDirectories {
|
|
941
|
+
if state.WorkingDirectories[i] == a.Directory {
|
|
942
|
+
state.WorkingDirectories = append(state.WorkingDirectories[:i], state.WorkingDirectories[i+1:]...)
|
|
943
|
+
return ReduceOutcomeApplied
|
|
944
|
+
}
|
|
945
|
+
}
|
|
946
|
+
return ReduceOutcomeNoOp
|
|
947
|
+
case *ahptypes.SessionWorkingDirectoryReplacedAction:
|
|
948
|
+
idx := -1
|
|
949
|
+
for i, directory := range state.WorkingDirectories {
|
|
950
|
+
if directory == a.Directory {
|
|
951
|
+
idx = i
|
|
952
|
+
break
|
|
953
|
+
}
|
|
954
|
+
}
|
|
955
|
+
if idx < 0 {
|
|
956
|
+
return ReduceOutcomeNoOp
|
|
957
|
+
}
|
|
958
|
+
for i := 0; i < idx; i++ {
|
|
959
|
+
if state.WorkingDirectories[i] == a.Replacement {
|
|
960
|
+
state.WorkingDirectories = append(state.WorkingDirectories[:idx], state.WorkingDirectories[idx+1:]...)
|
|
961
|
+
return ReduceOutcomeApplied
|
|
962
|
+
}
|
|
963
|
+
}
|
|
964
|
+
updated := make([]ahptypes.URI, 0, len(state.WorkingDirectories))
|
|
965
|
+
for i, directory := range state.WorkingDirectories {
|
|
966
|
+
if i == idx {
|
|
967
|
+
updated = append(updated, a.Replacement)
|
|
968
|
+
} else if directory != a.Replacement {
|
|
969
|
+
updated = append(updated, directory)
|
|
970
|
+
}
|
|
971
|
+
}
|
|
972
|
+
state.WorkingDirectories = updated
|
|
973
|
+
return ReduceOutcomeApplied
|
|
974
|
+
case *ahptypes.SessionInputNeededSetAction:
|
|
975
|
+
id, ok := sessionInputRequestID(a.Request)
|
|
976
|
+
if !ok {
|
|
977
|
+
return ReduceOutcomeNoOp
|
|
978
|
+
}
|
|
979
|
+
for i := range state.InputNeeded {
|
|
980
|
+
if got, ok := sessionInputRequestID(state.InputNeeded[i]); ok && got == id {
|
|
981
|
+
state.InputNeeded[i] = a.Request
|
|
982
|
+
state.Status = withInputNeededStatus(state.Status, state.InputNeeded)
|
|
983
|
+
return ReduceOutcomeApplied
|
|
984
|
+
}
|
|
985
|
+
}
|
|
986
|
+
state.InputNeeded = append(state.InputNeeded, a.Request)
|
|
987
|
+
state.Status = withInputNeededStatus(state.Status, state.InputNeeded)
|
|
988
|
+
return ReduceOutcomeApplied
|
|
989
|
+
case *ahptypes.SessionInputNeededRemovedAction:
|
|
990
|
+
for i := range state.InputNeeded {
|
|
991
|
+
if got, ok := sessionInputRequestID(state.InputNeeded[i]); ok && got == a.Id {
|
|
992
|
+
state.InputNeeded = append(state.InputNeeded[:i], state.InputNeeded[i+1:]...)
|
|
993
|
+
if len(state.InputNeeded) == 0 {
|
|
994
|
+
state.InputNeeded = nil
|
|
995
|
+
}
|
|
996
|
+
state.Status = withInputNeededStatus(state.Status, state.InputNeeded)
|
|
997
|
+
return ReduceOutcomeApplied
|
|
998
|
+
}
|
|
999
|
+
}
|
|
1000
|
+
return ReduceOutcomeNoOp
|
|
1001
|
+
case *ahptypes.SessionCustomizationsChangedAction:
|
|
1002
|
+
state.Customizations = append([]ahptypes.Customization(nil), a.Customizations...)
|
|
1003
|
+
return ReduceOutcomeApplied
|
|
1004
|
+
case *ahptypes.SessionCustomizationToggledAction:
|
|
1005
|
+
if state.Customizations == nil {
|
|
1006
|
+
return ReduceOutcomeNoOp
|
|
1007
|
+
}
|
|
1008
|
+
if applyToggle(state.Customizations, a.Id, a.Enablement) {
|
|
1009
|
+
return ReduceOutcomeApplied
|
|
1010
|
+
}
|
|
1011
|
+
return ReduceOutcomeNoOp
|
|
1012
|
+
case *ahptypes.SessionCustomizationUpdatedAction:
|
|
1013
|
+
actionID, ok := customizationID(a.Customization)
|
|
1014
|
+
if !ok {
|
|
1015
|
+
return ReduceOutcomeNoOp
|
|
1016
|
+
}
|
|
1017
|
+
list := state.Customizations
|
|
1018
|
+
idx := -1
|
|
1019
|
+
for i := range list {
|
|
1020
|
+
if got, ok := customizationID(list[i]); ok && got == actionID {
|
|
1021
|
+
idx = i
|
|
1022
|
+
break
|
|
1023
|
+
}
|
|
1024
|
+
}
|
|
1025
|
+
if idx >= 0 {
|
|
1026
|
+
list[idx] = a.Customization
|
|
1027
|
+
} else {
|
|
1028
|
+
list = append(list, a.Customization)
|
|
1029
|
+
}
|
|
1030
|
+
state.Customizations = list
|
|
1031
|
+
return ReduceOutcomeApplied
|
|
1032
|
+
case *ahptypes.SessionCustomizationRemovedAction:
|
|
1033
|
+
if state.Customizations == nil {
|
|
1034
|
+
return ReduceOutcomeNoOp
|
|
1035
|
+
}
|
|
1036
|
+
list := state.Customizations
|
|
1037
|
+
for i := range list {
|
|
1038
|
+
if got, ok := customizationID(list[i]); ok && got == a.Id {
|
|
1039
|
+
state.Customizations = append(list[:i], list[i+1:]...)
|
|
1040
|
+
return ReduceOutcomeApplied
|
|
1041
|
+
}
|
|
1042
|
+
}
|
|
1043
|
+
for i := range list {
|
|
1044
|
+
children := containerChildren(&list[i])
|
|
1045
|
+
if children == nil {
|
|
1046
|
+
continue
|
|
1047
|
+
}
|
|
1048
|
+
for j := range *children {
|
|
1049
|
+
if got, ok := childCustomizationID((*children)[j]); ok && got == a.Id {
|
|
1050
|
+
*children = append((*children)[:j], (*children)[j+1:]...)
|
|
1051
|
+
return ReduceOutcomeApplied
|
|
1052
|
+
}
|
|
1053
|
+
}
|
|
1054
|
+
}
|
|
1055
|
+
return ReduceOutcomeNoOp
|
|
1056
|
+
case *ahptypes.SessionMcpServerStateChangedAction:
|
|
1057
|
+
return updateMcpServerCustomizationState(state, a.Id, a.State, a.Channel)
|
|
1058
|
+
case *ahptypes.SessionMcpServerStartRequestedAction:
|
|
1059
|
+
return updateMcpServerCustomizationState(state, a.Id, ahptypes.McpServerState{Value: &ahptypes.McpServerStartingState{
|
|
1060
|
+
Kind: ahptypes.McpServerStatusStarting,
|
|
1061
|
+
}}, nil)
|
|
1062
|
+
case *ahptypes.SessionMcpServerBackgroundRequestedAction:
|
|
1063
|
+
mcp := findMcpServerCustomization(state, a.Id)
|
|
1064
|
+
if mcp == nil {
|
|
1065
|
+
return ReduceOutcomeNoOp
|
|
1066
|
+
}
|
|
1067
|
+
starting, ok := mcp.State.Value.(*ahptypes.McpServerStartingState)
|
|
1068
|
+
if !ok || starting.Blocking == nil || !*starting.Blocking {
|
|
1069
|
+
return ReduceOutcomeNoOp
|
|
1070
|
+
}
|
|
1071
|
+
blocking := false
|
|
1072
|
+
next := *starting
|
|
1073
|
+
next.Blocking = &blocking
|
|
1074
|
+
mcp.State = ahptypes.McpServerState{Value: &next}
|
|
1075
|
+
return ReduceOutcomeApplied
|
|
1076
|
+
case *ahptypes.SessionMcpServerStopRequestedAction:
|
|
1077
|
+
return updateMcpServerCustomizationState(state, a.Id, ahptypes.McpServerState{Value: &ahptypes.McpServerStoppedState{
|
|
1078
|
+
Kind: ahptypes.McpServerStatusStopped,
|
|
1079
|
+
}}, nil)
|
|
1080
|
+
}
|
|
1081
|
+
return ReduceOutcomeOutOfScope
|
|
1082
|
+
}
|
|
1083
|
+
|
|
1084
|
+
func applyTurnStarted(state *ahptypes.ChatState, a *ahptypes.ChatTurnStartedAction) ReduceOutcome {
|
|
1085
|
+
state.ActiveTurn = &ahptypes.ActiveTurn{
|
|
1086
|
+
Id: a.TurnId,
|
|
1087
|
+
StartedAt: a.StartedAt,
|
|
1088
|
+
Message: a.Message,
|
|
1089
|
+
ResponseParts: []ahptypes.ResponsePart{},
|
|
1090
|
+
}
|
|
1091
|
+
state.Status = summaryStatus(state, nil)
|
|
1092
|
+
state.ModifiedAt = a.StartedAt
|
|
1093
|
+
state.Status = withStatusFlag(state.Status, ahptypes.SessionStatusIsRead, false)
|
|
1094
|
+
|
|
1095
|
+
if a.QueuedMessageId != nil {
|
|
1096
|
+
qmid := *a.QueuedMessageId
|
|
1097
|
+
if state.SteeringMessage != nil && state.SteeringMessage.Id == qmid {
|
|
1098
|
+
state.SteeringMessage = nil
|
|
1099
|
+
}
|
|
1100
|
+
if state.QueuedMessages != nil {
|
|
1101
|
+
next := state.QueuedMessages[:0]
|
|
1102
|
+
for _, m := range state.QueuedMessages {
|
|
1103
|
+
if m.Id == qmid {
|
|
1104
|
+
continue
|
|
1105
|
+
}
|
|
1106
|
+
next = append(next, m)
|
|
1107
|
+
}
|
|
1108
|
+
if len(next) == 0 {
|
|
1109
|
+
state.QueuedMessages = nil
|
|
1110
|
+
} else {
|
|
1111
|
+
state.QueuedMessages = next
|
|
1112
|
+
}
|
|
1113
|
+
}
|
|
1114
|
+
}
|
|
1115
|
+
return ReduceOutcomeApplied
|
|
1116
|
+
}
|
|
1117
|
+
|
|
1118
|
+
func applyToolCallDelta(state *ahptypes.ChatState, a *ahptypes.ChatToolCallDeltaAction) ReduceOutcome {
|
|
1119
|
+
return updateToolCall(state, a.TurnId, a.ToolCallId, func(tc ahptypes.ToolCallState) ahptypes.ToolCallState {
|
|
1120
|
+
s, ok := tc.Value.(*ahptypes.ToolCallStreamingState)
|
|
1121
|
+
if !ok {
|
|
1122
|
+
return tc
|
|
1123
|
+
}
|
|
1124
|
+
if a.Meta != nil {
|
|
1125
|
+
s.Meta = a.Meta
|
|
1126
|
+
}
|
|
1127
|
+
if a.Content != nil {
|
|
1128
|
+
partial := ""
|
|
1129
|
+
if s.PartialInput != nil {
|
|
1130
|
+
partial = *s.PartialInput
|
|
1131
|
+
}
|
|
1132
|
+
partial += *a.Content
|
|
1133
|
+
s.PartialInput = &partial
|
|
1134
|
+
}
|
|
1135
|
+
if a.InvocationMessage != nil {
|
|
1136
|
+
invocationMessage := *a.InvocationMessage
|
|
1137
|
+
s.InvocationMessage = &invocationMessage
|
|
1138
|
+
}
|
|
1139
|
+
return tc
|
|
1140
|
+
})
|
|
1141
|
+
}
|
|
1142
|
+
|
|
1143
|
+
func refineToolCallContributor(current, next *ahptypes.ToolCallContributor) *ahptypes.ToolCallContributor {
|
|
1144
|
+
if next == nil {
|
|
1145
|
+
return current
|
|
1146
|
+
}
|
|
1147
|
+
if current != nil {
|
|
1148
|
+
if currentClient, ok := current.Value.(*ahptypes.ToolCallClientContributor); ok {
|
|
1149
|
+
if nextClient, ok := next.Value.(*ahptypes.ToolCallClientContributor); ok && nextClient.ClientId == currentClient.ClientId {
|
|
1150
|
+
return next
|
|
1151
|
+
}
|
|
1152
|
+
return current
|
|
1153
|
+
}
|
|
1154
|
+
}
|
|
1155
|
+
if _, ok := next.Value.(*ahptypes.ToolCallClientContributor); ok {
|
|
1156
|
+
return current
|
|
1157
|
+
}
|
|
1158
|
+
return next
|
|
1159
|
+
}
|
|
1160
|
+
|
|
1161
|
+
func applyToolCallReady(state *ahptypes.ChatState, a *ahptypes.ChatToolCallReadyAction) ReduceOutcome {
|
|
1162
|
+
return updateToolCall(state, a.TurnId, a.ToolCallId, func(tc ahptypes.ToolCallState) ahptypes.ToolCallState {
|
|
1163
|
+
common := toolCallMeta(tc)
|
|
1164
|
+
if a.Intention != nil {
|
|
1165
|
+
common.intention = a.Intention
|
|
1166
|
+
}
|
|
1167
|
+
if a.ToolInput != nil {
|
|
1168
|
+
common.toolInput = a.ToolInput
|
|
1169
|
+
}
|
|
1170
|
+
common.contributor = refineToolCallContributor(common.contributor, a.Contributor)
|
|
1171
|
+
if a.Meta != nil {
|
|
1172
|
+
common.meta = a.Meta
|
|
1173
|
+
}
|
|
1174
|
+
var pending *ahptypes.ToolCallPendingConfirmationState
|
|
1175
|
+
if value, ok := tc.Value.(*ahptypes.ToolCallPendingConfirmationState); ok {
|
|
1176
|
+
pending = value
|
|
1177
|
+
}
|
|
1178
|
+
switch tc.Value.(type) {
|
|
1179
|
+
case *ahptypes.ToolCallStreamingState,
|
|
1180
|
+
*ahptypes.ToolCallRunningState,
|
|
1181
|
+
*ahptypes.ToolCallPendingConfirmationState:
|
|
1182
|
+
if a.Confirmed != nil {
|
|
1183
|
+
return ahptypes.ToolCallState{Value: &ahptypes.ToolCallRunningState{
|
|
1184
|
+
Status: ahptypes.ToolCallStatusRunning,
|
|
1185
|
+
ToolCallId: common.id,
|
|
1186
|
+
ToolName: common.name,
|
|
1187
|
+
DisplayName: common.displayName,
|
|
1188
|
+
Intention: common.intention,
|
|
1189
|
+
ToolInput: common.toolInput,
|
|
1190
|
+
Contributor: common.contributor,
|
|
1191
|
+
Meta: common.meta,
|
|
1192
|
+
InvocationMessage: a.InvocationMessage,
|
|
1193
|
+
Confirmed: *a.Confirmed,
|
|
1194
|
+
}}
|
|
1195
|
+
}
|
|
1196
|
+
next := &ahptypes.ToolCallPendingConfirmationState{
|
|
1197
|
+
Status: ahptypes.ToolCallStatusPendingConfirmation,
|
|
1198
|
+
ToolCallId: common.id,
|
|
1199
|
+
ToolName: common.name,
|
|
1200
|
+
DisplayName: common.displayName,
|
|
1201
|
+
Intention: common.intention,
|
|
1202
|
+
ToolInput: common.toolInput,
|
|
1203
|
+
Contributor: common.contributor,
|
|
1204
|
+
Meta: common.meta,
|
|
1205
|
+
InvocationMessage: a.InvocationMessage,
|
|
1206
|
+
ConfirmationTitle: a.ConfirmationTitle,
|
|
1207
|
+
RiskAssessment: a.RiskAssessment,
|
|
1208
|
+
Edits: a.Edits,
|
|
1209
|
+
Editable: a.Editable,
|
|
1210
|
+
Options: a.Options,
|
|
1211
|
+
}
|
|
1212
|
+
if pending != nil {
|
|
1213
|
+
if next.ConfirmationTitle == nil {
|
|
1214
|
+
next.ConfirmationTitle = pending.ConfirmationTitle
|
|
1215
|
+
}
|
|
1216
|
+
if next.RiskAssessment == nil {
|
|
1217
|
+
next.RiskAssessment = pending.RiskAssessment
|
|
1218
|
+
}
|
|
1219
|
+
if next.Edits == nil {
|
|
1220
|
+
next.Edits = pending.Edits
|
|
1221
|
+
}
|
|
1222
|
+
if next.Editable == nil {
|
|
1223
|
+
next.Editable = pending.Editable
|
|
1224
|
+
}
|
|
1225
|
+
if next.Options == nil {
|
|
1226
|
+
next.Options = pending.Options
|
|
1227
|
+
}
|
|
1228
|
+
}
|
|
1229
|
+
return ahptypes.ToolCallState{Value: next}
|
|
1230
|
+
}
|
|
1231
|
+
return tc
|
|
1232
|
+
})
|
|
1233
|
+
}
|
|
1234
|
+
|
|
1235
|
+
func resolveSelectedOption(options []ahptypes.ConfirmationOption, id *string) *ahptypes.ConfirmationOption {
|
|
1236
|
+
if id == nil {
|
|
1237
|
+
return nil
|
|
1238
|
+
}
|
|
1239
|
+
for i := range options {
|
|
1240
|
+
if options[i].Id == *id {
|
|
1241
|
+
opt := options[i]
|
|
1242
|
+
return &opt
|
|
1243
|
+
}
|
|
1244
|
+
}
|
|
1245
|
+
return nil
|
|
1246
|
+
}
|
|
1247
|
+
|
|
1248
|
+
func applyToolCallConfirmed(state *ahptypes.ChatState, a *ahptypes.ChatToolCallConfirmedAction) ReduceOutcome {
|
|
1249
|
+
return updateToolCall(state, a.TurnId, a.ToolCallId, func(tc ahptypes.ToolCallState) ahptypes.ToolCallState {
|
|
1250
|
+
s, ok := tc.Value.(*ahptypes.ToolCallPendingConfirmationState)
|
|
1251
|
+
if !ok {
|
|
1252
|
+
return tc
|
|
1253
|
+
}
|
|
1254
|
+
selected := resolveSelectedOption(s.Options, a.SelectedOptionId)
|
|
1255
|
+
if a.Approved {
|
|
1256
|
+
toolInput := s.ToolInput
|
|
1257
|
+
if a.EditedToolInput != nil && toolInput != nil && toolInput.Inline != nil {
|
|
1258
|
+
inline := *a.EditedToolInput
|
|
1259
|
+
toolInput = &ahptypes.ToolInput{Inline: &inline}
|
|
1260
|
+
}
|
|
1261
|
+
meta := s.Meta
|
|
1262
|
+
if a.Meta != nil {
|
|
1263
|
+
meta = a.Meta
|
|
1264
|
+
}
|
|
1265
|
+
confirmed := ahptypes.ToolCallConfirmationReasonNotNeeded
|
|
1266
|
+
if a.Confirmed != nil {
|
|
1267
|
+
confirmed = *a.Confirmed
|
|
1268
|
+
}
|
|
1269
|
+
return ahptypes.ToolCallState{Value: &ahptypes.ToolCallRunningState{
|
|
1270
|
+
Status: ahptypes.ToolCallStatusRunning,
|
|
1271
|
+
ToolCallId: s.ToolCallId,
|
|
1272
|
+
ToolName: s.ToolName,
|
|
1273
|
+
DisplayName: s.DisplayName,
|
|
1274
|
+
Intention: s.Intention,
|
|
1275
|
+
ToolInput: toolInput,
|
|
1276
|
+
Contributor: s.Contributor,
|
|
1277
|
+
Meta: meta,
|
|
1278
|
+
InvocationMessage: s.InvocationMessage,
|
|
1279
|
+
Confirmed: confirmed,
|
|
1280
|
+
SelectedOption: selected,
|
|
1281
|
+
}}
|
|
1282
|
+
}
|
|
1283
|
+
reason := ahptypes.ToolCallCancellationReasonDenied
|
|
1284
|
+
if a.Reason != nil {
|
|
1285
|
+
reason = *a.Reason
|
|
1286
|
+
}
|
|
1287
|
+
meta := s.Meta
|
|
1288
|
+
if a.Meta != nil {
|
|
1289
|
+
meta = a.Meta
|
|
1290
|
+
}
|
|
1291
|
+
return ahptypes.ToolCallState{Value: &ahptypes.ToolCallCancelledState{
|
|
1292
|
+
Status: ahptypes.ToolCallStatusCancelled,
|
|
1293
|
+
ToolCallId: s.ToolCallId,
|
|
1294
|
+
ToolName: s.ToolName,
|
|
1295
|
+
DisplayName: s.DisplayName,
|
|
1296
|
+
Intention: s.Intention,
|
|
1297
|
+
ToolInput: s.ToolInput,
|
|
1298
|
+
Contributor: s.Contributor,
|
|
1299
|
+
Meta: meta,
|
|
1300
|
+
InvocationMessage: s.InvocationMessage,
|
|
1301
|
+
Reason: reason,
|
|
1302
|
+
ReasonMessage: a.ReasonMessage,
|
|
1303
|
+
UserSuggestion: a.UserSuggestion,
|
|
1304
|
+
SelectedOption: selected,
|
|
1305
|
+
}}
|
|
1306
|
+
})
|
|
1307
|
+
}
|
|
1308
|
+
|
|
1309
|
+
func applyToolCallComplete(state *ahptypes.ChatState, a *ahptypes.ChatToolCallCompleteAction) ReduceOutcome {
|
|
1310
|
+
return updateToolCall(state, a.TurnId, a.ToolCallId, func(tc ahptypes.ToolCallState) ahptypes.ToolCallState {
|
|
1311
|
+
common := toolCallMeta(tc)
|
|
1312
|
+
if a.Meta != nil {
|
|
1313
|
+
common.meta = a.Meta
|
|
1314
|
+
}
|
|
1315
|
+
var (
|
|
1316
|
+
invocation ahptypes.StringOrMarkdown
|
|
1317
|
+
toolInput *ahptypes.ToolInput
|
|
1318
|
+
confirmed = ahptypes.ToolCallConfirmationReasonNotNeeded
|
|
1319
|
+
selectedOption *ahptypes.ConfirmationOption
|
|
1320
|
+
preAuthContent []ahptypes.ToolResultContent
|
|
1321
|
+
fromAuthRequired bool
|
|
1322
|
+
)
|
|
1323
|
+
switch v := tc.Value.(type) {
|
|
1324
|
+
case *ahptypes.ToolCallRunningState:
|
|
1325
|
+
invocation = v.InvocationMessage
|
|
1326
|
+
toolInput = v.ToolInput
|
|
1327
|
+
confirmed = v.Confirmed
|
|
1328
|
+
selectedOption = v.SelectedOption
|
|
1329
|
+
case *ahptypes.ToolCallPendingConfirmationState:
|
|
1330
|
+
invocation = v.InvocationMessage
|
|
1331
|
+
toolInput = v.ToolInput
|
|
1332
|
+
case *ahptypes.ToolCallAuthRequiredState:
|
|
1333
|
+
// A client MAY cancel an auth-required MCP tool call by dispatching a
|
|
1334
|
+
// failed completion instead of authenticating. A successful completion
|
|
1335
|
+
// is invalid from auth-required — execution never resumed after the
|
|
1336
|
+
// challenge — and is ignored, leaving the call in auth-required. Any
|
|
1337
|
+
// partial content produced before the call paused for auth is
|
|
1338
|
+
// preserved unless the dispatched result supplies its own content.
|
|
1339
|
+
if a.Result.Success {
|
|
1340
|
+
return tc
|
|
1341
|
+
}
|
|
1342
|
+
invocation = v.InvocationMessage
|
|
1343
|
+
toolInput = v.ToolInput
|
|
1344
|
+
confirmed = v.Confirmed
|
|
1345
|
+
selectedOption = v.SelectedOption
|
|
1346
|
+
preAuthContent = v.Content
|
|
1347
|
+
fromAuthRequired = true
|
|
1348
|
+
default:
|
|
1349
|
+
return tc
|
|
1350
|
+
}
|
|
1351
|
+
content := a.Result.Content
|
|
1352
|
+
if content == nil {
|
|
1353
|
+
content = preAuthContent
|
|
1354
|
+
}
|
|
1355
|
+
// Cancelling from auth-required always completes terminally: the
|
|
1356
|
+
// pending auth challenge isn't a "pending result" the client can
|
|
1357
|
+
// review, so requiresResultConfirmation is ignored for this path —
|
|
1358
|
+
// it must never enter pending-result-confirmation.
|
|
1359
|
+
requiresResultConfirmation := a.RequiresResultConfirmation != nil && *a.RequiresResultConfirmation && !fromAuthRequired
|
|
1360
|
+
if requiresResultConfirmation {
|
|
1361
|
+
return ahptypes.ToolCallState{Value: &ahptypes.ToolCallPendingResultConfirmationState{
|
|
1362
|
+
Status: ahptypes.ToolCallStatusPendingResultConfirmation,
|
|
1363
|
+
ToolCallId: common.id,
|
|
1364
|
+
ToolName: common.name,
|
|
1365
|
+
DisplayName: common.displayName,
|
|
1366
|
+
Intention: common.intention,
|
|
1367
|
+
ToolInput: toolInput,
|
|
1368
|
+
Contributor: common.contributor,
|
|
1369
|
+
Meta: common.meta,
|
|
1370
|
+
InvocationMessage: invocation,
|
|
1371
|
+
Success: a.Result.Success,
|
|
1372
|
+
PastTenseMessage: a.Result.PastTenseMessage,
|
|
1373
|
+
Content: append([]ahptypes.ToolResultContent(nil), content...),
|
|
1374
|
+
StructuredContent: a.Result.StructuredContent,
|
|
1375
|
+
Error: a.Result.Error,
|
|
1376
|
+
Confirmed: confirmed,
|
|
1377
|
+
SelectedOption: selectedOption,
|
|
1378
|
+
}}
|
|
1379
|
+
}
|
|
1380
|
+
return ahptypes.ToolCallState{Value: &ahptypes.ToolCallCompletedState{
|
|
1381
|
+
Status: ahptypes.ToolCallStatusCompleted,
|
|
1382
|
+
ToolCallId: common.id,
|
|
1383
|
+
ToolName: common.name,
|
|
1384
|
+
DisplayName: common.displayName,
|
|
1385
|
+
Intention: common.intention,
|
|
1386
|
+
ToolInput: toolInput,
|
|
1387
|
+
Contributor: common.contributor,
|
|
1388
|
+
Meta: common.meta,
|
|
1389
|
+
InvocationMessage: invocation,
|
|
1390
|
+
Success: a.Result.Success,
|
|
1391
|
+
PastTenseMessage: a.Result.PastTenseMessage,
|
|
1392
|
+
Content: append([]ahptypes.ToolResultContent(nil), content...),
|
|
1393
|
+
StructuredContent: a.Result.StructuredContent,
|
|
1394
|
+
Error: a.Result.Error,
|
|
1395
|
+
Confirmed: confirmed,
|
|
1396
|
+
SelectedOption: selectedOption,
|
|
1397
|
+
}}
|
|
1398
|
+
})
|
|
1399
|
+
}
|
|
1400
|
+
|
|
1401
|
+
func applyToolCallResultConfirmed(state *ahptypes.ChatState, a *ahptypes.ChatToolCallResultConfirmedAction) ReduceOutcome {
|
|
1402
|
+
return updateToolCall(state, a.TurnId, a.ToolCallId, func(tc ahptypes.ToolCallState) ahptypes.ToolCallState {
|
|
1403
|
+
s, ok := tc.Value.(*ahptypes.ToolCallPendingResultConfirmationState)
|
|
1404
|
+
if !ok {
|
|
1405
|
+
return tc
|
|
1406
|
+
}
|
|
1407
|
+
if a.Approved {
|
|
1408
|
+
meta := s.Meta
|
|
1409
|
+
if a.Meta != nil {
|
|
1410
|
+
meta = a.Meta
|
|
1411
|
+
}
|
|
1412
|
+
return ahptypes.ToolCallState{Value: &ahptypes.ToolCallCompletedState{
|
|
1413
|
+
Status: ahptypes.ToolCallStatusCompleted,
|
|
1414
|
+
ToolCallId: s.ToolCallId,
|
|
1415
|
+
ToolName: s.ToolName,
|
|
1416
|
+
DisplayName: s.DisplayName,
|
|
1417
|
+
Intention: s.Intention,
|
|
1418
|
+
ToolInput: s.ToolInput,
|
|
1419
|
+
Contributor: s.Contributor,
|
|
1420
|
+
Meta: meta,
|
|
1421
|
+
InvocationMessage: s.InvocationMessage,
|
|
1422
|
+
Success: s.Success,
|
|
1423
|
+
PastTenseMessage: s.PastTenseMessage,
|
|
1424
|
+
Content: s.Content,
|
|
1425
|
+
StructuredContent: s.StructuredContent,
|
|
1426
|
+
Error: s.Error,
|
|
1427
|
+
Confirmed: s.Confirmed,
|
|
1428
|
+
SelectedOption: s.SelectedOption,
|
|
1429
|
+
}}
|
|
1430
|
+
}
|
|
1431
|
+
meta := s.Meta
|
|
1432
|
+
if a.Meta != nil {
|
|
1433
|
+
meta = a.Meta
|
|
1434
|
+
}
|
|
1435
|
+
return ahptypes.ToolCallState{Value: &ahptypes.ToolCallCancelledState{
|
|
1436
|
+
Status: ahptypes.ToolCallStatusCancelled,
|
|
1437
|
+
ToolCallId: s.ToolCallId,
|
|
1438
|
+
ToolName: s.ToolName,
|
|
1439
|
+
DisplayName: s.DisplayName,
|
|
1440
|
+
Intention: s.Intention,
|
|
1441
|
+
ToolInput: s.ToolInput,
|
|
1442
|
+
Contributor: s.Contributor,
|
|
1443
|
+
Meta: meta,
|
|
1444
|
+
InvocationMessage: s.InvocationMessage,
|
|
1445
|
+
Reason: ahptypes.ToolCallCancellationReasonResultDenied,
|
|
1446
|
+
SelectedOption: s.SelectedOption,
|
|
1447
|
+
}}
|
|
1448
|
+
})
|
|
1449
|
+
}
|
|
1450
|
+
|
|
1451
|
+
func applyToolCallAuthRequired(state *ahptypes.ChatState, a *ahptypes.ChatToolCallAuthRequiredAction) ReduceOutcome {
|
|
1452
|
+
return updateToolCall(state, a.TurnId, a.ToolCallId, func(tc ahptypes.ToolCallState) ahptypes.ToolCallState {
|
|
1453
|
+
s, ok := tc.Value.(*ahptypes.ToolCallRunningState)
|
|
1454
|
+
if !ok {
|
|
1455
|
+
return tc
|
|
1456
|
+
}
|
|
1457
|
+
if s.Contributor == nil {
|
|
1458
|
+
return tc
|
|
1459
|
+
}
|
|
1460
|
+
if _, ok := s.Contributor.Value.(*ahptypes.ToolCallMcpContributor); !ok {
|
|
1461
|
+
return tc
|
|
1462
|
+
}
|
|
1463
|
+
meta := s.Meta
|
|
1464
|
+
if a.Meta != nil {
|
|
1465
|
+
meta = a.Meta
|
|
1466
|
+
}
|
|
1467
|
+
return ahptypes.ToolCallState{Value: &ahptypes.ToolCallAuthRequiredState{
|
|
1468
|
+
Status: ahptypes.ToolCallStatusAuthRequired,
|
|
1469
|
+
ToolCallId: s.ToolCallId,
|
|
1470
|
+
ToolName: s.ToolName,
|
|
1471
|
+
DisplayName: s.DisplayName,
|
|
1472
|
+
Intention: s.Intention,
|
|
1473
|
+
ToolInput: s.ToolInput,
|
|
1474
|
+
Contributor: s.Contributor,
|
|
1475
|
+
Meta: meta,
|
|
1476
|
+
InvocationMessage: s.InvocationMessage,
|
|
1477
|
+
Confirmed: s.Confirmed,
|
|
1478
|
+
SelectedOption: s.SelectedOption,
|
|
1479
|
+
Auth: a.Auth,
|
|
1480
|
+
Content: append([]ahptypes.ToolResultContent(nil), s.Content...),
|
|
1481
|
+
}}
|
|
1482
|
+
})
|
|
1483
|
+
}
|
|
1484
|
+
|
|
1485
|
+
func applyToolCallAuthResolved(state *ahptypes.ChatState, a *ahptypes.ChatToolCallAuthResolvedAction) ReduceOutcome {
|
|
1486
|
+
return updateToolCall(state, a.TurnId, a.ToolCallId, func(tc ahptypes.ToolCallState) ahptypes.ToolCallState {
|
|
1487
|
+
s, ok := tc.Value.(*ahptypes.ToolCallAuthRequiredState)
|
|
1488
|
+
if !ok {
|
|
1489
|
+
return tc
|
|
1490
|
+
}
|
|
1491
|
+
meta := s.Meta
|
|
1492
|
+
if a.Meta != nil {
|
|
1493
|
+
meta = a.Meta
|
|
1494
|
+
}
|
|
1495
|
+
return ahptypes.ToolCallState{Value: &ahptypes.ToolCallRunningState{
|
|
1496
|
+
Status: ahptypes.ToolCallStatusRunning,
|
|
1497
|
+
ToolCallId: s.ToolCallId,
|
|
1498
|
+
ToolName: s.ToolName,
|
|
1499
|
+
DisplayName: s.DisplayName,
|
|
1500
|
+
Intention: s.Intention,
|
|
1501
|
+
ToolInput: s.ToolInput,
|
|
1502
|
+
Contributor: s.Contributor,
|
|
1503
|
+
Meta: meta,
|
|
1504
|
+
InvocationMessage: s.InvocationMessage,
|
|
1505
|
+
Confirmed: s.Confirmed,
|
|
1506
|
+
SelectedOption: s.SelectedOption,
|
|
1507
|
+
Content: append([]ahptypes.ToolResultContent(nil), s.Content...),
|
|
1508
|
+
}}
|
|
1509
|
+
})
|
|
1510
|
+
}
|
|
1511
|
+
|
|
1512
|
+
func updateMcpServerCustomizationState(state *ahptypes.SessionState, id string, nextState ahptypes.McpServerState, channel *ahptypes.URI) ReduceOutcome {
|
|
1513
|
+
mcp := findMcpServerCustomization(state, id)
|
|
1514
|
+
if mcp == nil {
|
|
1515
|
+
return ReduceOutcomeNoOp
|
|
1516
|
+
}
|
|
1517
|
+
mcp.State = nextState
|
|
1518
|
+
mcp.Channel = channel
|
|
1519
|
+
return ReduceOutcomeApplied
|
|
1520
|
+
}
|
|
1521
|
+
|
|
1522
|
+
// findMcpServerCustomization locates the McpServerCustomization with the given
|
|
1523
|
+
// id, searching the top-level list first and then every container's children.
|
|
1524
|
+
// Returns nil when no entry matches or the id targets a non-MCP customization.
|
|
1525
|
+
func findMcpServerCustomization(state *ahptypes.SessionState, id string) *ahptypes.McpServerCustomization {
|
|
1526
|
+
list := state.Customizations
|
|
1527
|
+
for i := range list {
|
|
1528
|
+
got, ok := customizationID(list[i])
|
|
1529
|
+
if !ok || got != id {
|
|
1530
|
+
continue
|
|
1531
|
+
}
|
|
1532
|
+
mcp, _ := list[i].Value.(*ahptypes.McpServerCustomization)
|
|
1533
|
+
return mcp
|
|
1534
|
+
}
|
|
1535
|
+
for i := range list {
|
|
1536
|
+
children := containerChildren(&list[i])
|
|
1537
|
+
if children == nil {
|
|
1538
|
+
continue
|
|
1539
|
+
}
|
|
1540
|
+
for j := range *children {
|
|
1541
|
+
got, ok := childCustomizationID((*children)[j])
|
|
1542
|
+
if !ok || got != id {
|
|
1543
|
+
continue
|
|
1544
|
+
}
|
|
1545
|
+
mcp, _ := (*children)[j].Value.(*ahptypes.McpServerCustomization)
|
|
1546
|
+
return mcp
|
|
1547
|
+
}
|
|
1548
|
+
}
|
|
1549
|
+
return nil
|
|
1550
|
+
}
|
|
1551
|
+
|
|
1552
|
+
func applyTruncated(state *ahptypes.ChatState, turnID *string) ReduceOutcome {
|
|
1553
|
+
if turnID == nil {
|
|
1554
|
+
state.Turns = []ahptypes.Turn{}
|
|
1555
|
+
state.TurnsNextCursor = nil
|
|
1556
|
+
} else {
|
|
1557
|
+
idx := -1
|
|
1558
|
+
for i := range state.Turns {
|
|
1559
|
+
if state.Turns[i].Id == *turnID {
|
|
1560
|
+
idx = i
|
|
1561
|
+
break
|
|
1562
|
+
}
|
|
1563
|
+
}
|
|
1564
|
+
if idx < 0 {
|
|
1565
|
+
return ReduceOutcomeNoOp
|
|
1566
|
+
}
|
|
1567
|
+
state.Turns = state.Turns[:idx+1]
|
|
1568
|
+
}
|
|
1569
|
+
state.ActiveTurn = nil
|
|
1570
|
+
state.Status = summaryStatus(state, nil)
|
|
1571
|
+
return ReduceOutcomeApplied
|
|
1572
|
+
}
|
|
1573
|
+
|
|
1574
|
+
func applyInputAnswerChanged(state *ahptypes.ChatState, a *ahptypes.ChatInputAnswerChangedAction) ReduceOutcome {
|
|
1575
|
+
if state.ActiveTurn == nil {
|
|
1576
|
+
return ReduceOutcomeNoOp
|
|
1577
|
+
}
|
|
1578
|
+
for i := range state.ActiveTurn.ResponseParts {
|
|
1579
|
+
input, ok := state.ActiveTurn.ResponseParts[i].Value.(*ahptypes.InputRequestResponsePart)
|
|
1580
|
+
if !ok || input.Response != nil || input.Request.Id != a.RequestId {
|
|
1581
|
+
continue
|
|
1582
|
+
}
|
|
1583
|
+
req := &input.Request
|
|
1584
|
+
if req.Answers == nil {
|
|
1585
|
+
req.Answers = make(map[string]ahptypes.ChatInputAnswer)
|
|
1586
|
+
}
|
|
1587
|
+
if a.Answer == nil {
|
|
1588
|
+
delete(req.Answers, a.QuestionId)
|
|
1589
|
+
} else {
|
|
1590
|
+
req.Answers[a.QuestionId] = *a.Answer
|
|
1591
|
+
}
|
|
1592
|
+
if len(req.Answers) == 0 {
|
|
1593
|
+
req.Answers = nil
|
|
1594
|
+
}
|
|
1595
|
+
return ReduceOutcomeApplied
|
|
1596
|
+
}
|
|
1597
|
+
return ReduceOutcomeNoOp
|
|
1598
|
+
}
|
|
1599
|
+
|
|
1600
|
+
// ─── Terminal Reducer ──────────────────────────────────────────────────
|
|
1601
|
+
|
|
1602
|
+
// ApplyActionToTerminal applies action to the [ahptypes.TerminalState]
|
|
1603
|
+
// in place. Returns [ReduceOutcomeOutOfScope] for actions that target a
|
|
1604
|
+
// different state tree.
|
|
1605
|
+
func ApplyActionToTerminal(state *ahptypes.TerminalState, action ahptypes.StateAction) ReduceOutcome {
|
|
1606
|
+
switch a := action.Value.(type) {
|
|
1607
|
+
case *ahptypes.TerminalDataAction:
|
|
1608
|
+
appendTerminalData(state, a.Data)
|
|
1609
|
+
return ReduceOutcomeApplied
|
|
1610
|
+
case *ahptypes.TerminalInputAction:
|
|
1611
|
+
return ReduceOutcomeNoOp
|
|
1612
|
+
case *ahptypes.TerminalResizedAction:
|
|
1613
|
+
cols := a.Cols
|
|
1614
|
+
rows := a.Rows
|
|
1615
|
+
state.Cols = &cols
|
|
1616
|
+
state.Rows = &rows
|
|
1617
|
+
return ReduceOutcomeApplied
|
|
1618
|
+
case *ahptypes.TerminalClaimedAction:
|
|
1619
|
+
state.Claim = a.Claim
|
|
1620
|
+
return ReduceOutcomeApplied
|
|
1621
|
+
case *ahptypes.TerminalTitleChangedAction:
|
|
1622
|
+
state.Title = a.Title
|
|
1623
|
+
return ReduceOutcomeApplied
|
|
1624
|
+
case *ahptypes.TerminalCwdChangedAction:
|
|
1625
|
+
cwd := a.Cwd
|
|
1626
|
+
state.Cwd = &cwd
|
|
1627
|
+
return ReduceOutcomeApplied
|
|
1628
|
+
case *ahptypes.TerminalExitedAction:
|
|
1629
|
+
state.Lifecycle = ahptypes.TerminalLifecycleState{Value: &ahptypes.TerminalExitedLifecycleState{
|
|
1630
|
+
Status: ahptypes.TerminalLifecycleStatusExited,
|
|
1631
|
+
ExitCode: a.ExitCode,
|
|
1632
|
+
}}
|
|
1633
|
+
return ReduceOutcomeApplied
|
|
1634
|
+
case *ahptypes.TerminalClearedAction:
|
|
1635
|
+
state.Content = []ahptypes.TerminalContentPart{}
|
|
1636
|
+
return ReduceOutcomeApplied
|
|
1637
|
+
case *ahptypes.TerminalCommandDetectionAvailableAction:
|
|
1638
|
+
t := true
|
|
1639
|
+
state.SupportsCommandDetection = &t
|
|
1640
|
+
return ReduceOutcomeApplied
|
|
1641
|
+
case *ahptypes.TerminalCommandExecutedAction:
|
|
1642
|
+
state.Content = append(state.Content, ahptypes.TerminalContentPart{Value: &ahptypes.TerminalCommandPart{
|
|
1643
|
+
Type: "command",
|
|
1644
|
+
CommandId: a.CommandId,
|
|
1645
|
+
CommandLine: a.CommandLine,
|
|
1646
|
+
Timestamp: a.Timestamp,
|
|
1647
|
+
IsComplete: false,
|
|
1648
|
+
}})
|
|
1649
|
+
t := true
|
|
1650
|
+
state.SupportsCommandDetection = &t
|
|
1651
|
+
return ReduceOutcomeApplied
|
|
1652
|
+
case *ahptypes.TerminalCommandFinishedAction:
|
|
1653
|
+
for i := range state.Content {
|
|
1654
|
+
c, ok := state.Content[i].Value.(*ahptypes.TerminalCommandPart)
|
|
1655
|
+
if !ok {
|
|
1656
|
+
continue
|
|
1657
|
+
}
|
|
1658
|
+
if c.CommandId == a.CommandId {
|
|
1659
|
+
c.IsComplete = true
|
|
1660
|
+
c.ExitCode = a.ExitCode
|
|
1661
|
+
c.DurationMs = a.DurationMs
|
|
1662
|
+
return ReduceOutcomeApplied
|
|
1663
|
+
}
|
|
1664
|
+
}
|
|
1665
|
+
return ReduceOutcomeNoOp
|
|
1666
|
+
}
|
|
1667
|
+
return ReduceOutcomeOutOfScope
|
|
1668
|
+
}
|
|
1669
|
+
|
|
1670
|
+
func appendTerminalData(state *ahptypes.TerminalState, data string) {
|
|
1671
|
+
n := len(state.Content)
|
|
1672
|
+
if n > 0 {
|
|
1673
|
+
switch tail := state.Content[n-1].Value.(type) {
|
|
1674
|
+
case *ahptypes.TerminalCommandPart:
|
|
1675
|
+
if !tail.IsComplete {
|
|
1676
|
+
tail.Output += data
|
|
1677
|
+
return
|
|
1678
|
+
}
|
|
1679
|
+
case *ahptypes.TerminalUnclassifiedPart:
|
|
1680
|
+
tail.Value += data
|
|
1681
|
+
return
|
|
1682
|
+
}
|
|
1683
|
+
}
|
|
1684
|
+
state.Content = append(state.Content, ahptypes.TerminalContentPart{Value: &ahptypes.TerminalUnclassifiedPart{
|
|
1685
|
+
Type: "unclassified",
|
|
1686
|
+
Value: data,
|
|
1687
|
+
}})
|
|
1688
|
+
}
|
|
1689
|
+
|
|
1690
|
+
// ─── Changeset Reducer ─────────────────────────────────────────────────
|
|
1691
|
+
|
|
1692
|
+
// ApplyActionToChangeset applies action to the [ahptypes.ChangesetState]
|
|
1693
|
+
// in place. Returns [ReduceOutcomeOutOfScope] for actions that target a
|
|
1694
|
+
// different state tree.
|
|
1695
|
+
func ApplyActionToChangeset(state *ahptypes.ChangesetState, action ahptypes.StateAction) ReduceOutcome {
|
|
1696
|
+
switch a := action.Value.(type) {
|
|
1697
|
+
case *ahptypes.ChangesetStatusChangedAction:
|
|
1698
|
+
state.Status = a.Status
|
|
1699
|
+
if a.Status == ahptypes.ChangesetStatusError {
|
|
1700
|
+
state.Error = a.Error
|
|
1701
|
+
} else {
|
|
1702
|
+
state.Error = nil
|
|
1703
|
+
}
|
|
1704
|
+
return ReduceOutcomeApplied
|
|
1705
|
+
|
|
1706
|
+
case *ahptypes.ChangesetFileSetAction:
|
|
1707
|
+
for i := range state.Files {
|
|
1708
|
+
if state.Files[i].Id == a.File.Id {
|
|
1709
|
+
state.Files[i] = a.File
|
|
1710
|
+
return ReduceOutcomeApplied
|
|
1711
|
+
}
|
|
1712
|
+
}
|
|
1713
|
+
state.Files = append(state.Files, a.File)
|
|
1714
|
+
return ReduceOutcomeApplied
|
|
1715
|
+
|
|
1716
|
+
case *ahptypes.ChangesetFileRemovedAction:
|
|
1717
|
+
for i := range state.Files {
|
|
1718
|
+
if state.Files[i].Id == a.FileId {
|
|
1719
|
+
state.Files = append(state.Files[:i], state.Files[i+1:]...)
|
|
1720
|
+
return ReduceOutcomeApplied
|
|
1721
|
+
}
|
|
1722
|
+
}
|
|
1723
|
+
return ReduceOutcomeNoOp
|
|
1724
|
+
|
|
1725
|
+
case *ahptypes.ChangesetFilesReviewChangedAction:
|
|
1726
|
+
ids := make(map[string]struct{}, len(a.Files))
|
|
1727
|
+
for _, id := range a.Files {
|
|
1728
|
+
ids[id] = struct{}{}
|
|
1729
|
+
}
|
|
1730
|
+
changed := false
|
|
1731
|
+
for i := range state.Files {
|
|
1732
|
+
if _, ok := ids[state.Files[i].Id]; !ok {
|
|
1733
|
+
continue
|
|
1734
|
+
}
|
|
1735
|
+
if state.Files[i].Reviewed != nil && *state.Files[i].Reviewed == a.Reviewed {
|
|
1736
|
+
continue
|
|
1737
|
+
}
|
|
1738
|
+
reviewed := a.Reviewed
|
|
1739
|
+
state.Files[i].Reviewed = &reviewed
|
|
1740
|
+
changed = true
|
|
1741
|
+
}
|
|
1742
|
+
if !changed {
|
|
1743
|
+
return ReduceOutcomeNoOp
|
|
1744
|
+
}
|
|
1745
|
+
return ReduceOutcomeApplied
|
|
1746
|
+
|
|
1747
|
+
case *ahptypes.ChangesetContentChangedAction:
|
|
1748
|
+
state.Files = a.Files
|
|
1749
|
+
if a.Operations != nil {
|
|
1750
|
+
state.Operations = a.Operations
|
|
1751
|
+
}
|
|
1752
|
+
return ReduceOutcomeApplied
|
|
1753
|
+
|
|
1754
|
+
case *ahptypes.ChangesetOperationsChangedAction:
|
|
1755
|
+
state.Operations = a.Operations
|
|
1756
|
+
return ReduceOutcomeApplied
|
|
1757
|
+
|
|
1758
|
+
case *ahptypes.ChangesetOperationStatusChangedAction:
|
|
1759
|
+
for i := range state.Operations {
|
|
1760
|
+
if state.Operations[i].Id == a.OperationId {
|
|
1761
|
+
state.Operations[i].Status = a.Status
|
|
1762
|
+
if a.Status == ahptypes.ChangesetOperationStatusError {
|
|
1763
|
+
state.Operations[i].Error = a.Error
|
|
1764
|
+
} else {
|
|
1765
|
+
state.Operations[i].Error = nil
|
|
1766
|
+
}
|
|
1767
|
+
return ReduceOutcomeApplied
|
|
1768
|
+
}
|
|
1769
|
+
}
|
|
1770
|
+
return ReduceOutcomeNoOp
|
|
1771
|
+
|
|
1772
|
+
case *ahptypes.ChangesetClearedAction:
|
|
1773
|
+
if len(state.Files) == 0 {
|
|
1774
|
+
return ReduceOutcomeNoOp
|
|
1775
|
+
}
|
|
1776
|
+
state.Files = []ahptypes.ChangesetFile{}
|
|
1777
|
+
return ReduceOutcomeApplied
|
|
1778
|
+
}
|
|
1779
|
+
return ReduceOutcomeOutOfScope
|
|
1780
|
+
}
|
|
1781
|
+
|
|
1782
|
+
// ─── Annotations Reducer ──────────────────────────────────────────────
|
|
1783
|
+
|
|
1784
|
+
// ApplyActionToAnnotations applies action to the [ahptypes.AnnotationsState]
|
|
1785
|
+
// in place. Returns [ReduceOutcomeOutOfScope] for actions that target a
|
|
1786
|
+
// different state tree.
|
|
1787
|
+
func ApplyActionToAnnotations(state *ahptypes.AnnotationsState, action ahptypes.StateAction) ReduceOutcome {
|
|
1788
|
+
switch a := action.Value.(type) {
|
|
1789
|
+
case *ahptypes.AnnotationsSetAction:
|
|
1790
|
+
for i := range state.Annotations {
|
|
1791
|
+
if state.Annotations[i].Id == a.Annotation.Id {
|
|
1792
|
+
state.Annotations[i] = a.Annotation
|
|
1793
|
+
return ReduceOutcomeApplied
|
|
1794
|
+
}
|
|
1795
|
+
}
|
|
1796
|
+
state.Annotations = append(state.Annotations, a.Annotation)
|
|
1797
|
+
return ReduceOutcomeApplied
|
|
1798
|
+
|
|
1799
|
+
case *ahptypes.AnnotationsUpdatedAction:
|
|
1800
|
+
for i := range state.Annotations {
|
|
1801
|
+
if state.Annotations[i].Id == a.AnnotationId {
|
|
1802
|
+
if a.Origin != nil {
|
|
1803
|
+
state.Annotations[i].Origin = *a.Origin
|
|
1804
|
+
}
|
|
1805
|
+
if a.Resource != nil {
|
|
1806
|
+
state.Annotations[i].Resource = *a.Resource
|
|
1807
|
+
}
|
|
1808
|
+
if a.Range != nil {
|
|
1809
|
+
state.Annotations[i].Range = a.Range
|
|
1810
|
+
}
|
|
1811
|
+
if a.Resolved != nil {
|
|
1812
|
+
state.Annotations[i].Resolved = *a.Resolved
|
|
1813
|
+
}
|
|
1814
|
+
return ReduceOutcomeApplied
|
|
1815
|
+
}
|
|
1816
|
+
}
|
|
1817
|
+
return ReduceOutcomeNoOp
|
|
1818
|
+
|
|
1819
|
+
case *ahptypes.AnnotationsRemovedAction:
|
|
1820
|
+
for i := range state.Annotations {
|
|
1821
|
+
if state.Annotations[i].Id == a.AnnotationId {
|
|
1822
|
+
state.Annotations = append(state.Annotations[:i], state.Annotations[i+1:]...)
|
|
1823
|
+
return ReduceOutcomeApplied
|
|
1824
|
+
}
|
|
1825
|
+
}
|
|
1826
|
+
return ReduceOutcomeNoOp
|
|
1827
|
+
|
|
1828
|
+
case *ahptypes.AnnotationsEntrySetAction:
|
|
1829
|
+
for i := range state.Annotations {
|
|
1830
|
+
if state.Annotations[i].Id != a.AnnotationId {
|
|
1831
|
+
continue
|
|
1832
|
+
}
|
|
1833
|
+
entries := state.Annotations[i].Entries
|
|
1834
|
+
for j := range entries {
|
|
1835
|
+
if entries[j].Id == a.Entry.Id {
|
|
1836
|
+
state.Annotations[i].Entries[j] = a.Entry
|
|
1837
|
+
return ReduceOutcomeApplied
|
|
1838
|
+
}
|
|
1839
|
+
}
|
|
1840
|
+
state.Annotations[i].Entries = append(state.Annotations[i].Entries, a.Entry)
|
|
1841
|
+
return ReduceOutcomeApplied
|
|
1842
|
+
}
|
|
1843
|
+
return ReduceOutcomeNoOp
|
|
1844
|
+
|
|
1845
|
+
case *ahptypes.AnnotationsEntryRemovedAction:
|
|
1846
|
+
for i := range state.Annotations {
|
|
1847
|
+
if state.Annotations[i].Id != a.AnnotationId {
|
|
1848
|
+
continue
|
|
1849
|
+
}
|
|
1850
|
+
entries := state.Annotations[i].Entries
|
|
1851
|
+
for j := range entries {
|
|
1852
|
+
if entries[j].Id == a.EntryId {
|
|
1853
|
+
state.Annotations[i].Entries = append(entries[:j], entries[j+1:]...)
|
|
1854
|
+
return ReduceOutcomeApplied
|
|
1855
|
+
}
|
|
1856
|
+
}
|
|
1857
|
+
return ReduceOutcomeNoOp
|
|
1858
|
+
}
|
|
1859
|
+
return ReduceOutcomeNoOp
|
|
1860
|
+
}
|
|
1861
|
+
return ReduceOutcomeOutOfScope
|
|
1862
|
+
}
|
|
1863
|
+
|
|
1864
|
+
// ─── Resource-Watch Reducer ────────────────────────────────────────────
|
|
1865
|
+
|
|
1866
|
+
// ApplyActionToResourceWatch applies action to the
|
|
1867
|
+
// [ahptypes.ResourceWatchState] in place. The state captures only the
|
|
1868
|
+
// watch descriptor set at subscription time; change events delivered
|
|
1869
|
+
// via [ahptypes.ResourceWatchChangedAction] are pass-through and leave
|
|
1870
|
+
// the state unchanged. Returns [ReduceOutcomeOutOfScope] for actions
|
|
1871
|
+
// that target a different state tree.
|
|
1872
|
+
func ApplyActionToResourceWatch(state *ahptypes.ResourceWatchState, action ahptypes.StateAction) ReduceOutcome {
|
|
1873
|
+
switch action.Value.(type) {
|
|
1874
|
+
case *ahptypes.ResourceWatchChangedAction:
|
|
1875
|
+
return ReduceOutcomeNoOp
|
|
1876
|
+
}
|
|
1877
|
+
return ReduceOutcomeOutOfScope
|
|
1878
|
+
}
|
|
1879
|
+
|
|
1880
|
+
// ApplyActionToAutomation applies an action to automation catalogue state.
|
|
1881
|
+
func ApplyActionToAutomation(state *ahptypes.AutomationState, action ahptypes.StateAction) ReduceOutcome {
|
|
1882
|
+
switch a := action.Value.(type) {
|
|
1883
|
+
case *ahptypes.AutomationCreateRequestedAction, *ahptypes.AutomationUpdateRequestedAction:
|
|
1884
|
+
return ReduceOutcomeNoOp
|
|
1885
|
+
case *ahptypes.AutomationSetAction:
|
|
1886
|
+
for i := range state.Entries {
|
|
1887
|
+
if state.Entries[i].Resource == a.Automation.Resource {
|
|
1888
|
+
state.Entries[i] = a.Automation
|
|
1889
|
+
return ReduceOutcomeApplied
|
|
1890
|
+
}
|
|
1891
|
+
}
|
|
1892
|
+
state.Entries = append(state.Entries, a.Automation)
|
|
1893
|
+
return ReduceOutcomeApplied
|
|
1894
|
+
case *ahptypes.AutomationRemovedAction:
|
|
1895
|
+
for i := range state.Entries {
|
|
1896
|
+
if state.Entries[i].Resource == a.Resource {
|
|
1897
|
+
state.Entries = append(state.Entries[:i], state.Entries[i+1:]...)
|
|
1898
|
+
return ReduceOutcomeApplied
|
|
1899
|
+
}
|
|
1900
|
+
}
|
|
1901
|
+
return ReduceOutcomeNoOp
|
|
1902
|
+
}
|
|
1903
|
+
return ReduceOutcomeOutOfScope
|
|
1904
|
+
}
|
|
1905
|
+
|
|
1906
|
+
// ApplyActionToAutomationRun applies an action to automation-run state.
|
|
1907
|
+
func ApplyActionToAutomationRun(state *ahptypes.AutomationRunState, action ahptypes.StateAction) ReduceOutcome {
|
|
1908
|
+
switch a := action.Value.(type) {
|
|
1909
|
+
case *ahptypes.AutomationRunLifecycleChangedAction:
|
|
1910
|
+
state.Lifecycle = a.Lifecycle
|
|
1911
|
+
return ReduceOutcomeApplied
|
|
1912
|
+
case *ahptypes.AutomationRunSessionSetAction:
|
|
1913
|
+
for _, session := range state.Sessions {
|
|
1914
|
+
if session == a.Session {
|
|
1915
|
+
return ReduceOutcomeNoOp
|
|
1916
|
+
}
|
|
1917
|
+
}
|
|
1918
|
+
state.Sessions = append(state.Sessions, a.Session)
|
|
1919
|
+
return ReduceOutcomeApplied
|
|
1920
|
+
case *ahptypes.AutomationRunSessionRemovedAction:
|
|
1921
|
+
for i, session := range state.Sessions {
|
|
1922
|
+
if session != a.Session {
|
|
1923
|
+
continue
|
|
1924
|
+
}
|
|
1925
|
+
state.Sessions = append(state.Sessions[:i], state.Sessions[i+1:]...)
|
|
1926
|
+
if state.PrimarySession != nil && *state.PrimarySession == a.Session {
|
|
1927
|
+
state.PrimarySession = nil
|
|
1928
|
+
}
|
|
1929
|
+
return ReduceOutcomeApplied
|
|
1930
|
+
}
|
|
1931
|
+
return ReduceOutcomeNoOp
|
|
1932
|
+
case *ahptypes.AutomationRunPrimarySessionChangedAction:
|
|
1933
|
+
state.PrimarySession = a.PrimarySession
|
|
1934
|
+
return ReduceOutcomeApplied
|
|
1935
|
+
case *ahptypes.AutomationRunCancelRequestedAction:
|
|
1936
|
+
return ReduceOutcomeNoOp
|
|
1937
|
+
}
|
|
1938
|
+
return ReduceOutcomeOutOfScope
|
|
1939
|
+
}
|