Skip to content

Commit b694dd3

Browse files
author
SqlRush
committed
Accept remote history event payload aliases
1 parent 82ce4ff commit b694dd3

5 files changed

Lines changed: 136 additions & 0 deletions

File tree

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

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -282,6 +282,7 @@ test/parity/ # golden tests against TS/official behavior
282282
- 本轮补充:remote history `SDKEvent` 解码接受 `eventID``messageId`/`messageID`/`message_id``messageUuid`/`messageUUID`/`message_uuid` 作为事件 ID aliases;只有 `uuid` 时也会作为 event ID fallback,materialize transcript 时能稳定生成 message UUID 与 parent chain(覆盖测试:`TestRemoteHistoryTranscriptMessagesAcceptsEventMessageIDAliases`)。
283283
- 本轮补充:remote history `SDKEvent` timestamp 解码接受 `created`/`createdTime`/`created_time``date_time``eventTime`/`event_time``occurredAt`/`occurred_at` 等相邻字段,并容忍 `timestamp`/`createdAt` 等时间字段为 JSON number,materialize transcript 时继续填充 record 与嵌套 message timestamp(覆盖测试:`TestSDKEventUnmarshalAcceptsTimestampAliases``TestRemoteHistoryTranscriptMessagesAcceptsEventTimestampAliases`)。
284284
- 本轮补充:remote history `SDKEvent.message` 现在接受顶层字符串和 content-block 数组形态,会先包装成 message content 再解码,避免 provider 把 assistant/user event message 直接写成 `"message":"text"``message:[...]` 时 JSON 解码失败(覆盖测试:`TestSDKEventUnmarshalAcceptsScalarMessagePayload``TestRemoteHistoryTranscriptMessagesAcceptsScalarEventMessagePayload`)。
285+
- 本轮补充:remote history `SDKEvent` 的 status/error/result 内容字段现在接受 `statusMessage`/`progress_message``errorMessage`/`failure_reason``resultText`/`outputText`/`completionText` 以及 result object aliases `output`/`response`/`value`/`completion`,仅在对应 canonical event type 下补值,避免和 assistant/user message payload 混淆(覆盖测试:`TestSDKEventUnmarshalAcceptsStatusErrorResultAliases``TestRemoteHistoryEventsAcceptStatusErrorResultAliases`)。
285286
- 本轮补充:memory 层补齐官方 `memoryAge`/freshness note 语义,`ReadDocumentsWithOptions` 可为超过 1 天的 memory 文档前缀 system-reminder,提示模型把 memory 当作 point-in-time observation 并核对当前代码。
286287
- 本轮补充:Read tool 在 metadata 提供 auto-memory 目录时,会为读取旧 auto-memory 文件的 tool result 前缀 freshness system-reminder,和官方 FileReadTool 的 memory freshness prefix 对齐。
287288
- 本轮补充:memory 层补齐官方 `relevant_memories` attachment 基础,包含 stable memory header、system-reminder 渲染、surfaced path/byte 扫描、按 200 行/4096 bytes 读取并附截断提示的 surfacing reader、mark-after-filter 的 duplicate memory attachment 过滤、最后非 meta user prompt/单词 prompt/60KB session cap 的 prefetch gating、多目录结果排除 read-state/surfaced 后取前 5 个候选的选择逻辑,以及 recent successful tools 窗口收集并排除 pending/failed/同名失败工具。

