الانتقال إلى المحتوى الرئيسي

نظرة عامة

دوال المنطق هي دوال TypeScript على جانب الخادم تعمل على منصة Twenty. يمكن تشغيلها بواسطة طلبات HTTP أو جداول cron أو أحداث قاعدة البيانات — كما يمكن إتاحتها كأدوات لوكلاء الذكاء الاصطناعي.
كل ملف وظيفة يستخدم defineLogicFunction() لتصدير تكوين مع معالج ومشغّلات اختيارية.
src/logic-functions/createPostCard.logic-function.ts
أنواع المشغّلات المتاحة:
  • httpRoute: يعرِض وظيفتك على مسار وطريقة HTTP. في شيفرة التطبيق، أضف البادئة /s/ إلى مسار التوجيه عند استخدام RestApiClient؛ يستخدم عنوان URL المنشور قاعدة TWENTY_FUNCTIONS_URL المُحدَّدة (أو \<server-url>/s إذا لم تُحدَّد).
لاستدعاء دالة منطقية يتم تشغيلها بواسطة مسار من مكون واجهة (بدون واجهة رسومية)، راجع قسم استدعاء دالة منطقية.
  • cron: يشغّل وظيفتك على جدول باستخدام تعبير CRON.
  • databaseEvent: يعمل على أحداث دورة حياة كائنات مساحة العمل. عندما تكون عملية الحدث هي updated، يمكن تحديد الحقول المحددة المراد الاستماع إليها في مصفوفة updatedFields. إذا تُركت غير معرّفة أو فارغة، فسيؤدي أي تحديث إلى تشغيل الدالة.
مثال: person.updated، *.created، company.*
  • serverRoute: يوفّر مسار HTTP واحدًا بنطاق التسجيل. تعمل دالة resolver (المُعلَنة باستخدام serverRouteTriggerSettings) في مساحة عمل المالك وتُرجِع إمّا كائن Response متزامنًا أو كلاً من مساحة العمل المستهدفة ودالة المنطق المطلوب إدراجها في قائمة الانتظار؛ في مسار الإدراج في قائمة الانتظار يؤكّد النظام الأساسي تلقّي الطلب برمز 202 ويُشغِّل ذلك الهدف في طابور العامل (worker queue). راجع مشغّل مسار الخادم.
يمكنك أيضًا تنفيذ دالة يدويًا باستخدام CLI:
يمكنك متابعة السجلات باستخدام:
في السحابة، يستهلك كل تشغيل أرصدة مساحة العمل: رسوم ثابتة لكل استدعاء بالإضافة إلى رسوم لكل ميلي ثانية عن الوقت الذي تعمل فيه الدالة، لذا فإن إبقاء timeoutSeconds محدودًا والكود سريعًا يخفض التكاليف. راجع الأرصدة لمعرفة الأسعار.

حمولة مشغل المسار

عندما يستدعي مُشغِّل المسار وظيفتك المنطقية، فإنها تتلقّى كائن RoutePayload الذي يتبع صيغة AWS HTTP API v2. استورد نوع RoutePayload من twenty-sdk/logic-function:
يحتوي نوع RoutePayload على البنية التالية:

forwardedRequestHeaders

افتراضيًا، لا تُمرَّر رؤوس HTTP من الطلبات الواردة إلى دالتك المنطقية لأسباب أمنية. للوصول إلى رؤوس محددة، أدرِجها في مصفوفة forwardedRequestHeaders:
في معالجك، يمكنك الوصول إلى الرؤوس المُمرَّرة بهذه الطريقة:
تُحوَّل أسماء الرؤوس إلى أحرف صغيرة. يمكنك الوصول إليها باستخدام مفاتيح بأحرف صغيرة (على سبيل المثال، event.headers['content-type']).

استجابة HTTP مخصصة

بشكل افتراضي، فإن إرجاع قيمة بسيطة من المعالج الخاص بك يعيدها كاستجابة 200 (بصيغة JSON للكائنات وtext/plain للسلاسل النصية). للتحكم في رمز الحالة ورؤوس الاستجابة، أعد كائن Response من twenty-sdk/logic-function:
لأسباب أمنية، يتم تقييد ترويسات الاستجابة بقائمة مسموح بها. يتم إسقاط أي ترويسة ليست في القائمة (مثل Set-Cookie، وترويسات CORS مثل Access-Control-Allow-Origin، أو ترويسات X-* المخصصة) بصمت قبل إرسال الاستجابة. ترويسات الاستجابة المسموح بها هي:
  • content-type
  • content-language
  • content-disposition
  • cache-control
  • retry-after
يجب أن يكون رمز الحالة رمز حالة HTTP صالحًا (بين 100 و599). تتم مطابقة أسماء ترويسات الاستجابة دون حساسية لحالة الأحرف.

استجابات أخطاء المنصة

إلى جانب استجابات المعالج لديك، تجيب المنصة عن استدعاءات المسارات مباشرةً في بعض الحالات: 404 عندما لا يكون المسار أو الدالة موجودًا، و403 عندما يكون التطبيق متوقفًا، و402 عندما تكون مساحة العمل قد استنفدت مخصصات رصيدها أو لا تحتوي على اشتراك نشط، و409 عند بلوغ حد التخزين، و429 عند بلوغ حد لمعدل التنفيذ أو حد للاستخدام، و422 عندما تكون dependencies الخاصة بالإنتاج للتطبيق كبيرة جدًا بحيث يتعذر تثبيتها — راجع حدود حجم dependencies.

