Skip to content

Commit 5e804f4

Browse files
author
SqlRush
committed
Accept remote history starting-after cursors
1 parent cb10c37 commit 5e804f4

5 files changed

Lines changed: 53 additions & 0 deletions

File tree

docs/cc-100-roadmap.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -122,6 +122,8 @@ M6 补充:remote history response parser 现在会解包 `payload`/`response`/
122122

123123
M6 补充:remote history response parser 现在也接受 provider-style `choices`/`outputs`/`candidates`/`generations` wrapper,可从 `message.content`、content-block array 和 `content.parts[].text` 中恢复 event page JSON(包括 fenced `json` code block),并保留 pagination 继续驱动 `before_id` 续抓。
124124

125+
M6 补充:remote history pagination 现在接受 `starting_after`/`startingAfter`/`after*` cursor aliases,page 字段和 link URL query 都可驱动下一页 `before_id` 续抓。
126+
125127
M6 补充:remote history response parser 现在接受 JSON:API `included` collection,会过滤非事件资源,并递归解包 `resource`/`attributes`/`properties` 后用外层 resource id 作为事件 ID fallback。
126128

127129
M6 补充:remote history `data`/`payload`/`response`/`result`/`body` 等 event-list 字段现在也接受单个 SDK event 对象,避免非数组单事件页被当作普通 wrapper 后丢失。

docs/claude-code-go-rewrite-plan.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -215,6 +215,7 @@ test/parity/ # golden tests against TS/official behavior
215215
- 本轮补充:remote history response parser 现在也接受 JSON:API `included` collection,会过滤非事件资源,并递归解包 `resource`/`attributes`/`properties` 后保留外层 resource id 作为事件 ID fallback。
216216
- 本轮补充:remote history response parser 会解包 `payload`/`response`/`result`/`body` 等通用响应外壳,外壳内的 event list、pagination、links 会继续递归解析。
217217
- 本轮补充:remote history response parser 现在也接受 provider-style `choices`/`outputs`/`candidates`/`generations` wrapper 以及顶层 `message`/`content`/`text` envelope,可从 `message.content`、content-block array 和 `content.parts[].text` 中恢复 event page JSON(包括 fenced `json` code block),并保留 pagination 继续驱动 `before_id` 续抓。
218+
- 本轮补充:remote history pagination 现在接受 `starting_after`/`startingAfter`/`after*` cursor aliases,page 字段和 link URL query 都可驱动下一页 `before_id` 续抓。
218219
- 本轮补充:remote history `SDKEvent` 本体接受 `eventType`/`event_type`/`role` 类型别名、`createdAt`/`created_at` 时间戳别名,以及 `payload`/`data`/`body`/`metadata`/`meta`/`attributes`/`properties`/`serializedMessage` message payload 别名;payload 只有 `role`/`content` 时也能 materialize 成 transcript message。
219220
- 本轮补充:transcript resume 的嵌套 content block 接受 `toolUseId`/`toolUseID``isError``cacheControl``cacheReference` 字段别名,并保留 cache edit 的 `cacheReference`
220221
- 本轮补充:transcript resume 的 nested content block `id`/`tool_use_id`/`toolUseId` 现在接受 JSON number,并保留为字符串 tool-use ID。

