agentle 0.9.39__py3-none-any.whl → 0.9.41__py3-none-any.whl

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.
@@ -98,7 +98,13 @@ class WhatsAppBotConfig(BaseModel):
98
98
  description="Default error message",
99
99
  )
100
100
  welcome_message: str | None = Field(
101
- default=None, description="Message to send on first interaction"
101
+ default=None, description="Message to send on first interaction (or caption if welcome image is set)"
102
+ )
103
+ welcome_image_url: str | None = Field(
104
+ default=None, description="URL of welcome image to send on first interaction"
105
+ )
106
+ welcome_image_base64: str | None = Field(
107
+ default=None, description="Base64-encoded welcome image to send on first interaction (alternative to welcome_image_url)"
102
108
  )
103
109
 
104
110
  # === Message Batching (Simplified) ===
@@ -257,6 +263,8 @@ class WhatsAppBotConfig(BaseModel):
257
263
  max_split_messages: int | None = None,
258
264
  error_message: str | None = None,
259
265
  welcome_message: str | None = None,
266
+ welcome_image_url: str | None = None,
267
+ welcome_image_base64: str | None = None,
260
268
  # Message Batching
261
269
  enable_message_batching: bool | None = None,
262
270
  batch_delay_seconds: float | None = None,
@@ -366,6 +374,10 @@ class WhatsAppBotConfig(BaseModel):
366
374
  overrides["error_message"] = error_message
367
375
  if welcome_message is not None:
368
376
  overrides["welcome_message"] = welcome_message
377
+ if welcome_image_url is not None:
378
+ overrides["welcome_image_url"] = welcome_image_url
379
+ if welcome_image_base64 is not None:
380
+ overrides["welcome_image_base64"] = welcome_image_base64
369
381
 
370
382
  # Message Batching
371
383
  if enable_message_batching is not None:
@@ -77,18 +77,69 @@ class WhatsAppWebhookPayload(BaseModel):
77
77
  ForwardedFrom: str | None = Field(default=None) # For forwarded messages
78
78
  FrequentlyForwarded: str | None = Field(default=None) # "true" or "false" as string
79
79
 
80
+ # Root level fallbacks for robust extraction
81
+ remoteJid: str | None = Field(default=None)
82
+ remoteJidAlt: str | None = Field(default=None)
83
+
80
84
  def model_post_init(self, context: Any, /) -> None:
81
- if self.phone_number_id or not self.data:
85
+ if self.phone_number_id:
86
+ return
87
+
88
+ # Initialize data if missing but root fields are present
89
+ if not self.data and (self.remoteJid or self.remoteJidAlt):
90
+ # This is a bit of a hack to ensure we have a structure to hold the key
91
+ # if we only got root level JIDs
92
+ from agentle.agents.whatsapp.models.key import Key
93
+ from agentle.agents.whatsapp.models.data import Data
94
+
95
+ self.data = Data(
96
+ key=Key(
97
+ remoteJid=self.remoteJid or self.remoteJidAlt or "", fromMe=False
98
+ )
99
+ )
100
+
101
+ if not self.data:
82
102
  return
83
103
 
84
104
  key = self.data.key
85
- if "@lid" in key.remoteJid:
86
- remote_jid_alt = key.remoteJidAlt
87
- if remote_jid_alt is None:
88
- raise ValueError("No remotejidalt was provided.")
89
105
 
90
- self.phone_number_id = remote_jid_alt.split("@")[0]
91
- self.data.key.remoteJid = remote_jid_alt
106
+ # Extraction logic:
107
+ # 1. key.remoteJid
108
+ # 2. remoteJid (root)
109
+ # 3. key.remoteJidAlt
110
+ # 4. remoteJidAlt (root)
111
+
112
+ candidates = [
113
+ key.remoteJid,
114
+ self.remoteJid,
115
+ key.remoteJidAlt,
116
+ self.remoteJidAlt,
117
+ ]
118
+
119
+ # Find first valid candidate
120
+ selected_jid = next((c for c in candidates if c), None)
121
+
122
+ if not selected_jid:
123
+ # Should we raise? For now let's just return and let validation fail if strict
92
124
  return