مشغّل مسار الخادم

httpRouteTriggerSettings يوفّر دالة تحت ‎/s/‎ ويحل مساحة العمل من مضيف الطلب — وهذا يعمل عندما تكون لكل مساحة عمل نطاقها الخاص. لكن المزوّدين من جهات خارجية يرسلون أحداث كل مستأجر إلى عنوان URL واحد. في هذه الحالة، استخدم serverRouteTriggerSettings.يتكوّن المشغّل من جزأين:
  1. دالة منطق resolver — يتم التصريح عنها باستخدام serverRouteTriggerSettings — تعمل في مساحة العمل المالكة (مساحة العمل التي تمتلك تسجيل التطبيق). تفحص الطلب الوارد وتُرجِع إمّا:
    • { workspaceId, targetLogicFunctionUniversalIdentifier, payload? } — يضع النظام الأساسي ذلك الهدف في قائمة الانتظار في مساحة العمل المُحدَّدة ويؤكّد تلقّي الطلب برمز 202 { queued: true }، أو
    • Response من twenty-sdk/logic-function — تُعيد المنصّة إرسال تلك الاستجابة عبر HTTP بشكل متزامن ولا تُدرِج هدفًا في قائمة الانتظار (استخدم هذا لمصافحات التحدّي مثل Slack url_verification).
    يُعَدّ الـ resolver نقطة التفويض الوحيدة — فعنوان URL يحمل فقط معرّف الـ resolver. هذا هو المكان المفضّل للتحقق من تواقيع الطلبات: يعمل الـ resolver قبل أي تأثير جانبي، ولديه إمكانية الوصول إلى rawBody الأصلي والرؤوس المُمرَّرة، ويمكنه رفض الطلب دون لمس الهدف مطلقًا.
  2. دالة منطق target — دالة منطق عادية لكل مساحة عمل — تعمل بعد ذلك في مساحة العمل التي تم حلّها باستخدام الحمولة التي أعادها الـ resolver (أو حمولة الطلب الأصلية إذا لم يقم الـ resolver بتحويلها). قيمة الإرجاع الخاصة به لا يطّلع عليها مستدعي HTTP عندما يختار الـ resolver مسار الإضافة إلى الطابور (enqueue path).
