This commit is contained in:
Timothy Jaeryang Baek committed 2026-08-13 21:36:41 -06:00
1 parent b4738d1a2e
commit fa94a5ab24
3 files changed
+132 -67

No files matched your search

+35 -13
View File
@@ -2063,16 +2063,6 @@ def process_messages_with_output(
return processed
def strip_compaction_fields(messages: list[dict]) -> list[dict]:
stripped = []
for message in messages:
clean = dict(message)
for key in ('id', 'files', 'output', 'contextSummary', 'context_summary', 'usage'):
clean.pop(key, None)
stripped.append(clean)
return stripped
def sanitize_tool_pairs(messages: list[dict]) -> list[dict]:
tool_result_ids = {
message.get('tool_call_id')
@@ -2331,8 +2321,6 @@ async def process_chat_payload(request, form_data, user, metadata, model):
except Exception:
log.exception('Context compaction failed; continuing with full chat history')
form_data['messages'] = strip_compaction_fields(form_data.get('messages', []))
# Process messages with OR-aligned output items for clean LLM messages
form_data['messages'] = process_messages_with_output(
form_data.get('messages', []),
@@ -3109,6 +3097,20 @@ async def drain_approved_tool_calls(request, form_data, user, model, metadata) -
and item.get('call_id') not in result_call_ids
]
if not approved_calls:
if metadata.get('params', {}).get('tool_approval_mode', 'full') == 'ask' and any(
item.get('type') == 'function_call'
and item.get('name') != 'ask_user'
and (item.get('call_id') or item.get('id'))
and item.get('status') == 'queued'
and item.get('approved') is not True
and (item.get('call_id') or item.get('id')) not in result_call_ids
for item in output
):
event_emitter, _ = await get_event_emitter_and_caller(metadata)
await pause_for_tool_approval(chat_id, message_id, output, form_data, metadata)
if event_emitter:
await event_emitter({'type': 'chat:completion', 'data': {'done': False, 'output': output}})
return True
return False
event_emitter, event_caller = await get_event_emitter_and_caller(metadata)
@@ -3146,6 +3148,21 @@ async def drain_approved_tool_calls(request, form_data, user, model, metadata) -
result_call_ids = {
item.get('call_id') for item in output if item.get('type') == 'function_call_output' and item.get('call_id')
}
if metadata.get('params', {}).get('tool_approval_mode', 'full') == 'ask' and any(
item.get('type') == 'function_call'
and item.get('name') != 'ask_user'
and (item.get('call_id') or item.get('id'))
and item.get('status') == 'queued'
and item.get('approved') is not True
and (item.get('call_id') or item.get('id')) not in result_call_ids
for item in output
):
await pause_for_tool_approval(chat_id, message_id, output, form_data, metadata)
result_call_ids = {
item.get('call_id')
for item in output
if item.get('type') == 'function_call_output' and item.get('call_id')
}
paused = any(
item.get('type') == 'function_call'
and item.get('call_id')
@@ -3207,6 +3224,7 @@ async def pause_for_tool_approval(chat_id: str, message_id: str, output: list[di
result_call_ids = {
item.get('call_id') for item in output if item.get('type') == 'function_call_output' and item.get('call_id')
}
has_pending_approval = False
for item in output:
if item.get('type') == 'function_call' and not item.get('call_id') and item.get('id'):
item['call_id'] = item['id']
@@ -3217,7 +3235,11 @@ async def pause_for_tool_approval(chat_id: str, message_id: str, output: list[di
and item.get('call_id') not in result_call_ids
and item.get('status') != 'rejected'
):
item['status'] = 'pending'
if not has_pending_approval:
item['status'] = 'pending'
has_pending_approval = True
elif item.get('status') == 'in_progress':
item['status'] = 'queued'
await Chats.upsert_message_to_chat_by_id_and_message_id(
chat_id,
+55 -46
View File
@@ -293,6 +293,7 @@ def convert_output_to_messages(
pending_reasoning = [] # Only populated when reasoning_format == 'reasoning_content'
pending_reasoning_details = []
pending_tool_image_urls = []
pending_tool_outputs = []
completed_call_ids = {
item.get('call_id')
for item in output
@@ -347,9 +348,60 @@ def convert_output_to_messages(
)
pending_tool_image_urls = []
def flush_tool_outputs():
nonlocal pending_tool_outputs
if not pending_tool_outputs:
return
flush_pending()
for output_item in pending_tool_outputs:
output_parts = output_item.get('output', [])
content = ''
image_urls = []
for part in output_parts:
if part.get('type') == 'input_text':
output_text = part.get('text', '')
content += str(output_text) if not isinstance(output_text, str) else output_text
elif part.get('type') == 'input_image':
url = part.get('image_url', '')
if url:
image_urls.append(url)
if flatten_tool_images:
messages.append(
{
'role': 'tool',
'tool_call_id': output_item.get('call_id', ''),
'content': content,
}
)
pending_tool_image_urls.extend(image_urls)
elif image_urls:
messages.append(
{
'role': 'tool',
'tool_call_id': output_item.get('call_id', ''),
'content': [
{'type': 'input_text', 'text': content},
*[{'type': 'input_image', 'image_url': url} for url in image_urls],
],
}
)
else:
messages.append(
{
'role': 'tool',
'tool_call_id': output_item.get('call_id', ''),
'content': content,
}
)
pending_tool_outputs = []
for item in output:
item_type = item.get('type', '')
if item_type != 'function_call_output':
if item_type not in {'function_call', 'function_call_output'}:
flush_tool_outputs()
flush_tool_images()
if item_type == 'message':
@@ -386,51 +438,7 @@ def convert_output_to_messages(
if item.get('call_id') not in function_call_ids:
continue
# Flush any pending content/tool_calls before adding tool result
flush_pending()
# Extract text and images from output content parts
output_parts = item.get('output', [])
content = ''
image_urls = []
for part in output_parts:
if part.get('type') == 'input_text':
output_text = part.get('text', '')
content += str(output_text) if not isinstance(output_text, str) else output_text
elif part.get('type') == 'input_image':
url = part.get('image_url', '')
if url:
image_urls.append(url)
if flatten_tool_images:
messages.append(
{
'role': 'tool',
'tool_call_id': item.get('call_id', ''),
'content': content,
}
)
if item.get('call_id') in function_call_ids:
pending_tool_image_urls.extend(image_urls)
elif image_urls:
messages.append(
{
'role': 'tool',
'tool_call_id': item.get('call_id', ''),
'content': [
{'type': 'input_text', 'text': content},
*[{'type': 'input_image', 'image_url': url} for url in image_urls],
],
}
)
else:
messages.append(
{
'role': 'tool',
'tool_call_id': item.get('call_id', ''),
'content': content,
}
)
pending_tool_outputs.append(item)
elif item_type == 'reasoning':
reasoning_details = item.get('reasoning_details') if raw else None
@@ -484,6 +492,7 @@ def convert_output_to_messages(
pass
# Flush remaining content/tool_calls
flush_tool_outputs()
flush_tool_images()
flush_pending()