fix(copilot): Address PR review comments

- Shield transcript upload and session save from generator cancellation
  (asyncio.shield) so page refresh/disconnect doesn't lose the transcript
- Return content_base64 for small image files (not just text) so
  _extract_image_block can actually work
- Add 32KB size limit to _extract_image_block to prevent oversized images
- Fix gap fill when transcript_msg_count == 0 (metadata absent)
- Add truncation to files.join in GenericTool.tsx
This commit is contained in:
Otto (AGPT)
2026-02-19 00:30:06 +00:00
parent b6128dd75f
commit 0c586c2edf
4 changed files with 35 additions and 12 deletions

View File

@@ -576,7 +576,7 @@ async def stream_chat_completion_sdk(
query_message = current_message
current_msg_count = len(session.messages)
if use_resume and transcript_msg_count > 0:
if use_resume and transcript_msg_count >= 0:
# Transcript covers messages[0..M-1]. Current session
# has N messages (last one is the new user msg).
# Gap = messages[M .. N-2] (everything between upload
@@ -707,11 +707,17 @@ async def stream_chat_completion_sdk(
raw_transcript = None
if raw_transcript:
await _try_upload_transcript(
user_id,
session_id,
raw_transcript,
message_count=len(session.messages),
# Shield the upload from generator cancellation so a
# client disconnect / page refresh doesn't lose the
# transcript. The upload must finish even if the SSE
# connection is torn down.
await asyncio.shield(
_try_upload_transcript(
user_id,
session_id,
raw_transcript,
message_count=len(session.messages),
)
)
except ImportError:
@@ -721,7 +727,7 @@ async def stream_chat_completion_sdk(
"to use the OpenAI-compatible fallback."
)
await upsert_chat_session(session)
await asyncio.shield(upsert_chat_session(session))
logger.debug(
f"[SDK] Session {session_id} saved with {len(session.messages)} messages"
)
@@ -731,7 +737,7 @@ async def stream_chat_completion_sdk(
except Exception as e:
logger.error(f"[SDK] Error: {e}", exc_info=True)
try:
await upsert_chat_session(session)
await asyncio.shield(upsert_chat_session(session))
except Exception as save_err:
logger.error(f"[SDK] Failed to save session on error: {save_err}")
yield StreamError(

View File

@@ -190,7 +190,14 @@ def _extract_image_block(text: str) -> dict[str, str] | None:
mime_type = data.get("mime_type", "")
base64_content = data.get("content_base64", "")
if mime_type in _SUPPORTED_IMAGE_TYPES and base64_content:
# Only inline small images — large ones would exceed Claude's limits.
# 32 KB raw ≈ ~43 KB base64.
_MAX_IMAGE_BASE64_BYTES = 43_000
if (
mime_type in _SUPPORTED_IMAGE_TYPES
and base64_content
and len(base64_content) <= _MAX_IMAGE_BASE64_BYTES
):
return {
"type": "image",
"data": base64_content,

View File

@@ -312,8 +312,18 @@ class ReadWorkspaceFileTool(BaseTool):
is_small_file = file_info.size_bytes <= self.MAX_INLINE_SIZE_BYTES
is_text_file = self._is_text_mime_type(file_info.mime_type)
# Return inline content for small text files (unless force_download_url)
if is_small_file and is_text_file and not force_download_url:
# Return inline content for small text/image files (unless force_download_url)
is_image_file = file_info.mime_type in {
"image/png",
"image/jpeg",
"image/gif",
"image/webp",
}
if (
is_small_file
and (is_text_file or is_image_file)
and not force_download_url
):
content = await manager.read_file_by_id(target_file_id)
content_b64 = base64.b64encode(content).decode("utf-8")

View File

@@ -501,7 +501,7 @@ function getFileAccordionData(
<ContentCodeBlock>{truncate(content, 2000)}</ContentCodeBlock>
)}
{files && files.length > 0 && (
<ContentCodeBlock>{files.join("\n")}</ContentCodeBlock>
<ContentCodeBlock>{truncate(files.join("\n"), 2000)}</ContentCodeBlock>
)}
{!content && !files && message && (
<ContentMessage>{message}</ContentMessage>