fix(chat): preserve attachment references from JSON RPC results (#1210)
Co-authored-by: M57 <41645758+Hy4ri@users.noreply.github.com>
This commit is contained in:
1 parent
799bb2959e
commit
83b2a8fa9b
4 files changed
+168
-4
No files matched your search
@@ -196,7 +196,14 @@ internal fun mapServerMessages(
|
||||
return@forEachIndexed
|
||||
}
|
||||
|
||||
var finalContent = rawContent
|
||||
// Context-reference preprocessing can persist model-facing attachment
|
||||
// contents after the user's text. A cold reload / second device has no
|
||||
// optimistic local bubble to hide that enriched REST representation,
|
||||
// so project the producer-owned suffix out at hydration time too.
|
||||
// Keep the actual @file:/@image: token here: unlike the dedupe path,
|
||||
// REST-only hydration cannot prove that reference was not user-authored.
|
||||
var finalContent =
|
||||
if (role == MessageRole.USER) stripGatewayAttachedContext(rawContent) else rawContent
|
||||
var attachments: List<Attachment>? = null
|
||||
if (role == MessageRole.ASSISTANT && rawContent.contains("MEDIA:")) {
|
||||
val items = HostMediaExtractor.extract(rawContent)
|
||||
|
||||
@@ -6,6 +6,38 @@ import kotlinx.serialization.json.JsonElement
|
||||
import kotlinx.serialization.json.JsonObject
|
||||
import kotlinx.serialization.json.JsonPrimitive
|
||||
|
||||
private val ATTACHED_CONTEXT_MARKER_RE =
|
||||
Regex("""(?:^|\n)--- Attached Context ---\s*\n""")
|
||||
|
||||
private val CONTEXT_REF_RE =
|
||||
Regex(
|
||||
"""@(file|folder|url|image|tool|terminal):(?:"[^"\n]+"|'[^'\n]+'|`[^`\n]+`|\S+)""",
|
||||
)
|
||||
|
||||
/**
|
||||
* Remove a gateway-generated `--- Attached Context ---` suffix while
|
||||
* preserving the user-authored portion of the message.
|
||||
*
|
||||
* The marker itself is plain text a user can legitimately type, so do not
|
||||
* treat it as a producer boundary unless the suffix also contains a context
|
||||
* reference of the shape emitted by the gateway.
|
||||
*
|
||||
* Unlike [stripAttachmentRefLines], this intentionally keeps `@file:` /
|
||||
* `@image:` references in the visible text. REST-only hydration has no local
|
||||
* optimistic bubble proving those references were injected by Mobile, and
|
||||
* users may have authored context references themselves.
|
||||
*/
|
||||
internal fun stripGatewayAttachedContext(content: String): String {
|
||||
val marker = ATTACHED_CONTEXT_MARKER_RE.find(content) ?: return content
|
||||
val attachedContext = content.substring(marker.range.last + 1)
|
||||
|
||||
if (!CONTEXT_REF_RE.containsMatchIn(attachedContext)) {
|
||||
return content
|
||||
}
|
||||
|
||||
return content.substring(0, marker.range.first).trimEnd()
|
||||
}
|
||||
|
||||
/**
|
||||
* Canonical comparison key for a tool message's result payload.
|
||||
*
|
||||
@@ -127,7 +159,7 @@ internal fun sameLogicalMessage(
|
||||
* run_agent). Blank lines left behind are dropped too.
|
||||
*/
|
||||
internal fun stripAttachmentRefLines(content: String): String =
|
||||
content
|
||||
stripGatewayAttachedContext(content)
|
||||
.lines()
|
||||
.map { it.trim() }
|
||||
.filterNot { line ->
|
||||
|
||||
@@ -1877,7 +1877,7 @@ class ChatViewModel(
|
||||
"filename" to attachment.name,
|
||||
"ext" to attachment.fileExtension,
|
||||
),
|
||||
)
|
||||
).let { if (it is JsonElement) it.toAny() else it }
|
||||
if (result != null) {
|
||||
@Suppress("UNCHECKED_CAST")
|
||||
val ok = (result as? Map<String, Any?>)?.get("attached") as? Boolean
|
||||
@@ -1897,7 +1897,10 @@ class ChatViewModel(
|
||||
"data_url" to "data:${attachment.mimeType};base64,$b64",
|
||||
"name" to attachment.name,
|
||||
),
|
||||
)?.let { result ->
|
||||
)?.let { response ->
|
||||
// request() returns raw JsonElement values; normalize before reading Kotlin types.
|
||||
val result = if (response is JsonElement) response.toAny() else response
|
||||
|
||||
@Suppress("UNCHECKED_CAST")
|
||||
val refText =
|
||||
(result as? Map<String, Any?>)?.get("ref_text") as? String
|
||||
|
||||
Reference in new issue
Block a user