125
+
126
+ # Optimization: treating @lid
127
+ # If the selected JID is an LID, verify if we have an ALT available
128
+ if "@lid" in selected_jid:
129
+ # If we selected a main JID that is LID, try to find an Alt
130
+ # candidates for alt: key.remoteJidAlt, self.remoteJidAlt
131
+ alt_candidates = [key.remoteJidAlt, self.remoteJidAlt]
132
+ alt_jid = next((c for c in alt_candidates if c), None)
133
+
134
+ if alt_jid:
135
+ selected_jid = alt_jid
136
+ elif not key.remoteJidAlt and not self.remoteJidAlt:
137
+ # If we have an LID but no ALT, we might be in trouble depending on the use case,
138
+ # but we proceed with what we have or raise as before
139
+ # The original code raised ValueError here
140
+ pass
93
141
 
94
- self.phone_number_id = key.remoteJid.split("@")[0]
142
+ self.phone_number_id = selected_jid.split("@")[0]
143
+
144
+ # Normalize the key inside data so the rest of the app uses the "best" JID
145
+ self.data.key.remoteJid = selected_jid
@@ -1597,15 +1597,18 @@ class EvolutionAPIProvider(WhatsAppProvider):
1597
1597
  Normalize phone number to Evolution API format.
1598
1598
 
1599
1599
  Evolution API expects phone numbers in the format: countrycode+number@s.whatsapp.net
1600
+ For Brazilian numbers, we now test with @lid format instead.
1600
1601
 
1601
1602
  Brazilian mobile format: 55 + DDD (2 digits) + 9 + 8 digits = 13 digits total
