Skip to content

Commit 4c709a4

Browse files
author
SqlRush
committed
Accept nested message id aliases
1 parent edcd0df commit 4c709a4

5 files changed

Lines changed: 41 additions & 3 deletions

File tree

docs/cc-100-roadmap.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,8 @@ M6 补充:transcript resume 的嵌套 content block 现在接受 `toolUseId`/`
3636

3737
M6 补充:嵌套 contract message 现在接受 `parentUUID``parentId`/`parentID`/`parent_id``parentMessageId`/`parentMessageID`/`parent_message_id` 和 parent-message UUID 别名,transcript/remote history payload 自带 parent alias 时不会丢失嵌套 parent。
3838

39+
M6 补充:嵌套 contract message 现在接受 `messageId`/`messageID`/`message_id``messageUuid`/`messageUUID`/`message_uuid` 作为自身 ID/UUID 别名,indexed resume 会保留 payload 自带的 nested message id。
40+
3941
M6 补充:remote history GraphQL/connection 分页现在接受 `hasPrevious`/`hasPreviousPage``hasOlder`/`more` 继续分页标记,以及 `previousCursor`/`prevCursor`/`beforeCursor`/`olderCursor` 等 before-id cursor 别名,避免只返回第一页历史。
4042

4143
M6 补充:remote history response parser 现在会递归解包 `data.session.events``data.projectSession.eventConnection``conversation``remoteHistory` 等 GraphQL/session wrapper,继续复用 `nodes`/`edges[].node``pageInfo` pagination 解析。

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

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -197,6 +197,7 @@ test/parity/ # golden tests against TS/official behavior
197197
- 本轮补充:transcript metadata loader 接受 `sessionID`/`session` 作为 session-scoped metadata ID 别名,并容忍 `prNumber``timeSavedMs``lastSpawnTokens` 等计数字段使用数字字符串。
198198
- 本轮补充:transcript message 和嵌套 contract message 接受顶层 `sessionID` 作为 session id 别名,`LoadTranscript``LoadTranscriptIndex` 和 indexed resume 会保留该 session id(覆盖测试:`TestLoadTranscriptAcceptsSessionIDUpperAlias`)。
199199
- 本轮补充:嵌套 contract message 接受 `parentUUID``parentId`/`parentID`/`parent_id``parentMessageId`/`parentMessageID`/`parent_message_id` 和 parent-message UUID 别名,transcript/remote history payload 自带 parent alias 时不会丢失嵌套 parent。
200+
- 本轮补充:嵌套 contract message 接受 `messageId`/`messageID`/`message_id``messageUuid`/`messageUUID`/`message_uuid` 作为自身 ID/UUID 别名,indexed resume 会保留 payload 自带的 nested message id。
200201
- 本轮补充:remote history `SDKEvent` 解码接受顶层 `sessionID` 作为事件 session id 别名,materialize transcript 时会填充 record 与嵌套 message 的 session id(覆盖测试:`TestRemoteHistoryTranscriptMessagesAcceptsSessionIDUpperAlias`)。
201202
- 本轮补充:remote history `SDKEvent` 解码接受 `parentUUID``parentId`/`parentID`/`parent_id``parentMessageId`/`parentMessageID`/`parent_message_id` parent aliases,materialize transcript 时会保留 parent chain(覆盖测试:`TestRemoteHistoryTranscriptMessagesAcceptsParentIDAliases`)。
202203
- 本轮补充:memory 层补齐官方 `memoryAge`/freshness note 语义,`ReadDocumentsWithOptions` 可为超过 1 天的 memory 文档前缀 system-reminder,提示模型把 memory 当作 point-in-time observation 并核对当前代码。

docs/first-second-parity-audit.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -131,6 +131,7 @@ M6 progress now includes:
131131
- `internal/session`: transcript full loader and line-offset index now accept entry type aliases such as `role`, `entry_type`, and `messageType`, plus `createdAt`/`created_at` timestamp aliases.
132132
- `internal/contracts`/`internal/session`: transcript resume now accepts nested content block aliases for `toolUseId`/`toolUseID`, `isError`, `cacheControl`, `cacheReference`, and cache edit `cacheReference`.
133133
- `internal/contracts`/`internal/session`: nested contract messages now accept parent aliases such as `parentUUID`, `parentId`/`parentID`/`parent_id`, `parentMessageId`/`parentMessageID`/`parent_message_id`, and parent-message UUID aliases, preserving payload-supplied parents in transcript and remote-history resume paths.
134+
- `internal/contracts`/`internal/session`: nested contract messages now accept `messageId`/`messageID`/`message_id` and `messageUuid`/`messageUUID`/`message_uuid` as their own ID/UUID aliases, preserving payload-supplied nested message IDs in indexed resume output.
134135
- `internal/session`: remote-history parsing now also accepts connection-style `history`/`messages` wrappers with `nodes`/`edges[].node` event lists and `pageInfo`/`page_info` `hasNextPage`/`endCursor`/`startCursor` pagination aliases.
135136
- `internal/session`: remote-history connection edges now use `edges[].cursor` as the event cursor when the nested node lacks an event ID, preserving pagination even when pageInfo omits a cursor.
136137
- `internal/session`: remote-history parsing now also accepts `eventList`/`event_list`, `sessionEvents`/`session_events`, and connection aliases such as `connection`, `eventConnection`, and `sessionEventsConnection`.

internal/contracts/messages.go

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -164,6 +164,12 @@ func (m *Message) UnmarshalJSON(data []byte) error {
164164
type MessageJSON Message
165165
var aux struct {
166166
*MessageJSON
167+
MessageID ID `json:"messageId"`
168+
MessageIDUpper ID `json:"messageID"`
169+
MessageIDSnake ID `json:"message_id"`
170+
MessageUUID ID `json:"messageUuid"`
171+
MessageUUIDUpper ID `json:"messageUUID"`
172+
MessageUUIDSnake ID `json:"message_uuid"`
167173
ParentUUIDUpper *ID `json:"parentUUID"`
168174
ParentUUIDSnake *ID `json:"parent_uuid"`
169175
ParentID *ID `json:"parentId"`
@@ -188,6 +194,19 @@ func (m *Message) UnmarshalJSON(data []byte) error {
188194
return err
189195
}
190196
*m = Message(base)
197+
if m.ID == "" {
198+
m.ID = string(firstMessageID(aux.MessageID, aux.MessageIDUpper, aux.MessageIDSnake))
199+
}
200+
if m.UUID == "" {
201+
m.UUID = firstMessageID(
202+
aux.MessageUUID,
203+
aux.MessageUUIDUpper,
204+
aux.MessageUUIDSnake,
205+
aux.MessageID,
206+
aux.MessageIDUpper,
207+
aux.MessageIDSnake,
208+
)
209+
}
191210
if m.ParentUUID == nil {
192211
m.ParentUUID = firstMessageIDPtr(
193212
aux.ParentUUIDUpper,
@@ -224,6 +243,15 @@ func (m *Message) UnmarshalJSON(data []byte) error {
224243
return nil
225244
}
226245

246+
func firstMessageID(values ...ID) ID {
247+
for _, value := range values {
248+
if value != "" {
249+
return value
250+
}
251+
}
252+
return ""
253+
}
254+
227255
func firstMessageIDPtr(values ...*ID) *ID {
228256
for _, value := range values {
229257
if value != nil && *value != "" {

internal/session/transcript_test.go

Lines changed: 9 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -157,9 +157,9 @@ func TestLoadTranscriptAcceptsSerializedMessageMetadata(t *testing.T) {
157157

158158
func TestLoadTranscriptAcceptsMessageUUIDFieldAliases(t *testing.T) {
159159
path := writeTranscript(t, []string{
160-
`{"type":"user","messageUuid":"u1","session_id":"sess_1","timestamp":"2026-01-01T00:00:00Z","message":{"type":"user","session_id":"sess_1","content":[{"type":"text","text":"hi"}]}}`,
160+
`{"type":"user","messageUuid":"u1","session_id":"sess_1","timestamp":"2026-01-01T00:00:00Z","message":{"type":"user","messageId":"nested_u1","session_id":"sess_1","content":[{"type":"text","text":"hi"}]}}`,
161161
`{"type":"progress","messageId":"p1","parentMessageUuid":"u1"}`,
162-
`{"type":"assistant","message_id":"a1","parentMessageID":"p1","session_id":"sess_1","timestamp":"2026-01-01T00:00:01Z","message":{"type":"assistant","session_id":"sess_1","content":[{"type":"text","text":"done"}]}}`,
162+
`{"type":"assistant","message_id":"a1","parentMessageID":"p1","session_id":"sess_1","timestamp":"2026-01-01T00:00:01Z","message":{"type":"assistant","messageUUID":"nested_a1","session_id":"sess_1","content":[{"type":"text","text":"done"}]}}`,
163163
})
164164

165165
transcript, err := LoadTranscript(path)
@@ -170,6 +170,12 @@ func TestLoadTranscriptAcceptsMessageUUIDFieldAliases(t *testing.T) {
170170
if assistant == nil || assistant.ParentUUID == nil || *assistant.ParentUUID != "u1" {
171171
t.Fatalf("aliased assistant parent = %#v", assistant)
172172
}
173+
if user := transcript.Messages["u1"]; user == nil || user.Message == nil || user.Message.ID != "nested_u1" || user.Message.UUID != "nested_u1" {
174+
t.Fatalf("aliased nested user id = %#v", user)
175+
}
176+
if assistant.Message == nil || assistant.Message.UUID != "nested_a1" {
177+
t.Fatalf("aliased nested assistant id = %#v", assistant.Message)
178+
}
173179
chain := transcript.BuildConversationChain("a1")
174180
if got := chainIDs(chain); strings.Join(got, ",") != "u1,a1" {
175181
t.Fatalf("aliased uuid chain = %#v", got)
@@ -186,7 +192,7 @@ func TestLoadTranscriptAcceptsMessageUUIDFieldAliases(t *testing.T) {
186192
if err != nil {
187193
t.Fatal(err)
188194
}
189-
if !resume.Found || strings.Join(chainIDs(resume.Chain), ",") != "u1,a1" || resume.Messages[1].UUID != "a1" {
195+
if !resume.Found || strings.Join(chainIDs(resume.Chain), ",") != "u1,a1" || resume.Messages[0].UUID != "nested_u1" || resume.Messages[1].UUID != "nested_a1" {
190196
t.Fatalf("aliased uuid indexed resume = %#v chain=%#v", resume, chainIDs(resume.Chain))
191197
}
192198
}

0 commit comments

Comments
 (0)