docs/first-second-parity-audit.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -145,6 +145,7 @@ M6 progress now includes:
145145
- `internal/memory`: session-memory recall and relevant-memory model selectors now also unwrap provider-style `choices`, `outputs`, `candidates`, and `generations` responses plus top-level `message`/`content`/`text` envelopes, recovering JSON selection payloads from nested `message.content`, content-block arrays, `content.parts[].text`, and fenced `json` code blocks.
146146
- `internal/memory`: model-backed memory fact extraction now also unwraps provider-style `choices`, `outputs`, `candidates`, and `generations` responses plus top-level `message`/`content`/`text` envelopes, recovering JSON facts payloads from nested `message.content`, content-block arrays, `content.parts[].text`, and fenced `json` code blocks.
147147
- `internal/session`: remote-history response parsing now also unwraps provider-style `choices`, `outputs`, `candidates`, and `generations` responses plus top-level `message`/`content`/`text` envelopes, recovering event page JSON plus pagination from nested `message.content`, content-block arrays, `content.parts[].text`, and fenced `json` code blocks.
148+
- `internal/session`: remote-history pagination now accepts `starting_after`/`startingAfter`/`after*` cursor aliases from page fields and link URL query parameters when deriving the next `before_id`.
148149
- `internal/session`: project session listing and pagination, prompt-history lock/buffered flush/field aliases, lightweight transcript index/title/text-preview inference, line-offset transcript indexing/window/byte-budget-window/parent-chain/resume/tail/byte-budget-tail loading, AI-title/last-prompt/task/agent/PR/worktree transcript metadata loading and type/field aliases, transcript message/session UUID field aliases, tombstone metadata loading with target/session/reason aliases and delete/relink replay, agent-scoped content replacement metadata/record field-alias loading, session-scoped metadata reappend including AI-title/last-prompt/task-summary, streaming transcript search snippets for resume/search UI, official `subagents/agent-*.jsonl` transcript layout with legacy sidechain listing, agent metadata sidecar read/write/field aliases, sidechain runtime start/append/finish/cancel/fail summary bridging plus parent-chain append/finish, sidechain manager orchestration for spawn/append/finish/cancel/fail/list/resume, sidechain state/list/resume support with content-field aliases, sidechain resume context construction, sidechain conversation and agent-scoped content-replacement reconstruction, transcript tail/window loading and byte-budget tail loading for bounded-memory resume/UI paths, lightweight transcript metadata loading, remote-history token refresh retry, page-field/event-list/records/entries/last-id/cursor/event-id/has-next aliases plus wrapped-data/links/paging/bare-array/keyed-event-map responses, before_id resume, fallback field fill, and duplicate-aware parent linking during transcript materialization.
149150
- `internal/session`: transcript full loader and line-offset index now accept top-level record UUID aliases such as `messageUuid`, `messageUUID`, `message_id`, `messageId`, and `id`, plus parent message UUID/ID aliases for progress bridge and indexed resume paths.
150151
- `internal/session`: indexed resume chains now distinguish budget-truncated parents from genuinely missing parents, exposing both markers while preserving bounded transcript reads.