src/logic-functions/resolve-server-route.logic-function.ts
src/logic-functions/handle-invoice-paid.logic-function.ts
يمكن الوصول إلى نقطة النهاية عند:
المقطع الأول هو معرّف تسجيل تطبيقك، والثاني هو universalIdentifier الخاص بالـ resolver من ملف manifest لديك. لا يُحلّ المسار إلا إلى resolver تابع لذلك التسجيل، في مساحة العمل الخاصة بمالكه. عندما يسجّل تطبيقك webhook نفسه، أنشئ عنوان URL من داخل دالة منطق باستخدام getServerRouteUrl؛ يحقن Twenty عنوان URL الأساسي لمسارات خادم تطبيقك باعتباره TWENTY_SERVER_ROUTES_URL:
تستمر عناوين URL التي لا تحتوي إلا على id أو universalIdentifier الخاص بالمحلل (/webhooks/server/:identifier) في الحل في الوقت الحالي، ويستجيب كلاهما بترويسة Deprecation. يعرض نموذج universalIdentifier الرمز 409 عندما تعلن عمليتا تسجيل عن المعرّف نفسه. انقل عناوين URL المسجلة لدى موفري الخدمة لديك إلى نموذج التسجيل.
الرد على طلب GET للتحقق. يتحقق بعض الموفّرين من نقطة نهاية قبل أن يرسلوا إليها، وذلك بإرسال طلب GET يحمل تحديًا إلى عنوان URL نفسه الذي سيرسلون إليه الأحداث لاحقًا عبر POST — وتُعد WhatsApp Cloud API من Meta أحدهم. لا يستجيب مسار الخادم إلا لطلبات POST ما لم تحدد خلاف ذلك، لذا أعلن الطريقتين معًا:
يصل التحدي في event.queryStringParameters، وتؤدي إعادة Response إلى إرجاعه إلى الموفّر ضمن الطلب نفسه. يُرسل نص سلسلة كـ text/plain، وهذا ما يتوقعه هؤلاء الموفّرون:
يستبدل httpMethods الإعداد الافتراضي بدلاً من الإضافة إليه، لذا فإن ['GET'] وحده يجعل المسار يرفض POST. يتم دعم GET وPOST فقط. اتركه غير معيّن ما لم يكن المزوّد يحتاج إلى الفعل الثاني: فالمسار الذي يعلن GET سيُنفَّذ محلّله بواسطة أي مستدعٍ غير موثَّق، بما في ذلك برامج الزحف وأدوات توسيع الروابط التي ترسل GET دون طلب مسبق. يُستجاب لكل ما لا تتوفر للمنصة طريقة له بالرمز 405 دون أن يُشغَّل المحلّل مطلقًا.
يجب المطالبة بالتطبيق وتثبيته في مساحة العمل الخاصة بمالكه. اتبع المطالبة بتطبيقك لتعيين مالك، ثم ثبّت التطبيق في مساحة العمل تلك. إلى أن يتحقق الأمران معًا، لن يكون لدى المحلل مكان للتشغيل وسيعيد مسار الخادم 404 (LOGIC_FUNCTION_NOT_FOUND)، حتى لو كان التطبيق مثبتًا في مكان آخر. يخفي بحث Marketplace التطبيقات ذات مسارات الخادم إلى أن يكتمل هذا الإعداد وجميع متغيرات الخادم المطلوبة. راجع تكاليف مساحة العمل الخاصة بالمالك وتوفرها للاطلاع على تفاصيل الفوترة والتشغيل.
عقد الـ Resolver. يفرض نوع LogicFunctionConfig في حزمة SDK هذا في وقت الترجمة: بمجرد تعيينك لـ serverRouteTriggerSettings، يُقيَّد الـ handler الخاص بك بأن يُرجِع إما Response، أو { workspaceId: string; targetLogicFunctionUniversalIdentifier: string; payload?: object } (أو Promise لأيٍّ منهما). على مسار الإرسال (dispatch path)، يجب أن يكون workspaceId لمساحة عمل تكون الدالة المستهدفة مثبّتة فيها، وإلا فسيتم رفض الطلب مع 404. أي نتيجة لا تطابق أياً من البنيتين — بما في ذلك تلك التي لا تكون معرّفاتها UUIDs — تُرفَض مع رمز الحالة 502.
مسؤولية التحقق من التوقيع تقع عليك — تحقّق في الـ resolver. المنصة لا تتحقق من تواقيع الطلبات. يُعَدّ الـ resolver المكان الموصى به للقيام بذلك: فهو يعمل أولًا، مع إمكانية الوصول إلى event.rawBody و الرؤوس التي أدرجتها في forwardedRequestHeaders، وأي خطأ يتم رميه (أو أي workspaceId لا يطابق) يوقف عملية الإرسال قبل استدعاء الهدف. إذا دفعت التحقق بدلًا من ذلك إلى داخل الهدف، فيجب على الهدف أن يكون حذرًا حتى لا يفقد rawBody والرؤوس — أي يجب ألّا يعيد الـ resolver خاصية payload. تحقّق دائمًا قبل أي تأثير جانبي، واستخدم مقارنة بزمن ثابت.
رموز Bearer المميزة. يوقّع بعض الموفّرين، مثل Microsoft Bot Framework، كل طلب باستخدام JWT في ترويسة Authorization بدلاً من ترويسة توقيع. يمكن لمسار خادم إدراج authorization في forwardedRequestHeaders لتلقّيه، ويتحقق المحلّل منه كما يتحقق من أي توقيع آخر. تزيل المنصة الترويسة إذا كانت تحمل رمزاً مميزاً صادراً عن Twenty، لذلك لا يتلقى مسار الخادم جلسة Twenty أبداً. لا تتم إعادة توجيه cookie وproxy-authorization أبداً، ولا يمكن لمسارات HTTP إعادة توجيه أي من هذه الترويسات الثلاث.بالنسبة لتواقيع الطلبات، يستخدم معظم المزوّدين HMAC-SHA256 للتوقيع؛ الأجزاء التي تختلف هي اسم الرأس وترميز الملخّص وسلسلة الحمولة الموقّعة. بعض الأمثلة:يُظهِر مثال الـ resolver أعلاه بالفعل تدفّق GitHub HMAC-SHA256 — عدِّل اسم الرأس وترميز الملخّص وسلسلة الحمولة الموقّعة بحسب المزوّد الذي تدمجه.
عندما يُرجِع الـ resolver كائن إرسال (dispatch object)، يستجيب المسار بـ 202 { queued: true } وتعمل الدالة المستهدفة على طابور العامل (worker queue) — المتصل لا يطّلع أبدًا على زمن استجابة الدالة المستهدفة أو نتيجتها أو حالات الفشل الخاصة بها (تُسجَّل هذه في سجلات التنفيذ). هذا يمنع عمليات إعادة الإرسال من جهة المرسِل من تضخيم تباطؤ المعالجة، وهو ما تريده عند استيعاب خطافات الويب.يعمل المحلل في مساحة عمل المالك ويستهلك أرصدتها، لذا يجيب المسار بـ 402 عندما تكون مساحة العمل قد استنفدت مخصصاتها أو لا تحتوي على اشتراك نشط، وبـ 429 عند بلوغ حد للاستخدام. لن يعيد المرسلون الذين يعيدون المحاولة عند 5xx المحاولة لهذه الحالات، وهذا ما تريده: لن يُرفع الرفض عند المحاولة التالية.عندما يجب على المتصل قراءة جسم الاستجابة في نفس الطلب (مثل challenge handshakes أو interactive acknowledgements)، أرجِع Response من الـ resolver بدلًا من ذلك. تعيد المنصّة إرجاعها بشكل متزامن وتتجاوز الطابور؛ تمر ترويساتها (headers) عبر نفس قائمة السماح (allow-list) الخاصة باستجابات مسارات HTTP. احرص على أن تكون دالة resolver سريعة — بعض المزوّدين (مثل Slack) تنتهي مهلة طلباتهم خلال بضع ثوانٍ. نظرًا لأن الـ resolver يمكن الوصول إليه كنقطة نهاية عامة، قم بحمايته من خلال تحديد المعدّل (rate limiting) على الحافة لديك.

