Skip to content

Commit 08aab20

Browse files
authored
Merge branch 'vertyco:main' into main
2 parents 2becd56 + aa16b9f commit 08aab20

File tree

10 files changed

+453
-86
lines changed

10 files changed

+453
-86
lines changed

appeals/commands/admin.py

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -267,6 +267,8 @@ async def approve_appeal(self, ctx: commands.Context, submission_id: int, *, rea
267267
else:
268268
try:
269269
message = await pending_channel.fetch_message(submission.message_id)
270+
if message.thread:
271+
await message.thread.delete()
270272
await message.delete()
271273
except discord.NotFound:
272274
await ctx.send(f"Submission message not found in {pending_channel.mention}")
@@ -363,6 +365,8 @@ async def deny_appeal(self, ctx: commands.Context, submission_id: int, *, reason
363365
else:
364366
try:
365367
message = await pending_channel.fetch_message(submission.message_id)
368+
if message.thread:
369+
await message.thread.delete()
366370
await message.delete()
367371
except discord.NotFound:
368372
await ctx.send(f"Submission message not found in {pending_channel.mention}")

appeals/main.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,7 @@ class Appeals(Commands, Listeners, commands.Cog, metaclass=CompositeMetaClass):
2525
"""Straightforward ban appeal system for Discord servers."""
2626

2727
__author__ = "[vertyco](https://github.com/vertyco/vrt-cogs)"
28-
__version__ = "0.1.1"
28+
__version__ = "0.1.2"
2929

3030
def __init__(self, bot: Red):
3131
super().__init__()

assistant/assistant.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -54,7 +54,7 @@ class Assistant(
5454
"""
5555

5656
__author__ = "[vertyco](https://github.com/vertyco/vrt-cogs)"
57-
__version__ = "6.12.23"
57+
__version__ = "6.12.24"
5858

5959
def format_help_for_context(self, ctx):
6060
helpcmd = super().format_help_for_context(ctx)

assistant/commands/admin.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1984,7 +1984,7 @@ async def endpoint_override(self, ctx: commands.Context, endpoint: str = None):
19841984
19851985
**Notes**
19861986
- Using a custom endpoint is not supported!
1987-
- Using an endpoing override will negate model settings like temperature and custom funcitons
1987+
- Using an endpoing override will negate model settings like temperature and custom functions
19881988
"""
19891989
if self.db.endpoint_override == endpoint:
19901990
return await ctx.send(_("Endpoint is already set to **{}**").format(endpoint))

assistant/common/utils.py

Lines changed: 62 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -2,8 +2,8 @@
22
import logging
33
import re
44
import sys
5+
import typing as t
56
from datetime import datetime
6-
from typing import List, Optional, Tuple, Union
77

88
import discord
99
from openai.types.chat.chat_completion_message import ChatCompletionMessage
@@ -103,7 +103,7 @@ def clean_name(name: str):
103103
return cleaned_name
104104

105105

106-
def get_attachments(message: discord.Message) -> List[discord.Attachment]:
106+
def get_attachments(message: discord.Message) -> list[discord.Attachment]:
107107
"""Get all attachments from context"""
108108
attachments = []
109109
if message.attachments:
@@ -118,7 +118,7 @@ def get_attachments(message: discord.Message) -> List[discord.Attachment]:
118118
return attachments
119119

120120

121-
async def wait_message(ctx: commands.Context) -> Optional[discord.Message]:
121+
async def wait_message(ctx: commands.Context) -> t.Optional[discord.Message]:
122122
def check(message: discord.Message):
123123
return message.author == ctx.author and message.channel == ctx.channel
124124

@@ -169,14 +169,14 @@ def embed_to_content(message: discord.Message) -> None:
169169
message.content = extracted
170170

171171

172-
def extract_code_blocks(content: str) -> List[str]:
172+
def extract_code_blocks(content: str) -> list[str]:
173173
code_blocks = re.findall(r"```(?:\w+)(.*?)```", content, re.DOTALL)
174174
if not code_blocks:
175175
code_blocks = re.findall(r"```(.*?)```", content, re.DOTALL)
176176
return [block.strip() for block in code_blocks]
177177

178178

179-
def extract_code_blocks_with_lang(content: str) -> List[Tuple[str, str]]:
179+
def extract_code_blocks_with_lang(content: str) -> list[tuple[str, str]]:
180180
code_blocks = re.findall(r"```(\w+)(.*?)```", content, re.DOTALL)
181181
if not code_blocks:
182182
code_blocks = re.findall(r"```(.*?)```", content, re.DOTALL)
@@ -223,8 +223,8 @@ def get_params(
223223
bot: Red,
224224
guild: discord.Guild,
225225
now: datetime,
226-
author: Optional[discord.Member],
227-
channel: Optional[Union[discord.TextChannel, discord.Thread, discord.ForumChannel]],
226+
author: t.Optional[discord.Member],
227+
channel: t.Optional[discord.TextChannel | discord.Thread | discord.ForumChannel],
228228
extras: dict,
229229
) -> dict:
230230
roles = [role for role in author.roles if "everyone" not in role.name] if author else []
@@ -262,9 +262,9 @@ def get_params(
262262

263263

264264
async def ensure_message_compatibility(
265-
messages: List[dict],
265+
messages: list[dict],
266266
conf: GuildSettings,
267-
user: Optional[discord.Member],
267+
user: t.Optional[discord.Member],
268268
) -> bool:
269269
cleaned = False
270270

@@ -281,7 +281,7 @@ async def ensure_message_compatibility(
281281
return cleaned
282282

283283

284-
async def ensure_supports_vision(messages: List[dict], conf: GuildSettings, user: Optional[discord.Member]) -> bool:
284+
async def ensure_supports_vision(messages: list[dict], conf: GuildSettings, user: t.Optional[discord.Member]) -> bool:
285285
"""Make sure that if a conversation payload contains images that the model supports vision"""
286286
cleaned = False
287287

@@ -301,7 +301,7 @@ async def ensure_supports_vision(messages: List[dict], conf: GuildSettings, user
301301
return cleaned
302302

303303

304-
async def purge_images(messages: List[dict]) -> bool:
304+
async def purge_images(messages: list[dict]) -> bool:
305305
"""Remove all images sourced from URLs from the message payload"""
306306
cleaned = False
307307
for idx, message in enumerate(list(messages)):
@@ -319,6 +319,36 @@ async def purge_images(messages: List[dict]) -> bool:
319319
return cleaned
320320

321321

322+
def clean_text_content(text: str) -> tuple[str, bool]:
323+
"""Remove invisible Unicode characters that AI detectors might flag.
324+
325+
Returns: (cleaned_text, was_modified)
326+
"""
327+
if not text:
328+
return text, False
329+
330+
original_length = len(text)
331+
to_clean = [
332+
"\u200b", # Zero-width space
333+
"\u200c", # Zero-width non-joiner
334+
"\u200d", # Zero-width joiner
335+
"\u2060", # Invisible separator
336+
"\u2061", # Invisible times
337+
"\u00ad", # Soft hyphen
338+
"\u180e", # Mongolian vowel separator
339+
"\u200b-", # Zero-width space (non-breaking)
340+
"\u200f", # Right-to-left mark
341+
"\u202a-", # Left-to-right embedding
342+
"\u202e", # Right-to-left embedding
343+
"\u2066-", # Left-to-right override
344+
"\u2069", # Pop directional formatting
345+
"\ufeff", # Zero-width no-break space (BOM)
346+
]
347+
for char in to_clean:
348+
text = text.replace(char, "")
349+
return text, len(text) != original_length
350+
351+
322352
async def clean_response(response: ChatCompletionMessage) -> bool:
323353
"""Clean the model response since its stupid and breaks itself
324354
@@ -335,9 +365,9 @@ async def clean_response(response: ChatCompletionMessage) -> bool:
335365
```
336366
Will return: Bad Request Error(400): 'multi_tool_use.create_ticket_for_user' does not match '^[a-zA-Z0-9_-]{1,64}$' - 'messages.16.tool_calls.0.function.name'
337367
"""
338-
if not response.tool_calls and not response.function_call:
339-
return False
340368
modified = False
369+
370+
# Clean function/tool names
341371
if response.tool_calls:
342372
for tool_call in response.tool_calls:
343373
original = tool_call.function.name
@@ -351,10 +381,27 @@ async def clean_response(response: ChatCompletionMessage) -> bool:
351381
if cleaned != original:
352382
response.function_call.name = cleaned
353383
modified = True
384+
385+
# Clean content from invisible Unicode characters
386+
if response.content:
387+
if isinstance(response.content, str):
388+
cleaned_content, was_cleaned = clean_text_content(response.content)
389+
if was_cleaned:
390+
response.content = cleaned_content
391+
modified = True
392+
elif isinstance(response.content, list):
393+
# Handle multi-modal content (list of content items)
394+
for item in response.content:
395+
if item.get("type") == "text" and "text" in item:
396+
cleaned_text, was_cleaned = clean_text_content(item["text"])
397+
if was_cleaned:
398+
item["text"] = cleaned_text
399+
modified = True
400+
354401
return modified
355402

356403

357-
async def clean_responses(messages: List[dict]) -> bool:
404+
async def clean_responses(messages: list[dict]) -> bool:
358405
"""Same as clean_response but cleans the whole message payload"""
359406
modified = False
360407
for message in messages:
@@ -369,7 +416,7 @@ async def clean_responses(messages: List[dict]) -> bool:
369416
return modified
370417

371418

372-
async def ensure_tool_consistency(messages: List[dict]) -> bool:
419+
async def ensure_tool_consistency(messages: list[dict]) -> bool:
373420
"""
374421
Ensure all tool calls satisfy schema requirements, modifying the messages payload in-place.
375422

0 commit comments

Comments
 (0)