f99010fae1
CI / lint (push) Failing after 1s
CI / frontend (push) Failing after 1s
CI / scripts (push) Failing after 1s
CI / Go Test (ubuntu-latest) (push) Failing after 0s
CI / frontend-node-25 (push) Failing after 1s
CI / docs (push) Failing after 0s
CI / coverage (push) Failing after 0s
CI / e2e (push) Failing after 0s
Docker / build-and-push (push) Failing after 1s
CI / integration (push) Failing after 4m43s
CI / Go Test (windows-latest) (push) Has been cancelled
CI / Desktop Unit Tests (Windows) (push) Has been cancelled
Desktop Artifacts / Desktop Build (Linux (arm64)) (push) Has been cancelled
Desktop Artifacts / Desktop Build (Linux) (push) Has been cancelled
Desktop Artifacts / Desktop Build (Windows) (push) Has been cancelled
Desktop Artifacts (macOS) / Desktop Build (macOS (aarch64)) (push) Has been cancelled
Desktop Artifacts (macOS) / Desktop Build (macOS (x86_64)) (push) Has been cancelled
650 lines
15 KiB
Go
650 lines
15 KiB
Go
// Package testjsonl provides shared JSONL fixture builders for
|
|
// Claude and Codex session test data. Used by both parser and
|
|
// sync test packages.
|
|
package testjsonl
|
|
|
|
import (
|
|
"encoding/json"
|
|
"strings"
|
|
)
|
|
|
|
// ClaudeUserJSON returns a Claude user message as a JSON string.
|
|
func ClaudeUserJSON(
|
|
content, timestamp string, cwd ...string,
|
|
) string {
|
|
m := map[string]any{
|
|
"type": "user",
|
|
"timestamp": timestamp,
|
|
"message": map[string]any{
|
|
"content": content,
|
|
},
|
|
}
|
|
if len(cwd) > 0 {
|
|
m["cwd"] = cwd[0]
|
|
}
|
|
return mustMarshal(m)
|
|
}
|
|
|
|
// ClaudeUserWithSessionIDJSON returns a Claude user message
|
|
// with a sessionId field as a JSON string.
|
|
func ClaudeUserWithSessionIDJSON(
|
|
content, timestamp, sessionID string, cwd ...string,
|
|
) string {
|
|
m := map[string]any{
|
|
"type": "user",
|
|
"timestamp": timestamp,
|
|
"sessionId": sessionID,
|
|
"message": map[string]any{
|
|
"content": content,
|
|
},
|
|
}
|
|
if len(cwd) > 0 {
|
|
m["cwd"] = cwd[0]
|
|
}
|
|
return mustMarshal(m)
|
|
}
|
|
|
|
// ClaudeMetaUserJSON returns a Claude user message with
|
|
// optional isMeta and isCompactSummary flags as a JSON string.
|
|
func ClaudeMetaUserJSON(
|
|
content, timestamp string, meta, compact bool,
|
|
) string {
|
|
m := map[string]any{
|
|
"type": "user",
|
|
"timestamp": timestamp,
|
|
"message": map[string]any{
|
|
"content": content,
|
|
},
|
|
}
|
|
if meta {
|
|
m["isMeta"] = true
|
|
}
|
|
if compact {
|
|
m["isCompactSummary"] = true
|
|
}
|
|
return mustMarshal(m)
|
|
}
|
|
|
|
// ClaudeToolResultUserJSON returns a Claude user message
|
|
// containing only a tool_result block (no text content).
|
|
// These messages are filtered out by pairAndFilter.
|
|
func ClaudeToolResultUserJSON(
|
|
toolUseID, resultContent, timestamp string,
|
|
) string {
|
|
m := map[string]any{
|
|
"type": "user",
|
|
"timestamp": timestamp,
|
|
"message": map[string]any{
|
|
"content": []map[string]any{{
|
|
"type": "tool_result",
|
|
"tool_use_id": toolUseID,
|
|
"content": resultContent,
|
|
}},
|
|
},
|
|
}
|
|
return mustMarshal(m)
|
|
}
|
|
|
|
// ClaudeAssistantJSON returns a Claude assistant message as a
|
|
// JSON string.
|
|
func ClaudeAssistantJSON(content any, timestamp string) string {
|
|
m := map[string]any{
|
|
"type": "assistant",
|
|
"timestamp": timestamp,
|
|
"message": map[string]any{
|
|
"content": content,
|
|
},
|
|
}
|
|
return mustMarshal(m)
|
|
}
|
|
|
|
// ClaudeQueuedCommandJSON returns a Claude attachment entry of
|
|
// type queued_command — a user message typed and submitted while
|
|
// Claude Code was mid-tool-call.
|
|
func ClaudeQueuedCommandJSON(
|
|
prompt, timestamp string,
|
|
) string {
|
|
m := map[string]any{
|
|
"type": "attachment",
|
|
"timestamp": timestamp,
|
|
"attachment": map[string]any{
|
|
"type": "queued_command",
|
|
"commandMode": "prompt",
|
|
"prompt": prompt,
|
|
},
|
|
}
|
|
return mustMarshal(m)
|
|
}
|
|
|
|
// ClaudeSnapshotJSON returns a Claude snapshot message as a
|
|
// JSON string.
|
|
func ClaudeSnapshotJSON(timestamp string) string {
|
|
m := map[string]any{
|
|
"type": "user",
|
|
"snapshot": map[string]any{
|
|
"timestamp": timestamp,
|
|
},
|
|
"message": map[string]any{
|
|
"content": "hello",
|
|
},
|
|
}
|
|
return mustMarshal(m)
|
|
}
|
|
|
|
// CodexSessionMetaJSON returns a Codex session_meta message as
|
|
// a JSON string.
|
|
func CodexSessionMetaJSON(
|
|
id, cwd, originator, timestamp string,
|
|
) string {
|
|
m := map[string]any{
|
|
"type": "session_meta",
|
|
"timestamp": timestamp,
|
|
"payload": map[string]any{
|
|
"id": id,
|
|
"cwd": cwd,
|
|
"originator": originator,
|
|
},
|
|
}
|
|
return mustMarshal(m)
|
|
}
|
|
|
|
// CodexForkedSessionMetaJSON returns the session_meta of a forked
|
|
// Codex session (payload carries forked_from_id).
|
|
func CodexForkedSessionMetaJSON(
|
|
id, forkedFromID, cwd, originator, timestamp string,
|
|
) string {
|
|
m := map[string]any{
|
|
"type": "session_meta",
|
|
"timestamp": timestamp,
|
|
"payload": map[string]any{
|
|
"id": id,
|
|
"forked_from_id": forkedFromID,
|
|
"cwd": cwd,
|
|
"originator": originator,
|
|
},
|
|
}
|
|
return mustMarshal(m)
|
|
}
|
|
|
|
// CodexTurnContextWithIDJSON returns a Codex turn_context entry
|
|
// carrying a turn_id, as newer CLI versions write.
|
|
func CodexTurnContextWithIDJSON(model, turnID, timestamp string) string {
|
|
m := map[string]any{
|
|
"type": "turn_context",
|
|
"timestamp": timestamp,
|
|
"payload": map[string]any{
|
|
"model": model,
|
|
"cwd": "/tmp",
|
|
"turn_id": turnID,
|
|
},
|
|
}
|
|
return mustMarshal(m)
|
|
}
|
|
|
|
// CodexMsgJSON returns a Codex response_item message as a JSON
|
|
// string.
|
|
func CodexMsgJSON(role, text, timestamp string) string {
|
|
contentType := "output_text"
|
|
if role == "user" {
|
|
contentType = "input_text"
|
|
}
|
|
m := map[string]any{
|
|
"type": "response_item",
|
|
"timestamp": timestamp,
|
|
"payload": map[string]any{
|
|
"role": role,
|
|
"content": []map[string]string{
|
|
{
|
|
"type": contentType,
|
|
"text": text,
|
|
},
|
|
},
|
|
},
|
|
}
|
|
return mustMarshal(m)
|
|
}
|
|
|
|
// CodexFunctionCallJSON returns a Codex function_call
|
|
// response_item as a JSON string.
|
|
func CodexFunctionCallJSON(
|
|
name, summary, timestamp string,
|
|
) string {
|
|
payload := map[string]any{
|
|
"type": "function_call",
|
|
"name": name,
|
|
"call_id": "call_test",
|
|
}
|
|
if summary != "" {
|
|
payload["summary"] = summary
|
|
}
|
|
m := map[string]any{
|
|
"type": "response_item",
|
|
"timestamp": timestamp,
|
|
"payload": payload,
|
|
}
|
|
return mustMarshal(m)
|
|
}
|
|
|
|
// CodexFunctionCallArgsJSON returns a Codex function_call
|
|
// response_item with arguments payload.
|
|
func CodexFunctionCallArgsJSON(
|
|
name string, arguments any, timestamp string,
|
|
) string {
|
|
payload := map[string]any{
|
|
"type": "function_call",
|
|
"name": name,
|
|
"call_id": "call_test",
|
|
"arguments": arguments,
|
|
}
|
|
m := map[string]any{
|
|
"type": "response_item",
|
|
"timestamp": timestamp,
|
|
"payload": payload,
|
|
}
|
|
return mustMarshal(m)
|
|
}
|
|
|
|
// CodexFunctionCallFieldsJSON returns a Codex function_call
|
|
// response_item with explicit arguments and input fields.
|
|
func CodexFunctionCallFieldsJSON(
|
|
name string, arguments, input any, timestamp string,
|
|
) string {
|
|
payload := map[string]any{
|
|
"type": "function_call",
|
|
"name": name,
|
|
"call_id": "call_test",
|
|
}
|
|
if arguments != nil {
|
|
payload["arguments"] = arguments
|
|
}
|
|
if input != nil {
|
|
payload["input"] = input
|
|
}
|
|
m := map[string]any{
|
|
"type": "response_item",
|
|
"timestamp": timestamp,
|
|
"payload": payload,
|
|
}
|
|
return mustMarshal(m)
|
|
}
|
|
|
|
// CodexFunctionCallWithCallIDJSON returns a Codex function_call
|
|
// response_item with an explicit call_id.
|
|
func CodexFunctionCallWithCallIDJSON(
|
|
name, callID string, arguments any, timestamp string,
|
|
) string {
|
|
payload := map[string]any{
|
|
"type": "function_call",
|
|
"name": name,
|
|
"call_id": callID,
|
|
}
|
|
if arguments != nil {
|
|
payload["arguments"] = arguments
|
|
}
|
|
m := map[string]any{
|
|
"type": "response_item",
|
|
"timestamp": timestamp,
|
|
"payload": payload,
|
|
}
|
|
return mustMarshal(m)
|
|
}
|
|
|
|
// CodexFunctionCallOutputJSON returns a Codex
|
|
// function_call_output response_item.
|
|
func CodexFunctionCallOutputJSON(
|
|
callID string, output any, timestamp string,
|
|
) string {
|
|
m := map[string]any{
|
|
"type": "response_item",
|
|
"timestamp": timestamp,
|
|
"payload": map[string]any{
|
|
"type": "function_call_output",
|
|
"call_id": callID,
|
|
"output": output,
|
|
},
|
|
}
|
|
return mustMarshal(m)
|
|
}
|
|
|
|
// CodexTurnContextJSON returns a Codex turn_context entry as a
|
|
// JSON string with the given model.
|
|
func CodexTurnContextJSON(model, timestamp string) string {
|
|
m := map[string]any{
|
|
"type": "turn_context",
|
|
"timestamp": timestamp,
|
|
"payload": map[string]any{
|
|
"model": model,
|
|
"cwd": "/tmp",
|
|
},
|
|
}
|
|
return mustMarshal(m)
|
|
}
|
|
|
|
// CodexTokenCountJSON returns a Codex event_msg with
|
|
// payload.type=token_count and last_token_usage fields.
|
|
func CodexTokenCountJSON(
|
|
timestamp string,
|
|
inputTokens, outputTokens, cachedInputTokens int,
|
|
) string {
|
|
m := map[string]any{
|
|
"type": "event_msg",
|
|
"timestamp": timestamp,
|
|
"payload": map[string]any{
|
|
"type": "token_count",
|
|
"info": map[string]any{
|
|
"last_token_usage": map[string]any{
|
|
"input_tokens": inputTokens,
|
|
"output_tokens": outputTokens,
|
|
"cached_input_tokens": cachedInputTokens,
|
|
"total_tokens": inputTokens + outputTokens,
|
|
},
|
|
},
|
|
},
|
|
}
|
|
return mustMarshal(m)
|
|
}
|
|
|
|
// ClaudeEntryJSON returns a Claude JSONL entry with uuid and
|
|
// parentUuid fields.
|
|
func ClaudeEntryJSON(
|
|
entryType, content, timestamp, uuid, parentUuid string,
|
|
cwd ...string,
|
|
) string {
|
|
m := map[string]any{
|
|
"type": entryType,
|
|
"timestamp": timestamp,
|
|
"uuid": uuid,
|
|
"message": map[string]any{
|
|
"content": content,
|
|
},
|
|
}
|
|
if parentUuid != "" {
|
|
m["parentUuid"] = parentUuid
|
|
}
|
|
if len(cwd) > 0 {
|
|
m["cwd"] = cwd[0]
|
|
}
|
|
return mustMarshal(m)
|
|
}
|
|
|
|
// JoinJSONL joins JSON lines with newlines and appends a
|
|
// trailing newline.
|
|
func JoinJSONL(lines ...string) string {
|
|
return strings.Join(lines, "\n") + "\n"
|
|
}
|
|
|
|
// SessionBuilder constructs JSONL session content using a
|
|
// fluent API.
|
|
type SessionBuilder struct {
|
|
lines []string
|
|
}
|
|
|
|
// NewSessionBuilder returns a new empty SessionBuilder.
|
|
func NewSessionBuilder() *SessionBuilder {
|
|
return &SessionBuilder{}
|
|
}
|
|
|
|
// AddClaudeUser appends a Claude user message line.
|
|
func (b *SessionBuilder) AddClaudeUser(
|
|
timestamp, content string, cwd ...string,
|
|
) *SessionBuilder {
|
|
b.lines = append(b.lines, ClaudeUserJSON(content, timestamp, cwd...))
|
|
return b
|
|
}
|
|
|
|
// AddClaudeUserWithSessionID appends a Claude user message
|
|
// line with a sessionId field.
|
|
func (b *SessionBuilder) AddClaudeUserWithSessionID(
|
|
timestamp, content, sessionID string, cwd ...string,
|
|
) *SessionBuilder {
|
|
b.lines = append(
|
|
b.lines,
|
|
ClaudeUserWithSessionIDJSON(
|
|
content, timestamp, sessionID, cwd...,
|
|
),
|
|
)
|
|
return b
|
|
}
|
|
|
|
// AddClaudeUserWithUUID appends a Claude user message with
|
|
// uuid and parentUuid fields.
|
|
func (b *SessionBuilder) AddClaudeUserWithUUID(
|
|
timestamp, content, uuid, parentUuid string,
|
|
cwd ...string,
|
|
) *SessionBuilder {
|
|
b.lines = append(b.lines, ClaudeEntryJSON(
|
|
"user", content, timestamp, uuid, parentUuid, cwd...,
|
|
))
|
|
return b
|
|
}
|
|
|
|
// AddClaudeAssistantWithUUID appends a Claude assistant message
|
|
// with uuid and parentUuid fields.
|
|
func (b *SessionBuilder) AddClaudeAssistantWithUUID(
|
|
timestamp, text, uuid, parentUuid string,
|
|
) *SessionBuilder {
|
|
m := map[string]any{
|
|
"type": "assistant",
|
|
"timestamp": timestamp,
|
|
"uuid": uuid,
|
|
"message": map[string]any{
|
|
"content": []map[string]string{
|
|
{"type": "text", "text": text},
|
|
},
|
|
},
|
|
}
|
|
if parentUuid != "" {
|
|
m["parentUuid"] = parentUuid
|
|
}
|
|
b.lines = append(b.lines, mustMarshal(m))
|
|
return b
|
|
}
|
|
|
|
// AddClaudeMetaUser appends a Claude user message line with
|
|
// isMeta and/or isCompactSummary flags.
|
|
func (b *SessionBuilder) AddClaudeMetaUser(
|
|
timestamp, content string, meta, compact bool,
|
|
) *SessionBuilder {
|
|
b.lines = append(
|
|
b.lines,
|
|
ClaudeMetaUserJSON(content, timestamp, meta, compact),
|
|
)
|
|
return b
|
|
}
|
|
|
|
// AddClaudeAssistant appends a Claude assistant message line.
|
|
func (b *SessionBuilder) AddClaudeAssistant(
|
|
timestamp, text string,
|
|
) *SessionBuilder {
|
|
b.lines = append(b.lines, ClaudeAssistantJSON(
|
|
[]map[string]string{{"type": "text", "text": text}},
|
|
timestamp,
|
|
))
|
|
return b
|
|
}
|
|
|
|
// AddCodexMeta appends a Codex session_meta line.
|
|
func (b *SessionBuilder) AddCodexMeta(
|
|
timestamp, id, cwd, originator string,
|
|
) *SessionBuilder {
|
|
b.lines = append(
|
|
b.lines,
|
|
CodexSessionMetaJSON(id, cwd, originator, timestamp),
|
|
)
|
|
return b
|
|
}
|
|
|
|
// AddCodexMessage appends a Codex response_item line.
|
|
func (b *SessionBuilder) AddCodexMessage(
|
|
timestamp, role, text string,
|
|
) *SessionBuilder {
|
|
b.lines = append(b.lines, CodexMsgJSON(role, text, timestamp))
|
|
return b
|
|
}
|
|
|
|
// AddCodexFunctionCall appends a Codex function_call line.
|
|
func (b *SessionBuilder) AddCodexFunctionCall(
|
|
timestamp, name, summary string,
|
|
) *SessionBuilder {
|
|
b.lines = append(
|
|
b.lines,
|
|
CodexFunctionCallJSON(name, summary, timestamp),
|
|
)
|
|
return b
|
|
}
|
|
|
|
// AddRaw appends an arbitrary raw line.
|
|
func (b *SessionBuilder) AddRaw(line string) *SessionBuilder {
|
|
b.lines = append(b.lines, line)
|
|
return b
|
|
}
|
|
|
|
// String returns the JSONL content with a trailing newline.
|
|
func (b *SessionBuilder) String() string {
|
|
return strings.Join(b.lines, "\n") + "\n"
|
|
}
|
|
|
|
// StringNoTrailingNewline returns the JSONL content without a
|
|
// trailing newline.
|
|
func (b *SessionBuilder) StringNoTrailingNewline() string {
|
|
return strings.Join(b.lines, "\n")
|
|
}
|
|
|
|
// GeminiToolCall defines a tool call for Gemini test fixtures.
|
|
type GeminiToolCall struct {
|
|
ID string
|
|
Name string
|
|
DisplayName string
|
|
Args map[string]string
|
|
ResultOutput string // if set, generates inline functionResponse result
|
|
}
|
|
|
|
// GeminiThought defines a thought for Gemini test fixtures.
|
|
type GeminiThought struct {
|
|
Subject string
|
|
Description string
|
|
Timestamp string
|
|
}
|
|
|
|
// GeminiMsgOpts holds optional fields for a Gemini assistant
|
|
// message.
|
|
type GeminiMsgOpts struct {
|
|
Thoughts []GeminiThought
|
|
ToolCalls []GeminiToolCall
|
|
Model string
|
|
}
|
|
|
|
// GeminiUserMsg builds a Gemini user message object.
|
|
func GeminiUserMsg(
|
|
id, timestamp, content string,
|
|
) map[string]any {
|
|
return map[string]any{
|
|
"id": id,
|
|
"timestamp": timestamp,
|
|
"type": "user",
|
|
"content": content,
|
|
}
|
|
}
|
|
|
|
// GeminiAssistantMsg builds a Gemini assistant message object.
|
|
func GeminiAssistantMsg(
|
|
id, timestamp, content string, opts *GeminiMsgOpts,
|
|
) map[string]any {
|
|
m := map[string]any{
|
|
"id": id,
|
|
"timestamp": timestamp,
|
|
"type": "gemini",
|
|
"content": content,
|
|
}
|
|
if opts == nil {
|
|
return m
|
|
}
|
|
if opts.Model != "" {
|
|
m["model"] = opts.Model
|
|
}
|
|
if len(opts.Thoughts) > 0 {
|
|
var thoughts []map[string]string
|
|
for _, th := range opts.Thoughts {
|
|
thoughts = append(thoughts, map[string]string{
|
|
"subject": th.Subject,
|
|
"description": th.Description,
|
|
"timestamp": th.Timestamp,
|
|
})
|
|
}
|
|
m["thoughts"] = thoughts
|
|
}
|
|
if len(opts.ToolCalls) > 0 {
|
|
var tcs []map[string]any
|
|
for _, tc := range opts.ToolCalls {
|
|
entry := map[string]any{
|
|
"name": tc.Name,
|
|
"displayName": tc.DisplayName,
|
|
"status": "success",
|
|
}
|
|
if tc.ID != "" {
|
|
entry["id"] = tc.ID
|
|
}
|
|
if tc.Args != nil {
|
|
entry["args"] = tc.Args
|
|
}
|
|
if tc.ResultOutput != "" {
|
|
entry["result"] = []map[string]any{
|
|
{
|
|
"functionResponse": map[string]any{
|
|
"id": tc.ID,
|
|
"name": tc.Name,
|
|
"response": map[string]any{
|
|
"output": tc.ResultOutput,
|
|
},
|
|
},
|
|
},
|
|
}
|
|
}
|
|
tcs = append(tcs, entry)
|
|
}
|
|
m["toolCalls"] = tcs
|
|
}
|
|
return m
|
|
}
|
|
|
|
// GeminiInfoMsg builds a Gemini info/system message object.
|
|
func GeminiInfoMsg(
|
|
id, timestamp, content, msgType string,
|
|
) map[string]any {
|
|
return map[string]any{
|
|
"id": id,
|
|
"timestamp": timestamp,
|
|
"type": msgType,
|
|
"content": content,
|
|
}
|
|
}
|
|
|
|
// GeminiSessionJSON builds a complete Gemini session JSON
|
|
// string from the given parameters.
|
|
func GeminiSessionJSON(
|
|
sessionID, projectHash string,
|
|
startTime, lastUpdated string,
|
|
messages []map[string]any,
|
|
) string {
|
|
session := map[string]any{
|
|
"sessionId": sessionID,
|
|
"projectHash": projectHash,
|
|
"startTime": startTime,
|
|
"lastUpdated": lastUpdated,
|
|
"messages": messages,
|
|
}
|
|
b, err := json.MarshalIndent(session, "", " ")
|
|
if err != nil {
|
|
panic(err)
|
|
}
|
|
return string(b)
|
|
}
|
|
|
|
func mustMarshal(v any) string {
|
|
b, err := json.Marshal(v)
|
|
if err != nil {
|
|
panic(err)
|
|
}
|
|
return string(b)
|
|
}
|