حمولة مُحفِّز حدث قاعدة البيانات

عندما يستدعي مشغل حدث قاعدة البيانات دالة المنطق الخاصة بك، فإنها تتلقى DatabaseEventPayload واحدًا لكل سجل تم تغييره (أو DatabaseEventBatchPayload يغطي عدة سجلات عند تمكين batchMode). تجمع الحمولة بين البيانات الوصفية حول مساحة العمل والكائن المصدر وبين الحدث على مستوى السجل.
تتضمن الحمولة ما يلي:في عمليات الحذف اللين (soft deletes)، يتبع .deleted بنية نمط التحديث لأن حقل deletedAt في السجل يتغيّر. في عمليات الحذف الدائم، استخدم .destroyed.
databaseEventTriggerSettings.updatedFields يرشّح أحداث التحديث التي تُشغِّل الدالة. event.properties.updatedFields يوضّح لك الحقول التي تغيّرت فعليًا في الحدث الحالي.
يقبل databaseEventTriggerSettings:مثال على حدث الإنشاء:
مثال على حدث التحديث:
تشغيل المشغّل فقط عند تحديثات البريد الإلكتروني:
مثال على حدث الحذف:

تجميع أحداث قاعدة البيانات في دفعات

تؤدي عملية كتابة مجمعة إلى إصدار العديد من الأحداث دفعة واحدة: فعلى سبيل المثال، يمكن أن يؤدي استيراد صندوق بريد إلى تحديث آلاف السجلات في عملية واحدة، ويؤدي إجراء استدعاء واحد لكل سجل إلى إغراق قائمة الانتظار. عيّن batchMode لتلقيها معاً:
يتلقى المعالج بعد ذلك DatabaseEventBatchPayload، الذي يحمل البيانات الوصفية نفسها التي يحملها DatabaseEventPayload، لكنه يستبدل الحقول على مستوى السجل بمصفوفة events:
حجم الدفعة ليس شيئاً يمكن للمعالج الاعتماد عليه. يقرر الخادم عدد الأحداث التي يحملها الاستدعاء، بحد أقصى 200، وتحتوي الدفعة على ما يتراوح بين حدث واحد وذلك الحد الأقصى بحسب عدد الأحداث التي أنتجتها عملية الكتابة الأصلية. اكتب المعالج بحيث يكرّر عبر events مهما كان طولها.
تُجمّع الأحداث حسب المستخدم الذي تسبب فيها قبل وضعها في دفعات، لذا يغطي كل استدعاء فاعلاً واحداً ويظل context.workspaceMemberId ذا معنى. يُطبّق ترشيح updatedFields قبل التجميع في دفعات، لذا لا تحتوي الدفعة إلا على السجلات التي طلبتها.من دون batchMode، يستمر المعالج في تلقي DatabaseEventPayload واحد لكل سجل.

سياق التنفيذ

تتلقى معالجات دوال المنطق العامة وسيطة ثانية تصف عملية التشغيل نفسها، بغض النظر عما أدى إلى تشغيلها. بينما يتغير شكل الوسيط الأول بحسب المُشغِّل، لا يتغير شكل هذا الوسيط:
يكون userWorkspaceId وworkspaceMemberId بقيمة null عندما لا يشغّل أحد عملية التشغيل: فلا توجد جهة خلف جداول cron وخطافات التثبيت وخطافات الويب غير الموثقة. ويكونان أيضًا بقيمة null عندما لا يكون لدى الشخص سجل عضو في مساحة العمل، أو عندما يكون سجله قد حُذف.
يوضح لك السياق من الذي شغّل التشغيل. أما ما يمكن للتشغيل فعله فهو تعريف منفصل، أدناه.

أذونات الوصول التي تستخدمها المكالمة

يتصرف كل عميل — CoreApiClient وMetadataApiClient وRestApiClient — بصفته الشخص الذي شغّل عملية التشغيل: إذ يتقاطع دوره مع دور تطبيقك، لذا لا يمكن للمكالمة أبدًا أن تفعل أكثر مما يسمح به أيّ منكما. هذا هو السلوك الافتراضي، ويعني أنه لا يمكن للشخص أبدًا استخدام تطبيقك لتجاوز أذوناته الخاصة.عندما لا يشغّل أحد عملية التشغيل، لا يوجد شخص للتصرف بصفته، لذا يعود العميل نفسه إلى أذونات الوصول الخاصة بتطبيقك: لا تحتاج جداول cron وخطافات التثبيت وخطافات الويب غير الموثقة إلى معالجة خاصة.تحتاج بعض الاستدعاءات بشكل مشروع إلى وصول التطبيق نفسه حتى عندما يكون شخص ما وراء التشغيل — مثل قراءة سجلات تهيئة تطبيقك، أو تنفيذ عمل لا يستطيع ذلك الشخص القيام به بنفسه. أنشئ عميلاً ثانيًا لهذه الحالات:
أنشئ كليهما مرة واحدة، في نطاق الوحدة، ثم يحدد كل موضع استدعاء أذونات الوصول التي يستخدمها من خلال العميل الذي يستدعيه. يأخذ RestApiClient الخيار نفسه:
عملية التشغيل التي لم يشغّلها أحد تتصرف بصفتها تطبيقك. لا يوجد شخص خلف جداول cron وخطافات التثبيت وخطافات الويب غير الموثقة، لذا يعود العميل الافتراضي إلى أذونات الوصول الخاصة بتطبيقك ويستمر في العمل. لا يلزم استخدام runAs: 'application' إلا عندما تريد أذونات الوصول تلك في عملية تشغيل شغّلها شخص بالفعل.تحقق من context.workspaceMemberId عندما تتصرف دالة بشكل مختلف بناءً على وجود شخص خلفها، على سبيل المثال لإسناد سجل.
تستخدم مساعدات SDK التي تصل إلى موارد تطبيقك الخاصة دائمًا صلاحيات الوصول الخاصة بها وتتجاهل runAs: مخزن المفتاح-القيمة، والاتصالات، وقنوات المراسلة، وrunAgent، وgetPublicAssetUrl، وتحصيل الأرصدة. يتبع ai.generate العملاء الافتراضيين بدلاً من ذلك: إذ يتصرف بصفته الشخص الذي بدأ التشغيل، ضمن دور تطبيقك، أو بصفته تطبيقك عندما لا يكون هناك من فعل ذلك.

