evalforge-runtime 0.2.2__tar.gz → 0.2.5__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {evalforge_runtime-0.2.2 → evalforge_runtime-0.2.5}/PKG-INFO +1 -1
- {evalforge_runtime-0.2.2 → evalforge_runtime-0.2.5}/pyproject.toml +1 -1
- {evalforge_runtime-0.2.2 → evalforge_runtime-0.2.5}/src/evalforge_runtime/connectors/gmail.py +87 -43
- {evalforge_runtime-0.2.2 → evalforge_runtime-0.2.5}/.github/workflows/workflow.yml +0 -0
- {evalforge_runtime-0.2.2 → evalforge_runtime-0.2.5}/.gitignore +0 -0
- {evalforge_runtime-0.2.2 → evalforge_runtime-0.2.5}/LICENSE +0 -0
- {evalforge_runtime-0.2.2 → evalforge_runtime-0.2.5}/README.md +0 -0
- {evalforge_runtime-0.2.2 → evalforge_runtime-0.2.5}/docs/decisions/001-file-forwarding-in-process-chains.md +0 -0
- {evalforge_runtime-0.2.2 → evalforge_runtime-0.2.5}/fixtures/evalforge.config.yaml +0 -0
- {evalforge_runtime-0.2.2 → evalforge_runtime-0.2.5}/src/evalforge_runtime/__init__.py +0 -0
- {evalforge_runtime-0.2.2 → evalforge_runtime-0.2.5}/src/evalforge_runtime/__main__.py +0 -0
- {evalforge_runtime-0.2.2 → evalforge_runtime-0.2.5}/src/evalforge_runtime/actions/__init__.py +0 -0
- {evalforge_runtime-0.2.2 → evalforge_runtime-0.2.5}/src/evalforge_runtime/actions/base.py +0 -0
- {evalforge_runtime-0.2.2 → evalforge_runtime-0.2.5}/src/evalforge_runtime/actions/builtins/__init__.py +0 -0
- {evalforge_runtime-0.2.2 → evalforge_runtime-0.2.5}/src/evalforge_runtime/actions/builtins/email_forward.py +0 -0
- {evalforge_runtime-0.2.2 → evalforge_runtime-0.2.5}/src/evalforge_runtime/actions/builtins/email_mark_read.py +0 -0
- {evalforge_runtime-0.2.2 → evalforge_runtime-0.2.5}/src/evalforge_runtime/actions/builtins/email_move.py +0 -0
- {evalforge_runtime-0.2.2 → evalforge_runtime-0.2.5}/src/evalforge_runtime/actions/builtins/email_reply.py +0 -0
- {evalforge_runtime-0.2.2 → evalforge_runtime-0.2.5}/src/evalforge_runtime/actions/builtins/email_send.py +0 -0
- {evalforge_runtime-0.2.2 → evalforge_runtime-0.2.5}/src/evalforge_runtime/actions/builtins/file_save_output.py +0 -0
- {evalforge_runtime-0.2.2 → evalforge_runtime-0.2.5}/src/evalforge_runtime/actions/builtins/gdrive_upload.py +0 -0
- {evalforge_runtime-0.2.2 → evalforge_runtime-0.2.5}/src/evalforge_runtime/actions/builtins/process_call.py +0 -0
- {evalforge_runtime-0.2.2 → evalforge_runtime-0.2.5}/src/evalforge_runtime/actions/builtins/sharepoint_upload.py +0 -0
- {evalforge_runtime-0.2.2 → evalforge_runtime-0.2.5}/src/evalforge_runtime/actions/builtins/webhook_post.py +0 -0
- {evalforge_runtime-0.2.2 → evalforge_runtime-0.2.5}/src/evalforge_runtime/actions/runner.py +0 -0
- {evalforge_runtime-0.2.2 → evalforge_runtime-0.2.5}/src/evalforge_runtime/auth.py +0 -0
- {evalforge_runtime-0.2.2 → evalforge_runtime-0.2.5}/src/evalforge_runtime/condition.py +0 -0
- {evalforge_runtime-0.2.2 → evalforge_runtime-0.2.5}/src/evalforge_runtime/config.py +0 -0
- {evalforge_runtime-0.2.2 → evalforge_runtime-0.2.5}/src/evalforge_runtime/connectors/__init__.py +0 -0
- {evalforge_runtime-0.2.2 → evalforge_runtime-0.2.5}/src/evalforge_runtime/connectors/base.py +0 -0
- {evalforge_runtime-0.2.2 → evalforge_runtime-0.2.5}/src/evalforge_runtime/connectors/exchange.py +0 -0
- {evalforge_runtime-0.2.2 → evalforge_runtime-0.2.5}/src/evalforge_runtime/connectors/slack.py +0 -0
- {evalforge_runtime-0.2.2 → evalforge_runtime-0.2.5}/src/evalforge_runtime/connectors/webhook.py +0 -0
- {evalforge_runtime-0.2.2 → evalforge_runtime-0.2.5}/src/evalforge_runtime/db.py +0 -0
- {evalforge_runtime-0.2.2 → evalforge_runtime-0.2.5}/src/evalforge_runtime/executor.py +0 -0
- {evalforge_runtime-0.2.2 → evalforge_runtime-0.2.5}/src/evalforge_runtime/files.py +0 -0
- {evalforge_runtime-0.2.2 → evalforge_runtime-0.2.5}/src/evalforge_runtime/observability.py +0 -0
- {evalforge_runtime-0.2.2 → evalforge_runtime-0.2.5}/src/evalforge_runtime/pipeline.py +0 -0
- {evalforge_runtime-0.2.2 → evalforge_runtime-0.2.5}/src/evalforge_runtime/scheduler.py +0 -0
- {evalforge_runtime-0.2.2 → evalforge_runtime-0.2.5}/src/evalforge_runtime/server.py +0 -0
- {evalforge_runtime-0.2.2 → evalforge_runtime-0.2.5}/src/evalforge_runtime/storage.py +0 -0
- {evalforge_runtime-0.2.2 → evalforge_runtime-0.2.5}/src/evalforge_runtime/types.py +0 -0
- {evalforge_runtime-0.2.2 → evalforge_runtime-0.2.5}/src/evalforge_runtime/ui.py +0 -0
- {evalforge_runtime-0.2.2 → evalforge_runtime-0.2.5}/tests/__init__.py +0 -0
- {evalforge_runtime-0.2.2 → evalforge_runtime-0.2.5}/tests/conftest.py +0 -0
- {evalforge_runtime-0.2.2 → evalforge_runtime-0.2.5}/tests/test_config.py +0 -0
- {evalforge_runtime-0.2.2 → evalforge_runtime-0.2.5}/tests/test_connectors.py +0 -0
- {evalforge_runtime-0.2.2 → evalforge_runtime-0.2.5}/tests/test_db.py +0 -0
- {evalforge_runtime-0.2.2 → evalforge_runtime-0.2.5}/tests/test_executor.py +0 -0
- {evalforge_runtime-0.2.2 → evalforge_runtime-0.2.5}/tests/test_langfuse.py +0 -0
- {evalforge_runtime-0.2.2 → evalforge_runtime-0.2.5}/tests/test_pipeline.py +0 -0
- {evalforge_runtime-0.2.2 → evalforge_runtime-0.2.5}/tests/test_reviews.py +0 -0
- {evalforge_runtime-0.2.2 → evalforge_runtime-0.2.5}/tests/test_server.py +0 -0
- {evalforge_runtime-0.2.2 → evalforge_runtime-0.2.5}/tests/test_ui.py +0 -0
- {evalforge_runtime-0.2.2 → evalforge_runtime-0.2.5}/uv.lock +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: evalforge-runtime
|
|
3
|
-
Version: 0.2.
|
|
3
|
+
Version: 0.2.5
|
|
4
4
|
Summary: Runtime engine for EvalForge generated applications
|
|
5
5
|
Project-URL: Homepage, https://github.com/JannisConen/evalforge-runtime
|
|
6
6
|
Project-URL: Repository, https://github.com/JannisConen/evalforge-runtime
|
{evalforge_runtime-0.2.2 → evalforge_runtime-0.2.5}/src/evalforge_runtime/connectors/gmail.py
RENAMED
|
@@ -220,8 +220,12 @@ class GmailConnector(Connector):
|
|
|
220
220
|
headers = {"Authorization": f"Bearer {token}"}
|
|
221
221
|
|
|
222
222
|
# Add date filter to Gmail search query
|
|
223
|
+
# Normalize filter value: "unread" → "is:unread" for Gmail API search syntax
|
|
224
|
+
api_filter = self._filter
|
|
225
|
+
if api_filter in ("unread", "all"):
|
|
226
|
+
api_filter = "is:unread" if api_filter == "unread" else ""
|
|
223
227
|
since_date = (datetime.now(timezone.utc) - timedelta(minutes=self._max_age_minutes)).strftime("%Y/%m/%d")
|
|
224
|
-
query = f"{
|
|
228
|
+
query = f"{api_filter} after:{since_date}".strip()
|
|
225
229
|
logger.debug("Gmail API query: %s", query)
|
|
226
230
|
|
|
227
231
|
async with httpx.AsyncClient() as client:
|
|
@@ -459,10 +463,82 @@ class GmailConnector(Connector):
|
|
|
459
463
|
else:
|
|
460
464
|
await self._forward_api(message_id, to, body)
|
|
461
465
|
|
|
466
|
+
def _build_forward_message(
|
|
467
|
+
self, original: email_lib.message.Message, to: str, fwd_subject: str, comment: str = ""
|
|
468
|
+
) -> email.mime.text.MIMEText | email.mime.multipart.MIMEMultipart:
|
|
469
|
+
"""Build a forward message with inline-quoted body and original attachments re-attached."""
|
|
470
|
+
import email.mime.multipart
|
|
471
|
+
import email.mime.base
|
|
472
|
+
|
|
473
|
+
html_body = self._build_forward_html(original, comment)
|
|
474
|
+
|
|
475
|
+
# Collect attachments from the original
|
|
476
|
+
attachments = []
|
|
477
|
+
if original.is_multipart():
|
|
478
|
+
for part in original.walk():
|
|
479
|
+
cd = str(part.get("Content-Disposition", ""))
|
|
480
|
+
if "attachment" in cd:
|
|
481
|
+
attachments.append(part)
|
|
482
|
+
|
|
483
|
+
if attachments:
|
|
484
|
+
fwd = email.mime.multipart.MIMEMultipart()
|
|
485
|
+
fwd["Subject"] = fwd_subject
|
|
486
|
+
fwd["To"] = to
|
|
487
|
+
fwd.attach(email.mime.text.MIMEText(html_body, "html", "utf-8"))
|
|
488
|
+
for part in attachments:
|
|
489
|
+
fwd.attach(part)
|
|
490
|
+
else:
|
|
491
|
+
fwd = email.mime.text.MIMEText(html_body, "html", "utf-8")
|
|
492
|
+
fwd["Subject"] = fwd_subject
|
|
493
|
+
fwd["To"] = to
|
|
494
|
+
|
|
495
|
+
return fwd
|
|
496
|
+
|
|
497
|
+
def _build_forward_html(
|
|
498
|
+
self, original: email_lib.message.Message, comment: str = ""
|
|
499
|
+
) -> str:
|
|
500
|
+
"""Build an inline-quoted HTML body for forwarding, like Gmail/Outlook do."""
|
|
501
|
+
from_addr = str(original.get("From", ""))
|
|
502
|
+
date = str(original.get("Date", ""))
|
|
503
|
+
subject = str(original.get("Subject", ""))
|
|
504
|
+
to_addr = str(original.get("To", ""))
|
|
505
|
+
|
|
506
|
+
# Extract plain-text or HTML body from original
|
|
507
|
+
orig_body = ""
|
|
508
|
+
if original.is_multipart():
|
|
509
|
+
for part in original.walk():
|
|
510
|
+
if part.get_content_type() == "text/html":
|
|
511
|
+
payload = part.get_payload(decode=True)
|
|
512
|
+
if payload:
|
|
513
|
+
orig_body = payload.decode(part.get_content_charset() or "utf-8", errors="replace")
|
|
514
|
+
break
|
|
515
|
+
if part.get_content_type() == "text/plain" and not orig_body:
|
|
516
|
+
payload = part.get_payload(decode=True)
|
|
517
|
+
if payload:
|
|
518
|
+
text = payload.decode(part.get_content_charset() or "utf-8", errors="replace")
|
|
519
|
+
orig_body = f"<pre>{text}</pre>"
|
|
520
|
+
else:
|
|
521
|
+
payload = original.get_payload(decode=True)
|
|
522
|
+
if payload:
|
|
523
|
+
text = payload.decode(original.get_content_charset() or "utf-8", errors="replace")
|
|
524
|
+
orig_body = f"<pre>{text}</pre>" if original.get_content_type() == "text/plain" else text
|
|
525
|
+
|
|
526
|
+
quoted = (
|
|
527
|
+
f'<div style="border-left:2px solid #ccc;padding-left:8px;margin-top:16px;color:#555">'
|
|
528
|
+
f'<p><b>---------- Forwarded message ----------</b><br>'
|
|
529
|
+
f'<b>From:</b> {from_addr}<br>'
|
|
530
|
+
f'<b>Date:</b> {date}<br>'
|
|
531
|
+
f'<b>Subject:</b> {subject}<br>'
|
|
532
|
+
f'<b>To:</b> {to_addr}</p>'
|
|
533
|
+
f'{orig_body}'
|
|
534
|
+
f'</div>'
|
|
535
|
+
)
|
|
536
|
+
return (comment + "<br><br>" if comment else "") + quoted
|
|
537
|
+
|
|
462
538
|
async def _forward_smtp(self, message_id: str, to: str, body: str) -> None:
|
|
463
|
-
"""Forward via SMTP — fetches original via IMAP and
|
|
539
|
+
"""Forward via SMTP — fetches original via IMAP and inlines content."""
|
|
464
540
|
def _do_forward() -> None:
|
|
465
|
-
mail = imaplib.IMAP4_SSL("imap.gmail.com")
|
|
541
|
+
mail = imaplib.IMAP4_SSL("imap.gmail.com", timeout=30)
|
|
466
542
|
mail.login(self._mailbox, self.secrets["GMAIL_APP_PASSWORD"])
|
|
467
543
|
mail.select("INBOX")
|
|
468
544
|
|
|
@@ -473,22 +549,10 @@ class GmailConnector(Connector):
|
|
|
473
549
|
original = email_lib.message_from_bytes(msg_data[0][1])
|
|
474
550
|
subject = str(original.get("Subject", ""))
|
|
475
551
|
fwd_subject = subject if subject.lower().startswith("fwd:") else f"Fwd: {subject}"
|
|
476
|
-
|
|
477
|
-
import email.mime.multipart
|
|
478
|
-
fwd = email.mime.multipart.MIMEMultipart()
|
|
552
|
+
fwd = self._build_forward_message(original, to, fwd_subject, body)
|
|
479
553
|
fwd["From"] = self._mailbox
|
|
480
|
-
fwd["To"] = to
|
|
481
|
-
fwd["Subject"] = fwd_subject
|
|
482
|
-
|
|
483
|
-
if body:
|
|
484
|
-
fwd.attach(email.mime.text.MIMEText(body, "html"))
|
|
485
|
-
|
|
486
|
-
# Attach original as message/rfc822
|
|
487
|
-
import email.mime.message
|
|
488
|
-
attached = email.mime.message.MIMEMessage(original)
|
|
489
|
-
fwd.attach(attached)
|
|
490
554
|
|
|
491
|
-
with smtplib.SMTP_SSL("smtp.gmail.com", 465) as server:
|
|
555
|
+
with smtplib.SMTP_SSL("smtp.gmail.com", 465, timeout=30) as server:
|
|
492
556
|
server.login(self._mailbox, self.secrets["GMAIL_APP_PASSWORD"])
|
|
493
557
|
server.send_message(fwd)
|
|
494
558
|
logger.info("Message %s forwarded via SMTP to %s", message_id, to)
|
|
@@ -497,26 +561,11 @@ class GmailConnector(Connector):
|
|
|
497
561
|
await asyncio.to_thread(_do_forward)
|
|
498
562
|
|
|
499
563
|
async def _forward_api(self, message_id: str, to: str, body: str) -> None:
|
|
500
|
-
"""Forward via Gmail API — fetches
|
|
564
|
+
"""Forward via Gmail API — fetches original and inlines content."""
|
|
501
565
|
token = await self._acquire_token()
|
|
502
566
|
|
|
503
567
|
async with httpx.AsyncClient() as client:
|
|
504
|
-
# Fetch
|
|
505
|
-
resp = await client.get(
|
|
506
|
-
f"https://gmail.googleapis.com/gmail/v1/users/{self._mailbox}"
|
|
507
|
-
f"/messages/{message_id}?format=metadata",
|
|
508
|
-
headers={"Authorization": f"Bearer {token}"},
|
|
509
|
-
)
|
|
510
|
-
resp.raise_for_status()
|
|
511
|
-
msg = resp.json()
|
|
512
|
-
headers_map = {
|
|
513
|
-
h["name"].lower(): h["value"]
|
|
514
|
-
for h in msg.get("payload", {}).get("headers", [])
|
|
515
|
-
}
|
|
516
|
-
subject = headers_map.get("subject", "")
|
|
517
|
-
fwd_subject = subject if subject.lower().startswith("fwd:") else f"Fwd: {subject}"
|
|
518
|
-
|
|
519
|
-
# Fetch raw RFC822
|
|
568
|
+
# Fetch raw RFC822 for full original content
|
|
520
569
|
raw_resp = await client.get(
|
|
521
570
|
f"https://gmail.googleapis.com/gmail/v1/users/{self._mailbox}"
|
|
522
571
|
f"/messages/{message_id}?format=raw",
|
|
@@ -526,15 +575,10 @@ class GmailConnector(Connector):
|
|
|
526
575
|
raw_bytes = base64.urlsafe_b64decode(raw_resp.json().get("raw", ""))
|
|
527
576
|
|
|
528
577
|
original = email_lib.message_from_bytes(raw_bytes)
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
fwd =
|
|
533
|
-
fwd["to"] = to
|
|
534
|
-
fwd["subject"] = fwd_subject
|
|
535
|
-
if body:
|
|
536
|
-
fwd.attach(email.mime.text.MIMEText(body, "html"))
|
|
537
|
-
fwd.attach(email.mime.message.MIMEMessage(original))
|
|
578
|
+
subject = str(original.get("Subject", ""))
|
|
579
|
+
fwd_subject = subject if subject.lower().startswith("fwd:") else f"Fwd: {subject}"
|
|
580
|
+
fwd = self._build_forward_message(original, to, fwd_subject, body)
|
|
581
|
+
fwd["from"] = self._mailbox
|
|
538
582
|
|
|
539
583
|
raw_fwd = base64.urlsafe_b64encode(fwd.as_bytes()).decode("utf-8")
|
|
540
584
|
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{evalforge_runtime-0.2.2 → evalforge_runtime-0.2.5}/src/evalforge_runtime/actions/__init__.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{evalforge_runtime-0.2.2 → evalforge_runtime-0.2.5}/src/evalforge_runtime/connectors/__init__.py
RENAMED
|
File without changes
|
{evalforge_runtime-0.2.2 → evalforge_runtime-0.2.5}/src/evalforge_runtime/connectors/base.py
RENAMED
|
File without changes
|
{evalforge_runtime-0.2.2 → evalforge_runtime-0.2.5}/src/evalforge_runtime/connectors/exchange.py
RENAMED
|
File without changes
|
{evalforge_runtime-0.2.2 → evalforge_runtime-0.2.5}/src/evalforge_runtime/connectors/slack.py
RENAMED
|
File without changes
|
{evalforge_runtime-0.2.2 → evalforge_runtime-0.2.5}/src/evalforge_runtime/connectors/webhook.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|