internal/session/remote_history.go

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -336,6 +336,12 @@ func (r *sessionEventsResponse) mergeScalarFields(raw map[string]json.RawMessage
336336
{name: "beforeCursor", target: &r.BeforeCursorCamel},
337337
{name: "ending_before", target: &r.BeforeCursor},
338338
{name: "endingBefore", target: &r.BeforeCursorCamel},
339+
{name: "starting_after", target: &r.NextCursor},
340+
{name: "startingAfter", target: &r.NextCursorCamel},
341+
{name: "after", target: &r.NextCursor},
342+
{name: "after_id", target: &r.NextCursor},
343+
{name: "afterId", target: &r.NextCursorCamel},
344+
{name: "afterID", target: &r.NextCursorCamel},
339345
{name: "until", target: &r.BeforeCursor},
340346
{name: "until_id", target: &r.BeforeCursor},
341347
{name: "untilId", target: &r.BeforeCursorCamel},
@@ -790,6 +796,7 @@ func remoteHistoryEventMapReservedKey(key string) bool {
790796
"last_id", "lastId", "start_cursor", "startCursor", "end_cursor", "endCursor",
791797
"previous_cursor", "previousCursor", "prev_cursor", "prevCursor", "before_cursor", "beforeCursor",
792798
"ending_before", "endingBefore", "until", "until_id", "untilId", "before", "beforeID",
799+
"starting_after", "startingAfter", "after", "after_id", "afterId", "afterID",
793800
"older_cursor", "olderCursor":
794801
return true
795802
default:
@@ -989,6 +996,8 @@ func remoteHistoryCursorField(raw map[string]json.RawMessage) string {
989996
remoteHistoryStringField(raw, "prev_cursor", "prevCursor"),
990997
remoteHistoryStringField(raw, "before_cursor", "beforeCursor"),
991998
remoteHistoryStringField(raw, "ending_before", "endingBefore"),
999+
remoteHistoryStringField(raw, "starting_after", "startingAfter"),
1000+
remoteHistoryStringField(raw, "after", "after_id", "afterId", "afterID"),
9921001
remoteHistoryStringField(raw, "until", "until_id", "untilId"),
9931002
remoteHistoryStringField(raw, "older_cursor", "olderCursor"),
9941003
remoteHistoryStringField(raw, "before", "beforeID"),
@@ -1498,6 +1507,12 @@ func remoteHistoryLinkCursor(link string) string {
14981507
query.Get("beforeCursor"),
14991508
query.Get("ending_before"),
15001509
query.Get("endingBefore"),
1510+
query.Get("starting_after"),
1511+
query.Get("startingAfter"),
1512+
query.Get("after"),
1513+
query.Get("after_id"),
1514+
query.Get("afterId"),
1515+
query.Get("afterID"),
15011516
query.Get("until"),
15021517
query.Get("until_id"),
15031518
query.Get("untilId"),

internal/session/remote_history_test.go

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -264,6 +264,40 @@ func TestFetchRemoteHistoryAcceptsCursorPageFields(t *testing.T) {
264264
}
265265
}
266266

267+
func TestFetchRemoteHistoryAcceptsStartingAfterCursorAliases(t *testing.T) {
268+
var seen []url.Values
269+
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
270+
seen = append(seen, r.URL.Query())
271+
w.Header().Set("Content-Type", "application/json")
272+
switch r.URL.Query().Get("before_id") {
273+
case "":
274+
_, _ = w.Write([]byte(`{"data":[{"type":"status","session_id":"s","status":"latest"}],"has_more":true,"starting_after":"evt_starting"}`))
275+
case "evt_starting":
276+
_, _ = w.Write([]byte(`{"data":[{"type":"status","session_id":"s","status":"middle"}],"links":{"next":"/v1/sessions/s/events?startingAfter=evt_link"}}`))
277+
case "evt_link":
278+
_, _ = w.Write([]byte(`{"data":[{"type":"status","session_id":"s","status":"oldest"}],"has_more":false}`))
279+
default:
280+
t.Fatalf("unexpected before_id = %q", r.URL.Query().Get("before_id"))
281+
}
282+
}))
283+
defer server.Close()
284+
285+
authCtx := NewRemoteHistoryAuthContext("s", "token", "", auth.OAuthConfig{BaseAPIURL: server.URL})
286+
events, err := FetchRemoteHistory(context.Background(), server.Client(), authCtx, RemoteHistoryFetchOptions{Limit: 3})
287+
if err != nil {
288+
t.Fatal(err)
289+
}
290+
if !events.Complete || events.Pages != 3 || events.NextBeforeID != "" {
291+
t.Fatalf("events = %#v", events)
292+
}
293+
if len(events.Events) != 3 || events.Events[0].Status != "latest" || events.Events[1].Status != "middle" || events.Events[2].Status != "oldest" {
294+
t.Fatalf("event order = %#v", events.Events)
295+
}
296+
if len(seen) != 3 || seen[1].Get("before_id") != "evt_starting" || seen[2].Get("before_id") != "evt_link" {
297+
t.Fatalf("queries = %#v", seen)
298+
}
299+
}
300+
267301
func TestFetchRemoteHistoryAcceptsNumericCursorFields(t *testing.T) {
268302
var seen []url.Values
269303
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {

0 commit comments

Comments
 (0)