docs/first-second-parity-audit.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -230,6 +230,7 @@ M6 progress now includes:
230230
- `internal/contracts`/`internal/session`: remote-history `SDKEvent` decoding now accepts event/message ID aliases such as `eventID`, `messageId`/`messageID`/`message_id`, and `messageUuid`/`messageUUID`/`message_uuid`, using them as transcript UUID fallbacks during materialization (`TestRemoteHistoryTranscriptMessagesAcceptsEventMessageIDAliases`).
231231
- `internal/contracts`/`internal/session`: remote-history `SDKEvent` timestamp decoding now accepts adjacent aliases such as `created`, `createdTime`/`created_time`, `date_time`, `eventTime`/`event_time`, and `occurredAt`/`occurred_at`, and tolerates JSON number timestamp fields before transcript materialization (`TestSDKEventUnmarshalAcceptsTimestampAliases`, `TestRemoteHistoryTranscriptMessagesAcceptsEventTimestampAliases`).
232232
- `internal/contracts`/`internal/session`: remote-history `SDKEvent.message` now accepts top-level string and content-block array payloads, wrapping them as message content before event decoding so provider-shaped assistant/user events still materialize into transcript messages (`TestSDKEventUnmarshalAcceptsScalarMessagePayload`, `TestRemoteHistoryTranscriptMessagesAcceptsScalarEventMessagePayload`).
233+
- `internal/contracts`/`internal/session`: remote-history `SDKEvent` status/error/result payloads now accept adjacent field aliases such as `statusMessage`, `progress_message`, `errorMessage`, `failure_reason`, `resultText`, `outputText`, and result object aliases including `output`, `response`, `value`, and `completion`, scoped to the matching canonical event type (`TestSDKEventUnmarshalAcceptsStatusErrorResultAliases`, `TestRemoteHistoryEventsAcceptStatusErrorResultAliases`).
233234
- `internal/contracts`/`internal/session`: remote-history `SDKEvent` type canonicalization now also accepts adjacent stream/provider aliases such as `assistant_delta`, `humanMessage`, `finalResult`, `response.completed`, `failureEvent`, and `statusMessage`, keeping transcript materialization stable across SDK event spellings.
234235
- `internal/memory`: memory age/freshness helpers now match official stale-memory guidance, and document loading can prefix old memory files with a system-reminder that they are point-in-time observations.
235236
- `internal/memory`: relevant-memory attachment primitives now match the official `relevant_memories` shape for stable headers, system-reminder rendering, surfaced path/byte scanning, 200-line/4096-byte surfacing reads with truncation notices, mark-after-filter duplicate attachment handling, last-non-meta-user/single-word/session-byte-cap prefetch gating, top-5 candidate filtering after read-state/surfaced de-dup, and recent successful tools collection excluding pending/failed/same-name-failed tools.

internal/contracts/messages_test.go

Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ package contracts
22

33
import (
44
"encoding/json"
5+
"reflect"
56
"testing"
67
)
78

@@ -250,6 +251,53 @@ func TestSDKEventUnmarshalAcceptsScalarMessagePayload(t *testing.T) {
250251
}
251252
}
252253

