diff --git a/pyproject.toml b/pyproject.toml
index 5319a8aaf..78b88cd67 100644
--- a/pyproject.toml
+++ b/pyproject.toml
@@ -71,7 +71,7 @@ dependencies = [
"langchain-text-splitters>=1.1.2",
"chromadb>=1.0.0,<2.0.0",
"qdrant-client (>=1.15.1,<2.0.0)",
- "langbot-plugin==0.7.11rc1",
+ "langbot-plugin @ git+https://github.com/langbot-app/langbot-plugin-sdk.git@8c29bc0787be3b514fb379c5154dfc5eaa561cbd",
"asyncpg>=0.30.0",
"line-bot-sdk>=3.19.0",
"matrix-nio>=0.25.2",
diff --git a/src/langbot/libs/dingtalk_api/api.py b/src/langbot/libs/dingtalk_api/api.py
index 91aa55a58..48032b72a 100644
--- a/src/langbot/libs/dingtalk_api/api.py
+++ b/src/langbot/libs/dingtalk_api/api.py
@@ -80,7 +80,9 @@ def is_stream_card_configured(config: dict) -> bool:
return False
template_id = config.get('card_template_id')
# Older manifests stored instructional placeholder text as the default.
- return isinstance(template_id, str) and bool(template_id.strip()) and template_id.strip() != '填写你的卡片template_id'
+ return (
+ isinstance(template_id, str) and bool(template_id.strip()) and template_id.strip() != '填写你的卡片template_id'
+ )
class DingTalkClient:
@@ -283,9 +285,9 @@ class DingTalkClient:
data = response.json()
if response.status_code != 200 or data.get('errcode', 0) != 0 or data.get('code'):
raise RuntimeError(
- f"DingTalk delivery failed (HTTP {response.status_code}, "
- f"code={data.get('errcode', data.get('code', 'unknown'))}): "
- f"{data.get('errmsg', data.get('message', 'Unknown error'))}"
+ f'DingTalk delivery failed (HTTP {response.status_code}, '
+ f'code={data.get("errcode", data.get("code", "unknown"))}): '
+ f'{data.get("errmsg", data.get("message", "Unknown error"))}'
)
return data
@@ -333,7 +335,8 @@ class DingTalkClient:
response = await client.post(
'https://oapi.dingtalk.com/media/upload',
params={'access_token': token, 'type': 'image' if is_image else 'file'},
- files={'media': (name, body)}, timeout=30.0,
+ files={'media': (name, body)},
+ timeout=30.0,
)
uploaded = self._check_send_result(response)
media_id = uploaded.get('media_id')
@@ -341,9 +344,11 @@ class DingTalkClient:
raise RuntimeError('DingTalk media upload returned no media ID')
if not await self.check_access_token():
await self.get_access_token()
- params = {'photoURL': media_id} if is_image else {
- 'mediaId': media_id, 'fileName': name, 'fileType': os.path.splitext(name)[1].lstrip('.')
- }
+ params = (
+ {'photoURL': media_id}
+ if is_image
+ else {'mediaId': media_id, 'fileName': name, 'fileType': os.path.splitext(name)[1].lstrip('.')}
+ )
payload = {
'robotCode': self.robot_code or self.key,
'msgKey': 'sampleImageMsg' if is_image else 'sampleFile',
@@ -359,7 +364,8 @@ class DingTalkClient:
response = await client.post(
f'{DINGTALK_OPENAPI_BASE}/v1.0/robot/{endpoint}',
headers={'x-acs-dingtalk-access-token': self.access_token},
- json=payload, timeout=30.0,
+ json=payload,
+ timeout=30.0,
)
return self._check_send_result(response)
@@ -724,7 +730,14 @@ class DingTalkClient:
else:
open_space_id = f'dtv1.card//IM_ROBOT.{incoming_message.sender_staff_id}'
- card_param_map = {'content': '', 'answer': '', 'reasoning': '', 'flowStatus': '1', 'brand': '', 'hasReasoning': ''}
+ card_param_map = {
+ 'content': '',
+ 'answer': '',
+ 'reasoning': '',
+ 'flowStatus': '1',
+ 'brand': '',
+ 'hasReasoning': '',
+ }
if incoming_message.message_type == 'text':
card_param_map['query'] = incoming_message.get_text_list()[0]
else:
diff --git a/src/langbot/libs/dingtalk_api/thinking.py b/src/langbot/libs/dingtalk_api/thinking.py
index a58d51f75..d4a8b54ee 100644
--- a/src/langbot/libs/dingtalk_api/thinking.py
+++ b/src/langbot/libs/dingtalk_api/thinking.py
@@ -2,17 +2,17 @@
import re
-_MARKER = re.compile(r"?think>", re.IGNORECASE)
+_MARKER = re.compile(r'?think>', re.IGNORECASE)
def visible_think_markers(content: str, markdown: bool = True) -> str:
def replace(match):
tag = match.group(1).lower()
- separator = "\n\n" if markdown else "\n"
- return separator + (f"`{tag}`" if markdown else tag) + separator
+ separator = '\n\n' if markdown else '\n'
+ return separator + (f'`{tag}`' if markdown else tag) + separator
# Normalize only marker boundaries; preserve paragraph spacing in the body.
- return re.sub(r"[ \t\r\n]*(?think>)[ \t\r\n]*", replace, content, flags=re.IGNORECASE).strip()
+ return re.sub(r'[ \t\r\n]*(?think>)[ \t\r\n]*', replace, content, flags=re.IGNORECASE).strip()
def card_text_fields(content: str) -> dict[str, str]:
@@ -20,20 +20,20 @@ def card_text_fields(content: str) -> dict[str, str]:
inside = False
offset = 0
for match in _MARKER.finditer(content):
- (thinking if inside else answer).append(content[offset:match.start()])
- inside = match.group(0).lower() == "
- {eventPatternLabel(selected.metadata.event_type ?? '', t)}
-
- {new Date(selected.created_at * 1000).toLocaleString()}
-
+ {eventPatternLabel(selected.metadata.event_type ?? '', t)}
+
+ {new Date(selected.created_at * 1000).toLocaleString()}
+ {t('agents.monitoring.runStatus')} {stateLabel(selected)}
+ {t('agents.monitoring.runStatus')}
+
+ {stateLabel(selected)}
+ {t('agents.monitoring.toolCalls')} {toolSteps.length}
+ {t('agents.monitoring.toolCalls')}
+
+ {toolSteps.length}
+ {t('agents.monitoring.delivery')}
- {t(`agents.monitoring.${sendFailed ? 'sendFailed' : sentCount ? 'sendCount' : 'noSend'}`, { count: sentCount })}
+
+ {t('agents.monitoring.delivery')}
+
+ {t(
+ `agents.monitoring.${sendFailed ? 'sendFailed' : sentCount ? 'sendCount' : 'noSend'}`,
+ { count: sentCount },
+ )}
{t('agents.monitoring.noSteps')}
+ {t('agents.monitoring.noSteps')}
+ {step.text} {replyText} {actionText} {t('agents.monitoring.recipient')}: {summary.recipient} {step.error} {t(`agents.monitoring.${mock ? 'simulatedAction' : 'actionCompleted'}`)}
+ {step.text}
+
+ {replyText}
+
+ {actionText}
+
+ {t('agents.monitoring.recipient')}: {summary.recipient}
+
+ {step.error}
+
+ {t(
+ `agents.monitoring.${mock ? 'simulatedAction' : 'actionCompleted'}`,
+ )}
+
- {String(event.data.text)}
-
+ {String(event.data.text)}
+
- {t('bots.noMatchingAdapters')}
-
+ {t('bots.noMatchingAdapters')}
+ {t('botSetup.warning')} {text('intro')}
+ {text('intro')}
+ {text('instructions')} {t('bots.legacyAdaptersHint')}
+ {t('bots.legacyAdaptersHint')}
+ {t('agents.eventProcessor.input')}
-
-
-
+ {t('agents.eventProcessor.input')}
+
+
+
+
{t('agents.monitoring.execution')}
- {steps.length}
+
+ {steps.length}
+
- {!steps.length &&
- {step.kind === 'message' ? t('agents.debugTextOutput') : labels[step.name] || step.name}
+ {step.kind === 'message'
+ ? t('agents.debugTextOutput')
+ : labels[step.name] || step.name}
- {status && {text('title')}
- {t('bots.legacyAdapters')}
-
{t('monitoring.execution.detail.duration')}
-{record.duration_ms != null ? formatRunDuration(record.duration_ms) : '—'}
++ {t('monitoring.execution.detail.duration')} +
++ {record.duration_ms != null + ? formatRunDuration(record.duration_ms) + : '—'} +
{t('monitoring.llmCalls.totalTokens')}
-{record.usage?.total_tokens?.toLocaleString() ?? '—'}
++ {t('monitoring.llmCalls.totalTokens')} +
++ {record.usage?.total_tokens?.toLocaleString() ?? '—'} +
{t('monitoring.unified.deliveries')}
-{visible?.pages.deliveries?.items.length ?? 0}{visible?.pages.deliveries?.has_more ? '+' : ''}
++ {t('monitoring.unified.deliveries')} +
++ {visible?.pages.deliveries?.items.length ?? 0} + {visible?.pages.deliveries?.has_more ? '+' : ''} +
- {record.status_reason.split('; ').map((reason) => - reason === 'failure_reason_not_recorded' - ? t('common.failureReasonNotRecorded') : reason, - ).join('; ')} -
- )} + {record.status_reason && + !['stop', 'completed', 'success'].includes( + record.status_reason, + ) && ( ++ {record.status_reason + .split('; ') + .map((reason) => + reason === 'failure_reason_not_recorded' + ? t('common.failureReasonNotRecorded') + : reason, + ) + .join('; ')} +
+ )} {mainSections.map((section) => renderSection(section, true))} {secondarySections.map((section) => renderSection(section))}