إتاحة دالة كأداة ذكاء اصطناعي أو كإجراء ضمن سير العمل

يمكن إتاحة دوال المنطق على واجهتين، ولكلٍ منهما مشغِّل خاص به:
  • toolTriggerSettings — يجعل الدالة قابلة للاكتشاف عبر ميزات الذكاء الاصطناعي الخاصة بـ Twenty (الدردشة، MCP، استدعاء الدوال). يستخدم JSON Schema القياسي، وهو التنسيق الذي تفهمه LLMs أصلاً.
  • workflowActionTriggerSettings — يجعل الدالة تظهر كخطوة في منشئ سير العمل المرئي. يستخدم InputSchema الغني الخاص بـ Twenty لكي يتمكن المُنشئ من عرض محرّرات الحقول المناسبة، وأدوات انتقاء المتغيّرات، والتسميات.
يمكن للدالة اختيار أحدهما، أو الآخر، أو كليهما. توجد جنبًا إلى جنب مع cronTriggerSettings وdatabaseEventTriggerSettings وhttpRouteTriggerSettings — النمط نفسه، والشكل نفسه.
العلاقة بإجراء Code الخاص بسير العمل. يُعَد إجراء Code المضمَّن في منشئ سير العمل دالة منطقية بحد ذاته — حيث ينشئ Twenty واحدة لكل خطوة Code ويعرض محررها مضمّنًا. تُستخدَم workflowActionTriggerSettings لتحويل هذا الكود المضمَّن لمرة واحدة إلى إجراء قابل لإعادة الاستخدام: عرِّف الدالة مرة واحدة في تطبيقك وستصبح قابلة للاختيار في أي سير عمل، بدلًا من نسخها ولصقها في كل خطوة Code. راجع إجراء Code في دليل المستخدم لعرض منظور المستخدم النهائي.
src/logic-functions/enrich-company.logic-function.ts
النقاط الرئيسية:
  • يمكن للدالة مزج الواجهات — صرِّح بكلٍ من toolTriggerSettings وworkflowActionTriggerSettings لإتاحتها في الدردشة وفي منشئ سير العمل.
  • toolTriggerSettings.inputSchema وworkflowActionTriggerSettings.inputSchema كلاهما اختياري. عند الإغفال، يستنتج مُنشئ البيان هذه المخططات من الشيفرة المصدرية للمعالج (JSON Schema لأداة الذكاء الاصطناعي، وInputSchema الخاصة بـ Twenty لإجراء سير العمل). قدّم واحدًا صراحةً عندما ترغب في أنواع أكثر ثراءً — على سبيل المثال، مع حقول واعية بـ FieldMetadataType مثل CURRENCY أو RELATION لمنشئ سير العمل، أو مع حقول description التي يمكن لوكيل الذكاء الاصطناعي قراءتها:
