Skip to content

Commit eb5b50b

Browse files
author
SqlRush
committed
Normalize remote history cursor fields
1 parent c3ec5b2 commit eb5b50b

3 files changed

Lines changed: 36 additions & 8 deletions

File tree

docs/first-second-parity-audit.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -198,7 +198,7 @@ M6 progress now includes:
198198
- `internal/session`: remote-history response parsing now recursively unwraps GraphQL/session/HAL containers such as `data.session.events`, `data.projectSession.eventConnection`, `conversation`, `remoteHistory`, and `_embedded` before applying `nodes`/`edges[].node` event-list and `pageInfo` pagination parsing.
199199
- `internal/session`: remote-history response parsing now also unwraps GraphQL `viewer` and `node` containers, covering shapes such as `data.viewer.session.events` and `data.node.eventConnection` while preserving the same cursor pagination behavior.
200200
- `internal/session`: remote-history link pagination now accepts `links`/`_links` `next`/`previous`/`prev`/`older` string URLs or `{href,url,uri,link}` objects and extracts before/cursor query parameters for continuation.
201-
- `internal/session`: remote-history link pagination now also accepts link objects and link-array items that carry cursor fields directly, such as `cursor`, `beforeId`, and `lastEvaluatedKey`, instead of requiring href/url links.
201+
- `internal/session`: remote-history link pagination now also accepts link objects and link-array items that carry cursor fields directly, such as `cursor`, `beforeId`, and `lastEvaluatedKey`, including `_`/`-` field-name variants, instead of requiring href/url links.
202202
- `internal/session`: remote-history event selection now prefers materialized event payloads over JSON:API relationship identifier lists, so `relationships.events.data` `{type,id}` links can resolve through top-level `included` resources instead of yielding empty SDK events.
203203
- `internal/session`: remote-history event-list arrays now also flatten JSON:API/session resource wrappers that contain nested relationship event pages, merge their nested pagination fields, and skip clearly non-event resources such as tool/task records instead of materializing empty SDK events.
204204
- `internal/session`: remote-history link pagination now also accepts RFC/JSON:API-style `links` arrays with relation values from `rel`, `relation`, `name`, `type`, `kind`, or `label`, such as `previous`, `prev`, `older`, and `next`, including `_`/`-` page/link/cursor suffix variants.

internal/session/remote_history.go

Lines changed: 33 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1031,20 +1031,48 @@ func firstObjectRawField(raw map[string]json.RawMessage, names ...string) json.R
10311031
func remoteHistoryStringField(raw map[string]json.RawMessage, names ...string) string {
10321032
for _, name := range names {
10331033
value, ok := raw[name]
1034-
if !ok || len(bytes.TrimSpace(value)) == 0 || bytes.Equal(bytes.TrimSpace(value), []byte("null")) {
1034+
if !ok {
10351035
continue
10361036
}
1037-
var text string
1038-
if err := json.Unmarshal(value, &text); err == nil {
1037+
if text, ok := remoteHistoryStringRawValue(value); ok {
10391038
return text
10401039
}
1041-
if text, ok := jsonNumberString(value); ok {
1042-
return text
1040+
}
1041+
for _, name := range names {
1042+
normalizedName := remoteHistoryNormalizedFieldName(name)
1043+
for rawName, value := range raw {
1044+
if remoteHistoryNormalizedFieldName(rawName) != normalizedName {
1045+
continue
1046+
}
1047+
if text, ok := remoteHistoryStringRawValue(value); ok {
1048+
return text
1049+
}
10431050
}
10441051
}
10451052
return ""
10461053
}
10471054

1055+
func remoteHistoryStringRawValue(value json.RawMessage) (string, bool) {
1056+
if len(bytes.TrimSpace(value)) == 0 || bytes.Equal(bytes.TrimSpace(value), []byte("null")) {
1057+
return "", false
1058+
}
1059+
var text string
1060+
if err := json.Unmarshal(value, &text); err == nil {
1061+
return text, true
1062+
}
1063+
if text, ok := jsonNumberString(value); ok {
1064+
return text, true
1065+
}
1066+
return "", false
1067+
}
1068+
1069+
func remoteHistoryNormalizedFieldName(name string) string {
1070+
name = strings.ToLower(strings.TrimSpace(name))
1071+
name = strings.ReplaceAll(name, "_", "")
1072+
name = strings.ReplaceAll(name, "-", "")
1073+
return name
1074+
}
1075+
10481076
func remoteHistoryCursorField(raw map[string]json.RawMessage) string {
10491077
return firstNonEmpty(
10501078
remoteHistoryStringField(raw, "before_id", "beforeId"),

internal/session/remote_history_test.go

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -713,9 +713,9 @@ func TestFetchRemoteHistoryAcceptsLinkObjectCursorFields(t *testing.T) {
713713
w.Header().Set("Content-Type", "application/json")
714714
switch r.URL.Query().Get("before_id") {
715715
case "":
716-
_, _ = w.Write([]byte(`{"data":[{"type":"status","session_id":"s","status":"latest"}],"links":{"older":{"lastEvaluatedKey":"evt_object"}}}`))
716+
_, _ = w.Write([]byte(`{"data":[{"type":"status","session_id":"s","status":"latest"}],"links":{"older":{"last-evaluated-key":"evt_object"}}}`))
717717
case "evt_object":
718-
_, _ = w.Write([]byte(`{"data":[{"type":"status","session_id":"s","status":"older"}],"links":[{"rel":"previous","cursor":"evt_array"}]}`))
718+
_, _ = w.Write([]byte(`{"data":[{"type":"status","session_id":"s","status":"older"}],"links":[{"rel":"previous","before-id":"evt_array"}]}`))
719719
case "evt_array":
720720
_, _ = w.Write([]byte(`{"data":[{"type":"status","session_id":"s","status":"oldest"}],"links":{"next":null}}`))
721721
default:

0 commit comments

Comments
 (0)