254+
func TestSDKEventUnmarshalAcceptsStatusErrorResultAliases(t *testing.T) {
255+
for name, tc := range map[string]struct {
256+
raw string
257+
wantStatus string
258+
wantError string
259+
wantResult any
260+
}{
261+
"status message": {
262+
raw: `{"type":"status","statusMessage":"queued"}`,
263+
wantStatus: "queued",
264+
},
265+
"progress message": {
266+
raw: `{"eventType":"progress","progress_message":"working"}`,
267+
wantStatus: "working",
268+
},
269+
"error message": {
270+
raw: `{"type":"error","errorMessage":"boom"}`,
271+
wantError: "boom",
272+
},
273+
"failure reason": {
274+
raw: `{"eventType":"failureEvent","failure_reason":"denied"}`,
275+
wantError: "denied",
276+
},
277+
"result text": {
278+
raw: `{"type":"result","outputText":"done"}`,
279+
wantResult: "done",
280+
},
281+
"result object": {
282+
raw: `{"eventType":"finalResult","response":{"ok":true}}`,
283+
wantResult: map[string]any{"ok": true},
284+
},
285+
} {
286+
t.Run(name, func(t *testing.T) {
287+
var event SDKEvent
288+
if err := json.Unmarshal([]byte(tc.raw), &event); err != nil {
289+
t.Fatal(err)
290+
}
291+
if event.Status != tc.wantStatus || event.Error != tc.wantError {
292+
t.Fatalf("event = %#v", event)
293+
}
294+
if tc.wantResult != nil && !reflect.DeepEqual(event.Result, tc.wantResult) {
295+
t.Fatalf("result = %#v, want %#v", event.Result, tc.wantResult)
296+
}
297+
})
298+
}
299+
}
300+
253301
func TestContentBlockUnmarshalAcceptsTextAliases(t *testing.T) {
254302
for name, raw := range map[string]string{
255303
"body": `{"type":"text","body":"from body"}`,

internal/contracts/session.go

Lines changed: 69 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -185,6 +185,31 @@ func (e *SDKEvent) UnmarshalJSON(data []byte) error {
185185
EventTimeSnake string `json:"event_time"`
186186
OccurredAt string `json:"occurredAt"`
187187
OccurredAtSnake string `json:"occurred_at"`
188+
StatusMessage string `json:"statusMessage"`
189+
StatusMessageSnake string `json:"status_message"`
190+
StatusText string `json:"statusText"`
191+
StatusTextSnake string `json:"status_text"`
192+
State string `json:"state"`
193+
Phase string `json:"phase"`
194+
ProgressMessage string `json:"progressMessage"`
195+
ProgressMessageSnake string `json:"progress_message"`
196+
ErrorMessage string `json:"errorMessage"`
197+
ErrorMessageSnake string `json:"error_message"`
198+
ErrorText string `json:"errorText"`
199+
ErrorTextSnake string `json:"error_text"`
200+
FailureReason string `json:"failureReason"`
201+
FailureReasonSnake string `json:"failure_reason"`
202+
Reason string `json:"reason"`
203+
ResultText string `json:"resultText"`
204+
ResultTextSnake string `json:"result_text"`
205+
OutputText string `json:"outputText"`
206+
OutputTextSnake string `json:"output_text"`
207+
CompletionText string `json:"completionText"`
208+
CompletionTextSnake string `json:"completion_text"`
209+
Output json.RawMessage `json:"output"`
210+
Response json.RawMessage `json:"response"`
211+
Value json.RawMessage `json:"value"`
212+
Completion json.RawMessage `json:"completion"`
188213
MessagePayload json.RawMessage `json:"message_payload"`
189214
MessagePayloadCamel json.RawMessage `json:"messagePayload"`
190215
SerializedMessage json.RawMessage `json:"serialized_message"`
@@ -260,6 +285,36 @@ func (e *SDKEvent) UnmarshalJSON(data []byte) error {
260285
aux.ParentMessageIDSnake,
261286
)
262287
}
288+
if e.Type == SDKEventStatus && e.Status == "" {
289+
e.Status = firstSDKEventString(
290+
aux.StatusMessage,
291+
aux.StatusMessageSnake,
292+
aux.StatusText,
293+
aux.StatusTextSnake,
294+
aux.ProgressMessage,
295+
aux.ProgressMessageSnake,
296+
aux.State,
297+
aux.Phase,
298+
)
299+
}
300+
if e.Type == SDKEventError && e.Error == "" {
301+
e.Error = firstSDKEventString(
302+
aux.ErrorMessage,
303+
aux.ErrorMessageSnake,
304+
aux.ErrorText,
305+
aux.ErrorTextSnake,
306+
aux.FailureReason,
307+
aux.FailureReasonSnake,
308+
aux.Reason,
309+
)
310+
}
311+
if e.Type == SDKEventResult && e.Result == nil {
312+
if result := firstSDKEventString(aux.ResultText, aux.ResultTextSnake, aux.OutputText, aux.OutputTextSnake, aux.CompletionText, aux.CompletionTextSnake); result != "" {
313+
e.Result = result
314+
} else {
315+
e.Result = firstSDKEventRawAny(aux.Output, aux.Response, aux.Value, aux.Completion)
316+
}
317+
}
263318
if e.Message == nil {
264319
e.Message = firstSDKEventMessage(e.Type,
265320
aux.MessagePayload,
@@ -394,6 +449,20 @@ func firstSDKEventString(values ...string) string {
394449
return ""
395450
}
396451

452+
func firstSDKEventRawAny(values ...json.RawMessage) any {
453+
for _, value := range values {
454+
value = bytes.TrimSpace(value)
455+
if len(value) == 0 || bytes.Equal(value, []byte("null")) {
456+
continue
457+
}
458+
var out any
459+
if err := json.Unmarshal(value, &out); err == nil {
460+
return out
461+
}
462+
}
463+
return nil
464+
}
465+
397466
func firstSDKEventID(values ...ID) ID {
398467
for _, value := range values {
399468
if value != "" {

internal/session/remote_history_test.go

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2025,6 +2025,23 @@ func TestRemoteHistoryTranscriptMessagesAcceptsEventTypeAliases(t *testing.T) {
20252025
}
20262026
}
20272027

2028+
func TestRemoteHistoryEventsAcceptStatusErrorResultAliases(t *testing.T) {
2029+
var response sessionEventsResponse
2030+
if err := json.Unmarshal([]byte(`{"events":[
2031+
{"type":"status","eventId":"evt_status_alias","sessionID":"s_alias","statusMessage":"queued"},
2032+
{"type":"error","eventId":"evt_error_alias","sessionID":"s_alias","errorMessage":"boom"},
2033+
{"type":"result","eventId":"evt_result_alias","sessionID":"s_alias","outputText":"done"}
2034+
]}`), &response); err != nil {
2035+
t.Fatal(err)
2036+
}
2037+
if len(response.Events) != 3 {
2038+
t.Fatalf("events = %#v", response.Events)
2039+
}
2040+
if response.Events[0].Status != "queued" || response.Events[1].Error != "boom" || response.Events[2].Result != "done" {
2041+
t.Fatalf("event aliases = %#v", response.Events)
2042+
}
2043+
}
2044+
20282045
func TestRemoteHistoryTranscriptMessagesAcceptsNestedEventPayloadWrappers(t *testing.T) {
20292046
var response sessionEventsResponse
20302047
if err := json.Unmarshal([]byte(`{"events":[

0 commit comments

Comments
 (0)