للتصريح بمعاملاتك مرة واحدة وخدمة كلتا الواجهتين، عرّف مخطط JSON واحد (InputJsonSchema) وحوِّله لاستخدامه في إجراء سير العمل باستخدام jsonSchemaToInputSchema من twenty-sdk/logic-function. toolTriggerSettings.inputSchema يستخدم مخطط JSON مباشرة، بينما workflowActionTriggerSettings.inputSchema يتوقّع InputSchema الخاص بـ Twenty:
عرض استدعاء أداة باستخدام مكوّن أمامي.
بشكل افتراضي، يظهر استدعاء الأداة في دردشة الذكاء الاصطناعي كصف خطوة: اسم الأداة ومؤشر تحميل إلى أن تُرجع نتيجة. وجّه toolTriggerSettings.frontComponentUniversalIdentifier إلى أحد المكوّنات الأمامية لديك، وستعرض الدردشة ذلك المكوّن مضمّنًا بدلًا من ذلك، لكل استدعاء لهذه الأداة.
يقرأ المكوّن الاستدعاء الذي يعرضه باستخدام useToolCall():
src/front-components/invoice-preview.tsx
يُركَّب المكوّن بمجرد أن ينتهي الوكيل من بث وسائطه، لذا فهو يتولى الاستدعاء بأكمله، وليس النتيجة فقط. يمر toolCall.status عبر input-available وحالات الموافقة عندما تتطلبها الأداة، ثم output-available أو output-denied أو output-error؛ ويمثل input ما اقترحه الوكيل، بينما يمثل output ما أرجعه المعالج لديك. اعرض كل حالة تهمك، إذ لم يعد صف الخطوة موجودًا لتغطيتها.يكون كل من input وoutput من النوع Record\<string, unknown>، لذا ضيّق نطاق كل حقل قبل استخدامه بدلًا من إجراء تحويل النوع. يكون ذلك مهمًا للغاية لأي شيء تمرره إلى DOM: فقيمة تضعها في href من دون التحقق من أنها سلسلة نصية تكون رابطًا يتحكم فيه ناتج الأداة.
إذا لم يطابق المعرّف أي مكون أمامي في التطبيق، فستعود الدردشة إلى صف الخطوة الافتراضي. تعمل المكونات الأمامية في دردشة الذكاء الاصطناعي ضمن الدور الذي يعلنه تطبيقك، لذلك لا يمكن للأداة المصغرة سوى قراءة وكتابة ما يُسمح للتطبيق نفسه به.
مثال كامل لإجراء سير عمل
تقبل workflowActionTriggerSettings أربعة حقول:تجميع ذلك معًا — دالّة معروضة كإجراء سير عمل، مع مخرَج مُعلَن بحيث يمكن للخطوات اللاحقة الرجوع إلى taskId:
src/logic-functions/enrich-company.logic-function.ts
بمجرد تثبيت التطبيق، سيظهر Enrich Company في منتقّي الإجراءات في منشئ سير العمل. يعرض المُنشئ companyName وdomain كحقول إدخال (كلٌّ منهما قادر على سحب القيم من الخطوات السابقة)، ويمكن للخطوات اللاحقة الرجوع إلى مخرجات الخطوة taskId وenriched.
اكتب description جيدًا. يعتمد وكلاء الذكاء الاصطناعي على حقل description الخاص بالدالة لتحديد وقت استخدام الأداة. كن محددًا بشأن ما تفعله الأداة ومتى ينبغي استدعاؤها.
مساعدات وقت التشغيل. يقوم twenty-sdk/utils بإعادة تصدير مساعدات صغيرة لوقت التشغيل حتى لا تستورد المعالجات مباشرةً من twenty-shared. على سبيل المثال، تُرجِع isDefined(value) القيمة false لكلٍّ من null وundefined — استخدمها لتضييق نطاق مُدخلات المعالِجات الاختيارية بأمان، والتي يمكن أن تصل كقيمة null أثناء وقت التشغيل حتى عندما تكون مكتوبة كـ T | undefined:
خطافات التثبيت — معالجات ما قبل التثبيت وما بعد التثبيت وإلغاء التثبيت — تشترك في وقت التشغيل نفسه، ولكن يُصرَّح عنها بدوال تعريف خاصة بها ولا تأخذ إعدادات المشغّلات. راجع خطافات التثبيت لمعرفة definePreInstallLogicFunction وdefinePostInstallLogicFunction و defineUninstallLogicFunction.

فحص الحالة الصحية.

عرّف فحصًا للحالة الصحية باستخدام defineHealthCheck للإبلاغ عمّا إذا كان تطبيقك قادرًا بالفعل على التشغيل. وهو مخصص لما لا يمكن أن يعرفه إلا التطبيق: مفتاح موجود لكنه مُلغى، أو حساب ضمن الخطة الخاطئة، أو خطاف ويب لم يُسجَّل قط. ما يمكن لـ Twenty رؤيته بنفسه — متغير مطلوب لم يملأه أحد — تغطيه isRequired ولا يحتاج إلى أي تعليمات برمجية. يعمل على جانب الخادم، لذا يقرأ المتغيرات السرية مثل أي دالة منطقية أخرى. لا يرتبط فحص مساحة العمل بأي عضو، لذا لا يحتاج المعالج إلى أي وسيطات.
src/logic-functions/health-check.ts
النتيجة هي اتحاد مميّز، وليست قيمة منطقية: لا يعرض OK أي لافتة على الإطلاق؛ وتعرض كل حالة أخرى العنوان في لافتة ملوّنة بحسبها، لذا اختر الحالة التي تتوافق مع مدى حاجة الشخص إلى اتخاذ إجراء. الحالات هي ApplicationHealthStatus الخاصة بـ Twenty، لذا يمكن استخدام السلسلة النصية العادية وعضو التعداد بالتبادل:
يشغّل Twenty الفحص عند فتح صفحة إعدادات التطبيق ويعرض النتيجة في لافتة هناك. لا يتم تخزين أي شيء، لذا فإن ما تُبلّغ عنه هو دائمًا ما أعاده الفحص للتو. يُعامل الفحص الذي يطرح استثناءً أو تنتهي مهلته أو يعيد بنية لا يستطيع Twenty قراءتها على أنه غير معروف، وليس خطأً مطلقًا، ولا تُعرض أي لافتة. يجب ألا يؤدي انقطاع خدمة تابعة لجهة خارجية إلى تلوين كل تطبيق مثبّت بالأحمر — فلو حدث ذلك، لتعلّم الناس تجاهل الإشارة. يجب أن يظل الموقع داخل Twenty، لذا يبدأ بـ / أو #: أما الموقع الخارجي أو النسبي للبروتوكول أو النسبي فيفقد زره بدلًا من أن يصبح إعادة توجيه إلى مكان آخر. يُزال الزر أيضًا عندما يُبلغ التطبيق عن عدم وجود موقع ولا يحتوي على علامة تبويب للإعدادات للرجوع إليها، لذا فإن التحقق من تطبيق لا يحتوي على متغيرات ولا عناصر قائمة إعدادات يعرض عنوانه دون زر معطّل.

