package ingest import ( "encoding/json" "fmt" "os" "path/filepath" "strings" "time" ) type jsonKind int const ( jsonObject jsonKind = iota jsonArray ) var _ Ingester = (*SessionDiffIngester)(nil) type SessionDiffIngester struct { Version string } func NewSessionDiffIngester() *SessionDiffIngester { return &SessionDiffIngester{Version: "session-diff/v1"} } func (s *SessionDiffIngester) SourceID() string { return "opencode-session" } func (s *SessionDiffIngester) Ingest(path string) (*IngestResult, error) { data, err := os.ReadFile(path) if err != nil { return nil, fmt.Errorf("read session diff: %w", err) } if len(data) < 10 { return nil, nil } raw, kind := parseJSON(data) if raw == nil { return nil, nil } sessionID := guessString(raw, "session_id", "id", "sessionId") project := guessString(raw, "project", "project_id", "projectPath", "projectId") title := guessString(raw, "title", "name", "label") createdAt := guessString(raw, "created_at", "timestamp", "date", "started_at") if createdAt == "" { if fi, err := os.Stat(path); err == nil { createdAt = fi.ModTime().UTC().Format(time.RFC3339) } } if sessionID == "" { base := filepath.Base(path) sessionID = strings.TrimSuffix(base, filepath.Ext(base)) sessionID = strings.TrimPrefix(sessionID, "ses_") } if title == "" { title = fmt.Sprintf("Session %s", sessionID[:min(8, len(sessionID))]) } summary := extractSummary(raw, kind) fp := FingerprintWithMeta(data, map[string]string{ "session_id": sessionID, "source": "session_diff", }) confidence := 0.7 if kind == jsonArray { confidence = 0.4 } return &IngestResult{ Fingerprint: fp, SourceID: s.SourceID(), SourcePath: path, Project: project, ContentType: ".json", Title: title, Summary: summary, CreatedAt: createdAt, LineStart: 0, LineEnd: 0, Confidence: confidence, IngesterVersion: s.Version, Provenance: map[string]any{ "session_id": sessionID, "kind": kindName(kind), "field_count": len(raw), }, }, nil } func parseJSON(data []byte) (map[string]any, jsonKind) { var obj map[string]any if err := json.Unmarshal(data, &obj); err == nil { return obj, jsonObject } var arr []any if err := json.Unmarshal(data, &arr); err == nil { m := make(map[string]any) m["_count"] = float64(len(arr)) m["_kind"] = "array" if len(arr) > 0 { if first, ok := arr[0].(map[string]any); ok { for k, v := range first { if str, ok := v.(string); ok && len(str) < 200 { m["_sample_"+k] = str break } } } } return m, jsonArray } return nil, jsonObject } func extractSummary(raw map[string]any, kind jsonKind) string { if msg, ok := raw["message"].(string); ok && len(msg) > 0 { return truncate(msg, 200) } if msgs, ok := raw["messages"].([]any); ok && len(msgs) > 0 { parts := make([]string, 0, len(msgs)) for _, m := range msgs { if mm, ok := m.(map[string]any); ok { if c, ok := mm["content"].(string); ok { parts = append(parts, truncate(c, 100)) } } } return strings.Join(parts, " | ") } if count, ok := raw["_count"].(float64); ok && kind == jsonArray { return fmt.Sprintf("Array with %.0f entries", count) } b, _ := json.Marshal(raw) return truncate(string(b), 200) } func guessString(m map[string]any, keys ...string) string { for _, k := range keys { if v, ok := m[k].(string); ok && v != "" { return v } } return "" } func kindName(k jsonKind) string { if k == jsonArray { return "array" } return "object" }