1602
- Example: 5534998137839@s.whatsapp.net
1603
+ Example: 5534998137839@lid
1603
1604
  """
1604
1605
  original_phone = phone
1605
1606
 
1606
- # Remove @s.whatsapp.net suffix if present
1607
+ # Remove @s.whatsapp.net or @lid suffix if present
1607
1608
  if "@s.whatsapp.net" in phone:
1608
1609
  phone = phone.split("@")[0]
1610
+ elif "@lid" in phone:
1611
+ phone = phone.split("@")[0]
1609
1612
 
1610
1613
  # Remove non-numeric characters
1611
1614
  phone = "".join(c for c in phone if c.isdigit())
@@ -1657,9 +1660,15 @@ class EvolutionAPIProvider(WhatsAppProvider):
1657
1660
  f"Phone number may be invalid - 5th digit is not '9': {phone}"
1658
1661
  )
1659
1662
 
1660
- # Add @s.whatsapp.net suffix if not present
1661
- if not phone.endswith("@s.whatsapp.net"):
1662
- phone = phone + "@s.whatsapp.net"
1663
+ # TESTING: Use @lid for Brazilian numbers (country code 55) instead of @s.whatsapp.net
1664
+ if not phone.endswith("@lid") and not phone.endswith("@s.whatsapp.net"):
1665
+ if phone.startswith("55"):
1666
+ # Brazilian number - use @lid
1667
+ phone = phone + "@lid"
1668
+ logger.info(f"🧪 TESTING: Using @lid for Brazilian number: {phone}")
1669
+ else:
1670
+ # Non-Brazilian number - use @s.whatsapp.net
1671
+ phone = phone + "@s.whatsapp.net"
1663
1672
 
1664
1673
  if original_phone != phone:
1665
1674
  logger.info(f"Phone number normalized: {original_phone} -> {phone}")
@@ -431,23 +431,117 @@ class WhatsAppBot[T_Schema: WhatsAppResponseBase = WhatsAppResponseBase](BaseMod
431
431
  f"[MESSAGE_HANDLER] Effective chat_id para conversação: {effective_chat_id}"
432
432
  )
433
433
 
434
- # Check welcome message for first interaction
434
+ # Check welcome message/image for first interaction
435
435
  if (
436
436
  await cast(
437
437
  ConversationStore, self.agent.conversation_store
438
438
  ).get_conversation_history_length(effective_chat_id)
439
439
  == 0
440
- and self.config.welcome_message
440
+ and (
441
+ self.config.welcome_message
442
+ or self.config.welcome_image_url
443
+ or self.config.welcome_image_base64
444
+ )
441
445
  ):
442
446
  logger.info(
443
- f"[WELCOME] Sending welcome message to {message.from_number}"
447
+ f"[WELCOME] Sending welcome message/image to {message.from_number}"
444
448
  )
445
- formatted_welcome = self._format_whatsapp_markdown(
446
- self.config.welcome_message
447
- )
448
- await self.provider.send_text_message(
449
- message.from_number, formatted_welcome
449
+
450
+ # Determine if we're sending an image or text
451
+ has_welcome_image = (
452
+ self.config.welcome_image_url or self.config.welcome_image_base64
450
453
  )
454
+
455
+ if has_welcome_image:
456
+ # Prepare caption from welcome_message if available
457
+ caption = None
458
+ if self.config.welcome_message:
459
+ caption = self._format_whatsapp_markdown(
460
+ self.config.welcome_message
461
+ )
462
+
463
+ # Handle welcome image via URL
464
+ if self.config.welcome_image_url:
465
+ logger.info(
466
+ f"[WELCOME] Sending welcome image from URL to {message.from_number}"
467
+ )
468
+ await self.provider.send_media_message(
469
+ to=message.from_number,
470
+ media_url=self.config.welcome_image_url,
471
+ media_type="image",
472
+ caption=caption,
473
+ )
474
+ # Handle welcome image via base64
475
+ elif self.config.welcome_image_base64:
476
+ # Try to upload base64 to file storage if available
477
+ image_url = None
478
+ if self.file_storage_manager:
479
+ try:
480
+ import base64
481
+ import time
482
+
483
+ logger.info(
484
+ "[WELCOME] Uploading base64 welcome image to file storage"
485
+ )
486
+
487
+ # Decode base64 to bytes
488
+ image_bytes = base64.b64decode(
489
+ self.config.welcome_image_base64
490
+ )
491
+
492
+ # Generate unique filename
493
+ timestamp = int(time.time())
494
+ filename = f"welcome_image_{timestamp}.jpg"
495
+
496
+ # Upload to storage
497
+ image_url = await self.file_storage_manager.upload_file(
498
+ file_data=image_bytes,
499
+ filename=filename,
500
+ mime_type="image/jpeg",
501
+ )
502
+
503
+ logger.info(
504
+ f"[WELCOME] Welcome image uploaded to storage: {image_url}"
505
+ )
506
+ except Exception as e:
507
+ logger.warning(
508
+ f"[WELCOME] Failed to upload welcome image to storage: {e}"
509
+ )
510
+ image_url = None
511
+
512
+ # Send image via URL if upload succeeded, otherwise send text fallback
513
+ if image_url:
514
+ logger.info(
515
+ f"[WELCOME] Sending uploaded welcome image to {message.from_number}"
516
+ )
517
+ await self.provider.send_media_message(
518
+ to=message.from_number,
519
+ media_url=image_url,
520
+ media_type="image",
521
+ caption=caption,
522
+ )
523
+ else:
524
+ # Fallback to text if base64 upload failed and no URL available
525
+ logger.warning(
526
+ "[WELCOME] Could not send welcome image (base64 upload failed and no URL), falling back to text"
527
+ )
528
+ if caption:
529
+ await self.provider.send_text_message(
530
+ message.from_number, caption
531
+ )
532
+ else:
533
+ logger.warning(
534
+ "[WELCOME] No caption available, skipping welcome message"
535
+ )
536
+ else:
537
+ # Send text-only welcome message (backward compatible)
538
+ formatted_welcome = self._format_whatsapp_markdown(
539
+ self.config.welcome_message
540
+ )
541
+ await self.provider.send_text_message(
542
+ message.from_number, formatted_welcome
543
+ )
544
+
451
545
  session.message_count += 1
452
546
  await self.provider.update_session(session)
453
547
 
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: agentle
3
- Version: 0.9.39
3
+ Version: 0.9.41
4
4
  Summary: ...
5
5
  Author-email: Arthur Brenno <64020210+arthurbrenno@users.noreply.github.com>
6
6
  License-File: LICENSE
@@ -137,7 +137,7 @@ agentle/agents/ui/__init__.py,sha256=IjHRV0k2DNwvFrEHebmsXiBvmITE8nQUnsR07h9tVkU
137
137
  agentle/agents/ui/streamlit.py,sha256=9afICL0cxtG1o2pWh6vH39-NdKiVfADKiXo405F2aB0,42829
138
138
  agentle/agents/whatsapp/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
139
139
  agentle/agents/whatsapp/human_delay_calculator.py,sha256=BGCDeoNTPsMn4d_QYmG0BWGCG8SiUJC6Fk295ulAsAk,18268
140
- agentle/agents/whatsapp/whatsapp_bot.py,sha256=3ohpjdjeeX0MPxrcllLamT8lfi6y2UjSzFxYTeVLQ-o,165392
140
+ agentle/agents/whatsapp/whatsapp_bot.py,sha256=o3fa5iSD-26CcmcMw0pzU0aS9ju1Mm9vD9ZHWwfEI9o,169964
141
141
  agentle/agents/whatsapp/models/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
142
142
  agentle/agents/whatsapp/models/audio_message.py,sha256=af2apMWzxKcCtXfQN6U2qfOFoiwRj0nCUrKmrBD0whE,3067
143
143
  agentle/agents/whatsapp/models/context_info.py,sha256=sk80KuNE36S6VRnLh7n6UXmzZCXIB4E4lNxnRyVizg8,563
@@ -152,7 +152,7 @@ agentle/agents/whatsapp/models/message_context_info.py,sha256=msCSuu8uMN3G9GDaXd
152
152
  agentle/agents/whatsapp/models/quoted_message.py,sha256=QC4sp7eLPE9g9i-_f3avb0sDO7gKpkzZR2qkbxqptts,1073
153
153
  agentle/agents/whatsapp/models/video_message.py,sha256=0s4ak68euff25a_tXvYscKCFn7rQj8Rj6U89rupQnO0,3697
154
154
  agentle/agents/whatsapp/models/whatsapp_audio_message.py,sha256=AAcnjzJC1O5VjyWZaSWpG_tmZFc2-CdcPn9abjyLrpc,378
155
- agentle/agents/whatsapp/models/whatsapp_bot_config.py,sha256=hkbOAdSZbSt634mZEARuPQSer7wf3biL7dL9TFW-a7o,37164
155
+ agentle/agents/whatsapp/models/whatsapp_bot_config.py,sha256=-YLnzFOUlNwNvROTOlJ1W8RLNwnJ3irXI3znaMo7UnI,37831
156
156
  agentle/agents/whatsapp/models/whatsapp_contact.py,sha256=6iO6xmFs7z9hd1N9kZzGyNHYvCaUoCHn3Yi1DAJN4YU,240
157
157
  agentle/agents/whatsapp/models/whatsapp_document_message.py,sha256=ECM_hXF-3IbC9itbtZI0eA_XRNXFVefw9Mr-Lo_lrH0,323
158
158
  agentle/agents/whatsapp/models/whatsapp_image_message.py,sha256=xOAPRRSgqj9gQ2ZZOGdFWfOgtmNpE1W8mIUAmB5YTpo,314
@@ -165,13 +165,13 @@ agentle/agents/whatsapp/models/whatsapp_response_base.py,sha256=IIDONx9Ipt593tAZ
165
165
  agentle/agents/whatsapp/models/whatsapp_session.py,sha256=9G1HC-A2G9jTdpwYy3w9bnYkOGK2vvA7kdYAf32oWMU,15640
166
166
  agentle/agents/whatsapp/models/whatsapp_text_message.py,sha256=GpSwFrPC4qpQlVCWKKgYjQJKNv0qvwgYfuoD3ttLzdQ,441
167
167
  agentle/agents/whatsapp/models/whatsapp_video_message.py,sha256=-d-4hnkkxyLVNoje3a1pOEAvzWqoCLFcBn70wUpnyXY,346
168
- agentle/agents/whatsapp/models/whatsapp_webhook_payload.py,sha256=aY9pnUt4WJdvrRsXZIkmR8hP7oV9gdOJ1wJiYzFhU8w,4270
168
+ agentle/agents/whatsapp/models/whatsapp_webhook_payload.py,sha256=qtLlJJ1RxHkj89bU9tFHplv9qcV_5SqIsp1GOzQFCEU,6207
169
169
  agentle/agents/whatsapp/providers/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
170
170
  agentle/agents/whatsapp/providers/base/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
171
171
  agentle/agents/whatsapp/providers/base/whatsapp_provider.py,sha256=Iaywrv0xer4fhZprMttC-NP4-rRYdU_45UzIZQ7dkYA,5349
172
172
  agentle/agents/whatsapp/providers/evolution/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
173
173
  agentle/agents/whatsapp/providers/evolution/evolution_api_config.py,sha256=mc3jVJ1olnFgt7jP6P_eygvUVhN3XYSAYp1ozIxtAsc,1288
174
- agentle/agents/whatsapp/providers/evolution/evolution_api_provider.py,sha256=rE0OwJma7mnLiPXcK2oBFFmr69zQxTRfmHNrEvj4NoU,68539
174
+ agentle/agents/whatsapp/providers/evolution/evolution_api_provider.py,sha256=6nIItw8dwBEuWDa8lb3qaEthN4ywbSRJgYyY49EXowI,69043
175
175
  agentle/agents/whatsapp/providers/meta/__init__.py,sha256=ArZ2y9qUALahP2-c0j0ESFKmRjDHiZIurqxYC7MTWA8,1038
176
176
  agentle/agents/whatsapp/providers/meta/meta_whatsapp_config.py,sha256=ECzb76Sba0ExrO4NAB7v9HLlgAxsjyTg57mewdVt8EY,1257
177
177
  agentle/agents/whatsapp/providers/meta/meta_whatsapp_provider.py,sha256=95xvVrqp6f5Ku49fqOEHUhJUIJPGb1rRvCntoIY2JSM,35953
@@ -1018,7 +1018,7 @@ agentle/web/actions/scroll.py,sha256=WqVVAORNDK3BL1oASZBPmXJYeSVkPgAOmWA8ibYO82I
1018
1018
  agentle/web/actions/viewport.py,sha256=KCwm88Pri19Qc6GLHC69HsRxmdJz1gEEAODfggC_fHo,287
1019
1019
  agentle/web/actions/wait.py,sha256=IKEywjf-KC4ni9Gkkv4wgc7bY-hk7HwD4F-OFWlyf2w,571
1020
1020
  agentle/web/actions/write_text.py,sha256=9mxfHcpKs_L7BsDnJvOYHQwG8M0GWe61SRJAsKk3xQ8,748
1021
- agentle-0.9.39.dist-info/METADATA,sha256=koWpbsp8gWkx7AtBogPGlGRfoQQ6lo2_A7v-gBJisZs,86879
1022
- agentle-0.9.39.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
1023
- agentle-0.9.39.dist-info/licenses/LICENSE,sha256=T90S9vqRS6qP-voULxAcvwEs558wRRo6dHuZrjgcOUI,1085
1024
- agentle-0.9.39.dist-info/RECORD,,
1021
+ agentle-0.9.41.dist-info/METADATA,sha256=AYTLK3-J2KRaGK_AwjNjyzxsUlgOJciq4GxvsPgW2wI,86879
1022
+ agentle-0.9.41.dist-info/WHEEL,sha256=WLgqFyCfm_KASv4WHyYy0P3pM_m7J5L9k2skdKLirC8,87
1023
+ agentle-0.9.41.dist-info/licenses/LICENSE,sha256=T90S9vqRS6qP-voULxAcvwEs558wRRo6dHuZrjgcOUI,1085
1024
+ agentle-0.9.41.dist-info/RECORD,,
@@ -1,4 +1,4 @@
1
1
  Wheel-Version: 1.0
2
- Generator: hatchling 1.27.0
2
+ Generator: hatchling 1.28.0
3
3
  Root-Is-Purelib: true
4
4
  Tag: py3-none-any