التحقق من إعداد كل عضو الخاص

يعرّف التطبيق دالة واحدة لفحص السلامة. احذف runOnUserPreferences أو اضبطه على false لتشغيل فحص لمساحة العمل في صفحة إعدادات التطبيق، باستخدام معالج بلا وسيطات. اضبط runOnUserPreferences: true لتشغيل فحص للعضو في صفحة تفضيلات التطبيقات الخاصة به بدلاً من ذلك. يتلقى معالج العضو { userWorkspaceId: string } المطلوب من الجلسة المصادَق عليها:
src/logic-functions/health-check.ts
يعمل فحص العضو عندما يفتح عضو التطبيق في صفحة تفضيلات التطبيقات الخاصة به، ولا يرى لافتته سوى ذلك العضو. يعمل بهوية ذلك العضو: يتلقى المعالج { userWorkspaceId } من الجلسة المصادَق عليها، وتحتفظ المتغيرات ذات النطاق المستخدم بقيمها. كائن المعلمات والمعرّف مطلوبان لهذا الوضع. لا يعمل في إعدادات التطبيق لمساحة العمل. مرر userWorkspaceId إلى listConnections() كي يعيد اتصالات ذلك العضو فقط. يُستبعد من القائمة أي اتصال يفشل تحديث رمزه، لذا يشمل “عدم وجود اتصال” “إعادة الاتصال” أيضاً. يؤدي الزر الذي لا يحتوي على location إلى علامة التبويب “عام” للتطبيق في تلك الصفحة.
تتطلب فحوصات الأعضاء Twenty 2.55.0 أو إصداراً أحدث. لم يتم تفعيل تفضيلات التطبيقات بعد في كل مساحة عمل: حيث لا تكون مفعّلة، لا يعمل فحص العضو أبداً.
يمكن للتطبيق تعريف دالة واحدة فقط لفحص السلامة، إما في وضع مساحة العمل أو وضع العضو. تحتفظ التعريفات الحالية بمعالج مساحة العمل بلا وسيطات. يؤدي تعريف دالة ثانية إلى فشل الإنشاء.

إنشاء نشاط في المخطط الزمني

استخدم createTimelineActivity() لنشر حدث مجال صريح من دالة منطقية. عرّف الحدث أولاً باعتباره نوع نشاط في المخطط الزمني، ثم عالج النوع والكائنات باستخدام معرفاتها العامة الثابتة:
يحوّل Twenty المعرفات العامة إلى معرّفات البيانات الوصفية الخاصة بالتثبيت، ويتحقق من أن نوع نشاط المخطط الزمني ينتمي إلى التطبيق المستدعي، ويلتقط لقطة من بيانات العرض الوصفية الخاصة به في النشاط الجديد. المدخلات المطلوبة هي timelineActivityTypeUniversalIdentifier وtargetObjectUniversalIdentifier وtargetRecordId. يمكنك أيضاً توفير happensAt وproperties وworkspaceMemberId. يتحكم happensAt في موضع الحدث ووقته المعروض في المخطط الزمني؛ ويكون افتراضياً وقت الإنشاء. لربط سجل آخر بالحدث، وفّر linkedRecordId وlinkedObjectMetadataUniversalIdentifier معاً. يمكنك بالإضافة إلى ذلك توفير linkedRecordCachedName كخيار احتياطي للعرض التاريخي:
يحتاج دور الدالة المنطقية إلى إذن كتابة على كائن timelineActivity القياسي. أبقِ أنواع الأحداث الصريحة غير مرتبطة بـaction؛ إذ إن النوع المرتبط بإجراء يتلقى بالفعل أحداث تدقيق تلقائية، وإلا فسينتج صفوفاً مكررة.

عملاء واجهة برمجة تطبيقات مضبوطة الأنواع (twenty-client-sdk)

