mirror of
https://github.com/lobehub/lobehub
synced 2026-04-21 17:47:27 +00:00
✨ feat: add slash action tags, topic reference tool, and command bus system (#12860)
* ✨ feat: add slash action tags in chat input Made-with: Cursor * ✨ feat: enhance editor with new slash actions and localization updates - Added new slash actions: change tone, condense, expand, polish, rewrite, summarize, and translate. - Updated localization files for English and Chinese to include new action tags and slash commands. - Removed deprecated useSlashItems component and integrated its functionality directly into InputEditor. Signed-off-by: Innei <tukon479@gmail.com> * ✨ feat: add slash placement configuration to chat input components - Introduced `slashPlacement` prop to `ChatInputProvider`, `StoreUpdater`, and `InputEditor` for customizable slash menu positioning. - Updated initial state to include `slashPlacement` with default value 'top'. - Adjusted `ChatInput` and `InputArea` components to utilize the new `slashPlacement` prop. This enhancement allows for better control over the user interface in chat input interactions. Signed-off-by: Innei <tukon479@gmail.com> * ✨ feat: implement command bus for slash action tags processing Add command bus system to parse and execute slash commands (compact context, new topic). Refactor action tag categories from ai/prompt to command/skill. Add useEnabledSkills hook for dynamic skill registration. * feat: compress command Signed-off-by: Innei <tukon479@gmail.com> * refactor: compress Signed-off-by: Innei <tukon479@gmail.com> * fix: skill inject * ✨ feat: slash action tags with context engine integration Made-with: Cursor * ✨ feat: add topic reference builtin tool and server runtime Made-with: Cursor * ✨ feat: add topic mention items and update ReferTopic integration Made-with: Cursor * 🐛 fix: preserve editorData through assistant-group edit flow and update RichTextMessage reactively - EditState now forwards editorData from EditorModal to modifyMessageContent - modifyMessageContent accepts and passes editorData to updateMessageContent - RichTextMessage uses useEditor + effect to update document on content change instead of key-based remount - Refactored RichTextMessage plugins to use shared createChatInputRichPlugins() * ✨ feat(context-engine): add metadata types and update processors/providers Made-with: Cursor * ✨ feat(chat-input): add slash action tags and restore failed input state * 🔧 chore: update package dependencies and enhance Vite configuration - Changed @lobehub/ui dependency to a specific package URL. - Added multiple SPA entry points and layout files to the Vite warmup configuration. - Removed unused monorepo packages from sharedOptimizeDeps and added various dayjs locales for better localization support. Signed-off-by: Innei <tukon479@gmail.com> * 🔧 chore: update @lobehub/ui dependency to version 5.4.0 in package.json Signed-off-by: Innei <tukon479@gmail.com> * 🐛 fix: correct SkillsApiName.runSkill to activateSkill and update trimmed content assertions * 🐛 fix: resolve type errors in context-engine tests and InputEditor slashPlacement * 🐛 fix: update runSkill to activateSkill in conversationLifecycle test * 🐛 fix: avoid regex backtracking in placeholder parser * ✨ feat(localization): add action tags and tooltips for slash commands across multiple languages Signed-off-by: Innei <tukon479@gmail.com> * 🐛 fix: preserve file attachments when /newTopic has no text content * cleanup Signed-off-by: Innei <tukon479@gmail.com> --------- Signed-off-by: Innei <tukon479@gmail.com>
This commit is contained in:
parent
d7bfd1b6c8
commit
4438b559e6
180 changed files with 6021 additions and 534 deletions
|
|
@ -14,6 +14,9 @@ description: TypeScript code style and optimization guidelines. Use when writing
|
|||
- Prefer `as const satisfies XyzInterface` over plain `as const`
|
||||
- Prefer `@ts-expect-error` over `@ts-ignore` over `as any`
|
||||
- Avoid meaningless null/undefined parameters; design strict function contracts
|
||||
- Prefer ES module augmentation (`declare module '...'`) over `namespace`; do not introduce `namespace`-based extension patterns
|
||||
- When a type needs extensibility, expose a small mergeable interface at the source type and let each feature/plugin augment it locally instead of centralizing all extension fields in one registry file
|
||||
- For package-local extensibility patterns like `PipelineContext.metadata`, define the metadata fields next to the processor/provider/plugin that reads or writes them
|
||||
|
||||
## Async Patterns
|
||||
|
||||
|
|
|
|||
|
|
@ -1,4 +1,10 @@
|
|||
{
|
||||
"actionTag.category.command": "أمر",
|
||||
"actionTag.category.skill": "مهارة",
|
||||
"actionTag.category.tool": "أداة",
|
||||
"actionTag.tooltip.command": "يشغّل أمر الشرطة المائلة على جانب العميل قبل الإرسال.",
|
||||
"actionTag.tooltip.skill": "يحمّل حزمة مهارات قابلة لإعادة الاستخدام لهذا الطلب.",
|
||||
"actionTag.tooltip.tool": "يشير إلى أداة اختارها المستخدم صراحةً لهذا الطلب.",
|
||||
"actions.expand.off": "طي",
|
||||
"actions.expand.on": "توسيع",
|
||||
"actions.typobar.off": "إخفاء شريط تنسيق النص",
|
||||
|
|
@ -34,12 +40,17 @@
|
|||
"modifier.acceptAll": "الاحتفاظ بالجميع",
|
||||
"modifier.reject": "تراجع",
|
||||
"modifier.rejectAll": "تراجع عن الكل",
|
||||
"slash.compact": "ضغط السياق",
|
||||
"slash.h1": "عنوان 1",
|
||||
"slash.h2": "عنوان 2",
|
||||
"slash.h3": "عنوان 3",
|
||||
"slash.hr": "فاصل",
|
||||
"slash.newTopic": "الإرسال في موضوع جديد",
|
||||
"slash.rewrite": "إعادة الصياغة",
|
||||
"slash.summarize": "تلخيص",
|
||||
"slash.table": "جدول",
|
||||
"slash.tex": "صيغة TeX",
|
||||
"slash.translate": "ترجمة",
|
||||
"table.delete": "حذف الجدول",
|
||||
"table.deleteColumn": "حذف العمود",
|
||||
"table.deleteRow": "حذف الصف",
|
||||
|
|
|
|||
|
|
@ -201,6 +201,8 @@
|
|||
"builtins.lobe-skills.apiName.searchSkill": "البحث عن المهارات",
|
||||
"builtins.lobe-skills.title": "المهارات",
|
||||
"builtins.lobe-tools.apiName.activateTools": "تفعيل الأدوات",
|
||||
"builtins.lobe-topic-reference.apiName.getTopicContext": "الحصول على سياق الموضوع",
|
||||
"builtins.lobe-topic-reference.title": "مرجع الموضوع",
|
||||
"builtins.lobe-user-memory.apiName.addContextMemory": "إضافة ذاكرة السياق",
|
||||
"builtins.lobe-user-memory.apiName.addExperienceMemory": "إضافة ذاكرة الخبرة",
|
||||
"builtins.lobe-user-memory.apiName.addIdentityMemory": "إضافة ذاكرة الهوية",
|
||||
|
|
|
|||
|
|
@ -1,4 +1,10 @@
|
|||
{
|
||||
"actionTag.category.command": "Команда",
|
||||
"actionTag.category.skill": "Умение",
|
||||
"actionTag.category.tool": "Инструмент",
|
||||
"actionTag.tooltip.command": "Изпълнява локална slash команда преди изпращане.",
|
||||
"actionTag.tooltip.skill": "Зарежда многократно използваем пакет с умения за тази заявка.",
|
||||
"actionTag.tooltip.tool": "Маркира инструмент, който потребителят е избрал изрично за тази заявка.",
|
||||
"actions.expand.off": "Свий",
|
||||
"actions.expand.on": "Разгъни",
|
||||
"actions.typobar.off": "Скрий лентата с форматиране",
|
||||
|
|
@ -34,12 +40,17 @@
|
|||
"modifier.acceptAll": "Запази всички",
|
||||
"modifier.reject": "Отмени",
|
||||
"modifier.rejectAll": "Отмени всички",
|
||||
"slash.compact": "Компресирай контекста",
|
||||
"slash.h1": "Заглавие 1",
|
||||
"slash.h2": "Заглавие 2",
|
||||
"slash.h3": "Заглавие 3",
|
||||
"slash.hr": "Разделител",
|
||||
"slash.newTopic": "Изпрати в нова тема",
|
||||
"slash.rewrite": "Пренапиши",
|
||||
"slash.summarize": "Обобщи",
|
||||
"slash.table": "Таблица",
|
||||
"slash.tex": "TeX формула",
|
||||
"slash.translate": "Преведи",
|
||||
"table.delete": "Изтрий таблицата",
|
||||
"table.deleteColumn": "Изтрий колона",
|
||||
"table.deleteRow": "Изтрий ред",
|
||||
|
|
|
|||
|
|
@ -201,6 +201,8 @@
|
|||
"builtins.lobe-skills.apiName.searchSkill": "Търсене на умения",
|
||||
"builtins.lobe-skills.title": "Умения",
|
||||
"builtins.lobe-tools.apiName.activateTools": "Активиране на инструменти",
|
||||
"builtins.lobe-topic-reference.apiName.getTopicContext": "Вземи контекста на темата",
|
||||
"builtins.lobe-topic-reference.title": "Препратка към тема",
|
||||
"builtins.lobe-user-memory.apiName.addContextMemory": "Добавяне на контекстна памет",
|
||||
"builtins.lobe-user-memory.apiName.addExperienceMemory": "Добавяне на памет за опит",
|
||||
"builtins.lobe-user-memory.apiName.addIdentityMemory": "Добавяне на памет за идентичност",
|
||||
|
|
|
|||
|
|
@ -1,4 +1,10 @@
|
|||
{
|
||||
"actionTag.category.command": "Befehl",
|
||||
"actionTag.category.skill": "Fähigkeit",
|
||||
"actionTag.category.tool": "Werkzeug",
|
||||
"actionTag.tooltip.command": "Führt vor dem Senden einen clientseitigen Slash-Befehl aus.",
|
||||
"actionTag.tooltip.skill": "Lädt für diese Anfrage ein wiederverwendbares Fähigkeitspaket.",
|
||||
"actionTag.tooltip.tool": "Markiert ein Werkzeug, das der Benutzer für diese Anfrage ausdrücklich ausgewählt hat.",
|
||||
"actions.expand.off": "Einklappen",
|
||||
"actions.expand.on": "Ausklappen",
|
||||
"actions.typobar.off": "Formatierungsleiste ausblenden",
|
||||
|
|
@ -34,12 +40,17 @@
|
|||
"modifier.acceptAll": "Alle beibehalten",
|
||||
"modifier.reject": "Zurücksetzen",
|
||||
"modifier.rejectAll": "Alle zurücksetzen",
|
||||
"slash.compact": "Kontext komprimieren",
|
||||
"slash.h1": "Überschrift 1",
|
||||
"slash.h2": "Überschrift 2",
|
||||
"slash.h3": "Überschrift 3",
|
||||
"slash.hr": "Trennlinie",
|
||||
"slash.newTopic": "In neuem Thema senden",
|
||||
"slash.rewrite": "Umschreiben",
|
||||
"slash.summarize": "Zusammenfassen",
|
||||
"slash.table": "Tabelle",
|
||||
"slash.tex": "TeX-Formel",
|
||||
"slash.translate": "Übersetzen",
|
||||
"table.delete": "Tabelle löschen",
|
||||
"table.deleteColumn": "Spalte löschen",
|
||||
"table.deleteRow": "Zeile löschen",
|
||||
|
|
|
|||
|
|
@ -201,6 +201,8 @@
|
|||
"builtins.lobe-skills.apiName.searchSkill": "Fähigkeiten suchen",
|
||||
"builtins.lobe-skills.title": "Fähigkeiten",
|
||||
"builtins.lobe-tools.apiName.activateTools": "Werkzeuge aktivieren",
|
||||
"builtins.lobe-topic-reference.apiName.getTopicContext": "Themenkontext abrufen",
|
||||
"builtins.lobe-topic-reference.title": "Themenreferenz",
|
||||
"builtins.lobe-user-memory.apiName.addContextMemory": "Kontextgedächtnis hinzufügen",
|
||||
"builtins.lobe-user-memory.apiName.addExperienceMemory": "Erfahrungsgedächtnis hinzufügen",
|
||||
"builtins.lobe-user-memory.apiName.addIdentityMemory": "Identitätsgedächtnis hinzufügen",
|
||||
|
|
|
|||
|
|
@ -1,4 +1,10 @@
|
|||
{
|
||||
"actionTag.category.command": "Command",
|
||||
"actionTag.category.skill": "Skill",
|
||||
"actionTag.category.tool": "Tool",
|
||||
"actionTag.tooltip.command": "Runs a client-side slash command before sending.",
|
||||
"actionTag.tooltip.skill": "Loads a reusable skill package for this request.",
|
||||
"actionTag.tooltip.tool": "Marks a tool the user explicitly selected for this request.",
|
||||
"actions.expand.off": "Collapse",
|
||||
"actions.expand.on": "Expand",
|
||||
"actions.typobar.off": "Hide formatting toolbar",
|
||||
|
|
@ -34,12 +40,17 @@
|
|||
"modifier.acceptAll": "Keep All",
|
||||
"modifier.reject": "Revert",
|
||||
"modifier.rejectAll": "Revert All",
|
||||
"slash.compact": "Compact context",
|
||||
"slash.h1": "Heading 1",
|
||||
"slash.h2": "Heading 2",
|
||||
"slash.h3": "Heading 3",
|
||||
"slash.hr": "Divider",
|
||||
"slash.newTopic": "Send in new topic",
|
||||
"slash.rewrite": "Rewrite",
|
||||
"slash.summarize": "Summarize",
|
||||
"slash.table": "Table",
|
||||
"slash.tex": "TeX Formula",
|
||||
"slash.translate": "Translate",
|
||||
"table.delete": "Delete table",
|
||||
"table.deleteColumn": "Delete column",
|
||||
"table.deleteRow": "Delete row",
|
||||
|
|
|
|||
|
|
@ -202,6 +202,8 @@
|
|||
"builtins.lobe-skills.apiName.searchSkill": "Search Skills",
|
||||
"builtins.lobe-skills.title": "Skills",
|
||||
"builtins.lobe-tools.apiName.activateTools": "Activate Tools",
|
||||
"builtins.lobe-topic-reference.apiName.getTopicContext": "Get Topic Context",
|
||||
"builtins.lobe-topic-reference.title": "Topic Reference",
|
||||
"builtins.lobe-user-memory.apiName.addContextMemory": "Add context memory",
|
||||
"builtins.lobe-user-memory.apiName.addExperienceMemory": "Add experience memory",
|
||||
"builtins.lobe-user-memory.apiName.addIdentityMemory": "Add identity memory",
|
||||
|
|
|
|||
|
|
@ -1,4 +1,10 @@
|
|||
{
|
||||
"actionTag.category.command": "Comando",
|
||||
"actionTag.category.skill": "Habilidad",
|
||||
"actionTag.category.tool": "Herramienta",
|
||||
"actionTag.tooltip.command": "Ejecuta un comando de barra en el cliente antes de enviar.",
|
||||
"actionTag.tooltip.skill": "Carga un paquete de habilidades reutilizable para esta solicitud.",
|
||||
"actionTag.tooltip.tool": "Marca una herramienta que el usuario seleccionó explícitamente para esta solicitud.",
|
||||
"actions.expand.off": "Colapsar",
|
||||
"actions.expand.on": "Expandir",
|
||||
"actions.typobar.off": "Ocultar barra de formato",
|
||||
|
|
@ -34,12 +40,17 @@
|
|||
"modifier.acceptAll": "Conservar todo",
|
||||
"modifier.reject": "Revertir",
|
||||
"modifier.rejectAll": "Revertir todo",
|
||||
"slash.compact": "Compactar contexto",
|
||||
"slash.h1": "Encabezado 1",
|
||||
"slash.h2": "Encabezado 2",
|
||||
"slash.h3": "Encabezado 3",
|
||||
"slash.hr": "Separador",
|
||||
"slash.newTopic": "Enviar en un tema nuevo",
|
||||
"slash.rewrite": "Reescribir",
|
||||
"slash.summarize": "Resumir",
|
||||
"slash.table": "Tabla",
|
||||
"slash.tex": "Fórmula TeX",
|
||||
"slash.translate": "Traducir",
|
||||
"table.delete": "Eliminar tabla",
|
||||
"table.deleteColumn": "Eliminar columna",
|
||||
"table.deleteRow": "Eliminar fila",
|
||||
|
|
|
|||
|
|
@ -201,6 +201,8 @@
|
|||
"builtins.lobe-skills.apiName.searchSkill": "Buscar Habilidades",
|
||||
"builtins.lobe-skills.title": "Habilidades",
|
||||
"builtins.lobe-tools.apiName.activateTools": "Activar Herramientas",
|
||||
"builtins.lobe-topic-reference.apiName.getTopicContext": "Obtener contexto del tema",
|
||||
"builtins.lobe-topic-reference.title": "Referencia de tema",
|
||||
"builtins.lobe-user-memory.apiName.addContextMemory": "Agregar memoria de contexto",
|
||||
"builtins.lobe-user-memory.apiName.addExperienceMemory": "Agregar memoria de experiencia",
|
||||
"builtins.lobe-user-memory.apiName.addIdentityMemory": "Agregar memoria de identidad",
|
||||
|
|
|
|||
|
|
@ -1,4 +1,10 @@
|
|||
{
|
||||
"actionTag.category.command": "دستور",
|
||||
"actionTag.category.skill": "مهارت",
|
||||
"actionTag.category.tool": "ابزار",
|
||||
"actionTag.tooltip.command": "پیش از ارسال، یک دستور اسلش سمت کلاینت را اجرا میکند.",
|
||||
"actionTag.tooltip.skill": "برای این درخواست، یک بسته مهارت قابل استفاده مجدد را بارگذاری میکند.",
|
||||
"actionTag.tooltip.tool": "ابزاری را که کاربر بهطور صریح برای این درخواست انتخاب کرده است علامتگذاری میکند.",
|
||||
"actions.expand.off": "جمع کردن",
|
||||
"actions.expand.on": "باز کردن",
|
||||
"actions.typobar.off": "پنهان کردن نوار ابزار قالببندی",
|
||||
|
|
@ -34,12 +40,17 @@
|
|||
"modifier.acceptAll": "نگهداشتن همه",
|
||||
"modifier.reject": "بازگرداندن",
|
||||
"modifier.rejectAll": "بازگرداندن همه",
|
||||
"slash.compact": "فشردهسازی زمینه",
|
||||
"slash.h1": "سرفصل ۱",
|
||||
"slash.h2": "سرفصل ۲",
|
||||
"slash.h3": "سرفصل ۳",
|
||||
"slash.hr": "خط جداکننده",
|
||||
"slash.newTopic": "ارسال در موضوع جدید",
|
||||
"slash.rewrite": "بازنویسی",
|
||||
"slash.summarize": "خلاصهسازی",
|
||||
"slash.table": "جدول",
|
||||
"slash.tex": "فرمول TeX",
|
||||
"slash.translate": "ترجمه",
|
||||
"table.delete": "حذف جدول",
|
||||
"table.deleteColumn": "حذف ستون",
|
||||
"table.deleteRow": "حذف ردیف",
|
||||
|
|
|
|||
|
|
@ -201,6 +201,8 @@
|
|||
"builtins.lobe-skills.apiName.searchSkill": "جستجوی مهارتها",
|
||||
"builtins.lobe-skills.title": "مهارتها",
|
||||
"builtins.lobe-tools.apiName.activateTools": "فعال کردن ابزارها",
|
||||
"builtins.lobe-topic-reference.apiName.getTopicContext": "دریافت زمینه موضوع",
|
||||
"builtins.lobe-topic-reference.title": "ارجاع به موضوع",
|
||||
"builtins.lobe-user-memory.apiName.addContextMemory": "افزودن حافظه زمینه",
|
||||
"builtins.lobe-user-memory.apiName.addExperienceMemory": "افزودن حافظه تجربه",
|
||||
"builtins.lobe-user-memory.apiName.addIdentityMemory": "افزودن حافظه هویتی",
|
||||
|
|
|
|||
|
|
@ -1,4 +1,10 @@
|
|||
{
|
||||
"actionTag.category.command": "Commande",
|
||||
"actionTag.category.skill": "Compétence",
|
||||
"actionTag.category.tool": "Outil",
|
||||
"actionTag.tooltip.command": "Exécute une commande slash côté client avant l’envoi.",
|
||||
"actionTag.tooltip.skill": "Charge un paquet de compétences réutilisable pour cette requête.",
|
||||
"actionTag.tooltip.tool": "Marque un outil que l’utilisateur a explicitement sélectionné pour cette requête.",
|
||||
"actions.expand.off": "Réduire",
|
||||
"actions.expand.on": "Développer",
|
||||
"actions.typobar.off": "Masquer la barre de mise en forme",
|
||||
|
|
@ -34,12 +40,17 @@
|
|||
"modifier.acceptAll": "Tout conserver",
|
||||
"modifier.reject": "Annuler",
|
||||
"modifier.rejectAll": "Tout annuler",
|
||||
"slash.compact": "Compacter le contexte",
|
||||
"slash.h1": "Titre 1",
|
||||
"slash.h2": "Titre 2",
|
||||
"slash.h3": "Titre 3",
|
||||
"slash.hr": "Séparateur",
|
||||
"slash.newTopic": "Envoyer dans un nouveau sujet",
|
||||
"slash.rewrite": "Réécrire",
|
||||
"slash.summarize": "Résumer",
|
||||
"slash.table": "Tableau",
|
||||
"slash.tex": "Formule TeX",
|
||||
"slash.translate": "Traduire",
|
||||
"table.delete": "Supprimer le tableau",
|
||||
"table.deleteColumn": "Supprimer la colonne",
|
||||
"table.deleteRow": "Supprimer la ligne",
|
||||
|
|
|
|||
|
|
@ -201,6 +201,8 @@
|
|||
"builtins.lobe-skills.apiName.searchSkill": "Rechercher des Compétences",
|
||||
"builtins.lobe-skills.title": "Compétences",
|
||||
"builtins.lobe-tools.apiName.activateTools": "Activer les Outils",
|
||||
"builtins.lobe-topic-reference.apiName.getTopicContext": "Obtenir le contexte du sujet",
|
||||
"builtins.lobe-topic-reference.title": "Référence de sujet",
|
||||
"builtins.lobe-user-memory.apiName.addContextMemory": "Ajouter une mémoire de contexte",
|
||||
"builtins.lobe-user-memory.apiName.addExperienceMemory": "Ajouter une mémoire d'expérience",
|
||||
"builtins.lobe-user-memory.apiName.addIdentityMemory": "Ajouter une mémoire d'identité",
|
||||
|
|
|
|||
|
|
@ -1,4 +1,10 @@
|
|||
{
|
||||
"actionTag.category.command": "Comando",
|
||||
"actionTag.category.skill": "Abilità",
|
||||
"actionTag.category.tool": "Strumento",
|
||||
"actionTag.tooltip.command": "Esegue un comando slash lato client prima dell'invio.",
|
||||
"actionTag.tooltip.skill": "Carica un pacchetto di abilità riutilizzabile per questa richiesta.",
|
||||
"actionTag.tooltip.tool": "Contrassegna uno strumento selezionato esplicitamente dall'utente per questa richiesta.",
|
||||
"actions.expand.off": "Comprimi",
|
||||
"actions.expand.on": "Espandi",
|
||||
"actions.typobar.off": "Nascondi barra di formattazione",
|
||||
|
|
@ -34,12 +40,17 @@
|
|||
"modifier.acceptAll": "Mantieni tutto",
|
||||
"modifier.reject": "Ripristina",
|
||||
"modifier.rejectAll": "Ripristina tutto",
|
||||
"slash.compact": "Compatta il contesto",
|
||||
"slash.h1": "Titolo 1",
|
||||
"slash.h2": "Titolo 2",
|
||||
"slash.h3": "Titolo 3",
|
||||
"slash.hr": "Separatore",
|
||||
"slash.newTopic": "Invia in un nuovo argomento",
|
||||
"slash.rewrite": "Riscrivi",
|
||||
"slash.summarize": "Riassumi",
|
||||
"slash.table": "Tabella",
|
||||
"slash.tex": "Formula TeX",
|
||||
"slash.translate": "Traduci",
|
||||
"table.delete": "Elimina tabella",
|
||||
"table.deleteColumn": "Elimina colonna",
|
||||
"table.deleteRow": "Elimina riga",
|
||||
|
|
|
|||
|
|
@ -201,6 +201,8 @@
|
|||
"builtins.lobe-skills.apiName.searchSkill": "Cerca Abilità",
|
||||
"builtins.lobe-skills.title": "Abilità",
|
||||
"builtins.lobe-tools.apiName.activateTools": "Attiva Strumenti",
|
||||
"builtins.lobe-topic-reference.apiName.getTopicContext": "Ottieni contesto dell'argomento",
|
||||
"builtins.lobe-topic-reference.title": "Riferimento argomento",
|
||||
"builtins.lobe-user-memory.apiName.addContextMemory": "Aggiungi memoria contestuale",
|
||||
"builtins.lobe-user-memory.apiName.addExperienceMemory": "Aggiungi memoria esperienziale",
|
||||
"builtins.lobe-user-memory.apiName.addIdentityMemory": "Aggiungi memoria identità",
|
||||
|
|
|
|||
|
|
@ -1,4 +1,10 @@
|
|||
{
|
||||
"actionTag.category.command": "コマンド",
|
||||
"actionTag.category.skill": "スキル",
|
||||
"actionTag.category.tool": "ツール",
|
||||
"actionTag.tooltip.command": "送信前にクライアント側のスラッシュコマンドを実行します。",
|
||||
"actionTag.tooltip.skill": "このリクエスト用に再利用可能なスキルパッケージを読み込みます。",
|
||||
"actionTag.tooltip.tool": "このリクエストでユーザーが明示的に選択したツールを示します。",
|
||||
"actions.expand.off": "折りたたむ",
|
||||
"actions.expand.on": "展開する",
|
||||
"actions.typobar.off": "書式ツールバーを非表示",
|
||||
|
|
@ -34,12 +40,17 @@
|
|||
"modifier.acceptAll": "すべて承諾する",
|
||||
"modifier.reject": "取り消し",
|
||||
"modifier.rejectAll": "すべてを取り消す",
|
||||
"slash.compact": "コンテキストを圧縮",
|
||||
"slash.h1": "見出し1",
|
||||
"slash.h2": "見出し2",
|
||||
"slash.h3": "見出し3",
|
||||
"slash.hr": "区切り線",
|
||||
"slash.newTopic": "新しいトピックで送信",
|
||||
"slash.rewrite": "書き直す",
|
||||
"slash.summarize": "要約",
|
||||
"slash.table": "表",
|
||||
"slash.tex": "TeX 数式",
|
||||
"slash.translate": "翻訳",
|
||||
"table.delete": "表を削除",
|
||||
"table.deleteColumn": "列を削除",
|
||||
"table.deleteRow": "行を削除",
|
||||
|
|
|
|||
|
|
@ -201,6 +201,8 @@
|
|||
"builtins.lobe-skills.apiName.searchSkill": "スキルを検索",
|
||||
"builtins.lobe-skills.title": "スキル",
|
||||
"builtins.lobe-tools.apiName.activateTools": "ツールをアクティブ化",
|
||||
"builtins.lobe-topic-reference.apiName.getTopicContext": "トピックコンテキストを取得",
|
||||
"builtins.lobe-topic-reference.title": "トピック参照",
|
||||
"builtins.lobe-user-memory.apiName.addContextMemory": "コンテキスト記憶を追加",
|
||||
"builtins.lobe-user-memory.apiName.addExperienceMemory": "経験記憶を追加",
|
||||
"builtins.lobe-user-memory.apiName.addIdentityMemory": "アイデンティティ記憶を追加",
|
||||
|
|
|
|||
|
|
@ -1,4 +1,10 @@
|
|||
{
|
||||
"actionTag.category.command": "명령",
|
||||
"actionTag.category.skill": "스킬",
|
||||
"actionTag.category.tool": "도구",
|
||||
"actionTag.tooltip.command": "전송 전에 클라이언트 측 슬래시 명령을 실행합니다.",
|
||||
"actionTag.tooltip.skill": "이번 요청에 사용할 재사용 가능한 스킬 패키지를 불러옵니다.",
|
||||
"actionTag.tooltip.tool": "이번 요청에서 사용자가 명시적으로 선택한 도구를 표시합니다.",
|
||||
"actions.expand.off": "접기",
|
||||
"actions.expand.on": "펼치기",
|
||||
"actions.typobar.off": "서식 도구 모음 숨기기",
|
||||
|
|
@ -34,12 +40,17 @@
|
|||
"modifier.acceptAll": "모두 수락",
|
||||
"modifier.reject": "취소",
|
||||
"modifier.rejectAll": "모두 취소",
|
||||
"slash.compact": "컨텍스트 압축",
|
||||
"slash.h1": "제목 1",
|
||||
"slash.h2": "제목 2",
|
||||
"slash.h3": "제목 3",
|
||||
"slash.hr": "구분선",
|
||||
"slash.newTopic": "새 토픽으로 보내기",
|
||||
"slash.rewrite": "다시 쓰기",
|
||||
"slash.summarize": "요약",
|
||||
"slash.table": "표",
|
||||
"slash.tex": "TeX 수식",
|
||||
"slash.translate": "번역",
|
||||
"table.delete": "표 삭제",
|
||||
"table.deleteColumn": "열 삭제",
|
||||
"table.deleteRow": "행 삭제",
|
||||
|
|
|
|||
|
|
@ -201,6 +201,8 @@
|
|||
"builtins.lobe-skills.apiName.searchSkill": "스킬 검색",
|
||||
"builtins.lobe-skills.title": "스킬",
|
||||
"builtins.lobe-tools.apiName.activateTools": "도구 활성화",
|
||||
"builtins.lobe-topic-reference.apiName.getTopicContext": "토픽 컨텍스트 가져오기",
|
||||
"builtins.lobe-topic-reference.title": "토픽 참조",
|
||||
"builtins.lobe-user-memory.apiName.addContextMemory": "상황 기억 추가",
|
||||
"builtins.lobe-user-memory.apiName.addExperienceMemory": "경험 기억 추가",
|
||||
"builtins.lobe-user-memory.apiName.addIdentityMemory": "신원 기억 추가",
|
||||
|
|
|
|||
|
|
@ -1,4 +1,10 @@
|
|||
{
|
||||
"actionTag.category.command": "Commando",
|
||||
"actionTag.category.skill": "Vaardigheid",
|
||||
"actionTag.category.tool": "Hulpmiddel",
|
||||
"actionTag.tooltip.command": "Voert vóór het verzenden een slash-commando aan de clientzijde uit.",
|
||||
"actionTag.tooltip.skill": "Laadt voor dit verzoek een herbruikbaar vaardigheidspakket.",
|
||||
"actionTag.tooltip.tool": "Markeert een hulpmiddel dat de gebruiker expliciet voor dit verzoek heeft geselecteerd.",
|
||||
"actions.expand.off": "Samenvouwen",
|
||||
"actions.expand.on": "Uitvouwen",
|
||||
"actions.typobar.off": "Opmaakwerkbalk verbergen",
|
||||
|
|
@ -34,12 +40,17 @@
|
|||
"modifier.acceptAll": "Alles behouden",
|
||||
"modifier.reject": "Ongedaan maken",
|
||||
"modifier.rejectAll": "Alles ongedaan maken",
|
||||
"slash.compact": "Context comprimeren",
|
||||
"slash.h1": "Kop 1",
|
||||
"slash.h2": "Kop 2",
|
||||
"slash.h3": "Kop 3",
|
||||
"slash.hr": "Scheiding",
|
||||
"slash.newTopic": "Verzenden in nieuw onderwerp",
|
||||
"slash.rewrite": "Herschrijven",
|
||||
"slash.summarize": "Samenvatten",
|
||||
"slash.table": "Tabel",
|
||||
"slash.tex": "TeX-formule",
|
||||
"slash.translate": "Vertalen",
|
||||
"table.delete": "Tabel verwijderen",
|
||||
"table.deleteColumn": "Kolom verwijderen",
|
||||
"table.deleteRow": "Rij verwijderen",
|
||||
|
|
|
|||
|
|
@ -201,6 +201,8 @@
|
|||
"builtins.lobe-skills.apiName.searchSkill": "Vaardigheden Zoeken",
|
||||
"builtins.lobe-skills.title": "Vaardigheden",
|
||||
"builtins.lobe-tools.apiName.activateTools": "Hulpmiddelen Activeren",
|
||||
"builtins.lobe-topic-reference.apiName.getTopicContext": "Onderwerpcontext ophalen",
|
||||
"builtins.lobe-topic-reference.title": "Onderwerpverwijzing",
|
||||
"builtins.lobe-user-memory.apiName.addContextMemory": "Contextgeheugen toevoegen",
|
||||
"builtins.lobe-user-memory.apiName.addExperienceMemory": "Ervaringsgeheugen toevoegen",
|
||||
"builtins.lobe-user-memory.apiName.addIdentityMemory": "Identiteitsgeheugen toevoegen",
|
||||
|
|
|
|||
|
|
@ -1,4 +1,10 @@
|
|||
{
|
||||
"actionTag.category.command": "Polecenie",
|
||||
"actionTag.category.skill": "Umiejętność",
|
||||
"actionTag.category.tool": "Narzędzie",
|
||||
"actionTag.tooltip.command": "Uruchamia polecenie slash po stronie klienta przed wysłaniem.",
|
||||
"actionTag.tooltip.skill": "Wczytuje pakiet umiejętności wielokrotnego użytku dla tego żądania.",
|
||||
"actionTag.tooltip.tool": "Oznacza narzędzie, które użytkownik jawnie wybrał dla tego żądania.",
|
||||
"actions.expand.off": "Zwiń",
|
||||
"actions.expand.on": "Rozwiń",
|
||||
"actions.typobar.off": "Ukryj pasek formatowania",
|
||||
|
|
@ -34,12 +40,17 @@
|
|||
"modifier.acceptAll": "Zachowaj wszystkie",
|
||||
"modifier.reject": "Cofnij",
|
||||
"modifier.rejectAll": "Cofnij wszystkie",
|
||||
"slash.compact": "Kompresuj kontekst",
|
||||
"slash.h1": "Nagłówek 1",
|
||||
"slash.h2": "Nagłówek 2",
|
||||
"slash.h3": "Nagłówek 3",
|
||||
"slash.hr": "Separator",
|
||||
"slash.newTopic": "Wyślij w nowym temacie",
|
||||
"slash.rewrite": "Przepisz",
|
||||
"slash.summarize": "Podsumuj",
|
||||
"slash.table": "Tabela",
|
||||
"slash.tex": "Formuła TeX",
|
||||
"slash.translate": "Tłumacz",
|
||||
"table.delete": "Usuń tabelę",
|
||||
"table.deleteColumn": "Usuń kolumnę",
|
||||
"table.deleteRow": "Usuń wiersz",
|
||||
|
|
|
|||
|
|
@ -201,6 +201,8 @@
|
|||
"builtins.lobe-skills.apiName.searchSkill": "Wyszukaj Umiejętności",
|
||||
"builtins.lobe-skills.title": "Umiejętności",
|
||||
"builtins.lobe-tools.apiName.activateTools": "Aktywuj Narzędzia",
|
||||
"builtins.lobe-topic-reference.apiName.getTopicContext": "Pobierz kontekst tematu",
|
||||
"builtins.lobe-topic-reference.title": "Odwołanie do tematu",
|
||||
"builtins.lobe-user-memory.apiName.addContextMemory": "Dodaj pamięć kontekstu",
|
||||
"builtins.lobe-user-memory.apiName.addExperienceMemory": "Dodaj pamięć doświadczenia",
|
||||
"builtins.lobe-user-memory.apiName.addIdentityMemory": "Dodaj pamięć tożsamości",
|
||||
|
|
|
|||
|
|
@ -1,4 +1,10 @@
|
|||
{
|
||||
"actionTag.category.command": "Comando",
|
||||
"actionTag.category.skill": "Habilidade",
|
||||
"actionTag.category.tool": "Ferramenta",
|
||||
"actionTag.tooltip.command": "Executa um comando slash no cliente antes do envio.",
|
||||
"actionTag.tooltip.skill": "Carrega um pacote de habilidades reutilizável para esta solicitação.",
|
||||
"actionTag.tooltip.tool": "Marca uma ferramenta que o usuário selecionou explicitamente para esta solicitação.",
|
||||
"actions.expand.off": "Recolher",
|
||||
"actions.expand.on": "Expandir",
|
||||
"actions.typobar.off": "Ocultar barra de formatação",
|
||||
|
|
@ -34,12 +40,17 @@
|
|||
"modifier.acceptAll": "Manter tudo",
|
||||
"modifier.reject": "Reverter",
|
||||
"modifier.rejectAll": "Reverter tudo",
|
||||
"slash.compact": "Compactar contexto",
|
||||
"slash.h1": "Título 1",
|
||||
"slash.h2": "Título 2",
|
||||
"slash.h3": "Título 3",
|
||||
"slash.hr": "Divisor",
|
||||
"slash.newTopic": "Enviar em novo tópico",
|
||||
"slash.rewrite": "Reescrever",
|
||||
"slash.summarize": "Resumir",
|
||||
"slash.table": "Tabela",
|
||||
"slash.tex": "Fórmula TeX",
|
||||
"slash.translate": "Traduzir",
|
||||
"table.delete": "Excluir tabela",
|
||||
"table.deleteColumn": "Excluir coluna",
|
||||
"table.deleteRow": "Excluir linha",
|
||||
|
|
|
|||
|
|
@ -201,6 +201,8 @@
|
|||
"builtins.lobe-skills.apiName.searchSkill": "Buscar Habilidades",
|
||||
"builtins.lobe-skills.title": "Habilidades",
|
||||
"builtins.lobe-tools.apiName.activateTools": "Ativar Ferramentas",
|
||||
"builtins.lobe-topic-reference.apiName.getTopicContext": "Obter contexto do tópico",
|
||||
"builtins.lobe-topic-reference.title": "Referência de tópico",
|
||||
"builtins.lobe-user-memory.apiName.addContextMemory": "Adicionar memória de contexto",
|
||||
"builtins.lobe-user-memory.apiName.addExperienceMemory": "Adicionar memória de experiência",
|
||||
"builtins.lobe-user-memory.apiName.addIdentityMemory": "Adicionar memória de identidade",
|
||||
|
|
|
|||
|
|
@ -1,4 +1,10 @@
|
|||
{
|
||||
"actionTag.category.command": "Команда",
|
||||
"actionTag.category.skill": "Навык",
|
||||
"actionTag.category.tool": "Инструмент",
|
||||
"actionTag.tooltip.command": "Выполняет клиентскую slash-команду перед отправкой.",
|
||||
"actionTag.tooltip.skill": "Загружает для этого запроса переиспользуемый пакет навыков.",
|
||||
"actionTag.tooltip.tool": "Помечает инструмент, который пользователь явно выбрал для этого запроса.",
|
||||
"actions.expand.off": "Свернуть",
|
||||
"actions.expand.on": "Развернуть",
|
||||
"actions.typobar.off": "Скрыть панель форматирования",
|
||||
|
|
@ -34,12 +40,17 @@
|
|||
"modifier.acceptAll": "Оставить все",
|
||||
"modifier.reject": "Отменить",
|
||||
"modifier.rejectAll": "Отменить все",
|
||||
"slash.compact": "Сжать контекст",
|
||||
"slash.h1": "Заголовок 1",
|
||||
"slash.h2": "Заголовок 2",
|
||||
"slash.h3": "Заголовок 3",
|
||||
"slash.hr": "Разделитель",
|
||||
"slash.newTopic": "Отправить в новую тему",
|
||||
"slash.rewrite": "Переписать",
|
||||
"slash.summarize": "Суммировать",
|
||||
"slash.table": "Таблица",
|
||||
"slash.tex": "Формула TeX",
|
||||
"slash.translate": "Перевести",
|
||||
"table.delete": "Удалить таблицу",
|
||||
"table.deleteColumn": "Удалить столбец",
|
||||
"table.deleteRow": "Удалить строку",
|
||||
|
|
|
|||
|
|
@ -201,6 +201,8 @@
|
|||
"builtins.lobe-skills.apiName.searchSkill": "Поиск навыков",
|
||||
"builtins.lobe-skills.title": "Навыки",
|
||||
"builtins.lobe-tools.apiName.activateTools": "Активировать инструменты",
|
||||
"builtins.lobe-topic-reference.apiName.getTopicContext": "Получить контекст темы",
|
||||
"builtins.lobe-topic-reference.title": "Ссылка на тему",
|
||||
"builtins.lobe-user-memory.apiName.addContextMemory": "Добавить контекстную память",
|
||||
"builtins.lobe-user-memory.apiName.addExperienceMemory": "Добавить память опыта",
|
||||
"builtins.lobe-user-memory.apiName.addIdentityMemory": "Добавить память личности",
|
||||
|
|
|
|||
|
|
@ -1,4 +1,10 @@
|
|||
{
|
||||
"actionTag.category.command": "Komut",
|
||||
"actionTag.category.skill": "Beceri",
|
||||
"actionTag.category.tool": "Araç",
|
||||
"actionTag.tooltip.command": "Göndermeden önce istemci tarafında bir slash komutu çalıştırır.",
|
||||
"actionTag.tooltip.skill": "Bu istek için yeniden kullanılabilir bir beceri paketini yükler.",
|
||||
"actionTag.tooltip.tool": "Kullanıcının bu istek için açıkça seçtiği bir aracı işaretler.",
|
||||
"actions.expand.off": "Daralt",
|
||||
"actions.expand.on": "Genişlet",
|
||||
"actions.typobar.off": "Biçimlendirme araç çubuğunu gizle",
|
||||
|
|
@ -34,12 +40,17 @@
|
|||
"modifier.acceptAll": "Tümünü koru",
|
||||
"modifier.reject": "Geri al",
|
||||
"modifier.rejectAll": "Tümünü geri al",
|
||||
"slash.compact": "Bağlamı sıkıştır",
|
||||
"slash.h1": "Başlık 1",
|
||||
"slash.h2": "Başlık 2",
|
||||
"slash.h3": "Başlık 3",
|
||||
"slash.hr": "Ayraç",
|
||||
"slash.newTopic": "Yeni konuda gönder",
|
||||
"slash.rewrite": "Yeniden yaz",
|
||||
"slash.summarize": "Özetle",
|
||||
"slash.table": "Tablo",
|
||||
"slash.tex": "TeX Formülü",
|
||||
"slash.translate": "Çevir",
|
||||
"table.delete": "Tabloyu sil",
|
||||
"table.deleteColumn": "Sütunu sil",
|
||||
"table.deleteRow": "Satırı sil",
|
||||
|
|
|
|||
|
|
@ -201,6 +201,8 @@
|
|||
"builtins.lobe-skills.apiName.searchSkill": "Becerileri Ara",
|
||||
"builtins.lobe-skills.title": "Beceriler",
|
||||
"builtins.lobe-tools.apiName.activateTools": "Araçları Etkinleştir",
|
||||
"builtins.lobe-topic-reference.apiName.getTopicContext": "Konu bağlamını al",
|
||||
"builtins.lobe-topic-reference.title": "Konu referansı",
|
||||
"builtins.lobe-user-memory.apiName.addContextMemory": "Bağlam hafızası ekle",
|
||||
"builtins.lobe-user-memory.apiName.addExperienceMemory": "Deneyim hafızası ekle",
|
||||
"builtins.lobe-user-memory.apiName.addIdentityMemory": "Kimlik hafızası ekle",
|
||||
|
|
|
|||
|
|
@ -1,4 +1,10 @@
|
|||
{
|
||||
"actionTag.category.command": "Lệnh",
|
||||
"actionTag.category.skill": "Kỹ năng",
|
||||
"actionTag.category.tool": "Công cụ",
|
||||
"actionTag.tooltip.command": "Chạy lệnh slash phía client trước khi gửi.",
|
||||
"actionTag.tooltip.skill": "Tải gói kỹ năng có thể tái sử dụng cho yêu cầu này.",
|
||||
"actionTag.tooltip.tool": "Đánh dấu công cụ mà người dùng đã chọn rõ ràng cho yêu cầu này.",
|
||||
"actions.expand.off": "Thu gọn",
|
||||
"actions.expand.on": "Mở rộng",
|
||||
"actions.typobar.off": "Ẩn thanh công cụ định dạng",
|
||||
|
|
@ -34,12 +40,17 @@
|
|||
"modifier.acceptAll": "Giữ tất cả",
|
||||
"modifier.reject": "Hoàn tác",
|
||||
"modifier.rejectAll": "Hoàn tác tất cả",
|
||||
"slash.compact": "Nén ngữ cảnh",
|
||||
"slash.h1": "Tiêu đề 1",
|
||||
"slash.h2": "Tiêu đề 2",
|
||||
"slash.h3": "Tiêu đề 3",
|
||||
"slash.hr": "Đường phân cách",
|
||||
"slash.newTopic": "Gửi trong chủ đề mới",
|
||||
"slash.rewrite": "Viết lại",
|
||||
"slash.summarize": "Tóm tắt",
|
||||
"slash.table": "Bảng",
|
||||
"slash.tex": "Công thức TeX",
|
||||
"slash.translate": "Dịch",
|
||||
"table.delete": "Xóa bảng",
|
||||
"table.deleteColumn": "Xóa cột",
|
||||
"table.deleteRow": "Xóa hàng",
|
||||
|
|
|
|||
|
|
@ -201,6 +201,8 @@
|
|||
"builtins.lobe-skills.apiName.searchSkill": "Tìm kiếm Kỹ năng",
|
||||
"builtins.lobe-skills.title": "Kỹ năng",
|
||||
"builtins.lobe-tools.apiName.activateTools": "Kích hoạt Công cụ",
|
||||
"builtins.lobe-topic-reference.apiName.getTopicContext": "Lấy ngữ cảnh chủ đề",
|
||||
"builtins.lobe-topic-reference.title": "Tham chiếu chủ đề",
|
||||
"builtins.lobe-user-memory.apiName.addContextMemory": "Thêm trí nhớ ngữ cảnh",
|
||||
"builtins.lobe-user-memory.apiName.addExperienceMemory": "Thêm trí nhớ kinh nghiệm",
|
||||
"builtins.lobe-user-memory.apiName.addIdentityMemory": "Thêm trí nhớ danh tính",
|
||||
|
|
|
|||
|
|
@ -1,4 +1,10 @@
|
|||
{
|
||||
"actionTag.category.command": "指令",
|
||||
"actionTag.category.skill": "技能",
|
||||
"actionTag.category.tool": "工具",
|
||||
"actionTag.tooltip.command": "发送前会先在客户端执行这条 slash 指令。",
|
||||
"actionTag.tooltip.skill": "表示这是一条可为本次请求加载的技能包。",
|
||||
"actionTag.tooltip.tool": "表示这是用户为本次请求显式选中的工具。",
|
||||
"actions.expand.off": "收起",
|
||||
"actions.expand.on": "展开",
|
||||
"actions.typobar.off": "隐藏格式工具栏",
|
||||
|
|
@ -34,12 +40,17 @@
|
|||
"modifier.acceptAll": "全部保留",
|
||||
"modifier.reject": "撤销",
|
||||
"modifier.rejectAll": "全部撤销",
|
||||
"slash.compact": "压缩上下文",
|
||||
"slash.h1": "一级标题",
|
||||
"slash.h2": "二级标题",
|
||||
"slash.h3": "三级标题",
|
||||
"slash.hr": "分割线",
|
||||
"slash.newTopic": "在新话题中发送",
|
||||
"slash.rewrite": "重写",
|
||||
"slash.summarize": "总结",
|
||||
"slash.table": "表格",
|
||||
"slash.tex": "TeX 公式",
|
||||
"slash.translate": "翻译",
|
||||
"table.delete": "删除表格",
|
||||
"table.deleteColumn": "删除列",
|
||||
"table.deleteRow": "删除行",
|
||||
|
|
|
|||
|
|
@ -202,6 +202,8 @@
|
|||
"builtins.lobe-skills.apiName.searchSkill": "搜索技能",
|
||||
"builtins.lobe-skills.title": "技能",
|
||||
"builtins.lobe-tools.apiName.activateTools": "激活工具",
|
||||
"builtins.lobe-topic-reference.apiName.getTopicContext": "获取话题上下文",
|
||||
"builtins.lobe-topic-reference.title": "话题引用",
|
||||
"builtins.lobe-user-memory.apiName.addContextMemory": "添加情境记忆",
|
||||
"builtins.lobe-user-memory.apiName.addExperienceMemory": "添加经验记忆",
|
||||
"builtins.lobe-user-memory.apiName.addIdentityMemory": "添加身份记忆",
|
||||
|
|
|
|||
|
|
@ -1,4 +1,10 @@
|
|||
{
|
||||
"actionTag.category.command": "命令",
|
||||
"actionTag.category.skill": "技能",
|
||||
"actionTag.category.tool": "工具",
|
||||
"actionTag.tooltip.command": "在發送前執行客戶端斜線命令。",
|
||||
"actionTag.tooltip.skill": "為此次請求載入可重用的技能包。",
|
||||
"actionTag.tooltip.tool": "標記使用者在此次請求中明確選擇的工具。",
|
||||
"actions.expand.off": "收合",
|
||||
"actions.expand.on": "展開",
|
||||
"actions.typobar.off": "隱藏格式工具列",
|
||||
|
|
@ -34,12 +40,17 @@
|
|||
"modifier.acceptAll": "全部接受",
|
||||
"modifier.reject": "撤銷",
|
||||
"modifier.rejectAll": "全部取消",
|
||||
"slash.compact": "壓縮上下文",
|
||||
"slash.h1": "一級標題",
|
||||
"slash.h2": "二級標題",
|
||||
"slash.h3": "三級標題",
|
||||
"slash.hr": "分隔線",
|
||||
"slash.newTopic": "在新話題中發送",
|
||||
"slash.rewrite": "改寫",
|
||||
"slash.summarize": "摘要",
|
||||
"slash.table": "表格",
|
||||
"slash.tex": "TeX 公式",
|
||||
"slash.translate": "翻譯",
|
||||
"table.delete": "刪除表格",
|
||||
"table.deleteColumn": "刪除列",
|
||||
"table.deleteRow": "刪除行",
|
||||
|
|
|
|||
|
|
@ -201,6 +201,8 @@
|
|||
"builtins.lobe-skills.apiName.searchSkill": "搜尋技能",
|
||||
"builtins.lobe-skills.title": "技能",
|
||||
"builtins.lobe-tools.apiName.activateTools": "啟用工具",
|
||||
"builtins.lobe-topic-reference.apiName.getTopicContext": "取得話題上下文",
|
||||
"builtins.lobe-topic-reference.title": "話題引用",
|
||||
"builtins.lobe-user-memory.apiName.addContextMemory": "新增情境記憶",
|
||||
"builtins.lobe-user-memory.apiName.addExperienceMemory": "新增經驗記憶",
|
||||
"builtins.lobe-user-memory.apiName.addIdentityMemory": "新增身份記憶",
|
||||
|
|
|
|||
|
|
@ -195,6 +195,7 @@
|
|||
"@icons-pack/react-simple-icons": "^13.8.0",
|
||||
"@khmyznikov/pwa-install": "0.3.9",
|
||||
"@langchain/community": "^0.3.59",
|
||||
"@lexical/utils": "^0.39.0",
|
||||
"@lobechat/adapter-lark": "workspace:*",
|
||||
"@lobechat/adapter-qq": "workspace:*",
|
||||
"@lobechat/agent-runtime": "workspace:*",
|
||||
|
|
@ -216,6 +217,7 @@
|
|||
"@lobechat/builtin-tool-skill-store": "workspace:*",
|
||||
"@lobechat/builtin-tool-skills": "workspace:*",
|
||||
"@lobechat/builtin-tool-tools": "workspace:*",
|
||||
"@lobechat/builtin-tool-topic-reference": "workspace:*",
|
||||
"@lobechat/builtin-tool-web-browsing": "workspace:*",
|
||||
"@lobechat/builtin-tools": "workspace:*",
|
||||
"@lobechat/business-config": "workspace:*",
|
||||
|
|
@ -312,6 +314,7 @@
|
|||
"fflate": "^0.8.2",
|
||||
"ffmpeg-static": "^5.3.0",
|
||||
"file-type": "^21.3.0",
|
||||
"fuse.js": "^7.0.0",
|
||||
"gray-matter": "^4.0.3",
|
||||
"html-to-text": "^9.0.5",
|
||||
"i18next": "^25.8.0",
|
||||
|
|
@ -326,6 +329,7 @@
|
|||
"langchain": "^0.3.37",
|
||||
"langfuse": "^3.38.6",
|
||||
"langfuse-core": "^3.38.6",
|
||||
"lexical": "^0.39.0",
|
||||
"lucide-react": "^0.562.0",
|
||||
"mammoth": "^1.11.0",
|
||||
"marked": "^17.0.1",
|
||||
|
|
|
|||
|
|
@ -67,8 +67,14 @@ class AgentManagementExecutor extends BaseExecutor<typeof AgentManagementApiName
|
|||
params: CallAgentParams,
|
||||
ctx: BuiltinToolContext,
|
||||
): Promise<BuiltinToolResult> => {
|
||||
const { agentId, instruction, runAsTask, taskTitle, timeout, skipCallSupervisor = false } =
|
||||
params;
|
||||
const {
|
||||
agentId,
|
||||
instruction,
|
||||
runAsTask,
|
||||
taskTitle,
|
||||
timeout,
|
||||
skipCallSupervisor = false,
|
||||
} = params;
|
||||
|
||||
if (runAsTask) {
|
||||
// Execute as async task using GTD exec_task pattern
|
||||
|
|
@ -184,17 +190,27 @@ class AgentManagementExecutor extends BaseExecutor<typeof AgentManagementApiName
|
|||
return;
|
||||
}
|
||||
|
||||
// If instruction is provided, inject it as a virtual User Message
|
||||
// Same pattern as group orchestration's call_agent executor:
|
||||
// virtual message with <speaker> tag gives the called agent clear direction
|
||||
const now = Date.now();
|
||||
const messagesWithInstruction = instruction
|
||||
? [
|
||||
...messages,
|
||||
{
|
||||
content: `<speaker name="Supervisor" />\n${instruction}`,
|
||||
createdAt: now,
|
||||
id: `virtual_speak_instruction_${now}`,
|
||||
role: 'user' as const,
|
||||
updatedAt: now,
|
||||
},
|
||||
]
|
||||
: messages;
|
||||
|
||||
try {
|
||||
// Execute with subAgentId + scope: 'sub_agent'
|
||||
// - context.agentId = current agent (for message storage and message.agentId)
|
||||
// - context.topicId = current topic
|
||||
// - context.subAgentId = target agent (for agent config - model, prompt, etc.)
|
||||
// - context.scope = 'sub_agent' (indicates this is agent-to-agent call, not group)
|
||||
// This will create messages in current agent's conversation but use target agent's config
|
||||
// The message.agentId will still be current agent, but metadata stores subAgentId + scope
|
||||
await get().internal_execAgentRuntime({
|
||||
context: { ...conversationContext, subAgentId: agentId, scope: 'sub_agent' },
|
||||
messages,
|
||||
messages: messagesWithInstruction,
|
||||
parentMessageId: ctx.messageId,
|
||||
parentMessageType: 'tool',
|
||||
});
|
||||
|
|
|
|||
13
packages/builtin-tool-topic-reference/package.json
Normal file
13
packages/builtin-tool-topic-reference/package.json
Normal file
|
|
@ -0,0 +1,13 @@
|
|||
{
|
||||
"name": "@lobechat/builtin-tool-topic-reference",
|
||||
"version": "1.0.0",
|
||||
"private": true,
|
||||
"exports": {
|
||||
".": "./src/index.ts",
|
||||
"./executor": "./src/executor/index.ts"
|
||||
},
|
||||
"main": "./src/index.ts",
|
||||
"devDependencies": {
|
||||
"@lobechat/types": "workspace:*"
|
||||
}
|
||||
}
|
||||
40
packages/builtin-tool-topic-reference/src/executor/index.ts
Normal file
40
packages/builtin-tool-topic-reference/src/executor/index.ts
Normal file
|
|
@ -0,0 +1,40 @@
|
|||
import type { BuiltinToolContext, BuiltinToolResult } from '@lobechat/types';
|
||||
import { BaseExecutor } from '@lobechat/types';
|
||||
|
||||
import { lambdaClient } from '@/libs/trpc/client';
|
||||
|
||||
import { TopicReferenceIdentifier } from '../types';
|
||||
|
||||
const TopicReferenceApiName = {
|
||||
getTopicContext: 'getTopicContext',
|
||||
} as const;
|
||||
|
||||
interface GetTopicContextParams {
|
||||
topicId: string;
|
||||
}
|
||||
|
||||
class TopicReferenceExecutor extends BaseExecutor<typeof TopicReferenceApiName> {
|
||||
readonly identifier = TopicReferenceIdentifier;
|
||||
protected readonly apiEnum = TopicReferenceApiName;
|
||||
|
||||
getTopicContext = async (
|
||||
params: GetTopicContextParams,
|
||||
_ctx: BuiltinToolContext,
|
||||
): Promise<BuiltinToolResult> => {
|
||||
const { topicId } = params;
|
||||
|
||||
if (!topicId) {
|
||||
return { content: 'topicId is required', success: false };
|
||||
}
|
||||
|
||||
try {
|
||||
const result = await lambdaClient.topic.getTopicContext.query({ topicId });
|
||||
return { content: result.content, success: result.success };
|
||||
} catch (error) {
|
||||
const errorMessage = error instanceof Error ? error.message : String(error);
|
||||
return { content: `Failed to fetch topic context: ${errorMessage}`, success: false };
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
export const topicReferenceExecutor = new TopicReferenceExecutor();
|
||||
6
packages/builtin-tool-topic-reference/src/index.ts
Normal file
6
packages/builtin-tool-topic-reference/src/index.ts
Normal file
|
|
@ -0,0 +1,6 @@
|
|||
export { TopicReferenceManifest } from './manifest';
|
||||
export {
|
||||
TopicReferenceApiName,
|
||||
type TopicReferenceApiNameType,
|
||||
TopicReferenceIdentifier,
|
||||
} from './types';
|
||||
32
packages/builtin-tool-topic-reference/src/manifest.ts
Normal file
32
packages/builtin-tool-topic-reference/src/manifest.ts
Normal file
|
|
@ -0,0 +1,32 @@
|
|||
import { type BuiltinToolManifest } from '@lobechat/types';
|
||||
|
||||
import { TopicReferenceApiName, TopicReferenceIdentifier } from './types';
|
||||
|
||||
export const TopicReferenceManifest: BuiltinToolManifest = {
|
||||
api: [
|
||||
{
|
||||
description:
|
||||
'Retrieve context from a referenced topic conversation. Returns the topic summary if available, otherwise returns the most recent messages. Use this when you see a topic reference tag in the user message and need to understand what was discussed in that topic.',
|
||||
name: TopicReferenceApiName.getTopicContext,
|
||||
parameters: {
|
||||
additionalProperties: false,
|
||||
properties: {
|
||||
topicId: {
|
||||
description: 'The ID of the topic to retrieve context from',
|
||||
type: 'string',
|
||||
},
|
||||
},
|
||||
required: ['topicId'],
|
||||
type: 'object',
|
||||
},
|
||||
},
|
||||
],
|
||||
identifier: TopicReferenceIdentifier,
|
||||
meta: {
|
||||
avatar: '📋',
|
||||
description: 'Retrieve context from referenced topic conversations',
|
||||
title: 'Topic Reference',
|
||||
},
|
||||
systemRole: '',
|
||||
type: 'builtin',
|
||||
};
|
||||
8
packages/builtin-tool-topic-reference/src/types.ts
Normal file
8
packages/builtin-tool-topic-reference/src/types.ts
Normal file
|
|
@ -0,0 +1,8 @@
|
|||
export const TopicReferenceIdentifier = 'lobe-topic-reference';
|
||||
|
||||
export const TopicReferenceApiName = {
|
||||
getTopicContext: 'getTopicContext',
|
||||
} as const;
|
||||
|
||||
export type TopicReferenceApiNameType =
|
||||
(typeof TopicReferenceApiName)[keyof typeof TopicReferenceApiName];
|
||||
18
packages/builtin-tool-topic-reference/tsconfig.json
Normal file
18
packages/builtin-tool-topic-reference/tsconfig.json
Normal file
|
|
@ -0,0 +1,18 @@
|
|||
{
|
||||
"compilerOptions": {
|
||||
"target": "ES2022",
|
||||
"module": "ESNext",
|
||||
"moduleResolution": "node",
|
||||
"declaration": true,
|
||||
"outDir": "dist",
|
||||
"rootDir": "src",
|
||||
"strict": true,
|
||||
"skipLibCheck": true,
|
||||
"esModuleInterop": true,
|
||||
"allowSyntheticDefaultImports": true,
|
||||
"forceConsistentCasingInFileNames": true,
|
||||
"resolveJsonModule": true
|
||||
},
|
||||
"exclude": ["node_modules"],
|
||||
"include": ["src/**/*"]
|
||||
}
|
||||
|
|
@ -30,6 +30,7 @@
|
|||
"@lobechat/builtin-tool-skill-store": "workspace:*",
|
||||
"@lobechat/builtin-tool-skills": "workspace:*",
|
||||
"@lobechat/builtin-tool-tools": "workspace:*",
|
||||
"@lobechat/builtin-tool-topic-reference": "workspace:*",
|
||||
"@lobechat/builtin-tool-web-browsing": "workspace:*",
|
||||
"@lobechat/const": "workspace:*"
|
||||
},
|
||||
|
|
|
|||
|
|
@ -13,6 +13,7 @@ import { PageAgentManifest } from '@lobechat/builtin-tool-page-agent';
|
|||
import { SkillStoreManifest } from '@lobechat/builtin-tool-skill-store';
|
||||
import { SkillsManifest } from '@lobechat/builtin-tool-skills';
|
||||
import { LobeToolsManifest } from '@lobechat/builtin-tool-tools';
|
||||
import { TopicReferenceManifest } from '@lobechat/builtin-tool-topic-reference';
|
||||
import { WebBrowsingManifest } from '@lobechat/builtin-tool-web-browsing';
|
||||
|
||||
export const builtinToolIdentifiers: string[] = [
|
||||
|
|
@ -30,6 +31,7 @@ export const builtinToolIdentifiers: string[] = [
|
|||
GTDManifest.identifier,
|
||||
MemoryManifest.identifier,
|
||||
NotebookManifest.identifier,
|
||||
TopicReferenceManifest.identifier,
|
||||
LobeToolsManifest.identifier,
|
||||
SkillStoreManifest.identifier,
|
||||
];
|
||||
|
|
|
|||
|
|
@ -14,6 +14,7 @@ import { RemoteDeviceManifest } from '@lobechat/builtin-tool-remote-device';
|
|||
import { SkillStoreManifest } from '@lobechat/builtin-tool-skill-store';
|
||||
import { SkillsManifest } from '@lobechat/builtin-tool-skills';
|
||||
import { LobeToolsManifest } from '@lobechat/builtin-tool-tools';
|
||||
import { TopicReferenceManifest } from '@lobechat/builtin-tool-topic-reference';
|
||||
import { WebBrowsingManifest } from '@lobechat/builtin-tool-web-browsing';
|
||||
import { isDesktop } from '@lobechat/const';
|
||||
import { type LobeBuiltinTool } from '@lobechat/types';
|
||||
|
|
@ -31,6 +32,7 @@ export const defaultToolIds = [
|
|||
MemoryManifest.identifier,
|
||||
LocalSystemManifest.identifier,
|
||||
CloudSandboxManifest.identifier,
|
||||
TopicReferenceManifest.identifier,
|
||||
];
|
||||
|
||||
/**
|
||||
|
|
@ -147,4 +149,11 @@ export const builtinTools: LobeBuiltinTool[] = [
|
|||
manifest: RemoteDeviceManifest,
|
||||
type: 'builtin',
|
||||
},
|
||||
{
|
||||
discoverable: false,
|
||||
hidden: true,
|
||||
identifier: TopicReferenceManifest.identifier,
|
||||
manifest: TopicReferenceManifest,
|
||||
type: 'builtin',
|
||||
},
|
||||
];
|
||||
|
|
|
|||
49
packages/context-engine/src/__tests__/metadata.types.test.ts
Normal file
49
packages/context-engine/src/__tests__/metadata.types.test.ts
Normal file
|
|
@ -0,0 +1,49 @@
|
|||
import { describe, expectTypeOf, it } from 'vitest';
|
||||
|
||||
import type { PipelineContext, PipelineContextMetadata } from '../types';
|
||||
|
||||
declare module '../types' {
|
||||
interface PipelineContextMetadataOverrides {
|
||||
customMetadata?: {
|
||||
enabled: boolean;
|
||||
source: 'test';
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
describe('PipelineContextMetadata', () => {
|
||||
it('should keep built-in metadata fields strongly typed', () => {
|
||||
expectTypeOf<PipelineContextMetadata['historySummary']>().toEqualTypeOf<
|
||||
| {
|
||||
formattedLength: number;
|
||||
injected: boolean;
|
||||
originalLength: number;
|
||||
}
|
||||
| undefined
|
||||
>();
|
||||
expectTypeOf<PipelineContextMetadata['toolMessageReorder']>().toEqualTypeOf<
|
||||
| {
|
||||
originalCount: number;
|
||||
removedInvalidTools: number;
|
||||
reorderedCount: number;
|
||||
}
|
||||
| undefined
|
||||
>();
|
||||
});
|
||||
|
||||
it('should support template literal injected-count metadata keys', () => {
|
||||
expectTypeOf<PipelineContextMetadata['CustomProviderInjectedCount']>().toEqualTypeOf<
|
||||
number | undefined
|
||||
>();
|
||||
});
|
||||
|
||||
it('should expose consumer-side metadata extensions through pipeline context', () => {
|
||||
expectTypeOf<PipelineContext['metadata']['customMetadata']>().toEqualTypeOf<
|
||||
| {
|
||||
enabled: boolean;
|
||||
source: 'test';
|
||||
}
|
||||
| undefined
|
||||
>();
|
||||
});
|
||||
});
|
||||
|
|
@ -176,9 +176,9 @@ describe('ContextEngine', () => {
|
|||
|
||||
expect(result.isAborted).toBe(false);
|
||||
expect(result.messages).toEqual(input.messages);
|
||||
expect(result.metadata.p1).toBe(true);
|
||||
expect(result.metadata.p2).toBe(true);
|
||||
expect(result.metadata.p3).toBe(true);
|
||||
expect((result.metadata as any).p1).toBe(true);
|
||||
expect((result.metadata as any).p2).toBe(true);
|
||||
expect((result.metadata as any).p3).toBe(true);
|
||||
expect(result.stats.processedCount).toBe(3);
|
||||
});
|
||||
|
||||
|
|
|
|||
|
|
@ -185,14 +185,14 @@ describe('BaseProcessor', () => {
|
|||
it('should preserve all context properties', async () => {
|
||||
const processor = new TestProcessor();
|
||||
const context = createContext([{ content: 'test', role: 'user' }]);
|
||||
context.metadata.customField = 'customValue';
|
||||
(context.metadata as any).customField = 'customValue';
|
||||
|
||||
const result = await processor.process(context);
|
||||
|
||||
expect(result.isAborted).toBe(context.isAborted);
|
||||
expect(result.metadata.model).toBe(context.metadata.model);
|
||||
expect(result.metadata.maxTokens).toBe(context.metadata.maxTokens);
|
||||
expect(result.metadata.customField).toBe('customValue');
|
||||
expect((result.metadata as any).customField).toBe('customValue');
|
||||
});
|
||||
});
|
||||
|
||||
|
|
|
|||
|
|
@ -35,6 +35,7 @@ import {
|
|||
KnowledgeInjector,
|
||||
PageEditorContextInjector,
|
||||
PageSelectionsInjector,
|
||||
SelectedSkillInjector,
|
||||
SkillContextProvider,
|
||||
SystemDateProvider,
|
||||
SystemRoleInjector,
|
||||
|
|
@ -129,6 +130,7 @@ export class MessagesEngine {
|
|||
formatHistorySummary,
|
||||
knowledge,
|
||||
skillsConfig,
|
||||
selectedSkills,
|
||||
toolDiscoveryConfig,
|
||||
toolsConfig,
|
||||
capabilities,
|
||||
|
|
@ -157,6 +159,8 @@ export class MessagesEngine {
|
|||
const isGroupContextEnabled =
|
||||
isAgentGroupEnabled || !!agentGroup?.currentAgentId || !!agentGroup?.members;
|
||||
const isUserMemoryEnabled = userMemory?.enabled && userMemory?.memories;
|
||||
const hasSelectedSkills = (selectedSkills?.length ?? 0) > 0;
|
||||
|
||||
// Page editor is enabled if either direct pageContentContext or initialContext.pageEditor is provided
|
||||
const isPageEditorEnabled = !!pageContentContext || !!initialContext?.pageEditor;
|
||||
// GTD is enabled if gtd.enabled is true and either plan or todos is provided
|
||||
|
|
@ -283,16 +287,19 @@ export class MessagesEngine {
|
|||
historySummary,
|
||||
}),
|
||||
|
||||
// 14. Page Selections injection (inject user-selected text into each user message that has them)
|
||||
// 14. Selected skill injection (ephemeral user-selected slash skills for this request)
|
||||
...(hasSelectedSkills ? [new SelectedSkillInjector({ selectedSkills })] : []),
|
||||
|
||||
// 15. Page Selections injection (inject user-selected text into each user message that has them)
|
||||
new PageSelectionsInjector({ enabled: isPageEditorEnabled }),
|
||||
|
||||
// 15. Page Editor context injection (inject current page content to last user message)
|
||||
// 16. Page Editor context injection (inject current page content to last user message)
|
||||
new PageEditorContextInjector({
|
||||
enabled: isPageEditorEnabled,
|
||||
// Use direct pageContentContext if provided (server-side), otherwise build from initialContext + stepContext (frontend)
|
||||
pageContentContext: pageContentContext
|
||||
? pageContentContext
|
||||
: initialContext?.pageEditor
|
||||
pageContentContext:
|
||||
pageContentContext ??
|
||||
(initialContext?.pageEditor
|
||||
? {
|
||||
markdown: initialContext.pageEditor.markdown,
|
||||
metadata: {
|
||||
|
|
@ -303,10 +310,10 @@ export class MessagesEngine {
|
|||
// Use latest XML from stepContext if available, otherwise fallback to initial XML
|
||||
xml: stepContext?.stepPageEditor?.xml || initialContext.pageEditor.xml,
|
||||
}
|
||||
: undefined,
|
||||
: undefined),
|
||||
}),
|
||||
|
||||
// 16. GTD Todo injection (conditionally added, at end of last user message)
|
||||
// 17. GTD Todo injection (conditionally added, at end of last user message)
|
||||
...(isGTDTodoEnabled ? [new GTDTodoInjector({ enabled: true, todos: gtd.todos })] : []),
|
||||
|
||||
// =============================================
|
||||
|
|
|
|||
|
|
@ -1,6 +1,10 @@
|
|||
/* eslint-disable perfectionist/sort-interfaces */
|
||||
import type { FileContent, KnowledgeBaseInfo, PageContentContext } from '@lobechat/prompts';
|
||||
import type { RuntimeInitialContext, RuntimeStepContext } from '@lobechat/types';
|
||||
import type {
|
||||
RuntimeInitialContext,
|
||||
RuntimeSelectedSkill,
|
||||
RuntimeStepContext,
|
||||
} from '@lobechat/types';
|
||||
|
||||
import type { OpenAIChatMessage, UIChatMessage } from '@/types/index';
|
||||
|
||||
|
|
@ -15,6 +19,7 @@ import type { GTDPlan } from '../../providers/GTDPlanInjector';
|
|||
import type { GTDTodoList } from '../../providers/GTDTodoInjector';
|
||||
import type { SkillMeta } from '../../providers/SkillContextProvider';
|
||||
import type { ToolDiscoveryMeta } from '../../providers/ToolDiscoveryProvider';
|
||||
import type { PipelineContextMetadata } from '../../types';
|
||||
import type { LobeToolManifest } from '../tools/types';
|
||||
|
||||
/**
|
||||
|
|
@ -239,6 +244,8 @@ export interface MessagesEngineParams {
|
|||
// ========== Skills ==========
|
||||
/** Skills configuration */
|
||||
skillsConfig?: SkillsConfig;
|
||||
/** Skills explicitly selected by the user for the current request */
|
||||
selectedSkills?: RuntimeSelectedSkill[];
|
||||
|
||||
// ========== Tool Discovery ==========
|
||||
/** Tool Discovery configuration (available tools for dynamic activation) */
|
||||
|
|
@ -299,7 +306,7 @@ export interface MessagesEngineResult {
|
|||
/** Processed messages in OpenAI format */
|
||||
messages: OpenAIChatMessage[];
|
||||
/** Processing metadata */
|
||||
metadata: Record<string, any>;
|
||||
metadata: PipelineContextMetadata;
|
||||
/** Processing statistics */
|
||||
stats: {
|
||||
/** Number of processors executed */
|
||||
|
|
|
|||
|
|
@ -3,6 +3,15 @@ import debug from 'debug';
|
|||
import { BaseProcessor } from '../base/BaseProcessor';
|
||||
import type { PipelineContext, ProcessorOptions } from '../types';
|
||||
|
||||
declare module '../types' {
|
||||
interface PipelineContextMetadataOverrides {
|
||||
agentCouncilAssistantMessagesCreated?: number;
|
||||
agentCouncilFlattenProcessed?: number;
|
||||
agentCouncilMessagesFlattened?: number;
|
||||
agentCouncilToolMessagesCreated?: number;
|
||||
}
|
||||
}
|
||||
|
||||
const log = debug('context-engine:processor:AgentCouncilFlattenProcessor');
|
||||
|
||||
/**
|
||||
|
|
|
|||
|
|
@ -3,6 +3,12 @@ import debug from 'debug';
|
|||
import { BaseProcessor } from '../base/BaseProcessor';
|
||||
import type { Message, PipelineContext, ProcessorOptions } from '../types';
|
||||
|
||||
declare module '../types' {
|
||||
interface PipelineContextMetadataOverrides {
|
||||
compressedGroupRoleTransformProcessed?: number;
|
||||
}
|
||||
}
|
||||
|
||||
const log = debug('context-engine:processor:CompressedGroupRoleTransformProcessor');
|
||||
|
||||
/**
|
||||
|
|
|
|||
|
|
@ -3,6 +3,15 @@ import debug from 'debug';
|
|||
import { BaseProcessor } from '../base/BaseProcessor';
|
||||
import type { PipelineContext, ProcessorOptions } from '../types';
|
||||
|
||||
declare module '../types' {
|
||||
interface PipelineContextMetadataOverrides {
|
||||
assistantMessagesCreated?: number;
|
||||
groupMessagesFlattened?: number;
|
||||
groupMessagesFlattenProcessed?: number;
|
||||
toolMessagesCreated?: number;
|
||||
}
|
||||
}
|
||||
|
||||
const log = debug('context-engine:processor:GroupMessageFlattenProcessor');
|
||||
|
||||
/**
|
||||
|
|
|
|||
|
|
@ -3,6 +3,16 @@ import debug from 'debug';
|
|||
import { BaseProcessor } from '../base/BaseProcessor';
|
||||
import type { Message, PipelineContext, ProcessorOptions } from '../types';
|
||||
|
||||
declare module '../types' {
|
||||
interface PipelineContextMetadataOverrides {
|
||||
orchestrationFilterProcessed?: {
|
||||
assistantFiltered: number;
|
||||
filteredCount: number;
|
||||
toolFiltered: number;
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
const log = debug('context-engine:processor:GroupOrchestrationFilterProcessor');
|
||||
|
||||
/**
|
||||
|
|
|
|||
|
|
@ -3,6 +3,15 @@ import debug from 'debug';
|
|||
import { BaseProcessor } from '../base/BaseProcessor';
|
||||
import type { Message, PipelineContext, ProcessorOptions } from '../types';
|
||||
|
||||
declare module '../types' {
|
||||
interface PipelineContextMetadataOverrides {
|
||||
groupRoleTransformProcessed?: {
|
||||
assistantTransformed: number;
|
||||
toolTransformed: number;
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
const log = debug('context-engine:processor:GroupRoleTransformProcessor');
|
||||
|
||||
/**
|
||||
|
|
|
|||
|
|
@ -3,6 +3,13 @@ import debug from 'debug';
|
|||
import { BaseProcessor } from '../base/BaseProcessor';
|
||||
import type { PipelineContext, ProcessorOptions } from '../types';
|
||||
|
||||
declare module '../types' {
|
||||
interface PipelineContextMetadataOverrides {
|
||||
finalMessageCount?: number;
|
||||
historyTruncated?: number;
|
||||
}
|
||||
}
|
||||
|
||||
const log = debug('context-engine:processor:HistoryTruncateProcessor');
|
||||
|
||||
export interface HistoryTruncateConfig {
|
||||
|
|
@ -261,7 +268,11 @@ export const getSlicedMessages = (
|
|||
|
||||
// Step 2: Walk backwards through messages to select last N groups
|
||||
const selectedGroupIndices = new Set<number>();
|
||||
for (let i = messages.length - 1; i >= 0 && selectedGroupIndices.size < options.historyCount; i--) {
|
||||
for (
|
||||
let i = messages.length - 1;
|
||||
i >= 0 && selectedGroupIndices.size < options.historyCount;
|
||||
i--
|
||||
) {
|
||||
const msg = messages[i] as MinimalMessage;
|
||||
const groupIdx = messageToGroup.get(msg.id);
|
||||
if (groupIdx !== undefined) {
|
||||
|
|
|
|||
|
|
@ -4,6 +4,12 @@ import { template } from 'es-toolkit/compat';
|
|||
import { BaseProcessor } from '../base/BaseProcessor';
|
||||
import type { PipelineContext, ProcessorOptions } from '../types';
|
||||
|
||||
declare module '../types' {
|
||||
interface PipelineContextMetadataOverrides {
|
||||
inputTemplateProcessed?: number;
|
||||
}
|
||||
}
|
||||
|
||||
const log = debug('context-engine:processor:InputTemplateProcessor');
|
||||
|
||||
export interface InputTemplateConfig {
|
||||
|
|
@ -39,7 +45,7 @@ export class InputTemplateProcessor extends BaseProcessor {
|
|||
try {
|
||||
// Compile the template
|
||||
const compiler = template(this.config.inputTemplate, {
|
||||
interpolate: /{{\s*(text)\s*}}/g,
|
||||
interpolate: /\{\{\s*(text)\s*\}\}/g,
|
||||
});
|
||||
|
||||
log(`Applying input template: ${this.config.inputTemplate}`);
|
||||
|
|
|
|||
|
|
@ -3,6 +3,15 @@ import debug from 'debug';
|
|||
import { BaseProcessor } from '../base/BaseProcessor';
|
||||
import type { PipelineContext, ProcessorOptions } from '../types';
|
||||
|
||||
declare module '../types' {
|
||||
interface PipelineContextMetadataOverrides {
|
||||
messageCleanup?: {
|
||||
cleanedCount: number;
|
||||
totalMessages: number;
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
const log = debug('context-engine:processor:MessageCleanupProcessor');
|
||||
|
||||
/**
|
||||
|
|
|
|||
|
|
@ -8,6 +8,14 @@ import debug from 'debug';
|
|||
import { BaseProcessor } from '../base/BaseProcessor';
|
||||
import type { PipelineContext, ProcessorOptions } from '../types';
|
||||
|
||||
declare module '../types' {
|
||||
interface PipelineContextMetadataOverrides {
|
||||
assistantMessagesProcessed?: number;
|
||||
messageContentProcessed?: number;
|
||||
userMessagesProcessed?: number;
|
||||
}
|
||||
}
|
||||
|
||||
const log = debug('context-engine:processor:MessageContentProcessor');
|
||||
|
||||
/**
|
||||
|
|
|
|||
|
|
@ -3,9 +3,23 @@ import debug from 'debug';
|
|||
import { BaseProcessor } from '../base/BaseProcessor';
|
||||
import type { PipelineContext, ProcessorOptions } from '../types';
|
||||
|
||||
declare module '../types' {
|
||||
interface PipelineContextMetadataOverrides {
|
||||
placeholderVariablesProcessed?: number;
|
||||
}
|
||||
}
|
||||
|
||||
const log = debug('context-engine:processor:PlaceholderVariablesProcessor');
|
||||
|
||||
const placeholderVariablesRegex = /{{(.*?)}}/g;
|
||||
const PLACEHOLDER_START = '{{';
|
||||
const PLACEHOLDER_END = '}}';
|
||||
|
||||
interface PlaceholderToken {
|
||||
end: number;
|
||||
key: string;
|
||||
raw: string;
|
||||
start: number;
|
||||
}
|
||||
|
||||
export type PlaceholderValue = unknown | (() => unknown);
|
||||
export type PlaceholderValueMap = Record<string, PlaceholderValue>;
|
||||
|
|
@ -63,9 +77,56 @@ export interface PlaceholderVariablesConfig {
|
|||
* @param text String containing template variables
|
||||
* @returns Array of variable names, e.g. ['date', 'nickname']
|
||||
*/
|
||||
const extractPlaceholderVariables = (text: string): string[] => {
|
||||
const matches = [...text.matchAll(placeholderVariablesRegex)];
|
||||
return matches.map((m) => m[1].trim());
|
||||
const extractPlaceholderTokens = (text: string): PlaceholderToken[] => {
|
||||
const tokens: PlaceholderToken[] = [];
|
||||
let searchIndex = 0;
|
||||
|
||||
while (searchIndex < text.length) {
|
||||
const start = text.indexOf(PLACEHOLDER_START, searchIndex);
|
||||
if (start === -1) break;
|
||||
|
||||
const end = text.indexOf(PLACEHOLDER_END, start + PLACEHOLDER_START.length);
|
||||
if (end === -1) break;
|
||||
|
||||
const tokenEnd = end + PLACEHOLDER_END.length;
|
||||
tokens.push({
|
||||
end: tokenEnd,
|
||||
key: text.slice(start + PLACEHOLDER_START.length, end).trim(),
|
||||
raw: text.slice(start, tokenEnd),
|
||||
start,
|
||||
});
|
||||
|
||||
searchIndex = tokenEnd;
|
||||
}
|
||||
|
||||
return tokens;
|
||||
};
|
||||
|
||||
const replaceAvailablePlaceholders = (
|
||||
text: string,
|
||||
placeholders: PlaceholderToken[],
|
||||
availableVariables: Record<string, string>,
|
||||
): string => {
|
||||
const output: string[] = [];
|
||||
let cursor = 0;
|
||||
let changed = false;
|
||||
|
||||
for (const placeholder of placeholders) {
|
||||
output.push(text.slice(cursor, placeholder.start));
|
||||
|
||||
if (Object.hasOwn(availableVariables, placeholder.key)) {
|
||||
output.push(availableVariables[placeholder.key]);
|
||||
changed = true;
|
||||
} else {
|
||||
output.push(placeholder.raw);
|
||||
}
|
||||
|
||||
cursor = placeholder.end;
|
||||
}
|
||||
|
||||
output.push(text.slice(cursor));
|
||||
|
||||
return changed ? output.join('') : text;
|
||||
};
|
||||
|
||||
/**
|
||||
|
|
@ -85,14 +146,17 @@ export const parsePlaceholderVariables = (
|
|||
// Recursive parsing to handle cases like {{text}} containing additional preset variables
|
||||
for (let i = 0; i < depth; i++) {
|
||||
try {
|
||||
const extractedVariables = extractPlaceholderVariables(result);
|
||||
const placeholders = extractPlaceholderTokens(result);
|
||||
const extractedVariables = placeholders.map((placeholder) => placeholder.key);
|
||||
|
||||
if (placeholders.length === 0) break;
|
||||
|
||||
log('Extracted variables from text: %o', extractedVariables);
|
||||
log('Available generator keys: %o', Object.keys(variableGenerators));
|
||||
|
||||
// Debug: check if text contains {{username}} pattern
|
||||
if (result.includes('username') || result.includes('{{')) {
|
||||
const matches = result.match(/{{[^}]*}}/g);
|
||||
const matches = placeholders.map((placeholder) => placeholder.raw);
|
||||
log('All {{...}} patterns found in text: %o', matches);
|
||||
}
|
||||
|
||||
|
|
@ -112,16 +176,7 @@ export const parsePlaceholderVariables = (
|
|||
// Only perform replacement when there are available variables
|
||||
if (Object.keys(availableVariables).length === 0) break;
|
||||
|
||||
// Replace variables one by one to avoid es-toolkit template's error handling for undefined variables
|
||||
let tempResult = result;
|
||||
for (const [key, value] of Object.entries(availableVariables)) {
|
||||
const regex = new RegExp(
|
||||
`{{\\s*${key.replaceAll(/[$()*+.?[\\\]^{|}]/g, '\\$&')}\\s*}}`,
|
||||
'g',
|
||||
);
|
||||
// @ts-ignore
|
||||
tempResult = tempResult.replace(regex, value);
|
||||
}
|
||||
const tempResult = replaceAvailablePlaceholders(result, placeholders, availableVariables);
|
||||
|
||||
if (tempResult === result) break;
|
||||
result = tempResult;
|
||||
|
|
|
|||
|
|
@ -4,6 +4,12 @@ import debug from 'debug';
|
|||
import { BaseProcessor } from '../base/BaseProcessor';
|
||||
import type { PipelineContext, ProcessorOptions } from '../types';
|
||||
|
||||
declare module '../types' {
|
||||
interface PipelineContextMetadataOverrides {
|
||||
reactionFeedbackProcessed?: number;
|
||||
}
|
||||
}
|
||||
|
||||
const log = debug('context-engine:processor:ReactionFeedbackProcessor');
|
||||
|
||||
export interface ReactionFeedbackConfig {
|
||||
|
|
|
|||
|
|
@ -3,6 +3,12 @@ import debug from 'debug';
|
|||
import { BaseProcessor } from '../base/BaseProcessor';
|
||||
import type { Message, PipelineContext, ProcessorOptions } from '../types';
|
||||
|
||||
declare module '../types' {
|
||||
interface PipelineContextMetadataOverrides {
|
||||
supervisorRoleRestoreProcessed?: number;
|
||||
}
|
||||
}
|
||||
|
||||
const log = debug('context-engine:processor:SupervisorRoleRestoreProcessor');
|
||||
|
||||
/**
|
||||
|
|
|
|||
|
|
@ -3,6 +3,12 @@ import debug from 'debug';
|
|||
import { BaseProcessor } from '../base/BaseProcessor';
|
||||
import type { PipelineContext, ProcessorOptions } from '../types';
|
||||
|
||||
declare module '../types' {
|
||||
interface PipelineContextMetadataOverrides {
|
||||
taskMessagesProcessed?: number;
|
||||
}
|
||||
}
|
||||
|
||||
const log = debug('context-engine:processor:TaskMessageProcessor');
|
||||
|
||||
/**
|
||||
|
|
|
|||
|
|
@ -3,6 +3,14 @@ import debug from 'debug';
|
|||
import { BaseProcessor } from '../base/BaseProcessor';
|
||||
import type { PipelineContext, ProcessorOptions } from '../types';
|
||||
|
||||
declare module '../types' {
|
||||
interface PipelineContextMetadataOverrides {
|
||||
taskMessagesCreated?: number;
|
||||
tasksFlattenProcessed?: number;
|
||||
tasksMessagesFlattened?: number;
|
||||
}
|
||||
}
|
||||
|
||||
const log = debug('context-engine:processor:TasksFlattenProcessor');
|
||||
|
||||
/**
|
||||
|
|
|
|||
|
|
@ -3,6 +3,15 @@ import debug from 'debug';
|
|||
import { BaseProcessor } from '../base/BaseProcessor';
|
||||
import type { MessageToolCall, PipelineContext, ProcessorOptions } from '../types';
|
||||
|
||||
declare module '../types' {
|
||||
interface PipelineContextMetadataOverrides {
|
||||
supportTools?: boolean;
|
||||
toolCallProcessed?: number;
|
||||
toolCallsConverted?: number;
|
||||
toolMessagesConverted?: number;
|
||||
}
|
||||
}
|
||||
|
||||
const log = debug('context-engine:processor:ToolCallProcessor');
|
||||
|
||||
export interface ToolCallConfig {
|
||||
|
|
|
|||
|
|
@ -3,6 +3,16 @@ import debug from 'debug';
|
|||
import { BaseProcessor } from '../base/BaseProcessor';
|
||||
import type { PipelineContext, ProcessorOptions } from '../types';
|
||||
|
||||
declare module '../types' {
|
||||
interface PipelineContextMetadataOverrides {
|
||||
toolMessageReorder?: {
|
||||
originalCount: number;
|
||||
removedInvalidTools: number;
|
||||
reorderedCount: number;
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
const log = debug('context-engine:processor:ToolMessageReorder');
|
||||
|
||||
/**
|
||||
|
|
|
|||
|
|
@ -301,8 +301,8 @@ describe('MessageCleanupProcessor', () => {
|
|||
const result = await processor.process(context);
|
||||
|
||||
// Both messages are "cleaned" because comparison is done by reference
|
||||
expect(result.metadata.messageCleanup.cleanedCount).toBe(2);
|
||||
expect(result.metadata.messageCleanup.totalMessages).toBe(2);
|
||||
expect(result.metadata.messageCleanup!.cleanedCount).toBe(2);
|
||||
expect(result.metadata.messageCleanup!.totalMessages).toBe(2);
|
||||
});
|
||||
|
||||
it('should handle empty messages array', async () => {
|
||||
|
|
|
|||
|
|
@ -53,6 +53,18 @@ describe('PlaceholderVariablesProcessor', () => {
|
|||
const result = parsePlaceholderVariables(text, mockVariableGenerators);
|
||||
expect(result).toBe('No placeholders here');
|
||||
});
|
||||
|
||||
it('should replace placeholders with surrounding whitespace', () => {
|
||||
const text = 'Hello {{ username }}, today is {{ date }}';
|
||||
const result = parsePlaceholderVariables(text, mockVariableGenerators);
|
||||
expect(result).toBe('Hello TestUser, today is 2023-12-25');
|
||||
});
|
||||
|
||||
it('should handle malformed repeated opening braces without backtracking issues', () => {
|
||||
const text = '{{{{'.repeat(2000);
|
||||
const result = parsePlaceholderVariables(text, mockVariableGenerators);
|
||||
expect(result).toBe(text);
|
||||
});
|
||||
});
|
||||
|
||||
describe('parsePlaceholderVariablesMessages', () => {
|
||||
|
|
|
|||
|
|
@ -1,21 +1,16 @@
|
|||
import { escapeXml } from '@lobechat/prompts';
|
||||
import debug from 'debug';
|
||||
|
||||
import { BaseProvider } from '../base/BaseProvider';
|
||||
import type { PipelineContext, ProcessorOptions } from '../types';
|
||||
|
||||
const log = debug('context-engine:provider:AgentBuilderContextInjector');
|
||||
declare module '../types' {
|
||||
interface PipelineContextMetadataOverrides {
|
||||
agentBuilderContextInjected?: boolean;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Escape XML special characters
|
||||
*/
|
||||
const escapeXml = (str: string): string => {
|
||||
return str
|
||||
.replaceAll('&', '&')
|
||||
.replaceAll('<', '<')
|
||||
.replaceAll('>', '>')
|
||||
.replaceAll('"', '"')
|
||||
.replaceAll("'", ''');
|
||||
};
|
||||
const log = debug('context-engine:provider:AgentBuilderContextInjector');
|
||||
|
||||
/**
|
||||
* Official tool item for Agent Builder context
|
||||
|
|
|
|||
|
|
@ -1,21 +1,17 @@
|
|||
import { escapeXml } from '@lobechat/prompts';
|
||||
import type { RuntimeMentionedAgent } from '@lobechat/types';
|
||||
import debug from 'debug';
|
||||
|
||||
import { BaseProvider } from '../base/BaseProvider';
|
||||
import type { PipelineContext, ProcessorOptions } from '../types';
|
||||
|
||||
const log = debug('context-engine:provider:AgentManagementContextInjector');
|
||||
declare module '../types' {
|
||||
interface PipelineContextMetadataOverrides {
|
||||
agentManagementContextInjected?: boolean;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Escape XML special characters
|
||||
*/
|
||||
const escapeXml = (str: string): string => {
|
||||
return str
|
||||
.replaceAll('&', '&')
|
||||
.replaceAll('<', '<')
|
||||
.replaceAll('>', '>')
|
||||
.replaceAll('"', '"')
|
||||
.replaceAll("'", ''');
|
||||
};
|
||||
const log = debug('context-engine:provider:AgentManagementContextInjector');
|
||||
|
||||
/**
|
||||
* Available model info for Agent Management context
|
||||
|
|
@ -70,6 +66,8 @@ export interface AgentManagementContext {
|
|||
availablePlugins?: AvailablePluginInfo[];
|
||||
/** Available providers and models */
|
||||
availableProviders?: AvailableProviderInfo[];
|
||||
/** Agents @mentioned by the user — supervisor should delegate to these via callAgent */
|
||||
mentionedAgents?: RuntimeMentionedAgent[];
|
||||
}
|
||||
|
||||
export interface AgentManagementContextInjectorConfig {
|
||||
|
|
@ -166,6 +164,21 @@ ${parts.join('\n')}
|
|||
</agent_management_context>`;
|
||||
};
|
||||
|
||||
/**
|
||||
* Format mentioned agents as delegation context for injection after the user message.
|
||||
* Instructs the AI to delegate to the mentioned agent(s) via callAgent.
|
||||
*/
|
||||
const formatMentionedAgentsContext = (mentionedAgents: RuntimeMentionedAgent[]): string => {
|
||||
const agentsXml = mentionedAgents
|
||||
.map((a) => ` <agent id="${escapeXml(a.id)}" name="${escapeXml(a.name)}" />`)
|
||||
.join('\n');
|
||||
|
||||
return `<mentioned_agents>
|
||||
<instruction>The user has @mentioned the following agent(s) in their message. You MUST use the callAgent tool to delegate the user's request to the mentioned agent. Do NOT attempt to handle the request yourself — call the agent and let them respond.</instruction>
|
||||
${agentsXml}
|
||||
</mentioned_agents>`;
|
||||
};
|
||||
|
||||
/**
|
||||
* Agent Management Context Injector
|
||||
* Responsible for injecting available models and plugins when Agent Management tool is enabled
|
||||
|
|
@ -195,41 +208,70 @@ export class AgentManagementContextInjector extends BaseProvider {
|
|||
return this.markAsExecuted(clonedContext);
|
||||
}
|
||||
|
||||
// Format context
|
||||
const hasMentionedAgents =
|
||||
this.config.context.mentionedAgents && this.config.context.mentionedAgents.length > 0;
|
||||
|
||||
// Format context (excluding mentionedAgents — those are injected separately after the last user message)
|
||||
const contextWithoutMentions: AgentManagementContext = hasMentionedAgents
|
||||
? {
|
||||
availablePlugins: this.config.context.availablePlugins,
|
||||
availableProviders: this.config.context.availableProviders,
|
||||
}
|
||||
: this.config.context;
|
||||
|
||||
const formatFn = this.config.formatContext || defaultFormatContext;
|
||||
const formattedContent = formatFn(this.config.context);
|
||||
const formattedContent = formatFn(contextWithoutMentions);
|
||||
|
||||
// Skip if no content to inject
|
||||
if (!formattedContent) {
|
||||
log('No content to inject after formatting');
|
||||
return this.markAsExecuted(clonedContext);
|
||||
// Inject agent-management context (providers/plugins) before the first user message
|
||||
if (formattedContent) {
|
||||
const firstUserIndex = clonedContext.messages.findIndex((msg) => msg.role === 'user');
|
||||
|
||||
if (firstUserIndex !== -1) {
|
||||
const contextMessage = {
|
||||
content: formattedContent,
|
||||
createdAt: Date.now(),
|
||||
id: `agent-management-context-${Date.now()}`,
|
||||
meta: { injectType: 'agent-management-context', systemInjection: true },
|
||||
role: 'user' as const,
|
||||
updatedAt: Date.now(),
|
||||
};
|
||||
|
||||
clonedContext.messages.splice(firstUserIndex, 0, contextMessage);
|
||||
clonedContext.metadata.agentManagementContextInjected = true;
|
||||
log('Agent Management context injected before first user message');
|
||||
}
|
||||
}
|
||||
|
||||
// Find the first user message index
|
||||
const firstUserIndex = clonedContext.messages.findIndex((msg) => msg.role === 'user');
|
||||
// Inject mentionedAgents delegation context AFTER the last user message
|
||||
// This position makes the delegation instruction most salient to the model
|
||||
if (hasMentionedAgents) {
|
||||
const mentionedContent = formatMentionedAgentsContext(this.config.context.mentionedAgents!);
|
||||
|
||||
if (firstUserIndex === -1) {
|
||||
log('No user messages found, skipping injection');
|
||||
return this.markAsExecuted(clonedContext);
|
||||
// Find the last user message index
|
||||
let lastUserIndex = -1;
|
||||
for (let i = clonedContext.messages.length - 1; i >= 0; i--) {
|
||||
if (clonedContext.messages[i].role === 'user') {
|
||||
lastUserIndex = i;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (lastUserIndex !== -1) {
|
||||
const mentionMessage = {
|
||||
content: mentionedContent,
|
||||
createdAt: Date.now(),
|
||||
id: `agent-mention-delegation-${Date.now()}`,
|
||||
meta: { injectType: 'agent-mention-delegation', systemInjection: true },
|
||||
role: 'user' as const,
|
||||
updatedAt: Date.now(),
|
||||
};
|
||||
|
||||
// Insert after the last user message
|
||||
clonedContext.messages.splice(lastUserIndex + 1, 0, mentionMessage);
|
||||
log('Mentioned agents delegation context injected after last user message');
|
||||
}
|
||||
}
|
||||
|
||||
// Insert a new user message with context before the first user message
|
||||
const contextMessage = {
|
||||
content: formattedContent,
|
||||
createdAt: Date.now(),
|
||||
id: `agent-management-context-${Date.now()}`,
|
||||
meta: { injectType: 'agent-management-context', systemInjection: true },
|
||||
role: 'user' as const,
|
||||
updatedAt: Date.now(),
|
||||
};
|
||||
|
||||
clonedContext.messages.splice(firstUserIndex, 0, contextMessage);
|
||||
|
||||
// Update metadata
|
||||
clonedContext.metadata.agentManagementContextInjected = true;
|
||||
|
||||
log('Agent Management context injected as new user message');
|
||||
|
||||
return this.markAsExecuted(clonedContext);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -3,6 +3,12 @@ import debug from 'debug';
|
|||
import { BaseProvider } from '../base/BaseProvider';
|
||||
import type { PipelineContext, ProcessorOptions } from '../types';
|
||||
|
||||
declare module '../types' {
|
||||
interface PipelineContextMetadataOverrides {
|
||||
evalContextInjected?: boolean;
|
||||
}
|
||||
}
|
||||
|
||||
const log = debug('context-engine:provider:EvalContextSystemInjector');
|
||||
|
||||
export interface EvalContext {
|
||||
|
|
|
|||
|
|
@ -3,6 +3,12 @@ import debug from 'debug';
|
|||
import { BaseProvider } from '../base/BaseProvider';
|
||||
import type { PipelineContext, ProcessorOptions } from '../types';
|
||||
|
||||
declare module '../types' {
|
||||
interface PipelineContextMetadataOverrides {
|
||||
forceFinishInjected?: boolean;
|
||||
}
|
||||
}
|
||||
|
||||
const log = debug('context-engine:provider:ForceFinishSummaryInjector');
|
||||
|
||||
export interface ForceFinishSummaryInjectorConfig {
|
||||
|
|
|
|||
|
|
@ -3,6 +3,13 @@ import debug from 'debug';
|
|||
import { BaseFirstUserContentProvider } from '../base/BaseFirstUserContentProvider';
|
||||
import type { PipelineContext, ProcessorOptions } from '../types';
|
||||
|
||||
declare module '../types' {
|
||||
interface PipelineContextMetadataOverrides {
|
||||
gtdPlanId?: string;
|
||||
gtdPlanInjected?: boolean;
|
||||
}
|
||||
}
|
||||
|
||||
const log = debug('context-engine:provider:GTDPlanInjector');
|
||||
|
||||
/**
|
||||
|
|
|
|||
|
|
@ -3,6 +3,15 @@ import debug from 'debug';
|
|||
import { BaseLastUserContentProvider } from '../base/BaseLastUserContentProvider';
|
||||
import type { PipelineContext, ProcessorOptions } from '../types';
|
||||
|
||||
declare module '../types' {
|
||||
interface PipelineContextMetadataOverrides {
|
||||
gtdTodoCompletedCount?: number;
|
||||
gtdTodoCount?: number;
|
||||
gtdTodoInjected?: boolean;
|
||||
gtdTodoProcessingCount?: number;
|
||||
}
|
||||
}
|
||||
|
||||
const log = debug('context-engine:provider:GTDTodoInjector');
|
||||
|
||||
/** Status of a todo item */
|
||||
|
|
|
|||
|
|
@ -1,21 +1,16 @@
|
|||
import { escapeXml } from '@lobechat/prompts';
|
||||
import debug from 'debug';
|
||||
|
||||
import { BaseFirstUserContentProvider } from '../base/BaseFirstUserContentProvider';
|
||||
import type { PipelineContext, ProcessorOptions } from '../types';
|
||||
|
||||
const log = debug('context-engine:provider:GroupAgentBuilderContextInjector');
|
||||
declare module '../types' {
|
||||
interface PipelineContextMetadataOverrides {
|
||||
groupAgentBuilderContextInjected?: boolean;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Escape XML special characters
|
||||
*/
|
||||
const escapeXml = (str: string): string => {
|
||||
return str
|
||||
.replaceAll('&', '&')
|
||||
.replaceAll('<', '<')
|
||||
.replaceAll('>', '>')
|
||||
.replaceAll('"', '"')
|
||||
.replaceAll("'", ''');
|
||||
};
|
||||
const log = debug('context-engine:provider:GroupAgentBuilderContextInjector');
|
||||
|
||||
/**
|
||||
* Group member info for Group Agent Builder context
|
||||
|
|
|
|||
|
|
@ -5,6 +5,12 @@ import debug from 'debug';
|
|||
import { BaseFirstUserContentProvider } from '../base/BaseFirstUserContentProvider';
|
||||
import type { PipelineContext, ProcessorOptions } from '../types';
|
||||
|
||||
declare module '../types' {
|
||||
interface PipelineContextMetadataOverrides {
|
||||
groupContextInjected?: boolean;
|
||||
}
|
||||
}
|
||||
|
||||
const log = debug('context-engine:provider:GroupContextInjector');
|
||||
|
||||
/**
|
||||
|
|
|
|||
|
|
@ -3,6 +3,16 @@ import debug from 'debug';
|
|||
import { BaseProvider } from '../base/BaseProvider';
|
||||
import type { PipelineContext, ProcessorOptions } from '../types';
|
||||
|
||||
declare module '../types' {
|
||||
interface PipelineContextMetadataOverrides {
|
||||
historySummary?: {
|
||||
formattedLength: number;
|
||||
injected: boolean;
|
||||
originalLength: number;
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
const log = debug('context-engine:provider:HistorySummaryProvider');
|
||||
|
||||
/**
|
||||
|
|
|
|||
|
|
@ -5,6 +5,14 @@ import debug from 'debug';
|
|||
import { BaseFirstUserContentProvider } from '../base/BaseFirstUserContentProvider';
|
||||
import type { PipelineContext, ProcessorOptions } from '../types';
|
||||
|
||||
declare module '../types' {
|
||||
interface PipelineContextMetadataOverrides {
|
||||
filesCount?: number;
|
||||
knowledgeBasesCount?: number;
|
||||
knowledgeInjected?: boolean;
|
||||
}
|
||||
}
|
||||
|
||||
const log = debug('context-engine:provider:KnowledgeInjector');
|
||||
|
||||
export interface KnowledgeInjectorConfig {
|
||||
|
|
|
|||
|
|
@ -5,6 +5,12 @@ import debug from 'debug';
|
|||
import { BaseLastUserContentProvider } from '../base/BaseLastUserContentProvider';
|
||||
import type { PipelineContext, ProcessorOptions } from '../types';
|
||||
|
||||
declare module '../types' {
|
||||
interface PipelineContextMetadataOverrides {
|
||||
pageEditorContextInjected?: boolean;
|
||||
}
|
||||
}
|
||||
|
||||
const log = debug('context-engine:provider:PageEditorContextInjector');
|
||||
|
||||
export interface PageEditorContextInjectorConfig {
|
||||
|
|
|
|||
|
|
@ -0,0 +1,92 @@
|
|||
import { escapeXml } from '@lobechat/prompts';
|
||||
import type { RuntimeSelectedSkill } from '@lobechat/types';
|
||||
import debug from 'debug';
|
||||
|
||||
import { BaseLastUserContentProvider } from '../base/BaseLastUserContentProvider';
|
||||
import type { PipelineContext, ProcessorOptions } from '../types';
|
||||
|
||||
declare module '../types' {
|
||||
interface PipelineContextMetadataOverrides {
|
||||
selectedSkillContext?: {
|
||||
injected: boolean;
|
||||
skillsCount: number;
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
const log = debug('context-engine:provider:SelectedSkillInjector');
|
||||
|
||||
export interface SelectedSkillInjectorConfig {
|
||||
selectedSkills?: RuntimeSelectedSkill[];
|
||||
}
|
||||
|
||||
const formatSelectedSkills = (selectedSkills: RuntimeSelectedSkill[]): string | null => {
|
||||
if (selectedSkills.length === 0) return null;
|
||||
|
||||
const lines = [
|
||||
'The user explicitly selected these skills for this request. Prefer them when relevant.',
|
||||
'<selected_skills>',
|
||||
...selectedSkills.map(
|
||||
(skill) =>
|
||||
` <skill identifier="${escapeXml(skill.identifier)}" name="${escapeXml(skill.name)}" />`,
|
||||
),
|
||||
'</selected_skills>',
|
||||
];
|
||||
|
||||
return lines.join('\n');
|
||||
};
|
||||
|
||||
/**
|
||||
* Selected Skill Injector
|
||||
* Appends user-selected slash-menu skills to the last user message as ephemeral context.
|
||||
*/
|
||||
export class SelectedSkillInjector extends BaseLastUserContentProvider {
|
||||
readonly name = 'SelectedSkillInjector';
|
||||
|
||||
constructor(
|
||||
private config: SelectedSkillInjectorConfig,
|
||||
options: ProcessorOptions = {},
|
||||
) {
|
||||
super(options);
|
||||
}
|
||||
|
||||
protected async doProcess(context: PipelineContext): Promise<PipelineContext> {
|
||||
const clonedContext = this.cloneContext(context);
|
||||
const selectedSkills = this.config.selectedSkills ?? [];
|
||||
|
||||
if (selectedSkills.length === 0) {
|
||||
log('No selected skills, skipping injection');
|
||||
return this.markAsExecuted(clonedContext);
|
||||
}
|
||||
|
||||
const content = formatSelectedSkills(selectedSkills);
|
||||
|
||||
if (!content) {
|
||||
log('No selected skill content generated, skipping injection');
|
||||
return this.markAsExecuted(clonedContext);
|
||||
}
|
||||
|
||||
const lastUserIndex = this.findLastUserMessageIndex(clonedContext.messages);
|
||||
|
||||
if (lastUserIndex === -1) {
|
||||
log('No user messages found, skipping injection');
|
||||
return this.markAsExecuted(clonedContext);
|
||||
}
|
||||
|
||||
const hasExistingWrapper = this.hasExistingSystemContext(clonedContext);
|
||||
const contentToAppend = hasExistingWrapper
|
||||
? this.createContextBlock(content, 'selected_skill_context')
|
||||
: this.wrapWithSystemContext(content, 'selected_skill_context');
|
||||
|
||||
this.appendToLastUserMessage(clonedContext, contentToAppend);
|
||||
|
||||
clonedContext.metadata.selectedSkillContext = {
|
||||
injected: true,
|
||||
skillsCount: selectedSkills.length,
|
||||
};
|
||||
|
||||
log('Selected skill context appended, skills count: %d', selectedSkills.length);
|
||||
|
||||
return this.markAsExecuted(clonedContext);
|
||||
}
|
||||
}
|
||||
|
|
@ -4,6 +4,15 @@ import debug from 'debug';
|
|||
import { BaseProvider } from '../base/BaseProvider';
|
||||
import type { PipelineContext, ProcessorOptions } from '../types';
|
||||
|
||||
declare module '../types' {
|
||||
interface PipelineContextMetadataOverrides {
|
||||
skillContext?: {
|
||||
injected: boolean;
|
||||
skillsCount: number;
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
const log = debug('context-engine:provider:SkillContextProvider');
|
||||
|
||||
/**
|
||||
|
|
|
|||
|
|
@ -3,6 +3,12 @@ import debug from 'debug';
|
|||
import { BaseProvider } from '../base/BaseProvider';
|
||||
import type { PipelineContext, ProcessorOptions } from '../types';
|
||||
|
||||
declare module '../types' {
|
||||
interface PipelineContextMetadataOverrides {
|
||||
systemDateInjected?: boolean;
|
||||
}
|
||||
}
|
||||
|
||||
const log = debug('context-engine:provider:SystemDateProvider');
|
||||
|
||||
export interface SystemDateProviderConfig {
|
||||
|
|
|
|||
|
|
@ -3,6 +3,12 @@ import debug from 'debug';
|
|||
import { BaseProvider } from '../base/BaseProvider';
|
||||
import type { PipelineContext, ProcessorOptions } from '../types';
|
||||
|
||||
declare module '../types' {
|
||||
interface PipelineContextMetadataOverrides {
|
||||
systemRoleInjected?: boolean;
|
||||
}
|
||||
}
|
||||
|
||||
const log = debug('context-engine:provider:SystemRoleInjector');
|
||||
|
||||
export interface SystemRoleInjectorConfig {
|
||||
|
|
|
|||
|
|
@ -4,6 +4,15 @@ import debug from 'debug';
|
|||
import { BaseFirstUserContentProvider } from '../base/BaseFirstUserContentProvider';
|
||||
import type { PipelineContext, ProcessorOptions } from '../types';
|
||||
|
||||
declare module '../types' {
|
||||
interface PipelineContextMetadataOverrides {
|
||||
toolDiscoveryContext?: {
|
||||
injected: boolean;
|
||||
toolsCount: number;
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
const log = debug('context-engine:provider:ToolDiscoveryProvider');
|
||||
|
||||
export interface ToolDiscoveryMeta {
|
||||
|
|
|
|||
|
|
@ -7,6 +7,17 @@ import { ToolNameResolver } from '../engine/tools';
|
|||
import type { LobeToolManifest } from '../engine/tools/types';
|
||||
import type { PipelineContext, ProcessorOptions } from '../types';
|
||||
|
||||
declare module '../types' {
|
||||
interface PipelineContextMetadataOverrides {
|
||||
toolSystemRole?: {
|
||||
contentLength: number;
|
||||
injected: boolean;
|
||||
supportsFunctionCall: boolean;
|
||||
toolsCount: number;
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
const log = debug('context-engine:provider:ToolSystemRoleProvider');
|
||||
|
||||
/**
|
||||
|
|
@ -58,7 +69,7 @@ export class ToolSystemRoleProvider extends BaseProvider {
|
|||
clonedContext.metadata.toolSystemRole = {
|
||||
contentLength: toolSystemRole.length,
|
||||
injected: true,
|
||||
supportsFunctionCall: this.config.isCanUseFC(this.config.model, this.config.provider),
|
||||
supportsFunctionCall: !!this.config.isCanUseFC(this.config.model, this.config.provider),
|
||||
toolsCount: this.config.manifests.length,
|
||||
};
|
||||
|
||||
|
|
|
|||
|
|
@ -5,6 +5,12 @@ import debug from 'debug';
|
|||
import { BaseFirstUserContentProvider } from '../base/BaseFirstUserContentProvider';
|
||||
import { type PipelineContext, type ProcessorOptions } from '../types';
|
||||
|
||||
declare module '../types' {
|
||||
interface PipelineContextMetadataOverrides {
|
||||
userMemoryInjected?: boolean;
|
||||
}
|
||||
}
|
||||
|
||||
const log = debug('context-engine:provider:UserMemoryInjector');
|
||||
|
||||
export interface UserMemoryInjectorConfig {
|
||||
|
|
|
|||
|
|
@ -0,0 +1,205 @@
|
|||
import { describe, expect, it } from 'vitest';
|
||||
|
||||
import type { PipelineContext } from '../../types';
|
||||
import { AgentManagementContextInjector } from '../AgentManagementContextInjector';
|
||||
|
||||
describe('AgentManagementContextInjector', () => {
|
||||
const createContext = (messages: any[]): PipelineContext => ({
|
||||
initialState: { messages: [] },
|
||||
isAborted: false,
|
||||
messages,
|
||||
metadata: {},
|
||||
});
|
||||
|
||||
describe('disabled / no context', () => {
|
||||
it('should skip when disabled', async () => {
|
||||
const injector = new AgentManagementContextInjector({ enabled: false });
|
||||
const ctx = createContext([
|
||||
{ role: 'system', content: 'sys' },
|
||||
{ role: 'user', content: 'hi' },
|
||||
]);
|
||||
const result = await injector.process(ctx);
|
||||
expect(result.messages).toHaveLength(2);
|
||||
});
|
||||
|
||||
it('should skip when no context provided', async () => {
|
||||
const injector = new AgentManagementContextInjector({ enabled: true });
|
||||
const ctx = createContext([
|
||||
{ role: 'system', content: 'sys' },
|
||||
{ role: 'user', content: 'hi' },
|
||||
]);
|
||||
const result = await injector.process(ctx);
|
||||
expect(result.messages).toHaveLength(2);
|
||||
});
|
||||
});
|
||||
|
||||
describe('agent-management context (providers/plugins)', () => {
|
||||
it('should inject before the first user message', async () => {
|
||||
const injector = new AgentManagementContextInjector({
|
||||
enabled: true,
|
||||
context: {
|
||||
availableProviders: [
|
||||
{
|
||||
id: 'openai',
|
||||
name: 'OpenAI',
|
||||
models: [{ id: 'gpt-4', name: 'GPT-4' }],
|
||||
},
|
||||
],
|
||||
},
|
||||
});
|
||||
|
||||
const ctx = createContext([
|
||||
{ role: 'system', content: 'sys' },
|
||||
{ role: 'user', content: 'create an agent' },
|
||||
]);
|
||||
const result = await injector.process(ctx);
|
||||
|
||||
expect(result.messages).toHaveLength(3);
|
||||
expect(result.messages[0].role).toBe('system');
|
||||
// Injected context before user message
|
||||
expect(result.messages[1].role).toBe('user');
|
||||
expect(result.messages[1].content).toContain('<agent_management_context>');
|
||||
expect(result.messages[1].content).toContain('gpt-4');
|
||||
// Original user message
|
||||
expect(result.messages[2].content).toBe('create an agent');
|
||||
expect(result.metadata.agentManagementContextInjected).toBe(true);
|
||||
});
|
||||
});
|
||||
|
||||
describe('mentionedAgents delegation', () => {
|
||||
it('should inject delegation context after the last user message', async () => {
|
||||
const injector = new AgentManagementContextInjector({
|
||||
enabled: true,
|
||||
context: {
|
||||
mentionedAgents: [{ id: 'agt_designer', name: 'Designer Agent' }],
|
||||
},
|
||||
});
|
||||
|
||||
const ctx = createContext([
|
||||
{ role: 'system', content: 'sys' },
|
||||
{ role: 'user', content: 'Let @Designer Agent help me' },
|
||||
]);
|
||||
const result = await injector.process(ctx);
|
||||
|
||||
// system + user + injected delegation
|
||||
expect(result.messages).toHaveLength(3);
|
||||
expect(result.messages[1].content).toBe('Let @Designer Agent help me');
|
||||
|
||||
const delegationMsg = result.messages[2];
|
||||
expect(delegationMsg.role).toBe('user');
|
||||
expect(delegationMsg.content).toContain('<mentioned_agents>');
|
||||
expect(delegationMsg.content).toContain('agt_designer');
|
||||
expect(delegationMsg.content).toContain('Designer Agent');
|
||||
expect(delegationMsg.content).toContain('MUST use the callAgent tool');
|
||||
expect(delegationMsg.meta.injectType).toBe('agent-mention-delegation');
|
||||
});
|
||||
|
||||
it('should inject after the LAST user message, not the first', async () => {
|
||||
const injector = new AgentManagementContextInjector({
|
||||
enabled: true,
|
||||
context: {
|
||||
mentionedAgents: [{ id: 'agt_1', name: 'Agent A' }],
|
||||
},
|
||||
});
|
||||
|
||||
const ctx = createContext([
|
||||
{ role: 'system', content: 'sys' },
|
||||
{ role: 'user', content: 'first message' },
|
||||
{ role: 'assistant', content: 'reply' },
|
||||
{ role: 'user', content: 'Let @Agent A do this' },
|
||||
]);
|
||||
const result = await injector.process(ctx);
|
||||
|
||||
// system + user + assistant + user + injected
|
||||
expect(result.messages).toHaveLength(5);
|
||||
expect(result.messages[3].content).toBe('Let @Agent A do this');
|
||||
expect(result.messages[4].content).toContain('<mentioned_agents>');
|
||||
expect(result.messages[4].content).toContain('agt_1');
|
||||
});
|
||||
|
||||
it('should handle multiple mentioned agents', async () => {
|
||||
const injector = new AgentManagementContextInjector({
|
||||
enabled: true,
|
||||
context: {
|
||||
mentionedAgents: [
|
||||
{ id: 'agt_1', name: 'Agent A' },
|
||||
{ id: 'agt_2', name: 'Agent B' },
|
||||
],
|
||||
},
|
||||
});
|
||||
|
||||
const ctx = createContext([{ role: 'user', content: 'hello' }]);
|
||||
const result = await injector.process(ctx);
|
||||
|
||||
const delegationMsg = result.messages[1];
|
||||
expect(delegationMsg.content).toContain('agt_1');
|
||||
expect(delegationMsg.content).toContain('agt_2');
|
||||
expect(delegationMsg.content).toContain('Agent A');
|
||||
expect(delegationMsg.content).toContain('Agent B');
|
||||
});
|
||||
});
|
||||
|
||||
describe('combined: agent-management + mentionedAgents', () => {
|
||||
it('should inject providers before first user and delegation after last user', async () => {
|
||||
const injector = new AgentManagementContextInjector({
|
||||
enabled: true,
|
||||
context: {
|
||||
availableProviders: [
|
||||
{
|
||||
id: 'anthropic',
|
||||
name: 'Anthropic',
|
||||
models: [{ id: 'claude-sonnet-4-5-20250514', name: 'Claude Sonnet' }],
|
||||
},
|
||||
],
|
||||
mentionedAgents: [{ id: 'agt_dev', name: 'Developer' }],
|
||||
},
|
||||
});
|
||||
|
||||
const ctx = createContext([
|
||||
{ role: 'system', content: 'sys' },
|
||||
{ role: 'user', content: 'Let @Developer build this' },
|
||||
]);
|
||||
const result = await injector.process(ctx);
|
||||
|
||||
// system + management_context + user + delegation
|
||||
expect(result.messages).toHaveLength(4);
|
||||
|
||||
// Management context before first user
|
||||
expect(result.messages[1].content).toContain('<agent_management_context>');
|
||||
expect(result.messages[1].content).toContain('claude-sonnet-4-5-20250514');
|
||||
// Management context should NOT contain mentionedAgents
|
||||
expect(result.messages[1].content).not.toContain('<mentioned_agents>');
|
||||
|
||||
// Original user message
|
||||
expect(result.messages[2].content).toBe('Let @Developer build this');
|
||||
|
||||
// Delegation after last user
|
||||
expect(result.messages[3].content).toContain('<mentioned_agents>');
|
||||
expect(result.messages[3].content).toContain('agt_dev');
|
||||
});
|
||||
});
|
||||
|
||||
describe('only mentionedAgents (no providers/plugins)', () => {
|
||||
it('should NOT inject empty agent-management context but SHOULD inject delegation', async () => {
|
||||
const injector = new AgentManagementContextInjector({
|
||||
enabled: true,
|
||||
context: {
|
||||
// No providers, no plugins — only mentionedAgents
|
||||
mentionedAgents: [{ id: 'agt_x', name: 'Agent X' }],
|
||||
},
|
||||
});
|
||||
|
||||
const ctx = createContext([
|
||||
{ role: 'system', content: 'sys' },
|
||||
{ role: 'user', content: 'Ask @Agent X' },
|
||||
]);
|
||||
const result = await injector.process(ctx);
|
||||
|
||||
// system + user + delegation (no empty management context)
|
||||
expect(result.messages).toHaveLength(3);
|
||||
expect(result.messages[1].content).toBe('Ask @Agent X');
|
||||
expect(result.messages[2].content).toContain('<mentioned_agents>');
|
||||
expect(result.messages[2].content).toContain('agt_x');
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
@ -0,0 +1,76 @@
|
|||
import { describe, expect, it } from 'vitest';
|
||||
|
||||
import type { PipelineContext } from '../../types';
|
||||
import { SelectedSkillInjector } from '../SelectedSkillInjector';
|
||||
|
||||
const createContext = (messages: any[] = []): PipelineContext => ({
|
||||
initialState: {
|
||||
messages: [],
|
||||
model: 'gpt-4',
|
||||
provider: 'openai',
|
||||
},
|
||||
isAborted: false,
|
||||
messages,
|
||||
metadata: {
|
||||
maxTokens: 4096,
|
||||
model: 'gpt-4',
|
||||
},
|
||||
});
|
||||
|
||||
describe('SelectedSkillInjector', () => {
|
||||
it('should append selected skills to the last user message', async () => {
|
||||
const provider = new SelectedSkillInjector({
|
||||
selectedSkills: [
|
||||
{ identifier: 'user_memory', name: 'User Memory' },
|
||||
{ identifier: 'instruction', name: 'Instruction' },
|
||||
],
|
||||
});
|
||||
|
||||
const context = createContext([
|
||||
{ content: 'Earlier question', id: 'user-1', role: 'user' },
|
||||
{ content: 'Assistant reply', id: 'assistant-1', role: 'assistant' },
|
||||
{ content: 'Current request', id: 'user-2', role: 'user' },
|
||||
]);
|
||||
|
||||
const result = await provider.process(context);
|
||||
|
||||
expect(result.messages).toHaveLength(3);
|
||||
expect(result.messages[2].content).toContain('Current request');
|
||||
expect(result.messages[2].content).toContain('<selected_skill_context>');
|
||||
expect(result.messages[2].content).toContain('<selected_skills>');
|
||||
expect(result.messages[2].content).toContain(
|
||||
'<skill identifier="user_memory" name="User Memory" />',
|
||||
);
|
||||
expect(result.metadata.selectedSkillContext).toEqual({
|
||||
injected: true,
|
||||
skillsCount: 2,
|
||||
});
|
||||
});
|
||||
|
||||
it('should reuse existing system context wrapper on the last user message', async () => {
|
||||
const provider = new SelectedSkillInjector({
|
||||
selectedSkills: [{ identifier: 'user_memory', name: 'User Memory' }],
|
||||
});
|
||||
|
||||
const context = createContext([
|
||||
{
|
||||
content: `Current request
|
||||
|
||||
<!-- SYSTEM CONTEXT (NOT PART OF USER QUERY) -->
|
||||
<current_page_context>
|
||||
<page>draft</page>
|
||||
</current_page_context>
|
||||
<!-- END SYSTEM CONTEXT -->`,
|
||||
id: 'user-1',
|
||||
role: 'user',
|
||||
},
|
||||
]);
|
||||
|
||||
const result = await provider.process(context);
|
||||
const content = result.messages[0].content as string;
|
||||
|
||||
expect(content.match(/<!-- SYSTEM CONTEXT \(NOT PART OF USER QUERY\) -->/g)).toHaveLength(1);
|
||||
expect(content).toContain('<current_page_context>');
|
||||
expect(content).toContain('<selected_skill_context>');
|
||||
});
|
||||
});
|
||||
|
|
@ -44,8 +44,8 @@ describe('ToolSystemRoleProvider', () => {
|
|||
|
||||
// Should update metadata
|
||||
expect(result.metadata.toolSystemRole).toBeDefined();
|
||||
expect(result.metadata.toolSystemRole.injected).toBe(true);
|
||||
expect(result.metadata.toolSystemRole.supportsFunctionCall).toBe(true);
|
||||
expect(result.metadata.toolSystemRole!.injected).toBe(true);
|
||||
expect(result.metadata.toolSystemRole!.supportsFunctionCall).toBe(true);
|
||||
});
|
||||
|
||||
it('should merge tool system role with existing system message', async () => {
|
||||
|
|
|
|||
|
|
@ -12,6 +12,7 @@ export { HistorySummaryProvider } from './HistorySummary';
|
|||
export { KnowledgeInjector } from './KnowledgeInjector';
|
||||
export { PageEditorContextInjector } from './PageEditorContextInjector';
|
||||
export { PageSelectionsInjector } from './PageSelectionsInjector';
|
||||
export { SelectedSkillInjector } from './SelectedSkillInjector';
|
||||
export { SkillContextProvider } from './SkillContextProvider';
|
||||
export { SystemDateProvider } from './SystemDateProvider';
|
||||
export { SystemRoleInjector } from './SystemRoleInjector';
|
||||
|
|
@ -51,6 +52,7 @@ export type { HistorySummaryConfig } from './HistorySummary';
|
|||
export type { KnowledgeInjectorConfig } from './KnowledgeInjector';
|
||||
export type { PageEditorContextInjectorConfig } from './PageEditorContextInjector';
|
||||
export type { PageSelectionsInjectorConfig } from './PageSelectionsInjector';
|
||||
export type { SelectedSkillInjectorConfig } from './SelectedSkillInjector';
|
||||
export type { SkillContextProviderConfig, SkillMeta } from './SkillContextProvider';
|
||||
export type { SystemDateProviderConfig } from './SystemDateProvider';
|
||||
export type { SystemRoleInjectorConfig } from './SystemRoleInjector';
|
||||
|
|
|
|||
|
|
@ -1,5 +1,17 @@
|
|||
import type { UIChatMessage } from '@lobechat/types';
|
||||
|
||||
/**
|
||||
* Consumer-side metadata extensions for PipelineContext.metadata.
|
||||
*
|
||||
* Example:
|
||||
* declare module '@lobechat/context-engine' {
|
||||
* interface PipelineContextMetadataOverrides {
|
||||
* myCustomFlag?: boolean;
|
||||
* }
|
||||
* }
|
||||
*/
|
||||
export interface PipelineContextMetadataOverrides {}
|
||||
|
||||
/**
|
||||
* Agent state - inferred from original project types
|
||||
*/
|
||||
|
|
@ -39,6 +51,19 @@ export interface Message {
|
|||
role: string;
|
||||
}
|
||||
|
||||
/**
|
||||
* Metadata shared across pipeline processors.
|
||||
* Consumers can extend this through declaration merging on
|
||||
* `LobeChatContextEngine.PipelineContextMetadataOverrides`.
|
||||
*/
|
||||
export interface PipelineContextMetadata extends PipelineContextMetadataOverrides {
|
||||
[key: `${string}InjectedCount`]: number | undefined;
|
||||
currentTokenCount?: number;
|
||||
maxTokens?: number;
|
||||
model?: string;
|
||||
provider?: string;
|
||||
}
|
||||
|
||||
/**
|
||||
* Pipeline context - core data structure flowing through the pipeline
|
||||
*/
|
||||
|
|
@ -55,16 +80,7 @@ export interface PipelineContext {
|
|||
/** Mutable message list being built */
|
||||
messages: Message[];
|
||||
/** Metadata for communication between processors */
|
||||
metadata: {
|
||||
/** Other custom metadata */
|
||||
[key: string]: any;
|
||||
/** Current token count estimate */
|
||||
currentTokenCount?: number;
|
||||
/** Maximum token limit */
|
||||
maxTokens?: number;
|
||||
/** Model identifier */
|
||||
model?: string;
|
||||
};
|
||||
metadata: PipelineContextMetadata;
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -98,7 +114,7 @@ export interface PipelineResult {
|
|||
/** Final processed messages */
|
||||
messages: any[];
|
||||
/** Metadata from processing */
|
||||
metadata: Record<string, any>;
|
||||
metadata: PipelineContextMetadata;
|
||||
/** Execution statistics */
|
||||
stats: {
|
||||
/** Number of processors processed */
|
||||
|
|
|
|||
|
|
@ -217,6 +217,7 @@ export class MessageModel {
|
|||
id: messages.id,
|
||||
role: messages.role,
|
||||
content: messages.content,
|
||||
editorData: messages.editorData,
|
||||
reasoning: messages.reasoning,
|
||||
search: messages.search,
|
||||
metadata: messages.metadata,
|
||||
|
|
@ -555,6 +556,7 @@ export class MessageModel {
|
|||
id: messages.id,
|
||||
role: messages.role,
|
||||
content: messages.content,
|
||||
editorData: messages.editorData,
|
||||
reasoning: messages.reasoning,
|
||||
search: messages.search,
|
||||
metadata: messages.metadata,
|
||||
|
|
|
|||
|
|
@ -1,2 +1,3 @@
|
|||
export * from './crawlResults';
|
||||
export * from './searchResults';
|
||||
export * from './xmlEscape';
|
||||
|
|
|
|||
|
|
@ -1,3 +1,17 @@
|
|||
/**
|
||||
* Escape all XML special characters (safe for both attributes and content)
|
||||
* Includes: & < > " '
|
||||
*/
|
||||
export const escapeXml = (text: string | undefined | null): string => {
|
||||
if (!text) return '';
|
||||
return text
|
||||
.replaceAll('&', '&')
|
||||
.replaceAll('<', '<')
|
||||
.replaceAll('>', '>')
|
||||
.replaceAll('"', '"')
|
||||
.replaceAll("'", ''');
|
||||
};
|
||||
|
||||
/**
|
||||
* Escape special characters for XML attributes
|
||||
* Includes: & " < >
|
||||
|
|
|
|||
|
|
@ -1,6 +1,9 @@
|
|||
import { z } from 'zod';
|
||||
|
||||
import type { MessageMetadata } from './message/common';
|
||||
import { ChatToolPayloadSchema, MessageMetadataSchema } from './message/common';
|
||||
import type { UIChatMessage } from './message';
|
||||
import type { CreateMessageParams } from './message/ui/params';
|
||||
import type { PageSelection } from './message/ui/params';
|
||||
import { PageSelectionSchema } from './message/ui/params';
|
||||
import type { OpenAIChatMessage } from './openai/chat';
|
||||
|
|
@ -12,6 +15,8 @@ import { ThreadType } from './topic/thread';
|
|||
|
||||
export interface SendNewMessage {
|
||||
content: string;
|
||||
/** Lexical editor JSON state for rich text rendering */
|
||||
editorData?: Record<string, any>;
|
||||
// if message has attached with files, then add files to message and the agent
|
||||
files?: string[];
|
||||
/** Page selections attached to this message (for Ask AI functionality) */
|
||||
|
|
@ -19,6 +24,15 @@ export interface SendNewMessage {
|
|||
parentId?: string;
|
||||
}
|
||||
|
||||
export interface SendPreloadMessage
|
||||
extends Omit<
|
||||
Pick<CreateMessageParams, 'content' | 'metadata' | 'plugin' | 'tool_call_id' | 'tools'>,
|
||||
'metadata'
|
||||
> {
|
||||
metadata?: MessageMetadata;
|
||||
role: 'assistant' | 'tool';
|
||||
}
|
||||
|
||||
/**
|
||||
* Parameters for creating a new thread along with message
|
||||
*/
|
||||
|
|
@ -57,6 +71,7 @@ export interface SendMessageServerParams {
|
|||
title?: string;
|
||||
topicMessageIds?: string[];
|
||||
};
|
||||
preloadMessages?: SendPreloadMessage[];
|
||||
newUserMessage: SendNewMessage;
|
||||
sessionId?: string;
|
||||
threadId?: string;
|
||||
|
|
@ -71,6 +86,22 @@ export const CreateThreadWithMessageSchema = z.object({
|
|||
type: z.enum([ThreadType.Continuation, ThreadType.Standalone, ThreadType.Isolation]),
|
||||
});
|
||||
|
||||
const SendPreloadMessageSchema = z.object({
|
||||
content: z.string(),
|
||||
metadata: MessageMetadataSchema.optional(),
|
||||
plugin: z
|
||||
.object({
|
||||
apiName: z.string(),
|
||||
arguments: z.string(),
|
||||
identifier: z.string(),
|
||||
type: z.string(),
|
||||
})
|
||||
.optional(),
|
||||
role: z.enum(['assistant', 'tool']),
|
||||
tool_call_id: z.string().optional(),
|
||||
tools: z.array(ChatToolPayloadSchema).optional(),
|
||||
});
|
||||
|
||||
export const AiSendMessageServerSchema = z.object({
|
||||
agentId: z.string().optional(),
|
||||
groupId: z.string().optional(),
|
||||
|
|
@ -86,8 +117,10 @@ export const AiSendMessageServerSchema = z.object({
|
|||
topicMessageIds: z.array(z.string()).optional(),
|
||||
})
|
||||
.optional(),
|
||||
preloadMessages: z.array(SendPreloadMessageSchema).optional(),
|
||||
newUserMessage: z.object({
|
||||
content: z.string(),
|
||||
editorData: z.record(z.unknown()).optional(),
|
||||
files: z.array(z.string()).optional(),
|
||||
pageSelections: z.array(PageSelectionSchema).optional(),
|
||||
parentId: z.string().optional(),
|
||||
|
|
|
|||
|
|
@ -91,6 +91,7 @@ export interface NewMessage {
|
|||
|
||||
export interface UpdateMessageParams {
|
||||
content?: string;
|
||||
editorData?: Record<string, any> | null;
|
||||
error?: ChatMessageError | null;
|
||||
imageList?: ChatImageItem[];
|
||||
metadata?: MessageMetadata;
|
||||
|
|
@ -117,6 +118,7 @@ export interface NewMessageQueryParams {
|
|||
export const UpdateMessageParamsSchema = z
|
||||
.object({
|
||||
content: z.string().optional(),
|
||||
editorData: z.record(z.any()).nullable().optional(),
|
||||
error: ChatMessageErrorSchema.nullable().optional(),
|
||||
imageList: z.array(ChatImageItemSchema).optional(),
|
||||
metadata: MessageMetadataSchema.optional(),
|
||||
|
|
|
|||
|
|
@ -113,6 +113,8 @@ export interface UIChatMessage {
|
|||
compressedMessages?: UIChatMessage[];
|
||||
content: string;
|
||||
createdAt: number;
|
||||
/** Lexical editor JSON state for rich text rendering */
|
||||
editorData?: Record<string, any> | null;
|
||||
error?: ChatMessageError | null;
|
||||
// Extended fields
|
||||
extra?: ChatMessageExtra;
|
||||
|
|
|
|||
Some files were not shown because too many files have changed in this diff Show more
Loading…
Reference in a new issue