1package soy.iko.opencode.data.model
3import androidx.compose.runtime.Immutable
4import kotlinx.serialization.SerialName
5import kotlinx.serialization.Serializable
6import kotlinx.serialization.json.JsonElement
7import kotlinx.serialization.json.JsonObject
8import kotlinx.serialization.json.JsonPrimitive
11 * A message part — a discriminated union on `type` (the global class discriminator).
12 * Unknown types decode to [UnknownPart] via the registered polymorphic default,
13 * so the stream survives server additions.
15 * Every part carries [id] plus its parent [messageID]/[sessionID], which the reducer
16 * uses to attach streamed parts to the right message.
19sealed interface Part {
21 val messageID: String?
22 val sessionID: String?
29 override val id: String = "",
30 override val messageID: String? = null,
31 override val sessionID: String? = null,
32 val text: String = "",
33 val synthetic: Boolean = false,
34 val ignored: Boolean = false,
35 val time: PartTimeInfo? = null,
40@SerialName("reasoning")
41data class ReasoningPart(
42 override val id: String = "",
43 override val messageID: String? = null,
44 override val sessionID: String? = null,
45 val text: String = "",
46 val time: PartTimeInfo? = null,
53 override val id: String = "",
54 override val messageID: String? = null,
55 override val sessionID: String? = null,
56 val callID: String = "",
57 val tool: String = "",
58 val state: ToolState = ToolPending,
65 override val id: String = "",
66 override val messageID: String? = null,
67 override val sessionID: String? = null,
68 val mime: String? = null,
69 val filename: String? = null,
70 val url: String? = null,
71 // opencode's message-v2 schema sends `source` as a structured FilePartSource OBJECT
72 // ({ type, path, text, ... }), not a string, for @-file mentions. Typing it as String
73 // threw a SerializationException on decode — which the SSE path silently swallows, so the
74 // whole part vanished. Keep it as a tolerant JsonElement (mirroring Permission.pattern) and
75 // read the path via [sourcePath]; a bare-string source is still tolerated.
76 val source: JsonElement? = null,
79/** The file path referenced by a [FilePart.source]. opencode sends `source` as a FilePartSource
80 * object whose `path` field holds the file path; older/other shapes may send a bare string.
81 * Returns null when there's no usable path. */
82val FilePart.sourcePath: String?
83 get() = when (val s = source) {
84 is JsonPrimitive -> if (s.isString) s.content else null
85 is JsonObject -> (s["path"] as? JsonPrimitive)?.let { if (it.isString) it.content else null }
91@SerialName("step-start")
92data class StepStartPart(
93 override val id: String = "",
94 override val messageID: String? = null,
95 override val sessionID: String? = null,
100@SerialName("step-finish")
101data class StepFinishPart(
102 override val id: String = "",
103 override val messageID: String? = null,
104 override val sessionID: String? = null,
105 val cost: Double? = null,
106 val tokens: Tokens? = null,
109/** Fallback for an unrecognized `type`. */
112@SerialName("__unknown")
113data class UnknownPart(
114 override val id: String = "",
115 override val messageID: String? = null,
116 override val sessionID: String? = null,
keysesc to close
- j k
- move down, up
- enter
- open
- esc
- back, or up a level
- 1 2 3 4
- readme, files, commits, refs
- /
- filter
- y
- copy the clone command
- c
- compare the chosen commit with another (or shift-click both)
- ?
- show or hide this list