توفر حزمة twenty-client-sdk عميلين لـ GraphQL ذوي أنواع ثابتة للتفاعل مع واجهة Twenty البرمجية من وظائفك المنطقية ومكوّنات الواجهة الأمامية.
CoreApiClient هو العميل الرئيسي للاستعلام وتعديل بيانات مساحة العمل. يُولَّد من مخطط مساحة العمل لديك باستخدام twenty app generate-client، أو بعد المزامنة باستخدام twenty app apply أو twenty app dev، لذا فهو محدّد الأنواع بالكامل ليتوافق مع كائناتك وحقولك.
لا يهم shareWith إلا في كائن تكون سجلاته خاصة؛ وفي غير ذلك، يتم تجاهل الوسيطة. في مثل هذا الكائن، تسرد هذه الخاصية من يحصل على حق الوصول إلى السجل المُنشأ: يستهدف كل إدخال واحدًا فقط من workspaceMemberId أو roleId أو everyone: true مع accessLevel بقيمة READ أو READ_WRITE أو FULL. يتلقى عضو مساحة العمل الذي ينشئ سجلًا دائمًا منحة مالك FULL، لذا تكون الوسيطة اختيارية بالنسبة إليه؛ كما تتلقى دالة منطقية تعمل كتطبيقها أو مفتاح API منحة FULL لدورها، ويجب على هؤلاء المستدعين، مثل مستدعٍ للنظام، تمرير shareWith وإلا فسيتم رفض الإنشاء. يجب أن يحدد كل إدخال عضوًا أو دورًا في مساحة العمل. تشارك الدالة المنطقية أو مفتاح API عبر دوره، بحيث يحصل كل من لديه ذلك الدور على نفس إمكانية الوصول إلى السجلات التي ينشئها؛ امنح المفتاح الذي يجب أن تظل سجلاته خاصة به دورًا خاصًا به.يستخدم العميل صياغة مجموعة اختيار: مرِّر true لتضمين حقل، واستخدم __args للوسيطات، وعشّش الكائنات للعلاقات. ستحصل على إكمال تلقائي كامل وفحص للأنواع يعتمد على مخطط مساحة العمل لديك.
شغّل twenty app generate-client لتحديث العميل من مخطط GraphQL الخاص بمساحة العمل لديك. يعيد twenty app apply وtwenty app dev أيضًا إنشاءه بعد المزامنة. يبني twenty app build محليًا فقط ولا يُنشئ العميل.

استخدام CoreSchema للتعليقات التوضيحية للأنواع

CoreSchema يوفّر أنواع TypeScript المطابقة لكائنات مساحة العمل لديك — مفيد لتعيين أنواع حالة المكوّن أو معاملات الدوال:
يأتي MetadataApiClient مُجهّزًا مسبقًا مع SDK (لا حاجة للتوليد). يستعلم عن نقطة النهاية /metadata للحصول على تكوين مساحة العمل والتطبيقات ورفع الملفات. يأخذ خيار runAs نفسه الذي يأخذه CoreApiClient — راجع أذونات الوصول التي تستخدمها المكالمة.وهي أيضًا الطريقة التي يقرأ بها التطبيق متغيرات التطبيق الخاصة به ويحدّثها متغيرات التطبيق، في دالة منطقية كما في مكوّن أمامي. لا يصل رمز التطبيق إلا إلى التطبيق الخاص به، ضمن مساحة العمل الخاصة به.يوجد متغير مستخدم في process.env بقيمة العضو الذي شغّل التنفيذ، أو بقيمته الافتراضية إلى أن يعيّن قيمة خاصة به. التنفيذ الذي لم يشغّله أحد لا يتلقى أي متغيرات مستخدم. لقراءة قيمة متغيرات المستخدم الخاصة بتطبيقك لكل عضو، على سبيل المثال في مهمة cron، استعلم عن myUserApplicationVariables باستخدام runAs: 'application'.

رفع الملفات

يتضمن MetadataApiClient طريقة uploadFile لإرفاق الملفات بالحقول من نوع الملف. ينتقل الملف مباشرةً إلى تخزين ملفات Twenty: تُصدر واجهة برمجة التطبيقات وجهة رفع، ويرسل العميل البايتات إليها، ثم تؤكد واجهة برمجة التطبيقات عملية الرفع.
النقاط الرئيسية:
  • يستخدم universalIdentifier الخاص بالحقل (وليس معرّفه الخاص بمساحة العمل)، بحيث يعمل كود الرفع لديك عبر أي مساحة عمل مُثبَّت فيها تطبيقك.
  • يُكتشف نوع MIME من محتويات الملف بمجرد تحميله، لذا لا يوجد ما يجب تعريفه.
  • العنوان url المُعاد هو عنوان URL موقّع يمكنك استخدامه للوصول إلى الملف المرفوع.
عند تشغيل كودك على Twenty (وظائف منطقية أو مكوّنات أمامية)، يقوم النظام الأساسي بحقن بيانات الاعتماد كمتغيرات بيئية:
  • TWENTY_API_URL — عنوان URL الأساسي لواجهة Twenty البرمجية
  • TWENTY_APP_ACCESS_TOKEN — مفتاح قصير العمر لأذونات الوصول الافتراضية: دور الشخص متقاطعًا مع دور تطبيقك عندما يكون هناك شخص خلف عملية التشغيل، ودور تطبيقك الخاص عندما لا يكون هناك أحد. الشخص هو من شغّل عملية التشغيل في دالة منطقية، أو من ينظر إلى الصفحة في مكوّن أمامي.
  • TWENTY_APP_APPLICATION_ACCESS_TOKEN — مفتاح قصير العمر يقتصر نطاقه على دور تطبيقك الخاص وحده. للدوال المنطقية فقط، ويُحقن فيها دائمًا، وهو ما يستخدمه runAs: 'application'.
لا تحتاج إلى تمرير هذه إلى العملاء — فهم يقرؤون من process.env تلقائيًا، ويغطي أذونات الوصول التي تستخدمها المكالمة الاختيار بينها. تُحدَّد أذونات تطبيقك الخاصة بواسطة الدور المُعلن باستخدام defineApplicationRole() (أو المشار إليه عبر defaultRoleUniversalIdentifier في application-config.ts)؛ ولا يمكن لعملية تشغيل تتصرف بصفة شخص أن تتجاوز ذلك الدور أو دوره.